refactor(code): show line numbers

This commit is contained in:
07akioni 2022-07-26 00:52:05 +08:00
parent f6c04c411e
commit 814e6908db
8 changed files with 94 additions and 109 deletions

View File

@ -49,8 +49,8 @@ line-numbers.vue
| --- | --- | --- | --- | --- |
| code | `string` | `''` | Incoming code string. | |
| hljs | `Object` | `undefined` | If you want to set hljs locally, pass it using this prop. | |
| language | `string` | `undefined` | Code language in highlightjs. | |
| trim | `boolean` | `true` | Whether to display trimmed code. | |
| inline | `boolean` | `false` | Whether the code is displayed as inline. | |
| language | `string` | `undefined` | Code language in highlightjs. | |
| show-line-numbers | `boolean` | `false` | Whether to show line numbers. Won't work if `inline` or `word-wrap` is `true`. | NEXT_VERSION |
| trim | `boolean` | `true` | Whether to display trimmed code. | |
| word-wrap | `boolean` | `false` | Whether to display word-wrapped code. | 2.24.0 |
| line-numbers | `boolean` | `false` | Whether to display line numbers. | |

View File

@ -6,7 +6,7 @@ It can show line numbers in the code block.
<template>
<div style="overflow: auto">
<n-code :code="code" language="cpp" line-numbers />
<n-code :code="code" language="cpp" show-line-numbers />
</div>
</template>
@ -20,7 +20,16 @@ export default defineComponent({
using namespace std;
int main() {
cout << "It is always morning somewhere in the world." << endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
cout <<"\\n" <<endl;
return 0;
}`
}

View File

@ -49,9 +49,9 @@ line-numbers.vue
| 名称 | 类型 | 默认值 | 说明 | 版本 |
| --- | --- | --- | --- | --- |
| code | `string` | `''` | 传入的 code 字符串 | |
| inline | `boolean` | `false` | 使用行内样式 | |
| hljs | `Object` | `undefined` | 如果你想局部设定 hljs可以通过这个属性传给组件 | |
| language | `string` | `undefined` | 代码在 highlightjs 中的语言 | |
| show-line-numbers | `boolean` | `false` | 是否显示行号,在 `inline``word-wrap` 的情况下不生效 | NEXT_VERSION |
| trim | `boolean` | `true` | 是否显示 trim 后的代码 | |
| inline | `boolean` | `false` | 使用行内样式 | |
| word-wrap | `boolean` | `false` | 代码过长时是否自动换行 | 2.24.0 |
| line-numbers | `boolean` | `false` | 是否显示行号 | |

View File

@ -6,7 +6,7 @@
<template>
<div style="overflow: auto">
<n-code :code="code" language="cpp" line-numbers />
<n-code :code="code" language="cpp" show-line-numbers />
</div>
</template>
@ -20,7 +20,14 @@ export default defineComponent({
using namespace std;
int main() {
cout << "最孤独的人最亲切,受过伤的人总是笑的最灿烂。" << "—— 素媛" << endl;
cout <<"你" << endl;
cout <<"觉" << endl;
cout <<"得" << endl;
cout <<"恨" << endl;
cout <<"却" << endl;
cout <<"离" << endl;
cout <<"不" << endl;
cout <<"开" << endl;
return 0;
}`
}

View File

@ -36,7 +36,7 @@ export const codeProps = {
uri: Boolean,
inline: Boolean,
wordWrap: Boolean,
lineNumbers: Boolean,
showLineNumbers: Boolean,
// In n-log, we only need to mount code's style for highlight
internalFontSize: Number,
internalNoHighlight: Boolean
@ -68,20 +68,10 @@ export default defineComponent({
language
}).value
}
// reference: https://www.yangdx.com/2020/04/144.html
const addLineNumbersForCode = (html: string): string => {
let num = 1
html += '<span class="ln-eof"></span>'
html = html.replace(/\r\n|\r|\n/g, function (a) {
num++
const text = (' ' + String(num)).slice(-4) // 最大支持到千位数
return a + '<span class="ln-num" data-num="' + text + '"></span>'
})
html = '<span class="ln-num" data-num=" 1"></span>' + html
html = '<span class="ln-bg"></span>' + html
return html
}
const mergedShowLineNumbersRef = computed(() => {
if (props.inline || props.wordWrap) return false
return props.showLineNumbers
})
const setCode = (): void => {
if (slots.default) return
const { value: codeEl } = codeRef
@ -93,11 +83,16 @@ export default defineComponent({
if (language) {
const html = createCodeHtml(language, code, props.trim)
if (html !== null) {
codeEl.innerHTML = props.inline
? html
: `<pre ${props.lineNumbers ? 'class="hljsln"' : ''}>${
props.lineNumbers ? addLineNumbersForCode(html) : html
}</pre>`
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
}
}
@ -105,24 +100,15 @@ export default defineComponent({
codeEl.textContent = code
return
}
const maybePreEl = codeEl.children[0]
if (maybePreEl && maybePreEl.tagName === 'PRE') {
if (props.lineNumbers) {
maybePreEl.classList.add('hljsln')
maybePreEl.innerHTML = addLineNumbersForCode(code)
} else {
maybePreEl.textContent = code
}
const maybePreEl = codeEl.querySelector('.__code__')
if (maybePreEl) {
maybePreEl.textContent = code
} else {
const warp = document.createElement('pre')
if (props.lineNumbers) {
warp.classList.add('hljsln')
warp.innerHTML = code
} else {
warp.textContent = code
}
const wrap = document.createElement('pre')
wrap.className = '__code__'
wrap.textContent = code
codeEl.innerHTML = ''
codeEl.appendChild(warp)
codeEl.appendChild(wrap)
}
}
onMounted(setCode)
@ -144,6 +130,7 @@ export default defineComponent({
textColor,
fontSize,
fontWeightStrong,
lineNumberTextColor,
// extracted from hljs atom-one-light.scss
'mono-3': $1,
'hue-1': $2,
@ -153,9 +140,7 @@ export default defineComponent({
'hue-5': $6,
'hue-5-2': $7,
'hue-6': $8,
'hue-6-2': $9,
'padding-color': $10,
'line-number-color': $11
'hue-6-2': $9
}
} = themeRef.value
const { internalFontSize } = props
@ -174,8 +159,7 @@ export default defineComponent({
'--n-hue-5-2': $7,
'--n-hue-6': $8,
'--n-hue-6-2': $9,
'--n-padding-color': $10,
'--n-line-number-color': $11
'--n-line-number-text-color': lineNumberTextColor
}
})
const themeClassHandle = inlineThemeDisabled
@ -191,25 +175,49 @@ export default defineComponent({
return {
mergedClsPrefix: mergedClsPrefixRef,
codeRef,
mergedShowLineNumbers: mergedShowLineNumbersRef,
lineNumbers: computed(() => {
let number = 1
const numbers: number[] = []
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?.themeClass,
onRender: themeClassHandle?.onRender
}
},
render () {
const { mergedClsPrefix, wordWrap, onRender } = this
const { mergedClsPrefix, wordWrap, mergedShowLineNumbers, onRender } = this
onRender?.()
return (
<code
class={[
`${mergedClsPrefix}-code`,
this.themeClass,
wordWrap && `${mergedClsPrefix}-code--word-wrap`
wordWrap && `${mergedClsPrefix}-code--word-wrap`,
mergedShowLineNumbers && `${mergedClsPrefix}-code--show-line-numbers`
]}
style={this.cssVars as any}
ref="codeRef"
>
{this.$slots}
{mergedShowLineNumbers ? (
<pre class={`${mergedClsPrefix}-code__line-numbers`}>
{this.lineNumbers}
</pre>
) : null}
{this.$slots.default?.()}
</code>
)
}

View File

@ -1,4 +1,4 @@
import { c, cB, cM } from '../../../_utils/cssr'
import { c, cB, cE, cM } from '../../../_utils/cssr'
// vars:
// --n-font-size
@ -15,11 +15,23 @@ import { c, cB, cM } from '../../../_utils/cssr'
// --n-hue-5-2
// --n-hue-6
// --n-hue-6-2
// --n-line-number-color
// --n-line-number-text-color
export default c([
cB('code', `
font-size: var(--n-font-size);
font-family: var(--n-font-family);
`, [
cM('show-line-numbers', `
display: flex;
`),
cE('line-numbers', `
user-select: none;
padding-right: 12px;
text-align: right;
transition: color .3s var(--n-bezier);
color: var(--n-line-number-text-color);
`),
cM('word-wrap', [
c('pre', `
white-space: pre-wrap;
@ -28,6 +40,8 @@ export default c([
]),
c('pre', `
margin: 0;
line-height: inherit;
font-size: inherit;
font-family: inherit;
`),
c('[class^=hljs]', `
@ -97,57 +111,6 @@ export default c([
}`,
`${codeClass} .hljs-link {
text-decoration: underline;
}`,
// 行号显示
`.hljsln {
position: relative;
display: block;
padding-left: 3.1em !important;
}`,
`.hljsln::-webkit-scrollbar {
height: 15px;
}`,
`.hljsln::-webkit-scrollbar-thumb {
background: #666;
}`,
`.hljsln::-webkit-scrollbar-thumb:hover {
background: #797979;
}`,
`.hljsln::-webkit-scrollbar-thumb:active {
background: #949494;
}`,
`.hljsln .ln-bg {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 2.4em;
height: 100%;
background: var(--n-padding-color);
}`,
`.hljsln .ln-num {
position: absolute;
z-index: 2;
left: 0;
width: 2.4em;
height: 1em;
text-align: center;
display: inline-block;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}`,
`.hljsln .ln-num::before {
color: var(--n-line-number-color);
font-style: normal;
font-weight: normal;
content: attr(data-num);
}`,
`.hljsln .ln-eof {
display: inline-block;
}`
]
}

View File

@ -21,8 +21,7 @@ const codeDark: CodeTheme = {
'hue-6': '#d19a66',
'hue-6-2': '#e6c07b',
// line-number styles
'padding-color': 'rgb(39, 39, 39)',
'line-number-color': textColor3
lineNumberTextColor: textColor3
}
}
}

View File

@ -19,8 +19,7 @@ const self = (vars: ThemeCommonVars) => {
'hue-6': '#986801',
'hue-6-2': '#c18401',
// line-number styles
'padding-color': 'rgb(243, 243, 243)',
'line-number-color': textColor3
lineNumberTextColor: textColor3
}
}