经过最近几天的改造, 本博客正式加入了Slide模块和Notebook模块. 这两个模块已经添加到博客的头部菜单栏, 分别点击Slides和Notebook就可以进入相应的界面

头部菜单栏


点击Slides后就进入了Slides模块:

slide目录

目录也是以slide的模式组织的. 通过方向键就可以切换slide.


点击NoteBook后进入NoteBook模块:

NoteBook目录

这里的目录还是按照普通的博客文章的样式构成的. 点击相应的连接就可以进入相应的NoteBook.

技术分析

Slide采用Reveal-md技术构建, 而Reveal-md的本质是Reveal.js的Markdown版本, 因此Reveal-md具有几乎所有Reveal.js的特性.

Reveal-md支持动态预览和编译项目为HTML, 所以只需要将相应的Markdown文件编译为HTML文件, 在将这些文件放置到指定的文件夹中, 就可以从博客访问这些slide


NoteBook是指Jupyter NoteBook, Jupyter本身的功能已经非常完善, 并且提供了将NoteBook编译为HTML的指令. 所以只需要将相应的NoteBook编译为HTML, 并放置到指定的文件夹中, 就可以从博客访问这些NoteBook.

项目结构

最初的hexo的source目录结构如下

1
2
3
4
5
_drafts/
_posts/
about/
images/
others/

其中_drafts/存放文章草稿, _posts/存放文章源文件, about/存在博客头部菜单栏About选项用到的文件, images/存放博客中的图片, others/存放其他文件.

按照Hexo的规则, 所有以_开头的目录内的内容都不会直接出现在输出目录中, 而其他的目录中的文件, 即使无法被渲染(例如非文本文件)也会被复制到最后的输出目录中.

结合这一特性, 加入slide模块和notebook模块后目录结果如下

1
2
3
4
5
6
7
8
9
_drafts/
_notebook/
_posts/
_slides/
about/
images/
notebook/
others/
slides/

其中_notebook/_slides/存放源代码, 即.ipynb文件和.md文件. 对应地, notebook/slides/存放编译为HTML之后的文件. 经过hexo渲染以后, 访问博客的/slides/notebook路径就可以查看相应的文件.

博客页面设置

本博客采用了MiHo主题, 在相应的配置文件中, 有如下的一段内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Menu setting | 菜单设置
# name: Font Awesome icon | Font Awesome 图标
# title: Home Title | 标题
# url: //minhow.com Url, absolute or relative path | 链接, 绝对或相对路径
# target: true Whether to jump out | 是否跳出
menu:
archive:
title: Archives
url: /archives
target: false
user:
title: About
url: /about
target: false

因此只要在menu下加入两个新的项, 就可以在博客的头部菜单栏添加两个新的选项, 具体内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
menu:
archive:
title: Archives
url: /archives
target: false
tv:
title: Slides
url: /slides
target: false
sticky-note:
title: NoteBook
url: /notebook
target: false
user:
title: About
url: /about
target: false

在博客的页面上点击选项, 就能跳转到url指定的地址上, 只有在对应的位置加入index.html文件, 就完成了模块的添加.

Reveal-md新建

Reveal-md和hexo的博客文章一样, 需要使用一些头部的信息, 所以可以考虑创建一个文章模板, 使用hexo指令创建新的文章, 然后将创建的文件复制到Reveal-md的源文件目录中. 这样一方面不需要添加额外的文件新建机制, 又充分使用了hexo的特性.

首先在hexo的scaffolds目录下新建一个slide.md文件, 内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
title: {{ title }}
date: {{ date }}
theme: solarized
revealOptions:
transition: 'fade'
---

## Title

---

## Content

---

这里指定了主题和切换方式的默认值, 并且利用hexo的特性, 自动获得了文件名和创建时间.

每当需要创建新的slide时, 可以执行如下的指令

1
2
3
read FILENAME
hexo new slide $FILENAME
mv source/_posts/${FILENAME}.md source/_slides/${FILENAME}.md

使用hexo, 指定使用slide目标创建新文件, 然后将此文件移动到存放slide的目录. 当然这一段指令可以写成脚本, 下次使用的时候, 双击就能使用.

Reveal-md编译

Reveal-md编译脚本如下所示:

1
2
3
@echo off
cd hexo\source
reveal-md .\_slides\ --static slides --theme solarized & pause

所有的参数含义都可以在Reveal-md的官网上获得.这里要注意的是指令末尾的& pause. reveal-md命令执行完毕会自动结束进程, 进而导致后续的指令无法执行, 所以一定要使用&符号组合pause指令才能实现暂停效果.

Reveal-md实时预览

Reveal-md提供了一个实时预览功能, 本地做出的修改可以实时的展示到页面之上. 指令如下

1
2
3
@echo off
cd hexo\source
reveal-md .\_slides\ -w --theme solarized --listing-template _template\list.html

其中-w指令表示监控本地目录的变化. 但Reveal-md存在一个bug, 当文件名为中文是, 无法在实时预览模式下访问相应的文件. 联想到Hexo的草稿发布模式, 我额外编写了一个小脚本, 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import os
import sys
import json
import random

configPath = "../_config/currentSlideName.json"

def makeNewSlide(filename:str):
if os.path.exists(configPath):
info = readDictFromFile(configPath)
else:
info = {"currentName":[]}

if len(info["currentName"]) != 0:
n = len(info["currentName"])
os.system(f"mv current.md current_{n}.md")

info["currentName"].append(filename)
os.system(f"mv ../_posts/{filename}.md current.md")
writeDictToFile(configPath,info)


