mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-19 06:54:23 +01:00
feat: emoji autocomplete
This commit is contained in:
81
package-lock.json
generated
81
package-lock.json
generated
@@ -16,6 +16,8 @@
|
||||
"@tanstack/query-sync-storage-persister": "^4.29.19",
|
||||
"@tanstack/solid-query": "^4.29.19",
|
||||
"@tanstack/solid-virtual": "^3.0.0-beta.6",
|
||||
"@textcomplete/core": "^0.1.12",
|
||||
"@textcomplete/textarea": "^0.1.12",
|
||||
"@thisbeyond/solid-dnd": "^0.7.4",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"emoji-mart": "^5.5.2",
|
||||
@@ -1308,6 +1310,32 @@
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@textcomplete/core": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@textcomplete/core/-/core-0.1.12.tgz",
|
||||
"integrity": "sha512-37Q8Wic3IGpZHtknlJ/ODKMyvaBhVMM56Vl7aoBfno2Qq099fFqoCL0VzKhTn8qvTf1Z/8ymlHwPCBzPvW7KSQ==",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@textcomplete/textarea": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@textcomplete/textarea/-/textarea-0.1.12.tgz",
|
||||
"integrity": "sha512-E05H4wXr1Q50CrCFBAHewyZqvQEX681V5zleDw/31tr8vl5PDFl6TyFmS1W0jQjlrQfxa5uVvgHCx+gpfICBDQ==",
|
||||
"dependencies": {
|
||||
"@textcomplete/utils": "^0.1.11",
|
||||
"textarea-caret": "^3.1.0",
|
||||
"undate": "^0.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@textcomplete/core": "^0.1.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@textcomplete/utils": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@textcomplete/utils/-/utils-0.1.12.tgz",
|
||||
"integrity": "sha512-llHhD1FAVwFaaHzs7PU0BZYTpNLDzTccDWbw+5cj0TiB2NOXZGjPm6l7PJrJwN/yUuPDxOHip/3I+kF6OBkBAg=="
|
||||
},
|
||||
"node_modules/@thisbeyond/solid-dnd": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@thisbeyond/solid-dnd/-/solid-dnd-0.7.4.tgz",
|
||||
@@ -3476,6 +3504,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz",
|
||||
@@ -6734,6 +6767,11 @@
|
||||
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/textarea-caret": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz",
|
||||
"integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q=="
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
@@ -6962,6 +7000,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/undate": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undate/-/undate-0.3.0.tgz",
|
||||
"integrity": "sha512-ssH8QTNBY6B+2fRr3stSQ+9m2NT8qTaun3ExTx5ibzYQvP7yX4+BnX0McNxFCvh6S5ia/DYu6bsCKQx/U4nb/Q=="
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
@@ -8228,6 +8271,29 @@
|
||||
"@reach/observe-rect": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@textcomplete/core": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@textcomplete/core/-/core-0.1.12.tgz",
|
||||
"integrity": "sha512-37Q8Wic3IGpZHtknlJ/ODKMyvaBhVMM56Vl7aoBfno2Qq099fFqoCL0VzKhTn8qvTf1Z/8ymlHwPCBzPvW7KSQ==",
|
||||
"requires": {
|
||||
"eventemitter3": "^4.0.4"
|
||||
}
|
||||
},
|
||||
"@textcomplete/textarea": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@textcomplete/textarea/-/textarea-0.1.12.tgz",
|
||||
"integrity": "sha512-E05H4wXr1Q50CrCFBAHewyZqvQEX681V5zleDw/31tr8vl5PDFl6TyFmS1W0jQjlrQfxa5uVvgHCx+gpfICBDQ==",
|
||||
"requires": {
|
||||
"@textcomplete/utils": "^0.1.11",
|
||||
"textarea-caret": "^3.1.0",
|
||||
"undate": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"@textcomplete/utils": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@textcomplete/utils/-/utils-0.1.12.tgz",
|
||||
"integrity": "sha512-llHhD1FAVwFaaHzs7PU0BZYTpNLDzTccDWbw+5cj0TiB2NOXZGjPm6l7PJrJwN/yUuPDxOHip/3I+kF6OBkBAg=="
|
||||
},
|
||||
"@thisbeyond/solid-dnd": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@thisbeyond/solid-dnd/-/solid-dnd-0.7.4.tgz",
|
||||
@@ -9799,6 +9865,11 @@
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
},
|
||||
"execa": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz",
|
||||
@@ -12138,6 +12209,11 @@
|
||||
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
|
||||
"dev": true
|
||||
},
|
||||
"textarea-caret": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz",
|
||||
"integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q=="
|
||||
},
|
||||
"thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
@@ -12313,6 +12389,11 @@
|
||||
"which-boxed-primitive": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"undate": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undate/-/undate-0.3.0.tgz",
|
||||
"integrity": "sha512-ssH8QTNBY6B+2fRr3stSQ+9m2NT8qTaun3ExTx5ibzYQvP7yX4+BnX0McNxFCvh6S5ia/DYu6bsCKQx/U4nb/Q=="
|
||||
},
|
||||
"update-browserslist-db": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"@tanstack/query-sync-storage-persister": "^4.29.19",
|
||||
"@tanstack/solid-query": "^4.29.19",
|
||||
"@tanstack/solid-virtual": "^3.0.0-beta.6",
|
||||
"@textcomplete/core": "^0.1.12",
|
||||
"@textcomplete/textarea": "^0.1.12",
|
||||
"@thisbeyond/solid-dnd": "^0.7.4",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"emoji-mart": "^5.5.2",
|
||||
|
||||
@@ -17,20 +17,6 @@ const EmojiPicker: Component<EmojiPickerProps> = (props) => {
|
||||
const { config } = useConfig();
|
||||
const [pickerElement, setPickerElement] = createSignal<HTMLElement | undefined>(undefined);
|
||||
|
||||
/*
|
||||
const buildCustom = () => {
|
||||
const emojis = Object.values(config().customEmojis).map(({ shortcode, url }) => ({
|
||||
id: shortcode,
|
||||
name: shortcode,
|
||||
keywords: [shortcode],
|
||||
skins: [{ src: url }],
|
||||
}));
|
||||
|
||||
console.log(emojis);
|
||||
return [{ id: 'custom_rabbit', name: 'カスタム絵文字', emojis }];
|
||||
};
|
||||
*/
|
||||
|
||||
const handleOpen = () => {
|
||||
const picker = new Picker({
|
||||
data: async () => {
|
||||
@@ -41,7 +27,6 @@ const EmojiPicker: Component<EmojiPickerProps> = (props) => {
|
||||
const response = await fetch('https://cdn.jsdelivr.net/npm/@emoji-mart/data/i18n/ja.json');
|
||||
return response.json();
|
||||
},
|
||||
// custom: props.customEmojis ? buildCustom() : [],
|
||||
autoFocus: false,
|
||||
locale: 'ja',
|
||||
theme: 'light',
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Event as NostrEvent } from 'nostr-tools';
|
||||
import EmojiPicker from '@/components/EmojiPicker';
|
||||
import UserNameDisplay from '@/components/UserDisplayName';
|
||||
import useConfig from '@/core/useConfig';
|
||||
import useEmojiComplete from '@/hooks/useEmojiComplete';
|
||||
import usePersistStatus from '@/hooks/usePersistStatus';
|
||||
import { textNote } from '@/nostr/event';
|
||||
import parseTextNote, { ParsedTextNote } from '@/nostr/parseTextNote';
|
||||
@@ -88,6 +89,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
let textAreaRef: HTMLTextAreaElement | undefined;
|
||||
let fileInputRef: HTMLInputElement | undefined;
|
||||
|
||||
const { elementRef: emojiTextAreaRef } = useEmojiComplete();
|
||||
const [text, setText] = createSignal<string>('');
|
||||
const [contentWarning, setContentWarning] = createSignal(false);
|
||||
const [contentWarningReason, setContentWarningReason] = createSignal('');
|
||||
@@ -364,6 +366,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
ref={(el) => {
|
||||
textAreaRef = el;
|
||||
props.textAreaRef?.(el);
|
||||
emojiTextAreaRef(el);
|
||||
}}
|
||||
name="text"
|
||||
class="min-h-[40px] rounded-md border-none focus:ring-rose-300"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type Accessor, type Setter } from 'solid-js';
|
||||
|
||||
import { sortBy } from 'lodash';
|
||||
import uniq from 'lodash/uniq';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import { Kind, type Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import {
|
||||
@@ -54,6 +54,7 @@ type UseConfig = {
|
||||
saveEmojis: (emojis: CustomEmojiConfig[]) => void;
|
||||
removeEmoji: (shortcode: string) => void;
|
||||
getEmoji: (shortcode: string) => CustomEmojiConfig | undefined;
|
||||
searchEmojis: (term: string) => CustomEmojiConfig[];
|
||||
// mute
|
||||
addMutedPubkey: (pubkey: string) => void;
|
||||
removeMutedPubkey: (pubkey: string) => void;
|
||||
@@ -171,6 +172,12 @@ const useConfig = (): UseConfig => {
|
||||
const getEmoji = (shortcode: string): CustomEmojiConfig | undefined =>
|
||||
config.customEmojis[shortcode];
|
||||
|
||||
const searchEmojis = (term: string): CustomEmojiConfig[] =>
|
||||
sortBy(
|
||||
Object.values(config.customEmojis).filter(({ shortcode }) => shortcode.includes(term)),
|
||||
[(e) => e.shortcode.length],
|
||||
);
|
||||
|
||||
const isPubkeyMuted = (pubkey: string) => config.mutedPubkeys.includes(pubkey);
|
||||
|
||||
const hasMutedKeyword = (event: NostrEvent) => {
|
||||
@@ -223,6 +230,7 @@ const useConfig = (): UseConfig => {
|
||||
saveEmojis,
|
||||
removeEmoji,
|
||||
getEmoji,
|
||||
searchEmojis,
|
||||
// mute
|
||||
addMutedPubkey,
|
||||
removeMutedPubkey,
|
||||
|
||||
54
src/hooks/useEmojiComplete.tsx
Normal file
54
src/hooks/useEmojiComplete.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { createEffect, createSignal, onCleanup } from 'solid-js';
|
||||
|
||||
import { Textcomplete } from '@textcomplete/core';
|
||||
import { TextareaEditor } from '@textcomplete/textarea';
|
||||
|
||||
import useConfig, { CustomEmojiConfig } from '@/core/useConfig';
|
||||
|
||||
const useEmojiComplete = () => {
|
||||
const { searchEmojis } = useConfig();
|
||||
|
||||
const [elementRef, setElementRef] = createSignal<HTMLTextAreaElement | undefined>();
|
||||
|
||||
createEffect(() => {
|
||||
const el = elementRef();
|
||||
if (el == null) return;
|
||||
|
||||
const editor = new TextareaEditor(el);
|
||||
const textcomplete = new Textcomplete(
|
||||
editor,
|
||||
[
|
||||
{
|
||||
id: 'customEmoji',
|
||||
match: /\B:(\w+)$/,
|
||||
search: (term, callback) => {
|
||||
callback(searchEmojis(term));
|
||||
},
|
||||
template: (config: CustomEmojiConfig) => {
|
||||
const e = (
|
||||
<div class="flex gap-1 pb-1">
|
||||
<img class="h-6 max-w-[3rem]" src={config.url} alt={config.shortcode} />
|
||||
<div>{config.shortcode}</div>
|
||||
</div>
|
||||
) as HTMLElement;
|
||||
return e.outerHTML;
|
||||
},
|
||||
replace: (result: CustomEmojiConfig) => `:${result.shortcode}: `,
|
||||
},
|
||||
],
|
||||
{
|
||||
dropdown: {
|
||||
className: 'px-2 pt-2 pb-1 bg-white shadow rounded',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
onCleanup(() => {
|
||||
textcomplete.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
return { elementRef: setElementRef };
|
||||
};
|
||||
|
||||
export default useEmojiComplete;
|
||||
Reference in New Issue
Block a user