91 Plus

自由轉調、輕鬆練歌,打造 91 譜的最佳體驗!

  1. // ==UserScript==
  2. // @name 91 Plus
  3. // @namespace https://github.com/DonkeyBear
  4. // @version 1.8.4
  5. // @author DonkeyBear
  6. // @description 自由轉調、輕鬆練歌,打造 91 譜的最佳體驗!
  7. // @icon https://www.91pu.com.tw/icons/favicon-32x32.png
  8. // @match *://www.91pu.com.tw/m/*
  9. // @match *://www.91pu.com.tw/song/*
  10. // @require https://cdn.jsdelivr.net/npm/vue@3.3.10/dist/vue.global.prod.js
  11. // @require https://cdn.jsdelivr.net/npm/zipson@0.2.12/dist/zipson.min.js
  12. // @require https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js
  13. // @require https://cdn.jsdelivr.net/npm/vexchords@1.2.0/dist/vexchords.dev.min.js
  14. // @grant GM_addStyle
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant unsafeWindow
  18. // @antifeature tracking 使用 Google Analytics 了解使用情況
  19. // ==/UserScript==
  20.  
  21. (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/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css";#trigger-overlay[data-v-658df74c]{position:fixed;top:0;left:0;right:0;bottom:0;z-index:500}.bi[data-v-6e52047a]{color:var(--2b53b793);font-size:var(--4bbf91d1);-webkit-text-stroke:var(--5ab9f408) var(--2b53b793)}.bi[data-v-6e52047a]:before{transition:text-shadow .2s}.bi[active=true][data-v-6e52047a]:before{text-shadow:0 0 .5rem rgb(75,156,169)}.toolbar-icon[data-v-3dbcd695]{cursor:pointer;padding:.25rem .75rem;display:flex;flex-direction:column;align-items:center;gap:.15rem}.toolbar-icon-text[data-v-3dbcd695]{color:color-mix(in srgb,var(--52bb32ad) 70%,rgba(75,156,169,.65));font-size:.5rem;letter-spacing:.15rem;margin-right:-.15rem}.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-f161c46c],.slide-and-fade-leave-active[data-v-f161c46c]{transition:all .2s}.slide-and-fade-enter-from[data-v-f161c46c],.slide-and-fade-leave-to[data-v-f161c46c]{transform:translateY(10%);opacity:0}#plus91-sheet-popup[data-v-f161c46c]{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-f161c46c]::-webkit-scrollbar{display:none}.transpose-range-container[data-v-f161c46c]{margin-top:1rem}.transpose-range-container input[type=range][data-v-f161c46c]{width:100%}.instrument-select-container[data-v-f161c46c]{display:flex;border:1px solid lightgray;border-radius:.25rem;margin-top:1rem;background:white}.instrument-select-container .instrument-select-button[data-v-f161c46c]{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-f161c46c]:last-child{border:0;border-radius:0 .25rem .25rem 0}.instrument-select-container .instrument-select-button[data-v-f161c46c]:first-child{border-radius:.25rem 0 0 .25rem}.instrument-select-container .instrument-select-button[data-v-f161c46c]: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-2210cdf0],.slide-and-fade-leave-active[data-v-2210cdf0]{transition:all .2s}.slide-and-fade-enter-from[data-v-2210cdf0],.slide-and-fade-leave-to[data-v-2210cdf0]{transform:translateY(10%);opacity:0}#plus91-chord-popup[data-v-2210cdf0]{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-2210cdf0]::-webkit-scrollbar{display:none}#plus91-chord-popup .banner[data-v-2210cdf0]{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-2210cdf0]{flex-grow:1;margin-left:.5rem}#plus91-chord-popup .chord-popup-container[data-v-2210cdf0]{display:grid;grid-template-columns:repeat(6,1fr);column-gap:.5rem;padding-top:.4rem}#plus91-chord-popup.banner-only .banner[data-v-2210cdf0]{margin-bottom:0;background:rgba(246,210,102,.25);color:color-mix(in srgb,#f6d266 50%,black 35%)}#plus91-chord-popup.banner-only .chord-popup-container[data-v-2210cdf0]{padding-top:0}.slide-and-fade-enter-active[data-v-eff17405],.slide-and-fade-leave-active[data-v-eff17405]{transition:all .2s}.slide-and-fade-enter-from[data-v-eff17405],.slide-and-fade-leave-to[data-v-eff17405]{transform:translateY(10%);opacity:0}#plus91-font-popup[data-v-eff17405]{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-eff17405]::-webkit-scrollbar{display:none}.slide-and-fade-enter-active[data-v-e329f5af],.slide-and-fade-leave-active[data-v-e329f5af]{transition:all .2s}.slide-and-fade-enter-from[data-v-e329f5af],.slide-and-fade-leave-to[data-v-e329f5af]{transform:translateY(10%);opacity:0}#plus91-settings-popup[data-v-e329f5af]{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-e329f5af]::-webkit-scrollbar{display:none}#plus91-settings-popup .setting-item[data-v-e329f5af]{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;border-radius:.5rem;cursor:pointer}#plus91-settings-popup .setting-item[data-v-e329f5af]:hover{background:rgba(0,0,0,.05)}.icon-button[data-v-e9902592]{display:flex;flex-direction:column;align-items:center;cursor:pointer;padding:0 .6rem .4rem;border-radius:.25rem}.icon-button[data-v-e9902592]:hover{background:rgba(0,0,0,.025)}.icon-button .button-text[data-v-e9902592]{font-size:.5rem;color:var(--9047bc34)}.slide-and-fade-enter-active[data-v-47af8eb5],.slide-and-fade-leave-active[data-v-47af8eb5]{transition:all .2s}.slide-and-fade-enter-from[data-v-47af8eb5],.slide-and-fade-leave-to[data-v-47af8eb5]{transform:translateY(10%);opacity:0}#plus91-menu-popup[data-v-47af8eb5]{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-47af8eb5]::-webkit-scrollbar{display:none}#plus91-menu-popup .menu-popup-container[data-v-47af8eb5]{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-4e274b3c],.slide-leave-active[data-v-4e274b3c]{transition:transform .2s}.slide-enter-from[data-v-4e274b3c],.slide-leave-to[data-v-4e274b3c]{transform:translateY(100%)}#plus91-footer[data-v-4e274b3c]{z-index:1000;display:flex;justify-content:center;position:fixed;left:0;right:0;bottom:0}.footer-container[data-v-4e274b3c]{width:min(100vw,768px);background:rgba(75,156,169,.65);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);padding:.25rem .75rem .75rem;display:flex;justify-content:space-between;align-items:center;border-top:1px solid rgb(90,140,160)}@media (min-width: 768px){.footer-container[data-v-4e274b3c]{border-radius:1rem 1rem 0 0}}.slide-enter-active[data-v-5ddafe3d],.slide-leave-active[data-v-5ddafe3d]{transition:transform .2s}.slide-enter-from[data-v-5ddafe3d],.slide-leave-to[data-v-5ddafe3d]{transform:translateY(-100%)}#plus91-header[data-v-5ddafe3d]{z-index:1000;display:flex;justify-content:center;position:fixed;left:0;right:0;top:0}.header-container[data-v-5ddafe3d]{width:min(100vw,768px);background:rgba(75,156,169,.65);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);padding:.25rem .75rem;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid rgb(90,140,160)}@media (min-width: 768px){.header-container[data-v-5ddafe3d]{border-radius:0 0 1rem 1rem}}.header-container input[data-v-5ddafe3d]{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-5ddafe3d]:focus-visible{outline:0;opacity:1}.fade-enter-active[data-v-6cf58435],.fade-leave-active[data-v-6cf58435]{transition:opacity .2s}.fade-enter-from[data-v-6cf58435],.fade-leave-to[data-v-6cf58435]{opacity:0}#dark-mode-overlay[data-v-6cf58435]{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,[class^=AD2M],[id^=adGeek]{display:none!important} ');
  22.  
  23. (function (vue, zipson, vexchords, html2canvas) {
  24. 'use strict';
  25.  
  26. var __defProp = Object.defineProperty;
  27. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  28. var __publicField = (obj, key, value) => {
  29. __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  30. return value;
  31. };
  32. var __accessCheck = (obj, member, msg) => {
  33. if (!member.has(obj))
  34. throw TypeError("Cannot " + msg);
  35. };
  36. var __privateGet = (obj, member, getter) => {
  37. __accessCheck(obj, member, "read from private field");
  38. return getter ? getter.call(obj) : member.get(obj);
  39. };
  40. var __privateAdd = (obj, member, value) => {
  41. if (member.has(obj))
  42. throw TypeError("Cannot add the same private member more than once");
  43. member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
  44. };
  45. var __privateSet = (obj, member, value, setter) => {
  46. __accessCheck(obj, member, "write to private field");
  47. setter ? setter.call(obj, value) : member.set(obj, value);
  48. return value;
  49. };
  50. var __privateMethod = (obj, member, method) => {
  51. __accessCheck(obj, member, "access private method");
  52. return method;
  53. };
  54. var _unformat, unformat_fn, _store, _watchTranspose, watchTranspose_fn, _watchFontSize, watchFontSize_fn;
  55. var isVue2 = false;
  56. /*!
  57. * pinia v2.1.7
  58. * (c) 2023 Eduardo San Martin Morote
  59. * @license MIT
  60. */
  61. let activePinia;
  62. const setActivePinia = (pinia2) => activePinia = pinia2;
  63. const piniaSymbol = (
  64. /* istanbul ignore next */
  65. Symbol()
  66. );
  67. function isPlainObject(o) {
  68. return o && typeof o === "object" && Object.prototype.toString.call(o) === "[object Object]" && typeof o.toJSON !== "function";
  69. }
  70. var MutationType;
  71. (function(MutationType2) {
  72. MutationType2["direct"] = "direct";
  73. MutationType2["patchObject"] = "patch object";
  74. MutationType2["patchFunction"] = "patch function";
  75. })(MutationType || (MutationType = {}));
  76. function createPinia() {
  77. const scope = vue.effectScope(true);
  78. const state = scope.run(() => vue.ref({}));
  79. let _p = [];
  80. let toBeInstalled = [];
  81. const pinia2 = vue.markRaw({
  82. install(app) {
  83. setActivePinia(pinia2);
  84. {
  85. pinia2._a = app;
  86. app.provide(piniaSymbol, pinia2);
  87. app.config.globalProperties.$pinia = pinia2;
  88. toBeInstalled.forEach((plugin) => _p.push(plugin));
  89. toBeInstalled = [];
  90. }
  91. },
  92. use(plugin) {
  93. if (!this._a && !isVue2) {
  94. toBeInstalled.push(plugin);
  95. } else {
  96. _p.push(plugin);
  97. }
  98. return this;
  99. },
  100. _p,
  101. // it's actually undefined here
  102. // @ts-expect-error
  103. _a: null,
  104. _e: scope,
  105. _s: /* @__PURE__ */ new Map(),
  106. state
  107. });
  108. return pinia2;
  109. }
  110. const noop = () => {
  111. };
  112. function addSubscription(subscriptions, callback, detached, onCleanup = noop) {
  113. subscriptions.push(callback);
  114. const removeSubscription = () => {
  115. const idx = subscriptions.indexOf(callback);
  116. if (idx > -1) {
  117. subscriptions.splice(idx, 1);
  118. onCleanup();
  119. }
  120. };
  121. if (!detached && vue.getCurrentScope()) {
  122. vue.onScopeDispose(removeSubscription);
  123. }
  124. return removeSubscription;
  125. }
  126. function triggerSubscriptions(subscriptions, ...args) {
  127. subscriptions.slice().forEach((callback) => {
  128. callback(...args);
  129. });
  130. }
  131. const fallbackRunWithContext = (fn) => fn();
  132. function mergeReactiveObjects(target, patchToApply) {
  133. if (target instanceof Map && patchToApply instanceof Map) {
  134. patchToApply.forEach((value, key) => target.set(key, value));
  135. }
  136. if (target instanceof Set && patchToApply instanceof Set) {
  137. patchToApply.forEach(target.add, target);
  138. }
  139. for (const key in patchToApply) {
  140. if (!patchToApply.hasOwnProperty(key))
  141. continue;
  142. const subPatch = patchToApply[key];
  143. const targetValue = target[key];
  144. if (isPlainObject(targetValue) && isPlainObject(subPatch) && target.hasOwnProperty(key) && !vue.isRef(subPatch) && !vue.isReactive(subPatch)) {
  145. target[key] = mergeReactiveObjects(targetValue, subPatch);
  146. } else {
  147. target[key] = subPatch;
  148. }
  149. }
  150. return target;
  151. }
  152. const skipHydrateSymbol = (
  153. /* istanbul ignore next */
  154. Symbol()
  155. );
  156. function shouldHydrate(obj) {
  157. return !isPlainObject(obj) || !obj.hasOwnProperty(skipHydrateSymbol);
  158. }
  159. const { assign } = Object;
  160. function isComputed(o) {
  161. return !!(vue.isRef(o) && o.effect);
  162. }
  163. function createOptionsStore(id, options, pinia2, hot) {
  164. const { state, actions, getters } = options;
  165. const initialState = pinia2.state.value[id];
  166. let store;
  167. function setup() {
  168. if (!initialState && true) {
  169. {
  170. pinia2.state.value[id] = state ? state() : {};
  171. }
  172. }
  173. const localState = vue.toRefs(pinia2.state.value[id]);
  174. return assign(localState, actions, Object.keys(getters || {}).reduce((computedGetters, name) => {
  175. computedGetters[name] = vue.markRaw(vue.computed(() => {
  176. setActivePinia(pinia2);
  177. const store2 = pinia2._s.get(id);
  178. return getters[name].call(store2, store2);
  179. }));
  180. return computedGetters;
  181. }, {}));
  182. }
  183. store = createSetupStore(id, setup, options, pinia2, hot, true);
  184. return store;
  185. }
  186. function createSetupStore($id, setup, options = {}, pinia2, hot, isOptionsStore) {
  187. let scope;
  188. const optionsForPlugin = assign({ actions: {} }, options);
  189. const $subscribeOptions = {
  190. deep: true
  191. // flush: 'post',
  192. };
  193. let isListening;
  194. let isSyncListening;
  195. let subscriptions = [];
  196. let actionSubscriptions = [];
  197. let debuggerEvents;
  198. const initialState = pinia2.state.value[$id];
  199. if (!isOptionsStore && !initialState && true) {
  200. {
  201. pinia2.state.value[$id] = {};
  202. }
  203. }
  204. vue.ref({});
  205. let activeListener;
  206. function $patch(partialStateOrMutator) {
  207. let subscriptionMutation;
  208. isListening = isSyncListening = false;
  209. if (typeof partialStateOrMutator === "function") {
  210. partialStateOrMutator(pinia2.state.value[$id]);
  211. subscriptionMutation = {
  212. type: MutationType.patchFunction,
  213. storeId: $id,
  214. events: debuggerEvents
  215. };
  216. } else {
  217. mergeReactiveObjects(pinia2.state.value[$id], partialStateOrMutator);
  218. subscriptionMutation = {
  219. type: MutationType.patchObject,
  220. payload: partialStateOrMutator,
  221. storeId: $id,
  222. events: debuggerEvents
  223. };
  224. }
  225. const myListenerId = activeListener = Symbol();
  226. vue.nextTick().then(() => {
  227. if (activeListener === myListenerId) {
  228. isListening = true;
  229. }
  230. });
  231. isSyncListening = true;
  232. triggerSubscriptions(subscriptions, subscriptionMutation, pinia2.state.value[$id]);
  233. }
  234. const $reset = isOptionsStore ? function $reset2() {
  235. const { state } = options;
  236. const newState = state ? state() : {};
  237. this.$patch(($state) => {
  238. assign($state, newState);
  239. });
  240. } : (
  241. /* istanbul ignore next */
  242. noop
  243. );
  244. function $dispose() {
  245. scope.stop();
  246. subscriptions = [];
  247. actionSubscriptions = [];
  248. pinia2._s.delete($id);
  249. }
  250. function wrapAction(name, action) {
  251. return function() {
  252. setActivePinia(pinia2);
  253. const args = Array.from(arguments);
  254. const afterCallbackList = [];
  255. const onErrorCallbackList = [];
  256. function after(callback) {
  257. afterCallbackList.push(callback);
  258. }
  259. function onError(callback) {
  260. onErrorCallbackList.push(callback);
  261. }
  262. triggerSubscriptions(actionSubscriptions, {
  263. args,
  264. name,
  265. store,
  266. after,
  267. onError
  268. });
  269. let ret;
  270. try {
  271. ret = action.apply(this && this.$id === $id ? this : store, args);
  272. } catch (error) {
  273. triggerSubscriptions(onErrorCallbackList, error);
  274. throw error;
  275. }
  276. if (ret instanceof Promise) {
  277. return ret.then((value) => {
  278. triggerSubscriptions(afterCallbackList, value);
  279. return value;
  280. }).catch((error) => {
  281. triggerSubscriptions(onErrorCallbackList, error);
  282. return Promise.reject(error);
  283. });
  284. }
  285. triggerSubscriptions(afterCallbackList, ret);
  286. return ret;
  287. };
  288. }
  289. const partialStore = {
  290. _p: pinia2,
  291. // _s: scope,
  292. $id,
  293. $onAction: addSubscription.bind(null, actionSubscriptions),
  294. $patch,
  295. $reset,
  296. $subscribe(callback, options2 = {}) {
  297. const removeSubscription = addSubscription(subscriptions, callback, options2.detached, () => stopWatcher());
  298. const stopWatcher = scope.run(() => vue.watch(() => pinia2.state.value[$id], (state) => {
  299. if (options2.flush === "sync" ? isSyncListening : isListening) {
  300. callback({
  301. storeId: $id,
  302. type: MutationType.direct,
  303. events: debuggerEvents
  304. }, state);
  305. }
  306. }, assign({}, $subscribeOptions, options2)));
  307. return removeSubscription;
  308. },
  309. $dispose
  310. };
  311. const store = vue.reactive(partialStore);
  312. pinia2._s.set($id, store);
  313. const runWithContext = pinia2._a && pinia2._a.runWithContext || fallbackRunWithContext;
  314. const setupStore = runWithContext(() => pinia2._e.run(() => (scope = vue.effectScope()).run(setup)));
  315. for (const key in setupStore) {
  316. const prop = setupStore[key];
  317. if (vue.isRef(prop) && !isComputed(prop) || vue.isReactive(prop)) {
  318. if (!isOptionsStore) {
  319. if (initialState && shouldHydrate(prop)) {
  320. if (vue.isRef(prop)) {
  321. prop.value = initialState[key];
  322. } else {
  323. mergeReactiveObjects(prop, initialState[key]);
  324. }
  325. }
  326. {
  327. pinia2.state.value[$id][key] = prop;
  328. }
  329. }
  330. } else if (typeof prop === "function") {
  331. const actionValue = wrapAction(key, prop);
  332. {
  333. setupStore[key] = actionValue;
  334. }
  335. optionsForPlugin.actions[key] = prop;
  336. } else
  337. ;
  338. }
  339. {
  340. assign(store, setupStore);
  341. assign(vue.toRaw(store), setupStore);
  342. }
  343. Object.defineProperty(store, "$state", {
  344. get: () => pinia2.state.value[$id],
  345. set: (state) => {
  346. $patch(($state) => {
  347. assign($state, state);
  348. });
  349. }
  350. });
  351. pinia2._p.forEach((extender) => {
  352. {
  353. assign(store, scope.run(() => extender({
  354. store,
  355. app: pinia2._a,
  356. pinia: pinia2,
  357. options: optionsForPlugin
  358. })));
  359. }
  360. });
  361. if (initialState && isOptionsStore && options.hydrate) {
  362. options.hydrate(store.$state, initialState);
  363. }
  364. isListening = true;
  365. isSyncListening = true;
  366. return store;
  367. }
  368. function defineStore(idOrOptions, setup, setupOptions) {
  369. let id;
  370. let options;
  371. const isSetupStore = typeof setup === "function";
  372. if (typeof idOrOptions === "string") {
  373. id = idOrOptions;
  374. options = isSetupStore ? setupOptions : setup;
  375. } else {
  376. options = idOrOptions;
  377. id = idOrOptions.id;
  378. }
  379. function useStore2(pinia2, hot) {
  380. const hasContext = vue.hasInjectionContext();
  381. pinia2 = // in test mode, ignore the argument provided as we can always retrieve a
  382. // pinia instance with getActivePinia()
  383. pinia2 || (hasContext ? vue.inject(piniaSymbol, null) : null);
  384. if (pinia2)
  385. setActivePinia(pinia2);
  386. pinia2 = activePinia;
  387. if (!pinia2._s.has(id)) {
  388. if (isSetupStore) {
  389. createSetupStore(id, setup, options, pinia2);
  390. } else {
  391. createOptionsStore(id, options, pinia2);
  392. }
  393. }
  394. const store = pinia2._s.get(id);
  395. return store;
  396. }
  397. useStore2.$id = id;
  398. return useStore2;
  399. }
  400. function isObject(v) {
  401. return typeof v === "object" && v !== null;
  402. }
  403. function normalizeOptions(options, factoryOptions) {
  404. options = isObject(options) ? options : /* @__PURE__ */ Object.create(null);
  405. return new Proxy(options, {
  406. get(target, key, receiver) {
  407. if (key === "key")
  408. return Reflect.get(target, key, receiver);
  409. return Reflect.get(target, key, receiver) || Reflect.get(factoryOptions, key, receiver);
  410. }
  411. });
  412. }
  413. function get(state, path) {
  414. return path.reduce((obj, p) => {
  415. return obj == null ? void 0 : obj[p];
  416. }, state);
  417. }
  418. function set(state, path, val) {
  419. return path.slice(0, -1).reduce((obj, p) => {
  420. if (/^(__proto__)$/.test(p))
  421. return {};
  422. else
  423. return obj[p] = obj[p] || {};
  424. }, state)[path[path.length - 1]] = val, state;
  425. }
  426. function pick(baseState, paths) {
  427. return paths.reduce((substate, path) => {
  428. const pathArray = path.split(".");
  429. return set(substate, pathArray, get(baseState, pathArray));
  430. }, {});
  431. }
  432. function hydrateStore(store, { storage, serializer, key, debug }) {
  433. try {
  434. const fromStorage = storage == null ? void 0 : storage.getItem(key);
  435. if (fromStorage)
  436. store.$patch(serializer == null ? void 0 : serializer.deserialize(fromStorage));
  437. } catch (error) {
  438. if (debug)
  439. console.error(error);
  440. }
  441. }
  442. function persistState(state, { storage, serializer, key, paths, debug }) {
  443. try {
  444. const toStore = Array.isArray(paths) ? pick(state, paths) : state;
  445. storage.setItem(key, serializer.serialize(toStore));
  446. } catch (error) {
  447. if (debug)
  448. console.error(error);
  449. }
  450. }
  451. function createPersistedState(factoryOptions = {}) {
  452. return (context) => {
  453. const { auto = false } = factoryOptions;
  454. const {
  455. options: { persist = auto },
  456. store,
  457. pinia: pinia2
  458. } = context;
  459. if (!persist)
  460. return;
  461. if (!(store.$id in pinia2.state.value)) {
  462. const original_store = pinia2._s.get(store.$id.replace("__hot:", ""));
  463. if (original_store)
  464. Promise.resolve().then(() => original_store.$persist());
  465. return;
  466. }
  467. const persistences = (Array.isArray(persist) ? persist.map((p) => normalizeOptions(p, factoryOptions)) : [normalizeOptions(persist, factoryOptions)]).map(
  468. ({
  469. storage = localStorage,
  470. beforeRestore = null,
  471. afterRestore = null,
  472. serializer = {
  473. serialize: JSON.stringify,
  474. deserialize: JSON.parse
  475. },
  476. key = store.$id,
  477. paths = null,
  478. debug = false
  479. }) => {
  480. var _a;
  481. return {
  482. storage,
  483. beforeRestore,
  484. afterRestore,
  485. serializer,
  486. key: ((_a = factoryOptions.key) != null ? _a : (k) => k)(typeof key == "string" ? key : key(store.$id)),
  487. paths,
  488. debug
  489. };
  490. }
  491. );
  492. store.$persist = () => {
  493. persistences.forEach((persistence) => {
  494. persistState(store.$state, persistence);
  495. });
  496. };
  497. store.$hydrate = ({ runHooks = true } = {}) => {
  498. persistences.forEach((persistence) => {
  499. const { beforeRestore, afterRestore } = persistence;
  500. if (runHooks)
  501. beforeRestore == null ? void 0 : beforeRestore(context);
  502. hydrateStore(store, persistence);
  503. if (runHooks)
  504. afterRestore == null ? void 0 : afterRestore(context);
  505. });
  506. };
  507. persistences.forEach((persistence) => {
  508. const { beforeRestore, afterRestore } = persistence;
  509. beforeRestore == null ? void 0 : beforeRestore(context);
  510. hydrateStore(store, persistence);
  511. afterRestore == null ? void 0 : afterRestore(context);
  512. store.$subscribe(
  513. (_mutation, state) => {
  514. persistState(state, persistence);
  515. },
  516. {
  517. detached: true
  518. }
  519. );
  520. });
  521. };
  522. }
  523. var src_default = createPersistedState();
  524. const _export_sfc = (sfc, props) => {
  525. const target = sfc.__vccOpts || sfc;
  526. for (const [key, val] of props) {
  527. target[key] = val;
  528. }
  529. return target;
  530. };
  531. const _sfc_main$g = {};
  532. const _hoisted_1$f = { id: "trigger-overlay" };
  533. function _sfc_render(_ctx, _cache) {
  534. return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$f);
  535. }
  536. const TriggerOverlay = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["render", _sfc_render], ["__scopeId", "data-v-658df74c"]]);
  537. const _Chord = class _Chord {
  538. /** @param {string} chordString */
  539. constructor(chordString) {
  540. this.chordString = chordString;
  541. }
  542. /**
  543. * @param {number} delta
  544. * @returns {Chord}
  545. */
  546. transpose(delta) {
  547. this.chordString = this.chordString.replaceAll(/[A-G][#b]?/g, (note) => {
  548. const isSharp = _Chord.sharps.includes(note);
  549. const scale = isSharp ? _Chord.sharps : _Chord.flats;
  550. const noteIndex = scale.indexOf(note);
  551. const transposedIndex = (noteIndex + delta + 12) % 12;
  552. const transposedNote = scale[transposedIndex];
  553. return transposedNote;
  554. });
  555. return this;
  556. }
  557. /** @returns {Chord} */
  558. switchModifier() {
  559. this.chordString = this.chordString.replaceAll(/[A-G][#b]/g, (note) => {
  560. const scale = note.includes("#") ? _Chord.sharps : _Chord.flats;
  561. const newScale = note.includes("#") ? _Chord.flats : _Chord.sharps;
  562. const noteIndex = scale.indexOf(note);
  563. return newScale[noteIndex];
  564. });
  565. return this;
  566. }
  567. /** @returns {Chord} */
  568. useSharpModifier() {
  569. this.chordString = this.chordString.replaceAll(/[A-G]b/g, (note) => {
  570. const noteIndex = _Chord.flats.indexOf(note);
  571. return _Chord.sharps[noteIndex];
  572. });
  573. return this;
  574. }
  575. /** @returns {Chord} */
  576. useFlatModifier() {
  577. this.chordString = this.chordString.replaceAll(/[A-G]#/g, (note) => {
  578. const noteIndex = _Chord.sharps.indexOf(note);
  579. return _Chord.flats[noteIndex];
  580. });
  581. return this;
  582. }
  583. /** @returns {string} */
  584. toString() {
  585. return this.chordString;
  586. }
  587. /** @returns {string} */
  588. toFormattedString() {
  589. return this.chordString.replaceAll(
  590. /[#b]/g,
  591. /* html */
  592. `<sup>$&</sup>`
  593. );
  594. }
  595. };
  596. __publicField(_Chord, "sharps", ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]);
  597. __publicField(_Chord, "flats", ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]);
  598. let Chord = _Chord;
  599. var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  600. var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  601. var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
  602. class MonkeyStorage {
  603. /**
  604. * @param {String} key
  605. * @returns {String|null}
  606. */
  607. static getItem(key) {
  608. if (_GM_getValue) {
  609. return _GM_getValue(key, null);
  610. } else {
  611. return localStorage.getItem(key);
  612. }
  613. }
  614. /**
  615. * @param {String} key
  616. * @param {String} value
  617. * @returns {void}
  618. */
  619. static setItem(key, value) {
  620. if (_GM_setValue) {
  621. _GM_setValue(key, value);
  622. } else {
  623. localStorage.setItem(key, value);
  624. }
  625. }
  626. }
  627. const useStore = defineStore("store", {
  628. state() {
  629. return {
  630. // ####################
  631. // 元件相關狀態
  632. // ####################
  633. isDarkMode: false,
  634. isToolbarsShow: false,
  635. isPopupShow: {
  636. sheet: false,
  637. chord: false,
  638. font: false,
  639. settings: false,
  640. menu: false,
  641. // 選單內功能
  642. hotkey: false
  643. },
  644. // ####################
  645. // 偏好設定相關狀態
  646. // ####################
  647. agreeToArchiveSheet: true,
  648. // ####################
  649. // 譜面相關狀態
  650. // ####################
  651. transpose: 0,
  652. /** 在 `StoreHandler` 裡賦值 */
  653. originalCapo: 0,
  654. /** 在 `StoreHandler` 裡賦值,HTML 格式 */
  655. originalKey: "",
  656. /** `font-size` 的變化值 */
  657. fontSizeDelta: 0,
  658. /** 在 `StoreHandler` 裡賦值,單位為 px */
  659. originalFontSize: 0,
  660. /** 在 `StoreHandler` 裡賦值,單位為 px */
  661. originalLineHeight: 0
  662. };
  663. },
  664. persist: {
  665. key: "plus91-preferences",
  666. storage: MonkeyStorage,
  667. deserialize: zipson.parse,
  668. serialize: zipson.stringify,
  669. paths: ["isDarkMode", "agreeToArchiveSheet"],
  670. beforeRestore() {
  671. console.log("[91 Plus] 讀取偏好設置中");
  672. },
  673. afterRestore() {
  674. console.log("[91 Plus] 偏好設置讀取完畢");
  675. },
  676. debug: true
  677. },
  678. getters: {
  679. currentCapo() {
  680. return this.originalCapo + this.transpose;
  681. },
  682. currentKey() {
  683. return new Chord(this.originalKey).transpose(-this.transpose).toFormattedString();
  684. }
  685. },
  686. actions: {
  687. toggleToolbars() {
  688. if (this.isToolbarsShow) {
  689. this.closePopups();
  690. } else {
  691. this.isPopupShow.sheet = true;
  692. }
  693. this.isToolbarsShow = !this.isToolbarsShow;
  694. },
  695. closePopups() {
  696. for (const popup in this.isPopupShow) {
  697. this.isPopupShow[popup] = false;
  698. }
  699. },
  700. /** @param {'sheet'|'chord'|'font'|'settings'|'menu'|'hotkey'} name */
  701. togglePopup(name) {
  702. for (const popup in this.isPopupShow) {
  703. if (popup === name) {
  704. this.isPopupShow[popup] = !this.isPopupShow[popup];
  705. } else {
  706. this.isPopupShow[popup] = false;
  707. }
  708. }
  709. },
  710. plusTranspose(numberToPlus) {
  711. let newTranspose = this.transpose + numberToPlus;
  712. const newCapo = this.originalCapo + newTranspose;
  713. if (newCapo === 12 || newCapo === -12) {
  714. newTranspose = -this.originalCapo;
  715. }
  716. this.transpose = newTranspose;
  717. }
  718. }
  719. });
  720. const _hoisted_1$e = ["active"];
  721. const _sfc_main$f = {
  722. __name: "BootstrapIcon",
  723. props: {
  724. icon: {
  725. type: String,
  726. required: true
  727. },
  728. color: {
  729. type: String,
  730. default: "whitesmoke"
  731. },
  732. size: {
  733. type: String,
  734. default: "1rem"
  735. },
  736. stroke: {
  737. type: String,
  738. default: "0"
  739. },
  740. active: {
  741. type: Boolean,
  742. default: false
  743. }
  744. },
  745. setup(__props) {
  746. vue.useCssVars((_ctx) => ({
  747. "2b53b793": __props.color,
  748. "4bbf91d1": __props.size,
  749. "5ab9f408": __props.stroke
  750. }));
  751. const props = __props;
  752. return (_ctx, _cache) => {
  753. return vue.openBlock(), vue.createElementBlock("i", {
  754. class: vue.normalizeClass(`bi bi-${props.icon}`),
  755. active: props.active
  756. }, null, 10, _hoisted_1$e);
  757. };
  758. }
  759. };
  760. const BootstrapIcon = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__scopeId", "data-v-6e52047a"]]);
  761. const _hoisted_1$d = { class: "toolbar-icon" };
  762. const _hoisted_2$c = { class: "toolbar-icon-text" };
  763. const _sfc_main$e = {
  764. __name: "ToolbarIcon",
  765. props: {
  766. icon: {
  767. type: String,
  768. required: true
  769. },
  770. text: {
  771. type: String,
  772. required: true
  773. },
  774. stroke: {
  775. type: String,
  776. default: "0"
  777. },
  778. active: {
  779. type: Boolean,
  780. default: false
  781. },
  782. color: {
  783. type: String,
  784. default: "whitesmoke"
  785. }
  786. },
  787. setup(__props) {
  788. vue.useCssVars((_ctx) => ({
  789. "52bb32ad": __props.color
  790. }));
  791. const props = __props;
  792. return (_ctx, _cache) => {
  793. return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$d, [
  794. vue.createVNode(BootstrapIcon, {
  795. size: "1.3rem",
  796. icon: props.icon,
  797. color: props.color,
  798. stroke: props.stroke,
  799. active: props.active
  800. }, null, 8, ["icon", "color", "stroke", "active"]),
  801. vue.createElementVNode("div", _hoisted_2$c, vue.toDisplayString(props.text), 1)
  802. ]);
  803. };
  804. }
  805. };
  806. const ToolbarIcon = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["__scopeId", "data-v-3dbcd695"]]);
  807. const _hoisted_1$c = { class: "adjust-widget" };
  808. const _hoisted_2$b = ["disabled"];
  809. const _hoisted_3$5 = ["disabled"];
  810. const _hoisted_4$3 = ["disabled"];
  811. const _sfc_main$d = {
  812. __name: "AdjustWidget",
  813. props: {
  814. iconLeft: {
  815. type: String,
  816. default: "caret-left-fill"
  817. },
  818. iconRight: {
  819. type: String,
  820. default: "caret-right-fill"
  821. },
  822. disabledLeft: {
  823. type: Boolean,
  824. default: false
  825. },
  826. disabledMiddle: {
  827. type: Boolean,
  828. default: false
  829. },
  830. disabledRight: {
  831. type: Boolean,
  832. default: false
  833. },
  834. color: {
  835. type: String,
  836. default: "#444"
  837. },
  838. size: {
  839. type: String,
  840. default: "1.25rem"
  841. },
  842. onclickLeft: Function,
  843. onclickMiddle: Function,
  844. onclickRight: Function
  845. },
  846. setup(__props) {
  847. vue.useCssVars((_ctx) => ({
  848. "13e75dc8": __props.color,
  849. "10392328": __props.size
  850. }));
  851. const props = __props;
  852. return (_ctx, _cache) => {
  853. return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$c, [
  854. vue.createElementVNode("button", {
  855. class: "adjust-button adjust-button-left",
  856. onClick: _cache[0] || (_cache[0] = (...args) => props.onclickLeft && props.onclickLeft(...args)),
  857. disabled: props.disabledLeft
  858. }, [
  859. vue.createVNode(BootstrapIcon, {
  860. icon: props.iconLeft,
  861. color: props.color,
  862. size: props.size
  863. }, null, 8, ["icon", "color", "size"])
  864. ], 8, _hoisted_2$b),
  865. vue.createElementVNode("button", {
  866. class: "adjust-button adjust-button-middle",
  867. onClick: _cache[1] || (_cache[1] = (...args) => props.onclickMiddle && props.onclickMiddle(...args)),
  868. disabled: props.disabledMiddle
  869. }, [
  870. vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
  871. ], 8, _hoisted_3$5),
  872. vue.createElementVNode("button", {
  873. class: "adjust-button adjust-button-right",
  874. onClick: _cache[2] || (_cache[2] = (...args) => props.onclickRight && props.onclickRight(...args)),
  875. disabled: props.disabledRight
  876. }, [
  877. vue.createVNode(BootstrapIcon, {
  878. icon: props.iconRight,
  879. color: props.color,
  880. size: props.size
  881. }, null, 8, ["icon", "color", "size"])
  882. ], 8, _hoisted_4$3)
  883. ]);
  884. };
  885. }
  886. };
  887. const AdjustWidget = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__scopeId", "data-v-cb8ab81d"]]);
  888. class ChordSheetElement {
  889. /** @param {HTMLElement} chordSheetElement */
  890. constructor(chordSheetElement) {
  891. /** @param {NodeList} nodeList */
  892. __privateAdd(this, _unformat);
  893. this.chordSheetElement = chordSheetElement;
  894. }
  895. /**
  896. * 將 Header 和譜上的和弦移調,並實質修改於 DOM
  897. * @param {number} delta 相對於當前調的移調值
  898. */
  899. static transposeSheet(delta) {
  900. $("#tone_z .tf").each(function() {
  901. const chord = new Chord($(this).text());
  902. const newChordHTML = chord.transpose(-delta).toFormattedString();
  903. $(this).html(newChordHTML);
  904. });
  905. }
  906. /** @returns {ChordSheetElement} */
  907. formatUnderlines() {
  908. const underlineEl = this.chordSheetElement.querySelectorAll("u");
  909. const doubleUnderlineEl = this.chordSheetElement.querySelectorAll("abbr");
  910. underlineEl.forEach((el) => {
  911. el.innerText = `{_${el.innerText}_}`;
  912. });
  913. doubleUnderlineEl.forEach((el) => {
  914. el.innerText = `{=${el.innerText}=}`;
  915. });
  916. return this;
  917. }
  918. /** @returns {ChordSheetElement} */
  919. unformatUnderlines() {
  920. const underlineEl = this.chordSheetElement.querySelectorAll("u");
  921. const doubleUnderlineEl = this.chordSheetElement.querySelectorAll("abbr");
  922. __privateMethod(this, _unformat, unformat_fn).call(this, underlineEl);
  923. __privateMethod(this, _unformat, unformat_fn).call(this, doubleUnderlineEl);
  924. return this;
  925. }
  926. }
  927. _unformat = new WeakSet();
  928. unformat_fn = function(nodeList) {
  929. nodeList.forEach((el) => {
  930. el.innerHTML = el.innerText.replaceAll(/{_|{=|=}|_}/g, "").replaceAll(
  931. /[a-zA-Z0-9#/]+/g,
  932. /* html */
  933. `<span class="tf">$&</span>`
  934. );
  935. });
  936. };
  937. class ChordSheetDocument {
  938. constructor() {
  939. this.el = {
  940. mtitle: document.getElementById("mtitle"),
  941. tkinfo: document.querySelector(".tkinfo"),
  942. capoSelect: document.querySelector(".capo .select"),
  943. tinfo: document.querySelector(".tinfo"),
  944. tone_z: document.getElementById("tone_z")
  945. };
  946. }
  947. getId() {
  948. const urlParams = new URLSearchParams(window.location.search);
  949. return Number(urlParams.get("id"));
  950. }
  951. getTitle() {
  952. return this.el.mtitle.innerText.trim();
  953. }
  954. getKey() {
  955. var _a;
  956. const match = (_a = this.el.tkinfo) == null ? void 0 : _a.innerText.match(new RegExp("(?<=原調:)\\w*"));
  957. return match ? match[0].trim() : "";
  958. }
  959. getPlay() {
  960. var _a;
  961. const match = (_a = this.el.capoSelect) == null ? void 0 : _a.innerText.split(/\s*\/\s*/);
  962. return match ? match[1].trim() : "";
  963. }
  964. getCapo() {
  965. var _a;
  966. const match = (_a = this.el.capoSelect) == null ? void 0 : _a.innerText.split(/\s*\/\s*/);
  967. return match ? Number(match[0]) : 0;
  968. }
  969. getSinger() {
  970. var _a;
  971. const match = (_a = this.el.tinfo) == null ? void 0 : _a.innerText.match(new RegExp("(?<=演唱:).*(?=\\n|$)"));
  972. return match ? match[0].trim() : "";
  973. }
  974. getComposer() {
  975. var _a;
  976. const match = (_a = this.el.tinfo) == null ? void 0 : _a.innerText.match(new RegExp("(?<=曲:).*?(?=詞:|$)"));
  977. return match ? match[0].trim() : "";
  978. }
  979. getLyricist() {
  980. var _a;
  981. const match = (_a = this.el.tinfo) == null ? void 0 : _a.innerText.match(new RegExp("(?<=詞:).*?(?=曲:|$)"));
  982. return match ? match[0].trim() : "";
  983. }
  984. getBpm() {
  985. var _a;
  986. const match = (_a = this.el.tkinfo) == null ? void 0 : _a.innerText.match(/\d+/);
  987. return match ? Number(match[0]) : 0;
  988. }
  989. getSheetText() {
  990. const formattedChordSheet = this.el.tone_z.innerText.replaceAll(/\s+?\n/g, "\n").replaceAll("\n\n", "\n").trim().replaceAll(/\s+/g, (match) => {
  991. return `{%${match.length}%}`;
  992. });
  993. return formattedChordSheet;
  994. }
  995. }
  996. class StoreHandler {
  997. constructor() {
  998. /** 當 `#store.transpose` 變動時,將譜面上的和弦進行移調 */
  999. __privateAdd(this, _watchTranspose);
  1000. __privateAdd(this, _watchFontSize);
  1001. // 命 `#store` 為私有屬性,在建立實例時再賦值,避免衝突
  1002. __privateAdd(this, _store, void 0);
  1003. __privateSet(this, _store, useStore());
  1004. }
  1005. initState() {
  1006. const capoSelected = $(".capo .select").eq(0).text().trim();
  1007. const originalCapo = +capoSelected.split(/\s*\/\s*/)[0];
  1008. const originalKey = capoSelected.split(/\s*\/\s*/)[1];
  1009. __privateGet(this, _store).originalCapo = originalCapo;
  1010. __privateGet(this, _store).originalKey = originalKey;
  1011. const fontSize = +$("#tone_z").css("font-size").match(/^\d+/)[0];
  1012. const lineHeight = +$("#tone_z > p").css("line-height").match(/^\d+/)[0];
  1013. __privateGet(this, _store).originalFontSize = fontSize;
  1014. __privateGet(this, _store).originalLineHeight = lineHeight;
  1015. const params = getQueryParams();
  1016. if (params.transpose) {
  1017. __privateGet(this, _store).transpose = params.transpose;
  1018. }
  1019. }
  1020. start() {
  1021. __privateMethod(this, _watchTranspose, watchTranspose_fn).call(this);
  1022. __privateMethod(this, _watchFontSize, watchFontSize_fn).call(this);
  1023. return this;
  1024. }
  1025. static handleKeydown(key) {
  1026. const store = useStore();
  1027. switch (key) {
  1028. case " ": {
  1029. store.toggleToolbars();
  1030. break;
  1031. }
  1032. case "/": {
  1033. if (!store.isToolbarsShow) {
  1034. store.toggleToolbars();
  1035. store.closePopups();
  1036. }
  1037. setTimeout(() => {
  1038. $("#plus91-header input").get(0).focus();
  1039. }, 0);
  1040. break;
  1041. }
  1042. case "Escape": {
  1043. if (store.isToolbarsShow) {
  1044. store.toggleToolbars();
  1045. }
  1046. break;
  1047. }
  1048. }
  1049. if (store.isPopupShow.sheet) {
  1050. switch (key) {
  1051. case "ArrowLeft": {
  1052. store.plusTranspose(-1);
  1053. break;
  1054. }
  1055. case "ArrowRight": {
  1056. store.plusTranspose(1);
  1057. break;
  1058. }
  1059. case "ArrowDown": {
  1060. store.transpose = 0;
  1061. break;
  1062. }
  1063. }
  1064. }
  1065. }
  1066. }
  1067. _store = new WeakMap();
  1068. _watchTranspose = new WeakSet();
  1069. watchTranspose_fn = function() {
  1070. vue.watch(() => {
  1071. return __privateGet(this, _store).transpose;
  1072. }, (newValue, oldValue) => {
  1073. ChordSheetElement.transposeSheet((newValue - oldValue) % 12);
  1074. });
  1075. };
  1076. _watchFontSize = new WeakSet();
  1077. watchFontSize_fn = function() {
  1078. vue.watch(() => {
  1079. return __privateGet(this, _store).fontSizeDelta;
  1080. }, (newValue) => {
  1081. const oFontSize = __privateGet(this, _store).originalFontSize;
  1082. const oLineHeight = __privateGet(this, _store).originalLineHeight;
  1083. $("#tone_z").css("font-size", `${oFontSize + newValue}px`);
  1084. $("#tone_z > p").css("line-height", `${oLineHeight + newValue}px`);
  1085. });
  1086. };
  1087. var LIBVERSION = "2.0.0-beta.3", EMPTY = "", UNKNOWN = "?", FUNC_TYPE = "function", UNDEF_TYPE = "undefined", OBJ_TYPE = "object", STR_TYPE = "string", MAJOR = "major", MODEL = "model", NAME = "name", TYPE = "type", VENDOR = "vendor", VERSION = "version", ARCHITECTURE = "architecture", CONSOLE = "console", MOBILE = "mobile", TABLET = "tablet", SMARTTV = "smarttv", WEARABLE = "wearable", XR = "xr", EMBEDDED = "embedded", USER_AGENT = "user-agent", UA_MAX_LENGTH = 500, BRANDS = "brands", FORMFACTORS = "formFactors", FULLVERLIST = "fullVersionList", PLATFORM = "platform", PLATFORMVER = "platformVersion", BITNESS = "bitness", CH_HEADER = "sec-ch-ua", CH_HEADER_FULL_VER_LIST = CH_HEADER + "-full-version-list", CH_HEADER_ARCH = CH_HEADER + "-arch", CH_HEADER_BITNESS = CH_HEADER + "-" + BITNESS, CH_HEADER_FORM_FACTORS = CH_HEADER + "-form-factors", CH_HEADER_MOBILE = CH_HEADER + "-" + MOBILE, CH_HEADER_MODEL = CH_HEADER + "-" + MODEL, CH_HEADER_PLATFORM = CH_HEADER + "-" + PLATFORM, CH_HEADER_PLATFORM_VER = CH_HEADER_PLATFORM + "-version", CH_ALL_VALUES = [BRANDS, FULLVERLIST, MOBILE, MODEL, PLATFORM, PLATFORMVER, ARCHITECTURE, FORMFACTORS, BITNESS], UA_BROWSER = "browser", UA_CPU = "cpu", UA_DEVICE = "device", UA_ENGINE = "engine", UA_OS = "os", UA_RESULT = "result", AMAZON = "Amazon", APPLE = "Apple", ASUS = "ASUS", BLACKBERRY = "BlackBerry", GOOGLE = "Google", HUAWEI = "Huawei", LENOVO = "Lenovo", LG = "LG", MICROSOFT = "Microsoft", MOTOROLA = "Motorola", SAMSUNG = "Samsung", SHARP = "Sharp", SONY = "Sony", XIAOMI = "Xiaomi", ZEBRA = "Zebra", PREFIX_MOBILE = "Mobile ", SUFFIX_BROWSER = " Browser", CHROME = "Chrome", EDGE = "Edge", FIREFOX = "Firefox", OPERA = "Opera", FACEBOOK = "Facebook", SOGOU = "Sogou", WINDOWS = "Windows";
  1088. var isWindow = typeof window !== UNDEF_TYPE, NAVIGATOR = isWindow && window.navigator ? window.navigator : void 0, NAVIGATOR_UADATA = NAVIGATOR && NAVIGATOR.userAgentData ? NAVIGATOR.userAgentData : void 0;
  1089. var extend = function(defaultRgx, extensions) {
  1090. var mergedRgx = {};
  1091. var extraRgx = extensions;
  1092. if (!isExtensions(extensions)) {
  1093. extraRgx = {};
  1094. for (var i in extensions) {
  1095. for (var j in extensions[i]) {
  1096. extraRgx[j] = extensions[i][j].concat(extraRgx[j] ? extraRgx[j] : []);
  1097. }
  1098. }
  1099. }
  1100. for (var k in defaultRgx) {
  1101. mergedRgx[k] = extraRgx[k] && extraRgx[k].length % 2 === 0 ? extraRgx[k].concat(defaultRgx[k]) : defaultRgx[k];
  1102. }
  1103. return mergedRgx;
  1104. }, enumerize = function(arr) {
  1105. var enums = {};
  1106. for (var i = 0; i < arr.length; i++) {
  1107. enums[arr[i].toUpperCase()] = arr[i];
  1108. }
  1109. return enums;
  1110. }, has = function(str1, str2) {
  1111. if (typeof str1 === OBJ_TYPE && str1.length > 0) {
  1112. for (var i in str1) {
  1113. if (lowerize(str1[i]) == lowerize(str2))
  1114. return true;
  1115. }
  1116. return false;
  1117. }
  1118. return isString(str1) ? lowerize(str2).indexOf(lowerize(str1)) !== -1 : false;
  1119. }, isExtensions = function(obj, deep) {
  1120. for (var prop in obj) {
  1121. return /^(browser|cpu|device|engine|os)$/.test(prop) || (deep ? isExtensions(obj[prop]) : false);
  1122. }
  1123. }, isString = function(val) {
  1124. return typeof val === STR_TYPE;
  1125. }, itemListToArray = function(header) {
  1126. if (!header)
  1127. return void 0;
  1128. var arr = [];
  1129. var tokens = strip(/\\?\"/g, header).split(",");
  1130. for (var i = 0; i < tokens.length; i++) {
  1131. if (tokens[i].indexOf(";") > -1) {
  1132. var token = trim(tokens[i]).split(";v=");
  1133. arr[i] = { brand: token[0], version: token[1] };
  1134. } else {
  1135. arr[i] = trim(tokens[i]);
  1136. }
  1137. }
  1138. return arr;
  1139. }, lowerize = function(str) {
  1140. return isString(str) ? str.toLowerCase() : str;
  1141. }, majorize = function(version) {
  1142. return isString(version) ? strip(/[^\d\.]/g, version).split(".")[0] : void 0;
  1143. }, setProps = function(arr) {
  1144. for (var i in arr) {
  1145. var propName = arr[i];
  1146. if (typeof propName == OBJ_TYPE && propName.length == 2) {
  1147. this[propName[0]] = propName[1];
  1148. } else {
  1149. this[propName] = void 0;
  1150. }
  1151. }
  1152. return this;
  1153. }, strip = function(pattern, str) {
  1154. return isString(str) ? str.replace(pattern, EMPTY) : str;
  1155. }, stripQuotes = function(str) {
  1156. return strip(/\\?\"/g, str);
  1157. }, trim = function(str, len) {
  1158. if (isString(str)) {
  1159. str = strip(/^\s\s*/, str);
  1160. return typeof len === UNDEF_TYPE ? str : str.substring(0, UA_MAX_LENGTH);
  1161. }
  1162. };
  1163. var rgxMapper = function(ua, arrays) {
  1164. if (!ua || !arrays)
  1165. return;
  1166. var i = 0, j, k, p, q, matches, match;
  1167. while (i < arrays.length && !matches) {
  1168. var regex = arrays[i], props = arrays[i + 1];
  1169. j = k = 0;
  1170. while (j < regex.length && !matches) {
  1171. if (!regex[j]) {
  1172. break;
  1173. }
  1174. matches = regex[j++].exec(ua);
  1175. if (!!matches) {
  1176. for (p = 0; p < props.length; p++) {
  1177. match = matches[++k];
  1178. q = props[p];
  1179. if (typeof q === OBJ_TYPE && q.length > 0) {
  1180. if (q.length === 2) {
  1181. if (typeof q[1] == FUNC_TYPE) {
  1182. this[q[0]] = q[1].call(this, match);
  1183. } else {
  1184. this[q[0]] = q[1];
  1185. }
  1186. } else if (q.length === 3) {
  1187. if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {
  1188. this[q[0]] = match ? q[1].call(this, match, q[2]) : void 0;
  1189. } else {
  1190. this[q[0]] = match ? match.replace(q[1], q[2]) : void 0;
  1191. }
  1192. } else if (q.length === 4) {
  1193. this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : void 0;
  1194. }
  1195. } else {
  1196. this[q] = match ? match : void 0;
  1197. }
  1198. }
  1199. }
  1200. }
  1201. i += 2;
  1202. }
  1203. }, strMapper = function(str, map) {
  1204. for (var i in map) {
  1205. if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
  1206. for (var j = 0; j < map[i].length; j++) {
  1207. if (has(map[i][j], str)) {
  1208. return i === UNKNOWN ? void 0 : i;
  1209. }
  1210. }
  1211. } else if (has(map[i], str)) {
  1212. return i === UNKNOWN ? void 0 : i;
  1213. }
  1214. }
  1215. return map.hasOwnProperty("*") ? map["*"] : str;
  1216. };
  1217. var windowsVersionMap = {
  1218. "ME": "4.90",
  1219. "NT 3.11": "NT3.51",
  1220. "NT 4.0": "NT4.0",
  1221. "2000": "NT 5.0",
  1222. "XP": ["NT 5.1", "NT 5.2"],
  1223. "Vista": "NT 6.0",
  1224. "7": "NT 6.1",
  1225. "8": "NT 6.2",
  1226. "8.1": "NT 6.3",
  1227. "10": ["NT 6.4", "NT 10.0"],
  1228. "RT": "ARM"
  1229. }, formFactorsMap = {
  1230. "embedded": "Automotive",
  1231. "mobile": "Mobile",
  1232. "tablet": ["Tablet", "EInk"],
  1233. "smarttv": "TV",
  1234. "wearable": "Watch",
  1235. "xr": ["VR", "XR"],
  1236. "?": ["Desktop", "Unknown"],
  1237. "*": void 0
  1238. };
  1239. var defaultRegexes = {
  1240. browser: [
  1241. [
  1242. // Most common regardless engine
  1243. /\b(?:crmo|crios)\/([\w\.]+)/i
  1244. // Chrome for Android/iOS
  1245. ],
  1246. [VERSION, [NAME, PREFIX_MOBILE + "Chrome"]],
  1247. [
  1248. /edg(?:e|ios|a)?\/([\w\.]+)/i
  1249. // Microsoft Edge
  1250. ],
  1251. [VERSION, [NAME, "Edge"]],
  1252. [
  1253. // Presto based
  1254. /(opera mini)\/([-\w\.]+)/i,
  1255. // Opera Mini
  1256. /(opera [mobiletab]{3,6})\b.+version\/([-\w\.]+)/i,
  1257. // Opera Mobi/Tablet
  1258. /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i
  1259. // Opera
  1260. ],
  1261. [NAME, VERSION],
  1262. [
  1263. /opios[\/ ]+([\w\.]+)/i
  1264. // Opera mini on iphone >= 8.0
  1265. ],
  1266. [VERSION, [NAME, OPERA + " Mini"]],
  1267. [
  1268. /\bop(?:rg)?x\/([\w\.]+)/i
  1269. // Opera GX
  1270. ],
  1271. [VERSION, [NAME, OPERA + " GX"]],
  1272. [
  1273. /\bopr\/([\w\.]+)/i
  1274. // Opera Webkit
  1275. ],
  1276. [VERSION, [NAME, OPERA]],
  1277. [
  1278. // Mixed
  1279. /\bb[ai]*d(?:uhd|[ub]*[aekoprswx]{5,6})[\/ ]?([\w\.]+)/i
  1280. // Baidu
  1281. ],
  1282. [VERSION, [NAME, "Baidu"]],
  1283. [
  1284. /(kindle)\/([\w\.]+)/i,
  1285. // Kindle
  1286. /(lunascape|maxthon|netfront|jasmine|blazer|sleipnir)[\/ ]?([\w\.]*)/i,
  1287. // Lunascape/Maxthon/Netfront/Jasmine/Blazer/Sleipnir
  1288. // Trident based
  1289. /(avant|iemobile|slim)\s?(?:browser)?[\/ ]?([\w\.]*)/i,
  1290. // Avant/IEMobile/SlimBrowser
  1291. /(?:ms|\()(ie) ([\w\.]+)/i,
  1292. // Internet Explorer
  1293. // Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon
  1294. /(flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|quark|qupzilla|falkon|rekonq|puffin|brave|whale(?!.+naver)|qqbrowserlite|duckduckgo|klar)\/([-\w\.]+)/i,
  1295. // Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar
  1296. /(heytap|ovi)browser\/([\d\.]+)/i,
  1297. // HeyTap/Ovi
  1298. /(weibo)__([\d\.]+)/i
  1299. // Weibo
  1300. ],
  1301. [NAME, VERSION],
  1302. [
  1303. /\bddg\/([\w\.]+)/i
  1304. // DuckDuckGo
  1305. ],
  1306. [VERSION, [NAME, "DuckDuckGo"]],
  1307. [
  1308. /(?:\buc? ?browser|(?:juc.+)ucweb)[\/ ]?([\w\.]+)/i
  1309. // UCBrowser
  1310. ],
  1311. [VERSION, [NAME, "UCBrowser"]],
  1312. [
  1313. /microm.+\bqbcore\/([\w\.]+)/i,
  1314. // WeChat Desktop for Windows Built-in Browser
  1315. /\bqbcore\/([\w\.]+).+microm/i,
  1316. /micromessenger\/([\w\.]+)/i
  1317. // WeChat
  1318. ],
  1319. [VERSION, [NAME, "WeChat"]],
  1320. [
  1321. /konqueror\/([\w\.]+)/i
  1322. // Konqueror
  1323. ],
  1324. [VERSION, [NAME, "Konqueror"]],
  1325. [
  1326. /trident.+rv[: ]([\w\.]{1,9})\b.+like gecko/i
  1327. // IE11
  1328. ],
  1329. [VERSION, [NAME, "IE"]],
  1330. [
  1331. /ya(?:search)?browser\/([\w\.]+)/i
  1332. // Yandex
  1333. ],
  1334. [VERSION, [NAME, "Yandex"]],
  1335. [
  1336. /slbrowser\/([\w\.]+)/i
  1337. // Smart Lenovo Browser
  1338. ],
  1339. [VERSION, [NAME, "Smart " + LENOVO + SUFFIX_BROWSER]],
  1340. [
  1341. /(avast|avg)\/([\w\.]+)/i
  1342. // Avast/AVG Secure Browser
  1343. ],
  1344. [[NAME, /(.+)/, "$1 Secure" + SUFFIX_BROWSER], VERSION],
  1345. [
  1346. /\bfocus\/([\w\.]+)/i
  1347. // Firefox Focus
  1348. ],
  1349. [VERSION, [NAME, FIREFOX + " Focus"]],
  1350. [
  1351. /\bopt\/([\w\.]+)/i
  1352. // Opera Touch
  1353. ],
  1354. [VERSION, [NAME, OPERA + " Touch"]],
  1355. [
  1356. /coc_coc\w+\/([\w\.]+)/i
  1357. // Coc Coc Browser
  1358. ],
  1359. [VERSION, [NAME, "Coc Coc"]],
  1360. [
  1361. /dolfin\/([\w\.]+)/i
  1362. // Dolphin
  1363. ],
  1364. [VERSION, [NAME, "Dolphin"]],
  1365. [
  1366. /coast\/([\w\.]+)/i
  1367. // Opera Coast
  1368. ],
  1369. [VERSION, [NAME, OPERA + " Coast"]],
  1370. [
  1371. /miuibrowser\/([\w\.]+)/i
  1372. // MIUI Browser
  1373. ],
  1374. [VERSION, [NAME, "MIUI" + SUFFIX_BROWSER]],
  1375. [
  1376. /fxios\/([\w\.-]+)/i
  1377. // Firefox for iOS
  1378. ],
  1379. [VERSION, [NAME, PREFIX_MOBILE + FIREFOX]],
  1380. [
  1381. /\bqihu|(qi?ho?o?|360)browser/i
  1382. // 360
  1383. ],
  1384. [[NAME, "360" + SUFFIX_BROWSER]],
  1385. [
  1386. /\b(qq)\/([\w\.]+)/i
  1387. // QQ
  1388. ],
  1389. [[NAME, /(.+)/, "$1Browser"], VERSION],
  1390. [
  1391. /(oculus|sailfish|huawei|vivo|pico)browser\/([\w\.]+)/i
  1392. ],
  1393. [[NAME, /(.+)/, "$1" + SUFFIX_BROWSER], VERSION],
  1394. [
  1395. // Oculus/Sailfish/HuaweiBrowser/VivoBrowser/PicoBrowser
  1396. /samsungbrowser\/([\w\.]+)/i
  1397. // Samsung Internet
  1398. ],
  1399. [VERSION, [NAME, SAMSUNG + " Internet"]],
  1400. [
  1401. /(comodo_dragon)\/([\w\.]+)/i
  1402. // Comodo Dragon
  1403. ],
  1404. [[NAME, /_/g, " "], VERSION],
  1405. [
  1406. /metasr[\/ ]?([\d\.]+)/i
  1407. // Sogou Explorer
  1408. ],
  1409. [VERSION, [NAME, SOGOU + " Explorer"]],
  1410. [
  1411. /(sogou)mo\w+\/([\d\.]+)/i
  1412. // Sogou Mobile
  1413. ],
  1414. [[NAME, SOGOU + " Mobile"], VERSION],
  1415. [
  1416. /(electron)\/([\w\.]+) safari/i,
  1417. // Electron-based App
  1418. /(tesla)(?: qtcarbrowser|\/(20\d\d\.[-\w\.]+))/i,
  1419. // Tesla
  1420. /m?(qqbrowser|2345Explorer)[\/ ]?([\w\.]+)/i
  1421. // QQBrowser/2345 Browser
  1422. ],
  1423. [NAME, VERSION],
  1424. [
  1425. /(lbbrowser|rekonq)/i,
  1426. // LieBao Browser/Rekonq
  1427. /\[(linkedin)app\]/i
  1428. // LinkedIn App for iOS & Android
  1429. ],
  1430. [NAME],
  1431. [
  1432. // WebView
  1433. /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i
  1434. // Facebook App for iOS & Android
  1435. ],
  1436. [[NAME, FACEBOOK], VERSION],
  1437. [
  1438. /(Klarna)\/([\w\.]+)/i,
  1439. // Klarna Shopping Browser for iOS & Android
  1440. /(kakao(?:talk|story))[\/ ]([\w\.]+)/i,
  1441. // Kakao App
  1442. /(naver)\(.*?(\d+\.[\w\.]+).*\)/i,
  1443. // Naver InApp
  1444. /safari (line)\/([\w\.]+)/i,
  1445. // Line App for iOS
  1446. /\b(line)\/([\w\.]+)\/iab/i,
  1447. // Line App for Android
  1448. /(alipay)client\/([\w\.]+)/i,
  1449. // Alipay
  1450. /(twitter)(?:and| f.+e\/([\w\.]+))/i,
  1451. // Twitter
  1452. /(chromium|instagram|snapchat)[\/ ]([-\w\.]+)/i
  1453. // Chromium/Instagram/Snapchat
  1454. ],
  1455. [NAME, VERSION],
  1456. [
  1457. /\bgsa\/([\w\.]+) .*safari\//i
  1458. // Google Search Appliance on iOS
  1459. ],
  1460. [VERSION, [NAME, "GSA"]],
  1461. [
  1462. /musical_ly(?:.+app_?version\/|_)([\w\.]+)/i
  1463. // TikTok
  1464. ],
  1465. [VERSION, [NAME, "TikTok"]],
  1466. [
  1467. /headlesschrome(?:\/([\w\.]+)| )/i
  1468. // Chrome Headless
  1469. ],
  1470. [VERSION, [NAME, CHROME + " Headless"]],
  1471. [
  1472. / wv\).+(chrome)\/([\w\.]+)/i
  1473. // Chrome WebView
  1474. ],
  1475. [[NAME, CHROME + " WebView"], VERSION],
  1476. [
  1477. /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i
  1478. // Android Browser
  1479. ],
  1480. [VERSION, [NAME, "Android" + SUFFIX_BROWSER]],
  1481. [
  1482. /chrome\/([\w\.]+) mobile/i
  1483. // Chrome Mobile
  1484. ],
  1485. [VERSION, [NAME, PREFIX_MOBILE + "Chrome"]],
  1486. [
  1487. /(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w\.]+)/i
  1488. // Chrome/OmniWeb/Arora/Tizen/Nokia
  1489. ],
  1490. [NAME, VERSION],
  1491. [
  1492. /version\/([\w\.\,]+) .*mobile(?:\/\w+ | ?)safari/i
  1493. // Safari Mobile
  1494. ],
  1495. [VERSION, [NAME, PREFIX_MOBILE + "Safari"]],
  1496. [
  1497. /iphone .*mobile(?:\/\w+ | ?)safari/i
  1498. ],
  1499. [[NAME, PREFIX_MOBILE + "Safari"]],
  1500. [
  1501. /version\/([\w\.\,]+) .*(safari)/i
  1502. // Safari
  1503. ],
  1504. [VERSION, NAME],
  1505. [
  1506. /webkit.+?(mobile ?safari|safari)(\/[\w\.]+)/i
  1507. // Safari < 3.0
  1508. ],
  1509. [NAME, [VERSION, "1"]],
  1510. [
  1511. /(webkit|khtml)\/([\w\.]+)/i
  1512. ],
  1513. [NAME, VERSION],
  1514. [
  1515. // Gecko based
  1516. /(?:mobile|tablet);.*(firefox)\/([\w\.-]+)/i
  1517. // Firefox Mobile
  1518. ],
  1519. [[NAME, PREFIX_MOBILE + FIREFOX], VERSION],
  1520. [
  1521. /(navigator|netscape\d?)\/([-\w\.]+)/i
  1522. // Netscape
  1523. ],
  1524. [[NAME, "Netscape"], VERSION],
  1525. [
  1526. /(wolvic)\/([\w\.]+)/i
  1527. // Wolvic
  1528. ],
  1529. [NAME, VERSION],
  1530. [
  1531. /mobile vr; rv:([\w\.]+)\).+firefox/i
  1532. // Firefox Reality
  1533. ],
  1534. [VERSION, [NAME, FIREFOX + " Reality"]],
  1535. [
  1536. /ekiohf.+(flow)\/([\w\.]+)/i,
  1537. // Flow
  1538. /(swiftfox)/i,
  1539. // Swiftfox
  1540. /(icedragon|iceweasel|camino|chimera|fennec|maemo browser|minimo|conkeror)[\/ ]?([\w\.\+]+)/i,
  1541. // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
  1542. /(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([-\w\.]+)$/i,
  1543. // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
  1544. /(firefox)\/([\w\.]+)/i,
  1545. // Other Firefox-based
  1546. /(mozilla)\/([\w\.]+) .+rv\:.+gecko\/\d+/i,
  1547. // Mozilla
  1548. // Other
  1549. /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|obigo|mosaic|(?:go|ice|up)[\. ]?browser)[-\/ ]?v?([\w\.]+)/i,
  1550. // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Obigo/Mosaic/Go/ICE/UP.Browser
  1551. /(links) \(([\w\.]+)/i
  1552. // Links
  1553. ],
  1554. [NAME, [VERSION, /_/g, "."]],
  1555. [
  1556. /(cobalt)\/([\w\.]+)/i
  1557. // Cobalt
  1558. ],
  1559. [NAME, [VERSION, /[^\d\.]+./, EMPTY]]
  1560. ],
  1561. cpu: [
  1562. [
  1563. /\b(?:(amd|x|x86[-_]?|wow|win)64)\b/i
  1564. // AMD64 (x64)
  1565. ],
  1566. [[ARCHITECTURE, "amd64"]],
  1567. [
  1568. /(ia32(?=;))/i,
  1569. // IA32 (quicktime)
  1570. /((?:i[346]|x)86)[;\)]/i
  1571. // IA32 (x86)
  1572. ],
  1573. [[ARCHITECTURE, "ia32"]],
  1574. [
  1575. /\b(aarch64|arm(v?8e?l?|_?64))\b/i
  1576. // ARM64
  1577. ],
  1578. [[ARCHITECTURE, "arm64"]],
  1579. [
  1580. /\b(arm(?:v[67])?ht?n?[fl]p?)\b/i
  1581. // ARMHF
  1582. ],
  1583. [[ARCHITECTURE, "armhf"]],
  1584. [
  1585. // PocketPC mistakenly identified as PowerPC
  1586. /windows (ce|mobile); ppc;/i
  1587. ],
  1588. [[ARCHITECTURE, "arm"]],
  1589. [
  1590. /((?:ppc|powerpc)(?:64)?)(?: mac|;|\))/i
  1591. // PowerPC
  1592. ],
  1593. [[ARCHITECTURE, /ower/, EMPTY, lowerize]],
  1594. [
  1595. /(sun4\w)[;\)]/i
  1596. // SPARC
  1597. ],
  1598. [[ARCHITECTURE, "sparc"]],
  1599. [
  1600. /((?:avr32|ia64(?=;))|68k(?=\))|\barm(?=v(?:[1-7]|[5-7]1)l?|;|eabi)|(?=atmel )avr|(?:irix|mips|sparc)(?:64)?\b|pa-risc)/i
  1601. // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
  1602. ],
  1603. [[ARCHITECTURE, lowerize]]
  1604. ],
  1605. device: [
  1606. [
  1607. //////////////////////////
  1608. // MOBILES & TABLETS
  1609. /////////////////////////
  1610. // Samsung
  1611. /\b(sch-i[89]0\d|shw-m380s|sm-[ptx]\w{2,4}|gt-[pn]\d{2,4}|sgh-t8[56]9|nexus 10)/i
  1612. ],
  1613. [MODEL, [VENDOR, SAMSUNG], [TYPE, TABLET]],
  1614. [
  1615. /\b((?:s[cgp]h|gt|sm)-\w+|sc[g-]?[\d]+a?|galaxy nexus)/i,
  1616. /samsung[- ]([-\w]+)/i,
  1617. /sec-(sgh\w+)/i
  1618. ],
  1619. [MODEL, [VENDOR, SAMSUNG], [TYPE, MOBILE]],
  1620. [
  1621. // Apple
  1622. /(?:\/|\()(ip(?:hone|od)[\w, ]*)(?:\/|;)/i
  1623. // iPod/iPhone
  1624. ],
  1625. [MODEL, [VENDOR, APPLE], [TYPE, MOBILE]],
  1626. [
  1627. /\((ipad);[-\w\),; ]+apple/i,
  1628. // iPad
  1629. /applecoremedia\/[\w\.]+ \((ipad)/i,
  1630. /\b(ipad)\d\d?,\d\d?[;\]].+ios/i
  1631. ],
  1632. [MODEL, [VENDOR, APPLE], [TYPE, TABLET]],
  1633. [
  1634. /(macintosh);/i
  1635. ],
  1636. [MODEL, [VENDOR, APPLE]],
  1637. [
  1638. // Sharp
  1639. /\b(sh-?[altvz]?\d\d[a-ekm]?)/i
  1640. ],
  1641. [MODEL, [VENDOR, SHARP], [TYPE, MOBILE]],
  1642. [
  1643. // Huawei
  1644. /\b((?:ag[rs][23]?|bah2?|sht?|btv)-a?[lw]\d{2})\b(?!.+d\/s)/i
  1645. ],
  1646. [MODEL, [VENDOR, HUAWEI], [TYPE, TABLET]],
  1647. [
  1648. /(?:huawei|honor)([-\w ]+)[;\)]/i,
  1649. /\b(nexus 6p|\w{2,4}e?-[atu]?[ln][\dx][012359c][adn]?)\b(?!.+d\/s)/i
  1650. ],
  1651. [MODEL, [VENDOR, HUAWEI], [TYPE, MOBILE]],
  1652. [
  1653. // Xiaomi
  1654. /\b(poco[\w ]+|m2\d{3}j\d\d[a-z]{2})(?: bui|\))/i,
  1655. // Xiaomi POCO
  1656. /\b; (\w+) build\/hm\1/i,
  1657. // Xiaomi Hongmi 'numeric' models
  1658. /\b(hm[-_ ]?note?[_ ]?(?:\d\w)?) bui/i,
  1659. // Xiaomi Hongmi
  1660. /\b(redmi[\-_ ]?(?:note|k)?[\w_ ]+)(?: bui|\))/i,
  1661. // Xiaomi Redmi
  1662. /oid[^\)]+; (m?[12][0-389][01]\w{3,6}[c-y])( bui|; wv|\))/i,
  1663. // Xiaomi Redmi 'numeric' models
  1664. /\b(mi[-_ ]?(?:a\d|one|one[_ ]plus|note lte|max|cc)?[_ ]?(?:\d?\w?)[_ ]?(?:plus|se|lite)?)(?: bui|\))/i
  1665. // Xiaomi Mi
  1666. ],
  1667. [[MODEL, /_/g, " "], [VENDOR, XIAOMI], [TYPE, MOBILE]],
  1668. [
  1669. /oid[^\)]+; (2\d{4}(283|rpbf)[cgl])( bui|\))/i,
  1670. // Redmi Pad
  1671. /\b(mi[-_ ]?(?:pad)(?:[\w_ ]+))(?: bui|\))/i
  1672. // Mi Pad tablets
  1673. ],
  1674. [[MODEL, /_/g, " "], [VENDOR, XIAOMI], [TYPE, TABLET]],
  1675. [
  1676. // OPPO
  1677. /; (\w+) bui.+ oppo/i,
  1678. /\b(cph[12]\d{3}|p(?:af|c[al]|d\w|e[ar])[mt]\d0|x9007|a101op)\b/i
  1679. ],
  1680. [MODEL, [VENDOR, "OPPO"], [TYPE, MOBILE]],
  1681. [
  1682. /\b(opd2\d{3}a?) bui/i
  1683. ],
  1684. [MODEL, [VENDOR, "OPPO"], [TYPE, TABLET]],
  1685. [
  1686. // Vivo
  1687. /vivo (\w+)(?: bui|\))/i,
  1688. /\b(v[12]\d{3}\w?[at])(?: bui|;)/i
  1689. ],
  1690. [MODEL, [VENDOR, "Vivo"], [TYPE, MOBILE]],
  1691. [
  1692. // Realme
  1693. /\b(rmx[1-3]\d{3})(?: bui|;|\))/i
  1694. ],
  1695. [MODEL, [VENDOR, "Realme"], [TYPE, MOBILE]],
  1696. [
  1697. // Motorola
  1698. /\b(milestone|droid(?:[2-4x]| (?:bionic|x2|pro|razr))?:?( 4g)?)\b[\w ]+build\//i,
  1699. /\bmot(?:orola)?[- ](\w*)/i,
  1700. /((?:moto[\w\(\) ]+|xt\d{3,4}|nexus 6)(?= bui|\)))/i
  1701. ],
  1702. [MODEL, [VENDOR, MOTOROLA], [TYPE, MOBILE]],
  1703. [
  1704. /\b(mz60\d|xoom[2 ]{0,2}) build\//i
  1705. ],
  1706. [MODEL, [VENDOR, MOTOROLA], [TYPE, TABLET]],
  1707. [
  1708. // LG
  1709. /((?=lg)?[vl]k\-?\d{3}) bui| 3\.[-\w; ]{10}lg?-([06cv9]{3,4})/i
  1710. ],
  1711. [MODEL, [VENDOR, LG], [TYPE, TABLET]],
  1712. [
  1713. /(lm(?:-?f100[nv]?|-[\w\.]+)(?= bui|\))|nexus [45])/i,
  1714. /\blg[-e;\/ ]+((?!browser|netcast|android tv)\w+)/i,
  1715. /\blg-?([\d\w]+) bui/i
  1716. ],
  1717. [MODEL, [VENDOR, LG], [TYPE, MOBILE]],
  1718. [
  1719. // Lenovo
  1720. /(ideatab[-\w ]+)/i,
  1721. /lenovo ?(s[56]000[-\w]+|tab(?:[\w ]+)|yt[-\d\w]{6}|tb[-\d\w]{6})/i
  1722. ],
  1723. [MODEL, [VENDOR, LENOVO], [TYPE, TABLET]],
  1724. [
  1725. // Nokia
  1726. /(?:maemo|nokia).*(n900|lumia \d+)/i,
  1727. /nokia[-_ ]?([-\w\.]*)/i
  1728. ],
  1729. [[MODEL, /_/g, " "], [VENDOR, "Nokia"], [TYPE, MOBILE]],
  1730. [
  1731. // Google
  1732. /(pixel c)\b/i
  1733. // Google Pixel C
  1734. ],
  1735. [MODEL, [VENDOR, GOOGLE], [TYPE, TABLET]],
  1736. [
  1737. /droid.+; (pixel[\daxl ]{0,6})(?: bui|\))/i
  1738. // Google Pixel
  1739. ],
  1740. [MODEL, [VENDOR, GOOGLE], [TYPE, MOBILE]],
  1741. [
  1742. // Sony
  1743. /droid.+ (a?\d[0-2]{2}so|[c-g]\d{4}|so[-gl]\w+|xq-a\w[4-7][12])(?= bui|\).+chrome\/(?![1-6]{0,1}\d\.))/i
  1744. ],
  1745. [MODEL, [VENDOR, SONY], [TYPE, MOBILE]],
  1746. [
  1747. /sony tablet [ps]/i,
  1748. /\b(?:sony)?sgp\w+(?: bui|\))/i
  1749. ],
  1750. [[MODEL, "Xperia Tablet"], [VENDOR, SONY], [TYPE, TABLET]],
  1751. [
  1752. // OnePlus
  1753. / (kb2005|in20[12]5|be20[12][59])\b/i,
  1754. /(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i
  1755. ],
  1756. [MODEL, [VENDOR, "OnePlus"], [TYPE, MOBILE]],
  1757. [
  1758. // Amazon
  1759. /(alexa)webm/i,
  1760. /(kf[a-z]{2}wi|aeo[c-r]{2})( bui|\))/i,
  1761. // Kindle Fire without Silk / Echo Show
  1762. /(kf[a-z]+)( bui|\)).+silk\//i
  1763. // Kindle Fire HD
  1764. ],
  1765. [MODEL, [VENDOR, AMAZON], [TYPE, TABLET]],
  1766. [
  1767. /((?:sd|kf)[0349hijorstuw]+)( bui|\)).+silk\//i
  1768. // Fire Phone
  1769. ],
  1770. [[MODEL, /(.+)/g, "Fire Phone $1"], [VENDOR, AMAZON], [TYPE, MOBILE]],
  1771. [
  1772. // BlackBerry
  1773. /(playbook);[-\w\),; ]+(rim)/i
  1774. // BlackBerry PlayBook
  1775. ],
  1776. [MODEL, VENDOR, [TYPE, TABLET]],
  1777. [
  1778. /\b((?:bb[a-f]|st[hv])100-\d)/i,
  1779. /\(bb10; (\w+)/i
  1780. // BlackBerry 10
  1781. ],
  1782. [MODEL, [VENDOR, BLACKBERRY], [TYPE, MOBILE]],
  1783. [
  1784. // Asus
  1785. /(?:\b|asus_)(transfo[prime ]{4,10} \w+|eeepc|slider \w+|nexus 7|padfone|p00[cj])/i
  1786. ],
  1787. [MODEL, [VENDOR, ASUS], [TYPE, TABLET]],
  1788. [
  1789. / (z[bes]6[027][012][km][ls]|zenfone \d\w?)\b/i
  1790. ],
  1791. [MODEL, [VENDOR, ASUS], [TYPE, MOBILE]],
  1792. [
  1793. // HTC
  1794. /(nexus 9)/i
  1795. // HTC Nexus 9
  1796. ],
  1797. [MODEL, [VENDOR, "HTC"], [TYPE, TABLET]],
  1798. [
  1799. /(htc)[-;_ ]{1,2}([\w ]+(?=\)| bui)|\w+)/i,
  1800. // HTC
  1801. // ZTE
  1802. /(zte)[- ]([\w ]+?)(?: bui|\/|\))/i,
  1803. /(alcatel|geeksphone|nexian|panasonic(?!(?:;|\.))|sony(?!-bra))[-_ ]?([-\w]*)/i
  1804. // Alcatel/GeeksPhone/Nexian/Panasonic/Sony
  1805. ],
  1806. [VENDOR, [MODEL, /_/g, " "], [TYPE, MOBILE]],
  1807. [
  1808. // Acer
  1809. /droid.+; ([ab][1-7]-?[0178a]\d\d?)/i
  1810. ],
  1811. [MODEL, [VENDOR, "Acer"], [TYPE, TABLET]],
  1812. [
  1813. // Meizu
  1814. /droid.+; (m[1-5] note) bui/i,
  1815. /\bmz-([-\w]{2,})/i
  1816. ],
  1817. [MODEL, [VENDOR, "Meizu"], [TYPE, MOBILE]],
  1818. [
  1819. // Ulefone
  1820. /; ((?:power )?armor(?:[\w ]{0,8}))(?: bui|\))/i
  1821. ],
  1822. [MODEL, [VENDOR, "Ulefone"], [TYPE, MOBILE]],
  1823. [
  1824. // MIXED
  1825. /(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron|infinix|tecno)[-_ ]?([-\w]*)/i,
  1826. // BlackBerry/BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron
  1827. /(hp) ([\w ]+\w)/i,
  1828. // HP iPAQ
  1829. /(asus)-?(\w+)/i,
  1830. // Asus
  1831. /(microsoft); (lumia[\w ]+)/i,
  1832. // Microsoft Lumia
  1833. /(lenovo)[-_ ]?([-\w]+)/i,
  1834. // Lenovo
  1835. /(jolla)/i,
  1836. // Jolla
  1837. /(oppo) ?([\w ]+) bui/i
  1838. // OPPO
  1839. ],
  1840. [VENDOR, MODEL, [TYPE, MOBILE]],
  1841. [
  1842. /(kobo)\s(ereader|touch)/i,
  1843. // Kobo
  1844. /(archos) (gamepad2?)/i,
  1845. // Archos
  1846. /(hp).+(touchpad(?!.+tablet)|tablet)/i,
  1847. // HP TouchPad
  1848. /(kindle)\/([\w\.]+)/i
  1849. // Kindle
  1850. ],
  1851. [VENDOR, MODEL, [TYPE, TABLET]],
  1852. [
  1853. /(surface duo)/i
  1854. // Surface Duo
  1855. ],
  1856. [MODEL, [VENDOR, MICROSOFT], [TYPE, TABLET]],
  1857. [
  1858. /droid [\d\.]+; (fp\du?)(?: b|\))/i
  1859. // Fairphone
  1860. ],
  1861. [MODEL, [VENDOR, "Fairphone"], [TYPE, MOBILE]],
  1862. [
  1863. /(shield[\w ]+) b/i
  1864. // Nvidia Shield Tablets
  1865. ],
  1866. [MODEL, [VENDOR, "Nvidia"], [TYPE, TABLET]],
  1867. [
  1868. /(sprint) (\w+)/i
  1869. // Sprint Phones
  1870. ],
  1871. [VENDOR, MODEL, [TYPE, MOBILE]],
  1872. [
  1873. /(kin\.[onetw]{3})/i
  1874. // Microsoft Kin
  1875. ],
  1876. [[MODEL, /\./g, " "], [VENDOR, MICROSOFT], [TYPE, MOBILE]],
  1877. [
  1878. /droid.+; ([c6]+|et5[16]|mc[239][23]x?|vc8[03]x?)\)/i
  1879. // Zebra
  1880. ],
  1881. [MODEL, [VENDOR, ZEBRA], [TYPE, TABLET]],
  1882. [
  1883. /droid.+; (ec30|ps20|tc[2-8]\d[kx])\)/i
  1884. ],
  1885. [MODEL, [VENDOR, ZEBRA], [TYPE, MOBILE]],
  1886. [
  1887. ///////////////////
  1888. // SMARTTVS
  1889. ///////////////////
  1890. /smart-tv.+(samsung)/i
  1891. // Samsung
  1892. ],
  1893. [VENDOR, [TYPE, SMARTTV]],
  1894. [
  1895. /hbbtv.+maple;(\d+)/i
  1896. ],
  1897. [[MODEL, /^/, "SmartTV"], [VENDOR, SAMSUNG], [TYPE, SMARTTV]],
  1898. [
  1899. /(nux; netcast.+smarttv|lg (netcast\.tv-201\d|android tv))/i
  1900. // LG SmartTV
  1901. ],
  1902. [[VENDOR, LG], [TYPE, SMARTTV]],
  1903. [
  1904. /(apple) ?tv/i
  1905. // Apple TV
  1906. ],
  1907. [VENDOR, [MODEL, APPLE + " TV"], [TYPE, SMARTTV]],
  1908. [
  1909. /crkey/i
  1910. // Google Chromecast
  1911. ],
  1912. [[MODEL, CHROME + "cast"], [VENDOR, GOOGLE], [TYPE, SMARTTV]],
  1913. [
  1914. /droid.+aft(\w+)( bui|\))/i
  1915. // Fire TV
  1916. ],
  1917. [MODEL, [VENDOR, AMAZON], [TYPE, SMARTTV]],
  1918. [
  1919. /\(dtv[\);].+(aquos)/i,
  1920. /(aquos-tv[\w ]+)\)/i
  1921. // Sharp
  1922. ],
  1923. [MODEL, [VENDOR, SHARP], [TYPE, SMARTTV]],
  1924. [
  1925. /(bravia[\w ]+)( bui|\))/i
  1926. // Sony
  1927. ],
  1928. [MODEL, [VENDOR, SONY], [TYPE, SMARTTV]],
  1929. [
  1930. /(mitv-\w{5}) bui/i
  1931. // Xiaomi
  1932. ],
  1933. [MODEL, [VENDOR, XIAOMI], [TYPE, SMARTTV]],
  1934. [
  1935. /Hbbtv.*(technisat) (.*);/i
  1936. // TechniSAT
  1937. ],
  1938. [VENDOR, MODEL, [TYPE, SMARTTV]],
  1939. [
  1940. /\b(roku)[\dx]*[\)\/]((?:dvp-)?[\d\.]*)/i,
  1941. // Roku
  1942. /hbbtv\/\d+\.\d+\.\d+ +\([\w\+ ]*; *([\w\d][^;]*);([^;]*)/i
  1943. // HbbTV devices
  1944. ],
  1945. [[VENDOR, trim], [MODEL, trim], [TYPE, SMARTTV]],
  1946. [
  1947. /\b(android tv|smart[- ]?tv|opera tv|tv; rv:)\b/i
  1948. // SmartTV from Unidentified Vendors
  1949. ],
  1950. [[TYPE, SMARTTV]],
  1951. [
  1952. ///////////////////
  1953. // CONSOLES
  1954. ///////////////////
  1955. /(ouya)/i,
  1956. // Ouya
  1957. /(nintendo) (\w+)/i
  1958. // Nintendo
  1959. ],
  1960. [VENDOR, MODEL, [TYPE, CONSOLE]],
  1961. [
  1962. /droid.+; (shield) bui/i
  1963. // Nvidia
  1964. ],
  1965. [MODEL, [VENDOR, "Nvidia"], [TYPE, CONSOLE]],
  1966. [
  1967. /(playstation \w+)/i
  1968. // Playstation
  1969. ],
  1970. [MODEL, [VENDOR, SONY], [TYPE, CONSOLE]],
  1971. [
  1972. /\b(xbox(?: one)?(?!; xbox))[\); ]/i
  1973. // Microsoft Xbox
  1974. ],
  1975. [MODEL, [VENDOR, MICROSOFT], [TYPE, CONSOLE]],
  1976. [
  1977. ///////////////////
  1978. // WEARABLES
  1979. ///////////////////
  1980. /((pebble))app/i
  1981. // Pebble
  1982. ],
  1983. [VENDOR, MODEL, [TYPE, WEARABLE]],
  1984. [
  1985. /(watch)(?: ?os[,\/]|\d,\d\/)[\d\.]+/i
  1986. // Apple Watch
  1987. ],
  1988. [MODEL, [VENDOR, APPLE], [TYPE, WEARABLE]],
  1989. [
  1990. /droid.+; (wt63?0{2,3})\)/i
  1991. ],
  1992. [MODEL, [VENDOR, ZEBRA], [TYPE, WEARABLE]],
  1993. [
  1994. ///////////////////
  1995. // XR
  1996. ///////////////////
  1997. /droid.+; (glass) \d/i
  1998. // Google Glass
  1999. ],
  2000. [MODEL, [VENDOR, GOOGLE], [TYPE, XR]],
  2001. [
  2002. /(quest( \d| pro)?)/i
  2003. // Oculus Quest
  2004. ],
  2005. [MODEL, [VENDOR, FACEBOOK], [TYPE, XR]],
  2006. [
  2007. ///////////////////
  2008. // EMBEDDED
  2009. ///////////////////
  2010. /(tesla)(?: qtcarbrowser|\/[-\w\.]+)/i
  2011. // Tesla
  2012. ],
  2013. [VENDOR, [TYPE, EMBEDDED]],
  2014. [
  2015. /(aeobc)\b/i
  2016. // Echo Dot
  2017. ],
  2018. [MODEL, [VENDOR, AMAZON], [TYPE, EMBEDDED]],
  2019. [
  2020. ////////////////////
  2021. // MIXED (GENERIC)
  2022. ///////////////////
  2023. /droid .+?; ([^;]+?)(?: bui|; wv\)|\) applew).+? mobile safari/i
  2024. // Android Phones from Unidentified Vendors
  2025. ],
  2026. [MODEL, [TYPE, MOBILE]],
  2027. [
  2028. /droid .+?; ([^;]+?)(?: bui|\) applew).+?(?! mobile) safari/i
  2029. // Android Tablets from Unidentified Vendors
  2030. ],
  2031. [MODEL, [TYPE, TABLET]],
  2032. [
  2033. /\b((tablet|tab)[;\/]|focus\/\d(?!.+mobile))/i
  2034. // Unidentifiable Tablet
  2035. ],
  2036. [[TYPE, TABLET]],
  2037. [
  2038. /(phone|mobile(?:[;\/]| [ \w\/\.]*safari)|pda(?=.+windows ce))/i
  2039. // Unidentifiable Mobile
  2040. ],
  2041. [[TYPE, MOBILE]],
  2042. [
  2043. /(android[-\w\. ]{0,9});.+buil/i
  2044. // Generic Android Device
  2045. ],
  2046. [MODEL, [VENDOR, "Generic"]]
  2047. ],
  2048. engine: [
  2049. [
  2050. /windows.+ edge\/([\w\.]+)/i
  2051. // EdgeHTML
  2052. ],
  2053. [VERSION, [NAME, EDGE + "HTML"]],
  2054. [
  2055. /webkit\/537\.36.+chrome\/(?!27)([\w\.]+)/i
  2056. // Blink
  2057. ],
  2058. [VERSION, [NAME, "Blink"]],
  2059. [
  2060. /(presto)\/([\w\.]+)/i,
  2061. // Presto
  2062. /(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna)\/([\w\.]+)/i,
  2063. // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m/Goanna
  2064. /ekioh(flow)\/([\w\.]+)/i,
  2065. // Flow
  2066. /(khtml|tasman|links)[\/ ]\(?([\w\.]+)/i,
  2067. // KHTML/Tasman/Links
  2068. /(icab)[\/ ]([23]\.[\d\.]+)/i,
  2069. // iCab
  2070. /\b(libweb)/i
  2071. ],
  2072. [NAME, VERSION],
  2073. [
  2074. /rv\:([\w\.]{1,9})\b.+(gecko)/i
  2075. // Gecko
  2076. ],
  2077. [VERSION, NAME]
  2078. ],
  2079. os: [
  2080. [
  2081. // Windows
  2082. /microsoft (windows) (vista|xp)/i
  2083. // Windows (iTunes)
  2084. ],
  2085. [NAME, VERSION],
  2086. [
  2087. /(windows (?:phone(?: os)?|mobile))[\/ ]?([\d\.\w ]*)/i
  2088. // Windows Phone
  2089. ],
  2090. [NAME, [VERSION, strMapper, windowsVersionMap]],
  2091. [
  2092. /windows nt 6\.2; (arm)/i,
  2093. // Windows RT
  2094. /windows[\/ ]?([ntce\d\. ]+\w)(?!.+xbox)/i,
  2095. /(?:win(?=3|9|n)|win 9x )([nt\d\.]+)/i
  2096. ],
  2097. [[VERSION, strMapper, windowsVersionMap], [NAME, WINDOWS]],
  2098. [
  2099. // iOS/macOS
  2100. /ip[honead]{2,4}\b(?:.*os ([\w]+) like mac|; opera)/i,
  2101. // iOS
  2102. /(?:ios;fbsv\/|iphone.+ios[\/ ])([\d\.]+)/i,
  2103. /cfnetwork\/.+darwin/i
  2104. ],
  2105. [[VERSION, /_/g, "."], [NAME, "iOS"]],
  2106. [
  2107. /(mac os x) ?([\w\. ]*)/i,
  2108. /(macintosh|mac_powerpc\b)(?!.+haiku)/i
  2109. // Mac OS
  2110. ],
  2111. [[NAME, "macOS"], [VERSION, /_/g, "."]],
  2112. [
  2113. // Mobile OSes
  2114. /droid ([\w\.]+)\b.+(android[- ]x86|harmonyos)/i
  2115. // Android-x86/HarmonyOS
  2116. ],
  2117. [VERSION, NAME],
  2118. [
  2119. // Android/WebOS/QNX/Bada/RIM/Maemo/MeeGo/Sailfish OS
  2120. /(android|webos|qnx|bada|rim tablet os|maemo|meego|sailfish)[-\/ ]?([\w\.]*)/i,
  2121. /(blackberry)\w*\/([\w\.]*)/i,
  2122. // Blackberry
  2123. /(tizen|kaios)[\/ ]([\w\.]+)/i,
  2124. // Tizen/KaiOS
  2125. /\((series40);/i
  2126. // Series 40
  2127. ],
  2128. [NAME, VERSION],
  2129. [
  2130. /\(bb(10);/i
  2131. // BlackBerry 10
  2132. ],
  2133. [VERSION, [NAME, BLACKBERRY]],
  2134. [
  2135. /(?:symbian ?os|symbos|s60(?=;)|series60)[-\/ ]?([\w\.]*)/i
  2136. // Symbian
  2137. ],
  2138. [VERSION, [NAME, "Symbian"]],
  2139. [
  2140. /mozilla\/[\d\.]+ \((?:mobile|tablet|tv|mobile; [\w ]+); rv:.+ gecko\/([\w\.]+)/i
  2141. // Firefox OS
  2142. ],
  2143. [VERSION, [NAME, FIREFOX + " OS"]],
  2144. [
  2145. /web0s;.+rt(tv)/i,
  2146. /\b(?:hp)?wos(?:browser)?\/([\w\.]+)/i
  2147. // WebOS
  2148. ],
  2149. [VERSION, [NAME, "webOS"]],
  2150. [
  2151. /watch(?: ?os[,\/]|\d,\d\/)([\d\.]+)/i
  2152. // watchOS
  2153. ],
  2154. [VERSION, [NAME, "watchOS"]],
  2155. [
  2156. // Google Chromecast
  2157. /crkey\/([\d\.]+)/i
  2158. // Google Chromecast
  2159. ],
  2160. [VERSION, [NAME, CHROME + "cast"]],
  2161. [
  2162. /(cros) [\w]+(?:\)| ([\w\.]+)\b)/i
  2163. // Chromium OS
  2164. ],
  2165. [[NAME, "Chrome OS"], VERSION],
  2166. [
  2167. // Smart TVs
  2168. /panasonic;(viera)/i,
  2169. // Panasonic Viera
  2170. /(netrange)mmh/i,
  2171. // Netrange
  2172. /(nettv)\/(\d+\.[\w\.]+)/i,
  2173. // NetTV
  2174. // Console
  2175. /(nintendo|playstation) (\w+)/i,
  2176. // Nintendo/Playstation
  2177. /(xbox); +xbox ([^\);]+)/i,
  2178. // Microsoft Xbox (360, One, X, S, Series X, Series S)
  2179. // Other
  2180. /\b(joli|palm)\b ?(?:os)?\/?([\w\.]*)/i,
  2181. // Joli/Palm
  2182. /(mint)[\/\(\) ]?(\w*)/i,
  2183. // Mint
  2184. /(mageia|vectorlinux)[; ]/i,
  2185. // Mageia/VectorLinux
  2186. /([kxln]?ubuntu|debian|suse|opensuse|gentoo|arch(?= linux)|slackware|fedora|mandriva|centos|pclinuxos|red ?hat|zenwalk|linpus|raspbian|plan 9|minix|risc os|contiki|deepin|manjaro|elementary os|sabayon|linspire)(?: gnu\/linux)?(?: enterprise)?(?:[- ]linux)?(?:-gnu)?[-\/ ]?(?!chrom|package)([-\w\.]*)/i,
  2187. // Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware/Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus/Raspbian/Plan9/Minix/RISCOS/Contiki/Deepin/Manjaro/elementary/Sabayon/Linspire
  2188. /(hurd|linux) ?([\w\.]*)/i,
  2189. // Hurd/Linux
  2190. /(gnu) ?([\w\.]*)/i,
  2191. // GNU
  2192. /\b([-frentopcghs]{0,5}bsd|dragonfly)[\/ ]?(?!amd|[ix346]{1,2}86)([\w\.]*)/i,
  2193. // FreeBSD/NetBSD/OpenBSD/PC-BSD/GhostBSD/DragonFly
  2194. /(haiku) (\w+)/i
  2195. // Haiku
  2196. ],
  2197. [NAME, VERSION],
  2198. [
  2199. /(sunos) ?([\w\.\d]*)/i
  2200. // Solaris
  2201. ],
  2202. [[NAME, "Solaris"], VERSION],
  2203. [
  2204. /((?:open)?solaris)[-\/ ]?([\w\.]*)/i,
  2205. // Solaris
  2206. /(aix) ((\d)(?=\.|\)| )[\w\.])*/i,
  2207. // AIX
  2208. /\b(beos|os\/2|amigaos|morphos|openvms|fuchsia|hp-ux|serenityos)/i,
  2209. // BeOS/OS2/AmigaOS/MorphOS/OpenVMS/Fuchsia/HP-UX/SerenityOS
  2210. /(unix) ?([\w\.]*)/i
  2211. // UNIX
  2212. ],
  2213. [NAME, VERSION]
  2214. ]
  2215. };
  2216. var defaultProps = function() {
  2217. var props = { init: {}, isIgnore: {}, isIgnoreRgx: {}, toString: {} };
  2218. setProps.call(props.init, [
  2219. [UA_BROWSER, [NAME, VERSION, MAJOR, TYPE]],
  2220. [UA_CPU, [ARCHITECTURE]],
  2221. [UA_DEVICE, [TYPE, MODEL, VENDOR]],
  2222. [UA_ENGINE, [NAME, VERSION]],
  2223. [UA_OS, [NAME, VERSION]]
  2224. ]);
  2225. setProps.call(props.isIgnore, [
  2226. [UA_BROWSER, [VERSION, MAJOR]],
  2227. [UA_ENGINE, [VERSION]],
  2228. [UA_OS, [VERSION]]
  2229. ]);
  2230. setProps.call(props.isIgnoreRgx, [
  2231. [UA_BROWSER, / ?browser$/i],
  2232. [UA_OS, / ?os$/i]
  2233. ]);
  2234. setProps.call(props.toString, [
  2235. [UA_BROWSER, [NAME, VERSION]],
  2236. [UA_CPU, [ARCHITECTURE]],
  2237. [UA_DEVICE, [VENDOR, MODEL]],
  2238. [UA_ENGINE, [NAME, VERSION]],
  2239. [UA_OS, [NAME, VERSION]]
  2240. ]);
  2241. return props;
  2242. }();
  2243. var createIData = function(item, itemType) {
  2244. var init_props = defaultProps.init[itemType], is_ignoreProps = defaultProps.isIgnore[itemType] || 0, is_ignoreRgx = defaultProps.isIgnoreRgx[itemType] || 0, toString_props = defaultProps.toString[itemType] || 0;
  2245. function IData() {
  2246. setProps.call(this, init_props);
  2247. }
  2248. IData.prototype.getItem = function() {
  2249. return item;
  2250. };
  2251. IData.prototype.withClientHints = function() {
  2252. if (!NAVIGATOR_UADATA) {
  2253. return item.parseCH().get();
  2254. }
  2255. return NAVIGATOR_UADATA.getHighEntropyValues(CH_ALL_VALUES).then(function(res) {
  2256. return item.setCH(new UACHData(res, false)).parseCH().get();
  2257. });
  2258. };
  2259. IData.prototype.withFeatureCheck = function() {
  2260. return item.detectFeature().get();
  2261. };
  2262. if (itemType != UA_RESULT) {
  2263. IData.prototype.is = function(strToCheck) {
  2264. var is = false;
  2265. for (var i in this) {
  2266. if (this.hasOwnProperty(i) && !has(is_ignoreProps, i) && lowerize(is_ignoreRgx ? strip(is_ignoreRgx, this[i]) : this[i]) == lowerize(is_ignoreRgx ? strip(is_ignoreRgx, strToCheck) : strToCheck)) {
  2267. is = true;
  2268. if (strToCheck != UNDEF_TYPE)
  2269. break;
  2270. } else if (strToCheck == UNDEF_TYPE && is) {
  2271. is = !is;
  2272. break;
  2273. }
  2274. }
  2275. return is;
  2276. };
  2277. IData.prototype.toString = function() {
  2278. var str = EMPTY;
  2279. for (var i in toString_props) {
  2280. if (typeof this[toString_props[i]] !== UNDEF_TYPE) {
  2281. str += (str ? " " : EMPTY) + this[toString_props[i]];
  2282. }
  2283. }
  2284. return str || UNDEF_TYPE;
  2285. };
  2286. }
  2287. if (!NAVIGATOR_UADATA) {
  2288. IData.prototype.then = function(cb) {
  2289. var that = this;
  2290. var IDataResolve = function() {
  2291. for (var prop in that) {
  2292. if (that.hasOwnProperty(prop)) {
  2293. this[prop] = that[prop];
  2294. }
  2295. }
  2296. };
  2297. IDataResolve.prototype = {
  2298. is: IData.prototype.is,
  2299. toString: IData.prototype.toString
  2300. };
  2301. var resolveData = new IDataResolve();
  2302. cb(resolveData);
  2303. return resolveData;
  2304. };
  2305. }
  2306. return new IData();
  2307. };
  2308. function UACHData(uach, isHttpUACH) {
  2309. uach = uach || {};
  2310. setProps.call(this, CH_ALL_VALUES);
  2311. if (isHttpUACH) {
  2312. setProps.call(this, [
  2313. [BRANDS, itemListToArray(uach[CH_HEADER])],
  2314. [FULLVERLIST, itemListToArray(uach[CH_HEADER_FULL_VER_LIST])],
  2315. [MOBILE, /\?1/.test(uach[CH_HEADER_MOBILE])],
  2316. [MODEL, stripQuotes(uach[CH_HEADER_MODEL])],
  2317. [PLATFORM, stripQuotes(uach[CH_HEADER_PLATFORM])],
  2318. [PLATFORMVER, stripQuotes(uach[CH_HEADER_PLATFORM_VER])],
  2319. [ARCHITECTURE, stripQuotes(uach[CH_HEADER_ARCH])],
  2320. [FORMFACTORS, itemListToArray(uach[CH_HEADER_FORM_FACTORS])],
  2321. [BITNESS, stripQuotes(uach[CH_HEADER_BITNESS])]
  2322. ]);
  2323. } else {
  2324. for (var prop in uach) {
  2325. if (this.hasOwnProperty(prop) && typeof uach[prop] !== UNDEF_TYPE)
  2326. this[prop] = uach[prop];
  2327. }
  2328. }
  2329. }
  2330. function UAItem(itemType, ua, rgxMap, uaCH) {
  2331. this.get = function(prop) {
  2332. if (!prop)
  2333. return this.data;
  2334. return this.data.hasOwnProperty(prop) ? this.data[prop] : void 0;
  2335. };
  2336. this.set = function(prop, val) {
  2337. this.data[prop] = val;
  2338. return this;
  2339. };
  2340. this.setCH = function(ch) {
  2341. this.uaCH = ch;
  2342. return this;
  2343. };
  2344. this.detectFeature = function() {
  2345. if (NAVIGATOR && NAVIGATOR.userAgent == this.ua) {
  2346. switch (this.itemType) {
  2347. case UA_BROWSER:
  2348. if (NAVIGATOR.brave && typeof NAVIGATOR.brave.isBrave == FUNC_TYPE) {
  2349. this.set(NAME, "Brave");
  2350. }
  2351. break;
  2352. case UA_DEVICE:
  2353. if (!this.get(TYPE) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[MOBILE]) {
  2354. this.set(TYPE, MOBILE);
  2355. }
  2356. if (this.get(MODEL) == "Macintosh" && NAVIGATOR && typeof NAVIGATOR.standalone !== UNDEF_TYPE && NAVIGATOR.maxTouchPoints && NAVIGATOR.maxTouchPoints > 2) {
  2357. this.set(MODEL, "iPad").set(TYPE, TABLET);
  2358. }
  2359. break;
  2360. case UA_OS:
  2361. if (!this.get(NAME) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[PLATFORM]) {
  2362. this.set(NAME, NAVIGATOR_UADATA[PLATFORM]);
  2363. }
  2364. break;
  2365. case UA_RESULT:
  2366. var data = this.data;
  2367. var detect = function(itemType2) {
  2368. return data[itemType2].getItem().detectFeature().get();
  2369. };
  2370. this.set(UA_BROWSER, detect(UA_BROWSER)).set(UA_CPU, detect(UA_CPU)).set(UA_DEVICE, detect(UA_DEVICE)).set(UA_ENGINE, detect(UA_ENGINE)).set(UA_OS, detect(UA_OS));
  2371. }
  2372. }
  2373. return this;
  2374. };
  2375. this.parseUA = function() {
  2376. if (this.itemType != UA_RESULT) {
  2377. rgxMapper.call(this.data, this.ua, this.rgxMap);
  2378. }
  2379. if (this.itemType == UA_BROWSER) {
  2380. this.set(MAJOR, majorize(this.get(VERSION)));
  2381. }
  2382. return this;
  2383. };
  2384. this.parseCH = function() {
  2385. var uaCH2 = this.uaCH, rgxMap2 = this.rgxMap;
  2386. switch (this.itemType) {
  2387. case UA_BROWSER:
  2388. var brands = uaCH2[FULLVERLIST] || uaCH2[BRANDS], prevName;
  2389. if (brands) {
  2390. for (var i in brands) {
  2391. var brandName = strip(/(Google|Microsoft) /, brands[i].brand || brands[i]), brandVersion = brands[i].version;
  2392. if (!/not.a.brand/i.test(brandName) && (!prevName || /chrom/i.test(prevName) && !/chromi/i.test(brandName))) {
  2393. this.set(NAME, brandName).set(VERSION, brandVersion).set(MAJOR, majorize(brandVersion));
  2394. prevName = brandName;
  2395. }
  2396. }
  2397. }
  2398. break;
  2399. case UA_CPU:
  2400. var archName = uaCH2[ARCHITECTURE];
  2401. if (archName) {
  2402. if (archName && uaCH2[BITNESS] == "64")
  2403. archName += "64";
  2404. rgxMapper.call(this.data, archName + ";", rgxMap2);
  2405. }
  2406. break;
  2407. case UA_DEVICE:
  2408. if (uaCH2[MOBILE]) {
  2409. this.set(TYPE, MOBILE);
  2410. }
  2411. if (uaCH2[MODEL]) {
  2412. this.set(MODEL, uaCH2[MODEL]);
  2413. }
  2414. if (uaCH2[MODEL] == "Xbox") {
  2415. this.set(TYPE, CONSOLE).set(VENDOR, MICROSOFT);
  2416. }
  2417. if (uaCH2[FORMFACTORS]) {
  2418. var ff;
  2419. if (typeof uaCH2[FORMFACTORS] !== "string") {
  2420. var idx = 0;
  2421. while (!ff && idx < uaCH2[FORMFACTORS].length) {
  2422. ff = strMapper(uaCH2[FORMFACTORS][idx++], formFactorsMap);
  2423. }
  2424. } else {
  2425. ff = strMapper(uaCH2[FORMFACTORS], formFactorsMap);
  2426. }
  2427. this.set(TYPE, ff);
  2428. }
  2429. break;
  2430. case UA_OS:
  2431. var osName = uaCH2[PLATFORM];
  2432. if (osName) {
  2433. var osVersion = uaCH2[PLATFORMVER];
  2434. if (osName == WINDOWS)
  2435. osVersion = parseInt(majorize(osVersion), 10) >= 13 ? "11" : "10";
  2436. this.set(NAME, osName).set(VERSION, osVersion);
  2437. }
  2438. if (this.get(NAME) == WINDOWS && uaCH2[MODEL] == "Xbox") {
  2439. this.set(NAME, "Xbox").set(VERSION, void 0);
  2440. }
  2441. break;
  2442. case UA_RESULT:
  2443. var data = this.data;
  2444. var parse2 = function(itemType2) {
  2445. return data[itemType2].getItem().setCH(uaCH2).parseCH().get();
  2446. };
  2447. this.set(UA_BROWSER, parse2(UA_BROWSER)).set(UA_CPU, parse2(UA_CPU)).set(UA_DEVICE, parse2(UA_DEVICE)).set(UA_ENGINE, parse2(UA_ENGINE)).set(UA_OS, parse2(UA_OS));
  2448. }
  2449. return this;
  2450. };
  2451. setProps.call(this, [
  2452. ["itemType", itemType],
  2453. ["ua", ua],
  2454. ["uaCH", uaCH],
  2455. ["rgxMap", rgxMap],
  2456. ["data", createIData(this, itemType)]
  2457. ]);
  2458. return this;
  2459. }
  2460. function UAParser(ua, extensions, headers) {
  2461. if (typeof ua === OBJ_TYPE) {
  2462. if (isExtensions(ua, true)) {
  2463. if (typeof extensions === OBJ_TYPE) {
  2464. headers = extensions;
  2465. }
  2466. extensions = ua;
  2467. } else {
  2468. headers = ua;
  2469. extensions = void 0;
  2470. }
  2471. ua = void 0;
  2472. } else if (typeof ua === STR_TYPE && !isExtensions(extensions, true)) {
  2473. headers = extensions;
  2474. extensions = void 0;
  2475. }
  2476. if (!(this instanceof UAParser)) {
  2477. return new UAParser(ua, extensions, headers).getResult();
  2478. }
  2479. var userAgent2 = typeof ua === STR_TYPE ? ua : (
  2480. // Passed user-agent string
  2481. NAVIGATOR && NAVIGATOR.userAgent ? NAVIGATOR.userAgent : (
  2482. // navigator.userAgent
  2483. headers && headers[USER_AGENT] ? headers[USER_AGENT] : (
  2484. // User-Agent from passed headers
  2485. EMPTY
  2486. )
  2487. )
  2488. ), httpUACH = new UACHData(headers, true), regexMap = extensions ? extend(defaultRegexes, extensions) : defaultRegexes, createItemFunc = function(itemType) {
  2489. if (itemType == UA_RESULT) {
  2490. return function() {
  2491. return new UAItem(itemType, userAgent2, regexMap, httpUACH).set("ua", userAgent2).set(UA_BROWSER, this.getBrowser()).set(UA_CPU, this.getCPU()).set(UA_DEVICE, this.getDevice()).set(UA_ENGINE, this.getEngine()).set(UA_OS, this.getOS()).get();
  2492. };
  2493. } else {
  2494. return function() {
  2495. return new UAItem(itemType, userAgent2, regexMap[itemType], httpUACH).parseUA().get();
  2496. };
  2497. }
  2498. };
  2499. setProps.call(this, [
  2500. ["getBrowser", createItemFunc(UA_BROWSER)],
  2501. ["getCPU", createItemFunc(UA_CPU)],
  2502. ["getDevice", createItemFunc(UA_DEVICE)],
  2503. ["getEngine", createItemFunc(UA_ENGINE)],
  2504. ["getOS", createItemFunc(UA_OS)],
  2505. ["getResult", createItemFunc(UA_RESULT)],
  2506. ["getUA", function() {
  2507. return userAgent2;
  2508. }],
  2509. ["setUA", function(ua2) {
  2510. if (isString(ua2))
  2511. userAgent2 = ua2.length > UA_MAX_LENGTH ? trim(ua2, UA_MAX_LENGTH) : ua2;
  2512. return this;
  2513. }]
  2514. ]).setUA(userAgent2);
  2515. return this;
  2516. }
  2517. UAParser.VERSION = LIBVERSION;
  2518. UAParser.BROWSER = enumerize([NAME, VERSION, MAJOR, TYPE]);
  2519. UAParser.CPU = enumerize([ARCHITECTURE]);
  2520. UAParser.DEVICE = enumerize([MODEL, VENDOR, TYPE, CONSOLE, MOBILE, SMARTTV, TABLET, WEARABLE, EMBEDDED]);
  2521. UAParser.ENGINE = UAParser.OS = enumerize([NAME, VERSION]);
  2522. function redirect() {
  2523. const currentUrl = window.location.href;
  2524. if (/\/song\//.test(currentUrl)) {
  2525. const sheetId = currentUrl.match(new RegExp("(?<=\\/)\\d+(?=\\.)"))[0];
  2526. const newUrl = `https://www.91pu.com.tw/m/tone.shtml?id=${sheetId}`;
  2527. window.location.replace(newUrl);
  2528. }
  2529. }
  2530. function injectGtag() {
  2531. const newScript = document.createElement("script");
  2532. newScript.src = "https://www.googletagmanager.com/gtag/js?id=G-JF4S3HZY31";
  2533. newScript.async = true;
  2534. document.head.appendChild(newScript);
  2535. newScript.onload = () => {
  2536. window.dataLayer = window.dataLayer || [];
  2537. function gtag() {
  2538. window.dataLayer.push(arguments);
  2539. }
  2540. gtag("js", /* @__PURE__ */ new Date());
  2541. gtag("config", "G-JF4S3HZY31");
  2542. };
  2543. }
  2544. function getQueryParams() {
  2545. const url = new URL(window.location.href);
  2546. const params = {
  2547. transpose: +url.searchParams.get("transpose"),
  2548. darkMode: !!url.searchParams.get("darkmode")
  2549. };
  2550. return params;
  2551. }
  2552. function changeTitle() {
  2553. const newTitle = $("#mtitle").text().trim();
  2554. document.title = `${newTitle} | 91+`;
  2555. }
  2556. function archiveChordSheet() {
  2557. const sheet = document.getElementById("tone_z");
  2558. const chordSheetDocument = new ChordSheetDocument();
  2559. try {
  2560. const chordSheetElement = new ChordSheetElement(sheet);
  2561. chordSheetElement.formatUnderlines();
  2562. const formBody = {
  2563. id: chordSheetDocument.getId(),
  2564. title: chordSheetDocument.getTitle(),
  2565. key: chordSheetDocument.getKey(),
  2566. play: chordSheetDocument.getPlay(),
  2567. capo: chordSheetDocument.getCapo(),
  2568. singer: chordSheetDocument.getSinger(),
  2569. composer: chordSheetDocument.getComposer(),
  2570. lyricist: chordSheetDocument.getLyricist(),
  2571. bpm: chordSheetDocument.getBpm(),
  2572. sheet_text: chordSheetDocument.getSheetText()
  2573. };
  2574. chordSheetElement.unformatUnderlines();
  2575. fetch("https://91-plus-plus-api.fly.dev/archive", {
  2576. method: "POST",
  2577. headers: {
  2578. "Content-Type": "application/json"
  2579. },
  2580. body: JSON.stringify(formBody)
  2581. }).then((response) => {
  2582. console.log("[91 Plus] 雲端樂譜備份成功:", response);
  2583. }).catch((error) => {
  2584. console.error("[91 Plus] 雲端樂譜備份失敗:", error);
  2585. });
  2586. } catch {
  2587. console.warn("[91 Plus] 樂譜解析失敗,無法備份");
  2588. fetch(
  2589. `https://91-plus-plus-api.fly.dev/report?id=${chordSheetDocument.getId()}`
  2590. );
  2591. }
  2592. }
  2593. function initMutationObserver() {
  2594. return new MutationObserver((records, observer) => {
  2595. const isMutationDone = !!document.querySelector("#tone_z").childElementCount;
  2596. if (!isMutationDone) {
  2597. return;
  2598. }
  2599. $("body").trigger("mutation.done");
  2600. observer.disconnect();
  2601. }).observe(document.body, { childList: true, subtree: true });
  2602. }
  2603. function onDomReady(callback) {
  2604. $("body").on("mutation.done", callback);
  2605. }
  2606. function handleEvents() {
  2607. $("html").on("keydown", (event) => {
  2608. const excludedTags = ["input"];
  2609. const tagName = event.target.tagName.toLowerCase();
  2610. if (excludedTags.includes(tagName)) {
  2611. return;
  2612. }
  2613. StoreHandler.handleKeydown(event.key);
  2614. });
  2615. }
  2616. function switchInstrument(instrument) {
  2617. switch (instrument) {
  2618. case "guitar": {
  2619. $(".schord").trigger("click");
  2620. break;
  2621. }
  2622. case "ukulele": {
  2623. $(".ukschord").trigger("click");
  2624. break;
  2625. }
  2626. default: {
  2627. $(".nsChord").trigger("click");
  2628. break;
  2629. }
  2630. }
  2631. }
  2632. function getChordShapes() {
  2633. const chordShapes = _unsafeWindow.chord_shapes;
  2634. return chordShapes;
  2635. }
  2636. function getChordList() {
  2637. const chordList = [];
  2638. $("#tone_z .tf").each(function() {
  2639. chordList.push($(this).text());
  2640. });
  2641. return [...new Set(chordList)];
  2642. }
  2643. function convertChordName(chordName) {
  2644. const root = chordName.match(/^[A-G]#?/)[0];
  2645. const rest = chordName.replace(/^[A-G]#?/, "");
  2646. return `${rest} ${root}`;
  2647. }
  2648. const userAgent = UAParser(navigator.userAgent);
  2649. const _hoisted_1$b = { id: "plus91-sheet-popup" };
  2650. const _hoisted_2$a = { class: "sheet-popup-container" };
  2651. const _hoisted_3$4 = { class: "text-capo" };
  2652. const _hoisted_4$2 = ["innerHTML"];
  2653. const _hoisted_5$1 = { class: "transpose-range-container" };
  2654. const _hoisted_6$1 = ["value"];
  2655. const _hoisted_7 = { class: "instrument-select-container" };
  2656. const _sfc_main$c = {
  2657. __name: "SheetPopup",
  2658. setup(__props) {
  2659. const store = useStore();
  2660. return (_ctx, _cache) => {
  2661. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, {
  2662. default: vue.withCtx(() => [
  2663. vue.withDirectives(vue.createElementVNode("div", _hoisted_1$b, [
  2664. vue.createElementVNode("div", _hoisted_2$a, [
  2665. vue.createVNode(AdjustWidget, {
  2666. "onclick-left": () => {
  2667. vue.unref(store).plusTranspose(-1);
  2668. },
  2669. "onclick-middle": () => {
  2670. vue.unref(store).transpose = 0;
  2671. },
  2672. "onclick-right": () => {
  2673. vue.unref(store).plusTranspose(1);
  2674. }
  2675. }, {
  2676. default: vue.withCtx(() => [
  2677. vue.createTextVNode(" CAPO:"),
  2678. vue.createElementVNode("span", _hoisted_3$4, vue.toDisplayString(vue.unref(store).currentCapo), 1),
  2679. vue.createTextVNode(" ("),
  2680. vue.createElementVNode("span", {
  2681. class: "text-key",
  2682. innerHTML: vue.unref(store).currentKey
  2683. }, null, 8, _hoisted_4$2),
  2684. vue.createTextVNode(") ")
  2685. ]),
  2686. _: 1
  2687. }, 8, ["onclick-left", "onclick-middle", "onclick-right"]),
  2688. vue.createElementVNode("div", _hoisted_5$1, [
  2689. vue.createElementVNode("input", {
  2690. type: "range",
  2691. min: "-11",
  2692. max: "11",
  2693. value: vue.unref(store).currentCapo,
  2694. onInput: _cache[0] || (_cache[0] = ($event) => {
  2695. vue.unref(store).transpose = $event.target.value - vue.unref(store).originalCapo;
  2696. })
  2697. }, null, 40, _hoisted_6$1)
  2698. ]),
  2699. vue.createElementVNode("div", _hoisted_7, [
  2700. vue.createElementVNode("button", {
  2701. class: "instrument-select-button",
  2702. onClick: _cache[1] || (_cache[1] = () => {
  2703. vue.unref(switchInstrument)("");
  2704. })
  2705. }, " 無 "),
  2706. vue.createElementVNode("button", {
  2707. class: "instrument-select-button",
  2708. onClick: _cache[2] || (_cache[2] = () => {
  2709. vue.unref(switchInstrument)("guitar");
  2710. })
  2711. }, " 吉他 "),
  2712. vue.createElementVNode("button", {
  2713. class: "instrument-select-button",
  2714. onClick: _cache[3] || (_cache[3] = () => {
  2715. vue.unref(switchInstrument)("ukulele");
  2716. })
  2717. }, " 烏克莉莉 ")
  2718. ])
  2719. ])
  2720. ], 512), [
  2721. [vue.vShow, vue.unref(store).isPopupShow.sheet]
  2722. ])
  2723. ]),
  2724. _: 1
  2725. });
  2726. };
  2727. }
  2728. };
  2729. const SheetPopup = /* @__PURE__ */ _export_sfc(_sfc_main$c, [["__scopeId", "data-v-f161c46c"]]);
  2730. const _hoisted_1$a = {
  2731. key: 0,
  2732. class: "chord-container"
  2733. };
  2734. const _hoisted_2$9 = { class: "chord-name" };
  2735. const _hoisted_3$3 = ["chord-name"];
  2736. const _sfc_main$b = {
  2737. __name: "ChordChart",
  2738. props: {
  2739. chord: String
  2740. },
  2741. setup(__props) {
  2742. const props = __props;
  2743. const chordRef = vue.ref(void 0);
  2744. const chordShapes = getChordShapes();
  2745. const isChordExist = vue.ref(true);
  2746. vue.onMounted(() => {
  2747. var _a;
  2748. const formattedChordKey = convertChordName(props.chord);
  2749. const chordShape = chordShapes[formattedChordKey];
  2750. if (!chordShape) {
  2751. return isChordExist.value = false;
  2752. }
  2753. const chordObject = {
  2754. ...chordShape,
  2755. // position: chordShape.position,
  2756. // positionText: chordShape.position_text,
  2757. barres: (_a = chordShape.bars) == null ? void 0 : _a.map((barre) => {
  2758. return {
  2759. ...barre,
  2760. fromString: barre.from_string,
  2761. toString: barre.to_string
  2762. };
  2763. }),
  2764. chord: chordShape.chord.map(([stringNum, fretNum]) => {
  2765. const raw = [stringNum, fretNum];
  2766. if (isNaN(+fretNum)) {
  2767. return raw;
  2768. }
  2769. let newFretNum = fretNum;
  2770. newFretNum += chordShape.position || 0;
  2771. newFretNum -= chordShape.position_text || 0;
  2772. return [stringNum, newFretNum];
  2773. })
  2774. };
  2775. vue.nextTick(() => {
  2776. const width = chordRef.value.clientWidth;
  2777. const chordBoxSelector = `.chord-chart[chord-name="${props.chord}"]`;
  2778. const chordBox = new vexchords.ChordBox(chordBoxSelector, {
  2779. width,
  2780. height: width * 1.25,
  2781. circleRadius: 5,
  2782. numStrings: 6,
  2783. numFrets: 5,
  2784. showTuning: false,
  2785. defaultColor: "#444",
  2786. bgColor: "transparent"
  2787. });
  2788. chordBox.draw(chordObject);
  2789. });
  2790. });
  2791. return (_ctx, _cache) => {
  2792. return isChordExist.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$a, [
  2793. vue.createElementVNode("div", _hoisted_2$9, vue.toDisplayString(props.chord), 1),
  2794. vue.createElementVNode("div", {
  2795. class: "chord-chart",
  2796. "chord-name": props.chord,
  2797. ref_key: "chordRef",
  2798. ref: chordRef
  2799. }, null, 8, _hoisted_3$3)
  2800. ])) : vue.createCommentVNode("", true);
  2801. };
  2802. }
  2803. };
  2804. const ChordChart = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["__scopeId", "data-v-735734f6"]]);
  2805. const _hoisted_1$9 = { class: "banner" };
  2806. const _hoisted_2$8 = { class: "chord-popup-container" };
  2807. const _sfc_main$a = {
  2808. __name: "ChordPopup",
  2809. setup(__props) {
  2810. const store = useStore();
  2811. const canShowChord = vue.ref(true);
  2812. if (userAgent.browser.name === "Mobile Safari") {
  2813. canShowChord.value = false;
  2814. }
  2815. const bannerText = vue.ref("");
  2816. const bannerTextList = [
  2817. "此處的和弦圖示僅供參考!由於技術問題,目前尚無法準確繪製,尤其在把位較常出現錯誤,請注意。",
  2818. "在 91 譜中沒有資料的和弦是畫不出來的呦!"
  2819. ];
  2820. const randomIndex = vue.ref(0);
  2821. const refreshBanner = () => {
  2822. if (!canShowChord.value) {
  2823. bannerText.value = "很抱歉,由於技術問題,你所使用的瀏覽器目前尚無法繪製出和弦圖,開發者正在試著修正這個問題,敬請期待更新。";
  2824. } else {
  2825. randomIndex.value = Math.floor(Math.random() * bannerTextList.length);
  2826. bannerText.value = bannerTextList[randomIndex.value];
  2827. }
  2828. };
  2829. const chordList = vue.ref([]);
  2830. vue.watch(store.isPopupShow, () => {
  2831. if (!store.isPopupShow.chord) {
  2832. return;
  2833. }
  2834. refreshBanner();
  2835. chordList.value = getChordList();
  2836. });
  2837. return (_ctx, _cache) => {
  2838. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, {
  2839. default: vue.withCtx(() => [
  2840. vue.withDirectives(vue.createElementVNode("div", {
  2841. id: "plus91-chord-popup",
  2842. class: vue.normalizeClass({ "banner-only": !chordList.value.length })
  2843. }, [
  2844. vue.createElementVNode("div", _hoisted_1$9, [
  2845. vue.createVNode(BootstrapIcon, {
  2846. icon: "info-circle-fill",
  2847. color: "inherit",
  2848. size: "inherit"
  2849. }),
  2850. vue.createElementVNode("section", null, vue.toDisplayString(bannerText.value), 1)
  2851. ]),
  2852. vue.createElementVNode("div", _hoisted_2$8, [
  2853. (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(chordList.value, (chord) => {
  2854. return vue.openBlock(), vue.createBlock(ChordChart, {
  2855. key: `${chord}_${( new Date()).getTime()}`,
  2856. chord
  2857. }, null, 8, ["chord"]);
  2858. }), 128))
  2859. ])
  2860. ], 2), [
  2861. [vue.vShow, vue.unref(store).isPopupShow.chord]
  2862. ])
  2863. ]),
  2864. _: 1
  2865. });
  2866. };
  2867. }
  2868. };
  2869. const ChordPopup = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__scopeId", "data-v-2210cdf0"]]);
  2870. const _hoisted_1$8 = { id: "plus91-font-popup" };
  2871. const _hoisted_2$7 = { class: "font-popup-container" };
  2872. const _sfc_main$9 = {
  2873. __name: "FontSizePopup",
  2874. setup(__props) {
  2875. const store = useStore();
  2876. const getFontSize = vue.computed(() => {
  2877. return store.originalFontSize + store.fontSizeDelta;
  2878. });
  2879. return (_ctx, _cache) => {
  2880. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, {
  2881. default: vue.withCtx(() => [
  2882. vue.withDirectives(vue.createElementVNode("div", _hoisted_1$8, [
  2883. vue.createElementVNode("div", _hoisted_2$7, [
  2884. vue.createVNode(AdjustWidget, {
  2885. "onclick-left": () => {
  2886. vue.unref(store).fontSizeDelta--;
  2887. },
  2888. "onclick-middle": () => {
  2889. vue.unref(store).fontSizeDelta = 0;
  2890. },
  2891. "onclick-right": () => {
  2892. vue.unref(store).fontSizeDelta++;
  2893. },
  2894. "disabled-left": getFontSize.value <= 8,
  2895. "disabled-right": getFontSize.value >= 30
  2896. }, {
  2897. default: vue.withCtx(() => [
  2898. vue.createTextVNode(vue.toDisplayString(getFontSize.value) + "px ", 1)
  2899. ]),
  2900. _: 1
  2901. }, 8, ["onclick-left", "onclick-middle", "onclick-right", "disabled-left", "disabled-right"])
  2902. ])
  2903. ], 512), [
  2904. [vue.vShow, vue.unref(store).isPopupShow.font]
  2905. ])
  2906. ]),
  2907. _: 1
  2908. });
  2909. };
  2910. }
  2911. };
  2912. const FontSizePopup = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__scopeId", "data-v-eff17405"]]);
  2913. const _withScopeId = (n) => (vue.pushScopeId("data-v-e329f5af"), n = n(), vue.popScopeId(), n);
  2914. const _hoisted_1$7 = { id: "plus91-settings-popup" };
  2915. const _hoisted_2$6 = { class: "settings-popup-container" };
  2916. const _hoisted_3$2 = { class: "setting-item" };
  2917. const _hoisted_4$1 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", null, "深色模式", -1));
  2918. const _hoisted_5 = { class: "setting-item" };
  2919. const _hoisted_6 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", null, "協助測試雲端備份樂譜功能", -1));
  2920. const _sfc_main$8 = {
  2921. __name: "SettingsPopup",
  2922. setup(__props) {
  2923. const store = useStore();
  2924. return (_ctx, _cache) => {
  2925. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, {
  2926. default: vue.withCtx(() => [
  2927. vue.withDirectives(vue.createElementVNode("div", _hoisted_1$7, [
  2928. vue.createElementVNode("div", _hoisted_2$6, [
  2929. vue.createElementVNode("label", _hoisted_3$2, [
  2930. _hoisted_4$1,
  2931. vue.withDirectives(vue.createElementVNode("input", {
  2932. type: "checkbox",
  2933. "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => vue.unref(store).isDarkMode = $event)
  2934. }, null, 512), [
  2935. [vue.vModelCheckbox, vue.unref(store).isDarkMode]
  2936. ])
  2937. ]),
  2938. vue.createElementVNode("label", _hoisted_5, [
  2939. _hoisted_6,
  2940. vue.withDirectives(vue.createElementVNode("input", {
  2941. type: "checkbox",
  2942. "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => vue.unref(store).agreeToArchiveSheet = $event)
  2943. }, null, 512), [
  2944. [vue.vModelCheckbox, vue.unref(store).agreeToArchiveSheet]
  2945. ])
  2946. ])
  2947. ])
  2948. ], 512), [
  2949. [vue.vShow, vue.unref(store).isPopupShow.settings]
  2950. ])
  2951. ]),
  2952. _: 1
  2953. });
  2954. };
  2955. }
  2956. };
  2957. const SettingsPopup = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-e329f5af"]]);
  2958. const _hoisted_1$6 = { class: "icon-button" };
  2959. const _hoisted_2$5 = { class: "button-text" };
  2960. const _sfc_main$7 = {
  2961. __name: "MenuButton",
  2962. props: {
  2963. icon: String,
  2964. name: String,
  2965. color: String
  2966. },
  2967. setup(__props) {
  2968. vue.useCssVars((_ctx) => ({
  2969. "9047bc34": __props.color
  2970. }));
  2971. const props = __props;
  2972. return (_ctx, _cache) => {
  2973. return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$6, [
  2974. vue.createVNode(ToolbarIcon, {
  2975. icon: props.icon,
  2976. color: props.color
  2977. }, null, 8, ["icon", "color"]),
  2978. vue.createElementVNode("div", _hoisted_2$5, vue.toDisplayString(props.name), 1)
  2979. ]);
  2980. };
  2981. }
  2982. };
  2983. const MenuButton = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["__scopeId", "data-v-e9902592"]]);
  2984. const _hoisted_1$5 = { id: "plus91-menu-popup" };
  2985. const _hoisted_2$4 = { class: "menu-popup-container" };
  2986. const BUTTON_COLOR = "#555";
  2987. const _sfc_main$6 = {
  2988. __name: "MenuPopup",
  2989. setup(__props) {
  2990. const store = useStore();
  2991. const captureAsImage = () => {
  2992. const content = document.querySelector("section.content");
  2993. html2canvas(content).then((canvas) => {
  2994. const newWindow = window.open();
  2995. newWindow.document.write(`<img src="${canvas.toDataURL()}" />`);
  2996. });
  2997. };
  2998. const searchOnYoutube = () => {
  2999. const chordSheetDocument = new ChordSheetDocument();
  3000. const title = chordSheetDocument.getTitle();
  3001. const artist = chordSheetDocument.getSinger();
  3002. const url = `https://www.youtube.com/results?search_query=${title}+${artist}`;
  3003. window.open(url, "_blank").focus();
  3004. };
  3005. const goToGithubPage = () => {
  3006. const url = "https://github.com/DonkeyBear/91Plus/blob/main/README.md";
  3007. window.open(url, "_blank").focus();
  3008. };
  3009. return (_ctx, _cache) => {
  3010. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, {
  3011. default: vue.withCtx(() => [
  3012. vue.withDirectives(vue.createElementVNode("div", _hoisted_1$5, [
  3013. vue.createElementVNode("div", _hoisted_2$4, [
  3014. vue.createVNode(MenuButton, {
  3015. icon: "keyboard",
  3016. name: "快捷鍵",
  3017. color: BUTTON_COLOR,
  3018. onClick: _cache[0] || (_cache[0] = () => {
  3019. vue.unref(store).togglePopup("hotkey");
  3020. })
  3021. }),
  3022. vue.createVNode(MenuButton, {
  3023. icon: "file-earmark-image",
  3024. name: "擷取為圖片",
  3025. color: BUTTON_COLOR,
  3026. onClick: captureAsImage
  3027. }),
  3028. vue.createVNode(MenuButton, {
  3029. icon: "youtube",
  3030. name: "搜尋 YouTube",
  3031. color: BUTTON_COLOR,
  3032. onClick: searchOnYoutube
  3033. }),
  3034. vue.createVNode(MenuButton, {
  3035. icon: "github",
  3036. name: "關於 91 Plus",
  3037. color: BUTTON_COLOR,
  3038. onClick: goToGithubPage
  3039. })
  3040. ])
  3041. ], 512), [
  3042. [vue.vShow, vue.unref(store).isPopupShow.menu]
  3043. ])
  3044. ]),
  3045. _: 1
  3046. });
  3047. };
  3048. }
  3049. };
  3050. const MenuPopup = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-47af8eb5"]]);
  3051. const _hoisted_1$4 = { class: "hotkey-item" };
  3052. const _hoisted_2$3 = {
  3053. key: 0,
  3054. class: "hotkeys"
  3055. };
  3056. const _hoisted_3$1 = {
  3057. key: 1,
  3058. class: "hr"
  3059. };
  3060. const _sfc_main$5 = {
  3061. __name: "HotkeyItem",
  3062. props: {
  3063. hotkey: {
  3064. type: String,
  3065. required: false
  3066. },
  3067. desc: String
  3068. },
  3069. setup(__props) {
  3070. const props = __props;
  3071. const hotkeyList = props.hotkey.split(" ");
  3072. return (_ctx, _cache) => {
  3073. return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$4, [
  3074. vue.createElementVNode("div", {
  3075. class: vue.normalizeClass(["desc", { "title": !__props.hotkey }])
  3076. }, vue.toDisplayString(__props.desc), 3),
  3077. __props.hotkey ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2$3, [
  3078. (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(hotkeyList), (key) => {
  3079. return vue.openBlock(), vue.createElementBlock("kbd", {
  3080. key: `${key}_${__props.hotkey}_${__props.desc}`
  3081. }, vue.toDisplayString(key), 1);
  3082. }), 128))
  3083. ])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_3$1))
  3084. ]);
  3085. };
  3086. }
  3087. };
  3088. const HotkeyItem = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-3c43f6cf"]]);
  3089. const hotkeysLeft = [
  3090. {
  3091. hotkey: "空白鍵",
  3092. desc: "開啟 / 關閉功能選單"
  3093. },
  3094. {
  3095. hotkey: "ESC",
  3096. desc: "關閉功能選單"
  3097. },
  3098. {
  3099. hotkey: "/",
  3100. desc: "切換至搜尋框"
  3101. }
  3102. ];
  3103. const hotkeysRight = [
  3104. {
  3105. hotkey: "",
  3106. desc: "移調選單開啟時"
  3107. },
  3108. {
  3109. hotkey: "← →",
  3110. desc: "移調"
  3111. },
  3112. {
  3113. hotkey: "↓",
  3114. desc: "移回初始調"
  3115. },
  3116. {
  3117. hotkey: "",
  3118. desc: "在搜尋框內"
  3119. },
  3120. {
  3121. hotkey: "Enter",
  3122. desc: "搜尋"
  3123. },
  3124. {
  3125. hotkey: "ESC",
  3126. desc: "跳出搜尋框"
  3127. }
  3128. ];
  3129. const hotkeyData = {
  3130. hotkeysLeft,
  3131. hotkeysRight
  3132. };
  3133. const _hoisted_1$3 = { id: "plus91-hotkey-popup" };
  3134. const _hoisted_2$2 = { class: "hotkey-popup-container" };
  3135. const _hoisted_3 = { class: "left-part" };
  3136. const _hoisted_4 = { class: "right-part" };
  3137. const _sfc_main$4 = {
  3138. __name: "HotkeyPopup",
  3139. setup(__props) {
  3140. const store = useStore();
  3141. return (_ctx, _cache) => {
  3142. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide-and-fade" }, {
  3143. default: vue.withCtx(() => [
  3144. vue.withDirectives(vue.createElementVNode("div", _hoisted_1$3, [
  3145. vue.createElementVNode("div", _hoisted_2$2, [
  3146. vue.createElementVNode("section", _hoisted_3, [
  3147. (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(hotkeyData).hotkeysLeft, (item, index) => {
  3148. return vue.openBlock(), vue.createBlock(HotkeyItem, {
  3149. key: `${item.hotkey}_${item.desc}_${index}`,
  3150. hotkey: item.hotkey,
  3151. desc: item.desc
  3152. }, null, 8, ["hotkey", "desc"]);
  3153. }), 128))
  3154. ]),
  3155. vue.createElementVNode("section", _hoisted_4, [
  3156. (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(hotkeyData).hotkeysRight, (item, index) => {
  3157. return vue.openBlock(), vue.createBlock(HotkeyItem, {
  3158. key: `${item.hotkey}_${item.desc}_${index}`,
  3159. hotkey: item.hotkey,
  3160. desc: item.desc
  3161. }, null, 8, ["hotkey", "desc"]);
  3162. }), 128))
  3163. ])
  3164. ])
  3165. ], 512), [
  3166. [vue.vShow, vue.unref(store).isPopupShow.hotkey]
  3167. ])
  3168. ]),
  3169. _: 1
  3170. });
  3171. };
  3172. }
  3173. };
  3174. const HotkeyPopup = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-eb86b87c"]]);
  3175. const _hoisted_1$2 = { id: "plus91-footer" };
  3176. const _hoisted_2$1 = { class: "footer-container" };
  3177. const _sfc_main$3 = {
  3178. __name: "AppFooter",
  3179. props: {
  3180. active: Boolean
  3181. },
  3182. setup(__props) {
  3183. const store = useStore();
  3184. const props = __props;
  3185. return (_ctx, _cache) => {
  3186. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide" }, {
  3187. default: vue.withCtx(() => [
  3188. vue.withDirectives(vue.createElementVNode("div", _hoisted_1$2, [
  3189. vue.createElementVNode("div", _hoisted_2$1, [
  3190. vue.createVNode(ToolbarIcon, {
  3191. icon: "music-note-beamed",
  3192. text: "譜面",
  3193. stroke: ".05rem",
  3194. active: vue.unref(store).isPopupShow.sheet,
  3195. onClick: _cache[0] || (_cache[0] = ($event) => vue.unref(store).togglePopup("sheet"))
  3196. }, null, 8, ["active"]),
  3197. vue.createVNode(ToolbarIcon, {
  3198. icon: "table",
  3199. text: "和弦",
  3200. active: vue.unref(store).isPopupShow.chord,
  3201. onClick: _cache[1] || (_cache[1] = ($event) => vue.unref(store).togglePopup("chord"))
  3202. }, null, 8, ["active"]),
  3203. vue.createVNode(ToolbarIcon, {
  3204. icon: "type",
  3205. text: "字型",
  3206. stroke: ".05rem",
  3207. active: vue.unref(store).isPopupShow.font,
  3208. onClick: _cache[2] || (_cache[2] = ($event) => vue.unref(store).togglePopup("font"))
  3209. }, null, 8, ["active"]),
  3210. vue.createVNode(ToolbarIcon, {
  3211. icon: "gear-wide-connected",
  3212. text: "設定",
  3213. active: vue.unref(store).isPopupShow.settings,
  3214. onClick: _cache[3] || (_cache[3] = ($event) => vue.unref(store).togglePopup("settings"))
  3215. }, null, 8, ["active"]),
  3216. vue.createVNode(ToolbarIcon, {
  3217. icon: "list",
  3218. text: "其他",
  3219. stroke: ".05rem",
  3220. active: vue.unref(store).isPopupShow.menu,
  3221. onClick: _cache[4] || (_cache[4] = ($event) => vue.unref(store).togglePopup("menu"))
  3222. }, null, 8, ["active"]),
  3223. vue.createVNode(SheetPopup),
  3224. vue.createVNode(ChordPopup),
  3225. vue.createVNode(FontSizePopup),
  3226. vue.createVNode(SettingsPopup),
  3227. vue.createVNode(MenuPopup),
  3228. vue.createVNode(HotkeyPopup)
  3229. ])
  3230. ], 512), [
  3231. [vue.vShow, props.active]
  3232. ])
  3233. ]),
  3234. _: 1
  3235. });
  3236. };
  3237. }
  3238. };
  3239. const AppFooter = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-4e274b3c"]]);
  3240. const _hoisted_1$1 = { id: "plus91-header" };
  3241. const _hoisted_2 = { class: "header-container" };
  3242. const _sfc_main$2 = {
  3243. __name: "AppHeader",
  3244. props: {
  3245. active: Boolean
  3246. },
  3247. setup(__props) {
  3248. const props = __props;
  3249. const searchText = vue.ref("");
  3250. const search = () => {
  3251. if (!searchText.value) {
  3252. return;
  3253. }
  3254. const url = `https://www.91pu.com.tw/plus/search.php?keyword=${searchText.value}`;
  3255. window.open(url, "_blank").focus();
  3256. searchText.value = "";
  3257. };
  3258. const backToPreviousPage = () => {
  3259. history.back();
  3260. };
  3261. return (_ctx, _cache) => {
  3262. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "slide" }, {
  3263. default: vue.withCtx(() => [
  3264. vue.withDirectives(vue.createElementVNode("div", _hoisted_1$1, [
  3265. vue.createElementVNode("div", _hoisted_2, [
  3266. vue.createVNode(ToolbarIcon, {
  3267. icon: "chevron-left",
  3268. stroke: ".04rem",
  3269. onClick: backToPreviousPage
  3270. }),
  3271. vue.withDirectives(vue.createElementVNode("input", {
  3272. type: "text",
  3273. placeholder: "91 Plus",
  3274. "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchText.value = $event),
  3275. onKeydown: [
  3276. vue.withKeys(search, ["enter"]),
  3277. _cache[1] || (_cache[1] = vue.withKeys((event) => {
  3278. event.target.blur();
  3279. }, ["esc"]))
  3280. ]
  3281. }, null, 544), [
  3282. [
  3283. vue.vModelText,
  3284. searchText.value,
  3285. void 0,
  3286. { trim: true }
  3287. ]
  3288. ]),
  3289. vue.createVNode(ToolbarIcon, {
  3290. icon: "search",
  3291. stroke: ".03rem",
  3292. onClick: _cache[2] || (_cache[2] = ($event) => search())
  3293. })
  3294. ])
  3295. ], 512), [
  3296. [vue.vShow, props.active]
  3297. ])
  3298. ]),
  3299. _: 1
  3300. });
  3301. };
  3302. }
  3303. };
  3304. const AppHeader = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-5ddafe3d"]]);
  3305. const _hoisted_1 = { id: "dark-mode-overlay" };
  3306. const _sfc_main$1 = {
  3307. __name: "DarkModeOverlay",
  3308. props: {
  3309. active: Boolean
  3310. },
  3311. setup(__props) {
  3312. const props = __props;
  3313. return (_ctx, _cache) => {
  3314. return vue.openBlock(), vue.createBlock(vue.Transition, { name: "fade" }, {
  3315. default: vue.withCtx(() => [
  3316. vue.withDirectives(vue.createElementVNode("div", _hoisted_1, null, 512), [
  3317. [vue.vShow, props.active]
  3318. ])
  3319. ]),
  3320. _: 1
  3321. });
  3322. };
  3323. }
  3324. };
  3325. const DarkModeOverlay = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-6cf58435"]]);
  3326. const _sfc_main = {
  3327. __name: "App",
  3328. setup(__props) {
  3329. const store = useStore();
  3330. return (_ctx, _cache) => {
  3331. return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
  3332. vue.createVNode(TriggerOverlay, {
  3333. onClick: vue.unref(store).toggleToolbars
  3334. }, null, 8, ["onClick"]),
  3335. vue.createVNode(AppHeader, {
  3336. active: vue.unref(store).isToolbarsShow
  3337. }, null, 8, ["active"]),
  3338. vue.createVNode(AppFooter, {
  3339. active: vue.unref(store).isToolbarsShow
  3340. }, null, 8, ["active"]),
  3341. vue.createVNode(DarkModeOverlay, {
  3342. active: vue.unref(store).isDarkMode
  3343. }, null, 8, ["active"])
  3344. ], 64);
  3345. };
  3346. }
  3347. };
  3348. function init() {
  3349. redirect();
  3350. injectGtag();
  3351. initMutationObserver();
  3352. handleEvents();
  3353. const storeHandler = new StoreHandler().start();
  3354. onDomReady(() => {
  3355. changeTitle();
  3356. storeHandler.initState();
  3357. const store = useStore();
  3358. if (store.agreeToArchiveSheet) {
  3359. archiveChordSheet();
  3360. }
  3361. });
  3362. }
  3363. const pinia = createPinia();
  3364. pinia.use(src_default);
  3365. vue.createApp(_sfc_main).use(pinia).mount(
  3366. (() => {
  3367. const app = document.createElement("div");
  3368. app.id = "vue-91plus";
  3369. document.body.append(app);
  3370. return app;
  3371. })()
  3372. );
  3373. init();
  3374.  
  3375. })(Vue, zipson, vexchords, html2canvas);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址