--- title: 【種樹】顯示文章最後修改時間 date: 2020-09-07 00:07 is_modified: false disqus: cynthiahackmd categories: - "程式設計 › 程式語言與架構" tags: - "程式設計 › 程式語言與架構" - "技能樹栽種" - "Github Page" - "Jekyll-NextT" - "Jekyll" - "Liquid" - "前端" - "Published" --- {%hackmd @CynthiaChuang/Github-Page-Theme %} <br> 那天幫文章做了點更新,卻想到文章沒辦法顯示最後修改時間,因此來研究一下該如何處理。 這整個計畫嚴格來說在最後一步失敗了,但還是記錄下...畢竟我都搞這久了,不~~騙一篇網誌~~留下一點記錄,我實在不甘心 QAQ(附上[傳送門](#首頁)給想先看看失敗地方的人)。但也不算完全失敗,最後用了點 tricy 的作法,也算是符合要求? <!--more--> ## 顯示修改時間 好了,就按部就班、一步一步來吧,先來試試如何顯示修改時間。 雖然在 [Jekyll-NextT 的使用文件](http://theme-next.simpleyyt.com/)中,沒有看到關於顯示最後修改時間的說明,不過我在程式碼中發現了些端倪。 1. 在 `_config.yml` 的 `post_meta` 中有個 `updated_at` 字段,將這個參數打開(即設為 `true`)。 ```yaml= # Post meta display settings post_meta: item_text: true created_at: true updated_at: true categories: true ``` <br> 2. 並在每篇文章的 yaml 區域中加上 `updated` 與時間: ``` --- title: 文章標題 date: 2013-12-24 updated: 2020-07-28 categories: - Foo tags: - Foo - Bar - Baz --- // 以下開始撰寫你的文章 ``` <br> 出來的效果如下圖。但是我不是很喜歡這效果,原因有二: 1. **陳列的資訊太多的** 這有違我選這個主題的初衷,當初選擇這個主題很大的原因是因為簡潔,為了更簡潔我還把原先資訊列上的說明文字全拿掉了。現在變這麼長,實在讓我感到很阿雜 :cry: 2. **排序問題** 另一個原因是,當來到首頁與其他出現時間軸頁面時,會發現它們的排序還是照著發布時間來,與我原先預期不同,我期望最後修改的文章會在最前面的說。 <p class="illustration"> <img src="https://i.imgur.com/mOzmMf5.png" alt="顯示修改時間"> </p> ## 時間擇一顯示 雖然沒有符合預期的原生結果,但還好身為一個工程師,我可以自己動手做,~~至於失敗就是另外一回事了~~。 巡視了下網站,發現時間戳會出現在網站的三處:==文章本身==、==首頁== 以及 ==時間軸== 三處。不過實際翻看程式碼,會注意到文章與首頁的時間戳其實共用一個 HTML 的,所以需要修改的地方只有兩處。 <br> :::warning :warning: **我更改了 yaml 區域中最後修改時間的字段** 相比 `updated` ,我更喜歡用 `modified` 這個單字。所以在我修改後的程式碼當中,都使用了 `post.modified` 取代了 `post.updated`。若不想隨我更改,請將下列中的 `modified` 取代掉。 ::: ### 限制條件 先提提一個限制條件。 在每篇文章的 yaml 區中一定要同時存在 `date` 與 `modified` 兩個字段,即便文章沒有更新過,也必須在 `modified` 字段中填入發布的時間(這也是我喜歡用 `modified` 取代 `updated` 的原因)。否則下面程式碼在執行的時候,會因為 `modified` 為空值,導致時間顯示空白;或依照 `modified` 排序時,因為空值,而導致順序掉到最後面去。 為了解決這問題,我有試著[找過並詢問過](https://stackoverflow.com/questions/63149510/using-liquid-to-sort-posts-by-the-latest-datelatestposted-date-updated-date) Liquid 中是否可以像 python 一樣能自定義 [sort 功能](https://shopify.github.io/liquid/filters/sort/),讓未修改過的文章不必填寫兩個相同時間,但很不幸的 Liquid 沒有支援這個功能。 所以...還是乖乖填兩個字段吧 :smiley: ### 文章 & 首頁 回到顯示時間的部分,我們來看看如何修改顯示結果。 在文章與首頁這兩個部份時間戳是共用 `_includes/_macro/post.html` 這份程式碼的,而時間戳則是定義在 `<span class="post-time">...</span>` 的區塊中。 觀察下原先的程式碼會發現,顯示發布時間與最後修改時間的結構非常的相像,兩者相異之處也只有相對應的參數不同。按這概念先試著做了一次變數抽取,可以將原先顯示發布時間程式碼改寫成: ```htmlmixed= {% assign icon = "fa fa-calendar-o" %} {% assign title_hint = __.post.created %} {% assign itemprop_hint = "dateCreated datePublished" %} {% assign timestamp = post.date %} <span class="post-meta-item-icon"> <i class="{{ icon }}"></i> </span> <time title="{{ title_hint }}" itemprop="{{ itemprop_hint }}" datetime="{{ timestamp | date_to_xmlschema }}"> {{ timestamp | date: site.date_format }} </time> ``` <br> 確認要提取的變數後,就可以開始進行修改: 1. **移除原始程式碼** 這邊顯示的邏輯與原本的程式碼不同,因此方便起見我把之前原先顯示兩個時間,也就是 `<span class="post-time">...</span>` 的部份全部移掉。 2. **確認顯示邏輯** 兩個變數互相搭配的情況下,會有 4 種可能: | created_at | updated_at | 顯示邏輯 | | ---------- | ---------- | ------------------------------------------------------------------- | | true | false | 顯示發布時間 | | true | true | 這個比較麻煩,必須再進一步判斷,如果發布時間與修改時間一致,則顯示發布時間;若相異,則顯示修改時間。| | false | true | ...雖然我覺得應該不會有人這樣設,不過這狀況應該是顯示修改時間? | | false | false | 整個區塊不顯示。 | 3. **程式撰寫** 按照上面的真值表邏輯,就可以開始撰寫了。 1. **format_modified** 考慮到如果 updated_at 為 `false` 的情況, yaml 中可能不會存在該字段,所以先判斷它是否存在,再來套用 Filter。 2. **show** 另外用了一個名為 `show` 的變數來當 flag。一來是因為想盡可能遵守==程式碼不重複==的原則,二來是因為 Liquid 的流程控制真的超難寫,有些 Ruby 的語法我在這邊都找不到。:cry: <br> 修改後的程式碼如下: ```htmlmixed= {% if site.post_meta.created_at or site.post_meta.updated_at %} {% assign format_date = post.date | date: site.date_format%} {% assign format_modified = post.modified %} {% if format_modified %} {% assign format_modified = format_modified | date: site.date_format %} {% endif %} {% assign show = "published" %} {% if site.post_meta.created_at %} {% if site.post_meta.updated_at and format_modified != format_date%} {% assign show = "modified" %} {% endif %} {% else %} {% assign show = "modified" %} {% endif %} {% if show == "published" %} {% assign icon = "fa fa-calendar-o" %} {% assign title_hint = __.post.created %} {% assign itemprop_hint = "dateCreated datePublished" %} {% assign timestamp = post.date %} {% else %} {% assign icon = "fa fa-calendar-check-o" %} {% assign title_hint = __.post.modified %} {% assign itemprop_hint = "dateModified" %} {% assign timestamp = post.modified %} {% endif %} <span class="post-meta-item-icon"> <i class="{{ icon }}"></i> </span> <time title="{{ title_hint }}" itemprop="{{ itemprop_hint }}" datetime="{{ timestamp | date_to_xmlschema }}"> {{ timestamp | date: site.date_format }} </time> {% endif %} ``` ### 時間軸 時間軸這邊改起來算快,因為之前為了[在時間軸上顯示完整日期](/@CynthiaChuang/Show-Full-Timestamp-on-Timeline),其實已經改過一次了。 一樣到 `_includes/_macro/post-collapse.html` 中,修改時間軸上的 Item 物件。可以看到程式碼中原本是讀取 `post.date` 顯示時間,這邊加個流程控制來決定讀取的變數。 ```htmlmixed= {% if site.post_meta.updated_at != true%} {% assign timestamp = post.date %} {% assign itemprop_hint = "dateCreated" %} {% else %} {% assign timestamp = post.modified %} {% assign itemprop_hint = "dateModified" %} {% endif %} <time class="post-time" itemprop="{{ itemprop_hint }}" datetime="{{ timestamp | date_to_xmlschema }}" content="{{ timestamp | date: site.date_format }}" > {{ timestamp | date: '%Y.%m.%d' }} </time> ``` <br> 另外在時間軸上也藏了一個小小需要改的地方,就是在 archives 頁面上那個年份的分隔。 這邊是定義在 `_includes/archive.html` 中,還滿好找的,上方剛好有一個註解寫 ==Show year==。一樣幫那行加上個流程控制: ```htmlmixed= {% comment %} Show year {% endcomment %} {% if site.post_meta.updated_at != true%} {% assign timestamp = post.date %} {% else %} {% assign timestamp = post.modified %} {% endif %} {% assign post_year = timestamp | date: '%Y' %} ``` ## 按修改時間排序 搞定時間顯示後,我還希望文章可以按照最後修改的日期來排序,不然我辛苦改完了不就沒人知道嗎? 而與排序有關的有==時間軸==以及==首頁==的部分。 ### 時間軸 剛剛頁面都停在了時間軸附近了,所以排序我就從這裡改起了! 跟前面不太一樣,前面顯示時間是時間軸中的 Item 實作的。這邊則是牽扯所有文章的排序,必須在 Item 外就完成,因此會在出現時間軸的頁面,分別是 Categories、 Tags、 Archives ,中各自實作。但三者個改法其實都是相同的。 分別在 `_includes/category.html`、`_includes/archive.html` 與 `_includes/tag.html` 中找到 for 迴圈的位置,並將原先傳入的變數,先用另一個變數暫存,再判斷是否需要按修改時間排序,最後再將結果傳入迴圈中: ```htmlmixed= {% assign sorted_list = site.posts %} {% if site.post_meta.updated_at %} {% assign sorted_list = sorted_list | sort:"modified" | reverse %} {% endif %} {% for post in sorted_list %} ``` ### 首頁 最後是首頁的部分,這邊也是我唯一改不動的部分。也不能說改不動,如果你沒有啟動分頁器功能,其實是可以順利完成,但一旦啟動分頁器就 GG 了。 打開 `_includes/index.html` ,會看到原先兩行程式碼分別對應兩種狀況:有無使用==分頁器==。原本不打算動這邊的邏輯,直接在最後套上判斷式就好,結果如下: ```htmlmixed= {% assign posts = site.posts %} {% if site.paginate > 0 %} {% assign posts = paginator.posts %} {% endif %} {% if site.post_meta.updated_at %} {% assign posts = posts | sort:"modified" | reverse %} {% endif %} ``` <br> 但,越看越不對勁。在分頁器啟動的情況下,它其實是先從分頁器取出了一頁假設 10 篇的文章,而我排序僅僅是這 10 篇文章,而不是對著全部文章排序阿...。這樣子,如果是修改某篇很舊的文中,它還是不會被排到頂端阿! 只好用關鍵字 [jekyll-paginate](https://jekyllrb.com/docs/pagination/) sort by modified date 之類的,下去找找有無相關資訊。但並沒有看到任何有用的建議,倒是發現了這個 [Pull request](https://github.com/jekyll/jekyll-paginate/pull/31/files#)。這個 request 的目的是想要按照更新日期來排序,但很明顯地它還沒有被合併阿阿阿阿...。 ## tricy 的作法 因為搞不定在開起分頁器後,全部文章的排序問題,這個計畫原本該宣告失敗的。但我實在不甘心,超想要那個打勾的日曆 icon 阿阿阿阿! 我想到既然它只能照 `date` 這個字段來排,那我就讓它照這個來排吧!但我直接來告訴它何時要顯示 modified 的 icon。 基於這個想法,我把上面寫的程式碼全部 rollback 回去,砍掉重練。 所以現在文章的 yaml 變成了這樣,一旦文章有修改,則更改時間,並把 `is_modified` 設置為 `true`: ``` --- title: 文章標題 date: 2020-09-01 is_modified: true categories: - Foo tags: - Foo - Bar - Baz --- // 以下開始撰寫你的文章 ``` <br> 接下來去改 `_includes/_macro/post.html`,一樣動 ` <span class="post-time">...</span>` 中間的程式碼,程式碼跟[之前的章節](#文章-amp-首頁)類似,唯一的區別就是我把 `timestamp` 變數抽掉了,全部都只讀 `date` 這個字段,如此我就不參與排序的處理了。 ```htmlmixed= {% if site.post_meta.created_at or site.post_meta.updated_at %} {% assign show = "published" %} {% if site.post_meta.created_at %} {% if site.post_meta.updated_at and post.is_modified %} {% assign show = "modified" %} {% endif %} {% else %} {% assign show = "modified" %} {% endif %} {% if show == "published" %} {% assign icon = "fa fa-calendar-o" %} {% assign title_hint = __.post.created %} {% assign itemprop_hint = "dateCreated datePublished" %} {% else %} {% assign icon = "fa fa-calendar-check-o" %} {% assign title_hint = __.post.modified %} {% assign itemprop_hint = "dateModified" %} {% endif %} <span class="post-meta-item-icon"> <i class="{{ icon }}"></i> </span> {% if site.post_meta.item_text %} <span class="post-meta-item-text">{{ __.post.posted }}</span> {% endif %} <time title="{{ title_hint }}" itemprop="{{ itemprop_hint }}" datetime="{{ post.date | date_to_xmlschema }}"> {{ post.date | date: site.date_format }} </time> {% endif %} ``` 是說,別忘了 `_config.yml` 中,該開的還是要開。 ## 後記 說實話,這個功能開發時程拉了好長一陣子,原本我都已經把它部署到 github page 上了,結果在寫網誌的時候發現有 bug,只好先把功能給 rollback 掉,爾後來回嘗試了些方法,最後只先這樣了。雖然 yaml 的可讀性看起來怪怪的,不過 UI 上顯示一切正常,也算是也成功? 不過說真的,寫網誌真的會促進思考,為了順利寫完網誌,還重新理了理程式邏輯,把原本懶得處理的例外狀況都補上,還順便發了自己好幾條 issue ... ## 更新紀錄 :::spoiler 最後更新日期:2020-09-07 - 2020-09-07 發布 - 2020-09-01 完稿 - 2020-07-22 起稿 ::: <br><br> > **本文作者**: 辛西亞.Cynthia > **本文連結**: [辛西亞的技能樹](https://cynthiachuang.github.io/Show-the-Last-Modified-Time-in-Jekyll-NextT-Theme) / [hackmd 版本](https://hackmd.io/@CynthiaChuang/Show-the-Last-Modified-Time-in-Jekyll-NextT-Theme) > **版權聲明**: 部落格中所有文章,均採用 [姓名標示-非商業性-相同方式分享 4.0 國際](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en) (CC BY-NC-SA 4.0) 許可協議。轉載請標明作者、連結與出處!