hexo-theme-stellar 1.32.4 → 1.33.1
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 +24 -14
- 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/_plugins/mathjax.ejs +47 -18
- 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/md.styl +2 -0
- package/source/css/_components/pages/article-story.styl +2 -0
- package/source/css/_components/partial/navbar.styl +2 -2
- 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/_custom.styl +4 -2
- package/source/css/_defines/func.styl +16 -8
- package/source/css/_defines/theme_base.styl +2 -1
- 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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.tag-plugin.ds-vote
|
|
2
|
+
text-align: center
|
|
3
|
+
.tag-plugin.ds-vote .header
|
|
4
|
+
margin: .5rem
|
|
5
|
+
font-weight: 500
|
|
6
|
+
color: var(--text)
|
|
7
|
+
.tag-plugin.ds-vote .body
|
|
8
|
+
width 50%
|
|
9
|
+
min-width: 240px
|
|
10
|
+
max-width: 320px
|
|
11
|
+
margin: 1rem auto
|
|
12
|
+
display: grid
|
|
13
|
+
grid-template-columns: 1fr 1fr
|
|
14
|
+
grid-gap: 2rem
|
|
15
|
+
button
|
|
16
|
+
color: var(--text-p1)
|
|
17
|
+
font-size: 1rem
|
|
18
|
+
display: inline-flex
|
|
19
|
+
flex-direction: column
|
|
20
|
+
align-items: center
|
|
21
|
+
justify-content: center
|
|
22
|
+
padding: .5rem
|
|
23
|
+
background: var(--block)
|
|
24
|
+
border-radius: $border-button
|
|
25
|
+
font-weight: 500
|
|
26
|
+
svg
|
|
27
|
+
font-size: 1.5em
|
|
28
|
+
margin: 8px
|
|
29
|
+
span
|
|
30
|
+
line-height: 1
|
|
31
|
+
font-size: 1em
|
|
32
|
+
margin-bottom: 4px
|
|
33
|
+
trans1 all
|
|
34
|
+
color: var(--theme)
|
|
35
|
+
background: var(--theme-a10)
|
|
36
|
+
@media (prefers-color-scheme: dark)
|
|
37
|
+
background: var(--theme-a20)
|
|
38
|
+
&.vote-up
|
|
39
|
+
x-set-theme-with-color($c-green)
|
|
40
|
+
&.vote-down
|
|
41
|
+
x-set-theme-with-color($c-red)
|
|
42
|
+
&:hover,&.active
|
|
43
|
+
color: white
|
|
44
|
+
background: var(--theme)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// 已经投过票了
|
|
48
|
+
.tag-plugin.ds-vote.voted
|
|
49
|
+
pointer-events: none
|
package/source/css/_custom.styl
CHANGED
|
@@ -35,8 +35,10 @@ $leftbar-background-color = hexo-config('style.leftbar.background-color')
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
// colors
|
|
38
|
-
$c-theme =
|
|
39
|
-
$c-accent =
|
|
38
|
+
$c-theme = convert(hexo-config('style.color.theme'))
|
|
39
|
+
$c-accent = convert(hexo-config('style.color.accent'))
|
|
40
|
+
$c-link = convert(hexo-config('style.color.link'))
|
|
41
|
+
|
|
40
42
|
$c-base-hue = 210deg // 背景/文本颜色的主基调(一般看不出来不需要修改)
|
|
41
43
|
|
|
42
44
|
// icons
|
|
@@ -136,6 +136,13 @@ hoverable-card()
|
|
|
136
136
|
newblur()
|
|
137
137
|
box-shadow: 0 0 2px rgba(black, .04), 0 2px 8px rgba(black, .04), 0 4px 16px rgba(black, .04)
|
|
138
138
|
trans1: all
|
|
139
|
+
text-shadow: 0 0 4px rgba(white, 1), 0 0 8px rgba(white, 0.8), 0 0 16px rgba(white, 0.6)
|
|
140
|
+
@media (prefers-color-scheme: dark)
|
|
141
|
+
text-shadow: 0 0 2px rgba(black, 0.2), 0 0 4px rgba(black, 0.4)
|
|
142
|
+
nav a:not(.active,:hover)
|
|
143
|
+
filter: drop-shadow(0 0 0 white) drop-shadow(0 0 4px white)
|
|
144
|
+
@media (prefers-color-scheme: dark)
|
|
145
|
+
filter: none
|
|
139
146
|
&:before,&:after
|
|
140
147
|
z-index -1
|
|
141
148
|
position: absolute
|
|
@@ -149,17 +156,18 @@ newblur()
|
|
|
149
156
|
&:before
|
|
150
157
|
backdrop-filter: blur(8px)
|
|
151
158
|
-webkit-backdrop-filter: blur(8px)
|
|
152
|
-
background:
|
|
159
|
+
background: rgba(white, 0.3)
|
|
160
|
+
margin: 1px
|
|
161
|
+
@media (prefers-color-scheme: dark)
|
|
162
|
+
background: rgba(white, 0.2)
|
|
153
163
|
&:after
|
|
154
|
-
--blur-px:
|
|
155
|
-
--blur-sat:
|
|
156
|
-
background: var(--bg-a50)
|
|
164
|
+
--blur-px: 16px
|
|
165
|
+
--blur-sat: 300%
|
|
157
166
|
backdrop-filter: saturate(var(--blur-sat)) blur(var(--blur-px))
|
|
158
167
|
-webkit-backdrop-filter: saturate(var(--blur-sat)) blur(var(--blur-px))
|
|
159
|
-
mask: linear-gradient(
|
|
160
|
-
-webkit-mask: linear-gradient(
|
|
168
|
+
mask: linear-gradient(white, 20%, transparent, 80%, white), linear-gradient(90deg, white, 20%, transparent, 80%, white)
|
|
169
|
+
-webkit-mask-image: linear-gradient(white, 20%, transparent, 80%, white), linear-gradient(90deg, white, 20%, transparent, 80%, white)
|
|
161
170
|
box-shadow: inset 0 0 4px 1px rgba(white, 0.5)
|
|
162
171
|
trans1 all
|
|
163
172
|
@media (prefers-color-scheme: dark)
|
|
164
|
-
|
|
165
|
-
background: var(--bg-a20)
|
|
173
|
+
box-shadow: inset 0 0 2px 1px rgba(white, 0.2)
|
|
@@ -13,6 +13,7 @@ x-set-theme-with-color($color)
|
|
|
13
13
|
--light: lightness($color)
|
|
14
14
|
--alpha: alpha($color)
|
|
15
15
|
--theme: x-theme-alpha(1)
|
|
16
|
+
--theme-a10: x-theme-alpha(0.1)
|
|
16
17
|
--theme-a20: x-theme-alpha(0.2)
|
|
17
18
|
--theme-a30: x-theme-alpha(0.3)
|
|
18
19
|
|
|
@@ -90,7 +91,7 @@ dynamic-theme-dark()
|
|
|
90
91
|
// 强调色
|
|
91
92
|
--accent: $c-accent
|
|
92
93
|
// 链接
|
|
93
|
-
x-set-link-with-color(
|
|
94
|
+
x-set-link-with-color($c-link)
|
|
94
95
|
|
|
95
96
|
// dynamic colors
|
|
96
97
|
dynamic-theme-light()
|
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
height: 3rem
|
|
70
70
|
width: 12rem
|
|
71
71
|
margin: -1.5rem auto 0
|
|
72
|
+
&:hover
|
|
73
|
+
background: var(--card)
|
|
74
|
+
.atk-list-footer .atk-copyright
|
|
75
|
+
padding: 1rem
|
|
72
76
|
.atk-error-layer
|
|
73
77
|
background: none
|
|
74
78
|
|
|
@@ -97,17 +101,52 @@
|
|
|
97
101
|
--at-color-green: $c-green
|
|
98
102
|
--at-color-gradient: linear-gradient(180deg, transparent, var(--card), 50%, var(--card))
|
|
99
103
|
|
|
100
|
-
.md-text .atk-comment>.atk-main>.atk-body
|
|
101
|
-
overflow: visible
|
|
102
|
-
.md-text .artalk pre code, .atk-layer-wrap pre code
|
|
103
|
-
background: none
|
|
104
|
-
.md-text .atk-main-editor>.atk-textarea-wrap>.atk-textarea
|
|
105
|
-
background: none
|
|
106
|
-
padding: 0.5rem 1rem
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
// fix style
|
|
106
|
+
.cmt-body.artalk
|
|
107
|
+
.atk-comment>.atk-main>.atk-body
|
|
108
|
+
overflow: visible
|
|
109
|
+
.artalk pre code, .atk-layer-wrap pre code
|
|
110
|
+
background: none
|
|
111
|
+
.atk-main-editor>.atk-textarea-wrap>.atk-textarea
|
|
112
|
+
background: none
|
|
113
|
+
padding: 1rem
|
|
114
|
+
margin: 0
|
|
115
|
+
border: none
|
|
116
|
+
|
|
117
|
+
.atk-loading
|
|
118
|
+
border-radius: $border-card
|
|
119
|
+
.atk-main-editor .atk-editor-user-wrap .atk-editor-user .atk-user-profile-btn
|
|
120
|
+
padding: 0
|
|
121
|
+
&:hover
|
|
122
|
+
background: none
|
|
123
|
+
|
|
124
|
+
.artalk>.atk-list>.atk-list-header
|
|
125
|
+
padding: .5rem 1rem
|
|
126
|
+
.atk-comment-count
|
|
127
|
+
display: flex
|
|
128
|
+
align-items: baseline
|
|
129
|
+
.artalk>.atk-list>.atk-list-header .atk-dropdown-wrap .atk-arrow-down-icon
|
|
130
|
+
height: 8px
|
|
131
|
+
.artalk>.atk-list>.atk-list-header .atk-text
|
|
132
|
+
display: block
|
|
133
|
+
|
|
134
|
+
.atk-notify-wrap .atk-notify
|
|
135
|
+
border-radius: $border-bar
|
|
136
|
+
margin: 8px
|
|
137
|
+
|
|
138
|
+
// emoticons
|
|
139
|
+
.atk-editor-plug-emoticons>.atk-grp-wrap>.atk-grp
|
|
140
|
+
padding: .5rem
|
|
141
|
+
margin-bottom: 32px
|
|
142
|
+
.atk-editor-plug-emoticons>.atk-grp-wrap>.atk-grp[data-type=image]>.atk-item
|
|
143
|
+
width 3rem
|
|
144
|
+
height: 3rem
|
|
145
|
+
margin: 0
|
|
146
|
+
border-radius: 4px
|
|
147
|
+
padding: .5rem
|
|
148
|
+
>img
|
|
149
|
+
border-radius: 4px
|
|
111
150
|
|
|
112
151
|
|
|
113
152
|
// override
|
|
@@ -121,11 +160,12 @@
|
|
|
121
160
|
border-radius: $border-image-s
|
|
122
161
|
a>img
|
|
123
162
|
display: inline
|
|
124
|
-
|
|
163
|
+
img[atk-emoticon]
|
|
125
164
|
max-height: 1.5em
|
|
126
165
|
transform: scale(1.2)
|
|
127
166
|
margin: 0 4px
|
|
128
167
|
vertical-align: bottom
|
|
168
|
+
border-radius: 4px
|
|
129
169
|
ul li, ol li
|
|
130
170
|
font-size: $fs-14
|
|
131
171
|
blockquote
|
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
|
+
}
|