a tool for shared writing and social publishing

implement single line blockquote

+41 -73
+1
components/Blocks/Block.tsx
··· 104 104 ? "pb-0" 105 105 : "pb-2" 106 106 } 107 + ${props.type === "blockquote" && props.previousBlock?.type === "blockquote" ? "-mt-3" : ""} 107 108 ${ 108 109 !props.previousBlock 109 110 ? props.type === "heading" || props.type === "text"
+10 -11
components/Blocks/BlockCommands.tsx
··· 31 31 import { ListUnorderedSmall } from "components/Toolbar/ListToolbar"; 32 32 import { BlockMathSmall } from "components/Icons/BlockMathSmall"; 33 33 import { BlockCodeSmall } from "components/Icons/BlockCodeSmall"; 34 - import { QuoteTiny } from "components/Icons/QuoteTiny"; 35 34 import { QuoteSmall } from "components/Icons/QuoteSmall"; 36 35 37 36 type Props = { ··· 160 159 clearCommandSearchText(entity); 161 160 }, 162 161 }, 163 - // { 164 - // name: "Block Quote", 165 - // icon: <QuoteSmall />, 166 - // type: "text", 167 - // onSelect: async (rep, props, um) => { 168 - // if (props.entityID) clearCommandSearchText(props.entityID); 169 - // let entity = await createBlockWithType(rep, props, "blockquote"); 170 - // clearCommandSearchText(entity); 171 - // }, 172 - // }, 162 + { 163 + name: "Block Quote", 164 + icon: <QuoteSmall />, 165 + type: "text", 166 + onSelect: async (rep, props, um) => { 167 + if (props.entityID) clearCommandSearchText(props.entityID); 168 + let entity = await createBlockWithType(rep, props, "blockquote"); 169 + clearCommandSearchText(entity); 170 + }, 171 + }, 173 172 174 173 { 175 174 name: "Image",
+5 -2
components/Blocks/TextBlock/RenderYJSFragment.tsx
··· 9 9 attrs, 10 10 }: { 11 11 node: XmlElement | XmlText | XmlHook; 12 - wrapper?: "h1" | "h2" | "h3" | null; 12 + wrapper?: "h1" | "h2" | "h3" | null | "blockquote"; 13 13 attrs?: { [k: string]: any }; 14 14 }) { 15 15 if (node.constructor === XmlElement) { ··· 63 63 } 64 64 65 65 const BlockWrapper = (props: { 66 - wrapper?: "h1" | "h2" | "h3" | null; 66 + wrapper?: "h1" | "h2" | "h3" | null | "blockquote"; 67 67 children: React.ReactNode; 68 68 attrs?: { [k: string]: any }; 69 69 }) => { 70 70 if (props.wrapper === null) return <>{props.children}</>; 71 71 if (!props.wrapper) return <p {...props.attrs}>{props.children}</p>; 72 72 switch (props.wrapper) { 73 + case "blockquote": 74 + return <blockquote {...props.attrs}>{props.children}</blockquote>; 75 + 73 76 case "h1": 74 77 return <h1 {...props.attrs}>{props.children}</h1>; 75 78 case "h2":
+3 -8
components/Blocks/TextBlock/index.tsx
··· 218 218 let handlePaste = useHandlePaste(props.entityID, propsRef); 219 219 useLayoutEffect(() => { 220 220 if (!mountRef.current) return; 221 - let km = TextBlockKeymap( 222 - propsRef, 223 - repRef, 224 - rep.undoManager, 225 - props.type === "blockquote", 226 - ); 221 + let km = TextBlockKeymap(propsRef, repRef, rep.undoManager); 227 222 let editor = EditorState.create({ 228 - schema: props.type === "blockquote" ? multiBlockSchema : schema, 223 + schema: schema, 229 224 plugins: [ 230 225 ySyncPlugin(value), 231 226 keymap(km), ··· 342 337 }, 343 338 })); 344 339 }; 345 - }, [props.entityID, props.parent, value, handlePaste, rep, props.type]); 340 + }, [props.entityID, props.parent, value, handlePaste, rep]); 346 341 347 342 return ( 348 343 <>
+11 -11
components/Blocks/TextBlock/inputRules.ts
··· 152 152 return tr; 153 153 }), 154 154 155 - // //Blockquote 156 - // new InputRule(/^([>]{1})\s$/, (state, match) => { 157 - // let tr = state.tr; 158 - // tr.delete(0, 2); 159 - // repRef.current?.mutate.assertFact({ 160 - // entity: propsRef.current.entityID, 161 - // attribute: "block/type", 162 - // data: { type: "block-type-union", value: "blockquote" }, 163 - // }); 164 - // return tr; 165 - // }), 155 + //Blockquote 156 + new InputRule(/^([>]{1})\s$/, (state, match) => { 157 + let tr = state.tr; 158 + tr.delete(0, 2); 159 + repRef.current?.mutate.assertFact({ 160 + entity: propsRef.current.entityID, 161 + attribute: "block/type", 162 + data: { type: "block-type-union", value: "blockquote" }, 163 + }); 164 + return tr; 165 + }), 166 166 167 167 //Header 168 168 new InputRule(/^([#]{1,3})\s$/, (state, match) => {
+2 -7
components/Blocks/TextBlock/useHandlePaste.ts
··· 254 254 default: 255 255 type = null; 256 256 } 257 - let content = 258 - type === "blockquote" ? multilineParser.parse(child) : parser.parse(child); 257 + let content = parser.parse(child); 259 258 if (!type) return; 260 259 261 260 let entityID: string; ··· 498 497 block.editor.selection.to !== undefined 499 498 ) 500 499 tr.delete(block.editor.selection.from, block.editor.selection.to); 501 - if (type === "blockquote") { 502 - tr.replaceWith(0, tr.doc.content.size, content.content); 503 - } else { 504 - tr.replaceSelectionWith(content); 505 - } 500 + tr.replaceSelectionWith(content); 506 501 let newState = block.editor.apply(tr); 507 502 setEditorState(entityID, { 508 503 editor: newState,
+3 -8
src/utils/focusBlock.ts
··· 104 104 } 105 105 } 106 106 107 - if (block.type === "blockquote" && position.type === "start") { 108 - let sel = NodeSelection.create(tr.doc, 0); 109 - nextBlock.view.dispatch(tr.setSelection(sel)); 110 - } else { 111 - nextBlock.view.dispatch( 112 - tr.setSelection(TextSelection.create(tr.doc, pos?.pos || 1)), 113 - ); 114 - } 107 + nextBlock.view.dispatch( 108 + tr.setSelection(TextSelection.create(tr.doc, pos?.pos || 1)), 109 + ); 115 110 nextBlock.view.focus(); 116 111 } 117 112
+5 -26
src/utils/getBlocksAsHTML.tsx
··· 74 74 tx: ReadTransaction, 75 75 ignoreWrapper?: boolean, 76 76 ) { 77 - let wrapper: undefined | "h1" | "h2" | "h3"; 77 + let wrapper: undefined | "h1" | "h2" | "h3" | "blockquote"; 78 78 let [alignment] = await scanIndex(tx).eav(b.value, "block/text-alignment"); 79 79 if (b.type === "horizontal-rule") { 80 80 return "<hr />"; ··· 125 125 </a>, 126 126 ); 127 127 } 128 + if (b.type === "blockquote") { 129 + wrapper = "blockquote"; 130 + } 128 131 if (b.type === "heading") { 129 132 let headingLevel = 130 133 (await scanIndex(tx).eav(b.value, "block/heading-level"))[0]?.data ··· 162 165 </div>, 163 166 ); 164 167 } 165 - if (b.type === "blockquote") { 166 - let value = (await scanIndex(tx).eav(b.value, "block/text"))[0]; 167 - if (!value) return "<blockquote></blockquote>"; 168 - let doc = new Y.Doc(); 169 - const update = base64.toByteArray(value.data.value); 170 - Y.applyUpdate(doc, update); 171 - let nodes = doc.getXmlElement("prosemirror").toArray(); 172 - //Have to handle this specially because it's a multi-line block 173 - return `<blockquote>${nodes 174 - .map((node) => { 175 - if (node.constructor === Y.XmlElement) { 176 - let children = node.toArray(); 177 - if (children.length === 0) return "<p></p>"; 178 - return renderToStaticMarkup( 179 - <RenderYJSFragment 180 - attrs={{ 181 - "data-alignment": alignment?.data.value, 182 - }} 183 - node={node} 184 - />, 185 - ); 186 - } 187 - }) 188 - .join("\n")}</blockquote>`; 189 - } 190 168 let value = (await scanIndex(tx).eav(b.value, "block/text"))[0]; 169 + console.log("getBlockasHTML", value); 191 170 if (!value) 192 171 return ignoreWrapper ? "" : `<${wrapper || "p"}></${wrapper || "p"}>`; 193 172 let doc = new Y.Doc();
+1
src/utils/isTextBlock.ts
··· 5 5 } = { 6 6 text: true, 7 7 heading: true, 8 + blockquote: true, 8 9 };