Skip to content

Commit 86f8eb4

Browse files
committed
table try to output html when cell contains complex content
1 parent 2cbeb6a commit 86f8eb4

6 files changed

Lines changed: 169 additions & 22 deletions

File tree

markdown-schema/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@
3535
"happy-dom": "^18.0.1",
3636
"hast-util-to-html": "^9.0.5",
3737
"mdast-util-from-markdown": "^2.0.2",
38+
"mdast-util-to-hast": "^13.2.1",
3839
"mdast-util-to-markdown": "^2.1.2",
3940
"micromark-util-types": "^2.0.2",
4041
"prosekit": "catalog:",
4142
"prosemirror-inputrules": "catalog:",
42-
"prosemirror-tables": "^1.8.1",
4343
"prosemirror-model": "catalog:",
44+
"prosemirror-tables": "^1.8.1",
4445
"prosemirror-transformer-markdown": "workspace:*",
4546
"remark-comment-config": "^8.0.0",
4647
"remark-gfm": "catalog:"
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2025 Riccardo Perra
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { EXIT, visit } from 'unist-util-visit'
18+
import { toHast } from 'mdast-util-to-hast'
19+
import { toHtml } from 'hast-util-to-html'
20+
import type { PhrasingContent, Root } from 'mdast'
21+
22+
const nodeTypes: Array<string> = [
23+
'text',
24+
'break',
25+
'link',
26+
'inlineCode',
27+
'strong',
28+
] satisfies Array<PhrasingContent['type']>
29+
30+
export function remarkTableToHtmlOnComplexContent() {
31+
return (tree: Root) => {
32+
visit(tree, 'table', (node, index, parent) => {
33+
let complex = false
34+
35+
visit(node, 'tableCell', (cell) => {
36+
visit(cell, function (node) {
37+
if (node.type !== 'tableCell' && !nodeTypes.includes(node.type)) {
38+
complex = true
39+
return EXIT
40+
}
41+
if (complex) {
42+
return EXIT
43+
}
44+
})
45+
})
46+
47+
if (complex && parent && typeof index === 'number') {
48+
const hast = toHast(node)
49+
const html = toHtml(hast)
50+
parent.children[index] = { type: 'html', value: html }
51+
}
52+
})
53+
}
54+
}

markdown-schema/src/table/table.test.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,26 @@ import {
3232
defineHardbreakMarkdown,
3333
remarkHtmlHardbreak,
3434
} from '../hardbreak/hardbreak'
35-
import { defineListMarkdown } from '../list/list'
35+
import { defineListMarkdown, unistMergeAdjacentList } from '../list/list'
3636
import { defineTableMarkdown } from './table'
37+
import { remarkTableToHtmlOnComplexContent } from './remarkTableOutputHtml'
3738

3839
const extension = getMarksBaseExtensions([
3940
defineTableMarkdown(),
4041
defineHardbreakMarkdown(),
4142
defineListMarkdown(),
4243
])
4344

44-
const { doc, p, table, tableHeaderCell, tableRow, tableCell, br } = builders(
45-
extension.schema!,
46-
{
45+
const { doc, p, table, tableHeaderCell, tableRow, tableCell, br, list } =
46+
builders(extension.schema!, {
4747
p: { nodeType: 'paragraph' },
4848
br: { nodeType: 'hardBreak' },
4949
table: { markType: 'table' },
5050
tableHeaderCell: { markType: 'tableHeaderCell' },
5151
tableRow: { markType: 'tableRow' },
5252
tableCell: { markType: 'tableCell' },
53-
},
54-
)
53+
list: { nodeType: 'list' },
54+
})
5555

