forked from GitHub/virtualtabletop
added monochrome version of Noto Emoji (#2324)
This commit is contained in:
parent
c006a7d2bc
commit
e503775698
BIN
assets/fonts/NotoEmoji-VariableFont_wght.ttf
Normal file
BIN
assets/fonts/NotoEmoji-VariableFont_wght.ttf
Normal file
Binary file not shown.
@ -309,6 +309,19 @@
|
||||
font-family: "Material Icons Two Tone";
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Noto Emoji";
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
src: url("i/fonts/NotoEmoji-VariableFont_wght.ttf") format("truetype");
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
.emoji-monochrome {
|
||||
font-family: "Noto Emoji";
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "VTT-Symbols";
|
||||
src: url("i/fonts/VTT-Symbols.ttf?version=6") format("truetype");
|
||||
@ -461,9 +474,15 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#symbolPickerOverlay .emoji:hover, #symbolPickerOverlay .emojiFlag, #symbolPickerOverlay.fewResults .emoji {
|
||||
#symbolPickerOverlay .emoji-monochrome {
|
||||
font-size: calc(0.85 * var(--symbolSize));
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#symbolPickerOverlay .emoji-color:hover, #symbolPickerOverlay .emojiFlag, #symbolPickerOverlay.fewResults .emoji-color {
|
||||
background: var(--url) center no-repeat;
|
||||
color: transparent;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
#symbolPickerOverlay .gameicons {
|
||||
@ -488,11 +507,12 @@
|
||||
|
||||
#symbolPickerOverlay .hidden,
|
||||
#symbolPickerOverlay.hideImages h2.imageCategory,
|
||||
#symbolPickerOverlay.hideImages .emoji,
|
||||
#symbolPickerOverlay.hideImages .emoji-color,
|
||||
#symbolPickerOverlay.hideImages .gameicons,
|
||||
#symbolPickerOverlay.hideFonts h2.fontCategory,
|
||||
#symbolPickerOverlay.hideFonts .symbols,
|
||||
#symbolPickerOverlay.hideFonts .material-icons {
|
||||
#symbolPickerOverlay.hideFonts .material-icons,
|
||||
#symbolPickerOverlay.hideFonts .emoji-monochrome {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -985,7 +985,7 @@ button.visualProgress {
|
||||
width: 1em;
|
||||
}
|
||||
.richtextSymbol.symbols {
|
||||
top: 0.1em;
|
||||
top: 0.15em;
|
||||
}
|
||||
.richtextSymbol.material-icons {
|
||||
top: 0.2em;
|
||||
@ -993,6 +993,10 @@ button.visualProgress {
|
||||
.richtextSymbol.gameicons, img.emoji {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.richtextSymbol.emoji-monochrome {
|
||||
font-size: 1em !important;
|
||||
width: 1.2em !important;
|
||||
}
|
||||
|
||||
.symbols {
|
||||
font-family: 'VTT-Symbols';
|
||||
|
@ -108,6 +108,8 @@ export function regexEscape(string) {
|
||||
}
|
||||
|
||||
export function setText(node, text) {
|
||||
if(node.classList.contains('emoji-monochrome'))
|
||||
text = toNotoMonochrome(text);
|
||||
for(const child of node.childNodes) {
|
||||
if(child.nodeType == Node.TEXT_NODE) {
|
||||
child.nodeValue = text;
|
||||
@ -366,7 +368,29 @@ export function formField(field, dom, id) {
|
||||
|
||||
function emojis2images(dom) {
|
||||
const regex = /\ud83c\udff4(\udb40[\udc61-\udc7a])+\udb40\udc7f|(\ud83c[\udde6-\uddff]){2}|([\#\*0-9]\ufe0f?\u20e3)|(\u00a9|\u00ae|[\u203c\u2049\u20e3\u2122\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23e9-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u261d\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u265f\u2660\u2663\u2665\u2666\u2668\u267b\u267e\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26ce\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299]|\ud83c[\udc04\udccf\udd70\udd71\udd7e\udd7f\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude02\ude1a\ude2f\ude32-\ude3a\ude50\ude51\udf00-\udf21\udf24-\udf93\udf96\udf97\udf99-\udf9b\udf9e-\udff0\udff3-\udff5\udff7-\udfff]|\ud83d[\udc00-\udcfd\udcff-\udd3d\udd49-\udd4e\udd50-\udd67\udd6f\udd70\udd73-\udd7a\udd87\udd8a-\udd8d\udd90\udd95\udd96\udda4\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa-\ude4f\ude80-\udec5\udecb-\uded2\uded5-\uded7\udedc-\udee5\udee9\udeeb\udeec\udef0\udef3-\udefc\udfe0-\udfeb\udff0]|\ud83e[\udd0c-\udd3a\udd3c-\udd45\udd47-\ude7c\ude80-\ude88\ude90-\udebd\udebf-\udec5\udece-\udedb\udee0-\udee8\udef0-\udef8])((\ud83c[\udffb-\udfff])?(\ud83e[\uddb0-\uddb3])?(\ufe0f?\u200d([\u2000-\u3300]|[\ud83c-\ud83e][\ud000-\udfff])\ufe0f?)?)*/g;
|
||||
dom.innerHTML = dom.innerHTML.replace(regex, m=>`<img class="emoji" src="i/noto-emoji/emoji_u${emojiToFilename(m)}.svg" alt="${m}">`);
|
||||
|
||||
function replaceEmojisInNode(node) {
|
||||
// Skip nodes with the class "emoji-monochrome"
|
||||
if (node.classList && node.classList.contains('emoji-monochrome')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process text nodes only
|
||||
node.childNodes.forEach(child => {
|
||||
if (child.nodeType === Node.TEXT_NODE) {
|
||||
const replacedContent = child.textContent.replace(regex, m =>
|
||||
`<img class="emoji" src="i/noto-emoji/emoji_u${emojiToFilename(m)}.svg" alt="${m}">`
|
||||
);
|
||||
const span = document.createElement('span');
|
||||
span.innerHTML = replacedContent;
|
||||
child.replaceWith(span);
|
||||
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
||||
replaceEmojisInNode(child); // Recurse into child elements
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
replaceEmojisInNode(dom);
|
||||
}
|
||||
|
||||
function images2emojis(dom) {
|
||||
@ -459,6 +483,21 @@ function emojiToFilename(emoji) {
|
||||
return [...emoji].map(char => char.codePointAt(0).toString(16).padStart(4, '0')).join('_').replace(/_fe0f/g, '');
|
||||
}
|
||||
|
||||
function toNotoMonochrome(emoji) {
|
||||
return emoji
|
||||
.replace(/[\u{1f3fb}-\u{1f3ff}]/ug, '') // remove skin tone modifiers (they are not supported by Noto Emoji)
|
||||
.replace(/\u200d[\u2640\u2642]/, '') // remove gender modifiers (they are not supported by Noto Emoji)
|
||||
.replace(/\ufe0f/g, '') // remove variation selectors (they tell Firefox to use color emoji)
|
||||
.replace(/\u{1faf1}\u200d\u{1faf2}/ug, '🤝') // join variants of emojis that have
|
||||
.replace(/.*\u200d\u{1f91d}\u200d.*/ug, '👭') // two people interacting because
|
||||
.replace(/.*\u200d\u2764\u200d\u{1f48b}\u200d.*/ug, '💏') // they have a special notation for
|
||||
.replace(/.*\u200d\u2764\u200d.*/ug, '💑'); // skin and gender variants
|
||||
}
|
||||
|
||||
function skipForNotoMonochrome(emoji) {
|
||||
return emoji.match(/^[\u{1f3c3}-\u{1f3cc}]\u{fe0f}?\u{200d}[\u{2640}\u{2642}]\u{fe0f}(\u{200d}\u{27a1}\u{fe0f})?|\u{1f468}|\u{1f468}\u{200d}[\u{1f33e}\u{1f373}\u{1f37c}\u{1f393}\u{1f3a4}\u{1f3a8}\u{1f3eb}\u{1f3ed}\u{1f4bb}\u{1f4bc}\u{1f527}\u{1f52c}\u{1f680}\u{1f692}\u{1f9af}\u{1f9b1}\u{1f9b2}\u{1f9bc}\u{1f9bd}]|\u{1f468}\u{200d}[\u{1f9af}\u{1f9bc}\u{1f9bd}]\u{200d}\u{27a1}\u{fe0f}|\u{1f468}\u{200d}[\u{2695}\u{2696}\u{2708}]\u{fe0f}|\u{1f468}\u{200d}\u{2764}\u{fe0f}\u{200d}(\u{1f468}|\u{1f48b}\u{200d}\u{1f468})|\u{1f469}\u{200d}[\u{1f33e}\u{1f373}\u{1f393}\u{1f3a4}\u{1f3a8}\u{1f3eb}\u{1f3ed}\u{1f4bb}\u{1f4bc}\u{1f527}\u{1f52c}\u{1f680}\u{1f692}\u{1f9af}-\u{1f9b3}\u{1f9bc}\u{1f9bd}]|\u{1f469}\u{200d}[\u{1f9af}\u{1f9bc}\u{1f9bd}]\u{200d}\u{27a1}\u{fe0f}|\u{1f469}\u{200d}[\u{2695}\u{2696}\u{2708}]\u{fe0f}|\u{1f469}\u{200d}\u{2764}\u{fe0f}\u{200d}(\u{1f48b}\u{200d})?[\u{1f468}\u{1f469}]|\u{1f46b}|\u{1f46c}|\u{200d}[\u{2640}\u{2642}]|\u{1f478}|\u{1f57a}|\u{1f934}|\u{1f936}|\u{1f9d1}\u{200d}(\u{1f37c}|\u{1f384}|\u{1f91d}\u{200d}\u{1f9d1})|\u{1fac3}|\u{1fac4}$/u);
|
||||
}
|
||||
|
||||
let symbolData = null;
|
||||
export async function loadSymbolPicker() {
|
||||
if(symbolData === null) {
|
||||
@ -466,18 +505,30 @@ export async function loadSymbolPicker() {
|
||||
symbolData = await (await fetch('i/fonts/symbols.json')).json();
|
||||
let list = '';
|
||||
for(const [ category, symbols ] of Object.entries(symbolData)) {
|
||||
list += `<h2 class="${category.match(/Material|VTT/)?'fontCategory':'imageCategory'}">${category}</h2>`;
|
||||
if(category == 'Emoji - Flags')
|
||||
continue;
|
||||
list += `<h2 class="${category.match(/Material|VTT|Emoji/)?'fontCategory':'imageCategory'}">${category}</h2>`;
|
||||
for(const [ symbol, keywords ] of Object.entries(symbols)) {
|
||||
if(symbol.includes('/')) {
|
||||
const gameIconsIndex = keywords.shift();
|
||||
// increase resource limits in /etc/ImageMagick-6/policy.xml to 8GiB and then: montage -background none assets/game-icons.net/*/*.svg -geometry 48x48+0+0 -tile 60x assets/game-icons.net/overview.png
|
||||
list += `<i class="gameicons" title="game-icons.net: ${symbol}" data-type="game-icons" data-symbol="${symbol}" data-keywords="${symbol.split('/')[1]},${keywords.join().toLowerCase()}" style="--x:${gameIconsIndex%60};--y:${Math.floor(gameIconsIndex/60)};--url:url('i/game-icons.net/${symbol}.svg')"></i>`;
|
||||
} else {
|
||||
let className = 'emoji';
|
||||
let className = 'emoji-monochrome';
|
||||
if(symbol[0] == '[')
|
||||
className = 'symbols';
|
||||
else if(symbol.match(/^[a-z0-9_]+$/))
|
||||
className = 'material-icons';
|
||||
if(className != 'emoji-monochrome' || !skipForNotoMonochrome(symbol))
|
||||
list += `<i class="${className}" title="${className}: ${symbol}" data-type="${className}" data-symbol="${symbol}" data-keywords="${symbol},${keywords.join().toLowerCase()}" style="--url:url('i/noto-emoji/emoji_u${emojiToFilename(symbol)}.svg')">${toNotoMonochrome(symbol)}</i>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(const [ category, symbols ] of Object.entries(symbolData)) {
|
||||
if(category.match(/Emoji/)) {
|
||||
list += `<h2 class="imageCategory">${category}</h2>`;
|
||||
for(const [ symbol, keywords ] of Object.entries(symbols)) {
|
||||
let className = 'emoji-color';
|
||||
if(category == 'Emoji - Flags')
|
||||
className += ' emojiFlag';
|
||||
list += `<i class="${className}" title="${className}: ${symbol}" data-type="${className}" data-symbol="${symbol}" data-keywords="${symbol},${keywords.join().toLowerCase()}" style="--url:url('i/noto-emoji/emoji_u${emojiToFilename(symbol)}.svg')">${symbol}</i>`;
|
||||
@ -522,9 +573,9 @@ export async function pickSymbol(type='all', bigPreviews=true, closeOverlay=true
|
||||
icon.onclick = function(e) {
|
||||
if(closeOverlay)
|
||||
showOverlay(null);
|
||||
const isImage = ['emoji','game-icons'].indexOf(icon.dataset.type) != -1;
|
||||
const isImage = ['emoji-color','game-icons'].indexOf(icon.dataset.type) != -1;
|
||||
let url = null;
|
||||
if(icon.dataset.type == 'emoji')
|
||||
if(icon.dataset.type == 'emoji-color')
|
||||
url = `/i/noto-emoji/emoji_u${emojiToFilename(icon.dataset.symbol)}.svg`;
|
||||
if(icon.dataset.type == 'game-icons')
|
||||
url = `/i/game-icons.net/${icon.dataset.symbol}.svg`;
|
||||
@ -612,15 +663,10 @@ export function addRichtextControls(dom) {
|
||||
if(icon.classList.contains('gameicons')) {
|
||||
document.execCommand('inserthtml', false, `<i class="richtextSymbol gameicons"><img src="i/game-icons.net/${icon.dataset.symbol}.svg"></i>`);
|
||||
} else {
|
||||
let className = 'emoji';
|
||||
if(icon.innerText[0] == '[')
|
||||
className = 'symbols';
|
||||
else if(icon.innerText.match(/^[a-z0-9_]+$/))
|
||||
className = 'material-icons';
|
||||
if(className == 'emoji')
|
||||
if(icon.classList.contains('emoji-color'))
|
||||
document.execCommand('inserthtml', false, icon.innerText);
|
||||
else
|
||||
document.execCommand('inserthtml', false, `<i class="richtextSymbol ${className}">${icon.innerText}</i>`);
|
||||
document.execCommand('inserthtml', false, `<i class="richtextSymbol ${icon.className}">${icon.innerText}</i>`);
|
||||
}
|
||||
for(const insertedSymbol of $a('.richtextSymbol'))
|
||||
insertedSymbol.contentEditable = false; // adding the property above causes Chrome to insert two icons
|
||||
|
@ -300,7 +300,7 @@ const jeCommands = [
|
||||
{
|
||||
id: 'je_symbolPickerText',
|
||||
name: 'pick an asset from the symbol picker',
|
||||
context: '^button ↦ text$',
|
||||
context: '^(button|basic) ↦ text$',
|
||||
call: async function() {
|
||||
const a = await pickSymbol('fonts');
|
||||
if(a) {
|
||||
@ -311,7 +311,7 @@ const jeCommands = [
|
||||
}
|
||||
},
|
||||
show: function() {
|
||||
return [ 'symbols', 'material-icons' ].indexOf(jeStateNow.classes) != -1;
|
||||
return [ 'symbols', 'material-icons', 'emoji-monochrome' ].indexOf(jeStateNow.classes) != -1;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user