Remove v-if prop anti-pattern, clean up, add reader mode

This commit is contained in:
Andrew Illarionov 2024-02-18 14:36:50 +03:00
parent 3853e8fa20
commit 9386c1d158
21 changed files with 131 additions and 204 deletions

View File

@ -1 +1 @@
[{"D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\transitions.scss":"1","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\TransitionY.vue":"2","D:\\Software\\Development\\Websites\\enderman.ch\\index\\app.vue":"3","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\EMail.vue":"4","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\about.vue":"5","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\index.vue":"6","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\projects.vue":"7","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\social.vue":"8","D:\\Software\\Development\\Websites\\enderman.ch\\index\\layouts\\Card.vue":"9","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Options.vue":"10","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Flooter.vue":"11","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\SwipeControls.vue":"12","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Logo.vue":"13","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Route.vue":"14","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\Portal.vue":"15","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\ui\\Icon.vue":"16","D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\vuetify.scss":"17","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\[...slug].vue":"18","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\index.vue":"19","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\[slug].vue":"20"},{"size":165,"mtime":1700688268778,"hashOfConfig":"21"},{"size":1513,"mtime":1700430002840,"hashOfConfig":"21"},{"size":1550,"mtime":1700850776284,"hashOfConfig":"21"},{"size":804,"mtime":1700688952880,"hashOfConfig":"21"},{"size":2135,"mtime":1700689265787,"hashOfConfig":"21"},{"size":3117,"mtime":1700689271383,"hashOfConfig":"21"},{"size":2304,"mtime":1700689265769,"hashOfConfig":"21"},{"size":3232,"mtime":1700689265781,"hashOfConfig":"21"},{"size":1623,"mtime":1700748530633,"hashOfConfig":"21"},{"size":2187,"mtime":1706788559308,"hashOfConfig":"22"},{"size":2067,"mtime":1708180203174,"hashOfConfig":"22"},{"size":1236,"mtime":1706205683132,"hashOfConfig":"22"},{"size":1135,"mtime":1706030243608,"hashOfConfig":"22"},{"size":979,"mtime":1706003786966,"hashOfConfig":"22"},{"size":16968,"mtime":1708180277353,"hashOfConfig":"22"},{"size":935,"mtime":1706445231843,"hashOfConfig":"22"},{"size":120,"mtime":1706789613657,"hashOfConfig":"23"},{"size":1988,"mtime":1708191547889,"hashOfConfig":"22"},{"size":2560,"mtime":1708202402381,"hashOfConfig":"22"},{"size":983,"mtime":1708206997656,"hashOfConfig":"22"},"5tgxr3","1nljphs","1lk8nat"]
[{"D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\transitions.scss":"1","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\TransitionY.vue":"2","D:\\Software\\Development\\Websites\\enderman.ch\\index\\app.vue":"3","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\EMail.vue":"4","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\about.vue":"5","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\index.vue":"6","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\projects.vue":"7","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\social.vue":"8","D:\\Software\\Development\\Websites\\enderman.ch\\index\\layouts\\Card.vue":"9","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Options.vue":"10","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Flooter.vue":"11","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\SwipeControls.vue":"12","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Logo.vue":"13","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Route.vue":"14","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\Portal.vue":"15","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\ui\\Icon.vue":"16","D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\vuetify.scss":"17","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\[...slug].vue":"18","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\index.vue":"19","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\[slug].vue":"20"},{"size":165,"mtime":1700688268778,"hashOfConfig":"21"},{"size":1513,"mtime":1700430002840,"hashOfConfig":"21"},{"size":1550,"mtime":1700850776284,"hashOfConfig":"21"},{"size":804,"mtime":1700688952880,"hashOfConfig":"21"},{"size":2135,"mtime":1700689265787,"hashOfConfig":"21"},{"size":3117,"mtime":1700689271383,"hashOfConfig":"21"},{"size":2304,"mtime":1700689265769,"hashOfConfig":"21"},{"size":3232,"mtime":1700689265781,"hashOfConfig":"21"},{"size":1623,"mtime":1700748530633,"hashOfConfig":"21"},{"size":3065,"mtime":1708254606285,"hashOfConfig":"22"},{"size":1132,"mtime":1708254584095,"hashOfConfig":"22"},{"size":1269,"mtime":1708255068679,"hashOfConfig":"22"},{"size":1147,"mtime":1708248797872,"hashOfConfig":"22"},{"size":979,"mtime":1706003786966,"hashOfConfig":"22"},{"size":16968,"mtime":1708180277353,"hashOfConfig":"22"},{"size":935,"mtime":1706445231843,"hashOfConfig":"22"},{"size":120,"mtime":1706789613657,"hashOfConfig":"23"},{"size":1988,"mtime":1708191547889,"hashOfConfig":"22"},{"size":2560,"mtime":1708202402381,"hashOfConfig":"22"},{"size":983,"mtime":1708206997656,"hashOfConfig":"22"},"5tgxr3","1nljphs","1lk8nat"]

