From 56c1851848f7047856f50f0cd8381a43361aeccc Mon Sep 17 00:00:00 2001 From: SuYao Date: Tue, 9 Sep 2025 11:55:41 +0800 Subject: [PATCH] feat: add font size and table of contents settings to RichEditor (#10034) * feat: add font size and table of contents settings to RichEditor - Introduced font size customization in the RichEditor component, allowing users to adjust the font size for better readability. - Added a toggle for displaying a table of contents in the editor settings. - Updated localization files to include new settings descriptions. - Enhanced the NotesSettings component with a slider for font size adjustment and a switch for the table of contents feature. - Migrated state management to include new settings in the Redux store. * feat: enhance CodeEditor with customizable font size and responsive layout * feat: enhance markdown conversion to preserve square brackets - Improved the htmlToMarkdown function to correctly handle and preserve wiki-style double brackets [[foo]] and single brackets [foo] while maintaining proper Markdown link syntax. - Added unit tests to verify the preservation of these bracket formats during conversion. * feat: enhance YamlFrontMatterNodeView with editor content check * fix * chore * chore: bump store persistence version to 153 --------- Co-authored-by: icarus --- src/renderer/src/assets/styles/richtext.css | 1 + .../components/YamlFrontMatterNodeView.tsx | 22 +++-- .../src/components/RichEditor/index.tsx | 4 +- .../src/components/RichEditor/styles.ts | 2 + .../src/components/RichEditor/types.ts | 2 + src/renderer/src/i18n/locales/en-us.json | 7 ++ src/renderer/src/i18n/locales/zh-cn.json | 7 ++ src/renderer/src/i18n/locales/zh-tw.json | 7 ++ src/renderer/src/pages/notes/HeaderNavbar.tsx | 42 +++++++++- src/renderer/src/pages/notes/MenuConfig.tsx | 30 +++++++ src/renderer/src/pages/notes/NotesEditor.tsx | 48 ++++++++--- src/renderer/src/pages/notes/NotesNavbar.tsx | 83 ------------------- src/renderer/src/pages/notes/NotesPage.tsx | 8 +- .../src/pages/settings/NotesSettings.tsx | 39 ++++++++- .../src/pages/settings/SettingGroup.tsx | 2 +- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/migrate.ts | 12 +++ src/renderer/src/store/note.ts | 4 + .../utils/__tests__/markdownConverter.test.ts | 17 +++- src/renderer/src/utils/markdownConverter.ts | 30 ++++++- 20 files changed, 259 insertions(+), 110 deletions(-) delete mode 100644 src/renderer/src/pages/notes/NotesNavbar.tsx diff --git a/src/renderer/src/assets/styles/richtext.css b/src/renderer/src/assets/styles/richtext.css index c6afcf4011..547a5e17bb 100644 --- a/src/renderer/src/assets/styles/richtext.css +++ b/src/renderer/src/assets/styles/richtext.css @@ -5,6 +5,7 @@ min-height: 120px; overflow-wrap: break-word; word-break: break-word; + font-size: var(--editor-font-size, 16px); } .tiptap:focus { diff --git a/src/renderer/src/components/RichEditor/components/YamlFrontMatterNodeView.tsx b/src/renderer/src/components/RichEditor/components/YamlFrontMatterNodeView.tsx index 67d2c2126b..7b2bde078e 100644 --- a/src/renderer/src/components/RichEditor/components/YamlFrontMatterNodeView.tsx +++ b/src/renderer/src/components/RichEditor/components/YamlFrontMatterNodeView.tsx @@ -15,7 +15,7 @@ interface ParsedProperty { type: 'string' | 'array' | 'date' | 'number' | 'boolean' } -const YamlFrontMatterNodeView: React.FC = ({ node, updateAttributes }) => { +const YamlFrontMatterNodeView: React.FC = ({ node, updateAttributes, editor }) => { const { t } = useTranslation() const [editingProperty, setEditingProperty] = useState(null) const [newPropertyName, setNewPropertyName] = useState('') @@ -408,6 +408,11 @@ const YamlFrontMatterNodeView: React.FC = ({ node, updateAttribut ) } + // Check if there's content in the entire editor (excluding YAML front matter) + const hasContent = useMemo(() => { + return editor.getText().trim().length > 0 + }, [editor]) + return ( = ({ node, updateAttribut } }}> { // Prevent node selection when clicking inside properties e.stopPropagation() @@ -485,7 +491,7 @@ const YamlFrontMatterNodeView: React.FC = ({ node, updateAttribut /> ) : ( - setShowAddProperty(true)}> + setShowAddProperty(true)}> @@ -497,7 +503,7 @@ const YamlFrontMatterNodeView: React.FC = ({ node, updateAttribut ) } -const PropertiesContainer = styled.div` +const PropertiesContainer = styled.div<{ hasContent?: boolean }>` margin: 16px 0; padding: 0; display: flex; @@ -705,7 +711,7 @@ const ArrayInput = styled(Input)` } ` -const AddPropertyRow = styled.button` +const AddPropertyRow = styled.button<{ hasContent?: boolean }>` display: flex; align-items: center; padding: 6px 8px; @@ -716,16 +722,22 @@ const AddPropertyRow = styled.button` cursor: pointer; border-radius: 6px; width: 100%; + opacity: ${({ hasContent }) => (hasContent ? 0 : 1)}; + transition: opacity 0.2s; &:hover { background-color: var(--color-hover); } + + ${PropertiesContainer}:hover & { + opacity: 1; + } ` const AddPropertyText = styled.div` font-size: 14px; font-family: var(--font-family); - color: var(--color-text); + color: var(--color-text-secondary); ` const PropertyActions = styled.div` diff --git a/src/renderer/src/components/RichEditor/index.tsx b/src/renderer/src/components/RichEditor/index.tsx index a14af5d0fc..0b9e3876ac 100644 --- a/src/renderer/src/components/RichEditor/index.tsx +++ b/src/renderer/src/components/RichEditor/index.tsx @@ -47,7 +47,8 @@ const RichEditor = ({ showTableOfContents = false, enableContentSearch = false, isFullWidth = false, - fontFamily = 'default' + fontFamily = 'default', + fontSize = 16 // toolbarItems: _toolbarItems // TODO: Implement custom toolbar items }: RichEditorProps & { ref?: React.RefObject }) => { // Use the rich editor hook for complete editor management @@ -388,6 +389,7 @@ const RichEditor = ({ $maxHeight={maxHeight} $isFullWidth={isFullWidth} $fontFamily={fontFamily} + $fontSize={fontSize} onKeyDown={onKeyDownEditor}> {showToolbar && ( ` display: flex; flex-direction: column; @@ -16,6 +17,7 @@ export const RichEditorWrapper = styled.div<{ width: ${({ $isFullWidth }) => ($isFullWidth ? '100%' : '60%')}; margin: ${({ $isFullWidth }) => ($isFullWidth ? '0' : '0 auto')}; font-family: ${({ $fontFamily }) => ($fontFamily === 'serif' ? 'var(--font-family-serif)' : 'var(--font-family)')}; + ${({ $fontSize }) => $fontSize && `--editor-font-size: ${$fontSize}px;`} ${({ $minHeight }) => $minHeight && `min-height: ${$minHeight}px;`} ${({ $maxHeight }) => $maxHeight && `max-height: ${$maxHeight}px;`} diff --git a/src/renderer/src/components/RichEditor/types.ts b/src/renderer/src/components/RichEditor/types.ts index 66e8280967..8804210aef 100644 --- a/src/renderer/src/components/RichEditor/types.ts +++ b/src/renderer/src/components/RichEditor/types.ts @@ -48,6 +48,8 @@ export interface RichEditorProps { isFullWidth?: boolean /** Font family setting */ fontFamily?: 'default' | 'serif' + /** Font size in pixels */ + fontSize?: number } export interface ToolbarItem { diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 17213ec798..287e513048 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1743,8 +1743,15 @@ "compress_content": "Content Compression", "compress_content_description": "When enabled, it will limit the number of characters per line, reducing the content displayed on the screen, but making longer paragraphs more readable.", "default_font": "Default font", + "font_size": "Font Size", + "font_size_description": "Adjust the font size for better reading experience (10-30px)", + "font_size_large": "Large", + "font_size_medium": "Medium", + "font_size_small": "Small", "font_title": "Font settings", "serif_font": "Serif font", + "show_table_of_contents": "Show Table of Contents", + "show_table_of_contents_description": "Display a table of contents sidebar for easy navigation within documents", "title": "Display Settings" }, "editor": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 221e36f6af..9bc972cc23 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1743,8 +1743,15 @@ "compress_content": "缩减栏宽", "compress_content_description": "开启后将限制每行字数,使屏幕显示的内容减少", "default_font": "默认字体", + "font_size": "字体大小", + "font_size_description": "调整字体大小以获得更好的阅读体验 (10-30px)", + "font_size_large": "大", + "font_size_medium": "中", + "font_size_small": "小", "font_title": "字体设置", "serif_font": "衬线字体", + "show_table_of_contents": "显示目录大纲", + "show_table_of_contents_description": "显示目录大纲侧边栏,方便文档内导航", "title": "显示设置" }, "editor": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 3f080ee249..05583836c4 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1743,8 +1743,15 @@ "compress_content": "縮減欄寬", "compress_content_description": "開啟後將限制每行字數,使屏幕顯示的內容減少", "default_font": "默認字體", + "font_size": "字體大小", + "font_size_description": "調整字體大小以獲得更好的閱讀體驗 (10-30px)", + "font_size_large": "大", + "font_size_medium": "中", + "font_size_small": "小", "font_title": "字體設置", "serif_font": "襯線字體", + "show_table_of_contents": "顯示目錄大綱", + "show_table_of_contents_description": "顯示目錄大綱側邊欄,方便文檔內導航", "title": "顯示" }, "editor": { diff --git a/src/renderer/src/pages/notes/HeaderNavbar.tsx b/src/renderer/src/pages/notes/HeaderNavbar.tsx index ead7af825c..d1bdb29085 100644 --- a/src/renderer/src/pages/notes/HeaderNavbar.tsx +++ b/src/renderer/src/pages/notes/HeaderNavbar.tsx @@ -7,7 +7,7 @@ import { useShowWorkspace } from '@renderer/hooks/useShowWorkspace' import { findNodeInTree } from '@renderer/services/NotesTreeService' import { Breadcrumb, BreadcrumbProps, Dropdown, Tooltip } from 'antd' import { t } from 'i18next' -import { MoreHorizontal, PanelLeftClose, PanelRightClose } from 'lucide-react' +import { MoreHorizontal, PanelLeftClose, PanelRightClose, Star } from 'lucide-react' import { useCallback, useEffect, useState } from 'react' import styled from 'styled-components' @@ -15,16 +15,23 @@ import { menuItems } from './MenuConfig' const logger = loggerService.withContext('HeaderNavbar') -const HeaderNavbar = ({ notesTree, getCurrentNoteContent }) => { +const HeaderNavbar = ({ notesTree, getCurrentNoteContent, onToggleStar }) => { const { showWorkspace, toggleShowWorkspace } = useShowWorkspace() const { activeNode } = useActiveNode(notesTree) const [breadcrumbItems, setBreadcrumbItems] = useState['items']>([]) const { settings, updateSettings } = useNotesSettings() + const canShowStarButton = activeNode?.type === 'file' && onToggleStar const handleToggleShowWorkspace = useCallback(() => { toggleShowWorkspace() }, [toggleShowWorkspace]) + const handleToggleStarred = useCallback(() => { + if (activeNode) { + onToggleStar(activeNode.id) + } + }, [activeNode, onToggleStar]) + const handleCopyContent = useCallback(async () => { try { const content = getCurrentNoteContent?.() @@ -132,6 +139,17 @@ const HeaderNavbar = ({ notesTree, getCurrentNoteContent }) => { + {canShowStarButton && ( + + + {activeNode.isStarred ? ( + + ) : ( + + )} + + + )} updateSettings({ isFullWidth: !settings.isFullWidth }), isActive: (settings) => !settings.isFullWidth }, + { + key: 'table-of-contents', + labelKey: 'notes.settings.display.show_table_of_contents', + icon: Type, + action: (settings, updateSettings) => updateSettings({ showTableOfContents: !settings.showTableOfContents }), + isActive: (settings) => settings.showTableOfContents + }, { key: 'divider1', type: 'divider', @@ -54,6 +61,29 @@ export const menuItems: MenuItem[] = [ labelKey: 'notes.settings.display.serif_font', action: (_, updateSettings) => updateSettings({ fontFamily: 'serif' }), isActive: (settings) => settings.fontFamily === 'serif' + }, + { + key: 'divider2', + type: 'divider', + labelKey: '' + }, + { + key: 'font-size-small', + labelKey: 'notes.settings.display.font_size_small', + action: (_, updateSettings) => updateSettings({ fontSize: 14 }), + isActive: (settings) => settings.fontSize === 14 + }, + { + key: 'font-size-medium', + labelKey: 'notes.settings.display.font_size_medium', + action: (_, updateSettings) => updateSettings({ fontSize: 16 }), + isActive: (settings) => settings.fontSize === 16 + }, + { + key: 'font-size-large', + labelKey: 'notes.settings.display.font_size_large', + action: (_, updateSettings) => updateSettings({ fontSize: 20 }), + isActive: (settings) => settings.fontSize === 20 } ] } diff --git a/src/renderer/src/pages/notes/NotesEditor.tsx b/src/renderer/src/pages/notes/NotesEditor.tsx index affc5b7942..a9ed8f592f 100644 --- a/src/renderer/src/pages/notes/NotesEditor.tsx +++ b/src/renderer/src/pages/notes/NotesEditor.tsx @@ -59,16 +59,19 @@ const NotesEditor: FC = memo( <> {tmpViewMode === 'source' ? ( - + + + ) : ( = memo( onCommandsReady={handleCommandsReady} showToolbar={tmpViewMode === 'preview'} editable={tmpViewMode === 'preview'} - showTableOfContents + showTableOfContents={settings.showTableOfContents} enableContentSearch className="notes-rich-editor" isFullWidth={settings.isFullWidth} fontFamily={settings.fontFamily} + fontSize={settings.fontSize} /> )} @@ -172,6 +176,28 @@ const RichEditorContainer = styled.div` } ` +const SourceEditorWrapper = styled.div<{ isFullWidth: boolean; fontSize: number }>` + height: 100%; + width: ${({ isFullWidth }) => (isFullWidth ? '100%' : '60%')}; + margin: ${({ isFullWidth }) => (isFullWidth ? '0' : '0 auto')}; + + /* 应用字体大小到CodeEditor */ + .monaco-editor { + font-size: ${({ fontSize }) => fontSize}px !important; + } + + /* 确保CodeEditor内部元素也应用字体大小 */ + .monaco-editor .monaco-editor-background, + .monaco-editor .inputarea.ime-input, + .monaco-editor .monaco-editor-container, + .monaco-editor .overflow-guard, + .monaco-editor .monaco-scrollable-element, + .monaco-editor .lines-content.monaco-editor-background, + .monaco-editor .view-line { + font-size: ${({ fontSize }) => fontSize}px !important; + } +` + const BottomPanel = styled.div` padding: 8px 16px; border-top: 0.5px solid var(--color-border); diff --git a/src/renderer/src/pages/notes/NotesNavbar.tsx b/src/renderer/src/pages/notes/NotesNavbar.tsx deleted file mode 100644 index f6dbe77573..0000000000 --- a/src/renderer/src/pages/notes/NotesNavbar.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar' -import { HStack } from '@renderer/components/Layout' -import { isMac } from '@renderer/config/constant' -import { useFullscreen } from '@renderer/hooks/useFullscreen' -import { useShowWorkspace } from '@renderer/hooks/useShowWorkspace' -import { Tooltip } from 'antd' -import { PanelLeftClose, PanelRightClose } from 'lucide-react' -import { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import styled from 'styled-components' - -const NotesNavbar = () => { - const { t } = useTranslation() - const { showWorkspace, toggleShowWorkspace } = useShowWorkspace() - const isFullscreen = useFullscreen() - - const handleToggleShowWorkspace = useCallback(() => { - toggleShowWorkspace() - }, [toggleShowWorkspace]) - - return ( - - {showWorkspace && ( - - - - - - - - )} - - - {!showWorkspace && ( - - - - - - )} - - - - ) -} - -export const NavbarIcon = styled.div` - -webkit-app-region: none; - border-radius: 8px; - height: 30px; - padding: 0 7px; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - transition: all 0.2s ease-in-out; - cursor: pointer; - .iconfont { - font-size: 18px; - color: var(--color-icon); - &.icon-a-addchat { - font-size: 20px; - } - &.icon-a-darkmode { - font-size: 20px; - } - &.icon-appstore { - font-size: 20px; - } - } - .anticon { - color: var(--color-icon); - font-size: 16px; - } - &:hover { - background-color: var(--color-background-mute); - color: var(--color-icon-white); - } -` - -export default NotesNavbar diff --git a/src/renderer/src/pages/notes/NotesPage.tsx b/src/renderer/src/pages/notes/NotesPage.tsx index 1ca06c000e..b65c779f4b 100644 --- a/src/renderer/src/pages/notes/NotesPage.tsx +++ b/src/renderer/src/pages/notes/NotesPage.tsx @@ -20,8 +20,8 @@ import { selectActiveFilePath, selectSortType, setActiveFilePath, setSortType } import { NotesSortType, NotesTreeNode } from '@renderer/types/note' import { FileChangeEvent } from '@shared/config/types' import { useLiveQuery } from 'dexie-react-hooks' -import { AnimatePresence, motion } from 'framer-motion' import { debounce } from 'lodash' +import { AnimatePresence, motion } from 'motion/react' import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -629,7 +629,11 @@ const NotesPage: FC = () => { )} - + { {t('notes.settings.display.compress_content')} - updateSettings({ isFullWidth: checked })} /> + updateSettings({ isFullWidth: !checked })} /> {t('notes.settings.display.compress_content_description')} + + + {t('notes.settings.display.font_size')} + + updateSettings({ fontSize: value })} + style={{ width: 200, marginRight: 16 }} + /> + {settings.fontSize}px + + + {t('notes.settings.display.font_size_description')} + + + {t('notes.settings.display.show_table_of_contents')} + updateSettings({ showTableOfContents: checked })} + /> + + {t('notes.settings.display.show_table_of_contents_description')} ) @@ -194,4 +218,15 @@ const ActionButtons = styled.div` align-self: flex-start; ` +const FontSizeContainer = styled.div` + display: flex; + align-items: center; +` + +const FontSizeValue = styled.span` + min-width: 40px; + font-size: 14px; + color: var(--color-text-secondary); +` + export default NotesSettings diff --git a/src/renderer/src/pages/settings/SettingGroup.tsx b/src/renderer/src/pages/settings/SettingGroup.tsx index bd5efc70cc..575e672dac 100644 --- a/src/renderer/src/pages/settings/SettingGroup.tsx +++ b/src/renderer/src/pages/settings/SettingGroup.tsx @@ -1,6 +1,6 @@ import { ThemeMode } from '@renderer/types' -import { AnimatePresence, motion } from 'framer-motion' import { ChevronDown, ChevronRight } from 'lucide-react' +import { AnimatePresence, motion } from 'motion/react' import { useState } from 'react' import styled from 'styled-components' diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 2710ca7a4a..baf85581ec 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -67,7 +67,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 152, + version: 153, blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 6485a7a2aa..e879b49d3e 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -2439,6 +2439,18 @@ const migrateConfig = { logger.error('migrate 152 error', error as Error) return state } + }, + '153': (state: RootState) => { + try { + if (state.note.settings) { + state.note.settings.fontSize = notesInitialState.settings.fontSize + state.note.settings.showTableOfContents = notesInitialState.settings.showTableOfContents + } + return state + } catch (error) { + logger.error('migrate 153 error', error as Error) + return state + } } } diff --git a/src/renderer/src/store/note.ts b/src/renderer/src/store/note.ts index 7a7a695f54..df4478c07a 100644 --- a/src/renderer/src/store/note.ts +++ b/src/renderer/src/store/note.ts @@ -6,6 +6,8 @@ import { NotesSortType } from '@renderer/types/note' export interface NotesSettings { isFullWidth: boolean fontFamily: 'default' | 'serif' + fontSize: number + showTableOfContents: boolean defaultViewMode: 'edit' | 'read' defaultEditMode: Omit showTabStatus: boolean @@ -26,6 +28,8 @@ export const initialState: NoteState = { settings: { isFullWidth: true, fontFamily: 'default', + fontSize: 16, + showTableOfContents: true, defaultViewMode: 'edit', defaultEditMode: 'preview', showTabStatus: true, diff --git a/src/renderer/src/utils/__tests__/markdownConverter.test.ts b/src/renderer/src/utils/__tests__/markdownConverter.test.ts index 3929be608c..44396d76ca 100644 --- a/src/renderer/src/utils/__tests__/markdownConverter.test.ts +++ b/src/renderer/src/utils/__tests__/markdownConverter.test.ts @@ -485,7 +485,7 @@ describe('markdownConverter', () => { }) }) - describe('shoud keep YAML front matter', () => { + describe('should keep YAML front matter', () => { it('should keep YAML front matter', () => { const markdown = `--- tags: @@ -505,4 +505,19 @@ cssclasses: expect(backToMarkdown).toBe(markdown) }) }) + + describe('should keep []', () => { + it('should keep [[foo]]', () => { + const markdown = `[[foo]]` + const result = markdownToHtml(markdown) + const backToMarkdown = htmlToMarkdown(result) + expect(backToMarkdown).toBe(markdown) + }) + it('should keep []', () => { + const markdown = `[foo]` + const result = markdownToHtml(markdown) + const backToMarkdown = htmlToMarkdown(result) + expect(backToMarkdown).toBe(markdown) + }) + }) }) diff --git a/src/renderer/src/utils/markdownConverter.ts b/src/renderer/src/utils/markdownConverter.ts index 08b6ec506c..3b766cd32c 100644 --- a/src/renderer/src/utils/markdownConverter.ts +++ b/src/renderer/src/utils/markdownConverter.ts @@ -750,7 +750,35 @@ export const htmlToMarkdown = (html: string | null | undefined): string => { try { const encodedHtml = escapeCustomTags(html) const turndownResult = turndownService.turndown(encodedHtml) - const finalResult = he.decode(turndownResult) + let finalResult = he.decode(turndownResult) + + // Post-process to unescape square brackets that are not part of Markdown link syntax + // This preserves wiki-style double brackets [[foo]] and single brackets [foo] + // but keeps proper Markdown links [text](url) intact + + // Use a more sophisticated approach: check for the link pattern first, + // then unescape standalone brackets + + // First, protect actual Markdown links by temporarily replacing them + const linkPlaceholders: string[] = [] + let linkCounter = 0 + + // Find and replace all Markdown links with placeholders + finalResult = finalResult.replace(/\\\[([^\]]*)\\\]\([^)]*\)/g, (match) => { + const placeholder = `__MDLINK_${linkCounter++}__` + linkPlaceholders[linkCounter - 1] = match + return placeholder + }) + + // Now unescape all remaining square brackets + finalResult = finalResult.replace(/\\\[/g, '[').replace(/\\\]/g, ']') + + // Restore the Markdown links + for (let i = 0; i < linkPlaceholders.length; i++) { + const placeholder = `__MDLINK_${i}__` + finalResult = finalResult.replace(placeholder, linkPlaceholders[i]) + } + return finalResult } catch (error) { logger.error('Error converting HTML to Markdown:', error as Error)