a tool for shared writing and social publishing

handle header sizes and base size for fonts

+51 -97
+6 -4
app/lish/[did]/[publication]/[rkey]/PostContent.tsx
··· 34 import { PublishedPollBlock } from "./Blocks/PublishedPollBlock"; 35 import { PollData } from "./fetchPollData"; 36 import { ButtonPrimary } from "components/Buttons"; 37 38 export function PostContent({ 39 blocks, ··· 346 case PubLeafletBlocksText.isMain(b.block): 347 return ( 348 <p 349 className={`textBlock ${className} ${b.block.textSize === "small" ? "text-sm text-secondary" : b.block.textSize === "large" ? "text-lg" : ""}`} 350 {...blockProps} 351 > ··· 362 case PubLeafletBlocksHeader.isMain(b.block): { 363 if (b.block.level === 1) 364 return ( 365 - <h2 className={`h1Block ${className}`} {...blockProps}> 366 <TextBlock 367 {...b.block} 368 index={index} ··· 373 ); 374 if (b.block.level === 2) 375 return ( 376 - <h3 className={`h2Block ${className}`} {...blockProps}> 377 <TextBlock 378 {...b.block} 379 index={index} ··· 384 ); 385 if (b.block.level === 3) 386 return ( 387 - <h4 className={`h3Block ${className}`} {...blockProps}> 388 <TextBlock 389 {...b.block} 390 index={index} ··· 396 // if (b.block.level === 4) return <h4>{b.block.plaintext}</h4>; 397 // if (b.block.level === 5) return <h5>{b.block.plaintext}</h5>; 398 return ( 399 - <h6 className={`h6Block ${className}`} {...blockProps}> 400 <TextBlock 401 {...b.block} 402 index={index}
··· 34 import { PublishedPollBlock } from "./Blocks/PublishedPollBlock"; 35 import { PollData } from "./fetchPollData"; 36 import { ButtonPrimary } from "components/Buttons"; 37 + import { blockTextSize } from "src/utils/blockTextSize"; 38 39 export function PostContent({ 40 blocks, ··· 347 case PubLeafletBlocksText.isMain(b.block): 348 return ( 349 <p 350 + style={{ fontSize: blockTextSize.p }} 351 className={`textBlock ${className} ${b.block.textSize === "small" ? "text-sm text-secondary" : b.block.textSize === "large" ? "text-lg" : ""}`} 352 {...blockProps} 353 > ··· 364 case PubLeafletBlocksHeader.isMain(b.block): { 365 if (b.block.level === 1) 366 return ( 367 + <h2 style={{ fontSize: blockTextSize.h1 }} className={`h1Block ${className}`} {...blockProps}> 368 <TextBlock 369 {...b.block} 370 index={index} ··· 375 ); 376 if (b.block.level === 2) 377 return ( 378 + <h3 style={{ fontSize: blockTextSize.h2 }} className={`h2Block ${className}`} {...blockProps}> 379 <TextBlock 380 {...b.block} 381 index={index} ··· 386 ); 387 if (b.block.level === 3) 388 return ( 389 + <h4 style={{ fontSize: blockTextSize.h3 }} className={`h3Block ${className}`} {...blockProps}> 390 <TextBlock 391 {...b.block} 392 index={index} ··· 398 // if (b.block.level === 4) return <h4>{b.block.plaintext}</h4>; 399 // if (b.block.level === 5) return <h5>{b.block.plaintext}</h5>; 400 return ( 401 + <h6 style={{ fontSize: blockTextSize.h4 }} className={`h6Block ${className}`} {...blockProps}> 402 <TextBlock 403 {...b.block} 404 index={index}
+6 -5
app/lish/[did]/[publication]/[rkey]/StaticPostContent.tsx
··· 12 PubLeafletPagesLinearDocument, 13 } from "lexicons/api"; 14 import { blobRefToSrc } from "src/utils/blobRefToSrc"; 15 import { TextBlockCore, TextBlockCoreProps } from "./Blocks/TextBlockCore"; 16 import { StaticMathBlock } from "./Blocks/StaticMathBlock"; 17 import { codeToHtml, bundledLanguagesInfo, bundledThemesInfo } from "shiki"; ··· 119 } 120 case PubLeafletBlocksText.isMain(b.block): 121 return ( 122 - <p> 123 <StaticBaseTextBlock 124 facets={b.block.facets} 125 plaintext={b.block.plaintext} ··· 130 case PubLeafletBlocksHeader.isMain(b.block): { 131 if (b.block.level === 1) 132 return ( 133 - <h1> 134 <StaticBaseTextBlock {...b.block} index={[]} /> 135 </h1> 136 ); 137 if (b.block.level === 2) 138 return ( 139 - <h2> 140 <StaticBaseTextBlock {...b.block} index={[]} /> 141 </h2> 142 ); 143 if (b.block.level === 3) 144 return ( 145 - <h3> 146 <StaticBaseTextBlock {...b.block} index={[]} /> 147 </h3> 148 ); 149 // if (b.block.level === 4) return <h4>{b.block.plaintext}</h4>; 150 // if (b.block.level === 5) return <h5>{b.block.plaintext}</h5>; 151 return ( 152 - <h6> 153 <StaticBaseTextBlock {...b.block} index={[]} /> 154 </h6> 155 );
··· 12 PubLeafletPagesLinearDocument, 13 } from "lexicons/api"; 14 import { blobRefToSrc } from "src/utils/blobRefToSrc"; 15 + import { blockTextSize } from "src/utils/blockTextSize"; 16 import { TextBlockCore, TextBlockCoreProps } from "./Blocks/TextBlockCore"; 17 import { StaticMathBlock } from "./Blocks/StaticMathBlock"; 18 import { codeToHtml, bundledLanguagesInfo, bundledThemesInfo } from "shiki"; ··· 120 } 121 case PubLeafletBlocksText.isMain(b.block): 122 return ( 123 + <p style={{ fontSize: blockTextSize.p }}> 124 <StaticBaseTextBlock 125 facets={b.block.facets} 126 plaintext={b.block.plaintext} ··· 131 case PubLeafletBlocksHeader.isMain(b.block): { 132 if (b.block.level === 1) 133 return ( 134 + <h1 style={{ fontSize: blockTextSize.h1 }}> 135 <StaticBaseTextBlock {...b.block} index={[]} /> 136 </h1> 137 ); 138 if (b.block.level === 2) 139 return ( 140 + <h2 style={{ fontSize: blockTextSize.h2 }}> 141 <StaticBaseTextBlock {...b.block} index={[]} /> 142 </h2> 143 ); 144 if (b.block.level === 3) 145 return ( 146 + <h3 style={{ fontSize: blockTextSize.h3 }}> 147 <StaticBaseTextBlock {...b.block} index={[]} /> 148 </h3> 149 ); 150 // if (b.block.level === 4) return <h4>{b.block.plaintext}</h4>; 151 // if (b.block.level === 5) return <h5>{b.block.plaintext}</h5>; 152 return ( 153 + <h6 style={{ fontSize: blockTextSize.h4 }}> 154 <StaticBaseTextBlock {...b.block} index={[]} /> 155 </h6> 156 );
+20 -5
components/Blocks/TextBlock/index.tsx
··· 25 import { DotLoader } from "components/utils/DotLoader"; 26 import { useMountProsemirror } from "./mountProsemirror"; 27 import { schema } from "./schema"; 28 29 import { Mention, MentionAutocomplete } from "components/Mention"; 30 import { addMentionToEditor } from "app/[leaflet_id]/publish/BskyPostEditorProsemirror"; 31 32 const HeadingStyle = { 33 - 1: "text-xl font-bold [font-family:var(--theme-heading-font)]", 34 - 2: "text-lg font-bold [font-family:var(--theme-heading-font)]", 35 - 3: "text-base font-bold text-secondary [font-family:var(--theme-heading-font)]", 36 } as { [level: number]: string }; 37 38 export function TextBlock( ··· 162 } 163 return ( 164 <div 165 - style={{ wordBreak: "break-word" }} // better than tailwind break-all! 166 className={` 167 ${alignmentClass} 168 ${props.type === "blockquote" ? (props.previousBlock?.type === "blockquote" ? `blockquote pt-3 ` : "blockquote") : ""} ··· 266 // unless we break *only* on urls, this is better than tailwind 'break-all' 267 // b/c break-all can cause breaks in the middle of words, but break-word still 268 // forces break if a single text string (e.g. a url) spans more than a full line 269 - style={{ wordBreak: "break-word", fontFamily: props.type === "heading" ? "var(--theme-heading-font)" : "var(--theme-font)" }} 270 className={` 271 ${alignmentClass} 272 grow resize-none align-top whitespace-pre-wrap bg-transparent ··· 290 props.nextBlock === null ? ( 291 // if this is the only block on the page and is empty or is a canvas, show placeholder 292 <div 293 className={`${props.className} ${alignmentClass} w-full pointer-events-none absolute top-0 left-0 italic text-tertiary flex flex-col 294 ${props.type === "heading" ? HeadingStyle[headingLevel?.data.value || 1] : textStyle} 295 `}
··· 25 import { DotLoader } from "components/utils/DotLoader"; 26 import { useMountProsemirror } from "./mountProsemirror"; 27 import { schema } from "./schema"; 28 + import { blockTextSize } from "src/utils/blockTextSize"; 29 30 import { Mention, MentionAutocomplete } from "components/Mention"; 31 import { addMentionToEditor } from "app/[leaflet_id]/publish/BskyPostEditorProsemirror"; 32 33 const HeadingStyle = { 34 + 1: "font-bold [font-family:var(--theme-heading-font)]", 35 + 2: "font-bold [font-family:var(--theme-heading-font)]", 36 + 3: "font-bold text-secondary [font-family:var(--theme-heading-font)]", 37 + } as { [level: number]: string }; 38 + 39 + const headingFontSize = { 40 + 1: blockTextSize.h1, 41 + 2: blockTextSize.h2, 42 + 3: blockTextSize.h3, 43 } as { [level: number]: string }; 44 45 export function TextBlock( ··· 169 } 170 return ( 171 <div 172 + style={{ 173 + wordBreak: "break-word", 174 + ...(props.type === "heading" ? { fontSize: headingFontSize[headingLevel?.data.value || 1] } : {}), 175 + }} 176 className={` 177 ${alignmentClass} 178 ${props.type === "blockquote" ? (props.previousBlock?.type === "blockquote" ? `blockquote pt-3 ` : "blockquote") : ""} ··· 276 // unless we break *only* on urls, this is better than tailwind 'break-all' 277 // b/c break-all can cause breaks in the middle of words, but break-word still 278 // forces break if a single text string (e.g. a url) spans more than a full line 279 + style={{ 280 + wordBreak: "break-word", 281 + fontFamily: props.type === "heading" ? "var(--theme-heading-font)" : "var(--theme-font)", 282 + ...(props.type === "heading" ? { fontSize: headingFontSize[headingLevel?.data.value || 1] } : {}), 283 + }} 284 className={` 285 ${alignmentClass} 286 grow resize-none align-top whitespace-pre-wrap bg-transparent ··· 304 props.nextBlock === null ? ( 305 // if this is the only block on the page and is empty or is a canvas, show placeholder 306 <div 307 + style={props.type === "heading" ? { fontSize: headingFontSize[headingLevel?.data.value || 1] } : undefined} 308 className={`${props.className} ${alignmentClass} w-full pointer-events-none absolute top-0 left-0 italic text-tertiary flex flex-col 309 ${props.type === "heading" ? HeadingStyle[headingLevel?.data.value || 1] : textStyle} 310 `}
+12 -83
src/fonts.ts
··· 6 displayName: string; 7 fontFamily: string; 8 fallback: string[]; 9 } & ( 10 | { 11 // Self-hosted fonts with local files ··· 33 id: "quattro", 34 displayName: "iA Writer Quattro", 35 fontFamily: "iA Writer Quattro V", 36 type: "local", 37 files: [ 38 { ··· 52 id: "lora", 53 displayName: "Lora", 54 fontFamily: "Lora", 55 type: "local", 56 files: [ 57 { ··· 67 ], 68 fallback: ["Georgia", "serif"], 69 }, 70 - "source-sans": { 71 - id: "source-sans", 72 - displayName: "Source Sans", 73 - fontFamily: "Source Sans 3", 74 - type: "local", 75 - files: [ 76 - { 77 - path: "/fonts/SourceSans3-Variable.woff2", 78 - style: "normal", 79 - weight: "200 900", 80 - }, 81 - { 82 - path: "/fonts/SourceSans3-Italic-Variable.woff2", 83 - style: "italic", 84 - weight: "200 900", 85 - }, 86 - ], 87 - fallback: ["system-ui", "sans-serif"], 88 - }, 89 "atkinson-hyperlegible": { 90 id: "atkinson-hyperlegible", 91 displayName: "Atkinson Hyperlegible", 92 fontFamily: "Atkinson Hyperlegible Next", 93 type: "google", 94 googleFontsFamily: 95 "Atkinson+Hyperlegible+Next:ital,wght@0,200..800;1,200..800", 96 fallback: ["system-ui", "sans-serif"], 97 }, 98 - "space-mono": { 99 - id: "space-mono", 100 - displayName: "Space Mono", 101 - fontFamily: "Space Mono", 102 - type: "google", 103 - googleFontsFamily: "Space+Mono:ital,wght@0,400;0,700;1,400;1,700", 104 - fallback: ["monospace"], 105 - }, 106 - 107 // Additional Google Fonts - Mono 108 "sometype-mono": { 109 id: "sometype-mono", 110 displayName: "Sometype Mono", 111 fontFamily: "Sometype Mono", 112 type: "google", 113 googleFontsFamily: "Sometype+Mono:ital,wght@0,400;0,700;1,400;1,700", 114 fallback: ["monospace"], 115 }, 116 117 // Additional Google Fonts - Sans 118 - "pt-sans": { 119 - id: "pt-sans", 120 - displayName: "PT Sans", 121 - fontFamily: "PT Sans", 122 - type: "google", 123 - googleFontsFamily: "PT+Sans:ital,wght@0,400;0,700;1,400;1,700", 124 - fallback: ["system-ui", "sans-serif"], 125 - }, 126 montserrat: { 127 id: "montserrat", 128 displayName: "Montserrat", 129 fontFamily: "Montserrat", 130 type: "google", 131 googleFontsFamily: "Montserrat:ital,wght@0,400;0,700;1,400;1,700", 132 fallback: ["system-ui", "sans-serif"], 133 }, 134 - "alegreya-sans": { 135 - id: "alegreya-sans", 136 - displayName: "Alegreya Sans", 137 - fontFamily: "Alegreya Sans", 138 type: "google", 139 - googleFontsFamily: "Alegreya+Sans:ital,wght@0,400;0,700;1,400;1,700", 140 fallback: ["system-ui", "sans-serif"], 141 - }, 142 - 143 - // Additional Google Fonts - Serif 144 - "pt-serif": { 145 - id: "pt-serif", 146 - displayName: "PT Serif", 147 - fontFamily: "PT Serif", 148 - type: "google", 149 - googleFontsFamily: "PT+Serif:ital,wght@0,400;0,700;1,400;1,700", 150 - fallback: ["Georgia", "serif"], 151 - }, 152 - "crimson-text": { 153 - id: "crimson-text", 154 - displayName: "Crimson Text", 155 - fontFamily: "Crimson Text", 156 - type: "google", 157 - googleFontsFamily: "Crimson+Text:ital,wght@0,400;0,700;1,400;1,700", 158 - fallback: ["Georgia", "serif"], 159 - }, 160 - cardo: { 161 - id: "cardo", 162 - displayName: "Cardo", 163 - fontFamily: "Cardo", 164 - type: "google", 165 - googleFontsFamily: "Cardo:ital,wght@0,400;0,700;1,400", 166 - fallback: ["Georgia", "serif"], 167 - }, 168 - "ibm-plex-serif": { 169 - id: "ibm-plex-serif", 170 - displayName: "IBM Plex Serif", 171 - fontFamily: "IBM Plex Serif", 172 - type: "google", 173 - googleFontsFamily: "IBM+Plex+Serif:ital,wght@0,400;0,700;1,400;1,700", 174 - fallback: ["Georgia", "serif"], 175 - }, 176 - "source-serif": { 177 - id: "source-serif", 178 - displayName: "Source Serif 4", 179 - fontFamily: "Source Serif 4", 180 - type: "google", 181 - googleFontsFamily: "Source+Serif+4:ital,wght@0,400;0,700;1,400;1,700", 182 - fallback: ["Georgia", "serif"], 183 }, 184 }; 185
··· 6 displayName: string; 7 fontFamily: string; 8 fallback: string[]; 9 + baseSize?: number; // base font size in px for document content 10 } & ( 11 | { 12 // Self-hosted fonts with local files ··· 34 id: "quattro", 35 displayName: "iA Writer Quattro", 36 fontFamily: "iA Writer Quattro V", 37 + baseSize: 16, 38 type: "local", 39 files: [ 40 { ··· 54 id: "lora", 55 displayName: "Lora", 56 fontFamily: "Lora", 57 + baseSize: 17, 58 type: "local", 59 files: [ 60 { ··· 70 ], 71 fallback: ["Georgia", "serif"], 72 }, 73 "atkinson-hyperlegible": { 74 id: "atkinson-hyperlegible", 75 displayName: "Atkinson Hyperlegible", 76 fontFamily: "Atkinson Hyperlegible Next", 77 + baseSize: 18, 78 type: "google", 79 googleFontsFamily: 80 "Atkinson+Hyperlegible+Next:ital,wght@0,200..800;1,200..800", 81 fallback: ["system-ui", "sans-serif"], 82 }, 83 // Additional Google Fonts - Mono 84 "sometype-mono": { 85 id: "sometype-mono", 86 displayName: "Sometype Mono", 87 fontFamily: "Sometype Mono", 88 + baseSize: 17, 89 type: "google", 90 googleFontsFamily: "Sometype+Mono:ital,wght@0,400;0,700;1,400;1,700", 91 fallback: ["monospace"], 92 }, 93 94 // Additional Google Fonts - Sans 95 montserrat: { 96 id: "montserrat", 97 displayName: "Montserrat", 98 fontFamily: "Montserrat", 99 + baseSize: 17, 100 type: "google", 101 googleFontsFamily: "Montserrat:ital,wght@0,400;0,700;1,400;1,700", 102 fallback: ["system-ui", "sans-serif"], 103 }, 104 + "source-sans": { 105 + id: "source-sans", 106 + displayName: "Source Sans 3", 107 + fontFamily: "Source Sans 3", 108 + baseSize: 18, 109 type: "google", 110 + googleFontsFamily: "Source+Sans+3:ital,wght@0,400;0,700;1,400;1,700", 111 fallback: ["system-ui", "sans-serif"], 112 }, 113 }; 114
+7
src/utils/blockTextSize.ts
···
··· 1 + export const blockTextSize = { 2 + p: "1em", 3 + h1: "2em", 4 + h2: "1.5em", 5 + h3: "1.25em", 6 + h4: "1.125em", 7 + } as const;