0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-03-27 21:08:13 +00:00

emoji autocomplete

This commit is contained in:
Víctor Losada Hernández
2026-03-26 23:44:49 +01:00
parent 1328d1aaca
commit d4c1eba3f3
5 changed files with 90 additions and 84 deletions

View File

@@ -1,3 +1,5 @@
import { autocompletion } from '@codemirror/autocomplete';
import diceFont from '@themes/fonts/iconFonts/diceFont.js'; import diceFont from '@themes/fonts/iconFonts/diceFont.js';
import elderberryInn from '@themes/fonts/iconFonts/elderberryInn.js'; import elderberryInn from '@themes/fonts/iconFonts/elderberryInn.js';
import fontAwesome from '@themes/fonts/iconFonts/fontAwesome.js'; import fontAwesome from '@themes/fonts/iconFonts/fontAwesome.js';
@@ -10,75 +12,61 @@ const emojis = {
...gameIcons ...gameIcons
}; };
const showAutocompleteEmoji = function(CodeMirror, editor) { const emojiCompletionList = (context)=>{
CodeMirror.commands.autocomplete = function(editor) { const word = context.matchBefore(/:[^\s:]*/);
editor.showHint({ if(!word) return null;
completeSingle : false,
hint : function(editor) {
const cursor = editor.getCursor();
const line = cursor.line;
const lineContent = editor.getLine(line);
const start = lineContent.lastIndexOf(':', cursor.ch - 1) + 1;
const end = cursor.ch;
const currentWord = lineContent.slice(start, end);
const line = context.state.doc.lineAt(context.pos);
const textToCursor = line.text.slice(0, context.pos - line.from);
const list = Object.keys(emojis).filter(function(emoji) { if(textToCursor.includes('{')) {
return emoji.toLowerCase().indexOf(currentWord.toLowerCase()) >= 0; const curlyToCursor = textToCursor.slice(textToCursor.indexOf('{'));
}).sort((a, b)=>{ const curlySpanRegex = /{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1$/g;
const lowerA = a.replace(/\d+/g, function(match) { // Temporarily convert any numbers in emoji string if(curlySpanRegex.test(curlyToCursor)) return null;
return match.padStart(4, '0'); // to 4-digits, left-padded with 0's, to aid in }
}).toLowerCase(); // sorting numbers, i.e., "d6, d10, d20", not "d10, d20, d6"
const lowerB = b.replace(/\d+/g, function(match) { // Also make lowercase for case-insensitive alpha sorting
return match.padStart(4, '0');
}).toLowerCase();
if(lowerA < lowerB) const currentWord = word.text.slice(1); // remove ':'
return -1;
return 1;
}).map(function(emoji) {
return {
text : `${emoji}:`, // Text to output to editor when option is selected
render : function(element, self, data) { // How to display the option in the dropdown
const div = document.createElement('div');
div.innerHTML = `<i class="emojiPreview ${emojis[emoji]}"></i> ${emoji}`;
element.appendChild(div);
}
};
});
return { const options = Object.keys(emojis)
list : list.length ? list : [], .filter((e)=>e.toLowerCase().includes(currentWord.toLowerCase()))
from : CodeMirror.Pos(line, start), .sort((a, b)=>{
to : CodeMirror.Pos(line, end) const normalize = (str)=>str.replace(/\d+/g, (m)=>m.padStart(4, '0')).toLowerCase();
}; return normalize(a) < normalize(b) ? -1 : 1;
} })
}); .map((e)=>({
label : e,
apply : `${e}:`,
type : 'text',
info : ()=>{
const div = document.createElement('div');
div.innerHTML = `<i class="emojiPreview ${emojis[e]}"></i> ${e}`;
return div;
}
}));
return {
from : word.from + 1,
options,
}; };
editor.on('inputRead', function(instance, change) {
const cursor = editor.getCursor();
const line = editor.getLine(cursor.line);
// Get the text from the start of the line to the cursor
const textToCursor = line.slice(0, cursor.ch);
// Do not autosuggest emojis in curly span/div/injector properties
if(line.includes('{')) {
const curlyToCursor = textToCursor.slice(textToCursor.indexOf(`{`));
const curlySpanRegex = /{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1$/g;
if(curlySpanRegex.test(curlyToCursor))
return;
}
// Check if the text ends with ':xyz'
if(/:[^\s:]+$/.test(textToCursor)) {
CodeMirror.commands.autocomplete(editor);
}
});
}; };
export default { export const autocompleteEmoji = autocompletion({
showAutocompleteEmoji override : [emojiCompletionList],
}; activateOnTyping : true,
addToOptions : [
{
render(completion) {
const e = completion.label;
const icon = document.createElement('i');
icon.className = `emojiPreview ${emojis[e]}`;
// append directly to a DocumentFragment to return a single node
const fragment = document.createDocumentFragment();
fragment.appendChild(icon);
return fragment;
}
}
]
});

View File

@@ -18,6 +18,7 @@ import { defaultKeymap, history, historyField, undo, redo } from '@codemirror/co
import { languages } from '@codemirror/language-data'; import { languages } from '@codemirror/language-data';
import { css } from '@codemirror/lang-css'; import { css } from '@codemirror/lang-css';
import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { autocompleteEmoji } from './autocompleteEmoji.js';
import * as themes from '@uiw/codemirror-themes-all'; import * as themes from '@uiw/codemirror-themes-all';
const themeCompartment = new Compartment(); const themeCompartment = new Compartment();
@@ -31,20 +32,20 @@ import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './lega
const createHighlightPlugin = (renderer, tab)=>{ const createHighlightPlugin = (renderer, tab)=>{
const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown;
class countWidget extends WidgetType { class countWidget extends WidgetType {
constructor(count) { constructor(count) {
super(); super();
this.count = count; this.count = count;
} }
toDOM() { toDOM() {
const span = document.createElement("span"); const span = document.createElement('span');
span.className = "cm-page-count"; span.className = 'cm-page-count';
span.textContent = this.count; span.textContent = this.count;
span.style.color = "#989898"; span.style.color = '#989898';
return span; return span;
} }
ignoreEvent() { return true; } ignoreEvent() { return true; }
} }
return ViewPlugin.fromClass( return ViewPlugin.fromClass(
class { class {
@@ -154,6 +155,7 @@ const CodeEditor = forwardRef(
highlightActiveLine(), highlightActiveLine(),
highlightActiveLineGutter(), highlightActiveLineGutter(),
highlightCompartment.of(combinedHighlight), highlightCompartment.of(combinedHighlight),
autocompleteEmoji,
]; ];
}; };

View File

@@ -2,11 +2,11 @@
@import '@themes/codeMirror/customEditorStyles.less'; @import '@themes/codeMirror/customEditorStyles.less';
.editor { .editor {
position : relative; position : relative;
width : 100%; width : 100%;
height : 100%; height : 100%;
container : editor / inline-size; container : editor / inline-size;
background:white; background : white;
.codeEditor { .codeEditor {
height : calc(100% - 25px); height : calc(100% - 25px);
.cm-editor { height : 100%; } .cm-editor { height : 100%; }
@@ -15,6 +15,20 @@
border-top : #333399 solid 1px; border-top : #333399 solid 1px;
} }
.cm-tooltip-autocomplete {
li {
display : flex;
gap : 10px;
align-items : center;
justify-content : flex-start;
.cm-completionIcon { display : none; }
.cm-tooltip-autocomplete .cm-completionLabel { translate : 0 -2px; }
}
}
.cm-page-count { .cm-page-count {
float : right; float : right;
color : grey; color : grey;

1
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"@babel/preset-env": "^7.29.0", "@babel/preset-env": "^7.29.0",
"@babel/preset-react": "^7.28.5", "@babel/preset-react": "^7.28.5",
"@babel/runtime": "^7.28.6", "@babel/runtime": "^7.28.6",
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/commands": "^6.10.3", "@codemirror/commands": "^6.10.3",
"@codemirror/highlight": "^0.19.8", "@codemirror/highlight": "^0.19.8",
"@codemirror/lang-css": "^6.3.1", "@codemirror/lang-css": "^6.3.1",

View File

@@ -91,6 +91,7 @@
"@babel/preset-env": "^7.29.0", "@babel/preset-env": "^7.29.0",
"@babel/preset-react": "^7.28.5", "@babel/preset-react": "^7.28.5",
"@babel/runtime": "^7.28.6", "@babel/runtime": "^7.28.6",
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/commands": "^6.10.3", "@codemirror/commands": "^6.10.3",
"@codemirror/highlight": "^0.19.8", "@codemirror/highlight": "^0.19.8",
"@codemirror/lang-css": "^6.3.1", "@codemirror/lang-css": "^6.3.1",