mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-16 11:41:17 +02:00
refactor: optimize website performance and layout
- Replace ScrollButton with BackToTop component - Improve theme toggle and scrollbar event handling - Update layout and index page styling - Optimize client:load and client:idle directives - Remove deprecated Heti addon plugin
This commit is contained in:
parent
14f53a979a
commit
47951152d1
13 changed files with 164 additions and 559 deletions
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
|
@ -1175,8 +1175,8 @@ packages:
|
||||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
ansis@3.14.0:
|
ansis@3.15.0:
|
||||||
resolution: {integrity: sha512-R1LnSpYZWMDEFoAyCrfgToVz4ES25luDpjlZsUlD5GXdPWb91U+TZGkxWAOvt+7zWRY/ctOxhtTx5HUtL3qmbA==}
|
resolution: {integrity: sha512-zIcWDJ+Kwqxfdnogx66Gxzr0kVmCcRAdat9nlY2IHsshqTN4fBH6tMeRMPA/2w0rpBayIJvjQAaa2/4RDrNqwg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
anymatch@3.1.3:
|
anymatch@3.1.3:
|
||||||
|
@ -1289,8 +1289,8 @@ packages:
|
||||||
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
|
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001699:
|
caniuse-lite@1.0.30001700:
|
||||||
resolution: {integrity: sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==}
|
resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==}
|
||||||
|
|
||||||
ccount@2.0.1:
|
ccount@2.0.1:
|
||||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||||
|
@ -3767,7 +3767,7 @@ snapshots:
|
||||||
'@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
|
'@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
|
||||||
'@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
|
'@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
|
||||||
'@vitest/eslint-plugin': 1.1.31(@typescript-eslint/utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
|
'@vitest/eslint-plugin': 1.1.31(@typescript-eslint/utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
|
||||||
ansis: 3.14.0
|
ansis: 3.15.0
|
||||||
eslint: 9.20.1(jiti@2.4.2)
|
eslint: 9.20.1(jiti@2.4.2)
|
||||||
eslint-config-flat-gitignore: 2.0.0(eslint@9.20.1(jiti@2.4.2))
|
eslint-config-flat-gitignore: 2.0.0(eslint@9.20.1(jiti@2.4.2))
|
||||||
eslint-flat-config-utils: 2.0.1
|
eslint-flat-config-utils: 2.0.1
|
||||||
|
@ -4919,7 +4919,7 @@ snapshots:
|
||||||
|
|
||||||
ansi-styles@6.2.1: {}
|
ansi-styles@6.2.1: {}
|
||||||
|
|
||||||
ansis@3.14.0: {}
|
ansis@3.15.0: {}
|
||||||
|
|
||||||
anymatch@3.1.3:
|
anymatch@3.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5152,7 +5152,7 @@ snapshots:
|
||||||
|
|
||||||
browserslist@4.24.4:
|
browserslist@4.24.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001699
|
caniuse-lite: 1.0.30001700
|
||||||
electron-to-chromium: 1.5.101
|
electron-to-chromium: 1.5.101
|
||||||
node-releases: 2.0.19
|
node-releases: 2.0.19
|
||||||
update-browserslist-db: 1.1.2(browserslist@4.24.4)
|
update-browserslist-db: 1.1.2(browserslist@4.24.4)
|
||||||
|
@ -5172,7 +5172,7 @@ snapshots:
|
||||||
|
|
||||||
camelcase@8.0.0: {}
|
camelcase@8.0.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001699: {}
|
caniuse-lite@1.0.30001700: {}
|
||||||
|
|
||||||
ccount@2.0.1: {}
|
ccount@2.0.1: {}
|
||||||
|
|
||||||
|
|
77
src/components/BackToTop.astro
Normal file
77
src/components/BackToTop.astro
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<!-- Sentinel element for scroll detection -->
|
||||||
|
<div
|
||||||
|
id="top-sentinel"
|
||||||
|
class="pointer-events-none absolute left-0 top-0 h-px w-full"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="back-to-top-button"
|
||||||
|
aria-label="Back to top"
|
||||||
|
class="fixed bottom-8 right-8 h-10 w-10 rounded-full bg-background transition-all duration-300 ease-out"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
class="m-auto h-60% w-60%"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path d="M18 15l-6-6-6 6" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let observer: IntersectionObserver | null = null
|
||||||
|
let backToTopButton: HTMLButtonElement | null = null
|
||||||
|
|
||||||
|
function initBackToTop() {
|
||||||
|
// Get elements
|
||||||
|
const sentinel = document.getElementById('top-sentinel')
|
||||||
|
backToTopButton = document.getElementById('back-to-top-button') as HTMLButtonElement
|
||||||
|
|
||||||
|
if (!sentinel || !backToTopButton)
|
||||||
|
return
|
||||||
|
|
||||||
|
// Initialize IntersectionObserver
|
||||||
|
observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
backToTopButton?.classList.add('opacity-0', 'pointer-events-none', 'translate-y-4')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
backToTopButton?.classList.remove('opacity-0', 'pointer-events-none', 'translate-y-4')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0,
|
||||||
|
rootMargin: '30% 0% 0% 0%',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Observe sentinel
|
||||||
|
observer.observe(sentinel)
|
||||||
|
|
||||||
|
// Add click handler
|
||||||
|
backToTopButton.addEventListener('click', () => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
// Cleanup observer
|
||||||
|
if (observer) {
|
||||||
|
observer.disconnect()
|
||||||
|
observer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove event listeners
|
||||||
|
backToTopButton = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle page transitions
|
||||||
|
document.addEventListener('astro:page-load', initBackToTop)
|
||||||
|
document.addEventListener('astro:before-swap', cleanup)
|
||||||
|
</script>
|
|
@ -10,6 +10,8 @@
|
||||||
lightbox.destroy()
|
lightbox.destroy()
|
||||||
lightbox = null
|
lightbox = null
|
||||||
}
|
}
|
||||||
|
document.removeEventListener('astro:page-load', createPhotoSwipe)
|
||||||
|
document.removeEventListener('astro:before-swap', cleanup)
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPhotoSwipe() {
|
function createPhotoSwipe() {
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
<button
|
|
||||||
id="scroll-button"
|
|
||||||
aria-label="Scroll to bottom"
|
|
||||||
class="fixed bottom-8 right-8 h-10 w-10 rounded-full bg-background p-0 opacity-0 transition-all duration-300"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="m-auto h-6 w-6 c-primary transition-transform"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<path d="M15 18l-6-6 6-6" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const SCROLL_DISPLAY_THRESHOLD = 200
|
|
||||||
const DIRECTION_CHANGE_THRESHOLD = 200
|
|
||||||
const BOTTOM_PROXIMITY_THRESHOLD = 50
|
|
||||||
|
|
||||||
// Initialize scroll button functionality and return cleanup function
|
|
||||||
function initScrollButton(): (() => void) | void {
|
|
||||||
const button = document.getElementById('scroll-button')
|
|
||||||
if (!button)
|
|
||||||
return
|
|
||||||
|
|
||||||
const buttonElement = button as HTMLButtonElement
|
|
||||||
let isScrollingDown = true
|
|
||||||
let lastScrollPosition = window.scrollY
|
|
||||||
let scrollTimeout: number | null = null
|
|
||||||
|
|
||||||
function updateButton() {
|
|
||||||
const currentScrollPosition = window.scrollY
|
|
||||||
const scrollDifference = currentScrollPosition - lastScrollPosition
|
|
||||||
const maxScroll = document.documentElement.scrollHeight - window.innerHeight
|
|
||||||
const isNearBottom = maxScroll - currentScrollPosition < BOTTOM_PROXIMITY_THRESHOLD
|
|
||||||
|
|
||||||
// Update button direction and behavior when scroll direction changes or near bottom
|
|
||||||
if (Math.abs(scrollDifference) > DIRECTION_CHANGE_THRESHOLD || isNearBottom) {
|
|
||||||
isScrollingDown = isNearBottom ? false : scrollDifference > 0
|
|
||||||
lastScrollPosition = currentScrollPosition
|
|
||||||
|
|
||||||
const svg = buttonElement.querySelector('svg')
|
|
||||||
if (svg instanceof SVGElement) {
|
|
||||||
svg.style.transform = isScrollingDown ? 'rotate(-90deg)' : 'rotate(90deg)'
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonElement.setAttribute('aria-label', isScrollingDown ? 'Scroll to bottom' : 'Scroll to top')
|
|
||||||
buttonElement.onclick = () => {
|
|
||||||
window.scrollTo({
|
|
||||||
top: isScrollingDown ? maxScroll : 0,
|
|
||||||
behavior: 'smooth',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldShow = currentScrollPosition > SCROLL_DISPLAY_THRESHOLD
|
|
||||||
buttonElement.classList.toggle('opacity-100', shouldShow)
|
|
||||||
buttonElement.classList.toggle('invisible', !shouldShow)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle scroll events with requestAnimationFrame for performance
|
|
||||||
function scrollHandler() {
|
|
||||||
if (scrollTimeout) {
|
|
||||||
window.cancelAnimationFrame(scrollTimeout)
|
|
||||||
}
|
|
||||||
scrollTimeout = window.requestAnimationFrame(updateButton)
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('scroll', scrollHandler, { passive: true })
|
|
||||||
updateButton()
|
|
||||||
|
|
||||||
// Return cleanup function to remove event listeners
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('scroll', scrollHandler)
|
|
||||||
if (scrollTimeout) {
|
|
||||||
window.cancelAnimationFrame(scrollTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cleanup: (() => void) | null = null
|
|
||||||
|
|
||||||
document.addEventListener('astro:page-load', () => {
|
|
||||||
cleanup?.()
|
|
||||||
cleanup = initScrollButton() || null
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener('astro:before-swap', () => cleanup?.())
|
|
||||||
</script>
|
|
|
@ -41,7 +41,6 @@ document.addEventListener('astro:before-swap', () => {
|
||||||
scrollbarsInstance = null
|
scrollbarsInstance = null
|
||||||
})
|
})
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', initScrollbar)
|
|
||||||
document.addEventListener('astro:page-load', initScrollbar)
|
document.addEventListener('astro:page-load', initScrollbar)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,29 @@
|
||||||
|
<button
|
||||||
|
aria-pressed="false"
|
||||||
|
aria-label="Theme Toggle Button"
|
||||||
|
class="absolute right-[calc(9.94vw-1.18rem)] top-[calc(7.3vw+2.6rem)] z-99 aspect-square w-7 c-secondary [@supports(-webkit-touch-callout:none)]:top-[calc(7.3vw+2.2rem)] active:scale-90"
|
||||||
|
lg="hidden"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path d="m12 1c-6.1 0-11 4.9-11 11s4.9 11 11 11 11-4.9 11-11-4.9-11-11-11m0 20c-5.8 0-10.5-4-10.5-9s4.7-9 10.5-9 10.5 4 10.5 9-4.7 9-10.5 9" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Initialize theme toggle button
|
const theme = localStorage.getItem('theme')
|
||||||
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
|
document.documentElement.classList.toggle('dark', theme === 'dark')
|
||||||
themeToggle.setAttribute('aria-pressed', String(document.documentElement.classList.contains('dark')))
|
|
||||||
|
|
||||||
function switchTheme() {
|
function switchTheme() {
|
||||||
document.body.removeAttribute('data-restore-theme')
|
document.body.removeAttribute('data-restore-theme')
|
||||||
const isDark = document.documentElement.classList.toggle('dark')
|
const isDark = document.documentElement.classList.toggle('dark')
|
||||||
|
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
|
||||||
|
if (themeToggle) {
|
||||||
themeToggle.setAttribute('aria-pressed', String(isDark))
|
themeToggle.setAttribute('aria-pressed', String(isDark))
|
||||||
|
}
|
||||||
localStorage.setItem('theme', isDark ? 'dark' : 'light')
|
localStorage.setItem('theme', isDark ? 'dark' : 'light')
|
||||||
document.dispatchEvent(new Event('theme-changed'))
|
document.dispatchEvent(new Event('theme-changed'))
|
||||||
}
|
}
|
||||||
|
@ -17,7 +34,6 @@ function toggleTheme() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set temporary transition name for theme toggle
|
|
||||||
document.documentElement.style.setProperty('view-transition-name', 'theme-transition')
|
document.documentElement.style.setProperty('view-transition-name', 'theme-transition')
|
||||||
document.documentElement.setAttribute('data-theme-transition', '')
|
document.documentElement.setAttribute('data-theme-transition', '')
|
||||||
|
|
||||||
|
@ -25,7 +41,6 @@ function toggleTheme() {
|
||||||
switchTheme()
|
switchTheme()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Clean up after transition
|
|
||||||
transition.finished.then(() => {
|
transition.finished.then(() => {
|
||||||
document.documentElement.style.removeProperty('view-transition-name')
|
document.documentElement.style.removeProperty('view-transition-name')
|
||||||
document.documentElement.removeAttribute('data-theme-transition')
|
document.documentElement.removeAttribute('data-theme-transition')
|
||||||
|
@ -36,38 +51,44 @@ function syncTheme() {
|
||||||
document.documentElement.setAttribute('data-restore-theme', 'true')
|
document.documentElement.setAttribute('data-restore-theme', 'true')
|
||||||
const theme = localStorage.getItem('theme')
|
const theme = localStorage.getItem('theme')
|
||||||
document.documentElement.classList.toggle('dark', theme === 'dark')
|
document.documentElement.classList.toggle('dark', theme === 'dark')
|
||||||
|
|
||||||
|
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
|
||||||
|
if (themeToggle) {
|
||||||
themeToggle.setAttribute('aria-pressed', String(theme === 'dark'))
|
themeToggle.setAttribute('aria-pressed', String(theme === 'dark'))
|
||||||
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
document.documentElement.removeAttribute('data-restore-theme')
|
document.documentElement.removeAttribute('data-restore-theme')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('astro:page-load', () => {
|
// 事件处理函数
|
||||||
|
function handleAfterSwap() {
|
||||||
|
syncTheme()
|
||||||
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
|
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
|
||||||
themeToggle.setAttribute('aria-pressed', String(document.documentElement.classList.contains('dark')))
|
if (themeToggle) {
|
||||||
themeToggle.addEventListener('click', toggleTheme)
|
themeToggle.addEventListener('click', toggleTheme)
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('astro:after-swap', syncTheme)
|
function handlePageShow(event: PageTransitionEvent) {
|
||||||
window.addEventListener('popstate', syncTheme)
|
|
||||||
window.addEventListener('pageshow', (event) => {
|
|
||||||
if (event.persisted) {
|
if (event.persisted) {
|
||||||
syncTheme()
|
syncTheme()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
<button
|
// 集中管理所有事件监听器
|
||||||
aria-pressed="false"
|
function initThemeEvents() {
|
||||||
aria-label="Theme Toggle Button"
|
const themeToggle = document.querySelector('button[aria-pressed]') as HTMLButtonElement
|
||||||
class="absolute right-[calc(9.94vw-1.18rem)] top-[calc(7.3vw+2.6rem)] z-99 aspect-square w-7 c-secondary [@supports(-webkit-touch-callout:none)]:top-[calc(7.3vw+2.2rem)] active:scale-90"
|
if (themeToggle) {
|
||||||
lg="hidden"
|
themeToggle.addEventListener('click', toggleTheme)
|
||||||
>
|
}
|
||||||
<svg
|
|
||||||
viewBox="0 0 24 24"
|
document.addEventListener('astro:after-swap', handleAfterSwap)
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
window.addEventListener('popstate', syncTheme)
|
||||||
fill="currentColor"
|
window.addEventListener('pageshow', handlePageShow)
|
||||||
>
|
}
|
||||||
<path d="m12 1c-6.1 0-11 4.9-11 11s4.9 11 11 11 11-4.9 11-11-4.9-11-11-11m0 20c-5.8 0-10.5-4-10.5-9s4.7-9 10.5-9 10.5 4 10.5 9-4.7 9-10.5 9" />
|
|
||||||
</svg>
|
// 初始化
|
||||||
</button>
|
initThemeEvents()
|
||||||
|
</script>
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const themeConfig: ThemeConfig = {
|
||||||
global: {
|
global: {
|
||||||
locale: 'zh', // support 'zh', 'zh-tw', 'ja', 'en', 'es', 'ru'. Default language setting
|
locale: 'zh', // support 'zh', 'zh-tw', 'ja', 'en', 'es', 'ru'. Default language setting
|
||||||
moreLocale: ['zh-tw', 'ja', 'en', 'es', 'ru'], // ['zh', 'zh-tw', 'ja', 'en', 'es', 'ru']. Not fill in the default locale code again
|
moreLocale: ['zh-tw', 'ja', 'en', 'es', 'ru'], // ['zh', 'zh-tw', 'ja', 'en', 'es', 'ru']. Not fill in the default locale code again
|
||||||
fontStyle: 'serif', // sans, serif. Font styles for post content
|
fontStyle: 'sans', // sans, serif. Font styles for post content
|
||||||
titleSpace: 3, // 1, 2, 3. Space between title and subtitle
|
titleSpace: 3, // 1, 2, 3. Space between title and subtitle
|
||||||
},
|
},
|
||||||
// GLOBAL SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
|
// GLOBAL SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
|
||||||
|
|
|
@ -80,7 +80,7 @@ function getCurrentTheme() {
|
||||||
return document.documentElement.classList.contains('dark')
|
return document.documentElement.classList.contains('dark')
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateThemeColor(isDark) {
|
function updateMetaTheme(isDark) {
|
||||||
const metaThemeColor = document.querySelector('meta[name="theme-color"]')
|
const metaThemeColor = document.querySelector('meta[name="theme-color"]')
|
||||||
if (metaThemeColor) {
|
if (metaThemeColor) {
|
||||||
metaThemeColor.setAttribute('content', isDark ? darkMode : lightMode)
|
metaThemeColor.setAttribute('content', isDark ? darkMode : lightMode)
|
||||||
|
@ -89,7 +89,7 @@ function updateThemeColor(isDark) {
|
||||||
|
|
||||||
function syncTheme() {
|
function syncTheme() {
|
||||||
const isDark = getCurrentTheme()
|
const isDark = getCurrentTheme()
|
||||||
updateThemeColor(isDark)
|
updateMetaTheme(isDark)
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTheme() {
|
function initTheme() {
|
||||||
|
@ -105,7 +105,7 @@ function initTheme() {
|
||||||
|
|
||||||
const isDark = theme === 'dark'
|
const isDark = theme === 'dark'
|
||||||
document.documentElement.classList.toggle('dark', isDark)
|
document.documentElement.classList.toggle('dark', isDark)
|
||||||
updateThemeColor(isDark)
|
updateMetaTheme(isDark)
|
||||||
}
|
}
|
||||||
|
|
||||||
initTheme()
|
initTheme()
|
||||||
|
@ -113,7 +113,7 @@ initTheme()
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||||
if (!localStorage.getItem('theme')) {
|
if (!localStorage.getItem('theme')) {
|
||||||
document.documentElement.classList.toggle('dark', e.matches)
|
document.documentElement.classList.toggle('dark', e.matches)
|
||||||
updateThemeColor(e.matches)
|
updateMetaTheme(e.matches)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
import BackToTop from '@/components/BackToTop.astro'
|
||||||
import Footer from '@/components/Footer.astro'
|
import Footer from '@/components/Footer.astro'
|
||||||
import LanguageSwitcher from '@/components/LanguageSwitcher.astro'
|
import LanguageSwitcher from '@/components/LanguageSwitcher.astro'
|
||||||
import MainHeader from '@/components/MainHeader.astro'
|
import MainHeader from '@/components/MainHeader.astro'
|
||||||
|
@ -6,7 +7,6 @@ import MobileHeader from '@/components/MobileHeader.astro'
|
||||||
import Navigation from '@/components/Navbar.astro'
|
import Navigation from '@/components/Navbar.astro'
|
||||||
import PhotoSwipe from '@/components/PhotoSwipe.astro'
|
import PhotoSwipe from '@/components/PhotoSwipe.astro'
|
||||||
import Scrollbar from '@/components/Scrollbar.astro'
|
import Scrollbar from '@/components/Scrollbar.astro'
|
||||||
import ScrollButton from '@/components/ScrollButton.astro'
|
|
||||||
import ThemeToggle from '@/components/ThemeToggle.astro'
|
import ThemeToggle from '@/components/ThemeToggle.astro'
|
||||||
import themeConfig from '@/config'
|
import themeConfig from '@/config'
|
||||||
import Head from '@/layouts/Head.astro'
|
import Head from '@/layouts/Head.astro'
|
||||||
|
@ -35,8 +35,8 @@ const fontStyle = themeConfig.global.fontStyle === 'serif' ? 'font-serif' : 'fon
|
||||||
<Head {postTitle} {postDescription} {postImage} />
|
<Head {postTitle} {postDescription} {postImage} />
|
||||||
<body data-overlayscrollbars-initialize>
|
<body data-overlayscrollbars-initialize>
|
||||||
<div
|
<div
|
||||||
class="mx-a max-w-492 min-h-dvh"
|
class="mx-auto max-w-313.344 min-w-128 min-h-dvh"
|
||||||
p="x-[calc(9.942vw-1.18rem)] y-[calc(7.3vw+0.52rem)] lg:(x-36 y-[max(10.4vh,7.8rem)])"
|
p="x-[min(7.25vw,5.568rem)] y-17.6 lg:(x-36 y-[max(10.4vh,7.8rem)])"
|
||||||
lg="grid cols-[1fr_22rem] rows-1 gap-[min(calc(16.83vw-6.27rem),18rem)]"
|
lg="grid cols-[1fr_22rem] rows-1 gap-[min(calc(16.83vw-6.27rem),18rem)]"
|
||||||
>
|
>
|
||||||
<div class={!isHome && isPost ? 'hidden lg:block' : ''}>
|
<div class={!isHome && isPost ? 'hidden lg:block' : ''}>
|
||||||
|
@ -51,10 +51,10 @@ const fontStyle = themeConfig.global.fontStyle === 'serif' ? 'font-serif' : 'fon
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<ThemeToggle />
|
<Scrollbar client:load />
|
||||||
|
<ThemeToggle client:idle />
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
<Scrollbar />
|
<BackToTop client:idle />
|
||||||
<PhotoSwipe />
|
<PhotoSwipe client:idle />
|
||||||
<ScrollButton />
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -47,7 +47,7 @@ const postsByYear = await getPostsByYear(lang)
|
||||||
{/* Post Title */}
|
{/* Post Title */}
|
||||||
<a href={`/${lang}/posts/${post.data.slug || post.slug}`}>{post.data.title}</a>
|
<a href={`/${lang}/posts/${post.data.slug || post.slug}`}>{post.data.title}</a>
|
||||||
{/* Post Date */}
|
{/* Post Date */}
|
||||||
<time class="block text-5.6 leading-7 font-navbar opacity-30 lg:inline lg:before:content-['_']" aria-hidden="true">
|
<time class="block text-5.6 leading-7 font-navbar opacity-30 lg:inline lg:before:content-['_']">
|
||||||
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
{post.data.published.toLocaleDateString('en-US', { month: '2-digit', day: '2-digit' }).replace('/', '-')}
|
||||||
{post.remarkPluginFrontmatter?.minutes && <span class="ml-2"> {post.remarkPluginFrontmatter.minutes} min</span>}
|
{post.remarkPluginFrontmatter?.minutes && <span class="ml-2"> {post.remarkPluginFrontmatter.minutes} min</span>}
|
||||||
</time>
|
</time>
|
||||||
|
|
|
@ -36,7 +36,7 @@ const postsByYear = await getPostsByYear()
|
||||||
{posts.map(post => (
|
{posts.map(post => (
|
||||||
// Single Post
|
// Single Post
|
||||||
<li class="mt-7">
|
<li class="mt-7">
|
||||||
{/* Post Title */}
|
|
||||||
<a
|
<a
|
||||||
class="hover:c-primary"
|
class="hover:c-primary"
|
||||||
href={`/posts/${post.data.slug || post.slug}`}
|
href={`/posts/${post.data.slug || post.slug}`}
|
||||||
|
@ -45,9 +45,9 @@ const postsByYear = await getPostsByYear()
|
||||||
>
|
>
|
||||||
{post.data.title}
|
{post.data.title}
|
||||||
</a>
|
</a>
|
||||||
{/* Post Date */}
|
|
||||||
<div
|
<div
|
||||||
class="mb-9 block text-5.6 leading-11 font-time lg:ml-4 lg:inline"
|
class="mb-9 text-5.6 leading-11 font-time lg:(hidden)"
|
||||||
transition:name={`time-${post.data.slug || post.slug}`}
|
transition:name={`time-${post.data.slug || post.slug}`}
|
||||||
data-disable-transition-on-theme
|
data-disable-transition-on-theme
|
||||||
>
|
>
|
||||||
|
@ -58,6 +58,16 @@ const postsByYear = await getPostsByYear()
|
||||||
{post.remarkPluginFrontmatter?.minutes} min
|
{post.remarkPluginFrontmatter?.minutes} min
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-9 hidden text-5.6 leading-11 font-time lg:(ml-4 inline)">
|
||||||
|
<time>
|
||||||
|
{post.data.published.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-')}
|
||||||
|
</time>
|
||||||
|
<span class="ml-2">
|
||||||
|
{post.remarkPluginFrontmatter?.minutes} min
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,412 +0,0 @@
|
||||||
/* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
|
@ -4,7 +4,6 @@
|
||||||
* Author:Sivan [sun.sivan@gmail.com]
|
* Author:Sivan [sun.sivan@gmail.com]
|
||||||
*/
|
*/
|
||||||
.heti {
|
.heti {
|
||||||
max-width:42em;
|
|
||||||
line-height:1.5;
|
line-height:1.5;
|
||||||
overflow-wrap:break-word;
|
overflow-wrap:break-word;
|
||||||
word-wrap:break-word;
|
word-wrap:break-word;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue