mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 20:44:34 +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>
|
||||||
<template v-if="player.url && playerEnabled">
|
<div v-if="groupLockout" style="display: none"></div>
|
||||||
|
<template v-else-if="player.url && playerEnabled">
|
||||||
<div
|
<div
|
||||||
:class="$style.player"
|
:class="$style.player"
|
||||||
:style="player.width ? `padding: ${(player.height || 0) / player.width * 100}% 0 0` : `padding: ${(player.height || 0)}px 0 0`"
|
: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>
|
</I18n>
|
||||||
<p v-else-if="linkAttribution" :class="$style.linkAttribution"><MkEllipsis/></p>
|
<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">
|
<div v-if="tweetId" :class="$style.action">
|
||||||
<MkButton :small="true" inline @click="tweetExpanded = true">
|
<MkButton :small="true" inline @click="tweetExpanded = true">
|
||||||
<i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }}
|
<i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }}
|
||||||
|
@ -99,8 +100,53 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
<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 { url as local } from '@@/js/config.js';
|
||||||
import { versatileLang } from '@@/js/intl-const.js';
|
import { versatileLang } from '@@/js/intl-const.js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
@ -119,6 +165,11 @@ import DynamicNoteSimple from '@/components/DynamicNoteSimple.vue';
|
||||||
import { $i } from '@/i';
|
import { $i } from '@/i';
|
||||||
import { userPage } from '@/filters/user.js';
|
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>>;
|
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
@ -128,17 +179,24 @@ const props = withDefaults(defineProps<{
|
||||||
showAsQuote?: boolean;
|
showAsQuote?: boolean;
|
||||||
showActions?: boolean;
|
showActions?: boolean;
|
||||||
skipNoteIds?: (string | undefined)[];
|
skipNoteIds?: (string | undefined)[];
|
||||||
|
group?: PreviewGroup;
|
||||||
}>(), {
|
}>(), {
|
||||||
detail: false,
|
detail: false,
|
||||||
compact: false,
|
compact: false,
|
||||||
showAsQuote: false,
|
showAsQuote: false,
|
||||||
showActions: true,
|
showActions: true,
|
||||||
skipNoteIds: undefined,
|
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 MOBILE_THRESHOLD = 500;
|
||||||
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
|
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
|
||||||
|
|
||||||
|
const groupLockout = ref<boolean>(false);
|
||||||
const hidePreview = ref<boolean>(false);
|
const hidePreview = ref<boolean>(false);
|
||||||
const maybeRelativeUrl = maybeMakeRelative(props.url, local);
|
const maybeRelativeUrl = maybeMakeRelative(props.url, local);
|
||||||
const self = maybeRelativeUrl !== props.url;
|
const self = maybeRelativeUrl !== props.url;
|
||||||
|
@ -170,6 +228,7 @@ const tweetHeight = ref(150);
|
||||||
const unknownUrl = ref(false);
|
const unknownUrl = ref(false);
|
||||||
const theNote = ref<Misskey.entities.Note | null>(null);
|
const theNote = ref<Misskey.entities.Note | null>(null);
|
||||||
const fetchingTheNote = ref(false);
|
const fetchingTheNote = ref(false);
|
||||||
|
const preview = ref<SummalyResult & { haveNoteLocally?: boolean } | null>(null);
|
||||||
|
|
||||||
onDeactivated(() => {
|
onDeactivated(() => {
|
||||||
playerEnabled.value = false;
|
playerEnabled.value = false;
|
||||||
|
@ -190,6 +249,9 @@ async function fetchNote() {
|
||||||
hidePreview.value = true;
|
hidePreview.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (props.group && !props.group.claimNoteId(response['object'].id, uid)) {
|
||||||
|
groupLockout.value = true;
|
||||||
|
}
|
||||||
theNote.value = response['object'];
|
theNote.value = response['object'];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
|
@ -216,7 +278,16 @@ if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/
|
||||||
|
|
||||||
requestUrl.hash = '';
|
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({
|
const params = new URLSearchParams({
|
||||||
url: requestUrl.href,
|
url: requestUrl.href,
|
||||||
lang: versatileLang,
|
lang: versatileLang,
|
||||||
|
@ -243,6 +314,7 @@ function refresh(withFetch = false) {
|
||||||
userId: string,
|
userId: string,
|
||||||
}
|
}
|
||||||
} | null) => {
|
} | null) => {
|
||||||
|
preview.value = info;
|
||||||
unknownUrl.value = info == null;
|
unknownUrl.value = info == null;
|
||||||
title.value = info?.title ?? null;
|
title.value = info?.title ?? null;
|
||||||
description.value = info?.description ?? null;
|
description.value = info?.description ?? null;
|
||||||
|
@ -257,6 +329,7 @@ function refresh(withFetch = false) {
|
||||||
};
|
};
|
||||||
sensitive.value = info?.sensitive ?? false;
|
sensitive.value = info?.sensitive ?? false;
|
||||||
activityPub.value = info?.activityPub ?? null;
|
activityPub.value = info?.activityPub ?? null;
|
||||||
|
groupLockout.value = info != null && props.group != null && !props.group.claimUrl(info.url, uid);
|
||||||
linkAttribution.value = info?.linkAttribution ?? null;
|
linkAttribution.value = info?.linkAttribution ?? null;
|
||||||
if (linkAttribution.value) {
|
if (linkAttribution.value) {
|
||||||
try {
|
try {
|
||||||
|
@ -275,8 +348,9 @@ function refresh(withFetch = false) {
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
fetching.value = null;
|
fetching.value = null;
|
||||||
|
emit('loaded', preview.value, theNote.value);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function adjustTweetHeight(message: MessageEvent) {
|
function adjustTweetHeight(message: MessageEvent) {
|
||||||
if (message.origin !== 'https://platform.twitter.com') return;
|
if (message.origin !== 'https://platform.twitter.com') return;
|
||||||
|
@ -301,6 +375,13 @@ window.addEventListener('message', adjustTweetHeight);
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('message', adjustTweetHeight);
|
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
|
// Load initial data
|
||||||
|
@ -388,7 +469,7 @@ refresh();
|
||||||
.body {
|
.body {
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 16px;
|
padding: 16px !important; // Unfortunately needed to win a specificity race with MkNoteSimple / SkNoteSimple
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|
Loading…
Add table
Reference in a new issue