
前端开发工具【免费下载链接】dillingerThe last Markdown editor, ever.项目地址https://gitcode.com/gh_mirrors/di/dillinger点击查看免费下载本文是 Dillinger 开源仓库根目录 CLAUDE.md 的技术指南解读与源码级展开。Dillinger 是一个云就绪、移动友好、兼容离线存储的 Markdown 编辑器当前以 Next.js 14App Router React 18 TypeScript 重构实现。读完本文你将掌握该项目的完整技术栈选型、目录架构、代码规范、安全与性能实践、OAuth 云端集成模式、以及覆盖单元测试到 E2E 的全套测试与部署流程可以直接据此参与开发、二次扩展或将其作为 Next.js 全栈应用的技术范本。一、项目定位与快速上手Dillinger 的目标用户是开发者、技术写作者与内容创作者他们需要一个无干扰、可连接云端、键盘驱动的工作流。项目描述为 Cloud-enabled, mobile-ready, offline-storage-compatible Markdown editor其核心体验是左侧写 Markdown、右侧实时预览同时支持将文档同步到 GitHub、Dropbox、Google Drive、OneDrive 与 Bitbucket 五个云服务并支持导出 HTML / PDF / Markdown。1.1 快速命令参考在仓库根目录安装依赖后日常开发使用以下命令均定义于 package.jsonnpm run dev # 启动开发服务器http://localhost:3000 npm run build # 生产构建 npm run start # 启动生产服务器 npm run lint # 运行 ESLintnext lint npm run typecheck # 运行 tsc --noEmit 类型检查1.2 技术栈一览分类技术版本框架Next.jsApp Router14.2.35语言TypeScriptstrict 模式5.xUIReact18.x样式Tailwind CSS3.4.1状态管理Zustand5.0.10编辑器Monaco Editormonaco-editor/react4.7.0图标Lucide React0.562.0Markdownmarkdown-it 插件14.1.0以上版本号均可在 package.json 的 dependencies 中核对。值得注意的是依赖中还包含了monaco-vim与monaco-emacs编辑器键位模式、dompurify渲染安全、katex数学公式、highlight.js代码高亮、turndown与breakdanceHTML 转 Markdown 导入、以及sparticuz/chromiumpuppeteer-core服务端 PDF 渲染。二、架构总览App Router 下的目录职责项目采用 Next.js App Router 组织目录结构如下与 CLAUDE.md 的 Architecture Overview 一致dillinger/ ├── app/ # Next.js App Router │ ├── api/ # API route handlers │ │ ├── github/ # GitHub OAuth file ops │ │ ├── dropbox/ # Dropbox integration │ │ ├── google-drive/ # Google Drive integration │ │ ├── onedrive/ # Microsoft OneDrive │ │ ├── bitbucket/ # Bitbucket integration │ │ ├── medium/ # Medium publishing │ │ └── export/ # PDF/HTML export │ ├── layout.tsx # 根布局providers、metadata、JSON-LD │ ├── page.tsx # 主编辑器页面 │ ├── globals.css # 全局样式 Tailwind │ ├── error.tsx # 错误边界 │ └── not-found.tsx # 404 页面 ├── components/ │ ├── editor/ # Monaco 编辑器封装 │ ├── preview/ # Markdown 预览面板 │ ├── navbar/ # 顶部导航 │ ├── sidebar/ # 文档列表 集成入口 │ ├── modals/ # OAuth 对话框GitHub、Dropbox 等 │ ├── providers/ # Context providers │ └── ui/ # 可复用原语Toast、Skeleton ├── hooks/ # 自定义 React hooks │ ├── useGitHub.ts │ ├── useDropbox.ts │ ├── useGoogleDrive.ts │ ├── useOneDrive.ts │ ├── useBitbucket.ts │ └── useMedium.ts ├── stores/ │ └── store.ts # Zustand storedocuments、settings、UI ├── lib/ # 工具与辅助函数 └── types/ # TypeScript 类型定义从实际仓库看hooks/目录目前包含 useGitHub.ts、useDropbox.ts、useGoogleDrive.ts、useOneDrive.ts、useBitbucket.ts 与 useImageUpload.ts每个云端集成对应一个独立 hook与文档描述的一个集成一个 hook约定一致。app/api/下则按服务拆分 route handlergithub、dropbox、google-drive、onedrive、bitbucket 各自包含 callback / oauth / files / save / status / unlink 等路由外加export/html、markdown、pdf与import/html-to-markdown与upload/image。2.1 关键文件速查文件职责stores/store.tsZustand store 定义文档、设置、UI 状态tailwind.config.ts设计 token、主题next.config.mjs构建配置打包优化、服务端外部包.env.local.example必需环境变量模板app/layout.tsx根布局providers、metadata、结构化数据components/editor/EditorContainer.tsx主应用壳侧边栏、编辑区、预览区、zen 模式.impeccable.md设计系统与设计上下文vitest.config.tsVitest 测试运行器配置vitest.setup.ts测试环境初始化mocksplaywright.config.tsE2E 测试配置lib/cache.tsAPI 路由用内存 LRU 缓存三、代码规范TypeScript、组件与导入路径3.1 TypeScript 严格模式项目开启 strict 模式所有代码必须通过严格类型检查。类型书写约定对象形状用interface联合/交叉类型用type导出的函数尽量显式声明返回类型导入一律使用/*路径别名指向仓库根目录。// 推荐使用路径别名 import { useAppStore } from /stores/store import { Document } from /types // 避免深层相对路径 import { useAppStore } from ../../../stores/store/*别名在 tsconfig.json 中配置vitest.config.ts通过vite-tsconfig-paths插件让测试代码同样享受别名解析。3.2 组件约定Client / Server 组件边界Client Components交互性组件文件顶部必须加use client指令Server Components布局与静态内容默认使用无需指令命名组件 PascalCasehooks/工具函数 camelCase文件命名与组件名一致例如EditorContainer.tsx导出EditorContainer。// Client component 模式 use client import { useState } from react interface Props { initialValue: string onChange: (value: string) void } export function MyComponent({ initialValue, onChange }: Props) { const [value, setValue] useState(initialValue) // ... }3.3 动态导入Dynamic Imports以下场景必须使用next/dynamic动态导入依赖浏览器专属 API 的组件Monaco、localStorage初始加载不需要的重型组件会导致 hydration 不匹配的组件。EditorContainer.tsx 就是典型范例——Sidebar 因内部依赖 GitHub/Dropbox 等浏览器端 hook 而被动态导入并关闭 SSRimport dynamic from next/dynamic const Sidebar dynamic(() import(/components/sidebar/Sidebar), { ssr: false, // 对纯客户端组件禁用 SSR })四、样式体系Tailwind 设计 Token 与 z-index 分层4.1 设计 Token 与cn()工具样式必须使用 tailwind.config.ts 中定义的设计 token颜色、间距、z-index并借助 lib/utils.ts 中的cn()clsxtailwind-merge组合合并类名import { cn } from /lib/utils // 推荐 - 使用设计 token div className{cn( bg-bg-sidebar text-text-primary, w-sidebar p-gutter, isActive bg-bg-highlight )} / // 避免 - 硬编码值 div classNamebg-[#2B2F36] w-[270px] p-8 /token 定义摘录侧边栏宽270px、内容留白32px、字体栈含 Source Sans Pro / Ubuntu Mono主题色 plum#35D7BB。4.2 z-index 分层约定分层顺序自低到高sidebar(1) page(2) editor(3) preview(4) overlay(5) navbar(6) settings(7) modal(50) toast(60)。这一约定同时体现在 tailwind.config.ts 的zIndex扩展中例如拖放覆盖层在 EditorContainer.tsx 中使用z-modal层级。新增任何浮层组件时都应沿用该量表避免层级冲突。五、状态管理Zustand 单 Store 设计5.1 Store 位置与访问方式全局 store 位于 stores/store.ts通过useAppStore(selector)钩子访问。必须使用选择器做选择性订阅防止无关状态变化触发不必要的重渲染// 推荐 - 选择性订阅 const documents useAppStore((state) state.documents) const addDocument useAppStore((state) state.addDocument) // 避免 - 订阅整个 store const store useAppStore()5.2 状态分片与动作从源码看store 状态分为四组文档documents、currentDocument、editorInstance设置settings类型见 lib/types.ts 的UserSettingsUI 状态sidebarOpen、settingsOpen、shortcutsOpen、previewVisible、zenMode、isDirty、editorScrollPercent、editorTopLine动作createDocument、selectDocument、deleteDocument、updateDocumentBody、updateDocumentTitle、insertMarkdownAtCursor、updateSettings、hydrate、persist等。其中insertMarkdownAtCursor是编辑器与 store 联动的关键动作若存在 Monaco 编辑器实例则通过editor.executeEdits(dillinger-inline-insert, ...)在光标处插入 Markdown用于粘贴图片、拖放文件等场景否则回退为在文档末尾拼接文本。5.3 持久化与水合Hydration持久化store 自动以2 秒防抖写入 localStorage源码中persist()写入files、currentDocument、profileV3三个 key并把isDirty复位为false水合由 components/providers/StoreProvider.tsx 负责在客户端useEffect中调用hydrate()读取 localStorage 并保证至少存在一个文档、currentDocument 必须有效首次访问时自动展开侧边栏。测试中对这一行为的验证见 tests/store/store.test.ts每个用例前通过useStore.setState(initialState, true)重置 store覆盖文档 CRUD、持久化与 hydration 逻辑。六、API 路由App Router Route Handlers 与 OAuth 模式6.1 路由编写约定使用 Next.js App Router 的 route handlersroute.tsOAuth 相关路由必须声明export const dynamic force-dynamic保证不被打包为静态响应cookie/token 每次实时读取统一返回NextResponse.json()并携带恰当状态码环境变量直接通过process.env访问。以 app/api/github/repos/route.ts 为完整范例// app/api/example/route.ts import { NextRequest, NextResponse } from next/server export const dynamic force-dynamic export async function GET(request: NextRequest) { try { const data await fetchData() return NextResponse.json({ data }) } catch (error) { return NextResponse.json( { error: Failed to fetch }, { status: 500 } ) } }实际路由中GitHub token 从 HTTP-only cookiegithub_token读取而非 localStorage未认证返回 401缺少owner参数返回 400同时通过 lib/cache.ts 的 LRU 缓存默认 TTL 5 分钟、上限 200 条key 用 token 前 8 位 查询参数拼合避免重复请求 GitHub API。6.2 自定义 Hooks 约定每个云端集成对应一个 hookhooks 自行管理状态与 API 调用。关键实践使用useRef保存最新状态避免回调中的陈旧闭包问题useGitHub.ts 中stateRef的典型用法与数据一起返回loading/error状态通过useToast向用户反馈异步操作进度Fetching repositories...、Saved to GitHub! 等。export function useGitHub() { const [repos, setRepos] useStateRepo[]([]) const [loading, setLoading] useState(false) const [error, setError] useStatestring | null(null) const fetchRepos useCallback(async () { setLoading(true) setError(null) try { const res await fetch(/api/github/repos) const data await res.json() setRepos(data) } catch (e) { setError(Failed to fetch repos) } finally { setLoading(false) } }, []) return { repos, loading, error, fetchRepos } }七、安全指南XSS、环境变量与 OAuth7.1 XSS 防护渲染用户生成的 HTML 前必须用 DOMPurify 消毒Markdown 预览仅在使用 DOMPurify 消毒后才允许dangerouslySetInnerHTML。预览组件 components/preview/MarkdownPreview.tsx 的消毒配置非常具体启用USE_PROFILES: { html: true, mathMl: true, svg: true }允许 KaTeX 公式所需的 MathML/SVG放行target、class、data-line-start、data-line-end属性后者是滚动同步的行锚点并禁止script、style标签。渲染管线为markdown-it生成 HTML → DOMPurify 清洗 →dangerouslySetInnerHTML。7.2 环境变量管理密钥存放于.env.local绝不提交到版本库仅客户端可见的值使用NEXT_PUBLIC_*前缀必需变量参考 .env.local.example# GitHub OAuth GITHUB_CLIENT_IDyour_github_client_id GITHUB_CLIENT_SECRETyour_github_client_secret # Dropbox OAuth DROPBOX_APP_KEYyour_dropbox_app_key DROPBOX_APP_SECRETyour_dropbox_app_secret # App URL (for OAuth callbacks) NEXT_PUBLIC_BASE_URLhttp://localhost:3000部署Vercel时需在控制台逐一设置这些变量。7.3 OAuth 安全要点所有 OAuth 流程走服务端 route handler客户端只发起跳转不接触密钥Token 存储在HTTP-only cookie而非 localStorage如github_token校验回调 redirect URI 与预期模式完全一致。八、性能最佳实践8.1 打包优化Lucide 图标通过 next.config.mjs 的experimental.optimizePackageImports: [lucide-react]优化 barrel 导入配合 tree-shaking源码中同时约定逐个导入图标而非整库导入// 推荐 - 可 tree-shaking import { FileText, Settings, Download } from lucide-react // 避免 - 导入整个库 import * as Icons from lucide-react此外 next.config.mjs 还将sparticuz/chromium、puppeteer-core声明为serverComponentsExternalPackages服务端 PDF 导出所需避免打入客户端 bundle。8.2 渲染策略静态内容布局、头部用 Server Components仅交互、hooks、浏览器 API 场景用 Client Components为路由级 suspense 边界添加loading.tsx。8.3 记忆化Memoization昂贵计算用useMemo如 Markdown 渲染、统计计算作为 props 传递的函数用useCallback避免过早优化——先做性能分析。源码中的实例MonacoEditor的editorOptions与字数统计均用useMemo包裹components/editor/MonacoEditor.tsx编辑内容通过onDidScrollChange监听滚动并写入 store滚动同步本身也只在enableScrollSync开启时工作。九、常见交互模式9.1 Modal 模式弹窗开关状态由 Zustand 统一管理而非组件本地 state// Modals 使用 Zustand 控制开合 const isSettingsOpen useAppStore((state) state.isSettingsOpen) const toggleSettings useAppStore((state) state.toggleSettings)9.2 Toast 通知通过 ToastProvider 提供上下文import { useToast } from /components/providers/ToastProvider const { showToast } useToast() showToast(Document saved!, success)实际组件位于 components/ui/Toast.tsx且带有对应单元测试 tests/components/toast.test.tsx。9.3 键盘快捷键快捷键动作Cmd/Ctrl Shift Z切换 zen 模式Escape退出 zen 模式?打开快捷键面板以上在 components/editor/EditorContainer.tsx 中以全局keydown监听实现Monaco 编辑器内则通过设置支持 Vim / Emacs 键位见下一节。十、编辑器与预览核心实现虽然 CLAUDE.md 以工程规范为主但其技术栈表提到的 Monaco 与 markdown-it 值得结合源码深入理解它们是架构概述中 editor/preview 两个核心模块的落地。10.1 Monaco 编辑器封装components/editor/MonacoEditor.tsx 完成自定义主题dillinger-light/dillinger-dark两套主题dark 模式背景#1D212A键位模式monaco-vim动态加载启用 Vim 模式initVimMode(editor, statusNode)状态栏实时显示当前键位Default / Vim / Emacs滚动同步onDidScrollChange计算滚动百分比并更新 store 的editorScrollPercent/editorTopLine剪贴板图片粘贴监听paste事件通过useImageUpload上传后以executeEdits在光标处插入自动保存enableAutoSave开启时以2 秒防抖调用persist()统计栏底栏显示words/characters由countDocumentStats计算受enableWordsCount、enableCharactersCount设置控制。10.2 Markdown 渲染管线lib/markdown.ts 是预览的渲染核心全部插件与markdown-it、highlight.js、katex均为动态 importPromise.all首次调用时组装单例渲染器markdown-it开启html: true、linkify、typographer、breakshighlight回调对接 highlight.js未知语言回退默认转义挂载 10 个扩展插件markdown-it-toc、abbr、checkbox、deflist、footnote、ins、mark、sub、sup、texmathkatex 引擎、dollars分隔符通过applyLegacyRendererRules保留 Dillinger 旧版行号锚点特性为段落/图片/代码块/列表项注入has-line-data、data-line-start、data-line-end属性标题生成id锚点——这正是预览区滚动同步的定位依据。对应的单元测试见 tests/lib/markdown.test.ts覆盖标题、粗斜体、链接含target_blank与 rel 属性、hljs 高亮、复选框等场景文档特别提醒renderMarkdown()是异步函数测试必须await它。十一、测试体系从单元到 E2E 的完整链路11.1 测试栈工具用途Vitest单元 集成测试React Testing Library组件渲染与交互PlaywrightE2E 浏览器自动化vitest/coverage-v8代码覆盖率11.2 运行测试npm run test:unit # 运行单元/集成测试 npm run test:watch # 监听模式 npm run test:e2e # 先构建再跑 E2EPlaywright npm run test:e2e:headed # 可见浏览器中跑 E2E npm run test # 单元 E2E npm run verify # lint typecheck 单元 E2E npx vitest run --coverage # 单元测试并生成覆盖率报告上述脚本均定义于 package.json 的 scripts 中其中verify是 CI 前的一站式校验命令。11.3 测试目录结构tests/ ├── lib/ # 纯工具单元测试 │ ├── cache.test.ts │ ├── document.test.ts │ ├── export.test.ts │ ├── import.test.ts │ ├── markdown.test.ts │ └── utils.test.ts ├── store/ │ └── store.test.ts # Zustand store 动作 持久化 ├── hooks/ │ ├── useGitHub.test.ts # 使用 mocked fetch 的集成测试 │ └── useImageUpload.test.ts ├── components/ # React Testing Library 组件测试 │ ├── navbar.test.tsx │ ├── settings-modal.test.tsx │ ├── github-modal.test.tsx │ ├── delete-confirm-modal.test.tsx │ ├── document-title.test.tsx │ ├── document-list.test.tsx │ ├── toast.test.tsx │ └── skeleton.test.tsx ├── routes/ # API 路由处理器测试vitest-environment node │ ├── github.route.test.ts │ ├── export-html.route.test.ts │ ├── export-markdown.route.test.ts │ ├── export-pdf.route.test.ts │ ├── import-html-to-markdown.route.test.ts │ └── upload-image.route.test.ts └── e2e/ # Playwright 浏览器测试 ├── smoke.spec.ts ├── editor.spec.ts ├── settings-sidebar.spec.ts ├── import-export.spec.ts └── logobar.spec.ts文档标注当前覆盖率为98% statements、91% branches、99.5% functions、98% lines294 个单元测试 39 个 E2E 测试这些数字属于文档声明的项目状态。11.4 编写测试的约定命名Vitest 用*.test.ts/*.test.tsxPlaywright 用*.spec.ts导入显式导入import { describe, it, expect } from vitest不用全局变量——vitest.config.ts 中globals: false与之一致Store 重置beforeEach中用useStore.setState(initialState)重置见 tests/store/store.test.tsMock模块用vi.mock()API 调用用vi.spyOn(globalThis, fetch)组件渲染若组件用到useToast()需用 providers 包裹API 路由测试文件顶部加// vitest-environment node直接导入 route handlerE2E 状态播种用page.addInitScript()在导航前写入 localStoragetests/e2e/editor.spec.ts 展示了向files/currentDocument/profileV3三个 key 播种的完整模式异步渲染renderMarkdown()是异步函数测试必须 await。十二、部署与故障排查12.1 部署平台Vercel从 GitHub 自动部署配置vercel.json 声明framework: nextjs并为 PDF 导出路由app/api/export/pdf/route.ts通过includeFiles打包sparticuz/chromium/bin/**/*无头浏览器二进制环境在 Vercel 控制台配置所有.env.local变量。12.2 常见问题排查Hydration 不匹配将浏览器专属代码放入useEffect或用dynamic(..., { ssr: false })包裹检查是否存在typeof window ! undefined守卫。Monaco 编辑器问题Monaco 只通过动态导入在客户端加载确保monaco-editor/react只在客户端组件中导入。OAuth 回调失败核对 provider 后台的 redirect URI 与实际地址逐字符一致检查NEXT_PUBLIC_BASE_URL与部署 URL 是否匹配确认 OAuth 路由带上了dynamic force-dynamic。十三、设计上下文克制而聚焦的产品气质CLAUDE.md 的 Design Context 部分与根目录设计系统文档 .impeccable.md 一脉相承用户画像开发者、技术写作者、内容创作者需要无干扰、云端连接、键盘驱动的写作工具品牌个性Focused. Capable. Understated.专注、可靠、低调——界面应当像精密仪器沉稳而不张扬情感目标Calm focus——UI 退后内容主导设计原则内容为王——每个 UI 元素都为写作体验服务安静的自信——plum 紫青色#35D7BB是整个中性色板中唯一的亮色精致而非装饰——品质来自间距、对齐、过渡与排版而非点缀渐进式披露——需要什么显示什么侧边栏可折叠、预览可开关、zen 模式剥离一切默认可访问——WCAG AA 最低标准焦点环、对比度、键盘导航、reduced-motion。主题模式完整支持亮色、暗色与跟随系统三种模式plum 强调色在所有主题下保持不变保证品牌一致性对应 MonacoEditor.tsx 中两套编辑器主题与 tailwind.config.ts 的darkMode: class。结语CLAUDE.md 既是 Dillinger 的工程说明书也是一份可复用的 Next.js 14 全栈应用开发清单从 App Router 的目录组织、Client/Server 组件边界、force-dynamic的 OAuth 路由到 Zustand 选择性订阅与 2 秒防抖持久化、DOMPurify 驱动的安全渲染、98% 覆盖率的多层测试体系每一环都有可对照的真实源码。对希望改造或借鉴该项目的开发者而言最值得沿用的模式是集成 hook 服务端 OAuth 路由 LRU 缓存的云端扩展范式以及markdown-it 渲染 → DOMPurify 消毒 → 行锚点滚动同步的实时预览链路。赞分享前端开发工具【免费下载链接】dillingerThe last Markdown editor, ever.项目地址https://gitcode.com/gh_mirrors/di/dillinger点击查看免费下载相关推荐Dillinger 迁移实战从 Angular.js 到 Next.js 14 的 Markdown 编辑器重构实施指南Dillinger 迁移实战从 Angular.js 到 Next.js 14 的 Markdown 编辑器重构实施指南 本文档对应仓库中的实施计划原文 d前端开发工具Dillinger 从 Angular.js 到 Next.js 14 App Router 的全栈迁移设计与实践Dillinger 从 Angular.js 到 Next.js 14 App Router 的全栈迁移设计与实践 本篇技术指南围绕 Dillinger 官方迁前端开发工具Dillinger 项目脚手架指南基于 Next.js 14 App Router 的工程目录结构与核心文件规划Dillinger 项目脚手架指南基于 Next.js 14 App Router 的工程目录结构与核心文件规划 导读 本指南以仓库内 .agent/skil前端开发工具上一篇如何快速降级iOS系统Legacy iOS Kit完整指南下一篇TPOT API 完全指南TPOTClassifier 与 TPOTRegressor 参数、属性与方法详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考