From 04af9401449235f04ff32b12a6532937efd14341 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Sat, 2 Nov 2024 21:01:11 +0800 Subject: [PATCH] feat: export to word --- package.json | 3 + src/main/ipc.ts | 12 +- src/main/services/ExportService.ts | 222 ++++++++++++++++++++ src/main/services/FileManager.ts | 24 ++- src/preload/index.d.ts | 10 +- src/preload/index.ts | 6 +- src/renderer/src/assets/styles/index.scss | 1 + src/renderer/src/i18n/locales/en-us.json | 11 + src/renderer/src/i18n/locales/zh-cn.json | 11 + src/renderer/src/i18n/locales/zh-tw.json | 11 + src/renderer/src/pages/home/Tabs/Topics.tsx | 10 +- src/renderer/src/utils/export.ts | 33 +-- yarn.lock | 144 ++++++++++++- 13 files changed, 478 insertions(+), 20 deletions(-) create mode 100644 src/main/services/ExportService.ts diff --git a/package.json b/package.json index 5f37f4e5..02e2c3b2 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,14 @@ "@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/utils": "^3.0.0", "archiver": "^7.0.1", + "docx": "^9.0.2", "electron-log": "^5.1.5", "electron-store": "^8.2.0", "electron-updater": "^6.3.9", "electron-window-state": "^5.0.3", "fs-extra": "^11.2.0", "html2canvas": "^1.4.1", + "markdown-it": "^14.1.0", "officeparser": "^4.1.1", "sharp": "^0.33.5", "unzipper": "^0.12.3", @@ -61,6 +63,7 @@ "@reduxjs/toolkit": "^2.2.5", "@types/fs-extra": "^11", "@types/lodash": "^4.17.5", + "@types/markdown-it": "^14", "@types/node": "^18.19.9", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", diff --git a/src/main/ipc.ts b/src/main/ipc.ts index d88f7771..cb03797f 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -5,17 +5,18 @@ import { BrowserWindow, ipcMain, session, shell } from 'electron' import { appConfig, titleBarOverlayDark, titleBarOverlayLight } from './config' import AppUpdater from './services/AppUpdater' import BackupManager from './services/BackupManager' +import { ExportService } from './services/ExportService' import FileManager from './services/FileManager' import { compress, decompress } from './utils/zip' import { createMinappWindow } from './window' const fileManager = new FileManager() const backupManager = new BackupManager() +const exportService = new ExportService(fileManager) export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { const { autoUpdater } = new AppUpdater(mainWindow) - // IPC ipcMain.handle('app:info', () => ({ version: app.getVersion(), isPackaged: app.isPackaged, @@ -33,13 +34,17 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle('reload', () => mainWindow.reload()) + // zip ipcMain.handle('zip:compress', (_, text: string) => compress(text)) ipcMain.handle('zip:decompress', (_, text: Buffer) => decompress(text)) + + // backup ipcMain.handle('backup:backup', backupManager.backup) ipcMain.handle('backup:restore', backupManager.restore) ipcMain.handle('backup:backupToWebdav', backupManager.backupToWebdav) ipcMain.handle('backup:restoreFromWebdav', backupManager.restoreFromWebdav) + // file ipcMain.handle('file:open', fileManager.open) ipcMain.handle('file:save', fileManager.save) ipcMain.handle('file:select', fileManager.selectFile) @@ -54,7 +59,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle('file:saveImage', fileManager.saveImage) ipcMain.handle('file:base64Image', fileManager.base64Image) ipcMain.handle('file:download', fileManager.downloadFile) + ipcMain.handle('file:copy', fileManager.copyFile) + // minapp ipcMain.handle('minapp', (_, args) => { createMinappWindow({ url: args.url, @@ -66,6 +73,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) }) + // theme ipcMain.handle('set-theme', (_, theme: 'light' | 'dark') => { appConfig.set('theme', theme) mainWindow?.setTitleBarOverlay && @@ -79,4 +87,6 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { update: await autoUpdater.checkForUpdates() } }) + + ipcMain.handle('export:word', exportService.exportToWord) } diff --git a/src/main/services/ExportService.ts b/src/main/services/ExportService.ts new file mode 100644 index 00000000..bf2636bd --- /dev/null +++ b/src/main/services/ExportService.ts @@ -0,0 +1,222 @@ +/* eslint-disable no-case-declarations */ +// ExportService + +import { AlignmentType, BorderStyle, Document, HeadingLevel, Packer, Paragraph, ShadingType, TextRun } from 'docx' +import { dialog } from 'electron' +import Logger from 'electron-log' +import MarkdownIt from 'markdown-it' + +import FileManager from './FileManager' + +export class ExportService { + private fileManager: FileManager + private md: MarkdownIt + + constructor(fileManager: FileManager) { + this.fileManager = fileManager + this.md = new MarkdownIt() + } + + private convertMarkdownToDocxElements(markdown: string) { + const tokens = this.md.parse(markdown, {}) + const elements: any[] = [] + let listLevel = 0 + + const processInlineTokens = (tokens: any[]): TextRun[] => { + const runs: TextRun[] = [] + for (const token of tokens) { + switch (token.type) { + case 'text': + runs.push(new TextRun(token.content)) + break + case 'strong': + runs.push(new TextRun({ text: token.content, bold: true })) + break + case 'em': + runs.push(new TextRun({ text: token.content, italics: true })) + break + case 'code_inline': + runs.push(new TextRun({ text: token.content, font: 'Consolas', size: 20 })) + break + } + } + return runs + } + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i] + + switch (token.type) { + case 'heading_open': + // 获取标题级别 (h1 -> h6) + const level = parseInt(token.tag.slice(1)) as 1 | 2 | 3 | 4 | 5 | 6 + const headingText = tokens[i + 1].content + elements.push( + new Paragraph({ + text: headingText, + heading: HeadingLevel[`HEADING_${level}`], + spacing: { + before: 240, + after: 120 + } + }) + ) + i += 2 // 跳过内容标记和闭合标记 + break + + case 'paragraph_open': + const inlineTokens = tokens[i + 1].children || [] + elements.push( + new Paragraph({ + children: processInlineTokens(inlineTokens), + spacing: { + before: 120, + after: 120 + } + }) + ) + i += 2 + break + + case 'bullet_list_open': + listLevel++ + break + + case 'bullet_list_close': + listLevel-- + break + + case 'list_item_open': + const itemInlineTokens = tokens[i + 2].children || [] + elements.push( + new Paragraph({ + children: [ + new TextRun({ text: '•', bold: true }), + new TextRun({ text: '\t' }), + ...processInlineTokens(itemInlineTokens) + ], + indent: { + left: listLevel * 720 + } + }) + ) + i += 3 + break + + case 'fence': // 代码块 + const codeLines = token.content.split('\n') + elements.push( + new Paragraph({ + children: codeLines.map( + (line) => + new TextRun({ + text: line + '\n', + font: 'Consolas', + size: 20, + break: 1 + }) + ), + shading: { + type: ShadingType.SOLID, + color: 'F5F5F5' + }, + spacing: { + before: 120, + after: 120 + }, + border: { + top: { style: BorderStyle.SINGLE, size: 1, color: 'DDDDDD' }, + bottom: { style: BorderStyle.SINGLE, size: 1, color: 'DDDDDD' }, + left: { style: BorderStyle.SINGLE, size: 1, color: 'DDDDDD' }, + right: { style: BorderStyle.SINGLE, size: 1, color: 'DDDDDD' } + } + }) + ) + break + + case 'hr': + elements.push( + new Paragraph({ + children: [new TextRun({ text: '─'.repeat(50), color: '999999' })], + alignment: AlignmentType.CENTER + }) + ) + break + + case 'blockquote_open': + const quoteText = tokens[i + 2].content + elements.push( + new Paragraph({ + children: [ + new TextRun({ + text: quoteText, + italics: true + }) + ], + indent: { + left: 720 + }, + border: { + left: { + style: BorderStyle.SINGLE, + size: 3, + color: 'CCCCCC' + } + }, + spacing: { + before: 120, + after: 120 + } + }) + ) + i += 3 + break + } + } + + return elements + } + + public exportToWord = async (_: Electron.IpcMainInvokeEvent, markdown: string, fileName: string): Promise => { + try { + const elements = this.convertMarkdownToDocxElements(markdown) + + const doc = new Document({ + styles: { + paragraphStyles: [ + { + id: 'Normal', + name: 'Normal', + run: { + size: 24, + font: 'Arial' + } + } + ] + }, + sections: [ + { + properties: {}, + children: elements + } + ] + }) + + const buffer = await Packer.toBuffer(doc) + + const filePath = dialog.showSaveDialogSync({ + title: '保存文件', + filters: [{ name: 'Word Document', extensions: ['docx'] }], + defaultPath: fileName + }) + + if (filePath) { + await this.fileManager.writeFile(_, filePath, buffer) + Logger.info('[ExportService] Document exported successfully') + } + } catch (error) { + Logger.error('[ExportService] Export to Word failed:', error) + throw error + } + } +} diff --git a/src/main/services/FileManager.ts b/src/main/services/FileManager.ts index e8eb2b84..991ca48a 100644 --- a/src/main/services/FileManager.ts +++ b/src/main/services/FileManager.ts @@ -301,7 +301,7 @@ class FileManager { fileName: string, content: string, options?: SaveDialogOptions - ): Promise => { + ): Promise => { try { const result: SaveDialogReturnValue = await dialog.showSaveDialog({ title: '保存文件', @@ -312,8 +312,11 @@ class FileManager { if (!result.canceled && result.filePath) { await writeFileSync(result.filePath, content, { encoding: 'utf-8' }) } + + return result.filePath } catch (err) { logger.error('[IPC - Error]', 'An error occurred saving the file:', err) + return null } } @@ -431,6 +434,25 @@ class FileManager { return mimeToExtension[mimeType] || '.bin' } + + public copyFile = async (_: Electron.IpcMainInvokeEvent, id: string, destPath: string): Promise => { + try { + const sourcePath = path.join(this.storageDir, id) + + // 确保目标目录存在 + const destDir = path.dirname(destPath) + if (!fs.existsSync(destDir)) { + await fs.promises.mkdir(destDir, { recursive: true }) + } + + // 复制文件 + await fs.promises.copyFile(sourcePath, destPath) + logger.info('[FileManager] File copied successfully:', { from: sourcePath, to: destPath }) + } catch (error) { + logger.error('[FileManager] Copy file failed:', error) + throw error + } + } } export default FileManager diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 37ada499..ab0051cc 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -39,10 +39,18 @@ declare global { create: (fileName: string) => Promise write: (filePath: string, data: Uint8Array | string) => Promise open: (options?: OpenDialogOptions) => Promise<{ fileName: string; filePath: string; content: Buffer } | null> - save: (path: string, content: string | NodeJS.ArrayBufferView, options?: SaveDialogOptions) => void + save: ( + path: string, + content: string | NodeJS.ArrayBufferView, + options?: SaveDialogOptions + ) => Promise saveImage: (name: string, data: string) => void base64Image: (fileId: string) => Promise<{ mime: string; base64: string; data: string }> download: (url: string) => Promise + copy: (fileId: string, destPath: string) => Promise + } + export: { + toWord: (markdown: string, fileName: string) => Promise } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index ff43114d..1933df84 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -36,7 +36,11 @@ const api = { selectFolder: () => ipcRenderer.invoke('file:selectFolder'), saveImage: (name: string, data: string) => ipcRenderer.invoke('file:saveImage', name, data), base64Image: (fileId: string) => ipcRenderer.invoke('file:base64Image', fileId), - download: (url: string) => ipcRenderer.invoke('file:download', url) + download: (url: string) => ipcRenderer.invoke('file:download', url), + copy: (fileId: string, destPath: string) => ipcRenderer.invoke('file:copy', fileId, destPath) + }, + export: { + toWord: (markdown: string, fileName: string) => ipcRenderer.invoke('export:word', markdown, fileName) } } diff --git a/src/renderer/src/assets/styles/index.scss b/src/renderer/src/assets/styles/index.scss index 116ae16a..4df3a414 100644 --- a/src/renderer/src/assets/styles/index.scss +++ b/src/renderer/src/assets/styles/index.scss @@ -168,6 +168,7 @@ body, body[os='mac'] { #content-container { border-top-left-radius: 10px; + border-bottom-left-radius: 10px; border-left: 0.5px solid var(--color-border); box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.05); } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index ad039f76..b1762cea 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -84,6 +84,7 @@ "topics.export.title": "Export", "topics.export.image": "Export as image", "topics.export.md": "Export as markdown", + "topics.export.word": "Export as Word", "input.new_topic": "New Topic", "input.topics": " Topics ", "input.clear": "Clear", @@ -387,6 +388,16 @@ "words": { "knowledgeGraph": "Knowledge Graph", "visualization": "Visualization" + }, + "export": { + "attached_files": "Attached Files", + "user": "User", + "assistant": "Assistant", + "created": "Created", + "last_updated": "Last Updated", + "messages": "Messages", + "conversation_details": "Conversation Details", + "conversation_history": "Conversation History" } } } diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 16af530a..c1ba3d7f 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -84,6 +84,7 @@ "topics.export.title": "导出", "topics.export.image": "导出为图片", "topics.export.md": "导出为 Markdown", + "topics.export.word": "导出为 Word", "input.new_topic": "新话题", "input.topics": " 话题 ", "input.clear": "清空消息", @@ -387,6 +388,16 @@ "words": { "knowledgeGraph": "知识图谱", "visualization": "可视化" + }, + "export": { + "attached_files": "附件", + "user": "用户", + "assistant": "助手", + "created": "创建时间", + "last_updated": "最后更新", + "messages": "消息数", + "conversation_details": "会话详情", + "conversation_history": "会话历史" } } } diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index b06f417a..27ff408b 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -84,6 +84,7 @@ "topics.export.title": "匯出", "topics.export.image": "匯出為圖片", "topics.export.md": "匯出為 Markdown", + "topics.export.word": "導出為 Word", "input.new_topic": "新話題", "input.topics": " 話題 ", "input.clear": "清除", @@ -387,6 +388,16 @@ "words": { "knowledgeGraph": "知識圖譜", "visualization": "可視化" + }, + "export": { + "attached_files": "附件", + "user": "用戶", + "assistant": "助手", + "created": "創建時間", + "last_updated": "最後更新", + "messages": "訊息數", + "conversation_details": "會話詳情", + "conversation_history": "會話歷史" } } } diff --git a/src/renderer/src/pages/home/Tabs/Topics.tsx b/src/renderer/src/pages/home/Tabs/Topics.tsx index 225d9257..1bc07840 100644 --- a/src/renderer/src/pages/home/Tabs/Topics.tsx +++ b/src/renderer/src/pages/home/Tabs/Topics.tsx @@ -17,7 +17,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import store, { useAppSelector } from '@renderer/store' import { setGenerating } from '@renderer/store/runtime' import { Assistant, Topic } from '@renderer/types' -import { exportTopicAsMarkdown } from '@renderer/utils/export' +import { exportTopicAsMarkdown, topicToMarkdown } from '@renderer/utils/export' import { Dropdown, MenuProps } from 'antd' import dayjs from 'dayjs' import { findIndex } from 'lodash' @@ -141,6 +141,14 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic label: t('chat.topics.export.md'), key: 'markdown', onClick: () => exportTopicAsMarkdown(topic) + }, + { + label: t('chat.topics.export.word'), + key: 'word', + onClick: async () => { + const markdown = await topicToMarkdown(topic) + window.api.export.toWord(markdown, topic.name) + } } ] } diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index cffbf1c4..4cb562f4 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -1,24 +1,31 @@ import db from '@renderer/databases' import { Message, Topic } from '@renderer/types' -export const exportMessageAsMarkdown = (message: Message) => { - if (message.role === 'user') { - return `### User\n\n${message.content}` - } +export const messageToMarkdown = (message: Message) => { + const roleText = message.role === 'user' ? '🧑‍💻 User' : '🤖 Assistant' + const titleSection = `### ${roleText}` + const contentSection = message.content - return `### Assistant\n\n${message.content}` + return [titleSection, '', contentSection].join('\n') } -export const exportMessagesAsMarkdown = (messages: Message[]) => { - return messages.map((message) => exportMessageAsMarkdown(message)).join('\n\n---\n\n') +export const messagesToMarkdown = (messages: Message[]) => { + return messages.map((message) => messageToMarkdown(message)).join('\n\n---\n\n') +} + +export const topicToMarkdown = async (topic: Topic) => { + const topicName = `# ${topic.name}` + const topicMessages = await db.topics.get(topic.id) + + if (topicMessages) { + return topicName + '\n\n' + messagesToMarkdown(topicMessages.messages) + } + + return '' } export const exportTopicAsMarkdown = async (topic: Topic) => { const fileName = topic.name + '.md' - const topicMessages = await db.topics.get(topic.id) - if (topicMessages) { - const title = '# ' + topic.name + `\n\n` - const markdown = exportMessagesAsMarkdown(topicMessages.messages) - window.api.file.save(fileName, title + markdown) - } + const markdown = await topicToMarkdown(topic) + window.api.file.save(fileName, markdown) } diff --git a/yarn.lock b/yarn.lock index 2c0fad52..bac11503 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2094,6 +2094,13 @@ __metadata: languageName: node linkType: hard +"@types/linkify-it@npm:^5": + version: 5.0.0 + resolution: "@types/linkify-it@npm:5.0.0" + checksum: 10c0/7bbbf45b9dde17bf3f184fee585aef0e7342f6954f0377a24e4ff42ab5a85d5b806aaa5c8d16e2faf2a6b87b2d94467a196b7d2b85c9c7de2f0eaac5487aaab8 + languageName: node + linkType: hard + "@types/lodash@npm:^4.17.5": version: 4.17.7 resolution: "@types/lodash@npm:4.17.7" @@ -2101,6 +2108,16 @@ __metadata: languageName: node linkType: hard +"@types/markdown-it@npm:^14": + version: 14.1.2 + resolution: "@types/markdown-it@npm:14.1.2" + dependencies: + "@types/linkify-it": "npm:^5" + "@types/mdurl": "npm:^2" + checksum: 10c0/34f709f0476bd4e7b2ba7c3341072a6d532f1f4cb6f70aef371e403af8a08a7c372ba6907ac426bc618d356dab660c5b872791ff6c1ead80c483e0d639c6f127 + languageName: node + linkType: hard + "@types/mathjax@npm:^0.0.40": version: 0.0.40 resolution: "@types/mathjax@npm:0.0.40" @@ -2117,6 +2134,13 @@ __metadata: languageName: node linkType: hard +"@types/mdurl@npm:^2": + version: 2.0.0 + resolution: "@types/mdurl@npm:2.0.0" + checksum: 10c0/cde7bb571630ed1ceb3b92a28f7b59890bb38b8f34cd35326e2df43eebfc74985e6aa6fd4184e307393bad8a9e0783a519a3f9d13c8e03788c0f98e5ec869c5e + languageName: node + linkType: hard + "@types/minimatch@npm:*": version: 5.1.2 resolution: "@types/minimatch@npm:5.1.2" @@ -2166,6 +2190,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.7.5": + version: 22.8.6 + resolution: "@types/node@npm:22.8.6" + dependencies: + undici-types: "npm:~6.19.8" + checksum: 10c0/d3a11f2549234a91a4c5d0ff35ab4bdcb7ba34db4d3f1d189be39b8bd41c19aac98d117150a95a9c5a9d45b1014135477ea240b2b8317c86ae3d3cf1c3b3f8f4 + languageName: node + linkType: hard + "@types/plist@npm:^3.0.1": version: 3.0.5 resolution: "@types/plist@npm:3.0.5" @@ -2453,6 +2486,7 @@ __metadata: "@reduxjs/toolkit": "npm:^2.2.5" "@types/fs-extra": "npm:^11" "@types/lodash": "npm:^4.17.5" + "@types/markdown-it": "npm:^14" "@types/node": "npm:^18.19.9" "@types/react": "npm:^18.2.48" "@types/react-dom": "npm:^18.2.18" @@ -2466,6 +2500,7 @@ __metadata: dayjs: "npm:^1.11.11" dexie: "npm:^4.0.8" dexie-react-hooks: "npm:^1.1.7" + docx: "npm:^9.0.2" dotenv-cli: "npm:^7.4.2" electron: "npm:^28.3.3" electron-builder: "npm:^24.9.1" @@ -2489,6 +2524,7 @@ __metadata: i18next: "npm:^23.11.5" localforage: "npm:^1.10.0" lodash: "npm:^4.17.21" + markdown-it: "npm:^14.1.0" mime: "npm:^4.0.4" officeparser: "npm:^4.1.1" openai: "npm:^4.52.1" @@ -4464,6 +4500,20 @@ __metadata: languageName: node linkType: hard +"docx@npm:^9.0.2": + version: 9.0.2 + resolution: "docx@npm:9.0.2" + dependencies: + "@types/node": "npm:^22.7.5" + hash.js: "npm:^1.1.7" + jszip: "npm:^3.10.1" + nanoid: "npm:^5.0.4" + xml: "npm:^1.0.1" + xml-js: "npm:^1.6.8" + checksum: 10c0/6065391bac9384084dd74f8ff1fb04ac781e8d10d0a0174bb62dc338e75130fac57f6413ff3e6b0f264d61850baddcccbfa7994be036c6fbc113127c4422d342 + languageName: node + linkType: hard + "dom-walk@npm:^0.1.0": version: 0.1.2 resolution: "dom-walk@npm:0.1.2" @@ -6111,6 +6161,16 @@ __metadata: languageName: node linkType: hard +"hash.js@npm:^1.1.7": + version: 1.1.7 + resolution: "hash.js@npm:1.1.7" + dependencies: + inherits: "npm:^2.0.3" + minimalistic-assert: "npm:^1.0.1" + checksum: 10c0/41ada59494eac5332cfc1ce6b7ebdd7b88a3864a6d6b08a3ea8ef261332ed60f37f10877e0c825aaa4bddebf164fbffa618286aeeec5296675e2671cbfa746c4 + languageName: node + linkType: hard + "hasha@npm:^2.2.0": version: 2.2.0 resolution: "hasha@npm:2.2.0" @@ -7410,7 +7470,7 @@ __metadata: languageName: node linkType: hard -"jszip@npm:^3.1.0": +"jszip@npm:^3.1.0, jszip@npm:^3.10.1": version: 3.10.1 resolution: "jszip@npm:3.10.1" dependencies: @@ -7528,6 +7588,15 @@ __metadata: languageName: node linkType: hard +"linkify-it@npm:^5.0.0": + version: 5.0.0 + resolution: "linkify-it@npm:5.0.0" + dependencies: + uc.micro: "npm:^2.0.0" + checksum: 10c0/ff4abbcdfa2003472fc3eb4b8e60905ec97718e11e33cca52059919a4c80cc0e0c2a14d23e23d8c00e5402bc5a885cdba8ca053a11483ab3cc8b3c7a52f88e2d + languageName: node + linkType: hard + "load-bmfont@npm:^1.3.1, load-bmfont@npm:^1.4.0": version: 1.4.2 resolution: "load-bmfont@npm:1.4.2" @@ -7735,6 +7804,22 @@ __metadata: languageName: node linkType: hard +"markdown-it@npm:^14.1.0": + version: 14.1.0 + resolution: "markdown-it@npm:14.1.0" + dependencies: + argparse: "npm:^2.0.1" + entities: "npm:^4.4.0" + linkify-it: "npm:^5.0.0" + mdurl: "npm:^2.0.0" + punycode.js: "npm:^2.3.1" + uc.micro: "npm:^2.1.0" + bin: + markdown-it: bin/markdown-it.mjs + checksum: 10c0/9a6bb444181d2db7016a4173ae56a95a62c84d4cbfb6916a399b11d3e6581bf1cc2e4e1d07a2f022ae72c25f56db90fbe1e529fca16fbf9541659dc53480d4b4 + languageName: node + linkType: hard + "markdown-table@npm:^3.0.0": version: 3.0.3 resolution: "markdown-table@npm:3.0.3" @@ -8005,6 +8090,13 @@ __metadata: languageName: node linkType: hard +"mdurl@npm:^2.0.0": + version: 2.0.0 + resolution: "mdurl@npm:2.0.0" + checksum: 10c0/633db522272f75ce4788440669137c77540d74a83e9015666a9557a152c02e245b192edc20bc90ae953bbab727503994a53b236b4d9c99bdaee594d0e7dd2ce0 + languageName: node + linkType: hard + "memoize-one@npm:^6.0.0": version: 6.0.0 resolution: "memoize-one@npm:6.0.0" @@ -8460,6 +8552,13 @@ __metadata: languageName: node linkType: hard +"minimalistic-assert@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: 10c0/96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd + languageName: node + linkType: hard + "minimatch@npm:9.0.3": version: 9.0.3 resolution: "minimatch@npm:9.0.3" @@ -8675,6 +8774,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^5.0.4": + version: 5.0.8 + resolution: "nanoid@npm:5.0.8" + bin: + nanoid: bin/nanoid.js + checksum: 10c0/0281d3cc0f3d03fb3010b479f28e8ddedfafb9b5d60e54315589ef0a54a0e9cfcb0bf382dd3b620fdad6b72b8c1f5b89a15d55c6b6a9134b58b16eb230b3a3d7 + languageName: node + linkType: hard + "napi-build-utils@npm:^1.0.1": version: 1.0.2 resolution: "napi-build-utils@npm:1.0.2" @@ -9648,6 +9756,13 @@ __metadata: languageName: node linkType: hard +"punycode.js@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode.js@npm:2.3.1" + checksum: 10c0/1d12c1c0e06127fa5db56bd7fdf698daf9a78104456a6b67326877afc21feaa821257b171539caedd2f0524027fa38e67b13dd094159c8d70b6d26d2bea4dfdb + languageName: node + linkType: hard + "punycode@npm:^2.1.0, punycode@npm:^2.1.1, punycode@npm:^2.3.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -12333,6 +12448,13 @@ __metadata: languageName: node linkType: hard +"uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": + version: 2.1.0 + resolution: "uc.micro@npm:2.1.0" + checksum: 10c0/8862eddb412dda76f15db8ad1c640ccc2f47cdf8252a4a30be908d535602c8d33f9855dfcccb8b8837855c1ce1eaa563f7fa7ebe3c98fd0794351aab9b9c55fa + languageName: node + linkType: hard + "unbox-primitive@npm:^1.0.2": version: 1.0.2 resolution: "unbox-primitive@npm:1.0.2" @@ -12362,7 +12484,7 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.2": +"undici-types@npm:~6.19.2, undici-types@npm:~6.19.8": version: 6.19.8 resolution: "undici-types@npm:6.19.8" checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344 @@ -13057,6 +13179,17 @@ __metadata: languageName: node linkType: hard +"xml-js@npm:^1.6.8": + version: 1.6.11 + resolution: "xml-js@npm:1.6.11" + dependencies: + sax: "npm:^1.2.4" + bin: + xml-js: ./bin/cli.js + checksum: 10c0/c83631057f10bf90ea785cee434a8a1a0030c7314fe737ad9bf568a281083b565b28b14c9e9ba82f11fc9dc582a3a907904956af60beb725be1c9ad4b030bc5a + languageName: node + linkType: hard + "xml-name-validator@npm:^5.0.0": version: 5.0.0 resolution: "xml-name-validator@npm:5.0.0" @@ -13081,6 +13214,13 @@ __metadata: languageName: node linkType: hard +"xml@npm:^1.0.1": + version: 1.0.1 + resolution: "xml@npm:1.0.1" + checksum: 10c0/04bcc9b8b5e7b49392072fbd9c6b0f0958bd8e8f8606fee460318e43991349a68cbc5384038d179ff15aef7d222285f69ca0f067f53d071084eb14c7fdb30411 + languageName: node + linkType: hard + "xmlbuilder@npm:>=11.0.1, xmlbuilder@npm:^15.1.1": version: 15.1.1 resolution: "xmlbuilder@npm:15.1.1"