mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
ui: switch to shiki
This commit is contained in:
7 files changed
+173
-150
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 = $('<textarea>');
|
||||
$('body').append($temp);
|
||||
$temp.val(content).select();
|
||||
document.execCommand('copy');
|
||||
$temp.remove();
|
||||
Notification.success(i18n('Content copied to clipboard!'), 1000);
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
const transformers = [
|
||||
transformerNotationDiff(),
|
||||
transformerNotationHighlight(),
|
||||
transformerNotationWordHighlight(),
|
||||
transformerMetaHighlight(),
|
||||
];
|
||||
|
||||
if (UserContext.showInvisibleChar) {
|
||||
transformers.push(transformerRenderWhitespace());
|
||||
}
|
||||
|
||||
async function highlightBlocks($dom) {
|
||||
const eles = $dom.find('pre code').get();
|
||||
const langs = Array.from(new Set(eles.map((code) => {
|
||||
const language = ($(code).attr('class') || '').trim();
|
||||
const m = language.match(/language-([a-z0-9]+)/);
|
||||
return m ? m[1] : null;
|
||||
}))).filter((i: string) => i && (bundledLanguages[i] || bundledLanguagesAlias[i])) as string[];
|
||||
const shiki = await createHighlighter({ langs, themes: ['github-dark', 'light-plus'] });
|
||||
for (const code of eles) {
|
||||
const $code = $(code);
|
||||
const $pre = $code.parent();
|
||||
if ($pre.hasClass('syntax-hl')) continue;
|
||||
const language = ($(code).attr('class') || '').trim();
|
||||
console.log(language);
|
||||
// try to map the language name
|
||||
const m = language.match(/language-([a-z0-9]+)(?=\.line-numbers)?(?=\|([\d,-]+))?/);
|
||||
if (!m) continue;
|
||||
$pre.replaceWith($(shiki.codeToHtml($code.text(), {
|
||||
lang: m[1],
|
||||
theme: 'light-plus',
|
||||
meta: {
|
||||
__raw: m[2] ? `{${m[2]}}` : null,
|
||||
},
|
||||
transformers: transformers.concat({
|
||||
pre(node) {
|
||||
this.addClassToHast(node, 'syntax-hl');
|
||||
if (language.includes('line-numbers')) {
|
||||
this.addClassToHast(node, 'line-numbers');
|
||||
}
|
||||
node.children.push({
|
||||
tagName: 'div',
|
||||
type: 'element',
|
||||
properties: {
|
||||
class: 'toolbar',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
tagName: 'div',
|
||||
type: 'element',
|
||||
properties: {
|
||||
class: 'toolbar-item',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
tagName: 'a',
|
||||
type: 'element',
|
||||
properties: {
|
||||
href: 'javascript:;',
|
||||
class: 'copy-to-clipboard',
|
||||
'data-clipboard-text': $code.text(),
|
||||
},
|
||||
children: [
|
||||
{ type: 'text', value: 'Copy' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
}),
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
async function highlight(text: string, grammar, language) {
|
||||
const shiki = await createHighlighter({ langs: [language], themes: ['github-dark', 'light-plus'] });
|
||||
return shiki.codeToHtml(text, { lang: language, theme: 'light-plus' });
|
||||
}
|
||||
|
||||
export {
|
||||
highlightBlocks,
|
||||
highlight,
|
||||
createHighlighter,
|
||||
};
|
||||
@@ -92,7 +92,6 @@
|
||||
"plugin-error": "^2.0.1",
|
||||
"postcss-loader": "7.3.4",
|
||||
"prism-themes": "^1.9.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"qface": "^1.4.1",
|
||||
"qrcode": "^1.5.3",
|
||||
@@ -131,6 +130,7 @@
|
||||
"webpackbar": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@shikijs/transformers": "^1.11.1",
|
||||
"ansi_up": "^6.0.2",
|
||||
"cssfilter": "^0.0.10",
|
||||
"esbuild": "0.23.0",
|
||||
@@ -149,6 +149,7 @@
|
||||
"markdown-it-table-of-contents": "^0.6.0",
|
||||
"nunjucks": "^3.2.4",
|
||||
"schemastery-jsonschema": "^1.1.0",
|
||||
"shiki": "^1.11.1",
|
||||
"streamsaver": "^2.0.6",
|
||||
"uuid": "^10.0.0",
|
||||
"xss": "^1.0.15"
|
||||
|
||||
@@ -76,13 +76,16 @@ const page = new NamedPage('problem_files', () => {
|
||||
}
|
||||
}
|
||||
|
||||
let prism = null;
|
||||
const load = import('../components/highlighter/prismjs');
|
||||
load.then(({ default: p }) => {
|
||||
prism = p.Prism;
|
||||
}).catch(() => { });
|
||||
|
||||
let shiki = null;
|
||||
let shikiPromise = null;
|
||||
async function handleClickRenameSelected(type) {
|
||||
shikiPromise ||= import('../components/highlighter/shiki')
|
||||
.then(({ createHighlighter }) => createHighlighter({ langs: ['regexp'], themes: ['light-plus'] }))
|
||||
.then((s) => {
|
||||
shiki = s;
|
||||
console.log('shiki loaded');
|
||||
}).catch(console.error);
|
||||
|
||||
const selectedFiles = ensureAndGetSelectedFiles(type);
|
||||
if (!selectedFiles?.length) return;
|
||||
let onActionButton = (_: string) => false;
|
||||
@@ -113,7 +116,13 @@ const page = new NamedPage('problem_files', () => {
|
||||
if (copy.endsWith('/')) {
|
||||
copy = copy.substring(0, copy.length - 1);
|
||||
flags.reverse();
|
||||
if (prism) setHighlight(`/${prism.highlight(copy, prism.languages.regex, 'RegExp')}/${flags.join('')}`);
|
||||
if (shiki) {
|
||||
const html = shiki.codeToHtml(copy, {
|
||||
lang: 'regex',
|
||||
theme: 'light-plus',
|
||||
});
|
||||
setHighlight(`/${$(html).html()}/${flags.join('')}`);
|
||||
}
|
||||
try {
|
||||
s = new RegExp(copy, flags.join(''));
|
||||
setRegexValid(true);
|
||||
@@ -161,7 +170,7 @@ const page = new NamedPage('problem_files', () => {
|
||||
|
||||
const style = { fontFamily: 'var(--code-font-family)' };
|
||||
|
||||
return <div className="typo" style={{ maxHeight: '60vh', overflow: 'scroll' }}>
|
||||
return <div className="typo" style={{ maxHeight: '60vh', overflow: 'auto' }}>
|
||||
{!preview ? <>
|
||||
<div className="row">
|
||||
<div className="medium-6 small-6 columns">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<pre class="line-numbers"><code class="language-{{ model.setting.langs[rdoc.lang].highlight }}">{{ rdoc['code'] }}</code></pre>
|
||||
<pre><code class="language-{{ model.setting.langs[rdoc.lang].highlight }} line-numbers">{{ rdoc['code'] }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
Reference in new issue
Block a user