diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..beca556 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# 系统文件 +.DS_Store +Thumbs.db +desktop.ini + +# 编辑器 +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# 临时文件 +*.log +*.tmp diff --git a/README.md b/README.md index 265a5ec..7da4f7e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,153 @@ -# markmap +# Markmap - Markdown 思维导图编辑器 -markdown转思维导图 \ No newline at end of file +纯 HTML + CSS + JavaScript 实现的 Markdown 思维导图工具,无需 Node.js、无需编译,开箱即用。 + +## 快速开始 + +### 方式一:本地服务器(推荐) + +双击 `start.bat` 即可启动(需要 Python 3): + +``` +start.bat +``` + +或手动运行: + +``` +python serve.py # 默认端口 8080 +python serve.py 3000 # 指定端口 +``` + +启动后浏览器会自动打开 `http://127.0.0.1:8080`。 + +### 方式二:直接打开 + +直接双击 `index.html` 用浏览器打开。 + +> 注意:直接打开时 `content.md` 加载会失败,编辑器会自动使用内置示例内容。分享链接功能在 `file://` 协议下不可用。 + +## 功能 + +| 功能 | 说明 | +|------|------| +| 实时预览 | 左侧输入 Markdown,右侧实时生成思维导图 | +| 文件操作 | 新建、加载 `.md` 文件、保存为 `.md` 文件 | +| 插入图片 | 点击"图片"按钮选择本地图片,自动转 Base64 插入到光标位置 | +| 历史文档 | 左侧侧边栏管理历史文档,自动缓存到浏览器,刷新不丢失 | +| 主题切换 | 7 种节点配色主题(默认、柔和、深色、森林、单色、极简、彩色) | +| 日间/夜间模式 | 一键切换浅色/深色背景,夜间模式适合暗光环境 | +| 可拖动分割线 | 拖动中间分割线调整编辑区与预览区的占比 | +| 导出 | 支持导出 SVG / PNG / PDF | +| 分享链接 | 将内容压缩编码到 URL 中,一键分享 | +| 缩放控制 | 放大、缩小、适应窗口 | +| 全屏模式 | 隐藏编辑器,全屏展示思维导图 | +| 节点折叠 | 点击节点圆点折叠/展开子树 | +| 代码高亮 | 支持 JavaScript、Python 等语法高亮 | +| 数学公式 | 支持 KaTeX 行内和块级公式 | +| 快捷键 | Ctrl+S 保存文件 | + +## Markdown 语法支持 + +使用 `#` 标题和 `-` 列表来构建思维导图的层级结构: + +```markdown +# 根节点 + +## 一级分支 + +- 二级项目 + - 三级项目 + - 另一个三级项目 +- 另一个二级项目 +``` + +### Frontmatter 配置 + +在 Markdown 开头使用 YAML frontmatter 配置思维导图选项: + +```yaml +--- +title: 我的思维导图 +markmap: + colorFreezeLevel: 2 + theme: soft +--- +``` + +#### 配色主题 + +控制思维导图节点和连线的颜色、节点背景、连线弧度、线宽及悬停效果,不影响页面背景(除 `dark` 主题自带画布底色,已自动移除以保持与日/夜间模式独立): + +| 主题值 | 名称 | 说明 | +|--------|------|------| +| (留空) | 默认 | markmap 原始配色(schemeCategory10) | +| `soft` | 柔和 | 柔和暖色调,带节点背景和悬停变暗 | +| `dark` | 深色 | 深色系配色,适合夜间模式 | +| `forest` | 森林 | 绿色系自然色调,带节点背景 | +| `monochrome` | 单色 | 灰度配色,简约连线 | +| `minimal` | 极简 | 透明背景,按层级不同字号 | +| `colorful` | 彩色 | 鲜艳多彩,每条分支不同颜色 | + +通过工具栏"主题"按钮可快速切换,切换会自动更新 frontmatter 中的 `theme` 字段。 + +#### 日间/夜间模式 + +通过工具栏"日间/夜间"按钮切换整个页面的浅色/深色背景。夜间模式会将背景、编辑器、预览区全部切换为深色配色,模式选择会保存在浏览器本地,下次打开自动恢复。 + +#### 历史文档 + +左侧侧边栏自动记录所有编辑过的文档,缓存在浏览器 `localStorage` 中,刷新页面不会丢失。每次输入内容后自动保存(防抖 1 秒),最多保留 50 条记录。 + +- 点击侧边栏文档可切换查看 +- 鼠标悬停显示删除按钮 +- 点击侧边栏顶部 `+` 或工具栏"新建"创建新文档 +- 侧边栏可通过左上角菜单按钮收起/展开,状态自动记忆 + +## 项目结构 + +``` +markmap/ +├── index.html # 主页面 +├── content.md # 默认加载的示例内容 +├── serve.py # Python 本地服务器 +├── start.bat # Windows 启动脚本 +├── README.md # 本文档 +├── LICENSE # MIT 许可证 +├── css/ +│ └── styles.css # 样式表 +├── javascript/ +│ ├── markmap_editor.js # 编辑器核心逻辑 +│ └── export_utils.js # 导出功能(SVG/PNG/PDF) +└── libs/ # 预构建依赖库(无需安装) + ├── d3/ # D3 可视化库 + ├── markmap_view/ # 思维导图渲染 + ├── markmap_lib/ # Markdown 转换 + ├── katex/ # 数学公式渲染 + ├── prismjs/ # 代码语法高亮 + ├── highlightjs/ # 代码高亮备选 + ├── webfontloader/ # 字体加载 + ├── jspdf.js # PDF 生成 + ├── html2canvas.js # HTML 转图片 + └── pako.js # 压缩/解压 +``` + +## 技术栈 + +- **HTML5** - 页面结构 +- **CSS3** - 样式(使用 CSS 变量,支持主题定制) +- **JavaScript (ES6+)** - 所有应用逻辑,无框架依赖 +- **D3.js** - 数据可视化 +- **markmap-lib / markmap-view** - Markdown 解析与思维导图渲染 +- **KaTeX** - 数学公式 +- **Prism.js / highlight.js** - 代码高亮 +- **jsPDF + html2canvas** - 导出功能 +- **pako** - URL 分享压缩 + +## 浏览器兼容性 + +支持所有现代浏览器:Chrome、Firefox、Edge、Safari。 + +## 许可证 + +MIT diff --git a/content.md b/content.md new file mode 100644 index 0000000..c125546 --- /dev/null +++ b/content.md @@ -0,0 +1,84 @@ +--- +title: Markmap Features +markmap: + colorFreezeLevel: 2 + direction: balanced + theme: soft +--- + +## Themes + +- classic +- soft +- dark +- forest +- monochrome +- minimal + +## Directions + +- right +- left +- down +- balanced + +## Link Styles + +- `classic` - stroke lines +- `0.3` - slight taper +- `0.5` - moderate taper +- `0.85` - brush style +- `1` - full taper + +## Node Styling + +### Backgrounds + +- Capsule root node +- Rounded rect L1/L2 +- Underline L3+ +- Auto border colors + +### Typography + +- Font size by depth +- Font weight by depth +- Auto text color (dark/light) + +### Effects + +- Drop shadow (L1) +- Hover dimming +- Canvas background color + +## Directives + +### Icons + +- `done` +- `wip` +- `blocked` +- `priority-high` +- `priority-mid` +- `priority-low` +- `flag` +- `question` + +### Relations + +- `rel: target | label` +- Auto arc direction +- Collision avoidance + +### Boundaries + +- `boundary: name` - with title +- `boundary: name | #color` - custom color +- `boundary: | #color` - color only, no title +- `boundary` - default style + +### Folding + +- `fold` - fold node +- `foldAll` - fold recursively +- Balanced: left/right independent diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..866a3bf --- /dev/null +++ b/css/styles.css @@ -0,0 +1,716 @@ +:root { + --accent-color: #4a9eff; + --accent-soft: rgba(74, 158, 255, 0.1); + --accent-medium: rgba(74, 158, 255, 0.18); + --text-primary: #1a1a2e; + --text-secondary: #555; + --text-muted: #999; + --glass-bg: rgba(255, 255, 255, 0.55); + --glass-border: rgba(255, 255, 255, 0.6); + --glass-shadow: 0 2px 16px rgba(0, 0, 0, 0.04); + --header-bg: rgba(245, 247, 252, 0.88); + --header-border: rgba(180, 195, 220, 0.35); + --footer-bg: rgba(242, 244, 248, 0.88); + --footer-border: rgba(180, 195, 220, 0.3); + --divider-color: rgba(0, 0, 0, 0.06); + --editor-bg: rgba(255, 255, 255, 0.72); + --editor-font: 'Cascadia Code', 'Fira Code', 'Consolas', 'Monaco', monospace; + --editor-text: #2c2c2c; + --preview-bg: rgba(248, 250, 252, 0.6); + --toolbar-bg: rgba(255, 255, 255, 0.65); + --toolbar-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); + --btn-bg: rgba(255, 255, 255, 0.7); + --scrollbar-thumb: rgba(0, 0, 0, 0.1); + --scrollbar-thumb-hover: rgba(0, 0, 0, 0.2); + --splitter-handle: rgba(0, 0, 0, 0.12); + --sidebar-bg: rgba(250, 250, 253, 0.85); + --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --radius: 10px; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + height: 100vh; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + overflow: hidden; + color: var(--text-primary); + background: linear-gradient(135deg, #e8f0fe 0%, #f5f7fa 30%, #fef6f0 70%, #f0f4ff 100%); + background-attachment: fixed; +} + +/* ── Header ── */ + +header { + background: var(--header-bg); + color: var(--text-primary); + padding: 0 20px; + display: flex; + align-items: center; + justify-content: space-between; + height: 52px; + flex-shrink: 0; + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.06); + z-index: 100; + -webkit-backdrop-filter: blur(20px) saturate(180%); + backdrop-filter: blur(20px) saturate(180%); + border-bottom: 1px solid var(--header-border); +} + +header h1 { + margin: 0; + font-size: 17px; + font-weight: 600; + letter-spacing: 0.3px; + display: flex; + align-items: center; + gap: 8px; + color: var(--text-primary); +} + +header h1::before { + content: ''; + display: inline-block; + width: 20px; + height: 20px; + background: var(--accent-color); + border-radius: 5px; + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M2 4h6v2H4v10h4v2H2V4zm10 0h6v14h-6v-2h4V6h-4V4z' fill='black'/%3E%3C/svg%3E") center / contain no-repeat; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M2 4h6v2H4v10h4v2H2V4zm10 0h6v14h-6v-2h4V6h-4V4z' fill='black'/%3E%3C/svg%3E") center / contain no-repeat; +} + +header .toolbar { + display: flex; + gap: 6px; +} + +.header-left { + display: flex; + align-items: center; + gap: 10px; +} + +.icon-btn { + background-color: var(--btn-bg); + color: var(--text-secondary); + border: 1px solid var(--header-border); + padding: 6px; + border-radius: 8px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition); + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); +} + +.icon-btn:hover { + background-color: var(--accent-soft); + border-color: var(--accent-color); + color: var(--accent-color); + transform: translateY(-1px); +} + +.icon-btn svg { + fill: currentColor; +} + +header .toolbar button { + background-color: var(--btn-bg); + color: var(--text-secondary); + border: 1px solid var(--header-border); + padding: 6px 16px; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: all var(--transition); + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); +} + +header .toolbar button:hover { + background-color: var(--accent-soft); + border-color: var(--accent-color); + color: var(--accent-color); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(74, 158, 255, 0.15); +} + +header .toolbar button:active { + background-color: var(--accent-medium); + transform: translateY(0); +} + +/* ── Layout ── */ + +#app { + display: flex; + flex-direction: column; + height: 100%; +} + +main { + display: flex; + flex: 1; + overflow: hidden; + min-height: 0; +} + +/* ── Editor ── */ + +#editor-container { + width: 40%; + flex-shrink: 0; + height: 100%; + background: var(--editor-bg); + position: relative; + -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); +} + +#editor { + width: 100%; + height: 100%; + resize: none; + padding: 16px 20px; + border: none; + background: transparent; + font-family: var(--editor-font); + font-size: 13.5px; + line-height: 1.7; + color: var(--editor-text); + outline: none; + tab-size: 2; +} + +#editor::placeholder { + color: var(--text-muted); + line-height: 1.8; +} + +#editor::-webkit-scrollbar { + width: 6px; +} + +#editor::-webkit-scrollbar-track { + background: transparent; +} + +#editor::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 3px; +} + +#editor::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +/* ── Sidebar ── */ + +#sidebar { + width: 220px; + flex-shrink: 0; + height: 100%; + background: var(--sidebar-bg); + border-right: 1px solid var(--divider-color); + display: flex; + flex-direction: column; + overflow: hidden; + transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1); + -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); +} + +#sidebar.collapsed { + width: 0; + border-right: none; +} + +#sidebar.no-anim { + transition: none; +} + +.sidebar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; + border-bottom: 1px solid var(--divider-color); + font-size: 13px; + font-weight: 600; + color: var(--text-secondary); + flex-shrink: 0; +} + +.sidebar-header button { + background: var(--btn-bg); + border: 1px solid var(--header-border); + border-radius: 6px; + cursor: pointer; + font-size: 16px; + color: var(--text-secondary); + width: 26px; + height: 26px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + line-height: 1; +} + +.sidebar-header button:hover { + background: var(--accent-soft); + color: var(--accent-color); + border-color: var(--accent-color); +} + +.sidebar-docs { + flex: 1; + overflow-y: auto; + padding: 6px; +} + +.sidebar-docs::-webkit-scrollbar { + width: 4px; +} + +.sidebar-docs::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 2px; +} + +.sidebar-empty { + text-align: center; + color: var(--text-muted); + font-size: 12px; + padding: 20px 10px; +} + +.doc-item { + padding: 8px 10px; + border-radius: 6px; + cursor: pointer; + transition: background 0.2s; + position: relative; + margin-bottom: 2px; +} + +.doc-item:hover { + background: var(--accent-soft); +} + +.doc-item.active { + background: var(--accent-medium); +} + +.doc-item-title { + font-size: 13px; + color: var(--text-primary); + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 20px; +} + +.doc-item-time { + font-size: 11px; + color: var(--text-muted); + margin-top: 2px; +} + +.doc-item-delete { + position: absolute; + top: 50%; + right: 6px; + transform: translateY(-50%); + width: 20px; + height: 20px; + border: none; + background: transparent; + color: var(--text-muted); + cursor: pointer; + border-radius: 4px; + display: none; + align-items: center; + justify-content: center; + font-size: 14px; + line-height: 1; +} + +.doc-item:hover .doc-item-delete { + display: flex; +} + +.doc-item-delete:hover { + background: rgba(255, 99, 71, 0.15); + color: #ff6347; +} + +/* ── Splitter ── */ + +#splitter { + width: 5px; + flex-shrink: 0; + cursor: col-resize; + background: var(--divider-color); + position: relative; + z-index: 10; + transition: background 0.2s; +} + +#splitter::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 3px; + height: 32px; + border-radius: 2px; + background: var(--splitter-handle); + transition: background 0.2s; +} + +#splitter:hover::after, +#splitter.dragging::after { + background: var(--accent-color); +} + +#splitter:hover, +#splitter.dragging { + background: rgba(74, 158, 255, 0.1); +} + +/* ── Theme Selector ── */ + +.theme-selector { + position: relative; +} + +.theme-dropdown { + display: none; + position: absolute; + top: calc(100% + 6px); + right: 0; + background: var(--header-bg); + border: 1px solid var(--header-border); + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); + -webkit-backdrop-filter: blur(20px) saturate(180%); + backdrop-filter: blur(20px) saturate(180%); + padding: 4px; + min-width: 100px; + z-index: 200; +} + +.theme-dropdown.show { + display: flex; + flex-direction: column; +} + +.theme-dropdown button { + background: transparent; + border: none; + color: var(--text-secondary); + padding: 6px 14px; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + text-align: left; + transition: all 0.2s; + white-space: nowrap; +} + +.theme-dropdown button:hover { + background: var(--accent-soft); + color: var(--accent-color); +} + +.theme-dropdown button.active { + background: var(--accent-medium); + color: var(--accent-color); + font-weight: 500; +} + +/* ── 夜间模式 ── */ + +body.night-mode { + --text-primary: #e0e0e0; + --text-secondary: #aaa; + --text-muted: #777; + --glass-bg: rgba(30, 30, 45, 0.55); + --glass-border: rgba(60, 60, 80, 0.4); + --header-bg: rgba(20, 20, 35, 0.88); + --header-border: rgba(60, 60, 80, 0.35); + --footer-bg: rgba(20, 20, 35, 0.88); + --footer-border: rgba(60, 60, 80, 0.3); + --divider-color: rgba(255, 255, 255, 0.06); + --editor-bg: rgba(30, 30, 45, 0.72); + --editor-text: #e0e0e0; + --preview-bg: rgba(25, 25, 40, 0.6); + --toolbar-bg: rgba(30, 30, 45, 0.65); + --btn-bg: rgba(255, 255, 255, 0.08); + --scrollbar-thumb: rgba(255, 255, 255, 0.15); + --scrollbar-thumb-hover: rgba(255, 255, 255, 0.25); + --splitter-handle: rgba(255, 255, 255, 0.15); + --sidebar-bg: rgba(20, 20, 35, 0.85); + background: linear-gradient(135deg, #0d1117 0%, #161b22 30%, #1a1a2e 70%, #0d1117 100%); + background-attachment: fixed; +} + +body.night-mode .markmap-node text { + fill: #e0e0e0; +} + +body.night-mode .markmap-link { + stroke: rgba(255, 255, 255, 0.15); +} + +/* ── 思维导图节点内图片尺寸 ── */ + +.markmap-node img { + max-width: 200px; + max-height: 150px; + border-radius: 4px; + object-fit: contain; +} + +/* ── Preview ── */ + +#markmap-container { + flex: 1; + height: 100%; + position: relative; + background: var(--preview-bg); + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); +} + +#markmap { + width: 100%; + height: 100%; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +/* ── Footer ── */ + +footer { + background: var(--footer-bg); + color: var(--text-muted); + text-align: center; + padding: 6px 0; + font-size: 11.5px; + flex-shrink: 0; + letter-spacing: 0.3px; + -webkit-backdrop-filter: blur(20px) saturate(180%); + backdrop-filter: blur(20px) saturate(180%); + border-top: 1px solid var(--footer-border); + box-shadow: 0 -1px 6px rgba(0, 0, 0, 0.04); +} + +/* ── Floating Toolbar ── */ + +.markmap-toolbar { + position: absolute; + bottom: 16px; + right: 16px; + display: flex; + gap: 4px; + z-index: 10000; + background: var(--toolbar-bg); + border: 1px solid var(--glass-border); + border-radius: var(--radius); + padding: 6px; + box-shadow: var(--toolbar-shadow); + -webkit-backdrop-filter: blur(20px) saturate(180%); + backdrop-filter: blur(20px) saturate(180%); +} + +.markmap-btn { + width: 32px; + height: 32px; + padding: 5px; + background: transparent; + border: none; + border-radius: 7px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition); + color: var(--text-secondary); + position: relative; +} + +.markmap-btn::after { + content: attr(data-tooltip); + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%) translateY(4px); + background: rgba(40, 42, 60, 0.92); + color: #fff; + padding: 4px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s ease, transform 0.2s ease; + z-index: 10001; +} + +.markmap-btn::before { + content: ''; + position: absolute; + bottom: calc(100% + 3px); + left: 50%; + transform: translateX(-50%); + border: 5px solid transparent; + border-top-color: rgba(40, 42, 60, 0.92); + opacity: 0; + transition: opacity 0.2s ease; + pointer-events: none; +} + +.markmap-btn:hover::after { + opacity: 1; + transform: translateX(-50%) translateY(0); +} + +.markmap-btn:hover::before { + opacity: 1; +} + +.markmap-btn:hover { + background: var(--accent-soft); + color: var(--accent-color); + transform: translateY(-1px); +} + +.markmap-btn:active { + transform: translateY(0); + background: var(--accent-medium); +} + +.markmap-btn svg { + width: 18px; + height: 18px; + fill: currentColor; +} + +/* 自动适应窗口激活状态 */ +.markmap-btn.autofit-active { + background: var(--accent-soft); + color: var(--accent-color); +} + +.markmap-btn.autofit-active::after { + background: var(--accent-color); +} + +.markmap-btn.autofit-active::before { + border-top-color: var(--accent-color); +} + +/* 激活状态脉冲指示点 */ +.markmap-btn.autofit-active svg { + animation: autofit-pulse 2s ease-in-out infinite; +} + +@keyframes autofit-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* ── PDF 方向选择弹出菜单 ── */ + +.pdf-orientation-menu { + background: var(--toolbar-bg); + -webkit-backdrop-filter: blur(20px) saturate(180%); + backdrop-filter: blur(20px) saturate(180%); + border: 1px solid var(--header-border); + border-radius: 10px; + box-shadow: var(--toolbar-shadow); + padding: 4px; + z-index: 10000; + display: flex; + flex-direction: column; + gap: 2px; + min-width: 140px; + animation: pdfMenuFadeIn 0.15s ease-out; +} + +@keyframes pdfMenuFadeIn { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} + +.pdf-orientation-item { + background: transparent; + border: none; + padding: 8px 12px; + border-radius: 6px; + cursor: pointer; + display: flex; + flex-direction: column; + gap: 2px; + text-align: left; + transition: all 0.15s; +} + +.pdf-orientation-item:hover { + background: var(--accent-soft); +} + +.pdf-orientation-label { + font-size: 13px; + font-weight: 500; + color: var(--text-primary); +} + +.pdf-orientation-desc { + font-size: 11px; + color: var(--text-muted); +} + +/* ── Fullscreen ── */ + +body.fullscreen header, +body.fullscreen footer, +body.fullscreen #editor-container, +body.fullscreen #splitter, +body.fullscreen #sidebar { + display: none !important; +} + +body.fullscreen #markmap-container { + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100vw !important; + height: 100vh !important; + z-index: 9999 !important; + border: none !important; +} + +body.fullscreen .markmap-toolbar { + bottom: 24px; + right: 24px; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..05e7c52 --- /dev/null +++ b/index.html @@ -0,0 +1,75 @@ + + +
+ + +0?d[i-1]:l,v.x1=i
0)for(i=0;i=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e=i)&&(e=i)}return e}function tt(t,n){let e,r=-1,i=-1;if(void 0===n)for(const n of t)++i,null!=n&&(e =0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=un);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o >1)+h+t+M+S.slice(A);break;default:t=S+h+t+M}return u(t)}return y=void 0===y?6:/[gprs]/.test(_)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),M.toString=function(){return t+""},M}return{format:l,formatPrefix:function(t,n){var e=l(((t=Jc(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(Zc(n)/3))),i=Math.pow(10,-r),o=uf[8+r/3];return function(t){return e(i*t)+o}}}}function ff(n){return of=cf(n),t.format=of.format,t.formatPrefix=of.formatPrefix,of}function sf(t){return Math.max(0,-Zc(Math.abs(t)))}function lf(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Zc(n)/3)))-Zc(Math.abs(t)))}function hf(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,Zc(n)-Zc(t))+1}t.format=void 0,t.formatPrefix=void 0,ff({thousands:",",grouping:[3],currency:["$",""]});var df=1e-6,pf=1e-12,gf=Math.PI,yf=gf/2,vf=gf/4,_f=2*gf,bf=180/gf,mf=gf/180,xf=Math.abs,wf=Math.atan,Mf=Math.atan2,Tf=Math.cos,Af=Math.ceil,Sf=Math.exp,Ef=Math.hypot,Nf=Math.log,kf=Math.pow,Cf=Math.sin,Pf=Math.sign||function(t){return t>0?1:t<0?-1:0},zf=Math.sqrt,$f=Math.tan;function Df(t){return t>1?0:t<-1?gf:Math.acos(t)}function Rf(t){return t>1?yf:t<-1?-yf:Math.asin(t)}function Ff(t){return(t=Cf(t/2))*t}function qf(){}function Uf(t,n){t&&Of.hasOwnProperty(t.type)&&Of[t.type](t,n)}var If={Feature:function(t,n){Uf(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r=0?1:-1,i=r*e,o=Tf(n=(n*=mf)/2+vf),a=Cf(n),u=Vf*a,c=Gf*o+u*Tf(i),f=u*r*Cf(i);as.add(Mf(f,c)),Xf=t,Gf=o,Vf=a}function ds(t){return[Mf(t[1],t[0]),Rf(t[2])]}function ps(t){var n=t[0],e=t[1],r=Tf(e);return[r*Tf(n),r*Cf(n),Cf(e)]}function gs(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function ys(t,n){return[t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]]}function vs(t,n){t[0]+=n[0],t[1]+=n[1],t[2]+=n[2]}function _s(t,n){return[t[0]*n,t[1]*n,t[2]*n]}function bs(t){var n=zf(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=n,t[1]/=n,t[2]/=n}var ms,xs,ws,Ms,Ts,As,Ss,Es,Ns,ks,Cs,Ps,zs,$s,Ds,Rs,Fs={point:qs,lineStart:Is,lineEnd:Os,polygonStart:function(){Fs.point=Bs,Fs.lineStart=Ys,Fs.lineEnd=Ls,rs=new T,cs.polygonStart()},polygonEnd:function(){cs.polygonEnd(),Fs.point=qs,Fs.lineStart=Is,Fs.lineEnd=Os,as<0?(Wf=-(Kf=180),Zf=-(Qf=90)):rs>df?Qf=90:rs<-df&&(Zf=-90),os[0]=Wf,os[1]=Kf},sphere:function(){Wf=-(Kf=180),Zf=-(Qf=90)}};function qs(t,n){is.push(os=[Wf=t,Kf=t]),n t.r&&(t.r=t[n].r)}function c(){if(n){var r,i,o=n.length;for(e=new Array(o),r=0;r >>1;f[g]1;)i-=2;for(let t=2;t0){if(n>=this.ymax)return null;(i=(this.ymax-n)/r)0){if(t>=this.xmax)return null;(i=(this.xmax-t)/e)this.xmax?2:0)|(n9999?"+"+Ku(n,6):Ku(n,4))+"-"+Ku(t.getUTCMonth()+1,2)+"-"+Ku(t.getUTCDate(),2)+(o?"T"+Ku(e,2)+":"+Ku(r,2)+":"+Ku(i,2)+"."+Ku(o,3)+"Z":i?"T"+Ku(e,2)+":"+Ku(r,2)+":"+Ku(i,2)+"Z":r||e?"T"+Ku(e,2)+":"+Ku(r,2)+"Z":"")}function Ju(t){var n=new RegExp('["'+t+"\n\r]"),e=t.charCodeAt(0);function r(t,n){var r,i=[],o=t.length,a=0,u=0,c=o<=0,f=!1;function s(){if(c)return Hu;if(f)return f=!1,ju;var n,r,i=a;if(t.charCodeAt(i)===Xu){for(;a++=v)<<1|t>=y)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-f],p[p.length-1-f]=c)}else{var _=t-+this._x.call(null,g.data),b=n-+this._y.call(null,g.data),m=_*_+b*b;if(m=u)){(t.data!==n||t.next)&&(0===l&&(p+=(l=Uc(e))*l),0===h&&(p+=(h=Uc(e))*h),p(t=(Lc*t+jc)%Hc)/Hc}();function l(){h(),f.call("tick",n),e1?(f.on(t,e),n):f.on(t)}}},t.forceX=function(t){var n,e,r,i=qc(.1);function o(t){for(var i,o=0,a=n.length;o=.12&&i<.234&&r>=-.425&&r<-.214?u:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:a).invert(t)},s.stream=function(e){return t&&n===e?t:(r=[a.stream(n=e),u.stream(e),c.stream(e)],i=r.length,t={point:function(t,n){for(var e=-1;++ejs(r[0],r[1])&&(r[1]=i[1]),js(i[0],r[1])>js(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(a=-1/0,n=0,r=o[e=o.length-1];n<=e;r=i,++n)i=o[n],(u=js(r[1],i[0]))>a&&(a=u,Wf=i[0],Kf=r[1])}return is=os=null,Wf===1/0||Zf===1/0?[[NaN,NaN],[NaN,NaN]]:[[Wf,Zf],[Kf,Qf]]},t.geoCentroid=function(t){ms=xs=ws=Ms=Ts=As=Ss=Es=0,Ns=new T,ks=new T,Cs=new T,Lf(t,Gs);var n=+Ns,e=+ks,r=+Cs,i=Ef(n,e,r);return i and other contributors
+ License: BSD-3-Clause
+ */
+var hljs=function(){"use strict";function e(n){
+return n instanceof Map?n.clear=n.delete=n.set=()=>{
+throw Error("map is read-only")}:n instanceof Set&&(n.add=n.clear=n.delete=()=>{
+throw Error("set is read-only")
+}),Object.freeze(n),Object.getOwnPropertyNames(n).forEach((t=>{
+const a=n[t],i=typeof a;"object"!==i&&"function"!==i||Object.isFrozen(a)||e(a)
+})),n}class n{constructor(e){
+void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}
+ignoreMatch(){this.isMatchIgnored=!0}}function t(e){
+return e.replace(/&/g,"&").replace(/