@@ -96,12 +96,10 @@ async function removeSelectorsFromHostname(node) {
9696 qsa$ ( hostnameNode , 'li.selector.removed:not([data-ugly=""])' )
9797 ) . map ( a => a . dataset . ugly ) ;
9898 if ( selectors . length === 0 ) { return ; }
99- dom . cl . add ( dom . body , 'readonly' ) ;
100- updateContentEditability ( ) ;
99+ updateContentEditability ( false ) ;
101100 await sendMessage ( { what : 'removeCustomFilters' , hostname, selectors } ) ;
102101 await debounceRenderCustomFilters ( ) ;
103- dom . cl . remove ( dom . body , 'readonly' ) ;
104- updateContentEditability ( ) ;
102+ updateContentEditability ( true ) ;
105103}
106104
107105async function unremoveSelectorsFromHostname ( node ) {
@@ -111,12 +109,10 @@ async function unremoveSelectorsFromHostname(node) {
111109 if ( hostname === undefined ) { return ; }
112110 const selectors = selectorsFromNode ( hostnameNode ) ;
113111 if ( selectors . length === 0 ) { return ; }
114- dom . cl . add ( dom . body , 'readonly' ) ;
115- updateContentEditability ( ) ;
112+ updateContentEditability ( false ) ;
116113 await sendMessage ( { what : 'addCustomFilters' , hostname, selectors } ) ;
117114 await debounceRenderCustomFilters ( ) ;
118- dom . cl . remove ( dom . body , 'readonly' ) ;
119- updateContentEditability ( ) ;
115+ updateContentEditability ( true ) ;
120116}
121117
122118/******************************************************************************/
@@ -225,8 +221,9 @@ debounceRenderCustomFilters.debouncer = undefined;
225221
226222/******************************************************************************/
227223
228- function updateContentEditability ( ) {
229- if ( dom . cl . has ( dom . body , 'readonly' ) ) {
224+ function updateContentEditability ( readWrite ) {
225+ dom . cl . toggle ( dom . body , 'readonly' , readWrite === false ) ;
226+ if ( readWrite === false ) {
230227 dom . attr ( 'section[data-pane="filters"] [contenteditable]' , 'contenteditable' , 'false' ) ;
231228 return ;
232229 }
@@ -267,18 +264,23 @@ async function validateSelector(target, selector) {
267264/******************************************************************************/
268265
269266async function onHostnameChanged ( target , before , after ) {
267+ const hostnameNode = target . closest ( 'li.hostname' ) ;
268+ if ( hostnameNode === null ) { return ; }
269+ if ( before === '' ) {
270+ const succeeded = await importFromText ( after ) ;
271+ if ( succeeded ) {
272+ return debounceRenderCustomFilters ( ) ;
273+ }
274+ }
275+
276+ after = after . replace ( '\n' , '' ) ;
277+
270278 const uglyAfter = punycode . toASCII ( after ) ;
271279 if ( isValidHostname ( uglyAfter ) === false ) {
272280 target . textContent = before ;
273281 return ;
274282 }
275283
276- const hostnameNode = target . closest ( 'li.hostname' ) ;
277- if ( hostnameNode === null ) { return ; }
278-
279- dom . cl . add ( dom . body , 'readonly' ) ;
280- updateContentEditability ( ) ;
281-
282284 // Remove old hostname from storage
283285 if ( hostnameNode . dataset . ugly ) {
284286 await sendMessage ( { what : 'removeAllCustomFilters' ,
@@ -295,11 +297,11 @@ async function onHostnameChanged(target, before, after) {
295297 } ) ;
296298
297299 await debounceRenderCustomFilters ( ) ;
298- dom . cl . remove ( dom . body , 'readonly' ) ;
299- updateContentEditability ( ) ;
300300}
301301
302302async function onSelectorChanged ( target , before , after ) {
303+ after = after . replace ( '\n' , '' ) ;
304+
303305 const selectorNode = target . closest ( 'li.selector' ) ;
304306 if ( selectorNode === null ) { return ; }
305307
@@ -310,9 +312,6 @@ async function onSelectorChanged(target, before, after) {
310312 return ;
311313 }
312314
313- dom . cl . add ( dom . body , 'readonly' ) ;
314- updateContentEditability ( ) ;
315-
316315 const hostname = hostnameFromNode ( target ) ;
317316
318317 // Remove old selector from storage
@@ -330,16 +329,14 @@ async function onSelectorChanged(target, before, after) {
330329 } ) ;
331330
332331 await debounceRenderCustomFilters ( ) ;
333- dom . cl . remove ( dom . body , 'readonly' ) ;
334- updateContentEditability ( ) ;
335332}
336333
337- function onTextChanged ( target ) {
334+ async function onTextChanged ( target ) {
338335 const itemNode = target . closest ( '[data-pretty]' ) ;
339336 if ( itemNode === null ) { return ; }
340337 dom . cl . remove ( itemNode , 'error' ) ;
341338 const before = itemNode . dataset . pretty ;
342- const after = target . textContent . trim ( ) . replace ( '\n' , '' ) ;
339+ const after = target . textContent . trim ( ) ;
343340 if ( after !== target . textContent ) {
344341 target . textContent = after ;
345342 }
@@ -348,11 +345,16 @@ function onTextChanged(target) {
348345 target . textContent = before ;
349346 return ;
350347 }
348+
349+ updateContentEditability ( false ) ;
350+
351351 if ( target . matches ( '.hostname' ) ) {
352- onHostnameChanged ( target , before , after ) ;
352+ await onHostnameChanged ( target , before , after ) ;
353353 } else if ( target . matches ( '.selector' ) ) {
354- onSelectorChanged ( target , before , after ) ;
354+ await onSelectorChanged ( target , before , after ) ;
355355 }
356+
357+ updateContentEditability ( true ) ;
356358}
357359
358360/******************************************************************************/
@@ -388,20 +390,53 @@ function validateEdit(ev) {
388390 if ( itemNode === null ) { return ; }
389391 const after = target . textContent . trim ( ) . replace ( '\n' , '' ) ;
390392 if ( after === '' ) { return ; }
393+ const before = itemNode . dataset . ugly ;
391394 if ( target . matches ( '.selector' ) ) {
392395 validateSelector ( target , after ) . then ( ( { pretty } ) => {
393396 if ( focusedEditableContent !== target ) { return ; }
394397 dom . cl . toggle ( itemNode , 'error' , after !== '' && Boolean ( pretty ) === false ) ;
395398 } ) ;
396399 } else if ( target . matches ( '.hostname' ) ) {
397- dom . cl . toggle ( itemNode , 'error' , isValidHostname ( punycode . toASCII ( after ) ) === false ) ;
400+ dom . cl . toggle ( itemNode , 'error' ,
401+ before !== '' && isValidHostname ( punycode . toASCII ( after ) ) === false
402+ ) ;
398403 }
399404}
400405
401406let focusedEditableContent = null ;
402407
403408/******************************************************************************/
404409
410+ function onCopyClicked ( ev ) {
411+ const { target } = ev ;
412+ const selectorNode = target . closest ( 'li.selector:not(.removed):not([data-ugly=""])' ) ;
413+ const hostnameNode = target . closest ( 'li.hostname:not(.removed):not([data-ugly=""])' ) ;
414+ const hostname = hostnameFromNode ( hostnameNode ) ;
415+ if ( Boolean ( hostname ) === false ) { return ; }
416+ const selectorNodes = [ ] ;
417+ let copyNode ;
418+ if ( selectorNode ) {
419+ selectorNodes . push ( selectorNode ) ;
420+ copyNode = selectorNode ;
421+ } else {
422+ selectorNodes . push ( ...qsa$ ( hostnameNode , 'li.selector:not(.removed):not([data-ugly=""])' ) ) ;
423+ copyNode = hostnameNode ;
424+ }
425+ const text = [ ] ;
426+ for ( const node of selectorNodes ) {
427+ const selector = node . dataset . pretty ;
428+ if ( Boolean ( selector ) === false ) { continue ; }
429+ text . push ( `${ hostname } ##${ selector } ` ) ;
430+ }
431+ if ( text . length === 0 ) { return ; }
432+ text . push ( '\n' ) ;
433+ const item = new ClipboardItem ( { 'text/plain' : text . join ( '\n' ) } ) ;
434+ navigator . clipboard . write ( [ item ] ) ;
435+ const copyNodes = qsa$ ( copyNode , '.copy' ) ;
436+ dom . cl . add ( copyNodes , 'copied' ) ;
437+ self . setTimeout ( ( ) => { dom . cl . remove ( copyNodes , 'copied' ) ; } , 1000 ) ;
438+ }
439+
405440function onTrashClicked ( ev ) {
406441 const { target } = ev ;
407442 const selectorNode = target . closest ( 'li.selector' ) ;
@@ -474,10 +509,9 @@ async function importFromText(text) {
474509 }
475510 }
476511
477- if ( hostnameToSelectorsMap . size === 0 ) { return ; }
512+ if ( hostnameToSelectorsMap . size === 0 ) { return false ; }
478513
479- dom . cl . add ( dom . body , 'readonly' ) ;
480- updateContentEditability ( ) ;
514+ updateContentEditability ( false ) ;
481515
482516 const promises = [ ] ;
483517 for ( const [ hostname , selectors ] of hostnameToSelectorsMap ) {
@@ -489,10 +523,11 @@ async function importFromText(text) {
489523 ) ;
490524 }
491525 await Promise . all ( promises ) ;
492-
493526 await debounceRenderCustomFilters ( ) ;
494- dom . cl . remove ( dom . body , 'readonly' ) ;
495- updateContentEditability ( ) ;
527+
528+ updateContentEditability ( true ) ;
529+
530+ return true ;
496531}
497532
498533/******************************************************************************/
@@ -558,6 +593,7 @@ async function start() {
558593 dom . on ( dataContainer , 'focusin' , 'section[data-pane="filters"] [contenteditable]' , startEdit ) ;
559594 dom . on ( dataContainer , 'focusout' , 'section[data-pane="filters"] [contenteditable]' , endEdit ) ;
560595 dom . on ( dataContainer , 'input' , 'section[data-pane="filters"] [contenteditable]' , commitEdit ) ;
596+ dom . on ( dataContainer , 'click' , 'section[data-pane="filters"] .copy' , onCopyClicked ) ;
561597 dom . on ( dataContainer , 'click' , 'section[data-pane="filters"] .remove' , onTrashClicked ) ;
562598 dom . on ( dataContainer , 'click' , 'section[data-pane="filters"] .undo' , onUndoClicked ) ;
563599 dom . on ( 'section[data-pane="filters"] [data-i18n="addButton"]' , 'click' , importFromTextarea ) ;
0 commit comments