popup-inject

向网页中插入一个侧边按钮和一个弹窗

目前為 2023-08-19 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/473443/1237616/popup-inject.js

  1. // @name popup-inject
  2. // @description 向网页中插入一个侧边按钮和一个弹窗
  3. // @version 1.0.5
  4. // @author paso
  5.  
  6. ;(function () {
  7. 'use strict'
  8.  
  9. const _injectHtml = (config, resolve) => {
  10. document.head.insertAdjacentHTML(
  11. 'beforeend',
  12. `
  13. <style>
  14. .${config.namespace} {
  15. color: black;
  16. }
  17. .${config.namespace} * {
  18. box-sizing: border-box;
  19. }
  20. .${config.namespace} *::-webkit-scrollbar {
  21. width: 8px;
  22. height: 8px;
  23. }
  24. .${config.namespace} *::-webkit-scrollbar-thumb {
  25. border-radius: 4px;
  26. background-color: rgba(0, 0, 0, 0.5);
  27. }
  28. .${config.namespace} *::-webkit-scrollbar-track {
  29. border-radius: 4px;
  30. background-color: transparent;
  31. }
  32. .${config.namespace} .flex {
  33. display: flex;
  34. flex-direction: row;
  35. align-items: stretch;
  36. justify-content: flex-start;
  37. }
  38. .${config.namespace} .flex.col {
  39. flex-direction: column;
  40. }
  41. .${config.namespace} .sticky-bar {
  42. position: fixed;
  43. top: ${config.location};
  44. left: 0;
  45. transform: translateX(calc(12px - ${config.collapse}));
  46. z-index: 99999999;
  47. background: #3D7FFF;
  48. color: white;
  49. padding: 2px 10px 2px 4px;
  50. cursor: pointer;
  51. user-select: none;
  52. border-radius: 0 12px 12px 0;
  53. box-shadow: 0 2px 4px 1px #0006;
  54. transition: transform 0.5s ease;
  55. }
  56. .${config.namespace} .sticky-bar:hover {
  57. transform: none;
  58. }
  59. .${config.namespace} .mask {
  60. position: fixed;
  61. inset: 0;
  62. padding: 24px;
  63. overflow: auto;
  64. z-index: 99999999;
  65. background-color: rgba(0, 0, 0, 0.4);
  66. display: flex;
  67. align-items: center;
  68. justify-content: center;
  69. opacity: 0;
  70. pointer-events: none;
  71. transition: opacity .6s;
  72. }
  73. .${config.namespace}.open .mask {
  74. opacity: 1;
  75. pointer-events: all;
  76. }
  77. .${config.namespace} .popup {
  78. position: relative;
  79. margin: auto;
  80. padding: 16px;
  81. background: #f0f2f5;
  82. border-radius: 2px;
  83. box-shadow: 0 1px 12px 2px rgba(0, 0, 0, 0.4);
  84. transform: scale(0);
  85. transition: transform .3s;
  86. }
  87. .${config.namespace}.open .popup {
  88. transform: scale(1);
  89. }
  90. .${config.namespace} label {
  91. margin-top: .5em;
  92. margin-bottom: 0;
  93. user-select: none;
  94. }
  95. .${config.namespace} .monospace {
  96. font-family: v-mono, Consolas, SFMono-Regular, Menlo, Courier, v-sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  97. }
  98. .${config.namespace} .button {
  99. user-select: none;
  100. min-width: unset;
  101. min-height: unset;
  102. margin: unset;
  103. padding: 4px 16px;
  104. color: #fff;
  105. border: 1px solid #3D7FFF;
  106. border-radius: 2px;
  107. background: #3D7FFF;
  108. text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
  109. box-shadow: 0 2px 0 rgba(0, 0, 0, 0.05);
  110. }
  111. .${config.namespace} textarea {
  112. background: white;
  113. resize: vertical;
  114. }
  115. .${config.namespace} .input, .${config.namespace} .button {
  116. height: 32px;
  117. transition: all 0.3s, height 0s;
  118. }
  119. .${config.namespace} .button:before, .${config.namespace} .button:after {
  120. content: none;
  121. }
  122. .${config.namespace} .button:hover, .${config.namespace} .button:focus {
  123. border-color: #669eff;
  124. background: #669eff;
  125. }
  126. .${config.namespace} .button:active {
  127. border-color: #295ed9;
  128. background: #295ed9;
  129. }
  130. .${config.namespace} .input {
  131. padding: 4px 8px;
  132. background: white;
  133. border: 1px solid #d9d9d9;
  134. border-radius: 2px;
  135. }
  136. .${config.namespace} .input:hover {
  137. border-color: #669eff;
  138. outline: 0;
  139. }
  140. .${config.namespace} .input:focus {
  141. border-color: #669eff;
  142. box-shadow: 0 0 0 2px rgba(61, 127, 255, 0.2);
  143. border-right-width: 1px;
  144. outline: 0;
  145. }
  146. ${config.style.replaceAll(/<\/?style>/g, '')}
  147. </style>`
  148. )
  149. const container = createElement('div', { class: config.namespace })
  150. const stickyBar = createElement('div', { class: 'sticky-bar' }, config.actionName)
  151. const mask = createElement('div', { class: 'mask' })
  152. const popup = createElement('div', { class: 'popup flex col' }, config.content)
  153. container.append(stickyBar, mask)
  154. mask.append(popup)
  155.  
  156. mask.onclick = () => {
  157. container.classList.remove('open')
  158. config.onPopHide && config.onPopHide()
  159. }
  160. popup.onclick = (e) => e.stopPropagation()
  161.  
  162. const _data = {
  163. stickyBarHeight: 0,
  164. innerOffset: 0,
  165. stickyBarClick: false
  166. }
  167.  
  168. const stickyMouseMove = (e) => {
  169. _data.stickyBarClick = false
  170. requestAnimationFrame(() => {
  171. let height = document.documentElement.clientHeight - _data.stickyBarHeight
  172. let newTop = e.pageY - _data.innerOffset
  173. if (newTop >= 0 && newTop <= height) {
  174. stickyBar.style.top = `${newTop}px`
  175. }
  176. })
  177. }
  178.  
  179. const stickyMouseUp = leftKey(() => {
  180. document.removeEventListener('mousemove', stickyMouseMove)
  181. document.removeEventListener('mouseup', stickyMouseUp)
  182. })
  183.  
  184. stickyBar.onmousedown = leftKey((e) => {
  185. _data.stickyBarClick = true
  186. const stickyBarStyle = window.getComputedStyle(stickyBar)
  187. _data.innerOffset = e.pageY - getNumber(stickyBarStyle.top)
  188. _data.stickyBarHeight =
  189. stickyBar.clientHeight + getNumber(stickyBarStyle.borderTopWidth) + getNumber(stickyBarStyle.borderBottomWidth)
  190. document.addEventListener('mousemove', stickyMouseMove)
  191. document.addEventListener('mouseup', stickyMouseUp)
  192. })
  193.  
  194. stickyBar.onmouseup = leftKey((e) => {
  195. if (_data.stickyBarClick) {
  196. _data.stickyBarClick = false
  197. container.classList.add('open')
  198. config.onPopShow && config.onPopShow()
  199. }
  200. stickyMouseUp()
  201. e.stopPropagation()
  202. })
  203.  
  204. document.body.append(container)
  205. // ---- other code
  206. resolve && resolve({ container, stickyBar, mask, popup })
  207. }
  208.  
  209. function createElement(tag, attrs, children) {
  210. const el = document.createElement(tag)
  211. if (attrs) {
  212. Object.entries(attrs).forEach(([k, v]) => {
  213. el.setAttribute(k, v)
  214. })
  215. }
  216. if (children instanceof HTMLElement) {
  217. el.append(children)
  218. } else if (typeof children === 'string') {
  219. el.innerHTML = children
  220. }
  221. return el
  222. }
  223.  
  224. function leftKey(fn) {
  225. return (...args) => {
  226. let key = args && args[0] && args[0].button
  227. if (key === 0 || key === void 0) {
  228. fn.apply(this, args)
  229. }
  230. }
  231. }
  232.  
  233. function getNumber(str) {
  234. if (str) {
  235. let mArr = str.match(/\d+(\.\d*)?|\.\d+/)
  236. if (mArr && mArr.length) {
  237. return parseFloat(mArr[0])
  238. }
  239. }
  240. return void 0
  241. }
  242.  
  243. function _checkConfig(config) {
  244. if (!config) throw new Error('config is required. you should call window.paso.injectPopup(config)')
  245. if (!config.namespace) throw new Error('config.namespace is required and it cannot be empty.')
  246. if (!/^[-\w]+$/.test(config.namespace)) throw new Error('config.namespace must match the regex /^[-\\w]+$/.')
  247. }
  248.  
  249. if (!window.paso || !(window.paso instanceof Object)) window.paso = {}
  250. window.paso.injectPopup = (config) => {
  251. _checkConfig(config)
  252. const _config = Object.assign(
  253. {},
  254. {
  255. namespace: '',
  256. actionName: 'Action',
  257. collapse: '100%',
  258. location: '25%',
  259. content: '<label>Hello World</label>',
  260. style: '',
  261. onPopShow() {},
  262. onPopHide() {}
  263. },
  264. config
  265. )
  266. return new Promise((resolve) => {
  267. _injectHtml(_config, resolve)
  268. })
  269. }
  270. })()