Fixed note rendering issues and added debouncing for timeline notes
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
This commit is contained in:
parent
16739635d1
commit
169e85dead
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 {
|
||||
// 初回表示時はunshiftだけでOK
|
||||
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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue