Advertisement
Guest User

Untitled

a guest
Sep 16th, 2019
138
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.83 KB | None | 0 0
  1. import React from "react"
  2. import ReactDOMServer from "react-dom/server"
  3.  
  4. import globals from "./globals"
  5.  
  6. import "./NewNoteV02.css"
  7.  
  8. function H1(props) {
  9. const x1 = "# ".length
  10. if (props.renderMarkup) {
  11. return (
  12. <h1 className={props.renderSemanticClasses ? "header-1" : null}>
  13. {props.renderIndents && "\n\t\t"}
  14. {props.children.slice(x1)}
  15. {props.renderIndents && "\n\t"}
  16. </h1>
  17. )
  18. }
  19. return (
  20. <h1 className="fw:700 c:gray-900">
  21. {!props.readOnly && <span className="c:gray-900">{props.children.slice(0, x1)}</span>}
  22. {props.children.slice(x1)}
  23. </h1>
  24. )
  25. }
  26.  
  27. function H2(props) {
  28. const x1 = "## ".length
  29. if (props.renderMarkup) {
  30. return (
  31. <h2 className={props.renderSemanticClasses ? "header-2" : null}>
  32. {props.renderIndents && "\n\t\t"}
  33. {props.children.slice(x1)}
  34. {props.renderIndents && "\n\t"}
  35. </h2>
  36. )
  37. }
  38. return (
  39. <h2 className="fw:700 c:gray-900">
  40. {!props.readOnly && <span className="c:gray-800">{props.children.slice(0, x1)}</span>}
  41. {props.children.slice(x1)}
  42. </h2>
  43. )
  44. }
  45.  
  46. function H3(props) {
  47. const x1 = "### ".length
  48. if (props.renderMarkup) {
  49. return (
  50. <h3 className={props.renderSemanticClasses ? "header-3" : null}>
  51. {props.renderIndents && "\n\t\t"}
  52. {props.children.slice(x1)}
  53. {props.renderIndents && "\n\t"}
  54. </h3>
  55. )
  56. }
  57. return (
  58. <h3 className="fw:700 c:gray-900">
  59. {!props.readOnly && <span className="c:gray-700">{props.children.slice(0, x1)}</span>}
  60. {props.children.slice(x1)}
  61. </h3>
  62. )
  63. }
  64.  
  65. function H4(props) {
  66. const x1 = "#### ".length
  67. if (props.renderMarkup) {
  68. return (
  69. <h4 className={props.renderSemanticClasses ? "header-4" : null}>
  70. {props.renderIndents && "\n\t\t"}
  71. {props.children.slice(x1)}
  72. {props.renderIndents && "\n\t"}
  73. </h4>
  74. )
  75. }
  76. return (
  77. <h4 className="fw:700 c:gray-900">
  78. {!props.readOnly && <span className="c:gray-600">{props.children.slice(0, x1)}</span>}
  79. {props.children.slice(x1)}
  80. </h4>
  81. )
  82. }
  83.  
  84. function H5(props) {
  85. const x1 = "##### ".length
  86. if (props.renderMarkup) {
  87. return (
  88. <h5 className={props.renderSemanticClasses ? "header-5" : null}>
  89. {props.renderIndents && "\n\t\t"}
  90. {props.children.slice(x1)}
  91. {props.renderIndents && "\n\t"}
  92. </h5>
  93. )
  94. }
  95. return (
  96. <h5 className="fw:700 c:gray-900">
  97. {!props.readOnly && <span className="c:gray-500">{props.children.slice(0, x1)}</span>}
  98. {props.children.slice(x1)}
  99. </h5>
  100. )
  101. }
  102.  
  103. function H6(props) {
  104. const x1 = "###### ".length
  105. if (props.renderMarkup) {
  106. return (
  107. <h6 className={props.renderSemanticClasses ? "header-6" : null}>
  108. {props.renderIndents && "\n\t\t"}
  109. {props.children.slice(x1)}
  110. {props.renderIndents && "\n\t"}
  111. </h6>
  112. )
  113. }
  114. return (
  115. <h6 className="fw:700 c:gray-900">
  116. {!props.readOnly && <span className="c:gray-400">{props.children.slice(0, x1)}</span>}
  117. {props.children.slice(x1)}
  118. </h6>
  119. )
  120. }
  121.  
  122. function Comment(props) {
  123. const x1 = "//".length
  124. if (props.renderMarkup) {
  125. return null
  126. }
  127. return (
  128. <div>
  129. {props.children.split("\n").map((children, index) => (
  130. <p key={index} className="c:gray-400">
  131. <span>{children.slice(0, x1)}</span>
  132. {children.slice(x1)}
  133. </p>
  134. ))}
  135. </div>
  136. )
  137. }
  138.  
  139. function Paragraph(props) {
  140. if (props.renderMarkup) {
  141. return (
  142. <p className={props.renderSemanticClasses ? "paragraph" : null}>
  143. {props.renderIndents && "\n\t\t"}
  144. {props.children || <br />}
  145. {props.renderIndents && "\n\t"}
  146. </p>
  147. )
  148. }
  149. return (
  150. <p className="c:gray-900">
  151. {props.children || <br />}
  152. </p>
  153. )
  154. }
  155.  
  156. function Blockquote(props) {
  157. const x1 = "> ".length
  158. const multiline = props.children.split("\n")
  159. if (props.renderMarkup) {
  160. return (
  161. <blockquote className={props.renderSemanticClasses ? "blockquote" : null}>
  162. {props.renderIndents && "\n\t\t"}
  163. <ul>
  164. {multiline.map((children, index) => (
  165. <>
  166. {props.renderIndents && "\n\t\t\t"}
  167. <li key={index}>
  168. {props.renderIndents && "\n\t\t\t\t"}
  169. {children.slice(x1) || <br />}
  170. {props.renderIndents && "\n\t\t\t"}
  171. </li>
  172. </>
  173. ))}
  174. {props.renderIndents && "\n\t\t"}
  175. </ul>
  176. {props.renderIndents && "\n\t"}
  177. </blockquote>
  178. )
  179. }
  180. return (
  181. <blockquote className="p:1 br:0.1" style={{ background: "hsla(var(--blue-a200), 0.05)" }}>
  182. {multiline.map((children, index) => (
  183. <p key={index} className="c:blue-a200">
  184. {!props.readOnly && <span>{children.slice(0, x1)}</span>}
  185. {children.slice(x1) || <br />}
  186. </p>
  187. ))}
  188. </blockquote>
  189. )
  190. }
  191.  
  192. function CodeBlock(props) {
  193. const x1 = "```".length
  194. const x2 = props.children.length - "```".length
  195. // If we’re rendering markup, we need to drop the leading
  196. // and trailing newlines.
  197. const multiline = !props.renderMarkup
  198. ? props.children.slice(x1, x2).split("\n")
  199. : props.children.slice(x1 + (props.children.charAt(x1) === "\n"), x2 - (props.children.charAt(x2 - 1) === "\n")).split("\n")
  200. if (props.renderMarkup) {
  201. return (
  202. <code className={props.renderSemanticClasses ? "code-block" : null}>
  203. {props.renderIndents && "\n\t\t"}
  204. <ul>
  205. {multiline.map((children, index) => (
  206. <>
  207. {props.renderIndents && "\n\t\t\t"}
  208. <li key={index}>
  209. {props.renderIndents && "\n\t\t\t\t"}
  210. {children || <br />}
  211. {props.renderIndents && "\n\t\t\t"}
  212. </li>
  213. </>
  214. ))}
  215. {props.renderIndents && "\n\t\t"}
  216. </ul>
  217. {props.renderIndents && "\n\t"}
  218. </code>
  219. )
  220. }
  221. return (
  222. // `block` is needed for `code`.
  223. <code className="p:1 block b:gray-100 br:0.1 overflow -x:scroll" style={{ font: "calc(19px * 0.75)/1.5 'Monaco', 'Roboto Mono'", whiteSpace: "pre" }}>
  224. {multiline.map((children, index) => (
  225. <p key={index} className="c:gray-900">
  226. {!index && (
  227. !props.readOnly && (
  228. <span>
  229. {props.children.slice(0, x1)}
  230. </span>
  231. )
  232. )}
  233. {children || ((index > 0 && index + 1 !== multiline.length) && <br />)}
  234. {index + 1 === multiline.length && (
  235. !props.readOnly && (
  236. <span>
  237. {props.children.slice(0, x1)}
  238. </span>
  239. )
  240. )}
  241. </p>
  242. ))}
  243. </code>
  244. )
  245. }
  246.  
  247. function SectionBreak(props) {
  248. if (props.renderMarkup) {
  249. return <hr className={props.renderSemanticClasses ? "section-break" : null} />
  250. }
  251. if (props.readOnly) {
  252. return <hr style={{ border: "2px solid hsl(var(--gray-200))" }} />
  253. }
  254. return (
  255. <div className="relative -x -y">
  256. <div className="absolute -x -y no-pointer-events">
  257. <div className="flex -c -y:center h:max">
  258. <hr style={{ border: "2px solid hsl(var(--gray-200))" }} />
  259. </div>
  260. </div>
  261. <p style={{ color: "transparent" }}>
  262. {props.children}
  263. </p>
  264. </div>
  265. )
  266. }
  267.  
  268. const ComponentMap = {
  269. H1,
  270. H2,
  271. H3,
  272. H4,
  273. H5,
  274. H6,
  275. Comment,
  276. Paragraph,
  277. Blockquote,
  278. CodeBlock,
  279. SectionBreak
  280. }
  281.  
  282. function Lex(data) {
  283. const items = []
  284. for (let x2 = 0; x2 <= data.length; x2++) { // Use `let`.
  285. let x1 = x2 // Use `let`.
  286. let Component = "" // Use `let`.
  287. switch (true) {
  288. // Header.
  289. case (
  290. data.slice(x2, x2 + 2) === "# " ||
  291. data.slice(x2, x2 + 3) === "## " ||
  292. data.slice(x2, x2 + 4) === "### " ||
  293. data.slice(x2, x2 + 5) === "#### " ||
  294. data.slice(x2, x2 + 6) === "##### " ||
  295. data.slice(x2, x2 + 7) === "###### "
  296. ):
  297. Component = ["H1", "H2", "H3", "H4", "H5", "H6"][data.slice(x2).indexOf("# ")]
  298. while (x2 < data.length && data.charAt(x2) !== "\n") {
  299. x2++
  300. }
  301. break
  302. // Comment.
  303. case data.slice(x2, x2 + 2) === "//":
  304. Component = "Comment"
  305. while (x2 < data.length && data.charAt(x2) !== "\n") {
  306. x2++
  307. }
  308. break
  309. // Blockquote.
  310. case data.charAt(x2) === ">":
  311. Component = "Blockquote"
  312. while (x2 < data.length && (data.charAt(x2) !== "\n" || data.slice(x2, x2 + 2) === "\n>")) {
  313. x2++
  314. }
  315. break
  316. // Code block (strict).
  317. case data.slice(x2, x2 + 3) === "```" && (() => {
  318. const peekStart = data.slice(x2 + 3).indexOf("```")
  319. if (peekStart === -1) {
  320. return
  321. }
  322. // We need to use `x2 + ` to make `peekStart` an
  323. // absolute index.
  324. const peekEnd = x2 + 3 + peekStart + 3
  325. return !data.charAt(peekEnd) || data.charAt(peekEnd) === "\n"
  326. })():
  327. Component = "CodeBlock"
  328. x2 += 3
  329. while (x2 < data.length && data.slice(x2, x2 + 3) !== "```") {
  330. x2++
  331. }
  332. x2 += 3
  333. break
  334. // Section break (strict).
  335. case data.slice(x2, x2 + 3) === "---" && (!data.charAt(x2 + 3) || data.charAt(x2 + 3) === "\n"):
  336. Component = "SectionBreak"
  337. x2 += 3
  338. break
  339. // Paragraph.
  340. default:
  341. Component = "Paragraph"
  342. while (x2 < data.length && data.charAt(x2) !== "\n") {
  343. x2++
  344. }
  345. break
  346. }
  347. const children = data.slice(x1, x2)
  348. items.push({ Component, _Component: ComponentMap[Component], children })
  349. }
  350. return items
  351. }
  352.  
  353. // `restoreSession` restores a local storage session.
  354. //
  355. // FIXME: Error handling?
  356. function restoreSession(key) {
  357. const localStorageState = JSON.parse(localStorage.getItem(key))
  358. const setLS = data => localStorage.setItem(key, JSON.stringify(data))
  359. return [localStorageState, setLS]
  360. }
  361.  
  362. const KEY_CODE_SLASH = 191
  363. const KEY_CODE_TAB = 9
  364.  
  365. function shouldObscure({ Component, children }) {
  366. const ok = (
  367. Component === "Comment" ||
  368. (Component === "Paragraph" && !children)
  369. )
  370. return ok
  371. }
  372.  
  373. // `readOnlyComponents` obscures non read-only components.
  374. function readOnlyComponents({ readOnly, Components: ComponentsAsIs }) {
  375. const Components = [...ComponentsAsIs] // Don’t mutate `ComponentsAsIs`.
  376. if (readOnly) {
  377. // We need to iterate backwards because we’re using
  378. // `splice`. See djave.co.uk/blog/read/splice-doesnt-work-very-well-in-a-javascript-for-loop
  379. // for reference.
  380. for (let x = Components.length - 2; x >= 0; x--) { // Use `let`.
  381. if (shouldObscure(Components[x]) && shouldObscure(Components[x + 1])) {
  382. Components.splice(x, 1)
  383. }
  384. }
  385. const leading = Components[0]
  386. if (Components.length > 1 && shouldObscure(leading)) {
  387. Components.splice(0, 1)
  388. }
  389. const trailing = Components[Components.length - 1]
  390. if (Components.length > 1 && shouldObscure(trailing)) {
  391. Components.splice(Components.length - 1, 1)
  392. }
  393. }
  394. return Components
  395. }
  396.  
  397. const [session, storeSession] = restoreSession(`codex-editor-alpha-${globals.version}`)
  398.  
  399. /* eslint-disable no-useless-escape */
  400. const defaultValue = `// Version ${globals.version}.\n\n> Hello! 🖖\n> \n> Here’s the tl;dr: this is a prototype for what I’m building, an interactive, WYSIWYG editor designed from scratch for developers.\n> \n> Feel free to play to poke around. 😉\n>\n> And if you’re interested in learning how this editor works, read on to the second header where I explain more.\n\n# What this editor supports (so far)\n\n# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6\n\n// Comment 👻 — these are hidden when ‘Markdown’ is disabled.\n\nParagraph — empty paragraphs are hidden when ‘Markdown’ is disabled.\n\n> (Multiline blockquotes)\n>\n> To be, or not to be, that is the question.\n>\n> William Shakespeare.\n\n\`\`\`\n(Multiline code blocks)\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n\tfmt.Println("hello, world!")\n}\n\`\`\`\n\nAnd section breaks:\n\n---\n\nI’m working on unordered lists, ordered lists, and inline elements. After that, this editor will transition from alpha to beta. 🤠\n\n---\n\n# What you’re looking at\n\nIn order to help me understand all of the moving parts for the WYSIWYG, enhanced Markdown editor I’m building, I decided to take a break from working on the WYSIWYG aspect and just focus on lexing.\n\nWhat is lexing?\n\nLexing is the process of searching for qualifiers and tokens from text streams; so in programming, a qualifier could be the opening quote of a string, e.g. \`"\`, and the token would be the string, e.g. \`"hello, world"\`. Because I’m parsing Markdown, a qualifier could be the opening syntax of a header e.g. \`#\` and the token would be the header in full, e.g. \`# What you’re looking at\`.\n\nSo this prototype does a couple of things: the left-hand side is an editable textarea and the right-hand side is a read-only view of the textarea’s text stream. You can view your input as React components, HTML, JSON, or JSON with the VDOM, that is, all of the metadata this editor uses under-the-hood.\n\n# Some discrete examples\n\nI’m building this editor in React for a couple of reasons, namely, because I tried to do it _without_ React and it was too hard. While it _can_ be done, I wouldn’t recommend it…\n\nFor example, here’s the \`Paragraph\` component this editor uses under-the-hood. If you look closely, you’ll notice the component outputs one of two representations: markup and HTML. Markup is what you see when you’re viewing the editor as ‘HTML’. In essence, markup is simplified HTML, with optional semantic classes and indents. The other representation is the rendered HTML in ‘Components’ mode.\n\n\`\`\`\nfunction Paragraph(props) {\n\tif (props.renderMarkup) {\n\t\treturn (\n\t\t\t<p className={props.renderSemanticClasses ? "paragraph" : null}>\n\t\t\t\t{props.renderIndents && "\\n\\t\\t"}\n\t\t\t\t\t{props.children || <br />}\n\t\t\t\t{props.renderIndents && "\\n\\t"}\n\t\t\t</p>\n\t\t)\n\t}\n\treturn (\n\t\t<p className="c:gray-900">\n\t\t\t{props.children || <br />}\n\t\t</p>\n\t)\n}\n\`\`\``
  401.  
  402. const RenderModes = {
  403. Components: 0,
  404. HTML: 1,
  405. JSON: 2,
  406. JSONWithMetadata: 3
  407. }
  408.  
  409. const initialState = (session && session.data && session) || {
  410. renderMode: RenderModes.Components,
  411. readOnly: false,
  412. renderSemanticClasses: true,
  413. renderIndents: true,
  414. data: defaultValue,
  415. pos1: 0,
  416. pos2: 0,
  417. Components: Lex(defaultValue)
  418. }
  419.  
  420. // Temporary fix: re-lex the localStorage session.
  421. ;(() => {
  422. if (!session || !session.data) {
  423. return
  424. }
  425. initialState.Components = Lex(session.data)
  426. })()
  427.  
  428. function NewNoteV02(props) {
  429. const ref = React.useRef(null)
  430.  
  431. const [editorState, setEditorState] = React.useState(initialState)
  432.  
  433. React.useEffect(() => {
  434. const handleKeyDown = e => {
  435. if ((globals.isAppleOS ? !e.metaKey : !e.ctrlKey) || e.keyCode !== KEY_CODE_SLASH) {
  436. return
  437. }
  438. setEditorState({ ...editorState, readOnly: !editorState.readOnly})
  439. }
  440. document.addEventListener("keydown", handleKeyDown)
  441. return () => {
  442. document.removeEventListener("keydown", handleKeyDown)
  443. }
  444. }, [editorState])
  445.  
  446. React.useEffect(() => {
  447. storeSession(editorState)
  448. })
  449.  
  450. React.useLayoutEffect(() => {
  451. ref.current.selectionStart = editorState.pos1
  452. ref.current.selectionEnd = editorState.pos2
  453. }, [editorState.pos1, editorState.pos2])
  454.  
  455. const handleSelect = e => {
  456. const [pos1, pos2] = [ref.current.selectionStart, ref.current.selectionEnd]
  457. setEditorState({ ...editorState, pos1, pos2 })
  458. }
  459.  
  460. const handleChange = e => {
  461. const data = e.target.value
  462. setEditorState({ ...editorState, data, Components: Lex(data) })
  463. }
  464.  
  465. const handleKeyDown = e => {
  466. if (e.keyCode !== KEY_CODE_TAB) {
  467. return
  468. }
  469. if (e.shiftKey) {
  470. e.preventDefault()
  471. return
  472. }
  473. e.preventDefault()
  474. const data = editorState.data.slice(0, editorState.pos1) + "\t" + editorState.data.slice(editorState.pos2)
  475. const pos1 = editorState.pos1 + 1
  476. const pos2 = editorState.pos2 + 1
  477. setEditorState({ ...editorState, data, pos1, pos2, Components: Lex(data) })
  478. }
  479.  
  480. // Properties are sorted alphabetically.
  481. const preformatted = { font: "calc(18px * 0.75)/1.5 'Monaco', 'Roboto Mono'", tabSize: 2, whiteSpace: "pre" }
  482. const formatted = { font: "18px/1.5 'BlinkMacSystemFont', system-ui, -apple-system, 'Roboto'", tabSize: 2 }
  483.  
  484. return (
  485. <main>
  486. <textarea ref={ref} className="p-x:2 p-y:4 b:gray-100 overflow -y:scroll" style={{ ...preformatted, border: "none", outline: "none", resize: "none", whiteSpace: "normal" }} value={editorState.data} onSelect={handleSelect} onChange={handleChange} onKeyDown={handleKeyDown} spellCheck={true} />
  487. <div className="p-x:2 p-y:4 overflow -y:scroll">
  488. {editorState.renderMode === RenderModes.Components && (
  489. <article style={formatted}>
  490. {readOnlyComponents(editorState).map(({ _Component: Component, children }, index) => (
  491. <Component key={index} readOnly={editorState.readOnly}>
  492. {children}
  493. </Component>
  494. ))}
  495. </article>
  496. )}
  497. {editorState.renderMode === RenderModes.HTML && (
  498. <article style={preformatted}>
  499. <p>
  500. {ReactDOMServer.renderToStaticMarkup(
  501. <article>
  502. {readOnlyComponents(editorState).map(({ _Component: Component, children }, index) => (
  503. <>
  504. {/* React generates a unique key
  505. warning because strings are keyless. */}
  506. {Component !== Comment && "\n\t"}
  507. <Component
  508. key={index}
  509. renderMarkup
  510. readOnly={editorState.readOnly}
  511. renderSemanticClasses={editorState.renderSemanticClasses}
  512. renderIndents={editorState.renderIndents}
  513. >
  514. {children}
  515. </Component>
  516. </>
  517. ))}
  518. {"\n"}
  519. </article>
  520. )}
  521. </p>
  522. </article>
  523. )}
  524. {(
  525. editorState.renderMode === RenderModes.JSON ||
  526. editorState.renderMode === RenderModes.JSONWithMetadata
  527. ) && (
  528. <article style={preformatted}>
  529. <p>
  530. {JSON.stringify(
  531. (editorState.renderMode === RenderModes.JSON && { Components: readOnlyComponents(editorState) }) ||
  532. (editorState.renderMode === RenderModes.JSONWithMetadata && editorState),
  533. "", "\t"
  534. )}
  535. </p>
  536. </article>
  537. )}
  538. </div>
  539. <footer className="fixed -x -b" style={{ font: "16px/1 'BlinkMacSystemFont', system-ui, -apple-system, 'Roboto'" }}>
  540. <div className="p-x:1 flex -r -x:between b:white" style={{ borderTop: "1px solid hsl(var(--gray-200))" }}>
  541. <div className="flex -r">
  542. {generateSettings(editorState, setEditorState).slice(0, 4).map(props => (
  543. <label htmlFor={props.id}>
  544. <div className="p-x:0.4 p-y:1 flex -r -y:center pointer-events">
  545. <input id={props.id} type="radio" checked={props.checked} onChange={props.onChange} />
  546. <div className="w:0.4" />
  547. <p className="c:gray-900">
  548. {props.children}
  549. </p>
  550. </div>
  551. </label>
  552. ))}
  553. </div>
  554. <div className="flex -r">
  555. {generateSettings(editorState, setEditorState).slice(4, 7).map(props => (
  556. <label htmlFor={props.id}>
  557. <div className="p-x:0.4 p-y:1 flex -r -y:center pointer-events">
  558. <input id={props.id} type="checkbox" checked={props.checked} onChange={props.onChange} />
  559. <div className="w:0.4" />
  560. <p className="c:gray-900">
  561. {props.children}
  562. </p>
  563. </div>
  564. </label>
  565. ))}
  566. </div>
  567. </div>
  568. </footer>
  569. </main>
  570. )
  571. }
  572.  
  573. const generateSettings = (editorState, setEditorState) => [
  574. { id: "render-components", checked: editorState.renderMode === RenderModes.Components, onChange: e => setEditorState({ ...editorState, renderMode: RenderModes.Components }), children: "Components" },
  575. { id: "render-html", checked: editorState.renderMode === RenderModes.HTML, onChange: e => setEditorState({ ...editorState, renderMode: RenderModes.HTML }), children: "HTML" },
  576. { id: "render-json", checked: editorState.renderMode === RenderModes.JSON, onChange: e => setEditorState({ ...editorState, renderMode: RenderModes.JSON }), children: "JSON" },
  577. { id: "render-json-with-metadata", checked: editorState.renderMode === RenderModes.JSONWithMetadata, onChange: e => setEditorState({ ...editorState, renderMode: RenderModes.JSONWithMetadata }), children: "JSON (with metadata)" },
  578. { id: "render-read-only", checked: !editorState.readOnly, onChange: e => setEditorState({ ...editorState, readOnly: !editorState.readOnly }), children: "Markdown (Components)" },
  579. { id: "render-semantic-classes", checked: editorState.renderSemanticClasses, onChange: e => setEditorState({ ...editorState, renderSemanticClasses: !editorState.renderSemanticClasses }), children: "Semantic classes (HTML)" },
  580. { id: "render-indents", checked: editorState.renderIndents, onChange: e => setEditorState({ ...editorState, renderIndents: !editorState.renderIndents }), children: "Indents (HTML)" }
  581. ]
  582.  
  583. export default NewNoteV02
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement