Feat: emoji picker profile

This commit is contained in:
mattyatea 2024-01-14 13:54:37 +09:00
parent 693efb99a0
commit 6fa319afc6
12 changed files with 502 additions and 345 deletions

View File

@ -118,7 +118,7 @@ redis:
# ┌───────────────────────────┐ # ┌───────────────────────────┐
#───┘ MeiliSearch configuration └───────────────────────────── #───┘ MeiliSearch configuration └─────────────────────────────
# You can set scope to local (default value) or global # You can set scope to local (default value) or global
# (include notes from remote). # (include notes from remote).
#meilisearch: #meilisearch:
@ -180,7 +180,9 @@ id: 'aidx'
#outgoingAddressFamily: ipv4 #outgoingAddressFamily: ipv4
# Proxy for HTTP/HTTPS # Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128 #
proxyBypassHosts: proxyBypassHosts:
- api.deepl.com - api.deepl.com
@ -214,7 +216,7 @@ proxyRemoteFiles: true
signToActivityPubGet: true signToActivityPubGet: true
# For security reasons, uploading attachments from the intranet is prohibited, # For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined". # but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [ #allowedPrivateNetworks: [
# '127.0.0.1/32' # '127.0.0.1/32'

View File

@ -9,6 +9,8 @@ notifications: "Notifications"
username: "Username" username: "Username"
password: "Password" password: "Password"
forgotPassword: "Forgot password" forgotPassword: "Forgot password"
setDefaultProfileConfirm: "Do you want to make this profile the default?"
emojiPickerProfile: "Emoji picker profile"
fetchingAsApObject: "Fetching from the Fediverse..." fetchingAsApObject: "Fetching from the Fediverse..."
ok: "OK" ok: "OK"
gotIt: "Got it!" gotIt: "Got it!"

3
locales/index.d.ts vendored
View File

@ -14,6 +14,8 @@ export interface Locale {
"forgotPassword": string; "forgotPassword": string;
"fetchingAsApObject": string; "fetchingAsApObject": string;
"ok": string; "ok": string;
"setDefaultProfileConfirm": string;
"emojiPickerProfile": string;
"notificationIndicator": string; "notificationIndicator": string;
"hanntenn": string; "hanntenn": string;
"hanntennInfo": string; "hanntennInfo": string;
@ -1767,6 +1769,7 @@ export interface Locale {
}; };
"_options": { "_options": {
"gtlAvailable": string; "gtlAvailable": string;
"emojiPickerProfileLimit": string;
"ltlAvailable": string; "ltlAvailable": string;
"canPublicNote": string; "canPublicNote": string;
"canEditNote": string; "canEditNote": string;

View File

@ -11,6 +11,8 @@ password: "パスワード"
forgotPassword: "パスワードを忘れた" forgotPassword: "パスワードを忘れた"
fetchingAsApObject: "連合に照会中" fetchingAsApObject: "連合に照会中"
ok: "OK" ok: "OK"
setDefaultProfileConfirm: "このプロファイルをデフォルトにしますか?"
emojiPickerProfile: "絵文字ピッカーのプロファイル"
notificationIndicator: "通知のインジケーターの数字を表示する" notificationIndicator: "通知のインジケーターの数字を表示する"
hanntenn: "アイコンとバナーを反転させる" hanntenn: "アイコンとバナーを反転させる"
hanntennInfo: "ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。" hanntennInfo: "ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。"
@ -1673,6 +1675,7 @@ _role:
high: "高" high: "高"
_options: _options:
gtlAvailable: "グローバルタイムラインの閲覧" gtlAvailable: "グローバルタイムラインの閲覧"
emojiPickerProfileLimit: "絵文字ピッカーのプロファイルの上限数(最大5)"
ltlAvailable: "ローカルタイムラインの閲覧" ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可" canPublicNote: "パブリック投稿の許可"
canEditNote: "ノートの編集" canEditNote: "ノートの編集"

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2023.12.2-PrisMisskey.4", "version": "2023.12.2-PrisMisskey.5",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -59,6 +59,7 @@ export type RolePolicies = {
userEachUserListsLimit: number; userEachUserListsLimit: number;
rateLimitFactor: number; rateLimitFactor: number;
avatarDecorationLimit: number; avatarDecorationLimit: number;
emojiPickerProfileLimit: number;
}; };
export const DEFAULT_POLICIES: RolePolicies = { export const DEFAULT_POLICIES: RolePolicies = {
@ -74,7 +75,6 @@ export const DEFAULT_POLICIES: RolePolicies = {
canManageCustomEmojis: false, canManageCustomEmojis: false,
canRequestCustomEmojis: false, canRequestCustomEmojis: false,
canManageAvatarDecorations: false, canManageAvatarDecorations: false,
canRequestCustomEmojis: false,
canSearchNotes: false, canSearchNotes: false,
canUseTranslator: true, canUseTranslator: true,
canHideAds: false, canHideAds: false,
@ -90,6 +90,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
userEachUserListsLimit: 50, userEachUserListsLimit: 50,
rateLimitFactor: 1, rateLimitFactor: 1,
avatarDecorationLimit: 1, avatarDecorationLimit: 1,
emojiPickerProfileLimit: 2,
}; };
@Injectable() @Injectable()
@ -355,6 +356,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)), userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)), rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)), avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
emojiPickerProfileLimit: calc('emojiPickerProfileLimit', vs => Math.max(...vs)),
}; };
} }

