Greasy Fork is available in English.

WhereIsMyForm

管理你的表单,不让他们走丢。适用场景:问卷,发帖,……

As of 08.11.2020. See ბოლო ვერსია.

// ==UserScript==
// @name         WhereIsMyForm
// @namespace    https://github.com/ForkFG
// @version      0.2
// @description  管理你的表单,不让他们走丢。适用场景:问卷,发帖,……
// @author       ForkKILLET
// @match        *://*/*
// @noframes
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @require      https://code.jquery.com/jquery-1.11.0.min.js
// ==/UserScript==

function Throw(msg, detail) {
    msg = `[WIMF] ${msg}`
    arguments.length === 2
        ? console.error(msg + "\n%o", detail)
        : console.error(msg)
}

function Dat(getter, setter) {
    return function dat(opt) {
        for (let n in opt) {
            Object.defineProperty(dat, n, {
                get: () => getter(n),
                set: val => setter(n, val)
            })
            if (! dat[n]) dat[n] = opt[n]
        }
    }
}

const ls = Dat(
    n => JSON.parse(unsafeWindow.localStorage.getItem("WIMF-" + n)),
    (n, v) => unsafeWindow.localStorage.setItem("WIMF-" + n, JSON.stringify(v))
)
const ts = Dat(
    n => GM_getValue(n),
    (n, v) => GM_setValue(n, v)
)

$.fn.extend({
    path() {
        // Note: Too strict. We need a smarter path.
        //       It doesn't work on dynamic pages sometimes.
        return (function _path(e, p = "", f = true) {
            if (! e) return p
            const $e = $(e), t = e.tagName.toLowerCase()
            let pn = t
            if (e.id) pn += `#${e.id}`
            if (e.name) pn += `[name=${e.name}]`
            if (! e.id && $e.parent().children(t).length > 1) pn += `:nth-of-type(${
                $e.prevAll(t).length + 1
            })`
            return _path(e.parentElement, pn + (f ? "" : `>${p}`), false)
        })(this[0])
    },
    one(event, func) {
        return this.off(event).on(event, func)
    },
    forWhat() {
        if (! this.is("label")) return null
        let for_ = this.attr("for")
        if (for_) return $(`#${for_}`)
        for (let i of [ "prev", "next", "children" ]) {
            let $i = this[i]("input[type=checkbox]")
            if ($i.length) return $i
        }
        return null
    }
})

function scan(hl) {
    const op = ls.op

    const $t = $("input[type=text],textarea")
    $t.one("change.WIMF", function() {
        const $_ = $(this), path = $_.path(), val = $_.val()
        let f = true; for (let i in op) {
            if (op[i].type === "text" && op[i].path === path){
                op[i].val = val
                f = false; break
            }
        }
        if (f) op.push({ path, val, type: "text" })
        ls.op = op
    })

    const $r = $("input[type=radio],label")
    $r.one("click.WIMF", function() {
        let $_ = $(this)
        let path = $_.path(), label
        if ($_.is("label")) {
            label = path
            $_ = $_.forWhat()
            path = $_.path()
        }
        if (! $_.is("[type=radio]")) return

        let f = true; for (let i in op) {
            if (op[i].type === "radio") {
                if (op[i].path === path){
                    f = false; break
                }
                // Note: Replace the old choice.
                if ($(op[i].path).attr("name") === $_.attr("name")) {
                    op[i].path = path
                    f = false; break
                }
            }
        }
        if (f) op.push({ path, label, type: "radio" })
        ls.op = op
    })

    const $c = $("input[type=checkbox],label")
    $c.one("click.WIMF", function() {
        let $_ = $(this)
        let path = $_.path(), label
        if ($_.is("label")) {
            label = path
            $_ = $_.forWhat()
            path = $_.path()
        }
        if (! $_.is("[type=checkbox]")) return

        let f = true; for (let i in op) {
            if (op[i].type === "checkbox") {
                if (op[i].path === path){
                    f = false; break
                }
            }
        }
        if (f) op.push({ path, label, type: "checkbox" })
        ls.op = op
    })

    if (typeof hl === "function") for (let $i of [ $t, $r, $c ]) hl($i)
}

