Hugo默认没有提供代码分组的支持,本文基于这条帖子gitbook-plugin-prism-codetab-fox通过修改Even主题的相关代码,实现基于Tab的代码块分组功能。

代码修改

  1. assets/sass/_custom/_custom.scss中添加如下内容,此部分内容来源于codetab.css

    .codetabs {
    	border: 1px solid rgba(211, 220, 228, 1.00);
    }
    .codetabs .codetabs-header {
    	background: #f7f8f9;
    }
    .codetabs .codetabs-header .tab {
    	display: inline-block;
    	padding: 3px 20px;
    	cursor: pointer;
    	opacity: 0.5;
    	border-right: 1px solid rgba(211, 220, 228, 1.00);
    	border-bottom: 1px solid rgba(211, 220, 228, 1.00);
    	background-color: #f7f7f7;
    	font-weight: bold;
    	font-size: 13px;
    }
    .codetabs .codetabs-header .tab.active {
    	border-bottom: 0px;
    	cursor: default;
    	opacity: 1;
    	background: #fff;
    	color: #2674ba;
    }
    .codetabs .codetabs-body .tab {
    	display: none;
    }
    .codetabs .codetabs-body .tab.active {
    	display: block;
    }
    
  2. layouts/_default/_markup/render-codeblock.html的内容修改如下,主要是添加了额外的tab头信息用于后续初始化tab并展示

    <!-- only show line numbers when there is more than one line or specific it manually -->
    {{- $classStr := "language-%s" }}
    {{- $lineCount := len (split .Inner "\n") -}}
    {{- if gt $lineCount 1 }}
      {{- $classStr = print "line-numbers " print $classStr  }}
    {{- else -}}
      {{- $classStr = print "no-line-numbers " print $classStr  }}
    {{- end }}
    
    {{- $attributes := .Attributes }}
    
    {{- $globalCodeTabSeperator := site.Params.codeTabSeperator | default "#" -}}
    {{- $codeTabSeperator := .Page.Params.codeTabSeperator | default $globalCodeTabSeperator -}}
    {{- $title :=index (split .Type $codeTabSeperator) 1 | default .Type -}}
    {{- $type :=index (split .Type $codeTabSeperator) 0 | default .Type -}}
    
    {{- $classes := slice (printf $classStr $type) .Attributes.class }}
    {{- $attributes = merge $attributes (dict "class" (delimit $classes " ") "title" $title) -}}
    <pre
      {{- range $k, $v := $attributes }}
        {{- printf " %s=%q" $k $v | safeHTMLAttr }}
      {{- end -}}
    >
    <code>
    {{- .Inner -}}
    </code>
    </pre>
    {{- .Page.Store.Set "hasCodeBlock" true }}
    
  3. layouts/shortcodes下创建一个名为codetabs.html的文件并写入如下内容

    <div class='codetabs'>
      <div class="codetabs-header"></div>
      <div class="codetabs-body">{{ .Inner }}</div>
    </div>
    
  4. layouts/partials/scripts.html中找到代码块逻辑处理部分

    {{ if .Store.Get "hasCodeBlock" }}
      <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-highlight/prism-line-highlight.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
    {{ end }}
    

    并在尾部添加如下代码

    <script type="text/javascript">
         document.addEventListener('DOMContentLoaded', initCodeTabs);
    	 document.addEventListener('DOMContentLoaded', switchTabCodes);
    	 function initCodeTabs(){
    	     document.querySelectorAll('.codetabs').forEach(tab =>{
    		    let tabHeader = tab.querySelector('.codetabs-header');
    			let tabBody = tab.querySelector('.codetabs-body');
    		    let codeblocks = tab.querySelectorAll('.codetabs-body > .highlight-wrapper');
    			codeblocks.forEach((block,index) =>{
    			   let titleContent = block.querySelector('pre').getAttribute('title');
    			   let title = document.createElement('div');
    			   title.classList.add('tab');
    			   if(index == 0){
    			   	  title.classList.add('active');
    			   }
    			   title.setAttribute('data-codetab',index);
    			   title.appendChild(document.createTextNode(titleContent));
    			   tabHeader.appendChild(title);
    
    			   let div = document.createElement('div');
    			   div.classList.add('tab');
    			   if(index == 0){
    			   	  div.classList.add('active');
    			   }
    			   div.setAttribute('data-codetab',index);
    			   div.appendChild(block);
    			   tabBody.appendChild(div);
    			});
    		 });
    	 }
    	 function switchTabCodes(){
    		 document.querySelectorAll('.codetabs  .codetabs-header > .tab').forEach(tab => {
    			 tab.addEventListener('click', function() {
    			      if(tab.classList.contains('active')){
    					 return;
    				  }
    				  let tabId = tab.getAttribute('data-codetab');
    				  let codetabs = tab.parentNode.parentNode;
    
    				  codetabs.querySelectorAll('.codetabs .tab.active').forEach(e => e.classList.remove('active'));
    				  codetabs.querySelectorAll('.codetabs .tab[data-codetab="' + tabId + '"]').forEach(e => e.classList.add('active'));  
    			 });
    		 });
    	 }
    </script>
    

