More actions
Create gadget |
Rewrite |
||
| Line 1: | Line 1: | ||
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); | |||
}); | |||
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);
});