mirror of
https://github.com/versia-pub/frontend.git
synced 2026-03-13 03:29:16 +01:00
refactor: ♻️ Reimplement Notes
This commit is contained in:
parent
9ced2c98e4
commit
d29f181000
21 changed files with 335 additions and 30 deletions
69
components/notes/content.vue
Normal file
69
components/notes/content.vue
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<div :class="['prose block relative dark:prose-invert duration-200 !max-w-full break-words', $style.content]" v-html="content">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { content } = defineProps<{
|
||||
content: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style module>
|
||||
.content pre:has(code) {
|
||||
word-wrap: normal;
|
||||
background: transparent;
|
||||
background-color: #ffffff0d;
|
||||
border-radius: .25rem;
|
||||
hyphens: none;
|
||||
margin-top: 1rem;
|
||||
overflow-x: auto;
|
||||
padding: .75rem 1rem;
|
||||
tab-size: 4;
|
||||
white-space: pre;
|
||||
word-break: normal;
|
||||
word-spacing: normal;
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), 0 0 #0000;
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
--tw-ring-color: hsla(0, 0%, 100%, .1)
|
||||
}
|
||||
|
||||
.content pre code {
|
||||
display: block;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.content code:not(pre code)::after,
|
||||
.content code:not(pre code)::before {
|
||||
content: ""
|
||||
}
|
||||
|
||||
.content ol li input[type=checkbox],
|
||||
.content ul li input[type=checkbox] {
|
||||
border-radius:.25rem;
|
||||
margin-bottom:0.2rem;
|
||||
margin-right:.5rem;
|
||||
margin-top:0;
|
||||
vertical-align: middle;
|
||||
--tw-text-opacity:1;
|
||||
color: var(--theme-primary-400);
|
||||
}
|
||||
|
||||
.content code:not(pre code) {
|
||||
border-radius: .25rem;
|
||||
padding: .25rem .5rem;
|
||||
word-wrap: break-word;
|
||||
background: transparent;
|
||||
background-color: #ffffff0d;
|
||||
hyphens: none;
|
||||
margin-top: 1rem;
|
||||
tab-size: 4;
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), 0 0 #0000;
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
--tw-ring-color: hsla(0, 0%, 100%, .1)
|
||||
}
|
||||
</style>
|
||||
39
components/notes/copyable-text.vue
Normal file
39
components/notes/copyable-text.vue
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<span :class="cn('text-primary group', $props.class)">
|
||||
<span class="group-hover:hidden">
|
||||
<slot />
|
||||
</span>
|
||||
<span class="hidden group-hover:inline">
|
||||
<span @click="copyText" v-if="!hasCopied"
|
||||
class="select-none cursor-pointer space-x-1">
|
||||
<Clipboard class="size-4 -translate-y-0.5 inline" />
|
||||
Click to copy
|
||||
</span>
|
||||
<span v-else class="select-none space-x-1">
|
||||
<Check class="size-4 -translate-y-0.5 inline" />
|
||||
Copied!
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Check, Clipboard } from "lucide-vue-next";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const { text } = defineProps<{
|
||||
text: string;
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
|
||||
const hasCopied = ref(false);
|
||||
const { copy } = useClipboard();
|
||||
const copyText = () => {
|
||||
copy(text);
|
||||
hasCopied.value = true;
|
||||
setTimeout(() => {
|
||||
hasCopied.value = false;
|
||||
}, 2000);
|
||||
};
|
||||
</script>
|
||||
70
components/notes/header.vue
Normal file
70
components/notes/header.vue
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<div class="rounded flex flex-row gap-4">
|
||||
<Avatar class="size-14 rounded border border-card">
|
||||
<AvatarImage :src="avatar" alt="" />
|
||||
<AvatarFallback class="rounded-lg"> AA </AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="flex flex-col gap-0.5 justify-center flex-1 text-left leading-tight">
|
||||
<span class="truncate font-semibold">{{
|
||||
displayName
|
||||
}}</span>
|
||||
<span class="truncate text-sm">
|
||||
<CopyableText :text="acct">
|
||||
<span
|
||||
class="font-semibold bg-gradient-to-tr from-pink-300 via-purple-300 to-indigo-400 text-transparent bg-clip-text">
|
||||
@{{ username }}
|
||||
</span>
|
||||
<span class="text-muted-foreground">{{ instance && "@" }}{{ instance }}</span>
|
||||
</CopyableText>
|
||||
·
|
||||
<span class="text-muted-foreground ml-auto" :title="fullTime">{{ timeAgo }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 justify-center items-end">
|
||||
<span class="text-xs text-muted-foreground" :title="visibilities[visibility].text">
|
||||
<component :is="visibilities[visibility].icon" class="size-5" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { StatusVisibility } from "@versia/client/types";
|
||||
import { AtSign, Globe, Lock, LockOpen } from "lucide-vue-next";
|
||||
import CopyableText from "./copyable-text.vue";
|
||||
|
||||
const { acct, createdAt } = defineProps<{
|
||||
avatar: string;
|
||||
acct: string;
|
||||
displayName: string;
|
||||
visibility: StatusVisibility;
|
||||
url: string;
|
||||
createdAt: Date;
|
||||
}>();
|
||||
|
||||
const [username, instance] = acct.split("@");
|
||||
const timeAgo = useTimeAgo(createdAt);
|
||||
const fullTime = new Intl.DateTimeFormat("en-US", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
}).format(createdAt);
|
||||
|
||||
const visibilities = {
|
||||
public: {
|
||||
icon: Globe,
|
||||
text: "This note is public: it can be seen by anyone.",
|
||||
},
|
||||
unlisted: {
|
||||
icon: LockOpen,
|
||||
text: "This note is unlisted: it can be seen by anyone with the link.",
|
||||
},
|
||||
private: {
|
||||
icon: Lock,
|
||||
text: "This note is private: it can only be seen by followers.",
|
||||
},
|
||||
direct: {
|
||||
icon: AtSign,
|
||||
text: "This note is direct: it can only be seen by mentioned users.",
|
||||
},
|
||||
};
|
||||
</script>
|
||||
26
components/notes/note.vue
Normal file
26
components/notes/note.vue
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<Card as="article" class="rounded-none border-0 hover:bg-muted/50 duration-200">
|
||||
<CardHeader class="pb-4">
|
||||
<Header :avatar="note.account.avatar" :acct="note.account.acct" :display-name="note.account.display_name"
|
||||
:visibility="note.visibility" :url="accountUrl" :created-at="new Date(note.created_at)" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Content :content="note.content" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Status } from "@versia/client/types";
|
||||
import { Card, CardHeader } from "../ui/card";
|
||||
import { Separator } from "../ui/separator";
|
||||
import Content from "./content.vue";
|
||||
import Header from "./header.vue";
|
||||
|
||||
const { note } = defineProps<{
|
||||
note: Status;
|
||||
}>();
|
||||
|
||||
const url = `/@${note.account.acct}/${note.id}`;
|
||||
const accountUrl = `/@${note.account.acct}`;
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue