1const QUILL_CSS_URL = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/quill.snow.css'; 2const QUILL_JS_URL = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/quill.js'; 3 4let quillLoaderPromise = null; 5const instances = new Map(); 6 7function loadQuill() { 8 if (quillLoaderPromise) return quillLoaderPromise; 9 10 quillLoaderPromise = new Promise((resolve, reject) => { 11 if (!document.querySelector('link[data-quill-css]')) { 12 const link = document.createElement('link'); 13 link.rel = 'stylesheet'; 14 link.href = QUILL_CSS_URL; 15 link.setAttribute('data-quill-css', ''); 16 document.head.appendChild(link); 17 } 18 19 if (window.Quill) { 20 resolve(window.Quill); 21 return; 22 } 23 24 const existing = document.querySelector('script[data-quill-js]'); 25 if (existing) { 26 existing.addEventListener('load', () => resolve(window.Quill)); 27 existing.addEventListener('error', () => reject(new Error('Failed to load Quill from jsdelivr'))); 28 return; 29 } 30 31 const script = document.createElement('script'); 32 script.src = QUILL_JS_URL; 33 script.async = true; 34 script.setAttribute('data-quill-js', ''); 35 script.addEventListener('load', () => resolve(window.Quill)); 36 script.addEventListener('error', () => reject(new Error('Failed to load Quill from jsdelivr'))); 37 document.head.appendChild(script); 38 }); 39 40 return quillLoaderPromise; 41} 42 43export async function init(id, initialHtml, uploadUrl, placeholder, dotNetRef) { 44 const Quill = await loadQuill(); 45 const container = document.getElementById(id); 46 if (!container) return; 47 48 const toolbar = [ 49 [{ header: [2, 3, 4, false] }], 50 ['bold', 'italic', 'blockquote'], 51 [{ list: 'ordered' }, { list: 'bullet' }], 52 [{ indent: '-1' }, { indent: '+1' }], 53 [{ align: [] }], 54 ['link', 'image'], 55 ['clean'] 56 ]; 57 58 const quill = new Quill(container, { 59 theme: 'snow', 60 placeholder: placeholder || '', 61 modules: { toolbar: { container: toolbar } } 62 }); 63 64 if (initialHtml) { 65 quill.clipboard.dangerouslyPasteHTML(initialHtml, 'silent'); 66 } 67 68 if (uploadUrl) { 69 const toolbarModule = quill.getModule('toolbar'); 70 toolbarModule.addHandler('image', () => { 71 const input = document.createElement('input'); 72 input.type = 'file'; 73 input.accept = 'image/png,image/jpeg,image/gif,image/webp,image/svg+xml'; 74 input.addEventListener('change', async () => { 75 const file = input.files && input.files[0]; 76 if (!file) return; 77 78 const formData = new FormData(); 79 formData.append('file', file, file.name); 80 81 try { 82 const res = await fetch(uploadUrl, { 83 method: 'POST', 84 credentials: 'same-origin', 85 body: formData 86 }); 87 88 if (!res.ok) { 89 console.error('Quill image upload failed', res.status, await res.text()); 90 return; 91 } 92 93 const data = await res.json(); 94 if (!data || !data.url) { 95 console.error('Quill image upload: response missing url', data); 96 return; 97 } 98 99 const range = quill.getSelection(true); 100 quill.insertEmbed(range.index, 'image', data.url, 'user'); 101 quill.setSelection(range.index + 1, 0, 'user'); 102 } catch (err) { 103 console.error('Quill image upload error', err); 104 } 105 }); 106 input.click(); 107 }); 108 } 109 110 const handler = () => { 111 const html = quill.root.innerHTML; 112 dotNetRef.invokeMethodAsync('OnContentChangedFromJs', html); 113 }; 114 quill.on('text-change', handler); 115 116 instances.set(id, { quill, dotNetRef, handler }); 117} 118 119export function setContent(id, html) { 120 const entry = instances.get(id); 121 if (!entry) return; 122 123 const current = entry.quill.root.innerHTML; 124 if (current === html) return; 125 126 entry.quill.clipboard.dangerouslyPasteHTML(html || '', 'silent'); 127} 128 129export function dispose(id) { 130 const entry = instances.get(id); 131 if (!entry) return; 132 133 try { 134 entry.quill.off('text-change', entry.handler); 135 } catch (_) { 136 // ignore â Quill may already be torn down 137 } 138 139 instances.delete(id); 140}
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.