UserscriptAPIMessage

https://gitee.com/liangjiancang/userscript/tree/master/lib/UserscriptAPI

当前为 2021-09-07 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/432000/968205/UserscriptAPIMessage.js

  1. /**
  2. * UserscriptAPIMessage
  3. *
  4. * 依赖于 `UserscriptAPI`,`UserscriptAPIDom`。
  5. * @version 1.1.0.20210907
  6. * @author Laster2800
  7. * @see {@link https://gitee.com/liangjiancang/userscript/tree/master/lib/UserscriptAPI UserscriptAPI}
  8. */
  9. class UserscriptAPIMessage {
  10. /**
  11. * @param {UserscriptAPI} api `UserscriptAPI`
  12. */
  13. constructor(api) {
  14. this.api = api
  15.  
  16. api.initModuleStyle(`
  17. .${api.options.id}-infobox {
  18. z-index: 100000000;
  19. background-color: #000000bf;
  20. font-size: 16px;
  21. max-width: 24em;
  22. min-width: 2em;
  23. color: white;
  24. padding: 0.5em 1em;
  25. border-radius: 9.6px;
  26. opacity: 0;
  27. transition: opacity ${api.options.fadeTime}ms ease-in-out;
  28. user-select: none;
  29. pointer-events: none;
  30. }
  31.  
  32. .${api.options.id}-infobox .gm-advanced-table td {
  33. vertical-align: middle;
  34. }
  35. .${api.options.id}-infobox .gm-advanced-table td:first-child {
  36. padding-right: 0.6em;
  37. }
  38.  
  39. .${api.options.id}-dialog {
  40. z-index: 90000000;
  41. background-color: white;
  42. font-size: 17px;
  43. min-width: 18em;
  44. max-width: 35em;
  45. border-radius: 4px;
  46. opacity: 0;
  47. box-shadow: #000000aa 0px 3px 6px;
  48. transition: opacity 150ms cubic-bezier(0.68, -0.55, 0.27, 1.55);
  49. }
  50. .${api.options.id}-dialog .gm-header {
  51. padding: 0.5em 1em 0.4em;
  52. border-bottom: 1px solid #d5d5d5;
  53. }
  54. .${api.options.id}-dialog .gm-body {
  55. padding: 0.8em 1em;
  56. }
  57. .${api.options.id}-dialog .gm-bottom {
  58. padding: 0 1em 0.6em;
  59. text-align: right;
  60. }
  61. .${api.options.id}-dialog .gm-content {
  62. line-height: 1.6em;
  63. }
  64. .${api.options.id}-dialog button.gm-interactive {
  65. font-size: 0.9em;
  66. padding: 0.1em 0.6em;
  67. margin-left: 0.8em;
  68. cursor: pointer;
  69. background-color: white;
  70. border: 1px solid #909090;
  71. border-radius: 2px;
  72. }
  73. .${api.options.id}-dialog button.gm-interactive:hover,
  74. .${api.options.id}-dialog button.gm-interactive:focus {
  75. background-color: #ebebeb;
  76. }
  77. .${api.options.id}-dialog input.gm-interactive {
  78. outline: none;
  79. border: 1px solid #909090;
  80. width: calc(100% - 12px);
  81. margin-top: 0.6em;
  82. padding: 4px 6px;
  83. }
  84. .${api.options.id}-dialog textarea.gm-interactive {
  85. outline: none;
  86. border: 1px solid #909090;
  87. width: calc(100% - 2em);
  88. margin: 0.6em 0 -0.4em;
  89. padding: 1em;
  90. resize: none;
  91. }
  92. .${api.options.id}-dialog textarea.gm-interactive::-webkit-scrollbar {
  93. width: 6px;
  94. height: 6px;
  95. background-color: transparent;
  96. }
  97. .${api.options.id}-dialog textarea.gm-interactive::-webkit-scrollbar-thumb {
  98. border-radius: 3px;
  99. background-color: #0000002b;
  100. }
  101. .${api.options.id}-dialog textarea.gm-interactive::-webkit-scrollbar-corner {
  102. background-color: transparent;
  103. }
  104. `)
  105. }
  106.  
  107. /**
  108. * 创建信息
  109. * @param {string} msg 信息
  110. * @param {Object} [options] 选项
  111. * @param {() => void} [options.onOpened] 信息打开后的回调
  112. * @param {() => void} [options.onClosed] 信息关闭后的回调
  113. * @param {boolean} [options.autoClose=true] 是否自动关闭信息,配合 `options.ms` 使用
  114. * @param {number} [options.ms=1500] 显示时间(单位:ms,不含渐显/渐隐时间)
  115. * @param {boolean} [options.html=false] 是否将 `msg` 理解为 HTML
  116. * @param {string} [options.width] 信息框的宽度,不设置的情况下根据内容决定,但有最小宽度和最大宽度的限制
  117. * @param {{top: string, left: string}} [options.position] 信息框的位置,必须带单位或以百分号结尾;不设置该项时,相当于设置为 `{ top: '70%', left: '50%' }`
  118. * @return {HTMLElement} 信息框元素
  119. */
  120. info(msg, options) {
  121. const api = this.api
  122. options = {
  123. autoClose: true,
  124. ms: 1500,
  125. html: false,
  126. width: null,
  127. position: {
  128. top: '70%',
  129. left: '50%',
  130. },
  131. ...options,
  132. }
  133.  
  134. const infobox = document.createElement('div')
  135. infobox.className = `${api.options.id}-infobox`
  136. if (options.width) {
  137. infobox.style.minWidth = 'auto'
  138. infobox.style.maxWidth = 'none'
  139. infobox.style.width = options.width
  140. }
  141. if (options.html) {
  142. infobox.innerHTML = msg
  143. } else {
  144. infobox.textContent = msg
  145. }
  146. document.body.appendChild(infobox)
  147. api.dom.setPosition(infobox, options.position)
  148.  
  149. api.dom.fade(true, infobox, () => {
  150. options.onOpened?.call(infobox)
  151. if (options.autoClose) {
  152. setTimeout(() => {
  153. this.close(infobox, options.onClosed)
  154. }, options.ms)
  155. }
  156. })
  157. return infobox
  158. }
  159.  
  160. /**
  161. * 创建高级信息
  162. * @param {HTMLElement} el 启动元素
  163. * @param {string} msg 信息
  164. * @param {string} [flag] 标志信息
  165. * @param {Object} [options] 选项
  166. * @param {string} [options.flagSize='1.8em'] 标志大小
  167. * @param {string} [options.width] 信息框的宽度,不设置的情况下根据内容决定,但有最小宽度和最大宽度的限制
  168. * @param {{top: string, left: string}} [options.position] 信息框的位置,不设置该项时,沿用 `api.message.infobox()` 的默认设置
  169. * @param {() => boolean} [options.disabled] 用于获取是否禁用信息的方法
  170. */
  171. advancedInfo(el, msg, flag, options) {
  172. options = {
  173. flagSize: '1.8em',
  174. ...options
  175. }
  176.  
  177. const _self = this
  178. el.addEventListener('mouseenter', function() {
  179. if (options.disabled?.()) return
  180. const htmlMsg = `
  181. <table class="gm-advanced-table"><tr>
  182. ${flag ? `<td style="font-size:${options.flagSize};line-height:${options.flagSize}">${flag}</td>` : ''}
  183. <td>${msg}</td>
  184. </tr></table>
  185. `
  186. this.infobox = _self.info(htmlMsg, { ...options, html: true, autoClose: false })
  187. })
  188. el.addEventListener('mouseleave', function() {
  189. _self.close(this.infobox)
  190. })
  191. }
  192.  
  193. /**
  194. * @typedef DialogElement
  195. * @property {HTMLElement[]} interactives 交互元素
  196. * @property {(callback?: () => void) => void} open 打开对话框
  197. * @property {(callback?: () => void) => void} close 关闭对话框
  198. */
  199. /**
  200. * 创建对话框
  201. * @param {string} msg 信息
  202. * @param {Object} [options] 选项
  203. * @param {boolean} [options.html] 信息是否为 HTML
  204. * @param {string} [options.title=api.options.label] 标题
  205. * @param {boolean} [options.titleHtml] 标题是否为 HTML
  206. * @param {boolean} [options.lineInput] 是否添加单行输入框
  207. * @param {boolean} [options.boxInput] 是否添加多行输入框
  208. * @param {string[]} [options.buttons] 对话框按钮文本
  209. * @param {string} [options.width] 对话框宽度,不设置的情况下根据内容决定,但有最小宽度和最大宽度的限制
  210. * @param {{top: string, left: string}} [options.position] 信息框的位置,必须带单位或以百分号结尾;不设置该项时绝对居中
  211. * @returns {HTMLElement & DialogElement} 对话框元素
  212. */
  213. dialog(msg, options) {
  214. const _self = this
  215. const api = _self.api
  216. options = {
  217. title: api.options.label,
  218. position: {
  219. top: '50%',
  220. left: '50%',
  221. },
  222. ...options,
  223. }
  224.  
  225. const dialog = document.createElement('div')
  226. dialog.className = `${api.options.id}-dialog`
  227. if (options.width) {
  228. dialog.style.minWidth = 'auto'
  229. dialog.style.maxWidth = 'none'
  230. dialog.style.width = options.width
  231. }
  232.  
  233. let bottomHtml = ''
  234. if (options.buttons) {
  235. for (const button of options.buttons) {
  236. bottomHtml += `<button class="gm-interactive">${button}</button>`
  237. }
  238. if (bottomHtml) {
  239. bottomHtml = `<div class="gm-bottom">${bottomHtml}</div>`
  240. }
  241. }
  242. dialog.innerHTML = `
  243. ${options.title ? '<div class="gm-header"></div>' : ''}
  244. <div class="gm-body">
  245. <div class="gm-content"></div>
  246. ${options.lineInput ? '<input type="text" class="gm-interactive">' : ''}
  247. ${options.boxInput ? '<textarea class="gm-interactive"></textarea>' : ''}
  248. </div>
  249. ${bottomHtml}
  250. `
  251. if (options.title) {
  252. const header = dialog.querySelector('.gm-header')
  253. if (options.titleHtml) {
  254. header.innerHTML = options.title
  255. } else {
  256. header.textContent = options.title
  257. }
  258. }
  259. const content = dialog.querySelector('.gm-content')
  260. if (options.html) {
  261. content.innerHTML = msg
  262. } else {
  263. content.textContent = msg
  264. }
  265. dialog.interactives = dialog.querySelectorAll('.gm-interactive')
  266. document.body.appendChild(dialog)
  267.  
  268. dialog.fadeOutNoInteractive = true
  269. dialog.open = function(callback) {
  270. api.dom.setPosition(this, options.position)
  271. api.dom.fade(true, this, callback && (() => callback.call(this)))
  272. }
  273. dialog.close = function(callback) {
  274. _self.close(this, callback)
  275. }
  276. return dialog
  277. }
  278.  
  279. /**
  280. * 创建提醒对话框
  281. * @param {string} msg 信息
  282. * @param {Object} [options] 选项
  283. * @param {boolean} [options.primitive] 使用原生组件
  284. * @param {boolean} [options.html] 信息是否为 HTML
  285. * @param {string} [options.title=api.options.label] 标题
  286. * @param {boolean} [options.titleHtml] 标题是否为 HTML
  287. * @param {string} [options.width] 对话框宽度,不设置的情况下根据内容决定,但有最小宽度和最大宽度的限制
  288. * @param {{top: string, left: string}} [options.position] 信息框的位置,不设置该项时绝对居中
  289. * @returns {Promise<void>} 用户输入
  290. */
  291. alert(msg, options) {
  292. const _self = this
  293. return new Promise(resolve => {
  294. let primitive = !document.body || options?.primitive
  295. if (!primitive) {
  296. try {
  297. const dialog = _self.dialog(msg, {
  298. ...options,
  299. buttons: ['确定'],
  300. })
  301. const confirm = dialog.interactives[0]
  302. confirm.focus({ preventScroll: true })
  303. confirm.addEventListener('click', function() {
  304. dialog.close()
  305. resolve()
  306. })
  307. dialog.open()
  308. } catch (e) { // not true error
  309. primitive = true
  310. }
  311. }
  312. if (primitive) {
  313. const label = _self.api.options.label
  314. if (options?.html) {
  315. const el = document.createElement('div')
  316. el.innerHTML = msg
  317. msg = el.textContent
  318. }
  319. resolve(alert(`${label ? `${label}\n\n` : ''}${msg}`))
  320. }
  321. })
  322. }
  323.  
  324. /**
  325. * 创建确认对话框
  326. * @param {string} msg 信息
  327. * @param {Object} [options] 选项
  328. * @param {boolean} [options.primitive] 使用原生组件
  329. * @param {boolean} [options.html] 信息是否为 HTML
  330. * @param {string} [options.title=api.options.label] 标题
  331. * @param {boolean} [options.titleHtml] 标题是否为 HTML
  332. * @param {string} [options.width] 对话框宽度,不设置的情况下根据内容决定,但有最小宽度和最大宽度的限制
  333. * @param {{top: string, left: string}} [options.position] 信息框的位置,不设置该项时绝对居中
  334. * @returns {Promise<boolean>} 用户输入
  335. */
  336. confirm(msg, options) {
  337. const _self = this
  338. return new Promise(resolve => {
  339. let primitive = !document.body || options?.primitive
  340. if (!primitive) {
  341. try {
  342. const dialog = _self.dialog(msg, {
  343. ...options,
  344. buttons: ['确定', '取消'],
  345. })
  346. const confirm = dialog.interactives[0]
  347. const cancel = dialog.interactives[1]
  348. confirm.focus({ preventScroll: true })
  349. confirm.addEventListener('click', function() {
  350. dialog.close()
  351. resolve(true)
  352. })
  353. cancel.addEventListener('click', function() {
  354. dialog.close()
  355. resolve(false)
  356. })
  357. dialog.open()
  358. } catch (e) { // not true error
  359. primitive = true
  360. }
  361. }
  362. if (primitive) {
  363. const label = _self.api.options.label
  364. if (options?.html) {
  365. const el = document.createElement('div')
  366. el.innerHTML = msg
  367. msg = el.textContent
  368. }
  369. resolve(confirm(`${label ? `${label}\n\n` : ''}${msg}`))
  370. }
  371. })
  372. }
  373.  
  374. /**
  375. * 创建输入对话框
  376. * @param {string} msg 信息
  377. * @param {string} [val] 默认值
  378. * @param {Object} [options] 选项
  379. * @param {boolean} [options.primitive] 使用原生组件
  380. * @param {boolean} [options.html] 信息是否为 HTML
  381. * @param {string} [options.title=api.options.label] 标题
  382. * @param {boolean} [options.titleHtml] 标题是否为 HTML
  383. * @param {string} [options.width] 对话框宽度,不设置的情况下根据内容决定,但有最小宽度和最大宽度的限制
  384. * @param {{top: string, left: string}} [options.position] 信息框的位置,不设置该项时绝对居中
  385. * @returns {Promise<string>} 用户输入
  386. */
  387. prompt(msg, val, options) {
  388. const _self = this
  389. return new Promise(resolve => {
  390. let primitive = !document.body || options?.primitive
  391. if (!primitive) {
  392. try {
  393. const dialog = _self.dialog(msg, {
  394. ...options,
  395. buttons: ['确定', '取消'],
  396. lineInput: true,
  397. })
  398. const input = dialog.interactives[0]
  399. const confirm = dialog.interactives[1]
  400. const cancel = dialog.interactives[2]
  401. if (val) {
  402. input.value = val
  403. }
  404. input.focus({ preventScroll: true })
  405. input.addEventListener('keyup', function(e) {
  406. if (e.keyCode == 13) {
  407. confirm.dispatchEvent(new Event('click'))
  408. }
  409. })
  410. confirm.addEventListener('click', function() {
  411. dialog.close()
  412. resolve(input.value)
  413. })
  414. cancel.addEventListener('click', function() {
  415. dialog.close()
  416. resolve(null)
  417. })
  418. dialog.open()
  419. } catch (e) { // not true error
  420. primitive = true
  421. }
  422. }
  423. if (primitive) {
  424. const label = _self.api.options.label
  425. if (options?.html) {
  426. const el = document.createElement('div')
  427. el.innerHTML = msg
  428. msg = el.textContent
  429. }
  430. resolve(prompt(`${label ? `${label}\n\n` : ''}${msg}`, val))
  431. }
  432. })
  433. }
  434.  
  435. /**
  436. * 关闭信息元素
  437. * @param {HTMLElement} msgEl 信息元素
  438. * @param {() => void} [callback] 信息关闭后的回调
  439. */
  440. close(msgEl, callback) {
  441. if (msgEl) {
  442. this.api.dom.fade(false, msgEl, () => {
  443. callback?.call(msgEl)
  444. msgEl?.remove()
  445. })
  446. }
  447. }
  448. }
  449.  
  450. /* global UserscriptAPI */
  451. { UserscriptAPI.registerModule('message', UserscriptAPIMessage) }

QingJ © 2025

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