Feat: emoji picker profile
This commit is contained in:
parent
693efb99a0
commit
6fa319afc6
|
@ -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
|
||||||
|
|
|
@ -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
3
locales/index.d.ts
vendored
|
@ -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;
|
||||||
|
|
|
@ -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: "ノートの編集"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -99,6 +99,7 @@ export const ROLE_POLICIES = [
|
||||||
'userEachUserListsLimit',
|
'userEachUserListsLimit',
|
||||||
'rateLimitFactor',
|
'rateLimitFactor',
|
||||||
'avatarDecorationLimit',
|
'avatarDecorationLimit',
|
||||||
|
'emojiPickerProfileLimit'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// なんか動かない
|
// なんか動かない
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue