From 92d793ede07922a7c1d2eed3f6e83c0a68004082 Mon Sep 17 00:00:00 2001 From: Magic Date: Wed, 6 Mar 2024 13:45:14 -0800 Subject: [PATCH] v6.5.1 Signed-off-by: Magic --- dist/magic-userjs.user.js | 659 +++++++++++++++++++------------------- package.json | 4 +- src/UserJS/header.js | 13 + src/UserJS/main.js | 642 ++++++++++++++++++------------------- src/sass/_main.scss | 2 + 5 files changed, 662 insertions(+), 658 deletions(-) diff --git a/dist/magic-userjs.user.js b/dist/magic-userjs.user.js index ca24a70..10be08f 100644 --- a/dist/magic-userjs.user.js +++ b/dist/magic-userjs.user.js @@ -19,7 +19,7 @@ // @description:zh-TW 显示站点的用户脚本 (UserJS)。 为 Tampermonkey 安装自定义脚本的简单方法。 // @author Magic // @icon data:image/png;base64,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 -// @version 6.5.0 +// @version 6.5.1 // @downloadURL https://github.com/magicoflolis/Userscript-Plus/releases/latest/download/magic-userjs.user.js // @updateURL https://github.com/magicoflolis/Userscript-Plus/releases/latest/download/magic-userjs.user.js // @namespace https://github.com/magicoflolis/Userscript-Plus @@ -51,12 +51,25 @@ // ==/UserScript== 'use strict'; (() => { +let userjs = (self.userjs = {}); +/** Skip text/plain documents */ +if ( + (document instanceof Document || + (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && + /^image\/|^text\/plain/.test(document.contentType || '') === false && + (self.userjs instanceof Object === false || userjs.UserJS !== true) +) { + userjs = self.userjs = { UserJS: true }; +} +if (!(typeof userjs === 'object' && userjs.UserJS && window && window.self === window.top)) { + return; +} /** * To compile this CSS `pnpm run build:Sass` * @desc Link to uncompiled Cascading Style Sheet * @link https://github.com/magicoflolis/Userscript-Plus/tree/master/src/sass */ -const main_css = `mujs-root{--mujs-even-row: hsl(222, 14%, 22%);--mujs-odd-row: hsl(222, 14%, 11%);--mujs-even-err: hsl(0, 100%, 22%);--mujs-odd-err: hsl(0, 100%, 11%);--mujs-background-color: hsl(222, 14%, 33%);--mujs-gf-color: hsl(204, 100%, 40%);--mujs-sf-color: hsl(12, 86%, 50%);--mujs-border-b-color: hsla(0, 0%, 0%, 0);--mujs-gf-btn-color: hsl(211, 87%, 56%);--mujs-sf-btn-color: hsl(12, 86%, 50%);--mujs-sf-txt-color: hsl(12, 79%, 55%);--mujs-txt-color: hsl(0, 0%, 100%);--mujs-chck-color: hsla(0, 0%, 100%, 0.568);--mujs-chck-gf: hsla(197, 100%, 50%, 0.568);--mujs-chck-git: hsla(213, 13%, 16%, 0.568);--mujs-chck-open: hsla(12, 86%, 50%, 0.568);--mujs-placeholder: hsl(81, 56%, 54%);--mujs-position-top: unset;--mujs-position-bottom: 1em;--mujs-position-left: unset;--mujs-position-right: 1em;font-family:Arial,Helvetica,sans-serif}mujs-root *{scrollbar-color:var(--mujs-txt-color, hsl(0, 0%, 100%)) #2e323d;scrollbar-width:thin}@supports not (scrollbar-width: thin){mujs-root * ::-webkit-scrollbar{width:1.4vw;height:3.3vh}mujs-root * ::-webkit-scrollbar-track{background-color:#2e323d;border-radius:16px;margin-top:3px;margin-bottom:3px;box-shadow:inset 0 0 6px rgba(0,0,0,.3)}mujs-root * ::-webkit-scrollbar-thumb{border-radius:16px;background-color:var(--mujs-txt-color, hsl(0, 0%, 100%));background-image:-webkit-linear-gradient(45deg, hsla(0, 0%, 100%, 0.2) 25%, transparent 25%, transparent 50%, hsla(0, 0%, 100%, 0.2) 50%, hsla(0, 0%, 100%, 0.2) 75%, transparent 75%, transparent)}mujs-root * ::-webkit-scrollbar-thumb:hover{background:var(--mujs-txt-color, hsl(0, 0%, 100%))}}mu-js{line-height:normal}mu-js{color:var(--mujs-txt-color, hsl(0, 0%, 100%))}body.webext-page,.main{font-size:14px}mujs-section>label,.mujs-homepag e,td.mujs-list,.install{font-size:16px}.install,.mujs-homepage{font-weight:700}mujs-section>label,td.mujs-list{font-weight:500}.mujs-sty-flex>mujs-btn{margin:auto}.mujs-invalid{border-radius:8px !important;border-width:2px !important;border-style:solid !important;border-color:red !important}mujs-tabs,mujs-column,mujs-row,.mujs-sty-flex{display:flex}mujs-column,mujs-row{gap:.5em}@media screen and (max-width: 800px){mujs-column{flex-flow:row wrap}}mujs-column count-frame:nth-child(1){background:var(--mujs-gf-color, hsl(204, 100%, 40%))}mujs-column count-frame:nth-child(2){background:var(--mujs-sf-color, hsl(12, 86%, 50%))}mujs-row{flex-flow:column wrap}mu-js{cursor:default}.hidden{display:none !important;z-index:-1 !important}.main{width:100%;width:-moz-available;width:-webkit-fill-available;background:var(--mujs-background-color, hsl(222, 14%, 33%)) !important;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));border-radius:16px}@media screen and (max-height: 720px){.main:not(.webext-page){height:100% !important;bottom:0rem !important;right:0rem !important;margin:0rem !important}}.main.expanded{height:100% !important;bottom:0rem !important}.main:not(.webext-page){position:fixed;height:492px}.main:not(.webext-page):not(.expanded){margin-left:1rem;margin-right:1rem;right:1rem;bottom:1rem}.main:not(.hidden){z-index:100000000000000000 !important;display:flex !important;flex-direction:column !important}.main mujs-tabs{gap:.5em;width:100%;width:-moz-available;width:-webkit-fill-available;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.main mujs-tabs mujs-tab{padding:.25em;min-width:150px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;display:flex;place-content:space-between;border:1px solid rgba(0,0,0,0);border-radius:4px;background:rgba(0,0,0,0)}.main mujs-tabs mujs-tab.active{background:var(--mujs-even-row, hsl(222, 14%, 18%))}.main mujs-tabs mujs-tab:not(.active):hover{background:var(--mujs-even-row, hsl(222, 14%, 18%))}.main mujs-tabs mujs-tab mujs-host{float:left}.main mujs-tabs mujs-tab mu-js{float:right}.main mujs-tabs mujs-addtab{order:999999999999;font-size:20px;padding:0px .25em}.main mujs-tabs mujs-addtab:hover{background:var(--mujs-even-row, hsl(222, 14%, 18%))}.main mujs-tab,.main mujs-btn,.main input{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.main input{background:rgba(0,0,0,0);color:var(--mujs-txt-color, hsl(0, 0%, 100%))}.main input:not([type=checkbox]){border:rgba(0,0,0,0);outline:none !important}.main textarea{background:inherit;overflow-y:auto;color:var(--mujs-placeholder, hsl(81, 56%, 54%));border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));border-radius:10px;resize:vertical;outline:none;font-family:monospace;font-size:14px}.main textarea:focus{outline:none}.main th,.main .mujs-cfg *:not(input[type=password],input[type=text],input[type=number]){-webkit-user-select:none !important;-moz-user-select:none !important;-ms-user-select:none !important;user-select:none !important}.main .mujs-footer{order:2;width:100%;width:-moz-available;width:-webkit-fill-available;overflow-x:hidden;text-align:center;border-radius:16px}.main .mujs-footer>*{min-height:50px}.main .mujs-footer>*:nth-child(even){background:var(--mujs-even-err, hsl(0, 100%, 22%)) !important}.main .mujs-footer>*:nth-child(odd){background:var(--mujs-odd-err, hsl(0, 100%, 11%)) !important}.mainframe{background:rgba(0,0,0,0);position:fixed;bottom:var(--mujs-position-bottom, 1rem);right:var(--mujs-position-right, 1rem);top:var(--mujs-position-top, unset);left:var(--mujs-position-left, unset)}.mainframe count-frame{width:fit-content;width:-moz-fit-content;width:-webkit-fit-content;height:auto;padding:14px 16px}.mainframe:not(.hidden){z-index:100000000000000000 !important;display:block}count-frame{border-radius:1000px;margin:0px 3px;padding:4px 6px;border:2px solid var(--mujs-border-b-color, hsla(0, 0%, 0%, 0));font-size:16px;font-weight:400;display:inline-block;text-align:center;min-width:1em;background:var(--mujs-background-color, hsl(222, 14%, 33%));color:var(--mujs-txt-color, hsl(0, 0%, 100%));-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mujs-header-prim{order:0;display:flex;border-bottom:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));border-top-left-radius:10px;border-top-right-radius:10px;padding:.5em;font-size:1em;place-content:space-between;height:fit-content;height:-moz-fit-content;height:-webkit-fit-content;width:100%;width:-moz-available;width:-webkit-fill-available}.mujs-body{order:1;overflow-x:hidden;padding:0px;width:100%;width:-moz-available;width:-webkit-fill-available;height:100%;border:1px solid var(--mujs-border-b-color, hsla(0, 0%, 0%, 0));border-bottom-left-radius:16px;border-bottom-right-radius:16px}.mujs-body .mujs-ratings{padding:0 .25em;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));border-radius:1000px}.mujs-body mu-jsbtn{color:var(--mujs-txt-color, hsl(0, 0%, 100%));-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mujs-body table,.mujs-body th,.mujs-body td{border-collapse:collapse}.mujs-body table{width:100%;width:-moz-available;width:-webkit-fill-available}@media screen and (max-width: 1180px){.mujs-body table thead>tr{display:table-column}.mujs-body table .frame:not(.webext-page){width:100%;display:flex;flex-flow:row wrap;align-items:center;border-bottom:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));padding-top:.5em;padding-bottom:.5em}.mujs-body table .frame:not(.webext-page) td{margin:auto;border-bottom:1px solid rgba(0,0,0,0)}.mujs-body table .frame:not(.webext-page) td>mujs-a,.mujs-body table .frame:not(.webext-page) td>mu-js,.mujs-body table .frame:not(.webext-page) td>mujs-column{text-align:center;justify-content:center}.mujs-body table .frame:not(.webext-page) td:not(.mujs-name,.install-btn){width:25%}.mujs-body table .frame:not(.webext-page) .mujs-name{width:100%}}.mujs-body table th,.mujs-body table td{border-bottom:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%))}.mujs-body table th{position:-webkit-sticky;position:sticky;top:0;background:rgba(72,79,96,.75)}.mujs-body table th.mujs-header-name{width:50%}@media screen and (max-width: 800px){.mujs-body table th.mujs-header-name{width:auto !important}}.mujs-body table .frame:nth-child(even){background:var(--mujs-even-row, hsl(222, 14%, 18%)) !important}.mujs-body table .frame:nth-child(odd){background:var(--mujs-odd-row, hsl(222, 14%, 33%)) !important}.mujs-body table .frame:not([data-engine=sleazyfork],[data-engine=greasyfork]) mujs-a{color:var(--mujs-sf-txt-color, hsl(12, 79%, 55%))}.mujs-body table .frame:not([data-engine=sleazyfork],[data-engine=greasyfork]) mu-jsbtn{background:var(--mujs-sf-btn-color, hsl(12, 86%, 50%));border-color:var(--mujs-sf-btn-color, hsl(12, 86%, 50%))}.mujs-body table .frame:not([data-engine=sleazyfork],[data-engine=greasyfork]) mu-jsbtn:hover{background:var(--mujs-sf-txt-color, hsl(12, 79%, 55%));border-color:var(--mujs-sf-txt-color, hsl(12, 79%, 55%))}.mujs-body table .frame[data-engine=sleazyfork] mujs-a,.mujs-body table .frame[data-engine=greasyfork] mujs-a{color:var(--mujs-gf-color, hsl(197, 100%, 50%))}.mujs-body table .frame[data-engine=sleazyfork] mujs-a:hover,.mujs-body table .frame[data-engine=greasyfork] mujs-a:hover{color:var(--mujs-gf-btn-color, hsl(211, 87%, 56%))}.mujs-body table .frame[data-engine=sleazyfork] mu-jsbtn,.mujs-body table .frame[data-engine=greasyfork] mu-jsbtn{color:var(--mujs-txt-color, hsl(0, 0%, 100%));background:var(--mujs-gf-color, hsl(204, 100%, 40%));border-color:var(--mujs-gf-color, hsl(204, 100%, 40%))}.mujs-body table .frame[data-engine=sleazyfork] mu-jsbtn:hover,.mujs-body table .frame[data-engine=greasyfork] mu-jsbtn:hover{background:var(--mujs-gf-btn-color, hsl(211, 87%, 56%));border-color:var(--mujs-gf-btn-color, hsl(211, 87%, 56%))}.mujs-body table .frame[data-good] mujs-a,.mujs-body table .frame[data-author] mujs-a{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-body table .frame[data-good] mujs-a:hover,.mujs-body table .frame[data-author] mujs-a:hover{color:#80ab30}.mujs-body table .frame[data-good] .mujs-list,.mujs-body table .frame[data-author] .mujs-list{color:#fff}.mujs-body table .frame[data-good] mu-jsbtn,.mujs-body table .frame[data-author] mu-jsbtn{color:#20385a;background:var(--mujs-placeholder, hsl(81, 56%, 54%));border-color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-body table .frame[data-good] mu-jsbtn:hover,.mujs-body table .frame[data-author] mu-jsbtn:hover{background:#b5d874;border-color:#b5d874}.mujs-body table .frame svg{fill:currentColor;width:14px;height:14px;background:rgba(0,0,0,0)}.mujs-body table .frame>td:not(.mujs-name){text-align:center}.mujs-body table .frame>.mujs-name>mujs-a{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.mujs-body table .frame>.mujs-name mu-jsbtn,.mujs-body table .frame>.mujs-name mu-js{height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.mujs-body table .frame>.mujs-name>mu-jsbtn{margin:auto}.mujs-body table .frame>.mujs-name>mujs-column>mu-jsbtn{padding:0px 7px}.mujs-body table .frame>.mujs-uframe>mujs-a{font-size:16px;font-weight:500;padding-left:.5rem;padding-right:.5rem}@media screen and (max-width: 1150px){.mujs-cfg{margin:0px auto 1rem auto !important}}.mujs-cfg{height:fit-content;height:-moz-fit-content;height:-webkit-fit-content}@media screen and (max-height: 720px){.mujs-cfg:not(.webext-page){height:100%;height:-moz-available;height:-webkit-fill-available;width:100%;width:-moz-available;width:-webkit-fill-available;overflow-x:auto;padding:.5em}}.mujs-cfg mujs-section{border-radius:16px;padding:.5em}.mujs-cfg mujs-section:nth-child(even){background:var(--mujs-even-row, hsl(222, 14%, 18%)) !important}.mujs-cfg mujs-section:nth-child(odd){background:var(--mujs-odd-row, hsl(222, 14%, 33%)) !important}.mujs-cfg mujs-section>label{display:flex;justify-content:space-between}.mujs-cfg mujs-section>label input:not([type=checkbox]){font-size:14px;position:relative;border-radius:4px;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%))}.mujs-cfg mujs-section>label input[type=text]::-webkit-input-placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg mujs-section>label input[type=text]::-moz-placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg mujs-section>label input[type=text]:-ms-input-placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg mujs-section>label input[type=text]::-ms-input-placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg mujs-section>label input[type=text]::placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg .mujs-inlab{position:relative;width:38px}.mujs-cfg .mujs-inlab input[type=checkbox]{display:none}.mujs-cfg .mujs-inlab input[type=checkbox]:checked+label{margin-left:0;background:var(--mujs-chck-color, hsla(0, 0%, 100%, 0.568))}.mujs-cfg .mujs-inlab input[type=checkbox]:checked+label:before{right:0px}.mujs-cfg .mujs-inlab input[type=checkbox][data-name=greasyfork]:checked+label,.mujs-cfg .mujs-inlab input[type=checkbox][data-name=sleazyfork]:checked+label{background:var(--mujs-chck-gf, hsla(197, 100%, 50%, 0.568))}.mujs-cfg .mujs-inlab input[type=checkbox][data-name=openuserjs]:checked+label{background:var(--mujs-chck-open, hsla(12, 86%, 50%, 0.568))}.mujs-cfg .mujs-inlab input[type=checkbox][data-name=github]:checked+label{background:var(--mujs-chck-git, hsla(213, 13%, 16%, 0.568))}.mujs-cfg .mujs-inlab label{padding:0;display:block;overflow:hidden;height:16px;border-radius:20px;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%))}.mujs-cfg .mujs-inlab label:before{content:"";display:block;width:20px;height:20px;margin:-2px;background:var(--mujs-txt-color, hsl(0, 0%, 100%));position:absolute;top:0;right:20px;border-radius:20px}.mujs-cfg .mujs-sty-flex mujs-btn{color:var(--mujs-txt-color, hsl(0, 0%, 100%))}.mujs-cfg .mujs-sty-flex mujs-btn[data-command=reset]{background:var(--mujs-sf-btn-color, hsl(12, 86%, 50%));border-color:var(--mujs-sf-btn-color, hsl(12, 86%, 50%))}.mujs-cfg .mujs-sty-flex mujs-btn[data-command=reset]:hover{background:var(--mujs-sf-txt-color, hsl(12, 79%, 55%));border-color:var(--mujs-sf-txt-color, hsl(12, 79%, 55%))}.mujs-cfg .mujs-sty-flex mujs-btn[data-command=save]{background:var(--mujs-gf-color, hsl(204, 100%, 40%));border-color:var(--mujs-gf-color, hsl(204, 100%, 40%))}.mujs-cfg .mujs-sty-flex mujs-btn[data-command=save]:hover{background:var(--mujs-gf-btn-color, hsl(211, 87%, 56%));border-color:var(--mujs-gf-btn-color, hsl(211, 87%, 56%))}.mujs-cfg:not(.webext-page){margin:1rem 25rem}mujs-a{display:inline-block}.mujs-name{display:flex;flex-flow:column wrap;gap:.5em}.mujs-name span{font-size:.8em !important}mujs-btn{font-style:normal;font-weight:500;font-variant:normal;text-transform:none;text-rendering:auto;text-align:center;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));font-size:16px;border-radius:4px;line-height:1;padding:6px 15px}mujs-btn svg{fill:var(--mujs-txt-color, hsl(0, 0%, 100%));width:14px;height:14px}mu-jsbtn{font-size:14px;border-radius:4px;font-style:normal;padding:7px 15%;font-weight:400;font-variant:normal;line-height:normal;display:block;text-align:center}mujs-a,mu-jsbtn,.mujs-pointer,.mujs-cfg mujs-section *:not(input[type=password],input[type=text],input[type=number]),.mainbtn,.mainframe,mujs-btn{cursor:pointer !important}/*# sourceMappingURL=magicuserjs.css.map */ +const main_css = `mujs-root{--mujs-even-row: hsl(222, 14%, 22%);--mujs-odd-row: hsl(222, 14%, 11%);--mujs-even-err: hsl(0, 100%, 22%);--mujs-odd-err: hsl(0, 100%, 11%);--mujs-background-color: hsl(222, 14%, 33%);--mujs-gf-color: hsl(204, 100%, 40%);--mujs-sf-color: hsl(12, 86%, 50%);--mujs-border-b-color: hsla(0, 0%, 0%, 0);--mujs-gf-btn-color: hsl(211, 87%, 56%);--mujs-sf-btn-color: hsl(12, 86%, 50%);--mujs-sf-txt-color: hsl(12, 79%, 55%);--mujs-txt-color: hsl(0, 0%, 100%);--mujs-chck-color: hsla(0, 0%, 100%, 0.568);--mujs-chck-gf: hsla(197, 100%, 50%, 0.568);--mujs-chck-git: hsla(213, 13%, 16%, 0.568);--mujs-chck-open: hsla(12, 86%, 50%, 0.568);--mujs-placeholder: hsl(81, 56%, 54%);--mujs-position-top: unset;--mujs-position-bottom: 1em;--mujs-position-left: unset;--mujs-position-right: 1em;font-family:Arial,Helvetica,sans-serif}mujs-root *{scrollbar-color:var(--mujs-txt-color, hsl(0, 0%, 100%)) #2e323d;scrollbar-width:thin}@supports not (scrollbar-width: thin){mujs-root * ::-webkit-scrollbar{width:1.4vw;height:3.3vh}mujs-root * ::-webkit-scrollbar-track{background-color:#2e323d;border-radius:16px;margin-top:3px;margin-bottom:3px;box-shadow:inset 0 0 6px rgba(0,0,0,.3)}mujs-root * ::-webkit-scrollbar-thumb{border-radius:16px;background-color:var(--mujs-txt-color, hsl(0, 0%, 100%));background-image:-webkit-linear-gradient(45deg, hsla(0, 0%, 100%, 0.2) 25%, transparent 25%, transparent 50%, hsla(0, 0%, 100%, 0.2) 50%, hsla(0, 0%, 100%, 0.2) 75%, transparent 75%, transparent)}mujs-root * ::-webkit-scrollbar-thumb:hover{background:var(--mujs-txt-color, hsl(0, 0%, 100%))}}mu-js{line-height:normal}mu-js{color:var(--mujs-txt-color, hsl(0, 0%, 100%))}body.webext-page,.main{font-size:14px}mujs-section>label,.mujs-homepag e,td.mujs-list,.install{font-size:16px}.install,.mujs-homepage{font-weight:700}mujs-section>label,td.mujs-list{font-weight:500}.mujs-sty-flex>mujs-btn{margin:auto}.mujs-invalid{border-radius:8px !important;border-width:2px !important;border-style:solid !important;border-color:red !important}mujs-tabs,mujs-column,mujs-row,.mujs-sty-flex{display:flex}mujs-column,mujs-row{gap:.5em}@media screen and (max-width: 800px){mujs-column{flex-flow:row wrap}}mujs-column count-frame:nth-child(1){background:var(--mujs-gf-color, hsl(204, 100%, 40%))}mujs-column count-frame:nth-child(2){background:var(--mujs-sf-color, hsl(12, 86%, 50%))}mujs-row{flex-flow:column wrap}mu-js{cursor:default}.hidden{display:none !important;z-index:-1 !important}.main{width:100%;width:-moz-available;width:-webkit-fill-available;background:var(--mujs-background-color, hsl(222, 14%, 33%)) !important;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));border-radius:16px}@media screen and (max-height: 720px){.main:not(.webext-page){height:100% !important;bottom:0rem !important;right:0rem !important;margin:0rem !important}}.main.expanded{height:100% !important;bottom:0rem !important}.main:not(.webext-page){position:fixed;height:492px}.main:not(.webext-page):not(.expanded){margin-left:1rem;margin-right:1rem;right:1rem;bottom:1rem}.main:not(.hidden){z-index:100000000000000000 !important;display:flex !important;flex-direction:column !important}.main mujs-tabs{gap:.5em;width:100%;width:-moz-available;width:-webkit-fill-available;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.main mujs-tabs mujs-tab{padding:.25em;min-width:150px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;display:flex;place-content:space-between;border:1px solid rgba(0,0,0,0);border-radius:4px;background:rgba(0,0,0,0)}.main mujs-tabs mujs-tab.active{background:var(--mujs-even-row, hsl(222, 14%, 18%))}.main mujs-tabs mujs-tab:not(.active):hover{background:var(--mujs-even-row, hsl(222, 14%, 18%))}.main mujs-tabs mujs-tab mujs-host{float:left}.main mujs-tabs mujs-tab mu-js{float:right}.main mujs-tabs mujs-addtab{order:999999999999;font-size:20px;padding:0px .25em}.main mujs-tabs mujs-addtab:hover{background:var(--mujs-even-row, hsl(222, 14%, 18%))}.main mujs-tab,.main mujs-btn,.main input{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.main input{background:rgba(0,0,0,0);color:var(--mujs-txt-color, hsl(0, 0%, 100%))}.main input:not([type=checkbox]){border:rgba(0,0,0,0);outline:none !important}.main textarea{background:inherit;overflow-y:auto;color:var(--mujs-placeholder, hsl(81, 56%, 54%));border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));border-radius:10px;resize:vertical;outline:none;font-family:monospace;font-size:14px}.main textarea:focus{outline:none}.main th,.main .mujs-cfg *:not(input[type=password],input[type=text],input[type=number]){-webkit-user-select:none !important;-moz-user-select:none !important;-ms-user-select:none !important;user-select:none !important}.main .mujs-footer{order:2;width:100%;width:-moz-available;width:-webkit-fill-available;overflow-x:hidden;text-align:center;border-radius:16px}.main .mujs-footer>*{min-height:50px}.main .mujs-footer .error:nth-child(even){background:var(--mujs-even-err, hsl(0, 100%, 22%)) !important}.main .mujs-footer .error:nth-child(odd){background:var(--mujs-odd-err, hsl(0, 100%, 11%)) !important}.mainframe{background:rgba(0,0,0,0);position:fixed;bottom:var(--mujs-position-bottom, 1rem);right:var(--mujs-position-right, 1rem);top:var(--mujs-position-top, unset);left:var(--mujs-position-left, unset)}.mainframe count-frame{width:fit-content;width:-moz-fit-content;width:-webkit-fit-content;height:auto;padding:14px 16px}.mainframe:not(.hidden){z-index:100000000000000000 !important;display:block}count-frame{border-radius:1000px;margin:0px 3px;padding:4px 6px;border:2px solid var(--mujs-border-b-color, hsla(0, 0%, 0%, 0));font-size:16px;font-weight:400;display:inline-block;text-align:center;min-width:1em;background:var(--mujs-background-color, hsl(222, 14%, 33%));color:var(--mujs-txt-color, hsl(0, 0%, 100%));-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mujs-header-prim{order:0;display:flex;border-bottom:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));border-top-left-radius:10px;border-top-right-radius:10px;padding:.5em;font-size:1em;place-content:space-between;height:fit-content;height:-moz-fit-content;height:-webkit-fit-content;width:100%;width:-moz-available;width:-webkit-fill-available}.mujs-body{order:1;overflow-x:hidden;padding:0px;width:100%;width:-moz-available;width:-webkit-fill-available;height:100%;border:1px solid var(--mujs-border-b-color, hsla(0, 0%, 0%, 0));border-bottom-left-radius:16px;border-bottom-right-radius:16px}.mujs-body .mujs-ratings{padding:0 .25em;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));border-radius:1000px}.mujs-body mu-jsbtn{color:var(--mujs-txt-color, hsl(0, 0%, 100%));-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mujs-body table,.mujs-body th,.mujs-body td{border-collapse:collapse}.mujs-body table{width:100%;width:-moz-available;width:-webkit-fill-available}@media screen and (max-width: 1180px){.mujs-body table thead>tr{display:table-column}.mujs-body table .frame:not(.webext-page){width:100%;display:flex;flex-flow:row wrap;align-items:center;border-bottom:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));padding-top:.5em;padding-bottom:.5em}.mujs-body table .frame:not(.webext-page) td{margin:auto;border-bottom:1px solid rgba(0,0,0,0)}.mujs-body table .frame:not(.webext-page) td>mujs-a,.mujs-body table .frame:not(.webext-page) td>mu-js,.mujs-body table .frame:not(.webext-page) td>mujs-column{text-align:center;justify-content:center}.mujs-body table .frame:not(.webext-page) td:not(.mujs-name,.install-btn){width:25%}.mujs-body table .frame:not(.webext-page) .mujs-name{width:100%}}.mujs-body table th,.mujs-body table td{border-bottom:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%))}.mujs-body table th{position:-webkit-sticky;position:sticky;top:0;background:rgba(72,79,96,.75)}.mujs-body table th.mujs-header-name{width:50%}@media screen and (max-width: 800px){.mujs-body table th.mujs-header-name{width:auto !important}}.mujs-body table .frame:nth-child(even){background:var(--mujs-even-row, hsl(222, 14%, 18%)) !important}.mujs-body table .frame:nth-child(odd){background:var(--mujs-odd-row, hsl(222, 14%, 33%)) !important}.mujs-body table .frame:not([data-engine=sleazyfork],[data-engine=greasyfork]) mujs-a{color:var(--mujs-sf-txt-color, hsl(12, 79%, 55%))}.mujs-body table .frame:not([data-engine=sleazyfork],[data-engine=greasyfork]) mu-jsbtn{background:var(--mujs-sf-btn-color, hsl(12, 86%, 50%));border-color:var(--mujs-sf-btn-color, hsl(12, 86%, 50%))}.mujs-body table .frame:not([data-engine=sleazyfork],[data-engine=greasyfork]) mu-jsbtn:hover{background:var(--mujs-sf-txt-color, hsl(12, 79%, 55%));border-color:var(--mujs-sf-txt-color, hsl(12, 79%, 55%))}.mujs-body table .frame[data-engine=sleazyfork] mujs-a,.mujs-body table .frame[data-engine=greasyfork] mujs-a{color:var(--mujs-gf-color, hsl(197, 100%, 50%))}.mujs-body table .frame[data-engine=sleazyfork] mujs-a:hover,.mujs-body table .frame[data-engine=greasyfork] mujs-a:hover{color:var(--mujs-gf-btn-color, hsl(211, 87%, 56%))}.mujs-body table .frame[data-engine=sleazyfork] mu-jsbtn,.mujs-body table .frame[data-engine=greasyfork] mu-jsbtn{color:var(--mujs-txt-color, hsl(0, 0%, 100%));background:var(--mujs-gf-color, hsl(204, 100%, 40%));border-color:var(--mujs-gf-color, hsl(204, 100%, 40%))}.mujs-body table .frame[data-engine=sleazyfork] mu-jsbtn:hover,.mujs-body table .frame[data-engine=greasyfork] mu-jsbtn:hover{background:var(--mujs-gf-btn-color, hsl(211, 87%, 56%));border-color:var(--mujs-gf-btn-color, hsl(211, 87%, 56%))}.mujs-body table .frame[data-good] mujs-a,.mujs-body table .frame[data-author] mujs-a{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-body table .frame[data-good] mujs-a:hover,.mujs-body table .frame[data-author] mujs-a:hover{color:#80ab30}.mujs-body table .frame[data-good] .mujs-list,.mujs-body table .frame[data-author] .mujs-list{color:#fff}.mujs-body table .frame[data-good] mu-jsbtn,.mujs-body table .frame[data-author] mu-jsbtn{color:#20385a;background:var(--mujs-placeholder, hsl(81, 56%, 54%));border-color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-body table .frame[data-good] mu-jsbtn:hover,.mujs-body table .frame[data-author] mu-jsbtn:hover{background:#b5d874;border-color:#b5d874}.mujs-body table .frame svg{fill:currentColor;width:14px;height:14px;background:rgba(0,0,0,0)}.mujs-body table .frame>td:not(.mujs-name){text-align:center}.mujs-body table .frame>.mujs-name>mujs-a{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.mujs-body table .frame>.mujs-name mu-jsbtn,.mujs-body table .frame>.mujs-name mu-js{height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.mujs-body table .frame>.mujs-name>mu-jsbtn{margin:auto}.mujs-body table .frame>.mujs-name>mujs-column>mu-jsbtn{padding:0px 7px}.mujs-body table .frame>.mujs-uframe>mujs-a{font-size:16px;font-weight:500;padding-left:.5rem;padding-right:.5rem}@media screen and (max-width: 1150px){.mujs-cfg{margin:0px auto 1rem auto !important}}.mujs-cfg{height:fit-content;height:-moz-fit-content;height:-webkit-fit-content}@media screen and (max-height: 720px){.mujs-cfg:not(.webext-page){height:100%;height:-moz-available;height:-webkit-fill-available;width:100%;width:-moz-available;width:-webkit-fill-available;overflow-x:auto;padding:.5em}}.mujs-cfg mujs-section{border-radius:16px;padding:.5em}.mujs-cfg mujs-section:nth-child(even){background:var(--mujs-even-row, hsl(222, 14%, 18%)) !important}.mujs-cfg mujs-section:nth-child(odd){background:var(--mujs-odd-row, hsl(222, 14%, 33%)) !important}.mujs-cfg mujs-section>label{display:flex;justify-content:space-between}.mujs-cfg mujs-section>label input:not([type=checkbox]){font-size:14px;position:relative;border-radius:4px;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%))}.mujs-cfg mujs-section>label input[type=text]::-webkit-input-placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg mujs-section>label input[type=text]::-moz-placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg mujs-section>label input[type=text]:-ms-input-placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg mujs-section>label input[type=text]::-ms-input-placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg mujs-section>label input[type=text]::placeholder{color:var(--mujs-placeholder, hsl(81, 56%, 54%))}.mujs-cfg .mujs-inlab{position:relative;width:38px}.mujs-cfg .mujs-inlab input[type=checkbox]{display:none}.mujs-cfg .mujs-inlab input[type=checkbox]:checked+label{margin-left:0;background:var(--mujs-chck-color, hsla(0, 0%, 100%, 0.568))}.mujs-cfg .mujs-inlab input[type=checkbox]:checked+label:before{right:0px}.mujs-cfg .mujs-inlab input[type=checkbox][data-name=greasyfork]:checked+label,.mujs-cfg .mujs-inlab input[type=checkbox][data-name=sleazyfork]:checked+label{background:var(--mujs-chck-gf, hsla(197, 100%, 50%, 0.568))}.mujs-cfg .mujs-inlab input[type=checkbox][data-name=openuserjs]:checked+label{background:var(--mujs-chck-open, hsla(12, 86%, 50%, 0.568))}.mujs-cfg .mujs-inlab input[type=checkbox][data-name=github]:checked+label{background:var(--mujs-chck-git, hsla(213, 13%, 16%, 0.568))}.mujs-cfg .mujs-inlab label{padding:0;display:block;overflow:hidden;height:16px;border-radius:20px;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%))}.mujs-cfg .mujs-inlab label:before{content:"";display:block;width:20px;height:20px;margin:-2px;background:var(--mujs-txt-color, hsl(0, 0%, 100%));position:absolute;top:0;right:20px;border-radius:20px}.mujs-cfg .mujs-sty-flex mujs-btn{color:var(--mujs-txt-color, hsl(0, 0%, 100%))}.mujs-cfg .mujs-sty-flex mujs-btn[data-command=reset]{background:var(--mujs-sf-btn-color, hsl(12, 86%, 50%));border-color:var(--mujs-sf-btn-color, hsl(12, 86%, 50%))}.mujs-cfg .mujs-sty-flex mujs-btn[data-command=reset]:hover{background:var(--mujs-sf-txt-color, hsl(12, 79%, 55%));border-color:var(--mujs-sf-txt-color, hsl(12, 79%, 55%))}.mujs-cfg .mujs-sty-flex mujs-btn[data-command=save]{background:var(--mujs-gf-color, hsl(204, 100%, 40%));border-color:var(--mujs-gf-color, hsl(204, 100%, 40%))}.mujs-cfg .mujs-sty-flex mujs-btn[data-command=save]:hover{background:var(--mujs-gf-btn-color, hsl(211, 87%, 56%));border-color:var(--mujs-gf-btn-color, hsl(211, 87%, 56%))}.mujs-cfg:not(.webext-page){margin:1rem 25rem}mujs-a{display:inline-block}.mujs-name{display:flex;flex-flow:column wrap;gap:.5em}.mujs-name span{font-size:.8em !important}mujs-btn{font-style:normal;font-weight:500;font-variant:normal;text-transform:none;text-rendering:auto;text-align:center;border:1px solid var(--mujs-txt-color, hsl(0, 0%, 100%));font-size:16px;border-radius:4px;line-height:1;padding:6px 15px}mujs-btn svg{fill:var(--mujs-txt-color, hsl(0, 0%, 100%));width:14px;height:14px}mu-jsbtn{font-size:14px;border-radius:4px;font-style:normal;padding:7px 15%;font-weight:400;font-variant:normal;line-height:normal;display:block;text-align:center}mujs-a,mu-jsbtn,.mujs-pointer,.mujs-cfg mujs-section *:not(input[type=password],input[type=text],input[type=number]),.mainbtn,.mainframe,mujs-btn{cursor:pointer !important} `; /** * Link to uncompressed locales + compiler @@ -64,16 +77,6 @@ const main_css = `mujs-root{--mujs-even-row: hsl(222, 14%, 22%);--mujs-odd-row: * @link https://github.com/magicoflolis/Userscript-Plus/blob/master/tools/languageLoader.js */ const languageList = {"en":{"legacy":"PLEASE RESET YOUR CONFIG!","createdby":"Created by","name":"Name","daily":"Daily Installs","close":"Close","filterA":"Filter","max":"Maximize","min":"Minimize","search":"Search","searcher":"Title | Description | Author...","install":"Install","issue":"New Issue","version":"Version","updated":"Last Updated","total":"Total Installs","rating":"Ratings","good":"Good","ok":"Ok","bad":"Bad","created":"Created","redirect":"Greasy Fork for adults","filter":"Filter out other languages","dtime":"Display Timeout","save":"Save","reset":"Reset","codePreview":"Preview Code","saveFile":"Save File"},"es":{"legacy":"¡POR FAVOR RESTABLECE TU CONFIGURACIÓN!","createdby":"Creado por","name":"Nombre","daily":"Instalaciones diarias","close":"Ya no se muestra","filterA":"Filtro","max":"Maximizar","min":"Minimizar","search":"Busque en","searcher":"Título | Descripción | Autor...","install":"Instalar","issue":"Nueva edición","version":"Versión","updated":"Última actualización","total":"Total de instalaciones","rating":"Clasificaciones","good":"Bueno","ok":"Ok","bad":"Malo","created":"Creado","redirect":"Greasy Fork para adultos","filter":"Filtrar otros idiomas","dtime":"Mostrar el tiempo de espera","save":"Guardar","reset":"Reiniciar","codePreview":"Preview Code","saveFile":"Save File"},"fr":{"legacy":"VEUILLEZ RÉINITIALISER VOTRE CONFIG !","createdby":"Créé par","name":"Nom","daily":"Installations quotidiennes","close":"Ne plus montrer","filterA":"Filtre","max":"Maximiser","min":"Minimiser","search":"Recherche","searcher":"Titre | Description | Auteur...","install":"Installer","issue":"Nouveau numéro","version":"Version","updated":"Dernière mise à jour","total":"Total des installations","rating":"Notations","good":"Bon","ok":"Ok","bad":"Mauvais","created":"Créé","redirect":"Greasy Fork pour les adultes","filter":"Filtrer les autres langues","dtime":"Délai d'affichage","save":"Sauvez","reset":"Réinitialiser","codePreview":"Preview Code","saveFile":"Save File"},"ja":{"legacy":"設定をリセットしてください。","createdby":"によって作成された","name":"名前","daily":"デイリーインストール","close":"表示されなくなりました","filterA":"フィルター","max":"最大化","min":"ミニマム","search":"検索","searcher":"タイトル|説明|著者...","install":"インストール","issue":"新刊のご案内","version":"バージョン","updated":"最終更新日","total":"総インストール数","rating":"レーティング","good":"グッド","ok":"良い","bad":"悪い","created":"作成","redirect":"大人のGreasyfork","filter":"他の言語をフィルタリングする","dtime":"表示タイムアウト","save":"拯救","reset":"リセット","codePreview":"Preview Code","saveFile":"Save File"},"nl":{"legacy":"RESET UW CONFIG!","createdby":"Gemaakt door","name":"Naam","daily":"Dagelijkse Installaties","close":"Sluit","filterA":"Filter","max":"Maximaliseer","min":"Minimaliseer","search":"Zoek","searcher":"Titel | Beschrijving | Auteur...","install":"Installeer","issue":"Nieuw Issue","version":"Versie","updated":"Laatste Update","total":"Totale Installaties","rating":"Beoordeling","good":"Goed","ok":"Ok","bad":"Slecht","created":"Aangemaakt","redirect":"Greasy Fork voor volwassenen","filter":"Filter andere talen","dtime":"Weergave timeout","save":"Opslaan","reset":"Opnieuw instellen","codePreview":"Preview Code","saveFile":"Save File"},"ru":{"legacy":"ПОЖАЛУЙСТА, СБРОСЬТЕ КОНФИГ!","createdby":"Сделано","name":"Имя","daily":"Ежедневные установки","close":"Больше не показывать","filterA":"Фильтр","max":"Максимизировать","min":"Минимизировать","search":"Поиск","searcher":"Название | Описание | Автор...","install":"Установите","issue":"Новый выпуск","version":"Версия","updated":"Последнее обновление","total":"Всего установок","rating":"Рейтинги","good":"Хорошо","ok":"Хорошо","bad":"Плохо","created":"Создано","redirect":"Greasy Fork для взрослых","filter":"Отфильтровать другие языки","dtime":"Тайм-аут отображения","save":"Сохранить","reset":"Перезагрузить","codePreview":"Preview Code","saveFile":"Save File"},"zh":{"legacy":"请重置您的配置!","createdby":"由...制作","name":"姓名","daily":"日常安装","close":"不再显示","filterA":"过滤器","max":"最大化","min":"最小化","search":"搜索","searcher":"标题|描述|作者...","install":"安装","issue":"新问题","version":"版本","updated":"最后更新","total":"总安装量","rating":"评级","good":"好的","ok":"好的","bad":"不好","created":"创建","redirect":"大人的Greasyfork","filter":"过滤掉其他语言","dtime":"显示超时","save":"拯救","reset":"重置","codePreview":"Preview Code","saveFile":"Save File"}}; -let userjs = (self.userjs = {}); -/** Skip text/plain documents */ -if ( - (document instanceof Document || - (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && - /^image\/|^text\/plain/.test(document.contentType || '') === false && - (self.userjs instanceof Object === false || userjs.UserJS !== true) -) { - userjs = self.userjs = { UserJS: true }; -} let cfg = {}; let lang = {}; let legacyMsg = null; @@ -103,18 +106,19 @@ const isMobile = /Mobile|Tablet/.test(navigator.userAgent); const Supports = { gm: typeof GM !== 'undefined' }; -//#region Console -// const dbg = (...msg) => { -// const dt = new Date(); -// console.debug( -// '[%cUserJS%c] %cDBG', -// 'color: rgb(29, 155, 240);', -// '', -// 'color: rgb(255, 212, 0);', -// `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, -// ...msg -// ); -// }; +// #region Console +// eslint-disable-next-line no-unused-vars +const dbg = (...msg) => { + const dt = new Date(); + console.debug( + '[%cUserJS%c] %cDBG', + 'color: rgb(29, 155, 240);', + '', + 'color: rgb(255, 212, 0);', + `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, + ...msg + ); +}; const err = (...msg) => { console.error( '[%cUserJS%c] %cERROR', @@ -152,7 +156,7 @@ const log = (...msg) => { ...msg ); }; -//#endregion +// #endregion const MU = {}; const hasOwn = Object.hasOwn || Object.prototype.hasOwnProperty.call; /** @@ -426,6 +430,27 @@ class Timeout { }); } } +class Memorize { + constructor() { + this.cache = new Map(); + } + /** + * @template { string } S + * @param { ...S } maps + * @returns { S | S[] } + */ + create(...maps) { + const resp = []; + for (const key of maps) { + if (this.cache.has(key)) { + return this.cache.get(key); + } + this.cache.set(key, new Map()); + resp.push(this.cache.get(key)); + } + return resp.length >= 2 ? resp : resp[0]; + } +} const alang = []; const defcfg = { cache: true, @@ -642,51 +667,96 @@ const make = (tagName, cname, attrs = {}) => { } return el; }; -/** - * Inject CSS (Cascading Style Sheet Document) into `document.head` - * @param { string } css - CSS to inject - * @param { string } name - (optional) Name of stylesheet `mph-` - * @param { * } root - (optional) Custom `document.head` path - * @return { HTMLStyleElement | null } Style element - */ -const loadCSS = (css, name = 'CSS', root = document) => { - /** @type {Element} */ - let el; - try { - if (typeof css !== 'string') { - throw new Error('[loadCSS] "css" must be a typeof "String"'); - } - if (typeof name !== 'string') { - throw new Error('[loadCSS] "name" must be a typeof "String"'); +const Container = class { + constructor() { + this.remove = this.remove.bind(this); + this.onFrameLoad = this.onFrameLoad.bind(this); + this.ready = false; + this.supported = isFN(document.createElement('main-userjs').attachShadow); + if (this.supported) { + this.frame = make('main-userjs', '', { + dataset: { + insertedBy: 'userscript-plus', + role: 'primary-container' + } + }); + /** + * @type { ShadowRoot } + */ + this.root = this.frame.attachShadow({ mode: 'open', clonable: false, delegatesfocus: false }); + this.ready = true; + } else { + this.frame = make('iframe', 'mujs-iframe', { + dataset: { + insertedBy: 'userscript-plus', + role: 'primary-iframe' + }, + loading: 'lazy', + src: 'about:blank', + style: + 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', + onload: this.onFrameLoad + }); } - el = root || document.head; - if (isBlank(css)) { - throw new Error(`[loadCSS] "${name}" contains empty CSS string`); + ael(window.self ?? window, 'beforeunload', this.remove); + } + /** + * @param { Function } callback + * @param { document } doc + */ + async inject(callback, doc) { + if (!doc) { + return; } - for (const s of normalizeTarget(el.querySelectorAll('style[data-role]'))) { - if (Object.is(s.dataset.role, name)) { - return s; - } + while (this.ready === false) { + await Task.requestAFrame(); } - const sty = make('style', `mujs-${name}`, { - textContent: css, - dataset: { - insertedBy: 'userscript-plus', - role: name - } - }); - // sty.appendChild(document.createTextNode(css)); - if (!isEmpty(el.shadowRoot)) { - el.shadowRoot.appendChild(sty); - } else { - el.appendChild(sty); + + doc.documentElement.appendChild(this.frame); + + if (isFN(callback)) { + callback.call({}, this.root, doc); } - return sty; - } catch (ex) { - err(ex); } - return null; + + remove() { + this.frame.remove(); + } + + onFrameLoad(iFrame) { + /** + * @type { HTMLIFrameElement } + */ + const target = iFrame.target; + this.root = target.contentDocument.documentElement; + this.ready = true; + + dom.cl.add([this.root, target.contentDocument.body], 'mujs-iframe'); + } }; +const container = new Container(); +const listener = (evt) => { + if (evt.isTrusted !== true) { + return; + } + if (evt.disposition !== 'enforce') { + return; + } + if (evt.target.tagName !== 'MAIN-USERJS') { + return; + } + container.remove(); + err('Failed to inject due to CSP violation', { + url: evt.blockedURL || evt.blockedURI, + policy: evt.originalPolicy, + directive: evt.effectiveDirective || evt.violatedDirective + }); + document.removeEventListener('securitypolicyviolation', listener); +}; +if (document !== null) { + document.addEventListener('securitypolicyviolation', listener); +} + const iconSVG = { cfg: { viewBox: '0 0 24 24', @@ -977,7 +1047,6 @@ MU.info = Supports.gm version: 'Bookmarklet' } }; - MU.tab = { /** * Open a new window @@ -1063,77 +1132,7 @@ MU.storage = { } } }; -const getHost = (str = '') => { - return str.split('.').splice(-2).join('.'); -}; -const Container = class { - constructor() { - this.remove = this.remove.bind(this); - this.onFrameLoad = this.onFrameLoad.bind(this); - this.ready = false; - this.supported = isFN(document.createElement('main-userjs').attachShadow); - if (this.supported) { - this.frame = make('main-userjs', '', { - dataset: { - insertedBy: 'userscript-plus', - role: 'primary-container' - } - }); - /** - * @type { ShadowRoot } - */ - this.root = this.frame.attachShadow({ mode: 'open', clonable: false, delegatesfocus: false }); - this.ready = true; - } else { - this.frame = make('iframe', 'mujs-iframe', { - dataset: { - insertedBy: 'userscript-plus', - role: 'primary-iframe' - }, - loading: 'lazy', - src: 'about:blank', - style: - 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', - onload: this.onFrameLoad - }); - } - ael(window.self ?? window, 'beforeunload', this.remove); - } - /** - * @param { Function } callback - * @param { document } doc - */ - async inject(callback, doc) { - if (!doc) { - return; - } - while (this.ready === false) { - await Task.requestAFrame(); - } - - doc.documentElement.appendChild(this.frame); - - if (isFN(callback)) { - callback.call({}, this.root); - } - } - remove() { - this.frame.remove(); - } - - onFrameLoad(iFrame) { - /** - * @type { HTMLIFrameElement } - */ - const target = iFrame.target; - this.root = target.contentDocument.documentElement; - this.ready = true; - - dom.cl.add([this.root, target.contentDocument.body], 'mujs-iframe'); - } -}; -const container = new Container(); const sleazyRedirect = () => { if (!/greasyfork\.org/.test(window.location.hostname) && cfg.sleazyredirect) { return; @@ -1153,20 +1152,60 @@ const sleazyRedirect = () => { : false; }; // #region Primary Function -const primaryFN = (injCon) => { +/** + * @param { ShadowRoot | HTMLIFrameElement } injCon + * @param { Document } doc + */ +const primaryFN = (injCon, doc) => { try { const mujsRoot = make('mujs-root'); + /** + * Inject CSS (Cascading Style Sheet Document) into `document.head` + * @param { string } css - CSS to inject + * @param { string } name - (optional) Name of stylesheet `mph-` + * @param { * } root - (optional) Custom `document.head` path + * @return { HTMLStyleElement | null } Style element + */ + const loadCSS = (css, name = 'CSS', root = document) => { + /** @type {Element} */ + let el; + try { + if (typeof css !== 'string') { + throw new Error('[loadCSS] "css" must be a typeof "String"'); + } + if (typeof name !== 'string') { + throw new Error('[loadCSS] "name" must be a typeof "String"'); + } + el = root || doc; + if (isBlank(css)) { + throw new Error(`[loadCSS] "${name}" contains empty CSS string`); + } + if (el.querySelector(`style[data-role="${name}"]`)) { + return el.querySelector(`style[data-role="${name}"]`); + } + const sty = make('style', `mujs-${name}`, { + textContent: css, + dataset: { + insertedBy: 'userscript-plus', + role: name + } + }); + if (!isEmpty(el.shadowRoot)) { + el.shadowRoot.appendChild(sty); + } else { + el.appendChild(sty); + } + return sty; + } catch (ex) { + err(ex); + } + return null; + }; const injectedCore = loadCSS(main_css, 'primary-stylesheet', mujsRoot); if (!injectedCore) { throw new Error('Failed to initialize script!', { cause: 'loadCSS' }); } - - if (qs('mujs-root', injCon)) { - return; - } - injCon.append(mujsRoot); - if (navigator.languages.length > 0) { for (const nlang of navigator.languages) { const lg = nlang.split('-')[0]; @@ -1179,10 +1218,14 @@ const primaryFN = (injCon) => { alang.push(Language.navLang); } - const sh = (elem) => injCon.querySelector(elem); - const shA = (elem) => injCon.querySelectorAll(elem); + const memory = new Memorize(); + const memorized = memory.cache; + memory.create('cfg', 'container', 'userjs'); - const cfgMap = new Map(); + const getHost = (str = '') => { + return str.split('.').splice(-2).join('.'); + }; + const cfgMap = memorized.get('cfg'); const rebuildCfg = () => { for (const i of cfg.engines) { if (cfgMap.has(i.name)) { @@ -1239,7 +1282,7 @@ const primaryFN = (injCon) => { if (sibling.dataset.command !== 'new-tab') { activeTab(sibling); } - }; + } tab.remove(); }; const newTab = (host = undefined) => { @@ -1307,7 +1350,8 @@ const primaryFN = (injCon) => { await mouseTimeout.set(2500); target.style.opacity = '0.15'; }, - onclick(evt) { + // #region Event handler + async onclick(evt) { try { /** @type { Element } */ const target = evt.target.closest('[data-command]'); @@ -1364,9 +1408,7 @@ const primaryFN = (injCon) => { dom.cl.remove(mainframe, 'hidden'); timeoutFrame(); } else if (cmd === 'save') { - if (sh('.saveerror')) { - sh('.saveerror').remove(); - } + // MUJS.refresh(); if (!isNull(legacyMsg)) { legacyMsg = null; MUJS.rebuild = true; @@ -1388,11 +1430,8 @@ const primaryFN = (injCon) => { MUJS.rebuild = true; rebuildCfg(); } else if (cmd === 'settings') { - if (MUJS.unsaved && !sh('.saveerror')) { - const txt = make('mujs-row', 'saveerror', { - innerHTML: 'Unsaved changes' - }); - countframe.insertAdjacentHTML('afterend', txt.outerHTML.toString()); + if (MUJS.unsaved) { + MUJS.showError('Unsaved changes'); } if (dom.cl.has(cfgpage, 'hidden')) { dom.cl.remove(cfgpage, 'hidden'); @@ -1414,13 +1453,101 @@ const primaryFN = (injCon) => { dom.cl.add(cfgpage, 'hidden'); dom.cl.remove(table, 'hidden'); activeTab(target); - } else if (cmd === 'close-tab') { + } else if (cmd === 'close-tab' && target.parentElement) { closeTab(target.parentElement); + } else if (cmd === 'download-userjs') { + if (!MUJS.userjsCache.has(+dataset.userjs)) { + return; + } + const txt = await reqCode(MUJS.userjsCache.get(+dataset.userjs)); + if (typeof txt !== 'string') { + return; + } + const makeUserJS = new Blob([txt], { type: 'text/plain' }); + const dlBtn = make('a', 'mujs_Downloader'); + dlBtn.href = URL.createObjectURL(makeUserJS); + dlBtn.download = 'test.user.js'; + dlBtn.click(); + URL.revokeObjectURL(dlBtn.href); + dlBtn.remove(); + } else if (cmd === 'load-userjs') { + if (!MUJS.userjsCache.has(+dataset.userjs)) { + return; + } + const codeArea = qs('textarea', target.parentElement.parentElement); + if (!isEmpty(codeArea.value)) { + dom.cl.toggle(codeArea, 'hidden'); + return; + } + const txt = await reqCode(MUJS.userjsCache.get(+dataset.userjs)); + if (typeof txt !== 'string') { + return; + } + codeArea.value = txt; + dom.cl.remove(codeArea, 'hidden'); + } else if (/export-/.test(cmd)) { + const str = JSON.stringify(cmd === 'export-cfg' ? cfg : cfg.theme, null, ' '); + const bytes = new TextEncoder().encode(str); + const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); + const dlBtn = make('a', 'mujs-exporter', { + href: URL.createObjectURL(blob), + download: `Magic_Userscript_${cmd === 'export-cfg' ? 'config' : 'theme'}.json` + }); + dlBtn.click(); + URL.revokeObjectURL(dlBtn.href); + } else if (/import-/.test(cmd)) { + if (qs('input', target.parentElement)) { + qs('input', target.parentElement).click(); + return; + } + const inpJSON = make('input', 'hidden', { + type: 'file', + accept: '.json', + onchange: (evt) => { + try { + [...evt.target.files].forEach((file) => { + const reader = new FileReader(); + reader.readAsText(file); + reader.onload = () => { + const result = JSON.parse(reader.result); + if (result.blacklist) { + log(`Imported config: { ${file.name} }`, result); + cfg = result; + MUJS.unsaved = true; + MUJS.rebuild = true; + rebuildCfg(); + MUJS.save(); + sleazyRedirect(); + MUJS.cache.clear(); + buildlist(); + MUJS.unsaved = false; + MUJS.rebuild = false; + } else { + log(`Imported theme: { ${file.name} }`, result); + cfg.theme = result; + renderTheme(cfg.theme); + } + inpJSON.remove(); + }; + reader.onerror = () => { + MUJS.showError(reader.error); + inpJSON.remove(); + }; + }); + } catch (ex) { + MUJS.showError(ex); + inpJSON.remove(); + } + } + }); + target.parentElement.append(inpJSON); + inpJSON.click(); } } catch (ex) { err(ex); } } + // #endregion }); const tbody = make('mu-js', 'mujs-body'); const header = make('mu-js', 'mujs-header-prim'); @@ -1458,9 +1585,8 @@ const primaryFN = (injCon) => { this.webpage = window.location; } this.host = getHost(this.webpage.hostname); - // this.host = location.hostname.split('.').splice(-2).join('.'); - this.cache = new Map(); - this.userjsCache = new Map(); + this.cache = memorized.get('container'); + this.userjsCache = memorized.get('userjs'); this.unsaved = false; this.isBlacklisted = false; this.rebuild = false; @@ -1697,23 +1823,9 @@ const primaryFN = (injCon) => { const fBtns = make('mujs-column', 'mujs-list hidden'); const dwnCode = make('mu-jsbtn', '', { innerHTML: `${iconSVG.load('install')} ${lang.saveFile}`, - async onclick(evt) { - evt.preventDefault(); - try { - const txt = await reqCode(ujs); - if (typeof txt !== 'string') { - return; - } - const makeUserJS = new Blob([txt], { type: 'text/plain' }); - const dlBtn = make('a', 'mujs_Downloader'); - dlBtn.href = URL.createObjectURL(makeUserJS); - dlBtn.download = 'test.user.js'; - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - dlBtn.remove(); - } catch (ex) { - err(ex); - } + dataset: { + command: 'download-userjs', + userjs: ujs.id } }); const tr = make('tr', 'frame'); @@ -1728,22 +1840,9 @@ const primaryFN = (injCon) => { }); const loadCode = make('mu-jsbtn', '', { innerHTML: `${iconSVG.load('search')} ${lang.codePreview}`, - async onclick(evt) { - evt.preventDefault(); - try { - if (!isEmpty(codeArea.value)) { - dom.cl.toggle(codeArea, 'hidden'); - return; - } - const txt = await reqCode(ujs); - if (typeof txt !== 'string') { - return; - } - codeArea.value = txt; - dom.cl.remove(codeArea, 'hidden'); - } catch (ex) { - err(ex); - } + dataset: { + command: 'load-userjs', + userjs: ujs.id } }); if (engine) { @@ -1807,18 +1906,6 @@ const primaryFN = (injCon) => { const engines = cfg.engines.filter((e) => e.enabled); const cache = MUJS.cache.get(host); const customRecords = []; - const rateFN = (data) => { - try { - for (const [key, value] of Object.entries(data.resources.code_search)) { - const txt = make('mujs-row', 'rate-info', { - innerHTML: `${key.toUpperCase()}: ${value}` - }); - rateContainer.append(txt); - } - } catch (ex) { - MUJS.showError(ex); - } - }; const isSupported = (name) => { for (const [k, v] of Object.entries(unsupported)) { if (k !== name) { @@ -1870,7 +1957,7 @@ const primaryFN = (injCon) => { if (typeof txt !== 'string') { continue; } - const headers = txt.match(/\/\/\s@[\w][\s\S]+/g); // txt.match(/\/\/\s?==UserScript==([\s\S]*?)\/\/\s?==\/UserScript==/gm); + const headers = txt.match(/\/\/\s@[\w][\s\S]+/g); if (isNull(headers)) { continue; } @@ -2062,14 +2149,23 @@ const primaryFN = (injCon) => { 'X-GitHub-Api-Version': '2022-11-28' } }) - .then(rateFN) + .then((data) => { + for (const [key, value] of Object.entries(data.resources.code_search)) { + const txt = make('mujs-row', 'rate-info', { + innerHTML: `${key.toUpperCase()}: ${value}` + }); + rateContainer.append(txt); + } + }) .catch(MUJS.showError); }) .catch(MUJS.showError); } else { - Network.req(`${eURL}${host}`, 'GET', 'document').then(customFN).catch((error) => { - MUJS.showError(`Engine: "${engine.name}"`, error); - }); + Network.req(`${eURL}${host}`, 'GET', 'document') + .then(customFN) + .catch((error) => { + MUJS.showError(`Engine: "${engine.name}"`, error); + }); } } } @@ -2083,107 +2179,29 @@ const primaryFN = (injCon) => { const exBtn = make('mu-js', 'mujs-sty-flex'); const exportCFG = make('mujs-btn', 'mujs-export', { innerHTML: 'Export Config', - onclick() { - try { - const str = JSON.stringify(cfg, null, ' '); - const bytes = new TextEncoder().encode(str); - const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); - const dlBtn = make('a', 'mujs-exporter', { - href: URL.createObjectURL(blob), - download: 'Magic_Userscript_config.json' - }); - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - } catch (ex) { - MUJS.showError(ex); - } - } - }); - const cfgJSON = make('input', 'hidden', { - type: 'file', - accept: '.json', - onchange: (evt) => { - try { - [...evt.target.files].forEach((file) => { - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = () => { - const result = JSON.parse(reader.result); - log(`Imported config: { ${file.name} }`, result); - cfg = result; - MUJS.unsaved = true; - MUJS.rebuild = true; - rebuildCfg(); - MUJS.save(); - sleazyRedirect(); - MUJS.cache.clear(); - buildlist(); - MUJS.unsaved = false; - MUJS.rebuild = false; - }; - reader.onerror = () => { - MUJS.showError(reader.error); - }; - }); - } catch (ex) { - MUJS.showError(ex); - } + dataset: { + command: 'export-cfg' } }); const importCFG = make('mujs-btn', 'mujs-import', { innerHTML: 'Import Config', - onclick() { - cfgJSON.click(); + dataset: { + command: 'import-cfg' } }); const exportTheme = make('mujs-btn', 'mujs-export', { innerHTML: 'Export Theme', - onclick() { - try { - const str = JSON.stringify(cfg.theme, null, ' '); - const bytes = new TextEncoder().encode(str); - const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); - const dlBtn = make('a', 'mujs-exporter', { - href: URL.createObjectURL(blob), - download: 'Magic_Userscript_theme.json' - }); - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - } catch (ex) { - MUJS.showError(ex); - } - } - }); - const themeJSON = make('input', 'hidden', { - type: 'file', - accept: '.json', - onchange: (evt) => { - try { - [...evt.target.files].forEach((file) => { - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = () => { - const result = JSON.parse(reader.result); - log(`Imported theme: { ${file.name} }`, result); - cfg.theme = result; - renderTheme(cfg.theme); - }; - reader.onerror = () => { - MUJS.showError(reader.error); - }; - }); - } catch (ex) { - MUJS.showError(ex); - } + dataset: { + command: 'export-theme' } }); const importTheme = make('mujs-btn', 'mujs-import', { innerHTML: 'Import Theme', - onclick() { - themeJSON.click(); + dataset: { + command: 'import-theme' } }); - exBtn.append(importCFG, exportCFG, cfgJSON, exportTheme, themeJSON, importTheme); + exBtn.append(importCFG, importTheme, exportCFG, exportTheme); cfgpage.append(exBtn); const makerow = (desc = 'Placeholder', type = null, nm = 'Placeholder', attrs = {}) => { @@ -2454,11 +2472,11 @@ const primaryFN = (injCon) => { oninput(evt) { evt.preventDefault(); if (isEmpty(evt.target.value)) { - dom.cl.remove(shA('tr.frame'), 'hidden'); + dom.cl.remove(qsA('tr', tabbody), 'hidden'); return; } const reg = new RegExp(evt.target.value, 'gi'); - for (const ujs of shA('tr.frame')) { + for (const ujs of qsA('tr', tabbody)) { const m = ujs.children[0]; const n = ujs.children[1]; const final = m.textContent.match(reg) || n.textContent.match(reg) || []; @@ -2527,16 +2545,7 @@ const primaryFN = (injCon) => { countframe.append(gfcounter, sfcounter); fsearch.append(filterList); btnHandles.append(btnHide, btnfullscreen, closebtn); - btnframe.append( - fsearch, - filterBtn, - btncfg, - btnissue, - btnhome, - btngreasy, - btnnav, - btnHandles - ); + btnframe.append(fsearch, filterBtn, btncfg, btnissue, btnhome, btngreasy, btnnav, btnHandles); header.append(countframe, rateContainer, btnframe); ntHead.append(ntAdd); tbody.append(table, cfgpage); @@ -2561,7 +2570,6 @@ const primaryFN = (injCon) => { ]); const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; - const comparer = (idx, asc) => (a, b) => ((v1, v2) => v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) @@ -2589,7 +2597,8 @@ const primaryFN = (injCon) => { mujsRoot.append(mainframe, main); makecfg(); - buildlist().then(timeoutFrame); + buildlist(); + // buildlist().then(timeoutFrame); if (cfg.injection) { info('Migrating old config...'); @@ -2604,11 +2613,10 @@ const primaryFN = (injCon) => { // #endregion /** * @param { Function } callback - * @returns { null | true } */ const loadDOM = (callback) => { if (!isFN(callback)) { - return null; + return; } if (document.readyState === 'interactive' || document.readyState === 'complete') { callback.call({}, document); @@ -2616,7 +2624,6 @@ const loadDOM = (callback) => { document.addEventListener('DOMContentLoaded', (evt) => callback.call({}, evt.target), { once: true }); - return true; }; const Setup = async () => { try { @@ -2624,27 +2631,21 @@ const Setup = async () => { lang = Language.cache; info('Config:', cfg); loadDOM((doc) => { - try { - if (window.location === null) { - err('"window.location" is null, reload the webpage or use a different one'); - return; - } - if (doc === null) { - err('"doc" is null, reload the webpage or use a different one'); - return; - } - sleazyRedirect(); - container.inject(primaryFN, doc); - } catch (ex) { - err(ex); + if (window.location === null) { + err('"window.location" is null, reload the webpage or use a different one'); + return; } + if (doc === null) { + err('"doc" is null, reload the webpage or use a different one'); + return; + } + sleazyRedirect(); + container.inject(primaryFN, doc); }); } catch (ex) { err(ex); } }; -if (typeof userjs === 'object' && userjs.UserJS && window && window.self === window.top) { - Setup(); -} +Setup(); })(); \ No newline at end of file diff --git a/package.json b/package.json index 4766ffa..b6c7177 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "url": "https://github.com/magicoflolis/Userscript-Plus/issues/new" }, "userJS": { - "version": "6.5.0", + "version": "6.5.1", "name": "Magic Userscript+ : Show Site All UserJS", "bugs": "https://github.com/magicoflolis/Userscript-Plus/issues/new", "homepage": "https://github.com/magicoflolis/Userscript-Plus", @@ -69,7 +69,7 @@ }, "type": "module", "scripts": { - "dev:UserJS": "concurrently \"pnpm run dev:Webserver\" \"pnpm run dev:Sass\" \"node ./tools/userscript.js\"", + "dev:UserJS": "concurrently \"pnpm run dev:Webserver\" \"sass --no-source-map -s compressed -w ./src/sass:./build/css\" \"node ./tools/userscript.js\"", "dev:FF": "concurrently \"pnpm run dev:Sass\" \"pnpm run webpack:FF\"", "dev:Cr": "concurrently \"pnpm run dev:Sass\" \"pnpm run webpack:Cr\"", "dev:Sass": "sass --embed-sources -s compressed -w ./src/sass:./build/css", diff --git a/src/UserJS/header.js b/src/UserJS/header.js index 1d2afe2..5978a76 100644 --- a/src/UserJS/header.js +++ b/src/UserJS/header.js @@ -1,6 +1,19 @@ {{metadata}} 'use strict'; (() => { +let userjs = (self.userjs = {}); +/** Skip text/plain documents */ +if ( + (document instanceof Document || + (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && + /^image\/|^text\/plain/.test(document.contentType || '') === false && + (self.userjs instanceof Object === false || userjs.UserJS !== true) +) { + userjs = self.userjs = { UserJS: true }; +} +if (!(typeof userjs === 'object' && userjs.UserJS && window && window.self === window.top)) { + return; +} /** * To compile this CSS `pnpm run build:Sass` * @desc Link to uncompiled Cascading Style Sheet diff --git a/src/UserJS/main.js b/src/UserJS/main.js index ac4297b..293f1cc 100644 --- a/src/UserJS/main.js +++ b/src/UserJS/main.js @@ -1,13 +1,3 @@ -let userjs = (self.userjs = {}); -/** Skip text/plain documents */ -if ( - (document instanceof Document || - (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && - /^image\/|^text\/plain/.test(document.contentType || '') === false && - (self.userjs instanceof Object === false || userjs.UserJS !== true) -) { - userjs = self.userjs = { UserJS: true }; -} let cfg = {}; let lang = {}; let legacyMsg = null; @@ -37,18 +27,19 @@ const isMobile = /Mobile|Tablet/.test(navigator.userAgent); const Supports = { gm: typeof GM !== 'undefined' }; -//#region Console -// const dbg = (...msg) => { -// const dt = new Date(); -// console.debug( -// '[%cUserJS%c] %cDBG', -// 'color: rgb(29, 155, 240);', -// '', -// 'color: rgb(255, 212, 0);', -// `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, -// ...msg -// ); -// }; +// #region Console +// eslint-disable-next-line no-unused-vars +const dbg = (...msg) => { + const dt = new Date(); + console.debug( + '[%cUserJS%c] %cDBG', + 'color: rgb(29, 155, 240);', + '', + 'color: rgb(255, 212, 0);', + `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, + ...msg + ); +}; const err = (...msg) => { console.error( '[%cUserJS%c] %cERROR', @@ -86,7 +77,7 @@ const log = (...msg) => { ...msg ); }; -//#endregion +// #endregion const MU = {}; const hasOwn = Object.hasOwn || Object.prototype.hasOwnProperty.call; /** @@ -360,6 +351,27 @@ class Timeout { }); } } +class Memorize { + constructor() { + this.cache = new Map(); + } + /** + * @template { string } S + * @param { ...S } maps + * @returns { S | S[] } + */ + create(...maps) { + const resp = []; + for (const key of maps) { + if (this.cache.has(key)) { + return this.cache.get(key); + } + this.cache.set(key, new Map()); + resp.push(this.cache.get(key)); + } + return resp.length >= 2 ? resp : resp[0]; + } +} const alang = []; const defcfg = { cache: true, @@ -576,51 +588,96 @@ const make = (tagName, cname, attrs = {}) => { } return el; }; -/** - * Inject CSS (Cascading Style Sheet Document) into `document.head` - * @param { string } css - CSS to inject - * @param { string } name - (optional) Name of stylesheet `mph-` - * @param { * } root - (optional) Custom `document.head` path - * @return { HTMLStyleElement | null } Style element - */ -const loadCSS = (css, name = 'CSS', root = document) => { - /** @type {Element} */ - let el; - try { - if (typeof css !== 'string') { - throw new Error('[loadCSS] "css" must be a typeof "String"'); - } - if (typeof name !== 'string') { - throw new Error('[loadCSS] "name" must be a typeof "String"'); +const Container = class { + constructor() { + this.remove = this.remove.bind(this); + this.onFrameLoad = this.onFrameLoad.bind(this); + this.ready = false; + this.supported = isFN(document.createElement('main-userjs').attachShadow); + if (this.supported) { + this.frame = make('main-userjs', '', { + dataset: { + insertedBy: 'userscript-plus', + role: 'primary-container' + } + }); + /** + * @type { ShadowRoot } + */ + this.root = this.frame.attachShadow({ mode: 'open', clonable: false, delegatesfocus: false }); + this.ready = true; + } else { + this.frame = make('iframe', 'mujs-iframe', { + dataset: { + insertedBy: 'userscript-plus', + role: 'primary-iframe' + }, + loading: 'lazy', + src: 'about:blank', + style: + 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', + onload: this.onFrameLoad + }); } - el = root || document.head; - if (isBlank(css)) { - throw new Error(`[loadCSS] "${name}" contains empty CSS string`); + ael(window.self ?? window, 'beforeunload', this.remove); + } + /** + * @param { Function } callback + * @param { document } doc + */ + async inject(callback, doc) { + if (!doc) { + return; } - for (const s of normalizeTarget(el.querySelectorAll('style[data-role]'))) { - if (Object.is(s.dataset.role, name)) { - return s; - } + while (this.ready === false) { + await Task.requestAFrame(); } - const sty = make('style', `mujs-${name}`, { - textContent: css, - dataset: { - insertedBy: 'userscript-plus', - role: name - } - }); - // sty.appendChild(document.createTextNode(css)); - if (!isEmpty(el.shadowRoot)) { - el.shadowRoot.appendChild(sty); - } else { - el.appendChild(sty); + + doc.documentElement.appendChild(this.frame); + + if (isFN(callback)) { + callback.call({}, this.root, doc); } - return sty; - } catch (ex) { - err(ex); } - return null; + + remove() { + this.frame.remove(); + } + + onFrameLoad(iFrame) { + /** + * @type { HTMLIFrameElement } + */ + const target = iFrame.target; + this.root = target.contentDocument.documentElement; + this.ready = true; + + dom.cl.add([this.root, target.contentDocument.body], 'mujs-iframe'); + } +}; +const container = new Container(); +const listener = (evt) => { + if (evt.isTrusted !== true) { + return; + } + if (evt.disposition !== 'enforce') { + return; + } + if (evt.target.tagName !== 'MAIN-USERJS') { + return; + } + container.remove(); + err('Failed to inject due to CSP violation', { + url: evt.blockedURL || evt.blockedURI, + policy: evt.originalPolicy, + directive: evt.effectiveDirective || evt.violatedDirective + }); + document.removeEventListener('securitypolicyviolation', listener); }; +if (document !== null) { + document.addEventListener('securitypolicyviolation', listener); +} + const iconSVG = { cfg: { viewBox: '0 0 24 24', @@ -911,7 +968,6 @@ MU.info = Supports.gm version: 'Bookmarklet' } }; - MU.tab = { /** * Open a new window @@ -997,77 +1053,7 @@ MU.storage = { } } }; -const getHost = (str = '') => { - return str.split('.').splice(-2).join('.'); -}; -const Container = class { - constructor() { - this.remove = this.remove.bind(this); - this.onFrameLoad = this.onFrameLoad.bind(this); - this.ready = false; - this.supported = isFN(document.createElement('main-userjs').attachShadow); - if (this.supported) { - this.frame = make('main-userjs', '', { - dataset: { - insertedBy: 'userscript-plus', - role: 'primary-container' - } - }); - /** - * @type { ShadowRoot } - */ - this.root = this.frame.attachShadow({ mode: 'open', clonable: false, delegatesfocus: false }); - this.ready = true; - } else { - this.frame = make('iframe', 'mujs-iframe', { - dataset: { - insertedBy: 'userscript-plus', - role: 'primary-iframe' - }, - loading: 'lazy', - src: 'about:blank', - style: - 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', - onload: this.onFrameLoad - }); - } - ael(window.self ?? window, 'beforeunload', this.remove); - } - /** - * @param { Function } callback - * @param { document } doc - */ - async inject(callback, doc) { - if (!doc) { - return; - } - while (this.ready === false) { - await Task.requestAFrame(); - } - - doc.documentElement.appendChild(this.frame); - - if (isFN(callback)) { - callback.call({}, this.root); - } - } - - remove() { - this.frame.remove(); - } - - onFrameLoad(iFrame) { - /** - * @type { HTMLIFrameElement } - */ - const target = iFrame.target; - this.root = target.contentDocument.documentElement; - this.ready = true; - dom.cl.add([this.root, target.contentDocument.body], 'mujs-iframe'); - } -}; -const container = new Container(); const sleazyRedirect = () => { if (!/greasyfork\.org/.test(window.location.hostname) && cfg.sleazyredirect) { return; @@ -1087,20 +1073,60 @@ const sleazyRedirect = () => { : false; }; // #region Primary Function -const primaryFN = (injCon) => { +/** + * @param { ShadowRoot | HTMLIFrameElement } injCon + * @param { Document } doc + */ +const primaryFN = (injCon, doc) => { try { const mujsRoot = make('mujs-root'); + /** + * Inject CSS (Cascading Style Sheet Document) into `document.head` + * @param { string } css - CSS to inject + * @param { string } name - (optional) Name of stylesheet `mph-` + * @param { * } root - (optional) Custom `document.head` path + * @return { HTMLStyleElement | null } Style element + */ + const loadCSS = (css, name = 'CSS', root = document) => { + /** @type {Element} */ + let el; + try { + if (typeof css !== 'string') { + throw new Error('[loadCSS] "css" must be a typeof "String"'); + } + if (typeof name !== 'string') { + throw new Error('[loadCSS] "name" must be a typeof "String"'); + } + el = root || doc; + if (isBlank(css)) { + throw new Error(`[loadCSS] "${name}" contains empty CSS string`); + } + if (el.querySelector(`style[data-role="${name}"]`)) { + return el.querySelector(`style[data-role="${name}"]`); + } + const sty = make('style', `mujs-${name}`, { + textContent: css, + dataset: { + insertedBy: 'userscript-plus', + role: name + } + }); + if (!isEmpty(el.shadowRoot)) { + el.shadowRoot.appendChild(sty); + } else { + el.appendChild(sty); + } + return sty; + } catch (ex) { + err(ex); + } + return null; + }; const injectedCore = loadCSS(main_css, 'primary-stylesheet', mujsRoot); if (!injectedCore) { throw new Error('Failed to initialize script!', { cause: 'loadCSS' }); } - - if (qs('mujs-root', injCon)) { - return; - } - injCon.append(mujsRoot); - if (navigator.languages.length > 0) { for (const nlang of navigator.languages) { const lg = nlang.split('-')[0]; @@ -1113,10 +1139,14 @@ const primaryFN = (injCon) => { alang.push(Language.navLang); } - const sh = (elem) => injCon.querySelector(elem); - const shA = (elem) => injCon.querySelectorAll(elem); + const memory = new Memorize(); + const memorized = memory.cache; + memory.create('cfg', 'container', 'userjs'); - const cfgMap = new Map(); + const getHost = (str = '') => { + return str.split('.').splice(-2).join('.'); + }; + const cfgMap = memorized.get('cfg'); const rebuildCfg = () => { for (const i of cfg.engines) { if (cfgMap.has(i.name)) { @@ -1173,7 +1203,7 @@ const primaryFN = (injCon) => { if (sibling.dataset.command !== 'new-tab') { activeTab(sibling); } - }; + } tab.remove(); }; const newTab = (host = undefined) => { @@ -1241,7 +1271,8 @@ const primaryFN = (injCon) => { await mouseTimeout.set(2500); target.style.opacity = '0.15'; }, - onclick(evt) { + // #region Event handler + async onclick(evt) { try { /** @type { Element } */ const target = evt.target.closest('[data-command]'); @@ -1298,9 +1329,7 @@ const primaryFN = (injCon) => { dom.cl.remove(mainframe, 'hidden'); timeoutFrame(); } else if (cmd === 'save') { - if (sh('.saveerror')) { - sh('.saveerror').remove(); - } + // MUJS.refresh(); if (!isNull(legacyMsg)) { legacyMsg = null; MUJS.rebuild = true; @@ -1322,11 +1351,8 @@ const primaryFN = (injCon) => { MUJS.rebuild = true; rebuildCfg(); } else if (cmd === 'settings') { - if (MUJS.unsaved && !sh('.saveerror')) { - const txt = make('mujs-row', 'saveerror', { - innerHTML: 'Unsaved changes' - }); - countframe.insertAdjacentHTML('afterend', txt.outerHTML.toString()); + if (MUJS.unsaved) { + MUJS.showError('Unsaved changes'); } if (dom.cl.has(cfgpage, 'hidden')) { dom.cl.remove(cfgpage, 'hidden'); @@ -1348,13 +1374,101 @@ const primaryFN = (injCon) => { dom.cl.add(cfgpage, 'hidden'); dom.cl.remove(table, 'hidden'); activeTab(target); - } else if (cmd === 'close-tab') { + } else if (cmd === 'close-tab' && target.parentElement) { closeTab(target.parentElement); + } else if (cmd === 'download-userjs') { + if (!MUJS.userjsCache.has(+dataset.userjs)) { + return; + } + const txt = await reqCode(MUJS.userjsCache.get(+dataset.userjs)); + if (typeof txt !== 'string') { + return; + } + const makeUserJS = new Blob([txt], { type: 'text/plain' }); + const dlBtn = make('a', 'mujs_Downloader'); + dlBtn.href = URL.createObjectURL(makeUserJS); + dlBtn.download = 'test.user.js'; + dlBtn.click(); + URL.revokeObjectURL(dlBtn.href); + dlBtn.remove(); + } else if (cmd === 'load-userjs') { + if (!MUJS.userjsCache.has(+dataset.userjs)) { + return; + } + const codeArea = qs('textarea', target.parentElement.parentElement); + if (!isEmpty(codeArea.value)) { + dom.cl.toggle(codeArea, 'hidden'); + return; + } + const txt = await reqCode(MUJS.userjsCache.get(+dataset.userjs)); + if (typeof txt !== 'string') { + return; + } + codeArea.value = txt; + dom.cl.remove(codeArea, 'hidden'); + } else if (/export-/.test(cmd)) { + const str = JSON.stringify(cmd === 'export-cfg' ? cfg : cfg.theme, null, ' '); + const bytes = new TextEncoder().encode(str); + const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); + const dlBtn = make('a', 'mujs-exporter', { + href: URL.createObjectURL(blob), + download: `Magic_Userscript_${cmd === 'export-cfg' ? 'config' : 'theme'}.json` + }); + dlBtn.click(); + URL.revokeObjectURL(dlBtn.href); + } else if (/import-/.test(cmd)) { + if (qs('input', target.parentElement)) { + qs('input', target.parentElement).click(); + return; + } + const inpJSON = make('input', 'hidden', { + type: 'file', + accept: '.json', + onchange: (evt) => { + try { + [...evt.target.files].forEach((file) => { + const reader = new FileReader(); + reader.readAsText(file); + reader.onload = () => { + const result = JSON.parse(reader.result); + if (result.blacklist) { + log(`Imported config: { ${file.name} }`, result); + cfg = result; + MUJS.unsaved = true; + MUJS.rebuild = true; + rebuildCfg(); + MUJS.save(); + sleazyRedirect(); + MUJS.cache.clear(); + buildlist(); + MUJS.unsaved = false; + MUJS.rebuild = false; + } else { + log(`Imported theme: { ${file.name} }`, result); + cfg.theme = result; + renderTheme(cfg.theme); + } + inpJSON.remove(); + }; + reader.onerror = () => { + MUJS.showError(reader.error); + inpJSON.remove(); + }; + }); + } catch (ex) { + MUJS.showError(ex); + inpJSON.remove(); + } + } + }); + target.parentElement.append(inpJSON); + inpJSON.click(); } } catch (ex) { err(ex); } } + // #endregion }); const tbody = make('mu-js', 'mujs-body'); const header = make('mu-js', 'mujs-header-prim'); @@ -1392,9 +1506,8 @@ const primaryFN = (injCon) => { this.webpage = window.location; } this.host = getHost(this.webpage.hostname); - // this.host = location.hostname.split('.').splice(-2).join('.'); - this.cache = new Map(); - this.userjsCache = new Map(); + this.cache = memorized.get('container'); + this.userjsCache = memorized.get('userjs'); this.unsaved = false; this.isBlacklisted = false; this.rebuild = false; @@ -1631,23 +1744,9 @@ const primaryFN = (injCon) => { const fBtns = make('mujs-column', 'mujs-list hidden'); const dwnCode = make('mu-jsbtn', '', { innerHTML: `${iconSVG.load('install')} ${lang.saveFile}`, - async onclick(evt) { - evt.preventDefault(); - try { - const txt = await reqCode(ujs); - if (typeof txt !== 'string') { - return; - } - const makeUserJS = new Blob([txt], { type: 'text/plain' }); - const dlBtn = make('a', 'mujs_Downloader'); - dlBtn.href = URL.createObjectURL(makeUserJS); - dlBtn.download = 'test.user.js'; - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - dlBtn.remove(); - } catch (ex) { - err(ex); - } + dataset: { + command: 'download-userjs', + userjs: ujs.id } }); const tr = make('tr', 'frame'); @@ -1662,22 +1761,9 @@ const primaryFN = (injCon) => { }); const loadCode = make('mu-jsbtn', '', { innerHTML: `${iconSVG.load('search')} ${lang.codePreview}`, - async onclick(evt) { - evt.preventDefault(); - try { - if (!isEmpty(codeArea.value)) { - dom.cl.toggle(codeArea, 'hidden'); - return; - } - const txt = await reqCode(ujs); - if (typeof txt !== 'string') { - return; - } - codeArea.value = txt; - dom.cl.remove(codeArea, 'hidden'); - } catch (ex) { - err(ex); - } + dataset: { + command: 'load-userjs', + userjs: ujs.id } }); if (engine) { @@ -1741,18 +1827,6 @@ const primaryFN = (injCon) => { const engines = cfg.engines.filter((e) => e.enabled); const cache = MUJS.cache.get(host); const customRecords = []; - const rateFN = (data) => { - try { - for (const [key, value] of Object.entries(data.resources.code_search)) { - const txt = make('mujs-row', 'rate-info', { - innerHTML: `${key.toUpperCase()}: ${value}` - }); - rateContainer.append(txt); - } - } catch (ex) { - MUJS.showError(ex); - } - }; const isSupported = (name) => { for (const [k, v] of Object.entries(unsupported)) { if (k !== name) { @@ -1804,7 +1878,7 @@ const primaryFN = (injCon) => { if (typeof txt !== 'string') { continue; } - const headers = txt.match(/\/\/\s@[\w][\s\S]+/g); // txt.match(/\/\/\s?==UserScript==([\s\S]*?)\/\/\s?==\/UserScript==/gm); + const headers = txt.match(/\/\/\s@[\w][\s\S]+/g); if (isNull(headers)) { continue; } @@ -1996,14 +2070,23 @@ const primaryFN = (injCon) => { 'X-GitHub-Api-Version': '2022-11-28' } }) - .then(rateFN) + .then((data) => { + for (const [key, value] of Object.entries(data.resources.code_search)) { + const txt = make('mujs-row', 'rate-info', { + innerHTML: `${key.toUpperCase()}: ${value}` + }); + rateContainer.append(txt); + } + }) .catch(MUJS.showError); }) .catch(MUJS.showError); } else { - Network.req(`${eURL}${host}`, 'GET', 'document').then(customFN).catch((error) => { - MUJS.showError(`Engine: "${engine.name}"`, error); - }); + Network.req(`${eURL}${host}`, 'GET', 'document') + .then(customFN) + .catch((error) => { + MUJS.showError(`Engine: "${engine.name}"`, error); + }); } } } @@ -2017,107 +2100,29 @@ const primaryFN = (injCon) => { const exBtn = make('mu-js', 'mujs-sty-flex'); const exportCFG = make('mujs-btn', 'mujs-export', { innerHTML: 'Export Config', - onclick() { - try { - const str = JSON.stringify(cfg, null, ' '); - const bytes = new TextEncoder().encode(str); - const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); - const dlBtn = make('a', 'mujs-exporter', { - href: URL.createObjectURL(blob), - download: 'Magic_Userscript_config.json' - }); - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - } catch (ex) { - MUJS.showError(ex); - } - } - }); - const cfgJSON = make('input', 'hidden', { - type: 'file', - accept: '.json', - onchange: (evt) => { - try { - [...evt.target.files].forEach((file) => { - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = () => { - const result = JSON.parse(reader.result); - log(`Imported config: { ${file.name} }`, result); - cfg = result; - MUJS.unsaved = true; - MUJS.rebuild = true; - rebuildCfg(); - MUJS.save(); - sleazyRedirect(); - MUJS.cache.clear(); - buildlist(); - MUJS.unsaved = false; - MUJS.rebuild = false; - }; - reader.onerror = () => { - MUJS.showError(reader.error); - }; - }); - } catch (ex) { - MUJS.showError(ex); - } + dataset: { + command: 'export-cfg' } }); const importCFG = make('mujs-btn', 'mujs-import', { innerHTML: 'Import Config', - onclick() { - cfgJSON.click(); + dataset: { + command: 'import-cfg' } }); const exportTheme = make('mujs-btn', 'mujs-export', { innerHTML: 'Export Theme', - onclick() { - try { - const str = JSON.stringify(cfg.theme, null, ' '); - const bytes = new TextEncoder().encode(str); - const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); - const dlBtn = make('a', 'mujs-exporter', { - href: URL.createObjectURL(blob), - download: 'Magic_Userscript_theme.json' - }); - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - } catch (ex) { - MUJS.showError(ex); - } - } - }); - const themeJSON = make('input', 'hidden', { - type: 'file', - accept: '.json', - onchange: (evt) => { - try { - [...evt.target.files].forEach((file) => { - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = () => { - const result = JSON.parse(reader.result); - log(`Imported theme: { ${file.name} }`, result); - cfg.theme = result; - renderTheme(cfg.theme); - }; - reader.onerror = () => { - MUJS.showError(reader.error); - }; - }); - } catch (ex) { - MUJS.showError(ex); - } + dataset: { + command: 'export-theme' } }); const importTheme = make('mujs-btn', 'mujs-import', { innerHTML: 'Import Theme', - onclick() { - themeJSON.click(); + dataset: { + command: 'import-theme' } }); - exBtn.append(importCFG, exportCFG, cfgJSON, exportTheme, themeJSON, importTheme); + exBtn.append(importCFG, importTheme, exportCFG, exportTheme); cfgpage.append(exBtn); const makerow = (desc = 'Placeholder', type = null, nm = 'Placeholder', attrs = {}) => { @@ -2388,11 +2393,11 @@ const primaryFN = (injCon) => { oninput(evt) { evt.preventDefault(); if (isEmpty(evt.target.value)) { - dom.cl.remove(shA('tr.frame'), 'hidden'); + dom.cl.remove(qsA('tr', tabbody), 'hidden'); return; } const reg = new RegExp(evt.target.value, 'gi'); - for (const ujs of shA('tr.frame')) { + for (const ujs of qsA('tr', tabbody)) { const m = ujs.children[0]; const n = ujs.children[1]; const final = m.textContent.match(reg) || n.textContent.match(reg) || []; @@ -2461,16 +2466,7 @@ const primaryFN = (injCon) => { countframe.append(gfcounter, sfcounter); fsearch.append(filterList); btnHandles.append(btnHide, btnfullscreen, closebtn); - btnframe.append( - fsearch, - filterBtn, - btncfg, - btnissue, - btnhome, - btngreasy, - btnnav, - btnHandles - ); + btnframe.append(fsearch, filterBtn, btncfg, btnissue, btnhome, btngreasy, btnnav, btnHandles); header.append(countframe, rateContainer, btnframe); ntHead.append(ntAdd); tbody.append(table, cfgpage); @@ -2495,7 +2491,6 @@ const primaryFN = (injCon) => { ]); const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; - const comparer = (idx, asc) => (a, b) => ((v1, v2) => v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) @@ -2523,7 +2518,8 @@ const primaryFN = (injCon) => { mujsRoot.append(mainframe, main); makecfg(); - buildlist().then(timeoutFrame); + buildlist(); + // buildlist().then(timeoutFrame); if (cfg.injection) { info('Migrating old config...'); @@ -2538,11 +2534,10 @@ const primaryFN = (injCon) => { // #endregion /** * @param { Function } callback - * @returns { null | true } */ const loadDOM = (callback) => { if (!isFN(callback)) { - return null; + return; } if (document.readyState === 'interactive' || document.readyState === 'complete') { callback.call({}, document); @@ -2550,7 +2545,6 @@ const loadDOM = (callback) => { document.addEventListener('DOMContentLoaded', (evt) => callback.call({}, evt.target), { once: true }); - return true; }; const Setup = async () => { try { @@ -2558,25 +2552,19 @@ const Setup = async () => { lang = Language.cache; info('Config:', cfg); loadDOM((doc) => { - try { - if (window.location === null) { - err('"window.location" is null, reload the webpage or use a different one'); - return; - } - if (doc === null) { - err('"doc" is null, reload the webpage or use a different one'); - return; - } - sleazyRedirect(); - container.inject(primaryFN, doc); - } catch (ex) { - err(ex); + if (window.location === null) { + err('"window.location" is null, reload the webpage or use a different one'); + return; } + if (doc === null) { + err('"doc" is null, reload the webpage or use a different one'); + return; + } + sleazyRedirect(); + container.inject(primaryFN, doc); }); } catch (ex) { err(ex); } }; -if (typeof userjs === 'object' && userjs.UserJS && window && window.self === window.top) { - Setup(); -} +Setup(); diff --git a/src/sass/_main.scss b/src/sass/_main.scss index 8686a50..54d6d17 100644 --- a/src/sass/_main.scss +++ b/src/sass/_main.scss @@ -291,6 +291,8 @@ mu-js { border-radius: 16px; > * { min-height: 50px; + } + .error { &:nth-child(even) { background: var(--mujs-even-err, hsl(0, 100%, 22%)) !important; }