diff --git a/src/code/demos/enUS/index.demo-entry.md b/src/code/demos/enUS/index.demo-entry.md index 1badb65f9..a107c8656 100644 --- a/src/code/demos/enUS/index.demo-entry.md +++ b/src/code/demos/enUS/index.demo-entry.md @@ -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. | | \ No newline at end of file diff --git a/src/code/demos/enUS/line-numbers.demo.vue b/src/code/demos/enUS/line-numbers.demo.vue index 43985cd2e..6787c08e5 100644 --- a/src/code/demos/enUS/line-numbers.demo.vue +++ b/src/code/demos/enUS/line-numbers.demo.vue @@ -6,7 +6,7 @@ It can show line numbers in the code block. @@ -20,7 +20,16 @@ export default defineComponent({ using namespace std; int main() { - cout << "It is always morning somewhere in the world." << endl; + cout <<"\\n" <
- +
@@ -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; }` } diff --git a/src/code/src/Code.tsx b/src/code/src/Code.tsx index 2f6a0cdfd..0e6fa4991 100644 --- a/src/code/src/Code.tsx +++ b/src/code/src/Code.tsx @@ -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 += '' - html = html.replace(/\r\n|\r|\n/g, function (a) { - num++ - const text = (' ' + String(num)).slice(-4) // 最大支持到千位数 - return a + '' - }) - html = '' + html - html = '' + 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 - : `
${
-                props.lineNumbers ? addLineNumbersForCode(html) : html
-              }
` + 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 ( - {this.$slots} + {mergedShowLineNumbers ? ( +
+            {this.lineNumbers}
+          
+ ) : null} + {this.$slots.default?.()}
) } diff --git a/src/code/src/styles/index.cssr.ts b/src/code/src/styles/index.cssr.ts index f6a4b05e1..8ed10d534 100644 --- a/src/code/src/styles/index.cssr.ts +++ b/src/code/src/styles/index.cssr.ts @@ -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; }` ] } diff --git a/src/code/styles/dark.ts b/src/code/styles/dark.ts index f344ee062..46dcf5ab5 100644 --- a/src/code/styles/dark.ts +++ b/src/code/styles/dark.ts @@ -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 } } } diff --git a/src/code/styles/light.ts b/src/code/styles/light.ts index 1f5bc9ddd..3ecb2d448 100644 --- a/src/code/styles/light.ts +++ b/src/code/styles/light.ts @@ -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 } }