Skip to content

Commit 915be01

Browse files
authored
Reveal js editor render (#411)
#411 * Editor rendering in reveal slides * Remove mardown renderer for presentation * Changeset added * Markdown parser reverted
1 parent 3635ac2 commit 915be01

File tree

8 files changed

+283
-31
lines changed

8 files changed

+283
-31
lines changed

.changeset/poor-poets-march.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'mexit-webapp': patch
3+
---
4+
5+
Editor renderer instead of markdown for presentation

apps/webapp/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
"ky": "^0.32.2",
5353
"lodash": "^4.17.21",
5454
"lottie-react": "^2.2.1",
55-
"markdown-to-jsx": "^7.1.9",
5655
"match-sorter": "^6.3.1",
5756
"md5": "2.3.0",
5857
"mixpanel-browser": "^2.43.0",

apps/webapp/src/Components/Editor/Plateless.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,9 @@ RenderPlateless.displayName = 'RenderPlateless'
292292
*/
293293
const Plateless = ({ content, multiline = false }: PlatelessProps) => {
294294
const typeMap = useTypeMap(multiline)
295-
295+
if (!content.length) {
296+
return <span>Loading...</span>
297+
}
296298
return (
297299
<PlatelessStyled readOnly multiline={multiline}>
298300
<RenderPlateless content={content} typeMap={typeMap} multiline={multiline} />

apps/webapp/src/Components/Presenter/index.tsx

+28-19
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ import { useEffect, useRef, useState } from 'react'
44
import { useParams, useSearchParams } from 'react-router-dom'
55

66
import { usePlateEditorRef } from '@udecode/plate'
7-
import Markdown from 'markdown-to-jsx'
87
import Reveal from 'reveal.js'
98

109
import { tinykeys } from '@workduck-io/tinykeys'
1110

12-
import { ELEMENT_PARAGRAPH, FeatureFlags, SECTION_SEPARATOR, SLIDE_SEPARATOR, useContentStore } from '@mexit/core'
11+
import { FeatureFlags, NodeEditorContent, useContentStore } from '@mexit/core'
1312
import { FeatureFlag } from '@mexit/shared'
1413

15-
import parseToMarkdown from '../../Editor/utils'
14+
import { splitToSlides } from '../../Editor/presenterUtils'
15+
import { createViewFilterStore, ViewFilterProvider } from '../../Hooks/todo/useTodoFilters'
16+
import Editor from '../Editor/Editor'
1617

1718
import { PresenterContainer } from './styled'
1819

1920
const Presenter = () => {
20-
const [markdown, setMarkdown] = useState('#Loading...')
21+
const [slidesContent, setSlideContent] = useState<NodeEditorContent[][]>([[[]]])
2122
const presenterRef = useRef<HTMLDivElement>(null)
2223

2324
const noteId = useParams().nodeId
@@ -26,6 +27,7 @@ const Presenter = () => {
2627
const isPresenting = searchParams.get('present') === 'true'
2728

2829
const editor = usePlateEditorRef(noteId)
30+
const content = editor?.children ?? useContentStore.getState().getContent(noteId)?.content
2931

3032
const goFullScreen = (element: any) => {
3133
if (element.requestFullscreen) {
@@ -37,16 +39,14 @@ const Presenter = () => {
3739
}
3840
}
3941

40-
const setMarkdownFromEditor = () => {
41-
const content = editor?.children ?? useContentStore.getState().getContent(noteId)?.content
42-
const markdownContent = parseToMarkdown({ children: content, type: ELEMENT_PARAGRAPH })
43-
setMarkdown(markdownContent)
42+
const setContentFromEditor = () => {
43+
setSlideContent(splitToSlides(content))
4444
}
4545

4646
useEffect(() => {
4747
if (isPresenting) {
4848
Reveal.initialize().then(() => {
49-
setMarkdownFromEditor()
49+
setContentFromEditor()
5050
})
5151

5252
const unsubscribe = tinykeys(window, {
@@ -70,20 +70,29 @@ const Presenter = () => {
7070
}
7171
}, [isPresenting, noteId])
7272

73-
if (!markdown?.length) return
74-
7573
return (
7674
<PresenterContainer $isPresenting={isPresenting} className="reveal" ref={presenterRef}>
7775
<div className="slides">
78-
{markdown?.split(SLIDE_SEPARATOR)?.map((slideContent, idx) => (
79-
<section key={idx}>
80-
{slideContent?.split(SECTION_SEPARATOR)?.map((sectionContent, idxN) => (
81-
<section key={`${idx}_${idxN}`}>
82-
<Markdown>{sectionContent}</Markdown>
76+
{slidesContent?.map(
77+
(slideContent, idx) =>
78+
isPresenting && (
79+
<section key={idx}>
80+
{slideContent?.map((sectionContent, idxN) => (
81+
<section key={`${idx}_${idxN}`}>
82+
<ViewFilterProvider createStore={createViewFilterStore}>
83+
<Editor
84+
// includeBlockInfo={true}
85+
content={sectionContent}
86+
nodeUID={`${noteId}_EDITOR_${idx}_${idxN}`}
87+
readOnly={true}
88+
autoFocus={false}
89+
/>
90+
</ViewFilterProvider>
91+
</section>
92+
))}
8393
</section>
84-
))}
85-
</section>
86-
))}
94+
)
95+
)}
8796
</div>
8897
</PresenterContainer>
8998
)
+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import {
2+
ELEMENT_BLOCKQUOTE,
3+
ELEMENT_CODE_BLOCK,
4+
ELEMENT_H1,
5+
ELEMENT_H2,
6+
ELEMENT_H3,
7+
ELEMENT_H4,
8+
ELEMENT_H5,
9+
ELEMENT_H6,
10+
ELEMENT_HR,
11+
// ELEMENT_INDENT,
12+
ELEMENT_LI,
13+
ELEMENT_LINK,
14+
ELEMENT_MEDIA_EMBED,
15+
ELEMENT_OL,
16+
ELEMENT_PARAGRAPH,
17+
ELEMENT_SECTION_SEPARATOR,
18+
ELEMENT_TAG,
19+
// ELEMENT_TABLE_CELL,
20+
// ELEMENT_TABLE_PRE,
21+
// ELEMENT_TABLE_ROW,
22+
// ELEMENT_TABLE_SUF,
23+
// ELEMENT_TABLE_WRAP,
24+
// ELEMENT_TASK_LIST,
25+
ELEMENT_TODO_LI,
26+
ELEMENT_UL,
27+
SECTION_SEPARATOR,
28+
SLIDE_SEPARATOR
29+
} from '@mexit/core'
30+
31+
const LIST_TYPES = [
32+
ELEMENT_H1,
33+
ELEMENT_H2,
34+
ELEMENT_H3,
35+
ELEMENT_H4,
36+
ELEMENT_H5,
37+
ELEMENT_H6,
38+
ELEMENT_HR,
39+
// ELEMENT_INDENT,
40+
ELEMENT_LI,
41+
ELEMENT_SECTION_SEPARATOR,
42+
ELEMENT_LINK,
43+
ELEMENT_OL,
44+
ELEMENT_PARAGRAPH,
45+
// ELEMENT_TABLE_CELL,
46+
// ELEMENT_TABLE_PRE,
47+
// ELEMENT_TABLE_ROW,
48+
// ELEMENT_TABLE_SUF,
49+
// ELEMENT_TABLE_WRAP,
50+
// ELEMENT_TASK_LIST,
51+
ELEMENT_UL
52+
]
53+
54+
type LeafType = {
55+
text: string
56+
strikeThrough?: boolean
57+
bold?: boolean
58+
italic?: boolean
59+
}
60+
61+
type BlockType = {
62+
type: string
63+
parentType?: string
64+
link?: string
65+
children: Array<any>
66+
}
67+
68+
export default function parseToMarkdown(chunk: any, ignoreParagraphNewline = false, listDepth = 0) {
69+
const text = chunk.text || ''
70+
let type = chunk.type || ''
71+
72+
let children =
73+
type && chunk.children
74+
? // if we have a type, we're a BlockType element which _always_ has a children array.
75+
// $FlowFixMe
76+
chunk.children
77+
?.map((c) => {
78+
const isTag = type === ELEMENT_TAG
79+
if (isTag) return chunk.value
80+
const isList = LIST_TYPES.includes(c.type || '')
81+
const selfIsList = LIST_TYPES.includes(chunk.type || '')
82+
const childrenHasLink =
83+
Array.isArray(chunk.children) && chunk.children.some((f) => f.type && f.type === 'link')
84+
85+
return parseToMarkdown(
86+
{ ...c, parentType: type },
87+
// WOAH.
88+
// what we're doing here is pretty tricky, it relates to the block below where
89+
// we check for ignoreParagraphNewline and set type to paragraph.
90+
// We want to strip out empty paragraphs sometimes, but other times we don't.
91+
// If we're the descendant of a list, we know we don't want a bunch
92+
// of whitespace. If we're parallel to a link we also don't want
93+
// to respect neighboring paraphs
94+
ignoreParagraphNewline || isList || selfIsList || childrenHasLink,
95+
// track depth of nested lists so we can add proper spacing
96+
LIST_TYPES.includes(c.type || '') ? listDepth + 1 : listDepth
97+
)
98+
})
99+
.join('')
100+
: text
101+
102+
// This is pretty fragile code, check the long comment where we iterate over children
103+
if (!ignoreParagraphNewline && chunk.text === '' && chunk.parentType === ELEMENT_PARAGRAPH) {
104+
type = ELEMENT_PARAGRAPH
105+
children = '<br>'
106+
}
107+
108+
if (children === '' && type !== ELEMENT_HR && type !== ELEMENT_SECTION_SEPARATOR) return
109+
110+
if (chunk.bold) {
111+
children = retainWhitespaceAndFormat(children, '**')
112+
}
113+
114+
if (chunk.italic) {
115+
children = retainWhitespaceAndFormat(children, '_')
116+
}
117+
118+
if (chunk.strikeThrough) {
119+
children = `~~${children}~~`
120+
}
121+
122+
switch (type) {
123+
case ELEMENT_H1:
124+
return `# ${children}\n`
125+
case ELEMENT_H2:
126+
return `## ${children}\n`
127+
case ELEMENT_H3:
128+
return `### ${children}\n`
129+
case ELEMENT_H4:
130+
return `#### ${children}\n`
131+
case ELEMENT_H5:
132+
return `##### ${children}\n`
133+
case ELEMENT_H6:
134+
return `###### ${children}\n`
135+
case ELEMENT_HR:
136+
return `\n${SLIDE_SEPARATOR}\n`
137+
case ELEMENT_SECTION_SEPARATOR:
138+
return `\n${SECTION_SEPARATOR}\n`
139+
case ELEMENT_MEDIA_EMBED:
140+
return `[${children}](${chunk.link || ''})`
141+
case ELEMENT_BLOCKQUOTE:
142+
// For some reason, marked is parsing blockquotes w/ one new line
143+
// as contiued blockquotes, so adding two new lines ensures that doesn't
144+
// happen
145+
return `> ${children}\n\n`
146+
case ELEMENT_CODE_BLOCK:
147+
return `
148+
<div>
149+
\`\`\`
150+
${children}\`\`\`
151+
</div>
152+
`
153+
case 'link':
154+
case 'a':
155+
return `[${children}](${chunk.link || ''})`
156+
case ELEMENT_TAG:
157+
return `\`#${children}\``
158+
case ELEMENT_UL:
159+
case ELEMENT_OL:
160+
return `${children}`
161+
case ELEMENT_TODO_LI:
162+
return `\n[ ] ${children}`
163+
case ELEMENT_LI: {
164+
// $FlowFixMe // We're not a LeafType here and flow doesn't get that
165+
const isOL = chunk && chunk.parentType === ELEMENT_OL
166+
167+
let spacer = ''
168+
for (let k = 1; listDepth > k; k++) {
169+
if (isOL) {
170+
// https://github.com/remarkjs/remark-react/issues/65
171+
spacer += ' '
172+
} else {
173+
spacer += ' '
174+
}
175+
}
176+
return `${spacer}${isOL ? '1.' : '-'} ${children}`
177+
}
178+
case ELEMENT_PARAGRAPH:
179+
return `${children}\n`
180+
default:
181+
return `${children}\n`
182+
}
183+
}
184+
185+
// This function handles the case of a string like this: " foo "
186+
// Where it would be invalid markdown to generate this: "** foo **"
187+
// We instead, want to trim the whitespace out, apply formatting, and then
188+
// bring the whitespace back. So our returned string looks like this: " **foo** "
189+
function retainWhitespaceAndFormat(string: string, format: string) {
190+
const left = string.trimLeft()
191+
const right = string.trimRight()
192+
let children = string.trim()
193+
194+
const fullFormat = `${format}${children}${format}`
195+
196+
if (children.length === string.length) {
197+
return fullFormat
198+
}
199+
200+
const leftFormat = `${format}${children}`
201+
202+
if (left.length !== string.length) {
203+
const diff = string.length - left.length
204+
children = `${new Array(diff + 1).join(' ')}${leftFormat}`
205+
} else {
206+
children = leftFormat
207+
}
208+
209+
const rightFormat = `${children}${format}`
210+
211+
if (right.length !== string.length) {
212+
const diff = string.length - right.length
213+
children = `${rightFormat}${new Array(diff + 1).join(' ')}`
214+
} else {
215+
children = rightFormat
216+
}
217+
218+
return children
219+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ELEMENT_HR, ELEMENT_SECTION_SEPARATOR, NodeEditorContent } from '@mexit/core'
2+
3+
export const splitToSlides = (content: NodeEditorContent): NodeEditorContent[][] => {
4+
return content.reduce(
5+
(acc, val) => {
6+
if (val.type === ELEMENT_HR) return [...acc, [[]]]
7+
else {
8+
const currentSlideIdx = acc.length - 1
9+
const currentSlide = acc[currentSlideIdx]
10+
11+
const currectSectionIdx = currentSlide.length - 1
12+
if (val.type === ELEMENT_SECTION_SEPARATOR) {
13+
acc[currentSlideIdx] = [...currentSlide, []]
14+
return acc
15+
}
16+
acc[currentSlideIdx][currectSectionIdx] = [...currentSlide[currectSectionIdx], val]
17+
18+
return acc
19+
}
20+
},
21+
[[[]]]
22+
)
23+
}

apps/webapp/src/Editor/utils.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,11 @@ export default function parseToMarkdown(chunk: any, ignoreParagraphNewline = fal
145145
return `> ${children}\n\n`
146146
case ELEMENT_CODE_BLOCK:
147147
return `
148-
<div>
149-
\`\`\`
150-
${children}\`\`\`
151-
</div>
152-
`
148+
<div>
149+
\`\`\`
150+
${children}\`\`\`
151+
</div>
152+
`
153153
case 'link':
154154
case 'a':
155155
return `[${children}](${chunk.link || ''})`

yarn.lock

-5
Original file line numberDiff line numberDiff line change
@@ -7720,11 +7720,6 @@ map-obj@^4.0.0:
77207720
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a"
77217721
integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==
77227722

7723-
markdown-to-jsx@^7.1.9:
7724-
version "7.2.0"
7725-
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.2.0.tgz#e7b46b65955f6a04d48a753acd55874a14bdda4b"
7726-
integrity sha512-3l4/Bigjm4bEqjCR6Xr+d4DtM1X6vvtGsMGSjJYyep8RjjIvcWtrXBS8Wbfe1/P+atKNMccpsraESIaWVplzVg==
7727-
77287723
match-sorter@^6.3.1:
77297724
version "6.3.1"
77307725
resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda"

0 commit comments

Comments
 (0)