1export default class ReadingTime { 2 quill; 3 wpm; 4 label; 5 suffix; 6 targetElement; 7 readTimeOk; 8 readTimeMedium; 9 toolbarContainer = null; 10 toolbarOriginalPaddingRight = null; 11 targetIsCustom = false; 12 constructor(quill, options) { 13 if (options === void 0) { 14 options = {}; 15 } 16 this.quill = quill; 17 this.wpm = options.wpm || 200; 18 this.label = options.label || 'â± Reading time: ~ '; 19 this.suffix = options.suffix || ' minute(s)'; 20 this.readTimeOk = options.readTimeOk || 5; 21 this.readTimeMedium = options.readTimeMedium || 8; 22 if (options.target) { 23 const el = document.querySelector(options.target); 24 if (!el) { 25 throw new Error(`Cannot find target element: ${options.target}`); 26 } 27 this.targetElement = el; 28 this.targetIsCustom = true; 29 } else { 30 this.targetElement = document.createElement('div'); 31 this.targetElement.classList.add('ql-reading-time'); 32 this.targetElement.style.cssText = ` 33 font-size: 12px; 34 font-weight: 500; 35 padding: 4px 10px; 36 border-radius: 6px; 37 background: #f5f5f5; 38 color: #2e7d32; 39 font-family: sans-serif; 40 transition: background 0.3s ease, color 0.3s ease; 41 position: absolute; 42 right: 10px; 43 top: 50%; 44 transform: translateY(-50%); 45 pointer-events: none; 46 box-sizing: border-box; 47 min-width: 48px; /* minimum to stabilise width */ 48 `; 49 const toolbar = this.quill.getModule('toolbar'); 50 if (toolbar?.container) { 51 const tb = toolbar.container; 52 if (window.getComputedStyle(tb).position === 'static') { 53 tb.style.position = 'relative'; 54 } 55 tb.appendChild(this.targetElement); 56 this.toolbarContainer = tb; 57 this.toolbarOriginalPaddingRight = window.getComputedStyle(tb).paddingRight || ''; 58 } else { 59 const wrapper = document.createElement('div'); 60 wrapper.style.position = 'relative'; 61 this.quill.container.parentNode?.insertBefore(wrapper, this.quill.container); 62 wrapper.appendChild(this.quill.container); 63 wrapper.appendChild(this.targetElement); 64 this.toolbarContainer = wrapper; 65 this.toolbarOriginalPaddingRight = window.getComputedStyle(wrapper).paddingRight || ''; 66 } 67 } 68 this.updateReadingTime(); 69 this.quill.on('text-change', () => this.updateReadingTime()); 70 window.addEventListener('resize', this.onWindowResize); 71 } 72 onWindowResize = () => { 73 if (this.toolbarContainer && !this.targetIsCustom) { 74 const width = Math.ceil(this.targetElement.getBoundingClientRect().width); 75 this.toolbarContainer.style.paddingRight = `${width + 12}px`; 76 } 77 }; 78 updateReadingTime() { 79 const text = this.quill.getText().trim(); 80 const words = text.length > 0 ? text.split(/\s+/).length : 0; 81 const minutes = Math.max(1, Math.ceil(words / this.wpm)); 82 if (minutes <= this.readTimeOk) { 83 this.setStyle('#2e7d32', '#e8f5e9'); 84 } else if (minutes <= this.readTimeMedium) { 85 this.setStyle('#ef6c00', '#fff3e0'); 86 } else { 87 this.setStyle('#c62828', '#ffebee'); 88 } 89 this.targetElement.textContent = `${this.label}${minutes}${this.suffix}`; 90 if (this.toolbarContainer && !this.targetIsCustom) { 91 const width = Math.ceil(this.targetElement.getBoundingClientRect().width); 92 this.toolbarContainer.style.paddingRight = `${width + 12}px`; // 12px marge 93 } 94 } 95 setStyle(color, background) { 96 this.targetElement.style.color = color; 97 this.targetElement.style.background = background; 98 } 99 destroy() { 100 if (this.toolbarContainer && this.toolbarOriginalPaddingRight !== null && !this.targetIsCustom) { 101 this.toolbarContainer.style.paddingRight = this.toolbarOriginalPaddingRight; 102 } 103 if (!this.targetIsCustom && this.targetElement.parentNode) { 104 this.targetElement.parentNode.removeChild(this.targetElement); 105 } 106 window.removeEventListener('resize', this.onWindowResize); 107 } 108}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.