// ==UserScript==
// @name Web漫画アンテナお気に入り管理
// @description d:作者名を読み込む a:作者をお気に入りに追加/削除 e:検索ワード入力 Shift+E:全編集
// @match *://webcomics.jp/*
// @version 0.1.5
// @grant GM_setValue
// @grant GM_getValue
// @grant GM.openInTab
// @grant GM_deleteValue
// @namespace https://greasyfork.org/users/181558
// @require https://code.jquery.com/jquery-3.4.1.min.js
// @downloadURL none
// ==/UserScript==
(function() {
var keyFunc = [];
var INTERVAL = function() { return 5000; }
const isBusy = function() { return Number(pref("busy") || 0) > Date.now() }
const setBusy = function(delay = INTERVAL()) { if (Date.now() + delay > Number(pref("busy") || 0)) pref("busy", Date.now() + delay) }
var scrollForGet = 0;
const V = 0; // 1-3:verbose
let addstyle = {
added: [],
add: function(str) {
if (this.added.some(v => v[1] === str)) return;
var S = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // var S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
var d = Date.now()
var uid = Array.from(Array(12)).map(() => S[Math.floor((d + Math.random() * S.length) % S.length)]).join('')
document.head.insertAdjacentHTML("beforeend", ``);
this.added.push([uid, str]);
return uid;
},
remove: function(str) { // str:登録したCSSでもaddでreturnしたuidでも良い
let uid = this.added.find(v => v[1] === str || v[0] === str)?.[0]
if (uid) {
eleget0(`#${uid}`)?.remove()
this.added = this.added.filter(v => v[0] !== uid)
}
}
}
var db = {};
db.manga = pref("db.manga") || [];
db.favo = pref('db.favo') || [];
db.favhis = pref('db.favhis') || [];
// db.favhis= (Array.isArray(db.favhis)&&db.favhis.length) ? new Map(db.favhis): new Map();
var latestget = Date.now() - INTERVAL()
var busy = 0;
var GF = {}
document.querySelector(`head`).insertAdjacentHTML('beforeend', ``)
String.prototype.autrep = function() { return this.replace(/\([^)]*\)|([^)]*)|原作|作画|漫画|キャラクター|キャタクターデザイン|ネーム|原案|著者|作者|シナリオ|[作|画][\::]|\:|:|・|\,|、|,|\/|/|\+|+|\&|&/gmi, " ").replace(/ +|\s+/gmi, " ").trim() } // gフラグ不可
String.prototype.match0 = function(re) { let tmp = this.match(re); if (!tmp) { return null } else if (tmp.length > 1) { return tmp[1] } else return tmp[0] } // gフラグ不可
String.prototype.sanit = function() { return this.replace(//g, ">").replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, '`') }
String.prototype.esc = function() { return this.replace(/[&<>"'`]/g, match => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' } [match])) }
function adja(place = document.body, pos, html) {
return place ? (place.insertAdjacentHTML(pos, html), place) : null;
}
var JS = (v) => { return JSON.stringify(v) }
var JP = (v) => { return JSON.parse(v) }
var mousex = 0;
var mousey = 0;
var hovertimer
document.addEventListener("mousemove", e => ((mousex = e.clientX), (mousey = e.clientY), (hovertimer = 0), undefined), false)
var keyListen = function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.getAttribute('contenteditable') === 'true') return;
var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key;
var ele = document.elementFromPoint(mousex, mousey);
var sel = (window.getSelection) ? window.getSelection().toString().trim() : ""
if (pushkey(key, ele, sel)) { e.preventDefault(); return false }
}
document.addEventListener('keydown', keyListen, false)
document.addEventListener("mousedown", function(e) { // クリック
var ele = document.elementFromPoint(mousex, mousey);
if (e.button == 0 && ele.dataset.key) {
if (pushkey(ele.dataset.key, ele)) return false
}
})
document.addEventListener("contextmenu", function(e) { // クリック
var ele = document.elementFromPoint(mousex, mousey);
if (ele.dataset.keyr) {
if (pushkey(ele.dataset.keyr, ele)) { e.preventDefault(); return false }
}
})
function storemanga(tit, aut, ele) {
db.manga = pref("db.manga") || []
db.manga = db.manga.filter(v => v.t != tit)
db.manga.push({ t: tit, a: aut })
db.manga = (Array.from(new Set(db.manga.map(v => JSON.stringify(v))))).map(v => JSON.parse(v)) // uniq:オブジェクトの配列→JSON文字列配列→uniq→オブジェクトの配列
pref("db.manga", db.manga)
V >= 3 && console.table(db.manga)
run(ele)
}
function addaut(aut, ele = document) {
if (!aut || aut == "-") return
aut = aut.autrep() // 加工後の作者名で記憶する
db.favo = pref("db.favo") || []
if (!db.favo.includes(aut)) { db.favo.push(aut) } else { db.favo = db.favo.filter(v => v !== aut) }
db.favo = (Array.from(new Set(db.favo.map(v => JSON.stringify(v))))).map(v => JSON.parse(v)) // uniq:オブジェクトの配列→JSON文字列配列→uniq→オブジェクトの配列
pref("db.favo", db.favo)
V >= 3 && console.table(db.favo)
run(ele)
}
var que = {
q: [], //{ele,key}
add: function(ele, key) {
this.q.push({ ele: ele?.closest(".entry"), key: key })
},
do: function() {
V >= 2 && this.q.length && console.table(this.q)
this.q.forEach(v => {
var box = v.ele
var key = v.key
if (!box) { v.stop = 1; return 0 }
var tit = eleget0('div.entry-title>a:first-child', box)?.textContent?.trim()
var desc = eleget0('//span[@class="entry-detail"]/a[1]|.//a[contains(@class,"entry-detail")]', box)
var aut = eleget0('.aut', box)?.dataset?.author;
if (aut == "-") { v.stop = 1; return 0 }
if (aut) aut = decodeURI(aut)
var descurl = desc?.href
if (aut) {
storemanga(tit, aut, box.parentNode)
key == "a" && addaut(aut, box.parentNode)
autsearch()
v.stop = 1;
return 0
}
var q = eleget0('.autq:not(.waiting)', box);
if (q) { q.classList.add("waiting"); }
box.dataset.que = 1
if (descurl && !aut && !box.dataset.wait && Date.now() - latestget > INTERVAL() && !busy && !isBusy()) {
busy = 1;
setBusy();
box.dataset.wait = 1
v.stop = 1
var quee = eleget0('.autq', box)
quee.style.color = "#f0f"
V >= 1 && notify(`${"get:" + tit}\n${(Date.now() - latestget)/1000} sec.`, document.title)
latestget = Date.now()
if (scrollForGet) box.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" })
$.get(descurl).done(got => {
setBusy(INTERVAL() / 2)
busy = 0
//latestget = Date.now()-INTERVAL()/2
delete box.dataset.que;
aut = $('div.comic-info-right div.comic-author', got)?.text()?.replace("作者: ", "").trim() || "-"
V > 1 && notify(`${"done:" + tit}\n${aut}`, document.title)
if (aut) {
storemanga(tit, aut, box.parentNode)
if (key == "a") addaut(aut, box.parentNode)
}
}).fail(err => {
alert(`通信エラー\n${descurl}`)
this.q = []
delete box.dataset.que;
location.reload();
})
autsearch()
}
})
this.q = this.q.filter(v => !v.stop)
},
}
setInterval(() => { que.do() }, 1000)
function pushkey(key, ele = null, sel = "") {
keyFunc.forEach(v => { if (v.key === key) { v.func(ele) } })
if (/^open:/.test(key)) {
window.open(key.replace(/^open:/, ""))
return 1
}
if (key === "e") { // e::
db.manga = pref("db.manga") || []
db.favo = pref('db.favo') || [];
db.favhis = pref('db.favhis') || [];
var favo = [...db.favo]
GF.sorttype = ((GF.sorttype || 0) % 3 + 1)
var [order, finstrfunc] = [
["登録順", a => a.join(" ")],
["abc順", a => a.sort(new Intl.Collator("ja", { numeric: true, sensitivity: 'base' }).compare).join(" ")],
["長さ→abc順", a => a.sort((a, b) => a.length == b.length ? (new Intl.Collator("ja", { numeric: true, sensitivity: 'base' }).compare)(a, b) : a.length > b.length ? 1 : -1).join(" ")]
][GF.sorttype - 1]
var target = (window.getSelection() && window.getSelection().toString().trim()) || (prompt(`お気に入りに登録するキーワードを入力してください\nすでに登録されている文字列を入力するとそれを削除します\n\n現在登録済み(${favo.length}): (${["登録順","abc順","長さ→abc順"][GF.sorttype-1]})\n${finstrfunc([...favo])}\n\n`) || "")?.trim();
target = target?.trim()
if (!target) return;
if (db.favo.includes(target)) {
if (confirm(`『${target}』は既に存在します\n削除しますか?\n`)) {
V && alert(`『${target}』をメモから削除しました`)
db.favo = db.favo.filter(v => v != target)
}
} else {
db.favo.push(target)
}
pref("db.favo", db.favo)
pref("db.manga", db.manga)
pref("db.favhis", db.favhis);
run()
}
if (key === "d" || key == "a") { // d:: a::
let descele = eleget0(".comic-info .aut", ele?.closest("#main")) || ele;
if (key == "a" && descele.dataset.author) { //alert("!");
var aut = decodeURI(descele.dataset.author)
addaut(aut)
autsearch()
return 1
}
que.add(ele, key);
que.do()
return 1
}
if (key === "Shift+E") { // Shift+E::
var tmp = prompt(`作品情報(${db.manga.length}) / お気に入り作者(${db.favo.length})\n全設定値をJSON形式で編集してください\n空欄を入力すれば全削除できます\n先頭の{の前に+を付けると現在のデータに追加(マージ)します\n\n` + JS(db), JS(db))
if (tmp !== null) { // ESCで抜けたのでなければ
try {
if (tmp?.trim()?.match(/^\+|^+/)) {
tmp = tmp?.trim()?.replace(/^\+|^+/, "")?.trim()
db.manga = (pref("db.manga") || []).concat(JSON.parse(tmp || "").manga)
db.favo = (pref('db.favo') || []).concat(JSON.parse(tmp || "").favo)
db.favhis = (pref('db.favhis') || []).concat(JSON.parse(tmp || "").favhis)
tmp = JSON.stringify(db)
}
var dbtmp = JP(tmp || '{"favo":[],"manga":[],"favhis":[]}')
dbtmp.manga = (Array.from(new Set(dbtmp.manga.map(v => JSON.stringify(v))))).map(v => JSON.parse(v)) // uniq:オブジェクトの配列→JSON文字列配列→uniq→オブジェクトの配列
dbtmp.favo = [...new Set(dbtmp.favo)]; // uniq
dbtmp.favhis = (Array.from(new Set(dbtmp.favhis.map(v => JSON.stringify(v))))).map(v => JSON.parse(v)) // uniq:オブジェクトの配列→JSON文字列配列→uniq→オブジェクトの配列
db = dbtmp
pref("db.favo", db.favo || [])
pref("db.manga", db.manga || [])
pref("db.favhis", db.favhis || []);
$("#favpanel").remove()
run();
} catch (e) {
alert(e + "\n入力された文字列がうまくparseできなかったので設定を変更しません\n正しいJSON書式になっているか確認してください");
return false
}
}
return 1
}
//
if (key == "0") { // 0::メモ一覧一括削除画面
db.favhis = pref('db.favhis') || [];
let coll = new Intl.Collator("ja", { numeric: true, sensitivity: 'base' })
var newstr = db.favhis.sort((a, b) => coll.compare(a[1].t, b[1].t)).sort((a, b) => coll.compare(a[1].a, b[1].a)).sort((a, b) => b[1].p - a[1].p)
//var newstr = db.favhis.sort((a, b) => b[1].p - a[1].p)
var words = newstr
$("#favpanel").remove()
end(document.body, `
×(Esc)0:○メモを付けた作品履歴(${words.length})
`)
addstyle.add(`#favtable{margin:0 auto; width:100%;}
#favpanel a{ text-decoration:none; padding-right:0.5em;}
#favpanel td{padding:0 0.5em; max-width:16vw; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#favpanel tr:nth-child(even){background-color:#f0f0f8;}
#favpanel th{background-color:#dde; padding:0.5em 0.5em;}
#favpanel img{max-width:100%;}
#favpanel span.yhmMyMemo{user-select: none !important; -webkit-user-select: none !important; -moz-user-select: none !important; -ms-user-select: none;}
#favpanel .yhmMyMemo{font-size:90%;}
#favpanel .favfiltericon{color: #aa7; font-size:99%; cursor:pointer; float: right; user-select: none !important; -webkit-user-select: none !important; -moz-user-select: none !important; -ms-user-select: none;}
#favpanel .favhissum{ max-width:2vw !important; }
`)
let list = []
words.forEach((w, i) => {
list.push(`
🗑 |
${i+1} |
${w[1].p} |
 |
▼${w[1].a.esc()} |
${w[1].t.esc()} |
${w[1].h} |
${w[1].s.esc()} |
${w[1]?.sum?.esc()||""} |
${gettime("YYYY/MM/DD",(new Date(w[1].o)))} |
`);
});
//
${w[1].t.esc()} |
list = list.join("") + "
";
end(eleget0("#favtable"), list)
function dispTA() { // textareaにテキスト化
let ta = eleget0('textarea#favta');
if (ta) {
ta.style.width = "80%";
ta.style.height = "5em";
ta.value = elegeta('.favhisentry:visible').map(n => `${eleget0('.favhistitle',n)?.textContent||""} ${eleget0('.favhisaut',n)?.textContent||""}\n${eleget0('.favhishref',n)?.href||""}\n`).join("");
}
}
setDragCol()
sortableTH("#favpanel th", dispTA)
dispTA()
eleget0('#favpanel')?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
$('#favpanelclose').one("click", e => $('#favpanel').remove())
GF.filteron = 0;
cbOnce(() => document.addEventListener("click", e => {
if (eleget0('#favpanel') && !e?.target?.closest(`#favpanel`)) {
$('#favpanel').remove()
e.stopPropagation()
e.preventDefault()
return false;
}
let filter = e.target?.dataset?.favfilter; // フィルタ
if (filter) {
e.stopPropagation()
e.preventDefault()
GF.filteron = 1 - GF.filteron;
elegeta('.favhisentry').forEach(e => {
e.style.display = "revert";
//console.log(GF.filteron, e,unescape(filter))
if (GF.filteron && e.textContent.indexOf(unescape(filter)) == -1) {
$(e).fadeOut(222);
setTimeout(() => { e.style.display = "none"; }, 222);
}
})
setTimeout(() => {
e?.target?.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
dispTA()
}, GF.filteron ? 223 : 1)
addstyle.add(`.attra{outline:4px solid #0f0;}`)
e.target.closest("tr")?.classList.add("attra");
setTimeout(() => $('.attra').removeClass("attra"), 1500)
return false;
}
let f = e.target?.dataset?.delfav; // 削除
if (!f) return;
db.favhis = db.favhis.filter(v => v[0] != unescape(f))
pref("db.favhis", db.favhis);
e.target?.closest('tr')?.remove()
}))
cbOnce(() => document.addEventListener("keydown", e => {
if (e.key == "Escape") $('#favpanel').remove()
}))
document.dispatchEvent(new CustomEvent("requestyhm")) //setTimeout(() => requestAnimationFrame(() => document.dispatchEvent(new CustomEvent("requestyhm"))), 0)
}
}
run()
// document.body.addEventListener('AutoPagerize_DOMNodeInserted', function(evt) { run(evt.target); }, false);
document.body.addEventListener('AutoPagerize_DOMNodeInserted', function(evt) { run(eleget0('.list.top', evt.target) || evt.target?.closest(".list.top") || document); }, false);
// タブにフォーカスが戻ったら再実行
window.addEventListener("focus", () => {
db.manga = pref("db.manga") || []
db.favo = pref('db.favo') || [];
db.favhis = pref('db.favhis') || [];
run()
})
// 詳細画面
var aut = $('div.comic-info-right div.comic-author')?.text()?.replace("作者: ", "")?.trim() || "-"
var tit = eleget0('//div/div/div/div[@class="comic-title"]/h2/a[1]')?.textContent?.trim()
if (aut && tit) {
storemanga(tit, aut, document)
}
function autsearch() {
elegeta(".autsearchele").forEach(e => e.remove());
let tmp = pref('db.favo') || [];
if (tmp.length) {
var aut = tmp[0]
var u = `https://webcomics.jp/search?q=${encodeURI(aut.autrep())}`
var u2 = `https://webcomics.jp/search?q=${(aut.autrep())}`
var l = aut != "-" ? `data-keyr="open:${u}"` : ""
var e = adja(eleget0('//div[@id="side"]'), "afterbegin", `
`)?.childNodes[0]
elegeta('#auta,#changeaut').forEach(e => {
e.addEventListener("click", v => {
db.favo = pref('db.favo') || [];
if (!db.favo.length) return
db.favo.push(db.favo.shift())
pref('db.favo', db.favo)
autsearch()
})
e.addEventListener("mouseup", v => {
if (v.button == 0) return
setTimeout(() => {
db.favo = pref('db.favo') || [];
if (!db.favo.length) return
db.favo.push(db.favo.shift())
pref('db.favo', db.favo)
autsearch()
}, 17)
if (v.button != 1) { v.preventDefault(); return false; }
})
e.addEventListener("contextmenu", v => { v.preventDefault(); return false })
})
} else {
var e = adja(eleget0('//div[@id="side"]'), "afterbegin", `
🗊
`)?.childNodes[0]
}
}
function run(node = document) { // run::
autsearch()
elegeta('.autele', node).forEach(v => v.remove())
// 一覧画面
elegeta('.entry', node).forEach(v => {
var title = eleget0('.entry-title>a:first-child', v)?.textContent?.trim()
var aut = db.manga.find(v => v.t === title)?.a
if (aut == "-") {
adja(eleget0('.entry-date', v), "beforeend", `
${aut.sanit()}`)
} else if (aut) {
var memo = db.favo.includes(aut.autrep()) // 加工後の作者名で記憶する
var u = `https://webcomics.jp/search?q=${encodeURI(aut.autrep())}`
var l = aut != "-" ? `data-keyr="open:${u}"` : ""
adja(eleget0('.entry-date', v), "beforeend", `
${memo?"●":"○"}${aut.autrep().sanit()}`)
} else {
adja(eleget0('.entry-date', v), "beforeend", `
?`)
}
})
// 詳細画面
var aut = $('div.comic-info-right div.comic-author')?.text()?.replace("作者: ", "")?.trim() || "-"
var tit = eleget0('//div/div/div/div[@class="comic-title"]/h2/a[1]')?.textContent?.trim()
if (aut && tit) {
if (aut == "-") {
adja(eleget0('.comic-info'), "afterbegin", `
${aut.sanit()}`)
} else if (aut) {
var memo = db.favo.includes(aut.autrep()) // 加工後の作者名で記憶する
var u = `https://webcomics.jp/search?q=${encodeURI(aut.autrep())}`
var l = aut != "-" ? `data-keyr="open:${u}"` : ""
V && notify(aut, memo)
adja(eleget0('.comic-info'), "afterbegin", `
${memo?"●":"○"}${aut.autrep().sanit()}`)
}
}
// 好き履歴保存 0::
moq3(ele => {
if (ele.matches(`.yhmMyMemoO`) || ele.querySelector(`.yhmMyMemoO`)) {
clearTimeout(GF?.favST)
GF.favST = setTimeout(() => {
db.favhis = pref('db.favhis') || [];
const favhis = new Map(db.favhis)
const lastfav = JSON.stringify(db.favhis)
elegeta('div.entry').map(v => { return { box: v, score: elegeta('.entry .yhmMyMemoO', v).reduce((a, b) => a + b?.textContent?.length, 0) } }).filter(v => v.score).forEach(n => {
let t = eleget0('div.entry-title > a:nth-of-type(1) , a.favhistitle', n.box);
let title = t.textContent?.trim();
let author = eleget0('a.autele.aut.autname , .favhisaut', n.box)?.textContent || "";
let site = eleget0('div.entry-site > a , span.favhissite', n.box);
let oldest = favhis.get(title + author)?.o || (new Date().setHours(0, 0, 0, 0));
let i = eleget0('div.entry-thumb > a > img , .favhisimg', n.box)?.src || ""
let detail = eleget0('span.entry-detail > a , a.favhishref', n.box)?.href || "";
let summary = eleget0('.entry-summary', n.box)?.textContent?.trim() || eleget0('.favhissum', n.box)?.textContent?.trim() || favhis.get(title + author)?.sum || "";
if (favhis.get(title)?.d == detail) favhis.delete(title) //タイトル同じで作者名未取得でdetailが同じものは削除=実質上書き
favhis.set(title + author, { t: t.textContent?.trim(), h: t.href, a: author, s: site?.textContent?.trim() || "", i: i, p: n.score, o: oldest, d: detail, sum: summary })
})
if (lastfav != JSON.stringify([...favhis])) {
db.favhis = [...favhis];
pref("db.favhis", [...favhis]);
}
}, 555)
}
})
function moq3(cb, observeNode = document.body) {
let mo = new MutationObserver((m) => {
let eles = [...m.filter(v => v.addedNodes).map(v => [...v.addedNodes]).filter(v => v.length)].flat().filter(v => v.nodeType === 1).forEach(v => cb(v));
})?.observe(observeNode || document.body, { attributes: false, childList: true, subtree: true });
return () => {
mo?.disconnect();
mo = null;
}
}
}
function elegeta(xpath, node = document) {
if (!xpath || !node) return [];
let flag
if (!/^\.?\//.test(xpath)) return /:inscreen$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:inscreen$/, ""))].filter(e => { var eler = e.getBoundingClientRect(); return (eler.top > 0 && eler.left > 0 && eler.left < document.documentElement.clientWidth && eler.top < document.documentElement.clientHeight) }) : /:visible$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:visible$/, ""))].filter(e => e.offsetHeight) : [...node.querySelectorAll(xpath)]
try {
var array = [];
var ele = document.evaluate("." + xpath.replace(/:visible$/, ""), node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
let l = ele.snapshotLength;
for (var i = 0; i < l; i++) array[i] = ele.snapshotItem(i);
return /:visible$/.test(xpath) ? array.filter(e => e.offsetHeight) : array;
} catch (e) { alert(e + "\n" + xpath + "\n" + JSON.stringify(node)); return []; }
}
function eleget0(xpath, node = document) {
if (!xpath || !node) return null;
if (!/^\.?\//.test(xpath)) return /:inscreen$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:inscreen$/, ""))].filter(e => { var eler = e.getBoundingClientRect(); return (eler.top > 0 && eler.left > 0 && eler.left < document.documentElement.clientWidth && eler.top < document.documentElement.clientHeight) })[0] ?? null : /:visible$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:visible$/, ""))].filter(e => e.offsetHeight)[0] ?? null : node.querySelector(xpath.replace(/:visible$/, ""));
try {
var ele = document.evaluate("." + xpath.replace(/:visible$/, ""), node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
return ele.snapshotLength > 0 ? ele.snapshotItem(0) : null;
} catch (e) { alert(e + "\n" + xpath + "\n" + JSON.stringify(node)); return null; }
}
function pref(name, store = null) { // prefs(name,data)で書き込み(数値でも文字列でも配列でもオブジェクトでも可)、prefs(name)で読み出し
if (store === null) { // 読み出し
let data = GM_getValue(name) || GM_getValue(name);
if (data == undefined) return null; // 値がない
if (data.substring(0, 1) === "[" && data.substring(data.length - 1) === "]") { // 配列なのでJSONで返す
try { return JSON.parse(data || '[]'); } catch (e) {
alert("データベースがバグってるのでクリアします\n" + e);
pref(name, []);
return;
}
} else return data;
}
if (store === "" || store === []) { // 書き込み、削除
GM_deleteValue(name);
return;
} else if (typeof store === "string") { // 書き込み、文字列
GM_setValue(name, store);
return store;
} else { // 書き込み、配列
try { GM_setValue(name, JSON.stringify(store)); } catch (e) {
alert("データベースがバグってるのでクリアします\n" + e);
pref(name, "");
}
return store;
}
}
function notify(body, title = "") {
if (!("Notification" in window)) return;
else if (Notification.permission == "granted") new Notification(title, { body: body });
else if (Notification.permission !== "denied") Notification.requestPermission().then(function(permission) {
if (permission === "granted") new Notification(title, { body: body });
});
}
function before(e, html) { e?.insertAdjacentHTML('beforebegin', html); return e?.previousElementSibling; }
function begin(e, html) { e?.insertAdjacentHTML('afterbegin', html); return e?.firstChild; }
function end(e, html) { e?.insertAdjacentHTML('beforeend', html); return e?.lastChild; }
function after(e, html) { e?.insertAdjacentHTML('afterend', html); return e?.nextElementSibling; }
function gettime(_fmt = 'YYYY/MM/DD hh:mm:ss.iii', _dt = new Date()) {
return [
['YYYY', _dt.getFullYear()],
['MM', _dt.getMonth() + 1],
['DD', _dt.getDate()],
['hh', _dt.getHours()],
['mm', _dt.getMinutes()],
['ss', _dt.getSeconds()],
['iii', _dt.getMilliseconds()],
].reduce((s, a) => s.replace(a[0], `${a[1]}`.padStart(a[0].length, '0')), _fmt)
}
function sortableTH(selector = "th", cb = () => {}) { // 重複して何度呼んでも大丈夫
if (!sortableTH?.added?.size) { // 呼ばれたのは初めて
$(document).on("mousedown", selector, function(e) { // ソート
e.target.dataset.order = 1 - (e.target.dataset.order || 0)
let c = e?.target?.cellIndex;
let tablebottom = e.target.closest('tbody')
var table = e.target.closest('table')
let collator = new Intl.Collator("ja", { numeric: true, sensitivity: 'base' })
var trs = Array.from(table.rows).slice(1).sort((a, b) => collator.compare(a.cells[c].textContent, b.cells[c].textContent))
if (e.target.dataset.order == 0) trs.reverse()
trs.forEach(tr => tablebottom.appendChild(tr))
cb();
})
}
if (!sortableTH?.added?.has(selector)) document.head.insertAdjacentHTML("beforeend", ``); // この引数は初めて
sortableTH.added = (sortableTH.added || new Set()).add(selector) // 1度やったのを記憶
}
// Tableの縦罫線をドラッグで動かす
function setDragCol(dragTarget = "td,th", GRABBABLE_WIDTH_PX = 8) {
if (setDragCol?.done) return;
else setDragCol.done = 1; // 1度しかやらない
document.addEventListener('mousedown', startResize, true);
document.addEventListener('mousemove', e => {
let pare = e?.target?.nodeType == 1 && e?.target?.closest(dragTarget)
if (pare && (e.clientX - pare.getBoundingClientRect().right > -GRABBABLE_WIDTH_PX)) {
if (document.body.style.cursor != 'col-resize') document.body.style.cursor = 'col-resize';
} else {
if (!setDragCol.ingrab && document.body.style.cursor != 'default') document.body.style.cursor = 'default'
}
});
function startResize(e) {
let pare = e?.target?.nodeType == 1 && e?.target?.closest(dragTarget)
if (!pare) return;
if (pare?.getBoundingClientRect()?.right - e?.clientX > GRABBABLE_WIDTH_PX) return;
e.preventDefault();
e.stopPropagation();
let startX = e.clientX;
let startWidth = parseFloat(getComputedStyle(pare).getPropertyValue('width'))
setDragCol.ingrab = 1;
document.addEventListener('mousemove', dragColumn);
document.addEventListener('mouseup', endDrag);
return false;
function dragColumn(em) {
//let pare=e.target.closest(dragTarget(e))
if (["TD", "TH"].includes(pare.tagName)) {
[...pare.closest("table").querySelectorAll(`:is(td,th):nth-child(${pare.cellIndex + 1})`)].forEach(cell => {
cell.style.width = `${startWidth + em.clientX - startX}px`;
cell.style.overflowWrap = "anywhere";
//cell.style.whiteSpace = "break-spaces";
cell.style.whiteSpace = "initial";
cell.style.wordWrap = "break-word";
cell.style.minWidth = `0px`;
cell.style.maxWidth = `${Number.MAX_SAFE_INTEGER}px`;
})
} else {
pare.style.resize = "both";
pare.style.overflow = "auto"
pare.style.width = `${startWidth + em.clientX - startX}px`;
pare.style.minWidth = `0px`;
pare.style.maxWidth = `${Number.MAX_SAFE_INTEGER}px`;
}
}
function endDrag() {
document.removeEventListener('mousemove', dragColumn);
document.removeEventListener('mouseup', endDrag);
setDragCol.ingrab = 0;
};
}
}
function cbOnce(cb) { // callbackを初回に呼ばれたときの1回だけ実行し2度め以降はしない ※要ルートブロックに設置
let cbstr = cb.toString();
//console.log(cbOnce.done , cbstr , cbOnce?.done?.has(cbstr))
if (cbOnce?.done?.has(cbstr)) return;
cbOnce.done = (cbOnce.done || new Set()).add(cbstr)
//console.log(cbOnce.done)
cb()
}
})()