@internetarchive/bookreader 5.0.0-60 → 5.0.0-61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"plugins/plugin.text_selection.js","mappings":"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","sources":["webpack://@internetarchive/bookreader/./src/util/strings.js","webpack://@internetarchive/bookreader/./src/plugins/plugin.text_selection.js","webpack://@internetarchive/bookreader/./src/util/browserSniffing.js","webpack://@internetarchive/bookreader/./node_modules/core-js/internals/number-parse-float.js","webpack://@internetarchive/bookreader/./node_modules/core-js/internals/string-trim-forced.js","webpack://@internetarchive/bookreader/./node_modules/core-js/modules/es.parse-float.js","webpack://@internetarchive/bookreader/./node_modules/core-js/modules/es.string.trim.js"],"sourcesContent":["/**\n * @typedef {String} StringWithVars\n * A template string with {{foo}} style variables\n * Also supports filters, like {{bookPath|urlencode}} (See APPLY_FILTERS for the\n * supported list of filters)\n **/\n\n/**\n * @param {StringWithVars|String} template\n * @param { {[varName: string]: { toString: () => string} } } vars\n * @param { {[varName: string]: { toString: () => string} } } [overrides]\n */\nexport function applyVariables(template, vars, overrides = {}, possibleFilters = APPLY_FILTERS) {\n return template?.replace(/\\{\\{([^}]*?)\\}\\}/g, ($0, $1) => {\n if (!$1) return $0;\n /** @type {string} */\n const expression = $1;\n const [varName, ...filterNames] = expression.split('|').map(x => x.trim());\n const defined = varName in overrides || varName in vars;\n\n // If it's not defined, don't expand it at all\n if (!defined) return $0;\n\n const value = varName in overrides ? overrides[varName]\n : varName in vars ? vars[varName] : null;\n const filters = filterNames.map(n => possibleFilters[n]);\n return filters.reduce((acc, cur) => cur(acc), value && value.toString());\n });\n}\n\n/** @type { {[filterName: String]:( string => string)} } */\nexport const APPLY_FILTERS = {\n urlencode: encodeURIComponent,\n};\n","//@ts-check\nimport { createSVGPageLayer } from '../BookReader/PageContainer.js';\nimport { isFirefox, isSafari } from '../util/browserSniffing.js';\nimport { applyVariables } from '../util/strings.js';\n/** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */\n/** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */\n\nconst BookReader = /** @type {typeof import('../BookReader').default} */(window.BookReader);\n\nexport const DEFAULT_OPTIONS = {\n enabled: true,\n /** @type {StringWithVars} The URL to fetch the entire DJVU xml. Supports options.vars */\n fullDjvuXmlUrl: null,\n /** @type {StringWithVars} The URL to fetch a single page of the DJVU xml. Supports options.vars. Also has {{pageIndex}} */\n singlePageDjvuXmlUrl: null,\n /** Whether to fetch the XML as a jsonp */\n jsonp: false,\n};\n/** @typedef {typeof DEFAULT_OPTIONS} TextSelectionPluginOptions */\n\n/**\n * @template T\n */\nexport class Cache {\n constructor(maxSize = 10) {\n this.maxSize = maxSize;\n /** @type {T[]} */\n this.entries = [];\n }\n\n /**\n * @param {T} entry\n */\n add(entry) {\n if (this.entries.length >= this.maxSize) {\n this.entries.shift();\n }\n this.entries.push(entry);\n }\n}\n\nexport class TextSelectionPlugin {\n\n constructor(options = DEFAULT_OPTIONS, optionVariables, avoidTspans = isFirefox(), pointerEventsOnParagraph = isSafari()) {\n this.options = options;\n this.optionVariables = optionVariables;\n /**@type {PromiseLike<JQuery<HTMLElement>|undefined>} */\n this.djvuPagesPromise = null;\n // Using text elements instead of tspans for words because Firefox does not allow svg tspan stretch.\n // Tspans are necessary on Chrome because they prevent newline character after every word when copying\n this.svgParagraphElement = \"text\";\n this.svgWordElement = \"tspan\";\n this.insertNewlines = avoidTspans;\n // Safari has a bug where `pointer-events` doesn't work on `<tspans>`. So\n // there we will set `pointer-events: all` on the paragraph element. We don't\n // do this everywhere, because it's a worse experience. Thanks Safari :/\n this.pointerEventsOnParagraph = pointerEventsOnParagraph;\n if (avoidTspans) {\n this.svgParagraphElement = \"g\";\n this.svgWordElement = \"text\";\n }\n\n /** @type {Cache<{index: number, response: any}>} */\n this.pageTextCache = new Cache();\n\n /**\n * Sometimes there are too many words on a page, and the browser becomes near\n * unusable. For now don't render text layer for pages with too many words.\n */\n this.maxWordRendered = 2500;\n }\n\n init() {\n // Only fetch the full djvu xml if the single page url isn't there\n if (this.options.singlePageDjvuXmlUrl) return;\n this.djvuPagesPromise = $.ajax({\n type: \"GET\",\n url: applyVariables(this.options.fullDjvuXmlUrl, this.optionVariables),\n dataType: this.options.jsonp ? \"jsonp\" : \"html\",\n cache: true,\n error: (e) => undefined\n }).then((res) => {\n try {\n const xmlMap = $.parseXML(res);\n return xmlMap && $(xmlMap).find(\"OBJECT\");\n } catch (e) {\n return undefined;\n }\n });\n }\n\n /**\n * @param {number} index\n * @returns {Promise<HTMLElement|undefined>}\n */\n async getPageText(index) {\n if (this.options.singlePageDjvuXmlUrl) {\n const cachedEntry = this.pageTextCache.entries.find(x => x.index == index);\n if (cachedEntry) {\n return cachedEntry.response;\n }\n const res = await $.ajax({\n type: \"GET\",\n url: applyVariables(this.options.singlePageDjvuXmlUrl, this.optionVariables, { pageIndex: index }),\n dataType: this.options.jsonp ? \"jsonp\" : \"html\",\n cache: true,\n error: (e) => undefined,\n });\n try {\n const xmlDoc = $.parseXML(res);\n const result = xmlDoc && $(xmlDoc).find(\"OBJECT\")[0];\n this.pageTextCache.add({ index, response: result });\n return result;\n } catch (e) {\n return undefined;\n }\n } else {\n const XMLpagesArr = await this.djvuPagesPromise;\n if (XMLpagesArr) return XMLpagesArr[index];\n }\n }\n\n /**\n * Intercept copied text to remove any styling applied to it\n * @param {JQuery} $container\n */\n interceptCopy($container) {\n $container[0].addEventListener('copy', (event) => {\n const selection = document.getSelection();\n event.clipboardData.setData('text/plain', selection.toString());\n event.preventDefault();\n });\n }\n\n /**\n * Applies mouse events when in default mode\n * @param {SVGElement} svg\n */\n defaultMode(svg) {\n svg.classList.remove(\"selectingSVG\");\n $(svg).on(\"mousedown.textSelectPluginHandler\", (event) => {\n if (!$(event.target).is(\".BRwordElement\")) return;\n event.stopPropagation();\n svg.classList.add(\"selectingSVG\");\n $(svg).one(\"mouseup.textSelectPluginHandler\", (event) => {\n if (window.getSelection().toString() != \"\") {\n event.stopPropagation();\n $(svg).off(\".textSelectPluginHandler\");\n this.textSelectingMode(svg);\n }\n else svg.classList.remove(\"selectingSVG\");\n });\n });\n }\n\n /**\n * Applies mouse events when in textSelecting mode\n * @param {SVGElement} svg\n */\n textSelectingMode(svg) {\n $(svg).on('mousedown.textSelectPluginHandler', (event) => {\n if (!$(event.target).is(\".BRwordElement\")) {\n if (window.getSelection().toString() != \"\") window.getSelection().removeAllRanges();\n }\n event.stopPropagation();\n });\n $(svg).on('mouseup.textSelectPluginHandler', (event) => {\n event.stopPropagation();\n if (window.getSelection().toString() == \"\") {\n $(svg).off(\".textSelectPluginHandler\");\n this.defaultMode(svg); }\n });\n }\n\n /**\n * Initializes text selection modes if there is an svg on the page\n * @param {JQuery} $container\n */\n stopPageFlip($container) {\n /** @type {JQuery<SVGElement>} */\n const $svg = $container.find('svg.textSelectionSVG');\n if (!$svg.length) return;\n $svg.each((i, s) => this.defaultMode(s));\n this.interceptCopy($container);\n }\n\n /**\n * @param {PageContainer} pageContainer\n */\n async createTextLayer(pageContainer) {\n const pageIndex = pageContainer.page.index;\n const $container = pageContainer.$container;\n const $svgLayers = $container.find('.textSelectionSVG');\n if ($svgLayers.length) return;\n const XMLpage = await this.getPageText(pageIndex);\n if (!XMLpage) return;\n\n const totalWords = $(XMLpage).find(\"WORD\").length;\n if (totalWords > this.maxWordRendered) {\n console.log(`Page ${pageIndex} has too many words (${totalWords} > ${this.maxWordRendered}). Not rendering text layer.`);\n return;\n }\n\n const svg = createSVGPageLayer(pageContainer.page, 'textSelectionSVG');\n $container.append(svg);\n\n $(XMLpage).find(\"PARAGRAPH\").each((i, paragraph) => {\n // Adding text element for each paragraph in the page\n const words = $(paragraph).find(\"WORD\");\n if (!words.length) return;\n const paragSvg = document.createElementNS(\"http://www.w3.org/2000/svg\", this.svgParagraphElement);\n paragSvg.setAttribute(\"class\", \"BRparagElement\");\n if (this.pointerEventsOnParagraph) {\n paragSvg.style.pointerEvents = \"all\";\n }\n\n const wordHeightArr = [];\n\n for (let i = 0; i < words.length; i++) {\n // Adding tspan for each word in paragraph\n const currWord = words[i];\n // eslint-disable-next-line no-unused-vars\n const [left, bottom, right, top] = $(currWord).attr(\"coords\").split(',').map(parseFloat);\n const wordHeight = bottom - top;\n wordHeightArr.push(wordHeight);\n\n const wordTspan = document.createElementNS(\"http://www.w3.org/2000/svg\", this.svgWordElement);\n wordTspan.setAttribute(\"class\", \"BRwordElement\");\n wordTspan.setAttribute(\"x\", left.toString());\n wordTspan.setAttribute(\"y\", bottom.toString());\n wordTspan.setAttribute(\"textLength\", (right - left).toString());\n wordTspan.setAttribute(\"lengthAdjust\", \"spacingAndGlyphs\");\n wordTspan.textContent = currWord.textContent;\n paragSvg.appendChild(wordTspan);\n\n // Adding spaces after words except at the end of the paragraph\n // TODO: assumes left-to-right text\n if (i < words.length - 1) {\n const nextWord = words[i + 1];\n // eslint-disable-next-line no-unused-vars\n const [leftNext, bottomNext, rightNext, topNext] = $(nextWord).attr(\"coords\").split(',').map(parseFloat);\n const spaceTspan = document.createElementNS(\"http://www.w3.org/2000/svg\", this.svgWordElement);\n spaceTspan.setAttribute(\"class\", \"BRwordElement\");\n spaceTspan.setAttribute(\"x\", right.toString());\n spaceTspan.setAttribute(\"y\", bottom.toString());\n if ((leftNext - right) > 0) spaceTspan.setAttribute(\"textLength\", (leftNext - right).toString());\n spaceTspan.setAttribute(\"lengthAdjust\", \"spacingAndGlyphs\");\n spaceTspan.textContent = \" \";\n paragSvg.appendChild(spaceTspan);\n }\n\n // Adds newline at the end of paragraph on Firefox\n if ((i == words.length - 1 && (this.insertNewlines))) {\n paragSvg.appendChild(document.createTextNode(\"\\n\"));\n }\n }\n\n wordHeightArr.sort();\n const paragWordHeight = wordHeightArr[Math.floor(wordHeightArr.length * 0.85)];\n paragSvg.setAttribute(\"font-size\", paragWordHeight.toString());\n svg.appendChild(paragSvg);\n });\n this.stopPageFlip($container);\n }\n}\n\nexport class BookreaderWithTextSelection extends BookReader {\n init() {\n const options = Object.assign({}, DEFAULT_OPTIONS, this.options.plugins.textSelection);\n if (options.enabled) {\n this.textSelectionPlugin = new TextSelectionPlugin(options, this.options.vars);\n // Write this back; this way the plugin is the source of truth, and BR just\n // contains a reference to it.\n this.options.plugins.textSelection = options;\n this.textSelectionPlugin.init();\n }\n super.init();\n }\n\n /**\n * @param {number} index\n */\n _createPageContainer(index) {\n const pageContainer = super._createPageContainer(index);\n // Disable if thumb mode; it's too janky\n // .page can be null for \"pre-cover\" region\n if (this.mode !== this.constModeThumb && pageContainer.page) {\n this.textSelectionPlugin?.createTextLayer(pageContainer);\n }\n return pageContainer;\n }\n}\nwindow.BookReader = BookreaderWithTextSelection;\nexport default BookreaderWithTextSelection;\n","\n/**\n * Checks whether the current browser is a Chrome/Chromium browser\n * Code from https://stackoverflow.com/a/4565120/2317712\n * @param {string} [userAgent]\n * @param {string} [vendor]\n * @return {boolean}\n */\nexport function isChrome(userAgent = navigator.userAgent, vendor = navigator.vendor) {\n return /chrome/i.test(userAgent) && /google inc/i.test(vendor);\n}\n\n/**\n * Checks whether the current browser is firefox\n * @param {string} [userAgent]\n * @return {boolean}\n */\nexport function isFirefox(userAgent = navigator.userAgent) {\n return /firefox/i.test(userAgent);\n}\n\n/**\n * Checks whether the current browser is safari\n * https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#Browser_Name\n * @param {string} [userAgent]\n * @return {boolean}\n */\nexport function isSafari(userAgent = navigator.userAgent) {\n return /safari/i.test(userAgent) && !/chrome|chromium/i.test(userAgent);\n}\n","var global = require('../internals/global');\nvar fails = require('../internals/fails');\nvar uncurryThis = require('../internals/function-uncurry-this');\nvar toString = require('../internals/to-string');\nvar trim = require('../internals/string-trim').trim;\nvar whitespaces = require('../internals/whitespaces');\n\nvar charAt = uncurryThis(''.charAt);\nvar n$ParseFloat = global.parseFloat;\nvar Symbol = global.Symbol;\nvar ITERATOR = Symbol && Symbol.iterator;\nvar FORCED = 1 / n$ParseFloat(whitespaces + '-0') !== -Infinity\n // MS Edge 18- broken with boxed symbols\n || (ITERATOR && !fails(function () { n$ParseFloat(Object(ITERATOR)); }));\n\n// `parseFloat` method\n// https://tc39.es/ecma262/#sec-parsefloat-string\nmodule.exports = FORCED ? function parseFloat(string) {\n var trimmedString = trim(toString(string));\n var result = n$ParseFloat(trimmedString);\n return result === 0 && charAt(trimmedString, 0) == '-' ? -0 : result;\n} : n$ParseFloat;\n","var PROPER_FUNCTION_NAME = require('../internals/function-name').PROPER;\nvar fails = require('../internals/fails');\nvar whitespaces = require('../internals/whitespaces');\n\nvar non = '\\u200B\\u0085\\u180E';\n\n// check that a method works with the correct list\n// of whitespaces and has a correct name\nmodule.exports = function (METHOD_NAME) {\n return fails(function () {\n return !!whitespaces[METHOD_NAME]()\n || non[METHOD_NAME]() !== non\n || (PROPER_FUNCTION_NAME && whitespaces[METHOD_NAME].name !== METHOD_NAME);\n });\n};\n","var $ = require('../internals/export');\nvar $parseFloat = require('../internals/number-parse-float');\n\n// `parseFloat` method\n// https://tc39.es/ecma262/#sec-parsefloat-string\n$({ global: true, forced: parseFloat != $parseFloat }, {\n parseFloat: $parseFloat\n});\n","'use strict';\nvar $ = require('../internals/export');\nvar $trim = require('../internals/string-trim').trim;\nvar forcedStringTrimMethod = require('../internals/string-trim-forced');\n\n// `String.prototype.trim` method\n// https://tc39.es/ecma262/#sec-string.prototype.trim\n$({ target: 'String', proto: true, forced: forcedStringTrimMethod('trim') }, {\n trim: function trim() {\n return $trim(this);\n }\n});\n"],"names":["applyVariables","template","vars","overrides","possibleFilters","APPLY_FILTERS","replace","$0","$1","split","map","x","trim","varName","filterNames","value","n","reduce","acc","cur","toString","urlencode","encodeURIComponent","BookReader","window","DEFAULT_OPTIONS","enabled","fullDjvuXmlUrl","singlePageDjvuXmlUrl","jsonp","Cache","maxSize","this","entries","entry","length","shift","push","TextSelectionPlugin","options","optionVariables","avoidTspans","isFirefox","pointerEventsOnParagraph","isSafari","djvuPagesPromise","svgParagraphElement","svgWordElement","insertNewlines","pageTextCache","maxWordRendered","$","ajax","type","url","dataType","cache","error","e","then","res","xmlMap","parseXML","find","index","cachedEntry","response","pageIndex","xmlDoc","result","add","undefined","XMLpagesArr","$container","addEventListener","event","selection","document","getSelection","clipboardData","setData","preventDefault","svg","classList","remove","on","target","is","stopPropagation","one","off","textSelectingMode","removeAllRanges","defaultMode","$svg","each","i","s","interceptCopy","pageContainer","page","getPageText","XMLpage","totalWords","console","log","createSVGPageLayer","append","paragraph","words","paragSvg","createElementNS","setAttribute","style","pointerEvents","wordHeightArr","currWord","attr","parseFloat","left","bottom","right","wordHeight","wordTspan","textContent","appendChild","nextWord","leftNext","spaceTspan","createTextNode","sort","paragWordHeight","Math","floor","stopPageFlip","BookreaderWithTextSelection","Object","assign","plugins","textSelection","textSelectionPlugin","init","mode","constModeThumb","createTextLayer","isChrome","userAgent","navigator","vendor","test","global","fails","uncurryThis","whitespaces","charAt","n$ParseFloat","Symbol","ITERATOR","iterator","FORCED","module","exports","string","trimmedString","PROPER_FUNCTION_NAME","METHOD_NAME","name","$parseFloat","forced","$trim","proto","forcedStringTrimMethod"],"sourceRoot":""}
1
+ {"version":3,"file":"plugins/plugin.text_selection.js","mappings":"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","sources":["webpack://@internetarchive/bookreader/./src/BookReader/utils/SelectionStartedObserver.js","webpack://@internetarchive/bookreader/./src/util/strings.js","webpack://@internetarchive/bookreader/./src/plugins/plugin.text_selection.js","webpack://@internetarchive/bookreader/./src/util/browserSniffing.js","webpack://@internetarchive/bookreader/./node_modules/core-js/internals/number-parse-float.js","webpack://@internetarchive/bookreader/./node_modules/core-js/internals/string-trim-forced.js","webpack://@internetarchive/bookreader/./node_modules/core-js/modules/es.parse-float.js","webpack://@internetarchive/bookreader/./node_modules/core-js/modules/es.string.trim.js"],"sourcesContent":["// @ts-check\nexport class SelectionStartedObserver {\n loggedForSelection = false;\n startedInSelector = false;\n\n /**\n * @param {string} selector\n * @param {function(): any} handler\n */\n constructor(selector, handler) {\n this.selector = selector;\n this.handler = handler;\n }\n\n attach() {\n // We can't just use select start, because Chrome fires that willy\n // nilly even when a user slightly long presses.\n document.addEventListener(\"selectstart\", this._onSelectStart);\n // This has to be on document :/\n document.addEventListener(\"selectionchange\", this._onSelectionChange);\n }\n\n detach() {\n document.removeEventListener(\"selectstart\", this._onSelectStart);\n document.removeEventListener(\"selectionchange\", this._onSelectionChange);\n }\n\n /**\n * @param {Event} ev\n */\n _onSelectStart = (ev) => {\n this.loggedForSelection = false;\n // Use jQuery because ev.target could be a Node (eg TextNode), which\n // doesn't have .closest on it.\n this.startedInSelector = $(ev.target).closest(this.selector).length > 0;\n };\n\n _onSelectionChange = () => {\n if (this.loggedForSelection || !this.startedInSelector) return;\n const sel = window.getSelection();\n if (sel.toString()) {\n this.loggedForSelection = true;\n this.handler();\n }\n };\n}\n","/**\n * @typedef {String} StringWithVars\n * A template string with {{foo}} style variables\n * Also supports filters, like {{bookPath|urlencode}} (See APPLY_FILTERS for the\n * supported list of filters)\n **/\n\n/**\n * @param {StringWithVars|String} template\n * @param { {[varName: string]: { toString: () => string} } } vars\n * @param { {[varName: string]: { toString: () => string} } } [overrides]\n */\nexport function applyVariables(template, vars, overrides = {}, possibleFilters = APPLY_FILTERS) {\n return template?.replace(/\\{\\{([^}]*?)\\}\\}/g, ($0, $1) => {\n if (!$1) return $0;\n /** @type {string} */\n const expression = $1;\n const [varName, ...filterNames] = expression.split('|').map(x => x.trim());\n const defined = varName in overrides || varName in vars;\n\n // If it's not defined, don't expand it at all\n if (!defined) return $0;\n\n const value = varName in overrides ? overrides[varName]\n : varName in vars ? vars[varName] : null;\n const filters = filterNames.map(n => possibleFilters[n]);\n return filters.reduce((acc, cur) => cur(acc), value && value.toString());\n });\n}\n\n/** @type { {[filterName: String]:( string => string)} } */\nexport const APPLY_FILTERS = {\n urlencode: encodeURIComponent,\n};\n","//@ts-check\nimport { createSVGPageLayer } from '../BookReader/PageContainer.js';\nimport { SelectionStartedObserver } from '../BookReader/utils/SelectionStartedObserver.js';\nimport { isFirefox, isSafari } from '../util/browserSniffing.js';\nimport { applyVariables } from '../util/strings.js';\n/** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */\n/** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */\n\nconst BookReader = /** @type {typeof import('../BookReader').default} */(window.BookReader);\n\nexport const DEFAULT_OPTIONS = {\n enabled: true,\n /** @type {StringWithVars} The URL to fetch the entire DJVU xml. Supports options.vars */\n fullDjvuXmlUrl: null,\n /** @type {StringWithVars} The URL to fetch a single page of the DJVU xml. Supports options.vars. Also has {{pageIndex}} */\n singlePageDjvuXmlUrl: null,\n /** Whether to fetch the XML as a jsonp */\n jsonp: false,\n};\n/** @typedef {typeof DEFAULT_OPTIONS} TextSelectionPluginOptions */\n\n/**\n * @template T\n */\nexport class Cache {\n constructor(maxSize = 10) {\n this.maxSize = maxSize;\n /** @type {T[]} */\n this.entries = [];\n }\n\n /**\n * @param {T} entry\n */\n add(entry) {\n if (this.entries.length >= this.maxSize) {\n this.entries.shift();\n }\n this.entries.push(entry);\n }\n}\n\nexport class TextSelectionPlugin {\n\n constructor(options = DEFAULT_OPTIONS, optionVariables, avoidTspans = isFirefox(), pointerEventsOnParagraph = isSafari()) {\n this.options = options;\n this.optionVariables = optionVariables;\n /**@type {PromiseLike<JQuery<HTMLElement>|undefined>} */\n this.djvuPagesPromise = null;\n // Using text elements instead of tspans for words because Firefox does not allow svg tspan stretch.\n // Tspans are necessary on Chrome because they prevent newline character after every word when copying\n this.svgParagraphElement = \"text\";\n this.svgWordElement = \"tspan\";\n this.insertNewlines = avoidTspans;\n // Safari has a bug where `pointer-events` doesn't work on `<tspans>`. So\n // there we will set `pointer-events: all` on the paragraph element. We don't\n // do this everywhere, because it's a worse experience. Thanks Safari :/\n this.pointerEventsOnParagraph = pointerEventsOnParagraph;\n if (avoidTspans) {\n this.svgParagraphElement = \"g\";\n this.svgWordElement = \"text\";\n }\n\n /** @type {Cache<{index: number, response: any}>} */\n this.pageTextCache = new Cache();\n\n /**\n * Sometimes there are too many words on a page, and the browser becomes near\n * unusable. For now don't render text layer for pages with too many words.\n */\n this.maxWordRendered = 2500;\n }\n\n init() {\n // Only fetch the full djvu xml if the single page url isn't there\n if (this.options.singlePageDjvuXmlUrl) return;\n this.djvuPagesPromise = $.ajax({\n type: \"GET\",\n url: applyVariables(this.options.fullDjvuXmlUrl, this.optionVariables),\n dataType: this.options.jsonp ? \"jsonp\" : \"html\",\n cache: true,\n error: (e) => undefined\n }).then((res) => {\n try {\n const xmlMap = $.parseXML(res);\n return xmlMap && $(xmlMap).find(\"OBJECT\");\n } catch (e) {\n return undefined;\n }\n });\n }\n\n /**\n * @param {number} index\n * @returns {Promise<HTMLElement|undefined>}\n */\n async getPageText(index) {\n if (this.options.singlePageDjvuXmlUrl) {\n const cachedEntry = this.pageTextCache.entries.find(x => x.index == index);\n if (cachedEntry) {\n return cachedEntry.response;\n }\n const res = await $.ajax({\n type: \"GET\",\n url: applyVariables(this.options.singlePageDjvuXmlUrl, this.optionVariables, { pageIndex: index }),\n dataType: this.options.jsonp ? \"jsonp\" : \"html\",\n cache: true,\n error: (e) => undefined,\n });\n try {\n const xmlDoc = $.parseXML(res);\n const result = xmlDoc && $(xmlDoc).find(\"OBJECT\")[0];\n this.pageTextCache.add({ index, response: result });\n return result;\n } catch (e) {\n return undefined;\n }\n } else {\n const XMLpagesArr = await this.djvuPagesPromise;\n if (XMLpagesArr) return XMLpagesArr[index];\n }\n }\n\n /**\n * Intercept copied text to remove any styling applied to it\n * @param {JQuery} $container\n */\n interceptCopy($container) {\n $container[0].addEventListener('copy', (event) => {\n const selection = document.getSelection();\n event.clipboardData.setData('text/plain', selection.toString());\n event.preventDefault();\n });\n }\n\n /**\n * Applies mouse events when in default mode\n * @param {SVGElement} svg\n */\n defaultMode(svg) {\n svg.classList.remove(\"selectingSVG\");\n $(svg).on(\"mousedown.textSelectPluginHandler\", (event) => {\n if (!$(event.target).is(\".BRwordElement\")) return;\n event.stopPropagation();\n svg.classList.add(\"selectingSVG\");\n $(svg).one(\"mouseup.textSelectPluginHandler\", (event) => {\n if (window.getSelection().toString() != \"\") {\n event.stopPropagation();\n $(svg).off(\".textSelectPluginHandler\");\n this.textSelectingMode(svg);\n }\n else svg.classList.remove(\"selectingSVG\");\n });\n });\n }\n\n /**\n * Applies mouse events when in textSelecting mode\n * @param {SVGElement} svg\n */\n textSelectingMode(svg) {\n $(svg).on('mousedown.textSelectPluginHandler', (event) => {\n if (!$(event.target).is(\".BRwordElement\")) {\n if (window.getSelection().toString() != \"\") window.getSelection().removeAllRanges();\n }\n event.stopPropagation();\n });\n $(svg).on('mouseup.textSelectPluginHandler', (event) => {\n event.stopPropagation();\n if (window.getSelection().toString() == \"\") {\n $(svg).off(\".textSelectPluginHandler\");\n this.defaultMode(svg); }\n });\n }\n\n /**\n * Initializes text selection modes if there is an svg on the page\n * @param {JQuery} $container\n */\n stopPageFlip($container) {\n /** @type {JQuery<SVGElement>} */\n const $svg = $container.find('svg.textSelectionSVG');\n if (!$svg.length) return;\n $svg.each((i, s) => this.defaultMode(s));\n this.interceptCopy($container);\n }\n\n /**\n * @param {PageContainer} pageContainer\n */\n async createTextLayer(pageContainer) {\n const pageIndex = pageContainer.page.index;\n const $container = pageContainer.$container;\n const $svgLayers = $container.find('.textSelectionSVG');\n if ($svgLayers.length) return;\n const XMLpage = await this.getPageText(pageIndex);\n if (!XMLpage) return;\n\n const totalWords = $(XMLpage).find(\"WORD\").length;\n if (totalWords > this.maxWordRendered) {\n console.log(`Page ${pageIndex} has too many words (${totalWords} > ${this.maxWordRendered}). Not rendering text layer.`);\n return;\n }\n\n const svg = createSVGPageLayer(pageContainer.page, 'textSelectionSVG');\n $container.append(svg);\n\n $(XMLpage).find(\"PARAGRAPH\").each((i, paragraph) => {\n // Adding text element for each paragraph in the page\n const words = $(paragraph).find(\"WORD\");\n if (!words.length) return;\n const paragSvg = document.createElementNS(\"http://www.w3.org/2000/svg\", this.svgParagraphElement);\n paragSvg.setAttribute(\"class\", \"BRparagElement\");\n if (this.pointerEventsOnParagraph) {\n paragSvg.style.pointerEvents = \"all\";\n }\n\n const wordHeightArr = [];\n\n for (let i = 0; i < words.length; i++) {\n // Adding tspan for each word in paragraph\n const currWord = words[i];\n // eslint-disable-next-line no-unused-vars\n const [left, bottom, right, top] = $(currWord).attr(\"coords\").split(',').map(parseFloat);\n const wordHeight = bottom - top;\n wordHeightArr.push(wordHeight);\n\n const wordTspan = document.createElementNS(\"http://www.w3.org/2000/svg\", this.svgWordElement);\n wordTspan.setAttribute(\"class\", \"BRwordElement\");\n wordTspan.setAttribute(\"x\", left.toString());\n wordTspan.setAttribute(\"y\", bottom.toString());\n wordTspan.setAttribute(\"textLength\", (right - left).toString());\n wordTspan.setAttribute(\"lengthAdjust\", \"spacingAndGlyphs\");\n wordTspan.textContent = currWord.textContent;\n paragSvg.appendChild(wordTspan);\n\n // Adding spaces after words except at the end of the paragraph\n // TODO: assumes left-to-right text\n if (i < words.length - 1) {\n const nextWord = words[i + 1];\n // eslint-disable-next-line no-unused-vars\n const [leftNext, bottomNext, rightNext, topNext] = $(nextWord).attr(\"coords\").split(',').map(parseFloat);\n const spaceTspan = document.createElementNS(\"http://www.w3.org/2000/svg\", this.svgWordElement);\n spaceTspan.setAttribute(\"class\", \"BRwordElement\");\n spaceTspan.setAttribute(\"x\", right.toString());\n spaceTspan.setAttribute(\"y\", bottom.toString());\n if ((leftNext - right) > 0) spaceTspan.setAttribute(\"textLength\", (leftNext - right).toString());\n spaceTspan.setAttribute(\"lengthAdjust\", \"spacingAndGlyphs\");\n spaceTspan.textContent = \" \";\n paragSvg.appendChild(spaceTspan);\n }\n\n // Adds newline at the end of paragraph on Firefox\n if ((i == words.length - 1 && (this.insertNewlines))) {\n paragSvg.appendChild(document.createTextNode(\"\\n\"));\n }\n }\n\n wordHeightArr.sort();\n const paragWordHeight = wordHeightArr[Math.floor(wordHeightArr.length * 0.85)];\n paragSvg.setAttribute(\"font-size\", paragWordHeight.toString());\n svg.appendChild(paragSvg);\n });\n this.stopPageFlip($container);\n }\n}\n\nexport class BookreaderWithTextSelection extends BookReader {\n init() {\n const options = Object.assign({}, DEFAULT_OPTIONS, this.options.plugins.textSelection);\n if (options.enabled) {\n this.textSelectionPlugin = new TextSelectionPlugin(options, this.options.vars);\n // Write this back; this way the plugin is the source of truth, and BR just\n // contains a reference to it.\n this.options.plugins.textSelection = options;\n this.textSelectionPlugin.init();\n\n // Track how often selection is used\n const sso = new SelectionStartedObserver('.textSelectionSVG', () => {\n // Don't assume the order of the plugins ; the analytics plugin could\n // have been added later. But at this point we should know for certain.\n if (!this.archiveAnalyticsSendEvent) {\n sso.detach();\n } else {\n this.archiveAnalyticsSendEvent('BookReader', 'SelectStart');\n }\n });\n sso.attach();\n }\n\n super.init();\n }\n\n /**\n * @param {number} index\n */\n _createPageContainer(index) {\n const pageContainer = super._createPageContainer(index);\n // Disable if thumb mode; it's too janky\n // .page can be null for \"pre-cover\" region\n if (this.mode !== this.constModeThumb && pageContainer.page) {\n this.textSelectionPlugin?.createTextLayer(pageContainer);\n }\n return pageContainer;\n }\n}\nwindow.BookReader = BookreaderWithTextSelection;\nexport default BookreaderWithTextSelection;\n","\n/**\n * Checks whether the current browser is a Chrome/Chromium browser\n * Code from https://stackoverflow.com/a/4565120/2317712\n * @param {string} [userAgent]\n * @param {string} [vendor]\n * @return {boolean}\n */\nexport function isChrome(userAgent = navigator.userAgent, vendor = navigator.vendor) {\n return /chrome/i.test(userAgent) && /google inc/i.test(vendor);\n}\n\n/**\n * Checks whether the current browser is firefox\n * @param {string} [userAgent]\n * @return {boolean}\n */\nexport function isFirefox(userAgent = navigator.userAgent) {\n return /firefox/i.test(userAgent);\n}\n\n/**\n * Checks whether the current browser is safari\n * https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#Browser_Name\n * @param {string} [userAgent]\n * @return {boolean}\n */\nexport function isSafari(userAgent = navigator.userAgent) {\n return /safari/i.test(userAgent) && !/chrome|chromium/i.test(userAgent);\n}\n","var global = require('../internals/global');\nvar fails = require('../internals/fails');\nvar uncurryThis = require('../internals/function-uncurry-this');\nvar toString = require('../internals/to-string');\nvar trim = require('../internals/string-trim').trim;\nvar whitespaces = require('../internals/whitespaces');\n\nvar charAt = uncurryThis(''.charAt);\nvar n$ParseFloat = global.parseFloat;\nvar Symbol = global.Symbol;\nvar ITERATOR = Symbol && Symbol.iterator;\nvar FORCED = 1 / n$ParseFloat(whitespaces + '-0') !== -Infinity\n // MS Edge 18- broken with boxed symbols\n || (ITERATOR && !fails(function () { n$ParseFloat(Object(ITERATOR)); }));\n\n// `parseFloat` method\n// https://tc39.es/ecma262/#sec-parsefloat-string\nmodule.exports = FORCED ? function parseFloat(string) {\n var trimmedString = trim(toString(string));\n var result = n$ParseFloat(trimmedString);\n return result === 0 && charAt(trimmedString, 0) == '-' ? -0 : result;\n} : n$ParseFloat;\n","var PROPER_FUNCTION_NAME = require('../internals/function-name').PROPER;\nvar fails = require('../internals/fails');\nvar whitespaces = require('../internals/whitespaces');\n\nvar non = '\\u200B\\u0085\\u180E';\n\n// check that a method works with the correct list\n// of whitespaces and has a correct name\nmodule.exports = function (METHOD_NAME) {\n return fails(function () {\n return !!whitespaces[METHOD_NAME]()\n || non[METHOD_NAME]() !== non\n || (PROPER_FUNCTION_NAME && whitespaces[METHOD_NAME].name !== METHOD_NAME);\n });\n};\n","var $ = require('../internals/export');\nvar $parseFloat = require('../internals/number-parse-float');\n\n// `parseFloat` method\n// https://tc39.es/ecma262/#sec-parsefloat-string\n$({ global: true, forced: parseFloat != $parseFloat }, {\n parseFloat: $parseFloat\n});\n","'use strict';\nvar $ = require('../internals/export');\nvar $trim = require('../internals/string-trim').trim;\nvar forcedStringTrimMethod = require('../internals/string-trim-forced');\n\n// `String.prototype.trim` method\n// https://tc39.es/ecma262/#sec-string.prototype.trim\n$({ target: 'String', proto: true, forced: forcedStringTrimMethod('trim') }, {\n trim: function trim() {\n return $trim(this);\n }\n});\n"],"names":["SelectionStartedObserver","selector","handler","ev","loggedForSelection","startedInSelector","$","target","closest","length","window","getSelection","toString","this","document","addEventListener","_onSelectStart","_onSelectionChange","removeEventListener","applyVariables","template","vars","overrides","possibleFilters","APPLY_FILTERS","replace","$0","$1","split","map","x","trim","varName","filterNames","value","n","reduce","acc","cur","urlencode","encodeURIComponent","BookReader","DEFAULT_OPTIONS","enabled","fullDjvuXmlUrl","singlePageDjvuXmlUrl","jsonp","Cache","maxSize","entries","entry","shift","push","TextSelectionPlugin","options","optionVariables","avoidTspans","isFirefox","pointerEventsOnParagraph","isSafari","djvuPagesPromise","svgParagraphElement","svgWordElement","insertNewlines","pageTextCache","maxWordRendered","ajax","type","url","dataType","cache","error","e","then","res","xmlMap","parseXML","find","index","cachedEntry","response","pageIndex","xmlDoc","result","add","undefined","XMLpagesArr","$container","event","selection","clipboardData","setData","preventDefault","svg","classList","remove","on","is","stopPropagation","one","off","textSelectingMode","removeAllRanges","defaultMode","$svg","each","i","s","interceptCopy","pageContainer","page","getPageText","XMLpage","totalWords","console","log","createSVGPageLayer","append","paragraph","words","paragSvg","createElementNS","setAttribute","style","pointerEvents","wordHeightArr","currWord","attr","parseFloat","left","bottom","right","wordHeight","wordTspan","textContent","appendChild","nextWord","leftNext","spaceTspan","createTextNode","sort","paragWordHeight","Math","floor","stopPageFlip","BookreaderWithTextSelection","Object","assign","plugins","textSelection","textSelectionPlugin","init","sso","archiveAnalyticsSendEvent","detach","attach","mode","constModeThumb","createTextLayer","isChrome","userAgent","navigator","vendor","test","global","fails","uncurryThis","whitespaces","charAt","n$ParseFloat","Symbol","ITERATOR","iterator","FORCED","module","exports","string","trimmedString","PROPER_FUNCTION_NAME","METHOD_NAME","name","$parseFloat","forced","$trim","proto","forcedStringTrimMethod"],"sourceRoot":""}
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 5.0.0-61
2
+ - Fix: Mode2up preview pages hanging on first click @cdrini
3
+ - Dev: Add analytics event for text layer page selection @cdrini
4
+
1
5
  # 5.0.0-60
