diff --git a/.vscode/settings.json b/.vscode/settings.json index acbf31efe..a46c7cc35 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -72,6 +72,7 @@ "taze", "Tongji", "tsbuildinfo", + "twoslash", "vite", "vuepress", "vueuse", diff --git "a/docs/2.preview/\344\270\273\351\242\230\346\225\210\346\236\234\351\242\204\350\247\210.md" "b/docs/2.preview/\344\270\273\351\242\230\346\225\210\346\236\234\351\242\204\350\247\210.md" index 8d1300da0..f9f9de031 100644 --- "a/docs/2.preview/\344\270\273\351\242\230\346\225\210\346\236\234\351\242\204\350\247\210.md" +++ "b/docs/2.preview/\344\270\273\351\242\230\346\225\210\346\236\234\351\242\204\350\247\210.md" @@ -124,6 +124,56 @@ const obj = { } ``` +**Code Blocks TwoSlash** + +```ts twoslash +// @errors: 2339 +const welcome = "Tudo bem gente?" +const words = welcome.contains(" ") +``` + +```ts twoslash +import express from "express" +const app = express() +app.get("/", function (req, res) { + res.send +}) +app.listen(3000) +``` + +```ts twoslash +import { getHighlighterCore } from 'shikiji/core' + +const highlighter = await getHighlighterCore({}) +// @log: Custom log message +const a = 1 +// @error: Custom error message +const b = 1 +// @warn: Custom warning message +const c = 1 +// @annotate: Custom annotation message +``` + +```ts twoslash +// @errors: 2540 +interface Todo { + title: string +} + +const todo: Readonly = { + title: 'Delete inactive users'.toUpperCase(), +// ^? +} + +todo.title = 'Hello' + +Number.parseInt('123', 10) +// ^| + + // + // +``` + **代码分组** ::: code-tabs diff --git a/package.json b/package.json index b309dc4e4..ff3d6e7b2 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,11 @@ "typescript": "^5.3.3", "vite": "^5.0.11" }, + "pnpm": { + "patchedDependencies": { + "@vuepress/markdown@2.0.0-rc.0": "patches/@vuepress__markdown@2.0.0-rc.0.patch" + } + }, "lint-staged": { "*": "eslint --fix" }, diff --git a/patches/@vuepress__markdown@2.0.0-rc.0.patch b/patches/@vuepress__markdown@2.0.0-rc.0.patch new file mode 100644 index 000000000..c091b50e2 --- /dev/null +++ b/patches/@vuepress__markdown@2.0.0-rc.0.patch @@ -0,0 +1,13 @@ +diff --git a/dist/index.js b/dist/index.js +index 996b0d16dac39667cc25496e52adcc9dd2b2befa..a4c9f5ba3a20967d9a561fcc73178d9e84f48279 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -245,7 +245,7 @@ var codePlugin = (md, { + const info = token.info ? md.utils.unescapeAll(token.info).trim() : ""; + const language = resolveLanguage(info); + const languageClass = `${options.langPrefix}${language.name}`; +- const code = options.highlight?.(token.content, language.name, "") || md.utils.escapeHtml(token.content); ++ const code = options.highlight?.(token.content, language.name, info || "") || md.utils.escapeHtml(token.content); + token.attrJoin("class", languageClass); + let result = code.startsWith("${code}`; + const useVPre = resolveVPre(info) ?? vPreBlock; diff --git a/plugins/plugin-copy-code/src/client/setupCopyCode.ts b/plugins/plugin-copy-code/src/client/setupCopyCode.ts index 18e5360c8..d8b84c6f7 100644 --- a/plugins/plugin-copy-code/src/client/setupCopyCode.ts +++ b/plugins/plugin-copy-code/src/client/setupCopyCode.ts @@ -8,7 +8,7 @@ const options = __COPY_CODE_OPTIONS__ const RE_LANGUAGE = /language-([\w]+)/ const RE_START_CODE = /^ *(\$|>)/gm const shells = ['shellscript', 'shell', 'bash', 'sh', 'zsh'] -const ignoredNodes = ['.diff.remove'] +const ignoredNodes = ['.diff.remove', '.vp-copy-ignore'] function isMobile(): boolean { return navigator diff --git a/plugins/plugin-shikiji/package.json b/plugins/plugin-shikiji/package.json index f0963ddf4..4b900ec47 100644 --- a/plugins/plugin-shikiji/package.json +++ b/plugins/plugin-shikiji/package.json @@ -37,7 +37,8 @@ "nanoid": "^5.0.4", "picocolors": "^1.0.0", "shikiji": "^0.9.18", - "shikiji-transformers": "^0.9.18" + "shikiji-transformers": "^0.9.18", + "shikiji-twoslash": "^0.9.18" }, "publishConfig": { "access": "public" diff --git a/plugins/plugin-shikiji/src/node/highlight.ts b/plugins/plugin-shikiji/src/node/highlight.ts index 2feb50e13..277574ae1 100644 --- a/plugins/plugin-shikiji/src/node/highlight.ts +++ b/plugins/plugin-shikiji/src/node/highlight.ts @@ -15,7 +15,9 @@ import { transformerNotationFocus, transformerNotationHighlight, } from 'shikiji-transformers' +import { rendererRich, transformerTwoSlash } from 'shikiji-twoslash' import type { HighlighterOptions, ThemeOptions } from './types.js' +import { resolveAttrs } from './resolveAttrs.js' const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10) @@ -88,9 +90,7 @@ export async function highlight( lang = defaultLang } } - - // const lineOptions = attrsToLines(attrs) - + const { attrs: attributes, rawAttrs } = resolveAttrs(attrs || '') const mustaches = new Map() const removeMustache = (s: string) => { @@ -110,32 +110,75 @@ export async function highlight( mustaches.forEach((marker, match) => { s = s.replaceAll(marker, match) }) - return s - } - const fillEmptyHighlightedLine = (s: string) => { - return `${s.replace( - /()(<\/span>)/g, - '$1$2', - ).replace(/(\/\/\s*?\[)\\(!code.*?\])/g, '$1$2')}\n` + return `${s}\n` } str = removeMustache(str).trimEnd() - const highlighted = highlighter.codeToHtml(str, { - lang, - transformers: [ - ...transformers, - ...userTransformers, - ], - meta: { - __raw: attrs, + const inlineTransformers: ShikijiTransformer[] = [ + { + name: 'vuepress-shikiji:empty-line', + pre(hast) { + hast.children.forEach((code) => { + if (code.type === 'element' && code.tagName === 'code') { + code.children.forEach((span) => { + if ( + span.type === 'element' + && span.tagName === 'span' + && Array.isArray(span.properties.class) + && span.properties.class.includes('line') + && span.children.length === 0 + ) { + span.children.push({ + type: 'element', + tagName: 'wbr', + properties: {}, + children: [], + }) + } + }) + } + }) + }, }, - ...(typeof theme === 'object' && 'light' in theme && 'dark' in theme - ? { themes: theme, defaultColor: false } - : { theme }), - }) + { + name: 'vuepress-shikiji:remove-escape', + postprocess(code) { + return code.replace(/\[\\\!code/g, '[!code') + }, + }, + ] + + if (attributes.twoslash) { + inlineTransformers.push(transformerTwoSlash({ + renderer: rendererRich({ + classExtra: 'vp-copy-ignore', + }), + })) + } - return fillEmptyHighlightedLine(restoreMustache(highlighted)) + try { + const highlighted = highlighter.codeToHtml(str, { + lang, + transformers: [ + ...transformers, + ...inlineTransformers, + ...userTransformers, + ], + meta: { + __raw: rawAttrs, + }, + ...(typeof theme === 'object' && 'light' in theme && 'dark' in theme + ? { themes: theme, defaultColor: false } + : { theme }), + }) + + return restoreMustache(highlighted) + } + catch (e) { + logger.error(e) + return str + } } } diff --git a/plugins/plugin-shikiji/src/node/resolveAttrs.ts b/plugins/plugin-shikiji/src/node/resolveAttrs.ts new file mode 100644 index 000000000..ccb67ce8e --- /dev/null +++ b/plugins/plugin-shikiji/src/node/resolveAttrs.ts @@ -0,0 +1,43 @@ +const RE_ATTR_VALUE = /(?:^|\s+)(?[\w\d-]+)(?:=\s*(?['"])(?.+?)\k)?(?:\s+|$)/ +const RE_CODE_BLOCKS = /^[\w\d-]*(\s*:[\w\d-]*)?(\s*\{[\d\w-,\s]+\})?\s*/ + +export function resolveAttrs(info: string): { + attrs: Record + rawAttrs: string +} { + if (!info) + return { rawAttrs: '', attrs: {} } + info = info.replace(RE_CODE_BLOCKS, '').trim() + if (!info) + return { rawAttrs: '', attrs: {} } + + const attrs: Record = {} + const rawAttrs = info + + let matched: RegExpMatchArray | null + + // eslint-disable-next-line no-cond-assign + while (matched = info.match(RE_ATTR_VALUE)) { + const { attr, value } = matched.groups || {} + attrs[attr] = value ?? true + info = info.slice(matched[0].length) + } + + Object.keys(attrs).forEach((key) => { + let value = attrs[key] + value = typeof value === 'string' ? value.trim() : value + if (value === 'true') + value = true + else if (value === 'false') + value = false + + attrs[key] = value + + if (key.includes('-')) { + const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase()) + attrs[_key] = value + } + }) + + return { attrs, rawAttrs } +} diff --git a/plugins/plugin-shikiji/src/node/shikijiPlugin.ts b/plugins/plugin-shikiji/src/node/shikijiPlugin.ts index a05df849a..62645cd5f 100644 --- a/plugins/plugin-shikiji/src/node/shikijiPlugin.ts +++ b/plugins/plugin-shikiji/src/node/shikijiPlugin.ts @@ -2,9 +2,6 @@ import type { Plugin } from '@vuepress/core' import { highlight } from './highlight.js' import type { HighlighterOptions } from './types' -/** - * Options of @vuepress/plugin-shiki - */ export type ShikijiPluginOptions = HighlighterOptions export function shikijiPlugin(options: ShikijiPluginOptions = {}): Plugin { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a52958e79..78d80a23f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + '@vuepress/markdown@2.0.0-rc.0': + hash: zvwmijlesxcdy3i4trojywcm7y + path: patches/@vuepress__markdown@2.0.0-rc.0.patch + importers: .: @@ -16,7 +21,7 @@ importers: version: 18.4.4 '@pengzhanbo/eslint-config-vue': specifier: ^1.5.3 - version: 1.5.3(@vue/compiler-sfc@3.4.5)(eslint@8.56.0)(typescript@5.3.3) + version: 1.5.3(@vue/compiler-sfc@3.4.7)(eslint@8.56.0)(typescript@5.3.3) '@pengzhanbo/stylelint-config': specifier: ^1.5.3 version: 1.5.3(stylelint@16.1.0) @@ -79,7 +84,7 @@ importers: version: 2.0.0-rc.0(@types/node@20.9.1)(sass@1.69.7)(typescript@5.3.3) '@vuepress/bundler-webpack': specifier: 2.0.0-rc.0 - version: 2.0.0-rc.0(@vue/compiler-sfc@3.4.5)(typescript@5.3.3) + version: 2.0.0-rc.0(@vue/compiler-sfc@3.4.7)(typescript@5.3.3) '@vuepress/cli': specifier: 2.0.0-rc.0 version: 2.0.0-rc.0(typescript@5.3.3) @@ -361,6 +366,9 @@ importers: shikiji-transformers: specifier: ^0.9.18 version: 0.9.18 + shikiji-twoslash: + specifier: ^0.9.18 + version: 0.9.18(typescript@5.3.3) theme: dependencies: @@ -3803,7 +3811,7 @@ packages: '@parcel/watcher-win32-x64': 2.3.0 dev: false - /@pengzhanbo/eslint-config-vue@1.5.3(@vue/compiler-sfc@3.4.5)(eslint@8.56.0)(typescript@5.3.3): + /@pengzhanbo/eslint-config-vue@1.5.3(@vue/compiler-sfc@3.4.7)(eslint@8.56.0)(typescript@5.3.3): resolution: {integrity: sha512-5QdiHebq5wQGLBC1nZKJGK4rjB1fMM3RcWWLNBn2NqbYMOKRllj9CVrnHvjhKoO9KTUNV89doRDBmnIIQoortA==} peerDependencies: '@unocss/eslint-plugin': '>=0.50.0' @@ -3820,7 +3828,7 @@ packages: eslint: 8.56.0 eslint-merge-processors: 0.1.0(eslint@8.56.0) eslint-plugin-vue: 9.19.2(eslint@8.56.0) - eslint-processor-vue-blocks: 0.1.1(@vue/compiler-sfc@3.4.5)(eslint@8.56.0) + eslint-processor-vue-blocks: 0.1.1(@vue/compiler-sfc@3.4.7)(eslint@8.56.0) vue-eslint-parser: 9.3.2(eslint@8.56.0) transitivePeerDependencies: - '@vue/compiler-sfc' @@ -4386,6 +4394,13 @@ packages: resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} dev: false + /@types/lz-string@1.5.0: + resolution: {integrity: sha512-s84fKOrzqqNCAPljhVyC5TjAo6BH4jKHw9NRNFNiRUY5QSgZCmVm5XILlWbisiKl+0OcS7eWihmKGS5akc2iQw==} + deprecated: This is a stub types definition. lz-string provides its own type definitions, so you do not need this installed. + dependencies: + lz-string: 1.5.0 + dev: false + /@types/markdown-it-emoji@2.0.4: resolution: {integrity: sha512-H6ulk/ZmbDxOayPwI/leJzrmoW1YKX1Z+MVSCHXuYhvqckV4I/c+hPTf6UiqJyn2avWugfj30XroheEb6/Ekqg==} dependencies: @@ -4699,6 +4714,28 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@typescript/twoslash@3.2.4(typescript@5.3.3): + resolution: {integrity: sha512-/TCIOuPQaKltzUUT1qJo6mplYwjbAxkaSFvkeZD3FeFt3Ovt+HJi8xisu8rcLyDRmM3VJ0+jAx+AAICwn4Zlhw==} + peerDependencies: + typescript: '*' + dependencies: + '@types/lz-string': 1.5.0 + '@typescript/vfs': 1.5.0 + debug: 4.3.4(supports-color@9.2.2) + lz-string: 1.5.0 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript/vfs@1.5.0: + resolution: {integrity: sha512-AJS307bPgbsZZ9ggCT3wwpg3VbTKMFNHfaY/uF0ahSkYYrPF2dSSKDNIDIQAHm9qJqbLvCsSJH7yN4Vs/CsMMg==} + dependencies: + debug: 4.3.4(supports-color@9.2.2) + transitivePeerDependencies: + - supports-color + dev: false + /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true @@ -4756,15 +4793,6 @@ packages: vue: 3.4.7(typescript@5.3.3) dev: false - /@vue/compiler-core@3.4.5: - resolution: {integrity: sha512-Daka7P1z2AgKjzuueWXhwzIsKu0NkLB6vGbNVEV2iJ8GJTrzraZo/Sk4GWCMRtd/qVi3zwnk+Owbd/xSZbwHtQ==} - dependencies: - '@babel/parser': 7.23.6 - '@vue/shared': 3.4.5 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.0.2 - /@vue/compiler-core@3.4.7: resolution: {integrity: sha512-hhCaE3pTMrlIJK7M/o3Xf7HV8+JoNTGOQ/coWS+V+pH6QFFyqtoXqQzpqsNp7UK17xYKua/MBiKj4e1vgZOBYw==} dependencies: @@ -4773,33 +4801,12 @@ packages: entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.0.2 - dev: false - - /@vue/compiler-dom@3.4.5: - resolution: {integrity: sha512-J8YlxknJVd90SXFJ4HwGANSAXsx5I0lK30sO/zvYV7s5gXf7gZR7r/1BmZ2ju7RGH1lnc6bpBc6nL61yW+PsAQ==} - dependencies: - '@vue/compiler-core': 3.4.5 - '@vue/shared': 3.4.5 /@vue/compiler-dom@3.4.7: resolution: {integrity: sha512-qDKBAIurCTub4n/6jDYkXwgsFuriqqmmLrIq1N2QDfYJA/mwiwvxi09OGn28g+uDdERX9NaKDLji0oTjE3sScg==} dependencies: '@vue/compiler-core': 3.4.7 '@vue/shared': 3.4.7 - dev: false - - /@vue/compiler-sfc@3.4.5: - resolution: {integrity: sha512-jauvkDuSSUbP0ebhfNqljhShA90YEfX/0wZ+w40oZF43IjGyWYjqYaJbvMJwGOd+9+vODW6eSvnk28f0SGV7OQ==} - dependencies: - '@babel/parser': 7.23.6 - '@vue/compiler-core': 3.4.5 - '@vue/compiler-dom': 3.4.5 - '@vue/compiler-ssr': 3.4.5 - '@vue/shared': 3.4.5 - estree-walker: 2.0.2 - magic-string: 0.30.5 - postcss: 8.4.33 - source-map-js: 1.0.2 /@vue/compiler-sfc@3.4.7: resolution: {integrity: sha512-Gec6CLkReVswDYjQFq79O5rktri4R7TsD/VPCiUoJw40JhNNxaNJJa8mrQrWoJluW4ETy6QN0NUyC/JO77OCOw==} @@ -4813,20 +4820,12 @@ packages: magic-string: 0.30.5 postcss: 8.4.33 source-map-js: 1.0.2 - dev: false - - /@vue/compiler-ssr@3.4.5: - resolution: {integrity: sha512-DDdEcDzj2lWTMfUMMtEpLDhURai9LhM0zSZ219jCt7b2Vyl0/jy3keFgCPMitG0V1S1YG4Cmws3lWHWdxHQOpg==} - dependencies: - '@vue/compiler-dom': 3.4.5 - '@vue/shared': 3.4.5 /@vue/compiler-ssr@3.4.7: resolution: {integrity: sha512-PvYeSOvnCkST5mGS0TLwEn5w+4GavtEn6adcq8AspbHaIr+mId5hp7cG3ASy3iy8b+LuXEG2/QaV/nj5BQ/Aww==} dependencies: '@vue/compiler-dom': 3.4.7 '@vue/shared': 3.4.7 - dev: false /@vue/devtools-api@6.5.1: resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==} @@ -4867,12 +4866,8 @@ packages: resolution: {integrity: sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==} dev: false - /@vue/shared@3.4.5: - resolution: {integrity: sha512-6XptuzlMvN4l4cDnDw36pdGEV+9njYkQ1ZE0Q6iZLwrKefKaOJyiFmcP3/KBDHbt72cJZGtllAc1GaHe6XGAyg==} - /@vue/shared@3.4.7: resolution: {integrity: sha512-G+i4glX1dMJk88sbJEcQEGWRQnVm9eIY7CcQbO5dpdsD9SF8jka3Mr5OqZYGjczGN1+D6EUwdu6phcmcx9iuPA==} - dev: false /@vuepress/bundler-vite@2.0.0-rc.0(@types/node@20.9.1)(sass@1.69.7)(typescript@5.3.3): resolution: {integrity: sha512-rX8S8IYpqqlJfNPstS/joorpxXx/4WuE7+gDM31i2HUrxOKGZVzq8ZsRRRU2UdoTwHZSd3LpUS4sMtxE5xLK1A==} @@ -4904,7 +4899,7 @@ packages: - typescript dev: false - /@vuepress/bundler-webpack@2.0.0-rc.0(@vue/compiler-sfc@3.4.5)(typescript@5.3.3): + /@vuepress/bundler-webpack@2.0.0-rc.0(@vue/compiler-sfc@3.4.7)(typescript@5.3.3): resolution: {integrity: sha512-PUbjaQCTE+pwkmHkozT4CCjdEiAEO89XOXKTO/VwEsv6hWNeT97fi7TnScV/x8R/9WeA45QrW3eHipMwkKJ8uQ==} dependencies: '@types/express': 4.17.21 @@ -4926,7 +4921,7 @@ packages: postcss-loader: 7.3.3(postcss@8.4.31)(webpack@5.89.0) style-loader: 3.3.3(webpack@5.89.0) vue: 3.4.7(typescript@5.3.3) - vue-loader: 17.3.1(@vue/compiler-sfc@3.4.5)(vue@3.4.7)(webpack@5.89.0) + vue-loader: 17.3.1(@vue/compiler-sfc@3.4.7)(vue@3.4.7)(webpack@5.89.0) vue-router: 4.2.5(vue@3.4.7) webpack: 5.89.0 webpack-chain: 6.5.1 @@ -4980,7 +4975,7 @@ packages: resolution: {integrity: sha512-uoOaZP1MdxZYJIAJcRcmYKKeCIVnxZeOuLMOOB9CPuAKSalT1RvJ1lztw6RX3q9SPnlqtSZPQXDncPAZivw4pA==} dependencies: '@vuepress/client': 2.0.0-rc.0(typescript@5.3.3) - '@vuepress/markdown': 2.0.0-rc.0 + '@vuepress/markdown': 2.0.0-rc.0(patch_hash=zvwmijlesxcdy3i4trojywcm7y) '@vuepress/shared': 2.0.0-rc.0 '@vuepress/utils': 2.0.0-rc.0 vue: 3.4.7(typescript@5.3.3) @@ -4990,7 +4985,7 @@ packages: - typescript dev: false - /@vuepress/markdown@2.0.0-rc.0: + /@vuepress/markdown@2.0.0-rc.0(patch_hash=zvwmijlesxcdy3i4trojywcm7y): resolution: {integrity: sha512-USmqdKKMT6ZFHYRztTjKUlO8qgGfnEygMAAq4AzC/uYXiEfrbMBLAWJhteyGS56P3rGLj0OPAhksE681bX/wOg==} dependencies: '@mdit-vue/plugin-component': 1.0.0 @@ -5012,6 +5007,7 @@ packages: transitivePeerDependencies: - supports-color dev: false + patched: true /@vuepress/plugin-active-header-links@2.0.0-rc.0(typescript@5.3.3): resolution: {integrity: sha512-UJdXLYNGL5Wjy5YGY8M2QgqT75bZ95EHebbqGi8twBdIJE9O+bM+dPJyYtAk2PIVqFORiw3Hj+PchsNSxdn9+g==} @@ -5033,7 +5029,7 @@ packages: dependencies: '@types/markdown-it': 13.0.7 '@vuepress/core': 2.0.0-rc.0(typescript@5.3.3) - '@vuepress/markdown': 2.0.0-rc.0 + '@vuepress/markdown': 2.0.0-rc.0(patch_hash=zvwmijlesxcdy3i4trojywcm7y) '@vuepress/shared': 2.0.0-rc.0 '@vuepress/utils': 2.0.0-rc.0 markdown-it: 13.0.2 @@ -5074,7 +5070,7 @@ packages: dependencies: '@vuepress/client': 2.0.0-rc.0(typescript@5.3.3) '@vuepress/core': 2.0.0-rc.0(typescript@5.3.3) - '@vuepress/markdown': 2.0.0-rc.0 + '@vuepress/markdown': 2.0.0-rc.0(patch_hash=zvwmijlesxcdy3i4trojywcm7y) '@vuepress/shared': 2.0.0-rc.0 '@vuepress/utils': 2.0.0-rc.0 vue: 3.4.7(typescript@5.3.3) @@ -8610,13 +8606,13 @@ packages: - supports-color dev: true - /eslint-processor-vue-blocks@0.1.1(@vue/compiler-sfc@3.4.5)(eslint@8.56.0): + /eslint-processor-vue-blocks@0.1.1(@vue/compiler-sfc@3.4.7)(eslint@8.56.0): resolution: {integrity: sha512-9+dU5lU881log570oBwpelaJmOfOzSniben7IWEDRYQPPWwlvaV7NhOtsTuUWDqpYT+dtKKWPsgz4OkOi+aZnA==} peerDependencies: '@vue/compiler-sfc': ^3.3.0 eslint: ^8.50.0 dependencies: - '@vue/compiler-sfc': 3.4.5 + '@vue/compiler-sfc': 3.4.7 eslint: 8.56.0 dev: true @@ -11611,6 +11607,11 @@ packages: engines: {node: '>=12'} dev: false + /lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + dev: false + /macos-release@3.1.0: resolution: {integrity: sha512-/M/R0gCDgM+Cv1IuBG1XGdfTFnMEG6PZeT+KGWHO/OG+imqmaD9CH5vHBTycEM3+Kc4uG2Il+tFAuUWLqQOeUA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -14365,6 +14366,16 @@ packages: shikiji: 0.9.18 dev: false + /shikiji-twoslash@0.9.18(typescript@5.3.3): + resolution: {integrity: sha512-+Np6QAdV244p6CVofefszrRWdcmbUNvTXaGV4V+wjKRyfp8jVTUtxjuVjtG7e3vyA8Nu989K99CCjhcTQOuEaw==} + dependencies: + '@typescript/twoslash': 3.2.4(typescript@5.3.3) + shikiji-core: 0.9.18 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + /shikiji@0.9.18: resolution: {integrity: sha512-/tFMIdV7UQklzN13VjF0/XFzmii6C606Jc878hNezvB8ZR8FG8FW9j0I4J9EJre0owlnPntgLVPpHqy27Gs+DQ==} dependencies: @@ -16031,7 +16042,7 @@ packages: - supports-color dev: true - /vue-loader@17.3.1(@vue/compiler-sfc@3.4.5)(vue@3.4.7)(webpack@5.89.0): + /vue-loader@17.3.1(@vue/compiler-sfc@3.4.7)(vue@3.4.7)(webpack@5.89.0): resolution: {integrity: sha512-nmVu7KU8geOyzsStyyaxID/uBGDMS8BkPXb6Lu2SNkMawriIbb+hYrNtgftHMKxOSkjjjTF5OSSwPo3KP59egg==} peerDependencies: '@vue/compiler-sfc': '*' @@ -16043,7 +16054,7 @@ packages: vue: optional: true dependencies: - '@vue/compiler-sfc': 3.4.5 + '@vue/compiler-sfc': 3.4.7 chalk: 4.1.2 hash-sum: 2.0.0 vue: 3.4.7(typescript@5.3.3) diff --git a/theme/src/client/styles/index.scss b/theme/src/client/styles/index.scss index 474174560..814e1e9f6 100644 --- a/theme/src/client/styles/index.scss +++ b/theme/src/client/styles/index.scss @@ -6,6 +6,8 @@ @import "utils"; @import "content"; @import "code"; + +@import "twoslash"; @import "md-enhance"; @import "search"; @import "@vuepress/plugin-palette/style"; diff --git a/theme/src/client/styles/twoslash.scss b/theme/src/client/styles/twoslash.scss new file mode 100644 index 000000000..ca937ca5a --- /dev/null +++ b/theme/src/client/styles/twoslash.scss @@ -0,0 +1,225 @@ +/* stylelint-disable no-descending-specificity */ + +/* ===== Basic ===== */ +:root { + --twoslash-border-color: var(--vp-c-divider); + --twoslash-jsdoc-color: #888; + --twoslash-underline-color: currentcolor; + --twoslash-popup-bg: var(--vp-c-neutral-inverse); + --twoslash-popup-shadow: var(--vp-shadow-2); + --twoslash-matched-color: inherit; + --twoslash-unmatched-color: #888; + --twoslash-cursor-color: #8888; + --twoslash-error-color: var(--vp-c-danger-1); + --twoslash-error-bg: var(--vp-c-danger-soft); + --twoslash-tag-color: var(--vp-c-tip-1); + --twoslash-tag-bg: var(--vp-c-tip-soft); + --twoslash-tag-warn-color: var(--vp-c-warning-1); + --twoslash-tag-warn-bg: var(--vp-c-warning-soft); + --twoslash-tag-annotate-color: var(--vp-c-green-1); + --twoslash-tag-annotate-bg: var(--vp-c-green-soft); +} + +div[class*="language-"].line-numbers-mode:has(> .twoslash) { + .line-numbers { + display: none; + } + + pre { + padding-left: 1.5rem; + margin-left: 0; + } +} + +/* Respect people's wishes to not have animations */ +@media (prefers-reduced-motion: reduce) { + .twoslash * { + transition: none !important; + } +} + +/* ===== Hover Info ===== */ +.twoslash:hover .twoslash-hover { + border-color: var(--twoslash-underline-color); +} + +.twoslash .twoslash-hover { + position: relative; + border-bottom: 1px dotted transparent; + transition: border-color 0.3s; + transition-timing-function: ease; +} + +.twoslash .twoslash-popup-info { + position: absolute; + z-index: 10; + display: inline-block; + padding: 4px 6px; + text-align: left; + pointer-events: none; + user-select: none; + background: var(--twoslash-popup-bg); + border: 1px solid var(--twoslash-border-color); + border-radius: 4px; + box-shadow: var(--twoslash-popup-shadow); + opacity: 0; + transition: opacity 0.3s; + transform: translateY(1.5em); +} + +.twoslash .twoslash-query-presisted .twoslash-popup-info { + left: 50%; + z-index: 9; + transform: translate(-1.3em, 1.8em); +} + +.twoslash .twoslash-hover:hover .twoslash-popup-info, +.twoslash .twoslash-query-presisted .twoslash-popup-info { + pointer-events: auto; + opacity: 1; +} + +.twoslash .twoslash-popup-info:hover { + user-select: auto; +} + +.twoslash .twoslash-popup-arrow { + position: absolute; + top: -4px; + left: 1em; + width: 6px; + height: 6px; + pointer-events: none; + background: var(--twoslash-popup-bg); + border-top: 1px solid var(--twoslash-border-color); + border-right: 1px solid var(--twoslash-border-color); + transform: rotate(-45deg); +} + +.twoslash .twoslash-popup-jsdoc { + padding-top: 6px; + padding-bottom: 2px; + font-family: sans-serif; + font-size: 0.8em; + color: var(--twoslash-jsdoc-color); +} + +/* ===== Error Line ===== */ +.twoslash .twoslash-error-line { + position: relative; + padding: 6px; + margin: 0.2em 0; + color: var(--twoslash-error-color); + background-color: var(--twoslash-error-bg); + border-left: 3px solid var(--twoslash-error-color); +} + +.twoslash .twoslash-error { + padding-bottom: 2px; + background: + url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") + repeat-x bottom left; +} + +/* ===== Completeions ===== */ +.twoslash .twoslash-completions-list { + position: relative; +} + +.twoslash .twoslash-completions-list ul { + position: absolute; + top: 0; + left: 0; + z-index: 8; + display: inline-block; + display: flex; + flex-direction: column; + gap: 4px; + width: 240px; + padding: 4px; + margin: 3px 0 0 -1px; + font-size: 0.8rem; + user-select: none; + background: var(--twoslash-popup-bg); + border: 1px solid var(--twoslash-border-color); + box-shadow: var(--twoslash-popup-shadow); + transform: translate(0, 1.2em); +} + +.twoslash .twoslash-completions-list ul:hover { + user-select: auto; +} + +.twoslash .twoslash-completions-list ul::before { + position: absolute; + top: -1.6em; + left: -1px; + width: 2px; + height: 1.4em; + content: " "; + background-color: var(--twoslash-cursor-color); +} + +.twoslash .twoslash-completions-list ul li { + display: flex; + gap: 0.25em; + align-items: center; + overflow: hidden; + line-height: 1em; +} + +.twoslash .twoslash-completions-list ul li span.twoslash-completions-unmatched { + color: var(--twoslash-unmatched-color); +} + +.twoslash .twoslash-completions-list ul .deprecated { + text-decoration: line-through; + opacity: 0.5; +} + +.twoslash .twoslash-completions-list ul li span.twoslash-completions-matched { + color: var(--twoslash-matched-color); +} + +/* Icons */ +.twoslash .twoslash-completions-list .twoslash-completions-icon { + flex: none; + width: 1em; + color: var(--twoslash-unmatched-color); +} + +/* Custom Tags */ +.twoslash .twoslash-tag-line { + position: relative; + display: flex; + gap: 0.3em; + align-items: center; + padding: 6px; + margin: 0.2em 0; + color: var(--twoslash-tag-color); + background-color: var(--twoslash-tag-bg); + border-left: 3px solid var(--twoslash-tag-color); +} + +.twoslash .twoslash-tag-line .twoslash-tag-icon { + width: 1.1em; + color: inherit; +} + +.twoslash .twoslash-tag-line.twoslash-tag-error-line { + color: var(--twoslash-error-color); + background-color: var(--twoslash-error-bg); + border-left: 3px solid var(--twoslash-error-color); +} + +.twoslash .twoslash-tag-line.twoslash-tag-warn-line { + color: var(--twoslash-tag-warn-color); + background-color: var(--twoslash-tag-warn-bg); + border-left: 3px solid var(--twoslash-tag-warn-color); +} + +.twoslash .twoslash-tag-line.twoslash-tag-annotate-line { + color: var(--twoslash-tag-annotate-color); + background-color: var(--twoslash-tag-annotate-bg); + border-left: 3px solid var(--twoslash-tag-annotate-color); +}