mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
support link attributions in SkUrlPreviewGroup
This commit is contained in:
parent
b811a8f0ab
commit
bae4c07bb3
2 changed files with 92 additions and 30 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue