「在世间转一圈,能求证多少誓言。」 古川政良。腐女子。中国嗑学院津港分院风水八卦研究所延毕博士生。 重生 / 白夜追凶 / 刀锋上的救赎 / given / 花归葬 / IDOLiSH7 / 海猫鸣泣之时。

Bitcron自定义主题按年份归档

!Warning
本文介绍一种特殊方式的归档页Jade文档写作,用以实现在形如/archive/2017这样的链接上载入对应年份的文章归档列表。思路是Hepo大神提供的思路(二话不说抱住大腿),示范代码由Baco的《心情复杂的归档页面》中的代码稍加改动而来。让我们给散发着人性光辉的Bacoちゃん鼓掌~

总之我们开门见山,我先把archive.jade的代码放出来:

extends base

block title
  title= '归档:'+request.path.split('/')[-1]+'年 - ' + site.title

block content
  if request.path.strip('/')=='archive'
    +response.redirect('/all_articles')
    
  year_start_date = '%s-1-1'%request.path.split('/')[-1]
  year_end_date = '%s-1-1'%(request.path.split('/')[-1].int+1)
  #postlist
    year_posts = d.get_data(types='post', limit=8, sort='desc', date_start=year_start_date, date_end=year_end_date)
    +page(as_ul=False)
      for post in year_posts: .block
        a(href=post.url)= post.title
          sup= (post.visits or 0)

当然还有另外一种写法:

extends base

block title
  title= '归档:'+ request.get_offset_path(1) +'年 - ' + site.title

block content
  if request.path.strip('/')=='archive'
    +response.redirect('/all_articles')
    
  year_start_date = '%s-1-1'%request.get_offset_path(1)
  year_end_date = '%s-1-1'%(request.get_offset_path(1).int+1)
  #postlist
    year_posts = d.get_data(types='post', limit=8, sort='desc', date_start=year_start_date, date_end=year_end_date)
    +page(as_ul=False)
      for post in year_posts: .block
        a(href=post.url)= post.title
          sup= (post.visits or 0)

这两种写法的微妙差别我会在文中解析~

不要慌,其实实现这个归档只有一个知识点:Bitcron API中request.path的使用。

归档的逻辑

来看我们的需求:按照年份归档,并使形如/archive/2017的页面下只显示对应年份的文章。

所谓“只显示对应年份的文章”,中间对应的逻辑其实是:用d.get_data函数,从我们的全站文件里,把我要的那一年的文章拿出来,然后列出来——和“在一个页面内进行全站归档”不同的是,全站归档的时候我们把全站的文章都取出来了。

但是如果你的归档页面在一页里有按年份排列,其实你会发现逻辑是完全一样的。举个例子,Baco的原来的代码中有这样的语句:

year_posts = d.get_data(types='post', limit=8, sort='desc', date_start='2017-1-1', date_end='2018-1-1')

这段代码的意思是:从我的全站文章里,一次取出满足条件的八条列表——条件是:“发表时间从2017年1月1日起,至2018年1月1日截止”(date_start='2017-1-1', date_end='2018-1-1')的“文章”(types='post'),最后把文章按时间倒序排列(sort='desc')。

这是一条静止的、没有变量的代码,但是因为我们每年都要归档一次,所以势必这里会出现一个变量:归档的年份。

那么我们先把这段代码里会变化的部分用一个变量代替一下,成为:

year_posts = d.get_data(types='post', limit=8, sort='desc', date_start=year_start_date, date_end=year_end_date)

好,我们有了一个孤零零的变量,但是,我们怎么告诉计算机这个变量是什么呢?为了跟计算机沟通,就有了我们下面这个重点函数:request.path

request.path的使用

官方API文档在这里:Request & Response,可以边看边对照此文。

当然官方文档写得比较像个工具手册,注重这个模板API本身,不过作为自己DIY又没有什么代码基础的折腾用户,其实我们只要知道这些函数是干啥用的,然后依样画葫芦地改改就好了,改不动了没出效果就去发邮件骚扰Hepo嘛。(Hepo看到这段大概会暴打我吧。)

咳,总之,我们要理解一下request.path这个函数的作用:从访问的地址提取所需要的信息

所谓访问的地址……就是形如https://mrx.moe/archive/2017这样的地址,然后你可以想象request.path这个函数就像一把手术刀(?),可以沿着分隔符/把我们的地址切成好几段,然后从中提取一段。

嗯,你肯定注意到了,其实我们想告诉计算机的变量值跟我们的地址是一一对应的,比如/archive/2017下就是2017年的文章、/archive/2016对应2016年的文章。

所以我们只要用request.path把地址的最后一个/后面的2017或者2016或者其他任何年份取出来,然后当做一个值赋给我们的变量就好了。

