`,body:``,removeOnClose:!0}).done((modal=>{modal.getRoot().addClass("tiny-cursive-modal"),modal.show();var lastEvent="";return modal.getRoot().on(_modal_events.save,(function(){var number=document.getElementById("inputUrl").value.trim();""===number||null==number?(editor.execCommand("Undo"),(0,_str.get_string)("pastewarning","tiny_cursive").then((str=>alert(str)))):editor.execCommand("Paste"),postOne("cursive_user_comments",{modulename:modulename,cmid:cmid,resourceid:resourceId,courseid:courseid,usercomment:number,timemodified:Date.now(),editorid:editorid||""}),lastEvent="save",modal.destroy()})),modal.getRoot().on(_modal_events.cancel,(function(){editor.execCommand("Undo"),lastEvent="cancel"})),modal.getRoot().on(_modal_events.hidden,(function(){"cancel"!=lastEvent&&"save"!=lastEvent&&editor.execCommand("Undo")})),modal}))})).catch((error=>window.console.error(error)))},sendKeyEvent=(events,editor)=>{if(ed=editor,event=events,filename=`${userid}_${resourceId}_${cmid}_${modulename}_attempt`,"quiz"===modulename&&(questionid=editorid.split(":")[1].split("_")[0],filename=`${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`),localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,editor.key=function(editor){switch(editor.button){case 0:return"left";case 1:return"middle";case 2:return"right"}return null}(editor),editor.keyCode=editor.button}function getCaretPosition(){let skip=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{if(!editor||!editor.selection)return{caretPosition:0,rePosition:0};const range=editor.selection.getRng(),body=editor.getBody(),preCaretRange=range.cloneRange();preCaretRange.selectNodeContents(body),preCaretRange.setEnd(range.endContainer,range.endOffset);const fragment=preCaretRange.cloneContents(),tempDiv=document.createElement("div");tempDiv.appendChild(fragment);let textBeforeCursor=tempDiv.innerText||"";const endContainer=range.endContainer;0===range.endOffset&&endContainer.nodeType===Node.ELEMENT_NODE&&editor.dom.isBlock(endContainer)&&endContainer.previousSibling&&(textBeforeCursor+="\n");const blockElements=tempDiv.querySelectorAll("p, div, h1, h2, h3, h4, h5, h6, li");let emptyBlockCount=0;blockElements.forEach((block=>{""===(block.innerText||block.textContent||"").trim()&&1===block.childNodes.length&&"BR"===block.childNodes[0].nodeName&&emptyBlockCount++})),emptyBlockCount>0&&(textBeforeCursor+="\n".repeat(emptyBlockCount));const absolutePosition=textBeforeCursor.length;if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey=`${userid}_${resourceId}_${cmid}_position`;let storedPos=parseInt(sessionStorage.getItem(storageKey),10);return isNaN(storedPos)&&(storedPos=0),storedPos++,lastCaretPos=storedPos,sessionStorage.setItem(storageKey,storedPos),{caretPosition:storedPos,rePosition:absolutePosition}}catch(e){return window.console.warn("Error getting caret position:",e),{caretPosition:lastCaretPos||1,rePosition:0}}}async function syncData(){let data=localStorage.getItem(filename);if(data&&0!==data.length){localStorage.removeItem(filename);let originalText=editor.getContent({format:"text"});try{return _cursive_autosave.default.updateSavingState("saving"),await postOne("cursive_write_local_to_json",{key:ed.key,event:event,keyCode:ed.keyCode,resourceId:resourceId,cmid:cmid,modulename:modulename,editorid:editorid,json_data:data,originalText:originalText})}catch(error){window.console.error("Error submitting data:",error)}}}function customTooltip(){try{const tooltipText=async function(){const[buttonTitle,buttonDes]=await Promise.all([(0,_str.get_string)("cursive:state:active","tiny_cursive"),(0,_str.get_string)("cursive:state:active:des","tiny_cursive")]);return{buttonTitle:buttonTitle,buttonDes:buttonDes}}(),menubarDiv=document.querySelectorAll('div[role="menubar"].tox-menubar');let classArray=[];menubarDiv.length&&menubarDiv.forEach((function(element,index){let className="cursive-menu-"+(index+=1);element.classList.add(className),classArray.push(className)}));const cursiveIcon=document.createElement("img");cursiveIcon.src=hasApiKey?_common.iconUrl:_common.iconGrayUrl,cursiveIcon.setAttribute("class","tiny_cursive_StateButton"),cursiveIcon.style.display="inline-block",function(cursiveIcon,menubarDiv,classArray){if(!menubarDiv)return;for(let index in classArray){const rightWrapper=document.createElement("div"),imgWrapper=document.createElement("span"),iconClone=cursiveIcon.cloneNode(!0),targetMenu=document.querySelector("."+classArray[index]);let elementId="tiny_cursive_StateIcon"+index;rightWrapper.style.cssText="\n margin-left: auto;\n display: flex;\n align-items: center;\n ",imgWrapper.id=elementId,imgWrapper.style.marginLeft=".2rem",imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper);let moduleIds={resourceId:resourceId,cmid:cmid,modulename:modulename,questionid:questionid,userid:userid,courseid:courseid};if(!isFullScreen||"assign"!==modulename&&"forum"!==modulename&&"lesson"!==modulename)if(isFullScreen&&"quiz"===modulename){var _editor$container,_editor$container$chi,_editor$container$chi2,_editor$container$chi3,_editor$container2;let existingElement=null===(_editor$container=editor.container)||void 0===_editor$container||null===(_editor$container$chi=_editor$container.childNodes[1])||void 0===_editor$container$chi||null===(_editor$container$chi2=_editor$container$chi.childNodes[0])||void 0===_editor$container$chi2||null===(_editor$container$chi3=_editor$container$chi2.childNodes[0])||void 0===_editor$container$chi3?void 0:_editor$container$chi3.childNodes[7],newHeader=null===(_editor$container2=editor.container)||void 0===_editor$container2?void 0:_editor$container2.childNodes[0];existingElement&&existingElement.remove(),newHeader&&!newHeader.querySelector("span[id*=tiny_cursive_StateIcon]")&&(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{var _editor$container3,_editor$container3$ch,_editor$container3$ch2;let menubar=null==editor||null===(_editor$container3=editor.container)||void 0===_editor$container3||null===(_editor$container3$ch=_editor$container3.children[0])||void 0===_editor$container3$ch||null===(_editor$container3$ch2=_editor$container3$ch.childNodes[0])||void 0===_editor$container3$ch2?void 0:_editor$container3$ch2.childNodes[0];if(targetMenu&&!targetMenu.querySelector(`#${elementId}`)&&targetMenu.appendChild(rightWrapper),"quiz"===modulename&&menubar){let wrapper=menubar.querySelector('span[id*="tiny_cursive_StateIcon"]');wrapper&&(_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,null==wrapper?void 0:wrapper.parentElement,moduleIds,isFullScreen))}else _cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{let existsElement=document.querySelector('.tox-menubar[class*="cursive-menu-"] > div');existsElement&&existsElement.remove(),document.querySelector(`#${elementId}`)||(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId=`tiny_cursive_tooltip${index}`;tooltipText.then((text=>setTooltip(text,document.querySelector(`#${elementId}`),tooltipId))).catch((error=>window.console.error(error))),(0,_jquery.default)(`#${elementId}`).on("mouseenter",(function(){(0,_jquery.default)(this).css("position","relative"),(0,_jquery.default)(`#${tooltipId}`).css(_common.tooltipCss)})),(0,_jquery.default)(`#${elementId}`).on("mouseleave",(function(){(0,_jquery.default)(`#${tooltipId}`).css("display","none")}))}}catch(error){window.console.error("Error setting up custom tooltip:",error)}}function setTooltip(text,cursiveIcon,tooltipId){if(!document.querySelector(`#${tooltipId}`)&&cursiveIcon){const tooltipSpan=document.createElement("span"),description=document.createElement("span"),linebreak=document.createElement("br"),tooltipTitle=document.createElement("strong");tooltipSpan.style.display="none",tooltipTitle.textContent=text.buttonTitle,tooltipTitle.style.fontSize="16px",tooltipTitle.style.fontWeight="bold",description.textContent=text.buttonDes,description.style.fontSize="14px",tooltipSpan.id=tooltipId,tooltipSpan.classList.add("shadow"),tooltipSpan.appendChild(tooltipTitle),tooltipSpan.appendChild(linebreak),tooltipSpan.appendChild(description),cursiveIcon.appendChild(tooltipSpan)}}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyUp",editor)})),editor.on("Paste",(async e=>{customTooltip();const pastedContent=(e.clipboardData||e.originalEvent.clipboardData).getData("text");if(!pastedContent)return;const trimmedPastedContent=pastedContent.trim(),lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&trimmedPastedContent===lastCopyCutContent;if(isStudent&&intervention){if("block"===PASTE_SETTING)return isFromOwnEditor?(shouldBlockPaste=!1,void(isPasteAllowed=!0)):(e.preventDefault(),shouldBlockPaste=!0,isPasteAllowed=!1,e.stopPropagation(),e.stopImmediatePropagation(),(0,_str.get_string)("paste_blocked","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error))),void setTimeout((()=>{isPasteAllowed=!0,shouldBlockPaste=!1}),100));if("cite_source"===PASTE_SETTING)return isFromOwnEditor||(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),getModal()),void(isPasteAllowed=!0)}isPasteAllowed=!0})),editor.on("Redo",(async e=>{customTooltip(),isStudent&&intervention&&getModal()})),editor.on("keyDown",(editor=>{customTooltip();if(("v"===editor.key||"V"===editor.key)&&(editor.ctrlKey||editor.metaKey)&&isStudent&&intervention&&"block"===PASTE_SETTING&&!isPasteAllowed)return void setTimeout((()=>{isPasteAllowed=!0}),100);let position=getCaretPosition();editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyDown",editor)})),editor.on("Cut",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("Copy",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("mouseDown",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)}),0)})),editor.on("mouseUp",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)}),10)})),editor.on("init",(()=>{customTooltip(),localStorage.removeItem("lastCopyCutContent")})),editor.on("SetContent",(()=>{customTooltip()})),editor.on("FullscreenStateChanged",(e=>{let view=new _document_view.default(user,Rubrics,submission,modulename,editor,quizInfo);isFullScreen=e.state;try{e.state?view.fullPageMode():view.normalMode()}catch(error){errorAlert&&(errorAlert=!1,(0,_str.get_string)("fullmodeerror","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))),view.normalMode(),window.console.error("Error ResizeEditor event:",error)}})),editor.on("execcommand",(function(e){if("mceInsertContent"===e.command){const contentObj=e.value,isPaste=contentObj&&"object"==typeof contentObj&&!0===contentObj.paste;let insertedContent=contentObj.content||contentObj,tempDiv=document.createElement("div");tempDiv.innerHTML=insertedContent;let text=tempDiv.textContent||tempDiv.innerText||"",pastedText=tempDiv.textContent||tempDiv.innerText||"",position=getCaretPosition(!0);if(editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,isPaste){if(shouldBlockPaste)return shouldBlockPaste=!1,e.preventDefault(),void editor.undoManager.undo();const lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&pastedText.trim()===lastCopyCutContent;if(isStudent&&intervention&&"block"===PASTE_SETTING&&!isFromOwnEditor)return isPasteAllowed=!1,void editor.undoManager.undo();sendKeyEvent("Paste",{key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition,pastedContent:pastedText,srcElement:{baseURI:window.location.href}})}else aiContents.push(text),sendKeyEvent("aiInsert",{key:"ai",keyCode:0,caretPosition:editor.caretPosition,rePosition:editor.rePosition,aiContent:text,srcElement:{baseURI:window.location.href}})}})),editor.on("input",(function(e){let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition;let aiContent=e.data;("insertReplacementText"===e.inputType||"insertText"===e.inputType&&aiContent&&aiContent.length>1)&&(aiContents.push(aiContent),e.key="ai",e.keyCode=0,e.caretPosition=position.caretPosition,e.rePosition=position.rePosition,e.aiContent=aiContent,sendKeyEvent("aiInsert",e))})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}}));
//# sourceMappingURL=autosaver.min.js.map
\ No newline at end of file
diff --git a/amd/build/autosaver.min.js.map b/amd/build/autosaver.min.js.map
index 31ebb527..3282341f 100644
--- a/amd/build/autosaver.min.js.map
+++ b/amd/build/autosaver.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"autosaver.min.js","sources":["../src/autosaver.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module tiny_cursive/autosaver\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\nimport {call} from 'core/ajax';\nimport {create} from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\nimport {save, cancel, hidden} from 'core/modal_events';\nimport $ from 'jquery';\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\nimport Autosave from 'tiny_cursive/cursive_autosave';\nimport DocumentView from 'tiny_cursive/document_view';\nimport {call as getUser} from \"core/ajax\";\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\n\n var isStudent = !($('#body').hasClass('teacher_admin'));\n var intervention = $('#body').hasClass('intervention');\n var host = M.cfg.wwwroot;\n var userid = userId;\n var courseid = M.cfg.courseId;\n var editorid = editor?.id;\n var cmid = M.cfg.contextInstanceId;\n var ed = \"\";\n var event = \"\";\n var filename = \"\";\n var questionid = 0;\n var quizSubmit = $('#mod_quiz-next-nav');\n let assignSubmit = $('#id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let aiContents = [];\n var isFullScreen = false;\n var user = null;\n let ur = window.location.href;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n var resourceId = modulesInfo.resourceId;\n var modulename = modulesInfo.name;\n var errorAlert = true;\n let PASTE_SETTING = pasteSetting || 'allow';\n let shouldBlockPaste = false;\n let isPasteAllowed = false;\n\n if (modulename !== 'assign') {\n PASTE_SETTING = 'cite_source';\n }\n const postOne = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n if (response) {\n setTimeout(() => {\n Autosave.updateSavingState('saved');\n }, 1000);\n }\n return response;\n } catch (error) {\n Autosave.updateSavingState('offline');\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n getUser([{\n methodname: 'core_user_get_users_by_field',\n args: {field: 'id', values: [M.cfg.userId]},\n }])[0].done(response => {\n user = response[0];\n }).fail((ex) => {\n window.console.error('Error fetching user data:', ex);\n });\n\n assignSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n assignSubmit.off('click').click();\n });\n } else {\n assignSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n quizSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n quizSubmit.off('click').click();\n });\n } else {\n quizSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n const getModal = () => {\n\n Promise.all([\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\n getString('tiny_cursive_placeholder', 'tiny_cursive')\n ]).then(function([title, titledes, placeholder]) {\n\n return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n })\n .done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n\n modal.getRoot().on(save, function() {\n\n var number = document.getElementById(\"inputUrl\").value.trim();\n\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n // eslint-disable-next-line\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\n }\n\n postOne('cursive_user_comments', {\n modulename: modulename,\n cmid: cmid,\n resourceid: resourceId,\n courseid: courseid,\n usercomment: number,\n timemodified: Date.now(),\n editorid: editorid ? editorid : \"\"\n });\n\n lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\n });\n\n modal.getRoot().on(hidden, function() {\n if (lastEvent != 'cancel' && lastEvent != 'save') {\n editor.execCommand('Undo');\n }\n });\n return modal;\n });\n }).catch(error => window.console.error(error));\n\n };\n\n const sendKeyEvent = (events, editor) => {\n ed = editor;\n event = events;\n\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\n editor.on('Paste', async(e) => {\n customTooltip();\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\n if (!pastedContent) {\n return;\n }\n // Trim both values for consistent comparison\n const trimmedPastedContent = pastedContent.trim();\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\n\n if (isStudent && intervention) {\n\n if (PASTE_SETTING === 'block') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n shouldBlockPaste = true;\n isPasteAllowed = false;\n e.stopPropagation();\n e.stopImmediatePropagation();\n getString('paste_blocked', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n setTimeout(() => {\n isPasteAllowed = true;\n shouldBlockPaste = false;\n }, 100);\n return;\n }\n shouldBlockPaste = false;\n isPasteAllowed = true;\n return;\n }\n if (PASTE_SETTING === 'cite_source') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n getModal(e);\n }\n isPasteAllowed = true;\n return;\n }\n }\n isPasteAllowed = true;\n });\n editor.on('Redo', async(e) => {\n customTooltip();\n if (isStudent && intervention) {\n getModal(e);\n }\n });\n editor.on('keyDown', (editor) => {\n customTooltip();\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\n (editor.ctrlKey || editor.metaKey);\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\n setTimeout(() => {\n isPasteAllowed = true;\n }, 100);\n return;\n }\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('mouseDown', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n }, 0);\n });\n editor.on('mouseUp', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n }, 10);\n });\n editor.on('init', () => {\n customTooltip();\n localStorage.removeItem('lastCopyCutContent');\n });\n editor.on('SetContent', () => {\n customTooltip();\n });\n editor.on('FullscreenStateChanged', (e) => {\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\n isFullScreen = e.state;\n try {\n if (!e.state) {\n view.normalMode();\n } else {\n view.fullPageMode();\n }\n } catch (error) {\n if (errorAlert) {\n errorAlert = false;\n getString('fullmodeerror', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n view.normalMode();\n window.console.error('Error ResizeEditor event:', error);\n }\n });\n\n editor.on('execcommand', function(e) {\n if (e.command === \"mceInsertContent\") {\n const contentObj = e.value;\n\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\n\n let insertedContent = contentObj.content || contentObj;\n let tempDiv = document.createElement('div');\n tempDiv.innerHTML = insertedContent;\n let text = tempDiv.textContent || tempDiv.innerText || '';\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\n\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n\n if (isPaste) {\n if (shouldBlockPaste) {\n shouldBlockPaste = false;\n e.preventDefault();\n editor.undoManager.undo();\n return;\n }\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\n\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\n isPasteAllowed = false;\n editor.undoManager.undo();\n return;\n }\n\n sendKeyEvent(\"Paste\", {\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n pastedContent: pastedText,\n srcElement: {baseURI: window.location.href}\n });\n } else {\n aiContents.push(text);\n\n sendKeyEvent(\"aiInsert\", {\n key: \"ai\",\n keyCode: 0,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n aiContent: text,\n srcElement: {baseURI: window.location.href}\n });\n }\n }\n });\n\n editor.on('input', function(e) {\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n let aiContent = e.data;\n\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\n\n aiContents.push(aiContent);\n\n e.key = \"ai\";\n e.keyCode = 0;\n e.caretPosition = position.caretPosition;\n e.rePosition = position.rePosition;\n e.aiContent = aiContent;\n\n sendKeyEvent(\"aiInsert\", e);\n }\n });\n\n\n /**\n * Constructs a mouse event object with caret position and button information\n * @param {Object} editor - The TinyMCE editor instance\n * @function constructMouseEvent\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\n */\n function constructMouseEvent(editor) {\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n editor.key = getMouseButton(editor);\n editor.keyCode = editor.button;\n }\n\n /**\n * Gets the string representation of a mouse button based on its numeric value\n * @param {Object} editor - The editor object containing button information\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\n */\n function getMouseButton(editor) {\n\n switch (editor.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n }\n return null;\n }\n\n /**\n * Gets the current caret position in the editor\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\n * @returns {Object} Object containing:\n * - caretPosition: Sequential position number stored in session\n * - rePosition: Absolute character offset from start of content\n * @throws {Error} Logs warning to console if error occurs during calculation\n */\n function getCaretPosition(skip = false) {\n try {\n if (!editor || !editor.selection) {\n return {caretPosition: 0, rePosition: 0};\n }\n\n const range = editor.selection.getRng();\n const body = editor.getBody();\n\n // Create a range from start of document to current caret\n const preCaretRange = range.cloneRange();\n preCaretRange.selectNodeContents(body);\n preCaretRange.setEnd(range.endContainer, range.endOffset);\n\n const fragment = preCaretRange.cloneContents();\n const tempDiv = document.createElement('div');\n tempDiv.appendChild(fragment);\n let textBeforeCursor = tempDiv.innerText || '';\n\n const endContainer = range.endContainer;\n const endOffset = range.endOffset;\n\n if (endOffset === 0 &&\n endContainer.nodeType === Node.ELEMENT_NODE &&\n editor.dom.isBlock(endContainer) &&\n endContainer.previousSibling) {\n textBeforeCursor += '\\n';\n }\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\n let emptyBlockCount = 0;\n blockElements.forEach(block => {\n const text = block.innerText || block.textContent || '';\n if (text.trim() === '' && block.childNodes.length === 1 &&\n block.childNodes[0].nodeName === 'BR') {\n emptyBlockCount++;\n }\n });\n\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\n if (emptyBlockCount > 0) {\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\n }\n\n const absolutePosition = textBeforeCursor.length;\n\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\n // Increment sequential caretPosition\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\n if (isNaN(storedPos)) {\n storedPos = 0;\n }\n storedPos++;\n lastCaretPos = storedPos;\n sessionStorage.setItem(storageKey, storedPos);\n\n return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\n }\n }\n\n\n /**\n * Synchronizes data from localStorage to server\n * @async\n * @function SyncData\n * @description Retrieves stored keypress data from localStorage and sends it to server\n * @returns {Promise} Returns response from server if data exists and is successfully sent\n * @throws {Error} Logs error to console if data submission fails\n */\n async function syncData() {\n\n let data = localStorage.getItem(filename);\n\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n let originalText = editor.getContent({format: 'text'});\n try {\n Autosave.updateSavingState('saving');\n // eslint-disable-next-line\n return await postOne('cursive_write_local_to_json', {\n key: ed.key,\n event: event,\n keyCode: ed.keyCode,\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n editorid: editorid,\n \"json_data\": data,\n originalText: originalText\n });\n } catch (error) {\n window.console.error('Error submitting data:', error);\n }\n }\n }\n /**\n * Sets up custom tooltip functionality for the Cursive icon\n * Initializes tooltip text, positions the icon in the menubar,\n * and sets up mouse event handlers for showing/hiding the tooltip\n * @function customTooltip\n */\n function customTooltip() {\n try {\n const tooltipText = getTooltipText();\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\n let classArray = [];\n\n if (menubarDiv.length) {\n menubarDiv.forEach(function(element, index) {\n index += 1;\n let className = 'cursive-menu-' + index;\n element.classList.add(className);\n classArray.push(className);\n });\n }\n\n const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n\n cursiveState(cursiveIcon, menubarDiv, classArray);\n\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n\n $(`#${elementId}`).on('mouseenter', function() {\n $(this).css('position', 'relative');\n $(`#${tooltipId}`).css(tooltipCss);\n });\n\n $(`#${elementId}`).on('mouseleave', function() {\n $(`#${tooltipId}`).css('display', 'none');\n });\n }\n } catch (error) {\n window.console.error('Error setting up custom tooltip:', error);\n }\n }\n\n /**\n * Retrieves tooltip text strings from language files\n * @async\n * @function getTooltipText\n * @returns {Promise