mirror of https://github.com/YunYouJun/valaxy
feat: collapse for code (#284)
* feat: collapse for code * feat: configure codeHeightLimit in frontmatter
This commit is contained in:
parent
492e675e1f
commit
cf81897ad8
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: Code height limit
|
||||
title_zh-CN: 代码块高度限制
|
||||
toc: true
|
||||
categories:
|
||||
- examples
|
||||
codeHeightLimit: 300
|
||||
---
|
||||
|
||||
## Configure for single page
|
||||
|
||||
You can configure it in frontmatter. For example:
|
||||
|
||||
```md
|
||||
---
|
||||
codeHeightLimit: 300
|
||||
---
|
||||
```
|
||||
|
||||
This is a code block that exceeds the height limit.
|
||||
|
||||
```ts
|
||||
import { defineValaxyConfig } from 'valaxy'
|
||||
import type { ThemeConfig } from 'valaxy-theme-yun'
|
||||
|
||||
const safelist = [
|
||||
'i-ri-home-line',
|
||||
]
|
||||
|
||||
export default defineValaxyConfig<ThemeConfig>({
|
||||
// site config see site.config.ts or write in siteConfig
|
||||
// siteConfig: {},
|
||||
|
||||
theme: 'yun',
|
||||
themeConfig: {
|
||||
|
||||
banner: {
|
||||
enable: true,
|
||||
title: '云游君的小站',
|
||||
},
|
||||
|
||||
notice: {
|
||||
enable: true,
|
||||
content: '公告测试',
|
||||
},
|
||||
},
|
||||
|
||||
unocss: {
|
||||
safelist,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Configure for the entire website
|
||||
|
||||
Add codeHeightLimit field in site.config.ts
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { defineSiteConfig } from 'valaxy'
|
||||
|
||||
export default defineSiteConfig({
|
||||
// ignore other configuration
|
||||
codeHeightLimit: 300
|
||||
})
|
||||
```
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, onUpdated, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { onContentUpdated, runContentUpdated, useAplayer, useCodePen, useCopyCode, useMediumZoom, wrapTable } from 'valaxy'
|
||||
import { onContentUpdated, runContentUpdated, useAplayer, useCodePen, useCollapseCode, useCopyCode, useMediumZoom, wrapTable } from 'valaxy'
|
||||
import type { Post } from 'valaxy'
|
||||
import { useVanillaLazyLoad } from '../composables/features/vanilla-lazyload'
|
||||
import { useCodeGroups } from '../composables/codeGroups'
|
||||
|
@ -35,6 +35,7 @@ if (props.frontmatter.codepen)
|
|||
|
||||
useCopyCode()
|
||||
useCodeGroups()
|
||||
useCollapseCode()
|
||||
|
||||
if (typeof props.frontmatter.medium_zoom === 'undefined' || props.frontmatter.medium_zoom)
|
||||
useMediumZoom()
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import { isClient } from '@vueuse/core'
|
||||
import { onMounted } from 'vue'
|
||||
import { useFrontmatter, useSiteConfig } from 'valaxy'
|
||||
|
||||
export function useCollapseCode() {
|
||||
const config = useSiteConfig()
|
||||
const frontmatter = useFrontmatter()
|
||||
|
||||
if (isClient) {
|
||||
window.addEventListener('click', (e) => {
|
||||
const el = e.target as HTMLElement
|
||||
if (el.matches('[class*="language-"] > button.collapse')) {
|
||||
const parent = el.parentElement
|
||||
parent?.removeAttribute('style')
|
||||
parent?.classList.remove('folded')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// determine whether to add folded class name
|
||||
onMounted(() => {
|
||||
const els = document.querySelectorAll('div[class*="language-"]')
|
||||
const siteConfigLimit = config.value.codeHeightLimit
|
||||
const frontmatterLimit = frontmatter.value.codeHeightLimit
|
||||
let codeHeightLimit: number
|
||||
|
||||
if (typeof frontmatterLimit !== 'number' || frontmatterLimit <= 0) {
|
||||
if (siteConfigLimit === undefined || siteConfigLimit <= 0)
|
||||
return
|
||||
else
|
||||
codeHeightLimit = siteConfigLimit
|
||||
}
|
||||
else {
|
||||
codeHeightLimit = frontmatterLimit
|
||||
}
|
||||
|
||||
for (const el of Array.from(els)) {
|
||||
if (el.scrollHeight > codeHeightLimit)
|
||||
el.classList.add('folded')
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
export * from './copy-code'
|
||||
export * from './medium-zoom'
|
||||
export * from './collapse-code'
|
||||
|
|
|
@ -38,6 +38,7 @@ html:not(.dark) .vp-code-dark {
|
|||
}
|
||||
|
||||
div[class*="language-"] {
|
||||
overflow-y: hidden;
|
||||
position: relative;
|
||||
background-color: var(--va-code-block-bg);
|
||||
overflow-x: auto;
|
||||
|
@ -220,4 +221,33 @@ html:not(.dark) .vp-code-dark {
|
|||
content: '+';
|
||||
color: var(--va-code-line-diff-add-symbol-color);
|
||||
}
|
||||
|
||||
// collapse
|
||||
[class*='language-'] > button.collapse {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,#fff 100%);
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: var(--va-icon-collapse);
|
||||
background-position: 50%;
|
||||
background-size: 16px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
[class*='language-'].folded > button.collapse {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ $c-primary: #0078e7 !default;
|
|||
:root {
|
||||
--va-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' class='h-6 w-6' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
|
||||
--va-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' class='h-6 w-6' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
|
||||
--va-icon-collapse: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='32'%20height='32' stroke='rgba(128,128,128,1)' viewBox='0%200%2024%2024'%3E%3Cpath%20fill='currentColor'%20d='m12%2016.175l3.9-3.875q.275-.275.688-.288t.712.288q.275.275.275.7t-.275.7l-4.6%204.6q-.15.15-.325.213t-.375.062q-.2%200-.375-.063T11.3%2018.3l-4.6-4.6q-.275-.275-.288-.687T6.7%2012.3q.275-.275.7-.275t.7.275l3.9%203.875Zm0-6L15.9%206.3q.275-.275.688-.287t.712.287q.275.275.275.7t-.275.7l-4.6%204.6q-.15.15-.325.213t-.375.062q-.2%200-.375-.062T11.3%2012.3L6.7%207.7q-.275-.275-.288-.688T6.7%206.3q.275-.275.7-.275t.7.275l3.9%203.875Z'/%3E%3C/svg%3E")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -53,10 +53,11 @@ export async function setupMarkdownPlugins(
|
|||
) {
|
||||
const mdOptions = options?.config.markdown || {}
|
||||
const theme = mdOptions.theme ?? defaultCodeTheme
|
||||
const siteConfig = options?.config.siteConfig || {}
|
||||
|
||||
// custom plugins
|
||||
md.use(highlightLinePlugin)
|
||||
.use(preWrapperPlugin, { theme })
|
||||
.use(preWrapperPlugin, { theme, siteConfig })
|
||||
.use(snippetPlugin, options?.userRoot)
|
||||
.use(containerPlugin, {
|
||||
...mdOptions.blocks,
|
||||
|
|
|
@ -93,6 +93,21 @@ function inferDescription(frontmatter: Record<string, any>) {
|
|||
return (head && getHeadMetaContent(head, 'description')) || ''
|
||||
}
|
||||
|
||||
function handleCodeHeightlimit(mainContentMd: string, options: ResolvedValaxyOptions, codeHeightLimit?: number): string {
|
||||
if (typeof codeHeightLimit !== 'number' || codeHeightLimit <= 0)
|
||||
return mainContentMd
|
||||
|
||||
const siteConfigLimit = options.config.siteConfig.codeHeightLimit
|
||||
mainContentMd = mainContentMd.replaceAll(/<div.+class="language-\w+">/g, (matchStr) => {
|
||||
if (siteConfigLimit !== undefined && siteConfigLimit > 0)
|
||||
matchStr = matchStr.replace(/\d+/, codeHeightLimit.toString())
|
||||
else matchStr = `${matchStr.slice(0, 5)}style="max-height: ${codeHeightLimit}px;"${matchStr.slice(5)}`
|
||||
|
||||
return matchStr
|
||||
})
|
||||
return mainContentMd
|
||||
}
|
||||
|
||||
export async function createMarkdownToVueRenderFn(
|
||||
options: ResolvedValaxyOptions,
|
||||
srcDir: string,
|
||||
|
@ -254,6 +269,9 @@ export async function createMarkdownToVueRenderFn(
|
|||
replaceRegex,
|
||||
vueTemplateBreaker,
|
||||
)
|
||||
|
||||
mainContentMd = handleCodeHeightlimit(mainContentMd, options, frontmatter.codeHeightLimit)
|
||||
|
||||
// handle mainContent, encrypt
|
||||
const { config: { siteConfig: { encrypt } } } = options
|
||||
if (encrypt.enable) {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// ref vitepress/packages/vitepress/src/node/markdown/plugins/preWrapper.ts
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import type { ThemeOptions } from '../../types'
|
||||
import type { SiteConfig } from '../../../../types/config'
|
||||
|
||||
export interface Options {
|
||||
theme: ThemeOptions
|
||||
siteConfig?: SiteConfig
|
||||
}
|
||||
|
||||
export function extractLang(info: string) {
|
||||
|
@ -38,6 +40,14 @@ export function extractTitle(info: string, html = false) {
|
|||
return info.match(/\[(.*)\]/)?.[1] || extractLang(info) || 'txt'
|
||||
}
|
||||
|
||||
function getCodeHeightLimitStyle(options: Options) {
|
||||
const codeHeightLimit = options?.siteConfig?.codeHeightLimit
|
||||
if (codeHeightLimit === undefined || codeHeightLimit <= 0)
|
||||
return ''
|
||||
|
||||
return `style="max-height: ${codeHeightLimit}px;"`
|
||||
}
|
||||
|
||||
// markdown-it plugin for wrapping <pre> ... </pre>.
|
||||
//
|
||||
// If your plugin was chained before preWrapper, you can add additional element directly.
|
||||
|
@ -57,8 +67,11 @@ export function preWrapperPlugin(md: MarkdownIt, options: Options) {
|
|||
const lang = extractLang(token.info)
|
||||
const rawCode = fence(...args)
|
||||
|
||||
return `<div class="language-${lang}${getAdaptiveThemeMarker(options)}${
|
||||
return `
|
||||
<div ${getCodeHeightLimitStyle(options)} class="language-${lang}${getAdaptiveThemeMarker(options)}${
|
||||
/ active( |$)/.test(token.info) ? ' active' : ''
|
||||
}"><button title="Copy Code" class="copy"></button><span class="lang">${lang}</span>${rawCode}</div>`
|
||||
}">
|
||||
<button title="Copy Code" class="copy"></button><span class="lang">${lang}</span><button class="collapse"></button>${rawCode}
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -307,6 +307,12 @@ export interface SiteConfig {
|
|||
*/
|
||||
// password: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @description:en-US Limit the height of the code block in px
|
||||
* @description:zh-CN 限制代码块的高度,单位是 px
|
||||
*/
|
||||
codeHeightLimit?: number
|
||||
}
|
||||
|
||||
export type PartialDeep<T> = {
|
||||
|
|
|
@ -162,6 +162,11 @@ export interface PageFrontMatter extends Record<string, any> {
|
|||
* @description:zh-CN 加密后的相册
|
||||
*/
|
||||
encryptedPhotos?: string
|
||||
/**
|
||||
* @description:en-US Limit the height of the code block in px
|
||||
* @description:zh-CN 限制代码块的高度,单位是 px
|
||||
*/
|
||||
codeHeightLimit?: number
|
||||
}
|
||||
|
||||
export interface PostFrontMatter extends PageFrontMatter {
|
||||
|
|
Loading…
Reference in New Issue