通过JS生成最新文章和相关推荐
发表于更新于
字数总计2k阅读时长10分钟阅读量
- 修复文章自我推荐的问题
- 相关推荐列表数量不够时随机选取文章填充
魔改思路
默认的最新文章和相关推荐的生成方式是在构建博客时直接写入到html
文件中,而我的SW缓存策略就导致更新博文时也要刷新全部或部分html
缓存,以此更新最新文章和相关推荐列表(下文简称“列表”)。
于是我想到了一个解决方案,我将列表有关的信息直接保存在一个json
文件中,然后用户访问页面的时候读取这个json
文件,再根据json
文件编写列表。这样子的话每次更新博文的时候不就只需要更新修改的博文和这个json
文件就可以了。
接下来就是制定json
格式,当前我是用的方案是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "info": { "[abbrlink]": { "title": "标题", "img": "封面", "time": "日期", "sort": "最新文章列表用到的日期", "date": "相关推荐列表用到的日期" } }, "recent": ["[abbrlink]", "[abbrlink]"], "related": { "[abbrlink]": ["[abbrlink]", "[abbrlink]"] } }
|
其中,当sort
和date
值相同时会用time
替代,这样设计的目的是为了压缩json
体积。
接下来就是考虑如何生成json
文件了,我们选择通过hexo
生成,就是在scripts
目录下面写一个js
用来生成json
。
教程
修改:[butterfly]\layout\includes\widget\card_recent_post.pug
:
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 | if theme.aside.card_recent_post.enable .card-widget.card-recent-post .item-headline i.fas.fa-history span= _p('aside.card_recent_post') + .aside-list(id='recent-list') - .aside-list - - let postLimit = theme.aside.card_recent_post.limit === 0 ? site.posts.length : theme.aside.card_recent_post.limit || 5 - - let sort = theme.aside.card_recent_post.sort === 'updated' ? 'updated' : 'date' - - site.posts.sort(sort, -1).limit(postLimit).each(function(article){ - - let link = article.link || article.path - - let title = article.title || _p('no_title') - - let no_cover = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : '' - - let post_cover = article.cover - .aside-list-item(class=no_cover) - if post_cover && theme.cover.aside_enable - a.thumbnail(href=url_for(link) title=title) - img(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) - .content - a.title(href=url_for(link) title=title)= title - if theme.aside.card_recent_post.sort === 'updated' - time(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated)) #[=date(article.updated, config.date_format)] - else - time(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date)) #[=date(article.date, config.date_format)] - - })
|
新建:[butterfly]\layout\includes\widget\card-related-post.pug
:
1 2 3 4 5 6 | .card-widget.card-recommend-post .item-headline i.iconfont.icon-tuijian span= _p('post.recommend') .aside-list(id='related-list')
|
修改:[butterfly]\layout\includes\widget\index.pug
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | if is_post() - const tocStyle = page.toc_style_simple - const tocStyleVal = tocStyle === true || tocStyle === false ? tocStyle : theme.toc.style_simple if showToc && tocStyleVal .sticky_layout include ./card_post_toc.pug else !=partial('includes/widget/card_author', {}, {cache: true}) !=partial('includes/widget/card_announcement', {}, {cache: true}) !=partial('includes/widget/card_top_self', {}, {cache: true}) .sticky_layout if showToc include ./card_post_toc.pug if theme.related_post && theme.related_post.enable + !=partial('includes/widget/card-related-post', {}, {cache: true}) - != related_posts(page,site.posts) !=partial('includes/widget/card_recent_post', {}, {cache: true}) !=partial('includes/widget/card_ad', {}, {cache: true})
|
删除:[butterfly]\scripts\helpers\related_post.js
新建:[butterfly]\scripts\customs\posts.js
:
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | const logger = require('hexo-log')()
hexo.extend.generator.register('buildPostJson', async () => { const resultJson = {} const config = hexo.theme.config const list = hexo.locals.get('posts').data const sort = config.aside.card_recent_post.sort const date_type = config.related_post.date_type
const buildAbbrlinkInfo = () => { const writeTime = sort === date_type ? (json, post) => json.time = (sort === 'updated' && post.updated ? post.updated : post.date) : (json, post) => { if (sort === 'updated') { json.sort = post.updated ? post.updated : post.date json.date = post.date } else { json.sort = post.date json.date = post.updated ? post.updated : post.date } } const json = {} for (let post of list) { const info = {} info.title = post.title writeTime(info, post) info.img = post.cover json[post.abbrlink] = info } resultJson.info = json }
const buildRecentJsonInfo = () => { if (!(config.aside.enable && config.aside.card_recent_post.enable)) return const getTime = sort === 'updated' ? post => post.updated ? post.updated : post.date : post => post.date const sorted = [] for (let post of list) sorted.push(post) sorted.sort((a, b) => getTime(b) - getTime(a)) resultJson.recent = { limit: config.aside.card_recent_post.limit || 5, list: sorted.map(it => it.abbrlink.toString()) } }
const buildRelatedJsonInfo = () => { if (!config.related_post.enable) return resultJson.related = {} const maxCount = config.related_post.limit resultJson.related.list = {} const categories = hexo.locals.get('categories').data const tags = hexo.locals.get('tags').data const findObj = (src, dist) => { for (let value of src) { if (value.name === dist) return value.posts.data } return [] } const getPostsByTags = tag => { const result = [] for (let value of findObj(tags, tag.name)) result.push(value) return result } const getPostsByCategories = cat => { const result = [] for (let value of findObj(categories, cat.name)) result.push(value) return result } const handle = post => { const map = new Map() const plusValue = (post, plus = 1) => { if (map.has(post)) map.set(post, map.get(post) + plus) else map.set(post, plus) } for (let tag of post.tags.data) { const list = getPostsByTags(tag) for (let value of list) plusValue(value) } for (let cat of post.categories.data) { const list = getPostsByCategories(cat) for (let value of list) plusValue(value, 2) } const result = [] map.forEach((value, key) => result.push({post: key, value: value})) result.sort((a, b) => b.value - a.value) return result }
for (let post of list) { const info = handle(post) const json = [] for (let value of info) { if (value.post.abbrlink === post.abbrlink) continue if (json.length === maxCount) break json.push(value.post.abbrlink.toString()) } for (; json.length !== maxCount;) { const index = Math.floor(Math.random() * list.length) const abbrlink = list[index].abbrlink.toString() if (abbrlink === post.abbrlink.toString() || json.indexOf(abbrlink) > -1) continue json.push(abbrlink) } resultJson.related.list[post.abbrlink] = json } }
const tasks = [buildAbbrlinkInfo, buildRecentJsonInfo, buildRelatedJsonInfo] await Promise.all(tasks.map(it => new Promise(resolve => { it() resolve() }))) logger.info(`文章JSON构建成功(${list.length})`) return { path: 'postsInfo.json', data: JSON.stringify(resultJson) } })
|
引入JS:
注意:如果你开启了pjax,请通过no-pjax
的方式引入该JS
如果你使用异步方式引入该JS,请使用defer
,以此保证JS运行时DOM已经解析完毕
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 | syncJsonInfo()
function syncJsonInfo() { function readAbbrlink(json, abbrlink) { return json['info'][abbrlink] } function syncRecentPosts(json) { function create(abbrlink, title, img, date) { return `<div class="aside-list-item">\<a class="thumbnail" href="/posts/${abbrlink}/" title="${title}"><img src="${img}" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="${title}"></a><div class="content"><a class="title" href="/posts/${abbrlink}/" title="${title}">${title}</a><time title="更新于 ${date.toLocaleString()}">${date.toLocaleDateString()}</time></div></div>` } const recent = json.recent const list = recent.list const div = document.getElementById('recent-list') if (!div) return const length = Math.min(list.length, recent.limit) for (let i = 0; i !== length; ++i) { const abbrlink = list[i] const info = readAbbrlink(json, abbrlink) const html = create(abbrlink, info['title'], info['img'], new Date(info['sort'] ? info['sort'] : info['time'])) div.insertAdjacentHTML('beforeend', html) } } function syncRelatedPosts(json) { function create(abbrlink, title, img, date) { return `<div class="aside-list-item"><a class="thumbnail" href="/posts/${abbrlink}/" title="${title}"><img alt="${title}" src="${img}"></a><div class="content"><a class="title" href="/posts/${abbrlink}/" title="${title}">${title}</a><time title="发表于 ${date.toLocaleDateString()}">${date.toLocaleDateString()}</time></div></div>` } const related = json['related'] const div = document.getElementById('related-list') if (!div) return const href = location.href.substring(0, location.href.length - 1) const abbrlink = href.substring(href.lastIndexOf('/') + 1) const list = related['list'][abbrlink] for (let i = 0; i !== list.length; ++i) { const info = readAbbrlink(json, list[i]) const html = create(list[i], info['title'], info['img'], new Date(info['date'] ? info['date'] : info['time'])) div.insertAdjacentHTML("beforeend", html) } }
const jsonCache = sessionStorage.getItem('postsInfo') if (jsonCache) { const json = JSON.parse(jsonCache) syncRecentPosts(json) syncRelatedPosts(json) } else { fetch(`/postsInfo.json`).then(response => { response.json().then(json => { sessionStorage.setItem('postsInfo', JSON.stringify(json)) syncRecentPosts(json) syncRelatedPosts(json) }) }) } }
|
然后通过hexo s
就能看到效果了。
创作不易,扫描下方打赏二维码支持一下吧ヾ(≧▽≦*)o
通过JS生成最新文章和相关推荐空 梦 | 山岳库博
更新于 2022-07-30
发布于 2022-06-09