diff options
Diffstat (limited to 'packages/frontend')
| -rw-r--r-- | packages/frontend/src/components/MkUrlPreview.vue | 64 | ||||
| -rw-r--r-- | packages/frontend/src/components/SkUrlPreviewGroup.vue | 58 |
2 files changed, 92 insertions, 30 deletions
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 680a9a7141..5d0e6e3df7 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -99,13 +99,24 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </template> +<script lang="ts"> +// eslint-disable-next-line import/order +import type { summaly } from '@misskey-dev/summaly'; + +export type SummalyResult = Awaited<ReturnType<typeof summaly>> & { + haveNoteLocally?: boolean, + linkAttribution?: { + userId: string, + } +}; +</script> + <script lang="ts" setup> import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue'; import { url as local } from '@@/js/config.js'; import { versatileLang } from '@@/js/intl-const.js'; import * as Misskey from 'misskey-js'; import { maybeMakeRelative } from '@@/js/url.js'; -import type { summaly } from '@misskey-dev/summaly'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { deviceKind } from '@/utility/device-kind.js'; @@ -119,13 +130,6 @@ import DynamicNoteSimple from '@/components/DynamicNoteSimple.vue'; import { $i } from '@/i'; import { userPage } from '@/filters/user.js'; -type SummalyResult = Awaited<ReturnType<typeof summaly>> & { - haveNoteLocally?: boolean, - linkAttribution?: { - userId: string, - } -}; - const props = withDefaults(defineProps<{ url: string; detail?: boolean; @@ -135,6 +139,7 @@ const props = withDefaults(defineProps<{ skipNoteIds?: (string | undefined)[]; previewHint?: SummalyResult; noteHint?: Misskey.entities.Note | null; + attributionHint?: Misskey.entities.User | null; }>(), { detail: false, compact: false, @@ -143,6 +148,7 @@ const props = withDefaults(defineProps<{ skipNoteIds: undefined, previewHint: undefined, noteHint: undefined, + attributionHint: undefined, }); const MOBILE_THRESHOLD = 500; @@ -179,11 +185,34 @@ const tweetHeight = ref(150); const unknownUrl = ref(false); const theNote = ref<Misskey.entities.Note | null>(null); const fetchingTheNote = ref(false); +const fetchingAttribution = ref<Promise<void> | null>(null); onDeactivated(() => { playerEnabled.value = false; }); +async function fetchAttribution(initial: boolean): Promise<void> { + if (!linkAttribution.value) return; + if (attributionUser.value) return; + if (fetchingAttribution.value) return fetchingAttribution.value; + + return fetchingAttribution.value ??= (async (userId: string): Promise<void> => { + try { + if (initial && props.attributionHint !== undefined) { + attributionUser.value = props.attributionHint; + } else { + attributionUser.value = await misskeyApi('users/show', { userId }); + } + } catch { + // makes the loading ellipsis vanish. + linkAttribution.value = null; + } finally { + // Reset promise to mark as done + fetchingAttribution.value = null; + } + })(linkAttribution.value.userId); +} + async function fetchNote(initial: boolean) { if (!props.showAsQuote) return; if (!activityPub.value) return; @@ -275,20 +304,15 @@ function refresh(withFetch = false, initial = false) { sensitive.value = info?.sensitive ?? false; activityPub.value = info?.activityPub ?? null; linkAttribution.value = info?.linkAttribution ?? null; - if (linkAttribution.value) { - try { - const response = await misskeyApi('users/show', { userId: linkAttribution.value.userId }); - attributionUser.value = response; - } catch { - // makes the loading ellipsis vanish. - linkAttribution.value = null; - } - } + // These will be populated by the fetch* functions + attributionUser.value = null; theNote.value = null; - if (info?.haveNoteLocally) { - await fetchNote(initial); - } + + await Promise.all([ + fetchAttribution(initial), + fetchNote(initial), + ]); }) .finally(() => { fetching.value = null; diff --git a/packages/frontend/src/components/SkUrlPreviewGroup.vue b/packages/frontend/src/components/SkUrlPreviewGroup.vue index 5175e16cfc..32b11d9db4 100644 --- a/packages/frontend/src/components/SkUrlPreviewGroup.vue +++ b/packages/frontend/src/components/SkUrlPreviewGroup.vue @@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only :url="preview.url" :previewHint="preview" :noteHint="preview.note" + :attributionHint="preview.attributionUser" :detail="detail" :compact="compact" :showAsQuote="showAsQuote" @@ -29,7 +30,7 @@ import * as mfm from '@transfem-org/sfm-js'; import { computed, ref, watch } from 'vue'; import { versatileLang } from '@@/js/intl-const'; import promiseLimit from 'promise-limit'; -import type { summaly } from '@misskey-dev/summaly'; +import type { SummalyResult } from '@/components/MkUrlPreview.vue'; import { extractPreviewUrls } from '@/utility/extract-preview-urls'; import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm'; import { $i } from '@/i'; @@ -37,11 +38,13 @@ import { misskeyApi } from '@/utility/misskey-api'; import MkUrlPreview from '@/components/MkUrlPreview.vue'; import { getNoteUrls } from '@/utility/getNoteUrls'; -type Summary = Awaited<ReturnType<typeof summaly>> & { - haveNoteLocally?: boolean; +type Summary = SummalyResult & { note?: Misskey.entities.Note | null; + attributionUser?: Misskey.entities.User | null; }; +type Limiter<T> = ReturnType<typeof promiseLimit<T>>; + const props = withDefaults(defineProps<{ sourceUrls?: string[]; sourceNodes?: mfm.MfmNode[]; @@ -90,9 +93,11 @@ const urls = computed<string[]>(() => { return []; }); +// todo un-ref these const isRefreshing = ref<Promise<void> | false>(false); const cachedNotes = ref(new Map<string, Misskey.entities.Note | null>()); const cachedPreviews = ref(new Map<string, Summary | null>()); +const cachedUsers = new Map<string, Misskey.entities.User | null>(); /** * Refreshes the group. @@ -124,6 +129,7 @@ async function doRefresh(): Promise<void> { } async function fetchPreviews(): Promise<Summary[]> { + const userLimiter = promiseLimit<Misskey.entities.User | null>(4); const noteLimiter = promiseLimit<Misskey.entities.Note | null>(2); const summaryLimiter = promiseLimit<Summary | null>(5); @@ -131,13 +137,11 @@ async function fetchPreviews(): Promise<Summary[]> { summaryLimiter(async () => { return await fetchPreview(url); }).then(async (summary) => { - if (summary && props.showAsQuote && summary.activityPub && summary.haveNoteLocally) { - // Have to pull this out to make TS happy - const noteUri = summary.activityPub; - - summary.note = await noteLimiter(async () => { - return await fetchNote(noteUri); - }); + if (summary) { + await Promise.all([ + attachNote(summary, noteLimiter), + attachAttribution(summary, userLimiter), + ]); } return summary; @@ -171,6 +175,17 @@ async function fetchPreview(url: string): Promise<Summary | null> { return null; } +async function attachNote(summary: Summary, noteLimiter: Limiter<Misskey.entities.Note | null>): Promise<void> { + if (props.showAsQuote && summary.activityPub && summary.haveNoteLocally) { + // Have to pull this out to make TS happy + const noteUri = summary.activityPub; + + summary.note = await noteLimiter(async () => { + return await fetchNote(noteUri); + }); + } +} + async function fetchNote(noteUri: string): Promise<Misskey.entities.Note | null> { const cached = cachedNotes.value.get(noteUri); if (cached) { @@ -194,6 +209,29 @@ async function fetchNote(noteUri: string): Promise<Misskey.entities.Note | null> return null; } +async function attachAttribution(summary: Summary, userLimiter: Limiter<Misskey.entities.User | null>): Promise<void> { + if (summary.linkAttribution) { + // Have to pull this out to make TS happy + const userId = summary.linkAttribution.userId; + + summary.attributionUser = await userLimiter(async () => { + return await fetchUser(userId); + }); + } +} + +async function fetchUser(userId: string): Promise<Misskey.entities.User | null> { + const cached = cachedUsers.get(userId); + if (cached) { + return cached; + } + + const user = await misskeyApi('users/show', { userId }).catch(() => null); + + cachedUsers.set(userId, user); + return user; +} + function deduplicatePreviews(previews: Summary[]): Summary[] { // eslint-disable-next-line no-param-reassign previews = previews |