a tool for shared writing and social publishing

make copying buttons and alignment work

+64 -16
+9 -6
components/Blocks/TextBlock/RenderYJSFragment.tsx
··· 6 6 export function RenderYJSFragment({ 7 7 node, 8 8 wrapper, 9 + attrs, 9 10 }: { 10 11 node: XmlElement | XmlText | XmlHook; 11 12 wrapper?: "h1" | "h2" | "h3" | null; 13 + attrs?: { [k: string]: any }; 12 14 }) { 13 15 if (node.constructor === XmlElement) { 14 16 switch (node.nodeName as keyof typeof nodes) { 15 17 case "paragraph": { 16 18 let children = node.toArray(); 17 19 return ( 18 - <BlockWrapper wrapper={wrapper}> 20 + <BlockWrapper wrapper={wrapper} attrs={attrs}> 19 21 {children.length === 0 ? ( 20 22 <br /> 21 23 ) : ( ··· 49 51 </a> 50 52 ); 51 53 return ( 52 - <span key={index} {...attributesToStyle(d)}> 54 + <span key={index} {...attributesToStyle(d)} {...attrs}> 53 55 {d.insert} 54 56 </span> 55 57 ); ··· 63 65 const BlockWrapper = (props: { 64 66 wrapper?: "h1" | "h2" | "h3" | null; 65 67 children: React.ReactNode; 68 + attrs?: { [k: string]: any }; 66 69 }) => { 67 70 if (props.wrapper === null) return <>{props.children}</>; 68 - if (!props.wrapper) return <p>{props.children}</p>; 71 + if (!props.wrapper) return <p {...props.attrs}>{props.children}</p>; 69 72 switch (props.wrapper) { 70 73 case "h1": 71 - return <h1>{props.children}</h1>; 74 + return <h1 {...props.attrs}>{props.children}</h1>; 72 75 case "h2": 73 - return <h2>{props.children}</h2>; 76 + return <h2 {...props.attrs}>{props.children}</h2>; 74 77 case "h3": 75 - return <h3>{props.children}</h3>; 78 + return <h3 {...props.attrs}>{props.children}</h3>; 76 79 } 77 80 }; 78 81
+34 -9
components/Blocks/TextBlock/useHandlePaste.ts
··· 54 54 let xml = new DOMParser().parseFromString(textHTML, "text/html"); 55 55 let currentPosition = propsRef.current.position; 56 56 let children = flattenHTMLToTextBlocks(xml.body); 57 - if ( 58 - children.find((c) => 59 - ["P", "H1", "H2", "H3", "UL", "DIV", "SPAN", "IMG"].includes( 60 - c.tagName, 61 - ), 62 - ) && 63 - !(children.length === 1 && children[0].tagName === "IMG") 64 - ) { 57 + if (!(children.length === 1 && children[0].tagName === "IMG")) { 65 58 children.forEach((child, index) => { 66 59 createBlockFromHTML(child, { 67 60 parentType: propsRef.current.pageType, ··· 243 236 }); 244 237 } 245 238 } 239 + let alignment = child.getAttribute("data-alignment"); 240 + if (alignment && ["right", "left", "center"].includes(alignment)) { 241 + rep.mutate.assertFact({ 242 + entity: entityID, 243 + attribute: "block/text-alignment", 244 + data: { 245 + type: "text-alignment-type-union", 246 + value: alignment as "right" | "left" | "center", 247 + }, 248 + }); 249 + } 246 250 if (child.tagName === "A") { 247 251 let href = child.getAttribute("href"); 252 + let dataType = child.getAttribute("data-type"); 248 253 if (href) { 249 - addLinkBlock(href, entityID, rep); 254 + if (dataType === "button") { 255 + rep.mutate.assertFact([ 256 + { 257 + entity: entityID, 258 + attribute: "block/type", 259 + data: { type: "block-type-union", value: "button" }, 260 + }, 261 + { 262 + entity: entityID, 263 + attribute: "button/text", 264 + data: { type: "string", value: child.textContent || "" }, 265 + }, 266 + { 267 + entity: entityID, 268 + attribute: "button/url", 269 + data: { type: "string", value: href }, 270 + }, 271 + ]); 272 + } else { 273 + addLinkBlock(href, entityID, rep); 274 + } 250 275 } 251 276 } 252 277 if (child.tagName === "IMG") {
+21 -1
src/utils/getBlocksAsHTML.tsx
··· 74 74 ignoreWrapper?: boolean, 75 75 ) { 76 76 let wrapper: undefined | "h1" | "h2" | "h3"; 77 + let [alignment] = await scanIndex(tx).eav(b.value, "block/text-alignment"); 77 78 if (b.type === "image") { 78 79 let [src] = await scanIndex(tx).eav(b.value, "block/image"); 79 80 if (!src) return ""; 80 - return renderToStaticMarkup(<img src={src.data.src} />); 81 + return renderToStaticMarkup( 82 + <img src={src.data.src} data-alignment={alignment?.data.value} />, 83 + ); 84 + } 85 + if (b.type === "button") { 86 + let [text] = await scanIndex(tx).eav(b.value, "button/text"); 87 + let [url] = await scanIndex(tx).eav(b.value, "button/url"); 88 + if (!text || !url) return ""; 89 + return renderToStaticMarkup( 90 + <a 91 + href={url.data.value} 92 + data-type="button" 93 + data-alignment={alignment?.data.value} 94 + > 95 + {text.data.value} 96 + </a>, 97 + ); 81 98 } 82 99 if (b.type === "heading") { 83 100 let headingLevel = ··· 125 142 let nodes = doc.getXmlElement("prosemirror").toArray(); 126 143 return renderToStaticMarkup( 127 144 <RenderYJSFragment 145 + attrs={{ 146 + "data-alignment": alignment?.data.value, 147 + }} 128 148 node={nodes[0]} 129 149 wrapper={ignoreWrapper ? null : wrapper} 130 150 />,