时间线模板的最终效果
注:此wiki站使用的版权协议为CC-BY-SA 4.0,相关代码版权以源站为准。
缘起 从参与这个粉丝wiki建设开始,我就想到利用时间线的形式来优化背景信息类条目的叙述。然而我却意外地发现时间线这种东西并没有好用的现成模板,从萌百搬过来的那个是纯文字,其他wiki站上要么没遇见过好用的,要么就是找不到源码。最近又有一位粉丝提出了通过时间线整理漫画剧情的方案,恰好我的那个杀戮尖塔mod项目又处于停顿状态,再加上我在这个wiki站最近的活跃度也不太够,于是就想着自己写一个能用的时间线模板出来。
经过 想要写出一个返回非维基文本的模板,基本都要借助模块。而这样的模板一般都有三部分组成:规定逻辑的模块,引用逻辑的模板和规定样式的样式表。
而这三者分别需要lua、mediawiki和三件套的相关知识。
这里就分别说明这几项。
三件套 在撰写模块之前,我必须先在一个html中先写出类似的效果。而这里并不需要js(逻辑部分是lua那边的事),只需要把静态效果弄出来就可以了。
本以为这应该是一件不难的事(因为之前有过三件套的开发经验),但实际上手起来才发现跟一开始想的不是一回事。
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     <body >          <div  class ="timeline" >              <div  class ="trunk" > </div >              <div  class ="branch"  id ="branch-1" >                  <div  class ="line" >                      <span  class ="id" > 123话</span >                  </div >                  <div  class ="text" >                      仑负着份韩慧十同仇种希,统说韦的名到是冷人,人但说备不书仆可联妄娘,重揽大头不我重不揽的和们么,而话有一你起的狂上小所事措地,统说韦的名到是冷人                 </div >              </div >              <div  class ="branch"  id ="branch-2" >                  <div  class ="line" >                      <span  class ="id" > 123话</span >                  </div >                  <div  class ="text" >                      仄师用叹拆即,统说韦的名到是冷人                 </div >              </div >              <div  class ="branch"  id ="branch-3" >                  <div  class ="line" >                      <span  class ="id" > 123话</span >                  </div >                  <div  class ="text" >                      对种榜让畴不今仃,雷爱所的可尘位哉国畴蒲火觉才不由少,战生轻韦如没里人谓第人知,太不投吴畴畴得牛我联求卑方不,行不的争死未宋无舟范他非三同,策花德攻水我洪,别夹太与今事派上交非范价,郭法出馆,兴登后洋时一的两会,不有正十之谓绪药定攻音啦死脱降办仍作锐,是。                 </div >              </div >              <div  class ="branch"  id ="branch-4" >                  <div  class ="line" >                      <span  class ="id" > 123话</span >                  </div >                  <div  class ="text" >                      严陀娟自足仄马土是见场他才不会变,办升他德仑而曰动与魂留足在说,娇老害判愚王同上留有帮洋知家学什少了亲,人宫己是,公那尝被谢龄中是放一子二白不,白恨洪狂竟皇县,衣降王司保间判上评有在未商留价后归,活马令国韩耳持一绝光反同同一家,却发要即官文来问德姑绝褒于。                     叹和到我国预,皇老耳卧传为,畴生至国人畴忧派的与程张己同选承语,易羊单房自不孔有厅愚饮,位人才楚皇罚谋皇学的我拾范使皇,躲狂游魂的事兴不即为但肯请,君为同当夫,太谷人尹是他感策付目我始你人榜,井老山不普勉处光为疾惊,在帝方孔他,感少为,帝蒲极化乐,投他人。                     入未有洞有后夹,衣小价偶以的是吞老藏中老他,太所张登挟夫生逃心书,回有谢勉乡乡台反薪时婵身会想,间也恼而承,单今时姑冷幕韩,罪德太读,死人未谓,爱非四朗蒲文韩法斯答方负不,不上得的彷感资同书畴洪房,宋仍未欲准中马她第里为同郭太胜,好人便将普曾,日了上,我。                 </div >              </div >              <div  class ="emptycell" > </div >          </div >      </body >  </html > 
 
html本体也就那么点事,主要的还是css。
在在布局问题上卡了很久之后,最终采用的布局架构是grid嵌套flex。
1 2 3 4 5 6 7 div .timeline  {		display : grid; 		grid-template-columns : 1 fr 1 fr; 		grid-auto-rows : max-content; 		gap : 5px ; 		position : relative; 	} 
 
总体的框架。这里将整个空闲区域分为两列,每一行的宽度取决于其中的最大宽度(但实际上似乎用处不大),每个格子间隔5px。
1 2 3 4 5 6 7 8 9 10 div .trunk  {		content : '' ; 		position : absolute; 		top : 0 ; 		bottom : 0 ; 		left : 50% ; 		width : 5px ; 		transform : translateX (-50% ); 		background-color : black; } 
 
整个时间线的主干。实际上就是一条黑线,从grid网格的顶部延申到底部。宽设置为5px,transform用于将这条线移到中间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 div .branch  {		position : relative; 		display : flex; 		justify-content : center; 		justify-items: end; 		align-items : center; } div .branch :nth-child (even) {		grid-column-start : -3 ; 		grid-column-end : -2 ; 		justify-content : end; 		flex-direction : row-reverse; } div .branch :nth-child (odd) {		top : 50px ; 		grid-column-start : -2 ; 		grid-column-end : -1 ; 		justify-content : start; } 
 
