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 30: | Line 30: | ||
}, | }, | ||
// API | // API | ||
async getPage(title) { | async getPage(title) { | ||
| Line 115: | Line 113: | ||
}, | }, | ||
// Preload parsing | // Preload parsing | ||
extractIncludeonly(source) { | extractIncludeonly(source) { | ||
| Line 154: | Line 150: | ||
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 169: | Line 164: | ||
expression: | expression: | ||
match[2].trim(), | match[2].trim(), | ||
index: | index: match.index | ||
}); | }); | ||
} | } | ||
| Line 183: | Line 177: | ||
condition => { | condition => { | ||
if ( | if ( | ||
condition.type !== 'if' && | condition.type !== | ||
condition.type !== 'elseif' | 'if' && | ||
condition.type !== | |||
'elseif' | |||
) { | ) { | ||
return; | return; | ||
| Line 217: | Line 213: | ||
return { | return { | ||
variables: | variables: | ||
this.findVariables(content), | this.findVariables( | ||
content | |||
), | |||
conditions: | conditions: | ||
this.findConditions(content), | this.findConditions( | ||
content | |||
), | |||
conditionVariables: | conditionVariables: | ||
| Line 229: | Line 229: | ||
}, | }, | ||
// Config | // Config | ||
getAutomaticPageField() { | getAutomaticPageField() { | ||
return { | return { | ||
label: 'Page Name', | label: 'Page Name', | ||
description: | description: 'Name of the page.', | ||
type: 'page', | type: 'page', | ||
required: true, | required: true, | ||
| Line 264: | Line 261: | ||
Array.isArray(config) | Array.isArray(config) | ||
) { | ) { | ||
config = this.emptyConfig(); | config = | ||
this.emptyConfig(); | |||
} | } | ||
| Line 271: | Line 269: | ||
typeof config.settings !== | typeof config.settings !== | ||
'object' || | 'object' || | ||
Array.isArray(config.settings) | Array.isArray( | ||
config.settings | |||
) | |||
) { | ) { | ||
config.settings = {}; | config.settings = {}; | ||
| Line 280: | Line 280: | ||
typeof config.fields !== | typeof config.fields !== | ||
'object' || | 'object' || | ||
Array.isArray(config.fields) | Array.isArray( | ||
config.fields | |||
) | |||
) { | ) { | ||
config.fields = {}; | config.fields = {}; | ||
| Line 286: | Line 288: | ||
/* | /* | ||
* PAGE is always a special automatic field. | * PAGE is always a special, | ||
* automatic field. | |||
* | |||
* Its configuration is precompiled | |||
* and cannot be edited. | |||
*/ | */ | ||
config.fields.PAGE = | config.fields.PAGE = | ||
| Line 300: | Line 306: | ||
return { | return { | ||
label: | label: | ||
this.humanizeName(name), | this.humanizeName( | ||
name | |||
), | |||
description: '', | description: '', | ||
| Line 335: | Line 343: | ||
}, | }, | ||
prepareConfigForVariables( | prepareConfigForVariables( | ||
config, | config, | ||
| Line 357: | Line 358: | ||
/* | /* | ||
* PAGE is always automatic | * PAGE is always automatic. | ||
*/ | */ | ||
fields.PAGE = | fields.PAGE = | ||
| Line 363: | Line 364: | ||
/* | /* | ||
* Add every variable currently | * Add every variable currently | ||
* in the preload. | * detected in the preload. | ||
* | * | ||
* Existing configuration is preserved. | * Existing configuration is | ||
* New variables receive a default | * preserved. | ||
* | |||
* New variables receive a | |||
* default configuration. | |||
*/ | */ | ||
analysis.variables.forEach( | analysis.variables.forEach( | ||
| Line 374: | Line 378: | ||
return; | return; | ||
} | } | ||
fields[name] = | fields[name] = | ||
existing[name] || | |||
this.createDefaultField( | this.createDefaultField( | ||
name, | name, | ||
| Line 388: | Line 389: | ||
/* | /* | ||
* Preserve existing fields | * Preserve existing fields even | ||
* | * if they are currently unused. | ||
* | * | ||
* This allows users to keep | |||
* configuration around while | |||
* editing the preload. | |||
*/ | */ | ||
Object.keys(existing).forEach( | Object.keys(existing).forEach( | ||
| Line 437: | Line 441: | ||
typeof config.fields !== | typeof config.fields !== | ||
'object' || | 'object' || | ||
Array.isArray(config.fields) | Array.isArray( | ||
config.fields | |||
) | |||
) { | ) { | ||
return { | return { | ||
| Line 556: | Line 562: | ||
}, | }, | ||
// Transformation helpers | // Transformation helpers | ||
transformationObject( | transformationObject( | ||
| Line 602: | Line 606: | ||
}, | }, | ||
// UI helpers | // UI helpers | ||
createSection( | createSection( | ||
| Line 707: | Line 709: | ||
? '' | ? '' | ||
: value, | : value, | ||
placeholder: | placeholder: | ||
placeholder || '' | placeholder || '' | ||
| Line 767: | Line 770: | ||
option.value | option.value | ||
) === | ) === | ||
String( | |||
value === | |||
undefined | |||
? '' | |||
: value | |||
) | |||
) | ) | ||
.appendTo(select); | .appendTo( | ||
select | |||
); | |||
} | } | ||
); | ); | ||
| Line 781: | Line 786: | ||
}, | }, | ||
// CodeMirror | // CodeMirror | ||
destroyEditor() { | destroyEditor() { | ||
| Line 821: | Line 824: | ||
getEditorContent() { | getEditorContent() { | ||
/* | |||
* Always read directly from the | |||
* CodeMirror state when available. | |||
* | |||
* This is important because the | |||
* native textarea may not contain | |||
* the actual current document. | |||
*/ | |||
if ( | if ( | ||
this.editorView && | this.editorView && | ||
| Line 876: | Line 887: | ||
return { | return { | ||
from: selection.from, | from: | ||
to: selection.to, | selection.from, | ||
to: | |||
selection.to, | |||
text: | text: | ||
this.editorView.state.doc | this.editorView.state.doc.sliceString( | ||
selection.from, | |||
selection.to | |||
) | |||
}; | }; | ||
}, | }, | ||
| Line 910: | Line 924: | ||
const from = | const from = | ||
selectionFrom === undefined | selectionFrom === | ||
undefined | |||
? selection.from | ? selection.from | ||
: selectionFrom; | : selectionFrom; | ||
const to = | const to = | ||
selectionTo === undefined | selectionTo === | ||
undefined | |||
? selection.to | ? selection.to | ||
: selectionTo; | : selectionTo; | ||
| Line 940: | Line 956: | ||
}, | }, | ||
// | // V2.3 editor tools | ||
wrapSelectionWithVariable() { | wrapSelectionWithVariable() { | ||
| Line 1,057: | Line 1,071: | ||
cursorOffset = | cursorOffset = | ||
opening.length + 1; | opening.length + | ||
1; | |||
} | } | ||
| Line 1,066: | Line 1,081: | ||
text = | text = | ||
'<!-- @elseif -->'; | '<!-- @elseif -->'; | ||
cursorOffset = | cursorOffset = | ||
text.length; | text.length; | ||
break; | break; | ||
| Line 1,073: | Line 1,090: | ||
text = | text = | ||
'<!-- @else -->'; | '<!-- @else -->'; | ||
cursorOffset = | cursorOffset = | ||
text.length; | text.length; | ||
break; | break; | ||
| Line 1,080: | Line 1,099: | ||
text = | text = | ||
'<!-- @endif -->'; | '<!-- @endif -->'; | ||
cursorOffset = | cursorOffset = | ||
text.length; | text.length; | ||
break; | break; | ||
| Line 1,147: | Line 1,168: | ||
option.label | option.label | ||
) | ) | ||
.appendTo(select); | .appendTo( | ||
select | |||
); | |||
} | } | ||
); | ); | ||
| Line 1,327: | Line 1,350: | ||
}, | }, | ||
{ | { | ||
value: | value: 'elseif', | ||
label: '@elseif' | |||
label: | |||
}, | }, | ||
{ | { | ||
value: 'else', | value: 'else', | ||
label: | label: '@else' | ||
}, | }, | ||
{ | { | ||
value: | value: 'endif', | ||
label: '@endif' | |||
label: | |||
} | } | ||
], | ], | ||
| Line 1,411: | Line 1,429: | ||
}, | }, | ||
// Preload syntax highlighting | // Preload syntax highlighting | ||
getKnownVariables() { | getKnownVariables() { | ||
| Line 1,428: | Line 1,444: | ||
'object' | 'object' | ||
) { | ) { | ||
Object.keys(fields).forEach( | Object.keys( | ||
fields | |||
).forEach( | |||
name => { | name => { | ||
known.add( | known.add( | ||
| Line 1,460: | Line 1,478: | ||
while ( | while ( | ||
(match = | ( | ||
match = | |||
variablePattern.exec( | |||
source | |||
) | |||
) !== null | |||
) { | ) { | ||
const name = | const name = | ||
| Line 1,493: | Line 1,513: | ||
while ( | while ( | ||
(match = | ( | ||
match = | |||
conditionPattern.exec( | |||
source | |||
) | |||
) !== null | |||
) { | ) { | ||
decorations.push( | decorations.push( | ||
| Line 1,647: | Line 1,669: | ||
syntaxField; | syntaxField; | ||
}, | }, | ||
async mountCodeMirror( | async mountCodeMirror( | ||
| Line 1,680: | Line 1,674: | ||
) { | ) { | ||
return new Promise( | return new Promise( | ||
(resolve, reject) => { | ( | ||
resolve, | |||
reject | |||
) => { | |||
let settled = false; | let settled = false; | ||
| Line 1,726: | Line 1,723: | ||
/* | /* | ||
* | * Keep the editor state | ||
* | * synchronized, but do not | ||
* rebuild the right panel | |||
* on every CodeMirror event. | |||
* | * | ||
* | * The Detect variables | ||
* | * button is the explicit | ||
* | * refresh action. | ||
*/ | */ | ||
this. | this.state | ||
.preloadContent = | |||
view.state.doc.toString(); | |||
}; | }; | ||
| Line 1,781: | Line 1,776: | ||
); | ); | ||
resolve(view); | |||
resolve( | |||
} catch ( | } catch ( | ||
error | error | ||
) { | ) { | ||
reject( | reject(error); | ||
} | } | ||
}; | }; | ||
| Line 1,840: | Line 1,824: | ||
}, | }, | ||
// Main UI | // Main UI | ||
init() { | init() { | ||
| Line 1,871: | Line 1,853: | ||
renderShell() { | renderShell() { | ||
this.destroyEditor(); | this.destroyEditor(); | ||
this.root.empty(); | this.root.empty(); | ||
| Line 2,025: | Line 2,006: | ||
}, | }, | ||
// Load / create | // Load / create | ||
async loadExisting( | async loadExisting( | ||
| Line 2,227: | Line 2,206: | ||
preloadContent: | preloadContent: | ||
this.defaults.preload, | this.defaults.preload, | ||
config: | config: | ||
this.emptyConfig(), | this.emptyConfig(), | ||
variables: ['PAGE'], | variables: ['PAGE'], | ||
conditions: [], | conditions: [], | ||
selectedVariable: | selectedVariable: 'PAGE', | ||
mode: 'new', | mode: 'new', | ||
dirty: true | dirty: true | ||
| Line 2,284: | Line 2,264: | ||
preloadContent: | preloadContent: | ||
sample, | sample, | ||
config: | config: | ||
this.emptyConfig(), | this.emptyConfig(), | ||
variables: ['PAGE'], | variables: ['PAGE'], | ||
conditions: [], | conditions: [], | ||
selectedVariable: | selectedVariable: 'PAGE', | ||
mode: 'new', | mode: 'new', | ||
dirty: true | dirty: true | ||
| Line 2,395: | Line 2,376: | ||
() => { | () => { | ||
overlay.remove(); | overlay.remove(); | ||
resolve( | resolve(null); | ||
} | } | ||
); | ); | ||
| Line 2,417: | Line 2,396: | ||
overlay.remove(); | overlay.remove(); | ||
resolve( | resolve(value); | ||
} | } | ||
); | ); | ||
| Line 2,524: | Line 2,501: | ||
this.state = { | this.state = { | ||
preloadTitle: | preloadTitle: title, | ||
preloadSource: source, | |||
preloadContent: source, | |||
preloadSource: | |||
preloadContent: | |||
config, | config, | ||
| Line 2,558: | Line 2,529: | ||
}, | }, | ||
// Editor | // Editor | ||
renderEditor() { | renderEditor() { | ||
this.destroyEditor(); | this.destroyEditor(); | ||
this.workspace.empty(); | this.workspace.empty(); | ||
| Line 2,659: | Line 2,627: | ||
/* | /* | ||
* RIGHT PANEL | * RIGHT PANEL | ||
* | |||
* Analysis | |||
* Variables | |||
* Variable configuration | |||
* Generated configuration | |||
* Actions | |||
*/ | */ | ||
| Line 2,680: | Line 2,654: | ||
'Variables' | 'Variables' | ||
); | ); | ||
/* | |||
* Explicit variable detection button. | |||
* | |||
* This is intentionally separate from | |||
* CodeMirror's input event because | |||
* CodeMirror 6 owns its own document | |||
* state and does not always synchronize | |||
* through the native textarea/input event. | |||
*/ | |||
const detectVariablesButton = | |||
this.createButton( | |||
'Detect variables', | |||
'preload-editor-button-primary preload-editor-detect-variables', | |||
() => | |||
this.detectVariables() | |||
); | |||
const detectHelp = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-help preload-editor-detect-variables-help' | |||
) | |||
.text( | |||
'Refresh the variable list after editing the preload.' | |||
); | |||
const detectControls = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-variable-detect' | |||
) | |||
.append( | |||
detectVariablesButton, | |||
detectHelp | |||
); | |||
variablesSection.append( | |||
detectControls | |||
); | |||
const variableContainer = | const variableContainer = | ||
| Line 2,798: | Line 2,812: | ||
}, | }, | ||
/* | |||
* Explicitly re-read the CodeMirror document | |||
* and refresh every part of the right panel. | |||
* | |||
* This is the important new part. | |||
*/ | |||
detectVariables() { | |||
const source = | const source = | ||
this.getEditorContent(); | this.getEditorContent(); | ||
/* | |||
* Make sure the editor's actual document | |||
* becomes the canonical preload content. | |||
*/ | |||
this.state.preloadContent = | this.state.preloadContent = | ||
source; | source; | ||
/* | |||
* Mark the preload as modified because | |||
* the user has edited the CodeMirror | |||
this.state. | * document. | ||
*/ | |||
this.state.dirty = true; | |||
/* | /* | ||
* | * Re-run the complete preload analysis. | ||
* | * | ||
* | * This discovers new variables such as: | ||
* | * | ||
* %%ZHS%% | * %%ZHS%% | ||
* | * | ||
* | * and creates their default config | ||
* | * entries. | ||
* | */ | ||
this.reanalyze(); | |||
/* | |||
* | * Refresh the right panel. | ||
*/ | */ | ||
this. | this.renderVariablesBySelector(); | ||
this.renderVariableConfigurationBySelector(); | |||
this.renderAnalysisBySelector(); | |||
this. | this.renderConfigPreviewBySelector(); | ||
/* | /* | ||
* | * Refresh the variable selectors in | ||
* the editor toolbar too. | |||
*/ | */ | ||
this.updateEditorTools(); | |||
/* | /* | ||
* | * Refresh syntax highlighting so a newly | ||
* detected variable becomes "known". | |||
* | |||
*/ | */ | ||
if ( | if ( | ||
this.editorView && | |||
this.editor && | |||
this.editor.syntaxField | |||
) { | ) { | ||
this.state | /* | ||
* The syntax decoration field normally | |||
* updates when CodeMirror's document | |||
' | * changes. Here we force a tiny | ||
* configuration refresh by replacing | |||
* the current syntax field with the | |||
* same configuration. | |||
* | |||
* If the field is already up to date, | |||
* CodeMirror simply keeps the current | |||
* document unchanged. | |||
*/ | |||
try { | |||
this.editorView.dispatch({ | |||
effects: | |||
this.editorView.state | |||
.field( | |||
this.editor.syntaxField | |||
) | |||
? [] | |||
: [] | |||
}); | |||
} catch ( | |||
error | |||
) { | |||
/* | |||
* Syntax highlighting is cosmetic; | |||
* never let it break variable | |||
* detection. | |||
*/ | |||
console.warn( | |||
'Preload Editor syntax refresh:', | |||
error | |||
); | |||
} | |||
} | } | ||
this.setStatus( | |||
'Variables detected from the current preload.', | |||
'success' | |||
); | |||
}, | }, | ||
reanalyze() { | |||
const source = | |||
this.getEditorContent(); | |||
const content = | |||
this.extractIncludeonly( | |||
source | |||
); | |||
const analysis = | const analysis = | ||
this.analyzePreload( | this.analyzePreload( | ||
content | |||
); | ); | ||
this.state.preloadContent = | |||
source; | |||
this.state.variables = | |||
analysis. | analysis.variables; | ||
this.state.conditions = | |||
analysis.conditions; | |||
this.state.config = | |||
this.prepareConfigForVariables( | |||
. | this.state.config || | ||
this.emptyConfig(), | |||
analysis | |||
); | |||
this.analysis = | |||
analysis; | |||
/* | |||
* PAGE is always available. | |||
* | |||
* If the selected variable | |||
* disappeared, select 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'; | |||
} | |||
}, | |||
container | renderAnalysis( | ||
container | |||
); | ) { | ||
container.empty(); | |||
const analysis = | |||
analysis | this.analysis || | ||
this.analyzePreload( | |||
this.extractIncludeonly( | |||
this.getEditorContent() | |||
) | |||
. | |||
); | ); | ||
const | const variableCount = | ||
analysis.variables.length; | |||
const conditionCount = | |||
analysis.conditions.length; | |||
const configuredCount = | |||
Object.keys( | |||
this.state.config && | |||
this.state.config.fields | |||
? this.state.config.fields | |||
: {} | |||
).length; | |||
); | |||
const summary = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-analysis-summary' | |||
); | |||
const createMetric = | |||
( | |||
value, | |||
label | |||
$('< | ) => | ||
$('<div>') | |||
.addClass( | .addClass( | ||
'preload-editor- | 'preload-editor-analysis-metric' | ||
) | |||
.append( | |||
$('<strong>') | |||
.addClass( | |||
'preload-editor-analysis-value' | |||
) | |||
.text(value), | |||
$('<span>') | |||
.addClass( | |||
'preload-editor-analysis-label' | |||
) | |||
.text(label) | |||
); | ); | ||
summary.append( | |||
createMetric( | |||
variableCount, | |||
variableCount === 1 | |||
? 'variable' | |||
: 'variables' | |||
), | |||
); | createMetric( | ||
conditionCount, | |||
conditionCount === 1 | |||
? 'condition marker' | |||
: 'condition markers' | |||
), | |||
createMetric( | |||
configuredCount, | |||
configuredCount === 1 | |||
? 'configured field' | |||
: 'configured fields' | |||
) | |||
); | |||
container.append( | |||
summary | |||
); | |||
if ( | if ( | ||
analysis.conditionVariables | |||
.length | |||
) { | ) { | ||
container.append( | container.append( | ||
$('<div>') | $('<div>') | ||
.addClass( | .addClass( | ||
'preload-editor- | 'preload-editor-analysis-note' | ||
) | ) | ||
.text( | .text( | ||
' | 'Condition variables: ' + | ||
analysis | |||
.conditionVariables | |||
.join(', ') | |||
) | ) | ||
); | ); | ||
} | } | ||
const validation = | |||
const | this.validateConfig( | ||
this. | this.state.config, | ||
analysis | |||
); | ); | ||
if ( | if ( | ||
validation.errors.length | |||
) { | ) { | ||
const errors = | |||
container | $('<ul>') | ||
.addClass( | |||
'preload-editor-validation-errors' | |||
); | |||
validation.errors.forEach( | |||
error => { | |||
$('<li>') | |||
.text(error) | |||
.appendTo( | |||
errors | |||
); | |||
} | |||
); | |||
container.append( | |||
errors | |||
); | ); | ||
} | } | ||
if ( | |||
validation.warnings.length | |||
) { | |||
const warnings = | |||
$('<ul>') | |||
.addClass( | |||
'preload-editor-validation-warnings' | |||
); | |||
validation.warnings.forEach( | |||
warning => { | |||
$('<li>') | |||
.text(warning) | |||
.appendTo( | |||
warnings | |||
); | |||
} | |||
); | ); | ||
container.append( | |||
warnings | |||
); | |||
} | |||
if ( | |||
!validation.errors.length && | |||
!validation.warnings.length | |||
) { | |||
container.append( | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-validation-success' | |||
) | |||
.text( | |||
'Preload and configuration are in sync.' | |||
) | |||
); | |||
} | |||
}, | |||
variables.forEach( | renderAnalysisBySelector() { | ||
const container = | |||
this.root && | |||
this.root.find( | |||
'.preload-editor-analysis' | |||
); | |||
if ( | |||
container && | |||
container.length | |||
) { | |||
this.renderAnalysis( | |||
container | |||
); | |||
} | |||
}, | |||
/* | |||
* Variable list | |||
*/ | |||
renderVariables( | |||
container | |||
) { | |||
container.empty(); | |||
const analysis = | |||
this.analysis || | |||
this.analyzePreload( | |||
this.extractIncludeonly( | |||
this.getEditorContent() | |||
) | |||
); | |||
const variables = | |||
analysis.variables.slice(); | |||
const ordered = | |||
['PAGE']; | |||
variables.forEach( | |||
name => { | name => { | ||
if ( | if ( | ||
| Line 3,098: | Line 3,196: | ||
) | ) | ||
) { | ) { | ||
ordered.push(name); | ordered.push( | ||
name | |||
); | |||
} | } | ||
} | } | ||
); | ); | ||
const list = | |||
$('<div>') | |||
const list = | |||
$('<div>') | |||
.addClass( | .addClass( | ||
'preload-editor-variable-list' | 'preload-editor-variable-list' | ||
| Line 3,186: | Line 3,255: | ||
if ( | if ( | ||
name === 'PAGE' | name === | ||
'PAGE' | |||
) { | ) { | ||
item.append( | item.append( | ||
| Line 3,220: | Line 3,290: | ||
list | list | ||
); | ); | ||
if ( | |||
!ordered.length | |||
) { | |||
container.append( | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-empty' | |||
) | |||
.text( | |||
'No variables found in the preload.' | |||
) | |||
); | |||
} | |||
}, | }, | ||
| Line 3,239: | Line 3,323: | ||
}, | }, | ||
/ | /* | ||
* Select a variable and display only | |||
* its configuration. | |||
* | |||
* No scrolling. | |||
*/ | |||
selectVariable( | selectVariable( | ||
name | name | ||
| Line 3,260: | Line 3,346: | ||
this.analysis && | this.analysis && | ||
this.analysis.variables | this.analysis.variables | ||
? this.analysis.variables | ? this.analysis | ||
.variables | |||
: [] | : [] | ||
).filter( | ).filter( | ||
| Line 3,269: | Line 3,356: | ||
); | ); | ||
if ( | |||
if ( | |||
!available.includes( | !available.includes( | ||
name | name | ||
| Line 3,360: | Line 3,420: | ||
if ( | if ( | ||
name === 'PAGE' | name === | ||
'PAGE' | |||
) { | ) { | ||
container.append( | container.append( | ||
| Line 3,380: | Line 3,441: | ||
}, | }, | ||
/ | /* | ||
* PAGE is automatic/precompiled. | |||
*/ | |||
createAutomaticPageCard( | createAutomaticPageCard( | ||
| Line 3,390: | Line 3,451: | ||
$('<div>') | $('<div>') | ||
.addClass( | .addClass( | ||
'preload-editor-variable | 'preload-editor-variable preload-editor-variable-automatic' | ||
) | ) | ||
.attr( | .attr( | ||
| Line 3,405: | Line 3,465: | ||
header.append( | header.append( | ||
$('<h3>').text( | $('<h3>') | ||
.text('PAGE'), | |||
$('<span>') | $('<span>') | ||
| Line 3,436: | Line 3,495: | ||
this.textInput( | this.textInput( | ||
field.label || | field.label || | ||
'Page Name' | |||
).prop( | ).prop( | ||
'disabled', | 'disabled', | ||
| Line 3,445: | Line 3,504: | ||
this.textarea( | this.textarea( | ||
field.description || | field.description || | ||
'Name of the page.' | |||
) | ) | ||
.addClass( | .addClass( | ||
| Line 3,458: | Line 3,517: | ||
this.textInput( | this.textInput( | ||
field.type || | field.type || | ||
'page' | |||
).prop( | ).prop( | ||
'disabled', | 'disabled', | ||
| Line 3,476: | Line 3,535: | ||
this.textInput( | this.textInput( | ||
field.namespace || | field.namespace || | ||
'Main' | |||
).prop( | ).prop( | ||
'disabled', | 'disabled', | ||
| Line 3,525: | Line 3,584: | ||
return card; | return card; | ||
}, | }, | ||
createVariableCard( | createVariableCard( | ||
| Line 3,552: | Line 3,607: | ||
header.append( | header.append( | ||
$('<h3>').text( | $('<h3>') | ||
.text(name) | |||
); | ); | ||
| Line 3,576: | Line 3,630: | ||
header | header | ||
); | ); | ||
const label = | const label = | ||
| Line 3,602: | Line 3,654: | ||
) | ) | ||
); | ); | ||
const description = | const description = | ||
this.textarea( | this.textarea( | ||
field.description || | field.description || | ||
'' | |||
).addClass( | ).addClass( | ||
'preload-editor-small-textarea' | 'preload-editor-small-textarea' | ||
| Line 3,631: | Line 3,681: | ||
) | ) | ||
); | ); | ||
const types = [ | const types = [ | ||
| Line 3,710: | Line 3,758: | ||
) | ) | ||
); | ); | ||
const required = | const required = | ||
| Line 3,739: | Line 3,785: | ||
) | ) | ||
); | ); | ||
const placeholder = | const placeholder = | ||
this.textInput( | this.textInput( | ||
field.placeholder || | field.placeholder || | ||
'' | |||
); | ); | ||
| Line 3,773: | Line 3,817: | ||
) | ) | ||
); | ); | ||
const defaultInput = | const defaultInput = | ||
| Line 3,810: | Line 3,852: | ||
) | ) | ||
); | ); | ||
if ( | if ( | ||
| Line 3,820: | Line 3,860: | ||
this.textInput( | this.textInput( | ||
field.maxRows || | field.maxRows || | ||
'' | |||
).attr( | ).attr( | ||
'type', | 'type', | ||
| Line 3,860: | Line 3,900: | ||
); | ); | ||
} | } | ||
if ( | if ( | ||
| Line 3,877: | Line 3,915: | ||
return card; | return card; | ||
}, | }, | ||
renderSelectConfiguration( | renderSelectConfiguration( | ||
| Line 3,888: | Line 3,922: | ||
const source = | const source = | ||
this.textInput( | this.textInput( | ||
field.source || | field.source || '', | ||
'category:Example' | 'category:Example' | ||
); | ); | ||
| Line 3,922: | Line 3,955: | ||
const sourcePrefix = | const sourcePrefix = | ||
this.textInput( | this.textInput( | ||
field[ | field['source-prefix'] || | ||
'' | |||
); | ); | ||
| Line 3,961: | Line 3,993: | ||
field.exclude && | field.exclude && | ||
field.exclude.category || | field.exclude.category || | ||
'' | |||
); | ); | ||
| Line 3,984: | Line 4,016: | ||
field.exclude | field.exclude | ||
) { | ) { | ||
delete field | delete field.exclude | ||
.category; | .category; | ||
| Line 4,126: | Line 4,157: | ||
); | ); | ||
}, | }, | ||
createTransformControls( | createTransformControls( | ||
| Line 4,176: | Line 4,203: | ||
}, | }, | ||
{ | { | ||
value: | value: 'replace', | ||
label: | label: | ||
'Replace' | 'Replace' | ||
| Line 4,189: | Line 4,215: | ||
this.textInput( | this.textInput( | ||
existing.prefix || | existing.prefix || | ||
'' | |||
); | ); | ||
| Line 4,195: | Line 4,221: | ||
this.textInput( | this.textInput( | ||
existing.suffix || | existing.suffix || | ||
'' | |||
); | ); | ||
| Line 4,201: | Line 4,227: | ||
this.textInput( | this.textInput( | ||
existing.from || | existing.from || | ||
'' | |||
); | ); | ||
| Line 4,207: | Line 4,233: | ||
this.textInput( | this.textInput( | ||
existing.to || | existing.to || | ||
'' | |||
); | ); | ||
| Line 4,315: | Line 4,341: | ||
object; | object; | ||
} else { | } else { | ||
delete field[ | delete field[property]; | ||
} | } | ||
| Line 4,352: | Line 4,376: | ||
transform | transform | ||
), | ), | ||
prefixRow, | prefixRow, | ||
suffixRow, | suffixRow, | ||
| Line 4,367: | Line 4,392: | ||
}, | }, | ||
// Config output | // Config output | ||
getCleanConfig() { | getCleanConfig() { | ||
| Line 4,382: | Line 4,405: | ||
config.fields || {}; | config.fields || {}; | ||
const orderedFields = {}; | const orderedFields = | ||
{}; | |||
/* | /* | ||
* PAGE is always first. | * PAGE is always first and automatic. | ||
*/ | */ | ||
orderedFields.PAGE = | orderedFields.PAGE = | ||
this.getAutomaticPageField(); | this.getAutomaticPageField(); | ||
Object.keys(fields) | Object.keys( | ||
fields | |||
) | |||
.filter( | .filter( | ||
name => | name => | ||
| Line 4,447: | Line 4,473: | ||
markDirty() { | markDirty() { | ||
this.state.dirty = | this.state.dirty = true; | ||
}, | }, | ||
// Save | // Save | ||
async savePreloadOnly() { | async savePreloadOnly() { | ||
| Line 4,459: | Line 4,482: | ||
String( | String( | ||
this.state.preloadTitle || | this.state.preloadTitle || | ||
'' | |||
).trim(); | ).trim(); | ||
| Line 4,479: | Line 4,502: | ||
} | } | ||
/* | |||
* Always pull the actual CodeMirror | |||
* document before saving. | |||
*/ | |||
this.state.preloadContent = | this.state.preloadContent = | ||
this.getEditorContent(); | this.getEditorContent(); | ||
| Line 4,529: | Line 4,556: | ||
'Preload saved.', | 'Preload saved.', | ||
{ | { | ||
type: 'success' | type: | ||
'success' | |||
} | } | ||
); | ); | ||
| Line 4,550: | Line 4,578: | ||
'Could not save preload.', | 'Could not save preload.', | ||
{ | { | ||
type: 'error' | type: | ||
'error' | |||
} | } | ||
); | ); | ||
| Line 4,560: | Line 4,589: | ||
String( | String( | ||
this.state.preloadTitle || | this.state.preloadTitle || | ||
'' | |||
).trim(); | ).trim(); | ||
| Line 4,580: | Line 4,609: | ||
} | } | ||
/* | |||
* Always use the actual CodeMirror | |||
* document when saving. | |||
*/ | |||
this.state.preloadContent = | this.state.preloadContent = | ||
this.getEditorContent(); | this.getEditorContent(); | ||
/* | /* | ||
* | * Make sure the configuration is | ||
* | * synchronized with anything newly | ||
* typed into the preload. | |||
* | |||
* This means even if the user forgot | |||
* to click Detect variables, saving | |||
* will still catch new variables. | |||
*/ | */ | ||
this.reanalyze(); | this.reanalyze(); | ||
| Line 4,660: | Line 4,698: | ||
'Preload and config saved.', | 'Preload and config saved.', | ||
{ | { | ||
type: 'success' | type: | ||
'success' | |||
} | } | ||
); | ); | ||
| Line 4,681: | Line 4,720: | ||
'Could not save preload/config.', | 'Could not save preload/config.', | ||
{ | { | ||
type: 'error' | type: | ||
'error' | |||
} | } | ||
); | ); | ||
| Line 4,694: | Line 4,734: | ||
String( | String( | ||
this.state.preloadTitle || | this.state.preloadTitle || | ||
'' | |||
).trim(); | ).trim(); | ||
| Line 4,754: | Line 4,794: | ||
/* | /* | ||
* Synchronize configuration one | * Synchronize configuration one final | ||
* time before | * time before saving. | ||
* | |||
* This catches variables even if the | |||
* user never clicked Detect variables. | |||
*/ | */ | ||
this.state.config = | this.state.config = | ||
| Line 4,852: | Line 4,895: | ||
), | ), | ||
{ | { | ||
type: 'error' | type: | ||
'error' | |||
} | } | ||
); | ); | ||