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.4.0', | ||
state: { | state: { | ||
| Line 12: | Line 12: | ||
variables: [], | variables: [], | ||
conditions: [], | conditions: [], | ||
selectedVariable: 'PAGE', | |||
mode: 'idle', | mode: 'idle', | ||
dirty: false | dirty: false | ||
| Line 181: | Line 182: | ||
// Config | // Config | ||
getAutomaticPageField() { | |||
return { | |||
label: 'Page Name', | |||
description: 'Name of the page.', | |||
type: 'page', | |||
required: true, | |||
namespace: 'Main', | |||
'allow-new': true | |||
}; | |||
}, | |||
emptyConfig() { | emptyConfig() { | ||
| Line 188: | Line 200: | ||
}, | }, | ||
fields: { | fields: { | ||
PAGE: | PAGE: this.getAutomaticPageField() | ||
} | } | ||
}; | }; | ||
| Line 225: | Line 230: | ||
} | } | ||
// PAGE is always a special, automatic field. | |||
// Its configuration is precompiled and only displayed | |||
// in the editor for reference. | |||
config.fields.PAGE = | |||
this.getAutomaticPageField(); | |||
return config; | return config; | ||
| Line 268: | Line 268: | ||
const fields = {}; | const fields = {}; | ||
fields.PAGE = | // PAGE is always automatic/precompiled. | ||
fields.PAGE = this.getAutomaticPageField(); | |||
analysis.variables.forEach(name => { | analysis.variables.forEach(name => { | ||
| Line 291: | Line 285: | ||
const upper = name.toUpperCase(); | const upper = name.toUpperCase(); | ||
if (!fields[upper]) { | if ( | ||
upper !== 'PAGE' && | |||
!fields[upper] | |||
) { | |||
fields[upper] = existing[name]; | fields[upper] = existing[name]; | ||
} | } | ||
| Line 956: | Line 953: | ||
'preload-editor-tools' | 'preload-editor-tools' | ||
); | ); | ||
const wrapGroup = $('<div>') | const wrapGroup = $('<div>') | ||
| Line 973: | Line 968: | ||
) | ) | ||
); | ); | ||
const variableGroup = $('<div>') | const variableGroup = $('<div>') | ||
| Line 1,008: | Line 1,001: | ||
) | ) | ||
); | ); | ||
const conditionGroup = $('<div>') | const conditionGroup = $('<div>') | ||
| Line 1,378: | Line 1,369: | ||
this.reanalyze(); | this.reanalyze(); | ||
this.renderVariablesBySelector(); | this.renderVariablesBySelector(); | ||
this.renderVariableConfigurationBySelector(); | |||
this.renderAnalysisBySelector(); | this.renderAnalysisBySelector(); | ||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
| Line 1,787: | Line 1,779: | ||
variables: ['PAGE'], | variables: ['PAGE'], | ||
conditions: [], | conditions: [], | ||
selectedVariable: 'PAGE', | |||
mode: 'new', | mode: 'new', | ||
dirty: true | dirty: true | ||
| Line 1,837: | Line 1,830: | ||
variables: ['PAGE'], | variables: ['PAGE'], | ||
conditions: [], | conditions: [], | ||
selectedVariable: 'PAGE', | |||
mode: 'new', | mode: 'new', | ||
dirty: true | dirty: true | ||
| Line 2,052: | Line 2,046: | ||
conditions: | conditions: | ||
analysis.conditions, | analysis.conditions, | ||
selectedVariable: | |||
analysis.variables.includes('PAGE') | |||
? 'PAGE' | |||
: ( | |||
analysis.variables[0] || | |||
'PAGE' | |||
), | |||
mode: 'edit', | mode: 'edit', | ||
dirty: false | dirty: false | ||
| Line 2,144: | Line 2,145: | ||
left.append(editorSection); | left.append(editorSection); | ||
/* | |||
* RIGHT PANEL | |||
* | |||
* Analysis is intentionally first. | |||
* Variables are now only a live list. | |||
* One configuration is displayed at a time. | |||
*/ | |||
const analysisSection = | const analysisSection = | ||
| Line 2,173: | Line 2,182: | ||
variablesSection.append( | variablesSection.append( | ||
variableContainer | variableContainer | ||
); | |||
const configurationSection = | |||
this.createSection( | |||
'Variable configuration' | |||
); | |||
const configurationContainer = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-variable-configuration' | |||
); | |||
configurationSection.append( | |||
configurationContainer | |||
); | ); | ||
| Line 2,212: | Line 2,236: | ||
analysisSection, | analysisSection, | ||
variablesSection, | variablesSection, | ||
configurationSection, | |||
configSection, | configSection, | ||
actions | actions | ||
| Line 2,233: | Line 2,258: | ||
this.renderAnalysis( | this.renderAnalysis( | ||
analysisContainer | analysisContainer | ||
); | |||
this.renderVariableConfiguration( | |||
configurationContainer | |||
); | ); | ||
| Line 2,289: | Line 2,318: | ||
this.analysis = | this.analysis = | ||
analysis; | analysis; | ||
/* | |||
* PAGE is always available. | |||
* If the selected variable disappears, | |||
* move the focus to PAGE or the first | |||
* currently-used variable. | |||
*/ | |||
const available = | |||
['PAGE'].concat( | |||
analysis.variables.filter( | |||
name => name !== 'PAGE' | |||
) | |||
); | |||
if ( | |||
!available.includes( | |||
this.state.selectedVariable | |||
) | |||
) { | |||
this.state.selectedVariable = | |||
available[0] || 'PAGE'; | |||
} | |||
}, | }, | ||
| Line 2,307: | Line 2,358: | ||
const conditionCount = | const conditionCount = | ||
analysis.conditions.length; | analysis.conditions.length; | ||
const configuredCount = | |||
Object.keys( | |||
this.state.config && | |||
this.state.config.fields | |||
? this.state.config.fields | |||
: {} | |||
).length; | |||
const summary = | const summary = | ||
| Line 2,313: | Line 2,372: | ||
'preload-editor-analysis-summary' | 'preload-editor-analysis-summary' | ||
); | ); | ||
const createMetric = | |||
(value, label) => | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-analysis-metric' | |||
) | |||
.append( | |||
$('<strong>') | |||
.addClass( | |||
'preload-editor-analysis-value' | |||
) | |||
.text(value), | |||
$('<span>') | |||
.addClass( | |||
'preload-editor-analysis-label' | |||
) | |||
.text(label) | |||
); | |||
summary.append( | summary.append( | ||
createMetric( | |||
variableCount | variableCount, | ||
' variable' | variableCount === 1 | ||
? 'variable' | |||
: 'variables' | |||
), | |||
createMetric( | |||
conditionCount, | |||
conditionCount === 1 | |||
? 'condition marker' | |||
: 'condition markers' | |||
), | ), | ||
createMetric( | |||
configuredCount, | |||
configuredCount === 1 | |||
? 'configured field' | |||
: 'configured fields' | |||
) | ) | ||
); | ); | ||
| Line 2,439: | Line 2,517: | ||
}, | }, | ||
/* | |||
* Variable list | |||
* | |||
* This replaces the old behaviour where every | |||
* variable rendered its entire configuration card. | |||
*/ | |||
renderVariables(container) { | renderVariables(container) { | ||
container.empty(); | container.empty(); | ||
| Line 2,464: | Line 2,548: | ||
}); | }); | ||
const list = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-variable-list' | |||
); | |||
ordered.forEach(name => { | |||
const isSelected = | |||
name === | |||
this.state.selectedVariable; | |||
const isUsed = | |||
const | analysis.variables.includes( | ||
name | |||
name | |||
); | ); | ||
this. | const item = | ||
$('<button>') | |||
.attr({ | |||
container.append( | type: 'button', | ||
this. | 'data-variable': name | ||
}) | |||
.addClass( | |||
analysis | 'preload-editor-variable-item' | ||
) | ) | ||
); | .toggleClass( | ||
}); | 'is-selected', | ||
}, | isSelected | ||
) | |||
.on( | |||
const container = | 'click.preloadEditor', | ||
this.root && | () => | ||
this.root.find( | this.selectVariable( | ||
'.preload-editor- | name | ||
); | ) | ||
); | |||
if ( | |||
container && | const nameLabel = | ||
container.length | $('<span>') | ||
) { | .addClass( | ||
this. | 'preload-editor-variable-name' | ||
container | ) | ||
); | .text(name); | ||
} | |||
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); | |||
if (!ordered.length) { | |||
container.append( | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-empty' | |||
) | |||
.text( | |||
'No variables found in the preload.' | |||
) | |||
); | |||
} | |||
}, | |||
renderVariablesBySelector() { | |||
const container = | |||
this.root && | |||
this.root.find( | |||
'.preload-editor-variables' | |||
); | |||
if ( | |||
container && | |||
container.length | |||
) { | |||
this.renderVariables( | |||
container | |||
); | |||
} | |||
}, | |||
/* | |||
* Select a variable from the list and display | |||
* only its configuration. | |||
*/ | |||
selectVariable(name) { | |||
name = | |||
String(name || '') | |||
.trim() | |||
.toUpperCase(); | |||
if (!name) return; | |||
const available = | |||
['PAGE'].concat( | |||
( | |||
this.analysis && | |||
this.analysis.variables | |||
? this.analysis.variables | |||
: [] | |||
).filter( | |||
variable => | |||
variable !== 'PAGE' | |||
) | |||
); | |||
if (!available.includes(name)) { | |||
return; | |||
} | |||
this.state.selectedVariable = | |||
name; | |||
this.renderVariablesBySelector(); | |||
this.renderVariableConfigurationBySelector(); | |||
const configuration = | |||
this.root && | |||
this.root.find( | |||
'.preload-editor-variable-configuration' | |||
); | |||
if ( | |||
configuration && | |||
configuration.length | |||
) { | |||
const element = | |||
configuration.get(0); | |||
if (element) { | |||
element.scrollIntoView({ | |||
behavior: 'smooth', | |||
block: 'nearest' | |||
}); | |||
} | |||
} | |||
}, | |||
renderVariableConfigurationBySelector() { | |||
const container = | |||
this.root && | |||
this.root.find( | |||
'.preload-editor-variable-configuration' | |||
); | |||
if ( | |||
container && | |||
container.length | |||
) { | |||
this.renderVariableConfiguration( | |||
container | |||
); | |||
} | |||
}, | |||
renderVariableConfiguration(container) { | |||
container.empty(); | |||
const analysis = | |||
this.analysis || | |||
this.analyzePreload( | |||
this.extractIncludeonly( | |||
this.getEditorContent() | |||
) | |||
); | |||
const name = | |||
this.state.selectedVariable || | |||
'PAGE'; | |||
const fields = | |||
this.state.config && | |||
this.state.config.fields | |||
? this.state.config.fields | |||
: {}; | |||
const field = | |||
fields[name] || | |||
this.createDefaultField( | |||
name, | |||
analysis | |||
); | |||
if (name === 'PAGE') { | |||
container.append( | |||
this.createAutomaticPageCard( | |||
field | |||
) | |||
); | |||
return; | |||
} | |||
container.append( | |||
this.createVariableCard( | |||
name, | |||
field, | |||
analysis | |||
) | |||
); | |||
}, | |||
/* | |||
* PAGE is precompiled/automatic. | |||
* | |||
* It remains selectable so the user can inspect | |||
* what PAGE represents, but its configuration | |||
* cannot be edited. | |||
*/ | |||
createAutomaticPageCard(field) { | |||
const card = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-variable preload-editor-variable-automatic' | |||
) | |||
.attr( | |||
'data-variable', | |||
'PAGE' | |||
); | |||
const header = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-variable-header' | |||
); | |||
header.append( | |||
$('<h3>').text('PAGE'), | |||
$('<span>') | |||
.addClass( | |||
'preload-editor-badge' | |||
) | |||
.text('AUTOMATIC') | |||
); | |||
card.append(header); | |||
$('<p>') | |||
.addClass( | |||
'preload-editor-analysis-note' | |||
) | |||
.text( | |||
'PAGE is a precompiled automatic variable. It is always available and is not edited like normal variables.' | |||
) | |||
.appendTo(card); | |||
const label = | |||
this.textInput( | |||
field.label || 'Page Name' | |||
).prop( | |||
'disabled', | |||
true | |||
); | |||
const description = | |||
this.textarea( | |||
field.description || 'Name of the page.' | |||
) | |||
.addClass( | |||
'preload-editor-small-textarea' | |||
) | |||
.prop( | |||
'disabled', | |||
true | |||
); | |||
const type = | |||
this.textInput( | |||
field.type || 'page' | |||
).prop( | |||
'disabled', | |||
true | |||
); | |||
const required = | |||
this.checkbox( | |||
field.required !== false | |||
).prop( | |||
'disabled', | |||
true | |||
); | |||
const namespace = | |||
this.textInput( | |||
field.namespace || 'Main' | |||
).prop( | |||
'disabled', | |||
true | |||
); | |||
const allowNew = | |||
this.checkbox( | |||
field['allow-new'] === true | |||
).prop( | |||
'disabled', | |||
true | |||
); | |||
card.append( | |||
this.fieldRow( | |||
'Label', | |||
label | |||
), | |||
this.fieldRow( | |||
'Description', | |||
description | |||
), | |||
this.fieldRow( | |||
'Type', | |||
type | |||
), | |||
this.fieldRow( | |||
'Required', | |||
required | |||
), | |||
this.fieldRow( | |||
'Namespace', | |||
namespace | |||
), | |||
this.fieldRow( | |||
'Allow new page', | |||
allowNew | |||
) | |||
); | |||
return card; | |||
}, | }, | ||
| Line 2,644: | Line 3,025: | ||
this.markDirty(); | this.markDirty(); | ||
this.renderVariableConfigurationBySelector(); | |||
this.renderConfigPreviewBySelector(); | this.renderConfigPreviewBySelector(); | ||
} | } | ||
| Line 2,662: | Line 3,036: | ||
) | ) | ||
); | ); | ||
const required = | const required = | ||
| Line 3,359: | Line 3,632: | ||
const orderedFields = {}; | const orderedFields = {}; | ||
// PAGE is always first and automatic. | |||
orderedFields.PAGE = | |||
this.getAutomaticPageField(); | |||
Object.keys(fields) | Object.keys(fields) | ||