您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Let DMHY header index back!
// ==UserScript== // @name DMHY Bangumi Index // @name:zh-TW 動漫花園新番索引 // @description Let DMHY header index back! // @description:zh-TW 把動漫花園上方的索引弄回來 // @namespace https://github.com/FlandreDaisuki // @author FlandreDaisuki // @match https://dmhy.org/ // @match https://dmhy.org/topics/* // @match https://share.dmhy.org/ // @match https://share.dmhy.org/topics/* // @require https://unpkg.com/[email protected]/dist/vue.global.prod.js // @require https://unpkg.com/[email protected]/dist/vue-router.global.prod.js // @require https://unpkg.com/[email protected]/dist/js-yaml.min.js // @require https://unpkg.com/[email protected]/libs/lz-string.min.js // @connect flandredaisuki.github.io // @license MIT // @noframes // @version 1.5.0 // @grant GM_xmlhttpRequest // @grant unsafeWindow // ==/UserScript== (function (vue, lzString, yaml) { 'use strict'; const HOUR_IN_MS = 60 * 60 * 1000; const WEEKDAY_STR = '日一二三四五六'; const BASE_URI = 'https://flandredaisuki.github.io/DMHY-Bangumi-Index'; // const BASE_URI = 'https://flandredaisuki.github.io/DMHY-Bangumi-Index/history/2019-10'; const $ = (s) => document.querySelector(s); const $$ = (s) => Array.from(document.querySelectorAll(s)); const createKeywordLink = (keyword) => `/topics/list?keyword=${keyword}`; const transformWeekday = (weekdayStr) => { switch (weekdayStr) { case '日': return '星期日(日)'; case '一': return '星期一(月)'; case '二': return '星期二(火)'; case '三': return '星期三(水)'; case '四': return '星期四(木)'; case '五': return '星期五(金)'; case '六': return '星期六(土)'; } }; const STORAGE_KEY$2 = 'DMHY-Bangumi-Index::favorite'; const favorites = vue.ref([]); const load$1 = () => { const fav = localStorage.getItem(STORAGE_KEY$2); if (!fav) { favorites.value = []; } try { favorites.value = JSON.parse(lzString.decompressFromBase64(fav)); } catch { favorites.value = []; } }; const save$1 = () => { localStorage.setItem( STORAGE_KEY$2, lzString.compressToBase64(JSON.stringify(favorites.value)), ); }; const find = (title) => { const foundIndex = favorites.value.findIndex((fav) => fav.title === title); return { found: (foundIndex >= 0) ? favorites.value[foundIndex] : null, foundIndex, }; }; /** @type {(title: string) => void */ const add$1 = (title) => { const keyword = new URL(location).searchParams.get('keyword'); favorites.value.push({ title, keyword }); save$1(); }; /** @type {(title: string) => void */ const remove = (title) => { const { found, foundIndex } = find(title); if (found) { favorites.value.splice(foundIndex, 1); } save$1(); }; const fetcher = async (url, options = {}) => { const defaultOptions = { method: 'GET', headers: { 'Cache-Control': 'no-cache', }, }; const opt = Object.assign({}, defaultOptions, options); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ ...opt, url, onload: (res) => { resolve(res.responseText); }, onerror: (err) => { console.error('DMHY-Bangumi-Index::req-err', err); reject(err); }, }); }); }; const YAMLToWeeklyBangumiPayload = (data, isnew) => { const weeklyBangumiPayload = {}; for (const weekdayStr of WEEKDAY_STR) { weeklyBangumiPayload[weekdayStr] = []; for (const [title, keyword] of Object.entries(data[weekdayStr])) { weeklyBangumiPayload[weekdayStr].push({ title, keyword, isnew, }); } } return weeklyBangumiPayload; }; const downloadBangumi = async (newold) => { const txt = await fetcher( `${BASE_URI}/${newold}.yaml`, ); const data = yaml.load(txt); return YAMLToWeeklyBangumiPayload(data, newold === 'new'); }; const weeklyBangumi = vue.ref({}); /** * wb := weeklyBangumi * { * 日: [ * {title, keyword, isnew}, * {title, keyword, isnew},... * ], * 一: [], * ... * } * → xwb := [...W].join('\0'); # ordered by WEEKDAY_STR # * → W := [...B].join('\1') * → B := [T, K, N].join('\2') * → N := true: 1; false: 0; */ const compressedEncode = (wb) => { return [...WEEKDAY_STR] .map((w) => wb[w] .map((b) => [b.title, b.keyword, Number(b.isnew)].join('\x02')) .join('\x01'), ) .join('\x00'); }; const compressedDecode = (xwb) => { return xwb .split('\x00') .map((xw, i) => { return { [WEEKDAY_STR[i]]: xw.split('\x01') .filter(Boolean) .map((b) => { const [title, keyword, isnew] = b.split('\x02'); return { title, keyword, isnew: isnew === '1', }; }), }; }) .reduce((c, v) => { return Object.assign(c, v); }, {}); }; const encodeWeeklyBangumiToStorage = (wb) => { return lzString.compressToBase64(compressedEncode(wb)); }; const decodeWeeklyBangumiFromStorage = (xwb) => { return compressedDecode(lzString.decompressFromBase64(xwb)); }; const STORAGE_KEY$1 = 'DMHY-Bangumi-Index::weekly-bangumi'; const CACHE_KEY = 'DMHY-Bangumi-Index::weekly-bangumi-cache-t'; const save = () => { localStorage.setItem( STORAGE_KEY$1, encodeWeeklyBangumiToStorage(weeklyBangumi.value), ); }; const load = () => { const xwb = localStorage.getItem(STORAGE_KEY$1); if (xwb) { weeklyBangumi.value = decodeWeeklyBangumiFromStorage(xwb) || {}; } else { weeklyBangumi.value = {}; } }; const add = (payload) => { for (const weekdayStr of WEEKDAY_STR) { if (!weeklyBangumi.value[weekdayStr]) { weeklyBangumi.value[weekdayStr] = []; } weeklyBangumi.value[weekdayStr].push(...payload[weekdayStr]); } }; const loadRemote = async () => { const [oldPayload, newPayload] = await Promise.all([ downloadBangumi('old'), downloadBangumi('new'), ]); add(oldPayload); add(newPayload); localStorage.setItem(CACHE_KEY, Date.now()); save(); }; const loadWithCache = async () => { const cacheTime = Number(localStorage.getItem(CACHE_KEY)) || 0; const maxCacheTime = 12 * HOUR_IN_MS; if (Date.now() - cacheTime > maxCacheTime) { await loadRemote(); } else { load(); } }; const cleanCacheTime = () => { localStorage.setItem(CACHE_KEY, 0); }; const STORAGE_KEY = 'DMHY-Bangumi-Index::expansion'; const expansion = vue.ref(Boolean(JSON.parse(localStorage.getItem(STORAGE_KEY) ?? 'false'))); const get = () => expansion.value; const set = (v) => { expansion.value = Boolean(v); localStorage.setItem(STORAGE_KEY, expansion.value); }; var script$2 = { setup() { const date = new Date(); const todayWeekday = date.getDay(); const toggleExpansion = () => set(!get()); const expansionStr = vue.computed(() => get() ? '收起' : '展開'); const todayStr = vue.computed(() => { const longWeekdayStr = new Intl.DateTimeFormat('zh', { weekday: 'long', }).format(date); const dateStr = new Intl.DateTimeFormat('zh', { day: 'numeric', month: 'long', year: 'numeric', }).format(date); return `西元 ${dateStr} ${longWeekdayStr}`; }); const TODAY_SENSITIVE_WEEKDAY_STR = WEEKDAY_STR.repeat(3) .slice(todayWeekday + 5, todayWeekday + 12); const orderedWeeklyBangumi = vue.computed(() => { const weeklyBangumiMap = [...TODAY_SENSITIVE_WEEKDAY_STR].reduce( (collection, weekdayStr) => { return collection.set(weekdayStr, weeklyBangumi.value[weekdayStr]); }, new Map(), ); return [...weeklyBangumiMap.entries()]; }); const forceUpdateWeekly = async () => { cleanCacheTime(); location.assign('https://share.dmhy.org/'); }; const isIndexShow = (index) => get() ? true : index < 4; return { todayStr, expansionStr, orderedWeeklyBangumi, toggleExpansion, createKeywordLink, transformWeekday, isIndexShow, forceUpdateWeekly, }; }, }; const _hoisted_1$2 = { class: "weekly-table" }; const _hoisted_2$1 = { class: "weekly-weekday-str" }; const _hoisted_3$1 = ["href"]; function render$2(_ctx, _cache, $props, $setup, $data, $options) { return (vue.openBlock(), vue.createElementBlock("div", null, [ vue.createElementVNode("header", null, [ _cache[2] || (_cache[2] = vue.createElementVNode("span", null, "新番資源索引", -1 /* HOISTED */)), vue.createElementVNode("span", null, vue.toDisplayString($setup.todayStr), 1 /* TEXT */), vue.createElementVNode("span", null, [ vue.createElementVNode("a", { href: "javascript:;", role: "button", onClick: _cache[0] || (_cache[0] = (...args) => ($setup.toggleExpansion && $setup.toggleExpansion(...args))) }, vue.toDisplayString($setup.expansionStr), 1 /* TEXT */) ]), vue.createElementVNode("span", null, [ vue.createElementVNode("a", { href: "javascript:;", role: "button", onClick: _cache[1] || (_cache[1] = (...args) => ($setup.forceUpdateWeekly && $setup.forceUpdateWeekly(...args))) }, "強制更新") ]) ]), vue.createElementVNode("table", _hoisted_1$2, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($setup.orderedWeeklyBangumi, ([weekday, dayBangumiList], index) => { return vue.withDirectives((vue.openBlock(), vue.createElementBlock("tr", { key: weekday, class: vue.normalizeClass(["weekly-tr", { 'weekly-tr-today': index === 2 }]) }, [ vue.createElementVNode("td", _hoisted_2$1, vue.toDisplayString($setup.transformWeekday(weekday)), 1 /* TEXT */), vue.createElementVNode("td", null, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(dayBangumiList, (bangumi) => { return (vue.openBlock(), vue.createElementBlock("a", { key: bangumi.title, class: vue.normalizeClass(["bangumi", { 'bangumi-old': !bangumi.isnew }]), href: $setup.createKeywordLink(bangumi.keyword) }, vue.toDisplayString(bangumi.title), 11 /* TEXT, CLASS, PROPS */, _hoisted_3$1)) }), 128 /* KEYED_FRAGMENT */)) ]) ], 2 /* CLASS */)), [ [vue.vShow, $setup.isIndexShow(index)] ]) }), 128 /* KEYED_FRAGMENT */)) ]) ])) } function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css_248z$2 = "\na[data-v-5ce41dcd] {\n color: #247;\n text-decoration: none;\n}\nheader[data-v-5ce41dcd] {\n color: #fff;\n background-color: #247;\n padding: 5px;\n display: flex;\n font-size: 0.8rem;\n}\nheader > span[data-v-5ce41dcd]:nth-of-type(n + 2)::before {\n content: \"::\";\n padding: 0 8px;\n}\nheader > span > a[data-v-5ce41dcd] {\n color: #fff;\n}\n.weekly-table[data-v-5ce41dcd] {\n border-collapse: collapse;\n width: 100%;\n}\n.weekly-tr[data-v-5ce41dcd] {\n display: flex;\n align-items: center;\n border: 2px solid white;\n background: white;\n}\n.weekly-tr.weekly-tr-today[data-v-5ce41dcd] {\n background-color: #ff9;\n}\n.weekly-weekday-str[data-v-5ce41dcd] {\n padding: 3px 15px;\n margin-right: 3px;\n background-color: #7e99be;\n color: white;\n font-weight: bolder;\n}\n.weekly-weekday-str + td[data-v-5ce41dcd] {\n display: flex;\n flex-flow: row wrap;\n flex: 1;\n}\n.bangumi[data-v-5ce41dcd] {\n border: 1px solid #ffa500;\n padding: 2px;\n margin: 1px 3px;\n display: inline-flex;\n align-items: center;\n}\n.bangumi-old[data-v-5ce41dcd] {\n border: 1px solid #002fff;\n}\n"; styleInject(css_248z$2); script$2.render = render$2; script$2.__scopeId = "data-v-5ce41dcd"; var script$1 = { setup() { const userInputStr = vue.ref(''); const validityMsg = vue.ref(''); const title = vue.computed(() => userInputStr.value.trim()); const titleInputEl = vue.ref(); const setValidity = (msg) => { validityMsg.value = msg; titleInputEl.value.setCustomValidity(msg); }; const onClickAdd = () => { if (!title.value) { return setValidity('名稱欄為空'); } if (find(title.value).found) { return setValidity('書籤名稱已存在'); } add$1(title.value); }; const onClickRemove = () => { if (!title.value) { return setValidity('名稱欄為空'); } if (!find(title.value).found) { return setValidity('書籤名稱不存在'); } remove(title.value); }; return { userInputStr, favorites, validityMsg, titleInputEl, createKeywordLink, setValidity, onClickAdd, onClickRemove, }; }, }; const _hoisted_1$1 = { class: "favorite-area" }; const _hoisted_2 = { class: "favorite-pool" }; const _hoisted_3 = ["href"]; const _hoisted_4 = { class: "input-area" }; const _hoisted_5 = { class: "tooltip" }; function render$1(_ctx, _cache, $props, $setup, $data, $options) { return (vue.openBlock(), vue.createElementBlock("div", null, [ _cache[5] || (_cache[5] = vue.createElementVNode("header", null, [ vue.createElementVNode("span", null, "書籤索引"), vue.createElementVNode("span", null, "將當前的搜索加入書籤,並自訂名稱") ], -1 /* HOISTED */)), vue.createElementVNode("div", _hoisted_1$1, [ vue.createElementVNode("div", _hoisted_2, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($setup.favorites, (bangumi) => { return (vue.openBlock(), vue.createElementBlock("a", { key: bangumi.title, href: $setup.createKeywordLink(bangumi.keyword), role: "button", class: "bangumi" }, vue.toDisplayString(bangumi.title), 9 /* TEXT, PROPS */, _hoisted_3)) }), 128 /* KEYED_FRAGMENT */)) ]), vue.createElementVNode("div", _hoisted_4, [ vue.withDirectives(vue.createElementVNode("input", { ref: "titleInputEl", "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($setup.userInputStr) = $event)), type: "text", class: "user-title-input", placeholder: "為目前網址取名", onInput: _cache[1] || (_cache[1] = $event => ($setup.setValidity(''))), onFocus: _cache[2] || (_cache[2] = $event => ($setup.setValidity(''))) }, null, 544 /* NEED_HYDRATION, NEED_PATCH */), [ [vue.vModelText, $setup.userInputStr] ]), vue.createElementVNode("span", _hoisted_5, vue.toDisplayString($setup.validityMsg), 1 /* TEXT */), vue.createElementVNode("button", { class: "add-btn", onClick: _cache[3] || (_cache[3] = (...args) => ($setup.onClickAdd && $setup.onClickAdd(...args))) }, " 加入 "), vue.createElementVNode("button", { class: "del-btn", onClick: _cache[4] || (_cache[4] = (...args) => ($setup.onClickRemove && $setup.onClickRemove(...args))) }, " 刪除 ") ]) ]) ])) } var css_248z$1 = "\na[data-v-65175dc4] {\n color: #247;\n text-decoration: none;\n}\nheader[data-v-65175dc4] {\n color: #fff;\n background-color: #247;\n padding: 5px;\n display: flex;\n font-size: 0.8rem;\n}\nheader > span[data-v-65175dc4]:nth-of-type(n + 2)::before {\n content: '::';\n padding: 0 8px;\n}\nheader > span > a[data-v-65175dc4] {\n color: #fff;\n}\n.favorite-area[data-v-65175dc4] {\n background-color: #fff;\n}\n.favorite-pool[data-v-65175dc4] {\n padding: 10px;\n min-height: 14px;\n display: flex;\n}\n.bangumi[data-v-65175dc4] {\n border: 1px solid #ffa500;\n padding: 2px;\n margin: 1px 3px;\n display: inline-flex;\n align-items: center;\n}\n.input-area[data-v-65175dc4] {\n display: flex;\n justify-content: center;\n padding: 4px;\n border-top: 1px dotted #247;\n}\n.input-area[data-v-65175dc4] > * {\n margin: 0 15px;\n}\n.input-area > .user-title-input[data-v-65175dc4] {\n border: 1px solid #247;\n padding: 0 7px;\n border-radius: 5px;\n font-size: 14px;\n}\n.tooltip[data-v-65175dc4] {\n position: absolute;\n background-color: #000;\n color: white;\n padding: 5px 10px;\n border-radius: 5px;\n transform-origin: bottom center;\n transform: translateY(-35px);\n display: none;\n}\n.tooltip[data-v-65175dc4]::after {\n content: '';\n width: 0;\n height: 0;\n border-left: 5px solid transparent;\n border-right: 5px solid transparent;\n border-top: 5px solid black;\n position: absolute;\n top: 100%;\n right: 20%;\n}\n.user-title-input:invalid + .tooltip[data-v-65175dc4] {\n display: block;\n}\n.input-area > button[data-v-65175dc4] {\n border: none;\n border-radius: 5px;\n padding: 5px 21px;\n font-size: 14px;\n}\n.input-area > button.add-btn[data-v-65175dc4] {\n background-color: lightgreen;\n}\n.input-area > button.del-btn[data-v-65175dc4] {\n background-color: crimson;\n color: white;\n}\n"; styleInject(css_248z$1); script$1.render = render$1; script$1.__scopeId = "data-v-65175dc4"; const TAB_NAMES = Object.freeze({ WEEKLY: 'weekly', FAVORITE: 'favorite', }); var script = { components: { PageFavorite: script$1, PageWeekly: script$2, }, setup() { const tab = vue.ref(TAB_NAMES.WEEKLY); vue.onMounted(() => { load$1(); save$1(); loadWithCache(); }); return { TAB_NAMES, setTab: (tabName) => { tab.value = tabName; }, shouldShowWeeklyTab: vue.computed(() => { return tab.value === TAB_NAMES.WEEKLY; }), shouldShowFavoriteTab: vue.computed(() => { return tab.value === TAB_NAMES.FAVORITE; }), }; }, }; const _hoisted_1 = { id: "🌐" }; function render(_ctx, _cache, $props, $setup, $data, $options) { const _component_PageWeekly = vue.resolveComponent("PageWeekly"); const _component_PageFavorite = vue.resolveComponent("PageFavorite"); return (vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [ vue.createElementVNode("nav", null, [ vue.createElementVNode("button", { type: "button", class: vue.normalizeClass({ 'active-tab': $setup.shouldShowWeeklyTab }), onClick: _cache[0] || (_cache[0] = $event => ($setup.setTab($setup.TAB_NAMES.WEEKLY))) }, " 新番索引 ", 2 /* CLASS */), vue.createElementVNode("button", { type: "button", class: vue.normalizeClass({ 'active-tab': $setup.shouldShowFavoriteTab }), onClick: _cache[1] || (_cache[1] = $event => ($setup.setTab($setup.TAB_NAMES.FAVORITE))) }, " 書籤索引 ", 2 /* CLASS */) ]), vue.withDirectives(vue.createVNode(_component_PageWeekly, null, null, 512 /* NEED_PATCH */), [ [vue.vShow, $setup.shouldShowWeeklyTab] ]), vue.withDirectives(vue.createVNode(_component_PageFavorite, null, null, 512 /* NEED_PATCH */), [ [vue.vShow, $setup.shouldShowFavoriteTab] ]) ])) } var css_248z = "\n#🌐[data-v-9efce4c4] {\n margin-top: 20px;\n font-size: 14px;\n}\nbutton[data-v-9efce4c4] {\n font-size: 1rem;\n color: black;\n text-decoration: none;\n}\nnav > button[data-v-9efce4c4] {\n display: inline-block;\n padding: 4px 16px;\n background: #fff;\n cursor: pointer;\n border-width: 1px 1px 0 1px;\n border-style: solid;\n border-color: #247;\n border-radius: 4px 4px 0 0;\n}\nnav > button.active-tab[data-v-9efce4c4] {\n border-top: 3px solid dodgerblue;\n}\n.page-view[data-v-9efce4c4] {\n border: 1px solid #247;\n}\n"; styleInject(css_248z); script.render = render; script.__scopeId = "data-v-9efce4c4"; // put constant as front as possible // pre-process const adSelectors = [ '.ad', '[id="1280_ad"] > a', '.main > div:first-child', ].join(','); for (const adEl of $$(adSelectors)) { adEl.remove(); } // entry point const app = vue.createApp(script) .mount($('#mini_jmd').parentElement); unsafeWindow.DMHYBangumiIndex$app = app; })(Vue, LZString, jsyaml);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址