refactor: ♻️ Reimplement Notes

This commit is contained in:
Jesse Wierzbinski 2024-11-30 02:19:32 +01:00
parent 9ced2c98e4
commit d29f181000
No known key found for this signature in database
21 changed files with 335 additions and 30 deletions

View 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>

View 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>

View 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>
&middot;
<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
View 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>