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.3.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()
                        label: 'Page Name',
                        description: 'Name of the page.',
                        type: 'page',
                        required: true,
                        namespace: 'Main',
                        'allow-new': true
                    }
                 }
                 }
             };
             };
Line 225: Line 230:
             }
             }


             if (!config.fields.PAGE) {
             // PAGE is always a special, automatic field.
                config.fields.PAGE = {
            // Its configuration is precompiled and only displayed
                    label: 'Page Name',
            // in the editor for reference.
                    description: 'Name of the page.',
            config.fields.PAGE =
                    type: 'page',
                this.getAutomaticPageField();
                    required: true,
                    namespace: 'Main',
                    'allow-new': true
                };
            }


             return config;
             return config;
Line 268: Line 268:
             const fields = {};
             const fields = {};


             fields.PAGE = existing.PAGE || {
            // PAGE is always automatic/precompiled.
                label: 'Page Name',
             fields.PAGE = this.getAutomaticPageField();
                description: 'Name of the page.',
                type: 'page',
                required: true,
                namespace: 'Main',
                'allow-new': true
            };


             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'
                 );
                 );
            // Wrap selection


             const wrapGroup = $('<div>')
             const wrapGroup = $('<div>')
Line 973: Line 968:
                 )
                 )
             );
             );
            // Insert existing variable


             const variableGroup = $('<div>')
             const variableGroup = $('<div>')
Line 1,008: Line 1,001:
                 )
                 )
             );
             );
            // Insert condition


             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(
                 $('<span>').text(
                 createMetric(
                     variableCount +
                     variableCount,
                     ' variable' +
                     variableCount === 1
                     (
                        ? 'variable'
                        variableCount === 1
                        : 'variables'
                            ? ''
                ),
                            : 's'
                createMetric(
                    )
                     conditionCount,
                    conditionCount === 1
                        ? 'condition marker'
                        : 'condition markers'
                 ),
                 ),
                 $('<span>').text(
                 createMetric(
                     conditionCount +
                     configuredCount,
                    ' condition marker' +
                     configuredCount === 1
                     (
                        ? 'configured field'
                        conditionCount === 1
                        : 'configured fields'
                            ? ''
                            : 's'
                    )
                 )
                 )
             );
             );
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:
             });
             });


             Object.keys(
             const list =
                 this.state.config.fields || {}
                 $('<div>')
            ).forEach(name => {
                    .addClass(
                const upper =
                        'preload-editor-variable-list'
                     name.toUpperCase();
                     );


                if (
            ordered.forEach(name => {
                    !ordered.includes(upper)
                 const isSelected =
                 ) {
                    name ===
                     ordered.push(upper);
                     this.state.selectedVariable;
                }
            });


            ordered.forEach(name => {
                 const isUsed =
                 const field =
                     analysis.variables.includes(
                     this.state.config.fields[name] ||
                         name
                    this.createDefaultField(
                         name,
                        analysis
                     );
                     );


                 this.state.config.fields[name] =
                 const item =
                     field;
                    $('<button>')
 
                        .attr({
                 container.append(
                            type: 'button',
                     this.createVariableCard(
                            'data-variable': name
                        name,
                        })
                         field,
                        .addClass(
                         analysis
                            'preload-editor-variable-item'
                     )
                        )
                 );
                        .toggleClass(
             });
                            'is-selected',
         },
                            isSelected
 
                        )
         renderVariablesBySelector() {
                        .on(
             const container =
                            'click.preloadEditor',
                 this.root &&
                            () =>
                 this.root.find(
                                this.selectVariable(
                     '.preload-editor-variables'
                                    name
                 );
                                )
 
                        );
             if (
 
                 container &&
                const nameLabel =
                 container.length
                    $('<span>')
             ) {
                        .addClass(
                 this.renderVariables(
                            '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();


                     card.replaceWith(
                     this.renderVariableConfigurationBySelector();
                        this.createVariableCard(
                            name,
                            field,
                            analysis
                        )
                    );
 
                     this.renderConfigPreviewBySelector();
                     this.renderConfigPreviewBySelector();
                 }
                 }
Line 2,662: Line 3,036:
                 )
                 )
             );
             );
            if (name === 'PAGE') {
                field.type = 'page';
                const required =
                    this.checkbox(
                        field.required !== false
                    );
                required.on(
                    'change.preloadEditor',
                    () => {
                        field.required =
                            required.prop(
                                'checked'
                            );
                        this.markDirty();
                        this.renderConfigPreviewBySelector();
                    }
                );
                card.append(
                    this.fieldRow(
                        'Required',
                        required
                    )
                );
                const namespace =
                    this.select(
                        [
                            {
                                value: 'Main',
                                label: 'Main'
                            },
                            {
                                value: 'Template',
                                label: 'Template'
                            },
                            {
                                value: 'File',
                                label: 'File'
                            },
                            {
                                value: 'Category',
                                label: 'Category'
                            },
                            {
                                value: 'User',
                                label: 'User'
                            }
                        ],
                        field.namespace || 'Main'
                    );
                namespace.on(
                    'change.preloadEditor',
                    () => {
                        field.namespace =
                            namespace.val();
                        this.markDirty();
                        this.renderConfigPreviewBySelector();
                    }
                );
                card.append(
                    this.fieldRow(
                        'Namespace',
                        namespace
                    )
                );
                const allowNew =
                    this.checkbox(
                        field['allow-new'] === true
                    );
                allowNew.on(
                    'change.preloadEditor',
                    () => {
                        field['allow-new'] =
                            allowNew.prop(
                                'checked'
                            );
                        this.markDirty();
                        this.renderConfigPreviewBySelector();
                    }
                );
                card.append(
                    this.fieldRow(
                        'Allow new page',
                        allowNew
                    )
                );
                return card;
            }


             const required =
             const required =
Line 3,359: Line 3,632:
             const orderedFields = {};
             const orderedFields = {};


             if (fields.PAGE) {
             // PAGE is always first and automatic.
                orderedFields.PAGE =
            orderedFields.PAGE =
                    fields.PAGE;
                this.getAutomaticPageField();
            }


             Object.keys(fields)
             Object.keys(fields)