Fixed note rendering issues and added debouncing for timeline notes
ci/woodpecker/push/ociImagePush Pipeline was successful Details

This commit is contained in:
Natty 2024-02-28 02:14:38 +01:00
parent 16739635d1
commit 169e85dead
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
5 changed files with 105 additions and 61 deletions

View File

@ -219,7 +219,6 @@
<script lang="ts" setup>
import type { Ref } from "vue";
import { computed, inject, ref, toRaw } from "vue";
import * as mfm from "mfm-js";
import type * as misskey from "calckey-js";
import XNoteSub from "@/components/MagNoteSub.vue";
import XSubNoteContent from "./MagSubNoteContent.vue";

View File

@ -27,11 +27,11 @@
></div>
</div>
<div class="body">
<XNoteHeader class="header" :note="note" :mini="true" />
<XNoteHeader class="header" :note="noteCpy" :mini="true" />
<div class="body">
<XSubNoteContent
class="text"
:note="note"
:note="noteCpy"
:parentId="parentId"
:conversation="conversation"
:detailedView="detailedView"
@ -143,7 +143,7 @@
/>
<div v-else-if="replies.length > 0" class="more">
<div class="line"></div>
<MkA class="text _link" :to="notePage(note)"
<MkA class="text _link" :to="notePage(noteCpy)"
>{{ i18n.ts.continueThread }}
<i class="ph-caret-double-right ph-bold ph-lg"></i
></MkA>
@ -154,11 +154,11 @@
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
<template #name>
<MkA
v-user-preview="note.user.id"
v-user-preview="noteCpy.user.id"
class="name"
:to="userPage(note.user)"
:to="userPage(noteCpy.user)"
>
<MkUserName :user="note.user" />
<MkUserName :user="noteCpy.user" />
</MkA>
</template>
<template #reason>
@ -170,7 +170,7 @@
<script lang="ts" setup>
import type { Ref } from "vue";
import { inject, ref, toRaw } from "vue";
import { inject, ref, toRaw, watch } from "vue";
import * as misskey from "calckey-js";
import XNoteHeader from "@/components/MagNoteHeader.vue";
import XSubNoteContent from "@/components/MagSubNoteContent.vue";
@ -222,7 +222,16 @@ const props = withDefaults(
}
);
let note = $ref<packed.PackNoteMaybeFull>(structuredClone(toRaw(props.note)));
let noteCpy = $ref<packed.PackNoteMaybeFull>(
structuredClone(toRaw(props.note))
);
watch(
() => props.note,
(val) => {
noteCpy = structuredClone(toRaw(val));
}
);
const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -241,10 +250,10 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement | null>(null);
let appearNote = $computed(
() => magEffectiveNote(props.note) as packed.PackNoteMaybeFull
() => magEffectiveNote(noteCpy) as packed.PackNoteMaybeFull
);
const isDeleted = ref(false);
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
const muted = ref(getWordSoftMute(noteCpy, $i, defaultStore.state.mutedWords));
const translation = ref(null);
const translating = ref(false);
const replies: packed.PackNoteMaybeFull[] =
@ -307,7 +316,7 @@ const currentClipPage = inject<Ref<misskey.entities.Clip> | null>(
function menu(viaKeyboard = false): void {
os.popupMenu(
getNoteMenu({
note: note,
note: noteCpy,
translating,
translation,
menuButton: menuButton.value,
@ -374,12 +383,12 @@ function onContextmenu(ev: MouseEvent): void {
copyToClipboard(`${url}${notePage(appearNote)}`);
},
},
note.user.host != null
noteCpy.user.host != null
? {
type: "a",
icon: "ph-arrow-square-up-right ph-bold ph-lg",
text: i18n.ts.showOnRemote,
href: note.url ?? note.uri ?? "",
href: noteCpy.url ?? noteCpy.uri ?? "",
target: "_blank",
}
: undefined,

View File

@ -52,6 +52,7 @@ import { $i } from "@/account";
import MagPagination, { Paging } from "@/components/MagPagination.vue";
import { endpoints, types } from "magnetar-common";
import { i18n } from "@/i18n";
import { ChannelEvent } from "magnetar-common/built/types";
const props = defineProps<{
includeTypes?: types.NotificationType[];
@ -89,7 +90,7 @@ const onNotification = (notification) => {
}
};
let notifStream;
let notifStream: ((e: ChannelEvent) => void) | undefined;
let connection;
onMounted(() => {
@ -132,6 +133,7 @@ onMounted(() => {
onUnmounted(() => {
if (connection) connection.dispose();
if (notifStream) magStream.off("message", notifStream);
});
</script>

View File

@ -263,42 +263,44 @@ const fetchMore = async (): Promise<void> => {
);
};
const prepend = (item: Item): void => {
const isFresh = (): boolean => {
if (!rootEl.value) return false;
if (props.pagination.reversed) {
if (rootEl.value) {
const container = getScrollContainer(rootEl.value);
if (container == null) {
// TODO?
} else {
const pos = getScrollPosition(rootEl.value);
const viewHeight = container.clientHeight;
const height = container.scrollHeight;
const isBottom = pos + viewHeight > height - 32;
if (isBottom) {
items.value = items.value.slice(-props.displayLimit);
hasMore.value = true;
}
}
}
items.value.push(item);
// TODO
} else {
// unshiftOK
if (!rootEl.value) {
items.value.unshift(item);
return;
const container = getScrollContainer(rootEl.value);
if (!container) {
return false;
}
const pos = getScrollPosition(rootEl.value);
const viewHeight = container.clientHeight;
const height = container.scrollHeight;
const isBottom = pos + viewHeight > height - 32;
return isBottom;
} else {
const isTop =
isBackTop.value ||
(document.body.contains(rootEl.value) &&
isTopVisible(rootEl.value));
return isTop;
}
};
if (isTop) {
const prepend = (item: Item): void => {
if (props.pagination.reversed) {
if (isFresh()) {
items.value = items.value.slice(-props.displayLimit);
hasMore.value = true;
}
items.value.push(item);
} else {
if (isFresh()) {
// Prepend the item
items.value = [item, ...items.value].slice(0, props.displayLimit);
} else {
if (!queue.value.length) {
if (!queue.value.length && rootEl.value) {
onScrollTop(rootEl.value, () => {
items.value = [
...queue.value.reverse(),
@ -370,6 +372,7 @@ defineExpose({
append,
removeItem,
updateItem,
isFresh,
});
</script>

View File

@ -18,7 +18,7 @@
</template>
<script lang="ts" setup>
import { onUnmounted } from "vue";
import { onUnmounted, ref } from "vue";
import XNotes from "@/components/MkNotes.vue";
import MkInfo from "@/components/MkInfo.vue";
import * as os from "@/os";
@ -27,7 +27,9 @@ import * as sound from "@/scripts/sound";
import { $i } from "@/account";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
import { endpoints } from "magnetar-common";
import { endpoints, packed } from "magnetar-common";
import { debounce } from "throttle-debounce";
import * as misskey from "calckey-js";
const props = defineProps<{
src: string;
@ -41,41 +43,70 @@ const emit = defineEmits<{
(ev: "queue", count: number): void;
}>();
const tlComponent: InstanceType<typeof XNotes> = $ref();
const displayLimit = 30;
const prepend = (note: string) => {
os.magApi(
endpoints.GetNoteById,
{ context: true, attachments: true },
{
id: note,
}
).then((n) => {
tlComponent.pagingComponent?.prepend(n);
const tlComponent = ref<InstanceType<typeof XNotes>>();
let debounceBuffer: Array<string> = [];
const prependMany = async () => {
let items = debounceBuffer;
debounceBuffer = [];
if (!tlComponent.value?.pagingComponent?.isFresh()) {
items = debounceBuffer.slice(-displayLimit);
}
const notes = (
await Promise.allSettled(
items.map((note) =>
os.magApi(
endpoints.GetNoteById,
{ context: true, attachments: true },
{
id: note,
}
)
)
)
)
.filter((p) => p.status === "fulfilled")
.map(
(p) => (p as PromiseFulfilledResult<packed.PackNoteMaybeFull>).value
);
for (const n of notes) {
tlComponent.value?.pagingComponent?.prepend(n);
emit("note");
}
if (props.sound) {
sound.play($i && n.user.id === $i.id ? "noteMy" : "note");
}
});
if (props.sound) {
if (notes.some((nn) => nn.user.id !== $i?.id)) sound.play("note");
if (notes.some((nn) => nn.user.id === $i?.id)) sound.play("noteMy");
}
};
const debouncePrepend = debounce(40, prependMany);
const prepend = (note: string) => {
debounceBuffer.push(note);
debouncePrepend();
};
const onUserAdded = () => {
tlComponent.pagingComponent?.reload();
tlComponent.value?.pagingComponent?.reload();
};
const onUserRemoved = () => {
tlComponent.pagingComponent?.reload();
tlComponent.value?.pagingComponent?.reload();
};
const onChangeFollowing = () => {
if (!tlComponent.pagingComponent?.backed) {
tlComponent.pagingComponent?.reload();
if (!tlComponent.value?.pagingComponent?.backed) {
tlComponent.value?.pagingComponent?.reload();
}
};
let endpoint;
let endpoint: keyof misskey.Endpoints;
let query;
let connection;
let connection2;