Compare commits

..

20 Commits

Author SHA1 Message Date
Julien Calixte
ed1a6b7fba fix: add the right margin to the right components 2026-03-29 22:09:01 +02:00
Julien Calixte
d5b251c4a0 fix: remove overflow because it's causing too much trouble 2026-03-29 22:00:22 +02:00
Julien Calixte
19b77810ec chore: remove healthcheck in docker to be faster 2026-03-29 21:55:32 +02:00
Julien Calixte
c8b0a78973 fix: add nginx SPA fallback to serve index.html for all routes
Prevents 404 errors when navigating directly to client-side routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 21:50:50 +02:00
Julien Calixte
087d1a355e revert: remove justify-content center from welcome content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:37:19 +01:00
Julien Calixte
5d90da8ab5 feat: center welcome content vertically
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:36:40 +01:00
Julien Calixte
72d065975d fix: lock html/body to 100dvh overflow hidden on all screen sizes
All views that need scroll use their own overflow-y: auto containers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:35:11 +01:00
Julien Calixte
8b3df48791 fix: clip app at 100dvh to prevent body scroll on mobile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:29:50 +01:00
Julien Calixte
cd8e173e05 fix: use 100dvh for body and #app to match dynamic viewport
Prevents white space below the app on Android Chrome where the
system nav bar makes 100vh > 100dvh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:25:19 +01:00
Julien Calixte
8767f7c430 fix: give .home explicit height so flex children resolve correctly
On Chrome Android, cross-axis stretch doesn't always produce a
definite height for inner flex items. Adding height: 100dvh to
.home ensures flex: 1 on .welcome-world resolves to full viewport.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:22:54 +01:00
Julien Calixte
369a200a42 fix: wrap content in flex:1 div so footer doesn't overflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 22:07:08 +01:00
Julien Calixte
06eaa3c9a7 fix: ensure footer stays at bottom with align-self stretch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 21:56:09 +01:00
Julien Calixte
4cbcf42e3d feat: replace BackButton and logo with HomeButton in PublicNoteView
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 21:32:19 +01:00
Julien Calixte
a0be25c0dd fix: prevent layout shift on first load in PWA mode
Replace space-between with flex-start + margin-top:auto on footer and
add gap to avoid wide spacing while async components are loading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 21:28:47 +01:00
Julien Calixte
dcee26100f fix: use 100dvh to prevent scroll on mobile first load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 21:05:34 +01:00
Julien Calixte
ac68c68f8a feat: reorganize FontChange layout and resize header icons
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 20:46:44 +01:00
Julien Calixte
982f3070a1 fix: use <a> for font modal trigger to match icon color
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 20:35:52 +01:00
Julien Calixte
20e9538983 feat: mv profile to footer 2026-03-28 20:24:08 +01:00
Julien Calixte
10c3e1ca60 feat: replace back button with HomeButton and fix view transition
- Use HomeButton component in HeaderNote for logo, hover, and view-transition-name
- Eagerly import HeaderNote in FluxNote so the logo exists in the DOM when the transition snapshot is taken
- Wait for afterEach + nextTick in the view transition hook to handle lazy-loaded routes
- Add cursor: pointer to font change button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 20:19:59 +01:00
6dc98c80ca Merge pull request 'chore/migrate-to-oxc' (#1) from chore/migrate-to-oxc into main
Reviewed-on: #1
2026-03-28 09:00:30 +00:00
20 changed files with 301 additions and 167 deletions

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@
node_modules node_modules
/dist /dist
# local env files # local env files
.env.local .env.local
.env.*.local .env.*.local

View File

@@ -28,8 +28,6 @@ RUN pnpm run build
FROM nginx:alpine AS runner FROM nginx:alpine AS runner
COPY --from=builder /app/dist /usr/share/nginx/html COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80 EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:80/ || exit 1

9
nginx.conf Normal file
View File

@@ -0,0 +1,9 @@
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@@ -17,7 +17,7 @@ const { isATProtoReady } = useATProtoLogin()
<style lang="scss"> <style lang="scss">
#main-app { #main-app {
height: 100vh; height: 100dvh;
width: 100%; width: 100%;
display: flex; display: flex;
flex: 1; flex: 1;

View File

@@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
computed, computed,
defineAsyncComponent,
nextTick, nextTick,
onMounted, onMounted,
onUnmounted, onUnmounted,
@@ -9,6 +8,7 @@ import {
watch watch
} from "vue" } from "vue"
import HeaderNote from "@/components/HeaderNote.vue"
import SkeletonLoader from "@/components/SkeletonLoader.vue" import SkeletonLoader from "@/components/SkeletonLoader.vue"
import StackedNote from "@/components/StackedNote.vue" import StackedNote from "@/components/StackedNote.vue"
import { useLinks } from "@/hooks/useLinks.hook" import { useLinks } from "@/hooks/useLinks.hook"
@@ -21,10 +21,6 @@ import CacheAllNotes from "@/modules/note/components/CacheAllNote.vue"
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store" import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
import { useUserSettings } from "@/modules/user/hooks/useUserSettings.hook" import { useUserSettings } from "@/modules/user/hooks/useUserSettings.hook"
const HeaderNote = defineAsyncComponent(
() => import("@/components/HeaderNote.vue")
)
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
user: string user: string

