├── .nvmrc
├── public
├── upload
│ └── .gitkeep
├── diagram.png
├── favicon.ico
├── mpmd
│ ├── icon-256.png
│ └── logo.svg
└── assets
│ ├── icons
│ ├── macos.png
│ ├── ubuntu.png
│ ├── windows.png
│ └── github.svg
│ └── images
│ ├── logo.png
│ ├── logo-2.png
│ ├── loading.jpg
│ ├── Snipaste_2025-03-07_14-10-08.png
│ ├── Snipaste_2025-03-07_14-11-26.png
│ ├── Snipaste_2025-03-07_14-12-20.png
│ ├── Snipaste_2025-03-07_14-12-58.png
│ └── Snipaste_2025-03-07_14-16-31.png
├── src
├── utils
│ ├── toast
│ │ └── index.ts
│ ├── MDKatex.d.ts
│ ├── fetch.ts
│ ├── tauri-fs.ts
│ └── MDKatex.js
├── vite-env.d.ts
├── components
│ ├── ui
│ │ ├── input
│ │ │ ├── index.ts
│ │ │ └── Input.vue
│ │ ├── label
│ │ │ ├── index.ts
│ │ │ └── Label.vue
│ │ ├── sonner
│ │ │ ├── index.ts
│ │ │ └── Sonner.vue
│ │ ├── switch
│ │ │ ├── index.ts
│ │ │ └── Switch.vue
│ │ ├── back-top
│ │ │ ├── index.ts
│ │ │ └── BackTop.vue
│ │ ├── textarea
│ │ │ ├── index.ts
│ │ │ └── Textarea.vue
│ │ ├── separator
│ │ │ ├── index.ts
│ │ │ └── Separator.vue
│ │ ├── popover
│ │ │ ├── index.ts
│ │ │ ├── PopoverTrigger.vue
│ │ │ ├── Popover.vue
│ │ │ └── PopoverContent.vue
│ │ ├── hover-card
│ │ │ ├── index.ts
│ │ │ ├── HoverCardTrigger.vue
│ │ │ ├── HoverCard.vue
│ │ │ └── HoverCardContent.vue
│ │ ├── tabs
│ │ │ ├── index.ts
│ │ │ ├── Tabs.vue
│ │ │ ├── TabsList.vue
│ │ │ ├── TabsContent.vue
│ │ │ └── TabsTrigger.vue
│ │ ├── tooltip
│ │ │ ├── index.ts
│ │ │ ├── TooltipTrigger.vue
│ │ │ ├── TooltipProvider.vue
│ │ │ ├── Tooltip.vue
│ │ │ └── TooltipContent.vue
│ │ ├── dialog
│ │ │ ├── DialogClose.vue
│ │ │ ├── DialogTrigger.vue
│ │ │ ├── DialogHeader.vue
│ │ │ ├── Dialog.vue
│ │ │ ├── DialogFooter.vue
│ │ │ ├── index.ts
│ │ │ ├── DialogDescription.vue
│ │ │ ├── DialogTitle.vue
│ │ │ ├── DialogScrollContent.vue
│ │ │ └── DialogContent.vue
│ │ ├── menubar
│ │ │ ├── MenubarMenu.vue
│ │ │ ├── MenubarGroup.vue
│ │ │ ├── MenubarShortcut.vue
│ │ │ ├── MenubarLabel.vue
│ │ │ ├── MenubarSub.vue
│ │ │ ├── MenubarRadioGroup.vue
│ │ │ ├── MenubarSeparator.vue
│ │ │ ├── Menubar.vue
│ │ │ ├── MenubarTrigger.vue
│ │ │ ├── index.ts
│ │ │ ├── MenubarItem.vue
│ │ │ ├── MenubarSubTrigger.vue
│ │ │ ├── MenubarRadioItem.vue
│ │ │ ├── MenubarCheckboxItem.vue
│ │ │ ├── MenubarContent.vue
│ │ │ └── MenubarSubContent.vue
│ │ ├── select
│ │ │ ├── SelectValue.vue
│ │ │ ├── SelectItemText.vue
│ │ │ ├── SelectLabel.vue
│ │ │ ├── Select.vue
│ │ │ ├── SelectSeparator.vue
│ │ │ ├── SelectGroup.vue
│ │ │ ├── index.ts
│ │ │ ├── SelectScrollUpButton.vue
│ │ │ ├── SelectScrollDownButton.vue
│ │ │ ├── SelectTrigger.vue
│ │ │ ├── SelectItem.vue
│ │ │ └── SelectContent.vue
│ │ ├── context-menu
│ │ │ ├── ContextMenuGroup.vue
│ │ │ ├── ContextMenuPortal.vue
│ │ │ ├── ContextMenuShortcut.vue
│ │ │ ├── ContextMenuTrigger.vue
│ │ │ ├── ContextMenu.vue
│ │ │ ├── ContextMenuSub.vue
│ │ │ ├── ContextMenuRadioGroup.vue
│ │ │ ├── ContextMenuSeparator.vue
│ │ │ ├── ContextMenuLabel.vue
│ │ │ ├── index.ts
│ │ │ ├── ContextMenuItem.vue
│ │ │ ├── ContextMenuSubTrigger.vue
│ │ │ ├── ContextMenuSubContent.vue
│ │ │ ├── ContextMenuRadioItem.vue
│ │ │ ├── ContextMenuCheckboxItem.vue
│ │ │ └── ContextMenuContent.vue
│ │ ├── dropdown-menu
│ │ │ ├── DropdownMenuGroup.vue
│ │ │ ├── DropdownMenuShortcut.vue
│ │ │ ├── DropdownMenuTrigger.vue
│ │ │ ├── DropdownMenu.vue
│ │ │ ├── DropdownMenuSub.vue
│ │ │ ├── DropdownMenuRadioGroup.vue
│ │ │ ├── DropdownMenuSeparator.vue
│ │ │ ├── DropdownMenuLabel.vue
│ │ │ ├── DropdownMenuItem.vue
│ │ │ ├── DropdownMenuSubTrigger.vue
│ │ │ ├── index.ts
│ │ │ ├── DropdownMenuSubContent.vue
│ │ │ ├── DropdownMenuRadioItem.vue
│ │ │ ├── DropdownMenuCheckboxItem.vue
│ │ │ └── DropdownMenuContent.vue
│ │ ├── alert-dialog
│ │ │ ├── AlertDialogTrigger.vue
│ │ │ ├── AlertDialogHeader.vue
│ │ │ ├── AlertDialog.vue
│ │ │ ├── AlertDialogFooter.vue
│ │ │ ├── AlertDialogTitle.vue
│ │ │ ├── index.ts
│ │ │ ├── AlertDialogAction.vue
│ │ │ ├── AlertDialogDescription.vue
│ │ │ ├── AlertDialogCancel.vue
│ │ │ └── AlertDialogContent.vue
│ │ ├── alert
│ │ │ ├── AlertDescription.vue
│ │ │ ├── AlertTitle.vue
│ │ │ ├── Alert.vue
│ │ │ └── index.ts
│ │ ├── number-field
│ │ │ ├── index.ts
│ │ │ ├── NumberFieldContent.vue
│ │ │ ├── NumberFieldInput.vue
│ │ │ ├── NumberField.vue
│ │ │ ├── NumberFieldDecrement.vue
│ │ │ └── NumberFieldIncrement.vue
│ │ └── button
│ │ │ ├── Button.vue
│ │ │ └── index.ts
│ ├── CodemirrorEditor
│ │ ├── EditorHeader
│ │ │ ├── EditDropdown.vue
│ │ │ ├── HelpDropdown.vue
│ │ │ ├── FileDropdown.vue
│ │ │ ├── ContactInfo.vue
│ │ │ ├── StyleOptionMenu.vue
│ │ │ ├── AboutDialog.vue
│ │ │ ├── PostTaskDialog.vue
│ │ │ └── StyleDropdown.vue
│ │ └── InsertFormDialog.vue
│ ├── FormItem.vue
│ ├── PreviewModeSwitch.vue
│ ├── RunLoading.vue
│ ├── CustomUploadForm.vue
│ └── Preview.vue
├── assets
│ ├── images
│ │ └── favicon.png
│ ├── less
│ │ ├── app.less
│ │ └── theme.less
│ ├── index.css
│ └── example
│ │ ├── theme-css.txt
│ │ └── markdown.md
├── lib
│ └── utils.ts
├── entrypoints
│ ├── popup
│ │ ├── popup.ts
│ │ ├── index.html
│ │ └── App.vue
│ └── background.ts
├── config
│ ├── index.ts
│ └── api.ts
├── types
│ ├── preview.ts
│ └── index.ts
├── main.ts
└── App.vue
├── src-tauri
├── build.rs
├── icons
│ ├── icon.ico
│ ├── icon.png
│ ├── 32x32.png
│ ├── icon.icns
│ ├── 128x128.png
│ └── 128x128@2x.png
├── .gitignore
├── src
│ ├── lib.rs
│ └── main.rs
├── capabilities
│ └── default.json
├── Cargo.toml
├── build-aux
│ ├── bundle_dmg.sh
│ └── convert_icon.cjs
└── tauri.conf.json
├── .vscode
├── extensions.json
└── settings.json
├── postcss.config.js
├── uno.config.ts
├── example
├── README.md
└── worker.js
├── tsconfig.json
├── .editorconfig
├── tsconfig.node.json
├── components.json
├── eslint.config.mjs
├── .github
├── workflows
│ └── code-review.yml
└── actions
│ ├── load-os-config
│ └── action.yml
│ ├── create-release
│ └── action.yml
│ └── process-artifacts
│ └── action.yml
├── .gitignore
├── tsconfig.app.json
├── wxt.config.ts
├── LICENSE
├── docs
└── changelog.md
├── vite.config.ts
├── README.md
├── index.html
├── tailwind.config.cjs
├── README_en.md
└── package.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20
2 |
--------------------------------------------------------------------------------
/public/upload/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/toast/index.ts:
--------------------------------------------------------------------------------
1 | export { toast } from 'vue-sonner'
2 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/ui/input/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Input } from './Input.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/label/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Label } from './Label.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/sonner/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Toaster } from './Sonner.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/switch/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Switch } from './Switch.vue'
2 |
--------------------------------------------------------------------------------
/public/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/diagram.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/components/ui/back-top/index.ts:
--------------------------------------------------------------------------------
1 | export { default as BackTop } from './BackTop.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/textarea/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Textarea } from './Textarea.vue'
2 |
--------------------------------------------------------------------------------
/src/components/ui/separator/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Separator } from './Separator.vue'
2 |
--------------------------------------------------------------------------------
/public/mpmd/icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/mpmd/icon-256.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/public/assets/icons/macos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/icons/macos.png
--------------------------------------------------------------------------------
/public/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/images/logo.png
--------------------------------------------------------------------------------
/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/src/assets/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/src/assets/images/favicon.png
--------------------------------------------------------------------------------
/public/assets/icons/ubuntu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/icons/ubuntu.png
--------------------------------------------------------------------------------
/public/assets/icons/windows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/icons/windows.png
--------------------------------------------------------------------------------
/public/assets/images/logo-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/images/logo-2.png
--------------------------------------------------------------------------------
/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/assets/images/loading.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/images/loading.jpg
--------------------------------------------------------------------------------
/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 | /gen/schemas
5 |
--------------------------------------------------------------------------------
/public/assets/images/Snipaste_2025-03-07_14-10-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/images/Snipaste_2025-03-07_14-10-08.png
--------------------------------------------------------------------------------
/public/assets/images/Snipaste_2025-03-07_14-11-26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/images/Snipaste_2025-03-07_14-11-26.png
--------------------------------------------------------------------------------
/public/assets/images/Snipaste_2025-03-07_14-12-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/images/Snipaste_2025-03-07_14-12-20.png
--------------------------------------------------------------------------------
/public/assets/images/Snipaste_2025-03-07_14-12-58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/images/Snipaste_2025-03-07_14-12-58.png
--------------------------------------------------------------------------------
/public/assets/images/Snipaste_2025-03-07_14-16-31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CrazyMrYan/md-tauri/HEAD/public/assets/images/Snipaste_2025-03-07_14-16-31.png
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/uno.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, presetMini } from 'unocss'
2 |
3 | export default defineConfig({
4 | presets: [
5 | presetMini({
6 | preflight: false,
7 | }),
8 | ],
9 | })
10 |
--------------------------------------------------------------------------------
/src-tauri/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[cfg_attr(mobile, tauri::mobile_entry_point)]
2 | pub fn run() {
3 | tauri::Builder::default()
4 | .run(tauri::generate_context!())
5 | .expect("error while running tauri application");
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/ui/popover/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Popover } from './Popover.vue'
2 | export { default as PopoverContent } from './PopoverContent.vue'
3 | export { default as PopoverTrigger } from './PopoverTrigger.vue'
4 |
--------------------------------------------------------------------------------
/src/utils/MDKatex.d.ts:
--------------------------------------------------------------------------------
1 | import type { MarkedExtension } from 'marked'
2 |
3 | export interface MarkedKatexOptions {
4 | nonStandard?: boolean
5 | }
6 |
7 | export function MDKatex(options?: MarkedKatexOptions): MarkedExtension
8 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card/index.ts:
--------------------------------------------------------------------------------
1 | export { default as HoverCard } from './HoverCard.vue'
2 | export { default as HoverCardContent } from './HoverCardContent.vue'
3 | export { default as HoverCardTrigger } from './HoverCardTrigger.vue'
4 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Example
2 |
3 | ## worker.js
4 |
5 | 公众号openapi接口代理服务示例,该项目将请求转发至微信公众号api。
6 |
7 | 开发调试:
8 |
9 | ```
10 | cd example
11 | npx wrangler dev worker.js
12 | ```
13 |
14 | 部署:
15 |
16 | 请将其部署到cloudflare workers。
17 |
--------------------------------------------------------------------------------
/src/components/ui/tabs/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Tabs } from './Tabs.vue'
2 | export { default as TabsContent } from './TabsContent.vue'
3 | export { default as TabsList } from './TabsList.vue'
4 | export { default as TabsTrigger } from './TabsTrigger.vue'
5 |
--------------------------------------------------------------------------------
/src/entrypoints/popup/popup.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 |
4 | import 'virtual:uno.css'
5 |
6 | /* 每个页面公共css */
7 | import '@/assets/index.css'
8 | import '@/assets/less/theme.less'
9 |
10 | createApp(App).mount(`#app`)
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "references": [
3 | {
4 | "path": "./tsconfig.app.json"
5 | },
6 | {
7 | "path": "./tsconfig.node.json"
8 | }
9 | ],
10 | "files": [],
11 | "compilerOptions": {
12 | "types": ["node", "@tauri-apps/api"]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-tauri/capabilities/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../gen/schemas/desktop-schema.json",
3 | "identifier": "default",
4 | "description": "enables the default permissions",
5 | "windows": [
6 | "main"
7 | ],
8 | "permissions": [
9 | "core:default"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Tooltip } from './Tooltip.vue'
2 | export { default as TooltipContent } from './TooltipContent.vue'
3 | export { default as TooltipProvider } from './TooltipProvider.vue'
4 | export { default as TooltipTrigger } from './TooltipTrigger.vue'
5 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogClose.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarMenu.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectValue.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarGroup.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src-tauri/src/main.rs:
--------------------------------------------------------------------------------
1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!!
2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3 |
4 | fn main() {
5 | tauri::Builder::default()
6 | .run(tauri::generate_context!())
7 | .expect("error while running tauri application");
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/ui/popover/PopoverTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectItemText.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/TooltipTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/TooltipProvider.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuGroup.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card/HoverCardTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuPortal.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuGroup.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogTrigger.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | max_line_length = 80
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | max_line_length = 0
15 | trim_trailing_whitespace = false
16 |
17 | [COMMIT_EDITMSG]
18 | max_line_length = 0
19 |
--------------------------------------------------------------------------------
/src/components/ui/alert/AlertDescription.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/alert/AlertTitle.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "strict": true,
8 | "noEmit": true,
9 | "allowSyntheticDefaultImports": true,
10 | "skipLibCheck": true
11 | },
12 | "include": ["vite.config.ts"]
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/ui/number-field/index.ts:
--------------------------------------------------------------------------------
1 | export { default as NumberField } from './NumberField.vue'
2 | export { default as NumberFieldContent } from './NumberFieldContent.vue'
3 | export { default as NumberFieldDecrement } from './NumberFieldDecrement.vue'
4 | export { default as NumberFieldIncrement } from './NumberFieldIncrement.vue'
5 | export { default as NumberFieldInput } from './NumberFieldInput.vue'
6 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarShortcut.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogHeader.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuShortcut.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogHeader.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/entrypoints/popup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 公众号内容编辑器
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuTrigger.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/Dialog.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://shadcn-vue.com/schema.json",
3 | "style": "default",
4 | "typescript": true,
5 | "tsConfigPath": "./tsconfig.json",
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/assets/index.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "framework": "vite",
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import antfu from '@antfu/eslint-config'
2 |
3 | export default antfu({
4 | vue: true,
5 | unocss: true,
6 | typescript: true,
7 | formatters: true,
8 | ignores: [`.github`, `scripts`, `docker`, `src/assets`, `example`],
9 | }, {
10 | rules: {
11 | 'semi': [`error`, `never`],
12 | 'quotes': [`error`, `backtick`],
13 | 'no-unused-vars': `off`,
14 | 'no-console': `off`,
15 | 'no-debugger': `off`,
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/tabs/Tabs.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/Tooltip.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api'
2 | export * from './style'
3 | export * from './theme'
4 |
5 | export const prefix = `MD`
6 |
7 | const isMac = /Mac/i.test(navigator.userAgent)
8 |
9 | export const ctrlKey = isMac ? `Cmd` : `Ctrl`
10 | export const altKey = `Alt`
11 | export const shiftKey = `Shift`
12 |
13 | export const ctrlSign = isMac ? `⌘` : `Ctrl`
14 | export const altSign = isMac ? `⌥` : `Alt`
15 | export const shiftSign = isMac ? `⇧` : `Shift`
16 |
--------------------------------------------------------------------------------
/src/components/ui/alert/Alert.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogFooter.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/ui/number-field/NumberFieldContent.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectLabel.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialog.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card/HoverCard.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/select/Select.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/ui/popover/Popover.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/types/preview.ts:
--------------------------------------------------------------------------------
1 | export enum PreviewMode {
2 | Adaptive = 'adaptive', // 自适应宽度
3 | Mobile = 'mobile', // 手机预览
4 | FullScreen = 'full' // 全屏预览
5 | }
6 |
7 | export interface PreviewConfig {
8 | mode: PreviewMode;
9 | mobileWidth: number; // 手机预览宽度
10 | adaptiveWidth: number; // 自适应预览最大宽度
11 | }
12 |
13 | export const DEFAULT_PREVIEW_CONFIG: PreviewConfig = {
14 | mode: PreviewMode.Adaptive,
15 | mobileWidth: 375, // iPhone 标准宽度
16 | adaptiveWidth: 800 // 默认最大宽度
17 | };
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogFooter.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenu.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarLabel.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenu.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuSub.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuSub.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarSub.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarRadioGroup.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuRadioGroup.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Dialog } from './Dialog.vue'
2 | export { default as DialogClose } from './DialogClose.vue'
3 | export { default as DialogContent } from './DialogContent.vue'
4 | export { default as DialogDescription } from './DialogDescription.vue'
5 | export { default as DialogFooter } from './DialogFooter.vue'
6 | export { default as DialogHeader } from './DialogHeader.vue'
7 | export { default as DialogScrollContent } from './DialogScrollContent.vue'
8 | export { default as DialogTitle } from './DialogTitle.vue'
9 | export { default as DialogTrigger } from './DialogTrigger.vue'
10 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectSeparator.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectGroup.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/entrypoints/background.ts:
--------------------------------------------------------------------------------
1 | import { browser } from 'wxt/browser'
2 | import { defineBackground } from 'wxt/sandbox'
3 |
4 | export default defineBackground({
5 | type: `module`,
6 | main() {
7 | browser.runtime.onInstalled.addListener((detail) => {
8 | if (import.meta.env.COMMAND === `serve`) {
9 | browser.runtime.openOptionsPage()
10 | return
11 | }
12 | if (detail.reason === `install`) {
13 | browser.tabs.create({ url: `https://mpmd.pages.dev/welcome` })
14 | }
15 | else if (detail.reason === `update`) {
16 | browser.runtime.openOptionsPage()
17 | }
18 | })
19 | },
20 | })
21 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuSeparator.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogTitle.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AlertDialog } from './AlertDialog.vue'
2 | export { default as AlertDialogAction } from './AlertDialogAction.vue'
3 | export { default as AlertDialogCancel } from './AlertDialogCancel.vue'
4 | export { default as AlertDialogContent } from './AlertDialogContent.vue'
5 | export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
6 | export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
7 | export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
8 | export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
9 | export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'
10 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarSeparator.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogAction.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/ui/number-field/NumberFieldInput.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/ui/tabs/TabsList.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/utils/fetch.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | // 创建axios实例
4 | const service = axios.create({
5 | baseURL: ``,
6 | timeout: 30 * 1000, // 请求超时时间
7 | })
8 |
9 | service.interceptors.request.use(
10 | (config) => {
11 | if (/^(?:post|put|delete)$/i.test(`${config.method}`)) {
12 | if (config.data && config.data.upload) {
13 | config.headers[`Content-Type`] = `multipart/form-data`
14 | }
15 | }
16 | return config
17 | },
18 | (error) => {
19 | Promise.reject(error)
20 | },
21 | )
22 |
23 | service.interceptors.response.use(
24 | (res) => {
25 | return res.data ? res.data : Promise.reject(res)
26 | },
27 | error => Promise.reject(error),
28 | )
29 |
30 | export default service
31 |
--------------------------------------------------------------------------------
/src/components/ui/label/Label.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
27 |
28 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogDescription.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/components/ui/button/Button.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/ui/select/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Select } from './Select.vue'
2 | export { default as SelectContent } from './SelectContent.vue'
3 | export { default as SelectGroup } from './SelectGroup.vue'
4 | export { default as SelectItem } from './SelectItem.vue'
5 | export { default as SelectItemText } from './SelectItemText.vue'
6 | export { default as SelectLabel } from './SelectLabel.vue'
7 | export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue'
8 | export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue'
9 | export { default as SelectSeparator } from './SelectSeparator.vue'
10 | export { default as SelectTrigger } from './SelectTrigger.vue'
11 | export { default as SelectValue } from './SelectValue.vue'
12 |
--------------------------------------------------------------------------------
/src/components/ui/tabs/TabsContent.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/code-review.yml:
--------------------------------------------------------------------------------
1 | name: Code Review
2 | on:
3 | pull_request_target:
4 | types:
5 | - opened # Triggers when a PR is opened
6 | - reopened # Triggers when a PR is reopened
7 | - synchronize # Triggers when a commit is pushed to the PR
8 |
9 | # fix: GraphQL: Resource not accessible by integration (addComment) error
10 | permissions:
11 | pull-requests: write
12 |
13 | jobs:
14 | setup-deepseek-review:
15 | runs-on: ubuntu-latest
16 | name: Code Review
17 | steps:
18 | - name: DeepSeek Code Review
19 | uses: hustcer/deepseek-review@v1
20 | with:
21 | chat-token: ${{ secrets.CHAT_TOKEN }}
22 | base-url: ${{ secrets.BASE_URL }}
23 | model: ${{ secrets.CHAT_MODEL }}
24 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogDescription.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuLabel.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "md-tauri"
3 | version = "1.1.0"
4 | description = "Markdown Editor"
5 | authors = ["you"]
6 | license = ""
7 | repository = ""
8 | edition = "2021"
9 | rust-version = "1.77.2"
10 |
11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 |
13 | [lib]
14 | name = "app_lib"
15 | crate-type = ["staticlib", "cdylib", "rlib"]
16 |
17 | [build-dependencies]
18 | tauri-build = { version = "1.5.1", features = [] }
19 |
20 | [dependencies]
21 | serde_json = "1.0"
22 | serde = { version = "1.0", features = ["derive"] }
23 | log = "0.4"
24 | tauri = { version = "1.5.4", features = [ "shell-open", "dialog-all", "fs-all"] }
25 |
26 | [features]
27 | default = ["custom-protocol"]
28 | custom-protocol = ["tauri/custom-protocol"]
29 |
--------------------------------------------------------------------------------
/src/components/ui/sonner/Sonner.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/EditorHeader/EditDropdown.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 | 功能
12 |
13 |
14 |
15 |
16 | 上传图片
17 |
18 |
19 |
20 | 插入表格
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuLabel.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogTitle.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogCancel.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/components/ui/number-field/NumberField.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createPinia } from 'pinia'
2 | import { createApp } from 'vue'
3 | import App from './App.vue'
4 |
5 | import 'virtual:uno.css'
6 | import 'codemirror/lib/codemirror.css'
7 | import 'codemirror/theme/xq-light.css'
8 | import 'codemirror/theme/darcula.css'
9 |
10 | /* 每个页面公共css */
11 | import '@/assets/index.css'
12 | import '@/assets/less/theme.less'
13 |
14 | import 'codemirror/mode/css/css'
15 | import 'codemirror/mode/javascript/javascript'
16 | import 'codemirror/mode/markdown/markdown'
17 | import 'codemirror/addon/edit/closebrackets'
18 | import 'codemirror/addon/edit/matchbrackets'
19 | import 'codemirror/addon/selection/active-line'
20 | import 'codemirror/addon/hint/show-hint'
21 | import 'codemirror/addon/hint/css-hint'
22 |
23 | const app = createApp(App)
24 |
25 | app.use(createPinia())
26 |
27 | app.mount(`#app`)
28 |
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/EditorHeader/HelpDropdown.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 | 帮助
10 |
11 |
12 |
13 | 关于
14 |
15 |
16 | 联系维护人
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectScrollUpButton.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/ui/alert/index.ts:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from 'class-variance-authority'
2 |
3 | export { default as Alert } from './Alert.vue'
4 | export { default as AlertDescription } from './AlertDescription.vue'
5 | export { default as AlertTitle } from './AlertTitle.vue'
6 |
7 | export const alertVariants = cva(
8 | `relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground`,
9 | {
10 | variants: {
11 | variant: {
12 | default: `bg-background text-foreground`,
13 | destructive:
14 | `border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive`,
15 | },
16 | },
17 | defaultVariants: {
18 | variant: `default`,
19 | },
20 | },
21 | )
22 |
23 | export type AlertVariants = VariantProps
24 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectScrollDownButton.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/Menubar.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | dist
26 |
27 | node_modules
28 |
29 | # Log files
30 | npm-debug.log*
31 | yarn-debug.log*
32 | yarn-error.log*
33 |
34 | # Editor directories and files
35 | .idea
36 | *.suo
37 | *.ntvs*
38 | *.njsproj
39 | *.sln
40 | *.sw?
41 |
42 | # mockm
43 | httpData
44 |
45 | public/upload/**
46 | !public/upload/*.gitkeep
47 | .history
48 |
49 | # Package manager lock file
50 | # yarn.lock
51 | pnpm-lock.yaml
52 | auto-imports.d.ts
53 | components.d.ts
54 |
55 | .wxt
56 | .output
57 | web-ext.config.ts
58 | package-lock.json
59 | .vscode
60 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5 | "target": "ES2020",
6 | "jsx": "preserve",
7 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
8 | "moduleDetection": "force",
9 | "useDefineForClassFields": true,
10 | "baseUrl": "./",
11 | "module": "ESNext",
12 |
13 | /* Bundler mode */
14 | "moduleResolution": "bundler",
15 | "paths": {
16 | "@/*": ["src/*"]
17 | },
18 | "resolveJsonModule": true,
19 | "allowImportingTsExtensions": true,
20 |
21 | /* Linting */
22 | "strict": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noUnusedLocals": true,
25 | "noUnusedParameters": true,
26 | "noEmit": true,
27 | "isolatedModules": true,
28 | "skipLibCheck": true
29 | },
30 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "components.d.ts", "auto-imports.d.ts"]
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/ui/number-field/NumberFieldDecrement.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/components/ui/number-field/NumberFieldIncrement.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/components/ui/textarea/Textarea.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/wxt.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'wxt'
2 | import ViteConfig from './vite.config'
3 |
4 | export default defineConfig({
5 | srcDir: `src`,
6 | publicDir: `../public`,
7 | extensionApi: `chrome`,
8 | manifest: {
9 | name: `公众号内容编辑器`,
10 | description: `一款高度简洁的微信 Markdown 编辑器:支持 Markdown 语法、色盘取色、多图上传、一键下载文档、自定义 CSS 样式、一键重置、微信公众号图床等特性`,
11 | icons: {
12 | 256: `/mpmd/icon-256.png`,
13 | },
14 | permissions: [`storage`],
15 | host_permissions: [
16 | `https://*.github.com/*`,
17 | `https://*.githubusercontent.com/*`,
18 | `https://*.gitee.com/*`,
19 | `https://*.weixin.qq.com/*`,
20 | // 微信公众号图片
21 | `https://*.qpic.cn/*`,
22 | ],
23 | web_accessible_resources: [
24 | {
25 | resources: [`*.png`, `*.svg`],
26 | matches: [``],
27 | },
28 | ],
29 | },
30 | analysis: {
31 | open: true,
32 | },
33 | vite: () => ({
34 | ...ViteConfig,
35 | base: `/`,
36 | }),
37 | })
38 |
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/EditorHeader/FileDropdown.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 | 文件
18 |
19 |
20 |
21 |
22 | 导入 .md
23 |
24 |
25 |
26 | 导出 .md
27 |
28 |
29 |
30 | 导出 .html
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/components/ui/input/Input.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarTrigger.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/components/FormItem.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
25 |
26 |
27 |
28 |
35 |
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/EditorHeader/ContactInfo.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
34 |
35 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuItem.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Menubar } from './Menubar.vue'
2 | export { default as MenubarCheckboxItem } from './MenubarCheckboxItem.vue'
3 | export { default as MenubarContent } from './MenubarContent.vue'
4 | export { default as MenubarGroup } from './MenubarGroup.vue'
5 | export { default as MenubarItem } from './MenubarItem.vue'
6 | export { default as MenubarLabel } from './MenubarLabel.vue'
7 | export { default as MenubarMenu } from './MenubarMenu.vue'
8 | export { default as MenubarRadioGroup } from './MenubarRadioGroup.vue'
9 | export { default as MenubarRadioItem } from './MenubarRadioItem.vue'
10 | export { default as MenubarSeparator } from './MenubarSeparator.vue'
11 | export { default as MenubarShortcut } from './MenubarShortcut.vue'
12 | export { default as MenubarSub } from './MenubarSub.vue'
13 | export { default as MenubarSubContent } from './MenubarSubContent.vue'
14 | export { default as MenubarSubTrigger } from './MenubarSubTrigger.vue'
15 | export { default as MenubarTrigger } from './MenubarTrigger.vue'
16 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ContextMenu } from './ContextMenu.vue'
2 | export { default as ContextMenuCheckboxItem } from './ContextMenuCheckboxItem.vue'
3 | export { default as ContextMenuContent } from './ContextMenuContent.vue'
4 | export { default as ContextMenuGroup } from './ContextMenuGroup.vue'
5 | export { default as ContextMenuItem } from './ContextMenuItem.vue'
6 | export { default as ContextMenuLabel } from './ContextMenuLabel.vue'
7 | export { default as ContextMenuRadioGroup } from './ContextMenuRadioGroup.vue'
8 | export { default as ContextMenuRadioItem } from './ContextMenuRadioItem.vue'
9 | export { default as ContextMenuSeparator } from './ContextMenuSeparator.vue'
10 | export { default as ContextMenuShortcut } from './ContextMenuShortcut.vue'
11 | export { default as ContextMenuSub } from './ContextMenuSub.vue'
12 | export { default as ContextMenuSubContent } from './ContextMenuSubContent.vue'
13 | export { default as ContextMenuSubTrigger } from './ContextMenuSubTrigger.vue'
14 | export { default as ContextMenuTrigger } from './ContextMenuTrigger.vue'
15 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarItem.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarSubTrigger.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuItem.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2012 Romain Lespinasse
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/ui/separator/Separator.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
28 | {{ props.label }}
34 |
35 |
36 |
--------------------------------------------------------------------------------
/example/worker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {object} Env
3 | * @property
4 | */
5 |
6 | export default {
7 | /**
8 | * @param {Request} request
9 | * @param {Env} env
10 | * @param {ExecutionContext} ctx
11 | * @returns {Promise}
12 | */
13 | async fetch(request, env, ctx) {
14 | const url = new URL(request.url)
15 | const targetUrl = `https://api.weixin.qq.com`
16 | const proxyRequest = new Request(targetUrl + url.pathname + url.search, {
17 | method: request.method,
18 | headers: request.headers,
19 | body: request.body,
20 | })
21 | const response = await fetch(proxyRequest)
22 | const proxyResponse = new Response(response.body, {
23 | status: response.status,
24 | statusText: response.statusText,
25 | headers: response.headers,
26 | })
27 | setCorsHeaders(proxyResponse.headers)
28 | return proxyResponse
29 | },
30 | }
31 | // 设置 CORS 头部
32 | function setCorsHeaders(headers) {
33 | headers.set(`Access-Control-Allow-Origin`, `*`)
34 | headers.set(`Access-Control-Allow-Methods`, `GET, POST, PUT, DELETE`)
35 | headers.set(`Access-Control-Allow-Headers`, `*`)
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/ui/tabs/TabsTrigger.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuSubTrigger.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DropdownMenu } from './DropdownMenu.vue'
2 |
3 | export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue'
4 | export { default as DropdownMenuContent } from './DropdownMenuContent.vue'
5 | export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue'
6 | export { default as DropdownMenuItem } from './DropdownMenuItem.vue'
7 | export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue'
8 | export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue'
9 | export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue'
10 | export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue'
11 | export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue'
12 | export { default as DropdownMenuSub } from './DropdownMenuSub.vue'
13 | export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue'
14 | export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue'
15 | export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue'
16 | export { DropdownMenuPortal } from 'radix-vue'
17 |
--------------------------------------------------------------------------------
/src/components/PreviewModeSwitch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/RunLoading.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
17 | 致力于让 Markdown 编辑更简单
18 |
19 |
20 |
21 |
22 |
60 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectTrigger.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectItem.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip/TooltipContent.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/EditorHeader/StyleOptionMenu.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
29 | {{ props.title }}
30 |
31 |
32 |
41 | {{ label }}
42 |
43 | {{ desc }}
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuSubContent.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarRadioItem.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // Disable the default formatter, use eslint instead
3 | "prettier.enable": false,
4 | "editor.formatOnSave": false,
5 |
6 | // Auto fix
7 | "editor.codeActionsOnSave": {
8 | "source.fixAll.eslint": "explicit",
9 | "source.organizeImports": "never"
10 | },
11 |
12 | // Silent the stylistic rules in you IDE, but still auto fix them
13 | "eslint.rules.customizations": [
14 | { "rule": "style/*", "severity": "off" },
15 | { "rule": "format/*", "severity": "off" },
16 | { "rule": "*-indent", "severity": "off" },
17 | { "rule": "*-spacing", "severity": "off" },
18 | { "rule": "*-spaces", "severity": "off" },
19 | { "rule": "*-order", "severity": "off" },
20 | { "rule": "*-dangle", "severity": "off" },
21 | { "rule": "*-newline", "severity": "off" },
22 | { "rule": "*quotes", "severity": "off" },
23 | { "rule": "*semi", "severity": "off" }
24 | ],
25 |
26 | // Enable eslint for all supported languages
27 | "eslint.validate": [
28 | "javascript",
29 | "javascriptreact",
30 | "typescript",
31 | "typescriptreact",
32 | "vue",
33 | "html",
34 | "markdown",
35 | "json",
36 | "jsonc",
37 | "yaml",
38 | "toml",
39 | "xml",
40 | "gql",
41 | "graphql",
42 | "astro",
43 | "css",
44 | "less",
45 | "scss",
46 | "pcss",
47 | "postcss"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarCheckboxItem.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card/HoverCardContent.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarContent.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
31 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuRadioItem.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/ui/menubar/MenubarSubContent.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuCheckboxItem.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/ui/context-menu/ContextMenuContent.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/EditorHeader/AboutDialog.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 |
55 |
56 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu/DropdownMenuContent.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/ui/button/index.ts:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from 'class-variance-authority'
2 |
3 | export { default as Button } from './Button.vue'
4 |
5 | export const buttonVariants = cva(
6 | `inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50`,
7 | {
8 | variants: {
9 | variant: {
10 | default: `bg-primary text-primary-foreground hover:bg-primary/90`,
11 | destructive:
12 | `bg-destructive text-destructive-foreground hover:bg-destructive/90`,
13 | outline:
14 | `border border-input bg-background hover:bg-accent hover:text-accent-foreground`,
15 | secondary:
16 | `bg-secondary text-secondary-foreground hover:bg-secondary/80`,
17 | ghost: `hover:bg-accent hover:text-accent-foreground`,
18 | link: `text-primary underline-offset-4 hover:underline`,
19 | },
20 | size: {
21 | default: `h-10 px-4 py-2`,
22 | xs: `h-7 rounded px-2`,
23 | sm: `h-9 rounded-md px-3`,
24 | lg: `h-11 rounded-md px-8`,
25 | icon: `h-10 w-10`,
26 | },
27 | },
28 | defaultVariants: {
29 | variant: `default`,
30 | size: `default`,
31 | },
32 | },
33 | )
34 |
35 | export type ButtonVariants = VariantProps
36 |
--------------------------------------------------------------------------------
/src/assets/less/app.less:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | html,
8 | body {
9 | height: 100%;
10 | font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue',
11 | sans-serif;
12 | }
13 |
14 | input,
15 | button,
16 | textarea {
17 | font-family: inherit;
18 | }
19 |
20 | h1,
21 | h2,
22 | h3,
23 | h4,
24 | h5,
25 | h6 {
26 | font-weight: normal;
27 | }
28 |
29 | em {
30 | font-style: normal !important;
31 | }
32 |
33 | section {
34 | height: 100%;
35 | }
36 |
37 | .web-title {
38 | margin: 0 15px 0 5px;
39 | }
40 |
41 | .web-icon {
42 | width: auto;
43 | height: 1.5rem;
44 | vertical-align: middle;
45 | }
46 |
47 | #editor {
48 | display: block;
49 | height: 100%;
50 | width: 100%;
51 | padding: 10px;
52 | border: none;
53 | }
54 |
55 | .ctrl {
56 | flex-basis: 60px;
57 | flex-grow: 1;
58 | flex-shrink: 1;
59 | display: flex;
60 | align-items: center;
61 | }
62 |
63 | .preview-wrapper {
64 | display: flex;
65 | align-items: center;
66 | justify-content: center;
67 | padding: 0;
68 | overflow-y: scroll;
69 | }
70 |
71 | .hint {
72 | opacity: 0.6;
73 | margin: 20px 0;
74 | }
75 |
76 | .preview {
77 | position: relative;
78 | margin: 0 -20px;
79 | width: 375px;
80 | min-height: 100%;
81 | padding: 20px;
82 | font-size: 14px;
83 | box-sizing: border-box;
84 | outline: none;
85 | word-wrap: break-word;
86 | }
87 |
88 | .preview table {
89 | margin-bottom: 10px;
90 | border-collapse: collapse;
91 | display: table;
92 | width: 100% !important;
93 | }
--------------------------------------------------------------------------------
/src/assets/less/theme.less:
--------------------------------------------------------------------------------
1 | @nightPreviewColor: #191919;
2 | @nightCodeMirrorColor: #191919;
3 | @nightActiveCodeMirrorColor: gray;
4 | @nightFontColor: gray;
5 | @nightLinkColor: #8e9eb9;
6 | @nightLinkTextColor: #84868b;
7 | @nightLineColor: #84868b;
8 |
9 | .dark {
10 | .container {
11 | .output_night {
12 | .preview {
13 | background-color: @nightPreviewColor;
14 | box-shadow: 0 0 70px rgba(0, 0, 0, 0.3);
15 | }
16 |
17 | .preview-wrapper {
18 | background-color: @nightCodeMirrorColor;
19 | box-shadow: inset 0 0 0 1px rgba(233, 231, 231, 0.102);
20 | }
21 |
22 | .code-snippet__fix {
23 | background-color: rgb(238, 238, 238);
24 | }
25 | }
26 |
27 | ::-webkit-scrollbar {
28 | background-color: @nightCodeMirrorColor;
29 | }
30 | }
31 | }
32 |
33 | .CodeMirror {
34 | padding-bottom: 0;
35 | height: 100% !important;
36 | font-size: 14px;
37 | font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue',
38 | sans-serif !important;
39 | }
40 |
41 | .CodeMirror-vscrollbar:focus {
42 | outline: none;
43 | }
44 |
45 | .CodeMirror-scroll {
46 | padding: 0 20px;
47 | overflow-x: hidden !important;
48 | overflow-y: scroll !important;
49 | }
50 |
51 | .CodeMirror-vscrollbar {
52 | width: 0px;
53 | height: 0px;
54 | }
55 |
56 | .CodeMirror-wrap {
57 | padding-top: 20px;
58 | padding-bottom: 20px;
59 | box-sizing: border-box;
60 | }
61 |
62 | .cm-em {
63 | font-style: normal;
64 | }
65 |
66 | .cm-comment {
67 | font-style: normal !important;
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/ui/popover/PopoverContent.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 |
36 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src-tauri/build-aux/bundle_dmg.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # src-tauri/build-aux/bundle_dmg.sh
3 |
4 | # 此脚本用于macOS上创建DMG文件
5 |
6 | APP_NAME="$1"
7 | DMG_PATH="$2"
8 | SOURCE_DIR="$3"
9 |
10 | if [ -z "$APP_NAME" ] || [ -z "$DMG_PATH" ] || [ -z "$SOURCE_DIR" ]; then
11 | echo "错误: 缺少必要参数"
12 | echo "使用: $0 APP_NAME DMG_PATH SOURCE_DIR"
13 | echo "当前参数:"
14 | echo "APP_NAME: $APP_NAME"
15 | echo "DMG_PATH: $DMG_PATH"
16 | echo "SOURCE_DIR: $SOURCE_DIR"
17 | exit 1
18 | fi
19 |
20 | # 检查源目录是否存在
21 | if [ ! -d "$SOURCE_DIR" ]; then
22 | echo "错误: 源目录不存在: $SOURCE_DIR"
23 | exit 1
24 | fi
25 |
26 | # 检查应用程序是否存在
27 | if [ ! -d "$SOURCE_DIR/$APP_NAME.app" ]; then
28 | echo "错误: 应用程序不存在: $SOURCE_DIR/$APP_NAME.app"
29 | ls -la "$SOURCE_DIR"
30 | exit 1
31 | fi
32 |
33 | # 确保目标目录存在
34 | mkdir -p "$(dirname "$DMG_PATH")" || {
35 | echo "错误: 无法创建目标目录: $(dirname "$DMG_PATH")"
36 | exit 1
37 | }
38 |
39 | # 检查create-dmg命令是否可用
40 | if ! command -v create-dmg &> /dev/null; then
41 | echo "错误: create-dmg 命令未找到,请确保已安装"
42 | exit 1
43 | fi
44 |
45 | echo "开始创建DMG文件..."
46 | echo "应用名称: $APP_NAME"
47 | echo "DMG路径: $DMG_PATH"
48 | echo "源目录: $SOURCE_DIR"
49 |
50 | # 创建DMG文件
51 | create-dmg \
52 | --volname "$APP_NAME" \
53 | --volicon "$SOURCE_DIR/$APP_NAME.app/Contents/Resources/icon.icns" \
54 | --window-pos 200 120 \
55 | --window-size 600 400 \
56 | --icon-size 100 \
57 | --icon "$APP_NAME.app" 200 190 \
58 | --hide-extension "$APP_NAME.app" \
59 | --app-drop-link 400 190 \
60 | "$DMG_PATH" \
61 | "$SOURCE_DIR" || {
62 | echo "错误: DMG文件创建失败"
63 | exit 1
64 | }
65 |
66 | echo "DMG文件创建成功: $DMG_PATH"
--------------------------------------------------------------------------------
/public/assets/icons/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/changelog.md:
--------------------------------------------------------------------------------
1 | ## v1.1.2
2 | ### 构建时间
3 | 2025-04-10T14:30:00Z
4 |
5 | ### 新特性
6 | - 添加内容安全策略(CSP)配置,提高应用安全性
7 | - 优化应用启动流程,确保预览内容立即渲染
8 |
9 | ### 优化
10 | - 优化文档删除逻辑,自动创建新文档避免空白状态
11 | - 改进文档和目录项的样式,使用系统默认字体大小
12 | - 优化对话框标题样式,减小字体大小并调整粗细
13 | - 增强图片加载错误处理,提供友好的占位图像
14 | - 优化按钮布局和图标显示
15 |
16 | ### 修复
17 | - 修复编辑器初始化和刷新时序问题
18 | - 修复表格单元格文本颜色问题
19 | - 修复工作流配置中的模型变量引用错误
20 |
21 | ### 依赖更新
22 | - 更新应用版本从 1.1.1 到 1.1.2
23 |
24 | ## v1.1.1
25 | ### 构建时间
26 | 2025-03-25T08:08:52Z
27 |
28 | ### 新特性
29 | - 新增文件目录树功能
30 | - 支持创建、重命名、删除目录
31 | - 支持无限层级的目录结构
32 | - 支持展开/折叠目录
33 | - 支持在指定目录下创建文档
34 | - 支持删除目录时选择是否同时删除目录下的文档
35 |
36 | ### 优化
37 | - 优化侧边栏交互体验
38 | - 新增侧边栏宽度调整功能
39 | - 优化文档列表的显示样式
40 | - 移除文档项的 hover 背景效果
41 |
42 | ### 依赖更新
43 | - 更新 uuid 依赖从 v11.0.5 到 v11.1.0
44 |
45 | -------
46 |
47 | ## v1.1.0
48 | ### 构建时间
49 | 2025-03-17T16:37:33Z
50 |
51 | ### 新特性
52 | - 使用 Tauri 重构项目,提供更高性能的桌面应用体验
53 | - 添加 rust-target 输入参数并更新构建产物路径处理逻辑
54 |
55 | ### 优化
56 | - 优化 Windows 构建流程并调整代码风格
57 | - 优化 GitHub Actions 工作流并更新前端配置
58 | - 拆解工作流文件,提高 CI/CD 效率
59 |
60 | ### 修复
61 | - 修复 Windows 和 Linux 编译的问题
62 | - 修复 MacOS 编译内存不足的问题
63 | - 修复 Ubuntu 编译缺失依赖的问题
64 | - 修复依赖安全漏洞问题
65 | - 修复创建 release 无法找到产物的问题
66 |
67 | -------
68 |
69 | ## v1.0.0
70 | ### 构建时间
71 | 2025-03-01T00:00:00Z
72 |
73 | ### 新特性
74 | - 添加黑暗模式切换并增强 CodemirrorEditor 交互
75 | - 增强文章管理功能
76 | - 增加文章阅读时间计算功能
77 | - 支持通过 Cloudflare r2 上传图片
78 | - 添加新的主题选项
79 |
80 | ### 优化
81 | - 优化配色方案
82 | - 更新右侧滑块样式
83 | - 更新布局结构
84 |
85 | ### 修复
86 | - 修复 XSS 安全漏洞
87 | - 修复 CSS 编辑器问题
88 | - 修复引用块样式
89 | - 修复 HTML 转义问题
90 | - 修复代码块和前置内容渲染问题
91 | - 修复正则表达式问题
92 | - 修复错误的列表项选择器
93 | - 修复标签切换问题
94 | - 修复偶尔无法获取账户的问题
95 | - 修复扩展检查异常
96 |
97 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
83 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog/AlertDialogContent.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
30 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/components/ui/back-top/BackTop.vue:
--------------------------------------------------------------------------------
1 |
56 |
57 |
58 |
61 |
62 |
--------------------------------------------------------------------------------
/src/assets/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 0 0% 3.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 0 0% 3.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 0 0% 3.9%;
15 |
16 | --primary: 0 0% 9%;
17 | --primary-foreground: 0 0% 98%;
18 |
19 | --secondary: 0 0% 96.1%;
20 | --secondary-foreground: 0 0% 9%;
21 |
22 | --muted: 0 0% 96.1%;
23 | --muted-foreground: 0 0% 45.1%;
24 |
25 | --accent: 0 0% 96.1%;
26 | --accent-foreground: 0 0% 9%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 0 0% 98%;
30 |
31 | --border:0 0% 89.8%;
32 | --input:0 0% 89.8%;
33 | --ring:0 0% 3.9%;
34 | --radius: 0.5rem;
35 |
36 | --blockquote-background: #f7f7f7;
37 | }
38 |
39 | .dark {
40 | --background:0 0% 3.9%;
41 | --foreground:0 0% 98%;
42 |
43 | --card:0 0% 3.9%;
44 | --card-foreground:0 0% 98%;
45 |
46 | --popover:0 0% 3.9%;
47 | --popover-foreground:0 0% 98%;
48 |
49 | --primary:0 0% 98%;
50 | --primary-foreground:0 0% 9%;
51 |
52 | --secondary:0 0% 14.9%;
53 | --secondary-foreground:0 0% 98%;
54 |
55 | --muted:0 0% 14.9%;
56 | --muted-foreground:0 0% 63.9%;
57 |
58 | --accent:0 0% 14.9%;
59 | --accent-foreground:0 0% 98%;
60 |
61 | --destructive:0 62.8% 30.6%;
62 | --destructive-foreground:0 0% 98%;
63 |
64 | --border:0 0% 14.9%;
65 | --input:0 0% 14.9%;
66 | --ring:0 0% 83.1%;
67 |
68 | --blockquote-background: #212121;
69 | }
70 | }
71 |
72 | @layer base {
73 | * {
74 | @apply border-border;
75 | }
76 | body {
77 | @apply bg-background text-foreground;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import process from 'node:process'
3 |
4 | import vue from '@vitejs/plugin-vue'
5 | import { visualizer } from 'rollup-plugin-visualizer'
6 | import UnoCSS from 'unocss/vite'
7 | import AutoImport from 'unplugin-auto-import/vite'
8 | import Components from 'unplugin-vue-components/vite'
9 | import { defineConfig } from 'vite'
10 | import { nodePolyfills } from 'vite-plugin-node-polyfills'
11 | import vueDevTools from 'vite-plugin-vue-devtools'
12 |
13 | // https://vitejs.dev/config/
14 | export default defineConfig({
15 | base: `./`, // Tauri应用使用相对路径
16 | define: {
17 | process,
18 | },
19 | plugins: [
20 | vue(),
21 | UnoCSS(),
22 | vueDevTools(),
23 | nodePolyfills({
24 | include: [`path`, `util`, `timers`, `stream`, `fs`],
25 | overrides: {
26 | // Since `fs` is not supported in browsers, we can use the `memfs` package to polyfill it.
27 | // fs: 'memfs',
28 | },
29 | }),
30 | process.env.ANALYZE === `true` && visualizer({
31 | emitFile: true,
32 | filename: `stats.html`,
33 | }),
34 | AutoImport({
35 | imports: [
36 | `vue`,
37 | `pinia`,
38 | `@vueuse/core`,
39 | ],
40 | dirs: [
41 | `./src/stores`,
42 | `./src/utils/toast`,
43 | ],
44 | }),
45 | Components({
46 | resolvers: [],
47 | }),
48 | ],
49 | resolve: {
50 | alias: {
51 | '@': path.resolve(__dirname, `./src`),
52 | },
53 | },
54 | css: {
55 | devSourcemap: true,
56 | },
57 | build: {
58 | rollupOptions: {
59 | output: {
60 | chunkFileNames: `static/js/md-[name]-[hash].js`,
61 | entryFileNames: `static/js/md-[name]-[hash].js`,
62 | assetFileNames: `static/[ext]/md-[name]-[hash].[ext]`,
63 | },
64 | },
65 | },
66 | })
67 |
--------------------------------------------------------------------------------
/src/components/CustomUploadForm.vue:
--------------------------------------------------------------------------------
1 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/entrypoints/popup/App.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
16 |
![]()
17 |
使用必读
25 |
26 |
27 | 如果您希望使用微信公众号素材库作为图床功能,需要进行以下配置:
28 |
29 | 1.开启公众号开发者模式
30 |
查看文档
34 |
35 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
73 |
--------------------------------------------------------------------------------
/src-tauri/build-aux/convert_icon.cjs:
--------------------------------------------------------------------------------
1 | // src-tauri/build-aux/convert_icon.js
2 | const fs = require('fs');
3 | const path = require('path');
4 | const { execSync } = require('child_process');
5 |
6 | /**
7 | * 此脚本用于在Windows构建前将PNG图标转换为正确格式的ICO文件
8 | * 解决Windows资源编译器错误: "resource file is not in 3.00 format"
9 | */
10 |
11 | function checkImageMagick() {
12 | try {
13 | // 检查是否安装了ImageMagick
14 | execSync('magick --version', { stdio: 'ignore' });
15 | return true;
16 | } catch (error) {
17 | console.log('ImageMagick未安装,将使用备用方法...');
18 | return false;
19 | }
20 | }
21 |
22 | function convertIconWithImageMagick(pngPath, icoPath) {
23 | try {
24 | // 使用ImageMagick转换PNG到ICO
25 | execSync(`magick convert "${pngPath}" -define icon:auto-resize=256,128,64,48,32,16 "${icoPath}"`);
26 | console.log(`成功使用ImageMagick将PNG转换为ICO: ${icoPath}`);
27 | return true;
28 | } catch (error) {
29 | console.error('ImageMagick转换失败:', error.message);
30 | return false;
31 | }
32 | }
33 |
34 | function main() {
35 | const iconsDir = path.join(__dirname, '..', 'icons');
36 | const pngPath = path.join(iconsDir, 'icon.png');
37 | const icoPath = path.join(iconsDir, 'icon.ico');
38 |
39 | // 检查源PNG文件是否存在
40 | if (!fs.existsSync(pngPath)) {
41 | console.error(`错误: 源PNG文件不存在: ${pngPath}`);
42 | process.exit(1);
43 | }
44 |
45 | // 尝试使用ImageMagick转换
46 | if (checkImageMagick()) {
47 | if (convertIconWithImageMagick(pngPath, icoPath)) {
48 | process.exit(0);
49 | }
50 | }
51 |
52 | // 如果ImageMagick不可用或转换失败,提供替代方案的指导
53 | console.log('\n无法自动转换图标。请手动执行以下步骤:');
54 | console.log('1. 使用在线工具如https://convertico.com将PNG转换为ICO');
55 | console.log('2. 确保生成的ICO文件包含多种尺寸(16x16, 32x32, 48x48, 64x64, 128x128)');
56 | console.log(`3. 将生成的ICO文件替换到: ${icoPath}\n`);
57 |
58 | // 在CI环境中,这是一个错误
59 | if (process.env.CI === 'true') {
60 | process.exit(1);
61 | }
62 | }
63 |
64 | main();
--------------------------------------------------------------------------------
/src/components/ui/switch/Switch.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
51 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/components/ui/select/SelectContent.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/config/api.ts:
--------------------------------------------------------------------------------
1 | export const githubConfig = {
2 | username: `bucketio`,
3 | repoList: Array.from({ length: 20 }, (_, i) => `img${i}`),
4 | branch: `main`,
5 | accessTokenList: [
6 | `ghp_sqQg5y7XC7Fy8XdoocsmdVEYRiRiTZPvbwzTL4MRjQc`,
7 | `ghp_jB5JXzBjpGbgzdoocsmdogWfSHhfCKGVstozw1cAsPv`,
8 | `ghp_zvy8wkHo259g7doocsmdJnUKOQd1WO1SPzZ9G0O9cJD`,
9 | `ghp_DnCJc2Ms0RVZ1doocsmdiWOAN78FurfSeD1Pv2Y28pO`,
10 | `ghp_EsMYDv9WVjXWP5doocsmd1nnDml2DEP95rOiz44bSo0`,
11 | `ghp_L4isHf01nllOOdoocsmdHBGoDG6jscCA09WV44QDvlg`,
12 | `ghp_qWciwYXHPakAUGdoocsmdBOBZdRcV08JThKey3mBZNJ`,
13 | `ghp_rxkvIO08wVL2DMdoocsmd2jDEhcatp2rfVyhd3A7RiS`,
14 | `ghp_1RvkWKboSxr0yVdoocsmd7OtBCpecYwoV6deh3utifJ`,
15 | `ghp_cduanDnAug60ngdoocsmdF1uDstXUi6S9RMhY1qdada`,
16 | `ghp_q6mxuJIkqAcsCXdoocsmdkkjWvzGlMVRuy5zI0IWNDx`,
17 | `ghp_Pv4npPeJpChKFMTdoocsmdCQneopUcqJrqrjl3vrt9A`,
18 | `ghp_gKMCFqMaQiLTqhjdoocsmd7BJE8RyK6AdRw4b42CutS`,
19 | `ghp_2oShgb33qFlqBmadoocsmdludmuLYxBFY5bao1XrsVo`,
20 | `ghp_eYyd3kxWTZmsV8doocsmdDFbAa7AEGQTJgmOd0GUmtY`,
21 | ],
22 | }
23 |
24 | export const giteeConfig = {
25 | username: `filesss`,
26 | repoList: Array.from({ length: 20 }, (_, i) => `img${i}`),
27 | branch: `main`,
28 | accessTokenList: [
29 | `ed5fc9866bd6c2fdoocsmddd433f806fd2f399c`,
30 | `5448ffebbbf1151doocsmdc4e337cf814fc8a62`,
31 | `25b05efd2557ca2doocsmd75b5c0835e3395911`,
32 | `11628c7a5aef015doocsmd2eeff9fb9566f0458`,
33 | `cb2f5145ed938dedoocsmdbd063b4ed244eecf8`,
34 | `d8c0b57500672c1doocsmd55f48b866b5ebcd98`,
35 | `78c56eadb88e453doocsmd43ddd95753351771a`,
36 | `03e1a688003948fdoocsmda16fcf41e6f03f1f0`,
37 | `c49121cf4d191fbdoocsmdd6a7877ed537e474a`,
38 | `adfeb2fadcdc4aadoocsmdfe1ee869ac9c968ff`,
39 | `116c94549ca4a0ddoocsmd192653af5c0694616`,
40 | `ecf30ed7f2eb184doocsmd51ea4ec8300371d9e`,
41 | `5837cf2bd5afd93doocsmd73904bed31934949e`,
42 | `b5b7e1c7d57e01fdoocsmd5266f552574297d78`,
43 | `684d55564ffbd0bdoocsmd7d747e5cc23aed6d6`,
44 | `3fc04a9d272ab71doocsmd010c56cb57d88d2ba`,
45 | ],
46 | }
47 |
--------------------------------------------------------------------------------
/src/assets/example/theme-css.txt:
--------------------------------------------------------------------------------
1 | /**
2 | * 按 Alt/Option + Shift + F 可格式化
3 | * 如需使用主题色,请使用 var(--md-primary-color) 代替颜色值
4 | * 如:color: var(--md-primary-color);
5 | *
6 | * 召集令:如果你有好看的主题样式,欢迎分享,让更多人能够使用到你的主题。
7 | * 提交区:https://github.com/CrazyMrYan/md-tauri/issues/1
8 | */
9 | /* 顶层容器样式 */
10 | container {
11 | }
12 | /* 一级标题样式 */
13 | h1 {
14 | }
15 | /* 二级标题样式 */
16 | h2 {
17 | }
18 | /* 三级标题样式 */
19 | h3 {
20 | }
21 | /* 四级标题样式 */
22 | h4 {
23 | }
24 | /* 五级标题样式 */
25 | h5 {
26 | }
27 | /* 六级标题样式 */
28 | h6 {
29 | }
30 | /* 图片样式 */
31 | image {
32 | }
33 | /* 引用样式 */
34 | blockquote {
35 | }
36 | /* 引用段落样式 */
37 | blockquote_p {
38 | }
39 | /* GFM note 样式 */
40 | blockquote_note {
41 | }
42 | /* GFM tip 样式 */
43 | blockquote_tip {
44 | }
45 | /* GFM important 样式 */
46 | blockquote_important {
47 | }
48 | /* GFM warning 样式 */
49 | blockquote_warning {
50 | }
51 | /* GFM caution 样式 */
52 | blockquote_caution {
53 | }
54 | /* GFM 通用标题 */
55 | blockquote_title {
56 | }
57 | /* GFM note 标题 */
58 | blockquote_title_note {
59 | }
60 | /* GFM tip 标题 */
61 | blockquote_title_tip {
62 | }
63 | /* GFM important 标题 */
64 | blockquote_title_important {
65 | }
66 | /* GFM warning 标题 */
67 | blockquote_title_warning {
68 | }
69 | /* GFM caution 标题 */
70 | blockquote_title_caution {
71 | }
72 | /* GFM note 段落样式 */
73 | blockquote_p_note {
74 | }
75 | /* GFM tip 段落样式 */
76 | blockquote_p_tip {
77 | }
78 | /* GFM important 段落样式 */
79 | blockquote_p_important {
80 | }
81 | /* GFM warning 段落样式 */
82 | blockquote_p_warning {
83 | }
84 | /* GFM caution 段落样式 */
85 | blockquote_p_caution {
86 | }
87 | /* 段落样式 */
88 | p {
89 | }
90 | /* 分割线样式 */
91 | hr {
92 | }
93 | /* 行内代码样式 */
94 | codespan {
95 | }
96 | /* 粗体样式 */
97 | strong {
98 | }
99 | /* 链接样式 */
100 | link {
101 | }
102 | /* 微信链接样式 */
103 | wx_link {
104 | }
105 | /* 有序列表样式 */
106 | ol {
107 | }
108 | /* 无序列表样式 */
109 | ul {
110 | }
111 | /* 列表项样式 */
112 | listitem {
113 | }
114 | /* 代码块样式 */
115 | code {
116 | }
117 | /* 代码块外层样式 */
118 | code_pre {
119 | }
120 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogScrollContent.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
32 | {
41 | const originalEvent = event.detail.originalEvent;
42 | const target = originalEvent.target as HTMLElement;
43 | if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
44 | event.preventDefault();
45 | }
46 | }"
47 | >
48 |
49 |
50 |
53 |
54 | Close
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/components/ui/dialog/DialogContent.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
32 |
40 |
41 |
42 |
45 |
46 | Close
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src-tauri/tauri.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/1.x/tooling/cli/schema.json",
3 | "build": {
4 | "beforeBuildCommand": "npm run build:only",
5 | "beforeDevCommand": "npm run dev",
6 | "devPath": "http://localhost:5173",
7 | "distDir": "../dist"
8 | },
9 | "package": {
10 | "productName": "md-tauri",
11 | "version": "1.1.2"
12 | },
13 | "tauri": {
14 | "allowlist": {
15 | "dialog": {
16 | "all": true
17 | },
18 | "fs": {
19 | "all": true,
20 | "scope": ["**"]
21 | },
22 | "shell": {
23 | "open": true
24 | }
25 | },
26 | "bundle": {
27 | "active": true,
28 | "category": "DeveloperTool",
29 | "copyright": "",
30 | "deb": {
31 | "depends": []
32 | },
33 | "externalBin": [],
34 | "icon": [
35 | "icons/32x32.png",
36 | "icons/128x128.png",
37 | "icons/128x128@2x.png",
38 | "icons/icon.icns",
39 | "icons/icon.ico"
40 | ],
41 | "identifier": "com.tauri.md-editor",
42 | "longDescription": "",
43 | "macOS": {
44 | "entitlements": null,
45 | "exceptionDomain": "",
46 | "frameworks": [],
47 | "providerShortName": null,
48 | "signingIdentity": null
49 | },
50 | "resources": [],
51 | "shortDescription": "",
52 | "targets": ["deb", "app", "dmg", "msi"],
53 | "windows": {
54 | "certificateThumbprint": null,
55 | "digestAlgorithm": "sha256",
56 | "wix": { "language": "zh-CN" }
57 | }
58 | },
59 | "security": {
60 | "csp": "default-src 'self' data: blob: filesystem: https: 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: blob: https:;",
61 | "dangerousDisableAssetCspModification": ["script-src", "style-src"]
62 | },
63 | "windows": [
64 | {
65 | "fullscreen": false,
66 | "height": 800,
67 | "resizable": true,
68 | "title": "MD Tauri",
69 | "width": 1200,
70 | "minWidth": 800,
71 | "minHeight": 600,
72 | "center": true
73 | }
74 | ]
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/Preview.vue:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
28 |
61 |
62 |
--------------------------------------------------------------------------------
/.github/actions/load-os-config/action.yml:
--------------------------------------------------------------------------------
1 | # .github/actions/load-os-config/action.yml
2 | name: 'Load OS Configuration'
3 | description: 'Load OS-specific configuration for the build process'
4 |
5 | runs:
6 | using: "composite"
7 | steps:
8 | - name: Setup macOS Rust target
9 | if: runner.os == 'macOS'
10 | shell: bash
11 | run: |
12 | ARCH=$(uname -m)
13 | if [ "$ARCH" = "arm64" ]; then
14 | echo "TAURI_TARGET=aarch64-apple-darwin" >> $GITHUB_ENV
15 | rustup target add aarch64-apple-darwin
16 | else
17 | echo "TAURI_TARGET=x86_64-apple-darwin" >> $GITHUB_ENV
18 | rustup target add x86_64-apple-darwin
19 | fi
20 |
21 | - name: Install Linux Dependencies
22 | if: runner.os == 'Linux'
23 | shell: bash
24 | run: |
25 | sudo apt-get update
26 | sudo apt-get remove -y libappindicator3-dev libappindicator3-1 || true
27 |
28 | # 安装核心依赖
29 | sudo apt-get install -y \
30 | libgtk-3-dev \
31 | libwebkit2gtk-4.1-dev \
32 | libjavascriptcoregtk-4.1-dev \
33 | libayatana-appindicator3-dev \
34 | libsoup2.4-dev \
35 | libsoup2.4-1 \
36 | libssl-dev \
37 | patchelf \
38 | libglib2.0-dev \
39 | libatk1.0-dev \
40 | libpango1.0-dev \
41 | libgdk-pixbuf2.0-dev \
42 | libcairo2-dev
43 |
44 | # 更新库缓存
45 | sudo ldconfig
46 | echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/lib/x86_64-linux-gnu/pkgconfig" >> $GITHUB_ENV
47 |
48 | - name: Prepare Windows Icon
49 | if: runner.os == 'Windows'
50 | shell: pwsh
51 | run: |
52 | if (-Not (Test-Path -Path "src-tauri/icons")) {
53 | New-Item -ItemType Directory -Force -Path "src-tauri/icons"
54 | }
55 |
56 | # 使用正确的图标URL
57 | $iconUrl = "https://raw.githubusercontent.com/CrazyMrYan/md-tauri/refs/heads/main/public/assets/images/logo.png"
58 | $icoPath = "src-tauri/icons/icon.ico"
59 |
60 | if (Test-Path -Path "public/icon.png") {
61 | Copy-Item -Path "public/icon.png" -Destination $icoPath -Force
62 | } else {
63 | Invoke-WebRequest -Uri $iconUrl -OutFile $icoPath
64 | }
65 |
66 | if (-Not (Test-Path -Path $icoPath)) {
67 | Write-Error "图标文件准备失败"
68 | exit 1
69 | }
--------------------------------------------------------------------------------
/.github/actions/create-release/action.yml:
--------------------------------------------------------------------------------
1 | # .github/actions/create-release/action.yml
2 | name: 'Create Release'
3 | description: 'Create a new GitHub release with artifacts'
4 |
5 | runs:
6 | using: "composite"
7 | steps:
8 | - name: Checkout code
9 | uses: actions/checkout@v4
10 |
11 | - name: Get Package Version
12 | id: package-version
13 | shell: bash
14 | run: |
15 | VERSION=$(node -p "require('./package.json').version")
16 | echo "version=$VERSION" >> $GITHUB_OUTPUT
17 |
18 | - name: Download artifacts
19 | uses: actions/download-artifact@v4
20 | with:
21 | path: artifacts
22 | merge-multiple: true
23 |
24 | - name: Create Tag
25 | id: create_tag
26 | shell: bash
27 | run: |
28 | TAG_NAME="v${{ steps.package-version.outputs.version }}"
29 | echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
30 | git tag $TAG_NAME
31 | git push origin $TAG_NAME
32 |
33 | - name: Generate Release Info
34 | id: release_info
35 | shell: bash
36 | run: |
37 | BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
38 | echo "build_date=$BUILD_DATE" >> $GITHUB_OUTPUT
39 |
40 | # 检查构建产物目录是否存在
41 | if [ ! -d "artifacts" ]; then
42 | echo "错误: 构建产物目录不存在,这可能表明构建过程失败或构建产物未被正确上传"
43 | exit 1
44 | fi
45 |
46 | # 列出下载的构建产物
47 | echo "找到以下构建产物:"
48 | find artifacts -type f -name "*.dmg" -o -name "*.msi" -o -name "*.deb" -o -name "*.AppImage" | sort
49 |
50 | # 检查是否找到了任何构建产物
51 | ARTIFACTS_COUNT=$(find artifacts -type f -name "*.dmg" -o -name "*.msi" -o -name "*.deb" -o -name "*.AppImage" | wc -l)
52 | if [ "$ARTIFACTS_COUNT" -eq 0 ]; then
53 | echo "警告: 没有找到任何构建产物文件,检查上传路径是否正确"
54 | # 列出所有文件以便调试
55 | echo "artifacts目录中的所有文件:"
56 | find artifacts -type f | sort
57 | fi
58 |
59 | - name: Create Release
60 | uses: softprops/action-gh-release@v1
61 | with:
62 | tag_name: ${{ steps.create_tag.outputs.tag_name }}
63 | name: Release ${{ steps.create_tag.outputs.tag_name }}
64 | body: |
65 | # 版本 ${{ steps.package-version.outputs.version }}
66 | 构建时间: ${{ steps.release_info.outputs.build_date }}
67 | files: |
68 | artifacts/**/*.dmg
69 | artifacts/**/*.msi
70 | artifacts/**/*.deb
71 | artifacts/**/*.AppImage
72 | fail_on_unmatched_files: false
--------------------------------------------------------------------------------
/src/utils/tauri-fs.ts:
--------------------------------------------------------------------------------
1 | import { save, open } from '@tauri-apps/api/dialog';
2 | import { writeTextFile, readTextFile } from '@tauri-apps/api/fs';
3 |
4 | /**
5 | * Tauri文件系统适配器
6 | * 提供与Tauri API交互的文件操作方法
7 | */
8 | export const tauriFs = {
9 | /**
10 | * 保存文件到本地
11 | * @param content 文件内容
12 | * @param fileName 默认文件名
13 | * @param fileExtension 文件扩展名
14 | */
15 | async saveFile(content: string, fileName = 'content', fileExtension = 'md') {
16 | try {
17 | // 使用新的 API
18 | const filePath = await save({
19 | defaultPath: `${fileName}.${fileExtension}`,
20 | filters: [{
21 | name: fileExtension.toUpperCase(),
22 | extensions: [fileExtension]
23 | }]
24 | });
25 |
26 | if (filePath) {
27 | // 写入文件内容
28 | await writeTextFile(filePath, content);
29 | return { success: true, path: filePath };
30 | }
31 | return { success: false, message: '用户取消了保存操作' };
32 | } catch (error) {
33 | console.error('保存文件失败:', error);
34 | return { success: false, message: `保存文件失败: ${error}` };
35 | }
36 | },
37 |
38 | /**
39 | * 从本地打开文件
40 | * @param fileExtensions 允许的文件扩展名数组
41 | */
42 | async openFile(fileExtensions = ['md']) {
43 | try {
44 | // 使用新的 API
45 | const selected = await open({
46 | multiple: false,
47 | filters: [{
48 | name: '文档',
49 | extensions: fileExtensions
50 | }]
51 | });
52 |
53 | if (selected && typeof selected === 'string') {
54 | // 读取文件内容
55 | const content = await readTextFile(selected);
56 | return { success: true, content, path: selected };
57 | }
58 | return { success: false, message: '用户取消了打开操作' };
59 | } catch (error) {
60 | console.error('打开文件失败:', error);
61 | return { success: false, message: `打开文件失败: ${error}` };
62 | }
63 | },
64 |
65 | /**
66 | * 导出HTML内容到文件
67 | * @param htmlContent HTML内容
68 | * @param fileName 默认文件名
69 | */
70 | async exportHtml(htmlContent: string, fileName = 'content') {
71 | return this.saveFile(
72 | `${htmlContent}
`,
73 | fileName,
74 | 'html'
75 | );
76 | },
77 |
78 | /**
79 | * 导出Markdown内容到文件
80 | * @param markdownContent Markdown内容
81 | * @param fileName 默认文件名
82 | */
83 | async exportMarkdown(markdownContent: string, fileName = 'content') {
84 | return this.saveFile(markdownContent, fileName, 'md');
85 | }
86 | };
87 |
88 | export default tauriFs;
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/InsertFormDialog.vue:
--------------------------------------------------------------------------------
1 |
38 |
39 |
40 |
85 |
86 |
87 |
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MD-Tauri
2 |
3 |
4 |

5 |
6 |
7 |
8 |
9 | [](https://github.com/CrazyMrYan/md-tauri/actions/workflows/code-review.yml)
10 | [](https://github.com/CrazyMrYan/md-tauri/actions/workflows/cross-platform-build.yml)
11 |
12 | **简体中文** | [English](README_en.md)
13 |
14 |
15 |
16 | 一个使用 Tauri 和 Vue 3 构建的现代化 Markdown 编辑器,为 Markdown 编辑和预览提供流畅的桌面体验。
17 |
18 | ## 特性
19 |
20 | - 🚀 使用 Tauri + Vue 3 构建,实现最佳桌面性能
21 | - 📝 实时 Markdown 预览
22 | - 🎨 代码语法高亮支持
23 | - 📊 Mermaid 图表支持
24 | - 🧮 数学公式渲染
25 | - 🖼️ 多种图片上传选项
26 | - 💾 草稿自动保存
27 | - 🎯 自定义主题和 CSS 样式
28 | - 📤 导入/导出功能
29 |
30 | ## 安装包
31 |
32 | | 系统 | 安装包 |
33 | | -------------------------------------- | --------------------------------------------------------------------------------------- |
34 | |  | [下载](https://github.com/CrazyMrYan/md-tauri/releases/latest) (.dmg) |
35 | |  | [下载](https://github.com/CrazyMrYan/md-tauri/releases/latest) (.msi) |
36 | |  | [下载](https://github.com/CrazyMrYan/md-tauri/releases/latest) (.deb) |
37 |
38 | ## 示意图
39 |
40 | 
41 |
42 | ## 开发设置
43 |
44 | ### 环境要求
45 |
46 | - Node.js >= 20
47 | - Rust (Tauri 所需)
48 | - Git
49 |
50 | ### 安装
51 |
52 | ```bash
53 | # 克隆仓库
54 | git@github.com:CrazyMrYan/md-tauri.git
55 | cd md-tauri
56 |
57 | # 安装依赖
58 | npm install
59 | ```
60 |
61 | ### 开发命令
62 |
63 | ```bash
64 | # 启动开发服务器
65 | npm run tauri:dev
66 |
67 | # 构建生产版本
68 | npm run tauri:build
69 |
70 | # 运行网页版本(不含 Tauri)
71 | npm run dev:web
72 |
73 | # 代码检查
74 | npm run lint
75 | ```
76 |
77 | ## 构建
78 |
79 | 构建生产版本:
80 |
81 | ```bash
82 | npm run tauri:build
83 | ```
84 |
85 | 这将在 `src-tauri/target/release` 目录下创建对应平台的可执行文件。
86 |
87 | ## 技术栈
88 |
89 | - Tauri
90 | - Vue 3
91 | - TypeScript
92 | - Vite
93 | - TailwindCSS
94 | - CodeMirror
95 | - Marked
96 | - Mermaid
97 | - 等等...
98 |
99 | ## 贡献指南
100 |
101 | 1. Fork 本仓库
102 | 2. 创建特性分支 (`git checkout -b feature/amazing-feature`)
103 | 3. 提交更改 (`git commit -m '添加一些很棒的特性'`)
104 | 4. 推送到分支 (`git push origin feature/amazing-feature`)
105 | 5. 提交 Pull Request
106 |
107 | ## 开源协议
108 |
109 | 本项目采用 [MIT 协议](LICENSE) 开源。
110 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import type { PropertiesHyphen } from 'csstype'
2 |
3 | import type { Token } from 'marked'
4 |
5 | type GFMBlock = `blockquote_note` | `blockquote_tip` | `blockquote_important` | `blockquote_warning` | `blockquote_caution` | `blockquote_title` | `blockquote_title_note` | `blockquote_title_tip` | `blockquote_title_important` | `blockquote_title_warning` | `blockquote_title_caution` | `blockquote_p` | `blockquote_p_note` | `blockquote_p_tip` | `blockquote_p_important` | `blockquote_p_warning` | `blockquote_p_caution`
6 | export type Block = `container` | `h1` | `h2` | `h3` | `h4` | `h5` | `h6` | `p` | `blockquote` | `blockquote_p` | `code_pre` | `code` | `image` | `ol` | `ul` | `footnotes` | `figure` | `hr` | GFMBlock
7 | export type Inline = `listitem` | `codespan` | `link` | `wx_link` | `strong` | `table` | `thead` | `td` | `footnote` | `figcaption` | `em`
8 |
9 | interface CustomCSSProperties {
10 | [`--md-primary-color`]?: string
11 | [key: `--${string}`]: string | undefined
12 | }
13 |
14 | export type ExtendedProperties = PropertiesHyphen & CustomCSSProperties
15 |
16 | export interface Theme {
17 | base: ExtendedProperties
18 | block: Record
19 | inline: Record
20 | }
21 |
22 | export interface IOpts {
23 | theme: Theme
24 | fonts: string
25 | size: string
26 | isUseIndent: boolean
27 | legend?: string
28 | citeStatus?: boolean
29 | countStatus?: boolean
30 | }
31 |
32 | export type ThemeStyles = Record
33 |
34 | export interface IConfigOption {
35 | label: string
36 | value: VT
37 | desc: string
38 | }
39 |
40 | /**
41 | * Options for the `markedAlert` extension.
42 | */
43 | export interface AlertOptions {
44 | className?: string
45 | variants?: AlertVariantItem[]
46 | styles?: ThemeStyles
47 | }
48 |
49 | /**
50 | * Configuration for an alert type.
51 | */
52 | export interface AlertVariantItem {
53 | type: string
54 | icon: string
55 | title?: string
56 | titleClassName?: string
57 | }
58 |
59 | /**
60 | * Represents an alert token.
61 | */
62 | export interface Alert {
63 | type: `alert`
64 | meta: {
65 | className: string
66 | variant: string
67 | icon: string
68 | title: string
69 | titleClassName: string
70 | }
71 | raw: string
72 | text: string
73 | tokens: Token[]
74 | }
75 |
76 | export interface PostAccount {
77 | avatar: string
78 | displayName: string
79 | home: string
80 | icon: string
81 | supportTypes: string[]
82 | title: string
83 | type: string
84 | uid: string
85 | checked: boolean
86 | status?: string
87 | error?: string
88 | }
89 |
90 | export interface Post {
91 | title: string
92 | desc: string
93 | thumb: string
94 | content: string
95 | markdown: string
96 | accounts: PostAccount[]
97 | }
98 |
--------------------------------------------------------------------------------
/src/utils/MDKatex.js:
--------------------------------------------------------------------------------
1 | const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n$]))\1(?=[\s?!.,:?!。,:]|$)/
2 | const inlineRuleNonStandard = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n$]))\1/ // Non-standard, even if there are no spaces before and after $ or $$, try to parse
3 |
4 | const blockRule = /^(\${1,2})\n((?:\\[\s\S]|[^\\])+?)\n\1(?:\n|$)/
5 |
6 | function createRenderer(display) {
7 | return (token) => {
8 | window.MathJax.texReset()
9 | const mjxContainer = window.MathJax.tex2svg(token.text, { display })
10 | const svg = mjxContainer.firstChild
11 | const width = svg.style[`min-width`] || svg.getAttribute(`width`)
12 | svg.removeAttribute(`width`)
13 | svg.style = `max-width: 300vw !important;`
14 | svg.style.width = width
15 | svg.style.display = `initial`
16 | if (display) {
17 | return ``
18 | }
19 | return `${svg.outerHTML}`
20 | }
21 | }
22 |
23 | function inlineKatex(options, renderer) {
24 | const nonStandard = options && options.nonStandard
25 | const ruleReg = nonStandard ? inlineRuleNonStandard : inlineRule
26 | return {
27 | name: `inlineKatex`,
28 | level: `inline`,
29 | start(src) {
30 | let index
31 | let indexSrc = src
32 |
33 | while (indexSrc) {
34 | index = indexSrc.indexOf(`$`)
35 | if (index === -1) {
36 | return
37 | }
38 | const f = nonStandard ? index > -1 : index === 0 || indexSrc.charAt(index - 1) === ` `
39 | if (f) {
40 | const possibleKatex = indexSrc.substring(index)
41 |
42 | if (possibleKatex.match(ruleReg)) {
43 | return index
44 | }
45 | }
46 |
47 | indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, ``)
48 | }
49 | },
50 | tokenizer(src) {
51 | const match = src.match(ruleReg)
52 | if (match) {
53 | return {
54 | type: `inlineKatex`,
55 | raw: match[0],
56 | text: match[2].trim(),
57 | displayMode: match[1].length === 2,
58 | }
59 | }
60 | },
61 | renderer,
62 | }
63 | }
64 |
65 | function blockKatex(options, renderer) {
66 | return {
67 | name: `blockKatex`,
68 | level: `block`,
69 | tokenizer(src) {
70 | const match = src.match(blockRule)
71 | if (match) {
72 | return {
73 | type: `blockKatex`,
74 | raw: match[0],
75 | text: match[2].trim(),
76 | displayMode: match[1].length === 2,
77 | }
78 | }
79 | },
80 | renderer,
81 | }
82 | }
83 |
84 | export function MDKatex(options = {}) {
85 | return {
86 | extensions: [
87 | inlineKatex(options, createRenderer(false)),
88 | blockKatex(options, createRenderer(true)),
89 | ],
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 | 微信 Markdown 编辑器
13 |
14 |
65 |
66 |
67 |
68 |
71 |
72 |
73 |
致力于让 Markdown 编辑更简单
74 |
正在加载编辑器
75 |
76 |
77 |
83 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | const animate = require(`tailwindcss-animate`)
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | darkMode: [`class`],
6 | safelist: [`dark`],
7 | prefix: ``,
8 | experimental: {
9 | optimizeUniversalDefaults: true,
10 | },
11 |
12 | content: [
13 | `./pages/**/*.{ts,tsx,vue}`,
14 | `./components/**/*.{ts,tsx,vue}`,
15 | `./app/**/*.{ts,tsx,vue}`,
16 | `./src/**/*.{ts,tsx,vue}`,
17 | ],
18 |
19 | theme: {
20 | extend: {
21 | colors: {
22 | border: `hsl(var(--border))`,
23 | input: `hsl(var(--input))`,
24 | ring: `hsl(var(--ring))`,
25 | background: `hsl(var(--background))`,
26 | foreground: `hsl(var(--foreground))`,
27 | primary: {
28 | DEFAULT: `hsl(var(--primary))`,
29 | foreground: `hsl(var(--primary-foreground))`,
30 | },
31 | secondary: {
32 | DEFAULT: `hsl(var(--secondary))`,
33 | foreground: `hsl(var(--secondary-foreground))`,
34 | },
35 | destructive: {
36 | DEFAULT: `hsl(var(--destructive))`,
37 | foreground: `hsl(var(--destructive-foreground))`,
38 | },
39 | muted: {
40 | DEFAULT: `hsl(var(--muted))`,
41 | foreground: `hsl(var(--muted-foreground))`,
42 | },
43 | accent: {
44 | DEFAULT: `hsl(var(--accent))`,
45 | foreground: `hsl(var(--accent-foreground))`,
46 | },
47 | popover: {
48 | DEFAULT: `hsl(var(--popover))`,
49 | foreground: `hsl(var(--popover-foreground))`,
50 | },
51 | card: {
52 | DEFAULT: `hsl(var(--card))`,
53 | foreground: `hsl(var(--card-foreground))`,
54 | },
55 | },
56 | borderRadius: {
57 | xl: `calc(var(--radius) + 4px)`,
58 | lg: `var(--radius)`,
59 | md: `calc(var(--radius) - 2px)`,
60 | sm: `calc(var(--radius) - 4px)`,
61 | },
62 | keyframes: {
63 | 'accordion-down': {
64 | from: { height: 0 },
65 | to: { height: `var(--radix-accordion-content-height)` },
66 | },
67 | 'accordion-up': {
68 | from: { height: `var(--radix-accordion-content-height)` },
69 | to: { height: 0 },
70 | },
71 | 'collapsible-down': {
72 | from: { height: 0 },
73 | to: { height: `var(--radix-collapsible-content-height)` },
74 | },
75 | 'collapsible-up': {
76 | from: { height: `var(--radix-collapsible-content-height)` },
77 | to: { height: 0 },
78 | },
79 | },
80 | animation: {
81 | 'accordion-down': `accordion-down 0.2s ease-out`,
82 | 'accordion-up': `accordion-up 0.2s ease-out`,
83 | 'collapsible-down': `collapsible-down 0.2s ease-in-out`,
84 | 'collapsible-up': `collapsible-up 0.2s ease-in-out`,
85 | },
86 | },
87 | },
88 | plugins: [animate],
89 | }
90 |
--------------------------------------------------------------------------------
/public/mpmd/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README_en.md:
--------------------------------------------------------------------------------
1 | # MD-Tauri
2 |
3 |
4 |

5 |
6 |
7 |
8 |
9 | [](https://github.com/CrazyMrYan/md-tauri/actions/workflows/code-review.yml)
10 | [](https://github.com/CrazyMrYan/md-tauri/actions/workflows/cross-platform-build.yml)
11 |
12 | [简体中文](README.md) | **English**
13 |
14 |
15 |
16 | A modern Markdown editor built with Tauri and Vue 3, providing a seamless desktop experience for Markdown editing and preview.
17 |
18 | ## Features
19 |
20 | - 🚀 Built with Tauri + Vue 3 for optimal desktop performance
21 | - 📝 Real-time Markdown preview
22 | - 🎨 Support for code syntax highlighting
23 | - 📊 Mermaid diagram support
24 | - 🧮 Mathematical formula rendering
25 | - 🖼️ Multiple image upload options
26 | - 💾 Auto-save drafts
27 | - 🎯 Custom theme and CSS styling
28 | - 📤 Import/Export functionality
29 |
30 | ## Installation Package
31 |
32 | | System | Package |
33 | | -------------------------------------- | --------------------------------------------------------------------------------------- |
34 | |  | [Download](https://github.com/CrazyMrYan/md-tauri/releases/latest) (.dmg) |
35 | |  | [Download](https://github.com/CrazyMrYan/md-tauri/releases/latest) (.msi) |
36 | |  | [Download](https://github.com/CrazyMrYan/md-tauri/releases/latest) (.deb) |
37 |
38 | ## Diagram
39 |
40 | 
41 |
42 | ## Development Setup
43 |
44 | ### Prerequisites
45 |
46 | - Node.js >= 20
47 | - Rust (for Tauri)
48 | - Git
49 |
50 | ### Installation
51 |
52 | ```bash
53 | # Clone the repository
54 | git@github.com:CrazyMrYan/md-tauri.git
55 | cd md-tauri
56 |
57 | # Install dependencies
58 | npm install
59 | ```
60 |
61 | ### Development Commands
62 |
63 | ```bash
64 | # Start development server
65 | npm run tauri:dev
66 |
67 | # Build for production
68 | npm run tauri:build
69 |
70 | # Run web version (without Tauri)
71 | npm run dev:web
72 |
73 | # Lint files
74 | npm run lint
75 | ```
76 |
77 | ## Build
78 |
79 | To build the application for production:
80 |
81 | ```bash
82 | npm run tauri:build
83 | ```
84 |
85 | This will create platform-specific binaries in the `src-tauri/target/release` directory.
86 |
87 | ## Tech Stack
88 |
89 | - Tauri
90 | - Vue 3
91 | - TypeScript
92 | - Vite
93 | - TailwindCSS
94 | - CodeMirror
95 | - Marked
96 | - Mermaid
97 | - And more...
98 |
99 | ## Contributing
100 |
101 | 1. Fork the repository
102 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
103 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
104 | 4. Push to the branch (`git push origin feature/amazing-feature`)
105 | 5. Open a Pull Request
106 |
107 | ## License
108 |
109 | This project is open source and available under the [MIT License](LICENSE).
--------------------------------------------------------------------------------
/src/assets/example/markdown.md:
--------------------------------------------------------------------------------
1 | # 探索 Markdown 的奇妙世界
2 |
3 | 欢迎来到 Markdown 的奇妙世界!无论你是写作爱好者、开发者、博主,还是想要简单记录点什么的人,Markdown 都能成为你新的好伙伴。它不仅让写作变得简单明了,还能轻松地将内容转化为漂亮的网页格式。今天,我们将全面探讨 Markdown 的基础和进阶语法,让你在这个过程中充分享受写作的乐趣!
4 |
5 | Markdown 是一种轻量级标记语言,用于格式化纯文本。它以简单、直观的语法而著称,可以快速地生成 HTML。Markdown 是写作与代码的完美结合,既简单又强大。
6 |
7 | ## Markdown 基础语法
8 |
9 | ### 1. 标题:让你的内容层次分明
10 |
11 | 用 `#` 号来创建标题。标题从 `#` 开始,`#` 的数量表示标题的级别。
12 |
13 | ```markdown
14 | # 一级标题
15 |
16 | ## 二级标题
17 |
18 | ### 三级标题
19 |
20 | #### 四级标题
21 | ```
22 |
23 | 以上代码将渲染出一组层次分明的标题,使你的内容井井有条。
24 |
25 | ### 2. 段落与换行:自然流畅
26 |
27 | Markdown 中的段落就是一行接一行的文本。要创建新段落,只需在两行文本之间空一行。
28 |
29 | ### 3. 字体样式:强调你的文字
30 |
31 | - **粗体**:用两个星号或下划线包裹文字,如 `**粗体**` 或 `__粗体__`。
32 | - _斜体_:用一个星号或下划线包裹文字,如 `*斜体*` 或 `_斜体_`。
33 | - ~~删除线~~:用两个波浪线包裹文字,如 `~~删除线~~`。
34 |
35 | 这些简单的标记可以让你的内容更有层次感和重点突出。
36 |
37 | ### 4. 列表:整洁有序
38 |
39 | - **无序列表**:用 `-`、`*` 或 `+` 加空格开始一行。
40 | - **有序列表**:使用数字加点号(`1.`、`2.`)开始一行。
41 |
42 | 在列表中嵌套其他内容?只需缩进即可实现嵌套效果。
43 |
44 | - 无序列表项 1
45 | 1. 嵌套有序列表项 1
46 | 2. 嵌套有序列表项 2
47 | - 无序列表项 2
48 |
49 | 1. 有序列表项 1
50 | 2. 有序列表项 2
51 |
52 | ### 5. 链接与图片:丰富内容
53 |
54 | - **链接**:用方括号和圆括号创建链接 `[显示文本](链接地址)`。
55 | - **图片**:和链接类似,只需在前面加上 `!`,如 ``。
56 |
57 | 轻松实现富媒体内容展示!
58 |
59 | > 因微信公众号平台不支持除公众号内容以外的链接,故其他平台的链接,会呈现链接样式但无法点击跳转。
60 |
61 | > 对于这些链接请注意明文书写,或点击左上角「格式->微信外链接转底部引用」开启引用,这样就可以在底部观察到链接指向。
62 |
63 | ### 6. 引用:引用名言或引人深思的句子
64 |
65 | 使用 `>` 来创建引用,只需在文本前面加上它。多层引用?在前一层 `>` 后再加一个就行。
66 |
67 | > 这是一个引用
68 | >
69 | > > 这是一个嵌套引用
70 |
71 | 这让你的引用更加富有层次感。
72 |
73 | ### 7. 代码块:展示你的代码
74 |
75 | - **行内代码**:用反引号包裹,如 `code`。
76 | - **代码块**:用三个反引号包裹,并指定语言,如:
77 |
78 | ```js
79 | console.log("Hello, CrazyYan!");
80 | ```
81 |
82 | 语法高亮让你的代码更易读。
83 |
84 | ### 8. 分割线:分割内容
85 |
86 | 用三个或更多的 `-`、`*` 或 `_` 来创建分割线。
87 |
88 | ---
89 |
90 | 为你的内容添加视觉分隔。
91 |
92 | ### 9. 表格:清晰展示数据
93 |
94 | Markdown 支持简单的表格,用 `|` 和 `-` 分隔单元格和表头。
95 |
96 | | 排名 | 品牌 | 数量(万) |
97 | | ---- | ------ | ---------- |
98 | | 1 | 比亚迪 | 160.71 |
99 | | 2 | 大众 | 126.66 |
100 | | 3 | 奇瑞 | 105.77 |
101 |
102 | 这样的表格让数据展示更为清爽!
103 |
104 | > 手动编写标记太麻烦?我们提供了便捷方式。左上方点击「编辑->插入表格」,即可快速实现表格渲染。
105 |
106 | ## Markdown 进阶技巧
107 |
108 | ### 1. LaTeX 公式:完美展示数学表达式
109 |
110 | Markdown 允许嵌入 LaTeX 语法展示数学公式:
111 |
112 | - **行内公式**:用 `$` 包裹公式,如 $E = mc^2$。
113 | - **块级公式**:用 `$$` 包裹公式,如:
114 |
115 | $$
116 | \begin{aligned}
117 | d_{i, j} &\leftarrow d_{i, j} + 1 \\
118 | d_{i, y + 1} &\leftarrow d_{i, y + 1} - 1 \\
119 | d_{x + 1, j} &\leftarrow d_{x + 1, j} - 1 \\
120 | d_{x + 1, y + 1} &\leftarrow d_{x + 1, y + 1} + 1
121 | \end{aligned}
122 | $$
123 |
124 | 这是展示复杂数学表达的利器!
125 |
126 | ### 2. Mermaid 流程图:可视化流程
127 |
128 | Mermaid 是强大的可视化工具,可以在 Markdown 中创建流程图、时序图等。
129 |
130 | ```mermaid
131 | graph LR
132 | A[GraphCommand] --> B[update]
133 | A --> C[goto]
134 | A --> D[send]
135 |
136 | B --> B1[更新状态]
137 | C --> C1[流程控制]
138 | D --> D1[消息传递]
139 | ```
140 |
141 | ```mermaid
142 | graph TD;
143 | A-->B;
144 | A-->C;
145 | B-->D;
146 | C-->D;
147 | ```
148 |
149 | ```mermaid
150 | pie
151 | title Key elements in Product X
152 | "Calcium" : 42.96
153 | "Potassium" : 50.05
154 | "Magnesium" : 10.01
155 | "Iron" : 5
156 | ```
157 |
158 | ```mermaid
159 | pie
160 | title 为什么总是宅在家里?
161 | "喜欢宅" : 45
162 | "天气太热" : 70
163 | "穷" : 500
164 | "没人约" : 95
165 | ```
166 |
167 | 这种方式不仅能直观展示流程,还能提升文档的专业性。
168 |
169 | > 更多用法,参见:[Mermaid User Guide](https://mermaid.js.org/intro/getting-started.html)。
170 |
--------------------------------------------------------------------------------
/.github/actions/process-artifacts/action.yml:
--------------------------------------------------------------------------------
1 | # .github/actions/process-artifacts/action.yml
2 | name: 'Process Artifacts'
3 | description: 'Process and rename build artifacts'
4 |
5 | inputs:
6 | os:
7 | description: 'Operating system'
8 | required: true
9 | rust-target:
10 | description: 'Rust target'
11 | required: true
12 |
13 | runs:
14 | using: "composite"
15 | steps:
16 | - name: Get Package Version
17 | id: package-version
18 | shell: bash
19 | run: |
20 | VERSION=$(node -p "require('./package.json').version")
21 | echo "version=$VERSION" >> $GITHUB_OUTPUT
22 |
23 | - name: Rename Artifacts with Version
24 | shell: bash
25 | run: |
26 | VERSION="${{ steps.package-version.outputs.version }}"
27 | OS="${{ inputs.os }}"
28 | RUST_TARGET="${{ inputs.rust-target }}"
29 |
30 | # 使用传入的rust目标平台
31 | ARTIFACTS_DIR="src-tauri/target/$RUST_TARGET/release/bundle"
32 | echo "使用构建产物目录: $ARTIFACTS_DIR"
33 |
34 | # 检查目录是否存在
35 | if [ ! -d "$ARTIFACTS_DIR" ]; then
36 | echo "警告: 目标平台目录不存在: $ARTIFACTS_DIR"
37 | echo "尝试使用通用路径..."
38 | ARTIFACTS_DIR="src-tauri/target/release/bundle"
39 |
40 | if [ ! -d "$ARTIFACTS_DIR" ]; then
41 | echo "错误: 构建产物目录不存在: $ARTIFACTS_DIR"
42 | echo "列出可用目录:"
43 | find src-tauri/target -type d -name "bundle" | sort
44 | exit 1
45 | fi
46 | fi
47 |
48 | # 列出构建产物目录内容以便调试
49 | echo "构建产物目录内容:"
50 | find "$ARTIFACTS_DIR" -type f | sort
51 |
52 | case $OS in
53 | Linux)
54 | for file in $ARTIFACTS_DIR/deb/*.deb; do
55 | if [ -f "$file" ]; then
56 | mv "$file" "${file%.deb}-$VERSION.deb"
57 | fi
58 | done
59 | for file in $ARTIFACTS_DIR/appimage/*.AppImage; do
60 | if [ -f "$file" ]; then
61 | mv "$file" "${file%.AppImage}-$VERSION.AppImage"
62 | fi
63 | done
64 | ;;
65 | Windows)
66 | for file in $ARTIFACTS_DIR/msi/*.msi; do
67 | if [ -f "$file" ]; then
68 | mv "$file" "${file%.msi}-$VERSION.msi"
69 | fi
70 | done
71 | ;;
72 | macOS)
73 | # 首先检查DMG目录是否存在
74 | if [ ! -d "$ARTIFACTS_DIR/dmg" ]; then
75 | echo "DMG目录不存在,创建目录: $ARTIFACTS_DIR/dmg"
76 | mkdir -p "$ARTIFACTS_DIR/dmg"
77 | fi
78 |
79 | # 查找DMG文件
80 | DMG_FILES=$(find "$ARTIFACTS_DIR" -name "*.dmg")
81 | if [ -z "$DMG_FILES" ]; then
82 | echo "错误: 没有找到任何DMG文件"
83 | # 检查app目录是否存在,这是DMG文件的源
84 | if [ -d "$ARTIFACTS_DIR/macos" ]; then
85 | echo "找到macos目录,内容如下:"
86 | ls -la "$ARTIFACTS_DIR/macos"
87 | fi
88 | if [ -d "$ARTIFACTS_DIR/app" ]; then
89 | echo "找到app目录,内容如下:"
90 | ls -la "$ARTIFACTS_DIR/app"
91 | fi
92 | exit 1
93 | else
94 | echo "找到以下DMG文件:"
95 | echo "$DMG_FILES"
96 |
97 | # 处理每个找到的DMG文件
98 | for file in $DMG_FILES; do
99 | filename=$(basename "$file" .dmg)
100 | target_file="$ARTIFACTS_DIR/dmg/$filename-$VERSION.dmg"
101 | echo "重命名: $file -> $target_file"
102 | mv "$file" "$target_file"
103 | done
104 | fi
105 | ;;
106 | esac
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/EditorHeader/PostTaskDialog.vue:
--------------------------------------------------------------------------------
1 |
55 |
56 |
57 |
113 |
114 |
115 |
120 |
--------------------------------------------------------------------------------
/src/components/CodemirrorEditor/EditorHeader/StyleDropdown.vue:
--------------------------------------------------------------------------------
1 |
56 |
57 |
58 |
59 | 样式
60 |
61 |
67 |
68 |
74 |
80 |
86 |
92 |
98 |
99 |
100 |
101 |
102 | 自定义主题色
103 |
104 |
105 |
115 |
116 |
117 |
118 |
119 | 自定义 CSS
120 |
121 |
122 |
123 | Mac 代码块
124 |
125 |
126 |
127 | 重置
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "md",
3 | "type": "module",
4 | "version": "1.1.2",
5 | "private": false,
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "npm run dev",
9 | "dev": "vite --host",
10 | "build": "tauri build",
11 | "build:only": "node --no-node-snapshot ./node_modules/vite/bin/vite.js build",
12 | "build:h5-netlify": "run-p type-check \"build:h5-netlify:only {@}\" --",
13 | "build:h5-netlify:only": "cross-env SERVER_ENV=NETLIFY vite build",
14 | "build:cli": "npm run build && npx shx rm -rf md-cli/dist && npx shx rm -rf dist/**/*.map && npx shx cp -r dist md-cli/ && cd md-cli && npm pack",
15 | "build:analyze": "cross-env ANALYZE=true vite build",
16 | "preview": "npm run build && vite preview",
17 | "ext:dev": "wxt",
18 | "ext:zip": "wxt zip",
19 | "firefox:dev": "wxt -b firefox",
20 | "firefox:zip": "wxt zip -b firefox",
21 | "lint": "eslint . --fix",
22 | "type-check": "vue-tsc --build --force",
23 | "postinstall": "simple-git-hooks && wxt prepare",
24 | "serve": "tauri dev",
25 | "tauri": "tauri",
26 | "dev:web": "vite --host",
27 | "tauri:dev": "LANG=zh_CN.UTF-8 && tauri dev",
28 | "tauri:build": "NODE_OPTIONS='--max-old-space-size=4096' tauri build"
29 | },
30 | "dependencies": {
31 | "@aws-sdk/client-s3": "^3.733.0",
32 | "@tauri-apps/api": "^1.5.3",
33 | "@tauri-apps/cli": "^1.5.9",
34 | "@vee-validate/yup": "^4.15.0",
35 | "@vueuse/core": "^12.5.0",
36 | "axios": "^1.8.2",
37 | "buffer-from": "^1.1.2",
38 | "class-variance-authority": "^0.7.1",
39 | "clsx": "^2.1.1",
40 | "codemirror": "^5.65.17",
41 | "core-js": "^3.40.0",
42 | "cos-js-sdk-v5": "^1.8.7",
43 | "crypto-js": "^4.2.0",
44 | "csstype": "^3.1.3",
45 | "dompurify": "^3.2.4",
46 | "es-toolkit": "^1.27.0",
47 | "form-data": "4.0.1",
48 | "front-matter": "^4.0.2",
49 | "github-markdown-css": "^5.8.1",
50 | "highlight.js": "^11.11.1",
51 | "juice": "^11.0.0",
52 | "lucide-vue-next": "^0.473.0",
53 | "marked": "^15.0.6",
54 | "mermaid": "^11.4.1",
55 | "minio": "7.1.3",
56 | "node-fetch": "^3.3.2",
57 | "pinia": "^2.3.1",
58 | "qiniu-js": "^3.4.2",
59 | "radix-vue": "^1.9.12",
60 | "reading-time": "^1.5.0",
61 | "tailwind-merge": "^2.6.0",
62 | "tailwindcss-animate": "^1.0.7",
63 | "tiny-oss": "^0.5.1",
64 | "uuid": "^11.1.0",
65 | "vee-validate": "^4.15.0",
66 | "vue": "^3.5.13",
67 | "vue-pick-colors": "^1.8.0",
68 | "vue-sonner": "^1.3.0",
69 | "yup": "^1.6.1"
70 | },
71 | "devDependencies": {
72 | "@antfu/eslint-config": "3.11.0",
73 | "@types/buffer-from": "^1.1.3",
74 | "@types/codemirror": "^5.60.15",
75 | "@types/crypto-js": "^4.2.2",
76 | "@types/node": "^22.10.7",
77 | "@types/uuid": "^10.0.0",
78 | "@unocss/eslint-plugin": "^0.64.1",
79 | "@vitejs/plugin-vue": "^5.2.1",
80 | "autoprefixer": "^10.4.20",
81 | "cross-env": "^7.0.3",
82 | "eslint": "^9.15.0",
83 | "eslint-plugin-format": "^0.1.2",
84 | "less": "^4.2.2",
85 | "npm-run-all": "^4.1.5",
86 | "postcss": "^8.5.1",
87 | "prettier": "3.3.3",
88 | "rollup-plugin-visualizer": "^5.12.0",
89 | "shx": "^0.3.4",
90 | "simple-git-hooks": "^2.11.1",
91 | "tailwindcss": "^3.4.17",
92 | "typescript": "~5.6.2",
93 | "unocss": "^0.64.1",
94 | "unplugin-auto-import": "^0.18.6",
95 | "unplugin-vue-components": "^0.27.5",
96 | "vite": "^5.4.18",
97 | "vite-plugin-node-polyfills": "^0.23.0",
98 | "vite-plugin-vue-devtools": "^7.6.5",
99 | "vue-tsc": "^2.2.0",
100 | "wxt": "^0.19.29"
101 | },
102 | "overrides": {
103 | "tough-cookie": "^4.1.3",
104 | "ws": "^8.17.1"
105 | },
106 | "resolutions": {
107 | "@babel/runtime": "^7.26.10",
108 | "@babel/runtime-corejs2": "^7.26.10",
109 | "axios": "^1.8.2"
110 | },
111 | "simple-git-hooks": {
112 | "pre-commit": "npx lint-staged"
113 | },
114 | "lint-staged": {
115 | "*": "npm run lint"
116 | }
117 | }
118 |
--------------------------------------------------------------------------------