WhereIsMyForm

管理你的表单,不让他们走丢。

  1. // ==UserScript==
  2. // @name WhereIsMyForm
  3. // @namespace https://github.com/ForkFG
  4. // @version 0.5.1
  5. // @description 管理你的表单,不让他们走丢。
  6. // @author ForkKILLET
  7. // @match *://*/*
  8. // @noframes
  9. // @grant unsafeWindow
  10. // @grant GM_addStyle
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @require https://code.jquery.com/jquery-1.11.0.min.js
  14. // ==/UserScript==
  15.  
  16. // :: dev
  17.  
  18. const $ = this.$ // Debug: Hack eslint warnings in TM editor.
  19. const debug = false
  20. function expose(o) {
  21. if (debug) for (let i in o) unsafeWindow[i] = o[i]
  22. }
  23. function Throw(msg, detail) {
  24. msg = `[WIMF] ${msg}`
  25. arguments.length === 2
  26. ? console.error(msg + "\n%o", detail)
  27. : console.error(msg)
  28. }
  29.  
  30. // :: ext
  31.  
  32. String.prototype.initialCase = function() {
  33. return this[0].toUpperCase() + this.slice(1)
  34. }
  35.  
  36. Math.random.token = n => Math.random().toString(36).slice(- n)
  37.  
  38. location.here = (location.origin + location.pathname).replace("_", "%5F")
  39.  
  40. function setImmediateInterval(f, t) {
  41. f()
  42. return setInterval(f, t)
  43. }
  44.  
  45. $.fn.extend({
  46. path() {
  47. // Note: Too strict. We need a smarter path.
  48. // It doesn't work on dynamic pages sometimes.
  49. return (function _path(e, p = "", f = true) {
  50. if (! e) return p
  51. const $e = $(e), t = e.tagName.toLowerCase()
  52. let pn = t
  53. if (e.id) pn += `#${ e.id }`
  54. if (e.name) pn += `[name=${ e.name }]`
  55. if (! e.id && $e.parent().children(t).length > 1) {
  56. pn += `:nth-of-type(${ $e.prevAll(t).length + 1 })`
  57. }
  58. return _path(e.parentElement, pn + (f ? "" : ">" + p), false)
  59. })(this[0])
  60. },
  61. one(event, func) {
  62. return this.off(event).on(event, func)
  63. },
  64. forWhat(notCheck) {
  65. if (! notCheck && ! this.is("label")) return null
  66. let for_ = this.attr("for")
  67. if (for_) return $(`#${for_}`)
  68. for (let i of [ "prev", "next", "children" ]) {
  69. let $i = this[i]("input[type=radio], input[type=checkbox]")
  70. if ($i.length) return $i
  71. }
  72. return null
  73. },
  74. melt(type, time, a, b) {
  75. const v = this.css("display") === "none"
  76. if (type === "fadeio") type = v ? "fadein" : "fadeout"
  77. if (b == null) b = type === "fadein" ? "show" : ""
  78. if (a == null) a = type === "fadein" ? "" : "hide"
  79. this[b]()
  80. this.css("animation", `melting-${type} ${time}s`)
  81. time *= 1000
  82. setTimeout(() => this[a](), time > 100 ? time - 100 : time * 0.9)
  83. // Note: A bit shorter than the animation duration for avoid "flash back".
  84. return v
  85. },
  86. ""() {}
  87. })
  88.  
  89. // :: dat
  90.  
  91. // Note: `dat.xxx.yyy = zzz` doesn't work. Now have to use `dat._.xxx_yyy = zzz`.
  92. function Dat({ getter, setter, useWrapper, getW, setW, dataW }) {
  93. const pn = (p, n) => p ? p + "_" + n : n
  94. function dat(opt, src = dat, p) {
  95. const R = src === dat, r = new Proxy(src, useWrapper
  96. ? {
  97. get: (_t, k) => {
  98. if (k === "_" && R) return _
  99. return _[pn(p, k)]
  100. },
  101. set: (_t, k, v) => {
  102. if (k === "_" && R) Throw("[Dat] Set _.")
  103. _[pn(p, k)] = v
  104. }
  105. }
  106. : {
  107. get: (_t, k) => getter(pn(p, k), k),
  108. set: (_t, k, v) => setter(pn(p, k), k, v)
  109. }
  110. )
  111. for (let n in opt) {
  112. if (typeof opt[n] === "object" && ! Array.isArray(opt[n])) {
  113. if (r[n] === undefined) r[n] = {}
  114. src[n] = dat(opt[n], src[n], pn(p, n))
  115. }
  116. else if (r[n] === undefined) r[n] = opt[n]
  117. }
  118. return r
  119. }
  120.  
  121. function parse(path, src = dat) {
  122. const keys = path.split("_"), len = keys.length
  123. function _parse(idx, now) {
  124. let k = keys[idx]
  125. if (len - idx <= 1) return [ now, k ]
  126. if (now == null) Throw("[Dat]: Saw undefined when _.")
  127. return _parse(idx + 1, now[k])
  128. }
  129. return _parse(0, src)
  130. }
  131.  
  132. const _ = useWrapper ? new Proxy({}, {
  133. get: (_, p) => {
  134. const r = parse(p, getW())
  135. return r[0][r[1]]
  136. },
  137. set: (_, p, v) => {
  138. const d = getW(), r = parse(p, d)
  139. r[0][r[1]] = v
  140. setW(dataW ? dataW(d) : d)
  141. }
  142. }) : null
  143.  
  144. return dat
  145. }
  146.  
  147. const ts = Dat({
  148. useWrapper: true,
  149. getW: () => GM_getValue("app") ?? {},
  150. setW: v => GM_setValue("app", v)
  151. })({
  152. window: {
  153. state: "open",
  154. top: 0,
  155. right: 0,
  156. },
  157. key: {
  158. leader: "Alt-w",
  159. shortcut: {
  160. toggle: "&q",
  161. mark: "&m",
  162. fill: "&f",
  163. list: "&l",
  164. conf: "&c",
  165. info: "&i"
  166. }
  167. },
  168. operation: {}
  169. })._
  170. const ls = Dat({
  171. useWrapper: true,
  172. getW: () => JSON.parse(unsafeWindow.localStorage.getItem("WIMF") ?? "{}"),
  173. setW: v => unsafeWindow.localStorage.setItem("WIMF", v),
  174. dataW: v => JSON.stringify(v)
  175. })({})._
  176. const op = Dat({
  177. getter: (_, n) => {
  178. if (n === "all") return ts.operation
  179. if (n === "here") n = location.here
  180. return ts["operation_" + n] ?? []
  181. },
  182. setter: (_, n, v) => {
  183. if (n === "here") n = location.here
  184. ts["operation_" + n] = v
  185. }
  186. })({})
  187.  
  188. // :: fun
  189.  
  190. function scan({ hl, root } = {
  191. root: "body"
  192. }) {
  193. const o = op.here
  194.  
  195. const $r = $(root), rule = [
  196. { type: "text", evt: "change", sel: `input[type=text], input:not([type]), textarea` },
  197. { type: "radio", evt: "click", sel: `input[type=radio]` },
  198. { type: "checkbox", evt: "click", sel: `input[type=checkbox]` }
  199. ], A$ = []
  200.  
  201. function work(type) {
  202. return function (_, { p, l } = {}) {
  203. const $_ = $(this), path = p || $_.path(),
  204. d = { path, label: l, type }
  205. let f = true
  206.  
  207. switch (type) {
  208. case "text":
  209. const val = $_.val()
  210. for (let i in o) {
  211. if (o[i].type === type && o[i].path === path) {
  212. o[i].val = val
  213. f = false; break
  214. }
  215. }
  216. break
  217. case "radio":
  218. for (let i in o) {
  219. if (o[i].type === type) {
  220. if (o[i].path === path){
  221. f = false; break
  222. }
  223. // Note: Replace the old choice.
  224. if ($(o[i].path).attr("name") === $_.attr("name")) {
  225. o[i].path = path
  226. f = false; break
  227. }
  228. }
  229. }
  230. break
  231. case "checkbox":
  232. for (let i in o) {
  233. if (o[i].type === type && o[i].path === path){
  234. f = false; break
  235. }
  236. }
  237. break
  238. }
  239.  
  240. if (f) o.push(d)
  241. op.here = o
  242. }
  243. }
  244.  
  245. for (let [ i, r ] of Object.entries(rule))
  246. (A$[i] = $r.find(r.sel)).one(`${ r.evt }.WIMF work.WIMF`, work(r.type))
  247.  
  248. $r.find("label").one("click.WIMF", function() {
  249. const $_ = $(this), l = $_.path(),
  250. $o = $_.forWhat(true)
  251. if (! $o.is("input, textarea")) return
  252. const p = $o.path()
  253. $o.trigger("work.WIMF", [ { p, l } ])
  254. })
  255.  
  256. if (typeof hl === "function") A$.forEach($i => hl($i))
  257.  
  258. return [ A$, A$.reduce((a, v) => a + v.length, 0) ]
  259. }
  260.  
  261. function shortcut() {
  262. let t_pk
  263. const pk = []
  264. pk.last = () => pk[pk.length - 1]
  265.  
  266. const $w = $(unsafeWindow), $r = $(".WIMF"),
  267. sc = ts.key_shortcut, lk = ts.key_leader,
  268. sc_rm = () => {
  269. for (let i in sc) sc[i].m = 0
  270. },
  271. ct = () => {
  272. clearTimeout(t_pk)
  273. pk.splice(0)
  274. pk.sdk = false
  275. t_pk = null
  276. sc_rm()
  277. },
  278. st = () => {
  279. clearTimeout(t_pk)
  280. t_pk = setTimeout(ct, 800)
  281. }
  282.  
  283. for (let i in sc) sc[i] = sc[i].split("&").map(i => i === "" ? lk : i)
  284. const c_k = {
  285. toggle() {
  286. ts.window_state = $(".WIMF").melt("fadeio", 1.5) ? "open" : "close"
  287. },
  288. mark: UI.action.mark,
  289. fill: UI.action.fill,
  290. list: UI.action.list,
  291. conf: UI.action.conf,
  292. info: UI.action.info
  293. }
  294.  
  295. ct()
  296. $w.one("keydown.WIMF", e => {
  297. st(); let ck = "", sdk = false
  298. for (let dk of [ "alt", "ctrl", "shift", "meta" ]) {
  299. if (e[dk + "Key"]) {
  300. ck += dk = dk.initialCase()
  301. if (e.key === dk || e.key === "Control") {
  302. sdk = true; break
  303. }
  304. ck += "-"
  305. }
  306. }
  307. if (! sdk) ck += e.key.toLowerCase()
  308.  
  309. if (pk.sdk && ck.includes(pk.last())) {
  310. pk.pop()
  311. }
  312. pk.sdk = sdk
  313. pk.push(ck)
  314.  
  315. for (let i in sc) {
  316. const k = sc[i]
  317. if (k.m === k.length) continue
  318. if (k[k.m] === ck) {
  319. if (++k.m === k.length) {
  320. if (i !== "leader") ct()
  321. if (c_k[i]) c_k[i]()
  322. }
  323. }
  324. else if (pk.sdk && k[k.m].includes(ck)) ;
  325. else k.m = 0
  326. }
  327. })
  328. }
  329.  
  330. const UI = {}
  331. UI.meta = {
  332. author: GM_info.script.author,
  333. slogan: GM_info.script.description,
  334.  
  335. title: t => `<b class="WIMF-title">${t}</b>`,
  336. link: u => `<a href="${u}">${u}</a>`,
  337. badge: t => `<span class="WIMF-badge">${t}</span>`,
  338. button: (name, emoji) => `<button class="WIMF-button" name="${name}">${emoji}</button>`,
  339. buttonLittle: (name, emoji) => `<button class="WIMF-button little" name="${name}">${emoji}</button>`,
  340.  
  341. html: `
  342. <div class="WIMF">
  343. <div class="WIMF-main">
  344. <b class="WIMF-title">WhereIsMyForm</b>
  345. #{button | mark 标记 | 🔍}
  346. #{button | fill 填充 | 📃}
  347. #{button | list 清单 | 📚}
  348. #{button | conf 设置 | ⚙️}
  349. #{button | info 关于 | ℹ️}
  350. #{button | quit 退出 | ❌}
  351. </div>
  352. <div class="WIMF-text"></div>
  353. <div class="WIMF-msg"></div>
  354. </div>
  355. `,
  356. aboutCompetition: `
  357. 华东师大二附中“创意·创新·创造”大赛 <br/>
  358. <i>--【数据删除】</i>
  359. `,
  360. info: `
  361. #{title | Infomation} <br/>
  362. <p>
  363. #{slogan} <br/>
  364. <i>-- #{author}</i>
  365. <br/> <br/>
  366.  
  367. 可用的测试页面:
  368. 问卷星:#{link | https://www.wjx.cn/newsurveys.aspx}
  369. </p>
  370. `,
  371. confInput: (zone, name, hint) => `
  372. ${ name.replace(/^[a-z]+_/, "").initialCase() } ${hint}
  373. <input type="text" name="${zone}_${name}"/>
  374. `,
  375. confApply: (zone) => `<button data-zone="${zone}">OK</button>`,
  376. conf: `
  377. #{title | Configuration} <br/>
  378. <b>Key 按键</b> <br/>
  379. #{confInput | key | leader | 引导}
  380. #{confInput | key | shortcut_toggle | 开关浮窗}
  381. #{confInput | key | shortcut_mark | 标记}
  382. #{confInput | key | shortcut_fill | 填充}
  383. #{confInput | key | shortcut_list | 清单}
  384. #{confInput | key | shortcut_conf | 设置}
  385. #{confInput | key | shortcut_info | 关于}
  386. #{confApply | key}
  387. `,
  388. listZone: (name, hint) => `
  389. <b>${ name.initialCase() } ${hint}</b>
  390. <ul data-name="${name}"></ul>
  391. `,
  392. list: `
  393. #{title | List}
  394. #{button | dela | 🗑️}
  395. #{button | impt | ⬆️}
  396. <input type="file" value="form" accept=".json"/>
  397. <br/>
  398. #{listZone | here | 本页}
  399. #{listZone | origin | 同源}
  400. #{listZone | else | 其它}
  401. `,
  402. styl: `
  403. /* :: animation */
  404.  
  405. @keyframes melting-sudden {
  406. 0%, 70% { opacity: 1; }
  407. 100% { opacity: 0; }
  408. }
  409. @keyframes melting-fadeout {
  410. 0% { opacity: 1; }
  411. 100% { opacity: 0; }
  412. }
  413. @keyframes melting-fadein {
  414. 0% { opacity: 0; }
  415. 100% { opacity: 1; }
  416. }
  417.  
  418. /* :: root */
  419.  
  420. .WIMF {
  421. position: fixed;
  422. z-index: 1919810;
  423. user-select: none;
  424.  
  425. opacity: 1;
  426. transition: top 1s, right 1s;
  427. transform: scale(.9);
  428.  
  429. }
  430. .WIMF, .WIMF * { /* Note: Disable styles from host page. */
  431. box-sizing: content-box;
  432. border: none;
  433. outline: none;
  434.  
  435. word-wrap: normal;
  436. font-size: inherit;
  437. line-height: 1.4;
  438. }
  439.  
  440. .WIMF-main, .WIMF-text, .WIMF-msg p {
  441. width: 100px;
  442. padding: 0 3px 0 4.5px;
  443.  
  444. border-radius: 12px;
  445. font-size: 12px;
  446. background-color: #fff;
  447. box-shadow: 0 0 4px #aaa;
  448. }
  449.  
  450. /* :: main */
  451.  
  452. .WIMF-main {
  453. position: absolute;
  454. top: 0;
  455. right: 0;
  456. height: 80px;
  457. }
  458.  
  459. .WIMF-main::after { /* Note: A cover. */
  460. position: absolute;
  461. width: 100%;
  462. height: 100%;
  463. top: 0;
  464. left: 0;
  465. pointer-events: none;
  466.  
  467. content: "";
  468. border-radius: 12px;
  469. background-color: black;
  470.  
  471. opacity: 0;
  472. transition: opacity .8s;
  473. }
  474. .WIMF-main.dragging::after {
  475. opacity: .5;
  476. }
  477.  
  478. /* :: cell */
  479.  
  480. .WIMF-mark {
  481. background-color: #ffff81 !important;
  482. }
  483.  
  484. .WIMF-title {
  485. display: block;
  486. text-align: center;
  487. }
  488.  
  489. .WIMF-badge {
  490. margin: 3px 0 2px;
  491. padding: 0 4px;
  492.  
  493. border-radius: 6px;
  494. background-color: #9f9;
  495. box-shadow: 0 0 4px #bbb;
  496. }
  497.  
  498. .WIMF a {
  499. overflow-wrap: anywhere;
  500. color: #0aa;
  501. transition: color .8s;
  502. }
  503. .WIMF a:hover {
  504. color: #0af;
  505. }
  506.  
  507. .WIMF-button {
  508. display: inline-block;
  509. width: 17px;
  510. height: 17px;
  511.  
  512. padding: 2px 3px 3px 3px;
  513. margin: 3px;
  514.  
  515. outline: none;
  516. border: none;
  517. border-radius: 7px;
  518.  
  519. font-size: 12px;
  520. text-align: center;
  521. box-shadow: 0 0 3px #bbb;
  522.  
  523. background-color: #fff;
  524. transition: background-color .8s;
  525. }
  526. .WIMF-button.little {
  527. transform: scale(0.9);
  528. margin: -1px 0;
  529. padding: 0 5px;
  530. border-radius: 3px;
  531. }
  532. .WIMF button:hover, .WIMF button.active {
  533. background-color: #bbb !important;
  534. }
  535. .WIMF-main > .WIMF-button:hover::before { /* Hints. */
  536. position: absolute;
  537. right: 114px;
  538. width: 75px;
  539.  
  540. content: attr(name);
  541. padding: 0 3px;
  542.  
  543. font-size: 14px;
  544. border-radius: 4px;
  545. background-color: #fff;
  546. box-shadow: 0 0 4px #aaa;
  547. }
  548.  
  549. /* :: msg */
  550.  
  551. .WIMF-msg {
  552. position: absolute;
  553. top: 0;
  554. right: 115px;
  555. }
  556.  
  557. .WIMF-msg > p {
  558. margin-bottom: 3px;
  559. }
  560.  
  561. .WIMF-msg > .succeed {
  562. background-color: #9f9;
  563. }
  564. .WIMF-msg > .fail {
  565. background-color: #f55;
  566. }
  567. .WIMF-msg > .confirm {
  568. background-color: #0cf;
  569. }
  570.  
  571. .WIMF-msg > .confirm > span:last-child {
  572. float: right;
  573. }
  574. .WIMF-msg > .confirm > span:last-child > span {
  575. color: #eee;
  576. }
  577. .WIMF-msg > .confirm > span:last-child > span:hover {
  578. color: #eee;
  579. text-decoration: underline;
  580. }
  581.  
  582. /* :: text */
  583.  
  584. .WIMF-text {
  585. position: absolute;
  586. display: none;
  587. top: 85px;
  588. right: 0;
  589. height: 300px;
  590.  
  591. overflow: -moz-scrollbars-none;
  592. overflow-y: scroll;
  593. -ms-overflow-style: none;
  594. }
  595. .WIMF-text::-webkit-scrollbar {
  596. display: none;
  597. }
  598.  
  599. .WIMF-text > div {
  600. padding-bottom: 5px;
  601. }
  602. .WIMF-text input:not([type]),
  603. .WIMF-text input[type=text], .WIMF-text input[type=file] {
  604. width: 95px;
  605.  
  606. margin: 3px 0;
  607. padding: 1px 2px;
  608.  
  609. border: none;
  610. border-radius: 3px;
  611. outline: none;
  612.  
  613. box-shadow: 0 0 3px #aaa;
  614. }
  615.  
  616. .WIMF-text input[type=file]::file-selector-button {
  617. display: none;
  618. }
  619. .WIMF-text input[type=file]::-webkit-file-upload-button {
  620. display: none;
  621. }
  622.  
  623. .WIMF-text button[data-zone] {
  624. margin: 3px 0;
  625. padding: 0 5px;
  626.  
  627. border-radius: 3px;
  628. box-shadow: 0 0 3px #aaa;
  629.  
  630. background-color: #fff;
  631. transition: background-color .8s;
  632. }
  633.  
  634. [data-name=list] li > div {
  635. display: none;
  636. }
  637. [data-name=list] li:hover > div {
  638. display: inline-block;
  639. }
  640. `
  641. }
  642. UI.M = new Proxy(s =>
  643. s.replace(/#{(.*?)}/g, (_, s) => {
  644. const [ k, ...a ] = s.split(/ *\| */), m = UI.meta[k]
  645. if (a.length && typeof m === "function") return m(...a)
  646. return m
  647. }), { get: (t, n) => t(UI.meta[n]) }
  648. )
  649.  
  650. UI.$btn = (n, p) => (p ? p.children : $).call(p, `.WIMF-button[name^=${n}]`)
  651. UI.action = {
  652. mark() {
  653. const $b = UI.$btn("mark")
  654. if ($b.is(".active")) {
  655. $(".WIMF-mark").removeClass("WIMF-mark")
  656. UI.msg([ "表单高亮已取消。", "Form highlight is canceled." ])
  657. }
  658. else {
  659. scan({
  660. root: "body",
  661. hl: $i => $i.addClass("WIMF-mark")
  662. })
  663. UI.msg([ "表单已高亮。", "Forms are highlighted." ])
  664. }
  665. $b.toggleClass("active")
  666. },
  667. fill() {
  668. let c = 0, c_e = 0; for (let o of op.here) {
  669. const $i = $(o.path)
  670. if (! $i.length) {
  671. c_e++
  672. continue
  673. }
  674. switch (o.type) {
  675. case "text":
  676. $i.val(o.val)
  677. break
  678. case "radio":
  679. case "checkbox":
  680. // Hack: HTMLElement:.click is stabler than $.click sometimes.
  681. // If user clicks <label> instead of <input>, we also do that.
  682. if (o.label) $(o.label)[0].click()
  683. else $i[0].click()
  684. break
  685. default:
  686. UI.msg([ `未知表单项类型 "${o.type}"。`, `Unknown form field type "${o.type}".` ],
  687. { type: "fail" })
  688. }
  689. c++
  690. }
  691. if (c_e) UI.msg([ `有 ${c_e} 个表单项无法定位。`, `${c_e} form field(s) is unable to be located.` ],
  692. { type: "fail" })
  693. UI.msg([ `已填充 ${c} 个表单项。`, `${c} form field(s) is filled.` ])
  694. },
  695. list() {
  696. UI.text.show("list")
  697.  
  698. const o = op.all, z$ = {}, $t = UI.$text()
  699. for (let i of [ "here", "origin", "else" ])
  700. z$[i] = $t.children(`ul[data-name="${i}"]`).html("")
  701. function checkEmpty() {
  702. for (let $i of Object.values(z$)) if (! $i.children().length) $i.html("-")
  703. }
  704.  
  705. let $i; for (let i in o) {
  706. const u = new URL(i), info = {
  707. URL: u, op: o[i], time: + new Date()
  708. }
  709. if (u.origin === location.origin)
  710. if (u.pathname === location.pathname) $i = z$.here;
  711. else $i = z$.origin
  712. else $i = z$.else
  713. const $_ = $(UI.M(`
  714. <li>
  715. #{link | ${u}} <br/> #{badge | ${o[u].length}}
  716. <div>
  717. #{buttonLittle | dele | 🗑️}
  718. <a href="${
  719. URL.createObjectURL(new Blob([ JSON.stringify(info) + "\n" ], { type: "application/json" }))
  720. }" download="WIMF-form-${ Math.random.token(8).toUpperCase() }.json">
  721. #{buttonLittle | expt | ⬇️}
  722. </a>
  723. </div>
  724. </li>
  725. `)).appendTo($i)
  726. const $b = $_.children("div")
  727.  
  728. UI.$btn("dele", $b).on("click", () => {
  729. delete o[$_.children("a").attr("href")]
  730. ts.operation = o
  731. scan() // Note: Update `o` in `work` closure.
  732. $_.remove()
  733. checkEmpty()
  734. UI.msg([ "已删除一个表单。", "The form is deleted." ])
  735. })
  736. }
  737. checkEmpty()
  738.  
  739. const $f = $t.children("input[type=file]")
  740. UI.$btn("impt", $t).one("click", async() => {
  741. const file = $f[0].files[0]
  742. if (! file) {
  743. UI.msg([ "请先选择需导入的文件。", "Please choose a file to import first." ],
  744. { type: "fail" })
  745. return
  746. }
  747. if (! file.name.endsWith(".json")) {
  748. UI.msg([ "文件格式应为 JSON。", "The file format should be JSON." ],
  749. { type: "fail" })
  750. return
  751. }
  752. const info = JSON.parse(await file.text())
  753. op[info.URL] = info.op
  754. UI.action.list() // Todo: Optmize this. Too expensive.
  755. UI.msg([ "所上传的表单数据已导入。", "The form data you uploaded is imported." ])
  756. })
  757. UI.$btn("dela", $t).one("click", () => {
  758. UI.msg([ "确定要删除所有表单吗?", "Are you sure to delete all forms?" ],
  759. { type: "confirm" })(y => {
  760. if (! y) return
  761. ts.operation = {}
  762. UI.action.list()
  763. UI.msg([ "表单数据已全部删除。", "All form data is deleted." ])
  764. })
  765. })
  766. },
  767. conf() {
  768. UI.text.show("conf")
  769.  
  770. const $A = $(".WIMF-text button")
  771. for (let i = 0; i < $A.length; i++) {
  772. const $b = $($A[i]),
  773. zone = $b.data("zone"),
  774. $t = $b.prevAll(`input[type=text][name^=${zone}_]`),
  775. c_b = {
  776. key: shortcut
  777. }
  778.  
  779. function map(it) {
  780. for (let j = $t.length - 1; j >= 0; j--) {
  781. const $e = $($t[j]), sp = $e.attr("name")
  782. it($e, sp)
  783. }
  784. }
  785. map(($_, sp) => $_.val(ts[sp]))
  786. $b.one("click", () => {
  787. map(($_, sp) => { ts[sp] = $_.val() })
  788. if (c_b[zone]) c_b[zone]()
  789. UI.msg([ `设置块 ${zone} 已应用。`, `Configuration zone ${zone} is applied.` ])
  790. })
  791. }
  792. },
  793. info() {
  794. UI.text.show("info")
  795. },
  796. quit() {
  797. $(".WIMF").melt("fadeout", 1.5)
  798. ts.window_state = "close"
  799. },
  800. back() {
  801. $(".WIMF-text").hide()
  802. UI.$btn("back").attr("name", "quit 退出")
  803. UI.text.hide()
  804. }
  805. }
  806. UI.$text = (n = UI.text.active) => $(`.WIMF-text > [data-name=${n}]`)
  807. UI.text = {
  808. hide: () => {
  809. UI.$btn(UI.text.active).removeClass("active")
  810. $(".WIMF-text").hide().children(`[data-name=${UI.text.active}]`).hide()
  811. },
  812. show: n => {
  813. UI.text.hide()
  814. UI.$btn(UI.text.active = n).addClass("active")
  815. const $t = $(".WIMF-text").show(), $p = $t.children(`[data-name=${n}]`)
  816. if ($p.length) $p.show()
  817. else $t.append(`<div data-name="${n}">${UI.M[n]}</div>`)
  818. UI.$btn("quit").attr("name", "back 返回")
  819. }
  820. }
  821. UI.msg = (m, { type, alive } = { type: "succeed" }) => {
  822. // Todo: English, `m[1]`.
  823. const $m = $(`<p class="${type}">${ m[0] }</p>`).prependTo($(".WIMF-msg"))
  824. if (type === "confirm") {
  825. const $c = $(`<span><span>Yes</span> | <span>No</span></span>`).appendTo($m)
  826. return f => $c.children().on("click", function() {
  827. f($(this).html() === "Yes")
  828. $m.melt("fadeout", 1, "remove")
  829. })
  830. // Note: Since it returns here, we needn't set `alive`.
  831. }
  832. if (! alive) $m.melt("sudden", 3, "remove")
  833. }
  834. UI.move = (t, r) => {
  835. if (t != null) ts.window_top = Math.max(t, 0)
  836. if (r != null) ts.window_right = Math.max(r, 0)
  837. $(".WIMF").css("top", ts.window_top + "px").css("right", ts.window_right + "px")
  838. }
  839. UI.init = () => {
  840. GM_addStyle(UI.M.styl)
  841. $("body").after(UI.M.html)
  842.  
  843. const $r = $(".WIMF"), $m = $(".WIMF-main"), $w = $(unsafeWindow)
  844. if (ts.window_state === "close") $r.hide()
  845. UI.move()
  846.  
  847. $(".WIMF-button").on("click", function() {
  848. UI.action[$(this).attr("name").split(" ")[0]]()
  849. })
  850.  
  851. $m.on("mousedown", e => {
  852. const { clientX: x0, clientY: y0 } = e
  853.  
  854. $w.on("mouseup", finish)
  855.  
  856. let c = false
  857. const t_f = setTimeout(finish, 1800),
  858. t_c = setTimeout(() => {
  859. c = true
  860. $m.addClass("dragging")
  861. }, 200) // Note: Differentiate from clickings.
  862.  
  863. function finish(f) {
  864. clearTimeout(t_f); clearTimeout(t_c)
  865. if (c && f) {
  866. const { clientX: x1, clientY: y1 } = f,
  867. dx = x1 - x0, dy = y1 - y0
  868. UI.move(ts.window_top + dy, ts.window_right - dx)
  869. }
  870. if (c) $m.removeClass("dragging").off("mousemove")
  871. $w.off("mouseup")
  872. }
  873. })
  874. }
  875.  
  876. $(function init() {
  877. UI.init()
  878.  
  879. shortcut()
  880.  
  881. let a; const t_s = setImmediateInterval(() => {
  882. let [ ,b ] = scan()
  883. if (b > 0 && b === a) clearInterval(t_s)
  884. a = b
  885. }, 1000)
  886. setTimeout(() => clearInterval(t_s), 10 * 1000)
  887. // Note: Some pages' `onload` goes before really loading (by frames).
  888. })
  889.  
  890. expose({ ts, op, UI })
  891.  

QingJ © 2025

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