Skip to content

Instantly share code, notes, and snippets.

@lpimem
Last active March 1, 2025 06:00
Show Gist options
  • Save lpimem/11913a8b856678ac7acee516e06b945f to your computer and use it in GitHub Desktop.
Save lpimem/11913a8b856678ac7acee516e06b945f to your computer and use it in GitHub Desktop.
Piao Tian Xiao Shuo - Reading Mode
// ==UserScript==
// @name Piao Tian Xiao Shuo
// @namespace http://www.piaotian.com/
// @version 0.2
// @match https://www.piaotian.com/*
// @match https://www.piaotia.com/*
// @match http://www.piaotian.com/*
// @match https://www.ptwxz.com/*
// @match http://www.piaotianw.com/*
// @grant none
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @downloadURL https://gist.github.com/lpimem/11913a8b856678ac7acee516e06b945f/raw/ptxs_reading_mode.user.js
// @updateURL https://gist.github.com/lpimem/11913a8b856678ac7acee516e06b945f/raw/ptxs_reading_mode.user.js
// ==/UserScript==
(function() {
'use strict';
function resetColors(){
save(K_COLOR_MAIN, "#edf6d0");
save(K_COLOR_BG, "#443");
save(K_COLOR_TEXT, "#000");
redrawColors();
}
function patchStyles(){
// bg: #443
// #edf6d0 #aeaea0 #cc9
let styles = ""
+ "#main { box-shadow: 0px 0px 4px rgba(100,100,100,0.25); border-radius: 5px; }"
+ "#content { font-family: SimSun; font-size: 18px; line-height: 1.5em; letter-spacing: -0.1em; }"
+ "#main #guild A, #main #feit2 ul li a { background: #443; border-radius: 5px; padding-top: 0; margin-top: 5px; color: #FFF; }"
+ "#main #content H1 { margin-top: 30px; }"
+ ".toplink a { color: #443; }"
+ "#shop { display: none; }"
+ "#content p { text-indent: 2em; }"
;
// + "#content{font-family:'KaiTi'; font-size:24px; line-height:1.7em; }";
console.info("Patching styles:");
console.info(styles);
let el = document.createElement("style");
el.innerText = styles;
document.body.appendChild(el);
}
function removeStyles(element) {
// Remove the style attribute from the element itself
element.removeAttribute("style");
// Remove style attribute from all descendant elements
const descendants = element.querySelectorAll('*');
descendants.forEach(el => el.removeAttribute("style"));
}
function removeEventListeners(element, eventType) {
// Create a clone of the element (deep clone to include children)
const newElement = element.cloneNode(true);
// Remove inline event listener attribute for the specified event, if it exists
newElement.removeAttribute("on" + eventType);
// Replace the original element with the clone in the DOM
if (element.parentNode) {
element.parentNode.replaceChild(newElement, element);
} else {
// If the element has no parent, you might need to handle it differently
console.warn("Element has no parent; cannot replace in DOM.");
}
}
/**
* Cleans HTML content by:
* 1. Removing leading non-breaking spaces ( )
* 2. Converting text between <br> tags into proper <p> elements
* @param {HTMLElement} element - The HTML element to clean
*/
function cleanText(element) {
// Skip processing if the element doesn't exist
if (!element) return;
// Create a temporary div to manipulate the content
const tempDiv = document.createElement('div');
tempDiv.innerHTML = element.innerHTML;
// Remove leading &nbsp; after <br> tags
tempDiv.innerHTML = tempDiv.innerHTML.replace(/(<br\s*\/?>)\s*(&nbsp;)+/gi, '$1');
// Preserve the toolbar if it exists
const toolbar = tempDiv.querySelector('#toolbar');
let toolbarHTML = '';
if (toolbar) {
toolbarHTML = toolbar.outerHTML;
toolbar.parentNode.removeChild(toolbar);
}
// Get all content
let content = tempDiv.innerHTML;
// Split content by <br> tags and convert to paragraphs
// First, standardize all <br> variants to a single form
content = content.replace(/<br\s*\/?>/gi, '<br>');
// Split by <br> tags
const segments = content.split('<br>');
// Convert each segment to a paragraph if it contains text
const paragraphs = segments.map(segment => {
// Trim the segment and check if it's not empty
const trimmed = segment.trim();
if (trimmed && !trimmed.match(/^<(h[1-6]|p|div|ul|ol|li|table)/i)) {
// Skip if it's already a block element
return `<p>${trimmed}</p>`;
}
return trimmed;
}).filter(p => p); // Remove empty paragraphs
// Combine the paragraphs
let newContent = paragraphs.join('');
// Re-insert the toolbar if it existed
if (toolbar && toolbarHTML) {
// Find where the toolbar was (after the h1)
const h1End = newContent.indexOf('</h1>');
if (h1End !== -1) {
newContent = newContent.substring(0, h1End + 5) +
toolbarHTML +
newContent.substring(h1End + 5);
} else {
// If no h1, just add it at the beginning
newContent = toolbarHTML + newContent;
}
}
// Update the element's content
element.innerHTML = newContent;
}
function setColor(main, bg, text) {
$("body").css("background-color", bg);
$("#main")
.css("background-color", main)
.css("color", text);
$(".mainbody .list")
.css("background-color", main)
.css("color", text);
$(".centent ul li")
.css("background-color", main)
.css("color", text);
$("#content")
.css("background-color", main)
.css("color", text);
}
function redrawColors(){
let main = get(K_COLOR_MAIN);
if (!main) {
resetColors();
return;
}
let bg = get(K_COLOR_BG);
let text = get(K_COLOR_TEXT);
console.info(">>> Redraw Colors: " + main +", " + bg + ", " + text);
setColor(main, bg, text);
}
function resetChapterTitle(){
let title = $("h1")[0].childNodes[1].nodeValue;
title = title.replace("正文 ", "");
$("head title")[0].innerText = title;
}
function removeAd(){
try{
let iframes = top.document.getElementsByTagName("iframe");
for(let i=0; i<iframes.length; i++){let ifm=iframes[i]; ifm.parentElement.removeChild(ifm);}
let ad_holder = top.document.getElementById("content").getElementsByTagName("table")[0];
if (ad_holder && ad_holder.parentElement) {
ad_holder.parentElement.removeChild(ad_holder);
}
}catch(e){
console.error("Cannot remove ad: ", e);
}
}
function adjustLayout(){
try{
let c = top.document.getElementById("content");
c.innerHTML = c.innerHTML.toString().replaceAll("<br><br>", "<br>");
}catch(e){
console.error("Cannot adjustLayout: " + e);
}
}
function sendGet(url, onload, onerror) {
let req = new XMLHttpRequest();
req.addEventListener("load", onload);
req.addEventListener("error", onerror);
req.open("GET", url); req.send();
}
function nextPageUrl(doc){
if (!doc){
doc = top.document;
}
return doc.getElementsByClassName("toplink")[0].children[2].href;
}
// return [content, nextPageUrl]
function extractResponse(resp){
let html = resp.target.responseText;
let parser = new DOMParser();
let doc = parser.parseFromString(html, "text/html");
let content = parseRawContent(doc);
let next = nextPageUrl(doc);
return [content.innerHTML, next];
}
function parseRawContent(rawdoc){
let h1 = rawdoc.getElementsByTagName("h1");
}
function appendContent(doc, htmlContent){
if (!doc){
doc = top.document;
}
let newContent = doc.createElement("div");
newContent.innerHTML = htmlContent;
let main = doc.getElementById("main");
main.appendChild(newContent);
}
function updateNextUrl(){
}
function loadNextPage(){
let url = nextPageUrl();
let onLoad = function(resp){
let result = extractResponse(resp);
appendContent(top.document, result[0]);
};
let onError = function(err){
console.error(err);
};
sendGet(url, onLoad, onError);
}
function onColorPickerJSLoaded(){
// console.info("!!! COLOR PICKER LOADED");
listenColorPickerEvts();
redrawColors();
resetChapterTitle();
}
function loadError(oError) {
throw new URIError("The script " + oError.target.src + " didn't load correctly.");
}
function affixScriptToHead(url, onloadFunction) {
var newScript = document.createElement("script");
newScript.onerror = loadError;
if (onloadFunction) { newScript.onload = onloadFunction; }
document.head.appendChild(newScript);
newScript.src = url;
}
function patchColorPicker(){
affixScriptToHead("https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.8.0/spectrum.min.js", onColorPickerJSLoaded);
document.body.innerHTML += '<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.8.0/spectrum.min.css">'
}
const COLOR_PICKER_ID = "cust_cp";
function addCP(id, label, container, onChoose, initValue){
let d = top.document;
let lbl = d.createElement("label");
lbl.innerText = label;
container.appendChild(lbl);
container.appendChild(d.createElement("br"));
let ipt = d.createElement("input");
ipt.id = id;
ipt.type = "text";
container.appendChild(ipt);
container.appendChild(d.createElement("br"));
let cp = $("#" + id).spectrum({
flat: true,
showInput: true,
preferredFormat: "hex",
change: function(color){
onChoose(color);
removeColorPicker();
}
});
if (initValue) {
cp.spectrum("set", initValue);
}
return cp;
}
function buildColorPickers(x, y, onChooseMain, onChooseBg, onChooseText){
let d = top.document;
let e = d.createElement("div");
e.id = COLOR_PICKER_ID;
d.body.append(e);
$(e).css( "background-color", "white" );
const main = addCP("cust_cp_main", "MAIN", e, onChooseMain, get(K_COLOR_MAIN));
const bg = addCP("cust_cp_bg", "BG", e, onChooseBg, get(K_COLOR_BG));
const text = addCP("cust_cp_text", "TEXT", e, onChooseText, get(K_COLOR_TEXT));
let btn = d.createElement("button");
btn.innerText = "Reset";
btn.addEventListener("click", function(e){
resetColors();
resetCPColors(main, bg, text);
removeColorPicker();
e.stopPropagation();
e.preventDefault();
}, true);
e.appendChild(btn);
positionColorPicker(x, y);
}
function resetCPColors(mainCp, bgCp, textCp) {
let main = get(K_COLOR_MAIN);
let bg = get(K_COLOR_BG);
let text = get(K_COLOR_TEXT);
mainCp.spectrum("set", main);
bgCp.spectrum("set", bg);
textCp.spectrum("set", text);
}
function removeColorPicker(){
$("#" + COLOR_PICKER_ID).remove();
}
function existColorPicker(){
let d = top.document;
let e = d.getElementById(COLOR_PICKER_ID);
return e;
}
function positionColorPicker(x, y){
let e = $("#" + COLOR_PICKER_ID);
e.css("position", "absolute");
e.css("top", y);
e.css("left", x);
}
const K_COLOR_MAIN = "key_color_main";
const K_COLOR_BG = "key_color_bg";
const K_COLOR_TEXT = "key_color_text";
function save(key, c) {
let s = top.localStorage;
s.setItem(key, c);
}
function get(key) {
return top.localStorage.getItem(key);
}
function listenColorPickerEvts() {
$("#content").dblclick(function(e){
console.info("building color picker.");
buildColorPickers(
e.pageX,
e.pageY,
function(mainColor) {
save(K_COLOR_MAIN, mainColor);
redrawColors();
},
function(bgColor) {
save(K_COLOR_BG, bgColor);
redrawColors();
},
function(textColor) {
save(K_COLOR_TEXT, textColor);
redrawColors();
}
);
});
}
function resetClickEvents() {
top.document.onclick = undefined;
top.document.ondblclick = undefined;
top.document.onmousedown = undefined;
}
if (document != top.document){
return;
}
resetClickEvents();
removeStyles(document.body);
patchColorPicker();
patchStyles();
removeAd();
adjustLayout();
setTimeout(removeAd, 500);
setTimeout(()=>{ cleanText($('#content').get(0)); }, 0);
// loadNextPage();
top.loadNextPage = loadNextPage;
top.onColorPickerJSLoaded = onColorPickerJSLoaded;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment