(function () { function initializeVisualIndexProsemirror() { if (window.__visualindexProsemirrorInitialized) return; if (!window.Prosemirror || !window.Prosemirror.classes) return; window.__visualindexProsemirrorInitialized = true; const {classes: {MenuItem, AbstractMenuItemDispatcher}} = window.Prosemirror; function hiddenMenuItem() { return new MenuItem({ label: '', render: () => { const el = document.createElement('span'); el.style.display = 'none'; return el; }, command: () => false }); } function shouldShowInEditorMenu() { const raw = window.JSINFO && JSINFO.plugins && JSINFO.plugins.visualindex ? JSINFO.plugins.visualindex.show_in_editor_menu : true; if (typeof raw === 'boolean') return raw; const normalized = String(raw).trim().toLowerCase(); return !(normalized === '0' || normalized === 'false' || normalized === 'off' || normalized === 'no'); } window.Prosemirror.pluginSchemas.push((nodes, marks) => { nodes = nodes.addToEnd('visualindex', { group: 'protected_block', inline: false, selectable: true, draggable: true, defining: true, isolating: true, code: true, attrs: { syntax: {default: '{{visualindex>.}}'} }, toDOM: (node) => ['pre', {class: 'dwplugin', 'data-pluginname': 'visualindex'}, node.attrs.syntax], parseDOM: [{ tag: 'pre.dwplugin[data-pluginname="visualindex"]', getAttrs: (dom) => ({syntax: (dom.textContent || '{{visualindex>.}}').trim()}) }] }); return {nodes, marks}; }); function parseVisualIndexSyntax(syntax) { const m = (syntax || '').match(/^\{\{visualindex>(.*?)\}\}$/i); if (!m) return null; const parts = m[1].split(';').map((p) => p.trim()).filter(Boolean); const namespace = parts.shift() || '.'; const options = {namespace, filter: '', desc: false, medias: false}; parts.forEach((part) => { const [keyRaw, valRaw] = part.split('=', 2); const key = (keyRaw || '').trim().toLowerCase(); const val = valRaw === undefined ? '1' : String(valRaw).trim(); if (key === 'filter') options.filter = val; if (key === 'desc') options.desc = (val !== '0' && val !== 'false' && val !== ''); if (key === 'medias') options.medias = (val !== '0' && val !== 'false' && val !== ''); }); return options; } function buildVisualIndexSyntax(values) { let syntax = `{{visualindex>${values.namespace || '.'}`; if (values.filter) syntax += `;filter=${values.filter}`; if (values.desc) syntax += ';desc=1'; if (values.medias) syntax += ';medias=1'; syntax += '}}'; return syntax; } function formatVisualIndexLabel(values) { const parts = [`VisualIndex: ${values.namespace || '.'}`]; if (values.filter) parts.push(`filter=${values.filter}`); if (values.desc) parts.push('desc'); if (values.medias) parts.push('medias'); return parts.join(' | '); } function getFolderIconUrl() { const svg = ""; return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`; } function getFolderMenuIcon() { const ns = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(ns, 'svg'); svg.setAttribute('viewBox', '0 0 24 24'); const path1 = document.createElementNS(ns, 'path'); path1.setAttribute('d', 'M10 4l2 2h8a2 2 0 0 1 2 2v2H2V6a2 2 0 0 1 2-2h6z'); path1.setAttribute('fill', 'currentColor'); svg.appendChild(path1); const path2 = document.createElementNS(ns, 'path'); path2.setAttribute('d', 'M2 10h20v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-8z'); path2.setAttribute('fill', 'currentColor'); svg.appendChild(path2); return svg; } function isLegacyVisualIndexPluginNode(node) { return !!( node && node.type && (node.type.name === 'dwplugin_inline' || node.type.name === 'dwplugin_block') && node.attrs && node.attrs['data-pluginname'] === 'visualindex' ); } function isVisualIndexNode(node) { return !!(node && node.type && node.type.name === 'visualindex') || isLegacyVisualIndexPluginNode(node); } function syntaxFromNode(node) { if (!node) return '{{visualindex>.}}'; if (node.type && node.type.name === 'visualindex') { return String((node.attrs && node.attrs.syntax) || '{{visualindex>.}}'); } return String(node.textContent || '{{visualindex>.}}'); } function createVisualIndexNode(schema, syntax) { const normalized = String(syntax || '{{visualindex>.}}').trim() || '{{visualindex>.}}'; if (schema.nodes.visualindex) { return schema.nodes.visualindex.createChecked({syntax: normalized}); } const fallback = schema.nodes.dwplugin_block; if (!fallback) return null; return fallback.createChecked( {class: 'dwplugin', 'data-pluginname': 'visualindex'}, schema.text(normalized) ); } function selectionIsVisualIndex(state) { const selected = findVisualIndexAtSelection(state); return !!selected; } function insertParagraphAfterSelectedVisualIndex(view) { if (!view || !view.state) return false; const selected = findVisualIndexAtSelection(view.state); if (!selected) return false; const {schema} = view.state; const paragraph = schema.nodes.paragraph && schema.nodes.paragraph.createAndFill(); if (!paragraph) return false; const insertPos = selected.pos + selected.node.nodeSize; let tr = view.state.tr.insert(insertPos, paragraph).scrollIntoView(); view.dispatch(tr); // Move cursor into the newly inserted paragraph. try { const SelectionClass = view.state.selection.constructor; const $target = view.state.doc.resolve(insertPos + 1); const selection = SelectionClass.near($target, 1); view.dispatch(view.state.tr.setSelection(selection).scrollIntoView()); } catch (e) { // Keep default selection if we can't safely resolve a text position. } view.focus(); return true; } function findVisualIndexAtSelection(state) { const {selection} = state; if (isVisualIndexNode(selection.node)) { return {node: selection.node, pos: selection.from}; } const $from = selection.$from; if ($from.depth > 0 && isVisualIndexNode($from.parent)) { return {node: $from.parent, pos: $from.before($from.depth)}; } if (isVisualIndexNode($from.nodeBefore)) { return {node: $from.nodeBefore, pos: $from.pos - $from.nodeBefore.nodeSize}; } if (isVisualIndexNode($from.nodeAfter)) { return {node: $from.nodeAfter, pos: $from.pos}; } for (let depth = $from.depth; depth > 0; depth -= 1) { const ancestor = $from.node(depth); if (isVisualIndexNode(ancestor)) { return {node: ancestor, pos: $from.before(depth)}; } } return null; } function insertVisualIndexBlock(view, pluginNode) { const state = view.state; const {$from} = state.selection; const index = $from.index(); if ($from.parent.canReplaceWith(index, index, pluginNode.type)) { view.dispatch(state.tr.replaceSelectionWith(pluginNode)); return true; } for (let depth = $from.depth; depth > 0; depth -= 1) { const insertPos = $from.after(depth); try { view.dispatch(state.tr.insert(insertPos, pluginNode)); return true; } catch (e) { // try a higher ancestor } } return false; } function showVisualIndexDialog(initialValues, onSubmit) { const values = { namespace: '.', filter: '', desc: false, medias: false, ...initialValues }; const $dialog = jQuery('
'); $dialog.append(''); const $namespace = jQuery('').val(values.namespace); $dialog.append($namespace); $dialog.append('