您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
即時調整 Wenku8 內容區字型(自定義)與大小,並可一鍵去除多餘空行,狀態自動記憶
// ==UserScript== // @name 輕小說文庫 wenku8 字型+大小調整+去除空行 // @namespace http://tampermonkey.net/ // @version 1.8.1 // @description 即時調整 Wenku8 內容區字型(自定義)與大小,並可一鍵去除多餘空行,狀態自動記憶 // @author shanlan(ChatGPT o3-mini) // @match http*://www.wenku8.net/modules/article/reader.php*cid=* // @match http*://www.wenku8.cc/modules/article/reader.php*cid=* // @match http*://www.wenku8.net/novel/* // @match http*://www.wenku8.cc/novel/* // @grant none // @run-at document-end // @license MIT // ==/UserScript== (function(){ "use strict"; const uiStyle = document.createElement("style"); uiStyle.textContent = ` .tm-ui-btn, .tm-ui-panel{ all:unset; box-sizing:border-box; font-family:sans-serif; } .tm-ui-btn{ position:fixed; width:48px; /* 調整前為 32px */ height:48px; /* 調整前為 32px */ background:#333 !important; color:#f1f1f1 !important; border:1px solid #555 !important; border-radius:50% !important; box-shadow:0 2px 8px rgba(0,0,0,0.5) !important; display:flex !important; align-items:center !important; justify-content:center !important; font-weight:bold !important; font-size:24px !important; /* 調整前為 18px */ cursor:pointer !important; z-index:9999 !important; user-select:none !important; touch-action:none !important; } .tm-ui-panel{ position:fixed; background:#222 !important; color:#f1f1f1 !important; border:1px solid #555 !important; border-radius:12px !important; box-shadow:0 2px 12px rgba(0,0,0,0.6) !important; padding:12px 16px !important; /* 調整前為 9px 10px */ font-size:24px !important; /* 調整前為 18px */ display:none; z-index:9999 !important; min-width:480px; /* 調整前為 320px */ } .tm-ui-panel select, .tm-ui-panel input, .tm-ui-panel button{ background:#333 !important; color:#f1f1f1 !important; border:1px solid #555 !important; border-radius:6px !important; padding:0px 4px !important; font-size:26px !important; /* 調整前為 20px */ margin-right:8px; } .tm-ui-panel button{ cursor:pointer !important; min-width:48px; min-height:48px; } .tm-ui-panel label, .tm-ui-panel span{ color:#f1f1f1 !important; font-size:28px !important; /* 調整前為 21px */ } .tm-ui-panel input[type="text"]{ width:80px !important; /* 調整前為 60px */ text-align:center; } `; document.head.appendChild(uiStyle); const builtInFonts = [ ["","預設"], ["\"Noto Sans TC\", \"思源黑體\", \"Microsoft JhengHei\", \"微軟正黑體\", sans-serif","思源黑體"], ["\"Noto Serif TC\", \"思源宋體\", \"PMingLiU\", \"新細明體\", serif","思源宋體"], ["\"Microsoft JhengHei\", \"微軟正黑體\", sans-serif","微軟正黑體"], ["\"Microsoft JhengHei UI\", \"微軟正黑體 UI\", sans-serif","微軟正黑體 UI"], ["\"PMingLiU\", \"新細明體\", serif","新細明體"], ["\"MingLiU\", \"細明體\", serif","細明體"], ["\"DFKai-SB\", \"標楷體\", serif","標楷體"], ["\"SimSun\", \"宋體\", serif","宋體"], ["\"Microsoft YaHei\", \"微軟雅黑\", sans-serif","微軟雅黑"], ["Arial, Helvetica, sans-serif","Arial"], ["serif","Serif"] ]; let customFonts = []; try{customFonts = JSON.parse(localStorage.getItem("wenku8_customFonts")||"[]");}catch(e){customFonts = [];} let font = localStorage.getItem("wenku8_font") || ""; let size = parseInt(localStorage.getItem("wenku8_fontsize")) || 18; function updateStyle(){ let s = document.getElementById("wenku8-style"); if(!s){s = document.createElement("style"); s.id="wenku8-style"; document.head.appendChild(s);} s.textContent = `#content, #content * { font-family: ${font||"inherit"} !important; font-size: ${size}px !important; }`; } updateStyle(); const mainBtn = document.createElement("div"); mainBtn.textContent = "字"; mainBtn.classList.add("tm-ui-btn"); const panel = document.createElement("div"); panel.classList.add("tm-ui-panel"); const fontLabel = document.createElement("label"); fontLabel.textContent = "字型:"; fontLabel.style.marginRight = "8px"; const fontSel = document.createElement("select"); fontSel.style.marginRight = "20px"; function updateFontOptions(){ while(fontSel.options.length > 0) fontSel.remove(0); const addOption = (arr)=>arr.forEach(([v,n])=>{ const opt = new Option(n,v); if(v===font) opt.selected = true; fontSel.add(opt); }); addOption(builtInFonts); if(customFonts.length>0) addOption(customFonts); } updateFontOptions(); const btnAddFont = document.createElement("button"); btnAddFont.textContent = "+"; const btnDelFont = document.createElement("button"); btnDelFont.textContent = "-"; const sizeContainer = document.createElement("span"); sizeContainer.style.display = "inline-flex"; sizeContainer.style.alignItems = "center"; const sizeLabel = document.createElement("span"); sizeLabel.textContent = "大小:"; sizeLabel.style.marginRight = "8px"; const otherLabel = document.createElement("span"); otherLabel.textContent = "其他:"; otherLabel.style.marginRight = "8px"; const btnMinus = document.createElement("button"); btnMinus.textContent = "-"; Object.assign(btnMinus.style,{width:"36px",height:"36px",fontSize:"22px",marginRight:"8px"}); const sizeInput = document.createElement("input"); Object.assign(sizeInput,{type:"text",value:size}); sizeInput.style.width = "60px"; sizeInput.style.textAlign = "center"; sizeInput.style.marginRight = "8px"; const btnPlus = document.createElement("button"); btnPlus.textContent = "+"; Object.assign(btnPlus.style,{width:"36px",height:"36px",fontSize:"22px"}); sizeContainer.append(sizeLabel,btnPlus,sizeInput,btnMinus); const divider = document.createElement("hr"); Object.assign(divider.style,{margin:"8px 0",border:"0",borderTop:"1px solid #555"}); const divider2 = document.createElement("hr"); Object.assign(divider2.style,{margin:"8px 0",border:"0",borderTop:"1px solid #555"}); const btnRemoveBr = document.createElement("button"); let removed = localStorage.getItem("wenku8_removebr") === "1"; btnRemoveBr.textContent = removed ? "恢復空行" : "去除空行"; panel.append(fontLabel,fontSel,btnAddFont,btnDelFont,divider,sizeContainer,divider2,otherLabel,btnRemoveBr); let originalHTML = null; function removeBr(){ const c = document.querySelector("#acontent") || document.querySelector("#content"); if(!c) return; if(originalHTML === null) originalHTML = c.innerHTML; c.innerHTML = c.innerHTML.replace(/(?:<br\s*\/?>\s*){2,}/gi,"<br>"); btnRemoveBr.textContent = "恢復空行"; removed = true; localStorage.setItem("wenku8_removebr","1"); } function restoreBr(){ const c = document.querySelector("#acontent") || document.querySelector("#content"); if(!c) return; if(originalHTML !== null) c.innerHTML = originalHTML; btnRemoveBr.textContent = "去除空行"; removed = false; localStorage.setItem("wenku8_removebr","0"); } btnRemoveBr.onclick = function(){ !removed ? removeBr() : restoreBr(); }; setTimeout(()=>{ const c = document.querySelector("#acontent") || document.querySelector("#content"); if(!c)return; if(removed){ if(originalHTML===null) originalHTML = c.innerHTML; c.innerHTML = c.innerHTML.replace(/(?:<br\s*\/?>\s*){2,}/gi,"<br>"); btnRemoveBr.textContent = "恢復空行"; } },0); function updateAll(){ font = fontSel.value; if(sizeInput.value.trim()==="") return; let num = parseInt(sizeInput.value,10); if(num <= 0) num = 18; size = num; sizeInput.value = size; localStorage.setItem("wenku8_font",font); localStorage.setItem("wenku8_fontsize",size); updateStyle(); } fontSel.addEventListener("change",updateAll); sizeInput.addEventListener("input",updateAll); btnMinus.addEventListener("click",()=>{ size = Math.max(1, size-2); sizeInput.value = size; updateAll(); }); btnPlus.addEventListener("click",()=>{ size = size+2; sizeInput.value = size; updateAll(); }); btnAddFont.onclick = function(){ const newFontValue = prompt('請輸入字體代碼,例如 "Comic Sans MS", cursive, sans-serif'); if(!newFontValue)return; const newFontName = prompt("請輸入字體顯示名稱"); if(!newFontName)return; customFonts.push([newFontValue,newFontName]); localStorage.setItem("wenku8_customFonts",JSON.stringify(customFonts)); updateFontOptions(); }; btnDelFont.onclick = function(){ const selVal = fontSel.value; const foundIndex = customFonts.findIndex(item=>item[0]===selVal); if(foundIndex===-1){ alert("無法刪除內建字體!"); return; } if(confirm("確定刪除選定的自訂字體?")){ customFonts.splice(foundIndex,1); localStorage.setItem("wenku8_customFonts",JSON.stringify(customFonts)); if(font===selVal){ font = ""; localStorage.setItem("wenku8_font",""); } updateFontOptions(); updateStyle(); } }; const MARGIN = 8, THRESHOLD = 4; function setBtnPosition(l,t){ mainBtn.style.left = l+"px"; mainBtn.style.top = t+"px"; } function clampBtnInView(){ const vw = window.innerWidth, vh = window.innerHeight; const bw = mainBtn.offsetWidth || 32, bh = mainBtn.offsetHeight || 32; let l = parseFloat(mainBtn.style.left)||0, t = parseFloat(mainBtn.style.top)||0; l = Math.min(Math.max(l,MARGIN),vw-bw-MARGIN); t = Math.min(Math.max(t,MARGIN),vh-bh-MARGIN); setBtnPosition(l,t); } function measurePanelSize(){ const pd = panel.style.display, pv = panel.style.visibility; panel.style.visibility="hidden"; panel.style.display="block"; const r = panel.getBoundingClientRect(); panel.style.display = pd; panel.style.visibility = pv; return {w:r.width,h:r.height}; } function positionPanel(){ if(panel.style.display==="none") return; const vw = window.innerWidth, vh = window.innerHeight; const btnRect = mainBtn.getBoundingClientRect(); const r = panel.getBoundingClientRect(); const sz = (r.width && r.height) ? {w:r.width, h:r.height} : measurePanelSize(); let left, top; const rightSpace = vw - (btnRect.right + MARGIN); const leftSpace = btnRect.left - MARGIN; if(rightSpace >= sz.w + 20) { left = btnRect.right + MARGIN; } else if(leftSpace >= sz.w + 20) { left = btnRect.left - sz.w - MARGIN; } else { left = Math.min(Math.max(btnRect.left + (btnRect.width - sz.w) / 2, MARGIN), vw - sz.w - MARGIN); } top = btnRect.bottom + MARGIN; if(top + sz.h > vh - MARGIN) top = vh - sz.h - MARGIN; if(top < MARGIN) top = MARGIN; panel.style.left = Math.round(left) + "px"; panel.style.top = Math.round(top) + "px"; } function togglePanel(){ if(panel.style.display === "block"){ panel.style.display = "none"; }else{ panel.style.display = "block"; positionPanel(); } } document.body.append(mainBtn,panel); let initLeft = parseFloat(localStorage.getItem("wenku8_btn_left")); let initTop = parseFloat(localStorage.getItem("wenku8_btn_top")); if(!Number.isFinite(initLeft)) initLeft = window.innerWidth - (mainBtn.offsetWidth||32) - 12; if(!Number.isFinite(initTop)) initTop = 12; setBtnPosition(initLeft,initTop); clampBtnInView(); let dragging = false, startX = 0, startY = 0, originL = 0, originT = 0; mainBtn.addEventListener("pointerdown",(e)=>{ e.preventDefault(); mainBtn.setPointerCapture(e.pointerId); dragging = false; const rect = mainBtn.getBoundingClientRect(); startX = e.clientX; startY = e.clientY; originL = rect.left; originT = rect.top; const onMove = (ev)=>{ const dx = ev.clientX - startX; const dy = ev.clientY - startY; if(!dragging && (Math.abs(dx)>THRESHOLD || Math.abs(dy)>THRESHOLD)) dragging = true; if(dragging){ let nl = originL + dx, nt = originT + dy; const vw = window.innerWidth, vh = window.innerHeight; const bw = mainBtn.offsetWidth, bh = mainBtn.offsetHeight; if(nl < MARGIN) nl = MARGIN; if(nt < MARGIN) nt = MARGIN; if(nl + bw > vw - MARGIN) nl = vw - bw - MARGIN; if(nt + bh > vh - MARGIN) nt = vh - bh - MARGIN; setBtnPosition(nl,nt); if(panel.style.display!=="none") positionPanel(); } }; const onUp = ()=>{ mainBtn.releasePointerCapture(e.pointerId); document.removeEventListener("pointermove",onMove); document.removeEventListener("pointerup",onUp); clampBtnInView(); if(panel.style.display!=="none") positionPanel(); const l = parseFloat(mainBtn.style.left)||0; const t = parseFloat(mainBtn.style.top)||0; localStorage.setItem("wenku8_btn_left",String(l)); localStorage.setItem("wenku8_btn_top",String(t)); if(!dragging) togglePanel(); dragging = false; }; document.addEventListener("pointermove",onMove); document.addEventListener("pointerup",onUp); }); mainBtn.addEventListener("click",(e)=>{ e.preventDefault(); e.stopPropagation(); }); document.addEventListener("pointerdown",(e)=>{ if(!panel.contains(e.target) && e.target!==mainBtn) panel.style.display="none"; }); window.addEventListener("resize",()=>{ clampBtnInView(); if(panel.style.display!=="none") positionPanel(); }); })();