Greasy Fork

Greasy Fork is available in English.

WhereIsMyForm

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

当前为 2020-11-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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()
})