a tool for shared writing and social publishing
0
fork

Configure Feed

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

add headings!!

+99 -13
+4 -2
app/globals.css
··· 16 16 17 17 html, 18 18 body { 19 - @apply bg-bg-page; 20 19 @apply h-full; 21 20 @apply p-0; 22 - @apply text-primary; 23 21 @apply overscroll-y-none; 24 22 min-height: -webkit-fill-available; 25 23 @apply font-sans; ··· 65 63 66 64 pre { 67 65 font-family: "Quattro" !important; 66 + } 67 + 68 + p { 69 + font-size: inherit; 68 70 } 69 71 70 72 ::placeholder {
+27 -4
components/Blocks.tsx
··· 219 219 nextPosition: string | null; 220 220 } & Block; 221 221 222 + let textBlocks: { [k in Fact<"block/type">["data"]["value"]]?: boolean } = { 223 + text: true, 224 + heading: true, 225 + }; 226 + 222 227 function Block(props: BlockProps) { 223 228 let selected = useUIState( 224 229 (s) => 225 - (props.type !== "text" || s.selectedBlock.length > 1) && 230 + (!textBlocks[props.type] || s.selectedBlock.length > 1) && 226 231 s.selectedBlock.includes(props.entityID), 227 232 ); 228 233 let { rep } = useReplicache(); ··· 247 252 if (!block) return; 248 253 } 249 254 if (e.key === "Backspace") { 250 - if (props.type === "text") return; 255 + if (textBlocks[props.type]) return; 251 256 e.preventDefault(); 252 257 r.mutate.removeBlock({ blockEntity: props.entityID }); 253 258 let block = props.previousBlock; ··· 314 319 {props.type === "card" ? ( 315 320 <CardBlock {...props} /> 316 321 ) : props.type === "text" ? ( 317 - <TextBlock {...props} /> 322 + <TextBlock {...props} className="" /> 323 + ) : props.type === "heading" ? ( 324 + <HeadingBlock {...props} /> 318 325 ) : props.type === "image" ? ( 319 326 <ImageBlock {...props} /> 320 327 ) : null} ··· 322 329 ); 323 330 } 324 331 332 + const HeadingStyle = { 333 + 1: "text-2xl font-bold", 334 + 2: "text-xl font-bold", 335 + 3: "text-lg font-bold", 336 + 4: "text-base font-bold italic", 337 + } as { [level: number]: string }; 338 + export function HeadingBlock(props: BlockProps) { 339 + let headingLevel = useEntity(props.entityID, "block/heading-level"); 340 + return ( 341 + <TextBlock 342 + {...props} 343 + className={HeadingStyle[headingLevel?.data.value || 1]} 344 + /> 345 + ); 346 + } 347 + 325 348 export function focusBlock( 326 - block: Block, 349 + block: Omit<Block, "position">, 327 350 left: number | "end" | "start", 328 351 top: "top" | "bottom", 329 352 ) {
+16 -6
components/TextBlock/index.tsx
··· 57 57 }); 58 58 }; 59 59 60 - export function TextBlock(props: BlockProps) { 60 + export function TextBlock(props: BlockProps & { className: string }) { 61 61 let initialized = useInitialPageLoad(); 62 62 return ( 63 63 <> 64 - {!initialized && <RenderedTextBlock entityID={props.entityID} />} 64 + {!initialized && ( 65 + <RenderedTextBlock 66 + entityID={props.entityID} 67 + className={props.className} 68 + /> 69 + )} 65 70 <div className={`relative group/text ${!initialized ? "hidden" : ""}`}> 66 71 <BaseTextBlock {...props} /> 67 72 </div> ··· 69 74 ); 70 75 } 71 76 72 - export function RenderedTextBlock(props: { entityID: string }) { 77 + export function RenderedTextBlock(props: { 78 + entityID: string; 79 + className: string; 80 + }) { 73 81 let initialFact = useEntity(props.entityID, "block/text"); 74 82 if (!initialFact) return <pre className="min-h-6" />; 75 83 let doc = new Y.Doc(); 76 84 const update = base64.toByteArray(initialFact.data.value); 77 85 Y.applyUpdate(doc, update); 78 86 return ( 79 - <pre className="w-full whitespace-pre-wrap outline-none min-h-6"> 87 + <pre 88 + className={`w-full whitespace-pre-wrap outline-none min-h-6 ${props.className}`} 89 + > 80 90 {doc 81 91 .getXmlElement("prosemirror") 82 92 .toArray() ··· 86 96 </pre> 87 97 ); 88 98 } 89 - export function BaseTextBlock(props: BlockProps) { 99 + export function BaseTextBlock(props: BlockProps & { className: string }) { 90 100 const [mount, setMount] = useState<HTMLElement | null>(null); 91 101 let selected = useUIState((s) => s.selectedBlock.includes(props.entityID)); 92 102 let [value, factID] = useYJSValue(props.entityID); ··· 219 229 }} 220 230 onPaste={async (e) => {}} 221 231 id={elementId.block(props.entityID).text} 222 - className={`textBlock w-full p-0 border-none outline-none resize-none align-top bg-transparent whitespace-pre-wrap`} 232 + className={`textBlock w-full p-0 border-none outline-none resize-none align-top bg-transparent whitespace-pre-wrap ${props.className}`} 223 233 ref={setMount} 224 234 /> 225 235
+17
components/TextBlock/keymap.ts
··· 17 17 keymap({ 18 18 "Meta-b": toggleMark(schema.marks.strong), 19 19 "Meta-i": toggleMark(schema.marks.em), 20 + "#": (state, dispatch, view) => { 21 + if (state.selection.content().size > 0) return false; 22 + if (state.selection.anchor > 1) return false; 23 + repRef.current?.mutate.increaseHeadingLevel({ 24 + entityID: propsRef.current.entityID, 25 + }); 26 + setTimeout( 27 + () => 28 + focusBlock( 29 + { value: propsRef.current.entityID, type: "heading" }, 30 + "start", 31 + "bottom", 32 + ), 33 + 10, 34 + ); 35 + return true; 36 + }, 20 37 ArrowUp: (_state, _tr, view) => { 21 38 if (!view) return false; 22 39 const viewClientRect = view.dom.getBoundingClientRect();
+5 -1
src/replicache/attributes.ts
··· 18 18 type: "text", 19 19 cardinality: "one", 20 20 }, 21 + "block/heading-level": { 22 + type: "number", 23 + cardinality: "one", 24 + }, 21 25 "block/image": { 22 26 type: "image", 23 27 cardinality: "one", ··· 91 95 reference: { type: "reference"; value: string }; 92 96 "block-type-union": { 93 97 type: "block-type-union"; 94 - value: "text" | "image" | "card"; 98 + value: "text" | "image" | "card" | "heading"; 95 99 }; 96 100 color: { type: "color"; value: string }; 97 101 }[(typeof Attributes)[A]["type"]];
+30
src/replicache/mutations.ts
··· 79 79 await ctx.assertFact(args); 80 80 }; 81 81 82 + const increaseHeadingLevel: Mutation<{ entityID: string }> = async ( 83 + args, 84 + ctx, 85 + ) => { 86 + let blockType = (await ctx.scanIndex.eav(args.entityID, "block/type"))[0]; 87 + let headinglevel = ( 88 + await ctx.scanIndex.eav(args.entityID, "block/heading-level") 89 + )[0]; 90 + if (blockType?.data.value !== "heading") 91 + await ctx.assertFact({ 92 + entity: args.entityID, 93 + attribute: "block/type", 94 + data: { type: "block-type-union", value: "heading" }, 95 + }); 96 + if (!headinglevel) 97 + await ctx.assertFact({ 98 + entity: args.entityID, 99 + attribute: "block/heading-level", 100 + data: { type: "number", value: 1 }, 101 + }); 102 + else if (headinglevel?.data.value === 4) return; 103 + else 104 + return await ctx.assertFact({ 105 + entity: args.entityID, 106 + attribute: "block/heading-level", 107 + data: { type: "number", value: headinglevel.data.value + 1 }, 108 + }); 109 + }; 110 + 82 111 export const mutations = { 83 112 addBlock, 84 113 assertFact, 85 114 retractFact, 86 115 removeBlock, 116 + increaseHeadingLevel, 87 117 };