最后备份:${new Date(
parseInt(lastBackupDictTime),
).toLocaleDateString("zh-CN")}
`;
} else {
lastBackupDict = `Last Backup: ${new Date(
parseInt(lastBackupDictTime),
).toLocaleDateString("en-US")}
`;
}
}
// label
const labelModal = document.createElement("div");
labelModal.className = "modal fade";
labelModal.id = "label_modal";
labelModal.tabIndex = -1;
labelModal.innerHTML = `
`;
// backdrop pin
labelModal.setAttribute(
"data-bs-backdrop",
backdropConfig ? "static" : "true",
);
const labelPinButton = labelModal.querySelector("#label_pin");
labelPinButton.addEventListener("click", () => {
const ins = bootstrap_.Modal.getOrCreateInstance(labelModal);
const backdrop = ins["_config"]["backdrop"] === "static";
if (backdrop) {
ins["_config"]["backdrop"] = true;
setValue("backdropConfig", "false");
labelPinButton.innerHTML = svgUnpin;
} else {
ins["_config"]["backdrop"] = "static";
setValue("backdropConfig", "true");
labelPinButton.innerHTML = svgPin;
}
});
// latest
labelModal
.querySelector("button#toggle_latest")
.addEventListener("click", () => {
setValue("version", version);
});
// search
const searchModal = document.createElement("div");
searchModal.className = "modal fade";
searchModal.id = "search_modal";
searchModal.tabIndex = -1;
searchModal.innerHTML = `
` +
[...invalidShow, ...invalidHide]
.map((w) => {
const { id, associatedTags, restrict, xRestrict } = w;
const l = lang.includes("zh") ? 0 : 1;
const info = [
["作品ID:", "ID: ", id],
[
"用户标签:",
"User Tags: ",
(associatedTags || []).join(", "),
],
[
"公开类型:",
"Publication: ",
restrict ? ["非公开", "hide"][l] : ["公开", "show"][l],
],
["限制分类:", "Restrict: ", xRestrict ? "R-18" : "SFW"],
];
return `
${info
.map((i) => `${i[l] + i[2]}`)
.join("
")}
`;
})
.join("") +
`
`;
const buttonContainer = document.createElement("div");
buttonContainer.className = "d-flex mt-3";
const labelButton = document.createElement("button");
labelButton.className = "btn btn-outline-primary";
labelButton.innerText = "标记失效 / Label As Invalid";
labelButton.addEventListener("click", async (evt) => {
evt.preventDefault();
if (
!window.confirm(
`是否确认批量为失效作品添加"INVALID"标签\nInvalid works (deleted/private) will be labelled as INVALID. Is this okay?`,
)
)
return;
window.runFlag = true;
const bookmarkIds = [...invalidShow, ...invalidHide]
.filter((w) => !w.associatedTags.includes("INVALID"))
.map((w) => w["bookmarkData"]["id"]);
featureProgress.classList.remove("d-none");
featurePrompt.classList.remove("d-none");
featurePrompt.innerText =
"添加标签中,请稍后 / Labeling invalid bookmarks";
await updateBookmarkTags(
bookmarkIds,
["INVALID"],
null,
featureProgressBar,
);
featurePrompt.innerText =
"标记完成,即将刷新页面 / Updated. The page is going to reload.";
setTimeout(() => {
if (window.runFlag && !hold) window.location.reload();
}, 1000);
});
const removeButton = document.createElement("button");
removeButton.className = "btn btn-outline-danger ms-auto";
removeButton.innerText = "确认删除 / Confirm Removing";
removeButton.addEventListener("click", async (evt) => {
evt.preventDefault();
if (
!window.confirm(
`是否确认批量删除失效作品\nInvalid works (deleted/private) will be removed. Is this okay?`,
)
)
return;
window.runFlag = true;
const bookmarkIds = [...invalidShow, ...invalidHide].map(
(w) => w["bookmarkData"]["id"],
);
featureProgress.classList.remove("d-none");
featurePrompt.classList.remove("d-none");
featurePrompt.innerText =
"删除中,请稍后 / Removing invalid bookmarks";
await removeBookmark(bookmarkIds, featureProgressBar);
featurePrompt.innerText =
"已删除,即将刷新页面 / Removed. The page is going to reload.";
setTimeout(() => {
if (window.runFlag && !hold) window.location.reload();
}, 1000);
});
buttonContainer.appendChild(labelButton);
buttonContainer.appendChild(removeButton);
display.appendChild(buttonContainer);
} else {
display.innerText = "未检测到失效作品 / No invalid works detected";
}
display.className = "mt-3";
} else {
featurePrompt.innerText = "操作中断 / Operation Aborted";
}
delete window.runFlag;
});
// bookmarks related
const featureBookmarkButtons = featureModal
.querySelector("#feature_bookmark_buttons")
.querySelectorAll("button");
// backup
featureBookmarkButtons[0].addEventListener("click", async () => {
featureProgress.classList.remove("d-none");
const show = await featureFetchWorks("", "show", featureProgressBar);
const hide = await featureFetchWorks("", "hide", featureProgressBar);
if (window.runFlag) {
const bookmarks = { show, hide };
const a = document.createElement("a");
a.href = URL.createObjectURL(
new Blob([JSON.stringify(bookmarks)], { type: "application/json" }),
);
a.setAttribute(
"download",
`label_pixiv_bookmarks_backup_${new Date().toLocaleDateString()}.json`,
);
a.click();
featurePrompt.innerText = "备份成功 / Backup successfully";
featureProgress.classList.add("d-none");
} else {
featurePrompt.innerText = "操作中断 / Operation Aborted";
}
delete window.runFlag;
});
// lookup invalid
const featureBookmarkDisplay = featureModal.querySelector(
"#feature_bookmark_display",
);
featureBookmarkButtons[1].addEventListener("click", async () => {
const input = document.createElement("input");
input.type = "file";
input.accept = "application/json";
input.addEventListener("change", async (evt) => {
const reader = new FileReader();
reader.onload = async (evt) => {
let json = {};
const invalidArray = [];
async function run(type) {
const col = await featureFetchWorks("", type, featureProgressBar);
if (!window.runFlag) return;
for (let work of col.filter((w) => w.title === "-----")) {
const jsonWork = json[type].find(
(w) => w.id.toString() === work.id.toString(),
);
invalidArray.push(jsonWork || work);
if (DEBUG) console.log(jsonWork);
}
}
try {
eval("json = " + evt.target.result.toString());
if (!json["show"])
return alert(
"请检查是否加载了正确的收藏夹备份\nPlease check if the backup file is correct",
);
if (DEBUG) console.log(json);
featureProgress.classList.remove("d-none");
await run("show");
await run("hide");
if (invalidArray.length) {
featureBookmarkDisplay.innerHTML =
`` +
invalidArray
.map((w) => {
const {
id,
title,
tags,
userId,
userName,
alt,
associatedTags,
restrict,
xRestrict,
} = w;
const l = lang.includes("zh") ? 0 : 1;
const info = [
["", "", alt],
["作品ID:", "ID: ", id],
["作品名称:", "Title: ", title],
["用户名称:", "User: ", userName + " - " + userId],
["作品标签:", "Tags: ", (tags || []).join(", ")],
[
"用户标签:",
"User Tags: ",
(associatedTags || []).join(", "),
],
[
"公开类型:",
"Publication: ",
restrict ? ["非公开", "hide"][l] : ["公开", "show"][l],
],
["限制分类:", "Restrict: ", xRestrict ? "R-18" : "SFW"],
];
return `
${info
.map((i) => `${i[l] + i[2]}`)
.join("
")}
`;
})
.join("") +
`
`;
} else {
featureBookmarkDisplay.innerText =
"未检测到失效作品 / No invalid works detected";
}
featureBookmarkDisplay.className = "mt-3";
} catch (err) {
alert("无法加载收藏夹 / Fail to load bookmarks\n" + err);
console.log(err);
} finally {
featurePrompt.classList.add("d-none");
featureProgress.classList.add("d-none");
delete window.runFlag;
}
};
reader.readAsText(evt.target.files[0]);
});
input.click();
});
// import bookmarks
const importBookmarkButtons = featureModal
.querySelector("#feature_import_bookmark")
.querySelectorAll("button");
importBookmarkButtons[0].addEventListener("click", async () => {
const input = document.createElement("input");
input.type = "file";
input.accept = "application/json";
input.addEventListener("change", (evt) => {
const reader = new FileReader();
reader.onload = async (evt) => {
let json = {};
try {
eval("json = " + evt.target.result.toString());
if (!json["show"])
return alert(
"请检查是否加载了正确的收藏夹备份\nPlease check if the backup file is correct",
);
window.bookmarkImport = json;
const selectTag = featureModal.querySelector(
"#feature_import_bookmark_tag",
);
while (selectTag.firstChild) {
selectTag.removeChild(selectTag.firstChild);
}
const tagShow = json["show"]
.map((w) => w.associatedTags || [])
.reduce((a, b) => [...new Set(a.concat(b))], []);
const tagHide = json["hide"]
.map((w) => w.associatedTags || [])
.reduce((a, b) => [...new Set(a.concat(b))], []);
console.log("tagShow", tagShow);
console.log("tagHide", tagHide);
const tagAll = sortByParody([...new Set(tagShow.concat(tagHide))]);
console.log("tagAll", tagAll);
const optionAll = document.createElement("option");
optionAll.value = "";
optionAll.innerText = `所有收藏 / All Works (${json["show"].length}, ${json["hide"].length})`;
const optionUncat = document.createElement("option");
optionUncat.value = "未分類";
const uncatS = json["show"].filter(
(w) => !(w.associatedTags || []).length,
).length;
const uncatH = json["hide"].filter(
(w) => !(w.associatedTags || []).length,
).length;
optionUncat.innerText = `未分类作品 / Uncategorized Works (${uncatS}, ${uncatH})`;
selectTag.appendChild(optionAll);
selectTag.appendChild(optionUncat);
tagAll.forEach((t) => {
const option = document.createElement("option");
option.value = t;
const s = json["show"].filter((w) =>
(w.associatedTags || []).includes(t),
).length;
const h = json["hide"].filter((w) =>
(w.associatedTags || []).includes(t),
).length;
option.innerText = `${t} (${s}, ${h})`;
selectTag.appendChild(option);
});
featureModal
.querySelector("#feature_import_bookmark_hide")
.classList.remove("d-none");
} catch (err) {
alert("无法加载收藏夹 / Fail to load bookmarks\n" + err);
console.log(err);
}
};
reader.readAsText(evt.target.files[0]);
});
input.click();
});
importBookmarkButtons[1].addEventListener("click", async () => {
if (!window.bookmarkImport?.["show"])
return alert("加载收藏夹备份失败!\nFail to load backup bookmarks");
const json = window.bookmarkImport;
const pub = featureModal.querySelector(
"#feature_import_bookmark_publication",
).value;
const tag = featureModal.querySelector(
"#feature_import_bookmark_tag",
).value;
const mode = featureModal.querySelector(
"#feature_import_bookmark_mode",
).value;
const importWorks = json[pub].filter((w) => {
if (tag === "") return true;
else if (tag === "未分類") return !w.associatedTags?.length;
else return w.associatedTags?.includes(tag);
});
importWorks.reverse();
featureProgress.classList.remove("d-none");
const existWorks = await featureFetchWorks(tag, pub, featureProgressBar);
featurePrompt.classList.remove("d-none");
const errorList = [];
window.runFlag = true;
for (let i = 0; i < importWorks.length; i++) {
const w = importWorks[i];
if (!window.runFlag) break;
let { id, title, restrict, associatedTags, alt } = w;
if (title === "-----") {
errorList.push({
message: "The creator has limited who can view this content",
...w,
});
continue;
}
if (!associatedTags) associatedTags = [];
const ew = existWorks.find((ew) => ew.id === id);
if (ew) {
// note that when work does not have target tag but is in exist bookmarked works, skip will not take effect
if (mode === "skip") continue;
const diff = (ew.associatedTags || []).filter(
(t) => !associatedTags.includes(t),
);
associatedTags = associatedTags.filter(
(t) => !(ew.associatedTags || []).includes(t),
);
if (!associatedTags) continue;
if (mode === "merge")
await updateBookmarkTags([ew["bookmarkData"]["id"]], associatedTags);
else if (mode === "override")
await updateBookmarkTags(
[ew["bookmarkData"]["id"]],
associatedTags,
diff,
);
} else {
const resRaw = await addBookmark(id, restrict, associatedTags);
if (!resRaw.ok) {
const res = await resRaw.json();
errorList.push({ ...res, ...w });
}
}
featurePrompt.innerText = alt;
featureProgressBar.innerText = i + "/" + importWorks.length;
const ratio = ((i / importWorks.length) * 100).toFixed(2);
featureProgressBar.style.width = ratio + "%";
}
if (!window.runFlag) {
featurePrompt.innerText = "操作中断 / Operation Aborted";
} else {
featurePrompt.innerText = "导入成功 / Import successfully";
featureProgress.classList.add("d-none");
}
if (errorList.length) {
console.log(errorList);
featurePrompt.innerText = "部分导入成功 / Import Partially Successful";
featureBookmarkDisplay.classList.remove("d-none");
featureBookmarkDisplay.innerText = errorList
.map((w) => {
const {
id,
title,
tags,
userId,
userName,
alt,
associatedTags,
xRestrict,
message,
} = w;
return `${alt}\ntitle: ${title}\nid: ${id}\nuser: ${userName} - ${userId}\ntags: ${(
tags || []
).join(", ")}\nuserTags: ${(associatedTags || []).join(
", ",
)}\nrestrict: ${xRestrict ? "R-18" : "SFW"}\nmessage: ${message}`;
})
.join("\n\n");
}
});
// switch dialog
const switchDialogButtons = featureModal
.querySelector("#feature_switch_tag_dialog")
.querySelectorAll("button");
// dialog style
switchDialogButtons[0].addEventListener("click", () => {
const tagSelectionDialog = getValue("tagSelectionDialog", "false");
if (tagSelectionDialog === "false") setValue("tagSelectionDialog", "true");
else setValue("tagSelectionDialog", "false");
window.location.reload();
});
// turbo mode
switchDialogButtons[1].addEventListener("click", () => {
if (turboMode) setValue("turboMode", "false");
else setValue("turboMode", "true");
window.location.reload();
});
// all tags selection modal
const c_ = ALL_TAGS_CONTAINER.slice(1);
const allTagsModal = document.createElement("div");
allTagsModal.className = "modal fade";
allTagsModal.id = "all_tags_modal";
allTagsModal.tabIndex = -1;
allTagsModal.innerHTML = `