在您安装前,Greasy Fork镜像 希望您知道此脚本包含可能不受欢迎的功能,也许会帮助脚本作者获利,而不能给你带来任何收益。
这个脚本含有追踪您的操作的代码。
脚本作者的说明:
使用 Google Analytics 了解使用情況
自由轉調、輕鬆練歌,打造 91 譜的最佳體驗!
目前为
// ==UserScript== // @name 91 Plus // @namespace https://github.com/DonkeyBear // @version 1.8.2 // @author DonkeyBear // @description 自由轉調、輕鬆練歌,打造 91 譜的最佳體驗! // @icon https://www.91pu.com.tw/icons/favicon-32x32.png // @match *://www.91pu.com.tw/m/* // @match *://www.91pu.com.tw/song/* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/pinia.iife.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/index.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/zipson.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vexchords.dev.min.js // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant unsafeWindow // @antifeature tracking 使用 Google Analytics 了解使用情況 // ==/UserScript== (e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const a=document.createElement("style");a.textContent=e,document.head.append(a)})(' @import"https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css";#trigger-overlay[data-v-77b574f3]{position:fixed;top:0;left:0;right:0;bottom:0;z-index:500}.bi[data-v-ff0eef21]{color:var(--4f7e9938);font-size:var(--3ce130e8);-webkit-text-stroke:var(--882971fa) var(--4f7e9938)}.bi[data-v-ff0eef21]:before{transition:text-shadow .2s}.bi[active=true][data-v-ff0eef21]:before{text-shadow:0 0 .5rem rgb(75,156,169)}.toolbar-icon[data-v-a789fc6b]{cursor:pointer;padding:.25rem .75rem}.adjust-widget[data-v-cb8ab81d]{display:flex}.adjust-widget .adjust-button[data-v-cb8ab81d]{border:0;border-radius:.25rem;background:transparent}.adjust-widget .adjust-button[data-v-cb8ab81d]:hover{background:rgba(0,0,0,.025)}.adjust-widget .adjust-button[data-v-cb8ab81d]:disabled{opacity:.25}.adjust-widget .adjust-button.adjust-button-middle[data-v-cb8ab81d]{flex-grow:1;color:var(--13e75dc8);font-size:calc(var(--10392328) * .75);font-weight:700}.adjust-widget .adjust-button.adjust-button-left[data-v-cb8ab81d]{padding-right:1rem}.adjust-widget .adjust-button.adjust-button-right[data-v-cb8ab81d]{padding-left:1rem}.slide-and-fade-enter-active[data-v-364cc4c8],.slide-and-fade-leave-active[data-v-364cc4c8]{transition:all .2s}.slide-and-fade-enter-from[data-v-364cc4c8],.slide-and-fade-leave-to[data-v-364cc4c8]{transform:translateY(10%);opacity:0}#plus91-sheet-popup[data-v-364cc4c8]{position:absolute;left:0;right:0;bottom:100%;background:#fafafa;border:1px solid lightgray;padding:1rem 2rem;border-radius:1rem;margin:.5rem 1rem;max-height:50vh;overflow-y:scroll}#plus91-sheet-popup[data-v-364cc4c8]::-webkit-scrollbar{display:none}.transpose-range-container[data-v-364cc4c8]{margin-top:1rem}.transpose-range-container input[type=range][data-v-364cc4c8]{width:100%}.instrument-select-container[data-v-364cc4c8]{display:flex;border:1px solid lightgray;border-radius:.25rem;margin-top:1rem;background:white}.instrument-select-container .instrument-select-button[data-v-364cc4c8]{width:33.3333333333%;border:0;border-right:1px solid lightgray;background:transparent;color:#666;padding:.5rem;font-size:.65rem;font-weight:700;cursor:pointer!important}.instrument-select-container .instrument-select-button[data-v-364cc4c8]:last-child{border:0;border-radius:0 .25rem .25rem 0}.instrument-select-container .instrument-select-button[data-v-364cc4c8]:first-child{border-radius:.25rem 0 0 .25rem}.instrument-select-container .instrument-select-button[data-v-364cc4c8]:hover{background:whitesmoke}.chord-container .chord-name[data-v-735734f6]{font-size:.5rem;font-weight:900;color:#666;text-align:center}.chord-container .chord-chart[data-v-735734f6]{margin:-.6rem 0 -.25rem}.slide-and-fade-enter-active[data-v-110e9a91],.slide-and-fade-leave-active[data-v-110e9a91]{transition:all .2s}.slide-and-fade-enter-from[data-v-110e9a91],.slide-and-fade-leave-to[data-v-110e9a91]{transform:translateY(10%);opacity:0}#plus91-chord-popup[data-v-110e9a91]{position:absolute;left:0;right:0;bottom:100%;background:#fafafa;border:1px solid lightgray;border-radius:1rem;margin:.5rem 1rem;max-height:50vh;overflow-y:scroll;padding:1rem}#plus91-chord-popup[data-v-110e9a91]::-webkit-scrollbar{display:none}#plus91-chord-popup .banner[data-v-110e9a91]{display:flex;align-items:center;background:rgba(75,156,169,.25);color:color-mix(in srgb,rgba(75,156,169,.65) 50%,black 50%);border-radius:.5rem;padding:.5rem .75rem;margin-bottom:.25rem}#plus91-chord-popup .banner section[data-v-110e9a91]{flex-grow:1;margin-left:.5rem}#plus91-chord-popup .chord-popup-container[data-v-110e9a91]{display:grid;grid-template-columns:repeat(6,1fr);column-gap:.5rem;padding-top:.4rem}.slide-and-fade-enter-active[data-v-3be5b338],.slide-and-fade-leave-active[data-v-3be5b338]{transition:all .2s}.slide-and-fade-enter-from[data-v-3be5b338],.slide-and-fade-leave-to[data-v-3be5b338]{transform:translateY(10%);opacity:0}#plus91-font-popup[data-v-3be5b338]{position:absolute;left:0;right:0;bottom:100%;background:#fafafa;border:1px solid lightgray;padding:1rem 2rem;border-radius:1rem;margin:.5rem 1rem;max-height:50vh;overflow-y:scroll}#plus91-font-popup[data-v-3be5b338]::-webkit-scrollbar{display:none}.slide-and-fade-enter-active[data-v-430d5768],.slide-and-fade-leave-active[data-v-430d5768]{transition:all .2s}.slide-and-fade-enter-from[data-v-430d5768],.slide-and-fade-leave-to[data-v-430d5768]{transform:translateY(10%);opacity:0}#plus91-settings-popup[data-v-430d5768]{position:absolute;left:0;right:0;bottom:100%;background:#fafafa;border:1px solid lightgray;border-radius:1rem;margin:.5rem 1rem;max-height:50vh;overflow-y:scroll;padding:1rem}#plus91-settings-popup[data-v-430d5768]::-webkit-scrollbar{display:none}#plus91-settings-popup .setting-item[data-v-430d5768]{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;border-radius:.5rem;cursor:pointer}#plus91-settings-popup .setting-item[data-v-430d5768]:hover{background:rgba(0,0,0,.05)}.icon-button[data-v-e9653884]{display:flex;flex-direction:column;align-items:center;cursor:pointer;padding:0 .6rem .4rem;border-radius:.25rem}.icon-button[data-v-e9653884]:hover{background:rgba(0,0,0,.025)}.icon-button .button-text[data-v-e9653884]{font-size:.5rem;color:var(--5908aad9)}.slide-and-fade-enter-active[data-v-fca8d708],.slide-and-fade-leave-active[data-v-fca8d708]{transition:all .2s}.slide-and-fade-enter-from[data-v-fca8d708],.slide-and-fade-leave-to[data-v-fca8d708]{transform:translateY(10%);opacity:0}#plus91-menu-popup[data-v-fca8d708]{position:absolute;left:0;right:0;bottom:100%;background:#fafafa;border:1px solid lightgray;padding:1rem 2rem;border-radius:1rem;margin:.5rem 1rem;max-height:50vh;overflow-y:scroll}#plus91-menu-popup[data-v-fca8d708]::-webkit-scrollbar{display:none}#plus91-menu-popup .menu-popup-container[data-v-fca8d708]{display:flex;justify-content:space-around}.hotkey-item[data-v-3c43f6cf]{display:flex;justify-content:space-between;align-items:center;padding:0 .25rem;border-radius:.25rem;height:1.4rem}.hotkey-item[data-v-3c43f6cf]:nth-child(odd){background:rgba(0,0,0,.025)}.desc.title[data-v-3c43f6cf]{font-size:.55rem;color:#999}.hotkeys[data-v-3c43f6cf]{display:flex}.hr[data-v-3c43f6cf]{display:flex;flex-grow:1;border-top:1px solid lightgray;margin-left:.25rem}kbd[data-v-3c43f6cf]{font-size:.6rem;border:solid lightgray;border-width:1px .1rem .15rem;border-radius:.2rem;padding:0 .2rem;letter-spacing:-.025rem;color:#666;margin-left:.15rem}.slide-and-fade-enter-active[data-v-eb86b87c],.slide-and-fade-leave-active[data-v-eb86b87c]{transition:all .2s}.slide-and-fade-enter-from[data-v-eb86b87c],.slide-and-fade-leave-to[data-v-eb86b87c]{transform:translateY(10%);opacity:0}#plus91-hotkey-popup[data-v-eb86b87c]{position:absolute;left:0;right:0;bottom:100%;background:#fafafa;border:1px solid lightgray;padding:1rem 2rem;border-radius:1rem;margin:.5rem 1rem;max-height:50vh;overflow-y:scroll}#plus91-hotkey-popup[data-v-eb86b87c]::-webkit-scrollbar{display:none}#plus91-hotkey-popup .hotkey-popup-container[data-v-eb86b87c]{display:flex;color:#444}#plus91-hotkey-popup section[data-v-eb86b87c]{flex-grow:1;width:50%;margin:-.1rem 0}#plus91-hotkey-popup section.left-part[data-v-eb86b87c]{border-right:1px solid lightgray;margin-left:-.5rem;padding-right:.5rem}#plus91-hotkey-popup section.right-part[data-v-eb86b87c]{padding-left:.5rem;margin-right:-.5rem}#plus91-hotkey-popup kbd[data-v-eb86b87c]{font-size:.65rem;border:solid lightgray;border-width:1px .1rem .15rem;border-radius:.2rem;padding:0 .2rem;letter-spacing:-.025rem;color:#666}.slide-enter-active[data-v-d7417138],.slide-leave-active[data-v-d7417138]{transition:transform .2s}.slide-enter-from[data-v-d7417138],.slide-leave-to[data-v-d7417138]{transform:translateY(100%)}#plus91-footer[data-v-d7417138]{z-index:1000;display:flex;justify-content:center;position:fixed;left:0;right:0;bottom:0}.footer-container[data-v-d7417138]{width:min(100vw,768px);background:rgba(75,156,169,.65);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);padding:.25rem .5rem .75rem;display:flex;justify-content:space-between;align-items:center;border-radius:1rem 1rem 0 0;border:1px solid rgb(90,140,160);border-bottom:0}.slide-enter-active[data-v-a1982e9d],.slide-leave-active[data-v-a1982e9d]{transition:transform .2s}.slide-enter-from[data-v-a1982e9d],.slide-leave-to[data-v-a1982e9d]{transform:translateY(-100%)}#plus91-header[data-v-a1982e9d]{z-index:1000;display:flex;justify-content:center;position:fixed;left:0;right:0;top:0}.header-container[data-v-a1982e9d]{width:min(100vw,768px);background:rgba(75,156,169,.65);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);padding:.25rem .5rem;display:flex;justify-content:space-between;align-items:center;border-radius:0 0 1rem 1rem;border:1px solid rgb(90,140,160);border-top:0}.header-container input[data-v-a1982e9d]{flex-grow:1;width:100%;border-radius:50rem;border:0;font-size:.8rem;font-weight:700;padding:.35rem 1.25rem;background:rgba(255,255,255,.6666666667);color:#0009;opacity:.5;transition:all .2s}.header-container input[data-v-a1982e9d]:focus-visible{outline:0;opacity:1}.fade-enter-active[data-v-a9f152b4],.fade-leave-active[data-v-a9f152b4]{transition:opacity .2s}.fade-enter-from[data-v-a9f152b4],.fade-leave-to[data-v-a9f152b4]{opacity:0}#dark-mode-overlay[data-v-a9f152b4]{position:fixed;top:0;left:0;right:0;bottom:0;z-index:800;-webkit-backdrop-filter:invert(1) hue-rotate(145deg) saturate(.75);backdrop-filter:invert(1) hue-rotate(145deg) saturate(.75);pointer-events:none}html{background:#fafafa url(/templets/pu/images/tone-bg.gif)}#vue-91plus{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif}.tfunc2{margin:10px}#mtitle{font-family:system-ui}input[type=range],input[type=range]::-webkit-slider-thumb,input[type=range]::-webkit-slider-runnable-track{-webkit-appearance:none;box-shadow:none}input[type=range]::-webkit-slider-thumb,input[type=range]::-webkit-slider-runnable-track{border:1px solid rgba(68,68,68,.25)}input[type=range]::-webkit-slider-thumb{background:#60748d}#viptoneWindow.window,#bottomad,.update_vip_bar,.wmask,header,footer,.autoscroll,.backplace,.set .keys,.set .plays,.set .clear,.setint .hr:nth-child(4),.setint .hr:nth-child(5),.setint .hr:nth-child(6),.adsbygoogle{display:none!important} '); (function (vue, pinia$1, piniaPluginPersistedstate, zipson, vexchords, html2canvas) { 'use strict'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; var __privateMethod = (obj, member, method) => { __accessCheck(obj, member, "access private method"); return method; }; var _unformat, unformat_fn, _store, _watchTranspose, watchTranspose_fn, _watchFontSize, watchFontSize_fn; const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const _sfc_main$g = {}; const _hoisted_1$f = { id: "trigger-overlay" }; function _sfc_render(_ctx, _cache) { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$f); } const TriggerOverlay = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["render", _sfc_render], ["__scopeId", "data-v-77b574f3"]]); const _Chord = class _Chord { /** @param {string} chordString */ constructor(chordString) { this.chordString = chordString; } /** * @param {number} delta * @returns {Chord} */ transpose(delta) { this.chordString = this.chordString.replaceAll(/[A-G][#b]?/g, (note) => { const isSharp = _Chord.sharps.includes(note); const scale = isSharp ? _Chord.sharps : _Chord.flats; const noteIndex = scale.indexOf(note); const transposedIndex = (noteIndex + delta + 12) % 12; const transposedNote = scale[transposedIndex]; return transposedNote; }); return this; } /** @returns {Chord} */ switchModifier() { this.chordString = this.chordString.replaceAll(/[A-G][#b]/g, (note) => { const scale = note.includes("#") ? _Chord.sharps : _Chord.flats; const newScale = note.includes("#") ? _Chord.flats : _Chord.sharps; const noteIndex = scale.indexOf(note); return newScale[noteIndex]; }); return this; } /** @returns {Chord} */ useSharpModifier() { this.chordString = this.chordString.replaceAll(/[A-G]b/g, (note) => { const noteIndex = _Chord.flats.indexOf(note); return _Chord.sharps[noteIndex]; }); return this; } /** @returns {Chord} */ useFlatModifier() { this.chordString = this.chordString.replaceAll(/[A-G]#/g, (note) => { const noteIndex = _Chord.sharps.indexOf(note); return _Chord.flats[noteIndex]; }); return this; } /** @returns {string} */ toString() { return this.chordString; } /** @returns {string} */ toFormattedString() { return this.chordString.replaceAll( /[#b]/g, /* html */ `<sup>$&</sup>` ); } }; __publicField(_Chord, "sharps", ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]); __publicField(_Chord, "flats", ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]); let Chord = _Chord; var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)(); var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)(); var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); class MonkeyStorage { /** * @param {String} key * @returns {String|null} */ static getItem(key) { if (_GM_getValue) { return _GM_getValue(key, null); } else { return localStorage.getItem(key); } } /** * @param {String} key * @param {String} value * @returns {void} */ static setItem(key, value) { if (_GM_setValue) { _GM_setValue(key, value); } else { localStorage.setItem(key, value); } } } const useStore = pinia$1.defineStore("store", { state() { return { // #################### // 元件相關狀態 // #################### isDarkMode: false, isToolbarsShow: false, isPopupShow: { sheet: false, chord: false, font: false, settings: false, menu: false, // 選單內功能 hotkey: false }, // #################### // 偏好設定相關狀態 // #################### agreeToArchiveSheet: true, // #################### // 譜面相關狀態 // #################### transpose: 0, /** 在 `StoreHandler` 裡賦值 */ originalCapo: 0, /** 在 `StoreHandler` 裡賦值,HTML 格式 */ originalKey: "", /** `font-size` 的變化值 */ fontSizeDelta: 0, /** 在 `StoreHandler` 裡賦值,單位為 px */ originalFontSize: 0, /** 在 `StoreHandler` 裡賦值,單位為 px */ originalLineHeight: 0 }; }, persist: { key: "plus91-preferences", storage: MonkeyStorage, deserialize: zipson.parse, serialize: zipson.stringify, paths: ["isDarkMode", "agreeToArchiveSheet"], beforeRestore() { console.log("[91 Plus] 讀取偏好設置中"); }, afterRestore() { console.log("[91 Plus] 偏好設置讀取完畢"); }, debug: true }, getters: { currentCapo() { return this.originalCapo + this.transpose; }, currentKey() { return new Chord(this.originalKey).transpose(-this.transpose).toFormattedString(); } }, actions: { toggleToolbars() { if (this.isToolbarsShow) { this.closePopups(); } else { this.isPopupShow.sheet = true; } this.isToolbarsShow = !this.isToolbarsShow; }, closePopups() { for (const popup in this.isPopupShow) { this.isPopupShow[popup] = false; } }, /** @param {'sheet'|'chord'|'font'|'settings'|'menu'|'hotkey'} name */ togglePopup(name) { for (const popup in this.isPopupShow) { if (popup === name) { this.isPopupShow[popup] = !this.isPopupShow[popup]; } else { this.isPopupShow[popup] = false; } } }, plusTranspose(numberToPlus) { let newTranspose = this.transpose + numberToPlus; const newCapo = this.originalCapo + newTranspose; if (newCapo === 12 || newCapo === -12) { newTranspose = -this.originalCapo; } this.transpose = newTranspose; } } }); const _hoisted_1$e = ["active"]; const _sfc_main$f = { __name: "BootstrapIcon", props: { icon: { type: String, required: true }, color: { type: String, default: "whitesmoke" }, size: { type: String, default: "1rem" }, stroke: { type: String, default: "0" }, active: { type: Boolean, default: false } }, setup(__props) { vue.useCssVars((_ctx) => ({ "4f7e9938": __props.color, "3ce130e8": __props.size, "882971fa": __props.stroke })); const props = __props; return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("i", { class: vue.normalizeClass(`bi bi-${props.icon}`), active: props.active }, null, 10, _hoisted_1$e); }; } }; const BootstrapIcon = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__scopeId", "data-v-ff0eef21"]]); const _hoisted_1$d = { class: "toolbar-icon" }; const _sfc_main$e = { __name: "ToolbarIcon", props: { icon: { type: String, required: true }, stroke: { type: String, default: "0" }, active: { type: Boolean, default: false }, color: { type: String, default: "whitesmoke" } }, setup(__props) { const props = __props; return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$d, [ vue.createVNode(BootstrapIcon, { icon: props.icon, color: props.color, size: "1.3rem", stroke: props.stroke, active: props.active }, null, 8, ["icon", "color", "stroke", "active"]) ]); }; } }; const ToolbarIcon = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["__scopeId", "data-v-a789fc6b"]]); const _hoisted_1$c = { class: "adjust-widget" }; const _hoisted_2$b = ["disabled"]; const _hoisted_3$6 = ["disabled"]; const _hoisted_4$4 = ["disabled"]; const _sfc_main$d = { __name: "AdjustWidget", props: { iconLeft: { type: String, default: "caret-left-fill" }, iconRight: { type: String, default: "caret-right-fill" }, disabledLeft: { type: Boolean, default: false }, disabledMiddle: { type: Boolean, default: false }, disabledRight: { type: Boolean, default: false }, color: { type: String, default: "#444" }, size: { type: String, default: "1.25rem" }, onclickLeft: Function, onclickMiddle: Function, onclickRight: Function }, setup(__props) { vue.useCssVars((_ctx) => ({ "13e75dc8": __props.color, "10392328": __props.size })); const props = __props; return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$c, [ vue.createElementVNode("button", { class: "adjust-button adjust-button-left", onClick: _cache[0] || (_cache[0] = (...args) => props.onclickLeft && props.onclickLeft(...args)), disabled: props.disabledLeft }, [ vue.createVNode(BootstrapIcon, { icon: props.iconLeft, color: props.color, size: props.size }, null, 8, ["icon", "color", "size"]) ], 8, _hoisted_2$b), vue.createElementVNode("button", { class: "adjust-button adjust-button-middle", onClick: _cache[1] || (_cache[1] = (...args) => props.onclickMiddle && props.onclickMiddle(...args)), disabled: props.disabledMiddle }, [ vue.renderSlot(_ctx.$slots, "default", {}, void 0, true) ], 8, _hoisted_3$6), vue.createElementVNode("button", { class: "adjust-button adjust-button-right", onClick: _cache[2] || (_cache[2] = (...args) => props.onclickRight && props.onclickRight(...args)), disabled: props.disabledRight }, [ vue.createVNode(BootstrapIcon, { icon: props.iconRight, color: props.color, size: props.size }, null, 8, ["icon", "color", "size"]) ], 8, _hoisted_4$4) ]); }; } }; const AdjustWidget = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__scopeId", "data-v-cb8ab81d"]]); class ChordSheetElement { /** @param {HTMLElement} chordSheetElement */ constructor(chordSheetElement) { /** @param {NodeList} nodeList */ __privateAdd(this, _unformat); this.chordSheetElement = chordSheetElement; } /** * 將 Header 和譜上的和弦移調,並實質修改於 DOM * @param {number} delta 相對於當前調的移調值 */ static transposeSheet(delta) { $("#tone_z .tf").each(function() { const chord = new Chord($(this).text()); const newChordHTML = chord.transpose(-delta).toFormattedString(); $(this).html(newChordHTML); }); } /** @returns {ChordSheetElement} */ formatUnderlines() { const underlineEl = this.chordSheetElement.querySelectorAll("u"); const doubleUnderlineEl = this.chordSheetElement.querySelectorAll("abbr"); underlineEl.forEach((el) => { el.innerText = `{_${el.innerText}_}`; }); doubleUnderlineEl.forEach((el) => { el.innerText = `{=${el.innerText}=}`; }); return this; } /** @returns {ChordSheetElement} */ unformatUnderlines() { const underlineEl = this.chordSheetElement.querySelectorAll("u"); const doubleUnderlineEl = this.chordSheetElement.querySelectorAll("abbr"); __privateMethod(this, _unformat, unformat_fn).call(this, underlineEl); __privateMethod(this, _unformat, unformat_fn).call(this, doubleUnderlineEl); return this; } } _unformat = new WeakSet(); unformat_fn = function(nodeList) { nodeList.forEach((el) => { el.innerHTML = el.innerText.replaceAll(/{_|{=|=}|_}/g, "").replaceAll( /[a-zA-Z0-9#/]+/g, /* html */ `<span class="tf">$&</span>` ); }); }; class ChordSheetDocument { constructor() { this.el = { mtitle: document.getElementById("mtitle"), tkinfo: document.querySelector(".tkinfo"), capoSelect: document.querySelector(".capo .select"), tinfo: document.querySelector(".tinfo"), tone_z: document.getElementById("tone_z") }; } getId() { const urlParams = new URLSearchParams(window.location.search); return Number(urlParams.get("id")); } getTitle() { return this.el.mtitle.innerText.trim(); } getKey() { var _a; const match = (_a = this.el.tkinfo) == null ? void 0 : _a.innerText.match(new RegExp("(?<=原調:)\\w*")); return match ? match[0].trim() : ""; } getPlay() { var _a; const match = (_a = this.el.capoSelect) == null ? void 0 : _a.innerText.split(/\s*\/\s*/); return match ? match[1].trim() : ""; } getCapo() { var _a; const match = (_a = this.el.capoSelect) == null ? void 0 : _a.innerText.split(/\s*\/\s*/); return match ? Number(match[0]) : 0; } getSinger() { var _a; const match = (_a = this.el.tinfo) == null ? void 0 : _a.innerText.match(new RegExp("(?<=演唱:).*(?=\\n|$)")); return match ? match[0].trim() : ""; } getComposer() { var _a; const match = (_a = this.el.tinfo) == null ? void 0 : _a.innerText.match(new RegExp("(?<=曲:).*?(?=詞:|$)")); return match ? match[0].trim() : ""; } getLyricist() { var _a; const match = (_a = this.el.tinfo) == null ? void 0 : _a.innerText.match(new RegExp("(?<=詞:).*?(?=曲:|$)")); return match ? match[0].trim() : ""; } getBpm() { var _a; const match = (_a = this.el.tkinfo) == null ? void 0 : _a.innerText.match(/\d+/); return match ? Number(match[0]) : 0; } getSheetText() { const formattedChordSheet = this.el.tone_z.innerText.replaceAll(/\s+?\n/g, "\n").replaceAll("\n\n", "\n").trim().replaceAll(/\s+/g, (match) => { return `{%${match.length}%}`; }); return formattedChordSheet; } } class StoreHandler { constructor() { /** 當 `#store.transpose` 變動時,將譜面上的和弦進行移調 */ __privateAdd(this, _watchTranspose); __privateAdd(this, _watchFontSize); // 命 `#store` 為私有屬性,在建立實例時再賦值,避免衝突 __privateAdd(this, _store, void 0); __privateSet(this, _store, useStore()); } initState() { const capoSelected = $(".capo .select").eq(0).text().trim(); const originalCapo = +capoSelected.split(/\s*\/\s*/)[0]; const originalKey = capoSelected.split(/\s*\/\s*/)[1]; __privateGet(this, _store).originalCapo = originalCapo; __privateGet(this, _store).originalKey = originalKey; const fontSize = +$("#tone_z").css("font-size").match(/^\d+/)[0]; const lineHeight = +$("#tone_z > p").css("line-height").match(/^\d+/)[0]; __privateGet(this, _store).originalFontSize = fontSize; __privateGet(this, _store).originalLineHeight = lineHeight; const params = getQueryParams(); if (params.transpose) { __privateGet(this, _store).transpose = params.transpose; } } start() { __privateMethod(this, _watchTranspose, watchTranspose_fn).call(this); __privateMethod(this, _watchFontSize, watchFontSize_fn).call(this); return this; } static handleKeydown(key) { const store = useStore(); switch (key) { case " ": { store.toggleToolbars(); break; } case "/": { if (!store.isToolbarsShow) { store.toggleToolbars(); store.closePopups(); } setTimeout(() => { $("#plus91-header input").get(0).focus(); }, 0); break; } case "Escape": { if (store.isToolbarsShow) { store.toggleToolbars(); } break; } } if (store.isPopupShow.sheet) { switch (key) { case "ArrowLeft": { store.plusTranspose(-1); break; } case "ArrowRight": { store.plusTranspose(1); break; } case "ArrowDown": { store.transpose = 0; break; } } } } } _store = new WeakMap(); _watchTranspose = new WeakSet(); watchTranspose_fn = function() { vue.watch(() => { return __privateGet(this, _store).transpose; }, (newValue, oldValue) => { ChordSheetElement.transposeSheet((newValue - oldValue) % 12); }); }; _watchFontSize = new WeakSet(); watchFontSize_fn = function() { vue.watch(() => { return __privateGet(this, _store).fontSizeDelta; }, (newValue) => { const oFontSize = __privateGet(this, _store).originalFontSize; const oLineHeight = __privateGet(this, _store).originalLineHeight; $("#tone_z").css("font-size", `${oFontSize + newValue}px`); $("#tone_z > p").css("line-height", `${oLineHeight + newValue}px`); }); }; function redirect() { const currentUrl = window.location.href; if (/\/song\//.test(currentUrl)) { const sheetId = currentUrl.match(new RegExp("(?<=\\/)\\d+(?=\\.)"))[0]; const newUrl = `https://www.91pu.com.tw/m/tone.shtml?id=${sheetId}`; window.location.replace(newUrl); } } function injectGtag() { const newScript = document.createElement("script"); newScript.src = "https://www.googletagmanager.com/gtag/js?id=G-JF4S3HZY31"; newScript.async = true; document.head.appendChild(newScript); newScript.onload = () => { window.dataLayer = window.dataLayer || []; function gtag() { window.dataLayer.push(arguments); } gtag("js", /* @__PURE__ */ new Date()); gtag("config", "G-JF4S3HZY31"); }; } function getQueryParams() { const url = new URL(window.location.href); const params = { transpose: +url.searchParams.get("transpose"), darkMode: !!url.searchParams.get("darkmode") }; return params; } function changeTitle() { const newTitle = $("#mtitle").text().trim(); document.title = `${newTitle} | 91+`; } function archiveChordSheet() { const sheet = document.getElementById("tone_z"); const chordSheetDocument = new ChordSheetDocument(); try { const chordSheetElement = new ChordSheetElement(sheet); chordSheetElement.formatUnderlines(); const formBody = { id: chordSheetDocument.getId(), title: chordSheetDocument.getTitle(), key: chordSheetDocument.getKey(), play: chordSheetDocument.getPlay(), capo: chordSheetDocument.getCapo(), singer: chordSheetDocument.getSinger(), composer: chordSheetDocument.getComposer(), lyricist: chordSheetDocument.getLyricist(), bpm: chordSheetDocument.getBpm(), sheet_text: chordSheetDocument.getSheetText() }; chordSheetElement.unformatUnderlines(); fetch("https://91-plus-plus-api.fly.dev/archive", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formBody) }).then((response) => { console.log("[91 Plus] 雲端樂譜備份成功:", response); }).catch((error) => { console.error("[91 Plus] 雲端樂譜備份失敗:", error); }); } catch { console.warn("[91 Plus] 樂譜解析失敗,無法備份"); fetch( `https://91-plus-plus-api.fly.dev/report?id=${chordSheetDocument.getId()}` ); } } function initMutationObserver() { return new MutationObserver((records, observer) => { const isMutationDone = !!document.querySelector("#tone_z").childElementCount; if (!isMutationDone) { return; } $("body").trigger("mutation.done"); observer.disconnect(); }).observe(document.body, { childList: true, subtree: true }); } function onDomReady(callback) { $("body").on("mutation.done", callback); } function handleEvents() { $("html").on("keydown", (event) => { const excludedTags = ["input"]; const tagName = event.target.tagName.toLowerCase(); if (excludedTags.includes(tagName)) { return; } StoreHandler.handleKeydown(event.key); }); } function switchInstrument(instrument) { switch (instrument) { case "guitar": { $(".schord").trigger("click"); break; } case "ukulele": { $(".ukschord").trigger("click"); break; } default: { $(".nsChord").trigger("click"); break; } } } function getChordShapes() { const chordShapes = _unsafeWindow.chord_shapes; return chordShapes; } function getChordList() { const chordList = []; $("#tone_z .tf").each(function() { chordList.push($(this).text()); }); return [...new Set(chordList)]; } function convertChordName(chordName) { const root = chordName.match(/^[A-G]#?/)[0]; const rest = chordName.replace(/^[A-G]#?/, ""); return `${rest} ${root}`; } const _hoisted_1$b = { id: "plus91-sheet-popup" }; const _hoisted_2$a = { class: "sheet-popup-container" }; const _hoisted_3$5 = { class: "text-capo" }; const _hoisted_4$3 = ["innerHTML"]; const _hoisted_5$1 = { class: "transpose-range-container" }; const _hoisted_6$1 = ["value"]; const _hoisted_7 = { class: "instrument-select-container" }; const _sfc_main$c = { __name: "SheetPopup", setup(__props) { const store = useStore(); return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$b, [ vue.createElementVNode("div", _hoisted_2$a, [ vue.createVNode(AdjustWidget, { "onclick-left": () => { vue.unref(store).plusTranspose(-1); }, "onclick-middle": () => { vue.unref(store).transpose = 0; }, "onclick-right": () => { vue.unref(store).plusTranspose(1); } }, { default: vue.withCtx(() => [ vue.createTextVNode(" CAPO:"), vue.createElementVNode("span", _hoisted_3$5, vue.toDisplayString(vue.unref(store).currentCapo), 1), vue.createTextVNode(" ("), vue.createElementVNode("span", { class: "text-key", innerHTML: vue.unref(store).currentKey }, null, 8, _hoisted_4$3), vue.createTextVNode(") ") ]), _: 1 }, 8, ["onclick-left", "onclick-middle", "onclick-right"]), vue.createElementVNode("div", _hoisted_5$1, [ vue.createElementVNode("input", { type: "range", min: "-11", max: "11", value: vue.unref(store).currentCapo, onInput: _cache[0] || (_cache[0] = ($event) => { vue.unref(store).transpose = $event.target.value - vue.unref(store).originalCapo; }) }, null, 40, _hoisted_6$1) ]), vue.createElementVNode("div", _hoisted_7, [ vue.createElementVNode("button", { class: "instrument-select-button", onClick: _cache[1] || (_cache[1] = () => { vue.unref(switchInstrument)(""); }) }, " 無 "), vue.createElementVNode("button", { class: "instrument-select-button", onClick: _cache[2] || (_cache[2] = () => { vue.unref(switchInstrument)("guitar"); }) }, " 吉他 "), vue.createElementVNode("button", { class: "instrument-select-button", onClick: _cache[3] || (_cache[3] = () => { vue.unref(switchInstrument)("ukulele"); }) }, " 烏克莉莉 ") ]) ]) ], 512), [ [vue.vShow, vue.unref(store).isPopupShow.sheet] ]) ]), _: 1 }); }; } }; const SheetPopup = /* @__PURE__ */ _export_sfc(_sfc_main$c, [["__scopeId", "data-v-364cc4c8"]]); const _hoisted_1$a = { key: 0, class: "chord-container" }; const _hoisted_2$9 = { class: "chord-name" }; const _hoisted_3$4 = ["chord-name"]; const _sfc_main$b = { __name: "ChordChart", props: { chord: String }, setup(__props) { const props = __props; const chordRef = vue.ref(void 0); const chordShapes = getChordShapes(); const isChordExist = vue.ref(true); vue.onMounted(() => { var _a; const formattedChordKey = convertChordName(props.chord); const chordShape = chordShapes[formattedChordKey]; if (!chordShape) { return isChordExist.value = false; } const chordObject = { ...chordShape, // position: chordShape.position, // positionText: chordShape.position_text, barres: (_a = chordShape.bars) == null ? void 0 : _a.map((barre) => { return { ...barre, fromString: barre.from_string, toString: barre.to_string }; }), chord: chordShape.chord.map(([stringNum, fretNum]) => { const raw = [stringNum, fretNum]; if (isNaN(+fretNum)) { return raw; } let newFretNum = fretNum; newFretNum += chordShape.position || 0; newFretNum -= chordShape.position_text || 0; return [stringNum, newFretNum]; }) }; vue.nextTick(() => { const width = chordRef.value.clientWidth; const chordBoxSelector = `.chord-chart[chord-name="${props.chord}"]`; const chordBox = new vexchords.ChordBox(chordBoxSelector, { width, height: width * 1.25, circleRadius: 5, numStrings: 6, numFrets: 5, showTuning: false, defaultColor: "#444", bgColor: "transparent" }); chordBox.draw(chordObject); }); }); return (_ctx, _cache) => { return isChordExist.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$a, [ vue.createElementVNode("div", _hoisted_2$9, vue.toDisplayString(props.chord), 1), vue.createElementVNode("div", { class: "chord-chart", "chord-name": props.chord, ref_key: "chordRef", ref: chordRef }, null, 8, _hoisted_3$4) ])) : vue.createCommentVNode("", true); }; } }; const ChordChart = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["__scopeId", "data-v-735734f6"]]); const _withScopeId$1 = (n) => (vue.pushScopeId("data-v-110e9a91"), n = n(), vue.popScopeId(), n); const _hoisted_1$9 = { id: "plus91-chord-popup" }; const _hoisted_2$8 = { class: "banner" }; const _hoisted_3$3 = /* @__PURE__ */ _withScopeId$1(() => /* @__PURE__ */ vue.createElementVNode("section", null, "此處的和弦圖示僅供參考,尤其把位部分較常出現錯誤,請注意。", -1)); const _hoisted_4$2 = { class: "chord-popup-container" }; const _sfc_main$a = { __name: "ChordPopup", setup(__props) { const store = useStore(); const chordList = vue.ref([]); vue.watch(store.isPopupShow, () => { if (!store.isPopupShow.chord) { return; } chordList.value = getChordList(); }); return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$9, [ vue.createElementVNode("div", _hoisted_2$8, [ vue.createVNode(BootstrapIcon, { icon: "info-circle-fill", color: "inherit", size: "inherit" }), _hoisted_3$3 ]), vue.createElementVNode("div", _hoisted_4$2, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(chordList.value, (chord) => { return vue.openBlock(), vue.createBlock(ChordChart, { key: `${chord}_${( new Date()).getTime()}`, chord }, null, 8, ["chord"]); }), 128)) ]) ], 512), [ [vue.vShow, vue.unref(store).isPopupShow.chord] ]) ]), _: 1 }); }; } }; const ChordPopup = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__scopeId", "data-v-110e9a91"]]); const _hoisted_1$8 = { id: "plus91-font-popup" }; const _hoisted_2$7 = { class: "font-popup-container" }; const _sfc_main$9 = { __name: "FontSizePopup", setup(__props) { const store = useStore(); const getFontSize = vue.computed(() => { return store.originalFontSize + store.fontSizeDelta; }); return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$8, [ vue.createElementVNode("div", _hoisted_2$7, [ vue.createVNode(AdjustWidget, { "onclick-left": () => { vue.unref(store).fontSizeDelta--; }, "onclick-middle": () => { vue.unref(store).fontSizeDelta = 0; }, "onclick-right": () => { vue.unref(store).fontSizeDelta++; }, "disabled-left": getFontSize.value <= 8, "disabled-right": getFontSize.value >= 30 }, { default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString(getFontSize.value) + "px ", 1) ]), _: 1 }, 8, ["onclick-left", "onclick-middle", "onclick-right", "disabled-left", "disabled-right"]) ]) ], 512), [ [vue.vShow, vue.unref(store).isPopupShow.font] ]) ]), _: 1 }); }; } }; const FontSizePopup = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__scopeId", "data-v-3be5b338"]]); const _withScopeId = (n) => (vue.pushScopeId("data-v-430d5768"), n = n(), vue.popScopeId(), n); const _hoisted_1$7 = { id: "plus91-settings-popup" }; const _hoisted_2$6 = { class: "settings-popup-container" }; const _hoisted_3$2 = { class: "setting-item" }; const _hoisted_4$1 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", null, "深色模式", -1)); const _hoisted_5 = { class: "setting-item" }; const _hoisted_6 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", null, "協助測試雲端備份樂譜功能", -1)); const _sfc_main$8 = { __name: "SettingsPopup", setup(__props) { const store = useStore(); return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$7, [ vue.createElementVNode("div", _hoisted_2$6, [ vue.createElementVNode("label", _hoisted_3$2, [ _hoisted_4$1, vue.withDirectives(vue.createElementVNode("input", { type: "checkbox", "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => vue.unref(store).isDarkMode = $event) }, null, 512), [ [vue.vModelCheckbox, vue.unref(store).isDarkMode] ]) ]), vue.createElementVNode("label", _hoisted_5, [ _hoisted_6, vue.withDirectives(vue.createElementVNode("input", { type: "checkbox", "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => vue.unref(store).agreeToArchiveSheet = $event) }, null, 512), [ [vue.vModelCheckbox, vue.unref(store).agreeToArchiveSheet] ]) ]) ]) ], 512), [ [vue.vShow, vue.unref(store).isPopupShow.settings] ]) ]), _: 1 }); }; } }; const SettingsPopup = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-430d5768"]]); const _hoisted_1$6 = { class: "icon-button" }; const _hoisted_2$5 = { class: "button-text" }; const _sfc_main$7 = { __name: "MenuButton", props: { icon: String, name: String, color: String }, setup(__props) { vue.useCssVars((_ctx) => ({ "5908aad9": __props.color })); const props = __props; return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$6, [ vue.createVNode(ToolbarIcon, { icon: props.icon, color: props.color }, null, 8, ["icon", "color"]), vue.createElementVNode("div", _hoisted_2$5, vue.toDisplayString(props.name), 1) ]); }; } }; const MenuButton = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["__scopeId", "data-v-e9653884"]]); const _hoisted_1$5 = { id: "plus91-menu-popup" }; const _hoisted_2$4 = { class: "menu-popup-container" }; const BUTTON_COLOR = "#555"; const _sfc_main$6 = { __name: "MenuPopup", setup(__props) { const store = useStore(); const captureAsImage = () => { const content = document.querySelector("section.content"); html2canvas(content).then((canvas) => { const newWindow = window.open(); newWindow.document.write(`<img src="${canvas.toDataURL()}" />`); }); }; const searchOnYoutube = () => { const chordSheetDocument = new ChordSheetDocument(); const title = chordSheetDocument.getTitle(); const artist = chordSheetDocument.getSinger(); const url = `https://www.youtube.com/results?search_query=${title}+${artist}`; window.open(url, "_blank").focus(); }; const goToGithubPage = () => { const url = "https://github.com/DonkeyBear/91Plus/blob/main/README.md"; window.open(url, "_blank").focus(); }; return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$5, [ vue.createElementVNode("div", _hoisted_2$4, [ vue.createVNode(MenuButton, { icon: "keyboard", name: "快捷鍵", color: BUTTON_COLOR, onClick: _cache[0] || (_cache[0] = () => { vue.unref(store).togglePopup("hotkey"); }) }), vue.createVNode(MenuButton, { icon: "file-earmark-image", name: "擷取為圖片", color: BUTTON_COLOR, onClick: captureAsImage }), vue.createVNode(MenuButton, { icon: "youtube", name: "搜尋 YouTube", color: BUTTON_COLOR, onClick: searchOnYoutube }), vue.createVNode(MenuButton, { icon: "github", name: "關於 91 Plus", color: BUTTON_COLOR, onClick: goToGithubPage }) ]) ], 512), [ [vue.vShow, vue.unref(store).isPopupShow.menu] ]) ]), _: 1 }); }; } }; const MenuPopup = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-fca8d708"]]); const _hoisted_1$4 = { class: "hotkey-item" }; const _hoisted_2$3 = { key: 0, class: "hotkeys" }; const _hoisted_3$1 = { key: 1, class: "hr" }; const _sfc_main$5 = { __name: "HotkeyItem", props: { hotkey: { type: String, required: false }, desc: String }, setup(__props) { const props = __props; const hotkeyList = props.hotkey.split(" "); return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$4, [ vue.createElementVNode("div", { class: vue.normalizeClass(["desc", { "title": !__props.hotkey }]) }, vue.toDisplayString(__props.desc), 3), __props.hotkey ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2$3, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(hotkeyList), (key) => { return vue.openBlock(), vue.createElementBlock("kbd", { key: `${key}_${__props.hotkey}_${__props.desc}` }, vue.toDisplayString(key), 1); }), 128)) ])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_3$1)) ]); }; } }; const HotkeyItem = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-3c43f6cf"]]); const hotkeysLeft = [ { hotkey: "空白鍵", desc: "開啟 / 關閉功能選單" }, { hotkey: "ESC", desc: "關閉功能選單" }, { hotkey: "/", desc: "切換至搜尋框" } ]; const hotkeysRight = [ { hotkey: "", desc: "移調選單開啟時" }, { hotkey: "← →", desc: "移調" }, { hotkey: "↓", desc: "移回初始調" }, { hotkey: "", desc: "在搜尋框內" }, { hotkey: "Enter", desc: "搜尋" }, { hotkey: "ESC", desc: "跳出搜尋框" } ]; const hotkeyData = { hotkeysLeft, hotkeysRight }; const _hoisted_1$3 = { id: "plus91-hotkey-popup" }; const _hoisted_2$2 = { class: "hotkey-popup-container" }; const _hoisted_3 = { class: "left-part" }; const _hoisted_4 = { class: "right-part" }; const _sfc_main$4 = { __name: "HotkeyPopup", setup(__props) { const store = useStore(); return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$3, [ vue.createElementVNode("div", _hoisted_2$2, [ vue.createElementVNode("section", _hoisted_3, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(hotkeyData).hotkeysLeft, (item, index) => { return vue.openBlock(), vue.createBlock(HotkeyItem, { key: `${item.hotkey}_${item.desc}_${index}`, hotkey: item.hotkey, desc: item.desc }, null, 8, ["hotkey", "desc"]); }), 128)) ]), vue.createElementVNode("section", _hoisted_4, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(hotkeyData).hotkeysRight, (item, index) => { return vue.openBlock(), vue.createBlock(HotkeyItem, { key: `${item.hotkey}_${item.desc}_${index}`, hotkey: item.hotkey, desc: item.desc }, null, 8, ["hotkey", "desc"]); }), 128)) ]) ]) ], 512), [ [vue.vShow, vue.unref(store).isPopupShow.hotkey] ]) ]), _: 1 }); }; } }; const HotkeyPopup = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-eb86b87c"]]); const _hoisted_1$2 = { id: "plus91-footer" }; const _hoisted_2$1 = { class: "footer-container" }; const _sfc_main$3 = { __name: "AppFooter", props: { active: Boolean }, setup(__props) { const store = useStore(); const props = __props; return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$2, [ vue.createElementVNode("div", _hoisted_2$1, [ vue.createVNode(ToolbarIcon, { icon: "music-note-beamed", stroke: ".05rem", active: vue.unref(store).isPopupShow.sheet, onClick: _cache[0] || (_cache[0] = ($event) => vue.unref(store).togglePopup("sheet")) }, null, 8, ["active"]), vue.createVNode(ToolbarIcon, { icon: "table", active: vue.unref(store).isPopupShow.chord, onClick: _cache[1] || (_cache[1] = ($event) => vue.unref(store).togglePopup("chord")) }, null, 8, ["active"]), vue.createVNode(ToolbarIcon, { icon: "type", stroke: ".05rem", active: vue.unref(store).isPopupShow.font, onClick: _cache[2] || (_cache[2] = ($event) => vue.unref(store).togglePopup("font")) }, null, 8, ["active"]), vue.createVNode(ToolbarIcon, { icon: "gear-wide-connected", active: vue.unref(store).isPopupShow.settings, onClick: _cache[3] || (_cache[3] = ($event) => vue.unref(store).togglePopup("settings")) }, null, 8, ["active"]), vue.createVNode(ToolbarIcon, { icon: "list", stroke: ".05rem", active: vue.unref(store).isPopupShow.menu, onClick: _cache[4] || (_cache[4] = ($event) => vue.unref(store).togglePopup("menu")) }, null, 8, ["active"]), vue.createVNode(SheetPopup), vue.createVNode(ChordPopup), vue.createVNode(FontSizePopup), vue.createVNode(SettingsPopup), vue.createVNode(MenuPopup), vue.createVNode(HotkeyPopup) ]) ], 512), [ [vue.vShow, props.active] ]) ]), _: 1 }); }; } }; const AppFooter = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-d7417138"]]); const _hoisted_1$1 = { id: "plus91-header" }; const _hoisted_2 = { class: "header-container" }; const _sfc_main$2 = { __name: "AppHeader", props: { active: Boolean }, setup(__props) { const props = __props; const searchText = vue.ref(""); const search = () => { if (!searchText.value) { return; } const url = `https://www.91pu.com.tw/plus/search.php?keyword=${searchText.value}`; window.open(url, "_blank").focus(); searchText.value = ""; }; const backToPreviousPage = () => { history.back(); }; return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1$1, [ vue.createElementVNode("div", _hoisted_2, [ vue.createVNode(ToolbarIcon, { icon: "chevron-left", stroke: ".04rem", onClick: backToPreviousPage }), vue.withDirectives(vue.createElementVNode("input", { type: "text", placeholder: "91 Plus", "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchText.value = $event), onKeydown: [ vue.withKeys(search, ["enter"]), _cache[1] || (_cache[1] = vue.withKeys((event) => { event.target.blur(); }, ["esc"])) ] }, null, 544), [ [ vue.vModelText, searchText.value, void 0, { trim: true } ] ]), vue.createVNode(ToolbarIcon, { icon: "search", stroke: ".03rem", onClick: _cache[2] || (_cache[2] = ($event) => search()) }) ]) ], 512), [ [vue.vShow, props.active] ]) ]), _: 1 }); }; } }; const AppHeader = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-a1982e9d"]]); const _hoisted_1 = { id: "dark-mode-overlay" }; const _sfc_main$1 = { __name: "DarkModeOverlay", props: { active: Boolean }, setup(__props) { const props = __props; return (_ctx, _cache) => { return vue.openBlock(), vue.createBlock(vue.Transition, { name: "fade" }, { default: vue.withCtx(() => [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1, null, 512), [ [vue.vShow, props.active] ]) ]), _: 1 }); }; } }; const DarkModeOverlay = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-a9f152b4"]]); const _sfc_main = { __name: "App", setup(__props) { const store = useStore(); return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [ vue.createVNode(TriggerOverlay, { onClick: vue.unref(store).toggleToolbars }, null, 8, ["onClick"]), vue.createVNode(AppHeader, { active: vue.unref(store).isToolbarsShow }, null, 8, ["active"]), vue.createVNode(AppFooter, { active: vue.unref(store).isToolbarsShow }, null, 8, ["active"]), vue.createVNode(DarkModeOverlay, { active: vue.unref(store).isDarkMode }, null, 8, ["active"]) ], 64); }; } }; function init() { redirect(); injectGtag(); initMutationObserver(); handleEvents(); const storeHandler = new StoreHandler().start(); onDomReady(() => { changeTitle(); storeHandler.initState(); const store = useStore(); if (store.agreeToArchiveSheet) { archiveChordSheet(); } }); } const pinia = pinia$1.createPinia(); pinia.use(piniaPluginPersistedstate); vue.createApp(_sfc_main).use(pinia).mount( (() => { const app = document.createElement("div"); app.id = "vue-91plus"; document.body.append(app); return app; })() ); init(); })(Vue, pinia, pinia-plugin-persistedstate, zipson, vexchords, html2canvas);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址