Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* jshint undef: true, devel: true, typed: true, jquery: true, strict: true, eqeqeq: true, freeze: true, latedef: true, shadow: outer, varstmt: true, quotmark: single, esversion: 6, futurehostile: true */
/* global importArticles */
if (
/* Only actual article viewing */
mw.config.values.wgAction === 'view' &&
!mw.config.values.wgDiffNewId &&
!document.body.classList.contains('ve-active') &&
!document.body.classList.contains('ve-loading')
) {
const config = mw.config.values;
const api = new mw.Api();
const init = (ccmLoader) => {
window.dev.ESE = window.dev.ESE || {};
if (window.dev.ESE.__LOADED) {return;}
else {window.dev.ESE.__LOADED = true; document.body.classList.add('ese-init')}
let popup;
document.querySelectorAll('.mw-editsection').forEach((edit)=>{
let section = edit.querySelector('a').getAttribute('href').replace(/^.+\§ion=/, '');
if (isNaN(section)) {return;} // avoid transcluded sections
edit.setAttribute('section', section);
edit.querySelectorAll('a').forEach((a)=>{
edit.setAttribute('data-href', a.getAttribute('href'));
a.removeAttribute('href');
a.setAttribute('tabindex', '0');
});
edit.classList.add('ese');
});
mw.hook('dev.modal').add((Modal)=>{ // Create popup
popup = new Modal.Modal({
title: 'Preview',
id: 'EmbeddedSectionEditor',
size: 'full',
content: ''
});
popup.create();
});
mw.hook('dev.CCM.ready').add((cm)=>{ // Enable functions and UI once the editor finishes loading
cm.view.focus();
let wrapper = $('.ese-wrapper'),
ctrl = $('.ese-controls'),
sections = wrapper.data('sections'),
delElements = [],
prevEl = wrapper[0],
contentChanged = ()=>{return cm.view.state.sliceDoc() !== sections[2].trim()};
while (true) {
let elem = prevEl.nextElementSibling;
if (!elem) {break;}
let elemlvl = elem.nodeName.match(/h(\d+)/i) ? elem.nodeName.match(/h(\d+)/i)[1] : '0';
if (
elemlvl === '0' ||
wrapper.data('level') < elemlvl
) {
delElements.push(elem);
prevEl = elem;
}
else {break;}
}
delElements = $(delElements);
delElements.hide();
let badSave = (e)=>{if (contentChanged()) {e.preventDefault()}};
window.addEventListener('beforeunload', badSave);
ctrl.find('.ese-close').on('click keyup', ()=>{
if (event.type === 'keyup' && event.key !== 'Enter') {return;}
if (
!!mw.user.options.values.useeditwarning && contentChanged() &&
!window.confirm('Changes made will not be saved!\nClose the editor anyway?') // idk why this returns true when clicking "cancel"
) {
return;
}
window.removeEventListener('beforeunload', badSave);
ctrl.remove();
wrapper.remove();
$('.ese-loaded').toggleClass('ese-loaded', false);
delElements.show();
});
ctrl.find('.ese-submit').on('click keyup', ()=>{
if (event.type === 'keyup' && event.key !== 'Enter') {return;}
let value = cm.view.state.sliceDoc();
if (value === sections[2].trim()) {mw.notify('No changes performed!'); return;}
if (!!mw.user.options.values.forceeditsummary && ctrl.find('.ese-summary').val().trim().length===0) {mw.notify('Edit summary is empty!'); return;}
api.postWithEditToken({
action: 'edit',
title: config.wgPageName,
text: sections[1]+value+sections[3],
minor: $('.ese-opt [value="minor"]').prop('checked'),
summary: '/* '+wrapper.data('name')+' */ '+ctrl.find('.ese-summary').val()
}).then(()=>{
mw.notify('Edit posted!');
api.get({
action: 'parse',
text: value,
prop: 'text',
disablelimitreport: true,
contentmodel: 'wikitext',
title: config.wgPageName
}).then((p)=>{
if (p && p.parse && p.parse.text && p.parse.text['*']) {
window.removeEventListener('beforeunload', badSave);
delElements.remove();
wrapper.replaceWith(p.parse.text['*']);
ctrl.remove();
$('.ese-loaded').toggleClass('ese-loaded', false);
}
}).catch(console.log);
}).catch(console.log);
});
ctrl.find('.ese-preview').on('click keyup', ()=>{
if (event.type === 'keyup' && event.key !== 'Enter') {return;}
let text = cm.view.state.sliceDoc();
let params = {
action: 'parse',
text: text,
prop: 'text',
title: config.wgPageName,
disablelimitreport: true,
contentmodel: 'wikitext'
};
api.post(params).then((p)=>{
if (p && p.parse && p.parse.text && p.parse.text['*']) {
popup.show();
let preview = $('<div>', {
'class': 'page-content',
html: $('<div>', {
'class':'page__main',
html: $('<div>', {
'class':'page',
html: $('<div>', {
'class':'main-container',
html: $('<div>', {
'class':'mw-parser-output',
html: [
$('h'+wrapper.data('level'), {text: wrapper.data('section')}),
$(p.parse.text['*'])
]
})
})
})
})
});
//console.log(preview);
popup.setContent(preview.get(0).outerHTML);
mw.hook('wikipage.content').fire(preview);
}
}).fail(console.log);
});
});
$('body').on('click keyup', '.ese', (event) => {
if (event.type === 'keyup' && event.key !== 'Enter') {return;}
// Avoid double render
let pencil = event.currentTarget;
if (pencil.classList.contains('ese-loaded')) {return;}
else if (document.querySelector('.ese-loaded')) {
mw.notify('Editor already open, please close it or submit your edit before attempting again!');
return;
}
pencil.classList.add('ese-loaded');
let header = pencil.closest('h1, h2, h3, h4, h5, h6');
let section = header.querySelector('.mw-headline').innerHTML;
let level = (header.nodeName.match(/\d+/)||['2'])[0];
let index = pencil.getAttribute('section');
api.get({
action: 'query',
titles: config.wgPageName,
prop: 'revisions',
rvslots: '*',
rvprop: 'content',
meta: 'tokens',
type: 'csrf'
}).then((data)=>{
let page_content = Object.entries(data.query.pages)[0][1].revisions[0].slots.main['*'];
let pre = [];
for (let i = 0; i < index; i++) {
let sp = RegExp('^([\\s\\S]*?\\n*==+.+[\\s\\n]*)([\\s\\S]*?$|$)').exec(page_content);
while (/<!--(?![\s\S]*?-->)[\s\S]*?$/.exec(sp[1]) && /^[\s\S]*?(?<!<!--[\s\S]*?)-->/.exec(sp[2])) {
if (/\n*==+.+[\s\n]*$/.exec(sp[1])) {i--;}
let spcomm = /^([\s\S]*?(?<!<!--[\s\S]*?)-->)([\s\S]*?)$/.exec(sp[2]);
sp[1]+=spcomm[1];
sp[2]=spcomm[2];
}
pre.push(sp[1]);
page_content = sp[2];
}
pre = pre.join('');
let sections = RegExp('^([\\s\\S]*?)(\\n+={2,'+level+'}[^=][\\s\\S]*?$|$)').exec(page_content);
while (/<!--(?![\s\S]*?-->)[\s\S]*?$/.exec(sections[1]) && /^[\s\S]*?(?<!<!--[\s\S]*?)-->/.exec(sections[2])) {
let comm = /^([\s\S]*?(?<!<!--[\s\S]*?)-->)([\s\S]*?)$/.exec(sections[2]);
sections[1]+=comm[1];
sections[2]=comm[2];
}
sections = [pre+page_content, pre, sections[1], sections[2]];
let wrapper = $('<div>',{
'class': 'ese-wrapper',
data: {
level: level,
sections: sections,
name: section,
index: index
},
html: $('<textarea>', {'class': 'ese-editor'})
});
let ctrl = $('<div>', {
'class': 'ese-controls',
html: [
$('<span>', {'class': 'wds-button ese-submit', tabindex: '0', text: 'Submit', title: 'Submit the current wikitext'}),
$('<span>', {'class': 'wds-button ese-preview', tabindex: '0', text: 'Preview', title: 'Preview the current wikitext'}),
$('<input>', {'class': 'ese-summary', placeholder: 'Edit summary.', title: 'Summary about the edit\'s changes'}),
$('<div>', {
'class': 'ese-grid',
html: [
$('<label>', {
'class': 'ese-opt',
html: [
'm',
$('<input>', {
'class': 'ese-opt-input',
type: 'checkbox',
value: 'minor',
title: 'Mark edit as minor',
checked: !!mw.user.options.values.minordefault
})
]
}),
$('<a>', {
href: pencil.getAttribute('data-href'),
tabindex: '-1',
html: $('<span>', {
'class': 'wds-button wds-icon-small ese-open',
title: 'Open the default version of section editing.',
tabindex: '0',
style: 'padding: 0;'
}).append('<svg viewBox="2 2 20 20"><path d="M5 3c-1.093 0-2 .907-2 2v14c0 1.093.907 2 2 2h14c1.093 0 2-.907 2-2v-7h-2v7H5V5h7V3zm9 0v2h3.586l-9.293 9.293 1.414 1.414L19 6.414V10h2V3z" /></svg>')
}),
$('<span>', {
'class': 'wds-button wds-icon-small ese-close',
tabindex: '0',
text: 'x',
title: 'Close the current editor instance. No changes will be applied or saved.', style: 'padding: 0; font-size: large;'
}),
]
}),
]
});
$(header).after(wrapper);
$(pencil).after(ctrl);
wrapper.find('textarea').val(sections[2]);
ctrl.get(0).scrollIntoView();
ccmLoader(wrapper.find('textarea'));
});
});
};
// Launch
mw.hook('dev.CCM.load').add(init);
importArticles({ type: 'style', article: 'u:dev:MediaWiki:EmbeddedSectionEditor.css' });
importArticles({ type: 'script', articles: [ 'u:dev:MediaWiki:Modal.js', 'u:dev:MediaWiki:CustomCodeMirror.js' ] });
}