Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Gadget-paragraphfinder.js: Difference between revisions

MediaWiki interface page
Create gadget
 
Rewrite
Line 1: Line 1:
( function () {
mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'ext.visualEditor.desktopArticleTarget.init', 'ext.cite.visualEditor'], function () {
  // integration with VE and the cite dialog
    // Wait for VisualEditor to be ready
  mw.hook( 've.startup' ).add( function () {
    if (!mw.config.get('wgVisualEditorConfig')) return;
    mw.hook( 've.ui.MWCitationDialog.dialogReady' ).add( function ( dialog ) {
      var surfaceModel = dialog.getSurface().getModel();
      var store = dialog.getModel();


      if ( store.get( 'schema' ) !== 'Cite book' ) return;
    const apiBase = 'https://wingsoffire.wiki/find_paragraph';


      // Prevent default content panel
    function showParagraphFinderDialog(onComplete) {
      dialog.content.clear();
        const windowManager = new OO.ui.WindowManager();
        $(document.body).append(windowManager.$element);


      // Build OOUI form
         const dialog = new OO.ui.ProcessDialog({
      var bookSelect = new OO.ui.DropdownInputWidget( {
            size: 'medium',
        options: mw.config.get( 'wgWingsofFireBooks' ).map( function ( b ) {
            title: 'Find Paragraph Automatically'
          return { data: b, label: b.replace( /_/g, ' ' ) };
         });
         } ),
        required: true
      } );
      var chapterSelect = new OO.ui.DropdownInputWidget( {
        required: true
      } );
      var quoteInput = new OO.ui.MultilineTextInputWidget( {
        placeholder: 'Paste the full paragraph here',
        required: true
      } );
      var findBtn = new OO.ui.ButtonWidget( {
        label: 'Find paragraph',
         flags: [ 'primary' ]
      } );


      // When book changes, load chapters
         dialog.getBodyHeight = function () {
      bookSelect.on( 'change', function ( val ) {
             return 250;
         if ( val ) {
         };
          mw.loader.using( 'mediawiki.api' ).done( function () {
            ( new mw.Api() ).get( {
              action: 'json',
              format: 'json',
              url: mw.config.get( 'wgScriptPath' ) + '/find_paragraph/books/' + encodeURIComponent( val ) + '/chapters'
            } ).done( function ( res ) {
              var chapters = res.available_chapters || [];
              chapterSelect.clearOptions();
              chapters.forEach( function ( c ) {
                chapterSelect.addOptions( [ { data: c, label: c } ] );
              } );
             } );
          } );
         }
      } );


      findBtn.on( 'click', function () {
         dialog.initialize = function () {
         var book = bookSelect.getValue(),
            OO.ui.ProcessDialog.prototype.initialize.apply(this, arguments);
            chapter = chapterSelect.getValue(),
            quote = quoteInput.getValue();
        if ( !book || !chapter || !quote ) {
          alert( 'Please select a book, chapter and paste the full paragraph.' );
          return;
        }
        findBtn.setDisabled( true );
        new mw.Api().post( {
          url: mw.config.get( 'wgScriptPath' ) + '/find_paragraph',
          method: 'POST',
          data: JSON.stringify( { book_name: book, chapter: chapter, paragraph: quote } ),
          contentType: 'application/json'
        } ).done( function ( data ) {
          dialog.close(); // close mini form
          // Open normal cite dialog
          var citationDialog = new ve.ui.MWCitationDialog();
          citationDialog.open();
          // Pre-fill on open
          citationDialog.getModel().once( 'change:paragraph', function () {
            var model = citationDialog.getModel();
            model.set( {
              book: book,
              chapter: chapter,
              paragraph: String( data.index ),
              quote: data.match
            } );
          } );
        } ).fail( function ( xhr ) {
          alert( xhr.responseJSON && xhr.responseJSON.reason || 'No match found.' );
        } ).always( function () {
          findBtn.setDisabled( false );
        } );
      } );


      // Assemble and display the form
            const dialogInstance = this;
      var panel = new OO.ui.PanelLayout( {
        expanded: false,
        padded: true,
        framed: true
      } );
      panel.$element.append(
        new OO.ui.FieldLayout( bookSelect, { label: 'Book', align: 'top' } ).$element,
        new OO.ui.FieldLayout( chapterSelect, { label: 'Chapter', align: 'top' } ).$element,
        new OO.ui.FieldLayout( quoteInput, { label: 'Paragraph text', align: 'top' } ).$element,
        findBtn.$element
      );
      dialog.content.add( panel );
      bookSelect.focus();
    } );
  } );


  // Preload book list on page load
            // UI Elements
  mw.loader.using( 'mediawiki.api' ).then( function () {
            this.bookSelect = new OO.ui.DropdownInputWidget({
    new mw.Api().get( {
                label: 'Select book',
      url: mw.config.get( 'wgScriptPath' ) + '/find_paragraph/books'
                options: [{ data: '', label: 'Loading…' }],
     } ).done( function ( res ) {
                required: true
      mw.config.set( 'wgWingsofFireBooks', res.available_books );
            });
    } );
 
  } );
            this.chapterSelect = new OO.ui.DropdownInputWidget({
}() );
                label: 'Select chapter',
                options: [{ data: '', label: 'Choose a book first' }],
                required: true
            });
 
            this.quoteInput = new OO.ui.MultilineTextInputWidget({
                placeholder: 'Paste the full paragraph you want to cite',
                autosize: true,
                rows: 4
            });
 
            this.errorLabel = new OO.ui.LabelWidget({
                label: '',
                classes: ['error'],
                invisibleLabel: true
            });
 
            this.panel = new OO.ui.PanelLayout({
                padded: true,
                expanded: false
            });
 
            this.panel.$element.append(
                new OO.ui.LabelWidget({ label: 'Book:' }).$element,
                this.bookSelect.$element,
                new OO.ui.LabelWidget({ label: 'Chapter:' }).$element,
                this.chapterSelect.$element,
                new OO.ui.LabelWidget({ label: 'Quote:' }).$element,
                this.quoteInput.$element,
                this.errorLabel.$element
            );
 
            this.$body.append(this.panel.$element);
 
            // Load book list
            fetch(apiBase + '/books')
                .then((r) => r.json())
                .then((data) => {
                    const opts = data.available_books.map((book) => ({
                        data: book,
                        label: book.replace(/_/g, ' ')
                    }));
                    this.bookSelect.setOptions(opts);
                });
 
            // Load chapter list when book changes
            this.bookSelect.on('change', (book) => {
                this.chapterSelect.setOptions([{ data: '', label: 'Loading…' }]);
                fetch(`${apiBase}/books/${encodeURIComponent(book)}/chapters`)
                    .then((r) => r.json())
                    .then((data) => {
                        const opts = data.available_chapters.map((ch) => ({
                            data: ch,
                            label: ch
                        }));
                        this.chapterSelect.setOptions(opts);
                    });
            });
        };
 
        dialog.getActionProcess = function (action) {
            if (action === 'find') {
                const book = this.bookSelect.getValue();
                const chapter = this.chapterSelect.getValue();
                const quote = this.quoteInput.getValue();
 
                if (!book || !chapter || !quote.trim()) {
                    this.errorLabel.setLabel('Please fill in all fields.');
                    return new OO.ui.Process(() => {});
                }
 
                this.errorLabel.setLabel('');
 
                return new OO.ui.Process(() => {
                    return fetch(apiBase, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                            book_name: book,
                            chapter: chapter,
                            paragraph: quote
                        })
                    })
                        .then(async (response) => {
                            if (!response.ok) {
                                const err = await response.json();
                                throw new Error(err.reason || 'No match found');
                            }
                            return response.json();
                        })
                        .then((result) => {
                            onComplete({
                                book,
                                chapter,
                                paragraph: result.index,
                                quote: result.match
                            });
                            windowManager.closeWindow(dialog);
                        })
                        .catch((err) => {
                            this.errorLabel.setLabel(err.message);
                        });
                });
            }
            return new OO.ui.Process(() => windowManager.closeWindow(dialog));
        };
 
        dialog.getActions = function () {
            return [
                { action: 'find', label: 'Find Paragraph', flags: ['primary', 'progressive'] },
                { action: 'cancel', label: 'Cancel', flags: ['safe', 'close'] }
            ];
        };
 
        windowManager.addWindows([dialog]);
        windowManager.openWindow(dialog);
     }
 
    function interceptCiteBookTemplate() {
        const originalGetTemplates = ve.init.target.ui.MWCitationDialog.prototype.getTemplateOptions;
 
        ve.init.target.ui.MWCitationDialog.prototype.getTemplateOptions = function () {
            const templates = originalGetTemplates.call(this);
 
            // Find Cite book template
            const citeBook = templates.find(t => t.template && t.template.getName() === 'Cite book');
 
            if (citeBook && !citeBook._paragraphFinderPatched) {
                citeBook._paragraphFinderPatched = true;
 
                const originalAction = citeBook.action;
 
                citeBook.action = () => {
                    showParagraphFinderDialog((data) => {
                        // Call original Cite book dialog
                        originalAction.call(citeBook);
 
                        // Wait for dialog to be ready
                        setTimeout(() => {
                            const dialog = ve.ui.windowFactory.getOpenedWindows()[0].dialog;
 
                            dialog.bookWidget.setValue(data.book);
                            dialog.chapterWidget.setValue(data.chapter);
                            dialog.paragraphWidget.setValue(data.paragraph.toString());
                            dialog.quoteWidget.setValue(data.quote);
                        }, 500);
                    });
                };
            }
 
            return templates;
        };
    }
 
    // Hook into VE once it loads
    mw.hook('ve.activationComplete').add(interceptCiteBookTemplate);
});

