Files
remanso/src/components/FluxNote.vue
Julien Calixte fc4ed188d7 feat(repo): add Octokit request timeouts and retry UI
Before, every GitHub call awaited indefinitely with no AbortSignal,
so a "lie-fi" connection (technically online, effectively dead) left
FluxNote stuck on the skeleton loader and the freshness badge spinning
forever. Inject an 8s AbortSignal.timeout via octokit.hook.before
(20s override on the recursive tree fetch), classify failures in the
userRepo store as auth vs network, and surface a "Couldn't reach
GitHub" retry button when no cached content is available.
2026-05-16 23:28:11 +02:00

288 lines
6.3 KiB
Vue

<script lang="ts" setup>
import { computed, onMounted, onUnmounted, toRefs, watch } from "vue"
import HeaderNote from "@/components/HeaderNote.vue"
import SignInGithub from "@/components/SignInGithub.vue"
import SkeletonLoader from "@/components/SkeletonLoader.vue"
import StackedNote from "@/components/StackedNote.vue"
import { useGitHubLogin } from "@/hooks/useGitHubLogin.hook"
import { useLinks } from "@/hooks/useLinks.hook"
import { markdownBuilder } from "@/hooks/useMarkdown.hook"
import { useMarkdownPostRender } from "@/hooks/useMarkdownPostRender.hook"
import { useNoteView } from "@/hooks/useNoteView.hook"
import { useResizeContainer } from "@/hooks/useResizeContainer.hook"
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
import { useVisitRepo } from "@/modules/history/hooks/useVisitRepo.hook"
import CacheAllNotes from "@/modules/note/components/CacheAllNote.vue"
import { useUserRepoStore } from "@/modules/repo/store/userRepo.store"
import { useUserSettings } from "@/modules/user/hooks/useUserSettings.hook"
const props = withDefaults(
defineProps<{
user: string
repo: string
content?: string | null
parseContent?: boolean
withContent?: boolean
withHeader?: boolean
}>(),
{
content: null,
parseContent: true,
withContent: true,
withHeader: true
}
)
const user = computed(() => props.user)
const repo = computed(() => props.repo)
const refProps = toRefs(props)
const store = useUserRepoStore()
useUserSettings()
const { visitRepo } = useVisitRepo({ user: user, repo: repo })
const { toHTML } = markdownBuilder(repo)
const { listenToClick } = useLinks("note-display")
const { stackedNotes, scrollToFocusedNote } = useRouteQueryStackedNotes()
const { titles } = useNoteView()
const { isLogged } = useGitHubLogin()
useResizeContainer("note-container", stackedNotes)
const renderedContent = computed(() =>
props.content !== null
? props.parseContent
? toHTML(props.content)
: props.content
: store.readme
)
const isLoading = computed(() => renderedContent.value === undefined)
const hasContent = computed(() => !!renderedContent.value)
useMarkdownPostRender(renderedContent, () => ".note-display", {
onReady: () => listenToClick(),
tikz: true
})
watch(
[refProps.user, refProps.repo],
() => {
store.setUserRepo(props.user, props.repo)
},
{ immediate: true }
)
const retryLoad = () => {
store.setUserRepo(props.user, props.repo)
}
onMounted(() => visitRepo())
onUnmounted(() => {
store.resetFiles()
})
</script>
<template>
<main class="flux-note repo-note content note-container">
<div class="note readme">
<header-note v-if="withHeader" class="header" :user="user" :repo="repo" />
<div class="repo-title-breadcrumb">
<a
class="title-stacked-note-link"
@click.prevent="scrollToFocusedNote()"
>{{ repo }}</a
>
</div>
<div class="repo-title">
<div class="repo-header">
<h1 class="heading-1">
{{ repo }}
</h1>
{{ user }}
</div>
<cache-all-notes />
</div>
<slot />
<skeleton-loader v-if="isLoading" />
<div
v-else-if="withContent && !hasContent && store.loadError === 'network'"
class="repo-network-error"
>
<p>Couldn't reach GitHub. Check your connection.</p>
<button class="btn btn-primary" @click="retryLoad">Retry</button>
</div>
<div v-else-if="withContent && !hasContent" class="repo-not-found">
<template v-if="isLogged">
<p>This repository is not accessible.</p>
</template>
<template v-else>
<p>This repository is private. Sign in to view it.</p>
<sign-in-github />
</template>
</div>
<p
v-else-if="withContent && hasContent"
class="note-display"
v-html="renderedContent"
/>
</div>
<stacked-note
v-for="(stackedNote, index) in stackedNotes"
:key="stackedNote"
class="note"
:index="index"
:sha="stackedNote"
:user="user"
:repo="repo"
:title="titles[stackedNote]"
/>
</main>
</template>
<style lang="scss">
$header-height: 40px;
.flux-note {
display: flex;
flex: 1;
&.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);
}
}
.header {
height: $header-height;
}
.readme {
position: sticky;
left: 0;
top: 0;
padding: 0 2rem;
scrollbar-width: none;
.repo-title {
margin-top: 1rem;
display: flex;
flex-direction: column;
align-items: center;
.title {
margin-bottom: 0;
}
.title,
.subtitle {
text-align: center;
}
img {
vertical-align: middle;
}
}
}
.repo-not-found,
.repo-network-error {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
padding: 2rem 0;
color: var(--color-base-content);
}
.note-display {
padding-bottom: 2rem;
}
.note {
display: flex;
flex-direction: column;
overflow-y: auto;
.title {
text-align: left;
}
}
@media screen and (min-width: 769px) {
background-color: var(--note-canvas-bg);
.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);
background-color: var(--color-base-100);
}
.readme {
box-shadow: var(--note-sheet-shadow);
}
}
}
@media print, screen and (max-width: 768px) {
.flux-note {
.readme {
padding: 0 0.75rem;
position: relative;
}
}
.flux-note {
flex-wrap: wrap;
}
.note {
width: 100vw;
height: 100svh;
overflow-y: visible;
}
.repo-title-breadcrumb {
display: none;
visibility: hidden;
}
}
</style>