我开头的代码中采用了两种不同的写法,现在可以开始解释他们都是什么意思了:

request.path.split('/')[-1]

request.path是函数名,split('/')表示以/为分隔符,[-1]表示“偏移值”——取这段地址的最后一截儿。

比如我访问:https://mrx.moe/archive/2017,那么以/为分隔符,取最后一截儿,所以取到的就是一串字符串2017,也就是我们想要的年份啦。

而这一段代码:

request.get_offset_path(1)

取到的虽然也是2017,但是取出来的方式不太一样,因为除了/以外,~~也是一个分割符号(当然我们这个地址没有,但是如果有的话,要在这里断一断),所以这一段代码的意思是:以~~/作为分割,不计域名,在砍掉分页信息以后,再砍掉正着数一段。

我们这个域名没有分页信息,所以不用砍,如果有形如/page/2的分页信息的话,先砍掉分页,然后砍掉/archive,也就剩下了2017。数数的顺序和上面的代码不太一样。

假设我们换一个访问地址:https://mrx.moe/archive/2017/11,这个时候两个代码分别返回了什么呢?

request.path.split('/')[-1]

返回的是11,倒着数第一段嘛。

request.path.split('/')[-2]

返回的是2017,倒着数的第二段。

request.get_offset_path(1)

返回的是2017/11,砍掉https://mrx.moe/archive以后剩下的。但是:

request.get_offset_path(2)

返回的就是11了,是连砍两段,砍掉了/archive/2017

所以我比较建议写归档的时候用request.path.split('/')[-1]来取数,因为不会乱,但是其实如果你不打算丧心病狂到按月份归档(低产写手表示这种归档也太可怕了),其实也没有差别……

现在我们取到年份了,只要把他们赋给我们的变量就OK了:

year_start_date = '%s-1-1'%request.path.split('/')[-1]
year_end_date = '%s-1-1'%(request.path.split('/')[-1].int+1)

其中第二行我们注意到了有个.int+1,这啥意思呢……

是这样的,我们从地址里切下来的2017,是一个字符串,跟hello world一个类型的,而字符串是不能进行运算的——你见过hello world + 1 = ??的嘛!对不对!所以我们用.int把它改成一个整数值——整数就有加减乘除了,然后我们让它+1(没有s,没有,想多了的抓出去续),就得到了下一年的年份。

最后一点收尾处理

我们能在archive.jade里这么写,主要是因为archive.jade这个文件会渲染一切以/archive开头的页面。

啊,但是,我们却并不希望/archive页面被这个文件渲染,我们只要它渲染/archive/2017/archive/2016等等等等。

因为在/archive里我们取出来的是字符串archive而不是一个年份……显然是取不出文章的!

所以我们加一段代码:

if request.path.strip('/')=='archive'
  +response.redirect('/all_articles')

意思是:如果访问/archive的话,重定向至/all_articles页面——当然这个名字我随便取的,请大家写的时候务必想一个正式一点的名字……~~其实如果是Baco的主题「淡泊」的话,直接重定向至/?status=loaded就可以了吧……~~

然后,我们的all_articles.jade就可以写一个年份索引了:

extends index

block title
    title= '归档 - ' + site.title

block content
    #postlist
        #archlist
            oldest_post = d.get_data(types='post', return_one=true, sort='asc') //return_one为True时,返回单篇文章(强制limit=1),如果不是的话返回的是一个列表,就没有date属性了
            latest_post = d.get_data(types='post', return_one=true, sort='desc')
            year_oldest = oldest_post.date.year
            year_latest = latest_post.date.year
            for year in range(year_latest.int, year_oldest.int-1, -1) //for循环倒序执行,效果是倒序输出年份
                year_start_date = '%s-1-1' %year
                year_end_date = '%s-1-1' %(year.int+1)
                yearly_count = d.get_data(types='post', return_count=true, date_start=year_start_date, date_end=year_end_date)
                if yearly_count != 0
                    .block
                        a(href='/archive/{{year}}')= year + '年'
                            sup= yearly_count

这部分,最关键的是保证跳转链接是/archive/{{year}}。但其实我和Baco在这里倒腾了挺久(……主要是我第一不熟悉get_data函数,第二对在html里写for循环确实太陌生了,面壁检讨……)

更具体的讨论细节在《归档页面只取年份及该年内文章计数》下,大家主要看Hepo的回答就好,看不懂的就……随它去吧,能用就行(什么)。

番外篇:网页渲染的实质

Baco在评论里问:

看了第二遍,关于年份这个变量的获取我明白了,就是不懂为什么这么写可以得到对 archive/年份 的渲染呢?

我一开始下意识以为她问的是Bitcron的模板渲染机制,于是直接回答说:

