Greasy Fork is available in English.
快速填写预设文本到输入框,适用于所有https网站。
当前为
// ==UserScript==
// @name 剪贴板 FillText
// @namespace http://tampermonkey.net/
// @version 1.6
// @description 快速填写预设文本到输入框,适用于所有https网站。
// @author Unitiny
// @match https://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant unsafeWindow
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
let online = true;
let global = {};
const flex = {
bs: "space-between", start: "flex-start", end: "flex-end", center: "center", column: "column", row: "row"
};
(function () {
global = getGlobal()
loadResource()
initStyle()
setInputDom()
operationBoard()
// saveGlobalInterval()
})();
function getLink() {
let link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0";
return link
}
function loadResource() {
document.head.appendChild(getLink());
}
function initStyle() {
global.style.boardStyle = `
${flexStyle({jc: flex.bs, fd: flex.column})}
position: absolute;
left: 0px;
top: 0px;
width: 330px;
height: 400px;
margin: 10px;
background: white;
border: 5px solid ${themeColor()};
border-radius: 5px;
overflow: auto;
z-index:999;`
global.style.hiddenBoardStyle = `
${flexStyle({})}
position: absolute;
left: 0px;
top: 0px;
width: 40px;
height: 40px;
margin: 10px;
border: 5px solid skyblue;
border-radius: 50%;
overflow: auto;
color: white;
background: skyblue;
z-index:999;
display: none;`
global.style.divStyle = `
${flexStyle({fd: flex.column})}
width: 300px;
height: 400px;
margin: 10px;
border: 1px solid skyblue;
border-radius: 5px;
overflow: auto`
global.style.cateSpanStyle = `
padding: 5px 12px;
width: auto;
height: 35px;
border: 1px solid ${themeColor()};
border-radius: 5px;
background: white;
font-size: 16px;
text-align: center;
line-height: 33px;
margin-right: 10px;`
global.style.spanStyle = `
${flexStyle({jc: flex.center, ai: flex.start, fd: flex.column})}
flex-grow: 0;
flex-shrink: 0;
min-width: 250px;
max-width: 250px;
min-height: 100px;
max-height: 100px;
margin: 10px 20px;
border: 1px solid ${themeColor()};
border-radius: 7px;
height: 70px;
padding: 0 10px 0 10px;`
global.style.pStyle = `
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;`
global.style.operationStyle = `
padding: 0 12px 0 12px;
width: auto;
height: 35px;
border-radius: 5px;
background: #4bb84b;
color: white;
font-size: 16px;
text-align: center;
line-height: 33px;
margin-right: 10px;`
global.style.textArea = `
border: 1px solid ${themeColor()};
border-radius: 5px;
flex-grow: 0;
flex-shrink: 0;
min-width: 250px;
max-width: 250px;
min-height: 100px;
max-height: 100px;
margin: 10px 0 10px 0;`
global.style.iconStyle = `
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;`
global.style.spanSettingStyle = `
width: 100%;
${flexStyle({jc: flex.end})}
`
global.style.displayNone = "display: none;"
global.style.displayFlex = "display: flex;"
global.style.borderRed = "border: 1px solid red;"
global.style.borderSkyblue = "border: 1px solid skyblue;"
}
function themeColor() {
return global.theme ? "skyblue" : "black"
}
//添加前缀来防止与原页面id重复
function prefixStr(str) {
return `${global.prefix}-${str}`
}
function isShow(show) {
return show ? "" : global.style.displayNone
}
function showDom(dom, add = global.style.displayFlex) {
dom.style.cssText = dom.style.cssText.replace(isShow(false), "")
dom.style.cssText += add
}
function hiddenDom(dom) {
dom.style.cssText += isShow(false)
}
function replaceStyle(dom, key, value) {
let list = dom.style.cssText.split(";");
for (let i = 0; i < list.length; i++) {
if (list[i].includes(key + ":")) {
list[i] = `${key}:${value}`
break
}
}
dom.style.cssText = list.join(";")
}
function flexStyle({jc = flex.center, ai = flex.center, fd = flex.row}) {
return `display: flex;
justify-content: ${jc};
align-items: ${ai};
flex-direction: ${fd};`
}
function toBottom() {
global.board.scrollTop = global.board.scrollHeight
}
function moveDom(dom) {
let isDragging = false;
let x, y;
dom.addEventListener("mousedown", function (event) {
isDragging = true;
x = event.x;
y = event.y;
});
dom.addEventListener("mousemove", function (event) {
if (isDragging) {
let offsetX = event.x - x;
let offsetY = event.y - y;
dom.style.left = parseInt(dom.style.left) + offsetX + "px";
dom.style.top = parseInt(dom.style.top) + offsetY + "px";
x = event.x
y = event.y
}
});
dom.addEventListener("mouseup", function () {
isDragging = false;
});
}
function doc() {
alert("快捷键建议Alt开头或纯字母数字,如:Alt+1、qq、11")
}
function containKeys(a, b) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
return aKeys.length >= bKeys.length && bKeys.every(key => aKeys.includes(key));
}
function defaultGlobal() {
return {
id: 0, //global版本号
order: 0,
style: {},
show: true,
theme: true,
curSpan: 0,
curInputDom: 0,
prefix: "fillText",
attributeName: "data-fillText",
inputDoms: [],
boardChildNodes: 3,
curCate: "default",
category: [
{
name: "default",
spansText: randText(),
setting: {
spans: []
}
},
{
name: "常用",
spansText: [],
setting: {
spans: []
}
}
]
}
}
function checkGlobal(data) {
return data && JSON.stringify(data) !== "{}" && containKeys(data, defaultGlobal())
}
function saveGlobalInterval() {
setInterval(function () {
saveGlobal()
}, 1000 * 30)
}
function saveGlobal(g = global) {
console.log("saveGlobal", g)
if(!online) {
return false
}
let lastG = GM_getValue("fillTextGlobal")
if(!checkGlobal(lastG)) {
GM_setValue("fillTextGlobal", g)
return true
}
g.id++
if (checkGlobal(g) && lastG.id < g.id) {
GM_setValue("fillTextGlobal", g)
return true
}
return false
}
function getGlobal() {
if (!online) {
return defaultGlobal();
}
let data = GM_getValue("fillTextGlobal")
if (!checkGlobal(data)) {
saveGlobal(defaultGlobal())
return defaultGlobal()
}
return data
}
function getCurCateVal() {
for (const v of global.category) {
if (v.name === global.curCate) {
return v
}
}
return global.category[0]
}
function showCateInput() {
if (global.board.childNodes.length > global.boardChildNodes + 1) {
return
}
let el = document.createElement("input");
el.placeholder = "请输入内容,按回车添加"
el.style.cssText = global.style.textArea
replaceStyle(el, "border", `1px solid ${themeColor()}`)
el.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
addCategory(event.target.value)
renderBoard(renderSpan())
toBottom()
}
})
global.board.appendChild(el)
toBottom()
}
function addCategory(name) {
if (name.length > 10) {
alert("分类名过长")
return
}
let has = false
global.category.forEach((v, i) => {
if (v.name === name) {
alert("已存在该分类")
has = true
return
}
})
if (has) return
global.category.push({
name: name,
spansText: [],
setting: {
spans: []
}
})
saveGlobal()
}
function delCategory(name) {
let index = 0
global.category.map((v, i) => {
if (v.name === name) {
index = i
}
})
global.category.splice(index, 1)
saveGlobal()
}
function categoryList() {
let list = []
for (let i = 0; i < global.category.length; i++) {
list.push(getCategorySpan(i, global.category[i]))
}
let div = document.createElement("div")
div.setAttribute("id", prefixStr("categoryList"))
div.style.cssText = `${flexStyle({jc: flex.start})}width:80%; margin: 0 20px 10px 0px;`
div.append(...list)
global.board.appendChild(div)
toBottom()
}
function getCategorySpan(i, cate) {
let close = document.createElement("span")
close.style = global.style.iconStyle
close.style.cssText += `
position: absolute;
right: -9px;
top: -14px;`
close.innerText = "×"
close.addEventListener("click", function (event) {
delCategory(cate.name)
let div = document.getElementById(prefixStr("categoryList"))
div.removeChild(div.childNodes[i])
event.stopImmediatePropagation()
})
let span = document.createElement("span")
span.innerText = cate.name
span.style.cssText = global.style.cateSpanStyle
span.style.cssText += `position: relative;`
span.addEventListener("click", function (event) {
global.curSpan = 0
global.curCate = cate.name
renderBoard(renderSpan())
document.getElementById(prefixStr("curCate")).innerText = `当前分类:${global.curCate}`
})
if (i !== 0) span.appendChild(close)
return span
}
function changeBoardHeight(h) {
global.board.style.height = parseInt(global.board.style.height) + h + "px"
}
//设置input节点
function setInputDom() {
let t = setInterval(() => {
let downingKeys = [];
let editKey = "";
let inputDoms = document.querySelectorAll('input');
inputDoms.forEach(function (dom, i) {
dom.setAttribute(global.attributeName, i)
dom.addEventListener("click", function (event) {
let d = event.target
global.curInputDom = d.getAttribute(global.attributeName)
if (global.order === 1) {
d.value = getCurCateVal().spansText[global.curSpan]
}
})
dom.addEventListener("keydown", function (event) {
//快捷键模式,按下Alt开始
if (event.key === "Alt") {
downingKeys = []
downingKeys.push(event.key)
editKey = event.key
return
}
if (downingKeys.length > 0) {
if (downingKeys.indexOf(event.key) !== -1) return;
downingKeys.push(event.key)
editKey += "+" + event.key
getCurCateVal().setting.spans.map(v => {
if (editKey === v.key) {
dom.value = getCurCateVal().spansText[v.index] || dom.value
event.preventDefault()
downingKeys = []
editKey = ""
}
})
return;
}
//编辑模式
let str = dom.value + event.key
getCurCateVal().setting.spans.map(v => {
if (str === v.key.split("+").join("")) {
dom.value = getCurCateVal().spansText[v.index] || dom.value
event.preventDefault() //阻止写入该字符
}
})
})
dom.addEventListener("keyup", function (event) {
let i = downingKeys.indexOf(event.key)
i !== -1 ? downingKeys.splice(i, 1) : null
if (event.key === "Alt") {
downingKeys = []
editKey = ""
}
})
})
global.inputDoms = inputDoms
//有拿到inputDoms
if (inputDoms.length > 0) {
clearInterval(t)
}
}, 1000)
}
function randText() {
if (online) {
return ["点击输入框,再点击该文本即可自动填写。也可设置快捷键填写。"]
}
let list = []
for (let i = 0; i < Math.round(Math.random() * 10 + 3); i++) {
let s = ""
for (let j = 0; j < Math.round(Math.random() * 50 + 10); j++) {
s += Math.round(Math.random() * 10).toString()
}
list.push(s)
}
return list
}
//输入栏添加span
function showInput() {
if (global.board.childNodes.length > global.boardChildNodes + 1) {
return
}
let el = document.createElement("input");
el.placeholder = "请输入内容,按回车添加"
el.style.cssText = global.style.textArea
replaceStyle(el, "border", `1px solid ${themeColor()}`)
el.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
getCurCateVal().spansText.push(event.target.value)
renderBoard(renderSpan())
toBottom()
saveGlobal()
}
})
global.board.appendChild(el)
toBottom()
}
function getSpanText(index) {
return getCurCateVal().spansText[index]
}
function getSpans() {
let list = []
for (let i = 0; i < getCurCateVal().spansText.length; i++) {
list.push(getSpan(i, getCurCateVal().spansText[i]))
}
return list
}
function getSpan(i, text) {
let span = document.createElement("span")
span.innerHTML = `
<p style="${global.style.pStyle}">${text}</p>
<div style="${global.style.spanSettingStyle}${isShow(getSpanSetting(i).show)}">
<input id="settingInput${i}" style="width: 80%; height: 30px;${isShow(getSpanSetting(i).input)}" type="text" placeholder="请按键设置" readonly>
<span style="${global.style.iconStyle}${isShow(getSpanSetting(i).set)}">settings</span>
<span style="${global.style.iconStyle}${isShow(getSpanSetting(i).finish)}">done</span>
<span style="${global.style.iconStyle}${isShow(getSpanSetting(i).cancel)}">close</span>
</div>
`
span.style.cssText = global.style.spanStyle
replaceStyle(span, "border", `1px solid ${themeColor()}`)
span.addEventListener("click", function (event) {
selectSpan(i)
if (global.order === 0) {
let target = `${global.attributeName}="${global.curInputDom}"`
let element = document.querySelector(`input[${target}]`);
element.value = text
}
})
let childSpan = span.getElementsByTagName("span")
childSpan[0].addEventListener("click", function () {
settingCtl({input: true, set: false, finish: true, cancel: true, index: i})
})
childSpan[1].addEventListener("click", function () {
saveKey(i)
})
childSpan[2].addEventListener("click", function () {
cancel(i)
})
return span
}
function selectSpan(i) {
let spanNodes = getCurCateVal().spanList.childNodes
replaceStyle(spanNodes[global.curSpan], "border", `1px solid ${themeColor()}`)
global.curSpan = i
replaceStyle(spanNodes[global.curSpan], "border", "1px solid red")
}
function delSpan() {
getCurCateVal().spansText.splice(global.curSpan, 1)
let i = 0
getCurCateVal().setting.spans.map((v, index) => {
if (v.index === global.curSpan) {
i = index
}
})
getCurCateVal().setting.spans.splice(i, 1)
global.curSpan = 0
renderBoard(renderSpan())
saveGlobal()
}
function delAllSpan() {
getCurCateVal().spansText = []
renderBoard(renderSpan())
saveGlobal()
}
function renderBoard(spanListDom) {
global.board.replaceChild(spanListDom, global.board.childNodes[1])
let l = global.board.childNodes.length
if (l > global.boardChildNodes) {
for (let i = global.boardChildNodes; i < l; i++) {
global.board.removeChild(global.board.lastChild)
}
}
}
function renderSpan() {
let div = document.createElement("div");
div.setAttribute("id", "textSpanList")
div.style.cssText = flexStyle({fd: flex.column})
div.append(...getSpans())
getCurCateVal().spanList = div
return div
}
function changeOrder() {
let d = document.getElementById(prefixStr("orderOperation"))
d.innerText = d.innerText === "先填后选" ? "先选后填" : "先填后选"
global.order = global.order ? 0 : 1
saveGlobal()
}
function editKey() {
settingCtl({show: !getCurCateVal().setting.show, set: !getCurCateVal().setting.set})
}
function settingCtl({show = true, input = false, set = true, finish = false, cancel = false, index = -1}) {
if (index !== -1) {
//单span控制
let has = false
for (const span of getCurCateVal().setting.spans) {
if (span.index === index) {
span.show = show
span.input = input
span.set = set
span.finish = finish
span.cancel = cancel
has = true
}
}
if (!has) {
getCurCateVal().setting.spans.push({
show: show, input: input, set: set, finish: finish, cancel: cancel, index: index, key: "", editKey: ""
})
}
} else {
//全局控制
getCurCateVal().setting.show = show
getCurCateVal().setting.input = input
getCurCateVal().setting.set = set
getCurCateVal().setting.finish = finish
getCurCateVal().setting.cancel = cancel
getCurCateVal().setting.spans.map(v => {
v.show = show
v.input = input
v.set = set
v.finish = finish
v.cancel = cancel
})
}
renderBoard(renderSpan())
if (input) {
listenKey(index)
}
//阻止冒泡
let event = window.event || arguments.callee.caller.arguments[0];
event.stopPropagation()
}
function listenKey(index) {
let ss = getSpanSetting(index)
let input = document.getElementById("settingInput" + index);
input.focus()
input.value = ss.key
ss.editKey = ss.key
let downingKeys = [] //按下未松开的keys
input.addEventListener("keydown", function (event) {
if (event.key === "Backspace") {
//如果存在,则移除按下中的key
let i = downingKeys.indexOf(event.key)
i !== -1 ? downingKeys.splice(i, 1) : null
let arr = ss.editKey.split("+")
arr.pop()
ss.editKey = arr.join("+")
} else if (!downingKeys.includes(event.key)) {
if (ss.editKey === "") {
ss.editKey += event.key
} else {
ss.editKey += "+" + event.key
}
downingKeys.push(event.key)
}
input.value = ss.editKey
});
input.addEventListener("keyup", function (event) {
if (event.key !== "Backspace") {
let i = downingKeys.indexOf(event.key)
i !== -1 ? downingKeys.splice(i, 1) : null
}
});
let event = window.event || arguments.callee.caller.arguments[0];
event.stopPropagation()
}
function saveKey(index) {
let ss = getSpanSetting(index)
let has = false
getCurCateVal().setting.spans.map(v => {
if (v.key === ss.editKey && v.index !== ss.index) {
alert(`文本 ${getSpanText(v.index)} 已设置该快捷键`)
has = true
}
})
if (!has) {
ss.key = ss.editKey
ss.editKey = ""
settingCtl({index: index})
}
saveGlobal()
}
function cancel(index) {
let ss = getSpanSetting(index)
ss.editKey = ""
settingCtl({index: index})
}
function getSpanSetting(index) {
let set = getCurCateVal().setting.spans.filter((v) => {
return index === v.index
})
return set.length > 0 ? set[0] : getCurCateVal().setting
}
function modeCtl() {
if (global.theme) {
global.theme = false
replaceStyle(global.board, "border", `5px solid ${themeColor()}`)
getCurCateVal().spanList.childNodes.forEach(v => {
replaceStyle(v, "border", `1px solid ${themeColor()}`)
})
return
}
global.theme = true
replaceStyle(global.board, "border", `5px solid ${themeColor()}`)
getCurCateVal().spanList.childNodes.forEach(v => {
replaceStyle(v, "border", `1px solid ${themeColor()}`)
})
saveGlobal()
}
function topOperation() {
let operation = document.createElement("div")
operation.style.width = "100%"
operation.innerHTML = `
<div id="${prefixStr("topOperation")}" style="${flexStyle({jc: flex.end})}margin: 0 20px 10px 0px;">
<span style="${global.style.iconStyle}font-size: 20px;">light_mode</span>
<span style="${global.style.iconStyle}">expand_more</span>
<span style="${global.style.iconStyle}">remove</span>
</div>`
let childSpan = operation.getElementsByTagName("span")
childSpan[0].addEventListener("click", modeCtl)
childSpan[1].addEventListener("click", toBottom)
childSpan[2].addEventListener("click", function () {
boardCtl(false)
})
return operation
}
function bottomOperation() {
let operation = document.createElement("div")
operation.style.width = "100%"
operation.innerHTML = `
<div id="${prefixStr("bottomOperation")}">
<div style="${flexStyle({jc: flex.end})}margin: 0 20px 10px 0px;">
<span style="${global.style.operationStyle}" >快捷键</span>
<span style="${global.style.operationStyle}">添加</span>
<span style="${global.style.operationStyle}background: #ea5c5c;">删除</span>
<span style="${global.style.operationStyle}margin-right: 0px;background: #ea5c5c;">清空</span>
</div>
<div style="${flexStyle({jc: flex.end})}margin: 0 20px 10px 0px;">
<span style="${global.style.operationStyle}">说明</span>
<span style="${global.style.operationStyle}">保存配置</span>
<span id="${prefixStr("orderOperation")}" style="${global.style.operationStyle}margin-right: 0px;">先填后选</span>
</div>
<div style="${flexStyle({jc: flex.end})}margin: 0 20px 10px 0px;">
<span style="${global.style.operationStyle}">⬆</span>
<span style="${global.style.operationStyle}">⬇</span>
<span style="${global.style.operationStyle}">添加分类</span>
<span style="${global.style.operationStyle}margin-right: 0px;">分类列表</span>
</div>
<div style="${flexStyle({jc: flex.end})}margin: 0 20px 10px 0px;">
<span id="${prefixStr("curCate")}" style="${global.style.operationStyle}margin-right: 0px;">当前分类:${global.curCate}</span>
</div>
</div>`
let list = [editKey, showInput, delSpan, delAllSpan, doc, saveGlobal, changeOrder, changeBoardHeight, changeBoardHeight, showCateInput, categoryList]
list.forEach((v, i) => {
if (v.name === "changeBoardHeight") {
operation.getElementsByTagName("span")[i].addEventListener("click", function () {
i % 2 === 0 ? v(40) : v(-40)
})
} else if (v.name === "saveGlobal") {
operation.getElementsByTagName("span")[i].addEventListener("click", function () {
if(saveGlobal()){
alert(`保存成功!`)
} else {
alert(`配置数据太旧,无法保存。请刷新页面后再编辑保存`)
}
})
} else {
operation.getElementsByTagName("span")[i].addEventListener("click", v)
}
})
return operation
}
function board() {
let b = document.createElement("div");
b.style.cssText = global.style.boardStyle
b.setAttribute("id", "fillTextBoard")
b.appendChild(topOperation())
b.appendChild(renderSpan())
b.appendChild(bottomOperation())
global.board = b
return b
}
function hiddenBoard() {
let hb = document.createElement("div")
hb.style.cssText = global.style.hiddenBoardStyle
hb.setAttribute("id", "fillTextHiddenBoard")
hb.innerHTML = `<span style="${global.style.iconStyle}font-size:30px;pointer-events: none;">add</span>`
hb.addEventListener("click", function () {
boardCtl(true)
})
global.hiddenBoard = hb
return hb
}
function boardCtl(show) {
if (show) {
hiddenDom(global.hiddenBoard)
showDom(global.board)
} else {
hiddenDom(global.board)
showDom(global.hiddenBoard)
}
global.show = show
saveGlobal()
}
//操作面板
function operationBoard() {
document.body.appendChild(board())
document.body.appendChild(hiddenBoard())
boardCtl(global.show)
moveDom(global.board)
}