diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-26 11:40:46 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-26 11:40:46 +0900 |
| commit | 34f5d81d1fa82abc985fafc1d83f1c8e7c48703c (patch) | |
| tree | ea24fd52c46127da9c5ea5031cc706e7ff5e2c3b /packages/frontend/src | |
| parent | Merge branch 'develop' (diff) | |
| parent | 13.2.3 (diff) | |
| download | misskey-34f5d81d1fa82abc985fafc1d83f1c8e7c48703c.tar.gz misskey-34f5d81d1fa82abc985fafc1d83f1c8e7c48703c.tar.bz2 misskey-34f5d81d1fa82abc985fafc1d83f1c8e7c48703c.zip | |
Merge branch 'develop'
Diffstat (limited to 'packages/frontend/src')
| -rw-r--r-- | packages/frontend/src/components/MkAutocomplete.vue | 89 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkEmojiPicker.section.vue | 6 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkEmojiPicker.vue | 17 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkMenu.vue | 3 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkSuperMenu.vue | 6 | ||||
| -rw-r--r-- | packages/frontend/src/components/global/MkEmoji.vue | 16 | ||||
| -rw-r--r-- | packages/frontend/src/custom-emojis.ts | 56 | ||||
| -rw-r--r-- | packages/frontend/src/init.ts | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/about.emojis.vue | 10 | ||||
| -rw-r--r-- | packages/frontend/src/pages/emoji-edit-dialog.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/mfm-cheat-sheet.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/scripts/aiscript/api.ts | 2 |
12 files changed, 114 insertions, 103 deletions
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 702fba9796..2cb3aeb3d8 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -33,7 +33,7 @@ </template> <script lang="ts"> -import { markRaw, ref, shallowRef, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; +import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import sanitizeHtml from 'sanitize-html'; import contains from '@/scripts/contains'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base'; @@ -61,59 +61,62 @@ type EmojiDef = { const lib = emojilist.filter(x => x.category !== 'flags'); -const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; +const emojiDb = computed(() => { + //#region Unicode Emoji + const char2path = defaultStore.reactiveState.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; -const emjdb: EmojiDef[] = lib.map(x => ({ - emoji: x.char, - name: x.name, - url: char2path(x.char), -})); + const unicodeEmojiDB: EmojiDef[] = lib.map(x => ({ + emoji: x.char, + name: x.name, + url: char2path(x.char), + })); -for (const x of lib) { - if (x.keywords) { - for (const k of x.keywords) { - emjdb.push({ - emoji: x.char, - name: k, - aliasOf: x.name, - url: char2path(x.char), - }); + for (const x of lib) { + if (x.keywords) { + for (const k of x.keywords) { + unicodeEmojiDB.push({ + emoji: x.char, + name: k, + aliasOf: x.name, + url: char2path(x.char), + }); + } } } -} -emjdb.sort((a, b) => a.name.length - b.name.length); + unicodeEmojiDB.sort((a, b) => a.name.length - b.name.length); + //#endregion -//#region Construct Emoji DB -const emojiDefinitions: EmojiDef[] = []; + //#region Custom Emoji + const customEmojiDB: EmojiDef[] = []; -for (const x of customEmojis) { - emojiDefinitions.push({ - name: x.name, - emoji: `:${x.name}:`, - isCustomEmoji: true, - }); + for (const x of customEmojis.value) { + customEmojiDB.push({ + name: x.name, + emoji: `:${x.name}:`, + isCustomEmoji: true, + }); - if (x.aliases) { - for (const alias of x.aliases) { - emojiDefinitions.push({ - name: alias, - aliasOf: x.name, - emoji: `:${x.name}:`, - isCustomEmoji: true, - }); + if (x.aliases) { + for (const alias of x.aliases) { + customEmojiDB.push({ + name: alias, + aliasOf: x.name, + emoji: `:${x.name}:`, + isCustomEmoji: true, + }); + } } } -} -emojiDefinitions.sort((a, b) => a.name.length - b.name.length); + customEmojiDB.sort((a, b) => a.name.length - b.name.length); + //#endregion -const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); -//#endregion + return markRaw([ ...customEmojiDB, ...unicodeEmojiDB ]); +}); export default { emojiDb, - emojiDefinitions, emojilist, }; </script> @@ -230,27 +233,27 @@ function exec() { } else if (props.type === 'emoji') { if (!props.q || props.q === '') { // 最近使った絵文字をサジェスト - emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[]; + emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[]; return; } const matched: EmojiDef[] = []; const max = 30; - emojiDb.some(x => { + emojiDb.value.some(x => { if (x.name.startsWith(props.q ?? '') && !x.aliasOf && !matched.some(y => y.emoji === x.emoji)) matched.push(x); return matched.length === max; }); if (matched.length < max) { - emojiDb.some(x => { + emojiDb.value.some(x => { if (x.name.startsWith(props.q ?? '') && !matched.some(y => y.emoji === x.emoji)) matched.push(x); return matched.length === max; }); } if (matched.length < max) { - emojiDb.some(x => { + emojiDb.value.some(x => { if (x.name.includes(props.q ?? '') && !matched.some(y => y.emoji === x.emoji)) matched.push(x); return matched.length === max; }); diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index 8b0b7cf29a..acced44793 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -18,10 +18,10 @@ </template> <script lang="ts" setup> -import { ref } from 'vue'; +import { ref, computed, Ref } from 'vue'; const props = defineProps<{ - emojis: string[]; + emojis: string[] | Ref<string[]>; initialShown?: boolean; }>(); @@ -29,5 +29,7 @@ const emit = defineEmits<{ (ev: 'chosen', v: string, event: MouseEvent): void; }>(); +const emojis = computed(() => Array.isArray(props.emojis) ? props.emojis : props.emojis.value); + const shown = ref(!!props.initialShown); </script> diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 9c6d62ce8b..f64cc6e9aa 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -60,7 +60,15 @@ </div> <div v-once class="group"> <header class="_acrylic">{{ i18n.ts.customEmojis }}</header> - <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.ts.other }}</XSection> + <XSection + v-for="category in customEmojiCategories" + :key="`custom:${category}`" + :initial-shown="false" + :emojis="computed(() => customEmojis.filter(e => category === null ? (e.category === 'null' || !e.category) : e.category === category).map(e => `:${e.name}:`))" + @chosen="chosen" + > + {{ category || i18n.ts.other }} + </XSection> </div> <div v-once class="group"> <header class="_acrylic">{{ i18n.ts.emoji }}</header> @@ -88,7 +96,7 @@ import { deviceKind } from '@/scripts/device-kind'; import { instance } from '@/instance'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; -import { getCustomEmojiCategories, customEmojis } from '@/custom-emojis'; +import { customEmojiCategories, customEmojis } from '@/custom-emojis'; const props = withDefaults(defineProps<{ showPinned?: boolean; @@ -104,7 +112,6 @@ const emit = defineEmits<{ (ev: 'chosen', v: string): void; }>(); -const customEmojiCategories = getCustomEmojiCategories(); const searchEl = shallowRef<HTMLInputElement>(); const emojisEl = shallowRef<HTMLDivElement>(); @@ -138,7 +145,7 @@ watch(q, () => { const searchCustom = () => { const max = 8; - const emojis = customEmojis; + const emojis = customEmojis.value; const matches = new Set<Misskey.entities.CustomEmoji>(); const exactMatch = emojis.find(emoji => emoji.name === newQ); @@ -323,7 +330,7 @@ function done(query?: string): boolean | void { if (query == null || typeof query !== 'string') return; const q2 = query.replace(/:/g, ''); - const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2); + const exactMatchCustom = customEmojis.value.find(emoji => emoji.name === q2); if (exactMatchCustom) { chosen(exactMatchCustom); return true; diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index 94dabcac90..eee77a9475 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -335,8 +335,7 @@ onBeforeUnmount(() => { } .icon { - margin-right: 5px; - width: 20px; + margin-right: 8px; } .caret { diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index bb2a789b3f..5d33ad0ad3 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -6,15 +6,15 @@ <div class="items"> <template v-for="(item, i) in group.items"> <a v-if="item.type === 'a'" :href="item.href" :target="item.target" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }"> - <i v-if="item.icon" class="icon ti-fw" :class="item.icon"></i> + <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span> <span class="text">{{ item.text }}</span> </a> <button v-else-if="item.type === 'button'" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="ev => item.action(ev)"> - <i v-if="item.icon" class="icon ti-fw" :class="item.icon"></i> + <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span> <span class="text">{{ item.text }}</span> </button> <MkA v-else :to="item.to" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }"> - <i v-if="item.icon" class="icon ti-fw" :class="item.icon"></i> + <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span> <span class="text">{{ item.text }}</span> </MkA> </template> diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index 93f50da20e..b554d5e47c 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -1,6 +1,6 @@ <template> <span v-if="isCustom && errored">:{{ customEmojiName }}:</span> -<img v-else-if="isCustom" :class="[$style.root, $style.custom, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true"/> +<img v-else-if="isCustom" :class="[$style.root, $style.custom, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true" @load="errored = false"/> <img v-else-if="char && !useOsNativeEmojis" :class="$style.root" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/> <span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span> <span v-else>{{ emoji }}</span> @@ -25,29 +25,29 @@ const props = defineProps<{ const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; const isCustom = computed(() => props.emoji.startsWith(':')); -const customEmojiName = props.emoji.substr(1, props.emoji.length - 2).replace('@.', ''); +const customEmojiName = computed(() => props.emoji.substr(1, props.emoji.length - 2).replace('@.', '')); const char = computed(() => isCustom.value ? undefined : props.emoji); const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native' && !props.isReaction); const url = computed(() => { if (char.value) { return char2path(char.value); - } else if (props.host == null && !customEmojiName.includes('@')) { - const found = customEmojis.find(x => x.name === customEmojiName); - return found ? found.url : null; + } else if (props.host == null && !customEmojiName.value.includes('@')) { + const found = customEmojis.value.find(x => x.name === customEmojiName.value); + return found ? defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(found.url) : found.url : null; } else { - const rawUrl = props.host ? `/emoji/${customEmojiName}@${props.host}.webp` : `/emoji/${customEmojiName}.webp`; + const rawUrl = props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`; return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(rawUrl) : rawUrl; } }); -const alt = computed(() => isCustom.value ? `:${customEmojiName}:` : char.value); +const alt = computed(() => isCustom.value ? `:${customEmojiName.value}:` : char.value); let errored = $ref(isCustom.value && url.value == null); // Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter function computeTitle(event: PointerEvent): void { const title = isCustom.value - ? `:${customEmojiName}:` + ? `:${customEmojiName.value}:` : (getEmojiName(char.value as string) ?? char.value as string); (event.target as HTMLElement).title = title; } diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts index a7ac4e03ca..0ba7cab5e2 100644 --- a/packages/frontend/src/custom-emojis.ts +++ b/packages/frontend/src/custom-emojis.ts @@ -1,45 +1,51 @@ -import { api } from './os'; +import { shallowRef, computed, markRaw } from 'vue'; +import * as Misskey from 'misskey-js'; +import { apiGet } from './os'; import { miLocalStorage } from './local-storage'; +import { stream } from '@/stream'; const storageCache = miLocalStorage.getItem('emojis'); -export let customEmojis: { - name: string; - aliases: string[]; - category: string; - url: string; -}[] = storageCache ? JSON.parse(storageCache) : []; +export const customEmojis = shallowRef<Misskey.entities.CustomEmoji[]>(storageCache ? JSON.parse(storageCache) : []); +export const customEmojiCategories = computed<[ ...string[], null ]>(() => { + const categories = new Set<string>(); + for (const emoji of customEmojis.value) { + if (emoji.category && emoji.category !== 'null') { + categories.add(emoji.category); + } + } + return markRaw([...Array.from(categories), null]); +}); + +stream.on('emojiAdded', emojiData => { + customEmojis.value = [emojiData.emoji, ...customEmojis.value]; +}); + +stream.on('emojiUpdated', emojiData => { + customEmojis.value = customEmojis.value.map(item => emojiData.emojis.find(search => search.name === item.name) as Misskey.entities.CustomEmoji ?? item); +}); + +stream.on('emojiDeleted', emojiData => { + customEmojis.value = customEmojis.value.filter(item => !emojiData.emojis.some(search => search.name === item.name)); +}); export async function fetchCustomEmojis() { const now = Date.now(); const lastFetchedAt = miLocalStorage.getItem('lastEmojisFetchedAt'); - if (lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60 * 24) return; + if (lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60) return; - const res = await api('emojis', {}); + const res = await apiGet('emojis', {}); - customEmojis = res.emojis; - miLocalStorage.setItem('emojis', JSON.stringify(customEmojis)); + customEmojis.value = res.emojis; + miLocalStorage.setItem('emojis', JSON.stringify(res.emojis)); miLocalStorage.setItem('lastEmojisFetchedAt', now.toString()); } -let cachedCategories; -export function getCustomEmojiCategories() { - if (cachedCategories) return cachedCategories; - - const categories = new Set(); - for (const emoji of customEmojis) { - categories.add(emoji.category); - } - const res = Array.from(categories); - cachedCategories = res; - return res; -} - let cachedTags; export function getCustomEmojiTags() { if (cachedTags) return cachedTags; const tags = new Set(); - for (const emoji of customEmojis) { + for (const emoji of customEmojis.value) { for (const tag of emoji.aliases) { tags.add(tag); } diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 36897545e2..2432b5f6f9 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -338,11 +338,6 @@ import { fetchCustomEmojis } from './custom-emojis'; } }); - stream.on('emojiAdded', emojiData => { - // TODO - //store.commit('instance/set', ); - }); - for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { import('./plugin').then(({ install }) => { install(plugin); diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index c0145a5035..d964e48b31 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -39,13 +39,13 @@ import MkSelect from '@/components/MkSelect.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkTab from '@/components/MkTab.vue'; import * as os from '@/os'; -import { customEmojis, getCustomEmojiCategories, getCustomEmojiTags } from '@/custom-emojis'; +import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis'; import { i18n } from '@/i18n'; +import * as Misskey from 'misskey-js'; -const customEmojiCategories = getCustomEmojiCategories(); const customEmojiTags = getCustomEmojiTags(); let q = $ref(''); -let searchEmojis = $ref(null); +let searchEmojis = $ref<Misskey.entities.CustomEmoji[]>(null); let selectedTags = $ref(new Set()); function search() { @@ -55,9 +55,9 @@ function search() { } if (selectedTags.size === 0) { - searchEmojis = customEmojis.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q)); + searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q)); } else { - searchEmojis = customEmojis.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t))); + searchEmojis = customEmojis.value.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t))); } } diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index b2880b60b1..4d84ed7f16 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -15,7 +15,7 @@ <MkInput v-model="name"> <template #label>{{ i18n.ts.name }}</template> </MkInput> - <MkInput v-model="category" :datalist="categories"> + <MkInput v-model="category" :datalist="customEmojiCategories"> <template #label>{{ i18n.ts.category }}</template> </MkInput> <MkInput v-model="aliases"> @@ -36,7 +36,7 @@ import MkInput from '@/components/MkInput.vue'; import * as os from '@/os'; import { unique } from '@/scripts/array'; import { i18n } from '@/i18n'; -import { getCustomEmojiCategories } from '@/custom-emojis'; +import { customEmojiCategories } from '@/custom-emojis'; const props = defineProps<{ emoji: any, @@ -46,7 +46,6 @@ let dialog = $ref(null); let name: string = $ref(props.emoji.name); let category: string = $ref(props.emoji.category); let aliases: string = $ref(props.emoji.aliases.join(' ')); -const categories = getCustomEmojiCategories(); const emit = defineEmits<{ (ev: 'done', v: { deleted?: boolean, updated?: any }): void, diff --git a/packages/frontend/src/pages/mfm-cheat-sheet.vue b/packages/frontend/src/pages/mfm-cheat-sheet.vue index b3932ff7ce..73a5716236 100644 --- a/packages/frontend/src/pages/mfm-cheat-sheet.vue +++ b/packages/frontend/src/pages/mfm-cheat-sheet.vue @@ -313,7 +313,7 @@ let preview_mention = $ref('@example'); let preview_hashtag = $ref('#test'); let preview_url = $ref('https://example.com'); let preview_link = $ref(`[${i18n.ts._mfm.dummy}](https://example.com)`); -let preview_emoji = $ref(customEmojis.length ? `:${customEmojis[0].name}:` : ':emojiname:'); +let preview_emoji = $ref(customEmojis.value.length ? `:${customEmojis.value[0].name}:` : ':emojiname:'); let preview_bold = $ref(`**${i18n.ts._mfm.dummy}**`); let preview_small = $ref(`<small>${i18n.ts._mfm.dummy}</small>`); let preview_center = $ref(`<center>${i18n.ts._mfm.dummy}</center>`); diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts index 29736ac60f..12f00bd32b 100644 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ b/packages/frontend/src/scripts/aiscript/api.ts @@ -10,7 +10,7 @@ export function createAiScriptEnv(opts) { USER_ID: $i ? values.STR($i.id) : values.NULL, USER_NAME: $i ? values.STR($i.name) : values.NULL, USER_USERNAME: $i ? values.STR($i.username) : values.NULL, - CUSTOM_EMOJIS: utils.jsToVal(customEmojis), + CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value), 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { await os.alert({ type: type ? type.value : 'info', |