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>
|
<script lang="ts" setup>
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, inject, ref, toRaw } from "vue";
|
import { computed, inject, ref, toRaw } from "vue";
|
||||||
import * as mfm from "mfm-js";
|
|
||||||
import type * as misskey from "calckey-js";
|
import type * as misskey from "calckey-js";
|
||||||
import XNoteSub from "@/components/MagNoteSub.vue";
|
import XNoteSub from "@/components/MagNoteSub.vue";
|
||||||
import XSubNoteContent from "./MagSubNoteContent.vue";
|
import XSubNoteContent from "./MagSubNoteContent.vue";
|
||||||
|
|
|
@ -27,11 +27,11 @@
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<XNoteHeader class="header" :note="note" :mini="true" />
|
<XNoteHeader class="header" :note="noteCpy" :mini="true" />
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<XSubNoteContent
|
<XSubNoteContent
|
||||||
class="text"
|
class="text"
|
||||||
:note="note"
|
:note="noteCpy"
|
||||||
:parentId="parentId"
|
:parentId="parentId"
|
||||||
:conversation="conversation"
|
:conversation="conversation"
|
||||||
:detailedView="detailedView"
|
:detailedView="detailedView"
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
/>
|
/>
|
||||||
<div v-else-if="replies.length > 0" class="more">
|
<div v-else-if="replies.length > 0" class="more">
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<MkA class="text _link" :to="notePage(note)"
|
<MkA class="text _link" :to="notePage(noteCpy)"
|
||||||
>{{ i18n.ts.continueThread }}
|
>{{ i18n.ts.continueThread }}
|
||||||
<i class="ph-caret-double-right ph-bold ph-lg"></i
|
<i class="ph-caret-double-right ph-bold ph-lg"></i
|
||||||
></MkA>
|
></MkA>
|
||||||
|
@ -154,11 +154,11 @@
|
||||||
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
||||||
<template #name>
|
<template #name>
|
||||||
<MkA
|
<MkA
|
||||||
v-user-preview="note.user.id"
|
v-user-preview="noteCpy.user.id"
|
||||||
class="name"
|
class="name"
|
||||||
:to="userPage(note.user)"
|
:to="userPage(noteCpy.user)"
|
||||||
>
|
>
|
||||||
<MkUserName :user="note.user" />
|
<MkUserName :user="noteCpy.user" />
|
||||||
</MkA>
|
</MkA>
|
||||||
</template>
|
</template>
|
||||||
<template #reason>
|
<template #reason>
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Ref } from "vue";
|
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 * as misskey from "calckey-js";
|
||||||
import XNoteHeader from "@/components/MagNoteHeader.vue";
|
import XNoteHeader from "@/components/MagNoteHeader.vue";
|
||||||
import XSubNoteContent from "@/components/MagSubNoteContent.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) => {
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
@ -241,10 +250,10 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
const reactButton = ref<HTMLElement | null>(null);
|
const reactButton = ref<HTMLElement | null>(null);
|
||||||
let appearNote = $computed(
|
let appearNote = $computed(
|
||||||
() => magEffectiveNote(props.note) as packed.PackNoteMaybeFull
|
() => magEffectiveNote(noteCpy) as packed.PackNoteMaybeFull
|
||||||
);
|
);
|
||||||
const isDeleted = ref(false);
|
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 translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const replies: packed.PackNoteMaybeFull[] =
|
const replies: packed.PackNoteMaybeFull[] =
|
||||||
|
@ -307,7 +316,7 @@ const currentClipPage = inject<Ref<misskey.entities.Clip> | null>(
|
||||||
function menu(viaKeyboard = false): void {
|
function menu(viaKeyboard = false): void {
|
||||||
os.popupMenu(
|
os.popupMenu(
|
||||||
getNoteMenu({
|
getNoteMenu({
|
||||||
note: note,
|
note: noteCpy,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton: menuButton.value,
|
menuButton: menuButton.value,
|
||||||
|
@ -374,12 +383,12 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
copyToClipboard(`${url}${notePage(appearNote)}`);
|
copyToClipboard(`${url}${notePage(appearNote)}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
note.user.host != null
|
noteCpy.user.host != null
|
||||||
? {
|
? {
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
||||||
text: i18n.ts.showOnRemote,
|
text: i18n.ts.showOnRemote,
|
||||||
href: note.url ?? note.uri ?? "",
|
href: noteCpy.url ?? noteCpy.uri ?? "",
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
@ -52,6 +52,7 @@ import { $i } from "@/account";
|
||||||
import MagPagination, { Paging } from "@/components/MagPagination.vue";
|
import MagPagination, { Paging } from "@/components/MagPagination.vue";
|
||||||
import { endpoints, types } from "magnetar-common";
|
import { endpoints, types } from "magnetar-common";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { ChannelEvent } from "magnetar-common/built/types";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
includeTypes?: types.NotificationType[];
|
includeTypes?: types.NotificationType[];
|
||||||
|
@ -89,7 +90,7 @@ const onNotification = (notification) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let notifStream;
|
let notifStream: ((e: ChannelEvent) => void) | undefined;
|
||||||
let connection;
|
let connection;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@ -132,6 +133,7 @@ onMounted(() => {
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (connection) connection.dispose();
|
if (connection) connection.dispose();
|
||||||
|
if (notifStream) magStream.off("message", notifStream);
|
||||||
});
|
});
|
||||||
</script>
|
</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 (props.pagination.reversed) {
|
||||||
if (rootEl.value) {
|
|
||||||
const container = getScrollContainer(rootEl.value);
|
const container = getScrollContainer(rootEl.value);
|
||||||
if (container == null) {
|
|
||||||
// TODO?
|
if (!container) {
|
||||||
} else {
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const pos = getScrollPosition(rootEl.value);
|
const pos = getScrollPosition(rootEl.value);
|
||||||
const viewHeight = container.clientHeight;
|
const viewHeight = container.clientHeight;
|
||||||
const height = container.scrollHeight;
|
const height = container.scrollHeight;
|
||||||
const isBottom = pos + viewHeight > height - 32;
|
const isBottom = pos + viewHeight > height - 32;
|
||||||
if (isBottom) {
|
return isBottom;
|
||||||
items.value = items.value.slice(-props.displayLimit);
|
|
||||||
hasMore.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items.value.push(item);
|
|
||||||
// TODO
|
|
||||||
} else {
|
} else {
|
||||||
// 初回表示時はunshiftだけでOK
|
|
||||||
if (!rootEl.value) {
|
|
||||||
items.value.unshift(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTop =
|
const isTop =
|
||||||
isBackTop.value ||
|
isBackTop.value ||
|
||||||
(document.body.contains(rootEl.value) &&
|
(document.body.contains(rootEl.value) &&
|
||||||
isTopVisible(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
|
// Prepend the item
|
||||||
items.value = [item, ...items.value].slice(0, props.displayLimit);
|
items.value = [item, ...items.value].slice(0, props.displayLimit);
|
||||||
} else {
|
} else {
|
||||||
if (!queue.value.length) {
|
if (!queue.value.length && rootEl.value) {
|
||||||
onScrollTop(rootEl.value, () => {
|
onScrollTop(rootEl.value, () => {
|
||||||
items.value = [
|
items.value = [
|
||||||
...queue.value.reverse(),
|
...queue.value.reverse(),
|
||||||
|
@ -370,6 +372,7 @@ defineExpose({
|
||||||
append,
|
append,
|
||||||
removeItem,
|
removeItem,
|
||||||
updateItem,
|
updateItem,
|
||||||
|
isFresh,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onUnmounted } from "vue";
|
import { onUnmounted, ref } from "vue";
|
||||||
import XNotes from "@/components/MkNotes.vue";
|
import XNotes from "@/components/MkNotes.vue";
|
||||||
import MkInfo from "@/components/MkInfo.vue";
|
import MkInfo from "@/components/MkInfo.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
@ -27,7 +27,9 @@ import * as sound from "@/scripts/sound";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
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<{
|
const props = defineProps<{
|
||||||
src: string;
|
src: string;
|
||||||
|
@ -41,41 +43,70 @@ const emit = defineEmits<{
|
||||||
(ev: "queue", count: number): void;
|
(ev: "queue", count: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tlComponent: InstanceType<typeof XNotes> = $ref();
|
const displayLimit = 30;
|
||||||
|
|
||||||
const prepend = (note: string) => {
|
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(
|
os.magApi(
|
||||||
endpoints.GetNoteById,
|
endpoints.GetNoteById,
|
||||||
{ context: true, attachments: true },
|
{ context: true, attachments: true },
|
||||||
{
|
{
|
||||||
id: note,
|
id: note,
|
||||||
}
|
}
|
||||||
).then((n) => {
|
)
|
||||||
tlComponent.pagingComponent?.prepend(n);
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.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");
|
emit("note");
|
||||||
|
}
|
||||||
|
|
||||||
if (props.sound) {
|
if (props.sound) {
|
||||||
sound.play($i && n.user.id === $i.id ? "noteMy" : "note");
|
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 = () => {
|
const onUserAdded = () => {
|
||||||
tlComponent.pagingComponent?.reload();
|
tlComponent.value?.pagingComponent?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUserRemoved = () => {
|
const onUserRemoved = () => {
|
||||||
tlComponent.pagingComponent?.reload();
|
tlComponent.value?.pagingComponent?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeFollowing = () => {
|
const onChangeFollowing = () => {
|
||||||
if (!tlComponent.pagingComponent?.backed) {
|
if (!tlComponent.value?.pagingComponent?.backed) {
|
||||||
tlComponent.pagingComponent?.reload();
|
tlComponent.value?.pagingComponent?.reload();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let endpoint;
|
let endpoint: keyof misskey.Endpoints;
|
||||||
let query;
|
let query;
|
||||||
let connection;
|
let connection;
|
||||||
let connection2;
|
let connection2;
|
||||||
|
|
Loading…
Reference in New Issue