Last active
March 1, 2025 06:00
-
-
Save lpimem/11913a8b856678ac7acee516e06b945f to your computer and use it in GitHub Desktop.
Piao Tian Xiao Shuo - Reading Mode
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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 after <br> tags | |
tempDiv.innerHTML = tempDiv.innerHTML.replace(/(<br\s*\/?>)\s*( )+/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