mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
implement de-duplication for MkUrlPreview
This commit is contained in:
parent
b05ccbc3ac
commit
c18edd106b
1 changed files with 87 additions and 6 deletions
|
@ -4,7 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<template v-if="player.url && playerEnabled">
|
||||
<div v-if="groupLockout" style="display: none"></div>
|
||||
<template v-else-if="player.url && playerEnabled">
|
||||
<div
|
||||
:class="$style.player"
|
||||
:style="player.width ? `padding: ${(player.height || 0) / player.width * 100}% 0 0` : `padding: ${(player.height || 0)}px 0 0`"
|
||||
|
@ -76,7 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</I18n>
|
||||
<p v-else-if="linkAttribution" :class="$style.linkAttribution"><MkEllipsis/></p>
|
||||
|
||||
<template v-if="showActions">
|
||||
<template v-if="showActions && !groupLockout">
|
||||
<div v-if="tweetId" :class="$style.action">
|
||||
<MkButton :small="true" inline @click="tweetExpanded = true">
|
||||
<i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }}
|
||||
|
@ -99,8 +100,53 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Links a group of previews to de-duplicate the results.
|
||||
* Between all MkUrlPreview instances that share a group, each URL and Note are guaranteed to appear only once.
|
||||
*/
|
||||
export class PreviewGroup {
|
||||
private readonly urls = new Map<string, number>();
|
||||
private readonly noteIds = new Map<string, number>();
|
||||
|
||||
public claimUrl(url: string, componentUid: number): boolean {
|
||||
return this.claim(this.urls, url, componentUid);
|
||||
}
|
||||
|
||||
public claimNoteId(noteId: string, componentUid: number): boolean {
|
||||
return this.claim(this.noteIds, noteId, componentUid);
|
||||
}
|
||||
|
||||
private claim(group: Map<string, number>, key: string, uid: number): boolean {
|
||||
const claim = group.get(key);
|
||||
|
||||
// Already claimed
|
||||
if (claim != null && claim !== uid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
group.set(key, uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
public releaseUrl(url: string, componentUid: number): void {
|
||||
this.release(this.urls, url, componentUid);
|
||||
}
|
||||
|
||||
public releaseNoteId(noteId: string, componentUid: number): void {
|
||||
this.release(this.noteIds, noteId, componentUid);
|
||||
}
|
||||
|
||||
private release(group: Map<string, number>, key: string, uid: number): void {
|
||||
if (group.get(key) === uid) {
|
||||
group.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue';
|
||||
import { defineAsyncComponent, onDeactivated, onUnmounted, ref, getCurrentInstance } from 'vue';
|
||||
import { url as local } from '@@/js/config.js';
|
||||
import { versatileLang } from '@@/js/intl-const.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
|
@ -119,6 +165,11 @@ import DynamicNoteSimple from '@/components/DynamicNoteSimple.vue';
|
|||
import { $i } from '@/i';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
|
||||
const uid = getCurrentInstance()?.uid ?? -1;
|
||||
if (uid === -1) {
|
||||
console.warn('[MkUrlPreview] Component has null instance??');
|
||||
}
|
||||
|
||||
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
|
@ -128,17 +179,24 @@ const props = withDefaults(defineProps<{
|
|||
showAsQuote?: boolean;
|
||||
showActions?: boolean;
|
||||
skipNoteIds?: (string | undefined)[];
|
||||
group?: PreviewGroup;
|
||||
}>(), {
|
||||
detail: false,
|
||||
compact: false,
|
||||
showAsQuote: false,
|
||||
showActions: true,
|
||||
skipNoteIds: undefined,
|
||||
group: undefined,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'loaded', preview: SummalyResult & { haveNoteLocally?: boolean } | null, note: Misskey.entities.Note | null): void;
|
||||
}>();
|
||||
|
||||
const MOBILE_THRESHOLD = 500;
|
||||
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
|
||||
|
||||
const groupLockout = ref<boolean>(false);
|
||||
const hidePreview = ref<boolean>(false);
|
||||
const maybeRelativeUrl = maybeMakeRelative(props.url, local);
|
||||
const self = maybeRelativeUrl !== props.url;
|
||||
|
@ -170,6 +228,7 @@ const tweetHeight = ref(150);
|
|||
const unknownUrl = ref(false);
|
||||
const theNote = ref<Misskey.entities.Note | null>(null);
|
||||
const fetchingTheNote = ref(false);
|
||||
const preview = ref<SummalyResult & { haveNoteLocally?: boolean } | null>(null);
|
||||
|
||||
onDeactivated(() => {
|
||||
playerEnabled.value = false;
|
||||
|
@ -190,6 +249,9 @@ async function fetchNote() {
|
|||
hidePreview.value = true;
|
||||
return;
|
||||
}
|
||||
if (props.group && !props.group.claimNoteId(response['object'].id, uid)) {
|
||||
groupLockout.value = true;
|
||||
}
|
||||
theNote.value = response['object'];
|
||||
} catch (err) {
|
||||
if (_DEV_) {
|
||||
|
@ -216,7 +278,16 @@ if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/
|
|||
|
||||
requestUrl.hash = '';
|
||||
|
||||
function refresh(withFetch = false) {
|
||||
const refresh = (withFetch = false) => {
|
||||
// Release URL/noteID when refreshing, in case it changes.
|
||||
// (Could happen since redirects are allowed.)
|
||||
if (preview.value && props.group) {
|
||||
props.group.releaseUrl(preview.value.url, uid);
|
||||
}
|
||||
if (theNote.value && props.group) {
|
||||
props.group.releaseNoteId(theNote.value.id, uid);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
url: requestUrl.href,
|
||||
lang: versatileLang,
|
||||
|
@ -243,6 +314,7 @@ function refresh(withFetch = false) {
|
|||
userId: string,
|
||||
}
|
||||
} | null) => {
|
||||
preview.value = info;
|
||||
unknownUrl.value = info == null;
|
||||
title.value = info?.title ?? null;
|
||||
description.value = info?.description ?? null;
|
||||
|
@ -257,6 +329,7 @@ function refresh(withFetch = false) {
|
|||
};
|
||||
sensitive.value = info?.sensitive ?? false;
|
||||
activityPub.value = info?.activityPub ?? null;
|
||||
groupLockout.value = info != null && props.group != null && !props.group.claimUrl(info.url, uid);
|
||||
linkAttribution.value = info?.linkAttribution ?? null;
|
||||
if (linkAttribution.value) {
|
||||
try {
|
||||
|
@ -275,8 +348,9 @@ function refresh(withFetch = false) {
|
|||
})
|
||||
.finally(() => {
|
||||
fetching.value = null;
|
||||
emit('loaded', preview.value, theNote.value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function adjustTweetHeight(message: MessageEvent) {
|
||||
if (message.origin !== 'https://platform.twitter.com') return;
|
||||
|
@ -301,6 +375,13 @@ window.addEventListener('message', adjustTweetHeight);
|
|||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('message', adjustTweetHeight);
|
||||
|
||||
if (preview.value && props.group) {
|
||||
props.group.releaseUrl(preview.value.url, uid);
|
||||
}
|
||||
if (theNote.value && props.group) {
|
||||
props.group.releaseNoteId(theNote.value.id, uid);
|
||||
}
|
||||
});
|
||||
|
||||
// Load initial data
|
||||
|
@ -388,7 +469,7 @@ refresh();
|
|||
.body {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
padding: 16px !important; // Unfortunately needed to win a specificity race with MkNoteSimple / SkNoteSimple
|
||||
}
|
||||
|
||||
.header {
|
||||
|
|
Loading…
Add table
Reference in a new issue