diff options
| author | Mar0xy <marie@kaifa.ch> | 2023-11-22 23:40:27 +0100 |
|---|---|---|
| committer | Mar0xy <marie@kaifa.ch> | 2023-11-22 23:40:27 +0100 |
| commit | 42bf8e5e76fb6a6dce804c1784e1e2833268c1e6 (patch) | |
| tree | 9f82e50010bf1fc287e98ab856ab12857f33d0b0 /packages/frontend/src/components | |
| parent | fix: attachments not working on FB import (diff) | |
| parent | 2023.11.1 (diff) | |
| download | sharkey-42bf8e5e76fb6a6dce804c1784e1e2833268c1e6.tar.gz sharkey-42bf8e5e76fb6a6dce804c1784e1e2833268c1e6.tar.bz2 sharkey-42bf8e5e76fb6a6dce804c1784e1e2833268c1e6.zip | |
merge: upstream
Diffstat (limited to 'packages/frontend/src/components')
14 files changed, 217 insertions, 117 deletions
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 063f7279f5..9e92c4bb03 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -45,12 +45,12 @@ import contains from '@/scripts/contains.js'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js'; import { acct } from '@/filters/user.js'; import * as os from '@/os.js'; -import { MFM_TAGS } from '@/scripts/mfm-tags.js'; import { defaultStore } from '@/store.js'; import { emojilist, getEmojiName } from '@/scripts/emojilist.js'; import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; import { customEmojis } from '@/custom-emojis.js'; +import { MFM_TAGS } from '@/const.js'; type EmojiDef = { emoji: string; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index f4f31f6a56..784a9ca348 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget"> <MkAvatar :class="$style.collapsedRenoteTargetAvatar" :user="appearNote.user" link preview/> - <Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'account'" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/> + <Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'respect'" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/> </div> <article v-else :class="$style.article" @contextmenu.stop="onContextmenu"> <div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/> <div style="container-type: inline-size;"> <p v-if="appearNote.cw != null" :class="$style.cw"> - <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'"/> + <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/> <MkCwButton v-model="showContent" :note="appearNote" style="margin: 4px 0;" v-on:click.stop/> </p> <div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]" > @@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only :parsedNodes="parsed" :text="appearNote.text" :author="appearNote.user" - :nyaize="'account'" + :nyaize="'respect'" :emojiUrls="appearNote.emojis" :enableEmojiMenu="true" :enableEmojiMenuReaction="true" @@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkLoading v-if="translating" mini/> <div v-else> <b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b> - <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/> + <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> </div> </div> <MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton> @@ -232,11 +232,17 @@ function noteclick(id: string) { // plugin if (noteViewInterruptors.length > 0) { onMounted(async () => { - let result:Misskey.entities.Note | null = deepClone(note); + let result: Misskey.entities.Note | null = deepClone(note); for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(result); - - if (result === null) return isDeleted.value = true; + try { + result = await interruptor.handler(result); + if (result === null) { + isDeleted.value = true; + return; + } + } catch (err) { + console.error(err); + } } note = result; }); @@ -265,7 +271,7 @@ const renoteUri = appearNote.renote ? appearNote.renote.uri : null; const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null); -const urls = parsed ? extractUrlFromMfm(parsed) : null; +const urls = $computed(() => parsed ? extractUrlFromMfm(parsed) : null); const animated = $computed(() => parsed ? checkAnimationFromMfm(parsed) : null); const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false); const isLong = shouldCollapsed(appearNote, urls ?? []); diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index b2594dec23..ef677389ca 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -68,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only </header> <div :class="$style.noteContent"> <p v-if="appearNote.cw != null" :class="$style.cw"> - <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'account'"/> + <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/> <MkCwButton v-model="showContent" :note="appearNote"/> </p> <div v-show="appearNote.cw == null || showContent"> @@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only :parsedNodes="parsed" :text="appearNote.text" :author="appearNote.user" - :nyaize="'account'" + :nyaize="'respect'" :emojiUrls="appearNote.emojis" :enableEmojiMenu="true" :enableEmojiMenuReaction="true" @@ -90,7 +90,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkLoading v-if="translating" mini/> <div v-else> <b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b> - <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/> + <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/> </div> </div> <MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton> @@ -270,11 +270,17 @@ let note = $ref(deepClone(props.note)); // plugin if (noteViewInterruptors.length > 0) { onMounted(async () => { - let result:Misskey.entities.Note | null = deepClone(note); + let result: Misskey.entities.Note | null = deepClone(note); for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(result); - - if (result === null) return isDeleted.value = true; + try { + result = await interruptor.handler(result); + if (result === null) { + isDeleted.value = true; + return; + } + } catch (err) { + console.error(err); + } } note = result; }); diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue index 81ea36c2a5..552f8137ed 100644 --- a/packages/frontend/src/components/MkNotePreview.vue +++ b/packages/frontend/src/components/MkNotePreview.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div> <div> - <Mfm :text="text.trim()" :author="user" :nyaize="'account'" :i="user"/> + <Mfm :text="text.trim()" :author="user" :nyaize="'respect'" :i="user"/> </div> </div> </div> diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 63b122048b..0c51ad9137 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkNoteHeader :class="$style.header" :note="note" :mini="true"/> <div> <p v-if="note.cw != null" :class="$style.cw"> - <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'" :emojiUrls="note.emojis"/> + <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/> <MkCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent"> diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 94f52b7f5d..f61fc608d9 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkNoteHeader :class="$style.header" :note="note" :mini="true"/> <div :class="$style.content"> <p v-if="note.cw != null" :class="$style.cw"> - <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'account'"/> + <Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'"/> <MkCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent"> diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index 77e66f0165..0c817bd64c 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -96,6 +96,10 @@ onUnmounted(() => { onDeactivated(() => { if (connection) connection.dispose(); }); + +defineExpose({ + reload, +}); </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 5643de7683..e7796dfcb5 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -43,12 +43,11 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts"> -import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue'; +import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, watch } from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os.js'; import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js'; import { useDocumentVisibility } from '@/scripts/use-document-visibility.js'; -import MkButton from '@/components/MkButton.vue'; import { defaultStore } from '@/store.js'; import { MisskeyEntity } from '@/types/date-separated-list'; import { i18n } from '@/i18n.js'; @@ -91,6 +90,7 @@ function concatMapWithArray(map: MisskeyEntityMap, entities: MisskeyEntity[]): M </script> <script lang="ts" setup> import { infoImageUrl } from '@/instance.js'; +import MkButton from '@/components/MkButton.vue'; const props = withDefaults(defineProps<{ pagination: Paging; @@ -185,9 +185,8 @@ watch([$$(backed), $$(contentEl)], () => { } }); -if (props.pagination.params && isRef(props.pagination.params)) { - watch(props.pagination.params, init, { deep: true }); -} +// パラメータに何らかの変更があった際、再読込したい(チャンネル等のIDが変わったなど) +watch(() => props.pagination.params, init, { deep: true }); watch(queue, (a, b) => { if (a.size === 0 && b.size === 0) return; @@ -440,8 +439,6 @@ const updateItem = (id: MisskeyEntity['id'], replacer: (old: MisskeyEntity) => M if (queueItem) queue.value.set(id, replacer(queueItem)); }; -const inited = init(); - onActivated(() => { isBackTop.value = false; }); @@ -454,8 +451,8 @@ function toBottom() { scrollToBottom(contentEl!); } -onMounted(() => { - inited.then(() => { +onBeforeMount(() => { + init().then(() => { if (props.pagination.reversed) { nextTick(() => { setTimeout(toBottom, 800); @@ -487,7 +484,6 @@ defineExpose({ queue, backed, more, - inited, reload, prepend, append: appendItem, diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 76e0944cbb..f72e4ddfc3 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -757,7 +757,11 @@ async function post(ev?: MouseEvent) { // plugin if (notePostInterruptors.length > 0) { for (const interruptor of notePostInterruptors) { - postData = await interruptor.handler(deepClone(postData)); + try { + postData = await interruptor.handler(deepClone(postData)); + } catch (err) { + console.error(err); + } } } @@ -1075,6 +1079,7 @@ defineExpose({ .preview { padding: 16px 20px 0 20px; + min-height: 75px; max-height: 150px; overflow: auto; } diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue index 17cd083561..1b0d8f74a3 100644 --- a/packages/frontend/src/components/MkReactionsViewer.details.vue +++ b/packages/frontend/src/components/MkReactionsViewer.details.vue @@ -73,7 +73,6 @@ function getReactionName(reaction: string): string { } .users { - contain: content; flex: 1; min-width: 0; margin: -4px 14px 0 10px; @@ -85,7 +84,7 @@ function getReactionName(reaction: string): string { line-height: 24px; padding-top: 4px; white-space: nowrap; - overflow: hidden; + overflow: visible; text-overflow: ellipsis; } diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index aec657536d..110644947f 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span> <MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" v-on:click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA> - <Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'account'" :isAnim="allowAnim" :emojiUrls="note.emojis"/> + <Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'respect'" :isAnim="allowAnim" :emojiUrls="note.emojis"/> <MkButton v-if="!allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton> <MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> <div v-if="note.text && translating || note.text && translation" :class="$style.translation"> diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 014e6b89e8..466c534259 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -5,19 +5,28 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkPullToRefresh ref="prComponent" :refresher="() => reloadTimeline()"> - <MkNotes ref="tlComponent" :noGap="!defaultStore.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)" @status="prComponent.setDisabled($event)"/> + <MkNotes + v-if="paginationQuery" + ref="tlComponent" + :pagination="paginationQuery" + :noGap="!defaultStore.state.showGapBetweenNotesInTimeline" + @queue="emit('queue', $event)" + @status="prComponent.setDisabled($event)" + /> </MkPullToRefresh> </template> <script lang="ts" setup> -import { computed, provide, onUnmounted } from 'vue'; +import { computed, watch, onUnmounted, provide } from 'vue'; +import { Connection } from 'misskey-js/built/streaming.js'; import MkNotes from '@/components/MkNotes.vue'; import MkPullToRefresh from '@/components/MkPullToRefresh.vue'; -import { useStream, reloadStream } from '@/stream.js'; +import { useStream } from '@/stream.js'; import * as sound from '@/scripts/sound.js'; import { $i } from '@/account.js'; import { instance } from '@/instance.js'; import { defaultStore } from '@/store.js'; +import { Paging } from '@/components/MkPagination.vue'; const props = withDefaults(defineProps<{ src: string; @@ -44,6 +53,18 @@ const emit = defineEmits<{ provide('inChannel', computed(() => props.src === 'channel')); +type TimelineQueryType = { + antennaId?: string, + withRenotes?: boolean, + withReplies?: boolean, + withFiles?: boolean, + withBots?: boolean, + visibility?: string, + listId?: string, + channelId?: string, + roleId?: string +} + const prComponent: InstanceType<typeof MkPullToRefresh> = $ref(); const tlComponent: InstanceType<typeof MkNotes> = $ref(); @@ -65,13 +86,13 @@ const prepend = note => { } }; -let endpoint; -let query; -let connection; -let connection2; +let connection: Connection; +let connection2: Connection; +let paginationQuery: Paging | null = null; const stream = useStream(); -const connectChannel = () => { + +function connectChannel() { if (props.src === 'antenna') { connection = stream.useChannel('antenna', { antennaId: props.antenna, @@ -128,89 +149,116 @@ const connectChannel = () => { }); } if (props.src !== 'directs' || props.src !== 'mentions') connection.on('note', prepend); -}; +} -if (props.src === 'antenna') { - endpoint = 'antennas/notes'; - query = { - antennaId: props.antenna, - }; -} else if (props.src === 'home') { - endpoint = 'notes/timeline'; - query = { - withRenotes: props.withRenotes, - withFiles: props.onlyFiles ? true : undefined, - withBots: props.withBots, - }; -} else if (props.src === 'local') { - endpoint = 'notes/local-timeline'; - query = { - withRenotes: props.withRenotes, - withReplies: props.withReplies, - withBots: props.withBots, - withFiles: props.onlyFiles ? true : undefined, - }; -} else if (props.src === 'social') { - endpoint = 'notes/hybrid-timeline'; - query = { - withRenotes: props.withRenotes, - withReplies: props.withReplies, - withBots: props.withBots, - withFiles: props.onlyFiles ? true : undefined, - }; -} else if (props.src === 'global') { - endpoint = 'notes/global-timeline'; - query = { - withRenotes: props.withRenotes, - withBots: props.withBots, - withFiles: props.onlyFiles ? true : undefined, - }; -} else if (props.src === 'mentions') { - endpoint = 'notes/mentions'; -} else if (props.src === 'directs') { - endpoint = 'notes/mentions'; - query = { - visibility: 'specified', - }; -} else if (props.src === 'list') { - endpoint = 'notes/user-list-timeline'; - query = { - withFiles: props.onlyFiles ? true : undefined, - listId: props.list, - }; -} else if (props.src === 'channel') { - endpoint = 'channels/timeline'; - query = { - channelId: props.channel, - }; -} else if (props.src === 'role') { - endpoint = 'roles/notes'; - query = { - roleId: props.role, - }; +function disconnectChannel() { + if (connection) connection.dispose(); + if (connection2) connection2.dispose(); } -if (!defaultStore.state.disableStreamingTimeline) { - connectChannel(); +function updatePaginationQuery() { + let endpoint: string | null; + let query: TimelineQueryType | null; - onUnmounted(() => { - connection.dispose(); - if (connection2) connection2.dispose(); - }); + if (props.src === 'antenna') { + endpoint = 'antennas/notes'; + query = { + antennaId: props.antenna, + }; + } else if (props.src === 'home') { + endpoint = 'notes/timeline'; + query = { + withRenotes: props.withRenotes, + withFiles: props.onlyFiles ? true : undefined, + withBots: props.withBots, + }; + } else if (props.src === 'local') { + endpoint = 'notes/local-timeline'; + query = { + withRenotes: props.withRenotes, + withReplies: props.withReplies, + withFiles: props.onlyFiles ? true : undefined, + withBots: props.withBots, + }; + } else if (props.src === 'social') { + endpoint = 'notes/hybrid-timeline'; + query = { + withRenotes: props.withRenotes, + withReplies: props.withReplies, + withFiles: props.onlyFiles ? true : undefined, + withBots: props.withBots, + }; + } else if (props.src === 'global') { + endpoint = 'notes/global-timeline'; + query = { + withRenotes: props.withRenotes, + withFiles: props.onlyFiles ? true : undefined, + withBots: props.withBots, + }; + } else if (props.src === 'mentions') { + endpoint = 'notes/mentions'; + query = null; + } else if (props.src === 'directs') { + endpoint = 'notes/mentions'; + query = { + visibility: 'specified', + }; + } else if (props.src === 'list') { + endpoint = 'notes/user-list-timeline'; + query = { + withFiles: props.onlyFiles ? true : undefined, + listId: props.list, + }; + } else if (props.src === 'channel') { + endpoint = 'channels/timeline'; + query = { + channelId: props.channel, + }; + } else if (props.src === 'role') { + endpoint = 'roles/notes'; + query = { + roleId: props.role, + }; + } else { + endpoint = null; + query = null; + } + + if (endpoint && query) { + paginationQuery = { + endpoint: endpoint, + limit: 10, + params: query, + }; + } else { + paginationQuery = null; + } } -const pagination = { - endpoint: endpoint, - limit: 10, - params: query, -}; +function refreshEndpointAndChannel() { + if (!defaultStore.state.disableStreamingTimeline) { + disconnectChannel(); + connectChannel(); + } + + updatePaginationQuery(); +} + +// IDが切り替わったら切り替え先のTLを表示させたい +watch(() => [props.list, props.antenna, props.channel, props.role], refreshEndpointAndChannel); + +// 初回表示用 +refreshEndpointAndChannel(); + +onUnmounted(() => { + disconnectChannel(); +}); function reloadTimeline() { return new Promise<void>((res) => { tlNotesCount = 0; tlComponent.pagingComponent?.reload().then(() => { - reloadStream(); res(); }); }); diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index 9208285e99..c2e36f58b8 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -7,6 +7,7 @@ import { VNode, h } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import MkUrl from '@/components/global/MkUrl.vue'; +import MkTime from '@/components/global/MkTime.vue'; import MkLink from '@/components/MkLink.vue'; import MkMention from '@/components/MkMention.vue'; import MkEmoji from '@/components/global/MkEmoji.vue'; @@ -36,7 +37,7 @@ type MfmProps = { isNote?: boolean; emojiUrls?: string[]; rootScale?: number; - nyaize: boolean | 'account'; + nyaize: boolean | 'respect'; parsedNodes?: mfm.MfmNode[] | null; enableEmojiMenu?: boolean; enableEmojiMenuReaction?: boolean; @@ -46,7 +47,7 @@ type MfmProps = { // eslint-disable-next-line import/no-default-export export default function(props: MfmProps) { const isNote = props.isNote ?? true; - const shouldNyaize = props.nyaize ? props.nyaize === 'account' ? props.author?.isCat ? props.author?.speakAsCat : false : false : false; + const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat ? props.author?.speakAsCat : false : false : false; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (props.text == null || props.text === '') return; @@ -239,6 +240,34 @@ export default function(props: MfmProps) { style = `background-color: #${color};`; break; } + case 'ruby': { + if (token.children.length === 1) { + const child = token.children[0]; + const text = child.type === 'text' ? child.props.text : ''; + return h('ruby', {}, [text.split(' ')[0], h('rt', text.split(' ')[1])]); + } else { + const rt = token.children.at(-1)!; + const text = rt.type === 'text' ? rt.props.text : ''; + return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]); + } + } + case 'unixtime': { + const child = token.children[0]; + const unixtime = parseInt(child.type === 'text' ? child.props.text : ''); + return h('span', { + style: 'display: inline-block; font-size: 90%; border: solid 1px var(--divider); border-radius: 999px; padding: 4px 10px 4px 6px;', + }, [ + h('i', { + class: 'ti ti-clock', + style: 'margin-right: 0.25em;', + }), + h(MkTime, { + key: Math.random(), + time: unixtime * 1000, + mode: 'detail', + }), + ]); + } } if (style == null) { return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index 5ba13ca3f3..f08d538fc0 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -49,8 +49,15 @@ const relative = $computed<string>(() => { ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() }) : ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : - ago >= -1 ? i18n.ts._ago.justNow : - i18n.ts._ago.future); + ago >= -3 ? i18n.ts._ago.justNow : + ago < -31536000 ? i18n.t('_timeIn.years', { n: Math.round(-ago / 31536000).toString() }) : + ago < -2592000 ? i18n.t('_timeIn.months', { n: Math.round(-ago / 2592000).toString() }) : + ago < -604800 ? i18n.t('_timeIn.weeks', { n: Math.round(-ago / 604800).toString() }) : + ago < -86400 ? i18n.t('_timeIn.days', { n: Math.round(-ago / 86400).toString() }) : + ago < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago / 3600).toString() }) : + ago < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago / 60)).toString() }) : + i18n.t('_timeIn.seconds', { n: (~~(-ago % 60)).toString() }) + ); }); let tickId: number; |