From be64b940990e7c5b6eb018dbfd94af4f98b918de Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Thu, 17 Jul 2025 18:48:25 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(code):=20=E6=96=B0=E5=A2=9E=E8=A1=8C?= =?UTF-8?q?=E9=AB=98=E4=BA=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持行高亮,聚焦,diff,自定义行高亮 --- src/css/modules/code.css | 9 +++ src/modules/code.js | 120 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/src/css/modules/code.css b/src/css/modules/code.css index afc60103..42751343 100644 --- a/src/css/modules/code.css +++ b/src/css/modules/code.css @@ -71,3 +71,12 @@ html #layuicss-skincodecss{display: none; position: absolute; width: 1989px;} .layui-code-view.layui-code-hl > .layui-code-ln-side{background-color: transparent;} .layui-code-theme-dark.layui-code-hl, .layui-code-theme-dark.layui-code-hl > .layui-code-ln-side{border-color: rgb(126 122 122 / 15%);} + +/*行高亮*/ +.layui-code-line-highlighted{background-color:rgba(142, 150, 170, .14)} +.layui-code-line-diff-add{background-color: rgba(16, 185, 129, .14);} +.layui-code-line-diff-remove{background-color: rgba(244, 63, 94, .14);} +.layui-code-line-diff-add:before{position:absolute; content: "+"; color: #18794e;} +.layui-code-line-diff-remove:before{position:absolute; content: "-"; color: #b8272c;} +.layui-code-has-focused-lines .layui-code-line:not(.layui-code-line-has-focus) {filter: blur(.095rem); opacity: .7; -webkit-transition: filter .35s, opacity .35s; transition: filter .35s, opacity .35s;} +.layui-code-has-focused-lines:hover .layui-code-line:not(.layui-code-line-has-focus) {filter: blur(); opacity: 1;} diff --git a/src/modules/code.js b/src/modules/code.js index 744e5ca1..97661567 100644 --- a/src/modules/code.js +++ b/src/modules/code.js @@ -47,6 +47,30 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ lang: 'text', // 指定语言类型 highlighter: false, // 是否开启语法高亮,'hljs','prism','shiki' langMarker: false, // 代码区域是否显示语言类型标记 + highlightLine: { // 行高亮 + // 聚焦 + focus: { + range: '', // 高亮范围,不可全局设置值 + comment: false, // 是否解析注释,性能敏感不可全局开启 [!code name:] + classActiveLine: 'layui-code-line-has-focus', // 添加到高亮行上的类 + classActivePre: 'layui-code-has-focused-lines' // 有高亮行时向根元素添加的类 + }, + // 高亮 + hl: { + comment: false, + classActiveLine: 'layui-code-line-highlighted', + }, + // diff++ + '++':{ + comment: false, + classActiveLine: 'layui-code-line-diff-remove', + }, + // diff-- + '--': { + comment: false, + classActiveLine: 'layui-code-line-diff-add', + } + } }; // 初始索引 @@ -60,6 +84,88 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ var trim = function(str){ return trimEnd(str).replace(/^\n|\n$/, ''); }; + + // '1,3-5,8' -> [1,3,4,5,8] + var parseHighlightedLines = function(rangeStr){ + var lineArray = $.map(rangeStr.split(','), function(v){ + var range = v.split('-'); + var start = parseInt(range[0], 10); + var end = parseInt(range[1], 10); + return start && end + ? $.map(new Array(end - start + 1), function(_, index){ return start + index }) + : start ? start : undefined + }) + return lineArray.length ? lineArray : undefined + } + + // 引用自 shiki + var hightLineRE = /(?:\/\/|\/\*{1,2}) *\[!code ([\w+-]+)(?::(\d+))?] *(?:\*{1,2}\/)?/; + var preprocessHighlightLine = function (highlightLineOptions, lines) { + var hasHighlightLine = false; + var needParseComment = false; + var lineClassMap = {}; + var preClassMap = {}; + + var updateLineClassMap = function (lineNumber, className) { + if (!lineClassMap[lineNumber]) { + lineClassMap[lineNumber] = []; + } + lineClassMap[lineNumber].push(className); + } + + $.each(highlightLineOptions, function (name, hlOpts) { + if (hlOpts.range) { + var hLines = parseHighlightedLines(hlOpts.range); + if (hLines.length > 0) { + hasHighlightLine = true; + if (hlOpts.classActivePre) { + preClassMap[hlOpts.classActivePre] = true; + } + $.each(hLines, function (i, lineNumber) { + updateLineClassMap(lineNumber, hlOpts.classActiveLine); + }); + } + } + if (hlOpts.comment) { + needParseComment = true; + } + }); + + if (needParseComment) { + // 解析注释中的高亮行 + $.each(lines, function (i, line) { + var matched = line.match(hightLineRE); + if (matched && matched[1] && lay.hasOwn(highlightLineOptions, matched[1])) { + var hlOpts = highlightLineOptions[matched[1]]; + hasHighlightLine = true; + if (hlOpts.classActivePre) { + preClassMap[hlOpts.classActivePre] = true; + } + // 要高亮的行数 + var lines = parseInt(matched[2], 10) + if (matched[2] && lines && lines > 1) { + var startLine = i + 1; + var endLine = startLine + lines - 1; + var hLines = parseHighlightedLines(startLine + '-' + endLine); + if (hLines.length > 0) { + $.each(hLines, function (i, lineNumber) { + updateLineClassMap(lineNumber, hlOpts.classActiveLine); + }); + } + }else{ + updateLineClassMap(i + 1, hlOpts.classActiveLine); + } + } + }); + } + + return { + needParseComment: needParseComment, + hasHighlightLine: hasHighlightLine, + preClass: Object.keys(preClassMap).join(' '), + lineClassMap: lineClassMap + } + } // export api exports('code', function(options, mode){ @@ -141,10 +247,16 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ // code 行 var lines = String(html).split(/\r?\n/g); + // 预处理行高亮 + var hl = preprocessHighlightLine(options.highlightLine, lines); + // 包裹 code 行结构 html = $.map(lines, function(line, num) { + var lineClass = (hl.hasHighlightLine && hl.lineClassMap[num + 1] && hl.lineClassMap[num + 1].length) + ? [CONST.ELEM_LINE].concat(hl.lineClassMap[num + 1]).join(' ') + : CONST.ELEM_LINE; return [ - '
', + '
', ( options.ln ? [ '
', @@ -153,12 +265,16 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ ].join('') : '' ), '
', - (line || ' '), + (hl.needParseComment ? line.replace(hightLineRE, '') : line) || ' ', '
', '
' ].join(''); }); + if(hl.preClass){ + othis.addClass(hl.preClass) + } + return { lines: lines, html: html From affab3c21cb31c69ef443d561c233b83f4e68f0b Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:58:38 +0800 Subject: [PATCH 2/4] update code --- src/modules/code.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/code.js b/src/modules/code.js index 97661567..d70cfaf6 100644 --- a/src/modules/code.js +++ b/src/modules/code.js @@ -63,12 +63,12 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ // diff++ '++':{ comment: false, - classActiveLine: 'layui-code-line-diff-remove', + classActiveLine: 'layui-code-line-diff-add', }, // diff-- '--': { comment: false, - classActiveLine: 'layui-code-line-diff-add', + classActiveLine: 'layui-code-line-diff-remove', } } }; From fe4d092e664058a01deabf2e079df00e5e938a19 Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Fri, 18 Jul 2025 11:39:53 +0800 Subject: [PATCH 3/4] =?UTF-8?q?chore:=20=E4=BC=98=E5=8C=96=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/code.js | 85 +++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/src/modules/code.js b/src/modules/code.js index d70cfaf6..e3fe9a30 100644 --- a/src/modules/code.js +++ b/src/modules/code.js @@ -50,8 +50,8 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ highlightLine: { // 行高亮 // 聚焦 focus: { - range: '', // 高亮范围,不可全局设置值 - comment: false, // 是否解析注释,性能敏感不可全局开启 [!code name:] + range: '', // 高亮范围,不可全局设置值 '1,3-5,8' + comment: false, // 是否解析注释,性能敏感不可全局开启 [!code type:] classActiveLine: 'layui-code-line-has-focus', // 添加到高亮行上的类 classActivePre: 'layui-code-has-focused-lines' // 有高亮行时向根元素添加的类 }, @@ -87,7 +87,8 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ // '1,3-5,8' -> [1,3,4,5,8] var parseHighlightedLines = function(rangeStr){ - var lineArray = $.map(rangeStr.split(','), function(v){ + if (typeof rangeStr !== 'string') return []; + var lines = $.map(rangeStr.split(','), function(v){ var range = v.split('-'); var start = parseInt(range[0], 10); var end = parseInt(range[1], 10); @@ -95,65 +96,67 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ ? $.map(new Array(end - start + 1), function(_, index){ return start + index }) : start ? start : undefined }) - return lineArray.length ? lineArray : undefined + return lines; } - // 引用自 shiki - var hightLineRE = /(?:\/\/|\/\*{1,2}) *\[!code ([\w+-]+)(?::(\d+))?] *(?:\*{1,2}\/)?/; - var preprocessHighlightLine = function (highlightLineOptions, lines) { + // 引用自 https://github.com/innocenzi/shiki-processor/blob/efa20624be415c866cc8e350d1ada886b6b5cd52/src/utils/create-range-processor.ts#L7 + // 添加了 HTML 注释支持,用来处理预览场景 + var highlightLineRegex = /(?:\/\/|\/\*{1,2}||-->)?/; + var preprocessHighlightLine = function (highlightLineOptions, codeLines) { var hasHighlightLine = false; var needParseComment = false; - var lineClassMap = {}; - var preClassMap = {}; + var lineClassMap = Object.create(null); + var preClassMap = Object.create(null); var updateLineClassMap = function (lineNumber, className) { if (!lineClassMap[lineNumber]) { - lineClassMap[lineNumber] = []; + lineClassMap[lineNumber] = [CONST.ELEM_LINE]; } lineClassMap[lineNumber].push(className); } - $.each(highlightLineOptions, function (name, hlOpts) { - if (hlOpts.range) { - var hLines = parseHighlightedLines(hlOpts.range); - if (hLines.length > 0) { + // 收集高亮行 className + $.each(highlightLineOptions, function (type, opts) { + if (opts.range) { + var highlightLines = parseHighlightedLines(opts.range); + if (highlightLines.length > 0) { hasHighlightLine = true; - if (hlOpts.classActivePre) { - preClassMap[hlOpts.classActivePre] = true; + if (opts.classActivePre) { + preClassMap[opts.classActivePre] = true; } - $.each(hLines, function (i, lineNumber) { - updateLineClassMap(lineNumber, hlOpts.classActiveLine); + $.each(highlightLines, function (i, lineNumber) { + updateLineClassMap(lineNumber, opts.classActiveLine); }); } } - if (hlOpts.comment) { + if (opts.comment) { needParseComment = true; } }); + // 解析行高亮注释并收集 className if (needParseComment) { - // 解析注释中的高亮行 - $.each(lines, function (i, line) { - var matched = line.match(hightLineRE); - if (matched && matched[1] && lay.hasOwn(highlightLineOptions, matched[1])) { - var hlOpts = highlightLineOptions[matched[1]]; + $.each(codeLines, function (i, line) { + var match = line.match(highlightLineRegex); + if (match && match[1] && lay.hasOwn(highlightLineOptions, match[1])) { + var opts = highlightLineOptions[match[1]]; hasHighlightLine = true; - if (hlOpts.classActivePre) { - preClassMap[hlOpts.classActivePre] = true; + if (opts.classActivePre) { + preClassMap[opts.classActivePre] = true; } - // 要高亮的行数 - var lines = parseInt(matched[2], 10) - if (matched[2] && lines && lines > 1) { + // 高亮的行数 + var lines = parseInt(match[2], 10) + if (match[2] && lines && lines > 1) { var startLine = i + 1; var endLine = startLine + lines - 1; - var hLines = parseHighlightedLines(startLine + '-' + endLine); - if (hLines.length > 0) { - $.each(hLines, function (i, lineNumber) { - updateLineClassMap(lineNumber, hlOpts.classActiveLine); + var highlightLines = parseHighlightedLines(startLine + '-' + endLine); + if (highlightLines.length > 0) { + $.each(highlightLines, function (i, lineNumber) { + updateLineClassMap(lineNumber, opts.classActiveLine); }); } }else{ - updateLineClassMap(i + 1, hlOpts.classActiveLine); + updateLineClassMap(i + 1, opts.classActiveLine); } } }); @@ -248,13 +251,13 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ var lines = String(html).split(/\r?\n/g); // 预处理行高亮 - var hl = preprocessHighlightLine(options.highlightLine, lines); + var highlightLineInfo = preprocessHighlightLine(options.highlightLine, lines); // 包裹 code 行结构 html = $.map(lines, function(line, num) { - var lineClass = (hl.hasHighlightLine && hl.lineClassMap[num + 1] && hl.lineClassMap[num + 1].length) - ? [CONST.ELEM_LINE].concat(hl.lineClassMap[num + 1]).join(' ') - : CONST.ELEM_LINE; + var lineClass = (highlightLineInfo.hasHighlightLine && highlightLineInfo.lineClassMap[num + 1]) + ? highlightLineInfo.lineClassMap[num + 1].join(' ') + : CONST.ELEM_LINE; return [ '
', ( @@ -265,14 +268,14 @@ layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){ ].join('') : '' ), '
', - (hl.needParseComment ? line.replace(hightLineRE, '') : line) || ' ', + (highlightLineInfo.needParseComment ? line.replace(highlightLineRegex, '') : line) || ' ', '
', '
' ].join(''); }); - if(hl.preClass){ - othis.addClass(hl.preClass) + if(highlightLineInfo.preClass){ + othis.addClass(highlightLineInfo.preClass) } return { From 0ff109ba295ba3c7b35fe8592a6b7d5baca4105b Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Sat, 19 Jul 2025 19:42:10 +0800 Subject: [PATCH 4/4] =?UTF-8?q?docs(code):=20=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/code/detail/options.md | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/code/detail/options.md b/docs/code/detail/options.md index a572fbd9..5b8d388c 100644 --- a/docs/code/detail/options.md +++ b/docs/code/detail/options.md @@ -393,6 +393,44 @@ onCopy: function(code, copied){ ``` + + + + + +[highlightLine](#options.highlightLine) 2.12+ + + + + +
+设置行高亮,可选值: +- hl:高亮 +- ++:diff++ +- --:diff-- +- focus:聚焦 +
+ +通过 `range` 选项或注释 `[!code type:]` 指定行高亮范围 + +``` +highlightLine: { + hl: { + range: '1,3-5,8', // 指定行高亮范围 '1,3,4,5,8' + comment: true, // 是否解析行中的高亮注释 + } +} +``` + +``` +layui.use(function(){ // [!code focus:5] + var layer = layui.layer; // [!code hl] + + layer.msg('test'); +}) + +``` +