!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
官方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
)。
发完邮件以后我笑倒在新买的粉红色团子抱枕上,心想我应该为这些文章单独开一个标签,不如浪漫一点地叫做“巴别塔之梯”——核心内容并非我的奇思妙想,我只是把大神们的话,用人类语言重写一遍。
谢谢!你讲得非常清楚哇,终于有人带了,挥泪挥泪挥泪(重要的泪挥三次)!
不过可惜我不太理解后端技术,看得一知半解……所以我想问要看懂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是个工具,本身是不带“目的”的,大家用起来实现什么什么功能的时候才有了“目的”……所以学某个具体的程序语言可能并不重要,理解广义上的程序语言是怎么跟计算机对话的比较重要……吧
另外你把字号改大了呀哈哈,看得很清楚!
对我来说这篇比官方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 ,好使
@林木木 噫,被木木大神翻牌了!~( ̄▽ ̄~)~