parent
738b111a4c
commit
a5fe0ac8b5
@ -1109,6 +1109,7 @@ isLocked: "This account has follow approvals"
|
|||||||
isModerator: "Moderator"
|
isModerator: "Moderator"
|
||||||
isAdmin: "Administrator"
|
isAdmin: "Administrator"
|
||||||
isPatron: "Calckey Patron"
|
isPatron: "Calckey Patron"
|
||||||
|
reactionPickerSkinTone: "Preferred emoji skin tone"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Reduces the effort of server moderation through automatically recognizing
|
description: "Reduces the effort of server moderation through automatically recognizing
|
||||||
|
@ -111,7 +111,7 @@
|
|||||||
<div v-once class="group">
|
<div v-once class="group">
|
||||||
<header>{{ i18n.ts.emoji }}</header>
|
<header>{{ i18n.ts.emoji }}</header>
|
||||||
<XSection
|
<XSection
|
||||||
v-for="category in categories"
|
v-for="category in unicodeEmojiCategories"
|
||||||
:key="category"
|
:key="category"
|
||||||
:emojis="
|
:emojis="
|
||||||
emojilist
|
emojilist
|
||||||
@ -164,9 +164,9 @@ import { ref, computed, watch, onMounted } from "vue";
|
|||||||
import * as Misskey from "calckey-js";
|
import * as Misskey from "calckey-js";
|
||||||
import XSection from "@/components/MkEmojiPicker.section.vue";
|
import XSection from "@/components/MkEmojiPicker.section.vue";
|
||||||
import {
|
import {
|
||||||
getEmojiData,
|
emojilist,
|
||||||
|
unicodeEmojiCategories,
|
||||||
UnicodeEmojiDef,
|
UnicodeEmojiDef,
|
||||||
unicodeEmojiCategories as categories,
|
|
||||||
getNicelyLabeledCategory,
|
getNicelyLabeledCategory,
|
||||||
} from "@/scripts/emojilist";
|
} from "@/scripts/emojilist";
|
||||||
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
|
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
|
||||||
@ -219,7 +219,6 @@ const height = computed(() =>
|
|||||||
const customEmojiCategories = emojiCategories;
|
const customEmojiCategories = emojiCategories;
|
||||||
const customEmojis = instance.emojis;
|
const customEmojis = instance.emojis;
|
||||||
const q = ref<string | null>(null);
|
const q = ref<string | null>(null);
|
||||||
const emojilist = await getEmojiData();
|
|
||||||
const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]);
|
const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]);
|
||||||
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
|
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
|
||||||
const tab = ref<"index" | "custom" | "unicode" | "tags">("index");
|
const tab = ref<"index" | "custom" | "unicode" | "tags">("index");
|
||||||
@ -321,7 +320,7 @@ watch(q, () => {
|
|||||||
|
|
||||||
// 名前にキーワードが含まれている
|
// 名前にキーワードが含まれている
|
||||||
for (const emoji of emojis) {
|
for (const emoji of emojis) {
|
||||||
if (keywords.every((keyword) => emoji.name.includes(keyword))) {
|
if (keywords.every((keyword) => emoji.slug.includes(keyword))) {
|
||||||
matches.add(emoji);
|
matches.add(emoji);
|
||||||
if (matches.size >= max) break;
|
if (matches.size >= max) break;
|
||||||
}
|
}
|
||||||
@ -401,7 +400,7 @@ function reset() {
|
|||||||
function getKey(
|
function getKey(
|
||||||
emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef
|
emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef
|
||||||
): string {
|
): 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) {
|
function chosen(emoji: any, ev?: MouseEvent) {
|
||||||
|
@ -41,6 +41,27 @@
|
|||||||
>
|
>
|
||||||
</FromSlot>
|
</FromSlot>
|
||||||
|
|
||||||
|
<FormRadios v-model="reactionPickerSkinTone" class="_formBlock">
|
||||||
|
<template #label>{{ i18n.ts.reactionPickerSkinTone }}</template>
|
||||||
|
<option :value="1">
|
||||||
|
<MkEmoji :normal="true" emoji="✌️" />
|
||||||
|
</option>
|
||||||
|
<option :value="6">
|
||||||
|
<MkEmoji :normal="true" emoji="✌🏿" />
|
||||||
|
</option>
|
||||||
|
<option :value="5">
|
||||||
|
<MkEmoji :normal="true" emoji="✌🏾" />
|
||||||
|
</option>
|
||||||
|
<option :value="4">
|
||||||
|
<MkEmoji :normal="true" emoji="✌🏽" />
|
||||||
|
</option>
|
||||||
|
<option :value="3">
|
||||||
|
<MkEmoji :normal="true" emoji="✌🏼" />
|
||||||
|
</option>
|
||||||
|
<option :value="2">
|
||||||
|
<MkEmoji :normal="true" emoji="✌🏻" />
|
||||||
|
</option>
|
||||||
|
</FormRadios>
|
||||||
<FormRadios v-model="reactionPickerSize" class="_formBlock">
|
<FormRadios v-model="reactionPickerSize" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.size }}</template>
|
<template #label>{{ i18n.ts.size }}</template>
|
||||||
<option :value="1">{{ i18n.ts.small }}</option>
|
<option :value="1">{{ i18n.ts.small }}</option>
|
||||||
@ -125,6 +146,9 @@ async function reloadAsk() {
|
|||||||
|
|
||||||
let reactions = $ref(deepClone(defaultStore.state.reactions));
|
let reactions = $ref(deepClone(defaultStore.state.reactions));
|
||||||
|
|
||||||
|
const reactionPickerSkinTone = $computed(
|
||||||
|
defaultStore.makeGetterSetter("reactionPickerSkinTone")
|
||||||
|
);
|
||||||
const reactionPickerSize = $computed(
|
const reactionPickerSize = $computed(
|
||||||
defaultStore.makeGetterSetter("reactionPickerSize")
|
defaultStore.makeGetterSetter("reactionPickerSize")
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import data from "unicode-emoji-json/data-by-group.json";
|
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 keywordSet from "emojilib";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
|
||||||
export const unicodeEmojiCategories = [
|
export const unicodeEmojiCategories = [
|
||||||
"emotion",
|
"emotion",
|
||||||
@ -26,13 +27,16 @@ export const categoryMapping = {
|
|||||||
"Flags": "flags",
|
"Flags": "flags",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const skinToneModifiers = [
|
function addSkinTone(emoji: string) {
|
||||||
"light_skin_tone",
|
const skinTone = defaultStore.state.reactionPickerSkinTone;
|
||||||
"medium_light_skin_tone",
|
if (skinTone === 1) return emoji;
|
||||||
"medium_skin_tone",
|
if (skinTone === 2) return emoji + emojiComponents.light_skin_tone;
|
||||||
"medium_dark_skin_tone",
|
if (skinTone === 3) return emoji + emojiComponents.medium_light_skin_tone;
|
||||||
"dark_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 = {};
|
const newData = {};
|
||||||
|
|
||||||
@ -42,17 +46,12 @@ Object.keys(data).forEach((originalCategory) => {
|
|||||||
newData[newCategory] = newData[newCategory] || [];
|
newData[newCategory] = newData[newCategory] || [];
|
||||||
Object.keys(data[originalCategory]).forEach((emojiIndex) => {
|
Object.keys(data[originalCategory]).forEach((emojiIndex) => {
|
||||||
const emojiObj = { ...data[originalCategory][emojiIndex] };
|
const emojiObj = { ...data[originalCategory][emojiIndex] };
|
||||||
|
if (emojiObj.skin_tone_support) {
|
||||||
|
emojiObj.emoji = addSkinTone(emojiObj.emoji);
|
||||||
|
}
|
||||||
|
emojiObj.category = newCategory;
|
||||||
emojiObj.keywords = keywordSet[emojiObj.emoji];
|
emojiObj.keywords = keywordSet[emojiObj.emoji];
|
||||||
newData[newCategory].push(emojiObj);
|
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 = {
|
export type UnicodeEmojiDef = {
|
||||||
emoji: string;
|
emoji: string;
|
||||||
category: typeof unicodeEmojiCategories[number];
|
category: typeof unicodeEmojiCategories[number];
|
||||||
skin_tone_support: boolean;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
slug: string;
|
||||||
emoji_version: string;
|
|
||||||
skin_tone?: string;
|
|
||||||
keywords?: string[];
|
keywords?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emojilist = newData as UnicodeEmojiDef[];
|
export const emojilist: UnicodeEmojiDef[] = Object.keys(newData).reduce((acc, category) => {
|
||||||
|
const categoryItems = newData[category].map((item) => {
|
||||||
const storeName = "emojiList";
|
return {
|
||||||
|
emoji: item.emoji,
|
||||||
function openDatabase() {
|
slug: item.slug,
|
||||||
return new Promise<IDBDatabase>((resolve, reject) => {
|
category: item.category,
|
||||||
const openRequest = indexedDB.open("emojiDatabase", 1);
|
keywords: item.keywords || [],
|
||||||
|
};
|
||||||
openRequest.onupgradeneeded = () => {
|
|
||||||
const db = openRequest.result;
|
|
||||||
if (!db.objectStoreNames.contains(storeName)) {
|
|
||||||
db.createObjectStore(storeName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
openRequest.onsuccess = () => {
|
|
||||||
resolve(openRequest.result);
|
|
||||||
};
|
|
||||||
openRequest.onerror = () => {
|
|
||||||
reject(openRequest.error);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
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<UnicodeEmojiDef[]> {
|
|
||||||
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<UnicodeEmojiDef[]> {
|
|
||||||
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) {
|
export function getNicelyLabeledCategory(internalName) {
|
||||||
return Object.keys(categoryMapping).find(
|
return Object.keys(categoryMapping).find(
|
||||||
|
@ -242,6 +242,10 @@ export const defaultStore = markRaw(
|
|||||||
where: "device",
|
where: "device",
|
||||||
default: "remote" as "none" | "remote" | "always",
|
default: "remote" as "none" | "remote" | "always",
|
||||||
},
|
},
|
||||||
|
reactionPickerSkinTone: {
|
||||||
|
where: "account",
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
reactionPickerSize: {
|
reactionPickerSize: {
|
||||||
where: "device",
|
where: "device",
|
||||||
default: 3,
|
default: 3,
|
||||||
|
Loading…
Reference in New Issue
Block a user