// ==UserScript==
// @name zhihu optimizer
// @namespace https://github.com/Kyouichirou
// @version 3.5.4.3
// @description now, I can say this is the best GM script for zhihu!
// @author HLA
// @run-at document-start
// @match https://*.zhihu.com/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_listValues
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_deleteValue
// @grant GM_unregisterMenuCommand
// @grant GM_addValueChangeListener
// @grant GM_removeValueChangeListener
// @grant GM_registerMenuCommand
// @grant GM_notification
// @grant GM_openInTab
// @grant GM_getTab
// @grant GM_getTabs
// @grant GM_download
// @grant GM_saveTab
// @grant GM_info
// @grant window.onurlchange
// @grant window.close
// @connect www.zhihu.com
// @connect lens.zhihu.com
// @connect api.zhihu.com
// @connect cn.bing.com
// @connect img.meituan.net
// @connect www.cnblogs.com
// @require https://cdn.staticfile.org/xlsx/0.10.0/xlsx.core.min.js
// @icon https://static.zhihu.com/heifetz/favicon.ico
// @compatible chrome 80+
// @license MIT
// @noframes
// @note cent browser(https://www.centbrowser.com/) is recommended, and edeg browser is not recommended, too bloated and jumbled
// @note just test on chrome 80+
// @downloadURL https://update.greasyfork.icu/scripts/420005/zhihu%20optimizer.user.js
// @updateURL https://update.greasyfork.icu/scripts/420005/zhihu%20optimizer.meta.js
// ==/UserScript==
(() => {
"use strict";
const Assist_info_URL = {
shortcuts:
"https://img.meituan.net/csc/df2540f418efadc25e0562df5924bb8b193354.png",
usermanual:
"https://github.com/Kyouichirou/D7E1293/blob/main/Tmapermonkey/zhihu_optimizer_manual.md",
feedback:
"https://greasyfork.org/zh-CN/scripts/420005-zhihu-optimizer/feedback",
github: "https://github.com/Kyouichirou",
greasyfork:
"https://greasyfork.org/zh-CN/scripts/420005-zhihu-optimizer",
cmd_help:
"https://img.meituan.net/csc/5409e56911b74b0fa3e8e0e3fc40c62587055.png",
search_help:
"https://img.meituan.net/csc/29bae0a159923ec0c3f196326b6e3a2816319.png",
Overview:
"https://img.meituan.net/csc/083a417e5e990b04248baf5912a24ca2333972.png",
};
const blackKey = [
"\u5171\u9752\u56e2",
"\u4e60\u4e3b\u5e2d",
"\u6bdb\u4e3b\u5e2d",
"\u8096\u6218",
"\u7559\u5b66\u4e2d\u4ecb",
"\u65b0\u534e\u793e",
"\u4eba\u6c11\u65e5\u62a5",
"\u5149\u660e\u7f51",
"\u5fb7\u4e91\u793e",
"\u6597\u7f57\u5927\u9646",
"\u592e\u89c6\u65b0\u95fb",
"\u4eba\u6c11\u7684\u540d\u4e49",
"\u5171\u4ea7\u515a",
"\u5f20\u827a\u5174",
"\u6c88\u9038",
"\u6731\u4e00\u9f99",
"\u8fea\u4e3d\u70ed\u5df4",
"\u738b\u4e00\u535a",
"\u6613\u70ca\u5343\u73ba",
"\u91d1\u707f\u8363",
"\u66fe\u4ed5\u5f3a",
"\u738b\u4fca\u51ef",
"\u9a81\u8bdd\u4e00\u4e0b",
"\u90fd\u5e02\u5c0f\u8bf4",
"\u8a00\u60c5\u5c0f\u8bf4",
"\u803d\u7f8e\u5c0f\u8bf4",
"\u6bdb\u6cfd\u4e1c",
];
let blackName = null;
let blackTopicAndQuestion = null;
let collect_Answers = null;
const Notification = (
content = "",
title = "",
duration = 2500,
cfunc,
ofunc
) => {
GM_notification({
text: content,
title: title,
timeout: duration,
onclick: cfunc,
ondone: ofunc,
});
};
const colorful_Console = {
//this function is adpated from jianshu.com
colors: {
warning: "#F73E3E",
Tips: "#327662",
info: "#1475b2",
},
main(info, bc) {
const t = info.title,
c = info.content,
a = [
"%c ".concat(t, " %c ").concat(c, " "),
"padding: 1px; border-radius: 3px 0 0 3px; color: #fff; font-size: 12px; background: ".concat(
"#606060",
";"
),
"padding: 1px; border-radius: 0 3px 3px 0; color: #fff; font-size: 12px; background: ".concat(
bc,
";"
),
];
(function () {
let e;
window.console &&
"function" === typeof window.console.log &&
(e = console).log.apply(e, arguments);
}.apply(null, a),
a);
},
};
const installTips = {
//first time run, open the usermanual webpage
e(mode) {
Notification(
mode
? "important update"
: "thanks for installing, please read user manual carefully",
"Tips",
6000
);
GM_setValue("initial", "3.5");
GM_setValue("installeddate", Date.now());
GM_openInTab(Assist_info_URL.usermanual, { insert: true });
setTimeout(
() => GM_openInTab(Assist_info_URL.shortcuts, { insert: true }),
300
);
},
main() {
const i = GM_getValue("initial");
if (i === true) this.e(true);
else if (!i) this.e();
},
};
const getSelection = () => {
const select = window.getSelection();
return select ? select.toString().trim() : null;
};
//clear zero-width character
const clear_zero_width = (text) =>
text.trim().replace(/[\u200B-\u200D\uFEFF]/g, "");
const escapeBlank = (target) => {
const type = typeof target;
if (type === "object") {
const entries = Object.entries(target);
for (const [key, value] of entries)
target[key] = value.replace(/\s/g, " ");
} else {
target = target.replace(/\s/g, " ");
}
return target;
};
const escapeHTML = (s) => {
const reg = /“|&|’|<|>|[\x00-\x20]|[\x7F-\xFF]|[\u0100-\u2700]/g;
return typeof s !== "string"
? s
: s.replace(reg, ($0) => {
let c = $0.charCodeAt(0),
r = [""];
c = c == 0x20 ? 0xa0 : c;
r.push(c);
r.push(";");
return r.join("");
});
};
//cut out part of title with specified length, take care of chinese character & english character
const titleSlice = (str) => {
let length = 0;
let newstr = "";
for (const e of str) {
length += e.charCodeAt(0).toString(16).length === 4 ? 2 : 1;
newstr += e;
if (length > 27) return `${newstr}...`;
}
return newstr;
};
const createButton = (name, title, otherButton = "", position = "left") => {
title = escapeBlank(title);
const html = `
${otherButton}
`;
document.documentElement.insertAdjacentHTML("beforeend", html);
};
const createPopup = (wtime = 3) => {
const html = `
`;
document.body.insertAdjacentHTML("beforeend", html);
};
/*
unfinished project
directly block some ad information url requests
loaded_article, control the quantity of loaded articles
salt_article, zhihu's paid_content
class, extends, super, class Inheritance, subclass
*/
unsafeWindow.XMLHttpRequest = class extends unsafeWindow.XMLHttpRequest {
open(...args) {
// keep the js of init.js from accessing continuously the url: zhihu-web-analytics.zhihu.com
if (args.length > 1 && args[1].includes("/logs/batch"))
(args[1] = "http://127.0.0.1"), (args[2] = true);
return super.open(...args);
}
};
const fetch_intercept = {
intercept() {
const fetch = unsafeWindow.fetch;
unsafeWindow.fetch = (...args) =>
(async (args) => {
const url = args[0];
const ad_list = [
"/commercial",
"include=ad_type",
"/read_count_statistics",
"/events/r",
"/top_search",
"/preset_words",
"/scan_info",
"/logs/batch",
"/paid_content",
"/market/rhea/questions",
];
const article_api = "/api/v4/questions/";
url.includes(article_api) && (this.loaded_articles += 1);
return ad_list.some((e) => url.includes(e))
? {
// keep the js of init.js from accessing continuously the object
status: 204,
headers: {
get() {
return null;
},
},
text() {
throw new SyntaxError("some error");
},
}
: await fetch(...args);
})(args);
},
// response, response.clone().json(), copy the response to new obj, the response can only be read once
// check if the answer is paid content(salt)
check_salt(response) {
response
.then((result) => this.check_content(result))
.catch(() => console.log("error, no content"));
},
check_content(result) {
for (const a in result) {
const obj = result[a];
const name = Object.prototype.toString.call(obj);
if (name === "[object Array]") {
if (a === "data") {
for (const e of obj) {
if (e.type === "knowledge_result") {
const item = e["answer_obj"];
if (item) {
const id = item["id"];
id &&
!this.salt_articles.includes(id) &&
this.salt_articles.push(id);
}
}
}
return;
}
} else if (name === "[object Object]") this.check_content(obj);
}
},
loaded_articles: 0,
salt_articles: null,
initial(index = 0) {
index > 0 && (this.salt_articles = []);
this.intercept();
},
};
/*
convert image to base64 code
just support a little type of image, chrome, don't support image/gif type
support type, test on this site: https://kangax.github.io/jstests/toDataUrl_mime_type_test/
*/
const imageConvertor = {
canvas(image, format, quality) {
const canvas = document.createElement("canvas");
const context2D = canvas.getContext("2d");
context2D.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
context2D.drawImage(image, 0, 0);
return canvas.toDataURL("image/" + format, quality);
//0-1, default: 0.92, the quality of pic
},
main(imgURL, format = "webp", quality = 0.92) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = imgURL;
img.crossOrigin = "";
img.onload = () => resolve(this.canvas(img, format, quality));
img.onerror = (err) => reject(err);
});
},
};
const Download_module = (url, filename, timeout = 3000) => {
return new Promise((resolve, reject) => {
GM_download({
url: url,
name: filename,
timeout: timeout,
onload: () => resolve(true),
onerror: (err) => {
console.log(err);
reject(null);
},
ontimeout: () => reject("timeout error"),
});
});
};
const image_base64_download = (url, filename) => {
let a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
a.remove();
a = null;
};
const xmlHTTPRequest = (
url,
time = 2500,
responeType = "json",
rType = false
) => {
return new Promise(function (resolve, reject) {
GM_xmlhttpRequest({
method: "GET",
url: url,
timeout: time,
responseType: responeType,
onload: (response) => {
if (response.status == 200) {
if (rType) {
//after redirect, get the final URL
resolve(response.finalUrl);
} else {
resolve(response.response);
}
} else {
console.log(`err: code ${response.status}`);
reject("request data error");
}
},
onerror: (e) => {
console.log(e);
reject("something error");
},
ontimeout: (e) => {
console.log(e);
reject("timeout error");
},
});
});
};
const column_Home = {
item_index: 0,
single_Content_request(type, id, node, info) {
const types = {
0: "answers",
1: "questions",
2: "articles",
};
const name = types[type];
if (!name) return;
const args = `include=data[*].is_normal,admin_closed_comment,reward_info,is_collapsed,annotation_action,annotation_detail,collapse_reason,is_sticky,collapsed_by,suggest_edit,comment_count,can_comment,content,editable_content,attachment,voteup_count,reshipment_settings,comment_permission,created_time,updated_time,review_info,relevant_info,question,excerpt,is_labeled,paid_info,paid_info_content,relationship.is_authorized,is_author,voting,is_thanked,is_nothelp,is_recognized;data[*].mark_infos[*].url;data[*].author.follower_count,badge[*].topics;data[*].settings.table_of_content.enabled`;
const api = `https://www.zhihu.com/api/v4/${name}/${id}?${args}`;
xmlHTTPRequest(api).then(
(json) => {
typeof json === "string" && (json = JSON.parse(json));
node.insertAdjacentHTML(
"afterbegin",
this.item_Raw(json, this.item_index, info)
);
this.item_index += 1;
},
(err) => {
const index =
zhihu.Column.home_Module.loaded_list.indexOf(id);
index > -1 &&
zhihu.Column.home_Module.loaded_list.splice(index, 1);
colorful_Console.main(
{ title: id, content: "failed to request data" },
colorful_Console.colors.warning
);
console.log(err);
}
);
},
time_Format(date) {
return (
`0${date.getHours()}`.slice(-2) +
":" +
`0${date.getMinutes()}`.slice(-2)
);
},
item_Raw(json, index, info) {
const author = json.author;
const ct = (json.created_time || json.created) * 1000;
const mt = (json.updated_time || json.created) * 1000;
const c = new Date(ct);
const m = new Date(mt);
const html = `
${json.content}
编辑于 ${zhihu.Column.timeStampconvertor(mt)}
`;
return html;
},
};
//hidden element = false
const elementVisible = {
getElementTop(obj) {
let top = 0;
while (obj) {
top += obj.offsetTop;
obj = obj.offsetParent;
}
return top;
},
check(wh, element, offset) {
const tp = this.getElementTop(element);
return tp + element.clientHeight > offset && offset + wh > tp;
},
main(fNode, args) {
const offset = fNode.scrollTop;
const wh = window.innerHeight;
const type = Object.prototype.toString.call(args);
if (type === "[object HTMLCollection]") {
for (const e of args) if (this.check(wh, e, offset)) return e;
return null;
} else return this.check(wh, args, offset);
},
};
const change_Title = (title) => {
const chs = document.head.children;
for (const c of chs) {
if (c.localName === "title") {
c.innerHTML = title;
document.title = title;
break;
}
}
};
const get_Title = () => {
const title = document.title;
return title.endsWith("- 知乎")
? title.slice(0, title.length - 5)
: title;
};
class Database {
/*
dname: name of database;
tname: name of table;
mode: read or read&write;
*/
constructor(dbname, tbname = "", rwmode = false, version = 1) {
this.dbopen =
version === 1
? indexedDB.open(dbname)
: indexedDB.open(dbname, version);
this.RWmode = rwmode ? "readwrite" : "readonly";
this.tbname = tbname;
const getIndex = (fieldname) => this.Table.index(fieldname);
}
Initialize() {
return new Promise((resolve, reject) => {
//if the db does not exist, this event will fired firstly;
//adjust the version of db, which can trigger this event => create/delete table or create/delete index (must lauch from this event);
this.dbopen.onupgradeneeded = (e) => {
this.store = e.target.result;
this.updateEvent = true;
this.storeEvent();
resolve(0);
};
this.dbopen.onsuccess = () => {
if (this.store) return;
this.updateEvent = false;
this.store = this.dbopen.result;
this.storeEvent();
resolve(1);
};
this.dbopen.onerror = (e) => {
console.log(e);
reject("error");
};
/*
The event handler for the blocked event.
This event is triggered when the upgradeneeded event should be triggered _
because of a version change but the database is still in use (i.e. not closed) somewhere,
even after the versionchange event was sent.
*/
this.dbopen.onblocked = () => {
console.log("please close others tab to update database");
reject("conflict");
};
this.dbopen.onversionchange = (e) =>
console.log("The version of this database has changed");
});
}
createTable(keyPath) {
if (this.updateEvent) {
const index = keyPath
? { keyPath: keyPath }
: { autoIncrement: true };
this.store.createObjectStore(this.tbname, index);
}
}
createNewTable(keyPath) {
return new Promise((resolve, reject) => {
this.version = this.store.version + 1;
this.store.close();
if (this.storeErr) {
reject("database generates some unknow error");
return;
}
this.dbopen = indexedDB.open(this.store.name, this.version);
this.Initialize().then(
() => {
this.createTable(keyPath);
resolve(true);
},
() => reject("database initial fail")
);
});
}
storeEvent() {
this.store.onclose = () => console.log("closing...");
this.store.onerror = () => (this.storeErr = true);
}
get checkTable() {
return this.store.objectStoreNames.contains(this.tbname);
}
get Tablenames() {
return this.store.objectStoreNames;
}
get DBname() {
return this.store.name;
}
get Indexnames() {
return this.Table.indexNames;
}
get DBversion() {
return this.store.version;
}
get Datacount() {
return new Promise((resolve, reject) => {
const req = this.Table.count();
req.onsuccess = (e) =>
resolve({
count: e.target.result,
name: e.target.source.name,
});
req.onerror = (e) => reject(e);
});
}
//take care the transaction, must make sure the transaction is alive when you need deal with something continually
get Table() {
const transaction = this.store.transaction(
[this.tbname],
this.RWmode
);
const table = transaction.objectStore(this.tbname);
transaction.onerror = () =>
console.log("warning, error on transaction");
return table;
}
rollback() {
this.transaction && this.transaction.abort();
}
read(keyPath) {
return new Promise((resolve, reject) => {
const request = this.Table.get(keyPath);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject("error");
});
}
batchCheck(tables, keyPath) {
return new Promise((resolve) => {
const arr = [];
for (const t of tables) {
const transaction = this.store.transaction(
this.store.objectStoreNames,
this.RWmode
);
const table = transaction.objectStore(t);
const rq = new Promise((reso, rej) => {
const req = table.get(keyPath);
req.onsuccess = () => reso(req.result);
req.onerror = () => rej("error");
});
arr.push(rq);
}
Promise.allSettled(arr).then((results) => {
resolve(
results.map((r) =>
r.status === "rejected" ? null : r.value
)
);
});
});
}
add(info) {
return new Promise((resolve, reject) => {
const op = this.Table.add(info);
op.onsuccess = () => resolve(true);
op.onerror = (e) => {
console.log(e);
reject("error");
};
});
}
getAll(tables) {
return new Promise((resolve) => {
const arr = [];
for (const t of tables) {
const transaction = this.store.transaction(
this.store.objectStoreNames,
this.RWmode
);
const table = transaction.objectStore(t);
const rq = new Promise((reso, rej) => {
const req = table.getAll();
req.onsuccess = (e) =>
reso({
name: e.target.source.name,
data: e.target.result,
});
req.onerror = () => rej(t);
});
arr.push(rq);
}
Promise.allSettled(arr).then((results) => resolve(results));
});
}
updateRecord(keyPath) {
return new Promise((resolve, reject) => {
this.read(keyPath).then(
(result) => {
if (result) {
result.visitTime = Date.now();
let times = result.visitTimes;
result.visitTimes = times ? ++times : 2;
this.update(result).then(
() => resolve(true),
(err) => reject(err)
);
} else resolve(true);
},
(err) => reject(err)
);
});
}
update(info, keyPath, mode = false) {
//if db has contained the item, will update the info; if it does not, a new item is added
return new Promise((resolve, reject) => {
//keep cursor
if (mode) {
this.read(info[keyPath]).then(
(result) => {
if (!result) {
this.add(info).then(
() => resolve(true),
(err) => reject(info)
);
} else {
const op = this.Table.put(
Object.assign(result, info)
);
op.onsuccess = () => resolve(true);
op.onerror = (e) => {
console.log(e);
reject(info);
};
}
},
(err) => console.log(err)
);
} else {
const op = this.Table.put(info);
op.onsuccess = () => resolve(true);
op.onerror = (e) => {
console.log(e);
reject(info);
};
}
});
}
clear() {
this.Table.clear();
}
//must have primary key
deleteiTems(keyPath) {
return new Promise((resolve, reject) => {
const op = this.Table.delete(keyPath);
op.onsuccess = () => {
console.log("delete item successfully");
resolve(true);
};
op.onerror = (e) => {
console.log(e);
reject("error");
};
});
}
//note: create a index must lauch from onupgradeneeded event; we need triggle update event
createIndex(indexName, keyPath, objectParameters) {
if (!this.updateEvent) {
console.log("this function must be through onupgradeneeded");
return;
}
this.table.createIndex(indexName, keyPath, objectParameters);
}
deleTable() {
if (!this.updateEvent) {
console.log("this function must be through onupgradeneeded");
return;
}
this.dbopen.deleteObjectStore(this.tbname);
}
deleIndex(indexName) {
if (!this.updateEvent) {
console.log("this function must be through onupgradeneeded");
return;
}
this.table.deleteIndex(indexName);
}
close() {
//The connection is not actually closed until all transactions created using this connection are complete
this.store.close();
}
static deleDB(dbname) {
return new Promise((resolve, reject) => {
const DBDeleteRequest = window.indexedDB.deleteDatabase(dbname);
DBDeleteRequest.onerror = (e) => {
console.log(e);
reject("error");
};
DBDeleteRequest.onsuccess = () => {
console.log("success");
resolve(true);
};
});
}
}
const dataBaseInstance = {
db: null,
auto_tags() {
const tags = document.getElementsByClassName("Tag-content");
const arr = [];
for (const tag of tags) {
const text = tag.innerText.trim();
text && arr.push(text);
}
return arr.length > 0 ? arr.join(" ") : null;
},
additem(columnID, node, pid) {
const info = {};
info.pid = pid || this.pid;
info.update = Date.now();
info.excerpt = "";
info.visitTimes = 1;
info.visitTime = info.update;
const content_id = node
? "RichText ztext CopyrightRichText-richText"
: "RichText ztext Post-RichText";
node = node || document.body;
const contentholder = node.getElementsByClassName(content_id);
if (contentholder.length > 0) {
const chs = contentholder[0].childNodes;
let excerpt = "";
//take some data as this article's digest
let ic = 0;
for (const node of chs) {
if (node.localName === "p") {
excerpt += node.innerText;
if (excerpt.length > 300) {
excerpt = excerpt.slice(0, 300);
break;
}
}
ic++;
if (ic > 5) break;
}
info.excerpt = excerpt;
}
info.userName = "";
info.userID = "";
const user = node.getElementsByClassName(
"UserLink AuthorInfo-name"
);
if (user.length > 0) {
const link = user[0].getElementsByTagName("a");
if (link.length > 0) {
const p = link[0].pathname;
info.userID = p.slice(p.lastIndexOf("/") + 1);
info.userName = link[0].text;
}
}
info.ColumnID = columnID || "";
const defaultV = this.auto_tags() || "A B";
const p = prompt(
"please input some tags about this article, like: javascript python; multiple tags use blank space to isolate",
defaultV
);
let tags = [];
if (p && p !== defaultV && p.trim()) {
const tmp = p.split(" ");
for (let e of tmp) {
e = e.trim();
e && tags.push(e);
}
}
const note = prompt(
"you can input something to highlight this article, eg: this article is about advantage python usage"
);
info.tags = tags;
info.note = note || "";
info.title = get_Title();
this.db.update(info, "pid", false).then(
() =>
Notification(
"add this article to collection successfully",
"Tips"
),
() =>
Notification(
"add this aritcle to collection fail",
"Warning"
)
);
},
fold(info) {
this.db.update(info, "name", true).then(
() => console.log("this answer has been folded"),
() => console.log("add this anser to folded list fail")
);
},
get DBname() {
return this.db.DBname;
},
get Table() {
return this.db.Table;
},
batchCheck(tableNames) {
const pid = this.pid;
return new Promise((resolve) =>
this.db
.batchCheck(tableNames, pid)
.then((results) => resolve(results))
);
},
updateRecord(keyPath) {
const pid = keyPath || this.pid;
this.db.updateRecord(pid).then(
() => console.log("update record finished"),
(err) => console.log(err)
);
},
check(keyPath) {
const pid = keyPath || this.pid;
return new Promise((resolve, reject) => {
this.db.read(pid).then(
(result) => resolve(result),
(err) => reject(err)
);
});
},
get pid() {
return location.pathname.slice(3);
},
dele(mode, keyPath) {
const pid = keyPath || this.pid;
this.db.deleteiTems(pid).then(
() =>
mode &&
Notification(
`the article of ${pid} has been deleted from collection successfully`,
"Tips"
),
() => mode && Notification("delete article fail")
);
},
update(info) {
return this.db.update(info);
},
/**
* @param {String} name
*/
set TableName(name) {
this.db.tbname = name;
},
close() {
this.db && this.db.close();
this.db = null;
},
getdataCount(tables) {
return new Promise((resolve) => {
const arr = tables.map((t) => {
this.TableName = t;
return this.db.Datacount;
});
Promise.allSettled(arr).then((results) => resolve(results));
});
},
getAll(tables) {
return new Promise((resolve) =>
this.db.getAll(tables).then((result) => resolve(result))
);
},
initial(tableNames, mode = false, keyPath = "pid") {
return new Promise((resolve, reject) => {
if (!Array.isArray(tableNames)) {
reject("this parameter must be array");
return;
}
const dbname = "zhihuDatabase";
const db = new Database(
dbname,
tableNames.length === 1 ? tableNames[0] : "",
mode
);
this.db = db;
db.Initialize().then(
(result) => {
if (result === 0) {
for (const table of tableNames) {
db.tbname = table;
db.createTable(keyPath);
}
}
resolve(result);
},
(err) => reject(err)
);
});
},
};
const data2Excel = {
s2ab(s) {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i !== s.length; ++i)
view[i] = s.charCodeAt(i) & 0xff;
return buf;
},
sheet2blob(sheet, sheetName) {
sheetName = sheetName || "sheet1";
const workbook = {
SheetNames: [sheetName],
Sheets: {},
};
workbook.Sheets[sheetName] = sheet;
const wopts = {
bookType: "xlsx",
bookSST: false,
type: "binary",
};
const wbout = XLSX.write(workbook, wopts);
const blob = new Blob([this.s2ab(wbout)], {
type: "application/octet-stream",
});
// string to ArrayBuffer
return blob;
},
openDownloadDialog(url, saveName) {
if (typeof url == "object" && url instanceof Blob)
url = URL.createObjectURL(url);
let aLink = document.createElement("a");
aLink.href = url;
aLink.download = saveName || "";
let event;
if (window.MouseEvent) event = new MouseEvent("click");
else {
event = document.createEvent("MouseEvents");
event.initMouseEvent(
"click",
true,
false,
window,
0,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
);
}
aLink.dispatchEvent(event);
URL.revokeObjectURL(url);
},
json_toExcel(data, wbname, shname) {
const sheet = XLSX.utils.json_to_sheet(data);
this.openDownloadDialog(
this.sheet2blob(sheet, shname),
`${wbname}.xlsx`
);
},
// dic => sheet, if set the header, the columns' name should been put the first place.
dic_toExcel(data, wbname, shname = "data") {
const sheet = XLSX.utils.aoa_to_sheet(Object.entries(data));
this.openDownloadDialog(
this.sheet2blob(sheet, shname),
`${wbname}.xlsx`
);
},
};
const control_pannel = {
collect() {
console.log("wait a few seconds, data is retrieving...");
dataBaseInstance.getAll(["collection"]).then((results) => {
const result = results[0];
if (!result) {
console.log("no data");
return;
}
if (result.status === "fulfilled") {
const data = result.value.data;
for (const e of data) {
const tags = e.tags;
if (tags && Array.isArray(tags))
e.tags = tags.join(";");
}
data2Excel.json_toExcel(data, "collection_" + Date.now());
console.log(
"successfully retrieved all collected articles"
);
} else console.log("failed to retrieve data");
});
},
};
const control_pannel_init = () => {
unsafeWindow.optimizer = {
collect: null,
};
let time_id = null;
Object.defineProperty(unsafeWindow.optimizer, "collect", {
enumerable: true,
configurable: true,
get() {
return "input: 'optimier.collect = true;' || download all collected articles as excel file";
},
set(value) {
if (value === true) {
time_id && clearTimeout(time_id);
time_id = setTimeout(() => control_pannel.collect(), 300);
}
},
});
};
const MangeData = {
importData: {
isRunning: false,
create(mode) {
const html = `
IndexedDB Data Import
`;
document.body.insertAdjacentHTML("beforeend", html);
this.event(mode);
},
notice(filename) {
colorful_Console.main(
{
tilte: "DB import data",
content: `this file(${filename}) does not match current DB`,
},
colorful_Console.colors.warning
);
},
checkFile_format(json, filename, lists) {
if (!json || !lists.includes(json.name)) {
this.notice(filename);
return null;
}
const data = json.data;
if (!data || !Array.isArray(data) || data.length === 0) {
this.notice(filename);
return null;
}
return data;
},
remove() {
this.timeID && clearTimeout(this.timeID, (this.timeID = null));
this.node && (this.node.remove(), (this.node = null));
},
node: null,
timeID: null,
read_file(file, lists) {
return new Promise((resolve, reject) => {
const r = new FileReader();
//default encode is UTF-8;
r.readAsText(file);
r.onload = (e) => {
const result = e.target.result;
if (!result) {
reject(file.name);
return;
}
try {
const json = JSON.parse(result);
const data = this.checkFile_format(
json,
file.name,
lists
);
if (!data) {
reject(file.name);
return;
}
const tname = json.name;
dataBaseInstance.TableName = tname;
const arr = data.map((e) =>
dataBaseInstance.update(e)
);
Promise.allSettled(arr).then((results) => {
results.forEach((e) => {
if (e.status === "rejected") {
const r = e.value;
r &&
colorful_Console.main(
{
title: "DB put data",
content: `ID of ${r.name} has failed to import`,
},
colorful_Console.colors.warning
);
}
});
Notification(
`the data(${tname}) import operation has completed`,
"Tips",
3500
);
resolve(file.name);
});
} catch (error) {
console.log(error);
reject(file.name);
}
};
r.onerror = (e) => {
console.log(e);
reject(file.name);
};
});
},
loadFile(e, lists) {
this.isRunning = true;
const arr = [];
for (const file of e.target.files)
arr.push(this.read_file(file, lists));
Promise.allSettled(arr).then((results) => {
results.forEach((e) =>
console.log(
e.status === "rejected"
? `failed to import file(${e.value}) to DB`
: `import file(${e.value}) to DB successfully`
)
);
this.timeID = setTimeout(
() => ((this.timeID = null), this.remove()),
1200
);
Notification(
"the operation import data to DB has finished",
"Tips"
);
this.isRunning = false;
});
},
event(mode) {
this.node = document.getElementById("read_local_text");
let i = this.node.getElementsByTagName("input")[0];
i.onchange = (e) => {
if (e.target.files.length === 0) return;
if (
!confirm(
"take care! are you sure to start importing data?"
)
)
return;
const lists = mode
? ["collection", "preference"]
: ["foldedAnswer"];
this.loadFile(e, lists);
};
i = null;
},
main(mode) {
!this.isRunning && this.node
? this.remove()
: this.create(mode);
},
},
exportData: {
_download(text, filename) {
const a = document.createElement("a");
a.setAttribute(
"href",
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
);
a.setAttribute("download", filename);
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
},
_getData(tables) {
dataBaseInstance.getAll(tables).then((results) => {
let i = 0;
for (const result of results) {
if (result.status === "rejected") {
Notification(
`the operation of backup DB(${result.value}) data fail`,
"Warning"
);
continue;
}
if (result.value.data.length === 0) {
Notification(
`the database(${result.value.name}) has not yet stored the data`,
"DB Tips"
);
}
setTimeout(
() =>
this._download(
JSON.stringify(result.value),
`DB_Backup_${
result.value.name
}_${Date.now()}.txt`
),
i
);
i += 2000;
}
});
},
_QAwebPage() {
this._getData(["foldedAnswer"]);
},
_columnPage() {
this._getData(["collection", "preference"]);
},
main(mode = true) {
mode ? this._QAwebPage() : this._columnPage();
},
},
};
const zhihu = {
/*
these original functions of zhihu webpage will be failed in reader mode, so need to be rebuilt
rebuild:
1. gif player;
2. video player;
3. show raw picture
add:
1. picture viewer, continually open the raw picture in viewer mode;
2. time clock
note:
the source of video come from v-list2, which has some problems, need escape ?
*/
qaReader: {
time_module: {
/*
1. adapted from https://zyjacya-in-love.github.io/flipclock-webpage/#
2. html and css is adopted, and some codes have been reedited or cutted;
3. rebuild js, the original js is too big, intricate or complicated;
*/
get formated_Time() {
const time = this.Date_format;
const info = {};
info.hour = time.h;
this.time_arr = [...time.string];
info.before = this.time_arr.map((e) =>
e === "0" ? "9" : (parseInt(e) - 1).toString()
);
return info;
},
time_arr: null,
create_Module(className, value) {
const html = `
`;
return html;
},
removeClassname(node) {
node.className = "";
},
addNewClassName(node, newName) {
node.className = newName;
},
exe(clname, node, e, index) {
const ul = node.getElementsByClassName(clname)[0];
this.removeClassname(ul.firstElementChild);
this.addNewClassName(
ul.lastElementChild,
"flip-clock-before"
);
ul.insertAdjacentHTML(
"beforeend",
this.create_Module("flip-clock-active", e)
);
ul.firstElementChild.remove();
this.time_arr[index] = e;
},
f0(node, e, index) {
this.exe("flip ahour", node, e, index);
},
f1(node, e, index) {
this.exe("flip bhour", node, e, index);
},
f2(node, e, index) {
this.exe("flip play aminute", node, e, index);
},
firstRun: false,
f3(node, e, index) {
this.exe("flip play bminute", node, e, index);
this.firstRun = true;
},
change_time_status(node, value) {
const a = node
.getElementsByClassName("flip-clock-meridium")[0]
.getElementsByTagName("a")[0];
a.innerText = value;
this.currentHour = value;
},
clock() {
const css = `
`;
const info = this.formated_Time;
const pref = "flip-clock-";
const html = `
${css}
${this.create_Module(
pref + "before",
info.before[0]
)}${this.create_Module(
pref + "active",
this.time_arr[0]
)}
${this.create_Module(
pref + "before",
info.before[1]
)}${this.create_Module(
pref + "active",
this.time_arr[1]
)}
${this.create_Module(
pref + "before",
info.before[2]
)}${this.create_Module(
pref + "active",
this.time_arr[2]
)}
${this.create_Module(
pref + "before",
info.before[3]
)}${this.create_Module(
pref + "active",
this.time_arr[3]
)}
`;
document.body.insertAdjacentHTML("beforeend", html);
this.event();
},
getCurrentHour_status(hour) {
return hour > 11 ? "PM" : "AM";
},
currentHour: "",
get Date_format() {
const date = new Date();
const h = date.getHours();
const hs = "".slice.call(`0${h.toString()}`, -2);
const ms = "".slice.call(
`0${date.getMinutes().toString()}`,
-2
);
return { h: h, string: hs + ms };
},
change(node) {
const time = this.Date_format;
[...time.string].forEach(
(s, index) =>
s !== this.time_arr[index] &&
this["f" + index](node, s, index)
);
const ts = this.getCurrentHour_status(time.h);
ts !== this.currentHour &&
this.change_time_status(node, ts);
},
get clock_box() {
return document.getElementById("clock_box");
},
event() {
setTimeout(() => {
const clock = this.clock_box;
let id = setInterval(() => {
!this.paused && this.change(clock);
if (this.firstRun) {
clearInterval(id);
setInterval(
() => !this.paused && this.change(clock),
60 * 1000
);
}
}, 1000);
}, 0);
},
/**
* @param {boolean} e
*/
set clock_paused(e) {
const box = this.clock_box;
box.style.display = e ? "none" : "block";
!e && this.change(box);
this.paused = e;
},
paused: false,
main() {
this.clock();
},
},
firstly: true,
readerMode: false,
get_article_title(node) {
return node.getElementsByClassName("ContentItem-title")[0]
.innerText;
},
Reader(node) {
/*
1. adapted from http://www.360doc.com/
2. css and html is adopted;
3. rebuild js
*/
const bgc = GM_getValue("articleBackground");
const arr = new Array(7);
let color = "#FFF";
let bgcimg = "";
if (bgc) {
for (let i = 1; i < 8; i++)
arr[i - 1] = bgc === `a_color${i}` ? " cur" : "";
if (bgc === "a_color7") {
const bgcM = `background-image: url(https://www.cnblogs.com/skins/coffee/images/bg_body.gif);`;
bgcimg = `background-image: url(${GM_getValue(
"readerbgimg"
)});`;
bgcimg.length < 50 &&
((bgcimg = bgcM),
GM_setValue("readerbgimg_mark", false));
} else color = this.colors_list(bgc);
} else {
arr.fill("", 0);
arr[5] = " cur";
}
let title = "";
if (this.no_scroll) title = this.get_article_title(node);
else {
title = get_Title();
}
const bgpic = GM_getValue("bgpreader");
const meta = node.getElementsByClassName(
"AuthorInfo AnswerItem-authorInfo AnswerItem-authorInfo--related"
);
const mBackup =
'No_data
';
const content = node.getElementsByClassName(
"RichText ztext CopyrightRichText-richText"
);
const cBackup = "No_data";
const html = `
${title}
${
meta.length > 0
? meta[0].outerHTML
: mBackup
}
|
`;
document.body.insertAdjacentHTML("beforeend", html);
setTimeout(() => {
const f = this.full;
f.getElementsByClassName(
"RichText ztext CopyrightRichText-richText"
)[0].innerHTML =
content.length > 0 ? content[0].innerHTML : cBackup;
this.Navigator();
this.toolBar(arr);
this.creatEvent(f);
}, 50);
},
Navigator() {
//this css adapted from @vizo, https://greasyfork.org/zh-CN/scripts/373008-%E7%99%BE%E5%BA%A6%E6%90%9C%E7%B4%A2%E4%BC%98%E5%8C%96sp
const [statusl, titlel] = this.prevNode
? ["", "previous answer"]
: [" disa", "no more content"];
const [statusr, titler] = this.nextNode
? ["", "next answer"]
: [" disa", "no more content"];
const html = `
<`;
document.body.insertAdjacentHTML("beforeend", html);
},
check_answer_collected(qid, aid) {
return collect_Answers.length === 0
? false
: collect_Answers.some((e) =>
e.qid === qid
? e.data.some((n) => n.aid === aid)
: false
);
},
change_Collected(tool, result) {
const b =
tool.lastElementChild.lastElementChild
.previousElementSibling.previousElementSibling;
const cl = b.className;
const tmp = cl.endsWith(" on")
? result
? null
: cl.slice(0, cl.length - 3)
: result
? cl + " on"
: null;
if (tmp) {
b.className = tmp;
b.title = tmp.endsWith(" on")
? `cancel the collection of ${this.content_type}`
: `add the ${this.content_type} to collection`;
}
tool = null;
},
check_article_collected(tool, aid) {
dataBaseInstance.TableName = "collection";
dataBaseInstance.check(aid).then(
(result) => this.change_Collected(tool, result),
() =>
colorful_Console(
{
title: "Warning:",
content: "failed to check article collection",
},
colorful_Console.colors.warning
)
);
},
get_pocket_ico(f) {
return f
? "data:image/webp;base64,UklGRnwEAABXRUJQVlA4WAoAAAAQAAAAPwAAPwAAQUxQSMUDAAABoGttm9ra+eYfsCTbAotleYeZk4qTGzhlGCpmTtpcQFZ6ZqrOvoR0qU8f5qRi2KhZSx5JYznpI8KBG0mOUt19ZJhjA8wXsBZrBQtxIRhAREM1DVbKGONCEGwgb+qR8jwFKwkpiRFr4VIpATtxPwh834WlJo4SfI1QjgJsvVhEU9jC3NYAuHKmM27LPE2TWNii/KmjCEw6jh+Gwg4/z4M0S4Xl7Sicuq5kTCmoKE6EDUFRBBBZkUkbnDT2GXMmnASASZrncpiwqkIAsqgKOYxX5BEDmBLMPI2iKo3iGO8q2TktMQpwVjsrhxuibqoqNt+PdytZ1pUEsd6P8Dk6mvnznnLWTOs6o+Fvoq4rBzjtxO2G7nv00ftu7+quRx595K72A26+AmBtVqucMKxpfVpw6cl/9GZqTt2yLnBVkN0ki+7Z0/p4M2mtX+Ir2xCu2tMHzaY6PNIvhpVdwLCrD3TTbHa70Uf698vALHPaP7oxstkVfagfgbCC44Q+XmdjH+pXwC25vUWPwgdg/2f0f4GPts7JrbO7UJbcN94hqaUV/NHx7l7sLG2c+OPxAepVNezwNP++EVHLusfmGg6q0/io805WddHdZh0G+EWVjDxvZVXlAuCu4N6MzbMiHH3ey6JIBfc9QZ4fJlmwhXUj8ywOA49hEkSJv5V1J9Mk8glQLQvaIuQFQZIltIUb2fqGy8hzMc+KVG7nKXOPk4TxRuW23oiz7rx179jSvO1bNw+NvW62vm63vm/8F/cti5c1jW6OjnXTWO2b9gzEqOOo9Zj79lC0/vRXfdh6xN+NwTyMS380PLDuNmIo94PjvJ8Mj4ZFRNdjMRz0eSSG0+9xnjIYGzebvNEdLT15dp1hm+zGypLbWppOXvArQq8v+sV00/JxtJI2MHH1njZWyZHW9/PTVnmv3eCmX4w/hY3e10/NT1/a2NtZvKf3jprjA+MVi7rXsjo9uOgnvX/cHO/pb4PJalkN2y3reP6JNnQvOBDVdU7d1KWL874yivrjOpBXL4uuO31DXiYc4oVTv/+0ew2MeqPlMiUj5bKU4Ehf/uzPb948GwR4VZkJM9zoW5IsJjCAginQOd2wLBMCZF4aJ0mACt1OVW6exRxgkmiioKJFaHwMB8AIHQV5HrNO39QtiDpVOcki4GyiiEnHmQc+g5X8LIuSrLclY/19WzBzHMlAyvGmlgFmcRwvBCwlZ54jN+9bwzCaM2zS+K7hQk2kbea+HwQzW0vV6ZsZSEpu2Rm7Dk08z7Ez53yg9ycbiIitCxRWNXWfCwBWUDggkAAAABAGAJ0BKkAAQAA//f7/f7+/t7IuEkxz8D+JaQANj8ImUrf0+gklgPogA0QMNUEJ7ufWDDUoyfbyAADM/9OIuT8zVXHQEz9GERQz6D+Z2WY1H89EFBYpFxQabQln+zAJuu5JdnmGILbGTkWvT7K3/qiKohH9n5X17smtGsW3pSOYgFzfI+596XJeZjAGd4AAAA=="
: "data:image/webp;base64,UklGRpYFAABXRUJQVlA4WAoAAAAQAAAAPwAAPwAAQUxQSLUEAAABoGvbthrL2ZdUKlVJKhDDq9dmxsyRhyPOmKm5zcx2xh/QkZmZ/QHtjswQMjMz3jFUuldH/QUR4UiSpLipqT6gFTsQBF/AslhTIJSQkgGc864+dfaUMSak5KDAvZHHHc9zQCqpFGecNQjlOBK0EkEYBsEQxBq4jhRLpOM6ANXZbDoCFTZsBCAcdzQWVPw4juaSihOMXIeDKdcNJhNJI0jTME5iSWxPJ6PhUDHmOHCm80hSCLMshEyyRFFw43nAmDsQXAIYxGmqupkUxQSAyopMdeNl6ZQBzJGs/TOyIm91jgmzopVF1OqAW6/UrmgVN1MU8/bzhNmTsioUOLO+IhAwahz4lu4sGVVV0j1QvaoqXGBxzEmtWnvuuWtPMuvUzeduPrV5wbEHAqxJXaeUcT6qFuF+T/yi+9Xfrx+/7GCdcdogm57+p94BdbuoqeE4uMm2c0+0n7Wf3HLqmXf8o/WNk4IWMDyp9dYIgAhDQfpbQh84/Df94/5gxCx+0duTcuHGq6uxoJS7sljxcJPWWyBJCByj9blYU9V1PQOtVFlV6SG/6a0QRE7S+kQETSKAbJ3v8p2+D4zOSYgbSkVlVNf57t/3RKzWyVJysnTPH/qxNlydYbSUGs52/0E/0IdzwxhoSwww+0E/0QshYEoM/O/1EzOHyNqmAQGLtMD/Tj8RVYqEOHd5m4DN7hg8MFspKUaBgdWumNyHqi66nSyCtW3s2mPFKSuL7Q6FxUKc1KZDW+wwVVSZOc26DAiyIoJBl2a6gCqKVAJiKIU3Zn6STYRJp+10A5VlsRSBJ7kXTKIkhIVOW7McBag0mU9Cj2EQTqMANjqtMw4aUHE0DTjgNMx4H0rVE+6FYZREnNzIlxIbybIxZNwbwk+yWBEvyfhoKf0S3xNcofWg3HhQZ4CWPR4kmDluhye36I5hj3Fr+242tuiMRcJ3Q/5uu2K187slzhsnQ9jCwBkgLHbMG9R5ayOEPYADgJla5y36vOkpew575p3tFwDc1DJv9pm3S25GYJ1e1qNW+8/ba8PVjJtZr/X/L32r9RNWW+tG33Urt0Vvxn6fd9isW/3XTWvWQ2B3wx21biJoSLkt0qoqdwBYrcuqLiyBVVnWZd63cWZzCZstgkvNWGV1VarBnr3X3ZJBiCvMWH0qL2oJZH0edKLW53nKeNply3T4Ys44Q9Bn3J6l9Vo4rJUrg4LD6t5faf0UIDH8Vj8ZU7+bdVpfB2Dj8qxY1KnVYXho4+MCOOAPffd0UVIcpQf/on958q7n/m/dMausqmI13Kc5+dpd9374vz4Di6rodljUo/N1u9ZBANOqMlVFlQ+xx6e6VS8Ao7LMTI19Q5rFHCe+/O13X287EhxoLMuYt5KXuYLEyn2ffvfduze5YPCKPJHtCNkwiJI5AwN83zeXckzyPOKASvPWj+SA5/tO+5cO02QuAKY4HzhwprMJA1pDxRIgTNM5s+2bWKtD3Pijolko2MDhTLmuHwYMpAqSZBolEXnfFo5dVzFwx/VGxADj+Xw+IwZQY89V/fetk8nUZ+iz8V0ipDNQ1PhBEIZjqsox9s0MXClB3BkPXT7wPJemEKJj788pcN5aeIQk9cm8FgBWUDggugAAANAHAJ0BKkAAQAA//f7/f7+6v7+oFA1T8D+JaQANi0QOJXBoKdiIBnAXCgp3qen7fvtcKjwNXA2QZ6PBbFK72dug46kcWHwAAMz/04i5PzOGoTXtS3giZtgpUNd3SnJzoqXXuqkHfniGeO/uELS/XADxhhv9u+5dWGtFF45Pg0DNnKK/whETOV627vbgsGVivDelEzAjy5AudHYhB0mtYPO9IG9//Mdp96HrUJ6XPQxCD4st5f1lGCgAAA==";
},
get check_pocket() {
const recent = GM_getValue("recent");
return (
recent &&
Array.isArray(recent) &&
recent.length > 0 &&
recent.some(
(r) => r.url.endsWith(`/${this.aid}`) && r.type === "p"
)
);
},
toolBar(arr) {
let collected = false;
collect_Answers = GM_getValue("collect_a");
collect_Answers && Array.isArray(collect_Answers)
? this.content_type === "answer" &&
(collected = this.check_answer_collected(
this.qid,
this.aid
))
: (collect_Answers = []);
const is_pocket = this.check_pocket;
const gifBase64 = `
data:image/gif;base64,R0lGODlhDQANAKIGABYcHwIEBP3///P7//T7/xccH////wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS41LWMwMTQgNzkuMTUxNDgxLCAyMDEzLzAzLzEzLTEyOjA5OjE1ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjQ3QzgxQkMzRUZGNDExRTVBQ0VCQTYwQ0ZDNzdGMDlEIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjQ3QzgxQkM0RUZGNDExRTVBQ0VCQTYwQ0ZDNzdGMDlEIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NDdDODFCQzFFRkY0MTFFNUFDRUJBNjBDRkM3N0YwOUQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NDdDODFCQzJFRkY0MTFFNUFDRUJBNjBDRkM3N0YwOUQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQBAAAGACwAAAAADQANAAADHWi63P7QlKgKoRbfFVpmA7B8TWgAQ0QUKSVQcGwkADs=`;
const jpgBase64 = `
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAICAgICAgICAgICAgICAwQDAgIDBAUEBAQEBAUGBQUFBQUFBgYGBgcGBgYICAkJCAgLCwsLCwsLCwsLCwsLCwv/2wBDAQMDAwUEBQgGBggMCggKDA4NDQ0NDg4LCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwv/wgARCAARABEDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAYHA//EABoBAAICAwAAAAAAAAAAAAAAAAAFAQQCAwb/2gAMAwEAAhADEAAAAbK7GNBQUtl+lsVGiDqpjeWAEBlP/8QAGBABAAMBAAAAAAAAAAAAAAAAAwABEyD/2gAIAQEAAQUChmecAAsQALDj/8QAHBEAAgICAwAAAAAAAAAAAAAAAQIDIQASEBEg/9oACAEDAQE/Acdj2b4kkbY2cjjXUV5//8QAHREAAgEEAwAAAAAAAAAAAAAAAQIAAxESIRAgMf/aAAgBAgEBPwGlSTAaHkvwiLiNRnN+v//EAB8QAQACAQMFAAAAAAAAAAAAAAECAwARElEQICIyQf/aAAgBAQAGPwLIeEPU+dKVpqVhHV2nGUrTUrCOrtOO3//EAB0QAQACAQUBAAAAAAAAAAAAAAEAEWEQICExUYH/2gAIAQEAAT8hAo4OohVlsaeaIwCSxaYjrYkRVGJ92f/aAAwDAQACAAMAAAAQymQkH//EAB4RAAEEAQUAAAAAAAAAAAAAAAEAEDFBESFRYXHB/9oACAEDAQE/EEHrJ9YcYmNndEEwgVwsMK7b/8QAHREBAAEDBQAAAAAAAAAAAAAAATEAEUEQICFRYf/aAAgBAgEBPxBisTB1Sr6OuCDHlFfLLnb/AP/EAB0QAQACAQUBAAAAAAAAAAAAAAEAESEQMVGBwdH/2gAIAQEAAT8Qz/bZqO2ZVlUZWp0xz9iiEVWy3HpfXglVsqyiOevIffNP/9k=`;
const html = `
`;
document.body.insertAdjacentHTML("beforeend", html);
this.toolBar_event();
},
color_chooser_display(e, display) {
const target = e.target;
const localName = target.localName;
const c =
localName === "img"
? target.nextElementSibling
: target.lastElementChild;
c.style.display = display;
},
colors_list(name) {
const colors = {
a_color1: "#E3EDCD",
a_color2: "#f5f1e6",
a_color3: "#B6B6B6",
a_color4: "#FFF2E2",
a_color5: "#FAF9DE",
a_color6: "#FFF",
};
const color = colors[name];
return color ? color : "#FFF";
},
timeID: null,
toolBar_event() {
setTimeout(() => {
let tool = this.Toolbar;
let bg = tool.getElementsByClassName("a_bgcolor")[0];
bg.onmouseenter = (e) => {
if (this.timeID) {
clearTimeout(this.timeID);
this.timeID = null;
}
this.color_chooser_display(e, "block");
};
bg.onmouseleave = (e) =>
(this.timeID = setTimeout(() => {
this.timeID = null;
this.color_chooser_display(e, "none");
}, 300));
bg = null;
let closer = tool.getElementsByClassName(
"artfullscreen_closer"
)[0];
closer.onclick = (e) => {
if (
this.autoScroll.scrollState ||
!this.load_content_finished
)
return;
let node = e.target.parentNode;
let ic = 0;
let cn = node.className;
while (cn !== "artfullscreen_toolbar") {
node = node.parentNode;
if (!node || ic > 4) {
node = null;
break;
}
cn = node.className;
ic++;
}
node && (node.style.display = "none");
this.ShowOrExit(false);
};
let colorlist =
tool.getElementsByClassName("a_colorlist")[0];
colorlist.onclick = (e) => {
const target = e.target;
const className = target.className;
if (
className &&
className.startsWith("a_color") &&
!className.endsWith("cur")
) {
const nodes = target.parentNode.children;
for (const node of nodes) {
const cn = node.className;
if (cn.endsWith("cur")) {
node.className = cn.slice(0, cn.length - 4);
break;
}
}
target.className = className + " cur";
const box =
document.getElementById("artfullscreen__box");
if (className === "a_color7") {
const img = GM_getValue("readerbgimg");
box.style.backgroundImage =
(img && `url(${img})`) ||
"url(https://www.cnblogs.com/skins/coffee/images/bg_body.gif)";
} else {
box.style.backgroundImage = "none";
box.style.background =
this.colors_list(className);
}
GM_setValue("articleBackground", className);
}
};
tool.lastElementChild.onclick = (e) => {
let name = e.target.className;
let t = null;
if (!name || typeof name !== "string") {
const path = e.path;
for (const p of path) {
const n = p.className;
if (
n &&
typeof n === "string" &&
n.startsWith("Button")
) {
t = p;
name = n.endsWith(" on")
? "remove_collect"
: "collect";
break;
}
}
} else if (name) {
if (name.startsWith("Button")) {
t = e.target;
name = name.endsWith(" on")
? "remove_collect"
: "collect";
} else if (name.startsWith("read_")) {
t = e.target;
name = "read_it_later";
}
}
name && this[name](t);
};
if (this.content_type === "article")
this.check_article_collected(tool, this.aid);
else tool = null;
colorlist = null;
closer = null;
const i = GM_getValue("bgpindex");
this.picIndex = i ? i : 0;
}, 50);
},
content_type: null,
check_Answers(qid) {
return collect_Answers.findIndex((e) => e.qid === qid);
},
Redirect: {
remove(type, href) {
this.columnsModule.recentModule.remove(type, href);
},
collect(node, pid) {
dataBaseInstance.TableName = "collection";
dataBaseInstance.additem(
this.home_Module.current_Column_id,
node,
pid
);
},
log(config) {
this.columnsModule.recentModule.log("c", config);
},
},
remove_pocket(mode) {
const href =
this.content_type === "answer"
? `https://www.zhihu.com/question/${this.qid}/answer/${this.aid}`
: `https://zhuanlan.zhihu.com/p/${this.aid}`;
if (mode) {
const config = {};
config.type = "p";
config.url = href;
config.update = Date.now();
config.title = get_Title();
this.Redirect.log.call(zhihu.Column, config);
} else this.Redirect.remove.call(zhihu.Column, "p", href);
},
change_pocket_status() {
const f = this.check_pocket;
const tool = this.Toolbar;
const p =
tool.lastElementChild.lastElementChild
.previousElementSibling;
const cl = p.className;
const tmp = cl.endsWith(" on")
? f
? null
: cl.slice(0, cl.length - 3)
: f
? cl + " on"
: null;
if (tmp) {
p.className = tmp;
p.style.content = `url(${this.get_pocket_ico(
tmp.endsWith(" on")
)})`;
}
},
read_it_later(target) {
const cl = target.className;
const f = cl.endsWith(" on");
target.style.content = `url(${this.get_pocket_ico(!f)})`;
target.className = f ? cl.slice(0, cl.length - 3) : `${cl} on`;
this.remove_pocket(!f);
},
collect(target) {
const config = {};
config.type = "c";
const upt = Date.now();
if (this.content_type === "answer") {
const index = this.check_Answers(this.qid);
const pref = "https://www.zhihu.com/question/";
if (index > -1) {
const c = collect_Answers[index];
const data = c.data;
if (!data.some((e) => e.aid === this.aid))
data.push({ aid: this.aid, update: upt });
config.title = c.title;
config.url = `${pref}${c.qid}/answer/${this.aid}`;
} else {
const info = {};
config.title = info.title = get_Title();
info.qid = this.qid;
info.data = [];
info.data.push({ aid: this.aid, update: upt });
config.url = `${pref}${this.qid}/answer/${this.aid}`;
collect_Answers.push(info);
}
GM_setValue("collect_a", collect_Answers);
} else if (this.content_type === "article") {
this.Redirect.collect.call(
zhihu.Column,
this.curNode,
this.aid
);
config.url = `https://zhuanlan.zhihu.com/p/${this.aid}`;
config.title = get_Title();
}
config.update = upt;
this.Redirect.log.call(zhihu.Column, config);
this.change_collect_status(target, true);
},
change_collect_status(target, mode) {
const className = target.className;
target.title = mode
? `cancel the collection of ${this.content_type}`
: `add the ${this.content_type} to collection`;
target.className = mode
? className + " on"
: className.slice(0, className.length - 3);
},
remove_collect(target) {
if (
!confirm(
`are you sure want to remove this ${this.content_type}`
)
)
return;
let href = "";
if (this.content_type === "answer") {
const index = this.check_Answers(this.qid);
if (index > -1) {
const data = collect_Answers[index].data;
if (data.length > 1) {
const i = data.findIndex((e) => e.aid === this.aid);
i > -1 && data.splice(i, 1);
} else collect_Answers.splice(index, 1);
GM_setValue("collect_a", collect_Answers);
}
href = `https://www.zhihu.com/question/${this.qid}/answer/${this.aid}`;
} else if (this.content_type === "article") {
dataBaseInstance.TableName = "collection";
dataBaseInstance.dele(false, this.aid);
href = `https://zhuanlan.zhihu.com/p/${this.aid}`;
}
this.change_collect_status(target, false);
this.Redirect.remove.call(zhihu.Column, "c", href);
},
picIndex: 0,
disable_bgp() {
this.full.style.backgroundImage = "none";
this.picIndex = 0;
GM_setValue("bgpindex", this.picIndex);
GM_setValue("bgpreader", "");
},
change_bgp_timeID: null,
change_bgp() {
this.change_bgp_timeID && clearTimeout(this.change_bgp_timeID);
this.change_bgp_timeID = setTimeout(() => {
this.change_bgp_timeID = null;
const pics = [
"d40e6fbabe339af2f9cc1b9a0c49b97c212417",
"50bfb5a296779e17d2901c6d13eb15cf215229",
"02ed4231ffac5cb7553df89a8ef6b3c4122737",
"826d9cecc220b11c5502f247ff046387247423",
"02f8f662ea840da01a9645fc2d18d3ac170812",
"f2a5e663cce16208e9e85c9a4bec502a101513",
"a3dc87ca1d8021f5091acf288c4fe0a4154893",
];
const pref = "https://img.meituan.net/csc/";
const suffix = ".jpg";
this.backgroundImage_cache.reader(
this.full,
pref + pics[this.picIndex] + suffix
);
this.picIndex + 1 === pics.length
? (this.picIndex = 0)
: (this.picIndex += 1);
GM_setValue("bgpindex", this.picIndex);
}, 300);
},
load_more() {
if (this.isRunning) return;
this.try_status = false;
this.isRunning = true;
this.simulation_scroll(this.full, this.curNode, true);
this.scroll_record = 0;
},
/**
* @param {boolean} mode
*/
set display_load_more(mode) {
if (this.load_more_status === mode) return;
this.Toolbar.lastElementChild.lastElementChild.style.display =
mode ? "block" : "none";
this.load_more_status = mode;
},
get_video_info(v, video_id) {
const api = `https://lens.zhihu.com/api/v4/videos/${video_id}`;
xmlHTTPRequest(api).then(
(json) => {
typeof json === "string" && (json = JSON.parse(json));
let videoURL = "";
const vlb = json.playlist;
const keys = Object.keys(vlb);
let back = "";
for (const k of keys) {
if (k === "HD" || k === "FHD") {
videoURL = vlb[k].play_url;
break;
} else back = vlb[k].play_url;
}
if (!videoURL && !back) return;
else if (!videoURL) videoURL = back;
const picURL = json.cover_url;
this.replace_Video(v, picURL, videoURL);
},
(err) => console.log(err)
);
},
get_video_ID(v) {
let set = v.dataset.zaExtraModule;
if (!set) set = v.dataset.zaModuleInfo;
if (!set) return;
typeof set === "string" && (set = JSON.parse(set));
const content = set.card.content;
if (content.is_playable === false) {
console.log("current video is not playable");
return;
}
this.get_video_info(v, content.video_id);
},
rawVideo_html(picURL, videoURL) {
const html = `
`;
return html;
},
replace_Video(v, picURL, videoURL) {
let player = v.getElementsByClassName("VideoCard-player");
if (player.length > 0) {
const html = this.rawVideo_html(picURL, videoURL, false);
player[0].children.length === 0
? player[0].insertAdjacentHTML("afterbegin", html)
: (player[0].innerHTML = html);
} else {
player = v.getElementsByClassName(
"ZVideoLinkCard-playerContainer"
);
if (player.length > 0) {
const html = this.rawVideo_html(picURL, videoURL, true);
player[0].style.paddingBottom = 0;
player[0].children.length === 0
? player[0].insertAdjacentHTML("afterbegin", html)
: (player[0].innerHTML = html);
} else console.log("warning, the id of player is null");
}
},
//click image to show the raw pic
imgClick: {
create(node, info) {
const html = `
`;
node.insertAdjacentHTML("beforeend", html);
},
imgPosition(info, target) {
const rw = target.dataset.rawwidth * 1;
const rh = target.dataset.rawheight * 1;
const owh = window.outerHeight * 1;
const wh = window.innerHeight * 1;
const ww = window.innerWidth * 1;
const sww = ww * 0.98;
const tw = target.width * 1;
const th = target.height * 1;
const xw = (ww - tw) / 2;
let sc = 0;
let yh = 0;
//if the width of raw pic is bigger than 98% width of window
if (rw > sww) {
sc = sww / tw;
} else {
sc = rw / tw;
}
if (rh > owh) {
//if the height is bigger than width
yh = rh > rw ? (rh - th) / 2 : (xw * th) / tw;
} else {
yh = (wh - th) / 2;
}
//margin
if (yh > wh) {
yh = yh / 2;
while (yh > wh) yh = yh / 2;
} else yh += 5;
info.transform = `translate(${xw}px, ${yh}px) scale(${sc.toFixed(
4
)})`;
info.width = `${tw}px`;
},
isExist: false,
restore(n) {
//retore the status of navigator
const l = n.children[1];
const r = n.children[2];
l.className !== this.lbackupNav[0] &&
(l.className = this.lbackupNav[0]);
r.className !== this.rbackupNav[0] &&
(r.className = this.rbackupNav[0]);
l.title = this.lbackupNav[1];
r.title = this.rbackupNav[1];
n = null;
},
remove(n) {
if (!this.isExist) return;
this.ImageView.parentNode.parentNode.remove();
this.isExist = false;
this.currentPicURL = null;
this.restore(n);
this.imgList = null;
this.rbackupNav = null;
this.lbackupNav = null;
this.ImageView = null;
this.toolBar_display(true);
},
currentPicURL: null,
ImageView: null,
toolBar_display(mode) {
const tool = document.getElementById(
"artfullscreen_toolbar"
);
tool && (tool.style.display = mode ? "block" : "none");
},
showRawPic(box, target, n) {
const url = target.dataset.original;
if (!url) return;
const info = {};
info.url = url;
this.imgPosition(info, target);
this.create(box, info);
this.isExist = true;
setTimeout(() => {
const viewer =
box.getElementsByClassName("ImageView-inner");
if (viewer.length > 0)
this.ImageView = viewer[0].firstElementChild;
}, 10);
this.toolBar_display(false);
this.currentPicURL = url;
this.imgNav(box, n);
},
rbackupNav: null,
lbackupNav: null,
imgList: null,
backUP(node, arr) {
arr.push(node.className);
arr.push(node.title);
},
imgNav(box, n) {
const imgs = box.getElementsByTagName("img");
this.imgList = [];
this.rbackupNav = [];
this.lbackupNav = [];
for (const img of imgs)
img.className.endsWith("lazy") &&
img.dataset.original &&
this.imgList.push(img);
const [titlel, namel, titler, namer] =
this.imgList.length === 1
? [
"no more picture",
"readerpage-l disa",
"no more picture",
"readerpage-r disa",
]
: [
"previous picture",
"readerpage-l",
"next picture",
"readerpage-r",
];
const l = n.children[1];
const r = n.children[2];
this.backUP(l, this.lbackupNav);
this.backUP(r, this.rbackupNav);
l.title = titlel;
this.lbackupNav[0] !== namel && (l.className = namel);
r.title = titler;
this.rbackupNav[0] !== namer && (r.className = namer);
},
change_time_id: null,
changPic(mode, show_status, f) {
this.change_time_id && clearTimeout(this.change_time_id);
this.change_time_id = setTimeout(() => {
this.change_time_id = null;
if (!this.ImageView) {
console.log(
"warning, the viewer of picture is null"
);
return;
}
const i = this.imgList.length - 1;
if (i === 0) return;
const index = this.imgList.findIndex(
(img) => img.dataset.original === this.currentPicURL
);
if ((index === i && mode) || (index === 0 && !mode)) {
show_status.main(
show_status.node ? null : f,
"no more picture"
);
return;
}
const imge = this.imgList[mode ? index + 1 : index - 1];
const url = imge.dataset.original;
this.currentPicURL = url;
const info = {};
this.imgPosition(info, imge);
this.ImageView.style.width = info.width;
this.ImageView.style.transform = info.transform;
this.ImageView.src = url;
}, 300);
},
no_mp4_giflist: null,
gif_time_id: null,
GifPlay(target, className) {
/*
some gifs are actually mp4 videos;
must check the gif whether has mp4 video
*/
this.gif_time_id && clearTimeout(this.gif_time_id);
this.gif_time_id = setTimeout(() => {
this.gif_time_id = null;
if (className.endsWith("gif2mp4")) {
this.gif_pNode_className(target, !target.paused);
target.paused ? target.play() : target.pause();
return;
}
const url = target.src;
const reg = /(?<=-)\w+(?=_)/;
const match = url.match(reg);
if (!match) {
console.log("get id of gif pic fail");
return;
}
const id = match[0];
if (this.no_mp4_giflist) {
if (this.no_mp4_giflist.includes(id)) {
this.Gif_Pic(url, target);
return;
}
} else this.no_mp4_giflist = [];
this.Gif_MP4(id).then(
(src) => {
if (!src) {
this.no_mp4_giflist.push(id);
this.Gif_Pic(url, target);
return;
}
target.insertAdjacentHTML(
"beforebegin",
this.gif_vidoe_raw(src, url)
);
setTimeout(() => {
this.gif_pNode_className(target, false);
target.previousElementSibling.play();
target.className =
className + " GifPlayer-gif2mp4Image";
}, 50);
},
() =>
Notification(
"get the play url of gif_mp4 fail",
"Reader Tips"
)
);
}, 300);
},
gif_pNode_className(target, mode) {
target.parentNode.className =
"GifPlayer" + (mode ? "" : " isPlaying");
},
gif_vidoe_raw(src, pic) {
const html = `
`;
return html;
},
Gif_Pic(url, target) {
const [a, b] = url.includes(".webp")
? [".webp", ".jpg"]
: [".jpg", ".webp"];
target.src = url.replace(a, b);
this.gif_pNode_className(target, a === ".webp");
},
Gif_MP4(id) {
return new Promise((resolve, reject) => {
const api = `https://api.zhihu.com/gif2mp4/v2-${id}`;
xmlHTTPRequest(api).then(
(json) => {
typeof json === "string" &&
(json = JSON.parse(json));
const plist = json.playlist;
let src = "";
let b = "";
for (const e of Object.keys(plist)) {
if (e === "SD") {
src = plist[e].play_url;
break;
} else b = plist[e].play_url;
}
resolve(src ? src : b);
},
(err) => {
console.log(err);
reject(null);
}
);
});
},
video_Play(video) {
video.nextElementSibling.style.display = "none";
video.previousElementSibling.style.display = "none";
video.play();
},
event(node, n, scroll) {
setTimeout(() => {
const box = node.lastElementChild;
box.onclick = (e) => {
if (scroll.scrollState) return;
const target = e.target;
const className = target.className;
if (className && typeof className === "string") {
if (className.endsWith("lazy"))
this.showRawPic(box, target, n);
else if (className.startsWith("ztext-gif"))
this.GifPlay(target, className);
else if (className === "_video_cover")
this.video_Play(target.nextElementSibling);
else this.remove(n);
} else {
const localName = target.localName;
if (localName) {
if (
localName === "path" ||
localName === "circle"
) {
const paths = e.path;
for (const p of paths) {
if (p.className === "_player_ico") {
this.video_Play(
p.previousElementSibling
);
break;
}
}
} else if (
localName === "img" &&
target.dataset.original
)
this.showRawPic(box, target, n);
}
}
};
}, 100);
},
},
scrollListen: false,
video_list: null,
loadedList: null,
getVideo_element(f) {
//if the video element is visible, load data
if (this.video_list) {
this.video_list = null;
this.loadedList = null;
}
const videoas = f.getElementsByClassName("RichText-video");
const videobs = f.getElementsByClassName("ZVideoLinkCard");
const a = videoas.length;
const b = videobs.length;
if (a + b === 0) return;
this.video_list = [];
for (let k = 0; k < a; k++)
elementVisible.main(f, videoas[k])
? this.get_video_ID(videoas[k])
: this.video_list.push(videoas[k]);
for (let k = 0; k < b; k++)
elementVisible.main(f, videobs[k])
? this.get_video_ID(videobs[k])
: this.video_list.push(videobs[k]);
const i = this.video_list.length;
if (i > 0) {
this.loadedList = new Array(i);
!this.scrollListen && this.scrollEvent(f);
} else this.video_list = null;
},
scrollEvent(f) {
let unfinished = false;
let cache = 0;
f.onscroll = () => {
cache += 1;
if (
cache < 5 ||
unfinished ||
!this.video_list ||
!this.loadedList
)
return;
cache = 0;
unfinished = true;
this.video_list.forEach((v, index) => {
if (!this.loadedList[index]) {
if (elementVisible.main(f, v)) {
this.loadedList[index] = true;
this.get_video_ID(v);
} else this.loadedList[index] = false;
}
});
if (this.loadedList.every((e) => e)) {
this.loadedList = null;
this.video_list = null;
}
unfinished = false;
};
this.scrollListen = true;
},
collected_answer_sync() {
GM_addValueChangeListener(
"collect_a",
(name, oldValue, newValue, remote) =>
remote && (collect_Answers = newValue)
);
},
creatEvent(f) {
const n = this.nav;
n.children[1].onclick = () => this.Previous(f);
n.children[2].onclick = () => this.Next(f);
this.loadLazy(f);
this.answer_card_module.initial(f);
this.imgClick.event(f, n, this.autoScroll);
this.getVideo_element(f);
setTimeout(() => this.time_module.main(), 300);
this.backgroundImage_cache.article();
this.collected_answer_sync();
},
turnPage: {
main(mode, node) {
const overlap = 100;
const wh = window.innerHeight;
let height = wh - overlap;
height < 0 && (height = 0);
let top = node.scrollTop;
if (mode) top += height;
else top < height ? (top = 0) : (top -= height);
node.scrollTo(0, top);
},
start(mode, node) {
window.requestAnimationFrame(
this.main.bind(this, mode, node)
);
},
},
autoScroll: {
stepTime: 40,
keyCount: 1,
scrollState: false,
scrollTime: null,
scrollPos: null,
node: null,
pageScroll(TimeStamp) {
if (!this.scrollState) return;
const position = this.node.scrollTop;
if (this.scrollTime) {
this.scrollPos =
this.scrollPos !== null
? this.scrollPos +
(TimeStamp - this.scrollTime) / this.stepTime
: position;
this.node.scrollTo(0, this.scrollPos);
}
this.scrollTime = TimeStamp;
const h = this.node.scrollHeight - window.innerHeight;
position < h
? window.requestAnimationFrame(
this.pageScroll.bind(this)
)
: this.stopScroll(true);
},
autoReader: null,
scrollEnd: false,
autoButton_event() {
createPopup(5);
const tips = document.getElementById("autoscroll-tips");
let buttons = tips.getElementsByTagName("button");
const id = setTimeout(() => {
tips.remove();
!this.auto_pause && this.autoReader();
}, 5000);
buttons[0].onclick = () => {
clearTimeout(id);
clearTimeout(this.timeID);
tips.remove();
!this.auto_pause && this.autoReader();
};
buttons[1].onclick = () => {
clearTimeout(id);
clearTimeout(this.timeID);
this.timeID = null;
tips.remove();
};
buttons = null;
},
timeID: null,
auto_pause: false,
remain_time: 0,
stopScroll(mode) {
if (this.scrollState) {
this.scrollPos = null;
this.scrollTime = null;
this.scrollState = false;
this.keyCount = 1;
if (mode && this.autoReader && !this.scrollEnd) {
if (this.auto_pause) return;
const stop = Date.now();
const gap = Math.floor(
(stop - this.stopwatch) / 1000
);
const wtime =
gap < 30
? 2000
: gap > 60 && gap < 90
? 3000
: gap > 180
? 5500
: 4000;
gap > 180
? this.autoButton_event()
: setTimeout(
() =>
!this.auto_pause && this.autoReader(),
wtime + this.remain_time
);
this.timeID = setTimeout(() => {
this.timeID = null;
if (this.auto_pause) return;
this.keyCount = 2;
this.start();
}, wtime + this.remain_time + 3500);
} else {
this.timeID && clearTimeout(this.timeID);
this.timeID = null;
!this.autoReader && (this.node = null);
this.remain_time = 0;
}
this.stopwatch = 0;
}
},
speedUP() {
this.scrollState && this.stepTime < 10
? (this.stepTime = 5)
: (this.stepTime -= 5);
},
slowDown() {
this.scrollState && this.stepTime > 100
? (this.stepTime = 100)
: (this.stepTime += 5);
},
stopwatch: 0,
startID: null,
is_nurse: false,
start() {
if (this.is_nurse) return;
this.keyCount += 1;
if (this.keyCount % 2 === 0) return;
if (this.scrollState) this.stopScroll(false);
else {
if (this.timeID) {
if (this.autoReader) return;
else clearTimeout(this.timeID);
}
this.stopwatch = Date.now();
this.scrollState = true;
!this.node &&
(this.node =
document.getElementById("artfullscreen"));
this.startID && clearTimeout(this.startID);
this.startID = setTimeout(() => {
this.startID = null;
window.requestAnimationFrame(
this.pageScroll.bind(this)
);
}, 600);
}
},
},
scroll: {
toTop(node) {
let hTop = node.scrollTop;
if (hTop === 0) return;
const rate = 8;
let sid = 0;
const scrollToTop = () => {
hTop = node.scrollTop;
if (hTop > 0) {
sid = window.requestAnimationFrame(scrollToTop);
node.scrollTo(0, hTop - hTop / rate);
} else {
sid !== 0 && window.cancelAnimationFrame(sid);
}
};
scrollToTop();
},
toBottom(node) {
let sid = 0;
let shTop = 0;
const initial = 100;
const scrollToBottom = () => {
const hTop = node.scrollTop || initial;
if (hTop !== shTop) {
shTop = hTop;
sid = window.requestAnimationFrame(scrollToBottom);
node.scrollTo(0, hTop + hTop);
} else {
sid !== 0 && window.cancelAnimationFrame(sid);
sid = 0;
}
};
scrollToBottom();
},
},
autoReader_mode: false,
autoReader() {
if (this.autoReader_mode) {
this.autoReader_mode = false;
this.show_status.istimeout = true;
this.show_status.main(
null,
"auto reader mode has ben cancelled"
);
this.autoScroll.autoReader = null;
this.autoScroll.auto_pause = false;
this.autoScroll.node = null;
this.auto_pause_mode = false;
} else {
this.show_status.main(this.full, "Auto Mode", 0, 0, false);
this.autoReader_mode = true;
this.autoScroll.autoReader = this.Next.bind(this);
}
},
auto_pause_mode: false,
auto_pause() {
this.autoScroll.auto_pause = this.auto_pause_mode =
!this.auto_pause_mode;
this.show_status.auto_scroll_change(
this.auto_pause_mode ? "Auto Mode_Paused" : "Auto Mode"
);
this.auto_pause_mode && (this.autoScroll.remain_time = 0);
},
keyEvent(keyCode, shift) {
this.imgClick.isExist
? keyCode === 37
? this.Previous()
: keyCode === 39
? this.Next()
: null
: shift
? keyCode === 65
? this.autoReader()
: null
: keyCode === 65
? this.autoReader_mode && this.auto_pause()
: keyCode === 84
? !this.autoScroll.scrollState &&
this.scroll.toTop(this.full)
: keyCode === 78
? !this.autoScroll.scrollState &&
this.turnPage.start(true, this.full)
: keyCode === 85
? !this.autoScroll.scrollState &&
this.turnPage.start(false, this.full)
: keyCode === 82
? !this.autoScroll.scrollState &&
this.scroll.toBottom(this.full)
: keyCode === 192
? this.autoScroll.start()
: keyCode === 187
? this.autoScroll.speedUP()
: keyCode === 189
? this.autoScroll.slowDown()
: keyCode === 37
? this.Previous()
: keyCode === 39
? this.Next()
: keyCode === 27
? this.ShowOrExit(false)
: zhihu.multiSearch.main(keyCode);
},
changeNav(node) {
const pre = node.children[1];
const pName = pre.className;
const [npName, titlel] = this.prevNode
? ["readerpage-l", "previous answer"]
: ["readerpage-l disa", "no more content"];
if (pName !== npName) {
pre.className = npName;
pre.title = titlel;
}
const next = node.children[2];
const nextName = next.className;
const [nnextName, titler] = this.nextNode
? ["readerpage-r", "next answer"]
: ["readerpage-r disa", "no more content"];
if (nextName !== nnextName) {
next.className = nnextName;
next.title = titler;
}
},
getAnswerID(node) {
let first = node.firstElementChild;
if (first.className !== "ContentItem AnswerItem")
first = this.getAnswerItem(node);
if (!first) {
console.log("warning, the structure of node has changed");
return null;
}
const ats = first.attributes;
if (ats)
for (const a of ats) if (a.name === "name") return a.value;
},
/**
* @param {any} node
*/
set answerID(node) {
this.aid = this.getAnswerID(node);
},
// trigger the scroll event to load more answers;
simulation_scroll(f, node, mode = false) {
setTimeout(() => {
this.overFlow = false;
f.style.overflow = "hidden";
node.scrollIntoView();
setTimeout(() => {
(this.scroll_record < 5 || mode) &&
window.scrollTo(0, 0.98 * this.DDSH);
setTimeout(() => {
this.scroll_record -= 1;
this.navPannel = this.curNode = node;
const time =
this.allAnswser_loaded || this.nextNode
? 50
: 650;
setTimeout(() => {
this.overFlow = true;
f.style.overflow = "auto";
this.isRunning = false;
mode
? this.nextNode &&
Notification(
"load more answers successfully",
"Tips"
)
: f.focus();
}, time);
}, 350);
}, 350);
}, 60);
},
isRunning: false,
scroll_record: 0,
chang_time_id: null,
//settimeout => prevent too many times of operation
changeContent(node, mode = true) {
this.chang_time_id && clearTimeout(this.chang_time_id);
this.chang_time_id = setTimeout(() => {
this.chang_time_id = null;
if (
(!this.autoReader_mode && this.autoScroll.node) ||
this.isRunning
)
return;
this.isRunning = true;
const cName = "RichText ztext CopyrightRichText-richText";
const aName =
"AuthorInfo AnswerItem-authorInfo AnswerItem-authorInfo--related";
const f = this.full;
const content = node.getElementsByClassName(cName);
const author = node.getElementsByClassName(aName);
const cn = f.getElementsByClassName(cName)[0];
if (content.length > 0) {
cn.innerHTML = content[0].innerHTML;
this.autoReader_mode &&
this.setRemainTime(content[0].innerText.length);
} else cn.innerHTML = "No data";
f.getElementsByClassName(aName)[0].innerHTML =
author.length > 0 ? author[0].innerHTML : "No data";
if (mode) {
this.answerID = node;
this.isSimple_page || this.allAnswser_loaded
? (this.navPannel = this.curNode = node)
: this.simulation_scroll(f, node);
} else this.ShowOrExit(true);
if (this.no_scroll) {
f.getElementsByTagName("h2")[0].innerText =
this.get_article_title(node);
this.set_current_Type(node);
}
this.content_type === "answer"
? this.change_Collected(
this.Toolbar,
this.check_answer_collected(this.qid, this.aid)
)
: this.check_article_collected(this.Toolbar, this.aid);
this.change_pocket_status();
this.loadLazy(f);
this.answer_card_module.initial(f);
this.getVideo_element(f);
setTimeout(() => {
f.scrollTo(0, 0);
(this.allAnswser_loaded ||
!mode ||
this.isSimple_page) &&
((this.isRunning = false), f.focus());
}, 50);
}, 300);
},
setRemainTime(len) {
if (
this.auto_pause_mode ||
this.full.scrollHeight > window.innerHeight
)
return;
const w =
len < 30
? 500
: len < 50
? 1000
: len < 100
? 1500
: (len / 100) * 500 + 1500;
this.autoScroll.remain_time = w > 6000 ? 6000 : w;
},
Next(f) {
if (!this.load_content_finished) return;
this.imgClick.isExist
? this.imgClick.changPic(true, this.show_status, f)
: this.nextNode
? (this.changeContent(this.nextNode),
this.autoReader_mode &&
(this.autoScroll.scrollEnd = false))
: this.autoReader_mode &&
(this.autoScroll.scrollEnd = true);
},
Previous(f) {
if (!this.load_content_finished) return;
this.imgClick.isExist
? this.imgClick.changPic(false, this.show_status, f)
: this.prevNode &&
(this.autoReader_mode &&
(this.autoScroll.scrollEnd = false),
this.changeContent(this.prevNode));
},
get nav() {
return document.getElementById("reader_navigator");
},
get full() {
return document.getElementById("artfullscreen");
},
/**
* @param {boolean} mode
*/
set Element_display(mode) {
const display = mode ? "block" : "none";
const n = this.nav;
n && (n.style.display = display);
const f = this.full;
f && (f.style.display = display);
const tool = this.Toolbar;
tool && (tool.style.display = display);
this.time_module.clock_paused = !mode;
},
ShowOrExit(mode) {
if (!mode && (this.isRunning || this.autoScroll.scrollState))
return;
this.Element_display = mode;
if (mode) return;
/*
exit reader mode, then move to the position of current node
wait the reader is hidden, scroll to current answer
*/
this.overFlow = false;
this.readerMode = mode;
if (document.title === "出了一点问题") {
confirm("the webpage has crashed, is reload?") &&
location.reload();
return;
}
this.curNode.offsetTop !== window.pageYOffset &&
(this.isSimple_page ||
this.nextNode ||
this.allAnswser_loaded) &&
setTimeout(() => this.curNode.scrollIntoView(), 300);
this.no_scroll
? change_Title("IGNORANCE IS STRENGTH")
: this.ctrl_click.call(zhihu, false);
},
// load lazy pic
loadLazy(node) {
const imgs = node.getElementsByTagName("img");
for (const img of imgs) {
const name = img.className;
name &&
name.endsWith("lazy") &&
img.src.startsWith("data:image/svg+xml") &&
(img.src = img.dataset.actualsrc);
}
},
// load the answer card information
answer_card_module: {
card_raw(info, href) {
const html = `
`;
return html;
},
load_card_info(curls, cnodes) {
const api = `https://www.zhihu.com/api/v4/link_card_infos?urls=${curls.join(
","
)}`;
xmlHTTPRequest(api).then(
(json) => {
if (typeof result === "string")
json = JSON.parse(json);
cnodes.forEach((c) => {
const info = json[c.url];
if (info)
c.node.outerHTML = this.card_raw(
info,
c.url
);
});
},
() => console.log("failed to load answer card info")
);
},
external_raw(info, href) {
const url = href.includes("link.zhihu.com/?target=")
? href.split("link.zhihu.com/?target=")[1]
: href;
const html = `
`;
return html;
},
check_data(a, node, href) {
// some card have contained the information
const info = a[0].dataset;
if (info.image) {
node.outerHTML = this.external_raw(
info,
decodeURIComponent(href)
);
return true;
}
return false;
},
get_card_answer(node) {
const cards = node.getElementsByClassName(
"RichText-LinkCardContainer"
);
if (cards.length === 0) return;
const curls = [];
const cnodes = [];
for (const card of cards) {
if (
card.getElementsByClassName(
"LinkCard-title loading"
).length === 0
)
continue;
const a = card.getElementsByTagName("a");
if (a.length > 0) {
const href = a[0].href;
if (!href || this.check_data(a, card, href))
continue;
cnodes.push({ url: href, node: card });
curls.push(href);
}
}
curls.length > 0 && this.load_card_info(curls, cnodes);
},
initial(node) {
this.get_card_answer(node);
},
},
/**
* @param {boolean} mode
*/
set overFlow(mode) {
document.documentElement.style.overflow = mode
? "hidden"
: "auto";
},
getAnswerItem(node) {
const item = node.getElementsByClassName(
"ContentItem AnswerItem"
);
return item.length > 0 ? item[0] : null;
},
//check the item whether is blocked
blockCheck(arg) {
if (
Object.prototype.toString.call(arg) ===
"[object HTMLCollection]"
) {
for (const e of arg) {
if (e.style.display === "none") continue;
const item = this.getAnswerItem(e);
if (item && item.style.display === "none") continue;
return e;
}
} else {
if (arg.style.display === "none") return null;
const item = this.getAnswerItem(arg);
if (item && item.style.display === "none") return null;
else return arg;
}
},
show_status: {
node: null,
show(f, tips, info) {
const html = `
${info.text + tips}...
`;
this.remove();
const anode = document.createElement("div");
f.appendChild(anode);
anode.outerHTML = html;
setTimeout(() => (this.node = f.lastElementChild), 0);
},
remove() {
if (this.node) {
this.timeID && clearTimeout(this.timeID);
this.node.remove();
this.node = null;
this.timeID = null;
this.istimeout = true;
this.backText = "";
this.backColor = "";
}
},
backText: "",
backColor: "",
changeTips(tips, time, info) {
if (this.node) {
if (this.istimeout) this.timeout(time);
else {
this.backText = this.node.innerText;
this.backColor = this.node.style.background;
this.timeID = setTimeout(() => {
this.timeID = null;
this.node.innerText = this.backText;
this.node.style.background = this.backColor;
}, time);
}
this.node.innerText = tips;
this.node.style.background = info.bgc;
}
},
auto_scroll_change(tips) {
this.node.innerText = tips;
},
timeout(time) {
this.timeID && clearTimeout(this.timeID);
this.timeID = setTimeout(
() => ((this.timeID = null), this.remove()),
time
);
},
timeID: null,
istimeout: true,
main(f, tips, type = 1, time = 2500, istimeout = true) {
const types = {
0: {
text: "",
bgc: "#FFBB59",
color: "#0A0A0D",
},
1: {
text: "Tips: ",
bgc: "#E1B5BA",
color: "#0A0A0D",
},
2: {
text: "Warning: ",
bgc: "#FF3300",
color: "#0A0A0D",
},
};
const info = types[type];
f
? this.show(f, tips, info)
: this.changeTips(info.text + tips, time, info);
this.istimeout &&
(this.istimeout = istimeout) &&
this.timeout(time);
},
},
get Toolbar() {
return document.getElementById("artfullscreen_toolbar");
},
allAnswser_loaded: false,
try_status: false,
load_more_status: false,
/**
* @param {{ parentNode: any; }} pnode
*/
set navPannel(pnode) {
//---------------------------------------check if the node has pre and next node
const className = pnode.className;
if (className === "QuestionAnswer-content") {
this.prevNode = null;
const list = document.getElementsByClassName("List-item");
this.nextNode =
list.length > 0 ? this.blockCheck(list) : null;
} else {
let next = pnode.nextElementSibling;
this.nextNode = null;
if (next) {
let nextName = next.className;
const arr = ["Pc-word", "List-item"];
let a = arr.indexOf(nextName);
let b = null;
while (a > -1) {
if (a === 0) {
b = next.nextElementSibling;
next.remove();
next = b;
}
if (this.blockCheck(next)) {
this.nextNode = next;
break;
} else {
next = next.nextElementSibling;
if (!next) break;
nextName = next.className;
a = arr.indexOf(nextName);
}
}
}
let pre = pnode.previousElementSibling;
this.prevNode = null;
if (pre) {
let pName = pre.className;
if (pName === "List-header") {
const c = document.getElementsByClassName(
"QuestionAnswer-content"
);
this.prevNode =
c.length > 0 ? this.blockCheck(c[0]) : null;
} else {
const arr = ["Pc-word", "List-item"];
let a = arr.indexOf(pName);
let b = null;
while (a > -1) {
if (a === 0) {
b = pre.previousElementSibling;
pre.remove();
pre = b;
}
if (this.blockCheck(pre)) {
this.prevNode = pre;
break;
} else {
pre = pre.previousElementSibling;
if (!pre) break;
pName = pre.className;
a = arr.indexOf(pName);
}
}
}
}
}
if (
this.allAnswser_loaded ||
(!this.nextNode &&
(this.isSimple_page ||
(this.allAnswser_loaded = this.is_scrollBottom)))
) {
this.isShowTips &&
this.show_status.main(
this.show_status.node ? null : this.full,
"all answers have been loaded"
);
this.isShowTips = false;
} else if (!(this.nextNode || this.try_status)) {
window.scrollTo(0, 0.75 * this.DDSH);
setTimeout(() => {
window.scrollTo(0, 0.98 * this.DDSH);
setTimeout(
() => (
(this.try_status = true),
(this.navPannel = pnode)
),
300
);
}, 300);
return;
}
this.changeNav(this.nav);
!this.isSimple_page &&
(this.display_load_more = !this.nextNode);
},
removeADs() {
const ads = document.getElementsByClassName("Pc-word");
let i = ads.length;
if (i > 0) for (i; i--; ) ads[i].remove();
},
backgroundImage_cache: {
_request(url) {
return new Promise((resolve, reject) => {
xmlHTTPRequest(url, 2500, "blob").then(
(blob) => {
const file = new FileReader();
file.readAsDataURL(blob);
file.onload = (result) =>
result.target.readyState === 2
? resolve(result.target.result)
: reject("file state");
file.onerror = (err) => {
console.log(err);
reject("file err");
};
},
(err) => {
console.log(err);
reject("xml err");
}
);
});
},
reader(f, url) {
this._request(url).then(
(base64) => {
f.style.backgroundImage = `url(${base64})`;
GM_setValue("bgpreader", base64);
colorful_Console.main(
{
title: "changeBGP",
content:
"background image has been cached successfully",
},
colorful_Console.colors.Tips
);
},
() =>
Notification(
"failed to get background image",
"Warning"
)
);
},
try_status: false,
article(back) {
if (GM_getValue("readerbgimg_mark")) return;
const url =
back ||
"https://www.cnblogs.com/skins/coffee/images/bg_body.gif";
this._request(url).then(
(base64) => {
GM_setValue("readerbgimg", base64);
GM_setValue("readerbgimg_mark", true);
console.log(
"background image has been cached successfully"
);
},
(err) => {
if (err.startsWith("file")) return;
if (this.try_status) {
colorful_Console(
{
title: "warning:",
content:
"failed to cach article background image",
},
colorful_Console.colors.warning
);
return;
}
this.try_status = true;
const b =
"https://img.meituan.net/csc/decb7d168d512de5341614a7e22b26e848725.gif";
this.article(b);
}
);
},
},
nextNode: null,
prevNode: null,
curNode: null,
aid: null,
qid: null,
isSimple_page: false,
isShowTips: false,
initial_id: null,
ctrl_click(mode) {
this.Filter.isReader = mode;
!(mode || location.href.endsWith("#")) &&
(this.Filter.is_jump = true);
},
get is_scrollBottom() {
return (
document.getElementsByClassName(
"Button QuestionAnswers-answerButton Button--blue Button--spread"
).length > 0
);
},
get DDSH() {
return document.documentElement.scrollHeight;
},
initial_set(p) {
setTimeout(() => {
this.overFlow = true;
this.navPannel = p;
}, 100);
},
Change(node, aid) {
aid === this.aid
? this.ShowOrExit(true)
: this.changeContent(node, false);
},
load_content_finished: true,
no_scroll: false,
set_current_Type(node, aid) {
const a = node
.getElementsByClassName("ContentItem-title")[0]
.getElementsByTagName("a")[0];
if (!aid || (aid && this.aid !== aid)) {
const href = a.href;
this.content_type = href.includes("zhuanlan")
? "article"
: href.includes("daily")
? "daily"
: "answer";
this.qid =
this.content_type === "answer"
? href.match(/(?<=question\/)\d+/)[0]
: null;
}
change_Title(a.innerText);
},
main(pnode, aid, mode = false) {
this.initial_id && clearTimeout(this.initial_id);
this.initial_id = setTimeout(() => {
this.initial_id = null;
this.load_content_finished = false;
if (this.no_scroll) {
this.set_current_Type(pnode, aid);
this.isSimple_page = true;
} else {
const pathname = location.pathname;
if (this.firstly) {
this.content_type = "answer";
this.qid = pathname.match(/(?<=question\/)\d+/)[0];
}
this.isSimple_page = pathname.includes("/answer/");
this.removeADs();
this.ctrl_click.call(zhihu, true);
}
this.curNode =
pnode.className === "ContentItem AnswerItem"
? pnode.parentNode.parentNode
: pnode;
this.firstly ? this.Reader(pnode) : this.Change(pnode, aid);
this.aid = aid;
if (this.isSimple_page) this.initial_set(this.curNode);
else {
if ((this.allAnswser_loaded = this.is_scrollBottom))
this.initial_set(this.curNode);
else {
setTimeout(() => {
window.scrollTo(0, 0.75 * this.DDSH);
setTimeout(
() => this.initial_set(this.curNode),
300
);
}, 100);
}
}
this.firstly = false;
this.readerMode = true;
this.isShowTips = true;
mode && (this.autoScroll.is_nurse = true);
setTimeout(() => {
this.full.focus();
if (mode) {
this.autoScroll.is_nurse = false;
this.autoReader();
this.autoScroll.keyCount = 2;
this.autoScroll.start();
this.load_content_finished = true;
} else this.load_content_finished = true;
}, 1500);
//shift focus to reader, whick can make navigator control key can scroll the page
}, 300);
},
},
getData() {
blackName = GM_getValue("blackname");
if (blackName && Array.isArray(blackName)) {
if (blackName.length > 0) {
const bn = GM_getValue("clear_blackname");
if (!bn) {
blackName = blackName.map((e) => clear_zero_width(e));
GM_setValue("blackname", blackName);
GM_setValue("clear_blackname", true);
}
}
} else blackName = [];
blackTopicAndQuestion = GM_getValue("blacktopicAndquestion");
(!blackTopicAndQuestion || !Array.isArray(blackTopicAndQuestion)) &&
(blackTopicAndQuestion = []);
},
clipboardClear: {
clear(text) {
const cs = [
/。/g,
/:/g,
/;/g,
/?/g,
/!/g,
/(/g,
/)/g,
/“/g,
/”/g,
/、/g,
/,/g,
/《/g,
/》/g,
];
const es = [
". ",
": ",
"; ",
"? ",
"! ",
"(",
")",
'"',
'"',
", ",
", ",
"<",
">",
];
cs.forEach((s, i) => (text = text.replace(s, es[i])));
this.write(text);
},
write(text) {
window.navigator.clipboard.writeText(text);
},
replace_ZH: true,
event() {
this.replace_ZH =
GM_getValue("clipboard") === false ? false : true;
document.oncopy = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
const copytext = getSelection();
copytext &&
(this.replace_ZH
? this.clear(copytext)
: this.write(copytext));
};
},
},
turnPage: {
main(mode) {
const overlap = 100;
const wh = window.innerHeight;
let height = wh - overlap;
height < 0 && (height = 0);
let top =
document.documentElement.scrollTop ||
document.body.scrollTop ||
window.pageYOffset;
if (mode) top += height;
else top < height ? (top = 0) : (top -= height);
window.scrollTo(0, top);
},
start(mode) {
//n => scroll down ; u => scroll top
window.requestAnimationFrame(this.main.bind(this, mode));
},
},
scroll: {
toTop() {
let hTop =
document.documentElement.scrollTop ||
document.body.scrollTop;
if (hTop === 0) return;
const rate = 8;
let sid = 0;
const scrollToTop = () => {
hTop =
document.documentElement.scrollTop ||
document.body.scrollTop;
if (hTop > 0) {
sid = window.requestAnimationFrame(scrollToTop);
window.scrollTo(0, hTop - hTop / rate);
} else {
sid !== 0 && window.cancelAnimationFrame(sid);
}
};
scrollToTop();
},
toBottom() {
//take care this, if the webpage adopts waterfall flow design
const height =
document.documentElement.scrollHeight ||
document.body.scrollHeight;
const sTop =
document.documentElement.scrollTop ||
document.body.scrollTop;
if (sTop >= height) return;
let sid = 0;
let shTop = 0;
let rate = 6;
const initial = 100;
const scrollToBottom = () => {
const hTop =
document.documentElement.scrollTop ||
document.body.scrollTop ||
initial;
if (hTop < height && hTop > shTop) {
shTop = hTop;
sid = window.requestAnimationFrame(scrollToBottom);
window.scrollTo(0, hTop + hTop / rate);
rate += 0.2;
} else {
sid !== 0 && window.cancelAnimationFrame(sid);
sid = 0;
rate = 6;
}
};
scrollToBottom();
},
},
multiSearch: {
main(keyCode, keyword) {
const Names = {
65: "AboutMe",
68: "Douban",
71: "Google",
73: "Install",
72: "Github",
77: "MDN",
66: "BiliBili",
90: "Zhihu",
80: "Python",
69: "Ecosia",
1002: "Douban_movie",
1001: "Douban_book",
};
const methods = {
Protocols: "https://",
string_length(str) {
const lg = [...str].reduce(
(length, e) =>
(length +=
e.charCodeAt(0).toString(16).length === 4
? 2
: 1),
0
);
return Math.floor(lg / 2);
},
Search(url, parameter = "") {
//baidu restrict the length of search keyword is 38;
const select = keyword || getSelection();
if (!select) return;
else {
const reg = /[\u4e00-\u9fa5]/;
const f = reg.test(select);
if (
f
? this.string_length(select) > 38
: select.length > 76
) {
Notification(
"the length of keyword is too long",
"Tips"
);
return;
}
if (f && blackKey.includes(select)) {
Notification(
"your keyword contains rubbish word; don't search rubbish",
"Warning",
3500
);
colorful_Console.main(
{ title: "rubbish:", content: select },
colorful_Console.colors.warning
);
return;
}
}
url += encodeURIComponent(select);
GM_openInTab(this.Protocols + url + parameter, {
insert: true,
active: true,
});
},
Douban_movie() {
this.Search(
"search.douban.com/movie/subject_search?search_text=",
"&cat=1002"
);
},
Douban_book() {
this.Search(
"search.douban.com/book/subject_search?search_text=",
"&cat=1001"
);
},
Ecosia() {
this.Search("www.ecosia.org/search?q=");
},
Google() {
this.Search("www.bing.com/search?q=");
},
Douban() {
this.Search("www.douban.com/search?q=");
},
Zhihu() {
this.Search("www.zhihu.com/search?q=", "&type=content");
},
MDN() {
this.Search("developer.mozilla.org/zh-CN/search?q=");
},
Github() {
this.Search("github.com/search?q=");
},
BiliBili() {
this.Search("search.bilibili.com/all?keyword=");
},
Install() {
this.Search("pypi.org/search/?q=");
},
Python() {
this.Search(
"docs.python.org/zh-cn/3/search.html?q=",
"&check_keywords=yes&area=default"
);
},
AboutMe() {
zhihu.shade.Support.main();
},
};
const name = Names[keyCode];
name && methods[name]();
},
checkCode(c) {
const code = c.charCodeAt(0);
return code > 97 && code < 123
? code - 32
: code > 65 && code < 91
? code
: 0;
},
get Searchbar() {
const header =
document.getElementsByClassName("Sticky AppHeader");
if (header.length === 0) return "";
const input = header[0].getElementsByTagName("input");
return input.length === 0 ? "" : input[0].defaultValue.trim();
},
m(keyword) {
const reg = /(?<=-)([a-z]|d[bm])\s/gi;
const m = (
keyword.slice(-2, -1) === "-" ? `${keyword} ` : keyword
).match(reg);
return m ? (m.length === 0 ? m[0] : [...new Set(m)]) : null;
},
last_time_s: "",
site() {
let keyword = prompt(
"support z, d, g, h, m, b, p, e,db, dm: like: z python; (search in zhihu); default: g",
getSelection() || this.Searchbar || this.last_time_s
);
if (!keyword || !(keyword = keyword.trim())) return true;
this.last_time_s = keyword;
if (keyword[0] === "$") {
keyword = keyword.slice(1);
const ms = this.m(keyword);
if (ms) {
const wreg = /(?<=-([a-z]|d[bm])\s)(?!-).+(?=[\s-\b])/i;
const tmp = (
keyword.slice(-2, -1) === "-"
? keyword
: `${keyword} `
).match(wreg);
if (tmp) {
ms.forEach((e, index) => {
setTimeout(() => {
let c = this.checkCode(e);
c &&
(c =
c === 68
? e[1]
? e[1].toLowerCase() === "b"
? 1001
: 1002
: c
: c) &&
this.main(c, tmp[0]);
}, index * 350);
});
} else Notification("failed to get keyword", "Warning");
return true;
}
}
const tmp = keyword.slice(0, 3).toLowerCase();
const i = ["db ", "dm "].indexOf(tmp);
if (i > -1) {
this.main(i === 0 ? 1001 : 1002, keyword.slice(3).trim());
return;
}
const reg = /[a-z]\s/i;
const code = this.checkCode(keyword[0]);
code && keyword.match(reg)
? this.main(code, keyword.slice(2).trim())
: this.main(71, keyword);
return true;
},
},
noteHighlight: {
editable: false,
disableSiderbar(pevent) {
const column = document.getElementById("column_lists");
if (column) column.style.pointerEvents = pevent;
},
EditDoc(status) {
const [edit, tips, pevent] = this.editable
? ["inherit", "exit", "inherit"]
: ["true", "enter", "none"];
document.body.contentEditable = edit;
Notification(tips + " page editable mode", "Editable");
this.disableSiderbar(pevent);
this.editable = !this.editable;
this.editable
? status.create("Editable Mode")
: status.remove();
},
get Selection() {
return window.getSelection();
},
setMark(text, type) {
return `${text}`;
},
get createElement() {
return document.createElement("markspan");
},
appendNewNode(node, type) {
const text = node.nodeValue;
const span = this.createElement;
node.parentNode.replaceChild(span, node);
span.outerHTML = this.setMark(text, type);
},
getTextNode(node, type) {
node.nodeType === 3 && this.appendNewNode(node, type);
},
Marker(keyCode) {
const cname = {
82: "red",
89: "yellow",
80: "purple",
71: "green",
};
const type = cname[keyCode];
if (!type) return;
const select = this.Selection;
if (!select.anchorNode || select.isCollapsed) return;
let i = select.rangeCount;
const r = select.getRangeAt(--i);
let start = r.startContainer;
const end = r.endContainer;
const offs = r.startOffset;
const offe = r.endOffset;
let nodeValue = r.startContainer.nodeValue;
if (start !== end) {
//start part
let next = start.nextSibling;
let p = start.parentNode;
if (!p.className.startsWith("AssistantMark")) {
const text = nodeValue.slice(offs);
const span = this.createElement;
p.replaceChild(span, start);
span.outerHTML =
nodeValue.slice(0, offs) + this.setMark(text, type);
}
//mid part
while (true) {
if (next) {
start = next;
} else {
next = p.nextSibling;
while (!next) {
p = p.parentNode;
next = p.nextSibling;
}
start = next;
}
//get the deepest level node
while (start.childNodes.length > 0)
start = start.childNodes[0];
if (start === end) break;
p = start.parentNode;
next = p.nextSibling;
!p.className.startsWith("AssistantMark") &&
this.getTextNode(start, type);
}
//end part
nodeValue = start.nodeValue;
start = start.parentNode;
if (start.className.startsWith("AssistantMark")) return;
const text = nodeValue.slice(0, offe);
const epan = this.createElement;
start.replaceChild(epan, end);
epan.outerHTML =
this.setMark(text, type) + nodeValue.slice(offe);
} else {
//all value in one node;
const text = nodeValue.slice(offs, offe);
const span = this.createElement;
start.parentNode.replaceChild(span, start);
span.outerHTML =
nodeValue.slice(0, offs) +
this.setMark(text, type) +
nodeValue.slice(offe);
}
},
Restore(node) {
const p = node.parentNode;
if (p.className.startsWith("AssistantMark")) {
p.parentNode.innerHTML = p.parentNode.innerText;
return true;
}
return false;
},
removeMark() {
const select = this.Selection;
if (!select.anchorNode || select.isCollapsed) return;
let i = select.rangeCount;
const r = select.getRangeAt(--i);
let start = r.startContainer;
const end = r.endContainer;
if (start !== end) {
let t = start.nodeType;
if (t !== 3 && r.collapsed) {
const nodes =
start.getElementsByClassName("AssistantMark");
let i = nodes.length;
if (i > 0) {
for (i; i--; ) {
const p = nodes[i].parentNode;
p.innerhHTML = p.innerText;
}
}
return;
}
while (start.childNodes.length > 0)
start = start.childNodes[0];
let p = start.parentNode.parentNode;
let next = start.nextSibling;
let result = this.Restore(start);
//if this is mark node, will be removed, so we need get the parentnode to backup, if it is not mark node, restore the parentnode
!result && (p = start.parentNode);
while (true) {
if (next) {
start = next;
} else {
next = p.nextSibling;
while (!next) {
p = p.parentNode;
next = p.nextSibling;
}
start = next;
}
while (start.childNodes.length > 0)
start = start.childNodes[0];
if (start === end) break;
p = start.parentNode.parentNode;
next = start.nextSibling;
result = this.Restore(start);
!result && (p = start.parentNode);
}
}
this.Restore(start);
},
},
autoScroll: {
stepTime: 40,
keyCount: 1,
scrollState: false,
scrollTime: null,
scrollPos: null,
bottom: 100,
zhuanlanAuto_mode: false,
pageScroll(TimeStamp) {
const position =
document.documentElement.scrollTop ||
document.body.scrollTop ||
window.pageYOffset;
if (this.scrollTime) {
this.scrollPos =
this.scrollPos !== null
? this.scrollPos +
(TimeStamp - this.scrollTime) / this.stepTime
: position;
window.scrollTo(0, this.scrollPos);
}
this.scrollTime = TimeStamp;
if (this.scrollState) {
let h =
document.documentElement.scrollHeight ||
document.body.scrollHeight;
h = h - window.innerHeight - this.bottom;
position < h
? window.requestAnimationFrame(
this.pageScroll.bind(this)
)
: this.stopScroll(true);
}
},
disableEvent(mode) {
const h = document.getElementsByClassName(
"RichText ztext Post-RichText"
);
if (h.length === 0) return;
h[0].style.pointerEvents = mode ? "none" : "inherit";
},
show_status: {
node: null,
show(tips) {
const html = `
Tips: ${tips}...
`;
const anode = document.createElement("div");
document.documentElement.appendChild(anode);
anode.outerHTML = html;
setTimeout(
() =>
(this.node =
document.documentElement.lastElementChild),
0
);
},
remove() {
if (this.node) {
this.node.remove();
this.node = null;
}
},
create(tips) {
this.show(tips);
},
},
zhuanlanAuto() {
if (!this.zhuanlanAuto_mode && zhihu.Column.targetIndex === 0) {
Notification("current article is not in left menu");
return;
}
this.zhuanlanAuto_mode = !this.zhuanlanAuto_mode;
const text = `${
this.zhuanlanAuto_mode ? "enter" : "exit"
} autoscroll mode`;
Notification(text, "Tips");
this.zhuanlanAuto_mode
? this.show_status.create("Auto Mode")
: this.show_status.remove();
},
get article_lists() {
const c = document.getElementById("column_lists");
if (!c) return null;
const a = c.getElementsByClassName("article_lists");
return a.length === 0 ? null : a[0].children;
},
nextPage() {
const ch = this.article_lists;
if (!ch) return;
const i = zhihu.Column.targetIndex;
if (ch.length === 0 || i === ch.length) {
Notification(
"no more content, have reach the last page of current menu",
"tips"
);
return;
}
ch[i].children[2].click();
setTimeout(() => {
this.keyCount = 2;
this.start();
}, 2500);
},
//0-9 => click target article;
key_Click(keyCode) {
const ch = this.article_lists;
let index = keyCode - 47;
if (index > ch.length) return;
ch[--index].children[2].click();
},
key_next_Pre(keyCode) {
const c = document.getElementById("column_lists");
if (!c) return;
const n = c.getElementsByClassName("nav button");
let i = keyCode - 188;
n.length > 0 &&
(i === 0
? n[0].children[i].click()
: n[0].children[--i].click());
},
popup() {
createPopup();
const tips = document.getElementById("autoscroll-tips");
let buttons = tips.getElementsByTagName("button");
const id = setTimeout(() => {
tips.remove();
zhihu.scroll.toTop();
this.nextPage();
}, 3500);
buttons[0].onclick = () => {
clearTimeout(id);
tips.remove();
zhihu.scroll.toTop();
this.nextPage();
};
buttons[1].onclick = () => {
clearTimeout(id);
tips.remove();
};
buttons = null;
},
stopScroll(mode = false) {
if (this.scrollState) {
this.scrollPos = null;
this.scrollTime = null;
this.scrollState = false;
this.keyCount = 1;
}
if (mode) {
this.disableEvent(false);
this.zhuanlanAuto_mode &&
setTimeout(() => this.popup(), 1500);
}
},
speedUP() {
this.stepTime < 10 ? (this.stepTime = 5) : (this.stepTime -= 5);
},
slowDown() {
this.stepTime > 100
? (this.stepTime = 100)
: (this.stepTime += 5);
},
start() {
this.keyCount += 1;
if (this.keyCount % 2 === 0) return;
this.scrollState
? (this.stopScroll(), this.disableEvent(false))
: ((this.scrollState = true),
this.disableEvent(true),
window.requestAnimationFrame(this.pageScroll.bind(this)));
},
noColorful() {
const color = GM_getValue("nocolofultext");
if (color) {
GM_setValue("nocolofultext", false);
const text = "the feature of colorful text has been enable";
zhihu.colorAssistant.main();
Notification(text, "Tips");
} else {
if (!confirm("disable colorful text?")) return;
GM_setValue("nocolofultext", true);
const text = "colorful text has been disable";
Notification(text, "Tips");
confirm("reload current webpage?") &&
(sessionStorage.clear(), location.reload());
}
},
Others(keyCode, shift, auto) {
shift
? keyCode === 67
? this.noteHighlight.removeMark()
: keyCode === 219
? auto
? Notification(
"please exit auto mode firstly",
"Tips"
)
: this.Column.pagePrint(this.autoScroll.show_status)
: keyCode === 70
? !this.autoScroll.scrollState && this.Column.follow()
: keyCode === 76
? this.Column.columnsModule.recentModule.log("p")
: keyCode === 83
? this.Column.subscribe()
: keyCode === 68
? this._top_Picture.main()
: this.noteHighlight.Marker(keyCode)
: keyCode === 113
? !this.autoScroll.scrollState &&
this.noteHighlight.EditDoc(this.autoScroll.show_status)
: keyCode === 78
? !this.autoScroll.scrollState && this.turnPage.start(true)
: keyCode === 84
? !this.autoScroll.scrollState && this.scroll.toTop()
: keyCode === 82
? !this.autoScroll.scrollState && this.scroll.toBottom()
: keyCode === 85
? !this.autoScroll.scrollState && this.turnPage.start(false)
: this.multiSearch.main(keyCode);
},
check_common_key(shift, keyCode) {
return this.common_KeyEevnt(shift, keyCode, 5);
},
key_conflict(keyCode, shift) {
return (
68 === keyCode || (shift && [85, 71, 67].includes(keyCode))
);
},
keyBoardEvent() {
document.addEventListener(
"keydown",
(e) => {
const keyCode = e.keyCode;
if (e.ctrlKey || e.altKey) return;
const className = e.target.className;
if (
(className &&
typeof className === "string" &&
className.includes("DraftEditor")) ||
e.target.localName === "input"
)
return;
const shift = e.shiftKey;
if (this.key_conflict(keyCode, shift)) {
e.preventDefault();
e.stopPropagation();
}
if (this.check_common_key.call(zhihu, shift, keyCode))
return;
shift
? keyCode === 65
? zhihu.Column.modePrint
? Notification(
"please exit print mode firstly",
"Tips"
)
: this.zhuanlanAuto()
: keyCode === 66
? !this.scrollState &&
MangeData.exportData.main(false)
: keyCode === 84
? this.noColorful()
: keyCode === 73
? !this.scrollState &&
MangeData.importData.main(true)
: this.Others.call(
zhihu,
keyCode,
shift,
this.zhuanlanAuto_mode
)
: keyCode === 192
? this.start()
: keyCode === 187
? this.speedUP()
: keyCode === 189
? this.slowDown()
: keyCode > 47 && keyCode < 58
? !this.scrollState && this.key_Click(keyCode)
: keyCode === 188 || keyCode === 190
? this.key_next_Pre(keyCode)
: this.Others.call(zhihu, keyCode);
},
true
);
},
},
shade: {
Support: {
interval: 0,
support: null,
tips: null,
opacity: null,
opacityChange(opacity) {
const target =
document.getElementById("screen_shade_cover");
target &&
(this.opacity === null
? (this.opacity = target.style.opacity)
: target.style.opacity !== opacity) &&
(target.style.opacity = opacity);
},
share_weibo() {
const url = `https%3A%2F%2Fservice.weibo.com%2Fshare%2Fshare.php%3Furl%3D${
Assist_info_URL.greasyfork
}%26title%3D%E4%B9%9F%E8%AE%B8%E8%BF%99%E6%98%AF%E9%92%88%E5%AF%B9%E7%9F%A5%E4%B9%8E%E6%9C%80%E6%A3%92%E7%9A%84GM%E6%B2%B9%E7%8C%B4%E8%84%9A%E6%9C%AC...%26summery%3Dundefined%26pic%3D${
Assist_info_URL.Overview
}%23_loginLayer_${Date.now()}`;
GM_openInTab(decodeURIComponent(url), {
insert: true,
active: true,
});
},
creatPopup() {
this.opacityChange(0);
const mt = -5;
const html = `
Support Me!
Shortcuts
|| Version: ${GM_info.script.version}
Make Thing Better && Simpler!
15s, this Tips will be automatically closed or can you just click
Github: Kyouichirou
`;
document.documentElement.insertAdjacentHTML(
"beforeend",
html
);
this.support = document.getElementById("support_me");
this.tips =
this.support.getElementsByClassName("timeout")[0];
let time = 15;
this.interval = setInterval(() => {
time--;
this.tips.innerText = `${time}s, this Tips will be automatically closed or you can just click`;
time === 0 && this.remove();
}, 1000);
this.support.onclick = (e) =>
e.target.localName === "i"
? this.share_weibo()
: e.target.localName !== "a" &&
setTimeout(() => this.remove(), 120);
},
remove() {
clearInterval(this.interval);
this.opacityChange(this.opacity);
this.opacity = null;
this.interval = null;
this.support.remove();
this.support = null;
this.tips = null;
},
main() {
this.support ? this.remove() : this.creatPopup();
},
},
cover(color, opacity = 0.5) {
const html = `
`;
const cnode = document.documentElement;
cnode && cnode.insertAdjacentHTML("afterbegin", html);
},
menu(e) {
const target = document.getElementById("screen_shade_cover");
target &&
target.style.background !== this[e] &&
(target.style.background = this[e]) &&
arguments.length === 2 &&
GM_setValue("color", e);
GM_deleteValue("tmp_cover");
},
get opacity() {
const date = new Date();
const m = date.getMonth();
const h = date.getHours();
const [start, a] = m > 9 ? [15, 0.08] : [16, 0.12];
let opacity =
h > 20
? h > 22
? 0.6
: 0.5
: h < 8
? 0.65
: h > start
? h === 18
? 0.35
: h === 19
? 0.45
: h === 20
? 0.5
: 0.3
: 0.15;
return (opacity += opacity < 0.2 ? 0 : a);
},
opacityMonitor() {
const opacity = GM_getValue("opacity");
const target = document.getElementById("screen_shade_cover");
target &&
opacity &&
target.style.opacity !== opacity &&
(target.style.opacity = opacity);
},
supportID: null,
SupportMenu() {
this.supportID = GM_registerMenuCommand(
"Support || Donation",
this.Support.main.bind(this.Support),
"d4"
);
},
disableShade: {
id: null,
cmenu() {
this.id = GM_registerMenuCommand(
"Switch",
this.func.bind(this),
"s5"
);
},
rmenu() {
GM_unregisterMenuCommand(this.id);
},
func() {
const target =
document.getElementById("screen_shade_cover");
target &&
(target.style.display =
target.style.display === "block"
? "none"
: "block");
},
},
menuID: null,
Switchfunc() {
const target = document.getElementById("screen_shade_cover");
let result = false;
if (target) {
if (arguments.length > 0 && !arguments[0]) return;
target.remove();
result = true;
this.disableShade.rmenu();
let i = this.menuID.length;
GM_removeValueChangeListener(this.opacitylistenID);
GM_removeValueChangeListener(this.colorlistenID);
for (i; i--; ) GM_unregisterMenuCommand(this.menuID[i]);
this.menuID = null;
} else {
//rebuild menu
if (arguments.length > 0 && arguments[0]) return;
if (this.menuID) return;
GM_unregisterMenuCommand(this.switchID);
GM_unregisterMenuCommand(this.supportID);
this.createShade();
this.SwitchMenu();
this.SupportMenu();
}
arguments.length === 0 && GM_setValue("turnoff", result);
},
switchID: null,
SwitchMenu() {
this.switchID = GM_registerMenuCommand(
"Turn(On/Off)",
this.Switchfunc.bind(this),
"t6"
);
},
turnoffID: null,
start() {
!GM_getValue("turnoff") && this.createShade();
this.SwitchMenu();
this.SupportMenu();
this.turnoffID = GM_addValueChangeListener(
"turnoff",
(name, oldValue, newValue, remote) => {
if (!remote || oldValue === newValue) return;
this.Switchfunc(newValue, true);
}
);
},
colorlistenID: null,
opacitylistenID: null,
get tmp_cover() {
const tc = GM_getValue("tmp_cover");
if (!tc) return null;
if (tc.status !== "a") return null;
const u = tc.update;
if (!u) return null;
const r = tc.rtime;
if (!r) return null;
if (Date.now() - u > r) return null;
return tc;
},
sing_protect: false,
tmp_c(color) {
const c = document.getElementById("screen_shade_cover");
c.style.background = color;
},
createShade() {
const colors = {
yellow: "rgb(247, 232, 176)",
green: "rgb(202 ,232, 207)",
grey: "rgb(182, 182, 182)",
olive: "rgb(207, 230, 161)",
};
const tc = this.tmp_cover;
let color = GM_getValue("color");
color && (color = colors[color]);
tc && tc.color && (color = tc.color);
if (!color) {
const h = new Date().getHours();
color = h > 8 && h < 17 ? colors.yellow : colors.grey;
}
const opacity =
(tc && tc.opacity && tc.opacity) || this.opacity;
this.cover(color, opacity);
const UpperCase = (e) =>
e.slice(0, 1).toUpperCase() + e.slice(1);
this.menuID = [];
for (const c of Object.entries(colors)) {
const id = GM_registerMenuCommand(
UpperCase(c[0]),
this.menu.bind(colors, c[0], true),
c[0]
);
this.menuID.push(id);
}
//note, who is the "this" in the GM_registerMenuCommand? take care of "this", must bind (function => this)
this.colorlistenID = GM_addValueChangeListener(
"color",
(name, oldValue, newValue, remote) => {
if (
!remote ||
oldValue === newValue ||
this.sing_protect
)
return;
newValue.startsWith("#")
? this.tmp_c(newValue)
: this.menu.call(colors, newValue);
}
);
GM_setValue("opacity", opacity);
this.opacitylistenID = GM_addValueChangeListener(
"opacity",
(name, oldValue, newValue, remote) =>
remote &&
!this.sing_protect &&
oldValue !== newValue &&
this.opacityMonitor()
);
this.disableShade.cmenu();
!tc && GM_deleteValue("tmp_cover");
},
},
settings_Popup: {
/*
1. adopted from https://greasyfork.org/zh-CN/scripts/27752-searchenginejump
2. part of html and css is adpoted;
*/
node: null,
create() {
const html = `
`;
document.body.insertAdjacentHTML("beforeend", html);
this.event();
},
event() {
setTimeout(() => {
this.node = document.getElementById("settingLayerMask");
this.node.onclick = (e) => {
const id = e.target.id;
id && id.includes("close") && this.remove();
};
}, 50);
},
remove() {
if (this.node) {
this.node.remove();
this.node = null;
}
},
main() {
this.node ? this.remove() : this.create();
},
},
white_noise: {
get Index() {
const index = GM_getValue("white_noise");
return typeof index === "number" ? index : 0;
},
get_musice(index) {
const audio_sources = [
"rain_sound_1",
"rain_sound_2",
"white_noise_1",
"river_stream_1",
"campfire_1",
"winter_traffic_1",
"ocean_waves_1",
"blizzard_1",
"forest_wind_1",
"crickets_1",
];
const pref = "https://noizzze.com/audio/";
const suffix = ".mp3";
const f = index > audio_sources.length - 1;
f && GM_setValue("white_noise", 0);
return pref + audio_sources[f ? 0 : index] + suffix;
},
audio_ctrl: {
audio: null,
volume: 0,
play_pause() {
this.audio && this.audio.paused
? this.audio.play()
: this.audio.pause();
return true;
},
voice_up_down(e) {
if (!this.audio) return true;
let vx = this.volume;
if (e) {
if (vx >= 1) return true;
vx += 0.1;
} else {
if (vx <= 0) return true;
vx -= 0.1;
}
this.audio.volume = vx;
this.volume = vx;
return true;
},
},
set_audio(audio, mode = false) {
audio.loop = true;
this.audio_ctrl.volume = audio.volume;
audio.oncanplay = () => (mode ? (mode = false) : audio.play());
audio.onerror = (e) => {
console.log(e);
Notification(
"failed to load mp3 of white noise",
"Warning"
);
};
},
just_audio() {
const audio = document.createElement("audio");
audio.src = this.get_musice(this.Index);
this.set_audio(audio);
Notification("create audio successfully", "Tips");
this.audio_ctrl.audio = audio;
},
destroy_audio() {
if (this.audio_ctrl.audio) {
this.audio_ctrl.audio.pause();
this.audio_ctrl.audio = null;
Notification("the audio has been destroyed", "Tips");
} else this.just_audio();
return true;
},
create(node) {
const html = `
`;
node.insertAdjacentHTML("afterbegin", html);
this.event(node);
},
change_music(mode) {
if (!this.audio_ctrl.audio) return true;
const index = this.Index + 1;
this.audio_ctrl.audio.src = this.get_musice(index);
GM_setValue("white_noise", index);
mode &&
Notification("change style of white noise successfully");
return true;
},
event(node) {
setTimeout(() => {
let f = node.firstElementChild.firstElementChild;
f.onclick = () => this.change_music();
const audio = f.nextElementSibling;
this.set_audio(audio, true);
this.audio_ctrl.audio = audio;
f = null;
}, 50);
},
},
create_settings(node) {
const html = `
`;
let f = node.previousElementSibling.getElementsByClassName(
"ColumnPageHeader-Button"
)[0];
let n = f.firstElementChild;
n.innerText = "Note";
n.title = "right mouse click to open note";
n.oncontextmenu = (e) => {
e.preventDefault();
this.settings_Popup.main();
};
n = null;
f.insertAdjacentHTML("afterend", html);
setTimeout(() => {
f.nextElementSibling.onclick = (e) =>
this.settings_Popup.main();
this.white_noise.create(f.parentNode);
f = null;
}, 50);
},
column_homePage(mode) {
// 这个css被从页面移除掉了
const css = `
`;
document.head.insertAdjacentHTML("beforeend", css);
const ch = document.getElementsByClassName("ColumnHome")[0];
let chs = ch.children;
let i = chs.length;
if (i === 0) {
colorful_Console.main(
{
title: "warning",
content: "failed to get target element",
},
colorful_Console.colors.warning
);
return;
}
for (i; i--; ) {
if (chs[i].className === "ColumnHomeTop") break;
else chs[i].remove();
}
let k = chs[i].children.length;
for (k; k--; ) chs[i].children[k].remove();
ch.style.display = "block";
if (mode) {
const r = GM_getValue("recent");
if (!this.Column.home_Module.loaded_list)
this.Column.home_Module.loaded_list = [];
if (r && Array.isArray(r) && r.length > 0) {
r.forEach((e) => {
const url = e.url;
const id = url.slice(url.lastIndexOf("/") + 1);
if (
!zhihu.Column.home_Module.loaded_list.includes(id)
) {
this.Column.home_Module.loaded_list.push(id);
column_Home.single_Content_request(
url.includes("answer") ? 0 : 2,
id,
chs[i],
e
);
}
});
}
}
const targetElements = this.Filter.getTagetElements(1);
this.qaReader.no_scroll = true;
chs[i].onclick = (e) => {
const target = e.target;
const item = this.Filter.check_click_all(
target.className,
target.localName,
target,
targetElements
);
if (!item) return;
this.qaReader.main(item, this.Filter.foldAnswer.getid(item));
};
this.create_settings(chs[i]);
chs = null;
this.QASkeyBoardEvent(9);
unsafeWindow.addEventListener("visibilitychange", () =>
this.visibleChange()
);
this.rightMouse_OpenQ(9);
},
antiRedirect() {
// only those links can be capture, which has the attribute of classname with ' external' ?
// some problems in video tag
const links = Object.getOwnPropertyDescriptors(
HTMLAnchorElement.prototype
).href;
Object.defineProperty(HTMLAnchorElement.prototype, "href", {
...links,
get() {
const href = decodeURIComponent(links.get.call(this));
const tmp = href.split("link.zhihu.com/?target=");
if (tmp.length > 1) {
this.href = tmp[1];
return tmp[1];
}
return href;
},
});
},
//if has logined or the login window is not loaded(or be blocked) when the page is loaded;
hasLogin: false,
is_delayed: false,
antiLogin() {
/*
note:
the timing of the js injection is uncertain, and for some reason the injection maybe late,
so that the occurrence of the event cannot be accurately captured
don't use dom load event =>
most of zhihu webpages require login
*/
let mo = new MutationObserver((events) => {
if (this.hasLogin) {
mo.disconnect();
mo = null;
document.documentElement.style.overflow = "auto";
return;
}
events.forEach((e) =>
e.addedNodes.forEach((node) => {
const type = node.nodeType;
if (
(type === 1 || type === 9 || type === 11) &&
node.getElementsByClassName("signFlowModal")
.length > 0
) {
node.style.display = "none";
setTimeout(() => {
this.is_delayed = false;
mo.disconnect();
mo = null;
node.remove();
document.documentElement.style.overflow =
"auto";
}, 0);
}
})
);
});
document.body
? mo.observe(document.body, { childList: true })
: (document.onreadystatechange = () =>
mo && mo.observe(document.body, { childList: true }));
setTimeout(() => (this.is_delayed = true), 10000);
},
//the original js(int.js) of zhihu, which will cause stuck autoscroll
anti_setInterval() {
unsafeWindow.setInterval = new Proxy(unsafeWindow.setInterval, {
apply: (target, thisArg, args) => {
const f = args[0];
let fn = "";
f && (fn = f.name);
fn &&
fn === "i" &&
args[1] === 2000 &&
(args[1] = 10000000);
return target.apply(thisArg, args);
},
});
},
Filter: {
/*
1. userName
2. question
3. answer
4. article
5. content keyword
*/
//click the ico of button
svgCheck(node, targetElements) {
let pnode = node.parentNode;
if (pnode.className === targetElements.buttonClass)
return pnode;
else {
pnode = pnode.parentNode;
let className = pnode.className;
let ic = 0;
while (className !== targetElements.buttonClass) {
pnode = pnode.parentNode;
if (!node || ic > 3) return null;
className = pnode.className;
ic++;
}
return pnode;
}
},
getiTem(target, targetElements) {
let item = target.parentNode;
if (item.className === targetElements.itemClass) {
return item;
} else {
item = item.parentNode;
let ic = 0;
let className = item.className;
while (className !== targetElements.itemClass) {
item = item.parentNode;
if (!item || ic > 3) return null;
className = item.className;
ic++;
}
return item;
}
},
//get the url id of the answer || article
getTargetID(item) {
const a = item.getElementsByTagName("a");
if (a.length === 0) return null;
const pathname = a[0].pathname;
return pathname.slice(pathname.lastIndexOf("/") + 1);
},
/*
0, normal
1, searchpage => check username
2, click => check all content
3, article page => check expand
if the item has been checked, return
*/
get Topic_question_ID() {
const href = location.href;
const reg = /(?<=(question|topic)\/)\d+/;
const match = href.match(reg);
if (!match) return null;
return match[0];
},
Topic_questionButton(targetElements) {
const header_line = (event, mode) => {
let question = null;
if (event) {
const path = event.path;
for (const e of path) {
if (e.className === targetElements.headerID) {
question = e;
break;
}
}
} else {
question = document.getElementsByClassName(
targetElements.headerID
)[0];
}
if (!question) return;
const h = question.getElementsByClassName(
targetElements.headerTitle
);
if (h.length === 0) return;
h[0].style.textDecoration = mode ? "line-through" : "none";
};
let q = document.getElementsByClassName(
targetElements.inserButtonID
);
if (q.length === 0) return;
const id = this.Topic_question_ID;
if (!id) return;
let mode = false;
const type = targetElements.index === 2 ? "topic" : "question";
let [name, title] = (mode = blackTopicAndQuestion.some(
(e) => e.id === id
))
? ["Remove", `remove the ${type} from block list`]
: ["Block", `add the ${type} to block list`];
const html = `
`;
let button = document.createElement("button");
q[0].insertAdjacentElement("beforeend", button);
button.outerHTML = html;
q[0].lastChild.onclick = function (event) {
if (mode) {
const index = blackTopicAndQuestion.findIndex(
(e) => e.id === id
);
if (index > -1) {
blackTopicAndQuestion.splice(index, 1);
GM_setValue(
targetElements.blockValueName,
blackTopicAndQuestion
);
}
} else {
if (blackTopicAndQuestion.some((e) => e.id === id))
return;
const info = {};
info.id = id;
info.type = type;
info.update = Date.now();
blackTopicAndQuestion.push(info);
GM_setValue(
targetElements.blockValueName,
blackTopicAndQuestion
);
}
mode = !mode;
[name, title] = mode
? ["Remove", `remove the ${type} from block list`]
: ["Block", `add the ${type} to block list`];
this.title = title;
this.innerText = name;
header_line(event, mode);
};
button = null;
q = null;
mode && header_line(null, true);
GM_addValueChangeListener(
targetElements.blockValueName,
(name, oldValue, newValue, remote) =>
remote && (blackTopicAndQuestion = newValue)
);
},
get_content_element(item, targetElements) {
const content = item.getElementsByClassName(
targetElements.contentID
);
return content.length === 0 ? null : content[0];
},
content_check(item, targetElements, node) {
const content =
node || this.get_content_element(item, targetElements);
if (!content) return false;
const text = content.innerText;
return text.length > 2500
? false
: blackKey.some((e) => {
if (text.includes(e)) {
colorful_Console.main(
{
title: "content block:",
content: "rubbish word " + e,
},
colorful_Console.colors.warning
);
this.hidden_item(item, targetElements);
return true;
}
return false;
});
},
hidden_item(item, targetElements) {
(item.className === "ContentItem AnswerItem"
? targetElements.index === 3
? item.parentNode.parentNode
: item.parentNode
: item
).style.display = "none";
},
user_check(item, targetElements) {
const user = item.getElementsByClassName(targetElements.userID);
if (user.length === 0) {
colorful_Console.main(
{ title: "anonymous user", content: "no user info" },
colorful_Console.colors.info
);
return false;
}
const i = user.length - 1;
const name = user[i > 1 ? 1 : i].innerText;
if (!name) return false;
const nwname = clear_zero_width(name);
const result = blackName.includes(nwname);
if (result) {
colorful_Console.main(
{ title: "Blocked User", content: nwname },
colorful_Console.colors.warning
);
this.hidden_item(item, targetElements);
}
return result;
},
search_check(item, targetElements) {
const content = this.get_content_element(item, targetElements);
if (!content) return false;
let user = content.firstElementChild.innerText;
if (!user) return false;
user = clear_zero_width(user);
if (blackName.includes(user)) {
colorful_Console.main(
{ title: "Blocked User", content: user },
colorful_Console.colors.warning
);
this.hidden_item(item, targetElements);
return true;
}
return this.content_check(item, targetElements, content);
},
get_main_element(item) {
return item.className === "ContentItem AnswerItem"
? item
: item.getElementsByClassName("ContentItem AnswerItem")[0];
},
check_Hot(item) {
const hot_list = ["TimeBox-MainContent", "MinorHotSpot"];
return hot_list.some(
(e) => item.getElementsByClassName(e).length > 0
);
},
check_salt(item) {
return (
item.getElementsByClassName(
"KfeCollection-OrdinaryLabel-content"
) > 0
);
},
check(item, targetElements) {
const i = targetElements.index;
if (i === 3 && this.check_Hot(item)) {
if (this.is_simple_search) {
const items = item.getElementsByClassName(
targetElements.itemClass
);
for (const it of items) this.check(it, targetElements);
}
return;
}
const tmp = this.get_main_element(item);
(tmp
? !(i === 3
? this.search_check(tmp, targetElements)
: this.user_check(tmp, targetElements) ||
this.content_check(tmp, targetElements))
: true) &&
this.dbInitial &&
(i < 2
? this.foldAnswer.check(tmp)
: this.foldAnswer.Three.main(
item,
i === 3 && this.is_simple_search
));
},
/*
1. URL change, for example, forward or backward, ...disable MutationObserver
2. URL match specific zone;
*/
checkURL(targetElements) {
const href = location.href;
return targetElements.index < 2
? true
: (targetElements.index === 2 &&
!href
.slice(href.lastIndexOf("/topic/") + 7)
.includes("/")) ||
targetElements.zone.some((e) => href.includes(e));
},
clickCheck(item, targetElements) {
/*
without userid when in the search page, if the answer is not expanded
the content only has the abstract section, if in search page and topic page
check the content, and record the cheched status;
*/
const id = this.foldAnswer.getid(item);
(id ? !this.checked_list.includes(id) : true) &&
setTimeout(
() =>
!this.content_check(item, targetElements) &&
this.checked_list.push(id),
350
);
},
//check the content when the content expanded
colorIndicator: {
lasttarget: null,
index: 0,
change: false,
stat: false,
color(target) {
if (!this.stat) return;
this.restore();
const colors = ["green", "red", "blue", "purple"];
target.style.color = colors[this.index];
target.style.fontSize = "16px";
target.style.letterSpacing = "0.3px";
if (target.style.fontWeight !== 600) {
target.style.fontWeight = 600;
this.change = true;
} else this.change = false;
this.index > 2 ? (this.index = 0) : (this.index += 1);
this.lasttarget = target;
},
restore() {
if (this.lasttarget) {
this.lasttarget.style.color = "";
this.lasttarget.style.fontSize = "";
this.lasttarget.style.letterSpacing = "";
if (this.change)
this.lasttarget.style.fontWeight = "normal";
this.lasttarget = null;
}
},
},
isReader: false,
auto_load_reader: false,
is_scroll_state: false,
check_click_all(className, localName, target, targetElements) {
if (className === targetElements.buttonClass) {
return this.getiTem(target, targetElements);
//click the ico of expand button
} else if (localName === "svg") {
const button = this.svgCheck(target, targetElements);
if (button) return this.getiTem(button, targetElements);
//click the answer, the content will be automatically expanded
}
return null;
},
search_clear(target) {
if (
target.localName === "a" &&
target.search.includes("&hybrid_search")
) {
const href = target.href.split("&search_source");
if (href.length > 1) {
const a = href[0] + "&type=content";
GM_openInTab(a, {
insert: true,
active: true,
});
target.href = a;
}
return true;
}
return false;
},
clickMonitor(node, targetElements) {
if (this.isMonitor) return;
const tags = ["blockquote", "p", "br", "li"];
this.colorIndicator.stat = GM_getValue("highlight");
if (!node) return;
node.onclick = (e) => {
//when open reader mode, if create click event of document, no node
const target = e.target;
if (this.search_clear(target)) {
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
return;
}
if (this.isReader) return;
let limit = true;
if (targetElements.index < 2)
limit = e.path.some(
(a) => a.className === targetElements.header
);
const localName = target.localName;
if (limit && tags.includes(localName)) {
if (target.style.color || this.foldAnswer.editableMode)
return;
this.colorIndicator.color(target);
return;
}
this.colorIndicator.restore();
const className = target.className;
//take care of svg element, the classname
if (
className &&
typeof className === "string" &&
className.startsWith("fold") &&
this.dbInitial
) {
if (targetElements.index < 2) {
const ends = [
"block",
"temp",
"element",
"select",
"edit",
"reader",
];
!this.is_scroll_state &&
ends.some((e) => className.endsWith(e)) &&
!this.auto_load_reader &&
this.foldAnswer.buttonclick(target);
} else {
const ends = [
"question",
"topic",
"element",
"temp",
"answer",
"article",
];
!this.is_scroll_state &&
ends.some((e) => className.endsWith(e)) &&
!this.auto_load_reader &&
this.foldAnswer.Three.btnClick(target);
}
return;
}
//click the expand button, the rich node has contained all content in q & a webpage;
if (targetElements.index < 2) return;
//-----------------------------------------------
let item = this.check_click_all(
className,
localName,
target,
targetElements
);
if (!item) {
if (className !== targetElements.expand) return;
for (const node of e.path) {
const className = node.className;
if (
className === targetElements.itemClass ||
className === targetElements.answerID
) {
item = node;
break;
}
}
}
if (item) this.clickCheck(item, targetElements);
else {
//some internal url with redirect paramter, the antiredirect function does not treat
const path = e.path;
let ic = 0;
for (const c of path) {
if (c.localName === "a") {
const cl = c.className;
if (
cl &&
typeof cl === "string" &&
!cl.endsWith("external")
) {
const s = c.search;
if (s.startsWith("?target=")) {
e.preventDefault();
e.stopImmediatePropagation();
const h = s.slice(s.indexof("=") + 1);
c.href = h;
window.open(h, "_blank");
break;
}
}
}
ic++;
if (ic > 4) break;
}
}
};
},
topicAndquestion(targetElements, info, index) {
const items =
document.getElementsByClassName("ContentItem-meta");
let n = items.length;
for (n; n--; ) {
const item = items[n];
const a = item.getElementsByClassName("UserLink-link");
let i = a.length;
if (i > 0) {
const username = a[--i].innerText;
if (username === info.username) {
const t = this.getiTem(item, targetElements);
t &&
(index === 0
? this.setDisplay(t.parentNode, info)
: this.setDisplay(t, info));
}
}
}
},
setDisplay(t, info) {
info.mode === "block"
? t.style.display !== "none" && (t.style.display = "none")
: t.style.display === "none" && (t.style.display = "block");
},
userChange(index) {
const info = GM_getValue("blacknamechange");
if (!info) return;
const targetElements = this.getTagetElements(
index === 0 ? 1 : index
);
index === 0 &&
(targetElements.itemClass = "ContentItem AnswerItem");
if (!this.checkURL(targetElements)) return;
if (index === 3) {
const items = document.getElementsByClassName(
targetElements.itemClass
);
let n = items.length;
for (n; n--; ) {
const item = items[n];
const a = item.getElementsByClassName(
targetElements.userID
);
let i = a.length;
if (i > 0) {
const name = a[--i].innerText;
name === info.username &&
this.setDisplay(item, info);
} else {
const content = item.getElementsByClassName(
targetElements.contentID
);
if (content.length === 0) continue;
const text = content[0].innerText;
const name = text.startsWith("匿名用户:")
? ""
: text.slice(0, text.indexOf(":"));
name &&
name === info.username &&
this.setDisplay(item, info);
}
}
} else this.topicAndquestion(targetElements, info, index);
},
//standby, commmunication between others and column
connectColumn() {
const r = GM_getValue("removearticleA");
if (r && Array.isArray(r) && r.length > 0) {
for (const e of r) dataBaseInstance.dele(false, e);
GM_setValue("removearticleA", "");
}
const b = GM_getValue("blockarticleA");
if (b && Array.isArray(b) && b.length > 0) {
for (const e of b) dataBaseInstance.fold(e);
GM_setValue("blockarticleA", "");
}
GM_addValueChangeListener(
"blockarticleA",
(name, oldValue, newValue, remote) => {
if (remote) {
dataBaseInstance.fold(newValue[0]);
GM_setValue("blockarticleA", "");
}
}
);
GM_addValueChangeListener(
"removearticleA",
(name, oldValue, newValue, remote) => {
if (remote) {
dataBaseInstance.dele(false, newValue[0]);
GM_setValue("removearticleA", "");
}
}
);
this.is_connect = true;
},
simple_search(item) {
const arr = [
"RelevantQuery",
"KfeCollection-PcCollegeCard-wrapper",
"ContentItem ZvideoItem",
"SearchClubCard",
"ContentItem-extra",
"SearchItem-userTitleWrapper",
];
for (const e of arr) {
if (item.getElementsByClassName(e).length > 0) {
item.className = `${item.className} hidden`;
item.style.display = "none";
break;
}
}
},
monitor(targetElements, node) {
//only in answer || question webpage, the muta will note be destroyed, without need to rebuilded;
const mo = new MutationObserver((e) => {
e.forEach((item) =>
item.addedNodes.length > 0 &&
item.addedNodes[0].className ===
targetElements.itemClass
? this.check(item.addedNodes[0], targetElements)
: targetElements.index === 3 &&
this.is_simple_search &&
this.simple_search(item.addedNodes[0])
);
targetElements.index === 1 &&
e.length > 2 &&
this.reader_sync();
});
mo.observe(node.parentNode, { childList: true });
},
getTagetElements(index) {
const pos = {
0: "answerPage",
1: "questionPage",
2: "topicPage",
3: "searchPage",
};
const targetElements = this[pos[index]](index);
return targetElements;
},
dbInitial: false,
reader_sync: null,
checked_list: null,
is_simple_search: false,
main(index, reader_sync) {
this.foldAnswer.initial().then((r) => {
index > 1 && (this.checked_list = []);
this.dbInitial = r;
this.reader_sync = reader_sync;
const targetElements = this.getTagetElements(index);
index !== 0 && this.firstRun(targetElements);
index !== 3 && this.Topic_questionButton(targetElements);
const w =
window.onurlchange === null
? window
: unsafeWindow.onurlchange === null
? unsafeWindow
: null;
w &&
w.addEventListener("urlchange", () =>
this.backwardORforward(targetElements)
);
this.connectColumn();
!this.isMonitor &&
this.clickMonitor(
document.getElementById(targetElements.mainID),
targetElements
);
});
},
is_jump: false,
bf_time_id: null,
backwardORforward(targetElements) {
//monitor forward or backward, this operation maybe not fire dom change event
if (this.is_jump) {
this.is_jump = false;
return;
}
this.bf_time_id && clearTimeout(this.bf_time_id);
this.bf_time_id = setTimeout(() => {
this.bf_time_id = null;
this.firstRun(targetElements);
}, 350);
},
is_update: false,
search_items() {
return document.getElementsByClassName("List")[0]
.firstElementChild.children;
},
common_items(cl) {
return document.getElementsByClassName(cl);
},
get_items(cl, ic, n, mode, targetElements, w) {
setTimeout(() => {
const items = this[w](cl);
const i = items.length;
if (i > n || ic > 20) {
if (i === 0) {
colorful_Console.main(
{
title: "info:",
content: "failed to get items",
},
colorful_Console.colors.info
);
return;
}
for (const item of items)
this.check(item, targetElements, 0);
mode && this.monitor(targetElements, items[0]);
} else this.get_items(cl, ++ic, n, mode, targetElements, w);
}, 50 + 5 * ic);
},
firstRun(targetElements) {
if (!this.checkURL(targetElements)) return;
let n = 4;
let cl = "";
let mode = true;
const i = targetElements.index;
if (i === 3 && this.is_simple_search) {
this.get_items(
cl,
0,
n,
mode,
targetElements,
"search_items"
);
return;
}
if (i > 1) cl = targetElements.itemClass;
else {
const p = location.pathname;
const a = p.includes("/answers/");
cl = !(n = p.includes("/answer/") ? 0 : 4)
? targetElements.header
: targetElements.itemClass;
mode = this.is_update ? !a : true;
this.is_update = a;
targetElements.index = n === 0 ? 0 : 1;
if (this.is_update && !mode) return;
}
this.get_items(cl, 0, n, mode, targetElements, "common_items");
},
isMonitor: false,
answerPage() {
const targetElements = this.questionPage(1);
const items = document.getElementsByClassName(
targetElements.header
);
for (const item of items)
this.check(item.parentNode, targetElements, 0);
this.clickMonitor(document, targetElements);
this.isMonitor = true;
return targetElements;
},
questionPage(index) {
const targetElements = {
buttonClass:
"Button ContentItem-rightButton ContentItem-expandButton Button--plain",
itemClass: "List-item",
mainID: "QuestionAnswers-answers",
contentID: "RichText ztext CopyrightRichText-richText",
userID: "UserLink-link",
backupClass: "Question-main",
header: "ContentItem AnswerItem",
headerID: "QuestionHeader",
headerTitle: "QuestionHeader-title",
expand: "RichText ztext CopyrightRichText-richText",
answerID: "ContentItem AnswerItem",
inserID: "LabelContainer-wrapper",
blockValueName: "blacktopicAndquestion",
inserButtonID: "QuestionHeaderActions",
index: index,
};
return targetElements;
},
searchPage(index) {
const nocontent = document.getElementsByClassName(
"SearchNoContent-title"
);
if (nocontent.length > 0) return null;
const targetElements = {
buttonClass: "Button ContentItem-more Button--plain",
itemClass: "Card SearchResult-Card",
mainID: "SearchMain",
contentID: "RichText ztext CopyrightRichText-richText",
expand: "RichContent-inner",
userID: "UserLink-link",
zone: ["type=content"],
index: index,
};
return targetElements;
},
topicPage(index) {
const targetElements = {
buttonClass: "Button ContentItem-more Button--plain",
itemClass: "List-item TopicFeedItem",
mainID: "TopicMain",
userID: "UserLink-link",
headerID: "ContentItem-head",
headerTitle: "ContentItem-title",
contentID: "RichText ztext CopyrightRichText-richText",
expand: "RichContent-inner",
inserButtonID: "TopicActions",
zone: ["/top-answers", "/hot", "newest"],
blockValueName: "blacktopicAndquestion",
index: index,
};
return targetElements;
},
foldAnswer: {
Three: {
initialR: false,
btnClick(button) {
const text = button.innerText;
if (text.startsWith("show")) {
this.showFold(button);
} else {
let bid = "";
if (text !== "Fold") {
bid = this.getbid(button);
if (!bid) return;
}
this[text](button, bid);
}
},
getbid(button) {
const attrs = button.attributes;
if (!attrs) return null;
for (const e of attrs)
if (e.name === "bid") return e.value;
return null;
},
Answer_Article(button, bid, type, n, t, from = "") {
const p = button.parentNode.parentNode;
const user = p.getElementsByClassName("UserLink-link");
let i = user.length - 1;
const info = {};
info.userID = "";
info.userName = "";
if (i > 0) {
i = i > 1 ? 1 : i;
info.userName = user[i].innerText;
const pn = user[i].pathname;
info.userID = pn.slice(pn.lastIndexOf("/") + 1);
}
info.from = from;
info.name = bid;
info.update = Date.now();
info.type = type;
dataBaseInstance.fold(info);
this.changeBtn(button, n, t);
},
changeBtn(button, n, t) {
button.innerText = n;
button.title = t;
this.Fold(button, "show blocked");
},
Question_Topic(button, bid, type, n, t) {
if (blackTopicAndQuestion.some((e) => e.id === bid))
return;
const info = {};
info.id = bid;
info.type = type;
info.update = Date.now();
blackTopicAndQuestion.push(info);
GM_setValue(
"blacktopicAndquestion",
blackTopicAndQuestion
);
this.changeBtn(button, n, t);
},
Answer(button, bid) {
const from = this.getbid(button.nextElementSibling);
this.Answer_Article(
button,
bid,
"answer",
"Remove",
"unblock the answer",
from ? from : ""
);
},
Question(button, bid) {
this.Question_Topic(
button,
bid,
"question",
"Remove",
"unblock the question"
);
},
Article(button, bid) {
this.Answer_Article(
button,
bid,
"article",
"Remove",
"unblock the article"
);
let b = GM_getValue("blockarticleB");
if (b && Array.isArray(b)) {
for (const e of b) if (e.pid === bid) return;
} else b = [];
const r = GM_getValue("removearticleB");
if (r && Array.isArray(r)) {
const i = r.indexOf(bid);
if (i > -1) {
r.splice(i, 1);
GM_setValue("removearticleB", r);
}
}
const info = {};
info.userID = "";
info.from = "";
info.pid = bid;
info.value = 0;
info.update = Date.now();
b.push(info);
GM_setValue("blockarticleB", b);
},
Topic(button, bid) {
this.Question_Topic(
button,
bid,
"topic",
"Remove",
"unblock the topic"
);
},
Remove(button, bid) {
const className = button.className;
const method = {
changeBtn(n, t) {
button.innerText = n;
button.title = t;
button.parentNode.previousElementSibling.innerText =
"show folded";
},
answer() {
dataBaseInstance.dele(false, bid);
this.changeBtn("Answer", "block the answer");
},
remove() {
let i = -1;
for (const e of blackTopicAndQuestion) {
i++;
if (e.id === bid) break;
}
if (i < 0) return;
blackTopicAndQuestion.splice(i, 1);
GM_setValue(
"blacktopicAndquestion",
blackTopicAndQuestion
);
},
topic() {
this.remove();
this.changeBtn("Topic", "block the topic");
},
article() {
dataBaseInstance.dele(false, bid);
this.changeBtn("Article", "block the article");
let r = GM_getValue("removearticleB");
if (r && Array.isArray(r) && r.length > 0) {
if (r.includes(bid)) return;
} else r = [];
const b = GM_getValue("blockarticleB");
if (b && Array.isArray(b)) {
const i = b.findIndex((e) => e.pid === bid);
if (i > -1) {
b.splice(i, 1);
GM_setValue("blockarticleB", b);
}
}
r.push(bid);
GM_setValue("removearticleB", r);
},
question() {
this.remove();
this.changeBtn(
"Question",
"block the question"
);
},
};
const n = className.slice(className.indexOf("_") + 1);
method[n]();
},
Fold(button, n = "") {
const p = button.parentNode;
p.previousElementSibling.style.display = "block";
p.nextElementSibling.style.display = "none";
p.style.display = "none";
n && (p.previousElementSibling.innerText = n);
},
showFold(button) {
button.style.display = "none";
const n = button.nextElementSibling;
if (!n) return;
n.style.display = "grid";
n.nextElementSibling.style.display = "block";
},
simple_search_hide(item) {
item.className = `${item.className} hidden`;
item.style.display = "none";
},
check_simple_search(p) {
const arr = [
"/zvideo/",
"/lives/",
"/club/",
"/market/",
"/people/",
];
return arr.some((e) => p.includes(e));
},
market_ad(item) {
const arr = [
"RelevantQuery",
"KfeCollection-PcCollegeCard-wrapper",
"ContentItem ZvideoItem",
"SearchClubCard",
"ContentItem-extra",
"SearchItem-userTitleWrapper",
];
for (const e of arr) {
if (item.getElementsByClassName(e).length > 0) {
this.simple_search_hide(item);
break;
}
}
},
getInfo(item, mode) {
const title = item.getElementsByTagName("h2");
if (title.length === 0) {
mode && this.market_ad(item);
return null;
}
if (mode && title[0].innerText === "相关搜索") {
this.simple_search_hide(item);
return null;
}
const a = title[0].getElementsByTagName("a");
if (a.length === 0) {
mode && this.market_ad(item);
return null;
}
const text = a[0].innerText;
if (
text &&
blackKey.some((e) => {
if (text.includes(e)) {
item.style.display = "none";
colorful_Console.main(
{
title: "Blocked title: ",
content: "rubbish word " + e,
},
colorful_Console.colors.warning
);
return true;
}
return false;
})
)
return null;
const p = a[0].pathname;
if (mode && this.check_simple_search(p)) {
this.simple_search_hide(item);
return null;
}
const info = {};
info.cblock = false;
info.ablcok = false;
info.qblock = false;
info.tblock = false;
const flags = ["/question", "/p/", "/topic"];
const index = flags.findIndex((e) => p.includes(e));
if (index === 0) {
info.type = "answer";
const tmp = p.split("/");
if (tmp.length !== 5) return null;
info.qid = tmp[2];
info.aid = tmp[4];
} else if (index === 1) {
info.type = "column";
info.cid = p.slice(p.lastIndexOf("/") + 1);
} else if (index === 2) {
info.type = "topic";
info.tid = p.slice(p.lastIndexOf("/") + 1);
} else return null;
return info;
},
btnRaw(c, t, n, b) {
return ``;
},
foldRaw(arr, info, adisplay, bdisplay, name) {
const d = JSON.stringify(info);
return `
show ${name}
${arr.join("")}
`;
},
checkAnswerOrArticle(id) {
return new Promise((resolve, reject) => {
dataBaseInstance.check(id).then(
(result) => resolve(result),
(err) => {
console.log(err);
reject();
}
);
});
},
checkTAndQ(id) {
return blackTopicAndQuestion.some((e) => e.id === id);
},
answer(item, info) {
const exe = (r) => {
const f = this.checkTAndQ(info.qid);
const html = [];
info.qblock = f;
info.ablcok = r;
(r || f) && this.hide_content(item);
let [t, n] = r
? ["unblock", "Remove"]
: ["block", "Answer"];
html.push(
this.btnRaw(
"answer",
t + " the answer",
n,
info.aid
)
);
[t, n] = f
? ["unblock", "Remove"]
: ["block", "Question"];
html.push(
this.btnRaw(
"question",
t + " the question",
n,
info.qid
)
);
const [a, b, name] =
r || f
? ["grid", "none", "blocked"]
: ["none", "grid", "folded"];
item.insertAdjacentHTML(
"afterbegin",
this.foldRaw(html, info, a, b, name)
);
};
this.initialR
? this.checkAnswerOrArticle(info.aid).then(
(r) => exe(r),
() => console.log("check blocked answer fail")
)
: exe(false);
},
hide_content(item) {
item.firstChild.style.display = "none";
},
column(item, info) {
const exe = (r) => {
info.cblock = r;
r && this.hide_content(item);
const [t, n, a, b, name] = r
? [
"unblock",
"Remove",
"grid",
"none",
"blocked",
]
: [
"block",
"Article",
"none",
"grid",
"folded",
];
item.insertAdjacentHTML(
"afterbegin",
this.foldRaw(
[
this.btnRaw(
"article",
t + " the article",
n,
info.cid
),
],
info,
a,
b,
name
)
);
};
this.initialR
? this.checkAnswerOrArticle(info.cid).then(
(r) => exe(r),
() =>
console.log("check blocked article fail")
)
: exe(false);
},
topic(item, info) {
const f = this.checkTAndQ(info.tid);
info.tblock = f;
f && this.hide_content(item);
const [t, n, a, b, name] = f
? ["unblock", "Remove", "block", "none", "blocked"]
: ["block", "Topic", "none", "grid", "folded"];
item.insertAdjacentHTML(
"afterbegin",
this.foldRaw(
[
this.btnRaw(
"topic",
t + " the topic",
n,
info.tid
),
],
info,
a,
b,
name
)
);
},
main(item, mode) {
const info = this.getInfo(item, mode);
if (!info) return;
this[info.type](item, info);
},
},
//---------------------------------------------------------------------------------------
buttonclick(button) {
const text = button.innerText;
text.startsWith("show")
? this.showFold(button, text)
: this[text](button);
},
getcontent(button, pnode) {
const p = pnode || this.getpNode(button);
if (!p) return null;
const expand = p.getElementsByClassName(
"Button ContentItem-rightButton ContentItem-expandButton Button--plain"
);
if (expand.length > 0) expand[0].click();
const ele = p.getElementsByClassName(
"RichText ztext CopyrightRichText-richText"
);
return ele.length === 0 ? null : ele[0];
},
editableMode: false,
Edit(button) {
const ele = this.getcontent(button);
if (!ele) return;
this.editableMode = true;
ele.contentEditable = true;
button.innerText = "Exit";
button.title = "exit editable mode";
},
Reader(button) {
if (this.editableMode) {
Notification("please exit editable mode", "Tips");
return;
}
const pnode = this.getpNode(button);
if (!pnode) return;
const aid = this.getid(pnode);
if (!aid) return;
zhihu.qaReader.main(pnode, aid);
},
Exit(button) {
const ele = this.getcontent(button);
if (!ele) return;
this.editableMode = false;
ele.contentEditable = false;
button.innerText = "Edit";
button.title = "edit the answer";
},
Select(button) {
if (this.editableMode) {
Notification("please exit editable mode", "Tips");
return;
}
const ele = this.getcontent(button);
if (!ele) return;
const selection = window.getSelection();
selection.removeAllRanges();
const range = new Range();
range.selectNodeContents(ele);
selection.addRange(range);
},
getid(item) {
const tmp =
item.className === "ContentItem AnswerItem"
? item
: item.getElementsByClassName(
"ContentItem AnswerItem"
)[0];
if (!tmp) return null;
const attrs = tmp.attributes;
if (!attrs) return null;
for (const a of attrs)
if (a.name === "name") return a.value;
return null;
},
getpNode(button) {
let pnode = button.parentNode;
let ic = 0;
while (pnode.className !== "ContentItem AnswerItem") {
pnode = pnode.parentNode;
ic++;
if (ic > 4 || !pnode) return null;
}
return pnode;
},
get from() {
const p = location.pathname;
const reg = /(?<=question\/)\d+/;
const m = p.match(reg);
return m ? m[0] : "";
},
Block(button, type = "answer") {
const p = this.getpNode(button);
if (!p) return;
const id = this.getid(p);
if (!id) return;
const info = {};
const user = p.getElementsByClassName("UserLink-link");
let i = user.length - 1;
if (i > 0) {
i = i > 1 ? 1 : i;
info.userName = user[i].innerText;
const pn = user[i].pathname;
info.userID = pn.slice(pn.lastIndexOf("/") + 1);
}
info.name = id;
info.update = Date.now();
info.type = type;
info.from = this.from;
dataBaseInstance.fold(info);
button.innerText = "Remove";
button.title = "remove the answer from block list";
this.insertShowFolded(p, "blocked");
},
Remove(button) {
//show temp button
const p = this.getpNode(button);
if (!p) return;
const id = this.getid(p);
if (!id) return;
dataBaseInstance.dele(false, id);
button.innerText = "Block";
button.title = "fold the answer forever";
},
Fold(button) {
const p = this.getpNode(button);
if (!p) return;
this.insertShowFolded(p, "folded");
},
showFold(button, text) {
const next = button.nextElementSibling;
next.style.display = "block";
button.style.display = "none";
if (text.endsWith("blocked")) this.insertButton(next, true);
},
check(item) {
if (!item) return;
if (this.initialR === 0) {
this.insertButton(item);
return;
}
const id = this.getid(item);
if (!id) return;
dataBaseInstance.check(id).then(
(r) => {
r
? this.insertShowFolded(item, "blocked")
: this.insertButton(item);
},
(err) => console.log(err)
);
},
initialR: 0,
initial() {
return new Promise((resolve) => {
const tables = ["foldedAnswer"];
dataBaseInstance.initial(tables, true, "name").then(
(result) => {
resolve(true),
(this.Three.initialR = this.initialR =
result);
},
(err) => {
console.log(err);
resolve(false);
}
);
});
},
insertShowFolded(item, name) {
//if it has already contained this element , to hide or show
const f =
item.parentNode.getElementsByClassName("fold_element");
if (f.length === 0) {
const html = `
show ${name}
`;
item.insertAdjacentHTML("beforebegin", html);
item.style.display = "none";
} else {
item.style.display = "none";
f[0].style.display = "block";
f[0].innerText = `show ${name}`;
}
},
insertButton(item, mode = false) {
const h = item.getElementsByClassName("hidden_fold");
if (h.length === 0) {
const obutton = `
`;
const html = `
${obutton}
`;
const r = `
${obutton}
`;
item.firstElementChild.lastElementChild.insertAdjacentHTML(
"beforebegin",
mode ? r : html
);
}
},
},
},
searchPage: {
is_simple_search: false,
get search_simple() {
let simple = "";
const common = `
.RelevantQuery,
.KfeCollection-PcCollegeCard-wrapper,
.ContentItem.ZvideoItem,
.SearchClubCard{display: none !important;}`;
for (let i = 2; i < 10; i++)
simple += `.SearchTabs-actions li.Tabs-item.Tabs-item--noMeta:nth-of-type(${i}),`;
return simple + common;
},
get raw() {
//html & css, adaped from bilibili
const [c, t] = this.is_simple_search
? [" on", escapeBlank("restore normal mode")]
: [
"",
escapeHTML(
"remove other items, just keep question, topic, article"
),
];
const html = `
`;
return html;
},
getPos(node) {
const search =
node.getElementsByClassName("SearchTabs-actions");
if (search.length === 0) {
colorful_Console.main(
{
title: "Warning:",
content:
"search page does not get the target element",
},
colorful_Console.colors.warning
);
return null;
}
return search;
},
buttons: null,
main() {
const search = this.getPos(document);
if (!search) return;
const i = search.length;
const html = this.raw;
this.buttons = [];
if (i > 1) for (const c of search) this.click_event(c, html);
else {
this.click_event(search[0], html);
this.monitor();
}
},
monitor() {
let mo = new MutationObserver((e) => {
if (e.length === 1 && e[0].addedNodes.length === 1) {
const newNode = e[0].addedNodes[0];
if (newNode.className.startsWith("PageHeader")) {
const search = this.getPos(newNode);
if (!search) return;
mo.disconnect();
mo = null;
this.click_event(search[0], this.raw);
}
}
});
mo.observe(
document.getElementsByClassName("Sticky AppHeader")[0]
.lastElementChild,
{ childList: true }
);
},
get _list() {
const list = document.getElementsByClassName("List");
return list.length > 0
? list[0].firstElementChild.children
: null;
},
pNode_hide() {
const arr = [
"RelevantQuery",
"KfeCollection-PcCollegeCard-wrapper",
"ContentItem ZvideoItem",
"SearchClubCard",
"ContentItem-extra",
"SearchItem-userTitleWrapper",
];
const list = this._list;
if (!list) return;
for (const l of list) {
for (const e of arr) {
if (l.getElementsByClassName(e).length > 0) {
l.className = `${l.className} hidden`;
l.style.display = "none";
break;
}
}
}
},
pNode_show() {
const list = this._list;
if (!list) return;
for (const l of list) {
const c = l.className;
if (c && c.endsWith(" hidden")) {
l.className = c.slice(0, -7);
l.style.display = "block";
}
}
},
click_event(c, html) {
c.insertAdjacentHTML("beforeend", html);
setTimeout(() => {
let timeid = null;
const node =
c.lastElementChild.lastElementChild.lastElementChild;
this.buttons.push(node);
node.onclick = (e) => {
timeid && clearTimeout(timeid);
timeid = setTimeout(() => {
timeid = null;
let f = false;
const className = e.target.className;
let newName = "";
if (className.endsWith(" on")) {
if (this.id) {
const style = document.getElementById(
this.id
);
this.pNode_show();
style && style.remove();
this.id = null;
}
newName = className.slice(
0,
className.length - 3
);
} else {
this.id = GM_addStyle(this.search_simple).id;
f = true;
newName = className + " on";
this.pNode_hide();
}
this.buttons.forEach(
(e) => (e.className = newName)
);
this.is_simple_search = f;
GM_setValue("simplesearch", f);
}, 300);
};
}, 0);
},
id: null,
add(common, inpustyle, search, topicAndquestion, ad, bgi) {
GM_addStyle(
common + inpustyle + search + topicAndquestion + ad + bgi
);
(this.is_simple_search = GM_getValue("simplesearch")) &&
(this.id = GM_addStyle(this.search_simple).id);
},
},
body_img_update: null,
body_image() {
const bgi = this.commander.bgi.get;
let s = "";
if (bgi && (s = bgi.status) && s !== "tmp") {
const url =
s === "auto"
? (this.body_img_update = bgi) && bgi.url
: s === "fixed" && GM_getValue("fixed_image");
return url ? `body{background-image: url(${url});}` : "";
}
return "";
},
addStyle(index) {
const common = `
.css-1hwwfws,
.css-1ynzxqw,
.ModalExp-content{display: none !important;}
span.RichText.ztext.CopyrightRichText-richText{text-align: justify !important;}
body{background-attachment: fixed !important;text-shadow: #a9a9a9 0.025em 0.015em 0.02em;}`;
const showfold = `
.fold_element{
max-height: 24px;
margin-left: 45%;
margin-top: 3px;
width: 100px;
text-align: center;
border: 1px solid #ccc !important;
}`;
const contentstyle = `
${showfold}
.hidden_fold:hover {
opacity: 1;
transition: opacity 2s;
}
.hidden_fold{opacity: 0.15;}
.hidden_fold button {
float: right;
border: 1px solid #ccc!important;
box-shadow: 1px 1px 4px #888888;
height: 21px;
font-size: 14px;
width: 54px;
border-radius: 5px;
}
div.Question-mainColumn{
margin: auto !important;
width: 100% !important;
max-width: 1000px !important;
min-width: 1000px !important;
}
.RichContent.RichContent--unescapable{width: 100% !important;}
figure{max-width: 70% !important;}
.RichContent-inner{
line-height: 30px !important;
margin: 35px 0px !important;
padding: 25px 30px !important;
border: 6px dashed rgba(133,144,166,0.2) !important;
border-radius: 6px !important;
}
.Comments{padding: 12px !important; margin: 60px !important;}`;
const inpustyle = `
input::-webkit-input-placeholder {
font-size: 0px !important;
text-align: right;
}`;
const search = `
.SearchResult-Card{opacity: 0.95 !important;}
.SearchMain{width: 930px !important;}
.SearchSideBar,
.Card.TopSearch{display: none !important;}
.KfeCollection-PcCollegeCard-wrapper,
.List-item{border: 1px solid transparent;}
.List-item{position: inherit !important;}
.KfeCollection-PcCollegeCard-wrapper:hover,
.List-item:hover {
border: 1px solid #B9D5FF;
box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, 0.10);
}`;
const topicAndquestion = `
${showfold}
.hidden_fold {
opacity: 0.15;
float: right;
}
.hidden_fold:hover {
opacity: 1;
transition: opacity 2s;
}
.hidden_fold button {
border: 1px solid #ccc!important;
box-shadow: 1px 1px 0px #888888;
height: 18px;
font-size: 12px;
width: 54px;
border-radius: 5px;
margin-top: 2px;
}`;
const topic = `
.ContentLayout{width: 100% !important;}
.ContentLayout-mainColumn{
margin-left: 23%;
width: 930px !important;
}
.ContentLayout-sideColumn{
margin-right: 15%;
}
.List-item.TopicFeedItem{border: 1px solid transparent;}
.List-item.TopicFeedItem:hover {
border: 1px solid #B9D5FF;
box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, 0.10);
}`;
const ad = `
.css-1ynzxqw,
a[href*="u.jd.com"],
.Pc-word,
.RichText-ADLinkCardContainer,
.MCNLinkCard,
.RichText-MCNLinkCardContainer,
div.Question-sideColumn,.Kanshan-container,
span.LinkCard-content.LinkCard-ecommerceLoadingCard,
.RichText-MCNLinkCardContainer{display: none !important;}`;
index === 3
? this.searchPage.add(
common,
inpustyle,
search,
topicAndquestion,
ad,
this.body_image()
)
: GM_addStyle(
common +
(index < 2
? contentstyle +
inpustyle +
this.body_image() +
ad
: index === 2
? inpustyle +
topicAndquestion +
topic +
this.body_image() +
ad
: inpustyle)
);
},
clearStorage() {
const rubbish = {};
rubbish.timeStamp = Date.now();
rubbish.words = [];
//localstorage must storage this info to ensure the history show
for (let i = 0; i < 5; i++)
rubbish.words.push({ displayQuery: "", query: "" });
localStorage.setItem("search::top-search", JSON.stringify(rubbish));
localStorage.setItem("search:preset_words", "");
localStorage.setItem("zap:SharedSession", "");
},
/*
disable blank search hot word;
disable show hot seach result;
clear placeholder
导致Promise出现错误
*/
inputBox: {
box: null,
monitor(index, visibleChange) {
this.box = document.getElementsByTagName("input")[0];
this.box.placeholder = "";
index > 3 &&
unsafeWindow.addEventListener(
"popstate",
(e) => {
e.preventDefault();
e.stopPropagation();
},
true
);
unsafeWindow.addEventListener(
"visibilitychange",
(e) => {
e.preventDefault();
this.box.placeholder = "";
e.stopPropagation();
index < 4 && visibleChange(document.hidden);
},
true
);
let button = document.getElementsByClassName(
"Button SearchBar-searchButton Button--primary"
);
if (button.length > 0) {
button[0].onclick = (e) => {
if (this.box.value.length === 0) {
e.preventDefault();
e.stopPropagation();
}
};
}
button = null;
this.box.addEventListener(
"keydown",
(fuckzhihu) => {
if (fuckzhihu.keyCode !== 13) return;
if (
this.box.value.length === 0 ||
this.box.value.trim().length === 0
) {
fuckzhihu.preventDefault();
fuckzhihu.stopImmediatePropagation();
fuckzhihu.stopPropagation();
} else {
if (
blackKey.some((e) => this.box.value.includes(e))
) {
Notification(
"keyword contains rubbish word",
"Warning"
);
return;
}
const url = `https://www.zhihu.com/search?q=${this.box.value}&type=content`;
window.open(url, "_blank");
}
},
true
);
this.box.onfocus = () => {
this.box.value.length === 0 && (this.box.placeholder = "");
localStorage.setItem("zap:SharedSession", "");
};
this.box.onblur = () => (this.box.placeholder = "");
this.firstRun();
},
firstRun() {
let mo = new MutationObserver((e) => {
if (e.length !== 1 || e[0].addedNodes.length !== 1) return;
const target = e[0].addedNodes[0];
const p = target.getElementsByClassName("Popover-content");
if (p.length === 0) return;
const tmp =
p[0].getElementsByClassName("AutoComplete-group");
if (tmp.length === 0) return;
this.AutoComplete = tmp[0];
if (p[0].innerText.startsWith("搜索发现"))
this.AutoComplete.style.display = "none";
mo.disconnect();
mo = null;
this.secondRun(p[0].parentNode);
});
mo.observe(document.body, { childList: true });
},
AutoComplete: null,
secondRun(target) {
const mo = new MutationObserver((e) => {
if (e.length === 1) {
if (e[0].addedNodes.length !== 1) {
this.AutoComplete = null;
return;
}
const t = e[0].addedNodes[0];
this.AutoComplete =
t.getElementsByClassName("AutoComplete-group")[0];
if (t.innerText.startsWith("搜索发现"))
this.AutoComplete.style.display = "none";
} else {
const style =
this.box.value.length > 0 ? "inline" : "none";
this.AutoComplete.style.display !== style &&
(this.AutoComplete.style.display = style);
}
});
mo.observe(target, { childList: true, subtree: true });
},
},
ErrorAutoClose() {
const w = document.getElementsByClassName("PostIndex-warning");
if (w.length === 0) return;
const h = document.createElement("h2");
w[0].insertAdjacentElement("afterbegin", h);
setTimeout(() => window.close(), 6100);
let time = 5;
const dot = ".";
h.innerText = `5s, current web will be automatically closed.....`;
let id = setInterval(() => {
time--;
h.innerText = `${time}s, current web will be automatically closed${dot.repeat(
time
)}`;
if (time === 0) clearInterval(id);
}, 1000);
},
zhuanlanStyle(mode) {
//font, the pic of header, main content, sidebar, main content letter spacing, comment zone, ..
//@media print, print preview, make the background-color can view when save webpage as pdf file
const article = `
mark.AssistantMark.red{background-color: rgba(255, 128, 128, 0.65) !important;box-shadow: rgb(255, 128, 128) 0px 1.2px;border-radius: 0.2em !important;}
mark.AssistantMark.yellow{background-color: rgba(255, 250, 90, 1) !important;box-shadow: rgb(255, 255, 170) 0px 1.2px;border-radius: 0.2em !important;}
mark.AssistantMark.green{background-color: rgba(170, 235, 140, 0.8) !important;box-shadow: rgb(170, 255, 170) 0px 2.2px;border-radius: 0.2em !important;}
mark.AssistantMark.purple{background-color: rgba(255, 170, 255, 0.8) !important;box-shadow: rgb(255, 170, 255) 0px 1.2px;border-radius: 0.2em !important;}
@media print {
mark.AssistantMark { box-shadow: unset !important; -webkit-print-color-adjust: exact !important; }
.CornerButtons,
div#load_status,
.toc-bar.toc-bar--collapsed,
div#assist-button-container {display : none;}
#column_lists {display : none !important;}
}
body{text-shadow: #a9a9a9 0.025em 0.015em 0.02em;}
.TitleImage{width: 500px !important}
.Post-Main .Post-RichText{text-align: justify !important;}
.Post-SideActions{left: calc(50vw - 560px) !important;}
.RichText.ztext.Post-RichText{letter-spacing: 0.1px;}
.Sticky.RichContent-actions.is-fixed.is-bottom{position: inherit !important}
.Comments-container,
.Post-RichTextContainer{width: 900px !important;}
.highlight{
box-shadow: rgb(170, 170, 170) 0px 0px 4px 0px;
box-sizing: border-box !important;
overflow-wrap: break-word !important;
}
${
mode === 0 && GM_getValue("topnopicture")
? ".TitleImage,"
: ""
}
a[href*="u.jd.com"],
.RichText-MCNLinkCardContainer,
.Post-RichTextContainer .Catalog.isCatalogV2,
span.LinkCard-content.LinkCard-ecommerceLoadingCard,
.css-1ynzxqw,
.RichText-MCNLinkCardContainer{display: none !important}`;
const list = `.Card:nth-of-type(3),.Card:last-child,.css-8txec3{width: 900px !important;}`;
const home = `
.RichContent.is-collapsed{cursor: default !important;}
.List-item {
margin-top: 8px;
position: relative;
padding: 16px 20px;
width: 1000px;
margin-left: 23%;
box-shadow: 0 1px 3px rgba(18,18,18,.1);
border: 1px solid #B9D5FF;
}
.ColumnHomeTop{
position: relative !important;
height: -webkit-fill-available !important;
}
.css-1hwwfws{display: none !important;}`;
if (mode < 2) {
if (mode === 0) {
if (document.title.startsWith("该内容暂无法显示")) {
window.onload = () => this.ErrorAutoClose();
return;
}
const r = GM_getValue("reader");
if (r) {
GM_addStyle(
article + this.Column.clearPage(0).join("")
);
this.Column.readerMode = true;
} else GM_addStyle(article);
this.Column.isZhuanlan = true;
} else {
document.title = "IGNORANCE IS STRENGTH";
Object.defineProperty(document, "title", {
writable: true,
enumerable: true,
configurable: true,
});
this.Column.is_column_home = true;
GM_addStyle(home);
}
window.onload = () => {
if (mode === 0) {
this.colorAssistant.main();
this.Column.main(0);
this.autoScroll.keyBoardEvent();
unsafeWindow.addEventListener("visibilitychange", () =>
this.visibleChange(document.hidden)
);
control_pannel_init();
} else this.column_homePage(this.Column.main(1));
setTimeout(() => this.show_Total.main(true), 30000);
this.key_ctrl_sync(true);
};
} else {
GM_addStyle(list);
this.Column.main(2);
}
},
Column: {
isZhuanlan: false,
is_column_home: false,
authorID: "",
authorName: "",
get ColumnDetail() {
const header = document.getElementsByClassName(
"ColumnLink ColumnPageHeader-TitleColumn"
);
if (header.length === 0) {
this.columnName = "";
this.columnID = "";
return false;
}
const href = header[0].href;
this.columnID = href.slice(href.lastIndexOf("/") + 1);
this.columnName = header[0].innerText;
const post = document.getElementsByClassName("Post-Author");
if (post.length > 0) {
const user =
post[0].getElementsByClassName("UserLink-link");
const i = user.length - 1;
const p = user[i].pathname;
this.authorName = user[i].innerText;
this.authorID = p.slice(p.lastIndexOf("/") + 1);
}
return true;
},
updateAuthor(author) {
const html = `
`;
const authorNode =
document.getElementsByClassName("AuthorInfo");
if (authorNode.length > 0) authorNode[0].outerHTML = html;
this.authorID = author.url_token;
this.authorName = author.name;
},
//column homepage
subscribeOrfollow() {
if (!this.ColumnDetail) return;
let fn = "follow";
let sn = "subscribe";
const f = GM_getValue(fn);
if (f && Array.isArray(f))
f.some((e) => this.columnID === e.columnID) &&
(fn = "remove");
const s = GM_getValue(sn);
if (s && Array.isArray(s))
s.some((e) => this.columnID === e.columnID) &&
(sn = "remove");
let [a, b] =
fn === "remove" ? ["remove", "from"] : ["add", "to"];
const ft = `${a} the column ${b} follow list`;
[a, b] = sn === "remove" ? ["remove", "from"] : ["add", "to"];
const st = `${a} the column ${b} subscribe list`;
const html = `
`;
//const bhtml = ``;
const user = document.getElementsByClassName(
"AuthorInfo AuthorInfo--plain"
);
if (user.length === 0) return;
user[0].parentNode.insertAdjacentHTML("beforeend", html);
let buttons =
document.getElementsByClassName("assistant-button")[0]
.children;
const exe = (button, mode) => {
const name = button.innerText;
if (name === "remove") {
const vname = mode === 1 ? "follow" : "subscribe";
const arr = GM_getValue(vname);
if (arr && Array.isArray(arr)) {
const index = arr.findIndex(
(e) => e.columnID === this.columnID
);
if (index > -1) {
arr.splice(index, 1);
GM_setValue(vname, arr);
if (mode === 1 && this.columnsModule.node)
this.columnsModule.database = arr;
}
Notification(`un${vname} successfully`, "Tips");
}
button.innerText = vname;
button.title = `add the column to ${vname} list`;
} else {
if (mode === 1) {
const i = this.follow(true);
if (i !== 1) {
button.innerText = "remove";
button.title =
"remove the column from follow list";
}
} else {
this.subscribe();
button.innerText = "remove";
button.title =
"remove the column from subscribe list";
}
}
};
buttons[1].onclick = function () {
exe(this, 1);
};
buttons[2].onclick = function () {
exe(this, 2);
};
/*
buttons[3].onclick =function () {
exe(this, 3);
}
*/
buttons = null;
},
//shift + f
follow(mode) {
if (!this.columnID) return;
let f = GM_getValue("follow");
if (f && Array.isArray(f)) {
let index = 0;
for (const e of f) {
if (this.columnID === e.columnID) {
const c = confirm(
`you have already followed this column on ${this.timeStampconvertor(
e.update
)}, is unfollow this column?`
);
if (!c) return 0;
f.splice(index, 1);
GM_setValue("follow", f);
Notification(
"unfollow this column successfully",
"Tips"
);
return 1;
}
index++;
}
} else f = [];
const p = prompt(
"please input some tags about this column, like: javascript python; multiple tags use blank space to isolate",
"javascript python"
);
let tags = [];
if (p && p.trim()) {
const tmp = p.split(" ");
for (let e of tmp) {
e = e.trim();
e && tags.push(e);
}
}
if (tags.length === 0 && !mode) {
const top = document.getElementsByClassName(
"TopicList Post-Topics"
);
if (top.length > 0) {
const topic = top[0].children;
for (const e of topic) tags.push(e.innerText);
}
}
const info = {};
info.columnID = this.columnID;
info.update = Date.now();
info.columnName = this.columnName;
info.tags = tags;
f.push(info);
this.columnsModule.node && (this.columnsModule.database = f);
GM_setValue("follow", f);
Notification(
"you have followed this column successfully",
"Tips",
3500
);
return 2;
},
//shift + s
subscribe() {
if (!this.columnID) return;
let s = GM_getValue("subscribe");
if (s && Array.isArray(s)) {
let i = 0;
for (const e of s) {
if (e.columnID === this.columnID) {
s.splice(i, 1);
break;
}
}
} else s = [];
const i = s.length;
const info = {};
info.columnID = this.columnID;
info.update = Date.now();
info.columnName = this.columnName;
if (i === 0) {
s.push(info);
} else {
i === 10 && s.pop();
s.unshift(info);
}
GM_setValue("subscribe", s);
Notification(
"you have subscribed this column successfully",
"Tips",
3500
);
},
Tabs: {
get GUID() {
// blob:https://xxx.com/+ uuid
const link = URL.createObjectURL(new Blob());
const blob = link.toString();
URL.revokeObjectURL(link);
return blob.substr(blob.lastIndexOf("/") + 1);
},
save(columnID) {
//if currentb window does't close, when reflesh page or open new url in current window(how to detect the change ?)
//if open new url in same tab, how to change the uuid?
GM_getTab((tab) => {
const uuid = this.GUID;
tab.id = uuid;
tab.columnID = columnID;
tab.title = document.title;
sessionStorage.setItem("uuid", uuid);
GM_saveTab(tab);
});
},
check(columnID) {
return new Promise((resolve) => {
GM_getTabs((tabs) => {
if (tabs) {
//when open a new tab with "_blank" method, this tab will carry the session data of origin tab
const uuid = sessionStorage.getItem("uuid");
if (!uuid) {
resolve(false);
} else {
const tablist = Object.values(tabs);
const title = document.title;
const f = tablist.some(
(e) =>
e.columnID === columnID &&
uuid === e.id &&
e.titlle !== title
);
resolve(f);
}
} else resolve(false);
});
});
},
},
tocMenu: {
change: false,
appendNode(toc) {
if (toc.className.endsWith("collapsed")) return;
const header =
document.getElementsByClassName("Post-Header");
if (header.length === 0) {
console.log("the header has been remove");
return;
}
header[0].appendChild(toc);
toc.style.position = "sticky";
toc.style.width = "900px";
this.change = true;
},
restoreNode(toc) {
if (!this.change) return;
document.body.append(toc);
toc.removeAttribute("style");
this.change = false;
},
main(mode) {
const toc = document.getElementById("toc-bar");
toc &&
(mode ? this.restoreNode(toc) : this.appendNode(toc));
},
},
titleChange: false,
clearPage(mode = 0) {
const ids = [
"Post-Sub Post-NormalSub",
"Post-Author",
"span.Voters button",
"ColumnPageHeader-Wrapper",
"Post-SideActions",
"Sticky RichContent-actions is-bottom",
];
if (mode === 0) {
const reg = /\s/g;
const css = ids.map(
(e) =>
`${
e.startsWith("span")
? e
: `.${e.replace(reg, ".")}`
}{display: none;}`
);
return css;
} else {
const style = mode === 1 ? "block" : "none";
ids.forEach((e) => {
const tmp = e.startsWith("span");
tmp &&
(e = e.slice(e.indexOf(".") + 1, e.indexOf(" ")));
const t = document.getElementsByClassName(e);
t.length > 0 &&
(tmp
? (t[0].firstChild.style.display = style)
: (t[0].style.display = style));
});
}
},
// load all lazy images in 'print' mode
load_all_lazy_img() {
const content = document.getElementsByClassName(
"RichText ztext Post-RichText"
);
if (content.length === 0) return;
const imgs = content[0].querySelectorAll("img.lazy");
for (const img of imgs) {
const src = img.src;
if (
src &&
src.startsWith(
"data:image/svg+xml;utf8,