MediaWiki:Gadget-PreloadEditor.js: Difference between revisions
MediaWiki interface page
More actions
ReisuDesign (talk | contribs) No edit summary |
ReisuDesign (talk | contribs) No edit summary |
||
| Line 3: | Line 3: | ||
const PreloadEditor = { | const PreloadEditor = { | ||
version: '0. | version: '0.5.0', | ||
state: { | state: { | ||
| Line 30: | Line 30: | ||
}, | }, | ||
// ========================================================= | |||
// API | // API | ||
// ========================================================= | |||
async getPage(title) { | async getPage(title) { | ||
| Line 59: | Line 61: | ||
return { | return { | ||
title: page.title || title, | title: page.title || title, | ||
content: typeof content === 'string' ? content : '' | content: | ||
typeof content === 'string' | |||
? content | |||
: '' | |||
}; | }; | ||
}, | }, | ||
| Line 84: | Line 89: | ||
}, | }, | ||
async savePage(title, content, summary, createOnly) { | async savePage( | ||
title, | |||
content, | |||
summary, | |||
createOnly | |||
) { | |||
const params = { | const params = { | ||
action: 'edit', | action: 'edit', | ||
title, | title, | ||
text: content, | text: content, | ||
summary: summary || 'Updated with Preload Editor', | summary: | ||
summary || | |||
'Updated with Preload Editor', | |||
formatversion: 2 | formatversion: 2 | ||
}; | }; | ||
if (createOnly) params.createonly = true; | if (createOnly) { | ||
params.createonly = true; | |||
} | |||
return new mw.Api().postWithToken('csrf', params); | return new mw.Api().postWithToken( | ||
'csrf', | |||
params | |||
); | |||
}, | }, | ||
// ========================================================= | |||
// Preload parsing | // Preload parsing | ||
// ========================================================= | |||
extractIncludeonly(source) { | extractIncludeonly(source) { | ||
const match = String(source || '').match( | const match = | ||
String(source || '').match( | |||
/<includeonly>([\s\S]*?)<\/includeonly>/i | |||
); | |||
return match | return match | ||
| Line 112: | Line 132: | ||
findVariables(content) { | findVariables(content) { | ||
const variables = []; | const variables = []; | ||
const pattern = /%%([A-Za-z0-9_-]+)%%/g; | const pattern = | ||
/%%([A-Za-z0-9_-]+)%%/g; | |||
let match; | let match; | ||
while ((match = pattern.exec(content)) !== null) { | while ( | ||
const name = match[1].toUpperCase(); | (match = pattern.exec(content)) !== | ||
null | |||
) { | |||
const name = | |||
match[1].toUpperCase(); | |||
if (!variables.includes(name)) { | if (!variables.includes(name)) { | ||
| Line 128: | Line 154: | ||
findConditions(content) { | findConditions(content) { | ||
const conditions = []; | const conditions = []; | ||
const pattern = | const pattern = | ||
/<!--\s*@(if|elseif|else|endif)\b([\s\S]*?)-->/gi; | /<!--\s*@(if|elseif|else|endif)\b([\s\S]*?)-->/gi; | ||
| Line 133: | Line 160: | ||
let match; | let match; | ||
while ((match = pattern.exec(content)) !== null) { | while ( | ||
(match = pattern.exec(content)) !== | |||
null | |||
) { | |||
conditions.push({ | conditions.push({ | ||
type: match[1].toLowerCase(), | type: | ||
expression: match[2].trim(), | match[1].toLowerCase(), | ||
index: match.index | expression: | ||
match[2].trim(), | |||
index: | |||
match.index | |||
}); | }); | ||
} | } | ||
| Line 147: | Line 180: | ||
const variables = []; | const variables = []; | ||
this.findConditions(content).forEach(condition => { | this.findConditions(content).forEach( | ||
condition => { | |||
if ( | |||
condition.type !== 'if' && | |||
condition.type !== 'elseif' | |||
) { | |||
return; | |||
} | |||
const matches = | |||
condition.expression.match( | |||
/[A-Za-z0-9_-]+/g | |||
) || []; | |||
matches.forEach(name => { | |||
name = | |||
name.toUpperCase(); | |||
if ( | |||
!variables.includes( | |||
} | name | ||
} | ) | ||
) { | |||
variables.push( | |||
name | |||
); | |||
} | |||
}); | |||
} | |||
); | |||
return variables; | return variables; | ||
| Line 174: | Line 216: | ||
analyzePreload(content) { | analyzePreload(content) { | ||
return { | return { | ||
variables: this.findVariables(content), | variables: | ||
conditions: this.findConditions(content), | this.findVariables(content), | ||
conditionVariables: | |||
this.findConditionVariables(content) | conditions: | ||
this.findConditions(content), | |||
conditionVariables: | |||
this.findConditionVariables( | |||
content | |||
) | |||
}; | }; | ||
}, | }, | ||
// ========================================================= | |||
// Config | // Config | ||
// ========================================================= | |||
getAutomaticPageField() { | getAutomaticPageField() { | ||
return { | return { | ||
label: 'Page Name', | label: 'Page Name', | ||
description: 'Name of the page.', | description: | ||
'Name of the page.', | |||
type: 'page', | type: 'page', | ||
required: true, | required: true, | ||
| Line 199: | Line 250: | ||
incomplete: 'add-stub' | incomplete: 'add-stub' | ||
}, | }, | ||
fields: { | fields: { | ||
PAGE: this.getAutomaticPageField() | PAGE: | ||
this.getAutomaticPageField() | |||
} | } | ||
}; | }; | ||
| Line 216: | Line 269: | ||
if ( | if ( | ||
!config.settings || | !config.settings || | ||
typeof config.settings !== 'object' || | typeof config.settings !== | ||
'object' || | |||
Array.isArray(config.settings) | Array.isArray(config.settings) | ||
) { | ) { | ||
| Line 224: | Line 278: | ||
if ( | if ( | ||
!config.fields || | !config.fields || | ||
typeof config.fields !== 'object' || | typeof config.fields !== | ||
'object' || | |||
Array.isArray(config.fields) | Array.isArray(config.fields) | ||
) { | ) { | ||
| Line 230: | Line 285: | ||
} | } | ||
/ | /* | ||
* PAGE is always a special automatic field. | |||
*/ | |||
config.fields.PAGE = | config.fields.PAGE = | ||
this.getAutomaticPageField(); | this.getAutomaticPageField(); | ||
| Line 239: | Line 294: | ||
}, | }, | ||
createDefaultField(name, analysis) { | createDefaultField( | ||
name, | |||
analysis | |||
) { | |||
return { | return { | ||
label: this.humanizeName(name), | label: | ||
this.humanizeName(name), | |||
description: '', | description: '', | ||
type: | type: | ||
analysis.conditionVariables.includes(name) | analysis.conditionVariables.includes( | ||
name | |||
) | |||
? 'boolean' | ? 'boolean' | ||
: 'text' | : 'text' | ||
| Line 252: | Line 315: | ||
humanizeName(name) { | humanizeName(name) { | ||
return String(name || '') | return String(name || '') | ||
.replace(/[_-]+/g, ' ') | .replace( | ||
.replace(/([a-z])([A-Z])/g, '$1 $2') | /[_-]+/g, | ||
.replace(/\s+/g, ' ') | ' ' | ||
) | |||
.replace( | |||
/([a-z])([A-Z])/g, | |||
'$1 $2' | |||
) | |||
.replace( | |||
/\s+/g, | |||
' ' | |||
) | |||
.trim() | .trim() | ||
.replace( | .replace( | ||
/\b\w/g, | /\b\w/g, | ||
char => char.toUpperCase() | char => | ||
char.toUpperCase() | |||
); | ); | ||
}, | }, | ||
prepareConfigForVariables(config, analysis) { | /* | ||
config = this.normalizeConfig(config); | * Makes sure every variable currently used | ||
* by the preload has a configuration field. | |||
* | |||
* This is the important part for newly typed | |||
* variables such as %%ZHS%%. | |||
*/ | |||
prepareConfigForVariables( | |||
config, | |||
analysis | |||
) { | |||
config = | |||
this.normalizeConfig( | |||
config | |||
); | |||
const existing = | |||
config.fields || {}; | |||
const fields = {}; | const fields = {}; | ||
/ | /* | ||
fields.PAGE = this.getAutomaticPageField(); | * PAGE is always automatic/precompiled. | ||
*/ | |||
fields.PAGE = | |||
this.getAutomaticPageField(); | |||
analysis.variables.forEach(name => { | /* | ||
* Add every variable currently found | |||
* in the preload. | |||
* | |||
* Existing configuration is preserved. | |||
* New variables receive a default field. | |||
*/ | |||
analysis.variables.forEach( | |||
name => { | |||
if (name === 'PAGE') { | |||
return; | |||
} | |||
const existingField = | |||
existing[name]; | |||
fields[name] = | |||
existingField || | |||
this.createDefaultField( | |||
name, | |||
analysis | |||
); | |||
} | |||
); | |||
Object.keys(existing).forEach(name => { | /* | ||
* Preserve existing fields which are no | |||
* longer used. These are shown as UNUSED | |||
* rather than silently deleted. | |||
*/ | |||
Object.keys(existing).forEach( | |||
name => { | |||
const upper = | |||
name.toUpperCase(); | |||
if ( | |||
upper !== 'PAGE' && | |||
!fields[upper] | |||
) { | |||
fields[upper] = | |||
existing[name]; | |||
} | |||
} | } | ||
); | |||
config.fields = fields; | config.fields = fields; | ||
| Line 298: | Line 412: | ||
}, | }, | ||
validateConfig(config, analysis) { | validateConfig( | ||
config, | |||
analysis | |||
) { | |||
const errors = []; | const errors = []; | ||
const warnings = []; | const warnings = []; | ||
| Line 309: | Line 426: | ||
return { | return { | ||
valid: false, | valid: false, | ||
errors: ['Config must be an object.'], | errors: [ | ||
'Config must be an object.' | |||
], | |||
warnings: [] | warnings: [] | ||
}; | }; | ||
| Line 316: | Line 435: | ||
if ( | if ( | ||
!config.fields || | !config.fields || | ||
typeof config.fields !== 'object' || | typeof config.fields !== | ||
'object' || | |||
Array.isArray(config.fields) | Array.isArray(config.fields) | ||
) { | ) { | ||
return { | return { | ||
valid: false, | valid: false, | ||
errors: ['"fields" must be an object.'], | errors: [ | ||
'"fields" must be an object.' | |||
], | |||
warnings | warnings | ||
}; | }; | ||
| Line 327: | Line 449: | ||
const configured = | const configured = | ||
Object.keys(config.fields | Object.keys( | ||
config.fields | |||
) | ).map( | ||
name => | |||
name.toUpperCase() | |||
name | |||
); | ); | ||
analysis.variables.forEach( | |||
name => { | |||
if ( | if ( | ||
! | !configured.includes( | ||
name | |||
) | |||
) { | ) { | ||
errors.push( | errors.push( | ||
'Field "' + | 'Variable "' + | ||
name + | |||
'" is not configured.' | |||
); | |||
} | |||
} | |||
); | |||
configured.forEach( | |||
name => { | |||
if ( | |||
!analysis.variables.includes( | |||
name | |||
) | |||
) { | |||
warnings.push( | |||
'Field "' + | |||
name + | |||
'" is not used by the preload.' | |||
); | |||
} | |||
} | |||
); | |||
if ( | |||
!configured.includes( | |||
'PAGE' | |||
) | |||
) { | |||
errors.push( | |||
'PAGE must always be configured.' | |||
); | |||
} | |||
Object.entries( | |||
config.fields | |||
).forEach( | |||
([name, field]) => { | |||
if ( | |||
!field || | |||
typeof field !== | |||
'object' || | |||
Array.isArray(field) | |||
) { | |||
errors.push( | |||
'Field "' + | |||
name + | name + | ||
'" must be an object.' | '" must be an object.' | ||
); | ); | ||
return; | return; | ||
} | } | ||
| Line 382: | Line 527: | ||
if ( | if ( | ||
( | ( | ||
field.type === 'select' || | field.type === | ||
field.type === 'multiselect' | 'select' || | ||
field.type === | |||
'multiselect' | |||
) && | ) && | ||
!field.source && | !field.source && | ||
!Array.isArray(field.options) | !Array.isArray( | ||
field.options | |||
) | |||
) { | ) { | ||
warnings.push( | warnings.push( | ||
| Line 400: | Line 549: | ||
return { | return { | ||
valid: !errors.length, | valid: | ||
!errors.length, | |||
errors, | errors, | ||
warnings | warnings | ||
| Line 406: | Line 556: | ||
}, | }, | ||
// ========================================================= | |||
// Transformation helpers | // Transformation helpers | ||
// ========================================================= | |||
transformationObject( | transformationObject( | ||
| Line 415: | Line 567: | ||
to | to | ||
) { | ) { | ||
if (!transform) return null; | if (!transform) { | ||
return null; | |||
} | |||
switch (transform) { | switch (transform) { | ||
case 'remove-prefix': | case 'remove-prefix': | ||
return prefix | return prefix | ||
? { transform, prefix } | ? { | ||
transform, | |||
prefix | |||
} | |||
: null; | : null; | ||
case 'remove-suffix': | case 'remove-suffix': | ||
return suffix | return suffix | ||
? { transform, suffix } | ? { | ||
transform, | |||
suffix | |||
} | |||
: null; | : null; | ||
| Line 442: | Line 602: | ||
}, | }, | ||
// ========================================================= | |||
// UI helpers | // UI helpers | ||
// ========================================================= | |||
createSection(title, className) { | createSection( | ||
const section = $('<section>') | title, | ||
className | |||
) { | |||
const section = | |||
$('<section>') | |||
.addClass( | |||
'preload-editor-section' | |||
); | |||
if (className) { | if (className) { | ||
section.addClass(className); | section.addClass( | ||
className | |||
); | |||
} | } | ||
| Line 462: | Line 632: | ||
}, | }, | ||
createButton(label, className, callback) { | createButton( | ||
const button = $('<button>') | label, | ||
className, | |||
callback | |||
) { | |||
const button = | |||
$('<button>') | |||
.attr( | |||
'type', | |||
'button' | |||
) | |||
.addClass( | |||
'preload-editor-button' | |||
) | |||
.text(label); | |||
if (className) { | if (className) { | ||
button.addClass(className); | button.addClass( | ||
className | |||
); | |||
} | } | ||
| Line 482: | Line 664: | ||
}, | }, | ||
fieldRow(label, input, description) { | fieldRow( | ||
const row = $('<div>') | label, | ||
input, | |||
description | |||
) { | |||
const row = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-field' | |||
); | |||
$('<label>') | $('<label>') | ||
.addClass('preload-editor-label') | .addClass( | ||
'preload-editor-label' | |||
) | |||
.text(label) | .text(label) | ||
.appendTo(row); | .appendTo(row); | ||
| Line 493: | Line 684: | ||
if (description) { | if (description) { | ||
$('<div>') | $('<div>') | ||
.addClass('preload-editor-help') | .addClass( | ||
'preload-editor-help' | |||
) | |||
.text(description) | .text(description) | ||
.appendTo(row); | .appendTo(row); | ||
| Line 503: | Line 696: | ||
}, | }, | ||
textInput(value, placeholder) { | textInput( | ||
value, | |||
placeholder | |||
) { | |||
return $('<input>') | return $('<input>') | ||
.attr({ | .attr({ | ||
| Line 514: | Line 710: | ||
placeholder || '' | placeholder || '' | ||
}) | }) | ||
.addClass('preload-editor-input'); | .addClass( | ||
'preload-editor-input' | |||
); | |||
}, | }, | ||
| Line 531: | Line 729: | ||
checkbox(checked) { | checkbox(checked) { | ||
return $('<input>') | return $('<input>') | ||
.attr('type', 'checkbox') | .attr( | ||
.prop('checked', !!checked) | 'type', | ||
'checkbox' | |||
) | |||
.prop( | |||
'checked', | |||
!!checked | |||
) | |||
.addClass( | .addClass( | ||
'preload-editor-checkbox' | 'preload-editor-checkbox' | ||
| Line 538: | Line 742: | ||
}, | }, | ||
select(options, value) { | select( | ||
const select = $('<select>') | options, | ||
value | |||
) { | |||
const select = | |||
$('<select>') | |||
.addClass( | |||
'preload-editor-select' | |||
); | |||
options.forEach(option => { | options.forEach( | ||
option => { | |||
$('<option>') | |||
.attr( | |||
'value', | |||
option.value | |||
) | ) | ||
.text( | |||
option.label | |||
) | |||
.prop( | |||
'selected', | |||
String( | |||
option.value | |||
) === | |||
String( | |||
value === | |||
undefined | |||
? '' | |||
: value | |||
) | |||
) | |||
.appendTo(select); | |||
} | |||
); | |||
return select; | return select; | ||
}, | }, | ||
// ========================================================= | |||
// CodeMirror | // CodeMirror | ||
// ========================================================= | |||
destroyEditor() { | destroyEditor() { | ||
| Line 580: | Line 797: | ||
} = this.editor; | } = this.editor; | ||
if (view && inputHandler) { | if ( | ||
view && | |||
inputHandler | |||
) { | |||
view.dom.removeEventListener( | view.dom.removeEventListener( | ||
'input', | 'input', | ||
| Line 608: | Line 828: | ||
} | } | ||
return this.state.preloadContent || ''; | return ( | ||
this.state.preloadContent || | |||
'' | |||
); | |||
}, | }, | ||
setEditorContent(content) { | setEditorContent(content) { | ||
content = String(content || ''); | content = | ||
String(content || ''); | |||
if (!this.editorView) { | if (!this.editorView) { | ||
this.state.preloadContent = content; | this.state.preloadContent = | ||
content; | |||
return; | return; | ||
} | } | ||
| Line 622: | Line 848: | ||
this.editorView.state.doc.toString(); | this.editorView.state.doc.toString(); | ||
if (current === content) return; | if (current === content) { | ||
return; | |||
} | |||
this.editorView.dispatch({ | this.editorView.dispatch({ | ||
changes: { | changes: { | ||
from: 0, | from: 0, | ||
to: this.editorView.state.doc.length, | to: | ||
this.editorView.state | |||
.doc.length, | |||
insert: content | insert: content | ||
} | } | ||
| Line 642: | Line 872: | ||
const selection = | const selection = | ||
this.editorView.state.selection.main; | this.editorView.state | ||
.selection.main; | |||
return { | return { | ||
| Line 648: | Line 879: | ||
to: selection.to, | to: selection.to, | ||
text: | text: | ||
this.editorView.state.doc.sliceString( | this.editorView.state.doc | ||
.sliceString( | |||
selection.from, | |||
selection.to | |||
) | |||
}; | }; | ||
}, | }, | ||
| Line 667: | Line 899: | ||
cursorPosition | cursorPosition | ||
) { | ) { | ||
if (!this.editorView) return; | if (!this.editorView) { | ||
return; | |||
} | |||
const view = | |||
this.editorView; | |||
const selection = | const selection = | ||
view.state.selection.main; | view.state.selection.main; | ||
| Line 684: | Line 920: | ||
const cursor = | const cursor = | ||
cursorPosition === undefined | cursorPosition === | ||
undefined | |||
? from + text.length | ? from + text.length | ||
: cursorPosition; | : cursorPosition; | ||
| Line 694: | Line 931: | ||
insert: text | insert: text | ||
}, | }, | ||
selection: { | selection: { | ||
anchor: cursor | anchor: cursor | ||
| Line 702: | Line 940: | ||
}, | }, | ||
// | // ========================================================= | ||
// Editor tools | |||
// ========================================================= | |||
wrapSelectionWithVariable() { | wrapSelectionWithVariable() { | ||
| Line 710: | Line 950: | ||
if ( | if ( | ||
!selected || | !selected || | ||
selected.from === selected.to | selected.from === | ||
selected.to | |||
) { | ) { | ||
this.focusEditor(); | this.focusEditor(); | ||
| Line 731: | Line 972: | ||
insertVariable(name) { | insertVariable(name) { | ||
name = String(name || '') | name = | ||
String(name || '') | |||
.trim() | |||
.toUpperCase(); | |||
if (!name) return; | if (!name) { | ||
return; | |||
} | |||
const selected = | const selected = | ||
this.getEditorSelection(); | this.getEditorSelection(); | ||
if (!selected) return; | if (!selected) { | ||
return; | |||
} | |||
const text = | const text = | ||
| Line 754: | Line 1,000: | ||
}, | }, | ||
insertCondition(type, variable) { | insertCondition( | ||
type, | |||
variable | |||
) { | |||
type = | type = | ||
String(type || '') | String(type || '') | ||
| Line 767: | Line 1,016: | ||
this.getEditorSelection(); | this.getEditorSelection(); | ||
if (!selected) return; | if (!selected) { | ||
return; | |||
} | |||
let text; | let text; | ||
| Line 774: | Line 1,025: | ||
switch (type) { | switch (type) { | ||
case 'if': { | case 'if': { | ||
if (!variable) return; | if (!variable) { | ||
return; | |||
} | |||
const opening = | const opening = | ||
| Line 811: | Line 1,064: | ||
case 'elseif': | case 'elseif': | ||
text = '<!-- @elseif -->'; | text = | ||
cursorOffset = text.length; | '<!-- @elseif -->'; | ||
cursorOffset = | |||
text.length; | |||
break; | break; | ||
case 'else': | case 'else': | ||
text = '<!-- @else -->'; | text = | ||
cursorOffset = text.length; | '<!-- @else -->'; | ||
cursorOffset = | |||
text.length; | |||
break; | break; | ||
case 'endif': | case 'endif': | ||
text = '<!-- @endif -->'; | text = | ||
cursorOffset = text.length; | '<!-- @endif -->'; | ||
cursorOffset = | |||
text.length; | |||
break; | break; | ||
| Line 849: | Line 1,108: | ||
name => { | name => { | ||
variables.add( | variables.add( | ||
String(name).toUpperCase() | String( | ||
name | |||
).toUpperCase() | |||
); | ); | ||
} | } | ||
| Line 855: | Line 1,116: | ||
} | } | ||
return Array.from(variables); | return Array.from( | ||
variables | |||
); | |||
}, | }, | ||
createToolSelect(options, className) { | createToolSelect( | ||
const select = $('<select>') | options, | ||
className | |||
) { | |||
const select = | |||
$('<select>') | |||
.addClass( | |||
'preload-editor-tool-select' | |||
); | |||
if (className) { | if (className) { | ||
select.addClass(className); | select.addClass( | ||
className | |||
); | |||
} | } | ||
options.forEach(option => { | options.forEach( | ||
option => { | |||
$('<option>') | |||
.attr( | |||
'value', | |||
option.value | |||
) | |||
.text( | |||
option.label | |||
) | |||
.appendTo(select); | |||
} | |||
); | |||
return select; | return select; | ||
| Line 882: | Line 1,155: | ||
updateEditorToolVariables() { | updateEditorToolVariables() { | ||
if (!this.editorTools) return; | if (!this.editorTools) { | ||
return; | |||
} | |||
const variables = | const variables = | ||
| Line 888: | Line 1,163: | ||
const current = | const current = | ||
this.editorTools.variableSelect.val(); | this.editorTools | ||
.variableSelect | |||
.val(); | |||
const currentCondition = | const currentCondition = | ||
| Line 895: | Line 1,172: | ||
.val(); | .val(); | ||
const populate = ( | const populate = | ||
( | |||
select, | |||
value | |||
) => { | |||
select.empty(); | |||
variables.forEach( | |||
name => { | |||
$('<option>') | |||
.attr( | |||
'value', | |||
name | |||
) | |||
.text(name) | |||
.appendTo( | |||
select | |||
); | |||
} | |||
); | |||
if ( | |||
value && | |||
. | variables.includes( | ||
value | |||
) | ) | ||
) { | |||
select.val( | |||
value | |||
); | |||
} else if ( | |||
variables.length | |||
) { | |||
select.val( | |||
variables[0] | |||
); | |||
} | |||
}; | |||
populate( | populate( | ||
this.editorTools.variableSelect, | this.editorTools | ||
.variableSelect, | |||
current | current | ||
); | ); | ||
| Line 936: | Line 1,225: | ||
updateConditionVariableVisibility() { | updateConditionVariableVisibility() { | ||
if (!this.editorTools) return; | if (!this.editorTools) { | ||
return; | |||
} | |||
const type = | const type = | ||
| Line 945: | Line 1,236: | ||
this.editorTools | this.editorTools | ||
.conditionVariableWrap | .conditionVariableWrap | ||
.toggle(type === 'if'); | .toggle( | ||
type === 'if' | |||
); | |||
}, | }, | ||
renderEditorTools() { | renderEditorTools() { | ||
const tools = $('<div>') | const tools = | ||
$('<div>') | |||
.addClass( | |||
'preload-editor-tools' | |||
); | |||
const wrapGroup = $('<div>') | const wrapGroup = | ||
$('<div>') | |||
.addClass( | |||
'preload-editor-tool-group ' + | |||
'preload-editor-tool-group-wrap' | |||
); | |||
wrapGroup.append( | wrapGroup.append( | ||
| Line 969: | Line 1,264: | ||
); | ); | ||
const variableGroup = $('<div>') | const variableGroup = | ||
$('<div>') | |||
.addClass( | |||
'preload-editor-tool-group ' + | |||
'preload-editor-tool-group-variable' | |||
); | |||
$('<span>') | $('<span>') | ||
| Line 982: | Line 1,278: | ||
'Insert existing variable' | 'Insert existing variable' | ||
) | ) | ||
.appendTo(variableGroup); | .appendTo( | ||
variableGroup | |||
); | |||
const variableSelect = | const variableSelect = | ||
| Line 992: | Line 1,290: | ||
variableGroup.append( | variableGroup.append( | ||
variableSelect, | variableSelect, | ||
this.createButton( | this.createButton( | ||
'Insert', | 'Insert', | ||
| Line 1,002: | Line 1,301: | ||
); | ); | ||
const conditionGroup = $('<div>') | const conditionGroup = | ||
$('<div>') | |||
.addClass( | |||
'preload-editor-tool-group ' + | |||
'preload-editor-tool-group-condition' | |||
); | |||
$('<span>') | $('<span>') | ||
| Line 1,012: | Line 1,312: | ||
'preload-editor-tool-label' | 'preload-editor-tool-label' | ||
) | ) | ||
.text('Insert condition') | .text( | ||
.appendTo(conditionGroup); | 'Insert condition' | ||
) | |||
.appendTo( | |||
conditionGroup | |||
); | |||
const conditionTypeSelect = | const conditionTypeSelect = | ||
| Line 1,023: | Line 1,327: | ||
}, | }, | ||
{ | { | ||
value: 'elseif', | value: | ||
label: '@elseif' | 'elseif', | ||
label: | |||
'@elseif' | |||
}, | }, | ||
{ | { | ||
value: 'else', | value: 'else', | ||
label: '@else' | label: | ||
'@else' | |||
}, | }, | ||
{ | { | ||
value: 'endif', | value: | ||
label: '@endif' | 'endif', | ||
label: | |||
'@endif' | |||
} | } | ||
], | ], | ||
| Line 1,102: | Line 1,411: | ||
}, | }, | ||
// ========================================================= | |||
// Preload syntax highlighting | // Preload syntax highlighting | ||
// ========================================================= | |||
getKnownVariables() { | getKnownVariables() { | ||
| Line 1,114: | Line 1,425: | ||
if ( | if ( | ||
fields && | fields && | ||
typeof fields === 'object' | typeof fields === | ||
'object' | |||
) { | ) { | ||
Object.keys(fields).forEach( | Object.keys(fields).forEach( | ||
name => { | name => { | ||
known.add( | known.add( | ||
String(name).toUpperCase() | String( | ||
name | |||
).toUpperCase() | |||
); | ); | ||
} | } | ||
| Line 1,133: | Line 1,447: | ||
) { | ) { | ||
const decorations = []; | const decorations = []; | ||
const source = | const source = | ||
state.doc.toString(); | state.doc.toString(); | ||
| Line 1,146: | Line 1,461: | ||
while ( | while ( | ||
(match = | (match = | ||
variablePattern.exec(source)) !== | variablePattern.exec( | ||
source | |||
)) !== null | |||
) { | ) { | ||
const name = | const name = | ||
| Line 1,153: | Line 1,469: | ||
const known = | const known = | ||
knownVariables.has(name); | knownVariables.has( | ||
name | |||
); | |||
decorations.push( | decorations.push( | ||
| Line 1,176: | Line 1,494: | ||
while ( | while ( | ||
(match = | (match = | ||
conditionPattern.exec(source)) !== | conditionPattern.exec( | ||
source | |||
)) !== null | |||
) { | ) { | ||
decorations.push( | decorations.push( | ||
| Line 1,243: | Line 1,562: | ||
if ( | if ( | ||
!knownVariables.has(name) | !knownVariables.has( | ||
name | |||
) | |||
) { | ) { | ||
continue; | continue; | ||
| Line 1,272: | Line 1,593: | ||
}, | }, | ||
async installSyntaxHighlighting(view) { | async installSyntaxHighlighting( | ||
view | |||
) { | |||
await mw.loader.using( | await mw.loader.using( | ||
'ext.CodeMirror.v6.lib' | 'ext.CodeMirror.v6.lib' | ||
| Line 1,325: | Line 1,648: | ||
}, | }, | ||
// | // ========================================================= | ||
// Editor state synchronization | |||
// ========================================================= | |||
/* | |||
* Re-analyze the current CodeMirror content, | |||
* create configuration fields for newly found | |||
* variables, keep selection valid, and update | |||
* all live UI. | |||
*/ | |||
syncEditorState() { | |||
this.reanalyze(); | |||
this.renderVariablesBySelector(); | |||
this.renderVariableConfigurationBySelector(); | |||
this.renderAnalysisBySelector(); | |||
this.renderConfigPreviewBySelector(); | |||
this.updateEditorTools(); | |||
}, | |||
// ========================================================= | |||
// CodeMirror mounting | |||
// ========================================================= | |||
async mountCodeMirror( | |||
textarea | |||
) { | |||
return new Promise( | |||
(resolve, reject) => { | |||
let settled = false; | |||
const cleanup = | |||
() => { | |||
mw.hook( | |||
'dev.CCM.ready' | |||
).remove( | |||
onReady | |||
); | |||
mw.hook( | |||
'dev.CCM.load' | |||
).remove( | |||
onLoad | |||
); | |||
}; | |||
const onReady = | |||
async cm => { | |||
if ( | |||
settled || | |||
!cm || | |||
cm.textarea !== | |||
textarea || | |||
!cm.view | |||
) { | |||
return; | |||
} | |||
settled = true; | |||
cleanup(); | |||
const view = | |||
cm.view; | |||
const inputHandler = | |||
() => { | |||
this.state | |||
.preloadContent = | |||
view.state.doc.toString(); | |||
this.state.dirty = | |||
true; | |||
/* | |||
* This now performs the | |||
* entire live synchronization. | |||
* | |||
* So typing %%ZHS%% will: | |||
* | |||
* - detect ZHS | |||
* - create config.fields.ZHS | |||
* - add ZHS to the variable list | |||
* - make ZHS selectable | |||
* - update its configuration | |||
* - update config preview | |||
* - update editor tools | |||
*/ | |||
this.syncEditorState(); | |||
}; | |||
view.dom.addEventListener( | |||
'input', | |||
inputHandler | |||
); | ); | ||
view.dom.classList.add( | |||
'preload-editor-codemirror' | |||
); | |||
const wrapper = | |||
view.dom.closest( | |||
'.ext-codemirror-wrapper' | |||
); | |||
if (wrapper) { | |||
wrapper.classList.add( | |||
'preload-editor-codemirror-wrapper' | |||
); | |||
} | |||
this.editor = { | |||
cm, | |||
view, | |||
textarea, | |||
inputHandler | |||
}; | |||
this.editorView = | |||
view; | |||
this.state | |||
.preloadContent = | |||
view.state.doc.toString(); | |||
try { | |||
await this.installSyntaxHighlighting( | |||
view | |||
); | |||
/* | |||
* Run one initial synchronization | |||
* after CodeMirror is mounted so the | |||
* UI is based on the actual document. | |||
*/ | |||
this.syncEditorState(); | |||
resolve( | |||
view | |||
); | |||
} catch ( | |||
error | |||
) { | |||
reject( | |||
error | |||
); | |||
} | |||
}; | |||
const onLoad = | |||
loader => { | |||
if ( | |||
settled || | |||
typeof loader !== | |||
'function' | |||
) { | |||
return; | |||
} | |||
try { | |||
loader( | |||
textarea | |||
); | |||
} catch ( | |||
error | |||
) { | |||
settled = true; | |||
cleanup(); | |||
reject( | |||
error | |||
); | |||
} | |||
}; | |||
mw.hook( | |||
'dev.CCM.ready' | |||
).add( | |||
onReady | |||
); | |||
mw.hook( | |||
'dev.CCM.load' | |||
).add( | |||
onLoad | |||
); | |||
} | |||
); | |||
}, | }, | ||
// ========================================================= | |||
// Main UI | |||
// ========================================================= | |||
const | init() { | ||
const root = | |||
$('#mw-content-text'); | |||
const titleInput = | if (!root.length) { | ||
this.textInput( | return; | ||
this.state.preloadTitle, | } | ||
this.root = | |||
$('<div>') | |||
.attr( | |||
'id', | |||
'preload-editor' | |||
) | |||
.addClass( | |||
'preload-editor' | |||
); | |||
root.append( | |||
this.root | |||
); | |||
this.renderShell(); | |||
}, | |||
renderShell() { | |||
this.destroyEditor(); | |||
this.root.empty(); | |||
const loader = | |||
this.createSection( | |||
'Preload' | |||
); | |||
const titleInput = | |||
this.textInput( | |||
this.state.preloadTitle, | |||
'Template:Example/Preload' | 'Template:Example/Preload' | ||
).attr( | ).attr( | ||
| Line 1,501: | Line 1,902: | ||
'New', | 'New', | ||
'', | '', | ||
() => this.startNew() | () => | ||
this.startNew() | |||
); | ); | ||
| Line 1,525: | Line 1,927: | ||
); | ); | ||
this.root.append(loader); | this.root.append( | ||
loader | |||
); | |||
this.status = | this.status = | ||
| Line 1,562: | Line 1,966: | ||
'Load an existing preload, create a new one, or start from a sample wiki page.' | 'Load an existing preload, create a new one, or start from a sample wiki page.' | ||
) | ) | ||
.appendTo(section); | .appendTo( | ||
section | |||
); | |||
const tips = | const tips = | ||
| Line 1,574: | Line 1,980: | ||
'New preload: starts with the standard Preload Template structure.', | 'New preload: starts with the standard Preload Template structure.', | ||
'Sample page: loads normal wiki page content so you can turn it into a preload.' | 'Sample page: loads normal wiki page content so you can turn it into a preload.' | ||
].forEach(text => { | ].forEach( | ||
text => { | |||
$('<li>') | |||
.text(text) | |||
.appendTo( | |||
tips | |||
); | |||
} | |||
); | |||
section.append( | |||
tips | |||
); | |||
this.workspace.append( | |||
this.workspace.append(section); | section | ||
); | |||
}, | }, | ||
setStatus(message, type) { | setStatus( | ||
if (!this.status) return; | message, | ||
type | |||
) { | |||
if (!this.status) { | |||
return; | |||
} | |||
this.status.removeClass( | this.status.removeClass( | ||
| Line 1,605: | Line 2,025: | ||
}, | }, | ||
// ========================================================= | |||
// Load / create | // Load / create | ||
// ========================================================= | |||
async loadExisting(title) { | async loadExisting( | ||
title | |||
) { | |||
title = | title = | ||
String(title || '').trim(); | String(title || '') | ||
.trim(); | |||
if (!title) { | if (!title) { | ||
mw.notify( | mw.notify( | ||
'Enter a preload page title first.', | 'Enter a preload page title first.', | ||
{ type: 'warn' } | { | ||
type: 'warn' | |||
} | |||
); | ); | ||
| Line 1,627: | Line 2,054: | ||
try { | try { | ||
const page = | const page = | ||
await this.getPage(title); | await this.getPage( | ||
title | |||
); | |||
if (!page) { | if (!page) { | ||
| Line 1,657: | Line 2,086: | ||
'success' | 'success' | ||
); | ); | ||
} catch (error) { | } catch ( | ||
error | |||
) { | |||
console.error( | console.error( | ||
'Preload Editor:', | 'Preload Editor:', | ||
| Line 1,671: | Line 2,102: | ||
}, | }, | ||
async findSimilarPreloads(title) { | async findSimilarPreloads( | ||
title | |||
) { | |||
let query = | let query = | ||
title | title | ||
| Line 1,696: | Line 2,129: | ||
) | ) | ||
); | ); | ||
} catch (error) { | } catch ( | ||
error | |||
) { | |||
console.error( | console.error( | ||
'Preload Editor search:', | 'Preload Editor search:', | ||
| Line 1,723: | Line 2,158: | ||
'".' | '".' | ||
) | ) | ||
.appendTo(section); | .appendTo( | ||
section | |||
); | |||
if (suggestions.length) { | if ( | ||
suggestions.length | |||
) { | |||
$('<h3>') | $('<h3>') | ||
.text('Similar preloads') | .text( | ||
.appendTo(section); | 'Similar preloads' | ||
) | |||
.appendTo( | |||
section | |||
); | |||
const list = | const list = | ||
| Line 1,751: | Line 2,194: | ||
); | ); | ||
section.append(list); | section.append( | ||
list | |||
); | |||
} | } | ||
| Line 1,759: | Line 2,204: | ||
'preload-editor-button-primary', | 'preload-editor-button-primary', | ||
() => | () => | ||
this.startNew(title) | this.startNew( | ||
title | |||
) | |||
) | ) | ||
); | ); | ||
this.workspace.append(section); | this.workspace.append( | ||
section | |||
); | |||
}, | }, | ||
async startNew(title) { | async startNew( | ||
title | |||
) { | |||
title = | title = | ||
String(title || '').trim(); | String(title || '') | ||
.trim(); | |||
this.state = { | this.state = { | ||
| Line 1,779: | Line 2,231: | ||
variables: ['PAGE'], | variables: ['PAGE'], | ||
conditions: [], | conditions: [], | ||
selectedVariable: 'PAGE', | selectedVariable: | ||
'PAGE', | |||
mode: 'new', | mode: 'new', | ||
dirty: true | dirty: true | ||
| Line 1,799: | Line 2,252: | ||
); | ); | ||
if (!result) return; | if (!result) { | ||
return; | |||
} | |||
this.setStatus( | this.setStatus( | ||
| Line 1,808: | Line 2,263: | ||
try { | try { | ||
const page = | const page = | ||
await this.getPage(result); | await this.getPage( | ||
result | |||
); | |||
if (!page) { | if (!page) { | ||
| Line 1,825: | Line 2,282: | ||
preloadTitle: '', | preloadTitle: '', | ||
preloadSource: '', | preloadSource: '', | ||
preloadContent: sample, | preloadContent: | ||
sample, | |||
config: | config: | ||
this.emptyConfig(), | this.emptyConfig(), | ||
variables: ['PAGE'], | variables: ['PAGE'], | ||
conditions: [], | conditions: [], | ||
selectedVariable: 'PAGE', | selectedVariable: | ||
'PAGE', | |||
mode: 'new', | mode: 'new', | ||
dirty: true | dirty: true | ||
| Line 1,842: | Line 2,301: | ||
'success' | 'success' | ||
); | ); | ||
} catch (error) { | } catch ( | ||
error | |||
) { | |||
console.error( | console.error( | ||
'Preload Editor sample:', | 'Preload Editor sample:', | ||
| Line 1,856: | Line 2,317: | ||
}, | }, | ||
convertSampleToPreload(source) { | convertSampleToPreload( | ||
source | |||
) { | |||
let content = | let content = | ||
String(source || '').trim(); | String(source || '') | ||
.trim(); | |||
const include = | const include = | ||
| Line 1,906: | Line 2,370: | ||
$('<h2>') | $('<h2>') | ||
.text(title) | .text(title) | ||
.appendTo(box); | .appendTo( | ||
box | |||
); | |||
$('<p>') | $('<p>') | ||
.text(message) | .text(message) | ||
.appendTo(box); | .appendTo( | ||
box | |||
); | |||
const input = | const input = | ||
| Line 1,927: | Line 2,395: | ||
() => { | () => { | ||
overlay.remove(); | overlay.remove(); | ||
resolve(null); | resolve( | ||
null | |||
); | |||
} | } | ||
); | ); | ||
| Line 1,942: | Line 2,412: | ||
).trim(); | ).trim(); | ||
if (!value) return; | if (!value) { | ||
return; | |||
} | |||
overlay.remove(); | overlay.remove(); | ||
resolve(value); | resolve( | ||
value | |||
); | |||
} | } | ||
); | ); | ||
| Line 1,959: | Line 2,433: | ||
); | ); | ||
overlay.append(box); | overlay.append( | ||
box | |||
); | |||
$(document.body) | $(document.body) | ||
.append(overlay); | .append( | ||
overlay | |||
); | |||
input.trigger('focus'); | input.trigger( | ||
'focus' | |||
); | |||
input.on( | input.on( | ||
| Line 2,004: | Line 2,485: | ||
const configTitle = | const configTitle = | ||
title.replace(/\/+$/, '') + | title.replace( | ||
/\/+$/, | |||
'' | |||
) + | |||
'/config.json'; | '/config.json'; | ||
| Line 2,018: | Line 2,502: | ||
configPage.content | configPage.content | ||
); | ); | ||
} catch (error) { | } catch ( | ||
error | |||
) { | |||
throw new Error( | throw new Error( | ||
'The existing config.json contains invalid JSON: ' + | 'The existing config.json contains invalid JSON: ' + | ||
| Line 2,038: | Line 2,524: | ||
this.state = { | this.state = { | ||
preloadTitle: title, | preloadTitle: | ||
preloadSource: source, | title, | ||
preloadContent: source, | |||
preloadSource: | |||
source, | |||
preloadContent: | |||
source, | |||
config, | config, | ||
variables: | variables: | ||
analysis.variables, | analysis.variables, | ||
conditions: | conditions: | ||
analysis.conditions, | analysis.conditions, | ||
selectedVariable: | selectedVariable: | ||
analysis.variables.includes('PAGE') | analysis.variables.includes( | ||
'PAGE' | |||
) | |||
? 'PAGE' | ? 'PAGE' | ||
: ( | : ( | ||
| Line 2,053: | Line 2,550: | ||
'PAGE' | 'PAGE' | ||
), | ), | ||
mode: 'edit', | mode: 'edit', | ||
dirty: false | dirty: false | ||
| Line 2,060: | Line 2,558: | ||
}, | }, | ||
// ========================================================= | |||
// Editor | // Editor | ||
// ========================================================= | |||
renderEditor() { | renderEditor() { | ||
this.destroyEditor(); | this.destroyEditor(); | ||
this.workspace.empty(); | this.workspace.empty(); | ||
| Line 2,101: | Line 2,602: | ||
'New preload' | 'New preload' | ||
) | ) | ||
.appendTo(meta); | .appendTo( | ||
meta | |||
); | |||
if ( | if ( | ||
| Line 2,112: | Line 2,615: | ||
) | ) | ||
.text('NEW') | .text('NEW') | ||
.appendTo(meta); | .appendTo( | ||
meta | |||
); | |||
} | } | ||
editorSection.append(meta); | editorSection.append( | ||
meta | |||
); | |||
editorSection.append( | editorSection.append( | ||
| Line 2,138: | Line 2,645: | ||
'preload-editor-codemirror-mount' | 'preload-editor-codemirror-mount' | ||
) | ) | ||
.append(textarea); | .append( | ||
textarea | |||
); | |||
editorSection.append( | editorSection.append( | ||
| Line 2,144: | Line 2,653: | ||
); | ); | ||
left.append(editorSection); | left.append( | ||
editorSection | |||
); | |||
/* | /* | ||
* RIGHT PANEL | * RIGHT PANEL | ||
*/ | */ | ||
| Line 2,224: | Line 2,731: | ||
'Save Preload + Config', | 'Save Preload + Config', | ||
'preload-editor-button-primary', | 'preload-editor-button-primary', | ||
() => this.saveAll() | () => | ||
this.saveAll() | |||
), | ), | ||
this.createButton( | this.createButton( | ||
'Save Preload Only', | 'Save Preload Only', | ||
'', | '', | ||
() => this.savePreloadOnly() | () => | ||
this.savePreloadOnly() | |||
) | ) | ||
); | ); | ||
| Line 2,272: | Line 2,782: | ||
this.mountCodeMirror( | this.mountCodeMirror( | ||
textarea.get(0) | textarea.get(0) | ||
).catch(error => { | ).catch( | ||
error => { | |||
console.error( | |||
'Preload Editor CodeMirror:', | |||
error | |||
); | |||
this.setStatus( | |||
error.message || | |||
'Could not initialize CodeMirror.', | |||
'error' | |||
); | |||
} | |||
); | |||
}, | }, | ||
| Line 2,309: | Line 2,821: | ||
analysis.conditions; | analysis.conditions; | ||
/* | |||
* This is where newly typed variables | |||
* become configuration fields. | |||
* | |||
* For example: | |||
* | |||
* %%ZHS%% | |||
* | |||
* becomes: | |||
* | |||
* config.fields.ZHS = { | |||
* label: 'Zhs', | |||
* description: '', | |||
* type: 'text' | |||
* }; | |||
*/ | |||
this.state.config = | this.state.config = | ||
this.prepareConfigForVariables( | this.prepareConfigForVariables( | ||
| Line 2,321: | Line 2,849: | ||
/* | /* | ||
* PAGE is always available. | * PAGE is always available. | ||
*/ | */ | ||
const available = | const available = | ||
['PAGE'].concat( | ['PAGE'].concat( | ||
analysis.variables.filter( | analysis.variables.filter( | ||
name => name !== 'PAGE' | name => | ||
name !== 'PAGE' | |||
) | ) | ||
); | ); | ||
/* | |||
* If the selected variable | |||
* disappeared from the preload, | |||
* move selection to PAGE or | |||
* the first available variable. | |||
*/ | |||
if ( | if ( | ||
!available.includes( | !available.includes( | ||
this.state.selectedVariable | this.state | ||
.selectedVariable | |||
) | ) | ||
) { | ) { | ||
this.state.selectedVariable = | this.state | ||
available[0] || 'PAGE'; | .selectedVariable = | ||
available[0] || | |||
'PAGE'; | |||
} | } | ||
}, | }, | ||
renderAnalysis(container) { | renderAnalysis( | ||
container | |||
) { | |||
container.empty(); | container.empty(); | ||
| Line 2,374: | Line 2,911: | ||
const createMetric = | const createMetric = | ||
(value, label) => | ( | ||
value, | |||
label | |||
) => | |||
$('<div>') | $('<div>') | ||
.addClass( | .addClass( | ||
| Line 2,385: | Line 2,925: | ||
) | ) | ||
.text(value), | .text(value), | ||
$('<span>') | $('<span>') | ||
.addClass( | .addClass( | ||
| Line 2,399: | Line 2,940: | ||
: 'variables' | : 'variables' | ||
), | ), | ||
createMetric( | createMetric( | ||
conditionCount, | conditionCount, | ||
| Line 2,405: | Line 2,947: | ||
: 'condition markers' | : 'condition markers' | ||
), | ), | ||
createMetric( | createMetric( | ||
configuredCount, | configuredCount, | ||
| Line 2,418: | Line 2,961: | ||
if ( | if ( | ||
analysis.conditionVariables.length | analysis.conditionVariables | ||
.length | |||
) { | ) { | ||
container.append( | container.append( | ||
| Line 2,453: | Line 2,997: | ||
$('<li>') | $('<li>') | ||
.text(error) | .text(error) | ||
.appendTo(errors); | .appendTo( | ||
errors | |||
); | |||
} | } | ||
); | ); | ||
| Line 2,475: | Line 3,021: | ||
$('<li>') | $('<li>') | ||
.text(warning) | .text(warning) | ||
.appendTo(warnings); | .appendTo( | ||
warnings | |||
); | |||
} | } | ||
); | ); | ||
| Line 2,517: | Line 3,065: | ||
}, | }, | ||
/ | // ========================================================= | ||
// Variables | |||
// ========================================================= | |||
renderVariables( | |||
container | |||
renderVariables(container) { | ) { | ||
container.empty(); | container.empty(); | ||
| Line 2,537: | Line 3,085: | ||
analysis.variables.slice(); | analysis.variables.slice(); | ||
/* | |||
* PAGE is always shown first. | |||
*/ | |||
const ordered = ['PAGE']; | const ordered = ['PAGE']; | ||
variables.forEach(name => { | variables.forEach( | ||
name => { | |||
if ( | |||
name !== 'PAGE' && | |||
) { | !ordered.includes( | ||
ordered.push( | name | ||
) | |||
) { | |||
ordered.push(name); | |||
} | |||
} | |||
); | |||
/* | |||
* Existing configured fields which | |||
* are no longer used are also shown. | |||
* | |||
* This preserves the previous behaviour | |||
* where unused configuration is visible. | |||
*/ | |||
const fields = | |||
this.state.config && | |||
this.state.config.fields | |||
? this.state.config.fields | |||
: {}; | |||
Object.keys(fields).forEach( | |||
name => { | |||
const upper = | |||
name.toUpperCase(); | |||
if ( | |||
upper !== 'PAGE' && | |||
!ordered.includes( | |||
upper | |||
) | |||
) { | |||
ordered.push( | |||
upper | |||
); | |||
} | |||
} | } | ||
); | |||
const list = | const list = | ||
| Line 2,554: | Line 3,140: | ||
); | ); | ||
ordered.forEach(name => { | ordered.forEach( | ||
name => { | |||
const isSelected = | |||
name === | |||
this.state | |||
.selectedVariable; | |||
const isUsed = | |||
analysis.variables.includes( | |||
name | |||
); | |||
const item = | |||
$('<button>') | |||
.attr({ | |||
type: 'button', | |||
'data-variable': | |||
name | name | ||
) | }) | ||
.addClass( | |||
'preload-editor-variable-item' | |||
) | |||
.toggleClass( | |||
'is-selected', | |||
isSelected | |||
) | |||
.on( | |||
'click.preloadEditor', | |||
() => | |||
this.selectVariable( | |||
name | |||
) | |||
); | |||
const nameLabel = | |||
$('<span>') | |||
.addClass( | |||
'preload-editor-variable-name' | |||
) | |||
.text(name); | |||
item.append( | item.append( | ||
nameLabel | |||
); | ); | ||
if ( | |||
name === 'PAGE' | |||
) { | |||
item.append( | |||
$('<span>') | |||
.addClass( | |||
'preload-editor-badge' | |||
) | |||
.text( | |||
'AUTOMATIC' | |||
) | |||
); | |||
} else if ( | |||
!isUsed | |||
) { | |||
item.append( | |||
$('<span>') | |||
.addClass( | |||
'preload-editor-badge is-warning' | |||
) | |||
.text( | |||
'UNUSED' | |||
) | |||
); | |||
} | |||
list.append( | |||
item | |||
); | ); | ||
} | } | ||
); | |||
container.append( | |||
list | |||
); | |||
container.append(list | }, | ||
}, | |||
renderVariablesBySelector() { | renderVariablesBySelector() { | ||
| Line 2,649: | Line 3,239: | ||
}, | }, | ||
/ | // ========================================================= | ||
// Variable selection / configuration | |||
// ========================================================= | |||
selectVariable(name) { | selectVariable( | ||
name | |||
) { | |||
name = | name = | ||
String(name || '') | String(name || '') | ||
| Line 2,659: | Line 3,251: | ||
.toUpperCase(); | .toUpperCase(); | ||
if (!name) return; | if (!name) { | ||
return; | |||
} | |||
const available = | const available = | ||
| Line 2,670: | Line 3,264: | ||
).filter( | ).filter( | ||
variable => | variable => | ||
variable !== 'PAGE' | variable !== | ||
'PAGE' | |||
) | ) | ||
); | ); | ||
if (!available.includes(name)) { | /* | ||
* Also allow selecting an existing | |||
* unused configuration field. | |||
*/ | |||
const fields = | |||
this.state.config && | |||
this.state.config.fields | |||
? this.state.config.fields | |||
: {}; | |||
Object.keys(fields).forEach( | |||
fieldName => { | |||
const upper = | |||
fieldName.toUpperCase(); | |||
if ( | |||
!available.includes( | |||
upper | |||
) | |||
) { | |||
available.push( | |||
upper | |||
); | |||
} | |||
} | |||
); | |||
if ( | |||
!available.includes( | |||
name | |||
) | |||
) { | |||
return; | return; | ||
} | } | ||
| Line 2,682: | Line 3,308: | ||
this.renderVariablesBySelector(); | this.renderVariablesBySelector(); | ||
this.renderVariableConfigurationBySelector(); | this.renderVariableConfigurationBySelector(); | ||
}, | }, | ||
| Line 2,703: | Line 3,329: | ||
}, | }, | ||
renderVariableConfiguration(container) { | renderVariableConfiguration( | ||
container | |||
) { | |||
container.empty(); | container.empty(); | ||
| Line 2,731: | Line 3,359: | ||
); | ); | ||
if (name === 'PAGE') { | if ( | ||
name === 'PAGE' | |||
) { | |||
container.append( | container.append( | ||
this.createAutomaticPageCard( | this.createAutomaticPageCard( | ||
| Line 2,750: | Line 3,380: | ||
}, | }, | ||
/ | // ========================================================= | ||
// Automatic PAGE | |||
// ========================================================= | |||
createAutomaticPageCard( | |||
field | |||
) { | |||
createAutomaticPageCard(field) { | |||
const card = | const card = | ||
$('<div>') | $('<div>') | ||
.addClass( | .addClass( | ||
'preload-editor-variable preload-editor-variable-automatic' | 'preload-editor-variable ' + | ||
'preload-editor-variable-automatic' | |||
) | ) | ||
.attr( | .attr( | ||
| Line 2,775: | Line 3,405: | ||
header.append( | header.append( | ||
$('<h3>').text('PAGE'), | $('<h3>').text( | ||
'PAGE' | |||
), | |||
$('<span>') | $('<span>') | ||
.addClass( | .addClass( | ||
'preload-editor-badge' | 'preload-editor-badge' | ||
) | ) | ||
.text('AUTOMATIC') | .text( | ||
'AUTOMATIC' | |||
) | |||
); | ); | ||
card.append(header); | card.append( | ||
header | |||
); | |||
$('<p>') | $('<p>') | ||
| Line 2,792: | Line 3,429: | ||
'PAGE is a precompiled automatic variable. It is always available and is not edited like normal variables.' | 'PAGE is a precompiled automatic variable. It is always available and is not edited like normal variables.' | ||
) | ) | ||
.appendTo(card); | .appendTo( | ||
card | |||
); | |||
const label = | const label = | ||
this.textInput( | this.textInput( | ||
field.label || 'Page Name' | field.label || | ||
'Page Name' | |||
).prop( | ).prop( | ||
'disabled', | 'disabled', | ||
| Line 2,804: | Line 3,444: | ||
const description = | const description = | ||
this.textarea( | this.textarea( | ||
field.description || 'Name of the page.' | field.description || | ||
'Name of the page.' | |||
) | ) | ||
.addClass( | .addClass( | ||
| Line 2,816: | Line 3,457: | ||
const type = | const type = | ||
this.textInput( | this.textInput( | ||
field.type || 'page' | field.type || | ||
'page' | |||
).prop( | ).prop( | ||
'disabled', | 'disabled', | ||
| Line 2,824: | Line 3,466: | ||
const required = | const required = | ||
this.checkbox( | this.checkbox( | ||
field.required !== false | field.required !== | ||
false | |||
).prop( | ).prop( | ||
'disabled', | 'disabled', | ||
| Line 2,832: | Line 3,475: | ||
const namespace = | const namespace = | ||
this.textInput( | this.textInput( | ||
field.namespace || 'Main' | field.namespace || | ||
'Main' | |||
).prop( | ).prop( | ||
'disabled', | 'disabled', | ||
| Line 2,840: | Line 3,484: | ||
const allowNew = | const allowNew = | ||
this.checkbox( | this.checkbox( | ||
field['allow-new'] === true | field['allow-new'] === | ||
true | |||
).prop( | ).prop( | ||
'disabled', | 'disabled', | ||
| Line 2,851: | Line 3,496: | ||
label | label | ||
), | ), | ||
this.fieldRow( | this.fieldRow( | ||
'Description', | 'Description', | ||
description | description | ||
), | ), | ||
this.fieldRow( | this.fieldRow( | ||
'Type', | 'Type', | ||
type | type | ||
), | ), | ||
this.fieldRow( | this.fieldRow( | ||
'Required', | 'Required', | ||
required | required | ||
), | ), | ||
this.fieldRow( | this.fieldRow( | ||
'Namespace', | 'Namespace', | ||
namespace | namespace | ||
), | ), | ||
this.fieldRow( | this.fieldRow( | ||
'Allow new page', | 'Allow new page', | ||
| Line 2,875: | Line 3,525: | ||
return card; | return card; | ||
}, | }, | ||
// ========================================================= | |||
// Normal variable card | |||
// ========================================================= | |||
createVariableCard( | createVariableCard( | ||
| Line 2,898: | Line 3,552: | ||
header.append( | header.append( | ||
$('<h3>').text(name) | $('<h3>').text( | ||
name | |||
) | |||
); | ); | ||
if ( | |||
!analysis.variables.includes( | |||
name | |||
) | |||
!analysis.variables.includes(name) | |||
) { | ) { | ||
header.append( | header.append( | ||
| Line 2,917: | Line 3,567: | ||
'preload-editor-badge is-warning' | 'preload-editor-badge is-warning' | ||
) | ) | ||
.text('UNUSED') | .text( | ||
'UNUSED' | |||
) | |||
); | ); | ||
} | } | ||
card.append(header); | card.append( | ||
header | |||
); | |||
// Label | |||
const label = | const label = | ||
| Line 2,935: | Line 3,591: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 2,945: | Line 3,602: | ||
) | ) | ||
); | ); | ||
// Description | |||
const description = | const description = | ||
this.textarea( | this.textarea( | ||
field.description || '' | field.description || | ||
'' | |||
).addClass( | ).addClass( | ||
'preload-editor-small-textarea' | 'preload-editor-small-textarea' | ||
| Line 2,960: | Line 3,620: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 2,970: | Line 3,631: | ||
) | ) | ||
); | ); | ||
// Type | |||
const types = [ | const types = [ | ||
[ | [ | ||
' | 'text', | ||
' | 'Text' | ||
], | ], | ||
['page', 'Page'], | [ | ||
['user', 'User'], | 'textarea', | ||
['date', 'Date'] | 'Textarea' | ||
], | |||
[ | |||
'number', | |||
'Number' | |||
], | |||
[ | |||
'boolean', | |||
'Boolean' | |||
], | |||
[ | |||
'select', | |||
'Select' | |||
], | |||
[ | |||
'multiselect', | |||
'Multi-select' | |||
], | |||
[ | |||
'page', | |||
'Page' | |||
], | |||
[ | |||
'user', | |||
'User' | |||
], | |||
[ | |||
'date', | |||
'Date' | |||
] | |||
]; | ]; | ||
| Line 2,989: | Line 3,676: | ||
this.select( | this.select( | ||
types.map( | types.map( | ||
([value, label]) => ({ | ( | ||
[ | |||
value, | |||
label | |||
] | |||
) => ({ | |||
value, | value, | ||
label | label | ||
}) | }) | ||
), | ), | ||
field.type || 'text' | field.type || | ||
'text' | |||
); | ); | ||
| Line 3,006: | Line 3,699: | ||
this.renderVariableConfigurationBySelector(); | this.renderVariableConfigurationBySelector(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,016: | Line 3,710: | ||
) | ) | ||
); | ); | ||
// Required | |||
const required = | const required = | ||
this.checkbox( | this.checkbox( | ||
field.required === true | field.required === | ||
true | |||
); | ); | ||
| Line 3,031: | Line 3,728: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,041: | Line 3,739: | ||
) | ) | ||
); | ); | ||
// Placeholder | |||
const placeholder = | const placeholder = | ||
this.textInput( | this.textInput( | ||
field.placeholder || '' | field.placeholder || | ||
'' | |||
); | ); | ||
| Line 3,061: | Line 3,762: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,071: | Line 3,773: | ||
) | ) | ||
); | ); | ||
// Default | |||
const defaultInput = | const defaultInput = | ||
this.textInput( | this.textInput( | ||
field.default === undefined | field.default === | ||
undefined | |||
? '' | ? '' | ||
: field.default | : field.default | ||
| Line 3,093: | Line 3,798: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,104: | Line 3,810: | ||
) | ) | ||
); | ); | ||
// Textarea options | |||
if ( | if ( | ||
field.type === 'textarea' | field.type === | ||
'textarea' | |||
) { | ) { | ||
const maxRows = | const maxRows = | ||
this.textInput( | this.textInput( | ||
field.maxRows || '' | field.maxRows || | ||
'' | |||
).attr( | ).attr( | ||
'type', | 'type', | ||
| Line 3,138: | Line 3,848: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,149: | Line 3,860: | ||
); | ); | ||
} | } | ||
// Select / multiselect | |||
if ( | if ( | ||
field.type === 'select' || | field.type === | ||
field.type === 'multiselect' | 'select' || | ||
field.type === | |||
'multiselect' | |||
) { | ) { | ||
this.renderSelectConfiguration( | this.renderSelectConfiguration( | ||
| Line 3,162: | Line 3,877: | ||
return card; | return card; | ||
}, | }, | ||
// ========================================================= | |||
// Select configuration | |||
// ========================================================= | |||
renderSelectConfiguration( | renderSelectConfiguration( | ||
| Line 3,169: | Line 3,888: | ||
const source = | const source = | ||
this.textInput( | this.textInput( | ||
field.source || '', | field.source || | ||
'', | |||
'category:Example' | 'category:Example' | ||
); | ); | ||
| Line 3,180: | Line 3,900: | ||
if (value) { | if (value) { | ||
field.source = value; | field.source = | ||
value; | |||
} else { | } else { | ||
delete field.source; | delete field.source; | ||
| Line 3,186: | Line 3,907: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,200: | Line 3,922: | ||
const sourcePrefix = | const sourcePrefix = | ||
this.textInput( | this.textInput( | ||
field['source-prefix'] || '' | field[ | ||
'source-prefix' | |||
] || '' | |||
); | ); | ||
| Line 3,210: | Line 3,934: | ||
if (value) { | if (value) { | ||
field['source-prefix'] = | field[ | ||
'source-prefix' | |||
] = value; | |||
} else { | } else { | ||
delete field[ | delete field[ | ||
| Line 3,219: | Line 3,944: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,235: | Line 3,961: | ||
field.exclude && | field.exclude && | ||
field.exclude.category || | field.exclude.category || | ||
'' | |||
); | ); | ||
| Line 3,248: | Line 3,974: | ||
Object.assign( | Object.assign( | ||
{}, | {}, | ||
field.exclude || {}, | field.exclude || | ||
{}, | |||
{ | { | ||
category: | category: | ||
| Line 3,257: | Line 3,984: | ||
field.exclude | field.exclude | ||
) { | ) { | ||
delete field.exclude.category; | delete field | ||
.exclude | |||
.category; | |||
if ( | if ( | ||
| Line 3,269: | Line 3,998: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,286: | Line 4,016: | ||
'Value transformation' | 'Value transformation' | ||
), | ), | ||
this.createTransformControls( | this.createTransformControls( | ||
field, | field, | ||
| Line 3,294: | Line 4,025: | ||
const optionsValue = | const optionsValue = | ||
Array.isArray(field.options) | Array.isArray( | ||
field.options | |||
) | |||
? field.options | ? field.options | ||
.map(option => { | .map( | ||
option => { | |||
if ( | |||
typeof option === | |||
'string' | |||
return option; | ) { | ||
return option; | |||
} | |||
return ( | |||
String( | |||
option.value || | |||
'' | |||
) + | |||
' | ' + | |||
String( | |||
option.label || | |||
option.value || | |||
'' | |||
) | |||
); | |||
} | } | ||
) | |||
.join('\n') | .join('\n') | ||
: ''; | : ''; | ||
| Line 3,338: | Line 4,073: | ||
line.trim() | line.trim() | ||
) | ) | ||
.filter(Boolean); | .filter( | ||
Boolean | |||
); | |||
if (!lines.length) { | if (!lines.length) { | ||
| Line 3,344: | Line 4,081: | ||
} else { | } else { | ||
field.options = | field.options = | ||
lines.map(line => { | lines.map( | ||
line => { | |||
const parts = | |||
line.split( | |||
); | /\s*\|\s*/ | ||
); | |||
if ( | |||
parts.length === | |||
1 | |||
) { | |||
return parts[0]; | |||
} | |||
return { | |||
value: | |||
parts[0], | |||
label: | |||
parts | |||
.slice( | |||
1 | |||
) | |||
.join( | |||
' | ' | |||
) | |||
}; | |||
} | } | ||
); | |||
} | } | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 3,383: | Line 4,126: | ||
); | ); | ||
}, | }, | ||
// ========================================================= | |||
// Transformation controls | |||
// ========================================================= | |||
createTransformControls( | createTransformControls( | ||
| Line 3,397: | Line 4,144: | ||
$('<h4>') | $('<h4>') | ||
.text(title) | .text(title) | ||
.appendTo(wrapper); | .appendTo( | ||
wrapper | |||
); | |||
const existing = | const existing = | ||
| Line 3,427: | Line 4,176: | ||
}, | }, | ||
{ | { | ||
value: 'replace', | value: | ||
label: 'Replace' | 'replace', | ||
label: | |||
'Replace' | |||
} | } | ||
], | ], | ||
existing.transform || '' | existing.transform || | ||
'' | |||
); | ); | ||
const prefix = | const prefix = | ||
this.textInput( | this.textInput( | ||
existing.prefix || '' | existing.prefix || | ||
'' | |||
); | ); | ||
const suffix = | const suffix = | ||
this.textInput( | this.textInput( | ||
existing.suffix || '' | existing.suffix || | ||
'' | |||
); | ); | ||
const from = | const from = | ||
this.textInput( | this.textInput( | ||
existing.from || '' | existing.from || | ||
'' | |||
); | ); | ||
const to = | const to = | ||
this.textInput( | this.textInput( | ||
existing.to || '' | existing.to || | ||
'' | |||
); | ); | ||
| Line 3,500: | Line 4,256: | ||
fromRow.toggle( | fromRow.toggle( | ||
type === 'replace' | type === | ||
'replace' | |||
); | ); | ||
toRow.toggle( | toRow.toggle( | ||
type === 'replace' | type === | ||
'replace' | |||
); | ); | ||
}; | }; | ||
| Line 3,513: | Line 4,271: | ||
transform.val(); | transform.val(); | ||
let object = null; | let object = | ||
null; | |||
if ( | if ( | ||
| Line 3,521: | Line 4,280: | ||
) { | ) { | ||
object = { | object = { | ||
transform: type, | transform: | ||
type, | |||
prefix: | prefix: | ||
prefix.val() | prefix.val() | ||
| Line 3,531: | Line 4,291: | ||
) { | ) { | ||
object = { | object = { | ||
transform: type, | transform: | ||
type, | |||
suffix: | suffix: | ||
suffix.val() | suffix.val() | ||
}; | }; | ||
} else if ( | } else if ( | ||
type === 'replace' && | type === | ||
'replace' && | |||
from.val() | from.val() | ||
) { | ) { | ||
object = { | object = { | ||
transform: type, | transform: | ||
from: from.val(), | type, | ||
to: to.val() | from: | ||
from.val(), | |||
to: | |||
to.val() | |||
}; | }; | ||
} | } | ||
| Line 3,550: | Line 4,315: | ||
object; | object; | ||
} else { | } else { | ||
delete field[property]; | delete field[ | ||
property | |||
]; | |||
} | } | ||
this.markDirty(); | this.markDirty(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
}; | }; | ||
| Line 3,570: | Line 4,338: | ||
from, | from, | ||
to | to | ||
].forEach(input => { | ].forEach( | ||
input => { | |||
input.on( | |||
'input.preloadEditor', | |||
update | |||
); | |||
} | |||
); | |||
controls.append( | controls.append( | ||
| Line 3,597: | Line 4,367: | ||
}, | }, | ||
// ========================================================= | |||
// Config output | // Config output | ||
// ========================================================= | |||
getCleanConfig() { | getCleanConfig() { | ||
| Line 3,612: | Line 4,384: | ||
const orderedFields = {}; | const orderedFields = {}; | ||
/ | /* | ||
* PAGE is always first. | |||
*/ | |||
orderedFields.PAGE = | orderedFields.PAGE = | ||
this.getAutomaticPageField(); | this.getAutomaticPageField(); | ||
| Line 3,622: | Line 4,396: | ||
'PAGE' | 'PAGE' | ||
) | ) | ||
.forEach(name => { | .forEach( | ||
name => { | |||
orderedFields[name] = | |||
fields[name]; | |||
} | |||
); | |||
config.fields = | config.fields = | ||
| Line 3,641: | Line 4,417: | ||
}, | }, | ||
renderConfigPreview(container) { | renderConfigPreview( | ||
container | |||
) { | |||
if (container) { | if (container) { | ||
container.text( | container.text( | ||
| Line 3,669: | Line 4,447: | ||
markDirty() { | markDirty() { | ||
this.state.dirty = true; | this.state.dirty = | ||
true; | |||
}, | }, | ||
// ========================================================= | |||
// Save | // Save | ||
// ========================================================= | |||
async savePreloadOnly() { | async savePreloadOnly() { | ||
| Line 3,678: | Line 4,459: | ||
String( | String( | ||
this.state.preloadTitle || | this.state.preloadTitle || | ||
'' | |||
).trim(); | ).trim(); | ||
| Line 3,688: | Line 4,469: | ||
); | ); | ||
if (!result) return; | if (!result) { | ||
return; | |||
} | |||
title = result; | title = result; | ||
this.state.preloadTitle = | this.state.preloadTitle = | ||
title; | title; | ||
| Line 3,748: | Line 4,532: | ||
} | } | ||
); | ); | ||
} catch (error) { | } catch ( | ||
error | |||
) { | |||
console.error( | console.error( | ||
'Preload Editor save:', | 'Preload Editor save:', | ||
| Line 3,774: | Line 4,560: | ||
String( | String( | ||
this.state.preloadTitle || | this.state.preloadTitle || | ||
'' | |||
).trim(); | ).trim(); | ||
| Line 3,784: | Line 4,570: | ||
); | ); | ||
if (!result) return; | if (!result) { | ||
return; | |||
} | |||
title = result; | title = result; | ||
this.state.preloadTitle = | this.state.preloadTitle = | ||
title; | title; | ||
| Line 3,793: | Line 4,582: | ||
this.state.preloadContent = | this.state.preloadContent = | ||
this.getEditorContent(); | this.getEditorContent(); | ||
/* | |||
* Ensure the latest editor content is | |||
* represented in the config before saving. | |||
*/ | |||
this.reanalyze(); | |||
const validation = | const validation = | ||
| Line 3,868: | Line 4,663: | ||
} | } | ||
); | ); | ||
} catch (error) { | } catch ( | ||
error | |||
) { | |||
console.error( | console.error( | ||
'Preload Editor save:', | 'Preload Editor save:', | ||
| Line 3,897: | Line 4,694: | ||
String( | String( | ||
this.state.preloadTitle || | this.state.preloadTitle || | ||
'' | |||
).trim(); | ).trim(); | ||
| Line 3,908: | Line 4,705: | ||
if ( | if ( | ||
title && | title && | ||
!/\/Preload$/i.test(title) | !/\/Preload$/i.test( | ||
title | |||
) | |||
) { | ) { | ||
warnings.push( | warnings.push( | ||
| Line 3,952: | Line 4,751: | ||
content | content | ||
) | ) | ||
); | |||
/* | |||
* Synchronize configuration one last | |||
* time before validation. | |||
*/ | |||
this.state.config = | |||
this.prepareConfigForVariables( | |||
this.state.config || | |||
this.emptyConfig(), | |||
analysis | |||
); | ); | ||
| Line 3,983: | Line 4,793: | ||
depth--; | depth--; | ||
if (depth < 0) { | if ( | ||
depth < 0 | |||
) { | |||
errors.push( | errors.push( | ||
'Found @endif without a matching @if.' | 'Found @endif without a matching @if.' | ||
| Line 3,996: | Line 4,808: | ||
'else' | 'else' | ||
) { | ) { | ||
if (depth === 0) { | if ( | ||
depth === 0 | |||
) { | |||
errors.push( | errors.push( | ||
'@' + | '@' + | ||
| Line 4,027: | Line 4,841: | ||
this.setStatus( | this.setStatus( | ||
validation.errors.join(' '), | validation.errors.join( | ||
' ' | |||
), | |||
'error' | 'error' | ||
); | ); | ||
mw.notify( | mw.notify( | ||
validation.errors.join('\n'), | validation.errors.join( | ||
'\n' | |||
), | |||
{ | { | ||
type: 'error' | type: 'error' | ||
| Line 4,045: | Line 4,863: | ||
$(function () { | $(function () { | ||
if ( | if ( | ||
mw.config.get('wgPageName') !== | mw.config.get( | ||
'wgPageName' | |||
) !== | |||
'Petit_Planet:Preload_Editor' | 'Petit_Planet:Preload_Editor' | ||
) { | ) { | ||