inkpen 0.7.1

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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.rubocop.yml +8 -0
  4. data/.yardopts +11 -0
  5. data/CLAUDE.md +141 -0
  6. data/README.md +409 -0
  7. data/Rakefile +19 -0
  8. data/app/assets/javascripts/inkpen/controllers/editor_controller.js +2050 -0
  9. data/app/assets/javascripts/inkpen/controllers/sticky_toolbar_controller.js +667 -0
  10. data/app/assets/javascripts/inkpen/controllers/toolbar_controller.js +693 -0
  11. data/app/assets/javascripts/inkpen/export/html.js +637 -0
  12. data/app/assets/javascripts/inkpen/export/index.js +30 -0
  13. data/app/assets/javascripts/inkpen/export/markdown.js +697 -0
  14. data/app/assets/javascripts/inkpen/export/pdf.js +372 -0
  15. data/app/assets/javascripts/inkpen/extensions/advanced_table.js +640 -0
  16. data/app/assets/javascripts/inkpen/extensions/block_commands.js +300 -0
  17. data/app/assets/javascripts/inkpen/extensions/block_gutter.js +338 -0
  18. data/app/assets/javascripts/inkpen/extensions/callout.js +303 -0
  19. data/app/assets/javascripts/inkpen/extensions/columns.js +403 -0
  20. data/app/assets/javascripts/inkpen/extensions/database.js +990 -0
  21. data/app/assets/javascripts/inkpen/extensions/document_section.js +352 -0
  22. data/app/assets/javascripts/inkpen/extensions/drag_handle.js +407 -0
  23. data/app/assets/javascripts/inkpen/extensions/embed.js +629 -0
  24. data/app/assets/javascripts/inkpen/extensions/enhanced_image.js +566 -0
  25. data/app/assets/javascripts/inkpen/extensions/export_commands.js +271 -0
  26. data/app/assets/javascripts/inkpen/extensions/file_attachment.js +593 -0
  27. data/app/assets/javascripts/inkpen/extensions/inkpen_table/index.js +58 -0
  28. data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table.js +638 -0
  29. data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table_cell.js +100 -0
  30. data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table_header.js +100 -0
  31. data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_constants.js +152 -0
  32. data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_helpers.js +254 -0
  33. data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_menu.js +282 -0
  34. data/app/assets/javascripts/inkpen/extensions/preformatted.js +239 -0
  35. data/app/assets/javascripts/inkpen/extensions/section.js +281 -0
  36. data/app/assets/javascripts/inkpen/extensions/section_title.js +126 -0
  37. data/app/assets/javascripts/inkpen/extensions/slash_commands.js +439 -0
  38. data/app/assets/javascripts/inkpen/extensions/table_of_contents.js +474 -0
  39. data/app/assets/javascripts/inkpen/extensions/toggle_block.js +332 -0
  40. data/app/assets/javascripts/inkpen/index.js +87 -0
  41. data/app/assets/stylesheets/inkpen/advanced_table.css +514 -0
  42. data/app/assets/stylesheets/inkpen/animations.css +626 -0
  43. data/app/assets/stylesheets/inkpen/block_gutter.css +265 -0
  44. data/app/assets/stylesheets/inkpen/callout.css +359 -0
  45. data/app/assets/stylesheets/inkpen/columns.css +314 -0
  46. data/app/assets/stylesheets/inkpen/database.css +658 -0
  47. data/app/assets/stylesheets/inkpen/document_section.css +305 -0
  48. data/app/assets/stylesheets/inkpen/drag_drop.css +220 -0
  49. data/app/assets/stylesheets/inkpen/editor.css +652 -0
  50. data/app/assets/stylesheets/inkpen/embed.css +468 -0
  51. data/app/assets/stylesheets/inkpen/enhanced_image.css +453 -0
  52. data/app/assets/stylesheets/inkpen/export.css +499 -0
  53. data/app/assets/stylesheets/inkpen/file_attachment.css +347 -0
  54. data/app/assets/stylesheets/inkpen/footnotes.css +136 -0
  55. data/app/assets/stylesheets/inkpen/inkpen_table.css +608 -0
  56. data/app/assets/stylesheets/inkpen/preformatted.css +215 -0
  57. data/app/assets/stylesheets/inkpen/search_replace.css +58 -0
  58. data/app/assets/stylesheets/inkpen/section.css +236 -0
  59. data/app/assets/stylesheets/inkpen/slash_menu.css +252 -0
  60. data/app/assets/stylesheets/inkpen/sticky_toolbar.css +314 -0
  61. data/app/assets/stylesheets/inkpen/toc.css +386 -0
  62. data/app/assets/stylesheets/inkpen/toggle.css +260 -0
  63. data/app/helpers/inkpen/editor_helper.rb +114 -0
  64. data/app/views/inkpen/_editor.html.erb +139 -0
  65. data/config/importmap.rb +170 -0
  66. data/docs/.DS_Store +0 -0
  67. data/docs/CHANGELOG.md +571 -0
  68. data/docs/FEATURES.md +436 -0
  69. data/docs/ROADMAP.md +3029 -0
  70. data/docs/VISION.md +235 -0
  71. data/docs/extensions/INKPEN_TABLE.md +482 -0
  72. data/docs/thinking/CORRECTED_NO_VUE.md +756 -0
  73. data/docs/thinking/EXECUTIVE_SUMMARY.md +403 -0
  74. data/docs/thinking/INKPEN_CODE_SAMPLES.md +1479 -0
  75. data/docs/thinking/INKPEN_MASTER_GUIDE.md +891 -0
  76. data/docs/thinking/README_START_HERE.md +341 -0
  77. data/lib/inkpen/configuration.rb +175 -0
  78. data/lib/inkpen/editor.rb +204 -0
  79. data/lib/inkpen/engine.rb +32 -0
  80. data/lib/inkpen/extensions/base.rb +109 -0
  81. data/lib/inkpen/extensions/code_block_syntax.rb +177 -0
  82. data/lib/inkpen/extensions/document_section.rb +111 -0
  83. data/lib/inkpen/extensions/forced_document.rb +183 -0
  84. data/lib/inkpen/extensions/mention.rb +155 -0
  85. data/lib/inkpen/extensions/preformatted.rb +111 -0
  86. data/lib/inkpen/extensions/section.rb +139 -0
  87. data/lib/inkpen/extensions/slash_commands.rb +100 -0
  88. data/lib/inkpen/extensions/table.rb +182 -0
  89. data/lib/inkpen/extensions/task_list.rb +145 -0
  90. data/lib/inkpen/sticky_toolbar.rb +157 -0
  91. data/lib/inkpen/toolbar.rb +145 -0
  92. data/lib/inkpen/version.rb +5 -0
  93. data/lib/inkpen.rb +101 -0
  94. data/sig/inkpen.rbs +4 -0
  95. metadata +165 -0
