├── .browserslistrc
├── .gitattributes
├── .github
├── scripts
│ └── release.js
└── workflows
│ ├── release.yml
│ └── static.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENCE
├── README.md
├── dist
├── docsify-auto-headers.js
└── docsify-auto-headers.min.js
├── docs
├── .nojekyll
├── data
│ ├── 404.md
│ ├── cookies.md
│ ├── cover.md
│ ├── navbar.md
│ ├── policy.md
│ ├── style.min.css
│ └── terms.md
├── index.html
└── pages
│ ├── _page1.md
│ ├── _page2.md
│ ├── _page3.md
│ ├── _page4.md
│ └── _page5.md
├── package-lock.json
└── package.json
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # browsers we support
2 | last 4 versions
3 | > 0.5%
4 | ie >= 11
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 | dist/* linguist-vendored=true
4 | *.js linguist-vendored=true
5 | *.md linguist-detectable=false
6 | *.html linguist-detectable=false
7 | *.css linguist-detectable=false
8 |
--------------------------------------------------------------------------------
/.github/scripts/release.js:
--------------------------------------------------------------------------------
1 | const jsonfile = require('jsonfile');
2 | const fs = require('fs');
3 | const path = require('path');
4 |
5 | const packageFile = './package.json';
6 | const packageData = jsonfile.readFileSync(packageFile);
7 | const currentVersion = packageData.version;
8 | const authorName = packageData.author;
9 | const versionParts = currentVersion.split('.');
10 | let [major, minor, patch] = versionParts.map(Number);
11 | const incrementType = process.argv[2];
12 | switch (incrementType) {
13 | case '-minor':
14 | minor += 1;
15 | patch = 0;
16 | break;
17 | case '-patch':
18 | patch += 1;
19 | break;
20 | case '-major':
21 | major += 1;
22 | minor = 0;
23 | patch = 0;
24 | break;
25 | default:
26 | console.log('Invalid increment type. Please use -minor, -patch, or -major.');
27 | process.exit(1);
28 | }
29 | const newVersion = `${major}.${minor}.${patch}`;
30 | packageData.version = newVersion;
31 | jsonfile.writeFileSync(packageFile, packageData, { spaces: 2 });
32 |
33 | const filesToUpdate = [
34 | "./dist/docsify-auto-headers.js",
35 | "./dist/docsify-auto-headers.min.js"
36 | ];
37 |
38 | filesToUpdate.forEach(filePath => {
39 | const fileName = getFileName(filePath);
40 | const header = `/*! ${fileName} ${newVersion} | (c) ${authorName} */\n`;
41 | const fileContent = fs.readFileSync(filePath, 'utf8');
42 | const headerRegex = /^\/\*![\s\S]*?\*\//; // Regular expression to match the header comment
43 | const contentWithoutHeader = fileContent.replace(headerRegex, '');
44 | const updatedContent = header + contentWithoutHeader.trimStart();
45 | fs.writeFileSync(filePath, updatedContent, 'utf8');
46 | console.log(`Header added to ${filePath}.`);
47 | });
48 |
49 | console.log('Header added successfully to all files.');
50 |
51 | const changelogPath = './CHANGELOG.md';
52 | const changelogContent = generateChangelog(newVersion, incrementType);
53 | fs.writeFileSync(changelogPath, changelogContent, 'utf8');
54 | console.log('Changelog generated successfully.');
55 |
56 | function generateChangelog(version, incrementType) {
57 | const currentDate = new Date().toDateString();
58 | const changeDescription = getChangeDescription(incrementType);
59 |
60 | // Read the existing changelog content if it exists
61 | let existingChangelog = '';
62 | if (fs.existsSync(changelogPath)) {
63 | existingChangelog = fs.readFileSync(changelogPath, 'utf8');
64 | }
65 | const newChangelogEntry = `\n## ${version} - ${currentDate}\n\n${changeDescription}\n`;
66 | return newChangelogEntry + existingChangelog;
67 | }
68 |
69 | function getChangeDescription(incrementType) {
70 | switch (incrementType) {
71 | case '-minor':
72 | return '### Added\n\n- Add your change description here.';
73 | case '-patch':
74 | return '### Fixed\n\n- Fix your change description here.';
75 | case '-major':
76 | return '### Breaking Changes\n\n- Describe any breaking changes here.';
77 | default:
78 | return '';
79 | }
80 | }
81 |
82 | function getFileName(filePath) {
83 | const fileNameWithExtension = path.basename(filePath);
84 | const fileName = fileNameWithExtension.split('.')[0];
85 | return fileName;
86 | }
87 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | release:
4 | types: [published]
5 |
6 | jobs:
7 | tag:
8 | name: Add/update 'latest' tag
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout repository
12 | uses: actions/checkout@v2
13 | - name: Run latest-tag
14 | uses: EndBug/latest-tag@v1
15 | env:
16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 |
18 | publish-npm:
19 | name: Publish on NPM
20 | runs-on: ubuntu-latest
21 | needs: tag
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Set up Node.js for NPM
25 | uses: actions/setup-node@v1
26 | with:
27 | registry-url: 'https://registry.npmjs.org'
28 | - run: npm install
29 | - run: npm publish
30 | env:
31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
32 |
33 | publish-gpr:
34 | name: Publish on GPR
35 | runs-on: ubuntu-latest
36 | needs: tag
37 | steps:
38 | - uses: actions/checkout@v2
39 | - name: Set up Node.js for GPR
40 | uses: actions/setup-node@v1
41 | with:
42 | registry-url: 'https://npm.pkg.github.com/'
43 | scope: '@markbattistella'
44 | - run: npm install
45 | - run: npm publish
46 | env:
47 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/static.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["main"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | - name: Setup Pages
35 | uses: actions/configure-pages@v5
36 | - name: Upload artifact
37 | uses: actions/upload-pages-artifact@v3
38 | with:
39 | # Upload entire repository
40 | path: './docs'
41 | - name: Deploy to GitHub Pages
42 | id: deployment
43 | uses: actions/deploy-pages@v4
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .markdownlint.json
3 | .DS_Store
4 | *babel.js
5 | .gitattributes
6 | node_modules
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .markdownlint.json
3 | .github
4 | .gitattributes
5 | .DS_Store
6 | *babel.js
7 | docs
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## 5.0.1 - Tue Jun 11 2024
3 |
4 | ### Fixed
5 |
6 | - Documentation fixes.
7 |
8 | ## 5.0.0 - Tue Jun 11 2024
9 |
10 | ### Breaking Changes
11 |
12 | - Bumped to new major version
13 | - Fixing the documentation
14 | - Added new functionality for `@autoHeaders:` and `` signifiers
15 | - Allows numbering in sidebar and main body
16 | - Better handling of debugging
17 | - Numbering can use letters and not just numbers
18 | - Many more fatures and cleanups
19 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Mark Battistella
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # docsify.js auto-headers
4 |
5 |
6 |
7 | This plugin enhances your Docsify documentation by automatically generating numbered headers for your markdown files. It allows you to configure the header levels, numbering format, and inclusion in the sidebar. By utilising this plugin, you can easily manage and navigate through your documentation headers, ensuring consistency and improved readability.
8 |
9 | ## Demo pages
10 |
11 | | Page link | Description |
12 | |-|-|
13 | | [](pages/_page1) | The `autoHeader` of this page is: `@autoHeader:1`. Assuming the original configuration is used, the splitter is `.` and the levels are `H1`-`H6`. |
14 | | [](pages/_page2) | The `autoHeader` of this page is: ``. Assuming the original configuration is used, the splitter is `.` and the levels are `H1`-`H6`. |
15 | | [](pages/_page3) | The `autoHeader` of this page is: `@autoHeader:`. Assuming the original configuration is used, the splitter is `.` and the levels are `H1`-`H6`. |
16 | | [](pages/_page4) | The `autoHeader` of this page is: ``. Assuming the original configuration is used, the splitter is `.` and the levels are `H1`-`H6`. |
17 | | [](pages/_page5) | The `autoHeader` of this page is: ``. Assuming the original configuration is used, the splitter is `.` and the levels are `H1`-`H6`. |
18 |
19 | ## Installation
20 |
21 | !> Note: There are breaking changes in the configuration from `v4.x` to `v5.x`. Please take the time to read all the documentation before upgrading
22 |
23 | ### Update `index.html` file
24 |
25 | Assuming you have a working [docsify](https://docsify.js.org/) framework set up, it is easy to use the plugin.
26 |
27 | 1. Add one of the following script tags to your `index.html` via either CDN or downloading it and using it locally:
28 |
29 | ```html
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | ```
39 |
40 | 1. In docsify setup, configure the plugin:
41 |
42 | ```js
43 |
62 | ```
63 |
64 | ## Configuration
65 |
66 | There are several options available for the docsify-auto-headers plugin:
67 |
68 | | Setting | Type | Options |
69 | |-------------|---------|-------------------------------------|
70 | | `separator` | String | e.g., `.`, `-`, `)` |
71 | | `sidebar` | Boolean | `true` or `false` |
72 | | `levels` | Number | `1` to `6` |
73 | | | Object | `{ start: Number, finish: Number }` |
74 | | `debug` | Boolean | `true` or `false` |
75 |
76 | ## Usage
77 |
78 | The plugin can be configured to apply scoped heading counts in either the sidebar or the main content, depending on your setup.
79 |
80 | ### Adding the signifier
81 |
82 | When you want to use the heading numbering on a markdown file you can add either of the following signifiers to the **first line** of the document:
83 |
84 | ```md
85 | @autoHeader:
86 | ```
87 |
88 | ```md
89 |
90 | ```
91 |
92 | After the colon (`:`) you can add in the number that will begin for heading level 1 (`H1`).
93 |
94 | ### Sidebar
95 |
96 | If the `sidebar` option is enabled, the headers will be included in the sidebar and processed before rendering the markdown.
97 |
98 | ### Main Content
99 |
100 | If the `sidebar` option is disabled, the headers will be processed and applied directly to the HTML after rendering.
101 |
102 | ## Contributing
103 |
104 | 1. Clone the repo: `git clone https://github.com/markbattistella/docsify-auto-headers.git`
105 |
106 | 1. Create your feature branch: `git checkout -b my-feature`
107 |
108 | 1. Commit your changes: `git commit -am 'Add some feature'`
109 |
110 | 1. `Push` to the branch: `git push origin my-new-feature`
111 |
112 | 1. Submit the `pull` request
113 |
--------------------------------------------------------------------------------
/dist/docsify-auto-headers.js:
--------------------------------------------------------------------------------
1 | /*! docsify-auto-headers 5.0.1 | (c) Mark Battistella */
2 | ; (() => {
3 |
4 | 'use strict';
5 |
6 | /**
7 | * Default settings for the Docsify Auto Headers plugin.
8 | * @type {Object}
9 | * @property {string} separator - The separator for header numbering (e.g., '.', '-', ')').
10 | * @property {boolean} sidebar - Boolean indicating if headers should be added to the sidebar.
11 | * @property {number|Object} levels - Number of header levels to include (1 to 6) or an object with start and finish properties.
12 | * @property {boolean} debug - Boolean to enable or disable debug messages.
13 | */
14 | const docsifyAutoHeadersDefaults = {
15 |
16 | // Separator for header numbering (e.g., '.', '-', ')')
17 | separator: '.',
18 |
19 | // Boolean indicating if headers should be added to the sidebar
20 | sidebar: false,
21 |
22 | // Number of header levels to include (1 to 6) or an object with start and finish properties
23 | levels: 6,
24 |
25 | // Boolean to enable or disable debug messages
26 | debug: false
27 | };
28 |
29 | /**
30 | * Main function for the Docsify Auto Headers plugin.
31 | * @param {Object} hook - Docsify hook object.
32 | * @param {Object} vm - Docsify virtual machine object.
33 | */
34 | const autoHeaders = (hook, vm) => {
35 |
36 | /**
37 | * Predefined error messages for invalid configurations.
38 | * @type {Object}
39 | * @property {string} invalidConfiguration - General error message for invalid configuration.
40 | * @property {string} invalidConfigurationSidebar - Error message for invalid sidebar configuration.
41 | * @property {string} invalidConfigurationLevels - Error message for invalid levels configuration.
42 | * @property {string} nonNumericHeadingLevel - Error message when levels start or finish values are not numbers.
43 | * @property {string} badNumericOrderHeadingLevel - Error message when start value is greater than finish value.
44 | * @property {string} outOfRangeHeadingLevel - Error message when start or finish values are out of range (1-6).
45 | * @property {string} moreThanSixElements - Error message when more than 6 elements are found in the signifier.
46 | * @property {string} invalidParsedElements - Error message when elements in the signifier are not purely numeric or alphabetic.
47 | * @property {string} signifierNotFound - Error message when the auto header signifier is missing in the markdown file.
48 | */
49 | const errorMessage = {
50 | invalidConfiguration:
51 | 'Configuration settings are not set correctly. Please review the autoHeaders parameters and documentation.',
52 | invalidConfigurationSidebar:
53 | 'The sidebar setting for autoHeaders only accepts a boolean of true or false. Please check you\'ve entered this data correctly.',
54 | invalidConfigurationLevels:
55 | 'The levels settings for autoHeaders only accepts a number from 1-6 or an object with the start and finish options. Please check you\'ve entered this data correctly.',
56 | nonNumericHeadingLevel:
57 | 'The levels setting has been configured with a start and finish option. However, the values for one of these is not a number. Please check you\'ve entered this data correctly.',
58 | badNumericOrderHeadingLevel:
59 | 'The levels setting has been configured with a start and finish option. However, the start value is greater than the finish. Please check you\'ve entered this data correctly.',
60 | outOfRangeHeadingLevel:
61 | 'The levels setting has been configured with a start and finish option. However, the values for one of these is not from 1-6. Please check you\'ve entered this data correctly.',
62 | moreThanSixElements:
63 | 'The elements found in the signifier have equated to more than 6 headings. Please check the configuration of your markdown that you have no more than 6 numbers',
64 | invalidParsedElements:
65 | 'The elements found in the signifier are not numbers only or alphabet only. Please check the configuration of your markdown that all the items are numeric or alphabetic.',
66 | signifierNotFound:
67 | 'The current markdown file is missing the @autoHeader: or signifier',
68 | };
69 |
70 | /**
71 | * Boolean flag indicating whether the processing should continue.
72 | * @type {boolean}
73 | */
74 | let shouldContinue = true;
75 |
76 | /**
77 | * Logs an error message if a configuration is invalid.
78 | * @param {boolean} shouldShow - Boolean indicating if the error message should be shown.
79 | * @param {string} message - The error message to log.
80 | * @returns {null} Always returns null after logging the error.
81 | */
82 | const logErrorMessage = (shouldShow, message) => {
83 | if (shouldShow) {
84 | console.warn(`Docsify Auto Headers:\n>> ${message}`);
85 | }
86 | shouldContinue = false;
87 | return null;
88 | };
89 |
90 | /**
91 | * Sets the default options for the plugin by merging user-defined options with defaults.
92 | * @param {Object} options - User-defined options to override the default settings.
93 | * @param {string} options.separator - The separator for header numbering.
94 | * @param {number|Object} options.levels - Number of header levels to include or an object with start and finish properties.
95 | * @param {boolean} options.sidebar - Boolean indicating if headers should be added to the sidebar.
96 | * @param {boolean} options.debug - Boolean to enable or disable debug messages.
97 | * @returns {Object|null} The final options object or null if there is an invalid configuration.
98 | */
99 | const setDefaultOptions = (options) => {
100 | if (!options.separator || options.levels === undefined) {
101 | return logErrorMessage(options.debug, errorMessage.invalidConfiguration);
102 | }
103 |
104 | // Map user-friendly separator names to actual separator characters
105 | const separatorMap = {
106 | 'decimal': '.',
107 | 'dot': '.',
108 | 'dash': '-',
109 | 'hyphen': '-',
110 | 'bracket': ')',
111 | 'parenthesis': ')'
112 | };
113 | // Determine the separator to use
114 | const separator = separatorMap[options.separator] || options.separator;
115 |
116 | // Set the levels, defaulting to 6 if not provided
117 | const levels = options.levels || 6;
118 |
119 | // Ensure sidebar and debug options are booleans
120 | const sidebar = !!options.sidebar;
121 |
122 | const debug = !!options.debug;
123 |
124 | return { separator, levels, sidebar, debug };
125 | };
126 |
127 | /**
128 | * Validates and retrieves the sidebar setting.
129 | * @param {boolean} input - The sidebar setting input.
130 | * @param {Object} options - The current plugin options.
131 | * @param {boolean} options.debug - Boolean to enable or disable debug messages.
132 | * @returns {boolean|null} The validated sidebar setting or null if the input is invalid.
133 | */
134 | const validateThenGetSidebar = (input, options) => {
135 | if (typeof input !== 'boolean') {
136 | return logErrorMessage(options.debug, errorMessage.invalidConfigurationSidebar);
137 | }
138 | return input;
139 | };
140 |
141 | /**
142 | * Validates and retrieves the heading range setting.
143 | * @param {number|Object} input - The levels setting input, which can be a number or an object with start and finish properties.
144 | * @param {Object} options - The current plugin options.
145 | * @param {boolean} options.debug - Boolean to enable or disable debug messages.
146 | * @returns {Object|null} The validated heading range configuration or null if the input is invalid.
147 | */
148 | const validateThenGetHeadingRange = (input, options) => {
149 | if (typeof input !== 'number' && (typeof input !== 'object' || input === null)) {
150 | return logErrorMessage(options.debug, errorMessage.invalidConfigurationLevels);
151 | }
152 |
153 | // Helper function to check if a value is within a specified range
154 | const isInRange = (value, min, max) => value >= min && value <= max;
155 | let start, finish;
156 |
157 | if (typeof input === 'number') {
158 | start = 1;
159 | finish = input;
160 | } else if (typeof input === 'object') {
161 | ({ start, finish } = input);
162 | if (typeof start !== 'number' || typeof finish !== 'number') {
163 | return logErrorMessage(options.debug, errorMessage.nonNumericHeadingLevel);
164 | }
165 | if (start > finish) {
166 | return logErrorMessage(options.debug, errorMessage.badNumericOrderHeadingLevel);
167 | }
168 | }
169 |
170 | if (!isInRange(start, 1, 6) || !isInRange(finish, 1, 6)) {
171 | return logErrorMessage(options.debug, errorMessage.outOfRangeHeadingLevel);
172 | }
173 |
174 | const headings = {};
175 | for (let i = 1; i <= 6; i++) {
176 | headings[`h${i}`] = { inScope: isInRange(i, start, finish) };
177 | }
178 | return headings;
179 | };
180 |
181 | /**
182 | * Converts a number to a header string based on the type (numeric or alphabetic).
183 | * @param {number} num - The number to convert.
184 | * @param {string} type - The type of conversion ("numeric", "alphabetic-lower", "alphabetic-upper").
185 | * @returns {string} The converted header string.
186 | */
187 | const numberToHeader = (num, type) => {
188 | if (type === "numeric") {
189 | return num + ""; // Convert number to string
190 | } else {
191 | num--; // Adjust the number for zero-based index
192 | let result = '';
193 | while (num >= 0) {
194 | let remainder = num % 26;
195 | result = String.fromCharCode(65 + remainder) + result;
196 | num = Math.floor(num / 26) - 1;
197 | }
198 |
199 | // Convert result to lowercase if type is "alphabetic-lower"
200 | return (type === "alphabetic-lower") ? result.toLowerCase() : result;
201 | }
202 | };
203 |
204 | /**
205 | * Converts a header string to a number based on the type.
206 | * @param {string} header - The header string to convert.
207 | * @param {string} type - The type of conversion ("numeric", "alphabetic-lower", "alphabetic-upper").
208 | * @returns {number} The converted number.
209 | */
210 | const headerToNumber = (header, type) => {
211 | if (type === "numeric") {
212 | return parseInt(header, 10); // Convert header string to number
213 | } else {
214 | header = header.toUpperCase(); // Convert header to uppercase for alphabetic conversion
215 | let result = 0;
216 | for (let i = 0; i < header.length; i++) {
217 | result *= 26;
218 | result += header.charCodeAt(i) - 65 + 1;
219 | }
220 | return result;
221 | }
222 | };
223 |
224 | /**
225 | * Parses the starting values for headers from the signifier.
226 | * @param {string} headerNumbers - The header numbers string.
227 | * @param {Object} options - The current plugin options.
228 | * @param {string} options.separator - The separator for header numbering.
229 | * @param {boolean} options.debug - Boolean to enable or disable debug messages.
230 | * @returns {Array