diff --git a/locales/en-US.yml b/locales/en-US.yml
index cd29cebda6..c619c26086 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1109,6 +1109,7 @@ isLocked: "This account has follow approvals"
isModerator: "Moderator"
isAdmin: "Administrator"
isPatron: "Calckey Patron"
+reactionPickerSkinTone: "Preferred emoji skin tone"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing
diff --git a/packages/client/src/components/MkEmojiPicker.vue b/packages/client/src/components/MkEmojiPicker.vue
index 9cff6b910b..2f6ba21686 100644
--- a/packages/client/src/components/MkEmojiPicker.vue
+++ b/packages/client/src/components/MkEmojiPicker.vue
@@ -111,7 +111,7 @@
const customEmojiCategories = emojiCategories;
const customEmojis = instance.emojis;
const q = ref(null);
-const emojilist = await getEmojiData();
const searchResultCustom = ref([]);
const searchResultUnicode = ref([]);
const tab = ref<"index" | "custom" | "unicode" | "tags">("index");
@@ -321,7 +320,7 @@ watch(q, () => {
// 名前にキーワードが含まれている
for (const emoji of emojis) {
- if (keywords.every((keyword) => emoji.name.includes(keyword))) {
+ if (keywords.every((keyword) => emoji.slug.includes(keyword))) {
matches.add(emoji);
if (matches.size >= max) break;
}
@@ -401,7 +400,7 @@ function reset() {
function getKey(
emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef
): string {
- return typeof emoji === "string" ? emoji : emoji.char || `:${emoji.name}:`;
+ return typeof emoji === "string" ? emoji : emoji.emoji || `:${emoji.name}:`;
}
function chosen(emoji: any, ev?: MouseEvent) {
diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue
index d8578a6f7f..929c71e957 100644
--- a/packages/client/src/pages/settings/reaction.vue
+++ b/packages/client/src/pages/settings/reaction.vue
@@ -41,6 +41,27 @@
>
+
+ {{ i18n.ts.reactionPickerSkinTone }}
+
+
+
+
+
+
+
{{ i18n.ts.size }}
@@ -125,6 +146,9 @@ async function reloadAsk() {
let reactions = $ref(deepClone(defaultStore.state.reactions));
+const reactionPickerSkinTone = $computed(
+ defaultStore.makeGetterSetter("reactionPickerSkinTone")
+);
const reactionPickerSize = $computed(
defaultStore.makeGetterSetter("reactionPickerSize")
);
diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts
index ad5f0abeb1..6f0e222a90 100644
--- a/packages/client/src/scripts/emojilist.ts
+++ b/packages/client/src/scripts/emojilist.ts
@@ -1,6 +1,7 @@
import data from "unicode-emoji-json/data-by-group.json";
-import components from "unicode-emoji-json/data-emoji-components.json";
+import emojiComponents from "unicode-emoji-json/data-emoji-components.json";
import keywordSet from "emojilib";
+import { defaultStore } from "@/store";
export const unicodeEmojiCategories = [
"emotion",
@@ -26,13 +27,16 @@ export const categoryMapping = {
"Flags": "flags",
} as const;
-const skinToneModifiers = [
- "light_skin_tone",
- "medium_light_skin_tone",
- "medium_skin_tone",
- "medium_dark_skin_tone",
- "dark_skin_tone",
-];
+function addSkinTone(emoji: string) {
+ const skinTone = defaultStore.state.reactionPickerSkinTone;
+ if (skinTone === 1) return emoji;
+ if (skinTone === 2) return emoji + emojiComponents.light_skin_tone;
+ if (skinTone === 3) return emoji + emojiComponents.medium_light_skin_tone;
+ if (skinTone === 4) return emoji + emojiComponents.medium_skin_tone;
+ if (skinTone === 5) return emoji + emojiComponents.medium_dark_skin_tone;
+ if (skinTone === 6) return emoji + emojiComponents.dark_skin_tone;
+ return emoji;
+}
const newData = {};
@@ -42,17 +46,12 @@ Object.keys(data).forEach((originalCategory) => {
newData[newCategory] = newData[newCategory] || [];
Object.keys(data[originalCategory]).forEach((emojiIndex) => {
const emojiObj = { ...data[originalCategory][emojiIndex] };
+ if (emojiObj.skin_tone_support) {
+ emojiObj.emoji = addSkinTone(emojiObj.emoji);
+ }
+ emojiObj.category = newCategory;
emojiObj.keywords = keywordSet[emojiObj.emoji];
newData[newCategory].push(emojiObj);
-
- if (emojiObj.skin_tone_support) {
- skinToneModifiers.forEach((modifier) => {
- const modifiedEmojiObj = { ...emojiObj };
- modifiedEmojiObj.emoji += components[modifier];
- modifiedEmojiObj.skin_tone = modifier;
- newData[newCategory].push(modifiedEmojiObj);
- });
- }
});
}
});
@@ -60,76 +59,22 @@ Object.keys(data).forEach((originalCategory) => {
export type UnicodeEmojiDef = {
emoji: string;
category: typeof unicodeEmojiCategories[number];
- skin_tone_support: boolean;
- name: string;
slug: string;
- emoji_version: string;
- skin_tone?: string;
keywords?: string[];
};
-export const emojilist = newData as UnicodeEmojiDef[];
-
-const storeName = "emojiList";
-
-function openDatabase() {
- return new Promise((resolve, reject) => {
- const openRequest = indexedDB.open("emojiDatabase", 1);
-
- openRequest.onupgradeneeded = () => {
- const db = openRequest.result;
- if (!db.objectStoreNames.contains(storeName)) {
- db.createObjectStore(storeName);
- }
- };
- openRequest.onsuccess = () => {
- resolve(openRequest.result);
- };
- openRequest.onerror = () => {
- reject(openRequest.error);
- };
+export const emojilist: UnicodeEmojiDef[] = Object.keys(newData).reduce((acc, category) => {
+ const categoryItems = newData[category].map((item) => {
+ return {
+ emoji: item.emoji,
+ slug: item.slug,
+ category: item.category,
+ keywords: item.keywords || [],
+ };
});
-}
+ return acc.concat(categoryItems);
+}, []);
-function storeData(db: IDBDatabase, data) {
- return new Promise((resolve, reject) => {
- const transaction = db.transaction(storeName, "readwrite");
- const store = transaction.objectStore(storeName);
- store.put(data, "emojiListKey");
-
- transaction.oncomplete = resolve;
- transaction.onerror = reject;
- });
-}
-
-function getData(db: IDBDatabase): Promise {
- return new Promise((resolve, reject) => {
- const transaction = db.transaction(storeName, "readonly");
- const store = transaction.objectStore(storeName);
- const getRequest = store.get("emojiListKey");
-
- getRequest.onsuccess = () => resolve(getRequest.result);
- getRequest.onerror = reject;
- });
-}
-
-export async function getEmojiData(): Promise {
- try {
- const db = await openDatabase();
- const cachedData = await getData(db);
-
- if (cachedData) {
- return cachedData;
- } else {
- await storeData(db, emojilist);
- console.log("Emoji data stored in IndexedDB");
- return emojilist;
- }
- } catch (err) {
- console.error("Error accessing IndexedDB:", err);
- return emojilist;
- }
-}
export function getNicelyLabeledCategory(internalName) {
return Object.keys(categoryMapping).find(
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index c8ce96b0ee..74ee6e7555 100644
--- a/packages/client/src/store.ts
+++ b/packages/client/src/store.ts
@@ -242,6 +242,10 @@ export const defaultStore = markRaw(
where: "device",
default: "remote" as "none" | "remote" | "always",
},
+ reactionPickerSkinTone: {
+ where: "account",
+ default: 1,
+ },
reactionPickerSize: {
where: "device",
default: 3,