hexo-theme-stellar 1.40.0 → 1.42.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/.claude/skills/stellar-theme-dev/SKILL.md +1 -2
- package/CHANGELOG.md +90 -0
- package/CONTRIBUTING.md +2 -1
- package/README.md +50 -21
- package/README_EN.md +113 -0
- package/_config.yml +40 -12
- package/_data/icons.yml +248 -37
- package/languages/en.yml +6 -0
- package/languages/zh-CN.yml +6 -0
- package/languages/zh-TW.yml +6 -0
- package/layout/_partial/comments/artalk/script.ejs +25 -8
- package/layout/_partial/comments/beaudar/script.ejs +1 -0
- package/layout/_partial/comments/twikoo/script.ejs +1 -0
- package/layout/_partial/comments/utterances/script.ejs +1 -0
- package/layout/_partial/comments/waline/script.ejs +1 -0
- package/layout/_partial/cover/index.ejs +0 -3
- package/layout/_partial/head.ejs +43 -7
- package/layout/_partial/main/article/article_tags.ejs +16 -0
- package/layout/_partial/main/navbar/article_banner.ejs +33 -20
- package/layout/_partial/main/notebook/note_card.ejs +1 -1
- package/layout/_partial/main/notebook/paginator.ejs +2 -2
- package/layout/_partial/main/pin_slider.ejs +61 -30
- package/layout/_partial/main/post_list/latest_post_card.ejs +25 -0
- package/layout/_partial/main/post_list/paginator.ejs +2 -2
- package/layout/_partial/main/post_list/post_card.ejs +16 -64
- package/layout/_partial/main/post_list/topic_card.ejs +26 -5
- package/layout/_partial/menubtn.ejs +2 -2
- package/layout/_partial/scripts/bootstrap.ejs +35 -0
- package/layout/_partial/scripts/defines.ejs +27 -2
- package/layout/_partial/scripts.ejs +39 -7
- package/layout/_partial/sidebar/logo.ejs +1 -1
- package/layout/_partial/sidebar/menu.ejs +1 -1
- package/layout/_partial/sidebar/search.ejs +1 -1
- package/layout/_partial/widgets/author.ejs +1 -1
- package/layout/_partial/widgets/components/link.ejs +2 -2
- package/layout/_partial/widgets/ghuser.ejs +1 -1
- package/layout/_partial/widgets/related.ejs +1 -1
- package/layout/_partial/widgets/toc.ejs +3 -3
- package/layout/_partial/widgets/tree.ejs +1 -1
- package/layout/_plugins/adaptive_text.ejs +11 -0
- package/layout/_plugins/copycode.ejs +2 -2
- package/layout/_plugins/fancybox.ejs +4 -2
- package/layout/_plugins/mermaid.ejs +3 -2
- package/layout/_plugins/scrollreveal.ejs +22 -16
- package/layout/_plugins/search/local_search.ejs +4 -3
- package/layout/_plugins/swiper.ejs +2 -1
- package/layout/categories.ejs +1 -1
- package/layout/index.ejs +10 -2
- package/layout/index_topic.ejs +4 -4
- package/layout/page.ejs +3 -0
- package/layout/tags.ejs +1 -0
- package/package.json +3 -3
- package/scripts/events/lib/doc_tree.js +12 -208
- package/scripts/events/lib/notebooks.js +8 -148
- package/scripts/events/lib/utils.js +10 -1
- package/scripts/filters/index.js +1 -0
- package/scripts/filters/lib/img_lazyload.js +4 -0
- package/scripts/filters/lib/img_onerror.js +4 -0
- package/scripts/filters/lib/md_table.js +46 -0
- package/scripts/generators/search.js +12 -18
- package/scripts/generators/stellar-icons.js +47 -0
- package/scripts/helpers/ai_label.js +29 -0
- package/scripts/helpers/icon.js +2 -2
- package/scripts/helpers/json_ld.js +10 -13
- package/scripts/helpers/related_posts.js +0 -7
- package/scripts/helpers/seo.js +9 -0
- package/scripts/helpers/subtitle.js +8 -0
- package/scripts/lib/ai_label.js +55 -0
- package/scripts/lib/doc_tree.js +267 -0
- package/scripts/lib/notebooks.js +181 -0
- package/scripts/lib/search_index.js +82 -0
- package/scripts/lib/seo.js +79 -0
- package/scripts/lib/subtitle.js +24 -0
- package/scripts/tags/lib/about.js +2 -2
- package/scripts/tags/lib/albums.js +1 -1
- package/scripts/tags/lib/banner.js +5 -4
- package/scripts/tags/lib/chat.js +49 -49
- package/scripts/tags/lib/copy.js +1 -1
- package/scripts/tags/lib/friends.js +1 -1
- package/scripts/tags/lib/gallery.js +1 -1
- package/scripts/tags/lib/hashtag.js +1 -1
- package/scripts/tags/lib/image.js +3 -3
- package/scripts/tags/lib/posters.js +1 -1
- package/scripts/tags/lib/sites.js +2 -2
- package/source/css/_common/base.styl +5 -9
- package/source/css/_common/cover-overlay.styl +111 -0
- package/source/css/_common/device.styl +3 -3
- package/source/css/_common/title.styl +17 -0
- package/source/css/_components/layout.styl +9 -13
- package/source/css/_components/list.styl +135 -64
- package/source/css/_components/main.styl +2 -2
- package/source/css/_components/md-table.styl +23 -0
- package/source/css/_components/pages/archives.styl +5 -22
- package/source/css/_components/pages/article-story.styl +5 -14
- package/source/css/_components/partial/article-banner.styl +61 -37
- package/source/css/_components/partial/article-tags.styl +13 -0
- package/source/css/_components/partial/bread-nav.styl +19 -12
- package/source/css/_components/partial/navbar.styl +11 -3
- package/source/css/_components/partial/post-panel.styl +76 -0
- package/source/css/_components/pin-slider.styl +38 -41
- package/source/css/_components/sidebar/footer.styl +1 -1
- package/source/css/_components/sidebar/logo.styl +2 -2
- package/source/css/_components/sidebar/search.styl +25 -5
- package/source/css/_components/sidebar/sidebar.styl +7 -7
- package/source/css/_components/tag-plugins/banner.styl +53 -11
- package/source/css/_components/tag-plugins/friends_posts.styl +5 -86
- package/source/css/_components/tag-plugins/gallery.styl +3 -0
- package/source/css/_components/tag-plugins/read/paper.styl +3 -3
- package/source/css/_components/tag-plugins/read/reel.styl +4 -4
- package/source/css/_components/tag-plugins/table.styl +16 -11
- package/source/css/_components/tag-plugins/timeline.styl +4 -0
- package/source/css/_components/widgets/components.styl +6 -0
- package/source/css/_components/widgets/list.styl +6 -0
- package/source/css/_components/widgets/timeline.styl +2 -2
- package/source/css/_components/widgets/toc.styl +13 -7
- package/source/css/_components/widgets/widgets.styl +8 -7
- package/source/css/_custom.styl +9 -21
- package/source/css/_defines/func.styl +28 -3
- package/source/css/_plugins/index.styl +0 -18
- package/source/css/_plugins/lazyload.styl +1 -0
- package/source/css/{_plugins/comments → comments}/artalk.styl +4 -0
- package/source/css/{_plugins/comments → comments}/twikoo.styl +4 -0
- package/source/css/{_plugins → plugins}/mermaid.styl +3 -0
- package/source/css/{_plugins → plugins}/swiper.styl +5 -1
- package/source/js/color.js +325 -0
- package/source/js/icons.js +60 -0
- package/source/js/main.js +115 -1
- package/source/js/plugins/adaptive-text.js +146 -0
- package/source/js/search/highlight.js +82 -0
- package/source/js/search/local-search.js +294 -103
- package/source/js/services/friends_and_posts.js +1 -1
- package/source/js/services/timeline.js +1 -1
- package/source/js/services/weibo.js +3 -3
- package/source/js/services.js +179 -0
- package/source/js/tagtree.js +29 -0
- package/source/js/theme.js +61 -0
- package/{layout/_partial/scripts/utils.ejs → source/js/utils.js} +27 -11
- package/layout/_partial/cover/post_cover.ejs +0 -37
- package/layout/_partial/scripts/services.ejs +0 -178
- package/layout/_partial/scripts/sidebar.ejs +0 -25
- package/layout/_partial/scripts/tagtree.ejs +0 -29
- package/layout/_partial/scripts/theme.ejs +0 -67
- /package/source/css/{_plugins/comments → comments}/beaudar.styl +0 -0
- /package/source/css/{_plugins/comments → comments}/utterances.styl +0 -0
- /package/source/css/{_plugins/comments → comments}/waline.styl +0 -0
- /package/source/css/{_plugins → plugins}/fancybox.styl +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const initServices = () => {
|
|
2
|
+
// 用于存储需要清理的资源
|
|
3
|
+
let timers = [];
|
|
4
|
+
|
|
5
|
+
for (let id of Object.keys(ctx.services)) {
|
|
6
|
+
const js = ctx.services[id].js;
|
|
7
|
+
if (id == 'siteinfo') {
|
|
8
|
+
const cardlinks = document.querySelectorAll('a.link-card[cardlink]');
|
|
9
|
+
if (cardlinks?.length > 0) {
|
|
10
|
+
utils.js(js, { defer: true }).then(function () {
|
|
11
|
+
setCardLink(cardlinks);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
} else if (id == 'ghinfo') {
|
|
15
|
+
const els = document.querySelectorAll('.ds-ghinfo');
|
|
16
|
+
if (els.length > 0) {
|
|
17
|
+
utils.js(js, { defer: true });
|
|
18
|
+
}
|
|
19
|
+
} else if (id == 'voice') {
|
|
20
|
+
const voiceAudios = document.querySelectorAll('.voice>audio');
|
|
21
|
+
if (voiceAudios?.length > 0) {
|
|
22
|
+
utils.js(js, { defer: true }).then(function () {
|
|
23
|
+
createVoiceDom(voiceAudios);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
} else if (id == 'video') {
|
|
27
|
+
const videos = document.querySelectorAll('.video>video');
|
|
28
|
+
if (videos?.length > 0) {
|
|
29
|
+
utils.js(js, { defer: true }).then(function () {
|
|
30
|
+
videoEvents(videos);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
} else if (id == 'download-file') {
|
|
34
|
+
const files = document.querySelectorAll('.chat-file');
|
|
35
|
+
if (files?.length > 0) {
|
|
36
|
+
utils.js(js, { defer: true }).then(function () {
|
|
37
|
+
downloadFileEvent(files);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
const els = document.getElementsByClassName(`ds-${id}`);
|
|
42
|
+
if (els?.length > 0) {
|
|
43
|
+
if (id == 'timeline' || id == 'memos' || id == 'marked' || id == 'mdrender') {
|
|
44
|
+
utils.js(deps.marked).then(function () {
|
|
45
|
+
utils.js(js, { defer: true });
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
utils.js(js, { defer: true });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// search
|
|
55
|
+
if (ctx.search && typeof searchFunc === 'function') {
|
|
56
|
+
const searchCfg = ctx.search.local_search;
|
|
57
|
+
const lazyLoad = searchCfg ? searchCfg.lazy_load !== false : true;
|
|
58
|
+
// 懒加载模式下由 local-search.js 的聚焦事件负责初始化,此处跳过
|
|
59
|
+
if (!lazyLoad) {
|
|
60
|
+
const inputArea = document.querySelector("input#search-input");
|
|
61
|
+
if (inputArea && !inputArea._searchInitialized) {
|
|
62
|
+
const path = ctx.search.path.startsWith('/') ? ctx.root + ctx.search.path.substring(1) : ctx.root + ctx.search.path;
|
|
63
|
+
const filter = inputArea.getAttribute('data-filter') || '';
|
|
64
|
+
searchFunc(path, filter, 'search-wrapper', 'search-input', 'search-result');
|
|
65
|
+
// searchFunc 内部管理 _searchInitialized(pending → true),此处不再覆盖
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// chat iphone time
|
|
71
|
+
let phoneTimes = document.querySelectorAll('.chat .status-bar .time');
|
|
72
|
+
let firstAdjustInterval = null;
|
|
73
|
+
let mainInterval = null;
|
|
74
|
+
|
|
75
|
+
if (phoneTimes.length > 0) {
|
|
76
|
+
NowTime();
|
|
77
|
+
const date = new Date();
|
|
78
|
+
const sec = date.getSeconds();
|
|
79
|
+
firstAdjustInterval = setInterval(firstAdjustTime, 1000 * (60 - sec));
|
|
80
|
+
timers.push(firstAdjustInterval);
|
|
81
|
+
|
|
82
|
+
function firstAdjustTime() {
|
|
83
|
+
NowTime();
|
|
84
|
+
if (firstAdjustInterval) {
|
|
85
|
+
clearInterval(firstAdjustInterval);
|
|
86
|
+
firstAdjustInterval = null;
|
|
87
|
+
}
|
|
88
|
+
mainInterval = setInterval(NowTime, 1000 * 60);
|
|
89
|
+
timers.push(mainInterval);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function NowTime() {
|
|
93
|
+
for (let i = 0; i < phoneTimes.length; ++i) {
|
|
94
|
+
const timeSpan = phoneTimes[i];
|
|
95
|
+
const date = new Date();
|
|
96
|
+
const hour = date.getHours();
|
|
97
|
+
const min = date.getMinutes();
|
|
98
|
+
timeSpan.innerHTML = check(hour) + ":" + check(min);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function check(val) {
|
|
103
|
+
if (val < 10) {
|
|
104
|
+
return ("0" + val);
|
|
105
|
+
}
|
|
106
|
+
return (val);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// chat quote - 存储事件监听器以便清理
|
|
111
|
+
const quoteClickHandlers = new Map();
|
|
112
|
+
const chat_quote_obverser = new IntersectionObserver((entries, observer) => {
|
|
113
|
+
entries.filter((entry) => { return entry.isIntersecting }).sort((a, b) => a.intersectionRect.y !== b.intersectionRect.y ? a.intersectionRect.y - b.intersectionRect.y : a.intersectionRect.x - b.intersectionRect.x).forEach((entry, index) => {
|
|
114
|
+
observer.unobserve(entry.target);
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
entry.target.classList.add('quote-blink');
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
entry.target.classList.remove('quote-blink');
|
|
119
|
+
}, 1000);
|
|
120
|
+
}, Math.max(100, 16) * (index + 1));
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
var chatQuotes = document.querySelectorAll(".chat .talk .quote");
|
|
125
|
+
chatQuotes.forEach((quote) => {
|
|
126
|
+
const handler = function () {
|
|
127
|
+
var chatCellDom = document.getElementById("quote-" + quote.getAttribute("quotedCellTag"));
|
|
128
|
+
if (chatCellDom) {
|
|
129
|
+
var chatDiv = chatCellDom.parentElement;
|
|
130
|
+
var mid = chatDiv.clientHeight / 2;
|
|
131
|
+
var offsetTop = chatCellDom.offsetTop;
|
|
132
|
+
if (offsetTop > mid - chatCellDom.clientHeight / 2) {
|
|
133
|
+
chatDiv.scrollTo({
|
|
134
|
+
top: chatCellDom.offsetTop - mid + chatCellDom.clientHeight / 2,
|
|
135
|
+
behavior: "smooth"
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
chatDiv.scrollTo({
|
|
139
|
+
top: 0,
|
|
140
|
+
behavior: "smooth"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
chat_quote_obverser.observe(chatCellDom);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
quote.addEventListener('click', handler);
|
|
147
|
+
quoteClickHandlers.set(quote, handler); // 保存处理器引用
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// 返回清理函数,用于清理定时器和观察器
|
|
151
|
+
return () => {
|
|
152
|
+
// 清理所有定时器(包括可能未完成的 firstAdjustInterval)
|
|
153
|
+
timers.forEach(timer => {
|
|
154
|
+
if (timer) clearInterval(timer);
|
|
155
|
+
});
|
|
156
|
+
timers = [];
|
|
157
|
+
if (firstAdjustInterval) {
|
|
158
|
+
clearInterval(firstAdjustInterval);
|
|
159
|
+
firstAdjustInterval = null;
|
|
160
|
+
}
|
|
161
|
+
if (mainInterval) {
|
|
162
|
+
clearInterval(mainInterval);
|
|
163
|
+
mainInterval = null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 断开观察器
|
|
167
|
+
if (chat_quote_obverser) {
|
|
168
|
+
chat_quote_obverser.disconnect();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 移除所有 click 事件监听器
|
|
172
|
+
quoteClickHandlers.forEach((handler, quote) => {
|
|
173
|
+
quote.removeEventListener('click', handler);
|
|
174
|
+
});
|
|
175
|
+
quoteClickHandlers.clear();
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
stellar.initPlugin(initServices, 'services');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const tagtreeConf = window.__STELLAR_TAGTREE__ || {};
|
|
3
|
+
|
|
4
|
+
const tagSwitchers = document.querySelectorAll('.tag-subtree.parent-tag > a > .tag-switcher-wrapper')
|
|
5
|
+
for (const tagSwitcher of tagSwitchers) {
|
|
6
|
+
tagSwitcher.addEventListener('click', (e) => {
|
|
7
|
+
const parent = e.target.closest('.tag-subtree.parent-tag')
|
|
8
|
+
parent.classList.toggle('expanded')
|
|
9
|
+
e.preventDefault()
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Get active tag from query string, then activate it.
|
|
14
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
15
|
+
const activeTag = urlParams.get('tag')
|
|
16
|
+
if (activeTag) {
|
|
17
|
+
let tag = document.querySelector(`.tag-subtree[data-tag="${activeTag}"]`)
|
|
18
|
+
if (tag) {
|
|
19
|
+
tag.querySelector('a').classList.add('active')
|
|
20
|
+
if (!tagtreeConf.expand_active) {
|
|
21
|
+
tag = tag.parentElement.closest('.tag-subtree.parent-tag')
|
|
22
|
+
}
|
|
23
|
+
while (tag) {
|
|
24
|
+
tag.classList.add('expanded')
|
|
25
|
+
tag = tag.parentElement.closest('.tag-subtree.parent-tag')
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const applyTheme = (theme) => {
|
|
2
|
+
if (theme === 'auto') {
|
|
3
|
+
document.documentElement.removeAttribute('data-theme')
|
|
4
|
+
} else {
|
|
5
|
+
document.documentElement.setAttribute('data-theme', theme)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// applyThemeToGiscus(theme)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// FIXME: 这会导致无法使用 preferred_color_scheme 以外的主题
|
|
12
|
+
const applyThemeToGiscus = (theme) => {
|
|
13
|
+
// theme = theme === 'auto' ? 'preferred_color_scheme' : theme
|
|
14
|
+
const cmt = document.getElementById('giscus')
|
|
15
|
+
if (cmt) {
|
|
16
|
+
// This works before giscus load.
|
|
17
|
+
cmt.setAttribute('data-theme', theme)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const iframe = document.querySelector('#comments > section.giscus > iframe')
|
|
21
|
+
if (iframe) {
|
|
22
|
+
// This works after giscus loaded.
|
|
23
|
+
const src = iframe.src
|
|
24
|
+
const newSrc = src.replace(/theme=[\w]+/, `theme=${theme}`)
|
|
25
|
+
iframe.src = newSrc
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const switchTheme = () => {
|
|
30
|
+
// light -> dark -> auto -> light -> ...
|
|
31
|
+
const currentTheme = document.documentElement.getAttribute('data-theme')
|
|
32
|
+
let newTheme;
|
|
33
|
+
switch (currentTheme) {
|
|
34
|
+
case 'light':
|
|
35
|
+
newTheme = 'dark'
|
|
36
|
+
break
|
|
37
|
+
case 'dark':
|
|
38
|
+
newTheme = 'auto'
|
|
39
|
+
break
|
|
40
|
+
default:
|
|
41
|
+
newTheme = 'light'
|
|
42
|
+
}
|
|
43
|
+
applyTheme(newTheme)
|
|
44
|
+
window.localStorage.setItem('Stellar.theme', newTheme)
|
|
45
|
+
utils.dark.mode = newTheme === 'auto' ? (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") : newTheme;
|
|
46
|
+
utils.dark.method.toggle.start();
|
|
47
|
+
|
|
48
|
+
const messages = window.__STELLAR_I18N__ || {};
|
|
49
|
+
hud?.toast?.(messages[newTheme])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
(() => {
|
|
53
|
+
// Apply user's preferred theme, if any.
|
|
54
|
+
const theme = window.localStorage.getItem('Stellar.theme')
|
|
55
|
+
if (theme !== null) {
|
|
56
|
+
applyTheme(theme)
|
|
57
|
+
} else {
|
|
58
|
+
utils.dark.mode = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
59
|
+
}
|
|
60
|
+
utils.dark.method.toggle.start();
|
|
61
|
+
})()
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
(function () {
|
|
2
|
+
// 防重复执行:utils.js 可能被重复加载,二次执行直接跳过,避免重复声明报错。
|
|
3
|
+
if (window.__stellarUtilsLoaded) return;
|
|
4
|
+
window.__stellarUtilsLoaded = true;
|
|
5
|
+
|
|
3
6
|
function RunItem() {
|
|
4
7
|
this.list = []; // 存放回调函数
|
|
5
8
|
this.start = () => {
|
|
@@ -63,7 +66,7 @@
|
|
|
63
66
|
var sheets = doc.styleSheets;
|
|
64
67
|
if (attributes) {
|
|
65
68
|
for (var attributeName in attributes) {
|
|
66
|
-
if (
|
|
69
|
+
if (Object.prototype.hasOwnProperty.call(attributes, attributeName)) {
|
|
67
70
|
ss.setAttribute(attributeName, attributes[attributeName]);
|
|
68
71
|
}
|
|
69
72
|
}
|
|
@@ -296,7 +299,7 @@
|
|
|
296
299
|
onLoading: (el) => {
|
|
297
300
|
if (el) {
|
|
298
301
|
if (el.querySelector('.loading-wrap') === null) {
|
|
299
|
-
el.insertAdjacentHTML('beforeend',
|
|
302
|
+
el.insertAdjacentHTML('beforeend', `<div class="loading-wrap">${ctx.icons['default:loading-spinner']}</div>`);
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
305
|
},
|
|
@@ -312,7 +315,7 @@
|
|
|
312
315
|
if (wrap) {
|
|
313
316
|
var svg = wrap.querySelector('svg');
|
|
314
317
|
if (svg) svg.remove();
|
|
315
|
-
wrap.insertAdjacentHTML('beforeend',
|
|
318
|
+
wrap.insertAdjacentHTML('beforeend', ctx.icons['default:warning']);
|
|
316
319
|
wrap.classList.add('error');
|
|
317
320
|
}
|
|
318
321
|
}
|
|
@@ -533,11 +536,13 @@
|
|
|
533
536
|
if (timedOut) return;
|
|
534
537
|
// 标记元素实例为已加载
|
|
535
538
|
if (el) utils._loadedElements.add(el);
|
|
536
|
-
//
|
|
539
|
+
// 写入缓存:回调会立即消费 data 的 body,必须先同步 clone 再延迟读取
|
|
537
540
|
if (cacheable && data.ok) {
|
|
541
|
+
const cacheClone = data.clone();
|
|
542
|
+
const contentType = data.headers.get('Content-Type') || 'application/json';
|
|
538
543
|
utils._defer(() => {
|
|
539
|
-
|
|
540
|
-
utils.cache.set(url, text,
|
|
544
|
+
cacheClone.text().then(text => {
|
|
545
|
+
utils.cache.set(url, text, contentType, ttl);
|
|
541
546
|
}).catch(() => {});
|
|
542
547
|
});
|
|
543
548
|
}
|
|
@@ -609,9 +614,12 @@
|
|
|
609
614
|
clearTimeout(timer);
|
|
610
615
|
if (timedOut) return;
|
|
611
616
|
if (cacheable) {
|
|
617
|
+
// 调用方会消费 resp 的 body,必须先同步 clone 再延迟读取
|
|
618
|
+
const cacheClone = resp.clone();
|
|
619
|
+
const contentType = resp.headers.get('Content-Type') || 'application/json';
|
|
612
620
|
utils._defer(() => {
|
|
613
|
-
|
|
614
|
-
utils.cache.set(url, text,
|
|
621
|
+
cacheClone.text().then(text => {
|
|
622
|
+
utils.cache.set(url, text, contentType, ttl);
|
|
615
623
|
}).catch(() => {});
|
|
616
624
|
});
|
|
617
625
|
}
|
|
@@ -722,4 +730,12 @@
|
|
|
722
730
|
utils.dark = Object.assign(utils.dark, {
|
|
723
731
|
push: utils.dark.method.toggle.push,
|
|
724
732
|
});
|
|
725
|
-
|
|
733
|
+
|
|
734
|
+
// 暴露到 window:页尾插件片段经裸标识符 utils 即可使用
|
|
735
|
+
window.utils = utils;
|
|
736
|
+
|
|
737
|
+
// 补跑解析期注册的插件队列
|
|
738
|
+
if (window.stellar && typeof window.stellar._flushPlugins === 'function') {
|
|
739
|
+
window.stellar._flushPlugins();
|
|
740
|
+
}
|
|
741
|
+
})();
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<%
|
|
2
|
-
function div() {
|
|
3
|
-
var el = '';
|
|
4
|
-
if ((page.banner == undefined || page.banner == false) && !theme.article.auto_banner) {
|
|
5
|
-
return el;
|
|
6
|
-
}
|
|
7
|
-
var url;
|
|
8
|
-
if (page.banner != undefined) {
|
|
9
|
-
if (page.banner.includes('/')) {
|
|
10
|
-
url = page.banner;
|
|
11
|
-
} else {
|
|
12
|
-
url = 'https://source.unsplash.com/2000x400/?' + page.banner;
|
|
13
|
-
}
|
|
14
|
-
} else {
|
|
15
|
-
// 自动以 tags 作为关键词搜索封面
|
|
16
|
-
if (page.tags) {
|
|
17
|
-
var params = '';
|
|
18
|
-
page.tags.reverse().forEach((tag, i) => {
|
|
19
|
-
if (i > 0) {
|
|
20
|
-
params += ',';
|
|
21
|
-
}
|
|
22
|
-
params += tag.name;
|
|
23
|
-
});
|
|
24
|
-
url = 'https://source.unsplash.com/2000x400/?' + params;
|
|
25
|
-
} else {
|
|
26
|
-
url = 'https://source.unsplash.com/random/2000x400';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
el += '<div class="l_cover post' + scrollreveal(' ') + '">';
|
|
30
|
-
el += '<div class="cover">';
|
|
31
|
-
el += '<div class="lazy img bg" data-bg="' + escape_html(url) + '"></div>';
|
|
32
|
-
el += '</div>';
|
|
33
|
-
el += '</div>';
|
|
34
|
-
return el;
|
|
35
|
-
}
|
|
36
|
-
%>
|
|
37
|
-
<%- div() %>
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
<script defer>
|
|
2
|
-
const initServices = () => {
|
|
3
|
-
// 用于存储需要清理的资源
|
|
4
|
-
let timers = [];
|
|
5
|
-
|
|
6
|
-
ctx.services = Object.assign({}, JSON.parse(`<%- JSON.stringify(theme.data_services) %>`));
|
|
7
|
-
for (let id of Object.keys(ctx.services)) {
|
|
8
|
-
const js = ctx.services[id].js;
|
|
9
|
-
if (id == 'siteinfo') {
|
|
10
|
-
const cardlinks = document.querySelectorAll('a.link-card[cardlink]');
|
|
11
|
-
if (cardlinks?.length > 0) {
|
|
12
|
-
utils.js(js, { defer: true }).then(function () {
|
|
13
|
-
setCardLink(cardlinks);
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
} else if (id == 'ghinfo') {
|
|
17
|
-
const els = document.querySelectorAll('.ds-ghinfo');
|
|
18
|
-
if (els.length > 0) {
|
|
19
|
-
utils.js(js, { defer: true });
|
|
20
|
-
}
|
|
21
|
-
} else if (id == 'voice') {
|
|
22
|
-
const voiceAudios = document.querySelectorAll('.voice>audio');
|
|
23
|
-
if (voiceAudios?.length > 0) {
|
|
24
|
-
utils.js(js, { defer: true }).then(function () {
|
|
25
|
-
createVoiceDom(voiceAudios);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
} else if (id == 'video') {
|
|
29
|
-
const videos = document.querySelectorAll('.video>video');
|
|
30
|
-
if (videos?.length > 0) {
|
|
31
|
-
utils.js(js, { defer: true }).then(function () {
|
|
32
|
-
videoEvents(videos);
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
} else if (id == 'download-file') {
|
|
36
|
-
const files = document.querySelectorAll('.chat-file');
|
|
37
|
-
if (files?.length > 0) {
|
|
38
|
-
utils.js(js, { defer: true }).then(function () {
|
|
39
|
-
downloadFileEvent(files);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
const els = document.getElementsByClassName(`ds-${id}`);
|
|
44
|
-
if (els?.length > 0) {
|
|
45
|
-
if (id == 'timeline' || id == 'memos' || id == 'marked' || id == 'mdrender') {
|
|
46
|
-
utils.js(deps.marked).then(function () {
|
|
47
|
-
utils.js(js, { defer: true });
|
|
48
|
-
});
|
|
49
|
-
} else {
|
|
50
|
-
utils.js(js, { defer: true });
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// search
|
|
57
|
-
if (ctx.search && typeof searchFunc === 'function') {
|
|
58
|
-
const inputArea = document.querySelector("input#search-input");
|
|
59
|
-
if (inputArea && !inputArea._searchInitialized) {
|
|
60
|
-
const path = ctx.search.path.startsWith('/') ? ctx.root + ctx.search.path.substring(1) : ctx.root + ctx.search.path;
|
|
61
|
-
const filter = inputArea.getAttribute('data-filter') || '';
|
|
62
|
-
searchFunc(path, filter, 'search-wrapper', 'search-input', 'search-result');
|
|
63
|
-
// 标记为已初始化,防止重复绑定
|
|
64
|
-
inputArea._searchInitialized = true;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// chat iphone time
|
|
69
|
-
let phoneTimes = document.querySelectorAll('.chat .status-bar .time');
|
|
70
|
-
let firstAdjustInterval = null;
|
|
71
|
-
let mainInterval = null;
|
|
72
|
-
|
|
73
|
-
if (phoneTimes.length > 0) {
|
|
74
|
-
NowTime();
|
|
75
|
-
const date = new Date();
|
|
76
|
-
const sec = date.getSeconds();
|
|
77
|
-
firstAdjustInterval = setInterval(firstAdjustTime, 1000 * (60 - sec));
|
|
78
|
-
timers.push(firstAdjustInterval);
|
|
79
|
-
|
|
80
|
-
function firstAdjustTime() {
|
|
81
|
-
NowTime();
|
|
82
|
-
if (firstAdjustInterval) {
|
|
83
|
-
clearInterval(firstAdjustInterval);
|
|
84
|
-
firstAdjustInterval = null;
|
|
85
|
-
}
|
|
86
|
-
mainInterval = setInterval(NowTime, 1000 * 60);
|
|
87
|
-
timers.push(mainInterval);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function NowTime() {
|
|
91
|
-
for (let i = 0; i < phoneTimes.length; ++i) {
|
|
92
|
-
const timeSpan = phoneTimes[i];
|
|
93
|
-
const date = new Date();
|
|
94
|
-
const hour = date.getHours();
|
|
95
|
-
const min = date.getMinutes();
|
|
96
|
-
timeSpan.innerHTML = check(hour) + ":" + check(min);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
function check(val) {
|
|
101
|
-
if (val < 10) {
|
|
102
|
-
return ("0" + val);
|
|
103
|
-
}
|
|
104
|
-
return (val);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// chat quote - 存储事件监听器以便清理
|
|
109
|
-
const quoteClickHandlers = new Map();
|
|
110
|
-
const chat_quote_obverser = new IntersectionObserver((entries, observer) => {
|
|
111
|
-
entries.filter((entry) => { return entry.isIntersecting }).sort((a, b) => a.intersectionRect.y !== b.intersectionRect.y ? a.intersectionRect.y - b.intersectionRect.y : a.intersectionRect.x - b.intersectionRect.x).forEach((entry, index) => {
|
|
112
|
-
observer.unobserve(entry.target);
|
|
113
|
-
setTimeout(() => {
|
|
114
|
-
entry.target.classList.add('quote-blink');
|
|
115
|
-
setTimeout(() => {
|
|
116
|
-
entry.target.classList.remove('quote-blink');
|
|
117
|
-
}, 1000);
|
|
118
|
-
}, Math.max(100, 16) * (index + 1));
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
var chatQuotes = document.querySelectorAll(".chat .talk .quote");
|
|
123
|
-
chatQuotes.forEach((quote) => {
|
|
124
|
-
const handler = function () {
|
|
125
|
-
var chatCellDom = document.getElementById("quote-" + quote.getAttribute("quotedCellTag"));
|
|
126
|
-
if (chatCellDom) {
|
|
127
|
-
var chatDiv = chatCellDom.parentElement;
|
|
128
|
-
var mid = chatDiv.clientHeight / 2;
|
|
129
|
-
var offsetTop = chatCellDom.offsetTop;
|
|
130
|
-
if (offsetTop > mid - chatCellDom.clientHeight / 2) {
|
|
131
|
-
chatDiv.scrollTo({
|
|
132
|
-
top: chatCellDom.offsetTop - mid + chatCellDom.clientHeight / 2,
|
|
133
|
-
behavior: "smooth"
|
|
134
|
-
});
|
|
135
|
-
} else {
|
|
136
|
-
chatDiv.scrollTo({
|
|
137
|
-
top: 0,
|
|
138
|
-
behavior: "smooth"
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
chat_quote_obverser.observe(chatCellDom);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
quote.addEventListener('click', handler);
|
|
145
|
-
quoteClickHandlers.set(quote, handler); // 保存处理器引用
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// 返回清理函数,用于清理定时器和观察器
|
|
149
|
-
return () => {
|
|
150
|
-
// 清理所有定时器(包括可能未完成的 firstAdjustInterval)
|
|
151
|
-
timers.forEach(timer => {
|
|
152
|
-
if (timer) clearInterval(timer);
|
|
153
|
-
});
|
|
154
|
-
timers = [];
|
|
155
|
-
if (firstAdjustInterval) {
|
|
156
|
-
clearInterval(firstAdjustInterval);
|
|
157
|
-
firstAdjustInterval = null;
|
|
158
|
-
}
|
|
159
|
-
if (mainInterval) {
|
|
160
|
-
clearInterval(mainInterval);
|
|
161
|
-
mainInterval = null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// 断开观察器
|
|
165
|
-
if (chat_quote_obverser) {
|
|
166
|
-
chat_quote_obverser.disconnect();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// 移除所有 click 事件监听器
|
|
170
|
-
quoteClickHandlers.forEach((handler, quote) => {
|
|
171
|
-
quote.removeEventListener('click', handler);
|
|
172
|
-
});
|
|
173
|
-
quoteClickHandlers.clear();
|
|
174
|
-
};
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
utils.initPlugin(initServices, 'services');
|
|
178
|
-
</script>
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
const sidebar = {
|
|
3
|
-
leftbar: () => {
|
|
4
|
-
if (l_body) {
|
|
5
|
-
l_body.toggleAttribute('leftbar');
|
|
6
|
-
l_body.removeAttribute('rightbar');
|
|
7
|
-
}
|
|
8
|
-
},
|
|
9
|
-
rightbar: () => {
|
|
10
|
-
if (l_body) {
|
|
11
|
-
l_body.toggleAttribute('rightbar');
|
|
12
|
-
l_body.removeAttribute('leftbar');
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
dismiss: () => {
|
|
16
|
-
if (l_body) {
|
|
17
|
-
l_body.removeAttribute('leftbar');
|
|
18
|
-
l_body.removeAttribute('rightbar');
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
toggleTOC: () => {
|
|
22
|
-
document.querySelector('#data-toc').classList.toggle('collapse');
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
</script>
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<script type="text/javascript">
|
|
2
|
-
(() => {
|
|
3
|
-
const tagSwitchers = document.querySelectorAll('.tag-subtree.parent-tag > a > .tag-switcher-wrapper')
|
|
4
|
-
for (const tagSwitcher of tagSwitchers) {
|
|
5
|
-
tagSwitcher.addEventListener('click', (e) => {
|
|
6
|
-
const parent = e.target.closest('.tag-subtree.parent-tag')
|
|
7
|
-
parent.classList.toggle('expanded')
|
|
8
|
-
e.preventDefault()
|
|
9
|
-
})
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Get active tag from query string, then activate it.
|
|
13
|
-
const urlParams = new URLSearchParams(window.location.search)
|
|
14
|
-
const activeTag = urlParams.get('tag')
|
|
15
|
-
if (activeTag) {
|
|
16
|
-
let tag = document.querySelector(`.tag-subtree[data-tag="${activeTag}"]`)
|
|
17
|
-
if (tag) {
|
|
18
|
-
tag.querySelector('a').classList.add('active')
|
|
19
|
-
<% if (!theme.widgets.tagtree.expand_active) { %>
|
|
20
|
-
tag = tag.parentElement.closest('.tag-subtree.parent-tag')
|
|
21
|
-
<% } %>
|
|
22
|
-
while (tag) {
|
|
23
|
-
tag.classList.add('expanded')
|
|
24
|
-
tag = tag.parentElement.closest('.tag-subtree.parent-tag')
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
})()
|
|
29
|
-
</script>
|