xref: /plugin/extranet/script/prosemirror.js (revision ef5bb1f3884025c3556b3c46e33332ca386a73a6)
12670d39fSLORTET(function () {
22670d39fSLORTET    function initExtranetProsemirror() {
32670d39fSLORTET        if (window.__extranetProsemirrorInitialized) return;
42670d39fSLORTET        if (!window.Prosemirror || !window.Prosemirror.classes) return;
52670d39fSLORTET        window.__extranetProsemirrorInitialized = true;
62670d39fSLORTET
72670d39fSLORTET        const {classes: {MenuItem, AbstractMenuItemDispatcher}} = window.Prosemirror;
82670d39fSLORTET        const polyfill = window.DokuWikiProsemirrorFileStatePolyfill || null;
92670d39fSLORTET        let localMacroState = {noextranet: null, extranet: null};
102670d39fSLORTET
112670d39fSLORTET        const defaultMode = window.JSINFO && JSINFO.plugin_extranet_default_mode
122670d39fSLORTET            ? String(JSINFO.plugin_extranet_default_mode).toLowerCase()
132670d39fSLORTET            : 'allow';
142670d39fSLORTET
15*ef5bb1f3SLORTET        function isFixedMode() {
16*ef5bb1f3SLORTET            return defaultMode === 'force_allow' || defaultMode === 'force_block';
17*ef5bb1f3SLORTET        }
18*ef5bb1f3SLORTET
192670d39fSLORTET        function hiddenMenuItem() {
202670d39fSLORTET            return new MenuItem({
212670d39fSLORTET                label: '',
222670d39fSLORTET                render: () => {
232670d39fSLORTET                    const el = document.createElement('span');
242670d39fSLORTET                    el.style.display = 'none';
252670d39fSLORTET                    return el;
262670d39fSLORTET                },
272670d39fSLORTET                command: () => false
282670d39fSLORTET            });
292670d39fSLORTET        }
302670d39fSLORTET
312670d39fSLORTET        function getNoExtranetLabel() {
322670d39fSLORTET            return (
332670d39fSLORTET                (window.LANG &&
342670d39fSLORTET                    LANG.plugins &&
352670d39fSLORTET                    LANG.plugins.prosemirror &&
362670d39fSLORTET                    LANG.plugins.prosemirror['label:noextranet']) ||
372670d39fSLORTET                (window.JSINFO && JSINFO.plugin_extranet_label_noextranet) ||
382670d39fSLORTET                'Blocked from Extranet'
392670d39fSLORTET            );
402670d39fSLORTET        }
412670d39fSLORTET
422670d39fSLORTET        function getExtranetLabel() {
432670d39fSLORTET            return (
442670d39fSLORTET                (window.JSINFO && JSINFO.plugin_extranet_label_extranet) ||
452670d39fSLORTET                'Allowed from Extranet'
462670d39fSLORTET            );
472670d39fSLORTET        }
482670d39fSLORTET
492670d39fSLORTET        function getManagedMacroKey() {
50*ef5bb1f3SLORTET            if (isFixedMode()) return null;
512670d39fSLORTET            return defaultMode === 'block' ? 'extranet' : 'noextranet';
522670d39fSLORTET        }
532670d39fSLORTET
542670d39fSLORTET        function getManagedMacroLabel() {
552670d39fSLORTET            return getManagedMacroKey() === 'extranet' ? getExtranetLabel() : getNoExtranetLabel();
562670d39fSLORTET        }
572670d39fSLORTET
582670d39fSLORTET        function getSettingsLabel() {
592670d39fSLORTET            return (
602670d39fSLORTET                (window.LANG &&
612670d39fSLORTET                    LANG.plugins &&
622670d39fSLORTET                    LANG.plugins.prosemirror &&
632670d39fSLORTET                    LANG.plugins.prosemirror['label:settings']) ||
642670d39fSLORTET                'Page Settings'
652670d39fSLORTET            );
662670d39fSLORTET        }
672670d39fSLORTET
682670d39fSLORTET        function getSettingsChildLabels() {
692670d39fSLORTET            const labels = [];
702670d39fSLORTET            const fromLang = window.LANG &&
712670d39fSLORTET                LANG.plugins &&
722670d39fSLORTET                LANG.plugins.prosemirror
732670d39fSLORTET                ? LANG.plugins.prosemirror
742670d39fSLORTET                : {};
752670d39fSLORTET
762670d39fSLORTET            labels.push(fromLang['label:nocache'] || 'Deactivate Cache');
772670d39fSLORTET            labels.push(fromLang['label:notoc'] || 'Hide Table of Contents');
782670d39fSLORTET            labels.push('NOCACHE');
792670d39fSLORTET            labels.push('NOTOC');
802670d39fSLORTET
812670d39fSLORTET            return labels
822670d39fSLORTET                .filter(Boolean)
832670d39fSLORTET                .map((s) => String(s).trim().toLowerCase());
842670d39fSLORTET        }
852670d39fSLORTET
862670d39fSLORTET        function findSettingsDropdown(menubar) {
872670d39fSLORTET            const settingsLabel = getSettingsLabel();
882670d39fSLORTET            const knownChildLabels = getSettingsChildLabels();
892670d39fSLORTET            const dropdowns = menubar.querySelectorAll('.menuitem.dropdown');
902670d39fSLORTET            let found = null;
912670d39fSLORTET
922670d39fSLORTET            dropdowns.forEach((dropdown) => {
932670d39fSLORTET                if (found) return;
942670d39fSLORTET                const labelEl = dropdown.querySelector(':scope > .menulabel');
952670d39fSLORTET                const dropdownLabelText = String((labelEl && labelEl.textContent) || '').trim();
962670d39fSLORTET                const dropdownLabelTitle = String((labelEl && labelEl.getAttribute('title')) || '').trim();
972670d39fSLORTET
982670d39fSLORTET                const looksLikeSettings =
992670d39fSLORTET                    dropdownLabelText === settingsLabel ||
1002670d39fSLORTET                    dropdownLabelTitle === settingsLabel;
1012670d39fSLORTET
1022670d39fSLORTET                const dropdownContent = dropdown.querySelector('.dropdown_content');
1032670d39fSLORTET                const childTexts = dropdownContent
1042670d39fSLORTET                    ? Array.from(dropdownContent.querySelectorAll('.menuitem .menulabel'))
1052670d39fSLORTET                        .map((el) => (
1062670d39fSLORTET                            String(el.textContent || el.getAttribute('title') || '')
1072670d39fSLORTET                                .trim()
1082670d39fSLORTET                                .toLowerCase()
1092670d39fSLORTET                        ))
1102670d39fSLORTET                    : [];
1112670d39fSLORTET
1122670d39fSLORTET                const hasKnownSettingsChildren = childTexts.some((txt) => knownChildLabels.includes(txt));
1132670d39fSLORTET
1142670d39fSLORTET                if (looksLikeSettings || hasKnownSettingsChildren) {
1152670d39fSLORTET                    found = dropdown;
1162670d39fSLORTET                }
1172670d39fSLORTET            });
1182670d39fSLORTET
1192670d39fSLORTET            return found;
1202670d39fSLORTET        }
1212670d39fSLORTET
1222670d39fSLORTET
1232670d39fSLORTET        window.Prosemirror.pluginSchemas.push((nodes, marks) => {
1242670d39fSLORTET            const doc = nodes.get('doc');
1252670d39fSLORTET            if (!doc) return {nodes, marks};
1262670d39fSLORTET
1272670d39fSLORTET            const attrs = {...(doc.attrs || {})};
1282670d39fSLORTET            let changed = false;
1292670d39fSLORTET
1302670d39fSLORTET            if (typeof attrs.noextranet === 'undefined') {
1312670d39fSLORTET                attrs.noextranet = {default: false};
1322670d39fSLORTET                changed = true;
1332670d39fSLORTET            }
1342670d39fSLORTET            if (typeof attrs.extranet === 'undefined') {
1352670d39fSLORTET                attrs.extranet = {default: false};
1362670d39fSLORTET                changed = true;
1372670d39fSLORTET            }
1382670d39fSLORTET
1392670d39fSLORTET            if (!changed) return {nodes, marks};
1402670d39fSLORTET
1412670d39fSLORTET            const updatedDoc = {
1422670d39fSLORTET                ...doc,
1432670d39fSLORTET                attrs
1442670d39fSLORTET            };
1452670d39fSLORTET            nodes = nodes.update('doc', updatedDoc);
1462670d39fSLORTET            return {nodes, marks};
1472670d39fSLORTET        });
1482670d39fSLORTET
1492670d39fSLORTET        function readMacroStateFromDoc(doc) {
1502670d39fSLORTET            if (!doc) return null;
1512670d39fSLORTET
1522670d39fSLORTET            if (doc.attrs && (typeof doc.attrs.noextranet !== 'undefined' || typeof doc.attrs.extranet !== 'undefined')) {
1532670d39fSLORTET                return {
1542670d39fSLORTET                    noextranet: !!doc.attrs.noextranet,
1552670d39fSLORTET                    extranet: !!doc.attrs.extranet
1562670d39fSLORTET                };
1572670d39fSLORTET            }
1582670d39fSLORTET
1592670d39fSLORTET            try {
1602670d39fSLORTET                const raw = doc.textBetween(0, doc.content.size, '\n', '\n');
1612670d39fSLORTET                return {
1622670d39fSLORTET                    noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw),
1632670d39fSLORTET                    extranet: /~~\s*EXTRANET\s*~~/i.test(raw)
1642670d39fSLORTET                };
1652670d39fSLORTET            } catch (e) {
1662670d39fSLORTET                return null;
1672670d39fSLORTET            }
1682670d39fSLORTET        }
1692670d39fSLORTET
1702670d39fSLORTET        function readMacroStateFromJsonField() {
1712670d39fSLORTET            const input = document.querySelector('#dw__editform [name="prosemirror_json"]');
1722670d39fSLORTET            if (!input || !input.value) return null;
1732670d39fSLORTET
1742670d39fSLORTET            try {
1752670d39fSLORTET                const json = JSON.parse(input.value);
1762670d39fSLORTET                if (json && json.attrs) {
1772670d39fSLORTET                    if (typeof json.attrs.noextranet !== 'undefined' || typeof json.attrs.extranet !== 'undefined') {
1782670d39fSLORTET                        return {
1792670d39fSLORTET                            noextranet: !!json.attrs.noextranet,
1802670d39fSLORTET                            extranet: !!json.attrs.extranet
1812670d39fSLORTET                        };
1822670d39fSLORTET                    }
1832670d39fSLORTET                }
1842670d39fSLORTET            } catch (e) {
1852670d39fSLORTET                // fallback below
1862670d39fSLORTET            }
1872670d39fSLORTET
1882670d39fSLORTET            const raw = String(input.value || '');
1892670d39fSLORTET            return {
1902670d39fSLORTET                noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw),
1912670d39fSLORTET                extranet: /~~\s*EXTRANET\s*~~/i.test(raw)
1922670d39fSLORTET            };
1932670d39fSLORTET        }
1942670d39fSLORTET
1952670d39fSLORTET        function readMacroStateFromWikiTextarea() {
1962670d39fSLORTET            const textarea = document.querySelector('#dw__editform textarea[name="wikitext"], #dw__editform #wiki__text');
1972670d39fSLORTET            if (!textarea) return null;
1982670d39fSLORTET            const raw = String(textarea.value || '');
1992670d39fSLORTET            return {
2002670d39fSLORTET                noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw),
2012670d39fSLORTET                extranet: /~~\s*EXTRANET\s*~~/i.test(raw)
2022670d39fSLORTET            };
2032670d39fSLORTET        }
2042670d39fSLORTET
2052670d39fSLORTET        function persistMacroStateInJsonField(state, macroState) {
2062670d39fSLORTET            const input = document.querySelector('#dw__editform [name="prosemirror_json"]');
2072670d39fSLORTET            if (!input || !state || !state.doc) return false;
2082670d39fSLORTET
2092670d39fSLORTET            try {
2102670d39fSLORTET                const json = polyfill && typeof polyfill.mergeAttrsFromHiddenJson === 'function'
2112670d39fSLORTET                    ? polyfill.mergeAttrsFromHiddenJson(state, input, {
2122670d39fSLORTET                        noextranet: !!macroState.noextranet,
2132670d39fSLORTET                        extranet: !!macroState.extranet
2142670d39fSLORTET                    })
2152670d39fSLORTET                    : state.doc.toJSON();
2162670d39fSLORTET
2172670d39fSLORTET                if (!json) return false;
2182670d39fSLORTET                if (!(polyfill && typeof polyfill.mergeAttrsFromHiddenJson === 'function')) {
2192670d39fSLORTET                    json.attrs = {
2202670d39fSLORTET                        ...(json.attrs || {}),
2212670d39fSLORTET                        noextranet: !!macroState.noextranet,
2222670d39fSLORTET                        extranet: !!macroState.extranet
2232670d39fSLORTET                    };
2242670d39fSLORTET                    input.value = JSON.stringify(json);
2252670d39fSLORTET                }
2262670d39fSLORTET                return true;
2272670d39fSLORTET            } catch (e) {
2282670d39fSLORTET                return false;
2292670d39fSLORTET            }
2302670d39fSLORTET        }
2312670d39fSLORTET
2322670d39fSLORTET        function getCurrentEditorView() {
2332670d39fSLORTET            return window.Prosemirror && window.Prosemirror.view ? window.Prosemirror.view : null;
2342670d39fSLORTET        }
2352670d39fSLORTET
2362670d39fSLORTET        function syncCurrentMacroStateToJsonField() {
2372670d39fSLORTET            const view = getCurrentEditorView();
2382670d39fSLORTET            if (!view || !view.state) return false;
2392670d39fSLORTET            const macroState = getMacroState(view.state);
2402670d39fSLORTET            return persistMacroStateInJsonField(view.state, macroState);
2412670d39fSLORTET        }
2422670d39fSLORTET
2432670d39fSLORTET        function getMacroState(state) {
2442670d39fSLORTET            // Prefer local state right after interactions to avoid one-tick lag from editor state.
2452670d39fSLORTET            if (localMacroState.noextranet !== null || localMacroState.extranet !== null) {
2462670d39fSLORTET                return {
2472670d39fSLORTET                    noextranet: !!localMacroState.noextranet,
2482670d39fSLORTET                    extranet: !!localMacroState.extranet
2492670d39fSLORTET                };
2502670d39fSLORTET            }
2512670d39fSLORTET
2522670d39fSLORTET            const fromDoc = state && state.doc ? readMacroStateFromDoc(state.doc) : null;
2532670d39fSLORTET            if (fromDoc) {
2542670d39fSLORTET                return fromDoc;
2552670d39fSLORTET            }
2562670d39fSLORTET
2572670d39fSLORTET            const fromJson = readMacroStateFromJsonField();
2582670d39fSLORTET            if (fromJson) return fromJson;
2592670d39fSLORTET
2602670d39fSLORTET            return {noextranet: false, extranet: false};
2612670d39fSLORTET        }
2622670d39fSLORTET
2632670d39fSLORTET        function applyMacroStateToText(text, macroState) {
2642670d39fSLORTET            const cleaned = String(text || '').replace(/^[\t ]*~~[\t ]*(NOEXTRANET|EXTRANET)[\t ]*~~[\t ]*(\r?\n)?/gimu, '').replace(/\s+$/, '');
2652670d39fSLORTET
2662670d39fSLORTET            if (macroState && macroState.noextranet) {
2672670d39fSLORTET                return cleaned ? `${cleaned}\n\n~~NOEXTRANET~~\n` : '~~NOEXTRANET~~\n';
2682670d39fSLORTET            }
2692670d39fSLORTET
2702670d39fSLORTET            if (macroState && macroState.extranet) {
2712670d39fSLORTET                return cleaned ? `${cleaned}\n\n~~EXTRANET~~\n` : '~~EXTRANET~~\n';
2722670d39fSLORTET            }
2732670d39fSLORTET
2742670d39fSLORTET            return cleaned ? `${cleaned}\n` : '';
2752670d39fSLORTET        }
2762670d39fSLORTET
2772670d39fSLORTET        function ensureShowDefaultEditorWrapped() {
2782670d39fSLORTET            if (window.__extranetWrappedShowDefaultEditor) return true;
2792670d39fSLORTET            if (typeof window.showDefaultEditor !== 'function') return false;
2802670d39fSLORTET
2812670d39fSLORTET            const originalShowDefaultEditor = window.showDefaultEditor;
2822670d39fSLORTET            window.showDefaultEditor = function extranetShowDefaultEditor(text) {
2832670d39fSLORTET                const view = getCurrentEditorView();
2842670d39fSLORTET                const nextText = applyMacroStateToText(text, getMacroState(view && view.state));
2852670d39fSLORTET                return originalShowDefaultEditor.call(this, nextText);
2862670d39fSLORTET            };
2872670d39fSLORTET            window.__extranetWrappedShowDefaultEditor = true;
2882670d39fSLORTET            return true;
2892670d39fSLORTET        }
2902670d39fSLORTET
2912670d39fSLORTET        function renderCheckboxIcon(checked) {
2922670d39fSLORTET            const ns = 'http://www.w3.org/2000/svg';
2932670d39fSLORTET            const svg = document.createElementNS(ns, 'svg');
2942670d39fSLORTET            svg.setAttribute('viewBox', '0 0 24 24');
2952670d39fSLORTET
2962670d39fSLORTET            const path = document.createElementNS(ns, 'path');
2972670d39fSLORTET            if (checked) {
2982670d39fSLORTET                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');
2992670d39fSLORTET            } else {
3002670d39fSLORTET                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');
3012670d39fSLORTET            }
3022670d39fSLORTET            svg.appendChild(path);
3032670d39fSLORTET
3042670d39fSLORTET            const span = document.createElement('span');
3052670d39fSLORTET            span.className = 'menuicon';
3062670d39fSLORTET            span.appendChild(svg);
3072670d39fSLORTET            return span;
3082670d39fSLORTET        }
3092670d39fSLORTET
3102670d39fSLORTET        function syncProxyCheckboxIcon(proxy, checked) {
3112670d39fSLORTET            const next = checked ? '1' : '0';
3122670d39fSLORTET            if (proxy.dataset.extranetChecked === next && proxy.querySelector(':scope > .menuicon')) {
3132670d39fSLORTET                return;
3142670d39fSLORTET            }
3152670d39fSLORTET            const oldIcon = proxy.querySelector(':scope > .menuicon');
3162670d39fSLORTET            const newIcon = renderCheckboxIcon(checked);
3172670d39fSLORTET            if (oldIcon) {
3182670d39fSLORTET                oldIcon.replaceWith(newIcon);
3192670d39fSLORTET            } else {
3202670d39fSLORTET                proxy.insertBefore(newIcon, proxy.firstChild || null);
3212670d39fSLORTET            }
3222670d39fSLORTET            proxy.dataset.extranetChecked = next;
3232670d39fSLORTET        }
3242670d39fSLORTET
3252670d39fSLORTET        function refreshAllExtranetMenuItemsFromMacroState(macroState) {
3262670d39fSLORTET            const items = document.querySelectorAll('.prosemirror_wrapper .menuitem.extranet-page-setting-item');
3272670d39fSLORTET            const modifiedElements = [];
3282670d39fSLORTET            const itemStates = [];
3292670d39fSLORTET            items.forEach((item) => {
3302670d39fSLORTET                const isNoextranet = item.classList.contains('extranet-noextranet-item');
3312670d39fSLORTET                const key = isNoextranet ? 'noextranet' : 'extranet';
3322670d39fSLORTET                const active = !!macroState[key];
3332670d39fSLORTET                const wasActive = item.dataset.extranetChecked === '1';
3342670d39fSLORTET                item.classList.remove('is-active');
3352670d39fSLORTET                item.setAttribute('aria-checked', active ? 'true' : 'false');
3362670d39fSLORTET                const expectedDataset = active ? '1' : '0';
3372670d39fSLORTET                const hasIcon = !!item.querySelector(':scope > .menuicon');
3382670d39fSLORTET                if (wasActive !== active || !hasIcon || item.dataset.extranetChecked !== expectedDataset) {
3392670d39fSLORTET                    syncProxyCheckboxIcon(item, active);
3402670d39fSLORTET                }
3412670d39fSLORTET                itemStates.push({
3422670d39fSLORTET                    key,
3432670d39fSLORTET                    before: wasActive,
3442670d39fSLORTET                    after: active,
3452670d39fSLORTET                    changed: wasActive !== active,
3462670d39fSLORTET                    element: item
3472670d39fSLORTET                });
3482670d39fSLORTET                if (wasActive !== active) {
3492670d39fSLORTET                    modifiedElements.push(item);
3502670d39fSLORTET                }
3512670d39fSLORTET            });
3522670d39fSLORTET            // Debug logging disabled: this runs frequently during editor updates.
3532670d39fSLORTET        }
3542670d39fSLORTET
3552670d39fSLORTET        function refreshAllExtranetMenuItems(state) {
3562670d39fSLORTET            refreshAllExtranetMenuItemsFromMacroState(getMacroState(state));
3572670d39fSLORTET        }
3582670d39fSLORTET
3592670d39fSLORTET        function setDocMacroState(view, macroState) {
3602670d39fSLORTET            const desired = {
3612670d39fSLORTET                noextranet: !!macroState.noextranet,
3622670d39fSLORTET                extranet: !!macroState.extranet
3632670d39fSLORTET            };
3642670d39fSLORTET            localMacroState = desired;
3652670d39fSLORTET
3662670d39fSLORTET            const hasDesiredAttrs = () => {
3672670d39fSLORTET                const attrs = view.state && view.state.doc && view.state.doc.attrs ? view.state.doc.attrs : {};
3682670d39fSLORTET                return !!attrs.noextranet === desired.noextranet && !!attrs.extranet === desired.extranet;
3692670d39fSLORTET            };
3702670d39fSLORTET
3712670d39fSLORTET            const {state} = view;
3722670d39fSLORTET            let tr = state.tr;
3732670d39fSLORTET
3742670d39fSLORTET            if (typeof tr.setDocAttr === 'function') {
3752670d39fSLORTET                tr = tr.setDocAttr('noextranet', desired.noextranet);
3762670d39fSLORTET                tr = tr.setDocAttr('extranet', desired.extranet);
3772670d39fSLORTET                view.dispatch(tr);
3782670d39fSLORTET                if (hasDesiredAttrs()) {
3792670d39fSLORTET                    persistMacroStateInJsonField(view.state, desired);
3802670d39fSLORTET                    return true;
3812670d39fSLORTET                }
3822670d39fSLORTET            }
3832670d39fSLORTET
3842670d39fSLORTET            const classes = (window.Prosemirror && window.Prosemirror.classes) || {};
3852670d39fSLORTET            const stepCtor = Object.values(classes).find(
3862670d39fSLORTET                (Ctor) => typeof Ctor === 'function' && /DocAttr/i.test(Ctor.name || '')
3872670d39fSLORTET            );
3882670d39fSLORTET            if (stepCtor) {
3892670d39fSLORTET                try {
3902670d39fSLORTET                    tr = state.tr;
3912670d39fSLORTET                    tr = tr.step(new stepCtor('noextranet', desired.noextranet, 'setDocAttr'));
3922670d39fSLORTET                    tr = tr.step(new stepCtor('extranet', desired.extranet, 'setDocAttr'));
3932670d39fSLORTET                    view.dispatch(tr);
3942670d39fSLORTET                    if (hasDesiredAttrs()) {
3952670d39fSLORTET                        persistMacroStateInJsonField(view.state, desired);
3962670d39fSLORTET                        return true;
3972670d39fSLORTET                    }
3982670d39fSLORTET                } catch (e) {
3992670d39fSLORTET                    // fallback below
4002670d39fSLORTET                }
4012670d39fSLORTET            }
4022670d39fSLORTET
4032670d39fSLORTET            return persistMacroStateInJsonField(state, desired);
4042670d39fSLORTET        }
4052670d39fSLORTET
4062670d39fSLORTET        function getSourceMacroState() {
4072670d39fSLORTET            const fromJson = readMacroStateFromJsonField();
4082670d39fSLORTET            if (fromJson) return fromJson;
4092670d39fSLORTET            const fromTextarea = readMacroStateFromWikiTextarea();
4102670d39fSLORTET            if (fromTextarea) return fromTextarea;
4112670d39fSLORTET            return null;
4122670d39fSLORTET        }
4132670d39fSLORTET
4142670d39fSLORTET        function bootstrapStateFromSource() {
4152670d39fSLORTET            const view = getCurrentEditorView();
4162670d39fSLORTET            if (!view || !view.state || !view.state.doc) return;
4172670d39fSLORTET
4182670d39fSLORTET            const sourceState = getSourceMacroState();
4192670d39fSLORTET            const docState = readMacroStateFromDoc(view.state.doc);
4202670d39fSLORTET            if (!sourceState) return;
4212670d39fSLORTET
4222670d39fSLORTET            const current = docState || {noextranet: false, extranet: false};
4232670d39fSLORTET            if (!!current.noextranet === !!sourceState.noextranet && !!current.extranet === !!sourceState.extranet) return;
4242670d39fSLORTET
4252670d39fSLORTET            setDocMacroState(view, sourceState);
4262670d39fSLORTET        }
4272670d39fSLORTET
4282670d39fSLORTET        function getMacroLabel(key) {
4292670d39fSLORTET            return key === 'extranet' ? getExtranetLabel() : getNoExtranetLabel();
4302670d39fSLORTET        }
4312670d39fSLORTET
4322670d39fSLORTET        function isMacroActive(state, key) {
4332670d39fSLORTET            const macroState = getMacroState(state);
4342670d39fSLORTET            return !!macroState[key];
4352670d39fSLORTET        }
4362670d39fSLORTET
4372670d39fSLORTET        function buildStateForToggle(state, key) {
4382670d39fSLORTET            const active = isMacroActive(state, key);
4392670d39fSLORTET            if (key === 'noextranet') {
4402670d39fSLORTET                return active
4412670d39fSLORTET                    ? {noextranet: false, extranet: false}
4422670d39fSLORTET                    : {noextranet: true, extranet: false};
4432670d39fSLORTET            }
4442670d39fSLORTET
4452670d39fSLORTET            return active
4462670d39fSLORTET                ? {noextranet: false, extranet: false}
4472670d39fSLORTET                : {noextranet: false, extranet: true};
4482670d39fSLORTET        }
4492670d39fSLORTET
4502670d39fSLORTET        function createMacroDispatcher(key) {
4512670d39fSLORTET            return class extends AbstractMenuItemDispatcher {
4522670d39fSLORTET                static isAvailable(schema) {
4532670d39fSLORTET                    return !!(schema && schema.nodes && schema.nodes.doc);
4542670d39fSLORTET                }
4552670d39fSLORTET
4562670d39fSLORTET                static getMenuItem(schema) {
4572670d39fSLORTET                    if (!this.isAvailable(schema)) return hiddenMenuItem();
4582670d39fSLORTET
4592670d39fSLORTET                    return new MenuItem({
4602670d39fSLORTET                        label: getMacroLabel(key),
4612670d39fSLORTET                        render: (view) => {
4622670d39fSLORTET                            const label = getMacroLabel(key);
4632670d39fSLORTET                            const item = document.createElement('span');
4642670d39fSLORTET                            item.className = `menuitem extranet-page-setting-item extranet-${key}-item`;
4652670d39fSLORTET
4662670d39fSLORTET                            item.appendChild(renderCheckboxIcon(isMacroActive(view.state, key)));
4672670d39fSLORTET
4682670d39fSLORTET                            const title = document.createElement('span');
4692670d39fSLORTET                            title.className = 'menulabel';
4702670d39fSLORTET                            title.setAttribute('title', label);
4712670d39fSLORTET                            title.textContent = label;
4722670d39fSLORTET                            item.appendChild(title);
4732670d39fSLORTET
4742670d39fSLORTET                            return item;
4752670d39fSLORTET                        },
4762670d39fSLORTET                        update: (view, dom) => {
4772670d39fSLORTET                            const checked = isMacroActive(view.state, key);
4782670d39fSLORTET                            if (dom) {
4792670d39fSLORTET                                syncProxyCheckboxIcon(dom, checked);
4802670d39fSLORTET                                const labelEl = dom.querySelector('.menulabel');
4812670d39fSLORTET                                if (labelEl) {
4822670d39fSLORTET                                    const label = getMacroLabel(key);
4832670d39fSLORTET                                    labelEl.textContent = label;
4842670d39fSLORTET                                    labelEl.setAttribute('title', label);
4852670d39fSLORTET                                }
4862670d39fSLORTET                            }
4872670d39fSLORTET                        },
4882670d39fSLORTET                        command: (state, dispatch, view) => {
4892670d39fSLORTET                            if (!dispatch || !view) return true;
4902670d39fSLORTET                            const currentState = getMacroState(state);
4912670d39fSLORTET                            const nextState = buildStateForToggle(state, key);
4922670d39fSLORTET                            const applied = setDocMacroState(view, nextState);
4932670d39fSLORTET                            if (applied) {
4942670d39fSLORTET                                // Force immediate UI sync from the intended target state.
4952670d39fSLORTET                                refreshAllExtranetMenuItemsFromMacroState(nextState);
4962670d39fSLORTET                                scheduleMoveBurst();
4972670d39fSLORTET                            }
4982670d39fSLORTET                            return applied;
4992670d39fSLORTET                        },
5002670d39fSLORTET                        // Keep parent "Modules" menu from turning blue for extranet page settings.
5012670d39fSLORTET                        isActive: () => false
5022670d39fSLORTET                    });
5032670d39fSLORTET                }
5042670d39fSLORTET            };
5052670d39fSLORTET        }
5062670d39fSLORTET
507*ef5bb1f3SLORTET        const managedMacroKey = getManagedMacroKey();
508*ef5bb1f3SLORTET        if (managedMacroKey) {
509*ef5bb1f3SLORTET            window.Prosemirror.pluginMenuItemDispatchers.push(createMacroDispatcher(managedMacroKey));
510*ef5bb1f3SLORTET        }
5112670d39fSLORTET
5122670d39fSLORTET        function moveExtranetItemsToSettingsMenu() {
5132670d39fSLORTET            const menubars = document.querySelectorAll('.prosemirror_wrapper .menubar');
5142670d39fSLORTET
5152670d39fSLORTET            menubars.forEach((menubar) => {
5162670d39fSLORTET                const sourceItems = menubar.querySelectorAll('.menuitem.extranet-page-setting-item');
5172670d39fSLORTET                if (!sourceItems.length) return;
5182670d39fSLORTET
5192670d39fSLORTET                const settingsDropdown = findSettingsDropdown(menubar);
5202670d39fSLORTET
5212670d39fSLORTET                if (!settingsDropdown) return;
5222670d39fSLORTET
5232670d39fSLORTET                const target = settingsDropdown.querySelector('.dropdown_content');
5242670d39fSLORTET                if (!target) return;
5252670d39fSLORTET
5262670d39fSLORTET                sourceItems.forEach((sourceItem) => {
5272670d39fSLORTET                    if (sourceItem.parentElement !== target) {
5282670d39fSLORTET                        target.appendChild(sourceItem);
5292670d39fSLORTET                    }
5302670d39fSLORTET                });
5312670d39fSLORTET
5322670d39fSLORTET                const view = window.Prosemirror && window.Prosemirror.view;
5332670d39fSLORTET                const currentState = view && view.state ? view.state : null;
5342670d39fSLORTET
5352670d39fSLORTET                sourceItems.forEach((sourceItem) => {
5362670d39fSLORTET                    const isNoextranet = sourceItem.classList.contains('extranet-noextranet-item');
5372670d39fSLORTET                    const key = isNoextranet ? 'noextranet' : 'extranet';
5382670d39fSLORTET                    const active = isMacroActive(currentState, key);
5392670d39fSLORTET                    sourceItem.classList.remove('is-active');
5402670d39fSLORTET                    syncProxyCheckboxIcon(sourceItem, !!active);
5412670d39fSLORTET                });
5422670d39fSLORTET            });
5432670d39fSLORTET
5442670d39fSLORTET            const view = window.Prosemirror && window.Prosemirror.view;
5452670d39fSLORTET            if (view && view.state) {
5462670d39fSLORTET                refreshAllExtranetMenuItems(view.state);
5472670d39fSLORTET            }
5482670d39fSLORTET        }
5492670d39fSLORTET
5502670d39fSLORTET        const scheduleMoveBurst = polyfill && typeof polyfill.makeBurstScheduler === 'function'
5512670d39fSLORTET            ? polyfill.makeBurstScheduler(moveExtranetItemsToSettingsMenu)
5522670d39fSLORTET            : (function () {
5532670d39fSLORTET                let scheduled = false;
5542670d39fSLORTET                function scheduleMove() {
5552670d39fSLORTET                    if (scheduled) return;
5562670d39fSLORTET                    scheduled = true;
5572670d39fSLORTET                    window.requestAnimationFrame(() => {
5582670d39fSLORTET                        scheduled = false;
5592670d39fSLORTET                        moveExtranetItemsToSettingsMenu();
5602670d39fSLORTET                    });
5612670d39fSLORTET                }
5622670d39fSLORTET
5632670d39fSLORTET                return function () {
5642670d39fSLORTET                    scheduleMove();
5652670d39fSLORTET                    window.setTimeout(scheduleMove, 0);
5662670d39fSLORTET                    window.setTimeout(scheduleMove, 100);
5672670d39fSLORTET                    window.setTimeout(scheduleMove, 300);
5682670d39fSLORTET                };
5692670d39fSLORTET            })();
5702670d39fSLORTET
5712670d39fSLORTET        if (polyfill && typeof polyfill.bindCommonEditorLifecycle === 'function') {
5722670d39fSLORTET            polyfill.bindCommonEditorLifecycle({
5732670d39fSLORTET                onSwitchToText: function () {
5742670d39fSLORTET                    ensureShowDefaultEditorWrapped();
5752670d39fSLORTET                    syncCurrentMacroStateToJsonField();
5762670d39fSLORTET                },
5772670d39fSLORTET                onSubmit: syncCurrentMacroStateToJsonField,
5782670d39fSLORTET                onEditorToggle: scheduleMoveBurst,
5792670d39fSLORTET                onAfterSwitch: scheduleMoveBurst
5802670d39fSLORTET            });
5812670d39fSLORTET        } else {
5822670d39fSLORTET            document.addEventListener('mousedown', (event) => {
5832670d39fSLORTET                const target = event.target instanceof Element ? event.target : null;
5842670d39fSLORTET                if (!target) return;
5852670d39fSLORTET                if (!target.closest('.plugin_prosemirror_useWYSIWYG')) return;
5862670d39fSLORTET                ensureShowDefaultEditorWrapped();
5872670d39fSLORTET                syncCurrentMacroStateToJsonField();
5882670d39fSLORTET            }, true);
5892670d39fSLORTET
5902670d39fSLORTET            document.addEventListener('click', (event) => {
5912670d39fSLORTET                const target = event.target instanceof Element ? event.target : null;
5922670d39fSLORTET                if (!target) return;
5932670d39fSLORTET                if (target.closest('.plugin_prosemirror_useWYSIWYG')) {
5942670d39fSLORTET                    scheduleMoveBurst();
5952670d39fSLORTET                }
5962670d39fSLORTET
5972670d39fSLORTET                const submitControl = target.closest('#dw__editform button[type="submit"], #dw__editform input[type="submit"]');
5982670d39fSLORTET                if (!submitControl) return;
5992670d39fSLORTET                syncCurrentMacroStateToJsonField();
6002670d39fSLORTET            }, true);
6012670d39fSLORTET
6022670d39fSLORTET            document.addEventListener('submit', (event) => {
6032670d39fSLORTET                const form = event.target instanceof HTMLFormElement ? event.target : null;
6042670d39fSLORTET                if (!form || form.id !== 'dw__editform') return;
6052670d39fSLORTET                syncCurrentMacroStateToJsonField();
6062670d39fSLORTET            }, true);
6072670d39fSLORTET
6082670d39fSLORTET            jQuery(window).on('fastwiki:afterSwitch', function () {
6092670d39fSLORTET                scheduleMoveBurst();
6102670d39fSLORTET            });
6112670d39fSLORTET        }
6122670d39fSLORTET
6132670d39fSLORTET        ensureShowDefaultEditorWrapped();
6142670d39fSLORTET        window.setTimeout(ensureShowDefaultEditorWrapped, 0);
615*ef5bb1f3SLORTET        if (!isFixedMode()) {
6162670d39fSLORTET            bootstrapStateFromSource();
6172670d39fSLORTET            window.setTimeout(bootstrapStateFromSource, 0);
6182670d39fSLORTET            scheduleMoveBurst();
6192670d39fSLORTET        }
620*ef5bb1f3SLORTET    }
6212670d39fSLORTET
6222670d39fSLORTET    jQuery(document).on('PROSEMIRROR_API_INITIALIZED', initExtranetProsemirror);
6232670d39fSLORTET    initExtranetProsemirror();
6242670d39fSLORTET})();
625