Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMax <max@nextcloud.com>2022-03-09 22:07:17 +0300
committerMax <max@nextcloud.com>2022-03-31 15:29:14 +0300
commit4629457932accaa24abefc9a8f444dc9398a94d5 (patch)
tree9d0a5d312e588f627ed905c25c8b13d34060610a /src
parent0db660bbe12c948e1964915f483ad8e406f72d4d (diff)
feature: initial table support
First take at parsing and serializing markdown tables. Use TableHead and TableBody nodes to mimic the markdown table structure with a single heading line on top and multiple td lines following. Signed-off-by: Max <max@nextcloud.com>
Diffstat (limited to 'src')
-rw-r--r--src/EditorFactory.js12
-rw-r--r--src/markdownit/index.js1
-rw-r--r--src/nodes/Table.js41
-rw-r--r--src/nodes/TableBody.js23
-rw-r--r--src/nodes/TableCell.js8
-rw-r--r--src/nodes/TableHead.js49
-rw-r--r--src/nodes/TableHeader.js8
-rw-r--r--src/nodes/TableRow.js8
-rw-r--r--src/tests/fixtures/table.html14
-rw-r--r--src/tests/fixtures/table.md3
-rw-r--r--src/tests/fixtures/tableWithOtherStructure.html10
-rw-r--r--src/tests/markdownit.spec.js7
-rw-r--r--src/tests/tables.spec.js36
13 files changed, 220 insertions, 0 deletions
diff --git a/src/EditorFactory.js b/src/EditorFactory.js
index ee31c96bf..ec681c489 100644
--- a/src/EditorFactory.js
+++ b/src/EditorFactory.js
@@ -32,6 +32,12 @@ import ListItem from '@tiptap/extension-list-item'
import CodeBlock from '@tiptap/extension-code-block'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import HorizontalRule from '@tiptap/extension-horizontal-rule'
+import Table from './nodes/Table'
+import TableBody from './nodes/TableBody'
+import TableCell from './nodes/TableCell'
+import TableHead from './nodes/TableHead'
+import TableHeader from './nodes/TableHeader'
+import TableRow from './nodes/TableRow'
/* eslint-enable import/no-named-as-default */
import { Editor } from '@tiptap/core'
@@ -88,6 +94,12 @@ const createEditor = ({ content, onCreate, onUpdate, extensions, enableRichEditi
HorizontalRule,
OrderedList,
ListItem,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
TaskList,
TaskItem,
Callout,
diff --git a/src/markdownit/index.js b/src/markdownit/index.js
index 5c8d35b14..d4c559533 100644
--- a/src/markdownit/index.js
+++ b/src/markdownit/index.js
@@ -6,6 +6,7 @@ import callouts from './callouts'
const markdownit = MarkdownIt('commonmark', { html: false, breaks: false })
.enable('strikethrough')
+ .enable('table')
.use(taskLists, { enable: true, labelAfter: true })
.use(splitMixedLists)
.use(underline)
diff --git a/src/nodes/Table.js b/src/nodes/Table.js
new file mode 100644
index 000000000..12bdf6486
--- /dev/null
+++ b/src/nodes/Table.js
@@ -0,0 +1,41 @@
+import { Table } from '@tiptap/extension-table'
+import { mergeAttributes } from '@tiptap/core'
+
+export default Table.extend({
+ content: 'tableHead tableBody',
+
+ renderHTML({ HTMLAttributes }) {
+ return ['table', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
+ },
+
+ toMarkdown(state, node) {
+ const widths = []
+ const head = node.child(0)
+ const body = node.child(1)
+ head.forEach(row => {
+ state.write('|')
+ row.forEach(cell => {
+ widths.push(cell.textContent.length)
+ state.renderInline(cell)
+ state.write('|')
+ })
+ })
+ state.ensureNewLine()
+ state.write('|')
+ widths.forEach(width => {
+ state.write(state.repeat('-', width))
+ state.write('|')
+ })
+ body.forEach(row => {
+ state.ensureNewLine()
+ state.write('|')
+ row.forEach((cell, _, i) => {
+ state.renderInline(cell)
+ state.write(state.repeat(' ', widths[i] - cell.textContent.length))
+ state.write('|')
+ })
+ })
+ state.closeBlock(node)
+ },
+
+})
diff --git a/src/nodes/TableBody.js b/src/nodes/TableBody.js
new file mode 100644
index 000000000..fa3cc83ba
--- /dev/null
+++ b/src/nodes/TableBody.js
@@ -0,0 +1,23 @@
+import { Node, mergeAttributes } from '@tiptap/core'
+
+export default Node.create({
+ name: 'tableBody',
+ content: 'tableRow',
+
+ addOptions() {
+ return {
+ HTMLAttributes: {},
+ }
+ },
+
+ parseHTML() {
+ return [
+ { tag: 'tbody' },
+ ]
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return ['tbody', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
+ },
+
+})
diff --git a/src/nodes/TableCell.js b/src/nodes/TableCell.js
new file mode 100644
index 000000000..606f2b428
--- /dev/null
+++ b/src/nodes/TableCell.js
@@ -0,0 +1,8 @@
+import { TableCell } from '@tiptap/extension-table-cell'
+
+export default TableCell.extend({
+ content: 'inline*',
+ addAttributes() {
+ return {}
+ },
+})
diff --git a/src/nodes/TableHead.js b/src/nodes/TableHead.js
new file mode 100644
index 000000000..6e45a3095
--- /dev/null
+++ b/src/nodes/TableHead.js
@@ -0,0 +1,49 @@
+import { Node, mergeAttributes } from '@tiptap/core'
+
+const tableHeadRow = Node.create({
+ name: 'tableHeadRow',
+ content: 'tableHeader*',
+
+ addOptions() {
+ return {
+ HTMLAttributes: {},
+ }
+ },
+
+ parseHTML() {
+ return [
+ { tag: 'tr' },
+ ]
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return ['tr', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
+ },
+
+})
+
+export default Node.create({
+ name: 'tableHead',
+ content: 'tableHeadRow',
+
+ addOptions() {
+ return {
+ HTMLAttributes: {},
+ }
+ },
+
+ addExtensions() {
+ return [tableHeadRow]
+ },
+
+ parseHTML() {
+ return [
+ { tag: 'thead' },
+ ]
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return ['thead', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
+ },
+
+})
diff --git a/src/nodes/TableHeader.js b/src/nodes/TableHeader.js
new file mode 100644
index 000000000..b50603bca
--- /dev/null
+++ b/src/nodes/TableHeader.js
@@ -0,0 +1,8 @@
+import { TableHeader } from '@tiptap/extension-table-header'
+
+export default TableHeader.extend({
+ content: 'inline*',
+ addAttributes() {
+ return {}
+ },
+})
diff --git a/src/nodes/TableRow.js b/src/nodes/TableRow.js
new file mode 100644
index 000000000..c42aeee34
--- /dev/null
+++ b/src/nodes/TableRow.js
@@ -0,0 +1,8 @@
+import { TableRow } from '@tiptap/extension-table-row'
+
+export default TableRow.extend({
+ content: 'tableCell*',
+ addAttributes() {
+ return {}
+ },
+})
diff --git a/src/tests/fixtures/table.html b/src/tests/fixtures/table.html
new file mode 100644
index 000000000..f260c28a0
--- /dev/null
+++ b/src/tests/fixtures/table.html
@@ -0,0 +1,14 @@
+<table>
+<thead>
+<tr>
+<th>heading</th>
+<th>other heading</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>cell</td>
+<td>other cell</td>
+</tr>
+</tbody>
+</table>
diff --git a/src/tests/fixtures/table.md b/src/tests/fixtures/table.md
new file mode 100644
index 000000000..1e7479f58
--- /dev/null
+++ b/src/tests/fixtures/table.md
@@ -0,0 +1,3 @@
+|heading|other heading|
+|-------|-------------|
+|cell |other cell |
diff --git a/src/tests/fixtures/tableWithOtherStructure.html b/src/tests/fixtures/tableWithOtherStructure.html
new file mode 100644
index 000000000..cc56a1799
--- /dev/null
+++ b/src/tests/fixtures/tableWithOtherStructure.html
@@ -0,0 +1,10 @@
+<table>
+ <tr>
+ <th>heading</th>
+ <td>other heading</td>
+ </tr>
+ <tr>
+ <th>cell</th>
+ <td>other cell</td>
+ </tr>
+</table>
diff --git a/src/tests/markdownit.spec.js b/src/tests/markdownit.spec.js
index 69ce3673d..30748860a 100644
--- a/src/tests/markdownit.spec.js
+++ b/src/tests/markdownit.spec.js
@@ -1,5 +1,7 @@
import markdownit from './../markdownit'
import { typesAvailable } from '../markdownit/callouts'
+import tableMarkdown from './fixtures/table.md'
+import tableHtml from './fixtures/table.html'
describe('markdownit', () => {
@@ -27,6 +29,11 @@ describe('markdownit', () => {
`))
})
+ it('renders tables', () => {
+ const rendered = markdownit.render(tableMarkdown)
+ expect(rendered).toBe(tableHtml)
+ })
+
describe('callouts', () => {
typesAvailable.forEach((type) => {
it(`render ${type}`, () => {
diff --git a/src/tests/tables.spec.js b/src/tests/tables.spec.js
new file mode 100644
index 000000000..7a347eb9e
--- /dev/null
+++ b/src/tests/tables.spec.js
@@ -0,0 +1,36 @@
+import { createEditor } from './../EditorFactory'
+import { createMarkdownSerializer } from './../extensions/Markdown'
+import markdownit from './../markdownit'
+import input from './fixtures/table.md'
+import output from './fixtures/table.html'
+import otherStructure from './fixtures/tableWithOtherStructure.html'
+
+describe('Table', () => {
+
+ test('load into editor', () => {
+ const tiptap = editorWithContent(markdownit.render(input))
+ expect(tiptap.getHTML().replaceAll('><', ">\n<")).toBe(output.replace(/\n$/, ''))
+ })
+
+ test('serialize from editor', () => {
+ const tiptap = editorWithContent(markdownit.render(input))
+ const serializer = createMarkdownSerializer(tiptap.schema)
+ expect(serializer.serialize(tiptap.state.doc)).toBe(input.replace(/\n$/, ''))
+ })
+
+ test('handle html table with other structure', () => {
+ const tiptap = editorWithContent(
+ otherStructure.replace(/\n\s*/g,'')
+ )
+ expect(tiptap.getHTML().replaceAll('><', ">\n<")).toBe(output.replace(/\n$/, ''))
+ })
+
+})
+
+function editorWithContent(content) {
+ return createEditor({
+ content,
+ enableRichEditing: true
+ })
+}
+