View File

@@ -16,19 +16,37 @@ const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`)
<template> <template>
<div class="font-change" v-if="sortedFontFamilies.length > 0"> <div class="font-change" v-if="sortedFontFamilies.length > 0">
<theme-swap /> <div>
<label for="title-font" class="font-label">t</label>
<select <select
id="title-font"
class="select" class="select"
:value="store.userSettings?.chosenFontFamily" :value="store.userSettings?.chosenTitleFont"
@change="store.setFontFamily(($event.target as HTMLSelectElement).value)" @change="store.setTitleFont(($event.target as HTMLSelectElement).value)"
> >
<option v-for="font in sortedFontFamilies" :key="font" :value="font"> <option v-for="font in sortedFontFamilies" :key="font" :value="font">
{{ font }} {{ font }}
</option> </option>
</select> </select>
<label for="body-font" class="font-label">p</label>
<select <select
id="body-font"
class="select"
:value="store.userSettings?.chosenBodyFont"
@change="store.setBodyFont(($event.target as HTMLSelectElement).value)"
>
<option v-for="font in sortedFontFamilies" :key="font" :value="font">
{{ font }}
</option>
</select>
</div>
<div>
<theme-swap />
<label for="font-size" class="font-label">s</label>
<select
id="font-size"
class="select" class="select"
:value="store.userSettings?.chosenFontSize" :value="store.userSettings?.chosenFontSize"
@change="store.setFontSize(($event.target as HTMLSelectElement).value)" @change="store.setFontSize(($event.target as HTMLSelectElement).value)"
@@ -38,19 +56,28 @@ const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`)
</option> </option>
</select> </select>
</div> </div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.font-change { .font-change {
flex: 1;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
select { select {
flex: 1; flex: 1;
display: flex; display: flex;
} }
div {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
margin: 1rem;
}
}
.font-label {
font-weight: bold;
font-size: 0.75rem;
opacity: 0.6;
} }
</style> </style>

View File