def readDictFromFile(filename):
with open(filename,"r",encoding="utf8") as f:
return json.loads(f.read())

def writeDictToFile(filename, data):
with open(filename,"w",encoding="utf8") as f:
f.write(json.dumps(data))


def publishSlide():
if os.path.exists(configPath):
info = readDictFromFile(configPath)
else:
raise EOFError("No File Need to be published")
if len( info["currentName"]) == 0:
raise EOFError("No File Need to be published")

filename = info["currentName"].pop()
os.system(f"mv current.md {filename}.md")
writeDictToFile(configPath,info)

# 如果有前面创建的文件, 则恢复前面的文件
if len(info["currentName"]) != 0:
n = len(info["currentName"])
os.system(f"mv current_{n}.md current.md")


if __name__ == "__main__":
if len(sys.argv) >= 2:
if sys.argv[1] == "new":
makeNewSlide(sys.argv[2])
elif sys.argv[1] == "publish":
publishSlide()

这个脚本实现了两个功能, 分别是新建和发布. 当创建一个新的slide时, 此脚本将slide的真实名称保存起来, 然后将slide重命名为current.md, 从而在实时预览模式下也可以正常查看. 当slide创建完毕以后, 执行发布指令, 此脚本恢复slide原本的文件名.

考虑到一个slide的编写过程中, 可能有创建新的slide的需求, 此脚本也支持连续多次执行新建和发布执行, 创建的文件按照栈结构保存..

NoteBook编译

NoteBook提供了编译的指令, 但是使用BAT执行时, 会产生原因不明的死循环, 且每次只能编译一个文件. 针对这一情况, 我编写了一个python文件来实现NoteBook相关的指令, 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import os
import sys


def makeHTML():
doNothing = True
for name in os.listdir():
if ".ipynb" in name and os.path.isfile(name):
core = name.split(".ipynb")[0]
if hasUpdated(core):
doNothing = False
cmd = "jupyter nbconvert --output-dir='../notebook' --to html " + name
os.system(cmd)
if hasUpdated("index",".md",".md"):
doNothing = False
print("[PyConvertApp] Update index.md")
os.system("cp index.md ../notebook/index.md")
if doNothing:
print("Everything up-to-date")



def hasUpdated(name, srcSuffix=".ipynb", dstSuffix=".html"):
src = name + srcSuffix
dst = "../notebook/" + name+dstSuffix
try:
ts = os.path.getmtime(src)
ds = os.path.getmtime(dst)
return ts > ds
except FileNotFoundError:
# 文件不存在时都重新生成文件
# 如果出现没有考虑到的异常, 则应该使程序崩溃
return True


def startJupyter():
os.system("jupyter notebook")


def genIndex():
# 为项目提供一个Index文件
# 自动生成或者手动维护
pass


if __name__ == "__main__":
if len(sys.argv) >= 2:
if sys.argv[1] == "start":
startJupyter()
elif sys.argv[1] == "build":
makeHTML()
else:
# 默认生成文件
makeHTML()

将此文件和NoteBook源文件放置在同一个目录下, 就可以实现启动Jupyter Notebook以及将所有的NoteBook编译为HTML的功能.

脚本中做了更新检测, 通过对比HTML和源文件之间的修改时间来确定是否需要重新编译源文件.

忽略编译结果

由于Hexo会自动渲染所有位于source下的文件, 因此经过Reveal-md编译后的html文件会被Hexo再次渲染, 进而导致slide样式出现问题.

Hexo的配置文件中提供了一个跳过渲染的选项, 按照如下的方式, 则可以忽略slides目录和others目录下的所有目录和文件, 以及notebook文件夹下所有的HTML文件.

1
2
3
4
skip_render:
- slides/**
- notebook/*.html
- others/**

创建index文件

使用Reveal-md编译一个文件夹时, 会自动为这个文件夹产生一个index.html文件, 不过这个文件只是简单的展示了全部的文件, 因此使用不够友好.

由于在slide源文件中创建的index.md文件也会编译成index.html文件, 所以只需要在源文件目录中创建一个index.md文件, 并且手动维护这个文件, 就可以为整个Slide系统提供一个目录.


在NoteBook系统中, 显然并不存在默认生成的index文件, 同时也不适合使用NoteBook格式创建一个目录. 所以还是可以创建index.md文件, 并且手动维护这个文件中的链接.

与Reveal-md不同的是, NoteBook系统并不自己编译这个md文件, 而是由hexo负责渲染, 所以在配置文件中只忽略了HTML文件, 而md文件还是照常渲染.

1
2
3
4
skip_render:
- slides/**
- notebook/*.html
- others/**

经过这样的设置以后, NoteBook的index页面就和普通的博客文章页面具有同样的页面结构了.

Notebook添加目录

安装NoteBook插件, 执行如下指令

1
2
pip install jupyter_contrib_nbextensions
jupyter contrib nbextension install --system

安装完成以后, 在Jupyter主界面上, 可以看到多了一个标签页, 名称为Nbextensions. 在其中开启插件Table of Contents(2)即可为NoteBook添加目录.

更多关于Jupyter插件的内容, 可以参考以下文章

Notebook修改字体和背景

1
jt -f fira -t onedork -fs 13 -cellw 90% -ofs 11 -dfs 11 -T -N

如果希望还原设置, 可以执行

1
jt -r

最后更新: 2024年03月28日 23:43

版权声明:本文为原创文章,转载请注明出处

原始链接: https://lizec.top/2019/08/03/%E5%8D%9A%E5%AE%A2%E5%8A%A0%E5%85%A5%E6%96%B0%E7%9A%84%E6%A8%A1%E5%9D%97/