Skip to content

Commit

Permalink
feat: update render markdown for tables
Browse files Browse the repository at this point in the history
Signed-off-by: Luka Trovic <luka@nextcloud.com>
  • Loading branch information
luka-nextcloud authored and max-nextcloud committed Aug 21, 2023
1 parent 1b09dc2 commit 1c63fab
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 29 deletions.
42 changes: 21 additions & 21 deletions cypress/fixtures/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ This is a table

| Header | other Header |
|--------|--------------|
| Cell | other cell |
| Cell | other cell |
| Cell | other cell |
| Cell | other cell |

## Create a table

Expand Down Expand Up @@ -54,40 +54,40 @@ did insertTable

## Add a new row at the end

| | | |
|--|--|--|
| | | |
| | | |
|--|--|-------------|
| | | |
| | | addRowAfter |


---

| | | |
|--|--|--|
| | | |
| | | |
|--|--|-----------------|
| | | |
| | | did addRowAfter |
| | | |
| | | |

## Add a new column at the end

| | | |
|--|--|--|
| | | |
| | | |
|--|--|----------------|
| | | |
| | | addColumnAfter |


---

| | | | |
|--|--|--|--|
| | | | |
| | | | |
|--|--|--------------------|--|
| | | | |
| | | did addColumnAfter | |

## Delete row at the end

| | | |
|--|--|--|
| | | |
| | | |
|--|--|-----------|
| | | |
| | | deleteRow |


Expand All @@ -99,9 +99,9 @@ did insertTable

## Delete column at the end

| | | |
|--|--|--|
| | | |
| | | |
|--|--|--------------|
| | | |
| | | deleteColumn |


Expand Down
48 changes: 48 additions & 0 deletions src/nodes/Table/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
selectedRect,
selectionCell,
} from '@tiptap/pm/tables'
import { Node } from '@tiptap/pm/model'

/**
*
Expand Down Expand Up @@ -66,6 +67,50 @@ function findSameCellInNextRow($cell) {
}
}

/**
*
* @param {Node} node - Table node
*/
function getColumns(node) {
const columns = []

node.content.forEach((row) => {
row.content.forEach((cell, offset, columnIndex) => {
if (!columns[columnIndex]) {
columns[columnIndex] = []
}
columns[columnIndex].push(cell)
})
})

return columns
}

/**
*
* @param {Array} columns - Columns of table
*/
function calculateColumnWidths(columns) {
const widths = []

columns.forEach((column) => {
let maxWidth = 0

column.forEach((cell) => {
let cellWidth = 0
cell.content.forEach(node => {
cellWidth += (node.text?.length || 6)
if (node.text?.includes('|')) cellWidth += 1
})
maxWidth = Math.max(maxWidth, cellWidth)
})

widths.push(maxWidth)
})

return widths
}