25
app.vue
View File

@ -1,6 +1,8 @@
<script setup lang="ts">
const config = useAppConfig()
const vdisp = ref(useVDisplay())
const theme = useVThemeSSR()
const { reader } = storeToRefs(usePageStore())
useHead({
titleTemplate: (chunk?) => {
@ -17,10 +19,11 @@ useHead({
>
<div
id="ender-layout"
class="d-flex flex-column align-items-center pt-sm-5 h-100"
class="p-animated d-flex flex-column align-items-center h-100"
:class="{ 'pt-sm-5': !reader }"
>
<AClientOnly appear render="xs" mode="out-in">
<SwipeControls />
<AClientOnly appear mode="out-in">
<SwipeControls v-if="vdisp.xs" />
</AClientOnly>
<NuxtLayout
@ -30,30 +33,28 @@ useHead({
<template #footer>
<AClientOnly
appear
render="xs"
mode="in-out"
enter="animate__animated animate__delay-1s animate__fadeIn"
leave="animate__animated animate__fadeOut"
>
<Flooter opaque />
<Flooter v-if="vdisp.xs" opaque />
</AClientOnly>
</template>
</NuxtLayout>
</div>
<ClientOnly>
<template #fallback> </template>
<Portal layout="#ender-layout" animate randomize fade />
</ClientOnly>
<AClientOnly
appear
render="sm-up"
mode="out-in"
enter="animate__animated animate__delay-1s animate__fadeInUpBig animate__slow"
leave="animate__animated animate__fadeOutDown"
>
<Flooter class="floaty mb-2 px-2" />
<Flooter v-if="vdisp.smAndUp" class="floaty mb-2 px-2" />
</AClientOnly>
<ClientOnly>
<template #fallback> </template>
<Portal layout="#ender-layout" animate randomize fade />
</ClientOnly>
</VThemeProvider>
</template>

View File

@ -61,6 +61,10 @@ body {
}
// Helper classes that don't exist in Bootstrap.
.mh-0 {
max-height: 100% !important;
}
.nobr {
white-space: nowrap;
}
@ -112,6 +116,14 @@ body {
word-break: keep-all;
}
.h-animated {
transition: max-height 0.3s ease;
}
.p-animated {
transition: padding 0.3s ease;
}
// Query-overridable classes.
.background {
background-color: rgb(0 0 0 / 50%);
@ -122,6 +134,10 @@ body {
min-height: 100%;
}
.separator {
color: white;
}
.dialog {
display: grid;
@ -257,6 +273,10 @@ body {
}
}
.separator {
color: black;
}
.radial-gradient {
background: radial-gradient(
circle at left,
@ -300,8 +320,6 @@ body {
bottom: 0;
right: 0;
padding: 0.5rem;
text-transform: uppercase;
background-color: rgb(153 153 255 / 10%);
@ -313,20 +331,32 @@ body {
}
&-thumb {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
> img {
width: 100%;
height: 280px;
height: 100%;
max-height: 280px;
object-fit: cover;
border-radius: 4px;
}
}
&-tags {
> span {
padding: 0.35em 0.65em;
font-size: 0.85em;
background-color: rgb(153 153 255 / 10%);
border: 1px solid rgb(153 153 255 / 60%);
border-radius: 1rem;
}
}
&-details {
}
&-preamble {
&-thumb {
max-width: 800px;
@ -334,12 +364,18 @@ body {
}
&-content {
:is(h1, h2, h3, h4, h5, h6) {
font-family: Alexandria, sans-serif;
font-weight: 700;
}
a {
@extend %link;
}
padding-left: 0.5rem;
img {
max-height: 400px;
}
}
}
@ -390,10 +426,10 @@ body {
&-thumb {
min-width: 352px;
max-height: 198px;
> img {
max-width: 352px;
height: 198px;
}
}

View File

@ -1,73 +0,0 @@
<script setup>
console.log('Transition started')
function enter(el, done) {
const width = getComputedStyle(el).width
console.log('@enter')
el.style.width = width
el.style.position = 'absolute'
el.style.visibility = 'hidden'
el.style.height = 'auto'
const height = getComputedStyle(el).height
el.style.width = ''
el.style.position = ''
el.style.visibility = ''
el.style.height = '0'
// Force repaint to make sure the
// animation is triggered correctly.
getComputedStyle(el).height
// Trigger the animation.
// We use `requestAnimationFrame` because we need
// to make sure the browser has finished
// painting after setting the `height`
// to `0` in the line above.
requestAnimationFrame(() => {
el.style.height = height
})
done()
}
function afterEnter(element) {
console.log('@after-enter')
element.style.height = 'auto'
}
function leave(element) {
console.log('@leave')
const height = getComputedStyle(element).height
element.style.height = height
// Force repaint to make sure the
// animation is triggered correctly.
getComputedStyle(element).height
requestAnimationFrame(() => {
element.style.height = '0'
})
}
</script>
<template>
<Transition
name="expand"
@enter="enter"
@after-enter="afterEnter"
@leave="leave"
>
<slot />
</Transition>
</template>
<style scoped lang="scss">
* {
will-change: height;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
</style>

View File

@ -2,35 +2,12 @@
import type { PropType } from 'vue'
type ModeOptions = 'in-out' | 'out-in' | 'default' | undefined
type RenderOptions =
| 'never'
| 'always'
| 'xs'
| 'sm'
| 'sm-down'
| 'sm-up'
| 'md'
| 'md-down'
| 'md-up'
| 'lg'
| 'lg-down'
| 'lg-up'
| 'xl'
| 'xl-down'
| 'xl-up'
| 'xxl'
| undefined
const vdisp = ref(useVDisplay())
const props = defineProps({
fallback: {
type: String,
default: '',
},
render: {
type: String as PropType<RenderOptions>,
default: 'always',
},
appear: {
type: Boolean,
default: false,
@ -48,45 +25,6 @@ const props = defineProps({
default: 'animate__fadeOut',
},
})
const render = computed(() => {
switch (props.render) {
case 'never':
return false
case 'always':
return true
case 'xs':
return vdisp.value.xs
case 'sm':
return vdisp.value.sm
case 'sm-down':
return vdisp.value.smAndDown
case 'sm-up':
return vdisp.value.smAndUp
case 'md':
return vdisp.value.md
case 'md-down':
return vdisp.value.mdAndDown
case 'md-up':
return vdisp.value.mdAndUp
case 'lg':
return vdisp.value.lg
case 'lg-down':
return vdisp.value.lgAndDown
case 'lg-up':
return vdisp.value.lgAndUp
case 'xl':
return vdisp.value.xl
case 'xl-down':
return vdisp.value.xlAndDown
case 'xl-up':
return vdisp.value.xlAndUp
case 'xxl':
return vdisp.value.xxl
default:
return false
}
})
</script>
<template>
@ -99,7 +37,7 @@ const render = computed(() => {
:enter-active-class="`animate__animated ${props.enter}`"
:leave-active-class="`animate__animated ${props.leave}`"
>
<slot v-if="render" />
<slot />
</Transition>
</ClientOnly>
</template>

View File

@ -4,14 +4,7 @@ const props = defineProps({
})
const config = useAppConfig()
const fqdn = config.url.split('//')[1]
const currentYear = new Date().getFullYear()
const mailTemplate = `I've just found a bug on ${config.url} and would like to report it.%0D%0A%0D%0AWebsite version: ${
config.build.version
}%0D%0ABuild date: ${
config.build.date
}%0D%0ATimestamp: ${new Date().toISOString()}%0D%0A%0D%0A%0D%0ASteps to reproduce:%0D%0A{ Explain in detail what happened here, or attach a video/screenshot }%0D%0A%0D%0AAdditional information:%0D%0A{ Helpful information, such as developer console output / Leave empty if none }%0D%0A%0D%0A%0D%0A// Keep in mind that it's just a template, you can change it as you wish! :)`
</script>
<template>
@ -25,19 +18,6 @@ const mailTemplate = `I've just found a bug on ${config.url} and would like to r
'text-shadow': !props.opaque,
}"
>
<EMail
:class="{
'link-hi-force-dark': !props.opaque,
'link-hi': props.opaque,
}"
:address="`contact@${fqdn}`"
:cc="`admin@${fqdn}`"
:subject="`Bug report: ${fqdn}`"
:body="mailTemplate"
>
<strong>Report a bug</strong>
</EMail>
<br />
© 2018-{{ currentYear }},
<a
:class="{

View File

@ -43,7 +43,7 @@ const props = defineProps({
/>
<div>
<h1 class="alex mb-0">{{ props.title }}</h1>
<Separator />
<Separator class="m-0" />
<p class="mb-0">{{ props.description }}</p>
</div>
</NuxtLink>

View File

@ -3,6 +3,14 @@ import cogIcon from '~/assets/images/icons/cog.png'
import pearlIcon from '~/assets/images/icons/pearl.gif'
const theme = useVThemeSSR()
const config = useAppConfig()
const fqdn = config.url.split('//')[1]
const mailTemplate = `I've just found a bug on ${config.url} and would like to report it.%0D%0A%0D%0AWebsite version: ${
config.build.version
}%0D%0ABuild date: ${
config.build.date
}%0D%0ATimestamp: ${new Date().toISOString()}%0D%0A%0D%0A%0D%0ASteps to reproduce:%0D%0A{ Explain in detail what happened here, or attach a video/screenshot }%0D%0A%0D%0AAdditional information:%0D%0A{ Helpful information, such as developer console output / Leave empty if none }%0D%0A%0D%0A%0D%0A// Keep in mind that it's just a template, you can change it as you wish! :)`
</script>
<template>
@ -48,7 +56,15 @@ const theme = useVThemeSSR()
</div>
</div>
</div>
Report a bug
<EMail
class="link-hi"
:address="`contact@${fqdn}`"
:cc="`admin@${fqdn}`"
:subject="`Bug report: ${fqdn}`"
:body="mailTemplate"
>
<strong>Report a bug</strong>
</EMail>
</VCardText>
<VCardActions>
<VSpacer></VSpacer>

View File

@ -13,7 +13,7 @@ const props = defineProps({
<template>
<hr
class="radial-gradient m-0 border-0 w-100"
class="separator radial-gradient border-0"
:style="{ height: props.height + 'px' }"
/>
</template>

View File

@ -8,7 +8,9 @@ const { pages } = storeToRefs(pageStore)
const route = useRoute()
const state = computed(() => {
const i = pages.value.indexOf(
pages.value.find((page) => page.path === route.fullPath)!,
pages.value.find(
(page) => page.path === '/' + route.fullPath.split('/')[1],
)!,
)
return {

View File

@ -1 +0,0 @@
<template></template>

View File

@ -1 +0,0 @@
<template></template>

View File

@ -1 +0,0 @@
<template></template>

View File

@ -4,7 +4,7 @@ description: We dive in deep into the Windows context menu and how to customize
created: 2023-08-13T15:51:19Z
updated: 2024-02-18T21:32:12Z
draft: false
tags: ['windows', 'context-menu']
tags: ['windows', 'context-menu', 'customization', 'registry', 'shell', 'shell-extensions', 'context-menu-handlers']
thumbnail: '/images/blog/thumbnails/the-windows-context-menu.png'
---

View File

@ -8,8 +8,8 @@ const router = useRouter()
const route = useRoute()
const card = ref<HTMLElement | null>(null)
const pageStore = usePageStore()
const { pages } = storeToRefs(pageStore)
const { pages, reader } = storeToRefs(usePageStore())
const swipe = useSwipe(card, {
passive: true,
onSwipeEnd: (_touch: TouchEvent, _direction: UseSwipeDirection) => {
@ -41,7 +41,11 @@ const swipe = useSwipe(card, {
<template>
<main
ref="card"
class="dimensions background rounded-1-sm overflow-auto d-flex flex-column gap-3 gap-sm-2 px-4 pt-4 pb-3"
class="dimensions background h-animated overflow-auto d-flex flex-column gap-3 gap-sm-2 px-4 pt-4 pb-3"
:class="{
'rounded-1-sm': !reader,
'mh-0': reader,
}"
>
<Options />
<Navigation />

View File

@ -0,0 +1,5 @@
export default defineNuxtRouteMiddleware((to, from) => {
const { reader } = storeToRefs(usePageStore())
reader.value = /\/blog\/(.*)/.test(to.path)
})

View File

@ -43,14 +43,14 @@ useHead({
<template>
<section>
<h3 class="alex">About me</h3>
<hr />
<Separator />
<p>
<strong>🚧 This page is currently under construction.</strong> Expect a
lot more content to be added as time passes.
<em>Please report all bugs you encounter via the link in the footer</em>,
I will make sure to sand them down.
</p>
<hr />
<Separator />
<p>
Nice to meet you! I'm Andrew, a {{ age }}-year-old guy from Kaluga,
Russia. I have been developing software since I was 10, and I have always

View File

@ -32,20 +32,36 @@ import { formatDate } from 'date-fns'
class="rounded-4"
/>
</div>
<div>
<div class="w-100">
<h3 class="post-title alex">{{ post.title }}</h3>
<p class="post-description">{{ post.description }}</p>
<p class="post-description mb-0">{{ post.description }}</p>
<div class="row">
<div class="col-6 d-flex flex-row gap-1 align-items-center">
<div class="post-tags d-flex flex-row flex-wrap gap-2 py-2">
<span
v-for="(tag, index) in post.tags.slice(0, 3)"
:key="index"
class="nobr"
>
{{ tag }}
</span>
<span v-if="post.tags.length > 3" class="nobr">
{{ post.tags.length - 3 }} more
</span>
</div>
<Separator class="m-0" />
<div class="post-details py-2">
<div class="d-flex flex-row gap-1 align-items-center">
<iconify-icon icon="mdi:calendar" />
<small class="nobr">
{{ formatDate(post.created, 'LLLL do, y &ndash; HH:mm') }}
</small>
</div>
</div>
<div v-if="post.updated" class="row">
<div class="col-12 d-flex flex-row gap-1 align-items-center">
<div
v-if="post.updated"
class="d-flex flex-row gap-1 align-items-center"
>
<iconify-icon icon="mdi:pencil" />
<small class="nobr">
{{ formatDate(post.updated, 'LLLL do, y &ndash; HH:mm') }}
@ -54,7 +70,9 @@ import { formatDate } from 'date-fns'
</div>
</div>
<div class="post-pocket d-flex flex-row gap-1 align-items-center">
<div
class="post-pocket d-flex flex-row gap-1 align-items-center p-2"
>
<iconify-icon icon="mdi:clock-outline" />
<small class="nobr font-monospace font-small">
{{ post.readingTime.text }}

View File

@ -69,14 +69,14 @@ useHead({
<template>
<section>
<h3 class="alex">Projects</h3>
<hr />
<Separator />
<p>
<strong>🚧 This page is currently under construction.</strong> Expect a
lot more content to be added as time passes.
<em>Please report all bugs you encounter via the link in the footer</em>,
I will make sure to sand them down.
</p>
<hr />
<Separator />
<p><strong>My current projects are:</strong></p>
<ul>
<li v-for="(item, index) in projects" :key="index">

View File

@ -117,14 +117,14 @@ useHead({
<template>
<section>
<h3 class="alex">Online presence</h3>
<hr />
<Separator />
<p>
<strong>🚧 This page is currently under construction.</strong> Expect a
lot more content to be added as time passes.
<em>Please report all bugs you encounter via the link in the footer</em>,
I will make sure to sand them down.
</p>
<hr />
<Separator />
<div class="row">
<div class="col-12 col-md-3">
<h5 class="alex">Social media</h5>

View File

@ -50,6 +50,8 @@ export const usePageStore = defineStore('page', () => {
},
])
const reader = ref(false)
function _autoFetchPages() {
while (pages.value.length) pages.value.pop()
@ -68,5 +70,6 @@ export const usePageStore = defineStore('page', () => {
description,
keywords,
pages,
reader,
}
})