Skip to content

Improve Usability and Beautify Markdown Representation #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 18 additions & 61 deletions src/components/TableEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const Element = props => {
return (<table>
<tbody {...attributes}>{children}</tbody>
</table>)
case 'table-header':
return <th {...attributes}>{children}</th>
case 'table-row':
return <tr {...attributes}>{children}</tr>
case 'table-cell':
Expand All @@ -25,62 +27,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])
Expand All @@ -99,12 +46,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
}
},
}),
Expand Down
16 changes: 16 additions & 0 deletions src/locales/de/translations.json
Original file line number Diff line number Diff line change
@@ -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"
}
4 changes: 4 additions & 0 deletions src/locales/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -11,6 +12,9 @@ const resources = {
'zh-CN': {
translation: translationZhCN,
},
de: {
translation: translationDE,
},
};

i18n.use(initReactI18next).init({
Expand Down
10 changes: 10 additions & 0 deletions src/pages/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
}
}, [])

Expand Down
2 changes: 1 addition & 1 deletion src/utils/parseRawInputByMarkdownIt.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
37 changes: 19 additions & 18 deletions src/utils/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

}

}
63 changes: 44 additions & 19 deletions src/utils/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,65 @@ 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)
}

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
));
});

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);

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 }]
}
}
23 changes: 20 additions & 3 deletions src/utils/withTable.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
export default withTable;