feat: lucide icon and latex formulas
This commit is contained in:
+2
-1
@@ -1,3 +1,4 @@
|
||||
VITE_APP_TITLE=My Blog
|
||||
VITE_APP_TITLE=<i icon=rss></i> My Blog
|
||||
VITE_APP_TITLE_NO_HTML=My Blog
|
||||
VITE_APP_SIGNATURE=By <b>me</b>
|
||||
VITE_APP_LANG=en
|
||||
@@ -6,6 +6,8 @@
|
||||
"name": "md-blog",
|
||||
"dependencies": {
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.16.45",
|
||||
"lucide": "^1.3.0",
|
||||
"vue": "^3.5.32",
|
||||
"vue-router": "^5.0.4",
|
||||
},
|
||||
@@ -365,6 +367,8 @@
|
||||
|
||||
"colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="],
|
||||
|
||||
"commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
|
||||
|
||||
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
@@ -511,6 +515,8 @@
|
||||
|
||||
"jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||
|
||||
"katex": ["katex@0.16.45", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="],
|
||||
@@ -549,6 +555,8 @@
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"lucide": ["lucide@1.3.0", "", {}, "sha512-8/98ZCQjNDdfOjSycUujwqxKNrq97WPzdlNBR6tG7BqZgiOFQqzJuFapIpjPceW57sJv3zGIBg0L3SWR7c4fqA=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="],
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
<title>%VITE_APP_TITLE_NO_HTML%</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" type="text/css" href="/src/style.scss" />
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.16.45",
|
||||
"lucide": "^1.3.0",
|
||||
"vue": "^3.5.32",
|
||||
"vue-router": "^5.0.4"
|
||||
},
|
||||
|
||||
+25
-1
@@ -1,3 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import hljs from 'highlight.js'
|
||||
import { createIcons, icons } from 'lucide'
|
||||
import { onMounted, onUpdated, nextTick } from 'vue'
|
||||
|
||||
async function update() {
|
||||
setTimeout(async () => {
|
||||
await nextTick()
|
||||
hljs.highlightAll()
|
||||
createIcons({
|
||||
icons,
|
||||
nameAttr: 'icon',
|
||||
attrs: {
|
||||
width: '1.1em',
|
||||
height: '1.1em',
|
||||
},
|
||||
})
|
||||
}, 100)
|
||||
}
|
||||
|
||||
onMounted(update)
|
||||
onUpdated(update)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
<RouterView @vue:mounted="update" @vue:updated="update" />
|
||||
</template>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { REPOSITORY, NAME, VERSION } from '@/lib/meta'
|
||||
|
||||
<template>
|
||||
<template v-if="$route.fullPath != '/'">
|
||||
<RouterLink to="/">Back to home</RouterLink>
|
||||
<RouterLink to="/"><i icon="undo-2"></i> Back to home</RouterLink>
|
||||
</template>
|
||||
<hr />
|
||||
<footer>
|
||||
|
||||
+20
-1
@@ -1,5 +1,6 @@
|
||||
import type { MarkdownData, Article, ArticleMetadata } from '@interfaces'
|
||||
import { dateFromParts } from './dates'
|
||||
import katex from 'katex'
|
||||
|
||||
function parseMetadata(
|
||||
srcAttributes: Record<string, unknown>,
|
||||
@@ -18,8 +19,26 @@ function parseMetadata(
|
||||
}
|
||||
}
|
||||
|
||||
const LATEX_REGEX = /\$\$((?:(?!\$\$)[\s\S])*)\$\$/m
|
||||
const INLINE_LATEX_REGEX = /\$([^$\n]*)\$/
|
||||
|
||||
function transformHtml(srcHtml: string, pathPrefix: string): string {
|
||||
return srcHtml.replaceAll('./', pathPrefix + '/')
|
||||
let html: string = srcHtml.replaceAll('="./', '="' + pathPrefix + '/')
|
||||
let match: RegExpExecArray | null
|
||||
do {
|
||||
match = LATEX_REGEX.exec(html)
|
||||
if (match && match[1]) {
|
||||
console.log(match)
|
||||
html = html.replace(match[0], katex.renderToString(match[1]))
|
||||
}
|
||||
} while (match && match[1])
|
||||
do {
|
||||
match = INLINE_LATEX_REGEX.exec(html)
|
||||
if (match && match[1]) {
|
||||
html = html.replace(match[0], katex.renderToString(match[1]))
|
||||
}
|
||||
} while (match && match[1])
|
||||
return html
|
||||
}
|
||||
|
||||
export async function loadArticle(date: Date): Promise<Article | null> {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export function stripHTML(html: string): string {
|
||||
const div = document.createElement('div')
|
||||
div.innerHTML = html
|
||||
return div.textContent || div.innerText || ''
|
||||
}
|
||||
+3
-2
@@ -1,2 +1,3 @@
|
||||
@import 'highlight.js/scss/arduino-light.scss';
|
||||
@import '@articles/style.scss';
|
||||
@use 'highlight.js/scss/arduino-light.scss';
|
||||
@use '@articles/style.scss';
|
||||
@use 'katex/dist/katex.min.css';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { Article } from '@interfaces'
|
||||
import { ref, onBeforeMount, onUpdated } from 'vue'
|
||||
import { ref, onBeforeMount } from 'vue'
|
||||
import { loadArticle } from '@lib/articles'
|
||||
import { useRoute, onBeforeRouteUpdate, type RouteLocation } from 'vue-router'
|
||||
import NotFoundView from './NotFoundView.vue'
|
||||
import { dateFromParts, simpleDateFormat } from '@lib/dates'
|
||||
import { SIGNATURE, TITLE } from '@lib/meta'
|
||||
import PageFooter from '@components/PageFooter.vue'
|
||||
import hljs from 'highlight.js'
|
||||
import { stripHTML } from '@/lib/strings'
|
||||
|
||||
const article = ref<Article | null>(null)
|
||||
const loading = ref<boolean>(true)
|
||||
@@ -22,16 +22,13 @@ async function loadPage(target: RouteLocation) {
|
||||
target.params.day as string,
|
||||
),
|
||||
)
|
||||
window.document.title = TITLE + ' — ' + (article.value?.metadata.title ?? 'Not Found')
|
||||
window.document.title =
|
||||
stripHTML(TITLE) + ' — ' + stripHTML(article.value?.metadata.title ?? 'Not Found')
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onBeforeMount(() => loadPage(route))
|
||||
onBeforeRouteUpdate(loadPage)
|
||||
|
||||
onUpdated(() => {
|
||||
hljs.highlightAll()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -45,7 +42,7 @@ onUpdated(() => {
|
||||
<main class="article">
|
||||
<div class="header">
|
||||
<RouterLink class="link-home" to="/">↑</RouterLink>
|
||||
<h1>{{ article.metadata.title }}</h1>
|
||||
<h1 v-html="article.metadata.title"></h1>
|
||||
<span class="time">
|
||||
<span>{{ article.metadata.draft ? 'Drafted on' : 'Published on' }}</span>
|
||||
{{ simpleDateFormat(article.metadata.date) }}
|
||||
|
||||
@@ -5,23 +5,24 @@ import type { ArticleMetadata } from '@interfaces'
|
||||
import { simpleDateFormat } from '@lib/dates'
|
||||
import { TITLE } from '@lib/meta'
|
||||
import PageFooter from '@components/PageFooter.vue'
|
||||
import { stripHTML } from '@/lib/strings'
|
||||
|
||||
const articles = ref<ArticleMetadata[]>([])
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const newArticles = await listArticles()
|
||||
articles.value.splice(0, articles.value.length, ...newArticles)
|
||||
window.document.title = TITLE + ' — Home'
|
||||
window.document.title = stripHTML(TITLE) + ' — Home'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1 class="title">{{ TITLE }}</h1>
|
||||
<h1 class="title" v-html="TITLE"></h1>
|
||||
<template v-for="(metadata, index) in articles" v-bind:key="index">
|
||||
<div v-if="!metadata.draft && metadata.path" class="article">
|
||||
<RouterLink :to="metadata.path">
|
||||
<h3>{{ metadata.title }}</h3>
|
||||
<h3 v-html="metadata.title"></h3>
|
||||
<span class="time"
|
||||
><span>Published on</span> {{ simpleDateFormat(metadata.date) }}</span
|
||||
>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { TITLE } from '@/lib/meta'
|
||||
import { stripHTML } from '@/lib/strings'
|
||||
import PageFooter from '@components/PageFooter.vue'
|
||||
import { onBeforeMount } from 'vue'
|
||||
|
||||
onBeforeMount(() => {
|
||||
window.document.title = TITLE + ' — Not Found'
|
||||
window.document.title = stripHTML(TITLE) + ' — Not Found'
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -14,5 +15,3 @@ onBeforeMount(() => {
|
||||
<PageFooter />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user