mokuji.js 4.6.0 → 4.8.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 CHANGED
@@ -32,6 +32,9 @@ listElement?.appendChild(mokujiList);
32
32
  anchorLinkSymbol: '#',
33
33
  anchorLinkPosition: 'before',
34
34
  anchorLinkClassName: '',
35
+ anchorContainerTagName: 'ol',
36
+ minLevel: 1,
37
+ maxLevel: 6,
35
38
  }
36
39
  ```
37
40
 
@@ -62,12 +65,27 @@ set the anchor link symbol
62
65
 
63
66
  set position (before/after) the anchor link in headings.
64
67
 
65
- Set the position of the anchor link with 'before' or 'after'.
66
-
67
-
68
68
  ### `anchorLinkClassName`
69
69
 
70
70
  (default: `''`)
71
71
 
72
- set anchor link class name.
72
+ set anchor link class name. Multiple class names can be specified with spaces.
73
+
74
+ ### `anchorContainerTagName`
75
+
76
+ (default: `'ol'`)
77
+
78
+ set the container element tag name for the table of contents. Possible values are 'ol' or 'ul'.
79
+
80
+ ### `minLevel`
81
+
82
+ (default: `1`)
83
+
84
+ set the minimum heading level to include in the table of contents (1 means h1).
85
+
86
+ ### `maxLevel`
87
+
88
+ (default: `6`)
89
+
90
+ set the maximum heading level to include in the table of contents (6 means h6).
73
91
 
package/dist/index.d.ts CHANGED
@@ -1,16 +1,80 @@
1
- type AnchorContainerTagNameProps = 'ul' | 'ol';
1
+ /**
2
+ * 目次生成のための型定義を提供するモジュール
3
+ */
4
+ /**
5
+ * 目次コンテナとして使用可能なHTML要素タグ名
6
+ */
7
+ type AnchorContainerTagName = 'ul' | 'ol';
8
+ /**
9
+ * 見出しレベルを表す型(h1-h6に対応する1-6の値)
10
+ */
11
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
12
+ /**
13
+ * アンカーリンクの配置位置
14
+ */
15
+ type AnchorLinkPosition = 'before' | 'after';
16
+ /**
17
+ * 目次生成のオプション設定
18
+ */
2
19
  type MokujiOption = {
20
+ /**
21
+ * Wikipediaスタイルのアンカーを生成するかどうか
22
+ * デフォルト: true (例: "見出し_テキスト" -> "見出し_テキスト", "見出し:テキスト" -> "見出しテキスト")
23
+ * falseの場合、スペースは '_' に置換されるが、エンコードや特殊文字の変換は最小限になる
24
+ */
3
25
  anchorType?: boolean;
26
+ /**
27
+ * 見出し要素の隣に、その見出しへのアンカーリンク(例: #)を追加するかどうか
28
+ * デフォルト: false
29
+ */
4
30
  anchorLink?: boolean;
31
+ /**
32
+ * `anchorLink: true` の場合に表示するシンボルまたはテキスト
33
+ * デフォルト: '#'
34
+ */
5
35
  anchorLinkSymbol?: string;
6
- /** @deprecated use anchorLinkPosition */
7
- anchorLinkBefore?: boolean;
8
- anchorLinkPosition?: 'before' | 'after';
36
+ /**
37
+ * `anchorLink: true` の場合のアンカーリンクの配置位置(見出しテキストの前または後)
38
+ * デフォルト: 'before'
39
+ */
40
+ anchorLinkPosition?: AnchorLinkPosition;
41
+ /**
42
+ * `anchorLink: true` の場合にアンカーリンク要素に適用するCSSクラス名(スペース区切りで複数指定可)
43
+ * デフォルト: ''
44
+ */
9
45
  anchorLinkClassName?: string;
10
- anchorContainerTagName?: AnchorContainerTagNameProps;
46
+ /**
47
+ * 生成される目次のリストコンテナ要素のタグ名
48
+ * デフォルト: 'ol' (順序付きリスト)
49
+ */
50
+ anchorContainerTagName?: AnchorContainerTagName;
51
+ /**
52
+ * 目次に含める最小の見出しレベル (1 = h1, 6 = h6)
53
+ * デフォルト: 1
54
+ */
55
+ minLevel?: HeadingLevel;
56
+ /**
57
+ * 目次に含める最大の見出しレベル (1 = h1, 6 = h6)
58
+ * デフォルト: 6
59
+ */
60
+ maxLevel?: HeadingLevel;
11
61
  };
12
62
 
13
- declare const Mokuji: (element: HTMLElement | null, externalOptions?: MokujiOption) => HTMLUListElement | HTMLOListElement | undefined;
63
+ /**
64
+ * 目次生成の結果型定義
65
+ */
66
+ type MokujiResult<T extends HTMLElement = HTMLElement> = {
67
+ element?: T;
68
+ list: HTMLUListElement | HTMLOListElement;
69
+ };
70
+
71
+ /**
72
+ * 与えられた要素内の見出しから目次を生成する (公開API)
73
+ */
74
+ declare const Mokuji: <T extends HTMLElement>(element: T | undefined, externalOptions?: MokujiOption) => MokujiResult<T> | undefined;
75
+ /**
76
+ * 生成された目次とアンカーリンクを破棄(削除)する
77
+ */
14
78
  declare const Destroy: () => void;
15
79
 
16
- export { Destroy, Mokuji, type MokujiOption };
80
+ export { Destroy, type HeadingLevel, Mokuji, type MokujiOption, type MokujiResult };
package/dist/index.js CHANGED
@@ -1,5 +1,2 @@
1
- var L=(t,n)=>!t||!n?!1:n.contains(t),T=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,"."),f=(t,n="")=>{let e=n,o=1;for(;o<=t.length;){let r=o===1?e:`${e}_${o}`;if(!u.has(r)){e=r,u.add(e);break}o++;}return e},M=(t,n)=>{let e=H(t);return e=e.replaceAll(/&+/g,"").replaceAll(/&amp;+/g,""),n&&(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 n=new Map;for(let e=0;e<t.length;e++){let o=t[e].hash.replace("#","");n.set(o,t[e]);}return n},b=(t,n,e)=>{let o=h("a");o.setAttribute(E,""),e.anchorLinkClassName&&o.classList.add(e.anchorLinkClassName);for(let r=0;r<t.length;r++){let s=t[r],c=n.get(encodeURIComponent(s.id));if(!c)continue;let l=o.cloneNode(!1);l.setAttribute("href",c.hash),e.anchorLinkSymbol&&(l.textContent=e.anchorLinkSymbol),e.anchorLinkPosition==="before"?s.insertBefore(l,s.firstChild):s.append(l);}},y=(t,n)=>{let e=new Map,o=new Map;for(let r=0;r<n.length;r++){let s=n[r],c=s.hash.slice(1),l=o.get(c)||[];l.push(s),o.set(c,l);}for(let r=0;r<t.length;r++){let s=t[r],c=s.id,l=e.get(c)||0;if(l>0){let a=`${c}-${l}`;s.id=a;let p=o.get(c)||[];for(let i=0;i<p.length;i++){let m=p[i];m.href=`#${a}`;}}e.set(c,l+1);}},I=(t,n,e)=>{let o=0,r=h("li"),s=h("a");for(let c=0;c<t.length;c++){let l=t[c],a=Number(l.tagName[1]);if(o!==0&&o<a){let d=h("ol");n.lastChild.append(d),n=d;}else if(o!==0&&o>a)for(let d=0;d<o-a;d++)L(n,n.parentNode)&&(n=n.parentNode?.parentNode);let p=f(t,l.textContent||""),i=M(p,e);l.id=i;let m=s.cloneNode(!1);m.href=`#${i}`,m.textContent=l.textContent;let g=r.cloneNode(!1);g.append(m),n.append(g),o=a;}},U=(t,n)=>{if(!t)return;let e={...N,...n},o=[...T(t)];if(o.length===0)return;let r=h(e.anchorContainerTagName);r.setAttribute(A,""),I(o,r,e.anchorType);let s=[...r.querySelectorAll("a")];if(s.length!==0){if(y(o,s),e.anchorLink){let c=x(s);b(o,c,e);}return r}},_=()=>{let t=document.querySelectorAll(`[${E}]`);for(let e=t.length-1;e>=0;e--)t[e].remove();let n=document.querySelector(`[${A}]`);n&&n.remove(),u.clear();};
2
-
3
- export { _ as Destroy, U as Mokuji };
4
- //# sourceMappingURL=index.js.map
1
+ var M=e=>[...e.querySelectorAll("h1, h2, h3, h4, h5, h6")],E=new Map,T=e=>(E.has(e)||E.set(e,document.createElement(e)),E.get(e).cloneNode(false));var y=/\s+/g,b=":",j=/&+/g,P=/&amp;+/g,S=/%+/g,U=".",D=e=>e.replaceAll(y,"_").replace(b,""),q=e=>encodeURIComponent(e).replaceAll(S,U),I=(e,n)=>{let t=D(e);return n&&(t=q(t)),!n&&t.includes("&")&&(t=t.replaceAll(j,"").replaceAll(P,"")),t};var w=/%[0-9A-F]{2}/gi,B=/%[^0-9A-F]|%[0-9A-F][^0-9A-F]|%$/i,x=e=>{if(!w.test(e)||B.test(e))return e;try{return decodeURIComponent(e)}catch{return e}},C=(e,n)=>{let t=(e.textContent||"").trim(),o=I(t,n);return e.id=o,o},N=(e,n,t)=>{let o=[],i=M(e);for(let r=0;r<i.length;r++){let s=i[r],a=Number(s.tagName.at(1));a>=n&&a<=t&&o.push(s);}return o},F=e=>{let n=new Map;for(let t=0;t<e.length;t++){let o=e[t];if(o.hash&&o.hash.length>1){let i=o.hash.slice(1),r=x(i),s=n.get(r)||[];s.push(o),n.set(r,s);}}return n},v=(e,n)=>{let t=new Map,o=F(n);for(let i=0;i<e.length;i++){let r=e[i],s=r.id,a=t.get(s)||0;if(a>0){let c=`${s}_${a}`;r.id=c;let l=x(s);if(l){let L=o.get(l)||[];for(let m=0;m<L.length;m++){let d=L[m];d.href=`#${c}`;}}}t.set(r.id,a+1);}};var A="data-mokuji-list",f="data-mokuji-anchor",O={anchorType:true,anchorLink:false,anchorLinkSymbol:"#",anchorLinkPosition:"before",anchorLinkClassName:"",anchorContainerTagName:"ol",minLevel:1,maxLevel:6};var k=e=>{let n=new Map;for(let t=0;t<e.length;t++){let o=e[t];o.hash&&o.hash.length>1&&n.set(o.hash.slice(1),o);}return n},$=(e,n)=>{let t=n.trim();t&&e.classList.add(...t.split(/\s+/).filter(Boolean));},W=e=>{let n=T("a");return n.setAttribute(f,""),$(n,e.anchorLinkClassName),n},G=(e,n,t="before")=>{t==="before"?e.insertBefore(n,e.firstChild):e.append(n);},J=(e,n,t,o)=>{let i=e.id,r=n.get(i);if(!r){let a=i.replace(/_\d+$/,"");if(a!==i&&(r=n.get(a)),!r){let c=e.textContent?.trim()||"";for(let[,l]of n.entries())if(l.textContent?.trim()===c){r=l;break}}if(!r)return}let s=t.cloneNode(false);return s.href=r.hash,s.textContent=o.anchorLinkSymbol,s},K=(e,n,t,o)=>{if(!e.parentNode)return;let i=J(e,n,t,o);if(i)return {heading:e,anchor:i}},R=(e,n,t)=>{let o=W(t),i=new Map;for(let r=0;r<e.length;r++){let s=e[r],a=K(s,n,o,t);if(!a||!a.heading.parentNode)continue;let c=a.heading.parentNode,l=i.get(c)||[];l.push(a),i.set(c,l);}for(let[,r]of i.entries())for(let s=0;s<r.length;s++){let{heading:a,anchor:c}=r[s];G(a,c,t.anchorLinkPosition);}};var _=(e,n,t)=>{let o=[],i=[];i.push({level:0,items:o});let r=e.length;for(let m=0;m<r;m++){let d=e[m],g=Number(d.tagName[1]),p=C(d,t),u={text:d.textContent,href:`#${p}`,level:g,children:[]},h=i.length-1,H=i[h];for(;g<=H.level&&h>0;)i.pop(),h--,H=i[h];H.items.push(u),i.push({level:g,items:u.children});}let s=T("li"),a=T("a"),c=n.tagName.toLowerCase(),l=T(c),L=(m,d)=>{for(let g=0;g<d.length;g++){let p=d[g],u=s.cloneNode(false),h=a.cloneNode(false);if(h.href=p.href,h.textContent=p.text,u.append(h),p.children.length>0){let H=l.cloneNode(false);L(H,p.children),u.append(H);}m.append(u);}};L(n,o);};var V=e=>{let n={...O,...e};return n.minLevel=Math.max(1,Math.min(n.minLevel,6)),n.maxLevel=Math.max(n.minLevel,Math.min(n.maxLevel,6)),n},Y=(e,n)=>{let t=T(n.anchorContainerTagName);t.setAttribute(A,""),_(e,t,n.anchorType);let o=[...t.querySelectorAll("a")];return {listContainer:t,anchors:o}},Te=(e,n)=>{if(!e){console.warn("Mokuji: Target element not found.");return}let t=V(n),{minLevel:o,maxLevel:i}=t,r=N(e,o,i);if(r.length===0)return;let{listContainer:s,anchors:a}=Y(r,t);if(a.length!==0){if(v(r,a),t.anchorLink){let c=k(a);R(r,c,t);}return {element:e,list:s}}},ge=()=>{let e=document.querySelectorAll(`[${f}]`);for(let t=0;t<e.length;t++)e[t].remove();let n=document.querySelector(`[${A}]`);n&&n.remove();};export{ge as Destroy,Te as Mokuji};//# sourceMappingURL=index.js.map
5
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","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(/&amp;+/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 a.classList.add(options.anchorLinkClassName);\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,"sources":["../src/common/dom.ts","../src/heading/text.ts","../src/heading/heading.ts","../src/common/constants.ts","../src/anchor/anchor.ts","../src/toc/core.ts","../src/index.ts"],"names":["getAllHeadingElements","containerElement","elementCache","createElement","tagName","WHITESPACE_PATTERN","COLON_CHARACTER","AMPERSAND_PATTERN","AMPERSAND_ENTITY_PATTERN","PERCENT_ENCODING_PATTERN","DOT_REPLACEMENT","replaceSpacesWithUnderscores","text","convertToWikipediaStyleAnchor","anchor","generateAnchorText","baseId","isConvertToWikipediaStyleAnchor","anchorText","VALID_PERCENT_ENCODING","INVALID_PERCENT_PATTERN","safeDecodeURIComponent","encoded","assignInitialIdToHeading","heading","baseHeadingId","getFilteredHeadings","element","minLevel","maxLevel","filteredHeadings","allHeadings","i","level","groupAnchorsByHeadingId","anchors","idToAnchorsMap","rawHeadingId","headingId","anchorsForId","ensureUniqueHeadingIds","headings","headingIdOccurrenceMap","currentHeadingId","occurrenceCount","uniqueHeadingId","originalIdDecoded","matchingAnchors","j","MOKUJI_LIST_DATASET_ATTRIBUTE","ANCHOR_DATASET_ATTRIBUTE","defaultOptions","generateAnchorsMap","result","applyClassNamesToElement","classNameString","trimmedClassNames","createAnchorTemplate","options","anchorTemplate","placeAnchorInHeading","position","createAnchorForHeading","anchorMap","matchedTocAnchor","idWithoutSuffix","headingText","anchorElement","createHeadingAnchorPair","insertAnchorToHeadings","headingsByParent","pair","parent","existingPairs","headingsWithAnchors","generateTableOfContents","listContainer","rootLevelItems","levelStack","headingsLength","currentHeadingLevel","newItem","stackIndex","currentStackTop","listItemTemplate","childListTagName","childListTemplate","buildListRecursive","parentListElement","items","item","elementList","elementAnchor","childListContainer","processOptions","externalOptions","generateTocAndAnchorsInternal","Mokuji","anchorsMap","Destroy","mokujiAnchors","tableOfContentsList"],"mappings":"AAOO,IAAMA,CAAyBC,CAAAA,CAAAA,EAE7B,CAAC,GADSA,CAAiB,CAAA,gBAAA,CAAiB,wBAAwB,CACxD,CAMfC,CAAAA,CAAAA,CAAe,IAAI,GAAA,CAEZC,EAAwDC,CAC9DF,GAAAA,CAAAA,CAAa,GAAIE,CAAAA,CAAO,CAC3BF,EAAAA,CAAAA,CAAa,GAAIE,CAAAA,CAAAA,CAAS,QAAS,CAAA,aAAA,CAAcA,CAAO,CAAC,CAGrCF,CAAAA,CAAAA,CAAa,GAAIE,CAAAA,CAAO,EAEzB,SAAU,CAAA,KAAK,CCnBtC,CAAA,CAAA,IAAMC,CAAqB,CAAA,MAAA,CACrBC,CAAkB,CAAA,GAAA,CAClBC,CAAoB,CAAA,KAAA,CACpBC,CAA2B,CAAA,SAAA,CAC3BC,CAA2B,CAAA,KAAA,CAC3BC,CAAkB,CAAA,GAAA,CAKlBC,EAAgCC,CAC7BA,EAAAA,CAAAA,CAAK,UAAWP,CAAAA,CAAAA,CAAoB,GAAG,CAAA,CAAE,OAAQC,CAAAA,CAAAA,CAAiB,EAAE,CAAA,CAMvEO,CAAiCC,CAAAA,CAAAA,EAC9B,kBAAmBA,CAAAA,CAAM,CAAE,CAAA,UAAA,CAAWL,EAA0BC,CAAe,CAAA,CAO3EK,CAAqB,CAAA,CAACC,CAAgBC,CAAAA,CAAAA,GAAqD,CACtG,IAAIC,CAAaP,CAAAA,CAAAA,CAA6BK,CAAM,CAAA,CAEpD,OAAIC,CAAAA,GACFC,CAAaL,CAAAA,CAAAA,CAA8BK,CAAU,CAGnD,CAAA,CAAA,CAACD,CAAmCC,EAAAA,CAAAA,CAAW,QAAS,CAAA,GAAG,CAC7DA,GAAAA,CAAAA,CAAaA,CAAW,CAAA,UAAA,CAAWX,CAAmB,CAAA,EAAE,CAAE,CAAA,UAAA,CAAWC,CAA0B,CAAA,EAAE,GAG5FU,CACT,CAAA,CC/BA,IAAMC,CAAAA,CAAyB,gBACzBC,CAAAA,CAAAA,CAA0B,mCAM1BC,CAAAA,CAAAA,CAA0BC,CAA4B,EAAA,CAK1D,GAJI,CAACH,CAAuB,CAAA,IAAA,CAAKG,CAAO,CAAA,EAIpCF,EAAwB,IAAKE,CAAAA,CAAO,CACtC,CAAA,OAAOA,CAGT,CAAA,GAAI,CACF,OAAO,kBAAmBA,CAAAA,CAAO,CACnC,CAAA,KAAQ,CACN,OAAOA,CACT,CACF,EAMaC,CAA2B,CAAA,CACtCC,CACAP,CAAAA,CAAAA,GACW,CACX,IAAMQ,CAAiBD,CAAAA,CAAAA,CAAAA,CAAQ,WAAe,EAAA,EAAA,EAAI,IAAK,EAAA,CACjDN,CAAaH,CAAAA,CAAAA,CAAmBU,CAAeR,CAAAA,CAA+B,EACpF,OAAAO,CAAAA,CAAQ,EAAKN,CAAAA,CAAAA,CACNA,CACT,CAAA,CAKaQ,CAAsB,CAAA,CACjCC,CACAC,CAAAA,CAAAA,CACAC,CACyB,GAAA,CACzB,IAAMC,CAAAA,CAAyC,EAAC,CAC1CC,EAAc/B,CAAsB2B,CAAAA,CAAO,CAEjD,CAAA,IAAA,IAASK,CAAI,CAAA,CAAA,CAAGA,CAAID,CAAAA,CAAAA,CAAY,MAAQC,CAAAA,CAAAA,EAAAA,CAAK,CAC3C,IAAMR,CAAUO,CAAAA,CAAAA,CAAYC,CAAC,CAAA,CACvBC,EAAQ,MAAOT,CAAAA,CAAAA,CAAQ,OAAQ,CAAA,EAAA,CAAG,CAAC,CAAC,CACtCS,CAAAA,CAAAA,EAASL,CAAYK,EAAAA,CAAAA,EAASJ,CAChCC,EAAAA,CAAAA,CAAiB,IAAKN,CAAAA,CAAO,EAEjC,CAEA,OAAOM,CACT,CAAA,CAKMI,CAA2BC,CAAAA,CAAAA,EAAmE,CAClG,IAAMC,CAAiB,CAAA,IAAI,GAE3B,CAAA,IAAA,IAASJ,CAAI,CAAA,CAAA,CAAGA,CAAIG,CAAAA,CAAAA,CAAQ,MAAQH,CAAAA,CAAAA,EAAAA,CAAK,CACvC,IAAMlB,CAAAA,CAASqB,CAAQH,CAAAA,CAAC,CACxB,CAAA,GAAIlB,CAAO,CAAA,IAAA,EAAQA,CAAO,CAAA,IAAA,CAAK,MAAS,CAAA,CAAA,CAAG,CACzC,IAAMuB,CAAevB,CAAAA,CAAAA,CAAO,KAAK,KAAM,CAAA,CAAC,CAClCwB,CAAAA,CAAAA,CAAYjB,CAAuBgB,CAAAA,CAAY,CAE/CE,CAAAA,CAAAA,CAAeH,CAAe,CAAA,GAAA,CAAIE,CAAS,CAAA,EAAK,EAAC,CACvDC,CAAa,CAAA,IAAA,CAAKzB,CAAM,CACxBsB,CAAAA,CAAAA,CAAe,GAAIE,CAAAA,CAAAA,CAAWC,CAAY,EAC5C,CACF,CAEA,OAAOH,CACT,CAAA,CAKaI,CAAyB,CAAA,CAACC,CAAgCN,CAAAA,CAAAA,GAAiC,CACtG,IAAMO,EAAyB,IAAI,GAAA,CAC7BN,CAAiBF,CAAAA,CAAAA,CAAwBC,CAAO,CAAA,CAEtD,IAAS,IAAA,CAAA,CAAI,CAAG,CAAA,CAAA,CAAIM,CAAS,CAAA,MAAA,CAAQ,CAAK,EAAA,CAAA,CACxC,IAAMjB,CAAAA,CAAUiB,EAAS,CAAC,CAAA,CACpBE,CAAmBnB,CAAAA,CAAAA,CAAQ,EAC3BoB,CAAAA,CAAAA,CAAkBF,CAAuB,CAAA,GAAA,CAAIC,CAAgB,CAAA,EAAK,CAExE,CAAA,GAAIC,CAAkB,CAAA,CAAA,CAAG,CACvB,IAAMC,EAAkB,CAAGF,EAAAA,CAAgB,CAAIC,CAAAA,EAAAA,CAAe,CAC9DpB,CAAAA,CAAAA,CAAAA,CAAQ,EAAKqB,CAAAA,CAAAA,CAEb,IAAMC,CAAAA,CAAoBzB,CAAuBsB,CAAAA,CAAgB,CAEjE,CAAA,GAAIG,CAAmB,CAAA,CACrB,IAAMC,CAAkBX,CAAAA,CAAAA,CAAe,GAAIU,CAAAA,CAAiB,CAAK,EAAA,EACjE,CAAA,IAAA,IAASE,CAAI,CAAA,CAAA,CAAGA,CAAID,CAAAA,CAAAA,CAAgB,MAAQC,CAAAA,CAAAA,EAAAA,CAAK,CAC/C,IAAMlC,EAASiC,CAAgBC,CAAAA,CAAC,CAChClC,CAAAA,CAAAA,CAAO,IAAO,CAAA,CAAA,CAAA,EAAI+B,CAAe,CAAA,EACnC,CACF,CACF,CAEAH,CAAAA,CAAuB,GAAIlB,CAAAA,CAAAA,CAAQ,EAAIoB,CAAAA,CAAAA,CAAkB,CAAC,EAC5D,CACF,CC/GO,CAAA,IAAMK,CAAgC,CAAA,kBAAA,CAChCC,CAA2B,CAAA,oBAAA,CAK3BC,CAAyC,CAAA,CACpD,UAAY,CAAA,IAAA,CACZ,UAAY,CAAA,KAAA,CACZ,gBAAkB,CAAA,GAAA,CAClB,mBAAoB,QACpB,CAAA,mBAAA,CAAqB,EACrB,CAAA,sBAAA,CAAwB,IACxB,CAAA,QAAA,CAAU,CACV,CAAA,QAAA,CAAU,CACZ,CAAA,CCbO,IAAMC,CAAAA,CAAsBjB,CAAiE,EAAA,CAClG,IAAMkB,CAAAA,CAAS,IAAI,GAEnB,CAAA,IAAA,IAASrB,CAAI,CAAA,CAAA,CAAGA,CAAIG,CAAAA,CAAAA,CAAQ,MAAQH,CAAAA,CAAAA,EAAAA,CAAK,CACvC,IAAMlB,CAASqB,CAAAA,CAAAA,CAAQH,CAAC,CAAA,CACpBlB,CAAO,CAAA,IAAA,EAAQA,EAAO,IAAK,CAAA,MAAA,CAAS,CACtCuC,EAAAA,CAAAA,CAAO,GAAIvC,CAAAA,CAAAA,CAAO,IAAK,CAAA,KAAA,CAAM,CAAC,CAAA,CAAGA,CAAM,EAE3C,CAEA,OAAOuC,CACT,CAAA,CAKMC,EAA2B,CAAC3B,CAAAA,CAAsB4B,CAAkC,GAAA,CACxF,IAAMC,CAAAA,CAAoBD,CAAgB,CAAA,IAAA,EACrCC,CAAAA,CAAAA,EAEL7B,CAAQ,CAAA,SAAA,CAAU,GAAI,CAAA,GAAG6B,CAAkB,CAAA,KAAA,CAAM,KAAK,CAAE,CAAA,MAAA,CAAO,OAAO,CAAC,EACzE,CAAA,CAKMC,CAAwBC,CAAAA,CAAAA,EAAuD,CACnF,IAAMC,CAAiBxD,CAAAA,CAAAA,CAAc,GAAG,CAAA,CACxC,OAAAwD,CAAAA,CAAe,aAAaT,CAA0B,CAAA,EAAE,CAIxDI,CAAAA,CAAAA,CAAyBK,CAAgBD,CAAAA,CAAAA,CAAQ,mBAAmB,CAAA,CAE7DC,CACT,CAAA,CAKMC,CAAuB,CAAA,CAC3BpC,CACAV,CAAAA,CAAAA,CACA+C,CAA+B,CAAA,QAAA,GACtB,CACLA,CAAa,GAAA,QAAA,CACfrC,CAAQ,CAAA,YAAA,CAAaV,CAAQU,CAAAA,CAAAA,CAAQ,UAAU,CAAA,CAE/CA,CAAQ,CAAA,MAAA,CAAOV,CAAM,EAEzB,CAKMgD,CAAAA,CAAAA,CAAyB,CAC7BtC,CAAAA,CACAuC,EACAJ,CACAD,CAAAA,CAAAA,GACkC,CAClC,IAAMpB,CAAYd,CAAAA,CAAAA,CAAQ,EACtBwC,CAAAA,CAAAA,CAAmBD,CAAU,CAAA,GAAA,CAAIzB,CAAS,CAAA,CAG9C,GAAI,CAAC0B,CAAkB,CAAA,CAErB,IAAMC,CAAkB3B,CAAAA,CAAAA,CAAU,OAAQ,CAAA,OAAA,CAAS,EAAE,CAAA,CAOrD,GANI2B,CAAAA,GAAoB3B,CAEtB0B,GAAAA,CAAAA,CAAmBD,CAAU,CAAA,GAAA,CAAIE,CAAe,CAAA,CAAA,CAI9C,CAACD,CAAAA,CAAkB,CACrB,IAAME,CAAAA,CAAc1C,CAAQ,CAAA,WAAA,EAAa,IAAK,EAAA,EAAK,EAEnD,CAAA,IAAA,GAAW,EAAGV,CAAM,CAAKiD,GAAAA,CAAAA,CAAU,OAAQ,EAAA,CACzC,GAAIjD,CAAAA,CAAO,aAAa,IAAK,EAAA,GAAMoD,CAAa,CAAA,CAC9CF,CAAmBlD,CAAAA,CAAAA,CACnB,KACF,CAEJ,CAGA,GAAI,CAACkD,CAAAA,CACH,MAEJ,CAEA,IAAMG,CAAAA,CAAgBR,EAAe,SAAU,CAAA,KAAK,CACpD,CAAA,OAAAQ,CAAc,CAAA,IAAA,CAAOH,CAAiB,CAAA,IAAA,CAEtCG,CAAc,CAAA,WAAA,CAAcT,CAAQ,CAAA,gBAAA,CAE7BS,CACT,CAAA,CAaMC,CAA0B,CAAA,CAC9B5C,EACAuC,CACAJ,CAAAA,CAAAA,CACAD,CACkC,GAAA,CAClC,GAAI,CAAClC,CAAQ,CAAA,UAAA,CAAY,OAEzB,IAAMV,CAASgD,CAAAA,CAAAA,CAAuBtC,CAASuC,CAAAA,CAAAA,CAAWJ,CAAgBD,CAAAA,CAAO,EACjF,GAAK5C,CAAAA,CAEL,OAAO,CAAE,OAAAU,CAAAA,CAAAA,CAAS,MAAAV,CAAAA,CAAO,CAC3B,CAAA,CAKauD,CAAyB,CAAA,CACpC5B,CACAsB,CAAAA,CAAAA,CACAL,CACS,GAAA,CACT,IAAMC,CAAiBF,CAAAA,CAAAA,CAAqBC,CAAO,CAAA,CAE7CY,CAAmB,CAAA,IAAI,GAE7B,CAAA,IAAA,IAAStC,CAAI,CAAA,CAAA,CAAGA,CAAIS,CAAAA,CAAAA,CAAS,MAAQT,CAAAA,CAAAA,EAAAA,CAAK,CACxC,IAAMR,EAAUiB,CAAST,CAAAA,CAAC,CACpBuC,CAAAA,CAAAA,CAAOH,CAAwB5C,CAAAA,CAAAA,CAASuC,CAAWJ,CAAAA,CAAAA,CAAgBD,CAAO,CAAA,CAChF,GAAI,CAACa,CAAQ,EAAA,CAACA,CAAK,CAAA,OAAA,CAAQ,WAAY,SAEvC,IAAMC,CAASD,CAAAA,CAAAA,CAAK,OAAQ,CAAA,UAAA,CACtBE,CAAgBH,CAAAA,CAAAA,CAAiB,GAAIE,CAAAA,CAAM,CAAK,EAAA,EACtDC,CAAAA,CAAAA,CAAc,IAAKF,CAAAA,CAAI,EACvBD,CAAiB,CAAA,GAAA,CAAIE,CAAQC,CAAAA,CAAa,EAC5C,CAEA,IAAW,GAAA,EAAGC,CAAmB,CAAKJ,GAAAA,CAAAA,CAAiB,OAAQ,EAAA,CAC7D,IAAStB,IAAAA,CAAAA,CAAI,EAAGA,CAAI0B,CAAAA,CAAAA,CAAoB,MAAQ1B,CAAAA,CAAAA,EAAAA,CAAK,CACnD,GAAM,CAAE,OAAA,CAAAxB,CAAS,CAAA,MAAA,CAAAV,CAAO,CAAA,CAAI4D,CAAoB1B,CAAAA,CAAC,CACjDY,CAAAA,CAAAA,CAAqBpC,EAASV,CAAQ4C,CAAAA,CAAAA,CAAQ,kBAAkB,EAClE,CAEJ,CAAA,CCxIO,IAAMiB,CAAAA,CAA0B,CACrClC,CAAAA,CACAmC,CACA3D,CAAAA,CAAAA,GACS,CACT,IAAM4D,CAA4B,CAAA,GAC5BC,CAAoD,CAAA,EAC1DA,CAAAA,CAAAA,CAAW,IAAK,CAAA,CAAE,KAAO,CAAA,CAAA,CAAG,KAAOD,CAAAA,CAAe,CAAC,CAAA,CAEnD,IAAME,CAAAA,CAAiBtC,CAAS,CAAA,MAAA,CAChC,QAAST,CAAI,CAAA,CAAA,CAAGA,CAAI+C,CAAAA,CAAAA,CAAgB/C,CAAK,EAAA,CAAA,CACvC,IAAMR,CAAAA,CAAUiB,CAAST,CAAAA,CAAC,CACpBgD,CAAAA,CAAAA,CAAsB,MAAOxD,CAAAA,CAAAA,CAAQ,OAAQ,CAAA,CAAC,CAAC,CAC/CN,CAAAA,CAAAA,CAAaK,CAAyBC,CAAAA,CAAAA,CAASP,CAA+B,CAAA,CAE9EgE,CAAmB,CAAA,CACvB,IAAMzD,CAAAA,CAAAA,CAAQ,WACd,CAAA,IAAA,CAAM,CAAIN,CAAAA,EAAAA,CAAU,CACpB,CAAA,CAAA,KAAA,CAAO8D,EACP,QAAU,CAAA,EACZ,CAAA,CAEIE,CAAaJ,CAAAA,CAAAA,CAAW,MAAS,CAAA,CAAA,CACjCK,CAAkBL,CAAAA,CAAAA,CAAWI,CAAU,CAAA,CAE3C,KAAOF,CAAAA,EAAuBG,CAAgB,CAAA,KAAA,EAASD,EAAa,CAClEJ,EAAAA,CAAAA,CAAW,GAAI,EAAA,CACfI,CACAC,EAAAA,CAAAA,CAAAA,CAAkBL,CAAWI,CAAAA,CAAU,CAGzCC,CAAAA,CAAAA,CAAgB,KAAM,CAAA,IAAA,CAAKF,CAAO,CAAA,CAClCH,CAAW,CAAA,IAAA,CAAK,CAAE,KAAOE,CAAAA,CAAAA,CAAqB,KAAOC,CAAAA,CAAAA,CAAQ,QAAS,CAAC,EACzE,CAEA,IAAMG,CAAmBjF,CAAAA,CAAAA,CAAc,IAAI,CAAA,CACrCwD,CAAiBxD,CAAAA,CAAAA,CAAc,GAAG,CAAA,CAClCkF,EAAmBT,CAAc,CAAA,OAAA,CAAQ,WAAY,EAAA,CACrDU,CAAoBnF,CAAAA,CAAAA,CAAckF,CAAgB,CAAA,CAElDE,CAAqB,CAAA,CAACC,CAAgCC,CAAAA,CAAAA,GAA2B,CACrF,IAAA,IAASzD,CAAI,CAAA,CAAA,CAAGA,EAAIyD,CAAM,CAAA,MAAA,CAAQzD,CAAK,EAAA,CAAA,CACrC,IAAM0D,CAAAA,CAAOD,CAAMzD,CAAAA,CAAC,CACd2D,CAAAA,CAAAA,CAAcP,CAAiB,CAAA,SAAA,CAAU,KAAK,CAAA,CAC9CQ,CAAgBjC,CAAAA,CAAAA,CAAe,UAAU,KAAK,CAAA,CAMpD,GAJAiC,CAAAA,CAAc,IAAOF,CAAAA,CAAAA,CAAK,IAC1BE,CAAAA,CAAAA,CAAc,WAAcF,CAAAA,CAAAA,CAAK,IACjCC,CAAAA,CAAAA,CAAY,MAAOC,CAAAA,CAAa,CAE5BF,CAAAA,CAAAA,CAAK,SAAS,MAAS,CAAA,CAAA,CAAG,CAC5B,IAAMG,CAAqBP,CAAAA,CAAAA,CAAkB,SAAU,CAAA,KAAK,CAC5DC,CAAAA,CAAAA,CAAmBM,CAAoBH,CAAAA,CAAAA,CAAK,QAAQ,CAAA,CACpDC,CAAY,CAAA,MAAA,CAAOE,CAAkB,EACvC,CACAL,CAAkB,CAAA,MAAA,CAAOG,CAAW,EACtC,CACF,CAAA,CAEAJ,CAAmBX,CAAAA,CAAAA,CAAeC,CAAc,EAClD,CChEA,CAAA,IAAMiB,CAAkBC,CAAAA,CAAAA,EAA2D,CACjF,IAAMrC,CAAAA,CAAU,CACd,GAAGP,CACH,CAAA,GAAG4C,CACL,CAAA,CAEA,OAAArC,CAAAA,CAAQ,QAAW,CAAA,IAAA,CAAK,GAAI,CAAA,CAAA,CAAG,IAAK,CAAA,GAAA,CAAIA,EAAQ,QAAU,CAAA,CAAC,CAAC,CAAA,CAC5DA,CAAQ,CAAA,QAAA,CAAW,IAAK,CAAA,GAAA,CAAIA,CAAQ,CAAA,QAAA,CAAU,IAAK,CAAA,GAAA,CAAIA,CAAQ,CAAA,QAAA,CAAU,CAAC,CAAC,EAEpEA,CACT,CAAA,CAKMsC,CAAgC,CAAA,CACpClE,CACA4B,CAAAA,CAAAA,GACyF,CACzF,IAAMkB,EAAgBzE,CAAcuD,CAAAA,CAAAA,CAAQ,sBAAsB,CAAA,CAClEkB,CAAc,CAAA,YAAA,CAAa3B,CAA+B,CAAA,EAAE,EAE5D0B,CAAwB7C,CAAAA,CAAAA,CAAkB8C,CAAelB,CAAAA,CAAAA,CAAQ,UAAU,CAAA,CAE3E,IAAMvB,CAAAA,CAAU,CAAC,GAAGyC,CAAc,CAAA,gBAAA,CAAiB,GAAG,CAAC,CAEvD,CAAA,OAAO,CAAE,aAAAA,CAAAA,CAAAA,CAAe,OAAAzC,CAAAA,CAAQ,CAClC,CAAA,CAKa8D,EAAS,CAAA,CACpBtE,CACAoE,CAAAA,CAAAA,GACgC,CAChC,GAAI,CAACpE,CAAAA,CAAS,CACZ,OAAA,CAAQ,KAAK,mCAAmC,CAAA,CAChD,MACF,CAEA,IAAM+B,CAAAA,CAAUoC,CAAeC,CAAAA,CAAe,CAExC,CAAA,CAAE,QAAAnE,CAAAA,CAAAA,CAAU,QAAAC,CAAAA,CAAS,CAAI6B,CAAAA,CAAAA,CACzB5B,EAAmBJ,CAAoBC,CAAAA,CAAAA,CAASC,CAAUC,CAAAA,CAAQ,CAExE,CAAA,GAAIC,CAAiB,CAAA,MAAA,GAAW,CAC9B,CAAA,OAGF,GAAM,CAAE,aAAA8C,CAAAA,CAAAA,CAAe,OAAAzC,CAAAA,CAAQ,EAAI6D,CAA8BlE,CAAAA,CAAAA,CAAkB4B,CAAO,CAAA,CAE1F,GAAIvB,CAAAA,CAAQ,MAAW,GAAA,CAAA,CAMvB,CAFAK,GAAAA,CAAAA,CAAuBV,CAAkBK,CAAAA,CAAO,CAE5CuB,CAAAA,CAAAA,CAAQ,UAAY,CAAA,CACtB,IAAMwC,CAAa9C,CAAAA,CAAAA,CAAmBjB,CAAO,CAAA,CAC7CkC,CAAuBvC,CAAAA,CAAAA,CAAkBoE,CAAYxC,CAAAA,CAAO,EAC9D,CAEA,OAAO,CAAE,OAAA/B,CAAAA,CAAAA,CAAS,IAAMiD,CAAAA,CAAc,EACxC,CAKauB,CAAAA,EAAAA,CAAU,IAAY,CACjC,IAAMC,CAAAA,CAAgB,QAAS,CAAA,gBAAA,CAAiB,CAAIlD,CAAAA,EAAAA,CAAwB,CAAG,CAAA,CAAA,CAAA,CAC/E,IAASlB,IAAAA,CAAAA,CAAI,CAAGA,CAAAA,CAAAA,CAAIoE,EAAc,MAAQpE,CAAAA,CAAAA,EAAAA,CACzBoE,CAAcpE,CAAAA,CAAC,CACvB,CAAA,MAAA,EAGT,CAAA,IAAMqE,EAAsB,QAAS,CAAA,aAAA,CAAc,CAAIpD,CAAAA,EAAAA,CAA6B,CAAG,CAAA,CAAA,CAAA,CACnFoD,CACFA,EAAAA,CAAAA,CAAoB,SAExB","file":"index.js","sourcesContent":["/**\n * DOM操作のための共通ユーティリティ関数\n */\n\n/**\n * h1からh6までのすべての見出し要素を取得する\n */\nexport const getAllHeadingElements = (containerElement: Element): HTMLHeadingElement[] => {\n const headings = containerElement.querySelectorAll('h1, h2, h3, h4, h5, h6');\n return [...headings] as HTMLHeadingElement[];\n};\n\n/**\n * 指定したタグ名の新しいHTML要素を作成する\n */\nconst elementCache = new Map<string, HTMLElement>();\n\nexport const createElement = <T extends keyof HTMLElementTagNameMap>(tagName: T): HTMLElementTagNameMap[T] => {\n if (!elementCache.has(tagName)) {\n elementCache.set(tagName, document.createElement(tagName));\n }\n\n const cachedElement = elementCache.get(tagName)!;\n\n return cachedElement.cloneNode(false) as HTMLElementTagNameMap[T];\n};\n","/**\n * テキスト処理とアンカーテキスト生成のためのユーティリティ関数\n */\n\n// 正規表現定義\nconst WHITESPACE_PATTERN = /\\s+/g;\nconst COLON_CHARACTER = ':';\nconst AMPERSAND_PATTERN = /&+/g;\nconst AMPERSAND_ENTITY_PATTERN = /&amp;+/g;\nconst PERCENT_ENCODING_PATTERN = /%+/g;\nconst DOT_REPLACEMENT = '.';\n\n/**\n * テキスト内のスペースをアンダースコアに置換し、コロンを除去する\n */\nconst replaceSpacesWithUnderscores = (text: string): string => {\n return text.replaceAll(WHITESPACE_PATTERN, '_').replace(COLON_CHARACTER, '');\n};\n\n/**\n * テキストをWikipediaスタイルのアンカー形式(パーセントエンコーディング後に%を.に置換)に変換する\n */\nconst convertToWikipediaStyleAnchor = (anchor: string): string => {\n return encodeURIComponent(anchor).replaceAll(PERCENT_ENCODING_PATTERN, DOT_REPLACEMENT);\n};\n\n/**\n * アンカーテキストを生成する\n * HTMLのid属性やhrefで使用される最終的な文字列を生成する。\n */\nexport const generateAnchorText = (baseId: string, isConvertToWikipediaStyleAnchor: boolean): string => {\n let anchorText = replaceSpacesWithUnderscores(baseId);\n\n if (isConvertToWikipediaStyleAnchor) {\n anchorText = convertToWikipediaStyleAnchor(anchorText);\n }\n\n if (!isConvertToWikipediaStyleAnchor && anchorText.includes('&')) {\n anchorText = anchorText.replaceAll(AMPERSAND_PATTERN, '').replaceAll(AMPERSAND_ENTITY_PATTERN, '');\n }\n\n return anchorText;\n};\n","/**\n * 見出し要素の処理(ID割り当て、フィルタリング、重複解決)に関するユーティリティを提供するモジュール\n */\nimport { generateAnchorText } from './text';\nimport { getAllHeadingElements } from '../common/dom';\nimport type { HeadingLevel } from '../common/types';\n\n/**\n * URLエンコード文字列の妥当性をチェックする正規表現\n * %の後に2桁の16進数が続くパターンが正しいものとみなす\n */\nconst VALID_PERCENT_ENCODING = /%[0-9A-F]{2}/gi;\nconst INVALID_PERCENT_PATTERN = /%[^0-9A-F]|%[0-9A-F][^0-9A-F]|%$/i;\n\n/**\n * URIコンポーネントを安全にデコードする(例外を発生させない)\n * 不正なエンコーディングの場合は元の文字列を返す\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 * 見出し要素に初期ID(アンカーテキスト)を割り当てる\n * この時点ではIDの重複可能性があるため、後続の ensureUniqueHeadingIds で解決される。\n */\nexport const assignInitialIdToHeading = (\n heading: HTMLHeadingElement,\n isConvertToWikipediaStyleAnchor: boolean,\n): string => {\n const baseHeadingId = (heading.textContent || '').trim();\n const anchorText = generateAnchorText(baseHeadingId, isConvertToWikipediaStyleAnchor);\n heading.id = anchorText;\n return anchorText;\n};\n\n/**\n * 指定したレベル範囲内の見出し要素を取得する\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 (let i = 0; i < allHeadings.length; i++) {\n const heading = allHeadings[i];\n const level = Number(heading.tagName.at(1)) as HeadingLevel;\n if (level >= minLevel && level <= maxLevel) {\n filteredHeadings.push(heading);\n }\n }\n\n return filteredHeadings;\n};\n\n/**\n * アンカー要素をhrefのハッシュ値(デコード済み)ごとにグループ化する\n */\nconst groupAnchorsByHeadingId = (anchors: HTMLAnchorElement[]): Map<string, HTMLAnchorElement[]> => {\n const idToAnchorsMap = new Map<string, HTMLAnchorElement[]>();\n\n for (let i = 0; i < anchors.length; i++) {\n const anchor = anchors[i];\n if (anchor.hash && anchor.hash.length > 1) {\n const rawHeadingId = anchor.hash.slice(1);\n const headingId = safeDecodeURIComponent(rawHeadingId);\n\n const anchorsForId = idToAnchorsMap.get(headingId) || [];\n anchorsForId.push(anchor);\n idToAnchorsMap.set(headingId, anchorsForId);\n }\n }\n\n return idToAnchorsMap;\n};\n\n/**\n * 見出し要素のIDが重複している場合に一意になるよう修正し、関連する目次アンカーのhrefも更新する\n */\nexport const ensureUniqueHeadingIds = (headings: HTMLHeadingElement[], anchors: HTMLAnchorElement[]) => {\n const headingIdOccurrenceMap = new Map<string, number>();\n const idToAnchorsMap = groupAnchorsByHeadingId(anchors);\n\n for (let i = 0; i < headings.length; i++) {\n const heading = headings[i];\n const currentHeadingId = heading.id;\n const occurrenceCount = headingIdOccurrenceMap.get(currentHeadingId) || 0;\n\n if (occurrenceCount > 0) {\n const uniqueHeadingId = `${currentHeadingId}_${occurrenceCount}`;\n heading.id = uniqueHeadingId;\n\n const originalIdDecoded = safeDecodeURIComponent(currentHeadingId);\n\n if (originalIdDecoded) {\n const matchingAnchors = idToAnchorsMap.get(originalIdDecoded) || [];\n for (let j = 0; j < matchingAnchors.length; j++) {\n const anchor = matchingAnchors[j];\n anchor.href = `#${uniqueHeadingId}`;\n }\n }\n }\n\n headingIdOccurrenceMap.set(heading.id, occurrenceCount + 1);\n }\n};\n","/**\n * 目次生成で使用する定数を提供するモジュール\n */\n\nimport type { MokujiOption } from './types';\n\n/**\n * 目次関連の要素を識別するためのデータ属性\n */\nexport const MOKUJI_LIST_DATASET_ATTRIBUTE = 'data-mokuji-list'; // 目次リスト本体\nexport const ANCHOR_DATASET_ATTRIBUTE = 'data-mokuji-anchor'; // 見出し横に挿入されるアンカー\n\n/**\n * デフォルトオプション設定\n */\nexport const defaultOptions: Required<MokujiOption> = {\n anchorType: true, // Wikipediaスタイルのアンカーを生成\n anchorLink: false, // 見出しへのアンカーリンクを追加\n anchorLinkSymbol: '#', // アンカーリンクのシンボル\n anchorLinkPosition: 'before', // アンカーリンクの位置\n anchorLinkClassName: '', // アンカーリンクのCSSクラス名\n anchorContainerTagName: 'ol', // 目次のコンテナ要素\n minLevel: 1, // 最小見出しレベル\n maxLevel: 6, // 最大見出しレベル\n} as const; // as const ensures the object is treated as readonly with literal types\n","/**\n * 見出し横へのアンカーリンク挿入に関する処理を提供するモジュール\n */\n\nimport { createElement } from '../common/dom';\nimport { ANCHOR_DATASET_ATTRIBUTE } from '../common/constants';\nimport type { MokujiOption, AnchorLinkPosition } from '../common/types';\n\n/**\n * 目次内のアンカー要素から、そのhrefのハッシュ値をキーとするマップを作成する\n */\nexport const generateAnchorsMap = (anchors: HTMLAnchorElement[]): Map<string, HTMLAnchorElement> => {\n const result = new Map<string, HTMLAnchorElement>();\n\n for (let i = 0; i < anchors.length; i++) {\n const anchor = anchors[i];\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 * 要素に複数のクラス名を適用する\n */\nconst 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 * アンカーテンプレート要素を作成する\n */\nconst createAnchorTemplate = (options: Required<MokujiOption>): HTMLAnchorElement => {\n const anchorTemplate = createElement('a');\n anchorTemplate.setAttribute(ANCHOR_DATASET_ATTRIBUTE, '');\n\n // anchorLinkClassNameが空文字列の場合でもクラス名適用処理を実行する\n // 空文字列の場合はapplyClassNamesToElement内で早期リターンされる\n applyClassNamesToElement(anchorTemplate, options.anchorLinkClassName);\n\n return anchorTemplate;\n};\n\n/**\n * 見出し要素内の指定した位置にアンカー要素を挿入する\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 * ある見出し要素に対応するアンカー要素を作成する\n */\nconst createAnchorForHeading = (\n heading: HTMLHeadingElement,\n anchorMap: Map<string, HTMLAnchorElement>,\n anchorTemplate: HTMLAnchorElement,\n options: Required<MokujiOption>,\n): HTMLAnchorElement | undefined => {\n const headingId = heading.id;\n let matchedTocAnchor = anchorMap.get(headingId);\n\n // headingIdから直接対応するアンカーが見つからない場合\n if (!matchedTocAnchor) {\n // IDが「ID_数字」の形式かチェックする(重複により変更されたIDの場合)\n const idWithoutSuffix = headingId.replace(/_\\d+$/, '');\n if (idWithoutSuffix !== headingId) {\n // サフィックスを取り除いた元のIDでアンカーを検索\n matchedTocAnchor = anchorMap.get(idWithoutSuffix);\n }\n\n // それでも見つからない場合はテキスト内容をベースに検索\n if (!matchedTocAnchor) {\n const headingText = heading.textContent?.trim() || '';\n // アンカーマップ内をテキスト内容で検索\n for (const [, anchor] of anchorMap.entries()) {\n if (anchor.textContent?.trim() === headingText) {\n matchedTocAnchor = anchor;\n break;\n }\n }\n }\n\n // 最終的にもマッチするアンカーが見つからなかった場合\n if (!matchedTocAnchor) {\n return undefined;\n }\n }\n\n const anchorElement = anchorTemplate.cloneNode(false) as HTMLAnchorElement;\n anchorElement.href = matchedTocAnchor.hash;\n\n anchorElement.textContent = options.anchorLinkSymbol;\n\n return anchorElement;\n};\n\n/**\n * 見出しと、それに対応する見出し横アンカーのペア\n */\ntype HeadingAnchorPair = {\n heading: HTMLHeadingElement;\n anchor: HTMLAnchorElement;\n};\n\n/**\n * 有効な見出しとアンカーのペアを作成する\n */\nconst createHeadingAnchorPair = (\n heading: HTMLHeadingElement,\n anchorMap: Map<string, HTMLAnchorElement>,\n anchorTemplate: HTMLAnchorElement,\n options: Required<MokujiOption>,\n): HeadingAnchorPair | undefined => {\n if (!heading.parentNode) return undefined;\n\n const anchor = createAnchorForHeading(heading, anchorMap, anchorTemplate, options);\n if (!anchor) return undefined;\n\n return { heading, anchor };\n};\n\n/**\n * 見出し要素にアンカーリンクを追加する\n */\nexport const insertAnchorToHeadings = (\n headings: HTMLHeadingElement[],\n anchorMap: Map<string, HTMLAnchorElement>,\n options: Required<MokujiOption>,\n): void => {\n const anchorTemplate = createAnchorTemplate(options);\n\n const headingsByParent = new Map<Node, HeadingAnchorPair[]>();\n\n for (let i = 0; i < headings.length; i++) {\n const heading = headings[i];\n const pair = createHeadingAnchorPair(heading, anchorMap, anchorTemplate, options);\n if (!pair || !pair.heading.parentNode) continue;\n\n const parent = pair.heading.parentNode;\n const existingPairs = headingsByParent.get(parent) || [];\n existingPairs.push(pair);\n headingsByParent.set(parent, existingPairs);\n }\n\n for (const [, headingsWithAnchors] of headingsByParent.entries()) {\n for (let j = 0; j < headingsWithAnchors.length; j++) {\n const { heading, anchor } = headingsWithAnchors[j];\n placeAnchorInHeading(heading, anchor, options.anchorLinkPosition);\n }\n }\n};\n","/**\n * 目次(もくじ)生成の中核ロジックを提供するモジュール (パフォーマンス改善版)\n */\nimport { createElement } from '../common/dom';\nimport { assignInitialIdToHeading } from '../heading/heading';\n\n/**\n * 目次アイテムの中間データ構造\n */\ntype TocItem = {\n text: string | null;\n href: string;\n level: number;\n children: TocItem[];\n};\n\n/**\n * 目次コンテナ要素の型 (ul または ol)\n */\ntype TableOfContentsContainer = HTMLUListElement | HTMLOListElement;\n\n/**\n * 見出し要素から階層構造を持つ目次データ構造を生成し、DOMを一括構築する\n *\n * @param headings 処理対象の見出し要素配列\n * @param listContainer 目次を格納するコンテナ要素 (ul or ol)\n * @param isConvertToWikipediaStyleAnchor Wikipediaスタイルのアンカー生成を行うかどうかのフラグ\n */\nexport const generateTableOfContents = (\n headings: HTMLHeadingElement[],\n listContainer: TableOfContentsContainer,\n isConvertToWikipediaStyleAnchor: boolean,\n): void => {\n const rootLevelItems: TocItem[] = [];\n const levelStack: { level: number; items: TocItem[] }[] = [];\n levelStack.push({ level: 0, items: rootLevelItems });\n\n const headingsLength = headings.length;\n for (let i = 0; i < headingsLength; i++) {\n const heading = headings[i];\n const currentHeadingLevel = Number(heading.tagName[1]);\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 let stackIndex = levelStack.length - 1;\n let currentStackTop = levelStack[stackIndex];\n\n while (currentHeadingLevel <= currentStackTop.level && stackIndex > 0) {\n levelStack.pop();\n stackIndex--;\n currentStackTop = levelStack[stackIndex];\n }\n\n currentStackTop.items.push(newItem);\n levelStack.push({ level: currentHeadingLevel, items: newItem.children });\n }\n\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, items: TocItem[]): void => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const elementList = listItemTemplate.cloneNode(false) as HTMLLIElement;\n const elementAnchor = anchorTemplate.cloneNode(false) as HTMLAnchorElement;\n\n elementAnchor.href = item.href;\n elementAnchor.textContent = item.text;\n elementList.append(elementAnchor);\n\n if (item.children.length > 0) {\n const childListContainer = childListTemplate.cloneNode(false) as TableOfContentsContainer;\n buildListRecursive(childListContainer, item.children);\n elementList.append(childListContainer);\n }\n parentListElement.append(elementList);\n }\n };\n\n buildListRecursive(listContainer, rootLevelItems);\n};\n","/**\n * 目次(もくじ)生成ライブラリのメインエントリーポイント\n */\nimport { createElement } from './common/dom';\nimport type { MokujiOption, HeadingLevel } from './common/types';\nimport { getFilteredHeadings, ensureUniqueHeadingIds } from './heading/heading';\nimport { generateAnchorsMap, insertAnchorToHeadings } from './anchor/anchor';\nimport { generateTableOfContents } from './toc/core';\nimport { MOKUJI_LIST_DATASET_ATTRIBUTE, ANCHOR_DATASET_ATTRIBUTE, defaultOptions } from './common/constants';\n\n/**\n * 目次生成の結果型定義\n */\nexport type MokujiResult<T extends HTMLElement = HTMLElement> = {\n element?: T;\n list: HTMLUListElement | HTMLOListElement;\n};\n\n// 型エクスポート\nexport { MokujiOption, HeadingLevel };\n\n/**\n * オプション設定を処理し、デフォルト値とマージして有効な範囲内に制限する\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 * 目次生成の主要ロジック(内部関数)\n */\nconst generateTocAndAnchorsInternal = (\n filteredHeadings: HTMLHeadingElement[],\n options: Required<MokujiOption>,\n): { listContainer: HTMLUListElement | HTMLOListElement; anchors: HTMLAnchorElement[] } => {\n const listContainer = createElement(options.anchorContainerTagName);\n listContainer.setAttribute(MOKUJI_LIST_DATASET_ATTRIBUTE, '');\n\n generateTableOfContents(filteredHeadings, listContainer, options.anchorType);\n\n const anchors = [...listContainer.querySelectorAll('a')];\n\n return { listContainer, anchors };\n};\n\n/**\n * 与えられた要素内の見出しから目次を生成する (公開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 const { listContainer, anchors } = generateTocAndAnchorsInternal(filteredHeadings, options);\n\n if (anchors.length === 0) {\n return undefined;\n }\n\n ensureUniqueHeadingIds(filteredHeadings, anchors);\n\n if (options.anchorLink) {\n const anchorsMap = generateAnchorsMap(anchors);\n insertAnchorToHeadings(filteredHeadings, anchorsMap, options);\n }\n\n return { element, list: listContainer };\n};\n\n/**\n * 生成された目次とアンカーリンクを破棄(削除)する\n */\nexport const Destroy = (): void => {\n const mokujiAnchors = document.querySelectorAll(`[${ANCHOR_DATASET_ATTRIBUTE}]`);\n for (let i = 0; i < mokujiAnchors.length; i++) {\n const anchor = mokujiAnchors[i];\n anchor.remove();\n }\n\n const tableOfContentsList = document.querySelector(`[${MOKUJI_LIST_DATASET_ATTRIBUTE}]`);\n if (tableOfContentsList) {\n tableOfContentsList.remove();\n }\n};\n"]}
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "mokuji.js",
3
- "version": "4.6.0",
3
+ "version": "4.8.0",
4
4
  "description": "A table of content JavaScript Library",