使用说明

默认方式

在原始的md文件中添加{{% codetabs %}} {{% /codetabs %}},之后按照常规的方式添加一个或多个代码块

{{% codetabs %}}  
​```java
class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!"); 
    }
}
​```

​```php
<?php
echo "My first PHP script!";
?>
​```

​```swift
let s: String = "sample";
​```

{{% /codetabs %}}

展示效果如下,可看出代码块已经被正确分组。

class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!"); 
    }
}
<?php
echo "My first PHP script!";
?>
let s: String = "sample";

自定义tab头

前述方式虽然能正常生效,但tab表头是对应的语言名称,当需要对多个相同的语言进行分组时,显示的tab名称会重复,影响阅读与使用体验。

class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!"); 
    }
}
<?php
echo "My first PHP script!";
?>
Map<String, Map<String, Map<String, List<String>>>> map = list.stream()
            .collect(Collectors.groupingBy(CompanyEntity::getName,
                    Collectors.groupingBy(CompanyEntity::getLocationName,
                    Collectors.groupingBy(CompanyEntity::getOfficeName,
                            Collectors.mapping(CompanyEntity::getBuildingName, Collectors.toCollection(ArrayList::new))))));

此时可通过分隔符的方式设置自定义的tab名称,默认的分隔符为 ::

{{% codetabs %}}  
​```java
class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!"); 
    }
}
​```

​```php::世界上最好的语言
<?php
echo "My first PHP script!";
?>
​```

​```java::Lambda表达式分组
Map<String, Map<String, Map<String, List<String>>>> map = list.stream()
            .collect(Collectors.groupingBy(CompanyEntity::getName,
                    Collectors.groupingBy(CompanyEntity::getLocationName,
                    Collectors.groupingBy(CompanyEntity::getOfficeName,
                            Collectors.mapping(CompanyEntity::getBuildingName, Collectors.toCollection(ArrayList::new))))));
​```

{{% /codetabs %}}

显示效果如下,可看出tab表头已经变的更有含义,同时也可根据需要只对部分代码块添加自定义表头

class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!"); 
    }
}
<?php
echo "My first PHP script!";
?>
Map<String, Map<String, Map<String, List<String>>>> map = list.stream()
            .collect(Collectors.groupingBy(CompanyEntity::getName,
                    Collectors.groupingBy(CompanyEntity::getLocationName,
                    Collectors.groupingBy(CompanyEntity::getOfficeName,
                            Collectors.mapping(CompanyEntity::getBuildingName, Collectors.toCollection(ArrayList::new))))));

tab分隔符设置

默认的tab名称分隔符为 ::,系统还支持页面分隔符和全局分隔符,它们的优先级如下

页面分隔符>全局分隔符>默认分隔符

全局分隔符可通过在config.toml添加如下配置来实现

[params]
  codeTabSeperator = "#"

页面分隔符可通过在文章头部添加如下配置来实现

codeTabSeperator: "::"