// ==UserScript==
// @name zhihu optimizer
// @namespace https://github.com/Kyouichirou
// @version 3.3.3.8
// @description make zhihu clean and tidy, for better experience
// @author HLA
// @run-at document-start
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @connect www.zhihu.com
// @connect lens.zhihu.com
// @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_saveTab
// @grant GM_info
// @grant window.onurlchange
// @grant window.close
// @match https://*.zhihu.com/*
// @compatible chrome 80+; test on chrome 64(x86), some features don't work
// @license MIT
// @noframes
// @note more spam users of zhihu, https://zhuanlan.zhihu.com/p/127021293, it is recommended to block all of these users.
/* !*******************zhihu*******************************!
www.zhihu.com/api/v4/creator/read_count_statistics*
www.zhihu.com/api/v4/me?include=ad_type*
www.zhihu.com/api/v4/search/top_search
www.zhihu.com/zbst/events/r
www.zhihu.com/api/v4/me/switches?include=is_creator
www.zhihu.com/api/v4/commercial/ecommerce
www.zhihu.com/api/v4/search/preset_words
!*******************zhihu*******************************!*/
//note add these rules to ublock or adblock => ensure the input box clear
// ==/UserScript==
(() => {
"use strict";
const blackKey = ["留学中介", "肖战"];
let blackName = null;
let blackTopicAndQuestion = null;
const Notification = (content = "", title = "", duration = 2500, func) => {
GM_notification({
text: content,
title: title,
timeout: duration,
onclick: func,
});
};
const installTips = () => {
//first time run, open the usermanual webpage
if (GM_getValue("initial")) return;
const usermanual =
"https://github.com/Kyouichirou/D7E1293/blob/main/Tmapermonkey/zhihu_optimizer_manual.md";
GM_setValue("initial", true);
Notification(
"thanks for installing, please read user manual carefully",
"Tips",
6000
);
GM_openInTab(usermanual, { insert: true });
};
const mergeArray = (origin, target) => {
origin = origin.concat(target);
const newArr = [];
const tmpObj = {};
for (const e of origin) {
if (!tmpObj[e]) {
newArr.push(e);
tmpObj[e] = 1;
}
}
return newArr;
};
const getSelection = () => {
const select = window.getSelection();
return select ? select.toString().trim() : null;
};
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 = `
<div
id = "assist-button-container"
>
<style>
button.assist-button {
border-radius: 0 1px 1px 0;
border: rgb(247, 232, 176) solid 1.2px;
display: inline-block;
margin-top: 4px;
font-size: 14px;
height: 28px;
width: 75px;
box-shadow: 3px 4px 1px #888888;
justify-content: center;
}
div#assist-button-container {
opacity: 0.15;
${position}: 4%;
width: 60px;
flex-direction: column;
position: fixed;
bottom: 7%;
}
div#assist-button-container:hover {
opacity: 1;
transition: opacity 2s;
}
</style>
${otherButton}
<button class="assist-button block" style="color: black;" title=${title}>${name}</button>
</div>`;
document.documentElement.insertAdjacentHTML("beforeend", html);
};
const createPopup = (wtime = 3) => {
const html = `
<div
id="autoscroll-tips"
style="
top: 45%;
left: 45%;
position: fixed;
background: whitesmoke;
width: 210px;
height: 96px;
z-index: 1000;
"
>
<div class="autotips_content" style="margin: 5px 5px 5px 22px">
<span class="tips-header">Auto Scroll Mode</span>
<br />
<span class="tips_show" style="font-size: 12px"
>After ${wtime}s, auto load next page</span
>
<br />
<button
style="
width: 60px;
height: 24px;
box-shadow: 3px 4px 1px #888888;
margin-top: 10px;
margin-right: 10px;
border: rgb(247, 232, 176) solid 1.2px;
"
>
OK
</button>
<button
style="
width: 60px;
height: 24px;
box-shadow: 3px 4px 1px #888888;
border: white solid 1px;
"
>
Cancel
</button>
</div>
</div>`;
document.body.insertAdjacentHTML("beforeend", html);
};
const xmlHTTPRequest = (url, time = 2500, rType = false) => {
return new Promise(function (resolve, reject) {
GM_xmlhttpRequest({
method: "GET",
url: url,
timeout: time,
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 rndRangeNum = (start, end, count) => {
if (end < 0 || start < 0) return null;
if (end < start || end - start + 1 < count) return null;
const tmpArr = [];
const rndArr = [];
end++;
for (let i = start; i < end; i++) tmpArr.push(i);
for (; count > 0; count--) {
const ir = tmpArr.length - 1;
const rnd = Math.floor(Math.random() * ir);
rndArr.push(tmpArr[rnd]);
tmpArr[rnd] = tmpArr[ir];
tmpArr.pop();
}
return rndArr;
};
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 Indexnames() {
return this.table.indexNames;
}
get DBversion() {
return this.store.version;
}
getTablecount(key) {
return this.table.count(key);
}
//take care the transaction, must make sure the transaction is alive when you need deal with something continually
openTable() {
this.isfinish = false;
this.transaction = this.store.transaction(
[this.tbname],
this.RWmode
);
this.table = this.transaction.objectStore(this.tbname);
this.transaction.oncomplete = () => (this.isfinish = true);
this.transaction.onerror = () =>
console.log("warning, error on transaction");
}
get Table() {
this.transaction = this.store.transaction(
[this.tbname],
this.RWmode
);
return this.transaction.objectStore(this.tbname);
}
rollback() {
this.transaction && this.transaction.abort();
}
read(keyPath) {
let ic = 0;
return new Promise((resolve, reject) => {
while (!this.table || this.isfinish) {
this.openTable();
ic++;
if (ic > 3) {
reject("the transaction has completed");
return;
}
}
try {
const request = this.table.get(keyPath);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject("error");
} catch (err) {
console.log(err);
const transaction = this.store.transaction(
[this.tbname],
this.RWmode
);
const table = transaction.objectStore(this.tbname);
const request = 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) => {
if (!this.table || this.isfinish) this.openTable();
const op = this.table.add(info);
op.onsuccess = () => resolve(true);
op.onerror = (e) => {
console.log(e);
reject("error");
};
});
}
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) => {
if (!this.table || this.isfinish) this.openTable();
//keep cursor
if (mode) {
this.read(info[keyPath]).then(
(result) => {
if (!result) {
this.add(info).then(
() => resolve(true),
(err) => reject(err)
);
} else {
const op = this.table.put(
Object.assign(result, info)
);
op.onsuccess = () => resolve(true);
op.onerror = (e) => {
console.log(e);
reject("error");
};
}
},
(err) => console.log(err)
);
} else {
const op = this.table.put(info);
op.onsuccess = () => resolve(true);
op.onerror = (e) => {
console.log(e);
reject("error");
};
}
});
}
clear() {
if (!this.table) this.openTable();
this.table.clear();
}
//must have primary key
deleteiTems(keyPath) {
return new Promise((resolve, reject) => {
if (!this.table || this.isfinish) this.openTable();
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;
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,
additem(columnID) {
const info = {};
info.pid = this.pid;
info.update = Date.now();
info.excerpt = "";
info.visitTimes = 1;
info.visitTime = info.update;
const contentholder = document.getElementsByClassName(
"RichText ztext Post-RichText"
);
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 = document.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 = "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 hightlight this article, eg: this article is about advantage python usage"
);
info.tags = tags;
const title = document.title;
info.note = note || "";
info.title = title.slice(0, title.length - 5);
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 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) {
this.db.update(info);
},
close(mode = true) {
mode && this.db.close();
this.db = null;
},
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 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 = `
<li class=${className}>
<a href="#"
><div class="up">
<div class="shadow"></div>
<div class="inn">${value}</div>
</div>
<div class="down">
<div class="shadow"></div>
<div class="inn">${value}</div>
</div></a
>
</li>`;
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 = `
<style>
.clock {
width: auto;
zoom: 0.6;
}
.flip-clock-dot {
background: #ccc;
}
.flip-clock-meridium a {
color: #ccc;
}
#box {
display: table;
}
#content {
text-align: center;
display: table-cell;
vertical-align: middle;
}
</style>
<style>
.flip-clock-wrapper * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-ms-backface-visibility: hidden;
-o-backface-visibility: hidden;
backface-visibility: hidden;
}
.flip-clock-wrapper a {
cursor: pointer;
text-decoration: none;
color: #ccc;
}
.flip-clock-wrapper a:hover {
color: #fff;
}
.flip-clock-wrapper ul {
list-style: none;
}
.flip-clock-wrapper.clearfix:before,
.flip-clock-wrapper.clearfix:after {
content: " ";
display: table;
}
.flip-clock-wrapper.clearfix:after {
clear: both;
}
.flip-clock-wrapper.clearfix {
*zoom: 1;
} /* Main */
.flip-clock-wrapper {
font: normal 11px "Helvetica Neue", Helvetica, sans-serif;
-webkit-user-select: none;
}
.flip-clock-meridium {
background: none !important;
box-shadow: 0 0 0 !important;
font-size: 36px !important;
}
.flip-clock-meridium a {
color: #313333;
}
.flip-clock-wrapper {
text-align: center;
position: relative;
width: 100%;
margin: 1em;
}
.flip-clock-wrapper:before,
.flip-clock-wrapper:after {
content: " "; /* 1 */
display: table; /* 2 */
}
.flip-clock-wrapper:after {
clear: both;
} /* Skeleton */
.flip-clock-wrapper ul {
position: relative;
float: left;
margin: 5px;
width: 60px;
height: 90px;
font-size: 80px;
font-weight: bold;
line-height: 87px;
border-radius: 6px;
background: #000;
}
.flip-clock-wrapper ul li {
z-index: 1;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
line-height: 87px;
text-decoration: none !important;
}
.flip-clock-wrapper ul li:first-child {
z-index: 2;
}
.flip-clock-wrapper ul li a {
display: block;
height: 100%;
-webkit-perspective: 200px;
-moz-perspective: 200px;
perspective: 200px;
margin: 0 !important;
overflow: visible !important;
cursor: default !important;
}
.flip-clock-wrapper ul li a div {
z-index: 1;
position: absolute;
left: 0;
width: 100%;
height: 50%;
font-size: 80px;
overflow: hidden;
outline: 1px solid transparent;
}
.flip-clock-wrapper ul li a div .shadow {
position: absolute;
width: 100%;
height: 100%;
z-index: 2;
}
.flip-clock-wrapper ul li a div.up {
-webkit-transform-origin: 50% 100%;
-moz-transform-origin: 50% 100%;
-ms-transform-origin: 50% 100%;
-o-transform-origin: 50% 100%;
transform-origin: 50% 100%;
top: -0.1px;
}
.flip-clock-wrapper ul li a div.up:after {
content: "";
position: absolute;
top: 44px;
left: 0;
z-index: 5;
width: 100%;
height: 3px;
background-color: #000;
background-color: rgba(0, 0, 0, 0.4);
}
.flip-clock-wrapper ul li a div.down {
-webkit-transform-origin: 50% 0;
-moz-transform-origin: 50% 0;
-ms-transform-origin: 50% 0;
-o-transform-origin: 50% 0;
transform-origin: 50% 0;
bottom: 0;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
}
.flip-clock-wrapper ul li a div div.inn {
position: absolute;
left: 0;
z-index: 1;
width: 100%;
height: 200%;
color: #ccc;
text-shadow: 0 1px 2px #000;
text-align: center;
background-color: #333;
border-radius: 6px;
font-size: 70px;
}
.flip-clock-wrapper ul li a div.up div.inn {
top: 0;
}
.flip-clock-wrapper ul li a div.down div.inn {
bottom: 0;
} /* PLAY */
.flip-clock-wrapper ul.play li.flip-clock-before {
z-index: 3;
}
.flip-clock-wrapper .flip {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.7);
}
.flip-clock-wrapper ul.play li.flip-clock-active {
-webkit-animation: asd 0.01s 0.49s linear both;
-moz-animation: asd 0.01s 0.49s linear both;
animation: asd 0.01s 0.49s linear both;
z-index: 5;
}
.flip-clock-divider {
float: left;
display: inline-block;
position: relative;
width: 20px;
height: 100px;
}
.flip-clock-divider:first-child {
width: 0;
}
.flip-clock-dot {
display: block;
background: #323434;
width: 10px;
height: 10px;
position: absolute;
border-radius: 50%;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
left: 5px;
}
.flip-clock-divider .flip-clock-label {
position: absolute;
top: -1.5em;
right: -86px;
color: black;
text-shadow: none;
}
.flip-clock-divider.minutes .flip-clock-label {
right: -88px;
}
.flip-clock-divider.seconds .flip-clock-label {
right: -91px;
}
.flip-clock-dot.top {
top: 30px;
}
.flip-clock-dot.bottom {
bottom: 30px;
}
@-webkit-keyframes asd {
0% {
z-index: 2;
}
100% {
z-index: 4;
}
}
@-moz-keyframes asd {
0% {
z-index: 2;
}
100% {
z-index: 4;
}
}
@-o-keyframes asd {
0% {
z-index: 2;
}
100% {
z-index: 4;
}
}
@keyframes asd {
0% {
z-index: 2;
}
100% {
z-index: 4;
}
}
.flip-clock-wrapper ul.play li.flip-clock-active .down {
z-index: 2;
-webkit-animation: turn 0.5s 0.5s linear both;
-moz-animation: turn 0.5s 0.5s linear both;
animation: turn 0.5s 0.5s linear both;
}
@-webkit-keyframes turn {
0% {
-webkit-transform: rotateX(90deg);
}
100% {
-webkit-transform: rotateX(0deg);
}
}
@-moz-keyframes turn {
0% {
-moz-transform: rotateX(90deg);
}
100% {
-moz-transform: rotateX(0deg);
}
}
@-o-keyframes turn {
0% {
-o-transform: rotateX(90deg);
}
100% {
-o-transform: rotateX(0deg);
}
}
@keyframes turn {
0% {
transform: rotateX(90deg);
}
100% {
transform: rotateX(0deg);
}
}
.flip-clock-wrapper ul.play li.flip-clock-before .up {
z-index: 2;
-webkit-animation: turn2 0.5s linear both;
-moz-animation: turn2 0.5s linear both;
animation: turn2 0.5s linear both;
}
@-webkit-keyframes turn2 {
0% {
-webkit-transform: rotateX(0deg);
}
100% {
-webkit-transform: rotateX(-90deg);
}
}
@-moz-keyframes turn2 {
0% {
-moz-transform: rotateX(0deg);
}
100% {
-moz-transform: rotateX(-90deg);
}
}
@-o-keyframes turn2 {
0% {
-o-transform: rotateX(0deg);
}
100% {
-o-transform: rotateX(-90deg);
}
}
@keyframes turn2 {
0% {
transform: rotateX(0deg);
}
100% {
transform: rotateX(-90deg);
}
}
.flip-clock-wrapper ul li.flip-clock-active {
z-index: 3;
} /* SHADOW */
.flip-clock-wrapper ul.play li.flip-clock-before .up .shadow {
background: -moz-linear-gradient(
top,
rgba(0, 0, 0, 0.1) 0%,
black 100%
);
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, rgba(0, 0, 0, 0.1)),
color-stop(100%, black)
);
background: linear, top, rgba(0, 0, 0, 0.1) 0%, black 100%;
background: -o-linear-gradient(
top,
rgba(0, 0, 0, 0.1) 0%,
black 100%
);
background: -ms-linear-gradient(
top,
rgba(0, 0, 0, 0.1) 0%,
black 100%
);
background: linear, to bottom, rgba(0, 0, 0, 0.1) 0%, black 100%;
-webkit-animation: show 0.5s linear both;
-moz-animation: show 0.5s linear both;
animation: show 0.5s linear both;
}
.flip-clock-wrapper ul.play li.flip-clock-active .up .shadow {
background: -moz-linear-gradient(
top,
rgba(0, 0, 0, 0.1) 0%,
black 100%
);
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, rgba(0, 0, 0, 0.1)),
color-stop(100%, black)
);
background: linear, top, rgba(0, 0, 0, 0.1) 0%, black 100%;
background: -o-linear-gradient(
top,
rgba(0, 0, 0, 0.1) 0%,
black 100%
);
background: -ms-linear-gradient(
top,
rgba(0, 0, 0, 0.1) 0%,
black 100%
);
background: linear, to bottom, rgba(0, 0, 0, 0.1) 0%, black 100%;
-webkit-animation: hide 0.5s 0.3s linear both;
-moz-animation: hide 0.5s 0.3s linear both;
animation: hide 0.5s 0.3s linear both;
} /*DOWN*/
.flip-clock-wrapper ul.play li.flip-clock-before .down .shadow {
background: -moz-linear-gradient(
top,
black 0%,
rgba(0, 0, 0, 0.1) 100%
);
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, black),
color-stop(100%, rgba(0, 0, 0, 0.1))
);
background: linear, top, black 0%, rgba(0, 0, 0, 0.1) 100%;
background: -o-linear-gradient(
top,
black 0%,
rgba(0, 0, 0, 0.1) 100%
);
background: -ms-linear-gradient(
top,
black 0%,
rgba(0, 0, 0, 0.1) 100%
);
background: linear, to bottom, black 0%, rgba(0, 0, 0, 0.1) 100%;
-webkit-animation: show 0.5s linear both;
-moz-animation: show 0.5s linear both;
animation: show 0.5s linear both;
}
.flip-clock-wrapper ul.play li.flip-clock-active .down .shadow {
background: -moz-linear-gradient(
top,
black 0%,
rgba(0, 0, 0, 0.1) 100%
);
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, black),
color-stop(100%, rgba(0, 0, 0, 0.1))
);
background: linear, top, black 0%, rgba(0, 0, 0, 0.1) 100%;
background: -o-linear-gradient(
top,
black 0%,
rgba(0, 0, 0, 0.1) 100%
);
background: -ms-linear-gradient(
top,
black 0%,
rgba(0, 0, 0, 0.1) 100%
);
background: linear, to bottom, black 0%, rgba(0, 0, 0, 0.1) 100%;
-webkit-animation: hide 0.5s 0.3s linear both;
-moz-animation: hide 0.5s 0.3s linear both;
animation: hide 0.5s 0.2s linear both;
}
@-webkit-keyframes show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-moz-keyframes show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-o-keyframes show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-webkit-keyframes hide {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@-moz-keyframes hide {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@-o-keyframes hide {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes hide {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>`;
const info = this.formated_Time;
const pref = "flip-clock-";
const html = `
<div
id="clock_box"
style="
top: 2%;
width: 20%;
float: left;
left: 50px;
z-index: 1000;
position: fixed;
"
>
${css}
<div id="content">
<div class="clock flip-clock-wrapper" id="flipclock">
<span class="flip-clock-divider"
><span class="flip-clock-label"></span
><span class="flip-clock-dot top"></span
><span class="flip-clock-dot bottom"></span
></span>
<ul class="flip ahour">${this.create_Module(
pref + "before",
info.before[0]
)}${this.create_Module(
pref + "active",
this.time_arr[0]
)}</ul>
<ul class="flip bhour">${this.create_Module(
pref + "before",
info.before[1]
)}${this.create_Module(
pref + "active",
this.time_arr[1]
)}</ul>
<span class="flip-clock-divider"
><span class="flip-clock-label"></span
><span class="flip-clock-dot top"></span
><span class="flip-clock-dot bottom"></span
></span>
<ul class="flip play aminute">${this.create_Module(
pref + "before",
info.before[2]
)}${this.create_Module(
pref + "active",
this.time_arr[2]
)}</ul>
<ul class="flip play bminute">${this.create_Module(
pref + "before",
info.before[3]
)}${this.create_Module(
pref + "active",
this.time_arr[3]
)}</ul>
<ul class="flip-clock-meridium">
<li><a href="#">${(this.currentHour = this.getCurrentHour_status(
info.hour
))}</a></li>
</ul>
</div>
</div>
</div>`;
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 {any} 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,
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";
const bgcM = `background-image: url(https://www.cnblogs.com/skins/coffee/images/bg_body.gif);`;
let bgcimg = "";
if (bgc) {
for (let i = 1; i < 8; i++)
arr[i - 1] = bgc === `a_color${i}` ? " cur" : "";
bgc === "a_color7"
? (bgcimg = bgcM)
: (color = this.colors_list(bgc));
} else {
arr.fill("", 0);
arr[5] = " cur";
}
let title = document.title;
title = title.slice(0, title.lastIndexOf("-") - 1);
const meta = node.getElementsByClassName(
"AuthorInfo AnswerItem-authorInfo AnswerItem-authorInfo--related"
);
const mBackup =
'<div class="AuthorInfo AnswerItem-authorInfo AnswerItem-authorInfo--related" itemprop="author" itemscope="" itemtype="http://schema.org/Person">No_data</div>';
const content = node.getElementsByClassName(
"RichText ztext CopyrightRichText-richText"
);
const cBackup = "No_data";
const html = `
<div
id="artfullscreen"
class="artfullscreen__"
style="display: block; height: -webkit-fill-available"
>
<style type="text/css">
div#artfullscreen {
width: 100%;
height: 100%;
background: #e3e3e3;
z-index: 999;
position: fixed;
left: 0;
top: 0;
text-align: left;
overflow: auto;
display: block;
}
div#artfullscreen__box {
padding: 32px 60px;
height: auto;
overflow: hidden;
background: ${color};
${bgcimg}
box-shadow: 0 0 6px #999;
display: table;
margin: 20px auto;
min-height: 95%;
}
div#artfullscreen__box_scr {
word-break: break-word;
height: auto;
font-size: 16px;
color: #2f2f2f;
line-height: 1.5;
position: relative;
}
h2#titiletext {
font-size: 30px;
font-family: simhei;
color: #000;
line-height: 40px;
margin: 0 0 30px 0;
overflow: hidden;
text-align: left;
word-break: break-all;
}
</style>
<div
id="artfullscreen__box"
class="artfullscreen__box"
style="width: 930px"
>
<div class="artfullscreen__box_scr" id="artfullscreen__box_scr">
<table style="width: 656px">
<tbody>
<tr>
<td id="artContent" style="max-width: 1000px">
<h2 id="titiletext">
${title}
</h2>
${
meta.length > 0
? meta[0].outerHTML
: mBackup
}
<hr>
<div
style="
width: 1200px;
margin: 0;
padding: 0;
height: 0;
"
></div>
<span class="RichText ztext CopyrightRichText-richText" itemprop="text"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>`;
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 = `
<<div id="reader_navigator">
<style type="text/css">
.readerpage-l,
.readerpage-r {
width: 300px;
height: 500px;
overflow: hidden;
cursor: pointer;
position: fixed;
top: 0;
bottom: 0;
margin: auto;
z-index: 1000;
}
.readerpage-l.disa,
.readerpage-r.disa {
cursor: not-allowed;
}
.readerpage-l:hover,
.readerpage-r:hover {
background: rgba(100, 100, 100, 0.03);
}
.readerpage-l::before,
.readerpage-r::before {
content: '';
width: 100px;
height: 100px;
border-left: 2px solid #ADAFB0;
border-bottom: 2px solid #ADAFB0;
position: absolute;
top: 0;
bottom: 0;
margin: auto;
}
.readerpage-l:hover::before,
.readerpage-r:hover::before {
border-color: #fff;
}
.readerpage-l {
left: 0;
}
.readerpage-l::before {
left: 45%;
transform: rotate(45deg);
}
.readerpage-r {
right: 0;
}
.readerpage-r::before {
right: 45%;
transform: rotate(225deg);
}
@media (max-width: 1280px) {
.readerpage-l,
.readerpage-r {
width: 150px;
}
}
</style>
<div class="readerpage-l${statusl}" title=${escapeBlank(
titlel
)}></div>
<div class="readerpage-r${statusr}" title=${escapeBlank(
titler
)}></div>
</div>`;
document.body.insertAdjacentHTML("beforeend", html);
},
toolBar(arr) {
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 = `
<div
class="artfullscreen_toolbar"
id="artfullscreen_toolbar"
style="z-index: 9999; left: 1540px"
>
<style type="text/css">
.artfullscreen_toolbar {
width: 24px;
height: 166px;
position: fixed;
top: 15px;
}
a#artfullscreen_closer {
color: #4a4a4a;
width: 30px;
height: 24px;
line-height: 24px;
text-align: center;
display: block;
font-size: 28px;
font-weight: bold;
text-decoration: none;
float: right;
}
.artfullscreen_toolbar>div {
height: 19px;
clear: both;
padding: 18px 3px 0 0;
}
.artfullscreen_toolbar .a_colorlist,.artfullscreen_toolbar .fschange {
box-shadow: 0 0 5px #ccc;
}
.artfullscreen_toolbar .fschange input {
margin-right: 2px;
}
.artfullscreen_toolbar .a_bgcolor {
margin-left: 0;
}
.a_colorlist {
z-index: 2;
width: 146px;
height: 35px;
border: 1px solid #cbcbcb;
background: #fff;
position: absolute;
right: 38px;
top: 30px;
text-align: center;
display: none;
}
.a_colorlist span {
display: inline-block;
width: 13px;
height: 13px;
overflow: hidden;
border: solid 1px #cbcbcb;
margin: 10px 1px 0;
cursor: pointer;
font-size: 12px;
}
.a_colorlist img {
vertical-align: top;
visibility: hidden;
}
.a_colorlist .cur img {
visibility: visible !important;
}
.a_color1 {
background: #E3EDCD !important;
}
.a_color2 {
background: #f5f1e6 !important;
}
.a_color3 {
background: #B6B6B6 !important;
}
.a_color4 {
background: #FFF2E2 !important;
}
.a_color5 {
background: #FAF9DE !important;
}
.a_color6 {
background: #FFF !important;
}
.a_color7 {
background: #EC9857 !important;
}
</style>
<a href="#" class="artfullscreen_closer" id="artfullscreen_closer">×</a>
<div class="d1">
<div class="a_bgcolor">
<img src=${jpgBase64} />
<div class="a_colorlist" style="display: none">
<span class="a_color1${arr[0]}">
<img src=${gifBase64}
/></span>
<span class="a_color2${arr[1]}">
<img src=${gifBase64}
/></span>
<span class="a_color3${arr[2]}">
<img src=${gifBase64}
/></span>
<span class="a_color4${arr[3]}">
<img src=${gifBase64}
/></span>
<span class="a_color5${arr[4]}">
<img src=${gifBase64}
/></span>
<span class="a_color6${arr[5]}">
<img src=${gifBase64}
/></span>
<span class="a_color7${arr[6]}" title="background image">
<img src=${gifBase64}
/></span>
</div>
</div>
</div>
<div class="d2" style="display: none;">
<i
class="load_more"
title="load more answers"
style="
height: 24px;
width: 24px;
margin-left: -2px;
content: url(data:image/webp;base64,UklGRigBAABXRUJQVlA4TBwBAAAvP8APEFDcto3D/cc+pF35HhABt9r2Os0niZKSDrv2Dmxg6nvFENAzCkkW8AwMQR/qXEuOdP7vVeC2jTI4hhw+gn4ClWRF7lEURWplwUlDiXVynZJivw24mpHIwuhopZBcMxpsbAzUoMhWjkaLhTlxVz8APKwLSpl0Jg3QTPoSxdoDgB/XiZEPQAgGHBOlRHpPQAjAU09SckeAhQB8tCUKOUEVzKzCvyVJUUzwYmb2gimTWPqHysxChRMpXcIdGpgZKlzILEAaNHBQaIZgZuQCFQEE7m1k7guCAWYNbiGrkA0EBxQBG1JC3g1aQBHwZf6AzDPOg4w6yrzQ5MBAOp9pIN1ooFP3Vu3vrbG4t9N3I597N7ff7f1/4yNQAg==);
"
>More</i
>
</div>
</div>`;
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(() => {
const tool = document.getElementById(
"artfullscreen_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.node || this.autoReader_mode) {
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 = 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"
);
const bgcM =
"url(https://www.cnblogs.com/skins/coffee/images/bg_body.gif)";
if (className === "a_color7") {
box.style.backgroundImage = bgcM;
} else {
box.style.backgroundImage = "none";
box.style.background = this.colors_list(
className
);
}
GM_setValue("articleBackground", className);
}
};
//tool.lastElementChild.onclick = () => this.loadMoreAnswer();
colorlist = null;
closer = null;
}, 50);
},
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, mode) {
const html = `
<img class="_video_cover" src=${picURL} style="object-fit: contain;${
mode
? " position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; z-index: 1;"
: " width: 100%;"
}" />
<video preload="metadata" width="100%" controls="">
<source
src=${videoURL}
type="video/mp4"
/>
</video>
<div
class="_player_ico"
style="
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
${mode ? "z-index: 2;" : ""}
"
>
<div class="_play_ico" style="width: 50px; height: 50px">
<span class="_play_ico_content"
><svg viewBox="0 0 72 72" class="_ico_content">
<g fill="none" fill-rule="evenodd">
<circle
cx="36"
cy="36"
r="36"
fill="#FFF"
fill-opacity=".95"
></circle>
<path
fill="#444"
fill-rule="nonzero"
d="M50.8350169,37.0602664 L29.4767217,49.9693832 C28.900608,50.3175908 28.1558807,50.1251285 27.8133266,49.5395068 C27.701749,49.3487566 27.6428571,49.1309436 27.6428571,48.9090213 L27.6428571,23.0907876 C27.6428571,22.4094644 28.1862113,21.8571429 28.8564727,21.8571429 C29.0747919,21.8571429 29.2890685,21.9170066 29.4767217,22.0304257 L50.8350169,34.9395425 C51.4111306,35.28775 51.6004681,36.0447682 51.257914,36.6303899 C51.154433,36.8072984 51.0090531,36.9550776 50.8350169,37.0602664 Z"
></path>
</g></svg
></span>
</div>
</div>`;
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");
}
},
ElementVisible(f, element) {
const getElementTopLeft = (obj) => {
let top = 0;
let left = 0;
while (obj) {
top += obj.offsetTop;
left += obj.offsetLeft;
obj = obj.offsetParent;
}
return { top: top, left: left };
};
const tmp = getElementTopLeft(element);
const offset = f.scrollTop;
return (
tmp.top + element.clientHeight > offset &&
offset + window.innerHeight > tmp.top
);
},
//click image to show the raw pic
imgClick: {
create(node, info) {
const html = `
<div class="ImageView is-active" style="padding-bottom: 10px">
<div class="ImageView-inner" style="overflow: auto">
<img
src=${info.url}
class="ImageView-img"
alt="preview"
style="
width: ${info.width};
transform: ${info.transform};
opacity: 1;
"
/>
</div>
</div>`;
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);
},
GifPlay(target) {
let url = target.src;
const [a, b] = url.includes(".webp")
? [".webp", ".jpg"]
: [".jpg", ".webp"];
target.src = url.replace(a, b);
target.parentNode.className =
"GifPlayer" + (a === ".webp" ? "" : " isPlaying");
},
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 === "ztext-gif")
this.GifPlay(target);
else if (className === "_video_cover")
this.video_Play(target.nextElementSibling);
else this.remove(n);
} else {
const localName = target.localName;
if (
localName &&
(localName === "path" ||
localName === "circle")
) {
const paths = e.path;
for (const p of paths) {
if (p.className === "_player_ico") {
this.video_Play(
p.previousElementSibling
);
break;
}
}
}
}
};
}, 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;
}
this.video_list = [];
const videoas = f.getElementsByClassName("RichText-video");
const videobs = f.getElementsByClassName("ZVideoLinkCard");
const a = videoas.length;
const b = videobs.length;
if (a + b === 0) return;
for (let k = 0; k < a; k++)
this.ElementVisible(f, videoas[k])
? this.get_video_ID(videoas[k])
: this.video_list.push(videoas[k]);
for (let k = 0; k < b; k++)
this.ElementVisible(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;
f.onscroll = () => {
if (unfinished || !this.video_list) return;
unfinished = true;
this.video_list.forEach((v, index) => {
if (!this.loadedList[index]) {
if (this.ElementVisible(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;
},
createMonitor() {
new MutationObserver(
(e) => e.length > 2 && (this.scroll_record += 3)
).observe(this.curNode.parentNode, { childList: true });
},
creatEvent(f) {
const n = this.nav;
n.children[1].onclick = () => this.Previous(f);
n.children[2].onclick = () => this.Next(f);
this.loadLazy(f);
this.imgClick.event(f, n, this.autoScroll);
this.getVideo_element(f);
this.createMonitor();
setTimeout(() => this.time_module.main(), 300);
},
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) {
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;
if (this.scrollState) {
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,
stopScroll(mode) {
if (this.scrollState) {
this.scrollPos = null;
this.scrollTime = null;
this.scrollState = false;
this.keyCount = 1;
//修改 scrollend
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 > 240
? 5000
: 4000;
gap > 240
? this.autoButton_event()
: setTimeout(
() =>
!this.auto_pause && this.autoReader(),
wtime
);
this.timeID = setTimeout(() => {
this.timeID = null;
if (this.auto_pause) return;
this.keyCount = 2;
this.start();
}, wtime + 3500);
} else {
this.timeID && clearTimeout(this.timeID);
this.timeID = null;
!this.autoReader && (this.node = null);
}
this.stopwatch = 0;
}
},
speedUP() {
this.scrollState && this.stepTime < 5
? (this.stepTime = 5)
: (this.stepTime -= 5);
},
slowDown() {
this.scrollState && this.stepTime > 100
? (this.stepTime = 100)
: (this.stepTime += 5);
},
stopwatch: 0,
startID: null,
start() {
this.keyCount += 1;
if (this.keyCount % 2 === 0) return;
if (this.scrollState) this.stopScroll(false);
else {
this.timeID &&
(clearTimeout(this.timeID), (this.timeID = null));
this.stopwatch = Date.now();
this.scrollState = true;
!this.node &&
(this.node = document.getElementById(
"artfullscreen"
));
this.startID &&
(clearTimeout(this.startID), (this.startID = null));
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"
);
},
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()
: zhihu.multiSearch(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);
},
isRunning: false,
scroll_record: 0,
chang_time_id: null,
//settimeout => prevent too many times of operation
changeContent(node, mode = true, direction) {
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;
this.imgClick.remove(f);
const content = node.getElementsByClassName(cName);
const author = node.getElementsByClassName(aName);
f.getElementsByClassName(cName)[0].innerHTML =
content.length > 0 ? content[0].innerHTML : "No data";
f.getElementsByClassName(aName)[0].innerHTML =
author.length > 0 ? author[0].innerHTML : "No data";
this.loadLazy(f);
if (mode) {
this.answerID = node;
// trigger the scroll event to load more answers;
this.isSimple_page || this.allAnswser_loaded
? ((this.navPannel = this.curNode = node),
this.changeNav(this.nav),
setTimeout(() => (this.isRunning = false), 400))
: setTimeout(() => {
this.overFlow = false;
f.style.overflow = "hidden";
node.scrollIntoView();
setTimeout(() => {
this.scroll_record < 5 &&
window.scrollTo(
0,
0.98 *
document.documentElement
.scrollHeight
);
setTimeout(() => {
this.scroll_record -= 1;
this.navPannel = this.curNode = node;
this.changeNav(this.nav);
const time = this.allAnswser_loaded
? 500
: 0;
setTimeout(() => {
this.overFlow = true;
f.style.overflow = "auto";
this.isRunning = false;
}, time);
}, 350);
}, 350);
}, 50);
} else this.ShowOrExit(true);
this.getVideo_element(f);
setTimeout(() => {
f.scrollTo(0, 0);
(this.allAnswser_loaded ||
!mode ||
this.isSimple_page) &&
(this.isRunning = false);
}, 300);
}, 300);
},
Next(f) {
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) {
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");
},
ShowOrExit(mode) {
if (!mode && (this.isRunning || this.autoScroll.scrollState))
return;
const n = this.nav;
const display = mode ? "block" : "none";
n && (n.style.display = display);
const f = this.full;
f && (f.style.display = display);
if (mode) {
this.changeNav(n);
const tool = document.getElementById(
"artfullscreen_toolbar"
);
tool && (tool.style.display = display);
this.time_module.clock_paused = false;
} else {
/*
exit reader mode, then move to the position of current node
wait the reader is hidden, scroll to current answer
*/
this.time_module.clock_paused = true;
this.overFlow = false;
this.readerMode = mode;
if (document.title === "出了一点问题") {
confirm("the webpage has crashed, is reload?") &&
location.reload();
return;
}
const offsetTop = this.curNode.offsetTop;
offsetTop !== window.pageYOffset &&
(this.isSimple_page ||
this.nextNode ||
this.allAnswser_loaded) &&
setTimeout(() => this.curNode.scrollIntoView(), 300);
}
},
Change(node, aid) {
aid === this.aid
? this.ShowOrExit(true)
: this.changeContent(node, 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);
}
},
/**
* @param {any} 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 = `
<div
id="load_status"
style="
top: 0%;
z-index: 1000;
position: fixed;
height: 24px;
width: 50%;
font-size: 14px;
font-weight: 500;
margin-left: 25%;
text-align: center;
color: ${info.color};
background: ${info.bgc};
opacity: 0.8;
box-shadow: 0 0 15px #FFBB59;
"
>
${info.text + tips}...
</div>`;
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,
more_Answer_button_display(toolbar, mode) {
if (!toolbar) return;
toolbar.lastElementChild.style.display = mode
? "block"
: "none";
},
/**
* @param {{ parentNode: any; }} pnode
*/
set navPannel(pnode) {
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.nextNode) this.allAnswser_loaded = false;
else {
if (this.allAnswser_loaded || this.isSimple_page) {
this.allAnswser_loaded = true;
this.isShowTips &&
this.show_status.main(
this.show_status.node ? null : this.full,
"all answers have been loaded"
);
this.isShowTips = false;
return;
}
const button = document.getElementsByClassName(
"Button QuestionAnswers-answerButton Button--blue Button--spread"
);
this.allAnswser_loaded = true;
button.length > 0
? button[0].scrollIntoView()
: (this.isShowTips &&
window.scrollTo(
0,
0.75 * document.documentElement.scrollHeight
),
setTimeout(
() =>
window.scrollTo(
0,
0.98 *
document.documentElement.scrollHeight
),
100
));
setTimeout(() => (this.navPannel = pnode), 350);
}
},
removeADs() {
const ads = document.getElementsByClassName("Pc-word");
let i = ads.length;
if (i > 0) for (i; i--; ) ads[i].remove();
},
nextNode: null,
prevNode: null,
curNode: null,
aid: null,
isSimple_page: false,
isShowTips: false,
main(pnode, aid) {
//---------------------------------------check if the node has pre and next node
const p = pnode.parentNode;
this.isSimple_page = location.pathname.includes("/answer/");
this.removeADs();
if (this.isSimple_page) this.navPannel = p;
this.firstly ? this.Reader(pnode) : this.Change(pnode, aid);
this.curNode = p;
!this.isSimple_page &&
setTimeout(() => {
window.scrollTo(
0,
0.75 * document.documentElement.scrollHeight
);
setTimeout(
() => (
(this.overFlow = true),
((this.navPannel = p), this.changeNav(this.nav))
),
300
);
}, 100);
this.firstly = false;
this.aid = aid;
this.readerMode = true;
this.isShowTips = true;
},
},
getData() {
blackName = GM_getValue("blackname");
(!blackName || !Array.isArray(blackName)) && (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);
},
event() {
document.oncopy = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
let copytext = getSelection();
if (!copytext) return;
this.clear(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(keyCode) {
const Names = {
65: "AboutMe",
68: "Douban",
71: "Google",
72: "Github",
77: "MDN",
66: "BiliBili",
90: "Zhihu",
};
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 = "") {
const select = getSelection();
//baidu restrict the length of search keyword is 38;
if (!select || this.string_length(select) > 38) return;
url += encodeURIComponent(select);
window.open(this.Protocols + url + parameter, "_blank");
},
Google() {
this.Search("cn.bing.com/results?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=");
},
AboutMe() {
zhihu.shade.Support.main();
},
};
const name = Names[keyCode];
name && methods[name]();
},
noteHightlight: {
editable: false,
disableSiderbar(pevent) {
const column = document.getElementById("column_lists");
if (column) column.style.pointerEvents = pevent;
},
EditDoc() {
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;
},
get Selection() {
return window.getSelection();
},
setMark(text, type) {
return `<mark class="AssistantMark ${type}">${text}</mark>`;
},
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";
},
zhuanlanAuto() {
if (zhihu.Column.targetIndex === 0) return;
const text = `${
this.zhuanlanAuto_mode ? "exit" : "enter"
} autoscroll mode`;
Notification(text, "Tips");
this.zhuanlanAuto_mode = !this.zhuanlanAuto_mode;
},
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",
"tips"
);
return;
}
ch[i].children[2].click();
setTimeout(() => {
this.keyCount = 2;
this.start();
}, 1800);
},
//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();
}, 3000);
buttons[0].onclick = () => {
clearTimeout(id);
tips.remove();
zhihu.scroll.toTop();
this.nextPage();
};
buttons[1].onclick = () => {
clearTimeout(id);
tips.remove();
this.zhuanlanAuto_mode = false;
Notification("exit autoscroll mode successfully", "tips");
};
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 && this.popup();
}
},
speedUP() {
this.stepTime < 5 ? (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)));
},
Others(keyCode, shift) {
shift
? keyCode === 67
? this.noteHightlight.removeMark()
: keyCode === 219
? this.Column.pagePrint()
: keyCode === 70
? this.Column.follow()
: keyCode === 76
? this.Column.columnsModule.recentModule.log("p")
: keyCode === 83
? this.Column.subscribe()
: this.noteHightlight.Marker(keyCode)
: keyCode === 113
? !this.autoScroll.scrollState &&
this.noteHightlight.EditDoc()
: 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(keyCode);
},
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());
}
},
keyBoardEvent() {
window.onkeydown = (e) => {
if (e.ctrlKey || e.altKey) return;
const className = e.target.className;
if (
(className && className.includes("DraftEditor")) ||
e.target.localName === "input"
)
return;
const keyCode = e.keyCode;
const shift = e.shiftKey;
if (keyCode === 68 || (shift && keyCode === 71)) {
//68, d, default is login shortcut of zhihu
//71, g, + shift, default is scroll to the bottom of webpage
e.preventDefault();
e.stopPropagation();
}
shift
? keyCode === 65
? this.zhuanlanAuto()
: keyCode === 84
? this.noColorful()
: this.Others.call(zhihu, keyCode, shift)
: keyCode === 192
? this.start()
: keyCode === 187
? this.speedUP()
: keyCode === 189
? this.slowDown()
: keyCode > 47 && keyCode < 58
? this.key_Click(keyCode)
: keyCode === 188 || keyCode === 190
? this.key_next_Pre(keyCode)
: this.Others.call(zhihu, keyCode);
};
},
},
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);
},
creatPopup() {
this.opacityChange(0);
const mt = -5;
const html = `
<div
id="support_me"
style="
background: darkgray;
text-align: justify;
width: 700px;
font-size: 16px;
height: 468px;
position: fixed;
left: 31%;
top: 25%;
z-index: 100000;
"
>
<div style="padding: 2.5%; font-weight: bold; font-size: 18px">
Support Me!
<span
class="shorts_cut"
style="font-size: 12px; font-weight: normal; float: right"
>
<a
href="https://img.meituan.net/csc/c67b957b2b711596f8af2d1ea29d4e1291396.png"
target="_blank"
title="shortcuts diagram"
style="color: #2b638b"
>Shortcuts</a
>
<span> || Version: ${GM_info.script.version}</span>
</span>
</div>
<div style="font-style: italic; font-size: 16px; padding-left: 2%">
Make Thing Better && Simpler!
<img
src="data:image/png;base64,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GEuv6XUAs4ZuQ4CCK840bndhAoCprELkC6QkFHzJHG5R+v2/y78BmfVhpxDogDsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDctMTlUMDM6Mzk6MjArMDA6MDCGZw5cAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAxLTA4VDE3OjI5OjQxKzAwOjAwZ4bKzwAAACB0RVh0c29mdHdhcmUAaHR0cHM6Ly9pbWFnZW1hZ2ljay5vcme8zx2dAAAAGHRFWHRUaHVtYjo6RG9jdW1lbnQ6OlBhZ2VzADGn/7svAAAAF3RFWHRUaHVtYjo6SW1hZ2U6OkhlaWdodAA2NLzgqYQAAAAWdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgANjRET2kJAAAAGXRFWHRUaHVtYjo6TWltZXR5cGUAaW1hZ2UvcG5nP7JWTgAAABd0RVh0VGh1bWI6Ok1UaW1lADE1NDY5Njg1ODEI9oyUAAAAEXRFWHRUaHVtYjo6U2l6ZQAxMzU2QtzgBkMAAABYdEVYdFRodW1iOjpVUkkAZmlsZTovLy9kYXRhL3d3d3Jvb3Qvd3d3LmVhc3lpY29uLm5ldC9jZG4taW1nLmVhc3lpY29uLmNuL2ZpbGVzLzU2LzU2Mjk0OC5wbmdNCDr0AAAAAElFTkSuQmCC"
style="
float: left;
height: 42px;
width: 42px;
margin: -10px 4px 0 0px;
"
/>
</div>
<div
class="support_img"
style="padding-top: 4%; width: 100%; padding-left: 7.5%"
>
<div class="qrCode">
<img
src="data:image/webp;base64,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"
/>
</div>
</div>
<div class="timeout" style="font-size: 12px; padding: 3%">
15s, this Tips will be automatically closed or can you just click
</div>
<a
href="https://github.com/Kyouichirou/D7E1293/blob/main/Tmapermonkey/zhihu_optimizer_manual.md"
target="blank"
style="margin: ${mt}px 10px 0px 0px; float: right; font-size: 14px"
title="user manual"
>
Github: Kyouichirou
</a>
</div>`;
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 = () =>
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 = `
<div
id="screen_shade_cover"
style="
transition: opacity 0.1s ease 0s;
z-index: 10000000;
margin: 0;
border-radius: 0;
padding: 0;
background: ${color};
pointer-events: none;
position: fixed;
top: -10%;
right: -10%;
width: 120%;
height: 120%;
opacity: ${opacity};
mix-blend-mode: multiply;
display: block;
"
></div>`;
document.documentElement.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);
},
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,
createShade() {
const colors = {
yellow: "rgb(247, 232, 176)",
green: "rgb(202 ,232, 207)",
grey: "rgb(182, 182, 182)",
olive: "rgb(207, 230, 161)",
};
let color = GM_getValue("color");
color && (color = colors[color]);
if (!color) {
const h = new Date().getHours();
color = h > 8 && h < 18 ? colors.yellow : colors.grey;
}
const 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) return;
this.menu.call(colors, newValue);
}
);
GM_setValue("opacity", opacity);
this.opacitylistenID = GM_addValueChangeListener(
"opacity",
this.opacityMonitor
);
this.disableShade.cmenu();
},
},
antiRedirect() {
//only those links can be capture, which has the attribute of classname with ' external'
const links = Object.getOwnPropertyDescriptors(
HTMLAnchorElement.prototype
).href;
Object.defineProperty(HTMLAnchorElement.prototype, "href", {
...links,
get() {
let href = decodeURIComponent(links.get.call(this));
href = href.split("link.zhihu.com/?target=");
if (href.length > 1) {
this.href = href[1];
return href[1];
}
return href[0];
},
});
},
//if has logined or the login window is not loaded(or be blocked) when the page is loaded;
hasLogin: 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(() => {
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 }));
},
//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
*/
checked: null,
//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 > 2) 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);
},
//checks if the part of answer is expanded
checkExpand(item) {
return (
item.getElementsByClassName(
"RichContent is-collapsed RichContent--unescapable"
).length > 0
);
},
/*
0, normal
1, searchpage => check username
2, click => check all content
3, article page => check expand
if the item has been checked, return
*/
contentCheck(item, targetElements, mode) {
let id = "";
if (mode === 2) {
id = this.getTargetID(item);
if (id && this.checked.includes(id)) return false;
}
const content = item.getElementsByClassName(
targetElements.contentID
);
if (content.length === 0) {
console.log("get content fail");
return false;
}
const text = content[0].innerText;
if (mode === 1) {
const name = text.startsWith("匿名用户:")
? ""
: text.slice(0, text.indexOf(":"));
if (name && blackName.includes(name)) {
console.log(
`%cuser of ${name} has been blocked`,
"color: red;"
);
item.style.display = "none";
return true;
}
}
const result = blackKey.some((e) => text.includes(e));
if (result) {
console.log("%citem has been blocked", "color: red;");
item.style.display = "none";
} else if (
mode === 2 ||
(targetElements.index < 2 && !this.checkExpand(item))
) {
(id || (id = this.getTargetID(item))) &&
this.checked.push(id);
}
return result;
},
userCheck(item, targetElements) {
const user = item.getElementsByClassName(targetElements.userID);
if (user.length === 0) {
console.log("get user fail, anonymous user");
return false;
}
let i = user.length - 1;
i = i > 1 ? 1 : i;
/*
const pathname = user[i].pathname;
if (!pathname) return false;
const id = pathname.slice(pathname.lastIndexOf("/") + 1);
let result = blackID.includes(id);
if (result) {
console.log(
`%cuser of ${id} has been blocked`,
"color: red;"
);
item.style.display = "none";
return result;
}
*/
const name = user[i].innerText;
const result = blackName.includes(name);
if (result) {
console.log(
`%cuser of ${name} has been blocked`,
"color: red;"
);
item.style.display = "none";
}
return result;
},
//block question
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 = `
<button
title=${escapeBlank(title)}
style="
font-size: 14px;
height: 22px;
width: 60px;
margin-left: 10px;
box-shadow: 1px 1px 4px #888888;
"
>
${name}
</button>`;
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)
);
},
check(item, targetElements, mode) {
let result = false;
if (targetElements.index === 3) {
//zhihu hot news(the whole) => don't treat
const h = item.getElementsByClassName("MinorHotSpot");
if (h.length > 0) return;
result = this.contentCheck(item, targetElements, 1);
} else {
result = this.userCheck(item, targetElements);
result =
!result &&
this.contentCheck(item, targetElements, mode);
}
if (!result && this.dbInitial) {
if (targetElements.index < 2) {
this.foldAnswer.check(
item.className !== "ContentItem AnswerItem"
? item.getElementsByClassName(
"ContentItem AnswerItem"
)[0]
: item
);
} else {
this.foldAnswer.Three.main(item);
}
}
},
URL_hasChange: false,
checkURL(targetElements, mode = false) {
const href = location.href;
if (mode && this.currentHREF !== href) {
this.URL_hasChange = true;
this.currentHREF = href;
return false;
}
return targetElements.index < 2
? true
: targetElements.zone.some((e) => href.includes(e));
},
clickCheck(item, targetElements) {
// user without userid when in the search page, if the answer is not expanded
if (targetElements.index === 3) {
const result = this.userCheck(item, targetElements);
if (result) return;
}
setTimeout(
() => this.contentCheck(item, targetElements, 2),
300
);
},
//check the content when the content expanded
colorIndicator: {
lasttarget: null,
index: 0,
change: false,
color(target) {
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;
}
},
},
clickMonitor(node, targetElements) {
const tags = ["blockquote", "p", "br", "li"];
node.onclick = (e) => {
const target = e.target;
const localName = target.localName;
if (tags.includes(localName)) {
if (target.style.color || this.foldAnswer.editableMode)
return;
this.colorIndicator.color(target);
return;
}
this.colorIndicator.restore();
const className = target.className;
if (
className &&
typeof className === "string" &&
className.startsWith("fold") &&
this.dbInitial
) {
if (targetElements.index < 2) {
const ends = [
"block",
"temp",
"element",
"select",
"edit",
"reader",
];
if (ends.some((e) => className.endsWith(e)))
this.foldAnswer.buttonclick(target);
} else {
const ends = [
"question",
"topic",
"element",
"temp",
"answer",
"article",
];
if (ends.some((e) => className.endsWith(e)))
this.foldAnswer.Three.btnClick(target);
}
return;
}
let item = null;
//click the expand button, the rich node has contained all content in q & a webpage;
if (targetElements.index < 2) return;
if (className === targetElements.buttonClass) {
item = this.getiTem(target, targetElements);
//click the ico of expand button
} else if (localName === "svg") {
const button = this.svgCheck(target, targetElements);
button && (item = this.getiTem(button, targetElements));
//click the answer, the content will be automatically expanded
} else {
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 {
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) {
if (info.mode === "block") {
t.style.display !== "none" && (t.style.display = "none");
} else {
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) {
debugger;
dataBaseInstance.dele(false, newValue[0]);
GM_setValue("removearticleA", "");
}
}
);
},
monitor(targetElements) {
let node = document.getElementById(targetElements.mainID);
if (!node) {
node = document.getElementsByClassName(
targetElements.backupClass
);
if (node.length === 0) {
console.log("%cget main id fail", "color: red;");
return;
} else {
node = node[0];
}
}
const mo = new MutationObserver((e) => {
if (
this.URL_hasChange ||
!this.checkURL(targetElements, targetElements.index > 1)
) {
return;
}
e.forEach((item) => {
if (item.addedNodes.length > 0) {
const additem = item.addedNodes[0];
if (additem.className === targetElements.itemClass)
this.check(additem, targetElements, 0);
}
});
});
mo.observe(node, { childList: true, subtree: true });
this.clickMonitor(node, targetElements);
this.connectColumn();
},
getTagetElements(index) {
const pos = {
0: "answerPage",
1: "questionPage",
2: "topicPage",
3: "searchPage",
};
this.checked = [];
const targetElements = this[pos[index]](index);
return targetElements;
},
dbInitial: false,
URL_change: false,
currentHREF: null,
main(index) {
this.currentHREF = location.href;
this.foldAnswer.initial().then((r) => {
this.checked = [];
this.dbInitial = r;
const targetElements = this.getTagetElements(index);
index !== 0 && this.firstRun(targetElements);
index !== 3 && this.Topic_questionButton(targetElements);
if (index > 1) {
unsafeWindow.addEventListener("urlchange", () =>
this.URL_change
? (this.URL_change = false)
: setTimeout(() => {
document.getElementsByClassName(
"fold_element"
).length === 0 &&
this.firstRun(targetElements, false);
}, 300)
);
//monitor forward or backward, this operation will not fire dom event
window.onpopstate = () => (
(this.URL_change = true),
this.firstRun(targetElements, false)
);
}
});
},
firstRun(targetElements, mode = true) {
if (!this.checkURL(targetElements)) {
mode && this.monitor(targetElements);
return;
}
let ic = 0;
let id = setInterval(() => {
const items = document.getElementsByClassName(
targetElements.itemClass
);
if (items.length > 4 || ic > 25) {
clearInterval(id);
for (const item of items)
this.check(item, targetElements, 0);
mode && this.monitor(targetElements);
this.URL_hasChange = false;
}
ic++;
}, 20);
},
answerPage() {
const targetElements = this.questionPage(1);
const items = document.getElementsByClassName(
targetElements.header
);
for (const item of items)
this.check(item.parentNode, targetElements, 0);
const node = document.getElementsByClassName(
targetElements.backupClass
);
this.clickMonitor(node[0], targetElements);
const all = document.getElementsByClassName(
"QuestionMainAction ViewAll-QuestionMainAction"
);
for (const button of all)
button.onclick = () =>
setTimeout(() => this.firstRun(targetElements), 300);
return targetElements;
},
questionPage(index) {
const targetElements = {
button:
"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 TopicMetaCard-actions",
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";
},
getInfo(item) {
const title = item.getElementsByTagName("h2");
if (title.length === 0) return null;
const a = title[0].getElementsByTagName("a");
if (a.length === 0) return null;
const p = a[0].pathname;
const info = {};
info.cblock = false;
info.ablcok = false;
info.qblock = false;
info.tblock = false;
const flags = ["/topic", "/p/", "/question"];
const index = flags.findIndex((e) => p.includes(e));
if (index === 0) {
info.type = "topic";
info.tid = p.slice(p.lastIndexOf("/") + 1);
} else if (index === 1) {
info.type = "column";
info.cid = p.slice(p.lastIndexOf("/") + 1);
} else {
info.type = "answer";
const tmp = p.split("/");
if (tmp.length !== 5) return null;
info.qid = tmp[2];
info.aid = tmp[4];
}
return info;
},
btnRaw(c, t, n, b) {
return `<button class="fold_${c}" title=${escapeBlank(
t
)} bid=${b}>${n}</button>`;
},
foldRaw(arr, info, adisplay, bdisplay, name) {
const d = JSON.stringify(info);
return `
<div
class="fold_element"
title="the ${info.type} has been folded"
data-info=${d}
style="display:${adisplay};"
>
show ${name}
</div>
<div class="hidden_fold" data-info=${d} style="display:${bdisplay};">
${arr.join("")}
<button class="fold_temp" title="temporarily fold the item">Fold</button>
</div>`;
},
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) {
const info = this.getInfo(item);
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 attrs = item.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 = `
<div
class="fold_element"
title="the answer has been folded"
>
show ${name}
</div>`;
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 = `
<button class="fold_temp" title="temporarily fold the answer">Fold</button>
<button class="fold_edit" title="edit the answer">Edit</button>
<button class="fold_select" title="select the answer">Select</button>
<button class="fold_reader" title="open the answer in reader">Reader</button>`;
const html = `
<div class="hidden_fold">
<button class="fold_block" title="fold the answer forever">Block</button>
${obutton}
</div>`;
const r = `
<div class="hidden_fold">
<button class="fold_block" title="remove the answer from block list">Remove</button>
${obutton}
</div>`;
item.firstElementChild.lastElementChild.insertAdjacentHTML(
"beforebegin",
mode ? r : html
);
}
},
},
},
addStyle(index) {
const common = `
.ModalExp-content{display: none !important;}
span.RichText.ztext.CopyrightRichText-richText{text-align: justify !important;}
body{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;
}
.RichText-MCNLinkCardContainer,
div.Question-sideColumn,.Kanshan-container{display: none !important;}
figure{max-width: 70% !important;}
.RichContent-inner{
line-height: 30px !important;
margin: 40px 60px !important;
padding: 40px 50px !important;
border: 6px dashed rgba(133,144,166,0.2) !important;
border-radius: 6px !important;
}
a[href*="u.jd.com"],
.Pc-word,
span.LinkCard-content.LinkCard-ecommerceLoadingCard,
.RichText-MCNLinkCardContainer{display: none !important;}
.Comments{padding: 12px !important; margin: 60px !important;}`;
const inpustyle = `
input::-webkit-input-placeholder {
font-size: 0px !important;
text-align: right;
}`;
const hotsearch =
".Card.TopSearch{display: none !important;}.List-item{position: inherit !important;}";
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;
}`;
GM_addStyle(
common +
(index < 2
? contentstyle + inpustyle
: index === 3
? inpustyle + hotsearch + topicAndquestion
: index === 2
? inpustyle + topicAndquestion
: 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
*/
inputBox: {
box: null,
controlEventListener() {
const windowEventListener = window.addEventListener;
const eventTargetEventListener =
EventTarget.prototype.addEventListener;
function addEventListener(type, listener, useCapture) {
//take care
const NewEventListener =
this instanceof Window
? windowEventListener
: eventTargetEventListener;
//block original keyboard event to prevent blank search(ads)
if (
type.startsWith("key") &&
!listener.toString().includes("(fuckzhihu)")
)
return;
Reflect.apply(NewEventListener, this, [
type,
listener,
useCapture,
]);
//this => who lauch this function, eg, window, document, htmlelement...
}
window.addEventListener = EventTarget.prototype.addEventListener = addEventListener;
},
monitor() {
this.box = document.getElementsByTagName("input")[0];
this.box.placeholder = "";
unsafeWindow.addEventListener(
"popstate",
(e) => {
e.preventDefault();
e.stopPropagation();
},
true
);
unsafeWindow.addEventListener(
"visibilitychange",
(e) => {
e.preventDefault();
this.box.placeholder = "";
e.stopPropagation();
},
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 {
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,
.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;}
a[href*="u.jd.com"],
.RichText-MCNLinkCardContainer,
span.LinkCard-content.LinkCard-ecommerceLoadingCard,
.RichText-MCNLinkCardContainer{display: none !important}`;
const list = `.Card:nth-of-type(3),.Card:last-child,.css-8txec3{width: 900px !important;}`;
if (mode) {
const r = GM_getValue("reader");
if (r) {
GM_addStyle(article + this.Column.clearPage(0).join(""));
this.Column.readerMode = true;
} else {
GM_addStyle(article);
}
if (document.title.startsWith("该内容暂无法显示")) {
window.onload = () => this.ErrorAutoClose();
return;
}
this.anti_setInterval();
this.Column.isZhuanlan = true;
window.onload = () => {
this.colorAssistant.main();
this.autoScroll.keyBoardEvent();
this.Column.main(0);
};
} else {
GM_addStyle(list);
this.Column.main(2);
}
},
Column: {
isZhuanlan: 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 = `
<div
class="AuthorInfo"
itemprop="author"
itemscope=""
itemtype="http://schema.org/Person"
>
<meta itemprop="name" content=${author.name} /><meta
itemprop="image"
content=${author.avatar_url}
/><meta
itemprop="url"
content=https://www.zhihu.com/people/${author.url_token}
/><meta itemprop="zhihu:followerCount" content="3287" /><span
class="UserLink AuthorInfo-avatarWrapper"
><div class="Popover">
<div
id="Popover8-toggle"
aria-haspopup="true"
aria-expanded="false"
aria-owns="Popover8-content"
>
<a
class="UserLink-link"
data-za-detail-view-element_name="User"
target="_blank"
href="//www.zhihu.com/people/${author.url_token}"
><img
class="Avatar Avatar--round AuthorInfo-avatar"
width="38"
height="38"
src=${author.avatar_url}
srcset=${author.avatar_url}
alt=${author.name}
/></a>
</div></div
></span>
<div class="AuthorInfo-content">
<div class="AuthorInfo-head">
<span class="UserLink AuthorInfo-name"
><div class="Popover">
<div
id="Popover9-toggle"
aria-haspopup="true"
aria-expanded="false"
aria-owns="Popover9-content"
>
<a
class="UserLink-link"
data-za-detail-view-element_name="User"
target="_blank"
href="//www.zhihu.com/people/${author.url_token}"
>${author.name}</a
>
</div>
</div></span
>
</div>
<div class="AuthorInfo-detail">
<div class="AuthorInfo-badge">
<div class="ztext AuthorInfo-badgeText">
${author.headline}
</div>
</div>
</div>
</div>
</div>`;
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 = `
<div class="assistant-button" style="margin-left: 15px">
<style type="text/css">
.assistant-button button {
box-shadow: 1px 1px 2px #848484;
height: 24px;
border: 1px solid #ccc !important;
border-radius: 8px;
}
</style>
<button class="follow" style="width: 80px; margin-right: 5px;color: #2196F3;" title=${ft}>
${fn}
</button>
<button class="subscribe" style="width: 90px" title=${st}>${sn}</button>
</div>`;
//const bhtml = `<button class="block" style="width: 70px" title="block the column">block</button>`;
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;
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 f = tablist.some(
(e) =>
e.columnID === columnID &&
uuid === e.id
);
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));
});
}
},
titleAlign() {
if (this.modePrint && !this.titleChange) return;
const title = document.getElementsByClassName("Post-Title");
if (title.length === 0) return;
if (this.modePrint) {
title[0].removeAttribute("style");
} else {
if (title[0].innerText.length > 28) return;
title[0].style.textAlign = "center";
}
this.titleChange = !this.titleChange;
},
modePrint: false,
pagePrint() {
Notification(
`${this.modePrint ? "exit" : "enter"} print mode`,
"Print",
3500
);
!this.readerMode && this.clearPage(this.modePrint ? 1 : 2);
this.tocMenu.main(this.modePrint);
this.titleAlign();
!this.modePrint && window.print();
this.modePrint = !this.modePrint;
},
Framework() {
const html = `
<div
id="column_lists"
style="
top: 54px;
width: 380px;
font-size: 14px;
box-sizing: border-box;
padding: 0 10px 10px 0;
box-shadow: 0 1px 3px #ddd;
border-radius: 4px;
transition: width 0.2s ease;
color: #333;
background: #fefefe;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
display: flex;
z-index: 1000;
position: absolute;
left: 2%;
"
>
<style type="text/css">
button.button {
margin: 15px 0px 5px 0px;
width: 60px;
height: 24px;
border-radius: 3px;
box-shadow: 1px 2px 5px #888888;
}
div#column_lists .list.num {
color: #fff;
width: 18px;
height: 18px;
text-align: center;
line-height: 18px;
background: #fff;
border-radius: 2px;
display: inline-block;
background: #00a1d6;
}
div#column_lists ul a:hover{
color: blue;
}
div#column_lists ul {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
line-height: 1.9;
}
div#column_lists .header{
font-weight: bold;
font-size: 16px;
}
</style>
<span
class="right_column"
style="margin-left: 5%; margin-top: 5px; width: 100%"
>
<span class="header current column">
<a
class="column name"
href= https://www.zhihu.com/column/${
this.columnID
}
target="_blank"
title=${
this.isZhuanlan
? ""
: escapeBlank("random column")
}
>${this.columnName}</a
>
<span class="tips" style="
float: right;
font-size: 14px;
font-weight: normal;
"></span>
<hr style="width: 340px" />
</span>
<ul
class="article_lists"
>
</ul>
<div class="nav button">
<button class="button last" title="previous page">Pre</button>
<button class="button next" title="next page">Next</button>
<button class="button hide" title="hide the menu">Hide</button>
<button class="button more" title="show more content">More</button>
<select class="select-pages" size="1" name="pageslist" style="margin-left: 20px; margin-top: 16px; height: 24px; position: absolute; width: 60px;box-shadow: 1px 2px 5px #888888;">
<option value="0" selected>pages</option>
</select>
</div>
</span>
</div>`;
document.body.insertAdjacentHTML("beforeend", html);
},
timeStampconvertor(timestamp) {
if (!timestamp) return "undefined";
if (typeof timestamp === "number") {
const s = timestamp.toString();
if (s.length === 10) timestamp *= 1000;
else if (s.length !== 13) return "undefined";
} else {
if (timestamp.length === 10) {
timestamp = parseInt(timestamp);
timestamp *= 1000;
} else if (timestamp.length === 13) {
timestamp = parseInt(timestamp);
} else {
return "";
}
}
const date = new Date(timestamp);
const y = date.getFullYear() + "-";
const m =
(date.getMonth() + 1 < 10
? "0" + (date.getMonth() + 1)
: date.getMonth() + 1) + "-";
let d = date.getDate();
d = d < 10 ? "0" + d : d;
return y + m + d;
},
backupInfo: null,
next: null,
previous: null,
index: 1,
rqReady: false,
requestData(url) {
if (this.rqReady) {
Notification("please request data slowly...", "Tips");
return;
}
this.rqReady = true;
xmlHTTPRequest(url).then(
(json) => {
typeof json === "string" && (json = JSON.parse(json));
const data = json.data;
let id = this.isReverse ? data.length : 1;
const html = [];
this.backupInfo = [];
const tips =
"click me, show the content in current webpage";
const HREF = location.href;
for (const e of data) {
const type = e.type;
if (type !== "article" && type !== "answer")
continue;
const info = {};
info.id = id;
let time = e.updated;
let title = e.title;
let className = '"list_date"';
let question = "";
info.url = e.url;
const tmp = {};
let fontWeight = "";
if (!time) {
time = e.updated_time;
className = "list_date_question";
title = e.question.title;
question =
"this is a question page, do not show in current page";
info.url = `https://www.zhihu.com/question/${e.question.id}/answer/${e.id}`;
} else {
HREF === info.url &&
((fontWeight =
' style="font-weight:bold;"'),
(this.targetIndex = id));
tmp.author = e.author;
}
info.excerpt = escapeHTML(
`${title} <摘要>: ` + e.excerpt
);
info.updated = this.timeStampconvertor(time);
title = titleSlice(title);
title = escapeHTML(title);
info.title = title;
html.push(
this.liTagRaw(
info,
question || tips,
className,
fontWeight
)
);
if (!question) {
tmp.content = e.content;
tmp.title = e.title;
}
this.backupInfo.push(tmp);
this.isReverse ? id-- : id++;
}
if (this.isReverse) {
this.backupInfo.reverse();
html.reverse();
}
const pag = json.paging;
const totals = pag.totals;
this.appendNode(html, totals);
this.previous = this.isReverse
? pag.is_end
? ""
: pag.next
: pag.is_start
? ""
: pag.previous;
this.next = this.isReverse
? pag.is_start
? ""
: pag.previous
: pag.is_end
? ""
: pag.next;
this.rqReady = false;
},
(err) => {
console.log(err);
this.rqReady = false;
}
);
},
firstAdd: true,
selectRaw(index, name) {
return `<option value=${index}>${name}</option>`;
},
total_pages: 0,
isReverse: false,
//pages list
appendSelect(node, pages, mode = false) {
const select = node.getElementsByClassName("select-pages");
if (select.length === 0) return;
this.total_pages = Math.ceil(pages / 10);
const end = this.total_pages > 30 ? 31 : this.total_pages + 1;
const html = [];
for (let index = 1; index < end; index++)
html.push(this.selectRaw(index, index));
if (this.total_pages > 1)
html.push(this.selectRaw(html.length + 1, "reverse"));
select[0].insertAdjacentHTML("beforeend", html.join(""));
const optionNum = select[0].length + 1;
const [ds, dh] =
optionNum < 9
? [optionNum, 15 * optionNum + "px"]
: [8, "120px"];
select[0].onmousedown = function () {
this.size = ds;
this.style.height = dh;
};
select[0].onblur = function () {
this.style.height = "24px";
this.size = 1;
};
//execute
const exe = (opt) => {
if (this.total_pages === 1) return;
const i = opt.value * 1;
const tmp = i === this.total_pages + 1;
if (tmp) {
if (this.isReverse) return;
this.isReverse = tmp;
this.index = 1;
opt.value = 1;
} else {
if (i === 0) {
if (this.isReverse) {
this.isReverse = false;
this.index = 1;
opt.value = 1;
} else {
return;
}
} else this.index = i;
}
if (mode) {
Reflect.apply(this.homePage.add, this, [
this.homePage.initial,
"f",
true,
]);
} else {
const m = this.isReverse
? this.total_pages - this.index
: this.index - 1;
const URL = `http://www.zhihu.com/api/v4/columns/${
this.columnID
}/items?limit=10&offset=${10 * m}`;
this.requestData(URL);
}
};
select[0].onchange = function () {
this.size = 1;
this.style.height = "24px";
exe(this);
};
},
changeSelect(mode) {
this.index += mode ? 1 : -1;
const column = document.getElementById("column_lists");
const select = column.getElementsByTagName("select")[0];
select.value = this.index;
},
appendNode(html, totals, mode = false) {
const column = document.getElementById("column_lists");
if (!column) {
console.log("the module of column has been deleted");
return;
}
const lists = column.getElementsByClassName("article_lists")[0];
this.firstAdd
? (lists.insertAdjacentHTML("afterbegin", html.join("")),
this.appendSelect(column, totals, mode),
this.clickEvent(column, mode))
: (lists.innerHTML = html.join(""));
this.firstAdd = false;
},
liTagRaw(info, title, className = "list_date", fontWeight = "") {
const html = `
<li${fontWeight}>
<span class="list num">${info.id}</span>
<a
href=${info.url}
target="_blank"
title=${info.excerpt}>${info.title}</a
>
<span class=${className} style="float: right" title=${title}>${info.updated}</span>
</li>`;
return html;
},
nextPage: false,
tipsTimeout: null,
showTips(tips) {
const column = document.getElementById("column_lists");
if (!column) return;
const tipNode = column.getElementsByClassName("tips")[0];
tipNode.innerText = tips;
this.tipsTimeout && clearTimeout(this.tipsTimeout);
this.tipsTimeout = setTimeout(() => {
tipNode.innerText = "";
this.tipsTimeout = null;
}, 2000);
},
/*
need add new function => simple mode and content mode, if it is simple mode, all page direct show the menu of column?
*/
followCursor: null,
homePage: {
follow: null,
get ColumnID() {
const i = Math.floor(Math.random() * this.follow.length);
return this.follow[i].columnID;
},
get initial() {
this.follow = GM_getValue("follow");
if (
!this.follow ||
!Array.isArray(this.follow) ||
this.follow.length === 0
)
return false;
return this.follow;
},
add(follow, direction, page) {
if (!follow) return;
setTimeout(() => {
const html = [];
const k = follow.length;
if (this.isReverse) {
this.followCursor = (this.index - 1) * 10 - 1;
} else {
if (page) {
this.followCursor = k - (this.index - 1) * 10;
} else {
if (this.followCursor === null) {
this.followCursor = k;
} else {
if (direction === "r")
this.followCursor += 20;
if (this.followCursor > k)
this.followCursor = k;
}
}
}
const className = "list_date_follow";
const title = "follow date";
let id = 1;
const prefix = "https://www.zhihu.com/column/";
const methods = {
r() {
this.followCursor += 1;
return this.followCursor < k;
},
f() {
this.followCursor -= 1;
return this.followCursor > -1;
},
};
const func = this.isReverse ? "r" : "f";
while (methods[func].call(this)) {
const e = follow[this.followCursor];
const info = {};
info.title = e.columnName;
info.updated = this.timeStampconvertor(e.update);
info.excerpt = e.tags.join("; ");
info.url = prefix + e.columnID;
info.id = id;
html.push(this.liTagRaw(info, title, className));
id++;
if (id > 10) break;
}
this.appendNode(html, k, true);
this.previous = this.isReverse
? this.index - 1
: !(this.followCursor + id - 1 === k);
this.next = this.isReverse
? this.index === Math.ceil(k / 10)
? 0
: 1
: this.followCursor > -1;
this.homePage.follow = null;
}, 0);
},
},
targetIndex: 0,
clickEvent(node, mode = false) {
let buttons = node.getElementsByClassName("nav button")[0]
.children;
let article = node.getElementsByTagName("ul")[0];
let aid = 0;
//prevent click too fast
let isReady = false;
//show content in current page;
article.onclick = (e) => {
//if under autoscroll mode, => not allow to click
if (
Reflect.get(zhihu.autoScroll, "scrollState") ||
Reflect.get(zhihu.noteHightlight, "editable")
)
return;
if (isReady) {
Notification("please operate slowly...", "Tips");
return;
}
const href = e.target.previousElementSibling.href;
if (location.href === href) return;
const className = e.target.className;
if (className === "list_date_follow") {
sessionStorage.clear();
window.open(href, "_self");
} else if (className !== "list_date") return;
const content = document.getElementsByClassName(
"RichText ztext Post-RichText"
);
if (content.length === 0) return;
const p = e.path;
let ic = 0;
for (const e of p) {
if (e.localName === "li") {
let id = e.children[0].innerText;
id *= 1;
if (id === aid) return;
aid = id;
break;
}
if (ic > 2) return;
ic++;
}
isReady = true;
const i = aid - 1;
const title = document.getElementsByClassName("Post-Title");
title.length > 0 &&
(title[0].innerText = this.backupInfo[i].title);
content[0].innerHTML = this.backupInfo[i].content;
zhihu.colorAssistant.main();
window.history.replaceState(null, null, href);
document.title = `${this.backupInfo[i].title} - 知乎`;
//refresh the menu
const toc = document.getElementById("toc-bar");
if (toc) {
const refresh = toc.getElementsByClassName(
"toc-bar__refresh toc-bar__icon-btn"
)[0];
refresh.click();
}
const author = this.backupInfo[i].author;
if (author && this.authorID !== author.url_token)
this.updateAuthor(author);
let pnode = e.target.parentNode;
let j = 0;
while (pnode.localName !== "li") {
pnode = pnode.parentNode;
j++;
if (j > 2) break;
}
j < 3 && (pnode.style.fontWeight = "bold");
if (this.targetIndex > 0) {
pnode.parentNode.children[
this.targetIndex - 1
].style.fontWeight = "normal";
}
this.targetIndex = aid;
this.reInject();
isReady = false;
const links = content[0].getElementsByTagName("a");
for (const link of links) {
const href = decodeURIComponent(link.href).split(
"link.zhihu.com/?target="
);
if (href.length > 1) link.href = href[1];
}
};
article = null;
//last page
let isCollapsed = false;
let change_time_id = null;
buttons[0].onclick = () => {
change_time_id && clearTimeout(change_time_id);
change_time_id = setTimeout(() => {
change_time_id = null;
!isCollapsed &&
(this.previous
? (mode
? Reflect.apply(this.homePage.add, this, [
this.homePage.initial,
"r",
false,
])
: this.requestData(this.previous),
(aid = 0),
this.changeSelect(false))
: this.showTips("no more content"));
}, 300);
};
//next page
let change_time_id_a;
buttons[1].onclick = () => {
change_time_id_a && clearTimeout(change_time_id_a);
change_time_id_a = setTimeout(() => {
change_time_id_a = null;
!isCollapsed &&
(this.next
? (mode
? Reflect.apply(this.homePage.add, this, [
this.homePage.initial,
"f",
false,
])
: this.requestData(this.next),
(aid = 0),
this.changeSelect(true))
: this.showTips("no more content"));
}, 300);
};
//hide the sidebar
buttons[2].onclick = function () {
const [style, text, title] = isCollapsed
? ["block", "Hide", "hide the menu"]
: ["none", "Expand", "show the menu"];
this.parentNode.parentNode.children[1].style.display = style;
const more = this.parentNode.nextElementSibling;
if (more) {
more.style.display = style;
more.nextElementSibling.style.display = style;
}
this.innerText = text;
this.title = title;
isCollapsed = !isCollapsed;
};
let addnew = true;
const createModule = (button) => {
const sub = GM_getValue("subscribe");
if (addnew) {
this.columnsModule.liTagRaw = this.liTagRaw;
this.columnsModule.isZhuanlan = this.isZhuanlan;
this.columnsModule.timeStampconvertor = this.timeStampconvertor;
}
let html = null;
let text = "";
if (sub && Array.isArray(sub)) {
let id = 1;
const prefix = "https://www.zhihu.com/column/";
const title = "subscribe time";
html = sub.map((e) => {
const info = {};
info.id = id;
info.url = prefix + e.columnID;
info.updated = this.timeStampconvertor(e.update);
info.title = e.columnName;
info.excerpt = "";
id++;
return this.liTagRaw(info, title);
});
text = "Subscribe";
} else text = "no more data";
addnew
? this.columnsModule.main(
button,
html,
text,
escapeBlank(
this.isZhuanlan
? "column/collection search"
: "column search"
)
)
: this.columnsModule.appendNewNode(html, text);
addnew = false;
};
buttons[3].onclick = function () {
!isCollapsed && createModule(this);
};
//if webpage is column home, expand this menu
mode && createModule(buttons[3]);
buttons = null;
},
columnsModule: {
recentModule: {
log(type) {
//history, pocket, recent collect: h, c, p
const href = location.href;
let r = GM_getValue("recent");
if (r && Array.isArray(r)) {
const index = r.findIndex((e) => e.url === href);
if (index > -1 && r[index].type === type) {
if (index === 0) return;
r.splice(index, 1);
}
} else r = [];
const info = {};
let title = document.title;
title = title.slice(0, title.length - 5);
info.title = title;
info.update = Date.now();
info.type = type;
info.url = href;
const i = r.length;
if (i === 0) {
r.push(info);
} else {
if (i === 5) r.pop();
r.unshift(info);
}
GM_setValue("recent", r);
type === "p" &&
Notification(
"add current article to read later successfully",
"Tips"
);
},
remove(type) {
const href = location.href;
const r = GM_getValue("recent");
if (r && Array.isArray(r)) {
const index = r.findIndex((e) => e.url === href);
if (r[index].type === type) {
r.splice(index, 1);
GM_setValue("recent", r);
}
}
},
addnew: true,
read(node, liTagRaw, timeStampconvertor) {
const r = GM_getValue("recent");
if (!r || !Array.isArray(r) || r.length === 0) return;
const html = r.map((e) => {
const info = {};
const type = e.type;
info.updated = timeStampconvertor(e.update);
info.id = type;
info.excerpt = "";
info.url = e.url;
info.title = titleSlice(e.title);
const title =
"recent " +
(type === "h"
? "read history"
: type === "c"
? "collection"
: "pocket");
return liTagRaw(info, title);
});
let ul = node.getElementsByTagName("ul")[0];
const pre = ul.previousElementSibling;
pre.style.display =
html.length === 0 ? "block" : "none";
ul.innerHTML = html.join("");
if (this.addnew) {
ul.onclick = (e) => {
if (e.target.className === "list_date") {
const a =
e.target.previousElementSibling.href;
location.href !== a &&
(sessionStorage.clear(),
window.open(a, "_self"));
}
};
}
ul = null;
},
main(node, liTagRaw, timeStampconvertor) {
const n = node.nextElementSibling;
this.read(n, liTagRaw, timeStampconvertor);
let button = n.getElementsByClassName(
"button refresh"
)[0];
button.onclick = () =>
this.read(n, liTagRaw, timeStampconvertor);
button = null;
},
},
database: null,
node: null,
liTagRaw: null,
addNewModule(text, placeholder) {
const html = `
<div class="more columns">
<hr>
<div class="search module" style="margin-bottom: 10px">
<input
type="text"
placeholder=${placeholder}
style="height: 24px; width: 250px"
/>
<button class="button search" style="margin-left: 11px;">Search</button>
</div>
<hr>
<span class="header columns">${text}</span>
<ul class="columns list">
</ul>
</div>
<div class="recently-activity">
<hr>
<span class="header columns"
>Recent
<button
class="button refresh"
style="margin-top: 5px; margin-left: 210px; font-weight: normal"
title="refresh recent content"
>
refresh
</button>
</span>
<hr>
<span class="header columns">no recent data</span>
<ul></ul>
</div>`;
const column = document.getElementById("column_lists");
if (column)
column.children[1].insertAdjacentHTML(
"beforeend",
html
);
},
appendNewNode(html, text = "", node) {
let pnode = node || this.node;
pnode = pnode.parentNode.nextElementSibling;
const ul = pnode.getElementsByTagName("ul")[0];
ul.innerHTML = html.join("");
text && (pnode.children[3].innerText = text);
},
checkInlcudes(e, key) {
if (e.columnName.includes(key)) return true;
return e.tags.some((t) =>
key.length > t.length
? key.includes(t)
: t.includes(key)
);
},
timeStampconvertor: null,
commandFormat(str) {
const treg = /(?<=\$)[dmhyw][<>=][0-9]+/g;
const areg = /(?<=\$)a=\(.+\)/g;
const preg = /(?<=\$)p=[0-9]{5,}/g;
const t = str.match(treg);
const p = str.match(preg);
if (t && p) return null;
const a = str.match(areg);
if (p && a) return null;
if (!(a || p || t)) return null;
const sigs = ["=", ">", "<"];
const sign = {
0: "equal",
1: "great",
2: "less",
};
const type = {};
if (t) {
let cm = "";
let ecount = 0;
let lcount = 0;
let gcount = 0;
for (const e of t) {
if (cm && cm !== e[0]) return null;
if (!type[e[0]]) type[e[0]] = {};
if (type[e[0]]) {
const sg = e[1];
const index = sigs.findIndex((e) => e === sg);
if (index === 0) {
ecount++;
if (ecount > 1) return null;
} else if (index === 1) {
gcount++;
if (gcount > 1) return null;
} else {
lcount++;
if (lcount > 1) return null;
}
const n = sign[index];
if (type[e[0]][n]) return null;
type[e[0]][n] = e.slice(2);
}
}
if (a) {
if (a.length > 1) return null;
const tmp = a[0];
type[tmp[0]] = tmp
.slice(3, tmp.length - 1)
.split(" ");
}
} else if (p) {
if (p.length > 1) return null;
const tmp = p[0];
type[tmp[0]] = tmp[2];
} else if (a) {
if (a.length > 1) return null;
const tmp = a[0];
type[tmp[0]] = tmp.slice(3, tmp.length - 1).split(" ");
}
return type;
},
//search box query command execute
ExecuteFunc: {
Resultshow(e, i, liTagRaw, timeStampconvertor) {
const info = {};
info.id = i;
info.title = titleSlice(e.title);
info.excerpt = escapeHTML(e.excerpt);
info.updated = timeStampconvertor(e.update);
const title = escapeBlank("collect time");
const prefix = "/p/";
info.url = prefix + e.pid;
return liTagRaw(info, title);
},
less(a, b) {
return a < b;
},
equal(a, b) {
return a === b;
},
great(a, b) {
return a > b;
},
get now() {
return Date.now();
},
get nowDate() {
return new Date();
},
getUpdate(update) {
return new Date(update);
},
w(value, mode, info) {
const now = this.now;
const time = info.update;
const week = (now - time) / (86400000 * 7);
return this[mode](week, value * 1);
},
d(value, mode, info) {
const now = this.now;
const time = info.update;
const day = (now - time) / 86400000;
return this[mode](day, value * 1);
},
m(value, mode, info) {
const endDate = this.nowDate;
const beginDate = this.getUpdate(info.update);
const month =
endDate.getFullYear() * 12 +
endDate.getMonth() -
(beginDate.getFullYear() * 12 +
beginDate.getMonth());
return this[mode](month, value * 1);
},
y(value, mode, info) {
const endDate = this.nowDate;
const beginDate = this.getUpdate(info.update);
const year =
endDate.getFullYear() * 12 +
endDate.getMonth() -
(beginDate.getFullYear() * 12 +
beginDate.getMonth()) /
12;
return this[mode](year, value * 1);
},
h(value, mode, info) {
const now = this.now;
const time = info.update;
const hour = (now - time) / (1000 * 60 * 60);
return this[mode](hour, value * 1);
},
a(value, info) {
const content =
info.title +
" | " +
info.tags.join("") +
info.excerpt;
return value.some((v) => content.includes(v));
},
search(table, fs, liTagRaw, timeStampconvertor) {
return new Promise((resolve, reject) => {
let ic = 0;
const html = [];
const cur = table.openCursor(null, "next");
cur.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
const info = cursor.value;
const result = fs.every((f) => f(info));
if (result) {
ic++;
html.push(
this.Resultshow(
info,
ic,
liTagRaw,
timeStampconvertor
)
);
}
if (ic === 10) {
resolve(html);
} else {
cursor.continue();
}
} else {
resolve(html);
}
};
cur.onerror = (e) => {
console.log(e);
reject("open db cursor fail");
};
});
},
funcs(
type,
liTagRaw,
timeStampconvertor,
node,
appendNewNode
) {
const fs = [];
const keys = Object.keys(type);
for (const f of keys) {
const tmp = type[f];
if (Array.isArray(tmp) || typeof tmp !== "object") {
if (f === "p") {
dataBaseInstance.check().then(
(r) => {
const html = [];
if (r) {
html.push(
this.Resultshow(
r,
1,
liTagRaw,
timeStampconvertor
)
);
}
appendNewNode(
html,
html.length === 0
? "no search result"
: "article search results",
node
);
},
(err) => console.log(err)
);
return;
}
fs.push(this[f].bind(this, tmp));
continue;
}
const tk = Object.keys(tmp);
for (const e of tk)
fs.push(this[f].bind(this, tmp[e], e));
}
return fs;
},
main(
table,
type,
liTagRaw,
timeStampconvertor,
node,
appendNewNode
) {
const fs = this.funcs(
type,
liTagRaw,
timeStampconvertor,
node,
appendNewNode
);
if (fs.length === 0) {
dataBaseInstance.close();
return;
}
this.search(
table,
fs,
liTagRaw,
timeStampconvertor
).then(
(html) => {
appendNewNode(
html,
html.length === 0
? "no search result"
: "article search results",
node
);
dataBaseInstance.close();
},
(err) => {
console.log(err);
dataBaseInstance.close();
}
);
},
},
initialDatabase(type) {
if (!type) return;
dataBaseInstance.initial(["collection"], false).then(
(result) => {
if (result === 0) {
this.appendNewNode([], "no search result");
return;
}
this.ExecuteFunc.main(
dataBaseInstance.Table,
type,
this.liTagRaw,
this.timeStampconvertor,
this.node,
this.appendNewNode
);
},
(err) => {
this.appendNewNode([], "open DB fail");
dataBaseInstance.close();
}
);
},
isZhuanlan: false,
searchDatabase(key) {
if (
key.charAt(0) === "$" &&
this.isZhuanlan &&
key.length > 1
) {
const cm = ["a", "p", "d", "m", "y", "h", "w"];
if (cm.includes(key.charAt(1).toLowerCase())) {
this.initialDatabase(this.commandFormat(key));
return true;
}
}
return false;
},
search(key) {
//search follow columns & collection of article
if (this.searchDatabase(key)) return;
let i = 0;
const html = [];
const prefix = "https://www.zhihu.com/column/";
const title = "follow time";
for (const e of this.database) {
if (this.checkInlcudes(e, key)) {
i++;
const info = {};
info.id = i;
info.title = e.columnName;
info.excerpt = e.tags.join("; ");
info.updated = this.timeStampconvertor(e.update);
info.url = prefix + e.columnID;
html.push(this.liTagRaw(info, title));
if (i === 10) break;
}
}
this.appendNewNode(
html,
html.length === 0
? "no search result"
: "column search results"
);
},
event() {
const p = this.node.parentNode.nextElementSibling;
const input = p.getElementsByTagName("input")[0];
input.onkeydown = (e) => {
if (e.keyCode !== 13) return;
const key = input.value.trim();
key.length > 1 && this.search(key);
};
let button = p.getElementsByClassName("button search")[0];
button.onclick = () => {
const key = input.value.trim();
key.length > 1 && this.search(key);
};
button = null;
let ul = p.getElementsByTagName("ul")[0];
ul.onclick = (e) => {
if (e.target.className === "list_date") {
const a = e.target.previousElementSibling.href;
location.href !== a && window.open(a, "_self");
}
};
ul = null;
this.recentModule.main(
p,
this.liTagRaw,
this.timeStampconvertor
);
},
main(node, html, text, placeholder) {
this.node = node;
this.addNewModule(text, placeholder);
html && this.appendNewNode(html);
this.database = GM_getValue("follow");
if (!this.database || !Array.isArray(this.database))
this.database = [];
this.event();
GM_addValueChangeListener(
"follow",
(name, oldValue, newValue, remote) =>
remote && (this.database = newValue)
);
},
},
assistNewModule: {
preferenceModule(ainfo, binfo) {
const html = `
<div class="article_attitude" style="margin-left: 23px;">
<i
class="like"
style="
display: ${ainfo.ldisplay};
width: 26px;
content: url(data:image/webp;base64,UklGRoQBAABXRUJQVlA4IHgBAACQCACdASogAB8APm0ukUakIqGhKA1QgA2JaQATIBYgOyI1I/+d+2b49NA3z97A/6i/5f7VTVdqWUnQ+1B4fpukJQWsIUl77RwBlh9AYAD+/zKmH7r/7EJor7FchRHELu3xLyzt7tJsA4Kzo9o+jvdvg5F0PPnJ9h0T6ZLB/b/rf/u1ZNAgIjiz+e0teMM3r/7DFRJvl0h7Fp4PhhsogsSnsOevchiVl0v6jl73n1FPJaHvKz9qfJjC7VQCJTZ5/BAH9nF7hT/zjn+4fX8wnMmQGU9umlNySGfj4UGkhjkHpIds5Roy6Pi8eUA5zXCS8zCLf44zw/qXPgWSHfCrt+fmUapijvBoIn63X72ltR/wx3tHDi5gRr/wpBL8ERY3qmeKlMEBs9Km2Kg7BwpkKE4XNXjYQXuHn5bv7F0uxIKi2Ubgl/XO+BoeBlATzvrM24XeHW2/tv3nQ/z/wzbte4iSl5C3+7TrtUmPDs/XPieOK13LuZjgAAAA);
height: 26px;
margin-bottom: 5px;
"
title=${ainfo.ltitle}
></i>
<i
class="dislike"
style="
content: url(data:image/webp;base64,UklGRmYBAABXRUJQVlA4IFoBAACQCQCdASogACAAPm0qkkWkIqGYDAYAQAbEtIAJ5c+RfUB0rJ7j9kpzPfcf1XiiNSX5s/7v5gPbD3Bv1F/0K6nH9glbEGQcDYiAI3S+NfKqNr+EMSyQAP7yUv/+QvpX0Z+oaIucX4be+hsni+QhcLK0f/ugn01LW17/NrkTFet/XNw/o/5+bPpPA3v7Ic8/NjVb4alH/V1LUJBkL7kko/v//XOzDfCaCoDzemP+zPNtC3dB6keXLJVpBMrLJgNzY/VUAKf+1SSCB/5BD/4Nf+lylTxAGWJl0/1twWPbOMbH0d+MQkF0fSsPqqBRpvc6FNf+PClifyl4OpWwfxGKiMm1C7O8/w0vDlPCbbwR4FKhQKuj2Fyul9WITHFC4fpzSNIBC39ZlySnghJRgDgCo/P+f9205ENaW+9XKyYLte/Gddtc43354MKLVHwYv44znVWV8gcjChTAAAAA);
display: ${ainfo.ddisplay};
width: 24px;
height: 24px;
"
title=${ainfo.dtitle}
></i>
</div>
<button class="assist-button collect" style="color: black;" title=${binfo.title}>${binfo.name}</button>`;
return html;
},
create(module) {
const node = document.getElementById(
"assist-button-container"
);
node.children[0].insertAdjacentHTML("afterend", module);
return node;
},
main() {
return new Promise((resolve) => {
const tables = ["collection", "preference"];
//open db with r&w mode, if this db is not exist then create db
dataBaseInstance.initial(tables, true).then(
(result) => {
const ainfo = {};
ainfo.ltitle = "like this article";
ainfo.ldisplay = "block";
ainfo.ddisplay = "block";
ainfo.dtitle = "dislike this article";
const binfo = {};
binfo.title = "add this article to collection";
binfo.name = "Collect";
if (result === 0) {
resolve(
this.create(
this.preferenceModule(
escapeBlank(ainfo),
escapeBlank(binfo)
)
)
);
dataBaseInstance.close(false);
} else {
dataBaseInstance
.batchCheck(tables)
.then((results) => {
const c = results[0];
if (c) {
binfo.title =
"remove this acticle from collection list";
binfo.name = "Remove";
}
if (results[1]) {
const pref = results[1].value;
if (pref === 1) {
ainfo.ltitle =
"cancel like this article";
ainfo.ddisplay = "none";
} else if (pref === 0) {
ainfo.dtitle =
"cancel dislike this article";
ainfo.ldisplay = "none";
}
}
resolve(
this.create(
this.preferenceModule(
escapeBlank(ainfo),
escapeBlank(binfo)
)
)
);
dataBaseInstance.close();
});
}
},
(err) => {
console.log(err);
resolve(null);
}
);
});
},
},
syncData(mode, newValue) {
dataBaseInstance.initial(["preference"], true).then(
() => {
mode
? dataBaseInstance.update(newValue[0])
: dataBaseInstance.dele(false, newValue[0]);
dataBaseInstance.close();
},
(err) => console.log(err)
);
},
communication() {
const monitor = () => {
GM_addValueChangeListener(
"blockarticleB",
(name, oldValue, newValue, remote) => {
if (remote) {
this.syncData(true, newValue);
GM_setValue("blockarticleB", "");
}
}
);
GM_addValueChangeListener(
"removearticleB",
(name, oldValue, newValue, remote) => {
if (remote) {
this.syncData(false, newValue);
GM_setValue("removearticleB", "");
}
}
);
};
const r = GM_getValue("removearticleB");
const b = GM_getValue("blockarticleB");
r || b
? dataBaseInstance.initial(["preference"], true).then(
() => {
if (r && Array.isArray(r) && r.length > 0) {
for (const e of r)
dataBaseInstance.dele(false, e);
GM_setValue("removearticleB", "");
}
if (b && Array.isArray(b) && b.length > 0) {
for (const e of b) dataBaseInstance.update(e);
GM_setValue("blockarticleB", "");
}
monitor();
dataBaseInstance.close();
},
(err) => {
console.log(err);
dataBaseInstance.close();
}
)
: monitor();
},
creatAssistantEvent(node, mode) {
const Button = (button, m) => {
if (m) {
if (!this.readerMode) return;
this.createFrame();
button.style.display = "none";
mode = false;
} else {
let text = "";
let style = "";
const column = document.getElementById("column_lists");
let i = 0;
let title = "";
if (this.readerMode) {
text = "Reader";
style = "none";
title = "enter";
if (column) column.style.display = style;
i = 1;
//show content;
} else {
style = "block";
text = "Exit";
title = "exit";
if (column) {
column.style.display = style;
} else {
//hide content
this.Tabs.check(this.columnID).then(
(result) => !result && this.createFrame()
);
}
i = 2;
}
!this.modePrint && this.clearPage(i);
button.title = `${title} the reader mode`;
button.innerText = text;
this.readerMode = !this.readerMode;
GM_setValue("reader", this.readerMode);
mode &&
(button.previousElementSibling.style.display = style);
}
};
let i = node.children.length;
//reder
node.children[--i].onclick = function () {
Button(this, false);
};
//menu
if (mode) {
node.children[--i].onclick = function () {
Button(this, true);
};
}
//collection
let cReady = false;
const collectionClick = (button) => {
if (cReady) return;
cReady = true;
dataBaseInstance.initial(["collection"], true).then(
(result) => {
const text = button.innerText;
let s = "",
t = "";
if (text === "Remove") {
s = "Collect";
t = "add this article to collection list";
dataBaseInstance.dele(true);
this.columnsModule.recentModule.remove();
} else {
s = "Remove";
t = "remove this article frome collection list";
dataBaseInstance.additem(this.columnID);
this.columnsModule.recentModule.log("c");
}
button.innerText = s;
button.title = t;
dataBaseInstance.close();
cReady = false;
},
(err) => {
cReady = false;
console.log(err);
dataBaseInstance.close();
}
);
};
node.children[--i].onclick = function () {
collectionClick(this);
};
//pref
const cm = (mode) => {
const bid = location.pathname.slice(3);
if (mode) {
let b = GM_getValue("blockarticleA");
if (b && Array.isArray(b)) {
for (const e of b) if (e.name === bid) return;
} else b = [];
const r = GM_getValue("removearticleA");
if (r && Array.isArray(r)) {
const i = r.indexOf(bid);
if (i > -1) {
r.splice(i, 1);
GM_setValue("removearticleA", r);
}
}
const info = {};
info.name = bid;
info.type = "article";
info.from = this.columnID;
info.update = Date.now();
info.userID = this.authorID;
info.userName = this.authorName;
b.push(info);
GM_setValue("blockarticleA", b);
} else {
let r = GM_getValue("removearticleA");
if (r && Array.isArray(r) && r.length > 0) {
if (r.includes(bid)) return;
} else r = [];
const b = GM_getValue("blockarticleA");
if (b && Array.isArray(b)) {
const i = b.findIndex((e) => e.name === bid);
if (i > -1) {
b.splice(i, 1);
GM_setValue("blockarticleA", b);
}
}
r.push(bid);
GM_setValue("removearticleA", r);
}
};
let pReady = false;
const prefclick = (button, other, mode) => {
if (pReady) return;
pReady = true;
dataBaseInstance.initial(["preference"], true).then(
(result) => {
let title = "";
let f = false;
let cl = false;
if (other.style.display === "none") {
dataBaseInstance.dele(false);
title = `${
mode ? "like" : "dislike"
} this article`;
other.style.display = "block";
cl = mode;
} else {
const info = {};
info.pid = dataBaseInstance.pid;
info.update = Date.now();
info.userID = this.authorID;
info.from = this.columnID;
info.value = mode ? 1 : 0;
dataBaseInstance.update(info);
title = `cancel ${
mode ? "like" : "dislike"
} this article`;
other.style.display = "none";
f = !mode;
}
button.title = title;
dataBaseInstance.close();
!cl && cm(f);
pReady = false;
},
(err) => {
pReady = false;
console.log(err);
dataBaseInstance.close();
}
);
};
node.children[--i].onclick = (e) => {
const target = e.target;
const className = target.className;
if (className === "like")
prefclick(target, target.nextElementSibling, true);
else if (className === "dislike")
prefclick(target, target.previousElementSibling, false);
};
},
recordID: null,
dataRecord() {
this.recordID && clearTimeout(this.recordID);
this.recordID = setTimeout(() => {
this.recordID = null;
const pid = dataBaseInstance.pid;
dataBaseInstance.initial(["collection"], true).then(
(result) => {
result === 1 && dataBaseInstance.updateRecord(pid);
dataBaseInstance.close();
},
() => console.log("open database fail")
);
if (this.columnID) {
const f = GM_getValue("follow");
if (f && Array.isArray(f) && f.length > 0) {
for (const e of f) {
if (e.columnID === this.columnID) {
e.visitTime = Date.now();
let times = e.visitTimes;
e.visitTimes = times ? ++times : 2;
GM_setValue("follow", f);
break;
}
}
}
}
}, 15000);
},
reInject() {
let assist = document.getElementById("assist-button-container");
if (assist) {
assist.remove();
assist = null;
}
this.injectButton();
},
injectButton(mode) {
const name = this.readerMode ? "Exit" : "Reader";
const mbutton =
'<button class="assist-button siderbar" style="color: black;" title="show the siderbar">Menu</button>';
createButton(
name,
(this.readerMode ? "exit " : "enter ") + "the reader mode",
mode ? mbutton : "",
"right"
);
this.assistNewModule
.main()
.then(
(node) => node && this.creatAssistantEvent(node, mode)
);
this.dataRecord();
},
readerMode: false,
createFrame() {
if (this.columnID) {
this.Framework();
//const pinURL = `https://www.zhihu.com/api/v4/columns/${this.columnID}/pinned-items`;
const url = `https://www.zhihu.com/api/v4/columns/${this.columnID}/items`;
this.requestData(url);
this.Tabs.save(this.columnID);
}
},
columnID: null,
columnName: null,
main(mode = 0) {
if (mode > 0) {
window.onload = () => {
const f = this.homePage.initial;
if (f) {
this.columnID = this.homePage.ColumnID;
this.columnName = "Follow";
this.Framework();
Reflect.apply(this.homePage.add, this, [
f,
"f",
false,
]);
}
mode === 2 && this.subscribeOrfollow();
};
return false;
}
if (this.ColumnDetail) {
if (this.readerMode)
this.Tabs.check(this.columnID).then((result) =>
result
? this.injectButton(true)
: (this.createFrame(), this.injectButton())
);
else this.injectButton();
} else {
this.injectButton();
}
setTimeout(() => this.communication(), 5000);
},
},
colorAssistant: {
index: 0,
arr: null,
blue: true,
grad: 5,
get rgbRed() {
this.index -= this.grad;
if (this.index < 0 || this.index > 255) {
this.index = 0;
this.blue = true;
this.grad < 0 && (this.grad *= -1);
}
const s =
this.index > 233
? 5
: this.index > 182
? 4
: this.index > 120
? 3
: this.index > 88
? 2
: this.index > 35
? 1
: 0;
return `rgb(${this.index}, ${s}, 0)`;
},
redc: false,
get rgbBlue() {
this.index += this.grad;
if (this.index > 255) {
this.blue = false;
if (this.redc) {
this.index = 0;
this.grad *= -1;
this.redc = false;
} else {
this.grad < 0 && (this.grad *= -1);
this.index = 255;
this.redc = true;
}
}
return `rgb(0, 0, ${this.index})`;
},
get textColor() {
return this.blue ? this.rgbBlue : this.rgbRed;
},
setColor(text, color) {
return `<colorspan class="color-node" style="color: ${color} !important;">${text}</colorspan>`;
},
setcolorGrad(tlength) {
this.grad =
tlength > 500
? 1
: tlength > 300
? 2
: tlength > 180
? 3
: tlength > 120
? 4
: tlength > 80
? 5
: 6;
},
num: 0,
textDetach(text) {
this.setcolorGrad(text.length);
const reg = /((\d+[\.-\/]\d+([\.-\/]\d+)?)|\d{2,}|[a-z]{2,})/gi;
let result = null;
let start = 0;
let end = 0;
let tmp = "";
result = reg.exec(text);
const numaColors = ["green", "#8B008B"];
if (result) {
while (result) {
tmp = result[0];
end = reg.lastIndex - tmp.length;
for (start; start < end; start++)
this.arr.push(
this.setColor(text[start], this.textColor)
);
start = reg.lastIndex;
this.arr.push(this.setColor(tmp, numaColors[this.num]));
this.num = this.num ^ 1;
result = reg.exec(text);
}
end = text.length;
for (start; start < end; start++)
this.arr.push(
this.setColor(text[start], this.textColor)
);
} else {
for (let t of text)
this.arr.push(this.setColor(t, this.textColor));
}
},
nodeCount: 0,
getItem(node) {
//those tags will be ignored
const localName = node.localName;
const tags = ["a", "br", "b", "span", "code", "strong", "u"];
if (localName && tags.includes(localName)) {
this.arr.push(node.outerHTML);
this.nodeCount += 1;
return;
} else {
const className = node.className;
if (className && className === "UserLink") {
this.arr.push(node.outerHTML);
this.nodeCount += 1;
return;
}
}
if (node.childNodes.length === 0) {
const text = node.nodeValue;
text && this.textDetach(text);
} else {
//this is a trick, no traversal of textnode, maybe some nodes will lost content, take care
for (const item of node.childNodes) this.getItem(item);
this.arr.length > 0 &&
node.childNodes.length - this.nodeCount <
this.nodeCount + 2 &&
(node.innerHTML = this.arr.join(""));
this.arr = [];
}
},
resetColor() {
this.blue = !this.blue;
this.index = this.blue ? 0 : 255;
},
codeHightlight(node) {
const keywords = [
"abstract",
"arguments",
"await",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"double",
"else",
"enum",
"eval",
"export",
"extends",
"false",
"final",
"finally",
"float",
"for",
"function",
"goto",
"if",
"implements",
"import",
"in",
"instanceof",
"int",
"interface",
"let",
"long",
"native",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"true",
"try",
"typeof",
"var",
"void",
"volatile",
"while",
"with",
"yield",
];
const code = node.getElementsByClassName("language-text");
if (code.length === 0 || code[0].childNodes.length > 1) return;
let html = code[0].innerHTML;
const reg = /(["'])(.+?)(["'])/g;
const keyReg = /([a-z]+(?=[\s\(]))/g;
const i = html.length;
const h = (match, color) =>
`<hgclass class="hgColor" style="color:${color} !important;">${match}</hgclass>`;
html = html.replace(
reg,
(e) => e[0] + h(e.slice(1, -1), "#FA842B") + e.slice(-1)
);
html = html.replace(keyReg, (e) => {
if (e && keywords.includes(e))
return h(e, "rgb(0, 0, 252)");
return e;
});
const Reg = /\/\//g;
html = html.replace(Reg, (e) => h(e, "green"));
i !== html.length && (code[0].innerHTML = html);
},
rightClickCopyCode: {
checkCodeZone(target) {
if (target.className === "highlight") {
return target;
}
let p = target.parentNode;
let className = p.className;
let i = 0;
while (className !== "highlight") {
p = p.parentNode;
if (!p || i > 2) return null;
className = p.className;
i++;
}
return p;
},
main(node) {
node.oncontextmenu = (e) => {
if (e.button !== 2 || !e.ctrlKey) return;
const code = this.checkCodeZone(e.target);
if (code) {
e.preventDefault();
zhihu.clipboardClear.clear(code.innerText);
Notification(
"this code has been copied to clipboard",
"clipboard"
);
}
};
},
},
main() {
if (GM_getValue("nocolofultext")) return;
let holder = document.getElementsByClassName(
"RichText ztext Post-RichText"
);
if (holder.length === 0) {
console.log("get content fail");
return;
}
this.blue = Math.ceil(Math.random() * 100) % 2 === 0;
!this.blue && (this.index = 255);
holder = holder[0];
const tags = ["p", "ul", "li", "ol", "blockquote"];
const textNode = [];
let i = -1;
let code = false;
const tips = "Ctrl + Right mouse button to copy this code";
for (const node of holder.childNodes) {
i++;
const type = node.nodeType;
//text node deal with separately
if (type === 3) {
textNode.push(i);
continue;
} else if (!tags.includes(node.tagName.toLowerCase())) {
//the continuity of content is interrupted, reset the color;
this.resetColor();
if (node.className === "highlight") {
this.codeHightlight(node);
node.title = tips;
code = true;
}
continue;
}
this.arr = [];
this.nodeCount = 0;
this.getItem(node);
}
i = textNode.length;
if (i > 0) {
for (i; i--; ) {
let node = holder.childNodes[textNode[i]];
const text = node.nodeValue;
if (text) {
this.arr = [];
this.textDetach(text);
const iNode = document.createElement("colorspan");
holder.insertBefore(iNode, node);
iNode.outerHTML = this.arr.join("");
node.remove();
}
}
}
code && this.rightClickCopyCode.main(holder);
this.arr = null;
},
},
userPage: {
username: null,
changeTitle(mode) {
const title = document.getElementsByClassName(
"ProfileHeader-contentHead"
);
if (title.length === 0) return;
title[0].style.textDecoration =
mode === "Block" ? "none" : "line-through";
},
userManage(mode) {
let text = "";
const info = {};
if (mode === "Block") {
info.mode = "block";
blackName.push(this.username);
text = "add user to blackname successfully";
} else {
info.mode = "unblock";
const i = blackName.indexOf(this.username);
i > -1 && blackName.splice(i, 1);
text = "remove user from blackname successfully";
}
info.username = this.username;
GM_setValue("blacknamechange", info);
GM_setValue("blackname", blackName);
Notification(text, "blackName");
},
injectButton(name) {
createButton(name, name + " this user");
let assist = document.getElementById("assist-button-container");
assist.children[1].onclick = (e) => {
const button = e.target;
const n = button.innerText;
this.userManage(n);
button.innerText = n === "Block" ? "unBlock" : "Block";
this.changeTitle(button.innerText);
this.title = button.innerText + " this user";
};
assist = null;
name === "unBlock" && this.changeTitle(name);
},
changeButton(mode) {
const button = document.getElementById(
"assist-button-container"
);
if (!button) return;
const name = button.children[1].innerText;
if ((mode && name === "unBlock") || (!mode && name === "Block"))
return;
button.children[1].innerText = blackName.includes(this.name)
? "unBlock"
: "Block";
},
main() {
const profile = document.getElementsByClassName(
"ProfileHeader-name"
);
if (profile.length === 0) {
console.log("get usename id fail");
return;
}
this.username = `${profile[0].innerText}`;
this.username &&
this.injectButton(
blackName.includes(this.username) ? "unBlock" : "Block"
);
},
},
QASkeyBoardEvent() {
document.onkeydown = (e) => {
if (e.ctrlKey || e.altKey) return;
if (e.target.localName === "input") return;
const className = e.target.className;
if (
className &&
typeof className === "string" &&
className.includes("DraftEditor")
)
return;
const r = this.qaReader.readerMode;
const shift = e.shiftKey;
if (shift && !r) return;
const keyCode = e.keyCode;
r
? this.qaReader.keyEvent(keyCode, shift)
: keyCode === 192
? this.autoScroll.start()
: keyCode === 187
? this.autoScroll.speedUP()
: keyCode === 189
? this.autoScroll.slowDown()
: this.multiSearch(keyCode);
};
},
pageOfQA(index, href) {
//inject as soon as possible; may be need to concern about some eventlisteners and MO
this.inputBox.controlEventListener();
index < 2 && this.anti_setInterval();
this.addStyle(index);
this.clearStorage();
window.onload = () => {
if (index !== 8) {
this.getData();
this.blackUserMonitor(index);
(index < 4
? !(index === 1 && href.endsWith("/waiting"))
: false) &&
setTimeout(() => {
this.Filter.main(index);
this.QASkeyBoardEvent();
}, 100);
(index === 6 || index === 7) && this.userPage.main();
}
this.inputBox.monitor();
};
},
blackUserMonitor(index) {
GM_addValueChangeListener(
"blackname",
(name, oldValue, newValue, remote) => {
if (!remote) return;
//mode => add user to blockname
blackName = newValue;
if (index === 6) {
const mode =
!oldValue || oldValue.length < newValue.length;
this.userPage.changeButton(mode);
} else {
this.Filter.userChange(index);
}
}
);
},
start() {
const pos = [
"/answer/",
"/question/",
"/topic/",
"/search",
"/column",
"/zhuanlan",
"/people/",
"/org/",
"/www",
];
const href = location.href;
const index = pos.findIndex((e) => href.includes(e));
let w = true;
let z = false;
let f = true;
(
(z = index === 5)
? href.endsWith("zhihu.com/")
? (f = this.Column.main(1))
: (w = !href.includes("/write"))
: index === 4
? true
: false
)
? this.zhuanlanStyle(z && href.includes("/p/"))
: index < 0
? null
: f && this.pageOfQA(index, href);
w && this.antiRedirect();
this.antiLogin();
setTimeout(() => (this.hasLogin = true), 3000);
this.shade.start();
this.clipboardClear.event();
installTips();
},
};
zhihu.start();
})();