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.9.0', | ||
state: { | state: { | ||
| Line 17: | Line 17: | ||
dirty: false, | dirty: false, | ||
dirtyPreload: false, | dirtyPreload: false, | ||
dirtyConfig: false | dirtyConfig: false, | ||
// Local draft state | |||
localDraftSavedAt: null, | |||
localDraftAvailable: false | |||
}, | }, | ||
| Line 26: | Line 30: | ||
workspace: null, | workspace: null, | ||
status: null, | status: null, | ||
localDraftStatus: null, | |||
editorTools: null, | editorTools: null, | ||
| Line 36: | Line 41: | ||
}, | }, | ||
// ========================================================= | |||
// Browser-local draft storage | // Browser-local draft storage | ||
// ========================================================= | |||
getDraftStorage() { | getDraftStorage() { | ||
| Line 150: | Line 157: | ||
return { | return { | ||
version: | version: 3, | ||
preloadTitle: | preloadTitle: | ||
| Line 214: | Line 221: | ||
}, | }, | ||
updateLocalDraftStatus( | |||
if (!this. | message, | ||
type | |||
) { | |||
if (!this.localDraftStatus) { | |||
return; | return; | ||
} | } | ||
this.localDraftStatus.removeClass( | |||
'is-saved', | |||
'is-warning', | |||
'is-error', | |||
'is-empty' | |||
); | |||
if (type) { | |||
this. | this.localDraftStatus.addClass( | ||
'is-' + type | |||
); | ); | ||
} | |||
this.localDraftStatus.text( | |||
message || '' | |||
); | |||
}, | |||
formatLocalDraftTime(timestamp) { | |||
if (!timestamp) { | |||
return ''; | |||
} | |||
try { | |||
return new Date( | |||
timestamp | |||
).toLocaleTimeString( | |||
); | [], | ||
{ | |||
hour: '2-digit', | |||
minute: '2-digit', | |||
second: '2-digit' | |||
} | |||
); | |||
} catch (error) { | |||
return ''; | |||
} | |||
}, | }, | ||
updateLocalDraftIndicator() { | |||
if (!this.localDraftStatus) { | |||
return; | |||
} | |||
if ( | if ( | ||
this.state.localDraftSavedAt | |||
) { | ) { | ||
const time = | |||
this.formatLocalDraftTime( | |||
this.state.localDraftSavedAt | |||
); | |||
this.updateLocalDraftStatus( | |||
time | |||
? 'Saved locally at ' + time | |||
: 'Saved locally', | |||
'saved' | |||
); | |||
return; | |||
} | } | ||
this. | this.updateLocalDraftStatus( | ||
'No local draft', | |||
'empty' | |||
); | ); | ||
}, | }, | ||
saveDraft() { | |||
if (!this.state.dirty) { | |||
return false; | |||
} | |||
const snapshot = | |||
this.getDraftSnapshot(); | |||
const id = | |||
this.getDraftId( | |||
snapshot.preloadTitle | |||
); | |||
const storage = | const storage = | ||
this.getDraftStorage(); | this.getDraftStorage(); | ||
if ( | storage.drafts[id] = | ||
!storage.drafts || | snapshot; | ||
!Object.keys( | |||
storage.drafts | storage.active = id; | ||
).length | |||
) { | const saved = | ||
return null; | this.setDraftStorage( | ||
storage | |||
); | |||
if (saved) { | |||
this.state.localDraftSavedAt = | |||
snapshot.savedAt; | |||
this.state.localDraftAvailable = | |||
true; | |||
this.updateLocalDraftIndicator(); | |||
} else { | |||
this.state.localDraftAvailable = | |||
false; | |||
this.updateLocalDraftStatus( | |||
'Could not save local draft', | |||
'error' | |||
); | |||
} | |||
return saved; | |||
}, | |||
clearDraft(title) { | |||
const storage = | |||
this.getDraftStorage(); | |||
const id = | |||
this.getDraftId(title); | |||
if ( | |||
storage.drafts && | |||
storage.drafts[id] | |||
) { | |||
delete storage.drafts[id]; | |||
} | |||
if ( | |||
storage.active === id | |||
) { | |||
storage.active = null; | |||
} | |||
this.setDraftStorage( | |||
storage | |||
); | |||
this.state.localDraftSavedAt = | |||
null; | |||
this.state.localDraftAvailable = | |||
false; | |||
this.updateLocalDraftIndicator(); | |||
}, | |||
getMostRecentDraft() { | |||
const storage = | |||
this.getDraftStorage(); | |||
if ( | |||
!storage.drafts || | |||
!Object.keys( | |||
storage.drafts | |||
).length | |||
) { | |||
return null; | |||
} | } | ||
| Line 493: | Line 605: | ||
hasDirtyFlags | hasDirtyFlags | ||
? !!draft.dirtyConfig | ? !!draft.dirtyConfig | ||
: true | : true, | ||
localDraftSavedAt: | |||
Number( | |||
draft.savedAt | |||
) || null, | |||
localDraftAvailable: | |||
true | |||
}; | }; | ||
this.reanalyze(); | this.reanalyze(); | ||
this.renderEditor(); | this.renderEditor(); | ||
this.updateLocalDraftIndicator(); | |||
this.setStatus( | this.setStatus( | ||
| Line 514: | Line 636: | ||
}, | }, | ||
// ========================================================= | |||
// API | // API | ||
// ========================================================= | |||
async getPage(title) { | async getPage(title) { | ||
| Line 604: | Line 728: | ||
}, | }, | ||
// ========================================================= | |||
// Preload parsing | // Preload parsing | ||
// ========================================================= | |||
extractIncludeonly(source) { | extractIncludeonly(source) { | ||
| Line 717: | Line 843: | ||
}, | }, | ||
// ========================================================= | |||
// Config | // Config | ||
// ========================================================= | |||
getAutomaticPageField() { | getAutomaticPageField() { | ||
| Line 1,019: | Line 1,147: | ||
}, | }, | ||
// ========================================================= | |||
// Transformation helpers | // Transformation helpers | ||
// ========================================================= | |||
transformationObject( | transformationObject( | ||
| Line 1,063: | Line 1,193: | ||
}, | }, | ||
// ========================================================= | |||
// UI helpers | // UI helpers | ||
// ========================================================= | |||
createSection( | createSection( | ||
| Line 1,236: | Line 1,368: | ||
}, | }, | ||
// ========================================================= | |||
// In-wiki dialogs | // In-wiki dialogs | ||
// ========================================================= | |||
createConfirmDialog( | createConfirmDialog( | ||
| Line 1,615: | Line 1,749: | ||
}, | }, | ||
// ========================================================= | |||
// CodeMirror | // CodeMirror | ||
// ========================================================= | |||
destroyEditor() { | destroyEditor() { | ||
| Line 1,771: | Line 1,907: | ||
}, | }, | ||
// ========================================================= | |||
// Editor tools | // Editor tools | ||
// ========================================================= | |||
wrapSelectionWithVariable() { | wrapSelectionWithVariable() { | ||
| Line 2,206: | Line 2,344: | ||
}, | }, | ||
// ========================================================= | |||
// Syntax highlighting | // Syntax highlighting | ||
// ========================================================= | |||
getKnownVariables() { | getKnownVariables() { | ||
| Line 2,571: | Line 2,711: | ||
}, | }, | ||
// ========================================================= | |||
// Main UI | // Main UI | ||
// ========================================================= | |||
init() { | init() { | ||
| Line 2,679: | Line 2,821: | ||
this.root.append(loader); | this.root.append(loader); | ||
this.workspace = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-workspace' | |||
); | |||
this.root.append( | |||
this.workspace | |||
); | |||
/* | |||
* Fixed footer | |||
* | |||
* Local draft status, normal status messages, | |||
* and save actions live here. | |||
*/ | |||
const footer = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-footer' | |||
); | |||
this.localDraftStatus = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-local-draft-status is-empty' | |||
) | |||
.text( | |||
'No local draft' | |||
); | |||
this.status = | |||
$('<div>') | |||
.addClass( | |||
'preload-editor-status' | |||
); | |||
footer.append( | |||
this.localDraftStatus, | |||
this.status | |||
); | |||
this.root.append( | |||
footer | |||
); | |||
this.updateLocalDraftIndicator(); | |||
this.renderWelcome(); | this.renderWelcome(); | ||
| Line 2,774: | Line 2,932: | ||
}, | }, | ||
// ========================================================= | |||
// Load / create | // Load / create | ||
// ========================================================= | |||
async loadExisting(title) { | async loadExisting(title) { | ||
| Line 2,955: | Line 3,115: | ||
dirty: true, | dirty: true, | ||
dirtyPreload: true, | dirtyPreload: true, | ||
dirtyConfig: true | dirtyConfig: true, | ||
localDraftSavedAt: null, | |||
localDraftAvailable: false | |||
}; | }; | ||
| Line 3,014: | Line 3,177: | ||
dirty: true, | dirty: true, | ||
dirtyPreload: true, | dirtyPreload: true, | ||
dirtyConfig: true | dirtyConfig: true, | ||
localDraftSavedAt: null, | |||
localDraftAvailable: false | |||
}; | }; | ||
| Line 3,139: | Line 3,305: | ||
dirty: false, | dirty: false, | ||
dirtyPreload: false, | dirtyPreload: false, | ||
dirtyConfig: false | dirtyConfig: false, | ||
localDraftSavedAt: null, | |||
localDraftAvailable: false | |||
}; | }; | ||
| Line 3,146: | Line 3,315: | ||
}, | }, | ||
// ========================================================= | |||
// Editor | // Editor | ||
// ========================================================= | |||
renderEditor() { | renderEditor() { | ||
| Line 3,295: | Line 3,466: | ||
configurationContainer | configurationContainer | ||
); | ); | ||
const configSection = | const configSection = | ||
| Line 3,331: | Line 3,494: | ||
); | ); | ||
right.append( | |||
analysisSection, | |||
variablesSection, | |||
configurationSection, | |||
configSection | |||
); | |||
layout.append( | |||
left, | |||
right | |||
); | |||
const | const saveBar = | ||
$('<div>') | $('<div>') | ||
.addClass( | .addClass( | ||
'preload-editor- | 'preload-editor-save-bar' | ||
); | ); | ||
saveBar.append( | |||
this.createButton( | this.createButton( | ||
'Save Preload', | 'Save Preload', | ||
| Line 3,345: | Line 3,518: | ||
() => | () => | ||
this.savePreloadOnly() | this.savePreloadOnly() | ||
) | ), | ||
this.createButton( | this.createButton( | ||
'Save Config', | 'Save Config', | ||
| Line 3,353: | Line 3,525: | ||
() => | () => | ||
this.saveConfigOnly() | this.saveConfigOnly() | ||
) | ), | ||
this.createButton( | this.createButton( | ||
'Save', | 'Save', | ||
| Line 3,361: | Line 3,532: | ||
() => | () => | ||
this.saveAll() | this.saveAll() | ||
); | ) | ||
); | |||
this.root | |||
.find('.preload-editor-footer') | |||
.append(saveBar); | |||
this.workspace.append( | |||
layout | |||
); | ); | ||
this.reanalyze(); | this.reanalyze(); | ||
| Line 3,430: | Line 3,558: | ||
this.updateEditorTools(); | this.updateEditorTools(); | ||
this.updateLocalDraftIndicator(); | |||
this.mountCodeMirror( | this.mountCodeMirror( | ||
| Line 3,448: | Line 3,577: | ||
); | ); | ||
}, | }, | ||
// ========================================================= | |||
// Variable detection | |||
// ========================================================= | |||
detectVariables() { | detectVariables() { | ||
| Line 3,455: | Line 3,588: | ||
this.state.preloadContent = | this.state.preloadContent = | ||
source; | source; | ||
const content = | |||
this.extractIncludeonly( | |||
source | |||
); | |||
const analysis = | |||
this.analyzePreload( | |||
content | |||
); | |||
/* | |||
* IMPORTANT: | |||
* | |||
* If a variable was previously deleted from | |||
* the configuration, detecting the current | |||
* preload again should allow it to come back. | |||
* | |||
* Example: | |||
* | |||
* delete FOO | |||
* edit preload -> %%FOO%% | |||
* Detect variables | |||
* | |||
* FOO is removed from deletedVariables here, | |||
* allowing prepareConfigForVariables() to | |||
* recreate/restore its configuration. | |||
*/ | |||
if ( | |||
Array.isArray( | |||
this.state.deletedVariables | |||
) | |||
) { | |||
const detected = | |||
new Set( | |||
analysis.variables | |||
.map( | |||
name => | |||
String( | |||
name | |||
).toUpperCase() | |||
) | |||
); | |||
this.state.deletedVariables = | |||
this.state.deletedVariables | |||
.filter( | |||
name => | |||
!detected.has( | |||
String( | |||
name | |||
).toUpperCase() | |||
) | |||
); | |||
} else { | |||
this.state.deletedVariables = []; | |||
} | |||
this.markDirty(); | this.markDirty(); | ||
| Line 3,481: | Line 3,671: | ||
const analysis = | const analysis = | ||
this.analyzePreload(content); | this.analyzePreload( | ||
content | |||
); | |||
this.state.preloadContent = | this.state.preloadContent = | ||
| Line 3,520: | Line 3,712: | ||
} | } | ||
}, | }, | ||
// ========================================================= | |||
// Analysis | |||
// ========================================================= | |||
renderAnalysis(container) { | renderAnalysis(container) { | ||
| Line 3,688: | Line 3,884: | ||
}, | }, | ||
// ========================================================= | |||
// Variables | // Variables | ||
// ========================================================= | |||
renderVariables(container) { | renderVariables(container) { | ||
| Line 4,521: | Line 4,719: | ||
); | ); | ||
}, | }, | ||
// ========================================================= | |||
// Select configuration | |||
// ========================================================= | |||
renderSelectConfiguration( | renderSelectConfiguration( | ||
| Line 4,956: | Line 5,158: | ||
}, | }, | ||
// ========================================================= | |||
// Config output | // Config output | ||
// ========================================================= | |||
getCleanConfig() { | getCleanConfig() { | ||
| Line 5,000: | Line 5,204: | ||
}, | }, | ||
renderConfigPreview(container) { | renderConfigPreview(container) { | ||
if (container) { | if (container) { | ||
| Line 5,015: | Line 5,213: | ||
renderConfigPreviewBySelector() { | renderConfigPreviewBySelector() { | ||
this.renderAnalysisBySelector(); | this.renderAnalysisBySelector(); | ||
}, | }, | ||
renderConfigReviewAvailability() { | renderConfigReviewAvailability() { | ||
this.renderAnalysisBySelector(); | this.renderAnalysisBySelector(); | ||
}, | }, | ||
// ========================================================= | |||
// Dirty state | // Dirty state | ||
// ========================================================= | |||
updateDirtyState() { | updateDirtyState() { | ||
| Line 5,056: | Line 5,245: | ||
markDirty() { | markDirty() { | ||
this.state.dirtyPreload = true; | this.state.dirtyPreload = true; | ||
this.state.dirtyConfig = true; | this.state.dirtyConfig = true; | ||
| Line 5,067: | Line 5,251: | ||
}, | }, | ||
// ========================================================= | |||
// Save | // Save | ||
// ========================================================= | |||
async savePreloadOnly() { | async savePreloadOnly() { | ||
| Line 5,442: | Line 5,628: | ||
} | } | ||
}, | }, | ||
// ========================================================= | |||
// Validation | |||
// ========================================================= | |||
validateBeforeSave() { | validateBeforeSave() { | ||