(function () { function initExtranetProsemirror() { if (window.__extranetProsemirrorInitialized) return; if (!window.Prosemirror || !window.Prosemirror.classes) return; window.__extranetProsemirrorInitialized = true; const {classes: {MenuItem, AbstractMenuItemDispatcher}} = window.Prosemirror; const polyfill = window.DokuWikiProsemirrorFileStatePolyfill || null; let localMacroState = {noextranet: null, extranet: null}; const defaultMode = window.JSINFO && JSINFO.plugin_extranet_default_mode ? String(JSINFO.plugin_extranet_default_mode).toLowerCase() : 'allow'; function isFixedMode() { return defaultMode === 'force_allow' || defaultMode === 'force_block'; } function hiddenMenuItem() { return new MenuItem({ label: '', render: () => { const el = document.createElement('span'); el.style.display = 'none'; return el; }, command: () => false }); } function getNoExtranetLabel() { return ( (window.LANG && LANG.plugins && LANG.plugins.prosemirror && LANG.plugins.prosemirror['label:noextranet']) || (window.JSINFO && JSINFO.plugin_extranet_label_noextranet) || 'Blocked from Extranet' ); } function getExtranetLabel() { return ( (window.JSINFO && JSINFO.plugin_extranet_label_extranet) || 'Allowed from Extranet' ); } function getManagedMacroKey() { if (isFixedMode()) return null; return defaultMode === 'block' ? 'extranet' : 'noextranet'; } function getManagedMacroLabel() { return getManagedMacroKey() === 'extranet' ? getExtranetLabel() : getNoExtranetLabel(); } function getSettingsLabel() { return ( (window.LANG && LANG.plugins && LANG.plugins.prosemirror && LANG.plugins.prosemirror['label:settings']) || 'Page Settings' ); } function getSettingsChildLabels() { const labels = []; const fromLang = window.LANG && LANG.plugins && LANG.plugins.prosemirror ? LANG.plugins.prosemirror : {}; labels.push(fromLang['label:nocache'] || 'Deactivate Cache'); labels.push(fromLang['label:notoc'] || 'Hide Table of Contents'); labels.push('NOCACHE'); labels.push('NOTOC'); return labels .filter(Boolean) .map((s) => String(s).trim().toLowerCase()); } function findSettingsDropdown(menubar) { const settingsLabel = getSettingsLabel(); const knownChildLabels = getSettingsChildLabels(); const dropdowns = menubar.querySelectorAll('.menuitem.dropdown'); let found = null; dropdowns.forEach((dropdown) => { if (found) return; const labelEl = dropdown.querySelector(':scope > .menulabel'); const dropdownLabelText = String((labelEl && labelEl.textContent) || '').trim(); const dropdownLabelTitle = String((labelEl && labelEl.getAttribute('title')) || '').trim(); const looksLikeSettings = dropdownLabelText === settingsLabel || dropdownLabelTitle === settingsLabel; const dropdownContent = dropdown.querySelector('.dropdown_content'); const childTexts = dropdownContent ? Array.from(dropdownContent.querySelectorAll('.menuitem .menulabel')) .map((el) => ( String(el.textContent || el.getAttribute('title') || '') .trim() .toLowerCase() )) : []; const hasKnownSettingsChildren = childTexts.some((txt) => knownChildLabels.includes(txt)); if (looksLikeSettings || hasKnownSettingsChildren) { found = dropdown; } }); return found; } window.Prosemirror.pluginSchemas.push((nodes, marks) => { const doc = nodes.get('doc'); if (!doc) return {nodes, marks}; const attrs = {...(doc.attrs || {})}; let changed = false; if (typeof attrs.noextranet === 'undefined') { attrs.noextranet = {default: false}; changed = true; } if (typeof attrs.extranet === 'undefined') { attrs.extranet = {default: false}; changed = true; } if (!changed) return {nodes, marks}; const updatedDoc = { ...doc, attrs }; nodes = nodes.update('doc', updatedDoc); return {nodes, marks}; }); function readMacroStateFromDoc(doc) { if (!doc) return null; if (doc.attrs && (typeof doc.attrs.noextranet !== 'undefined' || typeof doc.attrs.extranet !== 'undefined')) { return { noextranet: !!doc.attrs.noextranet, extranet: !!doc.attrs.extranet }; } try { const raw = doc.textBetween(0, doc.content.size, '\n', '\n'); return { noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw), extranet: /~~\s*EXTRANET\s*~~/i.test(raw) }; } catch (e) { return null; } } function readMacroStateFromJsonField() { const input = document.querySelector('#dw__editform [name="prosemirror_json"]'); if (!input || !input.value) return null; try { const json = JSON.parse(input.value); if (json && json.attrs) { if (typeof json.attrs.noextranet !== 'undefined' || typeof json.attrs.extranet !== 'undefined') { return { noextranet: !!json.attrs.noextranet, extranet: !!json.attrs.extranet }; } } } catch (e) { // fallback below } const raw = String(input.value || ''); return { noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw), extranet: /~~\s*EXTRANET\s*~~/i.test(raw) }; } function readMacroStateFromWikiTextarea() { const textarea = document.querySelector('#dw__editform textarea[name="wikitext"], #dw__editform #wiki__text'); if (!textarea) return null; const raw = String(textarea.value || ''); return { noextranet: /~~\s*NOEXTRANET\s*~~/i.test(raw), extranet: /~~\s*EXTRANET\s*~~/i.test(raw) }; } function persistMacroStateInJsonField(state, macroState) { const input = document.querySelector('#dw__editform [name="prosemirror_json"]'); if (!input || !state || !state.doc) return false; try { const json = polyfill && typeof polyfill.mergeAttrsFromHiddenJson === 'function' ? polyfill.mergeAttrsFromHiddenJson(state, input, { noextranet: !!macroState.noextranet, extranet: !!macroState.extranet }) : state.doc.toJSON(); if (!json) return false; if (!(polyfill && typeof polyfill.mergeAttrsFromHiddenJson === 'function')) { json.attrs = { ...(json.attrs || {}), noextranet: !!macroState.noextranet, extranet: !!macroState.extranet }; input.value = JSON.stringify(json); } return true; } catch (e) { return false; } } function getCurrentEditorView() { return window.Prosemirror && window.Prosemirror.view ? window.Prosemirror.view : null; } function syncCurrentMacroStateToJsonField() { const view = getCurrentEditorView(); if (!view || !view.state) return false; const macroState = getMacroState(view.state); return persistMacroStateInJsonField(view.state, macroState); } function getMacroState(state) { // Prefer local state right after interactions to avoid one-tick lag from editor state. if (localMacroState.noextranet !== null || localMacroState.extranet !== null) { return { noextranet: !!localMacroState.noextranet, extranet: !!localMacroState.extranet }; } const fromDoc = state && state.doc ? readMacroStateFromDoc(state.doc) : null; if (fromDoc) { return fromDoc; } const fromJson = readMacroStateFromJsonField(); if (fromJson) return fromJson; return {noextranet: false, extranet: false}; } function applyMacroStateToText(text, macroState) { const cleaned = String(text || '').replace(/^[\t ]*~~[\t ]*(NOEXTRANET|EXTRANET)[\t ]*~~[\t ]*(\r?\n)?/gimu, '').replace(/\s+$/, ''); if (macroState && macroState.noextranet) { return cleaned ? `${cleaned}\n\n~~NOEXTRANET~~\n` : '~~NOEXTRANET~~\n'; } if (macroState && macroState.extranet) { return cleaned ? `${cleaned}\n\n~~EXTRANET~~\n` : '~~EXTRANET~~\n'; } return cleaned ? `${cleaned}\n` : ''; } function ensureShowDefaultEditorWrapped() { if (window.__extranetWrappedShowDefaultEditor) return true; if (typeof window.showDefaultEditor !== 'function') return false; const originalShowDefaultEditor = window.showDefaultEditor; window.showDefaultEditor = function extranetShowDefaultEditor(text) { const view = getCurrentEditorView(); const nextText = applyMacroStateToText(text, getMacroState(view && view.state)); return originalShowDefaultEditor.call(this, nextText); }; window.__extranetWrappedShowDefaultEditor = true; return true; } function renderCheckboxIcon(checked) { const ns = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(ns, 'svg'); svg.setAttribute('viewBox', '0 0 24 24'); const path = document.createElementNS(ns, 'path'); if (checked) { 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'); } else { 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'); } svg.appendChild(path); const span = document.createElement('span'); span.className = 'menuicon'; span.appendChild(svg); return span; } function syncProxyCheckboxIcon(proxy, checked) { const next = checked ? '1' : '0'; if (proxy.dataset.extranetChecked === next && proxy.querySelector(':scope > .menuicon')) { return; } const oldIcon = proxy.querySelector(':scope > .menuicon'); const newIcon = renderCheckboxIcon(checked); if (oldIcon) { oldIcon.replaceWith(newIcon); } else { proxy.insertBefore(newIcon, proxy.firstChild || null); } proxy.dataset.extranetChecked = next; } function refreshAllExtranetMenuItemsFromMacroState(macroState) { const items = document.querySelectorAll('.prosemirror_wrapper .menuitem.extranet-page-setting-item'); const modifiedElements = []; const itemStates = []; items.forEach((item) => { const isNoextranet = item.classList.contains('extranet-noextranet-item'); const key = isNoextranet ? 'noextranet' : 'extranet'; const active = !!macroState[key]; const wasActive = item.dataset.extranetChecked === '1'; item.classList.remove('is-active'); item.setAttribute('aria-checked', active ? 'true' : 'false'); const expectedDataset = active ? '1' : '0'; const hasIcon = !!item.querySelector(':scope > .menuicon'); if (wasActive !== active || !hasIcon || item.dataset.extranetChecked !== expectedDataset) { syncProxyCheckboxIcon(item, active); } itemStates.push({ key, before: wasActive, after: active, changed: wasActive !== active, element: item }); if (wasActive !== active) { modifiedElements.push(item); } }); // Debug logging disabled: this runs frequently during editor updates. } function refreshAllExtranetMenuItems(state) { refreshAllExtranetMenuItemsFromMacroState(getMacroState(state)); } function setDocMacroState(view, macroState) { const desired = { noextranet: !!macroState.noextranet, extranet: !!macroState.extranet }; localMacroState = desired; const hasDesiredAttrs = () => { const attrs = view.state && view.state.doc && view.state.doc.attrs ? view.state.doc.attrs : {}; return !!attrs.noextranet === desired.noextranet && !!attrs.extranet === desired.extranet; }; const {state} = view; let tr = state.tr; if (typeof tr.setDocAttr === 'function') { tr = tr.setDocAttr('noextranet', desired.noextranet); tr = tr.setDocAttr('extranet', desired.extranet); view.dispatch(tr); if (hasDesiredAttrs()) { persistMacroStateInJsonField(view.state, desired); return true; } } const classes = (window.Prosemirror && window.Prosemirror.classes) || {}; const stepCtor = Object.values(classes).find( (Ctor) => typeof Ctor === 'function' && /DocAttr/i.test(Ctor.name || '') ); if (stepCtor) { try { tr = state.tr; tr = tr.step(new stepCtor('noextranet', desired.noextranet, 'setDocAttr')); tr = tr.step(new stepCtor('extranet', desired.extranet, 'setDocAttr')); view.dispatch(tr); if (hasDesiredAttrs()) { persistMacroStateInJsonField(view.state, desired); return true; } } catch (e) { // fallback below } } return persistMacroStateInJsonField(state, desired); } function getSourceMacroState() { const fromJson = readMacroStateFromJsonField(); if (fromJson) return fromJson; const fromTextarea = readMacroStateFromWikiTextarea(); if (fromTextarea) return fromTextarea; return null; } function bootstrapStateFromSource() { const view = getCurrentEditorView(); if (!view || !view.state || !view.state.doc) return; const sourceState = getSourceMacroState(); const docState = readMacroStateFromDoc(view.state.doc); if (!sourceState) return; const current = docState || {noextranet: false, extranet: false}; if (!!current.noextranet === !!sourceState.noextranet && !!current.extranet === !!sourceState.extranet) return; setDocMacroState(view, sourceState); } function getMacroLabel(key) { return key === 'extranet' ? getExtranetLabel() : getNoExtranetLabel(); } function isMacroActive(state, key) { const macroState = getMacroState(state); return !!macroState[key]; } function buildStateForToggle(state, key) { const active = isMacroActive(state, key); if (key === 'noextranet') { return active ? {noextranet: false, extranet: false} : {noextranet: true, extranet: false}; } return active ? {noextranet: false, extranet: false} : {noextranet: false, extranet: true}; } function createMacroDispatcher(key) { return class extends AbstractMenuItemDispatcher { static isAvailable(schema) { return !!(schema && schema.nodes && schema.nodes.doc); } static getMenuItem(schema) { if (!this.isAvailable(schema)) return hiddenMenuItem(); return new MenuItem({ label: getMacroLabel(key), render: (view) => { const label = getMacroLabel(key); const item = document.createElement('span'); item.className = `menuitem extranet-page-setting-item extranet-${key}-item`; item.appendChild(renderCheckboxIcon(isMacroActive(view.state, key))); const title = document.createElement('span'); title.className = 'menulabel'; title.setAttribute('title', label); title.textContent = label; item.appendChild(title); return item; }, update: (view, dom) => { const checked = isMacroActive(view.state, key); if (dom) { syncProxyCheckboxIcon(dom, checked); const labelEl = dom.querySelector('.menulabel'); if (labelEl) { const label = getMacroLabel(key); labelEl.textContent = label; labelEl.setAttribute('title', label); } } }, command: (state, dispatch, view) => { if (!dispatch || !view) return true; const currentState = getMacroState(state); const nextState = buildStateForToggle(state, key); const applied = setDocMacroState(view, nextState); if (applied) { // Force immediate UI sync from the intended target state. refreshAllExtranetMenuItemsFromMacroState(nextState); scheduleMoveBurst(); } return applied; }, // Keep parent "Modules" menu from turning blue for extranet page settings. isActive: () => false }); } }; } const managedMacroKey = getManagedMacroKey(); if (managedMacroKey) { window.Prosemirror.pluginMenuItemDispatchers.push(createMacroDispatcher(managedMacroKey)); } function moveExtranetItemsToSettingsMenu() { const menubars = document.querySelectorAll('.prosemirror_wrapper .menubar'); menubars.forEach((menubar) => { const sourceItems = menubar.querySelectorAll('.menuitem.extranet-page-setting-item'); if (!sourceItems.length) return; const settingsDropdown = findSettingsDropdown(menubar); if (!settingsDropdown) return; const target = settingsDropdown.querySelector('.dropdown_content'); if (!target) return; sourceItems.forEach((sourceItem) => { if (sourceItem.parentElement !== target) { target.appendChild(sourceItem); } }); const view = window.Prosemirror && window.Prosemirror.view; const currentState = view && view.state ? view.state : null; sourceItems.forEach((sourceItem) => { const isNoextranet = sourceItem.classList.contains('extranet-noextranet-item'); const key = isNoextranet ? 'noextranet' : 'extranet'; const active = isMacroActive(currentState, key); sourceItem.classList.remove('is-active'); syncProxyCheckboxIcon(sourceItem, !!active); }); }); const view = window.Prosemirror && window.Prosemirror.view; if (view && view.state) { refreshAllExtranetMenuItems(view.state); } } const scheduleMoveBurst = polyfill && typeof polyfill.makeBurstScheduler === 'function' ? polyfill.makeBurstScheduler(moveExtranetItemsToSettingsMenu) : (function () { let scheduled = false; function scheduleMove() { if (scheduled) return; scheduled = true; window.requestAnimationFrame(() => { scheduled = false; moveExtranetItemsToSettingsMenu(); }); } return function () { scheduleMove(); window.setTimeout(scheduleMove, 0); window.setTimeout(scheduleMove, 100); window.setTimeout(scheduleMove, 300); }; })(); if (polyfill && typeof polyfill.bindCommonEditorLifecycle === 'function') { polyfill.bindCommonEditorLifecycle({ onSwitchToText: function () { ensureShowDefaultEditorWrapped(); syncCurrentMacroStateToJsonField(); }, onSubmit: syncCurrentMacroStateToJsonField, onEditorToggle: scheduleMoveBurst, onAfterSwitch: scheduleMoveBurst }); } else { document.addEventListener('mousedown', (event) => { const target = event.target instanceof Element ? event.target : null; if (!target) return; if (!target.closest('.plugin_prosemirror_useWYSIWYG')) return; ensureShowDefaultEditorWrapped(); syncCurrentMacroStateToJsonField(); }, true); document.addEventListener('click', (event) => { const target = event.target instanceof Element ? event.target : null; if (!target) return; if (target.closest('.plugin_prosemirror_useWYSIWYG')) { scheduleMoveBurst(); } const submitControl = target.closest('#dw__editform button[type="submit"], #dw__editform input[type="submit"]'); if (!submitControl) return; syncCurrentMacroStateToJsonField(); }, true); document.addEventListener('submit', (event) => { const form = event.target instanceof HTMLFormElement ? event.target : null; if (!form || form.id !== 'dw__editform') return; syncCurrentMacroStateToJsonField(); }, true); jQuery(window).on('fastwiki:afterSwitch', function () { scheduleMoveBurst(); }); } ensureShowDefaultEditorWrapped(); window.setTimeout(ensureShowDefaultEditorWrapped, 0); if (!isFixedMode()) { bootstrapStateFromSource(); window.setTimeout(bootstrapStateFromSource, 0); scheduleMoveBurst(); } } jQuery(document).on('PROSEMIRROR_API_INITIALIZED', initExtranetProsemirror); initExtranetProsemirror(); })();