@geminilight/mindos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.local.example +38 -0
- package/LICENSE +21 -0
- package/README.md +423 -0
- package/README_zh.md +423 -0
- package/app/README.md +152 -0
- package/app/app/api/ask/route.ts +170 -0
- package/app/app/api/ask-sessions/route.ts +90 -0
- package/app/app/api/auth/route.ts +37 -0
- package/app/app/api/backlinks/route.ts +22 -0
- package/app/app/api/bootstrap/route.ts +37 -0
- package/app/app/api/extract-pdf/route.ts +82 -0
- package/app/app/api/file/route.ts +138 -0
- package/app/app/api/files/route.ts +12 -0
- package/app/app/api/git/route.ts +42 -0
- package/app/app/api/graph/route.ts +113 -0
- package/app/app/api/recent-files/route.ts +10 -0
- package/app/app/api/search/route.ts +17 -0
- package/app/app/api/settings/reset-token/route.ts +21 -0
- package/app/app/api/settings/route.ts +123 -0
- package/app/app/error.tsx +33 -0
- package/app/app/globals.css +368 -0
- package/app/app/icon.svg +35 -0
- package/app/app/layout.tsx +103 -0
- package/app/app/login/page.tsx +120 -0
- package/app/app/page.tsx +12 -0
- package/app/app/view/[...path]/ViewPageClient.tsx +343 -0
- package/app/app/view/[...path]/error.tsx +33 -0
- package/app/app/view/[...path]/loading.tsx +15 -0
- package/app/app/view/[...path]/page.tsx +93 -0
- package/app/components/AskFab.tsx +59 -0
- package/app/components/AskModal.tsx +398 -0
- package/app/components/Backlinks.tsx +75 -0
- package/app/components/Breadcrumb.tsx +31 -0
- package/app/components/CsvView.tsx +325 -0
- package/app/components/DirView.tsx +138 -0
- package/app/components/Editor.tsx +124 -0
- package/app/components/EditorWrapper.tsx +17 -0
- package/app/components/ErrorBoundary.tsx +53 -0
- package/app/components/FileTree.tsx +369 -0
- package/app/components/HomeContent.tsx +262 -0
- package/app/components/JsonView.tsx +27 -0
- package/app/components/MarkdownEditor.tsx +95 -0
- package/app/components/MarkdownView.tsx +118 -0
- package/app/components/SearchModal.tsx +193 -0
- package/app/components/SettingsModal.tsx +237 -0
- package/app/components/Sidebar.tsx +136 -0
- package/app/components/SidebarLayout.tsx +36 -0
- package/app/components/TableOfContents.tsx +150 -0
- package/app/components/ThemeToggle.tsx +34 -0
- package/app/components/WysiwygEditor.tsx +75 -0
- package/app/components/ask/FileChip.tsx +30 -0
- package/app/components/ask/MentionPopover.tsx +52 -0
- package/app/components/ask/MessageList.tsx +126 -0
- package/app/components/ask/SessionHistory.tsx +49 -0
- package/app/components/renderers/AgentInspectorRenderer.tsx +277 -0
- package/app/components/renderers/BacklinksRenderer.tsx +147 -0
- package/app/components/renderers/ConfigRenderer.tsx +236 -0
- package/app/components/renderers/CsvRenderer.tsx +77 -0
- package/app/components/renderers/DiffRenderer.tsx +310 -0
- package/app/components/renderers/GraphRenderer.tsx +428 -0
- package/app/components/renderers/SummaryRenderer.tsx +251 -0
- package/app/components/renderers/TimelineRenderer.tsx +213 -0
- package/app/components/renderers/TodoRenderer.tsx +474 -0
- package/app/components/renderers/WorkflowRenderer.tsx +404 -0
- package/app/components/renderers/csv/BoardView.tsx +146 -0
- package/app/components/renderers/csv/ConfigPanel.tsx +117 -0
- package/app/components/renderers/csv/EditableCell.tsx +43 -0
- package/app/components/renderers/csv/GalleryView.tsx +40 -0
- package/app/components/renderers/csv/TableView.tsx +164 -0
- package/app/components/renderers/csv/types.ts +87 -0
- package/app/components/settings/AiTab.tsx +111 -0
- package/app/components/settings/AppearanceTab.tsx +101 -0
- package/app/components/settings/KnowledgeTab.tsx +157 -0
- package/app/components/settings/PluginsTab.tsx +82 -0
- package/app/components/settings/Primitives.tsx +60 -0
- package/app/components/settings/ShortcutsTab.tsx +22 -0
- package/app/components/settings/types.ts +41 -0
- package/app/components/ui/button.tsx +60 -0
- package/app/components/ui/dialog.tsx +157 -0
- package/app/components/ui/input.tsx +20 -0
- package/app/components/ui/scroll-area.tsx +55 -0
- package/app/components/ui/toggle.tsx +44 -0
- package/app/components/ui/tooltip.tsx +66 -0
- package/app/components.json +25 -0
- package/app/data/pages/home-dark.png +0 -0
- package/app/data/pages/home-mobile-crop.png +0 -0
- package/app/data/pages/home-mobile.png +0 -0
- package/app/data/pages/home.png +0 -0
- package/app/data/pages/view-dir.png +0 -0
- package/app/data/pages/view-file-bot.png +0 -0
- package/app/data/pages/view-file-dark-crop.png +0 -0
- package/app/data/pages/view-file-dark.png +0 -0
- package/app/data/pages/view-file-mobile.png +0 -0
- package/app/data/pages/view-file-sm.png +0 -0
- package/app/data/pages/view-file-top.png +0 -0
- package/app/data/pages/view-file.png +0 -0
- package/app/eslint.config.mjs +18 -0
- package/app/hooks/useAskSession.ts +181 -0
- package/app/hooks/useFileUpload.ts +126 -0
- package/app/hooks/useMention.ts +65 -0
- package/app/lib/LocaleContext.tsx +40 -0
- package/app/lib/actions.ts +40 -0
- package/app/lib/agent/index.ts +3 -0
- package/app/lib/agent/model.ts +18 -0
- package/app/lib/agent/prompt.ts +32 -0
- package/app/lib/agent/tools.ts +151 -0
- package/app/lib/api.ts +55 -0
- package/app/lib/core/backlinks.ts +40 -0
- package/app/lib/core/csv.ts +28 -0
- package/app/lib/core/fs-ops.ts +118 -0
- package/app/lib/core/git.ts +50 -0
- package/app/lib/core/index.ts +58 -0
- package/app/lib/core/lines.ts +89 -0
- package/app/lib/core/search.ts +79 -0
- package/app/lib/core/security.ts +43 -0
- package/app/lib/core/tree.ts +113 -0
- package/app/lib/core/types.ts +40 -0
- package/app/lib/fs.ts +467 -0
- package/app/lib/i18n.ts +300 -0
- package/app/lib/jwt.ts +58 -0
- package/app/lib/renderers/index.ts +79 -0
- package/app/lib/renderers/registry.ts +70 -0
- package/app/lib/settings.ts +150 -0
- package/app/lib/types.ts +32 -0
- package/app/lib/utils.ts +34 -0
- package/app/next-env.d.ts +6 -0
- package/app/next.config.ts +10 -0
- package/app/package-lock.json +15306 -0
- package/app/package.json +71 -0
- package/app/postcss.config.mjs +7 -0
- package/app/proxy.ts +64 -0
- package/app/public/file.svg +1 -0
- package/app/public/globe.svg +1 -0
- package/app/public/landing/index.html +353 -0
- package/app/public/landing/style.css +216 -0
- package/app/public/logo-square.svg +37 -0
- package/app/public/logo.svg +37 -0
- package/app/public/next.svg +1 -0
- package/app/public/vercel.svg +1 -0
- package/app/public/window.svg +1 -0
- package/app/scripts/extract-pdf.cjs +56 -0
- package/app/tsconfig.json +34 -0
- package/app/vitest.config.ts +14 -0
- package/assets/demo-flow-zh.html +622 -0
- package/assets/images/demo-flow-dark.png +0 -0
- package/assets/images/demo-flow-light.png +0 -0
- package/assets/images/demo-flow-zh-dark.png +0 -0
- package/assets/images/demo-flow-zh-light.png +0 -0
- package/assets/images/gui-sync-cv.png +0 -0
- package/assets/logo-square.svg +37 -0
- package/bin/cli.js +894 -0
- package/mcp/README.md +113 -0
- package/mcp/package-lock.json +1717 -0
- package/mcp/package.json +18 -0
- package/mcp/src/index.ts +494 -0
- package/mcp/tsconfig.json +13 -0
- package/package.json +49 -0
- package/scripts/setup.js +675 -0
- package/scripts/upgrade-prompt.md +147 -0
- package/skills/mindos/SKILL.md +319 -0
- package/skills/mindos-zh/SKILL.md +318 -0
- package/templates/README.md +31 -0
- package/templates/empty/CHANGELOG.md +9 -0
- package/templates/empty/CONFIG.json +197 -0
- package/templates/empty/CONFIG.md +73 -0
- package/templates/empty/INSTRUCTION.md +177 -0
- package/templates/empty/README.md +27 -0
- package/templates/en/CHANGELOG.md +9 -0
- package/templates/en/CONFIG.json +197 -0
- package/templates/en/CONFIG.md +73 -0
- package/templates/en/INSTRUCTION.md +177 -0
- package/templates/en/README.md +27 -0
- package/templates/en/TODO.md +13 -0
- package/templates/en//360/237/221/244 Profile/INSTRUCTION.md" +21 -0
- package/templates/en//360/237/221/244 Profile/README.md" +15 -0
- package/templates/en//360/237/221/244 Profile//342/232/231/357/270/217 Preferences.md" +21 -0
- package/templates/en//360/237/221/244 Profile//360/237/216/257 Focus.md" +31 -0
- package/templates/en//360/237/221/244 Profile//360/237/221/244 Identity.md" +22 -0
- package/templates/en//360/237/223/232 Resources/INSTRUCTION.md" +29 -0
- package/templates/en//360/237/223/232 Resources/README.md" +21 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Influencers.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Products.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Scholars.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Tools.csv" +1 -0
- package/templates/en//360/237/223/235 Notes/INSTRUCTION.md" +31 -0
- package/templates/en//360/237/223/235 Notes/Ideas/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Ideas//360/237/247/252_example_product_idea.md" +16 -0
- package/templates/en//360/237/223/235 Notes/Inbox/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Inbox//360/237/247/252_example_quick_capture.md" +14 -0
- package/templates/en//360/237/223/235 Notes/Meetings/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Meetings//360/237/247/252_example_meeting_note.md" +17 -0
- package/templates/en//360/237/223/235 Notes/README.md" +24 -0
- package/templates/en//360/237/223/235 Notes/Waiting/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Waiting//360/237/247/252_example_blocked_item.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Configurations/README.md" +3 -0
- package/templates/en//360/237/224/204 Workflows/Configurations//360/237/247/252_example_config_update_sop.md" +14 -0
- package/templates/en//360/237/224/204 Workflows/INSTRUCTION.md" +21 -0
- package/templates/en//360/237/224/204 Workflows/Information/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Information//360/237/247/252_example_info_capture_sop.md" +13 -0
- package/templates/en//360/237/224/204 Workflows/Media/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Media//360/237/247/252_example_content_publish_sop.md" +13 -0
- package/templates/en//360/237/224/204 Workflows/README.md" +22 -0
- package/templates/en//360/237/224/204 Workflows/Research/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Research//360/237/247/252_example_lit_review_sop.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Startup/README.md" +3 -0
- package/templates/en//360/237/224/204 Workflows/Startup//360/237/247/252_example_weekly_founder_ops.md" +22 -0
- package/templates/en//360/237/224/227 Connections/Classmates/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Classmates//360/237/247/252_example_leo_chen.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Colleagues/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Colleagues//360/237/247/252_example_ethan_zhao.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Connections Overview.csv" +5 -0
- package/templates/en//360/237/224/227 Connections/Family/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Family//360/237/247/252_example_james_wang.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Friends/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Friends//360/237/247/252_example_lily_lin.md" +16 -0
- package/templates/en//360/237/224/227 Connections/INSTRUCTION.md" +56 -0
- package/templates/en//360/237/224/227 Connections/README.md" +20 -0
- package/templates/en//360/237/232/200 Projects/Archived/README.md" +9 -0
- package/templates/en//360/237/232/200 Projects/Archived//360/237/247/252_example_archived_project_note.md" +14 -0
- package/templates/en//360/237/232/200 Projects/INSTRUCTION.md" +29 -0
- package/templates/en//360/237/232/200 Projects/Products/README.md" +16 -0
- package/templates/en//360/237/232/200 Projects/Products//360/237/247/252_example_product_project_brief.md" +20 -0
- package/templates/en//360/237/232/200 Projects/README.md" +21 -0
- package/templates/en//360/237/232/200 Projects/Research/README.md" +16 -0
- package/templates/en//360/237/232/200 Projects/Research//360/237/247/252_example_research_project_brief.md" +16 -0
- package/templates/template-generation-skill.md +79 -0
- package/templates/zh/CHANGELOG.md +9 -0
- package/templates/zh/CONFIG.json +197 -0
- package/templates/zh/CONFIG.md +66 -0
- package/templates/zh/INSTRUCTION.md +177 -0
- package/templates/zh/README.md +27 -0
- package/templates/zh/TODO.md +13 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217/INSTRUCTION.md" +28 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217/README.md" +20 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//342/232/231/357/270/217 Preferences.md" +21 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/216/257 Focus.md" +31 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/221/244 Identity.md" +22 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220/INSTRUCTION.md" +29 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220/README.md" +21 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI Inferencers.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /344/272/247/345/223/201.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/255/246/350/200/205/346/270/205/345/215/225.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/267/245/345/205/267/346/270/205/345/215/225.csv" +1 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260/INSTRUCTION.md" +31 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260/README.md" +24 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256//360/237/247/252_example_/344/274/232/350/256/256/347/272/252/350/246/201.md" +17 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210//360/237/247/252_example_/345/276/205/345/217/215/351/246/210/344/272/213/351/241/271.md" +16 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225//360/237/247/252_example_/344/272/247/345/223/201/346/203/263/346/263/225.md" +16 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261//360/237/247/252_example_/344/270/264/346/227/266/351/200/237/350/256/260.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213/INSTRUCTION.md" +29 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213/README.md" +21 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257//360/237/247/252_example_/344/277/241/346/201/257/351/207/207/351/233/206/346/265/201/347/250/213.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223//360/237/247/252_example_/345/206/205/345/256/271/345/217/221/345/270/203/346/265/201/347/250/213.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224//360/237/247/252_example_/346/226/207/347/214/256/347/273/274/350/277/260/346/265/201/347/250/213.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256/README.md" +3 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256//360/237/247/252_example_/351/205/215/347/275/256/346/233/264/346/226/260/346/265/201/347/250/213.md" +26 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273/INSTRUCTION.md" +62 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273/README.md" +20 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/205/263/347/263/273/346/200/273/350/247/210.csv" +5 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213//360/237/247/252_example_/345/220/214/344/272/213/350/265/265/344/270/200/350/276/260.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246//360/237/247/252_example_/345/220/214/345/255/246/351/231/210/347/253/213/346/254/247.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272//360/237/247/252_example_/345/256/266/344/272/272/347/216/213/345/273/272/345/233/275.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213//360/237/247/252_example_/346/234/213/345/217/213/346/236/227/345/260/217/344/270/275.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256/INSTRUCTION.md" +31 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256/README.md" +21 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201/README.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201//360/237/247/252_example_/344/272/247/345/223/201/351/241/271/347/233/256/347/256/200/346/212/245.md" +20 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243/README.md" +9 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243//360/237/247/252_example_/345/275/222/346/241/243/351/241/271/347/233/256/350/256/260/345/275/225.md" +15 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224/README.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224//360/237/247/252_example_/347/247/221/347/240/224/351/241/271/347/233/256/347/256/200/346/212/245.md" +16 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { collectAllFiles, getFileContent } from '@/lib/fs';
|
|
5
|
+
|
|
6
|
+
export interface GraphNode {
|
|
7
|
+
id: string; // relative file path
|
|
8
|
+
label: string; // basename without extension
|
|
9
|
+
folder: string; // dirname
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GraphEdge {
|
|
13
|
+
source: string;
|
|
14
|
+
target: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GraphData {
|
|
18
|
+
nodes: GraphNode[];
|
|
19
|
+
edges: GraphEdge[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function extractLinks(content: string, sourcePath: string, fileSet: Set<string>, basenameMap: Map<string, string[]>): string[] {
|
|
23
|
+
const targets: string[] = [];
|
|
24
|
+
const sourceDir = path.dirname(sourcePath);
|
|
25
|
+
|
|
26
|
+
// WikiLinks: [[target]] or [[target|alias]] or [[target#section]]
|
|
27
|
+
const wikiRe = /\[\[([^\]|#]+)(?:[|#][^\]]*)?/g;
|
|
28
|
+
let m: RegExpExecArray | null;
|
|
29
|
+
while ((m = wikiRe.exec(content)) !== null) {
|
|
30
|
+
const raw = m[1].trim();
|
|
31
|
+
if (!raw) continue;
|
|
32
|
+
|
|
33
|
+
// Try exact match
|
|
34
|
+
if (fileSet.has(raw)) {
|
|
35
|
+
targets.push(raw);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// Try with .md
|
|
39
|
+
const withMd = raw.endsWith('.md') ? raw : raw + '.md';
|
|
40
|
+
if (fileSet.has(withMd)) {
|
|
41
|
+
targets.push(withMd);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Try basename lookup (case-insensitive)
|
|
45
|
+
const lower = path.basename(withMd).toLowerCase();
|
|
46
|
+
const candidates = basenameMap.get(lower);
|
|
47
|
+
if (candidates && candidates.length === 1) {
|
|
48
|
+
targets.push(candidates[0]);
|
|
49
|
+
}
|
|
50
|
+
// skip if ambiguous (multiple candidates)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Markdown links: [text](relative/path.md) or [text](relative/path.md#section)
|
|
54
|
+
const mdLinkRe = /\[[^\]]+\]\(([^)]+\.md)(?:#[^)]*)?\)/g;
|
|
55
|
+
while ((m = mdLinkRe.exec(content)) !== null) {
|
|
56
|
+
const raw = m[1].trim();
|
|
57
|
+
if (!raw || raw.startsWith('http')) continue;
|
|
58
|
+
const resolved = path.normalize(path.join(sourceDir, raw));
|
|
59
|
+
if (fileSet.has(resolved)) {
|
|
60
|
+
targets.push(resolved);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return targets;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function GET() {
|
|
68
|
+
try {
|
|
69
|
+
const allFiles = collectAllFiles().filter(f => f.endsWith('.md'));
|
|
70
|
+
const fileSet = new Set(allFiles);
|
|
71
|
+
|
|
72
|
+
// Build basename → relPath[] lookup
|
|
73
|
+
const basenameMap = new Map<string, string[]>();
|
|
74
|
+
for (const f of allFiles) {
|
|
75
|
+
const key = path.basename(f).toLowerCase();
|
|
76
|
+
if (!basenameMap.has(key)) basenameMap.set(key, []);
|
|
77
|
+
basenameMap.get(key)!.push(f);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const nodes: GraphNode[] = allFiles.map(f => ({
|
|
81
|
+
id: f,
|
|
82
|
+
label: path.basename(f, '.md'),
|
|
83
|
+
folder: path.dirname(f),
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
const edgeSet = new Set<string>();
|
|
87
|
+
const edges: GraphEdge[] = [];
|
|
88
|
+
|
|
89
|
+
for (const filePath of allFiles) {
|
|
90
|
+
let content: string;
|
|
91
|
+
try {
|
|
92
|
+
content = getFileContent(filePath);
|
|
93
|
+
} catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const targets = extractLinks(content, filePath, fileSet, basenameMap);
|
|
98
|
+
for (const target of targets) {
|
|
99
|
+
if (target === filePath) continue; // skip self-edges
|
|
100
|
+
const key = `${filePath}||${target}`;
|
|
101
|
+
if (!edgeSet.has(key)) {
|
|
102
|
+
edgeSet.add(key);
|
|
103
|
+
edges.push({ source: filePath, target });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return NextResponse.json({ nodes, edges } satisfies GraphData);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.error('[graph] Error building graph:', err);
|
|
111
|
+
return NextResponse.json({ error: 'Failed to build graph' }, { status: 500 });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { getRecentlyModified } from '@/lib/fs';
|
|
4
|
+
|
|
5
|
+
export async function GET(req: NextRequest) {
|
|
6
|
+
const limitParam = req.nextUrl.searchParams.get('limit');
|
|
7
|
+
const limit = limitParam ? parseInt(limitParam, 10) : 10;
|
|
8
|
+
const files = getRecentlyModified(Math.min(limit, 30));
|
|
9
|
+
return NextResponse.json(files);
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { searchFiles } from '@/lib/fs';
|
|
4
|
+
|
|
5
|
+
export async function GET(request: NextRequest) {
|
|
6
|
+
const q = request.nextUrl.searchParams.get('q') || '';
|
|
7
|
+
if (!q.trim()) {
|
|
8
|
+
return NextResponse.json([]);
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const results = searchFiles(q);
|
|
12
|
+
return NextResponse.json(results);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.error('Search error:', err);
|
|
15
|
+
return NextResponse.json({ error: 'Search failed' }, { status: 500 });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import { readSettings, writeSettings } from '@/lib/settings';
|
|
4
|
+
import { randomBytes } from 'crypto';
|
|
5
|
+
|
|
6
|
+
function generateToken(): string {
|
|
7
|
+
const hex = randomBytes(12).toString('hex'); // 24 hex chars
|
|
8
|
+
return (hex.match(/.{4}/g) as string[]).join('-');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// POST /api/settings/reset-token — generate a new auth token and persist it
|
|
12
|
+
export async function POST() {
|
|
13
|
+
try {
|
|
14
|
+
const current = readSettings();
|
|
15
|
+
const newToken = generateToken();
|
|
16
|
+
writeSettings({ ...current, authToken: newToken });
|
|
17
|
+
return NextResponse.json({ ok: true, token: newToken });
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return NextResponse.json({ error: String(err) }, { status: 500 });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { readSettings, writeSettings, ServerSettings } from '@/lib/settings';
|
|
4
|
+
import { invalidateCache } from '@/lib/fs';
|
|
5
|
+
|
|
6
|
+
function maskToken(token: string | undefined): string {
|
|
7
|
+
if (!token) return '';
|
|
8
|
+
// Show first 4 and last 4 chars: xxxx-••••-••••-••••-••••-xxxx
|
|
9
|
+
const parts = token.split('-');
|
|
10
|
+
if (parts.length >= 2) {
|
|
11
|
+
return parts[0] + '-' + parts.slice(1, -1).map(() => '••••').join('-') + '-' + parts[parts.length - 1];
|
|
12
|
+
}
|
|
13
|
+
return token.length > 8 ? token.slice(0, 4) + '••••••••' + token.slice(-4) : '***set***';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function GET() {
|
|
17
|
+
const settings = readSettings();
|
|
18
|
+
|
|
19
|
+
const envValues = {
|
|
20
|
+
AI_PROVIDER: process.env.AI_PROVIDER || '',
|
|
21
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ? '***set***' : '',
|
|
22
|
+
ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL || '',
|
|
23
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY ? '***set***' : '',
|
|
24
|
+
OPENAI_MODEL: process.env.OPENAI_MODEL || '',
|
|
25
|
+
OPENAI_BASE_URL: process.env.OPENAI_BASE_URL || '',
|
|
26
|
+
MIND_ROOT: process.env.MIND_ROOT || '',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Mask API keys
|
|
30
|
+
const anthropic = settings.ai.providers.anthropic;
|
|
31
|
+
const openai = settings.ai.providers.openai;
|
|
32
|
+
|
|
33
|
+
const masked = {
|
|
34
|
+
ai: {
|
|
35
|
+
provider: settings.ai.provider,
|
|
36
|
+
providers: {
|
|
37
|
+
anthropic: {
|
|
38
|
+
apiKey: anthropic?.apiKey ? '***set***' : '',
|
|
39
|
+
model: anthropic?.model ?? '',
|
|
40
|
+
},
|
|
41
|
+
openai: {
|
|
42
|
+
apiKey: openai?.apiKey ? '***set***' : '',
|
|
43
|
+
model: openai?.model ?? '',
|
|
44
|
+
baseUrl: openai?.baseUrl ?? '',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
mindRoot: settings.mindRoot,
|
|
49
|
+
webPassword: settings.webPassword ? '***set***' : '',
|
|
50
|
+
authToken: maskToken(settings.authToken),
|
|
51
|
+
mcpPort: settings.mcpPort ?? 8787,
|
|
52
|
+
envOverrides: {
|
|
53
|
+
AI_PROVIDER: !!process.env.AI_PROVIDER,
|
|
54
|
+
ANTHROPIC_API_KEY: !!process.env.ANTHROPIC_API_KEY,
|
|
55
|
+
ANTHROPIC_MODEL: !!process.env.ANTHROPIC_MODEL,
|
|
56
|
+
OPENAI_API_KEY: !!process.env.OPENAI_API_KEY,
|
|
57
|
+
OPENAI_MODEL: !!process.env.OPENAI_MODEL,
|
|
58
|
+
OPENAI_BASE_URL: !!process.env.OPENAI_BASE_URL,
|
|
59
|
+
MIND_ROOT: !!process.env.MIND_ROOT,
|
|
60
|
+
},
|
|
61
|
+
envValues,
|
|
62
|
+
};
|
|
63
|
+
return NextResponse.json(masked);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function POST(req: NextRequest) {
|
|
67
|
+
try {
|
|
68
|
+
const body = await req.json() as Partial<ServerSettings>;
|
|
69
|
+
const current = readSettings();
|
|
70
|
+
|
|
71
|
+
// Merge providers, preserving masked keys
|
|
72
|
+
const incomingAnthropic = body.ai?.providers?.anthropic;
|
|
73
|
+
const incomingOpenai = body.ai?.providers?.openai;
|
|
74
|
+
const curAnthropic = current.ai.providers.anthropic;
|
|
75
|
+
const curOpenai = current.ai.providers.openai;
|
|
76
|
+
|
|
77
|
+
// Resolve webPassword: '***set***' means keep existing, '' means clear, anything else = new value
|
|
78
|
+
const incomingWebPassword = body.webPassword;
|
|
79
|
+
const resolvedWebPassword = incomingWebPassword === '***set***'
|
|
80
|
+
? current.webPassword
|
|
81
|
+
: (incomingWebPassword ?? current.webPassword);
|
|
82
|
+
|
|
83
|
+
// authToken is read-only via POST (use /api/settings/reset-token to regenerate)
|
|
84
|
+
// but allow clearing it by passing empty string
|
|
85
|
+
const incomingAuthToken = body.authToken;
|
|
86
|
+
const resolvedAuthToken = (incomingAuthToken === '' || incomingAuthToken === undefined)
|
|
87
|
+
? (incomingAuthToken === '' ? '' : current.authToken)
|
|
88
|
+
: current.authToken;
|
|
89
|
+
|
|
90
|
+
const next: ServerSettings = {
|
|
91
|
+
ai: {
|
|
92
|
+
provider: body.ai?.provider ?? current.ai.provider,
|
|
93
|
+
providers: {
|
|
94
|
+
anthropic: {
|
|
95
|
+
...curAnthropic,
|
|
96
|
+
...(incomingAnthropic ?? {}),
|
|
97
|
+
apiKey: incomingAnthropic?.apiKey === '***set***'
|
|
98
|
+
? (curAnthropic.apiKey ?? '')
|
|
99
|
+
: (incomingAnthropic?.apiKey ?? curAnthropic.apiKey ?? ''),
|
|
100
|
+
model: incomingAnthropic?.model ?? curAnthropic.model ?? 'claude-sonnet-4-6',
|
|
101
|
+
},
|
|
102
|
+
openai: {
|
|
103
|
+
...curOpenai,
|
|
104
|
+
...(incomingOpenai ?? {}),
|
|
105
|
+
apiKey: incomingOpenai?.apiKey === '***set***'
|
|
106
|
+
? (curOpenai.apiKey ?? '')
|
|
107
|
+
: (incomingOpenai?.apiKey ?? curOpenai.apiKey ?? ''),
|
|
108
|
+
model: incomingOpenai?.model ?? curOpenai.model ?? 'gpt-5.4',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
mindRoot: body.mindRoot ?? current.mindRoot,
|
|
113
|
+
webPassword: resolvedWebPassword,
|
|
114
|
+
authToken: resolvedAuthToken,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
writeSettings(next);
|
|
118
|
+
if (next.mindRoot !== current.mindRoot) invalidateCache();
|
|
119
|
+
return NextResponse.json({ ok: true });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return NextResponse.json({ error: String(err) }, { status: 500 });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
export default function Error({
|
|
6
|
+
error,
|
|
7
|
+
reset,
|
|
8
|
+
}: {
|
|
9
|
+
error: Error & { digest?: string };
|
|
10
|
+
reset: () => void;
|
|
11
|
+
}) {
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
console.error('Page error:', error);
|
|
14
|
+
}, [error]);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="flex flex-col items-center justify-center min-h-[50vh] gap-4 px-6">
|
|
18
|
+
<div className="text-center">
|
|
19
|
+
<h2 className="text-lg font-semibold text-foreground mb-2">Something went wrong</h2>
|
|
20
|
+
<p className="text-sm text-muted-foreground max-w-md">
|
|
21
|
+
{error.message || 'An unexpected error occurred while rendering this page.'}
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
<button
|
|
25
|
+
onClick={reset}
|
|
26
|
+
className="px-4 py-2 rounded-md text-sm font-medium transition-colors"
|
|
27
|
+
style={{ background: 'var(--muted)', color: 'var(--foreground)' }}
|
|
28
|
+
>
|
|
29
|
+
Try again
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "shadcn/tailwind.css";
|
|
4
|
+
|
|
5
|
+
@custom-variant dark (&:is(.dark *));
|
|
6
|
+
|
|
7
|
+
@theme inline {
|
|
8
|
+
--color-background: var(--background);
|
|
9
|
+
--color-foreground: var(--foreground);
|
|
10
|
+
--font-sans: var(--font-sans);
|
|
11
|
+
--font-mono: var(--font-geist-mono);
|
|
12
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
13
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
14
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
15
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
16
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
17
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
18
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
19
|
+
--color-sidebar: var(--sidebar);
|
|
20
|
+
--color-chart-5: var(--chart-5);
|
|
21
|
+
--color-chart-4: var(--chart-4);
|
|
22
|
+
--color-chart-3: var(--chart-3);
|
|
23
|
+
--color-chart-2: var(--chart-2);
|
|
24
|
+
--color-chart-1: var(--chart-1);
|
|
25
|
+
--color-ring: var(--ring);
|
|
26
|
+
--color-input: var(--input);
|
|
27
|
+
--color-border: var(--border);
|
|
28
|
+
--color-destructive: var(--destructive);
|
|
29
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
30
|
+
--color-accent: var(--accent);
|
|
31
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
32
|
+
--color-muted: var(--muted);
|
|
33
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
34
|
+
--color-secondary: var(--secondary);
|
|
35
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
36
|
+
--color-primary: var(--primary);
|
|
37
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
38
|
+
--color-popover: var(--popover);
|
|
39
|
+
--color-card-foreground: var(--card-foreground);
|
|
40
|
+
--color-card: var(--card);
|
|
41
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
42
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
43
|
+
--radius-lg: var(--radius);
|
|
44
|
+
--radius-xl: calc(var(--radius) * 1.4);
|
|
45
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
46
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
47
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
body {
|
|
51
|
+
font-family: 'IBM Plex Sans', var(--font-geist-sans), system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:root {
|
|
55
|
+
--background: #f8f6f1;
|
|
56
|
+
--foreground: #1c1a17;
|
|
57
|
+
--card: #f2efe9;
|
|
58
|
+
--card-foreground: #1c1a17;
|
|
59
|
+
--popover: #f2efe9;
|
|
60
|
+
--popover-foreground: #1c1a17;
|
|
61
|
+
--primary: #1c1a17;
|
|
62
|
+
--primary-foreground: #f8f6f1;
|
|
63
|
+
--secondary: #e8e4db;
|
|
64
|
+
--secondary-foreground: #1c1a17;
|
|
65
|
+
--muted: #e8e4db;
|
|
66
|
+
--muted-foreground: #7a7568;
|
|
67
|
+
--accent: #d9d3c6;
|
|
68
|
+
--accent-foreground: #1c1a17;
|
|
69
|
+
--destructive: oklch(0.58 0.22 27);
|
|
70
|
+
--border: rgba(28, 26, 23, 0.1);
|
|
71
|
+
--input: rgba(28, 26, 23, 0.12);
|
|
72
|
+
--ring: rgba(28, 26, 23, 0.3);
|
|
73
|
+
--radius: 0.5rem;
|
|
74
|
+
--amber: #c8873a;
|
|
75
|
+
--amber-dim: rgba(200, 135, 58, 0.12);
|
|
76
|
+
--sidebar: #ede9e1;
|
|
77
|
+
--sidebar-foreground: #1c1a17;
|
|
78
|
+
--sidebar-primary: #1c1a17;
|
|
79
|
+
--sidebar-primary-foreground: #f8f6f1;
|
|
80
|
+
--sidebar-accent: #d9d3c6;
|
|
81
|
+
--sidebar-accent-foreground: #1c1a17;
|
|
82
|
+
--sidebar-border: rgba(28, 26, 23, 0.08);
|
|
83
|
+
--sidebar-ring: rgba(28, 26, 23, 0.3);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.dark {
|
|
87
|
+
--background: #131210;
|
|
88
|
+
--foreground: #e8e4dc;
|
|
89
|
+
--card: #1c1a17;
|
|
90
|
+
--card-foreground: #e8e4dc;
|
|
91
|
+
--popover: #1c1a17;
|
|
92
|
+
--popover-foreground: #e8e4dc;
|
|
93
|
+
--primary: #e8e4dc;
|
|
94
|
+
--primary-foreground: #131210;
|
|
95
|
+
--secondary: #252219;
|
|
96
|
+
--secondary-foreground: #e8e4dc;
|
|
97
|
+
--muted: #252219;
|
|
98
|
+
--muted-foreground: #8a8275;
|
|
99
|
+
--accent: #2e2b22;
|
|
100
|
+
--accent-foreground: #e8e4dc;
|
|
101
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
102
|
+
--border: rgba(232, 228, 220, 0.08);
|
|
103
|
+
--input: rgba(232, 228, 220, 0.1);
|
|
104
|
+
--ring: rgba(232, 228, 220, 0.2);
|
|
105
|
+
--amber: #d4954a;
|
|
106
|
+
--amber-dim: rgba(212, 149, 74, 0.12);
|
|
107
|
+
--sidebar: #1c1a17;
|
|
108
|
+
--sidebar-foreground: #e8e4dc;
|
|
109
|
+
--sidebar-primary: #d4954a;
|
|
110
|
+
--sidebar-primary-foreground: #131210;
|
|
111
|
+
--sidebar-accent: #252219;
|
|
112
|
+
--sidebar-accent-foreground: #e8e4dc;
|
|
113
|
+
--sidebar-border: rgba(232, 228, 220, 0.07);
|
|
114
|
+
--sidebar-ring: rgba(232, 228, 220, 0.2);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@layer base {
|
|
118
|
+
* { @apply border-border outline-ring/50; }
|
|
119
|
+
body { @apply bg-background text-foreground; }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Content width — controlled by Settings > Appearance */
|
|
123
|
+
:root { --content-width: 780px; }
|
|
124
|
+
.content-width { max-width: var(--content-width-override, var(--content-width)); margin-left: auto; margin-right: auto; }
|
|
125
|
+
|
|
126
|
+
::-webkit-scrollbar { width: 5px; height: 5px; }
|
|
127
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
128
|
+
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 99px; }
|
|
129
|
+
|
|
130
|
+
.sidebar-panel {
|
|
131
|
+
}
|
|
132
|
+
.sidebar-panel::before {
|
|
133
|
+
content: '';
|
|
134
|
+
position: absolute;
|
|
135
|
+
left: 0;
|
|
136
|
+
top: 15%;
|
|
137
|
+
bottom: 15%;
|
|
138
|
+
width: 1.5px;
|
|
139
|
+
background: linear-gradient(to bottom, transparent, var(--amber), transparent);
|
|
140
|
+
opacity: 0.45;
|
|
141
|
+
border-radius: 99px;
|
|
142
|
+
pointer-events: none;
|
|
143
|
+
z-index: 1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.prose {
|
|
147
|
+
font-family: var(--prose-font-override, 'Lora', Georgia, serif);
|
|
148
|
+
color: var(--prose-body);
|
|
149
|
+
line-height: 1.85;
|
|
150
|
+
font-size: 0.95rem;
|
|
151
|
+
}
|
|
152
|
+
@media (min-width: 640px) {
|
|
153
|
+
.prose { font-size: 1rem; }
|
|
154
|
+
}
|
|
155
|
+
.prose h1, .prose h2, .prose h3, .prose h4 {
|
|
156
|
+
font-family: 'IBM Plex Sans', sans-serif;
|
|
157
|
+
color: var(--prose-heading);
|
|
158
|
+
font-weight: 600;
|
|
159
|
+
line-height: 1.3;
|
|
160
|
+
margin-top: 1.5em;
|
|
161
|
+
margin-bottom: 0.6em;
|
|
162
|
+
letter-spacing: -0.01em;
|
|
163
|
+
}
|
|
164
|
+
@media (min-width: 640px) {
|
|
165
|
+
.prose h1, .prose h2, .prose h3, .prose h4 {
|
|
166
|
+
margin-top: 2em;
|
|
167
|
+
margin-bottom: 0.75em;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
.prose h1 { font-size: 1.85rem; margin-top: 0; }
|
|
171
|
+
.prose h2 { font-size: 1.35rem; border-bottom: 1px solid var(--prose-border); padding-bottom: 0.4em; }
|
|
172
|
+
.prose h3 { font-size: 1.15rem; }
|
|
173
|
+
.prose h4 { font-size: 1.05rem; }
|
|
174
|
+
.prose p { margin-top: 0; margin-bottom: 1.1em; }
|
|
175
|
+
.prose a { color: var(--amber); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(200,135,58,0.35); transition: text-decoration-color 0.15s; }
|
|
176
|
+
.prose a:hover { text-decoration-color: var(--amber); }
|
|
177
|
+
.prose strong { color: var(--prose-heading); font-weight: 600; }
|
|
178
|
+
.prose em { font-style: italic; color: var(--prose-muted); }
|
|
179
|
+
.prose ul, .prose ol { margin: 0.5em 0 1.1em 0; padding-left: 1.6em; }
|
|
180
|
+
.prose ul { list-style-type: disc; }
|
|
181
|
+
.prose ol { list-style-type: decimal; }
|
|
182
|
+
.prose li { margin-bottom: 0.3em; }
|
|
183
|
+
.prose blockquote { border-left: 2px solid var(--amber); padding-left: 1.1em; margin: 1.2em 0; color: var(--prose-subtle); font-style: italic; }
|
|
184
|
+
.prose code:not(pre code) { background: var(--prose-code-border); color: var(--prose-heading); padding: 0.15em 0.45em; border-radius: 4px; font-size: 0.855em; font-family: 'IBM Plex Mono', monospace; }
|
|
185
|
+
.prose pre { background: var(--prose-pre-bg); border: 1px solid var(--prose-code-border); border-radius: 8px; padding: 0.9em 1em; overflow-x: auto; margin: 1.2em 0; color: var(--prose-pre-color); max-width: 100%; }
|
|
186
|
+
@media (min-width: 640px) {
|
|
187
|
+
.prose pre { padding: 1.1em 1.3em; }
|
|
188
|
+
}
|
|
189
|
+
.prose pre code { background: transparent; color: inherit; padding: 0; font-size: 0.82em; font-family: 'IBM Plex Mono', monospace; word-break: break-all; }
|
|
190
|
+
@media (min-width: 640px) {
|
|
191
|
+
.prose pre code { font-size: 0.855em; word-break: normal; }
|
|
192
|
+
}
|
|
193
|
+
.prose table { width: 100%; border-collapse: collapse; margin: 1.2em 0; font-size: 0.85em; font-family: 'IBM Plex Sans', sans-serif; display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
194
|
+
@media (min-width: 640px) {
|
|
195
|
+
.prose table { font-size: 0.9em; display: table; overflow-x: visible; }
|
|
196
|
+
}
|
|
197
|
+
.prose thead th { background: var(--prose-th-bg); color: var(--prose-heading); font-weight: 600; padding: 0.6em 0.9em; border: 1px solid var(--prose-border); text-align: left; font-size: 0.78em; letter-spacing: 0.05em; text-transform: uppercase; }
|
|
198
|
+
.prose tbody td { padding: 0.55em 0.9em; border: 1px solid var(--prose-code-border); color: var(--prose-muted); }
|
|
199
|
+
.prose tbody tr:nth-child(even) { background: var(--prose-th-bg); }
|
|
200
|
+
.prose img { max-width: 100%; border-radius: 6px; border: 1px solid var(--prose-code-border); }
|
|
201
|
+
.prose hr { border: none; border-top: 1px solid var(--prose-border); margin: 2.5em 0; }
|
|
202
|
+
|
|
203
|
+
:root {
|
|
204
|
+
--prose-body: #3a3730;
|
|
205
|
+
--prose-heading: #1c1a17;
|
|
206
|
+
--prose-muted: #5a5750;
|
|
207
|
+
--prose-subtle: #7a7568;
|
|
208
|
+
--prose-border: #ddd9d0;
|
|
209
|
+
--prose-code-border: rgba(28,26,23,0.1);
|
|
210
|
+
--prose-th-bg: rgba(28,26,23,0.03);
|
|
211
|
+
--prose-pre-bg: #eae6de;
|
|
212
|
+
--prose-pre-color: #2a2724;
|
|
213
|
+
--hljs-comment: #8a8275;
|
|
214
|
+
--hljs-keyword: #9b4a1a;
|
|
215
|
+
--hljs-string: #4a7a46;
|
|
216
|
+
--hljs-variable: #7a6830;
|
|
217
|
+
--hljs-number: #2a5a8a;
|
|
218
|
+
--hljs-title: #6a3a8a;
|
|
219
|
+
--hljs-builtin: #2a6a40;
|
|
220
|
+
--hljs-deletion: #8a3030;
|
|
221
|
+
}
|
|
222
|
+
.dark {
|
|
223
|
+
--prose-body: #c8c2b8;
|
|
224
|
+
--prose-heading: #e8e4dc;
|
|
225
|
+
--prose-muted: #9a9488;
|
|
226
|
+
--prose-subtle: #7a7568;
|
|
227
|
+
--prose-border: rgba(232, 228, 220, 0.1);
|
|
228
|
+
--prose-code-border: rgba(232, 228, 220, 0.08);
|
|
229
|
+
--prose-th-bg: rgba(232, 228, 220, 0.03);
|
|
230
|
+
--prose-pre-bg: #0a0906;
|
|
231
|
+
--prose-pre-color: #c8c2b8;
|
|
232
|
+
--hljs-comment: #6a6560;
|
|
233
|
+
--hljs-keyword: #d4954a;
|
|
234
|
+
--hljs-string: #a5c4a0;
|
|
235
|
+
--hljs-variable: #d4c08a;
|
|
236
|
+
--hljs-number: #8ab4d8;
|
|
237
|
+
--hljs-title: #c8a0d8;
|
|
238
|
+
--hljs-builtin: #7aad80;
|
|
239
|
+
--hljs-deletion: #c86060;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.hljs { color: var(--prose-pre-color); background: var(--prose-pre-bg); }
|
|
243
|
+
.hljs-comment, .hljs-meta { color: var(--hljs-comment); font-style: italic; }
|
|
244
|
+
.hljs-keyword, .hljs-selector-tag, .hljs-tag { color: var(--hljs-keyword); }
|
|
245
|
+
.hljs-string, .hljs-attr { color: var(--hljs-string); }
|
|
246
|
+
.hljs-template-variable, .hljs-variable { color: var(--hljs-variable); }
|
|
247
|
+
.hljs-number, .hljs-literal, .hljs-type { color: var(--hljs-number); }
|
|
248
|
+
.hljs-title, .hljs-name, .hljs-selector-id, .hljs-selector-class { color: var(--hljs-title); }
|
|
249
|
+
.hljs-symbol, .hljs-bullet, .hljs-link { color: var(--hljs-keyword); }
|
|
250
|
+
.hljs-built_in, .hljs-addition { color: var(--hljs-builtin); }
|
|
251
|
+
.hljs-deletion { color: var(--hljs-deletion); }
|
|
252
|
+
|
|
253
|
+
@keyframes fadeSlideUp {
|
|
254
|
+
from { opacity: 0; transform: translateY(5px); }
|
|
255
|
+
to { opacity: 1; transform: translateY(0); }
|
|
256
|
+
}
|
|
257
|
+
.animate-in { animation: fadeSlideUp 0.22s ease both; }
|
|
258
|
+
|
|
259
|
+
/* Mobile bottom-sheet modal entrance */
|
|
260
|
+
@keyframes slideUp {
|
|
261
|
+
from { transform: translateY(100%); }
|
|
262
|
+
to { transform: translateY(0); }
|
|
263
|
+
}
|
|
264
|
+
@media (max-width: 767px) {
|
|
265
|
+
.modal-backdrop > [role="dialog"] {
|
|
266
|
+
animation: slideUp 0.3s ease-out both;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.modal-backdrop {
|
|
271
|
+
background: rgba(10, 9, 6, 0.72);
|
|
272
|
+
backdrop-filter: blur(8px);
|
|
273
|
+
-webkit-backdrop-filter: blur(8px);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Hide scrollbar but keep scroll functionality */
|
|
277
|
+
.scrollbar-none { -ms-overflow-style: none; scrollbar-width: none; }
|
|
278
|
+
.scrollbar-none::-webkit-scrollbar { display: none; }
|
|
279
|
+
|
|
280
|
+
/* iOS safe areas */
|
|
281
|
+
@supports (padding: env(safe-area-inset-bottom)) {
|
|
282
|
+
.safe-area-bottom { padding-bottom: env(safe-area-inset-bottom); }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Tap highlight removal for mobile */
|
|
286
|
+
@media (hover: none) {
|
|
287
|
+
button, a { -webkit-tap-highlight-color: transparent; }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* ─── Tiptap WYSIWYG editor ─────────────────────────────────────────── */
|
|
291
|
+
.wysiwyg-wrapper {
|
|
292
|
+
height: 100%;
|
|
293
|
+
cursor: text;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Tiptap uses ProseMirror internally */
|
|
297
|
+
.wysiwyg-editor {
|
|
298
|
+
min-height: 100%;
|
|
299
|
+
caret-color: var(--amber);
|
|
300
|
+
color: var(--foreground);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Placeholder */
|
|
304
|
+
.wysiwyg-editor p.is-editor-empty:first-child::before {
|
|
305
|
+
content: attr(data-placeholder);
|
|
306
|
+
color: var(--muted-foreground);
|
|
307
|
+
opacity: 0.45;
|
|
308
|
+
float: left;
|
|
309
|
+
pointer-events: none;
|
|
310
|
+
height: 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* Selection */
|
|
314
|
+
.wysiwyg-editor ::selection {
|
|
315
|
+
background: var(--amber);
|
|
316
|
+
color: #131210;
|
|
317
|
+
opacity: 0.35;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* Task list */
|
|
321
|
+
.wysiwyg-editor ul[data-type="taskList"] {
|
|
322
|
+
list-style: none;
|
|
323
|
+
padding: 0;
|
|
324
|
+
}
|
|
325
|
+
.wysiwyg-editor ul[data-type="taskList"] li {
|
|
326
|
+
display: flex;
|
|
327
|
+
align-items: baseline;
|
|
328
|
+
gap: 0.5rem;
|
|
329
|
+
}
|
|
330
|
+
.wysiwyg-editor ul[data-type="taskList"] li > label {
|
|
331
|
+
flex-shrink: 0;
|
|
332
|
+
margin-top: 2px;
|
|
333
|
+
}
|
|
334
|
+
.wysiwyg-editor ul[data-type="taskList"] li > label input[type="checkbox"] {
|
|
335
|
+
accent-color: var(--amber);
|
|
336
|
+
width: 14px;
|
|
337
|
+
height: 14px;
|
|
338
|
+
cursor: pointer;
|
|
339
|
+
}
|
|
340
|
+
.wysiwyg-editor ul[data-type="taskList"] li[data-checked="true"] > div {
|
|
341
|
+
text-decoration: line-through;
|
|
342
|
+
opacity: 0.5;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Tables */
|
|
346
|
+
.wysiwyg-editor table {
|
|
347
|
+
border-collapse: collapse;
|
|
348
|
+
width: 100%;
|
|
349
|
+
margin: 1.5rem 0;
|
|
350
|
+
}
|
|
351
|
+
.wysiwyg-editor th,
|
|
352
|
+
.wysiwyg-editor td {
|
|
353
|
+
border: 1px solid var(--border);
|
|
354
|
+
padding: 0.5rem 0.75rem;
|
|
355
|
+
min-width: 60px;
|
|
356
|
+
}
|
|
357
|
+
.wysiwyg-editor th {
|
|
358
|
+
background: var(--muted);
|
|
359
|
+
font-weight: 600;
|
|
360
|
+
}
|
|
361
|
+
.wysiwyg-editor .selectedCell {
|
|
362
|
+
background: color-mix(in srgb, var(--amber) 12%, transparent);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* Focus ring on the editor area */
|
|
366
|
+
.wysiwyg-wrapper:focus-within {
|
|
367
|
+
outline: none;
|
|
368
|
+
}
|