每个分支对应的区域。这里在分支区域使用了弹性盒布局以方便文字与框——线排布。
偶数位的分支占据右边一列,奇数位的占据左边一列。而弹性盒布局也能利用flex-direction轻松地指定元素左右翻转。同时也为奇偶数位指定分布开始的基准线(justify-content)。
1 2 3 4 5 6 div .line  {		position : relative; 		background-color : black; 		height : 2px ; 		min-width : 100px ; } 
 
每个分支中的指示线。没什么好说的。
1 2 3 4 5 6 7 8 div .text  {		position : relative; 		background-color : transparent; 		border : 2px  solid black; 		border-radius : 5px ; 		max-width : 500px ; 		padding : 5% ; } 
 
实际的文本框。实际运用时背景被设为了白色。
1 2 3 4 5 6 span .id  {		position : relative; 		margin-left : 10% ; 		margin-top : 2px ; 		font-family : 'Gill Sans' , 'Gill Sans MT' , Calibri, 'Trebuchet MS' , sans-serif; } 
 
指示线上的文本。这里的span是line的子元素,方便对齐。
1 2 3 4 div .emptycell  {		max-height : 5em ; 		min-height : 3em ; } 
 
占位符。为什么会有这东西?
在实际使用中,弹性盒并不会老老实实地待在网格之中,它对文本的自适应会使得其本身脱离网格范围。
这在单一的网页中并不会导致任何问题,但在实际使用中却出现了严重的出框现象。而这种出框就是因为网格并没有完全包含其中内容。
在许多次尝试之后,最终的解决方案是,在整个结构的最后加入一个空的占位符,这样就能规避出框现象。
在底部加入一个占位符后的效果,可以看出下面多了一行。
至此,网页测试部分基本完成。
模块/模板 这里主要参考的有:mbox模版,navbox模板,以及mw的官方文档 。
mediawiki的模块是利用lua编写的,而lua也是一项轻量的脚本语言。
而模块——模板方面真正出问题的点并不在lua本身,而是模块——模板的传参问题上。
模板本身的实现非常简单,只需要invoke对应的模块并引用对应样式即可。
1 <includeonly>{{#invoke: Flow-timeline | timeline}}</includeonly><templatestyles src="T:Flow-timeline/style.css" /> 
 
如何将模板的参数传到模块中,然后让模块返回内容呢?
首先是返回。
这个模板引用的实际意义是,调用模块Flow-timeline中的timeline函数。
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 56 57 58 59 60 local  data = {}local  framework={}local  function  getId (args)     for  key, value in  pairs (args) do          local  prefix = string .match (key, "(%a+)%d+" )         local  index = tonumber (string .match (key, "%d" ))         if  index and  not  data[index] then              data[index] = {}         end          if  prefix == "text"  then              data[index][2 ] = value         elseif  prefix == "id"  then              data[index][1 ] = value         end      end  end function  framework.timeline (frame)     local  args = frame:getParent().args     getId(args)     local  timeline = mw.html.create ("div" )     timeline:addClass("timeline-framework" )     local  trunk = mw.html.create ("div" )     trunk         :addClass("timeline-trunk" )     timeline:node(trunk)     for  index, value in  ipairs (data) do          local  branch = mw.html.create ("div" )         branch             :addClass("timeline-branch" )             :attr("id" ,"timeline-branch-"  .. index)         timeline:node(branch)         local  line = mw.html.create ("div" )         line             :addClass("timeline-line" )             :attr("id" ,"timeline-line-"  .. index)         branch:node(line)         if  value[1 ] then              local  id = mw.html.create ("span" )             id                 :addClass("timeline-id" )                 :attr("id" ,"timeline-id-"  .. index)                 :wikitext(value[1 ])             line:node(id)         end          if  value[2 ] then              local  text = mw.html.create ("div" )             text                 :addClass("timeline-text" )                 :attr("id" ,"timeline-text-"  .. index)                 :wikitext(value[2 ])             branch:node(text)         end      end      local  empty=mw.html.create ("div" )     empty:addClass("timeline-emptycell" )   	timeline:node(empty)     return  tostring (timeline) end return  framework
 
在mw的模块中,整体返回的值应该是一个表(return framework),而调用的函数应该是这个表的表函数。
在这个模块中,模板调用的便是表函数timeline。而整个模块实际上整体返回的值便是这个函数返回的值。
在这里,调用函数中使用mw自带的mw.html模块创建html内容,然后整体转为字符串返回就可以了。
那这个函数如何获取模板传入的参数呢?
这个函数的参数只有一个frame,而这个东西实际上就是模板传入的整体信息。而frame.args便是传入的参数表。
然而,这里却有一个很坑的点:
出于性能考虑,frame.args 是一个元数据表,而不是一个真正的参数表。参数值是按需从 MediaWiki 请求的。这意味着大多数其他表格方法将无法正常工作,包括 #frame.args 、 next( frame.args ) 以及 Table 库中的函数。——官方文档
 
这里如果尝试对args进行遍历,似乎是遍历不出来东西的。。。而这里显然并不需要考虑性能问题(因为所有的参数都迟早会被调用),所以这里使用frame:getParent().args来得到一个真正的参数表,这样就可以正常地进行遍历了。
结果 最终这个模板已经在这个wiki站投入使用,虽然还有一些bug(如移动端可能的显示问题),但至少能用了,效果如置顶图所示。
最终感想:css创始人,你睡了吗?我睡不着