├── .gitignore ├── postcss.config.js ├── docs ├── .vuepress │ ├── public │ │ └── images │ │ │ ├── avatar.png │ │ │ ├── logo.png │ │ │ ├── favicon.ico │ │ │ ├── covers │ │ │ ├── math1.jpg │ │ │ ├── math2.jpg │ │ │ ├── math3.jpg │ │ │ └── typewriter.jpg │ │ │ ├── logo-256x256.png │ │ │ ├── home │ │ │ ├── home_icon.png │ │ │ ├── folder1-cover.svg │ │ │ └── folder2-cover.svg │ │ │ ├── two-dishes-one-fish.png │ │ │ └── icons │ │ │ ├── more.svg │ │ │ ├── caret-down.svg │ │ │ ├── caret-up.svg │ │ │ ├── back.svg │ │ │ ├── date.svg │ │ │ ├── archive-fill.svg │ │ │ ├── archive.svg │ │ │ ├── grid-layout.svg │ │ │ ├── collection.svg │ │ │ ├── close-circle.svg │ │ │ ├── list.svg │ │ │ ├── post-fill.svg │ │ │ ├── folder-fill.svg │ │ │ ├── post.svg │ │ │ ├── resize.svg │ │ │ ├── masonry.svg │ │ │ ├── folder.svg │ │ │ ├── descend.svg │ │ │ ├── ascend.svg │ │ │ ├── email.svg │ │ │ ├── grid.svg │ │ │ ├── twitter.svg │ │ │ ├── dribbble.svg │ │ │ ├── github.svg │ │ │ ├── weibo.svg │ │ │ ├── juejin.svg │ │ │ └── tree-layout.svg │ ├── utils │ │ └── formatTime.js │ ├── theme │ │ ├── index.js │ │ └── layouts │ │ │ ├── HomeLayout.vue │ │ │ ├── Layout.vue │ │ │ ├── ClassificationLayout.vue │ │ │ └── FolderLayout.vue │ ├── styles │ │ └── index.scss │ ├── plugins │ │ ├── createHomePage.js │ │ ├── addTime.js │ │ ├── generateFolderPages.js │ │ └── generateListPages.js │ ├── components │ │ ├── SideBar.vue │ │ ├── Footer.vue │ │ ├── Catalog.vue │ │ ├── PostList.vue │ │ ├── CollectionModal.vue │ │ ├── PostCard.vue │ │ ├── Navbar.vue │ │ ├── FolderItem.vue │ │ └── PostsTree.vue │ └── config.js ├── folder1 │ ├── subfolder │ │ ├── deepfolder │ │ │ ├── images │ │ │ │ └── typewriter.jpg │ │ │ └── example.md │ │ └── test2.md │ └── test1.md └── folder2 │ ├── test1.md │ ├── test2.md │ └── test3.md ├── tailwind.config.js ├── LICENSE ├── package.json ├── .github └── workflows │ └── docs.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .temp 3 | .cache 4 | dist -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/avatar.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/logo.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/favicon.ico -------------------------------------------------------------------------------- /docs/.vuepress/public/images/covers/math1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/covers/math1.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/images/covers/math2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/covers/math2.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/images/covers/math3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/covers/math3.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/images/logo-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/logo-256x256.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/home/home_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/home/home_icon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/images/covers/typewriter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/covers/typewriter.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/images/two-dishes-one-fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/.vuepress/public/images/two-dishes-one-fish.png -------------------------------------------------------------------------------- /docs/folder1/subfolder/deepfolder/images/typewriter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benbinbin/two-dishes-one-fish/HEAD/docs/folder1/subfolder/deepfolder/images/typewriter.jpg -------------------------------------------------------------------------------- /docs/.vuepress/utils/formatTime.js: -------------------------------------------------------------------------------- 1 | function formatTime(value) { 2 | const time = new Date(value); 3 | return `${time.getFullYear()}/${time.getMonth() + 1}/${time.getDate()}`; 4 | } 5 | 6 | export default formatTime; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | // './docs/**/*.{vue,js,ts,jsx,tsx}', 4 | // './docs/.vuepress/**/*.{vue,js,ts,jsx,tsx}' 5 | ], 6 | darkMode: false, 7 | theme: { 8 | extend: {}, 9 | }, 10 | variants: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | } 15 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/more.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/caret-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/caret-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/date.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/archive-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/archive.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/grid-layout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/collection.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/close-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/post-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/folder-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/post.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/index.js: -------------------------------------------------------------------------------- 1 | const { path } = require('@vuepress/utils') 2 | 3 | module.exports = { 4 | name: 'vuepress-theme-two-dish-cat-fish', 5 | extends: '@vuepress/theme-default', 6 | layouts: { 7 | HomeLayout: path.resolve(__dirname, 'layouts/HomeLayout.vue'), 8 | ClassificationLayout: path.resolve(__dirname, 'layouts/ClassificationLayout.vue'), 9 | FolderLayout: path.resolve(__dirname, 'layouts/FolderLayout.vue'), 10 | Layout: path.resolve(__dirname, 'layouts/Layout.vue'), 11 | }, 12 | } -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/resize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/masonry.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | ::-webkit-scrollbar { 6 | width: 11px; 7 | height: 11px; 8 | } 9 | 10 | ::-webkit-scrollbar-thumb { 11 | border-radius: 10px; 12 | background-color: #6b7280; 13 | border: 3px solid transparent; 14 | background-clip: padding-box; 15 | } 16 | 17 | ::-webkit-scrollbar-thumb:hover { 18 | background-color: #4b5563; 19 | } 20 | 21 | html { 22 | scroll-behavior: smooth; 23 | } 24 | 25 | body { 26 | overflow: overlay; 27 | } 28 | 29 | .highlight { 30 | text-decoration: none; 31 | box-shadow: inset 0 -0.5em 0 rgba(243, 238, 102, 0.8); 32 | color: inherit; 33 | } 34 | -------------------------------------------------------------------------------- /docs/folder1/test1.md: -------------------------------------------------------------------------------- 1 | --- 2 | show: true 3 | collection: test 4 | collectionOrder: 1 5 | --- 6 | 7 | # Test1 8 | content 1 9 | 10 | ## Heading2 11 | 12 | content 2 13 | 14 | ### Heading3 15 | content 3 16 | 17 | ### This is a long heading 3 may be truncated in tree layout with small width page 18 | 19 | content 3-2 20 | 21 | #### Heading4 22 | content 4 23 | 24 | ##### Heading5 25 | content 5 26 | 27 | ###### Heading6 28 | content 6 29 | 30 | ## Heading2 31 | 32 | content 2 33 | 34 | ### Heading3 35 | content 3 36 | 37 | ### This is a long heading 3 may be truncated in tree layout with small width page 38 | 39 | content 3-2 40 | 41 | #### Heading4 42 | content 4 43 | 44 | ##### Heading5 45 | content 5 46 | 47 | ###### Heading6 48 | content 6 49 | -------------------------------------------------------------------------------- /docs/folder1/subfolder/test2.md: -------------------------------------------------------------------------------- 1 | --- 2 | show: true 3 | collection: test 4 | collectionOrder: 2 5 | --- 6 | 7 | # Test2 8 | content 1 9 | 10 | ## Heading2 11 | 12 | content 2 13 | 14 | ### Heading3 15 | content 3 16 | 17 | ### This is a long heading 3 may be truncated in tree layout with small width page 18 | 19 | content 3-2 20 | 21 | #### Heading4 22 | content 4 23 | 24 | ##### Heading5 25 | content 5 26 | 27 | ###### Heading6 28 | content 6 29 | 30 | ## Heading2 31 | 32 | content 2 33 | 34 | ### Heading3 35 | content 3 36 | 37 | ### This is a long heading 3 may be truncated in tree layout with small width page 38 | 39 | content 3-2 40 | 41 | #### Heading4 42 | content 4 43 | 44 | ##### Heading5 45 | content 5 46 | 47 | ###### Heading6 48 | content 6 49 | -------------------------------------------------------------------------------- /docs/folder2/test1.md: -------------------------------------------------------------------------------- 1 | --- 2 | show: true 3 | cover: math1.jpg 4 | collection: test 5 | collectionOrder: 1 6 | --- 7 | 8 | # Test1 9 | content 1 10 | 11 | ## Heading2 12 | 13 | content 2 14 | 15 | ### Heading3 16 | content 3 17 | 18 | ### This is a long heading 3 may be truncated in tree layout with small width page 19 | 20 | content 3-2 21 | 22 | #### Heading4 23 | content 4 24 | 25 | ##### Heading5 26 | content 5 27 | 28 | ###### Heading6 29 | content 6 30 | 31 | ## Heading2 32 | 33 | content 2 34 | 35 | ### Heading3 36 | content 3 37 | 38 | ### This is a long heading 3 may be truncated in tree layout with small width page 39 | 40 | content 3-2 41 | 42 | #### Heading4 43 | content 4 44 | 45 | ##### Heading5 46 | content 5 47 | 48 | ###### Heading6 49 | content 6 50 | -------------------------------------------------------------------------------- /docs/folder2/test2.md: -------------------------------------------------------------------------------- 1 | --- 2 | show: true 3 | cover: math2.jpg 4 | collection: test 5 | collectionOrder: 2 6 | --- 7 | 8 | # Test2 9 | content 1 10 | 11 | ## Heading2 12 | 13 | content 2 14 | 15 | ### Heading3 16 | content 3 17 | 18 | ### This is a long heading 3 may be truncated in tree layout with small width page 19 | 20 | content 3-2 21 | 22 | #### Heading4 23 | content 4 24 | 25 | ##### Heading5 26 | content 5 27 | 28 | ###### Heading6 29 | content 6 30 | 31 | ## Heading2 32 | 33 | content 2 34 | 35 | ### Heading3 36 | content 3 37 | 38 | ### This is a long heading 3 may be truncated in tree layout with small width page 39 | 40 | content 3-2 41 | 42 | #### Heading4 43 | content 4 44 | 45 | ##### Heading5 46 | content 5 47 | 48 | ###### Heading6 49 | content 6 50 | -------------------------------------------------------------------------------- /docs/folder2/test3.md: -------------------------------------------------------------------------------- 1 | --- 2 | show: true 3 | cover: math3.jpg 4 | collection: test 5 | collectionOrder: 3 6 | --- 7 | 8 | # Test3 9 | content 1 10 | 11 | ## Heading2 12 | 13 | content 2 14 | 15 | ### Heading3 16 | content 3 17 | 18 | ### This is a long heading 3 may be truncated in tree layout with small width page 19 | 20 | content 3-2 21 | 22 | #### Heading4 23 | content 4 24 | 25 | ##### Heading5 26 | content 5 27 | 28 | ###### Heading6 29 | content 6 30 | 31 | ## Heading2 32 | 33 | content 2 34 | 35 | ### Heading3 36 | content 3 37 | 38 | ### This is a long heading 3 may be truncated in tree layout with small width page 39 | 40 | content 3-2 41 | 42 | #### Heading4 43 | content 4 44 | 45 | ##### Heading5 46 | content 5 47 | 48 | ###### Heading6 49 | content 6 50 | -------------------------------------------------------------------------------- /docs/.vuepress/plugins/createHomePage.js: -------------------------------------------------------------------------------- 1 | const { createPage } = require('@vuepress/core') 2 | 3 | const createHomePage = (options, app) => { 4 | return { 5 | name: 'vuepress-plugin-createHomePage', 6 | async onInitialized(app) { 7 | // if homepage doesn't exist 8 | if (app.pages.every((page) => page.path !== '/')) { 9 | // async create a homepage 10 | const homepage = await createPage(app, { 11 | path: '/', 12 | // set frontmatter 13 | frontmatter: { 14 | layout: 'HomeLayout', 15 | cards: options.cards || [] 16 | }, 17 | }) 18 | 19 | // push the homepage to app.pages 20 | app.pages.push(homepage) 21 | } 22 | }, 23 | } 24 | } 25 | 26 | module.exports = createHomePage -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/descend.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/ascend.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/grid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ben 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "two-dishes-one-fish", 3 | "version": "1.0.0", 4 | "description": "A VuePress-next template to build a static web site as blog and knowledge management system.", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "docs:dev": "vuepress dev docs", 8 | "docs:build": "vuepress build docs" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Benbinbin/two-dishes-one-fish.git" 13 | }, 14 | "author": "Benbinbin", 15 | "license": "CC-BY-SA-4.0", 16 | "bugs": { 17 | "url": "https://github.com/Benbinbin/two-dishes-one-fish/issues" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^10.2.6", 21 | "postcss": "^8.3.0", 22 | "tailwindcss": "^2.1.4", 23 | "vuepress": "^2.0.0-beta.22", 24 | "vuepress-vite": "^2.0.0-beta.22" 25 | }, 26 | "dependencies": { 27 | "@traptitech/markdown-it-katex": "^3.4.3", 28 | "d3": "^6.5.0", 29 | "execa": "^5.1.1", 30 | "markdown-it-mark": "^3.0.1", 31 | "markdown-it-sub": "^1.0.0", 32 | "markdown-it-sup": "^1.0.0", 33 | "masonry-layout": "^4.2.2" 34 | } 35 | } -------------------------------------------------------------------------------- /docs/folder1/subfolder/deepfolder/example.md: -------------------------------------------------------------------------------- 1 | --- 2 | show: true 3 | cover: typewriter.jpg 4 | --- 5 | # 文章示例 6 | 7 | Markdown 是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。Markdown 文件的后缀名是 `.md` 8 | 9 | # 一级标题 H1 10 | 11 | ## 二级标题 H2 12 | 13 | ### 三级标题 H3 14 | 15 | #### 四级标题 H4 16 | 17 | ##### 五级标题 H5 18 | 19 | ###### 六级标题 H5 20 | 21 | --- 22 | 23 | ~~删除线~~*斜体字***粗体**上标 X^2^ 下标 O~2~ ==高亮== 24 | 25 | > 引用文本 Blockquotes 26 | 27 | [链接](https://benbinbin.github.io/) 28 | 29 | 行内代码 `npm install marked` 30 | 31 | 代码块 32 | 33 | ```javascript 34 | console.log('hello world!') 35 | ``` 36 | 37 | 表格 38 | 39 | | First Header | Second Header | 40 | | ------------- | ------------- | 41 | | Content Cell | Content Cell | 42 | | Content Cell | Content Cell | 43 | 44 | 45 | 图片 46 | 47 |  48 | 49 | 50 | 无序列表 51 | 52 | * 列表一 53 | * 内嵌列表 1 54 | * 内嵌列表 2 55 | * 内嵌列表 3 56 | * 深度嵌套列表 a 57 | * 深度嵌套列表 b 58 | * 深度嵌套列表 c 59 | * 列表二 60 | * 列表三 61 | 62 | 有序列表 63 | 64 | 1. 第一行 65 | 1. 内嵌列表 1 66 | 2. 内嵌列表 2 67 | 3. 内嵌列表 3 68 | 2. 第二行 69 | 3. 第三行 70 | 71 | [emoji](https://www.webfx.com/tools/emoji-cheat-sheet/) :smile: :bulb: :warning: 72 | 73 | 科学公式 [KaTeX](https://katex.org/docs/supported.html) 74 | 75 | 行内的公式 $E=mc^2$ 76 | 77 | 公式块 78 | $$ 79 | \begin{aligned} 80 | P \times A 81 | &= 82 | \begin{bmatrix} 83 | 0& 1\\ 84 | 1& 0 85 | \end{bmatrix} 86 | \begin{bmatrix} 87 | a& b\\ 88 | c& d 89 | \end{bmatrix} 90 | &= 91 | \begin{bmatrix} 92 | c&d \\ 93 | a&b 94 | \end{bmatrix} 95 | \end{aligned} 96 | $$ -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/dribbble.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | -------------------------------------------------------------------------------- /docs/.vuepress/plugins/addTime.js: -------------------------------------------------------------------------------- 1 | /** 2 | * refer to @vuepress/plugin-git: https://www.npmjs.com/package/@vuepress/plugin-git 3 | */ 4 | const execa = require('execa') 5 | 6 | /** 7 | * Check if the git repo is valid 8 | */ 9 | const checkGitRepo = (cwd) => { 10 | try { 11 | execa.commandSync('git log', { cwd }) 12 | return true 13 | } catch { 14 | return false 15 | } 16 | } 17 | 18 | const getUpdatedTime = async (filePath, cwd) => { 19 | const { stdout } = await execa( 20 | 'git', 21 | ['--no-pager', 'log', '-1', '--format=%at', filePath], 22 | { 23 | cwd, 24 | } 25 | ) 26 | 27 | return Number.parseInt(stdout, 10) * 1000 28 | } 29 | 30 | const getCreatedTime = async (filePath, cwd) => { 31 | const { stdout } = await execa( 32 | 'git', 33 | ['--no-pager', 'log', '--diff-filter=A', '--format=%at', filePath], 34 | { 35 | cwd, 36 | } 37 | ) 38 | 39 | return Number.parseInt(stdout, 10) * 1000 40 | } 41 | 42 | const addTime = { 43 | name: 'vuepress-plugin-addTime', 44 | async extendsPageOptions(options, app) { 45 | if (options.filePath) { 46 | filePath = options.filePath; 47 | const cwd = app.dir.source() 48 | const isGitRepoValid = checkGitRepo(cwd) 49 | 50 | let createdTime = null; 51 | let updatedTime = null; 52 | 53 | if (isGitRepoValid) { 54 | createdTime = await getCreatedTime(filePath, cwd) 55 | updatedTime = await getUpdatedTime(filePath, cwd) 56 | } 57 | 58 | return { 59 | frontmatter: { 60 | createdTime, 61 | updatedTime 62 | }, 63 | } 64 | } else { 65 | return {} 66 | } 67 | 68 | } 69 | } 70 | 71 | module.exports = addTime -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # reference: https://vuepress2.netlify.app/zh/guide/deployment.html#github-pages 2 | name: docs 3 | 4 | on: 5 | # 每当 push 到 main 分支时触发部署 6 | push: 7 | branches: [main] 8 | # 手动触发部署 9 | workflow_dispatch: 10 | 11 | jobs: 12 | docs: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | # “最近更新时间” 等 git 日志相关信息,需要拉取全部提交记录 19 | fetch-depth: 0 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v1 23 | with: 24 | # 选择要使用的 node 版本 25 | node-version: '14' 26 | 27 | # 缓存 node_modules 28 | # - name: Cache dependencies 29 | # uses: actions/cache@v2 30 | # id: yarn-cache 31 | # with: 32 | # path: | 33 | # **/node_modules 34 | # key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 35 | # restore-keys: | 36 | # ${{ runner.os }}-yarn- 37 | 38 | # 如果缓存没有命中,安装依赖 39 | # - name: Install dependencies 40 | # if: steps.yarn-cache.outputs.cache-hit != 'true' 41 | # run: yarn --frozen-lockfile 42 | 43 | # 安装依赖 44 | - name: Install dependencies 45 | run: yarn install 46 | 47 | # 运行构建脚本 48 | - name: Build VuePress site 49 | run: yarn docs:build 50 | 51 | # 查看 workflow 的文档来获取更多信息 52 | # @see https://github.com/crazy-max/ghaction-github-pages 53 | - name: Deploy to GitHub Pages 54 | uses: crazy-max/ghaction-github-pages@v2 55 | with: 56 | # 部署到 gh-pages 分支 57 | target_branch: gh-pages 58 | # 部署目录为 VuePress 的默认输出目录 59 | build_dir: docs/.vuepress/dist 60 | env: 61 | # @see https://docs.github.com/cn/actions/reference/authentication-in-a-workflow#about-the-github_token-secret 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/weibo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | -------------------------------------------------------------------------------- /docs/.vuepress/plugins/generateFolderPages.js: -------------------------------------------------------------------------------- 1 | const { createPage } = require('@vuepress/core'); 2 | 3 | const generateFolderPages = (options, app) => { 4 | let postFolders = {} 5 | options.postFolders.forEach(folder => { 6 | postFolders[folder] = { 7 | posts: [], 8 | tags: [] 9 | } 10 | }) 11 | 12 | return { 13 | name: 'vuepress-plugin-generateFolderPages', 14 | async onInitialized(app) { 15 | // rearrange posts to different folder 16 | app.pages.forEach((page) => { 17 | let folder = ''; 18 | if (page.filePathRelative) { 19 | folder = page.filePathRelative.split("/")[0] 20 | if (!(folder in postFolders)) return 21 | } else { 22 | return 23 | } 24 | 25 | const post = { 26 | key: page.key, 27 | title: page.title, 28 | path: page.path, 29 | pathRelative: page.htmlFilePathRelative, 30 | filePathRelative: page.filePathRelative, 31 | tags: page.frontmatter.tags || [], 32 | createdTime: page.frontmatter.createdTime || null, 33 | updatedTime: page.frontmatter.updatedTime || null, 34 | date: page.frontmatter.date || null, 35 | collection: page.frontmatter.collection || '', 36 | collectionOrder: page.frontmatter.collectionOrder || 0, 37 | } 38 | 39 | postFolders[folder].posts.push(post); 40 | postFolders[folder].tags = [...new Set([...postFolders[folder].tags, ...post.tags])] 41 | }) 42 | 43 | // add folder navigation pages 44 | let folderOptions = []; 45 | 46 | options.postFolders.forEach(item => { 47 | folderOptions.push({ 48 | path: `/folderslist/${item}`, 49 | frontmatter: { 50 | layout: 'FolderLayout', 51 | folder: item 52 | } 53 | }) 54 | }) 55 | 56 | // create folder pages 57 | let folderPagesPromise = [] 58 | folderOptions.forEach(option => { 59 | folderPagesPromise.push(createPage(app, option)) 60 | }) 61 | 62 | const folderPages = await Promise.all(folderPagesPromise) 63 | 64 | folderPages.forEach(page => { 65 | app.pages.push(page) 66 | }); 67 | 68 | }, 69 | extendsPageData: (page, app) => { 70 | // add data to each folder navigation pages 71 | if (page.frontmatter.folder) { 72 | return { 73 | postsData: postFolders[page.frontmatter.folder] 74 | } 75 | } else { 76 | return {} 77 | } 78 | }, 79 | } 80 | } 81 | 82 | module.exports = generateFolderPages -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/juejin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: This project is expired and no longer in maintenance, please check out the successor reposity **[BlogiNote](https://github.com/Benbinbin/BlogiNote)** 2 | 3 | # Two Dishes One Fish 4 |
5 |
6 |
A VuePress-next template to build a static web site as blog and knowledge management system.
9 | 10 | ## Introduction 11 | *"Two dishes one fish"* is a smart cuisine using different part of one fish to make two delicious dishes, inspired by it I want to build web site as a blog and a knowledge management system, with the same Markdown files system. 12 | 13 | I choose [VuePress](https://v2.vuepress.vuejs.org/) as the generator to compile the Markdown files to HTML files and deploy the static site to Github Page. 14 | 15 | VuePress is so easy to use by the out-of-the-box default theme just [setting some configurations](https://v2.vuepress.vuejs.org/reference/default-theme/config.html). My major work is to optimise the experience of browser the website and post: 16 | 17 | * the Markdown files can classify into tow types, it can be a blog post and a note at the same time, or just as a note. 18 | * add a blog posts navigation page, the blog post show as cards, user can browser the posts in three mode: cards grid mode, cards masonry mode, and posts list mode. 19 | * add a notes navigation page, all Markdown files will be notes, the notes show in a grid layout as the same structure as they save in the files system, powering by [D3.js](https://d3js.org/), you can also browser the notes in a tree chart. 20 | * support to add tags to Markdown file and filter the posts or notes in navigation page. 21 | * follow the RWD, responsive web design, principle, you can browser the website in different screen size. 22 | ## Demo 23 | * Live Demo: [Collection](https://benbinbin.github.io/Collection/) 24 | * Video Demo: [Youtube](https://youtu.be/cTJmWhbQ9Qg) | [Bilibili](https://www.bilibili.com/video/BV1W64y1X7ok) 25 | ## Wiki 26 | * Checkout the [Documentation](https://github.com/Benbinbin/two-dishes-one-fish/wiki/Documentation) of [wiki](https://github.com/Benbinbin/two-dishes-one-fish/wiki) to use this template and build your own site. 27 | * 根据 wiki 的[使用说明](https://github.com/Benbinbin/two-dishes-one-fish/wiki/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)或[少数派的文章](https://sspai.com/post/67518)的步骤搭建自己的网站。在 wiki 的[开发日志](https://github.com/Benbinbin/two-dishes-one-fish/wiki/%E5%BC%80%E5%8F%91%E6%97%A5%E5%BF%97)或[掘金的文章](https://juejin.cn/post/6980134339434512421)可查看该项目的开发笔记。 28 | 29 | ## License 30 | 31 | [MIT](./LICENSE) 32 | -------------------------------------------------------------------------------- /docs/.vuepress/components/SideBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 41 | 42 | 43 | 93 | 94 | -------------------------------------------------------------------------------- /docs/.vuepress/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 2 | 68 | 69 | 70 | 99 | 100 | -------------------------------------------------------------------------------- /docs/.vuepress/plugins/generateListPages.js: -------------------------------------------------------------------------------- 1 | const { createPage } = require('@vuepress/core') 2 | 3 | const generateListPages = (options, app) => { 4 | let postClassifications = { 5 | all: { 6 | posts: [], 7 | tags: [] 8 | } 9 | } 10 | options.postClassifications.forEach(classification => { 11 | postClassifications[classification] = { 12 | posts: [], 13 | tags: [] 14 | } 15 | }) 16 | 17 | return { 18 | name: 'vuepress-plugin-generateListPages', 19 | async onInitialized(app) { 20 | // rearrange posts to different classification type 21 | app.pages.forEach((page) => { 22 | let classification = ''; 23 | if (page.frontmatter.show && page.filePathRelative) { 24 | classification = page.filePathRelative.split("/")[0] 25 | if (!(classification in postClassifications)) return 26 | } else { 27 | return 28 | } 29 | 30 | const post = { 31 | title: page.title, 32 | path: page.path, 33 | pathRelative: page.htmlFilePathRelative, 34 | filePathRelative: page.filePathRelative, 35 | tags: page.frontmatter.tags || [], 36 | createdTime: page.frontmatter.createdTime || null, 37 | updatedTime: page.frontmatter.updatedTime || null, 38 | date: page.frontmatter.date || null, 39 | summary: page.frontmatter.summary || '', 40 | collection: page.frontmatter.collection || '', 41 | collectionOrder: page.frontmatter.collectionOrder || 0, 42 | cover: page.frontmatter.cover || '' 43 | } 44 | 45 | postClassifications.all.posts.push(post); 46 | postClassifications.all.tags = [...new Set([...postClassifications.all.tags, ...post.tags])] 47 | 48 | 49 | postClassifications[classification].posts.push(post); 50 | postClassifications[classification].tags = [...new Set([...postClassifications[classification].tags, ...post.tags])] 51 | }) 52 | 53 | // add classification navigation pages 54 | let listOptions = [{ 55 | path: '/postslist/all', 56 | frontmatter: { 57 | layout: 'ClassificationLayout', 58 | classification: 'all', 59 | } 60 | }]; 61 | 62 | options.postClassifications.forEach(item => { 63 | listOptions.push({ 64 | path: `/postslist/${item}`, 65 | frontmatter: { 66 | layout: 'ClassificationLayout', 67 | classification: item 68 | } 69 | }) 70 | }) 71 | 72 | let listPagesPromise = [] 73 | listOptions.forEach(option => { 74 | listPagesPromise.push(createPage(app, option)) 75 | }) 76 | 77 | const listPages = await Promise.all(listPagesPromise) 78 | 79 | listPages.forEach(page => { 80 | app.pages.push(page) 81 | }); 82 | }, 83 | extendsPageData: (page, app) => { 84 | // add data to each classification navigation pages 85 | if (page.frontmatter.classification) { 86 | return { 87 | postsList: postClassifications[page.frontmatter.classification] 88 | } 89 | } else { 90 | return {} 91 | } 92 | }, 93 | } 94 | } 95 | 96 | module.exports = generateListPages -------------------------------------------------------------------------------- /docs/.vuepress/components/Catalog.vue: -------------------------------------------------------------------------------- 1 | 2 |{{ heading.text }}
50 | 51 |