├── .DS_Store ├── .github └── workflows │ └── package.yml ├── .gitignore ├── .i18nrc.cjs ├── .npmrc ├── .prettierignore ├── .prettierrc.yaml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── build ├── entitlements.mac.plist ├── icon.icns └── icon.png ├── dev-app-update.yml ├── docs ├── d1.png ├── d2.png ├── v1.png └── v2.png ├── electron-builder.yml ├── electron.vite.config.ts ├── package.json ├── pnpm-lock.yaml ├── resources └── icon.png ├── scripts ├── afterPack.js ├── model.js ├── notarize.js └── upload.js ├── src ├── .DS_Store ├── main │ ├── database │ │ ├── api.ts │ │ └── model.ts │ ├── handle.ts │ ├── index.ts │ ├── update.ts │ ├── utils.ts │ └── window.ts ├── preload │ ├── api.ts │ ├── index.d.ts │ └── index.ts ├── renderer │ ├── index.html │ ├── src │ │ ├── App.tsx │ │ ├── assets │ │ │ └── logo.svg │ │ ├── editor │ │ │ ├── Editor.tsx │ │ │ ├── EditorFrame.tsx │ │ │ ├── Note.tsx │ │ │ ├── elements │ │ │ │ ├── CodeUI │ │ │ │ │ ├── Katex │ │ │ │ │ │ ├── InlineKatex.tsx │ │ │ │ │ │ └── Katex.tsx │ │ │ │ │ └── Mermaid.tsx │ │ │ │ ├── ace.tsx │ │ │ │ ├── acemodes.ts │ │ │ │ ├── attachment.tsx │ │ │ │ ├── blockquote.tsx │ │ │ │ ├── head.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── list.tsx │ │ │ │ ├── media.tsx │ │ │ │ ├── paragraph.tsx │ │ │ │ ├── table.tsx │ │ │ │ └── wikilink.tsx │ │ │ ├── icons │ │ │ │ ├── IMermaid.tsx │ │ │ │ └── IPlanet.tsx │ │ │ ├── index.d.ts │ │ │ ├── parser │ │ │ │ ├── container │ │ │ │ │ ├── container.js │ │ │ │ │ ├── factory-attributes.js │ │ │ │ │ ├── factory-label.js │ │ │ │ │ └── factory-name.js │ │ │ │ └── worker │ │ │ │ │ ├── bundle.d.ts │ │ │ │ │ ├── bundle.js │ │ │ │ │ ├── compiler.js │ │ │ │ │ ├── directive.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── parser.js │ │ │ ├── plugins │ │ │ │ ├── catchError.ts │ │ │ │ ├── elHeight.ts │ │ │ │ ├── elements.ts │ │ │ │ ├── hotKeyCommands │ │ │ │ │ ├── arrow.ts │ │ │ │ │ ├── backspace.ts │ │ │ │ │ ├── enter.ts │ │ │ │ │ ├── match.ts │ │ │ │ │ └── tab.ts │ │ │ │ ├── index.ts │ │ │ │ ├── useHighlight.ts │ │ │ │ ├── useKeyboard.ts │ │ │ │ └── useOnchange.ts │ │ │ ├── tools │ │ │ │ ├── DragHandle.tsx │ │ │ │ ├── FloatBar.tsx │ │ │ │ ├── InsertAutocomplete.tsx │ │ │ │ ├── InsertLink.tsx │ │ │ │ ├── LangAutocomplete.tsx │ │ │ │ ├── Leading.tsx │ │ │ │ ├── Links.tsx │ │ │ │ ├── Search.tsx │ │ │ │ ├── Title.tsx │ │ │ │ ├── langIconMap.ts │ │ │ │ └── langIcons │ │ │ │ │ ├── abap.svg │ │ │ │ │ ├── actionscript.svg │ │ │ │ │ ├── ada.svg │ │ │ │ │ ├── apache.svg │ │ │ │ │ ├── apex.svg │ │ │ │ │ ├── apl.svg │ │ │ │ │ ├── applescript.svg │ │ │ │ │ ├── astro.svg │ │ │ │ │ ├── awk.svg │ │ │ │ │ ├── ballerina.svg │ │ │ │ │ ├── bat.svg │ │ │ │ │ ├── bicep.svg │ │ │ │ │ ├── blade.svg │ │ │ │ │ ├── c.svg │ │ │ │ │ ├── cadence.svg │ │ │ │ │ ├── clojure.svg │ │ │ │ │ ├── cmake.svg │ │ │ │ │ ├── cobol.svg │ │ │ │ │ ├── codeql.svg │ │ │ │ │ ├── coffee.svg │ │ │ │ │ ├── console.svg │ │ │ │ │ ├── cpp.svg │ │ │ │ │ ├── crystal.svg │ │ │ │ │ ├── csharp.svg │ │ │ │ │ ├── css.svg │ │ │ │ │ ├── d.svg │ │ │ │ │ ├── dart.svg │ │ │ │ │ ├── database.svg │ │ │ │ │ ├── diff.svg │ │ │ │ │ ├── docker.svg │ │ │ │ │ ├── dotenv.svg │ │ │ │ │ ├── elixir.svg │ │ │ │ │ ├── elm.svg │ │ │ │ │ ├── erb.svg │ │ │ │ │ ├── erlang.svg │ │ │ │ │ ├── fsharp.svg │ │ │ │ │ ├── gdscript.svg │ │ │ │ │ ├── glsl.svg │ │ │ │ │ ├── gnuplot.svg │ │ │ │ │ ├── go.svg │ │ │ │ │ ├── graphql.svg │ │ │ │ │ ├── groovy.svg │ │ │ │ │ ├── hack.svg │ │ │ │ │ ├── haml.svg │ │ │ │ │ ├── handlebars.svg │ │ │ │ │ ├── haskell.svg │ │ │ │ │ ├── hcl.svg │ │ │ │ │ ├── hcl_light.svg │ │ │ │ │ ├── hjson.svg │ │ │ │ │ ├── hlsl.svg │ │ │ │ │ ├── html.svg │ │ │ │ │ ├── http.svg │ │ │ │ │ ├── imba.svg │ │ │ │ │ ├── ini.svg │ │ │ │ │ ├── java.svg │ │ │ │ │ ├── javascript.svg │ │ │ │ │ ├── jinja.svg │ │ │ │ │ ├── json.svg │ │ │ │ │ ├── json5.svg │ │ │ │ │ ├── jsonnet.svg │ │ │ │ │ ├── julia.svg │ │ │ │ │ ├── kotlin.svg │ │ │ │ │ ├── kusto.svg │ │ │ │ │ ├── less.svg │ │ │ │ │ ├── liquid.svg │ │ │ │ │ ├── lisp.svg │ │ │ │ │ ├── lua.svg │ │ │ │ │ ├── makefile.svg │ │ │ │ │ ├── markdown.svg │ │ │ │ │ ├── markojs.svg │ │ │ │ │ ├── matlab.svg │ │ │ │ │ ├── mdx.svg │ │ │ │ │ ├── mermaid.svg │ │ │ │ │ ├── mojo.svg │ │ │ │ │ ├── nginx.svg │ │ │ │ │ ├── nim.svg │ │ │ │ │ ├── nix.svg │ │ │ │ │ ├── objective-c.svg │ │ │ │ │ ├── objective-cpp.svg │ │ │ │ │ ├── ocaml.svg │ │ │ │ │ ├── pascal.svg │ │ │ │ │ ├── perl.svg │ │ │ │ │ ├── perl6.svg │ │ │ │ │ ├── php.svg │ │ │ │ │ ├── postcss.svg │ │ │ │ │ ├── powershell.svg │ │ │ │ │ ├── prisma.svg │ │ │ │ │ ├── prolog.svg │ │ │ │ │ ├── proto.svg │ │ │ │ │ ├── pug.svg │ │ │ │ │ ├── puppet.svg │ │ │ │ │ ├── purescript.svg │ │ │ │ │ ├── python.svg │ │ │ │ │ ├── r.svg │ │ │ │ │ ├── razor.svg │ │ │ │ │ ├── react.svg │ │ │ │ │ ├── react_ts.svg │ │ │ │ │ ├── ruby.svg │ │ │ │ │ ├── rust.svg │ │ │ │ │ ├── sas.svg │ │ │ │ │ ├── sass.svg │ │ │ │ │ ├── scala.svg │ │ │ │ │ ├── scheme.svg │ │ │ │ │ ├── shaderlab.svg │ │ │ │ │ ├── solidity.svg │ │ │ │ │ ├── sparql.svg │ │ │ │ │ ├── stata.svg │ │ │ │ │ ├── stylus.svg │ │ │ │ │ ├── svelte.svg │ │ │ │ │ ├── swift.svg │ │ │ │ │ ├── systemverilog.svg │ │ │ │ │ ├── tcl.svg │ │ │ │ │ ├── tex.svg │ │ │ │ │ ├── toml.svg │ │ │ │ │ ├── twig.svg │ │ │ │ │ ├── typescript.svg │ │ │ │ │ ├── verilog.svg │ │ │ │ │ ├── vhdl.svg │ │ │ │ │ ├── vim.svg │ │ │ │ │ ├── vue.svg │ │ │ │ │ ├── webassembly.svg │ │ │ │ │ ├── wenyan.svg │ │ │ │ │ ├── wgsl.svg │ │ │ │ │ ├── wolframlanguage.svg │ │ │ │ │ ├── xml.svg │ │ │ │ │ ├── xsl.svg │ │ │ │ │ ├── yaml.svg │ │ │ │ │ └── zig.svg │ │ │ ├── ui │ │ │ │ ├── Characters.tsx │ │ │ │ ├── Empty.tsx │ │ │ │ ├── History.tsx │ │ │ │ ├── QuickOpen.tsx │ │ │ │ ├── Tabs.tsx │ │ │ │ └── Webview.tsx │ │ │ └── utils │ │ │ │ ├── InlineChromiumBugfix.tsx │ │ │ │ ├── ace.ts │ │ │ │ ├── dom.ts │ │ │ │ ├── editorUtils.ts │ │ │ │ └── index.ts │ │ ├── env.d.ts │ │ ├── hooks │ │ │ ├── common.ts │ │ │ └── useLocalState.ts │ │ ├── icons │ │ │ ├── IBackLink.tsx │ │ │ ├── IFold.tsx │ │ │ ├── ILoad.tsx │ │ │ ├── IStop.tsx │ │ │ ├── ISwitch.tsx │ │ │ ├── IWorkspace.tsx │ │ │ └── keyboard │ │ │ │ ├── BackSlash.tsx │ │ │ │ ├── Backspace.tsx │ │ │ │ ├── Click.tsx │ │ │ │ ├── Command.tsx │ │ │ │ ├── Ctrl.tsx │ │ │ │ ├── Enter.tsx │ │ │ │ ├── IFull.tsx │ │ │ │ ├── ISort.tsx │ │ │ │ ├── Option.tsx │ │ │ │ ├── Shift.tsx │ │ │ │ └── System.tsx │ │ ├── locales │ │ │ ├── en_US.json │ │ │ └── zh_CN.json │ │ ├── main.tsx │ │ ├── output │ │ │ ├── markdownToDocx.ts │ │ │ ├── markdownToHtml.ts │ │ │ ├── markdownToPdf.ts │ │ │ └── markdownToPdfAdvanced.ts │ │ ├── parser │ │ │ ├── excelParser.ts │ │ │ ├── htmlToMarkdown.ts │ │ │ ├── pdfParser.ts │ │ │ └── wordParser.ts │ │ ├── store │ │ │ ├── api │ │ │ │ ├── api.ts │ │ │ │ ├── ipc │ │ │ │ │ ├── core.ts │ │ │ │ │ └── ipc.ts │ │ │ │ └── system.ts │ │ │ ├── chat.ts │ │ │ ├── keyboard.ts │ │ │ ├── llm │ │ │ │ ├── client.ts │ │ │ │ ├── data │ │ │ │ │ ├── data.tsx │ │ │ │ │ ├── image-models.json │ │ │ │ │ └── models.json │ │ │ │ ├── provider │ │ │ │ │ ├── claude.ts │ │ │ │ │ ├── gemini.ts │ │ │ │ │ ├── lmstudio.ts │ │ │ │ │ ├── ollama.ts │ │ │ │ │ ├── openRouter.ts │ │ │ │ │ ├── openai.ts │ │ │ │ │ └── struct.ts │ │ │ │ └── type.d.ts │ │ │ ├── menu.ts │ │ │ ├── note │ │ │ │ ├── TabCtx.ts │ │ │ │ ├── import.ts │ │ │ │ ├── keyboard.ts │ │ │ │ ├── local.ts │ │ │ │ ├── note.ts │ │ │ │ ├── output.ts │ │ │ │ ├── refactor.ts │ │ │ │ ├── tab.ts │ │ │ │ ├── table.ts │ │ │ │ └── worker │ │ │ │ │ ├── handle.ts │ │ │ │ │ └── main.ts │ │ │ ├── settings.ts │ │ │ ├── store.ts │ │ │ └── struct.ts │ │ ├── styles │ │ │ ├── ace.css │ │ │ ├── animate.css │ │ │ ├── chat.css │ │ │ ├── color.css │ │ │ ├── cover.css │ │ │ ├── editor.css │ │ │ └── index.css │ │ ├── types │ │ │ ├── ai.d.ts │ │ │ ├── ipc.d.ts │ │ │ └── module.d.ts │ │ ├── ui │ │ │ ├── Entry.tsx │ │ │ ├── Nav.tsx │ │ │ ├── chat │ │ │ │ ├── Chat.tsx │ │ │ │ ├── ChatInput │ │ │ │ │ ├── ChatInput.tsx │ │ │ │ │ └── ChooseFile.ts │ │ │ │ ├── ChatItem │ │ │ │ │ ├── AiMessage.tsx │ │ │ │ │ ├── UserMessage.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── EditableMessage.tsx │ │ │ │ │ │ ├── Loading.tsx │ │ │ │ │ │ └── MessageContent.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── style.ts │ │ │ │ │ └── type.ts │ │ │ │ ├── ChatList.tsx │ │ │ │ ├── ChatNotes.tsx │ │ │ │ ├── Empty.tsx │ │ │ │ ├── ModelIcon.tsx │ │ │ │ ├── Search.tsx │ │ │ │ ├── SwitchModel.tsx │ │ │ │ ├── ViewList.tsx │ │ │ │ └── message │ │ │ │ │ ├── BubbleLoading.tsx │ │ │ │ │ ├── Default.tsx │ │ │ │ │ └── Reasion.tsx │ │ │ ├── common │ │ │ │ ├── ErrorBoundary.tsx │ │ │ │ ├── HelpText.tsx │ │ │ │ ├── Menu.tsx │ │ │ │ └── ScrollList.tsx │ │ │ ├── dialog │ │ │ │ ├── ConfirmDialog.tsx │ │ │ │ └── Dialog.tsx │ │ │ ├── markdown │ │ │ │ ├── Code │ │ │ │ │ ├── CodeBlock.tsx │ │ │ │ │ ├── Highlighter.tsx │ │ │ │ │ ├── Pre.tsx │ │ │ │ │ ├── SyntaxHighlighter.tsx │ │ │ │ │ ├── highlighterStyle.ts │ │ │ │ │ └── style.ts │ │ │ │ ├── Markdown.tsx │ │ │ │ ├── Mermaid │ │ │ │ │ ├── FullFeatured.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── type.ts │ │ │ │ │ └── useMermaid.tsx │ │ │ │ ├── Snippet │ │ │ │ │ ├── Splotlight.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── style.ts │ │ │ │ │ └── styleSplotlight.ts │ │ │ │ ├── markdown.style.ts │ │ │ │ ├── plugins │ │ │ │ │ └── katexDir.ts │ │ │ │ ├── style.ts │ │ │ │ └── utils.ts │ │ │ ├── settings │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Keyboard.tsx │ │ │ │ ├── Model.tsx │ │ │ │ └── Settings.tsx │ │ │ ├── sidebar │ │ │ │ ├── Chats.tsx │ │ │ │ ├── SideBar.tsx │ │ │ │ └── tree │ │ │ │ │ ├── EditFolderDialog.tsx │ │ │ │ │ ├── FullSearch.tsx │ │ │ │ │ ├── ToogleSpace.tsx │ │ │ │ │ ├── Trash.tsx │ │ │ │ │ ├── Tree.tsx │ │ │ │ │ ├── TreeEmpty.tsx │ │ │ │ │ └── TreeRender.tsx │ │ │ └── space │ │ │ │ ├── EditSpace.tsx │ │ │ │ ├── ExportSpace.tsx │ │ │ │ ├── Files.tsx │ │ │ │ ├── ImportFolder.tsx │ │ │ │ └── SpaceItem.tsx │ │ ├── utils │ │ │ ├── ai.ts │ │ │ ├── clipboard.ts │ │ │ ├── common.ts │ │ │ ├── dom.tsx │ │ │ ├── i18n.ts │ │ │ └── string.ts │ │ └── worker.tsx │ └── worker.html └── types │ └── model.d.ts ├── tsconfig.json ├── tsconfig.node.json └── tsconfig.web.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | *.log* 4 | unpackage 5 | resources/lib/current.html 6 | resources/lib/current.json 7 | .env 8 | app/ 9 | electron.vite.config.*.mjs 10 | mdparser/index.js 11 | dist 12 | web 13 | build/bluestone.provisionprofile 14 | build/masDev.provisionprofile 15 | build/bs.provisionprofile 16 | build/entitlements.mas.inherit.plist 17 | build/entitlements.mas.plist 18 | build/entitlements.mas.loginhelper.plist 19 | src/renderer/src/share 20 | mas-builder.yml 21 | .DS_Store 22 | .VSCodeCounter 23 | .env 24 | out 25 | .cursor -------------------------------------------------------------------------------- /.i18nrc.cjs: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@lobehub/i18n-cli') 2 | module.exports = defineConfig({ 3 | entry: 'src/renderer/src/locales/zh_CN.json', 4 | entryLocale: 'zh_CN', 5 | output: 'src/renderer/src/locales', 6 | modelName: 'qwen-max', 7 | reference: 8 | '这是一个笔记应用,也是一个AI对话应用,根据中文词汇翻译文案,中文中的英文词汇不必再次翻译。', 9 | outputLocales: ['en_US'] 10 | }) 11 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # electron_mirror=https://npmmirror.com/mirrors/electron/ 2 | # electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ 3 | # shamefully-hoist=true 4 | 5 | # proxy=http://127.0.0.1:7890 6 | # https-proxy=http://127.0.0.1:7890 7 | # proxy=socks5://127.0.0.1:7891 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | pnpm-lock.yaml 4 | LICENSE.md 5 | tsconfig.json 6 | tsconfig.*.json 7 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | printWidth: 100 4 | trailingComma: none 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" 12 | }, 13 | "runtimeArgs": ["--sourcemap"] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[json]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "tailwindCSS.includeLanguages": { 12 | "html": "html", 13 | "javascript": "javascript", 14 | "typescript": "typescript", 15 | "tsx": "typescriptreact" 16 | }, 17 | "tailwindCSS.lint.cssConflict": "warning", 18 | "tailwindCSS.emmetCompletions": true, 19 | "tailwindCSS.classAttributes": ["class", "className"] 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Inkdown

