hexo-theme-stellar 1.32.4 → 1.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_config.yml +22 -7
- package/_data/icons.yml +8 -1
- package/languages/en.yml +1 -1
- package/languages/zh-CN.yml +1 -1
- package/languages/zh-TW.yml +1 -1
- package/layout/_partial/comments/artalk/script.ejs +7 -6
- package/layout/_partial/head.ejs +7 -1
- package/layout/_partial/main/article/contributors.ejs +1 -1
- package/layout/_partial/main/article/read_next.ejs +2 -2
- package/layout/_partial/main/navbar/article_banner.ejs +1 -1
- package/layout/_partial/main/navbar/breadcrumb/blog.ejs +3 -3
- package/layout/_partial/main/navbar/breadcrumb/note.ejs +2 -2
- package/layout/_partial/main/navbar/breadcrumb/page.ejs +1 -1
- package/layout/_partial/main/navbar/breadcrumb/wiki.ejs +2 -2
- package/layout/_partial/main/navbar/dateinfo.ejs +1 -1
- package/layout/_partial/main/navbar/ghinfo.ejs +1 -1
- package/layout/_partial/main/navbar/nav_tabs_blog.ejs +12 -12
- package/layout/_partial/main/navbar/nav_tabs_wiki.ejs +2 -2
- package/layout/_partial/main/notebook/note_tags.ejs +1 -1
- package/layout/_partial/scripts/lazyload.ejs +1 -1
- package/layout/_partial/scripts/utils.ejs +69 -39
- package/layout/_partial/sidebar/index_leftbar.ejs +1 -1
- package/layout/_partial/sidebar/menu.ejs +1 -1
- package/layout/_partial/widgets/components/link.ejs +1 -1
- package/layout/_partial/widgets/ghissues.ejs +7 -1
- package/layout/_partial/widgets/ghrepo.ejs +2 -2
- package/layout/_partial/widgets/ghuser.ejs +1 -1
- package/layout/_partial/widgets/recent.ejs +1 -1
- package/layout/_partial/widgets/related.ejs +2 -2
- package/layout/_partial/widgets/tagtree.ejs +1 -1
- package/layout/_partial/widgets/timeline.ejs +7 -1
- package/layout/_partial/widgets/tree.ejs +1 -1
- package/layout/archive.ejs +1 -1
- package/layout/categories.ejs +1 -1
- package/layout/index.ejs +1 -1
- package/layout/index_topic.ejs +1 -1
- package/layout/index_wiki.ejs +2 -2
- package/layout/notebooks.ejs +1 -1
- package/layout/notes.ejs +1 -1
- package/layout/tags.ejs +1 -1
- package/package.json +1 -1
- package/scripts/events/index.js +1 -1
- package/scripts/events/lib/config.js +7 -1
- package/scripts/filters/pretty_urls.js +25 -0
- package/scripts/helpers/json_ld.js +5 -4
- package/scripts/helpers/pretty_url.js +22 -0
- package/scripts/tags/index.js +2 -0
- package/scripts/tags/lib/albums.js +1 -1
- package/scripts/tags/lib/chat.js +1 -1
- package/scripts/tags/lib/friends.js +1 -1
- package/scripts/tags/lib/link.js +1 -1
- package/scripts/tags/lib/posters.js +1 -1
- package/scripts/tags/lib/rating.js +32 -0
- package/scripts/tags/lib/sites.js +1 -1
- package/scripts/tags/lib/timeline.js +2 -1
- package/scripts/tags/lib/vote.js +28 -0
- package/source/css/_common/button.styl +1 -0
- package/source/css/_components/pages/article-story.styl +2 -0
- package/source/css/_components/tag-plugins/image.styl +1 -0
- package/source/css/_components/tag-plugins/rating.styl +39 -0
- package/source/css/_components/tag-plugins/timeline.styl +5 -5
- package/source/css/_components/tag-plugins/vote.styl +49 -0
- package/source/css/_components/widgets/timeline.styl +1 -1
- package/source/css/_defines/theme_base.styl +1 -0
- package/source/css/_plugins/comments/artalk.styl +51 -11
- package/source/css/_plugins/lazyload.styl +3 -0
- package/source/js/main.js +12 -5
- package/source/js/search/local-search.js +3 -1
- package/source/js/services/artalk_latest_comment.js +1 -1
- package/source/js/services/contributors.js +1 -1
- package/source/js/services/fcircle.js +1 -1
- package/source/js/services/friends.js +1 -1
- package/source/js/services/friends_and_posts.js +1 -1
- package/source/js/services/ghinfo.js +1 -1
- package/source/js/services/giscus_latest_comment.js +1 -1
- package/source/js/services/memos.js +1 -1
- package/source/js/services/rating.js +142 -0
- package/source/js/services/siteinfo.js +1 -1
- package/source/js/services/sites.js +1 -1
- package/source/js/services/timeline.js +1 -1
- package/source/js/services/twikoo_latest_comment.js +1 -1
- package/source/js/services/vote.js +113 -0
- package/source/js/services/waline_latest_comment.js +1 -1
- package/source/js/services/weibo.js +1 -1
package/source/js/main.js
CHANGED
|
@@ -229,20 +229,27 @@ const init = {
|
|
|
229
229
|
document.body.appendChild(notice);
|
|
230
230
|
}
|
|
231
231
|
if (!canonical.originalHost) return;
|
|
232
|
+
const currentURL = new URL(window.location.href);
|
|
233
|
+
const currentHost = currentURL.hostname.replace(/^www\./, '');
|
|
234
|
+
if (currentHost == 'localhost') return;
|
|
235
|
+
const encodedCurrentHost = window.btoa(currentHost);
|
|
236
|
+
const isCurrentHostValid = canonical.encoded === encodedCurrentHost;
|
|
232
237
|
const canonicalTag = document.querySelector('link[rel="canonical"]');
|
|
233
238
|
if (!canonicalTag) {
|
|
239
|
+
if (isCurrentHostValid) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (canonical.officialHosts?.includes(currentHost)) {
|
|
243
|
+
showTip(true);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
234
246
|
showTip(false);
|
|
235
247
|
return;
|
|
236
248
|
}
|
|
237
249
|
const canonicalURL = new URL(canonicalTag.href);
|
|
238
|
-
const currentURL = new URL(window.location.href);
|
|
239
250
|
const canonicalHost = canonicalURL.hostname.replace(/^www\./, '');
|
|
240
|
-
const currentHost = currentURL.hostname.replace(/^www\./, '');
|
|
241
|
-
if (currentHost == 'localhost') return;
|
|
242
251
|
const encodedCanonicalHost = window.btoa(canonicalHost);
|
|
243
|
-
const encodedCurrentHost = window.btoa(currentHost);
|
|
244
252
|
const isCanonicalHostValid = canonical.encoded === encodedCanonicalHost;
|
|
245
|
-
const isCurrentHostValid = canonical.encoded === encodedCurrentHost;
|
|
246
253
|
if (isCanonicalHostValid && isCurrentHostValid) {
|
|
247
254
|
return;
|
|
248
255
|
}
|
|
@@ -48,7 +48,9 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
48
48
|
var dataContent = data.content;
|
|
49
49
|
var dataContentLowerCase = dataContent.toLowerCase();
|
|
50
50
|
var dataUrl = data.path.startsWith('//') ? data.path.slice(1) : data.path;
|
|
51
|
-
|
|
51
|
+
dataUrl = dataUrl.replace(/\/?index\.html$/, '/'); // index.html → /
|
|
52
|
+
dataUrl = dataUrl.replace(/\.html$/, '/'); // xxx.html → xxx/
|
|
53
|
+
|
|
52
54
|
var indexTitle = -1;
|
|
53
55
|
var indexContent = -1;
|
|
54
56
|
var firstOccur = -1;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
function getRatingKey(id) {
|
|
2
|
+
return `rating-${id}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function hasRated(id) {
|
|
6
|
+
return !!localStorage.getItem(getRatingKey(id));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getRatedValue(id) {
|
|
10
|
+
return parseInt(localStorage.getItem(getRatingKey(id)) || '0');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function storeRating(id, value) {
|
|
14
|
+
localStorage.setItem(getRatingKey(id), value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function clearHover(el) {
|
|
18
|
+
el.querySelectorAll('.star').forEach(s => s.classList.remove('hover'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function updatePreview(el, avg) {
|
|
22
|
+
const rounded = Math.floor(avg);
|
|
23
|
+
el.querySelectorAll('.star').forEach(s => {
|
|
24
|
+
const v = parseInt(s.dataset.value);
|
|
25
|
+
s.classList.toggle('preview', v <= rounded);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function setupHoverEffect(el) {
|
|
30
|
+
const stars = el.querySelectorAll('.star');
|
|
31
|
+
if (!stars.length) return;
|
|
32
|
+
|
|
33
|
+
stars.forEach(star => {
|
|
34
|
+
const value = parseInt(star.dataset.value);
|
|
35
|
+
|
|
36
|
+
star.addEventListener('mouseenter', () => {
|
|
37
|
+
stars.forEach(s => {
|
|
38
|
+
s.classList.remove('preview');
|
|
39
|
+
const v = parseInt(s.dataset.value);
|
|
40
|
+
s.classList.toggle('hover', v <= value);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
star.addEventListener('mouseleave', () => {
|
|
45
|
+
clearHover(el);
|
|
46
|
+
// 恢复平均分预览
|
|
47
|
+
const avg = parseFloat(el.querySelector('.avg')?.textContent.replace(/[()]/g, '') || '0');
|
|
48
|
+
updatePreview(el, avg);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function calculateAverage(rating = {}) {
|
|
54
|
+
const validScores = Object.entries(rating).filter(([k]) => !isNaN(Number(k)));
|
|
55
|
+
const total = validScores.reduce((sum, [k, c]) => sum + Number(k) * c, 0);
|
|
56
|
+
const votes = validScores.reduce((sum, [, c]) => sum + c, 0);
|
|
57
|
+
return votes > 0 ? (total / votes).toFixed(1) : '0.0';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function loadRating(el) {
|
|
61
|
+
const id = el.dataset.id;
|
|
62
|
+
const api = el.dataset.api;
|
|
63
|
+
if (!id || !api) return;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch(`${api}/info?id=${encodeURIComponent(id)}`);
|
|
67
|
+
const data = await res.json();
|
|
68
|
+
const rating = data.rating || {};
|
|
69
|
+
const avg = calculateAverage(rating);
|
|
70
|
+
|
|
71
|
+
// 计算评分人数
|
|
72
|
+
const validScores = Object.entries(rating).filter(([k]) => !isNaN(Number(k)));
|
|
73
|
+
const totalVotes = validScores.reduce((sum, [, c]) => sum + c, 0);
|
|
74
|
+
|
|
75
|
+
// 设置平均分
|
|
76
|
+
let avgEl = el.querySelector('.avg');
|
|
77
|
+
if (!avgEl) {
|
|
78
|
+
avgEl = document.createElement('span');
|
|
79
|
+
avgEl.className = 'avg';
|
|
80
|
+
el.appendChild(avgEl);
|
|
81
|
+
}
|
|
82
|
+
avgEl.textContent = `(${avg})`;
|
|
83
|
+
|
|
84
|
+
// 设置评分人数
|
|
85
|
+
let countEl = el.querySelector('.count');
|
|
86
|
+
if (!countEl) {
|
|
87
|
+
countEl = document.createElement('span');
|
|
88
|
+
countEl.className = 'count';
|
|
89
|
+
el.appendChild(countEl);
|
|
90
|
+
}
|
|
91
|
+
countEl.textContent = `${totalVotes}`;
|
|
92
|
+
|
|
93
|
+
updatePreview(el, avg);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.warn(`[rating] 加载失败: id=${id}`, e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function submitRating(el, value) {
|
|
100
|
+
const id = el.dataset.id;
|
|
101
|
+
const api = el.dataset.api;
|
|
102
|
+
if (!id || !api || hasRated(id)) return;
|
|
103
|
+
|
|
104
|
+
storeRating(id, value);
|
|
105
|
+
el.classList.add('rated');
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await fetch(`${api}/update?id=${encodeURIComponent(id)}&value=${value}`, {
|
|
109
|
+
method: 'POST'
|
|
110
|
+
});
|
|
111
|
+
loadRating(el);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.warn(`[rating] 提交失败: id=${id}`, e);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function initRatings() {
|
|
118
|
+
document.querySelectorAll('.ds-rating').forEach(el => {
|
|
119
|
+
const { id, api } = el.dataset;
|
|
120
|
+
if (!id || !api) return;
|
|
121
|
+
|
|
122
|
+
loadRating(el);
|
|
123
|
+
setupHoverEffect(el);
|
|
124
|
+
|
|
125
|
+
if (hasRated(id)) {
|
|
126
|
+
el.classList.add('rated');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
el.querySelectorAll('.star').forEach(star => {
|
|
130
|
+
const value = star.dataset.value;
|
|
131
|
+
star.addEventListener('click', () => {
|
|
132
|
+
if (!hasRated(id)) submitRating(el, value);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (document.readyState === 'loading') {
|
|
139
|
+
window.addEventListener('DOMContentLoaded', initRatings);
|
|
140
|
+
} else {
|
|
141
|
+
initRatings();
|
|
142
|
+
}
|
|
@@ -6,7 +6,7 @@ function setCardLink(nodes) {
|
|
|
6
6
|
// If it is not a tag element then it is not processed
|
|
7
7
|
if (el.nodeType !== 1) return;
|
|
8
8
|
el.removeAttribute('cardlink');
|
|
9
|
-
const api = el.
|
|
9
|
+
const api = el.dataset.api;
|
|
10
10
|
if (api == null) return;
|
|
11
11
|
fetch(api).then(function(response) {
|
|
12
12
|
if (response.ok) {
|
|
@@ -13,7 +13,7 @@ utils.jq(() => {
|
|
|
13
13
|
const timelines = document.getElementsByClassName('ds-timeline');
|
|
14
14
|
for (var i = 0; i < timelines.length; i++) {
|
|
15
15
|
const el = timelines[i];
|
|
16
|
-
const api = el.
|
|
16
|
+
const api = el.dataset.api;
|
|
17
17
|
if (api == null) {
|
|
18
18
|
continue;
|
|
19
19
|
}
|
|
@@ -3,7 +3,7 @@ utils.jq(() => {
|
|
|
3
3
|
const el = document.querySelector('.ds-twikoo');
|
|
4
4
|
utils.onLoading(el); // 加载动画
|
|
5
5
|
|
|
6
|
-
const api = el.
|
|
6
|
+
const api = el.dataset.api;
|
|
7
7
|
const limit = parseInt(el.getAttribute('limit')) || 10;
|
|
8
8
|
const reply = el.getAttribute('hide') !== 'reply';
|
|
9
9
|
if (!api) return;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function getVoteKey(id) {
|
|
2
|
+
return `vote-${id}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function hasVoted(id) {
|
|
6
|
+
return !!localStorage.getItem(getVoteKey(id));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getVotedValue(id) {
|
|
10
|
+
return localStorage.getItem(getVoteKey(id)); // 'up' 或 'down'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function storeVote(id, value) {
|
|
14
|
+
localStorage.setItem(getVoteKey(id), value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function removeVote(id) {
|
|
18
|
+
localStorage.removeItem(getVoteKey(id));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function markVoted(el, value) {
|
|
22
|
+
el.classList.add('voted');
|
|
23
|
+
if (value === 'up') {
|
|
24
|
+
el.querySelector('.vote-up')?.classList.add('active');
|
|
25
|
+
} else if (value === 'down') {
|
|
26
|
+
el.querySelector('.vote-down')?.classList.add('active');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function revertVote(el, value) {
|
|
31
|
+
const id = el.dataset.id;
|
|
32
|
+
|
|
33
|
+
// 回退数值
|
|
34
|
+
if (value === 'up') {
|
|
35
|
+
const upEl = el.querySelector('.up');
|
|
36
|
+
if (upEl) upEl.textContent = Math.max(0, parseInt(upEl.textContent || '1') - 1);
|
|
37
|
+
} else if (value === 'down') {
|
|
38
|
+
const downEl = el.querySelector('.down');
|
|
39
|
+
if (downEl) downEl.textContent = Math.max(0, parseInt(downEl.textContent || '1') - 1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 回退样式和状态
|
|
43
|
+
el.classList.remove('voted', 'active');
|
|
44
|
+
el.querySelector('.vote-up')?.classList.remove('active');
|
|
45
|
+
el.querySelector('.vote-down')?.classList.remove('active');
|
|
46
|
+
removeVote(id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function loadVote(el) {
|
|
50
|
+
const id = el.dataset.id;
|
|
51
|
+
const api = el.dataset.api;
|
|
52
|
+
if (!id || !api) return;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(`${api}/info?id=${encodeURIComponent(id)}`);
|
|
56
|
+
const data = await res.json();
|
|
57
|
+
|
|
58
|
+
el.querySelector('.up').textContent = data.votes?.up ?? 0;
|
|
59
|
+
el.querySelector('.down').textContent = data.votes?.down ?? 0;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.warn(`[vote] 加载失败: id=${id}`, e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function submitVote(el, value) {
|
|
66
|
+
const id = el.dataset.id;
|
|
67
|
+
const api = el.dataset.api;
|
|
68
|
+
if (!id || !api || hasVoted(id)) return;
|
|
69
|
+
|
|
70
|
+
const upEl = el.querySelector('.up');
|
|
71
|
+
const downEl = el.querySelector('.down');
|
|
72
|
+
|
|
73
|
+
// 乐观更新
|
|
74
|
+
if (value === 'up' && upEl) upEl.textContent = parseInt(upEl.textContent || '0') + 1;
|
|
75
|
+
if (value === 'down' && downEl) downEl.textContent = parseInt(downEl.textContent || '0') + 1;
|
|
76
|
+
|
|
77
|
+
storeVote(id, value);
|
|
78
|
+
markVoted(el, value);
|
|
79
|
+
|
|
80
|
+
// 后台同步
|
|
81
|
+
fetch(`${api}/update?id=${encodeURIComponent(id)}&value=${encodeURIComponent(value)}`, {
|
|
82
|
+
method: 'POST'
|
|
83
|
+
}).catch(e => {
|
|
84
|
+
console.warn(`[vote] 后台同步失败,撤销投票: id=${id}`, e);
|
|
85
|
+
revertVote(el, value);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function initVotes() {
|
|
90
|
+
document.querySelectorAll('.ds-vote').forEach(el => {
|
|
91
|
+
const { id, api } = el.dataset;
|
|
92
|
+
if (!id || !api) return;
|
|
93
|
+
|
|
94
|
+
loadVote(el);
|
|
95
|
+
|
|
96
|
+
const votedValue = getVotedValue(id);
|
|
97
|
+
if (votedValue) markVoted(el, votedValue);
|
|
98
|
+
|
|
99
|
+
el.querySelector('.vote-up')?.addEventListener('click', () => {
|
|
100
|
+
if (!el.classList.contains('active')) submitVote(el, 'up');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
el.querySelector('.vote-down')?.addEventListener('click', () => {
|
|
104
|
+
if (!el.classList.contains('active')) submitVote(el, 'down');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (document.readyState === 'loading') {
|
|
110
|
+
window.addEventListener('DOMContentLoaded', initVotes);
|
|
111
|
+
} else {
|
|
112
|
+
initVotes();
|
|
113
|
+
}
|