1(function () {
2    function initExtranetProsemirror() {
3        if (window.__extranetProsemirrorInitialized) return;
4        if (!window.Prosemirror || !window.Prosemirror.classes) return;
5        window.__extranetProsemirrorInitialized = true;
6
7        const {classes: {MenuItem, AbstractMenuItemDispatcher}} = window.Prosemirror;
8        const polyfill = window.DokuWikiProsemirrorFileStatePolyfill || null;
9        let localMacroState = {noextranet: null, extranet: null};
10
11        const defaultMode = window.JSINFO && JSINFO.plugin_extranet_default_mode
12            ? String(JSINFO.plugin_extranet_default_mode).toLowerCase()
13            : 'allow';
14
15        function isFixedMode() {
16            return defaultMode === 'force_allow' || defaultMode === 'force_block';
17        }
18
19        function hiddenMenuItem() {
20            return new MenuItem({
21                label: '',
22                render: () => {
23                    const el = document.createElement('span');
24                    el.style.display = 'none';
25                    return el;
26                },
27                command: () => false
28            });
29        }
30
31        function getNoExtranetLabel() {
32            return (
33                (window.LANG &&
34                    LANG.plugins &&
35                    LANG.plugins.prosemirror &&
36                    LANG.plugins.prosemirror['label:noextranet']) ||
37                (window.JSINFO && JSINFO.plugin_extranet_label_noextranet) ||
38                'Blocked from Extranet'
39            );
40        }
41
42        function getExtranetLabel() {
43            return (
44                (window.JSINFO && JSINFO.plugin_extranet_label_extranet) ||
45                'Allowed from Extranet'
46            );
47        }
48
49        function getManagedMacroKey() {
50            if (isFixedMode()) return null;
51            return defaultMode === 'block' ? 'extranet' : 'noextranet';
52        }
53
54        function getManagedMacroLabel() {
55            return getManagedMacroKey() === 'extranet' ? getExtranetLabel() : getNoExtranetLabel();
56        }
57
58        function getSettingsLabel() {
59            return (
60                (window.LANG &&
61                    LANG.plugins &&
62                    LANG.plugins.prosemirror &&
63                    LANG.plugins.prosemirror['label:settings']) ||
64                'Page Settings'
65            );
66        }
67
68        function getSettingsChildLabels() {
69            const labels = [];
70            const fromLang = window.LANG &&
71                LANG.plugins &&
72                LANG.plugins.prosemirror
73                ? LANG.plugins.prosemirror
74                : {};
75
76            labels.push(fromLang['label:nocache'] || 'Deactivate Cache');
77            labels.push(fromLang['label:notoc'] || 'Hide Table of Contents');
78            labels.push('NOCACHE');
79            labels.push('NOTOC');
80
81            return labels
82                .filter(Boolean)
83                .map((s) => String(s).trim().toLowerCase());
84        }
85
86        function findSettingsDropdown(menubar) {
87            const settingsLabel = getSettingsLabel();
88            const knownChildLabels = getSettingsChildLabels();
89            const dropdowns = menubar.querySelectorAll('.menuitem.dropdown');
90            let found = null;
91
92            dropdowns.forEach((dropdown) => {
93                if (found) return;
94                const labelEl = dropdown.querySelector(':scope > .menulabel');
95                const dropdownLabelText = String((labelEl && labelEl.textContent) || '').trim();
96                const dropdownLabelTitle = String((labelEl && labelEl.getAttribute('title')) || '').trim();
97
98                const looksLikeSettings =
99                    dropdownLabelText === settingsLabel ||
100                    dropdownLabelTitle === settingsLabel;
101
102                const dropdownContent = dropdown.querySelector('.dropdown_content');
103                const childTexts = dropdownContent
104                    ? Array.from(dropdownContent.querySelectorAll('.menuitem .menulabel'))
105                        .map((el) => (
106                            String(el.textContent || el.getAttribute('title') || '')
107                                .trim()
108                                .toLowerCase()
109                        ))
110                    : [];
111
112                const hasKnownSettingsChildren = childTexts.some((txt) => knownChildLabels.includes(txt));
113
114                if (looksLikeSettings || hasKnownSettingsChildren) {
115                    found = dropdown;
116                }
117            });
118
119            return found;
120        }
121
122
123        window.Prosemirror.pluginSchemas.push((nodes, marks) => {
124            const doc = nodes.get('doc');
125            if (!doc) return {nodes, marks};
126
127            const attrs = {...(doc.attrs || {})};
128            let changed = false;
129
130            if (typeof attrs.noextranet === 'undefined') {
131                attrs.noextranet = {default: false};
132                changed = true;
133            }
134            if (typeof attrs.extranet === 'undefined') {
135                attrs.extranet = {default: false};
136                changed = true;
137            }
138
139            if (!changed) return {nodes, marks};
140
141            const updatedDoc = {
142                ...doc,
143                attrs
144            };
145            nodes = nodes.update('doc', updatedDoc);
146            return {nodes, marks};
147        });
148
149        function readMacroStateFromDoc(doc) {
150            if (!doc) return null;
151
152            if (doc.attrs && (typeof doc.attrs.noextranet !== 'undefined' || typeof doc.attrs.extranet !== 'undefined')) {
153                return {
154                    noextranet: !!doc.attrs.noextranet,
155                    extranet: !!doc.attrs.extranet
156                };
157            }
158
159            try {
160                const raw = doc.textBetween(0, doc.content.size, '\n', '\n');
161                return {
162                    noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw),
163                    extranet: /~~\s*EXTRANET\s*~~/i.test(raw)
164                };
165            } catch (e) {
166                return null;
167            }
168        }
169
170        function readMacroStateFromJsonField() {
171            const input = document.querySelector('#dw__editform [name="prosemirror_json"]');
172            if (!input || !input.value) return null;
173
174            try {
175                const json = JSON.parse(input.value);
176                if (json && json.attrs) {
177                    if (typeof json.attrs.noextranet !== 'undefined' || typeof json.attrs.extranet !== 'undefined') {
178                        return {
179                            noextranet: !!json.attrs.noextranet,
180                            extranet: !!json.attrs.extranet
181                        };
182                    }
183                }
184            } catch (e) {
185                // fallback below
186            }
187
188            const raw = String(input.value || '');
189            return {
190                noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw),
191                extranet: /~~\s*EXTRANET\s*~~/i.test(raw)
192            };
193        }
194
195        function readMacroStateFromWikiTextarea() {
196            const textarea = document.querySelector('#dw__editform textarea[name="wikitext"], #dw__editform #wiki__text');
197            if (!textarea) return null;
198            const raw = String(textarea.value || '');
199            return {
200                noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw),
201                extranet: /~~\s*EXTRANET\s*~~/i.test(raw)
202            };
203        }
204
205        function persistMacroStateInJsonField(state, macroState) {
206            const input = document.querySelector('#dw__editform [name="prosemirror_json"]');
207            if (!input || !state || !state.doc) return false;
208
209            try {
210                const json = polyfill && typeof polyfill.mergeAttrsFromHiddenJson === 'function'
211                    ? polyfill.mergeAttrsFromHiddenJson(state, input, {
212                        noextranet: !!macroState.noextranet,
213                        extranet: !!macroState.extranet
214                    })
215                    : state.doc.toJSON();
216
217                if (!json) return false;
218                if (!(polyfill && typeof polyfill.mergeAttrsFromHiddenJson === 'function')) {
219                    json.attrs = {
220                        ...(json.attrs || {}),
221                        noextranet: !!macroState.noextranet,
222                        extranet: !!macroState.extranet
223                    };
224                    input.value = JSON.stringify(json);
225                }
226                return true;
227            } catch (e) {
228                return false;
229            }
230        }
231
232        function getCurrentEditorView() {
233            return window.Prosemirror && window.Prosemirror.view ? window.Prosemirror.view : null;
234        }
235
236        function syncCurrentMacroStateToJsonField() {
237            const view = getCurrentEditorView();
238            if (!view || !view.state) return false;
239            const macroState = getMacroState(view.state);
240            return persistMacroStateInJsonField(view.state, macroState);
241        }
242
243        function getMacroState(state) {
244            // Prefer local state right after interactions to avoid one-tick lag from editor state.
245            if (localMacroState.noextranet !== null || localMacroState.extranet !== null) {
246                return {
247                    noextranet: !!localMacroState.noextranet,
248                    extranet: !!localMacroState.extranet
249                };
250            }
251
252            const fromDoc = state && state.doc ? readMacroStateFromDoc(state.doc) : null;
253            if (fromDoc) {
254                return fromDoc;
255            }
256
257            const fromJson = readMacroStateFromJsonField();
258            if (fromJson) return fromJson;
259
260            return {noextranet: false, extranet: false};
261        }
262
263        function applyMacroStateToText(text, macroState) {
264            const cleaned = String(text || '').replace(/^[\t ]*~~[\t ]*(NOEXTRANET|EXTRANET)[\t ]*~~[\t ]*(\r?\n)?/gimu, '').replace(/\s+$/, '');
265
266            if (macroState && macroState.noextranet) {
267                return cleaned ? `${cleaned}\n\n~~NOEXTRANET~~\n` : '~~NOEXTRANET~~\n';
268            }
269
270            if (macroState && macroState.extranet) {
271                return cleaned ? `${cleaned}\n\n~~EXTRANET~~\n` : '~~EXTRANET~~\n';
272            }
273
274            return cleaned ? `${cleaned}\n` : '';
275        }
276
277        function ensureShowDefaultEditorWrapped() {
278            if (window.__extranetWrappedShowDefaultEditor) return true;
279            if (typeof window.showDefaultEditor !== 'function') return false;
280
281            const originalShowDefaultEditor = window.showDefaultEditor;
282            window.showDefaultEditor = function extranetShowDefaultEditor(text) {
283                const view = getCurrentEditorView();
284                const nextText = applyMacroStateToText(text, getMacroState(view && view.state));
285                return originalShowDefaultEditor.call(this, nextText);
286            };
287            window.__extranetWrappedShowDefaultEditor = true;
288            return true;
289        }
290
291        function renderCheckboxIcon(checked) {
292            const ns = 'http://www.w3.org/2000/svg';
293            const svg = document.createElementNS(ns, 'svg');
294            svg.setAttribute('viewBox', '0 0 24 24');
295
296            const path = document.createElementNS(ns, 'path');
297            if (checked) {
298                path.setAttribute('d', 'M19,19H5V5H15V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V11H19M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z');
299            } else {
300                path.setAttribute('d', 'M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z');
301            }
302            svg.appendChild(path);
303
304            const span = document.createElement('span');
305            span.className = 'menuicon';
306            span.appendChild(svg);
307            return span;
308        }
309
310        function syncProxyCheckboxIcon(proxy, checked) {
311            const next = checked ? '1' : '0';
312            if (proxy.dataset.extranetChecked === next && proxy.querySelector(':scope > .menuicon')) {
313                return;
314            }
315            const oldIcon = proxy.querySelector(':scope > .menuicon');
316            const newIcon = renderCheckboxIcon(checked);
317            if (oldIcon) {
318                oldIcon.replaceWith(newIcon);
319            } else {
320                proxy.insertBefore(newIcon, proxy.firstChild || null);
321            }
322            proxy.dataset.extranetChecked = next;
323        }
324
325        function refreshAllExtranetMenuItemsFromMacroState(macroState) {
326            const items = document.querySelectorAll('.prosemirror_wrapper .menuitem.extranet-page-setting-item');
327            const modifiedElements = [];
328            const itemStates = [];
329            items.forEach((item) => {
330                const isNoextranet = item.classList.contains('extranet-noextranet-item');
331                const key = isNoextranet ? 'noextranet' : 'extranet';
332                const active = !!macroState[key];
333                const wasActive = item.dataset.extranetChecked === '1';
334                item.classList.remove('is-active');
335                item.setAttribute('aria-checked', active ? 'true' : 'false');
336                const expectedDataset = active ? '1' : '0';
337                const hasIcon = !!item.querySelector(':scope > .menuicon');
338                if (wasActive !== active || !hasIcon || item.dataset.extranetChecked !== expectedDataset) {
339                    syncProxyCheckboxIcon(item, active);
340                }
341                itemStates.push({
342                    key,
343                    before: wasActive,
344                    after: active,
345                    changed: wasActive !== active,
346                    element: item
347                });
348                if (wasActive !== active) {
349                    modifiedElements.push(item);
350                }
351            });
352            // Debug logging disabled: this runs frequently during editor updates.
353        }
354
355        function refreshAllExtranetMenuItems(state) {
356            refreshAllExtranetMenuItemsFromMacroState(getMacroState(state));
357        }
358
359        function setDocMacroState(view, macroState) {
360            const desired = {
361                noextranet: !!macroState.noextranet,
362                extranet: !!macroState.extranet
363            };
364            localMacroState = desired;
365
366            const hasDesiredAttrs = () => {
367                const attrs = view.state && view.state.doc && view.state.doc.attrs ? view.state.doc.attrs : {};
368                return !!attrs.noextranet === desired.noextranet && !!attrs.extranet === desired.extranet;
369            };
370
371            const {state} = view;
372            let tr = state.tr;
373
374            if (typeof tr.setDocAttr === 'function') {
375                tr = tr.setDocAttr('noextranet', desired.noextranet);
376                tr = tr.setDocAttr('extranet', desired.extranet);
377                view.dispatch(tr);
378                if (hasDesiredAttrs()) {
379                    persistMacroStateInJsonField(view.state, desired);
380                    return true;
381                }
382            }
383
384            const classes = (window.Prosemirror && window.Prosemirror.classes) || {};
385            const stepCtor = Object.values(classes).find(
386                (Ctor) => typeof Ctor === 'function' && /DocAttr/i.test(Ctor.name || '')
387            );
388            if (stepCtor) {
389                try {
390                    tr = state.tr;
391                    tr = tr.step(new stepCtor('noextranet', desired.noextranet, 'setDocAttr'));
392                    tr = tr.step(new stepCtor('extranet', desired.extranet, 'setDocAttr'));
393                    view.dispatch(tr);
394                    if (hasDesiredAttrs()) {
395                        persistMacroStateInJsonField(view.state, desired);
396                        return true;
397                    }
398                } catch (e) {
399                    // fallback below
400                }
401            }
402
403            return persistMacroStateInJsonField(state, desired);
404        }
405
406        function getSourceMacroState() {
407            const fromJson = readMacroStateFromJsonField();
408            if (fromJson) return fromJson;
409            const fromTextarea = readMacroStateFromWikiTextarea();
410            if (fromTextarea) return fromTextarea;
411            return null;
412        }
413
414        function bootstrapStateFromSource() {
415            const view = getCurrentEditorView();
416            if (!view || !view.state || !view.state.doc) return;
417
418            const sourceState = getSourceMacroState();
419            const docState = readMacroStateFromDoc(view.state.doc);
420            if (!sourceState) return;
421
422            const current = docState || {noextranet: false, extranet: false};
423            if (!!current.noextranet === !!sourceState.noextranet && !!current.extranet === !!sourceState.extranet) return;
424
425            setDocMacroState(view, sourceState);
426        }
427
428        function getMacroLabel(key) {
429            return key === 'extranet' ? getExtranetLabel() : getNoExtranetLabel();
430        }
431
432        function isMacroActive(state, key) {
433            const macroState = getMacroState(state);
434            return !!macroState[key];
435        }
436
437        function buildStateForToggle(state, key) {
438            const active = isMacroActive(state, key);
439            if (key === 'noextranet') {
440                return active
441                    ? {noextranet: false, extranet: false}
442                    : {noextranet: true, extranet: false};
443            }
444
445            return active
446                ? {noextranet: false, extranet: false}
447                : {noextranet: false, extranet: true};
448        }
449
450        function createMacroDispatcher(key) {
451            return class extends AbstractMenuItemDispatcher {
452                static isAvailable(schema) {
453                    return !!(schema && schema.nodes && schema.nodes.doc);
454                }
455
456                static getMenuItem(schema) {
457                    if (!this.isAvailable(schema)) return hiddenMenuItem();
458
459                    return new MenuItem({
460                        label: getMacroLabel(key),
461                        render: (view) => {
462                            const label = getMacroLabel(key);
463                            const item = document.createElement('span');
464                            item.className = `menuitem extranet-page-setting-item extranet-${key}-item`;
465
466                            item.appendChild(renderCheckboxIcon(isMacroActive(view.state, key)));
467
468                            const title = document.createElement('span');
469                            title.className = 'menulabel';
470                            title.setAttribute('title', label);
471                            title.textContent = label;
472                            item.appendChild(title);
473
474                            return item;
475                        },
476                        update: (view, dom) => {
477                            const checked = isMacroActive(view.state, key);
478                            if (dom) {
479                                syncProxyCheckboxIcon(dom, checked);
480                                const labelEl = dom.querySelector('.menulabel');
481                                if (labelEl) {
482                                    const label = getMacroLabel(key);
483                                    labelEl.textContent = label;
484                                    labelEl.setAttribute('title', label);
485                                }
486                            }
487                        },
488                        command: (state, dispatch, view) => {
489                            if (!dispatch || !view) return true;
490                            const currentState = getMacroState(state);
491                            const nextState = buildStateForToggle(state, key);
492                            const applied = setDocMacroState(view, nextState);
493                            if (applied) {
494                                // Force immediate UI sync from the intended target state.
495                                refreshAllExtranetMenuItemsFromMacroState(nextState);
496                                scheduleMoveBurst();
497                            }
498                            return applied;
499                        },
500                        // Keep parent "Modules" menu from turning blue for extranet page settings.
501                        isActive: () => false
502                    });
503                }
504            };
505        }
506
507        const managedMacroKey = getManagedMacroKey();
508        if (managedMacroKey) {
509            window.Prosemirror.pluginMenuItemDispatchers.push(createMacroDispatcher(managedMacroKey));
510        }
511
512        function moveExtranetItemsToSettingsMenu() {
513            const menubars = document.querySelectorAll('.prosemirror_wrapper .menubar');
514
515            menubars.forEach((menubar) => {
516                const sourceItems = menubar.querySelectorAll('.menuitem.extranet-page-setting-item');
517                if (!sourceItems.length) return;
518
519                const settingsDropdown = findSettingsDropdown(menubar);
520
521                if (!settingsDropdown) return;
522
523                const target = settingsDropdown.querySelector('.dropdown_content');
524                if (!target) return;
525
526                sourceItems.forEach((sourceItem) => {
527                    if (sourceItem.parentElement !== target) {
528                        target.appendChild(sourceItem);
529                    }
530                });
531
532                const view = window.Prosemirror && window.Prosemirror.view;
533                const currentState = view && view.state ? view.state : null;
534
535                sourceItems.forEach((sourceItem) => {
536                    const isNoextranet = sourceItem.classList.contains('extranet-noextranet-item');
537                    const key = isNoextranet ? 'noextranet' : 'extranet';
538                    const active = isMacroActive(currentState, key);
539                    sourceItem.classList.remove('is-active');
540                    syncProxyCheckboxIcon(sourceItem, !!active);
541                });
542            });
543
544            const view = window.Prosemirror && window.Prosemirror.view;
545            if (view && view.state) {
546                refreshAllExtranetMenuItems(view.state);
547            }
548        }
549
550        const scheduleMoveBurst = polyfill && typeof polyfill.makeBurstScheduler === 'function'
551            ? polyfill.makeBurstScheduler(moveExtranetItemsToSettingsMenu)
552            : (function () {
553                let scheduled = false;
554                function scheduleMove() {
555                    if (scheduled) return;
556                    scheduled = true;
557                    window.requestAnimationFrame(() => {
558                        scheduled = false;
559                        moveExtranetItemsToSettingsMenu();
560                    });
561                }
562
563                return function () {
564                    scheduleMove();
565                    window.setTimeout(scheduleMove, 0);
566                    window.setTimeout(scheduleMove, 100);
567                    window.setTimeout(scheduleMove, 300);
568                };
569            })();
570
571        if (polyfill && typeof polyfill.bindCommonEditorLifecycle === 'function') {
572            polyfill.bindCommonEditorLifecycle({
573                onSwitchToText: function () {
574                    ensureShowDefaultEditorWrapped();
575                    syncCurrentMacroStateToJsonField();
576                },
577                onSubmit: syncCurrentMacroStateToJsonField,
578                onEditorToggle: scheduleMoveBurst,
579                onAfterSwitch: scheduleMoveBurst
580            });
581        } else {
582            document.addEventListener('mousedown', (event) => {
583                const target = event.target instanceof Element ? event.target : null;
584                if (!target) return;
585                if (!target.closest('.plugin_prosemirror_useWYSIWYG')) return;
586                ensureShowDefaultEditorWrapped();
587                syncCurrentMacroStateToJsonField();
588            }, true);
589
590            document.addEventListener('click', (event) => {
591                const target = event.target instanceof Element ? event.target : null;
592                if (!target) return;
593                if (target.closest('.plugin_prosemirror_useWYSIWYG')) {
594                    scheduleMoveBurst();
595                }
596
597                const submitControl = target.closest('#dw__editform button[type="submit"], #dw__editform input[type="submit"]');
598                if (!submitControl) return;
599                syncCurrentMacroStateToJsonField();
600            }, true);
601
602            document.addEventListener('submit', (event) => {
603                const form = event.target instanceof HTMLFormElement ? event.target : null;
604                if (!form || form.id !== 'dw__editform') return;
605                syncCurrentMacroStateToJsonField();
606            }, true);
607
608            jQuery(window).on('fastwiki:afterSwitch', function () {
609                scheduleMoveBurst();
610            });
611        }
612
613        ensureShowDefaultEditorWrapped();
614        window.setTimeout(ensureShowDefaultEditorWrapped, 0);
615        if (!isFixedMode()) {
616            bootstrapStateFromSource();
617            window.setTimeout(bootstrapStateFromSource, 0);
618            scheduleMoveBurst();
619        }
620    }
621
622    jQuery(document).on('PROSEMIRROR_API_INITIALIZED', initExtranetProsemirror);
623    initExtranetProsemirror();
624})();
625