Skip to content

Commit

Permalink
add post toc
Browse files Browse the repository at this point in the history
  • Loading branch information
heavenzone committed Jan 12, 2018
1 parent bc2cf64 commit 571e0ca
Showing 1 changed file with 301 additions and 0 deletions.
@@ -0,0 +1,301 @@
---
title: another toc solution for blogdown fixing the pure Chinese issue
author: Heaven Zone
date: '2018-01-12'
slug: another-toc-solution-for-blogdown-fixing-the-pure-chinese-issue
categories:
- Heaven Zone
tags:
- R
- blogdown
- toc
description: '为blogdown增加另外一个生成toc的方法'
simpletoc: true
---

## 简介

本文记录为R语言软件包blogdown增加另外一个生成toc的方法,修改自网上的一段代码。

我也把它放到了[github](https://github.com/heavenzone/simpletoc),有相同需求的朋友也可以拿去用。

## 问题描述

用blogdown自带的生成toc功能发现如果headline全部是中文,toc目录就没有生成链接,toc就不能跳转,rmd生成html_document的时候也是同样的问题[【传送门】](https://github.com/rstudio/rmarkdown/issues/1241),yihui回复是pandoc的问题,根据自己对javascript和jquery的一点点了解,觉得其实可以用js来生成toc的。

于是就有本文的出现了。

## 找解决方案

搜索一圈关于用javascript生成toc的方案,有最简单的几行代码就搞定可以生成toc的,例如如下([原地址](https://gist.github.com/cloudsben/6059930)):

```javascript
<script>
$(document).ready(function(){
$("h2,h3").each(function(i,item){
var tag = $(item).get(0).localName;
$(item).attr("id","wow"+i);
$("#mytoc").append('<a class="new'+tag+'" href="#wow'+i+'">'+$(this).text()+'</a></br>');
$(".newh2").css("margin-left",0);
$(".newh3").css("margin-left",20);
});
});
</script>
```

后来发现还可以**自动生成标题号**的,感觉上面那段代码挺简洁的,尝试自己在上面修改增加自动生成标题功能,发现自己不是程序员,不懂算法,没弄成,看了另外一个也是带自动生成带编码toc的,看不懂。

还了解到rmarkdown貌似也用到了tocify,看了一下发现太多依赖,既jquery又jquery-ui还bootstrap,要链接好几个文件,于是想一个简单的。

还看了[这个](http://i5ting.github.io/i5ting_ztree_toc/build/jquery.plugin.html)(太复杂不适合)和[这个](https://github.com/yijian166/md-toc.js)(没弄成功),最后看到[**这里**](https://www.cnblogs.com/qiudeqing/p/3229583.html),发现功能简单了一点,于是决定修改一下代码增加了几个功能:

- 可以修改在哪个标签下面进行搜索headlines(原来是body下的)
- 可以修改headlines的起始标题(原来是h1-h6,通常我们只需要h2-h4就够了)
- 一些小优化

## 应用到blogdown主题

下面简单说说怎么把这个弄到blogdown上去。

### 我的blogdown的主题

我的blogdown主题修改自[blackburn](https://github.com/yoshiharuyamashita/blackburn)

### 修改js文件

修改[上面所说的文档](https://www.cnblogs.com/qiudeqing/p/3229583.html)办成了下面这个样子:

```javascript
/**
* 本代码修改自:https://www.cnblogs.com/qiudeqing/p/3229583.html
*
**/
// =================添加变量控制标题深度
// 默认是h2至h4;
// 定于全局divID,方便toc函数和diyStyle参数调用
function isInArray(value,arr){
for(var i = 0; i < arr.length; i++){
if(value === arr[i]){
return true;
}
}
return false;
}

function toc(firstLevel = 2, lastLevel = 3, divID = "TOC", divToSearch = "" )
{

// 如果参数设置不正确的调整
if (firstLevel < 1 || firstLevel > 6 || firstLevel > lastLevel) firstLevel = 2;
if (lastLevel < firstLevel || lastLevel > 6) lastLevel = 3;
if (divToSearch == "") {
elementToSearch = document.body;
}
else {
elementToSearch = document.getElementById(divToSearch);
}
var depth = lastLevel - firstLevel + 1;
var searchHeadings = new Array();

// Find the TOC container element.
// If there isn't one, create one at the start of the document.
var toc = document.getElementById(divID);
if (!toc) {
toc = document.createElement("div");
toc.id = divID;
document.body.insertBefore(toc, document.body.firstChild);
}
// Find all section heading elements
var headings;
// =================生成要生成toc标题的标签
for (var i=firstLevel;i<firstLevel+depth;i++)
{
searchHeadings[i] = "H"+i;
}
//if (document.querySelectorAll) // Can we do it the easy way?
// headings = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
//else // Otherwise, find the headings the hard way
// ==================用elementToSearch控制搜索范围
headings = findHeadings(elementToSearch, [], searchHeadings);
// Recursively traverse the document body looking for headings
function findHeadings(root, sects) {
for(var c = root.firstChild; c != null; c = c.nextSibling) {
if (c.nodeType !== 1) continue;
//if (c.tagName.length == 2 && c.tagName.charAt(0) == "H")
// ================= 判断tagName是否在设置的标签里面
if (isInArray(c.tagName, searchHeadings))
sects.push(c);
else
findHeadings(c, sects);
}
return sects;
}
// Initialize an array that keeps track of section numbers.
var sectionNumbers = [0,0,0,0,0,0];
// Now loop through the section header elements we found.
for(var h = 0; h < headings.length; h++) {
var heading = headings[h];
// Skip the section heading if it is inside the TOC container.
if (heading.parentNode == toc) continue;
// Figure out what level heading it is.
var level = parseInt(heading.tagName.charAt(1));
if (isNaN(level) || level < 1 || level > 6) continue;
// Increment the section number for this heading level
// and reset all lower heading level numbers to zero.
sectionNumbers[level-1]++;
for(var i = level; i < 6; i++) sectionNumbers[i] = 0;
// Now combine section numbers for all heading levels
// to produce a section number like 2.3.1.
//=================修改成从firstLevel开始生成toc
var sectionNumber = sectionNumbers.slice(firstLevel-1,level).join(".")
// Add the section number to the section header title.
// We place the number in a <span> to make it styleable.
var span = document.createElement("span");
span.className = "TOCSectNum";
span.innerHTML = sectionNumber;
heading.insertBefore(span, heading.firstChild);
//===============改成直接在headlines上添加id用于链接跳转
heading.setAttribute("id", divID+sectionNumber);

// Wrap the heading in a named anchor so we can link to it.
//var anchor = document.createElement("a");
//anchor.name = "TOC"+sectionNumber;
//heading.parentNode.insertBefore(anchor, heading);
//anchor.appendChild(heading);

// Now create a link to this section.
var link = document.createElement("a");
link.href = "#"+divID+ sectionNumber; // Link destination
link.innerHTML = heading.innerHTML; // Link text is same as heading
// Place the link in a div that is styleable based on the level.
var entry = document.createElement("div");
//=================修改成firstLevel的标题在生成cssClass的名称的level为1开始递增
level = level-firstLevel+1;
entry.className = "TOCEntry TOCLevel" + level ;
entry.appendChild(link);
// And add the div to the TOC container.
toc.appendChild(entry);
}
}

// 添加样式函数
function addNewStyle(newStyle) {
var styleElement = document.getElementById('styles_js');

if (!styleElement) {
styleElement = document.createElement('style');
styleElement.type = 'text/css';
styleElement.id = 'styles_js';
document.getElementsByTagName('head')[0].appendChild(styleElement);
}
styleElement.appendChild(document.createTextNode(newStyle));
}
// 自定义样式,用不同的divid也可以有默认样式
function diyStyle (divID) {
addNewStyle('#'+divID+' .TOCSectNum { padding-right: 0.3em; }');
addNewStyle('#'+divID+' a { text-decoration: none; color: #0077bb !important; }');
addNewStyle('#'+divID+' .TOCLevel1 { padding-left: 0.2em; border-left:#0077bb solid 0.2em;}');
for (i = 2; i<=6; i++) {
k = i-1;
addNewStyle('#'+divID+' .TOCLevel'+i+' { margin-left: '+k+'em;}');
}
//addNewStyle('#'+divID+' { width: 200px;max-height: 100%;overflow: auto;margin-left: 50%;position: fixed;border: 1px solid #ccc;webkit-border-radius: 6px;moz-border-radius: 6px;border-radius: 12px;padding: 0.8em;');
addNewStyle('#'+divID+' {float:right;margin:0 0 1em 1em;position:fixed;top:100px;right:50px;border: 1px solid #ccc;webkit-border-radius: 6px;moz-border-radius: 6px;border-radius: 12px;padding: 0.8em;}');
}
```

把这个代码保存好,我把它改了个名字叫`simpletoc.js`,然后把它放到`themes/<theme-name>/static/js/simpletoc.js`

### 添加partials文件**simpletoc.html**

接着在`themes/<theme-name>/layouts/partials/`下面添加文件`simpletoc.html`,内容如下:

```javascript
<script src="/js/krup-toc.js"></script>
<script>
var firstLvl = 2; // h2开始
var lastLvl = 4; // h4结束
var divID = "TOC";
var divToSearch = "post-content";
window.onload=function(){
//参数控制
// 参数一:起始level,默认为h2
// 参数二:末位Level,默认为h3
// 参数三:用于生成toc的divID,默认为TOC
// 参数四:搜索headlines的divID
toc(firstLvl, lastLvl, divID, divToSearch);
diyStyle(divID);
addNewStyle('@media (max-width: 48em) {#'+divID+'{display: none;} }');
}
</script>
<div id="TOC"></div>
```

### 修改`single.html`文件

接着修改`themes/<theme-name>/layouts/post/single.html`文件,其实就是增加了带simpletoc那三行代码:

```html
{{ partial "header.html" . }}
<div class="header">
<h1>{{ .Title }}</h1>
<h2>{{ .Description }}</h2>
</div>
<div id="post-content" class="content">

{{ partial "post_meta.html" . }}

{{ if .Params.simpletoc }}
{{ partial "simpletoc.html" . }}
{{ end }}

{{ .Content }}
{{ partial "prev_next_post.html" . }}
{{ partial "disqus.html" . }}
</div>
{{ partial "footer.html" . }}
```


而其中的`.Params.simpletoc`是用来在**rmd文件**里面添加参数`simpletoc`来控制要不要显示toc,例如下面这个rmd头部:

```
---
title: Test toc
author: Heaven Zone
date: '2018-01-11'
slug: test-toc
categories:
- R
tags:
- R
- blogdown
simpletoc: true
---
```

### 调整样式

由于不懂美工,外观有点简陋。

默认样式主要是在`simpletoc.js`文件通过js添加了,由于[blackburn](https://github.com/yoshiharuyamashita/blackburn)主题用的是[pure](https://purecss.io)框架(其实blackburn也是基于该主页的默认主题),所以是响应式的,于是在partials文件`simpletoc.html`里面添加了下面这个样式:

```
addNewStyle('@media (max-width: 48em) {#'+divID+'{display: none;} }');
```

也就是当浏览器宽度缩小到一定程度就不显示toc了,这个是针对pure框架的,其他框架css可能不知道是不是也是一样。

另外也针对blackburn主题做了一个小调整,当浏览器宽度缩小但不至于消失的情况下,调整了标签layout的padding属性,为toc预留空间,避免遮挡了文章内容,修改文件为`themes/<theme-name>/css/side-menu.css`,在倒数第二段,也就是`@media (min-width: 48em) {`里面的`#layout`里面增加了`padding-right: 200px; `

没有作高度缩小的时候自动折叠滚动的处理,现在这样基本都满足需要了,有空闲再做研究-_-!

## 效果图

最后上一个效果图吧。

![](https://gitee.com/heavenzone/picturebed/raw/master/zhonghaoguang.com/20180112-1-simpletoc.png)




3 comments on commit 571e0ca

@yihui
Copy link

@yihui yihui commented on 571e0ca Jan 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 JS 水平再稍微练练应该就可以在我的其它项目里帮忙了。

@heavenzone
Copy link
Owner Author

@heavenzone heavenzone commented on 571e0ca Jan 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

只是拿了别人几个函数做了些许改动而已,还没认真学过js呢,离帮忙项目应该还比较远吧,不过像这篇文章这样帮忙写一下文档教程(中文-_-!)或者测试R包,我觉得还是可以,没问题的,因为我能从初学者的角度去写,应该会更加simple易懂,高手有可能会认为一些细节其他人可能已经懂的,而忽略掉一些细节的话,初学者有时会觉得有点难。

就好像我之前去解决用knit生成rticles-ctex pdf文档时plot中标题中文乱码的问题,我真的弄了很久才发现firefox收藏夹里面之前已经收藏了这个issue/799(可能是我收藏这个issue的时候还没安装showtext所以没有成功),虽然这个过程中也能学到东西,不过还是觉得这个过程花的时间有点长(觉得自己很菜不敢问-_-)。

所以,解决了这个问题之后,其实我想修改rticles\rmarkdown\templates\ctex\skeleton\skeleton.Rmd,在chunk set里面添加参数fig.showtext=TRUE,或者应该直接在setup chunk里面全局设置knitr::opts_chunk$set(fig.showtext=TRUE),做一个带中文标题的plot,然后做一个pull request的,好让像我一样有这个问题的人节省很多时间,既然这个模板是用来处理中文问题的,那么就应该把所有已经解决的中文问题都放在里面,让初学者在一个地方看到所有问题的解决办法(虽然说issue里面已经解决了这个问题,但是很多时候很难会去挨个去看issue),但是作为一个初学者(非程序猿,信息专业)真的有时候会想,我做这个会不会很low(估计很多大神没时间处理这种小事呢-_-!),直到看了你这篇文章:I Don't Always Offer Bounties on StackOverflow才有勇气发表一些自己发现的问题,而且看到里面有个同胞也提到了自己也刚好想问的问题,知道有解决办法了,就马上想到ctex模板也是有同样的问题,所以就做了一个pull request了。

另外也聊一些其他的吧,Yihui在一些博文里面会说到自己不是高技术型程序员,鉴于Yihui是统计学出身,我才会有点同意这个说法,但是我觉得Yihui绝对是产品型的程序员,因为你的包实在是给力,接地气,666,说改变世界,可能会被人说哇众取宠,但是说改变了R的世界,应该很多R人会同意^_^

Yihui很多次提到自己是站在巨人之上,我感觉Yihui是不是在很多巨人的肩上跑来跑去了?哈哈!

我该修改一下ctex模板的skeleton文件做个pull request吗?大神有空处理这种琐事吗?-_-!

@yihui
Copy link

@yihui yihui commented on 571e0ca Jan 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我很重视新手角度,写中文教程和测试 R 包对我来说都是很重要的工作,欢迎你参与贡献!可以写一篇 blogdown 的中文介绍文章投稿给统计之都 https://github.com/cosname/cosx.org

我并不在乎提问者的技术水平如何,我欢迎新手提问,但我有条件:我希望新手先花点时间自己研究一下,并详细告诉我你的失败尝试,越详细越好。因为我手里维护着二十多个 R 包,我真的没有时间去对每个问题一步步追问,我要是一看问题需要我追问信息,我就只能默默放弃了。

你总结得很对,我不是技术型程序猿,而是偏向产品型的。在成功的软件包中,技术只是一部分(甚至只是一小部分)因素。我确实也喜欢在巨人的肩膀上跑来跑去,虽然我自己造不出巨人来,但我对如何把这些巨人拼成一幅更大的图比较在行。

至于 ctex 模板中是否要启用 fig.showtext 选项,我并没有强烈的支持或反对意见,但我觉得至少 ctex 模板中可以提一下这件事。

Please sign in to comment.