前言
想掌握網(wǎng)站文章歡迎度、閱讀排行?那么「熱榜」功能就不可或缺
在寫這篇文章以前,本人也了解過其他博客的做法,無外乎是基于Leancloud
實(shí)現(xiàn)的。很少有用谷歌Firestore(Firebase)
來做的,這篇文章咱就來說說Firestore
。
PS:Leancloud現(xiàn)在需要上傳
手持身份證照片
進(jìn)行實(shí)名。。。
主要是本人比較排斥這樣搜刮隱私的行為??,So經(jīng)過幾次嘗(tou)試(ji)無果后,果斷放棄。如果你也有遇到這種情況,希望此文能對你有所幫助。
關(guān)于Leancloud
的相關(guān)實(shí)現(xiàn),可以參考這篇文章,精簡實(shí)用,本文也在此基礎(chǔ)上作了參考。
正文
/themes/next/_config.yml
文件,已經(jīng)提前預(yù)配了firestore
:
是不是已經(jīng)給你提示了呢?而且next
官方文檔也提到過,一共分為兩步:
1、注冊
2、配置
Firebase屬于谷歌的,確保你能訪問:https://console.firebase.google.com ? (tips:github)
步驟
注冊
可以直接使用Google 帳號
登錄,沒有的自行進(jìn)行注冊,此步驟略過。。。
登錄成功后,點(diǎn)擊網(wǎng)頁右上角:轉(zhuǎn)到控制臺
添加項目
輸入項目名稱,比如我用的是leafjame2019814
, 點(diǎn)擊繼續(xù),繼續(xù),用默認(rèn)的配置即可,完成后還可以再修改。
創(chuàng)建項目完成后,點(diǎn)擊網(wǎng)頁左側(cè)setttings
按鈕,如圖:
即可查看到自己的項目ID
和網(wǎng)絡(luò) API 密鑰
,這就是next主題配置文件中提到的projectId
和apiKey
創(chuàng)建數(shù)據(jù)庫
創(chuàng)建完項目后,接著需要創(chuàng)建存儲數(shù)據(jù)的地方,如下圖所示:
有兩種選項,自己用的話,選擇以測試模式開始
,就可以。
如果選擇
以鎖定模式開始
,則后續(xù)自己通過js api的方式寫入會提示沒有權(quán)限。。(可能需要配置權(quán)限什么的吧,我自己也沒搞懂)
下一步設(shè)置Cloud Firestore位置
,有一下好幾種,分為多域性
和區(qū)域性
。具體區(qū)別可查看文檔,默認(rèn)選擇nam5 (us-central)
即可,點(diǎn)擊完成,等待分配Database。
完成后顯示是這樣的,現(xiàn)在還沒有數(shù)據(jù)。
可參考Cloud Firestore 使用入門,文檔還是很全的
配置文章閱讀量
做完上邊的操作后,接著就是把代碼集成到自己的項目中了。
在/themes/next/layout/_third-party/analytics/firestore.swig
中,其實(shí)已經(jīng)實(shí)現(xiàn)了文章閱讀量的功能,只需要經(jīng)過本文以上的操作,然后在/themes/next/_config.yml
文件,啟用firestore
:
apiKey
和projectId
就是上文中創(chuàng)建的。collection
:集合ID,下邊會講到。
這個功能經(jīng)過配置后,可用于文章的閱讀次數(shù)
了,當(dāng)文章未被查看時,效果是這樣的:
此時的Firebase中的Database下也沒有數(shù)據(jù),當(dāng)瀏覽這篇文章后,
閱讀次數(shù)
加1了。如下圖:
再打開Firebase,你會發(fā)現(xiàn),也有數(shù)據(jù)了:
articles
就是上邊配置的collection
的值,即:集合ID
這控制臺能對集合、文檔、字段、值進(jìn)行操作,比如設(shè)置閱讀量啦什么的。。??
設(shè)置開發(fā)環(huán)境
每個頁面的閱讀量有了,不過我們要做的是排行榜啊,得把所有的頁面匯總排序。而且剛才的Database里存的數(shù)據(jù)也沒有URL等等這些信息呀,接下來要寫代碼了。。。
新增如下的頁面——「熱榜」,我想大家都會了吧,不細(xì)說了。
在這個index.md
文件中,引入要用到的Firebase代碼,可參照官方文檔進(jìn)行。
之前我想直接在此md文件中引入
firestore.swig
,但啟動一直報錯。。(我也是新手??????,應(yīng)該是沒配置對,知道答案的麻煩請留言告知下~)
報錯截圖如下:
多次改路徑,嘗試無果后。。。我覺定在md文件中再次引入依賴的js代碼。。
index.md
完整代碼如下:
---
title: 文章熱度排行
date: 2019-08-14 15:23:11
---
<div id="top" style="margin-top:80px;">
</div>
<!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/5.10.1/firebase-app.js"></script>
<!-- Add Firebase products that you want to use -->
<script src="https://www.gstatic.com/firebasejs/5.10.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.10.1/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.10.1/firebase-firestore.js"></script>
<script>
firebase.initializeApp({
apiKey: 'AIzaSyDKul4ZXXXXX6l6UiHzXXXXvcDsiE', //你的apiKey
projectId: 'aXXX5eXXX6b' //你的projectId
})
var title= '';
var count = 0;
var url = '';
const db = firebase.firestore();
var collection = 'articles'; //主題配置文件配置的collection //{{ theme.firestore.collection }}';
db.collection(collection).orderBy('count', 'desc').limit(10).get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// console.log(doc.id, " => ", doc.data());
title = doc.id;
count = doc.data().count;
url = doc.data().url;
var content="<h5><p>"+"<font color='#1C1C1C'>"+"【文章熱度: "+count+" ℃】"+"</font>" + '  ' + "<span'><a href='"+url+"'>"+title+"</a></span>"+"</p></h5>";
document.getElementById("top").innerHTML+=content
});
});
</script>
1、引入的js版本最好和
firestore.swig
中的保持一致
2、記得把apiKey
和projectId
換成你自己的。。
3、orderBy
這是是按Database中articles集合的count
值降序排序
4、limit
限制返回的結(jié)果集數(shù)量
tips:Firebase官方網(wǎng)站上,可全文搜索想要結(jié)果(都是谷歌網(wǎng)站的js包,必須能訪問外網(wǎng)。。)
參考文檔: Firebase讀取數(shù)據(jù)、 Firebase對數(shù)據(jù)進(jìn)行排序和限制數(shù)量、 github上firestore相關(guān)JS操作方法
完成以上步驟后,訪問熱榜界面,就能出現(xiàn)以下界面了:
文章排行有了,但是點(diǎn)擊文章要跳轉(zhuǎn)的鏈接還沒有呢。我們也看到了,F(xiàn)irebase只保存了每個文章訪問量count
值,沒有存文章的url信息。這就是接下來要解決的問題。。。
保存文章url地址
前文說過,Next為我們整合了firestore,那只能實(shí)現(xiàn)記錄每篇文章瀏覽量的功能,要做熱榜,似乎不滿足需求啊,這就要我們改代碼了。。??
修改/themes/next/layout/_third-party/analytics/firestore.swig
,主要修改的地方有三處:
修改文章頁判斷邏輯
以firestore.swig
之前的代碼來做排行榜功能時,發(fā)現(xiàn)不止文章出現(xiàn)在排行榜頁面,連左側(cè)點(diǎn)擊過的鏈接,比如分類
、標(biāo)簽
、歸檔
等等的鏈接也會顯示在排行榜頁面。。。
后來博主經(jīng)過多次調(diào)試、閱讀代碼后,發(fā)現(xiàn)了問題所在,原因就出現(xiàn)在現(xiàn)有的文章頁和非文章頁判斷邏輯上:
這個邏輯只能適用于單篇文章的閱讀次數(shù)統(tǒng)計。。不能滿足我們現(xiàn)在的需求了,代碼修改如下:
//https://hexo.io/zh-tw/docs/variables.html
var isPost = '{{ page.title }}'.length > 0
var isArchive = '{{ archive }}' === 'true'
var isCategory = '{{ category }}'.length > 0
var isTag = '{{ tag }}'.length > 0
var urlPath = '{{ page.path }}';
var urlFullPath = '{{ page.permalink }}';
var indexPath = 'index.html'; //首頁鏈接
var isMenu = false;
{% for name, path in theme.menu %}
{# 判斷當(dāng)前鏈接是否是左側(cè)菜單欄鏈接 #}
var menuLink = '{{ url_for(path.split('||')[0]) | trim }}';
if(urlPath.indexOf(menuLink) > 0 || urlPath == indexPath){
isMenu = true;
}
{% endfor %}
// if (isPost) { //is article page
if (!isMenu) { // 非菜單頁、非主頁(即在某篇文章鏈接里)
var title = '{{ page.title }}'
var doc = articles.doc(title)
// getCount(doc, true).then(appendCountTo($('.post-meta')))
getCount(doc, urlFullPath, true).then(appendCountTo($('.post-wordcount')))
}
// else if (!isArchive && !isCategory && !isTag) { //is index page
else if (urlPath == indexPath) { // 主頁
var titles = [] //array to titles
修改前后對比如下:
注意了:
var indexPath = 'index.html'; //首頁鏈接
這是我在站點(diǎn)配置文件中修改后的結(jié)果
之前的:permalink: :year/:month/:day/:title/
,在做SEO優(yōu)化改成了:permalink: :title.html
,所以這里能直接用來做判斷了。
當(dāng)然你不改的話,這里的if判斷就得改成你現(xiàn)在的邏輯。
修改getCount
方法
我也嘗試過在articles.doc(title)
中追加設(shè)置鏈接的方法,參考了Github,比如:
articles.doc(title).set({'uri': urlPath, 'url': urlFullPath});
不過這樣操作后,在getCount
方法中就會出現(xiàn)其它的錯誤,改動比較多。
偷懶一下,我就把url地址直接以參數(shù)的形式傳遞到getCount方法
getCount(doc, urlFullPath, true)
這樣改動的就相對少了。。。??
這里用了
window.localStorage
來保存整個網(wǎng)站的數(shù)據(jù),以title
為key
,保存的數(shù)據(jù)沒有過期時間,直到手動去刪除。
修改文章閱讀次數(shù)樣式(非必須)
最后的修改是為了改下文章閱讀次數(shù)的樣式
將firestore.swig
中的$('.post-meta')
替換成$('.post-wordcount')
;
將圖標(biāo)$('<i>').addClass('fa fa-users')
替換成$('<i>').addClass('fa fa-eye')
,效果如下:
附上firestore.swig
完整代碼:
{% if theme.firestore.enable %}
<script src="https://www.gstatic.com/firebasejs/4.6.0/firebase.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.6.0/firebase-firestore.js"></script>
{% if theme.firestore.bluebird %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.5.1/bluebird.core.min.js"></script>
{% endif %}
<script>
(function () {
firebase.initializeApp({
apiKey: '{{ theme.firestore.apiKey }}',
projectId: '{{ theme.firestore.projectId }}'
})
function getCount(doc, url, increaseCount) {
//increaseCount will be false when not in article page
return doc.get().then(function (d) {
var count
if (!d.exists) { //has no data, initialize count
if (increaseCount) {
doc.set({
count: 1,
'url': url
})
count = 1
}
else {
count = 0
}
}
else { //has data
count = d.data().count
if (increaseCount) {
if (!(window.localStorage && window.localStorage.getItem(title))) { //if first view this article
doc.set({ //increase count
count: count + 1
})
count++
}
}
}
if (window.localStorage && increaseCount) { //mark as visited
localStorage.setItem(title, true)
}
return count
})
}
function appendCountTo(el) {
return function (count) {
$(el).append(
$('<span>').addClass('post-visitors-count').append(
$('<span>').addClass('post-meta-divider').text('|')
).append(
$('<span>').addClass('post-meta-item-icon').append(
// $('<i>').addClass('fa fa-users')
$('<i>').addClass('fa fa-eye')
)
).append($('<span>').text('{{ __("post.visitors")}} ' + count + ' 次'))
)
}
}
var db = firebase.firestore()
var articles = db.collection('{{ theme.firestore.collection }}')
//https://hexo.io/zh-tw/docs/variables.html
var isPost = '{{ page.title }}'.length > 0
var isArchive = '{{ archive }}' === 'true'
var isCategory = '{{ category }}'.length > 0
var isTag = '{{ tag }}'.length > 0
var urlPath = '{{ page.path }}';
var urlFullPath = '{{ page.permalink }}';
var indexPath = 'index.html'; //首頁鏈接
var isMenu = false;
{% for name, path in theme.menu %}
{# 判斷當(dāng)前鏈接是否是左側(cè)菜單欄鏈接 #}
var menuLink = '{{ url_for(path.split('||')[0]) | trim }}';
if(urlPath.indexOf(menuLink) > 0 || urlPath == indexPath){
isMenu = true;
}
{% endfor %}
// if (isPost) { //is article page
if (!isMenu) { // 非菜單頁、非主頁(即在某篇文章鏈接里)
var title = '{{ page.title }}'
var doc = articles.doc(title)
// getCount(doc, true).then(appendCountTo($('.post-meta')))
getCount(doc, urlFullPath, true).then(appendCountTo($('.post-wordcount')))
}
// else if (!isArchive && !isCategory && !isTag) { //is index page
else if (urlPath == indexPath) { // 主頁
var titles = [] //array to titles
var postsstr = '{% for post in page.posts %}titles.push("{{ post.title }}");{% endfor %}' //if you have a better way to get titles of posts, please change it
eval(postsstr)
var promises = titles.map(function (title) {
return articles.doc(title)
}).map(function (doc) {
return getCount(doc)
})
Promise.all(promises).then(function (counts) {
// var metas = $('.post-meta')
var metas = $('.post-wordcount')
counts.forEach(function (val, idx) {
appendCountTo(metas[idx])(val)
})
})
}
})()
</script>
{% endif %}
結(jié)尾
至于熱榜頁面的樣式布局,可在其index.md
文件中修改即可
經(jīng)過博主三四天攻堅,以參閱Google官方文檔為主,至此,文章熱榜功能已經(jīng)全部完成。。。??????
最終效果:
基于Firestore做的排行榜也有個缺點(diǎn),那就是對于不能訪問谷歌的用戶來說,這個頁面是不能正常顯示的。。。
雖然Firebase
具有離線訪問數(shù)據(jù)功能,不過這是針對短期不能聯(lián)網(wǎng)的情況。。??????
如果需要做到國內(nèi)用戶普遍都能訪問,那好像就得依賴于Leancloud
實(shí)現(xiàn)了,不知道大家有什么其他方案,歡迎留言討論。
(本文完)