mirror of https://github.com/usememos/memos.git
refactor(table): rename resolveTableIndex, fix escapeCell backslash handling, remove redundant cast
- Rename `resolveTableIndex` useMemo value to `currentTableIndex` in Table.tsx so the name reflects a computed value rather than an action; update all references (callbacks, dependency arrays, JSDoc comment) - Fix `escapeCell` in markdown-table.ts: replace the single-char lookbehind regex `(?<!\\)|` with a character loop that counts consecutive backslashes before each pipe and only inserts an escape when the count is even, mirroring the parser logic and correctly handling sequences like `\\|` - Remove redundant `as ColumnAlignment` type assertion in `createEmptyTable`; TypeScript infers `\"none\"` correctly via contextual typing from the return type - Add regression test for the `\\|` round-trip case in markdown-table.test.ts
This commit is contained in:
parent
26dec86b70
commit
0ad53f9dd3
|
|
@ -34,8 +34,8 @@ export const Table = ({ children, className, node, ...props }: TableProps) => {
|
|||
|
||||
const tables = useMemo(() => findAllTables(memo.content), [memo.content]);
|
||||
|
||||
/** Resolve which markdown table index this rendered table corresponds to using AST source positions. */
|
||||
const resolveTableIndex = useMemo(() => {
|
||||
/** The index of the markdown table this rendered table corresponds to (from AST source positions). */
|
||||
const currentTableIndex = useMemo(() => {
|
||||
const nodeStart = node?.position?.start?.offset;
|
||||
if (nodeStart == null) return -1;
|
||||
|
||||
|
|
@ -50,16 +50,16 @@ export const Table = ({ children, className, node, ...props }: TableProps) => {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (resolveTableIndex < 0 || resolveTableIndex >= tables.length) return;
|
||||
if (currentTableIndex < 0 || currentTableIndex >= tables.length) return;
|
||||
|
||||
const parsed = parseMarkdownTable(tables[resolveTableIndex].text);
|
||||
const parsed = parseMarkdownTable(tables[currentTableIndex].text);
|
||||
if (!parsed) return;
|
||||
|
||||
setTableData(parsed);
|
||||
setTableIndex(resolveTableIndex);
|
||||
setTableIndex(currentTableIndex);
|
||||
setDialogOpen(true);
|
||||
},
|
||||
[tables, resolveTableIndex],
|
||||
[tables, currentTableIndex],
|
||||
);
|
||||
|
||||
const handleDeleteClick = useCallback(
|
||||
|
|
@ -67,12 +67,12 @@ export const Table = ({ children, className, node, ...props }: TableProps) => {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (resolveTableIndex < 0) return;
|
||||
if (currentTableIndex < 0) return;
|
||||
|
||||
setTableIndex(resolveTableIndex);
|
||||
setTableIndex(currentTableIndex);
|
||||
setDeleteDialogOpen(true);
|
||||
},
|
||||
[resolveTableIndex],
|
||||
[currentTableIndex],
|
||||
);
|
||||
|
||||
const handleConfirmEdit = useCallback(
|
||||
|
|
|
|||
|
|
@ -150,6 +150,24 @@ describe("serializeMarkdownTable", () => {
|
|||
expect(parsed?.rows[0][1]).toBe("baz");
|
||||
});
|
||||
|
||||
it("round-trips a cell whose value has two backslashes before a pipe (\\\\\\\\|)", () => {
|
||||
// Cell value "foo\\|bar" (two backslashes + pipe) is a valid parsed value
|
||||
// that comes from markdown "foo\\\\\\|bar". The old escapeCell regex
|
||||
// (?<!\\)\\| would NOT escape the pipe (it sees one \\ before the | and
|
||||
// thinks it is already escaped), causing the parser to split on the pipe.
|
||||
// The fixed escapeCell counts all consecutive backslashes: 2 is even, so
|
||||
// it correctly inserts an escape backslash.
|
||||
const data: TableData = {
|
||||
headers: ["A"],
|
||||
rows: [["foo\\\\|bar"]],
|
||||
alignments: ["none"],
|
||||
};
|
||||
const md = serializeMarkdownTable(data);
|
||||
const parsed = parseMarkdownTable(md);
|
||||
expect(parsed?.headers.length).toBe(1); // pipe must NOT split into extra columns
|
||||
expect(parsed?.rows[0][0]).toBe("foo\\\\|bar");
|
||||
});
|
||||
|
||||
it("round-trips through parse and serialize", () => {
|
||||
const original = `| Name | Age |
|
||||
| ----- | --- |
|
||||
|
|
|
|||
|
|
@ -107,7 +107,22 @@ export function serializeMarkdownTable(data: TableData): string {
|
|||
const { headers, rows, alignments } = data;
|
||||
const colCount = headers.length;
|
||||
|
||||
const escapeCell = (text: string): string => text.replace(/(?<!\\)\|/g, "\\|");
|
||||
const escapeCell = (text: string): string => {
|
||||
let result = "";
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (text[i] === "|") {
|
||||
let backslashes = 0;
|
||||
let j = i - 1;
|
||||
while (j >= 0 && text[j] === "\\") {
|
||||
backslashes++;
|
||||
j--;
|
||||
}
|
||||
if (backslashes % 2 === 0) result += "\\";
|
||||
}
|
||||
result += text[i];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Calculate maximum width per column (minimum 3 for the separator).
|
||||
const widths: number[] = [];
|
||||
|
|
@ -214,6 +229,6 @@ export function createEmptyTable(cols = 2, rows = 2): TableData {
|
|||
return {
|
||||
headers: Array.from({ length: cols }, (_, i) => `Header ${i + 1}`),
|
||||
rows: Array.from({ length: rows }, () => Array.from({ length: cols }, () => "")),
|
||||
alignments: Array.from({ length: cols }, () => "none" as ColumnAlignment),
|
||||
alignments: Array.from({ length: cols }, () => "none"),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue