/
Jonecuper
/
fb-reader-for-mozilla
Обзор
Документация
Войти
/
Jonecuper
/
fb-reader-for-mozilla
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/tools.js
244 строки
8 KB
Jonecuper
Initial commit: FB2 Night Reader v2.0
29 май 2026, 23:10
29 май 2026, 23:10
3e0c9d2
Код
Авторство
О чём код?
'use strict'; var FB2_NS = 'http://www.gribuser.ru/xml/fictionbook/2.0'; var XLINK_NS = 'http://www.w3.org/1999/xlink'; var HTML_NS = 'http://www.w3.org/1999/xhtml'; function fb2NsResolver() { return FB2_NS; } function queryElements(doc, query, resultType) { if (!resultType) { resultType = XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE; } return doc.evaluate( '//fb2:' + query, doc.documentElement || doc, fb2NsResolver, resultType, null ); } function querySingle(doc, query) { return doc.evaluate( '//fb2:' + query, doc.documentElement || doc, fb2NsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; } function getXlinkHref(elem) { var href = elem.getAttributeNS(XLINK_NS, 'href'); if (href && href.charAt(0) === '#') { return href.substring(1); } return href; } var FB2Tools = { positionFootnote: function(event) { var SCROLLBAR = 24; var a = event.target; var doc = event.target.ownerDocument; if (a.nodeName !== 'a') return; var note; try { var hrefId = getXlinkHref(a); note = querySingle(doc, "section[@id='" + hrefId + "']"); a.appendChild(note); } catch (e) { note = a.firstChild; while (note && note.nodeName !== 'section') { note = note.nextSibling; } } if (!note) return; var rect = note.getBoundingClientRect(); if (rect.right > window.innerWidth - SCROLLBAR) { note.setAttribute('position_h', 'left'); } if (rect.left < 0) { note.setAttribute('position_h', ''); } if (rect.bottom > window.innerHeight - SCROLLBAR) { note.setAttribute('position_v', 'up'); } if (rect.top < 0) { note.setAttribute('position_v', ''); } }, processImages: function(doc) { var images = queryElements(doc, 'image'); for (var i = 0; i < images.snapshotLength; i++) { try { var img = images.snapshotItem(i); var binId = getXlinkHref(img); if (!binId) continue; var bin = querySingle(doc, "binary[@id='" + binId + "']"); if (!bin) continue; var contentType = bin.getAttribute('content-type') || 'image/png'; var base64data = bin.textContent.trim(); var ximg = doc.createElementNS(HTML_NS, 'img'); ximg.setAttribute('src', 'data:' + contentType + ';base64,' + base64data); ximg.setAttribute('alt', ''); img.parentNode.insertBefore(ximg, img); } catch (e) { console.error('FB2 image error:', e); } } }, processFootnotes: function(doc) { var notes = queryElements(doc, "a[@type='note']"); for (var i = 0; i < notes.snapshotLength; i++) { var note = notes.snapshotItem(i); note.addEventListener('mouseover', FB2Tools.positionFootnote, true); } }, buildTableOfContents: function(doc) { var body = querySingle(doc, "body[@name!='notes' or not(@name)]"); var container = doc.getElementById('contents'); if (!container) return; var tocList = doc.createElementNS(HTML_NS, 'ul'); container.appendChild(tocList); var counter = 1; function walkSections(start, parentUl) { var sections = doc.evaluate( './fb2:section', start, fb2NsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); for (var i = 0; i < sections.snapshotLength; i++) { var section = sections.snapshotItem(i); var titleEl = doc.evaluate( './fb2:title', section, fb2NsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; if (!titleEl) continue; var titleClone = titleEl.cloneNode(true); var kids = doc.evaluate( './/fb2:*', titleClone, fb2NsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); for (var j = 0; j < kids.snapshotLength; j++) { kids.snapshotItem(j).setAttribute('id', ''); } var anchor = doc.createElementNS(HTML_NS, 'a'); anchor.appendChild(titleClone); var span = doc.createElementNS(HTML_NS, 'span'); span.id = 'zz_' + (counter++); titleEl.insertBefore(span, titleEl.firstChild); anchor.setAttribute('href', '#' + span.id); var li = doc.createElementNS(HTML_NS, 'li'); li.appendChild(anchor); parentUl.appendChild(li); var subUl = doc.createElementNS(HTML_NS, 'ul'); li.appendChild(subUl); walkSections(section, subUl); } } if (body) { walkSections(body, tocList); } if (!tocList.hasChildNodes()) { container.parentNode.removeChild(container); } }, processExternalLinks: function(doc) { var links = queryElements(doc, "a[@type!='note' or not(@type)]"); for (var i = 0; i < links.snapshotLength; i++) { var oldLink = links.snapshotItem(i); var href = getXlinkHref(oldLink); var originalHref = oldLink.getAttributeNS(XLINK_NS, 'href'); var newLink = doc.createElementNS(HTML_NS, 'a'); if (originalHref && originalHref.charAt(0) === '#') { var id = originalHref.substring(1); newLink.setAttribute('href', '#' + id); var target = querySingle(doc, "*[@id='" + id + "']"); if (target) { target.setAttribute('id', ''); var anchorSpan = doc.createElementNS(HTML_NS, 'span'); anchorSpan.id = id; target.parentNode.insertBefore(anchorSpan, target); } } else { newLink.setAttribute('href', originalHref || '#'); newLink.setAttribute('target', '_blank'); newLink.setAttribute('rel', 'noopener'); } oldLink.parentNode.insertBefore(newLink, oldLink); while (oldLink.firstChild) { newLink.appendChild(oldLink.firstChild); } } }, setupSettingsListener: function(doc) { var styleEl = doc.getElementById('fb2-settings'); if (!styleEl) { styleEl = doc.createElementNS(HTML_NS, 'style'); styleEl.id = 'fb2-settings'; styleEl.setAttribute('type', 'text/css'); var head = doc.getElementsByTagNameNS(HTML_NS, 'head')[0]; if (head) { head.appendChild(styleEl); } else { var root = doc.documentElement; if (root) root.appendChild(styleEl); } } }, init: function(doc) { doc = doc || document; FB2Tools.setupSettingsListener(doc); FB2Tools.processImages(doc); FB2Tools.processFootnotes(doc); FB2Tools.buildTableOfContents(doc); FB2Tools.processExternalLinks(doc); } };