├── .editorconfig ├── .gitattributes ├── .gitignore ├── README.md ├── docs ├── example-chinese.json ├── example-deep-readme.json ├── example-deep-readme │ └── 01.guide │ │ └── chapter-1 │ │ └── README.md ├── example-filter.json ├── example-filter │ ├── 01.guide │ │ └── README.md │ └── 02.api │ │ ├── help.md │ │ └── skip.md ├── example-missing-readme.json ├── example-missing-readme │ ├── nav.01.guide │ │ └── README.md │ └── nav.02.no-readme │ │ └── file-is-here-to-let-git-create-non-empty-directory.txt ├── example-no-navbar.json ├── example-no-navbar │ ├── 01.guide │ │ └── README.md │ └── 02.api │ │ └── help.md ├── example-simple-nav │ ├── nav.01.guide │ │ └── README.md │ └── nav.02.api │ │ ├── README.md │ │ └── classes │ │ └── member.md ├── example-sort.json ├── example-sort │ ├── 01-file.md │ ├── 02-folder │ │ └── file.md │ ├── 03-file.md │ └── no-name │ │ └── last.md ├── example-standard-no-readme-move.json ├── example-standard.json ├── example-standard │ ├── folder--nc,d1 │ │ ├── folder-doc.md │ │ └── subfolder │ │ │ └── subfolder-doc.md │ ├── nav.01.guide │ │ ├── README.md │ │ ├── how.md │ │ └── what.md │ ├── nav.02.api │ │ ├── 01.enums │ │ │ └── member-type.md │ │ ├── 02.classes │ │ │ └── member.md │ │ └── README.md │ └── nav.other │ │ └── nav.sub │ │ ├── README.md │ │ ├── navsub-doc.md │ │ └── sub-sub │ │ └── sub-sub.md ├── example-with-root-readme.json ├── example-with-root-readme │ ├── README.md │ ├── nav.20.navbar2 copy │ │ ├── nav.01.vue2 │ │ │ ├── 01.update_log2 │ │ │ │ ├── aa.md │ │ │ │ ├── bb.md │ │ │ │ └── common.md │ │ │ ├── 10.api2 │ │ │ │ └── 4 copy.md │ │ │ └── README.md │ │ └── nav.02.PC2 │ │ │ └── README.md │ └── nav.20.navbar2 │ │ ├── nav.01.vue │ │ ├── 01.update_log │ │ │ ├── aa.md │ │ │ ├── bb.md │ │ │ └── common.md │ │ ├── 10.api │ │ │ └── 4 copy.md │ │ └── README.md │ │ └── nav.02.PC │ │ └── README.md └── example-中文 │ ├── nav.01.中国 │ ├── Readme.md │ └── nav.02.北京 │ │ └── README.md │ └── nav.01.🇺🇸 │ └── readme.md ├── lib ├── index.js └── index.test.js ├── package-lock.json └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | max_line_length = 120 14 | trim_trailing_whitespace = true 15 | 16 | [*.md] 17 | max_line_length = 0 18 | trim_trailing_whitespace = false 19 | 20 | [COMMIT_EDITMSG] 21 | max_line_length = 0 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto 4 | 5 | # For the following file types, normalize line endings to LF on 6 | # checkin and prevent conversion to CRLF when they are checked out 7 | # (this is required in order to prevent newline related issues like, 8 | # for example, after the build script is run) 9 | *.css text eol=lf 10 | *.html text eol=lf 11 | *.jade text eol=lf 12 | *.js text eol=lf 13 | *.json text eol=lf 14 | *.less text eol=lf 15 | *.scss text eol=lf 16 | *.md text eol=lf 17 | *.sh text eol=lf 18 | *.txt text eol=lf 19 | *.xml text eol=lf 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 2 | 3 | # Created by https://www.gitignore.io/api/flutter,macos,node,visualstudiocode,vuejs,webstorm+all,windows,sonar 4 | # Edit at https://www.gitignore.io/?templates=flutter,macos,node,visualstudiocode,vuejs,webstorm+all,windows,sonar 5 | 6 | ### Flutter ### 7 | # Flutter/Dart/Pub related 8 | **/doc/api/ 9 | .dart_tool/ 10 | .flutter-plugins 11 | .packages 12 | .pub-cache/ 13 | .pub/ 14 | build/ 15 | 16 | # Android related 17 | **/android/**/gradle-wrapper.jar 18 | **/android/.gradle 19 | **/android/captures/ 20 | **/android/gradlew 21 | **/android/gradlew.bat 22 | **/android/local.properties 23 | **/android/**/GeneratedPluginRegistrant.java 24 | 25 | # iOS/XCode related 26 | **/ios/**/*.mode1v3 27 | **/ios/**/*.mode2v3 28 | **/ios/**/*.moved-aside 29 | **/ios/**/*.pbxuser 30 | **/ios/**/*.perspectivev3 31 | **/ios/**/*sync/ 32 | **/ios/**/.sconsign.dblite 33 | **/ios/**/.tags* 34 | **/ios/**/.vagrant/ 35 | **/ios/**/DerivedData/ 36 | **/ios/**/Icon? 37 | **/ios/**/Pods/ 38 | **/ios/**/.symlinks/ 39 | **/ios/**/profile 40 | **/ios/**/xcuserdata 41 | **/ios/.generated/ 42 | **/ios/Flutter/App.framework 43 | **/ios/Flutter/Flutter.framework 44 | **/ios/Flutter/Generated.xcconfig 45 | **/ios/Flutter/app.flx 46 | **/ios/Flutter/app.zip 47 | **/ios/Flutter/flutter_assets/ 48 | **/ios/ServiceDefinitions.json 49 | **/ios/Runner/GeneratedPluginRegistrant.* 50 | 51 | # Exceptions to above rules. 52 | !**/ios/**/default.mode1v3 53 | !**/ios/**/default.mode2v3 54 | !**/ios/**/default.pbxuser 55 | !**/ios/**/default.perspectivev3 56 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 57 | 58 | ### macOS ### 59 | # General 60 | .DS_Store 61 | .AppleDouble 62 | .LSOverride 63 | 64 | # Icon must end with two \r 65 | Icon 66 | 67 | # Thumbnails 68 | ._* 69 | 70 | # Files that might appear in the root of a volume 71 | .DocumentRevisions-V100 72 | .fseventsd 73 | .Spotlight-V100 74 | .TemporaryItems 75 | .Trashes 76 | .VolumeIcon.icns 77 | .com.apple.timemachine.donotpresent 78 | 79 | # Directories potentially created on remote AFP share 80 | .AppleDB 81 | .AppleDesktop 82 | Network Trash Folder 83 | Temporary Items 84 | .apdisk 85 | 86 | ### Node ### 87 | # Logs 88 | logs 89 | *.log 90 | npm-debug.log* 91 | yarn-debug.log* 92 | yarn-error.log* 93 | lerna-debug.log* 94 | 95 | # Diagnostic reports (https://nodejs.org/api/report.html) 96 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 97 | 98 | # Runtime data 99 | pids 100 | *.pid 101 | *.seed 102 | *.pid.lock 103 | 104 | # Directory for instrumented libs generated by jscoverage/JSCover 105 | lib-cov 106 | 107 | # Coverage directory used by tools like istanbul 108 | coverage 109 | 110 | # nyc test coverage 111 | .nyc_output 112 | 113 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 114 | .grunt 115 | 116 | # Bower dependency directory (https://bower.io/) 117 | bower_components 118 | 119 | # node-waf configuration 120 | .lock-wscript 121 | 122 | # Compiled binary addons (https://nodejs.org/api/addons.html) 123 | build/Release 124 | 125 | # Dependency directories 126 | node_modules/ 127 | jspm_packages/ 128 | 129 | # TypeScript v1 declaration files 130 | typings/ 131 | 132 | # Optional npm cache directory 133 | .npm 134 | 135 | # Optional eslint cache 136 | .eslintcache 137 | 138 | # Optional REPL history 139 | .node_repl_history 140 | 141 | # Output of 'npm pack' 142 | *.tgz 143 | 144 | # Yarn Integrity file 145 | .yarn-integrity 146 | 147 | # dotenv environment variables file 148 | .env 149 | .env.test 150 | 151 | # parcel-bundler cache (https://parceljs.org/) 152 | .cache 153 | 154 | # next.js build output 155 | .next 156 | 157 | # nuxt.js build output 158 | .nuxt 159 | 160 | # vuepress build output 161 | .vuepress/dist 162 | 163 | # Serverless directories 164 | .serverless/ 165 | 166 | # FuseBox cache 167 | .fusebox/ 168 | 169 | # DynamoDB Local files 170 | .dynamodb/ 171 | 172 | ### Sonar ### 173 | #Sonar generated dir 174 | /.sonar/ 175 | 176 | ### VisualStudioCode ### 177 | .vscode/* 178 | !.vscode/settings.json 179 | !.vscode/tasks.json 180 | !.vscode/launch.json 181 | !.vscode/extensions.json 182 | 183 | ### VisualStudioCode Patch ### 184 | # Ignore all local history of files 185 | .history 186 | 187 | ### Vuejs ### 188 | # Recommended template: Node.gitignore 189 | 190 | dist/ 191 | npm-debug.log 192 | yarn-error.log 193 | 194 | ### WebStorm+all ### 195 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 196 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 197 | 198 | # User-specific stuff 199 | .idea/**/workspace.xml 200 | .idea/**/tasks.xml 201 | .idea/**/usage.statistics.xml 202 | .idea/**/dictionaries 203 | .idea/**/shelf 204 | 205 | # Generated files 206 | .idea/**/contentModel.xml 207 | 208 | # Sensitive or high-churn files 209 | .idea/**/dataSources/ 210 | .idea/**/dataSources.ids 211 | .idea/**/dataSources.local.xml 212 | .idea/**/sqlDataSources.xml 213 | .idea/**/dynamic.xml 214 | .idea/**/uiDesigner.xml 215 | .idea/**/dbnavigator.xml 216 | 217 | # Gradle 218 | .idea/**/gradle.xml 219 | .idea/**/libraries 220 | 221 | # Gradle and Maven with auto-import 222 | # When using Gradle or Maven with auto-import, you should exclude module files, 223 | # since they will be recreated, and may cause churn. Uncomment if using 224 | # auto-import. 225 | # .idea/modules.xml 226 | # .idea/*.iml 227 | # .idea/modules 228 | 229 | # CMake 230 | cmake-build-*/ 231 | 232 | # Mongo Explorer plugin 233 | .idea/**/mongoSettings.xml 234 | 235 | # File-based project format 236 | *.iws 237 | 238 | # IntelliJ 239 | out/ 240 | 241 | # mpeltonen/sbt-idea plugin 242 | .idea_modules/ 243 | 244 | # JIRA plugin 245 | atlassian-ide-plugin.xml 246 | 247 | # Cursive Clojure plugin 248 | .idea/replstate.xml 249 | 250 | # Crashlytics plugin (for Android Studio and IntelliJ) 251 | com_crashlytics_export_strings.xml 252 | crashlytics.properties 253 | crashlytics-build.properties 254 | fabric.properties 255 | 256 | # Editor-based Rest Client 257 | .idea/httpRequests 258 | 259 | # Android studio 3.1+ serialized cache file 260 | .idea/caches/build_file_checksums.ser 261 | 262 | # JetBrains templates 263 | **___jb_tmp___ 264 | 265 | ### WebStorm+all Patch ### 266 | # Ignores the whole .idea folder and all .iml files 267 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 268 | 269 | .idea/ 270 | 271 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 272 | 273 | *.iml 274 | modules.xml 275 | .idea/misc.xml 276 | *.ipr 277 | 278 | # Sonarlint plugin 279 | .idea/sonarlint 280 | 281 | ### Windows ### 282 | # Windows thumbnail cache files 283 | Thumbs.db 284 | ehthumbs.db 285 | ehthumbs_vista.db 286 | 287 | # Dump file 288 | *.stackdump 289 | 290 | # Folder config file 291 | [Dd]esktop.ini 292 | 293 | # Recycle Bin used on file shares 294 | $RECYCLE.BIN/ 295 | 296 | # Windows Installer files 297 | *.cab 298 | *.msi 299 | *.msix 300 | *.msm 301 | *.msp 302 | 303 | # Windows shortcuts 304 | *.lnk 305 | 306 | # End of https://www.gitignore.io/api/flutter,macos,node,visualstudiocode,vuejs,webstorm+all,windows,sonar 307 | 308 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) 309 | 310 | .rts2* 311 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuepress-bar 2 | 3 | VuePress sidebar and navbar generator based on file and directory structure. Focus your documents, not sidebar or navbar. 4 | 5 | # Synopsis 6 | 7 | ```js 8 | //.vuepress/config.js 9 | const getConfig = require("vuepress-bar"); 10 | 11 | const { nav, sidebar } = getConfig(); 12 | 13 | module.exports = { themeConfig: { nav, sidebar } }; 14 | ``` 15 | 16 | # Usage 17 | 18 | ## 1. Get Menu & Bar Configuration 19 | 20 | ```js 21 | // .vuepress/config.js 22 | const { nav, sidebar } = getConfig(options); // Use default location of `.vuepress`: `${__dirname}/..` 23 | const { nav, sidebar } = getConfig("path/to/.vuepress/..", options); // Point to *parent* dir of `.vuepress`. 24 | ``` 25 | 26 | ## 2. Merge with VuePress Configuration 27 | 28 | ### Alternative 1: No Modification 29 | 30 | ```js 31 | // .vuepress/config.js 32 | module.exports = { themeConfig: { nav, sidebar } }; 33 | ``` 34 | 35 | ### Alternative 2: Modification 36 | 37 | ```js 38 | // .vuepress/config.js 39 | module.exports = { 40 | themeConfig: { 41 | nav: [{ text: 'pg-structure', link: 'https://www.pg-structure.com/' }, ...nav] 42 | sidebar, 43 | } 44 | }; 45 | ``` 46 | 47 | \*\* See advanced section below for more advanced modifications such as overriding. 48 | 49 | # Features 50 | 51 | - **Creates navbar & sidebar:** Add `navbar` prefix to your directories such as `nav.guide` or `nav.01.guide` 52 | - **Custom sort:** Prefix directories with numbers, or add `order` meta to files such as `01.guide` 53 | - **Multiple Sidebars** 54 | - **No configuration** 55 | - Adds README.md to the first available group like the VuePress web site. (Maybe disabled by options) 56 | - Pass parameters in directory names. (See advanced example below.) 57 | - Fİlter based on Front Matter meta data. 58 | 59 | # Examples with Explanations 60 | 61 | ## With Navbar 62 | 63 | ``` 64 | |- docs/ 65 | |- .vuepress 66 | |- nav.01.guide/ 67 | |- README.md 68 | |- nav.02.api/ 69 | |- classes/ 70 | |- member.md 71 | ``` 72 | 73 | ```js 74 | { 75 | nav: [ 76 | { text: 'Guide', link: '/nav.01.guide/' }, 77 | { text: 'Api', link: '/nav.02.api/' } 78 | ], 79 | sidebar: { 80 | '/nav.01.guide/': [ '' ], 81 | '/nav.02.api/': [ { title: 'Classes', children: [ '', 'classes/member' ] } ] 82 | } 83 | } 84 | ``` 85 | 86 | - Readme is moved into first group: `'/nav.02.api/': [ { title: 'Classes', children: [ '', 'classes/member' ] } ]` instead of `'/nav.02.api/': [ '', { title: 'Classes', children: [ 'classes/member' ] } ]` 87 | 88 | ## Without Navbar 89 | 90 | ``` 91 | |- docs/ 92 | |- .vuepress 93 | |- 01.guide/ 94 | |- README.md 95 | |- 02.api/ 96 | |- classes/ 97 | |- member.md 98 | ``` 99 | 100 | ```js 101 | { 102 | nav: [], 103 | sidebar: [ 104 | { title: "Guide", children: ["01.guide/"] }, 105 | { 106 | title: "Api", 107 | children: [{ title: "Classes", children: ["02.api/classes/member"] }] 108 | } 109 | ] 110 | }; 111 | ``` 112 | 113 | ## Advanced 114 | 115 | ### Parameters 116 | 117 | It is possible to pass sidebar parameters in directory names. You may pass the following parameters after double dash `--` separated by a comma: 118 | 119 | - `nc` sets `collapsable` to `false`. 120 | - `dX` sets `sidebarDepth` to `X`. 121 | 122 | ``` 123 | |- docs/ 124 | |- 01.guide--nc,d2/ 125 | |- README.md 126 | ``` 127 | 128 | ```js 129 | { 130 | nav: [], 131 | sidebar: [ 132 | { 133 | title: "Guide", 134 | collapsable: false, 135 | sidebarDepth: 1, 136 | children: ["01.guide--nc,d2/"] 137 | }, 138 | ] 139 | }; 140 | ``` 141 | 142 | ### Overriding 143 | 144 | You may want to override generated navbar or sidebar. That is the reason why `vuepress-bar` isn't written as a plugin. You can override generated config. 145 | 146 | **Rename "Api" to "API"** 147 | 148 | ```ts 149 | const { nav, sidebar } = getConfig(); 150 | 151 | // Find item with text "Api" and change it to "API". 152 | nav.find((item) => item.text === "Api").text = "API"; 153 | ``` 154 | 155 | **Filter Some Entries** 156 | 157 | ```ts 158 | module.exports = { 159 | themeConfig: { 160 | nav, 161 | sidebar: sidebar.filter((i) => i.title !== "Node Modules"), 162 | }, 163 | }; 164 | ``` 165 | 166 | **Filter Some Entries with Meta Data** 167 | 168 | Use YAML meta data (Front Matter). 169 | 170 | **CAUTION if you use "Theme API" and filter pages in the `ready` function:** The filter option has to be the same as what is in the `ready` function or there will be an error because the ready function deletes the pages. 171 | 172 | ```ts 173 | const { nav, sidebar } = getConfig({ filter: (meta) => meta.draft !== true }); 174 | ``` 175 | 176 | # Notes 177 | 178 | - VuePress requires `README.md` as default file in a `navbar` link. Forgetting `README.md` would skip that creation of that navbar item. 179 | 180 | # Options 181 | 182 | | Param | Type | Default | Description | 183 | | ------------------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------ | 184 | | stripNumbers | Boolean | `true` | Remove number prefixes from directory names where it helps sorting. | 185 | | maxLevel | Number | `2` | Maximum level of recursion for subdirectory traversing. | 186 | | navPrefix | String | `nav` | Prefix for directories for navbar and mulitple sidebars. | 187 | | skipEmptySidebar | Boolean | `true` | Do not add item to sidebar if directory is empty. | 188 | | skipEmptyNavbar | Boolean | `true` | Do not add item to navbar if directory is empty. | 189 | | multipleSideBar | Boolean | `true` | Create multiple sidebars if there are navbar items. | 190 | | addReadMeToFirstGroup | Boolean | `true` | Add README.md into first group of sidebar. (vuepress website's behaviour) | 191 | | mixDirectoriesAndFilesAlphabetically | Boolean | `true` | Add directories to alphabetic positions between files. (i.e. `01-file`, `02-folder`, `03-file` ) | 192 | | pinyinNav | Boolean | `false` | Translate chinese nav to pinyin. | 193 | | filter | Function | | Fİlter function to filter files. Front Matter meta data is passed as an object. | 194 | -------------------------------------------------------------------------------- /docs/example-chinese.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav": [ 3 | { 4 | "text": "中国", 5 | "items": [ 6 | { 7 | "text": "北京", 8 | "link": "/nav.01.zhong-guo/nav.02.bei-jing/" 9 | } 10 | ] 11 | }, 12 | { 13 | "text": "🇺🇸", 14 | "link": "/nav.01./" 15 | } 16 | ], 17 | "sidebar": { 18 | "/nav.01.zhong-guo/nav.02.bei-jing/": [""], 19 | "/nav.01./": [""] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/example-deep-readme.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav": [], 3 | "sidebar": [{ "title": "Guide", "children": [{ "title": "Chapter 1", "children": ["01.guide/chapter-1/"] }] }] 4 | } 5 | -------------------------------------------------------------------------------- /docs/example-deep-readme/01.guide/chapter-1/README.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | -------------------------------------------------------------------------------- /docs/example-filter.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav": [], 3 | "sidebar": [ 4 | { "title": "Guide", "children": ["01.guide/"] }, 5 | { "title": "Api", "children": ["02.api/help"] } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /docs/example-filter/01.guide/README.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | -------------------------------------------------------------------------------- /docs/example-filter/02.api/help.md: -------------------------------------------------------------------------------- 1 | # Help 2 | -------------------------------------------------------------------------------- /docs/example-filter/02.api/skip.md: -------------------------------------------------------------------------------- 1 | --- 2 | draft: true 3 | --- 4 | 5 | # Skip 6 | -------------------------------------------------------------------------------- /docs/example-missing-readme.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav": [{ "text": "Guide", "link": "/nav.01.guide/" }], 3 | "sidebar": { "/nav.01.guide/": [""] } 4 | } 5 | -------------------------------------------------------------------------------- /docs/example-missing-readme/nav.01.guide/README.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | -------------------------------------------------------------------------------- /docs/example-missing-readme/nav.02.no-readme/file-is-here-to-let-git-create-non-empty-directory.txt: -------------------------------------------------------------------------------- 1 | Git does not store and create empty folders. This file is created here as a stub to let Git create this directory without a README.md. 2 | -------------------------------------------------------------------------------- /docs/example-no-navbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav": [], 3 | "sidebar": [{ "title": "Guide", "children": ["01.guide/"] }, { "title": "Api", "children": ["02.api/help"] }] 4 | } 5 | -------------------------------------------------------------------------------- /docs/example-no-navbar/01.guide/README.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | -------------------------------------------------------------------------------- /docs/example-no-navbar/02.api/help.md: -------------------------------------------------------------------------------- 1 | # Help 2 | -------------------------------------------------------------------------------- /docs/example-simple-nav/nav.01.guide/README.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | -------------------------------------------------------------------------------- /docs/example-simple-nav/nav.02.api/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/vuepress-bar/67fa5b99d2ff72a8ab42256c442c5e7a1535f546/docs/example-simple-nav/nav.02.api/README.md -------------------------------------------------------------------------------- /docs/example-simple-nav/nav.02.api/classes/member.md: -------------------------------------------------------------------------------- 1 | # Help 2 | -------------------------------------------------------------------------------- /docs/example-sort.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav": [], 3 | "sidebar": [ 4 | "01-file", 5 | { 6 | "title": "Folder", 7 | "children": ["02-folder/file"] 8 | }, 9 | "03-file", 10 | { 11 | "title": "No Name", 12 | "children": ["no-name/last"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /docs/example-sort/01-file.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/vuepress-bar/67fa5b99d2ff72a8ab42256c442c5e7a1535f546/docs/example-sort/01-file.md -------------------------------------------------------------------------------- /docs/example-sort/02-folder/file.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/vuepress-bar/67fa5b99d2ff72a8ab42256c442c5e7a1535f546/docs/example-sort/02-folder/file.md -------------------------------------------------------------------------------- /docs/example-sort/03-file.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/vuepress-bar/67fa5b99d2ff72a8ab42256c442c5e7a1535f546/docs/example-sort/03-file.md -------------------------------------------------------------------------------- /docs/example-sort/no-name/last.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozum/vuepress-bar/67fa5b99d2ff72a8ab42256c442c5e7a1535f546/docs/example-sort/no-name/last.md -------------------------------------------------------------------------------- /docs/example-standard-no-readme-move.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav": [ 3 | { "text": "Guide", "link": "/nav.01.guide/" }, 4 | { "text": "Api", "link": "/nav.02.api/" }, 5 | { 6 | "text": "Other", 7 | "items": [{ "text": "Sub", "link": "/nav.other/nav.sub/" }] 8 | } 9 | ], 10 | "sidebar": { 11 | "/nav.01.guide/": ["", "what", "how"], 12 | "/nav.02.api/": [ 13 | "", 14 | { "title": "Enums", "children": ["01.enums/member-type"] }, 15 | { "title": "Classes", "children": ["02.classes/member"] } 16 | ], 17 | "/nav.other/nav.sub/": ["", "navsub-doc", { "title": "Sub Sub", "children": ["sub-sub/sub-sub"] }], 18 | "/": [ 19 | { 20 | "title": "Folder", 21 | "collapsable": false, 22 | "sidebarDepth": 1, 23 | "children": [ 24 | "folder--nc,d1/folder-doc", 25 | { 26 | "title": "Subfolder", 27 | "children": ["folder--nc,d1/subfolder/subfolder-doc"] 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/example-standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav": [ 3 | { "text": "Guide", "link": "/nav.01.guide/" }, 4 | { "text": "Api", "link": "/nav.02.api/" }, 5 | { 6 | "text": "Other", 7 | "items": [{ "text": "Sub", "link": "/nav.other/nav.sub/" }] 8 | } 9 | ], 10 | "sidebar": { 11 | "/nav.01.guide/": ["", "what", "how"], 12 | "/nav.02.api/": [ 13 | { "title": "Enums", "children": ["", "01.enums/member-type"] }, 14 | { "title": "Classes", "children": ["02.classes/member"] } 15 | ], 16 | "/nav.other/nav.sub/": ["", "navsub-doc", { "title": "Sub Sub", "children": ["sub-sub/sub-sub"] }], 17 | "/": [ 18 | { 19 | "title": "Folder", 20 | "collapsable": false, 21 | "sidebarDepth": 1, 22 | "children": [ 23 | "folder--nc,d1/folder-doc", 24 | { 25 | "title": "Subfolder", 26 | "children": ["folder--nc,d1/subfolder/subfolder-doc"] 27 | } 28 | ] 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/example-standard/folder--nc,d1/folder-doc.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: HomeDoc 4 | --- 5 | -------------------------------------------------------------------------------- /docs/example-standard/folder--nc,d1/subfolder/subfolder-doc.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: Sub Doc 4 | --- 5 | -------------------------------------------------------------------------------- /docs/example-standard/nav.01.guide/README.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | -------------------------------------------------------------------------------- /docs/example-standard/nav.01.guide/how.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: How? 4 | --- 5 | -------------------------------------------------------------------------------- /docs/example-standard/nav.01.guide/what.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: What? 4 | --- 5 | -------------------------------------------------------------------------------- /docs/example-standard/nav.02.api/01.enums/member-type.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: Member Type 4 | --- 5 | -------------------------------------------------------------------------------- /docs/example-standard/nav.02.api/02.classes/member.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: Member 4 | --- 5 | -------------------------------------------------------------------------------- /docs/example-standard/nav.02.api/README.md: -------------------------------------------------------------------------------- 1 | # API 2 | -------------------------------------------------------------------------------- /docs/example-standard/nav.other/nav.sub/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | -------------------------------------------------------------------------------- /docs/example-standard/nav.other/nav.sub/navsub-doc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Nav Sub Doc 3 | --- 4 | -------------------------------------------------------------------------------- /docs/example-standard/nav.other/nav.sub/sub-sub/sub-sub.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sub Sub 3 | --- 4 | -------------------------------------------------------------------------------- /docs/example-with-root-readme.json: -------------------------------------------------------------------------------- 1 | { 2 | "nav":[ 3 | { 4 | "text":"Navbar2", 5 | "items":[ 6 | {"text":"Vue","link":"/nav.20.navbar2/nav.01.vue/"}, 7 | {"text":"Pc","link":"/nav.20.navbar2/nav.02.PC/"} 8 | ] 9 | }, 10 | { 11 | "text":"Navbar2 Copy", 12 | "items":[ 13 | {"text":"Vue2","link":"/nav.20.navbar2 copy/nav.01.vue2/"}, 14 | {"text":"Pc2","link":"/nav.20.navbar2 copy/nav.02.PC2/"} 15 | ] 16 | } 17 | ], 18 | "sidebar":{ 19 | "/nav.20.navbar2/nav.01.vue/":[ 20 | { 21 | "title":"Update Log", 22 | "children":["","01.update_log/aa","01.update_log/bb","01.update_log/common"] 23 | }, 24 | {"title":"Api","children":["10.api/4 copy"]} 25 | ], 26 | "/nav.20.navbar2/nav.02.PC/":[""], 27 | "/nav.20.navbar2 copy/nav.01.vue2/":[ 28 | { 29 | "title":"Update Log2", 30 | "children":["","01.update_log2/aa","01.update_log2/bb","01.update_log2/common"] 31 | }, 32 | {"title":"Api2","children":["10.api2/4 copy"]} 33 | ], 34 | "/nav.20.navbar2 copy/nav.02.PC2/":[""] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | # heroImage: /hero.png 4 | heroText: Hero 标题 5 | tagline: Hero 副标题 6 | actionText: 快速上手 → 7 | actionLink: /documents/vue/ 8 | features: 9 | - title: 简洁至上 10 | details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。 11 | - title: Vue驱动 12 | details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。 13 | - title: 高性能 14 | details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。 15 | footer: MIT Licensed | Copyright © 2018-present Evan You 16 | --- 17 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2 copy/nav.01.vue2/01.update_log2/aa.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: aa 3 | --- 4 | 5 | ## this is aa 6 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2 copy/nav.01.vue2/01.update_log2/bb.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 更新日志sd 3 | --- 4 | 5 | tets1 6 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2 copy/nav.01.vue2/01.update_log2/common.md: -------------------------------------------------------------------------------- 1 | # 公用组件 2 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2 copy/nav.01.vue2/10.api2/4 copy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: api1 3 | --- 4 | 5 | ## api 6 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2 copy/nav.01.vue2/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 前端 3 | --- 4 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2 copy/nav.02.PC2/README.md: -------------------------------------------------------------------------------- 1 | # Hello VuePress--- new----2----! 2 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2/nav.01.vue/01.update_log/aa.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: aa 3 | --- 4 | 5 | ## this is aa 6 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2/nav.01.vue/01.update_log/bb.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: bb 3 | --- 4 | 5 | this is bb 6 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2/nav.01.vue/01.update_log/common.md: -------------------------------------------------------------------------------- 1 | # 公用组件 2 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2/nav.01.vue/10.api/4 copy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: api1 3 | --- 4 | 5 | ## api 6 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2/nav.01.vue/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 前端 3 | --- 4 | -------------------------------------------------------------------------------- /docs/example-with-root-readme/nav.20.navbar2/nav.02.PC/README.md: -------------------------------------------------------------------------------- 1 | # Hello VuePress--- new----2----! 2 | -------------------------------------------------------------------------------- /docs/example-中文/nav.01.中国/Readme.md: -------------------------------------------------------------------------------- 1 | # 中国 2 | -------------------------------------------------------------------------------- /docs/example-中文/nav.01.中国/nav.02.北京/README.md: -------------------------------------------------------------------------------- 1 | # 北京 2 | -------------------------------------------------------------------------------- /docs/example-中文/nav.01.🇺🇸/readme.md: -------------------------------------------------------------------------------- 1 | # 🇺🇸 2 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const sortBy = require("lodash.sortby"); 2 | const glob = require("glob"); 3 | const markdownIt = require("markdown-it"); 4 | const meta = require("markdown-it-meta"); 5 | const { lstatSync, readdirSync, readFileSync, existsSync } = require("fs"); 6 | const { join, normalize, sep, dirname, basename } = require("path"); 7 | const escapeRegExp = require("lodash.escaperegexp"); 8 | const slugify = require("transliteration").slugify; 9 | const { titleize } = require("inflection"); 10 | const parentModule = require("parent-module"); 11 | 12 | 13 | const isDirectory = source => lstatSync(source).isDirectory(); 14 | const getDirectories = source => 15 | readdirSync(source).filter(name => !(name === ".vuepress") && isDirectory(join(source, name))); 16 | const hasReadme = source => 17 | readdirSync(source).findIndex(name => name.toLowerCase() === "readme.md" && !isDirectory(join(source, name))) > -1; 18 | 19 | /** 20 | * Translate chinese to pinyin. 21 | * Compatible with vuepress-pluin-permalink-pinyin. 22 | * @param {Array} navArr 23 | */ 24 | function transliteratePinyin(navArr) { 25 | return navArr.map(nav => { 26 | const result = { ...nav }; 27 | if (nav.link) { 28 | result.link = slugify(nav.link, { ignore: ["/", "."] }); 29 | } 30 | if (nav.items) { 31 | result.items = transliteratePinyin(nav.items); 32 | } 33 | return result; 34 | }); 35 | } 36 | 37 | /** 38 | * https://github.com/ozum/vuepress-bar/issues/55 39 | */ 40 | function transliteratePinyinSiderbar(sidebar) { 41 | if (sidebar.length) { 42 | // array 43 | return sidebar.map(sidebar => { 44 | if (typeof(sidebar) == 'string') { 45 | return slugify(sidebar, { ignore: ["/", "."] }); 46 | } else if (sidebar.children) { 47 | sidebar.children = transliteratePinyinSiderbar(sidebar.children); 48 | } 49 | return sidebar; 50 | }); 51 | } else { 52 | // object 53 | for (let path in sidebar) { 54 | let newPath = slugify(path, { ignore: ["/", "."] }); 55 | sidebar[newPath] = transliteratePinyinSiderbar(sidebar[path]); 56 | delete sidebar[path]; 57 | } 58 | return sidebar; 59 | } 60 | } 61 | 62 | /** 63 | * Returns name to be used in menus after removing navigation prefix, prefix numbers used for ordering and `.`, `-`, `_` and spaces. 64 | * 65 | * @param {string} path - File path to get name for. 66 | * @param {Object} options - Options 67 | * @param {string} options.navPrefix - Navigation order prefix if present. 68 | * @param {boolean} options.stripNumbers - Whether to strip numbers. 69 | * @returns {string} - Name to be used in navigation. 70 | * @example 71 | * getName("/some/path/nav-01-how", { navPrefix: "nav", stripNumbers: true }); // how 72 | * getName("/some/path/nav.01.how", { navPrefix: "nav", stripNumbers: true }); // how 73 | */ 74 | function getName(path, { navPrefix, stripNumbers } = {}) { 75 | let name = path.split(sep).pop(); 76 | const argsIndex = name.lastIndexOf("--"); 77 | if (argsIndex > -1) { 78 | name = name.substring(0, argsIndex); 79 | } 80 | 81 | if (navPrefix) { 82 | // "nav.001.xyz" or "nav-001.xyz" or "nav_001.xyz" or "nav 001.xyz" -> "nav" 83 | const pattern = new RegExp(`^${escapeRegExp(navPrefix)}[.\-_ ]?`); 84 | name = name.replace(pattern, ""); 85 | } 86 | if (stripNumbers) { 87 | // "001.guide" or "001-guide" or "001_guide" or "001 guide" -> "guide" 88 | name = name.replace(/^\d+[.\-_ ]?/, ""); 89 | } 90 | 91 | return titleize(name.replace("-", " ")); 92 | } 93 | 94 | // Load all MD files in a specified directory and order by metadata 'order' value 95 | function getChildren(parent_path, dir, filter, recursive = true) { 96 | // CREDITS: https://github.com/benjivm (from: https://github.com/vuejs/vuepress/issues/613#issuecomment-495751473) 97 | parent_path = normalize(parent_path); 98 | parent_path = parent_path.endsWith(sep) ? parent_path.slice(0, -1) : parent_path; // Remove last / if exists. 99 | const pattern = recursive ? "/**/*.md" : "/*.md"; 100 | files = glob.sync(parent_path + (dir ? `/${dir}` : "") + pattern).map(path => { 101 | // Instantiate MarkdownIt 102 | md = new markdownIt(); 103 | // Add markdown-it-meta 104 | md.use(meta); 105 | // Get the order value 106 | file = readFileSync(path, "utf8"); 107 | md.render(file); 108 | 109 | // If available use filter option to filter based omn Front Matter meta data. 110 | if (filter && !filter(md.meta)) return undefined 111 | 112 | order = md.meta.order; 113 | // Remove "parent_path" and ".md" 114 | path = path.slice(parent_path.length + 1, -3); 115 | // Remove "README", making it the de facto index page 116 | if (basename(path.toLowerCase()) === "readme") { 117 | // if (path.toLowerCase().endsWith("readme")) { 118 | path = path.slice(0, -6); 119 | } 120 | 121 | return { 122 | path, 123 | order: path === "" && order === undefined ? 0 : order // README is first if it hasn't order 124 | }; 125 | }).filter(obj => obj !== undefined); 126 | 127 | // Return the ordered list of files, sort by 'order' then 'path' 128 | return sortBy(files, ["order", "path"]).map(file => file.path); 129 | } 130 | 131 | /** 132 | * Return sidebar config for given baseDir. 133 | * 134 | * @param {String} baseDir - Absolute path of directory to get sidebar config for. 135 | * @param {Object} options - Options 136 | * @param {String} relativeDir - Relative directory to add to baseDir 137 | * @param {Number} currentLevel - Current level of items. 138 | * @returns {Array.} - Recursion level 139 | */ 140 | function side( 141 | baseDir, 142 | { stripNumbers, maxLevel, navPrefix, skipEmptySidebar, addReadMeToFirstGroup, mixDirectoriesAndFilesAlphabetically, filter }, 143 | relativeDir = "", 144 | currentLevel = 1 145 | ) { 146 | const fileLinks = getChildren(baseDir, relativeDir, filter, currentLevel > maxLevel); 147 | if (currentLevel <= maxLevel) { 148 | getDirectories(join(baseDir, relativeDir)) 149 | .filter(subDir => !subDir.startsWith(navPrefix)) 150 | .forEach(subDir => { 151 | const children = side( 152 | baseDir, 153 | { stripNumbers, maxLevel, navPrefix, skipEmptySidebar, addReadMeToFirstGroup, mixDirectoriesAndFilesAlphabetically, filter }, 154 | join(relativeDir, subDir), 155 | currentLevel + 1 156 | ); 157 | 158 | if (children.length > 0 || !skipEmptySidebar) { 159 | // Where to put '02-folder' in ['01-file', { title: 'Other Folder', children: ['03-folder/file'] }] 160 | const sortedFolderPosition = fileLinks.findIndex( 161 | link => { 162 | let linkLabel = link 163 | 164 | if(link.children) { 165 | let childrenTitle = ""; 166 | if(typeof link.children[0] == 'string') childrenTitle = link.children[0]; 167 | else if (typeof link.children[0] == 'object') childrenTitle = link.children[0].title; 168 | linkLabel = childrenTitle.split(sep)[0]; 169 | } 170 | 171 | // Solution below is ugly, but could not find a better way. 172 | // Previously, subdirs in root level has been compared against dir name, whereas deep subdirs are compared against relative path. 173 | // Ugly patch below fixes that. 174 | return relativeDir === "" ? subDir < linkLabel : relativeDir + sep + subDir < linkLabel 175 | 176 | } 177 | ); 178 | 179 | const insertPosition = 180 | mixDirectoriesAndFilesAlphabetically && sortedFolderPosition > -1 ? sortedFolderPosition : fileLinks.length; 181 | 182 | fileLinks.splice(insertPosition, 0, { 183 | title: getName(subDir, { stripNumbers, navPrefix }), 184 | ...parseSidebarParameters(subDir), 185 | children 186 | }); 187 | } 188 | }); 189 | } 190 | 191 | // Remove README.md from first position and add it to first group. 192 | if (addReadMeToFirstGroup && fileLinks[0] === "" && typeof fileLinks[1] === "object") { 193 | fileLinks.shift(); 194 | fileLinks[0].children.unshift(""); 195 | } 196 | 197 | return fileLinks; 198 | } 199 | 200 | /** 201 | * Gets sidebar parameters from directory name. Arguments are given after double dash `--` and separated by comma. 202 | * - `nc` sets collapsable to `false`. 203 | * - `dX` sets sidebarDepth to `X`. 204 | * 205 | * @param {String} dirname - Name of the directory. 206 | * @returns {Object} - sidebar parameters. 207 | * @example 208 | * parseSidebarParameters("docs/api--nc,d2"); { collapsable: false, sidebarDepth: 2 } 209 | */ 210 | function parseSidebarParameters(dirname) { 211 | const index = dirname.lastIndexOf("--"); 212 | if (index === -1) { 213 | return {}; 214 | } 215 | 216 | const args = dirname.substring(index + 2).split(","); 217 | const parameters = {}; 218 | 219 | args.forEach(arg => { 220 | if (arg === "nc") { 221 | parameters.collapsable = false; 222 | } else if (arg.match(/d\d+/)) { 223 | parameters.sidebarDepth = Number(arg.substring(1)); 224 | } 225 | }); 226 | 227 | return parameters; 228 | } 229 | 230 | /** 231 | * Returns navbar configuration for given path. 232 | * @param {String} rootDir - Path of the directory to get navbar configuration for. 233 | * @param {Object} options - Options 234 | * @param {String} relativeDir - (Used internally for recursion) Relative directory to `rootDir` to get navconfig for. 235 | * @param {Number} currentNavLevel - (Used internally for recursion) Recursion level. 236 | * @returns {Array.} 237 | */ 238 | function nav(rootDir, { navPrefix, stripNumbers, skipEmptyNavbar }, relativeDir = "/", currentNavLevel = 1) { 239 | relativeDir = relativeDir.replace(/\\/g, "/"); 240 | const baseDir = join(rootDir, relativeDir); 241 | const childrenDirs = getDirectories(baseDir).filter(subDir => subDir.startsWith(navPrefix)); 242 | const options = { navPrefix, stripNumbers, skipEmptyNavbar }; 243 | let result; 244 | 245 | if (currentNavLevel > 1 && childrenDirs.length === 0) { 246 | if (!hasReadme(baseDir)) { 247 | if (skipEmptyNavbar) { 248 | return; 249 | } else { 250 | throw new Error( 251 | `README.md file cannot be found in ${baseDir}. VuePress would return 404 for that NavBar link.` 252 | ); 253 | } 254 | } 255 | result = { text: getName(baseDir, { stripNumbers, navPrefix }), link: relativeDir + "/" }; 256 | } else if (childrenDirs.length > 0) { 257 | const items = childrenDirs 258 | .map(subDir => nav(rootDir, options, join(relativeDir, subDir), currentNavLevel + 1)) 259 | .filter(Boolean); 260 | result = currentNavLevel === 1 ? items : { text: getName(baseDir, { stripNumbers, navPrefix }), items }; 261 | } 262 | 263 | return result; 264 | } 265 | 266 | /** 267 | * Returns multiple sidebars for given directory. 268 | * @param {String} rootDir - Directory to get navbars for. 269 | * @param {Object} nav - Navigation configuration (Used for calculating sidebars' roots.) 270 | * @param {Object} options - Options 271 | * @param {Number} currentLevel - Recursion level. 272 | * @returns {Object} - Multiple navbars. 273 | */ 274 | function multiSide(rootDir, nav, options, currentLevel = 1) { 275 | const sideBar = {}; 276 | 277 | nav.forEach(navItem => { 278 | if (navItem.link) { 279 | sideBar[navItem.link] = side(join(rootDir, navItem.link), options); 280 | } else { 281 | Object.assign(sideBar, multiSide(rootDir, navItem.items, options), currentLevel + 1); 282 | } 283 | }); 284 | 285 | if (options.skipEmptySidebar) { 286 | Object.keys(sideBar).forEach(key => { 287 | if (sideBar[key].length === 0) { 288 | delete sideBar[key]; 289 | } 290 | }); 291 | } 292 | 293 | if (currentLevel === 1) { 294 | const fallBackSide = side(rootDir, options); 295 | const singleEmptyElement = fallBackSide.length === 1 && fallBackSide[0] === ''; // [''] 296 | 297 | if (!options.skipEmptySidebar || (fallBackSide.length > 0 && !singleEmptyElement)) { // If [''] is present at root level, vuepress does not render sidebar correctly. See `example-with-root-readme` test 298 | sideBar["/"] = side(rootDir, options); 299 | } 300 | } 301 | 302 | return sideBar; 303 | } 304 | 305 | /** 306 | * Returns `nav` and `sidebar` configuration for VuePress calculated using structrue of directory and files in given path. 307 | * @param {String} rootDirOrOptions - Directory to get configuration for or options. 308 | * @param {Object} options - Options 309 | * @returns {Object} - { nav: ..., sidebar: ... } configuration. 310 | */ 311 | function getConfig(rootDirOrOptions, options) { 312 | let rootDir = typeof rootDirOrOptions === "string" ? normalize(rootDirOrOptions) : join(dirname(parentModule()), '..'); 313 | rootDir = rootDir.endsWith(sep) ? rootDir.slice(0, -1) : rootDir; // Remove last / if exists. 314 | 315 | options = { 316 | stripNumbers: true, 317 | maxLevel: 2, 318 | navPrefix: "nav", 319 | skipEmptySidebar: true, 320 | skipEmptyNavbar: true, 321 | multipleSideBar: true, 322 | addReadMeToFirstGroup: true, 323 | mixDirectoriesAndFilesAlphabetically: true, 324 | pinyinNav: false, 325 | ...(options || (typeof rootDirOrOptions === "object" ? rootDirOrOptions : {})) 326 | }; 327 | 328 | const navItems = nav(rootDir, options); 329 | 330 | const result = { 331 | nav: navItems || [], 332 | sidebar: options.multipleSideBar && navItems ? multiSide(rootDir, navItems, options) : side(rootDir, options) 333 | }; 334 | 335 | if (options.pinyinNav && nav.length) { 336 | result.nav = transliteratePinyin(result.nav); 337 | result.sidebar = transliteratePinyinSiderbar(result.sidebar); 338 | } 339 | 340 | return result; 341 | } 342 | 343 | module.exports = getConfig; 344 | -------------------------------------------------------------------------------- /lib/index.test.js: -------------------------------------------------------------------------------- 1 | const getConfig = require("./index"); 2 | 3 | const configOf = (name, options = {}) => getConfig(`${__dirname}/../docs/example-${name}`, options); 4 | const expected = name => require(`${__dirname}/../docs/example-${name}.json`); 5 | 6 | describe("getConfig", () => { 7 | it("should create config.", () => { 8 | expect(configOf("standard")).toEqual(expected("standard")); 9 | }); 10 | 11 | it("should create config without adding readme to first group.", () => { 12 | expect(configOf("standard", { addReadMeToFirstGroup: false })).toEqual(expected("standard-no-readme-move")); 13 | }); 14 | 15 | it("should throw for missing README.md in Navbar.", () => { 16 | expect(() => configOf("missing-readme", { skipEmptyNavbar: false })).toThrow("README.md file cannot be found"); 17 | }); 18 | 19 | it("should skip Navbar item for missing README.md.", () => { 20 | expect(configOf("missing-readme")).toEqual(expected("missing-readme")); 21 | }); 22 | 23 | it("should not fail when a folder only has subdirectories.", () => { 24 | expect(configOf("deep-readme")).toEqual(expected("deep-readme")); 25 | }); 26 | 27 | it("should skip Navbar item for missing README.md.", () => { 28 | expect(configOf("no-navbar")).toEqual(expected("no-navbar")); 29 | }); 30 | 31 | it("translate chinese to pinyin.", async () => { 32 | expect(configOf("中文", { pinyinNav: true })).toEqual(expected("chinese")); 33 | }); 34 | 35 | it("sort file and folder by prefix.", async () => { 36 | expect(configOf("sort")).toEqual(expected("sort")); 37 | }); 38 | 39 | it("should create with README.md in root.", () => { 40 | expect(configOf("with-root-readme")).toEqual(expected("with-root-readme")); 41 | }); 42 | 43 | it("should filter based on meta data.", () => { 44 | expect(configOf("filter", { filter : (meta) => meta.draft !== true })).toEqual(expected("filter")); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuepress-bar", 3 | "version": "0.4.5", 4 | "description": "VuePress sidebar and navbar generator based on file and directory structure. Focus your documents, not sidebar or navbar.", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "/lib" 8 | ], 9 | "scripts": { 10 | "test": "jest", 11 | "release": "git checkout master && git pull origin master && git commit --amend --no-edit && git push --follow-tags origin master && git push --tags && npm publish", 12 | "prepublishOnly": "npm test" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ozum/vuepress-bar.git" 17 | }, 18 | "keywords": [ 19 | "vuepress", 20 | "sidebar", 21 | "navbar", 22 | "menu", 23 | "generator", 24 | "automatic" 25 | ], 26 | "author": "Özüm Eldoğan", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/ozum/vuepress-bar/issues" 30 | }, 31 | "homepage": "https://github.com/ozum/vuepress-bar#readme", 32 | "devDependencies": { 33 | "jest": "^26.6.3", 34 | "prettier": "^2.3.0" 35 | }, 36 | "dependencies": { 37 | "glob": "^7.1.7", 38 | "inflection": "^1.13.1", 39 | "lodash.escaperegexp": "^4.1.2", 40 | "lodash.sortby": "^4.7.0", 41 | "markdown-it": "^12.0.6", 42 | "markdown-it-meta": "0.0.1", 43 | "parent-module": "^2.0.0", 44 | "transliteration": "^2.1.8" 45 | } 46 | } 47 | --------------------------------------------------------------------------------