Merge branch 'main' of ssh://git.apoena.dev:22222/julien/remanso

This commit is contained in:
Julien Calixte
2026-03-17 22:03:33 +01:00
13 changed files with 49 additions and 23 deletions

View File

@@ -1,3 +1,7 @@
# Remanso # Remanso
Welcome to Remanso!
---
[Remanso website](https://remanso.space) [Remanso website](https://remanso.space)

2
nixpacks.toml Normal file
View File

@@ -0,0 +1,2 @@
[phases.setup]
nixPkgs = ["nodejs_24", "pnpm"]

View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { PublicNoteListItem } from "@/modules/note/models/Note" import { PublicNoteListItem } from "@/modules/note/models/Note"
import { toShortDid } from "@/modules/atproto/shortDid"
import { slugify } from "@/utils/slugify" import { slugify } from "@/utils/slugify"
import { vInfiniteScroll } from "@vueuse/components" import { vInfiniteScroll } from "@vueuse/components"
@@ -25,7 +26,7 @@ defineSlots<{
:to="{ :to="{
name: 'PublicNoteView', name: 'PublicNoteView',
params: { params: {
did: note.did, shortDid: toShortDid(note.did),
rkey: note.rkey, rkey: note.rkey,
slug: slugify(note.title), slug: slugify(note.title),
}, },

View File

@@ -9,6 +9,7 @@ import { computedAsync } from "@vueuse/core"
import { getUrl } from "@/modules/atproto/getUrl" import { getUrl } from "@/modules/atproto/getUrl"
import { withATProtoImages } from "@/modules/atproto/withATProtoImages" import { withATProtoImages } from "@/modules/atproto/withATProtoImages"
import { getAuthor } from "@/modules/atproto/getAuthor" import { getAuthor } from "@/modules/atproto/getAuthor"
import { fromShortDid } from "@/modules/atproto/shortDid"
import { PublicNoteRecord } from "@/modules/atproto/publicNote.types" import { PublicNoteRecord } from "@/modules/atproto/publicNote.types"
const props = defineProps<{ const props = defineProps<{
@@ -17,7 +18,7 @@ const props = defineProps<{
}>() }>()
const didrkey = computed(() => props.didrkey) const didrkey = computed(() => props.didrkey)
const did = computed(() => props.didrkey.split("-")[0]) const did = computed(() => fromShortDid(props.didrkey.split("-")[0]))
const rkey = computed(() => props.didrkey.split("-")[1]) const rkey = computed(() => props.didrkey.split("-")[1])
const classNameId = computed(() => didrkey.value.replaceAll(":", "-")) const classNameId = computed(() => didrkey.value.replaceAll(":", "-"))

View File

@@ -3,6 +3,7 @@ import { ComputedRef, onUnmounted, Ref, toValue } from "vue"
import { isExternalLink } from "@/utils/link" import { isExternalLink } from "@/utils/link"
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook" import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
import { parseAtUri } from "@/modules/atproto/parseAtUri" import { parseAtUri } from "@/modules/atproto/parseAtUri"
import { toShortDid } from "@/modules/atproto/shortDid"
import { router } from "@/router/router" import { router } from "@/router/router"
export const useATProtoLinks = ( export const useATProtoLinks = (
@@ -35,25 +36,25 @@ export const useATProtoLinks = (
href.replace(window.location.origin, ""), href.replace(window.location.origin, ""),
) )
if (!params.did || !params.rkey) { if (!params.shortDid || !params.rkey) {
return return
} }
const noteId = params.slug const noteId = params.slug
? `${params.did}-${params.rkey}-${params.slug}` ? `${params.shortDid}-${params.rkey}-${params.slug}`
: `${params.did}-${params.rkey}` : `${params.shortDid}-${params.rkey}`
addStackedNote( addStackedNote(
toValue(currentAtUri) ?? "", toValue(currentAtUri) ?? "",
noteId, noteId,
`${params.did}-${params.rkey}`, `${params.shortDid}-${params.rkey}`,
) )
return return
} }
if (href.startsWith("at://")) { if (href.startsWith("at://")) {
const { did, rkey } = parseAtUri(href) const { did, rkey } = parseAtUri(href)
const noteId = `${did}-${rkey}` const noteId = `${toShortDid(did)}-${rkey}`
addStackedNote(toValue(currentAtUri) ?? "", noteId) addStackedNote(toValue(currentAtUri) ?? "", noteId)
} }

View File

@@ -3,11 +3,16 @@ import { PublicNoteListItem } from "@/modules/note/models/Note"
import { computedAsync } from "@vueuse/core" import { computedAsync } from "@vueuse/core"
import { computed, ref, Ref, watch } from "vue" import { computed, ref, Ref, watch } from "vue"
export function useFollowingNoteList(dids: Ref<Set<string>>, enabled: Ref<boolean>) { export function useFollowingNoteList(
dids: Ref<Set<string>>,
enabled: Ref<boolean>,
) {
const isLoading = ref(false) const isLoading = ref(false)
const notes = ref<PublicNoteListItem[]>([]) const notes = ref<PublicNoteListItem[]>([])
const cursor = ref<string | null | undefined>(null) const cursor = ref<string | null | undefined>(null)
const canLoadMore = computed(() => dids.value.size > 0 && cursor.value !== undefined) const canLoadMore = computed(
() => dids.value.size > 0 && cursor.value !== undefined,
)
const onLoadMore = async () => { const onLoadMore = async () => {
if (isLoading.value) return if (isLoading.value) return
@@ -24,13 +29,14 @@ export function useFollowingNoteList(dids: Ref<Set<string>>, enabled: Ref<boolea
body.cursor = cursor.value body.cursor = cursor.value
} }
const response = await fetch("https://api.litenote.li212.fr/notes/feed", { const response = await fetch("https://api.remanso.space/notes/feed", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(body), body: JSON.stringify(body),
}) })
const data: { notes: PublicNoteListItem[]; cursor?: string } = await response.json() const data: { notes: PublicNoteListItem[]; cursor?: string } =
await response.json()
notes.value.push(...data.notes) notes.value.push(...data.notes)
cursor.value = data.cursor cursor.value = data.cursor

View File

@@ -18,7 +18,7 @@ export function usePublicNoteList(options?: UsePublicNoteListOptions) {
isLoading.value = true isLoading.value = true
const path = options?.did?.value ? `/${options.did.value}/notes` : "/notes" const path = options?.did?.value ? `/${options.did.value}/notes` : "/notes"
const noteAPI = new URL(path, "https://api.litenote.li212.fr") const noteAPI = new URL(path, "https://api.remanso.space")
if (cursor.value) { if (cursor.value) {
noteAPI.searchParams.set("cursor", cursor.value) noteAPI.searchParams.set("cursor", cursor.value)

View File

@@ -0,0 +1,8 @@
export const toShortDid = (did: string) => did.replace(/^did:(plc:)?/, "")
// did:plc:xxx → xxx, did:web:x → web:x
export const fromShortDid = (shortDid: string) => {
if (shortDid.startsWith("did:")) return shortDid
return shortDid.includes(":") ? `did:${shortDid}` : `did:plc:${shortDid}`
}
// xxx → did:plc:xxx, web:x → did:web:x, did:plc:xxx → did:plc:xxx (passthrough)

View File

@@ -60,5 +60,5 @@ export const noteRouter = contract.router({
}) })
export const client = initQueryClient(noteRouter, { export const client = initQueryClient(noteRouter, {
baseUrl: "https://api.litenote.li212.fr", baseUrl: "https://api.remanso.space",
}) })

View File

@@ -25,13 +25,13 @@ const routes: Array<RouteRecordRaw> = [
component: () => import("@/views/PublicNoteListView.vue"), component: () => import("@/views/PublicNoteListView.vue"),
}, },
{ {
path: "/pub/:did", path: "/pub/:shortDid",
name: "PublicNoteListByDidView", name: "PublicNoteListByDidView",
props: true, props: true,
component: () => import("@/views/PublicNoteListByDidView.vue"), component: () => import("@/views/PublicNoteListByDidView.vue"),
}, },
{ {
path: "/pub/:did/:rkey/:slug?", path: "/pub/:shortDid/:rkey/:slug?",
name: "PublicNoteView", name: "PublicNoteView",
props: true, props: true,
component: () => import("@/views/PublicNoteView.vue"), component: () => import("@/views/PublicNoteView.vue"),

View File

@@ -3,11 +3,12 @@ import HomeButton from "@/components/HomeButton.vue"
import PublicNoteList from "@/components/PublicNoteList.vue" import PublicNoteList from "@/components/PublicNoteList.vue"
import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook" import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook"
import { getAuthor } from "@/modules/atproto/getAuthor" import { getAuthor } from "@/modules/atproto/getAuthor"
import { fromShortDid } from "@/modules/atproto/shortDid"
import { computedAsync } from "@vueuse/core" import { computedAsync } from "@vueuse/core"
import { computed } from "vue" import { computed } from "vue"
const props = defineProps<{ did: string }>() const props = defineProps<{ shortDid: string }>()
const did = computed(() => props.did) const did = computed(() => fromShortDid(props.shortDid))
const { notes, isLoading, canLoadMore, onLoadMore } = usePublicNoteList({ did }) const { notes, isLoading, canLoadMore, onLoadMore } = usePublicNoteList({ did })

View File

@@ -6,6 +6,7 @@ import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
import { useFollows } from "@/hooks/useFollows.hook" import { useFollows } from "@/hooks/useFollows.hook"
import { useFollowingNoteList } from "@/hooks/useFollowingNoteList.hook" import { useFollowingNoteList } from "@/hooks/useFollowingNoteList.hook"
import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook" import { usePublicNoteList } from "@/hooks/usePublicNoteList.hook"
import { toShortDid } from "@/modules/atproto/shortDid"
import { computed } from "vue" import { computed } from "vue"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
@@ -62,7 +63,7 @@ const following = useFollowingNoteList(follows, followingEnabled)
<template #meta="{ note }"> <template #meta="{ note }">
<router-link <router-link
v-if="all.getAuthor(note.did)" v-if="all.getAuthor(note.did)"
:to="{ name: 'PublicNoteListByDidView', params: { did: note.did } }" :to="{ name: 'PublicNoteListByDidView', params: { shortDid: toShortDid(note.did) } }"
class="link link-hover" class="link link-hover"
> >
{{ all.getAuthor(note.did) }} {{ all.getAuthor(note.did) }}
@@ -84,7 +85,7 @@ const following = useFollowingNoteList(follows, followingEnabled)
<template #meta="{ note }"> <template #meta="{ note }">
<router-link <router-link
v-if="following.getAuthor(note.did)" v-if="following.getAuthor(note.did)"
:to="{ name: 'PublicNoteListByDidView', params: { did: note.did } }" :to="{ name: 'PublicNoteListByDidView', params: { shortDid: toShortDid(note.did) } }"
class="link link-hover" class="link link-hover"
> >
{{ following.getAuthor(note.did) }} {{ following.getAuthor(note.did) }}

View File

@@ -8,6 +8,7 @@ import { getAuthor } from "@/modules/atproto/getAuthor"
import type { PublicNoteRecord } from "@/modules/atproto/publicNote.types" import type { PublicNoteRecord } from "@/modules/atproto/publicNote.types"
import { withATProtoImages } from "@/modules/atproto/withATProtoImages" import { withATProtoImages } from "@/modules/atproto/withATProtoImages"
import { getUrl } from "@/modules/atproto/getUrl" import { getUrl } from "@/modules/atproto/getUrl"
import { fromShortDid } from "@/modules/atproto/shortDid"
import { downloadFont } from "@/utils/downloadFont" import { downloadFont } from "@/utils/downloadFont"
import { slugify } from "@/utils/slugify" import { slugify } from "@/utils/slugify"
import { computedAsync } from "@vueuse/core" import { computedAsync } from "@vueuse/core"
@@ -19,9 +20,9 @@ import ThemeSwap from "@/components/ThemeSwap.vue"
import { useTitle } from "@vueuse/core" import { useTitle } from "@vueuse/core"
import { displayLanguage } from "@/utils/displayLanguage" import { displayLanguage } from "@/utils/displayLanguage"
const props = defineProps<{ did: string; rkey: string; slug?: string }>() const props = defineProps<{ shortDid: string; rkey: string; slug?: string }>()
const router = useRouter() const router = useRouter()
const did = computed(() => props.did) const did = computed(() => fromShortDid(props.shortDid))
const rkey = computed(() => props.rkey) const rkey = computed(() => props.rkey)
const author = computedAsync(async () => getAuthor(did.value)) const author = computedAsync(async () => getAuthor(did.value))
@@ -125,7 +126,7 @@ watch(
<div class="note article"> <div class="note article">
<div class="header"> <div class="header">
<back-button <back-button
:fallback="{ name: 'PublicNoteListByDidView', params: { did } }" :fallback="{ name: 'PublicNoteListByDidView', params: { shortDid } }"
:prefer-fallback="false" :prefer-fallback="false"
/> />
@@ -141,7 +142,7 @@ watch(
<span>&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;</span>
</template> </template>
<router-link <router-link
:to="{ name: 'PublicNoteListByDidView', params: { did: did } }" :to="{ name: 'PublicNoteListByDidView', params: { shortDid } }"
class="link link-hover" class="link link-hover"
> >
{{ author.handle }} {{ author.handle }}