feat(epub): align wxread css and overlay pagination behavior

This commit is contained in:
shen
2026-05-25 22:15:24 +08:00
parent c0aac56083
commit 23182e8b5a
31 changed files with 2585 additions and 1691 deletions
@@ -153,47 +153,195 @@
var absoluteLeft = Math.max(0, rect.left + currentPageOffset());
return Math.max(0, Math.floor(absoluteLeft / effectivePageStride()));
}
function unwrapHighlights() {
document.querySelectorAll('mark.ss-reader-highlight').forEach(function(mark) {
var parent = mark.parentNode;
if (!parent) { return; }
while (mark.firstChild) {
parent.insertBefore(mark.firstChild, mark);
}
parent.removeChild(mark);
});
var storedHighlights = [];
var storedSearchPresentation = null;
function normalizeHref(rawHref) {
if (!rawHref) { return ''; }
var value = String(rawHref);
value = value.replace(/^.*?:\/\/[^/]+\//, '');
value = value.split('#')[0];
value = value.replace(/^\/+/, '');
try {
value = decodeURIComponent(value);
} catch (error) {
}
return value;
}
function clearHighlights() {
storedHighlights = [];
}
function setHighlights(items) {
unwrapHighlights();
if (!Array.isArray(items)) { return; }
items.forEach(function(item) {
if (!item || !item.rangeInfo) { return; }
var range = rangeFromInfo(item.rangeInfo);
if (!range || range.collapsed) { return; }
storedHighlights = Array.isArray(items) ? items.slice() : [];
}
function setSearchPresentation(payload) {
storedSearchPresentation = payload || null;
}
function textNodes(doc) {
if (!doc || !doc.body) { return []; }
var walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT, {
acceptNode: function(node) {
if (!node || !node.nodeValue || !node.nodeValue.trim()) {
return NodeFilter.FILTER_REJECT;
}
var parent = node.parentNode;
if (!parent || !parent.nodeName) {
return NodeFilter.FILTER_REJECT;
}
var name = parent.nodeName.toLowerCase();
if (['script', 'style', 'noscript'].indexOf(name) >= 0) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
});
var nodes = [];
while (walker.nextNode()) {
nodes.push(walker.currentNode);
}
return nodes;
}
function documentContexts(rootDoc, baseOffsetX, baseOffsetY) {
var items = [];
if (!rootDoc) { return items; }
var offsetX = baseOffsetX || 0;
var offsetY = baseOffsetY || 0;
items.push({
doc: rootDoc,
href: normalizeHref(rootDoc.location && rootDoc.location.href),
offsetX: offsetX,
offsetY: offsetY
});
var frames = rootDoc.querySelectorAll ? rootDoc.querySelectorAll('iframe') : [];
frames.forEach(function(frame) {
try {
var mark = document.createElement('mark');
var style = item.style || 'highlight';
mark.className = 'ss-reader-highlight ss-reader-highlight-' + style;
mark.style.color = 'inherit';
if (style === 'underline') {
mark.style.background = 'transparent';
mark.style.textDecorationLine = 'underline';
mark.style.textDecorationStyle = 'solid';
mark.style.textDecorationThickness = '2px';
mark.style.textDecorationColor = item.color || '#F8E16C';
} else {
mark.style.background = item.color || '#F8E16C';
}
mark.dataset.highlightId = item.id || '';
var fragment = range.extractContents();
mark.appendChild(fragment);
range.insertNode(mark);
if (mark.parentNode && mark.parentNode.normalize) {
mark.parentNode.normalize();
}
if (!frame.contentDocument) { return; }
var frameRect = frame.getBoundingClientRect();
documentContexts(
frame.contentDocument,
offsetX + frameRect.left,
offsetY + frameRect.top
).forEach(function(item) {
items.push(item);
});
} catch (error) {
}
});
return items;
}
function rectPayloadForRange(range, offsetX, offsetY) {
if (!range || range.collapsed) { return []; }
var rects = [];
Array.prototype.forEach.call(range.getClientRects(), function(rect) {
if (!rect || rect.width < 0.5 || rect.height < 0.5) { return; }
rects.push({
x: rect.left + (offsetX || 0),
y: rect.top + (offsetY || 0),
width: rect.width,
height: rect.height
});
});
return rects;
}
function collectHighlightDecorations() {
return storedHighlights.map(function(item, index) {
if (!item || !item.rangeInfo) { return null; }
var range = rangeFromInfo(item.rangeInfo);
var rects = rectPayloadForRange(range, 0, 0);
if (!rects.length) { return null; }
return {
key: item.id || ('highlight-' + index),
kind: item.style === 'underline' ? 'underline' : 'highlight',
color: item.color || '#F8E16C',
rangeInfo: item.rangeInfo,
rects: rects
};
}).filter(Boolean);
}
function collectSearchDecorations() {
if (!storedSearchPresentation || !storedSearchPresentation.keyword) {
return [];
}
var resources = new Map();
if (Array.isArray(storedSearchPresentation.resources)) {
storedSearchPresentation.resources.forEach(function(resource) {
resources.set(normalizeHref(resource.href), resource);
});
}
var keyword = String(storedSearchPresentation.keyword);
var lowerKeyword = keyword.toLowerCase();
var decorations = [];
documentContexts(document, 0, 0).forEach(function(context) {
var resourceState = resources.get(context.href);
if (!resourceState || resourceState.matchCount <= 0) {
return;
}
var activeIndex = typeof resourceState.activeLocalMatchIndex === 'number' ? resourceState.activeLocalMatchIndex : null;
var currentLocalIndex = 0;
var activeRect = null;
var contextStartIndex = decorations.length;
textNodes(context.doc).forEach(function(node) {
var source = node.nodeValue || '';
var lowerSource = source.toLowerCase();
var cursor = 0;
while (cursor < source.length) {
var foundIndex = lowerSource.indexOf(lowerKeyword, cursor);
if (foundIndex < 0) {
break;
}
var range = context.doc.createRange();
range.setStart(node, foundIndex);
range.setEnd(node, foundIndex + keyword.length);
var rects = rectPayloadForRange(range, context.offsetX, context.offsetY);
if (rects.length) {
var isActive = currentLocalIndex === activeIndex;
decorations.push({
key: context.href + ':' + currentLocalIndex,
kind: isActive ? 'activeSearch' : 'search',
rects: rects
});
if (isActive) {
activeRect = rects[0];
}
}
currentLocalIndex += 1;
cursor = foundIndex + keyword.length;
}
});
if (activeRect && !isFixedLayoutDocument()) {
var previousPageIndex = visiblePageIndex;
var activePageIndex = Math.max(0, Math.floor(Math.max(0, activeRect.x + currentPageOffset()) / effectivePageStride()));
setVisiblePageIndex(activePageIndex);
var deltaX = (activePageIndex - previousPageIndex) * effectivePageStride();
if (deltaX !== 0) {
for (var index = contextStartIndex; index < decorations.length; index += 1) {
decorations[index].rects = decorations[index].rects.map(function(rect) {
return {
x: rect.x - deltaX,
y: rect.y,
width: rect.width,
height: rect.height
};
});
}
}
}
});
return decorations;
}
function resolveDecorations() {
return {
highlights: collectHighlightDecorations(),
search: collectSearchDecorations()
};
}
function selectionPayload() {
var selection = window.getSelection();
@@ -290,7 +438,9 @@
this.scrollToPage(derivedPageIndex);
},
setHighlights: setHighlights,
clearHighlights: unwrapHighlights,
clearHighlights: clearHighlights,
setSearchPresentation: setSearchPresentation,
resolveDecorations: resolveDecorations,
reportProgression: reportProgression,
selectionPayload: selectionPayload
};
@@ -0,0 +1,56 @@
/* 适配搜狗百科 bar 里的 icon 被染色 */
.header-home, .header-home:before, .header-logo, .loginBox, .header-search, .header-search:before, .header-more:before {
background-color: transparent !important;
}
/* 适配搜狗百科 某些透明的 div 被染色导致挡住图片 */
.abstract-img-none {
background: none !important;
}
/* 适配百度搜索图片丢失 */
.c-touchable-feedback-no-default * {
background-color: transparent !important;
}
img, video {
z-index: 1 !important;
}
/*背景纯黑*/
*, *:before, *:after {
background-color: rgba(0, 0, 0, 1) !important;
}
.wr-business-container,
.wr-business-container *,
.wr-business-container *:before,
.wr-business-container *:after {
background-color: rgb(28, 28, 29) !important;
}
/*背景颜色和一般字体颜色*/
div, h1, h2, h3, h4, h5, h6, p, body, em, html, link, textarea, form, select, input, span, button, em, menu, aside, table, tr, td, nav, dl, dt, dd, amp-iframe, main, section {
color: rgba(180, 180, 182, 1) !important;
border-color: #555555 !important;
text-shadow: 0 0 0 #000;
}
/*超链接*/
a {
color: rgba(84, 127, 176, 1) !important;
}
blockquote {
color : rgb(218, 220, 224);
background-color : rgb(10, 14, 18) !important;
border-left: 1px solid rgba(116, 120, 124, 1);
}
img {
border: none !important;
}
ul li:before, ol li:before {
background-color: rgba(196, 200, 204, 1) !important;
}
@@ -0,0 +1,221 @@
/* this file is processed with xxd via a build rule and embedded in library */
/* these styles come from Safari */
/* note that comments are only permitted before selectors and before the styles */
/* DO NOT fiddle with this file if you want to have your own styles,
pass your own stylesheet via the option parameter to override these defaults */
head {
display:none;
}
title {
display:none;
}
style {
display:none;
}
link {
display: none;
}
meta {
display: none;
}
script {
display: none;
}
html {
display:block;
margin:0;
padding:0;
}
body {
display:block;
font-size:16px;
margin:0;
padding:0;
}
article,aside,footer,header,hgroup,nav,section {
display:block;
}
p {
display:block;
margin:0;
}
img {
margin:0;
}
ul,ol,menu,dir {
display:block;
margin:1em 0 1em 0;
padding-left:24px;
}
ul {
list-style-type:disc;
}
ol {
list-style-type:decimal;
}
li {
display:list-item;
}
ul ul, ol ul {
list-style-type: circle;
}
ol ol ul, ol ul ul, ul ol ul, ul ul ul {
list-style-type: square;
}
code {
font-family:Courier;
}
pre, xmp, plaintext, listing {
display: block;
font-family: monospace;
white-space: pre;
margin: 1em 0;
}
a {
color:#2262A3;
text-decoration:underline;
}
a:active {
color:#2262A3;
}
center {
text-align:center;
display:block;
}
strong,b {
font-weight:bold;
}
i,em {
font-style:italic;
}
u {
text-decoration:underline;
}
big {
font-size:bigger;
}
small {
font-size:smaller;
}
sub {
font-size:smaller;
vertical-align:sub;
}
sup {
font-size:smaller;
vertical-align:super;
}
s,strike,del {
text-decoration:line-through;
}
tt,code,kbd,samp {
font-family:monospace;
}
pre,xmp,plaintext,listing {
display:block;
font-family:monospace;
white-space:pre;
margin-top:1em;
margin-right:0;
margin-bottom:1em;
}
pre {
background-color:rgba(0,0,0,.05);
padding-top:1em;
padding-bottom:1em;
border-radius:0.3em;
}
h1 {
display:block;
font-size:1.5em;
font-weight: normal;
}
h2 {
display:block;
font-size:1.4em;
margin-top: 0.83em;
}
h3 {
display:block;
font-size:1.3em;
margin-top: 1em;
}
h4 {
display:block;
font-size:1.2em;
margin-top: 1.33em;
}
h5 {
display:block;
font-size:1.1em;
margin-top: 1.67em;
}
h6 {
display:block;
font-size:1em;
margin-top: 2.33em;
}
div {
display: block;
}
hr {
display: block;
margin:0.5em auto 0.5em auto;
border-style: inset;
border-width: 1px;
}
table {
display: table;
border-collapse: separate;
border-spacing: 2px;
border-color: gray;
}
blockquote {
display: block;
}
@@ -0,0 +1,113 @@
/* <pre>代码块,注意必须写font-weight使字体生效 */
pre {
font-family: "Menlo";
font-weight: normal;
line-height: 1.5em;
}
/*版权信息*/
.copyRightTitle {
color: black;
font-size: 1.5em;
font-family: "Source Han Serif CN";
font-weight: normal;
}
/*图片说明文字*/
.eepub-single-image-title {
font-size: 0.75em;
text-align: center;
line-height: 1.4em;
color: rgba(0, 0, 0, 0.9);
margin: 0.2em 0.4em 1em 0.4em;
font-weight: normal;
}
/*标题*/
.firstTitle, h1.firstTitle {
font-size: 1.5em;
font-weight: bold;
line-height: 1.25em;
}
.secondTitle, h2.secondTitle {
font-size: 1.4em;
font-weight: bold;
line-height: 1.35em;
}
.thirdTitle, h3.thirdTitle {
font-size: 1.3em;
font-weight: bold;
line-height: 1.5em;
}
.fourthTitle, h4.fourthTitle {
font-size: 1.2em;
font-weight: bold;
line-height: 1.65em;
}
.fifthTitle, h5.fifthTitle {
font-size: 1.1em;
font-weight: bold;
line-height: 1.85em;
}
.sixthTitle, h6.sixthTitle {
font-size: 1em;
font-weight: bold;
line-height: 2em;
}
/*首字加大*/
/*浮动元素有默认的margin,所以这里会修复一下*/
.ftext {
float: left;
margin: 0em;
font-size: 2.38em;
font-weight: normal;
}
/*引用内容*/
.conQuot {
font-weight: normal;
margin: 0em 0em 0.2em 0em;
}
/*标题下来可能会有一行subHead*/
.subHead{
text-indent: 2em;
}
pre, pre span, pre code {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size:.7em!important;
}
.bodyPic {
wr-vertical-center-style: 2;
}
.qrbodyPic {
page-break-inside: avoid;
wr-vertical-center-style: 2;
}
/* 注标图 */
.qqreader-footnote {
width: 1em;
}
/* 私有垂直居中类 */
.wr-vertical-center {
wr-vertical-center-style: 1 !important;
}
/* 翻译 */
.wr-translation {
line-height: 1.7em !important;
}
/* 章节结尾工具 */
.book-chapter-tool {
display: block;
height: 78px;
width: 100%;
margin-top: 24px;
}
@@ -0,0 +1,171 @@
/* <pre>代码块,注意必须写font-weight使字体生效 */
pre {
font-family: "Menlo";
font-weight: normal;
line-height: 1.5em;
}
/*版权信息*/
.copyRightTitle {
color: black;
font-size: 1.5em;
font-family: "Source Han Serif CN";
font-weight: normal;
}
/*图片说明文字*/
.eepub-single-image-title {
font-size: 0.75em;
text-align: center;
line-height: 1.4em;
color: rgba(0, 0, 0, 0.9);
margin: 0.2em 0.4em 1em 0.4em;
font-family: "FZFSJW--GB1-0";
font-weight: normal;
}
/*标题*/
.firstTitle, h1.firstTitle {
font-size: 1.5em;
font-family: "Source Han Serif CN";
font-weight: bold;
line-height: 1.25em;
}
.secondTitle, h2.secondTitle {
font-size: 1.4em;
font-family: "Source Han Serif CN";
font-weight: bold;
line-height: 1.35em;
}
.thirdTitle, h3.thirdTitle {
font-size: 1.3em;
font-family: "Source Han Serif CN";
font-weight: bold;
line-height: 1.5em;
}
.fourthTitle, h4.fourthTitle {
font-size: 1.2em;
font-family: "Source Han Serif CN";
font-weight: bold;
line-height: 1.65em;
}
.fifthTitle, h5.fifthTitle {
font-size: 1.1em;
font-family: "Source Han Serif CN";
font-weight: bold;
line-height: 1.85em;
}
.sixthTitle, h6.sixthTitle {
font-size: 1em;
font-family: "Source Han Serif CN";
font-weight: bold;
line-height: 2em;
}
/*首字加大*/
/*浮动元素有默认的margin,所以这里会修复一下*/
.ftext {
float: left;
margin: 0em;
font-size: 2.38em;
font-weight: normal;
}
/*引用内容*/
.conQuot {
font-family: "FZFSJW--GB1-0";
font-weight: normal;
margin: 0em 0em 0.2em 0em;
}
/*标题下来可能会有一行subHead*/
.subHead{
text-indent: 2em;
}
pre, pre span, pre code {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size:.7em!important;
}
.bodyPic {
wr-vertical-center-style: 2;
}
.qrbodyPic {
page-break-inside: avoid;
wr-vertical-center-style: 2;
}
/* 注标图 */
.qqreader-footnote {
width: 1em;
}
/* 翻译 */
.wr-translation {
line-height: 1.7em !important;
}
/*-------------------- 小文章相关 ----------------------*/
/*epub-custom-rule-6 如果 class="weread-page-relate" 在开头,需要把上一页末尾一行移到这一页*/
.weread-page-relate {
weread-page-relate:true;
}
/*epub-custom-rule-29 小文章打赏的样式*/
.chapter-reward {
display: block;
height: 120px;
width: 100%;
margin-top: 24px;
}
/*epub-custom-rule-7 小文章工具栏标签的样式*/
.chapter-tool {
display: block;
height: 140px;
width: 100%;
margin-top: 24px;
}
/* 章节结尾工具 */
.book-chapter-tool {
display: block;
height: 78px;
width: 100%;
margin-top: 24px;
}
/* 调试好样式后和安卓同步,交给后台随小文章 css 下发,并把下面的样式从replace.css 删除 */
/* 安卓的re_bookItem是通过原生view留出上下空间的,但是iOS是通过css来控制 */
.re_bookItem{
display: block;
margin: 18px 0 18px 0;
}
p {
text-align:justify;
}
img[data-image-size="large"] {
width: 100%;
}
/* 文集公众号文章不支持控件的样式 */
.unsupported_iframe {
border-radius: 4px;
border: 1px solid #ffcdc0;
background: #fee;
padding: 12px 0px;
color: #ff8d8d;
text-align: center;
font-size: 14px;
}
/* 私有垂直居中类 */
.wr-vertical-center {
wr-vertical-center-style: 1 !important;
}