2
6
  - Fix: Update modal manager to fix duplicate definitions warnings on IA @cdrini
3
7
  - Fix: Common Sentry error `.spread` undefined @cdrini
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-60",
3
+ "version": "5.0.0-61",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,46 @@
1
+ // @ts-check
2
+ export class SelectionStartedObserver {
3
+ loggedForSelection = false;
4
+ startedInSelector = false;
5
+
6
+ /**
7
+ * @param {string} selector
8
+ * @param {function(): any} handler
9
+ */
10
+ constructor(selector, handler) {
11
+ this.selector = selector;
12
+ this.handler = handler;
13
+ }
14
+
15
+ attach() {
16
+ // We can't just use select start, because Chrome fires that willy
17
+ // nilly even when a user slightly long presses.
18
+ document.addEventListener("selectstart", this._onSelectStart);
19
+ // This has to be on document :/
20
+ document.addEventListener("selectionchange", this._onSelectionChange);
21
+ }
22
+
23
+ detach() {
24
+ document.removeEventListener("selectstart", this._onSelectStart);
25
+ document.removeEventListener("selectionchange", this._onSelectionChange);
26
+ }
27
+
28
+ /**
29
+ * @param {Event} ev
30
+ */
31
+ _onSelectStart = (ev) => {
32
+ this.loggedForSelection = false;
33
+ // Use jQuery because ev.target could be a Node (eg TextNode), which
34
+ // doesn't have .closest on it.
35
+ this.startedInSelector = $(ev.target).closest(this.selector).length > 0;
36
+ };
37
+
38
+ _onSelectionChange = () => {
39
+ if (this.loggedForSelection || !this.startedInSelector) return;
40
+ const sel = window.getSelection();
41
+ if (sel.toString()) {
42
+ this.loggedForSelection = true;
43
+ this.handler();
44
+ }
45
+ };
46
+ }
@@ -1,5 +1,6 @@
1
1
  //@ts-check