因为在访问/archive/xxxx的时候,bitcron匹配的jade就是archive.jade,bitcron应该是从末尾开始逐级往上尝试匹配jade的
 
比如访问/archive/2017/11,它会先找有没有11.jade,没有,找2017.jade,再没有,找archive.jade,还没有,报404,就这么一级一级往上找

但是似乎不是Baco要的回答。

今天下了热统去饭堂的时候我突然后知后觉、福至心灵地意会到了“为什么这么写可以得到对 archive/年份 的渲染”到底问的是什么。

在我意会到了以后我第一个反应是:这个问题Hepo肯定理解不了到底是问的什么,大豹笑o(*≧▽≦)ツ。

其实问题在于“渲染”这个词,这个词是一个主动态的词,造成了一种语言上的误解:不知道网页到底是怎么解析的同志们第一次接触到这个词的时候,脑内的图景是一个jade文件主动去……或者覆盖也好,或者说渲染也好,反正就是会理解成“jade文件主动对网页进行了某种神秘的操作”。

但其实这个图景是不对的。为什么我突然理解了呢,因为我突然意识到这跟我专业的同志们“脑内是数学图景的黑洞,研究的是物理图景的黑洞”的状态其实差不多;为什么我觉得Hepo大概get不到这个问题呢,因为在他们的世界里这个过程就是个天经地义的公理,是不需要解释,默认大家都知道的……

下面我把这个真实的“渲染”过程用人类语言描述一遍,也许有助于从零开始倒腾Bitcron的朋友们理解“渲染”的实质到底是什么。

我们访问/archive/2017

网页开始工作,尝试在网站文件中匹配2017.jade,没有,尝试匹配archive.jade,有,开始编译,逐行执行。

执行:extends index,载入index.jade

然后开始编译index.jade,逐行执行:加入html标签啦、加入head标签啦,一行一行往下执行。

直到执行到block content这一行的时候,index.jade里是没有定义block content的,所以这一部分返回上一级文件——也就是archive.jade,因为index.jade是被archive.jade调用的——寻找定义。

找到archive.jade中的block content定义,开始执行block content内容:

if request.path.strip('/')=='archive':判断路径是不是/archive,不是,不执行

(加id和class的部分我就都不讲了,这个……就那么回事儿对吧2333)

执行:year_start_date = '%s-1-1'%request.path.split('/')[-1]:提取访问地址的最后一截“2017”

好,关键就在这里了,这就是“为什么这么写可以得到对archive/年份的关键——是个顺序问题,是我们先访问了一个地址/archive/2017,所以计算机往下执行的时候才能从这里提取到“2017”,如果我们访问的是/archive/2016的话,计算机按照相同的流程往下执行,取得的就是2016了。

换句话说,jade(本质是html)文件,本质上是一个“告诉浏览器(计算机)按照什么套路提取与输出信息的操作指南”,所以,是先有了地址,浏览器才能按照地址来一步步解析应该拿出什么东西,展示什么东西。

这么想的话其实“渲染”这个词的主被动挺容易给人造成误解的,并不是Jade文件“主动”去渲染页面,而是反过来,页面“主动”按照设定好的规则去调用了Jade文件(当然执行Jade文件的时候还会调用其他的文件,比如style.scss)。

发完邮件以后我笑倒在新买的粉红色团子抱枕上,心想我应该为这些文章单独开一个标签,不如浪漫一点地叫做“巴别塔之梯”——核心内容并非我的奇思妙想,我只是把大神们的话,用人类语言重写一遍。

