第 10 篇:小细节 Markdown 文章自动生成目录,提升阅读体验
作者:HelloGitHub-追梦人物
文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库
上一篇中我们使用了 Markdown 来为文章提供排版支持。Markdown 在解析内容的同时还可以自动提取整个内容的目录结构,现在我们来使用 Markdown 为文章自动生成目录。
在文中插入目录
先来回顾一下博客的 Post(文章)模型,其中 body
是我们存储 Markdown 文本的字段:
blog/models.py
from django.db import models
class Post(models.Model):
# Other fields ...
body = models.TextField()
再来回顾一下文章详情页的视图,我们在 detail
视图函数中将 post
的 body
字段中的 Markdown 文本解析成了 HTML 文本,然后传递给模板显示。
blog/views.py
def detail(request, pk):
post = get_object_or_404(Post, pk=pk)
post.body = markdown.markdown(post.body,
extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
'markdown.extensions.toc',
])
return render(request, 'blog/detail.html', context={'post': post})
markdown.markdown()
方法把 post.body
中的 Markdown 文本解析成了 HTML 文本。同时我们还给该方法提供了一个 extensions
的额外参数。其中 markdown.extensions.toc
就是自动生成目录的拓展(这里可以看出我们有先见之明,如果你之前没有添加的话记得现在添加进去)。
在渲染 Markdown 文本时加入了 toc 拓展后,就可以在文中插入目录了。方法是在书写 Markdown 文本时,在你想生成目录的地方插入 [TOC]
标记即可。例如新写一篇 Markdown 博文,其 Markdown 文本内容如下:
[TOC]
## 我是标题一
这是标题一下的正文
## 我是标题二
这是标题二下的正文
### 我是标题二下的子标题
这是标题二下的子标题的正文
## 我是标题三
这是标题三下的正文
其最终解析后的效果就是:
原本 [TOC]
标记的地方被内容的目录替换了。
在页面的任何地方插入目录
上述方式的一个局限性就是只能通过 [TOC]
标记在文章内容中插入目录。如果我想在页面的其它地方,比如侧边栏插入一个目录该怎么做呢?方法其实也很简单,只需要稍微改动一下解析 Markdown 文本内容的方式即可,具体代码就像这样:
blog/views.py
def detail(request, pk):
post = get_object_or_404(Post, pk=pk)
md = markdown.Markdown(extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
'markdown.extensions.toc',
])
post.body = md.convert(post.body)
post.toc = md.toc
return render(request, 'blog/detail.html', context={'post': post})
和之前的代码不同,我们没有直接用 markdown.markdown()
方法来渲染 post.body
中的内容,而是先实例化了一个 markdown.Markdown
对象 md
,和 markdown.markdown()
方法一样,也传入了 extensions
参数。接着我们便使用该实例的 convert
方法将 post.body
中的 Markdown 文本解析成 HTML 文本。而一旦调用该方法后,实例 md
就会多出一个 toc
属性,这个属性的值就是内容的目录,我们把 md.toc
的值赋给 post.toc
属性(要注意这个 post 实例本身是没有 toc 属性的,我们给它动态添加了 toc 属性,这就是 Python 动态语言的好处)。
接下来就在博客文章详情页的文章目录侧边栏渲染文章的目录吧!删掉占位用的目录内容,替换成如下代码:
{% block toc %}
<div class="widget widget-content">
<h3 class="widget-title">文章目录</h3>
{{ post.toc|safe }}
</div>
{% endblock toc %}
即使用模板变量标签 {{ post.toc }} 显示模板变量的值,注意 post.toc 实际是一段 HTML 代码,我们知道 django 会对模板中的 HTML 代码进行转义,所以要使用 safe 标签防止 django 对其转义。其最终渲染后的效果就是:
处理空目录
现在目录已经可以完美生成了,不过还有一个异常情况,当文章没有任何标题元素时,Markdown 就提取不出目录结构,post.toc 就是一个空的 div 标签,如下:
<div class="toc">...............................
<ul></ul>
</div>
对于这种没有目录结构的文章,在侧边栏显示一个目录是没有意义的,所以我们希望只有在文章存在目录结构时,才显示侧边栏的目录。那么应该怎么做呢?
分析 toc 的内容,如果有目录结构,ul 标签中就有值,否则就没有值。我们可以使用正则表达式来测试 ul 标签中是否包裹有元素来确定是否存在目录。
def detail(request, pk):
post = get_object_or_404(Post, pk=pk)
md = markdown.Markdown(extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
'markdown.extensions.toc',
])
post.body = md.convert(post.body)
m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
post.toc = m.group(1) if m is not None else ''
return render(request, 'blog/detail.html', context={'post': post})
这里我们正则表达式去匹配生成的目录中包裹在 ul 标签中的内容,如果不为空,说明目录,就把 ul 标签中的值提取出来(目的是只要包含目录内容的最核心部分,多余的 HTML 标签结构丢掉)赋值给 post.toc
;否则,将 post 的 toc 置为空字符串,然后我们就可以在模板中通过判断 post.toc 是否为空,来决定是否显示侧栏目录:
{% block toc %}
{% if post.toc %}
<div class="widget widget-content">
<h3 class="widget-title">文章目录</h3>
<div class="toc">
<ul>
{{ post.toc|safe }}
</ul>
</div>
</div>
{% endif %}
{% endblock toc %}
这里我们看到了一个新的模板标签 {% if %}
,这个标签用来做条件判断,和 Python 中的 if 条件判断是类似的。
美化标题的锚点 URL
文章内容的标题被设置了锚点,点击目录中的某个标题,页面就会跳到该文章内容中标题所在的位置,这时候浏览器的 URL 显示的值可能不太美观,比如像下面的样子:
#_1
就是锚点,Markdown 在设置锚点时利用的是标题的值,由于通常我们的标题都是中文,Markdown 没法处理,所以它就忽略的标题的值,而是简单地在后面加了个 _1 这样的锚点值。为了解决这一个问题,需要修改一下传给 extentions
的参数,其具体做法如下:
blog/views.py
from django.utils.text import slugify
from markdown.extensions.toc import TocExtension
def detail(request, pk):
post = get_object_or_404(Post, pk=pk)
md = markdown.Markdown(extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
# 记得在顶部引入 TocExtension 和 slugify
TocExtension(slugify=slugify),
])
post.body = md.convert(post.body)
m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
post.toc = m.group(1) if m is not None else ''
return render(request, 'blog/detail.html', context={'post': post})
和之前不同的是,extensions
中的 toc
拓展不再是字符串 markdown.extensions.toc
,而是 TocExtension
的实例。TocExtension
在实例化时其 slugify
参数可以接受一个函数,这个函数将被用于处理标题的锚点值。Markdown 内置的处理方法不能处理中文标题,所以我们使用了 django.utils.text
中的 slugify
方法,该方法可以很好地处理中文。
这时候标题的锚点 URL 变得好看多了。
欢迎关注 HelloGitHub 公众号,获取更多开源项目的资料和内容
第 10 篇:小细节 Markdown 文章自动生成目录,提升阅读体验的更多相关文章
- 懒人小工具:T4自动生成Model,Insert,Select,Delete以及导出Excel的方法
之前写了篇文章,懒人小工具:[自动生成Model,Insert,Select,Delete以及导出Excel的方法](http://www.jianshu.com/p/d5b11589174a),但是 ...
- 用jquery实现文章自动生成二级目录(续)
前文:用jquery实现文章自动生成二级目录. 使用方法的补充 我们可以把我们的js和css上传到博客园,然后在页面HTML代码中使用他们. 发现的一些问题 在我把我的js放到自己的博客园上运行之后发 ...
- Markdown自动生成目录
Markdown自动生成目录 使用npm语法生成 1.安装npm 2.安装doctoc插件 3.执行生成 参考 Markdown自动生成目录 使用npm语法生成 1.安装npm 我的系统是deepin ...
- 去除office自动生成目录后生成的小框框(内容控件,目录控件)
如何自动生成目录在这里就不进行阐述了,想必能看到这这里的人已经完成了目录的自动生成,那我就来直接演示如何去除自动生成目录后烦人的目录内容控件吧 直接上图片
- Word 2010文档自动生成目录和某页插入页码
一.Word 2010文档自动生成目录 关于Word文档自动生成目录一直是我身边同学们最为难的地方,尤其是毕业论文,经常因为目录问题,被要求修改,而且每次修改完正文后,目录的内容和页码可能都会发生变化 ...
- csdn自动生成目录索引、插入代码片快捷键
文章目录 自动生成目录索引 插入代码片 自动生成目录索引 文章开头加入 @[TOC](目录描述) 目录描述可不写 插入代码片 cmd/ctrl + shift + k
- Word自动生成目录
博主最近在写报告的时候要在Word里面做个目录,再做个页码,然后上网搜了一些方法,非常零散,我弄了好久才弄好.在这里我把整套方法分享一下. 声明:内容完全独创! 工具:Word 2016. 效果:如下 ...
- tp5自动生成目录
1.// 定义应用目录 define('APP_PATH', __DIR__ . '/../application/'); // 加载框架引导文件 require __DIR__ . '/../thi ...
- Thinkphp5.0实战开发二------自动生成目录结构
序言 ThinkPHP5.0 具备自动创建功能,可以用来自动生成需要的模块及目录结构和文件等,自动生成主要调用\think\Build 类库.ThinkPHP5.0中模块文件夹在application ...
随机推荐
- 项目中操作redis改brpop阻塞模式为订阅模式的实现-java实习笔记二
更改项目需求以及项目之前阻塞模式问题的叙述已经在上一篇说过了,详情可参考:https://www.cnblogs.com/darope/p/10276213.html ,https://yq.ali ...
- 使用R语言预测产品销量
使用R语言预测产品销量 通过不同的广告投入,预测产品的销量.因为响应变量销量是一个连续的值,所以这个问题是一个回归问题.数据集共有200个观测值,每一组观测值对应一种市场情况. 数据特征 TV:对于一 ...
- 洛谷 P1463、POI2002、HAOI2007 反素数
题意: 求最小的$x\in[1,N]$,使得$x$为$g(x)$最大的数 中最小的一个. 分析: 1.$x$不会有超过$10$个不同质因子.理由:$2 \times 3\times 5...\time ...
- TensorFlow笔记-文件读取
小数量数据读取 这些只用于可以完全加载到内存中的小型数据集: 1,储存在常数中 2,储存在变量中,初始化后,永远不改变它的值 使用常量 training_data = ... training_lab ...
- C#3.0新增功能09 LINQ 基础04 基本 LINQ 查询操作
连载目录 [已更新最新开发文章,点击查看详细] 本篇介绍 LINQ 查询表达式和一些在查询中执行的典型操作. 获取数据源 在 LINQ 查询中,第一步是指定数据源. 和大多数编程语言相同,在使用 ...
- 安科 OJ 1190 连接电脑 (并查集)
时间限制:1 s 空间限制:128 M 传送门:https://oj.ahstu.cc/JudgeOnline/problem.php?id=1190 题目描述 机房里有若干台电脑,其中有一些电脑已经 ...
- Codeforces1144B(B题)Parity Alternated Deletions
B. Parity Alternated Deletions Polycarp has an array aa consisting of nn integers. He wants to play ...
- ubuntu环境下测试cache大小并校验
Cache存储器:电脑中为高速缓冲存储器,是位于CPU和主存储器DRAM(Dynamic Random Access Memory)之间,规模较小,但速度很高的存储器,通常由SRAM(Static R ...
- MicroPython TPYBoard v201 简易家庭气象站的实现过程
转载请注明文章来源,更多教程可自助参考docs.tpyboard.com,QQ技术交流群:157816561,公众号:MicroPython玩家汇 前言 上一篇教程中我们实现了一个简单网页的显示.本篇 ...
- PHP验证身份证格式
互联网公司对身份证验证的需求越来越多,然而普通的小公司是无法对接公安部门的身份认证系统的.几乎都是在网上买一些大的互联网公司的一些认证服务.即使是便宜一些的认证价格也达到了10万次/万元.也就是一角钱 ...