├── .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 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/caret-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/caret-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/date.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/archive-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/archive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/grid-layout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/collection.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/close-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/post-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/folder-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/post.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/masonry.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/ascend.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /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 | ![打字机](./images/typewriter.jpg) 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 | 5 | 9 | 10 | 19 | 20 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/dribbble.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 11 | 20 | 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 | 5 | 8 | 20 | 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 | 5 | 8 | 11 | 14 | 22 | 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 | 5 | 8 | 9 | 12 | 17 | 27 | 28 | 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 |

7 | 8 |

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 | 42 | 43 | 93 | 94 | -------------------------------------------------------------------------------- /docs/.vuepress/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 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 | 55 | 56 | 106 | 107 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/icons/tree-layout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/.vuepress/components/PostList.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 109 | 110 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const { path } = require('@vuepress/utils') 2 | 3 | module.exports = { 4 | open: true, 5 | lang: 'zh-CN', 6 | base: "/repo/", 7 | title: "Blog", 8 | description: 'A blog and knowledge management system.', 9 | head: [ 10 | ['link', { rel: 'icon', href: '/repo/images/favicon.ico' }], 11 | ['link', { rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/katex@0.13.5/dist/katex.min.css' }], 12 | ], 13 | bundler: '@vuepress/vite', 14 | bundlerConfig: { 15 | viteOptions: { 16 | css: { 17 | postcss: { 18 | plugins: [ 19 | require('tailwindcss'), 20 | require('autoprefixer') 21 | ] 22 | } 23 | }, 24 | } 25 | }, 26 | // bundler: '@vuepress/webpack', 27 | // bundlerConfig: { 28 | // postcss: { 29 | // postcssOptions: { 30 | // plugins: { 31 | // tailwindcss: {}, 32 | // autoprefixer: {}, 33 | // }, 34 | // }, 35 | // }, 36 | // }, 37 | plugins: [ 38 | require('./plugins/addTime.js'), 39 | [require('./plugins/createHomePage.js'), 40 | { 41 | cards: [ 42 | { 43 | name: "Folder1", 44 | image: "folder1-cover.svg" 45 | }, 46 | { 47 | name: "Folder2", 48 | image: "folder2-cover.svg" 49 | }, 50 | ] 51 | }], 52 | [require('./plugins/generateListPages.js'), 53 | { 54 | postClassifications: ['folder1', 'folder2'] 55 | }], 56 | [require('./plugins/generateFolderPages.js'), 57 | { 58 | postFolders: ['folder1', 'folder2'] 59 | }], 60 | ], 61 | theme: path.resolve(__dirname, './theme/index.js'), 62 | themeConfig: { 63 | navbar: false, 64 | sidebar: false, 65 | contributors: false, 66 | lastUpdatedText: '更新时间', 67 | themePlugins: { 68 | backToTop: false, 69 | nprogress: false, 70 | } 71 | }, 72 | markdown: { 73 | links: { 74 | externalIcon: false 75 | } 76 | }, 77 | extendsMarkdown: (md) => { 78 | md.use(require('@traptitech/markdown-it-katex'), { output: 'html' }); 79 | md.use(require('markdown-it-mark')); 80 | md.use(require('markdown-it-sub')); 81 | md.use(require('markdown-it-sup')); 82 | }, 83 | define: { 84 | __BASE__: "/repo/", 85 | __HOME_PAGE_TITLE__: "一鱼两吃", 86 | __HOME_PAGE_ICON__: "home_icon.png", 87 | __HOME_DESCRIPTION__: "这是我的部落格也是一个知识管理系统。", 88 | __HOME_PAGE_COLOR__: '#9CA3AF', 89 | __AVATAR__: 'avatar.png', 90 | __CLASSIFICATIONS__: ['All', 'Folder1', 'Folder2'], 91 | __FOLDERS__: ['Folder1', 'Folder2'], 92 | __FOOTER_AVATAR_LINK__: 'https://github.com/Benbinbin/two-dishes-one-fish', 93 | __AUTHOR__: 'two dishes on fish', 94 | __FOOTER_LICENSE__: 'CC-BY-SA-4.0', 95 | __FOOTER_LICENSE_LINK__: 'https://creativecommons.org/licenses/by-sa/4.0/deed.en', 96 | __SOCIAL_MEDIA__: [ 97 | { 98 | name: 'email', 99 | logo: 'email.svg', 100 | url: 'mailto:[example]@gmail.com' 101 | }, 102 | { 103 | name: 'github', 104 | logo: 'github.svg', 105 | url: 'https://github.com/Benbinbin/two-dishes-one-fish' 106 | }, 107 | { 108 | name: 'juejin', 109 | logo: 'juejin.svg', 110 | url: 'https://juejin.cn/user/[userid]/posts' 111 | }, 112 | { 113 | name: 'dribbble', 114 | logo: 'dribbble.svg', 115 | url: 'https://dribbble.com/[username]' 116 | }, 117 | { 118 | name: 'twitter', 119 | logo: 'twitter.svg', 120 | url: 'https://twitter.com/[username]' 121 | }, 122 | { 123 | name: 'weibo', 124 | logo: 'weibo.svg', 125 | url: 'https://weibo.com/[username]' 126 | }, 127 | ], 128 | }, 129 | } -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/HomeLayout.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 122 | 123 | -------------------------------------------------------------------------------- /docs/.vuepress/components/CollectionModal.vue: -------------------------------------------------------------------------------- 1 | 108 | 109 | 145 | 146 | -------------------------------------------------------------------------------- /docs/.vuepress/components/PostCard.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | 129 | 130 | -------------------------------------------------------------------------------- /docs/.vuepress/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 114 | 115 | 169 | 170 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 105 | 106 | 237 | 238 | -------------------------------------------------------------------------------- /docs/.vuepress/components/FolderItem.vue: -------------------------------------------------------------------------------- 1 | 239 | 240 | 266 | 267 | -------------------------------------------------------------------------------- /docs/.vuepress/components/PostsTree.vue: -------------------------------------------------------------------------------- 1 | 144 | 145 | 307 | 308 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/ClassificationLayout.vue: -------------------------------------------------------------------------------- 1 | 208 | 209 | 398 | 399 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/home/folder1-cover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 70 | 71 | 72 | 73 | 75 | 77 | 78 | 81 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 95 | 96 | 97 | 99 | 102 | 103 | 104 | 105 | 106 | 109 | 112 | 115 | 117 | 120 | 121 | 122 | 123 | 125 | 126 | 127 | 129 | 130 | 131 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 143 | 144 | 145 | 147 | 148 | 149 | 151 | 152 | 153 | 154 | 155 | 156 | 158 | 159 | 160 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 184 | 185 | 186 | 189 | 190 | 191 | 193 | 194 | 195 | 196 | 197 | 198 | 201 | 202 | 203 | 205 | 206 | 207 | 209 | 210 | 211 | 214 | 215 | 216 | 218 | 219 | 220 | 223 | 224 | 225 | 227 | 228 | 229 | 232 | 233 | 234 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 252 | 253 | 254 | 256 | 257 | 258 | 260 | 261 | 262 | 264 | 265 | 266 | 268 | 269 | 270 | 272 | 273 | 274 | 275 | 276 | 278 | 279 | 280 | 281 | 283 | 284 | 285 | 286 | 288 | 289 | 290 | 291 | 293 | 294 | 295 | 297 | 299 | 301 | 303 | 305 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/FolderLayout.vue: -------------------------------------------------------------------------------- 1 | 418 | 419 | 692 | 693 | -------------------------------------------------------------------------------- /docs/.vuepress/public/images/home/folder2-cover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 74 | 76 | 77 | 78 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 91 | 92 | 93 | 95 | 98 | 99 | 100 | 101 | 103 | 106 | 109 | 111 | 114 | 116 | 119 | 120 | 121 | 124 | 127 | 128 | 129 | 130 | 133 | 136 | 138 | 141 | 142 | 143 | 144 | 145 | 147 | 148 | 149 | 151 | 152 | 153 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 165 | 166 | 167 | 169 | 170 | 171 | 173 | 174 | 175 | 176 | 177 | 178 | 180 | 181 | 182 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 206 | 207 | 208 | 211 | 212 | 213 | 215 | 216 | 217 | 218 | 219 | 220 | 223 | 224 | 225 | 227 | 228 | 229 | 231 | 232 | 233 | 236 | 237 | 238 | 240 | 241 | 242 | 245 | 246 | 247 | 249 | 250 | 251 | 254 | 255 | 256 | 259 | 260 | 261 | 262 | 263 | 265 | 266 | 268 | 270 | 272 | 274 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 290 | 291 | 293 | 295 | 296 | 297 | 299 | 301 | 302 | 303 | 305 | 307 | 308 | 309 | 311 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 432 | 433 | 434 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 451 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 463 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 476 | 478 | 480 | 482 | 483 | 484 | 485 | --------------------------------------------------------------------------------