diff --git a/README.md b/README.md index 0eec743..d27e4fc 100644 --- a/README.md +++ b/README.md @@ -1,271 +1,271 @@ -
-
-
- 轻量级 Windows 本地 Markdown 编辑器 -
- -
-
-
-
-
-
-
-
- 基于 Electron + React + TypeScript 构建的现代化 Markdown 桌面编辑器。
- 多标签页 · 实时预览 · 代码高亮 · 暗色主题 · 拖拽打开 · 搜索替换 · 文件树 · IndexedDB 持久化。
-
- 如果觉得有用,请点个 ⭐ Star 支持一下! -
+
+
+
+ 轻量级 Windows 本地 Markdown 编辑器 +
+ +
+
+
+
+
+
+
+
+ 基于 Electron + React + TypeScript 构建的现代化 Markdown 桌面编辑器。
+ 多标签页 · 实时预览 · 代码高亮 · 暗色主题 · 拖拽打开 · 搜索替换 · 文件树 · IndexedDB 持久化。
+
+ 如果觉得有用,请点个 ⭐ Star 支持一下! +
diff --git a/eslint.config.mjs b/eslint.config.mjs index 91d7e57..0f22f03 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,28 +1,28 @@ -import eslint from '@eslint/js' -import tseslint from 'typescript-eslint' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - '@typescript-eslint/no-unused-vars': 'error', - 'no-console': 'warn', - 'prefer-const': 'error', - }, - }, - { - ignores: ['dist/', 'node_modules/', 'assets/', 'lib/'], - }, -) +import eslint from '@eslint/js' +import tseslint from 'typescript-eslint' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + '@typescript-eslint/no-unused-vars': 'error', + 'no-console': 'warn', + 'prefer-const': 'error', + }, + }, + { + ignores: ['dist/', 'node_modules/', 'assets/', 'lib/'], + }, +) diff --git a/package.json b/package.json index f07c37c..06e73a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "marklite", - "version": "0.3.2", + "version": "0.3.4", "description": "Lightweight Markdown Editor for Windows", "main": "./dist/main/index.js", "scripts": { diff --git a/src/main/file-system.ts b/src/main/file-system.ts index be4b9fe..fe542d5 100644 --- a/src/main/file-system.ts +++ b/src/main/file-system.ts @@ -1,13 +1,9 @@ import { readFile, stat, writeFile, rename, readdir, unlink } from 'fs/promises' import { join, extname, dirname, basename } from 'path' import type { ReadFileResult, SaveFileResult, FileNode } from '../shared/types' +import { MAX_FILE_SIZE, ALLOWED_EXTENSIONS, SKIP_DIRS } from '../shared/constants' -const MAX_FILE_SIZE = 20 * 1024 * 1024 // 20MB -const ALLOWED_EXTENSIONS = new Set(['.md', '.markdown', '.txt']) -const SKIP_DIRS = new Set([ - 'node_modules', '.git', '.svn', '.hg', 'dist', 'out', - '.next', '.nuxt', '__pycache__', '.DS_Store' -]) +const ALLOWED_EXTENSIONS_SET = new Set一款轻量级的 Windows 本地 Markdown 编辑器
-一款轻量级的 Windows 本地 Markdown 编辑器
+{message}
-{message}
+
- {this.state.error?.message}
-
-
-
+ {this.state.error?.message}
+
+
+ 渲染错误: ${errorMsg}
` - } -} +import { unified, type Plugin } from 'unified' +import remarkParse from 'remark-parse' +import remarkGfm from 'remark-gfm' +import remarkRehype from 'remark-rehype' +import rehypeRaw from 'rehype-raw' +import rehypeSanitize, { defaultSchema } from 'rehype-sanitize' +import rehypeStringify from 'rehype-stringify' +import rehypeHighlight from 'rehype-highlight' +import type { Element, Root } from 'hast' + +// 简单的路径解析(Electron renderer 中没有 path 模块) +function resolveRelativePath(base: string, rel: string): string { + const sep = base.includes('\\') ? '\\' : '/' + const parts = base.split(sep) + parts.pop() // 移除文件名 + const relParts = rel.split('/') + for (const p of relParts) { + if (p === '.' || p === '') continue + if (p === '..') { + if (parts.length > 0) parts.pop() + } else { + parts.push(p) + } + } + return parts.join(sep) +} + +// 自定义 rehype 插件:将相对路径图片转为 file:// 绝对路径 +function rehypeFixImages(filePath: string | null): Plugin<[], Root> { + return () => (tree: Root) => { + if (!filePath) return + + const dir: string = filePath.replace(/[/\\][^/\\]+$/, '') + const sep: string = dir.includes('\\') ? '\\' : '/' + + function visit(node: Element | Root): void { + if (!node.children) return + for (const child of node.children) { + if (child.type === 'element' && child.tagName === 'img') { + const src = child.properties?.src as string | undefined + if (src && !src.startsWith('http://') && !src.startsWith('https://') && !src.startsWith('data:') && !src.startsWith('file://')) { + // 处理Unix风格绝对路径(以/开头) + if (src.startsWith('/')) { + child.properties = { + ...child.properties, + src: 'file://' + src + } + continue + } + // 解析完整路径并检查是否越界到 markdown 文件所在目录之外 + const resolvedPath = resolveRelativePath(dir, src) + if (!resolvedPath.startsWith(dir + sep)) return + child.properties = { + ...child.properties, + src: 'file://' + (dir + sep + src).replace(/\\/g, '/') + } + } + } + if (child.type === 'element') { + visit(child as Element) + } + } + } + + visit(tree) + } +} + +// PF-01: Markdown处理器LRU缓存 +const MAX_CACHE_SIZE = 10 +const processorCache = new Map渲染错误: ${errorMsg}
` + } +} diff --git a/src/renderer/stores/editorStore.ts b/src/renderer/stores/editorStore.ts index da51f61..592ddbe 100644 --- a/src/renderer/stores/editorStore.ts +++ b/src/renderer/stores/editorStore.ts @@ -1,6 +1,19 @@ import { create } from 'zustand' +import type { EditorView } from '@milkdown/prose/view' import type { ViewMode } from '../types/settings' +// Module-level getter/setter for the ProseMirror EditorView +// Used by OutlinePanel for heading navigation +let _getView: (() => EditorView | null) | null = null + +export function setEditorViewGetter(fn: () => EditorView | null) { + _getView = fn +} + +export function getEditorView(): EditorView | null { + return _getView ? _getView() : null +} + interface EditorState { viewMode: ViewMode darkMode: boolean diff --git a/src/renderer/stores/tabStore.ts b/src/renderer/stores/tabStore.ts index 171bfaa..33eec4f 100644 --- a/src/renderer/stores/tabStore.ts +++ b/src/renderer/stores/tabStore.ts @@ -1,246 +1,246 @@ -import { create } from 'zustand' -import { nanoid } from 'nanoid' -import type { Tab } from '../types/tab' -import { tabRepository } from '../db/tabRepository' - -// PF-08: 防抖工具函数 -function debounce