a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

new styling on @mentions and adding profile popover on hover

celine a20acde0 c606e52c

+56 -32
+22 -4
app/globals.css
··· 296 296 @apply py-[1.5px]; 297 297 } 298 298 299 - /* Underline mention nodes when selected in ProseMirror */ 299 + .ProseMirror:focus-within .selection-highlight { 300 + background-color: transparent; 301 + } 302 + 300 303 .ProseMirror .atMention.ProseMirror-selectednode, 301 304 .ProseMirror .didMention.ProseMirror-selectednode { 302 - text-decoration: underline; 305 + @apply text-accent-contrast; 306 + @apply px-0.5; 307 + @apply -mx-[3px]; /* extra px to account for the border*/ 308 + @apply -my-px; /*to account for the border*/ 309 + @apply rounded-[4px]; 310 + @apply box-decoration-clone; 311 + background-color: rgba(var(--accent-contrast), 0.2); 312 + border: 1px solid rgba(var(--accent-contrast), 1); 303 313 } 304 314 305 - .ProseMirror:focus-within .selection-highlight { 306 - background-color: transparent; 315 + .mention { 316 + @apply cursor-pointer; 317 + @apply text-accent-contrast; 318 + @apply px-0.5; 319 + @apply -mx-[3px]; 320 + @apply -my-px; /*to account for the border*/ 321 + @apply rounded-[4px]; 322 + @apply box-decoration-clone; 323 + background-color: rgba(var(--accent-contrast), 0.2); 324 + border: 1px solid transparent; 307 325 } 308 326 309 327 .multiselected:focus-within .selection-highlight {
+10 -14
app/lish/[did]/[publication]/[rkey]/BaseTextBlock.tsx
··· 2 2 import { PubLeafletRichtextFacet } from "lexicons/api"; 3 3 import { didToBlueskyUrl } from "src/utils/mentionUtils"; 4 4 import { AtMentionLink } from "components/AtMentionLink"; 5 + import { ProfilePopover } from "components/ProfilePopover"; 5 6 6 7 type Facet = PubLeafletRichtextFacet.Main; 7 8 export function BaseTextBlock(props: { ··· 27 28 let isDidMention = segment.facet?.find( 28 29 PubLeafletRichtextFacet.isDidMention, 29 30 ); 30 - let isAtMention = segment.facet?.find( 31 - PubLeafletRichtextFacet.isAtMention, 32 - ); 31 + let isAtMention = segment.facet?.find(PubLeafletRichtextFacet.isAtMention); 33 32 let isUnderline = segment.facet?.find(PubLeafletRichtextFacet.isUnderline); 34 33 let isItalic = segment.facet?.find(PubLeafletRichtextFacet.isItalic); 35 34 let isHighlighted = segment.facet?.find( ··· 45 44 ${isHighlighted ? "highlight bg-highlight-1" : ""}`.replaceAll("\n", " "); 46 45 47 46 // Split text by newlines and insert <br> tags 48 - const textParts = segment.text.split('\n'); 47 + const textParts = segment.text.split("\n"); 49 48 const renderedText = textParts.flatMap((part, i) => 50 - i < textParts.length - 1 ? [part, <br key={`br-${counter}-${i}`} />] : [part] 49 + i < textParts.length - 1 50 + ? [part, <br key={`br-${counter}-${i}`} />] 51 + : [part], 51 52 ); 52 53 53 54 if (isCode) { ··· 58 59 ); 59 60 } else if (isDidMention) { 60 61 children.push( 61 - <a 62 - key={counter} 63 - href={didToBlueskyUrl(isDidMention.did)} 64 - className={`text-accent-contrast hover:underline cursor-pointer ${className}`} 65 - target="_blank" 66 - rel="noopener noreferrer" 67 - > 68 - {renderedText} 69 - </a>, 62 + <ProfilePopover 63 + didOrHandle={isDidMention.did} 64 + trigger={<span className="mention">{renderedText}</span>} 65 + />, 70 66 ); 71 67 } else if (isAtMention) { 72 68 children.push(
+2 -2
app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx
··· 119 119 {props.postTitle ? props.postTitle : "Untitled"} 120 120 </h2> 121 121 {props.postDescription ? ( 122 - <p className="postDescription italic text-secondary outline-hidden bg-transparent pt-1"> 122 + <div className="postDescription italic text-secondary outline-hidden bg-transparent pt-1"> 123 123 {props.postDescription} 124 - </p> 124 + </div> 125 125 ) : null} 126 126 <div className="postInfo text-sm text-tertiary pt-3 flex gap-1 flex-wrap justify-between"> 127 127 {props.postInfo}
+2 -2
components/AtMentionLink.tsx
··· 24 24 isPublication || isDocument ? ( 25 25 <img 26 26 src={`/api/pub_icon?at_uri=${encodeURIComponent(atURI)}`} 27 - className="inline-block w-5 h-5 rounded-full mr-1 align-text-top" 27 + className="inline-block w-4 h-4 rounded-full mr-1 mt-[3px] align-text-top" 28 28 alt="" 29 29 width="20" 30 30 height="20" ··· 37 37 href={atUriToUrl(atURI)} 38 38 target="_blank" 39 39 rel="noopener noreferrer" 40 - className={`text-accent-contrast hover:underline cursor-pointer ${isPublication ? "font-bold" : ""} ${isDocument ? "italic" : ""} ${className}`} 40 + className={`mention ${isPublication ? "font-bold" : ""} ${isDocument ? "italic" : ""} ${className}`} 41 41 > 42 42 {icon} 43 43 {children}
+14 -5
components/Blocks/TextBlock/RenderYJSFragment.tsx
··· 6 6 import { didToBlueskyUrl } from "src/utils/mentionUtils"; 7 7 import { AtMentionLink } from "components/AtMentionLink"; 8 8 import { Delta } from "src/utils/yjsFragmentToString"; 9 + import { ProfilePopover } from "components/ProfilePopover"; 9 10 10 11 type BlockElements = "h1" | "h2" | "h3" | null | "blockquote" | "p"; 11 12 export function RenderYJSFragment({ ··· 63 64 ); 64 65 } 65 66 66 - if (node.constructor === XmlElement && node.nodeName === "hard_break") { 67 + if ( 68 + node.constructor === XmlElement && 69 + node.nodeName === "hard_break" 70 + ) { 67 71 return <br key={index} />; 68 72 } 69 73 70 74 // Handle didMention inline nodes 71 - if (node.constructor === XmlElement && node.nodeName === "didMention") { 75 + if ( 76 + node.constructor === XmlElement && 77 + node.nodeName === "didMention" 78 + ) { 72 79 const did = node.getAttribute("did") || ""; 73 80 const text = node.getAttribute("text") || ""; 74 81 return ( ··· 77 84 target="_blank" 78 85 rel="noopener noreferrer" 79 86 key={index} 80 - className="text-accent-contrast hover:underline cursor-pointer" 87 + className="mention" 81 88 > 82 89 {text} 83 90 </a> ··· 85 92 } 86 93 87 94 // Handle atMention inline nodes 88 - if (node.constructor === XmlElement && node.nodeName === "atMention") { 95 + if ( 96 + node.constructor === XmlElement && 97 + node.nodeName === "atMention" 98 + ) { 89 99 const atURI = node.getAttribute("atURI") || ""; 90 100 const text = node.getAttribute("text") || ""; 91 101 return ( ··· 161 171 162 172 return props; 163 173 } 164 -
+4 -3
components/Blocks/TextBlock/schema.ts
··· 147 147 toDOM(node) { 148 148 // NOTE: This rendering should match the AtMentionLink component in 149 149 // components/AtMentionLink.tsx. If you update one, update the other. 150 - let className = "atMention text-accent-contrast"; 150 + let className = "atMention mention"; 151 151 let aturi = new AtUri(node.attrs.atURI); 152 152 if (aturi.collection === "pub.leaflet.publication") 153 153 className += " font-bold"; ··· 168 168 "img", 169 169 { 170 170 src: `/api/pub_icon?at_uri=${encodeURIComponent(node.attrs.atURI)}`, 171 - class: "inline-block w-5 h-5 rounded-full mr-1 align-text-top", 171 + class: 172 + "inline-block w-4 h-4 rounded-full mt-[3px] mr-1 align-text-top", 172 173 alt: "", 173 174 width: "16", 174 175 height: "16", ··· 214 215 return [ 215 216 "span", 216 217 { 217 - class: "didMention text-accent-contrast", 218 + class: "didMention mention", 218 219 "data-did": node.attrs.did, 219 220 }, 220 221 node.attrs.text,
+1 -1
components/ProfilePopover.tsx
··· 45 45 </div> 46 46 </div> 47 47 ) : ( 48 - <div className="text-secondary p-4">Profile not found</div> 48 + <div className="text-secondary py-2 px-4">Profile not found</div> 49 49 )} 50 50 </Tooltip> 51 51 );
+1 -1
components/Tooltip.tsx
··· 26 26 props.skipDelayDuration ? props.skipDelayDuration : 300 27 27 } 28 28 > 29 - <RadixTooltip.Root> 29 + <RadixTooltip.Root onOpenChange={props.onOpenChange}> 30 30 <RadixTooltip.Trigger disabled={props.disabled} asChild={props.asChild}> 31 31 {props.trigger} 32 32 </RadixTooltip.Trigger>