mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
feat(plugin-shikiji): add support for collapsed lines
This commit is contained in:
parent
48a6596297
commit
bb4ee6bb2d
@ -0,0 +1,15 @@
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
export function useCollapsedLines({
|
||||
selector = 'div[class*="language-"] > .collapsed-lines',
|
||||
}: { selector?: string } = {}): void {
|
||||
useEventListener('click', (e) => {
|
||||
const el = e.target as HTMLElement
|
||||
if (el.matches(selector)) {
|
||||
const parent = el.parentElement
|
||||
if (parent?.classList.toggle('collapsed')) {
|
||||
parent.scrollIntoView({ block: 'center', behavior: 'instant' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -2,9 +2,12 @@
|
||||
// v-pre block logic is in `../highlight.ts`
|
||||
import type { Markdown } from 'vuepress/markdown'
|
||||
import type { PreWrapperOptions } from '../types.js'
|
||||
import { resolveAttr, resolveLanguage } from '../utils/index.js'
|
||||
import { resolveAttr, resolveCollapsedLines, resolveLanguage } from '../utils/index.js'
|
||||
|
||||
export function preWrapperPlugin(md: Markdown, { preWrapper = true }: PreWrapperOptions = {}): void {
|
||||
export function preWrapperPlugin(
|
||||
md: Markdown,
|
||||
{ preWrapper = true, collapsedLines = false }: PreWrapperOptions = {},
|
||||
): void {
|
||||
const rawFence = md.renderer.rules.fence!
|
||||
|
||||
md.renderer.rules.fence = (...args) => {
|
||||
@ -16,17 +19,27 @@ export function preWrapperPlugin(md: Markdown, { preWrapper = true }: PreWrapper
|
||||
|
||||
const lang = resolveLanguage(info)
|
||||
const title = resolveAttr(info, 'title') || lang
|
||||
const languageClass = `${options.langPrefix}${lang}`
|
||||
const classes: string[] = [`${options.langPrefix}${lang}`]
|
||||
|
||||
let result = rawFence(...args)
|
||||
|
||||
if (!preWrapper) {
|
||||
// remove `<code>` attributes
|
||||
result = result.replace(/<code[\s\S]*?>/, '<code>')
|
||||
result = `<pre class="${languageClass}"${result.slice('<pre'.length)}`
|
||||
result = `<pre class="${classes.join(' ')}"${result.slice('<pre'.length)}`
|
||||
return result
|
||||
}
|
||||
const attrs: string[] = [
|
||||
`data-ext="${lang}"`,
|
||||
`data-title="${title}"`,
|
||||
]
|
||||
const collapsed = resolveCollapsedLines(info, collapsedLines)
|
||||
if (collapsed) {
|
||||
classes.push('has-collapsed', 'collapsed')
|
||||
attrs.push(`style="--vp-collapsed-lines:${collapsed}"`)
|
||||
result += `<div class="collapsed-lines"></div>`
|
||||
}
|
||||
|
||||
return `<div class="${languageClass}" data-ext="${lang}" data-title="${title}">${result}</div>`
|
||||
return `<div class="${classes.join(' ')}" ${attrs.join(' ')}>${result}</div>`
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ export async function prepareClientConfigFile(app: App, {
|
||||
`\
|
||||
${twoslash ? `import { enhanceTwoslash } from '${CLIENT_FOLDER}composables/twoslash.js'` : ''}
|
||||
${copyCode ? `import { useCopyCode } from '${CLIENT_FOLDER}composables/copy-code.js'` : ''}
|
||||
import { useCollapsedLines } from '${CLIENT_FOLDER}composables/collapsed-lines.js'
|
||||
|
||||
export default {
|
||||
${twoslash
|
||||
@ -30,6 +31,7 @@ export default {
|
||||
selector: __CC_SELECTOR__,
|
||||
duration: __CC_DURATION__,
|
||||
})
|
||||
useCollapsedLines()
|
||||
},`
|
||||
: ''}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import type { Plugin } from 'vuepress/core'
|
||||
import { getDirname } from 'vuepress/utils'
|
||||
import { isPlainObject } from 'vuepress/shared'
|
||||
import { highlight } from './highlight.js'
|
||||
import { highlight } from './highlight/index.js'
|
||||
import type {
|
||||
CopyCodeOptions,
|
||||
HighlighterOptions,
|
||||
@ -16,7 +15,8 @@ import {
|
||||
import { copyCodeButtonPlugin } from './copy-code-button/index.js'
|
||||
import { prepareClientConfigFile } from './prepareClientConfigFile.js'
|
||||
|
||||
export interface ShikiPluginOptions extends HighlighterOptions, LineNumberOptions, PreWrapperOptions {
|
||||
export interface ShikiPluginOptions
|
||||
extends HighlighterOptions, LineNumberOptions, PreWrapperOptions {
|
||||
/**
|
||||
* Add copy code button
|
||||
*
|
||||
@ -25,12 +25,11 @@ export interface ShikiPluginOptions extends HighlighterOptions, LineNumberOption
|
||||
copyCode?: boolean | CopyCodeOptions
|
||||
}
|
||||
|
||||
const __dirname = getDirname(import.meta.url)
|
||||
|
||||
export function shikiPlugin({
|
||||
preWrapper = true,
|
||||
lineNumbers = true,
|
||||
copyCode = true,
|
||||
collapsedLines = false,
|
||||
...options
|
||||
}: ShikiPluginOptions = {}): Plugin {
|
||||
const copyCodeOptions: CopyCodeOptions = isPlainObject(copyCode) ? copyCode : {}
|
||||
@ -54,9 +53,11 @@ export function shikiPlugin({
|
||||
md.options.highlight = await highlight(theme, options)
|
||||
|
||||
md.use(highlightLinesPlugin)
|
||||
md.use<PreWrapperOptions>(preWrapperPlugin, {
|
||||
md.use(preWrapperPlugin, {
|
||||
preWrapper,
|
||||
collapsedLines,
|
||||
})
|
||||
|
||||
if (preWrapper) {
|
||||
copyCodeButtonPlugin(md, app, copyCode)
|
||||
md.use<LineNumberOptions>(lineNumberPlugin, { lineNumbers })
|
||||
|
||||
@ -79,7 +79,7 @@ export interface HighlighterOptions {
|
||||
* Enable transformerRenderWhitespace
|
||||
* @default false
|
||||
*/
|
||||
whitespace?: boolean
|
||||
whitespace?: boolean | 'all' | 'boundary' | 'trailing'
|
||||
}
|
||||
|
||||
export interface LineNumberOptions {
|
||||
@ -99,6 +99,15 @@ export interface PreWrapperOptions {
|
||||
* - Required for title display of default theme
|
||||
*/
|
||||
preWrapper?: boolean
|
||||
|
||||
/**
|
||||
* Hide extra rows when exceeding a specific number of lines.
|
||||
*
|
||||
* `true` is equivalent to `15` .
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
collapsedLines?: number | boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
18
plugins/plugin-shikiji/src/node/utils/collapsedLines.ts
Normal file
18
plugins/plugin-shikiji/src/node/utils/collapsedLines.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export const COLLAPSED_LINES_REGEXP = /:collapsed-lines(?:=(\d+))?\b/
|
||||
export const NO_COLLAPSED_LINES_REGEXP = /:no-collapsed-lines\b/
|
||||
|
||||
const DEFAULT_LINES = 15
|
||||
|
||||
export function resolveCollapsedLines(info: string, defaultLines: boolean | number): number | false {
|
||||
if (NO_COLLAPSED_LINES_REGEXP.test(info))
|
||||
return false
|
||||
|
||||
const lines = defaultLines === true ? DEFAULT_LINES : defaultLines
|
||||
|
||||
const match = info.match(COLLAPSED_LINES_REGEXP)
|
||||
|
||||
if (match) {
|
||||
return Number(match[1]) || lines || DEFAULT_LINES
|
||||
}
|
||||
return lines ?? false
|
||||
}
|
||||
@ -2,3 +2,5 @@ export * from './attrsToLines.js'
|
||||
export * from './resolveAttr.js'
|
||||
export * from './resolveLanguage.js'
|
||||
export * from './lru.js'
|
||||
export * from './whitespace.js'
|
||||
export * from './collapsedLines.js'
|
||||
|
||||
@ -21,6 +21,7 @@ export default defineConfig(() => {
|
||||
entry: [
|
||||
'copy-code.ts',
|
||||
'twoslash.ts',
|
||||
'collapsed-lines.ts',
|
||||
].map(file => `./src/client/composables/${file}`),
|
||||
outDir: './lib/client/composables',
|
||||
external: [/.*\.css$/],
|
||||
|
||||
@ -310,3 +310,85 @@ html:not(.dark) .vp-code span {
|
||||
/* rtl:ignore */
|
||||
transform: translateX(calc(-100% - 1px));
|
||||
}
|
||||
|
||||
/*
|
||||
Collapsed lines
|
||||
--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.vp-doc div[class*="language-"].has-collapsed.collapsed {
|
||||
height: calc(var(--vp-collapsed-lines) * var(--vp-code-line-height) * var(--vp-code-font-size) + 62px);
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@property --vp-code-bg-collapsed-lines {
|
||||
inherits: false;
|
||||
initial-value: #fff;
|
||||
syntax: "<color>";
|
||||
}
|
||||
|
||||
.vp-doc div[class*="language-"].has-collapsed .collapsed-lines {
|
||||
--vp-code-bg-collapsed-lines: var(--vp-code-block-bg);
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
cursor: pointer;
|
||||
background: linear-gradient(to bottom, transparent 0%, var(--vp-code-bg-collapsed-lines) 50%, var(--vp-code-bg-collapsed-lines) 100%);
|
||||
transition: --vp-code-bg-collapsed-lines var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc div[class*="language-"].has-collapsed .collapsed-lines:hover {
|
||||
--vp-code-bg-collapsed-lines: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.vp-doc div[class*="language-"].has-collapsed .collapsed-lines::before {
|
||||
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' d='m18 12l-6 6l-6-6m12-6l-6 6l-6-6'/%3E%3C/svg%3E");
|
||||
--trans-rotate: 0deg;
|
||||
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
background-color: var(--vp-code-block-color);
|
||||
-webkit-mask-image: var(--icon);
|
||||
mask-image: var(--icon);
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-position: 50%;
|
||||
mask-position: 50%;
|
||||
-webkit-mask-size: 20px;
|
||||
mask-size: 20px;
|
||||
animation: code-collapsed-lines 1.2s infinite alternate-reverse ease-in-out;
|
||||
}
|
||||
|
||||
.vp-doc div[class*="language-"].has-collapsed:not(.collapsed) code {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.vp-doc div[class*="language-"].has-collapsed:not(.collapsed) .collapsed-lines:hover {
|
||||
--vp-code-bg-collapsed-lines: transparent;
|
||||
}
|
||||
|
||||
.vp-doc div[class*="language-"].has-collapsed:not(.collapsed) .collapsed-lines::before {
|
||||
--trans-rotate: 180deg;
|
||||
}
|
||||
|
||||
@keyframes code-collapsed-lines {
|
||||
0% {
|
||||
opacity: 0.3;
|
||||
transform: translateY(-2px) rotate(var(--trans-rotate));
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(2px) rotate(var(--trans-rotate));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user