const UI = {
    init() {
        GM_addStyle(`
.WIMF {
    position: fixed;
    z-index: 1919810;
    top: 3px;
    right: 3px;
}
.WIMF, .WIMF * {
    box-sizing: content-box;
}
.WIMF-main, .WIMF-text {
    position: absolute;

    padding: 0 3px 0 4.5px;

    border-radius: 12px;
    font-size: 12px;
    background-color: #fff;
    box-shadow: 0 0 4px #aaa;
}
.WIMF-main {
    top: 0;
    right: 0;
    width: 100px;
    height: 80px;
}
.WIMF-title {
    display: block;
    font-size: 12px;
    text-align: center;
    transform: scale(0.9)
}
.WIMF-button {
    display: inline-block;
    width: 17px;
    height: 17px;

    padding: 2px 3px 3px 3px;
    margin: 3px;

    border-radius: 7px;
    font-size: 12px;
    text-align: center;
    box-shadow: 0 0 3px #bbb;

    background-color: #fff;
    transition: background-color .8s;
}
.WIMF-button:hover {
    background-color: #bbb;
}
.WIMF-button:hover::before {
    position: absolute;
    right: 114px;
    width: 75px;

    content: attr(name);
    padding: 0 3px;

    font-size: 14px;
    border-radius: 4px;
    background-color: #fff;
    box-shadow: 0 0 4px #aaa;
}
.WIMF-mark {
    background-color: #ffff81;
}
.WIMF-text {
    display: none;
    top: 85px;
    right: 0;
    width: 100px;
    height: 300px;
}
.WIMF-text a {
    overflow-wrap: anywhere;
}
`)
        $("body").after(`
<div class="WIMF">
    <div class="WIMF-main">
        <b class="WIMF-title">WhereIsMyForm</b>
        <span class="WIMF-button" name="mark 标记">🔍</span>
        <span class="WIMF-button" name="fill 填充">📃</span>
        <span class="WIMF-button" name="rset 清存">🗑️</span>
        <span class="WIMF-button" name="conf 设置">⚙️</span>
        <span class="WIMF-button" name="info 关于">ℹ️</span>
        <span class="WIMF-button" name="quit 退出">❌</span>
    </div>
    <div class="WIMF-text"></div>
</div>
`)
        $(".WIMF-button").on("click", function() {
            UI[this.getAttribute("name").split(" ")[0]]()
        })
    },
    text(h) {
        let $t = $(".WIMF-text")
        $t.show().html(h)
        $(".WIMF-button[name^=quit]").attr("name", "back 返回")
    },

    mark() {
        scan($i => $i.addClass("WIMF-mark"))
    },
    fill() {
        for (let o of ls.op) {
            const $i = $(o.path)
            if (! $i.length) Throw("Form path not found")
            switch (o.type) {
                case "text":
                    $i.val(o.val)
                    break
                case "radio":
                case "checkbox":
                    // Hack: HTMLElement:.click is stabler than $.click sometimes.
                    //       If user clicks <label> instead of <input>, we also do that.
                    if (o.label) $(o.label)[0].click()
                    else $i[0].click()
                    break
                default:
                    Throw("Unknown form type.")
            }
        }
    },
    rset() {
        ls.op = []
    },
    conf() {
        UI.text(`
<b class="WIMF-title">Configuration</b> <br/>
<i>Todo... 施工中……</i>
`)
    },
    info() {
        UI.text(`
<b class="WIMF-title">Infomation</b> <br/>
<p>管理你的表单,不让他们走丢 <br/>
    <i>-- ForkKILLET</i>
</p> <br/>
<br/>
<p>华东师大二附中“创意·创新·创造”大赛 <br/>
    <i>-- 刘怀轩 东昌南校 初三2班
</p> <br/>
<br/>
<p>可用的测试页面:</p> <a href="https://www.wjx.cn/newsurveys.aspx">https://www.wjx.cn/newsurveys.aspx</a>
`)
    },
    quit() {
        $(".WIMF-main").hide()
    },
    back() {
        $(".WIMF-text").hide()
        $(".WIMF-button[name^=back]").attr("name", "quit 退出")
    }
}

$(function () {
    ls({
        op: []
    })
    UI.init()
    scan()
})