Skip to content

Commit 353c8b7

Browse files
author
Zaydek Michels-Gualtieri
committed
Better implementation of display markdown mode; this version relies on the presence of immutable DOM elements; markdown elements are set to contenteditable=false and the parsers have been updated not to interact with such elements (see test suites). Furthermore, this version makes copying the markdown text automatic as a side-effect.
Technically, this version renders a more complicated DOM but is simpler from the perspective of side-effects. The ::before and ::after version has a simpler DOM implementation but does not render selection backgrounds and does not implicitly solve copying text as markdown. It seems like the React-rendered implementation is slightly more advantageous, despite that it emits a more complicated DOM. This would **not** be the case if we could simply use JavaScript to conditionally render markdown elements.
1 parent dca5d67 commit 353c8b7

8 files changed

Lines changed: 140 additions & 106 deletions

File tree

src/RichTextEditor/RichTextEditor.css

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,4 @@
11
[data-root] {
2-
--font-sans:
3-
"-apple-system",
4-
"BlinkMacSystemFont",
5-
"Inter",
6-
"system-ui",
7-
"-apple-system",
8-
"BlinkMacSystemFont",
9-
"Segoe UI",
10-
"Roboto",
11-
"Helvetica Neue",
12-
"Arial",
13-
"Noto Sans",
14-
"sans-serif",
15-
"Apple Color Emoji",
16-
"Segoe UI Emoji",
17-
"Segoe UI Symbol",
18-
"Noto Color Emoji";
19-
--font-mono:
20-
"Menlo",
21-
"Monaco",
22-
"Consolas",
23-
"Liberation Mono",
24-
"Courier New",
25-
"monospace";
26-
272
-webkit-font-smoothing: auto;
283
-moz-osx-font-smoothing: auto;
294

@@ -33,31 +8,10 @@
338
caret-color: var(--black);
349
}
3510

