From 9f0469a1e3c324c94c2515799ae6f3aeeb059ab3 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Oct 2024 20:36:53 +0200 Subject: [PATCH 1/8] markdown table: pad entries to same width, pad against separators #19 --- src/utils/util.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/utils/util.js b/src/utils/util.js index 94b6511..3e2afca 100644 --- a/src/utils/util.js +++ b/src/utils/util.js @@ -13,15 +13,29 @@ export const stringToSlateValue = (str = '') => { } export const slateValueToString = (slateVal) => { + // Calculate the maximum width for each column + const columnWidths = slateVal.children[0].children.map((_, colIndex) => { + return Math.max(...slateVal.children.map(row => + row.children[colIndex].children[0].text?.replaceAll('\n', '[:br]').length || 0 + )); + }); + console.log('Debug: Column widths:', columnWidths); + let rowStrs = Array.from(slateVal.children, (row) => { - const cells = Array.from(row.children, (cell) => { - // 将换行符替换为 [:br] - return cell.children[0].text?.replaceAll('\n', '[:br]') - }).join('|') - return `|${cells}|` - }) - rowStrs.splice(1, 0, `|${Array.from(slateVal.children[0].children, () => '--').join('|')}|`) - return rowStrs.join('\n') + const cells = Array.from(row.children, (cell, index) => { + const cellText = cell.children[0].text?.replaceAll('\n', '[:br]') || ''; + return cellText.padEnd(columnWidths[index]); + }).join(' | '); + return `| ${cells} |`; + }); + + // Create the separator row + const separatorRow = `| ${columnWidths.map(width => '-'.repeat(width)).join(' | ')} |`; + rowStrs.splice(1, 0, separatorRow); + + // Add the preamble + const preamble = '#+attr_html: :class monospace-table'; + return `${preamble}\n${rowStrs.join('\n')}`; } const createRow = (cellText) => { From 9eb5b61dca7ad5a03f0c57c1eb1c73696f027530 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Oct 2024 20:37:30 +0200 Subject: [PATCH 2/8] remove whitespace from table cells when editing in modal --- src/utils/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/util.js b/src/utils/util.js index 3e2afca..eaebcb4 100644 --- a/src/utils/util.js +++ b/src/utils/util.js @@ -7,7 +7,7 @@ export const stringToSlateValue = (str = '') => { const contentArr = [_arr[0]].concat(_arr.slice(2)) const res = contentArr.map(rowStr => { const rowArr = rowStr.trim().split('|') - return rowArr.slice(1, rowArr.length - 1) + return rowArr.slice(1, rowArr.length - 1).map(cell => cell.trim()) }) return createTableNode(res) } From 361894e98246fc89951d9613fa5f4062027d295d Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Oct 2024 20:37:36 +0200 Subject: [PATCH 3/8] typo --- src/utils/parseRawInputByMarkdownIt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/parseRawInputByMarkdownIt.js b/src/utils/parseRawInputByMarkdownIt.js index 0ba8439..5033683 100644 --- a/src/utils/parseRawInputByMarkdownIt.js +++ b/src/utils/parseRawInputByMarkdownIt.js @@ -12,7 +12,7 @@ const parseMarkdownTable = (input) => { const tables = tokenList .filter(token => token?.type === 'table_open') .map(token => { - // map is Sourse map, format [startLine, endLine] + // map is Source map, format [startLine, endLine] let [startLine , endLine] = token.map const endLineStr = strArr[endLine] From 9eb46ee0929d43e65e8c369345f035a63629d99b Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Oct 2024 21:08:03 +0200 Subject: [PATCH 4/8] cleanup --- src/components/TableEditor.jsx | 55 ---------------------------------- 1 file changed, 55 deletions(-) diff --git a/src/components/TableEditor.jsx b/src/components/TableEditor.jsx index 4a27448..916340c 100644 --- a/src/components/TableEditor.jsx +++ b/src/components/TableEditor.jsx @@ -25,62 +25,7 @@ const Element = props => { } const TableEditor = ({ content = DEFAULT_TABLE, className = '' }, ref) => { - // const [value, setValue] = useState([ - // // { - // // type: 'paragaph', - // // children: [{ text: 'First line of text in Slate JS. ' }], - // // }, - // { - // type: 'table', - // children: [ - // { - // type: 'table-row', - // children: [ - // { - // type: 'table-cell', - // children: [ - // { - // text: 'title1', - // } - // ] - // }, - // { - // type: 'table-cell', - // children: [ - // { - // text: 'title2', - // } - // ] - // }, - // ] - // }, - // { - // type: 'table-row', - // children: [ - // { - // type: 'table-cell', - // children: [ - // { - // text: 'content1', - // } - // ] - // }, - // { - // type: 'table-cell', - // children: [ - // { - // text: 'content2', - // } - // ] - // }, - // ] - // }, - // ] - // } - // ]) - // console.log('[faiz:] === tableEditor input: \n', content) const [value, setValue] = useState([stringToSlateValue(content)]) - // console.log('[faiz:] === tableEditor format to Slate Editor Node: ', stringToSlateValue(content)) const editor = useMemo(() => withTables(withReact(createEditor())), []) const tableUtil = useMemo(() => new TableUtil(editor), [editor]) From c966f4096dad9eb240ee7c7caa5151ff085750a6 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Oct 2024 21:08:39 +0200 Subject: [PATCH 5/8] navigation: add support for up and down arrow keys to switch cells #18 --- src/components/TableEditor.jsx | 22 ++++++++++++++------ src/pages/App.jsx | 10 +++++++++ src/utils/table.js | 37 +++++++++++++++++----------------- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/components/TableEditor.jsx b/src/components/TableEditor.jsx index 916340c..3ccaa3b 100644 --- a/src/components/TableEditor.jsx +++ b/src/components/TableEditor.jsx @@ -44,12 +44,22 @@ const TableEditor = ({ content = DEFAULT_TABLE, className = '' }, ref) => { () => ({ getEditorValue: () => value, onKeydown: (code) => { - const isFocused = ReactEditor.isFocused(editor) - if (!isFocused) return - if (code === 'Tab') { - tableUtil.edit('cursor-next') - } else if (code === 'ShiftTab') { - tableUtil.edit('cursor-prev') + if (!ReactEditor.isFocused(editor)) + return + + switch (code) { + case 'Tab': + tableUtil.edit('cursor-next') + break + case 'ShiftTab': + tableUtil.edit('cursor-prev') + break + case 'ArrowUp': + tableUtil.edit('cursor-up') + break + case 'ArrowDown': + tableUtil.edit('cursor-down') + break } }, }), diff --git a/src/pages/App.jsx b/src/pages/App.jsx index 43dd37a..510dbef 100644 --- a/src/pages/App.jsx +++ b/src/pages/App.jsx @@ -75,6 +75,16 @@ const App = ({ content, tables, blockId }) => { Object.keys(tableEditorMapRef.current).forEach(key => { tableEditorMapRef.current?.[key]?.onKeydown('ShiftTab') }) + } else if (e.code === 'ArrowUp') { + e.preventDefault() + Object.keys(tableEditorMapRef.current).forEach(key => { + tableEditorMapRef.current?.[key]?.onKeydown('ArrowUp') + }) + } else if (e.code === 'ArrowDown') { + e.preventDefault() + Object.keys(tableEditorMapRef.current).forEach(key => { + tableEditorMapRef.current?.[key]?.onKeydown('ArrowDown') + }) } }, []) diff --git a/src/utils/table.js b/src/utils/table.js index 3ad4352..6756a66 100644 --- a/src/utils/table.js +++ b/src/utils/table.js @@ -155,27 +155,28 @@ export class TableUtil { } if (action === 'cursor-next') { - let path = [...focus.path] - if (cursorPosition.column < columnsCount - 1) { - // 横向移动到下一个 - path = [0, cursorPosition.row, cursorPosition.column + 1, 0] - } else if (cursorPosition.row < rowsCount - 1) { - // 处于当前行最后一个, 光标移动到下一行第一个 - path = [0, cursorPosition.row + 1, 0, 0] - } - transformSelect(this.editor, path) + const nextColumn = (cursorPosition.column + 1) % columnsCount; + const nextRow = nextColumn === 0 ? (cursorPosition.row + 1) % rowsCount : cursorPosition.row; + const path = [0, nextRow, nextColumn, 0]; + transformSelect(this.editor, path); } else if (action === 'cursor-prev') { - let path = [...focus.path] - if (cursorPosition.column > 0) { - // 横向移动到上一个 - path = [0, cursorPosition.row, cursorPosition.column - 1, 0] - } else if (cursorPosition.row > 0) { - // 处于当前行第一个, 光标移动到上一行最后一个 - path = [0, cursorPosition.row - 1, columnsCount - 1, 0] + const prevColumn = (cursorPosition.column - 1 + columnsCount) % columnsCount; + const prevRow = cursorPosition.column === 0 ? (cursorPosition.row - 1 + rowsCount) % rowsCount : cursorPosition.row; + const path = [0, prevRow, prevColumn, 0]; + transformSelect(this.editor, path); + } else if (action === 'cursor-up') { + if (cursorPosition.row > 0) { + const prevRow = cursorPosition.row - 1; + const path = [0, prevRow, cursorPosition.column, 0]; + transformSelect(this.editor, path); + } + } else if (action === 'cursor-down') { + if (cursorPosition.row < rowsCount - 1) { + const nextRow = cursorPosition.row + 1; + const path = [0, nextRow, cursorPosition.column, 0]; + transformSelect(this.editor, path); } - transformSelect(this.editor, path) } - } } From 82ec41eda79b24640e3a60a3504a509691a20988 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Oct 2024 21:48:20 +0200 Subject: [PATCH 6/8] ux: use elements for header row --- src/components/TableEditor.jsx | 2 ++ src/utils/util.js | 41 +++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/components/TableEditor.jsx b/src/components/TableEditor.jsx index 3ccaa3b..8724285 100644 --- a/src/components/TableEditor.jsx +++ b/src/components/TableEditor.jsx @@ -15,6 +15,8 @@ const Element = props => { return ({children}
) + case 'table-header': + return {children} case 'table-row': return {children} case 'table-cell': diff --git a/src/utils/util.js b/src/utils/util.js index eaebcb4..c93a9f2 100644 --- a/src/utils/util.js +++ b/src/utils/util.js @@ -15,11 +15,10 @@ export const stringToSlateValue = (str = '') => { export const slateValueToString = (slateVal) => { // Calculate the maximum width for each column const columnWidths = slateVal.children[0].children.map((_, colIndex) => { - return Math.max(...slateVal.children.map(row => + return Math.max(...slateVal.children.map(row => row.children[colIndex].children[0].text?.replaceAll('\n', '[:br]').length || 0 )); }); - console.log('Debug: Column widths:', columnWidths); let rowStrs = Array.from(slateVal.children, (row) => { const cells = Array.from(row.children, (cell, index) => { @@ -33,28 +32,40 @@ export const slateValueToString = (slateVal) => { const separatorRow = `| ${columnWidths.map(width => '-'.repeat(width)).join(' | ')} |`; rowStrs.splice(1, 0, separatorRow); - // Add the preamble - const preamble = '#+attr_html: :class monospace-table'; - return `${preamble}\n${rowStrs.join('\n')}`; + return rowStrs.join('\n'); } -const createRow = (cellText) => { - const newRow = Array.from(cellText, (value) => createTableCell(value)) +export const createTableNode = (rows) => { + return { + type: "table", + children: [createHeaderRow(rows[0])].concat(rows.slice(1).map(createRow)) + }; +} + +const createRow = (rowCells) => { return { type: "table-row", - children: newRow + children: rowCells.map(createTableCell) } } -const createTableCell = (text) => { +const createHeaderRow = (rowCells) => { return { - type: "table-cell", - children: [{ text }] + type: "table-row", + children: rowCells.map(createHeaderCell) + } +} + +const createHeaderCell = (cellText) => { + return { + type: "table-header", + children: [{ text: cellText }] } } -export const createTableNode = (cellText) => { - const tableChildren = Array.from(cellText, (value) => createRow(value)) - let tableNode = { type: "table", children: tableChildren } - return tableNode +const createTableCell = (cellText) => { + return { + type: "table-cell", + children: [{ text: cellText }] + } } \ No newline at end of file From 200f9bb3d054e437fe471108a5e44599da87b072 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Oct 2024 22:21:24 +0200 Subject: [PATCH 7/8] add german locale #24 --- src/locales/de/translations.json | 16 ++++++++++++++++ src/locales/i18n.js | 4 ++++ 2 files changed, 20 insertions(+) create mode 100644 src/locales/de/translations.json diff --git a/src/locales/de/translations.json b/src/locales/de/translations.json new file mode 100644 index 0000000..46a306b --- /dev/null +++ b/src/locales/de/translations.json @@ -0,0 +1,16 @@ +{ + "Markdown Table Editor": "Markdown Tabellen-Editor", + "Markdown table editor only support markdown": "Tabellen-Editor unterstützt nur Markdown", + "Add New Table": "Neue Tabelle hinzufügen", + "Cancel": "Abbrechen", + "Confirm": "Bestätigen", + "insert row above": "Zeile oberhalb einfügen", + "insert row below": "Zeile unterhalb einfügen", + "delete row": "Zeile löschen", + "insert column before": "Spalte vorher einfügen", + "insert column after": "Spalte nachfolgend einfügen", + "delete column": "Spalte löschen", + "uuid error": "UUID Fehler", + "markdown table overwrite success": "Tabelle wurde erfolgreich überschrieben", + "markdown table overwrite error": "Fehler beim Überschreiben der Tabelle" +} \ No newline at end of file diff --git a/src/locales/i18n.js b/src/locales/i18n.js index b21650b..a5ad753 100644 --- a/src/locales/i18n.js +++ b/src/locales/i18n.js @@ -3,6 +3,7 @@ import { initReactI18next } from "react-i18next"; import translationEN from "./en/translation.json"; import translationZhCN from "./zh-CN/translation.json"; +import translationDE from "./de/translations.json"; const resources = { en: { @@ -11,6 +12,9 @@ const resources = { 'zh-CN': { translation: translationZhCN, }, + de: { + translation: translationDE, + }, }; i18n.use(initReactI18next).init({ From fae5e399206cc4c34ee0f27612369a7fc0568af1 Mon Sep 17 00:00:00 2001 From: max Date: Sat, 19 Oct 2024 12:17:50 +0200 Subject: [PATCH 8/8] fix copy pasting table cells not working #15 --- src/utils/withTable.js | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/utils/withTable.js b/src/utils/withTable.js index e3d791d..17e4b08 100644 --- a/src/utils/withTable.js +++ b/src/utils/withTable.js @@ -1,7 +1,7 @@ -import { Editor, Range, Point, Element } from 'slate' +import { Editor, Range, Point, Element, Transforms, Node } from 'slate' const withTable = (editor) => { - const { deleteBackward, deleteForward, insertBreak } = editor + const { deleteBackward, deleteForward, insertBreak, insertFragment } = editor editor.deleteBackward = unit => { const { selection } = editor; @@ -84,8 +84,25 @@ const withTable = (editor) => { insertBreak() } + + editor.insertFragment = (fragment) => { + const inTable = Editor.above(editor, { + match: n => !Editor.isEditor(n) && Element.isElement(n) && n.type === 'table-cell', + }) + + if (inTable) { + // In a table, we need to strip the attributes of the node, + // and just keep the text. Otherwise pasting doesn't work properly + const text = fragment.map(node => Node.string(node)) + Transforms.insertText(editor, text) + } else { + // If we're not in a table, use the default behavior + insertFragment(fragment) + } + } + return editor; } -export default withTable; \ No newline at end of file +export default withTable;