1530 lines
50 KiB
JavaScript
1530 lines
50 KiB
JavaScript
var highlighter
|
||
var util = rangy.util
|
||
var doc = document
|
||
var body = util.getBody(doc)
|
||
var reviewClassNames = ['review', 'friend-review']
|
||
|
||
// 在DOM加载完成后应用字体设置
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
if (window.wrInitialFontScale) {
|
||
changeFontSizeWithScale(window.wrInitialFontScale);
|
||
}
|
||
});
|
||
|
||
rangy.ready(function () {
|
||
rangy.init();
|
||
rangy.config.preferTextRange = true;
|
||
|
||
highlighter = rangy.createHighlighter(window.document, 'TextRange');
|
||
|
||
highlighter.addClassApplier(rangy.createClassApplier("highlight", {
|
||
ignoreWhiteSpace: true,
|
||
tagNames: ["span"],
|
||
elementProperties: {
|
||
onclick: function (event) {
|
||
return click(this, event)
|
||
}
|
||
}
|
||
}));
|
||
|
||
util.forEach(reviewClassNames, function (cls) {
|
||
highlighter.addClassApplier(rangy.createClassApplier(cls, {
|
||
ignoreWhiteSpace: true,
|
||
tagNames: ["span"],
|
||
elementProperties: {
|
||
onclick: function (event) {
|
||
return click(this, event)
|
||
}
|
||
}
|
||
}));
|
||
})
|
||
|
||
highlighter.addClassApplier(rangy.createClassApplier("reference", {
|
||
ignoreWhiteSpace: true,
|
||
tagNames: ["span"]
|
||
}));
|
||
|
||
highlighter.addClassApplier(rangy.createClassApplier("tts", {
|
||
ignoreWhiteSpace: true,
|
||
tagNames: ["span"]
|
||
}));
|
||
|
||
rangy.util.addListener(document, 'selectionchange', debounce(function () {
|
||
var rectInfo = getRectInfoInSelection()
|
||
var result = {
|
||
cmd: 'selectionChange',
|
||
hasSelection: rectInfo != null && rectInfo.end > rectInfo.start,
|
||
hasSelectionRange: rectInfo != null
|
||
}
|
||
if (rectInfo != null) {
|
||
result = rangy.util.extend(result, rectInfo)
|
||
}
|
||
wereadBridge.execMPReaderMethod('MPReader', result);
|
||
}))
|
||
|
||
// 通知音频的播放、暂停
|
||
var switches = document.getElementsByClassName('icon_share_audio_switch');
|
||
for (var i = 0; i < switches.length; i++) {
|
||
switches[i].addEventListener('touchstart', function () {
|
||
wereadBridge.execMPReaderMethod('MPReader', {
|
||
cmd: 'audioWillToggle'
|
||
});
|
||
}, true);
|
||
}
|
||
|
||
addClickEventToImage()
|
||
transferH5Link()
|
||
mediaPlatform.getAuthor();
|
||
});
|
||
|
||
/**
|
||
* @startP 开始查询的段落, 如果未负数,则是从倒数第 startP 段往前检索
|
||
* @minDetectLength 最小检测长度,如果段落长度小于它,则跳过,查看下一段是否匹配,
|
||
* 如果没有匹配 minDetectLength 的段落,则返回最长的一段
|
||
* @collectLength 收集长度,如果大于minDetectLength 而小于collectLength,则返回整个段落
|
||
* 否则截取到 collectLength 之后句子结束的位置
|
||
*
|
||
* @return string
|
||
*/
|
||
function getShareAbstractText(startP, minDetectLength, collectLength) {
|
||
var jsContent = document.getElementById("js_content");
|
||
if (jsContent == null) {
|
||
return "";
|
||
}
|
||
var pList = jsContent.getElementsByTagName("p");
|
||
if (pList == null) {
|
||
return "";
|
||
}
|
||
var length = pList.length;
|
||
minDetectLength = minDetectLength || 50
|
||
collectLength = collectLength || 100
|
||
var dir = 1;
|
||
if (startP < 0) {
|
||
startP = Math.max(0, length + startP);
|
||
dir = -1;
|
||
} else if (startP >= length) {
|
||
startP = length - 1;
|
||
dir = -1;
|
||
}
|
||
var i = startP, text, failedFindText = "", failedFindTextLength = 0;
|
||
while (i >= 0 && i < length) {
|
||
text = pList[i].innerText;
|
||
text = text && text.replace(/(\s|\t){2,}/g, ' ').trim();
|
||
if (text && text.length > minDetectLength) {
|
||
if (text.length < collectLength) {
|
||
return text;
|
||
}
|
||
var endSequenceIndex = text.indexOf("。", collectLength - 1);
|
||
if (endSequenceIndex < collectLength) {
|
||
return text;
|
||
}
|
||
return text.substring(0, endSequenceIndex + 1)
|
||
} else {
|
||
if (text && text.length > failedFindTextLength) {
|
||
failedFindText = text;
|
||
failedFindTextLength = text.length
|
||
}
|
||
i += dir;
|
||
}
|
||
}
|
||
return failedFindText;
|
||
}
|
||
|
||
/**
|
||
* 从最开始获取接近 collectLength 的字数。末尾加三个点
|
||
*/
|
||
function getBeginAbstractText(collectLength) {
|
||
var jsContent = document.getElementById("js_content");
|
||
if (jsContent == null) {
|
||
return "";
|
||
}
|
||
var children = jsContent.children
|
||
if (children == null || children.length == 0) {
|
||
return "";
|
||
}
|
||
var length = children.length;
|
||
collectLength = collectLength || 100
|
||
var i = 0, text, ret = "";
|
||
while (collectLength > 0 && i < length) {
|
||
text = children[i].innerText;
|
||
text = text && text.replace(/(\s|\t){2,}/g, ' ').trim();
|
||
if(text.length > 0){
|
||
if(text.length < collectLength){
|
||
if(ret){
|
||
ret += '\n'
|
||
}
|
||
ret += text
|
||
collectLength -= text.length
|
||
}else{
|
||
if(ret){
|
||
ret += '\n'
|
||
}
|
||
ret += text.substring(0, collectLength) + "..."
|
||
return ret
|
||
}
|
||
}
|
||
i++
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
function addClickEventToImage() {
|
||
// if (rangy.Recognizer.isAndroid) {
|
||
util.addListener(body, 'click', function (event) {
|
||
var target = event.target
|
||
if (target.tagName.toLowerCase() === 'img') {
|
||
var images = getAllImagesAndTargetIndex(target)
|
||
if (images.targetIndex >= 0) {
|
||
wereadBridge.execMPReaderMethod('MPReader', {
|
||
cmd: 'didTapImage',
|
||
images: images.allImages,
|
||
index: images.targetIndex
|
||
});
|
||
}
|
||
}
|
||
})
|
||
// } else {
|
||
// // click delegate 在 ios 11.4.1 会造成滚动延迟和菜单弹出延迟
|
||
// // 且 click delegate 无法在 ios 的 document 或者 body 上生效
|
||
// // 如果直接绑定点击事件,则无法选择图片
|
||
// var imgs = document.getElementsByTagName('img')
|
||
// for (var i = 0; i < imgs.length; i++) {
|
||
// if (!isHidden(imgs[i])) {
|
||
// // 如果mp 支持了tap事件,就不需要我们自定义的了。否则需要自定义。
|
||
// rangy.attachTouch(document, 'tap')
|
||
// util.addListener(imgs[i], 'tap', function (e) {
|
||
// var images = getAllImagesAndTargetIndex(e.target)
|
||
// if (images.targetIndex >= 0) {
|
||
// wereadBridge.execMPReaderMethod('MPReader', {
|
||
// cmd: 'didTapImage',
|
||
// images: images.allImages,
|
||
// index: images.targetIndex
|
||
// });
|
||
// }
|
||
// return false
|
||
// })
|
||
// }
|
||
// }
|
||
// }
|
||
}
|
||
|
||
|
||
function click(el, event) {
|
||
var classNameStr = rangy.ClassApplier.util.getClass(el)
|
||
var classNames = ['highlight']
|
||
|
||
for (var i = 0; i < reviewClassNames.length; i++) {
|
||
if (classNameStr.indexOf(reviewClassNames[i]) >= 0) {
|
||
// 重叠部分,以 review 为主
|
||
classNames = reviewClassNames
|
||
break
|
||
}
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
var range = rangy.createRange(doc);
|
||
range.selectNodeContents(el);
|
||
var characterRange = highlighter.converter.rangeToCharacterRange(range, body)
|
||
var mergedCharacterRange = highlighter.getMergedCharacterRange(characterRange, reviewClassNames, classNames === reviewClassNames)
|
||
var isClickReview = false
|
||
if (mergedCharacterRange != null) {
|
||
isClickReview = true
|
||
} else {
|
||
mergedCharacterRange = highlighter.getMergedCharacterRange(characterRange, ['highlight'], true)
|
||
}
|
||
if (mergedCharacterRange == null) {
|
||
return false
|
||
}
|
||
var rect = getCharacterRangeRectInfo(mergedCharacterRange)
|
||
if (rect == null) {
|
||
return false
|
||
}
|
||
var result = rangy.util.extend({
|
||
cmd: !isClickReview ? 'didTapHighlight' : 'didTapReview',
|
||
start: mergedCharacterRange.start,
|
||
end: mergedCharacterRange.end,
|
||
content: getContentByPos(mergedCharacterRange.start, mergedCharacterRange.end)
|
||
}, rect);
|
||
wereadBridge.execMPReaderMethod('MPReader', result);
|
||
return true
|
||
}
|
||
|
||
function getRectInfoByPos(start, end) {
|
||
return getInfoByPos(start, end, getCharacterRangeRectInfo)
|
||
}
|
||
|
||
function getTopByPos(start, end) {
|
||
return getInfoByPos(start, end, getCharacterRangeTop)
|
||
}
|
||
|
||
function getInfoByPos(start, end, func) {
|
||
if (start === end) {
|
||
return null
|
||
}
|
||
if (start > end) {
|
||
var tmp = start
|
||
start = end
|
||
end = tmp
|
||
}
|
||
var chapterType = highlighter.createCharacterRange(start, end)
|
||
return func.call(null, chapterType)
|
||
}
|
||
|
||
|
||
function getCharacterRangeTop(chapterRange) {
|
||
if (!chapterRange || chapterRange.end <= chapterRange.start) {
|
||
return -1
|
||
}
|
||
return highlighter.converter.characterRangeToRange(document, chapterRange, body).nativeRange.getBoundingClientRect().top
|
||
}
|
||
|
||
function getCharacterRangeRectInfo(chapterRange) {
|
||
if (!chapterRange || chapterRange.end <= chapterRange.start) {
|
||
return null
|
||
}
|
||
var testRange = rangy.createRange(doc);
|
||
testRange.selectCharacters(body, chapterRange.start, chapterRange.start + 1);
|
||
var firstRect = testRange.nativeRange.getBoundingClientRect()
|
||
var lastRect = firstRect
|
||
if (chapterRange.end > chapterRange.start + 1) {
|
||
testRange.selectCharacters(body, chapterRange.end - 1, chapterRange.end)
|
||
lastRect = testRange.nativeRange.getBoundingClientRect()
|
||
}
|
||
var rangeRect = highlighter.converter.characterRangeToRange(document, chapterRange, body).nativeRange.getBoundingClientRect()
|
||
return {
|
||
startX: firstRect.left,
|
||
startY: firstRect.top,
|
||
endX: lastRect.right,
|
||
endY: lastRect.bottom,
|
||
minLeft: rangeRect.left,
|
||
maxRight: rangeRect.right,
|
||
singleLine: firstRect.top === lastRect.top
|
||
}
|
||
}
|
||
|
||
function getSelectionRectInfo() {
|
||
var selection = rangy.getSelection()
|
||
var nativeSelection = window.getSelection()
|
||
var ranges = selection.getAllRanges()
|
||
if (nativeSelection.rangeCount && !nativeSelection.isCollapsed) {
|
||
var doc = document
|
||
var body = rangy.util.getBody(doc)
|
||
var result = {
|
||
startX: 0,
|
||
startY: 0,
|
||
endX: 0,
|
||
endY: 0,
|
||
minLeft: Number.MAX_VALUE,
|
||
maxRight: 0,
|
||
singleLine: true
|
||
}
|
||
var firstTextPos = Number.MAX_VALUE, lastTextPos = 0
|
||
rangy.util.forEach(ranges, function (range) {
|
||
var chapterRange = highlighter.converter.rangeToCharacterRange(range, body)
|
||
firstTextPos = Math.min(chapterRange.start, firstTextPos)
|
||
lastTextPos = Math.max(lastTextPos, chapterRange.end)
|
||
var rect = range.nativeRange.getBoundingClientRect()
|
||
result.minLeft = Math.min(result.minLeft, rect.left)
|
||
result.maxRight = Math.max(result.maxRight, rect.right)
|
||
result.startY = result.startY === 0 ? rect.top : Math.min(result.startY, rect.top)
|
||
result.endY = result.endY === 0 ? rect.bottom : Math.max(result.endY, rect.bottom)
|
||
})
|
||
if (lastTextPos < firstTextPos) {
|
||
return null
|
||
} else if (lastTextPos === firstTextPos) {
|
||
var text = getSelectedText(false, true)
|
||
if (text.length > 0) {
|
||
// image
|
||
result.startX = result.minLeft
|
||
result.endX = result.maxRight
|
||
return result
|
||
}
|
||
return null
|
||
}
|
||
var testRange = rangy.createRange(doc);
|
||
testRange.selectCharacters(body, firstTextPos, firstTextPos + 1);
|
||
var firstRect = testRange.nativeRange.getBoundingClientRect()
|
||
var lastRect = firstRect
|
||
if (lastTextPos > firstTextPos + 1) {
|
||
testRange.selectCharacters(body, lastTextPos - 1, lastTextPos)
|
||
lastRect = testRange.nativeRange.getBoundingClientRect()
|
||
}
|
||
result.startX = firstRect.left
|
||
result.startY = firstRect.top
|
||
result.endX = lastRect.right
|
||
result.endY = lastRect.bottom
|
||
result.singleLine = firstRect.top === lastRect.top
|
||
return result
|
||
}
|
||
return null
|
||
}
|
||
|
||
// 初始化划线及高亮方法
|
||
function initAnnotations(items) {
|
||
rangy.load(function () {
|
||
var scrollTop = -1
|
||
removeAllAnnotations();
|
||
var serializedHighlights = "type:TextRange";
|
||
items = JSON.parse(items);
|
||
for (var i = 0; i < items.length; i++) {
|
||
|
||
var className
|
||
if (items[i].type === 'highlight') {
|
||
if (items[i].isReference) {
|
||
className = 'reference'
|
||
|
||
items.push({
|
||
type: 'highlight',
|
||
isReference: false,
|
||
isMySelf: items[i].isMySelf,
|
||
start: items[i].start,
|
||
end: items[i].end
|
||
})
|
||
} else {
|
||
className = 'highlight'
|
||
}
|
||
|
||
if (items[i].needScrollTo) {
|
||
scrollTop = getTopByPos(items[i].start, items[i].end)
|
||
}
|
||
} else if (items[i].type === 'review') {
|
||
if (items[i].isReference) {
|
||
className = 'reference'
|
||
scrollTop = getTopByPos(items[i].start, items[i].end)
|
||
items.push({
|
||
type: 'review',
|
||
isReference: false,
|
||
isMySelf: items[i].isMySelf,
|
||
start: items[i].start,
|
||
end: items[i].end
|
||
})
|
||
} else if (items[i].isMySelf) {
|
||
className = 'review'
|
||
} else {
|
||
className = 'friend-review'
|
||
}
|
||
|
||
if (items[i].needScrollTo) {
|
||
scrollTop = getTopByPos(items[i].start, items[i].end)
|
||
}
|
||
} else {
|
||
if (items[i].isReference) {
|
||
className = 'reference'
|
||
}
|
||
if (items[i].needScrollTo) {
|
||
scrollTop = getTopByPos(items[i].start, items[i].end)
|
||
}
|
||
}
|
||
|
||
if (className) {
|
||
serializedHighlights = serializedHighlights + '|' + items[i].start + '$' + items[i].end + '$' + (i + 1) + '$' + className;
|
||
}
|
||
}
|
||
if (scrollTop > 0) {
|
||
wereadBridge.execMPReaderMethod('MPReader', {
|
||
cmd: 'scrollToOffsetTop',
|
||
top: scrollTop
|
||
});
|
||
setTimeout(function () {
|
||
highlighter.deserialize(serializedHighlights);
|
||
}, 100)
|
||
} else {
|
||
highlighter.deserialize(serializedHighlights);
|
||
}
|
||
})
|
||
}
|
||
|
||
// 新增划线
|
||
function didTapCreateHighlightButton() {
|
||
var newHighlights = highlighter.highlightSelection("highlight", {
|
||
exclusive: false
|
||
});
|
||
if (newHighlights.length === 0) {
|
||
return null
|
||
}
|
||
var highlight = newHighlights[newHighlights.length - 1]
|
||
var result = getStartEndContentFromHighlight(highlight)
|
||
// 去除聚焦
|
||
removeSelection();
|
||
return result;
|
||
}
|
||
|
||
// 对指定位置新增划线
|
||
function addNewUnderLineByPosition(start, end) {
|
||
var result = null;
|
||
if (start >= 0 && end >= 0) {
|
||
var range = rangy.createRange(doc)
|
||
range.selectCharacters(body, start, end)
|
||
highlighter.highlightRanges("highlight", [range], {
|
||
exclusive: false
|
||
})
|
||
var characterRange = highlighter.converter.rangeToCharacterRange(range, body)
|
||
result = {
|
||
start: characterRange.start,
|
||
end: characterRange.end,
|
||
content: range.text()
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// 新增想法
|
||
function didTapCreateReviewButton(start, end) {
|
||
var selection = rangy.getSelection()
|
||
var hasSelection = selection.toString().length > 0
|
||
var result = null
|
||
if (hasSelection) {
|
||
highlighter.highlightSelection("review", {
|
||
exclusive: false
|
||
})
|
||
result = getStartEndContentFromRanges(selection.getAllRanges())
|
||
} else if (start >= 0 && end >= 0) {
|
||
var range = rangy.createRange(doc)
|
||
range.selectCharacters(body, start, end)
|
||
highlighter.highlightRanges("review", [range], {
|
||
exclusive: false
|
||
})
|
||
var characterRange = highlighter.converter.rangeToCharacterRange(range, body)
|
||
result = {
|
||
start: characterRange.start,
|
||
end: characterRange.end,
|
||
content: range.text()
|
||
}
|
||
} else {
|
||
return null
|
||
}
|
||
// 去除聚焦
|
||
removeSelection();
|
||
return result;
|
||
}
|
||
|
||
function getStartEndContentFromHighlight(highlight, includeImages) {
|
||
var characterRange = highlight.characterRange
|
||
return {
|
||
start: characterRange.start,
|
||
end: characterRange.end,
|
||
content: highlighter.converter.characterRangeToRange(doc, characterRange, body).text({
|
||
includeImages: includeImages
|
||
})
|
||
};
|
||
}
|
||
|
||
function getStartEndContentFromRanges(ranges, includeImages) {
|
||
var start = -1, end = -1, text = "", characterRange
|
||
util.forEach(ranges, function (range) {
|
||
characterRange = highlighter.converter.rangeToCharacterRange(range, body)
|
||
if (start < 0) {
|
||
start = characterRange.start
|
||
} else {
|
||
start = Math.min(start, characterRange.start)
|
||
}
|
||
end = Math.max(start, end, characterRange.end)
|
||
text += range.text({
|
||
includeImages: includeImages
|
||
})
|
||
})
|
||
return {
|
||
start: start,
|
||
end: end,
|
||
content: text
|
||
};
|
||
}
|
||
|
||
// 需要判断是否是删除划线
|
||
function getRectInfoInSelection() {
|
||
var rectInfo = getSelectionRectInfo(), characterRange
|
||
if (rectInfo == null) {
|
||
return null
|
||
}
|
||
var classNames = ['highlight']
|
||
var highlight = highlighter.highghtWhichContainSelection(classNames)
|
||
var start = -1, end = -1, isRemove = false
|
||
if (highlight != null) {
|
||
isRemove = true
|
||
characterRange = highlighter.getMergedCharacterRange(highlight.characterRange, classNames, true)
|
||
start = characterRange.start
|
||
end = characterRange.end
|
||
} else {
|
||
var selection = rangy.getSelection()
|
||
var ranges = selection.getAllRanges()
|
||
for (var i = 0; i < ranges.length; i++) {
|
||
characterRange = highlighter.converter.rangeToCharacterRange(ranges[i], body)
|
||
if (start < 0) {
|
||
start = characterRange.start
|
||
} else {
|
||
start = Math.min(characterRange.start, start)
|
||
}
|
||
end = Math.max(start, end, characterRange.end)
|
||
}
|
||
}
|
||
|
||
return rangy.util.extend({
|
||
start: start,
|
||
end: end,
|
||
isRemove: isRemove,
|
||
content: getSelectedText(false)
|
||
}, rectInfo);
|
||
}
|
||
|
||
// 删除所有想法 / 划线
|
||
function removeAllAnnotations() {
|
||
highlighter.removeAllHighlights();
|
||
}
|
||
|
||
// 删除选中的划线
|
||
function removeAnnotationFromSelectedText() {
|
||
highlighter.unhighlightSelection();
|
||
}
|
||
|
||
// 删除划线 / 想法
|
||
function didTapDeleteAnnotationButton(start, end, type) {
|
||
var allHighlights = highlighter.highlights, highlight
|
||
var removeHighlights = []
|
||
|
||
for (var i = 0; i < allHighlights.length; i++) {
|
||
highlight = allHighlights[i]
|
||
if (type === 'highlight') {
|
||
if (highlight.classApplier.className !== 'highlight') {
|
||
continue
|
||
}
|
||
} else if (type === 'review') {
|
||
if (highlight.classApplier.className !== 'review') {
|
||
continue
|
||
}
|
||
if (reviewClassNames.indexOf(highlight.classApplier.className) < 0) {
|
||
continue
|
||
}
|
||
}
|
||
if (highlight.characterRange.start >= start && highlight.characterRange.end <= end) {
|
||
removeHighlights.push(highlight)
|
||
|
||
}
|
||
}
|
||
if (removeHighlights.length > 0) {
|
||
highlighter.removeHighlights(removeHighlights)
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// 重新加载页面
|
||
function reloadPage(button) {
|
||
button.form.elements["serializedHighlights"].value = highlighter.serialize();
|
||
button.form.submit();
|
||
}
|
||
|
||
function removeSelection() {
|
||
rangy.getSelection().removeAllRanges()
|
||
}
|
||
|
||
function getAllForwardLinks() {
|
||
var arr = [], l = document.links;
|
||
for (var i = 0; i < l.length; i++) {
|
||
/*
|
||
某些公众号的链接通过 data-url 复制,href 是 javascript:; 的。而 iOS
|
||
的拦截是通过捕捉所有 link 跳转,所以需要下面这一句
|
||
*/
|
||
if (l[i].getAttribute('data-url')) {
|
||
l[i].href = l[i].getAttribute("data-url");
|
||
}
|
||
var href = l[i].href;
|
||
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
|
||
var ampRegEx = /&/g;
|
||
href = href.replace(ampRegEx, '&');
|
||
arr.push(href);
|
||
}
|
||
}
|
||
return arr;
|
||
}
|
||
|
||
function getAllImagesAndTargetIndex(target) {
|
||
var images = document.getElementsByTagName('img')
|
||
var result = [], targetIndex = -1
|
||
for (var i = 0; i < images.length; i++) {
|
||
|
||
var parent = images[i].parentNode
|
||
while (parent.nodeName != "A" && parent.nodeName != "BODY" && parent.nodeName != "HTML") {
|
||
parent = parent.parentNode;
|
||
}
|
||
|
||
// 小程序、跳转链接的图片不受响应
|
||
var invalid = parent.nodeName == "A" &&
|
||
(parent.className.indexOf('h5_image_link') > -1 ||
|
||
parent.className.indexOf('weapp_image_link') > -1 ||
|
||
parent.getAttribute('data-weread-bookid') != null ||
|
||
parent.getAttribute('data-weread-reviewid') != null);
|
||
|
||
var info = getImageInfo(images[i])
|
||
if (info != null && !invalid) {
|
||
if (target === images[i]) {
|
||
targetIndex = result.length
|
||
}
|
||
result.push(info)
|
||
}
|
||
}
|
||
return {
|
||
allImages: result,
|
||
targetIndex: targetIndex
|
||
}
|
||
}
|
||
|
||
function getAllImages() {
|
||
var images = document.getElementsByTagName('img')
|
||
var result = []
|
||
for (var i = 0; i < images.length; i++) {
|
||
var info = getImageInfo(images[i])
|
||
if (info) {
|
||
result.push(info)
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
function getImageInfo(image) {
|
||
if (!isHidden(image)) {
|
||
var src = image.getAttribute("data-src")
|
||
if (!src || !src.startsWith("http")) {
|
||
src = image.getAttribute("src")
|
||
if (!src || !src.startsWith("http")) {
|
||
src = image.src
|
||
}
|
||
}
|
||
if (src) {
|
||
var rect = image.getBoundingClientRect()
|
||
return {
|
||
src: src,
|
||
x: rect.left,
|
||
y: rect.top,
|
||
width: rect.width,
|
||
height: rect.height
|
||
}
|
||
}
|
||
}
|
||
return null
|
||
}
|
||
|
||
function getContentByPos(start, end, shouldRemoveSelection, includeImages) {
|
||
if (start >= 0 && end >= 0) {
|
||
var range = rangy.createRange(doc)
|
||
range.selectCharacters(body, start, end)
|
||
return range.text({
|
||
includeImages: includeImages
|
||
})
|
||
} else {
|
||
return getSelectedText(shouldRemoveSelection, includeImages)
|
||
}
|
||
}
|
||
|
||
function getSelectedText(shouldRemoveSelection, includeImages) {
|
||
var selection = rangy.getSelection()
|
||
var ranges = selection.getAllRanges()
|
||
var text = ""
|
||
if (ranges.length > 0) {
|
||
util.forEach(ranges, function (range) {
|
||
text += range.text({
|
||
includeImages: includeImages
|
||
})
|
||
})
|
||
}
|
||
if (shouldRemoveSelection) {
|
||
selection.removeAllRanges()
|
||
}
|
||
return text
|
||
}
|
||
|
||
// sample:https://weread.qq.com/web/appreader/6bb324405e15d46bb6d43d0?wtype=mpArticle
|
||
// 将以上的 link 在客户端转化为 data-weread-bookId 和 data-weread-chapterUid
|
||
function transferH5Link() {
|
||
var detectReader = 'weread.qq.com/web/appreader'
|
||
var detectLecture = 'weread.qq.com/wrpage/book/lecture'
|
||
var links = document.getElementsByTagName('a')
|
||
|
||
for (var i = 0; i < links.length; i++) {
|
||
var link = links[i]
|
||
if (link.href.indexOf(detectReader) > -1 || link.href.indexOf(detectLecture) > -1) {
|
||
var encryptString = link.href.split('/').slice(-1)[0]
|
||
if (encryptString) {
|
||
// remove params
|
||
encryptString = encryptString.split('?')[0]
|
||
|
||
// rule:web_encrypt(bookId) + 'k' + web_encrypt(chapterUid)
|
||
var encryptAry = encryptString.split('k')
|
||
var bookId, chapterUid, reviewId;
|
||
|
||
if (encryptAry[0]) {
|
||
bookId = WRLink_decrypt(encryptAry[0])
|
||
link.setAttribute('data-weread-bookid', bookId)
|
||
}
|
||
|
||
if (encryptAry[1]) {
|
||
chapterUid = WRLink_decrypt(encryptAry[1])
|
||
link.setAttribute('data-weread-chapteruid', chapterUid)
|
||
}
|
||
|
||
if (link.href.indexOf(detectLecture) > -1) {
|
||
reviewId = encryptString;
|
||
link.setAttribute('data-weread-reviewid', reviewId)
|
||
}
|
||
|
||
if (bookId.length || reviewId.length) {
|
||
// 删掉 href 以致于让 App 不显示微信自带的”即将打开新页面“
|
||
link.removeAttribute('href');
|
||
|
||
link.addEventListener('touchend', function (e) {
|
||
var bookId = this.getAttribute('data-weread-bookid') ? this.getAttribute('data-weread-bookid') : '';
|
||
var chapterUid = this.getAttribute('data-weread-chapteruid') ? this.getAttribute('data-weread-chapteruid') : '';
|
||
var reviewId = this.getAttribute('data-weread-reviewid') ? this.getAttribute('data-weread-reviewid') : '';
|
||
if (bookId.length || reviewId.length) {
|
||
wereadBridge.execMPReaderMethod('MPReader', {
|
||
cmd: 'didTapBook',
|
||
bookId: bookId,
|
||
chapterUid: chapterUid,
|
||
reviewId: reviewId,
|
||
});
|
||
}
|
||
}, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// copy by https://git.code.oa.com/WeRead/wrencryption/blob/master/src/index.js
|
||
// need check update
|
||
function WRLink_decrypt (str) {
|
||
var md5Length = 3;
|
||
if (typeof str !== 'string' || str.length <= md5Length) return '';
|
||
var type = str.charAt(3);
|
||
var randomLen = parseInt(str.charAt(4));
|
||
var firstContentPartIdx = 5 + randomLen;
|
||
var contentMaxIdx = str.length - 3; // 后面3位是 md5
|
||
var s = '';
|
||
var idx = firstContentPartIdx;
|
||
while (idx < contentMaxIdx && (idx === firstContentPartIdx || str.charAt(idx) === 'g')) {
|
||
if (idx !== firstContentPartIdx) {
|
||
idx++; // 跳过 'g'
|
||
}
|
||
if (idx + 2 > contentMaxIdx) {
|
||
return '';
|
||
}
|
||
var contentLength = parseInt(str.substr(idx, 2), 16);
|
||
if (idx + 2 + contentLength > contentMaxIdx) {
|
||
return '';
|
||
}
|
||
var substring = str.substr(idx + 2, contentLength);
|
||
var decodeResult = '';
|
||
if (type === '3') {
|
||
var number = parseInt(substring, 16);
|
||
if (isNaN(number)) {
|
||
return '';
|
||
}
|
||
decodeResult = '' + number;
|
||
} else {
|
||
for (var i = 0, iLen = substring.length; i < iLen; i += 2) {
|
||
var charCode = parseInt(substring.substr(i, 2), 16);
|
||
decodeResult += String.fromCharCode(charCode);
|
||
}
|
||
}
|
||
s += decodeResult;
|
||
|
||
idx = idx + 2 + contentLength;
|
||
}
|
||
return s;
|
||
}
|
||
|
||
// ================================ TTS ===================================================
|
||
|
||
var lastTTSInfo = {
|
||
lastTTSRange: null,
|
||
lastTTSHighlightInfo: null,
|
||
lastIndex: -1,
|
||
lastText: "",
|
||
lastTop: -1,
|
||
lastBottom: -1
|
||
}
|
||
var passTags = ['body', 'html', 'code', 'pre']
|
||
var filterTags = ['style', 'script', 'mpvoice', 'iframe', 'br']
|
||
var filterClass = ['js_audio_frame']
|
||
var mergeTags = ['p', 'li', 'a', 'h1', 'h2', 'h3', 'h4', 'h5']
|
||
|
||
function TTSRange() {
|
||
this.range = new rangy.DomRange(document)
|
||
this.matchedString = ""
|
||
this.start = false
|
||
this.lastInfo = null
|
||
}
|
||
|
||
function TTSText() {
|
||
this.result = []
|
||
this.stack = []
|
||
this.restoreIndex = -1
|
||
this.needHandleStart = true
|
||
this.tmpRange = null
|
||
this.getForCurrentIndex = -1
|
||
this.getForCurrentIndexHandled = false
|
||
}
|
||
|
||
/**
|
||
* 获取 TTS 文本内容
|
||
* @param startY
|
||
* @returns {string}
|
||
*/
|
||
function getTTSText(startY) {
|
||
var start = new Date - 0
|
||
var util = rangy.util
|
||
var body = util.getBody(document)
|
||
var ttsText = new TTSText()
|
||
handleTTSTextGet(body, startY, ttsText)
|
||
var handleResult = []
|
||
var startPos = 0
|
||
for (var i = 0; i < ttsText.result.length; i++) {
|
||
if (ttsText.result[i]) {
|
||
handleResult.push({index: i, text: ttsText.result[i], startPos: startPos})
|
||
startPos += ttsText.result[i].length
|
||
}
|
||
}
|
||
console.log("getTTSText speed: " + (new Date - start))
|
||
return handleResult
|
||
}
|
||
|
||
/**
|
||
* 获取 currentY 位置文本的 index 以及 pos
|
||
* @param startY
|
||
* @param currentY
|
||
* @returns {*}
|
||
*/
|
||
function getCurrentIndex(startY, currentY) {
|
||
if(currentY <= startY){
|
||
return {
|
||
index: 0,
|
||
pos: 0
|
||
}
|
||
}
|
||
|
||
var util = rangy.util
|
||
var body = util.getBody(document)
|
||
var ttsText = new TTSText()
|
||
ttsText.getForCurrentIndex = currentY
|
||
handleTTSTextGet(body, startY, ttsText)
|
||
var result = ttsText.result
|
||
if (result.length > 0) {
|
||
return {
|
||
index: result.length - 1,
|
||
pos: result[result.length - 1].length
|
||
}
|
||
} else {
|
||
return {
|
||
index: 0,
|
||
pos: 0
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* 恢复 TTS 播放的上下文记录
|
||
* @param startY
|
||
* @param restoreIndex
|
||
*/
|
||
function restoreTTSContext(startY, restoreIndex) {
|
||
var ttsText = new TTSText()
|
||
ttsText.restoreIndex = restoreIndex
|
||
handleTTSTextGet(body, startY, ttsText)
|
||
}
|
||
|
||
/**
|
||
* el 过滤
|
||
* @param el
|
||
* @returns {boolean}
|
||
*/
|
||
function filter(el) {
|
||
var tagName = el.nodeName.toLowerCase()
|
||
if (passTags.indexOf(tagName) >= 0) {
|
||
return false
|
||
}
|
||
if (filterTags.indexOf(tagName) >= 0) {
|
||
return true
|
||
}
|
||
|
||
if (el.getAttribute('wr-ignore-no-content')) {
|
||
return true
|
||
}
|
||
|
||
if (isNodeHidden(el)) {
|
||
return true
|
||
}
|
||
|
||
var parent = el.parentNode
|
||
if (parent == null) {
|
||
return true
|
||
}
|
||
|
||
if (tagName === 'p' && (parseInt(getStyle(el, 'text-indent'), 10) || 0) < -3000) {
|
||
el.setAttribute('wr-ignore-no-content', true)
|
||
return true
|
||
}
|
||
|
||
var offsetParent = el.offsetParent
|
||
var offsetWidth = el.offsetWidth
|
||
var offsetLeft = el.offsetLeft
|
||
var parentOffsetWidth = parent.offsetWidth
|
||
if (offsetParent == null) {
|
||
return true
|
||
}
|
||
if (parentOffsetWidth <= 0 || offsetWidth == 0) {
|
||
// offsetWidth 为 0,可能是离线 Webview, 如果检测?
|
||
// 如果 display 为 inline, 然后包裹其它元素,offsetWidth 可能会出错
|
||
return false
|
||
}
|
||
|
||
if ((offsetWidth != 0 && offsetLeft + offsetWidth < 0) || (offsetParent.offsetWidth != 0 && offsetLeft > offsetParent.offsetWidth)) {
|
||
el.setAttribute('wr-ignore-no-content', true)
|
||
return true
|
||
}
|
||
|
||
var classUtil = rangy.ClassApplier.util
|
||
for (var i = 0; i < filterClass.length; i++) {
|
||
if (classUtil.hasClass(el, filterClass[i])) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
function handleTTSTextGet(el, startY, ttsText) {
|
||
if (el.nodeType === 1) {
|
||
var tagName = el.nodeName.toLowerCase()
|
||
if (ttsText.needHandleStart) {
|
||
var rect = el.getBoundingClientRect()
|
||
if (rect.bottom <= startY && tagName !== 'body') {
|
||
// ignore
|
||
return
|
||
}
|
||
if (rect.top >= startY) {
|
||
ttsText.needHandleStart = false
|
||
}
|
||
}
|
||
|
||
if (filter(el)) {
|
||
return
|
||
}
|
||
|
||
var child, lastIndex;
|
||
if (mergeTags.indexOf(tagName) >= 0) {
|
||
var isInMergeTag = false
|
||
if (!ttsText.stack.length) {
|
||
ttsText.stack.push(tagName)
|
||
ttsText.result.push('')
|
||
}else{
|
||
isInMergeTag = true
|
||
}
|
||
child = el.firstChild
|
||
while (child) {
|
||
handleTTSTextGet(child, startY, ttsText)
|
||
if (ttsText.getForCurrentIndexHandled) {
|
||
break
|
||
}
|
||
child = child.nextSibling
|
||
}
|
||
if(!isInMergeTag){
|
||
ttsText.stack.pop()
|
||
lastIndex = ttsText.result.length - 1
|
||
ttsText.result[lastIndex] = ttsText.result[lastIndex].trim()
|
||
if (ttsText.getForCurrentIndexHandled) {
|
||
return
|
||
}
|
||
if (ttsText.result[lastIndex] === '') {
|
||
ttsText.result.pop()
|
||
el.setAttribute('wr-ignore-no-content', true)
|
||
} else {
|
||
if (ttsText.result.length === ttsText.restoreIndex) {
|
||
lastTTSInfo.lastTTSRange = rangy.createRange(doc)
|
||
lastTTSInfo.lastTTSRange.selectNodeContents(el)
|
||
lastTTSInfo.lastTTSRange = highlighter.converter.rangeToCharacterRange(lastTTSInfo.lastTTSRange, body)
|
||
}
|
||
}
|
||
}
|
||
|
||
} else {
|
||
child = el.firstChild
|
||
while (child) {
|
||
handleTTSTextGet(child, startY, ttsText)
|
||
if (ttsText.getForCurrentIndexHandled) {
|
||
return
|
||
}
|
||
child = child.nextSibling
|
||
}
|
||
}
|
||
} else if (el.nodeType === 3) {
|
||
if (!emptyTextNode(el)) {
|
||
if (!ttsText.needHandleStart) {
|
||
if(ttsText.getForCurrentIndex > startY && !ttsText.getForCurrentIndexHandled){
|
||
var range = ttsText.tmpRange
|
||
if (!range) {
|
||
range = ttsText.tmpRange = document.createRange()
|
||
}
|
||
range.selectNodeContents(el)
|
||
rect = range.getBoundingClientRect()
|
||
handleGetForCurrentIndex(ttsText, rect, el, range)
|
||
if(ttsText.getForCurrentIndexHandled){
|
||
return
|
||
}
|
||
}
|
||
collectText(el, ttsText, 0)
|
||
} else {
|
||
var range = ttsText.tmpRange
|
||
if (!range) {
|
||
range = ttsText.tmpRange = document.createRange()
|
||
}
|
||
range.selectNodeContents(el)
|
||
rect = range.getBoundingClientRect()
|
||
if (rect.bottom <= startY) {
|
||
// ignore
|
||
return
|
||
}
|
||
if (rect.top >= startY) {
|
||
ttsText.needHandleStart = false
|
||
collectText(el, ttsText, 0)
|
||
handleGetForCurrentIndex(ttsText, rect, el, range)
|
||
if(ttsText.getForCurrentIndexHandled){
|
||
return
|
||
}
|
||
} else {
|
||
handleGetForCurrentIndex(ttsText, rect, el, range)
|
||
if(ttsText.getForCurrentIndexHandled){
|
||
return
|
||
}
|
||
var start = 0, end = el.data.length - 1
|
||
var sliceIndex = sliceTextNode(el, range, startY, start, end, rect.top, rect.bottom)
|
||
if (sliceIndex + 1 <= end) {
|
||
ttsText.needHandleStart = false
|
||
collectText(el, ttsText, sliceIndex + 1)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function handleGetForCurrentIndex(ttsText, rect, el, range) {
|
||
if (ttsText.getForCurrentIndex > 0) {
|
||
if (rect.top > ttsText.getForCurrentIndex) {
|
||
if (!ttsText.stack.length) {
|
||
ttsText.result.push("")
|
||
}
|
||
ttsText.getForCurrentIndexHandled = true
|
||
} else if (rect.bottom > ttsText.getForCurrentIndex) {
|
||
var start = 0, end = el.data.length - 1
|
||
var slice = sliceTextNode(el, range, ttsText.getForCurrentIndex, start, end, rect.top, rect.bottom)
|
||
var data = slice > 0 ? el.data.substring(0, slice) : el.data
|
||
if (!ttsText.stack.length) {
|
||
ttsText.result.push(data)
|
||
} else {
|
||
var lastI = ttsText.result.length - 1
|
||
ttsText.result[lastI] = ttsText.result[lastI] + data
|
||
}
|
||
ttsText.getForCurrentIndexHandled = true
|
||
}
|
||
}
|
||
}
|
||
|
||
function sliceTextNode(el, range, startY, start, end) {
|
||
if (end - start <= 1) {
|
||
return end
|
||
}
|
||
var half = Math.floor((end + start) / 2), rect
|
||
range.setStart(el, half)
|
||
range.setEnd(el, half + 1)
|
||
rect = range.getBoundingClientRect()
|
||
if (rect.top >= startY) {
|
||
return sliceTextNode(el, range, startY, 0, half - 1)
|
||
} else {
|
||
return sliceTextNode(el, range, startY, half, end)
|
||
}
|
||
}
|
||
|
||
function collectText(el, ttsText, sliceIndex) {
|
||
var data = sliceIndex > 0 ? el.data.substring(sliceIndex) : el.data
|
||
if (ttsText.stack.length) {
|
||
var lastIndex = ttsText.result.length - 1
|
||
ttsText.result[lastIndex] = ttsText.result[lastIndex] + data
|
||
} else {
|
||
ttsText.result.push(data.trim())
|
||
if (ttsText.result.length === ttsText.restoreIndex) {
|
||
lastTTSInfo.lastTTSRange = rangy.createRange(doc)
|
||
lastTTSInfo.lastTTSRange.selectNodeContents(el)
|
||
lastTTSInfo.lastTTSRange = highlighter.converter.rangeToCharacterRange(lastTTSInfo.lastTTSRange, body)
|
||
}
|
||
}
|
||
}
|
||
|
||
function highlightTTSText(text, index, startY) {
|
||
var start = new Date - 0
|
||
var range = new TTSRange()
|
||
if (lastTTSInfo.lastTTSHighlightInfo != null) {
|
||
// 如果与上次信息相同,直接返回
|
||
if (lastTTSInfo.lastIndex === index && lastTTSInfo.lastText === text) {
|
||
return {
|
||
top: lastTTSInfo.lastTop,
|
||
bottom: lastTTSInfo.lastBottom
|
||
}
|
||
}
|
||
highlighter.removeHighlights(lastTTSInfo.lastTTSHighlightInfo)
|
||
lastTTSInfo.lastTTSHighlightInfo = null
|
||
}
|
||
lastTTSInfo.lastIndex = index
|
||
lastTTSInfo.lastText = text
|
||
if (lastTTSInfo.lastTTSRange == null && index > 0) {
|
||
restoreTTSContext(startY, index)
|
||
}
|
||
|
||
if (lastTTSInfo.lastTTSRange != null) {
|
||
range.lastInfo = highlighter.converter.characterRangeToRange(doc, lastTTSInfo.lastTTSRange, body)
|
||
}
|
||
var remain = handleTTSHighlight(text, body, range)
|
||
if (remain && remain.length) {
|
||
// there no matched text in dom
|
||
return {
|
||
reason: "there no matched text in dom",
|
||
top: -1,
|
||
bottom: -1
|
||
}
|
||
} else {
|
||
// 必须在 highlightRanges 前转换成 ChapterRange,
|
||
// highlightRanges 可能会造成 DOM 更改,使得之前所保存到 dom 节点失效
|
||
lastTTSInfo.lastTTSRange = highlighter.converter.rangeToCharacterRange(range.range, body)
|
||
lastTTSInfo.lastTTSHighlightInfo = highlighter.highlightRanges("tts", [range.range], {
|
||
exclusive: false
|
||
});
|
||
if (!lastTTSInfo.lastTTSHighlightInfo || !lastTTSInfo.lastTTSHighlightInfo.length) {
|
||
return {
|
||
reason: "no lastTTSHighlightInfo",
|
||
top: -1,
|
||
bottom: -1
|
||
}
|
||
}
|
||
var highlightRange = highlighter.converter.characterRangeToRange(doc, lastTTSInfo.lastTTSHighlightInfo[0].characterRange, body)
|
||
var rect = highlightRange.nativeRange.getBoundingClientRect()
|
||
console.log("highlightTTSText speed: " + (new Date - start))
|
||
lastTTSInfo.lastTop = rect.top
|
||
lastTTSInfo.lastBottom = rect.bottom
|
||
return {
|
||
top: rect.top,
|
||
bottom: rect.bottom
|
||
}
|
||
}
|
||
}
|
||
|
||
function handleTTSHighlight(text, el, range) {
|
||
if (text == null || text.length === 0) {
|
||
return null
|
||
}
|
||
|
||
var offset = 0
|
||
|
||
// 快速方案: 从上次匹配位置开始向后匹配高亮区域
|
||
// if (range.lastInfo != null) {
|
||
// var isOrIsAncestorOf = rangy.dom.isOrIsAncestorOf
|
||
// if (!isOrIsAncestorOf(el, range.lastInfo.endContainer)) {
|
||
// return text
|
||
// } else if (el === range.lastInfo.endContainer) {
|
||
// offset = range.lastInfo.endOffset
|
||
// range.lastInfo = null
|
||
// }
|
||
// }
|
||
var remain, index
|
||
if (el.nodeType === 1) {
|
||
if (filter(el)) {
|
||
return text
|
||
}
|
||
var child
|
||
if (offset > 0) {
|
||
if (el.children && el.children.length > offset + 1) {
|
||
child = el.children[offset + 1]
|
||
} else {
|
||
return text
|
||
}
|
||
} else {
|
||
child = el.firstChild
|
||
}
|
||
while (child) {
|
||
text = handleTTSHighlight(text, child, range)
|
||
if (text == null || text.length === 0) {
|
||
return null
|
||
}
|
||
child = child.nextSibling
|
||
}
|
||
return text
|
||
} else if (el.nodeType === 3) {
|
||
var elData = el.data
|
||
if (range.start) {
|
||
if (emptyTextNode(el)) {
|
||
return text
|
||
} else if (text.indexOf(elData) === 0) {
|
||
range.matchedString += elData
|
||
remain = text.substring(elData.length)
|
||
if (remain.length === 0) {
|
||
range.range.setEnd(el, text.length)
|
||
}
|
||
return remain
|
||
} else if (elData.indexOf(text) === 0) {
|
||
range.matchedString += text
|
||
range.range.setEnd(el, text.length)
|
||
return null
|
||
} else {
|
||
// 匹配错误, 重新匹配
|
||
console.log("matchedString = " + range.matchedString + "; remain = " + text)
|
||
range.start = false
|
||
text = range.matchedString + text
|
||
range.matchedString = ""
|
||
// TODO 从错误节点重新处理?
|
||
return handleTTSHighlight(text, el, range)
|
||
}
|
||
} else {
|
||
if (emptyTextNode(el)) {
|
||
return text
|
||
} else if (text.indexOf(elData) === offset) {
|
||
range.matchedString += elData
|
||
range.start = true
|
||
range.range.setStart(el, offset)
|
||
remain = text.substring(elData.length)
|
||
if (remain.length === 0) {
|
||
range.range.setEnd(el, text.length)
|
||
}
|
||
return remain
|
||
} else if ((index = elData.indexOf(text, offset)) >= 0) {
|
||
range.start = true
|
||
range.matchedString += text
|
||
range.range.setStart(el, index)
|
||
range.range.setEnd(el, index + text.length)
|
||
return null
|
||
}
|
||
var matchStart, matchIndex
|
||
matchStart = matchIndex = elData.indexOf(text[0], offset)
|
||
if (matchIndex === -1) {
|
||
return text
|
||
} else {
|
||
var textIndex = 0
|
||
while (true) {
|
||
matchIndex++
|
||
textIndex++
|
||
if (elData.length <= matchIndex || text.length <= textIndex) {
|
||
range.start = true
|
||
range.matchedString += elData.substring(matchStart, matchIndex)
|
||
range.range.setStart(el, matchStart)
|
||
text = text.substring(textIndex)
|
||
if (text.length === 0) {
|
||
range.range.setEnd(el, matchIndex + text.length)
|
||
}
|
||
return text
|
||
} else if (elData[matchIndex] !== text[textIndex]) {
|
||
matchStart = elData.indexOf(text[0], matchStart + 1)
|
||
if (matchStart === -1) {
|
||
return text
|
||
} else {
|
||
matchIndex = matchStart
|
||
textIndex = 0
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return text;
|
||
}
|
||
|
||
function emptyTextNode(el) {
|
||
return !el.data || /^(\s|\t)+$/g.test(el.data)
|
||
}
|
||
|
||
function isNodeHidden(node) {
|
||
if (node.sourceIndex === 0) {
|
||
return true
|
||
}
|
||
// 故事流之后不显示标题,但是为了与之前版本划线兼容,并不能隐藏标题
|
||
return getStyle(node, 'display') === 'none' || rangy.ClassApplier.util.hasClass(node, 'rich_media_title')
|
||
}
|
||
|
||
function isHidden(node) {
|
||
var ancestors = [node];
|
||
while (node.parentNode) {
|
||
ancestors.unshift(node.parentNode);
|
||
node = node.parentNode;
|
||
}
|
||
for (var i = 0, len = ancestors.length; i < len; ++i) {
|
||
if (ancestors[i].nodeType === 1 && isNodeHidden(ancestors[i])) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
function getStyle(node, name) {
|
||
if (window.getComputedStyle) {
|
||
return window.getComputedStyle(node, null)[name]
|
||
} else {
|
||
return node.currentStyle[name]
|
||
}
|
||
}
|
||
|
||
function finishTTSPlay() {
|
||
if (lastTTSInfo.lastTTSHighlightInfo != null) {
|
||
highlighter.removeHighlights(lastTTSInfo.lastTTSHighlightInfo)
|
||
lastTTSInfo.lastTTSHighlightInfo = null
|
||
}
|
||
lastTTSInfo.lastTTSRange = null
|
||
lastTTSInfo.lastIndex = -1
|
||
lastTTSInfo.lastText = ""
|
||
}
|
||
|
||
function debounce(callback, timeout) {
|
||
var timerId = null
|
||
timeout = timeout || 250
|
||
return function () {
|
||
var args = arguments
|
||
var self = this
|
||
if (timerId) {
|
||
clearTimeout(timerId);
|
||
|
||
timerId = null;
|
||
}
|
||
timerId = setTimeout(function () {
|
||
callback.apply(self, args)
|
||
}, timeout)
|
||
}
|
||
}
|
||
|
||
function changeFontSizeWithScale(scale) {
|
||
// iPad iOS13 不支持 -weikit-text-size-adjust,改用自定义方法
|
||
// 注意:传入的 scale 是 0.80 这样的值
|
||
var root = document.getElementById('js_content');
|
||
var setQueue = [];
|
||
var textWalker = document.createTreeWalker(root, 4); // 4 === NodeFilter.SHOW_TEXT
|
||
while (textWalker.nextNode()) {
|
||
var textContainer = textWalker.currentNode.parentNode;
|
||
var oriFontSize = textContainer.getAttribute('data-weread-fontsize');
|
||
var oriLineHeight = textContainer.getAttribute('data-weread-lineheight');
|
||
if (!oriFontSize) {
|
||
oriFontSize = getComputedStyle(textContainer).fontSize;
|
||
textContainer.setAttribute('data-weread-fontsize', oriFontSize);
|
||
}
|
||
if (!oriLineHeight) {
|
||
oriLineHeight = getComputedStyle(textContainer).lineHeight;
|
||
textContainer.setAttribute('data-weread-lineheight', oriLineHeight);
|
||
}
|
||
// 先不要马上设置fontSize,会影响子节点的fontSize,存起来最后统一设置
|
||
setQueue.push([textContainer, oriFontSize, oriLineHeight]);
|
||
}
|
||
setQueue.forEach(function (setPair) {
|
||
setPair[0].style.fontSize = parseFloat(setPair[1]) * scale + 'px';
|
||
setPair[0].style.lineHeight = parseFloat(setPair[2]) * scale + 'px';
|
||
});
|
||
}
|
||
|
||
function findAllBookTitles() {
|
||
var container = document.getElementById("js_content");
|
||
if (!container) return [];
|
||
|
||
var walker = document.createTreeWalker(
|
||
container,
|
||
NodeFilter.SHOW_TEXT,
|
||
null,
|
||
false
|
||
);
|
||
var node;
|
||
var regex = /《[^《》]{1,30}》/g;
|
||
var results = [];
|
||
while ((node = walker.nextNode())) {
|
||
var text = node.nodeValue;
|
||
var match;
|
||
while ((match = regex.exec(text)) !== null) {
|
||
results.push({
|
||
text: match[0],
|
||
});
|
||
}
|
||
}
|
||
return results;
|
||
}
|
||
|
||
function highlightSelectedBookTitles(indexArray) {
|
||
// indexArray: [{nodeIndex, start, end}, ...]
|
||
var container = document.getElementById("js_content");
|
||
if (!container) return;
|
||
|
||
// 清除上一次的高亮
|
||
var prevSpans = document.querySelectorAll('span.blue-clickable');
|
||
prevSpans.forEach(function(span) {
|
||
var parent = span.parentNode;
|
||
while (span.firstChild) {
|
||
parent.insertBefore(span.firstChild, span);
|
||
}
|
||
parent.removeChild(span);
|
||
});
|
||
|
||
// 重新遍历一遍,找到所有文本节点
|
||
var walker = document.createTreeWalker(
|
||
container,
|
||
NodeFilter.SHOW_TEXT,
|
||
null,
|
||
false
|
||
);
|
||
var nodes = [];
|
||
var node;
|
||
while ((node = walker.nextNode())) {
|
||
nodes.push(node);
|
||
}
|
||
|
||
var highlighter = rangy.createHighlighter();
|
||
var applier = rangy.createClassApplier("blue-clickable", {
|
||
tagNames: ["span"],
|
||
elementProperties: {
|
||
onclick: function (event) {
|
||
return clickHighlightBookTitle(this, event)
|
||
}
|
||
},
|
||
normalize: false,
|
||
});
|
||
highlighter.addClassApplier(applier);
|
||
|
||
var regex = /《[^《》]{1,30}》/g;
|
||
const textSet = new Set(indexArray);
|
||
|
||
// 1. 先收集每个 node 里所有需要高亮的区间
|
||
nodes.forEach(function(node) {
|
||
var text = node.nodeValue;
|
||
var match;
|
||
var ranges = [];
|
||
while ((match = regex.exec(text)) !== null) {
|
||
if (textSet.has(match[0])) {
|
||
ranges.push({
|
||
start: match.index,
|
||
end: match.index + match[0].length,
|
||
text: match[0]
|
||
});
|
||
}
|
||
}
|
||
// 2. 按 start 降序排序
|
||
ranges.sort((a, b) => b.start - a.start);
|
||
// 3. 倒序高亮
|
||
ranges.forEach(function(item) {
|
||
var range = rangy.createRange();
|
||
range.setStart(node, item.start);
|
||
range.setEnd(node, item.end);
|
||
highlighter.highlightRanges("blue-clickable", [range]);
|
||
});
|
||
});
|
||
}
|
||
|
||
function clickHighlightBookTitle(el, event) {
|
||
console.log('clickHighlightBookTitle', el, event);
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
var result = {
|
||
cmd: 'didTapHighlightBookTitle',
|
||
content: el.textContent,
|
||
}
|
||
wereadBridge.execMPReaderMethod('MPReader', result);
|
||
return true
|
||
} |