1*3c9c7f3bSLORTET(function () { 2*3c9c7f3bSLORTET function initializeVisualIndexProsemirror() { 3*3c9c7f3bSLORTET if (window.__visualindexProsemirrorInitialized) return; 4*3c9c7f3bSLORTET if (!window.Prosemirror || !window.Prosemirror.classes) return; 5*3c9c7f3bSLORTET window.__visualindexProsemirrorInitialized = true; 6*3c9c7f3bSLORTET 7*3c9c7f3bSLORTET const {classes: {MenuItem, AbstractMenuItemDispatcher}} = window.Prosemirror; 8*3c9c7f3bSLORTET function hiddenMenuItem() { 9*3c9c7f3bSLORTET return new MenuItem({ 10*3c9c7f3bSLORTET label: '', 11*3c9c7f3bSLORTET render: () => { 12*3c9c7f3bSLORTET const el = document.createElement('span'); 13*3c9c7f3bSLORTET el.style.display = 'none'; 14*3c9c7f3bSLORTET return el; 15*3c9c7f3bSLORTET }, 16*3c9c7f3bSLORTET command: () => false 17*3c9c7f3bSLORTET }); 18*3c9c7f3bSLORTET } 19*3c9c7f3bSLORTET 20*3c9c7f3bSLORTET function shouldShowInEditorMenu() { 21*3c9c7f3bSLORTET const raw = window.JSINFO && 22*3c9c7f3bSLORTET JSINFO.plugins && 23*3c9c7f3bSLORTET JSINFO.plugins.visualindex 24*3c9c7f3bSLORTET ? JSINFO.plugins.visualindex.show_in_editor_menu 25*3c9c7f3bSLORTET : true; 26*3c9c7f3bSLORTET 27*3c9c7f3bSLORTET if (typeof raw === 'boolean') return raw; 28*3c9c7f3bSLORTET const normalized = String(raw).trim().toLowerCase(); 29*3c9c7f3bSLORTET return !(normalized === '0' || normalized === 'false' || normalized === 'off' || normalized === 'no'); 30*3c9c7f3bSLORTET } 31*3c9c7f3bSLORTET 32*3c9c7f3bSLORTET window.Prosemirror.pluginSchemas.push((nodes, marks) => { 33*3c9c7f3bSLORTET nodes = nodes.addToEnd('visualindex', { 34*3c9c7f3bSLORTET group: 'protected_block', 35*3c9c7f3bSLORTET inline: false, 36*3c9c7f3bSLORTET selectable: true, 37*3c9c7f3bSLORTET draggable: true, 38*3c9c7f3bSLORTET defining: true, 39*3c9c7f3bSLORTET isolating: true, 40*3c9c7f3bSLORTET code: true, 41*3c9c7f3bSLORTET attrs: { 42*3c9c7f3bSLORTET syntax: {default: '{{visualindex>.}}'} 43*3c9c7f3bSLORTET }, 44*3c9c7f3bSLORTET toDOM: (node) => ['pre', {class: 'dwplugin', 'data-pluginname': 'visualindex'}, node.attrs.syntax], 45*3c9c7f3bSLORTET parseDOM: [{ 46*3c9c7f3bSLORTET tag: 'pre.dwplugin[data-pluginname="visualindex"]', 47*3c9c7f3bSLORTET getAttrs: (dom) => ({syntax: (dom.textContent || '{{visualindex>.}}').trim()}) 48*3c9c7f3bSLORTET }] 49*3c9c7f3bSLORTET }); 50*3c9c7f3bSLORTET return {nodes, marks}; 51*3c9c7f3bSLORTET }); 52*3c9c7f3bSLORTET 53*3c9c7f3bSLORTET function parseVisualIndexSyntax(syntax) { 54*3c9c7f3bSLORTET const m = (syntax || '').match(/^\{\{visualindex>(.*?)\}\}$/i); 55*3c9c7f3bSLORTET if (!m) return null; 56*3c9c7f3bSLORTET 57*3c9c7f3bSLORTET const parts = m[1].split(';').map((p) => p.trim()).filter(Boolean); 58*3c9c7f3bSLORTET const namespace = parts.shift() || '.'; 59*3c9c7f3bSLORTET const options = {namespace, filter: '', desc: false, medias: false}; 60*3c9c7f3bSLORTET 61*3c9c7f3bSLORTET parts.forEach((part) => { 62*3c9c7f3bSLORTET const [keyRaw, valRaw] = part.split('=', 2); 63*3c9c7f3bSLORTET const key = (keyRaw || '').trim().toLowerCase(); 64*3c9c7f3bSLORTET const val = valRaw === undefined ? '1' : String(valRaw).trim(); 65*3c9c7f3bSLORTET if (key === 'filter') options.filter = val; 66*3c9c7f3bSLORTET if (key === 'desc') options.desc = (val !== '0' && val !== 'false' && val !== ''); 67*3c9c7f3bSLORTET if (key === 'medias') options.medias = (val !== '0' && val !== 'false' && val !== ''); 68*3c9c7f3bSLORTET }); 69*3c9c7f3bSLORTET 70*3c9c7f3bSLORTET return options; 71*3c9c7f3bSLORTET } 72*3c9c7f3bSLORTET 73*3c9c7f3bSLORTET function buildVisualIndexSyntax(values) { 74*3c9c7f3bSLORTET let syntax = `{{visualindex>${values.namespace || '.'}`; 75*3c9c7f3bSLORTET if (values.filter) syntax += `;filter=${values.filter}`; 76*3c9c7f3bSLORTET if (values.desc) syntax += ';desc=1'; 77*3c9c7f3bSLORTET if (values.medias) syntax += ';medias=1'; 78*3c9c7f3bSLORTET syntax += '}}'; 79*3c9c7f3bSLORTET return syntax; 80*3c9c7f3bSLORTET } 81*3c9c7f3bSLORTET 82*3c9c7f3bSLORTET function formatVisualIndexLabel(values) { 83*3c9c7f3bSLORTET const parts = [`VisualIndex: ${values.namespace || '.'}`]; 84*3c9c7f3bSLORTET if (values.filter) parts.push(`filter=${values.filter}`); 85*3c9c7f3bSLORTET if (values.desc) parts.push('desc'); 86*3c9c7f3bSLORTET if (values.medias) parts.push('medias'); 87*3c9c7f3bSLORTET return parts.join(' | '); 88*3c9c7f3bSLORTET } 89*3c9c7f3bSLORTET 90*3c9c7f3bSLORTET function getFolderIconUrl() { 91*3c9c7f3bSLORTET const svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%232f6fae' d='M10 4l2 2h8a2 2 0 0 1 2 2v2H2V6a2 2 0 0 1 2-2h6z'/><path fill='%233f88c8' d='M2 10h20v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-8z'/></svg>"; 92*3c9c7f3bSLORTET return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`; 93*3c9c7f3bSLORTET } 94*3c9c7f3bSLORTET 95*3c9c7f3bSLORTET function getFolderMenuIcon() { 96*3c9c7f3bSLORTET const ns = 'http://www.w3.org/2000/svg'; 97*3c9c7f3bSLORTET const svg = document.createElementNS(ns, 'svg'); 98*3c9c7f3bSLORTET svg.setAttribute('viewBox', '0 0 24 24'); 99*3c9c7f3bSLORTET 100*3c9c7f3bSLORTET const path1 = document.createElementNS(ns, 'path'); 101*3c9c7f3bSLORTET path1.setAttribute('d', 'M10 4l2 2h8a2 2 0 0 1 2 2v2H2V6a2 2 0 0 1 2-2h6z'); 102*3c9c7f3bSLORTET path1.setAttribute('fill', 'currentColor'); 103*3c9c7f3bSLORTET svg.appendChild(path1); 104*3c9c7f3bSLORTET 105*3c9c7f3bSLORTET const path2 = document.createElementNS(ns, 'path'); 106*3c9c7f3bSLORTET path2.setAttribute('d', 'M2 10h20v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-8z'); 107*3c9c7f3bSLORTET path2.setAttribute('fill', 'currentColor'); 108*3c9c7f3bSLORTET svg.appendChild(path2); 109*3c9c7f3bSLORTET 110*3c9c7f3bSLORTET return svg; 111*3c9c7f3bSLORTET } 112*3c9c7f3bSLORTET 113*3c9c7f3bSLORTET function isLegacyVisualIndexPluginNode(node) { 114*3c9c7f3bSLORTET return !!( 115*3c9c7f3bSLORTET node && 116*3c9c7f3bSLORTET node.type && 117*3c9c7f3bSLORTET (node.type.name === 'dwplugin_inline' || node.type.name === 'dwplugin_block') && 118*3c9c7f3bSLORTET node.attrs && 119*3c9c7f3bSLORTET node.attrs['data-pluginname'] === 'visualindex' 120*3c9c7f3bSLORTET ); 121*3c9c7f3bSLORTET } 122*3c9c7f3bSLORTET 123*3c9c7f3bSLORTET function isVisualIndexNode(node) { 124*3c9c7f3bSLORTET return !!(node && node.type && node.type.name === 'visualindex') || isLegacyVisualIndexPluginNode(node); 125*3c9c7f3bSLORTET } 126*3c9c7f3bSLORTET 127*3c9c7f3bSLORTET function syntaxFromNode(node) { 128*3c9c7f3bSLORTET if (!node) return '{{visualindex>.}}'; 129*3c9c7f3bSLORTET if (node.type && node.type.name === 'visualindex') { 130*3c9c7f3bSLORTET return String((node.attrs && node.attrs.syntax) || '{{visualindex>.}}'); 131*3c9c7f3bSLORTET } 132*3c9c7f3bSLORTET return String(node.textContent || '{{visualindex>.}}'); 133*3c9c7f3bSLORTET } 134*3c9c7f3bSLORTET 135*3c9c7f3bSLORTET function createVisualIndexNode(schema, syntax) { 136*3c9c7f3bSLORTET const normalized = String(syntax || '{{visualindex>.}}').trim() || '{{visualindex>.}}'; 137*3c9c7f3bSLORTET if (schema.nodes.visualindex) { 138*3c9c7f3bSLORTET return schema.nodes.visualindex.createChecked({syntax: normalized}); 139*3c9c7f3bSLORTET } 140*3c9c7f3bSLORTET 141*3c9c7f3bSLORTET const fallback = schema.nodes.dwplugin_block; 142*3c9c7f3bSLORTET if (!fallback) return null; 143*3c9c7f3bSLORTET 144*3c9c7f3bSLORTET return fallback.createChecked( 145*3c9c7f3bSLORTET {class: 'dwplugin', 'data-pluginname': 'visualindex'}, 146*3c9c7f3bSLORTET schema.text(normalized) 147*3c9c7f3bSLORTET ); 148*3c9c7f3bSLORTET } 149*3c9c7f3bSLORTET 150*3c9c7f3bSLORTET function selectionIsVisualIndex(state) { 151*3c9c7f3bSLORTET const selected = findVisualIndexAtSelection(state); 152*3c9c7f3bSLORTET return !!selected; 153*3c9c7f3bSLORTET } 154*3c9c7f3bSLORTET 155*3c9c7f3bSLORTET function insertParagraphAfterSelectedVisualIndex(view) { 156*3c9c7f3bSLORTET if (!view || !view.state) return false; 157*3c9c7f3bSLORTET const selected = findVisualIndexAtSelection(view.state); 158*3c9c7f3bSLORTET if (!selected) return false; 159*3c9c7f3bSLORTET 160*3c9c7f3bSLORTET const {schema} = view.state; 161*3c9c7f3bSLORTET const paragraph = schema.nodes.paragraph && schema.nodes.paragraph.createAndFill(); 162*3c9c7f3bSLORTET if (!paragraph) return false; 163*3c9c7f3bSLORTET 164*3c9c7f3bSLORTET const insertPos = selected.pos + selected.node.nodeSize; 165*3c9c7f3bSLORTET let tr = view.state.tr.insert(insertPos, paragraph).scrollIntoView(); 166*3c9c7f3bSLORTET view.dispatch(tr); 167*3c9c7f3bSLORTET 168*3c9c7f3bSLORTET // Move cursor into the newly inserted paragraph. 169*3c9c7f3bSLORTET try { 170*3c9c7f3bSLORTET const SelectionClass = view.state.selection.constructor; 171*3c9c7f3bSLORTET const $target = view.state.doc.resolve(insertPos + 1); 172*3c9c7f3bSLORTET const selection = SelectionClass.near($target, 1); 173*3c9c7f3bSLORTET view.dispatch(view.state.tr.setSelection(selection).scrollIntoView()); 174*3c9c7f3bSLORTET } catch (e) { 175*3c9c7f3bSLORTET // Keep default selection if we can't safely resolve a text position. 176*3c9c7f3bSLORTET } 177*3c9c7f3bSLORTET 178*3c9c7f3bSLORTET view.focus(); 179*3c9c7f3bSLORTET return true; 180*3c9c7f3bSLORTET } 181*3c9c7f3bSLORTET 182*3c9c7f3bSLORTET function findVisualIndexAtSelection(state) { 183*3c9c7f3bSLORTET const {selection} = state; 184*3c9c7f3bSLORTET 185*3c9c7f3bSLORTET if (isVisualIndexNode(selection.node)) { 186*3c9c7f3bSLORTET return {node: selection.node, pos: selection.from}; 187*3c9c7f3bSLORTET } 188*3c9c7f3bSLORTET 189*3c9c7f3bSLORTET const $from = selection.$from; 190*3c9c7f3bSLORTET 191*3c9c7f3bSLORTET if ($from.depth > 0 && isVisualIndexNode($from.parent)) { 192*3c9c7f3bSLORTET return {node: $from.parent, pos: $from.before($from.depth)}; 193*3c9c7f3bSLORTET } 194*3c9c7f3bSLORTET 195*3c9c7f3bSLORTET if (isVisualIndexNode($from.nodeBefore)) { 196*3c9c7f3bSLORTET return {node: $from.nodeBefore, pos: $from.pos - $from.nodeBefore.nodeSize}; 197*3c9c7f3bSLORTET } 198*3c9c7f3bSLORTET 199*3c9c7f3bSLORTET if (isVisualIndexNode($from.nodeAfter)) { 200*3c9c7f3bSLORTET return {node: $from.nodeAfter, pos: $from.pos}; 201*3c9c7f3bSLORTET } 202*3c9c7f3bSLORTET 203*3c9c7f3bSLORTET for (let depth = $from.depth; depth > 0; depth -= 1) { 204*3c9c7f3bSLORTET const ancestor = $from.node(depth); 205*3c9c7f3bSLORTET if (isVisualIndexNode(ancestor)) { 206*3c9c7f3bSLORTET return {node: ancestor, pos: $from.before(depth)}; 207*3c9c7f3bSLORTET } 208*3c9c7f3bSLORTET } 209*3c9c7f3bSLORTET 210*3c9c7f3bSLORTET return null; 211*3c9c7f3bSLORTET } 212*3c9c7f3bSLORTET 213*3c9c7f3bSLORTET function insertVisualIndexBlock(view, pluginNode) { 214*3c9c7f3bSLORTET const state = view.state; 215*3c9c7f3bSLORTET const {$from} = state.selection; 216*3c9c7f3bSLORTET const index = $from.index(); 217*3c9c7f3bSLORTET 218*3c9c7f3bSLORTET if ($from.parent.canReplaceWith(index, index, pluginNode.type)) { 219*3c9c7f3bSLORTET view.dispatch(state.tr.replaceSelectionWith(pluginNode)); 220*3c9c7f3bSLORTET return true; 221*3c9c7f3bSLORTET } 222*3c9c7f3bSLORTET 223*3c9c7f3bSLORTET for (let depth = $from.depth; depth > 0; depth -= 1) { 224*3c9c7f3bSLORTET const insertPos = $from.after(depth); 225*3c9c7f3bSLORTET try { 226*3c9c7f3bSLORTET view.dispatch(state.tr.insert(insertPos, pluginNode)); 227*3c9c7f3bSLORTET return true; 228*3c9c7f3bSLORTET } catch (e) { 229*3c9c7f3bSLORTET // try a higher ancestor 230*3c9c7f3bSLORTET } 231*3c9c7f3bSLORTET } 232*3c9c7f3bSLORTET 233*3c9c7f3bSLORTET return false; 234*3c9c7f3bSLORTET } 235*3c9c7f3bSLORTET 236*3c9c7f3bSLORTET function showVisualIndexDialog(initialValues, onSubmit) { 237*3c9c7f3bSLORTET const values = { 238*3c9c7f3bSLORTET namespace: '.', 239*3c9c7f3bSLORTET filter: '', 240*3c9c7f3bSLORTET desc: false, 241*3c9c7f3bSLORTET medias: false, 242*3c9c7f3bSLORTET ...initialValues 243*3c9c7f3bSLORTET }; 244*3c9c7f3bSLORTET 245*3c9c7f3bSLORTET const $dialog = jQuery('<div class="plugin_visualindex_form" title="Visualindex"></div>'); 246*3c9c7f3bSLORTET $dialog.append('<label>Namespace</label>'); 247*3c9c7f3bSLORTET const $namespace = jQuery('<input type="text" class="edit" style="width:100%;" />').val(values.namespace); 248*3c9c7f3bSLORTET $dialog.append($namespace); 249*3c9c7f3bSLORTET $dialog.append('<div style="font-size:.9em;color:#555;margin-top:4px;">Dossier. "." = dossier courant.</div>'); 250*3c9c7f3bSLORTET 251*3c9c7f3bSLORTET $dialog.append('<label style="display:block;margin-top:8px;">Filtre</label>'); 252*3c9c7f3bSLORTET const $filter = jQuery('<input type="text" class="edit" style="width:100%;" />').val(values.filter); 253*3c9c7f3bSLORTET $dialog.append($filter); 254*3c9c7f3bSLORTET 255*3c9c7f3bSLORTET const $descWrap = jQuery('<label style="display:block;margin-top:10px;"></label>'); 256*3c9c7f3bSLORTET const $desc = jQuery('<input type="checkbox" />').prop('checked', !!values.desc); 257*3c9c7f3bSLORTET $descWrap.append($desc).append(' Ordre descendant'); 258*3c9c7f3bSLORTET $dialog.append($descWrap); 259*3c9c7f3bSLORTET 260*3c9c7f3bSLORTET const $mediasWrap = jQuery('<label style="display:block;margin-top:6px;"></label>'); 261*3c9c7f3bSLORTET const $medias = jQuery('<input type="checkbox" />').prop('checked', !!values.medias); 262*3c9c7f3bSLORTET $mediasWrap.append($medias).append(' Afficher les medias'); 263*3c9c7f3bSLORTET $dialog.append($mediasWrap); 264*3c9c7f3bSLORTET 265*3c9c7f3bSLORTET $dialog.dialog({ 266*3c9c7f3bSLORTET modal: true, 267*3c9c7f3bSLORTET width: 460, 268*3c9c7f3bSLORTET close: function () { 269*3c9c7f3bSLORTET jQuery(this).dialog('destroy').remove(); 270*3c9c7f3bSLORTET }, 271*3c9c7f3bSLORTET buttons: [ 272*3c9c7f3bSLORTET { 273*3c9c7f3bSLORTET text: 'Insérer', 274*3c9c7f3bSLORTET click: function () { 275*3c9c7f3bSLORTET onSubmit({ 276*3c9c7f3bSLORTET namespace: String($namespace.val() || '.').trim() || '.', 277*3c9c7f3bSLORTET filter: String($filter.val() || '').trim(), 278*3c9c7f3bSLORTET desc: $desc.is(':checked'), 279*3c9c7f3bSLORTET medias: $medias.is(':checked') 280*3c9c7f3bSLORTET }); 281*3c9c7f3bSLORTET jQuery(this).dialog('close'); 282*3c9c7f3bSLORTET } 283*3c9c7f3bSLORTET }, 284*3c9c7f3bSLORTET { 285*3c9c7f3bSLORTET text: 'Annuler', 286*3c9c7f3bSLORTET click: function () { 287*3c9c7f3bSLORTET jQuery(this).dialog('close'); 288*3c9c7f3bSLORTET } 289*3c9c7f3bSLORTET } 290*3c9c7f3bSLORTET ] 291*3c9c7f3bSLORTET }); 292*3c9c7f3bSLORTET } 293*3c9c7f3bSLORTET 294*3c9c7f3bSLORTET class VisualIndexNodeView { 295*3c9c7f3bSLORTET constructor(node, view, getPos) { 296*3c9c7f3bSLORTET this.node = node; 297*3c9c7f3bSLORTET this.view = view; 298*3c9c7f3bSLORTET this.getPos = getPos; 299*3c9c7f3bSLORTET this.dom = document.createElement('div'); 300*3c9c7f3bSLORTET const typeClass = (node.type && node.type.name === 'dwplugin_inline') ? 'pm_visualindex_inline' : 'pm_visualindex_block'; 301*3c9c7f3bSLORTET this.dom.className = 'plugin_visualindex pm_visualindex_node nodeHasForm ' + typeClass; 302*3c9c7f3bSLORTET this.dom.setAttribute('contenteditable', 'false'); 303*3c9c7f3bSLORTET this.render(); 304*3c9c7f3bSLORTET 305*3c9c7f3bSLORTET this.dom.addEventListener('click', (event) => { 306*3c9c7f3bSLORTET event.preventDefault(); 307*3c9c7f3bSLORTET event.stopPropagation(); 308*3c9c7f3bSLORTET this.openEditor(); 309*3c9c7f3bSLORTET }); 310*3c9c7f3bSLORTET } 311*3c9c7f3bSLORTET 312*3c9c7f3bSLORTET render() { 313*3c9c7f3bSLORTET const syntax = syntaxFromNode(this.node); 314*3c9c7f3bSLORTET const parsed = parseVisualIndexSyntax(syntax); 315*3c9c7f3bSLORTET const label = parsed ? formatVisualIndexLabel(parsed) : syntax; 316*3c9c7f3bSLORTET this.dom.textContent = ''; 317*3c9c7f3bSLORTET 318*3c9c7f3bSLORTET const icon = document.createElement('img'); 319*3c9c7f3bSLORTET icon.className = 'pm_visualindex_icon'; 320*3c9c7f3bSLORTET icon.src = getFolderIconUrl(); 321*3c9c7f3bSLORTET icon.alt = ''; 322*3c9c7f3bSLORTET icon.setAttribute('aria-hidden', 'true'); 323*3c9c7f3bSLORTET this.dom.appendChild(icon); 324*3c9c7f3bSLORTET 325*3c9c7f3bSLORTET const text = document.createElement('span'); 326*3c9c7f3bSLORTET text.textContent = label; 327*3c9c7f3bSLORTET this.dom.appendChild(text); 328*3c9c7f3bSLORTET this.dom.setAttribute('title', syntax); 329*3c9c7f3bSLORTET } 330*3c9c7f3bSLORTET 331*3c9c7f3bSLORTET openEditor() { 332*3c9c7f3bSLORTET const parsed = parseVisualIndexSyntax(syntaxFromNode(this.node)) || { 333*3c9c7f3bSLORTET namespace: '.', 334*3c9c7f3bSLORTET filter: '', 335*3c9c7f3bSLORTET desc: false, 336*3c9c7f3bSLORTET medias: false 337*3c9c7f3bSLORTET }; 338*3c9c7f3bSLORTET 339*3c9c7f3bSLORTET showVisualIndexDialog(parsed, (values) => { 340*3c9c7f3bSLORTET const syntax = buildVisualIndexSyntax(values); 341*3c9c7f3bSLORTET const replacement = createVisualIndexNode(this.view.state.schema, syntax); 342*3c9c7f3bSLORTET if (!replacement) return; 343*3c9c7f3bSLORTET 344*3c9c7f3bSLORTET const pos = this.getPos(); 345*3c9c7f3bSLORTET this.view.dispatch(this.view.state.tr.replaceWith(pos, pos + this.node.nodeSize, replacement)); 346*3c9c7f3bSLORTET this.view.focus(); 347*3c9c7f3bSLORTET }); 348*3c9c7f3bSLORTET } 349*3c9c7f3bSLORTET 350*3c9c7f3bSLORTET update(node) { 351*3c9c7f3bSLORTET if (!isVisualIndexNode(node)) return false; 352*3c9c7f3bSLORTET this.node = node; 353*3c9c7f3bSLORTET const typeClass = (node.type && node.type.name === 'dwplugin_inline') ? 'pm_visualindex_inline' : 'pm_visualindex_block'; 354*3c9c7f3bSLORTET this.dom.className = 'plugin_visualindex pm_visualindex_node nodeHasForm ' + typeClass; 355*3c9c7f3bSLORTET this.render(); 356*3c9c7f3bSLORTET return true; 357*3c9c7f3bSLORTET } 358*3c9c7f3bSLORTET 359*3c9c7f3bSLORTET selectNode() { this.dom.classList.add('ProseMirror-selectednode'); } 360*3c9c7f3bSLORTET deselectNode() { this.dom.classList.remove('ProseMirror-selectednode'); } 361*3c9c7f3bSLORTET stopEvent() { return true; } 362*3c9c7f3bSLORTET ignoreMutation() { return true; } 363*3c9c7f3bSLORTET } 364*3c9c7f3bSLORTET 365*3c9c7f3bSLORTET class VisualIndexMenuItemDispatcher extends AbstractMenuItemDispatcher { 366*3c9c7f3bSLORTET static isAvailable(schema) { 367*3c9c7f3bSLORTET return !!(schema.nodes.visualindex || schema.nodes.dwplugin_block); 368*3c9c7f3bSLORTET } 369*3c9c7f3bSLORTET 370*3c9c7f3bSLORTET static getIcon() { 371*3c9c7f3bSLORTET const wrapper = document.createElement('span'); 372*3c9c7f3bSLORTET wrapper.className = 'menuicon'; 373*3c9c7f3bSLORTET wrapper.appendChild(getFolderMenuIcon()); 374*3c9c7f3bSLORTET return wrapper; 375*3c9c7f3bSLORTET } 376*3c9c7f3bSLORTET 377*3c9c7f3bSLORTET static getMenuItem(schema) { 378*3c9c7f3bSLORTET if (!this.isAvailable(schema)) return hiddenMenuItem(); 379*3c9c7f3bSLORTET 380*3c9c7f3bSLORTET return new MenuItem({ 381*3c9c7f3bSLORTET label: 'VisualIndex', 382*3c9c7f3bSLORTET icon: this.getIcon(), 383*3c9c7f3bSLORTET command: (state, dispatch, view) => { 384*3c9c7f3bSLORTET const existing = findVisualIndexAtSelection(state); 385*3c9c7f3bSLORTET if (!dispatch || !view) return true; 386*3c9c7f3bSLORTET 387*3c9c7f3bSLORTET let initialValues = {namespace: '.', filter: '', desc: false, medias: false}; 388*3c9c7f3bSLORTET if (existing) { 389*3c9c7f3bSLORTET const parsed = parseVisualIndexSyntax(syntaxFromNode(existing.node)); 390*3c9c7f3bSLORTET if (parsed) initialValues = parsed; 391*3c9c7f3bSLORTET } 392*3c9c7f3bSLORTET 393*3c9c7f3bSLORTET showVisualIndexDialog(initialValues, (values) => { 394*3c9c7f3bSLORTET const syntax = buildVisualIndexSyntax(values); 395*3c9c7f3bSLORTET const pluginNode = createVisualIndexNode(schema, syntax); 396*3c9c7f3bSLORTET if (!pluginNode) return; 397*3c9c7f3bSLORTET 398*3c9c7f3bSLORTET if (existing) { 399*3c9c7f3bSLORTET view.dispatch(view.state.tr.replaceWith(existing.pos, existing.pos + existing.node.nodeSize, pluginNode)); 400*3c9c7f3bSLORTET } else if (!insertVisualIndexBlock(view, pluginNode)) { 401*3c9c7f3bSLORTET const endPos = view.state.doc.content.size; 402*3c9c7f3bSLORTET view.dispatch(view.state.tr.insert(endPos, pluginNode)); 403*3c9c7f3bSLORTET } 404*3c9c7f3bSLORTET 405*3c9c7f3bSLORTET view.focus(); 406*3c9c7f3bSLORTET }); 407*3c9c7f3bSLORTET 408*3c9c7f3bSLORTET return true; 409*3c9c7f3bSLORTET } 410*3c9c7f3bSLORTET }); 411*3c9c7f3bSLORTET } 412*3c9c7f3bSLORTET } 413*3c9c7f3bSLORTET 414*3c9c7f3bSLORTET if (shouldShowInEditorMenu()) { 415*3c9c7f3bSLORTET window.Prosemirror.pluginMenuItemDispatchers.push(VisualIndexMenuItemDispatcher); 416*3c9c7f3bSLORTET } 417*3c9c7f3bSLORTET window.Prosemirror.pluginNodeViews.visualindex = (node, view, getPos) => new VisualIndexNodeView(node, view, getPos); 418*3c9c7f3bSLORTET 419*3c9c7f3bSLORTET const originalInline = window.Prosemirror.pluginNodeViews.dwplugin_inline; 420*3c9c7f3bSLORTET window.Prosemirror.pluginNodeViews.dwplugin_inline = (node, view, getPos) => { 421*3c9c7f3bSLORTET if (isLegacyVisualIndexPluginNode(node)) return new VisualIndexNodeView(node, view, getPos); 422*3c9c7f3bSLORTET return typeof originalInline === 'function' ? originalInline(node, view, getPos) : undefined; 423*3c9c7f3bSLORTET }; 424*3c9c7f3bSLORTET 425*3c9c7f3bSLORTET const originalBlock = window.Prosemirror.pluginNodeViews.dwplugin_block; 426*3c9c7f3bSLORTET window.Prosemirror.pluginNodeViews.dwplugin_block = (node, view, getPos) => { 427*3c9c7f3bSLORTET if (isLegacyVisualIndexPluginNode(node)) return new VisualIndexNodeView(node, view, getPos); 428*3c9c7f3bSLORTET return typeof originalBlock === 'function' ? originalBlock(node, view, getPos) : undefined; 429*3c9c7f3bSLORTET }; 430*3c9c7f3bSLORTET 431*3c9c7f3bSLORTET if (!window.__visualindexKeyboardGuardInstalled) { 432*3c9c7f3bSLORTET window.__visualindexKeyboardGuardInstalled = true; 433*3c9c7f3bSLORTET document.addEventListener('keydown', (event) => { 434*3c9c7f3bSLORTET const view = window.Prosemirror && window.Prosemirror.view; 435*3c9c7f3bSLORTET if (!view || !view.state) return; 436*3c9c7f3bSLORTET if (!selectionIsVisualIndex(view.state)) return; 437*3c9c7f3bSLORTET 438*3c9c7f3bSLORTET if (event.key === 'Enter') { 439*3c9c7f3bSLORTET event.preventDefault(); 440*3c9c7f3bSLORTET event.stopPropagation(); 441*3c9c7f3bSLORTET insertParagraphAfterSelectedVisualIndex(view); 442*3c9c7f3bSLORTET return; 443*3c9c7f3bSLORTET } 444*3c9c7f3bSLORTET 445*3c9c7f3bSLORTET if (event.key === 'Backspace' || event.key === 'Delete') { 446*3c9c7f3bSLORTET event.preventDefault(); 447*3c9c7f3bSLORTET event.stopPropagation(); 448*3c9c7f3bSLORTET } 449*3c9c7f3bSLORTET }, true); 450*3c9c7f3bSLORTET } 451*3c9c7f3bSLORTET } 452*3c9c7f3bSLORTET 453*3c9c7f3bSLORTET jQuery(document).on('PROSEMIRROR_API_INITIALIZED', initializeVisualIndexProsemirror); 454*3c9c7f3bSLORTET initializeVisualIndexProsemirror(); 455*3c9c7f3bSLORTET})(); 456