@@ -1,32 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import FontChange from "@/components/FontChange.vue" import FontChange from "@/components/FontChange.vue"
import HomeButton from "@/components/HomeButton.vue"
defineProps<{ user: string; repo: string }>() defineProps<{ user: string; repo: string }>()
</script> </script>
<template> <template>
<header class="header-note"> <header class="header-note">
<router-link <home-button />
:to="{ name: 'Home' }"
class="button is-small is-white back-button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-narrow-left"
width="28"
height="28"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="5" y1="12" x2="19" y2="12" />
<line x1="5" y1="12" x2="9" y2="16" />
<line x1="5" y1="12" x2="9" y2="8" />
</svg>
</router-link>
<!-- <router-link <!-- <router-link
:to="{ name: 'SpacedRepetitionCard', params: { user, repo } }" :to="{ name: 'SpacedRepetitionCard', params: { user, repo } }"
> >
@@ -51,12 +32,12 @@ defineProps<{ user: string; repo: string }>()
</svg> </svg>
</router-link> --> </router-link> -->
<button onclick="font_modal.showModal()"> <a class="btn btn-ghost btn-circle" onclick="font_modal.showModal()">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icons-tabler-outline icon-tabler-typography" class="icon icon-tabler icons-tabler-outline icon-tabler-typography"
width="36" width="30"
height="36" height="30"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -70,12 +51,15 @@ defineProps<{ user: string; repo: string }>()
<path d="M10.2 6.3l5.8 13.7" /> <path d="M10.2 6.3l5.8 13.7" />
<path d="M5 20l6 -16l2 0l7 16" /> <path d="M5 20l6 -16l2 0l7 16" />
</svg> </svg>
</button> </a>
<router-link :to="{ name: 'FluxNoteView', params: { user, repo } }"> <router-link
class="btn btn-ghost btn-circle"
:to="{ name: 'FluxNoteView', params: { user, repo } }"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="36" width="30"
height="36" height="30"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
@@ -88,12 +72,15 @@ defineProps<{ user: string; repo: string }>()
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" /> <path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" />
</svg> </svg>
</router-link> </router-link>
<router-link :to="{ name: 'DraftNotes', params: { user, repo } }"> <router-link
class="btn btn-ghost btn-circle"
:to="{ name: 'DraftNotes', params: { user, repo } }"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-notes" class="icon icon-tabler icon-tabler-notes"
width="36" width="30"
height="36" height="30"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -107,11 +94,14 @@ defineProps<{ user: string; repo: string }>()
<line x1="9" y1="15" x2="13" y2="15" /> <line x1="9" y1="15" x2="13" y2="15" />
</svg> </svg>
</router-link> </router-link>
<router-link :to="{ name: 'TodoNotes', params: { user, repo } }"> <router-link
class="btn btn-ghost btn-circle"
:to="{ name: 'TodoNotes', params: { user, repo } }"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="36" width="30"
height="36" height="30"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -129,12 +119,15 @@ defineProps<{ user: string; repo: string }>()
<path d="M11 18l9 0" /> <path d="M11 18l9 0" />
</svg> </svg>
</router-link> </router-link>
<router-link :to="{ name: 'FleetingNotes', params: { user, repo } }"> <router-link
class="btn btn-ghost btn-circle"
:to="{ name: 'FleetingNotes', params: { user, repo } }"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-mailbox" class="icon icon-tabler icon-tabler-mailbox"
width="36" width="30"
height="36" height="30"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="1.5"
stroke="currentColor" stroke="currentColor"
@@ -150,7 +143,7 @@ defineProps<{ user: string; repo: string }>()
</svg> </svg>
</router-link> </router-link>
<dialog id="font_modal" class="modal"> <dialog id="font_modal" class="modal">
<div class="modal-box"> <div class="modal-box w-11/12 max-w-5xl">
<h3 class="text-lg font-bold">Style settings</h3> <h3 class="text-lg font-bold">Style settings</h3>
<font-change /> <font-change />
</div> </div>
@@ -167,15 +160,5 @@ defineProps<{ user: string; repo: string }>()
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-top: 10px; margin-top: 10px;
img {
&:hover {
cursor: pointer;
}
}
button {
color: var(--color-accent);
}
} }
</style> </style>

View File