View File

@ -4,98 +4,103 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
<input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" autocapitalize="off" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter"> <input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" autocapitalize="off" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter">
<!-- FirefoxのTabフォーカスが想定外の挙動となるためtabindex="-1"を追加 https://github.com/misskey-dev/misskey/issues/10744 --> <!-- FirefoxのTabフォーカスが想定外の挙動となるためtabindex="-1"を追加 https://github.com/misskey-dev/misskey/issues/10744 -->
<div ref="emojisEl" class="emojis" tabindex="-1"> <div ref="emojisEl" class="emojis" tabindex="-1">
<section class="result"> <section class="result">
<div v-if="searchResultCustom.length > 0" class="body"> <div v-if="searchResultCustom.length > 0" class="body">
<button <button
v-for="emoji in searchResultCustom" v-for="emoji in searchResultCustom"
:key="emoji.name" :key="emoji.name"
class="_button item" class="_button item"
:title="emoji.name" :title="emoji.name"
tabindex="0" tabindex="0"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
> >
<MkCustomEmoji class="emoji" :name="emoji.name"/> <MkCustomEmoji class="emoji" :name="emoji.name"/>
</button> </button>
</div> </div>
<div v-if="searchResultUnicode.length > 0" class="body"> <div v-if="searchResultUnicode.length > 0" class="body">
<button <button
v-for="emoji in searchResultUnicode" v-for="emoji in searchResultUnicode"
:key="emoji.name" :key="emoji.name"
class="_button item" class="_button item"
:title="emoji.name" :title="emoji.name"
tabindex="0" tabindex="0"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
> >
<MkEmoji class="emoji" :emoji="emoji.char"/> <MkEmoji class="emoji" :emoji="emoji.char"/>
</button> </button>
</div> </div>
</section> </section>
<div v-if="tab === 'index'" class="group index"> <div v-if="tab === 'index'" class="group index">
<section v-if="showPinned && pinned.length > 0"> <section v-if="showPinned">
<div class="body"> <div style="display: flex; ">
<button <div v-for="a in profileMax" :key="a" :title="defaultStore.state[`pickerProfileName${a > 1 ? a - 1 : ''}`]" class="sllfktkhgl" :class="{ active: activeIndex === a || isDefaultProfile === a }" @click="pinnedProfileSelect(a)">
v-for="emoji in pinned" {{ defaultStore.state[`pickerProfileName${a > 1 ? a - 1 : ''}`] }}
:key="emoji" </div>
:data-emoji="emoji" </div>
class="_button item" <div class="body">
tabindex="0" <button
@pointerenter="computeButtonTitle" v-for="emoji in pinnedEmojis"
@click="chosen(emoji, $event)" :key="emoji"
> :data-emoji="emoji"
<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/> class="_button item"
<MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/> tabindex="0"
</button> @pointerenter="computeButtonTitle"
</div> @click="chosen(emoji, $event)"
</section> >
<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/>
<MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/>
</button>
</div>
</section>
<section> <section>
<header class="_acrylic"><i class="ti ti-clock ti-fw"></i> {{ i18n.ts.recentUsed }}</header> <header class="_acrylic"><i class="ti ti-clock ti-fw"></i> {{ i18n.ts.recentUsed }}</header>
<div class="body"> <div class="body">
<button <button
v-for="emoji in recentlyUsedEmojis" v-for="emoji in recentlyUsedEmojis"
:key="emoji" :key="emoji"
class="_button item" class="_button item"
:data-emoji="emoji" :data-emoji="emoji"
@pointerenter="computeButtonTitle" @pointerenter="computeButtonTitle"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
> >
<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/> <MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/>
<MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/> <MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/>
</button> </button>
</div> </div>
</section> </section>
</div> </div>
<div v-once class="group"> <div v-once class="group">
<header class="_acrylic">{{ i18n.ts.customEmojis }}</header> <header class="_acrylic">{{ i18n.ts.customEmojis }}</header>
<XSection <XSection
v-for="child in customEmojiFolderRoot.children" v-for="child in customEmojiFolderRoot.children"
:key="`custom:${child.value}`" :key="`custom:${child.value}`"
:initialShown="false" :initialShown="false"
:emojis="computed(() => customEmojis.filter(e => child.value === '' ? (e.category === 'null' || !e.category) : e.category === child.value && !customEmojis.some(emoji => emoji.category !== null && emoji.category.includes(e.category+'/')) || e.category === child.category+'/'+child.category && !e.category).filter(filterAvailable).map(e => `:${e.name}:`))" :emojis="computed(() => customEmojis.filter(e => child.value === '' ? (e.category === 'null' || !e.category) : e.category === child.value && !customEmojis.some(emoji => emoji.category !== null && emoji.category.includes(e.category+'/')) || e.category === child.category+'/'+child.category && !e.category).filter(filterAvailable).map(e => `:${e.name}:`))"
:hasChildSection="child.children.length !== 0" :hasChildSection="child.children.length !== 0"
:customEmojiTree="child.children" :customEmojiTree="child.children"
@chosen="chosen" @chosen="chosen"
> >
{{ child.value || i18n.ts.other }} {{ child.value || i18n.ts.other }}
</XSection> </XSection>
</div> </div>
<div v-once class="group"> <div v-once class="group">
<header class="_acrylic">{{ i18n.ts.emoji }}</header> <header class="_acrylic">{{ i18n.ts.emoji }}</header>
<XSection v-for="category in categories" :key="category" :emojis="emojiCharByCategory.get(category) ?? []" :hasChildSection="false" @chosen="chosen">{{ category }}</XSection> <XSection v-for="category in categories" :key="category" :emojis="emojiCharByCategory.get(category) ?? []" :hasChildSection="false" @chosen="chosen">{{ category }}</XSection>
</div> </div>
</div> </div>
<div class="tabs"> <div class="tabs">
<button class="_button tab" :class="{ active: tab === 'index' }" @click="tab = 'index'"><i class="ti ti-asterisk ti-fw"></i></button> <button class="_button tab" :class="{ active: tab === 'index' }" @click="tab = 'index'"><i class="ti ti-asterisk ti-fw"></i></button>
<button class="_button tab" :class="{ active: tab === 'custom' }" @click="tab = 'custom'"><i class="ti ti-mood-happy ti-fw"></i></button> <button class="_button tab" :class="{ active: tab === 'custom' }" @click="tab = 'custom'"><i class="ti ti-mood-happy ti-fw"></i></button>
<button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><i class="ti ti-leaf ti-fw"></i></button> <button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><i class="ti ti-leaf ti-fw"></i></button>
<button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><i class="ti ti-hash ti-fw"></i></button> <button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><i class="ti ti-hash ti-fw"></i></button>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -103,12 +108,12 @@ import { ref, shallowRef, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XSection from '@/components/MkEmojiPicker.section.vue'; import XSection from '@/components/MkEmojiPicker.section.vue';
import { import {
emojilist, emojilist,
emojiCharByCategory, emojiCharByCategory,
UnicodeEmojiDef, UnicodeEmojiDef,
unicodeEmojiCategories as categories, unicodeEmojiCategories as categories,
getEmojiName, getEmojiName,
CustomEmojiFolderTree, CustomEmojiFolderTree,
} from '@/scripts/emojilist.js'; } from '@/scripts/emojilist.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -117,8 +122,10 @@ import { deviceKind } from '@/scripts/device-kind.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js'; import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
import { $i } from '@/account.js'; import { signinRequired } from '@/account.js';
import MkButton from '@/components/MkButton.vue';
import { deepClone } from '@/scripts/clone.js';
const $i = signinRequired();
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
showPinned?: boolean; showPinned?: boolean;
pinnedEmojis?: string[]; pinnedEmojis?: string[];
@ -127,21 +134,21 @@ const props = withDefaults(defineProps<{
asWindow?: boolean; asWindow?: boolean;
asReactionPicker?: boolean; // 使使 asReactionPicker?: boolean; // 使使
}>(), { }>(), {
showPinned: true, showPinned: true,
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'chosen', v: string): void; (ev: 'chosen', v: string): void;
}>(); }>();
const profileMax = $i.policies.emojiPickerProfileLimit;
const searchEl = shallowRef<HTMLInputElement>(); const searchEl = shallowRef<HTMLInputElement>();
const emojisEl = shallowRef<HTMLDivElement>(); const emojisEl = shallowRef<HTMLDivElement>();
const { const {
emojiPickerScale, emojiPickerScale,
emojiPickerWidth, emojiPickerWidth,
emojiPickerHeight, emojiPickerHeight,
recentlyUsedEmojis, recentlyUsedEmojis,
} = defaultStore.reactiveState; } = defaultStore.reactiveState;
const pinned = computed(() => props.pinnedEmojis); const pinned = computed(() => props.pinnedEmojis);
@ -152,81 +159,80 @@ const q = ref<string>('');
const searchResultCustom = ref<Misskey.entities.EmojiSimple[]>([]); const searchResultCustom = ref<Misskey.entities.EmojiSimple[]>([]);
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]); const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index'); const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
const pinnedEmojis = ref(pinned.value);
const customEmojiFolderRoot: CustomEmojiFolderTree = { value: '', category: '', children: [] }; const customEmojiFolderRoot: CustomEmojiFolderTree = { value: '', category: '', children: [] };
function parseAndMergeCategories(input: string, root: CustomEmojiFolderTree): CustomEmojiFolderTree { function parseAndMergeCategories(input: string, root: CustomEmojiFolderTree): CustomEmojiFolderTree {
const parts = input.split('/').map(p => p.trim()); // const parts = input.split('/').map(p => p.trim()); //
let currentNode: CustomEmojiFolderTree = root; // currentNode root let currentNode: CustomEmojiFolderTree = root; // currentNode root
let includesPart = customEmojis.value.some(emoji => emoji.category !== null && emoji.category.includes(parts[0]+'/')) ; let includesPart = customEmojis.value.some(emoji => emoji.category !== null && emoji.category.includes(parts[0] + '/'));
console.log(includesPart) if (parts.length === 1 && parts[0] !== '' && includesPart) { // parts 1
if (parts.length === 1 && parts[0] !== '' && includesPart) { // parts 1 parts.push(parts[0]); // parts parts[0] (test category test/test category )
parts.push(parts[0]) // parts parts[0] (test category test/test category ) }
}
for (const part of parts) { // parts for (const part of parts) { // parts
let existingNode = currentNode.children.find((node) => node.value === part); // currentNode children part value node let existingNode = currentNode.children.find((node) => node.value === part); // currentNode children part value node
if (!existingNode) { // if (!existingNode) { //
const newNode: CustomEmojiFolderTree = { value: part, category: input, children: [] }; // node const newNode: CustomEmojiFolderTree = { value: part, category: input, children: [] }; // node
currentNode.children.push(newNode); // currentNode children newNode currentNode.children.push(newNode); // currentNode children newNode
existingNode = newNode; // existingNode newNode existingNode = newNode; // existingNode newNode
} }
currentNode = existingNode; // currentNode existingNode currentNode = existingNode; // currentNode existingNode
} }
return currentNode; return currentNode;
} }
customEmojiCategories.value.forEach(ec => { customEmojiCategories.value.forEach(ec => {
if (ec !== null) { if (ec !== null) {
parseAndMergeCategories(ec, customEmojiFolderRoot); parseAndMergeCategories(ec, customEmojiFolderRoot);
} }
}); });
parseAndMergeCategories('', customEmojiFolderRoot); parseAndMergeCategories('', customEmojiFolderRoot);
watch(q, () => { watch(q, () => {
if (emojisEl.value) emojisEl.value.scrollTop = 0; if (emojisEl.value) emojisEl.value.scrollTop = 0;
if (q.value === '') { if (q.value === '') {
searchResultCustom.value = []; searchResultCustom.value = [];
searchResultUnicode.value = []; searchResultUnicode.value = [];
return; return;
} }
const newQ = q.value.replace(/:/g, '').toLowerCase(); const newQ = q.value.replace(/:/g, '').toLowerCase();
const searchCustom = () => { const searchCustom = () => {
const max = 100; const max = 100;
const emojis = customEmojis.value; const emojis = customEmojis.value;
const matches = new Set<Misskey.entities.EmojiSimple>(); const matches = new Set<Misskey.entities.EmojiSimple>();
const exactMatch = emojis.find(emoji => emoji.name === newQ); const exactMatch = emojis.find(emoji => emoji.name === newQ);
if (exactMatch) matches.add(exactMatch); if (exactMatch) matches.add(exactMatch);
if (newQ.includes(' ')) { // AND if (newQ.includes(' ')) { // AND
const keywords = newQ.split(' '); const keywords = newQ.split(' ');
// //
for (const emoji of emojis) { for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) { if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
// //
for (const emoji of emojis) { for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
} else { } else {
if (customEmojisMap.has(newQ)) { if (customEmojisMap.has(newQ)) {
matches.add(customEmojisMap.get(newQ)!); matches.add(customEmojisMap.get(newQ)!);
} }
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
@ -237,211 +243,219 @@ watch(q, () => {
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
if (matches.size >= max) return matches;for (const emoji of emojis) { if (matches.size >= max) return matches; for (const emoji of emojis) {
if (emoji.name.startsWith(newQ)) { if (emoji.name.startsWith(newQ)) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.startsWith(newQ))) { if (emoji.aliases.some(alias => alias.startsWith(newQ))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.name.includes(newQ)) { if (emoji.name.includes(newQ)) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.includes(newQ))) { if (emoji.aliases.some(alias => alias.includes(newQ))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
} }
return matches; return matches;
}; };
const searchUnicode = () => { const searchUnicode = () => {
const max = 100; const max = 100;
const emojis = emojilist; const emojis = emojilist;
const matches = new Set<UnicodeEmojiDef>(); const matches = new Set<UnicodeEmojiDef>();
const exactMatch = emojis.find(emoji => emoji.name === newQ); const exactMatch = emojis.find(emoji => emoji.name === newQ);
if (exactMatch) matches.add(exactMatch); if (exactMatch) matches.add(exactMatch);
if (newQ.includes(' ')) { // AND if (newQ.includes(' ')) { // AND
const keywords = newQ.split(' '); const keywords = newQ.split(' ');
for (const emoji of emojis) { for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) { if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) { for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) { for (const emoji of emojis) {
if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) { if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
} }
} else { } else {
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.name.startsWith(newQ)) { if (emoji.name.startsWith(newQ)) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) { for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) { for (const emoji of emojis) {
if (index[emoji.char].some(k => k.startsWith(newQ))) { if (index[emoji.char].some(k => k.startsWith(newQ))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
} }
for (const emoji of emojis) { for (const emoji of emojis) {
if (emoji.name.includes(newQ)) { if (emoji.name.includes(newQ)) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
if (matches.size >= max) return matches; if (matches.size >= max) return matches;
for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) { for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) { for (const emoji of emojis) {
if (index[emoji.char].some(k => k.includes(newQ))) { if (index[emoji.char].some(k => k.includes(newQ))) {
matches.add(emoji); matches.add(emoji);
if (matches.size >= max) break; if (matches.size >= max) break;
} }
} }
} }
} }
return matches; return matches;
}; };
searchResultCustom.value = Array.from(searchCustom()).filter(filterAvailable); searchResultCustom.value = Array.from(searchCustom()).filter(filterAvailable);
searchResultUnicode.value = Array.from(searchUnicode()); searchResultUnicode.value = Array.from(searchUnicode());
}); });
function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean { function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean {
return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))); return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id)));
} }
function focus() { function focus() {
if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
searchEl.value?.focus({ searchEl.value?.focus({
preventScroll: true, preventScroll: true,
}); });
} }
} }
function reset() { function reset() {
if (emojisEl.value) emojisEl.value.scrollTop = 0; if (emojisEl.value) emojisEl.value.scrollTop = 0;
q.value = ''; q.value = '';
} }
function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef): string { function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef): string {
return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`; return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`;
} }
/** @see MkEmojiPicker.section.vue */ /** @see MkEmojiPicker.section.vue */
function computeButtonTitle(ev: MouseEvent): void { function computeButtonTitle(ev: MouseEvent): void {
const elm = ev.target as HTMLElement; const elm = ev.target as HTMLElement;
const emoji = elm.dataset.emoji as string; const emoji = elm.dataset.emoji as string;
elm.title = getEmojiName(emoji) ?? emoji; elm.title = getEmojiName(emoji) ?? emoji;
} }
function chosen(emoji: any, ev?: MouseEvent) { function chosen(emoji: any, ev?: MouseEvent) {
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined; const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
if (el) { if (el) {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2); const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2); const y = rect.top + (el.offsetHeight / 2);
os.popup(MkRippleEffect, { x, y }, {}, 'end'); os.popup(MkRippleEffect, { x, y }, {}, 'end');
} }
const key = getKey(emoji); const key = getKey(emoji);
emit('chosen', key); emit('chosen', key);
// 使 // 使
if (!pinned.value?.includes(key)) { if (!pinned.value?.includes(key)) {
let recents = defaultStore.state.recentlyUsedEmojis; let recents = defaultStore.state.recentlyUsedEmojis;
recents = recents.filter((emoji: any) => emoji !== key); recents = recents.filter((emoji: any) => emoji !== key);
recents.unshift(key); recents.unshift(key);
defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
} }
} }
function input(): void { function input(): void {
// Using custom input event instead of v-model to respond immediately on // Using custom input event instead of v-model to respond immediately on
// Android, where composition happens on all languages // Android, where composition happens on all languages
// (v-model does not update during composition) // (v-model does not update during composition)
q.value = searchEl.value?.value.trim() ?? ''; q.value = searchEl.value?.value.trim() ?? '';
} }
function paste(event: ClipboardEvent): void { function paste(event: ClipboardEvent): void {
const pasted = event.clipboardData?.getData('text') ?? ''; const pasted = event.clipboardData?.getData('text') ?? '';
if (done(pasted)) { if (done(pasted)) {
event.preventDefault(); event.preventDefault();
} }
} }
function onEnter(ev: KeyboardEvent) { function onEnter(ev: KeyboardEvent) {
if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
done(); done();
}
const activeIndex = ref(defaultStore.state.pickerProfileDefault);
pinnedEmojis.value = props.asReactionPicker ? deepClone(defaultStore.state[`reactions${activeIndex.value > 1 ? activeIndex.value - 1 : ''}`]) : deepClone(defaultStore.state[`pinnedEmojis${activeIndex.value > 1 ? activeIndex.value - 1 : ''}`]);
function pinnedProfileSelect(index:number) {
pinnedEmojis.value = props.asReactionPicker ? deepClone(defaultStore.state[`reactions${index > 1 ? index - 1 : ''}`]) : deepClone(defaultStore.state[`pinnedEmojis${index > 1 ? index - 1 : ''}`]);
activeIndex.value = index;
} }
function done(query?: string): boolean | void { function done(query?: string): boolean | void {
if (query == null) query = q.value; if (query == null) query = q.value;
if (query == null || typeof query !== 'string') return; if (query == null || typeof query !== 'string') return;
const q2 = query.replace(/:/g, ''); const q2 = query.replace(/:/g, '');
const exactMatchCustom = customEmojisMap.get(q2); const exactMatchCustom = customEmojisMap.get(q2);
if (exactMatchCustom) { if (exactMatchCustom) {
chosen(exactMatchCustom); chosen(exactMatchCustom);
return true; return true;
} }
const exactMatchUnicode = emojilist.find(emoji => emoji.char === q2 || emoji.name === q2); const exactMatchUnicode = emojilist.find(emoji => emoji.char === q2 || emoji.name === q2);
if (exactMatchUnicode) { if (exactMatchUnicode) {
chosen(exactMatchUnicode); chosen(exactMatchUnicode);
return true; return true;
} }
if (searchResultCustom.value.length > 0) { if (searchResultCustom.value.length > 0) {
chosen(searchResultCustom.value[0]); chosen(searchResultCustom.value[0]);
return true; return true;
} }
if (searchResultUnicode.value.length > 0) { if (searchResultUnicode.value.length > 0) {
chosen(searchResultUnicode.value[0]); chosen(searchResultUnicode.value[0]);
return true; return true;
} }
} }
onMounted(() => { onMounted(() => {
focus(); focus();
}); });
defineExpose({ defineExpose({
focus, focus,
reset, reset,
}); });
</script> </script>
@ -685,4 +699,24 @@ left: 0;*/
} }
} }
} }
.sllfktkhgl{
display: inline-block;
padding: 0 4px;
font-size: 12px;
line-height: 32px;
text-align: center;
color: var(--fg);
cursor: pointer;
width: 100%;
transition: transform 0.3s ease;
box-shadow: 0 1.5px 0 var(--divider);
height: 32px;
overflow: hidden;
&:hover {
transform: translateY(1.5px);
}
&.active {
transform: translateY(5px);
}
}
</style> </style>