End.
Comments
Write a Comment
  • 谢谢!你讲得非常清楚哇,终于有人带了,挥泪挥泪挥泪(重要的泪挥三次)!

    不过可惜我不太理解后端技术,看得一知半解……所以我想问要看懂Bitcron的API是不是得学Python啊!然后你出现以后我才知道为什么我看不懂官方API了,因为它只写了用法,没写定义,所以那些代码们都是为什么而存在,除了一些名字看起来就能理解的,其他完全摸不着头脑。

    大神受我一跪!散发着人性的光辉!在我心中身影伟岸了起来!

    最后一个奢侈的小问题,/all_articles页面不是有limit=9999吗,据我所知最多只能取1000篇,那么如果文章超过1000篇,按照年份分组是不是就不完整了?1000篇以前的文章就取不出来了该怎么办呢?

    再次感谢!

    • @水八口 Bitcron看起来好像,并不是,用的Python,吧……【我也不知道【喂

      其实API结构写得还是很清楚的,就,变量空间、变量、函数,你可以脑补一个图书馆,变量空间是书架、变量是索引号、函数是检索目录,然后我们通过一些参数(根据书的特征)来操作函数,最后找出对应的书……就是这么个过程

      我还没试过文章超过1000篇的情况……不过我在想是不是可以不遍历全部文章,毕竟/all_articles只需要年份对吧,所以我们其实只要查询最早的一篇文章的年份,然后让它一直+1、输出超链接、+1、输出超链接……一直到现在的年份就可以了,我是这么想的,但不知道能不能实现(……)毕竟我不知道HTML有没有while循环!这个还是得问Hepo

    • @水八口 我大概知道怎么写了……

      反正咱只要年份的对吧……

      那就在/all_articles页面里这么写

      script.

      var y = parseInt(start_year);

      for y <= parseInt(site.now.year):

      document.write(y.link('/archive/y'))

      y = y + 1

      ……差不多就这个逻辑,这样的话我们只要访问最早的一篇文章就可以了,性能好像也提升了诶(「・ω・)「

      现在只要想办法get到最早的一篇文章,并拿到start_year就OK了

      • @矩阵良 嗯,我也是看了那么多遍硬是大概理解了这个最基础的关系,我需要语文好的程序员哈哈哈~总之现在我稍微有点理解后端逻辑了,但是不会用,想法和实现之间隔着一条鸿沟,不知道应该学什么好。之前问过Hepo,他只说Python比Bitcron API难多了,也没告诉我要理解要能使用改学什么,估计是混合的吧。

        我记得模板语言是Jade和Jinja的融合,Jinja好像是基于Python的啊。Jade我也没完全搞懂,看来我这样浑水摸鱼混不久,还是得老老实实学习,不然很难有深入的进步。

        • @水八口 嗯,我觉得看懂API最重要的是……学好语文!(弥天大雾)

          其实是这样的,API是个工具手册,因为Hepo也不知道大家都会有什么奇葩的需求(比如我(你好意思)),所以API就只能把一个函数能做到的所有的事情和写法都列出来,给大家排列组合按需取用,API是个工具,本身是不带“目的”的,大家用起来实现什么什么功能的时候才有了“目的”……所以学某个具体的程序语言可能并不重要,理解广义上的程序语言是怎么跟计算机对话的比较重要……吧

  • 另外你把字号改大了呀哈哈,看得很清楚!

  • 小F reply

    对我来说这篇比官方api好理解多了,毕竟外行人……

    • @小F 对吧对吧对吧!还好我不是一个人!抱住颤抖的自己!

    • @小F Hepo会很难过的!2333333

      • @矩阵良 这个没办法,毕竟我属于门外汉,没有基础看不懂就是看不懂……所以我已经放弃写bitcron主题了。

  • @矩阵良 比如吧,Hepo跟我说可以用request.path.split('/')[-1],但这一串是什么意思我搞不懂,当然就没法用啦。但你这篇里就会解释split是分割的意思,那我就理解多了。可是,即使理解意思,要怎么用还是不懂……总之应该也像你说的,一不了解程序语言如何跟计算机对话,二看不懂程序语言本身的含义。这些可能都是大家在学习某种程序语言时潜移默化吸收的,同时思考方式也会被改变。总之先这样混着哈哈哈哈(说好的追求呢)

    • @水八口 先混着吧,搞不好学了程序以后会像我一样,受到了理工科诅咒,审美开始疯狂掉线哦,那就完蛋了(什么)233333

  • @矩阵良 咱俩分工合作多好,男女搭配干活不累(什么)其实我也经常被说是男的,因为博客里太多代码了……不过遇到了会后端的女生,我还是自愧不如哈哈哈哈

    • @水八口 API应该还不算后端,后端数据库什么的响应逻辑什么的我也两眼一抹黑,就是因为不喜欢折腾后端我才跑路来了bitcron哈哈哈哈,bitcronAPI应该只能说,给了很多端口让我们从后端的黑匣子里拿东西23333

      • @矩阵良 是的,如果不是这样,我也玩不转Bitcron呀。不过也算给我点亮了一棵技能树了!

  • 看了第二遍,关于年份这个变量的获取我明白了,就是不懂为什么这么写可以得到对 archive/年份 的渲染呢?

    • @水八口 因为在访问/archive/xxxx的时候,bitcron匹配的jade就是archive.jade,bitcron应该是从末尾开始逐级往上尝试匹配jade的

      比如访问/archive/2017/11,它会先找有没有11.jade,没有,找2017.jade,再没有,找archive.jade,还没有,报404,就这么一级一级往上找

  • @矩阵良 嗯,我也不知道是我没法描述我的问题,还是没明白你的回答……先这样吧。

    另外我又看了一下,Bitcron的模板语言是Jade,然后会转化为Jinja,这两个鬼东西都是Python……

  • 大大的收货~ .split ,好使

    • @林木木 噫,被木木大神翻牌了!~( ̄▽ ̄~)~