emoji-selector-script
code:script.js
const Asearch = (function () {
var INITPAT, INITSTATE, MAXCHAR;
INITPAT = 0x80000000;
MAXCHAR = 0x100;
Asearch.prototype.isupper = function (c) {
return (c >= 0x41) && (c <= 0x5a);
};
Asearch.prototype.islower = function (c) {
return (c >= 0x61) && (c <= 0x7a);
};
Asearch.prototype.tolower = function (c) {
if (this.isupper(c)) {
return c + 0x20;
} else {
return c;
}
};
Asearch.prototype.toupper = function (c) {
if (this.islower(c)) {
return c - 0x20;
} else {
return c;
}
};
function Asearch(source) {
var c, i, j, len, mask, ref, ref1;
this.source = source;
this.shiftpat = [];
this.epsilon = 0;
this.acceptpat = 0;
mask = INITPAT;
for (c = i = 0, ref = MAXCHAR; 0 <= ref ? i < ref : i > ref; c = 0 <= ref ? ++i : --i) {
}
ref1 = this.unpack(this.source);
for (j = 0, len = ref1.length; j < len; j++) {
if (c === 0x20) {
this.epsilon |= mask;
} else {
mask >>>= 1;
}
}
this.acceptpat = mask;
return this;
}
Asearch.prototype.state = function (state, str) {
var c, i, i0, i1, i2, i3, len, mask, ref;
if (state == null) {
state = INITSTATE;
}
if (str == null) {
str = '';
}
ref = this.unpack(str);
for (i = 0, len = ref.length; i < len; i++) {
i3 = (i3 & this.epsilon) | ((i3 & mask) >>> 1) | (i2 >>> 1) | i2;
i2 = (i2 & this.epsilon) | ((i2 & mask) >>> 1) | (i1 >>> 1) | i1;
i1 = (i1 & this.epsilon) | ((i1 & mask) >>> 1) | (i0 >>> 1) | i0;
i0 = (i0 & this.epsilon) | ((i0 & mask) >>> 1);
i1 |= i0 >>> 1;
i2 |= i1 >>> 1;
i3 |= i2 >>> 1;
}
};
Asearch.prototype.match = function (str, ambig) {
var s;
if (ambig == null) {
ambig = 0;
}
s = this.state(INITSTATE, str);
if (!(ambig < INITSTATE.length)) {
ambig = INITSTATE.length - 1;
}
return (sambig & this.acceptpat) !== 0; };
Asearch.prototype.unpack = function (str) {
var bytes, c, code, i, len, ref;
bytes = [];
ref = str.split('');
for (i = 0, len = ref.length; i < len; i++) {
code = c.charCodeAt(0);
if (code > 0xFF) {
bytes.push((code & 0xFF00) >>> 8);
}
bytes.push(code & 0xFF);
}
return bytes;
};
return Asearch;
})();
const projectName = "yuyasurarin";//scrapbox.Project.name;
let emojis = [];
const box = $('<div>').addClass('form-group').css("position", "absolute");
const container = $('<div>').addClass('dropdown');
box.append(container);
let items = $('<ul>').addClass('dropdown-menu');
container.append(items);
$('#editor').append(box);
fetch(/api/pages/${projectName}?limit=10000, { credentials: 'same-origin' })
.then(res => res.text())
.then(text => {
const data = JSON.parse(text);
const pages = data.pages;
pages.filter(page => (page.image !== null && page.title.match(/^\w\s\-\++$/))) .forEach(page => {
emojis.push({
name: page.title,
path: page.title,
icon: /api/pages/${projectName}/${page.title}/icon,
})
})
})
export function emoji_fetch() {
fetch('/api/pages/discordwiki?limit=10000')
.then(res => res.text())
.then(text => {
const data = JSON.parse(text);
const pages = data.pages;
pages.filter(page => (page.image !== null && page.title.match(/^\w\s\-\++$/))) .forEach(page => {
for (let emoji of emojis) {
if (emoji.name === page.title) return;
}
emojis.push({
name: page.title,
path: '/discordwiki/' + page.title,
icon: /api/pages/discordwiki/${page.title}/icon,
})
})
})
}
// TODO: 様々な文字列が来る場合を考慮する
const taberareloo = (word, list) => {
const targetWord = word.replace(':', '');
const regStr = targetWord.split('').reduce((pre, cur) => pre + cur + '.*').replace('+', '\\+');
const reg = RegExp(regStr, 'i');
return list.filter(item => item.name.match(reg));
}
const asearched = (word, list) => {
const targetWord = word.replace(':', '');
const a = new Asearch(targetWord);
const limitCount = Math.floor(targetWord.length / 4) + 1;
let result = [];
for (let i = 0; i <= limitCount; i++) {
let matched = list.filter(item => a.match(item.name, i));
let notExisted = matched.filter(item => {
for (let r of result) {
if (r.name === item.name) {
return false;
}
}
return true;
})
}
return result;
}
const fizzSearch = (word, list) => {
const a = asearched(word, list);
const b = taberareloo(word, list);
const c = b.filter(item => {
for (let r of a) {
if (r.name == item.name) {
return false;
}
}
return true;
})
}
let stack = "";
const editor = $('#editor');
const open = () => container.addClass("open");
const close = () => {
stack = "";
container.removeClass("open");
}
const replaceText = (text, cursor, emojiPath) => {
cursor.focus();
setTimeout(() => {
for (let i = 0; i < text.length; i++) {
var ke1 = document.createEvent("Events");
ke1.initEvent("keydown", true, true);
ke1.keyCode = ke1.which = 8; // Backspace
cursor.dispatchEvent(ke1);
}
document.execCommand('insertText', null, [${emojiPath}.icon]);
close();
}, 50)
}
editor.keydown(e => {
const key = e.key;
if (key === undefined) return;
if (stack === "" && key !== ":") {
close();
return
};
if ($('.cursor-line').text().trim() == 'code:' || $('.cursor-line .code-block').length != 0) {
close();
return;
}
if ($('.cursor-line').text().trim() == 'table:' || $('tr.cursor-line').length != 0) {
close();
return;
}
if (key === ':' && stack.length !== 0) {
let name = stack.replace(':', '');
for (let emoji of emojis) {
if (emoji.name === name) {
let cursor = $('#text-input')0; replaceText(stack + ":", cursor, emoji.path);
return;
}
}
close()
return;
}
const cursor = $('#text-input')0; stack += e.key;
let focused = $(':focus');
if (focused.is(items.find('li > a'))) {
cursor.focus();
}
}
if (stack.length === 2) {
if (key === " ") {
stack = "";
return;
}
open();
}
switch (key) {
case 'Backspace':
stack = stack.slice(0, stack.length - 1);
if (stack.length === 0) {
close();
return;
}
break;
case 'ArrowUp':
let focusedUp = $(':focus');
if (focusedUp.is(items.find('li > a').eq(0))) {
e.stopPropagation();
cursor.focus();
} else if (!focusedUp.is(items.find('li > a'))) {
close();
return;
}
break;
case 'ArrowDown':
let focusedDown = $(':focus');
if (!focusedDown.is(items.find('li > a'))) {
e.stopPropagation();
e.preventDefault();
items.find("li > a").eq(0).focus();
}
break;
case 'Escape':
case 'ArrowLeft':
case 'ArrowRight':
case 'Home':
case 'End':
case 'PageUp':
case 'PageDown':
close();
break;
case 'Enter':
if (stack.length === 1) {
close();
break;
}
let focused = $(':focus');
if (!focused.is(items.find('li > a'))) {
e.stopPropagation();
e.preventDefault();
items.find('li > a').eq(0).click();
}
break;
}
if (stack.length <= 1 || !key.match(/^\w\s\:\-\+$|Backspace/)) return; const matchedEmoji = fizzSearch(stack, emojis)
if (matchedEmoji.length === 0) {
close();
return;
}
const newItems = $('<ul>').addClass('dropdown-menu');
matchedEmoji.forEach((emoji, index) => {
if (index > 30) return;
const li = $('<li>').addClass('dropdown-item');
const a = $('<a>').attr("tabindex", "0");
const img = $('<img>').attr("src", emoji.icon)
.addClass("icon").css({ height: "17px", float: "left" });
const nameTag = $('<div>').text(" :" + emoji.name + ":");
a.append(img);
a.append(nameTag);
li.append(a);
newItems.append(li);
a.on('click', () => {
cursor.focus();
replaceText(stack, cursor, emoji.path);
})
a.on('keypress', ev => {
if (ev.key === "Enter") {
ev.preventDefault();
ev.stopPropagation();
replaceText(stack, cursor, emoji.path);
}
})
})
items.replaceWith(newItems);
items = newItems;
let css = {};
cursor.style.cssText.split(';').filter(text => text !== '')
.forEach(text => {
const props = text.split(':').map(text => text.replace(' ', '').replace('px', ''));
});
box.css({
top: ${parseInt(css.top) + parseInt(css.height) + 3}px,
left: ${css.left}px,
});
})