View File

@ -68,8 +68,6 @@ import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { hostname } from '@/config.js'; import { hostname } from '@/config.js';
import { multipleSelectUser } from '@/os.js';
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'ok', selected: Misskey.entities.UserDetailed): void; (ev: 'ok', selected: Misskey.entities.UserDetailed): void;
(ev: 'cancel'): void; (ev: 'cancel'): void;

View File

@ -99,6 +99,7 @@ export const ROLE_POLICIES = [
'userEachUserListsLimit', 'userEachUserListsLimit',
'rateLimitFactor', 'rateLimitFactor',
'avatarDecorationLimit', 'avatarDecorationLimit',
'emojiPickerProfileLimit'
] as const; ] as const;
// なんか動かない // なんか動かない

View File

@ -72,6 +72,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
</MkFolder> </MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.emojiPickerProfileLimit, 'pickerProfileDefault'])">
<template #label>{{ i18n.ts._role._options.emojiPickerProfileLimit }}</template>
<template #suffix>{{ policies.emojiPickerProfileLimit }}</template>
<MkInput v-model="policies.emojiPickerProfileLimit" type="number">
</MkInput>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.inviteLimit, 'inviteLimit'])"> <MkFolder v-if="matchQuery([i18n.ts._role._options.inviteLimit, 'inviteLimit'])">
<template #label>{{ i18n.ts._role._options.inviteLimit }}</template> <template #label>{{ i18n.ts._role._options.inviteLimit }}</template>
<template #suffix>{{ policies.inviteLimit }}</template> <template #suffix>{{ policies.inviteLimit }}</template>

