2024-08-02 18:19:39 +08:00
|
|
|
import { computed, defineComponent, h, onMounted, ref, toRef, watch } from 'vue';
|
|
|
|
import { useConfig, useHljs, useTheme, useThemeClass } from "../../_mixins/index.mjs";
|
2024-01-29 09:26:07 +08:00
|
|
|
import { codeLight } from "../styles/index.mjs";
|
|
|
|
import style from "./styles/index.cssr.mjs";
|
|
|
|
export const codeProps = Object.assign(Object.assign({}, useTheme.props), {
|
|
|
|
language: String,
|
|
|
|
code: {
|
|
|
|
type: String,
|
|
|
|
default: ''
|
|
|
|
},
|
|
|
|
trim: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true
|
|
|
|
},
|
|
|
|
hljs: Object,
|
|
|
|
uri: Boolean,
|
|
|
|
inline: Boolean,
|
|
|
|
wordWrap: Boolean,
|
|
|
|
showLineNumbers: Boolean,
|
|
|
|
// In n-log, we only need to mount code's style for highlight
|
|
|
|
internalFontSize: Number,
|
|
|
|
internalNoHighlight: Boolean
|
|
|
|
});
|
|
|
|
export default defineComponent({
|
|
|
|
name: 'Code',
|
|
|
|
props: codeProps,
|
|
|
|
setup(props, {
|
|
|
|
slots
|
|
|
|
}) {
|
|
|
|
const {
|
|
|
|
internalNoHighlight
|
|
|
|
} = props;
|
|
|
|
const {
|
|
|
|
mergedClsPrefixRef,
|
|
|
|
inlineThemeDisabled
|
|
|
|
} = useConfig();
|
|
|
|
const codeRef = ref(null);
|
|
|
|
const hljsRef = internalNoHighlight ? {
|
|
|
|
value: undefined
|
|
|
|
} : useHljs(props);
|
|
|
|
const createCodeHtml = (language, code, trim) => {
|
|
|
|
const {
|
|
|
|
value: hljs
|
|
|
|
} = hljsRef;
|
|
|
|
if (!hljs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (!(language && hljs.getLanguage(language))) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return hljs.highlight(trim ? code.trim() : code, {
|
|
|
|
language
|
|
|
|
}).value;
|
|
|
|
};
|
|
|
|
const mergedShowLineNumbersRef = computed(() => {
|
|
|
|
if (props.inline || props.wordWrap) return false;
|
|
|
|
return props.showLineNumbers;
|
|
|
|
});
|
|
|
|
const setCode = () => {
|
|
|
|
if (slots.default) return;
|
|
|
|
const {
|
|
|
|
value: codeEl
|
|
|
|
} = codeRef;
|
|
|
|
if (!codeEl) return;
|
|
|
|
const {
|
|
|
|
language
|
|
|
|
} = props;
|
|
|
|
const code = props.uri ? window.decodeURIComponent(props.code) : props.code;
|
|
|
|
if (language) {
|
|
|
|
const html = createCodeHtml(language, code, props.trim);
|
|
|
|
if (html !== null) {
|
|
|
|
if (props.inline) {
|
|
|
|
codeEl.innerHTML = html;
|
|
|
|
} else {
|
|
|
|
const prevPreEl = codeEl.querySelector('.__code__');
|
|
|
|
if (prevPreEl) codeEl.removeChild(prevPreEl);
|
|
|
|
const preEl = document.createElement('pre');
|
|
|
|
preEl.className = '__code__';
|
|
|
|
preEl.innerHTML = html;
|
|
|
|
codeEl.appendChild(preEl);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (props.inline) {
|
|
|
|
codeEl.textContent = code;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const maybePreEl = codeEl.querySelector('.__code__');
|
|
|
|
if (maybePreEl) {
|
|
|
|
maybePreEl.textContent = code;
|
|
|
|
} else {
|
|
|
|
const wrap = document.createElement('pre');
|
|
|
|
wrap.className = '__code__';
|
|
|
|
wrap.textContent = code;
|
|
|
|
codeEl.innerHTML = '';
|
|
|
|
codeEl.appendChild(wrap);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
onMounted(setCode);
|
|
|
|
watch(toRef(props, 'language'), setCode);
|
|
|
|
watch(toRef(props, 'code'), setCode);
|
|
|
|
if (!internalNoHighlight) watch(hljsRef, setCode);
|
|
|
|
const themeRef = useTheme('Code', '-code', style, codeLight, props, mergedClsPrefixRef);
|
|
|
|
const cssVarsRef = computed(() => {
|
|
|
|
const {
|
|
|
|
common: {
|
|
|
|
cubicBezierEaseInOut,
|
|
|
|
fontFamilyMono
|
|
|
|
},
|
|
|
|
self: {
|
|
|
|
textColor,
|
|
|
|
fontSize,
|
|
|
|
fontWeightStrong,
|
|
|
|
lineNumberTextColor,
|
|
|
|
// extracted from hljs atom-one-light.scss
|
|
|
|
'mono-3': $1,
|
|
|
|
'hue-1': $2,
|
|
|
|
'hue-2': $3,
|
|
|
|
'hue-3': $4,
|
|
|
|
'hue-4': $5,
|
|
|
|
'hue-5': $6,
|
|
|
|
'hue-5-2': $7,
|
|
|
|
'hue-6': $8,
|
|
|
|
'hue-6-2': $9
|
|
|
|
}
|
|
|
|
} = themeRef.value;
|
|
|
|
const {
|
|
|
|
internalFontSize
|
|
|
|
} = props;
|
|
|
|
return {
|
|
|
|
'--n-font-size': internalFontSize ? `${internalFontSize}px` : fontSize,
|
|
|
|
'--n-font-family': fontFamilyMono,
|
|
|
|
'--n-font-weight-strong': fontWeightStrong,
|
|
|
|
'--n-bezier': cubicBezierEaseInOut,
|
|
|
|
'--n-text-color': textColor,
|
|
|
|
'--n-mono-3': $1,
|
|
|
|
'--n-hue-1': $2,
|
|
|
|
'--n-hue-2': $3,
|
|
|
|
'--n-hue-3': $4,
|
|
|
|
'--n-hue-4': $5,
|
|
|
|
'--n-hue-5': $6,
|
|
|
|
'--n-hue-5-2': $7,
|
|
|
|
'--n-hue-6': $8,
|
|
|
|
'--n-hue-6-2': $9,
|
|
|
|
'--n-line-number-text-color': lineNumberTextColor
|
|
|
|
};
|
|
|
|
});
|
|
|
|
const themeClassHandle = inlineThemeDisabled ? useThemeClass('code', computed(() => {
|
|
|
|
return `${props.internalFontSize || 'a'}`;
|
|
|
|
}), cssVarsRef, props) : undefined;
|
|
|
|
return {
|
|
|
|
mergedClsPrefix: mergedClsPrefixRef,
|
|
|
|
codeRef,
|
|
|
|
mergedShowLineNumbers: mergedShowLineNumbersRef,
|
|
|
|
lineNumbers: computed(() => {
|
|
|
|
let number = 1;
|
|
|
|
const numbers = [];
|
|
|
|
let lastIsLineWrap = false;
|
|
|
|
for (const char of props.code) {
|
|
|
|
if (char === '\n') {
|
|
|
|
lastIsLineWrap = true;
|
|
|
|
numbers.push(number++);
|
|
|
|
} else {
|
|
|
|
lastIsLineWrap = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!lastIsLineWrap) {
|
|
|
|
numbers.push(number++);
|
|
|
|
}
|
|
|
|
return numbers.join('\n');
|
|
|
|
}),
|
|
|
|
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
|
|
|
|
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
|
|
|
|
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender
|
|
|
|
};
|
|
|
|
},
|
|
|
|
render() {
|
|
|
|
var _a, _b;
|
|
|
|
const {
|
|
|
|
mergedClsPrefix,
|
|
|
|
wordWrap,
|
|
|
|
mergedShowLineNumbers,
|
|
|
|
onRender
|
|
|
|
} = this;
|
|
|
|
onRender === null || onRender === void 0 ? void 0 : onRender();
|
|
|
|
return h("code", {
|
|
|
|
class: [`${mergedClsPrefix}-code`, this.themeClass, wordWrap && `${mergedClsPrefix}-code--word-wrap`, mergedShowLineNumbers && `${mergedClsPrefix}-code--show-line-numbers`],
|
|
|
|
style: this.cssVars,
|
|
|
|
ref: "codeRef"
|
|
|
|
}, mergedShowLineNumbers ? h("pre", {
|
|
|
|
class: `${mergedClsPrefix}-code__line-numbers`
|
|
|
|
}, this.lineNumbers) : null, (_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
|
|
}
|
|
|
|
});
|