@@ -0,0 +1,474 @@
1
+ import { Node } from "@tiptap/core"
2
+
3
+ /**
4
+ * Table of Contents Extension for TipTap
5
+ *
6
+ * Auto-generated navigation from document headings.
7
+ *
8
+ * Features:
9
+ * - Auto-detect headings (H1-H6)
10
+ * - Clickable links with smooth scroll
11
+ * - Configurable max depth
12
+ * - Numbered, bulleted, or plain style
13
+ * - Collapsible sections
14
+ * - Sticky positioning option
15
+ * - Real-time updates as document changes
16
+ *
17
+ * @example
18
+ * editor.commands.insertTableOfContents()
19
+ * editor.commands.setTocMaxDepth(3)
20
+ * editor.commands.setTocStyle('numbered')
21
+ *
22
+ * @since 0.6.0
23
+ */
24
+
25
+ // TOC style options
26
+ const TOC_STYLES = {
27
+ numbered: { label: "Numbered", description: "1. 2. 3." },
28
+ bulleted: { label: "Bulleted", description: "• • •" },
29
+ plain: { label: "Plain", description: "No markers" }
30
+ }
31
+
32
+ export const TableOfContents = Node.create({
33
+ name: "tableOfContents",
34
+
35
+ group: "block",
36
+ atom: true,
37
+ draggable: true,
38
+ selectable: true,
39
+
40
+ addOptions() {
41
+ return {
42
+ defaultMaxDepth: 3,
43
+ defaultStyle: "numbered",
44
+ defaultTitle: "Table of Contents",
45
+ scrollOffset: 100,
46
+ HTMLAttributes: {
47
+ class: "inkpen-toc"
48
+ }
49
+ }
50
+ },
51
+
52
+ addAttributes() {
53
+ return {
54
+ maxDepth: {
55
+ default: this.options.defaultMaxDepth,
56
+ parseHTML: element => parseInt(element.getAttribute("data-max-depth")) || this.options.defaultMaxDepth,
57
+ renderHTML: attributes => ({ "data-max-depth": attributes.maxDepth })
58
+ },
59
+ style: {
60
+ default: this.options.defaultStyle,
61
+ parseHTML: element => element.getAttribute("data-style") || this.options.defaultStyle,
62
+ renderHTML: attributes => ({ "data-style": attributes.style })
63
+ },
64
+ title: {
65
+ default: this.options.defaultTitle,
66
+ parseHTML: element => element.getAttribute("data-title") || this.options.defaultTitle,
67
+ renderHTML: attributes => ({ "data-title": attributes.title })
68
+ },
69
+ collapsible: {
70
+ default: false,
71
+ parseHTML: element => element.hasAttribute("data-collapsible"),
72
+ renderHTML: attributes => attributes.collapsible ? { "data-collapsible": "" } : {}
73
+ },
74
+ sticky: {
75
+ default: false,
76
+ parseHTML: element => element.hasAttribute("data-sticky"),
77
+ renderHTML: attributes => attributes.sticky ? { "data-sticky": "" } : {}
78
+ }
79
+ }
80
+ },
81
+
82
+ parseHTML() {
83
+ return [
84
+ { tag: "nav.inkpen-toc" },
85
+ { tag: "div.inkpen-toc" },
86
+ { tag: "[data-toc]" }
87
+ ]
88
+ },
89
+
90
+ renderHTML({ HTMLAttributes }) {
91
+ return ["nav", { ...this.options.HTMLAttributes, ...HTMLAttributes, "data-toc": "" }, 0]
92
+ },
93
+
94
+ addNodeView() {
95
+ return ({ node, editor, getPos }) => {
96
+ const extension = this
97
+
98
+ // Main container
99
+ const dom = document.createElement("nav")
100
+ dom.className = "inkpen-toc"
101
+ if (node.attrs.sticky) dom.classList.add("inkpen-toc--sticky")
102
+ if (node.attrs.collapsible) dom.classList.add("inkpen-toc--collapsible")
103
+
104
+ // State
105
+ let isCollapsed = false
106
+
107
+ // Render function
108
+ const render = () => {
109
+ const headings = extension.getHeadings(editor, node.attrs.maxDepth)
110
+ dom.innerHTML = extension.renderTOC(headings, node.attrs, editor.isEditable)
111
+ extension.attachEventHandlers(dom, editor, getPos, node, () => isCollapsed, (val) => { isCollapsed = val })
112
+ }
113
+
114
+ // Initial render
115
+ render()
116
+
117
+ // Update on document changes
118
+ const updateHandler = () => render()
119
+ editor.on("update", updateHandler)
120
+
121
+ return {
122
+ dom,
123
+ update: (updatedNode) => {
124
+ if (updatedNode.type.name !== "tableOfContents") return false
125
+
126
+ // Update classes
127
+ dom.classList.toggle("inkpen-toc--sticky", updatedNode.attrs.sticky)
128
+ dom.classList.toggle("inkpen-toc--collapsible", updatedNode.attrs.collapsible)
129
+
130
+ // Re-render with new attributes
131
+ const headings = extension.getHeadings(editor, updatedNode.attrs.maxDepth)
132
+ dom.innerHTML = extension.renderTOC(headings, updatedNode.attrs, editor.isEditable)
133
+ extension.attachEventHandlers(dom, editor, getPos, updatedNode, () => isCollapsed, (val) => { isCollapsed = val })
134
+
135
+ return true
136
+ },
137
+ destroy: () => {
138
+ editor.off("update", updateHandler)
139
+ }
140
+ }
141
+ }
142
+ },
143
+
144
+ addCommands() {
145
+ return {
146
+ insertTableOfContents: (options = {}) => ({ commands }) => {
147
+ return commands.insertContent({
148
+ type: this.name,
149
+ attrs: {
150
+ maxDepth: options.maxDepth || this.options.defaultMaxDepth,
151
+ style: options.style || this.options.defaultStyle,
152
+ title: options.title || this.options.defaultTitle,
153
+ collapsible: options.collapsible || false,
154
+ sticky: options.sticky || false
155
+ }
156
+ })
157
+ },
158
+
159
+ setTocMaxDepth: (maxDepth) => ({ commands }) => {
160
+ return commands.updateAttributes(this.name, { maxDepth })
161
+ },
162
+
163
+ setTocStyle: (style) => ({ commands }) => {
164
+ return commands.updateAttributes(this.name, { style })
165
+ },
166
+
167
+ setTocTitle: (title) => ({ commands }) => {
168
+ return commands.updateAttributes(this.name, { title })
169
+ },
170
+
171
+ toggleTocCollapsible: () => ({ commands, state }) => {
172
+ const { selection } = state
173
+ const node = findTocNode(selection)
174
+ if (!node) return false
175
+ return commands.updateAttributes(this.name, { collapsible: !node.attrs.collapsible })
176
+ },
177
+
178
+ toggleTocSticky: () => ({ commands, state }) => {
179
+ const { selection } = state
180
+ const node = findTocNode(selection)
181
+ if (!node) return false
182
+ return commands.updateAttributes(this.name, { sticky: !node.attrs.sticky })
183
+ }
184
+ }
185
+ },
186
+
187
+ addKeyboardShortcuts() {
188
+ return {
189
+ "Mod-Shift-t": () => this.editor.commands.insertTableOfContents()
190
+ }
191
+ },
192
+
193
+ // Private: Get headings from document
194
+
195
+ getHeadings(editor, maxDepth) {
196
+ const headings = []
197
+ let index = 0
198
+
199
+ editor.state.doc.descendants((node, pos) => {
200
+ if (node.type.name === "heading" && node.attrs.level <= maxDepth) {
201
+ const text = node.textContent.trim()
202
+ if (text) {
203
+ headings.push({
204
+ id: `toc-heading-${index++}`,
205
+ level: node.attrs.level,
206
+ text,
207
+ pos
208
+ })
209
+ }
210
+ }
211
+ })
212
+
213
+ return headings
214
+ },
215
+
216
+ // Private: Render TOC HTML
217
+
218
+ renderTOC(headings, attrs, isEditable) {
219
+ const { title, style, collapsible } = attrs
220
+
221
+ // Header
222
+ let html = `
223
+ <div class="inkpen-toc__header">
224
+ <span class="inkpen-toc__title">${escapeHtml(title)}</span>
225
+ ${collapsible ? '<button type="button" class="inkpen-toc__toggle" aria-label="Toggle">▼</button>' : ''}
226
+ ${isEditable ? '<button type="button" class="inkpen-toc__settings" aria-label="Settings">⚙</button>' : ''}
227
+ </div>
228
+ `
229
+
230
+ // Empty state
231
+ if (headings.length === 0) {
232
+ html += `
233
+ <div class="inkpen-toc__empty">
234
+ No headings found. Add headings to generate a table of contents.
235
+ </div>
236
+ `
237
+ return html
238
+ }
239
+
240
+ // Navigation list
241
+ const listTag = style === "numbered" ? "ol" : "ul"
242
+
243
+ html += `
244
+ <nav class="inkpen-toc__nav">
245
+ <${listTag} class="inkpen-toc__list inkpen-toc__list--${style}">
246
+ ${headings.map(h => `
247
+ <li class="inkpen-toc__item inkpen-toc__item--level-${h.level}" style="--toc-indent: ${(h.level - 1) * 1}rem">
248
+ <a href="#${h.id}" class="inkpen-toc__link" data-pos="${h.pos}">
249
+ ${escapeHtml(h.text)}
250
+ </a>
251
+ </li>
252
+ `).join("")}
253
+ </${listTag}>
254
+ </nav>
255
+ `
256
+
257
+ return html
258
+ },
259
+
260
+ // Private: Attach event handlers
261
+
262
+ attachEventHandlers(dom, editor, getPos, node, getCollapsed, setCollapsed) {
263
+ const extension = this
264
+
265
+ // Toggle collapse
266
+ const toggleBtn = dom.querySelector(".inkpen-toc__toggle")
267
+ if (toggleBtn) {
268
+ toggleBtn.addEventListener("click", (e) => {
269
+ e.preventDefault()
270
+ const newCollapsed = !getCollapsed()
271
+ setCollapsed(newCollapsed)
272
+ dom.classList.toggle("is-collapsed", newCollapsed)
273
+ toggleBtn.textContent = newCollapsed ? "▶" : "▼"
274
+ })
275
+ }
276
+
277
+ // Settings button
278
+ const settingsBtn = dom.querySelector(".inkpen-toc__settings")
279
+ if (settingsBtn) {
280
+ settingsBtn.addEventListener("click", (e) => {
281
+ e.preventDefault()
282
+ e.stopPropagation()
283
+ extension.showSettingsDropdown(settingsBtn, node, editor, getPos)
284
+ })
285
+ }
286
+
287
+ // Heading links
288
+ dom.querySelectorAll(".inkpen-toc__link").forEach(link => {
289
+ link.addEventListener("click", (e) => {
290
+ e.preventDefault()
291
+ const pos = parseInt(link.dataset.pos)
292
+
293
+ // Scroll to heading
294
+ const coords = editor.view.coordsAtPos(pos)
295
+ const scrollTop = window.pageYOffset + coords.top - extension.options.scrollOffset
296
+
297
+ window.scrollTo({
298
+ top: scrollTop,
299
+ behavior: "smooth"
300
+ })
301
+
302
+ // Focus editor at heading position
303
+ editor.chain().focus().setTextSelection(pos + 1).run()
304
+ })
305
+ })
306
+ },
307
+
308
+ // Private: Show settings dropdown
309
+
310
+ showSettingsDropdown(anchor, node, editor, getPos) {
311
+ removeExistingDropdown()
312
+
313
+ const dropdown = document.createElement("div")
314
+ dropdown.className = "inkpen-toc__dropdown"
315
+
316
+ // Max depth options
317
+ const depthSection = document.createElement("div")
318
+ depthSection.className = "inkpen-toc__dropdown-section"
319
+ depthSection.innerHTML = `<div class="inkpen-toc__dropdown-label">Max Depth</div>`
320
+
321
+ const depthGroup = document.createElement("div")
322
+ depthGroup.className = "inkpen-toc__dropdown-row"
323
+
324
+ for (let d = 1; d <= 6; d++) {
325
+ const btn = document.createElement("button")
326
+ btn.type = "button"
327
+ btn.className = "inkpen-toc__dropdown-btn"
328
+ if (node.attrs.maxDepth === d) btn.classList.add("is-active")
329
+ btn.textContent = `H${d}`
330
+
331
+ btn.addEventListener("mousedown", (e) => e.preventDefault())
332
+ btn.addEventListener("click", () => {
333
+ editor.chain().focus().setTocMaxDepth(d).run()
334
+ dropdown.remove()
335
+ })
336
+
337
+ depthGroup.appendChild(btn)
338
+ }
339
+
340
+ depthSection.appendChild(depthGroup)
341
+ dropdown.appendChild(depthSection)
342
+
343
+ // Style options
344
+ const styleSection = document.createElement("div")
345
+ styleSection.className = "inkpen-toc__dropdown-section"
346
+ styleSection.innerHTML = `<div class="inkpen-toc__dropdown-label">Style</div>`
347
+
348
+ Object.entries(TOC_STYLES).forEach(([key, { label }]) => {
349
+ const btn = document.createElement("button")
350
+ btn.type = "button"
351
+ btn.className = "inkpen-toc__dropdown-item"
352
+ if (node.attrs.style === key) btn.classList.add("is-active")
353
+ btn.textContent = label
354
+
355
+ btn.addEventListener("mousedown", (e) => e.preventDefault())
356
+ btn.addEventListener("click", () => {
357
+ editor.chain().focus().setTocStyle(key).run()
358
+ dropdown.remove()
359
+ })
360
+
361
+ styleSection.appendChild(btn)
362
+ })
363
+
364
+ dropdown.appendChild(styleSection)
365
+
366
+ // Toggle options
367
+ const toggleSection = document.createElement("div")
368
+ toggleSection.className = "inkpen-toc__dropdown-section"
369
+
370
+ const stickyBtn = document.createElement("button")
371
+ stickyBtn.type = "button"
372
+ stickyBtn.className = "inkpen-toc__dropdown-item"
373
+ if (node.attrs.sticky) stickyBtn.classList.add("is-active")
374
+ stickyBtn.textContent = "Sticky"
375
+
376
+ stickyBtn.addEventListener("mousedown", (e) => e.preventDefault())
377
+ stickyBtn.addEventListener("click", () => {
378
+ if (typeof getPos === "function") {
379
+ const pos = getPos()
380
+ if (pos !== undefined) {
381
+ editor.chain().command(({ tr }) => {
382
+ tr.setNodeMarkup(pos, undefined, { ...node.attrs, sticky: !node.attrs.sticky })
383
+ return true
384
+ }).run()
385
+ }
386
+ }
387
+ dropdown.remove()
388
+ })
389
+
390
+ toggleSection.appendChild(stickyBtn)
391
+
392
+ const collapsibleBtn = document.createElement("button")
393
+ collapsibleBtn.type = "button"
394
+ collapsibleBtn.className = "inkpen-toc__dropdown-item"
395
+ if (node.attrs.collapsible) collapsibleBtn.classList.add("is-active")
396
+ collapsibleBtn.textContent = "Collapsible"
397
+
398
+ collapsibleBtn.addEventListener("mousedown", (e) => e.preventDefault())
399
+ collapsibleBtn.addEventListener("click", () => {
400
+ if (typeof getPos === "function") {
401
+ const pos = getPos()
402
+ if (pos !== undefined) {
403
+ editor.chain().command(({ tr }) => {
404
+ tr.setNodeMarkup(pos, undefined, { ...node.attrs, collapsible: !node.attrs.collapsible })
405
+ return true
406
+ }).run()
407
+ }
408
+ }
409
+ dropdown.remove()
410
+ })
411
+
412
+ toggleSection.appendChild(collapsibleBtn)
413
+ dropdown.appendChild(toggleSection)
414
+
415
+ // Position dropdown
416
+ const rect = anchor.getBoundingClientRect()
417
+ dropdown.style.position = "fixed"
418
+ dropdown.style.left = `${rect.right - 160}px`
419
+ dropdown.style.top = `${rect.bottom + 4}px`
420
+ dropdown.style.zIndex = "10000"
421
+ document.body.appendChild(dropdown)
422
+
423
+ // Close handlers
424
+ const closeHandler = (e) => {
425
+ if (!dropdown.contains(e.target) && !anchor.contains(e.target)) {
426
+ dropdown.remove()
427
+ document.removeEventListener("mousedown", closeHandler)
428
+ }
429
+ }
430
+
431
+ setTimeout(() => {
432
+ document.addEventListener("mousedown", closeHandler)
433
+ }, 0)
434
+
435
+ const escHandler = (e) => {
436
+ if (e.key === "Escape") {
437
+ dropdown.remove()
438
+ document.removeEventListener("keydown", escHandler)
439
+ }
440
+ }
441
+ document.addEventListener("keydown", escHandler)
442
+ }
443
+ })
444
+
445
+ // Helper: Find TOC node in selection
446
+
447
+ function findTocNode(selection) {
448
+ const { $from } = selection
449
+ for (let d = $from.depth; d > 0; d--) {
450
+ const node = $from.node(d)
451
+ if (node.type.name === "tableOfContents") {
452
+ return node
453
+ }
454
+ }
455
+ return null
456
+ }
457
+
458
+ // Helper: Remove existing dropdown
459
+
460
+ function removeExistingDropdown() {
461
+ const existing = document.querySelector(".inkpen-toc__dropdown")
462
+ if (existing) existing.remove()
463
+ }
464
+
465
+ // Helper: Escape HTML
466
+
467
+ function escapeHtml(text) {
468
+ const div = document.createElement("div")
469
+ div.textContent = text
470
+ return div.innerHTML
471
+ }
472
+
473
+ export { TOC_STYLES }
474
+ export default TableOfContents