跳到主要内容

博客开发之导航设计方案

· 阅读需 10 分钟

背景

基础

我使用过多款博客开发框架,Django | Vuepress | Nuxt | Gatsby等等,在这里暂时不谈DjangoGatsby,就我目前深入使用的VuepressNuxt(主要是Nuxt-Content中的Content-Theme-Docs主题)体验来说,导航的设计方案真地很有必要仔细考虑。

此处的导航,默认包括导航栏+侧边栏,甚至路由

类似的博客站点大多是默认基于文件路径生成路由,例如/zh/home.md文件一般会生成/zh/home的路由。此外,也会提供默认语言的设定,因此/zh/home最后会变成/home,两者等价。在此基础之上,有些框架还会再加入默认index file的设定,以vuepress为例,如果这个文件是index.md的话,则最后就等价于/,即主页。

以上是博客框架生成导航栏、侧边栏,显示文章标题等的一些支撑性概念,只有在理解这个的基础之上,才能设计出自己满意的路由与导航、侧边栏展示。

多语言

尽管现在的浏览器,都能很智能地解析中文网址,但我们必须审慎对待这样的一种“特殊待遇”。就拿我曾经使用的vuepress框架来说,我曾经为了里面的中文路径而苦苦 debug 一整天,后来发现是框架的作者对于路由的处理不够稳健,从而有中文路径不匹配的 bug,这是十分令人恼火的。

此外,不同的浏览器,以及不同的版本,对于非英文网址的转义设定总会有一些区别,在这里,大家都是有能力去处理,但并没有统一的约定去强制开发者遵循,所以中文路径往往并不是第一选择。但对于国人来说,好的中文路径依旧有其诱人的吸引力。以我的实际项目为例,考虑两个路由:/博客/考研专辑/blog/postgraduation-exam-album。也许有翻译不够优雅的问题,但从目标用户角度出发,这个中文路径就十分地友好。

基于此,我们试想一下,如果我们要设计一套多语言系统来适配我们的文章,该怎么做?按照惯例,我们首先有一批中文文档,并准备逐步更新成英文文档。

首先是中英文文件系统的一一映射必须要做,否则当我访问/博客/文章A时,切换成英文,如果对应的路由/blog/arcticel_A不存在,则不能满足我们的需求。但值得注意的是,这里的映射,已经不单单是文件系统的映射,还是路由的映射。

我们试想另一种解决方案——纯英文路径。当我访问/blog/arcticel_A时,切换成英文,对应路由变成/en/blog/arcticel_A,这种设计也是十分友好的。在这里,我必须指出,大多数人并不在乎网址是中文还是英文,他们更在乎页面的内容是中文还是英文。此外,中文的路径大多数人并不习惯,由此看起来甚至还有点点不专业。

对比下来,我个人觉得第二种解决方案操作起来比较友善,那就是一套文件地址,更换其根目录文件夹名,从而适配多语言系统,这听起来十分 make sense。

解决方案

基于多语言角度的考虑,我最终决定:文件系统的文件夹命名全部英文,方便网址的国际化。(在这里,文件名可以是中文,因为我一般使用typora写作文档,并且无须提前命名文件,而是直接写完一级标题然后保存就可以自动命名了。这样的话,文件名就是文章的标题名,并且往往是中文优先的。)

这样,假设我们的默认工作目录是/zh/articles/,我们新建了一个文档为home.md,则中文路由为/articles/home。我们"同步复制"该文档到英文目下,得到英文路由为/en/articles/home。这里的“同步复制”打引号是因为,复制的操作实在还是太麻烦了,因此在设计解决方案的时候,应该要做一些程序化的处理。

路径的问题解决了,(即以英文优先,默认使用文件路径,不同语言子路径相同),接下来就是导航栏的多语言设计了。

由于我们的文件路径用于多语言的匹配,与路由的生成,因此我们需要使用其他的方式生成导航栏的多语言

终极解决方案

  • 设置文档根目录,根目录下存放多个语言目录,以及可以预设一个settings.js|jaon|yaml的配置文件。

  • 指定默认语言,例如中文,即zh,该语言目录下的文档,最好是其他语言文档的超集。

  • 遍历文档目录下的文件夹与.md文件,生成文档树,得到以下字段:relativePath, dirName, fileName, mdTitle, mdID, mdDate, mdTags, mdDescription, mdAuthor 。其中:

    • relativePath, dirName, fileName均是文档系统默认的
    • mdTitle首先从文档的一级标题中获取,如若获取不到,再从frontmatter中获取,最后等于fileName
    • mdID首先从文档的frontmatter中获取,获取不到则等于relativePathhash
    • mdDate首先从文档的frontmatter中获取,获取不到再尝试从fileName中解析出日期,fallback 为空
    • mdTags首先从文档的frontmatter中获取,获取不到则等于relativePath上的所有文件夹名集合
    • mdDescription首先从文档的frontmatter中获取,获取不到则等于文档中开头的 N 个字,可设置是否包含一级标题
    • mdAuthor首选从文档的frontmatter中获取,获取不到则等于默认的作者名,比如南川,作者名也要配置多语言,在settings中。
  • 以上,可以得出,文档的frontmatter中最好包含以下字段:

    • id
    • title
    • date
    • tags
    • description
    • author

方案分析

对于 字典树的方案,并不太靠谱,我当时试想着

递归方案

分析输入

输入是相对路径,通过默认的根路径与相对路径拼接得到绝对路径,通过读取绝对路径下的子文件夹得到进一步的信息

分析输出

输出有两种,字典或者列表。

因为我们对输入的操作,涉及到了子文件夹的遍历,因此容易得到一个列表,所以用列表输出是可行的一种方案。

即:

输入:相对路径,得到 path 字段

处理:基于 path 字段得到一些基本信息

遍历:

输出的衔接

如果输出是一个列表,

{

path: input,

basic: handle(input),

children: get_children(input)

}

如果希望输入是一个空字典,最终返回一个字典,则函数的输出需要是一个字典。

如果希望输入是一个空字典,最终返回一个列表,然后进行拼接构造

登录bg-white
text-teal-700
bg-gray-900
text-teal-300
未登录bg-white
text-gray-500
bg-gray-900
text-gray-500