User:Nardog/IPAInput-core.js

(function ipaInputCore() {

mw.loader.addStyleTag('.ipainput-config{padding:0 1em 1em} .ipainput-input{position:sticky;top:0;left:0;opacity:0.8;font-size:200%;z-index:999} .ipainput-input > input{text-align:center} .ipainput .ipainput-input.oo-ui-indicatorElement > input{padding-right:56px} .ipainput .ipainput-input > .oo-ui-indicator-clear, .ipainput-symbol{cursor:pointer} .ipainput-undo, .ipainput-diaonly{font-size:50%;position:absolute;right:0;margin:0} .ipainput-diaonly{top:150%} .ipainput-status{text-align:center;font-size:120%;padding:1em 0 0.5em} .ipainput-status > a{font-weight:bold} .ipainput .mw-parser-output{margin:auto;width:max-content;max-width:100%;padding:0 0.5em;overflow:auto} .ipainput-symbol:hover{background-color:#fff} .ipainput-symbol:active{background-color:#c8ccd1} .ipainput-symbol:focus{outline:solid 2px #36c} .ipainput-symbol-disabled, .ipainput-symbol-disabled:hover, .ipainput-symbol-disabled:active, .ipainput-symbol-disabled:focus{cursor:auto;background-color:#c8ccd1 !important;outline:0} .ipainput-symbol-disabled, .ipainput-symbol-disabled a{color:#fff}');

let promise = mw.loader.using([

'jquery.textSelection', 'oojs-ui-windows', 'oojs-ui-widgets',

'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-editing-core',

'oojs-ui.styles.icons-editing-advanced'

]);

let langs = mw.storage.getObject('ipainput-cache');

if (!langs) {

mw.notify('Retrieving keys...', { autoHide: false, tag: 'ipainput' });

promise = $.when(promise, $.post('//en.wikipedia.org/api/rest_v1/transform/wikitext/to/html', {

wikitext: '{{#invoke:IPA/overview|keys}}',

body_only: true

}).then(response => {

langs = {

'(Full IPA chart)': [

['und', 'Undetermined'],

['', '(No linking)']

],

'English': [

['en', 'English']

]

};

let lastKey, lastLang;

$($.parseHTML(response)).find('td:last-child').each(function () {

let key, lang;

let prev = this.previousElementSibling;

if (prev) {

lang = prev.textContent;

lastLang = lang;

let prevPrev = prev.previousElementSibling;

if (prevPrev) {

key = prevPrev.textContent.slice(9);

lastKey = key;

} else {

key = lastKey;

}

} else {

key = lastKey;

lang = lastLang;

}

if (key === 'English') return;

if (!langs.hasOwnProperty(key)) langs[key] = [];

langs[key].push([this.textContent, lang]);

});

mw.requestIdleCallback(() => {

let notif = $('.mw-notification-tag-ipainput').data('mw-notification');

if (notif) notif.close();

mw.storage.setObject('ipainput-cache', langs, 604800);

});

}));

}

promise.then(() => {

function IpaInputDialog(config) {

IpaInputDialog.parent.call(this, config);

this.$element.addClass('ipainput');

}

OO.inheritClass(IpaInputDialog, OO.ui.ProcessDialog);

IpaInputDialog.static.name = 'ipaInputDialog';

IpaInputDialog.static.title = 'IPAInput';

IpaInputDialog.static.size = 'small';

IpaInputDialog.static.actions = [

{

modes: 'config',

flags: ['safe', 'close']

},

{

action: 'transcribe',

label: 'Transcribe',

modes: 'config',

flags: ['primary', 'progressive']

},

{

action: 'goBack',

modes: 'transcription',

flags: ['safe', 'back']

},

{

action: 'insert',

label: 'Insert',

modes: 'transcription',

flags: ['primary', 'progressive']

}

];

IpaInputDialog.prototype.initialize = function () {

IpaInputDialog.parent.prototype.initialize.apply(this, arguments);

this.keyDropdown = new OO.ui.DropdownWidget({

$overlay: this.$overlay,

menu: {

items: Object.keys(langs).map(k => (

new OO.ui.MenuOptionWidget({ label: k })

))

}

});

this.keyDropdown.getMenu().on('select', item => {

let options = langs[item.getLabel()].map(([code, lang]) => (

new OO.ui.MenuOptionWidget({

data: code,

label: code ? `${lang} (${code})` : lang

})

));

this.languageDropdown.getMenu().clearItems().addItems(options)

.selectItem(options[0]);

});

this.languageDropdown = new OO.ui.DropdownWidget({

$overlay: this.$overlay

});

this.noTemplateCheckbox = new OO.ui.CheckboxInputWidget()

.connect(this.languageDropdown, { change: 'setDisabled' });

this.rememberCheckbox = new OO.ui.CheckboxInputWidget();

this.form = new OO.ui.FormLayout({

items: [

new OO.ui.FieldLayout(this.keyDropdown, {

label: 'Key:',

align: 'top'

}),

new OO.ui.FieldLayout(this.languageDropdown, {

label: 'Language:',

align: 'top'

}),

new OO.ui.FieldLayout(this.noTemplateCheckbox, {

label: 'No template',

align: 'inline'

}),

new OO.ui.FieldLayout(this.rememberCheckbox, {

label: 'Remember these for next time',

align: 'inline'

})

],

content: [$('').attr({ type: 'submit', hidden: '' })],

classes: ['ipainput-config']

}).connect(this, { submit: ['executeAction', 'transcribe'] });

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

this.input = new OO.ui.TextInputWidget({

spellcheck: false,

classes: ['ipainput-input', 'IPA']

}).on('change', value => {

this.input.setIndicator(value ? 'clear' : null);

}).connect(this, { enter: ['executeAction', 'insert'] });

this.input.$input.on('keydown', e => {

if (e.which !== 27 || !this.input.getValue()) return;

e.stopPropagation();

this.input.setValue('');

});

this.input.$indicator.on('click', () => {

this.input.setValue('').focus();

});

this.$status = $('

').addClass('ipainput-status');

this.$parserOutput = $('

').attr({

class: 'mw-parser-output mw-body-content',

lang: 'en'

}).on('click', '.ipainput-symbol', e => {

if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey) return;

e.preventDefault();

e.stopPropagation();

if (e.currentTarget.classList.contains('ipainput-symbol-disabled')) {

return;

}

let $target = $(e.currentTarget).clone().find('.IPA');

if (!$target.length) $target = $target.end();

$target.find('.reference').remove();

let ins = $target.text().trim()

.replace(/◌|^[\(\/\[]+\s*(?=\S)|(\S)\s*[\)\/\]]+$/g, '$1');

if (e.currentTarget.classList.contains('ipainput-symbol-dia') &&

this.diaOnlyButton.getValue()

) {

let match = ins.normalize('NFD').match(/[^̧\P{Mn}]+/u);

if (match) ins = match[0];

}

let start = this.input.$input.prop('selectionStart');

let end = this.input.$input.prop('selectionEnd');

let text = this.input.getValue();

let pos = start + ins.length;

let newText = text.slice(0, start) + ins + text.slice(end);

this.input.setValue(newText).selectRange(pos).focus();

if (this.undoCache.length

? text !== this.undoCache[this.undoCache.length - 1][0]

: text

) {

this.undoCache.push([text, end]);

}

this.undoCache.push([newText, pos]);

this.undoCache = this.undoCache.slice(-500);

}).on('keydown', '.ipainput-symbol', function (e) {

if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey) return;

if (e.which === 13 || e.which === 32) {

e.preventDefault();

e.stopPropagation();

this.click();

this.focus();

}

});

this.undoButton = new OO.ui.ButtonWidget({

icon: 'undo',

invisibleLabel: true,

label: 'Undo',

classes: ['ipainput-undo']

}).on('click', () => {

let arr = this.undoCache.pop();

if (!arr) {

this.input.setValue('').focus();

return;

}

if (this.undoCache.length && this.input.getValue() === arr[0]) {

arr = this.undoCache.pop();

}

this.input.setValue(arr[0]).selectRange(arr[1]).focus();

});

this.diaOnlyButton = new OO.ui.ToggleButtonWidget({

icon: 'searchDiacritics',

invisibleLabel: true,

label: 'Insert diacrtics only',

classes: ['ipainput-diaonly']

}).on('change', enabled => {

this.$parserOutput.find(

'.ipainput-symbol:not(.ipainput-symbol-dia)'

).attr({

tabindex: enabled ? -1 : 0,

'aria-disabled': enabled ? 'true' : null

}).toggleClass('ipainput-symbol-disabled', enabled);

});

this.input.$element.append(

this.undoButton.$element, this.diaOnlyButton.$element

);

this.$transcription = $([

this.input.$element[0], this.$status[0], this.$parserOutput[0]

]);

};

IpaInputDialog.prototype.getSetupProcess = function (data) {

return IpaInputDialog.super.prototype.getSetupProcess.call(this, data).next(function () {

let storage = (mw.storage.get('ipainput') || '').split('|');

let key = langs.hasOwnProperty(storage[0])

? storage[0]

: 'English';

this.keyDropdown.getMenu().selectItemByLabel(key);

if (storage[1] === 'null') {

this.noTemplateCheckbox.setSelected(true);

} else if (langs[key].some(([k]) => k === storage[1])) {

this.languageDropdown.getMenu().selectItemByData(storage[1]);

}

this.rememberCheckbox.setSelected(storage[0]);

this.actions.setMode('config');

}, this);

};

IpaInputDialog.prototype.getKey = function () {

let isGeneric = this.keyName === '(Full IPA chart)';

let pageName = isGeneric

? 'International Phonetic Alphabet chart'

: 'Help:IPA/' + this.keyName;

this.actions.get()[3].setDisabled(true);

this.pushPending();

this.input.setValue('');

this.undoCache = [];

this.$status.empty().append(

'Loading ',

$('').attr({

href: mw.util.getUrl(pageName),

target: '_blank'

}).text(pageName),

'...'

);

this.$parserOutput.empty();

$.get(

'//en.wikipedia.org/api/rest_v1/page/html/' +

encodeURIComponent(pageName.replace(/ /g, '_'))

).then(data => {

this.curKeyName = this.keyName;

let $data = $($.parseHTML(data));

let $tables = $data.filter('section').children().unwrap();

if (isGeneric) {

$tables = $tables.filter('h2#Vowels').nextUntil('#See_also').addBack();

} else {

$tables = $tables.filter(function () {

return this.classList.contains('wikitable') ||

this.querySelector('.wikitable');

});

if ($tables.length > 1) {

$tables = $tables.first().nextUntil($tables.last().next()).addBack();

}

}

$tables.find('.IPA').filter(function () {

return /[\s,~]/.test(this.textContent.trim()) || this.querySelector('br');

}).find('*').addBack().contents().filter(function () {

return this.nodeType === 3;

}).replaceWith(function () {

return this.textContent.split(/([\s,~]+)/).reduce((acc, s, i) => {

if (s) {

acc.push(i % 2 ? s : $('').attr({

class: 'ipainput-symbol',

tabindex: 0,

role: 'button'

}).text(s));

}

return acc;

}, []);

});

$tables.find('td, th').filter(function () {

if (this.querySelector('.ipainput-symbol, .IPA-vowels-container')) return;

return this.classList.contains('IPA') ||

this.querySelector('.IPA') &&

!this.querySelector('br, p') &&

!$(this).find(':not(.IPA, .IPA *, .reference, .reference *)').addBack().contents().get()

.some(n => n.nodeType === 3 && n.textContent.trim());

}).addClass('ipainput-symbol').attr({

tabindex: 0,

role: 'button'

});

let $spans = $tables.find('.IPA:not(.ipainput-symbol, .ipainput-symbol .IPA)').filter(function () {

return !this.querySelector('.ipainput-symbol');

});

let consec = [];

$spans.filter(function (i) {

if ($spans[i + 1] === this.nextSibling) {

consec.push(this);

} else if (consec.length) {

consec.push(this);

$(consec).wrapAll('').parent()

.addClass('ipainput-symbol').attr({

tabindex: 0,

role: 'button'

});

consec.length = 0;

} else {

return true;

}

}).addClass('ipainput-symbol').attr({

tabindex: 0,

role: 'button'

});

$tables.find('[id], [about]').addBack().removeAttr('id about');

$tables.find('a').attr({

target: '_blank',

tabindex: -1

}).filter('[href^="./"]').attr('href', function () {

return '//en.wikipedia.org/wiki' +

this.getAttribute('href').slice(1);

});

let hasDia = $tables.find('.ipainput-symbol').filter(function () {

return /[^̧\P{Mn}]/u.test(this.textContent.normalize('NFD'));

}).addClass('ipainput-symbol-dia').length && true;

this.diaOnlyButton.setValue().toggle(hasDia);

let modules = (

$data.filter('meta[property="mw:moduleStyles"]').attr('content') || ''

).split('|');

return mw.loader.using(modules).always(() => {

this.$status.html(this.$status.children());

this.$parserOutput.append($tables);

this.updateSize();

this.actions.get()[3].setDisabled();

});

}, data => {

let msg = ((data || {}).responseJSON || {}).title;

if (msg && data.responseJSON.detail) {

msg += ': ' + data.responseJSON.detail;

}

this.$status.text(msg || 'Unknown error');

}).always(() => {

this.popPending();

});

};

IpaInputDialog.prototype.getActionProcess = function (action) {

if (action === 'transcribe') {

this.keyName = this.keyDropdown.getMenu().findSelectedItem().getLabel();

this.langCode = this.noTemplateCheckbox.isSelected()

? null

: this.languageDropdown.getMenu().findSelectedItem().getData();

this.actions.setMode('transcription');

this.form.toggle(false).$element.after(this.$transcription);

this.setSize('larger');

if (this.keyName !== this.curKeyName) {

this.getKey();

}

$(document).on('keydown.ipaInput', e => {

if (e.shiftKey || e.altKey) return;

if (e.which === 90 &&

[e.ctrlKey, e.metaKey].filter(Boolean).length === 1

) {

e.preventDefault();

this.undoButton.emit('click');

}

});

this.input.focus();

mw.requestIdleCallback(() => {

mw.storage.remove('IpaInput-keyName');

mw.storage.remove('IpaInput-template');

if (this.rememberCheckbox.isSelected()) {

mw.storage.set(

'ipainput',

this.keyName + '|' + this.langCode,

31556952

);

} else {

mw.storage.remove('ipainput');

}

});

} else {

$(document).off('keydown.ipaInput');

this.actions.setMode('config');

this.$transcription.detach();

this.form.toggle(true);

this.setSize('small');

if (action === 'insert') {

let text = this.input.getValue().trim(), template;

if (this.keyName === 'English') {

text = text

.replace(/\s+/g, '_')

.replace(/a[ɪʊ]ər|ɔɪər|[ɛɪʊ]ə[ˈˌ]r|\.\.\.|[ɑɔɜ]ːr|[ɛɪʊ]ər|!!|,_|a[ɪʊ]|[dlnstzθ]j(?=u|ʊə)|dʒ|eɪ|hw|[iuɑɔɜ]ː|oʊ|tʃ|[æɒɛɪʊʌ]r|[æɒ]̃|ɔɪ|(?:(?<=[bdfkprstvxzðɡʃʒʔθ]\.?)ə[ln]|(?<=[fsvzðʃʒθ]\.?)əm|ər)(?![aeæɑɒɔɛɜʊʌˈˌ]|[iu]ː|ɪə)|[!#\(\)\-\._bdfhijklmnprstuvwxzæðŋɒəɛɡɪʃʊʌʒʔˈˌθ]/g, '$&|')

.replace(/\|$/, '');

template = 'IPAc-en';

} else {

template = 'IPA';

if (this.langCode) {

text = this.langCode + '|' + text;

}

}

if (document.documentElement.classList.contains('ve-active')) {

text = this.langCode === null ? text : [{

type: 'mwTransclusionInline',

attributes: {

mw: {

parts: [{

template: {

target: { wt: template },

params: text.split('|').reduce((acc, s, i) => {

acc[i + 1] = { wt: s };

return acc;

}, {})

}

}]

}

}

}];

ve.init.target.getSurface().getModel().getFragment().collapseToEnd()

.insertContent(text).collapseToEnd().select();

} else {

if (this.langCode !== null) {

text = `{{${template}|${text}}}`;

}

$('#wpTextbox1').textSelection('encapsulateSelection', {

peri: text,

replace: true

});

}

this.input.setValue('');

this.undoCache = [];

this.close();

}

}

return IpaInputDialog.super.prototype.getActionProcess.call(this, action);

};

window.ipaInputDialog = new IpaInputDialog();

let winMan = new OO.ui.WindowManager();

winMan.addWindows([window.ipaInputDialog]);

winMan.$element.appendTo(OO.ui.getTeleportTarget());

window.ipaInputDialog.open();

});

}());