2
2
  import { createSVGPageLayer } from '../BookReader/PageContainer.js';
3
+ import { SelectionStartedObserver } from '../BookReader/utils/SelectionStartedObserver.js';
3
4
  import { isFirefox, isSafari } from '../util/browserSniffing.js';
4
5
  import { applyVariables } from '../util/strings.js';
5
6
  /** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
@@ -273,7 +274,20 @@ export class BookreaderWithTextSelection extends BookReader {
273
274
  // contains a reference to it.
274
275
  this.options.plugins.textSelection = options;
275
276
  this.textSelectionPlugin.init();
277
+
278
+ // Track how often selection is used
279
+ const sso = new SelectionStartedObserver('.textSelectionSVG', () => {
280
+ // Don't assume the order of the plugins ; the analytics plugin could
281
+ // have been added later. But at this point we should know for certain.
282
+ if (!this.archiveAnalyticsSendEvent) {
283
+ sso.detach();
284
+ } else {
285
+ this.archiveAnalyticsSendEvent('BookReader', 'SelectStart');
286
+ }
287
+ });
288
+ sso.attach();
276
289
  }
290
+
277
291
  super.init();
278
292
  }
279
293
 
@@ -418,7 +418,9 @@ BookReader.prototype._searchPluginGoToResult = async function (matchIndex) {
418
418
 
419
419
  // Trigger an update of book
420
420
  this._modes.mode1Up.mode1UpLit.updatePages();
421
- await this._modes.mode1Up.mode1UpLit.updateComplete;
421
+ if (this.activeMode == this._modes.mode1Up) {
422
+ await this._modes.mode1Up.mode1UpLit.updateComplete;
423
+ }
422
424
  }
423
425
  /* this updates the URL */
