diff --git a/docs/code/detail/options.md b/docs/code/detail/options.md index a572fbd9..af3fdcce 100644 --- a/docs/code/detail/options.md +++ b/docs/code/detail/options.md @@ -11,13 +11,13 @@ 描述 类型 默认值 - + elem - + 绑定元素选择器或 DOM 对象 @@ -37,11 +37,11 @@ preview 2.8+ - + 是否开启 Code 预览功能,可选值有: - `true` 开启 Code 的普通预览 -- `false` 关闭 Code 预览(默认) +- `false` 关闭 Code 预览(默认) - `"iframe"` 开启 Code 在 iframe 模式中预览 当开启该属性时,`elem` 指定的元素需要设置成以下结构: @@ -65,13 +65,13 @@ code content layout 2.8+ - + 开启预览后的面板布局方式,值为一个数组,数组的可选成员有: - `code` 代码区域 - `preview` 预览区域 -面板将根据数组的排列顺序来显示,如: +面板将根据数组的排列顺序来显示,如: ``` layout: ['code', 'preview'] @@ -84,7 +84,7 @@ layout: ['code', 'preview'] style 2.8+ - + 设置 Code 和预览区域的公共样式 @@ -94,7 +94,7 @@ layout: ['code', 'preview'] codeStyle 2.8+ - + 设置 Code 区域的局部样式,优先级高于 `style` 属性 @@ -104,7 +104,7 @@ layout: ['code', 'preview'] previewStyle 2.8+ - + 设置预览区域的局部样式,优先级高于 `style` 属性 @@ -114,7 +114,7 @@ layout: ['code', 'preview'] id 2.8+ - + 设置实例的唯一索引,以便用于其他操作 @@ -124,7 +124,7 @@ layout: ['code', 'preview'] className 2.8+ - + 追加实例面板的 `className`,以便对其自定义样式 @@ -134,7 +134,7 @@ layout: ['code', 'preview'] tools 2.8+ - + 用于开启 `preview` 属性后的面板头部右侧区域工具栏图标,值为一个数组,内置成员: - `copy` 2.8.2+ : 代码复制 @@ -168,7 +168,7 @@ tools: [ toolsEvent 2.8+ - + 点击工具栏的回调函数,功能同 `tools` 中的 `event`,只是需通过 `type` 属性来区分是哪个工具菜单。 ``` @@ -187,7 +187,7 @@ toolsEvent: function(obj){ copy 2.8.2+ - + 用于开启代码复制功能图标。若开启 `priview`,则自动放置在 `tools` 属性中,复制图标将显示在容器右上角工具栏;若未开启 `priview`,则显示在 code 区域右上角。 @@ -201,7 +201,7 @@ toolsEvent: function(obj){ text 2.8+ - + 自定义默认文本,值为一个对象,可选成员有: ``` @@ -218,7 +218,7 @@ text: { header 2.8+ - + 是否开启 Code 栏头部区域。 @@ -232,7 +232,7 @@ text: { ln - + 是否显示 Code 区域的行号 @@ -246,7 +246,7 @@ text: { theme 2.8.17+ - + Code 容器的主题风格,可选值有: - `light` 浅色模式(默认) @@ -259,7 +259,7 @@ Code 容器的主题风格,可选值有: encode - + 是否对 code 中的 html 进行编码(转义)。 @@ -358,7 +358,7 @@ code 组件语法高亮相关示例: -
+
组件渲染完毕的回调函数,函数返回一个 object 类型参数
@@ -379,7 +379,7 @@ done: function(obj){ -
+
点击复制图标时的回调函数。
@@ -393,6 +393,49 @@ onCopy: function(code, copied){ ``` + + + + + +[highlightLine](#options.highlightLine) 2.12+ + + + + +
+ +设置行高亮,可选项: +- `hl` : 高亮 +- `++` : diff++ +- `--` : diff-- +- `focus` : 聚焦 + +
+ +可通过 `hl.range` 选项来设置行高亮范围: + +``` +highlightLine: { + hl: { + range: '1,3-5,8', // 指定行高亮范围 '1,3,4,5,8' + comment: true, // 是否解析行中的高亮注释 + } +} +``` + +或通过特定注释格式: `[!code :]` 指定行高亮范围,规则解释: +- `` : `highlightLine` 的可选项,如 `h1, focus` 等 +- `` : 行数(含本行) + +``` +111 聚焦 2 行(含本行) // [!code focus:2] +222 高亮本行 // [!code hl] +333 +``` + +highlightLine 选项的详细用法可参考:highlightLine 行高亮在线示例 + diff --git a/examples/code.html b/examples/code.html index 2bb69152..93503fda 100644 --- a/examples/code.html +++ b/examples/code.html @@ -4,15 +4,16 @@ Code blocks adorn - Layui - - + +
+

代码高亮

+
   
 
+
+

行高亮和聚焦

+
+
+
+
+ +
+

diff

+
+
+
+
+ +

普通示例

+

 code line
 code line
diff --git a/src/css/modules/code.css b/src/css/modules/code.css
index bb5d4af9..f1d3af83 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 cd4aa7fe..582cf7c2 100644
--- a/src/modules/code.js
+++ b/src/modules/code.js
@@ -48,6 +48,30 @@ layui.define(['lay', 'i18n', 'util', 'element', 'tabs', 'form'], function(export
     lang: 'text', // 指定语言类型
     highlighter: false, // 是否开启语法高亮,'hljs','prism','shiki'
     langMarker: false, // 代码区域是否显示语言类型标记
+    highlightLine: { // 行高亮
+      // 聚焦
+      focus: {
+        range: '', // 高亮范围,不可全局设置值 '1,3-5,8'
+        comment: false, // 是否解析注释,性能敏感不可全局开启  [!code type:]
+        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-add',
+      },
+      // diff--
+      '--': {
+        comment: false,
+        classActiveLine: 'layui-code-line-diff-remove',
+      }
+    }
   };
 
   // 初始索引
@@ -61,6 +85,91 @@ layui.define(['lay', 'i18n', 'util', 'element', 'tabs', 'form'], function(export
   var trim = function(str){
     return trimEnd(str).replace(/^\n|\n$/, '');
   };
+  
+  // '1,3-5,8' -> [1,3,4,5,8]
+  var parseHighlightedLines  = function(rangeStr){
+    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);
+      return start && end
+        ? $.map(new Array(end - start + 1), function(_, index){ return start + index })
+        : start ? start : undefined
+    })
+    return 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 = Object.create(null);
+    var preClassMap = Object.create(null);
+
+    var updateLineClassMap = function (lineNumber, className) {
+      if (!lineClassMap[lineNumber]) {
+        lineClassMap[lineNumber] = [CONST.ELEM_LINE];
+      }
+      lineClassMap[lineNumber].push(className);
+    }
+
+    // 收集高亮行 className
+    $.each(highlightLineOptions, function (type, opts) {
+      if (opts.range) {
+        var highlightLines = parseHighlightedLines(opts.range);
+        if (highlightLines.length > 0) {
+          hasHighlightLine = true;
+          if (opts.classActivePre) {
+            preClassMap[opts.classActivePre] = true;
+          }
+          $.each(highlightLines, function (i, lineNumber) {
+            updateLineClassMap(lineNumber, opts.classActiveLine);
+          });
+        }
+      }
+      if (opts.comment) {
+        needParseComment = true;
+      }
+    });
+
+    // 解析行高亮注释并收集 className
+    if (needParseComment) {
+      $.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 (opts.classActivePre) {
+            preClassMap[opts.classActivePre] = true;
+          }
+          // 高亮的行数
+          var lines = parseInt(match[2], 10)
+          if (match[2] && lines && lines > 1) {
+            var startLine = i + 1;
+            var endLine = startLine + lines - 1;
+            var highlightLines = parseHighlightedLines(startLine + '-' + endLine);
+            if (highlightLines.length > 0) {
+              $.each(highlightLines, function (i, lineNumber) {
+                updateLineClassMap(lineNumber, opts.classActiveLine);
+              });
+            }
+          }else{
+            updateLineClassMap(i + 1, opts.classActiveLine);
+          }
+        }
+      });
+    }
+
+    return {
+      needParseComment: needParseComment,
+      hasHighlightLine: hasHighlightLine,
+      preClass: Object.keys(preClassMap).join(' '),
+      lineClassMap: lineClassMap
+    }
+  }
 
   // export api
   exports('code', function(options, mode){
@@ -142,10 +251,16 @@ layui.define(['lay', 'i18n', 'util', 'element', 'tabs', 'form'], function(export
       // code 行
       var lines = String(html).split(/\r?\n/g);
 
+      // 预处理行高亮
+      var highlightLineInfo = preprocessHighlightLine(options.highlightLine, lines);
+
       // 包裹 code 行结构
       html = $.map(lines, function(line, num) {
+        var lineClass = (highlightLineInfo.hasHighlightLine && highlightLineInfo.lineClassMap[num + 1])
+          ? highlightLineInfo.lineClassMap[num + 1].join(' ')
+          : CONST.ELEM_LINE;
         return [
-          '
', + '
', ( options.ln ? [ '
', @@ -154,12 +269,16 @@ layui.define(['lay', 'i18n', 'util', 'element', 'tabs', 'form'], function(export ].join('') : '' ), '
', - (line || ' '), + (highlightLineInfo.needParseComment ? line.replace(highlightLineRegex, '') : line) || ' ', '
', '
' ].join(''); }); + if(highlightLineInfo.preClass){ + othis.addClass(highlightLineInfo.preClass) + } + return { lines: lines, html: html