Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function () {
	let clicked, dialog;
	let openDialog = async context => {
		if (clicked) {
			if (dialog) {
				dialog.context = context;
				dialog.open();
			}
			return;
		}
		clicked = true;
		mw.loader.addStyleTag(`.oo-ui-windowManager-floating > .insertanychar > .oo-ui-window-frame{margin-top:0 !important;margin-inline-end:0 !important} .insertanychar{cursor:pointer} .insertanychar-item{padding-inline:0} .insertanychar-item > .oo-ui-labelElement-label{display:flex !important;overflow:visible !important;align-items:center} .insertanychar-char{font-size:200%;min-width:1em;line-height:1;margin-inline:8px;text-align:center;font-family:'Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif,'Andika','Noto Sans','Liberation Sans','Roboto Sans','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif} .insertanychar-name{white-space:normal !important;word-break:break-word;flex-grow:1} .insertanychar-item > .oo-ui-labelElement-label::after{content:attr(data-insertanychar);font-size:85%;margin-inline:4px;color:var(--color-subtle,#54595d)}`);
		let promise = mw.loader.using(['oojs-ui-windows', 'jquery.textSelection']);
		let response = await $.get('//en.wiktionary.org/w/rest.php/v1/revision/80968402');
		let data = [], block;
		response.source.split('\n').forEach(s => {
			let props = s.split(';');
			let v = parseInt(props[0], 16);
			let name = props[1];
			if (name?.[0] === '<') {
				if (name === '<control>') {
					name = props[10];
				} else {
					let blockName = name.match(/[^,<>]+/)?.[0].toUpperCase();
					if (!blockName || blockName.endsWith(' SURROGATE')) return;
					if (name.endsWith(', First>')) {
						block = [v];
						data.push([blockName, block]);
					} else if (name.endsWith(', Last>')) {
						block.push(v);
					}
					return;
				}
			}
			if (props[2] === 'Mn') {
				data.push([name, v, ['233', '234'].includes(props[3])]);
			} else {
				data.push([name, v]);
			}
		});
		await promise;
		function InsertAnyCharDialog(config) {
			InsertAnyCharDialog.super.call(this, config);
			this.$element.addClass('insertanychar');
		}
		OO.inheritClass(InsertAnyCharDialog, OO.ui.Dialog);
		InsertAnyCharDialog.static.name = 'insertAnyCharDialog';
		InsertAnyCharDialog.prototype.initialize = function () {
			InsertAnyCharDialog.super.prototype.initialize.call(this);
			this.$element.on('click', e => {
				if (!this.$content[0].contains(e.target) &&
					!this.$overlay[0].contains(e.target)
				) {
					this.close();
				}
			});
			this.moreButton = new OO.ui.MenuOptionWidget({
				label: 'Load more'
			});
			new IntersectionObserver(entries => {
				if (entries[0].isIntersecting) {
					this.continue();
				}
			}, { threshold: 0.75 }).observe(this.moreButton.$element[0]);
			this.input = new OO.ui.SearchInputWidget({
				autocomplete: false
			}).on('change', OO.ui.debounce(value => {
				this.menu.toggle(false).clearItems();
				this.res = value.toUpperCase().split(/[^\dA-Z]+/).filter(Boolean)
					.map(s => new RegExp('\\b' + s));
				if (!this.res.length) return;
				this.startFrom = 0;
				this.startBlockFrom = 0;
				this.lookUp();
			}, 250));
			this.menu = new OO.ui.MenuSelectWidget({
				$floatableContainer: this.input.$element,
				autoHide: false,
				hideOnChoose: false,
				input: this.input,
				widget: this.input
			}).on('choose', item => {
				if (item === this.moreButton) {
					this.continue();
					return;
				}
				this.context.$textarea.textSelection('encapsulateSelection', {
					peri: String.fromCodePoint(parseInt(item.$label.data('insertanychar'), 16)),
					selectPeri: false
				});
				this.menu.unselectItem();
			});
			this.manager.connect(this.menu, { resize: 'position' })
				.connect(this.menu, { resize: 'clip' });
			this.$body.append(this.input.$element);
			this.$overlay.append(this.menu.$element);
		};
		InsertAnyCharDialog.prototype.lookUp = function () {
			this.busy = true;
			let matches = [];
			let halted = data.slice(this.startFrom).some(entry => {
				if (typeof entry[1] === 'number') {
					this.startFrom++;
					if (this.res.every(re => re.test(entry[0]))) {
						return matches.push(entry) === 100;
					}
				} else if (this.res.every(re => (
					re.test(entry[0]) || /^\\b[\dA-F]+$/.test(re.source)
				))) {
					for (let i = entry[1][0] + this.startBlockFrom; i <= entry[1][1]; i++) {
						this.startBlockFrom++;
						let hex = i.toString(16).toUpperCase();
						if (this.res.every(re => re.test(entry[0]) || re.test(hex)) &&
							matches.push([entry[0], i]) === 100
						) {
							return true;
						}
					}
					this.startBlockFrom = 0;
					this.startFrom++;
				}
			});
			if (!matches.length) return;
			let items = matches.map(([name, v, isDouble]) => new OO.ui.MenuOptionWidget({
				$label: $('<span>').attr(
					'data-insertanychar',
					v.toString(16).toUpperCase().padStart(4, '0')
				),
				classes: ['insertanychar-item'],
				label: $('<span>').addClass('insertanychar-char').text(
					`${isDouble === undefined ? '' : '◌'}${
						String.fromCodePoint(v)
					}${isDouble === true ? '◌' : ''}`
				).add($('<span>').addClass('insertanychar-name').text(
					name.replace(
						/ (?:AND|AS|AT|BY|FOR|FROM|IN|OF|ON|OR|THE|TO|WITH)(?= )/g,
						s => s.toLowerCase()
					).replace(
						/\b(?!CJK\b)[\dA-Z]{2,}\b/g,
						s => s[0] + s.slice(1).toLowerCase()
					)
				))
			}));
			if (halted) {
				items.push(this.moreButton);
			}
			this.menu.addItems(items).toggle(true);
			setTimeout(() => {
				this.busy = false;
			}, 500);
		};
		InsertAnyCharDialog.prototype.continue = function () {
			if (this.busy) return;
			this.menu.removeItems([this.moreButton]);
			this.lookUp();
		};
		InsertAnyCharDialog.prototype.getReadyProcess = function () {
			return InsertAnyCharDialog.super.prototype.getReadyProcess.apply(this, arguments).next(function () {
				this.input.focus();
				this.menu.position().clip();
			}, this);
		};
		dialog = new InsertAnyCharDialog();
		dialog.context = context;
		let winMan = new OO.ui.WindowManager();
		winMan.addWindows([dialog]);
		winMan.$element.appendTo(OO.ui.getTeleportTarget());
		dialog.open();
	};
	mw.hook('wikiEditor.toolbarReady').add($textarea => {
		$textarea.wikiEditor('addToToolbar', {
			section: 'main',
			group: 'insert',
			tools: {
				dialog: {
					label: 'InsertAnyChar',
					type: 'button',
					oouiIcon: 'specialCharacter',
					action: { type: 'callback', execute: openDialog }
				}
			}
		});
	});
}());