Revision as of 06:46, 22 July 2025

mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'ext.visualEditor.desktopArticleTarget.init', 'ext.cite.visualEditor'], function () {
    // Wait for VisualEditor to be ready
    if (!mw.config.get('wgVisualEditorConfig')) return;

    const apiBase = 'https://wingsoffire.wiki/find_paragraph';

    function showParagraphFinderDialog(onComplete) {
        const windowManager = new OO.ui.WindowManager();
        $(document.body).append(windowManager.$element);

        const dialog = new OO.ui.ProcessDialog({
            size: 'medium',
            title: 'Find Paragraph Automatically'
        });

        dialog.getBodyHeight = function () {
            return 250;
        };

        dialog.initialize = function () {
            OO.ui.ProcessDialog.prototype.initialize.apply(this, arguments);

            const dialogInstance = this;

            // UI Elements
            this.bookSelect = new OO.ui.DropdownInputWidget({
                label: 'Select book',
                options: [{ data: '', label: 'Loading…' }],
                required: true
            });

            this.chapterSelect = new OO.ui.DropdownInputWidget({
                label: 'Select chapter',
                options: [{ data: '', label: 'Choose a book first' }],
                required: true
            });

            this.quoteInput = new OO.ui.MultilineTextInputWidget({
                placeholder: 'Paste the full paragraph you want to cite',
                autosize: true,
                rows: 4
            });

            this.errorLabel = new OO.ui.LabelWidget({
                label: '',
                classes: ['error'],
                invisibleLabel: true
            });

            this.panel = new OO.ui.PanelLayout({
                padded: true,
                expanded: false
            });

            this.panel.$element.append(
                new OO.ui.LabelWidget({ label: 'Book:' }).$element,
                this.bookSelect.$element,
                new OO.ui.LabelWidget({ label: 'Chapter:' }).$element,
                this.chapterSelect.$element,
                new OO.ui.LabelWidget({ label: 'Quote:' }).$element,
                this.quoteInput.$element,
                this.errorLabel.$element
            );

            this.$body.append(this.panel.$element);

            // Load book list
            fetch(apiBase + '/books')
                .then((r) => r.json())
                .then((data) => {
                    const opts = data.available_books.map((book) => ({
                        data: book,
                        label: book.replace(/_/g, ' ')
                    }));
                    this.bookSelect.setOptions(opts);
                });

            // Load chapter list when book changes
            this.bookSelect.on('change', (book) => {
                this.chapterSelect.setOptions([{ data: '', label: 'Loading…' }]);
                fetch(`${apiBase}/books/${encodeURIComponent(book)}/chapters`)
                    .then((r) => r.json())
                    .then((data) => {
                        const opts = data.available_chapters.map((ch) => ({
                            data: ch,
                            label: ch
                        }));
                        this.chapterSelect.setOptions(opts);
                    });
            });
        };

        dialog.getActionProcess = function (action) {
            if (action === 'find') {
                const book = this.bookSelect.getValue();
                const chapter = this.chapterSelect.getValue();
                const quote = this.quoteInput.getValue();

                if (!book || !chapter || !quote.trim()) {
                    this.errorLabel.setLabel('Please fill in all fields.');
                    return new OO.ui.Process(() => {});
                }

                this.errorLabel.setLabel('');

                return new OO.ui.Process(() => {
                    return fetch(apiBase, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                            book_name: book,
                            chapter: chapter,
                            paragraph: quote
                        })
                    })
                        .then(async (response) => {
                            if (!response.ok) {
                                const err = await response.json();
                                throw new Error(err.reason || 'No match found');
                            }
                            return response.json();
                        })
                        .then((result) => {
                            onComplete({
                                book,
                                chapter,
                                paragraph: result.index,
                                quote: result.match
                            });
                            windowManager.closeWindow(dialog);
                        })
                        .catch((err) => {
                            this.errorLabel.setLabel(err.message);
                        });
                });
            }
            return new OO.ui.Process(() => windowManager.closeWindow(dialog));
        };

        dialog.getActions = function () {
            return [
                { action: 'find', label: 'Find Paragraph', flags: ['primary', 'progressive'] },
                { action: 'cancel', label: 'Cancel', flags: ['safe', 'close'] }
            ];
        };

        windowManager.addWindows([dialog]);
        windowManager.openWindow(dialog);
    }

    function interceptCiteBookTemplate() {
        const originalGetTemplates = ve.init.target.ui.MWCitationDialog.prototype.getTemplateOptions;

        ve.init.target.ui.MWCitationDialog.prototype.getTemplateOptions = function () {
            const templates = originalGetTemplates.call(this);

            // Find Cite book template
            const citeBook = templates.find(t => t.template && t.template.getName() === 'Cite book');

            if (citeBook && !citeBook._paragraphFinderPatched) {
                citeBook._paragraphFinderPatched = true;

                const originalAction = citeBook.action;

                citeBook.action = () => {
                    showParagraphFinderDialog((data) => {
                        // Call original Cite book dialog
                        originalAction.call(citeBook);

                        // Wait for dialog to be ready
                        setTimeout(() => {
                            const dialog = ve.ui.windowFactory.getOpenedWindows()[0].dialog;

                            dialog.bookWidget.setValue(data.book);
                            dialog.chapterWidget.setValue(data.chapter);
                            dialog.paragraphWidget.setValue(data.paragraph.toString());
                            dialog.quoteWidget.setValue(data.quote);
                        }, 500);
                    });
                };
            }

            return templates;
        };
    }

    // Hook into VE once it loads
    mw.hook('ve.activationComplete').add(interceptCiteBookTemplate);
});
Cookies help us deliver our services. By using our services, you agree to our use of cookies.