From 1b45e837a89871352da45a3edfe813f89c73083b Mon Sep 17 00:00:00 2001 From: Shahriar075 Date: Thu, 5 Feb 2026 14:24:11 +0600 Subject: [PATCH] Add indexing to all database tables --- amd/build/analytic_events.min.js | 2 +- amd/build/analytic_events.min.js.map | 2 +- amd/build/analytic_modal.min.js | 9 +-- amd/build/analytic_modal.min.js.map | 2 +- amd/build/autosaver.min.js | 2 +- amd/build/autosaver.min.js.map | 2 +- amd/build/common.min.js | 2 +- amd/build/common.min.js.map | 2 +- amd/build/replay.min.js | 2 +- amd/build/replay.min.js.map | 2 +- amd/build/scatter_chart.min.js | 2 +- amd/build/scatter_chart.min.js.map | 2 +- amd/build/texteditor.min.js | 2 +- amd/build/texteditor.min.js.map | 2 +- amd/build/token_approve.min.js | 2 +- amd/build/token_approve.min.js.map | 2 +- db/install.xml | 19 ++++++ db/upgrade.php | 92 ++++++++++++++++++++++++++++ version.php | 2 +- 19 files changed, 128 insertions(+), 24 deletions(-) diff --git a/amd/build/analytic_events.min.js b/amd/build/analytic_events.min.js index 5d619932..3df044cc 100644 --- a/amd/build/analytic_events.min.js +++ b/amd/build/analytic_events.min.js @@ -7,6 +7,6 @@ define("tiny_cursive/analytic_events",["exports","./analytic_modal","core/ajax", * @module tiny_cursive/analytic_events * @copyright 2024 CTI * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_analytic_modal=(obj=_analytic_modal)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(){(0,_str.get_string)("notenoughtinfo","tiny_cursive").then((str=>(localStorage.setItem("notenoughtinfo",str),str)))}createModal(userid,context){let questionid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",replayInstances=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,authIcon=arguments.length>4?arguments[4]:void 0;const element=document.getElementById("analytics"+userid+questionid);element&&element.addEventListener("click",(function(e){e.preventDefault(),_analytic_modal.default.create({templateContext:context}).then((modal=>{const content=document.querySelector("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)");content&&(content.innerHTML=authIcon.outerHTML),modal.show();const moreBtn=document.querySelector("body #more"+userid+questionid);if(moreBtn){document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active")));const analyticBtn=document.getElementById("analytic"+userid+questionid),diffBtn=document.getElementById("diff"+userid+questionid);analyticBtn&&(analyticBtn.disabled=!0,analyticBtn.style.backgroundColor="rgba(168, 168, 168, 0.133)",analyticBtn.style.cursor="not-allowed"),diffBtn&&(diffBtn.disabled=!0,diffBtn.style.backgroundColor="rgba(168, 168, 168, 0.133)",diffBtn.style.cursor="not-allowed"),moreBtn.addEventListener("click",(function(){document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active"))),this.classList.add("active");const repBtn=document.getElementById("rep"+userid+questionid);repBtn&&(repBtn.disabled=!1),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay()}))}})).catch((error=>{window.console.error("Failed to create modal:",error)}))}))}analytics(userid,templates,context){let questionid=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"",replayInstances=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,authIcon=arguments.length>5?arguments[5]:void 0;document.body.addEventListener("click",(function(e){if(e.target&&e.target.id==="analytic"+userid+questionid){if(e.preventDefault(),e.target.disabled)return;const repBtn=document.getElementById("rep"+userid+questionid);repBtn&&(repBtn.disabled=!1);const qualityBtn=document.getElementById("quality"+userid+questionid);qualityBtn&&(qualityBtn.disabled=!1);const content=document.getElementById("content"+userid);if(content){content.setAttribute("data-label","analytics"),content.classList.remove("tiny_cursive_outputElement"),content.classList.add("tiny_cursive"),content.setAttribute("data-label","analytics");const player=document.getElementById("player_"+userid+questionid);player&&(player.style.display="none"),content.innerHTML="";const loaderDiv=document.createElement("div");loaderDiv.className="d-flex justify-content-center my-5";const loader=document.createElement("div");loader.className="tiny_cursive-loader",loaderDiv.appendChild(loader),content.appendChild(loaderDiv)}replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active"))),e.target.classList.add("active"),templates.render("tiny_cursive/analytics_table",context).then((function(html){const content=document.getElementById("content"+userid);content&&(content.innerHTML=html);const firstCell=document.querySelector("#content"+userid+" .tiny_cursive_table").querySelector("tbody tr:first-child").querySelector("td:nth-child(2)");return firstCell&&(firstCell.innerHTML=authIcon.outerHTML),!0})).catch((function(error){window.console.error("Failed to render template:",error)}))}}))}checkDiff(userid,fileid){let questionid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",replayInstances=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;const nodata=document.createElement("p");nodata.classList.add("tiny_cursive_nopayload","bg-light"),(0,_str.get_string)("nopaylod","tiny_cursive").then((str=>(nodata.textContent=str,!0))).catch((error=>window.console.log(error))),document.body.addEventListener("click",(function(e){if(e.target&&e.target.id==="diff"+userid+questionid){if(e.preventDefault(),e.target.disabled)return;const repElement=document.getElementById("rep"+userid+questionid);repElement&&(repElement.disabled=!1);const qualityElement=document.getElementById("quality"+userid+questionid);qualityElement&&(qualityElement.disabled=!1);const content=document.getElementById("content"+userid);if(content){content.setAttribute("data-label","diff"),content.classList.remove("tiny_cursive_outputElement"),content.classList.add("tiny_cursive"),content.innerHTML="";const loaderDiv=document.createElement("div");loaderDiv.className="d-flex justify-content-center my-5";const loader=document.createElement("div");loader.className="tiny_cursive-loader",loaderDiv.appendChild(loader),content.appendChild(loaderDiv)}const player=document.getElementById("player_"+userid+questionid);if(player&&(player.style.display="none"),document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active"))),e.target.classList.add("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),!fileid)throw content&&(content.innerHTML="",content.appendChild(nodata)),new Error("Missing file id or Difference Content not received yet");(0,_ajax.call)([{methodname:"cursive_get_writing_differences",args:{fileid:fileid}}])[0].then((response=>{let responsedata=JSON.parse(response.data);if(responsedata){let submittedText=atob(responsedata.submitted_text);(0,_str.get_strings)([{key:"original_text",component:"tiny_cursive"},{key:"editspastesai",component:"tiny_cursive"}]).then((strings=>{const originalTextString=strings[0],editsPastesAIString=strings[1],commentBox=document.createElement("div");commentBox.className="p-2 border rounded mb-2";const pasteCountDiv=document.createElement("div");(0,_str.get_string)("pastecount","tiny_cursive").then((str=>(pasteCountDiv.innerHTML=`
${str} : ${responsedata.commentscount}
`,!0))).catch((error=>window.console.log(error)));const commentsDiv=document.createElement("div");commentsDiv.className="border-bottom",(0,_str.get_string)("comments","tiny_cursive").then((str=>(commentsDiv.innerHTML=`${str}`,!0))).catch((error=>window.console.error(error)));const commentsList=document.createElement("div"),comments=responsedata.comments;for(let index in comments){const commentDiv=document.createElement("div");commentDiv.style.cssText="word-wrap: break-word; word-break: break-word",commentDiv.className="shadow-sm p-1 my-1",commentDiv.textContent=comments[index].usercomment,commentsList.appendChild(commentDiv)}commentBox.appendChild(pasteCountDiv),commentBox.appendChild(commentsDiv),commentBox.appendChild(commentsList);const legend=document.createElement("div");legend.className="d-flex p-2 border rounded mb-2";const attributedItem=document.createElement("div");attributedItem.className="tiny_cursive-legend-item";const attributedBox=document.createElement("div");attributedBox.className="tiny_cursive-box attributed";const attributedText=document.createElement("span");attributedText.textContent=originalTextString,attributedItem.appendChild(attributedBox),attributedItem.appendChild(attributedText);const unattributedItem=document.createElement("div");unattributedItem.className="tiny_cursive-legend-item";const unattributedBox=document.createElement("div");unattributedBox.className="tiny_cursive-box tiny_cursive_added";const unattributedText=document.createElement("span");unattributedText.textContent=editsPastesAIString,unattributedItem.appendChild(unattributedBox),unattributedItem.appendChild(unattributedText),legend.appendChild(attributedItem),legend.appendChild(unattributedItem);const contents=document.createElement("div");contents.className="tiny_cursive-comparison-content";const textBlock2=document.createElement("div");textBlock2.className="tiny_cursive-text-block";const reconstructedText=document.createElement("div");reconstructedText.id="tiny_cursive-reconstructed_text",reconstructedText.innerHTML=JSON.parse(submittedText),textBlock2.appendChild(reconstructedText),contents.appendChild(commentBox),contents.appendChild(legend),contents.appendChild(textBlock2),content&&(content.innerHTML="",content.appendChild(contents))})).catch((error=>{window.console.error("Failed to load language strings:",error),content&&(content.innerHTML="",content.appendChild(nodata))}))}else content&&(content.innerHTML="",content.appendChild(nodata))})).catch((error=>{throw content&&(content.innerHTML="",content.appendChild(nodata)),new Error("Error loading JSON file: "+error.message)}))}}))}replyWriting(userid,filepath){let questionid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",replayInstances=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;document.body.addEventListener("click",(function(e){if(e.target&&e.target.id==="rep"+userid+questionid){e.preventDefault();const replyBtn=document.getElementById("rep"+userid+questionid),qualityBtn=document.getElementById("quality"+userid+questionid),content=document.getElementById("content"+userid),player=document.getElementById("player_"+userid+questionid),replayControls=document.getElementById("replayControls_"+userid+questionid);filepath&&(replayControls&&replayControls.classList.remove("d-none"),content&&content.classList.add("tiny_cursive_outputElement")),replyBtn&&(replyBtn.disabled=!0),qualityBtn&&(qualityBtn.disabled=!1),content&&(content.dataset.label="replay"),player&&(player.style.display="block",player.style.paddingRight="8px"),content&&(content.innerHTML='\n
\n
\n
'),document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active"))),e.target.classList.add("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),questionid?video_playback(userid,filepath,questionid):video_playback(userid,filepath)}}))}formatedTime(data){if(data.total_time_seconds){let totalTimeSeconds=data.total_time_seconds;return`${Math.floor(totalTimeSeconds/3600).toString().padStart(2,0)}h ${Math.floor(totalTimeSeconds%3600/60).toString().padStart(2,0)}m ${(totalTimeSeconds%60).toString().padStart(2,0)}s`}return"0h 0m 0s"}authorshipStatus(firstFile,score,scoreSetting){var icon="fa fa-circle-o",color="font-size:32px;color:black";score=parseFloat(score),firstFile?(icon="fa fa-solid fa-info-circle",color="font-size:32px;color:#000000"):score>=scoreSetting&&(icon="fa fa-check-circle",color="font-size:32px;color:green");const iconElement=document.createElement("i");return iconElement.className=icon,iconElement.style=color,score(localStorage.setItem("notenoughtinfo",str),str)))}createModal(userid,context){let questionid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",replayInstances=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,authIcon=arguments.length>4?arguments[4]:void 0;const element=document.getElementById("analytics"+userid+questionid);element&&element.addEventListener("click",(function(e){e.preventDefault(),_analytic_modal.default.create({templateContext:context}).then((modal=>{const content=document.querySelector("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)");content&&(content.innerHTML=authIcon.outerHTML),modal.show();const moreBtn=document.querySelector("body #more"+userid+questionid);if(moreBtn){document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active")));const analyticBtn=document.getElementById("analytic"+userid+questionid),diffBtn=document.getElementById("diff"+userid+questionid);analyticBtn&&(analyticBtn.disabled=!0,analyticBtn.style.backgroundColor="rgba(168, 168, 168, 0.133)",analyticBtn.style.cursor="not-allowed"),diffBtn&&(diffBtn.disabled=!0,diffBtn.style.backgroundColor="rgba(168, 168, 168, 0.133)",diffBtn.style.cursor="not-allowed"),moreBtn.addEventListener("click",(function(){document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active"))),this.classList.add("active");const repBtn=document.getElementById("rep"+userid+questionid);repBtn&&(repBtn.disabled=!1),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay()}))}})).catch((error=>{window.console.error("Failed to create modal:",error)}))}))}analytics(userid,templates,context){let questionid=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"",replayInstances=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,authIcon=arguments.length>5?arguments[5]:void 0;document.body.addEventListener("click",(function(e){if(e.target&&e.target.id==="analytic"+userid+questionid){if(e.preventDefault(),e.target.disabled)return;const repBtn=document.getElementById("rep"+userid+questionid);repBtn&&(repBtn.disabled=!1);const qualityBtn=document.getElementById("quality"+userid+questionid);qualityBtn&&(qualityBtn.disabled=!1);const content=document.getElementById("content"+userid);if(content){content.setAttribute("data-label","analytics"),content.classList.remove("tiny_cursive_outputElement"),content.classList.add("tiny_cursive"),content.setAttribute("data-label","analytics");const player=document.getElementById("player_"+userid+questionid);player&&(player.style.display="none"),content.innerHTML="";const loaderDiv=document.createElement("div");loaderDiv.className="d-flex justify-content-center my-5";const loader=document.createElement("div");loader.className="tiny_cursive-loader",loaderDiv.appendChild(loader),content.appendChild(loaderDiv)}replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active"))),e.target.classList.add("active"),templates.render("tiny_cursive/analytics_table",context).then((function(html){const content=document.getElementById("content"+userid);content&&(content.innerHTML=html);const firstCell=document.querySelector("#content"+userid+" .tiny_cursive_table").querySelector("tbody tr:first-child").querySelector("td:nth-child(2)");return firstCell&&(firstCell.innerHTML=authIcon.outerHTML),!0})).catch((function(error){window.console.error("Failed to render template:",error)}))}}))}checkDiff(userid,fileid){let questionid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",replayInstances=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;const nodata=document.createElement("p");nodata.classList.add("tiny_cursive_nopayload","bg-light"),(0,_str.get_string)("nopaylod","tiny_cursive").then((str=>(nodata.textContent=str,!0))).catch((error=>window.console.log(error))),document.body.addEventListener("click",(function(e){if(e.target&&e.target.id==="diff"+userid+questionid){if(e.preventDefault(),e.target.disabled)return;const repElement=document.getElementById("rep"+userid+questionid);repElement&&(repElement.disabled=!1);const qualityElement=document.getElementById("quality"+userid+questionid);qualityElement&&(qualityElement.disabled=!1);const content=document.getElementById("content"+userid);if(content){content.setAttribute("data-label","diff"),content.classList.remove("tiny_cursive_outputElement"),content.classList.add("tiny_cursive"),content.innerHTML="";const loaderDiv=document.createElement("div");loaderDiv.className="d-flex justify-content-center my-5";const loader=document.createElement("div");loader.className="tiny_cursive-loader",loaderDiv.appendChild(loader),content.appendChild(loaderDiv)}const player=document.getElementById("player_"+userid+questionid);if(player&&(player.style.display="none"),document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active"))),e.target.classList.add("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),!fileid)throw content&&(content.innerHTML="",content.appendChild(nodata)),new Error("Missing file id or Difference Content not received yet");(0,_ajax.call)([{methodname:"cursive_get_writing_differences",args:{fileid:fileid}}])[0].then((response=>{let responsedata=JSON.parse(response.data);if(responsedata){let submittedText=atob(responsedata.submitted_text);(0,_str.get_strings)([{key:"original_text",component:"tiny_cursive"},{key:"editspastesai",component:"tiny_cursive"}]).then((strings=>{const originalTextString=strings[0],editsPastesAIString=strings[1],commentBox=document.createElement("div");commentBox.className="p-2 border rounded mb-2";const pasteCountDiv=document.createElement("div");(0,_str.get_string)("pastecount","tiny_cursive").then((str=>(pasteCountDiv.innerHTML="
".concat(str," : ").concat(responsedata.commentscount,"
"),!0))).catch((error=>window.console.log(error)));const commentsDiv=document.createElement("div");commentsDiv.className="border-bottom",(0,_str.get_string)("comments","tiny_cursive").then((str=>(commentsDiv.innerHTML="".concat(str,""),!0))).catch((error=>window.console.error(error)));const commentsList=document.createElement("div"),comments=responsedata.comments;for(let index in comments){const commentDiv=document.createElement("div");commentDiv.style.cssText="word-wrap: break-word; word-break: break-word",commentDiv.className="shadow-sm p-1 my-1",commentDiv.textContent=comments[index].usercomment,commentsList.appendChild(commentDiv)}commentBox.appendChild(pasteCountDiv),commentBox.appendChild(commentsDiv),commentBox.appendChild(commentsList);const legend=document.createElement("div");legend.className="d-flex p-2 border rounded mb-2";const attributedItem=document.createElement("div");attributedItem.className="tiny_cursive-legend-item";const attributedBox=document.createElement("div");attributedBox.className="tiny_cursive-box attributed";const attributedText=document.createElement("span");attributedText.textContent=originalTextString,attributedItem.appendChild(attributedBox),attributedItem.appendChild(attributedText);const unattributedItem=document.createElement("div");unattributedItem.className="tiny_cursive-legend-item";const unattributedBox=document.createElement("div");unattributedBox.className="tiny_cursive-box tiny_cursive_added";const unattributedText=document.createElement("span");unattributedText.textContent=editsPastesAIString,unattributedItem.appendChild(unattributedBox),unattributedItem.appendChild(unattributedText),legend.appendChild(attributedItem),legend.appendChild(unattributedItem);const contents=document.createElement("div");contents.className="tiny_cursive-comparison-content";const textBlock2=document.createElement("div");textBlock2.className="tiny_cursive-text-block";const reconstructedText=document.createElement("div");reconstructedText.id="tiny_cursive-reconstructed_text",reconstructedText.innerHTML=JSON.parse(submittedText),textBlock2.appendChild(reconstructedText),contents.appendChild(commentBox),contents.appendChild(legend),contents.appendChild(textBlock2),content&&(content.innerHTML="",content.appendChild(contents))})).catch((error=>{window.console.error("Failed to load language strings:",error),content&&(content.innerHTML="",content.appendChild(nodata))}))}else content&&(content.innerHTML="",content.appendChild(nodata))})).catch((error=>{throw content&&(content.innerHTML="",content.appendChild(nodata)),new Error("Error loading JSON file: "+error.message)}))}}))}replyWriting(userid,filepath){let questionid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",replayInstances=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;document.body.addEventListener("click",(function(e){if(e.target&&e.target.id==="rep"+userid+questionid){e.preventDefault();const replyBtn=document.getElementById("rep"+userid+questionid),qualityBtn=document.getElementById("quality"+userid+questionid),content=document.getElementById("content"+userid),player=document.getElementById("player_"+userid+questionid),replayControls=document.getElementById("replayControls_"+userid+questionid);filepath&&(replayControls&&replayControls.classList.remove("d-none"),content&&content.classList.add("tiny_cursive_outputElement")),replyBtn&&(replyBtn.disabled=!0),qualityBtn&&(qualityBtn.disabled=!1),content&&(content.dataset.label="replay"),player&&(player.style.display="block",player.style.paddingRight="8px"),content&&(content.innerHTML='\n
\n
\n
'),document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((el=>el.classList.remove("active"))),e.target.classList.add("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),questionid?video_playback(userid,filepath,questionid):video_playback(userid,filepath)}}))}formatedTime(data){if(data.total_time_seconds){let totalTimeSeconds=data.total_time_seconds,hours=Math.floor(totalTimeSeconds/3600).toString().padStart(2,0),minutes=Math.floor(totalTimeSeconds%3600/60).toString().padStart(2,0),seconds=(totalTimeSeconds%60).toString().padStart(2,0);return"".concat(hours,"h ").concat(minutes,"m ").concat(seconds,"s")}return"0h 0m 0s"}authorshipStatus(firstFile,score,scoreSetting){var icon="fa fa-circle-o",color="font-size:32px;color:black";score=parseFloat(score),firstFile?(icon="fa fa-solid fa-info-circle",color="font-size:32px;color:#000000"):score>=scoreSetting&&(icon="fa fa-check-circle",color="font-size:32px;color:green");const iconElement=document.createElement("i");return iconElement.className=icon,iconElement.style=color,score.\n\n/**\n * Module for handling analytics events in the Tiny Cursive plugin.\n * Provides functionality for displaying analytics data, replaying writing,\n * checking differences.\n *\n * @module tiny_cursive/analytic_events\n * @copyright 2024 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport myModal from \"./analytic_modal\";\nimport { call as getContent } from \"core/ajax\";\nimport { get_string as getString } from 'core/str';\nimport { get_strings as getStrings } from 'core/str';\n\nexport default class AnalyticEvents {\n\n constructor() {\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\n localStorage.setItem('notenoughtinfo', str);\n return str;\n });\n }\n\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\n const element = document.getElementById('analytics' + userid + questionid);\n if (element) {\n element.addEventListener('click', function (e) {\n e.preventDefault();\n\n myModal.create({ templateContext: context }).then(modal => {\n const content = document.querySelector('#content' + userid +\n ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)');\n if (content) {\n content.innerHTML = authIcon.outerHTML;\n }\n modal.show();\n\n const moreBtn = document.querySelector('body #more' + userid + questionid);\n\n if (moreBtn) {\n document\n .querySelectorAll('.tiny_cursive-nav-tab .active')\n .forEach(el => el.classList.remove('active'));\n\n const analyticBtn = document.getElementById('analytic' + userid + questionid);\n const diffBtn = document.getElementById('diff' + userid + questionid);\n\n if (analyticBtn) {\n analyticBtn.disabled = true;\n analyticBtn.style.backgroundColor = 'rgba(168, 168, 168, 0.133)';\n analyticBtn.style.cursor = 'not-allowed';\n }\n\n if (diffBtn) {\n diffBtn.disabled = true;\n diffBtn.style.backgroundColor = 'rgba(168, 168, 168, 0.133)';\n diffBtn.style.cursor = 'not-allowed';\n }\n\n moreBtn.addEventListener('click', function () {\n document\n .querySelectorAll('.tiny_cursive-nav-tab .active')\n .forEach(el => el.classList.remove('active'));\n\n this.classList.add('active');\n\n const repBtn = document.getElementById('rep' + userid + questionid);\n if (repBtn) {\n repBtn.disabled = false;\n }\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n });\n }\n\n }).catch(error => {\n window.console.error(\"Failed to create modal:\", error);\n });\n });\n }\n }\n\n analytics(userid, templates, context, questionid = '', replayInstances = null, authIcon) {\n document.body.addEventListener('click', function (e) {\n if (e.target && e.target.id === 'analytic' + userid + questionid) {\n e.preventDefault();\n\n if (e.target.disabled) {\n return;\n }\n\n const repBtn = document.getElementById('rep' + userid + questionid);\n if (repBtn) {\n repBtn.disabled = false;\n }\n\n const qualityBtn = document.getElementById('quality' + userid + questionid);\n if (qualityBtn) {\n qualityBtn.disabled = false;\n }\n\n const content = document.getElementById('content' + userid);\n if (content) {\n content.setAttribute('data-label', 'analytics');\n content.classList.remove('tiny_cursive_outputElement');\n content.classList.add('tiny_cursive');\n content.setAttribute('data-label', 'analytics');\n\n const player = document.getElementById('player_' + userid + questionid);\n if (player) {\n player.style.display = 'none';\n }\n\n content.innerHTML = '';\n const loaderDiv = document.createElement('div');\n loaderDiv.className = 'd-flex justify-content-center my-5';\n const loader = document.createElement('div');\n loader.className = 'tiny_cursive-loader';\n loaderDiv.appendChild(loader);\n content.appendChild(loaderDiv);\n }\n\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n\n document.querySelectorAll('.tiny_cursive-nav-tab .active').forEach(el => el.classList.remove('active'));\n e.target.classList.add('active');\n\n templates.render('tiny_cursive/analytics_table', context).then(function (html) {\n const content = document.getElementById('content' + userid);\n if (content) {\n content.innerHTML = html;\n }\n const table = document.querySelector('#content' + userid + ' .tiny_cursive_table');\n const firstRow = table.querySelector('tbody tr:first-child');\n const firstCell = firstRow.querySelector('td:nth-child(2)');\n\n if (firstCell) {\n firstCell.innerHTML = authIcon.outerHTML;\n }\n return true;\n }).catch(function (error) {\n window.console.error(\"Failed to render template:\", error);\n });\n }\n });\n }\n\n checkDiff(userid, fileid, questionid = '', replayInstances = null) {\n const nodata = document.createElement('p');\n nodata.classList.add('tiny_cursive_nopayload', 'bg-light');\n\n getString('nopaylod', 'tiny_cursive').then(str => {\n nodata.textContent = str;\n return true;\n }).catch(error => window.console.log(error));\n\n document.body.addEventListener('click', function (e) {\n if (e.target && e.target.id === 'diff' + userid + questionid) {\n e.preventDefault();\n\n if (e.target.disabled) {\n return;\n }\n\n // Enable rep and quality elements\n const repElement = document.getElementById('rep' + userid + questionid);\n if (repElement) {\n repElement.disabled = false;\n }\n\n const qualityElement = document.getElementById('quality' + userid + questionid);\n if (qualityElement) {\n qualityElement.disabled = false;\n }\n\n // Update content element attributes and classes\n const content = document.getElementById('content' + userid);\n if (content) {\n content.setAttribute('data-label', 'diff');\n content.classList.remove('tiny_cursive_outputElement');\n content.classList.add('tiny_cursive');\n content.innerHTML = '';\n const loaderDiv = document.createElement('div');\n loaderDiv.className = 'd-flex justify-content-center my-5';\n const loader = document.createElement('div');\n loader.className = 'tiny_cursive-loader';\n loaderDiv.appendChild(loader);\n content.appendChild(loaderDiv);\n }\n\n // Hide player element\n const player = document.getElementById('player_' + userid + questionid);\n if (player) {\n player.style.display = 'none';\n }\n\n // Update active tab\n document.querySelectorAll('.tiny_cursive-nav-tab .active').forEach(el => el.classList.remove('active'));\n e.target.classList.add('active');\n\n // Stop replay if applicable\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n\n // Check for missing fileid\n if (!fileid) {\n if (content) {\n content.innerHTML = '';\n content.appendChild(nodata);\n }\n throw new Error('Missing file id or Difference Content not received yet');\n }\n\n // Fetch content\n getContent([{\n methodname: 'cursive_get_writing_differences',\n args: { fileid: fileid },\n }])[0].then(response => {\n let responsedata = JSON.parse(response.data);\n if (responsedata) {\n let submittedText = atob(responsedata.submitted_text);\n\n // Fetch dynamic strings\n getStrings([\n { key: 'original_text', component: 'tiny_cursive' },\n { key: 'editspastesai', component: 'tiny_cursive' }\n ]).then(strings => {\n const originalTextString = strings[0];\n const editsPastesAIString = strings[1];\n\n // Create comment box\n const commentBox = document.createElement('div');\n commentBox.className = 'p-2 border rounded mb-2';\n\n // Paste count\n const pasteCountDiv = document.createElement('div');\n getString('pastecount', 'tiny_cursive').then(str => {\n pasteCountDiv.innerHTML = `
${str} : ${responsedata.commentscount}
`;\n return true;\n }).catch(error => window.console.log(error));\n\n // Comments header\n const commentsDiv = document.createElement('div');\n commentsDiv.className = 'border-bottom';\n getString('comments', 'tiny_cursive').then(str => {\n commentsDiv.innerHTML = `${str}`;\n return true;\n }).catch(error => window.console.error(error));\n\n // Comments list\n const commentsList = document.createElement('div');\n const comments = responsedata.comments;\n for (let index in comments) {\n const commentDiv = document.createElement('div');\n commentDiv.style.cssText = 'word-wrap: break-word; word-break: break-word';\n commentDiv.className = 'shadow-sm p-1 my-1';\n commentDiv.textContent = comments[index].usercomment;\n commentsList.appendChild(commentDiv);\n }\n\n commentBox.appendChild(pasteCountDiv);\n commentBox.appendChild(commentsDiv);\n commentBox.appendChild(commentsList);\n\n // Create legend\n const legend = document.createElement('div');\n legend.className = 'd-flex p-2 border rounded mb-2';\n\n // First legend item\n const attributedItem = document.createElement('div');\n attributedItem.className = 'tiny_cursive-legend-item';\n const attributedBox = document.createElement('div');\n attributedBox.className = 'tiny_cursive-box attributed';\n const attributedText = document.createElement('span');\n attributedText.textContent = originalTextString;\n attributedItem.appendChild(attributedBox);\n attributedItem.appendChild(attributedText);\n\n // Second legend item\n const unattributedItem = document.createElement('div');\n unattributedItem.className = 'tiny_cursive-legend-item';\n const unattributedBox = document.createElement('div');\n unattributedBox.className = 'tiny_cursive-box tiny_cursive_added';\n const unattributedText = document.createElement('span');\n unattributedText.textContent = editsPastesAIString;\n unattributedItem.appendChild(unattributedBox);\n unattributedItem.appendChild(unattributedText);\n\n legend.appendChild(attributedItem);\n legend.appendChild(unattributedItem);\n\n // Create content block\n const contents = document.createElement('div');\n contents.className = 'tiny_cursive-comparison-content';\n const textBlock2 = document.createElement('div');\n textBlock2.className = 'tiny_cursive-text-block';\n const reconstructedText = document.createElement('div');\n reconstructedText.id = 'tiny_cursive-reconstructed_text';\n reconstructedText.innerHTML = JSON.parse(submittedText);\n textBlock2.appendChild(reconstructedText);\n\n contents.appendChild(commentBox);\n contents.appendChild(legend);\n contents.appendChild(textBlock2);\n\n if (content) {\n content.innerHTML = '';\n content.appendChild(contents);\n }\n }).catch(error => {\n window.console.error(\"Failed to load language strings:\", error);\n if (content) {\n content.innerHTML = '';\n content.appendChild(nodata);\n }\n });\n } else {\n if (content) {\n content.innerHTML = '';\n content.appendChild(nodata);\n }\n }\n }).catch(error => {\n if (content) {\n content.innerHTML = '';\n content.appendChild(nodata);\n }\n throw new Error('Error loading JSON file: ' + error.message);\n });\n }\n });\n }\n\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\n document.body.addEventListener('click', function (e) {\n if (e.target && e.target.id === 'rep' + userid + questionid) {\n e.preventDefault();\n\n const replyBtn = document.getElementById('rep' + userid + questionid);\n const qualityBtn = document.getElementById('quality' + userid + questionid);\n const content = document.getElementById('content' + userid);\n const player = document.getElementById('player_' + userid + questionid);\n const replayControls = document.getElementById('replayControls_' + userid + questionid);\n\n if (filepath) {\n if (replayControls) {\n replayControls.classList.remove('d-none');\n }\n if (content) {\n content.classList.add('tiny_cursive_outputElement');\n }\n }\n\n if (replyBtn) {\n replyBtn.disabled = true;\n }\n if (qualityBtn) {\n qualityBtn.disabled = false;\n }\n\n if (content) {\n content.dataset.label = 'replay';\n }\n if (player) {\n player.style.display = 'block';\n player.style.paddingRight = '8px';\n }\n\n if (content) {\n content.innerHTML = `\n
\n
\n
`;\n }\n\n document\n .querySelectorAll('.tiny_cursive-nav-tab .active')\n .forEach(el => el.classList.remove('active'));\n e.target.classList.add('active');\n\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n\n if (questionid) {\n // eslint-disable-next-line\n video_playback(userid, filepath, questionid);\n } else {\n // eslint-disable-next-line\n video_playback(userid, filepath);\n }\n }\n });\n }\n\n\n formatedTime(data) {\n if (data.total_time_seconds) {\n let totalTimeSeconds = data.total_time_seconds;\n let hours = Math.floor(totalTimeSeconds / 3600).toString().padStart(2, 0);\n let minutes = Math.floor((totalTimeSeconds % 3600) / 60).toString().padStart(2, 0);\n let seconds = (totalTimeSeconds % 60).toString().padStart(2, 0);\n return `${hours}h ${minutes}m ${seconds}s`;\n } else {\n return \"0h 0m 0s\";\n }\n }\n\n authorshipStatus(firstFile, score, scoreSetting) {\n var icon = 'fa fa-circle-o';\n var color = 'font-size:32px;color:black';\n score = parseFloat(score);\n\n if (firstFile) {\n icon = 'fa fa-solid fa-info-circle';\n color = 'font-size:32px;color:#000000';\n } else if (score >= scoreSetting) {\n icon = 'fa fa-check-circle';\n color = 'font-size:32px;color:green';\n }\n\n const iconElement = document.createElement('i');\n iconElement.className = icon;\n iconElement.style = color;\n\n if (score < scoreSetting) {\n iconElement.className = 'fa fa-question-circle';\n iconElement.style = 'font-size:32px;color:#A9A9A9';\n iconElement.setAttribute('title', localStorage.getItem('notenoughtinfo'));\n }\n\n return iconElement;\n }\n}\n"],"names":["constructor","then","str","localStorage","setItem","createModal","userid","context","questionid","replayInstances","authIcon","element","document","getElementById","addEventListener","e","preventDefault","create","templateContext","modal","content","querySelector","innerHTML","outerHTML","show","moreBtn","querySelectorAll","forEach","el","classList","remove","analyticBtn","diffBtn","disabled","style","backgroundColor","cursor","add","repBtn","stopReplay","catch","error","window","console","analytics","templates","body","target","id","qualityBtn","setAttribute","player","display","loaderDiv","createElement","className","loader","appendChild","render","html","firstCell","checkDiff","fileid","nodata","textContent","log","repElement","qualityElement","Error","methodname","args","response","responsedata","JSON","parse","data","submittedText","atob","submitted_text","key","component","strings","originalTextString","editsPastesAIString","commentBox","pasteCountDiv","commentscount","commentsDiv","commentsList","comments","index","commentDiv","cssText","usercomment","legend","attributedItem","attributedBox","attributedText","unattributedItem","unattributedBox","unattributedText","contents","textBlock2","reconstructedText","message","replyWriting","filepath","replyBtn","replayControls","dataset","label","paddingRight","video_playback","formatedTime","total_time_seconds","totalTimeSeconds","Math","floor","toString","padStart","authorshipStatus","firstFile","score","scoreSetting","icon","color","parseFloat","iconElement","getItem"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"analytic_events.min.js","sources":["../src/analytic_events.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 for handling analytics events in the Tiny Cursive plugin.\n * Provides functionality for displaying analytics data, replaying writing,\n * checking differences.\n *\n * @module tiny_cursive/analytic_events\n * @copyright 2024 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport myModal from \"./analytic_modal\";\nimport { call as getContent } from \"core/ajax\";\nimport { get_string as getString } from 'core/str';\nimport { get_strings as getStrings } from 'core/str';\n\nexport default class AnalyticEvents {\n\n constructor() {\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\n localStorage.setItem('notenoughtinfo', str);\n return str;\n });\n }\n\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\n const element = document.getElementById('analytics' + userid + questionid);\n if (element) {\n element.addEventListener('click', function (e) {\n e.preventDefault();\n\n myModal.create({ templateContext: context }).then(modal => {\n const content = document.querySelector('#content' + userid +\n ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)');\n if (content) {\n content.innerHTML = authIcon.outerHTML;\n }\n modal.show();\n\n const moreBtn = document.querySelector('body #more' + userid + questionid);\n\n if (moreBtn) {\n document\n .querySelectorAll('.tiny_cursive-nav-tab .active')\n .forEach(el => el.classList.remove('active'));\n\n const analyticBtn = document.getElementById('analytic' + userid + questionid);\n const diffBtn = document.getElementById('diff' + userid + questionid);\n\n if (analyticBtn) {\n analyticBtn.disabled = true;\n analyticBtn.style.backgroundColor = 'rgba(168, 168, 168, 0.133)';\n analyticBtn.style.cursor = 'not-allowed';\n }\n\n if (diffBtn) {\n diffBtn.disabled = true;\n diffBtn.style.backgroundColor = 'rgba(168, 168, 168, 0.133)';\n diffBtn.style.cursor = 'not-allowed';\n }\n\n moreBtn.addEventListener('click', function () {\n document\n .querySelectorAll('.tiny_cursive-nav-tab .active')\n .forEach(el => el.classList.remove('active'));\n\n this.classList.add('active');\n\n const repBtn = document.getElementById('rep' + userid + questionid);\n if (repBtn) {\n repBtn.disabled = false;\n }\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n });\n }\n\n }).catch(error => {\n window.console.error(\"Failed to create modal:\", error);\n });\n });\n }\n }\n\n analytics(userid, templates, context, questionid = '', replayInstances = null, authIcon) {\n document.body.addEventListener('click', function (e) {\n if (e.target && e.target.id === 'analytic' + userid + questionid) {\n e.preventDefault();\n\n if (e.target.disabled) {\n return;\n }\n\n const repBtn = document.getElementById('rep' + userid + questionid);\n if (repBtn) {\n repBtn.disabled = false;\n }\n\n const qualityBtn = document.getElementById('quality' + userid + questionid);\n if (qualityBtn) {\n qualityBtn.disabled = false;\n }\n\n const content = document.getElementById('content' + userid);\n if (content) {\n content.setAttribute('data-label', 'analytics');\n content.classList.remove('tiny_cursive_outputElement');\n content.classList.add('tiny_cursive');\n content.setAttribute('data-label', 'analytics');\n\n const player = document.getElementById('player_' + userid + questionid);\n if (player) {\n player.style.display = 'none';\n }\n\n content.innerHTML = '';\n const loaderDiv = document.createElement('div');\n loaderDiv.className = 'd-flex justify-content-center my-5';\n const loader = document.createElement('div');\n loader.className = 'tiny_cursive-loader';\n loaderDiv.appendChild(loader);\n content.appendChild(loaderDiv);\n }\n\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n\n document.querySelectorAll('.tiny_cursive-nav-tab .active').forEach(el => el.classList.remove('active'));\n e.target.classList.add('active');\n\n templates.render('tiny_cursive/analytics_table', context).then(function (html) {\n const content = document.getElementById('content' + userid);\n if (content) {\n content.innerHTML = html;\n }\n const table = document.querySelector('#content' + userid + ' .tiny_cursive_table');\n const firstRow = table.querySelector('tbody tr:first-child');\n const firstCell = firstRow.querySelector('td:nth-child(2)');\n\n if (firstCell) {\n firstCell.innerHTML = authIcon.outerHTML;\n }\n return true;\n }).catch(function (error) {\n window.console.error(\"Failed to render template:\", error);\n });\n }\n });\n }\n\n checkDiff(userid, fileid, questionid = '', replayInstances = null) {\n const nodata = document.createElement('p');\n nodata.classList.add('tiny_cursive_nopayload', 'bg-light');\n\n getString('nopaylod', 'tiny_cursive').then(str => {\n nodata.textContent = str;\n return true;\n }).catch(error => window.console.log(error));\n\n document.body.addEventListener('click', function (e) {\n if (e.target && e.target.id === 'diff' + userid + questionid) {\n e.preventDefault();\n\n if (e.target.disabled) {\n return;\n }\n\n // Enable rep and quality elements\n const repElement = document.getElementById('rep' + userid + questionid);\n if (repElement) {\n repElement.disabled = false;\n }\n\n const qualityElement = document.getElementById('quality' + userid + questionid);\n if (qualityElement) {\n qualityElement.disabled = false;\n }\n\n // Update content element attributes and classes\n const content = document.getElementById('content' + userid);\n if (content) {\n content.setAttribute('data-label', 'diff');\n content.classList.remove('tiny_cursive_outputElement');\n content.classList.add('tiny_cursive');\n content.innerHTML = '';\n const loaderDiv = document.createElement('div');\n loaderDiv.className = 'd-flex justify-content-center my-5';\n const loader = document.createElement('div');\n loader.className = 'tiny_cursive-loader';\n loaderDiv.appendChild(loader);\n content.appendChild(loaderDiv);\n }\n\n // Hide player element\n const player = document.getElementById('player_' + userid + questionid);\n if (player) {\n player.style.display = 'none';\n }\n\n // Update active tab\n document.querySelectorAll('.tiny_cursive-nav-tab .active').forEach(el => el.classList.remove('active'));\n e.target.classList.add('active');\n\n // Stop replay if applicable\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n\n // Check for missing fileid\n if (!fileid) {\n if (content) {\n content.innerHTML = '';\n content.appendChild(nodata);\n }\n throw new Error('Missing file id or Difference Content not received yet');\n }\n\n // Fetch content\n getContent([{\n methodname: 'cursive_get_writing_differences',\n args: { fileid: fileid },\n }])[0].then(response => {\n let responsedata = JSON.parse(response.data);\n if (responsedata) {\n let submittedText = atob(responsedata.submitted_text);\n\n // Fetch dynamic strings\n getStrings([\n { key: 'original_text', component: 'tiny_cursive' },\n { key: 'editspastesai', component: 'tiny_cursive' }\n ]).then(strings => {\n const originalTextString = strings[0];\n const editsPastesAIString = strings[1];\n\n // Create comment box\n const commentBox = document.createElement('div');\n commentBox.className = 'p-2 border rounded mb-2';\n\n // Paste count\n const pasteCountDiv = document.createElement('div');\n getString('pastecount', 'tiny_cursive').then(str => {\n pasteCountDiv.innerHTML = `
${str} : ${responsedata.commentscount}
`;\n return true;\n }).catch(error => window.console.log(error));\n\n // Comments header\n const commentsDiv = document.createElement('div');\n commentsDiv.className = 'border-bottom';\n getString('comments', 'tiny_cursive').then(str => {\n commentsDiv.innerHTML = `${str}`;\n return true;\n }).catch(error => window.console.error(error));\n\n // Comments list\n const commentsList = document.createElement('div');\n const comments = responsedata.comments;\n for (let index in comments) {\n const commentDiv = document.createElement('div');\n commentDiv.style.cssText = 'word-wrap: break-word; word-break: break-word';\n commentDiv.className = 'shadow-sm p-1 my-1';\n commentDiv.textContent = comments[index].usercomment;\n commentsList.appendChild(commentDiv);\n }\n\n commentBox.appendChild(pasteCountDiv);\n commentBox.appendChild(commentsDiv);\n commentBox.appendChild(commentsList);\n\n // Create legend\n const legend = document.createElement('div');\n legend.className = 'd-flex p-2 border rounded mb-2';\n\n // First legend item\n const attributedItem = document.createElement('div');\n attributedItem.className = 'tiny_cursive-legend-item';\n const attributedBox = document.createElement('div');\n attributedBox.className = 'tiny_cursive-box attributed';\n const attributedText = document.createElement('span');\n attributedText.textContent = originalTextString;\n attributedItem.appendChild(attributedBox);\n attributedItem.appendChild(attributedText);\n\n // Second legend item\n const unattributedItem = document.createElement('div');\n unattributedItem.className = 'tiny_cursive-legend-item';\n const unattributedBox = document.createElement('div');\n unattributedBox.className = 'tiny_cursive-box tiny_cursive_added';\n const unattributedText = document.createElement('span');\n unattributedText.textContent = editsPastesAIString;\n unattributedItem.appendChild(unattributedBox);\n unattributedItem.appendChild(unattributedText);\n\n legend.appendChild(attributedItem);\n legend.appendChild(unattributedItem);\n\n // Create content block\n const contents = document.createElement('div');\n contents.className = 'tiny_cursive-comparison-content';\n const textBlock2 = document.createElement('div');\n textBlock2.className = 'tiny_cursive-text-block';\n const reconstructedText = document.createElement('div');\n reconstructedText.id = 'tiny_cursive-reconstructed_text';\n reconstructedText.innerHTML = JSON.parse(submittedText);\n textBlock2.appendChild(reconstructedText);\n\n contents.appendChild(commentBox);\n contents.appendChild(legend);\n contents.appendChild(textBlock2);\n\n if (content) {\n content.innerHTML = '';\n content.appendChild(contents);\n }\n }).catch(error => {\n window.console.error(\"Failed to load language strings:\", error);\n if (content) {\n content.innerHTML = '';\n content.appendChild(nodata);\n }\n });\n } else {\n if (content) {\n content.innerHTML = '';\n content.appendChild(nodata);\n }\n }\n }).catch(error => {\n if (content) {\n content.innerHTML = '';\n content.appendChild(nodata);\n }\n throw new Error('Error loading JSON file: ' + error.message);\n });\n }\n });\n }\n\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\n document.body.addEventListener('click', function (e) {\n if (e.target && e.target.id === 'rep' + userid + questionid) {\n e.preventDefault();\n\n const replyBtn = document.getElementById('rep' + userid + questionid);\n const qualityBtn = document.getElementById('quality' + userid + questionid);\n const content = document.getElementById('content' + userid);\n const player = document.getElementById('player_' + userid + questionid);\n const replayControls = document.getElementById('replayControls_' + userid + questionid);\n\n if (filepath) {\n if (replayControls) {\n replayControls.classList.remove('d-none');\n }\n if (content) {\n content.classList.add('tiny_cursive_outputElement');\n }\n }\n\n if (replyBtn) {\n replyBtn.disabled = true;\n }\n if (qualityBtn) {\n qualityBtn.disabled = false;\n }\n\n if (content) {\n content.dataset.label = 'replay';\n }\n if (player) {\n player.style.display = 'block';\n player.style.paddingRight = '8px';\n }\n\n if (content) {\n content.innerHTML = `\n
\n
\n
`;\n }\n\n document\n .querySelectorAll('.tiny_cursive-nav-tab .active')\n .forEach(el => el.classList.remove('active'));\n e.target.classList.add('active');\n\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n\n if (questionid) {\n // eslint-disable-next-line\n video_playback(userid, filepath, questionid);\n } else {\n // eslint-disable-next-line\n video_playback(userid, filepath);\n }\n }\n });\n }\n\n\n formatedTime(data) {\n if (data.total_time_seconds) {\n let totalTimeSeconds = data.total_time_seconds;\n let hours = Math.floor(totalTimeSeconds / 3600).toString().padStart(2, 0);\n let minutes = Math.floor((totalTimeSeconds % 3600) / 60).toString().padStart(2, 0);\n let seconds = (totalTimeSeconds % 60).toString().padStart(2, 0);\n return `${hours}h ${minutes}m ${seconds}s`;\n } else {\n return \"0h 0m 0s\";\n }\n }\n\n authorshipStatus(firstFile, score, scoreSetting) {\n var icon = 'fa fa-circle-o';\n var color = 'font-size:32px;color:black';\n score = parseFloat(score);\n\n if (firstFile) {\n icon = 'fa fa-solid fa-info-circle';\n color = 'font-size:32px;color:#000000';\n } else if (score >= scoreSetting) {\n icon = 'fa fa-check-circle';\n color = 'font-size:32px;color:green';\n }\n\n const iconElement = document.createElement('i');\n iconElement.className = icon;\n iconElement.style = color;\n\n if (score < scoreSetting) {\n iconElement.className = 'fa fa-question-circle';\n iconElement.style = 'font-size:32px;color:#A9A9A9';\n iconElement.setAttribute('title', localStorage.getItem('notenoughtinfo'));\n }\n\n return iconElement;\n }\n}\n"],"names":["constructor","then","str","localStorage","setItem","createModal","userid","context","questionid","replayInstances","authIcon","element","document","getElementById","addEventListener","e","preventDefault","create","templateContext","modal","content","querySelector","innerHTML","outerHTML","show","moreBtn","querySelectorAll","forEach","el","classList","remove","analyticBtn","diffBtn","disabled","style","backgroundColor","cursor","add","repBtn","stopReplay","catch","error","window","console","analytics","templates","body","target","id","qualityBtn","setAttribute","player","display","loaderDiv","createElement","className","loader","appendChild","render","html","firstCell","checkDiff","fileid","nodata","textContent","log","repElement","qualityElement","Error","methodname","args","response","responsedata","JSON","parse","data","submittedText","atob","submitted_text","key","component","strings","originalTextString","editsPastesAIString","commentBox","pasteCountDiv","commentscount","commentsDiv","commentsList","comments","index","commentDiv","cssText","usercomment","legend","attributedItem","attributedBox","attributedText","unattributedItem","unattributedBox","unattributedText","contents","textBlock2","reconstructedText","message","replyWriting","filepath","replyBtn","replayControls","dataset","label","paddingRight","video_playback","formatedTime","total_time_seconds","totalTimeSeconds","hours","Math","floor","toString","padStart","minutes","seconds","authorshipStatus","firstFile","score","scoreSetting","icon","color","parseFloat","iconElement","getItem"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/analytic_modal.min.js b/amd/build/analytic_modal.min.js index 447bc86e..4b816ea4 100644 --- a/amd/build/analytic_modal.min.js +++ b/amd/build/analytic_modal.min.js @@ -1,10 +1,3 @@ -define("tiny_cursive/analytic_modal",["exports","core/modal"],(function(_exports,_modal){var obj; -/** - * This module defines a custom modal for analytics. - * - * @module tiny_cursive/analytic_modal - * @copyright 2024 CTI - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class MyModal extends _modal.default{static TYPE="tiny_cursive/analytics_modal";static TEMPLATE="tiny_cursive/analytics_modal";configure(modalConfig){modalConfig.show=!0,modalConfig.removeOnClose=!0,modalConfig.backdrop=!0,super.configure(modalConfig)}show(){super.show();const root=this.getRoot();root.find(".modal-header").remove(),root.find(".modal-content").css({"border-radius":"30px"}).addClass("shadow-none border-none"),root.find(".modal-body").css({padding:"0","border-radius":"30px"}),root.find(".modal-dialog").css({"max-width":"800px","background-color":"transparent"}),root.find("#analytic-close").on("click",(()=>{this.destroy()})),root.on("click",(e=>{e.target.classList.contains("modal")&&this.destroy()}))}}return _exports.default=MyModal,_exports.default})); +define("tiny_cursive/analytic_modal",["exports","core/modal"],(function(_exports,_modal){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class MyModal extends _modal.default{configure(modalConfig){modalConfig.show=!0,modalConfig.removeOnClose=!0,modalConfig.backdrop=!0,super.configure(modalConfig)}show(){super.show();const root=this.getRoot();root.find(".modal-header").remove(),root.find(".modal-content").css({"border-radius":"30px"}).addClass("shadow-none border-none"),root.find(".modal-body").css({padding:"0","border-radius":"30px"}),root.find(".modal-dialog").css({"max-width":"800px","background-color":"transparent"}),root.find("#analytic-close").on("click",(()=>{this.destroy()})),root.on("click",(e=>{e.target.classList.contains("modal")&&this.destroy()}))}}return _exports.default=MyModal,_defineProperty(MyModal,"TYPE","tiny_cursive/analytics_modal"),_defineProperty(MyModal,"TEMPLATE","tiny_cursive/analytics_modal"),_exports.default})); //# sourceMappingURL=analytic_modal.min.js.map \ No newline at end of file diff --git a/amd/build/analytic_modal.min.js.map b/amd/build/analytic_modal.min.js.map index 72df3abb..806cd4a3 100644 --- a/amd/build/analytic_modal.min.js.map +++ b/amd/build/analytic_modal.min.js.map @@ -1 +1 @@ -{"version":3,"file":"analytic_modal.min.js","sources":["../src/analytic_modal.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 * This module defines a custom modal for analytics.\n *\n * @module tiny_cursive/analytic_modal\n * @copyright 2024 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nexport default class MyModal extends Modal {\n static TYPE = \"tiny_cursive/analytics_modal\";\n static TEMPLATE = \"tiny_cursive/analytics_modal\";\n\n configure(modalConfig) {\n // Show this modal on instantiation.\n modalConfig.show = true;\n\n // Remove from the DOM on close.\n modalConfig.removeOnClose = true;\n modalConfig.backdrop = true;\n\n // Call the parent configure method.\n super.configure(modalConfig);\n }\n\n // Override the parent show method to add custom behavior.\n show() {\n super.show();\n\n const root = this.getRoot();\n\n\n // Hide the default modal header.\n root.find('.modal-header').remove();\n\n root.find('.modal-content').css({\n 'border-radius': '30px'\n }).addClass('shadow-none border-none');\n // Remove padding from the modal content.\n root.find('.modal-body').css({\n 'padding': '0',\n 'border-radius': '30px'\n });\n root.find('.modal-dialog').css({\n 'max-width': '800px',\n 'background-color': 'transparent'\n });\n\n // Ensure modal closes on 'analytic-close' button click.\n root.find('#analytic-close').on('click', () => {\n this.destroy();\n });\n\n // Ensure modal closes on backdrop click.\n root.on('click', (e) => {\n if (e.target.classList.contains('modal')) {\n this.destroy();\n }\n });\n }\n}\n\n"],"names":["MyModal","Modal","configure","modalConfig","show","removeOnClose","backdrop","root","this","getRoot","find","remove","css","addClass","on","destroy","e","target","classList","contains"],"mappings":";;;;;;;iJAwBqBA,gBAAgBC,2BACnB,+CACI,+BAElBC,UAAUC,aAENA,YAAYC,MAAO,EAGnBD,YAAYE,eAAgB,EAC5BF,YAAYG,UAAW,QAGjBJ,UAAUC,aAIpBC,aACUA,aAEAG,KAAOC,KAAKC,UAIlBF,KAAKG,KAAK,iBAAiBC,SAE3BJ,KAAKG,KAAK,kBAAkBE,IAAI,iBACX,SAClBC,SAAS,2BAEZN,KAAKG,KAAK,eAAeE,IAAI,SACd,oBACM,SAErBL,KAAKG,KAAK,iBAAiBE,IAAI,aACd,2BACO,gBAIxBL,KAAKG,KAAK,mBAAmBI,GAAG,SAAS,UAChCC,aAITR,KAAKO,GAAG,SAAUE,IACVA,EAAEC,OAAOC,UAAUC,SAAS,eACvBJ"} \ No newline at end of file +{"version":3,"file":"analytic_modal.min.js","sources":["../src/analytic_modal.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 * This module defines a custom modal for analytics.\n *\n * @module tiny_cursive/analytic_modal\n * @copyright 2024 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nexport default class MyModal extends Modal {\n static TYPE = \"tiny_cursive/analytics_modal\";\n static TEMPLATE = \"tiny_cursive/analytics_modal\";\n\n configure(modalConfig) {\n // Show this modal on instantiation.\n modalConfig.show = true;\n\n // Remove from the DOM on close.\n modalConfig.removeOnClose = true;\n modalConfig.backdrop = true;\n\n // Call the parent configure method.\n super.configure(modalConfig);\n }\n\n // Override the parent show method to add custom behavior.\n show() {\n super.show();\n\n const root = this.getRoot();\n\n\n // Hide the default modal header.\n root.find('.modal-header').remove();\n\n root.find('.modal-content').css({\n 'border-radius': '30px'\n }).addClass('shadow-none border-none');\n // Remove padding from the modal content.\n root.find('.modal-body').css({\n 'padding': '0',\n 'border-radius': '30px'\n });\n root.find('.modal-dialog').css({\n 'max-width': '800px',\n 'background-color': 'transparent'\n });\n\n // Ensure modal closes on 'analytic-close' button click.\n root.find('#analytic-close').on('click', () => {\n this.destroy();\n });\n\n // Ensure modal closes on backdrop click.\n root.on('click', (e) => {\n if (e.target.classList.contains('modal')) {\n this.destroy();\n }\n });\n }\n}\n\n"],"names":["MyModal","Modal","configure","modalConfig","show","removeOnClose","backdrop","root","this","getRoot","find","remove","css","addClass","on","destroy","e","target","classList","contains"],"mappings":"iZAwBqBA,gBAAgBC,eAIjCC,UAAUC,aAENA,YAAYC,MAAO,EAGnBD,YAAYE,eAAgB,EAC5BF,YAAYG,UAAW,QAGjBJ,UAAUC,aAIpBC,aACUA,aAEAG,KAAOC,KAAKC,UAIlBF,KAAKG,KAAK,iBAAiBC,SAE3BJ,KAAKG,KAAK,kBAAkBE,IAAI,iBACX,SAClBC,SAAS,2BAEZN,KAAKG,KAAK,eAAeE,IAAI,SACd,oBACM,SAErBL,KAAKG,KAAK,iBAAiBE,IAAI,aACd,2BACO,gBAIxBL,KAAKG,KAAK,mBAAmBI,GAAG,SAAS,UAChCC,aAITR,KAAKO,GAAG,SAAUE,IACVA,EAAEC,OAAOC,UAAUC,SAAS,eACvBJ,8DA/CAf,eACH,gDADGA,mBAEC"} \ No newline at end of file diff --git a/amd/build/autosaver.min.js b/amd/build/autosaver.min.js index 1d82cd64..deef6dea 100644 --- a/amd/build/autosaver.min.js +++ b/amd/build/autosaver.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/autosaver",["exports","core/ajax","core/modal_factory","core/str","core/modal_events","tiny_cursive/common"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0;_exports.register=(editor,interval,userId,hasApiKey,MODULES)=>{var is_student=!document.querySelector("#body").classList.contains("teacher_admin"),intervention=document.querySelector("#body").classList.contains("intervention"),host=M.cfg.wwwroot,userid=userId,courseid=M.cfg.courseId,editorid=null==editor?void 0:editor.id,cmid=M.cfg.contextInstanceId,ed="",event="",filename="",modulename="",questionid=0,resourceId=0,quizSubmit=document.getElementById("mod_quiz-next-nav"),assignSubmit=document.getElementById("id_submitbutton"),syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let pastedContents=[];const postOne=async(methodname,args)=>{try{return await(0,_ajax.call)([{methodname:methodname,args:args}])[0]}catch(error){throw window.console.error("Error in postOne:",error),error}};if((document.getElementById("page-mod-assign-editsubmission")||document.getElementById("page-mod-forum-post")||document.getElementById("page-mod-forum-view"))&&assignSubmit){const handleAssignSubmit=async function(e){e.preventDefault(),filename&&await syncData(),assignSubmit.removeEventListener("click",handleAssignSubmit),assignSubmit.click(),assignSubmit.removeEventListener("click",handleAssignSubmit),localStorage.removeItem("lastCopyCutContent")};assignSubmit.addEventListener("click",handleAssignSubmit)}if(document.getElementById("page-mod-quiz-attempt")&&quizSubmit){const handleQuizSubmit=async e=>{e.preventDefault(),filename?(await syncData(),document.querySelector("#responseform").submit()):(quizSubmit.removeEventListener("click",handleQuizSubmit),quizSubmit.click()),localStorage.removeItem("lastCopyCutContent")};quizSubmit.addEventListener("click",handleQuizSubmit)}const getModal=e=>{Promise.all([(0,_str.get_string)("tiny_cursive_srcurl","tiny_cursive"),(0,_str.get_string)("tiny_cursive_srcurl_des","tiny_cursive"),(0,_str.get_string)("tiny_cursive_placeholder","tiny_cursive")]).then((function(_ref){let[title,titledes,placeholder]=_ref;return(0,_modal_factory.create)({type:"SAVE_CANCEL",title:`
${title}
\n ${titledes}
`,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();let ur=e.srcElement.baseURI,modulesInfo=getModulesInfo(ur,new URL(ur),MODULES);resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,""===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,eds)=>{ed=eds,event=events;let ur=eds.srcElement.baseURI,modulesInfo=getModulesInfo(ur,new URL(ur),MODULES);if(resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,filename=`${userid}_${resourceId}_${cmid}_${modulename}_attempt`,"quiz"===modulename&&(questionid=editorid.split(":")[1].split("_")[0],filename=`${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`),"Process"!==ed.key)if(localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:ed.key,keyCode:ed.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:pastedContents}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:ed.key,keyCode:ed.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:pastedContents}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition();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 rng=editor.selection.getRng();let absolutePosition=0,node=rng.startContainer;for(absolutePosition=rng.startOffset;node&&node!==editor.getBody();){for(;node.previousSibling;)node=node.previousSibling,node.textContent&&(absolutePosition+=node.textContent.length);node=node.parentNode}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:0,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 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)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.marginLeft="auto",rightWrapper.style.display="flex",rightWrapper.style.alignItems="center",imgWrapper.id=elementId,imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper),targetMenu&&!targetMenu.querySelector(`#${elementId}`)&&targetMenu.appendChild(rightWrapper)}}(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)));const element=document.querySelector(`#${elementId}`);element&&(element.addEventListener("mouseenter",(function(){this.style.position="relative";const tooltip=document.querySelector(`#${tooltipId}`);tooltip&&Object.assign(tooltip.style,_common.tooltipCss)})),element.addEventListener("mouseleave",(function(){const tooltip=document.querySelector(`#${tooltipId}`);tooltip&&(tooltip.style.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)}}function getModulesInfo(ur,parm,MODULES){if(!MODULES.some((module=>ur.includes(module))))return!1;null===(resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt"))&&(resourceId=0);for(const module of MODULES)if(ur.includes(module)){modulename=module,"lesson"===module||"assign"===module?resourceId=cmid:"oublog"===module&&(resourceId=0);break}return{resourceId:resourceId,name:modulename}}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!0);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&&is_student&&intervention&&pastedContent!==localStorage.getItem("lastCopyCutContent")){getModal(e),pastedContents=[],pastedContents.push(pastedContent);let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("Paste",{...e,key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition})}})),editor.on("Redo",(async e=>{customTooltip(),is_student&&intervention&&getModal(e)})),editor.on("keyDown",(editor=>{customTooltip();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=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)})),editor.on("mouseUp",(async editor=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)})),editor.on("init",(()=>{customTooltip()})),editor.on("SetContent",(()=>{customTooltip()})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}})); +define("tiny_cursive/autosaver",["exports","core/ajax","core/modal_factory","core/str","core/modal_events","tiny_cursive/common"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0;_exports.register=(editor,interval,userId,hasApiKey,MODULES)=>{var is_student=!document.querySelector("#body").classList.contains("teacher_admin"),intervention=document.querySelector("#body").classList.contains("intervention"),host=M.cfg.wwwroot,userid=userId,courseid=M.cfg.courseId,editorid=null==editor?void 0:editor.id,cmid=M.cfg.contextInstanceId,ed="",event="",filename="",modulename="",questionid=0,resourceId=0,quizSubmit=document.getElementById("mod_quiz-next-nav"),assignSubmit=document.getElementById("id_submitbutton"),syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let pastedContents=[];const postOne=async(methodname,args)=>{try{return await(0,_ajax.call)([{methodname:methodname,args:args}])[0]}catch(error){throw window.console.error("Error in postOne:",error),error}};if((document.getElementById("page-mod-assign-editsubmission")||document.getElementById("page-mod-forum-post")||document.getElementById("page-mod-forum-view"))&&assignSubmit){const handleAssignSubmit=async function(e){e.preventDefault(),filename&&await syncData(),assignSubmit.removeEventListener("click",handleAssignSubmit),assignSubmit.click(),assignSubmit.removeEventListener("click",handleAssignSubmit),localStorage.removeItem("lastCopyCutContent")};assignSubmit.addEventListener("click",handleAssignSubmit)}if(document.getElementById("page-mod-quiz-attempt")&&quizSubmit){const handleQuizSubmit=async e=>{e.preventDefault(),filename?(await syncData(),document.querySelector("#responseform").submit()):(quizSubmit.removeEventListener("click",handleQuizSubmit),quizSubmit.click()),localStorage.removeItem("lastCopyCutContent")};quizSubmit.addEventListener("click",handleQuizSubmit)}const getModal=e=>{Promise.all([(0,_str.get_string)("tiny_cursive_srcurl","tiny_cursive"),(0,_str.get_string)("tiny_cursive_srcurl_des","tiny_cursive"),(0,_str.get_string)("tiny_cursive_placeholder","tiny_cursive")]).then((function(_ref){let[title,titledes,placeholder]=_ref;return(0,_modal_factory.create)({type:"SAVE_CANCEL",title:'
'.concat(title,'
\n ').concat(titledes,"
"),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();let ur=e.srcElement.baseURI,modulesInfo=getModulesInfo(ur,new URL(ur),MODULES);resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,""===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,eds)=>{ed=eds,event=events;let ur=eds.srcElement.baseURI,modulesInfo=getModulesInfo(ur,new URL(ur),MODULES);if(resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,filename="".concat(userid,"_").concat(resourceId,"_").concat(cmid,"_").concat(modulename,"_attempt"),"quiz"===modulename&&(questionid=editorid.split(":")[1].split("_")[0],filename="".concat(userid,"_").concat(resourceId,"_").concat(cmid,"_").concat(questionid,"_").concat(modulename,"_attempt")),"Process"!==ed.key)if(localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:ed.key,keyCode:ed.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:pastedContents}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:ed.key,keyCode:ed.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:pastedContents}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition();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 rng=editor.selection.getRng();let absolutePosition=0,node=rng.startContainer;for(absolutePosition=rng.startOffset;node&&node!==editor.getBody();){for(;node.previousSibling;)node=node.previousSibling,node.textContent&&(absolutePosition+=node.textContent.length);node=node.parentNode}if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey="".concat(userid,"_").concat(resourceId,"_").concat(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:0,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 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)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.marginLeft="auto",rightWrapper.style.display="flex",rightWrapper.style.alignItems="center",imgWrapper.id=elementId,imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper),targetMenu&&!targetMenu.querySelector("#".concat(elementId))&&targetMenu.appendChild(rightWrapper)}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId="tiny_cursive_tooltip".concat(index);tooltipText.then((text=>setTooltip(text,document.querySelector("#".concat(elementId)),tooltipId))).catch((error=>window.console.error(error)));const element=document.querySelector("#".concat(elementId));element&&(element.addEventListener("mouseenter",(function(){this.style.position="relative";const tooltip=document.querySelector("#".concat(tooltipId));tooltip&&Object.assign(tooltip.style,_common.tooltipCss)})),element.addEventListener("mouseleave",(function(){const tooltip=document.querySelector("#".concat(tooltipId));tooltip&&(tooltip.style.display="none")})))}}catch(error){window.console.error("Error setting up custom tooltip:",error)}}function setTooltip(text,cursiveIcon,tooltipId){if(!document.querySelector("#".concat(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)}}function getModulesInfo(ur,parm,MODULES){if(!MODULES.some((module=>ur.includes(module))))return!1;null===(resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt"))&&(resourceId=0);for(const module of MODULES)if(ur.includes(module)){modulename=module,"lesson"===module||"assign"===module?resourceId=cmid:"oublog"===module&&(resourceId=0);break}return{resourceId:resourceId,name:modulename}}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!0);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&&is_student&&intervention&&pastedContent!==localStorage.getItem("lastCopyCutContent")){getModal(e),pastedContents=[],pastedContents.push(pastedContent);let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("Paste",{...e,key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition})}})),editor.on("Redo",(async e=>{customTooltip(),is_student&&intervention&&getModal(e)})),editor.on("keyDown",(editor=>{customTooltip();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=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)})),editor.on("mouseUp",(async editor=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)})),editor.on("init",(()=>{customTooltip()})),editor.on("SetContent",(()=>{customTooltip()})),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 cb00be06..b6c9441d 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 {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES) => {\n var is_student = !document.querySelector('#body').classList.contains('teacher_admin');\n var intervention = document.querySelector('#body').classList.contains('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 modulename = \"\";\n var questionid = 0;\n var resourceId = 0;\n var quizSubmit = document.getElementById('mod_quiz-next-nav');\n var assignSubmit = document.getElementById('id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let pastedContents = [];\n\n const postOne = async (methodname, args) => {\n try {\n const response = await call([{ methodname, args }])[0];\n return response;\n } catch (error) {\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n if (document.getElementById('page-mod-assign-editsubmission') ||\n document.getElementById('page-mod-forum-post') ||\n document.getElementById('page-mod-forum-view')) {\n\n if (assignSubmit) {\n const handleAssignSubmit = async function(e) {\n e.preventDefault();\n\n if (filename) {\n await syncData();\n }\n\n assignSubmit.removeEventListener('click', handleAssignSubmit);\n assignSubmit.click();\n assignSubmit.removeEventListener('click', handleAssignSubmit);\n\n localStorage.removeItem('lastCopyCutContent');\n };\n\n assignSubmit.addEventListener('click', handleAssignSubmit);\n }\n }\n\n if (document.getElementById('page-mod-quiz-attempt')) {\n if (quizSubmit) {\n const handleQuizSubmit = async (e) => {\n e.preventDefault();\n\n if (filename) {\n await syncData();\n document.querySelector('#responseform').submit();\n } else {\n quizSubmit.removeEventListener('click', handleQuizSubmit);\n quizSubmit.click();\n }\n\n localStorage.removeItem('lastCopyCutContent');\n };\n\n quizSubmit.addEventListener('click', handleQuizSubmit);\n }\n }\n\n const getModal = (e) => {\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 return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n }).done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n modal.getRoot().on(save, function() {\n var number = document.getElementById(\"inputUrl\").value.trim();\n let ur = e.srcElement.baseURI;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n resourceId = modulesInfo.resourceId;\n modulename = modulesInfo.name;\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\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 lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\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 const sendKeyEvent = (events, eds) => {\n ed = eds;\n event = events;\n let ur = eds.srcElement.baseURI;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n resourceId = modulesInfo.resourceId;\n modulename = modulesInfo.name;\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n if (ed.key !== \"Process\") {\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: ed.key,\n keyCode: ed.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: pastedContents\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: ed.key,\n keyCode: ed.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: pastedContents\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\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 if (is_student && intervention) {\n if (pastedContent !== localStorage.getItem('lastCopyCutContent')) {\n getModal(e);\n pastedContents = [];\n pastedContents.push(pastedContent);\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"Paste\", {\n ...e,\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition\n });\n }\n }\n });\n\n editor.on('Redo', async (e) => {\n customTooltip();\n if (is_student && intervention) {\n getModal(e);\n }\n });\n\n editor.on('keyDown', (editor) => {\n customTooltip();\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n\n editor.on('mouseDown', async (editor) => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n });\n\n editor.on('mouseUp', async (editor) => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n });\n\n editor.on('init', () => {\n customTooltip();\n });\n\n editor.on('SetContent', () => {\n customTooltip();\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();\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 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 const rng = editor.selection.getRng();\n let absolutePosition = 0;\n let node = rng.startContainer;\n absolutePosition = rng.startOffset;\n while (node && node !== editor.getBody()) {\n while (node.previousSibling) {\n node = node.previousSibling;\n if (node.textContent) {\n absolutePosition += node.textContent.length;\n }\n }\n node = node.parentNode;\n }\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\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 return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: 0, rePosition: 0};\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 let data = localStorage.getItem(filename);\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n let originalText = editor.getContent({format: 'text'});\n try {\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 /**\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 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 const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n cursiveState(cursiveIcon, menubarDiv, classArray);\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n const element = document.querySelector(`#${elementId}`);\n if (element) {\n element.addEventListener('mouseenter', function() {\n this.style.position = 'relative';\n const tooltip = document.querySelector(`#${tooltipId}`);\n if (tooltip) {\n Object.assign(tooltip.style, tooltipCss);\n }\n });\n element.addEventListener('mouseleave', function() {\n const tooltip = document.querySelector(`#${tooltipId}`);\n if (tooltip) {\n tooltip.style.display = 'none';\n }\n });\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} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [buttonTitle, buttonDes] = await Promise.all([\n getString('cursive:state:active', 'tiny_cursive'),\n getString('cursive:state:active:des', 'tiny_cursive'),\n ]);\n return {buttonTitle, buttonDes};\n }\n\n /**\n * Updates the Cursive icon state and positions it in the menubar\n * @param {HTMLElement} cursiveIcon - The Cursive icon element to modify\n * @param {HTMLElement} menubarDiv - The menubar div element\n * @param {Array} classArray - Array of class names for the menubar div elements\n */\n function cursiveState(cursiveIcon, menubarDiv, classArray) {\n if (menubarDiv) {\n for (let index in classArray) {\n const rightWrapper = document.createElement('div');\n const imgWrapper = document.createElement('span');\n const iconClone = cursiveIcon.cloneNode(true);\n const targetMenu = document.querySelector('.' + classArray[index]);\n let elementId = \"tiny_cursive_StateIcon\" + index;\n rightWrapper.style.marginLeft = 'auto';\n rightWrapper.style.display = 'flex';\n rightWrapper.style.alignItems = 'center';\n imgWrapper.id = elementId;\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n }\n }\n }\n\n /**\n * Sets up tooltip content and styling for the Cursive icon\n * @param {Object} text - Object containing tooltip text strings\n * @param {string} text.buttonTitle - Title text for the tooltip\n * @param {string} text.buttonDes - Description text for the tooltip\n * @param {HTMLElement} cursiveIcon - The Cursive icon element to attach tooltip to\n * @param {string} tooltipId - ID for the tooltip element\n */\n function setTooltip(text, cursiveIcon, tooltipId) {\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n const tooltipSpan = document.createElement('span');\n const description = document.createElement('span');\n const linebreak = document.createElement('br');\n const tooltipTitle = document.createElement('strong');\n tooltipSpan.style.display = 'none';\n tooltipTitle.textContent = text.buttonTitle;\n tooltipTitle.style.fontSize = '16px';\n tooltipTitle.style.fontWeight = 'bold';\n description.textContent = text.buttonDes;\n description.style.fontSize = '14px';\n tooltipSpan.id = tooltipId;\n tooltipSpan.classList.add(`shadow`);\n tooltipSpan.appendChild(tooltipTitle);\n tooltipSpan.appendChild(linebreak);\n tooltipSpan.appendChild(description);\n cursiveIcon.appendChild(tooltipSpan);\n }\n }\n\n /**\n * Extracts module information from URL parameters\n * @param {string} ur - The base URL to analyze\n * @param {URL} parm - URL object containing search parameters\n * @param {Array} MODULES - Array of valid module names to check against\n * @returns {Object|boolean} Object containing resourceId and module name if found, false if no valid module\n */\n function getModulesInfo(ur, parm, MODULES) {\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\n if (ur.includes(\"forum\") && !ur.includes(\"assign\")) {\n resourceId = parm.searchParams.get('edit');\n } else {\n resourceId = parm.searchParams.get('attempt');\n }\n if (resourceId === null) {\n resourceId = 0;\n }\n for (const module of MODULES) {\n if (ur.includes(module)) {\n modulename = module;\n if (module === \"lesson\" || module === \"assign\") {\n resourceId = cmid;\n } else if (module === \"oublog\") {\n resourceId = 0;\n }\n break;\n }\n }\n return {resourceId: resourceId, name: modulename};\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","is_student","document","querySelector","classList","contains","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","modulename","questionid","resourceId","quizSubmit","getElementById","assignSubmit","syncInterval","lastCaretPos","pastedContents","postOne","async","methodname","args","error","window","console","handleAssignSubmit","e","preventDefault","syncData","removeEventListener","click","localStorage","removeItem","addEventListener","handleQuizSubmit","submit","getModal","Promise","all","then","title","titledes","placeholder","type","body","removeOnClose","done","modal","getRoot","addClass","show","lastEvent","on","save","number","value","trim","ur","srcElement","baseURI","modulesInfo","getModulesInfo","URL","name","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","catch","sendKeyEvent","events","eds","split","key","getItem","data","JSON","parse","push","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","setItem","stringify","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","rng","getRng","absolutePosition","node","startContainer","startOffset","getBody","previousSibling","textContent","length","parentNode","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","originalText","getContent","format","json_data","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","querySelectorAll","classArray","forEach","element","index","className","add","cursiveIcon","createElement","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","elementId","marginLeft","alignItems","appendChild","cursiveState","tooltipId","text","setTooltip","tooltip","Object","assign","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","parm","some","module","includes","searchParams","get","clipboardData","originalEvent","getData","selectedContent","setInterval"],"mappings":"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"} \ No newline at end of file +{"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 {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES) => {\n var is_student = !document.querySelector('#body').classList.contains('teacher_admin');\n var intervention = document.querySelector('#body').classList.contains('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 modulename = \"\";\n var questionid = 0;\n var resourceId = 0;\n var quizSubmit = document.getElementById('mod_quiz-next-nav');\n var assignSubmit = document.getElementById('id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let pastedContents = [];\n\n const postOne = async (methodname, args) => {\n try {\n const response = await call([{ methodname, args }])[0];\n return response;\n } catch (error) {\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n if (document.getElementById('page-mod-assign-editsubmission') ||\n document.getElementById('page-mod-forum-post') ||\n document.getElementById('page-mod-forum-view')) {\n\n if (assignSubmit) {\n const handleAssignSubmit = async function(e) {\n e.preventDefault();\n\n if (filename) {\n await syncData();\n }\n\n assignSubmit.removeEventListener('click', handleAssignSubmit);\n assignSubmit.click();\n assignSubmit.removeEventListener('click', handleAssignSubmit);\n\n localStorage.removeItem('lastCopyCutContent');\n };\n\n assignSubmit.addEventListener('click', handleAssignSubmit);\n }\n }\n\n if (document.getElementById('page-mod-quiz-attempt')) {\n if (quizSubmit) {\n const handleQuizSubmit = async (e) => {\n e.preventDefault();\n\n if (filename) {\n await syncData();\n document.querySelector('#responseform').submit();\n } else {\n quizSubmit.removeEventListener('click', handleQuizSubmit);\n quizSubmit.click();\n }\n\n localStorage.removeItem('lastCopyCutContent');\n };\n\n quizSubmit.addEventListener('click', handleQuizSubmit);\n }\n }\n\n const getModal = (e) => {\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 return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n }).done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n modal.getRoot().on(save, function() {\n var number = document.getElementById(\"inputUrl\").value.trim();\n let ur = e.srcElement.baseURI;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n resourceId = modulesInfo.resourceId;\n modulename = modulesInfo.name;\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\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 lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\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 const sendKeyEvent = (events, eds) => {\n ed = eds;\n event = events;\n let ur = eds.srcElement.baseURI;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n resourceId = modulesInfo.resourceId;\n modulename = modulesInfo.name;\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n if (ed.key !== \"Process\") {\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: ed.key,\n keyCode: ed.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: pastedContents\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: ed.key,\n keyCode: ed.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: pastedContents\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\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 if (is_student && intervention) {\n if (pastedContent !== localStorage.getItem('lastCopyCutContent')) {\n getModal(e);\n pastedContents = [];\n pastedContents.push(pastedContent);\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"Paste\", {\n ...e,\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition\n });\n }\n }\n });\n\n editor.on('Redo', async (e) => {\n customTooltip();\n if (is_student && intervention) {\n getModal(e);\n }\n });\n\n editor.on('keyDown', (editor) => {\n customTooltip();\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n\n editor.on('mouseDown', async (editor) => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n });\n\n editor.on('mouseUp', async (editor) => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n });\n\n editor.on('init', () => {\n customTooltip();\n });\n\n editor.on('SetContent', () => {\n customTooltip();\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();\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 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 const rng = editor.selection.getRng();\n let absolutePosition = 0;\n let node = rng.startContainer;\n absolutePosition = rng.startOffset;\n while (node && node !== editor.getBody()) {\n while (node.previousSibling) {\n node = node.previousSibling;\n if (node.textContent) {\n absolutePosition += node.textContent.length;\n }\n }\n node = node.parentNode;\n }\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\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 return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: 0, rePosition: 0};\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 let data = localStorage.getItem(filename);\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n let originalText = editor.getContent({format: 'text'});\n try {\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 /**\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 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 const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n cursiveState(cursiveIcon, menubarDiv, classArray);\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n const element = document.querySelector(`#${elementId}`);\n if (element) {\n element.addEventListener('mouseenter', function() {\n this.style.position = 'relative';\n const tooltip = document.querySelector(`#${tooltipId}`);\n if (tooltip) {\n Object.assign(tooltip.style, tooltipCss);\n }\n });\n element.addEventListener('mouseleave', function() {\n const tooltip = document.querySelector(`#${tooltipId}`);\n if (tooltip) {\n tooltip.style.display = 'none';\n }\n });\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} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [buttonTitle, buttonDes] = await Promise.all([\n getString('cursive:state:active', 'tiny_cursive'),\n getString('cursive:state:active:des', 'tiny_cursive'),\n ]);\n return {buttonTitle, buttonDes};\n }\n\n /**\n * Updates the Cursive icon state and positions it in the menubar\n * @param {HTMLElement} cursiveIcon - The Cursive icon element to modify\n * @param {HTMLElement} menubarDiv - The menubar div element\n * @param {Array} classArray - Array of class names for the menubar div elements\n */\n function cursiveState(cursiveIcon, menubarDiv, classArray) {\n if (menubarDiv) {\n for (let index in classArray) {\n const rightWrapper = document.createElement('div');\n const imgWrapper = document.createElement('span');\n const iconClone = cursiveIcon.cloneNode(true);\n const targetMenu = document.querySelector('.' + classArray[index]);\n let elementId = \"tiny_cursive_StateIcon\" + index;\n rightWrapper.style.marginLeft = 'auto';\n rightWrapper.style.display = 'flex';\n rightWrapper.style.alignItems = 'center';\n imgWrapper.id = elementId;\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n }\n }\n }\n\n /**\n * Sets up tooltip content and styling for the Cursive icon\n * @param {Object} text - Object containing tooltip text strings\n * @param {string} text.buttonTitle - Title text for the tooltip\n * @param {string} text.buttonDes - Description text for the tooltip\n * @param {HTMLElement} cursiveIcon - The Cursive icon element to attach tooltip to\n * @param {string} tooltipId - ID for the tooltip element\n */\n function setTooltip(text, cursiveIcon, tooltipId) {\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n const tooltipSpan = document.createElement('span');\n const description = document.createElement('span');\n const linebreak = document.createElement('br');\n const tooltipTitle = document.createElement('strong');\n tooltipSpan.style.display = 'none';\n tooltipTitle.textContent = text.buttonTitle;\n tooltipTitle.style.fontSize = '16px';\n tooltipTitle.style.fontWeight = 'bold';\n description.textContent = text.buttonDes;\n description.style.fontSize = '14px';\n tooltipSpan.id = tooltipId;\n tooltipSpan.classList.add(`shadow`);\n tooltipSpan.appendChild(tooltipTitle);\n tooltipSpan.appendChild(linebreak);\n tooltipSpan.appendChild(description);\n cursiveIcon.appendChild(tooltipSpan);\n }\n }\n\n /**\n * Extracts module information from URL parameters\n * @param {string} ur - The base URL to analyze\n * @param {URL} parm - URL object containing search parameters\n * @param {Array} MODULES - Array of valid module names to check against\n * @returns {Object|boolean} Object containing resourceId and module name if found, false if no valid module\n */\n function getModulesInfo(ur, parm, MODULES) {\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\n if (ur.includes(\"forum\") && !ur.includes(\"assign\")) {\n resourceId = parm.searchParams.get('edit');\n } else {\n resourceId = parm.searchParams.get('attempt');\n }\n if (resourceId === null) {\n resourceId = 0;\n }\n for (const module of MODULES) {\n if (ur.includes(module)) {\n modulename = module;\n if (module === \"lesson\" || module === \"assign\") {\n resourceId = cmid;\n } else if (module === \"oublog\") {\n resourceId = 0;\n }\n break;\n }\n }\n return {resourceId: resourceId, name: modulename};\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","is_student","document","querySelector","classList","contains","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","modulename","questionid","resourceId","quizSubmit","getElementById","assignSubmit","syncInterval","lastCaretPos","pastedContents","postOne","async","methodname","args","error","window","console","handleAssignSubmit","e","preventDefault","syncData","removeEventListener","click","localStorage","removeItem","addEventListener","handleQuizSubmit","submit","getModal","Promise","all","then","title","titledes","placeholder","type","body","removeOnClose","done","modal","getRoot","addClass","show","lastEvent","on","save","number","value","trim","ur","srcElement","baseURI","modulesInfo","getModulesInfo","URL","name","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","catch","sendKeyEvent","events","eds","split","key","getItem","data","JSON","parse","push","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","setItem","stringify","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","rng","getRng","absolutePosition","node","startContainer","startOffset","getBody","previousSibling","textContent","length","parentNode","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","originalText","getContent","format","json_data","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","querySelectorAll","classArray","forEach","element","index","className","add","cursiveIcon","createElement","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","elementId","marginLeft","alignItems","appendChild","cursiveState","tooltipId","text","setTooltip","tooltip","Object","assign","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","parm","some","module","includes","searchParams","get","clipboardData","originalEvent","getData","selectedContent","setInterval"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/common.min.js b/amd/build/common.min.js index 6ef740d8..e383a730 100644 --- a/amd/build/common.min.js +++ b/amd/build/common.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;var _default={component:"tiny_cursive",pluginName:"tiny_cursive/plugin",iconUrl:M.util.image_url("cursive","tiny_cursive"),iconGrayUrl:M.util.image_url("cursive_gray","tiny_cursive"),tooltipCss:{display:"block",position:"absolute",transform:"translateX(-100%)",backgroundColor:"white",color:"black",border:"1px solid #ccc",marginBottom:"6px",padding:"10px",textAlign:"justify",minWidth:"200px",borderRadius:"1px",pointerEvents:"none",zIndex:1e4}};return _exports.default=_default,_exports.default})); +define("tiny_cursive/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;var _default={component:"tiny_cursive",pluginName:"".concat("tiny_cursive","/plugin"),iconUrl:M.util.image_url("cursive","tiny_cursive"),iconGrayUrl:M.util.image_url("cursive_gray","tiny_cursive"),tooltipCss:{display:"block",position:"absolute",transform:"translateX(-100%)",backgroundColor:"white",color:"black",border:"1px solid #ccc",marginBottom:"6px",padding:"10px",textAlign:"justify",minWidth:"200px",borderRadius:"1px",pointerEvents:"none",zIndex:1e4}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=common.min.js.map \ No newline at end of file diff --git a/amd/build/common.min.js.map b/amd/build/common.min.js.map index 11c488a9..9b5493d4 100644 --- a/amd/build/common.min.js.map +++ b/amd/build/common.min.js.map @@ -1 +1 @@ -{"version":3,"file":"common.min.js","sources":["../src/common.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/common\n * @category TinyMCE Editor\n * @copyright 2025 CTI \n * @author Brain Station 23 \n */\n\n\nconst component = 'tiny_cursive';\n\nexport default {\n component,\n pluginName: `${component}/plugin`,\n iconUrl: M.util.image_url('cursive', 'tiny_cursive'),\n iconGrayUrl: M.util.image_url('cursive_gray', 'tiny_cursive'),\n tooltipCss: {\n display: 'block',\n position: 'absolute',\n transform: 'translateX(-100%)',\n backgroundColor: 'white',\n color: 'black',\n border: '1px solid #ccc',\n marginBottom: '6px',\n padding: '10px',\n textAlign: 'justify',\n minWidth: '200px',\n borderRadius: '1px',\n pointerEvents: 'none',\n zIndex: 10000\n }\n};\n"],"names":["component","pluginName","iconUrl","M","util","image_url","iconGrayUrl","tooltipCss","display","position","transform","backgroundColor","color","border","marginBottom","padding","textAlign","minWidth","borderRadius","pointerEvents","zIndex"],"mappings":"0JAyBe,CACXA,UAHc,eAIdC,WAAa,sBACbC,QAASC,EAAEC,KAAKC,UAAU,UAAW,gBACrCC,YAAaH,EAAEC,KAAKC,UAAU,eAAgB,gBAC9CE,WAAY,CACRC,QAAS,QACTC,SAAU,WACVC,UAAW,oBACXC,gBAAiB,QACjBC,MAAO,QACPC,OAAQ,iBACRC,aAAc,MACdC,QAAS,OACTC,UAAW,UACXC,SAAU,QACVC,aAAc,MACdC,cAAe,OACfC,OAAQ"} \ No newline at end of file +{"version":3,"file":"common.min.js","sources":["../src/common.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/common\n * @category TinyMCE Editor\n * @copyright 2025 CTI \n * @author Brain Station 23 \n */\n\n\nconst component = 'tiny_cursive';\n\nexport default {\n component,\n pluginName: `${component}/plugin`,\n iconUrl: M.util.image_url('cursive', 'tiny_cursive'),\n iconGrayUrl: M.util.image_url('cursive_gray', 'tiny_cursive'),\n tooltipCss: {\n display: 'block',\n position: 'absolute',\n transform: 'translateX(-100%)',\n backgroundColor: 'white',\n color: 'black',\n border: '1px solid #ccc',\n marginBottom: '6px',\n padding: '10px',\n textAlign: 'justify',\n minWidth: '200px',\n borderRadius: '1px',\n pointerEvents: 'none',\n zIndex: 10000\n }\n};\n"],"names":["component","pluginName","iconUrl","M","util","image_url","iconGrayUrl","tooltipCss","display","position","transform","backgroundColor","color","border","marginBottom","padding","textAlign","minWidth","borderRadius","pointerEvents","zIndex"],"mappings":"0JAyBe,CACXA,UAHc,eAIdC,qBAJc,0BAKdC,QAASC,EAAEC,KAAKC,UAAU,UAAW,gBACrCC,YAAaH,EAAEC,KAAKC,UAAU,eAAgB,gBAC9CE,WAAY,CACRC,QAAS,QACTC,SAAU,WACVC,UAAW,oBACXC,gBAAiB,QACjBC,MAAO,QACPC,OAAQ,iBACRC,aAAc,MACdC,QAAS,OACTC,UAAW,UACXC,SAAU,QACVC,aAAc,MACdC,cAAe,OACfC,OAAQ"} \ No newline at end of file diff --git a/amd/build/replay.min.js b/amd/build/replay.min.js index 44538d4d..5fb5bd70 100644 --- a/amd/build/replay.min.js +++ b/amd/build/replay.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/replay",["exports","core/ajax","core/templates","core/str"],(function(_exports,_ajax,_templates,Str){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj},Str=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Str);return _exports.default=class{constructor(elementId,filePath){let speed=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,loop=arguments.length>3&&void 0!==arguments[3]&&arguments[3],controllerId=arguments.length>4?arguments[4]:void 0;this.controllerId=controllerId||"",this.replayInProgress=!1,this.speed=parseFloat(speed),this.loop=loop,this.highlightedChars=[],this.deletedChars=[],this.cursorPosition=0,this.currentEventIndex=0,this.totalEvents=0,this.currentTime=0,this.totalDuration=0,this.usercomments=[],this.pasteTimestamps=[],this.isPasteEvent=!1,this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.text="",this.pastedEvents=[],this.currentPasteIndex=0,this.pastedChars=[];const element=document.getElementById(elementId);if(!element)throw new Error(`Element with id '${elementId}' not found`);this.outputElement=element,this.loadJSON(filePath).then((data=>(data.status?(this.processData(data),this.totalEvents=this.logData.length,this.identifyPasteEvents(),this.controllerId&&this.logData&&this.constructController(this.controllerId),this.startReplay()):this.handleNoSubmission(),data))).catch((error=>{this.handleNoSubmission(),window.console.error("Error loading JSON file:",error.message)})),localStorage.getItem("nopasteevent")&&localStorage.getItem("pasteEvent")||(Str.get_string("nopasteevent","tiny_cursive").then((str=>(localStorage.setItem("nopasteevent",str),str))),Str.get_string("pasteEvent","tiny_cursive").then((str=>(localStorage.setItem("pasteEvent",str),str))))}processData(data){this.logData=JSON.parse(data.data),data.comments&&(this.usercomments=Array.isArray(JSON.parse(data.comments))?JSON.parse(data.comments):[]),"data"in this.logData&&(this.logData=this.logData.data),"payload"in this.logData&&(this.logData=this.logData.payload);for(let i=0;i0&&this.logData[0].unixTimestamp){const startTime=this.logData[0].unixTimestamp;this.logData=this.logData.map((event=>({...event,normalizedTime:event.unixTimestamp-startTime}))),this.totalDuration=this.logData[this.logData.length-1].normalizedTime}}async handleNoSubmission(){try{const[html,str]=await Promise.all([_templates.default.render("tiny_cursive/no_submission"),Str.get_string("warningpayload","tiny_cursive")]),tempDiv=document.createElement("div");tempDiv.innerHTML=html;const newElement=tempDiv.firstChild;newElement.textContent=str;return document.querySelectorAll(".tiny_cursive").forEach((element=>{element.innerHTML="",element.appendChild(newElement.cloneNode(!0))})),!0}catch(error){return window.console.error(error),!1}}stopReplay(){if(this.replayInProgress&&(clearTimeout(this.replayTimeout),this.replayInProgress=!1,this.playButton)){const playSvg=document.createElement("img");playSvg.src=M.util.image_url("playicon","tiny_cursive"),this.playButton.querySelector(".play-icon").innerHTML=playSvg.outerHTML}}constructController(controllerId){var _controlContainer$que;this.replayInProgress=!1,this.currentPosition=0,this.speed=1,this.replayIntervalId&&(clearInterval(this.replayIntervalId),this.replayIntervalId=null);const container=document.getElementById(controllerId);if(!container)return void window.console.error("Container not found with ID:",controllerId);const controlContainer=container.querySelector(".tiny_cursive_replay_control");controlContainer?(controlContainer.innerHTML='',this.buildControllerUI(controlContainer,container),null===(_controlContainer$que=controlContainer.querySelector(".tiny_cursive_loading_spinner"))||void 0===_controlContainer$que||_controlContainer$que.remove(),controlContainer.classList.remove("d-none")):window.console.error("Replay control container not found in:",controllerId)}buildControllerUI(controlContainer,container){const topRow=document.createElement("div");topRow.classList.add("tiny_cursive_top_row"),this.playButton=this.createPlayButton(),topRow.appendChild(this.playButton);const scrubberContainer=this.createScrubberContainer();topRow.appendChild(scrubberContainer),this.timeDisplay=this.createTimeDisplay(),topRow.appendChild(this.timeDisplay);const bottomRow=document.createElement("div");bottomRow.classList.add("tiny_cursive_bottom_row");const speedContainer=this.createSpeedControls();bottomRow.appendChild(speedContainer);const pasteEventsToggle=this.createPasteEventsToggle(container);bottomRow.appendChild(pasteEventsToggle),controlContainer.appendChild(topRow),controlContainer.appendChild(bottomRow),container.appendChild(this.pasteEventsPanel)}createPlayButton(){const playButton=document.createElement("button");playButton.classList.add("tiny_cursive_play_button");const playSvg=document.createElement("i");return playButton.innerHTML=`${playSvg.outerHTML}`,playButton.addEventListener("click",(()=>{this.replayInProgress?this.stopReplay():this.startReplay(!1),document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((btn=>btn.classList.remove("active"))),document.querySelectorAll('a[id^="rep"]').forEach((btn=>btn.classList.add("active")))})),playButton}createScrubberContainer(){const scrubberContainer=document.createElement("div");return scrubberContainer.classList.add("tiny_cursive_scrubber_container"),this.scrubberElement=document.createElement("input"),this.scrubberElement.classList.add("tiny_cursive_timeline_scrubber","timeline-scrubber"),this.scrubberElement.type="range",this.scrubberElement.max="100",this.scrubberElement.min="0",this.scrubberElement.value="0",this.scrubberElement.addEventListener("input",(()=>{this.skipToTime(parseInt(this.scrubberElement.value,10))})),scrubberContainer.appendChild(this.scrubberElement),scrubberContainer}createTimeDisplay(){const timeDisplay=document.createElement("div");return timeDisplay.classList.add("tiny_cursive_time_display"),timeDisplay.textContent="00:00 / 00:00",timeDisplay}createSpeedControls(){const speedContainer=document.createElement("div");speedContainer.classList.add("tiny_cursive_speed_controls","speed-controls");const speedLabel=document.createElement("span");speedLabel.classList.add("tiny_cursive_speed_label"),speedLabel.textContent="Speed: ",speedContainer.appendChild(speedLabel);const speedGroup=document.createElement("div");return speedGroup.classList.add("tiny_cursive_speed_group"),[1,1.5,2,5,10].forEach((speed=>{const speedBtn=document.createElement("button");speedBtn.textContent=`${speed}x`,speedBtn.classList.add("tiny_cursive_speed_btn","speed-btn"),parseFloat(speed)===this.speed&&speedBtn.classList.add("active"),speedBtn.dataset.speed=speed,speedBtn.addEventListener("click",(()=>{document.querySelectorAll(".tiny_cursive_speed_btn").forEach((btn=>btn.classList.remove("active"))),speedBtn.classList.add("active"),this.speed=parseFloat(speedBtn.dataset.speed),this.replayInProgress&&(this.stopReplay(),this.startReplay(!1))})),speedGroup.appendChild(speedBtn)})),speedContainer.appendChild(speedGroup),speedContainer}createPasteEventsToggle(container){const pasteEventsToggle=document.createElement("div");pasteEventsToggle.classList.add("tiny_cursive_paste_events_toggle","paste-events-toggle");const pasteEventsIcon=document.createElement("span"),pasteIcon=document.createElement("img");pasteIcon.src=M.util.image_url("pasteicon","tiny_cursive"),pasteEventsIcon.innerHTML=pasteIcon.outerHTML,pasteEventsIcon.classList.add("tiny_cursive_paste_events_icon");const pasteEventsText=document.createElement("span");pasteEventsText.textContent=localStorage.getItem("pasteEvent"),this.pasteEventCount=document.createElement("span"),this.pasteEventCount.textContent=`(${this.pasteTimestamps.length})`,this.pasteEventCount.className="paste-event-count",this.pasteEventCount.style.marginLeft="2px";const chevronIcon=document.createElement("span"),chevron=document.createElement("i");return chevron.className="fa fa-chevron-down",chevronIcon.innerHTML=chevron.outerHTML,chevronIcon.style.marginLeft="5px",chevronIcon.style.transition="transform 0.3s ease",pasteEventsToggle.appendChild(pasteEventsIcon),pasteEventsToggle.appendChild(pasteEventsText),pasteEventsToggle.appendChild(this.pasteEventCount),pasteEventsToggle.appendChild(chevronIcon),this.pasteEventsPanel=this.createPasteEventsPanel(container),pasteEventsToggle.addEventListener("click",(()=>{const isHidden="none"===this.pasteEventsPanel.style.display;this.pasteEventsPanel.style.display=isHidden?"block":"none",chevronIcon.style.transform=isHidden?"rotate(180deg)":"rotate(0deg)"})),pasteEventsToggle}createPasteEventsPanel(container){const existingPanel=container.querySelector(".paste-events-panel");existingPanel&&existingPanel.remove();const pasteEventsPanel=document.createElement("div");return pasteEventsPanel.classList.add("tiny_cursive_paste_events_panel","paste-events-panel"),pasteEventsPanel.style.display="none",this.populatePasteEventsPanel(pasteEventsPanel),pasteEventsPanel}identifyPasteEvents(){this.pasteTimestamps=[];let controlPressed=!1,shiftPressed=!1,pasteCount=0;for(let i=0;i0&&0===this.pasteTimestamps.length&&this.usercomments.forEach(((comment,i)=>{this.pasteTimestamps.push({index:i,time:0,formattedTime:this.formatTime(0),pastedText:comment,timestamp:0})}));this.pasteTimestamps.length';const nextButton=document.createElement("button");nextButton.classList.add("paste-event-next-btn","tiny_cursive_nav_button"),nextButton.innerHTML='',nextButton.disabled=this.pasteTimestamps.length<=1,navButtons.appendChild(prevButton),navButtons.appendChild(nextButton),navigationRow.appendChild(counterDisplay),navigationRow.appendChild(navButtons);const contentContainer=document.createElement("div");contentContainer.className="paste-events-content tiny_cursive_content_container",contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[0])),carouselContainer.appendChild(navigationRow),carouselContainer.appendChild(contentContainer),panel.appendChild(carouselContainer);let currentIndex=0;const updateDisplay=()=>{contentContainer.innerHTML="",contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[currentIndex])),counterDisplay.textContent="Paste Events",prevButton.disabled=0===currentIndex,prevButton.style.opacity=0===currentIndex?"0.5":"1",nextButton.disabled=currentIndex===this.pasteTimestamps.length-1,nextButton.style.opacity=currentIndex===this.pasteTimestamps.length-1?"0.5":"1"};prevButton.addEventListener("click",(()=>{currentIndex>0&&(currentIndex--,updateDisplay())})),nextButton.addEventListener("click",(()=>{currentIndexthis.jumpToTimestamp(pasteEvent.timestamp))),headerRow.appendChild(textContainer),headerRow.appendChild(playButton),eventRow.appendChild(headerRow),eventRow}jumpToTimestamp(timestamp){const percentage=this.totalDuration>0?timestamp/this.totalDuration*100:0;this.skipToTime(percentage),this.replayInProgress||this.startReplay(!1)}setScrubberVal(value){if(this.scrubberElement&&(this.scrubberElement.value=String(value),this.timeDisplay)){const displayTime=Math.min(this.currentTime,this.totalDuration);this.timeDisplay.textContent=`${this.formatTime(displayTime)} / ${this.formatTime(this.totalDuration)}`}}loadJSON(filePath){return(0,_ajax.call)([{methodname:"cursive_get_reply_json",args:{filepath:filePath}}])[0].done((response=>response)).fail((error=>{throw new Error(`Error loading JSON file: ${error.message}`)}))}formatTime(ms){const seconds=Math.floor(ms/1e3),remainingSeconds=seconds%60;return`${Math.floor(seconds/60).toString().padStart(2,"0")}:${remainingSeconds.toString().padStart(2,"0")}`}startReplay(){let reset=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.replayInProgress&&clearTimeout(this.replayTimeout);if((this.totalDuration>0&&this.currentTime>=this.totalDuration||this.currentEventIndex>=this.totalEvents)&&!reset&&(reset=!0),this.replayInProgress=!0,reset&&(this.outputElement.innerHTML="",this.text="",this.cursorPosition=0,this.currentEventIndex=0,this.currentTime=0,this.highlightedChars=[],this.deletedChars=[],this.isControlKeyPressed=!1,this.currentPasteIndex=0,this.pastedChars=[]),this.playButton){const pauseSvg=document.createElement("i");pauseSvg.className="fa fa-pause",this.playButton.querySelector(".play-icon").innerHTML=pauseSvg.outerHTML}this.replayLog()}replayLog(){if(this.replayInProgress){for(;this.currentEventIndexthis.currentTime)break;let text=this.text||"",cursor=this.cursorPosition,updatedHighlights=[...this.highlightedChars],updatedDeleted=[...this.deletedChars];void 0===event.rePosition||0!==this.currentEventIndex&&"mouseDown"!==event.event&&"mouseUp"!==event.event||(cursor=Math.max(0,Math.min(event.rePosition,text.length))),"keydown"===(null===(_event$event2=event.event)||void 0===_event$event2?void 0:_event$event2.toLowerCase())&&({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processKeydownEvent(event,text,cursor,updatedHighlights,updatedDeleted)),this.text=text,this.cursorPosition=cursor,this.highlightedChars=updatedHighlights.filter((h=>!h.expiresAt||h.expiresAt>this.currentTime)),this.deletedChars=updatedDeleted.filter((d=>!d.expiresAt||d.expiresAt>this.currentTime)),this.currentEventIndex++}if(this.updateDisplayText(this.text,this.cursorPosition,this.highlightedChars,this.deletedChars),this.totalDuration>0){const percentComplete=Math.min(this.currentTime/this.totalDuration*100,100);this.setScrubberVal(percentComplete)}if(this.replayInProgress){const baseIncrement=100,incrementTime=baseIncrement/this.speed;this.currentTime+=baseIncrement,this.currentEventIndex>=this.totalEvents?this.loop?this.startReplay(!0):(this.stopReplay(),this.updateDisplayText(this.text,this.cursorPosition,[],[])):this.replayTimeout=setTimeout((()=>this.replayLog()),incrementTime)}}else this.updateDisplayText(this.text,this.cursorPosition,[],[])}getLineAndColumn(text,pos){const before=text.substring(0,pos);return{lineIndex:before.split("\n").length-1,col:before.length-before.lastIndexOf("\n")-1}}processKeydownEvent(event,text,cursor,highlights,deletions){const key=event.key,charToInsert=this.applyKey(key);if(this.updateModifierStates(key),("v"===key||"V"===key)&&this.isControlKeyPressed&&this.pastedEvents&&this.currentPasteIndex0&&({text:text,cursor:cursor}=this.handleCharacterInsert(charToInsert,text,cursor,highlights)),{text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}}handlePasteInsert(pastedContent,text,cursor){const insertText=pastedContent||"";if(text=text.substring(0,cursor)+insertText+text.substring(cursor),""!==insertText.trim())for(let i=0;ip.index>=startIndex+numDeleted?{...p,index:p.index-numDeleted}:p.index>=startIndex&&p.indexnull!==p))}updateModifierStates(key){"Control"===key?this.isControlKeyPressed=!0:"Shift"===key?this.isShiftKeyPressed=!0:"v"!==key&&"V"!==key||!this.isControlKeyPressed?["Control","Backspace","Delete","ArrowLeft","ArrowRight"].includes(key)||(this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.isPasteEvent=!1):this.isPasteEvent=!0}isCtrlBackspace(key,cursor){return"Backspace"===key&&this.isControlKeyPressed&&cursor>0}isCtrlDelete(key,cursor,text){return"Delete"===key&&this.isControlKeyPressed&&cursor0}isRegularDelete(key,cursor,text){return"Delete"===key&&!this.isControlKeyPressed&&cursorp.index>=cursor?{...p,index:p.index+1}:p))),""!==charToInsert.trim()&&highlights.push({index:cursor,chars:charToInsert,time:this.currentTime,expiresAt:this.currentTime+1500}),{text:text,cursor:cursor+1}}handleCtrlDelete(text,cursor,deletions){const wordEnd=this.findNextWordBoundary(text,cursor),wordToDelete=text.substring(cursor,wordEnd);for(let i=0;i0){const prevLine=lines[lineIndex-1];cursor=lines.slice(0,lineIndex-1).join("\n").length+1+Math.min(col,prevLine.length)}else cursor=0;return cursor}handleArrowDown(text,cursor){const lines=text.split("\n"),{lineIndex:lineIndex,col:col}=this.getLineAndColumn(text,cursor);if(lineIndex0&&" "===text[wordStart-1];)wordStart--;for(;wordStart>0&&" "!==text[wordStart-1];)wordStart--;const wordToDelete=text.substring(wordStart,cursor);for(let i=0;i=text.length)return cursor;if(" "===text[cursor])for(;cursor=text.length){let lastNonSpace=text.length-1;for(;lastNonSpace>=0&&" "===text[lastNonSpace];)lastNonSpace--;return lastNonSpace+1}let wordEnd=cursor;for(;wordEnd0&&(" "===text[pos]||"\n"===text[pos]);)pos--;for(;pos>0&&" "!==text[pos-1]&&"\n"!==text[pos-1];)pos--;return pos}skipToEnd(){this.replayInProgress&&(this.replayInProgress=!1);let textOutput="";this.logData.forEach((event=>{"keydown"===event.event.toLowerCase()&&(textOutput=this.applyKey(event.key,textOutput))})),this.outputElement.innerHTML=textOutput.slice(0,-1),this.setScrubberVal(100)}skipToTime(percentage){const wasPlaying=this.replayInProgress;this.stopReplay();const targetTime=this.totalDuration*percentage/100;this.currentTime=targetTime,this.currentEventIndex=0,this.text="",this.cursorPosition=0,this.highlightedChars=[],this.deletedChars=[],this.isControlKeyPressed=!1,this.isPasteEvent=!1,this.pastedChars=[],this.currentPasteIndex=0;let text="",cursor=0,highlights=[],deletions=[],pasteIndex=0;for(let i=0;itargetTime){this.currentEventIndex=i;break}void 0===event.rePosition||0!==i&&"mouseDown"!==event.event&&"mouseUp"!==event.event||(cursor=Math.max(0,Math.min(event.rePosition,text.length))),"keydown"===(null===(_event$event3=event.event)||void 0===_event$event3?void 0:_event$event3.toLowerCase())&&(this.currentPasteIndex=pasteIndex,"v"!==event.key&&"V"!==event.key||!this.isControlKeyPressed||pasteIndex++,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processKeydownEvent(event,text,cursor,highlights,deletions))),this.currentEventIndex=i+1}this.currentPasteIndex=pasteIndex,this.text=text,this.cursorPosition=cursor,this.highlightedChars=highlights.filter((h=>!h.expiresAt||h.expiresAt>targetTime)),this.deletedChars=deletions.filter((d=>!d.expiresAt||d.expiresAt>targetTime)),this.updateDisplayText(this.text,this.cursorPosition,this.highlightedChars,this.deletedChars),this.setScrubberVal(percentage),wasPlaying&&(this.replayInProgress=!0,this.replayLog())}updateDisplayText(text,cursorPosition,highlights,deletions){let html="";const highlightMap={},deletionMap={},pastedMap={},currentTime=this.currentTime;highlights.forEach((h=>{let opacity=1;h.expiresAt&&h.expiresAt-currentTime<500&&(opacity=Math.max(0,(h.expiresAt-currentTime)/500)),highlightMap[h.index]={chars:h.chars,opacity:opacity}})),deletions.forEach((d=>{let opacity=.5;d.expiresAt&&d.expiresAt-currentTime<500&&(opacity=Math.max(0,(d.expiresAt-currentTime)/500*.5)),deletionMap[d.index]={chars:d.chars,opacity:opacity}})),this.pastedChars&&this.pastedChars.forEach((p=>{p.indexd.index>=text.length)),textLines=text.split("\n");let currentPosition=0;for(let lineIndex=0;lineIndex');const char=line[i];deletionMap[currentPosition]&&(html+=`${deletionMap[currentPosition].chars}`);const isPasted=pastedMap[currentPosition],isHighlighted=highlightMap[currentPosition]&&" "!==char;html+=isPasted&&isHighlighted?`${char}`:isPasted?`${" "===char?" ":this.escapeHtml(char)}`:isHighlighted?`${char}`:" "===char?" ":this.escapeHtml(char),currentPosition++}currentPosition===cursorPosition&&(html+=''),lineIndex",currentPosition++)}if(cursorPosition!==text.length||html.endsWith('')||(html+=''),outOfRangeDeletions.length>0){outOfRangeDeletions.sort(((a,b)=>a.index-b.index));const cursorHTML='',cursorPos=html.lastIndexOf(cursorHTML);if(-1!==cursorPos){let deletedWordHTML='';outOfRangeDeletions.forEach((d=>{deletedWordHTML+=d.chars})),deletedWordHTML+="",html=html.substring(0,cursorPos)+deletedWordHTML+html.substring(cursorPos)}}const wasScrolledToBottom=this.outputElement.scrollHeight-this.outputElement.clientHeight<=this.outputElement.scrollTop+1;this.outputElement.innerHTML=html,(wasScrolledToBottom||this.isCursorBelowViewport())&&(this.outputElement.scrollTop=this.outputElement.scrollHeight)}isCursorBelowViewport(){const cursorElement=this.outputElement.querySelector(".tiny_cursive-cursor:last-of-type");if(!cursorElement)return!1;const cursorRect=cursorElement.getBoundingClientRect(),outputRect=this.outputElement.getBoundingClientRect();return cursorRect.bottom>outputRect.bottom}escapeHtml(unsafe){return unsafe.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}applyKey(key){switch(key){case"Enter":return"\n";case"Backspace":case"Delete":case"ControlBackspace":return"";case" ":return" ";default:return["Shift","Ctrl","Alt","ArrowDown","ArrowUp","Control","ArrowRight","ArrowLeft","Meta","CapsLock","Tab","Escape","Delete","PageUp","PageDown","Insert","Home","End","NumLock","AudioVolumeUp","AudioVolumeDown","MediaPlayPause","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","PrintScreen","UnIdentified"].includes(key)?"":key}}},_exports.default})); +define("tiny_cursive/replay",["exports","core/ajax","core/templates","core/str"],(function(_exports,_ajax,_templates,Str){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj},Str=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Str);return _exports.default=class{constructor(elementId,filePath){let speed=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,loop=arguments.length>3&&void 0!==arguments[3]&&arguments[3],controllerId=arguments.length>4?arguments[4]:void 0;this.controllerId=controllerId||"",this.replayInProgress=!1,this.speed=parseFloat(speed),this.loop=loop,this.highlightedChars=[],this.deletedChars=[],this.cursorPosition=0,this.currentEventIndex=0,this.totalEvents=0,this.currentTime=0,this.totalDuration=0,this.usercomments=[],this.pasteTimestamps=[],this.isPasteEvent=!1,this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.text="",this.pastedEvents=[],this.currentPasteIndex=0,this.pastedChars=[];const element=document.getElementById(elementId);if(!element)throw new Error("Element with id '".concat(elementId,"' not found"));this.outputElement=element,this.loadJSON(filePath).then((data=>(data.status?(this.processData(data),this.totalEvents=this.logData.length,this.identifyPasteEvents(),this.controllerId&&this.logData&&this.constructController(this.controllerId),this.startReplay()):this.handleNoSubmission(),data))).catch((error=>{this.handleNoSubmission(),window.console.error("Error loading JSON file:",error.message)})),localStorage.getItem("nopasteevent")&&localStorage.getItem("pasteEvent")||(Str.get_string("nopasteevent","tiny_cursive").then((str=>(localStorage.setItem("nopasteevent",str),str))),Str.get_string("pasteEvent","tiny_cursive").then((str=>(localStorage.setItem("pasteEvent",str),str))))}processData(data){this.logData=JSON.parse(data.data),data.comments&&(this.usercomments=Array.isArray(JSON.parse(data.comments))?JSON.parse(data.comments):[]),"data"in this.logData&&(this.logData=this.logData.data),"payload"in this.logData&&(this.logData=this.logData.payload);for(let i=0;i0&&this.logData[0].unixTimestamp){const startTime=this.logData[0].unixTimestamp;this.logData=this.logData.map((event=>({...event,normalizedTime:event.unixTimestamp-startTime}))),this.totalDuration=this.logData[this.logData.length-1].normalizedTime}}async handleNoSubmission(){try{const[html,str]=await Promise.all([_templates.default.render("tiny_cursive/no_submission"),Str.get_string("warningpayload","tiny_cursive")]),tempDiv=document.createElement("div");tempDiv.innerHTML=html;const newElement=tempDiv.firstChild;newElement.textContent=str;return document.querySelectorAll(".tiny_cursive").forEach((element=>{element.innerHTML="",element.appendChild(newElement.cloneNode(!0))})),!0}catch(error){return window.console.error(error),!1}}stopReplay(){if(this.replayInProgress&&(clearTimeout(this.replayTimeout),this.replayInProgress=!1,this.playButton)){const playSvg=document.createElement("img");playSvg.src=M.util.image_url("playicon","tiny_cursive"),this.playButton.querySelector(".play-icon").innerHTML=playSvg.outerHTML}}constructController(controllerId){var _controlContainer$que;this.replayInProgress=!1,this.currentPosition=0,this.speed=1,this.replayIntervalId&&(clearInterval(this.replayIntervalId),this.replayIntervalId=null);const container=document.getElementById(controllerId);if(!container)return void window.console.error("Container not found with ID:",controllerId);const controlContainer=container.querySelector(".tiny_cursive_replay_control");controlContainer?(controlContainer.innerHTML='',this.buildControllerUI(controlContainer,container),null===(_controlContainer$que=controlContainer.querySelector(".tiny_cursive_loading_spinner"))||void 0===_controlContainer$que||_controlContainer$que.remove(),controlContainer.classList.remove("d-none")):window.console.error("Replay control container not found in:",controllerId)}buildControllerUI(controlContainer,container){const topRow=document.createElement("div");topRow.classList.add("tiny_cursive_top_row"),this.playButton=this.createPlayButton(),topRow.appendChild(this.playButton);const scrubberContainer=this.createScrubberContainer();topRow.appendChild(scrubberContainer),this.timeDisplay=this.createTimeDisplay(),topRow.appendChild(this.timeDisplay);const bottomRow=document.createElement("div");bottomRow.classList.add("tiny_cursive_bottom_row");const speedContainer=this.createSpeedControls();bottomRow.appendChild(speedContainer);const pasteEventsToggle=this.createPasteEventsToggle(container);bottomRow.appendChild(pasteEventsToggle),controlContainer.appendChild(topRow),controlContainer.appendChild(bottomRow),container.appendChild(this.pasteEventsPanel)}createPlayButton(){const playButton=document.createElement("button");playButton.classList.add("tiny_cursive_play_button");const playSvg=document.createElement("i");return playButton.innerHTML=''.concat(playSvg.outerHTML,""),playButton.addEventListener("click",(()=>{this.replayInProgress?this.stopReplay():this.startReplay(!1),document.querySelectorAll(".tiny_cursive-nav-tab .active").forEach((btn=>btn.classList.remove("active"))),document.querySelectorAll('a[id^="rep"]').forEach((btn=>btn.classList.add("active")))})),playButton}createScrubberContainer(){const scrubberContainer=document.createElement("div");return scrubberContainer.classList.add("tiny_cursive_scrubber_container"),this.scrubberElement=document.createElement("input"),this.scrubberElement.classList.add("tiny_cursive_timeline_scrubber","timeline-scrubber"),this.scrubberElement.type="range",this.scrubberElement.max="100",this.scrubberElement.min="0",this.scrubberElement.value="0",this.scrubberElement.addEventListener("input",(()=>{this.skipToTime(parseInt(this.scrubberElement.value,10))})),scrubberContainer.appendChild(this.scrubberElement),scrubberContainer}createTimeDisplay(){const timeDisplay=document.createElement("div");return timeDisplay.classList.add("tiny_cursive_time_display"),timeDisplay.textContent="00:00 / 00:00",timeDisplay}createSpeedControls(){const speedContainer=document.createElement("div");speedContainer.classList.add("tiny_cursive_speed_controls","speed-controls");const speedLabel=document.createElement("span");speedLabel.classList.add("tiny_cursive_speed_label"),speedLabel.textContent="Speed: ",speedContainer.appendChild(speedLabel);const speedGroup=document.createElement("div");return speedGroup.classList.add("tiny_cursive_speed_group"),[1,1.5,2,5,10].forEach((speed=>{const speedBtn=document.createElement("button");speedBtn.textContent="".concat(speed,"x"),speedBtn.classList.add("tiny_cursive_speed_btn","speed-btn"),parseFloat(speed)===this.speed&&speedBtn.classList.add("active"),speedBtn.dataset.speed=speed,speedBtn.addEventListener("click",(()=>{document.querySelectorAll(".tiny_cursive_speed_btn").forEach((btn=>btn.classList.remove("active"))),speedBtn.classList.add("active"),this.speed=parseFloat(speedBtn.dataset.speed),this.replayInProgress&&(this.stopReplay(),this.startReplay(!1))})),speedGroup.appendChild(speedBtn)})),speedContainer.appendChild(speedGroup),speedContainer}createPasteEventsToggle(container){const pasteEventsToggle=document.createElement("div");pasteEventsToggle.classList.add("tiny_cursive_paste_events_toggle","paste-events-toggle");const pasteEventsIcon=document.createElement("span"),pasteIcon=document.createElement("img");pasteIcon.src=M.util.image_url("pasteicon","tiny_cursive"),pasteEventsIcon.innerHTML=pasteIcon.outerHTML,pasteEventsIcon.classList.add("tiny_cursive_paste_events_icon");const pasteEventsText=document.createElement("span");pasteEventsText.textContent=localStorage.getItem("pasteEvent"),this.pasteEventCount=document.createElement("span"),this.pasteEventCount.textContent="(".concat(this.pasteTimestamps.length,")"),this.pasteEventCount.className="paste-event-count",this.pasteEventCount.style.marginLeft="2px";const chevronIcon=document.createElement("span"),chevron=document.createElement("i");return chevron.className="fa fa-chevron-down",chevronIcon.innerHTML=chevron.outerHTML,chevronIcon.style.marginLeft="5px",chevronIcon.style.transition="transform 0.3s ease",pasteEventsToggle.appendChild(pasteEventsIcon),pasteEventsToggle.appendChild(pasteEventsText),pasteEventsToggle.appendChild(this.pasteEventCount),pasteEventsToggle.appendChild(chevronIcon),this.pasteEventsPanel=this.createPasteEventsPanel(container),pasteEventsToggle.addEventListener("click",(()=>{const isHidden="none"===this.pasteEventsPanel.style.display;this.pasteEventsPanel.style.display=isHidden?"block":"none",chevronIcon.style.transform=isHidden?"rotate(180deg)":"rotate(0deg)"})),pasteEventsToggle}createPasteEventsPanel(container){const existingPanel=container.querySelector(".paste-events-panel");existingPanel&&existingPanel.remove();const pasteEventsPanel=document.createElement("div");return pasteEventsPanel.classList.add("tiny_cursive_paste_events_panel","paste-events-panel"),pasteEventsPanel.style.display="none",this.populatePasteEventsPanel(pasteEventsPanel),pasteEventsPanel}identifyPasteEvents(){this.pasteTimestamps=[];let controlPressed=!1,shiftPressed=!1,pasteCount=0;for(let i=0;i0&&0===this.pasteTimestamps.length&&this.usercomments.forEach(((comment,i)=>{this.pasteTimestamps.push({index:i,time:0,formattedTime:this.formatTime(0),pastedText:comment,timestamp:0})}));this.pasteTimestamps.length';const nextButton=document.createElement("button");nextButton.classList.add("paste-event-next-btn","tiny_cursive_nav_button"),nextButton.innerHTML='',nextButton.disabled=this.pasteTimestamps.length<=1,navButtons.appendChild(prevButton),navButtons.appendChild(nextButton),navigationRow.appendChild(counterDisplay),navigationRow.appendChild(navButtons);const contentContainer=document.createElement("div");contentContainer.className="paste-events-content tiny_cursive_content_container",contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[0])),carouselContainer.appendChild(navigationRow),carouselContainer.appendChild(contentContainer),panel.appendChild(carouselContainer);let currentIndex=0;const updateDisplay=()=>{contentContainer.innerHTML="",contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[currentIndex])),counterDisplay.textContent="Paste Events",prevButton.disabled=0===currentIndex,prevButton.style.opacity=0===currentIndex?"0.5":"1",nextButton.disabled=currentIndex===this.pasteTimestamps.length-1,nextButton.style.opacity=currentIndex===this.pasteTimestamps.length-1?"0.5":"1"};prevButton.addEventListener("click",(()=>{currentIndex>0&&(currentIndex--,updateDisplay())})),nextButton.addEventListener("click",(()=>{currentIndexthis.jumpToTimestamp(pasteEvent.timestamp))),headerRow.appendChild(textContainer),headerRow.appendChild(playButton),eventRow.appendChild(headerRow),eventRow}jumpToTimestamp(timestamp){const percentage=this.totalDuration>0?timestamp/this.totalDuration*100:0;this.skipToTime(percentage),this.replayInProgress||this.startReplay(!1)}setScrubberVal(value){if(this.scrubberElement&&(this.scrubberElement.value=String(value),this.timeDisplay)){const displayTime=Math.min(this.currentTime,this.totalDuration);this.timeDisplay.textContent="".concat(this.formatTime(displayTime)," / ").concat(this.formatTime(this.totalDuration))}}loadJSON(filePath){return(0,_ajax.call)([{methodname:"cursive_get_reply_json",args:{filepath:filePath}}])[0].done((response=>response)).fail((error=>{throw new Error("Error loading JSON file: ".concat(error.message))}))}formatTime(ms){const seconds=Math.floor(ms/1e3),minutes=Math.floor(seconds/60),remainingSeconds=seconds%60;return"".concat(minutes.toString().padStart(2,"0"),":").concat(remainingSeconds.toString().padStart(2,"0"))}startReplay(){let reset=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.replayInProgress&&clearTimeout(this.replayTimeout);if((this.totalDuration>0&&this.currentTime>=this.totalDuration||this.currentEventIndex>=this.totalEvents)&&!reset&&(reset=!0),this.replayInProgress=!0,reset&&(this.outputElement.innerHTML="",this.text="",this.cursorPosition=0,this.currentEventIndex=0,this.currentTime=0,this.highlightedChars=[],this.deletedChars=[],this.isControlKeyPressed=!1,this.currentPasteIndex=0,this.pastedChars=[]),this.playButton){const pauseSvg=document.createElement("i");pauseSvg.className="fa fa-pause",this.playButton.querySelector(".play-icon").innerHTML=pauseSvg.outerHTML}this.replayLog()}replayLog(){if(this.replayInProgress){for(;this.currentEventIndexthis.currentTime)break;let text=this.text||"",cursor=this.cursorPosition,updatedHighlights=[...this.highlightedChars],updatedDeleted=[...this.deletedChars];void 0===event.rePosition||0!==this.currentEventIndex&&"mouseDown"!==event.event&&"mouseUp"!==event.event||(cursor=Math.max(0,Math.min(event.rePosition,text.length))),"keydown"===(null===(_event$event2=event.event)||void 0===_event$event2?void 0:_event$event2.toLowerCase())&&({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processKeydownEvent(event,text,cursor,updatedHighlights,updatedDeleted)),this.text=text,this.cursorPosition=cursor,this.highlightedChars=updatedHighlights.filter((h=>!h.expiresAt||h.expiresAt>this.currentTime)),this.deletedChars=updatedDeleted.filter((d=>!d.expiresAt||d.expiresAt>this.currentTime)),this.currentEventIndex++}if(this.updateDisplayText(this.text,this.cursorPosition,this.highlightedChars,this.deletedChars),this.totalDuration>0){const percentComplete=Math.min(this.currentTime/this.totalDuration*100,100);this.setScrubberVal(percentComplete)}if(this.replayInProgress){const baseIncrement=100,incrementTime=baseIncrement/this.speed;this.currentTime+=baseIncrement,this.currentEventIndex>=this.totalEvents?this.loop?this.startReplay(!0):(this.stopReplay(),this.updateDisplayText(this.text,this.cursorPosition,[],[])):this.replayTimeout=setTimeout((()=>this.replayLog()),incrementTime)}}else this.updateDisplayText(this.text,this.cursorPosition,[],[])}getLineAndColumn(text,pos){const before=text.substring(0,pos);return{lineIndex:before.split("\n").length-1,col:before.length-before.lastIndexOf("\n")-1}}processKeydownEvent(event,text,cursor,highlights,deletions){const key=event.key,charToInsert=this.applyKey(key);if(this.updateModifierStates(key),("v"===key||"V"===key)&&this.isControlKeyPressed&&this.pastedEvents&&this.currentPasteIndex0&&({text:text,cursor:cursor}=this.handleCharacterInsert(charToInsert,text,cursor,highlights)),{text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}}handlePasteInsert(pastedContent,text,cursor){const insertText=pastedContent||"";if(text=text.substring(0,cursor)+insertText+text.substring(cursor),""!==insertText.trim())for(let i=0;ip.index>=startIndex+numDeleted?{...p,index:p.index-numDeleted}:p.index>=startIndex&&p.indexnull!==p))}updateModifierStates(key){"Control"===key?this.isControlKeyPressed=!0:"Shift"===key?this.isShiftKeyPressed=!0:"v"!==key&&"V"!==key||!this.isControlKeyPressed?["Control","Backspace","Delete","ArrowLeft","ArrowRight"].includes(key)||(this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.isPasteEvent=!1):this.isPasteEvent=!0}isCtrlBackspace(key,cursor){return"Backspace"===key&&this.isControlKeyPressed&&cursor>0}isCtrlDelete(key,cursor,text){return"Delete"===key&&this.isControlKeyPressed&&cursor0}isRegularDelete(key,cursor,text){return"Delete"===key&&!this.isControlKeyPressed&&cursorp.index>=cursor?{...p,index:p.index+1}:p))),""!==charToInsert.trim()&&highlights.push({index:cursor,chars:charToInsert,time:this.currentTime,expiresAt:this.currentTime+1500}),{text:text,cursor:cursor+1}}handleCtrlDelete(text,cursor,deletions){const wordEnd=this.findNextWordBoundary(text,cursor),wordToDelete=text.substring(cursor,wordEnd);for(let i=0;i0){const prevLine=lines[lineIndex-1];cursor=lines.slice(0,lineIndex-1).join("\n").length+1+Math.min(col,prevLine.length)}else cursor=0;return cursor}handleArrowDown(text,cursor){const lines=text.split("\n"),{lineIndex:lineIndex,col:col}=this.getLineAndColumn(text,cursor);if(lineIndex0&&" "===text[wordStart-1];)wordStart--;for(;wordStart>0&&" "!==text[wordStart-1];)wordStart--;const wordToDelete=text.substring(wordStart,cursor);for(let i=0;i=text.length)return cursor;if(" "===text[cursor])for(;cursor=text.length){let lastNonSpace=text.length-1;for(;lastNonSpace>=0&&" "===text[lastNonSpace];)lastNonSpace--;return lastNonSpace+1}let wordEnd=cursor;for(;wordEnd0&&(" "===text[pos]||"\n"===text[pos]);)pos--;for(;pos>0&&" "!==text[pos-1]&&"\n"!==text[pos-1];)pos--;return pos}skipToEnd(){this.replayInProgress&&(this.replayInProgress=!1);let textOutput="";this.logData.forEach((event=>{"keydown"===event.event.toLowerCase()&&(textOutput=this.applyKey(event.key,textOutput))})),this.outputElement.innerHTML=textOutput.slice(0,-1),this.setScrubberVal(100)}skipToTime(percentage){const wasPlaying=this.replayInProgress;this.stopReplay();const targetTime=this.totalDuration*percentage/100;this.currentTime=targetTime,this.currentEventIndex=0,this.text="",this.cursorPosition=0,this.highlightedChars=[],this.deletedChars=[],this.isControlKeyPressed=!1,this.isPasteEvent=!1,this.pastedChars=[],this.currentPasteIndex=0;let text="",cursor=0,highlights=[],deletions=[],pasteIndex=0;for(let i=0;itargetTime){this.currentEventIndex=i;break}void 0===event.rePosition||0!==i&&"mouseDown"!==event.event&&"mouseUp"!==event.event||(cursor=Math.max(0,Math.min(event.rePosition,text.length))),"keydown"===(null===(_event$event3=event.event)||void 0===_event$event3?void 0:_event$event3.toLowerCase())&&(this.currentPasteIndex=pasteIndex,"v"!==event.key&&"V"!==event.key||!this.isControlKeyPressed||pasteIndex++,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processKeydownEvent(event,text,cursor,highlights,deletions))),this.currentEventIndex=i+1}this.currentPasteIndex=pasteIndex,this.text=text,this.cursorPosition=cursor,this.highlightedChars=highlights.filter((h=>!h.expiresAt||h.expiresAt>targetTime)),this.deletedChars=deletions.filter((d=>!d.expiresAt||d.expiresAt>targetTime)),this.updateDisplayText(this.text,this.cursorPosition,this.highlightedChars,this.deletedChars),this.setScrubberVal(percentage),wasPlaying&&(this.replayInProgress=!0,this.replayLog())}updateDisplayText(text,cursorPosition,highlights,deletions){let html="";const highlightMap={},deletionMap={},pastedMap={},currentTime=this.currentTime;highlights.forEach((h=>{let opacity=1;h.expiresAt&&h.expiresAt-currentTime<500&&(opacity=Math.max(0,(h.expiresAt-currentTime)/500)),highlightMap[h.index]={chars:h.chars,opacity:opacity}})),deletions.forEach((d=>{let opacity=.5;d.expiresAt&&d.expiresAt-currentTime<500&&(opacity=Math.max(0,(d.expiresAt-currentTime)/500*.5)),deletionMap[d.index]={chars:d.chars,opacity:opacity}})),this.pastedChars&&this.pastedChars.forEach((p=>{p.indexd.index>=text.length)),textLines=text.split("\n");let currentPosition=0;for(let lineIndex=0;lineIndex');const char=line[i];deletionMap[currentPosition]&&(html+='').concat(deletionMap[currentPosition].chars,""));const isPasted=pastedMap[currentPosition],isHighlighted=highlightMap[currentPosition]&&" "!==char;html+=isPasted&&isHighlighted?'').concat(char,""):isPasted?''.concat(" "===char?" ":this.escapeHtml(char),""):isHighlighted?'').concat(char,""):" "===char?" ":this.escapeHtml(char),currentPosition++}currentPosition===cursorPosition&&(html+=''),lineIndex",currentPosition++)}if(cursorPosition!==text.length||html.endsWith('')||(html+=''),outOfRangeDeletions.length>0){outOfRangeDeletions.sort(((a,b)=>a.index-b.index));const cursorHTML='',cursorPos=html.lastIndexOf(cursorHTML);if(-1!==cursorPos){let deletedWordHTML='';outOfRangeDeletions.forEach((d=>{deletedWordHTML+=d.chars})),deletedWordHTML+="",html=html.substring(0,cursorPos)+deletedWordHTML+html.substring(cursorPos)}}const wasScrolledToBottom=this.outputElement.scrollHeight-this.outputElement.clientHeight<=this.outputElement.scrollTop+1;this.outputElement.innerHTML=html,(wasScrolledToBottom||this.isCursorBelowViewport())&&(this.outputElement.scrollTop=this.outputElement.scrollHeight)}isCursorBelowViewport(){const cursorElement=this.outputElement.querySelector(".tiny_cursive-cursor:last-of-type");if(!cursorElement)return!1;const cursorRect=cursorElement.getBoundingClientRect(),outputRect=this.outputElement.getBoundingClientRect();return cursorRect.bottom>outputRect.bottom}escapeHtml(unsafe){return unsafe.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}applyKey(key){switch(key){case"Enter":return"\n";case"Backspace":case"Delete":case"ControlBackspace":return"";case" ":return" ";default:return["Shift","Ctrl","Alt","ArrowDown","ArrowUp","Control","ArrowRight","ArrowLeft","Meta","CapsLock","Tab","Escape","Delete","PageUp","PageDown","Insert","Home","End","NumLock","AudioVolumeUp","AudioVolumeDown","MediaPlayPause","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","PrintScreen","UnIdentified"].includes(key)?"":key}}},_exports.default})); //# sourceMappingURL=replay.min.js.map \ No newline at end of file diff --git a/amd/build/replay.min.js.map b/amd/build/replay.min.js.map index 1ca8eb5d..bf6f474e 100644 --- a/amd/build/replay.min.js.map +++ b/amd/build/replay.min.js.map @@ -1 +1 @@ -{"version":3,"file":"replay.min.js","sources":["../src/replay.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/replay\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\nimport {call as fetchJson} from 'core/ajax';\nimport templates from 'core/templates';\nimport * as Str from 'core/str';\n\nexport default class Replay {\n constructor(elementId, filePath, speed = 1, loop = false, controllerId) {\n // Initialize core properties\n this.controllerId = controllerId || '';\n this.replayInProgress = false;\n this.speed = parseFloat(speed);\n this.loop = loop;\n this.highlightedChars = [];\n this.deletedChars = [];\n this.cursorPosition = 0;\n this.currentEventIndex = 0;\n this.totalEvents = 0;\n this.currentTime = 0;\n this.totalDuration = 0;\n this.usercomments = [];\n this.pasteTimestamps = [];\n this.isPasteEvent = false;\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.text = '';\n this.pastedEvents = [];\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n\n const element = document.getElementById(elementId);\n if (!element) {\n throw new Error(`Element with id '${elementId}' not found`);\n }\n this.outputElement = element;\n\n // Load JSON data and initialize replay\n this.loadJSON(filePath).then(data => {\n if (data.status) {\n this.processData(data);\n this.totalEvents = this.logData.length;\n this.identifyPasteEvents();\n if (this.controllerId && this.logData) {\n this.constructController(this.controllerId);\n }\n this.startReplay();\n } else {\n this.handleNoSubmission();\n }\n return data;\n }).catch(error => {\n this.handleNoSubmission();\n window.console.error('Error loading JSON file:', error.message);\n });\n if (!localStorage.getItem('nopasteevent') || !localStorage.getItem('pasteEvent')) {\n Str.get_string('nopasteevent', 'tiny_cursive').then(str => {\n localStorage.setItem('nopasteevent', str);\n return str;\n });\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\n localStorage.setItem('pasteEvent', str);\n return str;\n });\n }\n }\n\n // Process JSON data and normalize timestamps\n processData(data) {\n this.logData = JSON.parse(data.data);\n if (data.comments) {\n this.usercomments = Array.isArray(JSON.parse(data.comments)) ? JSON.parse(data.comments) : [];\n }\n if ('data' in this.logData) {\n this.logData = this.logData.data;\n }\n if ('payload' in this.logData) {\n this.logData = this.logData.payload;\n }\n for (let i = 0; i < this.logData.length; i++) {\n const event = this.logData[i];\n if (event.event === 'Paste' && Array.isArray(event.pastedContent)) {\n for (let j = 0; j < event.pastedContent.length; j++) {\n this.pastedEvents.push(event.pastedContent[j]);\n }\n }\n }\n if (this.logData.length > 0 && this.logData[0].unixTimestamp) {\n const startTime = this.logData[0].unixTimestamp;\n this.logData = this.logData.map(event => ({\n ...event,\n normalizedTime: event.unixTimestamp - startTime\n }));\n this.totalDuration = this.logData[this.logData.length - 1].normalizedTime;\n }\n }\n\n async handleNoSubmission() {\n try {\n const [html, str] = await Promise.all([\n templates.render('tiny_cursive/no_submission'),\n Str.get_string('warningpayload', 'tiny_cursive')\n ]);\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = html;\n const newElement = tempDiv.firstChild;\n newElement.textContent = str;\n\n const tinyCursiveElements = document.querySelectorAll('.tiny_cursive');\n tinyCursiveElements.forEach(element => {\n element.innerHTML = '';\n element.appendChild(newElement.cloneNode(true));\n });\n\n return true;\n } catch (error) {\n window.console.error(error);\n return false;\n }\n }\n\n // Stop the replay and update play button icon\n stopReplay() {\n if (this.replayInProgress) {\n clearTimeout(this.replayTimeout);\n this.replayInProgress = false;\n if (this.playButton) {\n const playSvg = document.createElement('img');\n playSvg.src = M.util.image_url('playicon', 'tiny_cursive');\n this.playButton.querySelector('.play-icon').innerHTML = playSvg.outerHTML;\n }\n }\n }\n\n // Build the replay control UI (play button, scrubber, speed controls)\n constructController(controllerId) {\n this.replayInProgress = false;\n this.currentPosition = 0;\n this.speed = 1;\n if (this.replayIntervalId) {\n clearInterval(this.replayIntervalId);\n this.replayIntervalId = null;\n }\n\n const container = document.getElementById(controllerId);\n if (!container) {\n window.console.error('Container not found with ID:', controllerId);\n return;\n }\n\n const controlContainer = container.querySelector('.tiny_cursive_replay_control');\n if (!controlContainer) {\n window.console.error('Replay control container not found in:', controllerId);\n return;\n }\n controlContainer.innerHTML = '';\n\n this.buildControllerUI(controlContainer, container);\n controlContainer.querySelector('.tiny_cursive_loading_spinner')?.remove();\n\n controlContainer.classList.remove('d-none');\n }\n\n buildControllerUI(controlContainer, container) {\n const topRow = document.createElement('div');\n topRow.classList.add('tiny_cursive_top_row');\n\n this.playButton = this.createPlayButton();\n topRow.appendChild(this.playButton);\n\n const scrubberContainer = this.createScrubberContainer();\n topRow.appendChild(scrubberContainer);\n\n this.timeDisplay = this.createTimeDisplay();\n topRow.appendChild(this.timeDisplay);\n\n const bottomRow = document.createElement('div');\n bottomRow.classList.add('tiny_cursive_bottom_row');\n\n const speedContainer = this.createSpeedControls();\n bottomRow.appendChild(speedContainer);\n\n const pasteEventsToggle = this.createPasteEventsToggle(container);\n bottomRow.appendChild(pasteEventsToggle);\n\n controlContainer.appendChild(topRow);\n controlContainer.appendChild(bottomRow);\n container.appendChild(this.pasteEventsPanel);\n }\n\n createPlayButton() {\n const playButton = document.createElement('button');\n playButton.classList.add('tiny_cursive_play_button');\n const playSvg = document.createElement('i');\n playButton.innerHTML = `${playSvg.outerHTML}`;\n playButton.addEventListener('click', () => {\n if (this.replayInProgress) {\n this.stopReplay();\n } else {\n this.startReplay(false);\n }\n document.querySelectorAll('.tiny_cursive-nav-tab .active').forEach(btn => btn.classList.remove('active'));\n document.querySelectorAll('a[id^=\"rep\"]').forEach(btn => btn.classList.add('active'));\n });\n return playButton;\n }\n\n createScrubberContainer() {\n const scrubberContainer = document.createElement('div');\n scrubberContainer.classList.add('tiny_cursive_scrubber_container');\n this.scrubberElement = document.createElement('input');\n this.scrubberElement.classList.add('tiny_cursive_timeline_scrubber', 'timeline-scrubber');\n this.scrubberElement.type = 'range';\n this.scrubberElement.max = '100';\n this.scrubberElement.min = '0';\n this.scrubberElement.value = '0';\n this.scrubberElement.addEventListener('input', () => {\n this.skipToTime(parseInt(this.scrubberElement.value, 10));\n });\n scrubberContainer.appendChild(this.scrubberElement);\n return scrubberContainer;\n }\n\n createTimeDisplay() {\n const timeDisplay = document.createElement('div');\n timeDisplay.classList.add('tiny_cursive_time_display');\n timeDisplay.textContent = '00:00 / 00:00';\n return timeDisplay;\n }\n\n createSpeedControls() {\n const speedContainer = document.createElement('div');\n speedContainer.classList.add('tiny_cursive_speed_controls', 'speed-controls');\n const speedLabel = document.createElement('span');\n speedLabel.classList.add('tiny_cursive_speed_label');\n speedLabel.textContent = 'Speed: ';\n speedContainer.appendChild(speedLabel);\n\n const speedGroup = document.createElement('div');\n speedGroup.classList.add('tiny_cursive_speed_group');\n [1, 1.5, 2, 5, 10].forEach(speed => {\n const speedBtn = document.createElement('button');\n speedBtn.textContent = `${speed}x`;\n speedBtn.classList.add('tiny_cursive_speed_btn', 'speed-btn');\n if (parseFloat(speed) === this.speed) {\n speedBtn.classList.add('active');\n }\n speedBtn.dataset.speed = speed;\n speedBtn.addEventListener('click', () => {\n document.querySelectorAll('.tiny_cursive_speed_btn').forEach(btn => btn.classList.remove('active'));\n speedBtn.classList.add('active');\n this.speed = parseFloat(speedBtn.dataset.speed);\n if (this.replayInProgress) {\n this.stopReplay();\n this.startReplay(false);\n }\n });\n speedGroup.appendChild(speedBtn);\n });\n speedContainer.appendChild(speedGroup);\n return speedContainer;\n }\n\n createPasteEventsToggle(container) {\n const pasteEventsToggle = document.createElement('div');\n pasteEventsToggle.classList.add('tiny_cursive_paste_events_toggle', 'paste-events-toggle');\n\n const pasteEventsIcon = document.createElement('span');\n const pasteIcon = document.createElement('img');\n pasteIcon.src = M.util.image_url('pasteicon', 'tiny_cursive');\n pasteEventsIcon.innerHTML = pasteIcon.outerHTML;\n pasteEventsIcon.classList.add('tiny_cursive_paste_events_icon');\n\n const pasteEventsText = document.createElement('span');\n pasteEventsText.textContent = localStorage.getItem('pasteEvent');\n\n this.pasteEventCount = document.createElement('span');\n this.pasteEventCount.textContent = `(${this.pasteTimestamps.length})`;\n this.pasteEventCount.className = 'paste-event-count';\n this.pasteEventCount.style.marginLeft = '2px';\n\n const chevronIcon = document.createElement('span');\n const chevron = document.createElement('i');\n chevron.className = 'fa fa-chevron-down';\n chevronIcon.innerHTML = chevron.outerHTML;\n chevronIcon.style.marginLeft = '5px';\n chevronIcon.style.transition = 'transform 0.3s ease';\n\n pasteEventsToggle.appendChild(pasteEventsIcon);\n pasteEventsToggle.appendChild(pasteEventsText);\n pasteEventsToggle.appendChild(this.pasteEventCount);\n pasteEventsToggle.appendChild(chevronIcon);\n\n this.pasteEventsPanel = this.createPasteEventsPanel(container);\n pasteEventsToggle.addEventListener('click', () => {\n const isHidden = this.pasteEventsPanel.style.display === 'none';\n this.pasteEventsPanel.style.display = isHidden ? 'block' : 'none';\n chevronIcon.style.transform = isHidden ? 'rotate(180deg)' : 'rotate(0deg)';\n });\n\n return pasteEventsToggle;\n }\n\n createPasteEventsPanel(container) {\n const existingPanel = container.querySelector('.paste-events-panel');\n if (existingPanel) {\n existingPanel.remove();\n }\n const pasteEventsPanel = document.createElement('div');\n pasteEventsPanel.classList.add('tiny_cursive_paste_events_panel', 'paste-events-panel');\n pasteEventsPanel.style.display = 'none';\n this.populatePasteEventsPanel(pasteEventsPanel);\n return pasteEventsPanel;\n }\n\n // Detect Ctrl+V paste events and sync with user comments\n identifyPasteEvents() {\n this.pasteTimestamps = [];\n let controlPressed = false;\n /* eslint-disable no-unused-vars */\n let shiftPressed = false;\n let pasteCount = 0;\n\n for (let i = 0; i < this.logData.length; i++) {\n const event = this.logData[i];\n if (event.event?.toLowerCase() === 'keydown') {\n if (event.key === 'Control') {\n controlPressed = true;\n } else if (event.key === 'Shift') {\n shiftPressed = true;\n } else if ((event.key === 'v' || event.key === 'V') && controlPressed) {\n if (this.pastedEvents[pasteCount]) {\n const timestamp = event.normalizedTime || 0;\n this.pasteTimestamps.push({\n index: pasteCount,\n time: timestamp,\n formattedTime: this.formatTime(timestamp),\n pastedText: this.pastedEvents[pasteCount],\n timestamp\n });\n }\n pasteCount++;\n controlPressed = false;\n shiftPressed = false;\n } else {\n controlPressed = false;\n shiftPressed = false;\n }\n }\n }\n\n if (this.usercomments.length > 0 && this.pasteTimestamps.length === 0) {\n this.usercomments.forEach((comment, i) => {\n this.pasteTimestamps.push({\n index: i,\n time: 0,\n formattedTime: this.formatTime(0),\n pastedText: comment,\n timestamp: 0\n });\n });\n }\n\n while (this.pasteTimestamps.length < this.usercomments.length) {\n const lastIndex = this.pasteTimestamps.length;\n this.pasteTimestamps.push({\n index: lastIndex,\n time: 0,\n formattedTime: this.formatTime(0),\n pastedText: this.usercomments[lastIndex],\n timestamp: 0\n });\n }\n\n if (this.pasteEventsPanel) {\n this.populatePasteEventsPanel(this.pasteEventsPanel);\n }\n }\n\n // Populate the paste events panel with navigation\n populatePasteEventsPanel(panel) {\n panel.innerHTML = '';\n panel.classList.add('tiny_cursive_event_panel');\n\n if (!this.pasteTimestamps.length) {\n const noEventsMessage = document.createElement('div');\n noEventsMessage.className = 'no-paste-events-message p-3';\n noEventsMessage.textContent = localStorage.getItem('nopasteevent');\n panel.appendChild(noEventsMessage);\n return;\n }\n\n const carouselContainer = document.createElement('div');\n carouselContainer.classList.add('tiny_cursive_paste_events_carousel', 'paste-events-carousel');\n\n const navigationRow = document.createElement('div');\n navigationRow.classList.add('paste-events-navigation', 'tiny_cursive_navigation_row');\n\n const counterDisplay = document.createElement('div');\n counterDisplay.classList.add('paste-events-counter', 'tiny_cursive_counter_display');\n counterDisplay.textContent = 'Paste Events';\n\n const navButtons = document.createElement('div');\n navButtons.classList.add('tiny_cursive_nav_buttons');\n const prevButton = document.createElement('button');\n prevButton.classList.add('paste-event-prev-btn', 'tiny_cursive_nav_button');\n prevButton.innerHTML = '';\n\n const nextButton = document.createElement('button');\n nextButton.classList.add('paste-event-next-btn', 'tiny_cursive_nav_button');\n nextButton.innerHTML = '';\n nextButton.disabled = this.pasteTimestamps.length <= 1;\n\n navButtons.appendChild(prevButton);\n navButtons.appendChild(nextButton);\n navigationRow.appendChild(counterDisplay);\n navigationRow.appendChild(navButtons);\n\n const contentContainer = document.createElement('div');\n contentContainer.className = 'paste-events-content tiny_cursive_content_container';\n contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[0]));\n\n carouselContainer.appendChild(navigationRow);\n carouselContainer.appendChild(contentContainer);\n panel.appendChild(carouselContainer);\n\n let currentIndex = 0;\n const updateDisplay = () => {\n contentContainer.innerHTML = '';\n contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[currentIndex]));\n counterDisplay.textContent = 'Paste Events';\n prevButton.disabled = currentIndex === 0;\n prevButton.style.opacity = currentIndex === 0 ? '0.5' : '1';\n nextButton.disabled = currentIndex === this.pasteTimestamps.length - 1;\n nextButton.style.opacity = currentIndex === this.pasteTimestamps.length - 1 ? '0.5' : '1';\n };\n\n prevButton.addEventListener('click', () => {\n if (currentIndex > 0) {\n currentIndex--;\n updateDisplay();\n }\n });\n\n nextButton.addEventListener('click', () => {\n if (currentIndex < this.pasteTimestamps.length - 1) {\n currentIndex++;\n updateDisplay();\n }\n });\n }\n\n createPasteEventDisplay(pasteEvent) {\n const eventRow = document.createElement('div');\n eventRow.className = 'tiny_cursive_event_row';\n\n const headerRow = document.createElement('div');\n headerRow.className = 'tiny_cursive_header_row';\n\n const textContainer = document.createElement('div');\n textContainer.className = 'tiny_cursive_text_container';\n\n const timestampContainer = document.createElement('div');\n timestampContainer.className = 'paste-event-timestamp tiny_cursive_paste_event_timestamp';\n timestampContainer.textContent = pasteEvent.formattedTime;\n\n const pastedTextContainer = document.createElement('div');\n pastedTextContainer.className = 'paste-event-text tiny_cursive_pasted_text_container';\n pastedTextContainer.textContent = pasteEvent.pastedText;\n\n textContainer.appendChild(timestampContainer);\n textContainer.appendChild(pastedTextContainer);\n\n const playButton = document.createElement('button');\n playButton.className = 'paste-event-play-btn tiny_cursive_seekplay_button';\n const playIcon = document.createElement('img');\n playIcon.src = M.util.image_url('seekplayicon', 'tiny_cursive');\n playButton.innerHTML = playIcon.outerHTML;\n playButton.addEventListener('click', () => this.jumpToTimestamp(pasteEvent.timestamp));\n\n headerRow.appendChild(textContainer);\n headerRow.appendChild(playButton);\n eventRow.appendChild(headerRow);\n\n return eventRow;\n }\n\n // Jump to a specific timestamp in the replay\n jumpToTimestamp(timestamp) {\n const percentage = this.totalDuration > 0 ? (timestamp / this.totalDuration) * 100 : 0;\n this.skipToTime(percentage);\n if (!this.replayInProgress) {\n this.startReplay(false);\n }\n }\n\n setScrubberVal(value) {\n if (this.scrubberElement) {\n this.scrubberElement.value = String(value);\n if (this.timeDisplay) {\n const displayTime = Math.min(this.currentTime, this.totalDuration);\n this.timeDisplay.textContent = `${this.formatTime(displayTime)} / ${this.formatTime(this.totalDuration)}`;\n }\n }\n }\n\n loadJSON(filePath) {\n return fetchJson([{\n methodname: 'cursive_get_reply_json',\n args: {filepath: filePath}\n }])[0].done(response => response).fail(error => {\n throw new Error(`Error loading JSON file: ${error.message}`);\n });\n }\n\n formatTime(ms) {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;\n }\n\n // Start or restart the replay\n startReplay(reset = true) {\n if (this.replayInProgress) {\n clearTimeout(this.replayTimeout);\n }\n const atEnd = (this.totalDuration > 0 && this.currentTime >= this.totalDuration) ||\n (this.currentEventIndex >= this.totalEvents);\n if (atEnd && !reset) {\n reset = true;\n }\n this.replayInProgress = true;\n if (reset) {\n this.outputElement.innerHTML = '';\n this.text = '';\n this.cursorPosition = 0;\n this.currentEventIndex = 0;\n this.currentTime = 0;\n this.highlightedChars = [];\n this.deletedChars = [];\n this.isControlKeyPressed = false;\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n }\n if (this.playButton) {\n const pauseSvg = document.createElement('i');\n pauseSvg.className = 'fa fa-pause';\n this.playButton.querySelector('.play-icon').innerHTML = pauseSvg.outerHTML;\n }\n this.replayLog();\n }\n\n // Process events in sequence to simulate typing\n replayLog() {\n if (!this.replayInProgress) {\n this.updateDisplayText(this.text, this.cursorPosition, [], []);\n return;\n }\n\n while (this.currentEventIndex < this.logData.length) {\n const event = this.logData[this.currentEventIndex];\n if (event.normalizedTime && event.normalizedTime > this.currentTime) {\n break;\n }\n\n let text = this.text || '';\n let cursor = this.cursorPosition;\n let updatedHighlights = [...this.highlightedChars];\n let updatedDeleted = [...this.deletedChars];\n\n if (event.rePosition !== undefined && (this.currentEventIndex === 0 ||\n event.event === 'mouseDown' || event.event === 'mouseUp')) {\n cursor = Math.max(0, Math.min(event.rePosition, text.length));\n }\n\n if (event.event?.toLowerCase() === 'keydown') {\n ({text, cursor, updatedHighlights, updatedDeleted} =\n this.processKeydownEvent(event, text, cursor, updatedHighlights, updatedDeleted));\n }\n\n this.text = text;\n this.cursorPosition = cursor;\n this.highlightedChars = updatedHighlights.filter(h => !h.expiresAt || h.expiresAt > this.currentTime);\n this.deletedChars = updatedDeleted.filter(d => !d.expiresAt || d.expiresAt > this.currentTime);\n\n this.currentEventIndex++;\n }\n\n this.updateDisplayText(this.text, this.cursorPosition, this.highlightedChars, this.deletedChars);\n if (this.totalDuration > 0) {\n const percentComplete = Math.min((this.currentTime / this.totalDuration) * 100, 100);\n this.setScrubberVal(percentComplete);\n }\n\n if (this.replayInProgress) {\n const baseIncrement = 100;\n const incrementTime = baseIncrement / this.speed;\n this.currentTime += baseIncrement;\n if (this.currentEventIndex >= this.totalEvents) {\n if (this.loop) {\n this.startReplay(true);\n } else {\n this.stopReplay();\n this.updateDisplayText(this.text, this.cursorPosition, [], []);\n }\n } else {\n this.replayTimeout = setTimeout(() => this.replayLog(), incrementTime);\n }\n }\n }\n\n getLineAndColumn(text, pos) {\n const before = text.substring(0, pos);\n const lineIndex = before.split('\\n').length - 1;\n const col = before.length - before.lastIndexOf('\\n') - 1;\n return {lineIndex, col};\n }\n\n // Handle keydown events (e.g., typing, backspace, Ctrl+V)\n processKeydownEvent(event, text, cursor, highlights, deletions) {\n const key = event.key;\n const charToInsert = this.applyKey(key);\n this.updateModifierStates(key);\n if ((key === 'v'|| key === 'V') && this.isControlKeyPressed) {\n if (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length) {\n const pastedContent = this.pastedEvents[this.currentPasteIndex];\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\n this.currentPasteIndex++;\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isPasteEvent = false;\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n }\n if (this.isCtrlBackspace(key, cursor)) {\n ({text, cursor} = this.handleCtrlBackspace(text, cursor, deletions));\n } else if (this.isCtrlDelete(key, cursor, text)) {\n ({text} = this.handleCtrlDelete(text, cursor, deletions));\n } else if (this.isCtrlArrowMove(key)) {\n cursor = this.handleCtrlArrowMove(key, text, cursor);\n } else if (this.isRegularBackspace(key, cursor)) {\n ({text, cursor} = this.handleBackspace(text, cursor, deletions));\n } else if (this.isRegularDelete(key, cursor, text)) {\n ({text} = this.handleDelete(text, cursor, deletions));\n } else if (this.isArrowUp(key)) {\n cursor = this.handleArrowUp(text, cursor);\n } else if (this.isArrowDown(key)) {\n cursor = this.handleArrowDown(text, cursor);\n } else if (this.isRegularArrowMove(key)) {\n cursor = this.handleArrowMove(key, text, cursor);\n } else if (charToInsert && charToInsert.length > 0) {\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\n }\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n\n // Handle Paste events to highlight pasted text\n handlePasteInsert(pastedContent, text, cursor) {\n const insertText = pastedContent || '';\n text = text.substring(0, cursor) + insertText + text.substring(cursor);\n\n // Mark characters as pasted for bold styling\n if (insertText.trim() !== '') {\n for (let i = 0; i < insertText.length; i++) {\n if (!this.pastedChars) {\n this.pastedChars = [];\n }\n this.pastedChars.push({\n index: cursor + i,\n chars: insertText[i]\n });\n }\n }\n\n return {text, cursor: cursor + insertText.length};\n }\n\n // Adjusts pasted chars indices after deletion to maintain styling for pasted text\n shiftPastedCharsIndices(startIndex, numDeleted) {\n this.pastedChars = this.pastedChars.map(p => {\n if (p.index >= startIndex + numDeleted) {\n return {...p, index: p.index - numDeleted};\n } else if (p.index >= startIndex && p.index < startIndex + numDeleted) {\n // Remove pasted characters that were deleted\n return null;\n }\n return p;\n }).filter(p => p !== null);\n }\n\n // Update state for modifier keys (Control, paste events)\n updateModifierStates(key) {\n if (key === 'Control') {\n this.isControlKeyPressed = true;\n } else if (key === 'Shift') {\n this.isShiftKeyPressed = true;\n } else if ((key === 'v' || key === 'V') && this.isControlKeyPressed) {\n this.isPasteEvent = true;\n } else if (!['Control', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isPasteEvent = false;\n }\n }\n\n isCtrlBackspace(key, cursor) {\n return key === 'Backspace' && this.isControlKeyPressed && cursor > 0;\n }\n\n isCtrlDelete(key, cursor, text) {\n return key === 'Delete' && this.isControlKeyPressed && cursor < text.length;\n }\n\n isCtrlArrowMove(key) {\n return this.isControlKeyPressed && (key === 'ArrowLeft' || key === 'ArrowRight');\n }\n\n isRegularBackspace(key, cursor) {\n return key === 'Backspace' && !this.isPasteEvent && cursor > 0;\n }\n\n isRegularDelete(key, cursor, text) {\n return key === 'Delete' && !this.isControlKeyPressed && cursor < text.length;\n }\n\n isRegularArrowMove(key) {\n return !this.isControlKeyPressed && (key === 'ArrowLeft' || key === 'ArrowRight');\n }\n\n isArrowUp(key) {\n return key === 'ArrowUp';\n }\n\n isArrowDown(key) {\n return key === 'ArrowDown';\n }\n\n handleCtrlArrowMove(key, text, cursor) {\n return key === 'ArrowLeft'\n ? this.findPreviousWordBoundary(text, cursor)\n : this.findNextWordBoundary(text, cursor);\n }\n\n handleBackspace(text, cursor, deletions) {\n deletions.push({\n index: cursor - 1,\n chars: text[cursor - 1],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n this.shiftPastedCharsIndices(cursor - 1, 1);\n return {\n text: text.substring(0, cursor - 1) + text.substring(cursor),\n cursor: cursor - 1\n };\n }\n\n handleDelete(text, cursor, deletions) {\n deletions.push({\n index: cursor,\n chars: text[cursor],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n this.shiftPastedCharsIndices(cursor, 1);\n return {\n text: text.substring(0, cursor) + text.substring(cursor + 1),\n cursor\n };\n }\n\n handleArrowMove(key, text, cursor) {\n return key === 'ArrowLeft'\n ? Math.max(0, cursor - 1)\n : Math.min(text.length, cursor + 1);\n }\n\n handleCharacterInsert(charToInsert, text, cursor, highlights) {\n text = text.substring(0, cursor) + charToInsert + text.substring(cursor);\n // Shift pasted chars indices after the insertion point\n if (this.pastedChars) {\n this.pastedChars = this.pastedChars.map(p => {\n return p.index >= cursor ? {...p, index: p.index + 1} : p;\n });\n }\n if (charToInsert.trim() !== '') {\n highlights.push({\n index: cursor,\n chars: charToInsert,\n time: this.currentTime,\n expiresAt: this.currentTime + 1500\n });\n }\n return {text, cursor: cursor + 1};\n }\n\n handleCtrlDelete(text, cursor, deletions) {\n const wordEnd = this.findNextWordBoundary(text, cursor);\n const wordToDelete = text.substring(cursor, wordEnd);\n for (let i = 0; i < wordToDelete.length; i++) {\n deletions.push({\n index: cursor + i,\n chars: wordToDelete[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n this.shiftPastedCharsIndices(cursor, wordToDelete.length);\n return {\n text: text.substring(0, cursor) + text.substring(wordEnd),\n cursor\n };\n }\n\n handleArrowUp(text, cursor) {\n const lines = text.split('\\n');\n const {lineIndex, col} = this.getLineAndColumn(text, cursor);\n if (lineIndex > 0) {\n const prevLine = lines[lineIndex - 1];\n cursor = lines.slice(0, lineIndex - 1).join('\\n').length + 1 + Math.min(col, prevLine.length);\n } else {\n cursor = 0;\n }\n return cursor;\n }\n\n handleArrowDown(text, cursor) {\n const lines = text.split('\\n');\n const {lineIndex, col} = this.getLineAndColumn(text, cursor);\n if (lineIndex < lines.length - 1) {\n const nextLine = lines[lineIndex + 1];\n cursor = lines.slice(0, lineIndex + 1).join('\\n').length + 1 + Math.min(col, nextLine.length);\n } else {\n cursor = text.length;\n }\n return cursor;\n }\n\n handleCtrlBackspace(text, cursor, deletions) {\n let wordStart = cursor;\n while (wordStart > 0 && text[wordStart - 1] === ' ') {\n wordStart--;\n }\n while (wordStart > 0 && text[wordStart - 1] !== ' ') {\n wordStart--;\n }\n const wordToDelete = text.substring(wordStart, cursor);\n for (let i = 0; i < wordToDelete.length; i++) {\n deletions.push({\n index: wordStart + i,\n chars: wordToDelete[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n this.shiftPastedCharsIndices(wordStart, wordToDelete.length);\n return {text: text.substring(0, wordStart) + text.substring(cursor), cursor: wordStart};\n }\n\n // Finds the index of the next word boundary after the cursor position\n findNextWordBoundary(text, cursor) {\n if (!text || cursor >= text.length) {\n return cursor;\n }\n if (text[cursor] === ' ') {\n while (cursor < text.length && text[cursor] === ' ') {\n cursor++;\n }\n }\n if (cursor >= text.length) {\n let lastNonSpace = text.length - 1;\n while (lastNonSpace >= 0 && text[lastNonSpace] === ' ') {\n lastNonSpace--;\n }\n return lastNonSpace + 1;\n }\n let wordEnd = cursor;\n while (wordEnd < text.length && text[wordEnd] !== ' ') {\n wordEnd++;\n }\n return wordEnd;\n }\n\n // Finds the index of the previous word boundary before the cursor position\n findPreviousWordBoundary(text, cursor) {\n if (cursor <= 0) {\n return 0;\n }\n let pos = cursor - 1;\n while (pos > 0 && (text[pos] === ' ' || text[pos] === '\\n')) {\n pos--;\n }\n while (pos > 0 && text[pos - 1] !== ' ' && text[pos - 1] !== '\\n') {\n pos--;\n }\n\n return pos;\n }\n\n skipToEnd() {\n if (this.replayInProgress) {\n this.replayInProgress = false;\n }\n let textOutput = \"\";\n this.logData.forEach(event => {\n if (event.event.toLowerCase() === 'keydown') {\n textOutput = this.applyKey(event.key, textOutput);\n }\n });\n this.outputElement.innerHTML = textOutput.slice(0, -1);\n this.setScrubberVal(100);\n }\n\n // Used by the scrubber to skip to a certain percentage of data\n skipToTime(percentage) {\n const wasPlaying = this.replayInProgress;\n this.stopReplay();\n\n const targetTime = (this.totalDuration * percentage) / 100;\n this.currentTime = targetTime;\n this.currentEventIndex = 0;\n this.text = '';\n this.cursorPosition = 0;\n this.highlightedChars = [];\n this.deletedChars = [];\n this.isControlKeyPressed = false;\n this.isPasteEvent = false;\n this.pastedChars = []; // Reset pasted characters tracking\n this.currentPasteIndex = 0;\n let text = '';\n let cursor = 0;\n let highlights = [];\n let deletions = [];\n let pasteIndex = 0;\n\n for (let i = 0; i < this.logData.length; i++) {\n const event = this.logData[i];\n if (event.normalizedTime && event.normalizedTime > targetTime) {\n this.currentEventIndex = i;\n break;\n }\n if (event.rePosition !== undefined && (i === 0 || event.event === 'mouseDown' || event.event === 'mouseUp')) {\n cursor = Math.max(0, Math.min(event.rePosition, text.length));\n }\n if (event.event?.toLowerCase() === 'keydown') {\n this.currentPasteIndex = pasteIndex;\n if ((event.key === 'v' || event.key === 'V') && this.isControlKeyPressed) {\n pasteIndex++;\n }\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\n }\n this.currentEventIndex = i + 1;\n }\n\n this.currentPasteIndex = pasteIndex;\n this.text = text;\n this.cursorPosition = cursor;\n this.highlightedChars = highlights.filter(h => !h.expiresAt || h.expiresAt > targetTime);\n this.deletedChars = deletions.filter(d => !d.expiresAt || d.expiresAt > targetTime);\n this.updateDisplayText(this.text, this.cursorPosition, this.highlightedChars, this.deletedChars);\n this.setScrubberVal(percentage);\n\n if (wasPlaying) {\n this.replayInProgress = true;\n this.replayLog();\n }\n }\n\n // Update display with text, cursor, highlights and deletions\n updateDisplayText(text, cursorPosition, highlights, deletions) {\n let html = '';\n const highlightMap = {};\n const deletionMap = {};\n const pastedMap = {};\n const currentTime = this.currentTime;\n\n highlights.forEach(h => {\n let opacity = 1;\n if (h.expiresAt && h.expiresAt - currentTime < 500) {\n opacity = Math.max(0, (h.expiresAt - currentTime) / 500);\n }\n highlightMap[h.index] = {chars: h.chars, opacity};\n });\n\n deletions.forEach(d => {\n let opacity = 0.5;\n if (d.expiresAt && d.expiresAt - currentTime < 500) {\n opacity = Math.max(0, ((d.expiresAt - currentTime) / 500) * 0.5);\n }\n deletionMap[d.index] = {chars: d.chars, opacity};\n });\n\n // Process pasted characters for bold styling\n if (this.pastedChars) {\n this.pastedChars.forEach(p => {\n if (p.index < text.length) {\n pastedMap[p.index] = true;\n }\n });\n }\n\n // Find if we have out-of-bounds deletions (from Control+Backspace)\n const outOfRangeDeletions = deletions.filter(d => d.index >= text.length);\n const textLines = text.split('\\n');\n let currentPosition = 0;\n\n for (let lineIndex = 0; lineIndex < textLines.length; lineIndex++) {\n const line = textLines[lineIndex];\n for (let i = 0; i < line.length; i++) {\n if (currentPosition === cursorPosition) {\n html += '';\n }\n const char = line[i];\n if (deletionMap[currentPosition]) {\n html += `${deletionMap[currentPosition].chars}`;\n }\n const isPasted = pastedMap[currentPosition];\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\n\n if (isPasted && isHighlighted) {\n // Character is both pasted and recently typed (highlighted) - show bold with highlight\n html += `${char}`;\n } else if (isPasted) {\n // Character is pasted - show in bold\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isHighlighted) {\n // Character is recently typed - show with green highlight\n html += `${char}`;\n } else {\n // Regular character\n html += char === ' ' ? ' ' : this.escapeHtml(char);\n }\n currentPosition++;\n }\n if (currentPosition === cursorPosition) {\n html += '';\n }\n if (lineIndex < textLines.length - 1) {\n html += '
';\n currentPosition++;\n }\n }\n\n if (cursorPosition === text.length && !html.endsWith('')) {\n html += '';\n }\n\n if (outOfRangeDeletions.length > 0) {\n outOfRangeDeletions.sort((a, b) => a.index - b.index);\n const cursorHTML = '';\n const cursorPos = html.lastIndexOf(cursorHTML);\n if (cursorPos !== -1) {\n let deletedWordHTML = '';\n outOfRangeDeletions.forEach(d => {\n deletedWordHTML += d.chars;\n });\n deletedWordHTML += '';\n html = html.substring(0, cursorPos) + deletedWordHTML + html.substring(cursorPos);\n }\n }\n\n const wasScrolledToBottom = this.outputElement.scrollHeight -\n this.outputElement.clientHeight <= this.outputElement.scrollTop + 1;\n this.outputElement.innerHTML = html;\n\n if (wasScrolledToBottom || this.isCursorBelowViewport()) {\n this.outputElement.scrollTop = this.outputElement.scrollHeight;\n }\n }\n\n // Check if cursor is below visible viewport\n isCursorBelowViewport() {\n const cursorElement = this.outputElement.querySelector('.tiny_cursive-cursor:last-of-type');\n if (!cursorElement) {\n return false;\n }\n\n const cursorRect = cursorElement.getBoundingClientRect();\n const outputRect = this.outputElement.getBoundingClientRect();\n\n return cursorRect.bottom > outputRect.bottom;\n }\n\n escapeHtml(unsafe) {\n return unsafe\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n\n // Used in various places to add a keydown, backspace, etc. to the output\n applyKey(key) {\n switch (key) {\n case 'Enter':\n return '\\n';\n case 'Backspace':\n case 'Delete':\n case 'ControlBackspace':\n return '';\n case ' ':\n return ' ';\n default:\n return !['Shift', 'Ctrl', 'Alt', 'ArrowDown', 'ArrowUp', 'Control', 'ArrowRight',\n 'ArrowLeft', 'Meta', 'CapsLock', 'Tab', 'Escape', 'Delete', 'PageUp', 'PageDown',\n 'Insert', 'Home', 'End', 'NumLock', 'AudioVolumeUp', 'AudioVolumeDown',\n 'MediaPlayPause', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10',\n 'F11', 'F12', 'PrintScreen', 'UnIdentified'].includes(key) ? key : '';\n }\n }\n}\n"],"names":["constructor","elementId","filePath","speed","loop","controllerId","replayInProgress","parseFloat","highlightedChars","deletedChars","cursorPosition","currentEventIndex","totalEvents","currentTime","totalDuration","usercomments","pasteTimestamps","isPasteEvent","isControlKeyPressed","isShiftKeyPressed","text","pastedEvents","currentPasteIndex","pastedChars","element","document","getElementById","Error","outputElement","loadJSON","then","data","status","processData","this","logData","length","identifyPasteEvents","constructController","startReplay","handleNoSubmission","catch","error","window","console","message","localStorage","getItem","Str","get_string","str","setItem","JSON","parse","comments","Array","isArray","payload","i","event","pastedContent","j","push","unixTimestamp","startTime","map","normalizedTime","html","Promise","all","templates","render","tempDiv","createElement","innerHTML","newElement","firstChild","textContent","querySelectorAll","forEach","appendChild","cloneNode","stopReplay","clearTimeout","replayTimeout","playButton","playSvg","src","M","util","image_url","querySelector","outerHTML","currentPosition","replayIntervalId","clearInterval","container","controlContainer","buildControllerUI","remove","classList","topRow","add","createPlayButton","scrubberContainer","createScrubberContainer","timeDisplay","createTimeDisplay","bottomRow","speedContainer","createSpeedControls","pasteEventsToggle","createPasteEventsToggle","pasteEventsPanel","addEventListener","btn","scrubberElement","type","max","min","value","skipToTime","parseInt","speedLabel","speedGroup","speedBtn","dataset","pasteEventsIcon","pasteIcon","pasteEventsText","pasteEventCount","className","style","marginLeft","chevronIcon","chevron","transition","createPasteEventsPanel","isHidden","display","transform","existingPanel","populatePasteEventsPanel","controlPressed","shiftPressed","pasteCount","toLowerCase","key","timestamp","index","time","formattedTime","formatTime","pastedText","comment","lastIndex","panel","noEventsMessage","carouselContainer","navigationRow","counterDisplay","navButtons","prevButton","nextButton","disabled","contentContainer","createPasteEventDisplay","currentIndex","updateDisplay","opacity","pasteEvent","eventRow","headerRow","textContainer","timestampContainer","pastedTextContainer","playIcon","jumpToTimestamp","percentage","setScrubberVal","String","displayTime","Math","methodname","args","filepath","done","response","fail","ms","seconds","floor","remainingSeconds","toString","padStart","reset","pauseSvg","replayLog","cursor","updatedHighlights","updatedDeleted","undefined","rePosition","processKeydownEvent","filter","h","expiresAt","d","updateDisplayText","percentComplete","baseIncrement","incrementTime","setTimeout","getLineAndColumn","pos","before","substring","lineIndex","split","col","lastIndexOf","highlights","deletions","charToInsert","applyKey","updateModifierStates","handlePasteInsert","isCtrlBackspace","handleCtrlBackspace","isCtrlDelete","handleCtrlDelete","isCtrlArrowMove","handleCtrlArrowMove","isRegularBackspace","handleBackspace","isRegularDelete","handleDelete","isArrowUp","handleArrowUp","isArrowDown","handleArrowDown","isRegularArrowMove","handleArrowMove","handleCharacterInsert","insertText","trim","chars","shiftPastedCharsIndices","startIndex","numDeleted","p","includes","findPreviousWordBoundary","findNextWordBoundary","wordEnd","wordToDelete","lines","prevLine","slice","join","nextLine","wordStart","lastNonSpace","skipToEnd","textOutput","wasPlaying","targetTime","pasteIndex","highlightMap","deletionMap","pastedMap","outOfRangeDeletions","textLines","line","char","isPasted","isHighlighted","escapeHtml","endsWith","sort","a","b","cursorHTML","cursorPos","deletedWordHTML","wasScrolledToBottom","scrollHeight","clientHeight","scrollTop","isCursorBelowViewport","cursorElement","cursorRect","getBoundingClientRect","outputRect","bottom","unsafe","replace"],"mappings":"utCA2BIA,YAAYC,UAAWC,cAAUC,6DAAQ,EAAGC,6DAAcC,yDAEjDA,aAAeA,cAAgB,QAC/BC,kBAAmB,OACnBH,MAAQI,WAAWJ,YACnBC,KAAOA,UACPI,iBAAmB,QACnBC,aAAe,QACfC,eAAiB,OACjBC,kBAAoB,OACpBC,YAAc,OACdC,YAAc,OACdC,cAAgB,OAChBC,aAAe,QACfC,gBAAkB,QAClBC,cAAe,OACfC,qBAAsB,OACtBC,mBAAoB,OACpBC,KAAO,QACPC,aAAe,QACfC,kBAAoB,OACpBC,YAAc,SAEbC,QAAUC,SAASC,eAAezB,eACnCuB,cACK,IAAIG,MAAO,oBAAmB1B,6BAEnC2B,cAAgBJ,aAGhBK,SAAS3B,UAAU4B,MAAKC,OACrBA,KAAKC,aACAC,YAAYF,WACZnB,YAAcsB,KAAKC,QAAQC,YAC3BC,sBACDH,KAAK7B,cAAgB6B,KAAKC,cACrBG,oBAAoBJ,KAAK7B,mBAE7BkC,oBAEAC,qBAEFT,QACRU,OAAMC,aACAF,qBACLG,OAAOC,QAAQF,MAAM,2BAA4BA,MAAMG,YAEtDC,aAAaC,QAAQ,iBAAoBD,aAAaC,QAAQ,gBAC/DC,IAAIC,WAAW,eAAgB,gBAAgBnB,MAAKoB,MAChDJ,aAAaK,QAAQ,eAAgBD,KAC9BA,OAEXF,IAAIC,WAAW,aAAc,gBAAgBnB,MAAKoB,MAC9CJ,aAAaK,QAAQ,aAAcD,KAC5BA,QAMnBjB,YAAYF,WACHI,QAAUiB,KAAKC,MAAMtB,KAAKA,MAC3BA,KAAKuB,gBACAvC,aAAewC,MAAMC,QAAQJ,KAAKC,MAAMtB,KAAKuB,WAAaF,KAAKC,MAAMtB,KAAKuB,UAAY,IAE3F,SAAUpB,KAAKC,eACVA,QAAUD,KAAKC,QAAQJ,MAE5B,YAAaG,KAAKC,eACbA,QAAUD,KAAKC,QAAQsB,aAE3B,IAAIC,EAAI,EAAGA,EAAIxB,KAAKC,QAAQC,OAAQsB,IAAK,OACpCC,MAAQzB,KAAKC,QAAQuB,MACP,UAAhBC,MAAMA,OAAqBJ,MAAMC,QAAQG,MAAMC,mBAC1C,IAAIC,EAAI,EAAGA,EAAIF,MAAMC,cAAcxB,OAAQyB,SACvCxC,aAAayC,KAAKH,MAAMC,cAAcC,OAInD3B,KAAKC,QAAQC,OAAS,GAAKF,KAAKC,QAAQ,GAAG4B,cAAe,OACpDC,UAAY9B,KAAKC,QAAQ,GAAG4B,mBAC7B5B,QAAUD,KAAKC,QAAQ8B,KAAIN,YACzBA,MACHO,eAAgBP,MAAMI,cAAgBC,mBAErClD,cAAgBoB,KAAKC,QAAQD,KAAKC,QAAQC,OAAS,GAAG8B,qDAMpDC,KAAMjB,WAAakB,QAAQC,IAAI,CAClCC,mBAAUC,OAAO,8BACjBvB,IAAIC,WAAW,iBAAkB,kBAE/BuB,QAAU/C,SAASgD,cAAc,OACvCD,QAAQE,UAAYP,WACdQ,WAAaH,QAAQI,WAC3BD,WAAWE,YAAc3B,WAEGzB,SAASqD,iBAAiB,iBAClCC,SAAQvD,UACxBA,QAAQkD,UAAY,GACpBlD,QAAQwD,YAAYL,WAAWM,WAAU,QAGtC,EACT,MAAOvC,cACLC,OAAOC,QAAQF,MAAMA,QACd,GAKfwC,gBACQhD,KAAK5B,mBACL6E,aAAajD,KAAKkD,oBACb9E,kBAAmB,EACpB4B,KAAKmD,YAAY,OACXC,QAAU7D,SAASgD,cAAc,OACvCa,QAAQC,IAAMC,EAAEC,KAAKC,UAAU,WAAY,qBACtCL,WAAWM,cAAc,cAAcjB,UAAYY,QAAQM,WAM5EtD,oBAAoBjC,6CACXC,kBAAmB,OACnBuF,gBAAkB,OAClB1F,MAAQ,EACT+B,KAAK4D,mBACLC,cAAc7D,KAAK4D,uBACdA,iBAAmB,YAGtBE,UAAYvE,SAASC,eAAerB,kBACrC2F,sBACDrD,OAAOC,QAAQF,MAAM,+BAAgCrC,oBAInD4F,iBAAmBD,UAAUL,cAAc,gCAC5CM,kBAILA,iBAAiBvB,UAAY,0DAExBwB,kBAAkBD,iBAAkBD,yCACzCC,iBAAiBN,cAAc,yFAAkCQ,SAEjEF,iBAAiBG,UAAUD,OAAO,WAR9BxD,OAAOC,QAAQF,MAAM,yCAA0CrC,cAWvE6F,kBAAkBD,iBAAkBD,iBAC1BK,OAAS5E,SAASgD,cAAc,OACtC4B,OAAOD,UAAUE,IAAI,6BAEhBjB,WAAanD,KAAKqE,mBACvBF,OAAOrB,YAAY9C,KAAKmD,kBAElBmB,kBAAoBtE,KAAKuE,0BAC/BJ,OAAOrB,YAAYwB,wBAEdE,YAAcxE,KAAKyE,oBACxBN,OAAOrB,YAAY9C,KAAKwE,mBAElBE,UAAYnF,SAASgD,cAAc,OACzCmC,UAAUR,UAAUE,IAAI,iCAElBO,eAAiB3E,KAAK4E,sBAC5BF,UAAU5B,YAAY6B,sBAEhBE,kBAAoB7E,KAAK8E,wBAAwBhB,WACvDY,UAAU5B,YAAY+B,mBAEtBd,iBAAiBjB,YAAYqB,QAC7BJ,iBAAiBjB,YAAY4B,WAC7BZ,UAAUhB,YAAY9C,KAAK+E,kBAG/BV,yBACUlB,WAAa5D,SAASgD,cAAc,UAC1CY,WAAWe,UAAUE,IAAI,kCACnBhB,QAAU7D,SAASgD,cAAc,YACvCY,WAAWX,UAAa,2BAA0BY,QAAQM,mBAC1DP,WAAW6B,iBAAiB,SAAS,KAC7BhF,KAAK5B,sBACA4E,kBAEA3C,aAAY,GAErBd,SAASqD,iBAAiB,iCAAiCC,SAAQoC,KAAOA,IAAIf,UAAUD,OAAO,YAC/F1E,SAASqD,iBAAiB,gBAAgBC,SAAQoC,KAAOA,IAAIf,UAAUE,IAAI,eAExEjB,WAGXoB,gCACUD,kBAAoB/E,SAASgD,cAAc,cACjD+B,kBAAkBJ,UAAUE,IAAI,wCAC3Bc,gBAAkB3F,SAASgD,cAAc,cACzC2C,gBAAgBhB,UAAUE,IAAI,iCAAkC,0BAChEc,gBAAgBC,KAAO,aACvBD,gBAAgBE,IAAM,WACtBF,gBAAgBG,IAAM,SACtBH,gBAAgBI,MAAQ,SACxBJ,gBAAgBF,iBAAiB,SAAS,UACtCO,WAAWC,SAASxF,KAAKkF,gBAAgBI,MAAO,QAEzDhB,kBAAkBxB,YAAY9C,KAAKkF,iBAC5BZ,kBAGXG,0BACUD,YAAcjF,SAASgD,cAAc,cAC3CiC,YAAYN,UAAUE,IAAI,6BAC1BI,YAAY7B,YAAc,gBACnB6B,YAGXI,4BACUD,eAAiBpF,SAASgD,cAAc,OAC9CoC,eAAeT,UAAUE,IAAI,8BAA+B,wBACtDqB,WAAalG,SAASgD,cAAc,QAC1CkD,WAAWvB,UAAUE,IAAI,4BACzBqB,WAAW9C,YAAc,UACzBgC,eAAe7B,YAAY2C,kBAErBC,WAAanG,SAASgD,cAAc,cAC1CmD,WAAWxB,UAAUE,IAAI,6BACxB,EAAG,IAAK,EAAG,EAAG,IAAIvB,SAAQ5E,cACjB0H,SAAWpG,SAASgD,cAAc,UACxCoD,SAAShD,YAAe,GAAE1E,SAC1B0H,SAASzB,UAAUE,IAAI,yBAA0B,aAC7C/F,WAAWJ,SAAW+B,KAAK/B,OAC3B0H,SAASzB,UAAUE,IAAI,UAE3BuB,SAASC,QAAQ3H,MAAQA,MACzB0H,SAASX,iBAAiB,SAAS,KAC/BzF,SAASqD,iBAAiB,2BAA2BC,SAAQoC,KAAOA,IAAIf,UAAUD,OAAO,YACzF0B,SAASzB,UAAUE,IAAI,eAClBnG,MAAQI,WAAWsH,SAASC,QAAQ3H,OACrC+B,KAAK5B,wBACA4E,kBACA3C,aAAY,OAGzBqF,WAAW5C,YAAY6C,aAE3BhB,eAAe7B,YAAY4C,YACpBf,eAGXG,wBAAwBhB,iBACde,kBAAoBtF,SAASgD,cAAc,OACjDsC,kBAAkBX,UAAUE,IAAI,mCAAoC,6BAE9DyB,gBAAkBtG,SAASgD,cAAc,QACzCuD,UAAYvG,SAASgD,cAAc,OACzCuD,UAAUzC,IAAMC,EAAEC,KAAKC,UAAU,YAAa,gBAC9CqC,gBAAgBrD,UAAYsD,UAAUpC,UACtCmC,gBAAgB3B,UAAUE,IAAI,wCAExB2B,gBAAkBxG,SAASgD,cAAc,QAC/CwD,gBAAgBpD,YAAc/B,aAAaC,QAAQ,mBAE9CmF,gBAAkBzG,SAASgD,cAAc,aACzCyD,gBAAgBrD,YAAe,IAAG3C,KAAKlB,gBAAgBoB,eACvD8F,gBAAgBC,UAAY,yBAC5BD,gBAAgBE,MAAMC,WAAa,YAElCC,YAAc7G,SAASgD,cAAc,QACrC8D,QAAU9G,SAASgD,cAAc,YACvC8D,QAAQJ,UAAY,qBACpBG,YAAY5D,UAAY6D,QAAQ3C,UAChC0C,YAAYF,MAAMC,WAAa,MAC/BC,YAAYF,MAAMI,WAAa,sBAE/BzB,kBAAkB/B,YAAY+C,iBAC9BhB,kBAAkB/B,YAAYiD,iBAC9BlB,kBAAkB/B,YAAY9C,KAAKgG,iBACnCnB,kBAAkB/B,YAAYsD,kBAEzBrB,iBAAmB/E,KAAKuG,uBAAuBzC,WACpDe,kBAAkBG,iBAAiB,SAAS,WAClCwB,SAAmD,SAAxCxG,KAAK+E,iBAAiBmB,MAAMO,aACxC1B,iBAAiBmB,MAAMO,QAAUD,SAAW,QAAU,OAC3DJ,YAAYF,MAAMQ,UAAYF,SAAW,iBAAmB,kBAGzD3B,kBAGX0B,uBAAuBzC,iBACb6C,cAAgB7C,UAAUL,cAAc,uBAC1CkD,eACAA,cAAc1C,eAEZc,iBAAmBxF,SAASgD,cAAc,cAChDwC,iBAAiBb,UAAUE,IAAI,kCAAmC,sBAClEW,iBAAiBmB,MAAMO,QAAU,YAC5BG,yBAAyB7B,kBACvBA,iBAIX5E,2BACSrB,gBAAkB,OACnB+H,gBAAiB,EAEjBC,cAAe,EACfC,WAAa,MAEZ,IAAIvF,EAAI,EAAGA,EAAIxB,KAAKC,QAAQC,OAAQsB,IAAK,wBACpCC,MAAQzB,KAAKC,QAAQuB,MACQ,kCAA/BC,MAAMA,kDAAOuF,kBACK,YAAdvF,MAAMwF,IACNJ,gBAAiB,OACd,GAAkB,UAAdpF,MAAMwF,IACbH,cAAe,OACZ,GAAmB,MAAdrF,MAAMwF,KAA6B,MAAdxF,MAAMwF,MAAgBJ,eAenDA,gBAAiB,EACjBC,cAAe,MAhBoD,IAC/D9G,KAAKb,aAAa4H,YAAa,OACzBG,UAAYzF,MAAMO,gBAAkB,OACrClD,gBAAgB8C,KAAK,CACtBuF,MAAOJ,WACPK,KAAMF,UACNG,cAAerH,KAAKsH,WAAWJ,WAC/BK,WAAYvH,KAAKb,aAAa4H,YAC9BG,UAAAA,YAGRH,aACAF,gBAAiB,EACjBC,cAAe,OAQvB9G,KAAKnB,aAAaqB,OAAS,GAAqC,IAAhCF,KAAKlB,gBAAgBoB,aAChDrB,aAAagE,SAAQ,CAAC2E,QAAShG,UAC3B1C,gBAAgB8C,KAAK,CACtBuF,MAAO3F,EACP4F,KAAM,EACNC,cAAerH,KAAKsH,WAAW,GAC/BC,WAAYC,QACZN,UAAW,OAKhBlH,KAAKlB,gBAAgBoB,OAASF,KAAKnB,aAAaqB,QAAQ,OACrDuH,UAAYzH,KAAKlB,gBAAgBoB,YAClCpB,gBAAgB8C,KAAK,CACtBuF,MAAOM,UACPL,KAAM,EACNC,cAAerH,KAAKsH,WAAW,GAC/BC,WAAYvH,KAAKnB,aAAa4I,WAC9BP,UAAW,IAIflH,KAAK+E,uBACA6B,yBAAyB5G,KAAK+E,kBAK3C6B,yBAAyBc,UACrBA,MAAMlF,UAAY,GAClBkF,MAAMxD,UAAUE,IAAI,6BAEfpE,KAAKlB,gBAAgBoB,OAAQ,OACxByH,gBAAkBpI,SAASgD,cAAc,cAC/CoF,gBAAgB1B,UAAY,8BAC5B0B,gBAAgBhF,YAAc/B,aAAaC,QAAQ,qBACnD6G,MAAM5E,YAAY6E,uBAIhBC,kBAAoBrI,SAASgD,cAAc,OACjDqF,kBAAkB1D,UAAUE,IAAI,qCAAsC,+BAEhEyD,cAAgBtI,SAASgD,cAAc,OAC7CsF,cAAc3D,UAAUE,IAAI,0BAA2B,qCAEjD0D,eAAiBvI,SAASgD,cAAc,OAC9CuF,eAAe5D,UAAUE,IAAI,uBAAwB,gCACrD0D,eAAenF,YAAc,qBAEvBoF,WAAaxI,SAASgD,cAAc,OAC1CwF,WAAW7D,UAAUE,IAAI,kCACnB4D,WAAazI,SAASgD,cAAc,UAC1CyF,WAAW9D,UAAUE,IAAI,uBAAwB,2BACjD4D,WAAWxF,UAAY,2CAEjByF,WAAa1I,SAASgD,cAAc,UAC1C0F,WAAW/D,UAAUE,IAAI,uBAAwB,2BACjD6D,WAAWzF,UAAY,sCACvByF,WAAWC,SAAWlI,KAAKlB,gBAAgBoB,QAAU,EAErD6H,WAAWjF,YAAYkF,YACvBD,WAAWjF,YAAYmF,YACvBJ,cAAc/E,YAAYgF,gBAC1BD,cAAc/E,YAAYiF,kBAEpBI,iBAAmB5I,SAASgD,cAAc,OAChD4F,iBAAiBlC,UAAY,sDAC7BkC,iBAAiBrF,YAAY9C,KAAKoI,wBAAwBpI,KAAKlB,gBAAgB,KAE/E8I,kBAAkB9E,YAAY+E,eAC9BD,kBAAkB9E,YAAYqF,kBAC9BT,MAAM5E,YAAY8E,uBAEdS,aAAe,QACbC,cAAgB,KAClBH,iBAAiB3F,UAAY,GAC7B2F,iBAAiBrF,YAAY9C,KAAKoI,wBAAwBpI,KAAKlB,gBAAgBuJ,gBAC/EP,eAAenF,YAAc,eAC7BqF,WAAWE,SAA4B,IAAjBG,aACtBL,WAAW9B,MAAMqC,QAA2B,IAAjBF,aAAqB,MAAQ,IACxDJ,WAAWC,SAAWG,eAAiBrI,KAAKlB,gBAAgBoB,OAAS,EACrE+H,WAAW/B,MAAMqC,QAAUF,eAAiBrI,KAAKlB,gBAAgBoB,OAAS,EAAI,MAAQ,KAG1F8H,WAAWhD,iBAAiB,SAAS,KAC7BqD,aAAe,IACfA,eACAC,oBAIRL,WAAWjD,iBAAiB,SAAS,KAC7BqD,aAAerI,KAAKlB,gBAAgBoB,OAAS,IAC7CmI,eACAC,oBAKZF,wBAAwBI,kBACdC,SAAWlJ,SAASgD,cAAc,OACxCkG,SAASxC,UAAY,+BAEfyC,UAAYnJ,SAASgD,cAAc,OACzCmG,UAAUzC,UAAY,gCAEhB0C,cAAgBpJ,SAASgD,cAAc,OAC7CoG,cAAc1C,UAAY,oCAEpB2C,mBAAqBrJ,SAASgD,cAAc,OAClDqG,mBAAmB3C,UAAY,2DAC/B2C,mBAAmBjG,YAAc6F,WAAWnB,oBAEtCwB,oBAAsBtJ,SAASgD,cAAc,OACnDsG,oBAAoB5C,UAAY,sDAChC4C,oBAAoBlG,YAAc6F,WAAWjB,WAE7CoB,cAAc7F,YAAY8F,oBAC1BD,cAAc7F,YAAY+F,2BAEpB1F,WAAa5D,SAASgD,cAAc,UAC1CY,WAAW8C,UAAY,0DACjB6C,SAAWvJ,SAASgD,cAAc,cACxCuG,SAASzF,IAAMC,EAAEC,KAAKC,UAAU,eAAgB,gBAChDL,WAAWX,UAAYsG,SAASpF,UAChCP,WAAW6B,iBAAiB,SAAS,IAAMhF,KAAK+I,gBAAgBP,WAAWtB,aAE3EwB,UAAU5F,YAAY6F,eACtBD,UAAU5F,YAAYK,YACtBsF,SAAS3F,YAAY4F,WAEdD,SAIXM,gBAAgB7B,iBACN8B,WAAahJ,KAAKpB,cAAgB,EAAKsI,UAAYlH,KAAKpB,cAAiB,IAAM,OAChF2G,WAAWyD,YACXhJ,KAAK5B,uBACDiC,aAAY,GAIzB4I,eAAe3D,UACPtF,KAAKkF,uBACAA,gBAAgBI,MAAQ4D,OAAO5D,OAChCtF,KAAKwE,aAAa,OACZ2E,YAAcC,KAAK/D,IAAIrF,KAAKrB,YAAaqB,KAAKpB,oBAC/C4F,YAAY7B,YAAe,GAAE3C,KAAKsH,WAAW6B,kBAAkBnJ,KAAKsH,WAAWtH,KAAKpB,kBAKrGe,SAAS3B,iBACE,cAAU,CAAC,CACdqL,WAAY,yBACZC,KAAM,CAACC,SAAUvL,aACjB,GAAGwL,MAAKC,UAAYA,WAAUC,MAAKlJ,cAC7B,IAAIf,MAAO,4BAA2Be,MAAMG,cAI1D2G,WAAWqC,UACDC,QAAUR,KAAKS,MAAMF,GAAK,KAE1BG,iBAAmBF,QAAU,SAC3B,GAFQR,KAAKS,MAAMD,QAAU,IAEnBG,WAAWC,SAAS,EAAG,QAAQF,iBAAiBC,WAAWC,SAAS,EAAG,OAI7F3J,kBAAY4J,iEACJjK,KAAK5B,kBACL6E,aAAajD,KAAKkD,mBAEPlD,KAAKpB,cAAgB,GAAKoB,KAAKrB,aAAeqB,KAAKpB,eAC7DoB,KAAKvB,mBAAqBuB,KAAKtB,eACtBuL,QACVA,OAAQ,QAEP7L,kBAAmB,EACpB6L,aACKvK,cAAc8C,UAAY,QAC1BtD,KAAO,QACPV,eAAiB,OACjBC,kBAAoB,OACpBE,YAAc,OACdL,iBAAmB,QACnBC,aAAe,QACfS,qBAAsB,OACtBI,kBAAoB,OACpBC,YAAc,IAEnBW,KAAKmD,WAAY,OACX+G,SAAW3K,SAASgD,cAAc,KACxC2H,SAASjE,UAAY,mBAChB9C,WAAWM,cAAc,cAAcjB,UAAY0H,SAASxG,eAEhEyG,YAITA,eACSnK,KAAK5B,uBAKH4B,KAAKvB,kBAAoBuB,KAAKC,QAAQC,QAAQ,yBAC3CuB,MAAQzB,KAAKC,QAAQD,KAAKvB,sBAC5BgD,MAAMO,gBAAkBP,MAAMO,eAAiBhC,KAAKrB,sBAIpDO,KAAOc,KAAKd,MAAQ,GACpBkL,OAASpK,KAAKxB,eACd6L,kBAAoB,IAAIrK,KAAK1B,kBAC7BgM,eAAiB,IAAItK,KAAKzB,mBAELgM,IAArB9I,MAAM+I,YAAwD,IAA3BxK,KAAKvB,mBACxB,cAAhBgD,MAAMA,OAAyC,YAAhBA,MAAMA,QACrC2I,OAAShB,KAAKhE,IAAI,EAAGgE,KAAK/D,IAAI5D,MAAM+I,WAAYtL,KAAKgB,UAGtB,mCAA/BuB,MAAMA,oDAAOuF,kBACX9H,KAAAA,KAAMkL,OAAAA,OAAQC,kBAAAA,kBAAmBC,eAAAA,gBAC/BtK,KAAKyK,oBAAoBhJ,MAAOvC,KAAMkL,OAAQC,kBAAmBC,sBAGpEpL,KAAOA,UACPV,eAAiB4L,YACjB9L,iBAAmB+L,kBAAkBK,QAAOC,IAAMA,EAAEC,WAAaD,EAAEC,UAAY5K,KAAKrB,mBACpFJ,aAAe+L,eAAeI,QAAOG,IAAMA,EAAED,WAAaC,EAAED,UAAY5K,KAAKrB,mBAE7EF,4BAGJqM,kBAAkB9K,KAAKd,KAAMc,KAAKxB,eAAgBwB,KAAK1B,iBAAkB0B,KAAKzB,cAC/EyB,KAAKpB,cAAgB,EAAG,OAClBmM,gBAAkB3B,KAAK/D,IAAKrF,KAAKrB,YAAcqB,KAAKpB,cAAiB,IAAK,UAC3EqK,eAAe8B,oBAGpB/K,KAAK5B,iBAAkB,OACjB4M,cAAgB,IAChBC,cAAgBD,cAAgBhL,KAAK/B,WACtCU,aAAeqM,cAChBhL,KAAKvB,mBAAqBuB,KAAKtB,YAC3BsB,KAAK9B,UACAmC,aAAY,SAEZ2C,kBACA8H,kBAAkB9K,KAAKd,KAAMc,KAAKxB,eAAgB,GAAI,UAG1D0E,cAAgBgI,YAAW,IAAMlL,KAAKmK,aAAac,0BAnDvDH,kBAAkB9K,KAAKd,KAAMc,KAAKxB,eAAgB,GAAI,IAwDnE2M,iBAAiBjM,KAAMkM,WACbC,OAASnM,KAAKoM,UAAU,EAAGF,WAG1B,CAACG,UAFUF,OAAOG,MAAM,MAAMtL,OAAS,EAE3BuL,IADPJ,OAAOnL,OAASmL,OAAOK,YAAY,MAAQ,GAK3DjB,oBAAoBhJ,MAAOvC,KAAMkL,OAAQuB,WAAYC,iBAC3C3E,IAAMxF,MAAMwF,IACZ4E,aAAe7L,KAAK8L,SAAS7E,aAC9B8E,qBAAqB9E,MACb,MAARA,KAAsB,MAARA,MAAgBjH,KAAKhB,qBAChCgB,KAAKb,cAAgBa,KAAKZ,kBAAoBY,KAAKb,aAAae,OAAQ,OAClEwB,cAAgB1B,KAAKb,aAAaa,KAAKZ,2BAC3CF,KAAAA,KAAMkL,OAAAA,QAAUpK,KAAKgM,kBAAkBtK,cAAexC,KAAMkL,cACzDhL,yBACAJ,qBAAsB,OACtBC,mBAAoB,OACpBF,cAAe,EACb,CACHG,KAAAA,KACAkL,OAAAA,OACAC,kBAAmBsB,WACnBrB,eAAgBsB,kBAIxB5L,KAAKiM,gBAAgBhF,IAAKmD,UACxBlL,KAAAA,KAAMkL,OAAAA,QAAUpK,KAAKkM,oBAAoBhN,KAAMkL,OAAQwB,YAClD5L,KAAKmM,aAAalF,IAAKmD,OAAQlL,QACpCA,KAAAA,MAAQc,KAAKoM,iBAAiBlN,KAAMkL,OAAQwB,YACvC5L,KAAKqM,gBAAgBpF,KAC5BmD,OAASpK,KAAKsM,oBAAoBrF,IAAK/H,KAAMkL,QACtCpK,KAAKuM,mBAAmBtF,IAAKmD,UAClClL,KAAAA,KAAMkL,OAAAA,QAAUpK,KAAKwM,gBAAgBtN,KAAMkL,OAAQwB,YAC9C5L,KAAKyM,gBAAgBxF,IAAKmD,OAAQlL,QACvCA,KAAAA,MAAQc,KAAK0M,aAAaxN,KAAMkL,OAAQwB,YACnC5L,KAAK2M,UAAU1F,KACtBmD,OAASpK,KAAK4M,cAAc1N,KAAMkL,QAC3BpK,KAAK6M,YAAY5F,KACxBmD,OAASpK,KAAK8M,gBAAgB5N,KAAMkL,QAC7BpK,KAAK+M,mBAAmB9F,KAC/BmD,OAASpK,KAAKgN,gBAAgB/F,IAAK/H,KAAMkL,QAClCyB,cAAgBA,aAAa3L,OAAS,KAC3ChB,KAAAA,KAAMkL,OAAAA,QAAUpK,KAAKiN,sBAAsBpB,aAAc3M,KAAMkL,OAAQuB,aAEtE,CACHzM,KAAAA,KACAkL,OAAAA,OACAC,kBAAmBsB,WACnBrB,eAAgBsB,WAKxBI,kBAAkBtK,cAAexC,KAAMkL,cAC7B8C,WAAaxL,eAAiB,MACpCxC,KAAOA,KAAKoM,UAAU,EAAGlB,QAAU8C,WAAahO,KAAKoM,UAAUlB,QAGrC,KAAtB8C,WAAWC,WACN,IAAI3L,EAAI,EAAGA,EAAI0L,WAAWhN,OAAQsB,IAC9BxB,KAAKX,mBACDA,YAAc,SAElBA,YAAYuC,KAAK,CAClBuF,MAAOiD,OAAS5I,EAChB4L,MAAOF,WAAW1L,WAKvB,CAACtC,KAAAA,KAAMkL,OAAQA,OAAS8C,WAAWhN,QAI9CmN,wBAAwBC,WAAYC,iBAC3BlO,YAAcW,KAAKX,YAAY0C,KAAIyL,GAChCA,EAAErG,OAASmG,WAAaC,WACjB,IAAIC,EAAGrG,MAAOqG,EAAErG,MAAQoG,YACxBC,EAAErG,OAASmG,YAAcE,EAAErG,MAAQmG,WAAaC,WAEhD,KAEJC,IACR9C,QAAO8C,GAAW,OAANA,IAInBzB,qBAAqB9E,KACL,YAARA,SACKjI,qBAAsB,EACZ,UAARiI,SACFhI,mBAAoB,EACT,MAARgI,KAAuB,MAARA,MAAgBjH,KAAKhB,oBAEpC,CAAC,UAAW,YAAa,SAAU,YAAa,cAAcyO,SAASxG,YAC1EjI,qBAAsB,OACtBC,mBAAoB,OACpBF,cAAe,QAJfA,cAAe,EAQ5BkN,gBAAgBhF,IAAKmD,cACF,cAARnD,KAAuBjH,KAAKhB,qBAAuBoL,OAAS,EAGvE+B,aAAalF,IAAKmD,OAAQlL,YACP,WAAR+H,KAAoBjH,KAAKhB,qBAAuBoL,OAASlL,KAAKgB,OAGzEmM,gBAAgBpF,YACLjH,KAAKhB,sBAAgC,cAARiI,KAA+B,eAARA,KAG/DsF,mBAAmBtF,IAAKmD,cACL,cAARnD,MAAwBjH,KAAKjB,cAAgBqL,OAAS,EAGjEqC,gBAAgBxF,IAAKmD,OAAQlL,YACV,WAAR+H,MAAqBjH,KAAKhB,qBAAuBoL,OAASlL,KAAKgB,OAG1E6M,mBAAmB9F,YACPjH,KAAKhB,sBAAgC,cAARiI,KAA+B,eAARA,KAGhE0F,UAAU1F,WACS,YAARA,IAGX4F,YAAY5F,WACO,cAARA,IAGXqF,oBAAoBrF,IAAK/H,KAAMkL,cACZ,cAARnD,IACDjH,KAAK0N,yBAAyBxO,KAAMkL,QACpCpK,KAAK2N,qBAAqBzO,KAAMkL,QAG1CoC,gBAAgBtN,KAAMkL,OAAQwB,kBAC1BA,UAAUhK,KAAK,CACXuF,MAAOiD,OAAS,EAChBgD,MAAOlO,KAAKkL,OAAS,GACrBhD,KAAMpH,KAAKrB,YACXiM,UAAW5K,KAAKrB,YAAc,WAE7B0O,wBAAwBjD,OAAS,EAAG,GAClC,CACHlL,KAAMA,KAAKoM,UAAU,EAAGlB,OAAS,GAAKlL,KAAKoM,UAAUlB,QACrDA,OAAQA,OAAS,GAIzBsC,aAAaxN,KAAMkL,OAAQwB,kBACvBA,UAAUhK,KAAK,CACXuF,MAAOiD,OACPgD,MAAOlO,KAAKkL,QACZhD,KAAMpH,KAAKrB,YACXiM,UAAW5K,KAAKrB,YAAc,WAE7B0O,wBAAwBjD,OAAQ,GAC9B,CACHlL,KAAMA,KAAKoM,UAAU,EAAGlB,QAAUlL,KAAKoM,UAAUlB,OAAS,GAC1DA,OAAAA,QAIR4C,gBAAgB/F,IAAK/H,KAAMkL,cACR,cAARnD,IACDmC,KAAKhE,IAAI,EAAGgF,OAAS,GACrBhB,KAAK/D,IAAInG,KAAKgB,OAAQkK,OAAS,GAGzC6C,sBAAsBpB,aAAc3M,KAAMkL,OAAQuB,mBAC9CzM,KAAOA,KAAKoM,UAAU,EAAGlB,QAAUyB,aAAe3M,KAAKoM,UAAUlB,QAE7DpK,KAAKX,mBACAA,YAAcW,KAAKX,YAAY0C,KAAIyL,GAC7BA,EAAErG,OAASiD,OAAS,IAAIoD,EAAGrG,MAAOqG,EAAErG,MAAQ,GAAKqG,KAGpC,KAAxB3B,aAAasB,QACbxB,WAAW/J,KAAK,CACZuF,MAAOiD,OACPgD,MAAOvB,aACPzE,KAAMpH,KAAKrB,YACXiM,UAAW5K,KAAKrB,YAAc,OAG/B,CAACO,KAAAA,KAAMkL,OAAQA,OAAS,GAGnCgC,iBAAiBlN,KAAMkL,OAAQwB,iBACrBgC,QAAU5N,KAAK2N,qBAAqBzO,KAAMkL,QAC1CyD,aAAe3O,KAAKoM,UAAUlB,OAAQwD,aACvC,IAAIpM,EAAI,EAAGA,EAAIqM,aAAa3N,OAAQsB,IACrCoK,UAAUhK,KAAK,CACXuF,MAAOiD,OAAS5I,EAChB4L,MAAOS,aAAarM,GACpB4F,KAAMpH,KAAKrB,YACXiM,UAAW5K,KAAKrB,YAAc,kBAGjC0O,wBAAwBjD,OAAQyD,aAAa3N,QAC3C,CACHhB,KAAMA,KAAKoM,UAAU,EAAGlB,QAAUlL,KAAKoM,UAAUsC,SACjDxD,OAAAA,QAIRwC,cAAc1N,KAAMkL,cACV0D,MAAQ5O,KAAKsM,MAAM,OACnBD,UAACA,UAADE,IAAYA,KAAOzL,KAAKmL,iBAAiBjM,KAAMkL,WACjDmB,UAAY,EAAG,OACTwC,SAAWD,MAAMvC,UAAY,GACnCnB,OAAS0D,MAAME,MAAM,EAAGzC,UAAY,GAAG0C,KAAK,MAAM/N,OAAS,EAAIkJ,KAAK/D,IAAIoG,IAAKsC,SAAS7N,aAEtFkK,OAAS,SAENA,OAGX0C,gBAAgB5N,KAAMkL,cACZ0D,MAAQ5O,KAAKsM,MAAM,OACnBD,UAACA,UAADE,IAAYA,KAAOzL,KAAKmL,iBAAiBjM,KAAMkL,WACjDmB,UAAYuC,MAAM5N,OAAS,EAAG,OACxBgO,SAAWJ,MAAMvC,UAAY,GACnCnB,OAAS0D,MAAME,MAAM,EAAGzC,UAAY,GAAG0C,KAAK,MAAM/N,OAAS,EAAIkJ,KAAK/D,IAAIoG,IAAKyC,SAAShO,aAEtFkK,OAASlL,KAAKgB,cAEXkK,OAGX8B,oBAAoBhN,KAAMkL,OAAQwB,eAC1BuC,UAAY/D,YACT+D,UAAY,GAA6B,MAAxBjP,KAAKiP,UAAY,IACrCA,iBAEGA,UAAY,GAA6B,MAAxBjP,KAAKiP,UAAY,IACrCA,kBAEEN,aAAe3O,KAAKoM,UAAU6C,UAAW/D,YAC1C,IAAI5I,EAAI,EAAGA,EAAIqM,aAAa3N,OAAQsB,IACrCoK,UAAUhK,KAAK,CACXuF,MAAOgH,UAAY3M,EACnB4L,MAAOS,aAAarM,GACpB4F,KAAMpH,KAAKrB,YACXiM,UAAW5K,KAAKrB,YAAc,kBAGjC0O,wBAAwBc,UAAWN,aAAa3N,QAC9C,CAAChB,KAAMA,KAAKoM,UAAU,EAAG6C,WAAajP,KAAKoM,UAAUlB,QAASA,OAAQ+D,WAIjFR,qBAAqBzO,KAAMkL,YAClBlL,MAAQkL,QAAUlL,KAAKgB,cACjBkK,UAEU,MAAjBlL,KAAKkL,aACEA,OAASlL,KAAKgB,QAA2B,MAAjBhB,KAAKkL,SAC/BA,YAGLA,QAAUlL,KAAKgB,OAAQ,KACnBkO,aAAelP,KAAKgB,OAAS,OAC1BkO,cAAgB,GAA4B,MAAvBlP,KAAKkP,eAC5BA,sBAEEA,aAAe,MAEtBR,QAAUxD,YACPwD,QAAU1O,KAAKgB,QAA4B,MAAlBhB,KAAK0O,UAChCA,iBAEEA,QAIXF,yBAAyBxO,KAAMkL,WACvBA,QAAU,SACH,MAEPgB,IAAMhB,OAAS,OACZgB,IAAM,IAAoB,MAAdlM,KAAKkM,MAA8B,OAAdlM,KAAKkM,OACxCA,WAEEA,IAAM,GAAuB,MAAlBlM,KAAKkM,IAAM,IAAgC,OAAlBlM,KAAKkM,IAAM,IACjDA,aAGEA,IAGXiD,YACQrO,KAAK5B,wBACAA,kBAAmB,OAExBkQ,WAAa,QACZrO,QAAQ4C,SAAQpB,QACiB,YAA9BA,MAAMA,MAAMuF,gBACZsH,WAAatO,KAAK8L,SAASrK,MAAMwF,IAAKqH,qBAGzC5O,cAAc8C,UAAY8L,WAAWN,MAAM,GAAI,QAC/C/E,eAAe,KAIxB1D,WAAWyD,kBACDuF,WAAavO,KAAK5B,sBACnB4E,mBAECwL,WAAcxO,KAAKpB,cAAgBoK,WAAc,SAClDrK,YAAc6P,gBACd/P,kBAAoB,OACpBS,KAAO,QACPV,eAAiB,OACjBF,iBAAmB,QACnBC,aAAe,QACfS,qBAAsB,OACtBD,cAAe,OACfM,YAAc,QACdD,kBAAoB,MACrBF,KAAO,GACPkL,OAAS,EACTuB,WAAa,GACbC,UAAY,GACZ6C,WAAa,MAEZ,IAAIjN,EAAI,EAAGA,EAAIxB,KAAKC,QAAQC,OAAQsB,IAAK,yBACpCC,MAAQzB,KAAKC,QAAQuB,MACvBC,MAAMO,gBAAkBP,MAAMO,eAAiBwM,WAAY,MACtD/P,kBAAoB+C,aAGJ+I,IAArB9I,MAAM+I,YAAmC,IAANhJ,GAA2B,cAAhBC,MAAMA,OAAyC,YAAhBA,MAAMA,QACnF2I,OAAShB,KAAKhE,IAAI,EAAGgE,KAAK/D,IAAI5D,MAAM+I,WAAYtL,KAAKgB,UAEtB,mCAA/BuB,MAAMA,oDAAOuF,sBACR5H,kBAAoBqP,WACN,MAAdhN,MAAMwF,KAA6B,MAAdxF,MAAMwF,MAAgBjH,KAAKhB,qBACjDyP,eAEFvP,KAAAA,KAAMkL,OAAAA,OAAQC,kBAAmBsB,WAAYrB,eAAgBsB,WAC3D5L,KAAKyK,oBAAoBhJ,MAAOvC,KAAMkL,OAAQuB,WAAYC,kBAE7DnN,kBAAoB+C,EAAI,OAG5BpC,kBAAoBqP,gBACpBvP,KAAOA,UACPV,eAAiB4L,YACjB9L,iBAAmBqN,WAAWjB,QAAOC,IAAMA,EAAEC,WAAaD,EAAEC,UAAY4D,kBACxEjQ,aAAeqN,UAAUlB,QAAOG,IAAMA,EAAED,WAAaC,EAAED,UAAY4D,kBACnE1D,kBAAkB9K,KAAKd,KAAMc,KAAKxB,eAAgBwB,KAAK1B,iBAAkB0B,KAAKzB,mBAC9E0K,eAAeD,YAEhBuF,kBACKnQ,kBAAmB,OACnB+L,aAKbW,kBAAkB5L,KAAMV,eAAgBmN,WAAYC,eAC5C3J,KAAO,SACLyM,aAAe,GACfC,YAAc,GACdC,UAAY,GACZjQ,YAAcqB,KAAKrB,YAEzBgN,WAAW9I,SAAQ8H,QACXpC,QAAU,EACVoC,EAAEC,WAAaD,EAAEC,UAAYjM,YAAc,MAC3C4J,QAAUa,KAAKhE,IAAI,GAAIuF,EAAEC,UAAYjM,aAAe,MAExD+P,aAAa/D,EAAExD,OAAS,CAACiG,MAAOzC,EAAEyC,MAAO7E,QAAAA,YAG7CqD,UAAU/I,SAAQgI,QACVtC,QAAU,GACVsC,EAAED,WAAaC,EAAED,UAAYjM,YAAc,MAC3C4J,QAAUa,KAAKhE,IAAI,GAAKyF,EAAED,UAAYjM,aAAe,IAAO,KAEhEgQ,YAAY9D,EAAE1D,OAAS,CAACiG,MAAOvC,EAAEuC,MAAO7E,QAAAA,YAIxCvI,KAAKX,kBACAA,YAAYwD,SAAQ2K,IACjBA,EAAErG,MAAQjI,KAAKgB,SACf0O,UAAUpB,EAAErG,QAAS,YAM3B0H,oBAAsBjD,UAAUlB,QAAOG,GAAKA,EAAE1D,OAASjI,KAAKgB,SAC5D4O,UAAY5P,KAAKsM,MAAM,UACzB7H,gBAAkB,MAEjB,IAAI4H,UAAY,EAAGA,UAAYuD,UAAU5O,OAAQqL,YAAa,OACzDwD,KAAOD,UAAUvD,eAClB,IAAI/J,EAAI,EAAGA,EAAIuN,KAAK7O,OAAQsB,IAAK,CAC9BmC,kBAAoBnF,iBACpByD,MAAQ,mDAEN+M,KAAOD,KAAKvN,GACdmN,YAAYhL,mBACZ1B,MAAS,oFACH0M,YAAYhL,iBAAiB4E,aAAaoG,YAAYhL,iBAAiByJ,sBAE3E6B,SAAWL,UAAUjL,iBACrBuL,cAAgBR,aAAa/K,kBAA6B,MAATqL,KAInD/M,MAFAgN,UAAYC,cAEH,iHACHR,aAAa/K,iBAAiB4E,aAAayG,cAC1CC,SAEE,0CAAkD,MAATD,KAAe,IAAMhP,KAAKmP,WAAWH,eAChFE,cAEE,wFACHR,aAAa/K,iBAAiB4E,aAAayG,cAGhC,MAATA,KAAe,IAAMhP,KAAKmP,WAAWH,MAEjDrL,kBAEAA,kBAAoBnF,iBACpByD,MAAQ,6CAERsJ,UAAYuD,UAAU5O,OAAS,IAC/B+B,MAAQ,OACR0B,sBAIJnF,iBAAmBU,KAAKgB,QAAW+B,KAAKmN,SAAS,+CACjDnN,MAAQ,6CAGR4M,oBAAoB3O,OAAS,EAAG,CAChC2O,oBAAoBQ,MAAK,CAACC,EAAGC,IAAMD,EAAEnI,MAAQoI,EAAEpI,cACzCqI,WAAa,4CACbC,UAAYxN,KAAKyJ,YAAY8D,gBAChB,IAAfC,UAAkB,KACdC,gBAAkB,iEACtBb,oBAAoBhM,SAAQgI,IACxB6E,iBAAmB7E,EAAEuC,SAEzBsC,iBAAmB,UACnBzN,KAAOA,KAAKqJ,UAAU,EAAGmE,WAAaC,gBAAkBzN,KAAKqJ,UAAUmE,kBAIzEE,oBAAsB3P,KAAKN,cAAckQ,aAC3C5P,KAAKN,cAAcmQ,cAAgB7P,KAAKN,cAAcoQ,UAAY,OACjEpQ,cAAc8C,UAAYP,MAE3B0N,qBAAuB3P,KAAK+P,gCACvBrQ,cAAcoQ,UAAY9P,KAAKN,cAAckQ,cAK1DG,8BACUC,cAAgBhQ,KAAKN,cAAc+D,cAAc,yCAClDuM,qBACM,QAGLC,WAAaD,cAAcE,wBAC3BC,WAAanQ,KAAKN,cAAcwQ,+BAE/BD,WAAWG,OAASD,WAAWC,OAG1CjB,WAAWkB,eACAA,OACFC,QAAQ,KAAM,SACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,UACdA,QAAQ,KAAM,UAIvBxE,SAAS7E,YACGA,SACC,cACM,SACN,gBACA,aACA,yBACM,OACN,UACO,kBAEA,CAAC,QAAS,OAAQ,MAAO,YAAa,UAAW,UAAW,aAChE,YAAa,OAAQ,WAAY,MAAO,SAAU,SAAU,SAAU,WACtE,SAAU,OAAQ,MAAO,UAAW,gBAAiB,kBACrD,iBAAkB,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,MACxE,MAAO,MAAO,cAAe,gBAAgBwG,SAASxG,KAAa,GAANA"} \ No newline at end of file +{"version":3,"file":"replay.min.js","sources":["../src/replay.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/replay\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\nimport {call as fetchJson} from 'core/ajax';\nimport templates from 'core/templates';\nimport * as Str from 'core/str';\n\nexport default class Replay {\n constructor(elementId, filePath, speed = 1, loop = false, controllerId) {\n // Initialize core properties\n this.controllerId = controllerId || '';\n this.replayInProgress = false;\n this.speed = parseFloat(speed);\n this.loop = loop;\n this.highlightedChars = [];\n this.deletedChars = [];\n this.cursorPosition = 0;\n this.currentEventIndex = 0;\n this.totalEvents = 0;\n this.currentTime = 0;\n this.totalDuration = 0;\n this.usercomments = [];\n this.pasteTimestamps = [];\n this.isPasteEvent = false;\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.text = '';\n this.pastedEvents = [];\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n\n const element = document.getElementById(elementId);\n if (!element) {\n throw new Error(`Element with id '${elementId}' not found`);\n }\n this.outputElement = element;\n\n // Load JSON data and initialize replay\n this.loadJSON(filePath).then(data => {\n if (data.status) {\n this.processData(data);\n this.totalEvents = this.logData.length;\n this.identifyPasteEvents();\n if (this.controllerId && this.logData) {\n this.constructController(this.controllerId);\n }\n this.startReplay();\n } else {\n this.handleNoSubmission();\n }\n return data;\n }).catch(error => {\n this.handleNoSubmission();\n window.console.error('Error loading JSON file:', error.message);\n });\n if (!localStorage.getItem('nopasteevent') || !localStorage.getItem('pasteEvent')) {\n Str.get_string('nopasteevent', 'tiny_cursive').then(str => {\n localStorage.setItem('nopasteevent', str);\n return str;\n });\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\n localStorage.setItem('pasteEvent', str);\n return str;\n });\n }\n }\n\n // Process JSON data and normalize timestamps\n processData(data) {\n this.logData = JSON.parse(data.data);\n if (data.comments) {\n this.usercomments = Array.isArray(JSON.parse(data.comments)) ? JSON.parse(data.comments) : [];\n }\n if ('data' in this.logData) {\n this.logData = this.logData.data;\n }\n if ('payload' in this.logData) {\n this.logData = this.logData.payload;\n }\n for (let i = 0; i < this.logData.length; i++) {\n const event = this.logData[i];\n if (event.event === 'Paste' && Array.isArray(event.pastedContent)) {\n for (let j = 0; j < event.pastedContent.length; j++) {\n this.pastedEvents.push(event.pastedContent[j]);\n }\n }\n }\n if (this.logData.length > 0 && this.logData[0].unixTimestamp) {\n const startTime = this.logData[0].unixTimestamp;\n this.logData = this.logData.map(event => ({\n ...event,\n normalizedTime: event.unixTimestamp - startTime\n }));\n this.totalDuration = this.logData[this.logData.length - 1].normalizedTime;\n }\n }\n\n async handleNoSubmission() {\n try {\n const [html, str] = await Promise.all([\n templates.render('tiny_cursive/no_submission'),\n Str.get_string('warningpayload', 'tiny_cursive')\n ]);\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = html;\n const newElement = tempDiv.firstChild;\n newElement.textContent = str;\n\n const tinyCursiveElements = document.querySelectorAll('.tiny_cursive');\n tinyCursiveElements.forEach(element => {\n element.innerHTML = '';\n element.appendChild(newElement.cloneNode(true));\n });\n\n return true;\n } catch (error) {\n window.console.error(error);\n return false;\n }\n }\n\n // Stop the replay and update play button icon\n stopReplay() {\n if (this.replayInProgress) {\n clearTimeout(this.replayTimeout);\n this.replayInProgress = false;\n if (this.playButton) {\n const playSvg = document.createElement('img');\n playSvg.src = M.util.image_url('playicon', 'tiny_cursive');\n this.playButton.querySelector('.play-icon').innerHTML = playSvg.outerHTML;\n }\n }\n }\n\n // Build the replay control UI (play button, scrubber, speed controls)\n constructController(controllerId) {\n this.replayInProgress = false;\n this.currentPosition = 0;\n this.speed = 1;\n if (this.replayIntervalId) {\n clearInterval(this.replayIntervalId);\n this.replayIntervalId = null;\n }\n\n const container = document.getElementById(controllerId);\n if (!container) {\n window.console.error('Container not found with ID:', controllerId);\n return;\n }\n\n const controlContainer = container.querySelector('.tiny_cursive_replay_control');\n if (!controlContainer) {\n window.console.error('Replay control container not found in:', controllerId);\n return;\n }\n controlContainer.innerHTML = '';\n\n this.buildControllerUI(controlContainer, container);\n controlContainer.querySelector('.tiny_cursive_loading_spinner')?.remove();\n\n controlContainer.classList.remove('d-none');\n }\n\n buildControllerUI(controlContainer, container) {\n const topRow = document.createElement('div');\n topRow.classList.add('tiny_cursive_top_row');\n\n this.playButton = this.createPlayButton();\n topRow.appendChild(this.playButton);\n\n const scrubberContainer = this.createScrubberContainer();\n topRow.appendChild(scrubberContainer);\n\n this.timeDisplay = this.createTimeDisplay();\n topRow.appendChild(this.timeDisplay);\n\n const bottomRow = document.createElement('div');\n bottomRow.classList.add('tiny_cursive_bottom_row');\n\n const speedContainer = this.createSpeedControls();\n bottomRow.appendChild(speedContainer);\n\n const pasteEventsToggle = this.createPasteEventsToggle(container);\n bottomRow.appendChild(pasteEventsToggle);\n\n controlContainer.appendChild(topRow);\n controlContainer.appendChild(bottomRow);\n container.appendChild(this.pasteEventsPanel);\n }\n\n createPlayButton() {\n const playButton = document.createElement('button');\n playButton.classList.add('tiny_cursive_play_button');\n const playSvg = document.createElement('i');\n playButton.innerHTML = `${playSvg.outerHTML}`;\n playButton.addEventListener('click', () => {\n if (this.replayInProgress) {\n this.stopReplay();\n } else {\n this.startReplay(false);\n }\n document.querySelectorAll('.tiny_cursive-nav-tab .active').forEach(btn => btn.classList.remove('active'));\n document.querySelectorAll('a[id^=\"rep\"]').forEach(btn => btn.classList.add('active'));\n });\n return playButton;\n }\n\n createScrubberContainer() {\n const scrubberContainer = document.createElement('div');\n scrubberContainer.classList.add('tiny_cursive_scrubber_container');\n this.scrubberElement = document.createElement('input');\n this.scrubberElement.classList.add('tiny_cursive_timeline_scrubber', 'timeline-scrubber');\n this.scrubberElement.type = 'range';\n this.scrubberElement.max = '100';\n this.scrubberElement.min = '0';\n this.scrubberElement.value = '0';\n this.scrubberElement.addEventListener('input', () => {\n this.skipToTime(parseInt(this.scrubberElement.value, 10));\n });\n scrubberContainer.appendChild(this.scrubberElement);\n return scrubberContainer;\n }\n\n createTimeDisplay() {\n const timeDisplay = document.createElement('div');\n timeDisplay.classList.add('tiny_cursive_time_display');\n timeDisplay.textContent = '00:00 / 00:00';\n return timeDisplay;\n }\n\n createSpeedControls() {\n const speedContainer = document.createElement('div');\n speedContainer.classList.add('tiny_cursive_speed_controls', 'speed-controls');\n const speedLabel = document.createElement('span');\n speedLabel.classList.add('tiny_cursive_speed_label');\n speedLabel.textContent = 'Speed: ';\n speedContainer.appendChild(speedLabel);\n\n const speedGroup = document.createElement('div');\n speedGroup.classList.add('tiny_cursive_speed_group');\n [1, 1.5, 2, 5, 10].forEach(speed => {\n const speedBtn = document.createElement('button');\n speedBtn.textContent = `${speed}x`;\n speedBtn.classList.add('tiny_cursive_speed_btn', 'speed-btn');\n if (parseFloat(speed) === this.speed) {\n speedBtn.classList.add('active');\n }\n speedBtn.dataset.speed = speed;\n speedBtn.addEventListener('click', () => {\n document.querySelectorAll('.tiny_cursive_speed_btn').forEach(btn => btn.classList.remove('active'));\n speedBtn.classList.add('active');\n this.speed = parseFloat(speedBtn.dataset.speed);\n if (this.replayInProgress) {\n this.stopReplay();\n this.startReplay(false);\n }\n });\n speedGroup.appendChild(speedBtn);\n });\n speedContainer.appendChild(speedGroup);\n return speedContainer;\n }\n\n createPasteEventsToggle(container) {\n const pasteEventsToggle = document.createElement('div');\n pasteEventsToggle.classList.add('tiny_cursive_paste_events_toggle', 'paste-events-toggle');\n\n const pasteEventsIcon = document.createElement('span');\n const pasteIcon = document.createElement('img');\n pasteIcon.src = M.util.image_url('pasteicon', 'tiny_cursive');\n pasteEventsIcon.innerHTML = pasteIcon.outerHTML;\n pasteEventsIcon.classList.add('tiny_cursive_paste_events_icon');\n\n const pasteEventsText = document.createElement('span');\n pasteEventsText.textContent = localStorage.getItem('pasteEvent');\n\n this.pasteEventCount = document.createElement('span');\n this.pasteEventCount.textContent = `(${this.pasteTimestamps.length})`;\n this.pasteEventCount.className = 'paste-event-count';\n this.pasteEventCount.style.marginLeft = '2px';\n\n const chevronIcon = document.createElement('span');\n const chevron = document.createElement('i');\n chevron.className = 'fa fa-chevron-down';\n chevronIcon.innerHTML = chevron.outerHTML;\n chevronIcon.style.marginLeft = '5px';\n chevronIcon.style.transition = 'transform 0.3s ease';\n\n pasteEventsToggle.appendChild(pasteEventsIcon);\n pasteEventsToggle.appendChild(pasteEventsText);\n pasteEventsToggle.appendChild(this.pasteEventCount);\n pasteEventsToggle.appendChild(chevronIcon);\n\n this.pasteEventsPanel = this.createPasteEventsPanel(container);\n pasteEventsToggle.addEventListener('click', () => {\n const isHidden = this.pasteEventsPanel.style.display === 'none';\n this.pasteEventsPanel.style.display = isHidden ? 'block' : 'none';\n chevronIcon.style.transform = isHidden ? 'rotate(180deg)' : 'rotate(0deg)';\n });\n\n return pasteEventsToggle;\n }\n\n createPasteEventsPanel(container) {\n const existingPanel = container.querySelector('.paste-events-panel');\n if (existingPanel) {\n existingPanel.remove();\n }\n const pasteEventsPanel = document.createElement('div');\n pasteEventsPanel.classList.add('tiny_cursive_paste_events_panel', 'paste-events-panel');\n pasteEventsPanel.style.display = 'none';\n this.populatePasteEventsPanel(pasteEventsPanel);\n return pasteEventsPanel;\n }\n\n // Detect Ctrl+V paste events and sync with user comments\n identifyPasteEvents() {\n this.pasteTimestamps = [];\n let controlPressed = false;\n /* eslint-disable no-unused-vars */\n let shiftPressed = false;\n let pasteCount = 0;\n\n for (let i = 0; i < this.logData.length; i++) {\n const event = this.logData[i];\n if (event.event?.toLowerCase() === 'keydown') {\n if (event.key === 'Control') {\n controlPressed = true;\n } else if (event.key === 'Shift') {\n shiftPressed = true;\n } else if ((event.key === 'v' || event.key === 'V') && controlPressed) {\n if (this.pastedEvents[pasteCount]) {\n const timestamp = event.normalizedTime || 0;\n this.pasteTimestamps.push({\n index: pasteCount,\n time: timestamp,\n formattedTime: this.formatTime(timestamp),\n pastedText: this.pastedEvents[pasteCount],\n timestamp\n });\n }\n pasteCount++;\n controlPressed = false;\n shiftPressed = false;\n } else {\n controlPressed = false;\n shiftPressed = false;\n }\n }\n }\n\n if (this.usercomments.length > 0 && this.pasteTimestamps.length === 0) {\n this.usercomments.forEach((comment, i) => {\n this.pasteTimestamps.push({\n index: i,\n time: 0,\n formattedTime: this.formatTime(0),\n pastedText: comment,\n timestamp: 0\n });\n });\n }\n\n while (this.pasteTimestamps.length < this.usercomments.length) {\n const lastIndex = this.pasteTimestamps.length;\n this.pasteTimestamps.push({\n index: lastIndex,\n time: 0,\n formattedTime: this.formatTime(0),\n pastedText: this.usercomments[lastIndex],\n timestamp: 0\n });\n }\n\n if (this.pasteEventsPanel) {\n this.populatePasteEventsPanel(this.pasteEventsPanel);\n }\n }\n\n // Populate the paste events panel with navigation\n populatePasteEventsPanel(panel) {\n panel.innerHTML = '';\n panel.classList.add('tiny_cursive_event_panel');\n\n if (!this.pasteTimestamps.length) {\n const noEventsMessage = document.createElement('div');\n noEventsMessage.className = 'no-paste-events-message p-3';\n noEventsMessage.textContent = localStorage.getItem('nopasteevent');\n panel.appendChild(noEventsMessage);\n return;\n }\n\n const carouselContainer = document.createElement('div');\n carouselContainer.classList.add('tiny_cursive_paste_events_carousel', 'paste-events-carousel');\n\n const navigationRow = document.createElement('div');\n navigationRow.classList.add('paste-events-navigation', 'tiny_cursive_navigation_row');\n\n const counterDisplay = document.createElement('div');\n counterDisplay.classList.add('paste-events-counter', 'tiny_cursive_counter_display');\n counterDisplay.textContent = 'Paste Events';\n\n const navButtons = document.createElement('div');\n navButtons.classList.add('tiny_cursive_nav_buttons');\n const prevButton = document.createElement('button');\n prevButton.classList.add('paste-event-prev-btn', 'tiny_cursive_nav_button');\n prevButton.innerHTML = '';\n\n const nextButton = document.createElement('button');\n nextButton.classList.add('paste-event-next-btn', 'tiny_cursive_nav_button');\n nextButton.innerHTML = '';\n nextButton.disabled = this.pasteTimestamps.length <= 1;\n\n navButtons.appendChild(prevButton);\n navButtons.appendChild(nextButton);\n navigationRow.appendChild(counterDisplay);\n navigationRow.appendChild(navButtons);\n\n const contentContainer = document.createElement('div');\n contentContainer.className = 'paste-events-content tiny_cursive_content_container';\n contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[0]));\n\n carouselContainer.appendChild(navigationRow);\n carouselContainer.appendChild(contentContainer);\n panel.appendChild(carouselContainer);\n\n let currentIndex = 0;\n const updateDisplay = () => {\n contentContainer.innerHTML = '';\n contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[currentIndex]));\n counterDisplay.textContent = 'Paste Events';\n prevButton.disabled = currentIndex === 0;\n prevButton.style.opacity = currentIndex === 0 ? '0.5' : '1';\n nextButton.disabled = currentIndex === this.pasteTimestamps.length - 1;\n nextButton.style.opacity = currentIndex === this.pasteTimestamps.length - 1 ? '0.5' : '1';\n };\n\n prevButton.addEventListener('click', () => {\n if (currentIndex > 0) {\n currentIndex--;\n updateDisplay();\n }\n });\n\n nextButton.addEventListener('click', () => {\n if (currentIndex < this.pasteTimestamps.length - 1) {\n currentIndex++;\n updateDisplay();\n }\n });\n }\n\n createPasteEventDisplay(pasteEvent) {\n const eventRow = document.createElement('div');\n eventRow.className = 'tiny_cursive_event_row';\n\n const headerRow = document.createElement('div');\n headerRow.className = 'tiny_cursive_header_row';\n\n const textContainer = document.createElement('div');\n textContainer.className = 'tiny_cursive_text_container';\n\n const timestampContainer = document.createElement('div');\n timestampContainer.className = 'paste-event-timestamp tiny_cursive_paste_event_timestamp';\n timestampContainer.textContent = pasteEvent.formattedTime;\n\n const pastedTextContainer = document.createElement('div');\n pastedTextContainer.className = 'paste-event-text tiny_cursive_pasted_text_container';\n pastedTextContainer.textContent = pasteEvent.pastedText;\n\n textContainer.appendChild(timestampContainer);\n textContainer.appendChild(pastedTextContainer);\n\n const playButton = document.createElement('button');\n playButton.className = 'paste-event-play-btn tiny_cursive_seekplay_button';\n const playIcon = document.createElement('img');\n playIcon.src = M.util.image_url('seekplayicon', 'tiny_cursive');\n playButton.innerHTML = playIcon.outerHTML;\n playButton.addEventListener('click', () => this.jumpToTimestamp(pasteEvent.timestamp));\n\n headerRow.appendChild(textContainer);\n headerRow.appendChild(playButton);\n eventRow.appendChild(headerRow);\n\n return eventRow;\n }\n\n // Jump to a specific timestamp in the replay\n jumpToTimestamp(timestamp) {\n const percentage = this.totalDuration > 0 ? (timestamp / this.totalDuration) * 100 : 0;\n this.skipToTime(percentage);\n if (!this.replayInProgress) {\n this.startReplay(false);\n }\n }\n\n setScrubberVal(value) {\n if (this.scrubberElement) {\n this.scrubberElement.value = String(value);\n if (this.timeDisplay) {\n const displayTime = Math.min(this.currentTime, this.totalDuration);\n this.timeDisplay.textContent = `${this.formatTime(displayTime)} / ${this.formatTime(this.totalDuration)}`;\n }\n }\n }\n\n loadJSON(filePath) {\n return fetchJson([{\n methodname: 'cursive_get_reply_json',\n args: {filepath: filePath}\n }])[0].done(response => response).fail(error => {\n throw new Error(`Error loading JSON file: ${error.message}`);\n });\n }\n\n formatTime(ms) {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;\n }\n\n // Start or restart the replay\n startReplay(reset = true) {\n if (this.replayInProgress) {\n clearTimeout(this.replayTimeout);\n }\n const atEnd = (this.totalDuration > 0 && this.currentTime >= this.totalDuration) ||\n (this.currentEventIndex >= this.totalEvents);\n if (atEnd && !reset) {\n reset = true;\n }\n this.replayInProgress = true;\n if (reset) {\n this.outputElement.innerHTML = '';\n this.text = '';\n this.cursorPosition = 0;\n this.currentEventIndex = 0;\n this.currentTime = 0;\n this.highlightedChars = [];\n this.deletedChars = [];\n this.isControlKeyPressed = false;\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n }\n if (this.playButton) {\n const pauseSvg = document.createElement('i');\n pauseSvg.className = 'fa fa-pause';\n this.playButton.querySelector('.play-icon').innerHTML = pauseSvg.outerHTML;\n }\n this.replayLog();\n }\n\n // Process events in sequence to simulate typing\n replayLog() {\n if (!this.replayInProgress) {\n this.updateDisplayText(this.text, this.cursorPosition, [], []);\n return;\n }\n\n while (this.currentEventIndex < this.logData.length) {\n const event = this.logData[this.currentEventIndex];\n if (event.normalizedTime && event.normalizedTime > this.currentTime) {\n break;\n }\n\n let text = this.text || '';\n let cursor = this.cursorPosition;\n let updatedHighlights = [...this.highlightedChars];\n let updatedDeleted = [...this.deletedChars];\n\n if (event.rePosition !== undefined && (this.currentEventIndex === 0 ||\n event.event === 'mouseDown' || event.event === 'mouseUp')) {\n cursor = Math.max(0, Math.min(event.rePosition, text.length));\n }\n\n if (event.event?.toLowerCase() === 'keydown') {\n ({text, cursor, updatedHighlights, updatedDeleted} =\n this.processKeydownEvent(event, text, cursor, updatedHighlights, updatedDeleted));\n }\n\n this.text = text;\n this.cursorPosition = cursor;\n this.highlightedChars = updatedHighlights.filter(h => !h.expiresAt || h.expiresAt > this.currentTime);\n this.deletedChars = updatedDeleted.filter(d => !d.expiresAt || d.expiresAt > this.currentTime);\n\n this.currentEventIndex++;\n }\n\n this.updateDisplayText(this.text, this.cursorPosition, this.highlightedChars, this.deletedChars);\n if (this.totalDuration > 0) {\n const percentComplete = Math.min((this.currentTime / this.totalDuration) * 100, 100);\n this.setScrubberVal(percentComplete);\n }\n\n if (this.replayInProgress) {\n const baseIncrement = 100;\n const incrementTime = baseIncrement / this.speed;\n this.currentTime += baseIncrement;\n if (this.currentEventIndex >= this.totalEvents) {\n if (this.loop) {\n this.startReplay(true);\n } else {\n this.stopReplay();\n this.updateDisplayText(this.text, this.cursorPosition, [], []);\n }\n } else {\n this.replayTimeout = setTimeout(() => this.replayLog(), incrementTime);\n }\n }\n }\n\n getLineAndColumn(text, pos) {\n const before = text.substring(0, pos);\n const lineIndex = before.split('\\n').length - 1;\n const col = before.length - before.lastIndexOf('\\n') - 1;\n return {lineIndex, col};\n }\n\n // Handle keydown events (e.g., typing, backspace, Ctrl+V)\n processKeydownEvent(event, text, cursor, highlights, deletions) {\n const key = event.key;\n const charToInsert = this.applyKey(key);\n this.updateModifierStates(key);\n if ((key === 'v'|| key === 'V') && this.isControlKeyPressed) {\n if (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length) {\n const pastedContent = this.pastedEvents[this.currentPasteIndex];\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\n this.currentPasteIndex++;\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isPasteEvent = false;\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n }\n if (this.isCtrlBackspace(key, cursor)) {\n ({text, cursor} = this.handleCtrlBackspace(text, cursor, deletions));\n } else if (this.isCtrlDelete(key, cursor, text)) {\n ({text} = this.handleCtrlDelete(text, cursor, deletions));\n } else if (this.isCtrlArrowMove(key)) {\n cursor = this.handleCtrlArrowMove(key, text, cursor);\n } else if (this.isRegularBackspace(key, cursor)) {\n ({text, cursor} = this.handleBackspace(text, cursor, deletions));\n } else if (this.isRegularDelete(key, cursor, text)) {\n ({text} = this.handleDelete(text, cursor, deletions));\n } else if (this.isArrowUp(key)) {\n cursor = this.handleArrowUp(text, cursor);\n } else if (this.isArrowDown(key)) {\n cursor = this.handleArrowDown(text, cursor);\n } else if (this.isRegularArrowMove(key)) {\n cursor = this.handleArrowMove(key, text, cursor);\n } else if (charToInsert && charToInsert.length > 0) {\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\n }\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n\n // Handle Paste events to highlight pasted text\n handlePasteInsert(pastedContent, text, cursor) {\n const insertText = pastedContent || '';\n text = text.substring(0, cursor) + insertText + text.substring(cursor);\n\n // Mark characters as pasted for bold styling\n if (insertText.trim() !== '') {\n for (let i = 0; i < insertText.length; i++) {\n if (!this.pastedChars) {\n this.pastedChars = [];\n }\n this.pastedChars.push({\n index: cursor + i,\n chars: insertText[i]\n });\n }\n }\n\n return {text, cursor: cursor + insertText.length};\n }\n\n // Adjusts pasted chars indices after deletion to maintain styling for pasted text\n shiftPastedCharsIndices(startIndex, numDeleted) {\n this.pastedChars = this.pastedChars.map(p => {\n if (p.index >= startIndex + numDeleted) {\n return {...p, index: p.index - numDeleted};\n } else if (p.index >= startIndex && p.index < startIndex + numDeleted) {\n // Remove pasted characters that were deleted\n return null;\n }\n return p;\n }).filter(p => p !== null);\n }\n\n // Update state for modifier keys (Control, paste events)\n updateModifierStates(key) {\n if (key === 'Control') {\n this.isControlKeyPressed = true;\n } else if (key === 'Shift') {\n this.isShiftKeyPressed = true;\n } else if ((key === 'v' || key === 'V') && this.isControlKeyPressed) {\n this.isPasteEvent = true;\n } else if (!['Control', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isPasteEvent = false;\n }\n }\n\n isCtrlBackspace(key, cursor) {\n return key === 'Backspace' && this.isControlKeyPressed && cursor > 0;\n }\n\n isCtrlDelete(key, cursor, text) {\n return key === 'Delete' && this.isControlKeyPressed && cursor < text.length;\n }\n\n isCtrlArrowMove(key) {\n return this.isControlKeyPressed && (key === 'ArrowLeft' || key === 'ArrowRight');\n }\n\n isRegularBackspace(key, cursor) {\n return key === 'Backspace' && !this.isPasteEvent && cursor > 0;\n }\n\n isRegularDelete(key, cursor, text) {\n return key === 'Delete' && !this.isControlKeyPressed && cursor < text.length;\n }\n\n isRegularArrowMove(key) {\n return !this.isControlKeyPressed && (key === 'ArrowLeft' || key === 'ArrowRight');\n }\n\n isArrowUp(key) {\n return key === 'ArrowUp';\n }\n\n isArrowDown(key) {\n return key === 'ArrowDown';\n }\n\n handleCtrlArrowMove(key, text, cursor) {\n return key === 'ArrowLeft'\n ? this.findPreviousWordBoundary(text, cursor)\n : this.findNextWordBoundary(text, cursor);\n }\n\n handleBackspace(text, cursor, deletions) {\n deletions.push({\n index: cursor - 1,\n chars: text[cursor - 1],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n this.shiftPastedCharsIndices(cursor - 1, 1);\n return {\n text: text.substring(0, cursor - 1) + text.substring(cursor),\n cursor: cursor - 1\n };\n }\n\n handleDelete(text, cursor, deletions) {\n deletions.push({\n index: cursor,\n chars: text[cursor],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n this.shiftPastedCharsIndices(cursor, 1);\n return {\n text: text.substring(0, cursor) + text.substring(cursor + 1),\n cursor\n };\n }\n\n handleArrowMove(key, text, cursor) {\n return key === 'ArrowLeft'\n ? Math.max(0, cursor - 1)\n : Math.min(text.length, cursor + 1);\n }\n\n handleCharacterInsert(charToInsert, text, cursor, highlights) {\n text = text.substring(0, cursor) + charToInsert + text.substring(cursor);\n // Shift pasted chars indices after the insertion point\n if (this.pastedChars) {\n this.pastedChars = this.pastedChars.map(p => {\n return p.index >= cursor ? {...p, index: p.index + 1} : p;\n });\n }\n if (charToInsert.trim() !== '') {\n highlights.push({\n index: cursor,\n chars: charToInsert,\n time: this.currentTime,\n expiresAt: this.currentTime + 1500\n });\n }\n return {text, cursor: cursor + 1};\n }\n\n handleCtrlDelete(text, cursor, deletions) {\n const wordEnd = this.findNextWordBoundary(text, cursor);\n const wordToDelete = text.substring(cursor, wordEnd);\n for (let i = 0; i < wordToDelete.length; i++) {\n deletions.push({\n index: cursor + i,\n chars: wordToDelete[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n this.shiftPastedCharsIndices(cursor, wordToDelete.length);\n return {\n text: text.substring(0, cursor) + text.substring(wordEnd),\n cursor\n };\n }\n\n handleArrowUp(text, cursor) {\n const lines = text.split('\\n');\n const {lineIndex, col} = this.getLineAndColumn(text, cursor);\n if (lineIndex > 0) {\n const prevLine = lines[lineIndex - 1];\n cursor = lines.slice(0, lineIndex - 1).join('\\n').length + 1 + Math.min(col, prevLine.length);\n } else {\n cursor = 0;\n }\n return cursor;\n }\n\n handleArrowDown(text, cursor) {\n const lines = text.split('\\n');\n const {lineIndex, col} = this.getLineAndColumn(text, cursor);\n if (lineIndex < lines.length - 1) {\n const nextLine = lines[lineIndex + 1];\n cursor = lines.slice(0, lineIndex + 1).join('\\n').length + 1 + Math.min(col, nextLine.length);\n } else {\n cursor = text.length;\n }\n return cursor;\n }\n\n handleCtrlBackspace(text, cursor, deletions) {\n let wordStart = cursor;\n while (wordStart > 0 && text[wordStart - 1] === ' ') {\n wordStart--;\n }\n while (wordStart > 0 && text[wordStart - 1] !== ' ') {\n wordStart--;\n }\n const wordToDelete = text.substring(wordStart, cursor);\n for (let i = 0; i < wordToDelete.length; i++) {\n deletions.push({\n index: wordStart + i,\n chars: wordToDelete[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n this.shiftPastedCharsIndices(wordStart, wordToDelete.length);\n return {text: text.substring(0, wordStart) + text.substring(cursor), cursor: wordStart};\n }\n\n // Finds the index of the next word boundary after the cursor position\n findNextWordBoundary(text, cursor) {\n if (!text || cursor >= text.length) {\n return cursor;\n }\n if (text[cursor] === ' ') {\n while (cursor < text.length && text[cursor] === ' ') {\n cursor++;\n }\n }\n if (cursor >= text.length) {\n let lastNonSpace = text.length - 1;\n while (lastNonSpace >= 0 && text[lastNonSpace] === ' ') {\n lastNonSpace--;\n }\n return lastNonSpace + 1;\n }\n let wordEnd = cursor;\n while (wordEnd < text.length && text[wordEnd] !== ' ') {\n wordEnd++;\n }\n return wordEnd;\n }\n\n // Finds the index of the previous word boundary before the cursor position\n findPreviousWordBoundary(text, cursor) {\n if (cursor <= 0) {\n return 0;\n }\n let pos = cursor - 1;\n while (pos > 0 && (text[pos] === ' ' || text[pos] === '\\n')) {\n pos--;\n }\n while (pos > 0 && text[pos - 1] !== ' ' && text[pos - 1] !== '\\n') {\n pos--;\n }\n\n return pos;\n }\n\n skipToEnd() {\n if (this.replayInProgress) {\n this.replayInProgress = false;\n }\n let textOutput = \"\";\n this.logData.forEach(event => {\n if (event.event.toLowerCase() === 'keydown') {\n textOutput = this.applyKey(event.key, textOutput);\n }\n });\n this.outputElement.innerHTML = textOutput.slice(0, -1);\n this.setScrubberVal(100);\n }\n\n // Used by the scrubber to skip to a certain percentage of data\n skipToTime(percentage) {\n const wasPlaying = this.replayInProgress;\n this.stopReplay();\n\n const targetTime = (this.totalDuration * percentage) / 100;\n this.currentTime = targetTime;\n this.currentEventIndex = 0;\n this.text = '';\n this.cursorPosition = 0;\n this.highlightedChars = [];\n this.deletedChars = [];\n this.isControlKeyPressed = false;\n this.isPasteEvent = false;\n this.pastedChars = []; // Reset pasted characters tracking\n this.currentPasteIndex = 0;\n let text = '';\n let cursor = 0;\n let highlights = [];\n let deletions = [];\n let pasteIndex = 0;\n\n for (let i = 0; i < this.logData.length; i++) {\n const event = this.logData[i];\n if (event.normalizedTime && event.normalizedTime > targetTime) {\n this.currentEventIndex = i;\n break;\n }\n if (event.rePosition !== undefined && (i === 0 || event.event === 'mouseDown' || event.event === 'mouseUp')) {\n cursor = Math.max(0, Math.min(event.rePosition, text.length));\n }\n if (event.event?.toLowerCase() === 'keydown') {\n this.currentPasteIndex = pasteIndex;\n if ((event.key === 'v' || event.key === 'V') && this.isControlKeyPressed) {\n pasteIndex++;\n }\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\n }\n this.currentEventIndex = i + 1;\n }\n\n this.currentPasteIndex = pasteIndex;\n this.text = text;\n this.cursorPosition = cursor;\n this.highlightedChars = highlights.filter(h => !h.expiresAt || h.expiresAt > targetTime);\n this.deletedChars = deletions.filter(d => !d.expiresAt || d.expiresAt > targetTime);\n this.updateDisplayText(this.text, this.cursorPosition, this.highlightedChars, this.deletedChars);\n this.setScrubberVal(percentage);\n\n if (wasPlaying) {\n this.replayInProgress = true;\n this.replayLog();\n }\n }\n\n // Update display with text, cursor, highlights and deletions\n updateDisplayText(text, cursorPosition, highlights, deletions) {\n let html = '';\n const highlightMap = {};\n const deletionMap = {};\n const pastedMap = {};\n const currentTime = this.currentTime;\n\n highlights.forEach(h => {\n let opacity = 1;\n if (h.expiresAt && h.expiresAt - currentTime < 500) {\n opacity = Math.max(0, (h.expiresAt - currentTime) / 500);\n }\n highlightMap[h.index] = {chars: h.chars, opacity};\n });\n\n deletions.forEach(d => {\n let opacity = 0.5;\n if (d.expiresAt && d.expiresAt - currentTime < 500) {\n opacity = Math.max(0, ((d.expiresAt - currentTime) / 500) * 0.5);\n }\n deletionMap[d.index] = {chars: d.chars, opacity};\n });\n\n // Process pasted characters for bold styling\n if (this.pastedChars) {\n this.pastedChars.forEach(p => {\n if (p.index < text.length) {\n pastedMap[p.index] = true;\n }\n });\n }\n\n // Find if we have out-of-bounds deletions (from Control+Backspace)\n const outOfRangeDeletions = deletions.filter(d => d.index >= text.length);\n const textLines = text.split('\\n');\n let currentPosition = 0;\n\n for (let lineIndex = 0; lineIndex < textLines.length; lineIndex++) {\n const line = textLines[lineIndex];\n for (let i = 0; i < line.length; i++) {\n if (currentPosition === cursorPosition) {\n html += '';\n }\n const char = line[i];\n if (deletionMap[currentPosition]) {\n html += `${deletionMap[currentPosition].chars}`;\n }\n const isPasted = pastedMap[currentPosition];\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\n\n if (isPasted && isHighlighted) {\n // Character is both pasted and recently typed (highlighted) - show bold with highlight\n html += `${char}`;\n } else if (isPasted) {\n // Character is pasted - show in bold\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isHighlighted) {\n // Character is recently typed - show with green highlight\n html += `${char}`;\n } else {\n // Regular character\n html += char === ' ' ? ' ' : this.escapeHtml(char);\n }\n currentPosition++;\n }\n if (currentPosition === cursorPosition) {\n html += '';\n }\n if (lineIndex < textLines.length - 1) {\n html += '
';\n currentPosition++;\n }\n }\n\n if (cursorPosition === text.length && !html.endsWith('')) {\n html += '';\n }\n\n if (outOfRangeDeletions.length > 0) {\n outOfRangeDeletions.sort((a, b) => a.index - b.index);\n const cursorHTML = '';\n const cursorPos = html.lastIndexOf(cursorHTML);\n if (cursorPos !== -1) {\n let deletedWordHTML = '';\n outOfRangeDeletions.forEach(d => {\n deletedWordHTML += d.chars;\n });\n deletedWordHTML += '';\n html = html.substring(0, cursorPos) + deletedWordHTML + html.substring(cursorPos);\n }\n }\n\n const wasScrolledToBottom = this.outputElement.scrollHeight -\n this.outputElement.clientHeight <= this.outputElement.scrollTop + 1;\n this.outputElement.innerHTML = html;\n\n if (wasScrolledToBottom || this.isCursorBelowViewport()) {\n this.outputElement.scrollTop = this.outputElement.scrollHeight;\n }\n }\n\n // Check if cursor is below visible viewport\n isCursorBelowViewport() {\n const cursorElement = this.outputElement.querySelector('.tiny_cursive-cursor:last-of-type');\n if (!cursorElement) {\n return false;\n }\n\n const cursorRect = cursorElement.getBoundingClientRect();\n const outputRect = this.outputElement.getBoundingClientRect();\n\n return cursorRect.bottom > outputRect.bottom;\n }\n\n escapeHtml(unsafe) {\n return unsafe\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n\n // Used in various places to add a keydown, backspace, etc. to the output\n applyKey(key) {\n switch (key) {\n case 'Enter':\n return '\\n';\n case 'Backspace':\n case 'Delete':\n case 'ControlBackspace':\n return '';\n case ' ':\n return ' ';\n default:\n return !['Shift', 'Ctrl', 'Alt', 'ArrowDown', 'ArrowUp', 'Control', 'ArrowRight',\n 'ArrowLeft', 'Meta', 'CapsLock', 'Tab', 'Escape', 'Delete', 'PageUp', 'PageDown',\n 'Insert', 'Home', 'End', 'NumLock', 'AudioVolumeUp', 'AudioVolumeDown',\n 'MediaPlayPause', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10',\n 'F11', 'F12', 'PrintScreen', 'UnIdentified'].includes(key) ? key : '';\n }\n }\n}\n"],"names":["constructor","elementId","filePath","speed","loop","controllerId","replayInProgress","parseFloat","highlightedChars","deletedChars","cursorPosition","currentEventIndex","totalEvents","currentTime","totalDuration","usercomments","pasteTimestamps","isPasteEvent","isControlKeyPressed","isShiftKeyPressed","text","pastedEvents","currentPasteIndex","pastedChars","element","document","getElementById","Error","outputElement","loadJSON","then","data","status","processData","this","logData","length","identifyPasteEvents","constructController","startReplay","handleNoSubmission","catch","error","window","console","message","localStorage","getItem","Str","get_string","str","setItem","JSON","parse","comments","Array","isArray","payload","i","event","pastedContent","j","push","unixTimestamp","startTime","map","normalizedTime","html","Promise","all","templates","render","tempDiv","createElement","innerHTML","newElement","firstChild","textContent","querySelectorAll","forEach","appendChild","cloneNode","stopReplay","clearTimeout","replayTimeout","playButton","playSvg","src","M","util","image_url","querySelector","outerHTML","currentPosition","replayIntervalId","clearInterval","container","controlContainer","buildControllerUI","remove","classList","topRow","add","createPlayButton","scrubberContainer","createScrubberContainer","timeDisplay","createTimeDisplay","bottomRow","speedContainer","createSpeedControls","pasteEventsToggle","createPasteEventsToggle","pasteEventsPanel","addEventListener","btn","scrubberElement","type","max","min","value","skipToTime","parseInt","speedLabel","speedGroup","speedBtn","dataset","pasteEventsIcon","pasteIcon","pasteEventsText","pasteEventCount","className","style","marginLeft","chevronIcon","chevron","transition","createPasteEventsPanel","isHidden","display","transform","existingPanel","populatePasteEventsPanel","controlPressed","shiftPressed","pasteCount","toLowerCase","key","timestamp","index","time","formattedTime","formatTime","pastedText","comment","lastIndex","panel","noEventsMessage","carouselContainer","navigationRow","counterDisplay","navButtons","prevButton","nextButton","disabled","contentContainer","createPasteEventDisplay","currentIndex","updateDisplay","opacity","pasteEvent","eventRow","headerRow","textContainer","timestampContainer","pastedTextContainer","playIcon","jumpToTimestamp","percentage","setScrubberVal","String","displayTime","Math","methodname","args","filepath","done","response","fail","ms","seconds","floor","minutes","remainingSeconds","toString","padStart","reset","pauseSvg","replayLog","cursor","updatedHighlights","updatedDeleted","undefined","rePosition","processKeydownEvent","filter","h","expiresAt","d","updateDisplayText","percentComplete","baseIncrement","incrementTime","setTimeout","getLineAndColumn","pos","before","substring","lineIndex","split","col","lastIndexOf","highlights","deletions","charToInsert","applyKey","updateModifierStates","handlePasteInsert","isCtrlBackspace","handleCtrlBackspace","isCtrlDelete","handleCtrlDelete","isCtrlArrowMove","handleCtrlArrowMove","isRegularBackspace","handleBackspace","isRegularDelete","handleDelete","isArrowUp","handleArrowUp","isArrowDown","handleArrowDown","isRegularArrowMove","handleArrowMove","handleCharacterInsert","insertText","trim","chars","shiftPastedCharsIndices","startIndex","numDeleted","p","includes","findPreviousWordBoundary","findNextWordBoundary","wordEnd","wordToDelete","lines","prevLine","slice","join","nextLine","wordStart","lastNonSpace","skipToEnd","textOutput","wasPlaying","targetTime","pasteIndex","highlightMap","deletionMap","pastedMap","outOfRangeDeletions","textLines","line","char","isPasted","isHighlighted","escapeHtml","endsWith","sort","a","b","cursorHTML","cursorPos","deletedWordHTML","wasScrolledToBottom","scrollHeight","clientHeight","scrollTop","isCursorBelowViewport","cursorElement","cursorRect","getBoundingClientRect","outputRect","bottom","unsafe","replace"],"mappings":"utCA2BIA,YAAYC,UAAWC,cAAUC,6DAAQ,EAAGC,6DAAcC,yDAEjDA,aAAeA,cAAgB,QAC/BC,kBAAmB,OACnBH,MAAQI,WAAWJ,YACnBC,KAAOA,UACPI,iBAAmB,QACnBC,aAAe,QACfC,eAAiB,OACjBC,kBAAoB,OACpBC,YAAc,OACdC,YAAc,OACdC,cAAgB,OAChBC,aAAe,QACfC,gBAAkB,QAClBC,cAAe,OACfC,qBAAsB,OACtBC,mBAAoB,OACpBC,KAAO,QACPC,aAAe,QACfC,kBAAoB,OACpBC,YAAc,SAEbC,QAAUC,SAASC,eAAezB,eACnCuB,cACK,IAAIG,iCAA0B1B,+BAEnC2B,cAAgBJ,aAGhBK,SAAS3B,UAAU4B,MAAKC,OACrBA,KAAKC,aACAC,YAAYF,WACZnB,YAAcsB,KAAKC,QAAQC,YAC3BC,sBACDH,KAAK7B,cAAgB6B,KAAKC,cACrBG,oBAAoBJ,KAAK7B,mBAE7BkC,oBAEAC,qBAEFT,QACRU,OAAMC,aACAF,qBACLG,OAAOC,QAAQF,MAAM,2BAA4BA,MAAMG,YAEtDC,aAAaC,QAAQ,iBAAoBD,aAAaC,QAAQ,gBAC/DC,IAAIC,WAAW,eAAgB,gBAAgBnB,MAAKoB,MAChDJ,aAAaK,QAAQ,eAAgBD,KAC9BA,OAEXF,IAAIC,WAAW,aAAc,gBAAgBnB,MAAKoB,MAC9CJ,aAAaK,QAAQ,aAAcD,KAC5BA,QAMnBjB,YAAYF,WACHI,QAAUiB,KAAKC,MAAMtB,KAAKA,MAC3BA,KAAKuB,gBACAvC,aAAewC,MAAMC,QAAQJ,KAAKC,MAAMtB,KAAKuB,WAAaF,KAAKC,MAAMtB,KAAKuB,UAAY,IAE3F,SAAUpB,KAAKC,eACVA,QAAUD,KAAKC,QAAQJ,MAE5B,YAAaG,KAAKC,eACbA,QAAUD,KAAKC,QAAQsB,aAE3B,IAAIC,EAAI,EAAGA,EAAIxB,KAAKC,QAAQC,OAAQsB,IAAK,OACpCC,MAAQzB,KAAKC,QAAQuB,MACP,UAAhBC,MAAMA,OAAqBJ,MAAMC,QAAQG,MAAMC,mBAC1C,IAAIC,EAAI,EAAGA,EAAIF,MAAMC,cAAcxB,OAAQyB,SACvCxC,aAAayC,KAAKH,MAAMC,cAAcC,OAInD3B,KAAKC,QAAQC,OAAS,GAAKF,KAAKC,QAAQ,GAAG4B,cAAe,OACpDC,UAAY9B,KAAKC,QAAQ,GAAG4B,mBAC7B5B,QAAUD,KAAKC,QAAQ8B,KAAIN,YACzBA,MACHO,eAAgBP,MAAMI,cAAgBC,mBAErClD,cAAgBoB,KAAKC,QAAQD,KAAKC,QAAQC,OAAS,GAAG8B,qDAMpDC,KAAMjB,WAAakB,QAAQC,IAAI,CAClCC,mBAAUC,OAAO,8BACjBvB,IAAIC,WAAW,iBAAkB,kBAE/BuB,QAAU/C,SAASgD,cAAc,OACvCD,QAAQE,UAAYP,WACdQ,WAAaH,QAAQI,WAC3BD,WAAWE,YAAc3B,WAEGzB,SAASqD,iBAAiB,iBAClCC,SAAQvD,UACxBA,QAAQkD,UAAY,GACpBlD,QAAQwD,YAAYL,WAAWM,WAAU,QAGtC,EACT,MAAOvC,cACLC,OAAOC,QAAQF,MAAMA,QACd,GAKfwC,gBACQhD,KAAK5B,mBACL6E,aAAajD,KAAKkD,oBACb9E,kBAAmB,EACpB4B,KAAKmD,YAAY,OACXC,QAAU7D,SAASgD,cAAc,OACvCa,QAAQC,IAAMC,EAAEC,KAAKC,UAAU,WAAY,qBACtCL,WAAWM,cAAc,cAAcjB,UAAYY,QAAQM,WAM5EtD,oBAAoBjC,6CACXC,kBAAmB,OACnBuF,gBAAkB,OAClB1F,MAAQ,EACT+B,KAAK4D,mBACLC,cAAc7D,KAAK4D,uBACdA,iBAAmB,YAGtBE,UAAYvE,SAASC,eAAerB,kBACrC2F,sBACDrD,OAAOC,QAAQF,MAAM,+BAAgCrC,oBAInD4F,iBAAmBD,UAAUL,cAAc,gCAC5CM,kBAILA,iBAAiBvB,UAAY,0DAExBwB,kBAAkBD,iBAAkBD,yCACzCC,iBAAiBN,cAAc,yFAAkCQ,SAEjEF,iBAAiBG,UAAUD,OAAO,WAR9BxD,OAAOC,QAAQF,MAAM,yCAA0CrC,cAWvE6F,kBAAkBD,iBAAkBD,iBAC1BK,OAAS5E,SAASgD,cAAc,OACtC4B,OAAOD,UAAUE,IAAI,6BAEhBjB,WAAanD,KAAKqE,mBACvBF,OAAOrB,YAAY9C,KAAKmD,kBAElBmB,kBAAoBtE,KAAKuE,0BAC/BJ,OAAOrB,YAAYwB,wBAEdE,YAAcxE,KAAKyE,oBACxBN,OAAOrB,YAAY9C,KAAKwE,mBAElBE,UAAYnF,SAASgD,cAAc,OACzCmC,UAAUR,UAAUE,IAAI,iCAElBO,eAAiB3E,KAAK4E,sBAC5BF,UAAU5B,YAAY6B,sBAEhBE,kBAAoB7E,KAAK8E,wBAAwBhB,WACvDY,UAAU5B,YAAY+B,mBAEtBd,iBAAiBjB,YAAYqB,QAC7BJ,iBAAiBjB,YAAY4B,WAC7BZ,UAAUhB,YAAY9C,KAAK+E,kBAG/BV,yBACUlB,WAAa5D,SAASgD,cAAc,UAC1CY,WAAWe,UAAUE,IAAI,kCACnBhB,QAAU7D,SAASgD,cAAc,YACvCY,WAAWX,4CAAuCY,QAAQM,qBAC1DP,WAAW6B,iBAAiB,SAAS,KAC7BhF,KAAK5B,sBACA4E,kBAEA3C,aAAY,GAErBd,SAASqD,iBAAiB,iCAAiCC,SAAQoC,KAAOA,IAAIf,UAAUD,OAAO,YAC/F1E,SAASqD,iBAAiB,gBAAgBC,SAAQoC,KAAOA,IAAIf,UAAUE,IAAI,eAExEjB,WAGXoB,gCACUD,kBAAoB/E,SAASgD,cAAc,cACjD+B,kBAAkBJ,UAAUE,IAAI,wCAC3Bc,gBAAkB3F,SAASgD,cAAc,cACzC2C,gBAAgBhB,UAAUE,IAAI,iCAAkC,0BAChEc,gBAAgBC,KAAO,aACvBD,gBAAgBE,IAAM,WACtBF,gBAAgBG,IAAM,SACtBH,gBAAgBI,MAAQ,SACxBJ,gBAAgBF,iBAAiB,SAAS,UACtCO,WAAWC,SAASxF,KAAKkF,gBAAgBI,MAAO,QAEzDhB,kBAAkBxB,YAAY9C,KAAKkF,iBAC5BZ,kBAGXG,0BACUD,YAAcjF,SAASgD,cAAc,cAC3CiC,YAAYN,UAAUE,IAAI,6BAC1BI,YAAY7B,YAAc,gBACnB6B,YAGXI,4BACUD,eAAiBpF,SAASgD,cAAc,OAC9CoC,eAAeT,UAAUE,IAAI,8BAA+B,wBACtDqB,WAAalG,SAASgD,cAAc,QAC1CkD,WAAWvB,UAAUE,IAAI,4BACzBqB,WAAW9C,YAAc,UACzBgC,eAAe7B,YAAY2C,kBAErBC,WAAanG,SAASgD,cAAc,cAC1CmD,WAAWxB,UAAUE,IAAI,6BACxB,EAAG,IAAK,EAAG,EAAG,IAAIvB,SAAQ5E,cACjB0H,SAAWpG,SAASgD,cAAc,UACxCoD,SAAShD,sBAAiB1E,WAC1B0H,SAASzB,UAAUE,IAAI,yBAA0B,aAC7C/F,WAAWJ,SAAW+B,KAAK/B,OAC3B0H,SAASzB,UAAUE,IAAI,UAE3BuB,SAASC,QAAQ3H,MAAQA,MACzB0H,SAASX,iBAAiB,SAAS,KAC/BzF,SAASqD,iBAAiB,2BAA2BC,SAAQoC,KAAOA,IAAIf,UAAUD,OAAO,YACzF0B,SAASzB,UAAUE,IAAI,eAClBnG,MAAQI,WAAWsH,SAASC,QAAQ3H,OACrC+B,KAAK5B,wBACA4E,kBACA3C,aAAY,OAGzBqF,WAAW5C,YAAY6C,aAE3BhB,eAAe7B,YAAY4C,YACpBf,eAGXG,wBAAwBhB,iBACde,kBAAoBtF,SAASgD,cAAc,OACjDsC,kBAAkBX,UAAUE,IAAI,mCAAoC,6BAE9DyB,gBAAkBtG,SAASgD,cAAc,QACzCuD,UAAYvG,SAASgD,cAAc,OACzCuD,UAAUzC,IAAMC,EAAEC,KAAKC,UAAU,YAAa,gBAC9CqC,gBAAgBrD,UAAYsD,UAAUpC,UACtCmC,gBAAgB3B,UAAUE,IAAI,wCAExB2B,gBAAkBxG,SAASgD,cAAc,QAC/CwD,gBAAgBpD,YAAc/B,aAAaC,QAAQ,mBAE9CmF,gBAAkBzG,SAASgD,cAAc,aACzCyD,gBAAgBrD,uBAAkB3C,KAAKlB,gBAAgBoB,iBACvD8F,gBAAgBC,UAAY,yBAC5BD,gBAAgBE,MAAMC,WAAa,YAElCC,YAAc7G,SAASgD,cAAc,QACrC8D,QAAU9G,SAASgD,cAAc,YACvC8D,QAAQJ,UAAY,qBACpBG,YAAY5D,UAAY6D,QAAQ3C,UAChC0C,YAAYF,MAAMC,WAAa,MAC/BC,YAAYF,MAAMI,WAAa,sBAE/BzB,kBAAkB/B,YAAY+C,iBAC9BhB,kBAAkB/B,YAAYiD,iBAC9BlB,kBAAkB/B,YAAY9C,KAAKgG,iBACnCnB,kBAAkB/B,YAAYsD,kBAEzBrB,iBAAmB/E,KAAKuG,uBAAuBzC,WACpDe,kBAAkBG,iBAAiB,SAAS,WAClCwB,SAAmD,SAAxCxG,KAAK+E,iBAAiBmB,MAAMO,aACxC1B,iBAAiBmB,MAAMO,QAAUD,SAAW,QAAU,OAC3DJ,YAAYF,MAAMQ,UAAYF,SAAW,iBAAmB,kBAGzD3B,kBAGX0B,uBAAuBzC,iBACb6C,cAAgB7C,UAAUL,cAAc,uBAC1CkD,eACAA,cAAc1C,eAEZc,iBAAmBxF,SAASgD,cAAc,cAChDwC,iBAAiBb,UAAUE,IAAI,kCAAmC,sBAClEW,iBAAiBmB,MAAMO,QAAU,YAC5BG,yBAAyB7B,kBACvBA,iBAIX5E,2BACSrB,gBAAkB,OACnB+H,gBAAiB,EAEjBC,cAAe,EACfC,WAAa,MAEZ,IAAIvF,EAAI,EAAGA,EAAIxB,KAAKC,QAAQC,OAAQsB,IAAK,wBACpCC,MAAQzB,KAAKC,QAAQuB,MACQ,kCAA/BC,MAAMA,kDAAOuF,kBACK,YAAdvF,MAAMwF,IACNJ,gBAAiB,OACd,GAAkB,UAAdpF,MAAMwF,IACbH,cAAe,OACZ,GAAmB,MAAdrF,MAAMwF,KAA6B,MAAdxF,MAAMwF,MAAgBJ,eAenDA,gBAAiB,EACjBC,cAAe,MAhBoD,IAC/D9G,KAAKb,aAAa4H,YAAa,OACzBG,UAAYzF,MAAMO,gBAAkB,OACrClD,gBAAgB8C,KAAK,CACtBuF,MAAOJ,WACPK,KAAMF,UACNG,cAAerH,KAAKsH,WAAWJ,WAC/BK,WAAYvH,KAAKb,aAAa4H,YAC9BG,UAAAA,YAGRH,aACAF,gBAAiB,EACjBC,cAAe,OAQvB9G,KAAKnB,aAAaqB,OAAS,GAAqC,IAAhCF,KAAKlB,gBAAgBoB,aAChDrB,aAAagE,SAAQ,CAAC2E,QAAShG,UAC3B1C,gBAAgB8C,KAAK,CACtBuF,MAAO3F,EACP4F,KAAM,EACNC,cAAerH,KAAKsH,WAAW,GAC/BC,WAAYC,QACZN,UAAW,OAKhBlH,KAAKlB,gBAAgBoB,OAASF,KAAKnB,aAAaqB,QAAQ,OACrDuH,UAAYzH,KAAKlB,gBAAgBoB,YAClCpB,gBAAgB8C,KAAK,CACtBuF,MAAOM,UACPL,KAAM,EACNC,cAAerH,KAAKsH,WAAW,GAC/BC,WAAYvH,KAAKnB,aAAa4I,WAC9BP,UAAW,IAIflH,KAAK+E,uBACA6B,yBAAyB5G,KAAK+E,kBAK3C6B,yBAAyBc,UACrBA,MAAMlF,UAAY,GAClBkF,MAAMxD,UAAUE,IAAI,6BAEfpE,KAAKlB,gBAAgBoB,OAAQ,OACxByH,gBAAkBpI,SAASgD,cAAc,cAC/CoF,gBAAgB1B,UAAY,8BAC5B0B,gBAAgBhF,YAAc/B,aAAaC,QAAQ,qBACnD6G,MAAM5E,YAAY6E,uBAIhBC,kBAAoBrI,SAASgD,cAAc,OACjDqF,kBAAkB1D,UAAUE,IAAI,qCAAsC,+BAEhEyD,cAAgBtI,SAASgD,cAAc,OAC7CsF,cAAc3D,UAAUE,IAAI,0BAA2B,qCAEjD0D,eAAiBvI,SAASgD,cAAc,OAC9CuF,eAAe5D,UAAUE,IAAI,uBAAwB,gCACrD0D,eAAenF,YAAc,qBAEvBoF,WAAaxI,SAASgD,cAAc,OAC1CwF,WAAW7D,UAAUE,IAAI,kCACnB4D,WAAazI,SAASgD,cAAc,UAC1CyF,WAAW9D,UAAUE,IAAI,uBAAwB,2BACjD4D,WAAWxF,UAAY,2CAEjByF,WAAa1I,SAASgD,cAAc,UAC1C0F,WAAW/D,UAAUE,IAAI,uBAAwB,2BACjD6D,WAAWzF,UAAY,sCACvByF,WAAWC,SAAWlI,KAAKlB,gBAAgBoB,QAAU,EAErD6H,WAAWjF,YAAYkF,YACvBD,WAAWjF,YAAYmF,YACvBJ,cAAc/E,YAAYgF,gBAC1BD,cAAc/E,YAAYiF,kBAEpBI,iBAAmB5I,SAASgD,cAAc,OAChD4F,iBAAiBlC,UAAY,sDAC7BkC,iBAAiBrF,YAAY9C,KAAKoI,wBAAwBpI,KAAKlB,gBAAgB,KAE/E8I,kBAAkB9E,YAAY+E,eAC9BD,kBAAkB9E,YAAYqF,kBAC9BT,MAAM5E,YAAY8E,uBAEdS,aAAe,QACbC,cAAgB,KAClBH,iBAAiB3F,UAAY,GAC7B2F,iBAAiBrF,YAAY9C,KAAKoI,wBAAwBpI,KAAKlB,gBAAgBuJ,gBAC/EP,eAAenF,YAAc,eAC7BqF,WAAWE,SAA4B,IAAjBG,aACtBL,WAAW9B,MAAMqC,QAA2B,IAAjBF,aAAqB,MAAQ,IACxDJ,WAAWC,SAAWG,eAAiBrI,KAAKlB,gBAAgBoB,OAAS,EACrE+H,WAAW/B,MAAMqC,QAAUF,eAAiBrI,KAAKlB,gBAAgBoB,OAAS,EAAI,MAAQ,KAG1F8H,WAAWhD,iBAAiB,SAAS,KAC7BqD,aAAe,IACfA,eACAC,oBAIRL,WAAWjD,iBAAiB,SAAS,KAC7BqD,aAAerI,KAAKlB,gBAAgBoB,OAAS,IAC7CmI,eACAC,oBAKZF,wBAAwBI,kBACdC,SAAWlJ,SAASgD,cAAc,OACxCkG,SAASxC,UAAY,+BAEfyC,UAAYnJ,SAASgD,cAAc,OACzCmG,UAAUzC,UAAY,gCAEhB0C,cAAgBpJ,SAASgD,cAAc,OAC7CoG,cAAc1C,UAAY,oCAEpB2C,mBAAqBrJ,SAASgD,cAAc,OAClDqG,mBAAmB3C,UAAY,2DAC/B2C,mBAAmBjG,YAAc6F,WAAWnB,oBAEtCwB,oBAAsBtJ,SAASgD,cAAc,OACnDsG,oBAAoB5C,UAAY,sDAChC4C,oBAAoBlG,YAAc6F,WAAWjB,WAE7CoB,cAAc7F,YAAY8F,oBAC1BD,cAAc7F,YAAY+F,2BAEpB1F,WAAa5D,SAASgD,cAAc,UAC1CY,WAAW8C,UAAY,0DACjB6C,SAAWvJ,SAASgD,cAAc,cACxCuG,SAASzF,IAAMC,EAAEC,KAAKC,UAAU,eAAgB,gBAChDL,WAAWX,UAAYsG,SAASpF,UAChCP,WAAW6B,iBAAiB,SAAS,IAAMhF,KAAK+I,gBAAgBP,WAAWtB,aAE3EwB,UAAU5F,YAAY6F,eACtBD,UAAU5F,YAAYK,YACtBsF,SAAS3F,YAAY4F,WAEdD,SAIXM,gBAAgB7B,iBACN8B,WAAahJ,KAAKpB,cAAgB,EAAKsI,UAAYlH,KAAKpB,cAAiB,IAAM,OAChF2G,WAAWyD,YACXhJ,KAAK5B,uBACDiC,aAAY,GAIzB4I,eAAe3D,UACPtF,KAAKkF,uBACAA,gBAAgBI,MAAQ4D,OAAO5D,OAChCtF,KAAKwE,aAAa,OACZ2E,YAAcC,KAAK/D,IAAIrF,KAAKrB,YAAaqB,KAAKpB,oBAC/C4F,YAAY7B,sBAAiB3C,KAAKsH,WAAW6B,2BAAkBnJ,KAAKsH,WAAWtH,KAAKpB,iBAKrGe,SAAS3B,iBACE,cAAU,CAAC,CACdqL,WAAY,yBACZC,KAAM,CAACC,SAAUvL,aACjB,GAAGwL,MAAKC,UAAYA,WAAUC,MAAKlJ,cAC7B,IAAIf,yCAAkCe,MAAMG,aAI1D2G,WAAWqC,UACDC,QAAUR,KAAKS,MAAMF,GAAK,KAC1BG,QAAUV,KAAKS,MAAMD,QAAU,IAC/BG,iBAAmBH,QAAU,mBACzBE,QAAQE,WAAWC,SAAS,EAAG,iBAAQF,iBAAiBC,WAAWC,SAAS,EAAG,MAI7F5J,kBAAY6J,iEACJlK,KAAK5B,kBACL6E,aAAajD,KAAKkD,mBAEPlD,KAAKpB,cAAgB,GAAKoB,KAAKrB,aAAeqB,KAAKpB,eAC7DoB,KAAKvB,mBAAqBuB,KAAKtB,eACtBwL,QACVA,OAAQ,QAEP9L,kBAAmB,EACpB8L,aACKxK,cAAc8C,UAAY,QAC1BtD,KAAO,QACPV,eAAiB,OACjBC,kBAAoB,OACpBE,YAAc,OACdL,iBAAmB,QACnBC,aAAe,QACfS,qBAAsB,OACtBI,kBAAoB,OACpBC,YAAc,IAEnBW,KAAKmD,WAAY,OACXgH,SAAW5K,SAASgD,cAAc,KACxC4H,SAASlE,UAAY,mBAChB9C,WAAWM,cAAc,cAAcjB,UAAY2H,SAASzG,eAEhE0G,YAITA,eACSpK,KAAK5B,uBAKH4B,KAAKvB,kBAAoBuB,KAAKC,QAAQC,QAAQ,yBAC3CuB,MAAQzB,KAAKC,QAAQD,KAAKvB,sBAC5BgD,MAAMO,gBAAkBP,MAAMO,eAAiBhC,KAAKrB,sBAIpDO,KAAOc,KAAKd,MAAQ,GACpBmL,OAASrK,KAAKxB,eACd8L,kBAAoB,IAAItK,KAAK1B,kBAC7BiM,eAAiB,IAAIvK,KAAKzB,mBAELiM,IAArB/I,MAAMgJ,YAAwD,IAA3BzK,KAAKvB,mBACxB,cAAhBgD,MAAMA,OAAyC,YAAhBA,MAAMA,QACrC4I,OAASjB,KAAKhE,IAAI,EAAGgE,KAAK/D,IAAI5D,MAAMgJ,WAAYvL,KAAKgB,UAGtB,mCAA/BuB,MAAMA,oDAAOuF,kBACX9H,KAAAA,KAAMmL,OAAAA,OAAQC,kBAAAA,kBAAmBC,eAAAA,gBAC/BvK,KAAK0K,oBAAoBjJ,MAAOvC,KAAMmL,OAAQC,kBAAmBC,sBAGpErL,KAAOA,UACPV,eAAiB6L,YACjB/L,iBAAmBgM,kBAAkBK,QAAOC,IAAMA,EAAEC,WAAaD,EAAEC,UAAY7K,KAAKrB,mBACpFJ,aAAegM,eAAeI,QAAOG,IAAMA,EAAED,WAAaC,EAAED,UAAY7K,KAAKrB,mBAE7EF,4BAGJsM,kBAAkB/K,KAAKd,KAAMc,KAAKxB,eAAgBwB,KAAK1B,iBAAkB0B,KAAKzB,cAC/EyB,KAAKpB,cAAgB,EAAG,OAClBoM,gBAAkB5B,KAAK/D,IAAKrF,KAAKrB,YAAcqB,KAAKpB,cAAiB,IAAK,UAC3EqK,eAAe+B,oBAGpBhL,KAAK5B,iBAAkB,OACjB6M,cAAgB,IAChBC,cAAgBD,cAAgBjL,KAAK/B,WACtCU,aAAesM,cAChBjL,KAAKvB,mBAAqBuB,KAAKtB,YAC3BsB,KAAK9B,UACAmC,aAAY,SAEZ2C,kBACA+H,kBAAkB/K,KAAKd,KAAMc,KAAKxB,eAAgB,GAAI,UAG1D0E,cAAgBiI,YAAW,IAAMnL,KAAKoK,aAAac,0BAnDvDH,kBAAkB/K,KAAKd,KAAMc,KAAKxB,eAAgB,GAAI,IAwDnE4M,iBAAiBlM,KAAMmM,WACbC,OAASpM,KAAKqM,UAAU,EAAGF,WAG1B,CAACG,UAFUF,OAAOG,MAAM,MAAMvL,OAAS,EAE3BwL,IADPJ,OAAOpL,OAASoL,OAAOK,YAAY,MAAQ,GAK3DjB,oBAAoBjJ,MAAOvC,KAAMmL,OAAQuB,WAAYC,iBAC3C5E,IAAMxF,MAAMwF,IACZ6E,aAAe9L,KAAK+L,SAAS9E,aAC9B+E,qBAAqB/E,MACb,MAARA,KAAsB,MAARA,MAAgBjH,KAAKhB,qBAChCgB,KAAKb,cAAgBa,KAAKZ,kBAAoBY,KAAKb,aAAae,OAAQ,OAClEwB,cAAgB1B,KAAKb,aAAaa,KAAKZ,2BAC3CF,KAAAA,KAAMmL,OAAAA,QAAUrK,KAAKiM,kBAAkBvK,cAAexC,KAAMmL,cACzDjL,yBACAJ,qBAAsB,OACtBC,mBAAoB,OACpBF,cAAe,EACb,CACHG,KAAAA,KACAmL,OAAAA,OACAC,kBAAmBsB,WACnBrB,eAAgBsB,kBAIxB7L,KAAKkM,gBAAgBjF,IAAKoD,UACxBnL,KAAAA,KAAMmL,OAAAA,QAAUrK,KAAKmM,oBAAoBjN,KAAMmL,OAAQwB,YAClD7L,KAAKoM,aAAanF,IAAKoD,OAAQnL,QACpCA,KAAAA,MAAQc,KAAKqM,iBAAiBnN,KAAMmL,OAAQwB,YACvC7L,KAAKsM,gBAAgBrF,KAC5BoD,OAASrK,KAAKuM,oBAAoBtF,IAAK/H,KAAMmL,QACtCrK,KAAKwM,mBAAmBvF,IAAKoD,UAClCnL,KAAAA,KAAMmL,OAAAA,QAAUrK,KAAKyM,gBAAgBvN,KAAMmL,OAAQwB,YAC9C7L,KAAK0M,gBAAgBzF,IAAKoD,OAAQnL,QACvCA,KAAAA,MAAQc,KAAK2M,aAAazN,KAAMmL,OAAQwB,YACnC7L,KAAK4M,UAAU3F,KACtBoD,OAASrK,KAAK6M,cAAc3N,KAAMmL,QAC3BrK,KAAK8M,YAAY7F,KACxBoD,OAASrK,KAAK+M,gBAAgB7N,KAAMmL,QAC7BrK,KAAKgN,mBAAmB/F,KAC/BoD,OAASrK,KAAKiN,gBAAgBhG,IAAK/H,KAAMmL,QAClCyB,cAAgBA,aAAa5L,OAAS,KAC3ChB,KAAAA,KAAMmL,OAAAA,QAAUrK,KAAKkN,sBAAsBpB,aAAc5M,KAAMmL,OAAQuB,aAEtE,CACH1M,KAAAA,KACAmL,OAAAA,OACAC,kBAAmBsB,WACnBrB,eAAgBsB,WAKxBI,kBAAkBvK,cAAexC,KAAMmL,cAC7B8C,WAAazL,eAAiB,MACpCxC,KAAOA,KAAKqM,UAAU,EAAGlB,QAAU8C,WAAajO,KAAKqM,UAAUlB,QAGrC,KAAtB8C,WAAWC,WACN,IAAI5L,EAAI,EAAGA,EAAI2L,WAAWjN,OAAQsB,IAC9BxB,KAAKX,mBACDA,YAAc,SAElBA,YAAYuC,KAAK,CAClBuF,MAAOkD,OAAS7I,EAChB6L,MAAOF,WAAW3L,WAKvB,CAACtC,KAAAA,KAAMmL,OAAQA,OAAS8C,WAAWjN,QAI9CoN,wBAAwBC,WAAYC,iBAC3BnO,YAAcW,KAAKX,YAAY0C,KAAI0L,GAChCA,EAAEtG,OAASoG,WAAaC,WACjB,IAAIC,EAAGtG,MAAOsG,EAAEtG,MAAQqG,YACxBC,EAAEtG,OAASoG,YAAcE,EAAEtG,MAAQoG,WAAaC,WAEhD,KAEJC,IACR9C,QAAO8C,GAAW,OAANA,IAInBzB,qBAAqB/E,KACL,YAARA,SACKjI,qBAAsB,EACZ,UAARiI,SACFhI,mBAAoB,EACT,MAARgI,KAAuB,MAARA,MAAgBjH,KAAKhB,oBAEpC,CAAC,UAAW,YAAa,SAAU,YAAa,cAAc0O,SAASzG,YAC1EjI,qBAAsB,OACtBC,mBAAoB,OACpBF,cAAe,QAJfA,cAAe,EAQ5BmN,gBAAgBjF,IAAKoD,cACF,cAARpD,KAAuBjH,KAAKhB,qBAAuBqL,OAAS,EAGvE+B,aAAanF,IAAKoD,OAAQnL,YACP,WAAR+H,KAAoBjH,KAAKhB,qBAAuBqL,OAASnL,KAAKgB,OAGzEoM,gBAAgBrF,YACLjH,KAAKhB,sBAAgC,cAARiI,KAA+B,eAARA,KAG/DuF,mBAAmBvF,IAAKoD,cACL,cAARpD,MAAwBjH,KAAKjB,cAAgBsL,OAAS,EAGjEqC,gBAAgBzF,IAAKoD,OAAQnL,YACV,WAAR+H,MAAqBjH,KAAKhB,qBAAuBqL,OAASnL,KAAKgB,OAG1E8M,mBAAmB/F,YACPjH,KAAKhB,sBAAgC,cAARiI,KAA+B,eAARA,KAGhE2F,UAAU3F,WACS,YAARA,IAGX6F,YAAY7F,WACO,cAARA,IAGXsF,oBAAoBtF,IAAK/H,KAAMmL,cACZ,cAARpD,IACDjH,KAAK2N,yBAAyBzO,KAAMmL,QACpCrK,KAAK4N,qBAAqB1O,KAAMmL,QAG1CoC,gBAAgBvN,KAAMmL,OAAQwB,kBAC1BA,UAAUjK,KAAK,CACXuF,MAAOkD,OAAS,EAChBgD,MAAOnO,KAAKmL,OAAS,GACrBjD,KAAMpH,KAAKrB,YACXkM,UAAW7K,KAAKrB,YAAc,WAE7B2O,wBAAwBjD,OAAS,EAAG,GAClC,CACHnL,KAAMA,KAAKqM,UAAU,EAAGlB,OAAS,GAAKnL,KAAKqM,UAAUlB,QACrDA,OAAQA,OAAS,GAIzBsC,aAAazN,KAAMmL,OAAQwB,kBACvBA,UAAUjK,KAAK,CACXuF,MAAOkD,OACPgD,MAAOnO,KAAKmL,QACZjD,KAAMpH,KAAKrB,YACXkM,UAAW7K,KAAKrB,YAAc,WAE7B2O,wBAAwBjD,OAAQ,GAC9B,CACHnL,KAAMA,KAAKqM,UAAU,EAAGlB,QAAUnL,KAAKqM,UAAUlB,OAAS,GAC1DA,OAAAA,QAIR4C,gBAAgBhG,IAAK/H,KAAMmL,cACR,cAARpD,IACDmC,KAAKhE,IAAI,EAAGiF,OAAS,GACrBjB,KAAK/D,IAAInG,KAAKgB,OAAQmK,OAAS,GAGzC6C,sBAAsBpB,aAAc5M,KAAMmL,OAAQuB,mBAC9C1M,KAAOA,KAAKqM,UAAU,EAAGlB,QAAUyB,aAAe5M,KAAKqM,UAAUlB,QAE7DrK,KAAKX,mBACAA,YAAcW,KAAKX,YAAY0C,KAAI0L,GAC7BA,EAAEtG,OAASkD,OAAS,IAAIoD,EAAGtG,MAAOsG,EAAEtG,MAAQ,GAAKsG,KAGpC,KAAxB3B,aAAasB,QACbxB,WAAWhK,KAAK,CACZuF,MAAOkD,OACPgD,MAAOvB,aACP1E,KAAMpH,KAAKrB,YACXkM,UAAW7K,KAAKrB,YAAc,OAG/B,CAACO,KAAAA,KAAMmL,OAAQA,OAAS,GAGnCgC,iBAAiBnN,KAAMmL,OAAQwB,iBACrBgC,QAAU7N,KAAK4N,qBAAqB1O,KAAMmL,QAC1CyD,aAAe5O,KAAKqM,UAAUlB,OAAQwD,aACvC,IAAIrM,EAAI,EAAGA,EAAIsM,aAAa5N,OAAQsB,IACrCqK,UAAUjK,KAAK,CACXuF,MAAOkD,OAAS7I,EAChB6L,MAAOS,aAAatM,GACpB4F,KAAMpH,KAAKrB,YACXkM,UAAW7K,KAAKrB,YAAc,kBAGjC2O,wBAAwBjD,OAAQyD,aAAa5N,QAC3C,CACHhB,KAAMA,KAAKqM,UAAU,EAAGlB,QAAUnL,KAAKqM,UAAUsC,SACjDxD,OAAAA,QAIRwC,cAAc3N,KAAMmL,cACV0D,MAAQ7O,KAAKuM,MAAM,OACnBD,UAACA,UAADE,IAAYA,KAAO1L,KAAKoL,iBAAiBlM,KAAMmL,WACjDmB,UAAY,EAAG,OACTwC,SAAWD,MAAMvC,UAAY,GACnCnB,OAAS0D,MAAME,MAAM,EAAGzC,UAAY,GAAG0C,KAAK,MAAMhO,OAAS,EAAIkJ,KAAK/D,IAAIqG,IAAKsC,SAAS9N,aAEtFmK,OAAS,SAENA,OAGX0C,gBAAgB7N,KAAMmL,cACZ0D,MAAQ7O,KAAKuM,MAAM,OACnBD,UAACA,UAADE,IAAYA,KAAO1L,KAAKoL,iBAAiBlM,KAAMmL,WACjDmB,UAAYuC,MAAM7N,OAAS,EAAG,OACxBiO,SAAWJ,MAAMvC,UAAY,GACnCnB,OAAS0D,MAAME,MAAM,EAAGzC,UAAY,GAAG0C,KAAK,MAAMhO,OAAS,EAAIkJ,KAAK/D,IAAIqG,IAAKyC,SAASjO,aAEtFmK,OAASnL,KAAKgB,cAEXmK,OAGX8B,oBAAoBjN,KAAMmL,OAAQwB,eAC1BuC,UAAY/D,YACT+D,UAAY,GAA6B,MAAxBlP,KAAKkP,UAAY,IACrCA,iBAEGA,UAAY,GAA6B,MAAxBlP,KAAKkP,UAAY,IACrCA,kBAEEN,aAAe5O,KAAKqM,UAAU6C,UAAW/D,YAC1C,IAAI7I,EAAI,EAAGA,EAAIsM,aAAa5N,OAAQsB,IACrCqK,UAAUjK,KAAK,CACXuF,MAAOiH,UAAY5M,EACnB6L,MAAOS,aAAatM,GACpB4F,KAAMpH,KAAKrB,YACXkM,UAAW7K,KAAKrB,YAAc,kBAGjC2O,wBAAwBc,UAAWN,aAAa5N,QAC9C,CAAChB,KAAMA,KAAKqM,UAAU,EAAG6C,WAAalP,KAAKqM,UAAUlB,QAASA,OAAQ+D,WAIjFR,qBAAqB1O,KAAMmL,YAClBnL,MAAQmL,QAAUnL,KAAKgB,cACjBmK,UAEU,MAAjBnL,KAAKmL,aACEA,OAASnL,KAAKgB,QAA2B,MAAjBhB,KAAKmL,SAC/BA,YAGLA,QAAUnL,KAAKgB,OAAQ,KACnBmO,aAAenP,KAAKgB,OAAS,OAC1BmO,cAAgB,GAA4B,MAAvBnP,KAAKmP,eAC5BA,sBAEEA,aAAe,MAEtBR,QAAUxD,YACPwD,QAAU3O,KAAKgB,QAA4B,MAAlBhB,KAAK2O,UAChCA,iBAEEA,QAIXF,yBAAyBzO,KAAMmL,WACvBA,QAAU,SACH,MAEPgB,IAAMhB,OAAS,OACZgB,IAAM,IAAoB,MAAdnM,KAAKmM,MAA8B,OAAdnM,KAAKmM,OACxCA,WAEEA,IAAM,GAAuB,MAAlBnM,KAAKmM,IAAM,IAAgC,OAAlBnM,KAAKmM,IAAM,IACjDA,aAGEA,IAGXiD,YACQtO,KAAK5B,wBACAA,kBAAmB,OAExBmQ,WAAa,QACZtO,QAAQ4C,SAAQpB,QACiB,YAA9BA,MAAMA,MAAMuF,gBACZuH,WAAavO,KAAK+L,SAAStK,MAAMwF,IAAKsH,qBAGzC7O,cAAc8C,UAAY+L,WAAWN,MAAM,GAAI,QAC/ChF,eAAe,KAIxB1D,WAAWyD,kBACDwF,WAAaxO,KAAK5B,sBACnB4E,mBAECyL,WAAczO,KAAKpB,cAAgBoK,WAAc,SAClDrK,YAAc8P,gBACdhQ,kBAAoB,OACpBS,KAAO,QACPV,eAAiB,OACjBF,iBAAmB,QACnBC,aAAe,QACfS,qBAAsB,OACtBD,cAAe,OACfM,YAAc,QACdD,kBAAoB,MACrBF,KAAO,GACPmL,OAAS,EACTuB,WAAa,GACbC,UAAY,GACZ6C,WAAa,MAEZ,IAAIlN,EAAI,EAAGA,EAAIxB,KAAKC,QAAQC,OAAQsB,IAAK,yBACpCC,MAAQzB,KAAKC,QAAQuB,MACvBC,MAAMO,gBAAkBP,MAAMO,eAAiByM,WAAY,MACtDhQ,kBAAoB+C,aAGJgJ,IAArB/I,MAAMgJ,YAAmC,IAANjJ,GAA2B,cAAhBC,MAAMA,OAAyC,YAAhBA,MAAMA,QACnF4I,OAASjB,KAAKhE,IAAI,EAAGgE,KAAK/D,IAAI5D,MAAMgJ,WAAYvL,KAAKgB,UAEtB,mCAA/BuB,MAAMA,oDAAOuF,sBACR5H,kBAAoBsP,WACN,MAAdjN,MAAMwF,KAA6B,MAAdxF,MAAMwF,MAAgBjH,KAAKhB,qBACjD0P,eAEFxP,KAAAA,KAAMmL,OAAAA,OAAQC,kBAAmBsB,WAAYrB,eAAgBsB,WAC3D7L,KAAK0K,oBAAoBjJ,MAAOvC,KAAMmL,OAAQuB,WAAYC,kBAE7DpN,kBAAoB+C,EAAI,OAG5BpC,kBAAoBsP,gBACpBxP,KAAOA,UACPV,eAAiB6L,YACjB/L,iBAAmBsN,WAAWjB,QAAOC,IAAMA,EAAEC,WAAaD,EAAEC,UAAY4D,kBACxElQ,aAAesN,UAAUlB,QAAOG,IAAMA,EAAED,WAAaC,EAAED,UAAY4D,kBACnE1D,kBAAkB/K,KAAKd,KAAMc,KAAKxB,eAAgBwB,KAAK1B,iBAAkB0B,KAAKzB,mBAC9E0K,eAAeD,YAEhBwF,kBACKpQ,kBAAmB,OACnBgM,aAKbW,kBAAkB7L,KAAMV,eAAgBoN,WAAYC,eAC5C5J,KAAO,SACL0M,aAAe,GACfC,YAAc,GACdC,UAAY,GACZlQ,YAAcqB,KAAKrB,YAEzBiN,WAAW/I,SAAQ+H,QACXrC,QAAU,EACVqC,EAAEC,WAAaD,EAAEC,UAAYlM,YAAc,MAC3C4J,QAAUa,KAAKhE,IAAI,GAAIwF,EAAEC,UAAYlM,aAAe,MAExDgQ,aAAa/D,EAAEzD,OAAS,CAACkG,MAAOzC,EAAEyC,MAAO9E,QAAAA,YAG7CsD,UAAUhJ,SAAQiI,QACVvC,QAAU,GACVuC,EAAED,WAAaC,EAAED,UAAYlM,YAAc,MAC3C4J,QAAUa,KAAKhE,IAAI,GAAK0F,EAAED,UAAYlM,aAAe,IAAO,KAEhEiQ,YAAY9D,EAAE3D,OAAS,CAACkG,MAAOvC,EAAEuC,MAAO9E,QAAAA,YAIxCvI,KAAKX,kBACAA,YAAYwD,SAAQ4K,IACjBA,EAAEtG,MAAQjI,KAAKgB,SACf2O,UAAUpB,EAAEtG,QAAS,YAM3B2H,oBAAsBjD,UAAUlB,QAAOG,GAAKA,EAAE3D,OAASjI,KAAKgB,SAC5D6O,UAAY7P,KAAKuM,MAAM,UACzB9H,gBAAkB,MAEjB,IAAI6H,UAAY,EAAGA,UAAYuD,UAAU7O,OAAQsL,YAAa,OACzDwD,KAAOD,UAAUvD,eAClB,IAAIhK,EAAI,EAAGA,EAAIwN,KAAK9O,OAAQsB,IAAK,CAC9BmC,kBAAoBnF,iBACpByD,MAAQ,mDAENgN,KAAOD,KAAKxN,GACdoN,YAAYjL,mBACZ1B,iGACM2M,YAAYjL,iBAAiB4E,sBAAaqG,YAAYjL,iBAAiB0J,wBAE3E6B,SAAWL,UAAUlL,iBACrBwL,cAAgBR,aAAahL,kBAA6B,MAATsL,KAInDhN,MAFAiN,UAAYC,sIAGNR,aAAahL,iBAAiB4E,sBAAa0G,gBAC1CC,0DAEoD,MAATD,KAAe,IAAMjP,KAAKoP,WAAWH,iBAChFE,6GAGDR,aAAahL,iBAAiB4E,sBAAa0G,gBAGhC,MAATA,KAAe,IAAMjP,KAAKoP,WAAWH,MAEjDtL,kBAEAA,kBAAoBnF,iBACpByD,MAAQ,6CAERuJ,UAAYuD,UAAU7O,OAAS,IAC/B+B,MAAQ,OACR0B,sBAIJnF,iBAAmBU,KAAKgB,QAAW+B,KAAKoN,SAAS,+CACjDpN,MAAQ,6CAGR6M,oBAAoB5O,OAAS,EAAG,CAChC4O,oBAAoBQ,MAAK,CAACC,EAAGC,IAAMD,EAAEpI,MAAQqI,EAAErI,cACzCsI,WAAa,4CACbC,UAAYzN,KAAK0J,YAAY8D,gBAChB,IAAfC,UAAkB,KACdC,gBAAkB,iEACtBb,oBAAoBjM,SAAQiI,IACxB6E,iBAAmB7E,EAAEuC,SAEzBsC,iBAAmB,UACnB1N,KAAOA,KAAKsJ,UAAU,EAAGmE,WAAaC,gBAAkB1N,KAAKsJ,UAAUmE,kBAIzEE,oBAAsB5P,KAAKN,cAAcmQ,aAC3C7P,KAAKN,cAAcoQ,cAAgB9P,KAAKN,cAAcqQ,UAAY,OACjErQ,cAAc8C,UAAYP,MAE3B2N,qBAAuB5P,KAAKgQ,gCACvBtQ,cAAcqQ,UAAY/P,KAAKN,cAAcmQ,cAK1DG,8BACUC,cAAgBjQ,KAAKN,cAAc+D,cAAc,yCAClDwM,qBACM,QAGLC,WAAaD,cAAcE,wBAC3BC,WAAapQ,KAAKN,cAAcyQ,+BAE/BD,WAAWG,OAASD,WAAWC,OAG1CjB,WAAWkB,eACAA,OACFC,QAAQ,KAAM,SACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,UACdA,QAAQ,KAAM,UAIvBxE,SAAS9E,YACGA,SACC,cACM,SACN,gBACA,aACA,yBACM,OACN,UACO,kBAEA,CAAC,QAAS,OAAQ,MAAO,YAAa,UAAW,UAAW,aAChE,YAAa,OAAQ,WAAY,MAAO,SAAU,SAAU,SAAU,WACtE,SAAU,OAAQ,MAAO,UAAW,gBAAiB,kBACrD,iBAAkB,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,MACxE,MAAO,MAAO,cAAe,gBAAgByG,SAASzG,KAAa,GAANA"} \ No newline at end of file diff --git a/amd/build/scatter_chart.min.js b/amd/build/scatter_chart.min.js index 4dcd1cc7..4fc870b1 100644 --- a/amd/build/scatter_chart.min.js +++ b/amd/build/scatter_chart.min.js @@ -6,6 +6,6 @@ define("tiny_cursive/scatter_chart",["exports","core/chartjs","core/str"],(funct * @module tiny_cursive/scatter_chart * @copyright 2025 Cursive Technology, Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_chartjs=(obj=_chartjs)&&obj.__esModule?obj:{default:obj};_exports.init=async(data,apiKey,caption)=>{const ctx=document.getElementById("effortScatterChart").getContext("2d");data&&(data=JSON.parse(document.getElementById("scatter-chart-data").dataset.data));let display=!0,isEmpty="";var dataset=[];const[applyFilter,noSubmission,noPayload,freemium]=await(0,_str.get_strings)([{key:"apply_filter",component:"tiny_cursive"},{key:"no_submission",component:"tiny_cursive"},{key:"nopaylod",component:"tiny_cursive"},{key:"freemium",component:"tiny_cursive"},{key:"chart_result",component:"tiny_cursive"}]);Array.isArray(data)&&!data.state&&apiKey&&(dataset=data,isEmpty=data.some((ds=>Array.isArray(ds.data)&&ds.data.some((point=>point&&"object"==typeof point&&Object.keys(point).length>0))))),apiKey&&0!==data.length&&isEmpty&&!1!==data||(display=!1);const fallbackMessagePlugin={id:"fallbackMessagePlugin",afterDraw(chart){apiKey?"apply_filter"!=data.state?"no_submission"!==data.state?isEmpty||data.state||drawMessage("⚠ "+noPayload,chart):drawMessage("⚠ "+noSubmission,chart):drawMessage("⚠ "+applyFilter,chart):drawMessage("⚠ "+freemium,chart)}};function formatTime(value){const minutes=Math.floor(value/60),seconds=value%60;return`${String(minutes).padStart(2,"0")}:${String(seconds).padStart(2,"0")}`}function drawMessage(text,chart){const{ctx:ctx,chartArea:{left:left,right:right,top:top,bottom:bottom}}=chart;ctx.save(),ctx.textAlign="center",ctx.textBaseline="middle",ctx.font='bold 16px "Segoe UI", Arial',ctx.fillStyle="#666";const centerX=(left+right)/2,centerY=(top+bottom)/2;ctx.fillText(text,centerX,centerY),ctx.restore()}new _chartjs.default(ctx,{type:"scatter",data:{datasets:dataset},options:{plugins:{title:{display:display,text:caption,font:{size:16,weight:"bold"},color:"#333",padding:{top:10,bottom:20},align:"center"},legend:{display:!0,position:"bottom",labels:{usePointStyle:!0,pointStyle:"circle",padding:20}},tooltip:{backgroundColor:"rgba(252, 252, 252, 0.8)",titleColor:"#000",bodyColor:"#000",borderColor:"#cccccc",borderWidth:1,displayColors:!1,callbacks:{title:function(context){return context[0].raw.label},label:function(context){const d=context.raw;return[`Time: ${formatTime(d.x)}`,`Effort: ${Math.round(100*d.effort*100)/100}%`,`Words: ${d.words}`,`WPM: ${d.wpm}`]}}}},scales:{x:{title:{display:!0,text:"Time Spent (mm:ss)"},min:0,ticks:{callback:function(value){return formatTime(value)}}},y:{title:{display:!0,text:"Effort Score"},min:0,ticks:{stepSize:.5}}}},plugins:[fallbackMessagePlugin]})}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_chartjs=(obj=_chartjs)&&obj.__esModule?obj:{default:obj};_exports.init=async(data,apiKey,caption)=>{const ctx=document.getElementById("effortScatterChart").getContext("2d");data&&(data=JSON.parse(document.getElementById("scatter-chart-data").dataset.data));let display=!0,isEmpty="";var dataset=[];const[applyFilter,noSubmission,noPayload,freemium]=await(0,_str.get_strings)([{key:"apply_filter",component:"tiny_cursive"},{key:"no_submission",component:"tiny_cursive"},{key:"nopaylod",component:"tiny_cursive"},{key:"freemium",component:"tiny_cursive"},{key:"chart_result",component:"tiny_cursive"}]);Array.isArray(data)&&!data.state&&apiKey&&(dataset=data,isEmpty=data.some((ds=>Array.isArray(ds.data)&&ds.data.some((point=>point&&"object"==typeof point&&Object.keys(point).length>0))))),apiKey&&0!==data.length&&isEmpty&&!1!==data||(display=!1);const fallbackMessagePlugin={id:"fallbackMessagePlugin",afterDraw(chart){apiKey?"apply_filter"!=data.state?"no_submission"!==data.state?isEmpty||data.state||drawMessage("⚠ "+noPayload,chart):drawMessage("⚠ "+noSubmission,chart):drawMessage("⚠ "+applyFilter,chart):drawMessage("⚠ "+freemium,chart)}};function formatTime(value){const minutes=Math.floor(value/60),seconds=value%60;return"".concat(String(minutes).padStart(2,"0"),":").concat(String(seconds).padStart(2,"0"))}function drawMessage(text,chart){const{ctx:ctx,chartArea:{left:left,right:right,top:top,bottom:bottom}}=chart;ctx.save(),ctx.textAlign="center",ctx.textBaseline="middle",ctx.font='bold 16px "Segoe UI", Arial',ctx.fillStyle="#666";const centerX=(left+right)/2,centerY=(top+bottom)/2;ctx.fillText(text,centerX,centerY),ctx.restore()}new _chartjs.default(ctx,{type:"scatter",data:{datasets:dataset},options:{plugins:{title:{display:display,text:caption,font:{size:16,weight:"bold"},color:"#333",padding:{top:10,bottom:20},align:"center"},legend:{display:!0,position:"bottom",labels:{usePointStyle:!0,pointStyle:"circle",padding:20}},tooltip:{backgroundColor:"rgba(252, 252, 252, 0.8)",titleColor:"#000",bodyColor:"#000",borderColor:"#cccccc",borderWidth:1,displayColors:!1,callbacks:{title:function(context){return context[0].raw.label},label:function(context){const d=context.raw;return["Time: ".concat(formatTime(d.x)),"Effort: ".concat(Math.round(100*d.effort*100)/100,"%"),"Words: ".concat(d.words),"WPM: ".concat(d.wpm)]}}}},scales:{x:{title:{display:!0,text:"Time Spent (mm:ss)"},min:0,ticks:{callback:function(value){return formatTime(value)}}},y:{title:{display:!0,text:"Effort Score"},min:0,ticks:{stepSize:.5}}}},plugins:[fallbackMessagePlugin]})}})); //# sourceMappingURL=scatter_chart.min.js.map \ No newline at end of file diff --git a/amd/build/scatter_chart.min.js.map b/amd/build/scatter_chart.min.js.map index 9dcaafb4..9cd01722 100644 --- a/amd/build/scatter_chart.min.js.map +++ b/amd/build/scatter_chart.min.js.map @@ -1 +1 @@ -{"version":3,"file":"scatter_chart.min.js","sources":["../src/scatter_chart.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 * A module that creates a scatter chart to visualize student effort data using Chart.js.\n * The chart displays effort scores against time spent, with tooltips showing additional metrics.\n *\n * @module tiny_cursive/scatter_chart\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Chart from 'core/chartjs';\nimport {get_strings as getStrings} from 'core/str';\nexport const init = async(data, apiKey, caption) => {\n\n const ctx = document.getElementById('effortScatterChart').getContext('2d');\n if (data) {\n data = JSON.parse(document.getElementById('scatter-chart-data').dataset.data);\n }\n\n let display = true;\n let isEmpty = \"\";\n var dataset = [];\n\n const [\n applyFilter,\n noSubmission,\n noPayload,\n freemium,\n ] = await getStrings([\n {key: 'apply_filter', component: 'tiny_cursive'},\n {key: 'no_submission', component: 'tiny_cursive'},\n {key: 'nopaylod', component: 'tiny_cursive'},\n {key: 'freemium', component: 'tiny_cursive'},\n {key: 'chart_result', component: 'tiny_cursive'}\n ]);\n\n if (Array.isArray(data) && !data.state && apiKey) {\n dataset = data;\n isEmpty = data.some(ds =>\n Array.isArray(ds.data) &&\n ds.data.some(point =>\n point && typeof point === 'object' && Object.keys(point).length > 0\n )\n );\n }\n\n if (!apiKey || data.length === 0 || !isEmpty || data === false) {\n display = false;\n }\n\n const fallbackMessagePlugin = {\n id: 'fallbackMessagePlugin',\n afterDraw(chart) {\n // ⚠ Case 1: Freemium user\n if (!apiKey) {\n drawMessage('⚠ ' + freemium, chart);\n return;\n }\n // ⚠ Case 2: Apply filter (data is empty array)\n if (data.state == \"apply_filter\") {\n drawMessage('⚠ ' + applyFilter, chart);\n return;\n }\n if (data.state === \"no_submission\") {\n drawMessage('⚠ ' + noSubmission, chart);\n return;\n }\n // ⚠ Case 3: No payload data (all `data` arrays are empty or full of empty objects)\n if (!isEmpty && !data.state) {\n drawMessage('⚠ ' + noPayload, chart);\n }\n\n }\n };\n\n new Chart(ctx, {\n type: 'scatter',\n data: {\n datasets: dataset,\n },\n options: {\n plugins: {\n title: {\n display: display,\n text: caption,\n font: {\n size: 16,\n weight: 'bold',\n },\n color: '#333',\n padding: {\n top: 10,\n bottom: 20\n },\n align: 'center'\n },\n legend: {\n display: true,\n position: 'bottom',\n labels: {\n usePointStyle: true,\n pointStyle: 'circle',\n padding: 20\n }\n },\n tooltip: {\n backgroundColor: 'rgba(252, 252, 252, 0.8)',\n titleColor: '#000',\n bodyColor: '#000',\n borderColor: '#cccccc',\n borderWidth: 1,\n displayColors: false,\n callbacks: {\n title: function (context) {\n const d = context[0].raw;\n return d.label; // This appears as bold title.\n },\n label: function (context) {\n const d = context.raw;\n return [\n `Time: ${formatTime(d.x)}`,\n `Effort: ${Math.round(d.effort * 100 * 100) / 100}%`,\n `Words: ${d.words}`,\n `WPM: ${d.wpm}`\n ];\n }\n }\n }\n },\n scales: {\n x: {\n title: {\n display: true,\n text: 'Time Spent (mm:ss)'\n },\n min: 0,\n ticks: {\n callback: function (value) {\n return formatTime(value);\n }\n }\n },\n y: {\n title: {\n display: true,\n text: 'Effort Score'\n },\n min: 0,\n ticks: {\n stepSize: 0.5\n }\n }\n }\n },\n plugins: [fallbackMessagePlugin]\n });\n\n /**\n * Formats a time value in seconds to a mm:ss string format\n * @param {number} value - The time value in seconds\n * @returns {string} The formatted time string in mm:ss format\n */\n function formatTime(value) {\n const minutes = Math.floor(value / 60);\n const seconds = value % 60;\n return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;\n }\n\n /**\n * Draws a message on the chart canvas\n * @param {string} text - The message to be displayed\n * @param {Chart} chart - The Chart.js chart object\n */\n function drawMessage(text, chart) {\n\n const {ctx, chartArea: {left, right, top, bottom}} = chart;\n ctx.save();\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.font = 'bold 16px \"Segoe UI\", Arial';\n ctx.fillStyle = '#666';\n\n const centerX = (left + right) / 2;\n const centerY = (top + bottom) / 2;\n\n ctx.fillText(text, centerX, centerY);\n ctx.restore();\n }\n};"],"names":["async","data","apiKey","caption","ctx","document","getElementById","getContext","JSON","parse","dataset","display","isEmpty","applyFilter","noSubmission","noPayload","freemium","key","component","Array","isArray","state","some","ds","point","Object","keys","length","fallbackMessagePlugin","id","afterDraw","chart","drawMessage","formatTime","value","minutes","Math","floor","seconds","String","padStart","text","chartArea","left","right","top","bottom","save","textAlign","textBaseline","font","fillStyle","centerX","centerY","fillText","restore","Chart","type","datasets","options","plugins","title","size","weight","color","padding","align","legend","position","labels","usePointStyle","pointStyle","tooltip","backgroundColor","titleColor","bodyColor","borderColor","borderWidth","displayColors","callbacks","context","raw","label","d","x","round","effort","words","wpm","scales","min","ticks","callback","y","stepSize"],"mappings":";;;;;;;;0JA0BoBA,MAAMC,KAAMC,OAAQC,iBAE9BC,IAAMC,SAASC,eAAe,sBAAsBC,WAAW,MACjEN,OACAA,KAAOO,KAAKC,MAAMJ,SAASC,eAAe,sBAAsBI,QAAQT,WAGxEU,SAAU,EACVC,QAAU,OACVF,QAAU,SAGVG,YACAC,aACAC,UACAC,gBACM,oBAAW,CACjB,CAACC,IAAK,eAAgBC,UAAW,gBACjC,CAACD,IAAK,gBAAiBC,UAAW,gBAClC,CAACD,IAAK,WAAYC,UAAW,gBAC7B,CAACD,IAAK,WAAYC,UAAW,gBAC7B,CAACD,IAAK,eAAgBC,UAAW,kBAGjCC,MAAMC,QAAQnB,QAAUA,KAAKoB,OAASnB,SACtCQ,QAAUT,KACVW,QAAUX,KAAKqB,MAAKC,IAChBJ,MAAMC,QAAQG,GAAGtB,OACjBsB,GAAGtB,KAAKqB,MAAKE,OACTA,OAA0B,iBAAVA,OAAsBC,OAAOC,KAAKF,OAAOG,OAAS,OAKzEzB,QAA0B,IAAhBD,KAAK0B,QAAiBf,UAAoB,IAATX,OAC5CU,SAAU,SAGRiB,sBAAwB,CAC1BC,GAAI,wBACJC,UAAUC,OAED7B,OAKa,gBAAdD,KAAKoB,MAIU,kBAAfpB,KAAKoB,MAKJT,SAAYX,KAAKoB,OAClBW,YAAY,KAAOjB,UAAWgB,OAL9BC,YAAY,KAAOlB,aAAciB,OAJjCC,YAAY,KAAOnB,YAAakB,OALhCC,YAAY,KAAOhB,SAAUe,kBA2GhCE,WAAWC,aACVC,QAAUC,KAAKC,MAAMH,MAAQ,IAC7BI,QAAUJ,MAAQ,SAChB,GAAEK,OAAOJ,SAASK,SAAS,EAAG,QAAQD,OAAOD,SAASE,SAAS,EAAG,gBAQrER,YAAYS,KAAMV,aAEjB3B,IAACA,IAAKsC,WAAWC,KAACA,KAADC,MAAOA,MAAPC,IAAcA,IAAdC,OAAmBA,SAAWf,MACrD3B,IAAI2C,OACJ3C,IAAI4C,UAAY,SAChB5C,IAAI6C,aAAe,SACnB7C,IAAI8C,KAAO,8BACX9C,IAAI+C,UAAY,aAEVC,SAAWT,KAAOC,OAAS,EAC3BS,SAAWR,IAAMC,QAAU,EAEjC1C,IAAIkD,SAASb,KAAMW,QAASC,SAC5BjD,IAAImD,cA/GJC,iBAAMpD,IAAK,CACXqD,KAAM,UACNxD,KAAM,CACFyD,SAAUhD,SAEdiD,QAAS,CACLC,QAAS,CACLC,MAAO,CACHlD,QAASA,QACT8B,KAAMtC,QACN+C,KAAM,CACFY,KAAM,GACNC,OAAQ,QAEZC,MAAO,OACPC,QAAS,CACLpB,IAAK,GACLC,OAAQ,IAEZoB,MAAO,UAEXC,OAAQ,CACJxD,SAAS,EACTyD,SAAU,SACVC,OAAQ,CACJC,eAAe,EACfC,WAAY,SACZN,QAAS,KAGjBO,QAAS,CACLC,gBAAiB,2BACjBC,WAAY,OACZC,UAAW,OACXC,YAAa,UACbC,YAAa,EACbC,eAAe,EACfC,UAAW,CACPlB,MAAO,SAAUmB,gBACHA,QAAQ,GAAGC,IACZC,OAEbA,MAAO,SAAUF,eACPG,EAAIH,QAAQC,UACX,CACF,SAAQhD,WAAWkD,EAAEC,KACrB,WAAUhD,KAAKiD,MAAiB,IAAXF,EAAEG,OAAe,KAAO,OAC7C,UAASH,EAAEI,QACX,QAAOJ,EAAEK,WAM9BC,OAAQ,CACJL,EAAG,CACCvB,MAAO,CACHlD,SAAS,EACT8B,KAAM,sBAEViD,IAAK,EACLC,MAAO,CACHC,SAAU,SAAU1D,cACTD,WAAWC,UAI9B2D,EAAG,CACChC,MAAO,CACHlD,SAAS,EACT8B,KAAM,gBAEViD,IAAK,EACLC,MAAO,CACHG,SAAU,OAK1BlC,QAAS,CAAChC"} \ No newline at end of file +{"version":3,"file":"scatter_chart.min.js","sources":["../src/scatter_chart.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 * A module that creates a scatter chart to visualize student effort data using Chart.js.\n * The chart displays effort scores against time spent, with tooltips showing additional metrics.\n *\n * @module tiny_cursive/scatter_chart\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Chart from 'core/chartjs';\nimport {get_strings as getStrings} from 'core/str';\nexport const init = async(data, apiKey, caption) => {\n\n const ctx = document.getElementById('effortScatterChart').getContext('2d');\n if (data) {\n data = JSON.parse(document.getElementById('scatter-chart-data').dataset.data);\n }\n\n let display = true;\n let isEmpty = \"\";\n var dataset = [];\n\n const [\n applyFilter,\n noSubmission,\n noPayload,\n freemium,\n ] = await getStrings([\n {key: 'apply_filter', component: 'tiny_cursive'},\n {key: 'no_submission', component: 'tiny_cursive'},\n {key: 'nopaylod', component: 'tiny_cursive'},\n {key: 'freemium', component: 'tiny_cursive'},\n {key: 'chart_result', component: 'tiny_cursive'}\n ]);\n\n if (Array.isArray(data) && !data.state && apiKey) {\n dataset = data;\n isEmpty = data.some(ds =>\n Array.isArray(ds.data) &&\n ds.data.some(point =>\n point && typeof point === 'object' && Object.keys(point).length > 0\n )\n );\n }\n\n if (!apiKey || data.length === 0 || !isEmpty || data === false) {\n display = false;\n }\n\n const fallbackMessagePlugin = {\n id: 'fallbackMessagePlugin',\n afterDraw(chart) {\n // ⚠ Case 1: Freemium user\n if (!apiKey) {\n drawMessage('⚠ ' + freemium, chart);\n return;\n }\n // ⚠ Case 2: Apply filter (data is empty array)\n if (data.state == \"apply_filter\") {\n drawMessage('⚠ ' + applyFilter, chart);\n return;\n }\n if (data.state === \"no_submission\") {\n drawMessage('⚠ ' + noSubmission, chart);\n return;\n }\n // ⚠ Case 3: No payload data (all `data` arrays are empty or full of empty objects)\n if (!isEmpty && !data.state) {\n drawMessage('⚠ ' + noPayload, chart);\n }\n\n }\n };\n\n new Chart(ctx, {\n type: 'scatter',\n data: {\n datasets: dataset,\n },\n options: {\n plugins: {\n title: {\n display: display,\n text: caption,\n font: {\n size: 16,\n weight: 'bold',\n },\n color: '#333',\n padding: {\n top: 10,\n bottom: 20\n },\n align: 'center'\n },\n legend: {\n display: true,\n position: 'bottom',\n labels: {\n usePointStyle: true,\n pointStyle: 'circle',\n padding: 20\n }\n },\n tooltip: {\n backgroundColor: 'rgba(252, 252, 252, 0.8)',\n titleColor: '#000',\n bodyColor: '#000',\n borderColor: '#cccccc',\n borderWidth: 1,\n displayColors: false,\n callbacks: {\n title: function (context) {\n const d = context[0].raw;\n return d.label; // This appears as bold title.\n },\n label: function (context) {\n const d = context.raw;\n return [\n `Time: ${formatTime(d.x)}`,\n `Effort: ${Math.round(d.effort * 100 * 100) / 100}%`,\n `Words: ${d.words}`,\n `WPM: ${d.wpm}`\n ];\n }\n }\n }\n },\n scales: {\n x: {\n title: {\n display: true,\n text: 'Time Spent (mm:ss)'\n },\n min: 0,\n ticks: {\n callback: function (value) {\n return formatTime(value);\n }\n }\n },\n y: {\n title: {\n display: true,\n text: 'Effort Score'\n },\n min: 0,\n ticks: {\n stepSize: 0.5\n }\n }\n }\n },\n plugins: [fallbackMessagePlugin]\n });\n\n /**\n * Formats a time value in seconds to a mm:ss string format\n * @param {number} value - The time value in seconds\n * @returns {string} The formatted time string in mm:ss format\n */\n function formatTime(value) {\n const minutes = Math.floor(value / 60);\n const seconds = value % 60;\n return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;\n }\n\n /**\n * Draws a message on the chart canvas\n * @param {string} text - The message to be displayed\n * @param {Chart} chart - The Chart.js chart object\n */\n function drawMessage(text, chart) {\n\n const {ctx, chartArea: {left, right, top, bottom}} = chart;\n ctx.save();\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.font = 'bold 16px \"Segoe UI\", Arial';\n ctx.fillStyle = '#666';\n\n const centerX = (left + right) / 2;\n const centerY = (top + bottom) / 2;\n\n ctx.fillText(text, centerX, centerY);\n ctx.restore();\n }\n};"],"names":["async","data","apiKey","caption","ctx","document","getElementById","getContext","JSON","parse","dataset","display","isEmpty","applyFilter","noSubmission","noPayload","freemium","key","component","Array","isArray","state","some","ds","point","Object","keys","length","fallbackMessagePlugin","id","afterDraw","chart","drawMessage","formatTime","value","minutes","Math","floor","seconds","String","padStart","text","chartArea","left","right","top","bottom","save","textAlign","textBaseline","font","fillStyle","centerX","centerY","fillText","restore","Chart","type","datasets","options","plugins","title","size","weight","color","padding","align","legend","position","labels","usePointStyle","pointStyle","tooltip","backgroundColor","titleColor","bodyColor","borderColor","borderWidth","displayColors","callbacks","context","raw","label","d","x","round","effort","words","wpm","scales","min","ticks","callback","y","stepSize"],"mappings":";;;;;;;;0JA0BoBA,MAAMC,KAAMC,OAAQC,iBAE9BC,IAAMC,SAASC,eAAe,sBAAsBC,WAAW,MACjEN,OACAA,KAAOO,KAAKC,MAAMJ,SAASC,eAAe,sBAAsBI,QAAQT,WAGxEU,SAAU,EACVC,QAAU,OACVF,QAAU,SAGVG,YACAC,aACAC,UACAC,gBACM,oBAAW,CACjB,CAACC,IAAK,eAAgBC,UAAW,gBACjC,CAACD,IAAK,gBAAiBC,UAAW,gBAClC,CAACD,IAAK,WAAYC,UAAW,gBAC7B,CAACD,IAAK,WAAYC,UAAW,gBAC7B,CAACD,IAAK,eAAgBC,UAAW,kBAGjCC,MAAMC,QAAQnB,QAAUA,KAAKoB,OAASnB,SACtCQ,QAAUT,KACVW,QAAUX,KAAKqB,MAAKC,IAChBJ,MAAMC,QAAQG,GAAGtB,OACjBsB,GAAGtB,KAAKqB,MAAKE,OACTA,OAA0B,iBAAVA,OAAsBC,OAAOC,KAAKF,OAAOG,OAAS,OAKzEzB,QAA0B,IAAhBD,KAAK0B,QAAiBf,UAAoB,IAATX,OAC5CU,SAAU,SAGRiB,sBAAwB,CAC1BC,GAAI,wBACJC,UAAUC,OAED7B,OAKa,gBAAdD,KAAKoB,MAIU,kBAAfpB,KAAKoB,MAKJT,SAAYX,KAAKoB,OAClBW,YAAY,KAAOjB,UAAWgB,OAL9BC,YAAY,KAAOlB,aAAciB,OAJjCC,YAAY,KAAOnB,YAAakB,OALhCC,YAAY,KAAOhB,SAAUe,kBA2GhCE,WAAWC,aACVC,QAAUC,KAAKC,MAAMH,MAAQ,IAC7BI,QAAUJ,MAAQ,mBACdK,OAAOJ,SAASK,SAAS,EAAG,iBAAQD,OAAOD,SAASE,SAAS,EAAG,eAQrER,YAAYS,KAAMV,aAEjB3B,IAACA,IAAKsC,WAAWC,KAACA,KAADC,MAAOA,MAAPC,IAAcA,IAAdC,OAAmBA,SAAWf,MACrD3B,IAAI2C,OACJ3C,IAAI4C,UAAY,SAChB5C,IAAI6C,aAAe,SACnB7C,IAAI8C,KAAO,8BACX9C,IAAI+C,UAAY,aAEVC,SAAWT,KAAOC,OAAS,EAC3BS,SAAWR,IAAMC,QAAU,EAEjC1C,IAAIkD,SAASb,KAAMW,QAASC,SAC5BjD,IAAImD,cA/GJC,iBAAMpD,IAAK,CACXqD,KAAM,UACNxD,KAAM,CACFyD,SAAUhD,SAEdiD,QAAS,CACLC,QAAS,CACLC,MAAO,CACHlD,QAASA,QACT8B,KAAMtC,QACN+C,KAAM,CACFY,KAAM,GACNC,OAAQ,QAEZC,MAAO,OACPC,QAAS,CACLpB,IAAK,GACLC,OAAQ,IAEZoB,MAAO,UAEXC,OAAQ,CACJxD,SAAS,EACTyD,SAAU,SACVC,OAAQ,CACJC,eAAe,EACfC,WAAY,SACZN,QAAS,KAGjBO,QAAS,CACLC,gBAAiB,2BACjBC,WAAY,OACZC,UAAW,OACXC,YAAa,UACbC,YAAa,EACbC,eAAe,EACfC,UAAW,CACPlB,MAAO,SAAUmB,gBACHA,QAAQ,GAAGC,IACZC,OAEbA,MAAO,SAAUF,eACPG,EAAIH,QAAQC,UACX,iBACMhD,WAAWkD,EAAEC,sBACXhD,KAAKiD,MAAiB,IAAXF,EAAEG,OAAe,KAAO,0BACpCH,EAAEI,sBACJJ,EAAEK,UAM9BC,OAAQ,CACJL,EAAG,CACCvB,MAAO,CACHlD,SAAS,EACT8B,KAAM,sBAEViD,IAAK,EACLC,MAAO,CACHC,SAAU,SAAU1D,cACTD,WAAWC,UAI9B2D,EAAG,CACChC,MAAO,CACHlD,SAAS,EACT8B,KAAM,gBAEViD,IAAK,EACLC,MAAO,CACHG,SAAU,OAK1BlC,QAAS,CAAChC"} \ No newline at end of file diff --git a/amd/build/texteditor.min.js b/amd/build/texteditor.min.js index d78e256c..3e4318a3 100644 --- a/amd/build/texteditor.min.js +++ b/amd/build/texteditor.min.js @@ -6,6 +6,6 @@ define("tiny_cursive/texteditor",["exports","core/config"],(function(_exports,Co * @copyright 2022 CTI * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -let tinyMCEPromise;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getTinyMCE=_exports.baseUrl=void 0;const baseUrl=`${(Config=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Config)).wwwroot}/lib/editor/tiny/loader.php/${M.cfg.jsrev}`;_exports.baseUrl=baseUrl;_exports.getTinyMCE=()=>tinyMCEPromise||(tinyMCEPromise=new Promise(((resolve,reject)=>{const head=document.querySelector("head");let script=head.querySelector('script[data-tinymce="tinymce"]');script&&resolve(window.tinyMCE),script=document.createElement("script"),script.dataset.tinymce="tinymce",script.src=`${baseUrl}/tinymce.js`,script.async=!0,script.addEventListener("load",(()=>{resolve(window.tinyMCE)}),!1),script.addEventListener("error",(err=>{reject(err)}),!1),head.append(script)})),tinyMCEPromise)})); +let tinyMCEPromise;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getTinyMCE=_exports.baseUrl=void 0,Config=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Config);const baseUrl="".concat(Config.wwwroot,"/lib/editor/tiny/loader.php/").concat(M.cfg.jsrev);_exports.baseUrl=baseUrl;_exports.getTinyMCE=()=>tinyMCEPromise||(tinyMCEPromise=new Promise(((resolve,reject)=>{const head=document.querySelector("head");let script=head.querySelector('script[data-tinymce="tinymce"]');script&&resolve(window.tinyMCE),script=document.createElement("script"),script.dataset.tinymce="tinymce",script.src="".concat(baseUrl,"/tinymce.js"),script.async=!0,script.addEventListener("load",(()=>{resolve(window.tinyMCE)}),!1),script.addEventListener("error",(err=>{reject(err)}),!1),head.append(script)})),tinyMCEPromise)})); //# sourceMappingURL=texteditor.min.js.map \ No newline at end of file diff --git a/amd/build/texteditor.min.js.map b/amd/build/texteditor.min.js.map index 053cad8b..cd6b3f19 100644 --- a/amd/build/texteditor.min.js.map +++ b/amd/build/texteditor.min.js.map @@ -1 +1 @@ -{"version":3,"file":"texteditor.min.js","sources":["../src/texteditor.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 * Tiny Loader for Moodle\n *\n * @module tiny_cursive/texteditor\n * @copyright 2022 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nlet tinyMCEPromise;\n\nimport * as Config from 'core/config';\n\nexport const baseUrl = `${Config.wwwroot}/lib/editor/tiny/loader.php/${M.cfg.jsrev}`;\n\n/**\n * Get the TinyMCE API Object.\n *\n * @returns {Promise} The TinyMCE API Object\n */\nexport const getTinyMCE = () => {\n if (tinyMCEPromise) {\n return tinyMCEPromise;\n }\n\n tinyMCEPromise = new Promise((resolve, reject) => {\n const head = document.querySelector('head');\n let script = head.querySelector('script[data-tinymce=\"tinymce\"]');\n if (script) {\n resolve(window.tinyMCE);\n }\n\n script = document.createElement('script');\n script.dataset.tinymce = 'tinymce';\n script.src = `${baseUrl}/tinymce.js`;\n script.async = true;\n\n script.addEventListener('load', () => {\n resolve(window.tinyMCE);\n }, false);\n\n script.addEventListener('error', (err) => {\n reject(err);\n }, false);\n\n head.append(script);\n });\n\n return tinyMCEPromise;\n\n};\n\n\n"],"names":["tinyMCEPromise","baseUrl","wwwroot","M","cfg","jsrev","Promise","resolve","reject","head","document","querySelector","script","window","tinyMCE","createElement","dataset","tinymce","src","async","addEventListener","err","append"],"mappings":";;;;;;;;IAuBIA,yHAISC,QAAW,iqBAASC,sCAAsCC,EAAEC,IAAIC,qDAOnD,IAClBL,iBAIJA,eAAiB,IAAIM,SAAQ,CAACC,QAASC,gBAC7BC,KAAOC,SAASC,cAAc,YAChCC,OAASH,KAAKE,cAAc,kCAC5BC,QACAL,QAAQM,OAAOC,SAGnBF,OAASF,SAASK,cAAc,UAChCH,OAAOI,QAAQC,QAAU,UACzBL,OAAOM,IAAO,GAAEjB,qBAChBW,OAAOO,OAAQ,EAEfP,OAAOQ,iBAAiB,QAAQ,KAC5Bb,QAAQM,OAAOC,YAChB,GAEHF,OAAOQ,iBAAiB,SAAUC,MAC9Bb,OAAOa,QACR,GAEHZ,KAAKa,OAAOV,WAGTZ"} \ No newline at end of file +{"version":3,"file":"texteditor.min.js","sources":["../src/texteditor.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 * Tiny Loader for Moodle\n *\n * @module tiny_cursive/texteditor\n * @copyright 2022 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nlet tinyMCEPromise;\n\nimport * as Config from 'core/config';\n\nexport const baseUrl = `${Config.wwwroot}/lib/editor/tiny/loader.php/${M.cfg.jsrev}`;\n\n/**\n * Get the TinyMCE API Object.\n *\n * @returns {Promise} The TinyMCE API Object\n */\nexport const getTinyMCE = () => {\n if (tinyMCEPromise) {\n return tinyMCEPromise;\n }\n\n tinyMCEPromise = new Promise((resolve, reject) => {\n const head = document.querySelector('head');\n let script = head.querySelector('script[data-tinymce=\"tinymce\"]');\n if (script) {\n resolve(window.tinyMCE);\n }\n\n script = document.createElement('script');\n script.dataset.tinymce = 'tinymce';\n script.src = `${baseUrl}/tinymce.js`;\n script.async = true;\n\n script.addEventListener('load', () => {\n resolve(window.tinyMCE);\n }, false);\n\n script.addEventListener('error', (err) => {\n reject(err);\n }, false);\n\n head.append(script);\n });\n\n return tinyMCEPromise;\n\n};\n\n\n"],"names":["tinyMCEPromise","baseUrl","Config","wwwroot","M","cfg","jsrev","Promise","resolve","reject","head","document","querySelector","script","window","tinyMCE","createElement","dataset","tinymce","src","async","addEventListener","err","append"],"mappings":";;;;;;;;IAuBIA,qxBAISC,kBAAaC,OAAOC,+CAAsCC,EAAEC,IAAIC,oDAOnD,IAClBN,iBAIJA,eAAiB,IAAIO,SAAQ,CAACC,QAASC,gBAC7BC,KAAOC,SAASC,cAAc,YAChCC,OAASH,KAAKE,cAAc,kCAC5BC,QACAL,QAAQM,OAAOC,SAGnBF,OAASF,SAASK,cAAc,UAChCH,OAAOI,QAAQC,QAAU,UACzBL,OAAOM,cAASlB,uBAChBY,OAAOO,OAAQ,EAEfP,OAAOQ,iBAAiB,QAAQ,KAC5Bb,QAAQM,OAAOC,YAChB,GAEHF,OAAOQ,iBAAiB,SAAUC,MAC9Bb,OAAOa,QACR,GAEHZ,KAAKa,OAAOV,WAGTb"} \ No newline at end of file diff --git a/amd/build/token_approve.min.js b/amd/build/token_approve.min.js index 57a54427..b9173fdd 100644 --- a/amd/build/token_approve.min.js +++ b/amd/build/token_approve.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/token_approve",["core/ajax","core/str"],(function(AJAX,str){var usersTable={init:function(page){str.get_strings([{key:"field_require",component:"tiny_cursive"}]).done((function(){usersTable.getToken(page),usersTable.generateToken()}))},getToken:function(){const approveTokenBtn=document.getElementById("approve_token");approveTokenBtn&&approveTokenBtn.addEventListener("click",(function(){const tokenInput=document.getElementById("id_s_tiny_cursive_secretkey");var token=tokenInput?tokenInput.value:"";AJAX.call([{methodname:"cursive_approve_token",args:{token:token}}])[0].done((function(json){var data=JSON.parse(json),messageAlert="";messageAlert=data.status?""+data.message+"":""+data.message+"";const tokenMessage=document.getElementById("token_message");tokenMessage&&(tokenMessage.innerHTML=messageAlert)}))}))},generateToken(){const generateToken=document.getElementById("generate_cursivetoken"),cursiveDisable=document.getElementById("cursivedisable"),cursiveEnable=document.getElementById("cursiveenable");generateToken&&generateToken.addEventListener("click",(function(e){e.preventDefault();var promise1=AJAX.call([{methodname:"cursive_generate_webtoken",args:[]}]);promise1[0].done((function(data){var messageAlert="";str.get_strings([{key:"webservtokengensucc",component:"tiny_cursive"},{key:"webservtokengenfail",component:"tiny_cursive"}]).then((function(_ref){let[success,fail]=_ref;if(data.token){const cursiveTokenInput=document.getElementById("id_s_tiny_cursive_cursivetoken");cursiveTokenInput&&(cursiveTokenInput.value=data.token),messageAlert=`${success}`}else messageAlert=`${fail}`;const cursiveTokenMsg=document.getElementById("cursivetoken_");return cursiveTokenMsg&&(cursiveTokenMsg.innerHTML=messageAlert,setTimeout((()=>{cursiveTokenMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))})),promise1[0].fail((function(textStatus){var errorMessage="";str.get_string("webservtokenerror","tiny_cursive").then((str=>{errorMessage+=str+" "+textStatus.error+"";const cursiveTokenMsg=document.getElementById("cursivetoken_");return cursiveTokenMsg&&(cursiveTokenMsg.innerHTML=errorMessage,setTimeout((function(){cursiveTokenMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))}))})),cursiveDisable&&cursiveDisable.addEventListener("click",(function(e){e.preventDefault();var promise1=AJAX.call([{methodname:"cursive_disable_all_course",args:{disable:!0}}]);promise1[0].done((function(data){var messageAlert="";str.get_strings([{key:"cursive:dis:succ",component:"tiny_cursive"},{key:"cursive:dis:fail",component:"tiny_cursive"}]).then((function(_ref2){let[success,fail]=_ref2;messageAlert=data?`${success}`:`${fail}`;const cursiveDisableMsg=document.getElementById("cursivedisable_");return cursiveDisableMsg&&(cursiveDisableMsg.innerHTML=messageAlert,setTimeout((()=>{cursiveDisableMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))})),promise1[0].fail((function(textStatus){var errorMessage="";str.get_string("cursive:status","tiny_cursive").then((str=>{errorMessage+=str+" "+textStatus.error+"";const cursiveDisableMsg=document.getElementById("cursivedisable_");return cursiveDisableMsg&&(cursiveDisableMsg.innerHTML=errorMessage,setTimeout((function(){cursiveDisableMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))}))})),cursiveEnable&&cursiveEnable.addEventListener("click",(function(e){e.preventDefault();var promise1=AJAX.call([{methodname:"cursive_disable_all_course",args:{disable:!1}}]);promise1[0].done((function(data){var messageAlert="";str.get_strings([{key:"cursive:ena:succ",component:"tiny_cursive"},{key:"cursive:ena:fail",component:"tiny_cursive"}]).then((function(_ref3){let[success,fail]=_ref3;messageAlert=data?`${success}`:`${fail}`;const cursiveDisableMsg=document.getElementById("cursivedisable_");return cursiveDisableMsg&&(cursiveDisableMsg.innerHTML=messageAlert,setTimeout((()=>{cursiveDisableMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))})),promise1[0].fail((function(textStatus){var errorMessage="";str.get_string("cursive:status","tiny_cursive").then((str=>{errorMessage+=str+" "+textStatus.error+"";const cursiveDisableMsg=document.getElementById("cursivedisable_");return cursiveDisableMsg&&(cursiveDisableMsg.innerHTML=errorMessage,setTimeout((function(){cursiveDisableMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))}))}))}};return usersTable})); +define("tiny_cursive/token_approve",["core/ajax","core/str"],(function(AJAX,str){var usersTable={init:function(page){str.get_strings([{key:"field_require",component:"tiny_cursive"}]).done((function(){usersTable.getToken(page),usersTable.generateToken()}))},getToken:function(){const approveTokenBtn=document.getElementById("approve_token");approveTokenBtn&&approveTokenBtn.addEventListener("click",(function(){const tokenInput=document.getElementById("id_s_tiny_cursive_secretkey");var token=tokenInput?tokenInput.value:"";AJAX.call([{methodname:"cursive_approve_token",args:{token:token}}])[0].done((function(json){var data=JSON.parse(json),messageAlert="";messageAlert=data.status?""+data.message+"":""+data.message+"";const tokenMessage=document.getElementById("token_message");tokenMessage&&(tokenMessage.innerHTML=messageAlert)}))}))},generateToken(){const generateToken=document.getElementById("generate_cursivetoken"),cursiveDisable=document.getElementById("cursivedisable"),cursiveEnable=document.getElementById("cursiveenable");generateToken&&generateToken.addEventListener("click",(function(e){e.preventDefault();var promise1=AJAX.call([{methodname:"cursive_generate_webtoken",args:[]}]);promise1[0].done((function(data){var messageAlert="";str.get_strings([{key:"webservtokengensucc",component:"tiny_cursive"},{key:"webservtokengenfail",component:"tiny_cursive"}]).then((function(_ref){let[success,fail]=_ref;if(data.token){const cursiveTokenInput=document.getElementById("id_s_tiny_cursive_cursivetoken");cursiveTokenInput&&(cursiveTokenInput.value=data.token),messageAlert="".concat(success,"")}else messageAlert="".concat(fail,"");const cursiveTokenMsg=document.getElementById("cursivetoken_");return cursiveTokenMsg&&(cursiveTokenMsg.innerHTML=messageAlert,setTimeout((()=>{cursiveTokenMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))})),promise1[0].fail((function(textStatus){var errorMessage="";str.get_string("webservtokenerror","tiny_cursive").then((str=>{errorMessage+=str+" "+textStatus.error+"";const cursiveTokenMsg=document.getElementById("cursivetoken_");return cursiveTokenMsg&&(cursiveTokenMsg.innerHTML=errorMessage,setTimeout((function(){cursiveTokenMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))}))})),cursiveDisable&&cursiveDisable.addEventListener("click",(function(e){e.preventDefault();var promise1=AJAX.call([{methodname:"cursive_disable_all_course",args:{disable:!0}}]);promise1[0].done((function(data){var messageAlert="";str.get_strings([{key:"cursive:dis:succ",component:"tiny_cursive"},{key:"cursive:dis:fail",component:"tiny_cursive"}]).then((function(_ref2){let[success,fail]=_ref2;messageAlert=data?"".concat(success,""):"".concat(fail,"");const cursiveDisableMsg=document.getElementById("cursivedisable_");return cursiveDisableMsg&&(cursiveDisableMsg.innerHTML=messageAlert,setTimeout((()=>{cursiveDisableMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))})),promise1[0].fail((function(textStatus){var errorMessage="";str.get_string("cursive:status","tiny_cursive").then((str=>{errorMessage+=str+" "+textStatus.error+"";const cursiveDisableMsg=document.getElementById("cursivedisable_");return cursiveDisableMsg&&(cursiveDisableMsg.innerHTML=errorMessage,setTimeout((function(){cursiveDisableMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))}))})),cursiveEnable&&cursiveEnable.addEventListener("click",(function(e){e.preventDefault();var promise1=AJAX.call([{methodname:"cursive_disable_all_course",args:{disable:!1}}]);promise1[0].done((function(data){var messageAlert="";str.get_strings([{key:"cursive:ena:succ",component:"tiny_cursive"},{key:"cursive:ena:fail",component:"tiny_cursive"}]).then((function(_ref3){let[success,fail]=_ref3;messageAlert=data?"".concat(success,""):"".concat(fail,"");const cursiveDisableMsg=document.getElementById("cursivedisable_");return cursiveDisableMsg&&(cursiveDisableMsg.innerHTML=messageAlert,setTimeout((()=>{cursiveDisableMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))})),promise1[0].fail((function(textStatus){var errorMessage="";str.get_string("cursive:status","tiny_cursive").then((str=>{errorMessage+=str+" "+textStatus.error+"";const cursiveDisableMsg=document.getElementById("cursivedisable_");return cursiveDisableMsg&&(cursiveDisableMsg.innerHTML=errorMessage,setTimeout((function(){cursiveDisableMsg.innerHTML=""}),3e3)),!0})).catch((error=>window.console.error(error)))}))}))}};return usersTable})); //# sourceMappingURL=token_approve.min.js.map \ No newline at end of file diff --git a/amd/build/token_approve.min.js.map b/amd/build/token_approve.min.js.map index 5dbc6ad5..ed64cadb 100644 --- a/amd/build/token_approve.min.js.map +++ b/amd/build/token_approve.min.js.map @@ -1 +1 @@ -{"version":3,"file":"token_approve.min.js","sources":["../src/token_approve.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/token_approve\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\ndefine([\"core/ajax\", \"core/str\"], function(AJAX, str) {\n var usersTable = {\n init: function(page) {\n str\n .get_strings([{key: \"field_require\", component: \"tiny_cursive\"}])\n .done(function() {\n usersTable.getToken(page);\n usersTable.generateToken();\n });\n },\n getToken: function() {\n const approveTokenBtn = document.getElementById(\"approve_token\");\n if (approveTokenBtn) {\n approveTokenBtn.addEventListener(\"click\", function() {\n const tokenInput = document.getElementById(\"id_s_tiny_cursive_secretkey\");\n var token = tokenInput ? tokenInput.value : \"\";\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_approve_token\",\n args: {\n token: token,\n },\n },\n ]);\n promise1[0].done(function(json) {\n var data = JSON.parse(json);\n var messageAlert = \"\";\n if (data.status) {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n } else {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n }\n const tokenMessage = document.getElementById(\"token_message\");\n if (tokenMessage) {\n tokenMessage.innerHTML = messageAlert;\n }\n });\n });\n }\n },\n\n generateToken() {\n const generateToken = document.getElementById(\"generate_cursivetoken\");\n const cursiveDisable = document.getElementById(\"cursivedisable\");\n const cursiveEnable = document.getElementById(\"cursiveenable\");\n\n if (generateToken) {\n generateToken.addEventListener(\"click\", function(e) {\n e.preventDefault();\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_generate_webtoken\",\n args: [],\n },\n ]);\n promise1[0].done(function(data) {\n var messageAlert = \"\";\n str.get_strings([\n {key: \"webservtokengensucc\", component: \"tiny_cursive\"},\n {key: \"webservtokengenfail\", component: \"tiny_cursive\"}\n ]).then(function([success, fail]) {\n\n if (data.token) {\n const cursiveTokenInput = document.getElementById(\"id_s_tiny_cursive_cursivetoken\");\n if (cursiveTokenInput) {\n cursiveTokenInput.value = data.token;\n }\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n const cursiveTokenMsg = document.getElementById(\"cursivetoken_\");\n if (cursiveTokenMsg) {\n cursiveTokenMsg.innerHTML = messageAlert;\n setTimeout(() => {\n cursiveTokenMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n promise1[0].fail(function(textStatus) {\n var errorMessage = \"\";\n str\n .get_string(\"webservtokenerror\", \"tiny_cursive\")\n .then((str) => {\n errorMessage += str + \" \" + textStatus.error + \"\";\n\n const cursiveTokenMsg = document.getElementById(\"cursivetoken_\");\n if (cursiveTokenMsg) {\n cursiveTokenMsg.innerHTML = errorMessage;\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n cursiveTokenMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n }\n\n if (cursiveDisable) {\n cursiveDisable.addEventListener(\"click\", function(e) {\n e.preventDefault();\n\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_disable_all_course\",\n args: {\n disable: true,\n },\n },\n ]);\n promise1[0].done(function(data) {\n var messageAlert = \"\";\n str.get_strings([\n {key: \"cursive:dis:succ\", component: \"tiny_cursive\"},\n {key: \"cursive:dis:fail\", component: \"tiny_cursive\"}\n ]).then(function([success, fail]) {\n if (data) {\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n\n const cursiveDisableMsg = document.getElementById(\"cursivedisable_\");\n if (cursiveDisableMsg) {\n cursiveDisableMsg.innerHTML = messageAlert;\n setTimeout(() => {\n cursiveDisableMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n promise1[0].fail(function(textStatus) {\n var errorMessage = \"\";\n str\n .get_string(\"cursive:status\", \"tiny_cursive\")\n .then((str) => {\n errorMessage += str + \" \" + textStatus.error + \"\";\n\n const cursiveDisableMsg = document.getElementById(\"cursivedisable_\");\n if (cursiveDisableMsg) {\n cursiveDisableMsg.innerHTML = errorMessage;\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n cursiveDisableMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n }\n\n if (cursiveEnable) {\n cursiveEnable.addEventListener(\"click\", function(e) {\n e.preventDefault();\n\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_disable_all_course\",\n args: {\n disable: false,\n },\n },\n ]);\n promise1[0].done(function(data) {\n var messageAlert = \"\";\n str.get_strings([\n {key: \"cursive:ena:succ\", component: \"tiny_cursive\"},\n {key: \"cursive:ena:fail\", component: \"tiny_cursive\"}\n ]).then(function([success, fail]) {\n if (data) {\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n\n const cursiveDisableMsg = document.getElementById(\"cursivedisable_\");\n if (cursiveDisableMsg) {\n cursiveDisableMsg.innerHTML = messageAlert;\n setTimeout(() => {\n cursiveDisableMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n promise1[0].fail(function(textStatus) {\n var errorMessage = \"\";\n str.get_string(\"cursive:status\", \"tiny_cursive\")\n .then((str) => {\n errorMessage += str + \" \" + textStatus.error + \"\";\n\n const cursiveDisableMsg = document.getElementById(\"cursivedisable_\");\n if (cursiveDisableMsg) {\n cursiveDisableMsg.innerHTML = errorMessage;\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n cursiveDisableMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n }\n },\n };\n return usersTable;\n});"],"names":["define","AJAX","str","usersTable","init","page","get_strings","key","component","done","getToken","generateToken","approveTokenBtn","document","getElementById","addEventListener","tokenInput","token","value","call","methodname","args","json","data","JSON","parse","messageAlert","status","message","tokenMessage","innerHTML","cursiveDisable","cursiveEnable","e","preventDefault","promise1","then","success","fail","cursiveTokenInput","cursiveTokenMsg","setTimeout","catch","error","window","console","textStatus","errorMessage","get_string","disable","cursiveDisableMsg"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"token_approve.min.js","sources":["../src/token_approve.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/token_approve\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\ndefine([\"core/ajax\", \"core/str\"], function(AJAX, str) {\n var usersTable = {\n init: function(page) {\n str\n .get_strings([{key: \"field_require\", component: \"tiny_cursive\"}])\n .done(function() {\n usersTable.getToken(page);\n usersTable.generateToken();\n });\n },\n getToken: function() {\n const approveTokenBtn = document.getElementById(\"approve_token\");\n if (approveTokenBtn) {\n approveTokenBtn.addEventListener(\"click\", function() {\n const tokenInput = document.getElementById(\"id_s_tiny_cursive_secretkey\");\n var token = tokenInput ? tokenInput.value : \"\";\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_approve_token\",\n args: {\n token: token,\n },\n },\n ]);\n promise1[0].done(function(json) {\n var data = JSON.parse(json);\n var messageAlert = \"\";\n if (data.status) {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n } else {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n }\n const tokenMessage = document.getElementById(\"token_message\");\n if (tokenMessage) {\n tokenMessage.innerHTML = messageAlert;\n }\n });\n });\n }\n },\n\n generateToken() {\n const generateToken = document.getElementById(\"generate_cursivetoken\");\n const cursiveDisable = document.getElementById(\"cursivedisable\");\n const cursiveEnable = document.getElementById(\"cursiveenable\");\n\n if (generateToken) {\n generateToken.addEventListener(\"click\", function(e) {\n e.preventDefault();\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_generate_webtoken\",\n args: [],\n },\n ]);\n promise1[0].done(function(data) {\n var messageAlert = \"\";\n str.get_strings([\n {key: \"webservtokengensucc\", component: \"tiny_cursive\"},\n {key: \"webservtokengenfail\", component: \"tiny_cursive\"}\n ]).then(function([success, fail]) {\n\n if (data.token) {\n const cursiveTokenInput = document.getElementById(\"id_s_tiny_cursive_cursivetoken\");\n if (cursiveTokenInput) {\n cursiveTokenInput.value = data.token;\n }\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n const cursiveTokenMsg = document.getElementById(\"cursivetoken_\");\n if (cursiveTokenMsg) {\n cursiveTokenMsg.innerHTML = messageAlert;\n setTimeout(() => {\n cursiveTokenMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n promise1[0].fail(function(textStatus) {\n var errorMessage = \"\";\n str\n .get_string(\"webservtokenerror\", \"tiny_cursive\")\n .then((str) => {\n errorMessage += str + \" \" + textStatus.error + \"\";\n\n const cursiveTokenMsg = document.getElementById(\"cursivetoken_\");\n if (cursiveTokenMsg) {\n cursiveTokenMsg.innerHTML = errorMessage;\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n cursiveTokenMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n }\n\n if (cursiveDisable) {\n cursiveDisable.addEventListener(\"click\", function(e) {\n e.preventDefault();\n\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_disable_all_course\",\n args: {\n disable: true,\n },\n },\n ]);\n promise1[0].done(function(data) {\n var messageAlert = \"\";\n str.get_strings([\n {key: \"cursive:dis:succ\", component: \"tiny_cursive\"},\n {key: \"cursive:dis:fail\", component: \"tiny_cursive\"}\n ]).then(function([success, fail]) {\n if (data) {\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n\n const cursiveDisableMsg = document.getElementById(\"cursivedisable_\");\n if (cursiveDisableMsg) {\n cursiveDisableMsg.innerHTML = messageAlert;\n setTimeout(() => {\n cursiveDisableMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n promise1[0].fail(function(textStatus) {\n var errorMessage = \"\";\n str\n .get_string(\"cursive:status\", \"tiny_cursive\")\n .then((str) => {\n errorMessage += str + \" \" + textStatus.error + \"\";\n\n const cursiveDisableMsg = document.getElementById(\"cursivedisable_\");\n if (cursiveDisableMsg) {\n cursiveDisableMsg.innerHTML = errorMessage;\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n cursiveDisableMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n }\n\n if (cursiveEnable) {\n cursiveEnable.addEventListener(\"click\", function(e) {\n e.preventDefault();\n\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_disable_all_course\",\n args: {\n disable: false,\n },\n },\n ]);\n promise1[0].done(function(data) {\n var messageAlert = \"\";\n str.get_strings([\n {key: \"cursive:ena:succ\", component: \"tiny_cursive\"},\n {key: \"cursive:ena:fail\", component: \"tiny_cursive\"}\n ]).then(function([success, fail]) {\n if (data) {\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n\n const cursiveDisableMsg = document.getElementById(\"cursivedisable_\");\n if (cursiveDisableMsg) {\n cursiveDisableMsg.innerHTML = messageAlert;\n setTimeout(() => {\n cursiveDisableMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n promise1[0].fail(function(textStatus) {\n var errorMessage = \"\";\n str.get_string(\"cursive:status\", \"tiny_cursive\")\n .then((str) => {\n errorMessage += str + \" \" + textStatus.error + \"\";\n\n const cursiveDisableMsg = document.getElementById(\"cursivedisable_\");\n if (cursiveDisableMsg) {\n cursiveDisableMsg.innerHTML = errorMessage;\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n cursiveDisableMsg.innerHTML = \"\";\n }, 3000);\n }\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n }\n },\n };\n return usersTable;\n});"],"names":["define","AJAX","str","usersTable","init","page","get_strings","key","component","done","getToken","generateToken","approveTokenBtn","document","getElementById","addEventListener","tokenInput","token","value","call","methodname","args","json","data","JSON","parse","messageAlert","status","message","tokenMessage","innerHTML","cursiveDisable","cursiveEnable","e","preventDefault","promise1","then","success","fail","cursiveTokenInput","cursiveTokenMsg","setTimeout","catch","error","window","console","textStatus","errorMessage","get_string","disable","cursiveDisableMsg"],"mappings":"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"} \ No newline at end of file diff --git a/db/install.xml b/db/install.xml index f0edcf5d..f8c3f3d6 100644 --- a/db/install.xml +++ b/db/install.xml @@ -20,6 +20,13 @@ + + + + + + + @@ -36,6 +43,12 @@ + + + + + +
@@ -56,6 +69,9 @@ + + +
@@ -69,6 +85,9 @@ + + +
diff --git a/db/upgrade.php b/db/upgrade.php index 018dbf57..8ad9f528 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -101,5 +101,97 @@ function xmldb_tiny_cursive_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2024060315, 'tiny', 'cursive'); } + if ($oldversion < 2025101001) { + + $table = new xmldb_table('tiny_cursive_files'); + + // Composite index for the most common query pattern (non-quiz modules). + $index = new xmldb_index('idx_files_lookup', XMLDB_INDEX_NOTUNIQUE, + ['cmid', 'modulename', 'resourceid', 'userid']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Composite index for quiz queries that include questionid. + $index = new xmldb_index('idx_files_quiz_lookup', XMLDB_INDEX_NOTUNIQUE, + ['cmid', 'modulename', 'resourceid', 'userid', 'questionid']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Supporting index for user-level queries. + $index = new xmldb_index('idx_files_userid', XMLDB_INDEX_NOTUNIQUE, ['userid']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Supporting index for course-level queries. + $index = new xmldb_index('idx_files_courseid', XMLDB_INDEX_NOTUNIQUE, ['courseid']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Index for upload status queries. + $index = new xmldb_index('idx_files_uploaded', XMLDB_INDEX_NOTUNIQUE, ['uploaded']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + $table = new xmldb_table('tiny_cursive_comments'); + + // Composite index for the most common query pattern (non-quiz modules). + $index = new xmldb_index('idx_comments_lookup', XMLDB_INDEX_NOTUNIQUE, + ['cmid', 'modulename', 'resourceid', 'userid']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Composite index for quiz queries that include questionid. + $index = new xmldb_index('idx_comments_quiz_lookup', XMLDB_INDEX_NOTUNIQUE, + ['cmid', 'modulename', 'resourceid', 'userid', 'questionid']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Supporting index for user-level queries. + $index = new xmldb_index('idx_comments_userid', XMLDB_INDEX_NOTUNIQUE, ['userid']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Supporting index for course-level queries. + $index = new xmldb_index('idx_comments_courseid', XMLDB_INDEX_NOTUNIQUE, ['courseid']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + $table = new xmldb_table('tiny_cursive_user_writing'); + + // Index for efficient joins with tiny_cursive_files. + $index = new xmldb_index('idx_user_writing_fileid', XMLDB_INDEX_NOTUNIQUE, ['file_id']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + $table = new xmldb_table('tiny_cursive_writing_diff'); + + // Index for efficient joins with tiny_cursive_files. + $index = new xmldb_index('idx_writing_diff_fileid', XMLDB_INDEX_NOTUNIQUE, ['file_id']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + $table = new xmldb_table('tiny_cursive_quality_metrics'); + + // Index for efficient joins with tiny_cursive_files. + $index = new xmldb_index('idx_quality_metrics_fileid', XMLDB_INDEX_NOTUNIQUE, ['file_id']); + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Savepoint reached. + upgrade_plugin_savepoint(true, 2025101001, 'tiny', 'cursive'); + } + return true; } diff --git a/version.php b/version.php index e76fad16..52d4fa82 100644 --- a/version.php +++ b/version.php @@ -29,6 +29,6 @@ $plugin->component = 'tiny_cursive'; $plugin->release = '2.0.0'; -$plugin->version = 2025101000; +$plugin->version = 2025101001; $plugin->requires = 2022041912; $plugin->maturity = MATURITY_STABLE;