// ==UserScript==
// @name 静画5ch風コメント
// @namespace http://tampermonkey.net/
// @version 0.12
// @description 複数コメントしてるユーザーを簡単に見分けられます
// @author cbxm
// @match https://seiga.nicovideo.jp/seiga/*
// @grant GM.xmlHttpRequest
// @run-at document-start
// @license MIT
// ==/UserScript==
//汎用関数
class Util {
static WaitDocumentElement() {
return new Promise(r => {
if (document.documentElement != null) {
return r();
}
window.addEventListener("DOMContentLoaded", () => {
return r();
});
});
}
//https://stackoverflow.com/questions/69368851/type-safe-way-of-narrowing-type-of-arrays-by-length-when-nouncheckedindexedacces
//TypeScriptくんを納得させるための関数
static HasLengthAtLeast(arr, len) {
return arr != null && arr.length >= len;
}
//TypeScriptくんを納得させるための関数
static IsLength(arr, len) {
return arr != null && arr.length == len;
}
//xmlString=未探査部分
static XmlToObj(xmlString) {
const F = (xmlString, obj) => {
//タグを抜き出す
let tagMatchs = null;
while (true) {
tagMatchs = xmlString.match(/<([^>]+)>/); //タグを探す
//タグがないということはそれが値になる
if (tagMatchs == null) {
return xmlString;
}
if (tagMatchs[1]?.[tagMatchs[1].length - 1] == "/") {
xmlString = xmlString.replace(/<[^>]+>([^]*)/, "$1");
}
else {
break;
}
}
if (!Util.HasLengthAtLeast(tagMatchs, 2)) {
return xmlString;
}
const tag = tagMatchs[1];
//タグの内側とその先を抜き出す
const matchChildlen = [];
while (true) {
const matchs = xmlString.match(new RegExp(`^[^<]*<${tag}>([^]+?)<\/${tag}>([^]*)`));
if (matchs == null || !Util.HasLengthAtLeast(matchs, 3)) {
break;
}
matchChildlen.push(matchs[1]);
xmlString = matchs[2];
}
//タグあったのにマッチしなかったおかしい
if (matchChildlen.length == 0) {
return obj;
}
//そのタグが一つしかないとき、オブジェクトになる
if (Util.IsLength(matchChildlen, 1)) {
//子を探す
obj[tag] = F(matchChildlen[0], {});
}
//そのタグが複数あるとき、配列になる
if (matchChildlen.length > 1) {
obj = [];
for (let i = 0; i < matchChildlen.length; i++) {
//子を探す
obj[i] = F(matchChildlen[i], {});
}
}
//兄弟を探す
F(xmlString, obj);
return obj;
};
//初期化で<xml>を取り除く
xmlString = xmlString.replace(/\s*<[^>]+>([^]+)/, "$1");
return F(xmlString, {});
}
static HtmlToDocument(str) {
const parser = new DOMParser();
return parser.parseFromString(str, "text/html");
}
static HtmlToChildNodes(str) {
return this.HtmlToDocument(str).body.childNodes;
}
static HtmlToElement(str) {
return this.HtmlToDocument(str).body.firstElementChild;
}
static HtmlToSVG(s) {
var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
div.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg">' + s + '</svg>';
var frag = document.createDocumentFragment();
while (div.firstChild?.firstChild)
frag.appendChild(div.firstChild.firstChild);
return frag;
}
static Wait(ms) {
return new Promise(r => setTimeout(() => r(null), ms));
}
static WithTimeOut(p, ms) {
return Promise.race([p, this.Wait(ms)]);
}
static async Retry(p, retryCount, wait, predicate) {
for (let i = 0; i < retryCount; i++) {
const result = await p();
if (predicate(result)) {
return result;
}
//console.log("wait...");
await Util.Wait(wait);
}
return null;
}
static async Download(url, name) {
const link = document.createElement("a");
document.body.appendChild(link);
link.download = name;
link.href = url;
link.click();
//すぐに消すと反応しないとか
await this.Wait(100);
document.body.removeChild(link);
}
static GMFetchText(url, optopns) {
//console.log(url);
return new Promise(r => {
GM.xmlHttpRequest({
url: url,
...optopns,
onload: (response) => {
r(response.responseText);
},
onerror: (e) => {
console.error(e);
r("");
}
});
});
}
static Unique(array) {
return Array.from(new Set(array));
}
static IsElementInViewport(el) {
var rect = el.getBoundingClientRect();
return (rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth));
}
static SetCookie(key, value, option = "") {
window.document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value) + "; " + option;
}
static SetCookieKeyAndValue(keyAndValue) {
window.document.cookie = keyAndValue;
}
static GetAllCookie() {
const cookies = window.document.cookie.split(";").map(c => decodeURI(c).replace(/^\s/, ""));
let keyVal = [];
for (let i = 0; i < cookies.length; i++) {
//cookies[i]=cookies[i].replace(/^\s/,""); ???
const s = cookies[i].match(/^(.*?)=(.*?)$/);
if (Util.HasLengthAtLeast(s, 3)) {
keyVal.push({ key: s[1], val: s[2] });
}
}
return keyVal;
}
static GetCookie(key) {
const cookies = window.document.cookie.split(";").map(c => decodeURI(c).replace(/^\s/, ""));
for (var c of cookies) {
if (RegExp(key).test(c)) {
return c;
}
}
return null;
}
static GetCookieVal(key) {
const cookies = window.document.cookie.split(";").map(c => decodeURI(c).replace(/^\s/, ""));
for (var c of cookies) {
const matched = c.match(`${key}=([^]*)`);
if (Util.HasLengthAtLeast(matched, 2)) {
return matched[1];
}
}
return null;
}
static DeleteCookie(key) {
window.document.cookie = encodeURI(key) + "=; max-age=0";
}
}
;
class MicroDOMTask {
constructor() {
this.isRun = false;
this.observer = null;
this.tasks = [];
this.element = null;
}
AddTask(task) {
this.tasks.push(task);
}
Observe(element, callback) {
this.element = element;
if (this.observer == null) {
this.observer = new IntersectionObserver((entrys, o) => {
for (const e of entrys) {
if (e.isIntersecting) {
callback();
o.disconnect();
return;
}
}
});
}
this.observer.observe(element);
}
Start() {
if (this.element instanceof HTMLElement && this.element.hidden) {
return false;
}
if (!this.isRun) {
this.isRun = true;
this.observer?.disconnect();
for (const t of this.tasks) {
t();
}
return true;
}
return false;
}
}
class BigDOMTask {
constructor() {
this.taskList = [];
}
AddMicroTask(task, observeElement) {
if (observeElement != undefined) {
//task.Observe(observeElement, () => this.taskList.unshift(task)); //配列の先頭に追加
task.Observe(observeElement, () => {
this.taskList.unshift(null);
requestAnimationFrame(() => task.Start());
}); //次のでやる
//task.Observe(observeElement, () => task.Start()); //すぐやる
//それぞれ挙動が違って面白い・・・いや面白くはない!
}
this.taskList.push(task);
}
Start() {
if (this.taskList.length > 0) {
requestAnimationFrame(n => this.ProcessTask.call(this, n));
}
}
ProcessTask(taskStartTime) {
while (true) {
const t = this.taskList.shift();
if (t == undefined) {
break;
}
if (t.Start()) {
if (performance.now() - taskStartTime > 1) {
break;
}
}
}
if (this.taskList.length > 0) {
requestAnimationFrame(n => this.ProcessTask.call(this, n));
}
}
}
//2分探索して検索、挿入ができる
class SortedArray extends Array {
constructor(array) {
super(...(array == undefined) ? [] :
Array.isArray(array) ? array :
[array]);
this.bsNearestCache = { cacheId: -1, nearest: -1 };
if (Array.isArray(array) && !this.isSorted()) {
this.sortOrder();
}
}
isSorted() {
for (let i = 0; i < this.length - 1; i++) {
if (this[i].id > this[i + 1].id) {
return false;
}
}
return true;
}
sortOrder() {
Array.prototype.sort.call(this, (a, b) => a.id - b.id); //昇順
}
//破壊的ソートさせない
sort(compareFn) {
if (compareFn != undefined) {
console.warn("ソートさせないぞ");
}
this.sortOrder();
return this;
}
//非破壊のこっちでやってね!
sortNonDestructive(compareFn) {
return Array.from(this).sort(compareFn);
}
binarySearchNearest(id) {
if (this.bsNearestCache.cacheId == id &&
this.bsNearestCache.nearest != -1) {
return this.bsNearestCache.nearest;
}
let lower = 0;
let upper = this.length;
while (lower < upper) {
const mid = Math.floor(lower + (upper - lower) / 2);
if (this[mid].id < id) {
lower = mid + 1;
}
else {
upper = mid;
}
}
this.bsNearestCache.cacheId = id;
this.bsNearestCache.nearest = lower;
return lower;
}
binarySearchIndex(id) {
const nearest = this.binarySearchNearest(id);
if (nearest in this && this[nearest].id == id) {
return nearest;
}
else {
return -1;
}
}
binarySearch(id) {
const index = this.binarySearchIndex(id);
if (index == -1) {
return undefined;
}
else {
return this[index];
}
}
// 上書き・挿入
insert(value) {
const nearest = this.binarySearchNearest(value.id);
if (nearest in this && this[nearest].id == value.id) {
this[nearest] = value;
}
else {
this.splice(nearest, 0, value);
}
return this[nearest];
}
toJSON() {
return [...this];
}
//最初突合せてダメなら検索して見つかるかを返す。1つでも見つからないとfalse
IsMatingOk(ids) {
let i = 0;
//正引きで突合せていく
for (; i < ids.length; i++) {
if (ids[i] != this[i]?.id) {
break;
}
}
for (; i < ids.length; i++) {
if (this.binarySearch(ids[i]) == undefined) {
break;
}
}
return i == ids.length;
}
//最初突合せてダメなら検索した結果の配列を返す。1つでも見つからないとnull
GetMatingList(ids) {
const list = [];
let i = 0;
//正引きで突合せていく
for (; i < ids.length; i++) {
if (ids[i] != this[i]?.id) {
break;
}
list.push(this[i]);
}
for (; i < ids.length; i++) {
const t = this.binarySearch(ids[i]);
if (t == undefined) {
break;
}
list.push(t);
}
return i == ids.length ? list : null;
}
}
;
;
//MutationObserver使った便利関数
class Observer {
static Wait(predicate, parent = document, option = null) {
return new Promise(r => {
if (option == null) {
option = {};
}
if (option.childList == undefined && option.attributes == undefined && option.characterData == undefined) {
option.childList = true;
option.subtree = true;
}
const mutationObserver = new MutationObserver((mrs) => {
if (predicate(mrs)) {
mutationObserver.disconnect();
r(mrs);
return;
}
});
mutationObserver.observe(parent, option);
});
}
;
static WaitAddedNodes(predicate, parent, option = null) {
return new Promise(r => {
if (option == null) {
option = {};
}
if (option.childList == undefined && option.attributes == undefined && option.characterData == undefined) {
option.childList = true;
option.subtree = true;
}
const mutationObserver = new MutationObserver(async (mrs) => {
//console.log(document.head.innerHTML);
//console.log(document.body.innerHTML);
const result = [];
for (let node of mrs) {
//console.log(added);
for (let i = 0; i < node.addedNodes.length; i++) {
result.push(...await predicate(node.addedNodes[i]));
}
}
if (result.length != 0) {
mutationObserver.disconnect();
r(result);
return;
}
});
mutationObserver.observe(parent, option);
});
}
;
static WaitAddedNode(predicate, parent, option = null) {
return new Promise(r => {
if (option == null) {
option = {};
}
if (option.childList == undefined && option.attributes == undefined && option.characterData == undefined) {
option.childList = true;
option.subtree = true;
}
if (option.childList == undefined && option.attributes == undefined && option.characterData == undefined) {
option.childList = true;
option.subtree = true;
}
const mutationObserver = new MutationObserver(async (mrs) => {
//console.log(document.head.innerHTML);
//console.log(document.body.innerHTML);
for (let node of mrs) {
//console.log(added);
for (let i = 0; i < node.addedNodes.length; i++) {
const ret = await predicate(node.addedNodes[i]);
if (ret != null) {
mutationObserver.disconnect();
r(ret);
return;
}
}
}
});
mutationObserver.observe(parent, option);
});
}
;
static async DefinitelyGetElementById(id, parent = document.documentElement, option = null) {
if (!(option?.doNotNormalCheck ?? false)) {
const e = document.getElementById(id);
if (e != null) {
return e;
}
}
return this.WaitAddedNode(e => (e instanceof Element && e.id == id) ? e : null, parent, option);
}
//getElementsByClassNameをつかうけど単体
static async DefinitelyGetElementByClassName(className, parent = document.documentElement, option = null) {
if (!(option?.doNotNormalCheck ?? false)) {
const e = parent.getElementsByClassName(className)[0];
if (e != null) {
return e;
}
}
return this.WaitAddedNode(e => {
if (e instanceof Element) {
if (e.classList.contains(className)) {
return e;
}
if (option?.isDeepSearch ?? false) {
const c = e.getElementsByClassName(className);
if (c.length != 0) {
return c[0];
}
}
}
return null;
}, parent, option);
}
//getElementsByTagNameをつかうけど単体
static async DefinitelyGetElementByTagName(tagName, parent = document.documentElement, option = null) {
tagName = tagName.toUpperCase();
if (!(option?.doNotNormalCheck ?? false)) {
const e = parent.getElementsByTagName(tagName)[0];
if (e != null) {
return e;
}
}
return this.WaitAddedNode(e => {
if (e instanceof Element) {
if (e.tagName == tagName) {
return e;
}
if (option?.isDeepSearch ?? false) {
const c = e.getElementsByTagName(tagName);
if (c.length != 0) {
return c[0];
}
}
}
return null;
}, parent, option);
}
static async DefinitelyGetElementsByClassName(className, parent = document.documentElement, option = null) {
if (!(option?.doNotNormalCheck ?? false)) {
const e = parent.getElementsByClassName(className);
if (e.length != 0) {
return Array.from(e);
}
}
return this.WaitAddedNodes(e => {
const ret = [];
if (e instanceof Element) {
if (e.classList.contains(className)) {
ret.push(e);
}
if (option?.isDeepSearch ?? false) {
ret.push(...Array.from(e.getElementsByClassName(className)));
}
}
return ret;
}, parent, option);
}
static async DefinitelyGetElementsByTagName(tagName, parent = document.documentElement, option = null) {
tagName = tagName.toUpperCase();
if (!(option?.doNotNormalCheck ?? false)) {
const e = parent.getElementsByTagName(tagName);
if (e.length != 0) {
return Array.from(e);
}
}
return this.WaitAddedNodes(e => {
const ret = [];
if (e instanceof Element) {
if (e.tagName == tagName) {
ret.push(e);
}
if (option?.isDeepSearch ?? false) {
ret.push(...Array.from(e.getElementsByTagName(tagName)));
}
}
return ret;
}, parent, option);
}
}
;
class CommentList {
constructor(illustId) {
this.illustId = illustId;
}
async FetchCommentList() {
const endPoint = `https://seiga.nicovideo.jp/ajax/illust/comment/list`;
const method = "GET";
//console.log("FetchNicorare");
const res = await Util.GMFetchText(`${endPoint}?id=${this.illustId.toString()}&mode=all`, { method });
//console.log(res);
if (res == "") {
return false;
}
const result = JSON.parse(res);
if (result?.result != "true" || result.comment_list == null) {
return false;
}
result.comment_list.forEach(c => {
if (typeof c.id == "string") {
c.id = parseInt(c.id);
}
});
this.commentList = new SortedArray(result.comment_list);
this.CountComment();
return true;
}
GetList() {
return this.commentList;
}
CountComment() {
for (const comment of this.commentList) {
if (comment.thisUserComments != undefined) {
continue;
}
const trueList = this.commentList.filter(c => c.user == comment.user);
for (const trued of trueList) {
trued.thisUserComments = trueList;
}
}
}
}
class PopUp {
constructor(createdElement, parent) {
this.createdElement = createdElement;
this.parent = parent;
this.children = [];
}
SetElement(parentParentElement, comes) {
const commentListElement = parentParentElement.getElementsByClassName("comment_list")[0];
if (!(commentListElement instanceof HTMLElement)) {
return;
}
this.blackFilterElement = document.createElement("div");
this.blackFilterElement.style.backgroundColor = "rgba(0,0,0,0.3)";
this.blackFilterElement.style.position = "absolute";
if (this.parent == null) {
this.blackFilterElement.style.top = commentListElement.offsetTop + "px";
this.blackFilterElement.style.height = commentListElement.offsetHeight + "px";
}
else {
this.blackFilterElement.style.top = this.parent.popupElement?.offsetTop + "px";
this.blackFilterElement.style.height = this.parent.popupElement?.offsetHeight + "px";
}
this.blackFilterElement.style.width = "300px";
parentParentElement.insertAdjacentElement("beforeend", this.blackFilterElement);
//クリックしたところに合わせて消す
this.blackFilterElement.addEventListener("click", (e) => {
if (popup != null) {
e.stopPropagation();
const all = popup.GetAll();
for (const p of all) {
if (p.createdElement == this.createdElement) {
p.RemoveAll();
if (popup == p) {
popup = null;
}
return;
}
}
}
});
this.popupElement = document.createElement("ul");
this.popupElement.className = "comment_list";
this.popupElement.style.position = "absolute";
this.popupElement.style.overflowY = "auto";
this.popupElement.style.width = "300px";
this.popupElement.style.backgroundColor = "white";
parentParentElement.insertAdjacentElement("beforeend", this.popupElement);
this.popupElement.addEventListener("click", (e) => {
if (popup != null) {
e.stopPropagation();
}
});
this.borderElement = document.createElement("div");
this.borderElement.style.position = "absolute";
this.borderElement.style.width = "299px";
this.borderElement.style.border = "1px solid";
this.borderElement.style.pointerEvents = "none";
parentParentElement.insertAdjacentElement("beforeend", this.borderElement);
const triangleSvgParent = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
triangleSvgParent.style.position = "relative";
triangleSvgParent.setAttribute("width", "20");
triangleSvgParent.setAttribute("height", "5");
this.borderElement.appendChild(triangleSvgParent);
const triangleSvg = document.createElementNS('http://www.w3.org/2000/svg', 'path');
triangleSvg.setAttribute('d', 'M0 0 L10 5 L20 0 Z');
triangleSvg.setAttribute('fill', 'white');
triangleSvg.setAttribute('stroke', 'white');
triangleSvg.setAttribute('stroke-width', "0");
triangleSvgParent.appendChild(triangleSvg);
const userTempIdInnserElement = document.createElement("span");
userTempIdInnserElement.textContent = comes[0].user;
userTempIdInnserElement.style.userSelect = "all";
const userTempIdElement = document.createElement("li");
userTempIdElement.textContent = `仮ID: `;
userTempIdElement.style.margin = "3px";
userTempIdElement.style.fontSize = "83%";
userTempIdElement.style.textAlign = "center";
userTempIdElement.appendChild(userTempIdInnserElement);
this.popupElement.appendChild(userTempIdElement);
for (const c of comes) {
const e = Util.HtmlToElement(`
<li class="comment_list_item PopupComment" data-bind="css:{unpublic: !is_visible(), user: is_owner, unpublic : is_filtered()}">
<ul class="comment_info" data-display_flag="">
<li class="count_new" data-bind="visible: is_new"${c.is_new ? "" : 'style="display: none;"'}> NEW</li>
<li class="date"><span data-bind="text: date">${c.date}</span></li>
<li class="id">No.<span data-bind="text: id">${c.id}</span></li>
<li class="user" data-bind="visible: is_owner" ${c.is_owner ? "" : 'style="display: none;"'}>投稿者</li>
<li class="text" data-bind="text: is_filtered()? '###このコメントは表示されません###' : text">${c.text}</li>
</ul>
</li>`);
//, event:{contextmenu: $parent.showNgMenu}
//` <li class="ng_menu">
// <ul class="ng_menu_list">
// <li class="add_ng_comment" data-bind="click: $parent.addNgComment">NGコメントに追加</li>
// <li class="add_ng_user" data-bind="click: $parent.addNgUser">NGユーザーに追加</li>
// <li class="open_ng_comment">NG設定一覧を見る</li>
// </ul>
// </li>`
if (e == null || !(e instanceof HTMLElement)) {
continue;
}
this.popupElement.appendChild(e);
}
const lastE = this.popupElement.lastElementChild;
lastE.firstElementChild.style.marginBottom = "0";
//位置大きさ計算
let padBottomBefore = parseFloat(window.getComputedStyle(this.popupElement, null).getPropertyValue('padding-bottom'));
if (isNaN(padBottomBefore)) {
padBottomBefore = 0;
}
const maxTop = commentListElement.offsetTop +
parentParentElement.scrollTop;
const maxHeight = commentListElement.offsetHeight;
let popupTop = this.createdElement.getBoundingClientRect().y -
parentParentElement.getBoundingClientRect().y +
parentParentElement.scrollTop -
this.popupElement.offsetHeight +
padBottomBefore - 3;
let popupHeight = this.popupElement.offsetHeight;
let padBottomNext = 0;
if (popupTop < maxTop) {
const popupTopOther = popupTop + this.popupElement.offsetHeight + this.createdElement.offsetHeight - padBottomBefore + 6;
// popupHeight -= maxTop - popupTop;
// popupTop = maxTop;
popupTop = popupTopOther;
if (maxHeight < popupHeight + popupTop && padBottomBefore != 0) {
padBottomNext = padBottomBefore;
}
triangleSvgParent.style.top = -13 + "px";
triangleSvgParent.setAttribute("transform", "rotate(180)");
}
else {
triangleSvgParent.style.top = popupHeight - padBottomBefore - 10 + 1 + "px";
}
triangleSvgParent.style.left = this.createdElement.offsetLeft + "px";
this.popupElement.style.paddingBottom = padBottomNext + "px";
// this.popupElement.style.height = popupHeight - padBottomBefore + "px";
this.popupElement.style.top = popupTop + "px";
this.borderElement.style.height = popupHeight - padBottomBefore - 1 + "px";
this.borderElement.style.top = popupTop + "px";
let p = this.parent;
let zIndex = 2001;
while (p != null) {
zIndex++;
p = p.parent;
}
const zIndexStr = zIndex.toString();
this.blackFilterElement.style.zIndex = zIndexStr;
this.popupElement.style.zIndex = zIndexStr;
this.borderElement.style.zIndex = zIndexStr;
}
GetAncestor() {
let p = this;
while (true) {
if (p.parent == null) {
return p;
}
p = p.parent;
}
}
GetAll() {
const cs = [this];
for (const c of this.children) {
cs.push(...c.GetAll());
}
return cs;
}
Remove() {
this.blackFilterElement?.remove();
this.popupElement?.remove();
this.borderElement?.remove();
}
RemoveAll() {
this.GetAll().forEach(e => e.Remove());
if (this.parent != null) {
this.parent.children = this.parent.children.filter(p => p != this);
}
}
}
let popup = null;
const SetCommentCountElement = async (commentListElements, commentList) => {
const bigTask = new BigDOMTask();
for (const commentListElement of commentListElements) {
const idElements = Array.from(commentListElement.getElementsByClassName("id")).filter(e => e.firstElementChild?.textContent?.length != 0);
const ids = idElements.map(e => parseInt(e.firstElementChild?.textContent ?? ""));
let matingList = commentList.GetList().GetMatingList(ids);
if (matingList == null) {
await commentList.FetchCommentList();
matingList = commentList.GetList().GetMatingList(ids) ?? null;
if (matingList == null) {
console.error("見つからない謎エラー");
continue;
}
}
const comeCounts = Array.from(commentListElement.getElementsByClassName("ComeCount"));
for (let i = 0; i < idElements.length; i++) {
const idElement = idElements[i];
const come = matingList[i];
const id = ids[i];
if (come?.thisUserComments == null || id == undefined) {
continue;
}
const commentCountText = `[${come.thisUserComments.findIndex(c => c.id == come.id) + 1}/${come.thisUserComments.length}]`;
const beforeElement = comeCounts[i];
if (beforeElement != undefined) {
//前のと同じならやめる、違ったら前の消しとく
if (beforeElement.textContent == commentCountText) {
continue;
}
else {
beforeElement.remove();
}
}
//コメント全体の要素
const commentItemElement = idElement.parentElement?.parentElement;
if (commentItemElement == null) {
continue;
}
const countElement = document.createElement("li");
countElement.textContent = commentCountText;
countElement.className = "ComeCount";
const task = new MicroDOMTask();
task.AddTask(() => idElement.insertAdjacentElement("beforebegin", countElement));
if (idElements.length < 15) {
requestAnimationFrame(() => task.Start());
}
else {
bigTask.AddMicroTask(task, commentItemElement);
}
if (come.thisUserComments.length >= 2) {
countElement.style.color = "rgb(35, 148, 216)";
}
if (come.thisUserComments.length >= 5) {
countElement.style.color = "rgb(216, 35, 35)";
}
countElement.style.cursor = "pointer";
countElement.addEventListener("click", (e) => {
e.stopPropagation();
let parentPopup = null;
if (popup != null) {
const all = popup.GetAll();
for (const p of all) {
//生成元をクリックしてる場合 その子供含めて消す
if (p.createdElement == countElement) {
p.RemoveAll();
if (popup == p) {
popup = null;
}
return;
}
for (const element of Array.from(p.popupElement?.children ?? [])) {
//このポップアップ要素の子供をクリックしてる場合 親にする
if (element == commentItemElement) {
parentPopup = p;
p.children.forEach(p => p.RemoveAll());
}
}
}
}
const newPopup = new PopUp(countElement, parentPopup);
if (parentPopup == null) {
popup?.RemoveAll();
popup = newPopup;
}
else {
parentPopup.children.push(newPopup);
}
newPopup.SetElement(commentListElement.parentElement, come.thisUserComments);
});
}
}
bigTask.Start();
};
(async () => {
"use strict";
console.log("start");
//イラストページのとき
const illustIdMatchs = location.href.match(/im(\d+)/);
if (Util.HasLengthAtLeast(illustIdMatchs, 2)) {
const illustId = parseInt(illustIdMatchs[1]);
const commentList = new CommentList(illustId);
await commentList.FetchCommentList();
await Util.WaitDocumentElement();
const commentListElements = await Util.Retry(() => Observer.DefinitelyGetElementsByClassName("comment_list"), 5, 1000, r => r.length >= 2) ?? [];
if (commentListElements.length == 0) {
console.error("commentListElementsが2つない");
return;
}
document.head.insertAdjacentHTML("beforeend", `<style>
.illust_main .illust_side .illust_comment .comment_list .comment_list_item.PopupComment .comment_info::after {
background: none;
background-image: none;
content: none;
}
.illust_main .illust_side .illust_comment .comment_list .comment_list_item .comment_info > li{
margin: 0 6px 0 0;
}
</style>`);
document.getElementById("wrapper")?.addEventListener("click", () => {
popup?.RemoveAll();
popup = null;
});
SetCommentCountElement(commentListElements, commentList);
//通常のリストにコメントが追加されたときのオブザーバー
const mutationObserver = new MutationObserver(async (mrs) => {
const nicorus = new Set();
for (let mr of mrs) {
for (let i = 0; i < mr.addedNodes.length; i++) {
const element = mr.addedNodes[i];
if (!(element instanceof HTMLElement)) {
continue;
}
if (element.classList.contains("comment_list_item")) {
nicorus.add(element.parentElement);
break;
}
}
}
SetCommentCountElement([...nicorus], commentList);
});
for (let e of commentListElements) {
mutationObserver.observe(e, {
childList: true,
});
}
const uniqueParents = Util.Unique(commentListElements.map(e => e.parentElement));
//リスト自体が追加されたときのオブザーバー
const mutationObserverParent = new MutationObserver(async (mrs) => {
const nicorus = new Set();
for (let mr of mrs) {
for (let i = 0; i < mr.addedNodes.length; i++) {
const element = mr.addedNodes[i];
if (!(element instanceof HTMLElement)) {
continue;
}
if (element.classList.contains("comment_list")) {
nicorus.add(element);
break;
}
}
}
SetCommentCountElement([...nicorus], commentList);
});
for (let e of uniqueParents) {
mutationObserverParent.observe(e, {
childList: true,
});
}
}
})();