fix(mobile): restore overflow-y and unstick readme on vertical scroll
- Restore explicit overflow-y:auto on #main-app for mobile (removed in
63f5d64) — implicit coercion from overflow-x:auto is not reliable in
all Safari/WebKit versions.
- Override position:sticky on .readme to position:relative on mobile.
The desktop sticky (left:0) is correct for horizontal scroll, but on
mobile vertical scroll it pinned the 100dvh-tall readme across the
entire viewport, hiding all stacked notes behind it.
This commit is contained in:
@@ -9,21 +9,21 @@ Migrate Remanso from a Vue 3 web app to a fully native iOS + Android app built w
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Concern | Current (Vue) | React Native |
|
||||
|---|---|---|
|
||||
| Framework | Vue 3 + Vite | Expo SDK (managed workflow) |
|
||||
| Routing | Vue Router | Expo Router (file-system routing over React Navigation v7) |
|
||||
| State | Pinia | Zustand |
|
||||
| Server state | TanStack Vue Query | TanStack Query (same library) |
|
||||
| Styling | DaisyUI + Tailwind CSS | NativeWind v4 |
|
||||
| Local DB | PouchDB (IndexedDB) | Expo SQLite |
|
||||
| Simple KV store | localStorage | MMKV |
|
||||
| Auth | OAuth redirects | expo-auth-session |
|
||||
| GitHub API | @octokit/rest | @octokit/rest (unchanged) |
|
||||
| i18n | vue-i18n | react-i18next |
|
||||
| Date utils | date-fns | date-fns (unchanged) |
|
||||
| Markdown content | markdown-it (DOM) | react-native-webview |
|
||||
| Fonts | CSS custom properties | expo-font |
|
||||
| Concern | Current (Vue) | React Native |
|
||||
| ---------------- | ---------------------- | ---------------------------------------------------------- |
|
||||
| Framework | Vue 3 + Vite | Expo SDK (managed workflow) |
|
||||
| Routing | Vue Router | Expo Router (file-system routing over React Navigation v7) |
|
||||
| State | Pinia | Zustand |
|
||||
| Server state | TanStack Vue Query | TanStack Query (same library) |
|
||||
| Styling | DaisyUI + Tailwind CSS | NativeWind v4 |
|
||||
| Local DB | PouchDB (IndexedDB) | Expo SQLite |
|
||||
| Simple KV store | localStorage | MMKV |
|
||||
| Auth | OAuth redirects | expo-auth-session |
|
||||
| GitHub API | @octokit/rest | @octokit/rest (unchanged) |
|
||||
| i18n | vue-i18n | react-i18next |
|
||||
| Date utils | date-fns | date-fns (unchanged) |
|
||||
| Markdown content | markdown-it (DOM) | react-native-webview |
|
||||
| Fonts | CSS custom properties | expo-font |
|
||||
|
||||
## Navigation Structure
|
||||
|
||||
@@ -137,14 +137,18 @@ Zustand replaces Pinia. The store shape is identical to `userRepo.store.ts`:
|
||||
|
||||
```ts
|
||||
const useRepoStore = create<RepoState>((set, get) => ({
|
||||
user: '',
|
||||
repo: '',
|
||||
user: "",
|
||||
repo: "",
|
||||
files: [],
|
||||
userSettings: null,
|
||||
needToLogin: false,
|
||||
setRepo: (user, repo) => set({ user, repo }),
|
||||
loadFiles: async () => { /* Octokit call */ },
|
||||
loadSettings: async () => { /* MMKV read */ },
|
||||
loadFiles: async () => {
|
||||
/* Octokit call */
|
||||
},
|
||||
loadSettings: async () => {
|
||||
/* MMKV read */
|
||||
}
|
||||
}))
|
||||
```
|
||||
|
||||
@@ -165,6 +169,7 @@ The markdown-it pipeline (KaTeX, Mermaid, shiki, tabler icons, html5-media, GitH
|
||||
`NoteWebView` is a native UIView/View wrapper around a WebView engine. It is a React Native component — not a web app. The surrounding app (navigation chrome, tab bar, headers, settings, auth screens) is 100% native. Only the note content pane renders HTML. This is the standard pattern for rich content in React Native (used by GitHub Mobile, Linear, and others).
|
||||
|
||||
The WebView communicates back to the native layer via `postMessage` for:
|
||||
|
||||
- Internal note link taps (trigger React Navigation push)
|
||||
- External URL taps (open in system browser)
|
||||
- Backlink detection events
|
||||
|
||||
@@ -25,6 +25,12 @@ const { isATProtoReady } = useATProtoLogin()
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
#main-app {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation-duration: 0.25s;
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
toRefs,
|
||||
watch
|
||||
} from "vue"
|
||||
import { computed, nextTick, onMounted, onUnmounted, toRefs, watch } from "vue"
|
||||
|
||||
import HeaderNote from "@/components/HeaderNote.vue"
|
||||
import SkeletonLoader from "@/components/SkeletonLoader.vue"
|
||||
@@ -241,6 +234,7 @@ $header-height: 40px;
|
||||
.flux-note {
|
||||
.readme {
|
||||
padding: 0 0.75rem;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,22 +50,14 @@ const fontSize = computed({
|
||||
<div class="font-change">
|
||||
<div>
|
||||
<label for="title-font" class="font-label">t</label>
|
||||
<select
|
||||
id="title-font"
|
||||
class="select"
|
||||
v-model="titleFont"
|
||||
>
|
||||
<select id="title-font" class="select" v-model="titleFont">
|
||||
<option v-for="font in sortedFontFamilies" :key="font" :value="font">
|
||||
{{ font }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<label for="body-font" class="font-label">p</label>
|
||||
<select
|
||||
id="body-font"
|
||||
class="select"
|
||||
v-model="bodyFont"
|
||||
>
|
||||
<select id="body-font" class="select" v-model="bodyFont">
|
||||
<option v-for="font in sortedFontFamilies" :key="font" :value="font">
|
||||
{{ font }}
|
||||
</option>
|
||||
@@ -75,11 +67,7 @@ const fontSize = computed({
|
||||
<theme-swap />
|
||||
|
||||
<label for="font-size" class="font-label">s</label>
|
||||
<select
|
||||
id="font-size"
|
||||
class="select"
|
||||
v-model="fontSize"
|
||||
>
|
||||
<select id="font-size" class="select" v-model="fontSize">
|
||||
<option v-for="size in fontSizes" :key="size" :value="size">
|
||||
{{ size }}
|
||||
</option>
|
||||
|
||||
@@ -6,7 +6,10 @@ const goHome = () => router.push({ name: "Home" })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="btn btn-ghost btn-circle btn-lg text-base-content" @click="goHome">
|
||||
<button
|
||||
class="btn btn-ghost btn-circle btn-lg text-base-content"
|
||||
@click="goHome"
|
||||
>
|
||||
<img src="/favicon.png" alt="Remanso icon" class="remanso-logo" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -16,7 +16,9 @@ const isMarkdown = (filename?: string) => filename?.endsWith(".md") ?? false
|
||||
|
||||
const yieldToMain = () =>
|
||||
"scheduler" in globalThis
|
||||
? (globalThis as unknown as { scheduler: { yield: () => Promise<void> } }).scheduler.yield()
|
||||
? (
|
||||
globalThis as unknown as { scheduler: { yield: () => Promise<void> } }
|
||||
).scheduler.yield()
|
||||
: new Promise<void>((r) => setTimeout(r, 0))
|
||||
|
||||
export const useComputeBacklinks = () => {
|
||||
@@ -43,9 +45,10 @@ export const useComputeBacklinks = () => {
|
||||
}
|
||||
|
||||
const fileBacklinkId = generateId(DataType.BacklinkNote, file.sha)
|
||||
const fileBacklink = await data.get<DataType.BacklinkNote, BacklinkNote>(
|
||||
fileBacklinkId
|
||||
)
|
||||
const fileBacklink = await data.get<
|
||||
DataType.BacklinkNote,
|
||||
BacklinkNote
|
||||
>(fileBacklinkId)
|
||||
if (fileBacklink) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -37,11 +37,20 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
||||
_persistFonts() {
|
||||
if (!this.userSettings) return
|
||||
try {
|
||||
const { chosenTitleFont, chosenBodyFont, chosenFontSize, chosenFontFamily } =
|
||||
this.userSettings
|
||||
const {
|
||||
chosenTitleFont,
|
||||
chosenBodyFont,
|
||||
chosenFontSize,
|
||||
chosenFontFamily
|
||||
} = this.userSettings
|
||||
localStorage.setItem(
|
||||
`remanso:fonts:${this.user}:${this.repo}`,
|
||||
JSON.stringify({ chosenTitleFont, chosenBodyFont, chosenFontSize, chosenFontFamily })
|
||||
JSON.stringify({
|
||||
chosenTitleFont,
|
||||
chosenBodyFont,
|
||||
chosenFontSize,
|
||||
chosenFontFamily
|
||||
})
|
||||
)
|
||||
} catch {
|
||||
// ignore
|
||||
@@ -61,7 +70,8 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
|
||||
}
|
||||
|
||||
if (Object.keys(lsFonts).length) {
|
||||
if (!this.userSettings) this.userSettings = { $type: DataType.UserSettings }
|
||||
if (!this.userSettings)
|
||||
this.userSettings = { $type: DataType.UserSettings }
|
||||
Object.assign(this.userSettings, lsFonts)
|
||||
}
|
||||
|
||||
|
||||
4
src/shims-vue.d.ts
vendored
4
src/shims-vue.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue"
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
// Update these values to change the light and dark themes
|
||||
|
||||
export const themeConfig = {
|
||||
light: 'emerald',
|
||||
dark: 'forest'
|
||||
light: "emerald",
|
||||
dark: "forest"
|
||||
}
|
||||
|
||||
@@ -8,8 +8,14 @@ import { useRepoList } from "@/modules/repo/hooks/useRepoList.hook"
|
||||
|
||||
const { username } = useGitHubLogin()
|
||||
const { isReady } = useRepos()
|
||||
const { favoriteRepos, otherRepos, favoriteCheckboxes, toggleCheckbox, canLoadMore, loadMore } =
|
||||
useRepoList()
|
||||
const {
|
||||
favoriteRepos,
|
||||
otherRepos,
|
||||
favoriteCheckboxes,
|
||||
toggleCheckbox,
|
||||
canLoadMore,
|
||||
loadMore
|
||||
} = useRepoList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user