pull/3877/merge
ToBinio 11 months ago committed by GitHub
commit e7dcbe302f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -619,6 +619,51 @@ const line3 = 'This is line 3'
const line4 = 'This is line 4'
```
## Code Modal
You can enable codeModals for each code blocks via config:
```js
export default {
markdown: {
codeModal: true
}
}
```
Please see [`markdown` options](../reference/site-config#markdown) for more details.
You can add `:modal` / `:no-modal` mark in your fenced code blocks to override the value set in config.
**Input**
````md:modal
```js:modal
export default {
data () {
return {
msg: 'Code with very long lines can often be hard to read in code blocks. Code modals can make seeing the whole content possible.',
lorem: 'ipsum',
}
}
}
```
````
**Output**
```js:modal
export default {
data () {
return {
msg: 'Code with very long lines can often be hard to read in code blocks. Code modals can make seeing the whole content possible.',
lorem: 'ipsum',
}
}
}
```
## Import Code Snippets
You can import code snippets from existing files via following syntax:

@ -0,0 +1,51 @@
import { inBrowser } from 'vitepress'
export function useCodeModal() {
if (inBrowser) {
window.addEventListener('click', (e) => {
const el = e.target as HTMLElement
if (el.matches('div[class*="language-"] > button.modal')) {
//remove focus from button
el.blur()
const parent = el.parentElement
const sibling = el.nextElementSibling
if (!parent || !sibling) {
return
}
sibling.classList.add('open')
}
if (
el.matches('div[class*="language-"] div.modal-container button.close')
) {
const parent = el.parentElement?.parentElement
if (!parent) {
return
}
parent.classList.remove('open')
}
if (el.matches('div[class*="language-"] > div.modal-container')) {
el.classList.remove('open')
}
})
window.addEventListener('keydown', (ev) => {
if (ev.key == 'Escape') {
let modal = window.document.querySelector(
'div[class*="language-"] > div.modal-container.open'
)
if (!modal) {
return
}
modal.classList.remove('open')
}
})
}
}

@ -17,6 +17,7 @@ import { usePrefetch } from './composables/preFetch'
import { dataSymbol, initData, siteDataRef, useData } from './data'
import { RouterSymbol, createRouter, scrollTo, type Router } from './router'
import { inBrowser, pathToFile } from './utils'
import { useCodeModal } from './composables/codeModal'
function resolveThemeExtends(theme: typeof RawTheme): typeof RawTheme {
if (theme.extends) {
@ -57,6 +58,8 @@ const VitePressApp = defineComponent({
useCopyCode()
// setup global code groups handler
useCodeGroups()
// setup global code modal handler
useCodeModal()
if (Theme.setup) Theme.setup()
return () => h(Theme.Layout!)

@ -5,3 +5,46 @@
html:not(.dark) .vp-code span {
color: var(--shiki-light, inherit);
}
.vp-code ~ .modal-container:not(.open) {
display: none;
}
.vp-code ~ .modal-container {
position: fixed;
z-index: 100;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(2px);
}
html:not(.dark) .vp-code ~ .modal-container {
background-color: rgba(0, 0, 0, 0.1);
}
.vp-code ~ .modal-container [class*='language-'] {
position: relative;
margin: 20px;
max-width: 90%;
width: min-content;
border-radius: 8px;
background-color: var(--vp-code-block-bg);
box-shadow: var(--vp-shadow-2);
}
.vp-code ~ .modal-container [class*='language-']:hover span.lang {
opacity: 0;
}

@ -387,7 +387,10 @@
opacity 0.35s;
}
.vp-doc [class*='language-']:hover .has-focused-lines .line:not(.has-focus) {
.vp-doc
[class*='language-']:hover:not(:has(.modal-container:hover))
.has-focused-lines
.line:not(.has-focus) {
filter: blur(0);
opacity: 1;
}
@ -437,7 +440,9 @@
color 0.5s;
}
.vp-doc [class*='language-'] > button.copy {
.vp-doc [class*='language-'] > button.copy,
.vp-doc [class*='language-'] > button.modal,
.vp-doc [class*='language-'] > button.close {
/*rtl:ignore*/
direction: ltr;
position: absolute;
@ -462,13 +467,35 @@
opacity 0.25s;
}
.vp-doc [class*='language-']:hover > button.copy,
.vp-doc [class*='language-'] > button.copy:focus {
.vp-doc [class*='language-'] > button.modal {
top: 64px;
background-image: var(--vp-icon-expand);
}
.vp-doc [class*='language-'] > button.close {
top: 64px;
background-image: var(--vp-icon-close);
}
.vp-doc
[class*='language-']:hover:not(:has(.modal-container:hover))
> button.copy,
.vp-doc [class*='language-'] > button.copy:focus,
.vp-doc
[class*='language-']:hover:not(:has(.modal-container:hover))
> button.modal,
.vp-doc [class*='language-'] > button.modal:focus,
.vp-doc
[class*='language-']:hover:not(:has(.modal-container:hover))
> button.close,
.vp-doc [class*='language-'] > button.close:focus {
opacity: 1;
}
.vp-doc [class*='language-'] > button.copy:hover,
.vp-doc [class*='language-'] > button.copy.copied {
.vp-doc [class*='language-'] > button.copy.copied,
.vp-doc [class*='language-'] > button.modal:hover,
.vp-doc [class*='language-'] > button.close:hover {
border-color: var(--vp-code-copy-code-hover-border-color);
background-color: var(--vp-code-copy-code-hover-bg);
}
@ -506,7 +533,7 @@
content: var(--vp-code-copy-copied-text-content);
}
.vp-doc [class*='language-'] > span.lang {
.vp-doc [class*='language-'] span.lang {
position: absolute;
top: 2px;
/*rtl:ignore*/
@ -520,7 +547,10 @@
opacity 0.4s;
}
.vp-doc [class*='language-']:hover > button.copy + span.lang,
.vp-doc
[class*='language-']:hover:not(:has(.modal-container:hover))
> button.copy
+ span.lang,
.vp-doc [class*='language-'] > button.copy:focus + span.lang {
opacity: 0;
}

@ -87,6 +87,10 @@
--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E");
/* clipboard-copy */
--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E");
--vp-icon-expand: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m21 21-6-6m6 6v-4.8m0 4.8h-4.8'/%3E%3Cpath d='M3 16.2V21m0 0h4.8M3 21l6-6'/%3E%3Cpath d='M21 7.8V3m0 0h-4.8M21 3l-6 6'/%3E%3Cpath d='M3 7.8V3m0 0h4.8M3 3l6 6'/%3E%3C/svg%3E");
--vp-icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M8 3v3a2 2 0 0 1-2 2H3'/%3E%3Cpath d='M21 8h-3a2 2 0 0 1-2-2V3'/%3E%3Cpath d='M3 16h3a2 2 0 0 1 2 2v3'/%3E%3Cpath d='M16 21v-3a2 2 0 0 1 2-2h3'/%3E%3C/svg%3E");
}
/* social icons - used under CC0 1.0 from https://simpleicons.org/ */

@ -36,6 +36,7 @@ import { linkPlugin } from './plugins/link'
import { preWrapperPlugin } from './plugins/preWrapper'
import { restoreEntities } from './plugins/restoreEntities'
import { snippetPlugin } from './plugins/snippet'
import { codeModalPlugin } from './plugins/codeModal'
export type { Header } from '../shared'
@ -115,6 +116,16 @@ export interface MarkdownOptions extends Options {
* @default 'Copy Code'
*/
codeCopyButtonTitle?: string
/**
* Show an additional button to open a fullscreen modal in code blocks
* @default false
*/
codeModal?: boolean
/**
* The tooltip text for the modal button in code blocks
* @default 'Open Modal'
*/
codeModalButtonTitle?: string
/* ==================== Markdown It Plugins ==================== */
@ -200,6 +211,7 @@ export const createMarkdownRenderer = async (
): Promise<MarkdownRenderer> => {
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
const codeCopyButtonTitle = options.codeCopyButtonTitle || 'Copy Code'
const codeModalButtonTitle = options.codeModalButtonTitle || 'Open Modal'
const hasSingleTheme = typeof theme === 'string' || 'name' in theme
const md = MarkdownIt({
@ -229,6 +241,7 @@ export const createMarkdownRenderer = async (
base
)
.use(lineNumberPlugin, options.lineNumbers)
.use(codeModalPlugin, options.codeModal, { codeModalButtonTitle })
md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
return '<table tabindex="0">\n'

@ -0,0 +1,41 @@
// markdown-it plugin for generating line numbers.
// It depends on preWrapper plugin.
import type MarkdownIt from 'markdown-it'
import type { MarkdownOptions } from '../markdown'
export const codeModalPlugin = (
md: MarkdownIt,
enable = false,
options: MarkdownOptions = {}
) => {
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const rawCode = fence(...args)
const [tokens, idx] = args
const info = tokens[idx].info
if (
(!enable && !/:modal($| |=)/.test(info)) ||
(enable && /:no-modal($| )/.test(info))
) {
return rawCode
}
let end = rawCode.lastIndexOf('</div>')
let innerCode =
rawCode.substring(0, end) +
`<button title="${options.codeModalButtonTitle}" class="close"></button>` +
'</div>'
const modal =
`<button title="${options.codeModalButtonTitle}" class="modal"></button>` +
'<div class="modal-container">' +
innerCode +
'</div>'
return rawCode.substring(0, end) + modal + '</div>'
}
}

@ -93,6 +93,7 @@ export async function highlight(
const vueRE = /-vue(?=:|$)/
const lineNoStartRE = /=(\d*)/
const lineNoRE = /:(no-)?line-numbers(=\d*)?$/
const modalNoRE = /:(no-)?modal(=\d*)?$/
const mustacheRE = /\{\{.*?\}\}/g
return (str: string, lang: string, attrs: string) => {
@ -101,6 +102,7 @@ export async function highlight(
lang
.replace(lineNoStartRE, '')
.replace(lineNoRE, '')
.replace(modalNoRE, '')
.replace(vueRE, '')
.toLowerCase() || defaultLang

@ -47,6 +47,7 @@ function extractLang(info: string) {
.trim()
.replace(/=(\d*)/, '')
.replace(/:(no-)?line-numbers({| |$|=\d*).*/, '')
.replace(/:(no-)?modal({| |$|=\d*).*/, '')
.replace(/(-vue|{| ).*$/, '')
.replace(/^vue-html$/, 'template')
.replace(/^ansi$/, '')

Loading…
Cancel
Save