Rewrite to TailwindCSS, enhance the DOM, uninstall Vuetify, improve styles

This commit is contained in:
2024-06-16 22:24:01 +03:00
parent 9339416c1b
commit e72a61b317
50 changed files with 1843 additions and 3459 deletions
+96 -71
View File
@@ -1,15 +1,16 @@
<script setup lang="ts">
import { formatDate } from 'date-fns'
import { useAsyncData } from '#app'
import chestAnimation from '~/assets/images/chest.webp'
const config = useAppConfig()
const route = useRoute()
let thumbnail: string | null = null
let slug = useRoute().params.slug
let slug = route.params.slug
if (Array.isArray(slug)) slug = slug.join('/')
const { data } = await useAsyncData('home', () =>
const { data, status } = await useAsyncData('home', () =>
queryContent(`/blog/${slug}`).findOne(),
)
@@ -58,55 +59,55 @@ if (data.value) {
)
// Hydrate the rendered items.
onMounted(() => {
document.querySelectorAll('pre').forEach((pre) => {
const icon = document.createElement('iconify-icon')
icon.setAttribute('icon', 'mdi:content-copy')
icon.setAttribute('inline', 'true')
icon.classList.add('button')
pre.appendChild(icon)
})
document
.querySelectorAll('code:not(pre *), pre > iconify-icon.button')
.forEach((code) => {
if (code instanceof HTMLElement) {
code.onclick = () => {
const area = document.createElement('textarea')
area.textContent =
code.nodeName && code.nodeName.toLowerCase() === 'code'
? code.textContent
: code.parentElement!.textContent
// It's necessary to create the textarea element every time you copy to get access to the select() method.
area.setSelectionRange(0, 99999) // An iOS gotcha.
area.select()
// Copy the text inside the textarea.
navigator.clipboard.writeText(area.value)
// TODO: Alert the user text has been successfully copied.
// Remove the textarea element.
area.remove()
}
}
})
})
onUnmounted(() => {
document.querySelectorAll('code').forEach((code) => {
code.onclick = null
})
document.querySelectorAll('pre').forEach((pre) => {
pre.querySelector('.clipboard')?.remove()
})
})
// onMounted(() => {
// document.querySelectorAll('pre').forEach((pre) => {
// const icon = document.createElement('iconify-icon')
//
// icon.setAttribute('icon', 'mdi:content-copy')
// icon.setAttribute('inline', 'true')
//
// icon.classList.add('button')
//
// pre.appendChild(icon)
// })
//
// document
// .querySelectorAll('code:not(pre *), pre > iconify-icon.button')
// .forEach((code) => {
// if (code instanceof HTMLElement) {
// code.onclick = () => {
// const area = document.createElement('textarea')
//
// area.textContent =
// code.nodeName && code.nodeName.toLowerCase() === 'code'
// ? code.textContent
// : code.parentElement!.textContent
//
// // It's necessary to create the textarea element every time you copy to get access to the select() method.
// area.setSelectionRange(0, 99999) // An iOS gotcha.
// area.select()
//
// // Copy the text inside the textarea.
// navigator.clipboard.writeText(area.value)
//
// // TODO: Alert the user text has been successfully copied.
//
// // Remove the textarea element.
// area.remove()
// }
// }
// })
// })
//
// onUnmounted(() => {
// document.querySelectorAll('code').forEach((code) => {
// code.onclick = null
// })
//
// document.querySelectorAll('pre').forEach((pre) => {
// pre.querySelector('.clipboard')?.remove()
// })
// })
}
useHead({
@@ -125,44 +126,68 @@ useHead({
</script>
<template>
<div
v-if="status === 'pending'"
class="flex flex-col items-center justify-center gap-4 w-full select-none text-center"
>
<img draggable="false" :src="chestAnimation" alt="The Ender Chest" />
<div>
<h1 class="font-enchant">Loading document...</h1>
<span class="opacity-0 hover:opacity-100 transition-ease">
<em>Loading document...</em>
</span>
</div>
</div>
<article
v-if="data"
class="post scrollbar fade-mask-sm flex flex-col gap-3 flex-grow-1 overflow-x-hidden overflow-y-auto sm:py-4 sm:pe-3"
v-else-if="status === 'success' && data"
class="flex-grow post fade-mask-sm flex flex-col gap-4 overflow-x-hidden overflow-y-auto sm:py-4 sm:pe-4"
>
<div
class="post-preamble"
class="relative flex flex-col justify-end items-start w-full min-h-[400px] rounded-xl accent-text-shadow post-preamble"
:style="{ backgroundImage: 'url(' + thumbnail + ')' }"
>
<div class="p-2">
<h3 class="alex">{{ data.title }}</h3>
<div class="flex flex-row gap-x-0 gap-y-2 flex-wrap">
<div class="flex flex-row items-center gap-1">
<h3>{{ data.title }}</h3>
<div class="flex flex-row flex-wrap gap-x-2 gap-y-0">
<div class="flex flex-row items-center gap-2">
<iconify-icon icon="mdi:calendar" />
<strong class="nobr font-small">
{{ formatDate(data.created, 'LLLL do, y &ndash; HH:mm') }}
</strong>
<small class="whitespace-nowrap">
<strong>
{{ formatDate(data.created, 'LLLL do, y &ndash; HH:mm') }}
</strong>
</small>
</div>
<div class="flex flex-row items-center gap-1">
<div class="flex flex-row items-center gap-2">
<iconify-icon icon="mdi:clock-outline" />
<strong class="nobr font-small">
{{ data!.readingTime.text.split(' ')[0] + ' minutes to read' }}
</strong>
<small class="whitespace-nowrap">
<strong>
{{ data!.readingTime.text.split(' ')[0] + ' minutes to read' }}
</strong>
</small>
</div>
</div>
</div>
<NuxtLink
to="/blog"
class="absolute top-0 left-0 p-2 text-inherit no-underline"
>
<iconify-icon
icon="icon-park-solid:back"
width="2em"
height="2em"
style="color: lavender"
/>
</NuxtLink>
<NuxtLink
:href="`https://twitter.com/share?url=${config.url}/blog/${slug}&text=${data.title}&hashtags=${data.tags.slice(0, 3).join(',').replace(/ /g, '')}`"
target="_blank"
class="link post-preamble-share px-2 py-1"
class="absolute top-0 right-0 p-2"
>
<iconify-icon icon="mdi:twitter"></iconify-icon>
</NuxtLink>
<NuxtLink to="/blog" class="link post-preamble-control px-2 py-1">
<iconify-icon icon="icon-park-solid:back"></iconify-icon>
<iconify-icon icon="logos:twitter" width="2em" height="2em" />
</NuxtLink>
</div>
<div class="post-content">
<hr class="accent accent-gradient border-0 h-px" />
<hr class="accent-text accent-gradient border-0 h-px" />
<ContentRenderer :value="data" />
</div>
</article>
+23 -23
View File
@@ -3,7 +3,7 @@ import { formatDate } from 'date-fns'
const config = useAppConfig()
const meta = {
title: 'The Enderchest',
title: 'The Ender Chest',
description:
'A blog about software development, scientific research and Windows quirks.',
image: `${config.url}/images/chest.png`,
@@ -25,7 +25,7 @@ useSeoMeta({
})
useHead({
title: 'The Enderchest',
title: 'The Ender Chest',
htmlAttrs: {
lang: config.locale || 'en',
},
@@ -41,12 +41,12 @@ useHead({
<template>
<section>
<h3 class="alex">The Enderchest</h3>
<p>
You're browsing the enderchest — a blog about software development,
<h3 class="mb-2">The Ender Chest</h3>
<p class="mb-4">
You're browsing the Ender Chest — a blog about software development,
scientific research and Windows quirks.
</p>
<h4 class="alex">Recent posts</h4>
<h4 class="mb-2">Recent posts</h4>
<ContentList
:query="{
path: '/blog',
@@ -56,15 +56,15 @@ useHead({
}"
>
<template #default="{ list }">
<div class="grid gap-3">
<div class="grid gap-4">
<NuxtLink
v-for="post in list"
:key="post._path"
:to="post._path"
class="no-decoration"
class="text-inherit hover:text-inherit no-underline"
>
<article
class="post-box flex flex-col md:flex-row gap-3 rounded-xl h-full"
class="post-box flex flex-col md:flex-row gap-4 rounded-xl h-full"
>
<div class="post-thumb">
<img
@@ -78,46 +78,48 @@ useHead({
/>
</div>
<div class="w-full">
<h3 class="post-title alex">{{ post.title }}</h3>
<h3 class="post-title">{{ post.title }}</h3>
<p class="post-description mb-0">{{ post.description }}</p>
<div class="post-tags flex flex-row flex-wrap gap-2 py-2">
<span
v-for="(tag, index) in post.tags.slice(0, 3)"
:key="index"
class="nobr"
class="whitespace-nowrap"
>
{{ tag }}
</span>
<span v-if="post.tags.length > 3" class="nobr">
<span v-if="post.tags.length > 3" class="whitespace-nowrap">
{{ post.tags.length - 3 }} more
</span>
</div>
<hr class="accent accent-gradient border-0 h-px" />
<hr class="accent-text accent-gradient border-0 h-px" />
<div class="post-details py-2">
<div class="flex flex-row items-center gap-1">
<div class="flex flex-row items-center gap-2">
<iconify-icon icon="mdi:calendar" />
<small class="nobr">
<small class="whitespace-nowrap">
{{ formatDate(post.created, 'LLLL do, y &ndash; HH:mm') }}
</small>
</div>
<div
v-if="post.updated"
class="flex flex-row items-center gap-1"
class="flex flex-row items-center gap-2"
>
<iconify-icon icon="mdi:pencil" />
<small class="nobr">
<small class="whitespace-nowrap">
{{ formatDate(post.updated, 'LLLL do, y &ndash; HH:mm') }}
</small>
</div>
</div>
</div>
<div class="post-pocket flex flex-row items-center gap-1 p-2">
<div
class="post-pocket absolute bottom-0 right-0 uppercase rounded-tl-xl flex flex-row items-center gap-2 p-2"
>
<iconify-icon icon="mdi:clock-outline" />
<small class="nobr font-monospace font-small">
<small class="whitespace-nowrap font-mono font-small">
{{ post.readingTime.text }}
</small>
</div>
@@ -127,13 +129,11 @@ useHead({
</template>
<template #not-found>
<div class="flex flex-col justify-center items-center gap-3">
<div class="flex flex-col justify-center items-center gap-4">
<span>No posts found 🙁</span>
<NuxtLink to="/" class="link-hi">Back to index</NuxtLink>
<NuxtLink to="/">Back to index</NuxtLink>
</div>
</template>
</ContentList>
</section>
</template>
<style scoped lang="scss"></style>