]> BookStack Code Mirror - hacks/blob - content/wysiwyg-footnotes/head.html
Added wysiwyg footnotes hack
[hacks] / content / wysiwyg-footnotes / head.html
1 <script>
2     // Take a footnote anchor and convert it to the HTML that would be expected
3     // at the bottom of the page in the list of references.
4     function footnoteToHtml(elem) {
5         const newWrap = document.createElement('div');
6         const newAnchor = document.createElement('a');
7         const sup = document.createElement('sup');
8         const text = document.createTextNode(' ' + elem.title.trim());
9         sup.textContent = elem.textContent.trim();
10         newAnchor.id = elem.getAttribute('href').replace('#', '');
11         newAnchor.href = '#';
12         newAnchor.append(sup);
13         newWrap.append(newAnchor, text);
14         return newWrap.outerHTML;
15     }
16
17     // Reset the numbering of all footnotes within the editor
18     function resetFootnoteNumbering(editor) {
19         const footnotes = editor.dom.select('a[href^="#bkmrk-footnote-"]');
20         for (let i = 0; i < footnotes.length; i++) {
21             const footnote = footnotes[i];
22             const textEl = footnote.querySelector('sup') || footnote;
23             textEl.textContent = String(i + 1);
24         }
25     }
26
27     // Update the footnotes list at the bottom of the content.
28     function updateFootnotes(editor) {
29         // Filter out existing footnote blocks on parse
30         const footnoteBlocks = editor.dom.select('body > div.footnotes');
31         for (const blocks of footnoteBlocks) {
32             blocks.remove();
33         }
34
35         // Gather our existing footnote references and return if nothing to add
36         const footnotes = editor.dom.select('a[href^="#bkmrk-footnote-"]');
37         if (footnotes.length === 0) {
38             return;
39         }
40
41         // Build and append our footnote block
42         resetFootnoteNumbering(editor);
43         const footnoteHtml = [...footnotes].map(f => footnoteToHtml(f));
44         editor.dom.add(editor.getBody(), 'div', {class: 'footnotes'}, '<hr/>' + footnoteHtml.join('\n'));
45     }
46
47     // Get the current selected footnote (if any)
48     function getSelectedFootnote(editor) {
49         return editor.selection.getNode().closest('a[href^="#bkmrk-footnote-"]');
50     }
51
52     // Insert a new footnote element within the editor at cursor position.
53     function insertFootnote(editor, text) {
54         const sup = editor.dom.create('sup', {}, '1');
55         const anchor = editor.dom.create('a', {href: `#bkmrk-footnote-${Date.now()}`, title: text});
56         anchor.append(sup);
57         editor.selection.collapse(false);
58         editor.insertContent(anchor.outerHTML + ' ');
59     }
60
61     function showFootnoteInsertDialog(editor) {
62         const footnote = getSelectedFootnote(editor);
63
64         // Show a custom form dialog window to edit the footnote text/label
65         const dialog = editor.windowManager.open({
66             title: 'Edit Footnote',
67             body: {
68                 type: 'panel',
69                 items: [{type: 'input', name: 'text', label: 'Footnote Label/Text'}],
70             },
71             buttons: [
72                 {type: 'cancel', text: 'Cancel'},
73                 {type: 'submit', text: 'Save', primary: true},
74             ],
75             onSubmit(api) {
76                 // On submit update or insert a footnote element
77                 const {text} = api.getData();
78                 if (footnote) {
79                     footnote.setAttribute('title', text);
80                 } else {
81                     insertFootnote(editor, text);
82                     editor.execCommand('RemoveFormat');
83                 }
84                 updateFootnotes(editor);
85                 api.close();
86             },
87         });
88
89         if (footnote) {
90             dialog.setData({text: footnote.getAttribute('title')});
91         }
92     }
93
94     // Listen to pre-init event to customize TinyMCE config
95     window.addEventListener('editor-tinymce::pre-init', event => {
96         const tinyConfig = event.detail.config;
97         // Add our custom footnote button to the toolbar
98         tinyConfig.toolbar = tinyConfig.toolbar.replace('italic ', 'italic footnote ');
99     });
100
101     // Listen to setup event so we customize the editor.
102     window.addEventListener('editor-tinymce::setup', event => {
103         // Get a reference to the TinyMCE Editor instance
104         const editor = event.detail.editor;
105
106         // Add our custom footnote button
107         editor.ui.registry.addToggleButton('footnote', {
108             icon: 'footnote',
109             tooltip: 'Add Footnote',
110             active: false,
111             onAction() {
112                 showFootnoteInsertDialog(editor);
113             },
114             onSetup(api) {
115                 editor.on('NodeChange', event => {
116                     api.setActive(Boolean(getSelectedFootnote(editor)));
117                 });
118             },
119         });
120
121         // Update footnotes before editor content is fetched
122         editor.on('BeforeGetContent', () => {
123             updateFootnotes(editor);
124         });
125     });
126 </script>