mokuji.js 4.7.0 → 5.0.0
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/README.md +183 -11
- package/dist/index.d.ts +68 -14
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +42 -35
- package/dist/index.cjs +0 -2
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -16
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
mokuji.js
|
|
2
|
-
===
|
|
1
|
+
# mokuji.js
|
|
3
2
|
|
|
4
3
|
A table of content JavaScript Library.
|
|
5
4
|
|
|
5
|
+
> "mokuji" means "table of contents" in Japanese.
|
|
6
|
+
|
|
6
7
|
## Installation
|
|
7
8
|
|
|
8
9
|
```bash
|
|
@@ -14,15 +15,153 @@ npm install --save mokuji.js
|
|
|
14
15
|
```javascript
|
|
15
16
|
import { Mokuji } from 'mokuji.js';
|
|
16
17
|
|
|
18
|
+
// Get the element containing your content with headings
|
|
17
19
|
const textElement = document.querySelector('.text');
|
|
18
|
-
|
|
20
|
+
if (!textElement) {
|
|
21
|
+
console.warn('Target element not found');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
// Generate the table of contents
|
|
26
|
+
const result = Mokuji(textElement);
|
|
27
|
+
if (!result) {
|
|
28
|
+
console.warn('No headings found in the element');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
21
31
|
|
|
32
|
+
// Append the generated list to your container
|
|
22
33
|
const listElement = document.querySelector('.list');
|
|
23
|
-
listElement
|
|
34
|
+
if (listElement) {
|
|
35
|
+
listElement.appendChild(result.list);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Clean up when no longer needed (instance-scoped)
|
|
39
|
+
// For SPAs: Call on component unmount
|
|
40
|
+
// For regular pages: Call on page unload
|
|
41
|
+
result.destroy();
|
|
24
42
|
```
|
|
25
43
|
|
|
44
|
+
### TypeScript
|
|
45
|
+
|
|
46
|
+
Full TypeScript support with type definitions included:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { Mokuji, MokujiOption, MokujiResult } from 'mokuji.js';
|
|
50
|
+
|
|
51
|
+
const options: MokujiOption = {
|
|
52
|
+
anchorType: true, // true: Wikipedia-style, false: RFC 3986 compliant
|
|
53
|
+
anchorLink: true, // Enable anchor links in headings
|
|
54
|
+
anchorLinkSymbol: '🔗', // Symbol for anchor links
|
|
55
|
+
minLevel: 2, // Start from h2 (skip h1)
|
|
56
|
+
maxLevel: 4, // Include up to h4
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const element = document.querySelector<HTMLElement>('.content');
|
|
60
|
+
if (!element) {
|
|
61
|
+
console.warn('Content element not found');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const result: MokujiResult | undefined = Mokuji(element, options);
|
|
66
|
+
if (!result) {
|
|
67
|
+
console.warn('No headings found within specified levels');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const tocContainer = document.querySelector<HTMLElement>('.toc');
|
|
72
|
+
if (tocContainer) {
|
|
73
|
+
tocContainer.appendChild(result.list);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Cleanup when component unmounts or page unloads
|
|
77
|
+
result.destroy();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Cleanup Examples
|
|
81
|
+
|
|
82
|
+
The `destroy()` method removes generated IDs and anchor links. Call it when the TOC is no longer needed:
|
|
83
|
+
|
|
84
|
+
#### React
|
|
85
|
+
|
|
86
|
+
```jsx
|
|
87
|
+
import { useEffect, useRef } from 'react';
|
|
88
|
+
import { Mokuji } from 'mokuji.js';
|
|
89
|
+
|
|
90
|
+
function TableOfContents({ contentRef }) {
|
|
91
|
+
const tocRef = useRef(null);
|
|
92
|
+
const resultRef = useRef(null);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (contentRef.current && tocRef.current) {
|
|
96
|
+
const result = Mokuji(contentRef.current);
|
|
97
|
+
if (result) {
|
|
98
|
+
tocRef.current.appendChild(result.list);
|
|
99
|
+
resultRef.current = result;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Cleanup on unmount
|
|
104
|
+
return () => {
|
|
105
|
+
resultRef.current?.destroy();
|
|
106
|
+
};
|
|
107
|
+
}, [contentRef]);
|
|
108
|
+
|
|
109
|
+
return <div ref={tocRef} className="toc" />;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Anchor Type Examples
|
|
114
|
+
|
|
115
|
+
The `anchorType` option controls how heading IDs are encoded:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// Wikipedia-style encoding (anchorType: true, default)
|
|
119
|
+
const wikiStyle = Mokuji(element, {
|
|
120
|
+
anchorType: true,
|
|
121
|
+
anchorLink: true,
|
|
122
|
+
});
|
|
123
|
+
// Result: "Hello World" → "Hello_World"
|
|
124
|
+
// "日本語" → ".E6.97.A5.E6.9C.AC.E8.AA.9E"
|
|
125
|
+
|
|
126
|
+
// RFC 3986 compliant encoding (anchorType: false)
|
|
127
|
+
const standardStyle = Mokuji(element, {
|
|
128
|
+
anchorType: false,
|
|
129
|
+
anchorLink: true,
|
|
130
|
+
});
|
|
131
|
+
// Result: "Hello World" → "Hello%20World"
|
|
132
|
+
// "日本語" → "%E6%97%A5%E6%9C%AC%E8%AA%9E"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Choose `anchorType: true` for compatibility with Wikipedia-style URLs or when you prefer underscores over percent-encoding. Choose `anchorType: false` for standard URL encoding that works universally with all web frameworks and browsers.
|
|
136
|
+
|
|
137
|
+
## API
|
|
138
|
+
|
|
139
|
+
### `Mokuji(element, options)`
|
|
140
|
+
|
|
141
|
+
Generates a table of contents from heading elements within the specified element.
|
|
142
|
+
|
|
143
|
+
**Parameters:**
|
|
144
|
+
|
|
145
|
+
- `element`: HTMLElement - The container element to scan for headings
|
|
146
|
+
- `options`: MokujiOption (optional) - Configuration options
|
|
147
|
+
|
|
148
|
+
**Returns:**
|
|
149
|
+
|
|
150
|
+
- `MokujiResult` object with:
|
|
151
|
+
- `element`: The original element passed
|
|
152
|
+
- `list`: `HTMLOListElement | HTMLUListElement` - The generated table of contents
|
|
153
|
+
- `destroy()`: Function - Removes this instance's generated content
|
|
154
|
+
|
|
155
|
+
Returns `undefined` if no headings are found.
|
|
156
|
+
|
|
157
|
+
## Important Notes
|
|
158
|
+
|
|
159
|
+
- **Duplicate Headings**: When multiple headings have the same text, they are automatically assigned unique IDs with numeric suffixes (e.g., `section`, `section_1`, `section_2`).
|
|
160
|
+
|
|
161
|
+
- **RFC 3986 Compliance**: When `anchorType` is `false`, generated anchors are fully RFC 3986 compliant for use as URI fragment identifiers.
|
|
162
|
+
|
|
163
|
+
- **Instance-scoped Cleanup**: Each `Mokuji()` call returns its own `destroy()` function, allowing multiple TOC instances to coexist without conflicts.
|
|
164
|
+
|
|
26
165
|
## Options
|
|
27
166
|
|
|
28
167
|
```javascript
|
|
@@ -32,6 +171,9 @@ listElement?.appendChild(mokujiList);
|
|
|
32
171
|
anchorLinkSymbol: '#',
|
|
33
172
|
anchorLinkPosition: 'before',
|
|
34
173
|
anchorLinkClassName: '',
|
|
174
|
+
anchorContainerTagName: 'ol',
|
|
175
|
+
minLevel: 1,
|
|
176
|
+
maxLevel: 6,
|
|
35
177
|
}
|
|
36
178
|
```
|
|
37
179
|
|
|
@@ -39,10 +181,22 @@ listElement?.appendChild(mokujiList);
|
|
|
39
181
|
|
|
40
182
|
(default: `true`)
|
|
41
183
|
|
|
42
|
-
|
|
184
|
+
Controls the anchor text encoding format.
|
|
185
|
+
|
|
186
|
+
- `true`: **Wikipedia-style format** - Spaces replaced with underscores, then URL-encoded with percent signs replaced by dots.
|
|
43
187
|
|
|
44
|
-
|
|
188
|
+
```
|
|
189
|
+
"Hello World" → "Hello_World"
|
|
190
|
+
"Section: Title" → "Section_Title"
|
|
191
|
+
"こんにちは" → ".E3.81.93.E3.82.93.E3.81.AB.E3.81.A1.E3.81.AF"
|
|
192
|
+
```
|
|
45
193
|
|
|
194
|
+
- `false`: **RFC 3986 compliant format** - Standard URI fragment identifier encoding using `encodeURIComponent`.
|
|
195
|
+
```
|
|
196
|
+
"Hello World" → "Hello%20World"
|
|
197
|
+
"Section: Title" → "Section%3A%20Title"
|
|
198
|
+
"こんにちは" → "%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF"
|
|
199
|
+
```
|
|
46
200
|
|
|
47
201
|
### `anchorLink`
|
|
48
202
|
|
|
@@ -62,12 +216,30 @@ set the anchor link symbol
|
|
|
62
216
|
|
|
63
217
|
set position (before/after) the anchor link in headings.
|
|
64
218
|
|
|
65
|
-
Set the position of the anchor link with 'before' or 'after'.
|
|
66
|
-
|
|
67
|
-
|
|
68
219
|
### `anchorLinkClassName`
|
|
69
220
|
|
|
70
221
|
(default: `''`)
|
|
71
222
|
|
|
72
|
-
set anchor link class name.
|
|
223
|
+
set anchor link class name. Multiple class names can be specified with spaces.
|
|
224
|
+
|
|
225
|
+
### `anchorContainerTagName`
|
|
226
|
+
|
|
227
|
+
(default: `'ol'`)
|
|
228
|
+
|
|
229
|
+
set the container element tag name for the table of contents. Possible values are 'ol' or 'ul'.
|
|
230
|
+
|
|
231
|
+
### `minLevel`
|
|
232
|
+
|
|
233
|
+
(default: `1`)
|
|
234
|
+
|
|
235
|
+
set the minimum heading level to include in the table of contents (1 means h1).
|
|
236
|
+
|
|
237
|
+
### `maxLevel`
|
|
238
|
+
|
|
239
|
+
(default: `6`)
|
|
240
|
+
|
|
241
|
+
set the maximum heading level to include in the table of contents (6 means h6).
|
|
242
|
+
|
|
243
|
+
## License
|
|
73
244
|
|
|
245
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type AnchorContainerTagName = 'ul' | 'ol';
|
|
3
|
+
/**
|
|
4
|
+
* Type representing heading levels (values 1-6 corresponding to h1-h6)
|
|
5
|
+
*/
|
|
6
|
+
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
7
|
+
/**
|
|
8
|
+
* Anchor link placement position
|
|
9
|
+
*/
|
|
10
|
+
type AnchorLinkPosition = 'before' | 'after';
|
|
11
|
+
/**
|
|
12
|
+
* Option settings for TOC generation
|
|
13
|
+
*/
|
|
2
14
|
type MokujiOption = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Whether to generate Wikipedia-style anchors
|
|
17
|
+
* Default: true (e.g., "heading_text" -> "heading_text", "heading:text" -> "headingtext")
|
|
18
|
+
* If false, spaces are replaced with '_' but encoding and special character conversion is minimal
|
|
19
|
+
*/
|
|
20
|
+
anchorType?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether to add anchor links (e.g., #) next to heading elements
|
|
23
|
+
* Default: false
|
|
24
|
+
*/
|
|
25
|
+
anchorLink?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Symbol or text to display when `anchorLink: true`
|
|
28
|
+
* Default: '#'
|
|
29
|
+
*/
|
|
30
|
+
anchorLinkSymbol?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Anchor link placement position (before or after heading text) when `anchorLink: true`
|
|
33
|
+
* Default: 'before'
|
|
34
|
+
*/
|
|
35
|
+
anchorLinkPosition?: AnchorLinkPosition;
|
|
36
|
+
/**
|
|
37
|
+
* CSS class names to apply to anchor link elements when `anchorLink: true` (multiple classes can be specified with space separation)
|
|
38
|
+
* Default: ''
|
|
39
|
+
*/
|
|
40
|
+
anchorLinkClassName?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Tag name of the generated TOC list container element
|
|
43
|
+
* Default: 'ol' (ordered list)
|
|
44
|
+
*/
|
|
45
|
+
anchorContainerTagName?: AnchorContainerTagName;
|
|
46
|
+
/**
|
|
47
|
+
* Minimum heading level to include in TOC (1 = h1, 6 = h6)
|
|
48
|
+
* Default: 1
|
|
49
|
+
*/
|
|
50
|
+
minLevel?: HeadingLevel;
|
|
51
|
+
/**
|
|
52
|
+
* Maximum heading level to include in TOC (1 = h1, 6 = h6)
|
|
53
|
+
* Default: 6
|
|
54
|
+
*/
|
|
55
|
+
maxLevel?: HeadingLevel;
|
|
11
56
|
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/index.d.ts
|
|
59
|
+
type MokujiResult<T extends HTMLElement = HTMLElement> = {
|
|
60
|
+
element?: T;
|
|
61
|
+
list: HTMLUListElement | HTMLOListElement;
|
|
62
|
+
destroy: () => void;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Generate table of contents from headings within the given element (public API)
|
|
66
|
+
*/
|
|
67
|
+
declare const Mokuji: <T extends HTMLElement>(element: T | undefined, externalOptions?: MokujiOption) => MokujiResult<T> | undefined;
|
|
68
|
+
//#endregion
|
|
69
|
+
export { HeadingLevel, Mokuji, MokujiOption, MokujiResult };
|
|
70
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
const e=e=>[...e.querySelectorAll(`h1, h2, h3, h4, h5, h6`)],t=e=>document.createElement(e),n=e=>{for(let t of e)t.remove()},r=/\s+/g,i=/%+/g,a=/%[0-9A-F]{2}/i,o=/%[^0-9A-F]|%[0-9A-F][^0-9A-F]|%$/i,s=/_\d+$/,c=e=>e.replaceAll(r,`_`).replace(`:`,``),l=e=>encodeURIComponent(e).replaceAll(i,`.`),u=(e,t)=>{if(t){let t=c(e);return l(t)}else return encodeURIComponent(e.trim())},d=e=>{if(!a.test(e)||o.test(e))return e;try{return decodeURIComponent(e)}catch{return e}},f=(e,t)=>{let n=e.getAttribute(`id`)?.trim();if(n)return e.id=n,n;let r=(e.textContent||``).trim(),i=u(r,t);return e.id=i,i},p=(e,t)=>{let n=[...t],r=new Set,i=new Map,a=new Map,o=new Set;for(let[t,n]of e.entries()){let e=n.id||`mokuji-heading-${t}`,r=d(e);a.set(t,{originalId:e,decodedId:r}),o.add(r)}for(let[t,c]of e.entries()){let e=n[t],{originalId:l,decodedId:u}=a.get(t);if(!r.has(u)){c.id=l,r.add(u),e&&(e.href=`#${l}`);continue}let d=u.replace(s,``)||u,f=(i.get(d)||0)+1,p,m=f;for(;;){if(p=`${d}_${m}`,!r.has(p)&&!o.has(p)){f=m;break}if(o.has(p)&&!r.has(p)){m++;continue}m++}i.set(d,f),c.id=p,r.add(p),e&&(e.href=`#${p}`)}},m=e=>{let t=Number.parseInt(e.tagName.slice(1),10);return t>=1&&t<=6?t:6},h=(t,n,r)=>{let i=[],a=e(t);for(let e of a){let t=m(e);t>=n&&t<=r&&i.push(e)}return i},g=`data-mokuji-anchor`,_={anchorType:!0,anchorLink:!1,anchorLinkSymbol:`#`,anchorLinkPosition:`before`,anchorLinkClassName:``,anchorContainerTagName:`ol`,minLevel:1,maxLevel:6},v=e=>{let t=new Map;for(let n of e)n.hash&&n.hash.length>1&&t.set(n.hash.slice(1),n);return t},y=e=>{let t=new Map;for(let n of e){let e=n.textContent?.trim();e&&t.set(e,n)}return t},b=(e,t)=>e.get(t),x=(e,t)=>{let n=t.replace(/_\d+$/,``);if(n!==t)return e.get(n)},S=(e,t)=>{let n=t.trim();if(n)return e.get(n)},C=(e,t,n,r)=>{let i=b(e,t);if(i)return i;let a=x(e,t);if(a)return a;if(r)return S(r,n);{let t=n.trim();if(!t)return;for(let[,n]of e.entries())if(n.textContent?.trim()===t)return n;return}},w=(e,t)=>{let n=t.trim();n&&e.classList.add(...n.split(/\s+/).filter(Boolean))},T=e=>{let n=t(`a`);return n.setAttribute(g,``),w(n,e.anchorLinkClassName),n},E=(e,t,n,r,i)=>{let a=e.id,o=C(t,a,e.textContent||``,i);if(!o)return;let s=n.cloneNode(!1);return s.href=o.hash,s.textContent=r.anchorLinkSymbol,s},D=e=>{let t=e.querySelectorAll(`[${g}]`);n(t)},O=(e,t,n=`before`)=>{n===`before`?e.insertBefore(t,e.firstChild):e.append(t)},k=(e,t,n,r)=>{let i=T(n),a=[];for(let o of e){D(o);let e=E(o,t,i,n,r);if(!e)continue;O(o,e,n.anchorLinkPosition),a.push(e)}return a},A=(e,t,n,r)=>k(e,t,r,n),j=(e,t)=>{let n=[],r=[{level:0,items:n}];for(let n of e){let e=m(n),i=f(n,t),a={text:n.textContent,href:`#${i}`,level:e,children:[]},o=r.at(-1);for(;r.length>1&&e<=o.level;)r.pop(),o=r.at(-1);o.items.push(a),r.push({level:e,items:a.children})}return n},M=(e,n)=>{let r=t(`li`),i=t(`a`),a=n.tagName.toLowerCase(),o=t(a),s=(e,t)=>{for(let n of t){let t=r.cloneNode(!1),a=i.cloneNode(!1);if(a.href=n.href,a.textContent=n.text,t.append(a),n.children.length>0){let e=o.cloneNode(!1);s(e,n.children),t.append(e)}e.append(t)}};s(n,e)},N=(e,t,n)=>{let r=j(e,n);M(r,t)},P=e=>{let t={..._,...e};return t.minLevel=Math.max(1,Math.min(t.minLevel,6)),t.maxLevel=Math.max(t.minLevel,Math.min(t.maxLevel,6)),t},F=(e,n)=>{if(!e){console.warn(`Mokuji: Target element not found.`);return}let r=P(n),{minLevel:i,maxLevel:a}=r,o=h(e,i,a);if(o.length===0)return;let s=t(r.anchorContainerTagName);s.setAttribute(`data-mokuji-list`,``),N(o,s,r.anchorType);let c=[...s.querySelectorAll(`a`)];if(c.length===0)return;p(o,c);let l=[];if(r.anchorLink){let e=v(c),t=y(c),n=A(o,e,t,r);l.push(...n)}return{element:e,list:s,destroy:()=>{s.remove();for(let e of l)e.remove()}}};export{F as Mokuji};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dom.ts","../src/text.ts","../src/index.ts"],"names":["hasParentNode","element","parent","getHeadingsElement","createElement","tagName","storeIds","replaceSpacesWithUnderscores","text","convert2WikipediaStyleAnchor","anchor","censorshipId","headings","textContent","id","suffix_count","tmp_id","generateAnchorText","isConvertToWikipediaStyleAnchor","MOKUJI_LIST_DATASET_ATTRIBUTE","ANCHOR_DATASET_ATTRIBUTE","defaultOptions","generateAnchorsMap","anchors","anchorMap","i","anchorId","insertAnchorToHeadings","options","a","anchorLinkClassName","classNames","className","heading","matchedAnchor","removeDuplicateIds","idCountMap","list","originalId","count","newId","matchingAnchors","j","generateHierarchyList","elementContainer","number","elementListClone","elementAnchorClone","currentNumber","nextElementOListClone","anchorText","elementAnchor","elementList","Mokuji","externalOptions","anchorsMap","Destroy","mokujiAnchor","mokujiList"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * 親要素内に要素が存在するか判定する\n */\nexport const hasParentNode = (element: Node | null, parent: Node | null) => {\n if (!element || !parent) {\n return false;\n }\n\n return parent.contains(element);\n};\n\n/**\n * 見出し要素をすべて取得する\n */\nexport const getHeadingsElement = (element: Element) => {\n return element.querySelectorAll<HTMLHeadingElement>('h1, h2, h3, h4, h5, h6');\n};\n\n/**\n * 要素を作成する\n */\nexport const createElement = <T extends keyof HTMLElementTagNameMap>(tagName: T): HTMLElementTagNameMap[T] => {\n return document.createElement(tagName);\n};\n","export const storeIds = new Set<string>();\n\nconst replaceSpacesWithUnderscores = (text: string) => {\n return text.replaceAll(/\\s+/g, '_').replaceAll(':', '');\n};\n\nconst convert2WikipediaStyleAnchor = (anchor: string) => {\n return encodeURIComponent(anchor).replaceAll(/%+/g, '.');\n};\n\nexport const censorshipId = (headings: HTMLHeadingElement[], textContent = '') => {\n let id = textContent;\n let suffix_count = 1;\n\n // IDが重複していた場合はsuffixを付ける\n while (suffix_count <= headings.length) {\n const tmp_id = suffix_count === 1 ? id : `${id}_${suffix_count}`;\n\n if (!storeIds.has(tmp_id)) {\n id = tmp_id;\n storeIds.add(id);\n break;\n }\n\n suffix_count++;\n }\n\n return id;\n};\n\nexport const generateAnchorText = (text: string, isConvertToWikipediaStyleAnchor: boolean) => {\n // convert spaces to _\n let anchor = replaceSpacesWithUnderscores(text);\n\n // remove &\n anchor = anchor.replaceAll(/&+/g, '').replaceAll(/&+/g, '');\n\n if (isConvertToWikipediaStyleAnchor) {\n anchor = convert2WikipediaStyleAnchor(anchor);\n }\n\n return anchor;\n};\n","import { hasParentNode, getHeadingsElement, createElement } from './dom';\nimport type { MokujiOption } from './types';\nimport { censorshipId, generateAnchorText, storeIds } from './text';\n\nexport { MokujiOption };\n\nconst MOKUJI_LIST_DATASET_ATTRIBUTE = 'data-mokuji-list';\nconst ANCHOR_DATASET_ATTRIBUTE = 'data-mokuji-anchor';\n\nconst defaultOptions = {\n anchorType: true,\n anchorLink: false,\n anchorLinkSymbol: '#',\n anchorLinkPosition: 'before',\n anchorLinkClassName: '',\n anchorContainerTagName: 'ol',\n} as const;\n\nconst generateAnchorsMap = (anchors: HTMLAnchorElement[]) => {\n const anchorMap = new Map<string, HTMLAnchorElement>();\n\n for (let i = 0; i < anchors.length; i++) {\n const anchorId = anchors[i].hash.replace('#', '');\n anchorMap.set(anchorId, anchors[i]);\n }\n\n return anchorMap;\n};\n\nconst insertAnchorToHeadings = (\n headings: HTMLHeadingElement[],\n anchorMap: Map<string, HTMLAnchorElement>,\n options: MokujiOption,\n) => {\n const a = createElement('a');\n a.setAttribute(ANCHOR_DATASET_ATTRIBUTE, '');\n\n if (options.anchorLinkClassName) {\n const anchorLinkClassName = options.anchorLinkClassName.trim();\n const classNames = anchorLinkClassName.split(/\\s+/);\n // スペース区切りの場合\n if (classNames.length > 1) {\n // eslint-disable-next-line unicorn/no-array-for-each\n classNames.forEach((className) => {\n a.classList.add(className.trim());\n });\n } else {\n // 通常の場合\n a.classList.add(anchorLinkClassName);\n }\n }\n\n for (let i = 0; i < headings.length; i++) {\n const heading = headings[i];\n const matchedAnchor = anchorMap.get(encodeURIComponent(heading.id));\n\n if (!matchedAnchor) {\n continue;\n }\n\n // create anchor\n const anchor = a.cloneNode(false) as HTMLAnchorElement;\n anchor.setAttribute('href', matchedAnchor.hash);\n\n if (options.anchorLinkSymbol) {\n anchor.textContent = options.anchorLinkSymbol;\n }\n\n // insert anchor into headings\n if (options.anchorLinkPosition === 'before') {\n // before\n heading.insertBefore(anchor, heading.firstChild);\n } else {\n // after\n heading.append(anchor);\n }\n }\n};\n\nconst removeDuplicateIds = (headings: HTMLHeadingElement[], anchors: HTMLAnchorElement[]) => {\n const idCountMap = new Map<string, number>();\n const anchorMap = new Map<string, HTMLAnchorElement[]>();\n\n // Build an anchor map based on hash\n for (let i = 0; i < anchors.length; i++) {\n const anchor = anchors[i];\n const id = anchor.hash.slice(1); // remove the '#' prefix\n const list = anchorMap.get(id) || [];\n list.push(anchor);\n anchorMap.set(id, list);\n }\n\n // Deduplicate ids and update headings and anchors\n for (let i = 0; i < headings.length; i++) {\n const heading = headings[i];\n const originalId = heading.id;\n const count = idCountMap.get(originalId) || 0;\n\n // If this is a duplicate id, append count to make it unique\n if (count > 0) {\n const newId = `${originalId}-${count}`;\n heading.id = newId;\n\n // Update the href of matching anchors\n const matchingAnchors = anchorMap.get(originalId) || [];\n for (let j = 0; j < matchingAnchors.length; j++) {\n const anchor = matchingAnchors[j];\n anchor.href = `#${newId}`;\n }\n }\n\n idCountMap.set(originalId, count + 1);\n }\n};\n\nconst generateHierarchyList = (\n headings: HTMLHeadingElement[],\n elementContainer: HTMLUListElement | HTMLOListElement,\n isConvertToWikipediaStyleAnchor: boolean,\n) => {\n let number = 0;\n const elementListClone = createElement('li');\n const elementAnchorClone = createElement('a');\n\n for (let i = 0; i < headings.length; i++) {\n const heading = headings[i];\n const currentNumber = Number(heading.tagName[1]);\n\n // check list hierarchy\n if (number !== 0 && number < currentNumber) {\n // number of the heading is large (small as heading)\n const nextElementOListClone = createElement('ol');\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n elementContainer.lastChild.append(nextElementOListClone);\n elementContainer = nextElementOListClone;\n } else if (number !== 0 && number > currentNumber) {\n // number of heading is small (large as heading)\n for (let i = 0; i < number - currentNumber; i++) {\n if (hasParentNode(elementContainer, elementContainer.parentNode)) {\n elementContainer = elementContainer.parentNode?.parentNode as HTMLUListElement | HTMLOListElement;\n }\n }\n }\n\n const textContent = censorshipId(headings, heading.textContent || '');\n\n // headingへidを付与\n const anchorText = generateAnchorText(textContent, isConvertToWikipediaStyleAnchor);\n heading.id = anchorText;\n\n // add to wrapper\n const elementAnchor = elementAnchorClone.cloneNode(false) as HTMLAnchorElement;\n elementAnchor.href = `#${anchorText}`;\n elementAnchor.textContent = heading.textContent;\n const elementList = elementListClone.cloneNode(false) as HTMLLIElement;\n elementList.append(elementAnchor);\n\n elementContainer.append(elementList);\n\n // update current number\n number = currentNumber;\n }\n};\n\nexport const Mokuji = (\n element: HTMLElement | null,\n externalOptions?: MokujiOption,\n): HTMLUListElement | HTMLOListElement | undefined => {\n if (!element) {\n return;\n }\n\n // Merge the default options with the external options.\n const options = {\n ...defaultOptions,\n ...externalOptions,\n };\n\n const headings = [...getHeadingsElement(element)];\n\n if (headings.length === 0) {\n return;\n }\n\n // mokuji start\n const elementContainer = createElement(options.anchorContainerTagName);\n elementContainer.setAttribute(MOKUJI_LIST_DATASET_ATTRIBUTE, '');\n\n // generate mokuji list\n generateHierarchyList(headings, elementContainer, options.anchorType);\n\n const anchors = [...elementContainer.querySelectorAll('a')];\n\n if (anchors.length === 0) {\n return;\n }\n\n // remove duplicates by adding suffix\n removeDuplicateIds(headings, anchors);\n\n // setup anchor link\n if (options.anchorLink) {\n const anchorsMap = generateAnchorsMap(anchors);\n insertAnchorToHeadings(headings, anchorsMap, options);\n }\n\n return elementContainer;\n};\n\nexport const Destroy = () => {\n // アンカー: [data-mokuji-anchor]要素をすべて破棄する\n const mokujiAnchor = document.querySelectorAll(`[${ANCHOR_DATASET_ATTRIBUTE}]`);\n for (let i = mokujiAnchor.length - 1; i >= 0; i--) {\n const element = mokujiAnchor[i];\n element.remove();\n }\n\n // 目次リスト: [data-mokuji-list]要素を破棄する\n const mokujiList = document.querySelector(`[${MOKUJI_LIST_DATASET_ATTRIBUTE}]`);\n if (mokujiList) {\n mokujiList.remove();\n }\n\n // 格納したidをクリアして次回の採番時に影響しないようにする\n storeIds.clear();\n};\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["MIN_HEADING_LEVEL: HeadingLevel","MAX_HEADING_LEVEL: HeadingLevel","candidateId: string","filteredHeadings: HTMLHeadingElement[]","defaultOptions: Required<MokujiOption>","insertedAnchors: HTMLAnchorElement[]","rootLevelItems: TocItem[]","levelStack: { level: number; items: TocItem[] }[]","newItem: TocItem","insertedAnchors: HTMLAnchorElement[]"],"sources":["../src/utils/dom.ts","../src/heading.ts","../src/utils/constants.ts","../src/anchor.ts","../src/mokuji-core.ts","../src/index.ts"],"sourcesContent":["export const getAllHeadingElements = (containerElement: Element): HTMLHeadingElement[] => {\n const headings = containerElement.querySelectorAll('h1, h2, h3, h4, h5, h6');\n return [...headings] as HTMLHeadingElement[];\n};\n\nexport const createElement = <T extends keyof HTMLElementTagNameMap>(tagName: T): HTMLElementTagNameMap[T] => {\n return document.createElement(tagName);\n};\n\nexport const removeAllElements = (elements: NodeListOf<Element> | Element[]): void => {\n for (const element of elements) {\n element.remove();\n }\n};\n","import { getAllHeadingElements } from './utils/dom';\nimport type { HeadingLevel } from './types';\n\n/**\n * Regular expressions and symbols used for text processing and anchor text generation\n */\nconst WHITESPACE_PATTERN = /\\s+/g;\nconst COLON_CHARACTER = ':';\nconst PERCENT_ENCODING_PATTERN = /%+/g;\nconst DOT_REPLACEMENT = '.';\n\n/**\n * Regular expressions for checking the validity of URL-encoded strings\n */\nconst VALID_PERCENT_ENCODING = /%[0-9A-F]{2}/i;\nconst INVALID_PERCENT_PATTERN = /%[^0-9A-F]|%[0-9A-F][^0-9A-F]|%$/i;\n\nconst HEADING_DUPLICATE_SUFFIX_PATTERN = /_\\d+$/;\n\nconst MIN_HEADING_LEVEL: HeadingLevel = 1;\nconst MAX_HEADING_LEVEL: HeadingLevel = 6;\nconst FALLBACK_HEADING_LEVEL = MAX_HEADING_LEVEL;\n\n/**\n * Replace spaces with underscores and remove colons in text\n */\nconst replaceSpacesWithUnderscores = (text: string): string => {\n return text.replaceAll(WHITESPACE_PATTERN, '_').replace(COLON_CHARACTER, '');\n};\n\n/**\n * Convert text to Wikipedia-style anchor format\n */\nconst convertToWikipediaStyleAnchor = (anchor: string): string => {\n return encodeURIComponent(anchor).replaceAll(PERCENT_ENCODING_PATTERN, DOT_REPLACEMENT);\n};\n\n/**\n * Generate anchor text\n */\nexport const generateAnchorText = (baseId: string, isConvertToWikipediaStyleAnchor: boolean): string => {\n if (isConvertToWikipediaStyleAnchor) {\n // Wikipedia style: Replace spaces with underscores, encode, then replace % with dots\n const anchorText = replaceSpacesWithUnderscores(baseId);\n return convertToWikipediaStyleAnchor(anchorText);\n } else {\n // RFC 3986 compliant: Use encodeURIComponent for proper fragment identifier encoding\n return encodeURIComponent(baseId.trim());\n }\n};\n\n/**\n * Safely decode URI component\n */\nconst safeDecodeURIComponent = (encoded: string): string => {\n if (!VALID_PERCENT_ENCODING.test(encoded)) {\n return encoded;\n }\n\n if (INVALID_PERCENT_PATTERN.test(encoded)) {\n return encoded;\n }\n\n try {\n return decodeURIComponent(encoded);\n } catch {\n return encoded;\n }\n};\n\n/**\n * Assign ID to heading element\n */\nexport const assignInitialIdToHeading = (\n heading: HTMLHeadingElement,\n isConvertToWikipediaStyleAnchor: boolean,\n): string => {\n const existingId = heading.getAttribute('id')?.trim();\n if (existingId) {\n heading.id = existingId;\n return existingId;\n }\n\n const baseHeadingId = (heading.textContent || '').trim();\n const anchorText = generateAnchorText(baseHeadingId, isConvertToWikipediaStyleAnchor);\n heading.id = anchorText;\n return anchorText;\n};\n\n/**\n * Resolve duplicate heading IDs and update anchors\n */\nexport const ensureUniqueHeadingIds = (headings: HTMLHeadingElement[], anchors: HTMLAnchorElement[]) => {\n const anchorList = [...anchors];\n const usedIds = new Set<string>();\n const idCounts = new Map<string, number>();\n\n // Cache decoded IDs to avoid recomputation\n const decodedIdCache = new Map<number, { originalId: string; decodedId: string }>();\n const originalIds = new Set<string>();\n\n // First pass: collect and cache all IDs\n for (const [i, heading] of headings.entries()) {\n const originalId = heading.id || `mokuji-heading-${i}`;\n const decodedId = safeDecodeURIComponent(originalId);\n decodedIdCache.set(i, { originalId, decodedId });\n originalIds.add(decodedId);\n }\n\n // Second pass: resolve duplicates using cached values\n for (const [i, heading] of headings.entries()) {\n const anchor = anchorList[i];\n const cached = decodedIdCache.get(i)!;\n const { originalId, decodedId } = cached;\n\n // If this ID is not used yet, keep it as is\n if (!usedIds.has(decodedId)) {\n heading.id = originalId;\n usedIds.add(decodedId);\n\n if (anchor) {\n anchor.href = `#${originalId}`;\n }\n continue;\n }\n\n // ID is already used, need to find a unique variant\n const baseId = decodedId.replace(HEADING_DUPLICATE_SUFFIX_PATTERN, '') || decodedId;\n let counter = (idCounts.get(baseId) || 0) + 1;\n let candidateId: string;\n\n // Find the next available suffix, skipping those reserved for original IDs\n let startingCounter = counter;\n while (true) {\n candidateId = `${baseId}_${startingCounter}`;\n\n // Available if not used and not reserved for original IDs\n if (!usedIds.has(candidateId) && !originalIds.has(candidateId)) {\n counter = startingCounter;\n break;\n }\n\n // If reserved for original ID but not used yet, skip this counter\n if (originalIds.has(candidateId) && !usedIds.has(candidateId)) {\n startingCounter++;\n continue;\n }\n\n // If already used, try next counter\n startingCounter++;\n }\n\n idCounts.set(baseId, counter);\n heading.id = candidateId;\n usedIds.add(candidateId);\n\n if (anchor) {\n anchor.href = `#${candidateId}`;\n }\n }\n};\n\n/**\n * Get heading element level as a numeric value\n */\nexport const getHeadingLevel = (heading: HTMLHeadingElement): HeadingLevel => {\n const numericLevel = Number.parseInt(heading.tagName.slice(1), 10);\n if (numericLevel >= MIN_HEADING_LEVEL && numericLevel <= MAX_HEADING_LEVEL) {\n return numericLevel as HeadingLevel;\n }\n return FALLBACK_HEADING_LEVEL;\n};\n\n/**\n * Get heading elements within the specified level range\n */\nexport const getFilteredHeadings = (\n element: Element,\n minLevel: HeadingLevel,\n maxLevel: HeadingLevel,\n): HTMLHeadingElement[] => {\n const filteredHeadings: HTMLHeadingElement[] = [];\n const allHeadings = getAllHeadingElements(element);\n\n for (const heading of allHeadings) {\n const level = getHeadingLevel(heading);\n if (level >= minLevel && level <= maxLevel) {\n filteredHeadings.push(heading);\n }\n }\n\n return filteredHeadings;\n};\n","import type { MokujiOption } from '../types';\n\nexport const MOKUJI_LIST_DATASET_ATTRIBUTE = 'data-mokuji-list';\nexport const ANCHOR_DATASET_ATTRIBUTE = 'data-mokuji-anchor';\n\nexport const defaultOptions: Required<MokujiOption> = {\n anchorType: true,\n anchorLink: false,\n anchorLinkSymbol: '#',\n anchorLinkPosition: 'before',\n anchorLinkClassName: '',\n anchorContainerTagName: 'ol',\n minLevel: 1,\n maxLevel: 6,\n} as const;\n","import { createElement, removeAllElements } from './utils/dom';\nimport { ANCHOR_DATASET_ATTRIBUTE } from './utils/constants';\nimport type { MokujiOption, AnchorLinkPosition } from './types';\n\n/**\n * Creates a map from anchor elements within the table of contents, using their href hash values as keys\n */\nexport const createAnchorMap = (anchors: HTMLAnchorElement[]): Map<string, HTMLAnchorElement> => {\n const result = new Map<string, HTMLAnchorElement>();\n\n for (const anchor of anchors) {\n if (anchor.hash && anchor.hash.length > 1) {\n result.set(anchor.hash.slice(1), anchor);\n }\n }\n\n return result;\n};\n\n/**\n * Creates a map with anchor element text content as keys\n */\nexport const createTextToAnchorMap = (anchors: HTMLAnchorElement[]): Map<string, HTMLAnchorElement> => {\n const result = new Map<string, HTMLAnchorElement>();\n\n for (const anchor of anchors) {\n const text = anchor.textContent?.trim();\n if (text) {\n result.set(text, anchor);\n }\n }\n\n return result;\n};\n\n/**\n * Search directly by ID from the anchor map\n */\nexport const findAnchorById = (\n anchorMap: Map<string, HTMLAnchorElement>,\n id: string,\n): HTMLAnchorElement | undefined => {\n return anchorMap.get(id);\n};\n\n/**\n * Search by ID with numeric suffix removed\n */\nexport const findAnchorByIdWithoutSuffix = (\n anchorMap: Map<string, HTMLAnchorElement>,\n id: string,\n): HTMLAnchorElement | undefined => {\n // Check if ID is in \"ID_number\" format (for IDs modified due to duplication)\n const idWithoutSuffix = id.replace(/_\\d+$/, '');\n if (idWithoutSuffix !== id) {\n // Search for anchor with the original ID without suffix\n return anchorMap.get(idWithoutSuffix);\n }\n return undefined;\n};\n\n/**\n * Search for anchor by text content\n */\nexport const findAnchorByText = (\n anchorMap: Map<string, HTMLAnchorElement>,\n text: string,\n): HTMLAnchorElement | undefined => {\n const trimmedText = text.trim();\n if (!trimmedText) return undefined;\n\n // Search within anchor map by text content\n for (const [, anchor] of anchorMap.entries()) {\n if (anchor.textContent?.trim() === trimmedText) {\n return anchor;\n }\n }\n return undefined;\n};\n\n/**\n * Search anchor from text map\n */\nexport const findAnchorInTextMap = (\n textToAnchorMap: Map<string, HTMLAnchorElement>,\n text: string,\n): HTMLAnchorElement | undefined => {\n const trimmedText = text.trim();\n if (!trimmedText) return undefined;\n\n return textToAnchorMap.get(trimmedText);\n};\n\n/**\n * Find matching anchor with 3-level fallback (core implementation)\n */\nconst findMatchingAnchorCore = (\n anchorMap: Map<string, HTMLAnchorElement>,\n headingId: string,\n headingText: string,\n textToAnchorMap?: Map<string, HTMLAnchorElement>,\n): HTMLAnchorElement | undefined => {\n // 1. Direct ID search → 2. Search after suffix removal → 3. Search by text content\n const directMatch = findAnchorById(anchorMap, headingId);\n if (directMatch) return directMatch;\n\n const suffixMatch = findAnchorByIdWithoutSuffix(anchorMap, headingId);\n if (suffixMatch) return suffixMatch;\n\n // Use optimized text map if available, otherwise fallback to linear search\n if (textToAnchorMap) {\n return findAnchorInTextMap(textToAnchorMap, headingText);\n } else {\n // Fallback: linear search through anchor map\n const trimmedText = headingText.trim();\n if (!trimmedText) return undefined;\n\n for (const [, anchor] of anchorMap.entries()) {\n if (anchor.textContent?.trim() === trimmedText) {\n return anchor;\n }\n }\n return undefined;\n }\n};\n\n/**\n * Find matching anchor with 3-level fallback\n */\nexport const findMatchingAnchor = (\n anchorMap: Map<string, HTMLAnchorElement>,\n headingId: string,\n headingText: string,\n): HTMLAnchorElement | undefined => {\n return findMatchingAnchorCore(anchorMap, headingId, headingText);\n};\n\n/**\n * Find matching anchor with 3-level fallback using maps\n */\nexport const findMatchingAnchorWithMaps = (\n anchorMap: Map<string, HTMLAnchorElement>,\n textToAnchorMap: Map<string, HTMLAnchorElement>,\n headingId: string,\n headingText: string,\n): HTMLAnchorElement | undefined => {\n return findMatchingAnchorCore(anchorMap, headingId, headingText, textToAnchorMap);\n};\n\n/**\n * Apply multiple class names to an element\n */\nexport const applyClassNamesToElement = (element: HTMLElement, classNameString: string): void => {\n const trimmedClassNames = classNameString.trim();\n if (!trimmedClassNames) return;\n\n element.classList.add(...trimmedClassNames.split(/\\s+/).filter(Boolean));\n};\n\n/**\n * Create anchor template element\n */\nexport const createAnchorElement = (options: Required<MokujiOption>): HTMLAnchorElement => {\n const anchorTemplate = createElement('a');\n anchorTemplate.setAttribute(ANCHOR_DATASET_ATTRIBUTE, '');\n\n // Execute class name application even if anchorLinkClassName is empty string\n // Early return occurs within applyClassNamesToElement for empty strings\n applyClassNamesToElement(anchorTemplate, options.anchorLinkClassName);\n\n return anchorTemplate;\n};\n\n/**\n * Create anchor element corresponding to a heading element (core implementation)\n */\nconst createAnchorForHeadingCore = (\n heading: HTMLHeadingElement,\n anchorMap: Map<string, HTMLAnchorElement>,\n anchorTemplate: HTMLAnchorElement,\n options: Required<MokujiOption>,\n textToAnchorMap?: Map<string, HTMLAnchorElement>,\n): HTMLAnchorElement | undefined => {\n const headingId = heading.id;\n const matchedTocAnchor = findMatchingAnchorCore(anchorMap, headingId, heading.textContent || '', textToAnchorMap);\n\n // If no matching anchor is found ultimately\n if (!matchedTocAnchor) {\n return undefined;\n }\n\n const anchorElement = anchorTemplate.cloneNode(false) as HTMLAnchorElement;\n anchorElement.href = matchedTocAnchor.hash;\n anchorElement.textContent = options.anchorLinkSymbol;\n\n return anchorElement;\n};\n\n/**\n * Create anchor element corresponding to a heading element\n */\nexport const createAnchorForHeading = (\n heading: HTMLHeadingElement,\n anchorMap: Map<string, HTMLAnchorElement>,\n anchorTemplate: HTMLAnchorElement,\n options: Required<MokujiOption>,\n): HTMLAnchorElement | undefined => {\n return createAnchorForHeadingCore(heading, anchorMap, anchorTemplate, options);\n};\n\n/**\n * Create anchor element corresponding to a heading element using maps\n */\nexport const createAnchorForHeadingWithMaps = (\n heading: HTMLHeadingElement,\n anchorMap: Map<string, HTMLAnchorElement>,\n textToAnchorMap: Map<string, HTMLAnchorElement>,\n anchorTemplate: HTMLAnchorElement,\n options: Required<MokujiOption>,\n): HTMLAnchorElement | undefined => {\n return createAnchorForHeadingCore(heading, anchorMap, anchorTemplate, options, textToAnchorMap);\n};\n\n/**\n * Remove existing table of contents anchors from headings to prevent duplicate insertion\n */\nconst removeExistingAnchors = (heading: HTMLHeadingElement): void => {\n const existingAnchors = heading.querySelectorAll(`[${ANCHOR_DATASET_ATTRIBUTE}]`);\n removeAllElements(existingAnchors);\n};\n\n/**\n * Insert anchor element at specified position within heading element\n */\nconst placeAnchorInHeading = (\n heading: HTMLHeadingElement,\n anchor: HTMLAnchorElement,\n position: AnchorLinkPosition = 'before',\n): void => {\n if (position === 'before') {\n heading.insertBefore(anchor, heading.firstChild);\n } else {\n heading.append(anchor);\n }\n};\n\n/**\n * Add anchor links to heading elements (core implementation)\n */\nconst insertAnchorsIntoHeadingsCore = (\n headings: HTMLHeadingElement[],\n anchorMap: Map<string, HTMLAnchorElement>,\n options: Required<MokujiOption>,\n textToAnchorMap?: Map<string, HTMLAnchorElement>,\n): HTMLAnchorElement[] => {\n const anchorTemplate = createAnchorElement(options);\n const insertedAnchors: HTMLAnchorElement[] = [];\n\n for (const heading of headings) {\n removeExistingAnchors(heading);\n const anchor = createAnchorForHeadingCore(heading, anchorMap, anchorTemplate, options, textToAnchorMap);\n if (!anchor) continue;\n\n placeAnchorInHeading(heading, anchor, options.anchorLinkPosition);\n insertedAnchors.push(anchor);\n }\n\n return insertedAnchors;\n};\n\n/**\n * Add anchor links to heading elements\n * @returns Array of inserted anchor elements\n */\nexport const insertAnchorsIntoHeadings = (\n headings: HTMLHeadingElement[],\n anchorMap: Map<string, HTMLAnchorElement>,\n options: Required<MokujiOption>,\n): HTMLAnchorElement[] => {\n return insertAnchorsIntoHeadingsCore(headings, anchorMap, options);\n};\n\n/**\n * Add anchor links to heading elements using maps\n * @returns Array of inserted anchor elements\n */\nexport const insertAnchorsIntoHeadingsWithMaps = (\n headings: HTMLHeadingElement[],\n anchorMap: Map<string, HTMLAnchorElement>,\n textToAnchorMap: Map<string, HTMLAnchorElement>,\n options: Required<MokujiOption>,\n): HTMLAnchorElement[] => {\n return insertAnchorsIntoHeadingsCore(headings, anchorMap, options, textToAnchorMap);\n};\n","import { createElement } from './utils/dom';\nimport { assignInitialIdToHeading, getHeadingLevel } from './heading';\n\ntype TocItem = {\n text: string | null;\n href: string;\n level: number;\n children: TocItem[];\n};\n\ntype TableOfContentsContainer = HTMLUListElement | HTMLOListElement;\n\n/**\n * Build hierarchical data structure from headings\n */\nconst buildTocHierarchy = (headings: HTMLHeadingElement[], isConvertToWikipediaStyleAnchor: boolean): TocItem[] => {\n const rootLevelItems: TocItem[] = [];\n const levelStack: { level: number; items: TocItem[] }[] = [{ level: 0, items: rootLevelItems }];\n\n for (const heading of headings) {\n const currentHeadingLevel = getHeadingLevel(heading);\n const anchorText = assignInitialIdToHeading(heading, isConvertToWikipediaStyleAnchor);\n\n const newItem: TocItem = {\n text: heading.textContent,\n href: `#${anchorText}`,\n level: currentHeadingLevel,\n children: [],\n };\n\n // Find appropriate parent level\n let topLevel = levelStack.at(-1)!;\n while (levelStack.length > 1 && currentHeadingLevel <= topLevel.level) {\n levelStack.pop();\n topLevel = levelStack.at(-1)!;\n }\n\n topLevel.items.push(newItem);\n levelStack.push({ level: currentHeadingLevel, items: newItem.children });\n }\n\n return rootLevelItems;\n};\n\n/**\n * Generate DOM elements from TOC hierarchy\n */\nconst buildTocDom = (items: TocItem[], listContainer: TableOfContentsContainer): void => {\n const listItemTemplate = createElement('li');\n const anchorTemplate = createElement('a');\n const childListTagName = listContainer.tagName.toLowerCase() as 'ul' | 'ol';\n const childListTemplate = createElement(childListTagName);\n\n const buildListRecursive = (parentListElement: HTMLElement, tocItems: TocItem[]): void => {\n for (const item of tocItems) {\n const listItem = listItemTemplate.cloneNode(false) as HTMLLIElement;\n const anchor = anchorTemplate.cloneNode(false) as HTMLAnchorElement;\n\n anchor.href = item.href;\n anchor.textContent = item.text;\n listItem.append(anchor);\n\n if (item.children.length > 0) {\n const childList = childListTemplate.cloneNode(false) as TableOfContentsContainer;\n buildListRecursive(childList, item.children);\n listItem.append(childList);\n }\n parentListElement.append(listItem);\n }\n };\n\n buildListRecursive(listContainer, items);\n};\n\n/**\n * Generate hierarchical table of contents data structure from heading elements and build DOM\n *\n * @param headings Array of heading elements to process\n * @param listContainer Container element to store the table of contents (ul or ol)\n * @param isConvertToWikipediaStyleAnchor Flag to generate Wikipedia-style anchors\n */\nexport const buildMokujiHierarchy = (\n headings: HTMLHeadingElement[],\n listContainer: TableOfContentsContainer,\n isConvertToWikipediaStyleAnchor: boolean,\n): void => {\n const tocItems = buildTocHierarchy(headings, isConvertToWikipediaStyleAnchor);\n buildTocDom(tocItems, listContainer);\n};\n","import { createElement } from './utils/dom';\nimport type { MokujiOption, HeadingLevel } from './types';\nimport { getFilteredHeadings, ensureUniqueHeadingIds } from './heading';\nimport { createAnchorMap, createTextToAnchorMap, insertAnchorsIntoHeadingsWithMaps } from './anchor';\nimport { buildMokujiHierarchy } from './mokuji-core';\nimport { MOKUJI_LIST_DATASET_ATTRIBUTE, defaultOptions } from './utils/constants';\n\nexport type MokujiResult<T extends HTMLElement = HTMLElement> = {\n element?: T;\n list: HTMLUListElement | HTMLOListElement;\n destroy: () => void;\n};\n\nexport { MokujiOption, HeadingLevel };\n\n/**\n * Process option settings, merge with default values, and restrict to valid range\n */\nconst processOptions = (externalOptions?: MokujiOption): Required<MokujiOption> => {\n const options = {\n ...defaultOptions,\n ...externalOptions,\n };\n\n options.minLevel = Math.max(1, Math.min(options.minLevel, 6)) as HeadingLevel;\n options.maxLevel = Math.max(options.minLevel, Math.min(options.maxLevel, 6)) as HeadingLevel;\n\n return options;\n};\n\n/**\n * Generate table of contents from headings within the given element (public API)\n */\nexport const Mokuji = <T extends HTMLElement>(\n element: T | undefined,\n externalOptions?: MokujiOption,\n): MokujiResult<T> | undefined => {\n if (!element) {\n console.warn('Mokuji: Target element not found.');\n return undefined;\n }\n\n const options = processOptions(externalOptions);\n\n const { minLevel, maxLevel } = options;\n const filteredHeadings = getFilteredHeadings(element, minLevel, maxLevel);\n\n if (filteredHeadings.length === 0) {\n return undefined;\n }\n\n // Generate table of contents\n const listContainer = createElement(options.anchorContainerTagName);\n listContainer.setAttribute(MOKUJI_LIST_DATASET_ATTRIBUTE, '');\n\n buildMokujiHierarchy(filteredHeadings, listContainer, options.anchorType);\n\n const anchors = [...listContainer.querySelectorAll('a')];\n\n if (anchors.length === 0) {\n return undefined;\n }\n\n ensureUniqueHeadingIds(filteredHeadings, anchors);\n\n const insertedAnchors: HTMLAnchorElement[] = [];\n\n if (options.anchorLink) {\n const anchorsMap = createAnchorMap(anchors);\n const textToAnchorMap = createTextToAnchorMap(anchors);\n const anchorElements = insertAnchorsIntoHeadingsWithMaps(filteredHeadings, anchorsMap, textToAnchorMap, options);\n insertedAnchors.push(...anchorElements);\n }\n\n const destroy = () => {\n listContainer.remove();\n\n for (const anchor of insertedAnchors) {\n anchor.remove();\n }\n };\n\n return { element, list: listContainer, destroy };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mokuji.js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "A table of content JavaScript Library",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"table of content",
|
|
7
|
+
"toc",
|
|
8
|
+
"contents",
|
|
9
|
+
"index"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/hiro0218/mokuji.js",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/hiro0218/mokuji.js/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/hiro0218/mokuji.js.git"
|
|
10
18
|
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "hiro",
|
|
21
|
+
"sideEffects": false,
|
|
11
22
|
"type": "module",
|
|
12
|
-
"main": "./dist/index.js",
|
|
13
23
|
"exports": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"default": "./dist/index.js"
|
|
24
|
+
".": "./dist/index.js",
|
|
25
|
+
"./package.json": "./package.json"
|
|
17
26
|
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
18
29
|
"types": "./dist/index.d.ts",
|
|
19
30
|
"directories": {
|
|
20
31
|
"lib": "lib"
|
|
@@ -25,6 +36,14 @@
|
|
|
25
36
|
"package.json",
|
|
26
37
|
"README.md"
|
|
27
38
|
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsdown",
|
|
41
|
+
"dev": "tsdown --watch",
|
|
42
|
+
"lint": "eslint --cache ./src/**/*.ts",
|
|
43
|
+
"prepare": "npm run build && husky",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest"
|
|
46
|
+
},
|
|
28
47
|
"lint-staged": {
|
|
29
48
|
"*.{js,ts,tsx}": [
|
|
30
49
|
"eslint --fix --cache",
|
|
@@ -35,32 +54,20 @@
|
|
|
35
54
|
"bash -c 'tsc --noEmit --skipLibCheck'"
|
|
36
55
|
]
|
|
37
56
|
},
|
|
38
|
-
"repository": {
|
|
39
|
-
"type": "git",
|
|
40
|
-
"url": "https://github.com/hiro0218/mokuji.js.git"
|
|
41
|
-
},
|
|
42
|
-
"keywords": [
|
|
43
|
-
"table of content",
|
|
44
|
-
"toc",
|
|
45
|
-
"contents",
|
|
46
|
-
"index"
|
|
47
|
-
],
|
|
48
|
-
"author": "hiro",
|
|
49
|
-
"license": "MIT",
|
|
50
|
-
"bugs": {
|
|
51
|
-
"url": "https://github.com/hiro0218/mokuji.js/issues"
|
|
52
|
-
},
|
|
53
|
-
"homepage": "https://github.com/hiro0218/mokuji.js",
|
|
54
57
|
"devDependencies": {
|
|
55
|
-
"@
|
|
56
|
-
"@
|
|
57
|
-
"eslint": "^8.
|
|
58
|
-
"
|
|
59
|
-
"eslint
|
|
58
|
+
"@testing-library/dom": "^10.4.1",
|
|
59
|
+
"@types/node": "~22.15.3",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
|
61
|
+
"@vitest/browser": "^3.2.4",
|
|
62
|
+
"eslint": "^9.36.0",
|
|
63
|
+
"eslint-config-prettier": "^10.1.8",
|
|
64
|
+
"eslint-plugin-unicorn": "^61.0.2",
|
|
60
65
|
"husky": "^9.1.7",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
66
|
+
"jsdom": "^26.1.0",
|
|
67
|
+
"lint-staged": "^16.1.6",
|
|
68
|
+
"prettier": "^3.6.2",
|
|
69
|
+
"tsdown": "^0.15.3",
|
|
70
|
+
"typescript": "~5.8.3",
|
|
71
|
+
"vitest": "^3.2.4"
|
|
65
72
|
}
|
|
66
73
|
}
|
package/dist/index.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
'use strict';var L=(t,o)=>!t||!o?!1:o.contains(t),f=t=>t.querySelectorAll("h1, h2, h3, h4, h5, h6"),h=t=>document.createElement(t);var u=new Set,H=t=>t.replaceAll(/\s+/g,"_").replaceAll(":",""),k=t=>encodeURIComponent(t).replaceAll(/%+/g,"."),T=(t,o="")=>{let e=o,n=1;for(;n<=t.length;){let r=n===1?e:`${e}_${n}`;if(!u.has(r)){e=r,u.add(e);break}n++;}return e},M=(t,o)=>{let e=H(t);return e=e.replaceAll(/&+/g,"").replaceAll(/&+/g,""),o&&(e=k(e)),e};var A="data-mokuji-list",E="data-mokuji-anchor",N={anchorType:!0,anchorLink:!1,anchorLinkSymbol:"#",anchorLinkPosition:"before",anchorLinkClassName:"",anchorContainerTagName:"ol"},x=t=>{let o=new Map;for(let e=0;e<t.length;e++){let n=t[e].hash.replace("#","");o.set(n,t[e]);}return o},b=(t,o,e)=>{let n=h("a");if(n.setAttribute(E,""),e.anchorLinkClassName){let r=e.anchorLinkClassName.trim(),s=r.split(/\s+/);s.length>1?s.forEach(c=>{n.classList.add(c.trim());}):n.classList.add(r);}for(let r=0;r<t.length;r++){let s=t[r],c=o.get(encodeURIComponent(s.id));if(!c)continue;let a=n.cloneNode(!1);a.setAttribute("href",c.hash),e.anchorLinkSymbol&&(a.textContent=e.anchorLinkSymbol),e.anchorLinkPosition==="before"?s.insertBefore(a,s.firstChild):s.append(a);}},y=(t,o)=>{let e=new Map,n=new Map;for(let r=0;r<o.length;r++){let s=o[r],c=s.hash.slice(1),a=n.get(c)||[];a.push(s),n.set(c,a);}for(let r=0;r<t.length;r++){let s=t[r],c=s.id,a=e.get(c)||0;if(a>0){let l=`${c}-${a}`;s.id=l;let p=n.get(c)||[];for(let i=0;i<p.length;i++){let m=p[i];m.href=`#${l}`;}}e.set(c,a+1);}},C=(t,o,e)=>{let n=0,r=h("li"),s=h("a");for(let c=0;c<t.length;c++){let a=t[c],l=Number(a.tagName[1]);if(n!==0&&n<l){let d=h("ol");o.lastChild.append(d),o=d;}else if(n!==0&&n>l)for(let d=0;d<n-l;d++)L(o,o.parentNode)&&(o=o.parentNode?.parentNode);let p=T(t,a.textContent||""),i=M(p,e);a.id=i;let m=s.cloneNode(!1);m.href=`#${i}`,m.textContent=a.textContent;let g=r.cloneNode(!1);g.append(m),o.append(g),n=l;}},U=(t,o)=>{if(!t)return;let e={...N,...o},n=[...f(t)];if(n.length===0)return;let r=h(e.anchorContainerTagName);r.setAttribute(A,""),C(n,r,e.anchorType);let s=[...r.querySelectorAll("a")];if(s.length!==0){if(y(n,s),e.anchorLink){let c=x(s);b(n,c,e);}return r}},_=()=>{let t=document.querySelectorAll(`[${E}]`);for(let e=t.length-1;e>=0;e--)t[e].remove();let o=document.querySelector(`[${A}]`);o&&o.remove(),u.clear();};exports.Destroy=_;exports.Mokuji=U;//# sourceMappingURL=index.cjs.map
|
|
2
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dom.ts","../src/text.ts","../src/index.ts"],"names":["hasParentNode","element","parent","getHeadingsElement","createElement","tagName","storeIds","replaceSpacesWithUnderscores","text","convert2WikipediaStyleAnchor","anchor","censorshipId","headings","textContent","id","suffix_count","tmp_id","generateAnchorText","isConvertToWikipediaStyleAnchor","MOKUJI_LIST_DATASET_ATTRIBUTE","ANCHOR_DATASET_ATTRIBUTE","defaultOptions","generateAnchorsMap","anchors","anchorMap","i","anchorId","insertAnchorToHeadings","options","a","anchorLinkClassName","classNames","className","heading","matchedAnchor","removeDuplicateIds","idCountMap","list","originalId","count","newId","matchingAnchors","j","generateHierarchyList","elementContainer","number","elementListClone","elementAnchorClone","currentNumber","nextElementOListClone","anchorText","elementAnchor","elementList","Mokuji","externalOptions","anchorsMap","Destroy","mokujiAnchor","mokujiList"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * 親要素内に要素が存在するか判定する\n */\nexport const hasParentNode = (element: Node | null, parent: Node | null) => {\n if (!element || !parent) {\n return false;\n }\n\n return parent.contains(element);\n};\n\n/**\n * 見出し要素をすべて取得する\n */\nexport const getHeadingsElement = (element: Element) => {\n return element.querySelectorAll<HTMLHeadingElement>('h1, h2, h3, h4, h5, h6');\n};\n\n/**\n * 要素を作成する\n */\nexport const createElement = <T extends keyof HTMLElementTagNameMap>(tagName: T): HTMLElementTagNameMap[T] => {\n return document.createElement(tagName);\n};\n","export const storeIds = new Set<string>();\n\nconst replaceSpacesWithUnderscores = (text: string) => {\n return text.replaceAll(/\\s+/g, '_').replaceAll(':', '');\n};\n\nconst convert2WikipediaStyleAnchor = (anchor: string) => {\n return encodeURIComponent(anchor).replaceAll(/%+/g, '.');\n};\n\nexport const censorshipId = (headings: HTMLHeadingElement[], textContent = '') => {\n let id = textContent;\n let suffix_count = 1;\n\n // IDが重複していた場合はsuffixを付ける\n while (suffix_count <= headings.length) {\n const tmp_id = suffix_count === 1 ? id : `${id}_${suffix_count}`;\n\n if (!storeIds.has(tmp_id)) {\n id = tmp_id;\n storeIds.add(id);\n break;\n }\n\n suffix_count++;\n }\n\n return id;\n};\n\nexport const generateAnchorText = (text: string, isConvertToWikipediaStyleAnchor: boolean) => {\n // convert spaces to _\n let anchor = replaceSpacesWithUnderscores(text);\n\n // remove &\n anchor = anchor.replaceAll(/&+/g, '').replaceAll(/&+/g, '');\n\n if (isConvertToWikipediaStyleAnchor) {\n anchor = convert2WikipediaStyleAnchor(anchor);\n }\n\n return anchor;\n};\n","import { hasParentNode, getHeadingsElement, createElement } from './dom';\nimport type { MokujiOption } from './types';\nimport { censorshipId, generateAnchorText, storeIds } from './text';\n\nexport { MokujiOption };\n\nconst MOKUJI_LIST_DATASET_ATTRIBUTE = 'data-mokuji-list';\nconst ANCHOR_DATASET_ATTRIBUTE = 'data-mokuji-anchor';\n\nconst defaultOptions = {\n anchorType: true,\n anchorLink: false,\n anchorLinkSymbol: '#',\n anchorLinkPosition: 'before',\n anchorLinkClassName: '',\n anchorContainerTagName: 'ol',\n} as const;\n\nconst generateAnchorsMap = (anchors: HTMLAnchorElement[]) => {\n const anchorMap = new Map<string, HTMLAnchorElement>();\n\n for (let i = 0; i < anchors.length; i++) {\n const anchorId = anchors[i].hash.replace('#', '');\n anchorMap.set(anchorId, anchors[i]);\n }\n\n return anchorMap;\n};\n\nconst insertAnchorToHeadings = (\n headings: HTMLHeadingElement[],\n anchorMap: Map<string, HTMLAnchorElement>,\n options: MokujiOption,\n) => {\n const a = createElement('a');\n a.setAttribute(ANCHOR_DATASET_ATTRIBUTE, '');\n\n if (options.anchorLinkClassName) {\n const anchorLinkClassName = options.anchorLinkClassName.trim();\n const classNames = anchorLinkClassName.split(/\\s+/);\n // スペース区切りの場合\n if (classNames.length > 1) {\n // eslint-disable-next-line unicorn/no-array-for-each\n classNames.forEach((className) => {\n a.classList.add(className.trim());\n });\n } else {\n // 通常の場合\n a.classList.add(anchorLinkClassName);\n }\n }\n\n for (let i = 0; i < headings.length; i++) {\n const heading = headings[i];\n const matchedAnchor = anchorMap.get(encodeURIComponent(heading.id));\n\n if (!matchedAnchor) {\n continue;\n }\n\n // create anchor\n const anchor = a.cloneNode(false) as HTMLAnchorElement;\n anchor.setAttribute('href', matchedAnchor.hash);\n\n if (options.anchorLinkSymbol) {\n anchor.textContent = options.anchorLinkSymbol;\n }\n\n // insert anchor into headings\n if (options.anchorLinkPosition === 'before') {\n // before\n heading.insertBefore(anchor, heading.firstChild);\n } else {\n // after\n heading.append(anchor);\n }\n }\n};\n\nconst removeDuplicateIds = (headings: HTMLHeadingElement[], anchors: HTMLAnchorElement[]) => {\n const idCountMap = new Map<string, number>();\n const anchorMap = new Map<string, HTMLAnchorElement[]>();\n\n // Build an anchor map based on hash\n for (let i = 0; i < anchors.length; i++) {\n const anchor = anchors[i];\n const id = anchor.hash.slice(1); // remove the '#' prefix\n const list = anchorMap.get(id) || [];\n list.push(anchor);\n anchorMap.set(id, list);\n }\n\n // Deduplicate ids and update headings and anchors\n for (let i = 0; i < headings.length; i++) {\n const heading = headings[i];\n const originalId = heading.id;\n const count = idCountMap.get(originalId) || 0;\n\n // If this is a duplicate id, append count to make it unique\n if (count > 0) {\n const newId = `${originalId}-${count}`;\n heading.id = newId;\n\n // Update the href of matching anchors\n const matchingAnchors = anchorMap.get(originalId) || [];\n for (let j = 0; j < matchingAnchors.length; j++) {\n const anchor = matchingAnchors[j];\n anchor.href = `#${newId}`;\n }\n }\n\n idCountMap.set(originalId, count + 1);\n }\n};\n\nconst generateHierarchyList = (\n headings: HTMLHeadingElement[],\n elementContainer: HTMLUListElement | HTMLOListElement,\n isConvertToWikipediaStyleAnchor: boolean,\n) => {\n let number = 0;\n const elementListClone = createElement('li');\n const elementAnchorClone = createElement('a');\n\n for (let i = 0; i < headings.length; i++) {\n const heading = headings[i];\n const currentNumber = Number(heading.tagName[1]);\n\n // check list hierarchy\n if (number !== 0 && number < currentNumber) {\n // number of the heading is large (small as heading)\n const nextElementOListClone = createElement('ol');\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n elementContainer.lastChild.append(nextElementOListClone);\n elementContainer = nextElementOListClone;\n } else if (number !== 0 && number > currentNumber) {\n // number of heading is small (large as heading)\n for (let i = 0; i < number - currentNumber; i++) {\n if (hasParentNode(elementContainer, elementContainer.parentNode)) {\n elementContainer = elementContainer.parentNode?.parentNode as HTMLUListElement | HTMLOListElement;\n }\n }\n }\n\n const textContent = censorshipId(headings, heading.textContent || '');\n\n // headingへidを付与\n const anchorText = generateAnchorText(textContent, isConvertToWikipediaStyleAnchor);\n heading.id = anchorText;\n\n // add to wrapper\n const elementAnchor = elementAnchorClone.cloneNode(false) as HTMLAnchorElement;\n elementAnchor.href = `#${anchorText}`;\n elementAnchor.textContent = heading.textContent;\n const elementList = elementListClone.cloneNode(false) as HTMLLIElement;\n elementList.append(elementAnchor);\n\n elementContainer.append(elementList);\n\n // update current number\n number = currentNumber;\n }\n};\n\nexport const Mokuji = (\n element: HTMLElement | null,\n externalOptions?: MokujiOption,\n): HTMLUListElement | HTMLOListElement | undefined => {\n if (!element) {\n return;\n }\n\n // Merge the default options with the external options.\n const options = {\n ...defaultOptions,\n ...externalOptions,\n };\n\n const headings = [...getHeadingsElement(element)];\n\n if (headings.length === 0) {\n return;\n }\n\n // mokuji start\n const elementContainer = createElement(options.anchorContainerTagName);\n elementContainer.setAttribute(MOKUJI_LIST_DATASET_ATTRIBUTE, '');\n\n // generate mokuji list\n generateHierarchyList(headings, elementContainer, options.anchorType);\n\n const anchors = [...elementContainer.querySelectorAll('a')];\n\n if (anchors.length === 0) {\n return;\n }\n\n // remove duplicates by adding suffix\n removeDuplicateIds(headings, anchors);\n\n // setup anchor link\n if (options.anchorLink) {\n const anchorsMap = generateAnchorsMap(anchors);\n insertAnchorToHeadings(headings, anchorsMap, options);\n }\n\n return elementContainer;\n};\n\nexport const Destroy = () => {\n // アンカー: [data-mokuji-anchor]要素をすべて破棄する\n const mokujiAnchor = document.querySelectorAll(`[${ANCHOR_DATASET_ATTRIBUTE}]`);\n for (let i = mokujiAnchor.length - 1; i >= 0; i--) {\n const element = mokujiAnchor[i];\n element.remove();\n }\n\n // 目次リスト: [data-mokuji-list]要素を破棄する\n const mokujiList = document.querySelector(`[${MOKUJI_LIST_DATASET_ATTRIBUTE}]`);\n if (mokujiList) {\n mokujiList.remove();\n }\n\n // 格納したidをクリアして次回の採番時に影響しないようにする\n storeIds.clear();\n};\n"]}
|
package/dist/index.d.cts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
type AnchorContainerTagNameProps = 'ul' | 'ol';
|
|
2
|
-
type MokujiOption = {
|
|
3
|
-
anchorType?: boolean;
|
|
4
|
-
anchorLink?: boolean;
|
|
5
|
-
anchorLinkSymbol?: string;
|
|
6
|
-
/** @deprecated use anchorLinkPosition */
|
|
7
|
-
anchorLinkBefore?: boolean;
|
|
8
|
-
anchorLinkPosition?: 'before' | 'after';
|
|
9
|
-
anchorLinkClassName?: string;
|
|
10
|
-
anchorContainerTagName?: AnchorContainerTagNameProps;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
declare const Mokuji: (element: HTMLElement | null, externalOptions?: MokujiOption) => HTMLUListElement | HTMLOListElement | undefined;
|
|
14
|
-
declare const Destroy: () => void;
|
|
15
|
-
|
|
16
|
-
export { Destroy, Mokuji, type MokujiOption };
|