5
5
  "scripts": {
6
6
  "build": "tsup",
7
7
  "dev": "tsup --watch",
8
8
  "lint": "eslint --cache ./src/**/*.ts",
9
+ "test": "vitest run",
10
+ "test:watch": "vitest",
9
11
  "prepare": "npm run build && husky"
10
12
  },
11
13
  "type": "module",
12
14
  "main": "./dist/index.js",
13
15
  "exports": {
14
16
  "import": "./dist/index.js",
15
- "require": "./dist/index.cjs",
16
17
  "default": "./dist/index.js"
17
18
  },
18
19
  "types": "./dist/index.d.ts",
@@ -52,15 +53,19 @@
52
53
  },
53
54
  "homepage": "https://github.com/hiro0218/mokuji.js",
54
55
  "devDependencies": {
56
+ "@testing-library/dom": "^10.4.0",
55
57
  "@types/node": "~22.10.1",
56
- "@typescript-eslint/eslint-plugin": "^5.59.7",
57
- "eslint": "^8.41.0",
58
- "eslint-config-prettier": "^8.8.0",
59
- "eslint-plugin-unicorn": "^47.0.0",
58
+ "@typescript-eslint/eslint-plugin": "^8.31.1",
59
+ "@vitest/browser": "^3.1.2",
60
+ "eslint": "^9.26.0",
61
+ "eslint-config-prettier": "^10.1.2",
62
+ "eslint-plugin-unicorn": "^59.0.0",
60
63
  "husky": "^9.1.7",
61
- "lint-staged": "^15.2.10",
62
- "prettier": "^3.4.1",
63
- "tsup": "^8.2.4",
64
- "typescript": "~5.0.4"
64
+ "jsdom": "^26.1.0",
65
+ "lint-staged": "^15.5.1",
66
+ "prettier": "^3.5.3",
67
+ "tsup": "^8.4.0",
68
+ "typescript": "~5.8.3",
69
+ "vitest": "^3.1.2"
65
70
  }
