PageSourceSearch

https://happyladysauce.cn/themes/theme-hao/assets/js/halo.js?v=1.6.6

js happyladysauce.cn collected 2026-10-07 02:47:10 UTC 17,833 bytes, 446 lines download raw bytes

1let halo = {
2    darkComment: () => {
3        if (document.querySelector('#comment div').shadowRoot.querySelector('.halo-comment-widget').classList != null) {
4            let commentDOMclass = document.querySelector('#comment div').shadowRoot.querySelector('.halo-comment-widget').classList
5            if (commentDOMclass.contains('light'))
6                commentDOMclass.replace('light', 'dark')
7            else
8                commentDOMclass.replace('dark', 'light')
9        }
10
11    },
12
13    dataCodeTheme: () => {
14
15        var t = document.documentElement.getAttribute('data-theme')
16        var e = document.querySelector("link[data-code-theme=light]"),
17            o = document.querySelector("link[data-code-theme=dark]");
18        (o || e) && ("light" === t ? (o.disabled = !0, e.disabled = !1) : (e.disabled = !0, o.disabled = !1))
19
20    },
21
22    /**
23     * 代码
24     * 只适用于halo的代码渲染
25     */
26    addPrismTool: () => {
27        if (typeof Prism === 'undefined' || typeof document === 'undefined') {
28            return;
29        }
30
31        if (!Prism.plugins.toolbar) {
32            console.warn('Copy to Clipboard plugin loaded before Toolbar plugin.');
33
34            return;
35        }
36
37        const enable = GLOBAL_CONFIG.prism.enable;
38        if (!enable) return;
39        const isEnableTitle = GLOBAL_CONFIG.prism.enable_title;
40        const isEnableHr = GLOBAL_CONFIG.prism.enable_hr;
41        const isEnableLine = GLOBAL_CONFIG.prism.enable_line;
42        const isEnableCopy = GLOBAL_CONFIG.prism.enable_copy;
43        const isEnableExpander = GLOBAL_CONFIG.prism.enable_expander;
44        const prismLimit = GLOBAL_CONFIG.prism.prism_limit;
45        const isEnableHeightLimit = GLOBAL_CONFIG.prism.enable_height_limit;
46
47        // https://stackoverflow.com/a/30810322/7595472
48
49        /** @param {CopyInfo} copyInfo */
50        function fallbackCopyTextToClipboard(copyInfo) {
51            var textArea = document.createElement('textarea');
52            textArea.value = copyInfo.getText();
53
54            // Avoid scrolling to bottom
55            textArea.style.top = '0';
56            textArea.style.left = '0';
57            textArea.style.position = 'fixed';
58
59            document.body.appendChild(textArea);
60            textArea.focus();
61            textArea.select();
62
63            try {
64                var successful = document.execCommand('copy');
65                setTimeout(function () {
66                    if (successful) {
67                        copyInfo.success();
68                    } else {
69                        copyInfo.error();
70                    }
71                }, 1);
72            } catch (err) {
73                setTimeout(function () {
74                    copyInfo.error(err);
75                }, 1);
76            }
77
78            document.body.removeChild(textArea);
79        }
80
81        /** @param {CopyInfo} copyInfo */
82        function copyTextToClipboard(copyInfo) {
83            if (navigator.clipboard) {
84                navigator.clipboard.writeText(copyInfo.getText()).then(copyInfo.success, function () {
85                    // try the fallback in case `writeText` didn't work
86                    fallbackCopyTextToClipboard(copyInfo);
87                });
88            } else {
89                fallbackCopyTextToClipboard(copyInfo);
90            }
91        }
92
93        /**
94         * Selects the text content of the given element.
95         *
96         * @param {Element} element
97         */
98        function selectElementText(element) {
99            // https://stackoverflow.com/a/20079910/7595472
100            window.getSelection().selectAllChildren(element);
101        }
102
103        /**
104         * Traverses up the DOM tree to find data attributes that override the default plugin settings.
105         *
106         * @param {Element} startElement An element to start from.
107         * @returns {Settings} The plugin settings.
108         * @typedef {Record<"copy" | "copy-error" | "copy-success" | "copy-timeout", string | number>} Settings
109         */
110        function getSettings(startElement) {
111            /** @type {Settings} */
112            var settings = {
113                'copy': 'Copy',
114                'copy-error': 'Press Ctrl+C to copy',
115                'copy-success': 'Copied!',
116                'copy-timeout': 5000
117            };
118
119            var prefix = 'data-prismjs-';
120            for (var key in settings) {
121                var attr = prefix + key;
122                var element = startElement;
123                while (element && !element.hasAttribute(attr)) {
124                    element = element.parentElement;
125                }
126                if (element) {
127                    settings[key] = element.getAttribute(attr);
128                }
129            }
130            return settings;
131        }
132
133        var r = Prism.plugins.toolbar.hook = function (a) {
134
135
136            var r = a.element.parentNode;
137            var toolbar = r.nextElementSibling;
138
139            //标题
140            isEnableTitle && toolbar.classList.add("c-title")
141            //标题分割线
142            isEnableHr && toolbar.classList.add("c-hr")
143            var customItem = document.createElement("div");
144            customItem.className = 'custom-item absolute top-0'
145
146            //复制
147            if (isEnableCopy) {
148                var copy = document.createElement("i");
149
150                copy.className = 'haofont hao-icon-paste copy-button code-copy cursor-pointer'
151                customItem.appendChild(copy)
152
153                copy.addEventListener('click', function () {
154                    copyTextToClipboard({
155                        getText: function () {
156                            return a.element.textContent;
157                        },
158                        success: function () {
159                            btf.snackbarShow('复制成功')
160                            setState('copy-success');
161                            resetText();
162                        },
163                        error: function () {
164                            setState('copy-error');
165
166                            setTimeout(function () {
167                                selectElementText(a.element);
168                            }, 1);
169
170                            resetText();
171                        }
172                    });
173
174                });
175
176            }
177
178            const prismToolsFn = function (e) {
179                const $target = e.target.classList;
180                if ($target.contains("code-expander")) prismShrinkFn(this);
181            };
182
183            //折叠
184            if (isEnableExpander) {
185                var expander = document.createElement("i");
186                expander.className = 'fa-sharp fa-solid haofont hao-icon-angle-down code-expander cursor-pointer'
187                customItem.appendChild(expander)
188
189                expander.addEventListener('click', prismToolsFn)
190
191
192            }
193
194            const expandCode = function () {
195                this.classList.toggle("expand-done");
196                this.style.display = "none";
197                r.classList.toggle("expand-done");
198            };
199
200            if (isEnableHeightLimit && r.offsetHeight > prismLimit) {
201
202                r.classList.add("close")
203                const ele = document.createElement("div");
204                ele.className = "code-expand-btn";
205                ele.innerHTML = '<i class="haofont hao-icon-angle-double-down"></i>';
206                ele.addEventListener("click", expandCode);
207                r.offsetParent.appendChild(ele);
208            }
209
210            const prismShrinkFn = ele => {
211                const $nextEle = r.offsetParent.lastElementChild.classList
212                toolbar.classList.toggle('c-expander')
213                r.classList.toggle("expand-done-expander");
214                if (toolbar.classList.contains('c-expander')) {
215                    r.firstElementChild.style.display = "none";
216                    if ($nextEle.contains('code-expand-btn')) {
217                        r.offsetParent.lastElementChild.style.display = "none";
218                    }
219                } else {
220                    r.firstElementChild.style.display = "block";
221                    if ($nextEle.contains('code-expand-btn') && !r.classList.contains('expand-done')) {
222                        r.offsetParent.lastElementChild.style.display = "block";
223                    }
224                }
225
226            };
227
228
229            toolbar.appendChild(customItem)
230
231            var settings = getSettings(a.element);
232
233
234            function resetText() {
235                setTimeout(function () {
236                    setState('copy');
237                }, settings['copy-timeout']);
238            }
239
240            /** @param {"copy" | "copy-error" | "copy-success"} state */
241            function setState(state) {
242                copy.setAttribute('data-copy-state', state);
243            }
244
245        };
246        Prism.hooks.add("complete", r)
247    },
248
249    addScript: (e, t, n) => {
250        if (document.getElementById(e))
251            return n ? n() : void 0;
252        let a = document.createElement("script");
253        a.src = t,
254            a.id = e,
255        n && (a.onload = n),
256            document.head.appendChild(a)
257    },
258
259    danmu: () => {
260        const e = new EasyDanmakuMin({
261            el: "#danmu",
262            line: 10,
263            speed: 20,
264            hover: !0,
265            loop: !0
266        });
267        let t = saveToLocal.get("danmu");
268        if (t)
269            e.batchSend(t, !0);
270        else {
271            let n = [];
272            if (GLOBAL_CONFIG.source.comments.use == 'Twikoo') {
273                fetch(GLOBAL_CONFIG.source.twikoo.twikooUrl, {
274                    method: "POST",
275                    body: JSON.stringify({
276                        event: "GET_RECENT_COMMENTS",
277                        accessToken: GLOBAL_CONFIG.source.twikoo.accessToken,
278                        includeReply: !1,
279                        pageSize: 5
280                    }),
281                    headers: {
282                        "Content-Type": "application/json"
283                    }
284                }).then((e => e.json())).then((({data: t}) => {
285                        t.forEach((e => {
286                                null == e.avatar && (e.avatar = "https://cravatar.cn/avatar/d615d5793929e8c7d70eab5f00f7f5f1?d=mp"),
287                                    n.push({
288                                        avatar: e.avatar,
289                                        content: e.nick + ":" + btf.changeContent(e.comment),
290                                        href: e.url + '#' + e.id
291
292                                    })
293                            }
294                        )),
295                            e.batchSend(n, !0),
296                            saveToLocal.set("danmu", n, .02)
297                    }
298                ))
299            }
300            if (GLOBAL_CONFIG.source.comments.use == 'Artalk') {
301                const statheaderList = {
302                    method: 'POST',
303                    headers: {
304                        'Content-Type': 'application/x-www-form-urlencoded',
305                        'Origin': window.location.origin
306                    },
307                    body: new URLSearchParams({
308                        'site_name': GLOBAL_CONFIG.source.artalk.siteName,
309                        'limit': '100',
310                        'type': 'latest_comments'
311                    })
312                }
313                fetch(GLOBAL_CONFIG.source.artalk.artalkUrl + 'api/stat', statheaderList)
314                    .then((e => e.json())).then((({data: t}) => {
315                        t.forEach((e => {
316                                n.push({
317                                    avatar: 'https://cravatar.cn/avatar/' + e.email_encrypted + '?d=mp&s=240',
318                                    content: e.nick + ":" + btf.changeContent(e.content_marked),
319                                    href: e.page_url + '#atk-comment-' + e.id
320
321                                })
322                            }
323                        )),
324                            e.batchSend(n, !0),
325                            saveToLocal.set("danmu", n, .02)
326                    }
327                ))
328            }
329            if (GLOBAL_CONFIG.source.comments.use == 'Waline') {
330                const loadWaline = () => {
331                    Waline.RecentComments({
332                        serverURL: GLOBAL_CONFIG.source.waline.serverURL,
333                        count: 50
334                    }).then(({comments}) => {
335                        const walineArray = comments.map(e => {
336                            return {
337                                'content': e.nick + ":" + btf.changeContent(e.comment),
338                                'avatar': e.avatar,
339                                'href': e.url + '#' + e.objectId,
340                            }
341                        })
342                        e.batchSend(walineArray, !0),
343                            saveToLocal.set("danmu", walineArray, .02)
344                    })
345                }
346                if (typeof Waline === 'object') loadWaline()
347                else getScript(GLOBAL_CONFIG.source.waline.js).then(loadWaline)
348            }
349
350        }
351        document.getElementById("danmuBtn").innerHTML = "<button class=\"hideBtn\" onclick=\"document.getElementById('danmu').
351classList.remove('hidedanmu')\">显示弹幕</button> <button class=\"hideBtn\" onclick=\"document.getElementById('danmu').classList.add('hidedanmu')\">隐藏弹幕</button>"
352    },
353
354    changeMarginLeft(element) {
355        var randomMargin = Math.floor(Math.random() * 901) + 100; // 生成100-1000之间的随机数
356        element.style.marginLeft = randomMargin + 'px';
357    },
358
359    getTopSponsors() {
360
361        var show_num = GLOBAL_CONFIG.source.power.showNum
362
363
364        function getPower() {
365            const url = "/apis/api.plugin.halo.run/v1alpha1/plugins/plugin-afdian/afdian/getSponsorList"
366            fetch(url)
367                .then(res => res.json())
368                .then(data => {
369                    console.log(data)
370                    if (200 === data["ec"]) {
371                        const values = data["data"]["list"];
372                        saveToLocal.set('power-data', JSON.stringify(values), 10 / (60 * 24))
373                        renderer(values);
374                    }
375
376                })
377        }
378
379        function renderer(values) {
380            var data = getArrayItems(values, 1);
381            let powerStar = document.getElementById("power-star")
382            if (values.length === 0) {
383                powerStar.href = GLOBAL_CONFIG.source.power.powerLink + GLOBAL_CONFIG.source.power.username
384                powerStar.innerHTML = ` 
385                        <div id="power-star-image" style="background-image: url('/themes/theme-hao/assets/images/afadian/afadian.webp')">
386                        </div>
387                        <div class="power-star-body">
388                            <div id="power-star-title">还没有人赞助~</div>
389                            <div id="power-star-desc">为爱发电,点击赞助</div>
390                        </div>`;
391            } else {
392                if (powerStar) {
393                    powerStar.href = GLOBAL_CONFIG.source.power.powerLink + data[0]["user"].user_id
394                    powerStar.innerHTML = ` 
395                        <div id="power-star-image" style="background-image: url(${data[0]["user"].avatar})">
396                        </div>
397                        <div class="power-star-body">
398                            <div id="power-star-title">${data[0]["user"].name}</div>
399                            <div id="power-star-desc">更多支持,为爱发电</div>
400                        </div>`;
401                }
402
403                if (values.length > 1) {
404                    var i = 0;
405                    var htmlText = '';
406                    for (let value of values) {
407                        if (i > parseInt(show_num)) {
408                            break;
409                        }
410                        htmlText += ` <a href="${"https://afdian.net/u/" + value["user"]["user_id"]}" rel="external nofollow" target="_blank" th:title="${value["user"]["name"]}">${value["user"]["name"]}</a>`;
411                        i = i + 1;
412                    }
413                    if (document.getElementById("power-item-link")) {
414                        document.getElementById("power-item-link").innerHTML = htmlText;
415                    }
416                }
417            }
418        }
419
420        function init() {
421            const data = saveToLocal.get('power-data')
422            if (data) {
423                renderer(JSON.parse(data))
424            } else {
425                getPower()
426            }
427        }
428
429        document.getElementById("power-star") && init()
430    }
431    ,
432
433    checkAd() {
434        var default_enable = GLOBAL_CONFIG.source.footer.default_enable
435        if (default_enable) {
436            var adElement = document.getElementById("footer-banner");
437            var notMusic = document.body.getAttribute("data-type") != "music"; // 检测是否为音乐页面
438            if ((adElement.offsetWidth <= 0 || adElement.offsetHeight <= 0) && notMusic) {
439                // 元素不可见,可能被拦截
440                console.log("Element may be blocked by AdBlocker Ultimate");
441                alert("页脚信息可能被AdBlocker Ultimate拦截,请检查广告拦截插件!")
442            }
443        }
444    },
445
446}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.