├── .gitattributes
├── .github
├── FUNDING.yml
└── workflows
│ └── test.yml
├── .gitignore
├── .markdownlint.json
├── .prettierrc.json
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── .nojekyll
├── assets
│ ├── css
│ │ └── main.css
│ └── img
│ │ ├── github.svg
│ │ ├── npm.svg
│ │ └── twitter.svg
├── index.html
├── index.md
└── sidebar.md
├── eslint.config.js
├── package-lock.json
├── package.json
├── postcss.config.cjs
├── prettierignore
├── rollup.config.js
├── screenshot.jpg
├── server.js
└── src
├── css
└── vars.css
├── js
└── index.js
└── scss
└── style.scss
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: jhildenbiddle
2 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: 'Build'
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | name: Build (${{ matrix.os }})
8 | runs-on: ${{ matrix.os }}
9 | strategy:
10 | matrix:
11 | os: [macos-latest, ubuntu-latest, windows-latest]
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Install
17 | run: npm ci
18 |
19 | - name: Lint
20 | run: npm run lint
21 |
22 | - name: Build
23 | run: npm run build
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Folders
2 | dist
3 | node_modules
4 |
5 | # Files
6 | *.log
7 |
8 | # OS
9 | ._*
10 | .cache
11 | .DS_Store
12 |
--------------------------------------------------------------------------------
/.markdownlint.json:
--------------------------------------------------------------------------------
1 | {
2 | "default": true,
3 | "MD001": false,
4 | "MD004": { "style": "consistent" },
5 | "MD013": false,
6 | "MD023": false,
7 | "MD024": false,
8 | "MD033": false,
9 | "MD036": false
10 | }
11 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "printWidth": 100,
4 | "singleQuote": true,
5 | "trailingComma": "none"
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": ["Codacy", "jhildenbiddle", "themeable"]
3 | }
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## 1.6.3
4 |
5 | _2024-04-24_
6 |
7 | - Fix tab heading anchors when using history routerMode (#55)
8 |
9 | ## 1.6.2
10 |
11 | _2024-02-29_
12 |
13 | - Fix tab selection on reload (#51)
14 |
15 | ## 1.6.1
16 |
17 | _2024-02-06_
18 |
19 | - Fix GitHub workflow badge
20 |
21 | ## 1.6.0
22 |
23 | _2022-09-11_
24 |
25 | - Add support for nested tabs (#5)
26 | - Fix tab content margin of first & last element
27 | - Update sessionStorage keys to include namespace
28 |
29 | ## 1.5.4
30 |
31 | _2022-09-10_
32 |
33 | - Fix inactive tab content flicker on tab change (#27)
34 | - Fix tab parsing with compressed HTML (#45)
35 |
36 | ## 1.5.3
37 |
38 | _2022-07-29_
39 |
40 | - Fix plugin insertion point (fix for docsify-mustache) (#44)
41 | - Update dependencies
42 |
43 | ## 1.5.2
44 |
45 | _2021-09-02_
46 |
47 | - Fix code quality badges
48 | - Add GitHub CI
49 |
50 | ## 1.5.1
51 |
52 | _2021-09-02_
53 |
54 | - Fix setting active tab from anchor with unicode (#41)
55 |
56 | ## 1.5.0
57 |
58 | _2021-04-27_
59 |
60 | - Add support for markdown and HTML in tab labels (#38)
61 | - Update custom style examples in documentation
62 |
63 | ## 1.4.4
64 |
65 | _2020-11-05_
66 |
67 | - Fix tab comments with code block rendering (#29)
68 |
69 | ## 1.4.3
70 |
71 | _2020-06-25_
72 |
73 | - Fix handling of regex replacement patterns in markdown (#26)
74 |
75 | ## 1.4.2
76 |
77 | _2020-05-11_
78 |
79 | - Fix error when no active tab set in URL (#23)
80 |
81 | ## 1.4.1
82 |
83 | _2020-05-09_
84 |
85 | - Fix handling of URL encoded anchor IDs (#22)
86 |
87 | ## 1.4.0
88 |
89 | _2020-04-12_
90 |
91 | - Add tab selection on hash change
92 | - Fix tab selection based on anchor ID in IE
93 |
94 | ## 1.3.0
95 |
96 | _2020-04-11_
97 |
98 | - Add tab selection based on anchor ID in URL (#20)
99 | - Fix tab content container padding and first/last child margins
100 |
101 | ## 1.2.0
102 |
103 | _2020-02-11_
104 |
105 | - Update sync behavior to allow synced tab selections across pages (#17)
106 | - Fix rendering of tabset when using tab comments (#16)
107 | - Remove Sentry.io
108 |
109 | ## 1.1.2
110 |
111 | _2019-01-08_
112 |
113 | - Add Sentry.io
114 | - Update dependencies
115 | - Update CDN links (switch from unpkg to jsdelivr)
116 | - Fix rollup plugin configuration
117 | - Fix website landscape display on notched devices
118 |
119 | ## 1.1.0
120 |
121 | _2018-11-10_
122 |
123 | - Add support for tabsets nested within lists
124 |
125 | ## 1.0.6
126 |
127 | _2018-11-01_
128 |
129 | - Fix rendering issue caused by marked package upgrade in docsify 4.8.0
130 |
131 | ## 1.0.5
132 |
133 | _2018-10-11_
134 |
135 | - Fix bug that prevented rendering of tabs from markdown with Windows-style
136 | line terminators
137 |
138 | ## 1.0.0 - 1.0.4
139 |
140 | _2018-10-09_
141 |
142 | - Initial release
143 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 John Hildenbiddle
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 | # docsify-tabs
2 |
3 | [](https://www.npmjs.com/package/docsify-tabs)
4 | [](https://github.com/jhildenbiddle/docsify-tabs/actions?query=branch%3Amaster+)
5 | [](https://app.codacy.com/gh/jhildenbiddle/docsify-tabs/dashboard)
6 | [](https://github.com/jhildenbiddle/docsify-tabs/blob/master/LICENSE)
7 | [](https://www.jsdelivr.com/package/npm/docsify-tabs)
8 | [](https://github.com/sponsors/jhildenbiddle)
9 |
10 | A [docsify.js](https://docsify.js.org) plugin for rendering tabbed content from markdown.
11 |
12 | - [Documentation & Demos](https://jhildenbiddle.github.io/docsify-tabs)
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | > 💡 Like this plugin? Check out [docsify-themeable](https://jhildenbiddle.github.io/docsify-themeable) for your site theme, [docsify-plugin-ethicalads](https://jhildenbiddle.github.io/docsify-plugin-ethicalads/) for EthicalAds integration, and [docsify-plugin-runkit](https://jhildenbiddle.github.io/docsify-plugin-runkit/) for live JavaScript REPLs!
21 |
22 | ## Features
23 |
24 | - Generate tabbed content using unobtrusive markup
25 | - Persist tab selections on refresh/revisit
26 | - Sync tab selection for tabs with matching labels
27 | - Nest tab sets within tab sets
28 | - Style tabs using "classic" or "material" tab theme
29 | - Customize styles without complex CSS using CSS custom properties
30 | - Compatible with [docsify-themeable](https://jhildenbiddle.github.io/docsify-themeable/) themes
31 |
32 | **Limitations**
33 |
34 | - Tabs wraps when their combined width exceeds the content area width
35 |
36 | ## Installation & Options
37 |
38 | See the [documentation site](https://jhildenbiddle.github.io/docsify-tabs) for details.
39 |
40 | ## Sponsorship
41 |
42 | A [sponsorship](https://github.com/sponsors/jhildenbiddle) is more than just a way to show appreciation for the open-source authors and projects we rely on; it can be the spark that ignites the next big idea, the inspiration to create something new, and the motivation to share so that others may benefit.
43 |
44 | If you benefit from this project, please consider lending your support and encouraging future efforts by [becoming a sponsor](https://github.com/sponsors/jhildenbiddle).
45 |
46 | Thank you! 🙏🏻
47 |
48 | ## Contact & Support
49 |
50 | - Follow 👨🏻💻 **@jhildenbiddle** on [Twitter](https://twitter.com/jhildenbiddle) and [GitHub](https://github.com/jhildenbiddle) for announcements
51 | - Create a 💬 [GitHub issue](https://github.com/jhildenbiddle/docsify-tabs/issues) for bug reports, feature requests, or questions
52 | - Add a ⭐️ [star on GitHub](https://github.com/jhildenbiddle/docsify-tabs) and 🐦 [tweet](https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Fjhildenbiddle%2Fdocsify-tabs&hashtags=css,developers,frontend,javascript) to promote the project
53 | - Become a 💖 [sponsor](https://github.com/sponsors/jhildenbiddle) to support the project and future efforts
54 |
55 | ## License
56 |
57 | This project is licensed under the MIT License. See the [LICENSE](https://github.com/jhildenbiddle/docsify-tabs/blob/master/LICENSE) for details.
58 |
59 | Copyright (c) John Hildenbiddle ([@jhildenbiddle](https://twitter.com/jhildenbiddle))
60 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhildenbiddle/docsify-tabs/ecc0184c042627961cb54882f1a43adecff0577e/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/assets/css/main.css:
--------------------------------------------------------------------------------
1 | /* Required for browsers w/o shadow DOM support */
2 | iframe[src*='buttons.github.io'] {
3 | margin: 0;
4 | }
5 |
6 | .markdown-section strong code {
7 | font-weight: normal;
8 | }
9 |
10 | /* Theme Toggles */
11 | label[data-class-target='label + .docsify-tabs'] {
12 | margin-right: 0.8em;
13 | }
14 |
15 | /* Custom Styles */
16 | /* ========================================================================== */
17 | /* Badges */
18 | .tab-badge,
19 | [data-tab='badge']:after {
20 | position: absolute;
21 | top: 0;
22 | right: 0;
23 | transform: translate(35%, -45%);
24 | padding: 0.25em 0.35em;
25 | border-radius: 3px;
26 | background: red;
27 | color: white;
28 | font-family: sans-serif;
29 | font-size: 11px;
30 | font-weight: bold;
31 | }
32 |
33 | [data-tab='badge']:after {
34 | content: 'New!';
35 | }
36 |
37 | /* Active State */
38 | .docsify-tabs__tab--active[data-tab='active state'] {
39 | box-shadow: none;
40 | background: #13547a;
41 | color: white;
42 | }
43 | .docsify-tabs__content[data-tab-content='active state'] {
44 | background-image: linear-gradient(0deg, #80d0c7 0%, #13547a 100%);
45 | }
46 | .docsify-tabs__content[data-tab-content='active state'] p strong {
47 | color: white;
48 | }
49 |
50 | /* CodePen */
51 | [data-tab-content='codepen'] .cp_embed_wrapper {
52 | position: relative;
53 | top: calc(0px - var(--docsifytabs-content-padding));
54 | left: calc(0px - var(--docsifytabs-content-padding));
55 | width: calc(100% + calc(var(--docsifytabs-content-padding) * 2));
56 | margin-bottom: calc(0px - var(--docsifytabs-content-padding));
57 | }
58 |
59 | [data-tab-content='codepen'] .cp_embed_wrapper > * {
60 | margin: 0;
61 | }
62 |
--------------------------------------------------------------------------------
/docs/assets/img/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/assets/img/npm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/assets/img/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
14 |
15 | docsify-tabs - A docsify.js plugin for rendering tabbed content from markdown
16 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
44 |
45 |
46 |
47 |
48 |
49 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # docsify-tabs
2 |
3 | [](https://www.npmjs.com/package/docsify-tabs)
4 | [](https://github.com/jhildenbiddle/docsify-tabs/actions?query=branch%3Amaster+)
5 | [](https://app.codacy.com/gh/jhildenbiddle/docsify-tabs/dashboard)
6 | [](https://github.com/jhildenbiddle/docsify-tabs/blob/master/LICENSE)
7 | [](https://www.jsdelivr.com/package/npm/docsify-tabs)
8 | [](https://github.com/sponsors/jhildenbiddle)
9 | [](https://github.com/jhildenbiddle/docsify-tabs)
10 | [](https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Fjhildenbiddle%2Fdocsify-tabs&hashtags=docsify,developers,frontend,plugin)
11 |
12 | A [docsify.js](https://docsify.js.org) plugin for rendering tabbed content from markdown.
13 |
14 | ## Demo
15 |
16 | A basic tab set using the default [options](#options).
17 |
18 |
19 |
20 | #### **Tab A**
21 |
22 | ### Heading for Tab A {docsify-ignore}
23 |
24 | This is some text.
25 |
26 | - List item A-1
27 | - List item A-2
28 |
29 | ```js
30 | // JavaScript
31 | function add(a, b) {
32 | return a + b;
33 | }
34 | ```
35 |
36 |
37 |
38 | #### **Nested Tab 1**
39 |
40 | Life is what happens when you're busy making other plans.
41 |
42 | \- _John Lennon_
43 |
44 | #### **Nested Tab 2**
45 |
46 | The greatest glory in living lies not in never falling, but in rising every time we fall.
47 |
48 | \- _Nelson Mandela_
49 |
50 |
51 |
52 | #### **Tab B**
53 |
54 | ### Heading for Tab B {docsify-ignore}
55 |
56 | This is some text.
57 |
58 | - List item B-1
59 | - List item B-2
60 |
61 | ```css
62 | /* CSS */
63 | body {
64 | background: white;
65 | color: #222;
66 | }
67 | ```
68 |
69 | #### **Tab C**
70 |
71 | This is some text.
72 |
73 | - List item C-1
74 | - List item C-2
75 |
76 | ```html
77 |
78 | Heading
79 | This is a paragraph.
80 | ```
81 |
82 |
83 |
84 | ?> Like this plugin? Check out [docsify-themeable](https://jhildenbiddle.github.io/docsify-themeable) for your site theme, [docsify-plugin-ethicalads](https://jhildenbiddle.github.io/docsify-plugin-ethicalads/) for EthicalAds integration, and [docsify-plugin-runkit](https://jhildenbiddle.github.io/docsify-plugin-runkit/) for live JavaScript REPLs!
85 |
86 | ## Features
87 |
88 | - Generate tabbed content using unobtrusive markup
89 | - Persist tab selections on refresh/revisit
90 | - Sync tab selection for tabs with matching labels
91 | - Nest tab sets within tab sets
92 | - Style tabs using "classic" or "material" tab theme
93 | - Customize styles without complex CSS using CSS custom properties
94 | - Compatible with [docsify-themeable](https://jhildenbiddle.github.io/docsify-themeable/) themes
95 |
96 | **Limitations**
97 |
98 | - Tabs wraps when their combined width exceeds the content area width
99 |
100 | ## Installation
101 |
102 | 1. Add the docsify-tabs plugin to your `index.html` after docsify. The plugin is available on [jsdelivr](https://www.jsdelivr.com/package/npm/docsify-tabs) (below), [unpkg](https://unpkg.com/browse/docsify-tabs/), and other CDN services that auto-publish npm packages.
103 |
104 | ```html
105 |
106 |
107 |
108 |
109 |
110 | ```
111 |
112 | !> Note the `@` version number lock in the URLs above. This prevents breaking changes in future releases from affecting your project and is therefore the safest method of loading dependencies from a CDN. When a new major version is released, you will need to manually update your CDN URLs by changing the version number after the @ symbol.
113 |
114 | 1. Review the [Options](#options) section and configure as needed.
115 |
116 | ```javascript
117 | window.$docsify = {
118 | // ...
119 | tabs: {
120 | persist: true, // default
121 | sync: true, // default
122 | theme: 'classic', // default
123 | tabComments: true, // default
124 | tabHeadings: true // default
125 | }
126 | };
127 | ```
128 |
129 | 1. Review the [Customization](#customization) section and set theme properties as needed.
130 |
131 | ```html
132 |
138 | ```
139 |
140 | ## Usage
141 |
142 | 1. Define a tab set using `tabs:start` and `tabs:end` HTML comments.
143 |
144 | HTML comments are used to mark the start and end of a tab set. The use of HTML comments prevents tab-related markup from being displayed when markdown is rendered as HTML outside of your docsify site (e.g. GitHub, GitLab, etc).
145 |
146 | ```markdown
147 |
148 |
149 | ...
150 |
151 |
152 | ```
153 |
154 | 1. Define tabs within a tab set using heading + bold markdown.
155 |
156 | Heading text will be used as the tab label, and all proceeding content will be associated with that tab up to start of the next tab or a `tab:end` comment. The use of heading + bold markdown allows tabs to be defined using standard markdown and ensures that tab content is displayed with a heading when rendered outside of your docsify site (e.g. GitHub, GitLab, etc).
157 |
158 | ```markdown
159 |
160 |
161 | #### **English**
162 |
163 | Hello!
164 |
165 | #### **French**
166 |
167 | Bonjour!
168 |
169 | #### **Italian**
170 |
171 | Ciao!
172 |
173 |
174 | ```
175 |
176 | See [`options.tabHeadings`](#tabheadings) for details or [`options.tabComments`](#tabcomments) for an alternate method of defining tabs using HTML comments.
177 |
178 | 1. Voilà! A tab set is formed.
179 |
180 |
181 |
182 | #### **English**
183 |
184 | Hello!
185 |
186 | #### **French**
187 |
188 | Bonjour!
189 |
190 | #### **Italian**
191 |
192 | Ciao!
193 |
194 |
195 |
196 | ## Options
197 |
198 | Options are set within the [`window.$docsify`](https://docsify.js.org/#/configuration) configuration under the `tabs` key:
199 |
200 | ```html
201 |
213 | ```
214 |
215 | ### persist
216 |
217 | - Type: `boolean`
218 | - Default: `true`
219 |
220 | Determines if tab selections will be restored after a page refresh/revisit.
221 |
222 | **Configuration**
223 |
224 | ```javascript
225 | window.$docsify = {
226 | // ...
227 | tabs: {
228 | persist: true // default
229 | }
230 | };
231 | ```
232 |
233 | ### sync
234 |
235 | - Type: `boolean`
236 | - Default: `true`
237 |
238 | Determines if tab selections will be synced across tabs with matching labels.
239 |
240 | **Configuration**
241 |
242 | ```javascript
243 | window.$docsify = {
244 | // ...
245 | tabs: {
246 | sync: true // default
247 | }
248 | };
249 | ```
250 |
251 | **Demo**
252 |
253 |
254 |
255 | #### **macOS**
256 |
257 | Instructions for macOS...
258 |
259 | #### **Windows**
260 |
261 | Instructions for Windows...
262 |
263 | #### **Linux**
264 |
265 | Instructions for Linux...
266 |
267 |
268 |
269 |
270 |
271 | #### **macOS**
272 |
273 | More instructions for macOS...
274 |
275 | #### **Windows**
276 |
277 | More instructions for Windows...
278 |
279 | #### **Linux**
280 |
281 | More instructions for Linux...
282 |
283 |
284 |
285 | ### theme
286 |
287 | - Type: `string|boolean`
288 | - Accepts: `'classic'`, `'material'`, `false`
289 | - Default: `'classic'`
290 |
291 | Sets the tab theme. A value of `false` will indicate that no theme should be applied, which should be used when creating custom tab themes.
292 |
293 | **Configuration**
294 |
295 | ```javascript
296 | window.$docsify = {
297 | // ...
298 | tabs: {
299 | theme: 'classic' // default
300 | }
301 | };
302 | ```
303 |
304 | **Demo**
305 |
306 |
309 |
312 |
315 |
316 |
317 |
318 | #### **Tab A**
319 |
320 | This is some text.
321 |
322 | #### **Tab B**
323 |
324 | This is some more text.
325 |
326 | #### **Tab C**
327 |
328 | Yes, this is even more text.
329 |
330 |
331 |
332 | ### tabComments
333 |
334 | - Type: `boolean`
335 | - Default: `true`
336 |
337 | Determines if tabs within a tab set can be defined using tab comments.
338 |
339 | Note that defining tabs using HTML comments means tab content will not be labeled when rendered outside of your docsify site (e.g. GitHub, GitLab, etc). For this reason, defining tabs using [`options.tabHeadings`](#tabheadings) is recommended.
340 |
341 | **Configuration**
342 |
343 | ```javascript
344 | window.$docsify = {
345 | // ...
346 | tabs: {
347 | tabComments: true // default
348 | }
349 | };
350 | ```
351 |
352 | **Example**
353 |
354 | ```markdown
355 |
356 |
357 |
358 |
359 | Hello!
360 |
361 |
362 |
363 | Bonjour!
364 |
365 |
366 |
367 | Ciao!
368 |
369 |
370 | ```
371 |
372 | ### tabHeadings
373 |
374 | - Type: `boolean`
375 | - Default: `true`
376 |
377 | Determines if tabs within a tab set can be defined using heading + bold markdown.
378 |
379 | The use of heading + bold markdown allows tabs to be defined using standard markdown and ensures that tab content is displayed with a heading when rendered outside of your docsify site (e.g. GitHub, GitLab, etc). Heading levels 1-6 are supported (e.g. `#` - `######`) as are both asterisks (`**`) and underscores (`__`) for bold text via markdown.
380 |
381 | **Configuration**
382 |
383 | ```javascript
384 | window.$docsify = {
385 | // ...
386 | tabs: {
387 | tabHeadings: true // default
388 | }
389 | };
390 | ```
391 |
392 | **Example**
393 |
394 | ```markdown
395 |
396 |
397 | #### **English**
398 |
399 | Hello!
400 |
401 | #### **French**
402 |
403 | Bonjour!
404 |
405 | #### **Italian**
406 |
407 | Ciao!
408 |
409 |
410 | ```
411 |
412 | ## Customization
413 |
414 | ### Themes
415 |
416 | See [`options.theme`](#theme) for details on available themes.
417 |
418 | ### Theme Properties
419 |
420 | Theme properties allow you to customize tab styles without writing complex CSS. The following list contains the default theme values.
421 |
422 | [vars.css](https://raw.githubusercontent.com/jhildenbiddle/docsify-tabs/master/src/css/vars.css ':include :type:code')
423 |
424 | To set theme properties, add a `
433 | ```
434 |
435 | ### Custom Styles
436 |
437 | The easiest way to create custom tab styles is by using markdown and/or HTML in your tab label.
438 |
439 |
440 |
441 | #### **Bold**
442 |
443 | **Tab Markdown**
444 |
445 | ```markdown
446 |
447 |
448 | #### **Bold**
449 |
450 | ...
451 |
452 |
453 | ```
454 |
455 | #### **Italic**
456 |
457 | **Tab Markdown**
458 |
459 | ```markdown
460 |
461 |
462 | #### **Italic**
463 |
464 | ...
465 |
466 |
467 | ```
468 |
469 | #### **Red**
470 |
471 | **Tab Markdown**
472 |
473 | ```markdown
474 |
475 |
476 | #### **Red**
477 |
478 | ...
479 |
480 |
481 | ```
482 |
483 | #### **:smile:**
484 |
485 | **Tab Markdown**
486 |
487 | ```markdown
488 |
489 |
490 | #### **:smile:**
491 |
492 | ...
493 |
494 |
495 | ```
496 |
497 | #### **😀**
498 |
499 | **Tab Markdown**
500 |
501 | ```markdown
502 |
503 |
504 | #### **😀**
505 |
506 | ...
507 |
508 |
509 | ```
510 |
511 | #### **Badge New!**
512 |
513 | **Tab Markdown**
514 |
515 | ```markdown
516 |
517 |
518 | #### **Badge New!**
519 |
520 | ...
521 |
522 |
523 | ```
524 |
525 | #### CSS
526 |
527 | ```html
528 |
543 | ```
544 |
545 |
546 |
547 | More advanced styling can be applied by leveraging the CSS class names and data attributes associated with tab containers, toggles, labels, and content blocks. Consider the following tab markdown and the HTML output generated by docsify-tabs:
548 |
549 | ```markdown
550 |
551 |
552 | #### **My Tab**
553 |
554 | ...
555 |
556 |
557 | ```
558 |
559 | ```html
560 |
561 | ...
562 | ```
563 |
564 | When the tab is active, note the addition of the `docsify-tabs__tab--active` class:
565 |
566 | ```html
567 |
568 | ```
569 |
570 | **Examples**
571 |
572 |
573 |
574 | #### **Active State**
575 |
576 | **Markdown**
577 |
578 | ```markdown
579 |
580 |
581 | #### **Active State**
582 |
583 | ...
584 |
585 |
586 | ```
587 |
588 | **HTML Output**
589 |
590 | ```html
591 |
594 | ...
595 | ```
596 |
597 | **Custom CSS**
598 |
599 | ```css
600 | .docsify-tabs__tab--active[data-tab='active state'] {
601 | box-shadow: none;
602 | background: #13547a;
603 | color: white;
604 | }
605 | .docsify-tabs__content[data-tab-content='active state'] {
606 | background-image: linear-gradient(0deg, #80d0c7 0%, #13547a 100%);
607 | }
608 | .docsify-tabs__content[data-tab-content='active state'] p strong {
609 | color: white;
610 | }
611 | ```
612 |
613 | #### **CodePen**
614 |
615 |
622 |
623 | **Markdown**
624 |
625 | ```markdown
626 |
627 |
628 | #### **CodePen**
629 |
630 | CodePen Embed Code...
631 |
632 |
633 | ```
634 |
635 | **HTML Output**
636 |
637 | ```html
638 |
639 | ...
640 | ```
641 |
642 | **Custom CSS**
643 |
644 | ```css
645 | [data-tab-content='codepen'] .cp_embed_wrapper {
646 | position: relative;
647 | top: calc(0px - var(--docsifytabs-content-padding));
648 | left: calc(0px - var(--docsifytabs-content-padding));
649 | width: calc(100% + calc(var(--docsifytabs-content-padding) * 2));
650 | margin-bottom: calc(0px - var(--docsifytabs-content-padding));
651 | }
652 |
653 | [data-tab-content='codepen'] .cp_embed_wrapper > * {
654 | margin: 0;
655 | }
656 | ```
657 |
658 | #### **Badge**
659 |
660 | **Markdown**
661 |
662 | ```markdown
663 |
664 |
665 | #### **Badge**
666 |
667 | ...
668 |
669 |
670 | ```
671 |
672 | **Custom CSS**
673 |
674 | ```css
675 | [data-tab='badge']:after {
676 | content: 'New!';
677 | position: absolute;
678 | top: 0;
679 | right: 0;
680 | transform: translate(35%, -45%);
681 | padding: 0.25em 0.35em;
682 | border-radius: 3px;
683 | background: red;
684 | color: white;
685 | font-family: sans-serif;
686 | font-size: 11px;
687 | font-weight: bold;
688 | }
689 | ```
690 |
691 |
692 |
693 | ## Sponsorship
694 |
695 | A [sponsorship](https://github.com/sponsors/jhildenbiddle) is more than just a way to show appreciation for the open-source authors and projects we rely on; it can be the spark that ignites the next big idea, the inspiration to create something new, and the motivation to share so that others may benefit.
696 |
697 | If you benefit from this project, please consider lending your support and encouraging future efforts by [becoming a sponsor](https://github.com/sponsors/jhildenbiddle).
698 |
699 | Thank you! 🙏🏻
700 |
701 |
702 |
703 | ## Contact & Support
704 |
705 | - Follow 👨🏻💻 **@jhildenbiddle** on [Twitter](https://twitter.com/jhildenbiddle) and [GitHub](https://github.com/jhildenbiddle) for announcements
706 | - Create a 💬 [GitHub issue](https://github.com/jhildenbiddle/docsify-tabs/issues) for bug reports, feature requests, or questions
707 | - Add a ⭐️ [star on GitHub](https://github.com/jhildenbiddle/docsify-tabs) and 🐦 [tweet](https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Fjhildenbiddle%2Fdocsify-tabs&hashtags=css,developers,frontend,javascript) to promote the project
708 | - Become a 💖 [sponsor](https://github.com/sponsors/jhildenbiddle) to support the project and future efforts
709 |
710 | ## License
711 |
712 | This project is licensed under the [MIT license](https://github.com/jhildenbiddle/docsify-tabs/blob/master/LICENSE).
713 |
714 | Copyright (c) John Hildenbiddle ([@jhildenbiddle](https://twitter.com/jhildenbiddle))
715 |
--------------------------------------------------------------------------------
/docs/sidebar.md:
--------------------------------------------------------------------------------
1 | - [Documentation](/)
2 | - [Changelog](changelog)
3 | - **Links**
4 | - [GitHub](https://github.com/jhildenbiddle/docsify-tabs)
5 | - [NPM](https://www.npmjs.com/package/docsify-tabs)
6 | - [@jhildenbiddle](http://twitter.com/jhildenbiddle)
7 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import eslintConfigPrettier from 'eslint-config-prettier';
2 | import globals from 'globals';
3 | import js from '@eslint/js';
4 |
5 | export default [
6 | {
7 | ignores: ['dist']
8 | },
9 | js.configs.recommended,
10 | eslintConfigPrettier,
11 | {
12 | languageOptions: {
13 | globals: {
14 | ...globals.browser
15 | }
16 | },
17 | rules: {
18 | 'array-bracket-spacing': ['error', 'never'],
19 | 'array-callback-return': ['error'],
20 | 'block-scoped-var': ['error'],
21 | 'block-spacing': ['error', 'always'],
22 | curly: ['error'],
23 | 'dot-notation': ['error'],
24 | eqeqeq: ['error'],
25 | 'no-console': ['warn'],
26 | 'no-floating-decimal': ['error'],
27 | 'no-implicit-coercion': ['error'],
28 | 'no-implicit-globals': ['error'],
29 | 'no-loop-func': ['error'],
30 | 'no-return-assign': ['error'],
31 | 'no-template-curly-in-string': ['error'],
32 | 'no-unneeded-ternary': ['error'],
33 | 'no-unused-vars': ['error', { args: 'none' }],
34 | 'no-useless-computed-key': ['error'],
35 | 'no-useless-return': ['error'],
36 | 'no-var': ['error'],
37 | 'prefer-const': ['error'],
38 | quotes: ['error', 'single'],
39 | semi: ['error', 'always']
40 | }
41 | }
42 | ];
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docsify-tabs",
3 | "version": "1.6.3",
4 | "description": "A docsify.js plugin for rendering tabbed content from markdown",
5 | "author": "John Hildenbiddle",
6 | "license": "MIT",
7 | "homepage": "https://jhildenbiddle.github.io/docsify-tabs/",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://jhildenbiddle@github.com/jhildenbiddle/docsify-tabs.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/jhildenbiddle/docsify-tabs/issues"
14 | },
15 | "keywords": [
16 | "docs",
17 | "docsify",
18 | "docsify.js",
19 | "documentation",
20 | "generator",
21 | "javascript",
22 | "js",
23 | "markdown",
24 | "md",
25 | "plugin",
26 | "tab",
27 | "tabs"
28 | ],
29 | "browserslist": [
30 | "ie >= 11"
31 | ],
32 | "files": [
33 | "dist"
34 | ],
35 | "main": "dist/docsify-tabs.js",
36 | "unpkg": "dist/docsify-tabs.min.js",
37 | "type": "module",
38 | "scripts": {
39 | "build": "rollup -c",
40 | "clean": "rimraf --glob dist/*",
41 | "escheck": "es-check es5 'dist/**/*.js'",
42 | "lint": "prettier . --check && eslint . && markdownlint *.md docs/*.md --ignore node_modules",
43 | "lint:fix": "prettier . --write && eslint . --fix",
44 | "prepare": "run-s clean build",
45 | "serve": "node server.js",
46 | "start": "run-p watch serve",
47 | "test": "echo \"Error: no test specified\" && exit 1",
48 | "watch": "run-p 'build -- -w'",
49 | "version": "run-s prepare lint escheck"
50 | },
51 | "devDependencies": {
52 | "@babel/core": "^7.13.16",
53 | "@babel/preset-env": "^7.13.15",
54 | "@eslint/js": "^9.1.1",
55 | "@rollup/plugin-babel": "^6.0.4",
56 | "@rollup/plugin-commonjs": "^25.0.7",
57 | "@rollup/plugin-json": "^6.1.0",
58 | "@rollup/plugin-node-resolve": "^15.2.3",
59 | "@rollup/plugin-terser": "^0.4.4",
60 | "autoprefixer": "^10.2.5",
61 | "browser-sync": "^3.0.2",
62 | "compression": "^1.7.4",
63 | "es-check": "^7.0.0",
64 | "eslint": "^9.1.1",
65 | "eslint-config-prettier": "^9.1.0",
66 | "globals": "^15.0.0",
67 | "markdownlint-cli": "^0.39.0",
68 | "mergician": "^2.0.0",
69 | "npm-run-all": "^4.1.5",
70 | "postcss": "^8.3.6",
71 | "postcss-custom-properties": "^13.3.5",
72 | "postcss-flexbugs-fixes": "^5.0.2",
73 | "postcss-import": "^16.0.1",
74 | "prettier": "^3.2.5",
75 | "rimraf": "^5.0.5",
76 | "rollup": "^4.12.0",
77 | "rollup-plugin-postcss": "^4.0.1",
78 | "sass": "^1.49.11"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | map: false,
3 | plugins: [
4 | require('postcss-import')(),
5 | require('autoprefixer')(),
6 | require('postcss-custom-properties')(),
7 | require('postcss-flexbugs-fixes')()
8 | ]
9 | };
10 |
--------------------------------------------------------------------------------
/prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { babel } from '@rollup/plugin-babel';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import fs from 'node:fs';
4 | import json from '@rollup/plugin-json';
5 | import { mergician } from 'mergician';
6 | import path from 'node:path';
7 | import postcss from 'rollup-plugin-postcss';
8 | import nodeResolve from '@rollup/plugin-node-resolve';
9 | import terser from '@rollup/plugin-terser';
10 |
11 | const pkg = JSON.parse(
12 | fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8') // prettier-ignore
13 | );
14 |
15 | // Settings
16 | // =============================================================================
17 | // Copyright
18 | const currentYear = new Date().getFullYear();
19 | const releaseYear = 2018;
20 |
21 | // Output
22 | const entryFile = path.resolve('.', 'src', 'js', 'index.js');
23 | const outputFile = path.resolve('.', 'dist', `${pkg.name}.js`);
24 |
25 | // Banner
26 | const bannerData = [
27 | `${pkg.name}`,
28 | `v${pkg.version}`,
29 | `${pkg.homepage}`,
30 | `(c) ${releaseYear}${currentYear === releaseYear ? '' : '-' + currentYear} ${pkg.author}`,
31 | `${pkg.license} license`
32 | ];
33 |
34 | // Plugins
35 | const pluginSettings = {
36 | babel: {
37 | babelrc: false,
38 | exclude: ['node_modules/**'],
39 | babelHelpers: 'bundled',
40 | presets: [
41 | [
42 | '@babel/preset-env',
43 | {
44 | modules: false,
45 | targets: {
46 | browsers: ['ie >= 11']
47 | }
48 | }
49 | ]
50 | ]
51 | },
52 | postcss: {
53 | inject: {
54 | insertAt: 'top'
55 | },
56 | minimize: true
57 | },
58 | terser: {
59 | beautify: {
60 | compress: false,
61 | mangle: false,
62 | output: {
63 | beautify: true,
64 | comments: /(?:^!|@(?:license|preserve))/
65 | }
66 | },
67 | minify: {
68 | compress: true,
69 | mangle: true,
70 | output: {
71 | comments: new RegExp(pkg.name)
72 | }
73 | }
74 | }
75 | };
76 |
77 | // Config
78 | // =============================================================================
79 | // Base
80 | const config = {
81 | input: entryFile,
82 | output: {
83 | banner: `/*!\n * ${bannerData.join('\n * ')}\n */`,
84 | file: outputFile,
85 | sourcemap: true
86 | },
87 | plugins: [
88 | nodeResolve(),
89 | commonjs(),
90 | json(),
91 | postcss(pluginSettings.postcss),
92 | babel(pluginSettings.babel)
93 | ],
94 | watch: {
95 | clearScreen: false
96 | }
97 | };
98 |
99 | // Formats
100 | // -----------------------------------------------------------------------------
101 | // IIFE
102 | const iife = mergician({}, config, {
103 | output: {
104 | format: 'iife'
105 | },
106 | plugins: config.plugins.concat([terser(pluginSettings.terser.beautify)])
107 | });
108 |
109 | // IIFE (Minified)
110 | const iifeMinified = mergician({}, config, {
111 | output: {
112 | file: iife.output.file.replace(/\.js$/, '.min.js'),
113 | format: iife.output.format
114 | },
115 | plugins: config.plugins.concat([terser(pluginSettings.terser.minify)])
116 | });
117 |
118 | // Exports
119 | // =============================================================================
120 | export default [iife, iifeMinified];
121 |
--------------------------------------------------------------------------------
/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhildenbiddle/docsify-tabs/ecc0184c042627961cb54882f1a43adecff0577e/screenshot.jpg
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | import { create } from 'browser-sync';
2 | import compression from 'compression';
3 |
4 | const bsServer = create();
5 |
6 | bsServer.init({
7 | files: ['./dist/**/*.*', './docs/**/*.*'],
8 | ghostMode: {
9 | clicks: false,
10 | forms: false,
11 | scroll: false
12 | },
13 | open: false,
14 | notify: false,
15 | cors: true,
16 | reloadDebounce: 1000,
17 | reloadOnRestart: true,
18 | server: {
19 | baseDir: ['./docs/'],
20 | middleware: [compression()],
21 | routes: {
22 | '/CHANGELOG.md': './CHANGELOG.md'
23 | }
24 | },
25 | serveStatic: ['./dist/'],
26 | rewriteRules: [
27 | // Replace CDN URLs with local paths
28 | {
29 | match: /https?.*\/CHANGELOG.md/g,
30 | replace: '/CHANGELOG.md'
31 | },
32 | {
33 | // CDN versioned default
34 | // Ex1: //cdn.com/package-name
35 | // Ex2: http://cdn.com/package-name@1.0.0
36 | // Ex3: https://cdn.com/package-name@latest
37 | match: /(?:https?:)*\/\/.*cdn.*docsify-tabs[@\d.latest]*(?=["'])/g,
38 | replace: '/docsify-tabs.js'
39 | },
40 | {
41 | // CDN paths to local paths
42 | // Ex1: //cdn.com/package-name/path/file.js => /path/file.js
43 | // Ex2: http://cdn.com/package-name@1.0.0/dist/file.js => /dist/file.js
44 | // Ex3: https://cdn.com/package-name@latest/dist/file.js => /dist/file.js
45 | match: /(?:https?:)*\/\/.*cdn.*docsify-tabs[@\d.latest]*\/(?:dist\/)/g,
46 | replace: '/'
47 | }
48 | ]
49 | });
50 |
--------------------------------------------------------------------------------
/src/css/vars.css:
--------------------------------------------------------------------------------
1 | :root {
2 | /* Tab blocks */
3 | --docsifytabs-border-color: #ededed;
4 | --docsifytabs-border-px: 1px;
5 | --docsifytabs-border-radius-px: ;
6 | --docsifytabs-margin: 1.5em 0;
7 |
8 | /* Tabs */
9 | --docsifytabs-tab-background: #f8f8f8;
10 | --docsifytabs-tab-background--active: var(--docsifytabs-content-background);
11 | --docsifytabs-tab-color: #999;
12 | --docsifytabs-tab-color--active: inherit;
13 | --docsifytabs-tab-highlight-px: 3px;
14 | --docsifytabs-tab-highlight-color: var(--theme-color, currentColor);
15 | --docsifytabs-tab-padding: 0.6em 1em;
16 |
17 | /* Tab content */
18 | --docsifytabs-content-background: inherit;
19 | --docsifytabs-content-padding: 1.5rem;
20 | }
21 |
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | // =============================================================================
3 | import { version as pkgVersion } from '../../package.json';
4 | import '../scss/style.scss';
5 |
6 | // Constants and variables
7 | // =============================================================================
8 | const commentReplaceMark = 'tabs:replace';
9 | const classNames = {
10 | tabsContainer: 'content',
11 | tabBlock: 'docsify-tabs',
12 | tabButton: 'docsify-tabs__tab',
13 | tabButtonActive: 'docsify-tabs__tab--active',
14 | tabContent: 'docsify-tabs__content'
15 | };
16 | const regex = {
17 | // Matches markdown code blocks (inline and multi-line)
18 | // Example: ```text```
19 | codeMarkup: /(```[\s\S]*?```)/gm,
20 |
21 | // Matches tab replacement comment
22 | // 0: Match
23 | // 1: Replacement HTML
24 | commentReplaceMarkup: new RegExp(``),
25 |
26 | // Matches inner-most tab set by start/end comment
27 | // Ex: ()
28 | // 0: Match
29 | // 1: Indent
30 | // 2: Start comment:
31 | // 3: undefined
32 | // 4: End comment:
33 | tabBlockMarkup:
34 | /( *)()(?:(?!())[\s\S])*()/,
35 |
36 | // Matches tab label and content
37 | // 0: Match
38 | // 1: Label:
39 | // 2: Content
40 | tabCommentMarkup:
41 | /[\r\n]*(\s*)[\r\n]+([\s\S]*?)[\r\n]*\s*(?=)/m
49 | };
50 | const settings = {
51 | persist: true,
52 | sync: true,
53 | theme: 'classic',
54 | tabComments: true,
55 | tabHeadings: true
56 | };
57 |
58 | const storageKeys = {
59 | get persist() {
60 | return `docsify-tabs.persist.${window.location.pathname}`;
61 | },
62 | sync: 'docsify-tabs.sync'
63 | };
64 |
65 | // Functions
66 | // =============================================================================
67 | /**
68 | * Traverses the element and its parents until it finds a node that matches the
69 | * provided selector string. Will return itself or the matching ancestor.
70 | *
71 | * @param {object} elm
72 | * @param {string} closestSelectorString
73 | * @return {(object|null)}
74 | */
75 | function getClosest(elm, closestSelectorString) {
76 | if (Element.prototype.closest) {
77 | return elm.closest(closestSelectorString);
78 | }
79 |
80 | while (elm) {
81 | const isMatch = matchSelector(elm, closestSelectorString);
82 |
83 | if (isMatch) {
84 | return elm;
85 | }
86 |
87 | elm = elm.parentNode || null;
88 | }
89 |
90 | return elm;
91 | }
92 |
93 | /**
94 | * Checks to see if the element would be selected by the provided selectorString
95 | *
96 | * @param {object} elm
97 | * @param {string} selectorString
98 | * @return {boolean}
99 | */
100 | function matchSelector(elm, selectorString) {
101 | const matches =
102 | Element.prototype.matches ||
103 | Element.prototype.msMatchesSelector ||
104 | Element.prototype.webkitMatchesSelector;
105 |
106 | return matches.call(elm, selectorString);
107 | }
108 |
109 | /**
110 | * Converts tab content into "stage 1" markup. Stage 1 markup contains temporary
111 | * comments which are replaced with HTML during Stage 2. This approach allows
112 | * all markdown to be converted to HTML before tab-specific HTML is added.
113 | *
114 | * @param {string} content
115 | * @returns {string}
116 | */
117 | function renderTabsStage1(content, vm) {
118 | const codeBlockMatch = content.match(regex.codeMarkup) || [];
119 | const codeBlockMarkers = codeBlockMatch.map((item, i) => {
120 | const codeMarker = ``;
121 |
122 | // Replace code block with marker to ensure tab markup within code
123 | // blocks is not processed. These markers are replaced with their
124 | // associated code blocs after tabs have been processed.
125 | content = content.replace(item, () => codeMarker);
126 |
127 | return codeMarker;
128 | });
129 | const tabTheme = settings.theme ? `${classNames.tabBlock}--${settings.theme}` : '';
130 | const tempElm = document.createElement('div');
131 |
132 | let tabBlockMatch = content.match(regex.tabBlockMarkup);
133 | let tabIndex = 1;
134 |
135 | // Process each tab set
136 | while (tabBlockMatch) {
137 | let tabBlockOut = tabBlockMatch[0];
138 |
139 | const tabBlockIndent = tabBlockMatch[1];
140 | const tabBlockStart = tabBlockMatch[2];
141 | const tabBlockEnd = tabBlockMatch[4];
142 | const hasTabComments = settings.tabComments && regex.tabCommentMarkup.test(tabBlockOut);
143 | const hasTabHeadings = settings.tabHeadings && regex.tabHeadingMarkup.test(tabBlockOut);
144 |
145 | let tabMatch;
146 | let tabStartReplacement = '';
147 | let tabEndReplacement = '';
148 |
149 | if (hasTabComments || hasTabHeadings) {
150 | tabStartReplacement = ``;
151 | tabEndReplacement = `\n${tabBlockIndent}`;
152 |
153 | // Process each tab panel
154 | while (
155 | (tabMatch =
156 | (settings.tabComments ? regex.tabCommentMarkup.exec(tabBlockOut) : null) ||
157 | (settings.tabHeadings ? regex.tabHeadingMarkup.exec(tabBlockOut) : null)) !== null
158 | ) {
159 | // Process tab title as markdown
160 | // Ex:
161 | tempElm.innerHTML = tabMatch[2].trim()
162 | ? vm.compiler.compile(tabMatch[2]).replace(/<\/?p>/g, '')
163 | : `Tab ${tabIndex}`;
164 |
165 | const tabTitle = tempElm.innerHTML;
166 | const tabContent = (tabMatch[3] || '').trim();
167 | const tabData = (
168 | tempElm.textContent ||
169 | tempElm.firstChild.getAttribute('alt') ||
170 | tempElm.firstChild.getAttribute('src')
171 | )
172 | .trim()
173 | .toLowerCase();
174 |
175 | // Use replace function to avoid regex special replacement
176 | // strings being processed ($$, $&, $`, $', $n)
177 | tabBlockOut = tabBlockOut.replace(tabMatch[0], () =>
178 | [
179 | `\n${tabBlockIndent}`,
180 | `\n${tabBlockIndent}`,
181 | `\n\n${tabBlockIndent}${tabContent}`,
182 | `\n\n${tabBlockIndent}`
183 | ].join('')
184 | );
185 |
186 | tabIndex++;
187 | }
188 | }
189 |
190 | tabBlockOut = tabBlockOut.replace(tabBlockStart, () => tabStartReplacement);
191 | tabBlockOut = tabBlockOut.replace(tabBlockEnd, () => tabEndReplacement);
192 | content = content.replace(tabBlockMatch[0], () => tabBlockOut);
193 |
194 | tabBlockMatch = content.match(regex.tabBlockMarkup);
195 | }
196 |
197 | // Restore code blocks
198 | codeBlockMarkers.forEach((item, i) => {
199 | content = content.replace(item, () => codeBlockMatch[i]);
200 | });
201 |
202 | return content;
203 | }
204 |
205 | /**
206 | * Converts "stage 1" markup into final markup by replacing temporary comments
207 | * with HTML.
208 | *
209 | * @param {string} html
210 | * @returns {string}
211 | */
212 | function renderTabsStage2(html) {
213 | let tabReplaceMatch;
214 |
215 | while ((tabReplaceMatch = regex.commentReplaceMarkup.exec(html)) !== null) {
216 | const tabComment = tabReplaceMatch[0];
217 | const tabReplacement = tabReplaceMatch[1] || '';
218 |
219 | html = html.replace(tabComment, () => tabReplacement);
220 | }
221 |
222 | return html;
223 | }
224 |
225 | /**
226 | * Sets the initial active tab for each tab group: the tab containing the
227 | * matching element ID from the URL, the first tab in the group, or the last tab
228 | * clicked (if persist option is enabled).
229 | */
230 | function setDefaultTabs() {
231 | const tabsContainer = document.querySelector(`.${classNames.tabsContainer}`);
232 | const tabBlocks = tabsContainer
233 | ? Array.apply(null, tabsContainer.querySelectorAll(`.${classNames.tabBlock}`))
234 | : [];
235 | const tabStoragePersist = JSON.parse(sessionStorage.getItem(storageKeys.persist)) || {};
236 | const tabStorageSync = JSON.parse(sessionStorage.getItem(storageKeys.sync)) || [];
237 |
238 | setActiveTabFromAnchor();
239 |
240 | tabBlocks.forEach((tabBlock, index) => {
241 | let activeButton = Array.apply(null, tabBlock.children).filter(elm =>
242 | matchSelector(elm, `.${classNames.tabButtonActive}`)
243 | )[0];
244 |
245 | if (!activeButton) {
246 | if (settings.sync && tabStorageSync.length) {
247 | activeButton = tabStorageSync
248 | .map(
249 | label =>
250 | Array.apply(null, tabBlock.children).filter(elm =>
251 | matchSelector(elm, `.${classNames.tabButton}[data-tab="${label}"]`)
252 | )[0]
253 | )
254 | .filter(elm => elm)[0];
255 | }
256 |
257 | if (!activeButton && settings.persist) {
258 | activeButton = Array.apply(null, tabBlock.children).filter(elm =>
259 | matchSelector(elm, `.${classNames.tabButton}[data-tab="${tabStoragePersist[index]}"]`)
260 | )[0];
261 | }
262 |
263 | activeButton = activeButton || tabBlock.querySelector(`.${classNames.tabButton}`);
264 | activeButton && activeButton.classList.add(classNames.tabButtonActive);
265 | }
266 | });
267 | }
268 |
269 | /**
270 | * Sets the active tab within a group. Optionally stores the selection so it can
271 | * persist across page loads and syncs active state to tabs with same data attr.
272 | *
273 | * @param {object} elm Tab toggle element to mark as active
274 | */
275 | function setActiveTab(elm, _isMatchingTabSync = false) {
276 | const activeButton = getClosest(elm, `.${classNames.tabButton}`);
277 |
278 | if (activeButton) {
279 | const activeButtonLabel = activeButton.getAttribute('data-tab');
280 | const tabsContainer = document.querySelector(`.${classNames.tabsContainer}`);
281 | const tabBlock = activeButton.parentNode;
282 | const tabButtons = Array.apply(null, tabBlock.children).filter(elm =>
283 | matchSelector(elm, 'button')
284 | );
285 | const tabBlockOffset = tabBlock.offsetTop;
286 |
287 | tabButtons.forEach(buttonElm => buttonElm.classList.remove(classNames.tabButtonActive));
288 | activeButton.classList.add(classNames.tabButtonActive);
289 |
290 | if (!_isMatchingTabSync) {
291 | if (settings.persist) {
292 | const tabBlocks = tabsContainer
293 | ? Array.apply(null, tabsContainer.querySelectorAll(`.${classNames.tabBlock}`))
294 | : [];
295 | const tabBlockIndex = tabBlocks.indexOf(tabBlock);
296 | const tabStorage = JSON.parse(sessionStorage.getItem(storageKeys.persist)) || {};
297 |
298 | tabStorage[tabBlockIndex] = activeButtonLabel;
299 | sessionStorage.setItem(storageKeys.persist, JSON.stringify(tabStorage));
300 | }
301 |
302 | if (settings.sync) {
303 | const tabButtonMatches = tabsContainer
304 | ? Array.apply(
305 | null,
306 | tabsContainer.querySelectorAll(
307 | `.${classNames.tabButton}[data-tab="${activeButtonLabel}"]`
308 | )
309 | )
310 | : [];
311 | const tabStorage = JSON.parse(sessionStorage.getItem(storageKeys.sync)) || [];
312 |
313 | tabButtonMatches.forEach(tabButtonMatch => {
314 | setActiveTab(tabButtonMatch, true);
315 | });
316 |
317 | // Maintain position in viewport when tab group's offset changes
318 | window.scrollBy(0, 0 - (tabBlockOffset - tabBlock.offsetTop));
319 |
320 | // Remove existing label if not first in array
321 | if (tabStorage.indexOf(activeButtonLabel) > 0) {
322 | tabStorage.splice(tabStorage.indexOf(activeButtonLabel), 1);
323 | }
324 |
325 | // Add label if not already in first position
326 | if (tabStorage.indexOf(activeButtonLabel) !== 0) {
327 | tabStorage.unshift(activeButtonLabel);
328 | sessionStorage.setItem(storageKeys.sync, JSON.stringify(tabStorage));
329 | }
330 | }
331 | }
332 | }
333 | }
334 |
335 | /**
336 | * Sets the active tab based on the anchor ID in the URL
337 | */
338 | function setActiveTabFromAnchor() {
339 | const uriComponent = window.location.hash || window.location.search;
340 | const anchorID = decodeURIComponent((uriComponent.match(/(?:id=)([^&]+)/) || [])[1]);
341 | const anchorSelector = anchorID && `.${classNames.tabBlock} #${anchorID}`;
342 | const isAnchorElmInTabBlock = anchorID && document.querySelector(anchorSelector);
343 |
344 | if (isAnchorElmInTabBlock) {
345 | const anchorElm = document.querySelector(`#${anchorID}`);
346 |
347 | let tabContent;
348 |
349 | if (anchorElm.closest) {
350 | tabContent = anchorElm.closest(`.${classNames.tabContent}`);
351 | } else {
352 | tabContent = anchorElm.parentNode;
353 |
354 | while (
355 | tabContent !== document.body &&
356 | !tabContent.classList.contains(`${classNames.tabContent}`)
357 | ) {
358 | tabContent = tabContent.parentNode;
359 | }
360 | }
361 |
362 | setActiveTab(tabContent.previousElementSibling);
363 | }
364 | }
365 |
366 | // Plugin
367 | // =============================================================================
368 | function docsifyTabs(hook, vm) {
369 | let hasTabs = false;
370 |
371 | hook.beforeEach(function (content) {
372 | hasTabs = regex.tabBlockMarkup.test(content);
373 |
374 | if (hasTabs) {
375 | content = renderTabsStage1(content, vm);
376 | }
377 |
378 | return content;
379 | });
380 |
381 | hook.afterEach(function (html, next) {
382 | if (hasTabs) {
383 | html = renderTabsStage2(html);
384 | }
385 |
386 | next(html);
387 | });
388 |
389 | hook.doneEach(function () {
390 | if (hasTabs) {
391 | setDefaultTabs();
392 | }
393 | });
394 |
395 | hook.mounted(function () {
396 | const tabsContainer = document.querySelector(`.${classNames.tabsContainer}`);
397 |
398 | tabsContainer &&
399 | tabsContainer.addEventListener('click', function handleTabClick(evt) {
400 | setActiveTab(evt.target);
401 | });
402 | });
403 | }
404 |
405 | if (window) {
406 | window.$docsify = window.$docsify || {};
407 |
408 | // Add config object
409 | window.$docsify.tabs = window.$docsify.tabs || {};
410 |
411 | // Update settings based on $docsify config
412 | Object.keys(window.$docsify.tabs).forEach(key => {
413 | if (Object.prototype.hasOwnProperty.call(settings, key)) {
414 | settings[key] = window.$docsify.tabs[key];
415 | }
416 | });
417 |
418 | // Add plugin data
419 | window.$docsify.tabs.version = pkgVersion;
420 |
421 | // Init plugin
422 | if (settings.tabComments || settings.tabHeadings) {
423 | window.$docsify.plugins = [].concat(window.$docsify.plugins || [], docsifyTabs);
424 | }
425 | }
426 |
--------------------------------------------------------------------------------
/src/scss/style.scss:
--------------------------------------------------------------------------------
1 | @use './../css/vars.css';
2 |
3 | // Base
4 | // =============================================================================
5 | .docsify-tabs:before,
6 | .docsify-tabs__tab {
7 | z-index: 1;
8 | }
9 |
10 | .docsify-tabs__tab:focus,
11 | .docsify-tabs__tab--active {
12 | z-index: 2;
13 | }
14 |
15 | .docsify-tabs {
16 | display: flex;
17 | flex-wrap: wrap;
18 | position: relative;
19 |
20 | &:before {
21 | content: '';
22 | order: 0;
23 | flex: 1;
24 | }
25 | }
26 |
27 | .docsify-tabs__tab {
28 | order: -1;
29 | position: relative;
30 | margin: 0;
31 | font-size: inherit;
32 | appearance: none;
33 | }
34 |
35 | .docsify-tabs__content[class] {
36 | // Add weight instead of !important
37 | visibility: hidden;
38 | position: absolute;
39 | overflow: hidden;
40 | height: 0;
41 | width: 100%;
42 |
43 | > :first-child {
44 | margin-top: 0;
45 | }
46 |
47 | > :last-child {
48 | margin-bottom: 0;
49 | }
50 |
51 | .docsify-tabs__tab--active + & {
52 | visibility: visible;
53 | position: relative;
54 | overflow: auto;
55 | height: auto;
56 | }
57 | }
58 |
59 | // Themes
60 | // =============================================================================
61 | [class*='docsify-tabs--'] {
62 | margin: var(--docsifytabs-margin);
63 |
64 | > .docsify-tabs__tab {
65 | padding: var(--docsifytabs-tab-padding);
66 | background: var(--docsifytabs-tab-background);
67 | color: var(--docsifytabs-tab-color);
68 | }
69 |
70 | > .docsify-tabs__tab--active {
71 | background: var(--docsifytabs-tab-background--active);
72 | color: var(--docsifytabs-tab-color--active);
73 | }
74 |
75 | > .docsify-tabs__content {
76 | background: var(--docsifytabs-content-background);
77 | }
78 |
79 | > .docsify-tabs__tab--active + .docsify-tabs__content {
80 | padding: var(--docsifytabs-content-padding);
81 | }
82 | }
83 |
84 | // Classic
85 | // -----------------------------------------------------------------------------
86 | .docsify-tabs--classic {
87 | &:before,
88 | > .docsify-tabs__tab,
89 | > .docsify-tabs__content {
90 | border-width: var(--docsifytabs-border-px);
91 | border-style: solid;
92 | border-color: var(--docsifytabs-border-color);
93 | }
94 |
95 | &:before {
96 | margin-right: var(--docsifytabs-border-px);
97 | border-top-width: 0;
98 | border-left-width: 0;
99 | border-right-width: 0;
100 | }
101 |
102 | > .docsify-tabs__tab {
103 | &:first-of-type {
104 | border-top-left-radius: var(--docsifytabs-border-radius-px);
105 | }
106 |
107 | &:last-of-type {
108 | border-top-right-radius: var(--docsifytabs-border-radius-px);
109 | }
110 | }
111 |
112 | > .docsify-tabs__tab ~ .docsify-tabs__tab {
113 | margin-left: calc(0px - var(--docsifytabs-border-px));
114 | }
115 |
116 | > .docsify-tabs__tab--active {
117 | border-bottom-width: 0;
118 | box-shadow: inset 0 var(--docsifytabs-tab-highlight-px) 0 0
119 | var(--docsifytabs-tab-highlight-color);
120 | }
121 |
122 | > .docsify-tabs__content {
123 | margin-top: calc(0px - var(--docsifytabs-border-px));
124 | border-top: 0;
125 | border-radius: 0 var(--docsifytabs-border-radius-px) var(--docsifytabs-border-radius-px)
126 | var(--docsifytabs-border-radius-px);
127 | }
128 | }
129 |
130 | // Material
131 | // -----------------------------------------------------------------------------
132 | .docsify-tabs--material {
133 | > .docsify-tabs__tab {
134 | margin-bottom: calc(var(--docsifytabs-tab-highlight-px) - var(--docsifytabs-border-px));
135 | background: transparent;
136 | border: 0;
137 | }
138 |
139 | > .docsify-tabs__tab--active {
140 | box-shadow: 0 var(--docsifytabs-tab-highlight-px) 0 0 var(--docsifytabs-tab-highlight-color);
141 | background: transparent;
142 | }
143 |
144 | > .docsify-tabs__content {
145 | border-width: var(--docsifytabs-border-px) 0;
146 | border-style: solid;
147 | border-color: var(--docsifytabs-border-color);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------