文章目录
- 0.思路引导
- 1.在文中插入目录
- 2.在页面的任何地方插入目录
- 3.美化标题的锚点 URL
0.思路引导
Markdown 在解析内容的同时还可以自动提取整个内容的目录结构,本文内容将从以下几个方面展开:
1)在文中插入目录;
2)在页面的任何地方插入目录;
3)美化标题的锚点 URL。
1.在文中插入目录
博客的 Post(文章)模型,其中 body 是我们存储 Markdown 文本的字段:
文件位置:blog/models.py
from django.db import modelsclass 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] 标记即可。
后台输入如下:
效果展示如下:
2.在页面的任何地方插入目录
上述方式的一个局限性就是只能通过 [TOC] 标记在文章内容中插入目录。如果想在页面的其它地方,比如侧边栏插入一个目录该怎么做呢?只需要稍微改动一下解析 Markdown 文本内容的方式即可。
以detail.py中的模板标签{% block toc %}为例,其渲染的位置位于详情页的侧边栏,现在想要在侧边栏中生成目录。
{% block toc %}
...
{% endblock toc %}
需要修改的代码如下:
文件位置: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)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})
文件位置:detail.html
{% 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 %}
在这里,我们没有直接用 markdown.markdown() 方法来渲染 post.body 中的内容,而是先实例化了一个 markdown.Markdown 对象 md,和 markdown.markdown() 方法一样,也传入了 extensions 参数。
接着我们便使用该实例的 convert 方法将 post.body 中的 Markdown 文本解析成 HTML 文本。而一旦调用该方法后,实例 md 就会多出一个 toc 属性,这个属性的值就是内容的目录,我们把 md.toc 的值通过整个表达式的处理,赋给 post.toc 。
注意:为了防止文章目录为空的情况,使用正则表达式来测试 ul 标签中是否包裹有元素,从而来确定是否存在目录。并使用新的模板标签 {% if %},来对post.toc做条件判断。
效果展示如下:
3.美化标题的锚点 URL
文章内容的标题被设置了锚点,点击目录中的某个标题,页面就会跳到该文章内容中标题所在的位置,这时候浏览器的 URL 显示的值可能不太美观,比如像下面的样子:
#1_1 就是锚点,Markdown 在设置锚点时利用的是标题的值,由于通常我们的标题都是中文,Markdown 没法处理,所以它就忽略的标题的值,而是简单地在后面加了个1 _1 这样的锚点值。为了解决这一个问题,需要修改一下传给 extentions 的参数,其具体做法如下:
文件位置:blog/views.py
from django.utils.text import slugify
from markdown.extensions.toc import TocExtensiondef detail(request, pk):post = get_object_or_404(Post, pk=pk)md = markdown.Markdown(extensions=['markdown.extensions.extra','markdown.extensions.codehilite',# 记得在顶部引入 TocExtension 和 slugifyTocExtension(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 方法,该方法可以很好地处理中文。
效果展示如下: