From 2204709c0e2636d5efeac4e0b1719149caecddc9 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 19 Jun 2024 18:06:17 +0300 Subject: [PATCH] Fix light theme, fix animation bugs, add programmatic scrolling, improve custom card blog component --- app.vue | 2 +- assets/styles/lists.scss | 8 +- assets/styles/main.scss | 143 +++++++++++++++-------- assets/styles/transitions.scss | 12 ++ components/content/Card.vue | 109 ++++++++--------- components/content/CodeControls.vue | 51 ++++---- content/blog/the-windows-context-menu.md | 10 +- layouts/Card.vue | 7 +- middleware/reader.global.ts | 2 +- nuxt.config.ts | 138 +++++++++++----------- pages/blog/[slug].vue | 45 +++++-- tailwind.config.ts | 8 ++ 12 files changed, 313 insertions(+), 222 deletions(-) diff --git a/app.vue b/app.vue index 6daad62..32e4938 100644 --- a/app.vue +++ b/app.vue @@ -56,6 +56,6 @@ useHead({ - + diff --git a/assets/styles/lists.scss b/assets/styles/lists.scss index df5deda..4c86ac1 100644 --- a/assets/styles/lists.scss +++ b/assets/styles/lists.scss @@ -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; diff --git a/assets/styles/main.scss b/assets/styles/main.scss index b3d8c23..fff90c8 100644 --- a/assets/styles/main.scss +++ b/assets/styles/main.scss @@ -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. diff --git a/assets/styles/transitions.scss b/assets/styles/transitions.scss index 45024f7..46cfc1f 100644 --- a/assets/styles/transitions.scss +++ b/assets/styles/transitions.scss @@ -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; diff --git a/components/content/Card.vue b/components/content/Card.vue index 6d3a970..d52e6bc 100644 --- a/components/content/Card.vue +++ b/components/content/Card.vue @@ -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, 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', + }, + }, } @@ -64,13 +71,15 @@ switch (props.icon) {

{{ props.title }}

-
+
@@ -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; - } -} diff --git a/components/content/CodeControls.vue b/components/content/CodeControls.vue index 6729da7..cb9a84a 100644 --- a/components/content/CodeControls.vue +++ b/components/content/CodeControls.vue @@ -1,32 +1,35 @@ - + - - diff --git a/content/blog/the-windows-context-menu.md b/content/blog/the-windows-context-menu.md index f96ebb8..6e71b26 100644 --- a/content/blog/the-windows-context-menu.md +++ b/content/blog/the-windows-context-menu.md @@ -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 diff --git a/layouts/Card.vue b/layouts/Card.vue index 5adf46a..d79590b 100644 --- a/layouts/Card.vue +++ b/layouts/Card.vue @@ -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, }" > - + - + diff --git a/middleware/reader.global.ts b/middleware/reader.global.ts index 439d8bc..4f2eddb 100644 --- a/middleware/reader.global.ts +++ b/middleware/reader.global.ts @@ -1,4 +1,4 @@ -export default defineNuxtRouteMiddleware((to, _from) => { +export default defineNuxtRouteMiddleware((to, from) => { const { reader } = storeToRefs(usePageStore()) reader.value = /\/blog\/(.*)/.test(to.path) diff --git a/nuxt.config.ts b/nuxt.config.ts index 37085ff..112aa3f 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -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', }, }, -}); +}) diff --git a/pages/blog/[slug].vue b/pages/blog/[slug].vue index 0a4cf1b..5c2cca9 100644 --- a/pages/blog/[slug].vue +++ b/pages/blog/[slug].vue @@ -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(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({
>{ 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',