basic code modals

pull/3877/head
tobinio 1 year ago
parent 99c0cece62
commit 8f29aa0ac2

@ -597,6 +597,29 @@ const line3 = 'This is line 3'
const line4 = 'This is line 4'
```
## Code Modal
[//]: # (todo - expand on docs )
````md:modal
```ts {1}
// line-numbers is disabled by default
const line2 = 'This is line 2'
const line3 = 'This is line 3'
```
```ts:line-numbers {1}
// line-numbers is enabled
const line2 = 'This is line 2'
const line3 = 'This is line 3'
```
```ts:line-numbers=2 {1}
// line-numbers is enabled and start from 2
const line3 = 'This is line 3'
const line4 = 'This is line 4'
```
````
## Import Code Snippets
You can import code snippets from existing files via following syntax:

@ -0,0 +1,37 @@
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')) {
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')) {
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;
}

@ -429,7 +429,8 @@
color 0.5s;
}
.vp-doc [class*='language-'] > button.copy {
.vp-doc [class*='language-'] > button.copy,
.vp-doc [class*='language-'] > button.modal {
/*rtl:ignore*/
direction: ltr;
position: absolute;
@ -454,13 +455,20 @@
opacity 0.25s;
}
.vp-doc [class*='language-'] > button.modal {
top: 64px;
}
.vp-doc [class*='language-']:hover > button.copy,
.vp-doc [class*='language-'] > button.copy:focus {
.vp-doc [class*='language-'] > button.copy:focus,
.vp-doc [class*='language-']:hover > button.modal,
.vp-doc [class*='language-'] > button.modal: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 {
border-color: var(--vp-code-copy-code-hover-border-color);
background-color: var(--vp-code-copy-code-hover-bg);
}
@ -498,7 +506,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*/
@ -512,8 +520,8 @@
opacity 0.4s;
}
.vp-doc [class*='language-']:hover > button.copy + span.lang,
.vp-doc [class*='language-'] > button.copy:focus + span.lang {
.vp-doc [class*='language-']:hover button.copy + span.lang,
.vp-doc [class*='language-'] button.copy:focus + span.lang {
opacity: 0;
}

@ -37,6 +37,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'
@ -116,6 +117,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 ==================== */
@ -201,6 +212,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({
@ -230,6 +242,7 @@ export const createMarkdownRenderer = async (
base
)
.use(lineNumberPlugin, options.lineNumbers)
.use(codeModalPlugin, options.codeModal, { codeModalButtonTitle })
if (options.gfmAlerts !== false) {
md.use(gitHubAlertsPlugin)

@ -0,0 +1,36 @@
// 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
}
const code =
`<button title="${options.codeModalButtonTitle}" class="modal"></button>` +
'<div class="modal-container">' +
fence(...args) +
'</div>'
let end = rawCode.lastIndexOf('</div>')
return rawCode.substring(0, end) + code + '</div>'
}
}

@ -94,6 +94,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) => {
@ -102,6 +103,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