@@ -40,6 +40,7 @@ const getStyle = (seed: string) => {
<style scoped lang="scss"> <style scoped lang="scss">
.repo-list { .repo-list {
display: flex; display: flex;
justify-content: space-evenly;
gap: 1rem; gap: 1rem;
flex-wrap: wrap; flex-wrap: wrap;

View File

@@ -3,16 +3,19 @@ import RepoList from "@/components/RepoList.vue"
import SignInAtproto from "@/components/SignInAtproto.vue" import SignInAtproto from "@/components/SignInAtproto.vue"
import SignInGithub from "@/components/SignInGithub.vue" import SignInGithub from "@/components/SignInGithub.vue"
import ThemeSwap from "@/components/ThemeSwap.vue" import ThemeSwap from "@/components/ThemeSwap.vue"
import { useATProtoLogin } from "@/hooks/useATProtoLogin.hook"
import { useForm } from "@/hooks/useForm.hook" import { useForm } from "@/hooks/useForm.hook"
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook" import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
import LastVisited from "@/modules/history/components/LastVisited.vue" import LastVisited from "@/modules/history/components/LastVisited.vue"
const { isLogged } = useGitHubLogin() const { isLogged } = useGitHubLogin()
const { isLoggedIn: isATProtoLoggedIn, avatarUrl } = useATProtoLogin()
const { userInput, repoInput, submit } = useForm() const { userInput, repoInput, submit } = useForm()
</script> </script>
<template> <template>
<div class="welcome-world"> <div class="welcome-world">
<div class="welcome-content">
<h1 class="title is-1"> <h1 class="title is-1">
<img src="/favicon.png" alt="Remanso icon" class="remanso-logo" /> <img src="/favicon.png" alt="Remanso icon" class="remanso-logo" />
Remanso Remanso
@@ -22,13 +25,6 @@ const { userInput, repoInput, submit } = useForm()
<last-visited /> <last-visited />
<div class="get-started">
<sign-in-github />
<router-link v-if="isLogged" :to="{ name: 'RepoList' }" class="btn btn-sm"
>Manage your repos</router-link
>
</div>
<form class="github-form" @submit.prevent> <form class="github-form" @submit.prevent>
<div>github/</div> <div>github/</div>
<input <input
@@ -44,12 +40,15 @@ const { userInput, repoInput, submit } = useForm()
type="text" type="text"
placeholder="repo" placeholder="repo"
/> />
<button type="submit" class="btn btn-primary" @click="submit">go</button> <button type="submit" class="btn btn-sm btn-primary" @click="submit">
go
</button>
</form> </form>
</div>
<footer> <footer>
<theme-swap /> <theme-swap />
Made with made with
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-heart" class="icon icon-tabler icon-tabler-heart"
@@ -71,7 +70,32 @@ const { userInput, repoInput, submit } = useForm()
<a href="https://apoena.dev" target="_blank" rel="noopener noreferrer" <a href="https://apoena.dev" target="_blank" rel="noopener noreferrer"
>apoena</a >apoena</a
> >
<sign-in-atproto :with-sign-out="false" /> <button
class="btn btn-ghost btn-circle btn-sm profile-btn"
onclick="profile_modal.showModal()"
>
<img
v-if="isATProtoLoggedIn && avatarUrl"
:src="avatarUrl"
class="profile-avatar"
alt="Profile"
/>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" />
<path d="M6 20c0 -2.21 2.686 -4 6 -4s6 1.79 6 4" />
</svg>
</button>
<router-link <router-link
:to="{ :to="{
name: 'FluxNoteView', name: 'FluxNoteView',
@@ -81,6 +105,28 @@ const { userInput, repoInput, submit } = useForm()
>Get started</router-link >Get started</router-link
> >
</footer> </footer>
<dialog id="profile_modal" class="modal">
<div class="modal-box profile-modal-box">
<h3 class="text-lg font-bold">Profile</h3>
<div class="profile-section">
<sign-in-atproto :with-sign-out="true" />
</div>
<div class="divider"></div>
<div class="profile-section">
<sign-in-github />
<router-link
v-if="isLogged"
:to="{ name: 'RepoList' }"
class="btn btn-sm"
>Manage your repos</router-link
>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button></button>
</form>
</dialog>
</div> </div>
</template> </template>
@@ -98,26 +144,26 @@ h1 {
} }
.welcome-world { .welcome-world {
padding: 1rem;
margin: auto; margin: auto;
display: flex; display: flex;
flex: 1; flex: 1;
align-self: stretch;
flex-direction: column; flex-direction: column;
justify-content: space-between;
.get-started {
margin: center;
text-align: center;
display: flex;
flex-wrap: wrap;
gap: 1rem; gap: 1rem;
justify-content: center;
}
.title { .title {
text-align: center; text-align: center;
} }
} }
.welcome-content {
display: flex;
flex: 1;
flex-direction: column;
gap: 1rem;
padding: 0 0.5rem;
}
.github-form { .github-form {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -127,15 +173,33 @@ h1 {
max-width: 140px; max-width: 140px;
} }
} }
footer { footer {
display: flex; display: flex;
gap: 1rem; gap: 0.2rem;
align-items: center; align-items: center;
justify-content: space-around;
img { padding: 0.5rem;
vertical-align: middle;
margin-top: 0;
} }
.profile-avatar {
max-width: 100%;
border-radius: 50%;
object-fit: cover;
box-shadow: none;
}
.profile-modal-box {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.profile-section {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
} }
.to-user-repo { .to-user-repo {

View File

@@ -14,14 +14,32 @@ import {
const did = ref<string | null>(null) const did = ref<string | null>(null)
const handle = ref<string | null>(null) const handle = ref<string | null>(null)
const avatarUrl = ref<string | null>(null)
let init = true let init = true
const fetchAvatar = async (actorDid: string) => {
try {
const res = await fetch(
`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(actorDid)}`
)
if (res.ok) {
const data = await res.json()
avatarUrl.value = data.avatar ?? null
}
} catch {
avatarUrl.value = null
}
}
const initializeAuth = async () => { const initializeAuth = async () => {
// Load cached session from IndexedDB first (fast, local) so the UI can render immediately // Load cached session from IndexedDB first (fast, local) so the UI can render immediately
const stored = await loadSession() const stored = await loadSession()
did.value = stored?.did ?? "" did.value = stored?.did ?? ""
handle.value = stored?.handle ?? "" handle.value = stored?.handle ?? ""
if (stored?.did) {
fetchAvatar(stored.did)
}
// Then restore OAuth session in the background (may involve network) // Then restore OAuth session in the background (may involve network)
const session = await restoreSession() const session = await restoreSession()
@@ -32,6 +50,7 @@ const initializeAuth = async () => {
did.value = session.did did.value = session.did
handle.value = resolvedHandle handle.value = resolvedHandle
await saveSession(session.did, resolvedHandle) await saveSession(session.did, resolvedHandle)
fetchAvatar(session.did)
window.history.replaceState( window.history.replaceState(
null, null,
@@ -61,11 +80,13 @@ export const useATProtoLogin = () => {
await clearSession() await clearSession()
did.value = "" did.value = ""
handle.value = "" handle.value = ""
avatarUrl.value = null
} }
return { return {
did, did,
handle, handle,
avatarUrl,
isLoggedIn, isLoggedIn,
isATProtoReady, isATProtoReady,
signIn, signIn,

View File

@@ -8,4 +8,6 @@ export interface UserSettings extends Model<DataType.UserSettings> {
fontSize?: string fontSize?: string
chosenFontSize?: string chosenFontSize?: string
backlink?: boolean backlink?: boolean
chosenTitleFont?: string
chosenBodyFont?: string
} }

View File

@@ -104,7 +104,16 @@ export const getUserSettingsContent = async (
return null return null
} }
return JSON.parse(atob(content)) as UserSettings const raw = JSON.parse(atob(content)) as UserSettings & {
t?: string
p?: string
}
const { t, p, ...rest } = raw
return {
...rest,
chosenTitleFont: t,
chosenBodyFont: p
}
} }
export const queryFileContent = async ( export const queryFileContent = async (

View File

@@ -81,6 +81,14 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
: userSettings?.fontFamily : userSettings?.fontFamily
const chosenFontSize = const chosenFontSize =
this.userSettings?.chosenFontSize ?? userSettings?.fontSize this.userSettings?.chosenFontSize ?? userSettings?.fontSize
const chosenTitleFont =
this.userSettings?.chosenTitleFont ??
userSettings?.chosenTitleFont ??
chosenFontFamily
const chosenBodyFont =
this.userSettings?.chosenBodyFont ??
userSettings?.chosenBodyFont ??
chosenFontFamily
this.userSettings = userSettings this.userSettings = userSettings
if (!this.userSettings) { if (!this.userSettings) {
@@ -91,6 +99,8 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
chosenFontFamily ?? this.userSettings.fontFamily chosenFontFamily ?? this.userSettings.fontFamily
this.userSettings.chosenFontSize = this.userSettings.chosenFontSize =
chosenFontSize ?? this.userSettings.fontSize chosenFontSize ?? this.userSettings.fontSize
this.userSettings.chosenTitleFont = chosenTitleFont
this.userSettings.chosenBodyFont = chosenBodyFont
data.update<DataType.UserSettings, UserSettings>({ data.update<DataType.UserSettings, UserSettings>({
...this.userSettings, ...this.userSettings,
@@ -156,6 +166,30 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
} }
this.userSettings.chosenFontSize = fontSize this.userSettings.chosenFontSize = fontSize
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
data.update<DataType.UserSettings, UserSettings>({
...this.userSettings,
_id: userSettingsId
})
},
setTitleFont(font: string) {
if (!this.userSettings) {
return
}
this.userSettings.chosenTitleFont = font
const userSettingsId = `UserSetting-${this.user}-${this.repo}`
data.update<DataType.UserSettings, UserSettings>({
...this.userSettings,
_id: userSettingsId
})
},
setBodyFont(font: string) {
if (!this.userSettings) {
return
}
this.userSettings.chosenBodyFont = font
const userSettingsId = `UserSetting-${this.user}-${this.repo}` const userSettingsId = `UserSetting-${this.user}-${this.repo}`
data.update<DataType.UserSettings, UserSettings>({ data.update<DataType.UserSettings, UserSettings>({
...this.userSettings, ...this.userSettings,

View File

@@ -12,10 +12,15 @@ export const useUserSettings = () => {
watchEffect(() => { watchEffect(() => {
const root = document.documentElement const root = document.documentElement
const fontFamily = store.userSettings?.chosenFontFamily
const fontSize = store.userSettings?.chosenFontSize const fontSize = store.userSettings?.chosenFontSize
const bodyFont = store.userSettings?.chosenBodyFont
const titleFont = store.userSettings?.chosenTitleFont
downloadFont(fontFamily || DEFAULT_FONT_POLICY) downloadFont(bodyFont || DEFAULT_FONT_POLICY, "--font-family")
downloadFont(
titleFont || bodyFont || DEFAULT_FONT_POLICY,
"--title-font-family"
)
root.style.setProperty("--font-size", fontSize || DEFAULT_FONT_SIZE) root.style.setProperty("--font-size", fontSize || DEFAULT_FONT_SIZE)
}) })
} }

View File

@@ -104,7 +104,12 @@ router.beforeEach(() => {
} }
).startViewTransition(async () => { ).startViewTransition(async () => {
resolve() resolve()
await nextTick() await new Promise<void>((r) => {
const unwatch = router.afterEach(() => {
unwatch()
nextTick().then(r)
})
})
}) })
}) })
}) })

View File

@@ -6,6 +6,7 @@
:root { :root {
--primary-color: #ffa4c0; --primary-color: #ffa4c0;
--font-family: "Libertinus Serif", serif; --font-family: "Libertinus Serif", serif;
--title-font-family: "Libertinus Serif", serif;
--font-size: 13pt; --font-size: 13pt;
--font-color: #4a4a4a; --font-color: #4a4a4a;
--link: #445fb9; --link: #445fb9;
@@ -48,22 +49,10 @@
} }
} }
html {
overflow-y: auto;
overflow-x: auto;
}
body {
height: 100vh;
scroll-behavior: smooth;
overflow-y: auto;
}
@media screen and (min-width: 769px) {
html, html,
body { body {
overflow-y: hidden; height: 100dvh;
} scroll-behavior: smooth;
} }
.columns { .columns {
@@ -77,7 +66,7 @@ body {
} }
#app { #app {
height: 100vh; height: 100dvh;
display: flex; display: flex;
} }

View File

@@ -6,7 +6,10 @@ const assembleFontLink = (font: string) => {
.replaceAll(" ", "+")}` .replaceAll(" ", "+")}`
} }
export const downloadFont = async (font: string): Promise<void> => { export const downloadFont = async (
font: string,
cssVar = "--font-family"
): Promise<void> => {
const href = assembleFontLink(font) const href = assembleFontLink(font)
// check if the href already exists // check if the href already exists
@@ -23,7 +26,7 @@ export const downloadFont = async (font: string): Promise<void> => {
try { try {
await new FontFaceObserver(font).load() await new FontFaceObserver(font).load()
document.documentElement.style.setProperty("--font-family", font) document.documentElement.style.setProperty(cssVar, font)
} catch (error) { } catch (error) {
console.warn("error when loading font") console.warn("error when loading font")
} }

View File

@@ -16,6 +16,7 @@ import WelcomeWorld from "@/components/WelcomeWorld.vue"
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
height: 100dvh;
} }
.authorize { .authorize {

View File

@@ -4,7 +4,7 @@ import { useTitle } from "@vueuse/core"
import { computed, nextTick, ref, watch } from "vue" import { computed, nextTick, ref, watch } from "vue"
import { useRouter } from "vue-router" import { useRouter } from "vue-router"
import BackButton from "@/components/BackButton.vue" import HomeButton from "@/components/HomeButton.vue"
import SkeletonLoader from "@/components/SkeletonLoader.vue" import SkeletonLoader from "@/components/SkeletonLoader.vue"
import StackedPublicNote from "@/components/StackedPublicNote.vue" import StackedPublicNote from "@/components/StackedPublicNote.vue"
import ThemeSwap from "@/components/ThemeSwap.vue" import ThemeSwap from "@/components/ThemeSwap.vue"
@@ -129,13 +129,7 @@ watch(
<main class="public-note-view repo-note note-container"> <main class="public-note-view repo-note note-container">
<div class="note article"> <div class="note article">
<div class="header"> <div class="header">
<back-button <home-button />
:fallback="{ name: 'PublicNoteListByDidView', params: { shortDid } }"
:prefer-fallback="false"
/>
<img src="/favicon.png" alt="Remanso" class="remanso-logo" />
<theme-swap /> <theme-swap />
</div> </div>
<div class="subheader"> <div class="subheader">
@@ -197,13 +191,6 @@ watch(
gap: 1rem; gap: 1rem;
} }
.remanso-logo {
width: 32px;
height: 32px;
box-shadow: none;
view-transition-name: remanso-logo;
}
.subheader { .subheader {
margin: 1rem auto 0; margin: 1rem auto 0;
} }

View File

@@ -11,7 +11,8 @@ const defaultTitleStyles = Array.from(
...acc, ...acc,
[heading]: { [heading]: {
"margin-top": "0", "margin-top": "0",
"margin-bottom": "0.5em" "margin-bottom": "0.5em",
"font-family": "var(--title-font-family)"
} }
}), }),
{} {}