36-
/* Displays markdown syntax as pseudo elements (1 of 2). */
37-
[data-display-markdown-mode="true"] [data-type="em"]::before,
38-
[data-display-markdown-mode="true"] [data-type="strong"]::before,
39-
[data-display-markdown-mode="true"] [data-type="code"]::before,
40-
[data-display-markdown-mode="true"] [data-type="strike"]::before,
41-
[data-display-markdown-mode="true"] [data-type="a"]::before {
42-
content: attr(data-markdown-start);
43-
--text-opacity: 1;
44-
color: #1c64f2;
45-
color: rgba(28, 100, 242, var(--text-opacity));
46-
}
47-
/* Displays markdown syntax as pseudo elements (2 of 2). */
48-
[data-display-markdown-mode="true"] [data-type="em"]::after,
49-
[data-display-markdown-mode="true"] [data-type="strong"]::after,
50-
[data-display-markdown-mode="true"] [data-type="code"]::after,
51-
[data-display-markdown-mode="true"] [data-type="strike"]::after,
52-
[data-display-markdown-mode="true"] [data-type="a"]::after {
53-
content: attr(data-markdown-end);
54-
--text-opacity: 1;
55-
color: #1c64f2;
56-
color: rgba(28, 100, 242, var(--text-opacity));
11+
[data-display-markdown-mode="false"] [data-type="markdown"] {
12+
display: none;
5713
}
58-
59-
/* <strike> .text-blue-600 -> <strike> .text-gray-400 */
60-
[data-display-markdown-mode="true"] [data-type="strike"] .text-blue-600 {
14+
[data-display-markdown-mode="true"] [data-type="strike"] [data-type="markdown"] {
6115
--text-opacity: 1;
6216
color: #9fa6b2;
6317
color: rgba(159, 166, 178, var(--text-opacity));

src/RichTextEditor/components/Debugger.js

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,25 @@ const Debugger = ({
1212
range,
1313
shouldRerender,
1414
}) => (
15-
<div className="mt-6 whitespace-pre-wrap text-sm font-mono" style={{ MozTabSize: 2, tabSize: 2 }}>
16-
{JSON.stringify(
17-
{
18-
...state,
19-
lastActionTimestamp: lastActionTimestamp && state.lastActionTimestamp,
20-
lastAction: lastAction && state.lastAction,
21-
readOnlyModeEnabled: readOnlyModeEnabled && state.readOnlyModeEnabled,
22-
displayMarkdownModeEnabled: displayMarkdownModeEnabled && state.displayMarkdownModeEnabled,
23-
focused: focused && state.focused,
24-
elements: elements && state.elements,
25-
range: range && state.range,
26-
shouldRerender: shouldRerender && state.shouldRerender,
27-
},
28-
null,
29-
"\t",
30-
)}
31-
</div>
15+
process.env.NODE_ENV !== "production" && (
16+
<div className="mt-6 whitespace-pre-wrap text-sm font-mono" style={{ MozTabSize: 2, tabSize: 2 }}>
17+
{JSON.stringify(
18+
{
19+
...state,
20+
lastActionTimestamp: lastActionTimestamp && state.lastActionTimestamp,
21+
lastAction: lastAction && state.lastAction,
22+
readOnlyModeEnabled: readOnlyModeEnabled && state.readOnlyModeEnabled,
23+
displayMarkdownModeEnabled: displayMarkdownModeEnabled && state.displayMarkdownModeEnabled,
24+
focused: focused && state.focused,
25+
elements: elements && state.elements,
26+
range: range && state.range,
27+
shouldRerender: shouldRerender && state.shouldRerender,
28+
},
29+
null,
30+
"\t",
31+
)}
32+
</div>
33+
)
3234
)
3335

3436
export default Debugger

src/RichTextEditor/components/Markdown.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import React from "react"
22
import toArray from "lib/Array/toArray"
33

4-
const Syntax = ({ children }) => (
5-
<span className="text-sm font-mono text-blue-600" data-type="markdown" contentEditable={false}>
4+
const Syntax = ({ className, style, children }) => (
5+
<span
6+
// className={className || "text-sm font-mono text-blue-600"}
7+
className={className || "text-blue-600"}
8+
style={style}
9+
data-type="markdown"
10+
contentEditable={false}
11+
>
612
{children}
713
</span>
814
)

src/RichTextEditor/components/Renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const Renderer = ({ forwardedRef: tree, state, dispatch }) => {
2626
if (selection.rangeCount) {
2727
selection.removeAllRanges()
2828
}
29+
console.log("ReactDOM.render")
2930
ReactDOM.render(<Elements state={state} dispatch={dispatch} />, tree.current, () => {
3031
if (state.readOnlyModeEnabled /* FIXME? */ || !state.focused) {
3132
// No-op

src/RichTextEditor/components/T.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import React from "react"
2-
import toArray from "lib/Array/toArray"
32

43
// Adds [data-type], [data-props], and [data-markdown].
5-
const T = ({ type, props, markdown, children }) => (
4+
const T = ({ type, props, /* markdown, */ children }) => (
65
React.cloneElement(children, {
76
"data-type": type,
87
"data-props": props && JSON.stringify(props, null, " ").replace(/\s*\n\s*/g, " "),
9-
"data-markdown-start": toArray(markdown)[0],
10-
"data-markdown-end": toArray(markdown).slice(-1)[0],
8+
// "data-markdown-start": toArray(markdown)[0],
9+
// "data-markdown-end": toArray(markdown).slice(-1)[0],
1110
})
1211
)
1312

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,59 @@
1-
// import Markdown from "./Markdown"
21
import attrs from "./attrs"
2+
import Markdown from "./Markdown"
33
import React from "react"
44
import T from "./T"
55

6-
// Renders <em>.
6+
// <em>
77
export const Em = ({ children }) => (
8-
<T type="em" markdown="_">
8+
<T type="em">
99
<span className="italic">
10-
{children}
10+
<Markdown syntax="_">
11+
{children}
12+
</Markdown>
1113
</span>
1214
</T>
1315
)
1416

15-
// Renders <strong>.
17+
// <strong>
1618
export const Strong = ({ children }) => (
17-
<T type="strong" markdown="**">
19+
<T type="strong">
1820
<span className="font-semibold">
19-
{children}
21+
<Markdown syntax="**">
22+
{children}
23+
</Markdown>
2024
</span>
2125
</T>
2226
)
2327

24-
// Renders <code>.
28+
// <code>
2529
export const Code = ({ children }) => (
26-
<T type="code" markdown="`">
30+
<T type="code">
2731
<span className="mx-px py-1 text-sm font-mono text-blue-600 border border-cool-gray-300" {...attrs.code}>
28-
{children}
32+
<Markdown syntax="`">
33+
{children}
34+
</Markdown>
2935
</span>
3036
</T>
3137
)
3238

33-
// Renders <strike>.
39+
// <strike>
3440
export const Strike = ({ children }) => (
35-
<T type="strike" markdown="~~">
41+
<T type="strike">
3642
<span className="line-through text-gray-400">
37-
{children}
43+
<Markdown syntax="~~">
44+
{children}
45+
</Markdown>
3846
</span>
3947
</T>
4048
)
4149

42-
// Renders <a href="...">.
50+
// <a href="...">
4351
export const A = ({ href, children }) => (
44-
<T type="a" props={{ href }} markdown={["[", "](" + href + ")"]}>
52+
<T type="a" props={{ href }}>
4553
<span className="mx-px underline text-blue-600" {...attrs.a}>
46-
{children}
54+
<Markdown syntax={["[", "](" + href + ")"]}>
55+
{children}
56+
</Markdown>
4757
</span>
4858
</T>
4959
)

src/RichTextEditor/parsers/parseRenderedElements.test.js

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
import newHash from "lib/x/newHash"
22
import React from "react"
33
import renderTree from "lib/DOM/renderTree"
4+
import toArray from "lib/Array/toArray"
45
import { parseRenderedElements } from "./parsers"
56

7+
const Syntax = ({ children }) => (
8+
<span data-type="markdown" contentEditable={false}>
9+
{children}
10+
</span>
11+
)
12+
13+
const Markdown = ({ syntax, children }) => (
14+
<React.Fragment>
15+
<Syntax>
16+
{toArray(syntax)[0]}
17+
</Syntax>
18+
{children}
19+
<Syntax>
20+
{toArray(syntax).slice(-1)[0]}
21+
</Syntax>
22+
</React.Fragment>
23+
)
24+
625
test("<p><br></p>", () => {
726
const id = newHash()
827
const tree = renderTree((
@@ -31,7 +50,9 @@ test("<p>Hello, <code>world</code>!</p>", () => {
3150
<div id={id} data-type="p">
3251
Hello,{" "}
3352
<span data-type="code">
34-
world
53+
<Markdown syntax="`">
54+
world
55+
</Markdown>
3556
</span>
3657
!
3758
</div>
@@ -68,15 +89,25 @@ test("<p>Hello, <code><a href='foo'><strike><strong><em>world</em></strong></str
6889
<div id={id} data-type="p">
6990
Hello,{" "}
7091
<span data-type="code">
71-
<span data-type="a" data-props={JSON.stringify({ href: "foo" })}>
72-
<span data-type="strike">
73-
<span data-type="strong">
74-
<span data-type="em">
75-
world
92+
<Markdown syntax="`">
93+
<span data-type="a" data-props={JSON.stringify({ href: "foo" })}>
94+
<Markdown syntax={["[", "](foo)"]}>
95+
<span data-type="strike">
96+
<Markdown syntax="~~">
97+
<span data-type="strong">
98+
<Markdown syntax="**">
99+
<span data-type="em">
100+
<Markdown syntax="**">
101+
world
102+
</Markdown>
103+
</span>
104+
</Markdown>
105+
</span>
106+
</Markdown>
76107
</span>
77-
</span>
108+
</Markdown>
78109
</span>
79-
</span>
110+
</Markdown>
80111
</span>
81112
!
82113
</div>

src/RichTextEditor/parsers/parseSemanticElements.test.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
import newHash from "lib/x/newHash"
22
import React from "react"
33
import renderTree from "lib/DOM/renderTree"
4+
import toArray from "lib/Array/toArray"
45
import { parseSemanticElements } from "./parsers"
56

7+
const Syntax = ({ children }) => (
8+
<span data-type="markdown" contentEditable={false}>
9+
{children}
10+
</span>
11+
)
12+
13+
const Markdown = ({ syntax, children }) => (
14+
<React.Fragment>
15+
<Syntax>
16+
{toArray(syntax)[0]}
17+
</Syntax>
18+
{children}
19+
<Syntax>
20+
{toArray(syntax).slice(-1)[0]}
21+
</Syntax>
22+
</React.Fragment>
23+
)
24+
625
test("<p><br></p>", () => {
726
const id = newHash()
827
const tree = renderTree((
@@ -31,7 +50,9 @@ test("<p>Hello, <code>world</code>!</p>", () => {
3150
<p id={id}>
3251
Hello,{" "}
3352
<code>
34-
world
53+
<Markdown syntax="`">
54+
world
55+
</Markdown>
3556
</code>
3657
!
3758
</p>
@@ -68,15 +89,25 @@ test("<p>Hello, <code><a href='foo'><strike><strong><em>world</em></strong></str
6889
<p id={id}>
6990
Hello,{" "}
7091
<code>
71-
<a href="foo">
72-
<strike>
73-
<strong>
74-
<em>
75-
world
76-
</em>
77-
</strong>
78-
</strike>
79-
</a>
92+
<Markdown syntax="`">
93+
<a href="foo">
94+
<Markdown syntax={["[", "](foo)"]}>
95+
<strike>
96+
<Markdown syntax="~~">
97+
<strong>
98+
<Markdown syntax="**">
99+
<em>
100+
<Markdown syntax="**">
101+
world
102+
</Markdown>
103+
</em>
104+
</Markdown>
105+
</strong>
106+
</Markdown>
107+
</strike>
108+
</Markdown>
109+
</a>
110+
</Markdown>
80111
</code>
81112
!
82113
</p>

0 commit comments

Comments
 (0)