Просмотр исходного кода

Merge branch 'feat/126521-emoji-features-2' into dev/7.0.x

WNomunomu 2 лет назад
Родитель
Сommit
e72456b231

+ 3 - 0
packages/editor/src/@types/scss.d.ts

@@ -1,2 +1,5 @@
 // prevent TS2307: Cannot find module './xxx.module.scss' or its corresponding type declarations.
 declare module '*.scss';
+
+// prevent TS7016: Could not find a declaration file for module 'emoji-mart'.
+declare module 'emoji-mart';

+ 1 - 1
packages/editor/src/components/CodeMirrorEditorMain.tsx

@@ -13,7 +13,6 @@ const additionalExtensions: Extension[] = [
   scrollPastEnd(),
 ];
 
-
 type Props = {
   onChange?: (value: string) => void,
   onSave?: () => void,
@@ -60,6 +59,7 @@ export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
     return cleanupFunction;
   }, [codeMirrorEditor, onSave]);
 
+
   return (
     <CodeMirrorEditor
       editorKey={GlobalCodeMirrorEditorKey.MAIN}

+ 8 - 2
packages/editor/src/services/codemirror-editor/use-codemirror-editor/use-codemirror-editor.ts

@@ -10,6 +10,8 @@ import { tags } from '@lezer/highlight';
 import { useCodeMirror, type UseCodeMirror } from '@uiw/react-codemirror';
 import deepmerge from 'ts-deepmerge';
 
+import { emojiAutocompletionSettings } from '../../extensions/emojiAutocompletionSettings';
+
 import { useAppendExtensions, type AppendExtensions } from './utils/append-extensions';
 import { useFocus, type Focus } from './utils/focus';
 import { useGetDoc, type GetDoc } from './utils/get-doc';
@@ -49,15 +51,19 @@ const defaultExtensions: Extension[] = [
   Prec.lowest(keymap.of(defaultKeymap)),
   syntaxHighlighting(markdownHighlighting),
   Prec.lowest(syntaxHighlighting(defaultHighlightStyle)),
+  emojiAutocompletionSettings,
 ];
 
+
 export const useCodeMirrorEditor = (props?: UseCodeMirror): UseCodeMirrorEditor => {
 
-  const mergedProps = useMemo<UseCodeMirror>(() => {
+  const mergedProps = useMemo(() => {
     return deepmerge(
       props ?? {},
       {
-        extensions: defaultExtensions,
+        extensions: [
+          defaultExtensions,
+        ],
         // Reset settings of react-codemirror.
         // Extensions are defined first will be used if they have the same priority.
         // If extensions conflict, disable them here.

+ 75 - 0
packages/editor/src/services/extensions/emojiAutocompletionSettings.ts

@@ -0,0 +1,75 @@
+import { type CompletionContext, type Completion, autocompletion } from '@codemirror/autocomplete';
+import { syntaxTree } from '@codemirror/language';
+import { emojiIndex } from 'emoji-mart';
+import emojiData from 'emoji-mart/data/all.json';
+
+const getEmojiDataArray = (): string[] => {
+  const rawEmojiDataArray = emojiData.categories;
+
+  const emojiCategoriesData = [
+    'people',
+    'nature',
+    'foods',
+    'activity',
+    'places',
+    'objects',
+    'symbols',
+    'flags',
+  ];
+
+  const fixedEmojiDataArray: string[] = [];
+
+  emojiCategoriesData.forEach((value) => {
+    const tempArray = rawEmojiDataArray.find(obj => obj.id === value)?.emojis;
+
+    if (tempArray == null) {
+      return;
+    }
+
+    fixedEmojiDataArray.push(...tempArray);
+  });
+
+  return fixedEmojiDataArray;
+};
+
+const emojiDataArray = getEmojiDataArray();
+
+const emojiOptions = emojiDataArray.map(
+  tag => ({ label: `:${tag}:`, type: tag }),
+);
+
+const TWO_OR_MORE_WORD_CHARACTERS_REGEX = /:\w{2,}$/;
+
+
+// EmojiAutocompletion is activated when two characters are entered into the editor.
+const emojiAutocompletion = (context: CompletionContext) => {
+  const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1);
+  const textBefore = context.state.sliceDoc(nodeBefore.from, context.pos);
+  const emojiBefore = TWO_OR_MORE_WORD_CHARACTERS_REGEX.exec(textBefore);
+
+  if (!emojiBefore && !context.explicit) return null;
+
+  return {
+    from: emojiBefore ? nodeBefore.from + emojiBefore.index : context.pos,
+    options: emojiOptions,
+    validFor: TWO_OR_MORE_WORD_CHARACTERS_REGEX,
+  };
+};
+
+export const emojiAutocompletionSettings = autocompletion({
+  addToOptions: [{
+    render: (completion: Completion) => {
+      const emojiName = completion.type ?? '';
+      const emojiData = emojiIndex.emojis[emojiName];
+
+      const emoji = emojiData.native ?? emojiData[1].native;
+
+      const element = document.createElement('span');
+      element.innerHTML = emoji;
+      return element;
+    },
+    position: 20,
+  }],
+  icons: false,
+  override: [emojiAutocompletion],
+});