424
426
  if (!this._isIndexDisplayed(pageIndex)) {
@@ -0,0 +1,73 @@
1
+ // @ts-check
2
+ import sinon from "sinon";
3
+ import { SelectionStartedObserver } from "@/src/BookReader/utils/SelectionStartedObserver";
4
+
5
+ afterEach(() => {
6
+ sinon.restore();
7
+ });
8
+
9
+ describe("SelectionStartedObserver", () => {
10
+ describe("_onSelectStart", () => {
11
+ test("sets matches selector correctly", () => {
12
+ const observer = new SelectionStartedObserver(".text-layer", () => {});
13
+ const ev = new Event("selectstart", {});
14
+ const target = document.createElement("div");
15
+ Object.defineProperty(ev, "target", { get: () => target });
16
+ observer._onSelectStart(ev);
17
+ expect(observer.startedInSelector).toBe(false);
18
+ target.classList.add("text-layer");
19
+ observer._onSelectStart(ev);
20
+ expect(observer.startedInSelector).toBe(true);
21
+ expect(observer.loggedForSelection).toBe(false);
22
+ });
23
+
24
+ test("resets loggedForSelction", () => {
25
+ const observer = new SelectionStartedObserver(".text-layer", () => {});
26
+ const ev = new Event("selectstart", {});
27
+ const target = document.createElement("div");
28
+ Object.defineProperty(ev, "target", { get: () => target });
29
+ target.classList.add("text-layer");
30
+ observer._onSelectStart(ev);
31
+ expect(observer.loggedForSelection).toBe(false);
32
+ observer.loggedForSelection = true;
33
+ observer._onSelectStart(ev);
34
+ expect(observer.loggedForSelection).toBe(false);
35
+ });
36
+ });
37
+
38
+ test("_onSelectionChange", () => {
39
+ const handler = sinon.spy();
40
+ const observer = new SelectionStartedObserver(".text-layer", handler);
41
+ const ev = new Event("selectstart", {});
42
+ const target = document.createElement("div");
43
+ target.classList.add("text-layer");
44
+ Object.defineProperty(ev, "target", { get: () => target });
45
+ observer._onSelectStart(ev);
46
+
47
+ // stub window.getSelection
48
+ sinon.stub(window, "getSelection").returns({ toString: () => "test" });
49
+ observer._onSelectionChange();
50
+ expect(handler.callCount).toBe(1);
51
+ expect(observer.loggedForSelection).toBe(true);
52
+
53
+ // Calling it again does not call the handler again
54
+ observer._onSelectionChange();
55
+ expect(handler.callCount).toBe(1);
56
+
57
+ // Until the selection is cleared
58
+ observer._onSelectStart(ev);
59
+ expect(observer.loggedForSelection).toBe(false);
60
+ expect(handler.callCount).toBe(1);
61
+
62
+ observer._onSelectionChange();
63
+ expect(handler.callCount).toBe(2);
64
+
65
+ observer._onSelectStart(ev);
66
+
67
+ // Calling it again does not call the handler again
68
+ sinon.restore();
69
+ sinon.stub(window, "getSelection").returns({ toString: () => "" });
70
+ observer._onSelectionChange();
71
+ expect(handler.callCount).toBe(2);
72
+ });
73
+ });