11//@ts -check
22import { createDIVPageLayer } from '../BookReader/PageContainer.js' ;
3- import { SelectionObserver } from '../BookReader/utils/SelectionObserver.js' ;
43import { BookReaderPlugin } from '../BookReaderPlugin.js' ;
54import { applyVariables } from '../util/strings.js' ;
65import { Cache } from '../util/cache.js' ;
76import { toISO6391 } from './tts/utils.js' ;
7+ import { TextSelectionManager } from '../util/TextSelectionManager.js' ;
88/** @typedef {import('../util/strings.js').StringWithVars } StringWithVars */
99/** @typedef {import('../BookReader/PageContainer.js').PageContainer } PageContainer */
1010
@@ -20,7 +20,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
2020 singlePageDjvuXmlUrl : null ,
2121 /** Whether to fetch the XML as a jsonp */
2222 jsonp : false ,
23- /** Mox words tha can be selected when the text layer is protected */
23+ /** Mox words that can be selected when the text layer is protected */
2424 maxProtectedWords : 200 ,
2525 }
2626
@@ -46,80 +46,17 @@ export class TextSelectionPlugin extends BookReaderPlugin {
4646 // now we do make that assumption.
4747 /** Whether the book is right-to-left */
4848 this . rtl = this . br . pageProgression === 'rl' ;
49- this . selectionObserver = new SelectionObserver ( '.BRtextLayer' , this . _onSelectionChange ) ;
49+ this . textSelectionManager = new TextSelectionManager ( '.BRtextLayer' , this . br , { selectionElement : [ '.BRwordElement' , '.BRspace' ] } , this . options . maxProtectedWords ) ;
5050 }
5151
5252 /** @override */
5353 init ( ) {
5454 if ( ! this . options . enabled ) return ;
5555
5656 this . loadData ( ) ;
57-
58- this . selectionObserver . attach ( ) ;
59- new SelectionObserver ( '.BRtextLayer' , ( selectEvent ) => {
60- // Track how often selection is used
61- if ( selectEvent == 'started' ) {
62- this . br . plugins . archiveAnalytics ?. sendEvent ( 'BookReader' , 'SelectStart' ) ;
63-
64- // Set a class on the page to avoid hiding it when zooming/etc
65- this . br . refs . $br . find ( '.BRpagecontainer--hasSelection' ) . removeClass ( 'BRpagecontainer--hasSelection' ) ;
66- $ ( window . getSelection ( ) . anchorNode ) . closest ( '.BRpagecontainer' ) . addClass ( 'BRpagecontainer--hasSelection' ) ;
67- }
68- } ) . attach ( ) ;
69-
70- if ( this . br . protected ) {
71- document . addEventListener ( 'selectionchange' , this . _limitSelection ) ;
72- // Prevent right clicking when selected text
73- $ ( document . body ) . on ( 'contextmenu dragstart copy' , ( e ) => {
74- const selection = document . getSelection ( ) ;
75- if ( selection ?. toString ( ) ) {
76- const intersectsTextLayer = $ ( '.BRtextLayer' )
77- . toArray ( )
78- . some ( el => selection . containsNode ( el , true ) ) ;
79- if ( intersectsTextLayer ) {
80- e . preventDefault ( ) ;
81- return false ;
82- }
83- }
84- } ) ;
85- }
57+ this . textSelectionManager . init ( ) ;
8658 }
8759
88- _limitSelection = ( ) => {
89- const selection = window . getSelection ( ) ;
90- if ( ! selection . rangeCount ) return ;
91-
92- const range = selection . getRangeAt ( 0 ) ;
93-
94- // Check if range.startContainer is inside the sub-tree of .BRContainer
95- const startInBr = ! ! range . startContainer . parentElement . closest ( '.BRcontainer' ) ;
96- const endInBr = ! ! range . endContainer . parentElement . closest ( '.BRcontainer' ) ;
97- if ( ! startInBr && ! endInBr ) return ;
98- if ( ! startInBr || ! endInBr ) {
99- // weird case, just clear the selection
100- selection . removeAllRanges ( ) ;
101- return ;
102- }
103-
104- // Find the last allowed word in the selection
105- const lastAllowedWord = genAt (
106- genFilter (
107- walkBetweenNodes ( range . startContainer , range . endContainer ) ,
108- ( node ) => node . classList ?. contains ( 'BRwordElement' ) ,
109- ) ,
110- this . options . maxProtectedWords - 1 ,
111- ) ;
112-
113- if ( ! lastAllowedWord || range . endContainer . parentNode == lastAllowedWord ) return ;
114-
115- const newRange = document . createRange ( ) ;
116- newRange . setStart ( range . startContainer , range . startOffset ) ;
117- newRange . setEnd ( lastAllowedWord . firstChild , lastAllowedWord . textContent . length ) ;
118-
119- selection . removeAllRanges ( ) ;
120- selection . addRange ( newRange ) ;
121- } ;
122-
12360 /**
12461 * @override
12562 * @param {PageContainer } pageContainer
@@ -134,20 +71,6 @@ export class TextSelectionPlugin extends BookReaderPlugin {
13471 return pageContainer ;
13572 }
13673
137- /**
138- * @param {'started' | 'cleared' } type
139- * @param {HTMLElement } target
140- */
141- _onSelectionChange = ( type , target ) => {
142- if ( type === 'started' ) {
143- this . textSelectingMode ( target ) ;
144- } else if ( type === 'cleared' ) {
145- this . defaultMode ( target ) ;
146- } else {
147- throw new Error ( `Unknown type ${ type } ` ) ;
148- }
149- }
150-
15174 loadData ( ) {
15275 // Only fetch the full djvu xml if the single page url isn't there
15376 if ( this . options . singlePageDjvuXmlUrl ) return ;
@@ -204,92 +127,6 @@ export class TextSelectionPlugin extends BookReaderPlugin {
204127 }
205128 }
206129
207- /**
208- * Intercept copied text to remove any styling applied to it
209- * @param {JQuery } $container
210- */
211- interceptCopy ( $container ) {
212- $container [ 0 ] . addEventListener ( 'copy' , ( event ) => {
213- const selection = document . getSelection ( ) ;
214- event . clipboardData . setData ( 'text/plain' , selection . toString ( ) ) ;
215- event . preventDefault ( ) ;
216- } ) ;
217- }
218-
219- /**
220- * Applies mouse events when in default mode
221- * @param {HTMLElement } textLayer
222- */
223- defaultMode ( textLayer ) {
224- const $pageContainer = $ ( textLayer ) . closest ( '.BRpagecontainer' ) ;
225- textLayer . style . pointerEvents = "none" ;
226- $pageContainer . find ( "img" ) . css ( "pointer-events" , "auto" ) ;
227-
228- $ ( textLayer ) . off ( ".textSelectPluginHandler" ) ;
229- const startedMouseDown = this . mouseIsDown ;
230- let skipNextMouseup = this . mouseIsDown ;
231- if ( startedMouseDown ) {
232- textLayer . style . pointerEvents = "auto" ;
233- }
234-
235- // Need to stop propagation to prevent DragScrollable from
236- // blocking selection
237- $ ( textLayer ) . on ( "mousedown.textSelectPluginHandler" , ( event ) => {
238- this . mouseIsDown = true ;
239- if ( $ ( event . target ) . is ( ".BRwordElement, .BRspace" ) ) {
240- event . stopPropagation ( ) ;
241- }
242- } ) ;
243-
244- $ ( textLayer ) . on ( "mouseup.textSelectPluginHandler" , ( event ) => {
245- this . mouseIsDown = false ;
246- textLayer . style . pointerEvents = "none" ;
247- if ( skipNextMouseup ) {
248- skipNextMouseup = false ;
249- event . stopPropagation ( ) ;
250- }
251- } ) ;
252- }
253-
254- /**
255- * This mode is active while there is a selection on the given textLayer
256- * @param {HTMLElement } textLayer
257- */
258- textSelectingMode ( textLayer ) {
259- const $pageContainer = $ ( textLayer ) . closest ( '.BRpagecontainer' ) ;
260- // Make text layer consume all events
261- textLayer . style . pointerEvents = "all" ;
262- // Block img from getting long-press to save while selecting
263- $pageContainer . find ( "img" ) . css ( "pointer-events" , "none" ) ;
264-
265- $ ( textLayer ) . off ( ".textSelectPluginHandler" ) ;
266-
267- $ ( textLayer ) . on ( 'mousedown.textSelectPluginHandler' , ( event ) => {
268- this . mouseIsDown = true ;
269- event . stopPropagation ( ) ;
270- } ) ;
271-
272- // Prevent page flip on click
273- $ ( textLayer ) . on ( 'mouseup.textSelectPluginHandler' , ( event ) => {
274- this . mouseIsDown = false ;
275- event . stopPropagation ( ) ;
276- } ) ;
277- }
278-
279- /**
280- * Initializes text selection modes if there is a text layer on the page
281- * @param {JQuery } $container
282- */
283- stopPageFlip ( $container ) {
284- /** @type {JQuery<HTMLElement> } */
285- const $textLayer = $container . find ( '.BRtextLayer' ) ;
286- if ( ! $textLayer . length ) return ;
287- $textLayer . each ( ( i , s ) => this . defaultMode ( s ) ) ;
288- if ( ! this . br . protected ) {
289- this . interceptCopy ( $container ) ;
290- }
291- }
292-
293130 /**
294131 * @param {PageContainer } pageContainer
295132 */
@@ -344,7 +181,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
344181 textLayer . appendChild ( paragEl ) ;
345182 }
346183 $container . append ( textLayer ) ;
347- this . stopPageFlip ( $container ) ;
184+ this . textSelectionManager . stopPageFlip ( $container ) ;
348185 this . br . trigger ( 'textLayerRendered' , {
349186 pageIndex,
350187 pageContainer,
0 commit comments