Fix light theme, fix animation bugs, add programmatic scrolling, improve custom card blog component

This commit is contained in:
Andrew Illarionov 2024-06-19 18:06:17 +03:00
parent 3e39bda014
commit 2204709c0e
12 changed files with 313 additions and 222 deletions

View File

@ -56,6 +56,6 @@ useHead({
</div>
<ClientOnly>
<LazyPortal v-model="animate" layout="#ender-layout" randomize fade />
<Portal v-model="animate" layout="#ender-layout" randomize fade />
</ClientOnly>
</template>

View File

@ -18,19 +18,19 @@
.list-style-type {
&-none {
list-style-type: none;
list-style-type: none !important;
}
&-do {
list-style-type: do;
list-style-type: do !important;
}
&-enjoy {
list-style-type: enjoy;
list-style-type: enjoy !important;
}
&-faq {
list-style-type: faq;
list-style-type: faq !important;
> li:nth-child(2n):not(:last-child) {
@apply mb-2;

View File

@ -40,6 +40,7 @@ html {
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
@ -52,13 +53,21 @@ html {
background-color: rgb(255 255 255 / 15%);
}
::-webkit-scrollbar-corner {
border-radius: 3px;
background-color: rgb(255 255 255 / 15%);
}
&.light {
::-webkit-scrollbar-track {
background-color: rgb(255 255 255 / 15%);
}
::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: rgb(0 0 0 / 30%);
}
::-webkit-scrollbar-corner {
background-color: rgb(0 0 0 / 30%);
}
}
@ -137,12 +146,19 @@ body {
margin: auto;
}
:not(nav) > ul {
list-style-type: disc;
padding-left: 1.5em;
:not(nav) {
> ul {
@apply ps-6;
list-style-type: disc;
}
> ol {
@apply ps-6;
list-style-type: decimal;
}
}
:is(section, article, aside).page {
:where(section, article, aside).page {
:is(div, p):not(:last-child) {
@apply mb-4;
}
@ -155,30 +171,37 @@ body {
color: indianred;
}
}
&:not(:last-child) {
@apply mb-2;
}
}
:is(ul, ol):not(:last-child) {
@apply mb-4;
ul {
list-style-type: disc;
}
:where(p, li) {
code {
&:hover {
background-color: rgb(138 71 245 / 20%);
}
ol {
list-style-type: decimal;
}
padding: 0.25em;
:where(ul, ol) {
&:not(:last-child) {
@apply mb-4;
}
li:not(:last-child) {
@apply mb-1;
}
}
border-radius: 0.5em;
code:not(pre *) {
@apply px-1 py-0.5 break-words cursor-pointer rounded-lg transition-ease;
cursor: pointer;
background-color: rgb(83 35 162 / 10%);
background-color: rgb(83 35 162 / 10%);
transition: 0.3s ease;
&:hover {
background-color: rgb(138 71 245 / 20%);
}
}
@ -187,56 +210,49 @@ body {
}
blockquote {
padding: 0.75em 1em;
border-left: 0.25em solid rgb(153 153 255 / 60%);
border-top-right-radius: 0.5em;
border-bottom-right-radius: 0.5em;
@apply px-4 py-3 mb-4 border-l-4 rounded-r-xl transition-ease;
border-left-color: rgb(153 153 255 / 60%);
background-color: rgb(153 153 255 / 10%);
transition: 0.3s ease;
&:hover {
border-left-color: rgb(153 153 255 / 100%);
background-color: rgb(153 153 255 / 15%);
}
:last-child {
margin-bottom: 0;
}
}
pre {
font-size: 14px;
padding: 1em;
@apply relative font-small overflow-auto min-h-14 max-h-[80rem] rounded-xl p-2 mb-4;
border-radius: 0.5em;
background-color: rgb(83 35 162 / 5%);
position: relative;
overflow-x: auto;
code {
&:hover > .absolute {
@apply opacity-100;
}
> code {
@apply scroll-p-2;
counter-reset: step;
counter-increment: step 0;
scroll-padding: 0.5em;
.line::before {
@apply inline-block w-4 mr-6 text-right;
content: counter(step);
counter-increment: step;
display: inline-block;
text-align: right;
color: rgb(238 246 250 / 40%);
width: 1rem;
margin-right: 1.5rem;
}
> span span:last-child {
@apply me-4;
}
}
.overlay-action {
background-color: rgb(153 153 255 / 10%);
border-color: rgb(153 153 255 / 60%);
}
}
}
@ -480,6 +496,41 @@ rt {
border-color: rgb(123 123 255 / 80%);
}
}
:is(section, article, aside).page {
code:not(pre *) {
background-color: rgb(138 71 245 / 20%);
&:hover {
background-color: rgb(83 35 162 / 20%);
}
}
blockquote {
border-left-color: rgb(153 153 255 / 60%);
background-color: rgb(153 153 255 / 10%);
&:hover {
border-left-color: rgb(153 153 255 / 100%);
background-color: rgb(153 153 255 / 15%);
}
}
pre {
background-color: rgb(83 35 162 / 10%);
code {
.line::before {
color: rgb(83 85 89 / 40%);
}
}
.overlay-action {
background-color: rgb(153 153 255 / 30%);
border-color: rgb(153 153 255 / 80%);
}
}
}
}
// Dynamic classes.

View File

@ -11,6 +11,18 @@
}
}
.switch {
&-enter-active,
&-leave-active {
transition: all 0.15s ease-in-out;
}
&-enter-from,
&-leave-to {
opacity: 0;
}
}
.transition {
&-ease {
transition: all 0.3s ease;

View File

@ -11,7 +11,7 @@ import ideaIcon from '~/assets/images/icons/accent/idea.png'
type IconOptions = 'warning' | 'error' | 'info' | 'help' | 'checkmark' | 'idea'
const props = defineProps({
icon: {
type: {
type: String as PropType<IconOptions>,
default: 'warning',
},
@ -21,42 +21,49 @@ const props = defineProps({
},
})
let cardIcon = warningIcon
let color = 'rgb(255 246 147 / 60%)'
switch (props.icon) {
case 'error':
cardIcon = errorIcon
color = 'rgb(255 113 113 / 60%)'
break
case 'info':
cardIcon = infoIcon
color = 'rgb(94 162 255 / 60%)'
break
case 'help':
cardIcon = helpIcon
color = 'rgb(178 104 255 / 60%)'
break
case 'checkmark':
cardIcon = checkIcon
color = 'rgb(69 255 255 / 60%)'
break
case 'idea':
cardIcon = ideaIcon
color = 'rgb(255 255 255 / 60%)'
break
case 'warning':
default:
break
const cards = {
warning: {
color: 'border-warning',
icon: {
src: warningIcon,
alt: 'Warning triangle',
},
},
error: {
color: 'border-error',
icon: {
src: errorIcon,
alt: 'Error circle',
},
},
info: {
color: 'border-info',
icon: {
src: infoIcon,
alt: 'Information circle',
},
},
help: {
color: 'border-help',
icon: {
src: helpIcon,
alt: 'Help circle',
},
},
checkmark: {
color: 'border-checkmark',
icon: {
src: checkIcon,
alt: 'Checkmark',
},
},
idea: {
color: 'border-idea',
icon: {
src: ideaIcon,
alt: 'Lightbulb',
},
},
}
</script>
@ -64,13 +71,15 @@ switch (props.icon) {
<div class="tilt flex flex-row items-center gap-2 mb-0 sm:mb-2 md:mb-4">
<img
draggable="false"
:src="cardIcon"
:alt="`Download`"
class="icon-image"
:src="cards[props.type].icon.src"
:alt="cards[props.type].icon.alt"
class="w-12 h-12"
/>
<h3 class="whitespace-nowrap mb-0">{{ props.title }}</h3>
</div>
<div class="box p-4 rounded-xl mb-4">
<div
:class="`box p-4 mb-4 ${cards[props.type].color} border-b border-r rounded-xl`"
>
<slot />
</div>
</template>
@ -79,22 +88,4 @@ switch (props.icon) {
.tilt {
transform: rotate(-2deg);
}
.box {
> p {
margin-bottom: 0;
}
margin-bottom: 1rem;
border-bottom: 1px ridge;
border-right: 1px ridge;
border-color: v-bind(color);
}
.icon {
&-image {
width: 48px;
height: 48px;
}
}
</style>

View File

@ -1,32 +1,35 @@
<script setup lang="ts"></script>
<script setup lang="ts">
const emit = defineEmits(['copy'])
const clicked = ref<boolean>(false)
function p() {
emit('copy')
if (!clicked.value) setTimeout(() => (clicked.value = false), 2000)
clicked.value = true
}
</script>
<template>
<div
class="absolute flex flex-col justify-start top-0 left-0 w-full h-full pointer-events-none"
class="absolute flex flex-col justify-start top-0 left-0 w-full h-full pointer-events-none opacity-0 hover:opacity-100 transition-ease"
>
<div class="flex flex-row justify-end items-center p-2">
<iconify-icon icon="mdi:content-copy" inline class="button" />
<span
class="overlay-action flex-grow-0 pointer-events-auto cursor-pointer p-2 border rounded-xl"
@click="p"
>
<Transition name="switch" mode="out-in">
<iconify-icon v-if="!clicked" icon="mdi:content-copy" inline />
<iconify-icon
v-else
icon="mdi:check-bold"
class="text-green-500"
inline
/>
</Transition>
</span>
</div>
</div>
</template>
<style scoped lang="scss">
.button {
@apply flex-grow-0;
pointer-events: auto;
padding: 0.5em;
cursor: pointer;
background-color: rgb(153 153 255 / 10%);
border: 1px solid rgb(153 153 255 / 60%);
border-radius: 0.5em;
opacity: 0.5;
transition: 0.3s ease;
}
</style>

View File

@ -48,7 +48,7 @@ and you are as much of a perfectionist as I am, buckle up and prepare for severa
::card
---
icon: warning
type: warning
title: Before we begin
---
Invoking `sfc /scannow` or `dism /restorehealth` will likely interfere with your context menu,
@ -141,7 +141,7 @@ Children of the `Shell` subkey are the «shell entries» — locally provided cu
::card
---
icon: info
type: info
title: Clearing up the confusion
---
The terminology may be a little confusing, since Microsoft hasn't ever offered a standard nomenclature for the
@ -195,7 +195,7 @@ with an exception to the following two macros:
::card
---
icon: error
type: error
title: Inconsistency
---
Keep in mind that the `%1` macro is the Batch version of `argv[1]` — the first item in the C argument vector.
@ -316,7 +316,7 @@ shell entry **do not seem to correspond**.
::card
---
icon: idea
type: idea
title: Tip
---
You can use the Explorer folder icon picker to acquire the icon DLL and visually calculate the offset
@ -669,7 +669,7 @@ The following are fairly common areas Winaero Tweaker doesn't cover.
::card
---
icon: idea
type: idea
title: Tip
---
As a rule of thumb, whenever you're dealing with existing shell entries, I _really_ suggest

View File

@ -53,17 +53,18 @@ onMounted(() => {
class="dimensions accent-background transition-ease overflow-auto flex flex-col gap-4 lm:gap-0 sm:gap-2 py-2 px-6 sm:p-4"
:class="{
'animate__animated-sm animate__delay-1-5s animate__fadeInDown':
!animationComplete,
!reader && !animationComplete,
'animate__animated-sm animate__fadeIn': reader && !animationComplete,
'lm:rounded-none sm:rounded-xl sm:mt-8 lm:mt-0 lt:mt-0': !reader,
'!max-h-full h-full': reader,
}"
>
<Settings v-if="animationComplete" />
<Settings />
<Navigation />
<slot name="header" />
<NuxtPage
class="sm:fade-mask flex-grow overflow-y-auto h-full sm:py-4 sm:pe-4"
/>
<slot v-if="animationComplete" name="footer" />
<slot name="footer" />
</main>
</template>

View File

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

View File

@ -1,35 +1,35 @@
import config from "./config";
import config from './config'
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
app: {
head: {
htmlAttrs: {
lang: config.locale || "en",
lang: config.locale || 'en',
},
link: [
{
rel: "icon",
type: "image/x-icon",
href: "/favicon.ico",
rel: 'icon',
type: 'image/x-icon',
href: '/favicon.ico',
},
],
meta: [
{
charset: "utf-8",
charset: 'utf-8',
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
{
"http-equiv": "X-UA-Compatible",
content: "ie=edge",
'http-equiv': 'X-UA-Compatible',
content: 'ie=edge',
},
],
},
pageTransition: { name: "page", mode: "out-in" },
rootId: "ender-app",
pageTransition: { name: 'page', mode: 'out-in' },
rootId: 'ender-app',
},
devtools: {
enabled: true,
@ -43,64 +43,68 @@ export default defineNuxtConfig({
components: {
dirs: [
{
path: "~/components",
path: '~/components',
pathPrefix: false,
extensions: [".vue"],
extensions: ['.vue'],
},
],
},
css: ["~/assets/styles/main.scss"],
css: ['~/assets/styles/main.scss'],
plugins: [],
modules: [
"@pinia/nuxt",
"@nuxt/content",
"@nuxtjs/seo",
"@nuxtjs/google-fonts",
"@nuxtjs/tailwindcss",
"@nuxtjs/color-mode",
"@nuxt/eslint",
["@nuxtjs/stylelint-module", { failOnError: true, lintOnStart: false }],
'@pinia/nuxt',
'@nuxt/content',
'@nuxtjs/seo',
'@nuxtjs/google-fonts',
'@nuxtjs/tailwindcss',
'@nuxtjs/color-mode',
'@nuxt/eslint',
['@nuxtjs/stylelint-module', { failOnError: true, lintOnStart: false }],
],
colorMode: {
preference: "system",
fallback: "dark",
classPrefix: "",
classSuffix: "",
componentName: "NuxtTheme",
storageKey: "ecmatheme",
preference: 'system',
fallback: 'dark',
classPrefix: '',
classSuffix: '',
componentName: 'NuxtTheme',
storageKey: 'ecmatheme',
},
content: {
markdown: {
remarkPlugins: ["remark-reading-time"],
remarkPlugins: ['remark-reading-time'],
},
highlight: {
theme: "github-dark",
theme: {
default: 'github-dark',
light: 'github-light',
sepia: 'monokai',
},
langs: [
"shell",
"batch",
"vb",
"ini",
"asm",
"c",
"cpp",
"java",
"python",
"csv",
"xml",
"json",
"yaml",
"html",
"css",
"sass",
"php",
"js",
"ts",
"vue",
"md",
"mdc",
"pascal",
"lisp",
"sql",
'shell',
'batch',
'vb',
'ini',
'asm',
'c',
'cpp',
'java',
'python',
'csv',
'xml',
'json',
'yaml',
'html',
'css',
'sass',
'php',
'js',
'ts',
'vue',
'md',
'mdc',
'pascal',
'lisp',
'sql',
],
},
},
@ -109,7 +113,7 @@ export default defineNuxtConfig({
crawlLinks: true,
autoSubfolderIndex: true,
failOnError: true,
routes: ["/robots.txt", "/sitemap.xml"],
routes: ['/robots.txt', '/sitemap.xml'],
},
},
googleFonts: {
@ -160,22 +164,22 @@ export default defineNuxtConfig({
// },
// },
sitemap: {
sources: ["/api/__sitemap__/content"],
sources: ['/api/__sitemap__/content'],
cacheMaxAgeSeconds: 360,
exclude: [],
credits: false,
xslColumns: [
{ label: "URL", width: "50%" },
{ label: "Last Modified", select: "sitemap:lastmod", width: "25%" },
{ label: "Priority", select: "sitemap:priority", width: "12.5%" },
{ label: 'URL', width: '50%' },
{ label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },
{ label: 'Priority', select: 'sitemap:priority', width: '12.5%' },
{
label: "Change Frequency",
select: "sitemap:changefreq",
width: "12.5%",
label: 'Change Frequency',
select: 'sitemap:changefreq',
width: '12.5%',
},
],
defaults: {
changefreq: "yearly",
changefreq: 'yearly',
priority: 0.7,
lastmod: config.build.date,
},
@ -194,7 +198,7 @@ export default defineNuxtConfig({
},
vue: {
compilerOptions: {
isCustomElement: (tag) => tag === "iconify-icon",
isCustomElement: (tag) => tag === 'iconify-icon',
},
},
});
})

View File

@ -5,6 +5,7 @@ import { render } from 'vue'
import { CodeControls } from '#components'
const config = useAppConfig()
const router = useRouter()
const route = useRoute()
let slug = route.params.slug
@ -40,14 +41,12 @@ useSeoMeta({
const clipboard = (el: HTMLElement) => {
if (el.textContent)
navigator.clipboard
.writeText(el.textContent)
.then(() => {
alert(`successfully copied ${el.textContent}`)
})
.catch(() => {
alert('something went wrong')
})
navigator.clipboard.writeText(el.textContent).catch(() => {
console.error(
'Failed to copy element data to the clipboard! Element data:',
el,
)
})
}
const content = ref<Element | null>(null)
@ -73,6 +72,11 @@ defineOgImageComponent(
// Hydrate the rendered items.
onMounted(() => {
if (route.hash)
content.value
?.querySelector(route.hash.toLowerCase())
?.scrollIntoView({ behavior: 'smooth' })
content.value
?.querySelectorAll('code:not(pre *)')
.forEach((code: Element) => {
@ -80,10 +84,27 @@ onMounted(() => {
code.addEventListener('click', () => clipboard(code))
})
content.value?.querySelectorAll('pre').forEach((pre: HTMLElement) => {
const icon = h(CodeControls)
content.value?.querySelectorAll('pre').forEach((pre: HTMLPreElement) => {
const overlay = h(CodeControls, { onCopy: () => clipboard(pre) })
render(icon, pre)
render(overlay, pre)
})
content.value?.querySelectorAll('a').forEach((a: HTMLAnchorElement) => {
if (
a.hash &&
a.host === window.location.host &&
a.pathname.toLowerCase() === route.path.toLowerCase()
)
a.addEventListener('click', (e: Event) => {
e.preventDefault()
content.value
?.querySelector(a.hash.toLowerCase())
?.scrollIntoView({ behavior: 'smooth' })
history.replaceState(history.state, '', a.hash)
})
})
})
@ -117,7 +138,7 @@ useHead({
</div>
<article
v-else-if="data"
class="flex-grow post snap-normal fade-mask-sm flex flex-col gap-4 overflow-x-hidden overflow-y-auto sm:py-4 sm:pe-4"
class="flex-grow post snap-normal fade-mask-sm flex flex-col gap-4 overflow-y-auto sm:py-4 sm:pe-4"
>
<div class="grid-thumbnail grid max-w-[768px] snap-end">
<img

View File

@ -20,6 +20,14 @@ export default <Partial<Config>>{
raw: '(max-height: 996px) and (min-width: 601px) and (max-width: 1280px)',
},
},
colors: {
warning: '#FFF693',
error: '#FF7171',
info: '#5EA2FF',
help: '#B268FF',
checkmark: '#45FFFF',
idea: '#FFFFFF',
},
fontFamily: {
sans: [
'Lato',