Box for modal / toast
此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.org/scripts/447483/1071404/Boxjs.js
// ==UserScript== // @name Box.js // @namespace https://github.com/invobzvr // @version 0.5 // @description Box for modal / toast // @author invobzvr // @homepageURL https://github.com/invobzvr/invotoys.js/tree/main/box.js // @supportURL https://github.com/invobzvr/invotoys.js/issues // @license GPL-3.0 // ==/UserScript== const LIB_NAME = 'Box'; !function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self)[LIB_NAME] = factory(); }(this, function () { 'use strict'; function element(tag, props, body) { let el = document.createElement(tag); props && Object.entries(props).forEach(([key, val]) => val && (el[key] = val)); body && body instanceof HTMLElement ? body.append(el) : typeof body === 'string' && document.querySelector(body).append(el); return el; } class Box { constructor(params) { this.params = Object.assign({}, params); this.build(); typeof this.params.didBuild === 'function' && this.params.didBuild(this.modal); this.register(); this.await = new Promise(resolve => this.close = async ret => { resolve(ret); await this.hide(); this[this.ctnr.childElementCount === 1 ? 'ctnr' : 'modal'].remove(); }); if (this.params.toast) { let time = this.params.time, bgc = { success: '#0a5', warning: '#fa0', error: '#f25', }[this.params.type]; Number.isFinite(time) && time > 0 && setTimeout(() => this.close(), time); bgc && (this.modal.style.background = bgc); } this.params.show !== false && this.show(); } static mixin(mixinParams) { return class MixinBox extends this { constructor(params) { super(Object.assign({}, mixinParams, params)); } } } build() { if (this.params.toast) { this.ctnr = document.querySelector('.box-toaster') || element('div', { className: 'box-toaster' }, document.body); this.modal = element('div', { className: 'box-toast-item' }, this.ctnr); } else { this.ctnr = element('div', { className: 'box-container' }, document.body); this.modal = element('div', { className: 'box-modal' }, this.ctnr); } element('div', { className: 'box-title', innerText: this.params.title, }, this.modal); element('div', { className: 'box-content', innerText: this.params.text, innerHTML: this.params.html, }, this.modal); if (this.params.toast) { return; } let actions = element('div', { className: 'box-actions' }, this.modal); Object.entries(this.params.actions || { OK: () => this.close() }).forEach(([key, val]) => element('button', { className: 'box-button', innerText: key, onclick: () => this.close(val(this.modal)), }, actions)); } register() { let ignore = false; this.modal.addEventListener('mousedown', () => { this.ctnr.addEventListener('mouseup', evt => evt.target === this.ctnr && (ignore = true), { once: true }); }); this.ctnr.addEventListener('mousedown', () => { this.ctnr.addEventListener('mouseup', evt => (evt.target === this.modal || this.modal.contains(evt.target)) && (ignore = true), { once: true }); }); this.ctnr.addEventListener('click', evt => { ignore ? ignore = false : evt.target === this.ctnr && this.close(); }); } show() { return new Promise(resolve => { setTimeout(() => { !this.params.toast && this.ctnr.classList.add('box-backdrop'); this.modal.classList.add('in'); }, 10); this.modal.addEventListener('transitionend', resolve, { once: true }); }); } hide() { return new Promise(resolve => { !this.params.toast && this.ctnr.classList.remove('box-backdrop'); this.modal.classList.remove('in'); this.modal.addEventListener('transitionend', resolve, { once: true }); }); } then(onFulfilled) { return this.await.then(onFulfilled); } } element('style', null, document.head).innerHTML = `.box-container { bottom: 0; display: grid; left: 0; overflow: auto; pointer-events: none; position: fixed; right: 0; top: 0; transition: .2s; z-index: 200; } .box-backdrop { background: #0006; pointer-events: auto; } .box-modal { align-self: center; background: #fff; border-radius: 5px; box-shadow: 0 3px 20px #0006; justify-self: center; margin: 20px; opacity: 0; padding: 0 30px; transform: scale(.8); transition: .2s; user-select: none; } .box-modal.in { opacity: 1; transform: scale(1); } .box-title { font-size: 30px; padding: 20px; text-align: center; } .box-input-group { display: table; margin-bottom: 10px; } .box-label { display: table-cell; font-size: 16px; padding: 0 10px; text-align: center; width: 100%; } .box-input { font-size: 20px; padding: 5px 9px; } .box-input[type=checkbox] { vertical-align: middle; } .box-options { margin: auto; width: 80%; } .box-option-item { margin-right: 12px; white-space: nowrap; } .box-option-item .box-label { font-size: 13px; padding: 0 0 0 3px; } .box-actions { margin: 20px; text-align: center; } .box-button { background: #09f; border-radius: 5px; border: none; color: #fff; cursor: pointer; font-size: 16px; margin: 5px; padding: 10px 18px; } .box-toaster { display: grid; min-width: 360px; padding: 10px; position: fixed; right: 0; top: 0; width: 27%; z-index: 201; } .box-toast-item { background: #09f; border-radius: 7px; box-shadow: 0 2px 10px #0005; color: #fff; margin-bottom: 10px; padding: 10px 10px 15px 10px; opacity: 0; transition: .2s; transform: scale(.8); } .box-toast-item.in { opacity: 1; transform: scale(1); } .box-toast-item .box-title, .box-toast-item .box-content { padding: 0 10px; text-align: left; word-break: break-word; }`; return Box; });