1
0
Fork 0

Add 404 pages

This commit is contained in:
Moritz Ruth 2021-12-18 18:42:15 +01:00
parent 481e30c1b3
commit f593c1c638
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
8 changed files with 142 additions and 31 deletions

View file

@ -1,12 +1,11 @@
<template>
<div class="h-100vh w-100vw text-light-100 overflow-x-hidden">
<suspense @pending="startLoading()" @resolve="stopLoading()">
<router-view v-slot="{ Component }">
<suspense @pending="startLoading()" @resolve="stopLoading()">
<component :is="Component"/>
</suspense>
</router-view>
</suspense>
<router-view v-slot="{ Component }">
<suspense @pending="startLoading()" @resolve="stopLoading()">
<!-- The key makes that components are not reused if only params changed -->
<component :is="Component" :key="$route.fullPath"/>
</suspense>
</router-view>
</div>
<div class="bg-background fixed top-0 left-0 right-0 bottom-0 z-100 backdrop-filter" :style="loadingOverlayStyle">
<div class="font-bold text-light-900 text-2xl h-full w-full flex justify-center items-center overflow-hidden" :style="loadingOverlayContentStyle">
@ -53,31 +52,21 @@
import { computed, ref } from "vue"
import { useWindowSize, whenever } from "@vueuse/core"
import { pageComponentLoading } from "./store"
import { useRouter } from "vue-router"
export default {
name: "App",
setup() {
const loadingStartedTime = ref<null | number>(null)
const isLoadingScreenActive = ref(false)
const START_TRANSITION_DURATION = 300
const START_TRANSITION_DELAY = 200
let suspenseLoading = false
const START_TRANSITION_DURATION = 200
const START_TRANSITION_DELAY = 0
const loadingTexts = ref<Array<{ x: number, y: number }>>([])
const stopLoading = () => {
if (loadingStartedTime.value === null) return
const stop = () => {
isLoadingScreenActive.value = false
loadingStartedTime.value = null
suspenseLoading = false
}
const timeSinceStart = Date.now() - loadingStartedTime.value
if (timeSinceStart > START_TRANSITION_DELAY) setTimeout(stop, START_TRANSITION_DURATION - timeSinceStart)
else stop()
isLoadingScreenActive.value = false
loadingStartedTime.value = null
}
const startLoading = () => {
@ -89,9 +78,22 @@
// Suspense @resolve is also called when the component is not async, so we don't need to handle stopping
whenever(pageComponentLoading, () => {
if (loadingStartedTime.value !== null) return
loadingStartedTime.value = Date.now()
isLoadingScreenActive.value = true
// Runs when a page component is imported
startLoading()
})
const router = useRouter()
let isFirst = true
router.beforeResolve((to, from, next) => {
if (isFirst) {
isFirst = false
next()
return
}
startLoading()
setTimeout(next, START_TRANSITION_DURATION)
})
const { width, height } = useWindowSize()

View file

@ -0,0 +1,52 @@
<template>
<NotFoundPage v-if="post === null" object-name="post" back-target="/blog"/>
<TopBarLayout title="Blog" back-target="/blog" v-else>
<article>
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
{{ post.title }}
</h1>
<XSpacer v="4"/>
<div class="flex justify-center -sm:flex-col -sm:space-y-1 -sm:pt-2 sm:space-x-3 text-sm sm:text-base">
<div>
Published at {{ post.published_at.slice(0, 10) }}
</div>
<div class="-sm:hidden">
|
</div>
<div>
Reading time: {{ post.reading_time_minutes }} minute{{ post.reading_time_minutes === 1 ? "" : "s" }}
</div>
</div>
<XSpacer v="8"/>
<Prose v-html="html"/>
</article>
</TopBarLayout>
</template>
<style module>
</style>
<script lang="ts">
import TopBarLayout from "./TopBarLayout.vue"
import { getPostBySlug } from "../posts"
import { useRoute } from "vue-router"
import Prose from "./Prose.vue"
import XSpacer from "./XSpacer.vue"
import NotFoundPage from "./NotFoundPage.vue"
export default {
name: "BlogPostPageContent",
components: { NotFoundPage, XSpacer, Prose, TopBarLayout },
async setup() {
const route = useRoute()
const post = await getPostBySlug(route.params.slug as string)
return {
post,
// Yep, that's awful code
html: post?.body_html?.replaceAll("h2>", "h3>")?.replaceAll("h1>", "h2>")
}
}
}
</script>

View file

@ -0,0 +1,39 @@
<template>
<div class="pt-40 px-5 flex flex-col items-center">
<div class="font-bold text-3xl text-center">
This&nbsp;{{ objectName }} does not&nbsp;exist.
</div>
<XSpacer v="5"/>
<router-link :to="backTarget" class="flex items-center">
<ArrowLeftIcon/>
<XSpacer h="2"/>
<div class="text-lg">
Back
</div>
</router-link>
</div>
</template>
<style module>
</style>
<script>
import ArrowLeftIcon from "~icons/ph/arrow-left"
import XSpacer from "../components/XSpacer.vue"
export default {
name: "NotFoundPage",
components: { XSpacer, ArrowLeftIcon },
props: {
objectName: {
type: String,
required: true
},
backTarget: {
type: String,
required: true
}
}
}
</script>

View file

@ -56,7 +56,7 @@
@apply not-italic;
}
:where(address, p, ol, ul) + :where(address, p, ol, ul) {
:where(address, p, ol, ul) + :is(address, p, ol, ul) {
@apply pt-4;
}
}

14
src/pages/[...all].vue Normal file
View file

@ -0,0 +1,14 @@
<template>
<NotFoundPage object-name="page" back-target="/"/>
</template>
<script>
import ArrowLeftIcon from "~icons/ph/arrow-left"
import XSpacer from "../components/XSpacer.vue"
import NotFoundPage from "../components/NotFoundPage.vue"
export default {
name: "FallbackPage",
components: { NotFoundPage, XSpacer, ArrowLeftIcon }
}
</script>

View file

@ -1,5 +1,6 @@
<template>
<TopBarLayout title="Blog" back-target="/blog">
<NotFoundPage v-if="post === null" object-name="post" back-target="/blog"/>
<TopBarLayout title="Blog" back-target="/blog" v-else>
<article>
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
{{ post.title }}
@ -32,10 +33,11 @@
import { useRoute } from "vue-router"
import Prose from "../../components/Prose.vue"
import XSpacer from "../../components/XSpacer.vue"
import NotFoundPage from "../../components/NotFoundPage.vue"
export default {
name: "BlogPostPage",
components: { XSpacer, Prose, TopBarLayout },
components: { NotFoundPage, XSpacer, Prose, TopBarLayout },
async setup() {
const route = useRoute()
const post = await getPostBySlug(route.params.slug as string)
@ -43,7 +45,7 @@
return {
post,
// Yep, that's awful code
html: post.body_html.replaceAll("h2>", "h3>").replaceAll("h1>", "h2>")
html: post?.body_html?.replaceAll("h2>", "h3>")?.replaceAll("h1>", "h2>")
}
}
}

View file

@ -16,4 +16,4 @@ export interface FullPost extends Post {
}
export const getPosts = () => $fetch<Post[]>(`https://dev.to/api/articles?username=${USERNAME}&per_page=1000`)
export const getPostBySlug = (slug: string) => $fetch<FullPost>(`https://dev.to/api/articles/${USERNAME}/${slug}`)
export const getPostBySlug = (slug: string) => $fetch<FullPost>(`https://dev.to/api/articles/${USERNAME}/${slug}`).catch(() => null)

View file

@ -7,7 +7,9 @@ import iconsPlugin from "unplugin-icons/vite"
export default defineConfig({
plugins: [
vuePlugin(),
pagesPlugin(),
pagesPlugin({
syncIndex: false
}),
windicssPlugin(),
iconsPlugin()
]