a tool for shared writing and social publishing
at feature/publish-leaflets 168 lines 5.0 kB view raw
1import { Doc, applyUpdate, XmlElement, XmlHook, XmlText } from "yjs"; 2import { nodes, marks } from "prosemirror-schema-basic"; 3import { CSSProperties, Fragment } from "react"; 4import { theme } from "tailwind.config"; 5import * as base64 from "base64-js"; 6 7type BlockElements = "h1" | "h2" | "h3" | null | "blockquote" | "p"; 8export function RenderYJSFragment({ 9 value, 10 wrapper, 11 attrs, 12}: { 13 value: string; 14 wrapper: BlockElements; 15 attrs?: { [k: string]: any }; 16}) { 17 if (!value) 18 return <BlockWrapper wrapper={wrapper} attrs={attrs}></BlockWrapper>; 19 let doc = new Doc(); 20 const update = base64.toByteArray(value); 21 applyUpdate(doc, update); 22 let [node] = doc.getXmlElement("prosemirror").toArray(); 23 if (node.constructor === XmlElement) { 24 switch (node.nodeName as keyof typeof nodes) { 25 case "paragraph": { 26 let children = node.toArray(); 27 return ( 28 <BlockWrapper wrapper={wrapper} attrs={attrs}> 29 {children.length === 0 ? ( 30 <br /> 31 ) : ( 32 node.toArray().map((node, index) => { 33 if (node.constructor === XmlText) { 34 let deltas = node.toDelta() as Delta[]; 35 if (deltas.length === 0) return <br key={index} />; 36 return ( 37 <Fragment key={index}> 38 {deltas.map((d, index) => { 39 if (d.attributes?.link) 40 return ( 41 <a 42 href={d.attributes.link.href} 43 key={index} 44 {...attributesToStyle(d)} 45 > 46 {d.insert} 47 </a> 48 ); 49 return ( 50 <span 51 key={index} 52 {...attributesToStyle(d)} 53 {...attrs} 54 > 55 {d.insert} 56 </span> 57 ); 58 })} 59 </Fragment> 60 ); 61 } 62 63 if (node.constructor === XmlElement && node.nodeName === "hard_break") { 64 return <br key={index} />; 65 } 66 67 return null; 68 }) 69 )} 70 </BlockWrapper> 71 ); 72 } 73 case "hard_break": 74 return <div />; 75 default: 76 return null; 77 } 78 } 79 return <br />; 80} 81 82const BlockWrapper = (props: { 83 wrapper: BlockElements; 84 children?: React.ReactNode; 85 attrs?: { [k: string]: any }; 86}) => { 87 if (props.wrapper === null && props.children === null) return <br />; 88 if (props.wrapper === null) return <>{props.children}</>; 89 switch (props.wrapper) { 90 case "p": 91 return <p {...props.attrs}>{props.children}</p>; 92 case "blockquote": 93 return <blockquote {...props.attrs}>{props.children}</blockquote>; 94 95 case "h1": 96 return <h1 {...props.attrs}>{props.children}</h1>; 97 case "h2": 98 return <h2 {...props.attrs}>{props.children}</h2>; 99 case "h3": 100 return <h3 {...props.attrs}>{props.children}</h3>; 101 } 102}; 103 104export type Delta = { 105 insert: string; 106 attributes?: { 107 strong?: {}; 108 code?: {}; 109 em?: {}; 110 underline?: {}; 111 strikethrough?: {}; 112 highlight?: { color: string }; 113 link?: { href: string }; 114 }; 115}; 116 117function attributesToStyle(d: Delta) { 118 let props = { 119 style: {}, 120 className: "", 121 } as { style: CSSProperties; className: string } & { 122 [s: `data-${string}`]: any; 123 }; 124 125 if (d.attributes?.code) props.className += " inline-code"; 126 if (d.attributes?.strong) props.style.fontWeight = "700"; 127 if (d.attributes?.em) props.style.fontStyle = "italic"; 128 if (d.attributes?.underline) props.style.textDecoration = "underline"; 129 if (d.attributes?.strikethrough) { 130 (props.style.textDecoration = "line-through"), 131 (props.style.textDecorationColor = theme.colors.tertiary); 132 } 133 if (d.attributes?.highlight) { 134 props.className += " highlight"; 135 props["data-color"] = d.attributes.highlight.color; 136 props.style.backgroundColor = 137 d.attributes?.highlight.color === "1" 138 ? theme.colors["highlight-1"] 139 : d.attributes.highlight.color === "2" 140 ? theme.colors["highlight-2"] 141 : theme.colors["highlight-3"]; 142 } 143 144 return props; 145} 146 147export function YJSFragmentToString( 148 node: XmlElement | XmlText | XmlHook, 149): string { 150 if (node.constructor === XmlElement) { 151 // Handle hard_break nodes specially 152 if (node.nodeName === "hard_break") { 153 return "\n"; 154 } 155 return node 156 .toArray() 157 .map((f) => YJSFragmentToString(f)) 158 .join(""); 159 } 160 if (node.constructor === XmlText) { 161 return (node.toDelta() as Delta[]) 162 .map((d) => { 163 return d.insert; 164 }) 165 .join(""); 166 } 167 return ""; 168}