├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── README.md ├── WriterSuite-ExampleVault ├── .obsidian │ ├── app.json │ ├── app.sync-conflict-20231126-140335-BRA7AFV.json │ ├── appearance.json │ ├── appearance.sync-conflict-20231126-140330-BRA7AFV.json │ ├── backlink.json │ ├── bookmarks.json │ ├── canvas.json │ ├── canvas.sync-conflict-20231126-140322-BRA7AFV.json │ ├── community-plugins.json │ ├── community-plugins.sync-conflict-20231126-140330-BRA7AFV.json │ ├── core-plugins-migration.json │ ├── core-plugins-migration.sync-conflict-20231126-140332-BRA7AFV.json │ ├── core-plugins.json │ ├── core-plugins.sync-conflict-20231126-140323-BRA7AFV.json │ ├── daily-notes.json │ ├── graph.json │ ├── hotkeys.json │ ├── note-composer.json │ ├── page-preview.json │ ├── plugins │ │ ├── easy-typing-obsidian │ │ │ ├── data.json │ │ │ ├── main.js │ │ │ └── manifest.json │ │ ├── homepage │ │ │ ├── data.json │ │ │ ├── main.js │ │ │ ├── manifest.json │ │ │ └── styles.css │ │ ├── obsidian-linter │ │ │ ├── data.json │ │ │ ├── main.js │ │ │ ├── manifest.json │ │ │ └── styles.css │ │ └── obsidian-writer-suite │ │ │ └── data.json │ ├── snippets │ │ └── copilot.css │ ├── starred.json │ ├── surfing-bookmark.json │ ├── switcher.json │ ├── templates.json │ ├── themes │ │ ├── Blue Topaz │ │ │ ├── manifest.json │ │ │ └── theme.css │ │ └── Obsidian Nord │ │ │ ├── manifest.json │ │ │ └── theme.css │ ├── todoist-token │ ├── types.json │ ├── types.sync-conflict-20231126-140327-BRA7AFV.json │ ├── workspace-mobile.json │ ├── workspace-mobile.sync-conflict-20231126-140322-BRA7AFV.json │ ├── workspace.json │ ├── workspaces.json │ ├── workspaces.sync-conflict-20231126-140321-BRA7AFV.json │ └── zk-prefixer.json ├── @附件 │ └── 灵感 │ │ ├── 全局灵感1.md │ │ └── 全局灵感2.md ├── 短篇小说1 │ ├── 信息.md │ ├── 大纲.md │ └── 小说正文.md ├── 短篇小说2 │ ├── 信息.md │ ├── 大纲.md │ └── 小说正文.md ├── 短篇小说3 │ ├── 信息.md │ └── 小说正文.md ├── 长篇小说1 │ ├── 信息.md │ ├── 小说文稿 │ │ ├── 楔子.md │ │ ├── 第1卷 │ │ │ ├── 第1章 .md │ │ │ └── 第2章.md │ │ └── 第2卷 │ │ │ └── 第3章.md │ └── 设定 │ │ └── 大纲 │ │ ├── 总纲 │ │ └── 大纲.md │ │ └── 章纲 │ │ ├── 第一章.md │ │ └── 第二章.md └── 长篇小说2 │ ├── 信息.md │ └── 小说文稿 │ └── 未命名章节.md ├── esbuild.config.mjs ├── images ├── LICENSE ├── image-1.png ├── image-2.png └── image.png ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── helper │ └── WordCount.ts ├── main.ts ├── model │ ├── InspirationModal.ts │ ├── deleteModal.ts │ ├── newBookModal.ts │ ├── newChapterModal.ts │ └── newItemModal.ts └── view │ ├── bookSettingView.ts │ ├── bookShelfView.ts │ └── tocView.ts ├── styles.css ├── tsconfig.json ├── version-bump.mjs └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | main.js 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: "18.x" 19 | 20 | - name: Build plugin 21 | run: | 22 | npm install 23 | npm run build 24 | 25 | - name: Copy files to plugin directory 26 | run: | 27 | cp ./main.js ./WriterSuite-ExampleVault/.obsidian/plugins/obsidian-writer-suite/main.js 28 | cp ./manifest.json ./WriterSuite-ExampleVault/.obsidian/plugins/obsidian-writer-suite/manifest.json 29 | cp ./styles.css ./WriterSuite-ExampleVault/.obsidian/plugins/obsidian-writer-suite/styles.css 30 | 31 | - name: Create zip package 32 | run: | 33 | zip -r WriterSuite-ExampleVault.zip WriterSuite-ExampleVault 34 | 35 | - name: Create release 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | run: | 39 | tag="${GITHUB_REF#refs/tags/}" 40 | 41 | gh release create "$tag" \ 42 | --title="$tag" \ 43 | --draft \ 44 | ./main.js ./manifest.json ./styles.css WriterSuite-ExampleVault.zip 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | /main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | /data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Writer Suite 2 | obsidian小说创作插件 3 | 4 | > ⚠ **警告** 5 | > 1. 插件目前还存在一些小bug,但是基本使用并无大碍 6 | > 2. 插件目前只有中文,后续可能会支持英文 7 | > 3. 诸如文件夹结构暂时无法自定义,目前只能使用我设定的这种文件夹结构 8 | > 4. 需要单独创建一个obsidian仓库使用 9 | > 5. 所有都是基于obsidian的文件和文件夹重新渲染展示,所以当使用同步插件时,插件不同步空文件夹就会导致创建的分类消失,所以创建了分类建议在分类下创建一个文件 10 | 11 | ## 特色 12 | 13 | 1. 涵盖长篇小说和短篇小说 14 | 2. 侧边栏显示大纲,创作时方便参考 15 | 3. 更舒服的目录,不用一层层的在树状文件结构中寻找章节 16 | 4. 全局灵感 17 | 5. 长篇小说的书籍设定部分包含大纲、角色、设定、灵感四大类,可自己创建分类 18 | 6. 基于obsidian的文件结构,零迁移成本。 19 | 7. 每一章节的字数统计以及全书的字数统计。 20 | 8. 以及其他 21 | 22 | ## 文件夹结构 23 | 24 | 仓库根目录中,除一个“@附件”的文件夹之外,一篇小说(无论长篇短篇)一个文件夹。 25 | 26 | ### @附件 27 | 28 | 这个文件夹存放可能的一些附件图片之类,目前有一个`灵感`文件夹,用于存放全局灵感。 29 | 30 | ### 长篇小说 31 | - 信息.md 32 | - 存储小说基本信息 33 | - 小说文稿 34 | - 存放小说文稿,可以分卷 35 | - 设定 36 | - 存放小说设定、大纲等 37 | - 目前存在大纲、设定、角色、灵感四个大分类 38 | - 大分类下可以自定义小分类 39 | 40 | ### 短篇小说 41 | 42 | 短篇小说文件夹下有三个文件 43 | 44 | - 信息.md 45 | - 小说基本信息 46 | - 小说正文.md 47 | - 短篇小说的正文 48 | - 大纲.md 49 | - 小说的大纲文件 50 | 51 | ## 插件功能 52 | 53 | ### 书架视图 54 | 55 | 书架视图,可以显示你创建的所有小说,包含长篇小说和短篇小说。 56 | 57 | #### 新建书籍 58 | 59 | 点击书架视图中右下角的“+”按钮,出现新建书籍对话框。 60 | 61 | 可以定义书籍名称,小说的类型,和简介。 62 | 63 | ### 个人信息 64 | 65 | 可以在插件设置中设置头像(暂时只支持图床链接)和昵称。 66 | 67 | 简单显示创建的短篇小说和长篇小说的数量 68 | 69 | ### 全局灵感 70 | 71 | 显示全局灵感的快捷添加和删除,点击进入文件修改。 72 | 73 | ## 插件截图 74 | 75 | ### 书架视图 76 | 77 | ![alt text](images/image.png) 78 | 79 | ### 短篇小说 80 | 81 | ![alt text](images/image-1.png) 82 | 83 | ### 长篇小说 84 | 85 | ![alt text](images/image-2.png) 86 | 87 | ## 使用 88 | 89 | 从`Releases`下载示例库压缩包,或者下载插件文件添加到自己的插件文件夹中。 -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "promptDelete": false, 3 | "strictLineBreaks": false, 4 | "alwaysUpdateLinks": true, 5 | "readableLineLength": true, 6 | "defaultViewMode": "source", 7 | "attachmentFolderPath": "@附件/图片", 8 | "foldHeading": true, 9 | "showInlineTitle": true, 10 | "newFileLocation": "root", 11 | "userIgnoreFilters": [ 12 | "archetypes/", 13 | "assets/", 14 | "data/", 15 | "i18n/", 16 | "layouts/", 17 | "public/", 18 | "resources/", 19 | "static/", 20 | "themes/" 21 | ], 22 | "livePreview": true, 23 | "newFileFolderPath": "@Inbox", 24 | "showUnsupportedFiles": true, 25 | "newLinkFormat": "relative", 26 | "spellcheck": true, 27 | "pdfExportSettings": { 28 | "includeName": true, 29 | "pageSize": "A4", 30 | "landscape": false, 31 | "margin": "2", 32 | "downscalePercent": 100 33 | }, 34 | "trashOption": "local", 35 | "showFrontmatter": false, 36 | "showLineNumber": false, 37 | "tabSize": 4, 38 | "useMarkdownLinks": false, 39 | "mobilePullAction": "obsidian-linter:lint-file", 40 | "propertiesInDocument": "visible", 41 | "showRibbon": true 42 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/app.sync-conflict-20231126-140335-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | { 2 | "promptDelete": false, 3 | "strictLineBreaks": false, 4 | "alwaysUpdateLinks": true, 5 | "readableLineLength": true, 6 | "defaultViewMode": "source", 7 | "attachmentFolderPath": "01-Extra/Images", 8 | "foldHeading": true, 9 | "showInlineTitle": true, 10 | "newFileLocation": "folder", 11 | "userIgnoreFilters": null, 12 | "livePreview": true, 13 | "newFileFolderPath": "@Inbox", 14 | "showUnsupportedFiles": true, 15 | "newLinkFormat": "shortest", 16 | "spellcheck": true, 17 | "pdfExportSettings": { 18 | "includeName": true, 19 | "pageSize": "A5", 20 | "landscape": false, 21 | "margin": "0", 22 | "downscalePercent": 60 23 | }, 24 | "trashOption": "system", 25 | "showFrontmatter": false, 26 | "showLineNumber": false, 27 | "tabSize": 4, 28 | "useMarkdownLinks": false 29 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/appearance.json: -------------------------------------------------------------------------------- 1 | { 2 | "accentColor": "#7d69e2", 3 | "theme": "moonstone", 4 | "cssTheme": "Obsidian Nord", 5 | "baseFontSize": 30, 6 | "translucency": false, 7 | "nativeMenus": true, 8 | "enabledCssSnippets": [ 9 | "dashboard", 10 | "整齐排列中文-两端对齐", 11 | "nav-labels-zh", 12 | "【日记】Time", 13 | "【日记】timeline", 14 | "【天气】weather", 15 | "【图标】双链图标", 16 | "【图标】预览网址加图标 - External Link Icons", 17 | "【卡片视图】cards", 18 | "【卡片视图】zettelkasten", 19 | "【日记】periodic日历", 20 | "【自定义】myhome 样式", 21 | "custom-admonitions.a95380", 22 | "page-width", 23 | "page-width.css", 24 | "body-width", 25 | "mybutton", 26 | "【主页设置】homepage setting", 27 | "font-size", 28 | "inline-title", 29 | "floating-toc", 30 | "column", 31 | "waterflow", 32 | "【边栏图标】sidepane", 33 | "【便签样式】stickies", 34 | "【侧边栏播放器图标】rightlane music player icon", 35 | "【侧边栏设置】rightlane setting", 36 | "【分栏支持】admonition-col2-col3-flex", 37 | "【进度颜色】colourful progress bar", 38 | "【情绪记录】MoodTracker", 39 | "【日记时间轴】Time", 40 | "【生日倒计时】birthday countdown", 41 | "【收件箱图标】收件箱icon", 42 | "【项目管理】sharetype", 43 | "【音乐播放器】Music Player", 44 | "【主页设置移动端】homepage setting media mobile", 45 | "【AD优化】admonitions", 46 | "myhome-simple", 47 | "Columns", 48 | "card", 49 | "cards", 50 | "idea", 51 | "fullwidth", 52 | "tags", 53 | "min-dataview-table", 54 | "excalidraw", 55 | "info-home", 56 | "banner-icon", 57 | "search", 58 | "novel-home", 59 | "yaml", 60 | "line-width", 61 | "full-width", 62 | "home", 63 | "title", 64 | "global", 65 | "codetab", 66 | "button", 67 | "themes", 68 | "slides", 69 | "box", 70 | "floating_toc", 71 | "appearance", 72 | "list", 73 | "copilot" 74 | ], 75 | "showViewHeader": false, 76 | "baseFontSizeAction": true, 77 | "interfaceFontFamily": "微软雅黑", 78 | "textFontFamily": "霞鹜文楷 GB", 79 | "monospaceFontFamily": "Hack" 80 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/appearance.sync-conflict-20231126-140330-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | { 2 | "accentColor": "", 3 | "theme": "moonstone", 4 | "cssTheme": "Obsidian Nord", 5 | "baseFontSize": 30, 6 | "translucency": false, 7 | "nativeMenus": true, 8 | "enabledCssSnippets": [ 9 | "dashboard", 10 | "整齐排列中文-两端对齐", 11 | "nav-labels-zh", 12 | "【日记】Time", 13 | "【日记】timeline", 14 | "【天气】weather", 15 | "【图标】双链图标", 16 | "【图标】预览网址加图标 - External Link Icons", 17 | "【卡片视图】cards", 18 | "【卡片视图】zettelkasten", 19 | "【日记】periodic日历", 20 | "【自定义】myhome 样式", 21 | "custom-admonitions.a95380", 22 | "page-width", 23 | "page-width.css", 24 | "body-width", 25 | "column", 26 | "mybutton", 27 | "fullwidth", 28 | "cards", 29 | "【主页设置】homepage setting", 30 | "yaml", 31 | "font-size", 32 | "inline-title", 33 | "floating-toc", 34 | "canvas", 35 | "myhome", 36 | "tags", 37 | "list", 38 | "image-captions", 39 | "theme", 40 | "outline", 41 | "writing" 42 | ], 43 | "showViewHeader": true, 44 | "baseFontSizeAction": true, 45 | "interfaceFontFamily": "微软雅黑", 46 | "textFontFamily": "霞鹜文楷", 47 | "monospaceFontFamily": "Hack" 48 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/backlink.json: -------------------------------------------------------------------------------- 1 | { 2 | "backlinkInDocument": false 3 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/bookmarks.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "type": "file", 5 | "ctime": 1683552822481, 6 | "path": "01-科研/未命名思维导图.md" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/canvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "snapToObjects": true, 3 | "snapToGrid": false, 4 | "newFileLocation": "root", 5 | "defaultWheelBehavior": "pan" 6 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/canvas.sync-conflict-20231126-140322-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | { 2 | "snapToObjects": true, 3 | "snapToGrid": true, 4 | "newFileLocation": "root", 5 | "defaultWheelBehavior": "pan" 6 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/community-plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | "dataview", 3 | "quickadd", 4 | "obsidian-latex-suite", 5 | "buttons", 6 | "obsidian-linter", 7 | "easy-typing-obsidian", 8 | "copilot", 9 | "copilot-kimi", 10 | "copilot_kimi", 11 | "homepage", 12 | "writer-suite", 13 | "obsidian-banners" 14 | ] -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/community-plugins.sync-conflict-20231126-140330-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | [ 2 | "cmdr", 3 | "dataview", 4 | "easy-typing-obsidian", 5 | "obsidian-latex-suite", 6 | "table-editor-obsidian", 7 | "obsidian-excalidraw-plugin", 8 | "obsidian-linter", 9 | "tag-wrangler", 10 | "obsidian-pandoc", 11 | "oz-clear-unused-images", 12 | "homepage", 13 | "quickadd", 14 | "remotely-save", 15 | "obsidian-style-settings", 16 | "recent-files-obsidian", 17 | "obsidian-banners" 18 | ] -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/core-plugins-migration.json: -------------------------------------------------------------------------------- 1 | { 2 | "file-explorer": true, 3 | "global-search": true, 4 | "switcher": true, 5 | "graph": true, 6 | "backlink": true, 7 | "outgoing-link": true, 8 | "tag-pane": false, 9 | "page-preview": true, 10 | "daily-notes": false, 11 | "templates": true, 12 | "note-composer": false, 13 | "command-palette": true, 14 | "slash-command": true, 15 | "editor-status": false, 16 | "starred": false, 17 | "markdown-importer": false, 18 | "zk-prefixer": false, 19 | "random-note": false, 20 | "outline": true, 21 | "word-count": true, 22 | "slides": true, 23 | "audio-recorder": false, 24 | "workspaces": true, 25 | "file-recovery": true, 26 | "publish": false, 27 | "sync": false, 28 | "canvas": true, 29 | "bookmarks": false, 30 | "properties": true 31 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/core-plugins-migration.sync-conflict-20231126-140332-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | { 2 | "file-explorer": true, 3 | "global-search": true, 4 | "switcher": true, 5 | "graph": true, 6 | "backlink": true, 7 | "outgoing-link": true, 8 | "tag-pane": true, 9 | "page-preview": true, 10 | "daily-notes": false, 11 | "templates": true, 12 | "note-composer": false, 13 | "command-palette": true, 14 | "slash-command": true, 15 | "editor-status": false, 16 | "starred": false, 17 | "markdown-importer": false, 18 | "zk-prefixer": false, 19 | "random-note": true, 20 | "outline": true, 21 | "word-count": true, 22 | "slides": true, 23 | "audio-recorder": false, 24 | "workspaces": true, 25 | "file-recovery": true, 26 | "publish": false, 27 | "sync": false, 28 | "canvas": true, 29 | "bookmarks": false, 30 | "properties": false 31 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/core-plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | "file-explorer", 3 | "global-search", 4 | "switcher", 5 | "graph", 6 | "backlink", 7 | "canvas", 8 | "outgoing-link", 9 | "properties", 10 | "page-preview", 11 | "templates", 12 | "command-palette", 13 | "slash-command", 14 | "outline", 15 | "word-count", 16 | "slides", 17 | "workspaces", 18 | "file-recovery" 19 | ] -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/core-plugins.sync-conflict-20231126-140323-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | [ 2 | "file-explorer", 3 | "global-search", 4 | "switcher", 5 | "graph", 6 | "backlink", 7 | "canvas", 8 | "outgoing-link", 9 | "tag-pane", 10 | "page-preview", 11 | "templates", 12 | "command-palette", 13 | "slash-command", 14 | "random-note", 15 | "outline", 16 | "word-count", 17 | "slides", 18 | "workspaces", 19 | "file-recovery" 20 | ] -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/daily-notes.json: -------------------------------------------------------------------------------- 1 | { 2 | "folder": "03-DailyNotes", 3 | "template": "01-Extra/Templates/DailyNote", 4 | "autorun": true, 5 | "format": "YYYY-MM-DD" 6 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/graph.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapse-filter": false, 3 | "search": "", 4 | "showTags": true, 5 | "showAttachments": false, 6 | "hideUnresolved": true, 7 | "showOrphans": true, 8 | "collapse-color-groups": true, 9 | "colorGroups": [ 10 | { 11 | "query": "path:", 12 | "color": { 13 | "a": 1, 14 | "rgb": 14048348 15 | } 16 | } 17 | ], 18 | "collapse-display": true, 19 | "showArrow": false, 20 | "textFadeMultiplier": 0, 21 | "nodeSizeMultiplier": 1, 22 | "lineSizeMultiplier": 1, 23 | "collapse-forces": false, 24 | "centerStrength": 0.6328125, 25 | "repelStrength": 20, 26 | "linkStrength": 0.440104166666667, 27 | "linkDistance": 30, 28 | "scale": 0.19450022690616783, 29 | "close": false 30 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/hotkeys.json: -------------------------------------------------------------------------------- 1 | { 2 | "daily-notes": [ 3 | { 4 | "modifiers": [ 5 | "Mod" 6 | ], 7 | "key": "D" 8 | } 9 | ], 10 | "obsidian-mind-map:app:markmap-preview": [ 11 | { 12 | "modifiers": [ 13 | "Mod" 14 | ], 15 | "key": "M" 16 | } 17 | ], 18 | "daily-notes:goto-next": [ 19 | { 20 | "modifiers": [ 21 | "Shift" 22 | ], 23 | "key": "ArrowDown" 24 | } 25 | ], 26 | "daily-notes:goto-prev": [ 27 | { 28 | "modifiers": [ 29 | "Shift" 30 | ], 31 | "key": "ArrowUp" 32 | } 33 | ], 34 | "editor:delete-paragraph": [], 35 | "editor:toggle-code": [], 36 | "obsidian-tasks-plugin:toggle-done": [], 37 | "obsidian-tasks-plugin:edit-task": [], 38 | "editor:toggle-source": [], 39 | "obsidian-excalidraw-plugin:insert-link-to-element": [], 40 | "obsidian-excalidraw-plugin:save": [], 41 | "app:toggle-left-sidebar": [ 42 | { 43 | "modifiers": [ 44 | "Mod", 45 | "Shift" 46 | ], 47 | "key": "ArrowLeft" 48 | } 49 | ], 50 | "app:toggle-right-sidebar": [ 51 | { 52 | "modifiers": [ 53 | "Mod", 54 | "Shift" 55 | ], 56 | "key": "ArrowRight" 57 | } 58 | ], 59 | "obsidian-footnotes:insert-footnote": [ 60 | { 61 | "modifiers": [ 62 | "Mod", 63 | "Shift" 64 | ], 65 | "key": "6" 66 | } 67 | ], 68 | "editor:swap-line-up": [ 69 | { 70 | "modifiers": [ 71 | "Mod", 72 | "Shift" 73 | ], 74 | "key": "ArrowUp" 75 | } 76 | ], 77 | "editor:swap-line-down": [ 78 | { 79 | "modifiers": [ 80 | "Mod", 81 | "Shift" 82 | ], 83 | "key": "ArrowDown" 84 | } 85 | ], 86 | "insert-current-time": [ 87 | { 88 | "modifiers": [ 89 | "Alt", 90 | "Mod" 91 | ], 92 | "key": "T" 93 | } 94 | ], 95 | "insert-current-date": [ 96 | { 97 | "modifiers": [ 98 | "Alt", 99 | "Mod" 100 | ], 101 | "key": "D" 102 | } 103 | ], 104 | "editor:toggle-italics": [], 105 | "app:go-forward": [ 106 | { 107 | "modifiers": [ 108 | "Alt" 109 | ], 110 | "key": "ArrowRight" 111 | } 112 | ], 113 | "starred:open": [], 114 | "remotely-save:start-sync": [ 115 | { 116 | "modifiers": [ 117 | "Mod", 118 | "Shift" 119 | ], 120 | "key": "S" 121 | } 122 | ], 123 | "obsidian-excalidraw-plugin:insert-link": [], 124 | "note-composer:merge-file": [], 125 | "workspace:split-horizontal": [ 126 | { 127 | "modifiers": [ 128 | "Alt" 129 | ], 130 | "key": "ArrowDown" 131 | } 132 | ], 133 | "note-composer:split-file": [], 134 | "starred:star-current-file": [], 135 | "graph:open": [], 136 | "graph:open-local": [ 137 | { 138 | "modifiers": [ 139 | "Mod" 140 | ], 141 | "key": "G" 142 | } 143 | ], 144 | "markdown:toggle-preview": [ 145 | { 146 | "modifiers": [ 147 | "Mod" 148 | ], 149 | "key": "Tab" 150 | } 151 | ], 152 | "editor:focus": [], 153 | "obsidian-pangu:pangu-format": [], 154 | "workspaces:open-modal": [], 155 | "editor:toggle-checklist-status": [], 156 | "workspaces:load": [], 157 | "file-explorer:new-file": [ 158 | { 159 | "modifiers": [ 160 | "Mod" 161 | ], 162 | "key": "N" 163 | } 164 | ], 165 | "quickadd:runQuickAdd": [ 166 | { 167 | "modifiers": [ 168 | "Mod" 169 | ], 170 | "key": "L" 171 | } 172 | ], 173 | "easy-typing-obsidian:easy-typing-insert-codeblock": [], 174 | "easy-typing-obsidian:easy-typing-format-article": [ 175 | { 176 | "modifiers": [ 177 | "Mod", 178 | "Shift" 179 | ], 180 | "key": "A" 181 | } 182 | ], 183 | "workspace:next-tab": [ 184 | { 185 | "modifiers": [ 186 | "Mod" 187 | ], 188 | "key": "ArrowDown" 189 | } 190 | ], 191 | "canvas:export-as-image": [], 192 | "quickadd:choice:4e85b1a5-798a-4f0a-9088-bb3d207a54e7": [ 193 | { 194 | "modifiers": [ 195 | "Mod" 196 | ], 197 | "key": "Y" 198 | } 199 | ], 200 | "templater-obsidian:obsidian/模板/单词卡.md": [], 201 | "easy-typing-obsidian:easy-typing-format-switch": [], 202 | "editor:insert-callout": [ 203 | { 204 | "modifiers": [ 205 | "Mod" 206 | ], 207 | "key": "\\" 208 | } 209 | ], 210 | "obsidian-footnotes:insert-autonumbered-footnote": [ 211 | { 212 | "modifiers": [ 213 | "Mod", 214 | "Shift" 215 | ], 216 | "key": "6" 217 | } 218 | ], 219 | "obsidian-citation-plugin:insert-citation": [], 220 | "obsidian-citation-plugin:open-literature-note": [], 221 | "obsidian-citation-plugin:update-bib-data": [], 222 | "obsidian-citation-plugin:insert-markdown-citation": [ 223 | { 224 | "modifiers": [ 225 | "Mod", 226 | "Shift" 227 | ], 228 | "key": "O" 229 | } 230 | ], 231 | "quickadd:choice:271ad8f3-82da-4571-8e47-c28e85181f3c": [ 232 | { 233 | "modifiers": [ 234 | "Alt" 235 | ], 236 | "key": "F" 237 | } 238 | ], 239 | "obsidian-footnotes:insert-named-footnote": [ 240 | { 241 | "modifiers": [ 242 | "Alt" 243 | ], 244 | "key": "F" 245 | } 246 | ], 247 | "insert-template": [ 248 | { 249 | "modifiers": [ 250 | "Mod" 251 | ], 252 | "key": "T" 253 | } 254 | ], 255 | "workspace:new-tab": [], 256 | "dbfolder:active-database-folder-add-new-row": [ 257 | { 258 | "modifiers": [ 259 | "Alt" 260 | ], 261 | "key": "N" 262 | } 263 | ], 264 | "file-explorer:move-file": [ 265 | { 266 | "modifiers": [ 267 | "Mod" 268 | ], 269 | "key": "M" 270 | } 271 | ], 272 | "editor:open-search": [], 273 | "global-search:open": [ 274 | { 275 | "modifiers": [ 276 | "Mod", 277 | "Shift" 278 | ], 279 | "key": "F" 280 | } 281 | ], 282 | "homepage:open-homepage": [ 283 | { 284 | "modifiers": [ 285 | "Mod" 286 | ], 287 | "key": "H" 288 | } 289 | ], 290 | "editor:open-search-replace": [ 291 | { 292 | "modifiers": [ 293 | "Mod" 294 | ], 295 | "key": "F" 296 | } 297 | ], 298 | "app:delete-file": [ 299 | { 300 | "modifiers": [ 301 | "Mod" 302 | ], 303 | "key": "Delete" 304 | } 305 | ], 306 | "app:go-back": [ 307 | { 308 | "modifiers": [ 309 | "Alt" 310 | ], 311 | "key": "ArrowLeft" 312 | } 313 | ], 314 | "quickadd:choice:c9374592-abf3-436e-8b8e-d0a4cda0d383": [ 315 | { 316 | "modifiers": [ 317 | "Mod", 318 | "Shift" 319 | ], 320 | "key": "S" 321 | } 322 | ], 323 | "file-explorer:reveal-active-file": [ 324 | { 325 | "modifiers": [ 326 | "Mod" 327 | ], 328 | "key": "I" 329 | } 330 | ] 331 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/note-composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "askBeforeMerging": false 3 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/page-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag-wrangler:tag-pane": false, 3 | "preview": true 4 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/plugins/easy-typing-obsidian/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tabout": true, 3 | "SelectionEnhance": true, 4 | "IntrinsicSymbolPairs": true, 5 | "BaseObEditEnhance": true, 6 | "FW2HWEnhance": true, 7 | "BetterCodeEdit": true, 8 | "AutoFormat": true, 9 | "ExcludeFiles": "", 10 | "ChineseEnglishSpace": true, 11 | "ChineseNumberSpace": true, 12 | "EnglishNumberSpace": true, 13 | "ChineseNoSpace": true, 14 | "PunctuationSpace": false, 15 | "AutoCapital": false, 16 | "AutoCapitalMode": "typing", 17 | "PunctuationSpaceMode": "global", 18 | "InlineCodeSpaceMode": 0, 19 | "InlineFormulaSpaceMode": 0, 20 | "InlineLinkSpaceMode": 0, 21 | "InlineLinkSmartSpace": false, 22 | "UserDefinedRegSwitch": true, 23 | "UserDefinedRegExp": "{{.*?}}|++\n<.*?>|--\n\\[\\!.*?\\][-+]{0,1}|-+\n(file:///|https?://|ftp://|obsidian://|zotero://|www.)[^\\s()《》。,,!?;;:“”‘’\\)\\(\\[\\]\\{\\}']+|++\n\n[a-zA-Z0-9_\\-.]+@[a-zA-Z0-9_\\-.]+|++\n(?()=>(t||a((t={exports:{}}).exports,t),t.exports),Ye=(a,t)=>{for(var e in t)I(a,e,{get:t[e],enumerable:!0})},pe=(a,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Ve(t))!Ie.call(a,i)&&i!==e&&I(a,i,{get:()=>t[i],enumerable:!(n=Ce(t,i))||n.enumerable});return a};var Ue=(a,t,e)=>(e=a!=null?Re(_e(a)):{},pe(t||!a||!a.__esModule?I(e,"default",{value:a,enumerable:!0}):e,a)),Be=a=>pe(I({},"__esModule",{value:!0}),a);var Oe=We(l=>{"use strict";Object.defineProperty(l,"__esModule",{value:!0});var g=require("obsidian"),X="YYYY-MM-DD",ee="gggg-[W]ww",fe="YYYY-MM",we="YYYY-[Q]Q",ye="YYYY";function S(a){let t=window.app.plugins.getPlugin("periodic-notes");return t&&t.settings?.[a]?.enabled}function E(){try{let{internalPlugins:a,plugins:t}=window.app;if(S("daily")){let{format:o,folder:s,template:p}=t.getPlugin("periodic-notes")?.settings?.daily||{};return{format:o||X,folder:s?.trim()||"",template:p?.trim()||""}}let{folder:e,format:n,template:i}=a.getPluginById("daily-notes")?.instance?.options||{};return{format:n||X,folder:e?.trim()||"",template:i?.trim()||""}}catch(a){console.info("No custom daily note settings found!",a)}}function L(){try{let a=window.app.plugins,t=a.getPlugin("calendar")?.options,e=a.getPlugin("periodic-notes")?.settings?.weekly;if(S("weekly"))return{format:e.format||ee,folder:e.folder?.trim()||"",template:e.template?.trim()||""};let n=t||{};return{format:n.weeklyNoteFormat||ee,folder:n.weeklyNoteFolder?.trim()||"",template:n.weeklyNoteTemplate?.trim()||""}}catch(a){console.info("No custom weekly note settings found!",a)}}function H(){let a=window.app.plugins;try{let t=S("monthly")&&a.getPlugin("periodic-notes")?.settings?.monthly||{};return{format:t.format||fe,folder:t.folder?.trim()||"",template:t.template?.trim()||""}}catch(t){console.info("No custom monthly note settings found!",t)}}function x(){let a=window.app.plugins;try{let t=S("quarterly")&&a.getPlugin("periodic-notes")?.settings?.quarterly||{};return{format:t.format||we,folder:t.folder?.trim()||"",template:t.template?.trim()||""}}catch(t){console.info("No custom quarterly note settings found!",t)}}function R(){let a=window.app.plugins;try{let t=S("yearly")&&a.getPlugin("periodic-notes")?.settings?.yearly||{};return{format:t.format||ye,folder:t.folder?.trim()||"",template:t.template?.trim()||""}}catch(t){console.info("No custom yearly note settings found!",t)}}function ve(...a){let t=[];for(let n=0,i=a.length;n{let Q=n(),J=a.clone().set({hour:Q.get("hour"),minute:Q.get("minute"),second:Q.get("second")});return T&&J.add(parseInt(v,10),f),m?J.format(m.substring(1).trim()):J.format(o)}).replace(/{{\s*yesterday\s*}}/gi,a.clone().subtract(1,"day").format(o)).replace(/{{\s*tomorrow\s*}}/gi,a.clone().add(1,"d").format(o)));return t.foldManager.save(y,d),y}catch(y){console.error(`Failed to create file: '${c}'`,y),new g.Notice("Unable to create new file.")}}function ze(a,t){return t[b(a,"day")]??null}function Ge(){let{vault:a}=window.app,{folder:t}=E(),e=a.getAbstractFileByPath(g.normalizePath(t));if(!e)throw new te("Failed to find daily notes folder");let n={};return g.Vault.recurseChildren(e,i=>{if(i instanceof g.TFile){let o=O(i,"day");if(o){let s=b(o,"day");n[s]=i}}}),n}var ae=class extends Error{};function Qe(){let{moment:a}=window,t=a.localeData()._week.dow,e=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"];for(;t;)e.push(e.shift()),t--;return e}function Je(a){return Qe().indexOf(a.toLowerCase())}async function Pe(a){let{vault:t}=window.app,{template:e,format:n,folder:i}=L(),[o,s]=await A(e),p=a.format(n),d=await C(i,p);try{let r=await t.create(d,o.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi,(c,y,k,P,T,v)=>{let f=window.moment(),m=a.clone().set({hour:f.get("hour"),minute:f.get("minute"),second:f.get("second")});return k&&m.add(parseInt(P,10),T),v?m.format(v.substring(1).trim()):m.format(n)}).replace(/{{\s*title\s*}}/gi,p).replace(/{{\s*time\s*}}/gi,window.moment().format("HH:mm")).replace(/{{\s*(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\s*:(.*?)}}/gi,(c,y,k)=>{let P=Je(y);return a.weekday(P).format(k.trim())}));return window.app.foldManager.save(r,s),r}catch(r){console.error(`Failed to create file: '${d}'`,r),new g.Notice("Unable to create new file.")}}function Ze(a,t){return t[b(a,"week")]??null}function Xe(){let a={};if(!De())return a;let{vault:t}=window.app,{folder:e}=L(),n=t.getAbstractFileByPath(g.normalizePath(e));if(!n)throw new ae("Failed to find weekly notes folder");return g.Vault.recurseChildren(n,i=>{if(i instanceof g.TFile){let o=O(i,"week");if(o){let s=b(o,"week");a[s]=i}}}),a}var ne=class extends Error{};async function Te(a){let{vault:t}=window.app,{template:e,format:n,folder:i}=H(),[o,s]=await A(e),p=a.format(n),d=await C(i,p);try{let r=await t.create(d,o.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi,(c,y,k,P,T,v)=>{let f=window.moment(),m=a.clone().set({hour:f.get("hour"),minute:f.get("minute"),second:f.get("second")});return k&&m.add(parseInt(P,10),T),v?m.format(v.substring(1).trim()):m.format(n)}).replace(/{{\s*date\s*}}/gi,p).replace(/{{\s*time\s*}}/gi,window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi,p));return window.app.foldManager.save(r,s),r}catch(r){console.error(`Failed to create file: '${d}'`,r),new g.Notice("Unable to create new file.")}}function et(a,t){return t[b(a,"month")]??null}function tt(){let a={};if(!Me())return a;let{vault:t}=window.app,{folder:e}=H(),n=t.getAbstractFileByPath(g.normalizePath(e));if(!n)throw new ne("Failed to find monthly notes folder");return g.Vault.recurseChildren(n,i=>{if(i instanceof g.TFile){let o=O(i,"month");if(o){let s=b(o,"month");a[s]=i}}}),a}var ie=class extends Error{};async function at(a){let{vault:t}=window.app,{template:e,format:n,folder:i}=x(),[o,s]=await A(e),p=a.format(n),d=await C(i,p);try{let r=await t.create(d,o.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi,(c,y,k,P,T,v)=>{let f=window.moment(),m=a.clone().set({hour:f.get("hour"),minute:f.get("minute"),second:f.get("second")});return k&&m.add(parseInt(P,10),T),v?m.format(v.substring(1).trim()):m.format(n)}).replace(/{{\s*date\s*}}/gi,p).replace(/{{\s*time\s*}}/gi,window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi,p));return window.app.foldManager.save(r,s),r}catch(r){console.error(`Failed to create file: '${d}'`,r),new g.Notice("Unable to create new file.")}}function nt(a,t){return t[b(a,"quarter")]??null}function it(){let a={};if(!Fe())return a;let{vault:t}=window.app,{folder:e}=x(),n=t.getAbstractFileByPath(g.normalizePath(e));if(!n)throw new ie("Failed to find quarterly notes folder");return g.Vault.recurseChildren(n,i=>{if(i instanceof g.TFile){let o=O(i,"quarter");if(o){let s=b(o,"quarter");a[s]=i}}}),a}var oe=class extends Error{};async function ot(a){let{vault:t}=window.app,{template:e,format:n,folder:i}=R(),[o,s]=await A(e),p=a.format(n),d=await C(i,p);try{let r=await t.create(d,o.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi,(c,y,k,P,T,v)=>{let f=window.moment(),m=a.clone().set({hour:f.get("hour"),minute:f.get("minute"),second:f.get("second")});return k&&m.add(parseInt(P,10),T),v?m.format(v.substring(1).trim()):m.format(n)}).replace(/{{\s*date\s*}}/gi,p).replace(/{{\s*time\s*}}/gi,window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi,p));return window.app.foldManager.save(r,s),r}catch(r){console.error(`Failed to create file: '${d}'`,r),new g.Notice("Unable to create new file.")}}function st(a,t){return t[b(a,"year")]??null}function rt(){let a={};if(!Ae())return a;let{vault:t}=window.app,{folder:e}=R(),n=t.getAbstractFileByPath(g.normalizePath(e));if(!n)throw new oe("Failed to find yearly notes folder");return g.Vault.recurseChildren(n,i=>{if(i instanceof g.TFile){let o=O(i,"year");if(o){let s=b(o,"year");a[s]=i}}}),a}function lt(){let{app:a}=window,t=a.internalPlugins.plugins["daily-notes"];if(t&&t.enabled)return!0;let e=a.plugins.getPlugin("periodic-notes");return e&&e.settings?.daily?.enabled}function De(){let{app:a}=window;if(a.plugins.getPlugin("calendar"))return!0;let t=a.plugins.getPlugin("periodic-notes");return t&&t.settings?.weekly?.enabled}function Me(){let{app:a}=window,t=a.plugins.getPlugin("periodic-notes");return t&&t.settings?.monthly?.enabled}function Fe(){let{app:a}=window,t=a.plugins.getPlugin("periodic-notes");return t&&t.settings?.quarterly?.enabled}function Ae(){let{app:a}=window,t=a.plugins.getPlugin("periodic-notes");return t&&t.settings?.yearly?.enabled}function ct(a){let t={day:E,week:L,month:H,quarter:x,year:R}[a];return t()}function dt(a,t){return{day:ke,month:Te,week:Pe}[a](t)}l.DEFAULT_DAILY_NOTE_FORMAT=X;l.DEFAULT_MONTHLY_NOTE_FORMAT=fe;l.DEFAULT_QUARTERLY_NOTE_FORMAT=we;l.DEFAULT_WEEKLY_NOTE_FORMAT=ee;l.DEFAULT_YEARLY_NOTE_FORMAT=ye;l.appHasDailyNotesPluginLoaded=lt;l.appHasMonthlyNotesPluginLoaded=Me;l.appHasQuarterlyNotesPluginLoaded=Fe;l.appHasWeeklyNotesPluginLoaded=De;l.appHasYearlyNotesPluginLoaded=Ae;l.createDailyNote=ke;l.createMonthlyNote=Te;l.createPeriodicNote=dt;l.createQuarterlyNote=at;l.createWeeklyNote=Pe;l.createYearlyNote=ot;l.getAllDailyNotes=Ge;l.getAllMonthlyNotes=tt;l.getAllQuarterlyNotes=it;l.getAllWeeklyNotes=Xe;l.getAllYearlyNotes=rt;l.getDailyNote=ze;l.getDailyNoteSettings=E;l.getDateFromFile=O;l.getDateFromPath=$e;l.getDateUID=b;l.getMonthlyNote=et;l.getMonthlyNoteSettings=H;l.getPeriodicNoteSettings=ct;l.getQuarterlyNote=nt;l.getQuarterlyNoteSettings=x;l.getTemplateInfo=A;l.getWeeklyNote=Ze;l.getWeeklyNoteSettings=L;l.getYearlyNote=st;l.getYearlyNoteSettings=R});var gt={};Ye(gt,{default:()=>G});module.exports=Be(gt);var M=require("obsidian");var w=require("obsidian");var Se=require("obsidian");var ue=require("obsidian");function N(a){return a?a.extension=="md"?a.path.slice(0,-3):a.path:""}function ge(a){return a.split("/").slice(-1)[0].contains(".")?a:`${a}.md`}function me(a){let t=a.vault.getFiles().filter(e=>["md","canvas"].contains(e.extension));if(t.length){let e=Math.floor(Math.random()*t.length);return N(t[e])}}function he(a){return a.workspace.getActiveViewOfType(ue.View)?.getViewType()=="empty"}function Z(a,t){return a.localeCompare(t,void 0,{sensitivity:"accent"})===0}function W(a){return new Promise(t=>setTimeout(t,a))}var u=Ue(Oe()),se={["Daily Note"]:{noun:"day",adjective:"daily",create:u.createDailyNote,get:u.getDailyNote,getAll:u.getAllDailyNotes},["Weekly Note"]:{noun:"week",adjective:"weekly",create:u.createWeeklyNote,get:u.getWeeklyNote,getAll:u.getAllWeeklyNotes},["Monthly Note"]:{noun:"month",adjective:"monthly",create:u.createMonthlyNote,get:u.getMonthlyNote,getAll:u.getAllMonthlyNotes},["Yearly Note"]:{noun:"year",adjective:"yearly",create:u.createYearlyNote,get:u.getYearlyNote,getAll:u.getAllYearlyNotes}},Y=["Daily Note","Weekly Note","Monthly Note","Yearly Note"];async function Ee(a,t){let e=t.communityPlugins["periodic-notes"],n=se[a],i=(0,Se.moment)().startOf(n.noun),o;if(He(e)){let s=n.getAll();Object.keys(s).length?o=n.get(i,s)||await n.create(i):o=await n.create(i)}else e.cache.initialize(),o=e.getPeriodicNote(n.noun,i)||await e.createPeriodicNote(n.noun,i);return N(o)}function Le(a,t){if(a=="Daily Note"&&t.internalPlugins["daily-notes"]?.enabled)return!0;let e=t.communityPlugins["periodic-notes"];if(!e)return!1;if(He(e)){let n=se[a].adjective;return t.communityPlugins["periodic-notes"].settings[n]?.enabled}else{let n=se[a].noun;return t.communityPlugins["periodic-notes"]?.calendarSetManager?.getActiveSet()[n]?.enabled}}function U(a){let t=a.internalPlugins["daily-notes"];return t?.enabled&&t?.instance.options.autorun}function He(a){return(a?.manifest.version||"0").startsWith("0")}var xe=["markdown","canvas","kanban"],pt=[...xe,"audio","graph","image","pdf","video"],F="Main Homepage",B="Mobile Homepage",q=(n=>(n.ReplaceAll="Replace all open notes",n.ReplaceLast="Replace last note",n.Retain="Keep open notes",n))(q||{}),re=(i=>(i.Default="Default view",i.Reading="Reading view",i.Source="Editing view (Source)",i.LivePreview="Editing view (Live Preview)",i))(re||{}),V=(c=>(c.File="File",c.Workspace="Workspace",c.Random="Random file",c.Graph="Graph view",c.None="Nothing",c.DailyNote="Daily Note",c.WeeklyNote="Weekly Note",c.MonthlyNote="Monthly Note",c.YearlyNote="Yearly Note",c.MomentDate="Date-dependent file",c))(V||{}),le=["Random file","Graph view","Nothing",...Y],_=class{constructor(t,e){this.lastView=void 0;this.openedViews=new WeakMap;this.name=t,this.plugin=e,this.app=e.app,this.data=e.settings.homepages[t]}async open(t=!1){if(this.plugin.hasRequiredPlugin(this.data.kind))this.data.kind==="Date-dependent file"&&new w.Notice("Moment-based homepages are deprecated, and will be removed in a future version. Please change your homepage to a Daily or Periodic Notes type in the Homepage settings pane.");else{new w.Notice("Homepage cannot be opened due to plugin unavailablity.");return}if(this.data.kind==="Workspace")await this.launchWorkspace();else if(this.data.kind!=="Nothing"){let e=this.plugin.loaded?this.data.manualOpenMode:this.data.openMode;t&&(e="Keep open notes"),await this.launchLeaf(e)}if(this.data.commands)for(let e of this.data.commands)this.app.commands.executeCommandById(e)}async launchWorkspace(){let t=this.plugin.internalPlugins.workspaces?.instance;if(!(this.data.value in t.workspaces)){new w.Notice(`Cannot find the workspace "${this.data.value}" to use as the homepage.`);return}t.loadWorkspace(this.data.value),await W(100)}async launchLeaf(t){let e;if(this.computedValue=await this.computeValue(),this.plugin.executing=!0,!(U(this.plugin)&&!this.plugin.loaded)){if(t!=="Replace all open notes"){let n=this.getOpened();if(n.length>0){this.app.workspace.setActiveLeaf(n[0]),await this.configure(n[0]);return}else t=="Keep open notes"&&he(this.app)&&(t="Replace last note")}t!=="Keep open notes"&&this.app.workspace.getActiveViewOfType(w.View)?.leaf.setPinned(!1),t==="Replace all open notes"&&(this.app.workspace?.floatingSplit?.children&&(await W(0),this.app.workspace.floatingSplit.children.forEach(n=>n.win.close())),pt.forEach(n=>this.app.workspace.detachLeavesOfType(n)),await W(0)),this.data.kind==="Graph view"?e=await this.launchGraph(t):e=await this.launchNote(t),e&&await this.configure(e)}}async launchGraph(t){if(t==="Keep open notes"){let e=this.app.workspace.getLeaf("tab");this.app.workspace.setActiveLeaf(e)}return this.app.commands.executeCommandById("graph:open"),this.app.workspace.getActiveViewOfType(w.View)?.leaf}async launchNote(t){let e=this.app.metadataCache.getFirstLinkpathDest(this.computedValue,"/");if(!e){if(!this.data.autoCreate){new w.Notice(`Homepage "${this.computedValue}" does not exist.`);return}e=await this.app.vault.create(ge(this.computedValue),"")}let n=await this.app.vault.cachedRead(e),i=this.app.workspace.getLeaf(t=="Keep open notes");return await i.openFile(e),this.app.workspace.setActiveLeaf(i),n!==await this.app.vault.read(e)&&await this.app.vault.modify(e,n),i}async configure(t){this.plugin.executing=!1;let e=t.view;if(!(e instanceof w.MarkdownView)){this.data.pin&&e.leaf.setPinned(!0);return}let n=e.getState();if(this.data.revertView&&(this.lastView=new WeakRef(e)),this.data.autoScroll){let i=e.editor.lineCount();n.mode=="preview"?e.previewMode.applyScroll(i-4):(e.editor.setCursor(i),e.editor.focus())}if(this.data.pin&&e.leaf.setPinned(!0),this.data.view!="Default view"){switch(this.data.view){case"Editing view (Live Preview)":case"Editing view (Source)":n.mode="source",n.source=this.data.view!="Editing view (Live Preview)";break;case"Reading view":n.mode="preview";break}await e.leaf.setViewState({type:"markdown",state:n}),this.plugin.loaded&&this.data.refreshDataview&&this.plugin.communityPlugins.dataview?.index.touch()}}getOpened(){return this.data.kind=="Graph view"?this.app.workspace.getLeavesOfType("graph"):xe.flatMap(e=>this.app.workspace.getLeavesOfType(e)).filter(e=>Z(N(e.view.file),this.computedValue))}async computeValue(){let t=this.data.value;switch(this.data.kind){case"Date-dependent file":t=(0,w.moment)().format(this.data.value);break;case"Random file":let e=me(this.app);e&&(t=e);break;case"Daily Note":case"Weekly Note":case"Monthly Note":case"Yearly Note":t=await Ee(this.data.kind,this.plugin);break}return t}async save(){this.plugin.settings.homepages[this.name]=this.data,await this.plugin.saveSettings()}async setToActiveFile(){this.data.value=N(this.app.workspace.getActiveFile()),await this.save(),new w.Notice(`The homepage has been changed to "${this.data.value}".`)}canSetToFile(){return this.app.workspace.getActiveFile()!==null&&!le.includes(this.data.kind)}async revertView(){if(this.lastView==null||this.data.view=="Default view")return;let t=this.lastView.deref();if(!t||Z(N(t.file),this.computedValue))return;let e=t.getState(),n=this.app.vault.config,i=n.defaultViewMode||"source",o=n.livePreview!==void 0?!n.livePreview:!1;t.leaf.getViewState().type=="markdown"&&(i!=e.mode||o!=e.source)&&(e.mode=i,e.source=o,await t.leaf.setViewState({type:"markdown",state:e,active:!0})),this.lastView=void 0}async openWhenEmpty(){if(!this.plugin.loaded||this.plugin.executing)return;let t=this.app.workspace.getActiveViewOfType(w.View)?.leaf;t?.getViewState().type!=="empty"||t.parentSplit.children.length!=1||await this.open(!0)}async apply(){let t=this.app.workspace.getActiveViewOfType(w.FileView);if(!t)return;let e=N(t.file);this.openedViews.get(t)!==e&&(this.openedViews.set(t,e),e===await this.computeValue()&&this.plugin.loaded&&!this.plugin.executing&&await this.configure(t.leaf))}};var h=require("obsidian");var D=require("obsidian");var K=class extends D.AbstractInputSuggest{getSuggestions(e){let n=this.app.vault.getAllLoadedFiles(),i=[],o=e.toLowerCase();return n.forEach(s=>{s instanceof D.TFile&&["md","canvas"].contains(s.extension)&&s.path.toLowerCase().contains(o)&&i.push(s)}),i}renderSuggestion(e,n){e.extension=="md"?n.setText(N(e)):(n.setText(e.path.slice(0,-7)),n.insertAdjacentHTML("beforeend",''))}selectSuggestion(e){this.textInputEl.value=N(e),this.textInputEl.trigger("input"),this.close()}},j=class extends D.AbstractInputSuggest{getSuggestions(e){let n=Object.keys(this.app.internalPlugins.plugins.workspaces?.instance.workspaces),i=e.toLowerCase();return n.filter(o=>o.toLowerCase().contains(i))}renderSuggestion(e,n){n.setText(e)}selectSuggestion(e){this.textInputEl.value=e,this.textInputEl.trigger("input"),this.close()}},$=class extends D.FuzzySuggestModal{constructor(e){super(e.plugin.app);this.homepage=e.plugin.homepage,this.tab=e}getItems(){return Object.values(this.app.commands.commands)}getItemText(e){return e.name}onChooseItem(e){if(e.id==="homepage:open-homepage"){new D.Notice("Really?");return}else this.homepage.data.commands||(this.homepage.data.commands=[]);this.homepage.data.commands.push(e.id),this.homepage.save(),this.tab.updateCommandBox()}};var de={version:3,homepages:{[F]:{value:"Home",kind:"File",openOnStartup:!0,openMode:"Replace all open notes",manualOpenMode:"Keep open notes",view:"Default view",revertView:!0,openWhenEmpty:!1,refreshDataview:!1,autoCreate:!0,autoScroll:!1,pin:!1,commands:[],alwaysApply:!1,hideReleaseNotes:!1}},separateMobile:!1},ce=de.homepages[F],z=class extends h.PluginSettingTab{constructor(e,n){super(e,n);this.plugin=n,this.settings=n.settings}sanitiseNote(e){return e===null||e.match(/^\s*$/)!==null?null:(0,h.normalizePath)(e)}display(){let e=this.plugin.homepage.data.kind=="Workspace",n=this.plugin.homepage.data.kind,i=U(this.plugin),o=!1,s=document.createElement("article"),p=e?j:K;this.containerEl.empty(),this.elements={},s.id="nv-desc";let d=new h.Setting(this.containerEl).setName("Homepage").addDropdown(async r=>{for(let c of Object.values(V)){if(!this.plugin.hasRequiredPlugin(c))if(c==this.plugin.homepage.data.kind)o=!0;else continue;let y=c;if(c=="Date-dependent file"){if(!this.enableMomentOption())continue;y="Moment (legacy)"}r.addOption(c,y)}r.setValue(this.plugin.homepage.data.kind),r.onChange(async c=>{this.plugin.homepage.data.kind=c,await this.plugin.homepage.save(),this.display()})});switch(d.settingEl.id="nv-main-setting",d.settingEl.append(s),n){case"File":s.innerHTML="Enter a note or canvas to use.";break;case"Workspace":s.innerHTML="Enter an Obsidian workspace to use.";break;case"Graph view":s.innerHTML="Your graph view will be used.";break;case"Nothing":s.innerHTML="Nothing will occur by default. Any commands added will still take effect.";break;case"Date-dependent file":s.innerHTML=`This type is deprecated and will eventually be removed. It is only available since you have previously chosen it. Use Daily/Weekly/Monthly/Yearly Note instead, which works natively with Daily and Periodic Notes.
2 | Enter a note or canvas to use based on Moment date formatting.`;break;case"Random file":s.innerHTML="A random note or canvas from your Obsidian folder will be selected.";break;case"Daily Note":s.innerHTML="Your Daily Note or Periodic Daily Note will be used.";break;case"Weekly Note":case"Monthly Note":case"Yearly Note":s.innerHTML=`Your Periodic ${this.plugin.homepage.data.kind} will be used.`;break}o&&s.createDiv({text:"The plugin required for this homepage type isn't available.",attr:{class:"mod-warning"}}),le.includes(n)?d.addText(r=>{r.setDisabled(!0)}):d.addText(r=>{new p(this.app,r.inputEl),r.setPlaceholder(ce.value),r.setValue(ce.value==this.plugin.homepage.data.value?"":this.plugin.homepage.data.value),r.onChange(async c=>{this.plugin.homepage.data.value=this.sanitiseNote(c)||ce.value,await this.plugin.homepage.save()})}),this.addToggle("Open on startup","When launching Obsidian, open the homepage.","openOnStartup",r=>this.display()),i&&(this.elements.openOnStartup.descEl.createDiv({text:`This setting has been disabled, as it isn't compatible with Daily Notes' "Open daily note on startup" functionality. To use it, disable the Daily Notes setting.`,attr:{class:"mod-warning"}}),this.disableSetting("openOnStartup")),this.addToggle("Open when empty","When there are no tabs open, open the homepage.","openWhenEmpty"),this.addToggle("Use when opening normally","Use homepage settings when opening it normally, such as from a link or the file browser.","alwaysApply"),new h.Setting(this.containerEl).setName("Separate mobile homepage").setDesc("For mobile devices, store the homepage and its settings separately.").addToggle(r=>r.setValue(this.plugin.settings.separateMobile).onChange(async c=>{this.plugin.settings.separateMobile=c,this.plugin.homepage=this.plugin.getHomepage(),await this.plugin.saveSettings(),this.display()})),this.addHeading("Commands","commandsHeading"),this.containerEl.createDiv({cls:"nv-command-desc setting-item-description",text:"Select commands that will be executed when opening the homepage."}),this.commandBox=this.containerEl.createDiv({cls:"nv-command-box"}),this.updateCommandBox(),this.addHeading("Vault environment","vaultHeading"),this.addDropdown("Opening method","Determine how extant tabs and views are affected on startup.","openMode",q),this.addDropdown("Manual opening method","Determine how extant tabs and views are affected when opening with commands or the ribbon button.","manualOpenMode",q),this.addToggle("Auto-create","If the homepage doesn't exist, create a note with the specified name.","autoCreate"),this.addToggle("Pin","Pin the homepage when opening.","pin"),this.addToggle("Hide release notes","Never display release notes when Obsidian updates.","hideReleaseNotes"),this.addHeading("Opened view","paneHeading"),this.addDropdown("Homepage view","Choose what view to open the homepage in.","view",re),this.addToggle("Revert view on close","When navigating away from the homepage, restore the default view.","revertView"),this.addToggle("Auto-scroll","When opening the homepage, scroll to the bottom and focus on the last line.","autoScroll"),"dataview"in this.plugin.communityPlugins&&(this.addToggle("Refresh Dataview","Always attempt to reload Dataview views when opening the homepage.","refreshDataview"),this.elements.refreshDataview.descEl.createDiv({text:"Requires Dataview auto-refresh to be enabled.",attr:{class:"mod-warning"}})),h.Platform.isMobile||new h.ButtonComponent(this.containerEl).setButtonText("Copy debug info").setClass("nv-debug-button").onClick(async()=>await this.copyDebugInfo()),(e||n==="Nothing")&&this.disableSettings("openWhenEmpty","alwaysApply","vaultHeading","openMode","manualOpenMode","autoCreate","pin"),(e||["Nothing","Graph view"].includes(n))&&this.disableSettings("paneHeading","view","revertView","autoScroll","refreshDataview"),(!this.plugin.homepage.data.openOnStartup||i)&&this.disableSetting("openMode"),Y.includes(this.plugin.homepage.data.kind)&&this.disableSetting("autoCreate")}disableSetting(e){this.elements[e]?.settingEl.setAttribute("nv-greyed","")}disableSettings(...e){e.forEach(n=>this.disableSetting(n))}addHeading(e,n){let i=new h.Setting(this.containerEl).setHeading().setName(e);this.elements[n]=i}addDropdown(e,n,i,o,s){let p=new h.Setting(this.containerEl).setName(e).setDesc(n).addDropdown(async d=>{for(let r of Object.values(o))d.addOption(r,r);d.setValue(this.plugin.homepage.data[i]),d.onChange(async r=>{this.plugin.homepage.data[i]=r,await this.plugin.homepage.save(),s&&s(r)})});this.elements[i]=p}addToggle(e,n,i,o){let s=new h.Setting(this.containerEl).setName(e).setDesc(n).addToggle(p=>p.setValue(this.plugin.homepage.data[i]).onChange(async d=>{this.plugin.homepage.data[i]=d,await this.plugin.homepage.save(),o&&o(d)}));this.elements[i]=s}updateCommandBox(){this.commandBox.innerHTML="";for(let[e,n]of this.plugin.homepage.data.commands.entries()){let i=this.app.commands.findCommand(n);if(!i)continue;let o=this.commandBox.createDiv({cls:"nv-command-pill",text:i.name});new h.ButtonComponent(o).setIcon("trash-2").setClass("clickable-icon").onClick(()=>{this.plugin.homepage.data.commands.splice(e,1),this.plugin.homepage.save(),this.updateCommandBox()})}new h.ButtonComponent(this.commandBox).setClass("nv-command-add-button").setButtonText("Add...").onClick(()=>{new $(this).open()})}enableMomentOption(){return this.plugin.homepage.data.kind=="Date-dependent file"||window.homepageLegacyOptionsEnabled}async copyDebugInfo(){let e=this.app.vault.config,n={...this.settings,_defaultViewMode:e.defaultViewMode||"default",_livePreview:e.livePreview!==void 0?e.livePreview:"default",_focusNewTab:e.focusNewTab!==void 0?e.focusNewTab:"default",_plugins:Object.keys(this.plugin.communityPlugins),_internalPlugins:Object.values(this.plugin.internalPlugins).flatMap(i=>i.enabled?[i.instance.id]:[]),_obsidianVersion:window.electron.ipcRenderer.sendSync("version")};await navigator.clipboard.writeText(JSON.stringify(n)),new h.Notice("Copied homepage debug information to clipboard")}};var ut='',G=class extends M.Plugin{constructor(){super(...arguments);this.newRelease=!1;this.loaded=!1;this.executing=!1;this.onLayoutChange=async()=>{this.homepage.data.revertView&&await this.homepage.revertView(),this.homepage.data.openWhenEmpty&&await this.homepage.openWhenEmpty(),this.homepage.data.alwaysApply&&await this.homepage.apply()}}async onload(){let e=document.body.querySelector(".progress-bar")!==null;this.patchReleaseNotes(),this.settings=await this.loadSettings(),this.internalPlugins=this.app.internalPlugins.plugins,this.communityPlugins=this.app.plugins.plugins,this.homepage=this.getHomepage(),this.app.workspace.onLayoutReady(async()=>{let n=this.homepage.data.openOnStartup&&e&&!await this.hasUrlParams();this.patchNewTabPage(),n&&await this.homepage.open(),this.loaded=!0,this.unpatchReleaseNotes()}),(0,M.addIcon)("homepage",ut),this.addRibbonIcon("homepage","Open homepage",n=>this.homepage.open(n.button==1||n.button==2||M.Keymap.isModifier(n,"Mod"))).setAttribute("id","nv-homepage-icon"),this.registerEvent(this.app.workspace.on("layout-change",this.onLayoutChange)),this.addSettingTab(new z(this.app,this)),this.addCommand({id:"open-homepage",name:"Open homepage",callback:()=>this.homepage.open()}),this.addCommand({id:"set-to-active-file",name:"Set to active file",checkCallback:n=>{if(n)return this.homepage.canSetToFile();this.homepage.setToActiveFile()}}),console.log(`Homepage: ${this.homepage.data.value} (method: ${this.homepage.data.openMode}, view: ${this.homepage.data.view}, kind: ${this.homepage.data.kind})`)}async onunload(){this.app.workspace.off("layout-change",this.onLayoutChange),this.unpatchNewTabPage()}getHomepage(){return this.settings.separateMobile&&M.Platform.isMobile?(B in this.settings.homepages||(this.settings.homepages[B]={...this.settings.homepages[F]}),new _(B,this)):new _(F,this)}async loadSettings(){let e=await this.loadData();if(!e||e.version!==2)return Object.assign({},de,e);{let n={version:3,homepages:{},separateMobile:!1},i=e;return e.workspaceEnabled?(i.value=i.workspace,i.kind="Workspace"):e.useMoment?(i.value=i.momentFormat,i.kind="Date-dependent file"):(i.value=i.defaultNote,i.kind="File"),i.commands=[],delete i.workspace,delete i.momentFormat,delete i.defaultNote,delete i.useMoment,delete i.workspaceEnabled,n.homepages[F]=i,n}}async saveSettings(){await this.saveData(this.settings)}async hasUrlParams(){let e,n;if(M.Platform.isMobile){let i=await window.Capacitor.Plugins.App.getLaunchUrl();if(!i)return!1;let o=new URL(i.url);n=Array.from(o.searchParams.keys()),e=o.hostname}else if(window.OBS_ACT)n=Object.keys(window.OBS_ACT),e=window.OBS_ACT.action;else return!1;return["open","advanced-uri"].includes(e)&&["file","filepath","workspace"].some(i=>n.includes(i))}hasRequiredPlugin(e){switch(e){case"Workspace":return this.internalPlugins.workspaces?.enabled;case"Graph view":return this.internalPlugins.graph?.enabled;case"Daily Note":case"Weekly Note":case"Monthly Note":case"Yearly Note":return Le(e,this);default:return!0}}patchNewTabPage(){let e=this.communityPlugins["new-tab-default-page"];e&&(e.nvOrig_checkForNewTab=e.checkForNewTab,e.checkForNewTab=async n=>{if(!(this&&this.executing))return await e.nvOrig_checkForNewTab(n)})}unpatchNewTabPage(){let e=this.communityPlugins["new-tab-default-page"];e&&(e.checkForNewTab=e._checkForNewTab)}patchReleaseNotes(){this.app.nvOrig_showReleaseNotes=this.app.showReleaseNotes,this.app.showReleaseNotes=()=>this.newRelease=!0}unpatchReleaseNotes(){this.newRelease&&!this.homepage.data.hideReleaseNotes&&this.app.nvOrig_showReleaseNotes(),this.app.showReleaseNotes=this.app.nvOrig_showReleaseNotes}}; 3 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/plugins/homepage/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "homepage", 3 | "name": "Homepage", 4 | "version": "3.8.1", 5 | "minAppVersion": "1.4.10", 6 | "description": "Open a specified note, canvas, or workspace on startup, or set it for quick access later.", 7 | "author": "novov", 8 | "authorUrl": "https://novov.me", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/plugins/homepage/styles.css: -------------------------------------------------------------------------------- 1 | .setting-item[nv-greyed] { 2 | opacity: .5; 3 | pointer-events: none !important; 4 | } 5 | 6 | #nv-main-setting { 7 | flex-wrap: wrap; 8 | margin-bottom: 30px; 9 | } 10 | 11 | #nv-main-setting .setting-item-control { 12 | padding-top: var(--size-4-2); 13 | flex-basis: 100%; 14 | align-items: stretch; 15 | } 16 | 17 | #nv-main-setting .setting-item-control input, #nv-main-setting .setting-item-control select { 18 | font-size: var(--font-ui-medium); 19 | font-weight: 600; 20 | } 21 | 22 | #nv-main-setting .setting-item-control select { 23 | padding: var(--size-4-3) var(--size-4-4); 24 | padding-right: var(--size-4-8); 25 | height: auto; 26 | } 27 | 28 | #nv-main-setting .setting-item-control input { 29 | flex-grow: 1; 30 | padding: var(--size-4-5) var(--size-4-4); 31 | } 32 | 33 | #nv-main-setting .setting-item-control input[disabled] { 34 | opacity: 0.3; 35 | } 36 | 37 | #nv-main-setting #nv-desc, #nv-main-setting #nv-info { 38 | flex-basis: 100%; 39 | } 40 | 41 | #nv-main-setting #nv-desc { 42 | font-weight: 500; 43 | color: var(--text-normal); 44 | font-size: var(--font-ui-small); 45 | padding: 10px 0 0; 46 | } 47 | 48 | #nv-main-setting #nv-desc code { 49 | font-family: var(--font-monospace); 50 | font-size: var(--font-smaller); 51 | border-radius: var(--radius-s); 52 | } 53 | 54 | #nv-main-setting #nv-desc small { 55 | display: block; 56 | font-weight: 400; 57 | color: var(--text-muted); 58 | font-size: calc(var(--font-ui-smaller) * 0.9); 59 | padding: 5px 0 0; 60 | } 61 | 62 | .nv-command-desc { 63 | padding: 1.2em 0 0; 64 | border-top: 1px solid var(--background-modifier-border); 65 | } 66 | 67 | .nv-command-box { 68 | margin: 1em 0 1.75em; 69 | display: flex; 70 | flex-wrap: wrap; 71 | gap: 12px; 72 | align-items: center; 73 | } 74 | 75 | .nv-command-pill { 76 | background-color: var(--background-secondary); 77 | border: 1px solid var(--background-modifier-border-hover); 78 | border-radius: var(--radius-s); 79 | font-size: var(--font-ui-small); 80 | padding: var(--size-2-1) var(--size-2-3); 81 | } 82 | 83 | .nv-command-pill button { 84 | display: inline-block; 85 | padding: 0; 86 | margin: 0 0 0 var(--size-2-3); 87 | vertical-align: bottom; 88 | } 89 | 90 | .nv-command-pill button svg { 91 | height: 1em; 92 | width: 1em; 93 | } 94 | 95 | .nv-command-add-button { 96 | font-size: var(--font-ui-small); 97 | padding: var(--size-2-2) var(--size-4-2); 98 | height: auto; 99 | } 100 | 101 | #nv-main-setting + .setting-item, .nv-command-desc + .setting-item { 102 | padding-top: 20px; 103 | border-top: none !important; 104 | } 105 | 106 | .nv-debug-button { 107 | margin: 3em 0 -0.2em; 108 | font-size: var(--font-ui-smaller); 109 | padding: 0; 110 | height: auto; 111 | float: right; 112 | box-shadow: none !important; 113 | background: none !important; 114 | color: var(--text-accent); 115 | font-weight: 600; 116 | cursor: pointer; 117 | } 118 | 119 | .nv-debug-button:hover, .nv-debug-button:active { 120 | text-decoration: underline; 121 | } 122 | 123 | .is-phone #nv-main-setting .setting-item-control { 124 | flex-wrap: wrap; 125 | justify-content: flex-start; 126 | } 127 | 128 | .is-phone #nv-main-setting .setting-item-control select { 129 | width: auto; 130 | max-width: auto; 131 | } 132 | 133 | .is-phone .nv-command-pill button, .is-phone .nv-command-add-button { 134 | width: auto; 135 | } 136 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/plugins/obsidian-linter/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "ruleConfigs": { 3 | "escape-yaml-special-characters": { 4 | "enabled": false, 5 | "try-to-escape-single-line-arrays": false 6 | }, 7 | "force-yaml-escape": { 8 | "enabled": false, 9 | "force-yaml-escape-keys": "" 10 | }, 11 | "format-tags-in-yaml": { 12 | "enabled": true 13 | }, 14 | "format-yaml-array": { 15 | "enabled": false, 16 | "alias-key": true, 17 | "tag-key": true, 18 | "default-array-style": "single-line", 19 | "default-array-keys": true, 20 | "force-single-line-array-style": "", 21 | "force-multi-line-array-style": "" 22 | }, 23 | "insert-yaml-attributes": { 24 | "enabled": false, 25 | "text-to-insert": "aliases: \ntags: " 26 | }, 27 | "move-tags-to-yaml": { 28 | "enabled": true, 29 | "how-to-handle-existing-tags": "Remove whole tag", 30 | "tags-to-ignore": "" 31 | }, 32 | "remove-yaml-keys": { 33 | "enabled": false, 34 | "yaml-keys-to-remove": "" 35 | }, 36 | "yaml-key-sort": { 37 | "enabled": false, 38 | "yaml-key-priority-sort-order": "", 39 | "priority-keys-at-start-of-yaml": true, 40 | "yaml-sort-order-for-other-keys": "None" 41 | }, 42 | "yaml-timestamp": { 43 | "enabled": false, 44 | "date-created": false, 45 | "date-created-key": "cday", 46 | "force-retention-of-create-value": false, 47 | "date-modified": false, 48 | "date-modified-key": "date modified", 49 | "format": "YYYY-MM-DD" 50 | }, 51 | "yaml-title": { 52 | "enabled": false, 53 | "title-key": "title" 54 | }, 55 | "yaml-title-alias": { 56 | "enabled": false, 57 | "preserve-existing-alias-section-style": false, 58 | "keep-alias-that-matches-the-filename": false, 59 | "use-yaml-key-to-keep-track-of-old-filename-or-heading": false 60 | }, 61 | "capitalize-headings": { 62 | "enabled": false, 63 | "style": "Title Case", 64 | "ignore-case-words": true, 65 | "ignore-words": "macOS, iOS, iPhone, iPad, JavaScript, TypeScript, AppleScript, I", 66 | "lowercase-words": "a, an, the, aboard, about, abt., above, abreast, absent, across, after, against, along, aloft, alongside, amid, amidst, mid, midst, among, amongst, anti, apropos, around, round, as, aslant, astride, at, atop, ontop, bar, barring, before, B4, behind, below, beneath, neath, beside, besides, between, 'tween, beyond, but, by, chez, circa, c., ca., come, concerning, contra, counting, cum, despite, spite, down, during, effective, ere, except, excepting, excluding, failing, following, for, from, in, including, inside, into, less, like, minus, modulo, mod, near, nearer, nearest, next, notwithstanding, of, o', off, offshore, on, onto, opposite, out, outside, over, o'er, pace, past, pending, per, plus, post, pre, pro, qua, re, regarding, respecting, sans, save, saving, short, since, sub, than, through, thru, throughout, thruout, till, times, to, t', touching, toward, towards, under, underneath, unlike, until, unto, up, upon, versus, vs., v., via, vice, vis-à-vis, wanting, with, w/, w., c̄, within, w/i, without, 'thout, w/o, abroad, adrift, aft, afterward, afterwards, ahead, apart, ashore, aside, away, back, backward, backwards, beforehand, downhill, downstage, downstairs, downstream, downward, downwards, downwind, east, eastward, eastwards, forth, forward, forwards, heavenward, heavenwards, hence, henceforth, here, hereby, herein, hereof, hereto, herewith, home, homeward, homewards, indoors, inward, inwards, leftward, leftwards, north, northeast, northward, northwards, northwest, now, onward, onwards, outdoors, outward, outwards, overboard, overhead, overland, overseas, rightward, rightwards, seaward, seawards, skywards, skyward, south, southeast, southwards, southward, southwest, then, thence, thenceforth, there, thereby, therein, thereof, thereto, therewith, together, underfoot, underground, uphill, upstage, upstairs, upstream, upward, upwards, upwind, west, westward, westwards, when, whence, where, whereby, wherein, whereto, wherewith, although, because, considering, given, granted, if, lest, once, provided, providing, seeing, so, supposing, though, unless, whenever, whereas, wherever, while, whilst, ago, according to, as regards, counter to, instead of, owing to, pertaining to, at the behest of, at the expense of, at the hands of, at risk of, at the risk of, at variance with, by dint of, by means of, by virtue of, by way of, for the sake of, for sake of, for lack of, for want of, from want of, in accordance with, in addition to, in case of, in charge of, in compliance with, in conformity with, in contact with, in exchange for, in favor of, in front of, in lieu of, in light of, in the light of, in line with, in place of, in point of, in quest of, in relation to, in regard to, with regard to, in respect to, with respect to, in return for, in search of, in step with, in touch with, in terms of, in the name of, in view of, on account of, on behalf of, on grounds of, on the grounds of, on the part of, on top of, with a view to, with the exception of, à la, a la, as soon as, as well as, close to, due to, far from, in case, other than, prior to, pursuant to, regardless of, subsequent to, as long as, as much as, as far as, by the time, in as much as, inasmuch, in order to, in order that, even, provide that, if only, whether, whose, whoever, why, how, or not, whatever, what, both, and, or, not only, but also, either, neither, nor, just, rather, no sooner, such, that, yet, is, it" 67 | }, 68 | "file-name-heading": { 69 | "enabled": false 70 | }, 71 | "header-increment": { 72 | "enabled": false, 73 | "start-at-h2": false 74 | }, 75 | "headings-start-line": { 76 | "enabled": false 77 | }, 78 | "remove-trailing-punctuation-in-heading": { 79 | "enabled": false, 80 | "punctuation-to-remove": ".,;:!。,;:!" 81 | }, 82 | "footnote-after-punctuation": { 83 | "enabled": false 84 | }, 85 | "move-footnotes-to-the-bottom": { 86 | "enabled": true 87 | }, 88 | "re-index-footnotes": { 89 | "enabled": true 90 | }, 91 | "auto-correct-common-misspellings": { 92 | "enabled": false, 93 | "ignore-words": "" 94 | }, 95 | "blockquote-style": { 96 | "enabled": false, 97 | "style": "space" 98 | }, 99 | "convert-bullet-list-markers": { 100 | "enabled": false 101 | }, 102 | "emphasis-style": { 103 | "enabled": false, 104 | "style": "consistent" 105 | }, 106 | "no-bare-urls": { 107 | "enabled": false 108 | }, 109 | "ordered-list-style": { 110 | "enabled": false, 111 | "number-style": "ascending", 112 | "list-end-style": "." 113 | }, 114 | "proper-ellipsis": { 115 | "enabled": false 116 | }, 117 | "remove-consecutive-list-markers": { 118 | "enabled": false 119 | }, 120 | "remove-empty-list-markers": { 121 | "enabled": false 122 | }, 123 | "remove-hyphenated-line-breaks": { 124 | "enabled": false 125 | }, 126 | "remove-multiple-spaces": { 127 | "enabled": true 128 | }, 129 | "strong-style": { 130 | "enabled": true, 131 | "style": "consistent" 132 | }, 133 | "two-spaces-between-lines-with-content": { 134 | "enabled": false 135 | }, 136 | "unordered-list-style": { 137 | "enabled": true, 138 | "list-style": "consistent" 139 | }, 140 | "compact-yaml": { 141 | "enabled": true, 142 | "inner-new-lines": false 143 | }, 144 | "consecutive-blank-lines": { 145 | "enabled": true 146 | }, 147 | "convert-spaces-to-tabs": { 148 | "enabled": false, 149 | "tabsize": 4 150 | }, 151 | "empty-line-around-blockquotes": { 152 | "enabled": true 153 | }, 154 | "empty-line-around-code-fences": { 155 | "enabled": true 156 | }, 157 | "empty-line-around-math-blocks": { 158 | "enabled": true 159 | }, 160 | "empty-line-around-tables": { 161 | "enabled": true 162 | }, 163 | "heading-blank-lines": { 164 | "enabled": true, 165 | "bottom": true, 166 | "empty-line-after-yaml": true 167 | }, 168 | "line-break-at-document-end": { 169 | "enabled": true 170 | }, 171 | "move-math-block-indicators-to-their-own-line": { 172 | "enabled": false 173 | }, 174 | "paragraph-blank-lines": { 175 | "enabled": true 176 | }, 177 | "remove-empty-lines-between-list-markers-and-checklists": { 178 | "enabled": true 179 | }, 180 | "remove-link-spacing": { 181 | "enabled": false 182 | }, 183 | "remove-space-around-characters": { 184 | "enabled": false, 185 | "include-fullwidth-forms": true, 186 | "include-cjk-symbols-and-punctuation": true, 187 | "include-dashes": true, 188 | "other-symbols": "" 189 | }, 190 | "remove-space-before-or-after-characters": { 191 | "enabled": false, 192 | "characters-to-remove-space-before": ",!?;:).’”]", 193 | "characters-to-remove-space-after": "¿¡‘“([" 194 | }, 195 | "space-after-list-markers": { 196 | "enabled": false 197 | }, 198 | "space-between-chinese-japanese-or-korean-and-english-or-numbers": { 199 | "enabled": false, 200 | "english-symbols-punctuation-before": "-+;:'\"°%$)]", 201 | "english-symbols-punctuation-after": "-+'\"([¥$" 202 | }, 203 | "trailing-spaces": { 204 | "enabled": false, 205 | "twp-space-line-break": false 206 | }, 207 | "add-blockquote-indentation-on-paste": { 208 | "enabled": false 209 | }, 210 | "prevent-double-checklist-indicator-on-paste": { 211 | "enabled": false 212 | }, 213 | "prevent-double-list-item-indicator-on-paste": { 214 | "enabled": false 215 | }, 216 | "proper-ellipsis-on-paste": { 217 | "enabled": false 218 | }, 219 | "remove-hyphens-on-paste": { 220 | "enabled": false 221 | }, 222 | "remove-leading-or-trailing-whitespace-on-paste": { 223 | "enabled": false 224 | }, 225 | "remove-leftover-footnotes-from-quote-on-paste": { 226 | "enabled": false 227 | }, 228 | "remove-multiple-blank-lines-on-paste": { 229 | "enabled": true 230 | }, 231 | "quote-style": { 232 | "enabled": false, 233 | "single-quote-enabled": true, 234 | "single-quote-style": "''", 235 | "double-quote-enabled": true, 236 | "double-quote-style": "\"\"" 237 | }, 238 | "add-blank-line-after-yaml": { 239 | "enabled": false 240 | }, 241 | "default-language-for-code-fences": { 242 | "enabled": false, 243 | "default-language": "" 244 | }, 245 | "dedupe-yaml-array-values": { 246 | "enabled": false, 247 | "dedupe-alias-key": true, 248 | "dedupe-tag-key": true, 249 | "dedupe-array-keys": true, 250 | "ignore-keys": "" 251 | }, 252 | "sort-yaml-array-values": { 253 | "enabled": false, 254 | "sort-alias-key": true, 255 | "sort-tag-key": true, 256 | "sort-array-keys": true, 257 | "ignore-keys": "", 258 | "sort-order": "Ascending Alphabetical" 259 | } 260 | }, 261 | "lintOnSave": true, 262 | "recordLintOnSaveLogs": false, 263 | "displayChanged": false, 264 | "lintOnFileChange": true, 265 | "displayLintOnFileChangeNotice": false, 266 | "settingsConvertedToConfigKeyValues": true, 267 | "foldersToIgnore": [ 268 | "@Extras" 269 | ], 270 | "linterLocale": "system-default", 271 | "logLevel": "ERROR", 272 | "lintCommands": [], 273 | "customRegexes": [], 274 | "commonStyles": { 275 | "aliasArrayStyle": "multi-line", 276 | "tagArrayStyle": "multi-line", 277 | "minimumNumberOfDollarSignsToBeAMathBlock": 2, 278 | "escapeCharacter": "\"", 279 | "removeUnnecessaryEscapeCharsForMultiLineArrays": false 280 | } 281 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/plugins/obsidian-linter/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-linter", 3 | "name": "Linter", 4 | "version": "1.24.0", 5 | "minAppVersion": "1.4.16", 6 | "description": "Formats and styles your notes. It can be used to format YAML tags, aliases, arrays, and metadata; footnotes; headings; spacing; math blocks; regular markdown contents like list, italics, and bold styles; and more with the use of custom rule options as well.", 7 | "author": "Victor Tao", 8 | "authorUrl": "https://github.com/platers", 9 | "helpUrl": "https://platers.github.io/obsidian-linter/", 10 | "isDesktopOnly": false 11 | } 12 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/plugins/obsidian-linter/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on https://github.com/Fevol/obsidian-translate/blob/master/src/ui/translator-components/SettingsPage.svelte 3 | */ 4 | 5 | .linter-navigation-item { 6 | cursor: pointer; 7 | border-radius: 100px; 8 | border: 1px solid var(--background-modifier-border); 9 | border-radius: 8px 8px 2px 2px; 10 | 11 | font-weight: bold; 12 | font-size: 16px; 13 | 14 | display: flex; 15 | flex-direction: row; 16 | white-space: nowrap; 17 | 18 | padding: 4px 6px; 19 | align-items: center; 20 | gap: 4px; 21 | overflow: hidden; 22 | 23 | background-color: var(--background-primary-secondary-alt); 24 | 25 | transition: color 0.25s ease-in-out, 26 | padding 0.25s ease-in-out, 27 | background-color 0.35s cubic-bezier(0.45, 0.25, 0.83, 0.67), 28 | max-width 0.35s cubic-bezier(0.57, 0.04, 0.58, 1); 29 | height: 32px; 30 | } 31 | 32 | @media screen and (max-width: 1325px) { 33 | .linter-navigation-item.linter-desktop { 34 | max-width: 32px; 35 | } 36 | } 37 | 38 | @media screen and (max-width: 800px) { 39 | .linter-navigation-item.linter-mobile { 40 | max-width: 32px; 41 | } 42 | } 43 | 44 | .linter-navigation-item-icon { 45 | padding-top: 5px; 46 | } 47 | 48 | .linter-navigation-item:hover { 49 | border-color: var(--interactive-accent-hover); 50 | border-bottom: 0px; 51 | } 52 | 53 | .linter-navigation-item-selected { 54 | background-color: var(--interactive-accent) !important; 55 | color: var(--text-on-accent); 56 | padding: 4px 9px !important; 57 | max-width: 100% !important; 58 | border: 1px solid var(--background-modifier-border); 59 | border-radius: 8px 8px 2px 2px; 60 | border-bottom: 0px; 61 | transition: color 0.25s ease-in-out, 62 | padding 0.25s ease-in-out, 63 | background-color 0.35s cubic-bezier(0.45, 0.25, 0.83, 0.67), 64 | max-width 0.45s cubic-bezier(0.57, 0.04, 0.58, 1) 0.2s; 65 | } 66 | 67 | /** 68 | * Based on https://github.com/phibr0/obsidian-commander/blob/main/src/styles.scss 69 | */ 70 | .linter { 71 | transition: transform 400ms 0s; 72 | } 73 | 74 | .linter-setting-title { 75 | display: flex; 76 | align-items: baseline; 77 | justify-content: space-between; 78 | gap: 30px; 79 | } 80 | .linter-setting-title.linter-mobile { 81 | justify-content: space-around; 82 | } 83 | .linter-setting-title h1 { 84 | font-weight: 900; 85 | margin-top: 6px; 86 | margin-bottom: 12px; 87 | } 88 | 89 | .linter-setting-header { 90 | margin-bottom: 24px; 91 | overflow-y: hidden; 92 | overflow-x: auto; 93 | } 94 | 95 | .linter-setting-header .linter-setting-tab-group { 96 | display: flex; 97 | align-items: flex-end; 98 | flex-wrap: wrap; 99 | width: 100%; 100 | } 101 | .linter-setting-tab-group { 102 | margin-top: 6px; 103 | padding-left: 2px; 104 | padding-right: 2px; 105 | border-bottom: 2px solid var(--background-modifier-border); 106 | } 107 | 108 | .linter-setting-header .linter-tab-settings { 109 | padding: 6px 12px; 110 | font-weight: 600; 111 | cursor: pointer; 112 | white-space: nowrap; 113 | border-left: 2px solid transparent; 114 | border-right: 2px solid transparent; 115 | } 116 | .linter-setting-header .linter-tab-settings:first-child { 117 | margin-left: 6px; 118 | } 119 | .linter-setting-header .linter-tab-settings.linter-tab-settings-active { 120 | border-bottom: 2px solid var(--background-primary); 121 | transform: translateY(2px); 122 | border-radius: 2px; 123 | border-left: 2px solid var(--background-modifier-border); 124 | border-top: 2px solid var(--background-modifier-border); 125 | border-right: 2px solid var(--background-modifier-border); 126 | } 127 | 128 | /** Hide linter element css 129 | * Based on https://zellwk.com/blog/hide-content-accessibly/ 130 | */ 131 | .linter-navigation-item:not(.linter-navigation-item-selected) > span:nth-child(2), 132 | .linter-visually-hidden { 133 | border: 0; 134 | clip: rect(0 0 0 0); 135 | clip-path: rect(0 0 0 0); 136 | height: auto; 137 | margin: 0; 138 | overflow: hidden; 139 | padding: 0; 140 | position: absolute; 141 | width: 1px; 142 | white-space: nowrap; 143 | } 144 | 145 | /** 146 | * Full-width text areas 147 | * Based on https://github.com/nyable/obsidian-code-block-enhancer/blob/bb0c636c1e7609b6d26c48a8d7ca15d5cd9abdcf/src/styles/index.scss 148 | */ 149 | textarea.full-width { 150 | width: 100%; 151 | min-height: 10em; 152 | margin-top: 0.8em; 153 | margin-bottom: 0.8em; 154 | } 155 | 156 | .full-width-textbox-input-wrapper { 157 | position: relative; 158 | } 159 | 160 | .settings-copy-button { 161 | position: absolute; 162 | top: 0.8em; 163 | right: 0.8em; 164 | margin: 0 0 0 auto; 165 | padding: 4px; 166 | } 167 | 168 | .settings-copy-button svg.linter-clipboard path { 169 | fill: var(--text-faint); 170 | } 171 | .settings-copy-button svg.linter-success path { 172 | fill: var(--interactive-success); 173 | } 174 | .settings-copy-button:hover, .settings-copy-button:active { 175 | cursor: pointer; 176 | } 177 | .settings-copy-button:hover svg path, .settings-copy-button:active svg path { 178 | fill: var(--text-accent-hover); 179 | transition: all ease 0.3s; 180 | } 181 | .settings-copy-button:focus { 182 | outline: 0; 183 | } 184 | 185 | /** 186 | * Custom regex replacement 187 | */ 188 | .linter-custom-regex-replacement-container div:last-child{ 189 | border: none; 190 | } 191 | .linter-custom-regex-replacement { 192 | margin-bottom: 15px; 193 | border: none; 194 | border-bottom: var(--hr-thickness) solid; 195 | border-color: var(--hr-color); 196 | } 197 | .linter-custom-regex-replacement-row2 { 198 | flex-wrap: wrap; 199 | } 200 | .linter-custom-regex-replacement-normal-input { 201 | width: 40%; 202 | } 203 | .linter-custom-regex-replacement-flags { 204 | width: 15%; 205 | } 206 | .linter-custom-regex-replacement-label { 207 | flex-direction: row-reverse; 208 | } 209 | .linter-custom-regex-replacement-label-input { 210 | width: 50%; 211 | } 212 | 213 | /** 214 | * Setting item no border 215 | */ 216 | .linter-no-border { 217 | border: none; 218 | } 219 | 220 | /** 221 | * Custom row 222 | */ 223 | .custom-row-description { 224 | margin-top: 0px; 225 | } 226 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/plugins/obsidian-writer-suite/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Morick莫里克", 3 | "picturePath": "https://www.morick66.com/images/Morick.jpg", 4 | "countPunctuation": false, 5 | "booksPerRow": 5 6 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/snippets/copilot.css: -------------------------------------------------------------------------------- 1 | .chat-icon-selection-tooltip { 2 | display: none !important; 3 | } 4 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/starred.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "type": "file", 5 | "title": "markdown语法", 6 | "path": "markdown语法.md" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/surfing-bookmark.json: -------------------------------------------------------------------------------- 1 | { 2 | "bookmarks": [ 3 | { 4 | "id": "2014068036", 5 | "name": "Obsidian", 6 | "url": "https://obsidian.md/", 7 | "description": "A awesome note-taking tool", 8 | "category": [ 9 | "ROOT" 10 | ], 11 | "tags": "", 12 | "created": 1672840861051, 13 | "modified": 1672840861052 14 | } 15 | ], 16 | "categories": [ 17 | { 18 | "value": "ROOT", 19 | "text": "ROOT", 20 | "label": "ROOT", 21 | "children": [] 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/switcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "showExistingOnly": false, 3 | "showAttachments": true, 4 | "showAllFileTypes": false 5 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "folder": "@Extras/文章模板", 3 | "dateFormat": "YYYY-MM-DD", 4 | "timeFormat": "HH:MM:SS" 5 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/themes/Blue Topaz/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blue Topaz", 3 | "version": "2024061401", 4 | "minAppVersion": "1.0.0", 5 | "author": "WhyI & Pkmer", 6 | "authorUrl": "https://github.com/whyt-byte" 7 | } 8 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/themes/Obsidian Nord/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Obsidian Nord", 3 | "version": "0.2.0", 4 | "minAppVersion": "0.16.0", 5 | "author": "insanum", 6 | "authorUrl": "https://insanum.com" 7 | } 8 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/themes/Obsidian Nord/theme.css: -------------------------------------------------------------------------------- 1 | 2 | :root 3 | { 4 | --dark0_x: 46,52,64; /* #2e3440 */ 5 | --dark0: rgb(var(--dark0_x)); 6 | --dark1_x: 59,66,82; /* #3b4252 */ 7 | --dark1: rgb(var(--dark1_x)); 8 | --dark2_x: 67,76,94; /* #434c5e */ 9 | --dark2: rgb(var(--dark2_x)); 10 | --dark3_x: 76,86,106; /* #4c566a */ 11 | --dark3: rgb(var(--dark3_x)); 12 | 13 | --light0_x: 216,222,233; /* #d8dee9 */ 14 | --light0: rgb(var(--light0_x)); 15 | --light1_x: 229,233,240; /* #e5e9f0 */ 16 | --light1: rgb(var(--light1_x)); 17 | --light2_x: 236,239,244; /* #eceff4 */ 18 | --light2: rgb(var(--light2_x)); 19 | --light3_x: 255,255,255; /* #ffffff */ 20 | --light3: rgb(var(--light3_x)); 21 | 22 | --frost0_x: 143,188,187; /* #8fbcbb */ 23 | --frost0: rgb(var(--frost0_x)); 24 | --frost1_x: 136,192,208; /* #88c0d0 */ 25 | --frost1: rgb(var(--frost1_x)); 26 | --frost2_x: 129,161,193; /* #81a1c1 */ 27 | --frost2: rgb(var(--frost2_x)); 28 | --frost3_x: 94,129,172; /* #5e81ac */ 29 | --frost3: rgb(var(--frost3_x)); 30 | 31 | --red_x: 191,97,106; /* #bf616a */ 32 | --red: rgb(var(--red_x)); 33 | --orange_x: 208,135,112; /* #d08770 */ 34 | --orange: rgb(var(--orange_x)); 35 | --yellow_x: 235,203,139; /* #ebcb8b */ 36 | --yellow: rgb(var(--yellow_x)); 37 | --green_x: 163,190,140; /* #a3be8c */ 38 | --green: rgb(var(--green_x)); 39 | --purple_x: 180,142,173; /* #b48ead */ 40 | --purple: rgb(var(--purple_x)); 41 | } 42 | 43 | body 44 | { 45 | --accent-h: 354; /* --red #bf616a */ 46 | --accent-s: 42%; 47 | --accent-l: 56%; 48 | 49 | --link-decoration: none; 50 | --link-decoration-hover: none; 51 | --link-external-decoration: none; 52 | --link-external-decoration-hover: none; 53 | 54 | --tag-decoration: none; 55 | --tag-decoration-hover: underline; 56 | --tag-padding-x: .5em; 57 | --tag-padding-y: .2em; 58 | --tag-radius: .5em; 59 | 60 | --tab-font-weight: 600; 61 | --bold-weight: 600; 62 | 63 | --checkbox-radius: 0; 64 | 65 | /* --list-indent: 2em; */ 66 | 67 | --embed-border-left: 6px double var(--interactive-accent); 68 | } 69 | 70 | .theme-dark 71 | { 72 | --color-red-rgb: var(--red_x); 73 | --color-red: var(--red); 74 | --color-purple-rgb: var(--purple_x); 75 | --color-purple: var(--purple); 76 | --color-green-rgb: var(--green_x); 77 | --color-green: var(--green); 78 | --color-cyan-rgb: var(--frost1_x); 79 | --color-cyan: var(--frost1); 80 | --color-blue-rgb: var(--frost3_x); 81 | --color-blue: var(--frost3); 82 | --color-yellow-rgb: var(--yellow_x); 83 | --color-yellow: var(--yellow); 84 | --color-orange-rgb: var(--orange_x); 85 | --color-orange: var(--orange); 86 | /* --color-pink: var(--purple); */ 87 | 88 | --background-primary: var(--dark0); 89 | --background-primary-alt: var(--dark0); 90 | --background-secondary: var(--dark1); 91 | --background-secondary-alt: var(--dark2); 92 | --background-modifier-border: var(--dark2); 93 | 94 | --cursor-line-background: rgba(var(--red_x), 0.2); 95 | 96 | --text-normal: var(--light2); 97 | --text-faint: var(--light0); 98 | --text-muted: var(--light1); 99 | 100 | --link-url: var(--purple); 101 | 102 | --h1-color: var(--red); 103 | --h2-color: var(--yellow); 104 | --h3-color: var(--green); 105 | --h4-color: var(--purple); 106 | --h5-color: var(--frost0); 107 | --h6-color: var(--frost2); 108 | 109 | --text-highlight-bg: var(--frost1); 110 | --text-highlight-fg: var(--dark0); 111 | 112 | --text-accent: var(--orange); 113 | --text-accent-hover: var(--frost2); 114 | 115 | --tag-color: var(--frost0); 116 | --tag-background: var(--dark2); 117 | --tag-background-hover: var(--dark1); 118 | 119 | --titlebar-text-color-focused: var(--red); 120 | 121 | --inline-title-color: var(--yellow); 122 | 123 | --bold-color: var(--yellow); 124 | --italic-color: var(--yellow); 125 | 126 | --checkbox-color: var(--frost0); 127 | --checkbox-color-hover: var(--frost0); 128 | --checkbox-border-color: var(--frost0); 129 | --checkbox-border-color-hover: var(--frost0); 130 | --checklist-done-color: rgba(var(--light2_x), 0.5); 131 | 132 | --table-header-background: hsl(220, 16%, 16%); 133 | --table-header-background-hover: var(--dark3); 134 | --table-row-even-background: hsl(220, 16%, 20%); 135 | --table-row-odd-background: hsl(220, 16%, 24%); 136 | --table-row-background-hover: var(--dark3); 137 | 138 | --text-selection: rgba(var(--red_x), 0.6); 139 | --flashing-background: rgba(var(--red_x), 0.3); 140 | 141 | --code-normal: var(--frost1); 142 | --code-background: var(--dark1); 143 | 144 | --mermaid-note: var(--frost3); 145 | --mermaid-loopline: var(--frost1); 146 | --mermaid-exclude: var(--dark3); 147 | --mermaid-seqnum: var(--dark0); 148 | 149 | --icon-color-hover: var(--red); 150 | --icon-color-focused: var(--frost2); 151 | 152 | --nav-item-color-hover: var(--red); 153 | --nav-item-color-active: var(--frost2); 154 | --nav-file-tag: rgba(var(--yellow_x), 0.9); 155 | 156 | --graph-line: var(--dark3); 157 | --graph-node: var(--light3); 158 | --graph-node-tag: var(--red); 159 | --graph-node-attachment: var(--green); 160 | 161 | --calendar-hover: var(--red); 162 | --calendar-background-hover: var(--dark3); 163 | --calendar-week: var(--yellow); 164 | --calendar-today: var(--yellow); 165 | 166 | --dataview-key: var(--text-faint); 167 | --dataview-key-background: rgba(var(--frost2_x), 0.3); 168 | --dataview-value: var(--text-faint); 169 | --dataview-value-background: rgba(var(--red_x), 0.3); 170 | 171 | --tab-text-color-focused-active: var(--frost2); 172 | --tab-text-color-focused-active-current: var(--red); 173 | } 174 | 175 | .theme-light 176 | { 177 | --color-red-rgb: var(--red_x); 178 | --color-red: var(--red); 179 | --color-purple-rgb: var(--purple_x); 180 | --color-purple: var(--purple); 181 | --color-green-rgb: var(--green_x); 182 | --color-green: var(--green); 183 | --color-cyan-rgb: var(--frost1_x); 184 | --color-cyan: var(--frost1); 185 | --color-blue-rgb: var(--frost3_x); 186 | --color-blue: var(--frost3); 187 | --color-yellow-rgb: var(--yellow_x); 188 | --color-yellow: var(--yellow); 189 | --color-orange-rgb: var(--orange_x); 190 | --color-orange: var(--orange); 191 | /* --color-pink: var(--purple); */ 192 | 193 | --background-primary: var(--light3); 194 | --background-primary-alt: var(--light3); 195 | --background-secondary: var(--light2); 196 | --background-secondary-alt: var(--light1); 197 | --background-modifier-border: var(--light1); 198 | 199 | --cursor-line-background: rgba(var(--red_x), 0.1); 200 | 201 | --text-normal: var(--dark2); 202 | --text-faint: var(--dark0); 203 | --text-muted: var(--dark1); 204 | 205 | --link-url: var(--purple); 206 | 207 | --h1-color: var(--red); 208 | --h2-color: var(--yellow); 209 | --h3-color: var(--green); 210 | --h4-color: var(--purple); 211 | --h5-color: var(--frost0); 212 | --h6-color: var(--frost2); 213 | 214 | --text-highlight-bg: var(--yellow); 215 | --text-highlight-fg: var(--dark0); 216 | 217 | --text-accent: var(--orange); 218 | --text-accent-hover: var(--frost2); 219 | 220 | --tag-color: var(--dark3); 221 | --tag-background: var(--light1); 222 | --tag-background-hover: var(--light0); 223 | 224 | --titlebar-text-color-focused: var(--red); 225 | 226 | --inline-title-color: var(--yellow); 227 | 228 | --bold-color: var(--green); 229 | --italic-color: var(--green); 230 | 231 | --checkbox-color: var(--frost2); 232 | --checkbox-color-hover: var(--frost2); 233 | --checkbox-border-color: var(--frost2); 234 | --checkbox-border-color-hover: var(--frost2); 235 | --checklist-done-color: rgba(var(--dark2_x), 0.4); 236 | 237 | --table-header-background: rgba(var(--light2_x), 0.2); 238 | --table-header-background-hover: var(--frost2); 239 | --table-row-even-background: rgba(var(--light2_x), 0.4); 240 | --table-row-odd-background: rgba(var(--light2_x), 0.8); 241 | --table-row-background-hover: var(--frost2); 242 | 243 | --text-selection: rgba(var(--red_x), 0.6); 244 | --flashing-background: rgba(var(--red_x), 0.3); 245 | 246 | --code-normal: var(--frost1); 247 | --code-background: var(--light2); 248 | 249 | --mermaid-note: var(--frost0); 250 | --mermaid-loopline: var(--frost1); 251 | --mermaid-exclude: var(--light0); 252 | --mermaid-seqnum: var(--light0); 253 | 254 | --icon-color-hover: var(--red); 255 | --icon-color-focused: var(--frost3); 256 | 257 | --nav-item-color-hover: var(--red); 258 | --nav-item-color-active: var(--frost2); 259 | --nav-file-tag: rgba(var(--orange_x), 0.9); 260 | 261 | --graph-line: var(--light0); 262 | --graph-node: var(--dark3); 263 | --graph-node-tag: var(--red); 264 | --graph-node-attachment: var(--green); 265 | 266 | --calendar-hover: var(--red); 267 | --calendar-background-hover: var(--light0); 268 | --calendar-week: var(--orange); 269 | --calendar-today: var(--orange); 270 | 271 | --dataview-key: var(--text-faint); 272 | --dataview-key-background: rgba(var(--frost2_x), 0.3); 273 | --dataview-value: var(--text-faint); 274 | --dataview-value-background: rgba(var(--red_x), 0.3); 275 | 276 | --tab-text-color-focused-active: var(--frost2); 277 | --tab-text-color-focused-active-current: var(--red); 278 | } 279 | 280 | table 281 | { 282 | border: 1px solid var(--background-secondary) !important; 283 | border-collapse: collapse; 284 | } 285 | 286 | thead 287 | { 288 | border-bottom: 2px solid var(--background-modifier-border) !important; 289 | } 290 | 291 | th 292 | { 293 | font-weight: 600 !important; 294 | border: 1px solid var(--background-secondary) !important; 295 | } 296 | 297 | td 298 | { 299 | border-left: 1px solid var(--background-secondary) !important; 300 | border-right: 1px solid var(--background-secondary) !important; 301 | border-bottom: 1px solid var(--background-secondary) !important; 302 | } 303 | 304 | .markdown-rendered tbody tr:nth-child(even) 305 | { 306 | background-color: var(--table-row-even-background) !important; 307 | } 308 | 309 | .markdown-rendered tbody tr:nth-child(odd) 310 | { 311 | background-color: var(--table-row-odd-background) !important; 312 | } 313 | 314 | .markdown-rendered tbody tr:nth-child(even):hover, 315 | .markdown-rendered tbody tr:nth-child(odd):hover 316 | { 317 | background-color: var(--table-row-background-hover) !important; 318 | } 319 | 320 | .markdown-rendered mark 321 | { 322 | background-color: var(--text-highlight-bg); 323 | color: var(--text-highlight-fg); 324 | } 325 | 326 | .markdown-rendered mark a 327 | { 328 | color: var(--red) !important; 329 | font-weight: 600; 330 | } 331 | 332 | .search-result-file-matched-text 333 | { 334 | color: var(--text-highlight-fg) !important; 335 | } 336 | 337 | .cm-hashtag-begin:hover, .cm-hashtag-end:hover 338 | { 339 | color: var(--text-accent); 340 | /* background-color: var(--tag-background-hover); */ 341 | text-decoration: underline; 342 | } 343 | 344 | input[type=checkbox] 345 | { 346 | border: 1px solid var(--checkbox-color); 347 | } 348 | 349 | input[type=checkbox]:checked 350 | { 351 | background-color: var(--checkbox-color); 352 | box-shadow: inset 0 0 0 2px var(--background-primary); 353 | } 354 | 355 | input[type=checkbox]:checked:after 356 | { 357 | display: none; 358 | } 359 | 360 | code[class*="language-"], 361 | pre[class*="language-"] 362 | { 363 | line-height: var(--line-height-tight) !important; 364 | } 365 | 366 | .cm-url 367 | { 368 | color: var(--link-url) !important; 369 | } 370 | 371 | .cm-url:hover 372 | { 373 | color: var(--text-accent-hover) !important; 374 | } 375 | 376 | /* Keep highlight/marks the same between viewer and editor. */ 377 | .cm-highlight 378 | { 379 | color: var(--text-highlight-fg) !important; 380 | } 381 | 382 | /* Keep inline code the same between viewer and editor. */ 383 | .cm-inline-code 384 | { 385 | border-radius: var(--radius-s); 386 | font-size: var(--code-size); 387 | padding: 0.1em 0.25em; 388 | } 389 | 390 | .cm-formatting-code + .cm-inline-code 391 | { 392 | border-radius: 0; 393 | padding: 0.1em 0; 394 | } 395 | 396 | .cm-formatting-code 397 | { 398 | border-radius: var(--radius-s) 0 0 var(--radius-s); 399 | padding: 0.1em 0 0.1em 0.25em; 400 | } 401 | 402 | .cm-inline-code + .cm-formatting-code 403 | { 404 | border-radius: 0 var(--radius-s) var(--radius-s) 0; 405 | padding: 0.1em 0.25em 0.1em 0; 406 | } 407 | 408 | .cm-line .cm-strong 409 | { 410 | color: var(--bold-color) !important; 411 | } 412 | 413 | /* 414 | * Keep list bullet padding the same between viewer and editor. 415 | * This is annoying with the cursor in the editor as there is a gap. 416 | */ 417 | /* 418 | .cm-formatting-list 419 | { 420 | padding-right: 4px !important; 421 | } 422 | */ 423 | 424 | /* 425 | * Keep sub-list indenting the same between viewer and editor. 426 | * This assumes --list-indent is default at 2em. 427 | */ 428 | /* 429 | .cm-indent 430 | { 431 | text-indent: 1em !important; 432 | } 433 | */ 434 | 435 | .mermaid .note 436 | { 437 | fill: var(--mermaid-note) !important; 438 | } 439 | 440 | .mermaid .loopLine 441 | { 442 | stroke: var(--mermaid-loopline) !important; 443 | } 444 | 445 | .mermaid .loopText>tspan, 446 | .mermaid .entityLabel 447 | { 448 | fill: var(--red) !important; 449 | } 450 | 451 | .mermaid .exclude-range 452 | { 453 | fill: var(--mermaid-exclude) !important; 454 | } 455 | 456 | .mermaid .sequenceNumber 457 | { 458 | fill: var(--mermaid-seqnum) !important; 459 | } 460 | 461 | .calendar .week-num 462 | { 463 | color: var(--calendar-week) !important; 464 | } 465 | 466 | .calendar .today 467 | { 468 | color: var(--calendar-today) !important; 469 | } 470 | 471 | .calendar .week-num:hover, 472 | .calendar .day:hover 473 | { 474 | color: var(--calendar-hover) !important; 475 | background-color: var(--calendar-background-hover) !important; 476 | } 477 | 478 | .markdown-embed-title 479 | { 480 | color: var(--yellow); 481 | font-weight: 600 !important; 482 | } 483 | 484 | .cm-active 485 | { 486 | background-color: var(--cursor-line-background) !important; 487 | } 488 | 489 | .nav-file-tag 490 | { 491 | color: var(--nav-file-tag) !important; 492 | } 493 | 494 | .is-flashing 495 | { 496 | background-color: var(--flashing-background) !important; 497 | } 498 | 499 | .dataview.inline-field-key 500 | { 501 | border-top-left-radius: var(--radius-s); 502 | border-bottom-left-radius: var(--radius-s); 503 | padding-left: 4px; 504 | font-family: var(--font-monospace); 505 | font-size: var(--font-smaller); 506 | color: var(--dataview-key) !important; 507 | background-color: var(--dataview-key-background) !important; 508 | } 509 | 510 | .dataview.inline-field-value 511 | { 512 | border-top-right-radius: var(--radius-s); 513 | border-bottom-right-radius: var(--radius-s); 514 | padding-right: 4px; 515 | font-family: var(--font-monospace); 516 | font-size: var(--font-smaller); 517 | color: var(--dataview-value) !important; 518 | background-color: var(--dataview-value-background) !important; 519 | } 520 | 521 | .suggestion-highlight 522 | { 523 | color: var(--red); 524 | } 525 | 526 | .workspace-leaf-content .view-content { 527 | padding: 4px 12px 12px 12px !important; 528 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/todoist-token: -------------------------------------------------------------------------------- 1 | 63a85a1ea32d18a9d68fdf4ca9c5222267564f78 -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/types.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": { 3 | "aliases": "aliases", 4 | "cssclasses": "multitext", 5 | "tags": "tags", 6 | "平台": "multitext", 7 | "记录时间": "date", 8 | "Income": "number", 9 | "status": "multitext", 10 | "状态": "text", 11 | "标签": "multitext", 12 | "城市": "text", 13 | "同台": "multitext", 14 | "场地": "text", 15 | "序号": "number", 16 | "日期": "date", 17 | "chapter": "multitext", 18 | "学习状态": "text", 19 | "时间": "date", 20 | "发布状态": "text", 21 | "发布时间": "date", 22 | "excalidraw-plugin": "text", 23 | "excalidraw-export-transparent": "checkbox", 24 | "excalidraw-mask": "checkbox", 25 | "excalidraw-export-dark": "checkbox", 26 | "excalidraw-export-padding": "number", 27 | "excalidraw-export-pngscale": "number", 28 | "excalidraw-link-prefix": "text", 29 | "excalidraw-url-prefix": "text", 30 | "excalidraw-link-brackets": "checkbox", 31 | "excalidraw-onload-script": "text", 32 | "excalidraw-linkbutton-opacity": "number", 33 | "excalidraw-default-mode": "text", 34 | "excalidraw-font": "text", 35 | "excalidraw-font-color": "text", 36 | "excalidraw-border-color": "text", 37 | "excalidraw-css": "text", 38 | "excalidraw-autoexport": "text", 39 | "excalidraw-iframe-theme": "text", 40 | "发布平台": "multitext", 41 | "地点": "multitext", 42 | "开始日期": "date", 43 | "结束日期": "date", 44 | "类目": "text", 45 | "featured": "text", 46 | "excalidraw-open-md": "checkbox", 47 | "slug": "text", 48 | "excalidraw-export-embed-scene": "checkbox", 49 | "excalidraw-embeddable-theme": "text" 50 | } 51 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/types.sync-conflict-20231126-140327-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": { 3 | "aliases": "aliases", 4 | "cssclasses": "multitext", 5 | "tags": "tags", 6 | "平台": "multitext", 7 | "记录时间": "date", 8 | "讨厌的生物群落": "multitext", 9 | "喜欢的生物群落": "multitext", 10 | "喜欢的 NPC": "multitext", 11 | "讨厌的 NPC": "multitext" 12 | } 13 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/workspace-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "id": "5b60aa19007ef85d", 4 | "type": "split", 5 | "children": [ 6 | { 7 | "id": "78e1e916943c43d3", 8 | "type": "tabs", 9 | "children": [ 10 | { 11 | "id": "e21380246796464b", 12 | "type": "leaf", 13 | "state": { 14 | "type": "markdown", 15 | "state": { 16 | "file": "02-Index/🏠Home.md", 17 | "mode": "preview", 18 | "backlinks": false, 19 | "source": false 20 | } 21 | } 22 | } 23 | ] 24 | } 25 | ], 26 | "direction": "vertical" 27 | }, 28 | "left": { 29 | "id": "ba6020d0746d6b39", 30 | "type": "mobile-drawer", 31 | "children": [ 32 | { 33 | "id": "cf52295c9c67a8f7", 34 | "type": "leaf", 35 | "state": { 36 | "type": "file-explorer", 37 | "state": { 38 | "sortOrder": "alphabetical" 39 | } 40 | } 41 | }, 42 | { 43 | "id": "e22196b75f1d46bb", 44 | "type": "leaf", 45 | "state": { 46 | "type": "tag", 47 | "state": { 48 | "sortOrder": "frequency", 49 | "useHierarchy": true 50 | } 51 | } 52 | } 53 | ], 54 | "currentTab": 0 55 | }, 56 | "right": { 57 | "id": "29e3b7e635b67de8", 58 | "type": "mobile-drawer", 59 | "children": [ 60 | { 61 | "id": "d92c6f04cfecfeca", 62 | "type": "leaf", 63 | "state": { 64 | "type": "outline", 65 | "state": { 66 | "file": "02-Index/🏠Home.md" 67 | } 68 | } 69 | }, 70 | { 71 | "id": "ed27d47c9ec4373a", 72 | "type": "leaf", 73 | "state": { 74 | "type": "backlink", 75 | "state": { 76 | "file": "02-Index/🏠Home.md", 77 | "collapseAll": false, 78 | "extraContext": false, 79 | "sortOrder": "alphabetical", 80 | "showSearch": false, 81 | "searchQuery": "", 82 | "backlinkCollapsed": false, 83 | "unlinkedCollapsed": true 84 | } 85 | } 86 | }, 87 | { 88 | "id": "e6a52c865242b706", 89 | "type": "leaf", 90 | "state": { 91 | "type": "outgoing-link", 92 | "state": { 93 | "file": "02-Index/🏠Home.md", 94 | "linksCollapsed": false, 95 | "unlinkedCollapsed": true 96 | } 97 | } 98 | } 99 | ], 100 | "currentTab": 0 101 | }, 102 | "left-ribbon": { 103 | "hiddenItems": { 104 | "homepage:Open homepage": false, 105 | "obsidian-excalidraw-plugin:新建绘图文件": true, 106 | "canvas:新建白板": true, 107 | "templates:插入模板": false, 108 | "switcher:打开快速切换": true, 109 | "workspaces:管理工作区布局": true, 110 | "command-palette:打开命令面板": false, 111 | "graph:查看关系图谱": false, 112 | "random-note:开始漫游笔记": false, 113 | "remotely-save:Remotely Save": false 114 | } 115 | }, 116 | "active": "cf52295c9c67a8f7", 117 | "lastOpenFiles": [ 118 | "03-MyNotes/玩转Vim从放弃到入门.md", 119 | "02-Index/🛠Tools&Skills.md", 120 | "03-MyNotes/📱Hardware.md", 121 | "02-Index/🏠Home.md", 122 | "02-Index/💾Software.md", 123 | "04-Project/研究生/开题.md", 124 | "03-MyNotes/PyQt5.md", 125 | "03-MyNotes/记账软件配置.md", 126 | "04-Project/研究生/优点.md", 127 | "03-MyNotes/v2rayA.md", 128 | "04-Project/研究生/小论文.md", 129 | "04-Project/想法/写作软件.md", 130 | "04-Project/研究生/大论文.md", 131 | "04-Project/想法/个人博客.md", 132 | "03-MyNotes/Mermaid.md", 133 | "03-MyNotes/Loss 函数.md", 134 | "04-Project/研究生/研究路线.md", 135 | "04-Project/研究生/联合仿真.md", 136 | "04-Project/研究生/行程控制.md", 137 | "04-Project/研究生/模型验证.md", 138 | "04-Project/研究生/故障报警.md", 139 | "04-Project/研究生/数学模型.md", 140 | "04-Project/研究生/恢复系数.md", 141 | "04-Project/研究生/分段分析.md", 142 | "04-Project/Xtab/小组件.md", 143 | "04-Project/研究生/整理后数据.md", 144 | "04-Project/研究生/电科院项目.canvas", 145 | "04-Project/Xtab/Xtab.canvas", 146 | "04-Project/LifeGo/LifeGo.canvas", 147 | "04-Project/研究生/Postgraduate.canvas", 148 | "04-Project/想法", 149 | "04-Project/研究生", 150 | "04-Project/@归档", 151 | "04-Project/Xtab", 152 | "04-Project/LifeGo", 153 | "04-Project", 154 | "05-Project/LifeGo/LifeGo.canvas", 155 | "01-Extra/Images/Banner/moon.jpg", 156 | "05-Project/Xtab/Xtab.canvas", 157 | "05-Project/研究生/Postgraduate.canvas", 158 | "01-Extra/Images/Pasted image 20231109111818.png", 159 | "01-Extra/Images/Pasted image 20231108212059.png", 160 | "01-Extra/Images/Pasted image 20231109105221.png", 161 | "01-Extra/Images/Pasted image 20231108185605.png", 162 | "01-Extra/Images/Pasted image 20231108200426.png", 163 | "01-Extra/Images/Pasted image 20231108185057.png", 164 | "01-Extra/Images/Pasted image 20231105215437.png", 165 | "01-Extra/Images/Pasted image 20231108185247.png", 166 | "01-Extra/Images/Pasted image 20231105215151.png", 167 | "05-Project/想法", 168 | "05-Project/Xtab", 169 | "05-Project/研究生/研究生白板.canvas", 170 | "05-Project/研究生/电科院项目.canvas", 171 | "01-Extra/Images/theme.css", 172 | "01-Extra/Images/Banner", 173 | "04-Spaces/Writing/血翼浮屠/人物设定/@人物设定.canvas" 174 | ] 175 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/workspace-mobile.sync-conflict-20231126-140322-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "id": "5b60aa19007ef85d", 4 | "type": "split", 5 | "children": [ 6 | { 7 | "id": "3c367ffc64d4b756", 8 | "type": "tabs", 9 | "children": [ 10 | { 11 | "id": "1689c571d27f1273", 12 | "type": "leaf", 13 | "state": { 14 | "type": "markdown", 15 | "state": { 16 | "file": "obsidian/Home.md", 17 | "mode": "preview", 18 | "backlinks": false, 19 | "source": false 20 | } 21 | } 22 | } 23 | ] 24 | } 25 | ], 26 | "direction": "vertical" 27 | }, 28 | "left": { 29 | "id": "351212192ae44531", 30 | "type": "mobile-drawer", 31 | "children": [], 32 | "currentTab": 0 33 | }, 34 | "right": { 35 | "id": "d8ca51b1171e53a9", 36 | "type": "mobile-drawer", 37 | "children": [], 38 | "currentTab": 0 39 | }, 40 | "left-ribbon": { 41 | "hiddenItems": { 42 | "templater-obsidian:Templater": true, 43 | "homepage:Open homepage": false, 44 | "obsidian-custom-frames:Open 欧路词典": false, 45 | "obsidian-excalidraw-plugin:新建 Excalidraw 绘图": false, 46 | "canvas:新建白板": false, 47 | "templates:插入模板": false, 48 | "switcher:打开快速切换": false, 49 | "workspaces:管理工作区布局": false, 50 | "table-editor-obsidian:Advanced Tables Toolbar": false, 51 | "cmdr:Workspaces Plus: Load: 科研": false, 52 | "cmdr:Workspaces Plus: Load: 编程": false, 53 | "command-palette:打开命令面板": true, 54 | "remotely-save:Remotely Save": true, 55 | "graph:查看关系图谱": false, 56 | "daily-notes:打开/创建今天的日记": true, 57 | "cmdr:Workspaces Plus: Load: 写作": false, 58 | "cmdr:Workspaces Plus: Load: 英语": false, 59 | "cmdr:Workspaces Plus: Load: 阅读": false 60 | } 61 | }, 62 | "active": "1689c571d27f1273", 63 | "lastOpenFiles": [ 64 | "日记/2022-02-16.md", 65 | "编程/markdown语法.md", 66 | "markdown语法.md", 67 | "日记/2021-01-19.md", 68 | "日记/2022-03-02.md", 69 | "日记/2022-03-03.md", 70 | "日记/2022-03-27.md", 71 | "日记/2022-04-25.md", 72 | "日记/2022-05-04.md", 73 | "日记/2021-09-13.md" 74 | ] 75 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "id": "5b60aa19007ef85d", 4 | "type": "split", 5 | "children": [ 6 | { 7 | "id": "9a00b0fe2633c7a5", 8 | "type": "tabs", 9 | "children": [ 10 | { 11 | "id": "8646cae11cdc4d36", 12 | "type": "leaf", 13 | "state": { 14 | "type": "bookshelf-view", 15 | "state": {} 16 | } 17 | } 18 | ] 19 | } 20 | ], 21 | "direction": "vertical" 22 | }, 23 | "left": { 24 | "id": "96126693d414b60b", 25 | "type": "split", 26 | "children": [ 27 | { 28 | "id": "a1d25f9a87b488e6", 29 | "type": "tabs", 30 | "dimension": 62.28513650151668, 31 | "children": [ 32 | { 33 | "id": "64b5c54b663de7b7", 34 | "type": "leaf", 35 | "state": { 36 | "type": "file-explorer", 37 | "state": { 38 | "sortOrder": "alphabetical" 39 | } 40 | } 41 | }, 42 | { 43 | "id": "4e5dbfc3ca789e34", 44 | "type": "leaf", 45 | "state": { 46 | "type": "file-list-view", 47 | "state": {} 48 | } 49 | } 50 | ], 51 | "currentTab": 1 52 | }, 53 | { 54 | "id": "a66edc443ecedb80", 55 | "type": "tabs", 56 | "dimension": 37.71486349848331, 57 | "children": [ 58 | { 59 | "id": "cb5053aae493a06f", 60 | "type": "leaf", 61 | "state": { 62 | "type": "search", 63 | "state": { 64 | "query": "选择器", 65 | "matchingCase": true, 66 | "explainSearch": false, 67 | "collapseAll": true, 68 | "extraContext": true, 69 | "sortOrder": "alphabetical" 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | ], 76 | "direction": "horizontal", 77 | "width": 217.5 78 | }, 79 | "right": { 80 | "id": "fb296b32c27524ad", 81 | "type": "split", 82 | "children": [ 83 | { 84 | "id": "45eb2d15b7f78c99", 85 | "type": "tabs", 86 | "children": [ 87 | { 88 | "id": "b74619744e1cb540", 89 | "type": "leaf", 90 | "state": { 91 | "type": "book-setting", 92 | "state": {} 93 | } 94 | } 95 | ] 96 | } 97 | ], 98 | "direction": "horizontal", 99 | "width": 624.5 100 | }, 101 | "left-ribbon": { 102 | "hiddenItems": { 103 | "homepage:Open homepage": false, 104 | "canvas:新建白板": false, 105 | "templates:插入模板": false, 106 | "switcher:打开快速切换": false, 107 | "workspaces:管理工作区布局": true, 108 | "command-palette:打开命令面板": false, 109 | "graph:查看关系图谱": false 110 | } 111 | }, 112 | "active": "8646cae11cdc4d36", 113 | "lastOpenFiles": [ 114 | "短篇小说3/小说正文.md", 115 | "长篇小说1/小说文稿/第1卷/第2章.md", 116 | "短篇小说2/小说正文.md", 117 | "短篇小说1/小说正文.md", 118 | "短篇小说1/大纲.md", 119 | "长篇小说2/小说文稿/未命名章节.md", 120 | "111/小说文稿/未命名章节.md", 121 | "222/小说文稿/未命名章节.md", 122 | "222/小说文稿", 123 | "222/信息.md", 124 | "222", 125 | "111/小说文稿", 126 | "111/信息.md", 127 | "111", 128 | "长篇小说1/设定/大纲/总纲/大纲.md", 129 | "长篇小说1/小说文稿/第2卷/第3章.md", 130 | "长篇小说1/设定/大纲/章纲/第二章.md", 131 | "长篇小说1/设定/大纲/章纲/第一章.md", 132 | "长篇小说1/设定/大纲/章纲", 133 | "长篇小说1/设定/大纲/总纲", 134 | "长篇小说1/设定/大纲", 135 | "长篇小说1/设定", 136 | "长篇小说1/小说文稿/第1卷/第1章 .md", 137 | "长篇小说1/小说文稿/第2卷", 138 | "长篇小说1/小说文稿/楔子.md", 139 | "长篇小说1/小说文稿/第1卷", 140 | "短篇小说2/大纲.md", 141 | "短篇小说3/信息.md", 142 | "短篇小说2/信息.md", 143 | "短篇小说1/信息.md", 144 | "长篇小说2/信息.md", 145 | "长篇小说1/信息.md", 146 | "@附件/灵感/全局灵感2.md", 147 | "@附件/灵感/全局灵感1.md", 148 | "欢迎.md", 149 | "短篇小说测试/小说正文.md", 150 | "@附件/图片/Morick.jpg", 151 | "叠龟宇宙/cover.jpg", 152 | "未命名.canvas", 153 | "门外有人/设定/角色/人物关系.canvas", 154 | "跟自己聊天/设定/角色/人物关系.canvas", 155 | "影子调查员/设定/角色/人物关系.canvas", 156 | "content/post/2024/05/20240506172253/2420240506170559.png", 157 | "content/post/2024/05/20240506172253/random.svg", 158 | "content/post/2024/05/20240516085858/Anytype.svg", 159 | "content/page/links/CodeKid.jpg", 160 | "public/links/CodeKid.jpg", 161 | "public/about/UFO.jpg", 162 | "public/categories/诗歌小说/novel.svg", 163 | "public/categories/经验分享/experience.svg", 164 | "@Extras/未命名.canvas", 165 | "02-Areas/研究生/研究笔记/@研究笔记.canvas", 166 | "02-Areas/研究生/研究笔记/量子启发式算法.canvas", 167 | "Home.canvas", 168 | "02-Areas/01-研究笔记/量子启发式算法.canvas", 169 | "02-Areas/01-研究笔记/@研究笔记.canvas" 170 | ] 171 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/workspaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspaces": { 3 | "主页": { 4 | "main": { 5 | "id": "5b60aa19007ef85d", 6 | "type": "split", 7 | "children": [ 8 | { 9 | "id": "9a00b0fe2633c7a5", 10 | "type": "tabs", 11 | "children": [ 12 | { 13 | "id": "8646cae11cdc4d36", 14 | "type": "leaf", 15 | "state": { 16 | "type": "bookshelf-view", 17 | "state": {} 18 | } 19 | } 20 | ] 21 | } 22 | ], 23 | "direction": "vertical" 24 | }, 25 | "left": { 26 | "id": "96126693d414b60b", 27 | "type": "split", 28 | "children": [ 29 | { 30 | "id": "a1d25f9a87b488e6", 31 | "type": "tabs", 32 | "dimension": 62.28513650151668, 33 | "children": [ 34 | { 35 | "id": "64b5c54b663de7b7", 36 | "type": "leaf", 37 | "state": { 38 | "type": "file-explorer", 39 | "state": { 40 | "sortOrder": "alphabetical" 41 | } 42 | } 43 | }, 44 | { 45 | "id": "4e5dbfc3ca789e34", 46 | "type": "leaf", 47 | "state": { 48 | "type": "file-list-view", 49 | "state": {} 50 | } 51 | } 52 | ], 53 | "currentTab": 1 54 | }, 55 | { 56 | "id": "a66edc443ecedb80", 57 | "type": "tabs", 58 | "dimension": 37.71486349848331, 59 | "children": [ 60 | { 61 | "id": "cb5053aae493a06f", 62 | "type": "leaf", 63 | "state": { 64 | "type": "search", 65 | "state": { 66 | "query": "选择器", 67 | "matchingCase": true, 68 | "explainSearch": false, 69 | "collapseAll": true, 70 | "extraContext": true, 71 | "sortOrder": "alphabetical" 72 | } 73 | } 74 | } 75 | ] 76 | } 77 | ], 78 | "direction": "horizontal", 79 | "width": 217.5 80 | }, 81 | "right": { 82 | "id": "fb296b32c27524ad", 83 | "type": "split", 84 | "children": [ 85 | { 86 | "id": "45eb2d15b7f78c99", 87 | "type": "tabs", 88 | "children": [ 89 | { 90 | "id": "b74619744e1cb540", 91 | "type": "leaf", 92 | "state": { 93 | "type": "book-setting", 94 | "state": {} 95 | } 96 | } 97 | ] 98 | } 99 | ], 100 | "direction": "horizontal", 101 | "width": 624.5 102 | }, 103 | "left-ribbon": { 104 | "hiddenItems": { 105 | "homepage:Open homepage": false, 106 | "canvas:新建白板": false, 107 | "templates:插入模板": false, 108 | "switcher:打开快速切换": false, 109 | "workspaces:管理工作区布局": true, 110 | "command-palette:打开命令面板": false, 111 | "graph:查看关系图谱": false, 112 | "copilot_kimi:Copilot Chat": true, 113 | "writer-suite:设定": false, 114 | "writer-suite:书架": false, 115 | "writer-suite:目录": false 116 | } 117 | }, 118 | "active": "8646cae11cdc4d36", 119 | "mtime": "2024-07-01T10:26:44+08:00" 120 | } 121 | }, 122 | "active": "主页" 123 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/workspaces.sync-conflict-20231126-140321-BRA7AFV.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspaces": { 3 | "主页": { 4 | "main": { 5 | "id": "5b60aa19007ef85d", 6 | "type": "split", 7 | "children": [ 8 | { 9 | "id": "78e1e916943c43d3", 10 | "type": "tabs", 11 | "children": [ 12 | { 13 | "id": "e21380246796464b", 14 | "type": "leaf", 15 | "state": { 16 | "type": "markdown", 17 | "state": { 18 | "file": "02-Index/🏠Home.md", 19 | "mode": "preview", 20 | "backlinks": false, 21 | "source": false 22 | } 23 | } 24 | } 25 | ] 26 | } 27 | ], 28 | "direction": "vertical" 29 | }, 30 | "left": { 31 | "id": "96126693d414b60b", 32 | "type": "split", 33 | "children": [ 34 | { 35 | "id": "a1d25f9a87b488e6", 36 | "type": "tabs", 37 | "dimension": 65, 38 | "children": [ 39 | { 40 | "id": "64b5c54b663de7b7", 41 | "type": "leaf", 42 | "state": { 43 | "type": "file-explorer", 44 | "state": { 45 | "sortOrder": "alphabetical" 46 | } 47 | } 48 | }, 49 | { 50 | "id": "cb5053aae493a06f", 51 | "type": "leaf", 52 | "state": { 53 | "type": "search", 54 | "state": { 55 | "query": "tag:#插件", 56 | "matchingCase": true, 57 | "explainSearch": false, 58 | "collapseAll": true, 59 | "extraContext": true, 60 | "sortOrder": "alphabetical" 61 | } 62 | } 63 | } 64 | ] 65 | }, 66 | { 67 | "id": "40b2cd084788a39a", 68 | "type": "tabs", 69 | "dimension": 35, 70 | "children": [ 71 | { 72 | "id": "cdb507b53df44574", 73 | "type": "leaf", 74 | "state": { 75 | "type": "tag", 76 | "state": { 77 | "sortOrder": "frequency", 78 | "useHierarchy": true 79 | } 80 | } 81 | } 82 | ] 83 | } 84 | ], 85 | "direction": "horizontal", 86 | "width": 329.5 87 | }, 88 | "right": { 89 | "id": "fb296b32c27524ad", 90 | "type": "split", 91 | "children": [ 92 | { 93 | "id": "485cdb48ee4b7ef5", 94 | "type": "tabs", 95 | "dimension": 63.07692307692307, 96 | "children": [ 97 | { 98 | "id": "4ccaf6b9d8e7bbf9", 99 | "type": "leaf", 100 | "state": { 101 | "type": "outline", 102 | "state": { 103 | "file": "02-Index/🏠Home.md" 104 | } 105 | } 106 | } 107 | ] 108 | }, 109 | { 110 | "id": "25be4a8a260fa456", 111 | "type": "tabs", 112 | "dimension": 36.92307692307693, 113 | "children": [ 114 | { 115 | "id": "865734b58d14fa1d", 116 | "type": "leaf", 117 | "state": { 118 | "type": "localgraph", 119 | "state": { 120 | "file": "02-Index/🏠Home.md", 121 | "options": { 122 | "collapse-filter": true, 123 | "search": "", 124 | "localJumps": 1, 125 | "localBacklinks": true, 126 | "localForelinks": true, 127 | "localInterlinks": false, 128 | "showTags": false, 129 | "showAttachments": false, 130 | "hideUnresolved": false, 131 | "collapse-color-groups": true, 132 | "colorGroups": [], 133 | "collapse-display": true, 134 | "showArrow": false, 135 | "textFadeMultiplier": 0, 136 | "nodeSizeMultiplier": 1, 137 | "lineSizeMultiplier": 1, 138 | "collapse-forces": true, 139 | "centerStrength": 0.518713248970312, 140 | "repelStrength": 10, 141 | "linkStrength": 1, 142 | "linkDistance": 250, 143 | "scale": 0.5087618855792587, 144 | "close": true 145 | } 146 | } 147 | } 148 | }, 149 | { 150 | "id": "d645248d48f913e1", 151 | "type": "leaf", 152 | "state": { 153 | "type": "backlink", 154 | "state": { 155 | "file": "02-Index/🏠Home.md", 156 | "collapseAll": false, 157 | "extraContext": false, 158 | "sortOrder": "alphabetical", 159 | "showSearch": false, 160 | "searchQuery": "", 161 | "backlinkCollapsed": false, 162 | "unlinkedCollapsed": true 163 | } 164 | } 165 | }, 166 | { 167 | "id": "7a5aa37298515d78", 168 | "type": "leaf", 169 | "state": { 170 | "type": "outgoing-link", 171 | "state": { 172 | "file": "02-Index/🏠Home.md", 173 | "linksCollapsed": false, 174 | "unlinkedCollapsed": false 175 | } 176 | } 177 | } 178 | ] 179 | } 180 | ], 181 | "direction": "horizontal", 182 | "width": 384.5 183 | }, 184 | "left-ribbon": { 185 | "hiddenItems": { 186 | "homepage:Open homepage": false, 187 | "obsidian-excalidraw-plugin:新建绘图文件": false, 188 | "oz-clear-unused-images:Clear Unused Images": false, 189 | "canvas:新建白板": false, 190 | "templates:插入模板": false, 191 | "switcher:打开快速切换": false, 192 | "workspaces:管理工作区布局": false, 193 | "command-palette:打开命令面板": false, 194 | "graph:查看关系图谱": false, 195 | "random-note:开始漫游笔记": false, 196 | "remotely-save:Remotely Save": false, 197 | "obsidian-custom-frames:Open Writeathon": false 198 | } 199 | }, 200 | "active": "4ccaf6b9d8e7bbf9" 201 | } 202 | }, 203 | "active": "主页" 204 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/.obsidian/zk-prefixer.json: -------------------------------------------------------------------------------- 1 | { 2 | "folder": "@Inbox", 3 | "format": "YYYY-MM-DD" 4 | } -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/@附件/灵感/全局灵感1.md: -------------------------------------------------------------------------------- 1 | 这里是全局灵感,这是第一个全局灵感 -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/@附件/灵感/全局灵感2.md: -------------------------------------------------------------------------------- 1 | 全局灵感是一个记录你突发奇想的灵感,但是不局限于某一本书 -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/短篇小说1/信息.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: short-story 3 | --- 4 | 名称: 短篇小说1 5 | 简介: 短篇小说简介 -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/短篇小说1/大纲.md: -------------------------------------------------------------------------------- 1 | 这是短篇小说的大纲内容 2 | 3 | # 起因 4 | 5 | # 经过 6 | 7 | # 结果 8 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/短篇小说1/小说正文.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/短篇小说2/信息.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: short-story 3 | --- 4 | 名称: 短篇小说2 5 | 简介: -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/短篇小说2/大纲.md: -------------------------------------------------------------------------------- 1 | 这是短篇小说的大纲 2 | 3 | 可以显示在侧边栏中 4 | 5 | # 显示标题 6 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/短篇小说2/小说正文.md: -------------------------------------------------------------------------------- 1 | # 标题 1 2 | 3 | 第二部分内容 4 | 5 | # 标题 2 6 | 7 | 第二部分内容 8 | 9 | # 标题 3 10 | 11 | 第三部分内容 12 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/短篇小说3/信息.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: short-story 3 | --- 4 | 名称: 短篇小说3 5 | 简介: -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/短篇小说3/小说正文.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说1/信息.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: novel 3 | --- 4 | 名称: 长篇小说1 5 | 简介: 这是书籍简介 -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说1/小说文稿/楔子.md: -------------------------------------------------------------------------------- 1 | 这是楔子的内容 2 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说1/小说文稿/第1卷/第1章 .md: -------------------------------------------------------------------------------- 1 | 这是长篇小说的第一章内容 2 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说1/小说文稿/第1卷/第2章.md: -------------------------------------------------------------------------------- 1 | 这是长篇小说的第二章内容 2 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说1/小说文稿/第2卷/第3章.md: -------------------------------------------------------------------------------- 1 | 这是长篇小说的第三章内容 2 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说1/设定/大纲/总纲/大纲.md: -------------------------------------------------------------------------------- 1 | 这是长篇小说大纲部分的内容 2 | 3 | # 起因 4 | 5 | 起因内容 6 | 7 | # 经过 8 | 9 | 经过内容 10 | 11 | # 结果 12 | 13 | 结果内容 14 | -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说1/设定/大纲/章纲/第一章.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Morick66/obsidian-writer-suite/97f42e92044ec31f324ce08355064bbbc15fbd30/WriterSuite-ExampleVault/长篇小说1/设定/大纲/章纲/第一章.md -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说1/设定/大纲/章纲/第二章.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Morick66/obsidian-writer-suite/97f42e92044ec31f324ce08355064bbbc15fbd30/WriterSuite-ExampleVault/长篇小说1/设定/大纲/章纲/第二章.md -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说2/信息.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: novel 3 | --- 4 | 名称: 长篇小说2 5 | 简介: 小说2的简介 -------------------------------------------------------------------------------- /WriterSuite-ExampleVault/长篇小说2/小说文稿/未命名章节.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === "production"); 13 | 14 | const context = await esbuild.context({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: [".//src/main.ts"], 19 | bundle: true, 20 | external: [ 21 | "obsidian", 22 | "electron", 23 | "@codemirror/autocomplete", 24 | "@codemirror/collab", 25 | "@codemirror/commands", 26 | "@codemirror/language", 27 | "@codemirror/lint", 28 | "@codemirror/search", 29 | "@codemirror/state", 30 | "@codemirror/view", 31 | "@lezer/common", 32 | "@lezer/highlight", 33 | "@lezer/lr", 34 | ...builtins], 35 | format: "cjs", 36 | target: "es2018", 37 | logLevel: "info", 38 | sourcemap: prod ? false : "inline", 39 | treeShaking: true, 40 | outfile: "main.js", 41 | }); 42 | 43 | if (prod) { 44 | await context.rebuild(); 45 | process.exit(0); 46 | } else { 47 | await context.watch(); 48 | } -------------------------------------------------------------------------------- /images/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Morick 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 | -------------------------------------------------------------------------------- /images/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Morick66/obsidian-writer-suite/97f42e92044ec31f324ce08355064bbbc15fbd30/images/image-1.png -------------------------------------------------------------------------------- /images/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Morick66/obsidian-writer-suite/97f42e92044ec31f324ce08355064bbbc15fbd30/images/image-2.png -------------------------------------------------------------------------------- /images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Morick66/obsidian-writer-suite/97f42e92044ec31f324ce08355064bbbc15fbd30/images/image.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "writer-suite", 3 | "name": "Writer Suite", 4 | "version": "0.2.3", 5 | "minAppVersion": "0.13.21", 6 | "description": "An obsidian plug-in that is more convenient for writing novels", 7 | "author": "Morick", 8 | "authorUrl": "https://github.com/Morick66", 9 | "isDesktopOnly": true 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-sample-plugin", 3 | "version": "1.0.0", 4 | "description": "This is a sample plugin for Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@types/node": "^16.11.6", 16 | "@typescript-eslint/eslint-plugin": "5.29.0", 17 | "@typescript-eslint/parser": "5.29.0", 18 | "builtin-modules": "3.3.0", 19 | "esbuild": "0.17.3", 20 | "obsidian": "latest", 21 | "tslib": "2.4.0", 22 | "typescript": "4.7.4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/helper/WordCount.ts: -------------------------------------------------------------------------------- 1 | import { TFile, TFolder } from 'obsidian'; 2 | import MyPlugin from '../main'; 3 | 4 | // WordCounter类,用于统计字数 5 | export class WordCounter { 6 | plugin: MyPlugin; 7 | 8 | constructor(plugin: MyPlugin) { 9 | this.plugin = plugin; 10 | } 11 | 12 | async getWordCount(file: TFile): Promise { 13 | const fileContents = await this.plugin.app.vault.read(file); 14 | const countPunctuation = this.plugin.settings.countPunctuation; 15 | 16 | const words = fileContents.replace(/\s/g, ''); 17 | 18 | if (!countPunctuation) { 19 | const noPunctuation = words.replace(/[\p{P}\p{S}]/gu, ''); 20 | return noPunctuation.length; 21 | } 22 | 23 | return words.length; 24 | } 25 | 26 | async getTotalWordCount(folder: TFolder): Promise { 27 | let totalWordCount = 0; 28 | 29 | const calculateWordCount = async (folder: TFolder) => { 30 | for (const child of folder.children) { 31 | if (child instanceof TFile) { 32 | const wordCount = await this.getWordCount(child); 33 | totalWordCount += wordCount; 34 | } else if (child instanceof TFolder) { 35 | await calculateWordCount(child); 36 | } 37 | } 38 | }; 39 | 40 | await calculateWordCount(folder); 41 | return totalWordCount; 42 | } 43 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { App, Plugin, PluginSettingTab, Setting, WorkspaceLeaf, TFile, Notice } from 'obsidian'; 2 | import { TocView, VIEW_TYPE_FILE_LIST } from './view/tocView'; 3 | import { BookshelfView, VIEW_TYPE_BOOKSHELF } from './view/bookShelfView'; 4 | import { BookSettingView, VIEW_TYPE_BOOK_SETTING } from './view/bookSettingView'; 5 | 6 | // 插件设置的接口 7 | interface MyPluginSettings { 8 | name: string; 9 | picturePath: string; // 新增图片路径设置项 10 | countPunctuation: boolean; 11 | booksPerRow: number; 12 | } 13 | 14 | // 默认设置 15 | const DEFAULT_SETTINGS: MyPluginSettings = { 16 | name: '', 17 | picturePath: '', 18 | countPunctuation: false, 19 | booksPerRow: 5, 20 | } 21 | 22 | // 插件设置面板 23 | class MyPluginSettingTab extends PluginSettingTab { 24 | plugin: MyPlugin; 25 | 26 | constructor(app: App, plugin: MyPlugin) { 27 | super(app, plugin); 28 | this.plugin = plugin; 29 | } 30 | 31 | display(): void { 32 | const { containerEl } = this; 33 | 34 | containerEl.empty(); 35 | 36 | containerEl.createEl('h2', { text: '插件设置' }); 37 | 38 | new Setting(containerEl) 39 | .setName('姓名') 40 | .addText(text => { 41 | text.setValue(this.plugin.settings.name).onChange(async (value) => { 42 | this.plugin.settings.name = value; 43 | await this.plugin.saveSettings(); 44 | this.plugin.refreshTocView(); 45 | }); 46 | }); 47 | 48 | new Setting(containerEl) 49 | .setName('头像图片路径') 50 | .addText(text => { 51 | text.setValue(this.plugin.settings.picturePath).onChange(async (value) => { 52 | this.plugin.settings.picturePath = value; 53 | await this.plugin.saveSettings(); 54 | this.plugin.refreshTocView(); 55 | }); 56 | }); 57 | 58 | new Setting(containerEl) 59 | .setName('标点符号计数') 60 | .setDesc('标点符号计算为一个字符') 61 | .addToggle(toggle => toggle 62 | .setValue(this.plugin.settings.countPunctuation) 63 | .onChange(async (value) => { 64 | this.plugin.settings.countPunctuation = value; 65 | await this.plugin.saveSettings(); 66 | const view = this.app.workspace.getLeavesOfType(VIEW_TYPE_FILE_LIST)[0]?.view; 67 | if (view instanceof TocView) { 68 | view.refresh(); 69 | } 70 | })); 71 | new Setting(containerEl) 72 | .setName('每行显示书籍数量') 73 | .addText(text => { 74 | text.setValue(this.plugin.settings.booksPerRow.toString()).onChange(async (value) => { 75 | const num = parseInt(value); 76 | if (!isNaN(num) && num > 0) { 77 | this.plugin.settings.booksPerRow = num; 78 | await this.plugin.saveSettings(); 79 | // 确保在设置保存后立即刷新书架视图 80 | this.plugin.refreshBookshelfView(); 81 | } else { 82 | new Notice('请输入一个有效的正整数'); 83 | } 84 | }) 85 | }); 86 | } 87 | } 88 | 89 | // 主插件类 90 | export default class MyPlugin extends Plugin { 91 | settings: MyPluginSettings; 92 | folderPath: string; 93 | 94 | async onload() { 95 | await this.loadSettings(); 96 | 97 | this.addRibbonIcon('folder-open', '目录', () => { 98 | this.activateView(VIEW_TYPE_FILE_LIST); 99 | }); 100 | 101 | this.addRibbonIcon('book', '书架', () => { 102 | this.activateView(VIEW_TYPE_BOOKSHELF); 103 | }); 104 | 105 | this.addRibbonIcon('book-text', '设定', () => { 106 | this.activateView(VIEW_TYPE_BOOK_SETTING); 107 | }); 108 | 109 | this.registerView( 110 | VIEW_TYPE_FILE_LIST, 111 | (leaf: WorkspaceLeaf) => new TocView(leaf, this) 112 | ); 113 | 114 | this.registerView( 115 | VIEW_TYPE_BOOKSHELF, 116 | (leaf: WorkspaceLeaf) => new BookshelfView(leaf, this) 117 | ); 118 | 119 | this.registerView( 120 | VIEW_TYPE_BOOK_SETTING, 121 | (leaf: WorkspaceLeaf) => new BookSettingView(leaf, this) 122 | ); 123 | 124 | this.addSettingTab(new MyPluginSettingTab(this.app, this)); 125 | 126 | this.registerEvent(this.app.vault.on('create', this.handleFileChange.bind(this))); 127 | this.registerEvent(this.app.vault.on('delete', this.handleFileChange.bind(this))); 128 | 129 | this.folderPath = ''; 130 | } 131 | 132 | handleFileChange(file: TFile) { 133 | const tocView = this.app.workspace.getLeavesOfType(VIEW_TYPE_FILE_LIST)[0]?.view; 134 | if (tocView instanceof TocView) { 135 | tocView.refresh(); 136 | } 137 | 138 | const bookshelfView = this.app.workspace.getLeavesOfType(VIEW_TYPE_BOOKSHELF)[0]?.view; 139 | if (bookshelfView instanceof BookshelfView) { 140 | bookshelfView.refresh(); 141 | } 142 | 143 | const booksettingView = this.app.workspace.getLeavesOfType(VIEW_TYPE_BOOK_SETTING)[0]?.view; 144 | if (booksettingView instanceof BookSettingView) { 145 | booksettingView.refresh(); 146 | } 147 | } 148 | 149 | async activateView(viewType: string) { 150 | this.app.workspace.detachLeavesOfType(viewType); 151 | let centerLeaf = this.app.workspace.getMostRecentLeaf(); 152 | if (viewType === VIEW_TYPE_FILE_LIST) { 153 | centerLeaf = this.app.workspace.getLeftLeaf(false); 154 | } else if (viewType === VIEW_TYPE_BOOK_SETTING) { 155 | centerLeaf = this.app.workspace.getRightLeaf(false); 156 | } 157 | if (centerLeaf) { 158 | await centerLeaf.setViewState({ 159 | type: viewType, 160 | active: true, 161 | state: { icon: viewType === VIEW_TYPE_FILE_LIST ? 'folder-open' : 'book' } 162 | }); 163 | 164 | this.app.workspace.revealLeaf( 165 | this.app.workspace.getLeavesOfType(viewType)[0] 166 | ); 167 | } 168 | } 169 | 170 | async setFolderPath(path: string) { 171 | this.folderPath = path; 172 | await this.saveSettings(); 173 | this.refreshTocView(); 174 | this.refreshBookSettingView(); 175 | this.refreshBookshelfView(); 176 | } 177 | 178 | refreshTocView() { 179 | const tocView = this.app.workspace.getLeavesOfType(VIEW_TYPE_FILE_LIST)[0]?.view as TocView; 180 | if (tocView) { 181 | tocView.updateTitle(); 182 | tocView.refresh(); 183 | } 184 | } 185 | 186 | refreshBookSettingView() { 187 | const bookSettingView = this.app.workspace.getLeavesOfType(VIEW_TYPE_BOOK_SETTING)[0]?.view as BookSettingView; 188 | if (bookSettingView) { 189 | bookSettingView.refresh(); 190 | } 191 | } 192 | 193 | refreshBookshelfView() { 194 | const bookshelfView = this.app.workspace.getLeavesOfType(VIEW_TYPE_BOOKSHELF)[0]?.view as BookshelfView; 195 | if (bookshelfView) { 196 | bookshelfView.refresh(); 197 | } 198 | } 199 | 200 | onunload() { 201 | this.app.workspace.detachLeavesOfType(VIEW_TYPE_FILE_LIST); 202 | this.app.workspace.detachLeavesOfType(VIEW_TYPE_BOOKSHELF); 203 | } 204 | 205 | async loadSettings() { 206 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 207 | } 208 | 209 | async saveSettings() { 210 | await this.saveData(this.settings); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/model/InspirationModal.ts: -------------------------------------------------------------------------------- 1 | import { App, ButtonComponent, ItemView, Modal, Notice, TextComponent, TFolder } from "obsidian"; 2 | 3 | // 新建灵感的模态框 4 | export class NewInspirationModal extends Modal { 5 | folder: TFolder; 6 | view: ItemView; 7 | 8 | constructor(app: App, folder: TFolder, view: ItemView, refreshCallback: () => void) { 9 | super(app); 10 | this.folder = folder; 11 | this.view = view; 12 | } 13 | 14 | onOpen() { 15 | const { contentEl } = this; 16 | contentEl.createEl('h2', { cls: 'pluginModal', text: '添加灵感' }); 17 | 18 | const input = new TextComponent(contentEl); 19 | input.setPlaceholder('灵感标题'); 20 | 21 | const descInputEl = contentEl.createEl('textarea', { cls: 'inspiration-textarea' }); 22 | descInputEl.placeholder = '灵感详细内容'; 23 | descInputEl.rows = 10; 24 | descInputEl.style.width = '100%'; 25 | 26 | new ButtonComponent(contentEl) 27 | .setButtonText('创建') 28 | .setCta() 29 | .onClick(async () => { 30 | const fileName = input.getValue(); 31 | const fileContent = descInputEl.value; 32 | if (!fileName) { 33 | new Notice('灵感标题不能为空'); 34 | return; 35 | } 36 | 37 | const filePath = `${this.folder.path}/${fileName}.md`; 38 | await this.app.vault.create(filePath, fileContent); 39 | this.close(); 40 | }); 41 | } 42 | 43 | onClose() { 44 | const { contentEl } = this; 45 | contentEl.empty(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/model/deleteModal.ts: -------------------------------------------------------------------------------- 1 | import { App, ButtonComponent, ItemView, Modal, TFile, TFolder } from "obsidian"; 2 | 3 | 4 | // 删除确认的模态框 5 | export class ConfirmDeleteModal extends Modal { 6 | fileOrFolder: TFile | TFolder; 7 | view: ItemView; 8 | refreshCallback: () => void; 9 | 10 | constructor(app: App, fileOrFolder: TFile | TFolder, view: ItemView, refreshCallback: () => void) { 11 | super(app); 12 | this.fileOrFolder = fileOrFolder; 13 | this.view = view; 14 | } 15 | 16 | onOpen() { 17 | const { contentEl } = this; 18 | contentEl.createEl('h2', { cls: 'pluginModal', text: '确认删除' }); 19 | 20 | const fileType = this.fileOrFolder instanceof TFile ? '文件' : '文件夹'; 21 | contentEl.createEl('span', { text: `删除${fileType}——` }); 22 | const strongtext = contentEl.createEl('span', { text: `${this.fileOrFolder.name.replace(/\.md$/, '')}` }); 23 | strongtext.style.fontWeight = 'bold'; 24 | strongtext.style.color = 'var(--bold-color)'; 25 | new ButtonComponent(contentEl) 26 | .setButtonText('删除') 27 | .setCta() 28 | .onClick(async () => { 29 | await this.app.vault.trash(this.fileOrFolder, false); 30 | this.close(); 31 | }); 32 | 33 | new ButtonComponent(contentEl) 34 | .setButtonText('取消') 35 | .onClick(() => { 36 | this.close(); 37 | }); 38 | } 39 | 40 | onClose() { 41 | const { contentEl } = this; 42 | contentEl.empty(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/model/newBookModal.ts: -------------------------------------------------------------------------------- 1 | import { ItemView, TFolder, Notice, ButtonComponent, TextComponent, Modal, App } from 'obsidian'; 2 | 3 | // 新建图书 4 | export class NewBookModal extends Modal { 5 | folder: TFolder; 6 | view: ItemView; 7 | refreshCallback: () => void; 8 | 9 | constructor(app: App, folder: TFolder, view: ItemView, refreshCallback: () => void) { 10 | super(app); 11 | this.folder = folder; 12 | this.view = view; 13 | } 14 | 15 | onOpen() { 16 | const { contentEl } = this; 17 | contentEl.createEl('h2', { cls: 'pluginModal', text: '新建书籍' }); 18 | 19 | const infoForm = contentEl.createDiv({ cls: 'info-form' }); 20 | 21 | const namelabelEl = infoForm.createEl('div', { cls: 'name-label' }); 22 | namelabelEl.createEl('div', { text: '书籍名称', cls: 'input-label' }); 23 | const nameInput = new TextComponent(namelabelEl); 24 | nameInput.setPlaceholder('在此输入书籍名称'); 25 | 26 | // 创建书籍类型下拉选择框 27 | const bookTypeLabel = namelabelEl.createEl('div', { text: "小说类型:", cls: 'option-label' }); 28 | const selectEl = bookTypeLabel.createEl('select', { cls: 'book-type-select' }); 29 | selectEl.id = 'bookType'; // 给 select 元素一个 ID 30 | 31 | // 创建 “长篇小说” 选项并添加到下拉选择框 32 | const novelOption = selectEl.createEl('option', { 33 | attr: { value: 'novel', selected: 'selected' } // 初始默认选中长篇小说 34 | }); 35 | novelOption.textContent = '长篇小说'; 36 | 37 | // 创建 “短篇小说” 选项并添加到下拉选择框 38 | const shortStoryOption = selectEl.createEl('option', { attr: { value: 'short-story' } }); 39 | shortStoryOption.textContent = '短篇小说'; 40 | 41 | const desclabelEl = infoForm.createEl('div', { cls: 'desc-label' }); 42 | desclabelEl.createEl('div', { text: '书籍简介', cls: 'input-label' }); 43 | const descInputEl = desclabelEl.createEl('textarea', { cls: 'book-description-textarea' }); 44 | descInputEl.placeholder = '请输入书籍简介'; 45 | descInputEl.rows = 10; 46 | descInputEl.style.width = '100%'; 47 | 48 | // 创建确认按钮 49 | new ButtonComponent(contentEl) 50 | .setButtonText('创建') 51 | .setCta() 52 | .onClick(async () => { 53 | const bookName = nameInput.getValue(); 54 | const bookDesc = descInputEl.value; 55 | let bookType = 'novel'; 56 | const selectedType = document.getElementById('bookType') as HTMLSelectElement; 57 | bookType = selectedType.value; 58 | // 验证书籍名称是否为空 59 | if (bookName.trim() === '') { 60 | new Notice('书籍名称不能为空'); 61 | return; 62 | } 63 | 64 | const bookFolderPath = `${this.folder.path}/${bookName}`; 65 | const newFolder = await this.app.vault.createFolder(bookFolderPath); 66 | if (newFolder) { 67 | await this.app.vault.create(newFolder.path + '/信息.md', `---\ntype: ${bookType}\n---\n名称: ${bookName}\n简介: ${bookDesc}`); 68 | if (bookType === 'novel') { 69 | const novelFolder = await this.app.vault.createFolder(newFolder.path + '/小说文稿'); 70 | await this.app.vault.create(novelFolder.path + '/未命名章节.md', ''); // 创建空章节 71 | } else if (bookType === 'short-story') { 72 | await this.app.vault.create(newFolder.path + '/小说正文.md', ''); // 创建空章节 73 | } 74 | } 75 | 76 | new Notice('书籍已创建'); 77 | this.close(); 78 | 79 | }); 80 | 81 | new ButtonComponent(contentEl) 82 | .setButtonText('取消') 83 | .onClick(() => { 84 | this.close(); 85 | }); 86 | } 87 | 88 | onClose() { 89 | const { contentEl } = this; 90 | contentEl.empty(); 91 | } 92 | } -------------------------------------------------------------------------------- /src/model/newChapterModal.ts: -------------------------------------------------------------------------------- 1 | import { ItemView, TFolder, Notice, Modal, TextComponent, ButtonComponent, App} from 'obsidian'; 2 | 3 | // 新建章节的模态框 4 | export class NewChapterModal extends Modal { 5 | folder: TFolder; 6 | view: ItemView; 7 | itemType: string; // 新建项目的类型,例如 "章节"、"灵感" 等 8 | refreshCallback: () => void; 9 | 10 | constructor(app: App, folder: TFolder, view: ItemView, itemType: string, refreshCallback: () => void) { 11 | super(app); 12 | this.folder = folder; 13 | this.view = view; 14 | this.itemType = itemType; 15 | this.refreshCallback = refreshCallback; 16 | } 17 | 18 | onOpen() { 19 | const { contentEl } = this; 20 | contentEl.createEl('h2', { cls: 'pluginModal', text: `新建${this.itemType}` }); 21 | 22 | const input = new TextComponent(contentEl); 23 | input.setPlaceholder(`${this.itemType}名称`); 24 | 25 | new ButtonComponent(contentEl) 26 | .setButtonText('创建') 27 | .setCta() 28 | .onClick(async () => { 29 | const fileName = input.getValue(); 30 | if (!fileName) { 31 | new Notice(`${this.itemType}名称不能为空`); 32 | return; 33 | } 34 | 35 | const filePath = `${this.folder.path}/${fileName}.md`; 36 | await this.app.vault.create(filePath, ''); 37 | this.close(); 38 | }); 39 | } 40 | 41 | onClose() { 42 | const { contentEl } = this; 43 | contentEl.empty(); 44 | } 45 | } -------------------------------------------------------------------------------- /src/model/newItemModal.ts: -------------------------------------------------------------------------------- 1 | import { ItemView, TFolder, Notice, Modal, TextComponent, ButtonComponent, App} from 'obsidian'; 2 | 3 | // 新建条目模态框 4 | export class NewItemModal extends Modal { 5 | folder: TFolder; 6 | view: ItemView; 7 | 8 | constructor(app: App, folder: TFolder, view: ItemView, refreshCallback: () => void) { 9 | super(app); 10 | this.folder = folder; 11 | this.view = view; 12 | } 13 | 14 | onOpen() { 15 | const { contentEl } = this; 16 | contentEl.empty(); 17 | contentEl.createEl('p', { text: '新建卷/章节', cls: 'modal-title' }); 18 | 19 | const nameInput = new TextComponent(contentEl); 20 | nameInput.setPlaceholder('输入卷/章节名称...'); 21 | 22 | const createFolderButton = new ButtonComponent(contentEl); 23 | createFolderButton.setButtonText('新卷') 24 | .onClick(async () => { 25 | const name = nameInput.getValue(); 26 | if (name) { 27 | await this.app.vault.createFolder(`${this.folder.path}/${name}`); 28 | new Notice(`Folder '${name}' created.`); 29 | this.close(); 30 | } 31 | }); 32 | 33 | const createFileButton = new ButtonComponent(contentEl); 34 | createFileButton.setButtonText('新章节') 35 | .onClick(async () => { 36 | const name = nameInput.getValue(); 37 | if (name) { 38 | const filePath = `${this.folder.path}/${name}.md`; 39 | await this.app.vault.create(filePath, ''); 40 | new Notice(`File '${name}.md' created.`); 41 | this.close(); 42 | } 43 | }); 44 | } 45 | 46 | onClose() { 47 | const { contentEl } = this; 48 | contentEl.empty(); 49 | } 50 | } -------------------------------------------------------------------------------- /src/view/bookSettingView.ts: -------------------------------------------------------------------------------- 1 | import { ItemView, WorkspaceLeaf, TFolder, TFile, setIcon, Notice, MarkdownRenderer, parseYaml } from 'obsidian'; 2 | import MyPlugin from '../main'; 3 | import { ConfirmDeleteModal } from '../model/deleteModal'; 4 | import { NewInspirationModal } from 'src/model/InspirationModal'; 5 | import { NewChapterModal } from 'src/model/newChapterModal'; 6 | import { NewItemModal } from 'src/model/newItemModal'; 7 | 8 | export const VIEW_TYPE_BOOK_SETTING = 'book-setting'; 9 | 10 | export class BookSettingView extends ItemView { 11 | plugin: MyPlugin; 12 | tabsContainer: HTMLElement; 13 | contentContainer: HTMLElement; 14 | currentTab = '大纲'; 15 | setContentContainer: HTMLElement; 16 | currentDisplayedFile: TFile | null = null; 17 | 18 | constructor(leaf: WorkspaceLeaf, plugin: MyPlugin) { 19 | super(leaf); 20 | this.plugin = plugin; 21 | this.icon = 'book-text'; 22 | } 23 | 24 | getViewType(): string { 25 | return VIEW_TYPE_BOOK_SETTING; 26 | } 27 | 28 | getDisplayText(): string { 29 | return '书籍设定'; 30 | } 31 | 32 | async onOpen() { 33 | this.containerEl.empty(); 34 | this.contentContainer = this.containerEl.createDiv({ cls: 'setting-content' }); 35 | await this.refresh(); 36 | } 37 | 38 | async refresh() { 39 | const previousTab = this.currentTab; 40 | this.containerEl.empty(); 41 | 42 | if (!this.plugin.folderPath) { 43 | await this.showInspiration(); 44 | } else { 45 | const infoFilePath = `${this.plugin.folderPath}/信息.md`; 46 | const file = this.app.vault.getAbstractFileByPath(infoFilePath); 47 | 48 | if (file instanceof TFile) { 49 | try { 50 | const yamlHeader = await this.getFileYaml(infoFilePath); 51 | if (yamlHeader?.type === 'short-story') { 52 | await this.shortStoryOutline(); 53 | } else { 54 | this.createSetView(); 55 | this.currentTab = previousTab; 56 | await this.showViewContent(this.currentTab); 57 | if (this.currentDisplayedFile) { 58 | await this.renderFileContent(this.currentDisplayedFile); 59 | } 60 | } 61 | } catch (error) { 62 | new Notice('解析信息.md文件时出错'); 63 | } 64 | } else { 65 | new Notice('未找到信息.md文件或文件路径不正确'); 66 | } 67 | } 68 | } 69 | 70 | async getFileYaml(filePath: string) { 71 | const file = this.app.vault.getAbstractFileByPath(filePath); 72 | if (file instanceof TFile) { 73 | const fileContents = await this.app.vault.read(file); 74 | const yamlHeader = fileContents.split('---')[1]?.trim(); 75 | return yamlHeader ? parseYaml(yamlHeader) : null; 76 | } else { 77 | new Notice('文件未找到或文件路径不正确'); 78 | return null; 79 | } 80 | } 81 | 82 | async shortStoryOutline() { 83 | const outlineContent = this.containerEl.createEl('div', { cls: 'outline-container' }); 84 | const shortStoryOutlinePath = `${this.plugin.folderPath}/大纲.md`; 85 | const shortStoryOutline = this.app.vault.getAbstractFileByPath(shortStoryOutlinePath); 86 | 87 | const setContentContainerTitle = outlineContent.createEl('h2', { text: '大纲', cls: 'set-content-title' }); 88 | const modifyButton = setContentContainerTitle.createEl('div', { cls: 'modify-button' }); 89 | setIcon(modifyButton, 'pencil'); 90 | modifyButton.addEventListener('click', () => { 91 | this.app.workspace.openLinkText(shortStoryOutlinePath, '', false); 92 | }); 93 | 94 | if (shortStoryOutline instanceof TFile) { 95 | const fileContent = await this.app.vault.read(shortStoryOutline); 96 | const shortStoryToc = outlineContent.createDiv({ cls: 'outline-content' }); 97 | shortStoryToc.empty(); 98 | MarkdownRenderer.render(this.app, fileContent, shortStoryToc, shortStoryOutlinePath, this); 99 | } else { 100 | new Notice('大纲文件未找到'); 101 | } 102 | } 103 | 104 | async showInspiration() { 105 | const inspirationPath = '@附件/灵感'; 106 | const folder = this.app.vault.getAbstractFileByPath(inspirationPath); 107 | 108 | const inspirationTitle = this.containerEl.createEl('h2', { cls: 'view-title' }); 109 | inspirationTitle.createEl('span', { text: '灵感' }); 110 | 111 | const inspirationIcon = inspirationTitle.createEl('span'); 112 | setIcon(inspirationIcon, 'plus'); 113 | inspirationIcon.title = '添加灵感'; 114 | inspirationIcon.addEventListener('click', (e) => { 115 | e.stopPropagation(); 116 | this.showNewInspirationModal(); 117 | }); 118 | 119 | if (folder instanceof TFolder) { 120 | for (const child of folder.children) { 121 | if (child instanceof TFile) { 122 | await this.displayInspirationItem(child); 123 | } 124 | } 125 | } else { 126 | this.contentContainer.createEl('div', { cls:'hint-info', text: '未找到灵感文件夹。' }); 127 | } 128 | } 129 | 130 | async displayInspirationItem(child: TFile) { 131 | const fileItem = this.containerEl.createEl('div', { cls: 'inspiration-item' }); 132 | fileItem.createEl('div', { text: child.name.replace(/\.md$/, ''), cls: 'file-title' }); 133 | 134 | const deleteButton = fileItem.createEl('div', { cls: 'deleteButtonPlus' }); 135 | setIcon(deleteButton, 'trash'); 136 | deleteButton.title = "删除灵感"; 137 | 138 | const fileContent = await this.app.vault.read(child); 139 | const snippet = fileContent.split('\n').slice(0, 5).join('\n'); 140 | fileItem.createEl('p', { text: snippet }); 141 | 142 | fileItem.addEventListener('click', () => { 143 | this.app.workspace.openLinkText(child.path, '', false); 144 | }); 145 | 146 | deleteButton.addEventListener('click', (e) => { 147 | e.stopPropagation(); 148 | this.confirmDelete(child); 149 | }); 150 | } 151 | 152 | async showNewInspirationModal() { 153 | const inspirationPath = '@附件/灵感'; 154 | let folder = this.app.vault.getAbstractFileByPath(inspirationPath); 155 | 156 | if (!(folder instanceof TFolder)) { 157 | try { 158 | await this.app.vault.createFolder(inspirationPath); 159 | folder = this.app.vault.getAbstractFileByPath(inspirationPath); 160 | } catch (error) { 161 | new Notice('创建灵感文件夹时出错'); 162 | return; 163 | } 164 | } 165 | 166 | if (folder instanceof TFolder) { 167 | const modal = new NewInspirationModal(this.app, folder, this, this.refresh.bind(this)); 168 | modal.open(); 169 | } else { 170 | new Notice('文件夹未找到'); 171 | } 172 | } 173 | 174 | 175 | async showViewContent(tabName: string) { 176 | this.contentContainer.empty(); 177 | const settingFolderPath = `${this.plugin.folderPath}/设定/${tabName}`; 178 | const settingFolder = this.app.vault.getAbstractFileByPath(settingFolderPath); 179 | if (settingFolder instanceof TFolder) { 180 | this.displayItems(this.contentContainer, settingFolder); 181 | } else { 182 | this.contentContainer.createEl('div', { cls:'hint-info', text: `未找到${tabName}文件夹` }); 183 | } 184 | } 185 | 186 | createSetView() { 187 | const setMainContainer = this.containerEl.createDiv({ cls: 'set-main-container' }); 188 | 189 | this.setContentContainer = setMainContainer.createDiv({ cls: 'item-container' }); 190 | 191 | const listContainer = setMainContainer.createDiv({ cls: 'list-container' }); 192 | const listHeaderContainer = listContainer.createDiv({ cls: 'list-container-header' }); 193 | 194 | const categoryNameDiv = listHeaderContainer.createEl('div', { cls: 'category-input' }); 195 | const inputCategoryName = categoryNameDiv.createEl('input', { placeholder: '分类名称' }); 196 | 197 | const listHeaderIcon = listHeaderContainer.createEl('div', { cls: 'list-header-icon' }); 198 | setIcon(listHeaderIcon, 'plus'); 199 | 200 | const listContentContainer = listContainer.createDiv({ cls: 'list-container-main' }); 201 | const tabsContainer = setMainContainer.createDiv({ cls: 'tabs-container' }); 202 | const tabsTop = tabsContainer.createDiv({ cls: 'tabs-top' }); 203 | 204 | const tabs = ['大纲', '角色', '设定', '灵感']; 205 | const tabsIcon = ['list', 'user-cog', 'file-cog', 'lightbulb']; 206 | const tabsArray: HTMLDivElement[] = []; 207 | 208 | const updateAndDisplayList = async (tabName: string) => { 209 | this.currentTab = tabName; 210 | const currentSettingFolderPath = `${this.plugin.folderPath}/设定/${tabName}`; 211 | const currentSettingFolder = this.app.vault.getAbstractFileByPath(currentSettingFolderPath); 212 | 213 | listContentContainer.empty(); 214 | 215 | if (currentSettingFolder instanceof TFolder) { 216 | this.displayItems(listContentContainer, currentSettingFolder); 217 | } else { 218 | listContentContainer.createEl('div', { cls:'hint-info', text: `${tabName}下无文件` }); 219 | } 220 | }; 221 | 222 | listHeaderIcon.addEventListener('click', async () => { 223 | const newCategoryName = inputCategoryName.value.trim(); 224 | if (newCategoryName) { 225 | const baseSettingFolderPath = `${this.plugin.folderPath}/设定`; 226 | const currentSettingFolderPath = `${baseSettingFolderPath}/${this.currentTab}`; 227 | const newCategoryPath = `${currentSettingFolderPath}/${newCategoryName}`; 228 | 229 | await createFolderIfNotExists.call(this, baseSettingFolderPath); 230 | await createFolderIfNotExists.call(this, currentSettingFolderPath); 231 | 232 | await this.app.vault.createFolder(newCategoryPath); 233 | updateAndDisplayList(this.currentTab); 234 | } else { 235 | new Notice('请输入分类名称'); 236 | } 237 | }); 238 | 239 | async function createFolderIfNotExists(path: string) { 240 | const folder = this.app.vault.getAbstractFileByPath(path); 241 | if (!(folder instanceof TFolder)) { 242 | const parentPath = path.substring(0, path.lastIndexOf('/')); 243 | await createFolderIfNotExists.call(this, parentPath); 244 | await this.app.vault.createFolder(path); 245 | } 246 | } 247 | 248 | updateAndDisplayList(this.currentTab); 249 | 250 | tabs.forEach(tabName => { 251 | const tab = tabsTop.createEl('div', { cls: 'tab-button' }); 252 | tabsArray.push(tab); 253 | tab.addEventListener('click', async () => { 254 | await updateAndDisplayList(tabName); 255 | 256 | tabsArray.forEach(t => t.removeClass('selected')); 257 | tab.addClass('selected'); 258 | 259 | await this.showViewContent(tabName); 260 | }); 261 | 262 | if (tabName === this.currentTab) { 263 | tab.addClass('selected'); 264 | } 265 | 266 | const tabIcon = tab.createDiv(); 267 | tab.createDiv({ cls: 'tab-name', text: tabName }); 268 | setIcon(tabIcon, tabsIcon[tabs.indexOf(tabName)]); 269 | tab.title = tabName; 270 | }); 271 | } 272 | 273 | displayItems(container: HTMLElement, folder: TFolder) { 274 | folder.children.forEach((child) => { 275 | const childContainer = container.createDiv({ cls: 'folder-item' }); 276 | if (child instanceof TFile) { 277 | this.displayFile(childContainer, child); 278 | } else if (child instanceof TFolder) { 279 | this.displayFolder(childContainer, child); 280 | } 281 | }); 282 | } 283 | 284 | displayFolder(container: HTMLElement, folder: TFolder) { 285 | const folderItem = container.createDiv({ cls: 'folder-item' }); 286 | const folderHeader = folderItem.createEl('div', { cls: 'folder-header' }); 287 | const folderName = folderHeader.createEl('div', { cls: 'folder-name' }); 288 | const folderIcon = folderName.createEl('span'); 289 | setIcon(folderIcon, 'folder-open'); 290 | folderName.createSpan({ text: folder.name }); 291 | 292 | const tocButton = folderHeader.createEl('div', { cls: 'tocButton' }); 293 | 294 | const addButton = tocButton.createEl('button'); 295 | setIcon(addButton, 'plus'); 296 | addButton.title = "添加"; 297 | addButton.addEventListener('click', (e) => { 298 | e.stopPropagation(); 299 | this.showNewChapterModal(folder); 300 | }); 301 | 302 | const deleteButton = tocButton.createEl('button'); 303 | setIcon(deleteButton, 'trash'); 304 | deleteButton.title = "删除分类"; 305 | deleteButton.addEventListener('click', (e) => { 306 | e.stopPropagation(); 307 | this.confirmDelete(folder); 308 | }); 309 | 310 | const fileList = folderItem.createEl('ul', { cls: 'file-list' }); 311 | fileList.style.display = 'block'; 312 | folderHeader.addEventListener('click', () => { 313 | fileList.style.display = fileList.style.display === 'none' ? 'block' : 'none'; 314 | setIcon(folderIcon, fileList.style.display === 'none' ? 'folder-minus' : 'folder-open'); 315 | }); 316 | 317 | const sortedChildren = folder.children.sort((a, b) => { 318 | if (a instanceof TFile && b instanceof TFile) { 319 | return a.stat.ctime - b.stat.ctime; 320 | } else if (a instanceof TFolder && b instanceof TFolder) { 321 | return a.name.localeCompare(b.name); 322 | } else if (a instanceof TFile) { 323 | return 1; 324 | } else { 325 | return -1; 326 | } 327 | }); 328 | 329 | sortedChildren.forEach(file => { 330 | if (file instanceof TFile) { 331 | this.displayFile(fileList, file); 332 | } else if (file instanceof TFolder) { 333 | this.displayFolder(fileList.createEl('li'), file); 334 | } 335 | }); 336 | } 337 | 338 | async displayFile(container: HTMLElement, file: TFile) { 339 | const fileItem = container.createEl('li', { cls: "chapter-title" }); 340 | const fileHeader = fileItem.createEl('div', { cls: 'file-header' }); 341 | const fileIcon = fileHeader.createEl('span'); 342 | setIcon(fileIcon, 'file-text'); 343 | const fileName = fileHeader.createEl('span'); 344 | fileName.textContent = file.name.replace(/\.md$/, ''); 345 | 346 | const deleteButton = fileItem.createEl('button', { cls: 'deleteButton' }); 347 | setIcon(deleteButton, 'trash'); 348 | deleteButton.title = "删除文件"; 349 | deleteButton.addEventListener('click', (e) => { 350 | e.stopPropagation(); 351 | this.confirmDelete(file); 352 | }); 353 | 354 | fileName.addEventListener('click', async () => { 355 | this.currentDisplayedFile = file; 356 | await this.renderFileContent(file); 357 | }); 358 | 359 | fileItem.dataset.path = file.path; 360 | } 361 | 362 | async renderFileContent(file: TFile) { 363 | const fileContent = await this.app.vault.read(file); 364 | const setContentContainer = this.setContentContainer; 365 | setContentContainer.empty(); 366 | const setContentContainerTitle = setContentContainer.createEl('h2', { cls: 'set-content-title', text: `${this.currentTab}——${file.name.replace(/\.md$/, '')}` }); 367 | 368 | const setContentContainerMain = setContentContainer.createEl('div', { cls: 'set-content-container-main' }); 369 | MarkdownRenderer.render(this.app, fileContent, setContentContainerMain, file.path, this); 370 | 371 | const modifyButton = setContentContainerTitle.createEl('div', { cls: 'modify-button' }); 372 | setIcon(modifyButton, 'pencil'); 373 | modifyButton.addEventListener('click', () => { 374 | this.app.workspace.openLinkText(file.path, '', false); 375 | }); 376 | } 377 | 378 | async showNewChapterModal(folder: TFolder) { 379 | const modal = new NewChapterModal(this.app, folder, this, '', this.refresh.bind(this)); 380 | modal.open(); 381 | } 382 | 383 | async showNewItemModal(folderPath: string) { 384 | const folder = this.app.vault.getAbstractFileByPath(folderPath); 385 | if (folder instanceof TFolder) { 386 | const modal = new NewItemModal(this.app, folder, this, this.refresh.bind(this)); 387 | modal.open(); 388 | } else { 389 | new Notice(`文件夹未发现: ${folderPath}`); 390 | } 391 | } 392 | 393 | confirmDelete(fileOrFolder: TFile | TFolder) { 394 | const modal = new ConfirmDeleteModal(this.app, fileOrFolder, this, this.refresh.bind(this)); 395 | modal.open(); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/view/bookShelfView.ts: -------------------------------------------------------------------------------- 1 | import { ItemView, WorkspaceLeaf, TFolder, TFile, Notice, parseYaml, setIcon } from 'obsidian'; 2 | import MyPlugin from '../main'; 3 | import { WordCounter } from '../helper/WordCount'; 4 | import { ConfirmDeleteModal } from '../model/deleteModal'; 5 | import { NewBookModal } from '../model/newBookModal'; 6 | 7 | export const VIEW_TYPE_BOOKSHELF = 'bookshelf-view'; 8 | 9 | // 书架视图 10 | export class BookshelfView extends ItemView { 11 | plugin: MyPlugin; 12 | wordCounter: WordCounter; 13 | 14 | constructor(leaf: WorkspaceLeaf, plugin: MyPlugin) { 15 | super(leaf); 16 | this.plugin = plugin; 17 | this.wordCounter = new WordCounter(plugin); 18 | this.icon = 'book'; 19 | } 20 | 21 | getViewType(): string { 22 | return VIEW_TYPE_BOOKSHELF; 23 | } 24 | 25 | getDisplayText(): string { 26 | return 'Bookshelf'; 27 | } 28 | 29 | async onOpen() { 30 | const container = this.containerEl.children[1]; 31 | container.empty(); 32 | 33 | // 创建书籍列表容器 34 | const bookList = this.containerEl.createDiv({ cls: 'book-list-container' }); 35 | bookList.style.gridTemplateColumns = `repeat(${this.plugin.settings.booksPerRow}, 1fr)` 36 | this.toggleFloatingButton(); 37 | await this.refresh(); 38 | } 39 | toggleFloatingButton() { 40 | // 添加浮动按钮 41 | const floatingButton = this.containerEl.createEl('button', { cls: 'floating-button' }); 42 | setIcon(floatingButton, 'plus'); 43 | floatingButton.title = '新建书籍'; 44 | floatingButton.addEventListener('click', () => { 45 | this.showNewBookModal(); 46 | }); 47 | } 48 | async refresh() { 49 | this.containerEl.empty(); // 仅清空书籍列表容器 50 | const bookListContainer = this.containerEl.createDiv({ cls: 'book-list-container' }); 51 | 52 | bookListContainer.style.gridTemplateColumns = `repeat(${this.plugin.settings.booksPerRow}, 1fr)`; 53 | 54 | const rootFolder = "/"; 55 | const folder = this.app.vault.getAbstractFileByPath(rootFolder); 56 | if (folder instanceof TFolder) { 57 | await this.displayBooks(bookListContainer, folder); 58 | } else { 59 | new Notice('根文件夹未找到'); 60 | } 61 | this.toggleFloatingButton(); 62 | } 63 | 64 | async displayBooks(container: HTMLElement, rootFolder: TFolder) { 65 | const bookFolders = rootFolder.children.filter(child => child instanceof TFolder) as TFolder[]; 66 | 67 | for (const folder of bookFolders) { 68 | const infoFile = folder.children.find(file => file instanceof TFile && file.name === '信息.md') as TFile; 69 | if (infoFile) { 70 | try { 71 | const fileContents = await this.app.vault.read(infoFile); 72 | const yamlHeader = fileContents.split('---')[1]?.trim(); 73 | const fileYaml = parseYaml(yamlHeader); 74 | if (fileYaml) { 75 | const novelFolder = folder.children.find(file => file instanceof TFolder && file.name === '小说文稿') as TFolder; 76 | const storyFile = folder.children.find(file => file instanceof TFile && file.name === '小说正文.md') as TFile; 77 | if (novelFolder) { 78 | const totalWordCount = await this.wordCounter.getTotalWordCount(novelFolder); 79 | this.displayBook(container, folder, fileYaml.type, totalWordCount); 80 | } else if (storyFile) { 81 | const totalWordCount = await this.wordCounter.getWordCount(storyFile); 82 | this.displayBook(container, folder, fileYaml.type, totalWordCount); 83 | } else { 84 | new Notice(`小说文件夹未发现 ${folder.name}`); 85 | } 86 | } 87 | } catch (error) { 88 | new Notice('解析信息.md文件时出错'); 89 | } 90 | } 91 | } 92 | } 93 | 94 | displayBook(container: HTMLElement, folder: TFolder, type: string, totalWordCount: number) { 95 | const bookItem = container.createDiv({ cls: 'book-item' }); 96 | bookItem.createEl('div', { cls: 'book-title', text: folder.name }); 97 | bookItem.createEl('div', { cls: 'book-count', text: `${totalWordCount} 字` }); 98 | bookItem.createEl('div', { cls: 'book-type', text: type === 'novel' ? '长篇' : '短篇' }); 99 | 100 | const deleteButton = bookItem.createEl('div', { text: '删除', cls: 'deleteButtonPlus' }); 101 | setIcon(deleteButton, 'trash'); 102 | deleteButton.title = "删除灵感"; 103 | deleteButton.addEventListener('click', (e) => { 104 | e.stopPropagation(); 105 | this.confirmDelete(folder); 106 | }); 107 | 108 | bookItem.addEventListener('click', () => { 109 | this.plugin.setFolderPath(folder.path); 110 | const novelFolderPath = `${this.plugin.folderPath}/小说文稿`; 111 | const novelFolder = this.app.vault.getAbstractFileByPath(novelFolderPath); 112 | if (novelFolder instanceof TFolder) { 113 | const latestFile = this.getLatestFile(novelFolder); 114 | if (latestFile) { 115 | this.app.workspace.openLinkText(latestFile.path, '', false); 116 | } else { 117 | new Notice('未发现最新文件'); 118 | } 119 | } else { 120 | const defaultPath = type === 'novel' ? `${this.plugin.folderPath}/小说文稿/未命名章节.md` : `${this.plugin.folderPath}/小说正文.md`; 121 | this.app.workspace.openLinkText(defaultPath, '', false); 122 | } 123 | }); 124 | } 125 | 126 | getLatestFile(folder: TFolder): TFile | null { 127 | let latestFile: TFile | null = null; 128 | let latestTime = 0; 129 | 130 | const checkFile = (file: TFile) => { 131 | if (file.stat.ctime > latestTime) { 132 | latestTime = file.stat.ctime; 133 | latestFile = file; 134 | } 135 | }; 136 | 137 | const traverseFolder = (folder: TFolder) => { 138 | folder.children.forEach((child) => { 139 | if (child instanceof TFile) { 140 | checkFile(child); 141 | } else if (child instanceof TFolder) { 142 | traverseFolder(child); 143 | } 144 | }); 145 | }; 146 | 147 | traverseFolder(folder); 148 | return latestFile; 149 | } 150 | 151 | confirmDelete(fileOrFolder: TFile | TFolder) { 152 | const modal = new ConfirmDeleteModal(this.app, fileOrFolder, this, this.refresh.bind(this)); 153 | modal.open(); 154 | } 155 | 156 | async showNewBookModal() { 157 | const rootFolderPath = '/'; 158 | const folder = this.app.vault.getAbstractFileByPath(rootFolderPath); 159 | if (folder instanceof TFolder) { 160 | const modal = new NewBookModal(this.app, folder, this, this.refresh.bind(this)); 161 | modal.open(); 162 | } else { 163 | new Notice('根文件夹未找到'); 164 | } 165 | } 166 | async onClose() { 167 | this.plugin.folderPath = ''; 168 | } 169 | } -------------------------------------------------------------------------------- /src/view/tocView.ts: -------------------------------------------------------------------------------- 1 | import { ItemView, WorkspaceLeaf, TFolder, TFile, Notice, setIcon, MarkdownView} from 'obsidian'; 2 | import MyPlugin from '../main'; 3 | import { WordCounter } from '../helper/WordCount'; 4 | import { ConfirmDeleteModal } from '../model/deleteModal'; 5 | import { NewChapterModal } from 'src/model/newChapterModal'; 6 | import { NewItemModal } from 'src/model/newItemModal'; 7 | 8 | export const VIEW_TYPE_FILE_LIST = 'file-list-view'; 9 | export const NEW_ITEM_MODAL = 'new-item-modal'; 10 | 11 | // 目录视图 12 | export class TocView extends ItemView { 13 | plugin: MyPlugin; 14 | wordCounter: WordCounter; 15 | 16 | constructor(leaf: WorkspaceLeaf, plugin: MyPlugin) { 17 | super(leaf); 18 | this.plugin = plugin; 19 | this.wordCounter = new WordCounter(plugin); 20 | this.icon = 'list'; 21 | } 22 | 23 | getViewType(): string { 24 | return VIEW_TYPE_FILE_LIST; 25 | } 26 | 27 | getDisplayText(): string { 28 | return 'File List'; 29 | } 30 | 31 | async onOpen() { 32 | const container = this.containerEl.children[1]; 33 | container.empty(); 34 | 35 | const title = container.createEl('h2', { text: '' }); 36 | title.className = 'view-title'; 37 | // 创建浮动按钮并默认隐藏 38 | const floatingButton = container.createEl('button', { 39 | cls: 'floating-button' 40 | }); 41 | setIcon(floatingButton, 'plus'); 42 | floatingButton.style.display = 'none'; 43 | floatingButton.title = '新建章节/卷'; 44 | floatingButton.addEventListener('click', () => { 45 | this.showNewItemModal(); 46 | }); 47 | 48 | this.refresh(); 49 | } 50 | 51 | // 浮动按钮的显示 52 | toggleFloatingButton() { 53 | const floatingButton = this.containerEl.querySelector('.floating-button') as HTMLButtonElement; 54 | if (floatingButton) { 55 | floatingButton.style.display = this.plugin.folderPath !== "" ? 'block' : 'none'; 56 | } 57 | } 58 | 59 | // 更新标题 60 | updateTitle() { 61 | const title = this.containerEl.querySelector('.view-title'); 62 | if (title) { 63 | title.textContent = this.plugin.folderPath; 64 | } 65 | } 66 | 67 | async refresh() { 68 | const container = this.containerEl.children[1] as HTMLElement; 69 | const existingContent = container.querySelectorAll('.folder-item, ul, p, .info-container'); 70 | existingContent.forEach(el => el.remove()); 71 | 72 | // 获取小说文稿文件夹路径,用于长篇小说 73 | const novelFolderPath = this.plugin.folderPath + '/小说文稿'; 74 | const novelFolder = this.app.vault.getAbstractFileByPath(novelFolderPath); 75 | 76 | // 直接获取短篇小说文件路径 77 | const shortStoryFilePath = this.plugin.folderPath + '/小说正文.md'; 78 | const shortStoryFile = this.app.vault.getAbstractFileByPath(shortStoryFilePath); 79 | 80 | if (novelFolder && novelFolder instanceof TFolder) { 81 | // 处理长篇小说的目录结构 82 | this.displayItems(container, novelFolder); 83 | this.toggleFloatingButton(); 84 | } else if (shortStoryFile && shortStoryFile instanceof TFile) { 85 | // 如果是短篇小说,展示大纲 86 | this.displayOutline(container, shortStoryFile); 87 | } else { 88 | this.displayInfo(container); 89 | } 90 | } 91 | displayInfo(container: HTMLElement) { 92 | const { settings } = this.plugin; 93 | const infoContainer = container.createDiv({ cls: 'info-container' }); 94 | 95 | // 如果用户设置了图片路径,展示图片 96 | if (settings.picturePath) { 97 | const imagePath = `${settings.picturePath}`; 98 | infoContainer.createEl('img', { 99 | attr: { alt: imagePath, src: imagePath }, 100 | cls: 'user-avatar' 101 | }); 102 | } 103 | 104 | infoContainer.createEl('h1', { text: settings.name, cls: 'user-name' }); 105 | 106 | this.getBookCountAndTotalWords().then(({ novelCount, shortStoryCount }) => { 107 | // 格式化显示文本 108 | const novelText = novelCount > 0 ? `${novelCount}` : "0"; 109 | const shortStoryText = shortStoryCount > 0 ? `${shortStoryCount}` : "0"; 110 | 111 | // 创建显示文本的div元素 112 | const infoText = [ 113 | `长篇小说: ${novelText}`, 114 | `短篇小说: ${shortStoryText}`, 115 | ].join(' | '); 116 | 117 | infoContainer.createDiv({ text: infoText, cls: 'book-info' }); 118 | }); 119 | } 120 | 121 | 122 | // 异步方法,用于获取书籍数量和总字数 123 | async getBookCountAndTotalWords(): Promise<{ novelCount: number; shortStoryCount: number }> { 124 | const rootFolder = this.app.vault.getRoot(); // 获取根目录 125 | let novelCount = 0; 126 | let shortStoryCount = 0; 127 | 128 | const processFolder = async (folder: TFolder) => { 129 | for (const file of folder.children) { 130 | if (file instanceof TFile && file.path.endsWith('信息.md')) { 131 | const content = await this.app.vault.read(file); 132 | const type = this.getFileType(content); // 您需要实现这个方法来从内容中提取type 133 | if (type === 'novel') novelCount++; 134 | if (type === 'short-story') shortStoryCount++; 135 | } else if (file instanceof TFolder) { 136 | await processFolder(file); // 递归处理子文件夹 137 | } 138 | } 139 | }; 140 | 141 | await processFolder(rootFolder); 142 | return { novelCount, shortStoryCount }; 143 | } 144 | 145 | // 辅助方法,从信息.md的内容中提取type 146 | getFileType(content: string): 'novel' | 'short-story' | null { 147 | const match = content.match(/type: (novel|short-story)/); 148 | return match ? match[1] as 'novel' | 'short-story' : null; 149 | } 150 | 151 | displayItems(container: HTMLElement, folder: TFolder) { 152 | folder.children.forEach((child) => { 153 | const childContainer = container.createDiv({ cls: 'folder-item' }); 154 | if (child instanceof TFile) { 155 | this.displayFile(childContainer, child); 156 | } else if (child instanceof TFolder) { 157 | this.displayFolder(childContainer, child); 158 | } 159 | }); 160 | } 161 | 162 | displayFolder(container: HTMLElement, folder: TFolder) { 163 | const folderItem = container.createDiv({ cls: 'folder-item' }); 164 | const folderHeader = folderItem.createEl('div', { cls: 'folder-header' }); 165 | const folderName = folderHeader.createEl('div',{cls: 'folder-name'}); 166 | const folderIcon = folderName.createEl('span'); 167 | setIcon(folderIcon, 'folder-open'); 168 | folderName.createSpan({text: folder.name}) 169 | 170 | const tocButton = folderHeader.createEl('div', { cls: 'tocButton' }); 171 | 172 | const addButton = tocButton.createEl('button'); 173 | setIcon(addButton, 'plus') 174 | 175 | const deleteButton = tocButton.createEl('button'); 176 | setIcon(deleteButton, 'trash') 177 | deleteButton.title = "删除文件夹"; 178 | 179 | 180 | addButton.title = "新建章节"; 181 | addButton.addEventListener('click', (e) => { 182 | e.stopPropagation(); 183 | this.showNewChapterModal(folder); 184 | }); 185 | 186 | deleteButton.addEventListener('click', (e) => { 187 | e.stopPropagation(); 188 | this.confirmDelete(folder); 189 | }); 190 | 191 | const fileList = folderItem.createEl('ul', { cls: 'file-list' }); 192 | fileList.style.display = 'block'; 193 | folderHeader.addEventListener('click', () => { 194 | if (fileList.style.display === 'none') { 195 | fileList.style.display = 'block' 196 | setIcon(folderIcon, 'folder-open'); 197 | } else { 198 | fileList.style.display = 'none' 199 | setIcon(folderIcon, 'folder-minus'); 200 | } 201 | }); 202 | 203 | const sortedChildren = folder.children.sort((a, b) => { 204 | if (a instanceof TFile && b instanceof TFile) { 205 | return a.stat.ctime - b.stat.ctime; 206 | } else if (a instanceof TFolder && b instanceof TFolder) { 207 | return a.name.localeCompare(b.name); 208 | } else { 209 | return a instanceof TFolder ? -1 : 1; 210 | } 211 | }); 212 | 213 | sortedChildren.forEach(file => { 214 | if (file instanceof TFile) { 215 | this.displayFile(fileList, file); 216 | } else if (file instanceof TFolder) { 217 | this.displayFolder(fileList.createEl('li'), file); 218 | } 219 | }); 220 | } 221 | 222 | async displayFile(container: HTMLElement, file: TFile) { 223 | const fileItem = container.createEl('li', {cls: "chapter-title" }); 224 | const fileHeader = fileItem.createEl('div', { cls: 'file-header' }); 225 | const fileIcon = fileHeader.createEl('span'); 226 | setIcon(fileIcon, 'file-text'); 227 | const fileName = fileHeader.createEl('span'); 228 | const wordCount = await this.wordCounter.getWordCount(file); 229 | fileName.textContent = `${file.name.replace(/\.md$/, '')}`; 230 | 231 | const deleteButton = fileItem.createEl('button', { cls: 'deleteButton' }); 232 | fileItem.createEl('div', { cls: 'word-count', text: `${wordCount}` }); 233 | setIcon(deleteButton, 'trash') 234 | deleteButton.title = "删除文件"; 235 | deleteButton.addEventListener('click', (e) => { 236 | e.stopPropagation(); 237 | this.confirmDelete(file); 238 | }); 239 | 240 | fileItem.addEventListener('click', () => { 241 | this.app.workspace.openLinkText(file.path, '', false); 242 | }); 243 | 244 | fileItem.dataset.path = file.path; 245 | } 246 | // 展示大纲的方法 247 | async displayOutline(container: HTMLElement, file: TFile) { 248 | const outline = await this.getOutline(file); // 获取文件大纲的方法 249 | this.renderOutlineList(container, outline, 1, file); // 根级别从1开始(即#) 250 | } 251 | 252 | // 递归渲染大纲列表的方法 253 | async renderOutlineList(container: HTMLElement, titles: string[], level: number, file: TFile) { 254 | const listType = level === 1 ? 'ul' : 'ol'; // 根级别使用无序列表,其他使用有序列表 255 | const list = container.createEl(listType, { cls: 'outline-list' }); 256 | 257 | while (titles.length > 0) { 258 | const title = titles.shift(); 259 | if (title) { 260 | const [text, nestedTitles] = this.splitTitleAndChildren(title, titles); 261 | 262 | // 创建包含标题文本的 div 263 | const titleDiv = list.createEl('div', { text }); 264 | titleDiv.className = 'outline-title'; // 添加类名 265 | 266 | // 创建列表项,并将标题 div 添加为其子元素 267 | const listItem = list.createEl('li'); 268 | listItem.appendChild(titleDiv); 269 | 270 | // 根据标题级别添加缩进效果 271 | listItem.style.paddingLeft = `${10}px`; // 为每个级别添加10px的缩进 272 | 273 | // 添加点击事件以定位到文件中的标题位置 274 | listItem.addEventListener('click', () => this.scrollToTitle(file, title)); 275 | 276 | // 如果有子标题,递归渲染子标题列表 277 | if (nestedTitles.length > 0) { 278 | this.renderOutlineList(listItem, nestedTitles, level + 1, file); 279 | } 280 | } 281 | } 282 | 283 | // 将当前列表项添加到容器中 284 | container.appendChild(list); 285 | } 286 | 287 | splitTitleAndChildren(title: string, remainingTitles: string[]): [string, string[]] { 288 | const match = title.match(/^(#+)\s*(.*)/); 289 | if (match) { 290 | const headerLevel = match[1].length; 291 | const text = match[2]; // 提取标题文本 292 | 293 | // 找出所有子标题 294 | const nestedTitles: string[] = []; 295 | for (let i = 0; i < remainingTitles.length; i++) { 296 | const nextTitle = remainingTitles[i]; 297 | const nextMatch = nextTitle.match(/^(#+)\s*(.*)/); 298 | if (nextMatch) { 299 | const nextHeaderLevel = nextMatch[1].length; 300 | if (nextHeaderLevel > headerLevel) { 301 | nestedTitles.push(nextTitle); 302 | } else if (nextHeaderLevel <= headerLevel) { 303 | break; 304 | } 305 | } 306 | } 307 | 308 | // 从 remainingTitles 中移除已处理的子标题 309 | remainingTitles.splice(0, nestedTitles.length); 310 | 311 | return [text, nestedTitles]; 312 | } else { 313 | // 如果没有匹配,返回原始标题文本和空的子标题数组 314 | return [title, []]; 315 | } 316 | } 317 | 318 | // 定位到文件中的标题位置的方法 319 | async scrollToTitle(file: TFile, title: string) { 320 | const fileContent = await this.app.vault.read(file); 321 | const lines = fileContent.split('\n'); 322 | const titleIndex = lines.findIndex(line => line.trim() === title); 323 | 324 | if (titleIndex !== -1) { 325 | const activeLeaf = this.app.workspace.getLeaf(); 326 | if (activeLeaf) { 327 | const view = activeLeaf.view; 328 | if (view instanceof MarkdownView) { 329 | const editor = view.editor; 330 | editor.setCursor({ line: titleIndex, ch: 0 }); 331 | 332 | // 使用 scrollIntoView 选项参数 333 | editor.scrollIntoView({ from: { line: titleIndex, ch: 0 }, to: { line: titleIndex, ch: 0 } }, true); 334 | } 335 | } 336 | } 337 | } 338 | 339 | // 假设的获取文件大纲的方法,需要您根据实际情况实现 340 | async getOutline(file: TFile): Promise { 341 | const fileContents = await this.app.vault.read(file); 342 | const lines = fileContents.split('\n'); 343 | const titles = lines.filter(line => line.startsWith('#')).map(line => line.trim()); 344 | return titles; 345 | } 346 | 347 | async updateFile(file: TFile) { 348 | const fileItem = this.containerEl.querySelector(`li[data-path="${file.path}"]`); 349 | if (fileItem) { 350 | const fileName = file.name.replace(/\.md$/, ''); 351 | const wordCount = await this.wordCounter.getWordCount(file); 352 | fileItem.textContent = `${fileName} (${wordCount} words)`; 353 | } 354 | } 355 | 356 | async showNewChapterModal(folder: TFolder) { 357 | const modal = new NewChapterModal(this.app, folder, this, '章节' ,this.refresh); 358 | modal.open(); 359 | } 360 | 361 | async showNewItemModal() { 362 | const rootFolderPath = this.plugin.folderPath + '/小说文稿'; 363 | const folder = this.app.vault.getAbstractFileByPath(rootFolderPath); 364 | if (folder && folder instanceof TFolder) { 365 | const modal = new NewItemModal(this.app, folder, this, this.refresh); 366 | modal.open(); 367 | } else { 368 | new Notice(`文件夹未发现: ${rootFolderPath}`); 369 | } 370 | } 371 | 372 | confirmDelete(fileOrFolder: TFile | TFolder) { 373 | const modal = new ConfirmDeleteModal(this.app, fileOrFolder, this, this.refresh); 374 | modal.open(); 375 | } 376 | 377 | async onClose() { 378 | // Clean up when view is closed, if necessary 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This CSS file will be included with your plugin, and 4 | available in the app when your plugin is enabled. 5 | 6 | If your plugin does not need CSS, delete this file. 7 | 8 | */ 9 | 10 | .folder-header { 11 | font-weight: bold; 12 | cursor: pointer; 13 | justify-content: space-between; 14 | align-items: center; 15 | } 16 | 17 | .folder-header:hover { 18 | border-color: var(--interactive-accent); 19 | } 20 | 21 | .file-list { 22 | list-style-type: none; 23 | padding-left: 20px; 24 | margin: 5px 0; 25 | border: 1px solid transparent; 26 | border-radius: 5px; 27 | } 28 | 29 | .view-content>.folder-item>.file-list { 30 | margin-left: 0; 31 | padding: 0; 32 | } 33 | .chapter-title { 34 | margin-left: 10px; 35 | } 36 | .chapter-title, 37 | .folder-header { 38 | display: flex; 39 | align-items: center; 40 | justify-content: space-between; 41 | padding: 5px 10px; 42 | border: 1px solid transparent; 43 | border-radius: 5px; 44 | box-sizing: border-box; 45 | } 46 | .chapter-title:hover, 47 | .folder-header:hover { 48 | border-color: var(--interactive-accent); 49 | } 50 | 51 | .chapter-title>.deleteButton { 52 | display: none; 53 | } 54 | .chapter-title:hover>.deleteButton { 55 | display: flex; 56 | } 57 | .chapter-title>.word-count { 58 | display: block; 59 | font-size: 0.8em; 60 | opacity: 0.5; 61 | } 62 | .chapter-title:hover>.word-count { 63 | display: none; 64 | } 65 | .file-header, 66 | .folder-name { 67 | display: flex; 68 | align-items: center; 69 | gap: 5px; 70 | } 71 | .folder-name svg, 72 | .file-header svg { 73 | display: flex; 74 | align-items: center; 75 | } 76 | /* 第二个条目 */ 77 | .folder-name span:nth-child(2) { 78 | width: 100px; 79 | display: flex; 80 | white-space: nowrap; 81 | overflow: hidden; 82 | text-overflow: ellipsis; 83 | position: relative; 84 | } 85 | .tocButton { 86 | display: flex; 87 | white-space: nowrap; 88 | overflow: hidden; 89 | text-overflow: ellipsis; 90 | position: relative; 91 | } 92 | .deleteButton, 93 | .tocButton>button { 94 | border: none; 95 | cursor: pointer; 96 | padding: 4px; 97 | height: 20px; 98 | width: 20px; 99 | margin-left: 5px; 100 | font-size: 1em; 101 | } 102 | 103 | .floating-button { 104 | position: absolute; 105 | box-sizing: border-box; 106 | right: 20px; 107 | bottom: 20px; 108 | border-radius: 10px; 109 | padding: 5px; 110 | width: max-content; 111 | height: 35px; 112 | } 113 | .floating-button svg { 114 | width: 25px; 115 | height: 25px; 116 | stroke-width: 3; 117 | } 118 | 119 | .modal-title { 120 | font-weight: bold; 121 | font-size: 20px; 122 | margin-top: 0; 123 | } 124 | 125 | .modal-content>button { 126 | margin-left: 5px; 127 | float: right; 128 | } 129 | 130 | .modal-content>input { 131 | width: 250px; 132 | } 133 | 134 | .book-item { 135 | padding: 0.5em; 136 | margin: 0.5em 0; 137 | border: 1.5px solid transparent; 138 | border-radius: 4px; 139 | cursor: pointer; 140 | position: relative; 141 | aspect-ratio: 2/3; 142 | background-color: var(--tab-container-background); 143 | box-sizing: border-box; 144 | } 145 | .book-item:hover { 146 | border: 1.5px solid var(--interactive-accent); 147 | } 148 | 149 | .book-title { 150 | font-weight: bold; 151 | color: var(--inline-title-color); 152 | font-size: 1.5em; 153 | text-align: center; 154 | margin-top: 30%; 155 | } 156 | 157 | .book-type { 158 | font-style: italic; 159 | position: absolute; 160 | bottom: 10px; 161 | right: 0; 162 | color: var(--text-normal); 163 | background-color: var(--background-primary); 164 | padding: 2px 5px; 165 | font-size: 12px; 166 | border-radius: 5px 0 0 5px; 167 | } 168 | 169 | .book-count { 170 | position: absolute; 171 | bottom: 10px; 172 | left: 10px; 173 | } 174 | 175 | .book-list-container { 176 | padding: 30px !important; 177 | display: grid; 178 | grid-gap: 20px; 179 | height: max-content !important; 180 | } 181 | 182 | .pluginModal { 183 | margin-top: 0; 184 | } 185 | 186 | .input-label { 187 | margin-bottom: 10px; 188 | } 189 | .name-label { 190 | flex-grow: 1; 191 | margin-right: 20px; 192 | } 193 | 194 | .name-label> input { 195 | width: 100%; 196 | } 197 | .option-label { 198 | margin-top: 20px; 199 | display: flex; 200 | justify-content: space-between; 201 | align-items: center; 202 | } 203 | .info-form { 204 | display: flex; 205 | } 206 | 207 | .desc-label { 208 | flex-grow: 2; 209 | } 210 | .inspiration-item { 211 | position: relative; 212 | background-color: var(--background-primary); 213 | box-sizing: border-box; 214 | padding: 10px 15px; 215 | margin: 0 20px 20px 20px; 216 | border-radius: 10px; 217 | border: 2px solid transparent; 218 | } 219 | 220 | .inspiration-item:hover { 221 | border: 2px solid var(--interactive-accent); 222 | } 223 | 224 | .inspiration-item>.file-title { 225 | font-size: 1.2em; 226 | font-weight: bold; 227 | } 228 | div[data-type="book-setting"]>.view-title { 229 | margin-left: 20px; 230 | margin-right: 20px; 231 | display: flex; 232 | justify-content: space-between; 233 | } 234 | div[data-type="book-setting"]>.view-title svg { 235 | width: 20px; 236 | height: 20px; 237 | stroke-width: 3; 238 | } 239 | 240 | .setting-tabs { 241 | display: flex; 242 | flex-direction: row; 243 | justify-content: space-around; 244 | } 245 | 246 | .set-main-container { 247 | display: flex; 248 | justify-content: space-between; 249 | height: 100%; 250 | } 251 | .tabs-container { 252 | width: 50px; 253 | border-left: var(--divider-width) solid var(--divider-color); 254 | background-color: var(--ribbon-background); 255 | display: flex; 256 | flex-direction: column; 257 | justify-content: space-between; 258 | } 259 | .tabs-bottom { 260 | margin-bottom: 17px; 261 | } 262 | 263 | .list-container { 264 | width: 200px; 265 | border-left: var(--divider-width) solid var(--divider-color); 266 | } 267 | .list-container-main { 268 | overflow-y: auto; 269 | } 270 | .list-container-header { 271 | height: 30px; 272 | font-weight: bold; 273 | display: flex; 274 | gap: 5px; 275 | justify-content:space-between; 276 | align-items: center; 277 | margin: 8px 5px; 278 | } 279 | .category-input, 280 | .category-input input { 281 | width: 100%; 282 | height: 100%; 283 | } 284 | 285 | .list-header-icon { 286 | display: flex; 287 | align-items: center; 288 | padding: 5px; 289 | } 290 | .set-content-title { 291 | display: flex; 292 | align-items: center; 293 | justify-content: space-between; 294 | margin: 0; 295 | padding: 10px 20px; 296 | background-color: var(--tab-container-background); 297 | } 298 | .item-container { 299 | width: calc(100% - 200px); 300 | } 301 | 302 | .outline-container { 303 | height: 100%; 304 | } 305 | .set-content-container-main, 306 | .outline-content { 307 | padding: 0 20px; 308 | height: 95%; 309 | overflow: auto; 310 | font-size: 18px; 311 | } 312 | 313 | .tab-button { 314 | width: max-content; 315 | height: max-content; 316 | margin: 10px auto; 317 | padding: 10px; 318 | border-radius: 5px; 319 | border-bottom: var(--divider-width) solid var(--divider-color); 320 | } 321 | .selected, 322 | .tab-button:hover { 323 | background-color: var(--background-primary); 324 | } 325 | .tab-button svg { 326 | width: 20px; 327 | height: 20px; 328 | } 329 | .tab-button .tab-name { 330 | text-align: center; 331 | font-size: 10px; 332 | } 333 | .inspiration-textarea { 334 | margin-top: 10px; 335 | } 336 | .deleteButtonPlus { 337 | position: absolute; 338 | top: 10px; 339 | right: 10px; 340 | border: none; 341 | width: 18px; 342 | height: 18px; 343 | display: none; 344 | } 345 | .deleteButtonPlus:hover svg { 346 | stroke: var(--interactive-accent); 347 | } 348 | .deleteButtonPlus svg { 349 | width: 18px; 350 | height: 18px; 351 | } 352 | .inspiration-item:hover >.deleteButtonPlus, 353 | .book-item:hover >.deleteButtonPlus { 354 | display: block; 355 | } 356 | 357 | div[data-type="file-list-view"] ul, ol { 358 | list-style-type: none; /* 去除项目符号或编号 */ 359 | padding: 0; 360 | } 361 | div[data-type="file-list-view"] .outline-title { 362 | padding: 5px 10px; 363 | border: 1px solid transparent; 364 | border-radius: 5px; 365 | box-sizing: border-box; 366 | } 367 | div[data-type="file-list-view"] .outline-title:hover { 368 | border-color: var(--interactive-accent); 369 | } 370 | 371 | .info-container { 372 | text-align: center; 373 | } 374 | 375 | .info-container .user-avatar { 376 | margin-top: 20px; 377 | border-radius: 50%; 378 | width: 50%; 379 | } 380 | 381 | .hint-info { 382 | text-align: center; 383 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ] 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.2.5": "0.2.5" 3 | } 4 | --------------------------------------------------------------------------------