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> <template>
<div class="h-100vh w-100vw text-light-100 overflow-x-hidden"> <div class="h-100vh w-100vw text-light-100 overflow-x-hidden">
<suspense @pending="startLoading()" @resolve="stopLoading()">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<suspense @pending="startLoading()" @resolve="stopLoading()"> <suspense @pending="startLoading()" @resolve="stopLoading()">
<component :is="Component"/> <!-- The key makes that components are not reused if only params changed -->
<component :is="Component" :key="$route.fullPath"/>
</suspense> </suspense>
</router-view> </router-view>
</suspense>
</div> </div>
<div class="bg-background fixed top-0 left-0 right-0 bottom-0 z-100 backdrop-filter" :style="loadingOverlayStyle"> <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"> <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 { computed, ref } from "vue"
import { useWindowSize, whenever } from "@vueuse/core" import { useWindowSize, whenever } from "@vueuse/core"
import { pageComponentLoading } from "./store" import { pageComponentLoading } from "./store"
import { useRouter } from "vue-router"
export default { export default {
name: "App", name: "App",
setup() { setup() {
const loadingStartedTime = ref<null | number>(null) const loadingStartedTime = ref<null | number>(null)
const isLoadingScreenActive = ref(false) const isLoadingScreenActive = ref(false)
const START_TRANSITION_DURATION = 300 const START_TRANSITION_DURATION = 200
const START_TRANSITION_DELAY = 200 const START_TRANSITION_DELAY = 0
let suspenseLoading = false
const loadingTexts = ref<Array<{ x: number, y: number }>>([]) const loadingTexts = ref<Array<{ x: number, y: number }>>([])
const stopLoading = () => { const stopLoading = () => {
if (loadingStartedTime.value === null) return
const stop = () => {
isLoadingScreenActive.value = false isLoadingScreenActive.value = false
loadingStartedTime.value = null loadingStartedTime.value = null
suspenseLoading = false
}
const timeSinceStart = Date.now() - loadingStartedTime.value
if (timeSinceStart > START_TRANSITION_DELAY) setTimeout(stop, START_TRANSITION_DURATION - timeSinceStart)
else stop()
} }
const startLoading = () => { 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 // Suspense @resolve is also called when the component is not async, so we don't need to handle stopping
whenever(pageComponentLoading, () => { whenever(pageComponentLoading, () => {
if (loadingStartedTime.value !== null) return // Runs when a page component is imported
loadingStartedTime.value = Date.now() startLoading()
isLoadingScreenActive.value = true })
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() 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; @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; @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> <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> <article>
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special"> <h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
{{ post.title }} {{ post.title }}
@ -32,10 +33,11 @@
import { useRoute } from "vue-router" import { useRoute } from "vue-router"
import Prose from "../../components/Prose.vue" import Prose from "../../components/Prose.vue"
import XSpacer from "../../components/XSpacer.vue" import XSpacer from "../../components/XSpacer.vue"
import NotFoundPage from "../../components/NotFoundPage.vue"
export default { export default {
name: "BlogPostPage", name: "BlogPostPage",
components: { XSpacer, Prose, TopBarLayout }, components: { NotFoundPage, XSpacer, Prose, TopBarLayout },
async setup() { async setup() {
const route = useRoute() const route = useRoute()
const post = await getPostBySlug(route.params.slug as string) const post = await getPostBySlug(route.params.slug as string)
@ -43,7 +45,7 @@
return { return {
post, post,
// Yep, that's awful code // 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 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({ export default defineConfig({
plugins: [ plugins: [
vuePlugin(), vuePlugin(),
pagesPlugin(), pagesPlugin({
syncIndex: false
}),
windicssPlugin(), windicssPlugin(),
iconsPlugin() iconsPlugin()
] ]