66
71
  }
package/dist/index.cjs DELETED
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var L=(t,n)=>!t||!n?!1:n.contains(t),T=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,"."),f=(t,n="")=>{let e=n,o=1;for(;o<=t.length;){let r=o===1?e:`${e}_${o}`;if(!u.has(r)){e=r,u.add(e);break}o++;}return e},M=(t,n)=>{let e=H(t);return e=e.replaceAll(/&+/g,"").replaceAll(/&amp;+/g,""),n&&(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 n=new Map;for(let e=0;e<t.length;e++){let o=t[e].hash.replace("#","");n.set(o,t[e]);}return n},b=(t,n,e)=>{let o=h("a");o.setAttribute(E,""),e.anchorLinkClassName&&o.classList.add(e.anchorLinkClassName);for(let r=0;r<t.length;r++){let s=t[r],c=n.get(encodeURIComponent(s.id));if(!c)continue;let l=o.cloneNode(!1);l.setAttribute("href",c.hash),e.anchorLinkSymbol&&(l.textContent=e.anchorLinkSymbol),e.anchorLinkPosition==="before"?s.insertBefore(l,s.firstChild):s.append(l);}},y=(t,n)=>{let e=new Map,o=new Map;for(let r=0;r<n.length;r++){let s=n[r],c=s.hash.slice(1),l=o.get(c)||[];l.push(s),o.set(c,l);}for(let r=0;r<t.length;r++){let s=t[r],c=s.id,l=e.get(c)||0;if(l>0){let a=`${c}-${l}`;s.id=a;let p=o.get(c)||[];for(let i=0;i<p.length;i++){let m=p[i];m.href=`#${a}`;}}e.set(c,l+1);}},I=(t,n,e)=>{let o=0,r=h("li"),s=h("a");for(let c=0;c<t.length;c++){let l=t[c],a=Number(l.tagName[1]);if(o!==0&&o<a){let d=h("ol");n.lastChild.append(d),n=d;}else if(o!==0&&o>a)for(let d=0;d<o-a;d++)L(n,n.parentNode)&&(n=n.parentNode?.parentNode);let p=f(t,l.textContent||""),i=M(p,e);l.id=i;let m=s.cloneNode(!1);m.href=`#${i}`,m.textContent=l.textContent;let g=r.cloneNode(!1);g.append(m),n.append(g),o=a;}},U=(t,n)=>{if(!t)return;let e={...N,...n},o=[...T(t)];if(o.length===0)return;let r=h(e.anchorContainerTagName);r.setAttribute(A,""),I(o,r,e.anchorType);let s=[...r.querySelectorAll("a")];if(s.length!==0){if(y(o,s),e.anchorLink){let c=x(s);b(o,c,e);}return r}},_=()=>{let t=document.querySelectorAll(`[${E}]`);for(let e=t.length-1;e>=0;e--)t[e].remove();let n=document.querySelector(`[${A}]`);n&&n.remove(),u.clear();};
4
-
5
- exports.Destroy = _;
6
- exports.Mokuji = U;
7
- //# sourceMappingURL=index.cjs.map
8
- //# sourceMappingURL=index.cjs.map
@@ -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","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":";;AAGO,IAAMA,CAAgB,CAAA,CAACC,CAAsBC,CAAAA,CAAAA,GAC9C,CAACD,CAAW,EAAA,CAACC,CACR,CAAA,CAAA,CAAA,CAGFA,EAAO,QAASD,CAAAA,CAAO,CAMnBE,CAAAA,CAAAA,CAAsBF,GAC1BA,CAAQ,CAAA,gBAAA,CAAqC,wBAAwB,CAAA,CAMjEG,EAAwDC,CAC5D,EAAA,QAAA,CAAS,aAAcA,CAAAA,CAAO,ECtBhC,IAAMC,CAAAA,CAAW,IAAI,GAAA,CAEtBC,EAAgCC,CAC7BA,EAAAA,CAAAA,CAAK,UAAW,CAAA,MAAA,CAAQ,GAAG,CAAE,CAAA,UAAA,CAAW,GAAK,CAAA,EAAE,CAGlDC,CAAAA,CAAAA,CAAgCC,CAC7B,EAAA,kBAAA,CAAmBA,CAAM,CAAE,CAAA,UAAA,CAAW,KAAO,CAAA,GAAG,EAG5CC,CAAe,CAAA,CAACC,CAAgCC,CAAAA,CAAAA,CAAc,KAAO,CAChF,IAAIC,CAAKD,CAAAA,CAAAA,CACLE,EAAe,CAGnB,CAAA,KAAOA,CAAgBH,EAAAA,CAAAA,CAAS,QAAQ,CACtC,IAAMI,CAASD,CAAAA,CAAAA,GAAiB,EAAID,CAAK,CAAA,CAAA,EAAGA,CAAE,CAAA,CAAA,EAAIC,CAAY,CAE9D,CAAA,CAAA,GAAI,CAACT,CAAAA,CAAS,GAAIU,CAAAA,CAAM,CAAG,CAAA,CACzBF,EAAKE,CACLV,CAAAA,CAAAA,CAAS,GAAIQ,CAAAA,CAAE,EACf,KACF,CAEAC,CACF,GAAA,CAEA,OAAOD,CACT,CAAA,CAEaG,CAAqB,CAAA,CAACT,EAAcU,CAA6C,GAAA,CAE5F,IAAIR,CAAAA,CAASH,EAA6BC,CAAI,CAAA,CAG9C,OAAAE,CAAAA,CAASA,EAAO,UAAW,CAAA,KAAA,CAAO,EAAE,CAAA,CAAE,WAAW,SAAW,CAAA,EAAE,CAE1DQ,CAAAA,CAAAA,GACFR,EAASD,CAA6BC,CAAAA,CAAM,CAGvCA,CAAAA,CAAAA,CACT,ECpCMS,IAAAA,CAAAA,CAAgC,kBAChCC,CAAAA,CAAAA,CAA2B,qBAE3BC,CAAiB,CAAA,CACrB,UAAY,CAAA,CAAA,CAAA,CACZ,WAAY,CACZ,CAAA,CAAA,gBAAA,CAAkB,GAClB,CAAA,kBAAA,CAAoB,SACpB,mBAAqB,CAAA,EAAA,CACrB,sBAAwB,CAAA,IAC1B,EAEMC,CAAsBC,CAAAA,CAAAA,EAAiC,CAC3D,IAAMC,EAAY,IAAI,GAAA,CAEtB,IAASC,IAAAA,CAAAA,CAAI,EAAGA,CAAIF,CAAAA,CAAAA,CAAQ,MAAQE,CAAAA,CAAAA,EAAAA,CAAK,CACvC,IAAMC,CAAWH,CAAAA,CAAAA,CAAQE,CAAC,CAAE,CAAA,IAAA,CAAK,OAAQ,CAAA,GAAA,CAAK,EAAE,CAChDD,CAAAA,CAAAA,CAAU,GAAIE,CAAAA,CAAAA,CAAUH,EAAQE,CAAC,CAAC,EACpC,CAEA,OAAOD,CACT,CAAA,CAEMG,CAAyB,CAAA,CAC7Bf,EACAY,CACAI,CAAAA,CAAAA,GACG,CACH,IAAMC,EAAIzB,CAAc,CAAA,GAAG,CAC3ByB,CAAAA,CAAAA,CAAE,aAAaT,CAA0B,CAAA,EAAE,CAEvCQ,CAAAA,CAAAA,CAAQ,mBACVC,EAAAA,CAAAA,CAAE,SAAU,CAAA,GAAA,CAAID,EAAQ,mBAAmB,CAAA,CAG7C,IAASH,IAAAA,CAAAA,CAAI,EAAGA,CAAIb,CAAAA,CAAAA,CAAS,MAAQa,CAAAA,CAAAA,EAAAA,CAAK,CACxC,IAAMK,CAAAA,CAAUlB,CAASa,CAAAA,CAAC,EACpBM,CAAgBP,CAAAA,CAAAA,CAAU,GAAI,CAAA,kBAAA,CAAmBM,EAAQ,EAAE,CAAC,CAElE,CAAA,GAAI,CAACC,CACH,CAAA,SAIF,IAAMrB,CAAAA,CAASmB,EAAE,SAAU,CAAA,CAAA,CAAK,CAChCnB,CAAAA,CAAAA,CAAO,YAAa,CAAA,MAAA,CAAQqB,CAAc,CAAA,IAAI,EAE1CH,CAAQ,CAAA,gBAAA,GACVlB,CAAO,CAAA,WAAA,CAAckB,EAAQ,gBAI3BA,CAAAA,CAAAA,CAAAA,CAAQ,kBAAuB,GAAA,QAAA,CAEjCE,EAAQ,YAAapB,CAAAA,CAAAA,CAAQoB,CAAQ,CAAA,UAAU,EAG/CA,CAAQ,CAAA,MAAA,CAAOpB,CAAM,EAEzB,CACF,CAEMsB,CAAAA,CAAAA,CAAqB,CAACpB,CAAAA,CAAgCW,IAAiC,CAC3F,IAAMU,CAAa,CAAA,IAAI,IACjBT,CAAY,CAAA,IAAI,GAGtB,CAAA,IAAA,IAASC,EAAI,CAAGA,CAAAA,CAAAA,CAAIF,CAAQ,CAAA,MAAA,CAAQE,IAAK,CACvC,IAAMf,CAASa,CAAAA,CAAAA,CAAQE,CAAC,CAClBX,CAAAA,CAAAA,CAAKJ,CAAO,CAAA,IAAA,CAAK,MAAM,CAAC,CAAA,CACxBwB,CAAOV,CAAAA,CAAAA,CAAU,IAAIV,CAAE,CAAA,EAAK,EAAC,CACnCoB,EAAK,IAAKxB,CAAAA,CAAM,CAChBc,CAAAA,CAAAA,CAAU,IAAIV,CAAIoB,CAAAA,CAAI,EACxB,CAGA,QAAST,CAAI,CAAA,CAAA,CAAGA,CAAIb,CAAAA,CAAAA,CAAS,MAAQa,CAAAA,CAAAA,EAAAA,CAAK,CACxC,IAAMK,EAAUlB,CAASa,CAAAA,CAAC,CACpBU,CAAAA,CAAAA,CAAaL,EAAQ,EACrBM,CAAAA,CAAAA,CAAQH,CAAW,CAAA,GAAA,CAAIE,CAAU,CAAK,EAAA,CAAA,CAG5C,GAAIC,CAAAA,CAAQ,EAAG,CACb,IAAMC,CAAQ,CAAA,CAAA,EAAGF,CAAU,CAAIC,CAAAA,EAAAA,CAAK,CACpCN,CAAAA,CAAAA,CAAAA,CAAQ,GAAKO,CAGb,CAAA,IAAMC,CAAkBd,CAAAA,CAAAA,CAAU,IAAIW,CAAU,CAAA,EAAK,EAAC,CACtD,IAASI,IAAAA,CAAAA,CAAI,CAAGA,CAAAA,CAAAA,CAAID,EAAgB,MAAQC,CAAAA,CAAAA,EAAAA,CAAK,CAC/C,IAAM7B,EAAS4B,CAAgBC,CAAAA,CAAC,CAChC7B,CAAAA,CAAAA,CAAO,KAAO,CAAI2B,CAAAA,EAAAA,CAAK,CACzB,EAAA,CACF,CAEAJ,CAAW,CAAA,GAAA,CAAIE,CAAYC,CAAAA,CAAAA,CAAQ,CAAC,EACtC,CACF,CAEMI,CAAAA,CAAAA,CAAwB,CAC5B5B,CACA6B,CAAAA,CAAAA,CACAvB,CACG,GAAA,CACH,IAAIwB,CAAS,CAAA,CAAA,CACPC,CAAmBvC,CAAAA,CAAAA,CAAc,IAAI,CAAA,CACrCwC,CAAqBxC,CAAAA,CAAAA,CAAc,GAAG,CAE5C,CAAA,IAAA,IAASqB,CAAI,CAAA,CAAA,CAAGA,EAAIb,CAAS,CAAA,MAAA,CAAQa,CAAK,EAAA,CAAA,CACxC,IAAMK,CAAUlB,CAAAA,CAAAA,CAASa,CAAC,CAAA,CACpBoB,EAAgB,MAAOf,CAAAA,CAAAA,CAAQ,OAAQ,CAAA,CAAC,CAAC,CAG/C,CAAA,GAAIY,CAAW,GAAA,CAAA,EAAKA,EAASG,CAAe,CAAA,CAE1C,IAAMC,CAAAA,CAAwB1C,EAAc,IAAI,CAAA,CAGhDqC,CAAiB,CAAA,SAAA,CAAU,OAAOK,CAAqB,CAAA,CACvDL,CAAmBK,CAAAA,EACrB,SAAWJ,CAAW,GAAA,CAAA,EAAKA,CAASG,CAAAA,CAAAA,CAElC,QAASpB,CAAI,CAAA,CAAA,CAAGA,CAAIiB,CAAAA,CAAAA,CAASG,EAAepB,CACtCzB,EAAAA,CAAAA,CAAAA,CAAcyC,CAAkBA,CAAAA,CAAAA,CAAiB,UAAU,CAC7DA,GAAAA,CAAAA,CAAmBA,CAAiB,CAAA,UAAA,EAAY,YAKtD,IAAM5B,CAAAA,CAAcF,CAAaC,CAAAA,CAAAA,CAAUkB,EAAQ,WAAe,EAAA,EAAE,CAG9DiB,CAAAA,CAAAA,CAAa9B,EAAmBJ,CAAaK,CAAAA,CAA+B,CAClFY,CAAAA,CAAAA,CAAQ,EAAKiB,CAAAA,CAAAA,CAGb,IAAMC,CAAAA,CAAgBJ,EAAmB,SAAU,CAAA,CAAA,CAAK,CACxDI,CAAAA,CAAAA,CAAc,KAAO,CAAID,CAAAA,EAAAA,CAAU,CACnCC,CAAAA,CAAAA,CAAAA,CAAc,YAAclB,CAAQ,CAAA,WAAA,CACpC,IAAMmB,CAAAA,CAAcN,EAAiB,SAAU,CAAA,CAAA,CAAK,CACpDM,CAAAA,CAAAA,CAAY,OAAOD,CAAa,CAAA,CAEhCP,CAAiB,CAAA,MAAA,CAAOQ,CAAW,CAGnCP,CAAAA,CAAAA,CAASG,EACX,CACF,EAEaK,CAAS,CAAA,CACpBjD,CACAkD,CAAAA,CAAAA,GACoD,CACpD,GAAI,CAAClD,CACH,CAAA,OAIF,IAAM2B,CAAU,CAAA,CACd,GAAGP,CAAAA,CACH,GAAG8B,CACL,CAAA,CAEMvC,CAAW,CAAA,CAAC,GAAGT,CAAmBF,CAAAA,CAAO,CAAC,CAAA,CAEhD,GAAIW,CAAS,CAAA,MAAA,GAAW,CACtB,CAAA,OAIF,IAAM6B,CAAmBrC,CAAAA,CAAAA,CAAcwB,CAAQ,CAAA,sBAAsB,EACrEa,CAAiB,CAAA,YAAA,CAAatB,CAA+B,CAAA,EAAE,EAG/DqB,CAAsB5B,CAAAA,CAAAA,CAAU6B,CAAkBb,CAAAA,CAAAA,CAAQ,UAAU,CAAA,CAEpE,IAAML,CAAAA,CAAU,CAAC,GAAGkB,CAAAA,CAAiB,gBAAiB,CAAA,GAAG,CAAC,CAE1D,CAAA,GAAIlB,CAAQ,CAAA,MAAA,GAAW,EAQvB,CAHAS,GAAAA,CAAAA,CAAmBpB,CAAUW,CAAAA,CAAO,EAGhCK,CAAQ,CAAA,UAAA,CAAY,CACtB,IAAMwB,EAAa9B,CAAmBC,CAAAA,CAAO,CAC7CI,CAAAA,CAAAA,CAAuBf,EAAUwC,CAAYxB,CAAAA,CAAO,EACtD,CAEA,OAAOa,CACT,CAAA,CAAA,CAEaY,CAAU,CAAA,IAAM,CAE3B,IAAMC,CAAAA,CAAe,QAAS,CAAA,gBAAA,CAAiB,IAAIlC,CAAwB,CAAA,CAAA,CAAG,CAC9E,CAAA,IAAA,IAASK,EAAI6B,CAAa,CAAA,MAAA,CAAS,CAAG7B,CAAAA,CAAAA,EAAK,EAAGA,CAC5B6B,EAAAA,CAAAA,CAAAA,CAAa7B,CAAC,CAAA,CACtB,QAIV,CAAA,IAAM8B,CAAa,CAAA,QAAA,CAAS,cAAc,CAAIpC,CAAAA,EAAAA,CAA6B,CAAG,CAAA,CAAA,CAAA,CAC1EoC,GACFA,CAAW,CAAA,MAAA,EAIbjD,CAAAA,CAAAA,CAAS,QACX","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(/&amp;+/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 a.classList.add(options.anchorLinkClassName);\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 };