export default Table.extend({
content: 'tableCaption? tableHeadRow tableRow*',

Expand Down Expand Up @@ -178,6 +223,9 @@ export default Table.extend({
},

toMarkdown(state, node) {
const columns = getColumns(node)
state.options.columnWidths = calculateColumnWidths(columns)
state.options.currentHeaderIndex = 0
state.renderContent(node)
state.closeBlock(node)
},
Expand Down
20 changes: 20 additions & 0 deletions src/nodes/Table/TableCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,30 @@ export default TableCell.extend({
toMarkdown(state, node) {
state.write(' ')
const backup = state.options?.escapeExtraCharacters
const columnIndex = state.options.currentColumnIndex
state.options.escapeExtraCharacters = /\|/

let cellRenderedContentLength = 0
node.content.forEach((childNode, offset, index) => {
cellRenderedContentLength += (childNode.text?.length || 6)
if (childNode.text?.includes('|')) cellRenderedContentLength += 1
if (childNode.attrs.syntax === ' ') node.child(index).attrs.syntax = 'html'
})
const columnWidth = state.options.columnWidths[columnIndex]
const align = node.attrs?.textAlign || 'left'
const space = columnWidth - cellRenderedContentLength
const leftPadding = Math.floor(space / 2)
const rightPadding = Math.ceil(space / 2)

if (align === 'center') state.write(' '.repeat(leftPadding))
if (align === 'right') state.write(' '.repeat(space))
state.renderInline(node)
if (align === 'center') state.write(' '.repeat(rightPadding))
if (align === 'left') state.write(' '.repeat(space))

state.options.escapeExtraCharacters = backup
state.write(' |')
state.options.currentColumnIndex++
},

parseHTML() {
Expand Down
4 changes: 2 additions & 2 deletions src/nodes/Table/TableHeadRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default TableRow.extend({
state.renderInline(node)
state.ensureNewLine()
state.write('|')
node.forEach(cell => {
let row = state.repeat('-', cell.textContent.length + 2)
node.forEach((cell, offset, index) => {
let row = state.repeat('-', state.options.columnWidths[index] + 2)
const align = cell.attrs?.textAlign
if (align === 'center' || align === 'left') row = ':' + row.slice(1)
if (align === 'center' || align === 'right') row = row.slice(0, -1) + ':'
Expand Down
12 changes: 12 additions & 0 deletions src/nodes/Table/TableHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,21 @@ export default TableHeader.extend({
content: 'inline*',

toMarkdown(state, node) {
const headerIndex = state.options.currentHeaderIndex
const columnWidth = state.options.columnWidths[headerIndex]
const align = node.attrs?.textAlign || 'left'
const space = columnWidth - node.content.size
const leftPadding = Math.floor(space / 2)
const rightPadding = Math.ceil(space / 2)

state.write(' ')
if (align === 'center') state.write(' '.repeat(leftPadding))
if (align === 'right') state.write(' '.repeat(space))
state.renderInline(node)
if (align === 'center') state.write(' '.repeat(rightPadding))
if (align === 'left') state.write(' '.repeat(space))
state.write(' |')
state.options.currentHeaderIndex++
},

parseHTML() {
Expand Down
1 change: 1 addition & 0 deletions src/nodes/Table/TableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default TableRow.extend({

toMarkdown(state, node) {
state.write('|')
state.options.currentColumnIndex = 0
state.renderInline(node)
state.ensureNewLine()
},
Expand Down
6 changes: 3 additions & 3 deletions src/tests/fixtures/tables/basic/table.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
| heading | heading 2 | heading 3 |
|:-------:|----------:|-----------|
| center | right | left cell <br />with line break |
| heading | heading 2 | heading 3 |
|:-------:|----------:|---------------------------------|
| center | right | left cell <br />with line break |
6 changes: 3 additions & 3 deletions src/tests/markdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,17 +200,17 @@ describe('Markdown serializer from html', () => {
})

test('table', () => {
expect(markdownThroughEditorHtml('<table><tbody><tr><th>greetings</th></tr><tr><td>hello</td></tr></tbody></table>')).toBe('| greetings |\n|-----------|\n| hello |\n')
expect(markdownThroughEditorHtml('<table><tbody><tr><th>greetings</th></tr><tr><td>hello</td></tr></tbody></table>')).toBe('| greetings |\n|-----------|\n| hello |\n')
})

test('table cell escaping', () => {
// while '|' has no special meaning in commonmark is has to be escaped for GFM tables
expect(markdownThroughEditorHtml('<table><tr><th>greetings</th></tr><tr><td>hello | hallo</td></tr></table>')).toBe('| greetings |\n|-----------|\n| hello \\| hallo |\n')
expect(markdownThroughEditorHtml('<table><tr><th>greetings</th></tr><tr><td>hello | hallo</td></tr></table>')).toBe('| greetings |\n|----------------|\n| hello \\| hallo |\n')
})

test('table pastes (#2708)', () => {
// while '|' has no special meaning in commonmark is has to be escaped for GFM tables
expect(markdownFromPaste('<table><tbody><tr><th>greetings</th></tr><tr><td>hello</td></tr></tbody></table>')).toBe('| greetings |\n|-----------|\n| hello |\n')
expect(markdownFromPaste('<table><tbody><tr><th>greetings</th></tr><tr><td>hello</td></tr></tbody></table>')).toBe('| greetings |\n|-----------|\n| hello |\n')
})

test('front matter', () => {
Expand Down

0 comments on commit 1c63fab

Please sign in to comment.