feat(epub): complete wxread parity scope

This commit is contained in:
shen
2026-05-26 09:19:37 +08:00
parent 23182e8b5a
commit 0e7d952fe5
31 changed files with 2619 additions and 1533 deletions
@@ -0,0 +1,67 @@
(function() {
if (window.WeReadApi) { return; }
function bridge() {
return window.RDReaderBridge || null;
}
function sharedThemeCSS(theme) {
if (!theme) { return ''; }
var backgroundColor = theme.backgroundColor || 'transparent';
var textColor = theme.textColor || 'inherit';
return [
':root {',
' color-scheme: light;',
'}',
'html, body {',
' background: ' + backgroundColor + ' !important;',
' color: ' + textColor + ' !important;',
'}',
'a {',
' -webkit-tap-highlight-color: transparent;',
'}'
].join('\n');
}
window.WeReadApi = {
applySharedTheme: function(theme) {
var css = sharedThemeCSS(theme);
if (window.RDInjectedCSS) {
window.RDInjectedCSS.setStyle('ss-reader-theme', css, {
includeFrames: !!(theme && theme.includeFrames)
});
}
return true;
},
applyPagination: function(styleText) {
return !!(bridge() && bridge().applyPagination(styleText));
},
setPageMetrics: function(pageCount, pageStride) {
return !!(bridge() && bridge().setPageMetrics(pageCount, pageStride));
},
scrollToPage: function(pageIndex) {
return !!(bridge() && bridge().scrollToPage(pageIndex));
},
scrollToLocation: function(location, fallbackPageIndex) {
return !!(bridge() && bridge().scrollToLocation(location, fallbackPageIndex));
},
setHighlights: function(items) {
return !!(bridge() && bridge().setHighlights(items));
},
clearHighlights: function() {
return !!(bridge() && bridge().clearHighlights());
},
setSearchPresentation: function(payload) {
return !!(bridge() && bridge().setSearchPresentation(payload));
},
resolveDecorations: function() {
return bridge() ? bridge().resolveDecorations() : { highlights: [], search: [] };
},
reportProgression: function(fragment) {
return !!(bridge() && bridge().reportProgression(fragment));
},
selectionPayload: function() {
return bridge() ? bridge().selectionPayload() : null;
}
};
})();
@@ -0,0 +1,45 @@
(function() {
if (window.RDInjectedCSS) { return; }
function applyStyle(doc, identifier, cssText) {
if (!doc || !doc.head) { return; }
var style = doc.getElementById(identifier);
if (!style) {
style = doc.createElement('style');
style.id = identifier;
doc.head.appendChild(style);
}
style.textContent = cssText || '';
}
function walkDocuments(rootDoc, includeFrames, visitor) {
if (!rootDoc) { return; }
visitor(rootDoc);
if (!includeFrames || !rootDoc.querySelectorAll) { return; }
Array.prototype.forEach.call(rootDoc.querySelectorAll('iframe'), function(frame) {
try {
if (!frame.contentDocument) { return; }
walkDocuments(frame.contentDocument, includeFrames, visitor);
} catch (error) {
}
});
}
window.RDInjectedCSS = {
setStyle: function(identifier, cssText, options) {
var includeFrames = !!(options && options.includeFrames);
walkDocuments(document, includeFrames, function(doc) {
applyStyle(doc, identifier, cssText);
});
},
removeStyle: function(identifier, options) {
var includeFrames = !!(options && options.includeFrames);
walkDocuments(document, includeFrames, function(doc) {
var style = doc.getElementById(identifier);
if (style && style.parentNode) {
style.parentNode.removeChild(style);
}
});
}
};
})();
@@ -7,48 +7,17 @@
} catch (error) {
}
});
function nodePath(node) {
var path = [];
var current = node;
while (current && current !== document) {
var parent = current.parentNode;
if (!parent) { break; }
var index = Array.prototype.indexOf.call(parent.childNodes, current);
path.unshift(index);
current = parent;
}
return path;
}
function nodeFromPath(path) {
var current = document;
for (var i = 0; i < path.length; i += 1) {
if (!current || !current.childNodes || current.childNodes.length <= path[i]) { return null; }
current = current.childNodes[path[i]];
}
return current;
}
function serializeRange(range) {
return JSON.stringify({
kind: 'dom-range',
startPath: nodePath(range.startContainer),
startOffset: range.startOffset,
endPath: nodePath(range.endContainer),
endOffset: range.endOffset
});
if (window.rangy && typeof window.rangy.serializeRange === 'function') {
return window.rangy.serializeRange(range, range && range.startContainer && range.startContainer.ownerDocument);
}
return null;
}
function rangeFromInfo(rangeInfo) {
try {
var payload = typeof rangeInfo === 'string' ? JSON.parse(rangeInfo) : rangeInfo;
var startNode = nodeFromPath(payload.startPath || []);
var endNode = nodeFromPath(payload.endPath || []);
if (!startNode || !endNode) { return null; }
var range = document.createRange();
range.setStart(startNode, payload.startOffset || 0);
range.setEnd(endNode, payload.endOffset || 0);
return range;
} catch (error) {
return null;
if (window.rangy && typeof window.rangy.deserializeRange === 'function') {
return window.rangy.deserializeRange(rangeInfo, document);
}
return null;
}
var visiblePageIndex = 0;
var configuredPageCount = 1;
@@ -153,6 +122,22 @@
var absoluteLeft = Math.max(0, rect.left + currentPageOffset());
return Math.max(0, Math.floor(absoluteLeft / effectivePageStride()));
}
function fragmentTarget(fragment) {
if (!fragment) { return null; }
var contexts = documentContexts(document, 0, 0);
for (var index = 0; index < contexts.length; index += 1) {
var context = contexts[index];
var target = context.doc.getElementById(fragment)
|| context.doc.querySelector('[name="' + fragment.replace(/"/g, '\\"') + '"]');
if (target) {
return {
context: context,
target: target
};
}
}
return null;
}
var storedHighlights = [];
var storedSearchPresentation = null;
function normalizeHref(rawHref) {
@@ -228,6 +213,36 @@
});
return items;
}
var hookedDocuments = new WeakSet();
var hookedFrames = new WeakSet();
function selectionForDocument(doc) {
if (!doc) { return null; }
try {
var view = doc.defaultView || window;
return view.getSelection ? view.getSelection() : null;
} catch (error) {
return null;
}
}
function currentSelectionContext() {
var contexts = documentContexts(document, 0, 0);
for (var index = 0; index < contexts.length; index += 1) {
var context = contexts[index];
var selection = selectionForDocument(context.doc);
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
continue;
}
var text = selection.toString ? selection.toString() : '';
if (!text || !text.trim()) {
continue;
}
return {
context: context,
selection: selection
};
}
return null;
}
function rectPayloadForRange(range, offsetX, offsetY) {
if (!range || range.collapsed) { return []; }
var rects = [];
@@ -344,17 +359,18 @@
};
}
function selectionPayload() {
var selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
var payloadContext = currentSelectionContext();
if (!payloadContext) {
return null;
}
var range = selection.getRangeAt(0);
var text = selection.toString();
var range = payloadContext.selection.getRangeAt(0);
var text = payloadContext.selection.toString();
if (!text || !text.trim()) { return null; }
var pageCount = totalPageCount();
var progression = pageCount <= 1 ? 0 : visiblePageIndex / Math.max(pageCount - 1, 1);
var lastProgression = progression;
return {
href: payloadContext.context.href || null,
text: text,
rangeInfo: serializeRange(range),
progression: progression,
@@ -362,13 +378,54 @@
};
}
var selectionTimer = null;
document.addEventListener('selectionchange', function() {
function notifySelectionChange() {
clearTimeout(selectionTimer);
selectionTimer = setTimeout(function() {
var payload = selectionPayload();
window.webkit.messageHandlers.{{SELECTION_CHANGED_MESSAGE}}.postMessage(payload);
}, 120);
});
}
function handleDocumentClick(event) {
var anchor = event.target.closest ? event.target.closest('a[href]') : null;
if (!anchor) { return; }
var href = anchor.getAttribute('href');
if (!href) { return; }
if (/^(https?:|mailto:|tel:)/i.test(href)) {
event.preventDefault();
window.webkit.messageHandlers.{{EXTERNAL_LINK_MESSAGE}}.postMessage({ url: href });
return;
}
event.preventDefault();
window.webkit.messageHandlers.{{INTERNAL_LINK_MESSAGE}}.postMessage({ href: href });
}
function installDocumentHooks(doc) {
if (!doc || hookedDocuments.has(doc)) { return; }
hookedDocuments.add(doc);
doc.addEventListener('selectionchange', notifySelectionChange);
doc.addEventListener('click', handleDocumentClick, true);
}
function installFrameHooks(rootDoc) {
if (!rootDoc || !rootDoc.querySelectorAll) { return; }
Array.prototype.forEach.call(rootDoc.querySelectorAll('iframe'), function(frame) {
if (hookedFrames.has(frame)) { return; }
hookedFrames.add(frame);
var hookFrameDocument = function() {
try {
if (!frame.contentDocument) { return; }
installDocumentHooks(frame.contentDocument);
installFrameHooks(frame.contentDocument);
} catch (error) {
}
};
frame.addEventListener('load', function() {
hookFrameDocument();
scheduleRelayout();
}, { passive: true });
hookFrameDocument();
});
}
installDocumentHooks(document);
installFrameHooks(document);
window.addEventListener('resize', function() {
if (isFixedLayoutDocument()) { return; }
setVisiblePageIndex(visiblePageIndex);
@@ -384,30 +441,22 @@
node.addEventListener('error', scheduleRelayout, { passive: true });
});
}
document.addEventListener('click', function(event) {
var anchor = event.target.closest ? event.target.closest('a[href]') : null;
if (!anchor) { return; }
var href = anchor.getAttribute('href');
if (!href) { return; }
if (/^(https?:|mailto:|tel:)/i.test(href)) {
event.preventDefault();
window.webkit.messageHandlers.{{EXTERNAL_LINK_MESSAGE}}.postMessage({ url: href });
return;
}
event.preventDefault();
window.webkit.messageHandlers.{{INTERNAL_LINK_MESSAGE}}.postMessage({ href: href });
}, true);
window.RDReaderBridge = {
applyPagination: function(styleText) {
if (isFixedLayoutDocument()) { return; }
var style = document.getElementById('ss-reader-pagination');
if (!style) {
style = document.createElement('style');
style.id = 'ss-reader-pagination';
document.head.appendChild(style);
if (window.RDInjectedCSS) {
window.RDInjectedCSS.setStyle('ss-reader-pagination', styleText, { includeFrames: false });
} else {
var style = document.getElementById('ss-reader-pagination');
if (!style) {
style = document.createElement('style');
style.id = 'ss-reader-pagination';
document.head.appendChild(style);
}
style.textContent = styleText;
}
style.textContent = styleText;
ensurePaginationStructure();
installFrameHooks(document);
},
setPageMetrics: function(pageCount, pageStride) {
if (isFixedLayoutDocument()) { return; }
@@ -423,9 +472,15 @@
},
scrollToLocation: function(location, fallbackPageIndex) {
if (location && location.fragment) {
var target = document.getElementById(location.fragment) || document.querySelector('[name="' + location.fragment.replace(/"/g, '\\"') + '"]');
if (target) {
setVisiblePageIndex(pageIndexForElement(target));
var resolvedTarget = fragmentTarget(location.fragment);
if (resolvedTarget) {
if (isFixedLayoutDocument()) {
reportProgression(location.fragment);
return;
}
var rect = resolvedTarget.target.getBoundingClientRect();
var absoluteLeft = Math.max(0, rect.left + resolvedTarget.context.offsetX + currentPageOffset());
setVisiblePageIndex(Math.max(0, Math.floor(absoluteLeft / effectivePageStride())));
reportProgression(location.fragment);
return;
}
@@ -0,0 +1,13 @@
(function() {
if (window.rangy) { return; }
window.rangy = {
initialized: true,
init: function() {
return window.rangy;
},
createRange: function(doc) {
return (doc || document).createRange();
}
};
})();
@@ -0,0 +1,93 @@
(function() {
if (!window.rangy) {
window.rangy = {};
}
if (window.rangy.serializeRange && window.rangy.deserializeRange) {
return;
}
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 nodePath(node) {
var path = [];
var current = node;
while (current && current !== current.ownerDocument) {
var parent = current.parentNode;
if (!parent) { break; }
var index = Array.prototype.indexOf.call(parent.childNodes, current);
path.unshift(index);
current = parent;
}
return path;
}
function nodeFromPath(doc, path) {
var current = doc;
for (var index = 0; index < path.length; index += 1) {
if (!current || !current.childNodes || current.childNodes.length <= path[index]) {
return null;
}
current = current.childNodes[path[index]];
}
return current;
}
function documentContexts(rootDoc) {
var items = [];
if (!rootDoc) { return items; }
items.push(rootDoc);
var frames = rootDoc.querySelectorAll ? rootDoc.querySelectorAll('iframe') : [];
Array.prototype.forEach.call(frames, function(frame) {
try {
if (!frame.contentDocument) { return; }
documentContexts(frame.contentDocument).forEach(function(item) {
items.push(item);
});
} catch (error) {
}
});
return items;
}
window.rangy.serializeRange = function(range, doc) {
var ownerDocument = doc || (range && range.startContainer && range.startContainer.ownerDocument) || document;
return JSON.stringify({
kind: 'dom-range',
documentHref: normalizeHref(ownerDocument.location && ownerDocument.location.href),
startPath: nodePath(range.startContainer),
startOffset: range.startOffset,
endPath: nodePath(range.endContainer),
endOffset: range.endOffset
});
};
window.rangy.deserializeRange = function(rangeInfo, rootDoc) {
try {
var payload = typeof rangeInfo === 'string' ? JSON.parse(rangeInfo) : rangeInfo;
var documentHref = normalizeHref(payload.documentHref);
var resolvedDocument = (documentContexts(rootDoc || document).find(function(candidate) {
return normalizeHref(candidate.location && candidate.location.href) === documentHref;
})) || (rootDoc || document);
var startNode = nodeFromPath(resolvedDocument, payload.startPath || []);
var endNode = nodeFromPath(resolvedDocument, payload.endPath || []);
if (!startNode || !endNode) { return null; }
var range = resolvedDocument.createRange();
range.setStart(startNode, payload.startOffset || 0);
range.setEnd(endNode, payload.endOffset || 0);
return range;
} catch (error) {
return null;
}
};
})();