View File

@ -5,6 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div class="_gaps_m"> <div class="_gaps_m">
<MkSelect v-model="nowProfileId">
<template #label>{{ i18n.ts.emojiPickerProfile }}</template>
<option v-for="a in profileMax" :key="a" :value="a">{{ a }}. {{ defaultStore.state[`pickerProfileName${a > 1 ? a - 1 : ''}`] }} </option>
</MkSelect>
<MkInput v-model="profileName">
<template #label>{{ i18n.ts.name }}</template>
</MkInput>
<MkFolder :defaultOpen="true"> <MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-pin"></i></template> <template #icon><i class="ti ti-pin"></i></template>
<template #label>{{ i18n.ts.pinned }} ({{ i18n.ts.reaction }})</template> <template #label>{{ i18n.ts.pinned }} ({{ i18n.ts.reaction }})</template>
@ -84,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
</MkFolder> </MkFolder>
<MkButton inline primary @click="setDefaultProfile"> {{ i18n.ts.default }}</MkButton>
<FormSection> <FormSection>
<template #label>{{ i18n.ts.emojiPickerDisplay }}</template> <template #label>{{ i18n.ts.emojiPickerDisplay }}</template>
@ -139,6 +146,9 @@ import { emojiPicker } from '@/scripts/emoji-picker.js';
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
import MkEmoji from '@/components/global/MkEmoji.vue'; import MkEmoji from '@/components/global/MkEmoji.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkSelect from '@/components/MkSelect.vue';
import { signinRequired } from '@/account.js';
import MkInput from '@/components/MkInput.vue';
const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(defaultStore.state.reactions)); const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(defaultStore.state.reactions));
const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmojis)); const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmojis));
@ -155,6 +165,20 @@ const setDefaultReaction = () => setDefault(pinnedEmojisForReaction);
const removeEmoji = (reaction: string, ev: MouseEvent) => remove(pinnedEmojis, reaction, ev); const removeEmoji = (reaction: string, ev: MouseEvent) => remove(pinnedEmojis, reaction, ev);
const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev); const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev);
const setDefaultEmoji = () => setDefault(pinnedEmojis); const setDefaultEmoji = () => setDefault(pinnedEmojis);
const nowProfileId = ref(defaultStore.state.pickerProfileDefault);
const $i = signinRequired();
const profileMax = $i.policies.emojiPickerProfileLimit;
const profileName = ref(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
pinnedEmojisForReaction.value = deepClone(defaultStore.state[`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
pinnedEmojis.value = deepClone(defaultStore.state[`pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
profileName.value = deepClone(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
watch(nowProfileId, () => {
pinnedEmojisForReaction.value = deepClone(defaultStore.state[`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
pinnedEmojis.value = deepClone(defaultStore.state[`pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
profileName.value = deepClone(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
});
function previewReaction(ev: MouseEvent) { function previewReaction(ev: MouseEvent) {
reactionPicker.show(getHTMLElement(ev)); reactionPicker.show(getHTMLElement(ev));
@ -226,17 +250,32 @@ function getHTMLElement(ev: MouseEvent): HTMLElement {
} }
watch(pinnedEmojisForReaction, () => { watch(pinnedEmojisForReaction, () => {
defaultStore.set('reactions', pinnedEmojisForReaction.value); defaultStore.set(`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, pinnedEmojisForReaction.value);
}, { }, {
deep: true, deep: true,
}); });
watch(pinnedEmojis, () => { watch(pinnedEmojis, () => {
defaultStore.set('pinnedEmojis', pinnedEmojis.value); defaultStore.set( `pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, pinnedEmojis.value);
}, { }, {
deep: true, deep: true,
}); });
watch(profileName, () => {
defaultStore.set(`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, profileName.value);
}, {
deep: true,
});
async function setDefaultProfile() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.setDefaultProfileConfirm,
});
if (canceled) return;
await defaultStore.set('pickerProfileDefault', nowProfileId.value);
}
definePageMetadata({ definePageMetadata({
title: i18n.ts.emojiPicker, title: i18n.ts.emojiPicker,
icon: 'ti ti-mood-happy', icon: 'ti ti-mood-happy',

View File

@ -58,11 +58,10 @@ export const noteActions: NoteAction[] = [];
export const noteViewInterruptors: NoteViewInterruptor[] = []; export const noteViewInterruptors: NoteViewInterruptor[] = [];
export const notePostInterruptors: NotePostInterruptor[] = []; export const notePostInterruptors: NotePostInterruptor[] = [];
export const pageViewInterruptors: PageViewInterruptor[] = []; export const pageViewInterruptors: PageViewInterruptor[] = [];
export const bannerDark='https://files.prismisskey.space/misskey/e088c6d1-b07f-4312-8d41-fee2f64071e9.png' export const bannerDark = 'https://files.prismisskey.space/misskey/e088c6d1-b07f-4312-8d41-fee2f64071e9.png';
export const bannerLight ='https://files.prismisskey.space/misskey/85500d2f-41a9-48ff-a737-65d6fdf74604.png' export const bannerLight = 'https://files.prismisskey.space/misskey/85500d2f-41a9-48ff-a737-65d6fdf74604.png';
export const iconDark='https://files.prismisskey.space/misskey/484efc68-de41-4786-b2b6-e5085c31c2c4.webp' export const iconDark = 'https://files.prismisskey.space/misskey/484efc68-de41-4786-b2b6-e5085c31c2c4.webp';
export const iconLight='https://files.prismisskey.space/misskey/c3d722fe-379f-4c85-9414-90c232d53237.webp' export const iconLight = 'https://files.prismisskey.space/misskey/c3d722fe-379f-4c85-9414-90c232d53237.webp';
// TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう) // TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう)
// あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない // あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない
@ -129,6 +128,74 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account', where: 'account',
default: [], default: [],
}, },
reactions1: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis1: {
where: 'account',
default: [],
},
reactions2: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis2: {
where: 'account',
default: [],
},
reactions3: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis3: {
where: 'account',
default: [],
},
reactions4: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis4: {
where: 'account',
default: [],
},
reactions5: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis5: {
where: 'account',
default: [],
},
pickerProfileName: {
where: 'account',
default: 'default',
},
pickerProfileName1: {
where: 'account',
default: '1',
},
pickerProfileName2: {
where: 'account',
default: '2',
},
pickerProfileName3: {
where: 'account',
default: '3',
},
pickerProfileName4: {
where: 'account',
default: '4',
},
pickerProfileName5: {
where: 'account',
default: '5',
},
pickerProfileDefault: {
where: 'account',
default: 1,
},
reactionAcceptance: { reactionAcceptance: {
where: 'account', where: 'account',
default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null, default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
@ -296,19 +363,19 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
gamingType: { gamingType: {
where: 'device', where: 'device',
default: 'dark', default: 'dark',
}, },
indicatorCounterToggle: { indicatorCounterToggle: {
where: 'device', where: 'device',
default: 'true', default: 'true',
}, },
bannerUrl:{ bannerUrl: {
where: 'device', where: 'device',
default: bannerDark default: bannerDark,
}, },
iconUrl:{ iconUrl: {
where: 'device', where: 'device',
default: iconDark default: iconDark,
}, },
instanceTicker: { instanceTicker: {
where: 'device', where: 'device',
@ -334,9 +401,9 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: [] as string[], default: [] as string[],
}, },
enablehanntenn:{ enablehanntenn: {
where:'device', where: 'device',
default: false default: false,
}, },
recentlyUsedUsers: { recentlyUsedUsers: {
where: 'device', where: 'device',
@ -378,39 +445,39 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: 3, default: 3,
}, },
specifiedColor:{ specifiedColor: {
where: 'device', where: 'device',
default: '#FFFF64', default: '#FFFF64',
}, },
followerColor:{ followerColor: {
where: 'device', where: 'device',
default: '#FF00FF', default: '#FF00FF',
}, },
homeColor:{ homeColor: {
where: 'device', where: 'device',
default: '#00FFFF', default: '#00FFFF',
}, },
localOnlyColor:{ localOnlyColor: {
where:'device', where: 'device',
default: '#2b2c41' default: '#2b2c41',
}, },
numberOfGamingSpeed: { numberOfGamingSpeed: {
where: 'device', where: 'device',
default: 44, default: 44,
}, },
onlyAndWithSave:{ onlyAndWithSave: {
where: 'device', where: 'device',
default: false, default: false,
}, },
onlyFiles:{ onlyFiles: {
where: 'device', where: 'device',
default: false, default: false,
}, },
withReplies:{ withReplies: {
where: 'device', where: 'device',
default: true, default: true,
}, },
withRenotes:{ withRenotes: {
where: 'device', where: 'device',
default: true, default: true,
}, },
@ -422,15 +489,15 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: false, default: false,
}, },
showMediaTimeline:{ showMediaTimeline: {
where: 'device', where: 'device',
default: true, default: true,
}, },
showGlobalTimeline:{ showGlobalTimeline: {
where: 'device', where: 'device',
default: true, default: true,
}, },
showVisibilityColor:{ showVisibilityColor: {
where: 'device', where: 'device',
default: false, default: false,
}, },
@ -557,7 +624,6 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
})); }));
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
const PREFIX = 'miux:' as const; const PREFIX = 'miux:' as const;