Jump to content
Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
If you make an edit, a temporary account will be created for you.

MediaWiki:Gadget-PreloadEditor.js: Difference between revisions

MediaWiki interface page
ReisuDesign (talk | contribs)
No edit summary
ReisuDesign (talk | contribs)
No edit summary
Line 3: Line 3:


     const PreloadEditor = {
     const PreloadEditor = {
         version: '0.8.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: 2,
                 version: 3,


                 preloadTitle:
                 preloadTitle:
Line 214: Line 221:
         },
         },


         saveDraft() {
         updateLocalDraftStatus(
             if (!this.state.dirty) {
            message,
            type
        ) {
             if (!this.localDraftStatus) {
                 return;
                 return;
             }
             }


             const snapshot =
             this.localDraftStatus.removeClass(
                this.getDraftSnapshot();
                'is-saved',
                'is-warning',
                'is-error',
                'is-empty'
            );


             const id =
             if (type) {
                 this.getDraftId(
                 this.localDraftStatus.addClass(
                     snapshot.preloadTitle
                     'is-' + type
                 );
                 );
            }


             const storage =
             this.localDraftStatus.text(
                this.getDraftStorage();
                message || ''
            );
        },


             storage.drafts[id] =
        formatLocalDraftTime(timestamp) {
                 snapshot;
             if (!timestamp) {
                 return '';
            }


             storage.active = id;
             try {
 
                return new Date(
            this.setDraftStorage(
                    timestamp
                 storage
                ).toLocaleTimeString(
             );
                    [],
                    {
                        hour: '2-digit',
                        minute: '2-digit',
                        second: '2-digit'
                    }
                 );
             } catch (error) {
                return '';
            }
         },
         },


         clearDraft(title) {
         updateLocalDraftIndicator() {
             const storage =
             if (!this.localDraftStatus) {
                this.getDraftStorage();
                return;
 
             }
             const id =
                this.getDraftId(title);


             if (
             if (
                 storage.drafts &&
                 this.state.localDraftSavedAt
                storage.drafts[id]
             ) {
             ) {
                 delete storage.drafts[id];
                 const time =
            }
                    this.formatLocalDraftTime(
                        this.state.localDraftSavedAt
                    );
 
                this.updateLocalDraftStatus(
                    time
                        ? 'Saved locally at ' + time
                        : 'Saved locally',
                    'saved'
                );


            if (
                 return;
                 storage.active === id
            ) {
                storage.active = null;
             }
             }


             this.setDraftStorage(
             this.updateLocalDraftStatus(
                 storage
                 'No local draft',
                'empty'
             );
             );
         },
         },


         getMostRecentDraft() {
         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 =
            this.workspace =
    $('<div>')
                $('<div>')
        .addClass(
                    .addClass(
            'preload-editor-workspace'
                        'preload-editor-workspace'
        );
                    );
 
this.root.append(this.workspace);
            this.root.append(
                this.workspace
/*
            );
* Fixed footer
 
*
            /*
* Status messages and save actions live here so they
            * Fixed footer
* remain visible while editing.
            *
*/
            * Local draft status, normal status messages,
const footer =
            * and save actions live here.
    $('<div>')
            */
        .addClass(
            const footer =
            'preload-editor-footer'
                $('<div>')
        );
                    .addClass(
                        'preload-editor-footer'
this.status =
                    );
    $('<div>')
 
        .addClass(
            this.localDraftStatus =
            'preload-editor-status'
                $('<div>')
        );
                    .addClass(
                        'preload-editor-local-draft-status is-empty'
footer.append(
                    )
    this.status
                    .text(
);
                        'No local draft'
                    );
this.root.append(footer);
 
            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
             );
             );
            /*
            * CONFIG REVIEW
            *
            * The generated JSON is no longer displayed
            * directly in the page. It can be opened in
            * the custom review dialog instead.
            */


             const configSection =
             const configSection =
Line 3,331: Line 3,494:
             );
             );


             // Save actions
             right.append(
                analysisSection,
                variablesSection,
                configurationSection,
                configSection
            );
 
            layout.append(
                left,
                right
            );


             const actions =
             const saveBar =
                 $('<div>')
                 $('<div>')
                     .addClass(
                     .addClass(
                         'preload-editor-actions'
                         'preload-editor-save-bar'
                     );
                     );


             const savePreloadButton =
             saveBar.append(
                 this.createButton(
                 this.createButton(
                     'Save Preload',
                     'Save Preload',
Line 3,345: Line 3,518:
                     () =>
                     () =>
                         this.savePreloadOnly()
                         this.savePreloadOnly()
                 );
                 ),


            const saveConfigButton =
                 this.createButton(
                 this.createButton(
                     'Save Config',
                     'Save Config',
Line 3,353: Line 3,525:
                     () =>
                     () =>
                         this.saveConfigOnly()
                         this.saveConfigOnly()
                 );
                 ),


            const saveBothButton =
                 this.createButton(
                 this.createButton(
                     'Save',
                     'Save',
Line 3,361: Line 3,532:
                     () =>
                     () =>
                         this.saveAll()
                         this.saveAll()
                 );
                 )
            );
 
            this.root
                .find('.preload-editor-footer')
                .append(saveBar);


             actions.append(
             this.workspace.append(
                 savePreloadButton,
                 layout
                saveConfigButton,
                saveBothButton
             );
             );
            right.append(
    analysisSection,
    variablesSection,
    configurationSection,
    configSection
);
layout.append(
    left,
    right
);
const saveBar =
    $('<div>')
        .addClass(
            'preload-editor-save-bar'
        );
saveBar.append(
    this.createButton(
        'Save Preload',
        '',
        () => this.savePreloadOnly()
    ),
    this.createButton(
        'Save Config',
        '',
        () => this.saveConfigOnly()
    ),
    this.createButton(
        'Save',
        'preload-editor-button-primary',
        () => 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:
         },
         },


        /*
        * Kept as a compatibility helper in case CSS or
        * another part of the gadget expects the method.
        * There is no longer a permanently rendered
        * config preview.
        */
         renderConfigPreview(container) {
         renderConfigPreview(container) {
             if (container) {
             if (container) {
Line 5,015: Line 5,213:


         renderConfigPreviewBySelector() {
         renderConfigPreviewBySelector() {
            /*
            * The old generated configuration <pre> no
            * longer exists in the editor UI.
            *
            * Keep this method so existing calls do not
            * break.
            */
             this.renderAnalysisBySelector();
             this.renderAnalysisBySelector();
         },
         },


         renderConfigReviewAvailability() {
         renderConfigReviewAvailability() {
            /*
            * The config is generated on demand when the
            * Review config.json button is pressed.
            */
             this.renderAnalysisBySelector();
             this.renderAnalysisBySelector();
         },
         },


        // =========================================================
         // Dirty state
         // Dirty state
        // =========================================================


         updateDirtyState() {
         updateDirtyState() {
Line 5,056: Line 5,245:


         markDirty() {
         markDirty() {
            /*
            * Existing configuration controls call
            * markDirty(), so keep it conservative:
            * both parts are considered locally dirty.
            */
             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() {