Files
remanso/src/views/PublicNoteView.vue
2026-02-16 23:56:57 +01:00

223 lines
5.1 KiB
Vue

<script setup lang="ts">
import { useATProtoLinks } from "@/hooks/useATProtoLinks.hook"
import { markdownBuilder } from "@/hooks/useMarkdown.hook"
import BackButton from "@/components/BackButton.vue"
import StackedPublicNote from "@/components/StackedPublicNote.vue"
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
import { getUniqueAka } from "@/modules/atproto/getAka"
import type { PublicNoteRecord } from "@/modules/atproto/publicNote.types"
import { withATProtoImages } from "@/modules/atproto/withATProtoImages"
import { getUrl } from "@/modules/atproto/getUrl"
import { downloadFont } from "@/utils/downloadFont"
import { computedAsync } from "@vueuse/core"
import { computed, nextTick, watch } from "vue"
import { useResizeContainer } from "@/hooks/useResizeContainer.hook"
import ThemeSwap from "@/components/ThemeSwap.vue"
const props = defineProps<{ did: string; rkey: string }>()
const did = computed(() => props.did)
const rkey = computed(() => props.rkey)
const author = computedAsync(async () => getUniqueAka(did.value))
const url = computedAsync(async () =>
getUrl({ did: did.value, rkey: rkey.value }),
)
const noteRecord = computedAsync(async () =>
url.value
? ((await fetch(url.value).then()).json() as Promise<PublicNoteRecord>)
: null,
)
watch(noteRecord, () => {
if (noteRecord.value?.value.fontFamily) {
downloadFont(noteRecord.value.value.fontFamily)
}
if (noteRecord.value?.value.fontSize) {
const root = document.documentElement
root.style.setProperty(
"--font-size",
`${noteRecord.value.value.fontSize}pt`,
)
}
})
const { toHTML } = markdownBuilder()
const title = computed(() => noteRecord.value?.value.title)
const content = computed(() =>
noteRecord.value?.value.content && author.value
? toHTML(
withATProtoImages(noteRecord.value.value.content, {
endpoint: author.value.endpoint,
did: did.value,
}),
)
: "",
)
const publishedAt = computed(() =>
noteRecord.value?.value.publishedAt
? new Date(noteRecord.value?.value.publishedAt).toLocaleDateString()
: null,
)
const { stackedNotes, scrollToFocusedNote } = useRouteQueryStackedNotes()
const { listenToClick } = useATProtoLinks("note-display")
useResizeContainer("note-container", stackedNotes)
watch(
content,
async () => {
await nextTick()
listenToClick()
},
{ immediate: true },
)
</script>
<template>
<div class="public-note-view repo-note note-container">
<div class="note article">
<div class="header">
<back-button
:fallback="{ name: 'PublicNoteListByDidView', params: { did } }"
:prefer-fallback="false"
/>
<theme-swap />
<span
class="badge badge-author badge-soft badge-accent"
v-if="author && content"
>
<router-link
:to="{ name: 'PublicNoteListByDidView', params: { did: did } }"
class="link link-hover"
>
{{ author.alias }}
</router-link>
<span v-if="publishedAt">&nbsp;&nbsp;{{ publishedAt }}</span>
</span>
</div>
<div class="repo-title-breadcrumb">
<a
class="title-stacked-note-link"
@click.prevent="scrollToFocusedNote()"
v-if="author && title"
>{{ title }}</a
>
</div>
<article class="note-display" v-html="content"></article>
</div>
<stacked-public-note
v-for="(stackedNote, index) in stackedNotes"
:key="stackedNote"
class="note"
:index="index"
:at-uri="stackedNote"
/>
</div>
</template>
<style lang="scss">
.public-note-view {
display: flex;
flex: 1;
.header {
margin-top: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
font-size: 1.5rem;
}
.article {
position: sticky;
padding: 0 2rem;
scrollbar-width: none;
article {
margin-top: 1rem;
}
}
&.content {
.title,
h1,
h2,
h3,
h4,
h5,
h6,
strong {
color: var(--color-base-content);
}
table {
color: var(--color-base-content);
background-color: var(--color-base-100);
thead {
th {
color: var(--color-base-content);
}
}
}
blockquote {
background-color: var(--color-base-100);
color: var(--color-base-content);
}
}
.note {
display: flex;
flex-direction: column;
overflow-y: auto;
height: 100vh;
.title {
text-align: left;
}
}
@media screen and (min-width: 769px) {
.repo-title-breadcrumb {
padding: 0.5rem 1rem 0;
transform-origin: 0 0;
transform: rotate(90deg);
font-size: 0.8em;
a {
color: var(--color-base-content);
display: block;
text-align: center;
}
}
.note {
min-width: var(--note-width);
max-width: var(--note-width);
}
}
@media screen and (max-width: 768px) {
flex-wrap: wrap;
.repo-title-breadcrumb {
display: none;
}
.article article {
margin-top: 48px;
}
}
}
</style>