diff --git a/packages/ui-default/components/highlighter/highlighter.page.styl b/packages/ui-default/components/highlighter/highlighter.page.styl index 0df508732..de75ef012 100644 --- a/packages/ui-default/components/highlighter/highlighter.page.styl +++ b/packages/ui-default/components/highlighter/highlighter.page.styl @@ -1,11 +1,9 @@ -@import 'prism-themes/themes/prism-vs.css' - pre, code background: $syntax-hl-bg-color pre > code background: none - line-height: 1.5 + line-height: 1.2 color: black text-align: left white-space: pre-wrap @@ -19,11 +17,16 @@ pre padding: rem(5px) margin-bottom: rem(20px) +pre.shiki + padding: 1em + margin: .5em 0 + border: 1px solid #dddddd + code color: $code-color font-size: rem($font-size-secondary) -.line-highlight +.shiki .line.highlighted position: absolute left: 0 right: 0 @@ -35,6 +38,33 @@ code line-height: inherit white-space: pre +.line-numbers code + counter-reset: step; + counter-increment: step 0; + + .line::before + content: counter(step); + counter-increment: step; + width: 1rem; + margin-right: 1.5rem; + display: inline-block; + text-align: right; + color: rgba(115,138,148,.6) + +.shiki + .tab, .space + position: relative + + .tab::before + content: '⇥'; + position: absolute; + opacity: 0.3; + + .space::before + content: '·'; + position: absolute; + opacity: 0.3; + .sample > .toolbar right: 0.9375em !important top: 3.7875em !important @@ -50,12 +80,17 @@ code font-family: $primary-font-family pointer-events: all user-select: none + transition: opacity .3s ease-in-out + z-index: 10 .toolbar-item a font-size: large !important border-radius: 0px !important background: none !important + box-shadow: 0 2px #0003 + padding: 0 .5em + color: #bbb .token.tab:not(:empty), .token.cr, @@ -87,7 +122,7 @@ code content: '\00B7' @media print { - .code-toolbar>.toolbar { + pre .toolbar { display: none !important; } } \ No newline at end of file diff --git a/packages/ui-default/components/highlighter/highlighter.page.ts b/packages/ui-default/components/highlighter/highlighter.page.ts index 7ae2b41b8..a5c2887a2 100644 --- a/packages/ui-default/components/highlighter/highlighter.page.ts +++ b/packages/ui-default/components/highlighter/highlighter.page.ts @@ -2,18 +2,10 @@ import $ from 'jquery'; import { AutoloadPage } from 'vj/misc/Page'; import { i18n, tpl } from 'vj/utils'; -const highlighterPage = new AutoloadPage('highlighterPage', () => { - import('./prismjs').then(({ default: prismjs }) => { +export default new AutoloadPage('highlighterPage', () => { + import('./shiki').then(({ highlightBlocks }) => { function runHighlight($container) { - $container.find('pre code').get().forEach((code) => { - const language = ($(code).attr('class') || '').trim(); - const m = language.match(/language-([a-z0-9]+)(\|[\d,-]+)/); - if (m?.[2]) { - $(code).parent().attr('data-line', m[2].substring(1)); - $(code).attr('class', `language-${m[1]}`); - } - }); - prismjs.highlightBlocks($container); + highlightBlocks($container); $container.find('pre code').get().forEach((code) => { const $code = $(code); const $root = $code.parent().parent(); @@ -41,5 +33,3 @@ const highlighterPage = new AutoloadPage('highlighterPage', () => { $(document).on('vjContentNew', (e) => runHighlight($(e.target))); }); }); - -export default highlighterPage; diff --git a/packages/ui-default/components/highlighter/prismjs.js b/packages/ui-default/components/highlighter/prismjs.js deleted file mode 100644 index 4e5f23779..000000000 --- a/packages/ui-default/components/highlighter/prismjs.js +++ /dev/null @@ -1,122 +0,0 @@ -import Prism from 'prismjs'; // eslint-disable-line - -import 'prismjs/plugins/toolbar/prism-toolbar'; -import 'prismjs/plugins/toolbar/prism-toolbar.css'; -import 'prismjs/plugins/line-numbers/prism-line-numbers'; -import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; -import 'prismjs/plugins/line-highlight/prism-line-highlight'; - -import Clipboard from 'clipboard'; -import $ from 'jquery'; -import components from 'prismjs/components'; -import getLoader from 'prismjs/dependencies'; -import Notification from 'vj/components/notification/index'; -import { i18n } from 'vj/utils'; -import languageMeta from './meta'; - -const files = require.context('prismjs/components/', true, /prism-[a-z0-9-]+\.js/); -const loadedLanguages = new Set(); -function loadLanguages() { - const languages = Object.keys(components.languages).filter((l) => l !== 'meta'); - const loaded = [...loadedLanguages, ...Object.keys(Prism.languages)]; - getLoader(components, languages, loaded).load((lang) => { - files(`./prism-${lang}.js`); - loadedLanguages.add(lang); - }); -} - -const languageExtMap = {}; -loadLanguages(); -// Map possible language names to Prism language name -languageMeta.forEach((meta) => { - for (let i = 0; i < meta.ext.length; ++i) { - if (Prism.languages[meta.ext[i]] !== undefined) { - meta.target = meta.ext[i]; - break; - } - } - meta.ext.forEach((ext) => { - languageExtMap[ext] = meta.target; - }); -}); - -// Copy to Clipboard -Prism.plugins.toolbar.registerButton('copy-to-clipboard', (env) => { - const linkCopy = document.createElement('a'); - linkCopy.href = 'javascript:;'; - linkCopy.textContent = 'Copy'; - const clip = new Clipboard(linkCopy, { text: () => env.code }); - clip.on('success', () => { - Notification.success(i18n('Content copied to clipboard!'), 1000); - }); - clip.on('error', () => { - Notification.error(i18n('Copy failed :(')); - }); - return linkCopy; -}); - -const invisibles = { - tab: /\t/, - crlf: /\r\n/, - lf: /\n/, - cr: /\r/, - space: / /, -}; - -function addInvisibles(grammar) { - if (!grammar || grammar.tab) return; - for (const name in invisibles) { - if (Object.prototype.hasOwnProperty.call(invisibles, name)) { - grammar[name] = invisibles[name]; - } - } - for (const name in grammar) { - if (Object.prototype.hasOwnProperty.call(grammar, name) && !invisibles[name]) { - if (name === 'rest') addInvisibles(grammar.rest); - else handlerInvisiblesToken(grammar, name); // eslint-disable-line @typescript-eslint/no-use-before-define - } - } -} - -function handlerInvisiblesToken(tokens, name) { - const value = tokens[name]; - const type = Prism.util.type(value); - if (type === 'RegExp') { - const inside = {}; - tokens[name] = { pattern: value, inside }; - addInvisibles(inside); - } else if (type === 'Array') { - for (let i = 0, l = value.length; i < l; i++) handlerInvisiblesToken(value, i); - } else { - value.inside ||= {}; - addInvisibles(value.inside); - } -} - -Prism.hooks.add('before-highlight', (env) => { - if (UserContext.showInvisibleChar) addInvisibles(env.grammar); -}); - -const prismjsApiWrap = { - highlightBlocks: ($dom) => { - $dom.find('pre code').get().forEach((code) => { - const $code = $(code); - const $pre = $code.parent(); - $pre.addClass('syntax-hl'); - const language = ($(code).attr('class') || '').trim(); - // try to map the language name - const m = language.match(/language-([a-z]+)/); - if (m && m[1]) { - const languageName = m[1].toLowerCase(); - if (languageExtMap[languageName]) { - $(code).attr('class', `language-${languageExtMap[languageName]}`); - } - } - Prism.highlightElement(code); - }); - }, - highlight: (text, grammar, language) => Prism.highlight(text, grammar, language), - Prism, -}; - -export default prismjsApiWrap; diff --git a/packages/ui-default/components/highlighter/shiki.ts b/packages/ui-default/components/highlighter/shiki.ts new file mode 100644 index 000000000..622a93e8d --- /dev/null +++ b/packages/ui-default/components/highlighter/shiki.ts @@ -0,0 +1,110 @@ +import { + transformerMetaHighlight, transformerNotationDiff, transformerNotationHighlight, + transformerNotationWordHighlight, transformerRenderWhitespace, +} from '@shikijs/transformers'; +import $ from 'jquery'; +import { + bundledLanguages, + bundledLanguagesAlias, + createHighlighter, +} from 'shiki/bundle/web'; +import Notification from 'vj/components/notification/index'; +import { i18n } from 'vj/utils'; + +$(document).on('click', '[data-clipboard-text]', function (ev) { + const content = $(this).attr('data-clipboard-text'); + const $temp = $('