├── .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 | 
12 |
13 | 
14 |
15 | ### Vs code
16 |
17 | 
18 |
19 | 
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/renderer/src/editor/tools/langIcons/c.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
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 |
5 |
--------------------------------------------------------------------------------
/src/renderer/src/editor/tools/langIcons/crystal.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/renderer/src/editor/tools/langIcons/csharp.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------
/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 |
2 |
--------------------------------------------------------------------------------
/src/renderer/src/editor/tools/langIcons/glsl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/renderer/src/editor/tools/langIcons/gnuplot.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
5 |
--------------------------------------------------------------------------------
/src/renderer/src/editor/tools/langIcons/objective-cpp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/renderer/src/editor/tools/langIcons/pascal.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------
/src/renderer/src/editor/tools/langIcons/stata.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/renderer/src/icons/keyboard/Option.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react'
2 | const Option = (props: SVGProps) => (
3 |
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 |
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 |
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 | }
33 | onClick={() => {
34 | store.settings.setData((state) => {
35 | state.open = true
36 | state.setTab = 2
37 | })
38 | }}
39 | >
40 | {t('chat.set_model')}
41 |
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 |
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 |
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 |
--------------------------------------------------------------------------------