diff --git a/.changeset/flat-parts-think.md b/.changeset/flat-parts-think.md new file mode 100644 index 0000000..c212041 --- /dev/null +++ b/.changeset/flat-parts-think.md @@ -0,0 +1,5 @@ +--- +'@dolphin/chrome-extension': patch +--- + +fix: error info length to long diff --git a/.changeset/shiny-drinks-strive.md b/.changeset/shiny-drinks-strive.md new file mode 100644 index 0000000..f1629f3 --- /dev/null +++ b/.changeset/shiny-drinks-strive.md @@ -0,0 +1,5 @@ +--- +'@dolphin/chrome-extension': patch +--- + +feat: support option to convert table to html diff --git a/.changeset/twelve-socks-crash.md b/.changeset/twelve-socks-crash.md new file mode 100644 index 0000000..0271a58 --- /dev/null +++ b/.changeset/twelve-socks-crash.md @@ -0,0 +1,5 @@ +--- +'@dolphin/lark': patch +--- + +feat: support rowspan/colspan diff --git a/apps/chrome-extension/src/common/issue.ts b/apps/chrome-extension/src/common/issue.ts index fc92a13..652e5fb 100644 --- a/apps/chrome-extension/src/common/issue.ts +++ b/apps/chrome-extension/src/common/issue.ts @@ -32,24 +32,31 @@ enum Label { function generateIssueUrl(issue: Issue): string { const { title, body, labels = [], template } = issue - const baseUrl = - 'https://github.com/whale4113/cloud-document-converter/issues/new' - const params = new URLSearchParams({ - title: title, - body: body, - labels: labels.join(','), - template, - }) + const url = new URL( + 'https://github.com/whale4113/cloud-document-converter/issues/new', + ) + + if (title) url.searchParams.set('title', title) + if (body) url.searchParams.set('body', body) + if (labels.length > 0) url.searchParams.set('labels', labels.join(',')) + if (template) url.searchParams.set('template', template) - return `${baseUrl}?${params.toString()}` + return url.toString() } export const reportBug = (error: unknown): void => { + let errorInfo = JSON.stringify(serializeError(error), null, 2) + const MAX_ERROR_LENGTH = 1000 + if (errorInfo.length > MAX_ERROR_LENGTH) { + errorInfo = + errorInfo.slice(0, MAX_ERROR_LENGTH) + '\n...[truncated due to length]' + } + const url = generateIssueUrl({ title: '', body: i18next.t(CommonTranslationKey.ISSUE_TEMPLATE_BODY, { version, - errorInfo: JSON.stringify(serializeError(error), null, 2), + errorInfo, ns: Namespace.COMMON, interpolation: { escapeValue: false }, }), diff --git a/apps/chrome-extension/src/common/settings.ts b/apps/chrome-extension/src/common/settings.ts index babf753..c983935 100644 --- a/apps/chrome-extension/src/common/settings.ts +++ b/apps/chrome-extension/src/common/settings.ts @@ -7,7 +7,7 @@ export enum SettingKey { Locale = 'general.locale', Theme = 'general.theme', DownloadMethod = 'download.method', - TableWithNonPhrasingContent = 'general.table_with_non_phrasing_content', + Table = 'general.table', Grid = 'general.grid', TextHighlight = 'general.text_highlight', DownloadFileWithUniqueName = 'download.file_with_unique_name', @@ -24,8 +24,9 @@ export enum DownloadMethod { ShowSaveFilePicker = 'showSaveFilePicker', } -export enum TableWithNonPhrasingContent { +export enum Table { Filtered = 'filtered', + NonPhrasingContentToHTML = 'nonPhrasingContentToHTML', ToHTML = 'toHTML', } @@ -39,7 +40,7 @@ export interface Settings { [SettingKey.Locale]: string [SettingKey.Theme]: (typeof Theme)[keyof typeof Theme] [SettingKey.DownloadMethod]: (typeof DownloadMethod)[keyof typeof DownloadMethod] - [SettingKey.TableWithNonPhrasingContent]: (typeof TableWithNonPhrasingContent)[keyof typeof TableWithNonPhrasingContent] + [SettingKey.Table]: (typeof Table)[keyof typeof Table] [SettingKey.Grid]: (typeof Grid)[keyof typeof Grid] [SettingKey.TextHighlight]: boolean [SettingKey.DownloadFileWithUniqueName]: boolean @@ -51,7 +52,7 @@ export const fallbackSettings: Settings = { [SettingKey.DownloadMethod]: supported ? DownloadMethod.ShowSaveFilePicker : DownloadMethod.Direct, - [SettingKey.TableWithNonPhrasingContent]: TableWithNonPhrasingContent.ToHTML, + [SettingKey.Table]: Table.NonPhrasingContentToHTML, [SettingKey.Grid]: Grid.Flatten, [SettingKey.TextHighlight]: true, [SettingKey.DownloadFileWithUniqueName]: false, diff --git a/apps/chrome-extension/src/common/utils.ts b/apps/chrome-extension/src/common/utils.ts index 28590e6..f770ddf 100644 --- a/apps/chrome-extension/src/common/utils.ts +++ b/apps/chrome-extension/src/common/utils.ts @@ -9,6 +9,12 @@ import { } from '@dolphin/lark' import { v4 as uuidv4 } from 'uuid' import { Second, waitForFunction } from '@dolphin/common' +import { + SettingKey, + Table as TableSetting, + Grid as GridSetting, + type Settings, +} from './settings' interface Ref { current: T @@ -101,37 +107,150 @@ export class UniqueFileName { } } -export const transformInvalidTablesToHtml = ( - invalidTables: TableWithParent[], +export const mapTableBySettings = ( + tables: TableWithParent[], + settings: Pick, +): TableWithParent[] => { + if (settings[SettingKey.Table] === TableSetting.Filtered) { + return [] + } + + return tables + .map(table => { + if (!table.inner.data?.invalid) return table + + const tableIndex = table.parent?.children.findIndex( + child => child === table.inner, + ) + + if (tableIndex !== undefined && tableIndex !== -1) { + const inner = { + ...table.inner, + children: table.inner.children.map(row => ({ + ...row, + children: row.children.map(cell => ({ + ...cell, + children: cell.data?.invalidChildren ?? cell.children, + })), + })), + } as mdast.Table + + table.parent?.children.splice(tableIndex, 1, inner) + + return { + ...table, + inner, + } + } + + return table + }) + .filter(table => + settings[SettingKey.Table] === TableSetting.NonPhrasingContentToHTML + ? table.inner.data?.invalid + : true, + ) +} + +/** + * Filters out redundant cells that are covered by rowSpan/colSpan + * and adds appropriate HTML properties to the spanning cells. + * + * @param table The Markdown AST table to process + */ +const processTableSpans = (table: mdast.Table): void => { + const occupied: boolean[][] = [] + + for (let rowIndex = 0; rowIndex < table.children.length; rowIndex++) { + const row = table.children[rowIndex] + const newCells: mdast.TableCell[] = [] + + for ( + let columnIndex = 0; + columnIndex < row.children.length; + columnIndex++ + ) { + // If this position is covered by a previous spanning cell, skip it + if (occupied[rowIndex]?.[columnIndex]) continue + + const cell = row.children[columnIndex] + newCells.push(cell) + + const rowSpan = cell.data?.rowSpan ?? 1 + const colSpan = cell.data?.colSpan ?? 1 + + if (rowSpan > 1 || colSpan > 1) { + // Ensure data and hProperties objects exist + cell.data ??= {} + cell.data.hProperties ??= {} + + // Add HTML span properties for correct rendering + if (rowSpan > 1) cell.data.hProperties['rowSpan'] = rowSpan + if (colSpan > 1) cell.data.hProperties['colSpan'] = colSpan + + // Mark the area covered by this spanning cell as occupied + for (let i = 0; i < rowSpan; i++) { + for (let j = 0; j < colSpan; j++) { + // Skip the current cell itself + if (i === 0 && j === 0) continue + const targetRow = rowIndex + i + const targetCol = columnIndex + j + occupied[targetRow] ??= [] + occupied[targetRow][targetCol] = true + } + } + } + } + + // Update row children with only the non-redundant cells + row.children = newCells + } +} + +export const transformTableToHtml = ( + tables: TableWithParent[], options: { allowDangerousHtml: boolean } = { allowDangerousHtml: false }, ): void => { - invalidTables.forEach(invalidTable => { - const invalidTableIndex = invalidTable.parent?.children.findIndex( - child => child === invalidTable.inner, + tables.forEach(table => { + const tableIndex = table.parent?.children.findIndex( + child => child === table.inner, ) - if (invalidTableIndex !== undefined && invalidTableIndex !== -1) { - invalidTable.parent?.children.splice(invalidTableIndex, 1, { + if (tableIndex !== undefined && tableIndex !== -1) { + processTableSpans(table.inner) + + const hastTable = toHast(table.inner, { + allowDangerousHtml: options.allowDangerousHtml, + }) + + if (hastTable.type === 'element') { + const hastColGroup: hast.Element = { + type: 'element', + tagName: 'colgroup', + properties: {}, + children: + table.inner.data?.colWidths?.map(width => ({ + type: 'element', + tagName: 'col', + properties: { + width: + table.inner.data?.type === BlockType.GRID + ? `${width.toFixed(2)}%` + : width, + }, + children: [], + })) ?? [], + } + + hastTable.children = ([hastColGroup] as hast.ElementContent[]).concat( + hastTable.children, + ) + } + + table.parent?.children.splice(tableIndex, 1, { type: 'html', - value: toHtml( - toHast( - { - ...invalidTable.inner, - children: invalidTable.inner.children.map(row => ({ - ...row, - children: row.children.map(cell => ({ - ...cell, - children: cell.data?.invalidChildren ?? cell.children, - })), - })), - } as mdast.Table, - { - allowDangerousHtml: options.allowDangerousHtml, - }, - ), - { - allowDangerousHtml: options.allowDangerousHtml, - }, - ), + value: toHtml(hastTable, { + allowDangerousHtml: options.allowDangerousHtml, + }), }) } }) @@ -245,36 +364,28 @@ export const transformMentionUsers = async ( } } -export interface TransformTableWithParentsOptions { - transformGridToHtml: boolean - transformInvalidTablesToHtml: boolean -} - -export const transformTableWithParents = ( - tableWithParents: TableWithParent[], - options: TransformTableWithParentsOptions, +export const transformTableBySettings = ( + tables: TableWithParent[], + settings: Pick, ): void => { - if (options.transformGridToHtml) { + if (settings[SettingKey.Grid] === GridSetting.ToHTML) { transformGridToHtml( - tableWithParents.filter(item => item.inner.data?.type === BlockType.GRID), + tables.filter(item => item.inner.data?.type === BlockType.GRID), { allowDangerousHtml: true, }, ) } - if (options.transformInvalidTablesToHtml) { - transformInvalidTablesToHtml( - tableWithParents.filter( - item => - item.inner.data?.invalid && - (options.transformGridToHtml - ? item.inner.data.type !== BlockType.GRID - : true), - ), - { - allowDangerousHtml: true, - }, - ) - } + transformTableToHtml( + mapTableBySettings( + settings[SettingKey.Grid] === GridSetting.ToHTML + ? tables.filter(item => item.inner.data?.type !== BlockType.GRID) + : tables, + settings, + ), + { + allowDangerousHtml: true, + }, + ) } diff --git a/apps/chrome-extension/src/pages/options/general.vue b/apps/chrome-extension/src/pages/options/general.vue index f5dfa41..bb73681 100644 --- a/apps/chrome-extension/src/pages/options/general.vue +++ b/apps/chrome-extension/src/pages/options/general.vue @@ -23,12 +23,7 @@ import { } from '@/components/ui/select' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' -import { - SettingKey, - TableWithNonPhrasingContent, - Grid, - Theme, -} from '@/common/settings' +import { SettingKey, Table, Grid, Theme } from '@/common/settings' import { useI18n } from 'vue-i18n' import { useSettings } from '../shared/settings' @@ -37,7 +32,7 @@ const { locale, availableLocales, t } = useI18n() const schema = z.object({ [SettingKey.Locale]: z.enum(availableLocales.value), [SettingKey.Theme]: z.enum(Theme), - [SettingKey.TableWithNonPhrasingContent]: z.enum(TableWithNonPhrasingContent), + [SettingKey.Table]: z.enum(Table), [SettingKey.Grid]: z.enum(Grid), [SettingKey.TextHighlight]: z.boolean(), }) @@ -55,7 +50,7 @@ watch(query.data, newValues => { values: pick(newValues, [ SettingKey.Locale, SettingKey.Theme, - SettingKey.TableWithNonPhrasingContent, + SettingKey.Table, SettingKey.Grid, SettingKey.TextHighlight, ]), @@ -157,16 +152,12 @@ const onSubmit = handleSubmit.withControlled(async values => { - + - {{ t('general.table_with_non_phrasing_content') }} + {{ + t('general.table') + }} @@ -176,26 +167,27 @@ const onSubmit = handleSubmit.withControlled(async values => { @update:model-value="field.onChange" > - + {{ t('general.table_with_non_phrasing_content.filtered') }} + :key="`${locale}_${Table.Filtered}`" + :value="Table.Filtered" + >{{ t('general.table.filtered') }} + + {{ t('general.table.non_phrasing_content_to_html') }} {{ t('general.table_with_non_phrasing_content.to_html') }} + :key="`${locale}_${Table.ToHTML}`" + :value="Table.ToHTML" + >{{ t('general.table.to_html') }} diff --git a/apps/chrome-extension/src/pages/shared/i18n.ts b/apps/chrome-extension/src/pages/shared/i18n.ts index c78bd2d..5338c0e 100644 --- a/apps/chrome-extension/src/pages/shared/i18n.ts +++ b/apps/chrome-extension/src/pages/shared/i18n.ts @@ -23,11 +23,12 @@ export const i18n = createI18n({ 'general.theme.light': 'Light', 'general.theme.dark': 'Dark', 'general.theme.system': 'System', - 'general.table_with_non_phrasing_content': - 'Handling of tables with non-phrasing content (list, blockquote, code block, etc.)', - 'general.table_with_non_phrasing_content.placeholder': 'Select handling', - 'general.table_with_non_phrasing_content.filtered': 'Filtered', - 'general.table_with_non_phrasing_content.to_html': 'To HTML', + 'general.table': 'Handling of tables', + 'general.table.placeholder': 'Select handling', + 'general.table.filtered': 'Filter non-phrasing content', + 'general.table.non_phrasing_content_to_html': + 'Convert table with non-phrasing content to HTML', + 'general.table.to_html': 'Convert all tables to HTML', 'general.grid': 'Handling of grids', 'general.grid.placeholder': 'Select handling', 'general.grid.flatten': 'Flatten', @@ -65,11 +66,12 @@ export const i18n = createI18n({ 'general.theme.light': '浅色', 'general.theme.dark': '深色', 'general.theme.system': '跟随系统', - 'general.table_with_non_phrasing_content': - '含有块级内容(列表、引用、代码块等)的表格的处理方式', - 'general.table_with_non_phrasing_content.placeholder': '选择处理方式', - 'general.table_with_non_phrasing_content.filtered': '过滤块级内容', - 'general.table_with_non_phrasing_content.to_html': '转换为 HTML', + 'general.table': '如何处理表格', + 'general.table.placeholder': '选择处理方式', + 'general.table.filtered': '过滤块级内容', + 'general.table.non_phrasing_content_to_html': + '将含有块级内容的表格转换为 HTML', + 'general.table.to_html': '所有表格都转换为 HTML', 'general.grid': '分栏的处理方式', 'general.grid.placeholder': '选择处理方式', 'general.grid.flatten': '平铺分栏', diff --git a/apps/chrome-extension/src/pages/shared/settings.ts b/apps/chrome-extension/src/pages/shared/settings.ts index 52017e1..ff01370 100644 --- a/apps/chrome-extension/src/pages/shared/settings.ts +++ b/apps/chrome-extension/src/pages/shared/settings.ts @@ -22,7 +22,7 @@ export const useSettings = < | SettingKey.Locale | SettingKey.Theme | SettingKey.DownloadMethod - | SettingKey.TableWithNonPhrasingContent + | SettingKey.Table | SettingKey.Grid | SettingKey.TextHighlight | SettingKey.DownloadFileWithUniqueName, @@ -42,7 +42,7 @@ export const useSettings = < SettingKey.Locale, SettingKey.Theme, SettingKey.DownloadMethod, - SettingKey.TableWithNonPhrasingContent, + SettingKey.Table, SettingKey.Grid, SettingKey.TextHighlight, SettingKey.DownloadFileWithUniqueName, diff --git a/apps/chrome-extension/src/scripts/copy-lark-docx-as-markdown.ts b/apps/chrome-extension/src/scripts/copy-lark-docx-as-markdown.ts index 19c9a28..548927c 100644 --- a/apps/chrome-extension/src/scripts/copy-lark-docx-as-markdown.ts +++ b/apps/chrome-extension/src/scripts/copy-lark-docx-as-markdown.ts @@ -7,14 +7,9 @@ import { confirm } from '../common/notification' import { reportBug } from '../common/issue' import { transformMentionUsers, - transformTableWithParents, + transformTableBySettings, } from '../common/utils' -import { - getSettings, - SettingKey, - TableWithNonPhrasingContent, - Grid, -} from '../common/settings' +import { getSettings, SettingKey, Grid } from '../common/settings' const enum TranslationKey { FAILED_TO_COPY_IMAGES = 'failed_to_copy_images', @@ -80,7 +75,7 @@ const main = async () => { } const settings = await getSettings([ - SettingKey.TableWithNonPhrasingContent, + SettingKey.Table, SettingKey.Grid, SettingKey.TextHighlight, ]) @@ -109,12 +104,7 @@ const main = async () => { }) .filter(isDefined) - transformTableWithParents(tableWithParents, { - transformGridToHtml: settings[SettingKey.Grid] === Grid.ToHTML, - transformInvalidTablesToHtml: - settings[SettingKey.TableWithNonPhrasingContent] === - TableWithNonPhrasingContent.ToHTML, - }) + transformTableBySettings(tableWithParents, settings) const markdown = Docx.stringify(root) diff --git a/apps/chrome-extension/src/scripts/download-lark-docx-as-markdown.ts b/apps/chrome-extension/src/scripts/download-lark-docx-as-markdown.ts index 403f218..d9999b9 100644 --- a/apps/chrome-extension/src/scripts/download-lark-docx-as-markdown.ts +++ b/apps/chrome-extension/src/scripts/download-lark-docx-as-markdown.ts @@ -13,13 +13,9 @@ import { transformMentionUsers, UniqueFileName, withSignal, - transformTableWithParents, + transformTableBySettings, } from '../common/utils' -import { - getSettings, - TableWithNonPhrasingContent, - Grid, -} from '../common/settings' +import { getSettings, Grid } from '../common/settings' import { DownloadMethod, SettingKey } from '@/common/settings' const uniqueFileName = new UniqueFileName() @@ -549,7 +545,7 @@ const main = async (options: { signal?: AbortSignal } = {}) => { const settings = await getSettings([ SettingKey.DownloadMethod, - SettingKey.TableWithNonPhrasingContent, + SettingKey.Table, SettingKey.Grid, SettingKey.TextHighlight, SettingKey.DownloadFileWithUniqueName, @@ -581,12 +577,7 @@ const main = async (options: { signal?: AbortSignal } = {}) => { }) const singleFileContent = () => { - transformTableWithParents(tableWithParents, { - transformGridToHtml: settings[SettingKey.Grid] === Grid.ToHTML, - transformInvalidTablesToHtml: - settings[SettingKey.TableWithNonPhrasingContent] === - TableWithNonPhrasingContent.ToHTML, - }) + transformTableBySettings(tableWithParents, settings) const markdown = Docx.stringify(root) @@ -649,12 +640,7 @@ const main = async (options: { signal?: AbortSignal } = {}) => { zipFs.addBlob(filename, content) }) - transformTableWithParents(tableWithParents, { - transformGridToHtml: settings[SettingKey.Grid] === Grid.ToHTML, - transformInvalidTablesToHtml: - settings[SettingKey.TableWithNonPhrasingContent] === - TableWithNonPhrasingContent.ToHTML, - }) + transformTableBySettings(tableWithParents, settings) const markdown = Docx.stringify(root) diff --git a/apps/chrome-extension/src/scripts/view-lark-docx-as-markdown.ts b/apps/chrome-extension/src/scripts/view-lark-docx-as-markdown.ts index 5fdab0b..9faa4e0 100644 --- a/apps/chrome-extension/src/scripts/view-lark-docx-as-markdown.ts +++ b/apps/chrome-extension/src/scripts/view-lark-docx-as-markdown.ts @@ -7,14 +7,9 @@ import { confirm } from '../common/notification' import { reportBug } from '../common/issue' import { transformMentionUsers, - transformTableWithParents, + transformTableBySettings, } from '../common/utils' -import { - getSettings, - SettingKey, - TableWithNonPhrasingContent, - Grid, -} from '../common/settings' +import { getSettings, SettingKey, Grid } from '../common/settings' const enum TranslationKey { FAILED_TO_LOAD_IMAGES = 'failed_to_load_images', @@ -85,7 +80,7 @@ const main = async () => { } const settings = await getSettings([ - SettingKey.TableWithNonPhrasingContent, + SettingKey.Table, SettingKey.Grid, SettingKey.TextHighlight, ]) @@ -113,12 +108,7 @@ const main = async () => { }) .filter(isDefined) - transformTableWithParents(tableWithParents, { - transformGridToHtml: settings[SettingKey.Grid] === Grid.ToHTML, - transformInvalidTablesToHtml: - settings[SettingKey.TableWithNonPhrasingContent] === - TableWithNonPhrasingContent.ToHTML, - }) + transformTableBySettings(tableWithParents, settings) const markdown = Docx.stringify(root) diff --git a/apps/chrome-extension/tsconfig.json b/apps/chrome-extension/tsconfig.json index 064eeea..5d18e6f 100644 --- a/apps/chrome-extension/tsconfig.json +++ b/apps/chrome-extension/tsconfig.json @@ -4,7 +4,8 @@ { "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }, { "path": "./tsconfig.extension.json" }, - { "path": "./tsconfig.pages.json" } + { "path": "./tsconfig.pages.json" }, + { "path": "./tsconfig.test.json" } ], "compilerOptions": { "baseUrl": ".", diff --git a/apps/chrome-extension/tsconfig.node.json b/apps/chrome-extension/tsconfig.node.json index 5872423..b2b430b 100644 --- a/apps/chrome-extension/tsconfig.node.json +++ b/apps/chrome-extension/tsconfig.node.json @@ -6,7 +6,6 @@ "extends": "@dolphin/typescript-config/node.json", "include": [ "scripts", - "tests", "tsdown.config.ts", "vite.config.ts", "vitest.config.ts" diff --git a/apps/chrome-extension/tsconfig.test.json b/apps/chrome-extension/tsconfig.test.json new file mode 100644 index 0000000..d9f220b --- /dev/null +++ b/apps/chrome-extension/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.test.tsbuildinfo", + "noEmit": true, + "erasableSyntaxOnly": false + }, + "extends": "@dolphin/typescript-config/node.json", + "include": ["tests"] +} diff --git a/packages/lark/package.json b/packages/lark/package.json index 127ea3c..3501797 100644 --- a/packages/lark/package.json +++ b/packages/lark/package.json @@ -30,19 +30,18 @@ "test": "vitest run" }, "devDependencies": { - "@types/lodash-es": "catalog:types", "@types/mdast": "catalog:types", "@types/hast": "catalog:types" }, "dependencies": { "@dolphin/common": "workspace:*", "js-base64": "catalog:prod", - "lodash-es": "catalog:utils", "mdast-util-gfm-strikethrough": "catalog:prod", "mdast-util-gfm-table": "catalog:prod", "mdast-util-gfm-task-list-item": "catalog:prod", "mdast-util-math": "catalog:prod", - "mdast-util-to-markdown": "catalog:markdown" + "mdast-util-to-markdown": "catalog:markdown", + "es-toolkit": "catalog:prod" }, "volta": { "extends": "../../package.json" diff --git a/packages/lark/src/docx.ts b/packages/lark/src/docx.ts index b025fc2..dc364ff 100644 --- a/packages/lark/src/docx.ts +++ b/packages/lark/src/docx.ts @@ -1,5 +1,5 @@ import type * as mdast from 'mdast' -import chunk from 'lodash-es/chunk' +import { chunk } from 'es-toolkit/array' import { toBlob as svgToBlob, imageDataToBlob, @@ -25,8 +25,9 @@ import { isListItemContent, } from './utils/mdast' import { resolveFileDownloadUrl } from './file' -import isString from 'lodash-es/isString' -import escape from 'lodash-es/escape' +import { isString } from 'es-toolkit/compat' +import { escape } from 'es-toolkit/compat' +import { toCamelCaseKeys } from 'es-toolkit/object' declare module 'mdast' { interface ImageData { @@ -49,11 +50,14 @@ declare module 'mdast' { type?: BlockType.TABLE | BlockType.GRID colWidths?: number[] invalid?: boolean + cellSet?: Record } interface TableCellData { width?: number invalidChildren?: mdast.Nodes[] + rowSpan?: number + colSpan?: number } interface InlineCodeData { @@ -265,17 +269,33 @@ interface ImageBlock extends Block { } } +interface MergeInfo { + row_span: number + col_span: number +} + +interface ColumnData { + column_width: number +} + +interface CellData { + merge_info: MergeInfo +} + interface TableBlock extends Block { type: BlockType.TABLE snapshot: { type: BlockType.TABLE rows_id: string[] columns_id: string[] + column_set: Record + cell_set: Record } } interface TableCellBlock extends Block { type: BlockType.CELL + cellId: string } interface Grid extends Block { @@ -1437,7 +1457,12 @@ export class Transformer { let table: mdast.Table = { type: 'table', children: [], - data: { type: block.type }, + data: { + type: block.type, + ...(block.type === BlockType.TABLE + ? { cellSet: block.snapshot.cell_set } + : {}), + }, } table = this.transformParentBlock( @@ -1451,10 +1476,13 @@ export class Transformer { typeof cell.data?.width === 'number', ) const colWidths = - block.type === BlockType.GRID && - widthCells.length === tableCells.length - ? widthCells.map(cell => cell.data.width) - : undefined + block.type === BlockType.GRID + ? widthCells.length === tableCells.length + ? widthCells.map(cell => cell.data.width) + : undefined + : block.snapshot.columns_id.map( + id => block.snapshot.column_set[id].column_width, + ) table.data = { ...table.data, @@ -1488,7 +1516,14 @@ export class Transformer { children: [], ...(block.type === BlockType.GRID_COLUMN ? { data: { width: block.snapshot.width_ratio } } - : {}), + : { + data: { + ...toCamelCaseKeys( + (this.parent as mdast.Table).data?.cellSet?.[block.cellId] + ?.merge_info, + ), + }, + }), } return this.transformParentBlock( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c18ff62..44661ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,9 +213,6 @@ catalogs: '@types/hast': specifier: ^3.0.4 version: 3.0.4 - '@types/lodash-es': - specifier: ^4.17.12 - version: 4.17.12 '@types/mdast': specifier: ^4.0.4 version: 4.0.4 @@ -226,9 +223,6 @@ catalogs: specifier: 2.0.0 version: 2.0.0 utils: - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 regexp.escape: specifier: 2.0.1 version: 2.0.1 @@ -446,12 +440,12 @@ importers: '@dolphin/common': specifier: workspace:* version: link:../common + es-toolkit: + specifier: catalog:prod + version: 1.39.10 js-base64: specifier: catalog:prod version: 3.7.8 - lodash-es: - specifier: catalog:utils - version: 4.17.21 mdast-util-gfm-strikethrough: specifier: catalog:prod version: 2.0.0 @@ -471,9 +465,6 @@ importers: '@types/hast': specifier: catalog:types version: 3.0.4 - '@types/lodash-es': - specifier: catalog:types - version: 4.17.12 '@types/mdast': specifier: catalog:types version: 4.0.4 @@ -1948,12 +1939,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/lodash-es@4.17.12': - resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} - - '@types/lodash@4.17.20': - resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} - '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -3699,9 +3684,6 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -6772,12 +6754,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/lodash-es@4.17.12': - dependencies: - '@types/lodash': 4.17.20 - - '@types/lodash@4.17.20': {} - '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -8701,8 +8677,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.21: {} - lodash.debounce@4.0.8: {} lodash.includes@4.3.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 56ed329..8530886 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -88,5 +88,4 @@ catalogs: '@types/node': 22.12.0 '@types/regexp.escape': 2.0.0 utils: - lodash-es: ^4.17.21 regexp.escape: 2.0.1