fix navigation between statuses
This commit is contained in:
parent
14e58c1b3d
commit
c8478df21c
14 changed files with 1096 additions and 1489 deletions
1
.prettierignore
Normal file
1
.prettierignore
Normal file
|
@ -0,0 +1 @@
|
|||
pnpm-lock.yaml
|
|
@ -16,12 +16,12 @@
|
|||
"@fontsource/fira-sans": "^5.0.18",
|
||||
"@tabler/icons-vue": "^2.44.0",
|
||||
"axios": "^1.6.2",
|
||||
"classnames": "^2.3.2",
|
||||
"domhandler": "^5.0.3",
|
||||
"flowbite": "^2.2.1",
|
||||
"flowbite-vue": "^0.1.2",
|
||||
"htmlparser2": "^9.0.0",
|
||||
"pinia": "^2.1.7",
|
||||
"sass": "^1.69.5",
|
||||
"swrv": "^1.0.4",
|
||||
"vue": "^3.3.11",
|
||||
"vue-i18n": "9",
|
||||
|
@ -43,6 +43,7 @@
|
|||
"npm-run-all2": "^6.1.1",
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^3.0.3",
|
||||
"sass": "^1.69.5",
|
||||
"tailwindcss": "^3.3.7",
|
||||
"typescript": "~5.3.0",
|
||||
"vite": "^5.0.10",
|
||||
|
|
2385
pnpm-lock.yaml
2385
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
5
src/assets/mfm.scss
Normal file
5
src/assets/mfm.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
.mfm {
|
||||
&._mfm_x2_ {
|
||||
font-size: 200%;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import classNames from "classnames";
|
||||
|
||||
import type Activity from "@/lib/api/entities/activity";
|
||||
import VulStatusInfo from "./VulStatusInfo.vue";
|
||||
import VulStatusContent from "./VulStatusContent.vue";
|
||||
import VulStatusButtons from "./VulStatusButtons.vue";
|
||||
|
||||
defineProps<{ status: Activity }>();
|
||||
const props = defineProps<{ status: Activity, current?: boolean }>();
|
||||
|
||||
const classes = classNames("max-w-3xl flex flex-col m-2 py-2 rounded-md", {
|
||||
"bg-gray-600 dark:bg-gray-600": props.current,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col m-2">
|
||||
<div :class="classes">
|
||||
<VulStatusInfo :status="status" />
|
||||
<VulStatusContent :status="status" />
|
||||
<VulStatusButtons :status="status" />
|
||||
<VulStatusButtons :status="status" :current="current" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,26 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
import classNames from "classnames";
|
||||
|
||||
import { IconDotsVertical, IconMessage, IconPlus, IconQuote, IconRepeat, IconStar } from "@tabler/icons-vue";
|
||||
import type Activity from "@/lib/api/entities/activity";
|
||||
|
||||
defineProps<{ status: Activity }>();
|
||||
const props = defineProps<{ status: Activity, current?: boolean }>();
|
||||
|
||||
const classes = classNames("pt-1 mt-4 flex border-t", {
|
||||
"dark:border-gray-600": props.current,
|
||||
"dark:border-gray-700": !props.current,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex">
|
||||
<ul class="flex space-x-4">
|
||||
<div :class="classes">
|
||||
<ul class="my-2 mx-4 flex space-x-4">
|
||||
<li>
|
||||
<span v-if="status.replies_count">{{ status.replies_count }}</span>
|
||||
<span v-if="status.replies_count" class="me-2">{{ status.replies_count }}</span>
|
||||
<IconMessage class="inline" />
|
||||
</li>
|
||||
<li>
|
||||
<span v-if="status.reblogs_count">{{ status.reblogs_count }}</span>
|
||||
<span v-if="status.reblogs_count" class="me-2">{{ status.reblogs_count }}</span>
|
||||
<IconRepeat class="inline" />
|
||||
</li>
|
||||
<li>
|
||||
<IconQuote class="inline" />
|
||||
</li>
|
||||
<li>
|
||||
<span v-if="status.favourites_count">{{ status.favourites_count }}</span>
|
||||
<span v-if="status.favourites_count" class="me-2">{{ status.favourites_count }}</span>
|
||||
<IconStar class="inline" />
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
@ -13,7 +13,7 @@ const isCollapsed = ref(!!status.spoiler_text);
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="mx-2 flex flex-col">
|
||||
<div v-if="status.spoiler_text">
|
||||
<strong v-html="status.spoiler_text"></strong>
|
||||
<FwbButton class="mx-2" color="alternative" size="sm" @click="() => (isCollapsed = !isCollapsed)">
|
||||
|
|
|
@ -34,7 +34,7 @@ const statusScopeIcon = ({ visibility }: Activity) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-row">
|
||||
<div class="mx-2 flex flex-row">
|
||||
<div class="flex flex-row flex-grow">
|
||||
<RouterLink :to="`/@${status.account.acct}`">
|
||||
<img
|
||||
|
|
|
@ -25,15 +25,7 @@ export default function HTMLContent(props: Props) {
|
|||
|
||||
const dom = htmlparser2.parseDocument(content);
|
||||
const elements = parseElement(dom, props.emoji, props.tags || [], props.mentions || []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<code>
|
||||
<pre>{dom}</pre>
|
||||
</code>
|
||||
{elements}
|
||||
</>
|
||||
);
|
||||
return <>{elements}</>;
|
||||
}
|
||||
|
||||
/** Create a custom emoji element and return its string representation.
|
||||
|
@ -57,8 +49,6 @@ function parseElement(elem: ChildNode, emoji: CustomEmoji[], tags: Tag[], mentio
|
|||
// text node, just add it to the output unmodified
|
||||
out.push(<>{elem.data}</>);
|
||||
} else if (elem instanceof Element) {
|
||||
console.log(elem.name, elem.attribs);
|
||||
|
||||
switch (elem.name) {
|
||||
case "x-emoji":
|
||||
// Turn <x-emoji> into a VulEmoji component
|
||||
|
@ -70,8 +60,8 @@ function parseElement(elem: ChildNode, emoji: CustomEmoji[], tags: Tag[], mentio
|
|||
if ("class" in elem.attribs && elem.attribs.class === "h-card") {
|
||||
// if so, parse that mention and add a component for it
|
||||
if (elem.children.length === 0) throw "Invalid mention, span with type h-card doesn't have child";
|
||||
const userId = (elem.children[0] as Element).attribs["data-user"];
|
||||
const user = mentions.find((m) => m.id === userId);
|
||||
const userUrl = (elem.children[0] as Element).attribs["href"];
|
||||
const user = mentions.find((m) => m.url === userUrl);
|
||||
if (!user) throw "Invalid mention, user is not found in Status.mentions";
|
||||
// add the mention component
|
||||
out.push(<VulMention mention={user} />);
|
||||
|
|
|
@ -11,6 +11,6 @@ defineProps<{ emoji: CustomEmoji }>();
|
|||
<style>
|
||||
.emoji {
|
||||
display: inline;
|
||||
height: 38px;
|
||||
height: 2em;
|
||||
}
|
||||
</style>
|
||||
|
|
20
src/components/status_tree/StatusTree.vue
Normal file
20
src/components/status_tree/StatusTree.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import type Activity from "@/lib/api/entities/activity";
|
||||
import VulStatus from "@/components/status/VulStatus.vue";
|
||||
|
||||
defineProps<{
|
||||
ancestors?: Activity[];
|
||||
descendants?: Activity[];
|
||||
currentStatus: Activity;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="ancestors">
|
||||
<VulStatus v-for="status in ancestors" :status="status" :key="status.id" />
|
||||
</template>
|
||||
<VulStatus :status="currentStatus" :key="currentStatus.id" :current="true" />
|
||||
<template v-if="descendants">
|
||||
<VulStatus v-for="status in descendants" :status="status" :key="status.id" />
|
||||
</template>
|
||||
</template>
|
|
@ -19,6 +19,11 @@ export default interface Activity {
|
|||
replies_count: number;
|
||||
}
|
||||
|
||||
export interface ActivityContext {
|
||||
ancestors: Activity[];
|
||||
descendants: Activity[];
|
||||
}
|
||||
|
||||
export interface ActivityAkkoma {
|
||||
source: ActivityAkkomaSource;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import "./assets/style.css";
|
||||
import "./assets/mfm.scss";
|
||||
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
|
|
|
@ -3,18 +3,23 @@ import { ref, watch } from "vue";
|
|||
import { useRouter, useRoute } from "vue-router";
|
||||
import useSWRV from "swrv";
|
||||
import type Activity from "@/lib/api/entities/activity";
|
||||
import type { ActivityContext } from "@/lib/api/entities/activity";
|
||||
import apiFetch from "@/lib/api-fetch";
|
||||
import watchTitle from "@/lib/title";
|
||||
|
||||
import { FwbSpinner } from "flowbite-vue";
|
||||
import VulStatus from "@/components/status/VulStatus.vue";
|
||||
import watchTitle from "@/lib/title";
|
||||
import StatusTree from "@/components/status_tree/StatusTree.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const username = ref(route.params.username);
|
||||
const statusId = ref(route.params.statusId);
|
||||
|
||||
const { data, error } = useSWRV(() => `/api/v1/statuses/${statusId.value}`, apiFetch<Activity>);
|
||||
const { data: status, error: statusError } = useSWRV(() => `/api/v1/statuses/${statusId.value}`, apiFetch<Activity>);
|
||||
const { data: context, error: contextError } = useSWRV(
|
||||
() => `/api/v1/statuses/${statusId.value}/context`,
|
||||
apiFetch<ActivityContext>,
|
||||
);
|
||||
|
||||
// update username/status ID whenever we navigate to another page
|
||||
watch(
|
||||
|
@ -27,9 +32,9 @@ watch(
|
|||
|
||||
// always have the correct username in the URL
|
||||
watch(
|
||||
() => ({ activity: data.value, username: route.params.username }),
|
||||
({ activity, username }) => {
|
||||
if (activity && activity.account.acct !== username) {
|
||||
() => ({ activity: status.value, username: route.params.username, statusId: statusId.value }),
|
||||
({ activity, username, statusId }) => {
|
||||
if (activity && activity.id === statusId && activity.account.acct !== username) {
|
||||
router.push({
|
||||
name: "user-status",
|
||||
params: { username: activity.account.acct, statusId: activity.id },
|
||||
|
@ -43,11 +48,12 @@ watchTitle((activity) => {
|
|||
const user = activity.account.display_name;
|
||||
let text = activity.spoiler_text || activity.content || "N/A";
|
||||
return `${user}: "${text.length > 29 ? text.slice(0, 30) + "…" : text}"`;
|
||||
}, data);
|
||||
}, status);
|
||||
</script>
|
||||
|
||||
<!-- TODO: replace the FwbSpinner with an actual loading component -->
|
||||
<template>
|
||||
<div v-if="error">Failed to load: {{ error }}</div>
|
||||
<FwbSpinner v-else-if="!data" size="10" />
|
||||
<VulStatus v-else :status="data" />
|
||||
<div v-if="statusError || contextError">Failed to load: {{ statusError || contextError }}</div>
|
||||
<FwbSpinner v-else-if="!status" size="10" />
|
||||
<StatusTree v-else :ancestors="context?.ancestors" :descendants="context?.descendants" :current-status="status" />
|
||||
</template>
|
||||
|
|
Loading…
Reference in a new issue