2 | 3 | Inkdown Editor is a WYSIWYG editor and an LLM dialogue tool, fully compatible with GitHub Flavored Markdown Spec. 4 | 5 | [Documentation](https://www.inkdown.cn/docs) 6 | 7 | | Mac | Windows | VsCode | 8 | | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | 9 | | [Arm64](https://www.inkdown.cn/download/darwin/arm64) [X64](https://www.inkdown.cn/download/darwin/x64) |   [Arm64](https://www.inkdown.cn/download/win32/arm64)   [X64](https://www.inkdown.cn/download/win32/x64) | [Market](https://marketplace.visualstudio.com/items?itemName=1943time.inkdown) | 10 | 11 | ![](./docs/d1.png) 12 | 13 | ![](./docs/d2.png) 14 | 15 | ### Vs code 16 | 17 | ![](./docs/v1.png) 18 | 19 | ![](./docs/v2.png) 20 | -------------------------------------------------------------------------------- /build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | com.apple.security.cs.disable-library-validation 12 | 13 | com.apple.security.files.user-selected.read-write 14 | 15 | com.apple.security.network.client 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/build/icon.icns -------------------------------------------------------------------------------- /build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/build/icon.png -------------------------------------------------------------------------------- /dev-app-update.yml: -------------------------------------------------------------------------------- 1 | provider: generic 2 | url: https://example.com/auto-updates 3 | updaterCacheDirName: app-updater 4 | -------------------------------------------------------------------------------- /docs/d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/docs/d1.png -------------------------------------------------------------------------------- /docs/d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/docs/d2.png -------------------------------------------------------------------------------- /docs/v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/docs/v1.png -------------------------------------------------------------------------------- /docs/v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/docs/v2.png -------------------------------------------------------------------------------- /electron.vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig, externalizeDepsPlugin } from 'electron-vite' 3 | import react from '@vitejs/plugin-react' 4 | import tailwindcss from '@tailwindcss/vite' 5 | export default defineConfig({ 6 | main: { 7 | plugins: [externalizeDepsPlugin()] 8 | }, 9 | preload: { 10 | plugins: [externalizeDepsPlugin()] 11 | }, 12 | renderer: { 13 | resolve: { 14 | alias: { 15 | '@renderer': resolve('src/renderer/src'), 16 | '@': resolve('src/renderer/src') // Alias for src folder 17 | } 18 | }, 19 | build: { 20 | minify: true, 21 | cssMinify: true, 22 | rollupOptions: { 23 | input: { 24 | index: resolve(__dirname, 'src/renderer/index.html'), 25 | worker: resolve(__dirname, 'src/renderer/worker.html') 26 | } 27 | } 28 | }, 29 | plugins: [tailwindcss(), react()] 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/resources/icon.png -------------------------------------------------------------------------------- /scripts/afterPack.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const os = require('os') 3 | const fs = require('fs') 4 | exports.default = async function (context) { 5 | const resorce = 6 | os.platform() === 'darwin' 7 | ? path.join(context.appOutDir, 'Inkdown.app/Contents/Resources') 8 | : path.join(context.appOutDir, 'resources') 9 | const onnBin = path.join(resorce, 'app.asar.unpacked/node_modules/onnxruntime-node/bin/napi-v3') 10 | console.log('onnBin', onnBin) 11 | 12 | if (fs.existsSync(path.join(onnBin, 'linux'))) { 13 | fs.rmSync(path.join(onnBin, 'linux'), { recursive: true, force: true }) 14 | } 15 | if (os.platform() === 'darwin') { 16 | if (fs.existsSync(path.join(onnBin, 'win32'))) { 17 | fs.rmSync(path.join(onnBin, 'win32'), { recursive: true, force: true }) 18 | } 19 | if (os.arch() === 'arm64') { 20 | if (fs.existsSync(path.join(onnBin, 'darwin/x64'))) { 21 | fs.rmSync(path.join(onnBin, 'darwin/x64'), { recursive: true, force: true }) 22 | } 23 | } else { 24 | if (fs.existsSync(path.join(onnBin, 'darwin/arm64'))) { 25 | fs.rmSync(path.join(onnBin, 'darwin/arm64'), { recursive: true, force: true }) 26 | } 27 | } 28 | } 29 | if (os.platform() === 'win32') { 30 | if (fs.existsSync(path.join(onnBin, 'darwin'))) { 31 | fs.rmSync(path.join(onnBin, 'darwin'), { recursive: true, force: true }) 32 | } 33 | if (os.arch() === 'arm64') { 34 | if (fs.existsSync(path.join(onnBin, 'win32/x64'))) { 35 | fs.rmSync(path.join(onnBin, 'win32/x64'), { recursive: true, force: true }) 36 | } 37 | } else { 38 | if (fs.existsSync(path.join(onnBin, 'win32/arm64'))) { 39 | fs.rmSync(path.join(onnBin, 'win32/arm64'), { recursive: true, force: true }) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scripts/model.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url' 2 | import { dirname, join } from 'path' 3 | import { writeFileSync } from 'fs' 4 | 5 | const __filename = fileURLToPath(import.meta.url) 6 | const __dirname = dirname(__filename) 7 | 8 | fetch('https://openrouter.ai/api/v1/models') 9 | .then((res) => res.json()) 10 | .then((data) => { 11 | const imageModels = data.data 12 | .filter((m) => m.architecture?.input_modalities?.includes('image')) 13 | .map((item) => { 14 | return item.id.split('/').pop() 15 | }) 16 | writeFileSync( 17 | join(__dirname, '../src/renderer/src/store/llm/data/image-models.json'), 18 | JSON.stringify(imageModels, null, 2), 19 | { encoding: 'utf-8' } 20 | ) 21 | }) 22 | -------------------------------------------------------------------------------- /scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize') 2 | const dotenv = require('dotenv') 3 | dotenv.config() 4 | exports.default = async function notarizing(context) { 5 | const { electronPlatformName, appOutDir } = context 6 | const appName = context.packager.appInfo.productFilename 7 | 8 | if (electronPlatformName !== 'darwin') { 9 | return 10 | } 11 | 12 | // 检查必要的环境变量 13 | if (!process.env.APPLEID || !process.env.APPLEIDPASS || !process.env.APPLETEAMID) { 14 | console.error('Missing required environment variables. Please check your .env file') 15 | return 16 | } 17 | 18 | try { 19 | await notarize({ 20 | appBundleId: 'bluestone', 21 | appPath: `${appOutDir}/${appName}.app`, 22 | appleId: process.env.APPLEID, 23 | appleIdPassword: process.env.APPLEIDPASS, 24 | teamId: process.env.APPLETEAMID 25 | }) 26 | } catch (error) { 27 | console.error('Notarization error:', error) 28 | } 29 | 30 | console.log(`done notarizing`) 31 | } 32 | -------------------------------------------------------------------------------- /scripts/upload.js: -------------------------------------------------------------------------------- 1 | const OSS = require('ali-oss') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const os = require('os') 5 | 6 | const platform = os.platform() 7 | const arch = process.env.OS_ARCH 8 | const distPath = path.join(__dirname, '..', 'dist', platform, arch) 9 | 10 | const client = new OSS({ 11 | region: process.env.OSS_ENDPOINT, 12 | accessKeyId: process.env.OSS_ACCESS_KEY_ID, 13 | accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET, 14 | bucket: process.env.OSS_BUCKET 15 | }) 16 | 17 | async function uploadAllFiles() { 18 | try { 19 | const files = fs.readdirSync(distPath) 20 | for (const file of files) { 21 | if (file.match(/^Inkdown.*\..*$/) || file.match(/^latest.*\.yml$/)) { 22 | const ossPath = `release/${process.env.REF_NAME}/${platform}/${arch}/${file}` 23 | await client.put(ossPath, path.join(distPath, file)) 24 | } 25 | } 26 | console.log('All files uploaded successfully') 27 | } catch (error) { 28 | console.error('Upload failed:', error) 29 | process.exit(1) 30 | } 31 | } 32 | 33 | uploadAllFiles() 34 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1943time/inkdown/a68a2aaf1f4897316576ec2f797da403178baa10/src/.DS_Store -------------------------------------------------------------------------------- /src/main/update.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, ipcMain } from 'electron' 2 | import { autoUpdater } from 'electron-updater' 3 | import os from 'os' 4 | import log from 'electron-log' 5 | autoUpdater.autoDownload = false 6 | log.initialize() 7 | autoUpdater.logger = log 8 | log.transports.file.level = 'info' 9 | export const registerUpdate = () => { 10 | if (app.isPackaged) { 11 | const feedUrl = `https://www.inkdown.cn/update/${app.getVersion()}/${os.platform()}/${os.arch()}` 12 | try { 13 | autoUpdater.setFeedURL({ 14 | provider: 'generic', 15 | url: feedUrl 16 | }) 17 | // autoUpdater.on('error', (err) => { 18 | // // log.error('更新出错:', err == null ? 'unknown' : (err.stack || err).toString()) 19 | // }) 20 | 21 | // autoUpdater.on('checking-for-update', () => { 22 | // // log.info('正在检查更新...') 23 | // }) 24 | // autoUpdater.on('update-not-available', () => { 25 | // // log.info('当前已是最新版本') 26 | // }) 27 | // 发现可用更新 28 | autoUpdater.on('update-available', () => { 29 | // log.info('发现新版本:', info.version) 30 | autoUpdater.downloadUpdate() 31 | }) 32 | autoUpdater.on('update-downloaded', () => { 33 | // log.info('更新下载完成,版本:', info.version) 34 | const windows = BrowserWindow.getAllWindows() 35 | windows.forEach((window) => { 36 | window.webContents.send('update-ready') 37 | }) 38 | }) 39 | autoUpdater.checkForUpdates() 40 | setInterval( 41 | () => { 42 | autoUpdater.checkForUpdates() 43 | }, 44 | 60 * 60 * 1000 45 | ) 46 | ipcMain.on('udpate-and-restart', () => { 47 | autoUpdater.quitAndInstall() 48 | }) 49 | } catch (e) { 50 | log.error('设置更新源失败:', e) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/utils.ts: -------------------------------------------------------------------------------- 1 | import { customAlphabet } from 'nanoid' 2 | 3 | export const pick = (obj: T, keys: K[]): Pick => { 4 | const result = {} as Pick 5 | keys.forEach((key) => { 6 | if (key in obj) { 7 | result[key] = obj[key] 8 | } 9 | }) 10 | return result 11 | } 12 | 13 | export const omit = (obj: T, keys: K[]): Omit => { 14 | const result = { ...obj } 15 | keys.forEach((key) => { 16 | delete result[key] 17 | }) 18 | return result as Omit 19 | } 20 | 21 | export const formatDate = (timestamp: number): string => { 22 | const date = new Date(timestamp) 23 | const year = date.getFullYear() 24 | const month = String(date.getMonth() + 1).padStart(2, '0') 25 | const day = String(date.getDate()).padStart(2, '0') 26 | return `${year} ${month} ${day}` 27 | } 28 | 29 | export const nid = customAlphabet( 30 | '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 31 | 15 32 | ) 33 | -------------------------------------------------------------------------------- /src/preload/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronAPI } from '@electron-toolkit/preload' 2 | import { Api } from './api' 3 | declare global { 4 | interface Window { 5 | electron: ElectronAPI 6 | api: typeof Api 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/preload/index.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge } from 'electron' 2 | import { electronAPI } from '@electron-toolkit/preload' 3 | import { Api } from './api' 4 | 5 | try { 6 | contextBridge.exposeInMainWorld('electron', electronAPI) 7 | contextBridge.exposeInMainWorld('api', Api) 8 | } catch (error) { 9 | console.error(error) 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inkdown 6 | 7 | 11 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/renderer/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/renderer/src/editor/elements/CodeUI/Katex/InlineKatex.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo, useRef } from 'react' 2 | import { InlineChromiumBugfix } from '../../../utils/InlineChromiumBugfix' 3 | import { Editor, Node, Transforms } from 'slate' 4 | import { ElementProps, InlineKatexNode } from '../../..' 5 | import { useSelStatus } from '@/editor/utils' 6 | import katex from 'katex' 7 | export default function ({ children, element, attributes }: ElementProps) { 8 | const renderEl = useRef(null) 9 | const [selected, path, store] = useSelStatus(element) 10 | useEffect(() => { 11 | if (!selected) { 12 | const value = Node.string(element) 13 | katex.render(value, renderEl.current!, { 14 | strict: false, 15 | output: 'html', 16 | throwOnError: false, 17 | macros: { 18 | '\\f': '#1f(#2)' 19 | } 20 | }) 21 | } 22 | }, [selected]) 23 | return ( 24 | 25 | 28 | 29 | {children} 30 | 31 | 32 | { 36 | Transforms.select(store.editor, Editor.end(store.editor, path)) 37 | }} 38 | className={`mx-1 select-none ${selected ? 'invisible w-0 h-0 overflow-hidden absolute' : ''}`} 39 | /> 40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/renderer/src/editor/elements/CodeUI/Katex/Katex.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | import { useGetSetState } from 'react-use' 3 | import { CodeNode } from '../../..' 4 | import { EditorUtils } from '../../../utils/editorUtils' 5 | import { useTab } from '@/store/note/TabCtx' 6 | import katex from 'katex' 7 | export default function Katex(props: { el: CodeNode }) { 8 | const tab = useTab() 9 | const [state, setState] = useGetSetState({ 10 | code: '', 11 | error: '' 12 | }) 13 | const divRef = useRef(null) 14 | const timer = useRef(0) 15 | useEffect(() => { 16 | const code = props.el.code || '' 17 | clearTimeout(timer.current) 18 | timer.current = window.setTimeout( 19 | () => { 20 | setState({ 21 | code: code 22 | }) 23 | if (state().code) { 24 | try { 25 | if (divRef.current) { 26 | katex.render(state().code, divRef.current!, { 27 | strict: false, 28 | output: 'htmlAndMathml', 29 | throwOnError: false, 30 | displayMode: true, 31 | macros: { 32 | '\\f': '#1f(#2)' 33 | } 34 | }) 35 | } 36 | } catch (e) { 37 | console.log('err', e) 38 | } 39 | } else { 40 | setState({ error: '' }) 41 | } 42 | }, 43 | !state().code ? 0 : 300 44 | ) 45 | return () => window.clearTimeout(timer.current) 46 | }, [props.el]) 47 | return ( 48 |
{ 53 | const editor = tab.codeMap.get(props.el) 54 | if (editor) { 55 | EditorUtils.focusAceEnd(editor) 56 | } 57 | }} 58 | contentEditable={false} 59 | > 60 |
61 | {!state().code.trim() &&
Formula
} 62 |
63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /src/renderer/src/editor/elements/blockquote.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import { BlockQuoteNode, ElementProps } from '..' 3 | export function Blockquote(props: ElementProps) { 4 | return ( 5 |
6 | {props.children} 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/src/editor/elements/head.tsx: -------------------------------------------------------------------------------- 1 | import { createElement, useMemo } from 'react' 2 | import { DragHandle } from '../tools/DragHandle' 3 | import { Node } from 'slate' 4 | import { slugify } from '../utils/dom' 5 | import { ElementProps, HeadNode } from '..' 6 | import { useSelStatus } from '../utils' 7 | 8 | export function Head({ element, attributes, children }: ElementProps) { 9 | const [selected, path] = useSelStatus(element) 10 | const str = Node.string(element) 11 | return createElement( 12 | `h${element.level}`, 13 | { 14 | ...attributes, 15 | ['data-be']: 'head', 16 | className: 'drag-el', 17 | ['data-head']: slugify(Node.string(element) || ''), 18 | ['data-title']: path?.[0] === 0, 19 | ['data-empty']: !str && selected && element.children?.length === 1 ? 'true' : undefined 20 | }, 21 | <> 22 | 23 | {children} 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/src/editor/elements/list.tsx: -------------------------------------------------------------------------------- 1 | import React, { createElement, useMemo, useRef } from 'react' 2 | import { Checkbox } from 'antd' 3 | import { ElementProps, ListItemNode, ListNode } from '..' 4 | import { useTab } from '@/store/note/TabCtx' 5 | import { useMEditor } from '../utils' 6 | 7 | export function List({ element, attributes, children }: ElementProps) { 8 | const tab = useTab() 9 | const tag = element.order ? 'ol' : 'ul' 10 | return ( 11 |
12 | {createElement( 13 | tag, 14 | { 15 | className: 'm-list', 16 | start: element.start, 17 | ['data-task']: element.task ? 'true' : undefined 18 | }, 19 | children 20 | )} 21 |
22 | ) 23 | } 24 | 25 | export function ListItem({ element, children, attributes }: ElementProps) { 26 | const tab = useTab() 27 | const [, update] = useMEditor(element) 28 | const isTask = typeof element.checked === 'boolean' 29 | return ( 30 |
  • tab.dragStart(e)} 34 | {...attributes} 35 | > 36 | {isTask && ( 37 | 38 | update({ checked: e.target.checked })} 41 | /> 42 | 43 | )} 44 | {children} 45 |
  • 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /src/renderer/src/editor/elements/paragraph.tsx: -------------------------------------------------------------------------------- 1 | import { DragHandle } from '../tools/DragHandle' 2 | import { Node } from 'slate' 3 | import { useSelStatus } from '../utils' 4 | import { useTab } from '@/store/note/TabCtx' 5 | import { ElementProps, ParagraphNode } from '..' 6 | 7 | export function Paragraph(props: ElementProps) { 8 | const tab = useTab() 9 | const [selected] = useSelStatus(props.element) 10 | const str = Node.string(props.element) 11 | return ( 12 |

    19 | 20 | {props.children} 21 |

    22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/src/editor/elements/wikilink.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@/store/store' 2 | import { ElementProps, WikiLinkNode } from '..' 3 | import { useSelStatus } from '../utils' 4 | import { InlineChromiumBugfix } from '../utils/InlineChromiumBugfix' 5 | import { Editor, Node, Path, Transforms } from 'slate' 6 | import { isMod } from '@/utils/common' 7 | import { useMemo, useRef } from 'react' 8 | import { EditorUtils } from '../utils/editorUtils' 9 | 10 | export function WikiLink({ element, children, attributes }: ElementProps) { 11 | const store = useStore() 12 | const [selected, path, tab] = useSelStatus(element) 13 | const pathRef = useRef(path) 14 | pathRef.current = path 15 | const displayText = useMemo(() => { 16 | const str = Node.string(element) 17 | const match = EditorUtils.parseWikiLink(str) 18 | return match?.displayText 19 | }, [element]) 20 | return ( 21 | { 26 | if (isMod(e)) { 27 | e.stopPropagation() 28 | } 29 | }} 30 | onClick={(e) => { 31 | if (isMod(e)) { 32 | store.note.toWikiLink(Node.string(element), !!e.altKey) 33 | } else if (!selected) { 34 | Transforms.select(tab.editor, path) 35 | setTimeout(() => { 36 | Transforms.select(tab.editor, Editor.end(tab.editor, path)) 37 | }, 16) 38 | } 39 | }} 40 | > 41 | 42 | 45 | {children} 46 | 47 | 51 | {displayText} 52 | 53 | 54 | 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/renderer/src/editor/icons/IMermaid.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { SVGProps } from 'react'; 3 | 4 | export default function IMermaid(props: SVGProps) { 5 | return (); 6 | } 7 | -------------------------------------------------------------------------------- /src/renderer/src/editor/icons/IPlanet.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function IPlanet(props: SVGProps) { 5 | return ( 6 | 7 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/src/editor/parser/container/factory-name.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('micromark-util-types').Effects} Effects 3 | * @typedef {import('micromark-util-types').State} State 4 | * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext 5 | * @typedef {import('micromark-util-types').TokenType} TokenType 6 | */ 7 | 8 | import {asciiAlpha, asciiAlphanumeric} from 'micromark-util-character' 9 | import {codes} from 'micromark-util-symbol' 10 | 11 | /** 12 | * @this {TokenizeContext} 13 | * @param {Effects} effects 14 | * @param {State} ok 15 | * @param {State} nok 16 | * @param {TokenType} type 17 | */ 18 | export function factoryName(effects, ok, nok, type) { 19 | const self = this 20 | 21 | return start 22 | 23 | /** @type {State} */ 24 | function start(code) { 25 | if (asciiAlpha(code)) { 26 | effects.enter(type) 27 | effects.consume(code) 28 | return name 29 | } 30 | 31 | return nok(code) 32 | } 33 | 34 | /** @type {State} */ 35 | function name(code) { 36 | if ( 37 | code === codes.dash || 38 | code === codes.underscore || 39 | asciiAlphanumeric(code) 40 | ) { 41 | effects.consume(code) 42 | return name 43 | } 44 | 45 | effects.exit(type) 46 | return self.previous === codes.dash || self.previous === codes.underscore 47 | ? nok(code) 48 | : ok(code) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/renderer/src/editor/parser/worker/bundle.d.ts: -------------------------------------------------------------------------------- 1 | declare module parser { 2 | export function parse(str: string): any 3 | } 4 | export default parser 5 | -------------------------------------------------------------------------------- /src/renderer/src/editor/parser/worker/compiler.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const TerserPlugin = require("terser-webpack-plugin"); 4 | 5 | webpack({ 6 | mode: "production", 7 | entry: path.join(__dirname, 'parser.js'), 8 | target: 'webworker', 9 | output: { 10 | filename: "bundle.js", 11 | path: __dirname, 12 | library: { 13 | type: "module", 14 | }, 15 | chunkFormat: 'module' 16 | }, 17 | experiments: { 18 | outputModule: true 19 | }, 20 | optimization: { 21 | minimizer: [new TerserPlugin({ 22 | extractComments: false 23 | })] 24 | } 25 | }).run() 26 | -------------------------------------------------------------------------------- /src/renderer/src/editor/parser/worker/directive.ts: -------------------------------------------------------------------------------- 1 | // import {directiveFromMarkdown} from 'mdast-util-directive' 2 | // import {directiveContainer} from './container/container' 3 | // @ts-ignore 4 | import {codes} from 'micromark-util-symbol' 5 | export function remarkDirective() { 6 | // @ts-ignore 7 | const data = this.data() 8 | 9 | add('micromarkExtensions', { 10 | // flow: {[codes.colon]: [directiveContainer]} 11 | }) 12 | // add('fromMarkdownExtensions', directiveFromMarkdown) 13 | // add('toMarkdownExtensions', directiveToMarkdown) 14 | // add('fromMarkdownExtensions', directiveFromMarkdown()) 15 | /** 16 | * @param {string} field 17 | * @param {unknown} value 18 | */ 19 | function add(field:any, value: any) { 20 | const list = /** @type {unknown[]} */ ( 21 | // Other extensions 22 | /* c8 ignore next 2 */ 23 | data[field] ? data[field] : (data[field] = []) 24 | ) 25 | 26 | list.push(value) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/renderer/src/editor/parser/worker/parser.js: -------------------------------------------------------------------------------- 1 | import { unified } from 'unified' 2 | import remarkParse from 'remark-parse' 3 | import remarkGfm from 'remark-gfm' 4 | import remarkMath from 'remark-math' 5 | import remarkFrontmatter from 'remark-frontmatter' 6 | import remarkWikiLink from 'remark-wiki-link' 7 | 8 | const parser = unified() 9 | .use(remarkParse) 10 | .use(remarkGfm) 11 | .use(remarkMath, { singleDollarTextMath: true }) 12 | .use(remarkFrontmatter, ['yaml']) 13 | .use(remarkWikiLink) 14 | 15 | export default parser 16 | -------------------------------------------------------------------------------- /src/renderer/src/editor/plugins/catchError.ts: -------------------------------------------------------------------------------- 1 | import {Editor} from 'slate' 2 | const tryCatchCallback = 3 | (editorFunc: any, editor: Editor) => 4 | (...editorFuncArgs: any) => { 5 | try { 6 | return editorFunc(...editorFuncArgs) 7 | } catch (error) { 8 | if (error instanceof Error) { 9 | console.error('slate:', error) 10 | } 11 | editor.undo() 12 | } 13 | } 14 | export const withErrorReporting = (editor: any): Editor => { 15 | Object.entries(editor).forEach(([key, value]) => { 16 | if (typeof value === 'function') { 17 | editor[key] = tryCatchCallback(value, editor) 18 | } 19 | }) 20 | 21 | return editor as Editor 22 | } 23 | -------------------------------------------------------------------------------- /src/renderer/src/editor/plugins/elHeight.ts: -------------------------------------------------------------------------------- 1 | import { Element, Transforms } from 'slate' 2 | import { ReactEditor } from 'slate-react' 3 | import { useDebounce } from 'react-use' 4 | import { CSSProperties, useRef } from 'react' 5 | import { TabStore } from '@/store/note/tab' 6 | 7 | export const useMonitorHeight = (tab: TabStore, el: Element) => { 8 | const first = useRef(true) 9 | useDebounce( 10 | () => { 11 | try { 12 | const path = ReactEditor.findPath(tab.editor, el) 13 | if (path.length > 1) return 14 | const dom = ReactEditor.toDOMNode(tab.editor, el) 15 | if (dom.clientHeight === el.h) return 16 | if (first.current && !!el.h) { 17 | first.current = false 18 | return 19 | } 20 | setTimeout(() => { 21 | try { 22 | first.current = false 23 | Transforms.setNodes(tab.editor, { h: dom.clientHeight }, { at: path }) 24 | dom.style.containIntrinsicSize = `0px ${dom.clientHeight}px` 25 | // @ts-ignore 26 | dom.style.contentVisibility = 'auto' 27 | } catch (e) { 28 | console.error(e) 29 | } 30 | }, 100) 31 | } catch (e) { 32 | console.error(e) 33 | } 34 | }, 35 | 300, 36 | [el.children] 37 | ) 38 | } 39 | 40 | export const getVisibleStyle = (el: Element): CSSProperties => { 41 | return { 42 | contentVisibility: el.h ? 'auto' : undefined, 43 | containIntrinsicSize: el.h ? `0px ${el.h}px` : undefined 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/renderer/src/editor/plugins/hotKeyCommands/match.ts: -------------------------------------------------------------------------------- 1 | import { Editor, Element, Node, NodeEntry, Range } from 'slate' 2 | import React from 'react' 3 | import { TextMatchNodes } from '../elements' 4 | import { TabStore } from '@/store/note/tab' 5 | 6 | export class MatchKey { 7 | private timer = 0 8 | get editor() { 9 | return this.tab.editor 10 | } 11 | constructor(private readonly tab: TabStore) {} 12 | 13 | private createParams(node: NodeEntry, match: RegExpMatchArray) { 14 | return { 15 | el: node[0], 16 | path: node[1], 17 | editor: this.editor, 18 | sel: this.editor.selection!, 19 | match, 20 | startText: match[0], 21 | tab: this.tab 22 | } 23 | } 24 | 25 | run(e: React.KeyboardEvent) { 26 | const [node] = Editor.nodes(this.editor, { 27 | match: (n) => Element.isElement(n), 28 | mode: 'lowest' 29 | }) 30 | if (!node || ['code'].includes(node[0].type)) return 31 | const sel = this.editor.selection 32 | if (!sel || !Range.isCollapsed(sel)) return 33 | const leaf = Node.leaf(this.editor, sel.anchor.path) 34 | if (!leaf) return 35 | for (let n of TextMatchNodes) { 36 | // 配置开启 37 | if (n.type === 'inlineKatex' && !this.tab.store.settings.state.autoConvertInlineFormula) { 38 | continue 39 | } 40 | if (typeof n.matchKey === 'object' ? n.matchKey.test(e.key) : n.matchKey === e.key) { 41 | if (n.checkAllow && !n.checkAllow({ editor: this.editor, node, sel })) continue 42 | try { 43 | const str = Node.string(leaf).slice(0, sel.anchor.offset) + e.key 44 | const m = str.match(n.reg) 45 | if (m) { 46 | if (n.run(this.createParams(node, m))) { 47 | e.preventDefault() 48 | } 49 | } 50 | } catch (e) { 51 | console.error(e) 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/renderer/src/editor/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import { NoteStore } from '@/store/note/note' 2 | import { TabStore } from '@/store/note/tab' 3 | import { Editor, Node, Transforms } from 'slate' 4 | 5 | export const inlineNode = new Set(['inline-katex', 'break', 'wiki-link']) 6 | const voidNode = new Set(['hr', 'break']) 7 | export const withMarkdown = (editor: Editor, store: TabStore) => { 8 | const { isInline, isVoid, apply } = editor 9 | editor.isInline = (element) => inlineNode.has(element.type) || isInline(element) 10 | 11 | editor.isVoid = (element) => { 12 | return voidNode.has(element.type) || isVoid(element) 13 | } 14 | editor.apply = (operation) => { 15 | if (operation.type === 'merge_node' && operation.properties?.type === 'table-cell') return 16 | if (!store.manual) { 17 | if (operation.type === 'move_node') { 18 | const node = Node.get(editor, operation.path) 19 | if (node?.type === 'table-cell') return 20 | } 21 | if (operation.type === 'remove_node') { 22 | const { node } = operation 23 | if (['table-row', 'table-cell'].includes(node.type)) { 24 | if (node.type === 'table-cell') { 25 | Transforms.insertFragment(editor, [{ text: '' }], { 26 | at: { 27 | anchor: Editor.start(editor, operation.path), 28 | focus: Editor.end(editor, operation.path) 29 | } 30 | }) 31 | } 32 | if (node.type === 'table-row') { 33 | for (let i = 0; i < node.children?.length; i++) { 34 | Transforms.insertFragment(editor, [{ text: '' }], { 35 | at: { 36 | anchor: Editor.start(editor, [...operation.path, i]), 37 | focus: Editor.end(editor, [...operation.path, i]) 38 | } 39 | }) 40 | } 41 | } 42 | return 43 | } 44 | } 45 | } 46 | apply(operation) 47 | } 48 | return editor 49 | } 50 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/DragHandle.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, memo, useRef } from 'react' 2 | import { GripVertical } from 'lucide-react' 3 | import { useTab } from '@/store/note/TabCtx' 4 | import { ReactEditor } from 'slate-react' 5 | 6 | export const DragHandle = memo((props: { style?: CSSProperties }) => { 7 | const ref = useRef(null) 8 | const tab = useTab() 9 | if (ReactEditor.isReadOnly(tab.editor)) return null 10 | return ( 11 | { 17 | let parent = ref.current!.parentElement! 18 | if (parent.parentElement?.dataset.be === 'list-item') { 19 | if ( 20 | !parent.previousSibling || 21 | (parent.previousSibling as HTMLElement).classList.contains('check-item') 22 | ) { 23 | parent = parent.parentElement 24 | } 25 | } 26 | tab.dragEl = parent 27 | tab.editor.selection = null 28 | 29 | tab.selChange$.next(null) 30 | tab.dragStart(e) 31 | e.stopPropagation() 32 | }} 33 | > 34 | 35 | 36 | ) 37 | }) 38 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/abap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/actionscript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/ada.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/apl.svg: -------------------------------------------------------------------------------- 1 | file_type_apl -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/applescript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/astro.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/ballerina.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/bicep.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/blade.svg: -------------------------------------------------------------------------------- 1 | file_type_blade -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/c.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/cadence.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/clojure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/cmake.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/cobol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/codeql.svg: -------------------------------------------------------------------------------- 1 | file_type_codeql 2 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/coffee.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/console.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/cpp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/crystal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/csharp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/css.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/d.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/dart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/database.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/diff.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/docker.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/dotenv.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/elixir.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/elm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/erb.svg: -------------------------------------------------------------------------------- 1 | file_type_erb -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/erlang.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/fsharp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/gdscript.svg: -------------------------------------------------------------------------------- 1 | file_type_godot 2 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/glsl.svg: -------------------------------------------------------------------------------- 1 | file_type_glsl -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/gnuplot.svg: -------------------------------------------------------------------------------- 1 | file_type_gnuplot -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/go.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/graphql.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/groovy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/hack.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/haml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/handlebars.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/haskell.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/hcl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/hcl_light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/hjson.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/hlsl.svg: -------------------------------------------------------------------------------- 1 | file_type_hlsl -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/html.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/http.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/imba.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/ini.svg: -------------------------------------------------------------------------------- 1 | file_type_ini -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/java.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/javascript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/json.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/jsonnet.svg: -------------------------------------------------------------------------------- 1 | file_type_jsonnet -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/julia.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/kotlin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/kusto.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/less.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/liquid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/lisp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/lua.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/makefile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/markdown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/markojs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/matlab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/mdx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/mermaid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/mojo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/nginx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/nim.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/objective-c.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/objective-cpp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/pascal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/perl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/php.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/postcss.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/powershell.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/prisma.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/prolog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/proto.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/puppet.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/purescript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/python.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/r.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/razor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/react_ts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/ruby.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/sas.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/sass.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/scala.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/scheme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/shaderlab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/solidity.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/sparql.svg: -------------------------------------------------------------------------------- 1 | file_type_sparql -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/stata.svg: -------------------------------------------------------------------------------- 1 | file_type_stata -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/stylus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/swift.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/tcl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/tex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/toml.svg: -------------------------------------------------------------------------------- 1 | file_type_toml -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/typescript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/verilog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/vhdl.svg: -------------------------------------------------------------------------------- 1 | file_type_vhdl -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/webassembly.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/wenyan.svg: -------------------------------------------------------------------------------- 1 | file_type_wenyan -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/wolframlanguage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/xml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/xsl.svg: -------------------------------------------------------------------------------- 1 | file_type_xsl -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/yaml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/tools/langIcons/zig.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/src/editor/utils/InlineChromiumBugfix.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | 3 | export const InlineChromiumBugfix = memo(() => ( 4 | 5 | {String.fromCodePoint(160)} 6 | 7 | )) 8 | -------------------------------------------------------------------------------- /src/renderer/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/renderer/src/hooks/common.ts: -------------------------------------------------------------------------------- 1 | import { IObjectDidChange, IValueDidChange, observe } from 'mobx' 2 | import { useEffect, useLayoutEffect } from 'react' 3 | import { Observable, Subject } from 'rxjs' 4 | 5 | export const useSubject = ( 6 | subject: Subject | Observable, 7 | fn: (value: T) => void, 8 | deps: any[] = [] 9 | ) => { 10 | useLayoutEffect(() => { 11 | const cancel = subject.subscribe(fn) 12 | return () => cancel.unsubscribe() 13 | }, deps) 14 | } 15 | 16 | export const useObserveKey = ( 17 | data: T, 18 | key: K, 19 | fn: (value: IValueDidChange) => void 20 | ) => { 21 | useEffect(() => { 22 | const cancel = observe(data, key, fn) 23 | return () => cancel() 24 | }, []) 25 | } 26 | 27 | export const useObserve = ( 28 | data: T, 29 | fn: (value: IObjectDidChange & { newValue: any; oldValue: any }) => void 30 | ) => { 31 | useEffect(() => { 32 | // @ts-ignore 33 | const cancel = observe(data, fn) 34 | return () => cancel() 35 | }, []) 36 | } 37 | -------------------------------------------------------------------------------- /src/renderer/src/hooks/useLocalState.ts: -------------------------------------------------------------------------------- 1 | import {useLocalObservable} from 'mobx-react-lite' 2 | import {action, AnnotationsMap} from 'mobx' 3 | import {useCallback} from 'react' 4 | 5 | type GetFields< 6 | T, 7 | K = { 8 | [P in keyof T]: T[P] extends Function ? never : P 9 | } 10 | > = K[keyof K] 11 | 12 | type SetState, F extends GetFields> = (data: {[P in F]?: T[P]} | ((state: T) => void)) => void 13 | export const useLocalState = >(data: (() => T) | T, annotations?: AnnotationsMap):[T, SetState>] => { 14 | const state = useLocalObservable(() => { 15 | return data instanceof Function ? data() : data 16 | }, annotations) as T 17 | const setState = useCallback(action((data: any) => { 18 | if (data instanceof Function) { 19 | // @ts-ignore 20 | data(state) 21 | } else { 22 | for (let key of Object.keys(data)) { 23 | // @ts-ignore 24 | state[key] = data[key] 25 | } 26 | } 27 | }), []) 28 | return [state, setState] 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/src/icons/IBackLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function IBackLink(props: SVGProps) { 5 | return ( 6 | 13 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/src/icons/IFold.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { SVGProps } from 'react' 3 | 4 | export const IFold = (props: SVGProps) => ( 5 | 16 | 17 | 18 | ) 19 | -------------------------------------------------------------------------------- /src/renderer/src/icons/ILoad.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function ILoad(props: SVGProps) { 5 | return ( 6 | 13 | 23 | 29 | 36 | 37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/renderer/src/icons/IStop.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function IStop(props: SVGProps) { 5 | return ( 6 | 13 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/src/icons/ISwitch.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function ISwitch(props: SVGProps) { 5 | return ( 6 | 7 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/src/icons/IWorkspace.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function IWorkspace(props: SVGProps) { 5 | return ( 6 | 7 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/BackSlash.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const SvgComponent = (props: SVGProps) => ( 4 | 11 | 19 | 20 | ) 21 | export default SvgComponent 22 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/Backspace.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { SVGProps } from 'react' 3 | const Backspace = (props: SVGProps) => ( 4 | 12 | 13 | 14 | ) 15 | export default Backspace 16 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/Click.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const SvgComponent = (props: SVGProps) => ( 4 | 12 | 13 | 14 | ) 15 | export default SvgComponent 16 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/Command.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { SVGProps } from 'react' 3 | const Command = (props: SVGProps) => ( 4 | 12 | 13 | 14 | ) 15 | export default Command 16 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/Ctrl.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const SvgComponent = (props: SVGProps) => ( 4 | 5 | 9 | 10 | ) 11 | export default SvgComponent 12 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/Enter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function IEnter(props: SVGProps) { 5 | return ( 6 | 7 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/IFull.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function IFull(props: SVGProps) { 5 | return ( 6 | 7 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/ISort.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import type { SVGProps } from 'react' 3 | 4 | export function ISort(props: SVGProps) { 5 | return ( 6 | 7 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/Option.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react' 2 | const Option = (props: SVGProps) => ( 3 | 11 | 12 | 13 | ) 14 | export default Option 15 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/Shift.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { SVGProps } from 'react'; 3 | 4 | export default function Shift(props: SVGProps) { 5 | return (); 6 | } 7 | -------------------------------------------------------------------------------- /src/renderer/src/icons/keyboard/System.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const SvgComponent = (props: SVGProps) => ( 4 | 11 | 18 | 19 | ) 20 | export default SvgComponent 21 | -------------------------------------------------------------------------------- /src/renderer/src/main.tsx: -------------------------------------------------------------------------------- 1 | import '@ant-design/v5-patch-for-react-19' 2 | import 'react-photo-view/dist/react-photo-view.css' 3 | import 'katex/dist/katex.min.css' 4 | import './utils/i18n' 5 | import ReactDOM from 'react-dom/client' 6 | import App from './App' 7 | 8 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 9 | // 10 | 11 | // 12 | ) 13 | -------------------------------------------------------------------------------- /src/renderer/src/parser/htmlToMarkdown.ts: -------------------------------------------------------------------------------- 1 | import * as gfm from 'turndown-plugin-gfm' 2 | import turndown from 'turndown' 3 | 4 | export const htmlToMarkdown = (html: string) => { 5 | const t = new turndown() 6 | t.use(gfm.gfm) 7 | // t.addRule('preWithLang', { 8 | // filter: ['pre'], 9 | // replacement: function(content, node) { 10 | // const lang = (node as HTMLElement).getAttribute('data-lang') || '' 11 | // return `\n\`\`\`${lang}\n${content}\n\`\`\`\n` 12 | // } 13 | // }) 14 | return t.turndown(html) 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/src/parser/wordParser.ts: -------------------------------------------------------------------------------- 1 | import mammoth from 'mammoth' 2 | import { htmlToMarkdown } from './htmlToMarkdown' 3 | 4 | export interface WordParseResult { 5 | text: string 6 | html: string 7 | metadata: { 8 | title?: string 9 | author?: string 10 | lastModified?: string 11 | } 12 | } 13 | 14 | export class WordParser { 15 | /** 16 | * 从Word文件读取内容 17 | * @param file Word文件对象 18 | * @returns 解析结果,包含文本内容和元数据 19 | */ 20 | public static async parseWord(file: File): Promise { 21 | try { 22 | // 将文件转换为 ArrayBuffer 23 | const arrayBuffer = await file.arrayBuffer() 24 | 25 | // 解析Word文档,保留格式 26 | const result = await mammoth.convertToHtml({ arrayBuffer }) 27 | 28 | return { 29 | text: result.value, 30 | html: result.value, 31 | metadata: { 32 | title: file.name, 33 | lastModified: new Date(file.lastModified).toISOString(), 34 | } 35 | } 36 | } catch (error: any) { 37 | throw new Error(`Word文档解析错误: ${error.message}`) 38 | } 39 | } 40 | 41 | /** 42 | * 完整的Word处理流程 43 | * @param file Word文件对象 44 | * @returns 处理后的文本 markdown格式 45 | */ 46 | public static async processForLLM(file: File): Promise { 47 | const { text } = await this.parseWord(file) 48 | return htmlToMarkdown(text) 49 | } 50 | } -------------------------------------------------------------------------------- /src/renderer/src/store/api/ipc/ipc.ts: -------------------------------------------------------------------------------- 1 | import { IpcCore } from './core' 2 | 3 | export class Ipc { 4 | private core = new IpcCore() 5 | constructor() { 6 | this.getSettings() 7 | } 8 | getSettings() { 9 | this.core.invoke('getSettings').then((res) => { 10 | console.log('res', res) 11 | }) 12 | } 13 | maxWindow() { 14 | this.core.invoke('maxWindow') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/src/store/llm/provider/struct.ts: -------------------------------------------------------------------------------- 1 | import { IMessageModel } from 'types/model' 2 | import { CompletionOptions, ModelConfig } from '../type' 3 | 4 | abstract class BaseModel { 5 | abstract config: ModelConfig 6 | abstract completion( 7 | messages: IMessageModel[], 8 | opts?: CompletionOptions 9 | ): Promise<[string, T]> 10 | abstract completionStream(messages: IMessageModel[], opts: StreamPipeOptions): Promise 11 | } 12 | 13 | export { BaseModel } 14 | -------------------------------------------------------------------------------- /src/renderer/src/store/llm/type.d.ts: -------------------------------------------------------------------------------- 1 | export type ModelConfig = Pick & { 2 | model: string 3 | } 4 | type ModelOptions = { 5 | temperature?: number 6 | top_p?: number 7 | presence_penalty?: number 8 | frequency_penalty?: number 9 | } 10 | type BaseCompletionOptions = { 11 | signal?: AbortSignal 12 | enable_search?: boolean 13 | timeout?: number 14 | max_tokens?: number 15 | modelOptions?: ModelOptions 16 | } 17 | export type StreamOptions = BaseCompletionOptions & { 18 | onError?: (code: string, message: string, e?: Error) => void 19 | onFinish?: (content: string) => void 20 | onChunk?: (fullText: string, text: string) => void 21 | onReasoning?: (reasoning: string) => void 22 | modelOptions?: ModelOptions 23 | } 24 | 25 | export type CompletionOptions = BaseCompletionOptions & {} 26 | -------------------------------------------------------------------------------- /src/renderer/src/store/note/TabCtx.ts: -------------------------------------------------------------------------------- 1 | import { useContext, createContext } from 'react' 2 | import { TabStore } from './tab' 3 | 4 | export const TabContext = createContext({} as any) 5 | 6 | export const useTab = () => { 7 | return useContext(TabContext) 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/src/store/struct.ts: -------------------------------------------------------------------------------- 1 | import { observable, runInAction, isObservable } from 'mobx' 2 | 3 | export class StructStore { 4 | state: T 5 | constructor(state: T) { 6 | if (isObservable(state)) { 7 | this.state = state 8 | } else { 9 | this.state = observable(state) 10 | } 11 | } 12 | setState(ctx: Partial | ((state: T) => void)) { 13 | runInAction(() => { 14 | if (ctx instanceof Function) { 15 | ctx(this.state) 16 | } else { 17 | Object.keys(ctx).forEach((key) => { 18 | this.state[key] = ctx[key] 19 | }) 20 | } 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/src/styles/ace.css: -------------------------------------------------------------------------------- 1 | .ace-cloud_editor, 2 | .ace-cloud_editor_dark { 3 | background: none !important; 4 | } 5 | .ace_gutter { 6 | background: none !important; 7 | } 8 | .cloud_editor_dark { 9 | background: none !important; 10 | } 11 | .ace_indent-guide-active { 12 | background: none !important; 13 | } 14 | .ace_focus { 15 | .ace_gutter-cell { 16 | @apply text-black/60 dark:text-white/60; 17 | &.ace_gutter-active-line { 18 | @apply text-black/90 dark:text-white/90; 19 | } 20 | } 21 | .ace_active-line { 22 | @apply bg-gray-600/5 rounded-sm dark:bg-white/5; 23 | } 24 | } 25 | .cloud_editor_dark .ace_marker-layer .ace_active-line { 26 | background: none; 27 | } 28 | .ace_focus.cloud_editor_dark .ace_marker-layer .ace_active-line { 29 | @apply bg-gray-600/5 rounded-sm dark:bg-white/5; 30 | } 31 | .ace_cursor { 32 | height: 16px !important; 33 | position: relative; 34 | top: 3px; 35 | } 36 | .ace_gutter-cell { 37 | @apply text-black/60 dark:text-white/60; 38 | background: none !important; 39 | } 40 | .ace_gutter-active-line, 41 | .ace_active-line { 42 | border: none !important; 43 | } 44 | .ace-tm { 45 | background: none !important; 46 | } 47 | .ace-el { 48 | padding: 2px 0; 49 | position: relative; 50 | margin-bottom: 0.5em; 51 | } 52 | .ace-container { 53 | @apply border border-black/10 dark:border-gray-200/10 pt-8 pb-2 rounded relative; 54 | margin-bottom: 0.5em; 55 | &.frontmatter { 56 | &:before { 57 | top: 3px; 58 | content: 'Front Matter'; 59 | width: 100%; 60 | height: 22px; 61 | line-height: 21px; 62 | position: absolute; 63 | z-index: 10; 64 | left: 0; 65 | padding-left: 10px; 66 | font-size: 12px; 67 | @apply dark:text-white/60 text-black/60; 68 | } 69 | } 70 | } 71 | .ace_hidden-cursors { 72 | display: none !important; 73 | } 74 | .match-text, 75 | .match-current { 76 | position: absolute; 77 | width: auto; 78 | } 79 | 80 | .match-text { 81 | @apply bg-black/15 dark:bg-white/15 rounded-sm; 82 | } 83 | 84 | .match-current { 85 | @apply bg-sky-500 rounded-sm; 86 | } 87 | -------------------------------------------------------------------------------- /src/renderer/src/styles/animate.css: -------------------------------------------------------------------------------- 1 | @keyframes hide { 2 | from { 3 | opacity: 1; 4 | } 5 | to { 6 | opacity: 0; 7 | } 8 | } 9 | .animate-hide { 10 | animation: hide 0.2s forwards ease; 11 | } 12 | 13 | @keyframes show { 14 | from { 15 | opacity: 0; 16 | } 17 | to { 18 | opacity: 1; 19 | } 20 | } 21 | .animate-show { 22 | animation: show 0.2s forwards ease; 23 | } 24 | 25 | @keyframes blink { 26 | 0% { 27 | opacity: 1; 28 | } 29 | 50% { 30 | opacity: 0; 31 | } 32 | 100% { 33 | opacity: 1; 34 | } 35 | } 36 | .animate-blink { 37 | animation: blink 1s infinite ease-in-out; 38 | } 39 | -------------------------------------------------------------------------------- /src/renderer/src/styles/chat.css: -------------------------------------------------------------------------------- 1 | .chat-input-mask { 2 | height: 30px; 3 | background: var(--chat-bg); 4 | position: absolute; 5 | top: -30px; 6 | width: 100%; 7 | left: 0; 8 | z-index: 10; 9 | /* 使用 mask-image 创建从透明到不透明的渐变 */ 10 | -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 100%); 11 | mask-image: linear-gradient(to bottom, transparent 0%, black 100%); 12 | pointer-events: none; /* 允许鼠标事件穿透遮罩层 */ 13 | } 14 | .chat { 15 | background-color: var(--chat-bg); 16 | @apply flex flex-col h-full relative; 17 | } 18 | .chat-list { 19 | display: flex; 20 | justify-content: center; 21 | opacity: 0; 22 | > div { 23 | max-width: 780px; 24 | } 25 | } 26 | .chat-input { 27 | max-width: 760px; 28 | } 29 | 30 | .chat-user-message { 31 | background-color: var(--chat-user-message-bg-color); 32 | border-radius: 24px 4px 24px 24px; 33 | } 34 | .chat-list .ai-message .last { 35 | display: none; 36 | } 37 | .chat-list .ai-message:last-child { 38 | min-height: calc(100vh - 260px); 39 | .last { 40 | display: flex; 41 | } 42 | } 43 | 44 | .chat-list .user-message .last { 45 | display: none; 46 | } 47 | .chat-list .user-message:nth-last-child(2) { 48 | .last { 49 | display: flex; 50 | } 51 | } 52 | 53 | .ai-msg-actions { 54 | opacity: 0; 55 | transition: opacity 0.3s ease-in-out; 56 | pointer-events: none; 57 | } 58 | 59 | .ai-message-content:hover .ai-msg-actions { 60 | opacity: 1; 61 | pointer-events: auto; 62 | } 63 | 64 | .pending .ai-message:last-child .ai-msg-actions { 65 | pointer-events: none; 66 | } 67 | .switch-model-menu { 68 | .ant-dropdown-menu-item { 69 | padding: 5px 10px !important; 70 | } 71 | .ant-dropdown-menu-item-group-title { 72 | padding: 5px 10px !important; 73 | } 74 | } 75 | 76 | .user-message, 77 | .ai-message { 78 | transform: translateZ(0); 79 | will-change: transform; 80 | contain: content; 81 | font-size: 15px; 82 | } 83 | 84 | .message-markdown > div > :first-child { 85 | margin-top: 5px !important; 86 | } 87 | 88 | .ai-message { 89 | scroll-margin: 100px; 90 | } 91 | -------------------------------------------------------------------------------- /src/renderer/src/styles/index.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @custom-variant dark (&:where(.dark, .dark *)); 3 | @import './animate.css'; 4 | @import './color.css'; 5 | @import './chat.css'; 6 | @import './cover.css'; 7 | @import './ace.css'; 8 | @import './editor.css'; 9 | .ant-tabs-tab { 10 | margin-block: 0 !important; 11 | } 12 | body, 13 | html { 14 | overscroll-behavior: none; 15 | background-color: transparent !important; 16 | background-color: var(--primary-bg-color) !important; 17 | -webkit-font-smoothing: antialiased; 18 | } 19 | 20 | body:not(.webview) { 21 | height: 100vh; 22 | overflow: hidden !important; 23 | } 24 | 25 | .action { 26 | @apply rounded duration-200 hover:bg-white/10 cursor-pointer; 27 | } 28 | 29 | .panel-bg { 30 | @apply dark:bg-[rgba(105,81,81,0.95)] bg-white/95 backdrop-blur; 31 | } 32 | 33 | .action-icon { 34 | @apply rounded-full duration-150 dark:hover:bg-white/15 cursor-pointer dark:bg-white/10; 35 | } 36 | .drag-nav { 37 | app-region: drag; 38 | } 39 | 40 | .drag-none { 41 | app-region: no-drag; 42 | } 43 | 44 | .side-move-transition { 45 | transition: 0.2s ease-in-out; 46 | } 47 | body.drag-sidebar { 48 | user-select: none; 49 | .side-move-transition { 50 | transition: none; 51 | } 52 | } 53 | 54 | .nav-action { 55 | @apply flex items-center justify-center w-[26px] h-[26px] rounded dark:hover:bg-white/10 hover:bg-black/5 duration-200 cursor-pointer dark:stroke-white/90 stroke-black/90; 56 | } 57 | -------------------------------------------------------------------------------- /src/renderer/src/types/ai.d.ts: -------------------------------------------------------------------------------- 1 | import { ChatMessage } from '@lobehub/ui' 2 | import { IChat, MessageRole } from 'types/model' 3 | 4 | export type IMessageProps = ChatMessage & { 5 | editableContent: ReactNode 6 | reasoning?: string 7 | duration?: number 8 | } 9 | 10 | export type IChatTable = Pick< 11 | IChat, 12 | | 'id' 13 | | 'topic' 14 | | 'created' 15 | | 'updated' 16 | | 'promptId' 17 | | 'websearch' 18 | | 'model' 19 | | 'clientId' 20 | | 'summaryIndex' 21 | | 'summary' 22 | > 23 | -------------------------------------------------------------------------------- /src/renderer/src/types/ipc.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | __ipcRendererCallback: (callbackId: string, response: any) => void 4 | webkit: { 5 | messageHandlers: { 6 | ipcHandler: { 7 | postMessage: (payload: any) => void 8 | } 9 | } 10 | } 11 | ipcEvents: Record void> 12 | } 13 | } 14 | 15 | export {} 16 | -------------------------------------------------------------------------------- /src/renderer/src/types/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'turndown-plugin-gfm' { 2 | const gfm: any 3 | export { gfm } 4 | } 5 | -------------------------------------------------------------------------------- /src/renderer/src/ui/Entry.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@/store/store' 2 | import { SideBar } from './sidebar/SideBar' 3 | import { Nav } from './Nav' 4 | import { Chat } from './chat/Chat' 5 | import { observer } from 'mobx-react-lite' 6 | import { ConfirmDialog } from './dialog/ConfirmDialog' 7 | import { EditFolderDialog } from './sidebar/tree/EditFolderDialog' 8 | import { EditSpace } from './space/EditSpace' 9 | import { Note } from '@/editor/Note' 10 | import { Settings } from './settings/Settings' 11 | import { ExportSpace } from './space/ExportSpace' 12 | import { ImportFolder } from './space/ImportFolder' 13 | import { SpaceFiles } from './space/Files' 14 | const Entry = observer(() => { 15 | const store = useStore() 16 | return ( 17 |
    18 |
    21 | 22 |
    23 |
    26 |
    33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
    42 | ) 43 | }) 44 | 45 | export default Entry 46 | -------------------------------------------------------------------------------- /src/renderer/src/ui/chat/ChatItem/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2 } from 'lucide-react' 2 | import { memo } from 'react' 3 | import { Flexbox } from 'react-layout-kit' 4 | 5 | import { useStyles } from '../style' 6 | import { ChatItemProps } from '../type' 7 | import { Icon } from '@lobehub/ui' 8 | 9 | export interface LoadingProps { 10 | loading?: ChatItemProps['loading'] 11 | placement?: ChatItemProps['placement'] 12 | } 13 | 14 | const Loading = memo(({ loading, placement }) => { 15 | const { styles } = useStyles({ placement }) 16 | 17 | if (!loading) return null 18 | 19 | return ( 20 | 21 | 22 | 23 | ) 24 | }) 25 | 26 | export default Loading 27 | -------------------------------------------------------------------------------- /src/renderer/src/ui/chat/ChatItem/components/MessageContent.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from 'react' 2 | import { Flexbox } from 'react-layout-kit' 3 | import { MarkdownProps } from '@lobehub/ui' 4 | import { Reasoning } from '../../message/Reasion' 5 | import EditableMessage from './EditableMessage' 6 | import { IMessage } from 'types/model' 7 | import { observer } from 'mobx-react-lite' 8 | 9 | export interface MessageContentProps { 10 | fontSize?: number 11 | markdownProps?: Omit 12 | message?: ReactNode 13 | reasoning?: string 14 | duration?: number 15 | } 16 | 17 | const MessageContent = observer<{ msg: IMessage }>(({ msg }) => { 18 | return ( 19 | 20 |
    21 | {!!msg.reasoning && ( 22 | 27 | )} 28 | 29 |
    30 |
    31 | ) 32 | }) 33 | 34 | export default MessageContent 35 | -------------------------------------------------------------------------------- /src/renderer/src/ui/chat/ChatItem/index.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | 3 | import { IMessage } from 'types/model' 4 | import { UserMessage } from './UserMessage' 5 | import { AiMessage } from './AiMessage' 6 | import { observer } from 'mobx-react-lite' 7 | 8 | const ChatItem = observer<{ msg: IMessage }>(({ msg }) => { 9 | if (msg.role === 'user') { 10 | return 11 | } 12 | if (msg.role === 'assistant') { 13 | return 14 | } 15 | }) 16 | 17 | export default ChatItem 18 | 19 | export type { ChatItemProps } from './type' 20 | -------------------------------------------------------------------------------- /src/renderer/src/ui/chat/Empty.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@/store/store' 2 | import { GlobalOutlined } from '@ant-design/icons' 3 | import { Button } from 'antd' 4 | import { observer } from 'mobx-react-lite' 5 | import { useMemo } from 'react' 6 | import { useTranslation } from 'react-i18next' 7 | 8 | export const ChatEmpty = observer(() => { 9 | const store = useStore() 10 | const { t } = useTranslation() 11 | const greeting = useMemo(() => { 12 | const hour = new Date().getHours() 13 | if (hour < 12 && hour > 4) { 14 | return t('chat.greeting.morning') 15 | } else if (hour > 12 && hour < 19) { 16 | return t('chat.greeting.afternoon') 17 | } else { 18 | return t('chat.greeting.evening') 19 | } 20 | }, [t]) 21 | return ( 22 |
    23 |
    24 |
    ChatBot, {greeting}
    25 | {!store.settings.state.models.length && ( 26 | <> 27 |
    {t('chat.chatbot_intro')}
    28 |
    29 | 42 |
    43 | 44 | )} 45 |
    46 |
    47 | ) 48 | }) 49 | -------------------------------------------------------------------------------- /src/renderer/src/ui/chat/ModelIcon.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Claude, 3 | DeepSeek, 4 | Gemini, 5 | LmStudio, 6 | Ollama, 7 | OpenAI, 8 | OpenRouter, 9 | Qwen 10 | } from '@lobehub/icons' 11 | import { memo } from 'react' 12 | 13 | export const ModelIcon = memo(({ mode, size }: { mode: string; size: number }) => { 14 | if (mode === 'openai') { 15 | return 16 | } 17 | if (mode === 'claude') { 18 | return 19 | } 20 | if (mode === 'ollama') { 21 | return 22 | } 23 | if (mode === 'lmstudio') { 24 | return 25 | } 26 | if (mode === 'qwen') { 27 | return 28 | } 29 | if (mode === 'deepseek') { 30 | return 31 | } 32 | if (mode === 'gemini') { 33 | return 34 | } 35 | if (mode === 'openrouter') { 36 | return 37 | } 38 | return null 39 | }) 40 | -------------------------------------------------------------------------------- /src/renderer/src/ui/chat/ViewList.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from 'mobx-react-lite' 2 | import ChatItem from './ChatItem' 3 | import { IChat } from 'types/model' 4 | 5 | export const ChatViewList = observer(({ chat }: { chat: IChat }) => { 6 | return ( 7 |
    8 |
    {chat.messages?.map((m) => )}
    9 |
    10 | ) 11 | }) 12 | -------------------------------------------------------------------------------- /src/renderer/src/ui/chat/message/BubbleLoading.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from '@lobehub/icons'; 2 | import { css, cx, useTheme } from 'antd-style'; 3 | import { forwardRef, memo } from 'react'; 4 | import { Center } from 'react-layout-kit'; 5 | 6 | const container = css` 7 | circle { 8 | animation: bubble 1.5s cubic-bezier(0.05, 0.2, 0.35, 1) infinite; 9 | 10 | &:nth-child(2) { 11 | animation-delay: 0.3s; 12 | } 13 | 14 | &:nth-child(3) { 15 | animation-delay: 0.6s; 16 | } 17 | } 18 | 19 | @keyframes bubble { 20 | 0% { 21 | opacity: 1; 22 | } 23 | 24 | 25% { 25 | opacity: 0.5; 26 | } 27 | 28 | 75% { 29 | opacity: 0.25; 30 | } 31 | 32 | to { 33 | opacity: 1; 34 | } 35 | } 36 | `; 37 | 38 | const BubblesLoadingIcon: IconType = forwardRef( 39 | ({ size = '1em', style, className, ...rest }, ref) => { 40 | return ( 41 | 52 | 53 | 54 | 55 | 56 | ); 57 | }, 58 | ); 59 | 60 | const BubblesLoading = memo(() => { 61 | const theme = useTheme(); 62 | return ( 63 |
    64 | 65 |
    66 | ); 67 | }); 68 | 69 | export default BubblesLoading; 70 | -------------------------------------------------------------------------------- /src/renderer/src/ui/chat/message/Default.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, memo } from 'react' 2 | import BubblesLoading from './BubbleLoading' 3 | 4 | export const DefaultMessage = memo<{ 5 | editableContent: ReactNode 6 | id: string 7 | }>(({ editableContent, id }) => { 8 | if (editableContent === '...') return 9 | 10 | return
    {editableContent}
    11 | }) 12 | -------------------------------------------------------------------------------- /src/renderer/src/ui/common/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { InfoCircleOutlined } from '@ant-design/icons' 3 | 4 | interface Props { 5 | fallback: (e: any) => ReactNode 6 | children: ReactNode 7 | } 8 | export class ErrorBoundary extends React.Component { 9 | state = { 10 | hasError: false, 11 | error: null as null | Error 12 | } 13 | constructor(props: Props) { 14 | super(props) 15 | this.state = { hasError: false, error: null } 16 | } 17 | 18 | static getDerivedStateFromError(error: any) { 19 | return { hasError: true, error } 20 | } 21 | 22 | componentDidCatch(error: any, info: any) { 23 | console.error('error', error, info) 24 | } 25 | 26 | render() { 27 | if (this.state.hasError) { 28 | return this.props.fallback(this.state.error) 29 | } 30 | return this.props.children 31 | } 32 | } 33 | 34 | export function ErrorFallback(props: { error: any }) { 35 | let message = '' 36 | if (props.error instanceof Error) message = props.error.message 37 | if (typeof props.error === 'string') message = props.error 38 | return ( 39 |
    40 |
    41 |
    42 | 43 | Oops, something went wrong. 44 |
    45 |
    system: {message}
    46 |
    47 | { 50 | window.open('https://github.com/1943time/inkdown/issues') 51 | }} 52 | > 53 | Send report 54 | 55 | | 56 | location.reload()}> 57 | Reload 58 | 59 |
    60 |
    61 |
    62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /src/renderer/src/ui/common/HelpText.tsx: -------------------------------------------------------------------------------- 1 | import { memo, ReactNode } from 'react' 2 | import { Tooltip } from 'antd' 3 | import { CircleHelp } from 'lucide-react' 4 | 5 | export const TextHelp = memo((props: { text: string | ReactNode }) => { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | }) 12 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Code/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import Pre, { PreMermaid, PreSingleLine } from './Pre'; 2 | import { FC } from 'react'; 3 | 4 | const countLines = (str: string): number => { 5 | const regex = /\n/g; 6 | const matches = str.match(regex); 7 | return matches ? matches.length : 1; 8 | }; 9 | 10 | export const useCode = (raw: any) => { 11 | if (!raw) return; 12 | 13 | const { children, className } = raw.props; 14 | 15 | if (!children) return; 16 | 17 | const content = Array.isArray(children) ? (children[0] as string) : children; 18 | 19 | const lang = className?.replace('language-', '') || 'txt'; 20 | 21 | const isSingleLine = countLines(content) <= 1 && content.length <= 32; 22 | 23 | return { 24 | content, 25 | isSingleLine, 26 | lang, 27 | }; 28 | }; 29 | 30 | interface CodeBlockProps { 31 | children: any; 32 | enableMermaid?: boolean; 33 | fullFeatured?: boolean; 34 | } 35 | 36 | const CodeBlock: FC = ({ 37 | fullFeatured, 38 | enableMermaid, 39 | children, 40 | ...rest 41 | }) => { 42 | const code = useCode(children); 43 | 44 | if (!code) return; 45 | 46 | if (enableMermaid && code.lang === 'mermaid') 47 | return ( 48 | 49 | {code.content} 50 | 51 | ); 52 | 53 | if (code.isSingleLine) 54 | return {code.content}; 55 | 56 | return ( 57 |
    58 |       {code.content}
    59 |     
    60 | ); 61 | }; 62 | 63 | export const CodeLite: FC = (props) => { 64 | return ; 65 | } -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Code/Pre.tsx: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style' 2 | import { FC } from 'react' 3 | 4 | import Highlighter, { type HighlighterProps } from './Highlighter' 5 | import Mermaid, { type MermaidProps } from '../Mermaid' 6 | import Snippet, { type SnippetProps } from '../Snippet' 7 | import { FALLBACK_LANG } from './SyntaxHighlighter' 8 | 9 | const useStyles = createStyles(({ css }) => ({ 10 | container: css` 11 | overflow: hidden; 12 | margin-block: 1em; 13 | border-radius: calc(var(--lobe-markdown-border-radius) * 1px); 14 | box-shadow: 0 0 0 1px var(--lobe-markdown-border-color); 15 | ` 16 | })) 17 | 18 | export type PreProps = HighlighterProps 19 | 20 | export const Pre: FC = ({ 21 | fullFeatured, 22 | fileName, 23 | language = FALLBACK_LANG, 24 | children, 25 | ...rest 26 | }) => { 27 | const { styles, cx } = useStyles() 28 | 29 | return ( 30 | 37 | {children} 38 | 39 | ) 40 | } 41 | 42 | export const PreSingleLine: FC = ({ 43 | language = FALLBACK_LANG, 44 | children, 45 | ...rest 46 | }) => { 47 | const { cx, styles } = useStyles() 48 | 49 | return ( 50 | 51 | {children} 52 | 53 | ) 54 | } 55 | 56 | export const PreMermaid: FC = ({ children, type, ...rest }) => { 57 | const { styles, cx } = useStyles() 58 | 59 | return ( 60 | 61 | {children} 62 | 63 | ) 64 | } 65 | 66 | export default Pre 67 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Code/SyntaxHighlighter.tsx: -------------------------------------------------------------------------------- 1 | import { memo, useEffect, useMemo, useState } from 'react' 2 | import { bundledLanguages, codeToHtml } from 'shiki' 3 | 4 | import { useStyles } from './style' 5 | import { useThemeMode } from 'antd-style' 6 | export const FALLBACK_LANG = 'txt' 7 | 8 | const langMap = new Set(Object.keys(bundledLanguages)) 9 | export interface SyntaxHighlighterProps { 10 | children: string 11 | enableTransformer?: boolean 12 | language: string 13 | } 14 | 15 | const SyntaxHighlighter = memo(({ children, language }) => { 16 | const { styles, cx } = useStyles() 17 | const { isDarkMode } = useThemeMode() 18 | const lang = language?.toLowerCase() 19 | const [dom, setDom] = useState('') 20 | const matchedLanguage = useMemo( 21 | () => (langMap.has(language as any) ? language : FALLBACK_LANG), 22 | [language] 23 | ) 24 | useEffect(() => { 25 | codeToHtml(children, { 26 | lang: matchedLanguage, 27 | theme: isDarkMode ? 'plastic' : 'github-light' 28 | }).then((res) => { 29 | setDom(res) 30 | }) 31 | }, [children, lang, isDarkMode]) 32 | return ( 33 |
    40 | ) 41 | }) 42 | 43 | export default SyntaxHighlighter 44 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Mermaid/FullFeatured.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronDown, ChevronRight } from 'lucide-react' 2 | import { ReactNode, memo, useState } from 'react' 3 | import { Flexbox } from 'react-layout-kit' 4 | 5 | import { MermaidProps } from './type' 6 | import { ActionIcon, CopyButton } from '@lobehub/ui' 7 | 8 | export const MermaidFullFeatured = memo< 9 | Omit & { children: ReactNode; content: string } 10 | >(({ showLanguage, content, children, className, ...rest }) => { 11 | const [expand, setExpand] = useState(true) 12 | return ( 13 |
    18 | 24 | setExpand(!expand)} 27 | size={{ blockSize: 24, size: 14, strokeWidth: 3 }} 28 | /> 29 | {showLanguage && ( 30 | 37 | mermaid 38 | 39 | )} 40 | 41 | 42 | 43 | 44 |
    {children}
    45 |
    46 | ) 47 | }) 48 | 49 | export default MermaidFullFeatured 50 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Mermaid/index.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | 3 | import FullFeatured from './FullFeatured' 4 | import { MermaidProps } from './type' 5 | import { useMermaid } from './useMermaid' 6 | 7 | const Mermaid = memo( 8 | ({ children, showLanguage = true, type = 'block', className, bodyRender, ...rest }) => { 9 | const tirmedChildren = children.trim() 10 | const MermaidRender = useMermaid(tirmedChildren) 11 | 12 | return ( 13 | 21 | 22 | 23 | ) 24 | } 25 | ) 26 | 27 | export default Mermaid 28 | 29 | export { type MermaidProps } from './type' 30 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Mermaid/type.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | export interface MermaidProps { 4 | bodyRender?: (props: { content: string; originalNode: ReactNode }) => ReactNode 5 | children: string 6 | showLanguage?: boolean 7 | className?: string 8 | type?: 'ghost' | 'block' | 'pure' 9 | } 10 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Mermaid/useMermaid.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from 'antd-style' 2 | import mermaid from 'mermaid' 3 | import { useCallback, useEffect, useState } from 'react' 4 | 5 | export const useMermaid = (content: string) => { 6 | const [mermaidContent, setMermaidContent] = useState() 7 | const theme = useTheme() 8 | useEffect(() => { 9 | mermaid.initialize({ 10 | fontFamily: theme.fontFamilyCode, 11 | securityLevel: 'loose', 12 | startOnLoad: true, 13 | theme: theme.isDarkMode ? 'dark' : 'base', 14 | themeVariables: { 15 | errorBkgColor: theme.colorError, 16 | errorTextColor: theme.colorText, 17 | fontSize: 14, 18 | lineColor: theme.colorText, 19 | primaryBorderColor: theme.colorPrimaryBorder, 20 | primaryColor: theme.colorPrimaryBg, 21 | primaryTextColor: theme.colorText, 22 | secondaryColor: theme.colorInfo, 23 | tertiaryColor: theme.colorSuccess 24 | } 25 | }) 26 | mermaid.contentLoaded() 27 | }, [mermaidContent, theme.isDarkMode]) 28 | 29 | const checkSyntax = async (textStr: string) => { 30 | try { 31 | if (await mermaid.parse(textStr)) { 32 | setMermaidContent(textStr) 33 | } 34 | } catch {} 35 | } 36 | 37 | useEffect(() => { 38 | checkSyntax(content) 39 | }, [content]) 40 | 41 | return useCallback(() => { 42 | return ( 43 |
    53 |         {mermaidContent}
    54 |       
    55 | ) 56 | }, [mermaidContent, theme.isDarkMode]) 57 | } 58 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Snippet/Splotlight.tsx: -------------------------------------------------------------------------------- 1 | import { memo, useEffect, useRef, useState } from 'react' 2 | import { DivProps } from '@lobehub/ui' 3 | import { useStyles } from './styleSplotlight' 4 | 5 | const useMouseOffset = (): any => { 6 | const [offset, setOffset] = useState<{ x: number; y: number }>() 7 | const [outside, setOutside] = useState(true) 8 | const reference = useRef(null) 9 | 10 | useEffect(() => { 11 | if (reference.current && reference.current.parentElement) { 12 | const element = reference.current.parentElement 13 | 14 | // debounce? 15 | const onMouseMove = (e: MouseEvent) => { 16 | const bound = element.getBoundingClientRect() 17 | setOffset({ x: e.clientX - bound.x, y: e.clientY - bound.y }) 18 | setOutside(false) 19 | } 20 | 21 | const onMouseLeave = () => { 22 | setOutside(true) 23 | } 24 | element.addEventListener('mousemove', onMouseMove) 25 | element.addEventListener('mouseleave', onMouseLeave) 26 | return () => { 27 | element.removeEventListener('mousemove', onMouseMove) 28 | element.removeEventListener('mouseleave', onMouseLeave) 29 | } 30 | } 31 | }, []) 32 | 33 | return [offset, outside, reference] as const 34 | } 35 | 36 | export interface SpotlightProps extends DivProps { 37 | /** 38 | * @description The size of the spotlight circle 39 | * @default 64 40 | */ 41 | size?: number 42 | } 43 | 44 | const Spotlight = memo(({ className, size = 64, ...properties }) => { 45 | const [offset, outside, reference] = useMouseOffset() 46 | const { styles, cx } = useStyles({ offset, outside, size }) 47 | 48 | return
    49 | }) 50 | 51 | export default Spotlight 52 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Snippet/index.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import { Flexbox } from 'react-layout-kit' 3 | 4 | import Spotlight from './Splotlight' 5 | 6 | import { useStyles } from './style' 7 | import { CopyButton } from '@lobehub/ui' 8 | import SyntaxHighlighter from '../Code/SyntaxHighlighter' 9 | 10 | export interface SnippetProps { 11 | /** 12 | * @description The content to be displayed inside the Snippet component 13 | */ 14 | children: string 15 | /** 16 | * @description Whether the Snippet component is copyable or not 17 | * @default true 18 | */ 19 | copyable?: boolean 20 | /** 21 | * @description The language of the content inside the Snippet component 22 | * @default 'tsx' 23 | */ 24 | language?: string 25 | /** 26 | * @description Whether add spotlight background 27 | * @default false 28 | */ 29 | spotlight?: boolean 30 | /** 31 | * @description The symbol to be displayed before the content inside the Snippet component 32 | */ 33 | symbol?: string 34 | /** 35 | * @description The type of the Snippet component 36 | * @default 'ghost' 37 | */ 38 | type?: 'ghost' | 'block' 39 | } 40 | 41 | const Snippet = memo( 42 | ({ symbol, language = 'tsx', children, copyable = true, type = 'ghost', spotlight, ...rest }) => { 43 | const { styles, cx } = useStyles(type) 44 | 45 | const tirmedChildren = children.trim() 46 | 47 | return ( 48 | 49 | {spotlight && } 50 | 51 | {[symbol, tirmedChildren].filter(Boolean).join(' ')} 52 | 53 | {copyable && } 54 | 55 | ) 56 | } 57 | ) 58 | 59 | export default Snippet 60 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Snippet/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles(({ css, cx, token, prefixCls }, type: 'ghost' | 'block') => { 4 | const typeStylish = css` 5 | background-color: ${type === 'block' ? token.colorFillTertiary : 'transparent'}; 6 | border: 1px solid ${type === 'block' ? 'transparent' : token.colorBorder}; 7 | `; 8 | 9 | return { 10 | container: cx( 11 | typeStylish, 12 | css` 13 | position: relative; 14 | 15 | overflow: hidden; 16 | 17 | max-width: 100%; 18 | height: 38px; 19 | padding-block: 0; 20 | padding-inline: 12px 8px; 21 | 22 | border-radius: ${token.borderRadius}px; 23 | 24 | transition: background-color 100ms ${token.motionEaseOut}; 25 | 26 | &:hover { 27 | background-color: ${token.colorFillTertiary}; 28 | } 29 | 30 | .${prefixCls}-highlighter-shiki { 31 | position: relative; 32 | overflow: hidden; 33 | flex: 1; 34 | } 35 | 36 | .prism-code { 37 | background: none !important; 38 | } 39 | 40 | pre { 41 | overflow: auto hidden !important; 42 | display: flex; 43 | align-items: center; 44 | 45 | width: 100%; 46 | height: 36px !important; 47 | margin: 0 !important; 48 | 49 | line-height: 1; 50 | text-wrap: nowrap !important; 51 | 52 | background: none !important; 53 | } 54 | 55 | code[class*='language-'] { 56 | background: none !important; 57 | } 58 | `, 59 | ), 60 | }; 61 | }); 62 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/Snippet/styleSplotlight.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles( 4 | ( 5 | { css, token, isDarkMode }, 6 | { offset, outside, size }: { offset: { x: number; y: number }; outside: boolean; size: number }, 7 | ) => { 8 | const spotlightX = (offset?.x ?? 0) + 'px'; 9 | const spotlightY = (offset?.y ?? 0) + 'px'; 10 | const spotlightOpacity = outside ? '0' : '.1'; 11 | const spotlightSize = size + 'px'; 12 | return css` 13 | pointer-events: none; 14 | 15 | position: absolute; 16 | z-index: 1; 17 | inset: 0; 18 | 19 | opacity: ${spotlightOpacity}; 20 | background: radial-gradient( 21 | ${spotlightSize} circle at ${spotlightX} ${spotlightY}, 22 | ${isDarkMode ? token.colorText : '#fff'}, 23 | ${isDarkMode ? 'transparent' : token.colorTextQuaternary} 24 | ); 25 | border-radius: inherit; 26 | 27 | transition: all 0.2s; 28 | `; 29 | }, 30 | ); 31 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/plugins/katexDir.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from 'unist'; 2 | import { visit } from 'unist-util-visit'; 3 | // katex-directive 4 | export const rehypeKatexDir = () => (tree: Node) => { 5 | visit(tree, 'element', (node: any) => { 6 | if (node.properties?.className?.includes('katex')) { 7 | node.properties.dir = 'ltr'; 8 | } 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/renderer/src/ui/markdown/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from 'antd-style'; 2 | 3 | export const useStyles = createStyles( 4 | ( 5 | { css, token }, 6 | { 7 | fontSize = 14, 8 | headerMultiple = 0.25, 9 | marginMultiple = 1, 10 | lineHeight = 1.6, 11 | }: { fontSize?: number; headerMultiple?: number; lineHeight?: number; marginMultiple?: number }, 12 | ) => { 13 | return { 14 | chat: css` 15 | --lobe-markdown-font-size: ${fontSize}px; 16 | --lobe-markdown-header-multiple: ${headerMultiple}; 17 | --lobe-markdown-margin-multiple: ${marginMultiple}; 18 | --lobe-markdown-line-height: ${lineHeight}; 19 | --lobe-markdown-border-radius: ${token.borderRadius}; 20 | ol, 21 | ul { 22 | li { 23 | li { 24 | &::marker { 25 | color: ${token.colorTextSecondary} !important; 26 | } 27 | } 28 | } 29 | } 30 | 31 | ul { 32 | list-style: unset; 33 | 34 | li { 35 | &::before { 36 | content: unset; 37 | display: unset; 38 | } 39 | } 40 | } 41 | `, 42 | latex: css` 43 | .katex-html { 44 | overflow: auto hidden; 45 | padding: 3px; 46 | 47 | .base { 48 | margin-block: 0; 49 | margin-inline: auto; 50 | } 51 | } 52 | 53 | .katex-html:has(span.tag) { 54 | display: flex !important; 55 | } 56 | 57 | .katex-html > .tag { 58 | position: relative !important; 59 | float: right; 60 | margin-inline-start: 0.25rem; 61 | } 62 | `, 63 | root: css` 64 | position: relative; 65 | overflow: hidden; 66 | max-width: 100%; 67 | 68 | #footnote-label { 69 | display: none; 70 | } 71 | 72 | sup:has(a[aria-describedby='footnote-label']) { 73 | vertical-align: super !important; 74 | } 75 | `, 76 | }; 77 | }, 78 | ); 79 | -------------------------------------------------------------------------------- /src/renderer/src/ui/sidebar/SideBar.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@/store/store' 2 | import { useCallback } from 'react' 3 | import { Chats } from './Chats' 4 | import { Bot, PenLine } from 'lucide-react' 5 | import { observer } from 'mobx-react-lite' 6 | import { Tree } from './tree/Tree' 7 | import { os } from '@/utils/common' 8 | export const SideBar = observer(() => { 9 | const store = useStore() 10 | const { sidePanelWidth, foldSideBar: fold, fullChatBot } = store.settings.state 11 | const move = useCallback((e: React.MouseEvent) => { 12 | const startX = e.clientX 13 | document.body.classList.add('drag-sidebar') 14 | const startWidth = store.settings.state.sidePanelWidth 15 | const move = (e: MouseEvent) => { 16 | let width = startWidth + e.clientX - startX 17 | if (width > 500) { 18 | width = 500 19 | } 20 | if (width < 200) { 21 | width = 200 22 | } 23 | store.settings.setState({ sidePanelWidth: width }) 24 | } 25 | window.addEventListener('mousemove', move) 26 | window.addEventListener( 27 | 'mouseup', 28 | () => { 29 | document.body.classList.remove('drag-sidebar') 30 | store.settings.setSetting('sidePanelWidth', store.settings.state.sidePanelWidth) 31 | window.removeEventListener('mousemove', move) 32 | }, 33 | { once: true } 34 | ) 35 | }, []) 36 | return ( 37 |
    43 |
    50 |
    51 |
    52 | 53 |
    54 |
    55 |
    56 | ) 57 | }) 58 | -------------------------------------------------------------------------------- /src/renderer/src/ui/sidebar/tree/TreeEmpty.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next' 2 | 3 | export function TreeEmpty() { 4 | const { t } = useTranslation() 5 | return ( 6 |
    7 |
    8 |
    {t('noDocuments')}
    9 |
    10 |
    11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/src/ui/space/SpaceItem.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@/store/store' 2 | import { Check, Folders } from 'lucide-react' 3 | import { ISpace } from 'types/model' 4 | 5 | export function SpaceItem(props: { item: ISpace; onClick: () => void }) { 6 | const store = useStore() 7 | return ( 8 |
    12 |
    13 | 14 | {props.item.name} 15 |
    16 | {store.note.state.nodes['root']?.id === props.item.id && ( 17 |
    18 | 19 |
    20 | )} 21 |
    22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/src/utils/ai.ts: -------------------------------------------------------------------------------- 1 | import { encode } from 'gpt-tokenizer' 2 | import dayjs from 'dayjs' 3 | 4 | export const getTokens = (text: string) => { 5 | return encode(text).length 6 | } 7 | 8 | export function findLastIndex(array: T[], callback: (item: T) => boolean) { 9 | const reversedIndex = [...array].reverse().findIndex(callback) 10 | return reversedIndex === -1 ? -1 : array.length - 1 - reversedIndex 11 | } 12 | 13 | export const modelToLabel = (model: string) => { 14 | return model 15 | .split('-') 16 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 17 | .join('-') 18 | } 19 | 20 | export const generateRandomNumber = (min: number, max: number): number => { 21 | return Math.floor(Math.random() * (max - min + 1)) + min 22 | } 23 | 24 | export const formatTime = (time: number): string => { 25 | const now = dayjs() 26 | const target = dayjs(time) 27 | 28 | if (target.isSame(now, 'day')) { 29 | return target.format('HH:mm:ss') 30 | } else if (target.isSame(now, 'year')) { 31 | return target.format('MM-DD HH:mm:ss') 32 | } else { 33 | return target.format('YYYY-MM-DD HH:mm:ss') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/renderer/src/utils/dom.tsx: -------------------------------------------------------------------------------- 1 | export const getOffsetTop = (dom: HTMLElement, target: HTMLElement = document.body) => { 2 | let top = 0 3 | while (target.contains(dom.offsetParent) && target !== dom) { 4 | top += dom.offsetTop 5 | dom = dom.offsetParent as HTMLElement 6 | } 7 | return top 8 | } 9 | 10 | export const getOffsetLeft = (dom: HTMLElement, target: HTMLElement = document.body) => { 11 | let left = 0 12 | while (target.contains(dom) && target !== dom) { 13 | left += dom.offsetLeft 14 | dom = dom.offsetParent as HTMLElement 15 | } 16 | return left 17 | } 18 | 19 | export const getDomRect = () => { 20 | try { 21 | const domSelection = window.getSelection() 22 | const domRange = domSelection?.getRangeAt(0) 23 | return domRange?.getBoundingClientRect() 24 | } catch (e) { 25 | return null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/src/utils/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next' 2 | import { initReactI18next } from 'react-i18next' 3 | import en from '../locales/en_US.json' 4 | import zh from '../locales/zh_CN.json' 5 | 6 | i18n.use(initReactI18next).init({ 7 | resources: { 8 | en: { 9 | translation: en 10 | }, 11 | zh: { 12 | translation: zh 13 | } 14 | }, 15 | fallbackLng: 'en', 16 | interpolation: { 17 | escapeValue: false 18 | } 19 | }) 20 | 21 | declare module 'i18next' { 22 | interface CustomTypeOptions { 23 | resources: { 24 | translation: typeof import('../locales/zh_CN.json') 25 | } 26 | } 27 | } 28 | export const getSystemLanguage = () => { 29 | const systemLang = navigator.language 30 | const lang = systemLang.split('-')[0] 31 | return lang === 'zh' ? 'zh' : 'en' 32 | } 33 | -------------------------------------------------------------------------------- /src/renderer/src/utils/string.ts: -------------------------------------------------------------------------------- 1 | export const getFileExtension = (name: string) => { 2 | return name.split('.').pop() 3 | } 4 | 5 | export const getFileName = (name: string) => { 6 | return name.split('.').shift() 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/worker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inkdown 6 | 7 | 11 | 21 | 22 | 23 | 24 |
    25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", 3 | "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/types/*.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["electron-vite/node"], 7 | "noImplicitReturns": false, 8 | "moduleResolution": "bundler", 9 | "paths": { 10 | "types/*": ["./src/types/*"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.web.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.web.json", 3 | "include": [ 4 | "src/renderer/src/env.d.ts", 5 | "src/renderer/src/**/*", 6 | "src/renderer/src/**/*.tsx", 7 | "src/preload/*.d.ts", 8 | "src/types/*.d.ts", 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "jsx": "react-jsx", 13 | "baseUrl": ".", 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": false, 17 | "paths": { 18 | "@/*": ["./src/renderer/src/*"], 19 | "types/*": ["./src/types/*"] 20 | } 21 | } 22 | } 23 | --------------------------------------------------------------------------------