mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-15 19:22:52 +02:00
feat: enhance typography and image handling
- Add Heti typography plugin for improved Chinese text layout - Implement rehype plugin to convert images to figure elements with captions - Update UnoCSS configuration by removing typography preset - Modify file extensions for consistency (.mjs to .js) - Add new plugins for admonition, GitHub card, and reading time components - Improve image and text styling with Heti CSS
This commit is contained in:
parent
c549814c7e
commit
9d6de20b6a
19 changed files with 1006 additions and 556 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -65,12 +65,16 @@
|
|||
"bmoji",
|
||||
"Frontmatter",
|
||||
"gtag",
|
||||
"Heti",
|
||||
"katex",
|
||||
"Lightbox",
|
||||
"mdast",
|
||||
"msrc",
|
||||
"msvalidate",
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"overlayscrollbars",
|
||||
"pagefind",
|
||||
"partytown",
|
||||
"photoswipe",
|
||||
"pswp",
|
||||
|
|
|
@ -20,11 +20,12 @@ import remarkSectionize from 'remark-sectionize'
|
|||
import UnoCSS from 'unocss/astro'
|
||||
import { themeConfig } from './src/config.js'
|
||||
// Local plugins
|
||||
import { AdmonitionComponent } from './src/plugins/rehype-component-admonition.mjs'
|
||||
import { GithubCardComponent } from './src/plugins/rehype-component-github-card.mjs'
|
||||
import { AdmonitionComponent } from './src/plugins/rehype-component-admonition.js'
|
||||
import { GithubCardComponent } from './src/plugins/rehype-component-github-card.js'
|
||||
import { rehypeImgToFigure } from './src/plugins/rehype-img-to-figure.js'
|
||||
import { parseDirectiveNode } from './src/plugins/remark-directive-rehype.js'
|
||||
import { remarkExcerpt } from './src/plugins/remark-excerpt.js'
|
||||
import { remarkReadingTime } from './src/plugins/remark-reading-time.mjs'
|
||||
import { remarkReadingTime } from './src/plugins/remark-reading-time.js'
|
||||
import { langMap } from './src/utils/ui'
|
||||
|
||||
const { url } = themeConfig.site
|
||||
|
@ -82,6 +83,7 @@ export default defineConfig({
|
|||
rehypePlugins: [
|
||||
rehypeSlug,
|
||||
rehypeKatex,
|
||||
rehypeImgToFigure,
|
||||
[
|
||||
rehypePrettyCode,
|
||||
{
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
"@astrojs/rss": "^4.0.11",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@rehype-pretty/transformers": "^0.13.2",
|
||||
"@unocss/reset": "^65.4.3",
|
||||
"astro": "^5.3.0",
|
||||
"astro-compress": "^2.3.6",
|
||||
"astro-robots-txt": "^1.0.0",
|
||||
|
@ -45,20 +44,20 @@
|
|||
"vite": "^6.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^4.2.0",
|
||||
"@antfu/eslint-config": "^4.2.1",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/sanitize-html": "^2.13.0",
|
||||
"@unocss/eslint-plugin": "^65.4.3",
|
||||
"@unocss/preset-attributify": "^65.4.3",
|
||||
"@unocss/eslint-plugin": "^65.5.0",
|
||||
"@unocss/reset": "^65.5.0",
|
||||
"astro-eslint-parser": "^1.2.1",
|
||||
"eslint": "^9.20.1",
|
||||
"eslint-plugin-astro": "^1.3.1",
|
||||
"lint-staged": "^15.4.3",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"reading-time": "^1.5.0",
|
||||
"unocss": "^65.4.3",
|
||||
"unocss": "^65.5.0",
|
||||
"unocss-preset-theme": "^0.14.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
|
724
pnpm-lock.yaml
generated
724
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -14,8 +14,9 @@ const marginBottom = {
|
|||
}[titleSpace] || 'mb-3'
|
||||
---
|
||||
|
||||
<header>
|
||||
<header class="mb-17">
|
||||
<h1 class={`${marginBottom} text-12.8 c-primary font-bold font-title`}>
|
||||
<!-- Fix text cut issue on ios by adding a div tag -->
|
||||
<div
|
||||
class="box-content inline-block pr-2"
|
||||
transition:name="site-title"
|
||||
|
|
|
@ -7,7 +7,8 @@ const currentPath = Astro.url.pathname
|
|||
const { getLocalizedPath } = getPagePath(currentPath)
|
||||
---
|
||||
|
||||
<header class="mt-4.7 text-8.6 c-secondary font-bold font-title lg:hidden">
|
||||
<header class="mt-4.7 mb-17 text-8.6 c-secondary font-bold font-title lg:hidden">
|
||||
<!-- Fix text cut issue on ios by adding a div tag -->
|
||||
<div
|
||||
class="box-content inline-block pr-2"
|
||||
transition:name="site-title"
|
||||
|
|
|
@ -12,7 +12,7 @@ const isTagActive = isTag
|
|||
const isAboutActive = isAbout;
|
||||
---
|
||||
|
||||
<nav class="mb-17 mt-17 text-5.84 font-semibold leading-14 font-navbar">
|
||||
<nav class="mb-17 text-5.84 font-semibold leading-14 font-navbar">
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
|
|
|
@ -4,7 +4,7 @@ published: 2022-06-17
|
|||
tags: ["胡适"]
|
||||
---
|
||||
|
||||

|
||||

|
||||
|
||||
十七八年前,我最后一次会见我的母校康耐儿大学的史学大师布尔先生(George Lincoln Burr)。我们谈到英国史学大师阿克顿(Lord Acton)一生准备要著作一部《自由之史》,没有写成他就死了。布尔先生那天谈话很多,有一句话我至今没有忘记。他说,“我年纪越大,越感觉到容忍(tolerance)比自由更重要”。
|
||||
|
||||
|
@ -61,5 +61,4 @@ tags: ["胡适"]
|
|||
我曾说过,我应该用容忍的态度来报答社会对我的容忍。我现在常常想我们还得戒律自己:我们若想别人容忍谅解我们的见解,我们必须先养成能够容忍谅解别人的见解的度量。至少至少我们应该戒约自己决不可“以吾辈所主张者为绝对之是”。我们受过实验主义的训练的人,本来就不承认有“绝对之是”,更不可以“以吾辈所主张者为绝对之是”。
|
||||
|
||||
四八、三、十二晨
|
||||
|
||||
(原载1959年3月16日台北《自由中国》第20卷第6期)
|
||||
|
|
|
@ -13,6 +13,7 @@ import Head from '@/layouts/Head.astro'
|
|||
import { getPagePath } from '@/utils/path'
|
||||
import '@/styles/font.css'
|
||||
import '@/styles/global.css'
|
||||
import '@/styles/heti.css'
|
||||
import '@/styles/photoswipe.css'
|
||||
|
||||
interface Props {
|
||||
|
|
|
@ -37,9 +37,17 @@ const postsByYear = await getPostsByYear()
|
|||
// Single Post
|
||||
<li class="mt-7">
|
||||
{/* Post Title */}
|
||||
<a class="hover:c-primary" href={`/posts/${post.data.slug || post.slug}`}>{post.data.title}</a>
|
||||
<a
|
||||
class="hover:c-primary"
|
||||
href={`/posts/${post.data.slug || post.slug}`}
|
||||
transition:name={`post-title-${post.data.slug}`}>
|
||||
{post.data.title}
|
||||
</a>
|
||||
{/* Post Date */}
|
||||
<div class="mb-9 block text-5.6 leading-11 font-time lg:ml-4 lg:inline">
|
||||
<div
|
||||
class="mb-9 block text-5.6 leading-11 font-time lg:ml-4 lg:inline"
|
||||
transition:name={`post-time-${post.data.slug}`}
|
||||
>
|
||||
{post.data.published.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-')}
|
||||
{post.remarkPluginFrontmatter?.minutes && <span class="ml-2"> {post.remarkPluginFrontmatter.minutes} min</span>}
|
||||
</div>
|
||||
|
|
|
@ -24,9 +24,9 @@ const { Content, remarkPluginFrontmatter } = await post.render()
|
|||
postDescription={post.data.description}
|
||||
postImage={post.data.image}
|
||||
>
|
||||
<article>
|
||||
<h1>{post.data.title}</h1>
|
||||
<time>
|
||||
<article class="heti">
|
||||
<h1 transition:name={`post-title-${post.data.slug}`}>{post.data.title}</h1>
|
||||
<time transition:name={`post-time-${post.data.slug}`}>
|
||||
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
||||
{remarkPluginFrontmatter.minutes && <span> · {remarkPluginFrontmatter.minutes} min</span>}
|
||||
</time>
|
||||
|
|
412
src/plugins/heti-addon.js
Normal file
412
src/plugins/heti-addon.js
Normal file
|
@ -0,0 +1,412 @@
|
|||
/* eslint-disable */
|
||||
!(function (e, t) {
|
||||
typeof exports == 'object' && typeof module != 'undefined' ? module.exports = t() : typeof define == 'function' && define.amd ? define(t) : (e = e || self).Heti = t()
|
||||
}(this, () => {
|
||||
'use strict'
|
||||
let e = typeof globalThis != 'undefined' ? globalThis : typeof window != 'undefined' ? window : typeof global != 'undefined' ? global : typeof self != 'undefined' ? self : {}
|
||||
let t = (function (e, t) {
|
||||
return e(t = {
|
||||
exports: {},
|
||||
}, t.exports), t.exports
|
||||
}((t) => {
|
||||
let n, i
|
||||
n = e, i = function () {
|
||||
let e = document
|
||||
let t = {}.hasOwnProperty
|
||||
|
||||
function n() {
|
||||
return i.apply(null, arguments) || r.apply(null, arguments)
|
||||
}
|
||||
|
||||
function i(e, t, i, o, s) {
|
||||
if (t && !t.nodeType && arguments.length <= 2)
|
||||
return !1
|
||||
let a; let d = typeof i == 'function'
|
||||
d && (a = i, i = function (e, t) {
|
||||
return a(e.text, t.startIndex)
|
||||
})
|
||||
let c = r(t, {
|
||||
find: e,
|
||||
wrap: d ? null : i,
|
||||
replace: d ? i : `$${o || '&'}`,
|
||||
prepMatch(e, t) {
|
||||
if (!e[0])
|
||||
throw 'findAndReplaceDOMText cannot handle zero-length matches'
|
||||
if (o > 0) {
|
||||
let n = e[o]
|
||||
e.index += e[0].indexOf(n), e[0] = n
|
||||
}
|
||||
return e.endIndex = e.index + e[0].length, e.startIndex = e.index, e.index = t, e
|
||||
},
|
||||
filterElements: s,
|
||||
})
|
||||
return n.revert = function () {
|
||||
return c.revert()
|
||||
}, !0
|
||||
}
|
||||
|
||||
function r(e, t) {
|
||||
return new o(e, t)
|
||||
}
|
||||
|
||||
function o(e, i) {
|
||||
i.offset || (i.offset = 0)
|
||||
let r = i.preset && n.PRESETS[i.preset]
|
||||
if (i.portionMode = i.portionMode || 'retain', r) {
|
||||
for (let o in r) t.call(r, o) && !t.call(i, o) && (i[o] = r[o])
|
||||
}
|
||||
this.node = e, this.options = i, this.prepMatch = i.prepMatch || this.prepMatch, this.reverts = [], this.matches = this.search(), this.matches.length && this.processMatches()
|
||||
}
|
||||
return n.NON_PROSE_ELEMENTS = {
|
||||
br: 1,
|
||||
hr: 1,
|
||||
script: 1,
|
||||
style: 1,
|
||||
img: 1,
|
||||
video: 1,
|
||||
audio: 1,
|
||||
canvas: 1,
|
||||
svg: 1,
|
||||
map: 1,
|
||||
object: 1,
|
||||
input: 1,
|
||||
textarea: 1,
|
||||
select: 1,
|
||||
option: 1,
|
||||
optgroup: 1,
|
||||
button: 1,
|
||||
}, n.NON_CONTIGUOUS_PROSE_ELEMENTS = {
|
||||
address: 1,
|
||||
article: 1,
|
||||
aside: 1,
|
||||
blockquote: 1,
|
||||
dd: 1,
|
||||
div: 1,
|
||||
dl: 1,
|
||||
fieldset: 1,
|
||||
figcaption: 1,
|
||||
figure: 1,
|
||||
footer: 1,
|
||||
form: 1,
|
||||
h1: 1,
|
||||
h2: 1,
|
||||
h3: 1,
|
||||
h4: 1,
|
||||
h5: 1,
|
||||
h6: 1,
|
||||
header: 1,
|
||||
hgroup: 1,
|
||||
hr: 1,
|
||||
main: 1,
|
||||
nav: 1,
|
||||
noscript: 1,
|
||||
ol: 1,
|
||||
output: 1,
|
||||
p: 1,
|
||||
pre: 1,
|
||||
section: 1,
|
||||
ul: 1,
|
||||
br: 1,
|
||||
li: 1,
|
||||
summary: 1,
|
||||
dt: 1,
|
||||
details: 1,
|
||||
rp: 1,
|
||||
rt: 1,
|
||||
rtc: 1,
|
||||
script: 1,
|
||||
style: 1,
|
||||
img: 1,
|
||||
video: 1,
|
||||
audio: 1,
|
||||
canvas: 1,
|
||||
svg: 1,
|
||||
map: 1,
|
||||
object: 1,
|
||||
input: 1,
|
||||
textarea: 1,
|
||||
select: 1,
|
||||
option: 1,
|
||||
optgroup: 1,
|
||||
button: 1,
|
||||
table: 1,
|
||||
tbody: 1,
|
||||
thead: 1,
|
||||
th: 1,
|
||||
tr: 1,
|
||||
td: 1,
|
||||
caption: 1,
|
||||
col: 1,
|
||||
tfoot: 1,
|
||||
colgroup: 1,
|
||||
}, n.NON_INLINE_PROSE = function (e) {
|
||||
return t.call(n.NON_CONTIGUOUS_PROSE_ELEMENTS, e.nodeName.toLowerCase())
|
||||
}, n.PRESETS = {
|
||||
prose: {
|
||||
forceContext: n.NON_INLINE_PROSE,
|
||||
filterElements(e) {
|
||||
return !t.call(n.NON_PROSE_ELEMENTS, e.nodeName.toLowerCase())
|
||||
},
|
||||
},
|
||||
}, n.Finder = o, o.prototype = {
|
||||
search() {
|
||||
let e; let t = 0
|
||||
let n = 0
|
||||
let i = this.options.find
|
||||
let r = this.getAggregateText()
|
||||
let o = []
|
||||
let s = this
|
||||
return i = typeof i == 'string' ? new RegExp(String(i).replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g') : i,
|
||||
(function r(a) {
|
||||
for (let d = 0, c = a.length; d < c; ++d) {
|
||||
let p = a[d]
|
||||
if (typeof p == 'string') {
|
||||
if (i.global) {
|
||||
for (; e = i.exec(p);) o.push(s.prepMatch(e, t++, n))
|
||||
}
|
||||
else {
|
||||
(e = p.match(i)) && o.push(s.prepMatch(e, 0, n))
|
||||
}
|
||||
n += p.length
|
||||
}
|
||||
else {
|
||||
r(p)
|
||||
}
|
||||
}
|
||||
}(r)), o
|
||||
},
|
||||
prepMatch(e, t, n) {
|
||||
if (!e[0])
|
||||
throw new Error('findAndReplaceDOMText cannot handle zero-length matches')
|
||||
return e.endIndex = n + e.index + e[0].length, e.startIndex = n + e.index, e.index = t, e
|
||||
},
|
||||
getAggregateText() {
|
||||
let e = this.options.filterElements
|
||||
let t = this.options.forceContext
|
||||
return (function n(i) {
|
||||
if (i.nodeType === Node.TEXT_NODE)
|
||||
return [i.data]
|
||||
if (e && !e(i))
|
||||
return []
|
||||
let r = ['']
|
||||
let o = 0
|
||||
if (i = i.firstChild) {
|
||||
do {
|
||||
if (i.nodeType !== Node.TEXT_NODE) {
|
||||
let s = n(i)
|
||||
t && i.nodeType === Node.ELEMENT_NODE && (!0 === t || t(i)) ? (r[++o] = s, r[++o] = '') : (typeof s[0] == 'string' && (r[o] += s.shift()), s.length && (r[++o] = s, r[++o] = ''))
|
||||
}
|
||||
else {
|
||||
r[o] += i.data
|
||||
}
|
||||
} while (i = i.nextSibling)
|
||||
}
|
||||
return r
|
||||
}(this.node))
|
||||
},
|
||||
processMatches() {
|
||||
let e; let t; let n; let i = this.matches
|
||||
let r = this.node
|
||||
let o = this.options.filterElements
|
||||
let s = []
|
||||
let a = r
|
||||
let d = i.shift()
|
||||
let c = 0
|
||||
let p = 0
|
||||
let h = [r]
|
||||
e: for (;;) {
|
||||
if (a.nodeType === Node.TEXT_NODE && (!t && a.length + c >= d.endIndex
|
||||
? t = {
|
||||
node: a,
|
||||
index: p++,
|
||||
text: a.data.substring(d.startIndex - c + this.options.offset, d.endIndex - c),
|
||||
indexInMatch: c === 0 ? 0 : c - d.startIndex,
|
||||
indexInNode: d.startIndex - c + this.options.offset,
|
||||
endIndexInNode: d.endIndex - c,
|
||||
isEnd: !0,
|
||||
}
|
||||
: e && s.push({
|
||||
node: a,
|
||||
index: p++,
|
||||
text: a.data,
|
||||
indexInMatch: c - d.startIndex,
|
||||
indexInNode: 0,
|
||||
}), !e && a.length + c > d.startIndex && (e = {
|
||||
node: a,
|
||||
index: p++,
|
||||
indexInMatch: 0,
|
||||
indexInNode: d.startIndex - c + this.options.offset,
|
||||
endIndexInNode: d.endIndex - c,
|
||||
text: a.data.substring(d.startIndex - c + this.options.offset, d.endIndex - c),
|
||||
}), c += a.data.length), n = a.nodeType === Node.ELEMENT_NODE && o && !o(a), e && t) {
|
||||
if (a = this.replaceMatch(d, e, s, t), c -= t.node.data.length - t.endIndexInNode, e = null, t = null, s = [], p = 0, !(d = i.shift()))
|
||||
break
|
||||
}
|
||||
else if (!n && (a.firstChild || a.nextSibling)) {
|
||||
a.firstChild ? (h.push(a), a = a.firstChild) : a = a.nextSibling
|
||||
continue
|
||||
}
|
||||
for (;;) {
|
||||
if (a.nextSibling) {
|
||||
a = a.nextSibling
|
||||
break
|
||||
}
|
||||
if ((a = h.pop()) === r)
|
||||
break e
|
||||
}
|
||||
}
|
||||
},
|
||||
revert() {
|
||||
for (let e = this.reverts.length; e--;) this.reverts[e]()
|
||||
this.reverts = []
|
||||
},
|
||||
prepareReplacementString(e, t, n) {
|
||||
let i = this.options.portionMode
|
||||
return i === 'first' && t.indexInMatch > 0
|
||||
? ''
|
||||
: (e = e.replace(/\$(\d+|[&`'])/g, (e, t) => {
|
||||
let i
|
||||
switch (t) {
|
||||
case '&':
|
||||
i = n[0]
|
||||
break
|
||||
case '`':
|
||||
i = n.input.substring(0, n.startIndex)
|
||||
break
|
||||
case '\'':
|
||||
i = n.input.substring(n.endIndex)
|
||||
break
|
||||
default:
|
||||
i = n[+t] || ''
|
||||
}
|
||||
return i
|
||||
}), i === 'first' ? e : t.isEnd ? e.substring(t.indexInMatch) : e.substring(t.indexInMatch, t.indexInMatch + t.text.length))
|
||||
},
|
||||
getPortionReplacementNode(t, n) {
|
||||
let i = this.options.replace || '$&'
|
||||
let r = this.options.wrap
|
||||
let o = this.options.wrapClass
|
||||
if (r && r.nodeType) {
|
||||
let s = e.createElement('div')
|
||||
s.innerHTML = r.outerHTML || (new XMLSerializer()).serializeToString(r), r = s.firstChild
|
||||
}
|
||||
if (typeof i == 'function')
|
||||
return (i = i(t, n)) && i.nodeType ? i : e.createTextNode(String(i))
|
||||
let a = typeof r == 'string' ? e.createElement(r) : r
|
||||
return a && o && (a.className = o), (i = e.createTextNode(this.prepareReplacementString(i, t, n))).data && a ? (a.appendChild(i), a) : i
|
||||
},
|
||||
replaceMatch(t, n, i, r) {
|
||||
let o; let s; let a = n.node
|
||||
let d = r.node
|
||||
if (a === d) {
|
||||
let c = a
|
||||
n.indexInNode > 0 && (o = e.createTextNode(c.data.substring(0, n.indexInNode)), c.parentNode.insertBefore(o, c))
|
||||
let p = this.getPortionReplacementNode(r, t)
|
||||
return c.parentNode.insertBefore(p, c), r.endIndexInNode < c.length && (s = e.createTextNode(c.data.substring(r.endIndexInNode)), c.parentNode.insertBefore(s, c)), c.parentNode.removeChild(c), this.reverts.push(() => {
|
||||
o === p.previousSibling && o.parentNode.removeChild(o), s === p.nextSibling && s.parentNode.removeChild(s), p.parentNode.replaceChild(c, p)
|
||||
}), p
|
||||
}
|
||||
o = e.createTextNode(a.data.substring(0, n.indexInNode)), s = e.createTextNode(d.data.substring(r.endIndexInNode))
|
||||
for (var h = this.getPortionReplacementNode(n, t), f = [], l = 0, u = i.length; l < u; ++l) {
|
||||
let g = i[l]
|
||||
let x = this.getPortionReplacementNode(g, t)
|
||||
g.node.parentNode.replaceChild(x, g.node), this.reverts.push(function (e, t) {
|
||||
return function () {
|
||||
t.parentNode.replaceChild(e.node, t)
|
||||
}
|
||||
}(g, x)), f.push(x)
|
||||
}
|
||||
let N = this.getPortionReplacementNode(r, t)
|
||||
return a.parentNode.insertBefore(o, a), a.parentNode.insertBefore(h, a), a.parentNode.removeChild(a), d.parentNode.insertBefore(N, d), d.parentNode.insertBefore(s, d), d.parentNode.removeChild(d), this.reverts.push(() => {
|
||||
o.parentNode.removeChild(o), h.parentNode.replaceChild(a, h), s.parentNode.removeChild(s), N.parentNode.replaceChild(d, N)
|
||||
}), N
|
||||
},
|
||||
}, n
|
||||
}, t.exports ? t.exports = i() : n.findAndReplaceDOMText = i()
|
||||
}))
|
||||
const n = {}.hasOwnProperty
|
||||
const i = Object.assign({}, t.NON_CONTIGUOUS_PROSE_ELEMENTS, {
|
||||
ins: 1,
|
||||
del: 1,
|
||||
s: 1,
|
||||
a: 1,
|
||||
})
|
||||
const r = Object.assign({}, t.NON_PROSE_ELEMENTS, {
|
||||
'pre': 1,
|
||||
'code': 1,
|
||||
'sup': 1,
|
||||
'sub': 1,
|
||||
'heti-spacing': 1,
|
||||
'heti-close': 1,
|
||||
})
|
||||
const o = '⺀-⼀--ゟ゠-ヺー-ヿ-ㄯ㈀-㋿㐀-䶿一-鿿豈-'
|
||||
const s = 'A-Za-z-ÿͰ-Ͽ0-9`~!@#\\$%\\^&\\*\\(\\)-_=\\+\\[\\]{}\\\\\\|;:\'",<.>\\/\\?'
|
||||
const a = `(?<=[${o}])( *[${s}]+(?: +[${s}]+)* *)(?=[${o}])`
|
||||
const d = `([${s}]+(?: +[${s}]+)* *)(?=[${o}])`
|
||||
const c = `(?<=[${o}])( *[${s}]+(?: +[${s}]+)*)`
|
||||
const p = `(?:[${o}])( *[${s}]+(?: +[${s}]+)* *)(?=[${o}])`
|
||||
const h = `(?:[${o}])( *[${s}]+(?: +[${s}]+)*)`
|
||||
return class {
|
||||
constructor(e) {
|
||||
let t = !0
|
||||
try {
|
||||
new RegExp('(?<=d)d', '').test('')
|
||||
}
|
||||
catch (e) {
|
||||
e.name, t = !1
|
||||
}
|
||||
this.rootSelector = e || '.heti', this.REG_FULL = new RegExp(t ? a : p, 'g'), this.REG_START = new RegExp(d, 'g'), this.REG_END = new RegExp(t ? c : h, 'g'), this.offsetWidth = t ? 0 : 1, this.funcForceContext = function (e) {
|
||||
return n.call(i, e.nodeName.toLowerCase())
|
||||
}, this.funcFilterElements = function (e) {
|
||||
return !(e.classList && e.classList.contains('heti-skip') || n.call(r, e.nodeName.toLowerCase()))
|
||||
}
|
||||
}
|
||||
|
||||
spacingElements(e) {
|
||||
for (let t of e) this.spacingElement(t)
|
||||
}
|
||||
|
||||
spacingElement(e) {
|
||||
const n = {
|
||||
forceContext: this.funcForceContext,
|
||||
filterElements: this.funcFilterElements,
|
||||
}
|
||||
const i = function (e, t, n) {
|
||||
const i = document.createElement(e)
|
||||
return i.className = t, i.textContent = n.trim(), i
|
||||
}
|
||||
t(e, Object.assign({}, n, {
|
||||
find: this.REG_FULL,
|
||||
replace: e => i('heti-spacing', 'heti-spacing-start heti-spacing-end', e.text),
|
||||
offset: this.offsetWidth,
|
||||
})), t(e, Object.assign({}, n, {
|
||||
find: this.REG_START,
|
||||
replace: e => i('heti-spacing', 'heti-spacing-start', e.text),
|
||||
})), t(e, Object.assign({}, n, {
|
||||
find: this.REG_END,
|
||||
replace: e => i('heti-spacing', 'heti-spacing-end', e.text),
|
||||
offset: this.offsetWidth,
|
||||
})), t(e, Object.assign({}, n, {
|
||||
find: new RegExp('([。.,、:;!‼?⁇])(?=[「『(《〈【〖〔[{」』)》〉】〗〕]}])|([「『(《〈【〖〔[{])(?=[「『(《〈【〖〔[{])|([」』)》〉】〗〕]}])(?=[。.,、:;!‼?⁇「『(《〈【〖〔[{」』)》〉】〗〕]}])', 'g'),
|
||||
replace: e => i('heti-adjacent', 'heti-adjacent-half', e.text),
|
||||
offset: this.offsetWidth,
|
||||
})), t(e, Object.assign({}, n, {
|
||||
find: new RegExp('([·・‧])(?=[「『(《〈【〖〔[{])|([」』)》〉】〗〕]}])(?=[·・‧])', 'g'),
|
||||
replace: e => i('heti-adjacent', 'heti-adjacent-quarter', e.text),
|
||||
offset: this.offsetWidth,
|
||||
})), t(e, Object.assign({}, n, {
|
||||
find: new RegExp('([。.,、:;!‼?⁇])(?=["\'' + '])|(["' + '])(?=[「『(《〈【〖〔[{])', 'g'),
|
||||
replace: e => i('heti-adjacent', 'heti-adjacent-quarter', e.text),
|
||||
offset: this.offsetWidth,
|
||||
}))
|
||||
}
|
||||
|
||||
autoSpacing() {
|
||||
const e = () => {
|
||||
const e = document.querySelectorAll(this.rootSelector)
|
||||
for (let t of e) this.spacingElement(t)
|
||||
}
|
||||
document.readyState === 'complete' ? setTimeout(e) : document.addEventListener('DOMContentLoaded', e)
|
||||
}
|
||||
}
|
||||
}))
|
31
src/plugins/rehype-img-to-figure.js
Normal file
31
src/plugins/rehype-img-to-figure.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { visit } from 'unist-util-visit'
|
||||
|
||||
export function rehypeImgToFigure() {
|
||||
return (tree) => {
|
||||
visit(tree, 'element', (node) => {
|
||||
if (node.tagName !== 'p' || !node.children)
|
||||
return
|
||||
|
||||
const child = node.children[0]
|
||||
if (!child || child.tagName !== 'img')
|
||||
return
|
||||
|
||||
const altText = child.properties.alt
|
||||
if (!altText)
|
||||
return
|
||||
|
||||
node.tagName = 'figure'
|
||||
node.children = [
|
||||
child,
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'figcaption',
|
||||
properties: {},
|
||||
children: [{ type: 'text', value: altText }],
|
||||
},
|
||||
]
|
||||
|
||||
child.properties.alt = ''
|
||||
})
|
||||
}
|
||||
}
|
|
@ -16,32 +16,7 @@ body {
|
|||
--at-apply: 'text-1.6rem ios-flash-fix';
|
||||
}
|
||||
|
||||
h1 {
|
||||
--at-apply: 'text-3.6rem';
|
||||
}
|
||||
|
||||
h2 {
|
||||
--at-apply: 'text-3rem';
|
||||
}
|
||||
|
||||
h3 {
|
||||
--at-apply: 'text-2.4rem';
|
||||
}
|
||||
|
||||
h4 {
|
||||
--at-apply: 'text-2rem';
|
||||
}
|
||||
|
||||
h5 {
|
||||
--at-apply: 'text-1.8rem';
|
||||
}
|
||||
|
||||
h6 {
|
||||
--at-apply: 'text-1.6rem';
|
||||
}
|
||||
|
||||
article img {
|
||||
/* Optimize animation performance of scrollbar */
|
||||
--at-apply: 'cursor-zoom-in force-gpu';
|
||||
}
|
||||
|
||||
|
|
317
src/styles/heti.css
Normal file
317
src/styles/heti.css
Normal file
|
@ -0,0 +1,317 @@
|
|||
/*!
|
||||
* Project:Heti
|
||||
* URL:https://github.com/sivan/heti
|
||||
* Author:Sivan [sun.sivan@gmail.com]
|
||||
*/
|
||||
.heti {
|
||||
max-width:42em;
|
||||
line-height:1.5;
|
||||
overflow-wrap:break-word;
|
||||
word-wrap:break-word;
|
||||
hyphens:auto;
|
||||
letter-spacing:.02em
|
||||
}
|
||||
.heti::before,.heti::after {
|
||||
content:"";
|
||||
display:table
|
||||
}
|
||||
.heti::after {
|
||||
clear:both
|
||||
}
|
||||
.heti>*:first-child,.heti section>*:first-child,.heti td>*:first-child {
|
||||
margin-block-start:0 !important
|
||||
}
|
||||
.heti>*:last-child,.heti section>*:last-child,.heti td>*:last-child {
|
||||
margin-block-end:0 !important
|
||||
}
|
||||
.heti blockquote {
|
||||
margin-block-start:12px;
|
||||
margin-block-end:24px;
|
||||
margin-inline-start:32px;
|
||||
margin-inline-end:32px;
|
||||
padding-block-start:12px;
|
||||
padding-block-end:12px;
|
||||
padding-inline-start:16px;
|
||||
padding-inline-end:16px;
|
||||
background-color:rgba(0,0,0,.054)
|
||||
}
|
||||
@media(prefers-color-scheme:dark) {
|
||||
.heti blockquote {
|
||||
background-color:rgba(255,255,255,.054)
|
||||
}
|
||||
}
|
||||
.heti img {
|
||||
--at-apply: 'my-12.8';
|
||||
}
|
||||
.heti figure {
|
||||
--at-apply: 'my-12.8 flex flex-col items-center';
|
||||
}
|
||||
.heti figure>img {
|
||||
--at-apply: 'mt-0 mb-4.8';
|
||||
}
|
||||
.heti figure>figcaption {
|
||||
--at-apply: 'opacity-75 w-95% text-center';
|
||||
}
|
||||
.heti hr {
|
||||
width:30%;
|
||||
height:1px;
|
||||
margin-block-start:48px;
|
||||
margin-block-end:47px;
|
||||
margin-inline-start:auto;
|
||||
margin-inline-end:auto;
|
||||
border:0;
|
||||
background-color:#ccc
|
||||
}
|
||||
@media(prefers-color-scheme:dark) {
|
||||
.heti hr {
|
||||
background-color:#404040
|
||||
}
|
||||
}
|
||||
.heti p {
|
||||
margin-block-start:12px;
|
||||
margin-block-end:24px;
|
||||
text-align:justify
|
||||
}
|
||||
.heti p:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti p:not(:lang(zh)) {
|
||||
text-align:start
|
||||
}
|
||||
.heti pre {
|
||||
margin-block-start:12px;
|
||||
margin-block-end:12px;
|
||||
margin-inline-start:0;
|
||||
margin-inline-end:0;
|
||||
padding-block-start:12px;
|
||||
padding-block-end:12px;
|
||||
padding-inline-start:16px;
|
||||
padding-inline-end:16px;
|
||||
overflow:auto;
|
||||
white-space:pre;
|
||||
word-wrap:normal;
|
||||
border-radius:4px;
|
||||
background-color:rgba(0,0,0,.054)
|
||||
}
|
||||
@media(prefers-color-scheme:dark) {
|
||||
.heti pre {
|
||||
background-color:rgba(255,255,255,.054)
|
||||
}
|
||||
}.heti pre code {
|
||||
margin:0;
|
||||
padding:0;
|
||||
border:0;
|
||||
border-radius:0;
|
||||
background-color:rgba(0,0,0,0);
|
||||
color:inherit
|
||||
}
|
||||
.heti:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti:not(:lang(zh)) {
|
||||
letter-spacing:0
|
||||
}
|
||||
.heti a,.heti abbr,.heti code,.heti heti-spacing,.heti [lang=en-US] {
|
||||
letter-spacing:normal
|
||||
}
|
||||
.heti h1,.heti h2,.heti h3,.heti h4,.heti h5,.heti h6 {
|
||||
position:relative;
|
||||
margin:0;
|
||||
margin-block-start:24px;
|
||||
margin-block-end:12px;
|
||||
font-weight:600
|
||||
}
|
||||
.heti h1 {
|
||||
margin-block-end:24px;
|
||||
font-size:32px;
|
||||
line-height:48px
|
||||
}
|
||||
.heti h2 {
|
||||
font-size:24px;
|
||||
line-height:36px
|
||||
}
|
||||
.heti h3 {
|
||||
font-size:20px;
|
||||
line-height:36px
|
||||
}
|
||||
.heti h4 {
|
||||
font-size:18px;
|
||||
line-height:24px
|
||||
}
|
||||
.heti h5 {
|
||||
font-size:16px;
|
||||
line-height:24px
|
||||
}
|
||||
.heti h6 {
|
||||
font-size:14px;
|
||||
line-height:24px
|
||||
}
|
||||
.heti h1,.heti h2,.heti h3 {
|
||||
letter-spacing:.05em
|
||||
}
|
||||
.heti h1:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti h1:not(:lang(zh)),.heti h2:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti h2:not(:lang(zh)),.heti h3:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti h3:not(:lang(zh)) {
|
||||
letter-spacing:0
|
||||
}
|
||||
.heti h1+h2,.heti h2+h3,.heti h3+h4,.heti h4+h5,.heti h5+h6 {
|
||||
margin-block-start:12px
|
||||
}
|
||||
.heti ul,.heti ol,.heti dl {
|
||||
margin-block-start:12px;
|
||||
margin-block-end:24px
|
||||
}
|
||||
.heti ul,.heti ol {
|
||||
padding-inline-start:32px
|
||||
}
|
||||
.heti ul ul,.heti ul ol,.heti ol ul,.heti ol ol {
|
||||
margin-block-start:0;
|
||||
margin-block-end:0
|
||||
}
|
||||
.heti ul {
|
||||
list-style-type:disc
|
||||
}
|
||||
.heti ol {
|
||||
list-style-type:decimal
|
||||
}
|
||||
.heti ul ul,.heti ol ul {
|
||||
list-style-type:circle
|
||||
}
|
||||
.heti ul ul ul,.heti ul ol ul,.heti ol ul ul,.heti ol ol ul {
|
||||
list-style-type:square
|
||||
}
|
||||
.heti li {
|
||||
list-style-type:unset
|
||||
}
|
||||
.heti table {
|
||||
box-sizing:border-box;
|
||||
table-layout:fixed;
|
||||
margin-block-start:12px;
|
||||
margin-block-end:24px;
|
||||
margin-inline-start:auto;
|
||||
margin-inline-end:auto;
|
||||
border-collapse:collapse;
|
||||
border-width:1px;
|
||||
border-style:solid;
|
||||
border-color:#ccc;
|
||||
word-break:break-word
|
||||
}
|
||||
@media(prefers-color-scheme:dark) {
|
||||
.heti table {
|
||||
border-color:#404040
|
||||
}
|
||||
}.heti th,.heti td {
|
||||
padding-block-start:6px;
|
||||
padding-block-end:6px;
|
||||
padding-inline-start:8px;
|
||||
padding-inline-end:8px;
|
||||
border-width:1px;
|
||||
border-style:solid;
|
||||
border-color:#ccc
|
||||
}
|
||||
@media(prefers-color-scheme:dark) {
|
||||
.heti th,.heti td {
|
||||
border-color:#404040
|
||||
}
|
||||
}.heti caption {
|
||||
caption-side:bottom;
|
||||
margin-block-start:2px;
|
||||
margin-block-end:-4px;
|
||||
font-size:14px;
|
||||
line-height:24px
|
||||
}
|
||||
.heti b,.heti strong {
|
||||
font-weight:600
|
||||
}
|
||||
.heti code {
|
||||
margin-inline-start:.25em;
|
||||
margin-inline-end:.25em;
|
||||
font-size:.875em
|
||||
}
|
||||
.heti dfn {
|
||||
font-weight:600
|
||||
}
|
||||
.heti dfn:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti dfn:not(:lang(zh)) {
|
||||
font-weight:400
|
||||
}
|
||||
.heti em {
|
||||
font-weight:600
|
||||
}
|
||||
.heti figcaption {
|
||||
display:inline-block;
|
||||
vertical-align:top;
|
||||
font-size:14px;
|
||||
text-align:start
|
||||
}
|
||||
.heti i {
|
||||
font-style:italic
|
||||
}
|
||||
.heti ins,.heti u {
|
||||
padding-block-end:1px;
|
||||
border-block-end:1px solid;
|
||||
text-decoration:none
|
||||
}
|
||||
.heti mark {
|
||||
padding-block-start:2px;
|
||||
padding-block-end:2px;
|
||||
padding-inline-start:1px;
|
||||
padding-inline-end:1px;
|
||||
margin-inline-start:1px;
|
||||
margin-inline-end:1px;
|
||||
background-color:rgba(255,247,0,.88);
|
||||
color:inherit
|
||||
}
|
||||
@media(prefers-color-scheme:dark) {
|
||||
.heti mark {
|
||||
background-color:rgba(77,74,0,.88)
|
||||
}
|
||||
}.heti q {
|
||||
quotes:"「" "」" "『" "』"
|
||||
}
|
||||
.heti q:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti q:not(:lang(zh)) {
|
||||
quotes:initial;
|
||||
quotes:auto
|
||||
}
|
||||
.heti rt {
|
||||
font-size:.875em;
|
||||
font-weight:400
|
||||
}
|
||||
.heti small {
|
||||
font-size:.875em
|
||||
}
|
||||
.heti strong {
|
||||
font-weight:600
|
||||
}
|
||||
.heti sub,.heti sup {
|
||||
position:relative;
|
||||
margin-inline-start:.25em;
|
||||
margin-inline-end:.25em;
|
||||
font-size:.75em;
|
||||
font-style:normal;
|
||||
line-height:1;
|
||||
vertical-align:baseline
|
||||
}
|
||||
.heti sub {
|
||||
bottom:-0.25em
|
||||
}
|
||||
.heti sup {
|
||||
top:-0.5em
|
||||
}
|
||||
.heti sup:target,.heti sup a:target {
|
||||
background-color:#dbedff
|
||||
}
|
||||
@media(prefers-color-scheme:dark) {
|
||||
.heti sup:target,.heti sup a:target {
|
||||
background-color:#3a6188
|
||||
}
|
||||
}.heti summary {
|
||||
padding-inline-start:1em;
|
||||
outline:0;
|
||||
cursor:pointer
|
||||
}
|
||||
.heti summary::-webkit-details-marker {
|
||||
width:.6em;
|
||||
margin-inline-end:.4em
|
||||
}
|
||||
.heti address,.heti cite,.heti dfn,.heti dt,.heti em {
|
||||
font-style:normal
|
||||
}
|
||||
.heti address:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti address:not(:lang(zh)),.heti cite:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti cite:not(:lang(zh)),.heti dfn:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti dfn:not(:lang(zh)),.heti dt:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti dt:not(:lang(zh)),.heti em:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)),.heti em:not(:lang(zh)) {
|
||||
font-style:italic
|
||||
}
|
||||
.heti .heti del,.heti ins,.heti s,.heti u {
|
||||
margin-inline-start:1px;
|
||||
margin-inline-end:1px
|
||||
}
|
|
@ -2,7 +2,6 @@ import type { Theme } from 'unocss/preset-uno'
|
|||
import {
|
||||
defineConfig,
|
||||
presetAttributify,
|
||||
presetTypography,
|
||||
presetUno,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
|
@ -16,7 +15,6 @@ export default defineConfig({
|
|||
presets: [
|
||||
presetUno(),
|
||||
presetAttributify(),
|
||||
presetTypography(),
|
||||
presetTheme<Theme>({
|
||||
theme: {
|
||||
dark: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue