1const CreateScriptLoader = () => { 2 let unique = 0; 3 4 const uuid = (prefix) => { 5 const time = Date.now(); 6 const random = Math.floor(Math.random() * 1000000000); 7 unique++; 8 return prefix + '_' + random + unique + String(time); 9 }; 10 const state = { 11 scriptId: uuid('tiny-script'), 12 listeners: [], 13 scriptLoaded: false 14 }; 15 16 const injectScript = (scriptId, doc, url, injectionCallback) => { 17 const scriptTag = doc.createElement('script'); 18 scriptTag.referrerPolicy = 'origin'; 19 scriptTag.type = 'application/javascript'; 20 scriptTag.id = scriptId; 21 scriptTag.src = url; 22 23 const handler = () => { 24 scriptTag.removeEventListener('load', handler); 25 injectionCallback(); 26 }; 27 scriptTag.addEventListener('load', handler); 28 if (doc.head) { 29 doc.head.appendChild(scriptTag); 30 } 31 }; 32 33 const load = (doc, url, cb) => { 34 if (state.scriptLoaded) { 35 cb(); 36 } else { 37 state.listeners.push(cb); 38 if (!doc.getElementById(state.scriptId)) { 39 injectScript(state.scriptId, doc, url, () => { 40 state.listeners.forEach((fn) => fn()); 41 state.scriptLoaded = true; 42 }); 43 } 44 } 45 } 46 47 return { 48 load 49 } 50} 51 52if (!window.tinymceBlazorLoader) { 53 window.tinymceBlazorLoader = CreateScriptLoader(); 54} 55 56const getGlobal = () => (typeof window !== 'undefined' ? window : global); 57 58const getTiny = () => { 59 const global = getGlobal(); 60 return global && global.tinymce ? global.tinymce : null; 61}; 62 63const updateTinyVal = (id, val) => { 64 if (getTiny() && getTiny().get(id).getContent() !== val) { 65 getTiny().get(id).setContent(val); 66 } 67}; 68 69const tinyEventHandler = (() => { 70 const eventCache = {}; 71 const bindEvent = (editor, event, fn) => { 72 if (!eventCache[editor.id]) eventCache[editor.id] = []; 73 eventCache[editor.id].push({ name: event, fn }); 74 editor.on(event, fn); 75 }; 76 const unbindEditor = (editorId) => { 77 if (!eventCache[editorId]) return; 78 const editor = getTiny() && getTiny().get(editorId); 79 if (editor) { 80 eventCache[editorId].forEach((event) => { 81 editor.off(event.name, event.fn); 82 }); 83 } 84 delete eventCache[editorId]; 85 }; 86 return { 87 bindEvent, 88 unbindEditor 89 } 90})(); 91 92const isElementReady = (el) => { 93 if (!el || !el.offsetParent && el.offsetWidth === 0 && el.offsetHeight === 0) return false; 94 const rect = el.getBoundingClientRect(); 95 return rect.width > 0 && rect.height > 0; 96}; 97 98const waitForElementReady = (el, timeoutMs = 2000) => { 99 return new Promise((resolve) => { 100 if (isElementReady(el)) { 101 resolve(); 102 return; 103 } 104 const start = Date.now(); 105 const poll = () => { 106 if (isElementReady(el)) { 107 resolve(); 108 } else if (Date.now() - start > timeoutMs) { 109 resolve(); // proceed anyway after timeout 110 } else { 111 requestAnimationFrame(poll); 112 } 113 }; 114 requestAnimationFrame(poll); 115 }); 116}; 117 118const chunkMap = (() => { 119 const map = new Map(); 120 const next = (streamId, editorId, val, index, size) => { 121 const acc = (map.has(streamId) ? map.get(streamId) : "") + val; 122 if (index === size) { 123 updateTinyVal(editorId, acc); 124 map.delete(streamId); 125 } else { 126 map.set(streamId, acc); 127 } 128 }; 129 return { 130 push: next 131 }; 132})(); 133 134window.tinymceBlazorWrapper = { 135 insertContent: (id, content, args) => { 136 const tiny = getTiny().get(id); 137 tiny?.insertContent(content, args); 138 }, 139 updateMode: (id, disable) => { 140 const tiny = getTiny().get(id); 141 if (tiny.mode && typeof tiny.mode.set === 'function') { 142 tiny.mode.set(disable ? 'readonly' : 'design'); 143 } else { 144 tiny.setMode(disable ? 'readonly' : 'design'); 145 } 146 }, 147 updateValue: (id, streamId, value, index, chunks) => { 148 chunkMap.push(streamId, id, value, index, chunks); 149 }, 150 init: (el, blazorConf, dotNetRef) => { 151 const chunkSize = 16 * 1024; 152 const update = (format, content) => { 153 const updateFn = format === 'text' ? 'UpdateText' : 'UpdateModel'; 154 const chunks = Math.floor(content.length / chunkSize) + 1; 155 const streamId = (Date.now() % 100000) + ''; 156 for (let i = 0; i < chunks; i++) { 157 const chunk = content.substring(chunkSize * i, chunkSize * (i + 1)); 158 dotNetRef.invokeMethodAsync(updateFn, streamId, i + 1, chunk, chunks); 159 } 160 }; 161 const getJsObj = (objectPath) => { 162 const jsConf = (objectPath !== null && typeof objectPath === 'string') ? objectPath.split('.').reduce((acc, current) => { 163 return acc !== undefined ? acc[current] : undefined; 164 }, window) : undefined; 165 return (jsConf !== undefined && typeof jsConf === 'object') ? jsConf : {}; 166 }; 167 const tinyConf = { ...getJsObj(blazorConf.jsConf), ...blazorConf.conf }; 168 tinyConf.inline = blazorConf.inline; 169 tinyConf.readonly = blazorConf.disabled; 170 tinyConf.target = el; 171 tinyConf._setup = tinyConf.setup; 172 173 tinyConf.setup = (editor) => { 174 // Bind init event - critical fix: call OnEditorReady in finally block 175 tinyEventHandler.bindEvent(editor, 'init', (e) => { 176 dotNetRef.invokeMethodAsync('GetValue') 177 .then(value => { 178 editor.setContent(value); 179 }) 180 .catch(err => { 181 console.error('[TinyMCE] GetValue() or setContent() failed:', err); 182 }) 183 .finally(() => { 184 // CRITICAL: Signal that editor is fully ready 185 // This prevents race conditions where user types before events are bound 186 dotNetRef.invokeMethodAsync('OnEditorReady') 187 .catch(err => { 188 console.error('[TinyMCE] OnEditorReady() callback failed:', err); 189 }); 190 }); 191 }); 192 193 // Bind change and input events 194 tinyEventHandler.bindEvent(editor, 'change', (e) => { 195 dotNetRef.invokeMethodAsync('OnChange'); 196 }); 197 tinyEventHandler.bindEvent(editor, 'input', (e) => { 198 dotNetRef.invokeMethodAsync('OnInput'); 199 }); 200 201 tinyEventHandler.bindEvent(editor, 'setcontent', (e) => { 202 const textContent = editor.getContent({ format: 'text' }); 203 update('text', textContent); 204 }); 205 tinyEventHandler.bindEvent(editor, blazorConf.modelEvents, (e) => { 206 const htmlContent = editor.getContent(); 207 const textContent = editor.getContent({ format: 'text' }); 208 update('html', htmlContent); 209 update('text', textContent); 210 }); 211 212 if (tinyConf._setup && typeof tinyConf._setup === 'function') { 213 tinyConf._setup(editor, tinyEventHandler); 214 } 215 }; 216 217 const doInit = () => getTiny().init(tinyConf); 218 const initWhenReady = () => { 219 if (isElementReady(el)) { 220 doInit(); 221 } else { 222 waitForElementReady(el).then(doInit); 223 } 224 }; 225 226 if (getTiny()) { 227 initWhenReady(); 228 } else { 229 if (el && el.ownerDocument) { 230 window.tinymceBlazorLoader.load(el.ownerDocument, blazorConf.src, () => { 231 initWhenReady(); 232 }); 233 } 234 } 235 }, 236 destroy: (el, id) => { 237 if (getTiny() && getTiny().get(id)) { 238 tinyEventHandler.unbindEditor(id); 239 getTiny().get(id).remove(); 240 } 241 } 242}
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.