@internetarchive/bookreader 5.0.0-60 → 5.0.0-61
Sign up to get free protection for your applications and to get access to all the features.
- package/BookReader/BookReader.js +1 -1
- package/BookReader/plugins/plugin.search.js +1 -1
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +1 -1
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/src/BookReader/utils/SelectionStartedObserver.js +46 -0
- package/src/plugins/plugin.text_selection.js +14 -0
- package/src/plugins/search/plugin.search.js +3 -1
- package/tests/jest/BookReader/utils/SelectionStartedObserver.test.js +73 -0
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"plugins/plugin.text_selection.js","mappings":"gmCAYO,SAASA,EAAeC,EAAUC,GAAuD,IAAjDC,EAAiD,uDAArC,GAAIC,EAAiC,uDAAfC,EAC/E,OAAOJ,MAAAA,OAAP,EAAOA,EAAUK,QAAQ,qBAAqB,SAACC,EAAIC,GACjD,IAAKA,EAAI,OAAOD,EAEhB,IACA,IADmBC,EAC0BC,MAAM,KAAKC,KAAI,SAAAC,GAAC,OAAIA,EAAEC,WAA5DC,EAAP,KAAmBC,EAAnB,WAIA,KAHgBD,KAAWV,MAAaU,KAAWX,GAGrC,OAAOK,EAErB,IAAMQ,EAAQF,KAAWV,EAAYA,EAAUU,GAC3CA,KAAWX,EAAOA,EAAKW,GAAW,KAEtC,OADgBC,EAAYJ,KAAI,SAAAM,GAAC,OAAIZ,EAAgBY,MACtCC,QAAO,SAACC,EAAKC,GAAN,OAAcA,EAAID,KAAMH,GAASA,EAAMK,e,wBAK1D,IAAMf,EAAgB,CAC3BgB,UAAWC,oB,qqFCzBb,IAAMC,EAAmEC,OAAOD,WAEnEE,EAAkB,CAC7BC,SAAS,EAETC,eAAgB,KAEhBC,qBAAsB,KAEtBC,OAAO,GAOIC,EAAb,WACE,aAA0B,IAAdC,EAAc,uDAAJ,GAAI,UACxBC,KAAKD,QAAUA,EAEfC,KAAKC,QAAU,GAJnB,6BAUE,SAAIC,GACEF,KAAKC,QAAQE,QAAUH,KAAKD,SAC9BC,KAAKC,QAAQG,QAEfJ,KAAKC,QAAQI,KAAKH,OAdtB,KAkBaI,EAAb,WAEE,aAA0H,IAA9GC,EAA8G,uDAApGd,EAAiBe,EAAmF,uCAAlEC,EAAkE,wDAApDC,EAAAA,EAAAA,MAAaC,EAAuC,wDAAZC,EAAAA,EAAAA,MAAY,UACxHZ,KAAKO,QAAUA,EACfP,KAAKQ,gBAAkBA,EAEvBR,KAAKa,iBAAmB,KAGxBb,KAAKc,oBAAsB,OAC3Bd,KAAKe,eAAiB,QACtBf,KAAKgB,eAAiBP,EAItBT,KAAKW,yBAA2BA,EAC5BF,IACFT,KAAKc,oBAAsB,IAC3Bd,KAAKe,eAAiB,QAIxBf,KAAKiB,cAAgB,IAAInB,EAMzBE,KAAKkB,gBAAkB,KA5B3B,sCA+BE,WAEMlB,KAAKO,QAAQX,uBACjBI,KAAKa,iBAAmBM,EAAEC,KAAK,CAC7BC,KAAM,MACNC,IAAKtD,EAAegC,KAAKO,QAAQZ,eAAgBK,KAAKQ,iBACtDe,SAAUvB,KAAKO,QAAQV,MAAQ,QAAU,OACzC2B,OAAO,EACPC,MAAO,SAACC,OACPC,MAAK,SAACC,GACP,IACE,IAAMC,EAASV,EAAEW,SAASF,GAC1B,OAAOC,GAAUV,EAAEU,GAAQE,KAAK,UAChC,MAAOL,GACP,cA7CR,uDAsDE,WAAkBM,GAAlB,kGACMhC,KAAKO,QAAQX,qBADnB,sBAEUqC,EAAcjC,KAAKiB,cAAchB,QAAQ8B,MAAK,SAAApD,GAAC,OAAIA,EAAEqD,OAASA,MAFxE,yCAIaC,EAAYC,UAJzB,uBAMsBf,EAAEC,KAAK,CACvBC,KAAM,MACNC,IAAKtD,EAAegC,KAAKO,QAAQX,qBAAsBI,KAAKQ,gBAAiB,CAAE2B,UAAWH,IAC1FT,SAAUvB,KAAKO,QAAQV,MAAQ,QAAU,OACzC2B,OAAO,EACPC,MAAO,SAACC,OAXd,cAMUE,EANV,gBAcYQ,EAASjB,EAAEW,SAASF,GACpBS,EAASD,GAAUjB,EAAEiB,GAAQL,KAAK,UAAU,GAClD/B,KAAKiB,cAAcqB,IAAI,CAAEN,MAAAA,EAAOE,SAAUG,IAhBhD,kBAiBaA,GAjBb,gEAmBaE,GAnBb,iDAsB8BvC,KAAKa,iBAtBnC,aAsBU2B,EAtBV,kDAuB4BA,EAAYR,IAvBxC,0DAtDF,yEAqFE,SAAcS,GACZA,EAAW,GAAGC,iBAAiB,QAAQ,SAACC,GACtC,IAAMC,EAAYC,SAASC,eAC3BH,EAAMI,cAAcC,QAAQ,aAAcJ,EAAUxD,YACpDuD,EAAMM,sBAzFZ,yBAiGE,SAAYC,GAAK,WACfA,EAAIC,UAAUC,OAAO,gBACrBjC,EAAE+B,GAAKG,GAAG,qCAAqC,SAACV,GACzCxB,EAAEwB,EAAMW,QAAQC,GAAG,oBACxBZ,EAAMa,kBACNN,EAAIC,UAAUb,IAAI,gBAClBnB,EAAE+B,GAAKO,IAAI,mCAAmC,SAACd,GACL,IAApCnD,OAAOsD,eAAe1D,YACxBuD,EAAMa,kBACNrC,EAAE+B,GAAKQ,IAAI,4BACX,EAAKC,kBAAkBT,IAEpBA,EAAIC,UAAUC,OAAO,yBA7GlC,+BAsHE,SAAkBF,GAAK,WACrB/B,EAAE+B,GAAKG,GAAG,qCAAqC,SAACV,GACzCxB,EAAEwB,EAAMW,QAAQC,GAAG,mBACkB,IAApC/D,OAAOsD,eAAe1D,YAAkBI,OAAOsD,eAAec,kBAEpEjB,EAAMa,qBAERrC,EAAE+B,GAAKG,GAAG,mCAAmC,SAACV,GAC5CA,EAAMa,kBACkC,IAApChE,OAAOsD,eAAe1D,aACxB+B,EAAE+B,GAAKQ,IAAI,4BACX,EAAKG,YAAYX,SAjIzB,0BAyIE,SAAaT,GAAY,WAEjBqB,EAAOrB,EAAWV,KAAK,wBACxB+B,EAAK3D,SACV2D,EAAKC,MAAK,SAACC,EAAGC,GAAJ,OAAU,EAAKJ,YAAYI,MACrCjE,KAAKkE,cAAczB,MA9IvB,2DAoJE,WAAsB0B,GAAtB,wGACQhC,EAAYgC,EAAcC,KAAKpC,QAC/BS,EAAa0B,EAAc1B,YACHV,KAAK,qBACpB5B,OAJjB,iEAKwBH,KAAKqE,YAAYlC,GALzC,UAKQmC,EALR,gEAQQC,EAAapD,EAAEmD,GAASvC,KAAK,QAAQ5B,QAC1BH,KAAKkB,iBATxB,wBAUIsD,QAAQC,IAAR,eAAoBtC,EAApB,gCAAqDoC,EAArD,cAAqEvE,KAAKkB,gBAA1E,iCAVJ,2BAcQgC,GAAMwB,EAAAA,EAAAA,IAAmBP,EAAcC,KAAM,oBACnD3B,EAAWkC,OAAOzB,GAElB/B,EAAEmD,GAASvC,KAAK,aAAagC,MAAK,SAACC,EAAGY,GAEpC,IAAMC,EAAQ1D,EAAEyD,GAAW7C,KAAK,QAChC,GAAK8C,EAAM1E,OAAX,CACA,IAAM2E,EAAWjC,SAASkC,gBAAgB,6BAA8B,EAAKjE,qBAC7EgE,EAASE,aAAa,QAAS,kBAC3B,EAAKrE,2BACPmE,EAASG,MAAMC,cAAgB,OAKjC,IAFA,IAAMC,EAAgB,GAEbnB,EAAI,EAAGA,EAAIa,EAAM1E,OAAQ6D,IAAK,CAErC,IAAMoB,EAAWP,EAAMb,GAEvB,IAAmC7C,EAAEiE,GAAUC,KAAK,UAAU5G,MAAM,KAAKC,IAAI4G,YAA7E,GAAOC,EAAP,KAAaC,EAAb,KAAqBC,EAArB,KACMC,EAAaF,EADnB,KAEAL,EAAc9E,KAAKqF,GAEnB,IAAMC,EAAY9C,SAASkC,gBAAgB,6BAA8B,EAAKhE,gBAW9E,GAVA4E,EAAUX,aAAa,QAAS,iBAChCW,EAAUX,aAAa,IAAKO,EAAKnG,YACjCuG,EAAUX,aAAa,IAAKQ,EAAOpG,YACnCuG,EAAUX,aAAa,cAAeS,EAAQF,GAAMnG,YACpDuG,EAAUX,aAAa,eAAgB,oBACvCW,EAAUC,YAAcR,EAASQ,YACjCd,EAASe,YAAYF,GAIjB3B,EAAIa,EAAM1E,OAAS,EAAG,CACxB,IAAM2F,EAAWjB,EAAMb,EAAI,GAE3B,IAAmD7C,EAAE2E,GAAUT,KAAK,UAAU5G,MAAM,KAAKC,IAAI4G,YAA7F,GAAOS,EAAP,KACMC,GADN,eACmBnD,SAASkC,gBAAgB,6BAA8B,EAAKhE,iBAC/EiF,EAAWhB,aAAa,QAAS,iBACjCgB,EAAWhB,aAAa,IAAKS,EAAMrG,YACnC4G,EAAWhB,aAAa,IAAKQ,EAAOpG,YAC/B2G,EAAWN,EAAS,GAAGO,EAAWhB,aAAa,cAAee,EAAWN,GAAOrG,YACrF4G,EAAWhB,aAAa,eAAgB,oBACxCgB,EAAWJ,YAAc,IACzBd,EAASe,YAAYG,GAIlBhC,GAAMa,EAAM1E,OAAS,GAAM,EAAKa,gBACnC8D,EAASe,YAAYhD,SAASoD,eAAe,OAIjDd,EAAce,OACd,IAAMC,EAAkBhB,EAAciB,KAAKC,MAA6B,IAAvBlB,EAAchF,SAC/D2E,EAASE,aAAa,YAAamB,EAAgB/G,YACnD8D,EAAI2C,YAAYf,OAElB9E,KAAKsG,aAAa7D,GAzEpB,iDApJF,qDAiOa8D,EAAb,a,qRAAA,U,IAAA,G,EAAA,E,+YAAA,oFACE,WACE,IAAMhG,EAAUiG,OAAOC,OAAO,GAAIhH,EAAiBO,KAAKO,QAAQmG,QAAQC,eACpEpG,EAAQb,UACVM,KAAK4G,oBAAsB,IAAItG,EAAoBC,EAASP,KAAKO,QAAQrC,MAGzE8B,KAAKO,QAAQmG,QAAQC,cAAgBpG,EACrCP,KAAK4G,oBAAoBC,QAE3B,2CAVJ,kCAgBE,SAAqB7E,GACnB,IAG6D,EAHvDmC,EAAgB,EAAH,sDAA8BnC,GAMjD,OAHIhC,KAAK8G,OAAS9G,KAAK+G,gBAAkB5C,EAAcC,OACrD,UAAApE,KAAK4G,2BAAL,SAA0BI,gBAAgB7C,IAErCA,MAvBX,GAAiD5E,GA0BjDC,OAAOD,WAAagH,G,kCC5Rb,SAASU,IAAqE,IAA5DC,EAA4D,uDAAhDC,UAAUD,UAAWE,EAA2B,uDAAlBD,UAAUC,OAC3E,MAAO,UAAUC,KAAKH,IAAc,cAAcG,KAAKD,GAQlD,SAAS1G,IAA2C,IAAjCwG,EAAiC,uDAArBC,UAAUD,UAC9C,MAAO,WAAWG,KAAKH,GASlB,SAAStG,IAA0C,IAAjCsG,EAAiC,uDAArBC,UAAUD,UAC7C,MAAO,UAAUG,KAAKH,KAAe,mBAAmBG,KAAKH,G,+GC5B/D,IAAII,EAAS,EAAQ,MACjBC,EAAQ,EAAQ,MAChBC,EAAc,EAAQ,MACtBpI,EAAW,EAAQ,MACnBR,EAAO,aACP6I,EAAc,EAAQ,MAEtBC,EAASF,EAAY,GAAGE,QACxBC,EAAeL,EAAOhC,WACtBsC,EAASN,EAAOM,OAChBC,EAAWD,GAAUA,EAAOE,SAC5BC,EAAS,EAAIJ,EAAaF,EAAc,QAAU,KAEhDI,IAAaN,GAAM,WAAcI,EAAanB,OAAOqB,OAI3DG,EAAOC,QAAUF,EAAS,SAAoBG,GAC5C,IAAIC,EAAgBvJ,EAAKQ,EAAS8I,IAC9B7F,EAASsF,EAAaQ,GAC1B,OAAkB,IAAX9F,GAA4C,KAA5BqF,EAAOS,EAAe,IAAa,EAAI9F,GAC5DsF,G,qBCrBJ,IAAIS,EAAuB,eACvBb,EAAQ,EAAQ,MAChBE,EAAc,EAAQ,MAM1BO,EAAOC,QAAU,SAAUI,GACzB,OAAOd,GAAM,WACX,QAASE,EAAYY,MANf,cAOGA,MACHD,GAAwBX,EAAYY,GAAaC,OAASD,O,qBCZpE,IAAIlH,EAAI,EAAQ,MACZoH,EAAc,EAAQ,MAI1BpH,EAAE,CAAEmG,QAAQ,EAAMkB,OAAQlD,YAAciD,GAAe,CACrDjD,WAAYiD,K,kCCLd,IAAIpH,EAAI,EAAQ,MACZsH,EAAQ,aAKZtH,EAAE,CAAEmC,OAAQ,SAAUoF,OAAO,EAAMF,OAJN,EAAQ,KAIMG,CAAuB,SAAW,CAC3E/J,KAAM,WACJ,OAAO6J,EAAMzI,W","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
@@ -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
|
-
|
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
|
+
});
|