2020-01-29 20:37:25 +01:00
|
|
|
<template>
|
2023-05-29 22:17:42 +02:00
|
|
|
<MkInfo
|
2024-03-01 16:19:05 +01:00
|
|
|
v-if="tlHint && !tlHintClosed && isSignedIn"
|
2023-05-29 22:17:42 +02:00
|
|
|
:closeable="true"
|
|
|
|
class="_gap"
|
|
|
|
@close="closeHint"
|
|
|
|
>
|
|
|
|
<I18n :src="tlHint">
|
2023-05-29 20:51:02 +02:00
|
|
|
<template #icon></template>
|
|
|
|
</I18n>
|
|
|
|
</MkInfo>
|
2023-07-02 03:35:05 +02:00
|
|
|
<div v-if="queue > 0" class="new">
|
|
|
|
<button
|
|
|
|
class="_buttonPrimary _shadow"
|
2023-10-17 03:57:20 +02:00
|
|
|
:class="{ instant: !defaultStore.state.animation }"
|
2024-02-29 14:21:19 +01:00
|
|
|
@click="tlComponent?.scrollTop()"
|
2023-07-02 03:35:05 +02:00
|
|
|
>
|
|
|
|
{{ i18n.ts.newNoteRecived }}
|
2023-10-17 03:57:20 +02:00
|
|
|
<i :class="icon('ph-arrow-up', false)"></i>
|
2023-07-02 03:35:05 +02:00
|
|
|
</button>
|
|
|
|
</div>
|
2024-02-29 14:21:19 +01:00
|
|
|
<MkPullToRefresh
|
|
|
|
v-if="defaultStore.state.enablePullToRefresh"
|
|
|
|
ref="pullToRefreshComponent"
|
|
|
|
:refresher="() => reloadTimeline()"
|
|
|
|
>
|
|
|
|
<XNotes
|
|
|
|
ref="tlComponent"
|
|
|
|
:no-gap="!defaultStore.state.showGapBetweenNotesInTimeline"
|
|
|
|
:pagination="pagination"
|
|
|
|
@queue="(x) => (queue = x)"
|
|
|
|
@status="pullToRefreshComponent?.setDisabled($event)"
|
|
|
|
/>
|
|
|
|
</MkPullToRefresh>
|
2023-04-08 02:01:42 +02:00
|
|
|
<XNotes
|
2024-02-29 14:21:19 +01:00
|
|
|
v-else
|
2023-04-08 02:01:42 +02:00
|
|
|
ref="tlComponent"
|
2023-10-17 03:57:20 +02:00
|
|
|
:no-gap="!defaultStore.state.showGapBetweenNotesInTimeline"
|
2023-04-08 02:01:42 +02:00
|
|
|
:pagination="pagination"
|
2023-07-02 05:46:33 +02:00
|
|
|
@queue="(x) => (queue = x)"
|
2024-02-29 14:21:19 +01:00
|
|
|
@status="pullToRefreshComponent?.setDisabled($event)"
|
2023-04-08 02:01:42 +02:00
|
|
|
/>
|
2020-01-29 20:37:25 +01:00
|
|
|
</template>
|
|
|
|
|
2022-01-09 14:57:27 +01:00
|
|
|
<script lang="ts" setup>
|
2023-09-02 01:27:33 +02:00
|
|
|
import { computed, onUnmounted, provide, ref } from "vue";
|
2024-04-03 17:10:49 +02:00
|
|
|
import type { entities } from "firefish-js";
|
2024-02-29 14:21:19 +01:00
|
|
|
import MkPullToRefresh from "@/components/MkPullToRefresh.vue";
|
2023-04-08 02:01:42 +02:00
|
|
|
import XNotes from "@/components/MkNotes.vue";
|
2023-05-29 20:51:02 +02:00
|
|
|
import MkInfo from "@/components/MkInfo.vue";
|
2024-02-29 14:21:19 +01:00
|
|
|
import { useStream } from "@/stream";
|
2023-04-08 02:01:42 +02:00
|
|
|
import * as sound from "@/scripts/sound";
|
2024-03-16 16:49:12 +01:00
|
|
|
import { isSignedIn, me } from "@/me";
|
2023-05-29 20:51:02 +02:00
|
|
|
import { i18n } from "@/i18n";
|
|
|
|
import { defaultStore } from "@/store";
|
2023-10-17 03:57:20 +02:00
|
|
|
import icon from "@/scripts/icon";
|
2024-04-03 17:10:49 +02:00
|
|
|
import type { EndpointsOf } from "@/components/MkPagination.vue";
|
|
|
|
|
|
|
|
export type TimelineSource =
|
|
|
|
| "antenna"
|
|
|
|
| "home"
|
|
|
|
| "local"
|
|
|
|
| "recommended"
|
|
|
|
| "social"
|
|
|
|
| "global"
|
|
|
|
| "mentions"
|
|
|
|
| "directs"
|
|
|
|
| "list"
|
|
|
|
| "channel"
|
|
|
|
| "file";
|
2020-01-29 20:37:25 +01:00
|
|
|
|
2022-01-09 14:57:27 +01:00
|
|
|
const props = defineProps<{
|
2024-04-03 17:10:49 +02:00
|
|
|
src: TimelineSource;
|
2022-01-09 14:57:27 +01:00
|
|
|
list?: string;
|
|
|
|
antenna?: string;
|
|
|
|
channel?: string;
|
|
|
|
sound?: boolean;
|
2023-03-27 18:43:49 +02:00
|
|
|
fileId?: string;
|
2022-01-09 14:57:27 +01:00
|
|
|
}>();
|
2020-01-29 20:37:25 +01:00
|
|
|
|
2022-01-09 14:57:27 +01:00
|
|
|
const emit = defineEmits<{
|
2023-04-08 02:01:42 +02:00
|
|
|
(ev: "note"): void;
|
|
|
|
(ev: "queue", count: number): void;
|
2022-01-09 14:57:27 +01:00
|
|
|
}>();
|
|
|
|
|
2024-02-29 14:21:19 +01:00
|
|
|
const tlComponent = ref<InstanceType<typeof XNotes>>();
|
|
|
|
const pullToRefreshComponent = ref<InstanceType<typeof MkPullToRefresh>>();
|
2022-01-09 14:57:27 +01:00
|
|
|
|
2024-04-03 17:10:49 +02:00
|
|
|
let endpoint: EndpointsOf<entities.Note[]>; // keyof Endpoints
|
2024-03-02 06:24:05 +01:00
|
|
|
let query: {
|
|
|
|
antennaId?: string | undefined;
|
|
|
|
withReplies?: boolean;
|
|
|
|
visibility?: string;
|
|
|
|
listId?: string | undefined;
|
|
|
|
channelId?: string | undefined;
|
|
|
|
fileId?: string | undefined;
|
2024-04-03 17:10:49 +02:00
|
|
|
} = {};
|
|
|
|
|
|
|
|
// FIXME: The type defination is wrong here, need fix
|
2024-03-02 06:24:05 +01:00
|
|
|
let connection: {
|
|
|
|
on: (
|
|
|
|
arg0: string,
|
|
|
|
arg1: { (note: any): void; (note: any): void; (note: any): void },
|
|
|
|
) => void;
|
|
|
|
dispose: () => void;
|
|
|
|
};
|
|
|
|
let connection2: { dispose: () => void } | null;
|
|
|
|
|
2024-02-29 14:21:19 +01:00
|
|
|
let tlHint: string;
|
|
|
|
let tlHintClosed: boolean;
|
|
|
|
let tlNotesCount = 0;
|
|
|
|
const queue = ref(0);
|
2022-01-09 14:57:27 +01:00
|
|
|
|
2024-04-03 17:10:49 +02:00
|
|
|
const prepend = (note: entities.Note) => {
|
2024-02-29 14:21:19 +01:00
|
|
|
tlNotesCount++;
|
|
|
|
tlComponent.value?.pagingComponent?.prepend(note);
|
2022-01-09 14:57:27 +01:00
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
emit("note");
|
2022-01-09 14:57:27 +01:00
|
|
|
|
|
|
|
if (props.sound) {
|
2024-04-03 17:10:49 +02:00
|
|
|
sound.play(isSignedIn && note.userId === me?.id ? "noteMy" : "note");
|
2020-01-29 20:37:25 +01:00
|
|
|
}
|
2022-01-09 14:57:27 +01:00
|
|
|
};
|
|
|
|
|
2023-04-08 02:01:42 +02:00
|
|
|
if (props.src === "antenna") {
|
|
|
|
endpoint = "antennas/notes";
|
2022-01-09 14:57:27 +01:00
|
|
|
query = {
|
2022-08-30 17:24:33 +02:00
|
|
|
antennaId: props.antenna,
|
2022-01-09 14:57:27 +01:00
|
|
|
};
|
2023-04-08 02:01:42 +02:00
|
|
|
} else if (props.src === "home") {
|
|
|
|
endpoint = "notes/timeline";
|
2023-06-15 05:17:56 +02:00
|
|
|
query = {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
};
|
2023-05-29 20:51:02 +02:00
|
|
|
tlHint = i18n.ts._tutorial.step5_3;
|
|
|
|
tlHintClosed = defaultStore.state.tlHomeHintClosed;
|
2023-04-08 02:01:42 +02:00
|
|
|
} else if (props.src === "local") {
|
|
|
|
endpoint = "notes/local-timeline";
|
2023-06-15 05:17:56 +02:00
|
|
|
query = {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
};
|
2023-05-29 20:51:02 +02:00
|
|
|
tlHint = i18n.ts._tutorial.step5_4;
|
|
|
|
tlHintClosed = defaultStore.state.tlLocalHintClosed;
|
2023-04-08 02:01:42 +02:00
|
|
|
} else if (props.src === "recommended") {
|
|
|
|
endpoint = "notes/recommended-timeline";
|
2023-06-15 05:17:56 +02:00
|
|
|
query = {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
};
|
2023-05-29 20:51:02 +02:00
|
|
|
tlHint = i18n.ts._tutorial.step5_6;
|
|
|
|
tlHintClosed = defaultStore.state.tlRecommendedHintClosed;
|
2023-04-08 02:01:42 +02:00
|
|
|
} else if (props.src === "social") {
|
|
|
|
endpoint = "notes/hybrid-timeline";
|
2023-06-15 05:17:56 +02:00
|
|
|
query = {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
};
|
2023-05-29 20:51:02 +02:00
|
|
|
tlHint = i18n.ts._tutorial.step5_5;
|
|
|
|
tlHintClosed = defaultStore.state.tlSocialHintClosed;
|
2023-04-08 02:01:42 +02:00
|
|
|
} else if (props.src === "global") {
|
|
|
|
endpoint = "notes/global-timeline";
|
2023-06-15 05:17:56 +02:00
|
|
|
query = {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
};
|
2023-05-29 20:51:02 +02:00
|
|
|
tlHint = i18n.ts._tutorial.step5_7;
|
|
|
|
tlHintClosed = defaultStore.state.tlGlobalHintClosed;
|
2023-04-08 02:01:42 +02:00
|
|
|
} else if (props.src === "mentions") {
|
|
|
|
endpoint = "notes/mentions";
|
|
|
|
} else if (props.src === "directs") {
|
|
|
|
endpoint = "notes/mentions";
|
2022-01-09 14:57:27 +01:00
|
|
|
query = {
|
2023-04-08 02:01:42 +02:00
|
|
|
visibility: "specified",
|
2022-01-09 14:57:27 +01:00
|
|
|
};
|
2023-04-08 02:01:42 +02:00
|
|
|
} else if (props.src === "list") {
|
|
|
|
endpoint = "notes/user-list-timeline";
|
2022-01-09 14:57:27 +01:00
|
|
|
query = {
|
2022-08-30 17:24:33 +02:00
|
|
|
listId: props.list,
|
2022-01-09 14:57:27 +01:00
|
|
|
};
|
2023-04-08 02:01:42 +02:00
|
|
|
} else if (props.src === "channel") {
|
|
|
|
endpoint = "channels/timeline";
|
2022-01-09 14:57:27 +01:00
|
|
|
query = {
|
2022-08-30 17:24:33 +02:00
|
|
|
channelId: props.channel,
|
2022-01-09 14:57:27 +01:00
|
|
|
};
|
2023-03-27 18:43:49 +02:00
|
|
|
} else if (props.src === "file") {
|
|
|
|
endpoint = "drive/files/attached-notes";
|
|
|
|
query = {
|
|
|
|
fileId: props.fileId,
|
|
|
|
};
|
2024-04-03 17:10:49 +02:00
|
|
|
} else {
|
|
|
|
throw "NoEndpointError";
|
2022-01-09 14:57:27 +01:00
|
|
|
}
|
|
|
|
|
2024-02-29 14:21:19 +01:00
|
|
|
const stream = useStream();
|
|
|
|
|
|
|
|
function connectChannel() {
|
|
|
|
if (props.src === "antenna") {
|
2024-04-03 17:10:49 +02:00
|
|
|
if (!props.antenna) throw "NoAntennaProvided";
|
2024-02-29 14:21:19 +01:00
|
|
|
connection = stream.useChannel("antenna", {
|
2024-04-03 17:10:49 +02:00
|
|
|
antennaId: props.antenna,
|
2024-02-29 14:21:19 +01:00
|
|
|
});
|
|
|
|
} else if (props.src === "home") {
|
|
|
|
connection = stream.useChannel("homeTimeline", {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
});
|
|
|
|
connection2 = stream.useChannel("main");
|
|
|
|
} else if (props.src === "local") {
|
|
|
|
connection = stream.useChannel("localTimeline", {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
});
|
|
|
|
} else if (props.src === "recommended") {
|
|
|
|
connection = stream.useChannel("recommendedTimeline", {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
});
|
|
|
|
} else if (props.src === "social") {
|
|
|
|
connection = stream.useChannel("hybridTimeline", {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
});
|
|
|
|
} else if (props.src === "global") {
|
|
|
|
connection = stream.useChannel("globalTimeline", {
|
|
|
|
withReplies: defaultStore.state.showTimelineReplies,
|
|
|
|
});
|
|
|
|
} else if (props.src === "mentions") {
|
|
|
|
connection = stream.useChannel("main");
|
|
|
|
connection.on("mention", prepend);
|
|
|
|
} else if (props.src === "directs") {
|
|
|
|
const onNote = (note) => {
|
|
|
|
if (note.visibility === "specified") {
|
|
|
|
prepend(note);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
connection = stream.useChannel("main");
|
|
|
|
connection.on("mention", onNote);
|
|
|
|
} else if (props.src === "list") {
|
|
|
|
connection = stream.useChannel("userList", {
|
|
|
|
listId: props.list,
|
|
|
|
});
|
|
|
|
} else if (props.src === "channel") {
|
|
|
|
connection = stream.useChannel("channel", {
|
|
|
|
channelId: props.channel,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (props.src !== "directs" && props.src !== "mentions")
|
|
|
|
connection.on("note", prepend);
|
|
|
|
}
|
|
|
|
|
|
|
|
provide(
|
|
|
|
"inChannel",
|
|
|
|
computed(() => props.src === "channel"),
|
|
|
|
);
|
|
|
|
|
2023-05-29 20:51:02 +02:00
|
|
|
function closeHint() {
|
|
|
|
switch (props.src) {
|
2023-05-29 22:17:42 +02:00
|
|
|
case "home":
|
2023-05-29 20:51:02 +02:00
|
|
|
defaultStore.set("tlHomeHintClosed", true);
|
|
|
|
break;
|
2023-05-29 22:17:42 +02:00
|
|
|
case "local":
|
2023-05-29 20:51:02 +02:00
|
|
|
defaultStore.set("tlLocalHintClosed", true);
|
|
|
|
break;
|
2023-05-29 22:17:42 +02:00
|
|
|
case "recommended":
|
2023-05-29 20:51:02 +02:00
|
|
|
defaultStore.set("tlRecommendedHintClosed", true);
|
|
|
|
break;
|
2023-05-29 22:17:42 +02:00
|
|
|
case "social":
|
2023-05-29 20:51:02 +02:00
|
|
|
defaultStore.set("tlSocialHintClosed", true);
|
|
|
|
break;
|
2023-05-29 22:17:42 +02:00
|
|
|
case "global":
|
2023-05-29 20:51:02 +02:00
|
|
|
defaultStore.set("tlGlobalHintClosed", true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-29 14:21:19 +01:00
|
|
|
if (defaultStore.state.enableTimelineStreaming) {
|
|
|
|
connectChannel();
|
|
|
|
onUnmounted(() => {
|
|
|
|
connection.dispose();
|
|
|
|
if (connection2) connection2.dispose();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function reloadTimeline() {
|
|
|
|
return new Promise<void>((res) => {
|
|
|
|
tlNotesCount = 0;
|
|
|
|
tlComponent.value?.pagingComponent?.reload().then(() => {
|
|
|
|
res();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-04-03 17:10:49 +02:00
|
|
|
const pagination = {
|
|
|
|
endpoint,
|
2022-01-09 14:57:27 +01:00
|
|
|
limit: 10,
|
|
|
|
params: query,
|
|
|
|
};
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
connection.dispose();
|
2024-02-29 14:21:19 +01:00
|
|
|
if (connection2 != null) connection2.dispose();
|
2020-01-29 20:37:25 +01:00
|
|
|
});
|
2022-01-09 14:57:27 +01:00
|
|
|
|
|
|
|
/* TODO
|
|
|
|
const timetravel = (date?: Date) => {
|
|
|
|
this.date = date;
|
|
|
|
this.$refs.tl.reload();
|
|
|
|
};
|
|
|
|
*/
|
2024-02-29 14:21:19 +01:00
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
reloadTimeline,
|
|
|
|
});
|
2020-01-29 20:37:25 +01:00
|
|
|
</script>
|
2023-07-02 03:35:05 +02:00
|
|
|
<style lang="scss" scoped>
|
|
|
|
@keyframes slideUp {
|
|
|
|
to {
|
|
|
|
transform: translateY(-100%);
|
|
|
|
opacity: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.new {
|
|
|
|
position: sticky;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
top: calc(var(--stickyTop, 0px) - 60px);
|
|
|
|
width: 600px;
|
|
|
|
max-width: 100%;
|
|
|
|
height: 60px;
|
|
|
|
pointer-events: none;
|
|
|
|
margin: auto;
|
|
|
|
margin-top: -60px;
|
|
|
|
z-index: 1001;
|
|
|
|
box-shadow: 0 24px 24px -20px var(--accentedBg);
|
|
|
|
&::after {
|
|
|
|
content: "";
|
|
|
|
position: absolute;
|
|
|
|
inset: -2px 0;
|
|
|
|
border: 2px solid var(--accentDarken);
|
|
|
|
mask: linear-gradient(
|
|
|
|
to right,
|
|
|
|
transparent,
|
|
|
|
black 40% 60%,
|
|
|
|
transparent
|
|
|
|
);
|
|
|
|
-webkit-mask: linear-gradient(
|
|
|
|
to right,
|
|
|
|
transparent,
|
|
|
|
black 40% 60%,
|
|
|
|
transparent
|
|
|
|
);
|
|
|
|
}
|
|
|
|
> button {
|
|
|
|
display: flex;
|
|
|
|
position: absolute;
|
|
|
|
top: 120%;
|
|
|
|
margin-inline: auto;
|
|
|
|
border-radius: 2em;
|
|
|
|
padding: 0.5em 1.2em;
|
|
|
|
background: var(--accentedBg);
|
|
|
|
border: 0;
|
|
|
|
color: var(--accent);
|
|
|
|
overflow: hidden;
|
|
|
|
pointer-events: all;
|
|
|
|
transform: translateY(-100%);
|
|
|
|
opacity: 0;
|
2023-07-06 03:28:27 +02:00
|
|
|
animation:
|
|
|
|
reset 0.4s forwards cubic-bezier(0, 0.4, 0, 1.1),
|
2023-07-02 03:35:05 +02:00
|
|
|
slideUp 1s 5s forwards cubic-bezier(1, 0, 1, 1);
|
|
|
|
&::before {
|
|
|
|
content: "";
|
|
|
|
position: absolute;
|
|
|
|
inset: 0;
|
|
|
|
background: var(--bg);
|
|
|
|
z-index: -1;
|
|
|
|
}
|
|
|
|
i {
|
|
|
|
margin-left: 0.7em;
|
|
|
|
border-left: 1px solid var(--accentedBg);
|
|
|
|
padding-left: 0.4em;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|