markdown-it-admon-collapsible 1.9.3 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,13 +1,27 @@
1
1
  # Changelog
2
2
 
3
- ## 1.9.2
3
+ ## 1.9.5
4
4
 
5
- 2025-12-26
5
+ 2026-01-01
6
6
 
7
- - forked from markdown-it-admon
7
+ - Made validation customizable
8
+ - General cleanup and added markdownlint
9
+
10
+ ## 1.9.4
11
+
12
+ 2025-12-27
13
+
14
+ - Reverted since the previous change was a misunderstanding
15
+ - Using details/summary for collapsible admonitions
8
16
 
9
17
  ## 1.9.3
10
18
 
11
19
  2025-12-27
12
20
 
13
21
  - Changed the generated HTML to be more like markdown-it-container
22
+
23
+ ## 1.9.2
24
+
25
+ 2025-12-26
26
+
27
+ - forked from markdown-it-admon
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # markdown-it-admon-collapsible
2
2
 
3
3
  > **Note:** This package is a fork of [markdown-it-admon](https://github.com/commenthol/markdown-it-admon) with added support for collapsible blocks (???), inspired by Material for MkDocs.
4
-
5
4
  > Plugin for creating admonitions for [markdown-it](https://github.com/markdown-it/markdown-it) markdown parser.
6
5
 
7
6
  With this plugin you can have collapsible admonitions:
@@ -31,7 +30,7 @@ Markdown syntax is inspired by [Material for MkDocs](https://squidfunk.github.io
31
30
 
32
31
  A styles file does support the following admonition types: Credits go to [vscode-markdown-extended][].
33
32
 
34
- ```
33
+ ```text
35
34
  'note',
36
35
  'summary', 'abstract', 'tldr',
37
36
  'info', 'todo',
@@ -45,17 +44,16 @@ A styles file does support the following admonition types: Credits go to [vscode
45
44
  'quote', 'cite'
46
45
  ```
47
46
 
48
- ![](./docs/admonition-types.png)
47
+ ![Admonition types](./docs/admonition-types.png)
49
48
 
50
49
  ## Installation
51
50
 
52
51
  node.js:
53
52
 
54
53
  ```bash
55
- $ npm install markdown-it-admon-collapsible --save
54
+ npm install markdown-it-admon-collapsible --save
56
55
  ```
57
56
 
58
-
59
57
  ## API
60
58
 
61
59
  ```js
@@ -63,11 +61,24 @@ const md = require('markdown-it')()
63
61
  .use(require('markdown-it-admon-collapsible') [, options]);
64
62
  ```
65
63
 
66
- Params:
64
+ Plugin Options
65
+
66
+ - `render`: (optional) Custom render function.
67
+ - `validate`: (optional) Custom validation function. If provided, this function will be used to validate the parameters for admonitions.
67
68
 
68
- - __name__ - container name (mandatory)
69
- - __options?:__
70
- - __render__ - optional, renderer function for opening/closing tokens.
69
+ ### Example usage
70
+
71
+ ```js
72
+ const md = require('markdown-it')();
73
+ const admonitionPlugin = require('markdown-it-admon-collapsible');
74
+
75
+ md.use(admonitionPlugin, {
76
+ validate: function(params) {
77
+ // Custom validation logic
78
+ return params.startsWith('note');
79
+ }
80
+ });
81
+ ```
71
82
 
72
83
  ## License
73
84
 
@@ -75,7 +86,7 @@ Params:
75
86
 
76
87
  ## References
77
88
 
78
- * [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/reference/admonitions/)
79
- * [vscode-markdown-extended][vscode-markdown-extended]
80
- * [rST]: https://docutils.sourceforge.io/docs/ref/rst/directives.html#specific-admonitions
81
- * [vscode-markdown-extended]: https://github.com/qjebbs/vscode-markdown-extended
89
+ - [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/reference/admonitions/)
90
+ - [vscode-markdown-extended][vscode-markdown-extended]
91
+ - [rST]: https://docutils.sourceforge.io/docs/ref/rst/directives.html#specific-admonitions
92
+ - [vscode-markdown-extended]: https://github.com/qjebbs/vscode-markdown-extended
package/index.js CHANGED
@@ -25,18 +25,18 @@ function getTag (params) {
25
25
  return {}
26
26
  }
27
27
 
28
- const joined = _title.join(' ');
29
- let title;
28
+ const joined = _title.join(' ')
29
+ let title
30
30
  if (!joined) {
31
- title = capitalize(tag);
31
+ title = capitalize(tag)
32
32
  } else if (joined === '""') {
33
- title = '';
33
+ title = ''
34
34
  } else if ((joined.startsWith('"') && joined.endsWith('"')) || (joined.startsWith("'") && joined.endsWith("'"))) {
35
- title = joined.slice(1, -1);
35
+ title = joined.slice(1, -1)
36
36
  } else {
37
- title = joined;
37
+ title = joined
38
38
  }
39
- return { tag: tag.toLowerCase(), title };
39
+ return { tag: tag.toLowerCase(), title }
40
40
  }
41
41
 
42
42
  function validate (params) {
@@ -44,188 +44,181 @@ function validate (params) {
44
44
  return !!tag
45
45
  }
46
46
 
47
-
48
- function renderDefault(tokens, idx, _options, env, slf) {
49
- return slf.renderToken(tokens, idx, _options, env, slf);
50
- }
51
-
52
- function renderCollapsibleOpen(tokens, idx) {
53
- const token = tokens[idx];
54
- const classes = token.attrs ? token.attrs.find(a => a[0] === 'class')[1] : '';
55
- return `<div class="${classes}">\n`;
56
- }
57
-
58
- function renderCollapsibleTitleOpen(tokens, idx) {
59
- const expanded = tokens[idx - 1]?.meta?.expanded;
60
- // Add toggle button
61
- return `<div class="admonition-title"><button class="collapsible-toggle" tabindex="0">${expanded ? '&#x2212;' : '&#x2b;'}</button>`;
62
- }
63
-
64
- function renderCollapsibleTitleClose() {
65
- return '</div>\n';
47
+ function renderDefault (tokens, idx, _options, env, slf) {
48
+ return slf.renderToken(tokens, idx, _options, env, slf)
66
49
  }
67
50
 
68
- function renderCollapsibleContentOpen(tokens, idx) {
69
- return '';
51
+ function renderCollapsibleContentOpen (tokens, idx) {
52
+ return ''
70
53
  }
71
54
 
72
- function renderCollapsibleContentClose() {
73
- return '';
55
+ function renderCollapsibleContentClose () {
56
+ return ''
74
57
  }
75
58
 
76
-
77
- const minMarkers = 3;
59
+ const minMarkers = 3
78
60
  const markerTypes = [
79
61
  { str: '!', type: 'admonition' },
80
62
  { str: '?', type: 'collapsible' }
81
- ];
82
-
63
+ ]
83
64
 
84
- function admonition(state, startLine, endLine, silent) {
85
- let pos, nextLine, token;
86
- const start = state.bMarks[startLine] + state.tShift[startLine];
87
- let max = state.eMarks[startLine];
65
+ function admonition (state, startLine, endLine, silent) {
66
+ let pos, nextLine, token
67
+ const start = state.bMarks[startLine] + state.tShift[startLine]
68
+ let max = state.eMarks[startLine]
88
69
 
89
70
  // Determine marker type
90
- let markerType = null;
71
+ let markerType = null
91
72
  for (const type of markerTypes) {
92
73
  if (state.src.substr(start, type.str.length) === type.str.repeat(type.str.length)) {
93
- markerType = type;
94
- break;
74
+ markerType = type
75
+ break
95
76
  }
96
77
  }
97
- if (!markerType) return false;
78
+ if (!markerType) return false
98
79
 
99
- const markerStr = markerType.str;
100
- const markerLen = markerStr.length;
80
+ const markerStr = markerType.str
81
+ const markerLen = markerStr.length
101
82
 
102
83
  // Check out the rest of the marker string
103
84
  for (pos = start + 1; pos <= max; pos++) {
104
85
  if (markerStr[(pos - start) % markerLen] !== state.src[pos]) {
105
- break;
86
+ break
106
87
  }
107
88
  }
108
89
 
109
- const markerCount = Math.floor((pos - start) / markerLen);
110
- if (markerCount < minMarkers) return false;
111
- const markerPos = pos - ((pos - start) % markerLen);
112
- let params = state.src.slice(markerPos, max);
113
- let markup = state.src.slice(start, markerPos);
90
+ const markerCount = Math.floor((pos - start) / markerLen)
91
+ if (markerCount < minMarkers) return false
92
+ const markerPos = pos - ((pos - start) % markerLen)
93
+ let params = state.src.slice(markerPos, max)
94
+ let markup = state.src.slice(start, markerPos)
114
95
 
115
96
  // Collapsible: check for plus sign
116
- let isCollapsible = markerType.type === 'collapsible';
117
- let expanded = false;
97
+ const isCollapsible = markerType.type === 'collapsible'
98
+ let expanded = false
118
99
  if (isCollapsible && params.trim().startsWith('+')) {
119
- expanded = true;
120
- params = params.trim().slice(1).trim();
121
- markup += '+';
100
+ expanded = true
101
+ params = params.trim().slice(1).trim()
102
+ markup += '+'
122
103
  }
123
104
 
124
- if (!validate(params)) return false;
125
- if (silent) return true;
105
+ const validateFn = state.md.options && state.md.options.admonitionValidate
106
+ ? state.md.options.admonitionValidate
107
+ : validate
108
+ if (!validateFn(params)) return false
109
+ if (silent) return true
126
110
 
127
- const oldParent = state.parentType;
128
- const oldLineMax = state.lineMax;
129
- const oldIndent = state.blkIndent;
111
+ const oldParent = state.parentType
112
+ const oldLineMax = state.lineMax
113
+ const oldIndent = state.blkIndent
130
114
 
131
- let blkStart = pos;
115
+ let blkStart = pos
132
116
  for (; blkStart < max; blkStart += 1) {
133
- if (state.src[blkStart] !== ' ') break;
117
+ if (state.src[blkStart] !== ' ') break
134
118
  }
135
- state.parentType = 'admonition';
136
- state.blkIndent += blkStart - start;
119
+ state.parentType = 'admonition'
120
+ state.blkIndent += blkStart - start
137
121
 
138
- let wasEmpty = false;
139
- nextLine = startLine;
122
+ let wasEmpty = false
123
+ nextLine = startLine
140
124
  for (;;) {
141
- nextLine++;
142
- if (nextLine >= endLine) break;
143
- pos = state.bMarks[nextLine] + state.tShift[nextLine];
144
- max = state.eMarks[nextLine];
145
- const isEmpty = state.sCount[nextLine] < state.blkIndent;
146
- if (isEmpty && wasEmpty) break;
147
- wasEmpty = isEmpty;
148
- if (pos < max && state.sCount[nextLine] < state.blkIndent) break;
125
+ nextLine++
126
+ if (nextLine >= endLine) break
127
+ pos = state.bMarks[nextLine] + state.tShift[nextLine]
128
+ max = state.eMarks[nextLine]
129
+ const isEmpty = state.sCount[nextLine] < state.blkIndent
130
+ if (isEmpty && wasEmpty) break
131
+ wasEmpty = isEmpty
132
+ if (pos < max && state.sCount[nextLine] < state.blkIndent) break
149
133
  }
150
- state.lineMax = nextLine;
134
+ state.lineMax = nextLine
151
135
 
152
- const { tag, title } = getTag(params);
136
+ const { tag, title } = getTag(params)
153
137
 
154
138
  // Use different token for collapsible
155
- const openType = isCollapsible ? 'collapsible_open' : 'admonition_open';
156
- const closeType = isCollapsible ? 'collapsible_close' : 'admonition_close';
157
-
158
- token = state.push(openType, 'div', 1);
159
- token.markup = markup;
160
- token.block = true;
161
- token.attrs = [['class', `admonition ${tag}${isCollapsible ? ' collapsible' : ''}${expanded ? ' expanded' : ''}`]];
162
- token.meta = { tag, expanded };
163
- token.content = title;
164
- token.info = params;
165
- token.map = [startLine, nextLine];
139
+ const openType = isCollapsible ? 'collapsible_open' : 'admonition_open'
140
+ const closeType = isCollapsible ? 'collapsible_close' : 'admonition_close'
141
+
142
+ token = isCollapsible ? state.push(openType, 'details', 1) : state.push(openType, 'div', 1)
143
+ token.markup = markup
144
+ token.block = true
145
+ token.attrs = [['class', `admonition ${tag}`]]
146
+ if (expanded) {
147
+ token.attrs.push(['open', ''])
148
+ }
149
+ token.meta = { tag, expanded }
150
+ token.content = title
151
+ token.info = params
152
+ token.map = [startLine, nextLine]
166
153
 
167
154
  if (title) {
168
- const titleMarkup = markup + ' ' + tag;
169
- token = state.push(isCollapsible ? 'collapsible_title_open' : 'admonition_title_open', 'div', 1);
170
- token.markup = titleMarkup;
171
- token.attrs = [['class', 'admonition-title']];
172
- token.map = [startLine, startLine + 1];
173
-
174
- token = state.push('inline', '', 0);
175
- token.content = title;
176
- token.map = [startLine, startLine + 1];
177
- token.children = [];
178
-
179
- token = state.push(isCollapsible ? 'collapsible_title_close' : 'admonition_title_close', 'div', -1);
180
- token.markup = titleMarkup;
155
+ const titleMarkup = markup + ' ' + tag
156
+ token = isCollapsible ? state.push('collapsible_title_open', 'summary', 1) : state.push('admonition_title_open', 'p', 1)
157
+ token.markup = titleMarkup
158
+ token.attrs = [['class', 'admonition-title']]
159
+ token.map = [startLine, startLine + 1]
160
+
161
+ token = state.push('inline', '', 0)
162
+ token.content = title
163
+ token.map = [startLine, startLine + 1]
164
+ token.children = []
165
+
166
+ token = isCollapsible ? state.push('collapsible_title_close', 'summary', -1) : state.push('admonition_title_close', 'p', -1)
167
+ token.markup = titleMarkup
181
168
  }
182
169
 
183
- state.md.block.tokenize(state, startLine + 1, nextLine);
170
+ state.md.block.tokenize(state, startLine + 1, nextLine)
184
171
 
185
- token = state.push(closeType, 'div', -1);
186
- token.markup = state.src.slice(start, pos);
187
- token.block = true;
172
+ token = state.push(closeType, 'div', -1)
173
+ token.markup = state.src.slice(start, pos)
174
+ token.block = true
188
175
 
189
- state.parentType = oldParent;
190
- state.lineMax = oldLineMax;
191
- state.blkIndent = oldIndent;
192
- state.line = nextLine;
176
+ state.parentType = oldParent
177
+ state.lineMax = oldLineMax
178
+ state.blkIndent = oldIndent
179
+ state.line = nextLine
193
180
 
194
- return true;
181
+ return true
195
182
  }
196
183
 
197
- module.exports = function admonitionPlugin(md, options = {}) {
198
- const render = options.render || renderDefault;
184
+ function admonitionPlugin (md, options = {}) {
185
+ const render = options.render || renderDefault
199
186
 
200
- md.renderer.rules.admonition_open = render;
201
- md.renderer.rules.admonition_close = render;
202
- md.renderer.rules.admonition_title_open = render;
203
- md.renderer.rules.admonition_title_close = render;
187
+ if (options.validate) {
188
+ md.options.admonitionValidate = options.validate
189
+ }
190
+
191
+ md.renderer.rules.admonition_open = render
192
+ md.renderer.rules.admonition_close = render
193
+ md.renderer.rules.admonition_title_open = render
194
+ md.renderer.rules.admonition_title_close = render
204
195
 
205
196
  // Collapsible rendering
206
- md.renderer.rules.collapsible_open = renderCollapsibleOpen;
207
- md.renderer.rules.collapsible_close = (tokens, idx) => '</div>\n';
208
- md.renderer.rules.collapsible_title_open = renderCollapsibleTitleOpen;
209
- md.renderer.rules.collapsible_title_close = renderCollapsibleTitleClose;
197
+ // Use default renderer for collapsible_open/close
198
+ md.renderer.rules.collapsible_close = (tokens, idx) => '</details>\n'
199
+ // Use default renderer for collapsible_title_open/close
210
200
 
211
201
  // Wrap content in collapsible-content div
212
- const origBlockTokenize = md.block.tokenize;
213
- md.block.tokenize = function(state, startLine, endLine) {
202
+ const origBlockTokenize = md.block.tokenize
203
+ md.block.tokenize = function (state, startLine, endLine) {
214
204
  if (state.parentType === 'admonition' && state.tokens.length > 0) {
215
- const lastToken = state.tokens[state.tokens.length - 1];
205
+ const lastToken = state.tokens[state.tokens.length - 1]
216
206
  if (lastToken.type === 'collapsible_title_close') {
217
- state.tokens.push({ type: 'collapsible_content_open' });
218
- origBlockTokenize.call(this, state, startLine, endLine);
219
- state.tokens.push({ type: 'collapsible_content_close' });
220
- return;
207
+ state.tokens.push({ type: 'collapsible_content_open' })
208
+ origBlockTokenize.call(this, state, startLine, endLine)
209
+ state.tokens.push({ type: 'collapsible_content_close' })
210
+ return
221
211
  }
222
212
  }
223
- origBlockTokenize.call(this, state, startLine, endLine);
224
- };
225
- md.renderer.rules.collapsible_content_open = renderCollapsibleContentOpen;
226
- md.renderer.rules.collapsible_content_close = renderCollapsibleContentClose;
213
+ origBlockTokenize.call(this, state, startLine, endLine)
214
+ }
215
+ md.renderer.rules.collapsible_content_open = renderCollapsibleContentOpen
216
+ md.renderer.rules.collapsible_content_close = renderCollapsibleContentClose
227
217
 
228
218
  md.block.ruler.before('fence', 'admonition', admonition, {
229
219
  alt: ['paragraph', 'reference', 'blockquote', 'list']
230
- });
220
+ })
231
221
  }
222
+
223
+ admonitionPlugin.admonition = admonition
224
+ module.exports = admonitionPlugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdown-it-admon-collapsible",
3
- "version": "1.9.3",
3
+ "version": "1.9.5",
4
4
  "description": "Plugin to create admonitions for markdown-it markdown parser",
5
5
  "keywords": [
6
6
  "admonition",
@@ -37,7 +37,7 @@
37
37
  "scripts": {
38
38
  "clean": "git clean -fXd -e \\!node_modules -e \\!node_modules/**/*",
39
39
  "coverage": "npm run test && c8 report --reporter html",
40
- "lint": "eslint --fix .",
40
+ "lint": "eslint --fix . && markdownlint-cli2 **/*.md",
41
41
  "test": "c8 mocha",
42
42
  "build": "mkdir -p dist && node_modules/.bin/browserify ./ -s markdownitAdmonCollapsible > dist/markdown-it-admon-collapsible.js && terser dist/markdown-it-admon-collapsible.js -c -m > dist/markdown-it-admon-collapsible.min.js"
43
43
  },
@@ -51,6 +51,9 @@
51
51
  "eslint-plugin-promise": "^6.1.1",
52
52
  "markdown-it": "^14.0.0",
53
53
  "markdown-it-testgen": "~0.1.6",
54
+ "markdownlint": "^0.40.0",
55
+ "markdownlint-cli2": "^0.20.0",
56
+ "markdownlint-rule-numbered-headings-unique": "^1.0.7",
54
57
  "mocha": "^11.7.5",
55
58
  "rimraf": "^6.1.2",
56
59
  "terser": "^5.26.0"
@@ -1,40 +1,38 @@
1
- /* Collapsible block styles */
2
- .collapsible {
3
- border-left-color: #888;
1
+
2
+ summary.admonition-title {
4
3
  position: relative;
5
4
  }
6
- .collapsible .admonition-title {
7
- cursor: pointer;
8
- user-select: none;
9
- padding-right: 2.5rem;
10
- }
11
- .collapsible .admonition-title {
12
- position: relative;
5
+ summary.admonition-title::marker {
6
+ content: none;
13
7
  }
14
- .collapsible .collapsible-toggle {
15
- position: absolute;
16
- right: 1.2rem;
17
- top: 50%;
18
- transform: translateY(-50%);
19
- font-size: 1.2rem;
20
- background: none;
21
- border: none;
22
- cursor: pointer;
23
- outline: none;
24
- margin: 0;
25
- padding: 0;
26
- line-height: 1;
8
+ summary.admonition-title::after {
9
+ content: '▶';
27
10
  }
28
- .collapsible.expanded {
29
- /* Optionally highlight expanded state */
11
+ summary.admonition-title::after {
12
+ position: absolute;
13
+ right: 0.5rem;
14
+ }
15
+ [open] summary.admonition-title::after {
16
+ transform: rotate(90deg);
17
+ transform-origin: 50% 50%;
30
18
  }
31
19
 
32
- /* Hide all siblings of .admonition-title when not expanded */
33
- .collapsible:not(.expanded) > .admonition-title ~ * {
34
- display: none;
20
+ /* Collapsible block styles using <details> and <summary> */
21
+ details.admonition {
22
+ border-left-color: #888;
23
+ position: relative;
35
24
  }
36
- .collapsible.expanded > .admonition-title ~ * {
37
- display: block;
25
+ summary.admonition-title {
26
+ cursor: pointer;
27
+ user-select: none;
28
+ position: relative;
29
+ padding-right: 2.5rem;
30
+ margin: 0 -1.2rem;
31
+ padding: .8rem 1.0rem .8rem 1.0rem;
32
+ border-bottom: 1px solid rgba(68, 138, 255, .2);
33
+ background-color: rgba(68, 138, 255, .1);
34
+ font-weight: 700;
35
+ list-style: none;
38
36
  }
39
37
  /**
40
38
  * @credits https://github.com/qjebbs/vscode-markdown-extended