5656
test('markdown -> prosemirror', () => {
5757
const editor = getEditorInstance(extension)
@@ -176,3 +176,60 @@ test('parse content with line breaks', () => {
176176
| Still nothing in this cell | 1. This cell uses line breaks<br>2. to appear as a numbered list |`,
177177
)
178178
})
179+
180+
test('render as html tag when includes non-phrasing content', () => {
181+
const editor = getEditorInstance(
182+
extension,
183+
doc(
184+
table(
185+
tableRow(
186+
tableHeaderCell(p('First Header')),
187+
tableHeaderCell(p('Second Header')),
188+
),
189+
tableRow(
190+
tableCell(p('Content Cell 1')),
191+
tableCell(
192+
list({ kind: 'bullet' }, p('First item')),
193+
list({ kind: 'bullet' }, p('Second item')),
194+
),
195+
),
196+
tableRow(
197+
tableCell(p('Content Cell 3')),
198+
tableCell(p('Content Cell 4')),
199+
),
200+
),
201+
),
202+
)
203+
204+
const result = convertPmSchemaToUnist(editor.state.doc, editor.schema, {
205+
postProcess: (node) => {
206+
unistMergeAdjacentList(node)
207+
remarkTableToHtmlOnComplexContent()(node)
208+
},
209+
})
210+
211+
sameMarkdown(
212+
result,
213+
`<table>
214+
<thead>
215+
<tr>
216+
<th><p>First Header</p></th>
217+
<th><p>Second Header</p></th>
218+
</tr>
219+
</thead>
220+
<tbody>
221+
<tr>
222+
<td>Content Cell 1</td>
223+
<td><ul>
224+
<li>First item</li>
225+
<li>Second item</li>
226+
</ul></td>
227+
</tr>
228+
<tr>
229+
<td>Content Cell 3</td>
230+
<td>Content Cell 4</td>
231+
</tr>
232+
</tbody>
233+
</table>`,
234+
)
235+
})

markdown-schema/src/table/table.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ import { pmNode } from '@prosemirror-processor/unist'
3131
import { tableEditing } from 'prosemirror-tables'
3232
import { Fragment, Slice } from 'prosemirror-model'
3333
import { Transform } from 'prosekit/pm/transform'
34-
import type { Parent } from 'mdast'
34+
import type { Parent, PhrasingContent } from 'mdast'
35+
36+
export { remarkTableToHtmlOnComplexContent } from './remarkTableOutputHtml'
3537

3638
export function defineTableMarkdown() {
3739
return union(
@@ -74,6 +76,20 @@ export function defineTableMarkdown() {
7476
return tr.doc
7577
},
7678
__toUnist: (node, parent, context) => {
79+
const { content } = node
80+
81+
if (
82+
content.childCount === 1 &&
83+
content.child(0).type.name === 'paragraph'
84+
) {
85+
const p = content.child(0)
86+
const childNodes = context.handleAll(p)
87+
return {
88+
type: 'tableCell',
89+
children: childNodes as unknown as Array<PhrasingContent>,
90+
} as const
91+
}
92+
7793
return fromProseMirrorNode('tableCell')(node, parent, context as any)
7894
},
7995
unistName: 'tableCell',

pnpm-lock.yaml

Lines changed: 20 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/editor/editor.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,22 @@ import './editor.css'
3333

3434
import { unistMergeAdjacentList } from '@prosedoc/markdown-schema'
3535
import { useDocChange, useStateUpdate } from 'prosekit/solid'
36-
import { unknownNodeHandler } from '../core/custom/unknown-node/unknown-node-handler'
37-
import { remarkGitHubIssueReferenceSupport } from '../core/custom/issue-reference/remarkGitHubIssueReference'
38-
import { ProsekitEditor } from '../core/editor/editor'
39-
import { defineExtension } from '../core/editor/extension'
40-
import { ConfigStore } from '../config.store'
4136
import { ExtensionEditorStore } from '../editor.store'
42-
import { log } from './utils/logger'
43-
import { patchJsNativeTextareaValue } from './utils/jsNativeTextareaValuePatch'
44-
import { unistNodeFromMarkdown } from './utils/unistNodeFromMarkdown'
45-
import { DebugNode } from './DebugNode'
46-
import { setEditorContent } from './utils/setContent'
37+
import { ConfigStore } from '../config.store'
38+
import { defineExtension } from '../core/editor/extension'
39+
import { ProsekitEditor } from '../core/editor/editor'
40+
import { remarkGitHubIssueReferenceSupport } from '../core/custom/issue-reference/remarkGitHubIssueReference'
41+
import { unknownNodeHandler } from '../core/custom/unknown-node/unknown-node-handler'
4742
import { forceGithubTextAreaSync } from './utils/forceGithubTextAreaSync'
48-
import type { Root } from 'mdast'
49-
import type { GitHubUploaderHandler } from '../core/custom/image/github-file-uploader'
50-
import type { SuggestionData } from './utils/loadSuggestionData'
43+
import { setEditorContent } from './utils/setContent'
44+
import { DebugNode } from './DebugNode'
45+
import { unistNodeFromMarkdown } from './utils/unistNodeFromMarkdown'
46+
import { patchJsNativeTextareaValue } from './utils/jsNativeTextareaValuePatch'
47+
import { log } from './utils/logger'
5148
import type { Schema } from 'prosemirror-model'
49+
import type { SuggestionData } from './utils/loadSuggestionData'
50+
import type { GitHubUploaderHandler } from '../core/custom/image/github-file-uploader'
51+
import type { Root } from 'mdast'
5252

5353
export interface EditorProps {
5454
suggestions: SuggestionData

0 commit comments

Comments
 (0)