From 8b275778ba140d4ec01f088ecfc426293a0a3296 Mon Sep 17 00:00:00 2001 From: Shahriar075 Date: Tue, 27 Jan 2026 13:21:37 +0600 Subject: [PATCH 1/5] Fix switched to advanced mode for teacher --- 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/append_fourm_post.min.js | 2 +- amd/build/append_fourm_post.min.js.map | 2 +- amd/build/append_lesson_grade_table.min.js | 2 +- .../append_lesson_grade_table.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/cursive_autosave.min.js | 9 +-------- amd/build/cursive_autosave.min.js.map | 2 +- amd/build/document_view.min.js | 2 +- amd/build/document_view.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/svg_repo.min.js | 10 +--------- amd/build/svg_repo.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 +- amd/src/append_fourm_post.js | 20 ++++++++++++++++++- 27 files changed, 45 insertions(+), 49 deletions(-) diff --git a/amd/build/analytic_events.min.js b/amd/build/analytic_events.min.js index 7a33c868..a771d356 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=_interopRequireDefault(_analytic_modal),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates);return _exports.default=class{constructor(){(0,_str.get_string)("notenoughtinfo","tiny_cursive").then((str=>(localStorage.setItem("notenoughtinfo",str),str))).catch((error=>window.console.log(error)))}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 self=this;(0,_jquery.default)("#analytics"+userid+questionid).on("click",(function(e){e.preventDefault();const isReplayButton=(0,_jquery.default)(this).find(".tiny_cursive-replay-button").length>0;_analytic_modal.default.create({templateContext:context}).then((modal=>{(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),modal.show(),isReplayButton&&setTimeout((()=>{(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active");const replayTab=(0,_jquery.default)("#rep"+userid+questionid);replayTab.length&&(replayTab.trigger("click"),replayTab.addClass("active"))}),50);let moreBtn=(0,_jquery.default)("body #more"+userid+questionid);return moreBtn.length>0&&((0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)("#analytic"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#diff"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#analytic"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),(0,_jquery.default)("#diff"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),moreBtn.on("click",(function(e){e.preventDefault(),self.learnMore((0,_jquery.default)(this),context,userid,questionid,replayInstances)}))),!0})).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;(0,_jquery.default)("body").on("click","#analytic"+userid+questionid,(function(e){(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","analytics"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","analytics"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),templates.render("tiny_cursive/analytics_table",context).then((function(html){return(0,_jquery.default)("#content"+userid).html(html),(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),!0})).fail((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))),(0,_jquery.default)("body").on("click","#diff"+userid+questionid,(function(e){if((0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","diff"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","diff"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),!fileid)throw(0,_jquery.default)("#content"+userid).html(nodata),new Error("Missing file id or Difference Content not received yet");(0,_ajax.call)([{methodname:"cursive_get_writing_differences",args:{fileid:fileid}}])[0].done((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"}]).done((strings=>{const originalTextString=strings[0],editsPastesAIString=strings[1],commentBox=(0,_jquery.default)('
');var pasteCountDiv=(0,_jquery.default)("
");(0,_str.get_string)("pastecount","tiny_cursive").then((str=>(pasteCountDiv.append("
"+str+" : "+responsedata.commentscount+"
"),!0))).catch((error=>window.console.log(error)));var commentsDiv=(0,_jquery.default)('
');(0,_str.get_string)("comments","tiny_cursive").then((str=>(commentsDiv.append(""+str+""),!0))).catch((error=>window.console.error(error)));var commentsList=(0,_jquery.default)("
");let comments=responsedata.comments;for(let index in comments){var commentDiv=(0,_jquery.default)('
').text(comments[index].usercomment);commentsList.append(commentDiv)}commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);const $legend=(0,_jquery.default)('
'),$attributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$attributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box attributed"}),$attributedText=(0,_jquery.default)("").text(originalTextString);$attributedItem.append($attributedBox).append($attributedText);const $unattributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$unattributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box tiny_cursive_added"}),$unattributedText=(0,_jquery.default)("").text(editsPastesAIString);$unattributedItem.append($unattributedBox).append($unattributedText),$legend.append($attributedItem).append($unattributedItem);let contents=(0,_jquery.default)("
").addClass("tiny_cursive-comparison-content"),textBlock2=(0,_jquery.default)("
").addClass("tiny_cursive-text-block").append((0,_jquery.default)("
").attr("id","tiny_cursive-reconstructed_text").html(JSON.parse(submittedText)));contents.append(commentBox,$legend,textBlock2),(0,_jquery.default)("#content"+userid).html(contents)})).fail((error=>{window.console.error("Failed to load language strings:",error),(0,_jquery.default)("#content"+userid).html(nodata)}))}else(0,_jquery.default)("#content"+userid).html(nodata)})).fail((error=>{throw(0,_jquery.default)("#content"+userid).html(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;(0,_jquery.default)("body").on("click","#rep"+userid+questionid,(function(e){filepath&&((0,_jquery.default)("#replayControls_"+userid+questionid).removeClass("d-none"),(0,_jquery.default)("#content"+userid).addClass("tiny_cursive_outputElement")),(0,_jquery.default)(this).prop("disabled",!0),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","replay"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"block","padding-right":"8px"}),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),questionid?video_playback(userid,filepath,questionid):video_playback(userid,filepath)}))}learnMore(moreBtn,context,userid,questionid,replayInstances){(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),moreBtn.addClass("active"),(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)("#content"+userid+questionid).removeClass("tiny_cursive_outputElement"),(0,_jquery.default)("#replayControls_"+userid+questionid).addClass("d-none"),_templates.default.render("tiny_cursive/learn_more",context).then((function(html){return(0,_jquery.default)("#content"+userid+questionid).html(html),!0})).fail((function(error){window.console.error("Failed to render template:",error)}))}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";return 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"),score").addClass(icon).attr("style",color).attr("title",localStorage.getItem("notenoughtinfo"))):(0,_jquery.default)("").addClass(icon).attr("style",color)}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_analytic_modal=_interopRequireDefault(_analytic_modal),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates);return _exports.default=class{constructor(){(0,_str.get_string)("notenoughtinfo","tiny_cursive").then((str=>(localStorage.setItem("notenoughtinfo",str),str))).catch((error=>window.console.log(error)))}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 self=this;(0,_jquery.default)("#analytics"+userid+questionid).on("click",(function(e){e.preventDefault();const isReplayButton=(0,_jquery.default)(this).find(".tiny_cursive-replay-button").length>0;_analytic_modal.default.create({templateContext:context}).then((modal=>{(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),modal.show(),isReplayButton&&setTimeout((()=>{(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active");const replayTab=(0,_jquery.default)("#rep"+userid+questionid);replayTab.length&&(replayTab.trigger("click"),replayTab.addClass("active"))}),50);let moreBtn=(0,_jquery.default)("body #more"+userid+questionid);return moreBtn.length>0&&((0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)("#analytic"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#diff"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#analytic"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),(0,_jquery.default)("#diff"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),moreBtn.on("click",(function(e){e.preventDefault(),self.learnMore((0,_jquery.default)(this),context,userid,questionid,replayInstances)}))),!0})).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;(0,_jquery.default)("body").on("click","#analytic"+userid+questionid,(function(e){(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","analytics"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","analytics"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),templates.render("tiny_cursive/analytics_table",context).then((function(html){return(0,_jquery.default)("#content"+userid).html(html),(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),!0})).fail((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))),(0,_jquery.default)("body").on("click","#diff"+userid+questionid,(function(e){if((0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","diff"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","diff"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),!fileid)throw(0,_jquery.default)("#content"+userid).html(nodata),new Error("Missing file id or Difference Content not received yet");(0,_ajax.call)([{methodname:"cursive_get_writing_differences",args:{fileid:fileid}}])[0].done((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"}]).done((strings=>{const originalTextString=strings[0],editsPastesAIString=strings[1],commentBox=(0,_jquery.default)('
');var pasteCountDiv=(0,_jquery.default)("
");(0,_str.get_string)("pastecount","tiny_cursive").then((str=>(pasteCountDiv.append("
"+str+" : "+responsedata.commentscount+"
"),!0))).catch((error=>window.console.log(error)));var commentsDiv=(0,_jquery.default)('
');(0,_str.get_string)("comments","tiny_cursive").then((str=>(commentsDiv.append(""+str+""),!0))).catch((error=>window.console.error(error)));var commentsList=(0,_jquery.default)("
");let comments=responsedata.comments;for(let index in comments){var commentDiv=(0,_jquery.default)('
').text(comments[index].usercomment);commentsList.append(commentDiv)}commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);const $legend=(0,_jquery.default)('
'),$attributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$attributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box attributed"}),$attributedText=(0,_jquery.default)("").text(originalTextString);$attributedItem.append($attributedBox).append($attributedText);const $unattributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$unattributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box tiny_cursive_added"}),$unattributedText=(0,_jquery.default)("").text(editsPastesAIString);$unattributedItem.append($unattributedBox).append($unattributedText),$legend.append($attributedItem).append($unattributedItem);let contents=(0,_jquery.default)("
").addClass("tiny_cursive-comparison-content"),textBlock2=(0,_jquery.default)("
").addClass("tiny_cursive-text-block").append((0,_jquery.default)("
").attr("id","tiny_cursive-reconstructed_text").html(JSON.parse(submittedText)));contents.append(commentBox,$legend,textBlock2),(0,_jquery.default)("#content"+userid).html(contents)})).fail((error=>{window.console.error("Failed to load language strings:",error),(0,_jquery.default)("#content"+userid).html(nodata)}))}else(0,_jquery.default)("#content"+userid).html(nodata)})).fail((error=>{throw(0,_jquery.default)("#content"+userid).html(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;(0,_jquery.default)("body").on("click","#rep"+userid+questionid,(function(e){filepath&&((0,_jquery.default)("#replayControls_"+userid+questionid).removeClass("d-none"),(0,_jquery.default)("#content"+userid).addClass("tiny_cursive_outputElement")),(0,_jquery.default)(this).prop("disabled",!0),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","replay"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"block","padding-right":"8px"}),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),questionid?video_playback(userid,filepath,questionid):video_playback(userid,filepath)}))}learnMore(moreBtn,context,userid,questionid,replayInstances){(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),moreBtn.addClass("active"),(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)("#content"+userid+questionid).removeClass("tiny_cursive_outputElement"),(0,_jquery.default)("#replayControls_"+userid+questionid).addClass("d-none"),_templates.default.render("tiny_cursive/learn_more",context).then((function(html){return(0,_jquery.default)("#content"+userid+questionid).html(html),!0})).fail((function(error){window.console.error("Failed to render template:",error)}))}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";return 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"),score").addClass(icon).attr("style",color).attr("title",localStorage.getItem("notenoughtinfo"))):(0,_jquery.default)("").addClass(icon).attr("style",color)}},_exports.default})); //# sourceMappingURL=analytic_events.min.js.map \ No newline at end of file diff --git a/amd/build/analytic_events.min.js.map b/amd/build/analytic_events.min.js.map index 321173da..536eeb19 100644 --- a/amd/build/analytic_events.min.js.map +++ b/amd/build/analytic_events.min.js.map @@ -1 +1 @@ -{"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 and showing quality metrics.\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 $ from 'jquery';\nimport {get_string as getString} from 'core/str';\nimport {get_strings as getStrings} from 'core/str';\nimport template from 'core/templates';\n\nexport default class AnalyticEvents {\n\n constructor() {\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\n localStorage.setItem('notenoughtinfo', str);\n return str;\n }).catch(error => window.console.log(error));\n }\n\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\n const self = this;\n $('#analytics' + userid + questionid).on('click', function(e) {\n e.preventDefault();\n\n const isReplayButton = $(this).find('.tiny_cursive-replay-button').length > 0;\n // Create Moodle modal\n myModal.create({templateContext: context}).then(modal => {\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n modal.show();\n\n if (isReplayButton) {\n setTimeout(() => {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n\n const replayTab = $('#rep' + userid + questionid);\n if (replayTab.length) {\n replayTab.trigger('click');\n replayTab.addClass('active');\n }\n }, 50);\n }\n\n let moreBtn = $('body #more' + userid + questionid);\n if (moreBtn.length > 0) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('#analytic' + userid + questionid).prop('disabled', true);\n $('#diff' + userid + questionid).prop('disabled', true);\n $('#analytic' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n $('#diff' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n moreBtn.on('click', function(e) {\n e.preventDefault();\n self.learnMore($(this), context, userid, questionid, replayInstances);\n });\n }\n\n return true;\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\n $('body').on('click', '#analytic' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'analytics');\n $('#player_' + userid + questionid).css({'display': 'none'});\n $('#content' + userid).removeClass('tiny_cursive_outputElement')\n .addClass('tiny_cursive').attr('data-label', 'analytics');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n\n templates.render('tiny_cursive/analytics_table', context).then(function(html) {\n $('#content' + userid).html(html);\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 getString('nopaylod', 'tiny_cursive').then(str => {\n nodata.textContent = str;\n return true;\n }).catch(error => window.console.log(error));\n $('body').on('click', '#diff' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'diff');\n $('#player_' + userid + questionid).css({\n 'display': 'none'\n });\n $('#content' + userid).removeClass('tiny_cursive_outputElement').addClass('tiny_cursive').attr('data-label', 'diff');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active');\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n if (!fileid) {\n $('#content' + userid).html(nodata);\n throw new Error('Missing file id or Difference Content not received yet');\n }\n getContent([{\n methodname: 'cursive_get_writing_differences',\n args: {fileid: fileid},\n }])[0].done(response => {\n let responsedata = JSON.parse(response.data);\n if (responsedata) {\n let submittedText = atob(responsedata.submitted_text);\n\n // Fetch the dynamic strings.\n getStrings([\n {key: 'original_text', component: 'tiny_cursive'},\n {key: 'editspastesai', component: 'tiny_cursive'}\n ]).done(strings => {\n const originalTextString = strings[0];\n const editsPastesAIString = strings[1];\n\n const commentBox = $('
');\n var pasteCountDiv = $('
');\n getString('pastecount', 'tiny_cursive').then(str => {\n pasteCountDiv.append('
' + str + ' : ' + responsedata.commentscount + '
');\n return true;\n }).catch(error => window.console.log(error));\n\n var commentsDiv = $('
');\n getString('comments', 'tiny_cursive').then(str => {\n commentsDiv.append('' + str + '');\n return true;\n }).catch(error => window.console.error(error));\n\n var commentsList = $('
');\n\n let comments = responsedata.comments;\n for (let index in comments) {\n var commentDiv = $(`
`).text(comments[index].usercomment);\n commentsList.append(commentDiv);\n }\n commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);\n\n const $legend = $('
');\n\n // Create the first legend item\n const $attributedItem = $('
', {\"class\": \"tiny_cursive-legend-item\"});\n const $attributedBox = $('
', {\"class\": \"tiny_cursive-box attributed\"});\n const $attributedText = $('').text(originalTextString);\n $attributedItem.append($attributedBox).append($attributedText);\n\n // Create the second legend item\n const $unattributedItem = $('
', {\"class\": 'tiny_cursive-legend-item'});\n const $unattributedBox = $('
', {\"class\": 'tiny_cursive-box tiny_cursive_added'});\n const $unattributedText = $('').text(editsPastesAIString);\n $unattributedItem.append($unattributedBox).append($unattributedText);\n\n // Append the legend items to the legend container.\n $legend.append($attributedItem).append($unattributedItem);\n\n let contents = $('
').addClass('tiny_cursive-comparison-content');\n let textBlock2 = $('
').addClass('tiny_cursive-text-block').append(\n $('
').attr('id', 'tiny_cursive-reconstructed_text').html(JSON.parse(submittedText))\n );\n\n contents.append(commentBox, $legend, textBlock2);\n $('#content' + userid).html(contents); // Update content.\n }).fail(error => {\n window.console.error(\"Failed to load language strings:\", error);\n $('#content' + userid).html(nodata);\n });\n } else {\n $('#content' + userid).html(nodata);\n }\n }).fail(error => {\n $('#content' + userid).html(nodata);\n throw new Error('Error loading JSON file: ' + error.message);\n });\n });\n }\n\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\n $('body').on('click', '#rep' + userid + questionid, function(e) {\n\n if (filepath) {\n $('#replayControls_' + userid + questionid).removeClass('d-none');\n $('#content' + userid).addClass('tiny_cursive_outputElement');\n }\n\n $(this).prop('disabled', true);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'replay');\n $('#player_' + userid + questionid).css({\n 'display': 'block',\n 'padding-right': '8px'\n });\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\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 learnMore(moreBtn, context, userid, questionid, replayInstances) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n moreBtn.addClass('active');\n $('#rep' + userid + questionid).prop('disabled', false);\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('#content' + userid + questionid).removeClass('tiny_cursive_outputElement');\n $('#replayControls_' + userid + questionid).addClass('d-none');\n template.render('tiny_cursive/learn_more', context).then(function(html) {\n $('#content' + userid + questionid).html(html);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 if (score < scoreSetting) {\n icon = 'fa fa-question-circle';\n color = 'font-size:32px;color:#A9A9A9';\n return $('').addClass(icon).attr('style', color).attr('title', localStorage.getItem('notenoughtinfo'));\n } else {\n return $('').addClass(icon).attr('style', color);\n }\n\n }\n}\n"],"names":["constructor","then","str","localStorage","setItem","catch","error","window","console","log","createModal","userid","context","questionid","replayInstances","authIcon","self","this","on","e","preventDefault","isReplayButton","find","length","create","templateContext","modal","html","show","setTimeout","removeClass","replayTab","trigger","addClass","moreBtn","prop","css","learnMore","analytics","templates","attr","append","stopReplay","render","fail","checkDiff","fileid","nodata","document","createElement","classList","add","textContent","Error","methodname","args","done","response","responsedata","JSON","parse","data","submittedText","atob","submitted_text","key","component","strings","originalTextString","editsPastesAIString","commentBox","pasteCountDiv","commentscount","commentsDiv","commentsList","comments","index","commentDiv","text","usercomment","$legend","$attributedItem","$attributedBox","$attributedText","$unattributedItem","$unattributedBox","$unattributedText","contents","textBlock2","message","replyWriting","filepath","video_playback","formatedTime","total_time_seconds","totalTimeSeconds","Math","floor","toString","padStart","authorshipStatus","firstFile","score","scoreSetting","icon","color","parseFloat","getItem"],"mappings":";;;;;;;;;iQAkCIA,kCACc,iBAAkB,gBAAgBC,MAAKC,MAC7CC,aAAaC,QAAQ,iBAAkBF,KAChCA,OACRG,OAAMC,OAASC,OAAOC,QAAQC,IAAIH,SAGzCI,YAAYC,OAAQC,aAASC,kEAAa,GAAIC,uEAAkB,KAAMC,sDAC5DC,KAAOC,yBACX,aAAeN,OAASE,YAAYK,GAAG,SAAS,SAASC,GACvDA,EAAEC,uBAEIC,gBAAiB,mBAAEJ,MAAMK,KAAK,+BAA+BC,OAAS,0BAEpEC,OAAO,CAACC,gBAAiBb,UAAUX,MAAKyB,4BAC1C,WAAaf,OAAS,8DAA8DgB,KAAKZ,UAC3FW,MAAME,OAEFP,gBACAQ,YAAW,yBACL,yBAAyBP,KAAK,WAAWQ,YAAY,gBAEjDC,WAAY,mBAAE,OAASpB,OAASE,YAClCkB,UAAUR,SACVQ,UAAUC,QAAQ,SAClBD,UAAUE,SAAS,aAExB,QAGHC,SAAU,mBAAE,aAAevB,OAASE,mBACpCqB,QAAQX,OAAS,wBACf,yBAAyBD,KAAK,WAAWQ,YAAY,8BACrD,YAAcnB,OAASE,YAAYsB,KAAK,YAAY,uBACpD,QAAUxB,OAASE,YAAYsB,KAAK,YAAY,uBAChD,YAAcxB,OAASE,YAAYuB,IAAI,oBACb,oCACV,oCAEhB,QAAUzB,OAASE,YAAYuB,IAAI,oBACT,oCACV,gBAElBF,QAAQhB,GAAG,SAAS,SAASC,GACzBA,EAAEC,iBACFJ,KAAKqB,WAAU,mBAAEpB,MAAOL,QAASD,OAAQE,WAAYC,sBAItD,KACRT,OAAMC,QACLC,OAAOC,QAAQF,MAAM,0BAA2BA,aAM5DgC,UAAU3B,OAAQ4B,UAAW3B,aAASC,kEAAa,GAAIC,uEAAkB,KAAMC,oEAEzE,QAAQG,GAAG,QAAS,YAAcP,OAASE,YAAY,SAASM,uBAC5D,OAASR,OAASE,YAAYsB,KAAK,YAAY,uBAC/C,WAAaxB,OAASE,YAAYsB,KAAK,YAAY,uBACnD,WAAaxB,QAAQ6B,KAAK,aAAc,iCACxC,WAAa7B,OAASE,YAAYuB,IAAI,SAAY,6BAClD,WAAazB,QAAQmB,YAAY,8BACZG,SAAS,gBAAgBO,KAAK,aAAc,aACnErB,EAAEC,qCACA,WAAaT,QAAQgB,MAAK,mBAAE,SAASM,SAAS,sCAC3CQ,QAAO,mBAAE,SAASR,SAAS,yBAC5BnB,iBAAmBA,gBAAgBH,SACnCG,gBAAgBH,QAAQ+B,iCAE1B,yBAAyBpB,KAAK,WAAWQ,YAAY,8BACrDb,MAAMgB,SAAS,UAEjBM,UAAUI,OAAO,+BAAgC/B,SAASX,MAAK,SAAS0B,gCAClE,WAAahB,QAAQgB,KAAKA,0BAC1B,WAAahB,OAAS,8DAA8DgB,KAAKZ,WACpF,KACR6B,MAAK,SAAStC,OACbC,OAAOC,QAAQF,MAAM,6BAA8BA,aAK/DuC,UAAUlC,OAAQmC,YAAQjC,kEAAa,GAAIC,uEAAkB,WACnDiC,OAASC,SAASC,cAAc,KACtCF,OAAOG,UAAUC,IAAI,yBAA0B,gCACrC,WAAY,gBAAgBlD,MAAKC,MACvC6C,OAAOK,YAAclD,KACd,KACRG,OAAMC,OAASC,OAAOC,QAAQC,IAAIH,6BACnC,QAAQY,GAAG,QAAS,QAAUP,OAASE,YAAY,SAASM,0BACxD,OAASR,OAASE,YAAYsB,KAAK,YAAY,uBAC/C,WAAaxB,OAASE,YAAYsB,KAAK,YAAY,uBACnD,WAAaxB,QAAQ6B,KAAK,aAAc,4BACxC,WAAa7B,OAASE,YAAYuB,IAAI,SACzB,6BAEb,WAAazB,QAAQmB,YAAY,8BAA8BG,SAAS,gBAAgBO,KAAK,aAAc,QAC7GrB,EAAEC,qCACA,WAAaT,QAAQgB,MAAK,mBAAE,SAASM,SAAS,sCAC3CQ,QAAO,mBAAE,SAASR,SAAS,6CAC9B,yBAAyBX,KAAK,WAAWQ,YAAY,8BACrDb,MAAMgB,SAAS,UACbnB,iBAAmBA,gBAAgBH,SACnCG,gBAAgBH,QAAQ+B,cAEvBI,gCACC,WAAanC,QAAQgB,KAAKoB,QACtB,IAAIM,MAAM,yEAET,CAAC,CACRC,WAAY,kCACZC,KAAM,CAACT,OAAQA,WACf,GAAGU,MAAKC,eACJC,aAAeC,KAAKC,MAAMH,SAASI,SACnCH,aAAc,KACVI,cAAgBC,KAAKL,aAAaM,qCAG3B,CACP,CAACC,IAAK,gBAAiBC,UAAW,gBAClC,CAACD,IAAK,gBAAiBC,UAAW,kBACnCV,MAAKW,gBACEC,mBAAqBD,QAAQ,GAC7BE,oBAAsBF,QAAQ,GAE9BG,YAAa,mBAAE,6CACjBC,eAAgB,mBAAE,mCACZ,aAAc,gBAAgBtE,MAAKC,MACzCqE,cAAc9B,OAAO,gBAAkBvC,IAAM,eAAiBwD,aAAac,cAAgB,WACpF,KACRnE,OAAMC,OAASC,OAAOC,QAAQC,IAAIH,aAEjCmE,aAAc,mBAAE,yDACV,WAAY,gBAAgBxE,MAAKC,MACvCuE,YAAYhC,OAAO,WAAavC,IAAM,cAC/B,KACRG,OAAMC,OAASC,OAAOC,QAAQF,MAAMA,aAEnCoE,cAAe,mBAAE,mBAEjBC,SAAWjB,aAAaiB,aACvB,IAAIC,SAASD,SAAU,KACpBE,YAAa,mBAAG,iIACoBC,KAAKH,SAASC,OAAOG,aAC7DL,aAAajC,OAAOoC,YAExBP,WAAW7B,OAAO8B,eAAe9B,OAAOgC,aAAahC,OAAOiC,oBAEtDM,SAAU,mBAAE,gDAGZC,iBAAkB,mBAAE,QAAS,OAAU,6BACvCC,gBAAiB,mBAAE,QAAS,OAAU,gCACtCC,iBAAkB,mBAAE,UAAUL,KAAKV,oBACzCa,gBAAgBxC,OAAOyC,gBAAgBzC,OAAO0C,uBAGxCC,mBAAoB,mBAAE,QAAS,OAAU,6BACzCC,kBAAmB,mBAAE,QAAS,OAAU,wCACxCC,mBAAoB,mBAAE,UAAUR,KAAKT,qBAC3Ce,kBAAkB3C,OAAO4C,kBAAkB5C,OAAO6C,mBAGlDN,QAAQvC,OAAOwC,iBAAiBxC,OAAO2C,uBAEnCG,UAAW,mBAAE,SAAStD,SAAS,mCAC/BuD,YAAa,mBAAE,SAASvD,SAAS,2BAA2BQ,QAC5D,mBAAE,SAASD,KAAK,KAAM,mCAAmCb,KAAKgC,KAAKC,MAAME,iBAG7EyB,SAAS9C,OAAO6B,WAAYU,QAASQ,gCACnC,WAAa7E,QAAQgB,KAAK4D,aAC7B3C,MAAKtC,QACJC,OAAOC,QAAQF,MAAM,mCAAoCA,2BACvD,WAAaK,QAAQgB,KAAKoB,mCAG9B,WAAapC,QAAQgB,KAAKoB,WAEjCH,MAAKtC,iCACF,WAAaK,QAAQgB,KAAKoB,QACtB,IAAIM,MAAM,4BAA8B/C,MAAMmF,eAKhEC,aAAa/E,OAAQgF,cAAU9E,kEAAa,GAAIC,uEAAkB,yBAC5D,QAAQI,GAAG,QAAS,OAASP,OAASE,YAAY,SAASM,GAErDwE,+BACE,mBAAqBhF,OAASE,YAAYiB,YAAY,8BACtD,WAAanB,QAAQsB,SAAS,mDAGlChB,MAAMkB,KAAK,YAAY,uBACvB,WAAaxB,OAASE,YAAYsB,KAAK,YAAY,uBACnD,WAAaxB,QAAQ6B,KAAK,aAAc,8BACxC,WAAa7B,OAASE,YAAYuB,IAAI,SACzB,wBACM,QAErBjB,EAAEC,qCACA,WAAaT,QAAQgB,MAAK,mBAAE,SAASM,SAAS,sCAC3CQ,QAAO,mBAAE,SAASR,SAAS,6CAC9B,yBAAyBX,KAAK,WAAWQ,YAAY,8BACrDb,MAAMgB,SAAS,UACbnB,iBAAmBA,gBAAgBH,SACnCG,gBAAgBH,QAAQ+B,aAExB7B,WAEA+E,eAAejF,OAAQgF,SAAU9E,YAGjC+E,eAAejF,OAAQgF,aAKnCtD,UAAUH,QAAStB,QAASD,OAAQE,WAAYC,qCAC1C,yBAAyBQ,KAAK,WAAWQ,YAAY,UACvDI,QAAQD,SAAS,8BACf,OAAStB,OAASE,YAAYsB,KAAK,YAAY,GAC7CrB,iBAAmBA,gBAAgBH,SACnCG,gBAAgBH,QAAQ+B,iCAE1B,WAAa/B,OAASE,YAAYiB,YAAY,kDAC9C,mBAAqBnB,OAASE,YAAYoB,SAAS,6BAC5CU,OAAO,0BAA2B/B,SAASX,MAAK,SAAS0B,gCAC5D,WAAahB,OAASE,YAAYc,KAAKA,OAClC,KACRiB,MAAK,SAAStC,OACbC,OAAOC,QAAQF,MAAM,6BAA8BA,UAI3DuF,aAAahC,SACLA,KAAKiC,mBAAoB,KACrBC,iBAAmBlC,KAAKiC,yBAIpB,GAHIE,KAAKC,MAAMF,iBAAmB,MAAMG,WAAWC,SAAS,EAAG,OACzDH,KAAKC,MAAOF,iBAAmB,KAAQ,IAAIG,WAAWC,SAAS,EAAG,QACjEJ,iBAAmB,IAAIG,WAAWC,SAAS,EAAG,YAGtD,WAIfC,iBAAiBC,UAAWC,MAAOC,kBAC3BC,KAAO,iBACPC,MAAQ,oCACZH,MAAQI,WAAWJ,OAEfD,WACAG,KAAO,6BACPC,MAAQ,gCACDH,OAASC,eAChBC,KAAO,qBACPC,MAAQ,8BAERH,MAAQC,cACRC,KAAO,wBACPC,MAAQ,gCACD,mBAAE,OAAOxE,SAASuE,MAAMhE,KAAK,QAASiE,OAAOjE,KAAK,QAASrC,aAAawG,QAAQ,qBAEhF,mBAAE,OAAO1E,SAASuE,MAAMhE,KAAK,QAASiE"} \ 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 and showing quality metrics.\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 $ from 'jquery';\nimport {get_string as getString} from 'core/str';\nimport {get_strings as getStrings} from 'core/str';\nimport template from 'core/templates';\n\nexport default class AnalyticEvents {\n\n constructor() {\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\n localStorage.setItem('notenoughtinfo', str);\n return str;\n }).catch(error => window.console.log(error));\n }\n\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\n const self = this;\n $('#analytics' + userid + questionid).on('click', function(e) {\n e.preventDefault();\n\n const isReplayButton = $(this).find('.tiny_cursive-replay-button').length > 0;\n // Create Moodle modal\n myModal.create({templateContext: context}).then(modal => {\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n modal.show();\n\n if (isReplayButton) {\n setTimeout(() => {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n\n const replayTab = $('#rep' + userid + questionid);\n if (replayTab.length) {\n replayTab.trigger('click');\n replayTab.addClass('active');\n }\n }, 50);\n }\n\n let moreBtn = $('body #more' + userid + questionid);\n if (moreBtn.length > 0) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('#analytic' + userid + questionid).prop('disabled', true);\n $('#diff' + userid + questionid).prop('disabled', true);\n $('#analytic' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n $('#diff' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n moreBtn.on('click', function(e) {\n e.preventDefault();\n self.learnMore($(this), context, userid, questionid, replayInstances);\n });\n }\n\n return true;\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\n $('body').on('click', '#analytic' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'analytics');\n $('#player_' + userid + questionid).css({'display': 'none'});\n $('#content' + userid).removeClass('tiny_cursive_outputElement')\n .addClass('tiny_cursive').attr('data-label', 'analytics');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n\n templates.render('tiny_cursive/analytics_table', context).then(function(html) {\n $('#content' + userid).html(html);\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 getString('nopaylod', 'tiny_cursive').then(str => {\n nodata.textContent = str;\n return true;\n }).catch(error => window.console.log(error));\n $('body').on('click', '#diff' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'diff');\n $('#player_' + userid + questionid).css({\n 'display': 'none'\n });\n $('#content' + userid).removeClass('tiny_cursive_outputElement').addClass('tiny_cursive').attr('data-label', 'diff');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active');\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n if (!fileid) {\n $('#content' + userid).html(nodata);\n throw new Error('Missing file id or Difference Content not received yet');\n }\n getContent([{\n methodname: 'cursive_get_writing_differences',\n args: {fileid: fileid},\n }])[0].done(response => {\n let responsedata = JSON.parse(response.data);\n if (responsedata) {\n let submittedText = atob(responsedata.submitted_text);\n\n // Fetch the dynamic strings.\n getStrings([\n {key: 'original_text', component: 'tiny_cursive'},\n {key: 'editspastesai', component: 'tiny_cursive'}\n ]).done(strings => {\n const originalTextString = strings[0];\n const editsPastesAIString = strings[1];\n\n const commentBox = $('
');\n var pasteCountDiv = $('
');\n getString('pastecount', 'tiny_cursive').then(str => {\n pasteCountDiv.append('
' + str + ' : ' + responsedata.commentscount + '
');\n return true;\n }).catch(error => window.console.log(error));\n\n var commentsDiv = $('
');\n getString('comments', 'tiny_cursive').then(str => {\n commentsDiv.append('' + str + '');\n return true;\n }).catch(error => window.console.error(error));\n\n var commentsList = $('
');\n\n let comments = responsedata.comments;\n for (let index in comments) {\n var commentDiv = $(`
`).text(comments[index].usercomment);\n commentsList.append(commentDiv);\n }\n commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);\n\n const $legend = $('
');\n\n // Create the first legend item\n const $attributedItem = $('
', {\"class\": \"tiny_cursive-legend-item\"});\n const $attributedBox = $('
', {\"class\": \"tiny_cursive-box attributed\"});\n const $attributedText = $('').text(originalTextString);\n $attributedItem.append($attributedBox).append($attributedText);\n\n // Create the second legend item\n const $unattributedItem = $('
', {\"class\": 'tiny_cursive-legend-item'});\n const $unattributedBox = $('
', {\"class\": 'tiny_cursive-box tiny_cursive_added'});\n const $unattributedText = $('').text(editsPastesAIString);\n $unattributedItem.append($unattributedBox).append($unattributedText);\n\n // Append the legend items to the legend container.\n $legend.append($attributedItem).append($unattributedItem);\n\n let contents = $('
').addClass('tiny_cursive-comparison-content');\n let textBlock2 = $('
').addClass('tiny_cursive-text-block').append(\n $('
').attr('id', 'tiny_cursive-reconstructed_text').html(JSON.parse(submittedText))\n );\n\n contents.append(commentBox, $legend, textBlock2);\n $('#content' + userid).html(contents); // Update content.\n }).fail(error => {\n window.console.error(\"Failed to load language strings:\", error);\n $('#content' + userid).html(nodata);\n });\n } else {\n $('#content' + userid).html(nodata);\n }\n }).fail(error => {\n $('#content' + userid).html(nodata);\n throw new Error('Error loading JSON file: ' + error.message);\n });\n });\n }\n\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\n $('body').on('click', '#rep' + userid + questionid, function(e) {\n\n if (filepath) {\n $('#replayControls_' + userid + questionid).removeClass('d-none');\n $('#content' + userid).addClass('tiny_cursive_outputElement');\n }\n\n $(this).prop('disabled', true);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'replay');\n $('#player_' + userid + questionid).css({\n 'display': 'block',\n 'padding-right': '8px'\n });\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\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 learnMore(moreBtn, context, userid, questionid, replayInstances) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n moreBtn.addClass('active');\n $('#rep' + userid + questionid).prop('disabled', false);\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('#content' + userid + questionid).removeClass('tiny_cursive_outputElement');\n $('#replayControls_' + userid + questionid).addClass('d-none');\n template.render('tiny_cursive/learn_more', context).then(function(html) {\n $('#content' + userid + questionid).html(html);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 if (score < scoreSetting) {\n icon = 'fa fa-question-circle';\n color = 'font-size:32px;color:#A9A9A9';\n return $('').addClass(icon).attr('style', color).attr('title', localStorage.getItem('notenoughtinfo'));\n } else {\n return $('').addClass(icon).attr('style', color);\n }\n\n }\n}\n"],"names":["constructor","then","str","localStorage","setItem","catch","error","window","console","log","createModal","userid","context","questionid","replayInstances","authIcon","self","this","on","e","preventDefault","isReplayButton","find","length","create","templateContext","modal","html","show","setTimeout","removeClass","replayTab","trigger","addClass","moreBtn","prop","css","learnMore","analytics","templates","attr","append","stopReplay","render","fail","checkDiff","fileid","nodata","document","createElement","classList","add","textContent","Error","methodname","args","done","response","responsedata","JSON","parse","data","submittedText","atob","submitted_text","key","component","strings","originalTextString","editsPastesAIString","commentBox","pasteCountDiv","commentscount","commentsDiv","commentsList","comments","index","commentDiv","text","usercomment","$legend","$attributedItem","$attributedBox","$attributedText","$unattributedItem","$unattributedBox","$unattributedText","contents","textBlock2","message","replyWriting","filepath","video_playback","formatedTime","total_time_seconds","totalTimeSeconds","hours","Math","floor","toString","padStart","minutes","seconds","authorshipStatus","firstFile","score","scoreSetting","icon","color","parseFloat","getItem"],"mappings":";;;;;;;;;iQAkCIA,kCACc,iBAAkB,gBAAgBC,MAAKC,MAC7CC,aAAaC,QAAQ,iBAAkBF,KAChCA,OACRG,OAAMC,OAASC,OAAOC,QAAQC,IAAIH,SAGzCI,YAAYC,OAAQC,aAASC,kEAAa,GAAIC,uEAAkB,KAAMC,sDAC5DC,KAAOC,yBACX,aAAeN,OAASE,YAAYK,GAAG,SAAS,SAASC,GACvDA,EAAEC,uBAEIC,gBAAiB,mBAAEJ,MAAMK,KAAK,+BAA+BC,OAAS,0BAEpEC,OAAO,CAACC,gBAAiBb,UAAUX,MAAKyB,4BAC1C,WAAaf,OAAS,8DAA8DgB,KAAKZ,UAC3FW,MAAME,OAEFP,gBACAQ,YAAW,yBACL,yBAAyBP,KAAK,WAAWQ,YAAY,gBAEjDC,WAAY,mBAAE,OAASpB,OAASE,YAClCkB,UAAUR,SACVQ,UAAUC,QAAQ,SAClBD,UAAUE,SAAS,aAExB,QAGHC,SAAU,mBAAE,aAAevB,OAASE,mBACpCqB,QAAQX,OAAS,wBACf,yBAAyBD,KAAK,WAAWQ,YAAY,8BACrD,YAAcnB,OAASE,YAAYsB,KAAK,YAAY,uBACpD,QAAUxB,OAASE,YAAYsB,KAAK,YAAY,uBAChD,YAAcxB,OAASE,YAAYuB,IAAI,oBACb,oCACV,oCAEhB,QAAUzB,OAASE,YAAYuB,IAAI,oBACT,oCACV,gBAElBF,QAAQhB,GAAG,SAAS,SAASC,GACzBA,EAAEC,iBACFJ,KAAKqB,WAAU,mBAAEpB,MAAOL,QAASD,OAAQE,WAAYC,sBAItD,KACRT,OAAMC,QACLC,OAAOC,QAAQF,MAAM,0BAA2BA,aAM5DgC,UAAU3B,OAAQ4B,UAAW3B,aAASC,kEAAa,GAAIC,uEAAkB,KAAMC,oEAEzE,QAAQG,GAAG,QAAS,YAAcP,OAASE,YAAY,SAASM,uBAC5D,OAASR,OAASE,YAAYsB,KAAK,YAAY,uBAC/C,WAAaxB,OAASE,YAAYsB,KAAK,YAAY,uBACnD,WAAaxB,QAAQ6B,KAAK,aAAc,iCACxC,WAAa7B,OAASE,YAAYuB,IAAI,SAAY,6BAClD,WAAazB,QAAQmB,YAAY,8BACZG,SAAS,gBAAgBO,KAAK,aAAc,aACnErB,EAAEC,qCACA,WAAaT,QAAQgB,MAAK,mBAAE,SAASM,SAAS,sCAC3CQ,QAAO,mBAAE,SAASR,SAAS,yBAC5BnB,iBAAmBA,gBAAgBH,SACnCG,gBAAgBH,QAAQ+B,iCAE1B,yBAAyBpB,KAAK,WAAWQ,YAAY,8BACrDb,MAAMgB,SAAS,UAEjBM,UAAUI,OAAO,+BAAgC/B,SAASX,MAAK,SAAS0B,gCAClE,WAAahB,QAAQgB,KAAKA,0BAC1B,WAAahB,OAAS,8DAA8DgB,KAAKZ,WACpF,KACR6B,MAAK,SAAStC,OACbC,OAAOC,QAAQF,MAAM,6BAA8BA,aAK/DuC,UAAUlC,OAAQmC,YAAQjC,kEAAa,GAAIC,uEAAkB,WACnDiC,OAASC,SAASC,cAAc,KACtCF,OAAOG,UAAUC,IAAI,yBAA0B,gCACrC,WAAY,gBAAgBlD,MAAKC,MACvC6C,OAAOK,YAAclD,KACd,KACRG,OAAMC,OAASC,OAAOC,QAAQC,IAAIH,6BACnC,QAAQY,GAAG,QAAS,QAAUP,OAASE,YAAY,SAASM,0BACxD,OAASR,OAASE,YAAYsB,KAAK,YAAY,uBAC/C,WAAaxB,OAASE,YAAYsB,KAAK,YAAY,uBACnD,WAAaxB,QAAQ6B,KAAK,aAAc,4BACxC,WAAa7B,OAASE,YAAYuB,IAAI,SACzB,6BAEb,WAAazB,QAAQmB,YAAY,8BAA8BG,SAAS,gBAAgBO,KAAK,aAAc,QAC7GrB,EAAEC,qCACA,WAAaT,QAAQgB,MAAK,mBAAE,SAASM,SAAS,sCAC3CQ,QAAO,mBAAE,SAASR,SAAS,6CAC9B,yBAAyBX,KAAK,WAAWQ,YAAY,8BACrDb,MAAMgB,SAAS,UACbnB,iBAAmBA,gBAAgBH,SACnCG,gBAAgBH,QAAQ+B,cAEvBI,gCACC,WAAanC,QAAQgB,KAAKoB,QACtB,IAAIM,MAAM,yEAET,CAAC,CACRC,WAAY,kCACZC,KAAM,CAACT,OAAQA,WACf,GAAGU,MAAKC,eACJC,aAAeC,KAAKC,MAAMH,SAASI,SACnCH,aAAc,KACVI,cAAgBC,KAAKL,aAAaM,qCAG3B,CACP,CAACC,IAAK,gBAAiBC,UAAW,gBAClC,CAACD,IAAK,gBAAiBC,UAAW,kBACnCV,MAAKW,gBACEC,mBAAqBD,QAAQ,GAC7BE,oBAAsBF,QAAQ,GAE9BG,YAAa,mBAAE,6CACjBC,eAAgB,mBAAE,mCACZ,aAAc,gBAAgBtE,MAAKC,MACzCqE,cAAc9B,OAAO,gBAAkBvC,IAAM,eAAiBwD,aAAac,cAAgB,WACpF,KACRnE,OAAMC,OAASC,OAAOC,QAAQC,IAAIH,aAEjCmE,aAAc,mBAAE,yDACV,WAAY,gBAAgBxE,MAAKC,MACvCuE,YAAYhC,OAAO,WAAavC,IAAM,cAC/B,KACRG,OAAMC,OAASC,OAAOC,QAAQF,MAAMA,aAEnCoE,cAAe,mBAAE,mBAEjBC,SAAWjB,aAAaiB,aACvB,IAAIC,SAASD,SAAU,KACpBE,YAAa,oJACuBC,KAAKH,SAASC,OAAOG,aAC7DL,aAAajC,OAAOoC,YAExBP,WAAW7B,OAAO8B,eAAe9B,OAAOgC,aAAahC,OAAOiC,oBAEtDM,SAAU,mBAAE,gDAGZC,iBAAkB,mBAAE,QAAS,OAAU,6BACvCC,gBAAiB,mBAAE,QAAS,OAAU,gCACtCC,iBAAkB,mBAAE,UAAUL,KAAKV,oBACzCa,gBAAgBxC,OAAOyC,gBAAgBzC,OAAO0C,uBAGxCC,mBAAoB,mBAAE,QAAS,OAAU,6BACzCC,kBAAmB,mBAAE,QAAS,OAAU,wCACxCC,mBAAoB,mBAAE,UAAUR,KAAKT,qBAC3Ce,kBAAkB3C,OAAO4C,kBAAkB5C,OAAO6C,mBAGlDN,QAAQvC,OAAOwC,iBAAiBxC,OAAO2C,uBAEnCG,UAAW,mBAAE,SAAStD,SAAS,mCAC/BuD,YAAa,mBAAE,SAASvD,SAAS,2BAA2BQ,QAC5D,mBAAE,SAASD,KAAK,KAAM,mCAAmCb,KAAKgC,KAAKC,MAAME,iBAG7EyB,SAAS9C,OAAO6B,WAAYU,QAASQ,gCACnC,WAAa7E,QAAQgB,KAAK4D,aAC7B3C,MAAKtC,QACJC,OAAOC,QAAQF,MAAM,mCAAoCA,2BACvD,WAAaK,QAAQgB,KAAKoB,mCAG9B,WAAapC,QAAQgB,KAAKoB,WAEjCH,MAAKtC,iCACF,WAAaK,QAAQgB,KAAKoB,QACtB,IAAIM,MAAM,4BAA8B/C,MAAMmF,eAKhEC,aAAa/E,OAAQgF,cAAU9E,kEAAa,GAAIC,uEAAkB,yBAC5D,QAAQI,GAAG,QAAS,OAASP,OAASE,YAAY,SAASM,GAErDwE,+BACE,mBAAqBhF,OAASE,YAAYiB,YAAY,8BACtD,WAAanB,QAAQsB,SAAS,mDAGlChB,MAAMkB,KAAK,YAAY,uBACvB,WAAaxB,OAASE,YAAYsB,KAAK,YAAY,uBACnD,WAAaxB,QAAQ6B,KAAK,aAAc,8BACxC,WAAa7B,OAASE,YAAYuB,IAAI,SACzB,wBACM,QAErBjB,EAAEC,qCACA,WAAaT,QAAQgB,MAAK,mBAAE,SAASM,SAAS,sCAC3CQ,QAAO,mBAAE,SAASR,SAAS,6CAC9B,yBAAyBX,KAAK,WAAWQ,YAAY,8BACrDb,MAAMgB,SAAS,UACbnB,iBAAmBA,gBAAgBH,SACnCG,gBAAgBH,QAAQ+B,aAExB7B,WAEA+E,eAAejF,OAAQgF,SAAU9E,YAGjC+E,eAAejF,OAAQgF,aAKnCtD,UAAUH,QAAStB,QAASD,OAAQE,WAAYC,qCAC1C,yBAAyBQ,KAAK,WAAWQ,YAAY,UACvDI,QAAQD,SAAS,8BACf,OAAStB,OAASE,YAAYsB,KAAK,YAAY,GAC7CrB,iBAAmBA,gBAAgBH,SACnCG,gBAAgBH,QAAQ+B,iCAE1B,WAAa/B,OAASE,YAAYiB,YAAY,kDAC9C,mBAAqBnB,OAASE,YAAYoB,SAAS,6BAC5CU,OAAO,0BAA2B/B,SAASX,MAAK,SAAS0B,gCAC5D,WAAahB,OAASE,YAAYc,KAAKA,OAClC,KACRiB,MAAK,SAAStC,OACbC,OAAOC,QAAQF,MAAM,6BAA8BA,UAI3DuF,aAAahC,SACLA,KAAKiC,mBAAoB,KACrBC,iBAAmBlC,KAAKiC,mBACxBE,MAAQC,KAAKC,MAAMH,iBAAmB,MAAMI,WAAWC,SAAS,EAAG,GACnEC,QAAUJ,KAAKC,MAAOH,iBAAmB,KAAQ,IAAII,WAAWC,SAAS,EAAG,GAC5EE,SAAWP,iBAAmB,IAAII,WAAWC,SAAS,EAAG,mBACnDJ,mBAAUK,qBAAYC,mBAEzB,WAIfC,iBAAiBC,UAAWC,MAAOC,kBAC3BC,KAAO,iBACPC,MAAQ,oCACZH,MAAQI,WAAWJ,OAEfD,WACAG,KAAO,6BACPC,MAAQ,gCACDH,OAASC,eAChBC,KAAO,qBACPC,MAAQ,8BAERH,MAAQC,cACRC,KAAO,wBACPC,MAAQ,gCACD,mBAAE,OAAO3E,SAAS0E,MAAMnE,KAAK,QAASoE,OAAOpE,KAAK,QAASrC,aAAa2G,QAAQ,qBAEhF,mBAAE,OAAO7E,SAAS0E,MAAMnE,KAAK,QAASoE"} \ No newline at end of file diff --git a/amd/build/analytic_modal.min.js b/amd/build/analytic_modal.min.js index b0b20b2d..7444b4d6 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","jquery"],(function(_exports,_modal,_jquery){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default: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=_interopRequireDefault(_modal),_jquery=_interopRequireDefault(_jquery);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",overflow:"auto","scrollbar-width":"none"}),root.find(".modal-dialog").css({"max-width":"800px","background-color":"transparent"}),root.find("#analytic-close").on("click",(()=>{this.destroy()})),root.on("click",(e=>{(0,_jquery.default)(e.target).hasClass("modal")&&this.destroy()}))}}return _exports.default=MyModal,_exports.default})); +define("tiny_cursive/analytic_modal",["exports","core/modal","jquery"],(function(_exports,_modal,_jquery){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default: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=_interopRequireDefault(_modal),_jquery=_interopRequireDefault(_jquery);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",overflow:"auto","scrollbar-width":"none"}),root.find(".modal-dialog").css({"max-width":"800px","background-color":"transparent"}),root.find("#analytic-close").on("click",(()=>{this.destroy()})),root.on("click",(e=>{(0,_jquery.default)(e.target).hasClass("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 068d5e6b..0e95843c 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';\nimport $ from 'jquery';\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 'overflow': 'auto',\n 'scrollbar-width': 'none'\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).hasClass('modal')) {\n this.destroy();\n }\n });\n }\n}\n"],"names":["MyModal","Modal","configure","modalConfig","show","removeOnClose","backdrop","root","this","getRoot","find","remove","css","addClass","on","destroy","e","target","hasClass"],"mappings":";;;;;;;yKAyBqBA,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,gBACL,yBACO,SAEvBL,KAAKG,KAAK,iBAAiBE,IAAI,aACd,2BACO,gBAIxBL,KAAKG,KAAK,mBAAmBI,GAAG,SAAS,UAChCC,aAITR,KAAKO,GAAG,SAAUE,KACV,mBAAEA,EAAEC,QAAQC,SAAS,eAChBH"} \ 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';\nimport $ from 'jquery';\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 'overflow': 'auto',\n 'scrollbar-width': 'none'\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).hasClass('modal')) {\n this.destroy();\n }\n });\n }\n}\n"],"names":["MyModal","Modal","configure","modalConfig","show","removeOnClose","backdrop","root","this","getRoot","find","remove","css","addClass","on","destroy","e","target","hasClass"],"mappings":"ogBAyBqBA,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,gBACL,yBACO,SAEvBL,KAAKG,KAAK,iBAAiBE,IAAI,aACd,2BACO,gBAIxBL,KAAKG,KAAK,mBAAmBI,GAAG,SAAS,UAChCC,aAITR,KAAKO,GAAG,SAAUE,KACV,mBAAEA,EAAEC,QAAQC,SAAS,eAChBH,8DAjDAf,eACH,gDADGA,mBAEC"} \ No newline at end of file diff --git a/amd/build/append_fourm_post.min.js b/amd/build/append_fourm_post.min.js index ea245724..7b9da09b 100644 --- a/amd/build/append_fourm_post.min.js +++ b/amd/build/append_fourm_post.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/append_fourm_post",["jquery","core/ajax","core/str","core/templates","./replay","./analytic_button","./replay_button","./analytic_events"],(function($,AJAX,str,templates,Replay,analyticButton,replayButton,AnalyticEvents){const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new Replay("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else templates.render("tiny_cursive/no_submission").then((html=>($("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var usersTable={init:function(scoreSetting,showcomment,hasApiKey){str.get_strings([{key:"field_require",component:"tiny_cursive"}]).done((function(){usersTable.getToken(scoreSetting,showcomment,hasApiKey)}))},getToken:function(scoreSetting,showcomment,hasApiKey){$("#page-mod-forum-discuss").find("article").get().forEach((function(entry){var replyButton=$('a[data-region="post-action"][title="Reply"]');replyButton.length>0&&replyButton.on("click",(function(event){event.preventDefault();var url=$(this).attr("href");window.location.href=url}));var ids=$("#"+entry.id).data("post-id"),cmid=M.cfg.contextInstanceId;let args={id:ids,modulename:"forum",cmid:cmid},com=AJAX.call([{methodname:"cursive_get_forum_comment_link",args:args}]);return com[0].done((function(json){var data=JSON.parse(json),filepath="";if(data.data.filename&&(filepath=data.data.filename),filepath){let analyticButtonDiv=document.createElement("div");hasApiKey?analyticButtonDiv.append(analyticButton(data.data.effort_ratio,ids)):$(analyticButtonDiv).html(replayButton(ids)),analyticButtonDiv.classList.add("text-center","my-2"),analyticButtonDiv.dataset.region="analytic-div"+ids,$("#"+entry.id).find("#post-content-"+ids).prepend(analyticButtonDiv);let myEvents=new AnalyticEvents;var context={tabledata:data.data,formattime:myEvents.formatedTime(data.data),page:scoreSetting,userid:ids,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.data.first_file,data.data.score,scoreSetting);myEvents.createModal(ids,context,"",replayInstances,authIcon),myEvents.analytics(ids,templates,context,"",replayInstances,authIcon),myEvents.checkDiff(ids,data.data.file_id,"",replayInstances),myEvents.replyWriting(ids,filepath,"",replayInstances)}})),com.usercomment}))}};return usersTable})); +define("tiny_cursive/append_fourm_post",["jquery","core/ajax","core/str","core/templates","./replay","./analytic_button","./replay_button","./analytic_events"],(function($,AJAX,str,templates,Replay,analyticButton,replayButton,AnalyticEvents){const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new Replay("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else templates.render("tiny_cursive/no_submission").then((html=>($("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var usersTable={init:function(scoreSetting,showcomment,hasApiKey){str.get_strings([{key:"field_require",component:"tiny_cursive"}]).done((function(){usersTable.getToken(scoreSetting,showcomment,hasApiKey)}))},getToken:function(scoreSetting,showcomment,hasApiKey){$("#page-mod-forum-discuss").find("article").get().forEach((function(entry){var replyButton=$('a[data-region="post-action"][title="Reply"]');replyButton.length>0&&replyButton.on("click",(function(event){if($("#body").hasClass("teacher_admin"))return!0;event.preventDefault();var urlParts=$(this).attr("href").split("#"),baseUrl=urlParts[0],hash=urlParts.length>1?"#"+urlParts[1]:"";baseUrl.indexOf("setformat=")>-1?baseUrl=baseUrl.replace(/setformat=\d/,"setformat=1"):baseUrl.indexOf("?")>-1?baseUrl+="&setformat=1":baseUrl+="?setformat=1";var finalUrl=baseUrl+hash;window.location.href=finalUrl}));var ids=$("#"+entry.id).data("post-id"),cmid=M.cfg.contextInstanceId;let args={id:ids,modulename:"forum",cmid:cmid},com=AJAX.call([{methodname:"cursive_get_forum_comment_link",args:args}]);return com[0].done((function(json){var data=JSON.parse(json),filepath="";if(data.data.filename&&(filepath=data.data.filename),filepath){let analyticButtonDiv=document.createElement("div");hasApiKey?analyticButtonDiv.append(analyticButton(data.data.effort_ratio,ids)):$(analyticButtonDiv).html(replayButton(ids)),analyticButtonDiv.classList.add("text-center","my-2"),analyticButtonDiv.dataset.region="analytic-div"+ids,$("#"+entry.id).find("#post-content-"+ids).prepend(analyticButtonDiv);let myEvents=new AnalyticEvents;var context={tabledata:data.data,formattime:myEvents.formatedTime(data.data),page:scoreSetting,userid:ids,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.data.first_file,data.data.score,scoreSetting);myEvents.createModal(ids,context,"",replayInstances,authIcon),myEvents.analytics(ids,templates,context,"",replayInstances,authIcon),myEvents.checkDiff(ids,data.data.file_id,"",replayInstances),myEvents.replyWriting(ids,filepath,"",replayInstances)}})),com.usercomment}))}};return usersTable})); //# sourceMappingURL=append_fourm_post.min.js.map \ No newline at end of file diff --git a/amd/build/append_fourm_post.min.js.map b/amd/build/append_fourm_post.min.js.map index 3f491c81..ff2485a3 100644 --- a/amd/build/append_fourm_post.min.js.map +++ b/amd/build/append_fourm_post.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_fourm_post.min.js","sources":["../src/append_fourm_post.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/append_fourm_post\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\",\n \"./replay_button\", \"./analytic_events\"], function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n replayButton,\n AnalyticEvents\n) {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n var usersTable = {\n init: function(scoreSetting, showcomment, hasApiKey) {\n str\n .get_strings([\n {key: \"field_require\", component: \"tiny_cursive\"},\n ])\n .done(function() {\n usersTable.getToken(scoreSetting, showcomment, hasApiKey);\n });\n },\n getToken: function(scoreSetting, showcomment, hasApiKey) {\n $('#page-mod-forum-discuss').find(\"article\").get().forEach(function(entry) {\n var replyButton = $('a[data-region=\"post-action\"][title=\"Reply\"]');\n if (replyButton.length > 0) {\n replyButton.on('click', function(event) {\n event.preventDefault();\n var url = $(this).attr('href');\n window.location.href = url;\n });\n }\n\n var ids = $(\"#\" + entry.id).data(\"post-id\");\n var cmid = M.cfg.contextInstanceId;\n\n let args = {id: ids, modulename: \"forum\", cmid: cmid};\n let methodname = 'cursive_get_forum_comment_link';\n let com = AJAX.call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n if (filepath) {\n\n let analyticButtonDiv = document.createElement('div');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(ids));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, ids));\n }\n\n analyticButtonDiv.classList.add('text-center', 'my-2');\n analyticButtonDiv.dataset.region = \"analytic-div\" + ids;\n\n $(\"#\" + entry.id).find('#post-content-' + ids).prepend(analyticButtonDiv);\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: ids,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(ids, context, '', replayInstances, authIcon);\n myEvents.analytics(ids, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(ids, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(ids, filepath, '', replayInstances);\n }\n\n });\n return com.usercomment;\n });\n },\n };\n return usersTable;\n\n\n});"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","replayButton","AnalyticEvents","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","get_strings","key","component","done","getToken","find","get","forEach","entry","replyButton","length","on","event","preventDefault","url","this","attr","location","href","ids","id","data","cmid","M","cfg","contextInstanceId","args","modulename","com","call","methodname","json","JSON","parse","filename","analyticButtonDiv","document","createElement","append","effort_ratio","classList","add","dataset","region","prepend","myEvents","context","tabledata","formattime","formatedTime","page","userid","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","usercomment"],"mappings":"AAsBAA,wCAAO,CAAC,SAAU,YAAa,WAAY,iBAAkB,WAAY,oBACrE,kBAAmB,sBAAsB,SACzCC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,aACAC,sBAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAASC,IAAKC,aACjB,KAAbA,SAAiB,OACXC,OAAS,IAAIT,OACf,UAAYO,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,YAEvBV,UAAUW,OAAO,8BAA8BC,MAAKC,OAChDhB,EAAE,WAAaW,KAAKK,KAAKA,OAClB,KACRC,OAAMC,GAAKT,OAAOU,QAAQC,MAAMF,YAEhC,OAIPG,WAAa,CACbC,KAAM,SAASC,aAAcC,YAAaC,WACtCvB,IACKwB,YAAY,CACT,CAACC,IAAK,gBAAiBC,UAAW,kBAErCC,MAAK,WACFR,WAAWS,SAASP,aAAcC,YAAaC,eAG3DK,SAAU,SAASP,aAAcC,YAAaC,WAC1CzB,EAAE,2BAA2B+B,KAAK,WAAWC,MAAMC,SAAQ,SAASC,WAC5DC,YAAcnC,EAAE,+CAChBmC,YAAYC,OAAS,GACrBD,YAAYE,GAAG,SAAS,SAASC,OAC7BA,MAAMC,qBACFC,IAAMxC,EAAEyC,MAAMC,KAAK,QACvBjC,OAAOkC,SAASC,KAAOJ,WAI3BK,IAAM7C,EAAE,IAAMkC,MAAMY,IAAIC,KAAK,WAC7BC,KAAOC,EAAEC,IAAIC,sBAEbC,KAAO,CAACN,GAAID,IAAKQ,WAAY,QAASL,KAAMA,MAE5CM,IAAMrD,KAAKsD,KAAK,CAAC,CAACC,WADL,iCACiBJ,KAAAA,eAClCE,IAAI,GAAGzB,MAAK,SAAS4B,UACbV,KAAOW,KAAKC,MAAMF,MAElB7C,SAAW,MACXmC,KAAKA,KAAKa,WACVhD,SAAWmC,KAAKA,KAAKa,UAErBhD,SAAU,KAENiD,kBAAoBC,SAASC,cAAc,OAE1CtC,UAGDoC,kBAAkBG,OAAO3D,eAAe0C,KAAKA,KAAKkB,aAAcpB,MAFhE7C,EAAE6D,mBAAmB7C,KAAKV,aAAauC,MAK3CgB,kBAAkBK,UAAUC,IAAI,cAAe,QAC/CN,kBAAkBO,QAAQC,OAAS,eAAiBxB,IAEpD7C,EAAE,IAAMkC,MAAMY,IAAIf,KAAK,iBAAmBc,KAAKyB,QAAQT,uBAEnDU,SAAW,IAAIhE,mBACfiE,QAAU,CACVC,UAAW1B,KAAKA,KAChB2B,WAAYH,SAASI,aAAa5B,KAAKA,MACvC6B,KAAMrD,aACNsD,OAAQhC,IACRiC,OAAQrD,eAGRsD,SAAWR,SAASS,iBAAiBjC,KAAKA,KAAKkC,WAAYlC,KAAKA,KAAKmC,MAAO3D,cAChFgD,SAASY,YAAYtC,IAAK2B,QAAS,GAAIhE,gBAAiBuE,UACxDR,SAASa,UAAUvC,IAAK1C,UAAWqE,QAAS,GAAIhE,gBAAiBuE,UACjER,SAASc,UAAUxC,IAAKE,KAAKA,KAAKuC,QAAS,GAAI9E,iBAC/C+D,SAASgB,aAAa1C,IAAKjC,SAAU,GAAIJ,qBAI1C8C,IAAIkC,wBAIhBnE"} \ No newline at end of file +{"version":3,"file":"append_fourm_post.min.js","sources":["../src/append_fourm_post.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/append_fourm_post\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\",\n \"./replay_button\", \"./analytic_events\"], function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n replayButton,\n AnalyticEvents\n) {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n var usersTable = {\n init: function(scoreSetting, showcomment, hasApiKey) {\n str\n .get_strings([\n {key: \"field_require\", component: \"tiny_cursive\"},\n ])\n .done(function() {\n usersTable.getToken(scoreSetting, showcomment, hasApiKey);\n });\n },\n getToken: function(scoreSetting, showcomment, hasApiKey) {\n $('#page-mod-forum-discuss').find(\"article\").get().forEach(function(entry) {\n var replyButton = $('a[data-region=\"post-action\"][title=\"Reply\"]');\n if (replyButton.length > 0) {\n replyButton.on('click', function(event) {\n // Check if user is a teacher/admin\n var isTeacher = $('#body').hasClass('teacher_admin');\n if (isTeacher) {\n return true;\n }\n event.preventDefault();\n var url = $(this).attr('href');\n\n // Split URL into base and hash\n var urlParts = url.split('#');\n var baseUrl = urlParts[0];\n var hash = urlParts.length > 1 ? '#' + urlParts[1] : '';\n\n if (baseUrl.indexOf('setformat=') > -1) {\n baseUrl = baseUrl.replace(/setformat=\\d/, 'setformat=1');\n } else if (baseUrl.indexOf('?') > -1) {\n baseUrl += '&setformat=1';\n } else {\n baseUrl += '?setformat=1';\n }\n var finalUrl = baseUrl + hash;\n\n window.location.href = finalUrl;\n });\n }\n\n var ids = $(\"#\" + entry.id).data(\"post-id\");\n var cmid = M.cfg.contextInstanceId;\n\n let args = {id: ids, modulename: \"forum\", cmid: cmid};\n let methodname = 'cursive_get_forum_comment_link';\n let com = AJAX.call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n if (filepath) {\n\n let analyticButtonDiv = document.createElement('div');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(ids));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, ids));\n }\n\n analyticButtonDiv.classList.add('text-center', 'my-2');\n analyticButtonDiv.dataset.region = \"analytic-div\" + ids;\n\n $(\"#\" + entry.id).find('#post-content-' + ids).prepend(analyticButtonDiv);\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: ids,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(ids, context, '', replayInstances, authIcon);\n myEvents.analytics(ids, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(ids, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(ids, filepath, '', replayInstances);\n }\n\n });\n return com.usercomment;\n });\n },\n };\n return usersTable;\n\n\n});"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","replayButton","AnalyticEvents","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","get_strings","key","component","done","getToken","find","get","forEach","entry","replyButton","length","on","event","hasClass","preventDefault","urlParts","this","attr","split","baseUrl","hash","indexOf","replace","finalUrl","location","href","ids","id","data","cmid","M","cfg","contextInstanceId","args","modulename","com","call","methodname","json","JSON","parse","filename","analyticButtonDiv","document","createElement","append","effort_ratio","classList","add","dataset","region","prepend","myEvents","context","tabledata","formattime","formatedTime","page","userid","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","usercomment"],"mappings":"AAsBAA,wCAAO,CAAC,SAAU,YAAa,WAAY,iBAAkB,WAAY,oBACrE,kBAAmB,sBAAsB,SACzCC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,aACAC,sBAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAASC,IAAKC,aACjB,KAAbA,SAAiB,OACXC,OAAS,IAAIT,OACf,UAAYO,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,YAEvBV,UAAUW,OAAO,8BAA8BC,MAAKC,OAChDhB,EAAE,WAAaW,KAAKK,KAAKA,OAClB,KACRC,OAAMC,GAAKT,OAAOU,QAAQC,MAAMF,YAEhC,OAIPG,WAAa,CACbC,KAAM,SAASC,aAAcC,YAAaC,WACtCvB,IACKwB,YAAY,CACT,CAACC,IAAK,gBAAiBC,UAAW,kBAErCC,MAAK,WACFR,WAAWS,SAASP,aAAcC,YAAaC,eAG3DK,SAAU,SAASP,aAAcC,YAAaC,WAC1CzB,EAAE,2BAA2B+B,KAAK,WAAWC,MAAMC,SAAQ,SAASC,WAC5DC,YAAcnC,EAAE,+CAChBmC,YAAYC,OAAS,GACrBD,YAAYE,GAAG,SAAS,SAASC,UAEbtC,EAAE,SAASuC,SAAS,wBAEzB,EAEXD,MAAME,qBAIFC,SAHMzC,EAAE0C,MAAMC,KAAK,QAGJC,MAAM,KACrBC,QAAUJ,SAAS,GACnBK,KAAOL,SAASL,OAAS,EAAI,IAAMK,SAAS,GAAK,GAEjDI,QAAQE,QAAQ,eAAiB,EACjCF,QAAUA,QAAQG,QAAQ,eAAgB,eACnCH,QAAQE,QAAQ,MAAQ,EAC/BF,SAAW,eAEXA,SAAW,mBAEXI,SAAWJ,QAAUC,KAEzBrC,OAAOyC,SAASC,KAAOF,gBAI3BG,IAAMpD,EAAE,IAAMkC,MAAMmB,IAAIC,KAAK,WAC7BC,KAAOC,EAAEC,IAAIC,sBAEbC,KAAO,CAACN,GAAID,IAAKQ,WAAY,QAASL,KAAMA,MAE5CM,IAAM5D,KAAK6D,KAAK,CAAC,CAACC,WADL,iCACiBJ,KAAAA,eAClCE,IAAI,GAAGhC,MAAK,SAASmC,UACbV,KAAOW,KAAKC,MAAMF,MAElBpD,SAAW,MACX0C,KAAKA,KAAKa,WACVvD,SAAW0C,KAAKA,KAAKa,UAErBvD,SAAU,KAENwD,kBAAoBC,SAASC,cAAc,OAE1C7C,UAGD2C,kBAAkBG,OAAOlE,eAAeiD,KAAKA,KAAKkB,aAAcpB,MAFhEpD,EAAEoE,mBAAmBpD,KAAKV,aAAa8C,MAK3CgB,kBAAkBK,UAAUC,IAAI,cAAe,QAC/CN,kBAAkBO,QAAQC,OAAS,eAAiBxB,IAEpDpD,EAAE,IAAMkC,MAAMmB,IAAItB,KAAK,iBAAmBqB,KAAKyB,QAAQT,uBAEnDU,SAAW,IAAIvE,mBACfwE,QAAU,CACVC,UAAW1B,KAAKA,KAChB2B,WAAYH,SAASI,aAAa5B,KAAKA,MACvC6B,KAAM5D,aACN6D,OAAQhC,IACRiC,OAAQ5D,eAGR6D,SAAWR,SAASS,iBAAiBjC,KAAKA,KAAKkC,WAAYlC,KAAKA,KAAKmC,MAAOlE,cAChFuD,SAASY,YAAYtC,IAAK2B,QAAS,GAAIvE,gBAAiB8E,UACxDR,SAASa,UAAUvC,IAAKjD,UAAW4E,QAAS,GAAIvE,gBAAiB8E,UACjER,SAASc,UAAUxC,IAAKE,KAAKA,KAAKuC,QAAS,GAAIrF,iBAC/CsE,SAASgB,aAAa1C,IAAKxC,SAAU,GAAIJ,qBAI1CqD,IAAIkC,wBAIhB1E"} \ No newline at end of file diff --git a/amd/build/append_lesson_grade_table.min.js b/amd/build/append_lesson_grade_table.min.js index 5f481a66..52a4b6ae 100644 --- a/amd/build/append_lesson_grade_table.min.js +++ b/amd/build/append_lesson_grade_table.min.js @@ -7,6 +7,6 @@ define("tiny_cursive/append_lesson_grade_table",["exports","./replay","jquery"," * @module tiny_cursive/append_lesson_grade_table * @copyright 2025 CTI * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_replay=_interopRequireDefault(_replay),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates),_analytic_events=_interopRequireDefault(_analytic_events),_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),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);_exports.init=(scoreSetting,showcomment,hasApiKey)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>((0,_jquery.default)("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var cmid=M.cfg.contextInstanceId,emailLink=(0,_jquery.default)('#page-content div[role="main"] td.lastcol a'),headcolumn=(0,_jquery.default)('#region-main div[role="main"] table thead tr');let url=new URL(window.location.href),mode=url.searchParams.get("mode"),user=url.searchParams.get("user");function analytics(userid,cmid,$emailLink,grade){let args={id:userid,modulename:"lesson",cmid:cmid},com=(0,_ajax.call)([{methodname:"cursive_get_lesson_submission_data",args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.res.filename&&(filepath=data.res.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.res.effort_ratio,userid)):(0,_jquery.default)(analyticButtonDiv).html((0,_replay_button.default)(userid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),grade?(analyticButtonDiv.classList.add("w-100"),(0,_jquery.default)("#fitem_id_response_editor .felement").prepend(analyticButtonDiv)):$emailLink.closest("tr").find("td:eq(1)").after(analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.res,formattime:myEvents.formatedTime(data.res),page:scoreSetting,userid:userid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.res.first_file,data.res.score,scoreSetting);myEvents.createModal(userid,context,"",replayInstances,authIcon),myEvents.analytics(userid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(userid,data.res.file_id,"",replayInstances),myEvents.replyWriting(userid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}Str.get_string("analytics","tiny_cursive").then((strs=>(headcolumn.each((function(){(0,_jquery.default)(this).find("th:eq(1)").after(`${strs}`)})),!0))).catch((e=>window.console.error(e))),mode&&"grade"===mode&&analytics(user,cmid,"",!0),emailLink.each((function(){let href=(0,_jquery.default)(this).attr("href");const $emailLink=(0,_jquery.default)(this);let userid=0;href&&(userid=parseInt(new URLSearchParams(href.split("?")[1]).get("userid")),userid?((0,_jquery.default)("#region-main").on("click","table tbody tr td.cell.c1 a",(function(e){e.preventDefault();const link=e.target.href,url=new URL(link);url.searchParams.append("user",userid),window.location.href=url.toString()})),analytics(userid,cmid,$emailLink,!1)):$emailLink.closest("tr").find("td:eq(1)").after(""))}))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_replay=_interopRequireDefault(_replay),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates),_analytic_events=_interopRequireDefault(_analytic_events),_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),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);_exports.init=(scoreSetting,showcomment,hasApiKey)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>((0,_jquery.default)("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var cmid=M.cfg.contextInstanceId,emailLink=(0,_jquery.default)('#page-content div[role="main"] td.lastcol a'),headcolumn=(0,_jquery.default)('#region-main div[role="main"] table thead tr');let url=new URL(window.location.href),mode=url.searchParams.get("mode"),user=url.searchParams.get("user");function analytics(userid,cmid,$emailLink,grade){let args={id:userid,modulename:"lesson",cmid:cmid},com=(0,_ajax.call)([{methodname:"cursive_get_lesson_submission_data",args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.res.filename&&(filepath=data.res.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.res.effort_ratio,userid)):(0,_jquery.default)(analyticButtonDiv).html((0,_replay_button.default)(userid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),grade?(analyticButtonDiv.classList.add("w-100"),(0,_jquery.default)("#fitem_id_response_editor .felement").prepend(analyticButtonDiv)):$emailLink.closest("tr").find("td:eq(1)").after(analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.res,formattime:myEvents.formatedTime(data.res),page:scoreSetting,userid:userid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.res.first_file,data.res.score,scoreSetting);myEvents.createModal(userid,context,"",replayInstances,authIcon),myEvents.analytics(userid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(userid,data.res.file_id,"",replayInstances),myEvents.replyWriting(userid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}Str.get_string("analytics","tiny_cursive").then((strs=>(headcolumn.each((function(){(0,_jquery.default)(this).find("th:eq(1)").after(''.concat(strs,""))})),!0))).catch((e=>window.console.error(e))),mode&&"grade"===mode&&analytics(user,cmid,"",!0),emailLink.each((function(){let href=(0,_jquery.default)(this).attr("href");const $emailLink=(0,_jquery.default)(this);let userid=0;href&&(userid=parseInt(new URLSearchParams(href.split("?")[1]).get("userid")),userid?((0,_jquery.default)("#region-main").on("click","table tbody tr td.cell.c1 a",(function(e){e.preventDefault();const link=e.target.href,url=new URL(link);url.searchParams.append("user",userid),window.location.href=url.toString()})),analytics(userid,cmid,$emailLink,!1)):$emailLink.closest("tr").find("td:eq(1)").after(""))}))}})); //# sourceMappingURL=append_lesson_grade_table.min.js.map \ No newline at end of file diff --git a/amd/build/append_lesson_grade_table.min.js.map b/amd/build/append_lesson_grade_table.min.js.map index ac5fede6..6ad80de5 100644 --- a/amd/build/append_lesson_grade_table.min.js.map +++ b/amd/build/append_lesson_grade_table.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_lesson_grade_table.min.js","sources":["../src/append_lesson_grade_table.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 to append analytics and grade table data for lesson submissions\n * Handles the display of analytics, replay functionality and grade information\n * for lesson submissions in the Moodle gradebook interface\n *\n * @module tiny_cursive/append_lesson_grade_table\n * @copyright 2025 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Replay from './replay';\nimport $ from 'jquery';\nimport {call as getData} from 'core/ajax';\nimport templates from 'core/templates';\nimport AnalyticEvents from './analytic_events';\nimport analyticButton from './analytic_button';\nimport replayButton from './replay_button';\nimport * as Str from 'core/str';\n\nexport const init = (scoreSetting, showcomment, hasApiKey) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n\n var cmid = M.cfg.contextInstanceId;\n var emailLink = $('#page-content div[role=\"main\"] td.lastcol a');\n var headcolumn = $('#region-main div[role=\"main\"] table thead tr');\n let url = new URL(window.location.href);\n let mode = url.searchParams.get('mode');\n let user = url.searchParams.get('user');\n\n Str.get_string('analytics', 'tiny_cursive').then((strs) => {\n headcolumn.each(function() {\n $(this).find('th:eq(1)').after(`${strs}`);\n });\n return true;\n }).catch(e => window.console.error(e));\n\n if (mode && mode === \"grade\") {\n analytics(user, cmid, \"\", true);\n }\n\n emailLink.each(function() {\n let href = $(this).attr('href');\n const $emailLink = $(this);\n let userid = 0;\n if (href) {\n userid = parseInt(new URLSearchParams(href.split('?')[1]).get('userid'));\n if (!userid) {\n $emailLink.closest('tr').find('td:eq(1)').after(\"\"); // For aligning the table column\n } else {\n\n $('#region-main').on('click', 'table tbody tr td.cell.c1 a', function(e) {\n e.preventDefault();\n const link = e.target.href;\n const url = new URL(link);\n url.searchParams.append('user', userid);\n window.location.href = url.toString();\n });\n\n analytics(userid, cmid, $emailLink, false);\n }\n }\n });\n\n /**\n * Fetches and displays analytics data for lesson submissions\n * @param {number} userid - The ID of the user whose analytics to fetch\n * @param {number} cmid - The course module ID\n * @param {jQuery|string} $emailLink - jQuery object of email link or empty string\n * @param {boolean} grade - Whether this is being called from grade view\n */\n function analytics(userid, cmid, $emailLink, grade) {\n\n let args = {id: userid, modulename: \"lesson\", cmid: cmid};\n let methodname = 'cursive_get_lesson_submission_data';\n let com = getData([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(userid));\n } else {\n analyticButtonDiv.append(analyticButton(data.res.effort_ratio, userid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n if (grade) {\n analyticButtonDiv.classList.add('w-100');\n $('#fitem_id_response_editor .felement').prepend(analyticButtonDiv);\n } else {\n $emailLink.closest('tr').find('td:eq(1)').after(analyticsColumn);\n }\n\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.res,\n formattime: myEvents.formatedTime(data.res),\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","showcomment","hasApiKey","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","catch","e","console","error","cmid","M","cfg","contextInstanceId","emailLink","headcolumn","url","URL","location","href","mode","searchParams","get","user","analytics","userid","$emailLink","grade","args","id","modulename","com","methodname","done","json","data","JSON","parse","res","filename","analyticButtonDiv","document","createElement","analyticsColumn","append","effort_ratio","dataset","region","classList","add","prepend","closest","find","after","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","templates","checkDiff","file_id","replyWriting","fail","Str","get_string","strs","each","this","attr","parseInt","URLSearchParams","split","on","preventDefault","link","target","toString"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"append_lesson_grade_table.min.js","sources":["../src/append_lesson_grade_table.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 to append analytics and grade table data for lesson submissions\n * Handles the display of analytics, replay functionality and grade information\n * for lesson submissions in the Moodle gradebook interface\n *\n * @module tiny_cursive/append_lesson_grade_table\n * @copyright 2025 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Replay from './replay';\nimport $ from 'jquery';\nimport {call as getData} from 'core/ajax';\nimport templates from 'core/templates';\nimport AnalyticEvents from './analytic_events';\nimport analyticButton from './analytic_button';\nimport replayButton from './replay_button';\nimport * as Str from 'core/str';\n\nexport const init = (scoreSetting, showcomment, hasApiKey) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n\n var cmid = M.cfg.contextInstanceId;\n var emailLink = $('#page-content div[role=\"main\"] td.lastcol a');\n var headcolumn = $('#region-main div[role=\"main\"] table thead tr');\n let url = new URL(window.location.href);\n let mode = url.searchParams.get('mode');\n let user = url.searchParams.get('user');\n\n Str.get_string('analytics', 'tiny_cursive').then((strs) => {\n headcolumn.each(function() {\n $(this).find('th:eq(1)').after(`${strs}`);\n });\n return true;\n }).catch(e => window.console.error(e));\n\n if (mode && mode === \"grade\") {\n analytics(user, cmid, \"\", true);\n }\n\n emailLink.each(function() {\n let href = $(this).attr('href');\n const $emailLink = $(this);\n let userid = 0;\n if (href) {\n userid = parseInt(new URLSearchParams(href.split('?')[1]).get('userid'));\n if (!userid) {\n $emailLink.closest('tr').find('td:eq(1)').after(\"\"); // For aligning the table column\n } else {\n\n $('#region-main').on('click', 'table tbody tr td.cell.c1 a', function(e) {\n e.preventDefault();\n const link = e.target.href;\n const url = new URL(link);\n url.searchParams.append('user', userid);\n window.location.href = url.toString();\n });\n\n analytics(userid, cmid, $emailLink, false);\n }\n }\n });\n\n /**\n * Fetches and displays analytics data for lesson submissions\n * @param {number} userid - The ID of the user whose analytics to fetch\n * @param {number} cmid - The course module ID\n * @param {jQuery|string} $emailLink - jQuery object of email link or empty string\n * @param {boolean} grade - Whether this is being called from grade view\n */\n function analytics(userid, cmid, $emailLink, grade) {\n\n let args = {id: userid, modulename: \"lesson\", cmid: cmid};\n let methodname = 'cursive_get_lesson_submission_data';\n let com = getData([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(userid));\n } else {\n analyticButtonDiv.append(analyticButton(data.res.effort_ratio, userid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n if (grade) {\n analyticButtonDiv.classList.add('w-100');\n $('#fitem_id_response_editor .felement').prepend(analyticButtonDiv);\n } else {\n $emailLink.closest('tr').find('td:eq(1)').after(analyticsColumn);\n }\n\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.res,\n formattime: myEvents.formatedTime(data.res),\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","showcomment","hasApiKey","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","catch","e","console","error","cmid","M","cfg","contextInstanceId","emailLink","headcolumn","url","URL","location","href","mode","searchParams","get","user","analytics","userid","$emailLink","grade","args","id","modulename","com","methodname","done","json","data","JSON","parse","res","filename","analyticButtonDiv","document","createElement","analyticsColumn","append","effort_ratio","dataset","region","classList","add","prepend","closest","find","after","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","templates","checkDiff","file_id","replyWriting","fail","Str","get_string","strs","each","this","attr","parseInt","URLSearchParams","split","on","preventDefault","link","target","toString"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/autosaver.min.js b/amd/build/autosaver.min.js index c049ca37..53d9377a 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","jquery","tiny_cursive/common","tiny_cursive/cursive_autosave","tiny_cursive/document_view"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_jquery,_common,_cursive_autosave,_document_view){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,_jquery=_interopRequireDefault(_jquery),_cursive_autosave=_interopRequireDefault(_cursive_autosave),_document_view=_interopRequireDefault(_document_view);_exports.register=(editor,interval,userId,hasApiKey,MODULES,Rubrics,submission,quizInfo,pasteSetting)=>{var isStudent=!(0,_jquery.default)("#body").hasClass("teacher_admin"),intervention=(0,_jquery.default)("#body").hasClass("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="",questionid=0,quizSubmit=(0,_jquery.default)("#mod_quiz-next-nav");let assignSubmit=(0,_jquery.default)("#id_submitbutton");var syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let aiContents=[];var isFullScreen=!1,user=null;let ur=window.location.href,modulesInfo=function(ur,parm,MODULES){if(function(){localStorage.getItem("sbTitle")||Promise.all([(0,_str.get_string)("assignment","tiny_cursive"),(0,_str.get_string)("discussion","tiny_cursive"),(0,_str.get_string)("pluginname","mod_quiz"),(0,_str.get_string)("pluginname","mod_lesson"),(0,_str.get_string)("description","tiny_cursive")]).then((function(strings){return localStorage.setItem("sbTitle",JSON.stringify(strings))})).catch((error=>window.console.error(error)));localStorage.getItem("docSideBar")||Promise.all([(0,_str.get_string)("details","tiny_cursive"),(0,_str.get_string)("student_info","tiny_cursive"),(0,_str.get_string)("progress","tiny_cursive"),(0,_str.get_string)("description","tiny_cursive"),(0,_str.get_string)("replyingto","tiny_cursive"),(0,_str.get_string)("answeringto","tiny_cursive"),(0,_str.get_string)("importantdates","tiny_cursive"),(0,_str.get_string)("rubrics","tiny_cursive"),(0,_str.get_string)("submission_status","tiny_cursive"),(0,_str.get_string)("status","tiny_cursive"),(0,_str.get_string)("draft","tiny_cursive"),(0,_str.get_string)("draftnot","tiny_cursive"),(0,_str.get_string)("last_modified","tiny_cursive"),(0,_str.get_string)("gradings","tiny_cursive"),(0,_str.get_string)("gradenot","tiny_cursive"),(0,_str.get_string)("word_count","tiny_cursive"),(0,_str.get_string)("timeleft","tiny_cursive"),(0,_str.get_string)("nolimit","tiny_cursive"),(0,_str.get_string)("name","tiny_cursive"),(0,_str.get_string)("userename","tiny_cursive"),(0,_str.get_string)("course","tiny_cursive"),(0,_str.get_string)("opened","tiny_cursive"),(0,_str.get_string)("due","tiny_cursive"),(0,_str.get_string)("overdue","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive"),(0,_str.get_string)("savechanges","tiny_cursive"),(0,_str.get_string)("subjectnot","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive")]).then((function(strings){return localStorage.setItem("docSideBar",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}(),!MODULES.some((module=>ur.includes(module))))return!1;resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt");null===resourceId&&(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}}(ur,new URL(ur),MODULES);var resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,errorAlert=!0;let PASTE_SETTING=pasteSetting||"allow",shouldBlockPaste=!1,isPasteAllowed=!1;"assign"!==modulename&&(PASTE_SETTING="cite_source");const postOne=async(methodname,args)=>{try{const response=await(0,_ajax.call)([{methodname:methodname,args:args}])[0];return response&&setTimeout((()=>{_cursive_autosave.default.updateSavingState("saved")}),1e3),response}catch(error){throw _cursive_autosave.default.updateSavingState("offline"),window.console.error("Error in postOne:",error),error}};(0,_ajax.call)([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[M.cfg.userId]}}])[0].done((response=>{user=response[0]})).fail((ex=>{window.console.error("Error fetching user data:",ex)})),assignSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{assignSubmit.off("click").click()})):assignSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")})),quizSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{quizSubmit.off("click").click()})):quizSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")}));const getModal=()=>{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();""===number||null==number?(editor.execCommand("Undo"),(0,_str.get_string)("pastewarning","tiny_cursive").then((str=>alert(str)))):editor.execCommand("Paste"),postOne("cursive_user_comments",{modulename:modulename,cmid:cmid,resourceid:resourceId,courseid:courseid,usercomment:number,timemodified:Date.now(),editorid:editorid||""}),lastEvent="save",modal.destroy()})),modal.getRoot().on(_modal_events.cancel,(function(){editor.execCommand("Undo"),lastEvent="cancel"})),modal.getRoot().on(_modal_events.hidden,(function(){"cancel"!=lastEvent&&"save"!=lastEvent&&editor.execCommand("Undo")})),modal}))})).catch((error=>window.console.error(error)))},sendKeyEvent=(events,editor)=>{if(ed=editor,event=events,filename=`${userid}_${resourceId}_${cmid}_${modulename}_attempt`,"quiz"===modulename&&(questionid=editorid.split(":")[1].split("_")[0],filename=`${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`),localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,editor.key=function(editor){switch(editor.button){case 0:return"left";case 1:return"middle";case 2:return"right"}return null}(editor),editor.keyCode=editor.button}function getCaretPosition(){let skip=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{if(!editor||!editor.selection)return{caretPosition:0,rePosition:0};const range=editor.selection.getRng(),body=editor.getBody(),preCaretRange=range.cloneRange();preCaretRange.selectNodeContents(body),preCaretRange.setEnd(range.endContainer,range.endOffset);const fragment=preCaretRange.cloneContents(),tempDiv=document.createElement("div");tempDiv.appendChild(fragment);let textBeforeCursor=tempDiv.innerText||"";const endContainer=range.endContainer;0===range.endOffset&&endContainer.nodeType===Node.ELEMENT_NODE&&editor.dom.isBlock(endContainer)&&endContainer.previousSibling&&(textBeforeCursor+="\n");const blockElements=tempDiv.querySelectorAll("p, div, h1, h2, h3, h4, h5, h6, li");let emptyBlockCount=0;blockElements.forEach((block=>{""===(block.innerText||block.textContent||"").trim()&&1===block.childNodes.length&&"BR"===block.childNodes[0].nodeName&&emptyBlockCount++})),emptyBlockCount>0&&(textBeforeCursor+="\n".repeat(emptyBlockCount));const absolutePosition=textBeforeCursor.length;if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey=`${userid}_${resourceId}_${cmid}_position`;let storedPos=parseInt(sessionStorage.getItem(storageKey),10);return isNaN(storedPos)&&(storedPos=0),storedPos++,lastCaretPos=storedPos,sessionStorage.setItem(storageKey,storedPos),{caretPosition:storedPos,rePosition:absolutePosition}}catch(e){return window.console.warn("Error getting caret position:",e),{caretPosition:lastCaretPos||1,rePosition:0}}}async function syncData(){let data=localStorage.getItem(filename);if(data&&0!==data.length){localStorage.removeItem(filename);let originalText=editor.getContent({format:"text"});try{return _cursive_autosave.default.updateSavingState("saving"),await postOne("cursive_write_local_to_json",{key:ed.key,event:event,keyCode:ed.keyCode,resourceId:resourceId,cmid:cmid,modulename:modulename,editorid:editorid,json_data:data,originalText:originalText})}catch(error){window.console.error("Error submitting data:",error)}}}function customTooltip(){try{const tooltipText=async function(){const[buttonTitle,buttonDes]=await Promise.all([(0,_str.get_string)("cursive:state:active","tiny_cursive"),(0,_str.get_string)("cursive:state:active:des","tiny_cursive")]);return{buttonTitle:buttonTitle,buttonDes:buttonDes}}(),menubarDiv=document.querySelectorAll('div[role="menubar"].tox-menubar');let classArray=[];menubarDiv.length&&menubarDiv.forEach((function(element,index){let className="cursive-menu-"+(index+=1);element.classList.add(className),classArray.push(className)}));const cursiveIcon=document.createElement("img");cursiveIcon.src=hasApiKey?_common.iconUrl:_common.iconGrayUrl,cursiveIcon.setAttribute("class","tiny_cursive_StateButton"),cursiveIcon.style.display="inline-block",function(cursiveIcon,menubarDiv,classArray){if(!menubarDiv)return;for(let index in classArray){const rightWrapper=document.createElement("div"),imgWrapper=document.createElement("span"),iconClone=cursiveIcon.cloneNode(!0),targetMenu=document.querySelector("."+classArray[index]);let elementId="tiny_cursive_StateIcon"+index;rightWrapper.style.cssText="\n margin-left: auto;\n display: flex;\n align-items: center;\n ",imgWrapper.id=elementId,imgWrapper.style.marginLeft=".2rem",imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper);let moduleIds={resourceId:resourceId,cmid:cmid,modulename:modulename,questionid:questionid,userid:userid,courseid:courseid};if(!isFullScreen||"assign"!==modulename&&"forum"!==modulename&&"lesson"!==modulename)if(isFullScreen&&"quiz"===modulename){var _editor$container,_editor$container$chi,_editor$container$chi2,_editor$container$chi3,_editor$container2;let existingElement=null===(_editor$container=editor.container)||void 0===_editor$container||null===(_editor$container$chi=_editor$container.childNodes[1])||void 0===_editor$container$chi||null===(_editor$container$chi2=_editor$container$chi.childNodes[0])||void 0===_editor$container$chi2||null===(_editor$container$chi3=_editor$container$chi2.childNodes[0])||void 0===_editor$container$chi3?void 0:_editor$container$chi3.childNodes[7],newHeader=null===(_editor$container2=editor.container)||void 0===_editor$container2?void 0:_editor$container2.childNodes[0];existingElement&&existingElement.remove(),newHeader&&!newHeader.querySelector("span[id*=tiny_cursive_StateIcon]")&&(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{var _editor$container3,_editor$container3$ch,_editor$container3$ch2;let menubar=null==editor||null===(_editor$container3=editor.container)||void 0===_editor$container3||null===(_editor$container3$ch=_editor$container3.children[0])||void 0===_editor$container3$ch||null===(_editor$container3$ch2=_editor$container3$ch.childNodes[0])||void 0===_editor$container3$ch2?void 0:_editor$container3$ch2.childNodes[0];if(targetMenu&&!targetMenu.querySelector(`#${elementId}`)&&targetMenu.appendChild(rightWrapper),"quiz"===modulename&&menubar){let wrapper=menubar.querySelector('span[id*="tiny_cursive_StateIcon"]');wrapper&&(_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,null==wrapper?void 0:wrapper.parentElement,moduleIds,isFullScreen))}else _cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{let existsElement=document.querySelector('.tox-menubar[class*="cursive-menu-"] > div');existsElement&&existsElement.remove(),document.querySelector(`#${elementId}`)||(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId=`tiny_cursive_tooltip${index}`;tooltipText.then((text=>setTooltip(text,document.querySelector(`#${elementId}`),tooltipId))).catch((error=>window.console.error(error))),(0,_jquery.default)(`#${elementId}`).on("mouseenter",(function(){(0,_jquery.default)(this).css("position","relative"),(0,_jquery.default)(`#${tooltipId}`).css(_common.tooltipCss)})),(0,_jquery.default)(`#${elementId}`).on("mouseleave",(function(){(0,_jquery.default)(`#${tooltipId}`).css("display","none")}))}}catch(error){window.console.error("Error setting up custom tooltip:",error)}}function setTooltip(text,cursiveIcon,tooltipId){if(!document.querySelector(`#${tooltipId}`)&&cursiveIcon){const tooltipSpan=document.createElement("span"),description=document.createElement("span"),linebreak=document.createElement("br"),tooltipTitle=document.createElement("strong");tooltipSpan.style.display="none",tooltipTitle.textContent=text.buttonTitle,tooltipTitle.style.fontSize="16px",tooltipTitle.style.fontWeight="bold",description.textContent=text.buttonDes,description.style.fontSize="14px",tooltipSpan.id=tooltipId,tooltipSpan.classList.add("shadow"),tooltipSpan.appendChild(tooltipTitle),tooltipSpan.appendChild(linebreak),tooltipSpan.appendChild(description),cursiveIcon.appendChild(tooltipSpan)}}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyUp",editor)})),editor.on("Paste",(async e=>{customTooltip();const pastedContent=(e.clipboardData||e.originalEvent.clipboardData).getData("text");if(!pastedContent)return;const trimmedPastedContent=pastedContent.trim(),lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&trimmedPastedContent===lastCopyCutContent;if(isStudent&&intervention){if("block"===PASTE_SETTING)return isFromOwnEditor?(shouldBlockPaste=!1,void(isPasteAllowed=!0)):(e.preventDefault(),shouldBlockPaste=!0,isPasteAllowed=!1,e.stopPropagation(),e.stopImmediatePropagation(),(0,_str.get_string)("paste_blocked","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error))),void setTimeout((()=>{isPasteAllowed=!0,shouldBlockPaste=!1}),100));if("cite_source"===PASTE_SETTING)return isFromOwnEditor||(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),getModal()),void(isPasteAllowed=!0)}isPasteAllowed=!0})),editor.on("Redo",(async e=>{customTooltip(),isStudent&&intervention&&getModal()})),editor.on("keyDown",(editor=>{customTooltip();if(("v"===editor.key||"V"===editor.key)&&(editor.ctrlKey||editor.metaKey)&&isStudent&&intervention&&"block"===PASTE_SETTING&&!isPasteAllowed)return void setTimeout((()=>{isPasteAllowed=!0}),100);let position=getCaretPosition();editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyDown",editor)})),editor.on("Cut",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("Copy",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("mouseDown",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)}),0)})),editor.on("mouseUp",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)}),10)})),editor.on("init",(()=>{customTooltip(),localStorage.removeItem("lastCopyCutContent")})),editor.on("SetContent",(()=>{customTooltip()})),editor.on("FullscreenStateChanged",(e=>{let view=new _document_view.default(user,Rubrics,submission,modulename,editor,quizInfo);isFullScreen=e.state;try{e.state?view.fullPageMode():view.normalMode()}catch(error){errorAlert&&(errorAlert=!1,(0,_str.get_string)("fullmodeerror","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))),view.normalMode(),window.console.error("Error ResizeEditor event:",error)}})),editor.on("execcommand",(function(e){if("mceInsertContent"===e.command){const contentObj=e.value,isPaste=contentObj&&"object"==typeof contentObj&&!0===contentObj.paste;let insertedContent=contentObj.content||contentObj,tempDiv=document.createElement("div");tempDiv.innerHTML=insertedContent;let text=tempDiv.textContent||tempDiv.innerText||"",pastedText=tempDiv.textContent||tempDiv.innerText||"",position=getCaretPosition(!0);if(editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,isPaste){if(shouldBlockPaste)return shouldBlockPaste=!1,e.preventDefault(),void editor.undoManager.undo();const lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&pastedText.trim()===lastCopyCutContent;if(isStudent&&intervention&&"block"===PASTE_SETTING&&!isFromOwnEditor)return isPasteAllowed=!1,void editor.undoManager.undo();sendKeyEvent("Paste",{key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition,pastedContent:pastedText,srcElement:{baseURI:window.location.href}})}else aiContents.push(text),sendKeyEvent("aiInsert",{key:"ai",keyCode:0,caretPosition:editor.caretPosition,rePosition:editor.rePosition,aiContent:text,srcElement:{baseURI:window.location.href}})}})),editor.on("input",(function(e){let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition;let aiContent=e.data;("insertReplacementText"===e.inputType||"insertText"===e.inputType&&aiContent&&aiContent.length>1)&&(aiContents.push(aiContent),e.key="ai",e.keyCode=0,e.caretPosition=position.caretPosition,e.rePosition=position.rePosition,e.aiContent=aiContent,sendKeyEvent("aiInsert",e))})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}})); +define("tiny_cursive/autosaver",["exports","core/ajax","core/modal_factory","core/str","core/modal_events","jquery","tiny_cursive/common","tiny_cursive/cursive_autosave","tiny_cursive/document_view"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_jquery,_common,_cursive_autosave,_document_view){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,_jquery=_interopRequireDefault(_jquery),_cursive_autosave=_interopRequireDefault(_cursive_autosave),_document_view=_interopRequireDefault(_document_view);_exports.register=(editor,interval,userId,hasApiKey,MODULES,Rubrics,submission,quizInfo,pasteSetting)=>{var isStudent=!(0,_jquery.default)("#body").hasClass("teacher_admin"),intervention=(0,_jquery.default)("#body").hasClass("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="",questionid=0,quizSubmit=(0,_jquery.default)("#mod_quiz-next-nav");let assignSubmit=(0,_jquery.default)("#id_submitbutton");var syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let aiContents=[];var isFullScreen=!1,user=null;let ur=window.location.href,modulesInfo=function(ur,parm,MODULES){if(function(){localStorage.getItem("sbTitle")||Promise.all([(0,_str.get_string)("assignment","tiny_cursive"),(0,_str.get_string)("discussion","tiny_cursive"),(0,_str.get_string)("pluginname","mod_quiz"),(0,_str.get_string)("pluginname","mod_lesson"),(0,_str.get_string)("description","tiny_cursive")]).then((function(strings){return localStorage.setItem("sbTitle",JSON.stringify(strings))})).catch((error=>window.console.error(error)));localStorage.getItem("docSideBar")||Promise.all([(0,_str.get_string)("details","tiny_cursive"),(0,_str.get_string)("student_info","tiny_cursive"),(0,_str.get_string)("progress","tiny_cursive"),(0,_str.get_string)("description","tiny_cursive"),(0,_str.get_string)("replyingto","tiny_cursive"),(0,_str.get_string)("answeringto","tiny_cursive"),(0,_str.get_string)("importantdates","tiny_cursive"),(0,_str.get_string)("rubrics","tiny_cursive"),(0,_str.get_string)("submission_status","tiny_cursive"),(0,_str.get_string)("status","tiny_cursive"),(0,_str.get_string)("draft","tiny_cursive"),(0,_str.get_string)("draftnot","tiny_cursive"),(0,_str.get_string)("last_modified","tiny_cursive"),(0,_str.get_string)("gradings","tiny_cursive"),(0,_str.get_string)("gradenot","tiny_cursive"),(0,_str.get_string)("word_count","tiny_cursive"),(0,_str.get_string)("timeleft","tiny_cursive"),(0,_str.get_string)("nolimit","tiny_cursive"),(0,_str.get_string)("name","tiny_cursive"),(0,_str.get_string)("userename","tiny_cursive"),(0,_str.get_string)("course","tiny_cursive"),(0,_str.get_string)("opened","tiny_cursive"),(0,_str.get_string)("due","tiny_cursive"),(0,_str.get_string)("overdue","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive"),(0,_str.get_string)("savechanges","tiny_cursive"),(0,_str.get_string)("subjectnot","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive")]).then((function(strings){return localStorage.setItem("docSideBar",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}(),!MODULES.some((module=>ur.includes(module))))return!1;resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt");null===resourceId&&(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}}(ur,new URL(ur),MODULES);var resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,errorAlert=!0;let PASTE_SETTING=pasteSetting||"allow",shouldBlockPaste=!1,isPasteAllowed=!1;"assign"!==modulename&&(PASTE_SETTING="cite_source");const postOne=async(methodname,args)=>{try{const response=await(0,_ajax.call)([{methodname:methodname,args:args}])[0];return response&&setTimeout((()=>{_cursive_autosave.default.updateSavingState("saved")}),1e3),response}catch(error){throw _cursive_autosave.default.updateSavingState("offline"),window.console.error("Error in postOne:",error),error}};(0,_ajax.call)([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[M.cfg.userId]}}])[0].done((response=>{user=response[0]})).fail((ex=>{window.console.error("Error fetching user data:",ex)})),assignSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{assignSubmit.off("click").click()})):assignSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")})),quizSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{quizSubmit.off("click").click()})):quizSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")}));const getModal=()=>{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();""===number||null==number?(editor.execCommand("Undo"),(0,_str.get_string)("pastewarning","tiny_cursive").then((str=>alert(str)))):editor.execCommand("Paste"),postOne("cursive_user_comments",{modulename:modulename,cmid:cmid,resourceid:resourceId,courseid:courseid,usercomment:number,timemodified:Date.now(),editorid:editorid||""}),lastEvent="save",modal.destroy()})),modal.getRoot().on(_modal_events.cancel,(function(){editor.execCommand("Undo"),lastEvent="cancel"})),modal.getRoot().on(_modal_events.hidden,(function(){"cancel"!=lastEvent&&"save"!=lastEvent&&editor.execCommand("Undo")})),modal}))})).catch((error=>window.console.error(error)))},sendKeyEvent=(events,editor)=>{if(ed=editor,event=events,filename="".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")),localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,editor.key=function(editor){switch(editor.button){case 0:return"left";case 1:return"middle";case 2:return"right"}return null}(editor),editor.keyCode=editor.button}function getCaretPosition(){let skip=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{if(!editor||!editor.selection)return{caretPosition:0,rePosition:0};const range=editor.selection.getRng(),body=editor.getBody(),preCaretRange=range.cloneRange();preCaretRange.selectNodeContents(body),preCaretRange.setEnd(range.endContainer,range.endOffset);const fragment=preCaretRange.cloneContents(),tempDiv=document.createElement("div");tempDiv.appendChild(fragment);let textBeforeCursor=tempDiv.innerText||"";const endContainer=range.endContainer;0===range.endOffset&&endContainer.nodeType===Node.ELEMENT_NODE&&editor.dom.isBlock(endContainer)&&endContainer.previousSibling&&(textBeforeCursor+="\n");const blockElements=tempDiv.querySelectorAll("p, div, h1, h2, h3, h4, h5, h6, li");let emptyBlockCount=0;blockElements.forEach((block=>{""===(block.innerText||block.textContent||"").trim()&&1===block.childNodes.length&&"BR"===block.childNodes[0].nodeName&&emptyBlockCount++})),emptyBlockCount>0&&(textBeforeCursor+="\n".repeat(emptyBlockCount));const absolutePosition=textBeforeCursor.length;if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey="".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:lastCaretPos||1,rePosition:0}}}async function syncData(){let data=localStorage.getItem(filename);if(data&&0!==data.length){localStorage.removeItem(filename);let originalText=editor.getContent({format:"text"});try{return _cursive_autosave.default.updateSavingState("saving"),await postOne("cursive_write_local_to_json",{key:ed.key,event:event,keyCode:ed.keyCode,resourceId:resourceId,cmid:cmid,modulename:modulename,editorid:editorid,json_data:data,originalText:originalText})}catch(error){window.console.error("Error submitting data:",error)}}}function customTooltip(){try{const tooltipText=async function(){const[buttonTitle,buttonDes]=await Promise.all([(0,_str.get_string)("cursive:state:active","tiny_cursive"),(0,_str.get_string)("cursive:state:active:des","tiny_cursive")]);return{buttonTitle:buttonTitle,buttonDes:buttonDes}}(),menubarDiv=document.querySelectorAll('div[role="menubar"].tox-menubar');let classArray=[];menubarDiv.length&&menubarDiv.forEach((function(element,index){let className="cursive-menu-"+(index+=1);element.classList.add(className),classArray.push(className)}));const cursiveIcon=document.createElement("img");cursiveIcon.src=hasApiKey?_common.iconUrl:_common.iconGrayUrl,cursiveIcon.setAttribute("class","tiny_cursive_StateButton"),cursiveIcon.style.display="inline-block",function(cursiveIcon,menubarDiv,classArray){if(!menubarDiv)return;for(let index in classArray){const rightWrapper=document.createElement("div"),imgWrapper=document.createElement("span"),iconClone=cursiveIcon.cloneNode(!0),targetMenu=document.querySelector("."+classArray[index]);let elementId="tiny_cursive_StateIcon"+index;rightWrapper.style.cssText="\n margin-left: auto;\n display: flex;\n align-items: center;\n ",imgWrapper.id=elementId,imgWrapper.style.marginLeft=".2rem",imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper);let moduleIds={resourceId:resourceId,cmid:cmid,modulename:modulename,questionid:questionid,userid:userid,courseid:courseid};if(!isFullScreen||"assign"!==modulename&&"forum"!==modulename&&"lesson"!==modulename)if(isFullScreen&&"quiz"===modulename){var _editor$container,_editor$container$chi,_editor$container$chi2,_editor$container$chi3,_editor$container2;let existingElement=null===(_editor$container=editor.container)||void 0===_editor$container||null===(_editor$container$chi=_editor$container.childNodes[1])||void 0===_editor$container$chi||null===(_editor$container$chi2=_editor$container$chi.childNodes[0])||void 0===_editor$container$chi2||null===(_editor$container$chi3=_editor$container$chi2.childNodes[0])||void 0===_editor$container$chi3?void 0:_editor$container$chi3.childNodes[7],newHeader=null===(_editor$container2=editor.container)||void 0===_editor$container2?void 0:_editor$container2.childNodes[0];existingElement&&existingElement.remove(),newHeader&&!newHeader.querySelector("span[id*=tiny_cursive_StateIcon]")&&(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{var _editor$container3,_editor$container3$ch,_editor$container3$ch2;let menubar=null==editor||null===(_editor$container3=editor.container)||void 0===_editor$container3||null===(_editor$container3$ch=_editor$container3.children[0])||void 0===_editor$container3$ch||null===(_editor$container3$ch2=_editor$container3$ch.childNodes[0])||void 0===_editor$container3$ch2?void 0:_editor$container3$ch2.childNodes[0];if(targetMenu&&!targetMenu.querySelector("#".concat(elementId))&&targetMenu.appendChild(rightWrapper),"quiz"===modulename&&menubar){let wrapper=menubar.querySelector('span[id*="tiny_cursive_StateIcon"]');wrapper&&(_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,null==wrapper?void 0:wrapper.parentElement,moduleIds,isFullScreen))}else _cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{let existsElement=document.querySelector('.tox-menubar[class*="cursive-menu-"] > div');existsElement&&existsElement.remove(),document.querySelector("#".concat(elementId))||(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId="tiny_cursive_tooltip".concat(index);tooltipText.then((text=>setTooltip(text,document.querySelector("#".concat(elementId)),tooltipId))).catch((error=>window.console.error(error))),(0,_jquery.default)("#".concat(elementId)).on("mouseenter",(function(){(0,_jquery.default)(this).css("position","relative"),(0,_jquery.default)("#".concat(tooltipId)).css(_common.tooltipCss)})),(0,_jquery.default)("#".concat(elementId)).on("mouseleave",(function(){(0,_jquery.default)("#".concat(tooltipId)).css("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)}}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyUp",editor)})),editor.on("Paste",(async e=>{customTooltip();const pastedContent=(e.clipboardData||e.originalEvent.clipboardData).getData("text");if(!pastedContent)return;const trimmedPastedContent=pastedContent.trim(),lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&trimmedPastedContent===lastCopyCutContent;if(isStudent&&intervention){if("block"===PASTE_SETTING)return isFromOwnEditor?(shouldBlockPaste=!1,void(isPasteAllowed=!0)):(e.preventDefault(),shouldBlockPaste=!0,isPasteAllowed=!1,e.stopPropagation(),e.stopImmediatePropagation(),(0,_str.get_string)("paste_blocked","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error))),void setTimeout((()=>{isPasteAllowed=!0,shouldBlockPaste=!1}),100));if("cite_source"===PASTE_SETTING)return isFromOwnEditor||(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),getModal()),void(isPasteAllowed=!0)}isPasteAllowed=!0})),editor.on("Redo",(async e=>{customTooltip(),isStudent&&intervention&&getModal()})),editor.on("keyDown",(editor=>{customTooltip();if(("v"===editor.key||"V"===editor.key)&&(editor.ctrlKey||editor.metaKey)&&isStudent&&intervention&&"block"===PASTE_SETTING&&!isPasteAllowed)return void setTimeout((()=>{isPasteAllowed=!0}),100);let position=getCaretPosition();editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyDown",editor)})),editor.on("Cut",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("Copy",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("mouseDown",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)}),0)})),editor.on("mouseUp",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)}),10)})),editor.on("init",(()=>{customTooltip(),localStorage.removeItem("lastCopyCutContent")})),editor.on("SetContent",(()=>{customTooltip()})),editor.on("FullscreenStateChanged",(e=>{let view=new _document_view.default(user,Rubrics,submission,modulename,editor,quizInfo);isFullScreen=e.state;try{e.state?view.fullPageMode():view.normalMode()}catch(error){errorAlert&&(errorAlert=!1,(0,_str.get_string)("fullmodeerror","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))),view.normalMode(),window.console.error("Error ResizeEditor event:",error)}})),editor.on("execcommand",(function(e){if("mceInsertContent"===e.command){const contentObj=e.value,isPaste=contentObj&&"object"==typeof contentObj&&!0===contentObj.paste;let insertedContent=contentObj.content||contentObj,tempDiv=document.createElement("div");tempDiv.innerHTML=insertedContent;let text=tempDiv.textContent||tempDiv.innerText||"",pastedText=tempDiv.textContent||tempDiv.innerText||"",position=getCaretPosition(!0);if(editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,isPaste){if(shouldBlockPaste)return shouldBlockPaste=!1,e.preventDefault(),void editor.undoManager.undo();const lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&pastedText.trim()===lastCopyCutContent;if(isStudent&&intervention&&"block"===PASTE_SETTING&&!isFromOwnEditor)return isPasteAllowed=!1,void editor.undoManager.undo();sendKeyEvent("Paste",{key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition,pastedContent:pastedText,srcElement:{baseURI:window.location.href}})}else aiContents.push(text),sendKeyEvent("aiInsert",{key:"ai",keyCode:0,caretPosition:editor.caretPosition,rePosition:editor.rePosition,aiContent:text,srcElement:{baseURI:window.location.href}})}})),editor.on("input",(function(e){let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition;let aiContent=e.data;("insertReplacementText"===e.inputType||"insertText"===e.inputType&&aiContent&&aiContent.length>1)&&(aiContents.push(aiContent),e.key="ai",e.keyCode=0,e.caretPosition=position.caretPosition,e.rePosition=position.rePosition,e.aiContent=aiContent,sendKeyEvent("aiInsert",e))})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}})); //# sourceMappingURL=autosaver.min.js.map \ No newline at end of file diff --git a/amd/build/autosaver.min.js.map b/amd/build/autosaver.min.js.map index 31ebb527..a0af08aa 100644 --- a/amd/build/autosaver.min.js.map +++ b/amd/build/autosaver.min.js.map @@ -1 +1 @@ -{"version":3,"file":"autosaver.min.js","sources":["../src/autosaver.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module tiny_cursive/autosaver\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\nimport {call} from 'core/ajax';\nimport {create} from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\nimport {save, cancel, hidden} from 'core/modal_events';\nimport $ from 'jquery';\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\nimport Autosave from 'tiny_cursive/cursive_autosave';\nimport DocumentView from 'tiny_cursive/document_view';\nimport {call as getUser} from \"core/ajax\";\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\n\n var isStudent = !($('#body').hasClass('teacher_admin'));\n var intervention = $('#body').hasClass('intervention');\n var host = M.cfg.wwwroot;\n var userid = userId;\n var courseid = M.cfg.courseId;\n var editorid = editor?.id;\n var cmid = M.cfg.contextInstanceId;\n var ed = \"\";\n var event = \"\";\n var filename = \"\";\n var questionid = 0;\n var quizSubmit = $('#mod_quiz-next-nav');\n let assignSubmit = $('#id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let aiContents = [];\n var isFullScreen = false;\n var user = null;\n let ur = window.location.href;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n var resourceId = modulesInfo.resourceId;\n var modulename = modulesInfo.name;\n var errorAlert = true;\n let PASTE_SETTING = pasteSetting || 'allow';\n let shouldBlockPaste = false;\n let isPasteAllowed = false;\n\n if (modulename !== 'assign') {\n PASTE_SETTING = 'cite_source';\n }\n const postOne = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n if (response) {\n setTimeout(() => {\n Autosave.updateSavingState('saved');\n }, 1000);\n }\n return response;\n } catch (error) {\n Autosave.updateSavingState('offline');\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n getUser([{\n methodname: 'core_user_get_users_by_field',\n args: {field: 'id', values: [M.cfg.userId]},\n }])[0].done(response => {\n user = response[0];\n }).fail((ex) => {\n window.console.error('Error fetching user data:', ex);\n });\n\n assignSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n assignSubmit.off('click').click();\n });\n } else {\n assignSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n quizSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n quizSubmit.off('click').click();\n });\n } else {\n quizSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n const getModal = () => {\n\n Promise.all([\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\n getString('tiny_cursive_placeholder', 'tiny_cursive')\n ]).then(function([title, titledes, placeholder]) {\n\n return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n })\n .done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n\n modal.getRoot().on(save, function() {\n\n var number = document.getElementById(\"inputUrl\").value.trim();\n\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n // eslint-disable-next-line\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\n }\n\n postOne('cursive_user_comments', {\n modulename: modulename,\n cmid: cmid,\n resourceid: resourceId,\n courseid: courseid,\n usercomment: number,\n timemodified: Date.now(),\n editorid: editorid ? editorid : \"\"\n });\n\n lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\n });\n\n modal.getRoot().on(hidden, function() {\n if (lastEvent != 'cancel' && lastEvent != 'save') {\n editor.execCommand('Undo');\n }\n });\n return modal;\n });\n }).catch(error => window.console.error(error));\n\n };\n\n const sendKeyEvent = (events, editor) => {\n ed = editor;\n event = events;\n\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\n editor.on('Paste', async(e) => {\n customTooltip();\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\n if (!pastedContent) {\n return;\n }\n // Trim both values for consistent comparison\n const trimmedPastedContent = pastedContent.trim();\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\n\n if (isStudent && intervention) {\n\n if (PASTE_SETTING === 'block') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n shouldBlockPaste = true;\n isPasteAllowed = false;\n e.stopPropagation();\n e.stopImmediatePropagation();\n getString('paste_blocked', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n setTimeout(() => {\n isPasteAllowed = true;\n shouldBlockPaste = false;\n }, 100);\n return;\n }\n shouldBlockPaste = false;\n isPasteAllowed = true;\n return;\n }\n if (PASTE_SETTING === 'cite_source') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n getModal(e);\n }\n isPasteAllowed = true;\n return;\n }\n }\n isPasteAllowed = true;\n });\n editor.on('Redo', async(e) => {\n customTooltip();\n if (isStudent && intervention) {\n getModal(e);\n }\n });\n editor.on('keyDown', (editor) => {\n customTooltip();\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\n (editor.ctrlKey || editor.metaKey);\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\n setTimeout(() => {\n isPasteAllowed = true;\n }, 100);\n return;\n }\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('mouseDown', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n }, 0);\n });\n editor.on('mouseUp', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n }, 10);\n });\n editor.on('init', () => {\n customTooltip();\n localStorage.removeItem('lastCopyCutContent');\n });\n editor.on('SetContent', () => {\n customTooltip();\n });\n editor.on('FullscreenStateChanged', (e) => {\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\n isFullScreen = e.state;\n try {\n if (!e.state) {\n view.normalMode();\n } else {\n view.fullPageMode();\n }\n } catch (error) {\n if (errorAlert) {\n errorAlert = false;\n getString('fullmodeerror', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n view.normalMode();\n window.console.error('Error ResizeEditor event:', error);\n }\n });\n\n editor.on('execcommand', function(e) {\n if (e.command === \"mceInsertContent\") {\n const contentObj = e.value;\n\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\n\n let insertedContent = contentObj.content || contentObj;\n let tempDiv = document.createElement('div');\n tempDiv.innerHTML = insertedContent;\n let text = tempDiv.textContent || tempDiv.innerText || '';\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\n\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n\n if (isPaste) {\n if (shouldBlockPaste) {\n shouldBlockPaste = false;\n e.preventDefault();\n editor.undoManager.undo();\n return;\n }\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\n\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\n isPasteAllowed = false;\n editor.undoManager.undo();\n return;\n }\n\n sendKeyEvent(\"Paste\", {\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n pastedContent: pastedText,\n srcElement: {baseURI: window.location.href}\n });\n } else {\n aiContents.push(text);\n\n sendKeyEvent(\"aiInsert\", {\n key: \"ai\",\n keyCode: 0,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n aiContent: text,\n srcElement: {baseURI: window.location.href}\n });\n }\n }\n });\n\n editor.on('input', function(e) {\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n let aiContent = e.data;\n\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\n\n aiContents.push(aiContent);\n\n e.key = \"ai\";\n e.keyCode = 0;\n e.caretPosition = position.caretPosition;\n e.rePosition = position.rePosition;\n e.aiContent = aiContent;\n\n sendKeyEvent(\"aiInsert\", e);\n }\n });\n\n\n /**\n * Constructs a mouse event object with caret position and button information\n * @param {Object} editor - The TinyMCE editor instance\n * @function constructMouseEvent\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\n */\n function constructMouseEvent(editor) {\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n editor.key = getMouseButton(editor);\n editor.keyCode = editor.button;\n }\n\n /**\n * Gets the string representation of a mouse button based on its numeric value\n * @param {Object} editor - The editor object containing button information\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\n */\n function getMouseButton(editor) {\n\n switch (editor.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n }\n return null;\n }\n\n /**\n * Gets the current caret position in the editor\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\n * @returns {Object} Object containing:\n * - caretPosition: Sequential position number stored in session\n * - rePosition: Absolute character offset from start of content\n * @throws {Error} Logs warning to console if error occurs during calculation\n */\n function getCaretPosition(skip = false) {\n try {\n if (!editor || !editor.selection) {\n return {caretPosition: 0, rePosition: 0};\n }\n\n const range = editor.selection.getRng();\n const body = editor.getBody();\n\n // Create a range from start of document to current caret\n const preCaretRange = range.cloneRange();\n preCaretRange.selectNodeContents(body);\n preCaretRange.setEnd(range.endContainer, range.endOffset);\n\n const fragment = preCaretRange.cloneContents();\n const tempDiv = document.createElement('div');\n tempDiv.appendChild(fragment);\n let textBeforeCursor = tempDiv.innerText || '';\n\n const endContainer = range.endContainer;\n const endOffset = range.endOffset;\n\n if (endOffset === 0 &&\n endContainer.nodeType === Node.ELEMENT_NODE &&\n editor.dom.isBlock(endContainer) &&\n endContainer.previousSibling) {\n textBeforeCursor += '\\n';\n }\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\n let emptyBlockCount = 0;\n blockElements.forEach(block => {\n const text = block.innerText || block.textContent || '';\n if (text.trim() === '' && block.childNodes.length === 1 &&\n block.childNodes[0].nodeName === 'BR') {\n emptyBlockCount++;\n }\n });\n\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\n if (emptyBlockCount > 0) {\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\n }\n\n const absolutePosition = textBeforeCursor.length;\n\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\n // Increment sequential caretPosition\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\n if (isNaN(storedPos)) {\n storedPos = 0;\n }\n storedPos++;\n lastCaretPos = storedPos;\n sessionStorage.setItem(storageKey, storedPos);\n\n return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\n }\n }\n\n\n /**\n * Synchronizes data from localStorage to server\n * @async\n * @function SyncData\n * @description Retrieves stored keypress data from localStorage and sends it to server\n * @returns {Promise} Returns response from server if data exists and is successfully sent\n * @throws {Error} Logs error to console if data submission fails\n */\n async function syncData() {\n\n let data = localStorage.getItem(filename);\n\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n let originalText = editor.getContent({format: 'text'});\n try {\n Autosave.updateSavingState('saving');\n // eslint-disable-next-line\n return await postOne('cursive_write_local_to_json', {\n key: ed.key,\n event: event,\n keyCode: ed.keyCode,\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n editorid: editorid,\n \"json_data\": data,\n originalText: originalText\n });\n } catch (error) {\n window.console.error('Error submitting data:', error);\n }\n }\n }\n /**\n * Sets up custom tooltip functionality for the Cursive icon\n * Initializes tooltip text, positions the icon in the menubar,\n * and sets up mouse event handlers for showing/hiding the tooltip\n * @function customTooltip\n */\n function customTooltip() {\n try {\n const tooltipText = getTooltipText();\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\n let classArray = [];\n\n if (menubarDiv.length) {\n menubarDiv.forEach(function(element, index) {\n index += 1;\n let className = 'cursive-menu-' + index;\n element.classList.add(className);\n classArray.push(className);\n });\n }\n\n const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n\n cursiveState(cursiveIcon, menubarDiv, classArray);\n\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n\n $(`#${elementId}`).on('mouseenter', function() {\n $(this).css('position', 'relative');\n $(`#${tooltipId}`).css(tooltipCss);\n });\n\n $(`#${elementId}`).on('mouseleave', function() {\n $(`#${tooltipId}`).css('display', 'none');\n });\n }\n } catch (error) {\n window.console.error('Error setting up custom tooltip:', error);\n }\n }\n\n /**\n * Retrieves tooltip text strings from language files\n * @async\n * @function getTooltipText\n * @returns {Promise} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [\n buttonTitle,\n buttonDes,\n ] = 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 return;\n }\n\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\n rightWrapper.style.cssText = `\n margin-left: auto;\n display: flex;\n align-items: center;\n `;\n\n imgWrapper.id = elementId;\n imgWrapper.style.marginLeft = '.2rem';\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n\n let moduleIds = {\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n questionid: questionid,\n userid: userid,\n courseid: courseid};\n // Document mode, other modules single editor instances\n if (isFullScreen && (modulename === 'assign' || modulename === 'forum'\n || modulename === 'lesson')) {\n let existsElement = document.querySelector('.tox-menubar[class*=\"cursive-menu-\"] > div');\n if (existsElement) {\n existsElement.remove();\n }\n\n if (!document.querySelector(`#${elementId}`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else if (isFullScreen && modulename === 'quiz') { // Document mode, quiz multiple editor instances\n let existingElement = editor.container?.childNodes[1]?.childNodes[0]?.childNodes[0]?.childNodes[7];\n let newHeader = editor.container?.childNodes[0];\n if (existingElement) {\n existingElement.remove();\n }\n\n if (newHeader && !newHeader.querySelector(`span[id*=tiny_cursive_StateIcon]`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else { // Regular view\n let menubar = editor?.container?.children[0]?.childNodes[0]?.childNodes[0];\n\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n // Regular view, multiple editor instances\n if (modulename === 'quiz' && menubar) {\n let wrapper = menubar.querySelector('span[id*=\"tiny_cursive_StateIcon\"]');\n\n if (wrapper) {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, wrapper?.parentElement, moduleIds, isFullScreen);\n }\n } else {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\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\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n\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\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\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 fetchStrings();\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\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\n if (resourceId === null) {\n resourceId = 0;\n }\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\n return {resourceId: resourceId, name: modulename};\n }\n\n /**\n * Fetches and caches localized strings used in the UI\n * @function fetchStrings\n * @description Retrieves strings for sidebar titles and document sidebar elements if not already cached in localStorage\n * Uses Promise.all to fetch multiple strings in parallel for better performance\n * Stores the fetched strings in localStorage under 'sbTitle' and 'docSideBar' keys\n */\n function fetchStrings() {\n if (!localStorage.getItem('sbTitle')) {\n Promise.all([\n getString('assignment', 'tiny_cursive'),\n getString('discussion', 'tiny_cursive'),\n getString('pluginname', 'mod_quiz'),\n getString('pluginname', 'mod_lesson'),\n getString('description', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('sbTitle', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n if (!localStorage.getItem('docSideBar')) {\n Promise.all([\n getString('details', 'tiny_cursive'),\n getString('student_info', 'tiny_cursive'),\n getString('progress', 'tiny_cursive'),\n getString('description', 'tiny_cursive'),\n getString('replyingto', 'tiny_cursive'),\n getString('answeringto', 'tiny_cursive'),\n getString('importantdates', 'tiny_cursive'),\n getString('rubrics', 'tiny_cursive'),\n getString('submission_status', 'tiny_cursive'),\n getString('status', 'tiny_cursive'),\n getString('draft', 'tiny_cursive'),\n getString('draftnot', 'tiny_cursive'),\n getString('last_modified', 'tiny_cursive'),\n getString('gradings', 'tiny_cursive'),\n getString('gradenot', 'tiny_cursive'),\n getString('word_count', 'tiny_cursive'),\n getString('timeleft', 'tiny_cursive'),\n getString('nolimit', 'tiny_cursive'),\n getString('name', 'tiny_cursive'),\n getString('userename', 'tiny_cursive'),\n getString('course', 'tiny_cursive'),\n getString('opened', 'tiny_cursive'),\n getString('due', 'tiny_cursive'),\n getString('overdue', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n getString('savechanges', 'tiny_cursive'),\n getString('subjectnot', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('docSideBar', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","Rubrics","submission","quizInfo","pasteSetting","isStudent","hasClass","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","questionid","quizSubmit","assignSubmit","syncInterval","lastCaretPos","aiContents","isFullScreen","user","ur","window","location","href","modulesInfo","parm","localStorage","getItem","Promise","all","then","strings","setItem","JSON","stringify","catch","error","console","fetchStrings","some","module","includes","resourceId","searchParams","get","modulename","name","getModulesInfo","URL","errorAlert","PASTE_SETTING","shouldBlockPaste","isPasteAllowed","postOne","async","methodname","args","response","setTimeout","updateSavingState","field","values","done","fail","ex","on","e","preventDefault","syncData","off","click","removeItem","getModal","title","titledes","placeholder","type","body","removeOnClose","modal","getRoot","addClass","show","lastEvent","save","number","document","getElementById","value","trim","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","sendKeyEvent","events","split","data","parse","push","key","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","aiContent","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","range","getRng","getBody","preCaretRange","cloneRange","selectNodeContents","setEnd","endContainer","endOffset","fragment","cloneContents","tempDiv","createElement","appendChild","textBeforeCursor","innerText","nodeType","Node","ELEMENT_NODE","dom","isBlock","previousSibling","blockElements","querySelectorAll","emptyBlockCount","forEach","block","textContent","childNodes","length","nodeName","repeat","absolutePosition","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","originalText","getContent","format","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","classArray","element","index","className","classList","add","cursiveIcon","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","querySelector","elementId","cssText","marginLeft","moduleIds","existingElement","container","_editor$container","_editor$container$chi","_editor$container$chi2","_editor$container$chi3","newHeader","_editor$container2","remove","marginTop","prepend","destroyInstance","getInstance","menubar","_editor$container3","children","_editor$container3$ch","_editor$container3$ch2","wrapper","parentElement","existsElement","cursiveState","tooltipId","text","setTooltip","this","css","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","clipboardData","originalEvent","getData","trimmedPastedContent","lastCopyCutContent","isFromOwnEditor","stopPropagation","stopImmediatePropagation","windowManager","ctrlKey","metaKey","selectedContent","view","DocumentView","state","fullPageMode","normalMode","command","contentObj","isPaste","paste","insertedContent","content","innerHTML","pastedText","undoManager","undo","srcElement","baseURI","inputType","addEventListener","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 $ from 'jquery';\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\nimport Autosave from 'tiny_cursive/cursive_autosave';\nimport DocumentView from 'tiny_cursive/document_view';\nimport {call as getUser} from \"core/ajax\";\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\n\n var isStudent = !($('#body').hasClass('teacher_admin'));\n var intervention = $('#body').hasClass('intervention');\n var host = M.cfg.wwwroot;\n var userid = userId;\n var courseid = M.cfg.courseId;\n var editorid = editor?.id;\n var cmid = M.cfg.contextInstanceId;\n var ed = \"\";\n var event = \"\";\n var filename = \"\";\n var questionid = 0;\n var quizSubmit = $('#mod_quiz-next-nav');\n let assignSubmit = $('#id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let aiContents = [];\n var isFullScreen = false;\n var user = null;\n let ur = window.location.href;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n var resourceId = modulesInfo.resourceId;\n var modulename = modulesInfo.name;\n var errorAlert = true;\n let PASTE_SETTING = pasteSetting || 'allow';\n let shouldBlockPaste = false;\n let isPasteAllowed = false;\n\n if (modulename !== 'assign') {\n PASTE_SETTING = 'cite_source';\n }\n const postOne = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n if (response) {\n setTimeout(() => {\n Autosave.updateSavingState('saved');\n }, 1000);\n }\n return response;\n } catch (error) {\n Autosave.updateSavingState('offline');\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n getUser([{\n methodname: 'core_user_get_users_by_field',\n args: {field: 'id', values: [M.cfg.userId]},\n }])[0].done(response => {\n user = response[0];\n }).fail((ex) => {\n window.console.error('Error fetching user data:', ex);\n });\n\n assignSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n assignSubmit.off('click').click();\n });\n } else {\n assignSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n quizSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n quizSubmit.off('click').click();\n });\n } else {\n quizSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n const getModal = () => {\n\n Promise.all([\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\n getString('tiny_cursive_placeholder', 'tiny_cursive')\n ]).then(function([title, titledes, placeholder]) {\n\n return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n })\n .done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n\n modal.getRoot().on(save, function() {\n\n var number = document.getElementById(\"inputUrl\").value.trim();\n\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n // eslint-disable-next-line\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\n }\n\n postOne('cursive_user_comments', {\n modulename: modulename,\n cmid: cmid,\n resourceid: resourceId,\n courseid: courseid,\n usercomment: number,\n timemodified: Date.now(),\n editorid: editorid ? editorid : \"\"\n });\n\n lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\n });\n\n modal.getRoot().on(hidden, function() {\n if (lastEvent != 'cancel' && lastEvent != 'save') {\n editor.execCommand('Undo');\n }\n });\n return modal;\n });\n }).catch(error => window.console.error(error));\n\n };\n\n const sendKeyEvent = (events, editor) => {\n ed = editor;\n event = events;\n\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\n editor.on('Paste', async(e) => {\n customTooltip();\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\n if (!pastedContent) {\n return;\n }\n // Trim both values for consistent comparison\n const trimmedPastedContent = pastedContent.trim();\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\n\n if (isStudent && intervention) {\n\n if (PASTE_SETTING === 'block') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n shouldBlockPaste = true;\n isPasteAllowed = false;\n e.stopPropagation();\n e.stopImmediatePropagation();\n getString('paste_blocked', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n setTimeout(() => {\n isPasteAllowed = true;\n shouldBlockPaste = false;\n }, 100);\n return;\n }\n shouldBlockPaste = false;\n isPasteAllowed = true;\n return;\n }\n if (PASTE_SETTING === 'cite_source') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n getModal(e);\n }\n isPasteAllowed = true;\n return;\n }\n }\n isPasteAllowed = true;\n });\n editor.on('Redo', async(e) => {\n customTooltip();\n if (isStudent && intervention) {\n getModal(e);\n }\n });\n editor.on('keyDown', (editor) => {\n customTooltip();\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\n (editor.ctrlKey || editor.metaKey);\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\n setTimeout(() => {\n isPasteAllowed = true;\n }, 100);\n return;\n }\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('mouseDown', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n }, 0);\n });\n editor.on('mouseUp', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n }, 10);\n });\n editor.on('init', () => {\n customTooltip();\n localStorage.removeItem('lastCopyCutContent');\n });\n editor.on('SetContent', () => {\n customTooltip();\n });\n editor.on('FullscreenStateChanged', (e) => {\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\n isFullScreen = e.state;\n try {\n if (!e.state) {\n view.normalMode();\n } else {\n view.fullPageMode();\n }\n } catch (error) {\n if (errorAlert) {\n errorAlert = false;\n getString('fullmodeerror', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n view.normalMode();\n window.console.error('Error ResizeEditor event:', error);\n }\n });\n\n editor.on('execcommand', function(e) {\n if (e.command === \"mceInsertContent\") {\n const contentObj = e.value;\n\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\n\n let insertedContent = contentObj.content || contentObj;\n let tempDiv = document.createElement('div');\n tempDiv.innerHTML = insertedContent;\n let text = tempDiv.textContent || tempDiv.innerText || '';\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\n\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n\n if (isPaste) {\n if (shouldBlockPaste) {\n shouldBlockPaste = false;\n e.preventDefault();\n editor.undoManager.undo();\n return;\n }\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\n\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\n isPasteAllowed = false;\n editor.undoManager.undo();\n return;\n }\n\n sendKeyEvent(\"Paste\", {\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n pastedContent: pastedText,\n srcElement: {baseURI: window.location.href}\n });\n } else {\n aiContents.push(text);\n\n sendKeyEvent(\"aiInsert\", {\n key: \"ai\",\n keyCode: 0,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n aiContent: text,\n srcElement: {baseURI: window.location.href}\n });\n }\n }\n });\n\n editor.on('input', function(e) {\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n let aiContent = e.data;\n\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\n\n aiContents.push(aiContent);\n\n e.key = \"ai\";\n e.keyCode = 0;\n e.caretPosition = position.caretPosition;\n e.rePosition = position.rePosition;\n e.aiContent = aiContent;\n\n sendKeyEvent(\"aiInsert\", e);\n }\n });\n\n\n /**\n * Constructs a mouse event object with caret position and button information\n * @param {Object} editor - The TinyMCE editor instance\n * @function constructMouseEvent\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\n */\n function constructMouseEvent(editor) {\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n editor.key = getMouseButton(editor);\n editor.keyCode = editor.button;\n }\n\n /**\n * Gets the string representation of a mouse button based on its numeric value\n * @param {Object} editor - The editor object containing button information\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\n */\n function getMouseButton(editor) {\n\n switch (editor.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n }\n return null;\n }\n\n /**\n * Gets the current caret position in the editor\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\n * @returns {Object} Object containing:\n * - caretPosition: Sequential position number stored in session\n * - rePosition: Absolute character offset from start of content\n * @throws {Error} Logs warning to console if error occurs during calculation\n */\n function getCaretPosition(skip = false) {\n try {\n if (!editor || !editor.selection) {\n return {caretPosition: 0, rePosition: 0};\n }\n\n const range = editor.selection.getRng();\n const body = editor.getBody();\n\n // Create a range from start of document to current caret\n const preCaretRange = range.cloneRange();\n preCaretRange.selectNodeContents(body);\n preCaretRange.setEnd(range.endContainer, range.endOffset);\n\n const fragment = preCaretRange.cloneContents();\n const tempDiv = document.createElement('div');\n tempDiv.appendChild(fragment);\n let textBeforeCursor = tempDiv.innerText || '';\n\n const endContainer = range.endContainer;\n const endOffset = range.endOffset;\n\n if (endOffset === 0 &&\n endContainer.nodeType === Node.ELEMENT_NODE &&\n editor.dom.isBlock(endContainer) &&\n endContainer.previousSibling) {\n textBeforeCursor += '\\n';\n }\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\n let emptyBlockCount = 0;\n blockElements.forEach(block => {\n const text = block.innerText || block.textContent || '';\n if (text.trim() === '' && block.childNodes.length === 1 &&\n block.childNodes[0].nodeName === 'BR') {\n emptyBlockCount++;\n }\n });\n\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\n if (emptyBlockCount > 0) {\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\n }\n\n const absolutePosition = textBeforeCursor.length;\n\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\n // Increment sequential caretPosition\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\n if (isNaN(storedPos)) {\n storedPos = 0;\n }\n storedPos++;\n lastCaretPos = storedPos;\n sessionStorage.setItem(storageKey, storedPos);\n\n return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\n }\n }\n\n\n /**\n * Synchronizes data from localStorage to server\n * @async\n * @function SyncData\n * @description Retrieves stored keypress data from localStorage and sends it to server\n * @returns {Promise} Returns response from server if data exists and is successfully sent\n * @throws {Error} Logs error to console if data submission fails\n */\n async function syncData() {\n\n let data = localStorage.getItem(filename);\n\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n let originalText = editor.getContent({format: 'text'});\n try {\n Autosave.updateSavingState('saving');\n // eslint-disable-next-line\n return await postOne('cursive_write_local_to_json', {\n key: ed.key,\n event: event,\n keyCode: ed.keyCode,\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n editorid: editorid,\n \"json_data\": data,\n originalText: originalText\n });\n } catch (error) {\n window.console.error('Error submitting data:', error);\n }\n }\n }\n /**\n * Sets up custom tooltip functionality for the Cursive icon\n * Initializes tooltip text, positions the icon in the menubar,\n * and sets up mouse event handlers for showing/hiding the tooltip\n * @function customTooltip\n */\n function customTooltip() {\n try {\n const tooltipText = getTooltipText();\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\n let classArray = [];\n\n if (menubarDiv.length) {\n menubarDiv.forEach(function(element, index) {\n index += 1;\n let className = 'cursive-menu-' + index;\n element.classList.add(className);\n classArray.push(className);\n });\n }\n\n const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n\n cursiveState(cursiveIcon, menubarDiv, classArray);\n\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n\n $(`#${elementId}`).on('mouseenter', function() {\n $(this).css('position', 'relative');\n $(`#${tooltipId}`).css(tooltipCss);\n });\n\n $(`#${elementId}`).on('mouseleave', function() {\n $(`#${tooltipId}`).css('display', 'none');\n });\n }\n } catch (error) {\n window.console.error('Error setting up custom tooltip:', error);\n }\n }\n\n /**\n * Retrieves tooltip text strings from language files\n * @async\n * @function getTooltipText\n * @returns {Promise} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [\n buttonTitle,\n buttonDes,\n ] = 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 return;\n }\n\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\n rightWrapper.style.cssText = `\n margin-left: auto;\n display: flex;\n align-items: center;\n `;\n\n imgWrapper.id = elementId;\n imgWrapper.style.marginLeft = '.2rem';\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n\n let moduleIds = {\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n questionid: questionid,\n userid: userid,\n courseid: courseid};\n // Document mode, other modules single editor instances\n if (isFullScreen && (modulename === 'assign' || modulename === 'forum'\n || modulename === 'lesson')) {\n let existsElement = document.querySelector('.tox-menubar[class*=\"cursive-menu-\"] > div');\n if (existsElement) {\n existsElement.remove();\n }\n\n if (!document.querySelector(`#${elementId}`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else if (isFullScreen && modulename === 'quiz') { // Document mode, quiz multiple editor instances\n let existingElement = editor.container?.childNodes[1]?.childNodes[0]?.childNodes[0]?.childNodes[7];\n let newHeader = editor.container?.childNodes[0];\n if (existingElement) {\n existingElement.remove();\n }\n\n if (newHeader && !newHeader.querySelector(`span[id*=tiny_cursive_StateIcon]`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else { // Regular view\n let menubar = editor?.container?.children[0]?.childNodes[0]?.childNodes[0];\n\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n // Regular view, multiple editor instances\n if (modulename === 'quiz' && menubar) {\n let wrapper = menubar.querySelector('span[id*=\"tiny_cursive_StateIcon\"]');\n\n if (wrapper) {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, wrapper?.parentElement, moduleIds, isFullScreen);\n }\n } else {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\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\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n\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\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\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 fetchStrings();\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\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\n if (resourceId === null) {\n resourceId = 0;\n }\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\n return {resourceId: resourceId, name: modulename};\n }\n\n /**\n * Fetches and caches localized strings used in the UI\n * @function fetchStrings\n * @description Retrieves strings for sidebar titles and document sidebar elements if not already cached in localStorage\n * Uses Promise.all to fetch multiple strings in parallel for better performance\n * Stores the fetched strings in localStorage under 'sbTitle' and 'docSideBar' keys\n */\n function fetchStrings() {\n if (!localStorage.getItem('sbTitle')) {\n Promise.all([\n getString('assignment', 'tiny_cursive'),\n getString('discussion', 'tiny_cursive'),\n getString('pluginname', 'mod_quiz'),\n getString('pluginname', 'mod_lesson'),\n getString('description', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('sbTitle', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n if (!localStorage.getItem('docSideBar')) {\n Promise.all([\n getString('details', 'tiny_cursive'),\n getString('student_info', 'tiny_cursive'),\n getString('progress', 'tiny_cursive'),\n getString('description', 'tiny_cursive'),\n getString('replyingto', 'tiny_cursive'),\n getString('answeringto', 'tiny_cursive'),\n getString('importantdates', 'tiny_cursive'),\n getString('rubrics', 'tiny_cursive'),\n getString('submission_status', 'tiny_cursive'),\n getString('status', 'tiny_cursive'),\n getString('draft', 'tiny_cursive'),\n getString('draftnot', 'tiny_cursive'),\n getString('last_modified', 'tiny_cursive'),\n getString('gradings', 'tiny_cursive'),\n getString('gradenot', 'tiny_cursive'),\n getString('word_count', 'tiny_cursive'),\n getString('timeleft', 'tiny_cursive'),\n getString('nolimit', 'tiny_cursive'),\n getString('name', 'tiny_cursive'),\n getString('userename', 'tiny_cursive'),\n getString('course', 'tiny_cursive'),\n getString('opened', 'tiny_cursive'),\n getString('due', 'tiny_cursive'),\n getString('overdue', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n getString('savechanges', 'tiny_cursive'),\n getString('subjectnot', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('docSideBar', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","Rubrics","submission","quizInfo","pasteSetting","isStudent","hasClass","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","questionid","quizSubmit","assignSubmit","syncInterval","lastCaretPos","aiContents","isFullScreen","user","ur","window","location","href","modulesInfo","parm","localStorage","getItem","Promise","all","then","strings","setItem","JSON","stringify","catch","error","console","fetchStrings","some","module","includes","resourceId","searchParams","get","modulename","name","getModulesInfo","URL","errorAlert","PASTE_SETTING","shouldBlockPaste","isPasteAllowed","postOne","async","methodname","args","response","setTimeout","updateSavingState","field","values","done","fail","ex","on","e","preventDefault","syncData","off","click","removeItem","getModal","title","titledes","placeholder","type","body","removeOnClose","modal","getRoot","addClass","show","lastEvent","save","number","document","getElementById","value","trim","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","sendKeyEvent","events","split","data","parse","push","key","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","aiContent","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","range","getRng","getBody","preCaretRange","cloneRange","selectNodeContents","setEnd","endContainer","endOffset","fragment","cloneContents","tempDiv","createElement","appendChild","textBeforeCursor","innerText","nodeType","Node","ELEMENT_NODE","dom","isBlock","previousSibling","blockElements","querySelectorAll","emptyBlockCount","forEach","block","textContent","childNodes","length","nodeName","repeat","absolutePosition","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","originalText","getContent","format","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","classArray","element","index","className","classList","add","cursiveIcon","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","querySelector","elementId","cssText","marginLeft","moduleIds","existingElement","container","_editor$container","_editor$container$chi","_editor$container$chi2","_editor$container$chi3","newHeader","_editor$container2","remove","marginTop","prepend","destroyInstance","getInstance","menubar","_editor$container3","children","_editor$container3$ch","_editor$container3$ch2","wrapper","parentElement","existsElement","cursiveState","tooltipId","text","setTooltip","this","css","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","clipboardData","originalEvent","getData","trimmedPastedContent","lastCopyCutContent","isFromOwnEditor","stopPropagation","stopImmediatePropagation","windowManager","ctrlKey","metaKey","selectedContent","view","DocumentView","state","fullPageMode","normalMode","command","contentObj","isPaste","paste","insertedContent","content","innerHTML","pastedText","undoManager","undo","srcElement","baseURI","inputType","addEventListener","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 c2b8ed1c..408c6660 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"),iconSaving:M.util.image_url("rotate","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"),iconSaving:M.util.image_url("rotate","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 ddc7a5f5..a8da292b 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 iconSaving: M.util.image_url('rotate', '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","iconSaving","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,WAAYH,EAAEC,KAAKC,UAAU,SAAU,gBACvCE,YAAaJ,EAAEC,KAAKC,UAAU,eAAgB,gBAC9CG,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 iconSaving: M.util.image_url('rotate', '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","iconSaving","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,WAAYH,EAAEC,KAAKC,UAAU,SAAU,gBACvCE,YAAaJ,EAAEC,KAAKC,UAAU,eAAgB,gBAC9CG,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/cursive_autosave.min.js b/amd/build/cursive_autosave.min.js index 2ff57d50..17a4fbb5 100644 --- a/amd/build/cursive_autosave.min.js +++ b/amd/build/cursive_autosave.min.js @@ -1,10 +1,3 @@ -define("tiny_cursive/cursive_autosave",["exports","core/templates","core/ajax","tiny_cursive/svg_repo","core/str"],(function(_exports,_templates,_ajax,_svg_repo,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} -/** - * TODO describe module cursive_autosave - * - * @module tiny_cursive/cursive_autosave - * @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.default=void 0,_templates=_interopRequireDefault(_templates),_svg_repo=_interopRequireDefault(_svg_repo);class CursiveAutosave{static instance=null;constructor(editor,rightWrapper,modules,isFullScreen){if(CursiveAutosave.instance)return CursiveAutosave.instance;this.editor=editor,this.module=modules,this.savingState="",this.rightWrapper=rightWrapper,this.isFullScreen=isFullScreen,this.fetchSavedContent=this.fetchSavedContent.bind(this),this.handleEscapeKey=this.handleEscapeKey.bind(this),this._savingTimer=null,CursiveAutosave.instance=this,this.fetchStrings()}static getInstance(editor,rightWrapper,modules,isFullScreen){this.instance||(this.instance=new CursiveAutosave(editor,rightWrapper,modules,isFullScreen)),this.instance.isFullScreen=isFullScreen;return("quiz"===modules.modulename?document.querySelector(`#tiny_cursive_savingState${modules.questionid}`):document.querySelector("#tiny_cursive_savingState"))||this.instance.init(),this.instance}init(){const stateWrapper=this.cursiveSavingState(this.savingState);stateWrapper.classList.add("tiny_cursive_savingState","btn"),"quiz"===this.module.modulename?stateWrapper.id=`tiny_cursive_savingState${this.module.questionid}`:stateWrapper.id="tiny_cursive_savingState",this.rightWrapper.prepend(stateWrapper),stateWrapper.addEventListener("click",this.fetchSavedContent)}destroy(){CursiveAutosave.instance=null}static destroyInstance(){this.instance&&(this.instance.destroy(),this.instance=null)}cursiveSavingState(state){let wrapperDiv=document.createElement("div"),textSpan=document.createElement("span"),button=document.createElement("button"),iconSpan=document.createElement("span");return button.style.padding=".3rem",textSpan.style.fontSize="0.75rem",textSpan.style.color="gray","quiz"===this.module.modulename?(iconSpan.id=`CursiveCloudIcon${this.module.questionid}`,textSpan.id=`CursiveStateText${this.module.questionid}`):(iconSpan.id="CursiveCloudIcon",textSpan.id="CursiveStateText"),state&&(textSpan.textContent=this.getStateText(state),iconSpan.innerHTML=this.getStateIcon(state)),wrapperDiv.style.verticalAlign="middle",wrapperDiv.appendChild(iconSpan),wrapperDiv.appendChild(textSpan),button.appendChild(wrapperDiv),button}static updateSavingState(state){const instance=this.instance;instance.savingState=state;let stateWrapper=null;stateWrapper="quiz"===instance.module.modulename?document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`):document.querySelector("#tiny_cursive_savingState");let iconSpan="",stateTextEl="";stateWrapper&&("quiz"===instance.module.modulename?(iconSpan=stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`),stateTextEl=stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`)):(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon"),stateTextEl=stateWrapper.querySelector("#CursiveStateText")),stateTextEl&&iconSpan&&(stateTextEl.textContent=instance.getStateText(state),iconSpan.innerHTML=instance.getStateIcon(state)),instance._savingTimer&&clearTimeout(instance._savingTimer),"saved"===state&&stateTextEl&&(instance._savingTimer=setTimeout((()=>{stateTextEl.textContent=""}),5e3)))}getStateText(state){const[saving,saved,offline]=this.getText("state");switch(state){case"saving":return saving;case"saved":return saved;case"offline":return offline;default:return""}}getStateIcon(state){switch(state){case"saving":case"saved":return _svg_repo.default.cloudSave;case"offline":return"data:image/svg+xml;base64,"+btoa(_svg_repo.default.offline);default:return""}}async fetchSavedContent(e){var _dropdown$classList,_this$editor;e.preventDefault();let dropdown=document.querySelector("#savedDropdown");if(null==dropdown||null===(_dropdown$classList=dropdown.classList)||void 0===_dropdown$classList?void 0:_dropdown$classList.contains("show"))return void this.closeSavedDropdown();let editorWrapper=null;editorWrapper="quiz"===this.module.modulename?document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`):document.querySelector("#tiny_cursive_savingState");let args={id:this.module.resourceId,cmid:this.module.cmid,modulename:`${this.module.modulename}_autosave`,editorid:null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id,userid:this.module.userid,courseid:this.module.courseid};(0,_ajax.call)([{methodname:"cursive_get_autosave_content",args:args}])[0].done((data=>{let context={comments:JSON.parse(data)};Object.values(context.comments).forEach((content=>{content.time=this.timeAgo(content.timemodified)})),this.renderCommentList(context,editorWrapper)})).fail((error=>{this.throwWarning("fullmodeerrorr",this.editor),window.console.error("Error fetching saved content:",error)}))}toggleSavedDropdown(){var _dropdown$classList2;const dropdown=document.querySelector("#savedDropdown");(null==dropdown||null===(_dropdown$classList2=dropdown.classList)||void 0===_dropdown$classList2?void 0:_dropdown$classList2.contains("show"))?this.closeSavedDropdown():this.openSavedDropdown()}openSavedDropdown(){document.querySelector("#savedDropdown").classList.add("show"),document.removeEventListener("keydown",this.handleEscapeKey),document.addEventListener("keydown",this.handleEscapeKey)}closeSavedDropdown(){const dropdown=document.querySelector("#savedDropdown");dropdown&&(dropdown.classList.remove("show"),dropdown.remove(),document.removeEventListener("keydown",this.handleEscapeKey))}handleEscapeKey(event){"Escape"===event.key&&this.closeSavedDropdown()}timeAgo(unixTime){const seconds=Math.floor(Date.now()/1e3)-unixTime;if(seconds<5)return"just now";if(seconds<60)return`${seconds} sec ago`;const minutes=Math.floor(seconds/60);if(minutes<60)return`${minutes} min ago`;const hours=Math.floor(minutes/60);if(hours<24)return`${hours} hour${hours>1?"s":""} ago`;const days=Math.floor(hours/24);if(days<7)return`${days} day${days>1?"s":""} ago`;const weeks=Math.floor(days/7);if(weeks<4)return`${weeks} week${weeks>1?"s":""} ago`;const months=Math.floor(days/30);if(months<12)return`${months} month${months>1?"s":""} ago`;const years=Math.floor(days/365);return`${years} year${years>1?"s":""} ago`}renderCommentList(context,editorWrapper){_templates.default.render("tiny_cursive/saved_content",context).then((html=>{editorWrapper.style.position="relative";const tempDiv=document.createElement("div");if(tempDiv.innerHTML=html.trim(),tempDiv.id="savedDropdown",tempDiv.classList.add("tiny_cursive-saved-dropdown"),!tempDiv)return window.console.error("Saved content template rendered empty or invalid HTML."),!1;let existingPanel=document.querySelector("#savedDropdown");return existingPanel||(editorWrapper.appendChild(tempDiv),existingPanel=tempDiv),existingPanel.classList.toggle("active"),this.openSavedDropdown(),this.insertSavedItems(this.editor),!0})).catch((error=>window.console.error(error)))}fetchStrings(){localStorage.getItem("state")||Promise.all([(0,_str.get_string)("saving","tiny_cursive"),(0,_str.get_string)("saved","tiny_cursive"),(0,_str.get_string)("offline","tiny_cursive")]).then((function(strings){return localStorage.setItem("state",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}throwWarning(str,editor){(0,_str.get_string)(str,"tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))}getText(key){return JSON.parse(localStorage.getItem(key))}insertSavedItems(editor){document.querySelectorAll(".tiny_cursive-item-preview").forEach((element=>{element.addEventListener("click",(function(){editor.insertContent(" "+this.textContent)}))}))}}return _exports.default=CursiveAutosave,_exports.default})); +define("tiny_cursive/cursive_autosave",["exports","core/templates","core/ajax","tiny_cursive/svg_repo","core/str"],(function(_exports,_templates,_ajax,_svg_repo,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_svg_repo=_interopRequireDefault(_svg_repo);class CursiveAutosave{constructor(editor,rightWrapper,modules,isFullScreen){if(CursiveAutosave.instance)return CursiveAutosave.instance;this.editor=editor,this.module=modules,this.savingState="",this.rightWrapper=rightWrapper,this.isFullScreen=isFullScreen,this.fetchSavedContent=this.fetchSavedContent.bind(this),this.handleEscapeKey=this.handleEscapeKey.bind(this),this._savingTimer=null,CursiveAutosave.instance=this,this.fetchStrings()}static getInstance(editor,rightWrapper,modules,isFullScreen){this.instance||(this.instance=new CursiveAutosave(editor,rightWrapper,modules,isFullScreen)),this.instance.isFullScreen=isFullScreen;return("quiz"===modules.modulename?document.querySelector("#tiny_cursive_savingState".concat(modules.questionid)):document.querySelector("#tiny_cursive_savingState"))||this.instance.init(),this.instance}init(){const stateWrapper=this.cursiveSavingState(this.savingState);stateWrapper.classList.add("tiny_cursive_savingState","btn"),"quiz"===this.module.modulename?stateWrapper.id="tiny_cursive_savingState".concat(this.module.questionid):stateWrapper.id="tiny_cursive_savingState",this.rightWrapper.prepend(stateWrapper),stateWrapper.addEventListener("click",this.fetchSavedContent)}destroy(){CursiveAutosave.instance=null}static destroyInstance(){this.instance&&(this.instance.destroy(),this.instance=null)}cursiveSavingState(state){let wrapperDiv=document.createElement("div"),textSpan=document.createElement("span"),button=document.createElement("button"),iconSpan=document.createElement("span");return button.style.padding=".3rem",textSpan.style.fontSize="0.75rem",textSpan.style.color="gray","quiz"===this.module.modulename?(iconSpan.id="CursiveCloudIcon".concat(this.module.questionid),textSpan.id="CursiveStateText".concat(this.module.questionid)):(iconSpan.id="CursiveCloudIcon",textSpan.id="CursiveStateText"),state&&(textSpan.textContent=this.getStateText(state),iconSpan.innerHTML=this.getStateIcon(state)),wrapperDiv.style.verticalAlign="middle",wrapperDiv.appendChild(iconSpan),wrapperDiv.appendChild(textSpan),button.appendChild(wrapperDiv),button}static updateSavingState(state){const instance=this.instance;instance.savingState=state;let stateWrapper=null;stateWrapper="quiz"===instance.module.modulename?document.querySelector("#tiny_cursive_savingState".concat(instance.module.questionid)):document.querySelector("#tiny_cursive_savingState");let iconSpan="",stateTextEl="";stateWrapper&&("quiz"===instance.module.modulename?(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon".concat(instance.module.questionid)),stateTextEl=stateWrapper.querySelector("#CursiveStateText".concat(instance.module.questionid))):(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon"),stateTextEl=stateWrapper.querySelector("#CursiveStateText")),stateTextEl&&iconSpan&&(stateTextEl.textContent=instance.getStateText(state),iconSpan.innerHTML=instance.getStateIcon(state)),instance._savingTimer&&clearTimeout(instance._savingTimer),"saved"===state&&stateTextEl&&(instance._savingTimer=setTimeout((()=>{stateTextEl.textContent=""}),5e3)))}getStateText(state){const[saving,saved,offline]=this.getText("state");switch(state){case"saving":return saving;case"saved":return saved;case"offline":return offline;default:return""}}getStateIcon(state){switch(state){case"saving":case"saved":return _svg_repo.default.cloudSave;case"offline":return"data:image/svg+xml;base64,"+btoa(_svg_repo.default.offline);default:return""}}async fetchSavedContent(e){var _dropdown$classList,_this$editor;e.preventDefault();let dropdown=document.querySelector("#savedDropdown");if(null==dropdown||null===(_dropdown$classList=dropdown.classList)||void 0===_dropdown$classList?void 0:_dropdown$classList.contains("show"))return void this.closeSavedDropdown();let editorWrapper=null;editorWrapper="quiz"===this.module.modulename?document.querySelector("#tiny_cursive_savingState".concat(this.module.questionid)):document.querySelector("#tiny_cursive_savingState");let args={id:this.module.resourceId,cmid:this.module.cmid,modulename:"".concat(this.module.modulename,"_autosave"),editorid:null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id,userid:this.module.userid,courseid:this.module.courseid};(0,_ajax.call)([{methodname:"cursive_get_autosave_content",args:args}])[0].done((data=>{let context={comments:JSON.parse(data)};Object.values(context.comments).forEach((content=>{content.time=this.timeAgo(content.timemodified)})),this.renderCommentList(context,editorWrapper)})).fail((error=>{this.throwWarning("fullmodeerrorr",this.editor),window.console.error("Error fetching saved content:",error)}))}toggleSavedDropdown(){var _dropdown$classList2;const dropdown=document.querySelector("#savedDropdown");(null==dropdown||null===(_dropdown$classList2=dropdown.classList)||void 0===_dropdown$classList2?void 0:_dropdown$classList2.contains("show"))?this.closeSavedDropdown():this.openSavedDropdown()}openSavedDropdown(){document.querySelector("#savedDropdown").classList.add("show"),document.removeEventListener("keydown",this.handleEscapeKey),document.addEventListener("keydown",this.handleEscapeKey)}closeSavedDropdown(){const dropdown=document.querySelector("#savedDropdown");dropdown&&(dropdown.classList.remove("show"),dropdown.remove(),document.removeEventListener("keydown",this.handleEscapeKey))}handleEscapeKey(event){"Escape"===event.key&&this.closeSavedDropdown()}timeAgo(unixTime){const seconds=Math.floor(Date.now()/1e3)-unixTime;if(seconds<5)return"just now";if(seconds<60)return"".concat(seconds," sec ago");const minutes=Math.floor(seconds/60);if(minutes<60)return"".concat(minutes," min ago");const hours=Math.floor(minutes/60);if(hours<24)return"".concat(hours," hour").concat(hours>1?"s":""," ago");const days=Math.floor(hours/24);if(days<7)return"".concat(days," day").concat(days>1?"s":""," ago");const weeks=Math.floor(days/7);if(weeks<4)return"".concat(weeks," week").concat(weeks>1?"s":""," ago");const months=Math.floor(days/30);if(months<12)return"".concat(months," month").concat(months>1?"s":""," ago");const years=Math.floor(days/365);return"".concat(years," year").concat(years>1?"s":""," ago")}renderCommentList(context,editorWrapper){_templates.default.render("tiny_cursive/saved_content",context).then((html=>{editorWrapper.style.position="relative";const tempDiv=document.createElement("div");if(tempDiv.innerHTML=html.trim(),tempDiv.id="savedDropdown",tempDiv.classList.add("tiny_cursive-saved-dropdown"),!tempDiv)return window.console.error("Saved content template rendered empty or invalid HTML."),!1;let existingPanel=document.querySelector("#savedDropdown");return existingPanel||(editorWrapper.appendChild(tempDiv),existingPanel=tempDiv),existingPanel.classList.toggle("active"),this.openSavedDropdown(),this.insertSavedItems(this.editor),!0})).catch((error=>window.console.error(error)))}fetchStrings(){localStorage.getItem("state")||Promise.all([(0,_str.get_string)("saving","tiny_cursive"),(0,_str.get_string)("saved","tiny_cursive"),(0,_str.get_string)("offline","tiny_cursive")]).then((function(strings){return localStorage.setItem("state",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}throwWarning(str,editor){(0,_str.get_string)(str,"tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))}getText(key){return JSON.parse(localStorage.getItem(key))}insertSavedItems(editor){document.querySelectorAll(".tiny_cursive-item-preview").forEach((element=>{element.addEventListener("click",(function(){editor.insertContent(" "+this.textContent)}))}))}}var obj,key,value;return _exports.default=CursiveAutosave,value=null,(key="instance")in(obj=CursiveAutosave)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,_exports.default})); //# sourceMappingURL=cursive_autosave.min.js.map \ No newline at end of file diff --git a/amd/build/cursive_autosave.min.js.map b/amd/build/cursive_autosave.min.js.map index f0ea83bb..56db2bab 100644 --- a/amd/build/cursive_autosave.min.js.map +++ b/amd/build/cursive_autosave.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cursive_autosave.min.js","sources":["../src/cursive_autosave.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 * TODO describe module cursive_autosave\n *\n * @module tiny_cursive/cursive_autosave\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport templates from 'core/templates';\nimport {call} from 'core/ajax';\nimport Icons from 'tiny_cursive/svg_repo';\nimport {get_string as getString} from 'core/str';\n\nexport default class CursiveAutosave {\n\n static instance = null;\n\n\n constructor(editor, rightWrapper, modules, isFullScreen) {\n if (CursiveAutosave.instance) {\n return CursiveAutosave.instance;\n }\n\n this.editor = editor;\n this.module = modules;\n this.savingState = '';\n this.rightWrapper = rightWrapper;\n this.isFullScreen = isFullScreen;\n // Bind methods that will be used as event listener\n this.fetchSavedContent = this.fetchSavedContent.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this._savingTimer = null;\n CursiveAutosave.instance = this;\n this.fetchStrings();\n }\n\n static getInstance(editor, rightWrapper, modules, isFullScreen) {\n if (!this.instance) {\n this.instance = new CursiveAutosave(editor, rightWrapper, modules, isFullScreen);\n }\n this.instance.isFullScreen = isFullScreen;\n const hasState = modules.modulename === 'quiz'\n ? document.querySelector(`#tiny_cursive_savingState${modules.questionid}`)\n : document.querySelector('#tiny_cursive_savingState');\n if (!hasState) {\n this.instance.init();\n }\n\n return this.instance;\n }\n\n init() {\n const stateWrapper = this.cursiveSavingState(this.savingState);\n stateWrapper.classList.add('tiny_cursive_savingState', 'btn');\n if (this.module.modulename === 'quiz') {\n stateWrapper.id = `tiny_cursive_savingState${this.module.questionid}`;\n } else {\n stateWrapper.id = 'tiny_cursive_savingState';\n }\n\n this.rightWrapper.prepend(stateWrapper);\n stateWrapper.addEventListener('click', this.fetchSavedContent);\n }\n\n destroy() {\n CursiveAutosave.instance = null;\n }\n\n static destroyInstance() {\n if (this.instance) {\n this.instance.destroy();\n this.instance = null;\n }\n }\n\n /**\n * Creates a wrapper div containing an icon and text to display the saving state\n * @param {string} state - The current saving state ('saving', 'saved', or 'offline')\n * @returns {HTMLElement} A div element containing the state icon and text\n * @description Creates and returns a div element with an icon and text span to show the current saving state.\n * The icon and text are updated based on the provided state parameter.\n */\n cursiveSavingState(state) {\n let wrapperDiv = document.createElement('div');\n let textSpan = document.createElement('span');\n let button = document.createElement('button');\n let iconSpan = document.createElement('span');\n\n button.style.padding = '.3rem';\n textSpan.style.fontSize = '0.75rem';\n textSpan.style.color = 'gray';\n\n if (this.module.modulename === 'quiz') {\n iconSpan.id = `CursiveCloudIcon${this.module.questionid}`;\n textSpan.id = `CursiveStateText${this.module.questionid}`;\n } else {\n iconSpan.id = 'CursiveCloudIcon';\n textSpan.id = 'CursiveStateText';\n }\n if (state) {\n textSpan.textContent = this.getStateText(state);\n iconSpan.innerHTML = this.getStateIcon(state);\n }\n\n wrapperDiv.style.verticalAlign = 'middle';\n\n wrapperDiv.appendChild(iconSpan);\n wrapperDiv.appendChild(textSpan);\n button.appendChild(wrapperDiv);\n\n return button;\n }\n\n /**\n * Updates the saving state icon and text in the editor\n * @param {string} state - The state to update to ('saving', 'saved', or 'offline')\n * @description Updates the global saving state and modifies the UI elements to reflect the new state\n */\n static updateSavingState(state) {\n const instance = this.instance;\n instance.savingState = state;\n let stateWrapper = null;\n if (instance.module.modulename === 'quiz') {\n stateWrapper = document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`);\n } else {\n stateWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let iconSpan = '';\n let stateTextEl = '';\n\n if (!stateWrapper) {\n return;\n }\n\n if (instance.module.modulename === 'quiz') {\n iconSpan = stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`);\n stateTextEl = stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`);\n } else {\n iconSpan = stateWrapper.querySelector('#CursiveCloudIcon');\n stateTextEl = stateWrapper.querySelector('#CursiveStateText');\n }\n\n if (stateTextEl && iconSpan) {\n stateTextEl.textContent = instance.getStateText(state);\n iconSpan.innerHTML = instance.getStateIcon(state);\n }\n\n\n if (instance._savingTimer) {\n clearTimeout(instance._savingTimer);\n }\n\n if (state === 'saved' && stateTextEl) {\n instance._savingTimer = setTimeout(() => {\n stateTextEl.textContent = '';\n }, 5000);\n }\n }\n\n /**\n * Gets the display text for a given saving state\n * @param {string} state - The state to get text for ('saving', 'saved', or 'offline')\n * @returns {string} The text to display for the given state\n * @description Returns appropriate text label based on the current saving state\n */\n getStateText(state) {\n const [saving, saved, offline] = this.getText('state');\n switch (state) {\n case 'saving': return saving;\n case 'saved': return saved;\n case 'offline': return offline;\n default: return '';\n }\n }\n /**\n * Gets the icon URL for a given saving state\n * @param {string} state - The state to get icon for ('saving', 'saved', or 'offline')\n * @returns {string} The URL of the icon image for the given state\n * @description Returns appropriate icon URL based on the current saving state\n */\n getStateIcon(state) {\n switch (state) {\n case 'saving': return Icons.cloudSave;\n case 'saved': return Icons.cloudSave;\n case 'offline': return 'data:image/svg+xml;base64,' + btoa(Icons.offline);\n default: return '';\n }\n }\n\n /**\n * Fetches and displays saved content in a dropdown\n * @async\n * @param {Event} e - The event object\n * @description Handles fetching and displaying saved content when the save state button is clicked.\n * If the dropdown is already visible, it will be closed. Otherwise it will fetch saved content\n * from the server (or use cached content if available) and display it in a dropdown panel.\n * @throws {Error} Logs error to console if fetching content fails\n */\n async fetchSavedContent(e) {\n e.preventDefault();\n\n let dropdown = document.querySelector('#savedDropdown');\n let isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n return;\n }\n let editorWrapper = null;\n if (this.module.modulename === 'quiz') {\n editorWrapper = document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`);\n } else {\n editorWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let args = {\n id: this.module.resourceId,\n cmid: this.module.cmid,\n modulename: `${this.module.modulename}_autosave`,\n editorid: this.editor?.id,\n userid: this.module.userid,\n courseid: this.module.courseid\n };\n\n call([{\n methodname: \"cursive_get_autosave_content\",\n args: args\n }])[0].done((data) => {\n let context = {comments: JSON.parse(data)};\n Object.values(context.comments).forEach(content => {\n content.time = this.timeAgo(content.timemodified);\n });\n this.renderCommentList(context, editorWrapper);\n\n }).fail((error) => {\n this.throwWarning('fullmodeerrorr', this.editor);\n window.console.error('Error fetching saved content:', error);\n });\n }\n\n /**\n * Toggles the visibility of the saved content dropdown\n * @description Checks if the saved content dropdown is currently visible and either closes or opens it accordingly.\n * If visible, calls closeSavedDropdown(). If hidden, calls openSavedDropdown().\n */\n toggleSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n const isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n } else {\n this.openSavedDropdown();\n }\n }\n\n /**\n * Opens the saved content dropdown panel\n * @description Shows the saved content dropdown by adding the 'show' class and sets up an event listener\n * for the Escape key to allow closing the dropdown. This is called when toggling the dropdown open.\n */\n openSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n dropdown.classList.add('show');\n\n // Add event listener to close on Escape key\n document.removeEventListener('keydown', this.handleEscapeKey);\n document.addEventListener('keydown', this.handleEscapeKey);\n }\n\n /**\n * Closes the saved content dropdown panel\n * @description Removes the 'show' class from the dropdown to hide it, removes the dropdown element from the DOM,\n * and removes the Escape key event listener. This is called when toggling the dropdown closed or when\n * clicking outside the dropdown.\n */\n closeSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n if (dropdown) {\n dropdown.classList.remove('show');\n dropdown.remove();\n document.removeEventListener('keydown', this.handleEscapeKey);\n }\n }\n\n /**\n * Handles the Escape key press event for closing the saved content dropdown\n * @param {KeyboardEvent} event - The keyboard event object\n * @description Event handler that checks if the Escape key was pressed and closes the saved content dropdown if it was\n */\n handleEscapeKey(event) {\n if (event.key === 'Escape') {\n this.closeSavedDropdown();\n }\n }\n\n timeAgo(unixTime) {\n const seconds = Math.floor(Date.now() / 1000) - unixTime;\n\n if (seconds < 5) {\n return \"just now\";\n }\n if (seconds < 60) {\n return `${seconds} sec ago`;\n }\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes} min ago`;\n }\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} hour${hours > 1 ? \"s\" : \"\"} ago`;\n }\n\n const days = Math.floor(hours / 24);\n if (days < 7) {\n return `${days} day${days > 1 ? \"s\" : \"\"} ago`;\n }\n\n const weeks = Math.floor(days / 7);\n if (weeks < 4) {\n return `${weeks} week${weeks > 1 ? \"s\" : \"\"} ago`;\n }\n\n const months = Math.floor(days / 30);\n if (months < 12) {\n return `${months} month${months > 1 ? \"s\" : \"\"} ago`;\n }\n\n const years = Math.floor(days / 365);\n return `${years} year${years > 1 ? \"s\" : \"\"} ago`;\n }\n\n\n /**\n * Renders the saved content dropdown list using a template\n * @param {Object} context - The context object containing saved comments data to render\n * @param {HTMLElement} editorWrapper - The wrapper element to attach the dropdown to\n * @description Renders the saved content dropdown using the tiny_cursive/saved_content template.\n * Creates and positions the dropdown relative to the editor wrapper element.\n * Handles toggling visibility and caching of the saved content.\n * @throws {Error} Logs error to console if template rendering fails\n */\n renderCommentList(context, editorWrapper) {\n templates.render('tiny_cursive/saved_content', context).then(html => {\n editorWrapper.style.position = 'relative';\n\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = html.trim();\n tempDiv.id = 'savedDropdown';\n tempDiv.classList.add('tiny_cursive-saved-dropdown');\n\n if (!tempDiv) {\n window.console.error(\"Saved content template rendered empty or invalid HTML.\");\n return false;\n }\n\n // Add to DOM if not already added\n let existingPanel = document.querySelector('#savedDropdown');\n\n if (!existingPanel) {\n editorWrapper.appendChild(tempDiv);\n existingPanel = tempDiv;\n }\n\n // Toggle visibility\n existingPanel.classList.toggle('active');\n this.openSavedDropdown();\n\n this.insertSavedItems(this.editor);\n\n return true;\n\n }).catch(error => window.console.error(error));\n }\n\n fetchStrings() {\n if (!localStorage.getItem('state')) {\n\n Promise.all([\n getString('saving', 'tiny_cursive'),\n getString('saved', 'tiny_cursive'),\n getString('offline', 'tiny_cursive')\n ]).then(function(strings) {\n return localStorage.setItem('state', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n }\n\n throwWarning(str, editor) {\n getString(str, 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key));\n }\n\n /**\n * Adds click event listeners to saved content items to insert them into the editor\n * @description Finds all elements with class 'tiny_cursive-item-preview' and adds click handlers that will\n * insert the element's text content into the editor when clicked. The text is inserted with\n * a leading space.\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {void}\n */\n insertSavedItems(editor) {\n const items = document.querySelectorAll('.tiny_cursive-item-preview');\n items.forEach(element => {\n element.addEventListener('click', function() {\n editor.insertContent(\" \" + this.textContent);\n });\n });\n }\n\n}"],"names":["CursiveAutosave","constructor","editor","rightWrapper","modules","isFullScreen","instance","module","savingState","fetchSavedContent","this","bind","handleEscapeKey","_savingTimer","fetchStrings","modulename","document","querySelector","questionid","init","stateWrapper","cursiveSavingState","classList","add","id","prepend","addEventListener","destroy","state","wrapperDiv","createElement","textSpan","button","iconSpan","style","padding","fontSize","color","textContent","getStateText","innerHTML","getStateIcon","verticalAlign","appendChild","stateTextEl","clearTimeout","setTimeout","saving","saved","offline","getText","Icons","cloudSave","btoa","e","preventDefault","dropdown","_dropdown$classList","contains","closeSavedDropdown","editorWrapper","args","resourceId","cmid","editorid","_this$editor","userid","courseid","methodname","done","data","context","comments","JSON","parse","Object","values","forEach","content","time","timeAgo","timemodified","renderCommentList","fail","error","throwWarning","window","console","toggleSavedDropdown","_dropdown$classList2","openSavedDropdown","removeEventListener","remove","event","key","unixTime","seconds","Math","floor","Date","now","minutes","hours","days","weeks","months","years","render","then","html","position","tempDiv","trim","existingPanel","toggle","insertSavedItems","catch","localStorage","getItem","Promise","all","strings","setItem","stringify","str","windowManager","alert","querySelectorAll","element","insertContent"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cursive_autosave.min.js","sources":["../src/cursive_autosave.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 * TODO describe module cursive_autosave\n *\n * @module tiny_cursive/cursive_autosave\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport templates from 'core/templates';\nimport {call} from 'core/ajax';\nimport Icons from 'tiny_cursive/svg_repo';\nimport {get_string as getString} from 'core/str';\n\nexport default class CursiveAutosave {\n\n static instance = null;\n\n\n constructor(editor, rightWrapper, modules, isFullScreen) {\n if (CursiveAutosave.instance) {\n return CursiveAutosave.instance;\n }\n\n this.editor = editor;\n this.module = modules;\n this.savingState = '';\n this.rightWrapper = rightWrapper;\n this.isFullScreen = isFullScreen;\n // Bind methods that will be used as event listener\n this.fetchSavedContent = this.fetchSavedContent.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this._savingTimer = null;\n CursiveAutosave.instance = this;\n this.fetchStrings();\n }\n\n static getInstance(editor, rightWrapper, modules, isFullScreen) {\n if (!this.instance) {\n this.instance = new CursiveAutosave(editor, rightWrapper, modules, isFullScreen);\n }\n this.instance.isFullScreen = isFullScreen;\n const hasState = modules.modulename === 'quiz'\n ? document.querySelector(`#tiny_cursive_savingState${modules.questionid}`)\n : document.querySelector('#tiny_cursive_savingState');\n if (!hasState) {\n this.instance.init();\n }\n\n return this.instance;\n }\n\n init() {\n const stateWrapper = this.cursiveSavingState(this.savingState);\n stateWrapper.classList.add('tiny_cursive_savingState', 'btn');\n if (this.module.modulename === 'quiz') {\n stateWrapper.id = `tiny_cursive_savingState${this.module.questionid}`;\n } else {\n stateWrapper.id = 'tiny_cursive_savingState';\n }\n\n this.rightWrapper.prepend(stateWrapper);\n stateWrapper.addEventListener('click', this.fetchSavedContent);\n }\n\n destroy() {\n CursiveAutosave.instance = null;\n }\n\n static destroyInstance() {\n if (this.instance) {\n this.instance.destroy();\n this.instance = null;\n }\n }\n\n /**\n * Creates a wrapper div containing an icon and text to display the saving state\n * @param {string} state - The current saving state ('saving', 'saved', or 'offline')\n * @returns {HTMLElement} A div element containing the state icon and text\n * @description Creates and returns a div element with an icon and text span to show the current saving state.\n * The icon and text are updated based on the provided state parameter.\n */\n cursiveSavingState(state) {\n let wrapperDiv = document.createElement('div');\n let textSpan = document.createElement('span');\n let button = document.createElement('button');\n let iconSpan = document.createElement('span');\n\n button.style.padding = '.3rem';\n textSpan.style.fontSize = '0.75rem';\n textSpan.style.color = 'gray';\n\n if (this.module.modulename === 'quiz') {\n iconSpan.id = `CursiveCloudIcon${this.module.questionid}`;\n textSpan.id = `CursiveStateText${this.module.questionid}`;\n } else {\n iconSpan.id = 'CursiveCloudIcon';\n textSpan.id = 'CursiveStateText';\n }\n if (state) {\n textSpan.textContent = this.getStateText(state);\n iconSpan.innerHTML = this.getStateIcon(state);\n }\n\n wrapperDiv.style.verticalAlign = 'middle';\n\n wrapperDiv.appendChild(iconSpan);\n wrapperDiv.appendChild(textSpan);\n button.appendChild(wrapperDiv);\n\n return button;\n }\n\n /**\n * Updates the saving state icon and text in the editor\n * @param {string} state - The state to update to ('saving', 'saved', or 'offline')\n * @description Updates the global saving state and modifies the UI elements to reflect the new state\n */\n static updateSavingState(state) {\n const instance = this.instance;\n instance.savingState = state;\n let stateWrapper = null;\n if (instance.module.modulename === 'quiz') {\n stateWrapper = document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`);\n } else {\n stateWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let iconSpan = '';\n let stateTextEl = '';\n\n if (!stateWrapper) {\n return;\n }\n\n if (instance.module.modulename === 'quiz') {\n iconSpan = stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`);\n stateTextEl = stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`);\n } else {\n iconSpan = stateWrapper.querySelector('#CursiveCloudIcon');\n stateTextEl = stateWrapper.querySelector('#CursiveStateText');\n }\n\n if (stateTextEl && iconSpan) {\n stateTextEl.textContent = instance.getStateText(state);\n iconSpan.innerHTML = instance.getStateIcon(state);\n }\n\n\n if (instance._savingTimer) {\n clearTimeout(instance._savingTimer);\n }\n\n if (state === 'saved' && stateTextEl) {\n instance._savingTimer = setTimeout(() => {\n stateTextEl.textContent = '';\n }, 5000);\n }\n }\n\n /**\n * Gets the display text for a given saving state\n * @param {string} state - The state to get text for ('saving', 'saved', or 'offline')\n * @returns {string} The text to display for the given state\n * @description Returns appropriate text label based on the current saving state\n */\n getStateText(state) {\n const [saving, saved, offline] = this.getText('state');\n switch (state) {\n case 'saving': return saving;\n case 'saved': return saved;\n case 'offline': return offline;\n default: return '';\n }\n }\n /**\n * Gets the icon URL for a given saving state\n * @param {string} state - The state to get icon for ('saving', 'saved', or 'offline')\n * @returns {string} The URL of the icon image for the given state\n * @description Returns appropriate icon URL based on the current saving state\n */\n getStateIcon(state) {\n switch (state) {\n case 'saving': return Icons.cloudSave;\n case 'saved': return Icons.cloudSave;\n case 'offline': return 'data:image/svg+xml;base64,' + btoa(Icons.offline);\n default: return '';\n }\n }\n\n /**\n * Fetches and displays saved content in a dropdown\n * @async\n * @param {Event} e - The event object\n * @description Handles fetching and displaying saved content when the save state button is clicked.\n * If the dropdown is already visible, it will be closed. Otherwise it will fetch saved content\n * from the server (or use cached content if available) and display it in a dropdown panel.\n * @throws {Error} Logs error to console if fetching content fails\n */\n async fetchSavedContent(e) {\n e.preventDefault();\n\n let dropdown = document.querySelector('#savedDropdown');\n let isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n return;\n }\n let editorWrapper = null;\n if (this.module.modulename === 'quiz') {\n editorWrapper = document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`);\n } else {\n editorWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let args = {\n id: this.module.resourceId,\n cmid: this.module.cmid,\n modulename: `${this.module.modulename}_autosave`,\n editorid: this.editor?.id,\n userid: this.module.userid,\n courseid: this.module.courseid\n };\n\n call([{\n methodname: \"cursive_get_autosave_content\",\n args: args\n }])[0].done((data) => {\n let context = {comments: JSON.parse(data)};\n Object.values(context.comments).forEach(content => {\n content.time = this.timeAgo(content.timemodified);\n });\n this.renderCommentList(context, editorWrapper);\n\n }).fail((error) => {\n this.throwWarning('fullmodeerrorr', this.editor);\n window.console.error('Error fetching saved content:', error);\n });\n }\n\n /**\n * Toggles the visibility of the saved content dropdown\n * @description Checks if the saved content dropdown is currently visible and either closes or opens it accordingly.\n * If visible, calls closeSavedDropdown(). If hidden, calls openSavedDropdown().\n */\n toggleSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n const isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n } else {\n this.openSavedDropdown();\n }\n }\n\n /**\n * Opens the saved content dropdown panel\n * @description Shows the saved content dropdown by adding the 'show' class and sets up an event listener\n * for the Escape key to allow closing the dropdown. This is called when toggling the dropdown open.\n */\n openSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n dropdown.classList.add('show');\n\n // Add event listener to close on Escape key\n document.removeEventListener('keydown', this.handleEscapeKey);\n document.addEventListener('keydown', this.handleEscapeKey);\n }\n\n /**\n * Closes the saved content dropdown panel\n * @description Removes the 'show' class from the dropdown to hide it, removes the dropdown element from the DOM,\n * and removes the Escape key event listener. This is called when toggling the dropdown closed or when\n * clicking outside the dropdown.\n */\n closeSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n if (dropdown) {\n dropdown.classList.remove('show');\n dropdown.remove();\n document.removeEventListener('keydown', this.handleEscapeKey);\n }\n }\n\n /**\n * Handles the Escape key press event for closing the saved content dropdown\n * @param {KeyboardEvent} event - The keyboard event object\n * @description Event handler that checks if the Escape key was pressed and closes the saved content dropdown if it was\n */\n handleEscapeKey(event) {\n if (event.key === 'Escape') {\n this.closeSavedDropdown();\n }\n }\n\n timeAgo(unixTime) {\n const seconds = Math.floor(Date.now() / 1000) - unixTime;\n\n if (seconds < 5) {\n return \"just now\";\n }\n if (seconds < 60) {\n return `${seconds} sec ago`;\n }\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes} min ago`;\n }\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} hour${hours > 1 ? \"s\" : \"\"} ago`;\n }\n\n const days = Math.floor(hours / 24);\n if (days < 7) {\n return `${days} day${days > 1 ? \"s\" : \"\"} ago`;\n }\n\n const weeks = Math.floor(days / 7);\n if (weeks < 4) {\n return `${weeks} week${weeks > 1 ? \"s\" : \"\"} ago`;\n }\n\n const months = Math.floor(days / 30);\n if (months < 12) {\n return `${months} month${months > 1 ? \"s\" : \"\"} ago`;\n }\n\n const years = Math.floor(days / 365);\n return `${years} year${years > 1 ? \"s\" : \"\"} ago`;\n }\n\n\n /**\n * Renders the saved content dropdown list using a template\n * @param {Object} context - The context object containing saved comments data to render\n * @param {HTMLElement} editorWrapper - The wrapper element to attach the dropdown to\n * @description Renders the saved content dropdown using the tiny_cursive/saved_content template.\n * Creates and positions the dropdown relative to the editor wrapper element.\n * Handles toggling visibility and caching of the saved content.\n * @throws {Error} Logs error to console if template rendering fails\n */\n renderCommentList(context, editorWrapper) {\n templates.render('tiny_cursive/saved_content', context).then(html => {\n editorWrapper.style.position = 'relative';\n\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = html.trim();\n tempDiv.id = 'savedDropdown';\n tempDiv.classList.add('tiny_cursive-saved-dropdown');\n\n if (!tempDiv) {\n window.console.error(\"Saved content template rendered empty or invalid HTML.\");\n return false;\n }\n\n // Add to DOM if not already added\n let existingPanel = document.querySelector('#savedDropdown');\n\n if (!existingPanel) {\n editorWrapper.appendChild(tempDiv);\n existingPanel = tempDiv;\n }\n\n // Toggle visibility\n existingPanel.classList.toggle('active');\n this.openSavedDropdown();\n\n this.insertSavedItems(this.editor);\n\n return true;\n\n }).catch(error => window.console.error(error));\n }\n\n fetchStrings() {\n if (!localStorage.getItem('state')) {\n\n Promise.all([\n getString('saving', 'tiny_cursive'),\n getString('saved', 'tiny_cursive'),\n getString('offline', 'tiny_cursive')\n ]).then(function(strings) {\n return localStorage.setItem('state', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n }\n\n throwWarning(str, editor) {\n getString(str, 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key));\n }\n\n /**\n * Adds click event listeners to saved content items to insert them into the editor\n * @description Finds all elements with class 'tiny_cursive-item-preview' and adds click handlers that will\n * insert the element's text content into the editor when clicked. The text is inserted with\n * a leading space.\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {void}\n */\n insertSavedItems(editor) {\n const items = document.querySelectorAll('.tiny_cursive-item-preview');\n items.forEach(element => {\n element.addEventListener('click', function() {\n editor.insertContent(\" \" + this.textContent);\n });\n });\n }\n\n}"],"names":["CursiveAutosave","constructor","editor","rightWrapper","modules","isFullScreen","instance","module","savingState","fetchSavedContent","this","bind","handleEscapeKey","_savingTimer","fetchStrings","modulename","document","querySelector","questionid","init","stateWrapper","cursiveSavingState","classList","add","id","prepend","addEventListener","destroy","state","wrapperDiv","createElement","textSpan","button","iconSpan","style","padding","fontSize","color","textContent","getStateText","innerHTML","getStateIcon","verticalAlign","appendChild","stateTextEl","clearTimeout","setTimeout","saving","saved","offline","getText","Icons","cloudSave","btoa","e","preventDefault","dropdown","_dropdown$classList","contains","closeSavedDropdown","editorWrapper","args","resourceId","cmid","editorid","_this$editor","userid","courseid","methodname","done","data","context","comments","JSON","parse","Object","values","forEach","content","time","timeAgo","timemodified","renderCommentList","fail","error","throwWarning","window","console","toggleSavedDropdown","_dropdown$classList2","openSavedDropdown","removeEventListener","remove","event","key","unixTime","seconds","Math","floor","Date","now","minutes","hours","days","weeks","months","years","render","then","html","position","tempDiv","trim","existingPanel","toggle","insertSavedItems","catch","localStorage","getItem","Promise","all","strings","setItem","stringify","str","windowManager","alert","querySelectorAll","element","insertContent"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/document_view.min.js b/amd/build/document_view.min.js index 7b04f290..40ae35f3 100644 --- a/amd/build/document_view.min.js +++ b/amd/build/document_view.min.js @@ -5,6 +5,6 @@ define("tiny_cursive/document_view",["exports","tiny_cursive/svg_repo"],(functio * @module tiny_cursive/document_view * @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.default=void 0,_svg_repo=(obj=_svg_repo)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(User,Rubrics,submission,modulename,editor,quizInfo){this.User=User,this.Rubrics=Rubrics,this.submission=submission,this.module=modulename,this.editor=editor,this.moduleIcon=_svg_repo.default.assignment,this.quizInfo=quizInfo,this.initStrings()}normalMode(){var _this$editor;let id=(null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id)+"_ifr";("assign"===this.module||"quiz"===this.module||"forum"===this.module||"lesson"===this.module)&&this.normalizePage(id)}fullPageMode(){var _this$editor4,_this$editor2;if("assign"===this.module)this.moduleIcon=_svg_repo.default.assignment,this.fullPageModule(null===(_this$editor2=this.editor)||void 0===_this$editor2?void 0:_this$editor2.id);else if("forum"===this.module){var _this$editor3;this.moduleIcon=_svg_repo.default.forum,this.fullPageModule(null===(_this$editor3=this.editor)||void 0===_this$editor3?void 0:_this$editor3.id)}else if("quiz"===this.module&&null!==(_this$editor4=this.editor)&&void 0!==_this$editor4&&_this$editor4.id){var _this$editor5;this.moduleIcon=_svg_repo.default.quiz,this.fullPageModule(null===(_this$editor5=this.editor)||void 0===_this$editor5?void 0:_this$editor5.id)}else if("lesson"===this.module){var _this$editor6;this.moduleIcon=_svg_repo.default.lesson,this.fullPageModule(null===(_this$editor6=this.editor)||void 0===_this$editor6?void 0:_this$editor6.id)}}docSideBar(status){var _this$editor7;const replyId=new URL(window.location.href).searchParams.get("reply"),toggle=document.querySelector("#cursive-fullpagemode-sidebar-toggle"),timelimitBlock=this.getTimerBlock(this.module),headerInfo=this.getSidebarTitle(),progressBar=document.querySelector(".box.progress_bar"),courseName=document.querySelector("#page-navbar > nav > ol > li:nth-child(1) > a"),courseDes=document.querySelector("#intro"),Dates=document.querySelector(".activity-dates");let openDate=null==Dates?void 0:Dates.querySelector("div:nth-child(1)"),dueDate=null==Dates?void 0:Dates.querySelector("div:nth-child(2)");const container=this.create("div");Object.assign(container,{id:"cursive-fullpagemode-sidebar",className:"bg-white h-100 shadow"}),Object.assign(container.style,{width:"300px",overflow:"auto"});const crossBtn=this.create("span");Object.assign(crossBtn,{id:"cursive-collapse-sidebar",className:"btn p-2",innerHTML:_svg_repo.default.close}),crossBtn.addEventListener("click",(()=>{container.style.transition="width 0.3s ease",container.style.width="0",toggle.style.display="flex"})),null==toggle||toggle.addEventListener("click",(function(){toggle.style.display="none",container.style.width="300px"}));const btnWrapper=this.create("div");Object.assign(btnWrapper,{padding:"0 1rem",position:"sticky",top:"0",backgroundColor:"white"}),btnWrapper.append(crossBtn);const header=this.create("div");header.className="border-bottom p-3 bg-light",Object.assign(header.style,{position:"sticky",top:"0"});const headerTitle=this.create("h3");headerTitle.className="mb-3 d-flex align-items-center",headerTitle.textContent=`${headerInfo.title} ${this.details}`,headerTitle.style.fontWeight="600";const headerIcon=document.querySelector(".page-header-image > div");headerIcon&&headerTitle.prepend(headerIcon.cloneNode(!0));let wordCount=this.wordCounter(status);null!=timelimitBlock&&timelimitBlock.textContent?header.append(headerTitle,wordCount,this.timerCountDown(timelimitBlock)):header.append(headerTitle,wordCount);const content=this.create("div");if(content.className="p-3",content.append(this.createBox({bg:"bg-info",titleColor:"text-info",icon:_svg_repo.default.people,title:this.studentInfo,bodyHTML:this.generateStudentInfo(this.User,courseName)})),"lesson"===this.module&&progressBar&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.progress,bodyHTML:progressBar.innerHTML})),courseDes&&""!==(null==courseDes?void 0:courseDes.textContent.trim())&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:`${this.getSidebarTitle().title} ${this.description}`,bodyHTML:courseDes.innerHTML})),"forum"===this.module&&replyId){this.checkForumSubject();let replyPost=document.querySelector(`#post-content-${replyId}`);null!=replyPost&&replyPost.textContent.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.replyingto,bodyHTML:replyPost.textContent.trim()}))}if("quiz"===this.module&&null!==(_this$editor7=this.editor)&&void 0!==_this$editor7&&_this$editor7.id){var _this$editor8;let questionId=this.getQuestionId(null===(_this$editor8=this.editor)||void 0===_this$editor8?void 0:_this$editor8.id),question=document.querySelector(`#question-${questionId} .qtext`),intro=atob(this.quizInfo.intro);null!=question&&question.textContent.trim()&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:this.moduleIcon,title:this.answeringto,bodyHTML:question.textContent})),intro&&""!==intro.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:`${this.quiz} ${this.description}`,bodyHTML:intro})),Number(this.quizInfo.open)&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(Number(this.quizInfo.open),Number(this.quizInfo.close))}))}return Object.keys(this.Rubrics).length&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.rubrics,bodyHTML:this.generateRubrics(this.Rubrics)})),Dates&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(openDate,dueDate)})),"assign"===this.module&&content.append(this.createBox({bg:"bg-green",titleColor:"text-success",icon:this.moduleIcon,title:this.subStatus,bodyHTML:this.submissionStatus(this.submission)})),container.append(btnWrapper,header,content),container}createBox(_ref){let{bg:bg,titleColor:titleColor,icon:icon,title:title,bodyHTML:bodyHTML}=_ref;const box=this.create("div");box.className=`tiny_cursive-fullpage-card ${bg}`;const heading=this.create("h4");heading.className=`tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`,heading.innerHTML=`${icon} ${title}`;const body=this.create("div");return body.className="tiny_cursive-fullpage-card-body",body.innerHTML=bodyHTML,box.append(heading,body),box}generateRubrics(Rubrics){const wrapper=this.create("div");return Rubrics.forEach((rubric=>{const rubricDiv=this.create("div");rubricDiv.className="tiny_cursive-rubric-card";const title=this.create("h3");title.className="tiny_cursive-rubric-title",title.textContent=rubric.description,rubricDiv.appendChild(title),Object.values(rubric.levels).forEach((level=>{const levelDiv=this.create("div"),score=Number(level.score);levelDiv.className=0===score?"tiny_cursive-rubric-level tiny_cursive-rubric-low":score<=2?"tiny_cursive-rubric-level tiny_cursive-rubric-mid":"tiny_cursive-rubric-level tiny_cursive-rubric-high",levelDiv.textContent=`${level.definition} / ${level.score}`,rubricDiv.appendChild(levelDiv)})),wrapper.appendChild(rubricDiv)})),wrapper.innerHTML}submissionStatus(submission){var _submission$current,_submission$current2;const wrapper=this.create("div"),statusWrapper=this.create("div");statusWrapper.className="tiny_cursive-status-row";const statusName=this.create("span");statusName.textContent=`${this.status}:`;const statusValue=this.create("span"),isNew="new"===(null==submission||null===(_submission$current=submission.current)||void 0===_submission$current?void 0:_submission$current.status);statusValue.textContent=isNew?this.draftnot:this.draft,statusValue.className="tiny_cursive-status-value "+(isNew?"tiny_cursive-status-red":"tiny_cursive-status-green"),statusWrapper.append(statusName,statusValue);const modifiedWrapper=this.create("div");modifiedWrapper.className="tiny_cursive-status-row";const modifiedName=this.create("span");modifiedName.textContent=`${this.lastModified}: `;const modifiedValue=this.create("span");if(null!=submission&&null!==(_submission$current2=submission.current)&&void 0!==_submission$current2&&_submission$current2.timemodified){const date=new Date(1e3*submission.current.timemodified);modifiedValue.textContent=this.formatDate(date)}else modifiedValue.textContent="N/A";modifiedWrapper.append(modifiedName,modifiedValue);const gradeWrapper=this.create("div");gradeWrapper.className="tiny_cursive-status-row";const gradeName=this.create("span");gradeName.textContent=`${this.gradings}: `;const gradeValue=this.create("span");return null!=submission&&submission.grade?gradeValue.textContent=Number(submission.grade.grade)>0?submission.grade.grade:this.gradenot:gradeValue.textContent=this.gradenot,gradeWrapper.append(gradeName,gradeValue),wrapper.append(statusWrapper,gradeWrapper,modifiedWrapper),wrapper.innerHTML}wordCounter(status){const wordCount=this.create("div"),labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");icon.className="me-2",icon.innerHTML=_svg_repo.default.assignment,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent=`${this.wordCount}:`,value.textContent="0",value.className="text-primary",value.style.fontWeight="600",value.style.fontSize="14px",wordCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2",wordCount.append(labelDiv,value),wordCount.style.fontSize="12px";return new MutationObserver((()=>{const newText=status.textContent.trim();value.textContent=`${newText.replace("words","")}`})).observe(status,{characterData:!0,subtree:!0,childList:!0}),wordCount}timerCountDown(timer){let warningDiv=document.querySelector("#user-notifications > div");if(warningDiv){var _clone$querySelector;let clone=warningDiv.cloneNode(!0);null===(_clone$querySelector=clone.querySelector("button"))||void 0===_clone$querySelector||_clone$querySelector.remove(),this.editor.notificationManager.open({text:clone.textContent,type:"error"})}const timerCount=this.create("div");timerCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2";const labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");if(icon.innerHTML=_svg_repo.default.time,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent=`${this.timeleft}: }`,value.textContent="00:00:00",value.className=warningDiv?"text-danger":"text-primary",Object.assign(value.style,{fontWeight:"600",fontSize:"14px"}),timerCount.append(labelDiv,value),timerCount.style.fontSize="12px",timer){new MutationObserver((()=>{const newText=timer.textContent.trim();value.textContent=`${newText}`})).observe(timer,{characterData:!0,subtree:!0,childList:!0})}else value.textContent=this.nolimit;return timerCount}generateStudentInfo(user,course){const wrapper=this.create("div"),nameWrapper=this.create("div"),usernameWrapper=this.create("div"),courseWrapper=this.create("div"),nameLabel=this.create("span"),nameValue=this.create("span"),usernameLabel=this.create("span"),usernameValue=this.create("span"),courseLabel=this.create("span"),courseValue=this.create("span");return nameLabel.textContent=`${this.name}`,nameValue.textContent=user.fullname,usernameLabel.textContent=`${this.userename}: `,usernameValue.textContent=user.username,courseLabel.textContent=`${this.course}: `,courseValue.textContent=course.title,nameWrapper.className="d-flex justify-content-between",usernameWrapper.className="d-flex justify-content-between",courseWrapper.className="d-flex justify-content-between",nameWrapper.append(nameLabel,nameValue),usernameWrapper.append(usernameLabel,usernameValue),courseWrapper.append(courseLabel,courseValue),wrapper.append(nameWrapper,usernameWrapper,courseWrapper),wrapper.innerHTML}generateImportantDates(open,due){const wrapper=this.create("div");let openDate=null,dueDate=null;const openedWrapper=this.create("div"),dueWrapper=this.create("div"),remainingWrapper=this.create("div"),openedLabel=this.create("span"),openedValue=this.create("span"),dueLabel=this.create("span"),dueValue=this.create("span"),remainingLabel=this.create("span"),remainingValue=this.create("span");return"quiz"===this.module?(openDate=1e3*open,dueDate=1e3*due):(openDate=this.extractDate(null==open?void 0:open.textContent),dueDate=this.extractDate(null==due?void 0:due.textContent)),openedLabel.textContent=`${this.opened}: `,openedValue.textContent=this.formatDate(openDate?new Date(openDate):null),openedValue.className="text-dark",dueLabel.textContent=`${this.due}: `,dueValue.textContent=this.formatDate(dueDate?new Date(dueDate):null),dueValue.className="text-danger",remainingLabel.textContent=`${this.remaining}: `,remainingValue.textContent=this.calculateDate(dueDate),remainingValue.className="text-danger",openedWrapper.className="d-flex justify-content-between",dueWrapper.className="d-flex justify-content-between",remainingWrapper.className="d-flex align-items-center justify-content-between mt-2 pt-2 border-top",openedWrapper.append(openedLabel,openedValue),dueWrapper.append(dueLabel,dueValue),remainingWrapper.append(remainingLabel,remainingValue),wrapper.append(openedWrapper,dueWrapper,remainingWrapper),wrapper.innerHTML}formatDate(date){if(!date)return"-";return date.toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"numeric",hour12:!0})}extractDate(text){if(!text)return"-";const parts=null==text?void 0:text.split(":");return parts.length>1?parts.slice(1).join(":").trim():text.trim()}calculateDate(date){if(!date)return"-";const diffMs=new Date(date)-new Date;if(diffMs<=0)return"Overdue";return`${Math.floor(diffMs/864e5)} days, ${Math.floor(diffMs/36e5%24)} hours`}fullPageModule(module){var _current$contentDocum,_current$contentWindo,_current$contentWindo2,_document$getElementB;let current="quiz"===this.module?document.getElementById(`${module}_ifr`):document.querySelector(`#${module}_ifr`),p1=current.parentElement,p2=p1.parentElement,p4=p2.parentElement.parentElement,statusBar=document.querySelector(".tox-statusbar__right-container > button"),assignName=document.querySelector(".page-context-header"),header=this.create("div"),btn=null;assignName.classList.remove("mb-2"),header.id="tiny_cursive-fullpage-custom-header",Object.assign(header.style,{backgroundColor:"white",display:"flex",justifyContent:"space-between"}),"quiz"===this.module?(btn=document.querySelector("#mod_quiz-next-nav").cloneNode(!0),btn.className="tiny_cursive-fullpage-submit-btn",btn.style.margin=".5rem"):(btn=this.create("input"),btn.className="tiny_cursive-fullpage-submit-btn",btn.value=this.savechanges,btn.type="submit",btn.style.margin=".5rem");const leftSide=this.create("div"),rightSide=this.create("div");let commonStyle={display:"flex",alignItems:"center",margin:"0 1rem"};Object.assign(leftSide.style,commonStyle),rightSide.id="tiny_cursive-fullpage-right-wrapper",Object.assign(rightSide.style,commonStyle),rightSide.appendChild(btn),leftSide.appendChild(assignName.cloneNode(!0)),header.appendChild(leftSide),header.appendChild(rightSide),p4.insertBefore(header,p4.firstChild),p2.style.backgroundColor="#efefef",Object.assign(current.style,{width:"750px",minWidth:"750px",boxShadow:"0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)"}),Object.assign(p1.style,{display:"flex",justifyContent:"center",outline:"none",margin:"2rem 0 0"});const style=this.create("style");style.id="tiny_cursive-fullpage-mode-style",style.textContent="\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }",document.head.appendChild(style);let iframeBody=(null===(_current$contentDocum=current.contentDocument)||void 0===_current$contentDocum?void 0:_current$contentDocum.body)||(null===(_current$contentWindo=current.contentWindow)||void 0===_current$contentWindo||null===(_current$contentWindo2=_current$contentWindo.document)||void 0===_current$contentWindo2?void 0:_current$contentWindo2.body);iframeBody&&(iframeBody.style.padding="0.5in"),p2.style.position="relative",null===(_document$getElementB=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB||_document$getElementB.remove();let toggle=this.create("div");toggle.id="cursive-fullpagemode-sidebar-toggle",toggle.innerHTML=_svg_repo.default.hamburger,p2.appendChild(toggle),p2.appendChild(this.docSideBar(statusBar))}normalizePage(editorId){var _document$getElementB2,_document$getElementB3,_current$contentDocum2,_current$contentWindo3,_current$contentWindo4,_document$head$queryS;null===(_document$getElementB2=document.getElementById("tiny_cursive-fullpage-custom-header"))||void 0===_document$getElementB2||_document$getElementB2.remove(),null===(_document$getElementB3=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB3||_document$getElementB3.remove();let current=document.getElementById(editorId),p1=current.parentElement,p2=p1.parentElement;Object.assign(p2.style,{backgroundColor:"",position:""}),Object.assign(current.style,{width:"",minWidth:"",boxShadow:""}),Object.assign(p1.style,{display:"",justifyContent:"",outline:"",margin:""}),p1.classList.remove("tiny-cursive-editor-container");let iframeBody=(null===(_current$contentDocum2=current.contentDocument)||void 0===_current$contentDocum2?void 0:_current$contentDocum2.body)||(null===(_current$contentWindo3=current.contentWindow)||void 0===_current$contentWindo3||null===(_current$contentWindo4=_current$contentWindo3.document)||void 0===_current$contentWindo4?void 0:_current$contentWindo4.body);iframeBody&&(iframeBody.style.padding="0"),null===(_document$head$queryS=document.head.querySelector("#tiny_cursive-fullpage-mode-style"))||void 0===_document$head$queryS||_document$head$queryS.remove()}checkForumSubject(){const form=document.querySelector("#tiny_cursive-fullpage-right-wrapper > input"),msg=this.subjectnot;form&&form.addEventListener("click",(e=>{const subjectInput=document.getElementById("id_subject");let content=this.editor.getContent().trim();subjectInput&&""!==subjectInput.value.trim()&&""!==content||(e.preventDefault(),e.stopPropagation(),this.editor.windowManager.alert(msg))}))}getSidebarTitle(){const[assign,discus,quiz,lesson]=this.getText("sbTitle");switch(this.module){case"assign":return{title:assign,icon:_svg_repo.default.assignment};case"forum":return{title:discus,icon:_svg_repo.default.forum};case"lesson":return{title:lesson,icon:_svg_repo.default.forum};case"quiz":return{title:quiz,icon:_svg_repo.default.quiz};default:return{title:"Page",icon:_svg_repo.default.quiz}}}getTimerBlock(module){switch(module){case"assign":return document.querySelector("#mod_assign_timelimit_block > div > div");case"forum":return document.querySelector("#mod_forum_timelimit_block");case"lesson":return document.querySelector("#lesson-timer");case"quiz":return document.querySelector("#quiz-time-left");default:return null}}getQuestionId(editoId){try{return editoId&&"string"==typeof editoId?editoId.replace(/^q(\d+):(\d+)_.*$/,"$1-$2"):""}catch(error){return window.console.error("Error getting question ID:",error),""}}initStrings(){[this.details,this.studentInfo,this.progress,this.description,this.replyingto,this.answeringto,this.importantdates,this.rubrics,this.subStatus,this.status,this.draft,this.draftnot,this.lastModified,this.gradings,this.gradenot,this.wordCount,this.timeleft,this.nolimit,this.name,this.userename,this.course,this.opened,this.due,this.overdue,this.remaining,this.savechanges,this.subjectnot]=this.getText("docSideBar")}getText(key){return JSON.parse(localStorage.getItem(key))||[]}create(tag){return document.createElement(tag)}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_svg_repo=(obj=_svg_repo)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(User,Rubrics,submission,modulename,editor,quizInfo){this.User=User,this.Rubrics=Rubrics,this.submission=submission,this.module=modulename,this.editor=editor,this.moduleIcon=_svg_repo.default.assignment,this.quizInfo=quizInfo,this.initStrings()}normalMode(){var _this$editor;let id=(null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id)+"_ifr";("assign"===this.module||"quiz"===this.module||"forum"===this.module||"lesson"===this.module)&&this.normalizePage(id)}fullPageMode(){var _this$editor4,_this$editor2;if("assign"===this.module)this.moduleIcon=_svg_repo.default.assignment,this.fullPageModule(null===(_this$editor2=this.editor)||void 0===_this$editor2?void 0:_this$editor2.id);else if("forum"===this.module){var _this$editor3;this.moduleIcon=_svg_repo.default.forum,this.fullPageModule(null===(_this$editor3=this.editor)||void 0===_this$editor3?void 0:_this$editor3.id)}else if("quiz"===this.module&&null!==(_this$editor4=this.editor)&&void 0!==_this$editor4&&_this$editor4.id){var _this$editor5;this.moduleIcon=_svg_repo.default.quiz,this.fullPageModule(null===(_this$editor5=this.editor)||void 0===_this$editor5?void 0:_this$editor5.id)}else if("lesson"===this.module){var _this$editor6;this.moduleIcon=_svg_repo.default.lesson,this.fullPageModule(null===(_this$editor6=this.editor)||void 0===_this$editor6?void 0:_this$editor6.id)}}docSideBar(status){var _this$editor7;const replyId=new URL(window.location.href).searchParams.get("reply"),toggle=document.querySelector("#cursive-fullpagemode-sidebar-toggle"),timelimitBlock=this.getTimerBlock(this.module),headerInfo=this.getSidebarTitle(),progressBar=document.querySelector(".box.progress_bar"),courseName=document.querySelector("#page-navbar > nav > ol > li:nth-child(1) > a"),courseDes=document.querySelector("#intro"),Dates=document.querySelector(".activity-dates");let openDate=null==Dates?void 0:Dates.querySelector("div:nth-child(1)"),dueDate=null==Dates?void 0:Dates.querySelector("div:nth-child(2)");const container=this.create("div");Object.assign(container,{id:"cursive-fullpagemode-sidebar",className:"bg-white h-100 shadow"}),Object.assign(container.style,{width:"300px",overflow:"auto"});const crossBtn=this.create("span");Object.assign(crossBtn,{id:"cursive-collapse-sidebar",className:"btn p-2",innerHTML:_svg_repo.default.close}),crossBtn.addEventListener("click",(()=>{container.style.transition="width 0.3s ease",container.style.width="0",toggle.style.display="flex"})),null==toggle||toggle.addEventListener("click",(function(){toggle.style.display="none",container.style.width="300px"}));const btnWrapper=this.create("div");Object.assign(btnWrapper,{padding:"0 1rem",position:"sticky",top:"0",backgroundColor:"white"}),btnWrapper.append(crossBtn);const header=this.create("div");header.className="border-bottom p-3 bg-light",Object.assign(header.style,{position:"sticky",top:"0"});const headerTitle=this.create("h3");headerTitle.className="mb-3 d-flex align-items-center",headerTitle.textContent="".concat(headerInfo.title," ").concat(this.details),headerTitle.style.fontWeight="600";const headerIcon=document.querySelector(".page-header-image > div");headerIcon&&headerTitle.prepend(headerIcon.cloneNode(!0));let wordCount=this.wordCounter(status);null!=timelimitBlock&&timelimitBlock.textContent?header.append(headerTitle,wordCount,this.timerCountDown(timelimitBlock)):header.append(headerTitle,wordCount);const content=this.create("div");if(content.className="p-3",content.append(this.createBox({bg:"bg-info",titleColor:"text-info",icon:_svg_repo.default.people,title:this.studentInfo,bodyHTML:this.generateStudentInfo(this.User,courseName)})),"lesson"===this.module&&progressBar&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.progress,bodyHTML:progressBar.innerHTML})),courseDes&&""!==(null==courseDes?void 0:courseDes.textContent.trim())&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:"".concat(this.getSidebarTitle().title," ").concat(this.description),bodyHTML:courseDes.innerHTML})),"forum"===this.module&&replyId){this.checkForumSubject();let replyPost=document.querySelector("#post-content-".concat(replyId));null!=replyPost&&replyPost.textContent.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.replyingto,bodyHTML:replyPost.textContent.trim()}))}if("quiz"===this.module&&null!==(_this$editor7=this.editor)&&void 0!==_this$editor7&&_this$editor7.id){var _this$editor8;let questionId=this.getQuestionId(null===(_this$editor8=this.editor)||void 0===_this$editor8?void 0:_this$editor8.id),question=document.querySelector("#question-".concat(questionId," .qtext")),intro=atob(this.quizInfo.intro);null!=question&&question.textContent.trim()&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:this.moduleIcon,title:this.answeringto,bodyHTML:question.textContent})),intro&&""!==intro.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:"".concat(this.quiz," ").concat(this.description),bodyHTML:intro})),Number(this.quizInfo.open)&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(Number(this.quizInfo.open),Number(this.quizInfo.close))}))}return Object.keys(this.Rubrics).length&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.rubrics,bodyHTML:this.generateRubrics(this.Rubrics)})),Dates&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(openDate,dueDate)})),"assign"===this.module&&content.append(this.createBox({bg:"bg-green",titleColor:"text-success",icon:this.moduleIcon,title:this.subStatus,bodyHTML:this.submissionStatus(this.submission)})),container.append(btnWrapper,header,content),container}createBox(_ref){let{bg:bg,titleColor:titleColor,icon:icon,title:title,bodyHTML:bodyHTML}=_ref;const box=this.create("div");box.className="tiny_cursive-fullpage-card ".concat(bg);const heading=this.create("h4");heading.className="tiny_cursive-fullpage-card-header ".concat(titleColor," d-flex align-items-center"),heading.innerHTML="".concat(icon," ").concat(title);const body=this.create("div");return body.className="tiny_cursive-fullpage-card-body",body.innerHTML=bodyHTML,box.append(heading,body),box}generateRubrics(Rubrics){const wrapper=this.create("div");return Rubrics.forEach((rubric=>{const rubricDiv=this.create("div");rubricDiv.className="tiny_cursive-rubric-card";const title=this.create("h3");title.className="tiny_cursive-rubric-title",title.textContent=rubric.description,rubricDiv.appendChild(title),Object.values(rubric.levels).forEach((level=>{const levelDiv=this.create("div"),score=Number(level.score);levelDiv.className=0===score?"tiny_cursive-rubric-level tiny_cursive-rubric-low":score<=2?"tiny_cursive-rubric-level tiny_cursive-rubric-mid":"tiny_cursive-rubric-level tiny_cursive-rubric-high",levelDiv.textContent="".concat(level.definition," / ").concat(level.score),rubricDiv.appendChild(levelDiv)})),wrapper.appendChild(rubricDiv)})),wrapper.innerHTML}submissionStatus(submission){var _submission$current,_submission$current2;const wrapper=this.create("div"),statusWrapper=this.create("div");statusWrapper.className="tiny_cursive-status-row";const statusName=this.create("span");statusName.textContent="".concat(this.status,":");const statusValue=this.create("span"),isNew="new"===(null==submission||null===(_submission$current=submission.current)||void 0===_submission$current?void 0:_submission$current.status);statusValue.textContent=isNew?this.draftnot:this.draft,statusValue.className="tiny_cursive-status-value ".concat(isNew?"tiny_cursive-status-red":"tiny_cursive-status-green"),statusWrapper.append(statusName,statusValue);const modifiedWrapper=this.create("div");modifiedWrapper.className="tiny_cursive-status-row";const modifiedName=this.create("span");modifiedName.textContent="".concat(this.lastModified,": ");const modifiedValue=this.create("span");if(null!=submission&&null!==(_submission$current2=submission.current)&&void 0!==_submission$current2&&_submission$current2.timemodified){const date=new Date(1e3*submission.current.timemodified);modifiedValue.textContent=this.formatDate(date)}else modifiedValue.textContent="N/A";modifiedWrapper.append(modifiedName,modifiedValue);const gradeWrapper=this.create("div");gradeWrapper.className="tiny_cursive-status-row";const gradeName=this.create("span");gradeName.textContent="".concat(this.gradings,": ");const gradeValue=this.create("span");return null!=submission&&submission.grade?gradeValue.textContent=Number(submission.grade.grade)>0?submission.grade.grade:this.gradenot:gradeValue.textContent=this.gradenot,gradeWrapper.append(gradeName,gradeValue),wrapper.append(statusWrapper,gradeWrapper,modifiedWrapper),wrapper.innerHTML}wordCounter(status){const wordCount=this.create("div"),labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");icon.className="me-2",icon.innerHTML=_svg_repo.default.assignment,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent="".concat(this.wordCount,":"),value.textContent="0",value.className="text-primary",value.style.fontWeight="600",value.style.fontSize="14px",wordCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2",wordCount.append(labelDiv,value),wordCount.style.fontSize="12px";return new MutationObserver((()=>{const newText=status.textContent.trim();value.textContent="".concat(newText.replace("words",""))})).observe(status,{characterData:!0,subtree:!0,childList:!0}),wordCount}timerCountDown(timer){let warningDiv=document.querySelector("#user-notifications > div");if(warningDiv){var _clone$querySelector;let clone=warningDiv.cloneNode(!0);null===(_clone$querySelector=clone.querySelector("button"))||void 0===_clone$querySelector||_clone$querySelector.remove(),this.editor.notificationManager.open({text:clone.textContent,type:"error"})}const timerCount=this.create("div");timerCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2";const labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");if(icon.innerHTML=_svg_repo.default.time,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent="".concat(this.timeleft,": }"),value.textContent="00:00:00",value.className=warningDiv?"text-danger":"text-primary",Object.assign(value.style,{fontWeight:"600",fontSize:"14px"}),timerCount.append(labelDiv,value),timerCount.style.fontSize="12px",timer){new MutationObserver((()=>{const newText=timer.textContent.trim();value.textContent="".concat(newText)})).observe(timer,{characterData:!0,subtree:!0,childList:!0})}else value.textContent=this.nolimit;return timerCount}generateStudentInfo(user,course){const wrapper=this.create("div"),nameWrapper=this.create("div"),usernameWrapper=this.create("div"),courseWrapper=this.create("div"),nameLabel=this.create("span"),nameValue=this.create("span"),usernameLabel=this.create("span"),usernameValue=this.create("span"),courseLabel=this.create("span"),courseValue=this.create("span");return nameLabel.textContent="".concat(this.name),nameValue.textContent=user.fullname,usernameLabel.textContent="".concat(this.userename,": "),usernameValue.textContent=user.username,courseLabel.textContent="".concat(this.course,": "),courseValue.textContent=course.title,nameWrapper.className="d-flex justify-content-between",usernameWrapper.className="d-flex justify-content-between",courseWrapper.className="d-flex justify-content-between",nameWrapper.append(nameLabel,nameValue),usernameWrapper.append(usernameLabel,usernameValue),courseWrapper.append(courseLabel,courseValue),wrapper.append(nameWrapper,usernameWrapper,courseWrapper),wrapper.innerHTML}generateImportantDates(open,due){const wrapper=this.create("div");let openDate=null,dueDate=null;const openedWrapper=this.create("div"),dueWrapper=this.create("div"),remainingWrapper=this.create("div"),openedLabel=this.create("span"),openedValue=this.create("span"),dueLabel=this.create("span"),dueValue=this.create("span"),remainingLabel=this.create("span"),remainingValue=this.create("span");return"quiz"===this.module?(openDate=1e3*open,dueDate=1e3*due):(openDate=this.extractDate(null==open?void 0:open.textContent),dueDate=this.extractDate(null==due?void 0:due.textContent)),openedLabel.textContent="".concat(this.opened,": "),openedValue.textContent=this.formatDate(openDate?new Date(openDate):null),openedValue.className="text-dark",dueLabel.textContent="".concat(this.due,": "),dueValue.textContent=this.formatDate(dueDate?new Date(dueDate):null),dueValue.className="text-danger",remainingLabel.textContent="".concat(this.remaining,": "),remainingValue.textContent=this.calculateDate(dueDate),remainingValue.className="text-danger",openedWrapper.className="d-flex justify-content-between",dueWrapper.className="d-flex justify-content-between",remainingWrapper.className="d-flex align-items-center justify-content-between mt-2 pt-2 border-top",openedWrapper.append(openedLabel,openedValue),dueWrapper.append(dueLabel,dueValue),remainingWrapper.append(remainingLabel,remainingValue),wrapper.append(openedWrapper,dueWrapper,remainingWrapper),wrapper.innerHTML}formatDate(date){if(!date)return"-";return date.toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"numeric",hour12:!0})}extractDate(text){if(!text)return"-";const parts=null==text?void 0:text.split(":");return parts.length>1?parts.slice(1).join(":").trim():text.trim()}calculateDate(date){if(!date)return"-";const diffMs=new Date(date)-new Date;if(diffMs<=0)return"Overdue";{const diffDays=Math.floor(diffMs/864e5),diffHours=Math.floor(diffMs/36e5%24);return"".concat(diffDays," days, ").concat(diffHours," hours")}}fullPageModule(module){var _current$contentDocum,_current$contentWindo,_current$contentWindo2,_document$getElementB;let current="quiz"===this.module?document.getElementById("".concat(module,"_ifr")):document.querySelector("#".concat(module,"_ifr")),p1=current.parentElement,p2=p1.parentElement,p4=p2.parentElement.parentElement,statusBar=document.querySelector(".tox-statusbar__right-container > button"),assignName=document.querySelector(".page-context-header"),header=this.create("div"),btn=null;assignName.classList.remove("mb-2"),header.id="tiny_cursive-fullpage-custom-header",Object.assign(header.style,{backgroundColor:"white",display:"flex",justifyContent:"space-between"}),"quiz"===this.module?(btn=document.querySelector("#mod_quiz-next-nav").cloneNode(!0),btn.className="tiny_cursive-fullpage-submit-btn",btn.style.margin=".5rem"):(btn=this.create("input"),btn.className="tiny_cursive-fullpage-submit-btn",btn.value=this.savechanges,btn.type="submit",btn.style.margin=".5rem");const leftSide=this.create("div"),rightSide=this.create("div");let commonStyle={display:"flex",alignItems:"center",margin:"0 1rem"};Object.assign(leftSide.style,commonStyle),rightSide.id="tiny_cursive-fullpage-right-wrapper",Object.assign(rightSide.style,commonStyle),rightSide.appendChild(btn),leftSide.appendChild(assignName.cloneNode(!0)),header.appendChild(leftSide),header.appendChild(rightSide),p4.insertBefore(header,p4.firstChild),p2.style.backgroundColor="#efefef",Object.assign(current.style,{width:"750px",minWidth:"750px",boxShadow:"0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)"}),Object.assign(p1.style,{display:"flex",justifyContent:"center",outline:"none",margin:"2rem 0 0"});const style=this.create("style");style.id="tiny_cursive-fullpage-mode-style",style.textContent="\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }",document.head.appendChild(style);let iframeBody=(null===(_current$contentDocum=current.contentDocument)||void 0===_current$contentDocum?void 0:_current$contentDocum.body)||(null===(_current$contentWindo=current.contentWindow)||void 0===_current$contentWindo||null===(_current$contentWindo2=_current$contentWindo.document)||void 0===_current$contentWindo2?void 0:_current$contentWindo2.body);iframeBody&&(iframeBody.style.padding="0.5in"),p2.style.position="relative",null===(_document$getElementB=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB||_document$getElementB.remove();let toggle=this.create("div");toggle.id="cursive-fullpagemode-sidebar-toggle",toggle.innerHTML=_svg_repo.default.hamburger,p2.appendChild(toggle),p2.appendChild(this.docSideBar(statusBar))}normalizePage(editorId){var _document$getElementB2,_document$getElementB3,_current$contentDocum2,_current$contentWindo3,_current$contentWindo4,_document$head$queryS;null===(_document$getElementB2=document.getElementById("tiny_cursive-fullpage-custom-header"))||void 0===_document$getElementB2||_document$getElementB2.remove(),null===(_document$getElementB3=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB3||_document$getElementB3.remove();let current=document.getElementById(editorId),p1=current.parentElement,p2=p1.parentElement;Object.assign(p2.style,{backgroundColor:"",position:""}),Object.assign(current.style,{width:"",minWidth:"",boxShadow:""}),Object.assign(p1.style,{display:"",justifyContent:"",outline:"",margin:""}),p1.classList.remove("tiny-cursive-editor-container");let iframeBody=(null===(_current$contentDocum2=current.contentDocument)||void 0===_current$contentDocum2?void 0:_current$contentDocum2.body)||(null===(_current$contentWindo3=current.contentWindow)||void 0===_current$contentWindo3||null===(_current$contentWindo4=_current$contentWindo3.document)||void 0===_current$contentWindo4?void 0:_current$contentWindo4.body);iframeBody&&(iframeBody.style.padding="0"),null===(_document$head$queryS=document.head.querySelector("#tiny_cursive-fullpage-mode-style"))||void 0===_document$head$queryS||_document$head$queryS.remove()}checkForumSubject(){const form=document.querySelector("#tiny_cursive-fullpage-right-wrapper > input"),msg=this.subjectnot;form&&form.addEventListener("click",(e=>{const subjectInput=document.getElementById("id_subject");let content=this.editor.getContent().trim();subjectInput&&""!==subjectInput.value.trim()&&""!==content||(e.preventDefault(),e.stopPropagation(),this.editor.windowManager.alert(msg))}))}getSidebarTitle(){const[assign,discus,quiz,lesson]=this.getText("sbTitle");switch(this.module){case"assign":return{title:assign,icon:_svg_repo.default.assignment};case"forum":return{title:discus,icon:_svg_repo.default.forum};case"lesson":return{title:lesson,icon:_svg_repo.default.forum};case"quiz":return{title:quiz,icon:_svg_repo.default.quiz};default:return{title:"Page",icon:_svg_repo.default.quiz}}}getTimerBlock(module){switch(module){case"assign":return document.querySelector("#mod_assign_timelimit_block > div > div");case"forum":return document.querySelector("#mod_forum_timelimit_block");case"lesson":return document.querySelector("#lesson-timer");case"quiz":return document.querySelector("#quiz-time-left");default:return null}}getQuestionId(editoId){try{return editoId&&"string"==typeof editoId?editoId.replace(/^q(\d+):(\d+)_.*$/,"$1-$2"):""}catch(error){return window.console.error("Error getting question ID:",error),""}}initStrings(){[this.details,this.studentInfo,this.progress,this.description,this.replyingto,this.answeringto,this.importantdates,this.rubrics,this.subStatus,this.status,this.draft,this.draftnot,this.lastModified,this.gradings,this.gradenot,this.wordCount,this.timeleft,this.nolimit,this.name,this.userename,this.course,this.opened,this.due,this.overdue,this.remaining,this.savechanges,this.subjectnot]=this.getText("docSideBar")}getText(key){return JSON.parse(localStorage.getItem(key))||[]}create(tag){return document.createElement(tag)}},_exports.default})); //# sourceMappingURL=document_view.min.js.map \ No newline at end of file diff --git a/amd/build/document_view.min.js.map b/amd/build/document_view.min.js.map index 39104e64..58382050 100644 --- a/amd/build/document_view.min.js.map +++ b/amd/build/document_view.min.js.map @@ -1 +1 @@ -{"version":3,"file":"document_view.min.js","sources":["../src/document_view.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 provides functionality for document view management in the Tiny editor,\n * including full page mode display and sidebar information\n * @module tiny_cursive/document_view\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Icons from 'tiny_cursive/svg_repo';\nexport default class DocumentView {\n\n constructor(User, Rubrics, submission, modulename, editor, quizInfo) {\n this.User = User;\n this.Rubrics = Rubrics;\n this.submission = submission;\n this.module = modulename;\n this.editor = editor;\n this.moduleIcon = Icons.assignment;\n this.quizInfo = quizInfo;\n this.initStrings();\n }\n\n normalMode() {\n let id = this.editor?.id + \"_ifr\";\n if (this.module === 'assign') {\n this.normalizePage(id);\n } else if (this.module === 'quiz') {\n this.normalizePage(id);\n } else if (this.module === 'forum') {\n this.normalizePage(id);\n } else if (this.module === 'lesson') {\n this.normalizePage(id);\n }\n }\n\n fullPageMode() {\n\n if (this.module === 'assign') {\n this.moduleIcon = Icons.assignment;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'forum') {\n this.moduleIcon = Icons.forum;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'quiz' && this.editor?.id) {\n this.moduleIcon = Icons.quiz;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'lesson') {\n this.moduleIcon = Icons.lesson;\n this.fullPageModule(this.editor?.id);\n }\n }\n\n docSideBar(status) {\n\n const url = new URL(window.location.href);\n const replyId = url.searchParams.get(\"reply\");\n const toggle = document.querySelector('#cursive-fullpagemode-sidebar-toggle');\n const timelimitBlock = this.getTimerBlock(this.module);\n const headerInfo = this.getSidebarTitle();\n const progressBar = document.querySelector('.box.progress_bar');\n\n const courseName = document.querySelector('#page-navbar > nav > ol > li:nth-child(1) > a');\n const courseDes = document.querySelector('#intro');\n const Dates = document.querySelector('.activity-dates');\n\n let openDate = Dates?.querySelector('div:nth-child(1)');\n let dueDate = Dates?.querySelector('div:nth-child(2)');\n\n const container = this.create('div');\n Object.assign(container, {\n id: 'cursive-fullpagemode-sidebar',\n className: 'bg-white h-100 shadow'\n });\n Object.assign(container.style, {\n width: '300px',\n overflow: 'auto'\n });\n\n const crossBtn = this.create('span');\n Object.assign(crossBtn, {\n id: 'cursive-collapse-sidebar',\n className: 'btn p-2',\n innerHTML: Icons.close\n });\n\n crossBtn.addEventListener('click', () => {\n container.style.transition = 'width 0.3s ease';\n container.style.width = '0';\n toggle.style.display = 'flex';\n });\n toggle?.addEventListener('click', function() {\n toggle.style.display = 'none';\n container.style.width = '300px';\n });\n\n const btnWrapper = this.create('div');\n Object.assign(btnWrapper, {\n padding: '0 1rem',\n position: 'sticky',\n top: '0',\n backgroundColor: 'white'\n });\n btnWrapper.append(crossBtn);\n\n\n const header = this.create('div');\n header.className = 'border-bottom p-3 bg-light';\n Object.assign(header.style, {\n position: 'sticky',\n top: '0'\n });\n\n const headerTitle = this.create('h3');\n headerTitle.className = 'mb-3 d-flex align-items-center';\n headerTitle.textContent = `${headerInfo.title} ${this.details}`;\n headerTitle.style.fontWeight = '600';\n\n const headerIcon = document.querySelector('.page-header-image > div');\n if (headerIcon) {\n headerTitle.prepend(headerIcon.cloneNode(true));\n }\n\n let wordCount = this.wordCounter(status);\n if (timelimitBlock?.textContent) {\n header.append(headerTitle, wordCount, this.timerCountDown(timelimitBlock));\n } else {\n header.append(headerTitle, wordCount);\n }\n\n const content = this.create('div');\n content.className = 'p-3';\n\n content.append(\n this.createBox({\n bg: 'bg-info',\n titleColor: 'text-info',\n icon: Icons.people,\n title: this.studentInfo,\n bodyHTML: this.generateStudentInfo(this.User, courseName)\n })\n );\n\n if (this.module === 'lesson' && progressBar) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.progress,\n bodyHTML: progressBar.innerHTML\n })\n );\n }\n\n if (courseDes && courseDes?.textContent.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.getSidebarTitle().title} ${this.description}`,\n bodyHTML: courseDes.innerHTML\n })\n );\n }\n\n if (this.module === 'forum' && replyId) {\n this.checkForumSubject();\n let replyPost = document.querySelector(`#post-content-${replyId}`);\n if (replyPost?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.replyingto,\n bodyHTML: replyPost.textContent.trim()\n })\n );\n }\n }\n\n if (this.module === 'quiz' && this.editor?.id) {\n\n let questionId = this.getQuestionId(this.editor?.id);\n let question = document.querySelector(`#question-${questionId} .qtext`);\n let intro = atob(this.quizInfo.intro);\n\n if (question?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.answeringto,\n bodyHTML: question.textContent\n })\n );\n }\n\n if (intro && intro.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.quiz} ${this.description}`,\n bodyHTML: intro\n })\n );\n }\n\n if (Number(this.quizInfo.open)) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(Number(this.quizInfo.open), Number(this.quizInfo.close))\n })\n );\n }\n }\n\n if (Object.keys(this.Rubrics).length) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.rubrics,\n bodyHTML: this.generateRubrics(this.Rubrics)\n })\n );\n }\n\n if (Dates) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(openDate, dueDate)\n })\n );\n }\n if (this.module === 'assign') {\n content.append(\n this.createBox({\n bg: 'bg-green',\n titleColor: 'text-success',\n icon: this.moduleIcon,\n title: this.subStatus,\n bodyHTML: this.submissionStatus(this.submission)\n })\n );\n }\n\n container.append(btnWrapper, header, content);\n return container;\n\n }\n // Helper to create info boxes\n createBox({bg, titleColor, icon, title, bodyHTML}) {\n const box = this.create('div');\n box.className = `tiny_cursive-fullpage-card ${bg}`;\n\n const heading = this.create('h4');\n heading.className = `tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`;\n heading.innerHTML = `${icon} ${title}`;\n\n const body = this.create('div');\n body.className = `tiny_cursive-fullpage-card-body`;\n body.innerHTML = bodyHTML;\n\n box.append(heading, body);\n return box;\n }\n\n generateRubrics(Rubrics) {\n const wrapper = this.create('div');\n\n Rubrics.forEach(rubric => {\n const rubricDiv = this.create('div');\n rubricDiv.className = 'tiny_cursive-rubric-card';\n\n const title = this.create('h3');\n title.className = 'tiny_cursive-rubric-title';\n title.textContent = rubric.description;\n rubricDiv.appendChild(title);\n\n Object.values(rubric.levels).forEach(level => {\n const levelDiv = this.create('div');\n const score = Number(level.score);\n\n // Assign background color class based on score\n if (score === 0) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-low';\n } else if (score <= 2) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-mid';\n } else {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-high';\n }\n\n levelDiv.textContent = `${level.definition} / ${level.score}`;\n rubricDiv.appendChild(levelDiv);\n });\n\n wrapper.appendChild(rubricDiv);\n });\n\n return wrapper.innerHTML;\n }\n\n submissionStatus(submission) {\n const wrapper = this.create('div');\n\n const statusWrapper = this.create('div');\n statusWrapper.className = 'tiny_cursive-status-row';\n\n const statusName = this.create('span');\n statusName.textContent = `${this.status}:`;\n\n const statusValue = this.create('span');\n const isNew = submission?.current?.status === 'new';\n statusValue.textContent = isNew ? this.draftnot : this.draft;\n statusValue.className = `tiny_cursive-status-value ${isNew ? 'tiny_cursive-status-red' : 'tiny_cursive-status-green'}`;\n\n statusWrapper.append(statusName, statusValue);\n\n const modifiedWrapper = this.create('div');\n modifiedWrapper.className = 'tiny_cursive-status-row';\n\n const modifiedName = this.create('span');\n modifiedName.textContent = `${this.lastModified}: `;\n\n const modifiedValue = this.create('span');\n if (submission?.current?.timemodified) {\n const date = new Date(submission.current.timemodified * 1000);\n modifiedValue.textContent = this.formatDate(date);\n } else {\n modifiedValue.textContent = 'N/A';\n }\n modifiedWrapper.append(modifiedName, modifiedValue);\n\n const gradeWrapper = this.create('div');\n gradeWrapper.className = 'tiny_cursive-status-row';\n\n const gradeName = this.create('span');\n gradeName.textContent = `${this.gradings}: `;\n\n const gradeValue = this.create('span');\n\n if (submission?.grade) {\n gradeValue.textContent = Number(submission.grade.grade) > 0\n ? submission.grade.grade\n : this.gradenot;\n } else {\n gradeValue.textContent = this.gradenot;\n }\n\n gradeWrapper.append(gradeName, gradeValue);\n wrapper.append(statusWrapper, gradeWrapper, modifiedWrapper);\n return wrapper.innerHTML;\n }\n\n wordCounter(status) {\n const wordCount = this.create('div');\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n\n icon.className = 'me-2';\n icon.innerHTML = Icons.assignment;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.wordCount}:`;\n value.textContent = '0';\n value.className = 'text-primary';\n value.style.fontWeight = '600';\n value.style.fontSize = '14px';\n\n wordCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n wordCount.append(labelDiv, value);\n wordCount.style.fontSize = '12px';\n\n const observer = new MutationObserver(() => {\n const newText = status.textContent.trim();\n value.textContent = `${newText.replace('words', '')}`;\n });\n\n observer.observe(status, {\n characterData: true,\n subtree: true,\n childList: true\n });\n\n return wordCount;\n }\n\n\n timerCountDown(timer) {\n\n let warningDiv = document.querySelector('#user-notifications > div');\n if (warningDiv) {\n let clone = warningDiv.cloneNode(true);\n clone.querySelector('button')?.remove();\n this.editor.notificationManager.open({\n text: clone.textContent,\n type: 'error'\n });\n }\n\n\n const timerCount = this.create('div');\n timerCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n icon.innerHTML = Icons.time;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.timeleft}: }`;\n value.textContent = '00:00:00';\n value.className = warningDiv ? 'text-danger' : 'text-primary';\n Object.assign(value.style, {\n fontWeight: '600',\n fontSize: '14px'\n });\n\n\n timerCount.append(labelDiv, value);\n timerCount.style.fontSize = '12px';\n if (timer) {\n const observer = new MutationObserver(() => {\n const newText = timer.textContent.trim();\n value.textContent = `${newText}`;\n });\n observer.observe(timer, {\n characterData: true,\n subtree: true,\n childList: true\n });\n } else {\n value.textContent = this.nolimit;\n }\n\n\n return timerCount;\n }\n\n\n generateStudentInfo(user, course) {\n\n const wrapper = this.create('div');\n\n const nameWrapper = this.create('div');\n const usernameWrapper = this.create('div');\n const courseWrapper = this.create('div');\n\n const nameLabel = this.create('span');\n const nameValue = this.create('span');\n const usernameLabel = this.create('span');\n const usernameValue = this.create('span');\n const courseLabel = this.create('span');\n const courseValue = this.create('span');\n\n nameLabel.textContent = `${this.name}`;\n nameValue.textContent = user.fullname;\n\n usernameLabel.textContent = `${this.userename}: `;\n usernameValue.textContent = user.username;\n\n courseLabel.textContent = `${this.course}: `;\n courseValue.textContent = course.title;\n\n nameWrapper.className = 'd-flex justify-content-between';\n usernameWrapper.className = 'd-flex justify-content-between';\n courseWrapper.className = 'd-flex justify-content-between';\n\n nameWrapper.append(nameLabel, nameValue);\n usernameWrapper.append(usernameLabel, usernameValue);\n courseWrapper.append(courseLabel, courseValue);\n\n wrapper.append(nameWrapper, usernameWrapper, courseWrapper);\n\n return wrapper.innerHTML;\n\n }\n\n generateImportantDates(open, due) {\n\n const wrapper = this.create('div');\n let openDate = null;\n let dueDate = null;\n\n const openedWrapper = this.create('div');\n const dueWrapper = this.create('div');\n const remainingWrapper = this.create('div');\n\n const openedLabel = this.create('span');\n const openedValue = this.create('span');\n const dueLabel = this.create('span');\n const dueValue = this.create('span');\n const remainingLabel = this.create('span');\n const remainingValue = this.create('span');\n if (this.module === 'quiz') {\n openDate = open * 1000;\n dueDate = due * 1000;\n } else {\n openDate = this.extractDate(open?.textContent);\n dueDate = this.extractDate(due?.textContent);\n }\n\n openedLabel.textContent = `${this.opened}: `;\n openedValue.textContent = this.formatDate(openDate ? new Date(openDate) : null);\n openedValue.className = 'text-dark';\n\n dueLabel.textContent = `${this.due}: `;\n dueValue.textContent = this.formatDate(dueDate ? new Date(dueDate) : null);\n dueValue.className = 'text-danger';\n\n remainingLabel.textContent = `${this.remaining}: `;\n remainingValue.textContent = this.calculateDate(dueDate);\n remainingValue.className = 'text-danger';\n\n openedWrapper.className = 'd-flex justify-content-between';\n dueWrapper.className = 'd-flex justify-content-between';\n remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top';\n\n openedWrapper.append(openedLabel, openedValue);\n dueWrapper.append(dueLabel, dueValue);\n remainingWrapper.append(remainingLabel, remainingValue);\n\n wrapper.append(openedWrapper, dueWrapper, remainingWrapper);\n\n return wrapper.innerHTML;\n }\n\n formatDate(date) {\n if (!date) {\n return '-';\n }\n\n let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true};\n return date.toLocaleString('en-US', options);\n }\n\n extractDate(text) {\n if (!text) {\n return '-';\n }\n // Split on first colon and return the right part\n const parts = text?.split(':');\n if (parts.length > 1) {\n return parts.slice(1).join(':').trim();\n }\n\n return text.trim();\n }\n\n\n calculateDate(date) {\n if (!date) {\n return '-';\n }\n const date1 = new Date(date); // Due date (local time)\n const now = new Date(); // Current date/time\n\n // Calculate the difference in milliseconds\n const diffMs = date1 - now;\n\n // Convert to days, hours, minutes\n if (diffMs <= 0) {\n return \"Overdue\";\n } else {\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n const diffHours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);\n\n return `${diffDays} days, ${diffHours} hours`;\n }\n\n }\n\n fullPageModule(module) {\n let current = this.module === 'quiz' ?\n document.getElementById(`${module}_ifr`) : document.querySelector(`#${module}_ifr`);\n\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n let p3 = p2.parentElement;\n let p4 = p3.parentElement;\n\n let statusBar = document.querySelector('.tox-statusbar__right-container > button');\n let assignName = document.querySelector('.page-context-header');\n let header = this.create('div');\n let btn = null;\n\n assignName.classList.remove('mb-2');\n header.id = 'tiny_cursive-fullpage-custom-header';\n Object.assign(header.style, {\n backgroundColor: 'white',\n display: 'flex',\n justifyContent: 'space-between'\n });\n\n if (this.module === 'quiz') {\n btn = document.querySelector('#mod_quiz-next-nav').cloneNode(true);\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.style.margin = '.5rem';\n } else {\n btn = this.create('input');\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.value = this.savechanges;\n btn.type = 'submit';\n btn.style.margin = '.5rem';\n }\n\n\n const leftSide = this.create('div');\n const rightSide = this.create('div');\n let commonStyle = {\n display: 'flex',\n alignItems: 'center',\n margin: '0 1rem'\n };\n\n Object.assign(leftSide.style, commonStyle);\n rightSide.id = 'tiny_cursive-fullpage-right-wrapper';\n Object.assign(rightSide.style, commonStyle);\n\n rightSide.appendChild(btn);\n leftSide.appendChild(assignName.cloneNode(true));\n\n header.appendChild(leftSide);\n header.appendChild(rightSide);\n\n p4.insertBefore(header, p4.firstChild);\n p2.style.backgroundColor = '#efefef';\n Object.assign(current.style, {\n width: '750px',\n minWidth: '750px',\n boxShadow: '0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)'\n });\n\n Object.assign(p1.style, {\n display: 'flex',\n justifyContent: 'center',\n outline: 'none',\n margin: '2rem 0 0'\n });\n const style = this.create('style');\n style.id = 'tiny_cursive-fullpage-mode-style';\n style.textContent = `\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }`;\n document.head.appendChild(style);\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n\n if (iframeBody) {\n iframeBody.style.padding = '0.5in';\n }\n p2.style.position = 'relative';\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let toggle = this.create('div');\n toggle.id = 'cursive-fullpagemode-sidebar-toggle';\n toggle.innerHTML = Icons.hamburger;\n p2.appendChild(toggle);\n p2.appendChild(this.docSideBar(statusBar));\n }\n\n normalizePage(editorId) {\n document.getElementById('tiny_cursive-fullpage-custom-header')?.remove();\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let current = document.getElementById(editorId);\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n\n Object.assign(p2.style, {\n backgroundColor: \"\",\n position: \"\"\n });\n\n Object.assign(current.style, {\n width: '',\n minWidth: '',\n boxShadow: '',\n });\n\n Object.assign(p1.style, {\n display: '',\n justifyContent: '',\n outline: '',\n margin: ''\n });\n\n p1.classList.remove('tiny-cursive-editor-container');\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n if (iframeBody) {\n iframeBody.style.padding = '0';\n }\n document.head.querySelector('#tiny_cursive-fullpage-mode-style')?.remove();\n }\n\n checkForumSubject() {\n const form = document.querySelector('#tiny_cursive-fullpage-right-wrapper > input');\n const msg = this.subjectnot;\n\n if (form) {\n form.addEventListener('click', (e) => {\n const subjectInput = document.getElementById('id_subject');\n let content = this.editor.getContent().trim();\n if (!subjectInput || subjectInput.value.trim() === '' || content === '') {\n e.preventDefault();\n e.stopPropagation();\n this.editor.windowManager.alert(msg);\n }\n });\n }\n }\n\n getSidebarTitle() {\n const [assign, discus, quiz, lesson] = this.getText('sbTitle');\n switch (this.module) {\n case 'assign':\n return {title: assign, icon: Icons.assignment};\n case 'forum':\n return {title: discus, icon: Icons.forum};\n case 'lesson':\n return {title: lesson, icon: Icons.forum};\n case 'quiz':\n return {title: quiz, icon: Icons.quiz};\n default:\n return {title: 'Page', icon: Icons.quiz};\n }\n }\n\n getTimerBlock(module) {\n switch (module) {\n case 'assign':\n return document.querySelector('#mod_assign_timelimit_block > div > div');\n case 'forum':\n return document.querySelector('#mod_forum_timelimit_block');\n case 'lesson':\n return document.querySelector('#lesson-timer');\n case 'quiz':\n return document.querySelector('#quiz-time-left');\n default:\n return null;\n }\n }\n\n getQuestionId(editoId) {\n try {\n if (!editoId || typeof editoId !== 'string') {\n return '';\n }\n return editoId.replace(/^q(\\d+):(\\d+)_.*$/, \"$1-$2\");\n } catch (error) {\n window.console.error('Error getting question ID:', error);\n return '';\n }\n }\n\n initStrings() {\n [\n this.details,\n this.studentInfo,\n this.progress,\n this.description,\n this.replyingto,\n this.answeringto,\n this.importantdates,\n this.rubrics,\n this.subStatus,\n this.status,\n this.draft,\n this.draftnot,\n this.lastModified,\n this.gradings,\n this.gradenot,\n this.wordCount,\n this.timeleft,\n this.nolimit,\n this.name,\n this.userename,\n this.course,\n this.opened,\n this.due,\n this.overdue,\n this.remaining,\n this.savechanges,\n this.subjectnot\n ] = this.getText('docSideBar');\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key)) || [];\n }\n\n create(tag) {\n return document.createElement(tag);\n }\n\n}"],"names":["constructor","User","Rubrics","submission","modulename","editor","quizInfo","module","moduleIcon","Icons","assignment","initStrings","normalMode","id","this","normalizePage","fullPageMode","fullPageModule","_this$editor2","forum","_this$editor3","_this$editor4","quiz","_this$editor5","lesson","_this$editor6","docSideBar","status","replyId","URL","window","location","href","searchParams","get","toggle","document","querySelector","timelimitBlock","getTimerBlock","headerInfo","getSidebarTitle","progressBar","courseName","courseDes","Dates","openDate","dueDate","container","create","Object","assign","className","style","width","overflow","crossBtn","innerHTML","close","addEventListener","transition","display","btnWrapper","padding","position","top","backgroundColor","append","header","headerTitle","textContent","title","details","fontWeight","headerIcon","prepend","cloneNode","wordCount","wordCounter","timerCountDown","content","createBox","bg","titleColor","icon","people","studentInfo","bodyHTML","generateStudentInfo","progress","trim","description","checkForumSubject","replyPost","replyingto","_this$editor7","questionId","getQuestionId","_this$editor8","question","intro","atob","answeringto","Number","open","time","importantdates","generateImportantDates","keys","length","rubrics","generateRubrics","subStatus","submissionStatus","box","heading","body","wrapper","forEach","rubric","rubricDiv","appendChild","values","levels","level","levelDiv","score","definition","statusWrapper","statusName","statusValue","isNew","current","draftnot","draft","modifiedWrapper","modifiedName","lastModified","modifiedValue","_submission$current2","timemodified","date","Date","formatDate","gradeWrapper","gradeName","gradings","gradeValue","grade","gradenot","labelDiv","label","value","fontSize","MutationObserver","newText","replace","observe","characterData","subtree","childList","timer","warningDiv","clone","remove","notificationManager","text","type","timerCount","timeleft","nolimit","user","course","nameWrapper","usernameWrapper","courseWrapper","nameLabel","nameValue","usernameLabel","usernameValue","courseLabel","courseValue","name","fullname","userename","username","due","openedWrapper","dueWrapper","remainingWrapper","openedLabel","openedValue","dueLabel","dueValue","remainingLabel","remainingValue","extractDate","opened","remaining","calculateDate","toLocaleString","year","month","day","hour","minute","hour12","parts","split","slice","join","diffMs","Math","floor","getElementById","p1","parentElement","p2","p4","statusBar","assignName","btn","classList","justifyContent","margin","savechanges","leftSide","rightSide","commonStyle","alignItems","insertBefore","firstChild","minWidth","boxShadow","outline","head","iframeBody","contentDocument","contentWindow","_current$contentWindo","_current$contentWindo2","hamburger","editorId","_current$contentWindo3","_current$contentWindo4","form","msg","subjectnot","e","subjectInput","getContent","preventDefault","stopPropagation","windowManager","alert","discus","getText","editoId","error","console","overdue","key","JSON","parse","localStorage","getItem","tag","createElement"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"document_view.min.js","sources":["../src/document_view.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 provides functionality for document view management in the Tiny editor,\n * including full page mode display and sidebar information\n * @module tiny_cursive/document_view\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Icons from 'tiny_cursive/svg_repo';\nexport default class DocumentView {\n\n constructor(User, Rubrics, submission, modulename, editor, quizInfo) {\n this.User = User;\n this.Rubrics = Rubrics;\n this.submission = submission;\n this.module = modulename;\n this.editor = editor;\n this.moduleIcon = Icons.assignment;\n this.quizInfo = quizInfo;\n this.initStrings();\n }\n\n normalMode() {\n let id = this.editor?.id + \"_ifr\";\n if (this.module === 'assign') {\n this.normalizePage(id);\n } else if (this.module === 'quiz') {\n this.normalizePage(id);\n } else if (this.module === 'forum') {\n this.normalizePage(id);\n } else if (this.module === 'lesson') {\n this.normalizePage(id);\n }\n }\n\n fullPageMode() {\n\n if (this.module === 'assign') {\n this.moduleIcon = Icons.assignment;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'forum') {\n this.moduleIcon = Icons.forum;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'quiz' && this.editor?.id) {\n this.moduleIcon = Icons.quiz;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'lesson') {\n this.moduleIcon = Icons.lesson;\n this.fullPageModule(this.editor?.id);\n }\n }\n\n docSideBar(status) {\n\n const url = new URL(window.location.href);\n const replyId = url.searchParams.get(\"reply\");\n const toggle = document.querySelector('#cursive-fullpagemode-sidebar-toggle');\n const timelimitBlock = this.getTimerBlock(this.module);\n const headerInfo = this.getSidebarTitle();\n const progressBar = document.querySelector('.box.progress_bar');\n\n const courseName = document.querySelector('#page-navbar > nav > ol > li:nth-child(1) > a');\n const courseDes = document.querySelector('#intro');\n const Dates = document.querySelector('.activity-dates');\n\n let openDate = Dates?.querySelector('div:nth-child(1)');\n let dueDate = Dates?.querySelector('div:nth-child(2)');\n\n const container = this.create('div');\n Object.assign(container, {\n id: 'cursive-fullpagemode-sidebar',\n className: 'bg-white h-100 shadow'\n });\n Object.assign(container.style, {\n width: '300px',\n overflow: 'auto'\n });\n\n const crossBtn = this.create('span');\n Object.assign(crossBtn, {\n id: 'cursive-collapse-sidebar',\n className: 'btn p-2',\n innerHTML: Icons.close\n });\n\n crossBtn.addEventListener('click', () => {\n container.style.transition = 'width 0.3s ease';\n container.style.width = '0';\n toggle.style.display = 'flex';\n });\n toggle?.addEventListener('click', function() {\n toggle.style.display = 'none';\n container.style.width = '300px';\n });\n\n const btnWrapper = this.create('div');\n Object.assign(btnWrapper, {\n padding: '0 1rem',\n position: 'sticky',\n top: '0',\n backgroundColor: 'white'\n });\n btnWrapper.append(crossBtn);\n\n\n const header = this.create('div');\n header.className = 'border-bottom p-3 bg-light';\n Object.assign(header.style, {\n position: 'sticky',\n top: '0'\n });\n\n const headerTitle = this.create('h3');\n headerTitle.className = 'mb-3 d-flex align-items-center';\n headerTitle.textContent = `${headerInfo.title} ${this.details}`;\n headerTitle.style.fontWeight = '600';\n\n const headerIcon = document.querySelector('.page-header-image > div');\n if (headerIcon) {\n headerTitle.prepend(headerIcon.cloneNode(true));\n }\n\n let wordCount = this.wordCounter(status);\n if (timelimitBlock?.textContent) {\n header.append(headerTitle, wordCount, this.timerCountDown(timelimitBlock));\n } else {\n header.append(headerTitle, wordCount);\n }\n\n const content = this.create('div');\n content.className = 'p-3';\n\n content.append(\n this.createBox({\n bg: 'bg-info',\n titleColor: 'text-info',\n icon: Icons.people,\n title: this.studentInfo,\n bodyHTML: this.generateStudentInfo(this.User, courseName)\n })\n );\n\n if (this.module === 'lesson' && progressBar) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.progress,\n bodyHTML: progressBar.innerHTML\n })\n );\n }\n\n if (courseDes && courseDes?.textContent.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.getSidebarTitle().title} ${this.description}`,\n bodyHTML: courseDes.innerHTML\n })\n );\n }\n\n if (this.module === 'forum' && replyId) {\n this.checkForumSubject();\n let replyPost = document.querySelector(`#post-content-${replyId}`);\n if (replyPost?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.replyingto,\n bodyHTML: replyPost.textContent.trim()\n })\n );\n }\n }\n\n if (this.module === 'quiz' && this.editor?.id) {\n\n let questionId = this.getQuestionId(this.editor?.id);\n let question = document.querySelector(`#question-${questionId} .qtext`);\n let intro = atob(this.quizInfo.intro);\n\n if (question?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.answeringto,\n bodyHTML: question.textContent\n })\n );\n }\n\n if (intro && intro.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.quiz} ${this.description}`,\n bodyHTML: intro\n })\n );\n }\n\n if (Number(this.quizInfo.open)) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(Number(this.quizInfo.open), Number(this.quizInfo.close))\n })\n );\n }\n }\n\n if (Object.keys(this.Rubrics).length) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.rubrics,\n bodyHTML: this.generateRubrics(this.Rubrics)\n })\n );\n }\n\n if (Dates) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(openDate, dueDate)\n })\n );\n }\n if (this.module === 'assign') {\n content.append(\n this.createBox({\n bg: 'bg-green',\n titleColor: 'text-success',\n icon: this.moduleIcon,\n title: this.subStatus,\n bodyHTML: this.submissionStatus(this.submission)\n })\n );\n }\n\n container.append(btnWrapper, header, content);\n return container;\n\n }\n // Helper to create info boxes\n createBox({bg, titleColor, icon, title, bodyHTML}) {\n const box = this.create('div');\n box.className = `tiny_cursive-fullpage-card ${bg}`;\n\n const heading = this.create('h4');\n heading.className = `tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`;\n heading.innerHTML = `${icon} ${title}`;\n\n const body = this.create('div');\n body.className = `tiny_cursive-fullpage-card-body`;\n body.innerHTML = bodyHTML;\n\n box.append(heading, body);\n return box;\n }\n\n generateRubrics(Rubrics) {\n const wrapper = this.create('div');\n\n Rubrics.forEach(rubric => {\n const rubricDiv = this.create('div');\n rubricDiv.className = 'tiny_cursive-rubric-card';\n\n const title = this.create('h3');\n title.className = 'tiny_cursive-rubric-title';\n title.textContent = rubric.description;\n rubricDiv.appendChild(title);\n\n Object.values(rubric.levels).forEach(level => {\n const levelDiv = this.create('div');\n const score = Number(level.score);\n\n // Assign background color class based on score\n if (score === 0) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-low';\n } else if (score <= 2) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-mid';\n } else {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-high';\n }\n\n levelDiv.textContent = `${level.definition} / ${level.score}`;\n rubricDiv.appendChild(levelDiv);\n });\n\n wrapper.appendChild(rubricDiv);\n });\n\n return wrapper.innerHTML;\n }\n\n submissionStatus(submission) {\n const wrapper = this.create('div');\n\n const statusWrapper = this.create('div');\n statusWrapper.className = 'tiny_cursive-status-row';\n\n const statusName = this.create('span');\n statusName.textContent = `${this.status}:`;\n\n const statusValue = this.create('span');\n const isNew = submission?.current?.status === 'new';\n statusValue.textContent = isNew ? this.draftnot : this.draft;\n statusValue.className = `tiny_cursive-status-value ${isNew ? 'tiny_cursive-status-red' : 'tiny_cursive-status-green'}`;\n\n statusWrapper.append(statusName, statusValue);\n\n const modifiedWrapper = this.create('div');\n modifiedWrapper.className = 'tiny_cursive-status-row';\n\n const modifiedName = this.create('span');\n modifiedName.textContent = `${this.lastModified}: `;\n\n const modifiedValue = this.create('span');\n if (submission?.current?.timemodified) {\n const date = new Date(submission.current.timemodified * 1000);\n modifiedValue.textContent = this.formatDate(date);\n } else {\n modifiedValue.textContent = 'N/A';\n }\n modifiedWrapper.append(modifiedName, modifiedValue);\n\n const gradeWrapper = this.create('div');\n gradeWrapper.className = 'tiny_cursive-status-row';\n\n const gradeName = this.create('span');\n gradeName.textContent = `${this.gradings}: `;\n\n const gradeValue = this.create('span');\n\n if (submission?.grade) {\n gradeValue.textContent = Number(submission.grade.grade) > 0\n ? submission.grade.grade\n : this.gradenot;\n } else {\n gradeValue.textContent = this.gradenot;\n }\n\n gradeWrapper.append(gradeName, gradeValue);\n wrapper.append(statusWrapper, gradeWrapper, modifiedWrapper);\n return wrapper.innerHTML;\n }\n\n wordCounter(status) {\n const wordCount = this.create('div');\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n\n icon.className = 'me-2';\n icon.innerHTML = Icons.assignment;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.wordCount}:`;\n value.textContent = '0';\n value.className = 'text-primary';\n value.style.fontWeight = '600';\n value.style.fontSize = '14px';\n\n wordCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n wordCount.append(labelDiv, value);\n wordCount.style.fontSize = '12px';\n\n const observer = new MutationObserver(() => {\n const newText = status.textContent.trim();\n value.textContent = `${newText.replace('words', '')}`;\n });\n\n observer.observe(status, {\n characterData: true,\n subtree: true,\n childList: true\n });\n\n return wordCount;\n }\n\n\n timerCountDown(timer) {\n\n let warningDiv = document.querySelector('#user-notifications > div');\n if (warningDiv) {\n let clone = warningDiv.cloneNode(true);\n clone.querySelector('button')?.remove();\n this.editor.notificationManager.open({\n text: clone.textContent,\n type: 'error'\n });\n }\n\n\n const timerCount = this.create('div');\n timerCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n icon.innerHTML = Icons.time;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.timeleft}: }`;\n value.textContent = '00:00:00';\n value.className = warningDiv ? 'text-danger' : 'text-primary';\n Object.assign(value.style, {\n fontWeight: '600',\n fontSize: '14px'\n });\n\n\n timerCount.append(labelDiv, value);\n timerCount.style.fontSize = '12px';\n if (timer) {\n const observer = new MutationObserver(() => {\n const newText = timer.textContent.trim();\n value.textContent = `${newText}`;\n });\n observer.observe(timer, {\n characterData: true,\n subtree: true,\n childList: true\n });\n } else {\n value.textContent = this.nolimit;\n }\n\n\n return timerCount;\n }\n\n\n generateStudentInfo(user, course) {\n\n const wrapper = this.create('div');\n\n const nameWrapper = this.create('div');\n const usernameWrapper = this.create('div');\n const courseWrapper = this.create('div');\n\n const nameLabel = this.create('span');\n const nameValue = this.create('span');\n const usernameLabel = this.create('span');\n const usernameValue = this.create('span');\n const courseLabel = this.create('span');\n const courseValue = this.create('span');\n\n nameLabel.textContent = `${this.name}`;\n nameValue.textContent = user.fullname;\n\n usernameLabel.textContent = `${this.userename}: `;\n usernameValue.textContent = user.username;\n\n courseLabel.textContent = `${this.course}: `;\n courseValue.textContent = course.title;\n\n nameWrapper.className = 'd-flex justify-content-between';\n usernameWrapper.className = 'd-flex justify-content-between';\n courseWrapper.className = 'd-flex justify-content-between';\n\n nameWrapper.append(nameLabel, nameValue);\n usernameWrapper.append(usernameLabel, usernameValue);\n courseWrapper.append(courseLabel, courseValue);\n\n wrapper.append(nameWrapper, usernameWrapper, courseWrapper);\n\n return wrapper.innerHTML;\n\n }\n\n generateImportantDates(open, due) {\n\n const wrapper = this.create('div');\n let openDate = null;\n let dueDate = null;\n\n const openedWrapper = this.create('div');\n const dueWrapper = this.create('div');\n const remainingWrapper = this.create('div');\n\n const openedLabel = this.create('span');\n const openedValue = this.create('span');\n const dueLabel = this.create('span');\n const dueValue = this.create('span');\n const remainingLabel = this.create('span');\n const remainingValue = this.create('span');\n if (this.module === 'quiz') {\n openDate = open * 1000;\n dueDate = due * 1000;\n } else {\n openDate = this.extractDate(open?.textContent);\n dueDate = this.extractDate(due?.textContent);\n }\n\n openedLabel.textContent = `${this.opened}: `;\n openedValue.textContent = this.formatDate(openDate ? new Date(openDate) : null);\n openedValue.className = 'text-dark';\n\n dueLabel.textContent = `${this.due}: `;\n dueValue.textContent = this.formatDate(dueDate ? new Date(dueDate) : null);\n dueValue.className = 'text-danger';\n\n remainingLabel.textContent = `${this.remaining}: `;\n remainingValue.textContent = this.calculateDate(dueDate);\n remainingValue.className = 'text-danger';\n\n openedWrapper.className = 'd-flex justify-content-between';\n dueWrapper.className = 'd-flex justify-content-between';\n remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top';\n\n openedWrapper.append(openedLabel, openedValue);\n dueWrapper.append(dueLabel, dueValue);\n remainingWrapper.append(remainingLabel, remainingValue);\n\n wrapper.append(openedWrapper, dueWrapper, remainingWrapper);\n\n return wrapper.innerHTML;\n }\n\n formatDate(date) {\n if (!date) {\n return '-';\n }\n\n let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true};\n return date.toLocaleString('en-US', options);\n }\n\n extractDate(text) {\n if (!text) {\n return '-';\n }\n // Split on first colon and return the right part\n const parts = text?.split(':');\n if (parts.length > 1) {\n return parts.slice(1).join(':').trim();\n }\n\n return text.trim();\n }\n\n\n calculateDate(date) {\n if (!date) {\n return '-';\n }\n const date1 = new Date(date); // Due date (local time)\n const now = new Date(); // Current date/time\n\n // Calculate the difference in milliseconds\n const diffMs = date1 - now;\n\n // Convert to days, hours, minutes\n if (diffMs <= 0) {\n return \"Overdue\";\n } else {\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n const diffHours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);\n\n return `${diffDays} days, ${diffHours} hours`;\n }\n\n }\n\n fullPageModule(module) {\n let current = this.module === 'quiz' ?\n document.getElementById(`${module}_ifr`) : document.querySelector(`#${module}_ifr`);\n\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n let p3 = p2.parentElement;\n let p4 = p3.parentElement;\n\n let statusBar = document.querySelector('.tox-statusbar__right-container > button');\n let assignName = document.querySelector('.page-context-header');\n let header = this.create('div');\n let btn = null;\n\n assignName.classList.remove('mb-2');\n header.id = 'tiny_cursive-fullpage-custom-header';\n Object.assign(header.style, {\n backgroundColor: 'white',\n display: 'flex',\n justifyContent: 'space-between'\n });\n\n if (this.module === 'quiz') {\n btn = document.querySelector('#mod_quiz-next-nav').cloneNode(true);\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.style.margin = '.5rem';\n } else {\n btn = this.create('input');\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.value = this.savechanges;\n btn.type = 'submit';\n btn.style.margin = '.5rem';\n }\n\n\n const leftSide = this.create('div');\n const rightSide = this.create('div');\n let commonStyle = {\n display: 'flex',\n alignItems: 'center',\n margin: '0 1rem'\n };\n\n Object.assign(leftSide.style, commonStyle);\n rightSide.id = 'tiny_cursive-fullpage-right-wrapper';\n Object.assign(rightSide.style, commonStyle);\n\n rightSide.appendChild(btn);\n leftSide.appendChild(assignName.cloneNode(true));\n\n header.appendChild(leftSide);\n header.appendChild(rightSide);\n\n p4.insertBefore(header, p4.firstChild);\n p2.style.backgroundColor = '#efefef';\n Object.assign(current.style, {\n width: '750px',\n minWidth: '750px',\n boxShadow: '0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)'\n });\n\n Object.assign(p1.style, {\n display: 'flex',\n justifyContent: 'center',\n outline: 'none',\n margin: '2rem 0 0'\n });\n const style = this.create('style');\n style.id = 'tiny_cursive-fullpage-mode-style';\n style.textContent = `\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }`;\n document.head.appendChild(style);\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n\n if (iframeBody) {\n iframeBody.style.padding = '0.5in';\n }\n p2.style.position = 'relative';\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let toggle = this.create('div');\n toggle.id = 'cursive-fullpagemode-sidebar-toggle';\n toggle.innerHTML = Icons.hamburger;\n p2.appendChild(toggle);\n p2.appendChild(this.docSideBar(statusBar));\n }\n\n normalizePage(editorId) {\n document.getElementById('tiny_cursive-fullpage-custom-header')?.remove();\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let current = document.getElementById(editorId);\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n\n Object.assign(p2.style, {\n backgroundColor: \"\",\n position: \"\"\n });\n\n Object.assign(current.style, {\n width: '',\n minWidth: '',\n boxShadow: '',\n });\n\n Object.assign(p1.style, {\n display: '',\n justifyContent: '',\n outline: '',\n margin: ''\n });\n\n p1.classList.remove('tiny-cursive-editor-container');\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n if (iframeBody) {\n iframeBody.style.padding = '0';\n }\n document.head.querySelector('#tiny_cursive-fullpage-mode-style')?.remove();\n }\n\n checkForumSubject() {\n const form = document.querySelector('#tiny_cursive-fullpage-right-wrapper > input');\n const msg = this.subjectnot;\n\n if (form) {\n form.addEventListener('click', (e) => {\n const subjectInput = document.getElementById('id_subject');\n let content = this.editor.getContent().trim();\n if (!subjectInput || subjectInput.value.trim() === '' || content === '') {\n e.preventDefault();\n e.stopPropagation();\n this.editor.windowManager.alert(msg);\n }\n });\n }\n }\n\n getSidebarTitle() {\n const [assign, discus, quiz, lesson] = this.getText('sbTitle');\n switch (this.module) {\n case 'assign':\n return {title: assign, icon: Icons.assignment};\n case 'forum':\n return {title: discus, icon: Icons.forum};\n case 'lesson':\n return {title: lesson, icon: Icons.forum};\n case 'quiz':\n return {title: quiz, icon: Icons.quiz};\n default:\n return {title: 'Page', icon: Icons.quiz};\n }\n }\n\n getTimerBlock(module) {\n switch (module) {\n case 'assign':\n return document.querySelector('#mod_assign_timelimit_block > div > div');\n case 'forum':\n return document.querySelector('#mod_forum_timelimit_block');\n case 'lesson':\n return document.querySelector('#lesson-timer');\n case 'quiz':\n return document.querySelector('#quiz-time-left');\n default:\n return null;\n }\n }\n\n getQuestionId(editoId) {\n try {\n if (!editoId || typeof editoId !== 'string') {\n return '';\n }\n return editoId.replace(/^q(\\d+):(\\d+)_.*$/, \"$1-$2\");\n } catch (error) {\n window.console.error('Error getting question ID:', error);\n return '';\n }\n }\n\n initStrings() {\n [\n this.details,\n this.studentInfo,\n this.progress,\n this.description,\n this.replyingto,\n this.answeringto,\n this.importantdates,\n this.rubrics,\n this.subStatus,\n this.status,\n this.draft,\n this.draftnot,\n this.lastModified,\n this.gradings,\n this.gradenot,\n this.wordCount,\n this.timeleft,\n this.nolimit,\n this.name,\n this.userename,\n this.course,\n this.opened,\n this.due,\n this.overdue,\n this.remaining,\n this.savechanges,\n this.subjectnot\n ] = this.getText('docSideBar');\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key)) || [];\n }\n\n create(tag) {\n return document.createElement(tag);\n }\n\n}"],"names":["constructor","User","Rubrics","submission","modulename","editor","quizInfo","module","moduleIcon","Icons","assignment","initStrings","normalMode","id","this","normalizePage","fullPageMode","fullPageModule","_this$editor2","forum","_this$editor3","_this$editor4","quiz","_this$editor5","lesson","_this$editor6","docSideBar","status","replyId","URL","window","location","href","searchParams","get","toggle","document","querySelector","timelimitBlock","getTimerBlock","headerInfo","getSidebarTitle","progressBar","courseName","courseDes","Dates","openDate","dueDate","container","create","Object","assign","className","style","width","overflow","crossBtn","innerHTML","close","addEventListener","transition","display","btnWrapper","padding","position","top","backgroundColor","append","header","headerTitle","textContent","title","details","fontWeight","headerIcon","prepend","cloneNode","wordCount","wordCounter","timerCountDown","content","createBox","bg","titleColor","icon","people","studentInfo","bodyHTML","generateStudentInfo","progress","trim","description","checkForumSubject","replyPost","replyingto","_this$editor7","questionId","getQuestionId","_this$editor8","question","intro","atob","answeringto","Number","open","time","importantdates","generateImportantDates","keys","length","rubrics","generateRubrics","subStatus","submissionStatus","box","heading","body","wrapper","forEach","rubric","rubricDiv","appendChild","values","levels","level","levelDiv","score","definition","statusWrapper","statusName","statusValue","isNew","current","draftnot","draft","modifiedWrapper","modifiedName","lastModified","modifiedValue","_submission$current2","timemodified","date","Date","formatDate","gradeWrapper","gradeName","gradings","gradeValue","grade","gradenot","labelDiv","label","value","fontSize","MutationObserver","newText","replace","observe","characterData","subtree","childList","timer","warningDiv","clone","remove","notificationManager","text","type","timerCount","timeleft","nolimit","user","course","nameWrapper","usernameWrapper","courseWrapper","nameLabel","nameValue","usernameLabel","usernameValue","courseLabel","courseValue","name","fullname","userename","username","due","openedWrapper","dueWrapper","remainingWrapper","openedLabel","openedValue","dueLabel","dueValue","remainingLabel","remainingValue","extractDate","opened","remaining","calculateDate","toLocaleString","year","month","day","hour","minute","hour12","parts","split","slice","join","diffMs","diffDays","Math","floor","diffHours","getElementById","p1","parentElement","p2","p4","statusBar","assignName","btn","classList","justifyContent","margin","savechanges","leftSide","rightSide","commonStyle","alignItems","insertBefore","firstChild","minWidth","boxShadow","outline","head","iframeBody","contentDocument","contentWindow","_current$contentWindo","_current$contentWindo2","hamburger","editorId","_current$contentWindo3","_current$contentWindo4","form","msg","subjectnot","e","subjectInput","getContent","preventDefault","stopPropagation","windowManager","alert","discus","getText","editoId","error","console","overdue","key","JSON","parse","localStorage","getItem","tag","createElement"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/replay.min.js b/amd/build/replay.min.js index 0b222771..f7306974 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","jquery","core/str"],(function(_exports,_ajax,_templates,_jquery,Str){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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_jquery=_interopRequireDefault(_jquery),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.isMetaKeyPressed=!1,this.text="",this.pastedEvents=[],this.currentPasteIndex=0,this.pastedChars=[],this.aiEvents=[],this.currentAiIndex=0,this.aiChars=[],this.undoTimestamps=[],this.undoChars=[];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.identifyUndoEvents(),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))).catch((error=>window.console.log(error))),Str.get_string("pasteEvent","tiny_cursive").then((str=>(localStorage.setItem("pasteEvent",str),str))).catch((error=>window.console.log(error))))}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")]),newElement=(0,_jquery.default)(html).text(str);return(0,_jquery.default)(".tiny_cursive").html(newElement)}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()):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),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)('a[id^="rep"]').addClass("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,metaPressed=!1,shiftPressed=!1,pasteCount=0;for(let i=0;i';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.isMetaKeyPressed=!1,this.currentPasteIndex=0,this.pastedChars=[],this.currentAiIndex=0,this.aiChars=[]),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$event3=event.event)||void 0===_event$event3?void 0:_event$event3.toLowerCase())?({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processKeydownEvent(event,text,cursor,updatedHighlights,updatedDeleted)):"aiInsert"===event.event&&({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processAiInsertEvent(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}}processAiInsertEvent(event,text,cursor,highlights,deletions){if(this.aiEvents&&this.currentAiIndex1,isNewLineInsertion=insertText.startsWith("\n")||insertText.endsWith("\n"),{wordStart:wordStart,wordEnd:wordEnd}=this.findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion),wordToReplace=text.substring(wordStart,wordEnd);this.markCharsAsDeleted(wordToReplace,wordStart,deletions);const replacedLength=wordToReplace.length;text=text.substring(0,wordStart)+insertText+text.substring(wordEnd);const positionDiff=insertText.length-replacedLength,newCursor=this.calculateNewCursorPosition(currentCursor,targetPosition,wordStart,wordEnd,insertText,isNewLineInsertion);return this.updateCharacterIndices(wordStart,wordEnd,positionDiff,insertText),{text:text,cursor:newCursor}}findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion){if(isNewLineInsertion)return{wordStart:currentCursor,wordEnd:currentCursor};const{lineStart:lineStart,lineEnd:lineEnd}=this.findLineRange(text,targetPosition),lineText=text.substring(lineStart,lineEnd),words=this.extractWordsFromLine(lineText,lineStart);return 0===words.length?{wordStart:currentCursor,wordEnd:currentCursor}:isMultiWord?this.findMultiWordMatch(words,aiWords,targetPosition):this.findSingleWordMatch(words,aiWords[0],targetPosition)}findLineRange(text,targetPosition){let lineStart=0;for(let i=targetPosition-1;i>=0;i--)if("\n"===text[i]){lineStart=i+1;break}let lineEnd=text.length;for(let i=targetPosition;i=lineText.length)break;const start=pos;for(;posstart&&words.push({text:lineText.substring(start,pos),start:lineStart+start,end:lineStart+pos})}return words}findMultiWordMatch(words,aiWords,targetPosition){let bestMatch={start:-1,end:-1,score:-1,wordCount:0,similarityScore:0};for(let i=0;ibestMatch.score||matchResult.totalScore===bestMatch.score&&matchResult.similarityScore>bestMatch.similarityScore)&&(bestMatch=matchResult)}if(bestMatch.score>10)return{wordStart:bestMatch.start,wordEnd:bestMatch.end};{const closest=this.findClosestWord(words,targetPosition);return{wordStart:closest.start,wordEnd:closest.end}}}evaluateMultiWordSequence(words,aiWords,startIndex,targetPosition){const seqWords=[];for(let j=0;j=seqStart&&targetPosition<=seqEndPos&&(positionScore+=10,targetPosition>=seqWords[0].start&&targetPosition<=seqWords[0].end&&(positionScore+=5)),positionScore}findSingleWordMatch(words,aiWord,targetPosition){const aiWordLower=aiWord.toLowerCase(),bestSimilarityMatch=this.findBestSimilarityMatch(words,aiWordLower);if(bestSimilarityMatch.score>.5)return{wordStart:bestSimilarityMatch.word.start,wordEnd:bestSimilarityMatch.word.end};const bestPositionMatch=this.findBestPositionMatch(words,aiWordLower,targetPosition);return bestPositionMatch.word?{wordStart:bestPositionMatch.word.start,wordEnd:bestPositionMatch.word.end}:this.findWordBoundaryAtPosition(words[0].start,words[words.length-1].end,targetPosition,this.text)}findBestSimilarityMatch(words,aiWordLower){let bestMatch={word:null,score:0};for(const word of words){let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),similarity>bestMatch.score&&(bestMatch={word:word,score:similarity})}return bestMatch}findBestPositionMatch(words,aiWordLower,targetPosition){let bestMatch={word:null,score:-1};for(const word of words){let score=this.calculateWordScore(word,aiWordLower,targetPosition);score>bestMatch.score&&(bestMatch={word:word,score:score})}return bestMatch}calculateWordScore(word,aiWordLower,targetPosition){let score=0;if(targetPosition>=word.start&&targetPosition<=word.end)score+=30;else{const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));score+=Math.max(0,20-distance)}let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();return wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),score+=10*similarity,score}findWordBoundaryAtPosition(lineStart,lineEnd,targetPosition,text){let wordStart=targetPosition;for(;wordStart>lineStart&&" "!==text[wordStart-1]&&"\n"!==text[wordStart-1];)wordStart--;let wordEnd=targetPosition;for(;wordEnd0)for(let i=0;i=wordStart&&targetPosition<=wordEnd)return wordStart+insertText.length;const positionDiff=insertText.length-(wordEnd-wordStart);return currentCursor>=wordEnd?currentCursor+positionDiff:currentCursor>wordStart&¤tCursorp.index>=wordEnd?{...p,index:p.index+positionDiff}:p.index>=wordStart&&p.indexnull!==p)))}markCharsAsAiInserted(wordStart,insertText){if(this.aiChars||(this.aiChars=[]),""!==insertText.trim())for(let i=0;i{if(!justAddedIndices.has(p.index)){if(p.index>=wordEnd)return{...p,index:p.index+positionDiff};if(p.index>=wordStart&&p.indexnull!==p))}calculateSimilarity(str1,str2){if(str1===str2)return 1;if(0===str1.length||0===str2.length)return 0;if(str1.startsWith(str2)||str2.startsWith(str1))return.8;const len1=str1.length,len2=str2.length,matrix=Array(len2+1).fill(null).map((()=>Array(len1+1).fill(0)));for(let i=0;i<=len1;i++)matrix[0][i]=i;for(let j=0;j<=len2;j++)matrix[j][0]=j;for(let j=1;j<=len2;j++)for(let i=1;i<=len1;i++){const cost=str1[i-1]===str2[j-1]?0:1;matrix[j][i]=Math.min(matrix[j][i-1]+1,matrix[j-1][i]+1,matrix[j-1][i-1]+cost)}const maxLen=Math.max(len1,len2);return 1-matrix[len2][len1]/maxLen}findClosestWord(words,targetPosition){if(0===words.length)return{start:targetPosition,end:targetPosition};let closest=words[0],minDistance=Math.min(Math.abs(targetPosition-words[0].start),Math.abs(targetPosition-words[0].end));for(const word of words){if(targetPosition>=word.start&&targetPosition<=word.end)return word;const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));distance0){const textBeforeUndo=text;text=text.substring(0,newPosition)+text.substring(cursor),cursor=newPosition;for(let i=0;i1}processKeyOperation(key,charToInsert,text,cursor,highlights,deletions,selection){return this.isCtrlBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleCtrlBackspace(text,cursor,deletions)):this.isCtrlDelete(key,cursor,text)?({text:text}=this.handleCtrlDelete(text,cursor,deletions)):this.isCtrlArrowMove(key)?cursor=this.handleCtrlArrowMove(key,text,cursor):this.isRegularBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleBackspace(text,cursor,deletions)):this.isRegularDelete(key,cursor,text)?({text:text}=this.handleDelete(text,cursor,deletions)):this.isArrowUp(key)?cursor=this.handleArrowUp(text,cursor):this.isArrowDown(key)?cursor=this.handleArrowDown(text,cursor):this.isRegularArrowMove(key)?cursor=this.handleArrowMove(key,text,cursor):charToInsert&&charToInsert.length>0&&(selection&&selection.length>0&&({text:text,cursor:cursor}=this.handleSelectionDeletion(selection,text,cursor,deletions)),({text:text,cursor:cursor}=this.handleCharacterInsert(charToInsert,text,cursor,highlights))),{text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}}detectSelection(eventIndex){var _currentEvent$event;const currentEvent=this.logData[eventIndex];if("keydown"===(null===(_currentEvent$event=currentEvent.event)||void 0===_currentEvent$event?void 0:_currentEvent$event.toLowerCase())&&("Backspace"===currentEvent.key||"Delete"===currentEvent.key)){const currentPos=currentEvent.rePosition;return this.processDetection(currentPos,currentEvent,eventIndex)}return null}processDetection(currentPos,currentEvent,eventIndex){for(let i=eventIndex+1;i1)return{start:Math.min(currentPos,nextPos),end:Math.max(currentPos,nextPos),length:positionDiff};if(1===positionDiff)return"Backspace"===currentEvent.key?{start:nextPos,end:currentPos,length:1}:{start:currentPos,end:nextPos,length:1};break}}return null}handleSelectionDeletion(selection,text,cursor,deletions){const{start:start,end:end,length:length}=selection;for(let i=start;ip.index>=startIndex+numDeleted?{...p,index:p.index-numDeleted}:p.index>=startIndex&&p.indexnull!==p)),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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:"Meta"===key?this.isMetaKeyPressed=!0:"v"!==key&&"V"!==key||!this.isControlKeyPressed&&!this.isMetaKeyPressed?["Control","Meta","Backspace","Delete","ArrowLeft","ArrowRight"].includes(key)||(this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.isMetaKeyPressed=!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))),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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.isMetaKeyPressed=!1,this.isPasteEvent=!1,this.pastedChars=[],this.currentPasteIndex=0,this.currentAiIndex=0,this.aiChars=[];let text="",cursor=0,highlights=[],deletions=[],pasteIndex=0,aiIndex=0;for(let i=0;itargetTime){this.currentEventIndex=i;break}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$event4=event.event)||void 0===_event$event4?void 0:_event$event4.toLowerCase())?(this.currentPasteIndex=pasteIndex,"v"!==event.key&&"V"!==event.key||!this.isControlKeyPressed&&!this.isMetaKeyPressed||pasteIndex++,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processKeydownEvent(event,text,cursor,highlights,deletions))):"aiInsert"===event.event&&(this.currentAiIndex=aiIndex,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processAiInsertEvent(event,text,cursor,highlights,deletions)),aiIndex++),this.currentEventIndex=i+1}this.currentPasteIndex=pasteIndex,this.currentAiIndex=aiIndex,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={},aiMap={},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.index{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],isAi=aiMap[currentPosition],isHighlighted=highlightMap[currentPosition]&&" "!==char;html+=isPasted&&isHighlighted?`${char}`:isAi&&isHighlighted?`${char}`:isPasted?`${" "===char?" ":this.escapeHtml(char)}`:isAi?`${" "===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","jquery","core/str"],(function(_exports,_ajax,_templates,_jquery,Str){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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_jquery=_interopRequireDefault(_jquery),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.isMetaKeyPressed=!1,this.text="",this.pastedEvents=[],this.currentPasteIndex=0,this.pastedChars=[],this.aiEvents=[],this.currentAiIndex=0,this.aiChars=[],this.undoTimestamps=[],this.undoChars=[];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.identifyUndoEvents(),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))).catch((error=>window.console.log(error))),Str.get_string("pasteEvent","tiny_cursive").then((str=>(localStorage.setItem("pasteEvent",str),str))).catch((error=>window.console.log(error))))}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")]),newElement=(0,_jquery.default)(html).text(str);return(0,_jquery.default)(".tiny_cursive").html(newElement)}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()):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),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)('a[id^="rep"]').addClass("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,metaPressed=!1,shiftPressed=!1,pasteCount=0;for(let i=0;i';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.isMetaKeyPressed=!1,this.currentPasteIndex=0,this.pastedChars=[],this.currentAiIndex=0,this.aiChars=[]),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$event3=event.event)||void 0===_event$event3?void 0:_event$event3.toLowerCase())?({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processKeydownEvent(event,text,cursor,updatedHighlights,updatedDeleted)):"aiInsert"===event.event&&({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processAiInsertEvent(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}}processAiInsertEvent(event,text,cursor,highlights,deletions){if(this.aiEvents&&this.currentAiIndex1,isNewLineInsertion=insertText.startsWith("\n")||insertText.endsWith("\n"),{wordStart:wordStart,wordEnd:wordEnd}=this.findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion),wordToReplace=text.substring(wordStart,wordEnd);this.markCharsAsDeleted(wordToReplace,wordStart,deletions);const replacedLength=wordToReplace.length;text=text.substring(0,wordStart)+insertText+text.substring(wordEnd);const positionDiff=insertText.length-replacedLength,newCursor=this.calculateNewCursorPosition(currentCursor,targetPosition,wordStart,wordEnd,insertText,isNewLineInsertion);return this.updateCharacterIndices(wordStart,wordEnd,positionDiff,insertText),{text:text,cursor:newCursor}}findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion){if(isNewLineInsertion)return{wordStart:currentCursor,wordEnd:currentCursor};const{lineStart:lineStart,lineEnd:lineEnd}=this.findLineRange(text,targetPosition),lineText=text.substring(lineStart,lineEnd),words=this.extractWordsFromLine(lineText,lineStart);return 0===words.length?{wordStart:currentCursor,wordEnd:currentCursor}:isMultiWord?this.findMultiWordMatch(words,aiWords,targetPosition):this.findSingleWordMatch(words,aiWords[0],targetPosition)}findLineRange(text,targetPosition){let lineStart=0;for(let i=targetPosition-1;i>=0;i--)if("\n"===text[i]){lineStart=i+1;break}let lineEnd=text.length;for(let i=targetPosition;i=lineText.length)break;const start=pos;for(;posstart&&words.push({text:lineText.substring(start,pos),start:lineStart+start,end:lineStart+pos})}return words}findMultiWordMatch(words,aiWords,targetPosition){let bestMatch={start:-1,end:-1,score:-1,wordCount:0,similarityScore:0};for(let i=0;ibestMatch.score||matchResult.totalScore===bestMatch.score&&matchResult.similarityScore>bestMatch.similarityScore)&&(bestMatch=matchResult)}if(bestMatch.score>10)return{wordStart:bestMatch.start,wordEnd:bestMatch.end};{const closest=this.findClosestWord(words,targetPosition);return{wordStart:closest.start,wordEnd:closest.end}}}evaluateMultiWordSequence(words,aiWords,startIndex,targetPosition){const seqWords=[];for(let j=0;j=seqStart&&targetPosition<=seqEndPos&&(positionScore+=10,targetPosition>=seqWords[0].start&&targetPosition<=seqWords[0].end&&(positionScore+=5)),positionScore}findSingleWordMatch(words,aiWord,targetPosition){const aiWordLower=aiWord.toLowerCase(),bestSimilarityMatch=this.findBestSimilarityMatch(words,aiWordLower);if(bestSimilarityMatch.score>.5)return{wordStart:bestSimilarityMatch.word.start,wordEnd:bestSimilarityMatch.word.end};const bestPositionMatch=this.findBestPositionMatch(words,aiWordLower,targetPosition);return bestPositionMatch.word?{wordStart:bestPositionMatch.word.start,wordEnd:bestPositionMatch.word.end}:this.findWordBoundaryAtPosition(words[0].start,words[words.length-1].end,targetPosition,this.text)}findBestSimilarityMatch(words,aiWordLower){let bestMatch={word:null,score:0};for(const word of words){let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),similarity>bestMatch.score&&(bestMatch={word:word,score:similarity})}return bestMatch}findBestPositionMatch(words,aiWordLower,targetPosition){let bestMatch={word:null,score:-1};for(const word of words){let score=this.calculateWordScore(word,aiWordLower,targetPosition);score>bestMatch.score&&(bestMatch={word:word,score:score})}return bestMatch}calculateWordScore(word,aiWordLower,targetPosition){let score=0;if(targetPosition>=word.start&&targetPosition<=word.end)score+=30;else{const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));score+=Math.max(0,20-distance)}let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();return wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),score+=10*similarity,score}findWordBoundaryAtPosition(lineStart,lineEnd,targetPosition,text){let wordStart=targetPosition;for(;wordStart>lineStart&&" "!==text[wordStart-1]&&"\n"!==text[wordStart-1];)wordStart--;let wordEnd=targetPosition;for(;wordEnd0)for(let i=0;i=wordStart&&targetPosition<=wordEnd)return wordStart+insertText.length;const positionDiff=insertText.length-(wordEnd-wordStart);return currentCursor>=wordEnd?currentCursor+positionDiff:currentCursor>wordStart&¤tCursorp.index>=wordEnd?{...p,index:p.index+positionDiff}:p.index>=wordStart&&p.indexnull!==p)))}markCharsAsAiInserted(wordStart,insertText){if(this.aiChars||(this.aiChars=[]),""!==insertText.trim())for(let i=0;i{if(!justAddedIndices.has(p.index)){if(p.index>=wordEnd)return{...p,index:p.index+positionDiff};if(p.index>=wordStart&&p.indexnull!==p))}calculateSimilarity(str1,str2){if(str1===str2)return 1;if(0===str1.length||0===str2.length)return 0;if(str1.startsWith(str2)||str2.startsWith(str1))return.8;const len1=str1.length,len2=str2.length,matrix=Array(len2+1).fill(null).map((()=>Array(len1+1).fill(0)));for(let i=0;i<=len1;i++)matrix[0][i]=i;for(let j=0;j<=len2;j++)matrix[j][0]=j;for(let j=1;j<=len2;j++)for(let i=1;i<=len1;i++){const cost=str1[i-1]===str2[j-1]?0:1;matrix[j][i]=Math.min(matrix[j][i-1]+1,matrix[j-1][i]+1,matrix[j-1][i-1]+cost)}const maxLen=Math.max(len1,len2);return 1-matrix[len2][len1]/maxLen}findClosestWord(words,targetPosition){if(0===words.length)return{start:targetPosition,end:targetPosition};let closest=words[0],minDistance=Math.min(Math.abs(targetPosition-words[0].start),Math.abs(targetPosition-words[0].end));for(const word of words){if(targetPosition>=word.start&&targetPosition<=word.end)return word;const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));distance0){const textBeforeUndo=text;text=text.substring(0,newPosition)+text.substring(cursor),cursor=newPosition;for(let i=0;i1}processKeyOperation(key,charToInsert,text,cursor,highlights,deletions,selection){return this.isCtrlBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleCtrlBackspace(text,cursor,deletions)):this.isCtrlDelete(key,cursor,text)?({text:text}=this.handleCtrlDelete(text,cursor,deletions)):this.isCtrlArrowMove(key)?cursor=this.handleCtrlArrowMove(key,text,cursor):this.isRegularBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleBackspace(text,cursor,deletions)):this.isRegularDelete(key,cursor,text)?({text:text}=this.handleDelete(text,cursor,deletions)):this.isArrowUp(key)?cursor=this.handleArrowUp(text,cursor):this.isArrowDown(key)?cursor=this.handleArrowDown(text,cursor):this.isRegularArrowMove(key)?cursor=this.handleArrowMove(key,text,cursor):charToInsert&&charToInsert.length>0&&(selection&&selection.length>0&&({text:text,cursor:cursor}=this.handleSelectionDeletion(selection,text,cursor,deletions)),({text:text,cursor:cursor}=this.handleCharacterInsert(charToInsert,text,cursor,highlights))),{text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}}detectSelection(eventIndex){var _currentEvent$event;const currentEvent=this.logData[eventIndex];if("keydown"===(null===(_currentEvent$event=currentEvent.event)||void 0===_currentEvent$event?void 0:_currentEvent$event.toLowerCase())&&("Backspace"===currentEvent.key||"Delete"===currentEvent.key)){const currentPos=currentEvent.rePosition;return this.processDetection(currentPos,currentEvent,eventIndex)}return null}processDetection(currentPos,currentEvent,eventIndex){for(let i=eventIndex+1;i1)return{start:Math.min(currentPos,nextPos),end:Math.max(currentPos,nextPos),length:positionDiff};if(1===positionDiff)return"Backspace"===currentEvent.key?{start:nextPos,end:currentPos,length:1}:{start:currentPos,end:nextPos,length:1};break}}return null}handleSelectionDeletion(selection,text,cursor,deletions){const{start:start,end:end,length:length}=selection;for(let i=start;ip.index>=startIndex+numDeleted?{...p,index:p.index-numDeleted}:p.index>=startIndex&&p.indexnull!==p)),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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:"Meta"===key?this.isMetaKeyPressed=!0:"v"!==key&&"V"!==key||!this.isControlKeyPressed&&!this.isMetaKeyPressed?["Control","Meta","Backspace","Delete","ArrowLeft","ArrowRight"].includes(key)||(this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.isMetaKeyPressed=!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))),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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.isMetaKeyPressed=!1,this.isPasteEvent=!1,this.pastedChars=[],this.currentPasteIndex=0,this.currentAiIndex=0,this.aiChars=[];let text="",cursor=0,highlights=[],deletions=[],pasteIndex=0,aiIndex=0;for(let i=0;itargetTime){this.currentEventIndex=i;break}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$event4=event.event)||void 0===_event$event4?void 0:_event$event4.toLowerCase())?(this.currentPasteIndex=pasteIndex,"v"!==event.key&&"V"!==event.key||!this.isControlKeyPressed&&!this.isMetaKeyPressed||pasteIndex++,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processKeydownEvent(event,text,cursor,highlights,deletions))):"aiInsert"===event.event&&(this.currentAiIndex=aiIndex,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processAiInsertEvent(event,text,cursor,highlights,deletions)),aiIndex++),this.currentEventIndex=i+1}this.currentPasteIndex=pasteIndex,this.currentAiIndex=aiIndex,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={},aiMap={},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.index{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],isAi=aiMap[currentPosition],isHighlighted=highlightMap[currentPosition]&&" "!==char;html+=isPasted&&isHighlighted?'').concat(char,""):isAi&&isHighlighted?'').concat(char,""):isPasted?''.concat(" "===char?" ":this.escapeHtml(char),""):isAi?''.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 126f99bc..681f26ef 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 kuldeep singh \n */\n\nimport {call as fetchJson} from 'core/ajax';\nimport templates from 'core/templates';\nimport $ from 'jquery';\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.isMetaKeyPressed = false;\n this.text = '';\n this.pastedEvents = [];\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.aiEvents = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\n this.undoTimestamps = [];\n this.undoChars = [];\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 this.identifyUndoEvents();\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 }).catch(error => window.console.log(error));\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\n localStorage.setItem('pasteEvent', str);\n return str;\n }).catch(error => window.console.log(error));\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') {\n if (typeof event.pastedContent === 'string' && event.pastedContent.trim() !== '') {\n this.pastedEvents.push(event.pastedContent);\n }\n }\n if (event.event === 'aiInsert' && event.aiContent) {\n this.aiEvents.push(event.aiContent);\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 newElement = $(html).text(str);\n return $('.tiny_cursive').html(newElement);\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\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 $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('a[id^=\"rep\"]').addClass('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 let metaPressed = 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 === 'Meta') {\n metaPressed = true;\n } else if (event.key === 'Shift') {\n shiftPressed = true;\n } else if ((event.key === 'v' || event.key === 'V') && (controlPressed || metaPressed)) {\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 metaPressed = false;\n } else {\n controlPressed = false;\n shiftPressed = false;\n metaPressed = false;\n }\n }\n }\n\n if (this.pasteEventsPanel) {\n this.populatePasteEventsPanel(this.pasteEventsPanel);\n }\n }\n\n identifyUndoEvents() {\n this.undoTimestamps = [];\n let controlPressed = false;\n let metaPressed = false;\n let undoCount = 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 === 'Meta') {\n metaPressed = true;\n } else if ((event.key === 'z' || event.key === 'Z') && (controlPressed || metaPressed)) {\n const timestamp = event.normalizedTime || 0;\n this.undoTimestamps.push({\n index: undoCount,\n time: timestamp,\n formattedTime: this.formatTime(timestamp),\n timestamp\n });\n undoCount++;\n controlPressed = false;\n metaPressed = false;\n } else {\n controlPressed = false;\n metaPressed = false;\n }\n }\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.isMetaKeyPressed = false;\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\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 } else if (event.event === 'aiInsert') {\n ({text, cursor, updatedHighlights, updatedDeleted} =\n this.processAiInsertEvent(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 processAiInsertEvent(event, text, cursor, highlights, deletions) {\n if (this.aiEvents && this.currentAiIndex < this.aiEvents.length) {\n const aiContent = this.aiEvents[this.currentAiIndex];\n // Use event.rePosition which points to where the word to replace is\n const targetPosition = event.rePosition;\n\n ({text, cursor} = this.handleAiReplacement(aiContent, text, targetPosition, cursor, deletions));\n this.currentAiIndex++;\n }\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n\n handleAiReplacement(aiContent, text, targetPosition, currentCursor, deletions) {\n const insertText = aiContent || '';\n const aiWords = insertText.trim().split(/\\s+/);\n const isMultiWord = aiWords.length > 1;\n const isNewLineInsertion = insertText.startsWith('\\n') || insertText.endsWith('\\n');\n\n const {wordStart, wordEnd} = this.findWordToReplace(\n text,\n targetPosition,\n currentCursor,\n aiWords,\n isMultiWord,\n isNewLineInsertion\n );\n\n const wordToReplace = text.substring(wordStart, wordEnd);\n\n // Mark replaced characters as deleted\n this.markCharsAsDeleted(wordToReplace, wordStart, deletions);\n\n // Perform the replacement\n const replacedLength = wordToReplace.length;\n text = text.substring(0, wordStart) + insertText + text.substring(wordEnd);\n const positionDiff = insertText.length - replacedLength;\n\n // Calculate new cursor position\n const newCursor = this.calculateNewCursorPosition(\n currentCursor,\n targetPosition,\n wordStart,\n wordEnd,\n insertText,\n isNewLineInsertion\n );\n\n // Update character tracking arrays\n this.updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText);\n\n return {text, cursor: newCursor};\n }\n\n findWordToReplace(text, targetPosition, currentCursor, aiWords, isMultiWord, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n const {lineStart, lineEnd} = this.findLineRange(text, targetPosition);\n const lineText = text.substring(lineStart, lineEnd);\n const words = this.extractWordsFromLine(lineText, lineStart);\n\n if (words.length === 0) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n if (isMultiWord) {\n return this.findMultiWordMatch(words, aiWords, targetPosition);\n } else {\n return this.findSingleWordMatch(words, aiWords[0], targetPosition);\n }\n }\n\n findLineRange(text, targetPosition) {\n let lineStart = 0;\n for (let i = targetPosition - 1; i >= 0; i--) {\n if (text[i] === '\\n') {\n lineStart = i + 1;\n break;\n }\n }\n\n let lineEnd = text.length;\n for (let i = targetPosition; i < text.length; i++) {\n if (text[i] === '\\n') {\n lineEnd = i;\n break;\n }\n }\n\n return {lineStart, lineEnd};\n }\n\n extractWordsFromLine(lineText, lineStart) {\n const words = [];\n let pos = 0;\n\n while (pos < lineText.length) {\n // Skip spaces\n while (pos < lineText.length && lineText[pos] === ' ') {\n pos++;\n }\n if (pos >= lineText.length) {\n break;\n }\n\n // Extract word\n const start = pos;\n while (pos < lineText.length && lineText[pos] !== ' ') {\n pos++;\n }\n\n if (pos > start) {\n words.push({\n text: lineText.substring(start, pos),\n start: lineStart + start,\n end: lineStart + pos\n });\n }\n }\n\n return words;\n }\n\n findMultiWordMatch(words, aiWords, targetPosition) {\n let bestMatch = {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n\n for (let i = 0; i < words.length; i++) {\n const matchResult = this.evaluateMultiWordSequence(words, aiWords, i, targetPosition);\n\n if (matchResult.totalScore > bestMatch.score ||\n (matchResult.totalScore === bestMatch.score &&\n matchResult.similarityScore > bestMatch.similarityScore)) {\n bestMatch = matchResult;\n }\n }\n\n if (bestMatch.score > 10) {\n return {wordStart: bestMatch.start, wordEnd: bestMatch.end};\n } else {\n const closest = this.findClosestWord(words, targetPosition);\n return {wordStart: closest.start, wordEnd: closest.end};\n }\n }\n\n evaluateMultiWordSequence(words, aiWords, startIndex, targetPosition) {\n const seqWords = [];\n for (let j = 0; j < aiWords.length && startIndex + j < words.length; j++) {\n seqWords.push(words[startIndex + j]);\n }\n\n if (seqWords.length === 0) {\n return {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n }\n\n const similarityScore = this.calculateSequenceSimilarity(aiWords, seqWords);\n const positionScore = this.calculatePositionScore(seqWords, targetPosition);\n const totalScore = similarityScore + positionScore + seqWords.length;\n\n return {\n start: seqWords[0].start,\n end: seqWords[seqWords.length - 1].end,\n score: totalScore,\n wordCount: seqWords.length,\n similarityScore: similarityScore\n };\n }\n\n calculateSequenceSimilarity(aiWords, seqWords) {\n let similarityScore = 0;\n const compareLength = Math.min(seqWords.length, aiWords.length);\n\n for (let k = 0; k < compareLength; k++) {\n const ai = aiWords[k].toLowerCase();\n const seq = seqWords[k].text.toLowerCase();\n\n if (ai === seq) {\n similarityScore += 10;\n } else {\n const similarity = this.calculateSimilarity(ai, seq);\n similarityScore += similarity * 10;\n }\n }\n\n return similarityScore;\n }\n\n calculatePositionScore(seqWords, targetPosition) {\n let positionScore = 0;\n const seqStart = seqWords[0].start;\n const seqEndPos = seqWords[seqWords.length - 1].end;\n\n if (targetPosition >= seqStart && targetPosition <= seqEndPos) {\n positionScore += 10;\n if (targetPosition >= seqWords[0].start && targetPosition <= seqWords[0].end) {\n positionScore += 5;\n }\n }\n\n return positionScore;\n }\n\n findSingleWordMatch(words, aiWord, targetPosition) {\n const aiWordLower = aiWord.toLowerCase();\n const bestSimilarityMatch = this.findBestSimilarityMatch(words, aiWordLower);\n\n if (bestSimilarityMatch.score > 0.5) {\n return {wordStart: bestSimilarityMatch.word.start, wordEnd: bestSimilarityMatch.word.end};\n }\n\n const bestPositionMatch = this.findBestPositionMatch(words, aiWordLower, targetPosition);\n\n if (bestPositionMatch.word) {\n return {wordStart: bestPositionMatch.word.start, wordEnd: bestPositionMatch.word.end};\n }\n\n // Fallback to position-based word boundary\n return this.findWordBoundaryAtPosition(words[0].start, words[words.length - 1].end,\n targetPosition, this.text);\n }\n\n findBestSimilarityMatch(words, aiWordLower) {\n let bestMatch = {word: null, score: 0};\n\n for (const word of words) {\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n\n // Penalize short prefix matches\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n\n if (similarity > bestMatch.score) {\n bestMatch = {word, score: similarity};\n }\n }\n\n return bestMatch;\n }\n\n findBestPositionMatch(words, aiWordLower, targetPosition) {\n let bestMatch = {word: null, score: -1};\n\n for (const word of words) {\n let score = this.calculateWordScore(word, aiWordLower, targetPosition);\n\n if (score > bestMatch.score) {\n bestMatch = {word, score};\n }\n }\n\n return bestMatch;\n }\n\n calculateWordScore(word, aiWordLower, targetPosition) {\n let score = 0;\n\n // Position score\n if (targetPosition >= word.start && targetPosition <= word.end) {\n score += 30;\n } else {\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n score += Math.max(0, 20 - distance);\n }\n\n // Similarity score with penalty\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n score += similarity * 10;\n\n return score;\n }\n\n findWordBoundaryAtPosition(lineStart, lineEnd, targetPosition, text) {\n let wordStart = targetPosition;\n while (wordStart > lineStart && text[wordStart - 1] !== ' ' && text[wordStart - 1] !== '\\n') {\n wordStart--;\n }\n let wordEnd = targetPosition;\n while (wordEnd < lineEnd && text[wordEnd] !== ' ' && text[wordEnd] !== '\\n') {\n wordEnd++;\n }\n return {wordStart, wordEnd};\n }\n\n markCharsAsDeleted(wordToReplace, wordStart, deletions) {\n if (wordToReplace.length > 0) {\n for (let i = 0; i < wordToReplace.length; i++) {\n deletions.push({\n index: wordStart + i,\n chars: wordToReplace[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n\n calculateNewCursorPosition(currentCursor, targetPosition, wordStart, wordEnd, insertText, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return wordStart + insertText.length;\n }\n\n if (targetPosition >= wordStart && targetPosition <= wordEnd) {\n return wordStart + insertText.length;\n }\n\n const positionDiff = insertText.length - (wordEnd - wordStart);\n\n if (currentCursor >= wordEnd) {\n return currentCursor + positionDiff;\n } else if (currentCursor > wordStart && currentCursor < wordEnd) {\n return wordStart + insertText.length;\n }\n\n return currentCursor;\n }\n\n updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText) {\n // Update pasted character indices\n this.updatePastedCharIndices(wordStart, wordEnd, positionDiff);\n\n // Mark characters as AI-inserted\n this.markCharsAsAiInserted(wordStart, insertText);\n\n // Update AI character indices\n this.updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText);\n }\n\n updatePastedCharIndices(wordStart, wordEnd, positionDiff) {\n if (this.pastedChars) {\n this.pastedChars = this.pastedChars.map(p => {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n return p;\n }).filter(p => p !== null);\n }\n }\n\n markCharsAsAiInserted(wordStart, insertText) {\n if (!this.aiChars) {\n this.aiChars = [];\n }\n\n if (insertText.trim() !== '') {\n for (let i = 0; i < insertText.length; i++) {\n this.aiChars.push({\n index: wordStart + i,\n chars: insertText[i]\n });\n }\n }\n }\n\n updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText) {\n const justAddedIndices = new Set();\n for (let i = 0; i < insertText.length; i++) {\n justAddedIndices.add(wordStart + i);\n }\n\n this.aiChars = this.aiChars.map(p => {\n if (!justAddedIndices.has(p.index)) {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n }\n return p;\n }).filter(p => p !== null);\n }\n\n // Calculate similarity between two strings\n calculateSimilarity(str1, str2) {\n if (str1 === str2) {\n return 1;\n }\n if (str1.length === 0 || str2.length === 0) {\n return 0;\n }\n\n // Check if one string is a prefix of the other\n if (str1.startsWith(str2) || str2.startsWith(str1)) {\n return 0.8;\n }\n\n // Levenshtein distance\n const len1 = str1.length;\n const len2 = str2.length;\n const matrix = Array(len2 + 1).fill(null).map(() => Array(len1 + 1).fill(0));\n\n for (let i = 0; i <= len1; i++) {\n matrix[0][i] = i;\n }\n for (let j = 0; j <= len2; j++) {\n matrix[j][0] = j;\n }\n\n for (let j = 1; j <= len2; j++) {\n for (let i = 1; i <= len1; i++) {\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\n matrix[j][i] = Math.min(\n matrix[j][i - 1] + 1,\n matrix[j - 1][i] + 1,\n matrix[j - 1][i - 1] + cost\n );\n }\n }\n\n const maxLen = Math.max(len1, len2);\n return 1 - (matrix[len2][len1] / maxLen);\n }\n\n // Find the word closest to a target position\n findClosestWord(words, targetPosition) {\n if (words.length === 0) {\n return {start: targetPosition, end: targetPosition};\n }\n\n let closest = words[0];\n let minDistance = Math.min(\n Math.abs(targetPosition - words[0].start),\n Math.abs(targetPosition - words[0].end)\n );\n\n for (const word of words) {\n if (targetPosition >= word.start && targetPosition <= word.end) {\n return word;\n }\n\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n\n if (distance < minDistance) {\n minDistance = distance;\n closest = word;\n }\n }\n\n return closest;\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\n // Handle copy operation (Ctrl+C)\n if (this.isCopyOperation(key)) {\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Handle undo operation (Ctrl+Z)\n if (this.isUndoOperation(key)) {\n return this.handleUndoOperation(event, text, cursor, highlights, deletions);\n }\n\n // Detect selection for current event\n const currentEventIndex = this.currentEventIndex;\n const selection = this.detectSelection(currentEventIndex);\n\n // Handle paste operation (Ctrl+V)\n if (this.isPasteOperation(key, event)) {\n return this.handlePasteOperation(event, selection, text, cursor, highlights, deletions);\n }\n\n // Update modifier key states\n this.updateModifierStates(key);\n\n // Handle selection deletion with Backspace/Delete\n if (this.isSelectionDeletion(key, selection)) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Process various key operations\n return this.processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection);\n }\n\n isCopyOperation(key) {\n return (key === 'c' || key === 'C') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n isUndoOperation(key) {\n return (key === 'z' || key === 'Z') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n handleUndoOperation(event, text, cursor, highlights, deletions) {\n const nextEventIndex = this.currentEventIndex + 1;\n if (nextEventIndex < this.logData.length) {\n const nextEvent = this.logData[nextEventIndex];\n\n if (nextEvent.event === 'keyUp' && (nextEvent.key === 'z' || nextEvent.key === 'Z')) {\n const newPosition = nextEvent.rePosition;\n if (newPosition < cursor && text.length > 0) {\n const textBeforeUndo = text;\n text = text.substring(0, newPosition) + text.substring(cursor);\n cursor = newPosition;\n\n // Mark as deleted for visual effect\n for (let i = 0; i < textBeforeUndo.length && i < cursor; i++) {\n deletions.push({\n index: newPosition,\n chars: textBeforeUndo[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n }\n\n this.isControlKeyPressed = false;\n this.isMetaKeyPressed = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n isPasteOperation(key, event) {\n if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n return (event.pastedContent && event.pastedContent.trim() !== '') ||\n (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length);\n }\n return false;\n }\n\n handlePasteOperation(event, selection, text, cursor, highlights, deletions) {\n const pastedContent = event.pastedContent || this.pastedEvents[this.currentPasteIndex];\n\n if (selection) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\n this.currentPasteIndex++;\n this.resetModifierStates();\n this.isPasteEvent = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n resetModifierStates() {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = false;\n }\n\n isSelectionDeletion(key, selection) {\n return (key === 'Backspace' || key === 'Delete') && selection && selection.length > 1;\n }\n\n processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection) {\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 if (selection && selection.length > 0) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\n }\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n detectSelection(eventIndex) {\n const currentEvent = this.logData[eventIndex];\n\n if (currentEvent.event?.toLowerCase() === 'keydown' &&\n (currentEvent.key === 'Backspace' || currentEvent.key === 'Delete')) {\n\n const currentPos = currentEvent.rePosition;\n return this.processDetection(currentPos, currentEvent, eventIndex);\n }\n return null;\n }\n\n processDetection(currentPos, currentEvent, eventIndex) {\n for (let i = eventIndex + 1; i < this.logData.length; i++) {\n const nextEvent = this.logData[i];\n\n if (nextEvent.event?.toLowerCase() === 'keyup' &&\n nextEvent.key === currentEvent.key) {\n\n const nextPos = nextEvent.rePosition;\n\n // Calculate the difference in positions\n const positionDiff = Math.abs(currentPos - nextPos);\n\n if (positionDiff > 1) {\n return {\n start: Math.min(currentPos, nextPos),\n end: Math.max(currentPos, nextPos),\n length: positionDiff\n };\n } else if (positionDiff === 1) {\n if (currentEvent.key === 'Backspace') {\n return {\n start: nextPos,\n end: currentPos,\n length: 1\n };\n } else {\n return {\n start: currentPos,\n end: nextPos,\n length: 1\n };\n }\n }\n break;\n }\n }\n return null;\n }\n\n handleSelectionDeletion(selection, text, cursor, deletions) {\n const {start, end, length} = selection;\n\n // Add each character in the selection to the deletions array\n for (let i = start; i < end && i < text.length; i++) {\n deletions.push({\n index: start,\n chars: text[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n\n text = text.substring(0, start) + text.substring(end);\n\n this.shiftPastedCharsIndices(start, length);\n\n cursor = start;\n\n return {text, cursor};\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 if (this.aiChars) {\n this.aiChars = this.aiChars.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 return null;\n }\n return p;\n }).filter(p => p !== null);\n }\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 === 'Meta') {\n this.isMetaKeyPressed = true;\n } else if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n this.isPasteEvent = true;\n } else if (!['Control', 'Meta', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = 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 (this.aiChars) {\n this.aiChars = this.aiChars.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.isMetaKeyPressed = false;\n this.isPasteEvent = false;\n this.pastedChars = [];\n this.currentPasteIndex = 0;\n this.currentAiIndex = 0;\n this.aiChars = [];\n let text = '';\n let cursor = 0;\n let highlights = [];\n let deletions = [];\n let pasteIndex = 0;\n let aiIndex = 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 && (this.currentEventIndex === 0 ||\n 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 || this.isMetaKeyPressed)) {\n pasteIndex++;\n }\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\n } else if (event.event === 'aiInsert') {\n this.currentAiIndex = aiIndex;\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processAiInsertEvent(event, text, cursor, highlights, deletions));\n aiIndex++;\n }\n this.currentEventIndex = i + 1;\n }\n\n this.currentPasteIndex = pasteIndex;\n this.currentAiIndex = aiIndex;\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 // eslint-disable-next-line complexity\n updateDisplayText(text, cursorPosition, highlights, deletions) {\n let html = '';\n const highlightMap = {};\n const deletionMap = {};\n const pastedMap = {};\n const aiMap = {};\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 // Process AI characters for styling\n if (this.aiChars) {\n this.aiChars.forEach(p => {\n if (p.index < text.length) {\n aiMap[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 isAi = aiMap[currentPosition];\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\n\n if (isPasted && isHighlighted) {\n html += `${char}`;\n } else if (isAi && isHighlighted) {\n html += `${char}`;\n } else if (isPasted) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isAi) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isHighlighted) {\n html += `${char}`;\n } else {\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","isMetaKeyPressed","text","pastedEvents","currentPasteIndex","pastedChars","aiEvents","currentAiIndex","aiChars","undoTimestamps","undoChars","element","document","getElementById","Error","outputElement","loadJSON","then","data","status","processData","this","logData","length","identifyPasteEvents","identifyUndoEvents","constructController","startReplay","handleNoSubmission","catch","error","window","console","message","localStorage","getItem","Str","get_string","str","setItem","log","JSON","parse","comments","Array","isArray","payload","i","event","pastedContent","trim","push","aiContent","unixTimestamp","startTime","map","normalizedTime","html","Promise","all","templates","render","newElement","stopReplay","clearTimeout","replayTimeout","playButton","playSvg","createElement","src","M","util","image_url","querySelector","innerHTML","outerHTML","currentPosition","replayIntervalId","clearInterval","container","controlContainer","buildControllerUI","remove","topRow","classList","add","createPlayButton","appendChild","scrubberContainer","createScrubberContainer","timeDisplay","createTimeDisplay","bottomRow","speedContainer","createSpeedControls","pasteEventsToggle","createPasteEventsToggle","pasteEventsPanel","addEventListener","find","removeClass","addClass","scrubberElement","type","max","min","value","skipToTime","parseInt","textContent","speedLabel","speedGroup","forEach","speedBtn","dataset","querySelectorAll","btn","pasteEventsIcon","pasteIcon","pasteEventsText","pasteEventCount","className","style","marginLeft","chevronIcon","chevron","transition","createPasteEventsPanel","isHidden","display","transform","existingPanel","populatePasteEventsPanel","controlPressed","metaPressed","shiftPressed","pasteCount","toLowerCase","key","timestamp","index","time","formattedTime","formatTime","pastedText","undoCount","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","processAiInsertEvent","filter","h","expiresAt","d","updateDisplayText","percentComplete","baseIncrement","incrementTime","setTimeout","getLineAndColumn","pos","before","substring","lineIndex","split","col","lastIndexOf","highlights","deletions","targetPosition","handleAiReplacement","currentCursor","insertText","aiWords","isMultiWord","isNewLineInsertion","startsWith","endsWith","wordStart","wordEnd","findWordToReplace","wordToReplace","markCharsAsDeleted","replacedLength","positionDiff","newCursor","calculateNewCursorPosition","updateCharacterIndices","lineStart","lineEnd","findLineRange","lineText","words","extractWordsFromLine","findMultiWordMatch","findSingleWordMatch","start","end","bestMatch","score","wordCount","similarityScore","matchResult","evaluateMultiWordSequence","totalScore","closest","findClosestWord","startIndex","seqWords","j","calculateSequenceSimilarity","calculatePositionScore","compareLength","k","ai","seq","calculateSimilarity","positionScore","seqStart","seqEndPos","aiWord","aiWordLower","bestSimilarityMatch","findBestSimilarityMatch","word","bestPositionMatch","findBestPositionMatch","findWordBoundaryAtPosition","similarity","wordLower","calculateWordScore","distance","abs","chars","updatePastedCharIndices","markCharsAsAiInserted","updateAiCharIndices","p","justAddedIndices","Set","has","str1","str2","len1","len2","matrix","fill","cost","maxLen","minDistance","charToInsert","applyKey","isCopyOperation","isUndoOperation","handleUndoOperation","selection","detectSelection","isPasteOperation","handlePasteOperation","updateModifierStates","isSelectionDeletion","handleSelectionDeletion","processKeyOperation","nextEventIndex","nextEvent","newPosition","textBeforeUndo","handlePasteInsert","resetModifierStates","isCtrlBackspace","handleCtrlBackspace","isCtrlDelete","handleCtrlDelete","isCtrlArrowMove","handleCtrlArrowMove","isRegularBackspace","handleBackspace","isRegularDelete","handleDelete","isArrowUp","handleArrowUp","isArrowDown","handleArrowDown","isRegularArrowMove","handleArrowMove","handleCharacterInsert","eventIndex","currentEvent","currentPos","processDetection","nextPos","shiftPastedCharsIndices","numDeleted","includes","findPreviousWordBoundary","findNextWordBoundary","wordToDelete","lines","prevLine","slice","join","nextLine","lastNonSpace","skipToEnd","textOutput","wasPlaying","targetTime","pasteIndex","aiIndex","highlightMap","deletionMap","pastedMap","aiMap","outOfRangeDeletions","textLines","line","char","isPasted","isAi","isHighlighted","escapeHtml","sort","a","b","cursorHTML","cursorPos","deletedWordHTML","wasScrolledToBottom","scrollHeight","clientHeight","scrollTop","isCursorBelowViewport","cursorElement","cursorRect","getBoundingClientRect","outputRect","bottom","unsafe","replace"],"mappings":"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"} \ 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 kuldeep singh \n */\n\nimport {call as fetchJson} from 'core/ajax';\nimport templates from 'core/templates';\nimport $ from 'jquery';\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.isMetaKeyPressed = false;\n this.text = '';\n this.pastedEvents = [];\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.aiEvents = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\n this.undoTimestamps = [];\n this.undoChars = [];\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 this.identifyUndoEvents();\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 }).catch(error => window.console.log(error));\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\n localStorage.setItem('pasteEvent', str);\n return str;\n }).catch(error => window.console.log(error));\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') {\n if (typeof event.pastedContent === 'string' && event.pastedContent.trim() !== '') {\n this.pastedEvents.push(event.pastedContent);\n }\n }\n if (event.event === 'aiInsert' && event.aiContent) {\n this.aiEvents.push(event.aiContent);\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 newElement = $(html).text(str);\n return $('.tiny_cursive').html(newElement);\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\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 $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('a[id^=\"rep\"]').addClass('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 let metaPressed = 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 === 'Meta') {\n metaPressed = true;\n } else if (event.key === 'Shift') {\n shiftPressed = true;\n } else if ((event.key === 'v' || event.key === 'V') && (controlPressed || metaPressed)) {\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 metaPressed = false;\n } else {\n controlPressed = false;\n shiftPressed = false;\n metaPressed = false;\n }\n }\n }\n\n if (this.pasteEventsPanel) {\n this.populatePasteEventsPanel(this.pasteEventsPanel);\n }\n }\n\n identifyUndoEvents() {\n this.undoTimestamps = [];\n let controlPressed = false;\n let metaPressed = false;\n let undoCount = 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 === 'Meta') {\n metaPressed = true;\n } else if ((event.key === 'z' || event.key === 'Z') && (controlPressed || metaPressed)) {\n const timestamp = event.normalizedTime || 0;\n this.undoTimestamps.push({\n index: undoCount,\n time: timestamp,\n formattedTime: this.formatTime(timestamp),\n timestamp\n });\n undoCount++;\n controlPressed = false;\n metaPressed = false;\n } else {\n controlPressed = false;\n metaPressed = false;\n }\n }\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.isMetaKeyPressed = false;\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\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 } else if (event.event === 'aiInsert') {\n ({text, cursor, updatedHighlights, updatedDeleted} =\n this.processAiInsertEvent(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 processAiInsertEvent(event, text, cursor, highlights, deletions) {\n if (this.aiEvents && this.currentAiIndex < this.aiEvents.length) {\n const aiContent = this.aiEvents[this.currentAiIndex];\n // Use event.rePosition which points to where the word to replace is\n const targetPosition = event.rePosition;\n\n ({text, cursor} = this.handleAiReplacement(aiContent, text, targetPosition, cursor, deletions));\n this.currentAiIndex++;\n }\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n\n handleAiReplacement(aiContent, text, targetPosition, currentCursor, deletions) {\n const insertText = aiContent || '';\n const aiWords = insertText.trim().split(/\\s+/);\n const isMultiWord = aiWords.length > 1;\n const isNewLineInsertion = insertText.startsWith('\\n') || insertText.endsWith('\\n');\n\n const {wordStart, wordEnd} = this.findWordToReplace(\n text,\n targetPosition,\n currentCursor,\n aiWords,\n isMultiWord,\n isNewLineInsertion\n );\n\n const wordToReplace = text.substring(wordStart, wordEnd);\n\n // Mark replaced characters as deleted\n this.markCharsAsDeleted(wordToReplace, wordStart, deletions);\n\n // Perform the replacement\n const replacedLength = wordToReplace.length;\n text = text.substring(0, wordStart) + insertText + text.substring(wordEnd);\n const positionDiff = insertText.length - replacedLength;\n\n // Calculate new cursor position\n const newCursor = this.calculateNewCursorPosition(\n currentCursor,\n targetPosition,\n wordStart,\n wordEnd,\n insertText,\n isNewLineInsertion\n );\n\n // Update character tracking arrays\n this.updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText);\n\n return {text, cursor: newCursor};\n }\n\n findWordToReplace(text, targetPosition, currentCursor, aiWords, isMultiWord, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n const {lineStart, lineEnd} = this.findLineRange(text, targetPosition);\n const lineText = text.substring(lineStart, lineEnd);\n const words = this.extractWordsFromLine(lineText, lineStart);\n\n if (words.length === 0) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n if (isMultiWord) {\n return this.findMultiWordMatch(words, aiWords, targetPosition);\n } else {\n return this.findSingleWordMatch(words, aiWords[0], targetPosition);\n }\n }\n\n findLineRange(text, targetPosition) {\n let lineStart = 0;\n for (let i = targetPosition - 1; i >= 0; i--) {\n if (text[i] === '\\n') {\n lineStart = i + 1;\n break;\n }\n }\n\n let lineEnd = text.length;\n for (let i = targetPosition; i < text.length; i++) {\n if (text[i] === '\\n') {\n lineEnd = i;\n break;\n }\n }\n\n return {lineStart, lineEnd};\n }\n\n extractWordsFromLine(lineText, lineStart) {\n const words = [];\n let pos = 0;\n\n while (pos < lineText.length) {\n // Skip spaces\n while (pos < lineText.length && lineText[pos] === ' ') {\n pos++;\n }\n if (pos >= lineText.length) {\n break;\n }\n\n // Extract word\n const start = pos;\n while (pos < lineText.length && lineText[pos] !== ' ') {\n pos++;\n }\n\n if (pos > start) {\n words.push({\n text: lineText.substring(start, pos),\n start: lineStart + start,\n end: lineStart + pos\n });\n }\n }\n\n return words;\n }\n\n findMultiWordMatch(words, aiWords, targetPosition) {\n let bestMatch = {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n\n for (let i = 0; i < words.length; i++) {\n const matchResult = this.evaluateMultiWordSequence(words, aiWords, i, targetPosition);\n\n if (matchResult.totalScore > bestMatch.score ||\n (matchResult.totalScore === bestMatch.score &&\n matchResult.similarityScore > bestMatch.similarityScore)) {\n bestMatch = matchResult;\n }\n }\n\n if (bestMatch.score > 10) {\n return {wordStart: bestMatch.start, wordEnd: bestMatch.end};\n } else {\n const closest = this.findClosestWord(words, targetPosition);\n return {wordStart: closest.start, wordEnd: closest.end};\n }\n }\n\n evaluateMultiWordSequence(words, aiWords, startIndex, targetPosition) {\n const seqWords = [];\n for (let j = 0; j < aiWords.length && startIndex + j < words.length; j++) {\n seqWords.push(words[startIndex + j]);\n }\n\n if (seqWords.length === 0) {\n return {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n }\n\n const similarityScore = this.calculateSequenceSimilarity(aiWords, seqWords);\n const positionScore = this.calculatePositionScore(seqWords, targetPosition);\n const totalScore = similarityScore + positionScore + seqWords.length;\n\n return {\n start: seqWords[0].start,\n end: seqWords[seqWords.length - 1].end,\n score: totalScore,\n wordCount: seqWords.length,\n similarityScore: similarityScore\n };\n }\n\n calculateSequenceSimilarity(aiWords, seqWords) {\n let similarityScore = 0;\n const compareLength = Math.min(seqWords.length, aiWords.length);\n\n for (let k = 0; k < compareLength; k++) {\n const ai = aiWords[k].toLowerCase();\n const seq = seqWords[k].text.toLowerCase();\n\n if (ai === seq) {\n similarityScore += 10;\n } else {\n const similarity = this.calculateSimilarity(ai, seq);\n similarityScore += similarity * 10;\n }\n }\n\n return similarityScore;\n }\n\n calculatePositionScore(seqWords, targetPosition) {\n let positionScore = 0;\n const seqStart = seqWords[0].start;\n const seqEndPos = seqWords[seqWords.length - 1].end;\n\n if (targetPosition >= seqStart && targetPosition <= seqEndPos) {\n positionScore += 10;\n if (targetPosition >= seqWords[0].start && targetPosition <= seqWords[0].end) {\n positionScore += 5;\n }\n }\n\n return positionScore;\n }\n\n findSingleWordMatch(words, aiWord, targetPosition) {\n const aiWordLower = aiWord.toLowerCase();\n const bestSimilarityMatch = this.findBestSimilarityMatch(words, aiWordLower);\n\n if (bestSimilarityMatch.score > 0.5) {\n return {wordStart: bestSimilarityMatch.word.start, wordEnd: bestSimilarityMatch.word.end};\n }\n\n const bestPositionMatch = this.findBestPositionMatch(words, aiWordLower, targetPosition);\n\n if (bestPositionMatch.word) {\n return {wordStart: bestPositionMatch.word.start, wordEnd: bestPositionMatch.word.end};\n }\n\n // Fallback to position-based word boundary\n return this.findWordBoundaryAtPosition(words[0].start, words[words.length - 1].end,\n targetPosition, this.text);\n }\n\n findBestSimilarityMatch(words, aiWordLower) {\n let bestMatch = {word: null, score: 0};\n\n for (const word of words) {\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n\n // Penalize short prefix matches\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n\n if (similarity > bestMatch.score) {\n bestMatch = {word, score: similarity};\n }\n }\n\n return bestMatch;\n }\n\n findBestPositionMatch(words, aiWordLower, targetPosition) {\n let bestMatch = {word: null, score: -1};\n\n for (const word of words) {\n let score = this.calculateWordScore(word, aiWordLower, targetPosition);\n\n if (score > bestMatch.score) {\n bestMatch = {word, score};\n }\n }\n\n return bestMatch;\n }\n\n calculateWordScore(word, aiWordLower, targetPosition) {\n let score = 0;\n\n // Position score\n if (targetPosition >= word.start && targetPosition <= word.end) {\n score += 30;\n } else {\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n score += Math.max(0, 20 - distance);\n }\n\n // Similarity score with penalty\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n score += similarity * 10;\n\n return score;\n }\n\n findWordBoundaryAtPosition(lineStart, lineEnd, targetPosition, text) {\n let wordStart = targetPosition;\n while (wordStart > lineStart && text[wordStart - 1] !== ' ' && text[wordStart - 1] !== '\\n') {\n wordStart--;\n }\n let wordEnd = targetPosition;\n while (wordEnd < lineEnd && text[wordEnd] !== ' ' && text[wordEnd] !== '\\n') {\n wordEnd++;\n }\n return {wordStart, wordEnd};\n }\n\n markCharsAsDeleted(wordToReplace, wordStart, deletions) {\n if (wordToReplace.length > 0) {\n for (let i = 0; i < wordToReplace.length; i++) {\n deletions.push({\n index: wordStart + i,\n chars: wordToReplace[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n\n calculateNewCursorPosition(currentCursor, targetPosition, wordStart, wordEnd, insertText, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return wordStart + insertText.length;\n }\n\n if (targetPosition >= wordStart && targetPosition <= wordEnd) {\n return wordStart + insertText.length;\n }\n\n const positionDiff = insertText.length - (wordEnd - wordStart);\n\n if (currentCursor >= wordEnd) {\n return currentCursor + positionDiff;\n } else if (currentCursor > wordStart && currentCursor < wordEnd) {\n return wordStart + insertText.length;\n }\n\n return currentCursor;\n }\n\n updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText) {\n // Update pasted character indices\n this.updatePastedCharIndices(wordStart, wordEnd, positionDiff);\n\n // Mark characters as AI-inserted\n this.markCharsAsAiInserted(wordStart, insertText);\n\n // Update AI character indices\n this.updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText);\n }\n\n updatePastedCharIndices(wordStart, wordEnd, positionDiff) {\n if (this.pastedChars) {\n this.pastedChars = this.pastedChars.map(p => {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n return p;\n }).filter(p => p !== null);\n }\n }\n\n markCharsAsAiInserted(wordStart, insertText) {\n if (!this.aiChars) {\n this.aiChars = [];\n }\n\n if (insertText.trim() !== '') {\n for (let i = 0; i < insertText.length; i++) {\n this.aiChars.push({\n index: wordStart + i,\n chars: insertText[i]\n });\n }\n }\n }\n\n updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText) {\n const justAddedIndices = new Set();\n for (let i = 0; i < insertText.length; i++) {\n justAddedIndices.add(wordStart + i);\n }\n\n this.aiChars = this.aiChars.map(p => {\n if (!justAddedIndices.has(p.index)) {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n }\n return p;\n }).filter(p => p !== null);\n }\n\n // Calculate similarity between two strings\n calculateSimilarity(str1, str2) {\n if (str1 === str2) {\n return 1;\n }\n if (str1.length === 0 || str2.length === 0) {\n return 0;\n }\n\n // Check if one string is a prefix of the other\n if (str1.startsWith(str2) || str2.startsWith(str1)) {\n return 0.8;\n }\n\n // Levenshtein distance\n const len1 = str1.length;\n const len2 = str2.length;\n const matrix = Array(len2 + 1).fill(null).map(() => Array(len1 + 1).fill(0));\n\n for (let i = 0; i <= len1; i++) {\n matrix[0][i] = i;\n }\n for (let j = 0; j <= len2; j++) {\n matrix[j][0] = j;\n }\n\n for (let j = 1; j <= len2; j++) {\n for (let i = 1; i <= len1; i++) {\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\n matrix[j][i] = Math.min(\n matrix[j][i - 1] + 1,\n matrix[j - 1][i] + 1,\n matrix[j - 1][i - 1] + cost\n );\n }\n }\n\n const maxLen = Math.max(len1, len2);\n return 1 - (matrix[len2][len1] / maxLen);\n }\n\n // Find the word closest to a target position\n findClosestWord(words, targetPosition) {\n if (words.length === 0) {\n return {start: targetPosition, end: targetPosition};\n }\n\n let closest = words[0];\n let minDistance = Math.min(\n Math.abs(targetPosition - words[0].start),\n Math.abs(targetPosition - words[0].end)\n );\n\n for (const word of words) {\n if (targetPosition >= word.start && targetPosition <= word.end) {\n return word;\n }\n\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n\n if (distance < minDistance) {\n minDistance = distance;\n closest = word;\n }\n }\n\n return closest;\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\n // Handle copy operation (Ctrl+C)\n if (this.isCopyOperation(key)) {\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Handle undo operation (Ctrl+Z)\n if (this.isUndoOperation(key)) {\n return this.handleUndoOperation(event, text, cursor, highlights, deletions);\n }\n\n // Detect selection for current event\n const currentEventIndex = this.currentEventIndex;\n const selection = this.detectSelection(currentEventIndex);\n\n // Handle paste operation (Ctrl+V)\n if (this.isPasteOperation(key, event)) {\n return this.handlePasteOperation(event, selection, text, cursor, highlights, deletions);\n }\n\n // Update modifier key states\n this.updateModifierStates(key);\n\n // Handle selection deletion with Backspace/Delete\n if (this.isSelectionDeletion(key, selection)) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Process various key operations\n return this.processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection);\n }\n\n isCopyOperation(key) {\n return (key === 'c' || key === 'C') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n isUndoOperation(key) {\n return (key === 'z' || key === 'Z') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n handleUndoOperation(event, text, cursor, highlights, deletions) {\n const nextEventIndex = this.currentEventIndex + 1;\n if (nextEventIndex < this.logData.length) {\n const nextEvent = this.logData[nextEventIndex];\n\n if (nextEvent.event === 'keyUp' && (nextEvent.key === 'z' || nextEvent.key === 'Z')) {\n const newPosition = nextEvent.rePosition;\n if (newPosition < cursor && text.length > 0) {\n const textBeforeUndo = text;\n text = text.substring(0, newPosition) + text.substring(cursor);\n cursor = newPosition;\n\n // Mark as deleted for visual effect\n for (let i = 0; i < textBeforeUndo.length && i < cursor; i++) {\n deletions.push({\n index: newPosition,\n chars: textBeforeUndo[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n }\n\n this.isControlKeyPressed = false;\n this.isMetaKeyPressed = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n isPasteOperation(key, event) {\n if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n return (event.pastedContent && event.pastedContent.trim() !== '') ||\n (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length);\n }\n return false;\n }\n\n handlePasteOperation(event, selection, text, cursor, highlights, deletions) {\n const pastedContent = event.pastedContent || this.pastedEvents[this.currentPasteIndex];\n\n if (selection) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\n this.currentPasteIndex++;\n this.resetModifierStates();\n this.isPasteEvent = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n resetModifierStates() {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = false;\n }\n\n isSelectionDeletion(key, selection) {\n return (key === 'Backspace' || key === 'Delete') && selection && selection.length > 1;\n }\n\n processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection) {\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 if (selection && selection.length > 0) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\n }\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n detectSelection(eventIndex) {\n const currentEvent = this.logData[eventIndex];\n\n if (currentEvent.event?.toLowerCase() === 'keydown' &&\n (currentEvent.key === 'Backspace' || currentEvent.key === 'Delete')) {\n\n const currentPos = currentEvent.rePosition;\n return this.processDetection(currentPos, currentEvent, eventIndex);\n }\n return null;\n }\n\n processDetection(currentPos, currentEvent, eventIndex) {\n for (let i = eventIndex + 1; i < this.logData.length; i++) {\n const nextEvent = this.logData[i];\n\n if (nextEvent.event?.toLowerCase() === 'keyup' &&\n nextEvent.key === currentEvent.key) {\n\n const nextPos = nextEvent.rePosition;\n\n // Calculate the difference in positions\n const positionDiff = Math.abs(currentPos - nextPos);\n\n if (positionDiff > 1) {\n return {\n start: Math.min(currentPos, nextPos),\n end: Math.max(currentPos, nextPos),\n length: positionDiff\n };\n } else if (positionDiff === 1) {\n if (currentEvent.key === 'Backspace') {\n return {\n start: nextPos,\n end: currentPos,\n length: 1\n };\n } else {\n return {\n start: currentPos,\n end: nextPos,\n length: 1\n };\n }\n }\n break;\n }\n }\n return null;\n }\n\n handleSelectionDeletion(selection, text, cursor, deletions) {\n const {start, end, length} = selection;\n\n // Add each character in the selection to the deletions array\n for (let i = start; i < end && i < text.length; i++) {\n deletions.push({\n index: start,\n chars: text[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n\n text = text.substring(0, start) + text.substring(end);\n\n this.shiftPastedCharsIndices(start, length);\n\n cursor = start;\n\n return {text, cursor};\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 if (this.aiChars) {\n this.aiChars = this.aiChars.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 return null;\n }\n return p;\n }).filter(p => p !== null);\n }\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 === 'Meta') {\n this.isMetaKeyPressed = true;\n } else if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n this.isPasteEvent = true;\n } else if (!['Control', 'Meta', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = 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 (this.aiChars) {\n this.aiChars = this.aiChars.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.isMetaKeyPressed = false;\n this.isPasteEvent = false;\n this.pastedChars = [];\n this.currentPasteIndex = 0;\n this.currentAiIndex = 0;\n this.aiChars = [];\n let text = '';\n let cursor = 0;\n let highlights = [];\n let deletions = [];\n let pasteIndex = 0;\n let aiIndex = 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 && (this.currentEventIndex === 0 ||\n 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 || this.isMetaKeyPressed)) {\n pasteIndex++;\n }\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\n } else if (event.event === 'aiInsert') {\n this.currentAiIndex = aiIndex;\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processAiInsertEvent(event, text, cursor, highlights, deletions));\n aiIndex++;\n }\n this.currentEventIndex = i + 1;\n }\n\n this.currentPasteIndex = pasteIndex;\n this.currentAiIndex = aiIndex;\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 // eslint-disable-next-line complexity\n updateDisplayText(text, cursorPosition, highlights, deletions) {\n let html = '';\n const highlightMap = {};\n const deletionMap = {};\n const pastedMap = {};\n const aiMap = {};\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 // Process AI characters for styling\n if (this.aiChars) {\n this.aiChars.forEach(p => {\n if (p.index < text.length) {\n aiMap[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 isAi = aiMap[currentPosition];\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\n\n if (isPasted && isHighlighted) {\n html += `${char}`;\n } else if (isAi && isHighlighted) {\n html += `${char}`;\n } else if (isPasted) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isAi) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isHighlighted) {\n html += `${char}`;\n } else {\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","isMetaKeyPressed","text","pastedEvents","currentPasteIndex","pastedChars","aiEvents","currentAiIndex","aiChars","undoTimestamps","undoChars","element","document","getElementById","Error","outputElement","loadJSON","then","data","status","processData","this","logData","length","identifyPasteEvents","identifyUndoEvents","constructController","startReplay","handleNoSubmission","catch","error","window","console","message","localStorage","getItem","Str","get_string","str","setItem","log","JSON","parse","comments","Array","isArray","payload","i","event","pastedContent","trim","push","aiContent","unixTimestamp","startTime","map","normalizedTime","html","Promise","all","templates","render","newElement","stopReplay","clearTimeout","replayTimeout","playButton","playSvg","createElement","src","M","util","image_url","querySelector","innerHTML","outerHTML","currentPosition","replayIntervalId","clearInterval","container","controlContainer","buildControllerUI","remove","topRow","classList","add","createPlayButton","appendChild","scrubberContainer","createScrubberContainer","timeDisplay","createTimeDisplay","bottomRow","speedContainer","createSpeedControls","pasteEventsToggle","createPasteEventsToggle","pasteEventsPanel","addEventListener","find","removeClass","addClass","scrubberElement","type","max","min","value","skipToTime","parseInt","textContent","speedLabel","speedGroup","forEach","speedBtn","dataset","querySelectorAll","btn","pasteEventsIcon","pasteIcon","pasteEventsText","pasteEventCount","className","style","marginLeft","chevronIcon","chevron","transition","createPasteEventsPanel","isHidden","display","transform","existingPanel","populatePasteEventsPanel","controlPressed","metaPressed","shiftPressed","pasteCount","toLowerCase","key","timestamp","index","time","formattedTime","formatTime","pastedText","undoCount","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","processAiInsertEvent","filter","h","expiresAt","d","updateDisplayText","percentComplete","baseIncrement","incrementTime","setTimeout","getLineAndColumn","pos","before","substring","lineIndex","split","col","lastIndexOf","highlights","deletions","targetPosition","handleAiReplacement","currentCursor","insertText","aiWords","isMultiWord","isNewLineInsertion","startsWith","endsWith","wordStart","wordEnd","findWordToReplace","wordToReplace","markCharsAsDeleted","replacedLength","positionDiff","newCursor","calculateNewCursorPosition","updateCharacterIndices","lineStart","lineEnd","findLineRange","lineText","words","extractWordsFromLine","findMultiWordMatch","findSingleWordMatch","start","end","bestMatch","score","wordCount","similarityScore","matchResult","evaluateMultiWordSequence","totalScore","closest","findClosestWord","startIndex","seqWords","j","calculateSequenceSimilarity","calculatePositionScore","compareLength","k","ai","seq","calculateSimilarity","positionScore","seqStart","seqEndPos","aiWord","aiWordLower","bestSimilarityMatch","findBestSimilarityMatch","word","bestPositionMatch","findBestPositionMatch","findWordBoundaryAtPosition","similarity","wordLower","calculateWordScore","distance","abs","chars","updatePastedCharIndices","markCharsAsAiInserted","updateAiCharIndices","p","justAddedIndices","Set","has","str1","str2","len1","len2","matrix","fill","cost","maxLen","minDistance","charToInsert","applyKey","isCopyOperation","isUndoOperation","handleUndoOperation","selection","detectSelection","isPasteOperation","handlePasteOperation","updateModifierStates","isSelectionDeletion","handleSelectionDeletion","processKeyOperation","nextEventIndex","nextEvent","newPosition","textBeforeUndo","handlePasteInsert","resetModifierStates","isCtrlBackspace","handleCtrlBackspace","isCtrlDelete","handleCtrlDelete","isCtrlArrowMove","handleCtrlArrowMove","isRegularBackspace","handleBackspace","isRegularDelete","handleDelete","isArrowUp","handleArrowUp","isArrowDown","handleArrowDown","isRegularArrowMove","handleArrowMove","handleCharacterInsert","eventIndex","currentEvent","currentPos","processDetection","nextPos","shiftPastedCharsIndices","numDeleted","includes","findPreviousWordBoundary","findNextWordBoundary","wordToDelete","lines","prevLine","slice","join","nextLine","lastNonSpace","skipToEnd","textOutput","wasPlaying","targetTime","pasteIndex","aiIndex","highlightMap","deletionMap","pastedMap","aiMap","outOfRangeDeletions","textLines","line","char","isPasted","isAi","isHighlighted","escapeHtml","sort","a","b","cursorHTML","cursorPos","deletedWordHTML","wasScrolledToBottom","scrollHeight","clientHeight","scrollTop","isCursorBelowViewport","cursorElement","cursorRect","getBoundingClientRect","outputRect","bottom","unsafe","replace"],"mappings":"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"} \ 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 8b434228..b072c128 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,SAASmB,gBACFA,QAAQ,GAAGC,IACZC,OAEbA,MAAO,SAASF,eACNG,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,SAAS1D,cACRD,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,SAASmB,gBACFA,QAAQ,GAAGC,IACZC,OAEbA,MAAO,SAASF,eACNG,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,SAAS1D,cACRD,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/svg_repo.min.js b/amd/build/svg_repo.min.js index ef22ae26..963b7faa 100644 --- a/amd/build/svg_repo.min.js +++ b/amd/build/svg_repo.min.js @@ -1,11 +1,3 @@ -define("tiny_cursive/svg_repo",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0; -/** - * SVG repository for icons used in the Curs - * - * @module tiny_cursive/svg_repo - * @copyright 2025 Cursive Technology, Inc. - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -var _default={people:'\n \n \n \n \n ',assignment:'\n \n \n \n ',time:'\n \n \n ',offline:'',forum:'',close:'\n \n ',hamburger:'\n \n ',quiz:'',cloudSave:'',lesson:''};return _exports.default=_default,_exports.default})); +define("tiny_cursive/svg_repo",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={people:'\n \n \n \n \n ',assignment:'\n \n \n \n ',time:'\n \n \n ',offline:'',forum:'',close:'\n \n ',hamburger:'\n \n ',quiz:'',cloudSave:'',lesson:''},_exports.default})); //# sourceMappingURL=svg_repo.min.js.map \ No newline at end of file diff --git a/amd/build/svg_repo.min.js.map b/amd/build/svg_repo.min.js.map index 5b3ccebe..4fbf06a2 100644 --- a/amd/build/svg_repo.min.js.map +++ b/amd/build/svg_repo.min.js.map @@ -1 +1 @@ -{"version":3,"file":"svg_repo.min.js","sources":["../src/svg_repo.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 * SVG repository for icons used in the Curs\n *\n * @module tiny_cursive/svg_repo\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n people: `\n \n \n \n \n `,\n assignment: `\n \n \n \n `,\n time: `\n \n \n `,\n offline: ``,\n forum: ``,\n close: `\n \n `,\n hamburger: `\n \n `,\n quiz: ``,\n cloudSave: ``,\n lesson: ``\n};\n"],"names":["people","assignment","time","offline","forum","close","hamburger","quiz","cloudSave","lesson"],"mappings":";;;;;;;;aAuBe,CACbA,OAAS,ijBASTC,WAAa,siBAQbC,KAAO,qaAOPC,QAAU,qgBAMVC,MAAQ,mtDAeNC,MAAQ,85BAQRC,UAAY,6jBAKZC,KAAO,ksFAuBPC,UAAY,qzBAOZC,OAAS"} \ No newline at end of file +{"version":3,"file":"svg_repo.min.js","sources":["../src/svg_repo.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 * SVG repository for icons used in the Curs\n *\n * @module tiny_cursive/svg_repo\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n people: `\n \n \n \n \n `,\n assignment: `\n \n \n \n `,\n time: `\n \n \n `,\n offline: ``,\n forum: ``,\n close: `\n \n `,\n hamburger: `\n \n `,\n quiz: ``,\n cloudSave: ``,\n lesson: ``\n};\n"],"names":["people","assignment","time","offline","forum","close","hamburger","quiz","cloudSave","lesson"],"mappings":"uKAuBe,CACbA,wjBASAC,ijBAQAC,0aAOAC,6gBAMAC,ytDAeEC,o6BAQAC,ukBAKAC,usFAuBAC,+zBAOAC"} \ 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 96a5ed03..20e600ba 100644 --- a/amd/build/token_approve.min.js +++ b/amd/build/token_approve.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/token_approve",["jquery","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(){$("#approve_token").click((function(){var token=$("#id_s_tiny_cursive_secretkey").val();AJAX.call([{methodname:"cursive_approve_token",args:{token:token}}])[0].done((function(json){var data=JSON.parse(json),messageAlert="";messageAlert=1==data.status?""+data.message+"":""+data.message+"",$("#token_message").html(messageAlert)}))}))},generateToken(){const generateToken=$("#generate_cursivetoken"),cursiveDisable=$("#cursivedisable"),cursiveEnable=$("#cursiveenable");generateToken.on("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;return data.token?($("#id_s_tiny_cursive_cursivetoken").val(data.token),messageAlert=`${success}`):messageAlert=`${fail}`,$("#cursivetoken_").html(messageAlert),setTimeout((()=>{$("#cursivetoken_").empty()}),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+"",$("#cursivetoken_").html(errorMessage),setTimeout((function(){$("#cursivetoken_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveDisable.on("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;return messageAlert=data?`${success}`:`${fail}`,$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveEnable.on("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;return messageAlert=data?`${success}`:`${fail}`,$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))}))}};return usersTable})); +define("tiny_cursive/token_approve",["jquery","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(){$("#approve_token").click((function(){var token=$("#id_s_tiny_cursive_secretkey").val();AJAX.call([{methodname:"cursive_approve_token",args:{token:token}}])[0].done((function(json){var data=JSON.parse(json),messageAlert="";messageAlert=1==data.status?""+data.message+"":""+data.message+"",$("#token_message").html(messageAlert)}))}))},generateToken(){const generateToken=$("#generate_cursivetoken"),cursiveDisable=$("#cursivedisable"),cursiveEnable=$("#cursiveenable");generateToken.on("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;return data.token?($("#id_s_tiny_cursive_cursivetoken").val(data.token),messageAlert="".concat(success,"")):messageAlert="".concat(fail,""),$("#cursivetoken_").html(messageAlert),setTimeout((()=>{$("#cursivetoken_").empty()}),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+"",$("#cursivetoken_").html(errorMessage),setTimeout((function(){$("#cursivetoken_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveDisable.on("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;return messageAlert=data?"".concat(success,""):"".concat(fail,""),$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveEnable.on("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;return messageAlert=data?"".concat(success,""):"".concat(fail,""),$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),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 44d98c9a..2236c250 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 kuldeep singh \n */\n\ndefine([\"jquery\", \"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 $(\"#approve_token\").click(function() {\n var token = $(\"#id_s_tiny_cursive_secretkey\").val();\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 == true) {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n } else {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n }\n $(\"#token_message\").html(messageAlert);\n });\n });\n },\n\n generateToken() {\n const generateToken = $(\"#generate_cursivetoken\");\n const cursiveDisable = $(\"#cursivedisable\");\n const cursiveEnable = $(\"#cursiveenable\");\n\n generateToken.on(\"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 $(\"#id_s_tiny_cursive_cursivetoken\").val(data.token);\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n $(\"#cursivetoken_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivetoken_\").empty();\n }, 3000);\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 $(\"#cursivetoken_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivetoken_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n\n cursiveDisable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n cursiveEnable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","usersTable","init","page","get_strings","key","component","done","getToken","generateToken","click","token","val","call","methodname","args","json","data","JSON","parse","messageAlert","status","message","html","cursiveDisable","cursiveEnable","on","e","preventDefault","promise1","then","success","fail","setTimeout","empty","catch","error","window","console","textStatus","errorMessage","get_string","disable"],"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 kuldeep singh \n */\n\ndefine([\"jquery\", \"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 $(\"#approve_token\").click(function() {\n var token = $(\"#id_s_tiny_cursive_secretkey\").val();\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 == true) {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n } else {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n }\n $(\"#token_message\").html(messageAlert);\n });\n });\n },\n\n generateToken() {\n const generateToken = $(\"#generate_cursivetoken\");\n const cursiveDisable = $(\"#cursivedisable\");\n const cursiveEnable = $(\"#cursiveenable\");\n\n generateToken.on(\"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 $(\"#id_s_tiny_cursive_cursivetoken\").val(data.token);\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n $(\"#cursivetoken_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivetoken_\").empty();\n }, 3000);\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 $(\"#cursivetoken_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivetoken_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n\n cursiveDisable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n cursiveEnable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","usersTable","init","page","get_strings","key","component","done","getToken","generateToken","click","token","val","call","methodname","args","json","data","JSON","parse","messageAlert","status","message","html","cursiveDisable","cursiveEnable","on","e","preventDefault","promise1","then","success","fail","setTimeout","empty","catch","error","window","console","textStatus","errorMessage","get_string","disable"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/append_fourm_post.js b/amd/src/append_fourm_post.js index 09ca2a82..36936fea 100644 --- a/amd/src/append_fourm_post.js +++ b/amd/src/append_fourm_post.js @@ -68,9 +68,27 @@ define(["jquery", "core/ajax", "core/str", "core/templates", "./replay", "./anal var replyButton = $('a[data-region="post-action"][title="Reply"]'); if (replyButton.length > 0) { replyButton.on('click', function(event) { + var isTeacher = $('#body').hasClass('teacher_admin'); + if (isTeacher) { + return true; + } event.preventDefault(); var url = $(this).attr('href'); - window.location.href = url; + + var urlParts = url.split('#'); + var baseUrl = urlParts[0]; + var hash = urlParts.length > 1 ? '#' + urlParts[1] : ''; + + if (baseUrl.indexOf('setformat=') > -1) { + baseUrl = baseUrl.replace(/setformat=\d/, 'setformat=1'); + } else if (baseUrl.indexOf('?') > -1) { + baseUrl += '&setformat=1'; + } else { + baseUrl += '?setformat=1'; + } + var finalUrl = baseUrl + hash; + + window.location.href = finalUrl; }); } From 696d5ff6a2c0438ff87b0b0ef99e12c00f63e79c Mon Sep 17 00:00:00 2001 From: Shahriar075 Date: Thu, 5 Feb 2026 13:29:28 +0600 Subject: [PATCH 2/5] Add indexing to all 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/append_lesson_grade_table.min.js | 2 +- .../append_lesson_grade_table.min.js.map | 2 +- amd/build/append_pdfannotator.min.js | 2 +- amd/build/append_pdfannotator.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/cursive_autosave.min.js | 9 +- amd/build/cursive_autosave.min.js.map | 2 +- amd/build/document_view.min.js | 2 +- amd/build/document_view.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/svg_repo.min.js | 10 +- amd/build/svg_repo.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 | 22 +++++ db/upgrade.php | 94 ++++++++++++++++++- version.php | 2 +- 29 files changed, 142 insertions(+), 50 deletions(-) diff --git a/amd/build/analytic_events.min.js b/amd/build/analytic_events.min.js index 7a33c868..a771d356 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=_interopRequireDefault(_analytic_modal),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates);return _exports.default=class{constructor(){(0,_str.get_string)("notenoughtinfo","tiny_cursive").then((str=>(localStorage.setItem("notenoughtinfo",str),str))).catch((error=>window.console.log(error)))}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 self=this;(0,_jquery.default)("#analytics"+userid+questionid).on("click",(function(e){e.preventDefault();const isReplayButton=(0,_jquery.default)(this).find(".tiny_cursive-replay-button").length>0;_analytic_modal.default.create({templateContext:context}).then((modal=>{(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),modal.show(),isReplayButton&&setTimeout((()=>{(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active");const replayTab=(0,_jquery.default)("#rep"+userid+questionid);replayTab.length&&(replayTab.trigger("click"),replayTab.addClass("active"))}),50);let moreBtn=(0,_jquery.default)("body #more"+userid+questionid);return moreBtn.length>0&&((0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)("#analytic"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#diff"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#analytic"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),(0,_jquery.default)("#diff"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),moreBtn.on("click",(function(e){e.preventDefault(),self.learnMore((0,_jquery.default)(this),context,userid,questionid,replayInstances)}))),!0})).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;(0,_jquery.default)("body").on("click","#analytic"+userid+questionid,(function(e){(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","analytics"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","analytics"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),templates.render("tiny_cursive/analytics_table",context).then((function(html){return(0,_jquery.default)("#content"+userid).html(html),(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),!0})).fail((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))),(0,_jquery.default)("body").on("click","#diff"+userid+questionid,(function(e){if((0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","diff"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","diff"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),!fileid)throw(0,_jquery.default)("#content"+userid).html(nodata),new Error("Missing file id or Difference Content not received yet");(0,_ajax.call)([{methodname:"cursive_get_writing_differences",args:{fileid:fileid}}])[0].done((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"}]).done((strings=>{const originalTextString=strings[0],editsPastesAIString=strings[1],commentBox=(0,_jquery.default)('
');var pasteCountDiv=(0,_jquery.default)("
");(0,_str.get_string)("pastecount","tiny_cursive").then((str=>(pasteCountDiv.append("
"+str+" : "+responsedata.commentscount+"
"),!0))).catch((error=>window.console.log(error)));var commentsDiv=(0,_jquery.default)('
');(0,_str.get_string)("comments","tiny_cursive").then((str=>(commentsDiv.append(""+str+""),!0))).catch((error=>window.console.error(error)));var commentsList=(0,_jquery.default)("
");let comments=responsedata.comments;for(let index in comments){var commentDiv=(0,_jquery.default)('
').text(comments[index].usercomment);commentsList.append(commentDiv)}commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);const $legend=(0,_jquery.default)('
'),$attributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$attributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box attributed"}),$attributedText=(0,_jquery.default)("").text(originalTextString);$attributedItem.append($attributedBox).append($attributedText);const $unattributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$unattributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box tiny_cursive_added"}),$unattributedText=(0,_jquery.default)("").text(editsPastesAIString);$unattributedItem.append($unattributedBox).append($unattributedText),$legend.append($attributedItem).append($unattributedItem);let contents=(0,_jquery.default)("
").addClass("tiny_cursive-comparison-content"),textBlock2=(0,_jquery.default)("
").addClass("tiny_cursive-text-block").append((0,_jquery.default)("
").attr("id","tiny_cursive-reconstructed_text").html(JSON.parse(submittedText)));contents.append(commentBox,$legend,textBlock2),(0,_jquery.default)("#content"+userid).html(contents)})).fail((error=>{window.console.error("Failed to load language strings:",error),(0,_jquery.default)("#content"+userid).html(nodata)}))}else(0,_jquery.default)("#content"+userid).html(nodata)})).fail((error=>{throw(0,_jquery.default)("#content"+userid).html(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;(0,_jquery.default)("body").on("click","#rep"+userid+questionid,(function(e){filepath&&((0,_jquery.default)("#replayControls_"+userid+questionid).removeClass("d-none"),(0,_jquery.default)("#content"+userid).addClass("tiny_cursive_outputElement")),(0,_jquery.default)(this).prop("disabled",!0),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","replay"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"block","padding-right":"8px"}),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),questionid?video_playback(userid,filepath,questionid):video_playback(userid,filepath)}))}learnMore(moreBtn,context,userid,questionid,replayInstances){(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),moreBtn.addClass("active"),(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)("#content"+userid+questionid).removeClass("tiny_cursive_outputElement"),(0,_jquery.default)("#replayControls_"+userid+questionid).addClass("d-none"),_templates.default.render("tiny_cursive/learn_more",context).then((function(html){return(0,_jquery.default)("#content"+userid+questionid).html(html),!0})).fail((function(error){window.console.error("Failed to render template:",error)}))}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";return 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"),score").addClass(icon).attr("style",color).attr("title",localStorage.getItem("notenoughtinfo"))):(0,_jquery.default)("").addClass(icon).attr("style",color)}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_analytic_modal=_interopRequireDefault(_analytic_modal),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates);return _exports.default=class{constructor(){(0,_str.get_string)("notenoughtinfo","tiny_cursive").then((str=>(localStorage.setItem("notenoughtinfo",str),str))).catch((error=>window.console.log(error)))}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 self=this;(0,_jquery.default)("#analytics"+userid+questionid).on("click",(function(e){e.preventDefault();const isReplayButton=(0,_jquery.default)(this).find(".tiny_cursive-replay-button").length>0;_analytic_modal.default.create({templateContext:context}).then((modal=>{(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),modal.show(),isReplayButton&&setTimeout((()=>{(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active");const replayTab=(0,_jquery.default)("#rep"+userid+questionid);replayTab.length&&(replayTab.trigger("click"),replayTab.addClass("active"))}),50);let moreBtn=(0,_jquery.default)("body #more"+userid+questionid);return moreBtn.length>0&&((0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)("#analytic"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#diff"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#analytic"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),(0,_jquery.default)("#diff"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),moreBtn.on("click",(function(e){e.preventDefault(),self.learnMore((0,_jquery.default)(this),context,userid,questionid,replayInstances)}))),!0})).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;(0,_jquery.default)("body").on("click","#analytic"+userid+questionid,(function(e){(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","analytics"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","analytics"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),templates.render("tiny_cursive/analytics_table",context).then((function(html){return(0,_jquery.default)("#content"+userid).html(html),(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),!0})).fail((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))),(0,_jquery.default)("body").on("click","#diff"+userid+questionid,(function(e){if((0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","diff"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","diff"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),!fileid)throw(0,_jquery.default)("#content"+userid).html(nodata),new Error("Missing file id or Difference Content not received yet");(0,_ajax.call)([{methodname:"cursive_get_writing_differences",args:{fileid:fileid}}])[0].done((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"}]).done((strings=>{const originalTextString=strings[0],editsPastesAIString=strings[1],commentBox=(0,_jquery.default)('
');var pasteCountDiv=(0,_jquery.default)("
");(0,_str.get_string)("pastecount","tiny_cursive").then((str=>(pasteCountDiv.append("
"+str+" : "+responsedata.commentscount+"
"),!0))).catch((error=>window.console.log(error)));var commentsDiv=(0,_jquery.default)('
');(0,_str.get_string)("comments","tiny_cursive").then((str=>(commentsDiv.append(""+str+""),!0))).catch((error=>window.console.error(error)));var commentsList=(0,_jquery.default)("
");let comments=responsedata.comments;for(let index in comments){var commentDiv=(0,_jquery.default)('
').text(comments[index].usercomment);commentsList.append(commentDiv)}commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);const $legend=(0,_jquery.default)('
'),$attributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$attributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box attributed"}),$attributedText=(0,_jquery.default)("").text(originalTextString);$attributedItem.append($attributedBox).append($attributedText);const $unattributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$unattributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box tiny_cursive_added"}),$unattributedText=(0,_jquery.default)("").text(editsPastesAIString);$unattributedItem.append($unattributedBox).append($unattributedText),$legend.append($attributedItem).append($unattributedItem);let contents=(0,_jquery.default)("
").addClass("tiny_cursive-comparison-content"),textBlock2=(0,_jquery.default)("
").addClass("tiny_cursive-text-block").append((0,_jquery.default)("
").attr("id","tiny_cursive-reconstructed_text").html(JSON.parse(submittedText)));contents.append(commentBox,$legend,textBlock2),(0,_jquery.default)("#content"+userid).html(contents)})).fail((error=>{window.console.error("Failed to load language strings:",error),(0,_jquery.default)("#content"+userid).html(nodata)}))}else(0,_jquery.default)("#content"+userid).html(nodata)})).fail((error=>{throw(0,_jquery.default)("#content"+userid).html(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;(0,_jquery.default)("body").on("click","#rep"+userid+questionid,(function(e){filepath&&((0,_jquery.default)("#replayControls_"+userid+questionid).removeClass("d-none"),(0,_jquery.default)("#content"+userid).addClass("tiny_cursive_outputElement")),(0,_jquery.default)(this).prop("disabled",!0),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","replay"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"block","padding-right":"8px"}),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),questionid?video_playback(userid,filepath,questionid):video_playback(userid,filepath)}))}learnMore(moreBtn,context,userid,questionid,replayInstances){(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),moreBtn.addClass("active"),(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)("#content"+userid+questionid).removeClass("tiny_cursive_outputElement"),(0,_jquery.default)("#replayControls_"+userid+questionid).addClass("d-none"),_templates.default.render("tiny_cursive/learn_more",context).then((function(html){return(0,_jquery.default)("#content"+userid+questionid).html(html),!0})).fail((function(error){window.console.error("Failed to render template:",error)}))}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";return 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"),score").addClass(icon).attr("style",color).attr("title",localStorage.getItem("notenoughtinfo"))):(0,_jquery.default)("").addClass(icon).attr("style",color)}},_exports.default})); //# sourceMappingURL=analytic_events.min.js.map \ No newline at end of file diff --git a/amd/build/analytic_events.min.js.map b/amd/build/analytic_events.min.js.map index 321173da..536eeb19 100644 --- a/amd/build/analytic_events.min.js.map +++ b/amd/build/analytic_events.min.js.map @@ -1 +1 @@ -{"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 and showing quality metrics.\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 $ from 'jquery';\nimport {get_string as getString} from 'core/str';\nimport {get_strings as getStrings} from 'core/str';\nimport template from 'core/templates';\n\nexport default class AnalyticEvents {\n\n constructor() {\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\n localStorage.setItem('notenoughtinfo', str);\n return str;\n }).catch(error => window.console.log(error));\n }\n\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\n const self = this;\n $('#analytics' + userid + questionid).on('click', function(e) {\n e.preventDefault();\n\n const isReplayButton = $(this).find('.tiny_cursive-replay-button').length > 0;\n // Create Moodle modal\n myModal.create({templateContext: context}).then(modal => {\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n modal.show();\n\n if (isReplayButton) {\n setTimeout(() => {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n\n const replayTab = $('#rep' + userid + questionid);\n if (replayTab.length) {\n replayTab.trigger('click');\n replayTab.addClass('active');\n }\n }, 50);\n }\n\n let moreBtn = $('body #more' + userid + questionid);\n if (moreBtn.length > 0) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('#analytic' + userid + questionid).prop('disabled', true);\n $('#diff' + userid + questionid).prop('disabled', true);\n $('#analytic' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n $('#diff' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n moreBtn.on('click', function(e) {\n e.preventDefault();\n self.learnMore($(this), context, userid, questionid, replayInstances);\n });\n }\n\n return true;\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\n $('body').on('click', '#analytic' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'analytics');\n $('#player_' + userid + questionid).css({'display': 'none'});\n $('#content' + userid).removeClass('tiny_cursive_outputElement')\n .addClass('tiny_cursive').attr('data-label', 'analytics');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n\n templates.render('tiny_cursive/analytics_table', context).then(function(html) {\n $('#content' + userid).html(html);\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 getString('nopaylod', 'tiny_cursive').then(str => {\n nodata.textContent = str;\n return true;\n }).catch(error => window.console.log(error));\n $('body').on('click', '#diff' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'diff');\n $('#player_' + userid + questionid).css({\n 'display': 'none'\n });\n $('#content' + userid).removeClass('tiny_cursive_outputElement').addClass('tiny_cursive').attr('data-label', 'diff');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active');\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n if (!fileid) {\n $('#content' + userid).html(nodata);\n throw new Error('Missing file id or Difference Content not received yet');\n }\n getContent([{\n methodname: 'cursive_get_writing_differences',\n args: {fileid: fileid},\n }])[0].done(response => {\n let responsedata = JSON.parse(response.data);\n if (responsedata) {\n let submittedText = atob(responsedata.submitted_text);\n\n // Fetch the dynamic strings.\n getStrings([\n {key: 'original_text', component: 'tiny_cursive'},\n {key: 'editspastesai', component: 'tiny_cursive'}\n ]).done(strings => {\n const originalTextString = strings[0];\n const editsPastesAIString = strings[1];\n\n const commentBox = $('
');\n var pasteCountDiv = $('
');\n getString('pastecount', 'tiny_cursive').then(str => {\n pasteCountDiv.append('
' + str + ' : ' + responsedata.commentscount + '
');\n return true;\n }).catch(error => window.console.log(error));\n\n var commentsDiv = $('
');\n getString('comments', 'tiny_cursive').then(str => {\n commentsDiv.append('' + str + '');\n return true;\n }).catch(error => window.console.error(error));\n\n var commentsList = $('
');\n\n let comments = responsedata.comments;\n for (let index in comments) {\n var commentDiv = $(`
`).text(comments[index].usercomment);\n commentsList.append(commentDiv);\n }\n commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);\n\n const $legend = $('
');\n\n // Create the first legend item\n const $attributedItem = $('
', {\"class\": \"tiny_cursive-legend-item\"});\n const $attributedBox = $('
', {\"class\": \"tiny_cursive-box attributed\"});\n const $attributedText = $('').text(originalTextString);\n $attributedItem.append($attributedBox).append($attributedText);\n\n // Create the second legend item\n const $unattributedItem = $('
', {\"class\": 'tiny_cursive-legend-item'});\n const $unattributedBox = $('
', {\"class\": 'tiny_cursive-box tiny_cursive_added'});\n const $unattributedText = $('').text(editsPastesAIString);\n $unattributedItem.append($unattributedBox).append($unattributedText);\n\n // Append the legend items to the legend container.\n $legend.append($attributedItem).append($unattributedItem);\n\n let contents = $('
').addClass('tiny_cursive-comparison-content');\n let textBlock2 = $('
').addClass('tiny_cursive-text-block').append(\n $('
').attr('id', 'tiny_cursive-reconstructed_text').html(JSON.parse(submittedText))\n );\n\n contents.append(commentBox, $legend, textBlock2);\n $('#content' + userid).html(contents); // Update content.\n }).fail(error => {\n window.console.error(\"Failed to load language strings:\", error);\n $('#content' + userid).html(nodata);\n });\n } else {\n $('#content' + userid).html(nodata);\n }\n }).fail(error => {\n $('#content' + userid).html(nodata);\n throw new Error('Error loading JSON file: ' + error.message);\n });\n });\n }\n\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\n $('body').on('click', '#rep' + userid + questionid, function(e) {\n\n if (filepath) {\n $('#replayControls_' + userid + questionid).removeClass('d-none');\n $('#content' + userid).addClass('tiny_cursive_outputElement');\n }\n\n $(this).prop('disabled', true);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'replay');\n $('#player_' + userid + questionid).css({\n 'display': 'block',\n 'padding-right': '8px'\n });\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\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 learnMore(moreBtn, context, userid, questionid, replayInstances) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n moreBtn.addClass('active');\n $('#rep' + userid + questionid).prop('disabled', false);\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('#content' + userid + questionid).removeClass('tiny_cursive_outputElement');\n $('#replayControls_' + userid + questionid).addClass('d-none');\n template.render('tiny_cursive/learn_more', context).then(function(html) {\n $('#content' + userid + questionid).html(html);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 if (score < scoreSetting) {\n icon = 'fa fa-question-circle';\n color = 'font-size:32px;color:#A9A9A9';\n return $('').addClass(icon).attr('style', color).attr('title', localStorage.getItem('notenoughtinfo'));\n } else {\n return $('').addClass(icon).attr('style', color);\n }\n\n }\n}\n"],"names":["constructor","then","str","localStorage","setItem","catch","error","window","console","log","createModal","userid","context","questionid","replayInstances","authIcon","self","this","on","e","preventDefault","isReplayButton","find","length","create","templateContext","modal","html","show","setTimeout","removeClass","replayTab","trigger","addClass","moreBtn","prop","css","learnMore","analytics","templates","attr","append","stopReplay","render","fail","checkDiff","fileid","nodata","document","createElement","classList","add","textContent","Error","methodname","args","done","response","responsedata","JSON","parse","data","submittedText","atob","submitted_text","key","component","strings","originalTextString","editsPastesAIString","commentBox","pasteCountDiv","commentscount","commentsDiv","commentsList","comments","index","commentDiv","text","usercomment","$legend","$attributedItem","$attributedBox","$attributedText","$unattributedItem","$unattributedBox","$unattributedText","contents","textBlock2","message","replyWriting","filepath","video_playback","formatedTime","total_time_seconds","totalTimeSeconds","Math","floor","toString","padStart","authorshipStatus","firstFile","score","scoreSetting","icon","color","parseFloat","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 and showing quality metrics.\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 $ from 'jquery';\nimport {get_string as getString} from 'core/str';\nimport {get_strings as getStrings} from 'core/str';\nimport template from 'core/templates';\n\nexport default class AnalyticEvents {\n\n constructor() {\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\n localStorage.setItem('notenoughtinfo', str);\n return str;\n }).catch(error => window.console.log(error));\n }\n\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\n const self = this;\n $('#analytics' + userid + questionid).on('click', function(e) {\n e.preventDefault();\n\n const isReplayButton = $(this).find('.tiny_cursive-replay-button').length > 0;\n // Create Moodle modal\n myModal.create({templateContext: context}).then(modal => {\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n modal.show();\n\n if (isReplayButton) {\n setTimeout(() => {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n\n const replayTab = $('#rep' + userid + questionid);\n if (replayTab.length) {\n replayTab.trigger('click');\n replayTab.addClass('active');\n }\n }, 50);\n }\n\n let moreBtn = $('body #more' + userid + questionid);\n if (moreBtn.length > 0) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('#analytic' + userid + questionid).prop('disabled', true);\n $('#diff' + userid + questionid).prop('disabled', true);\n $('#analytic' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n $('#diff' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n moreBtn.on('click', function(e) {\n e.preventDefault();\n self.learnMore($(this), context, userid, questionid, replayInstances);\n });\n }\n\n return true;\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\n $('body').on('click', '#analytic' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'analytics');\n $('#player_' + userid + questionid).css({'display': 'none'});\n $('#content' + userid).removeClass('tiny_cursive_outputElement')\n .addClass('tiny_cursive').attr('data-label', 'analytics');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n\n templates.render('tiny_cursive/analytics_table', context).then(function(html) {\n $('#content' + userid).html(html);\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 getString('nopaylod', 'tiny_cursive').then(str => {\n nodata.textContent = str;\n return true;\n }).catch(error => window.console.log(error));\n $('body').on('click', '#diff' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'diff');\n $('#player_' + userid + questionid).css({\n 'display': 'none'\n });\n $('#content' + userid).removeClass('tiny_cursive_outputElement').addClass('tiny_cursive').attr('data-label', 'diff');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active');\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n if (!fileid) {\n $('#content' + userid).html(nodata);\n throw new Error('Missing file id or Difference Content not received yet');\n }\n getContent([{\n methodname: 'cursive_get_writing_differences',\n args: {fileid: fileid},\n }])[0].done(response => {\n let responsedata = JSON.parse(response.data);\n if (responsedata) {\n let submittedText = atob(responsedata.submitted_text);\n\n // Fetch the dynamic strings.\n getStrings([\n {key: 'original_text', component: 'tiny_cursive'},\n {key: 'editspastesai', component: 'tiny_cursive'}\n ]).done(strings => {\n const originalTextString = strings[0];\n const editsPastesAIString = strings[1];\n\n const commentBox = $('
');\n var pasteCountDiv = $('
');\n getString('pastecount', 'tiny_cursive').then(str => {\n pasteCountDiv.append('
' + str + ' : ' + responsedata.commentscount + '
');\n return true;\n }).catch(error => window.console.log(error));\n\n var commentsDiv = $('
');\n getString('comments', 'tiny_cursive').then(str => {\n commentsDiv.append('' + str + '');\n return true;\n }).catch(error => window.console.error(error));\n\n var commentsList = $('
');\n\n let comments = responsedata.comments;\n for (let index in comments) {\n var commentDiv = $(`
`).text(comments[index].usercomment);\n commentsList.append(commentDiv);\n }\n commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);\n\n const $legend = $('
');\n\n // Create the first legend item\n const $attributedItem = $('
', {\"class\": \"tiny_cursive-legend-item\"});\n const $attributedBox = $('
', {\"class\": \"tiny_cursive-box attributed\"});\n const $attributedText = $('').text(originalTextString);\n $attributedItem.append($attributedBox).append($attributedText);\n\n // Create the second legend item\n const $unattributedItem = $('
', {\"class\": 'tiny_cursive-legend-item'});\n const $unattributedBox = $('
', {\"class\": 'tiny_cursive-box tiny_cursive_added'});\n const $unattributedText = $('').text(editsPastesAIString);\n $unattributedItem.append($unattributedBox).append($unattributedText);\n\n // Append the legend items to the legend container.\n $legend.append($attributedItem).append($unattributedItem);\n\n let contents = $('
').addClass('tiny_cursive-comparison-content');\n let textBlock2 = $('
').addClass('tiny_cursive-text-block').append(\n $('
').attr('id', 'tiny_cursive-reconstructed_text').html(JSON.parse(submittedText))\n );\n\n contents.append(commentBox, $legend, textBlock2);\n $('#content' + userid).html(contents); // Update content.\n }).fail(error => {\n window.console.error(\"Failed to load language strings:\", error);\n $('#content' + userid).html(nodata);\n });\n } else {\n $('#content' + userid).html(nodata);\n }\n }).fail(error => {\n $('#content' + userid).html(nodata);\n throw new Error('Error loading JSON file: ' + error.message);\n });\n });\n }\n\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\n $('body').on('click', '#rep' + userid + questionid, function(e) {\n\n if (filepath) {\n $('#replayControls_' + userid + questionid).removeClass('d-none');\n $('#content' + userid).addClass('tiny_cursive_outputElement');\n }\n\n $(this).prop('disabled', true);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'replay');\n $('#player_' + userid + questionid).css({\n 'display': 'block',\n 'padding-right': '8px'\n });\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\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 learnMore(moreBtn, context, userid, questionid, replayInstances) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n moreBtn.addClass('active');\n $('#rep' + userid + questionid).prop('disabled', false);\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('#content' + userid + questionid).removeClass('tiny_cursive_outputElement');\n $('#replayControls_' + userid + questionid).addClass('d-none');\n template.render('tiny_cursive/learn_more', context).then(function(html) {\n $('#content' + userid + questionid).html(html);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 if (score < scoreSetting) {\n icon = 'fa fa-question-circle';\n color = 'font-size:32px;color:#A9A9A9';\n return $('').addClass(icon).attr('style', color).attr('title', localStorage.getItem('notenoughtinfo'));\n } else {\n return $('').addClass(icon).attr('style', color);\n }\n\n }\n}\n"],"names":["constructor","then","str","localStorage","setItem","catch","error","window","console","log","createModal","userid","context","questionid","replayInstances","authIcon","self","this","on","e","preventDefault","isReplayButton","find","length","create","templateContext","modal","html","show","setTimeout","removeClass","replayTab","trigger","addClass","moreBtn","prop","css","learnMore","analytics","templates","attr","append","stopReplay","render","fail","checkDiff","fileid","nodata","document","createElement","classList","add","textContent","Error","methodname","args","done","response","responsedata","JSON","parse","data","submittedText","atob","submitted_text","key","component","strings","originalTextString","editsPastesAIString","commentBox","pasteCountDiv","commentscount","commentsDiv","commentsList","comments","index","commentDiv","text","usercomment","$legend","$attributedItem","$attributedBox","$attributedText","$unattributedItem","$unattributedBox","$unattributedText","contents","textBlock2","message","replyWriting","filepath","video_playback","formatedTime","total_time_seconds","totalTimeSeconds","hours","Math","floor","toString","padStart","minutes","seconds","authorshipStatus","firstFile","score","scoreSetting","icon","color","parseFloat","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 b0b20b2d..7444b4d6 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","jquery"],(function(_exports,_modal,_jquery){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default: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=_interopRequireDefault(_modal),_jquery=_interopRequireDefault(_jquery);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",overflow:"auto","scrollbar-width":"none"}),root.find(".modal-dialog").css({"max-width":"800px","background-color":"transparent"}),root.find("#analytic-close").on("click",(()=>{this.destroy()})),root.on("click",(e=>{(0,_jquery.default)(e.target).hasClass("modal")&&this.destroy()}))}}return _exports.default=MyModal,_exports.default})); +define("tiny_cursive/analytic_modal",["exports","core/modal","jquery"],(function(_exports,_modal,_jquery){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default: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=_interopRequireDefault(_modal),_jquery=_interopRequireDefault(_jquery);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",overflow:"auto","scrollbar-width":"none"}),root.find(".modal-dialog").css({"max-width":"800px","background-color":"transparent"}),root.find("#analytic-close").on("click",(()=>{this.destroy()})),root.on("click",(e=>{(0,_jquery.default)(e.target).hasClass("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 068d5e6b..0e95843c 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';\nimport $ from 'jquery';\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 'overflow': 'auto',\n 'scrollbar-width': 'none'\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).hasClass('modal')) {\n this.destroy();\n }\n });\n }\n}\n"],"names":["MyModal","Modal","configure","modalConfig","show","removeOnClose","backdrop","root","this","getRoot","find","remove","css","addClass","on","destroy","e","target","hasClass"],"mappings":";;;;;;;yKAyBqBA,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,gBACL,yBACO,SAEvBL,KAAKG,KAAK,iBAAiBE,IAAI,aACd,2BACO,gBAIxBL,KAAKG,KAAK,mBAAmBI,GAAG,SAAS,UAChCC,aAITR,KAAKO,GAAG,SAAUE,KACV,mBAAEA,EAAEC,QAAQC,SAAS,eAChBH"} \ 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';\nimport $ from 'jquery';\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 'overflow': 'auto',\n 'scrollbar-width': 'none'\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).hasClass('modal')) {\n this.destroy();\n }\n });\n }\n}\n"],"names":["MyModal","Modal","configure","modalConfig","show","removeOnClose","backdrop","root","this","getRoot","find","remove","css","addClass","on","destroy","e","target","hasClass"],"mappings":"ogBAyBqBA,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,gBACL,yBACO,SAEvBL,KAAKG,KAAK,iBAAiBE,IAAI,aACd,2BACO,gBAIxBL,KAAKG,KAAK,mBAAmBI,GAAG,SAAS,UAChCC,aAITR,KAAKO,GAAG,SAAUE,KACV,mBAAEA,EAAEC,QAAQC,SAAS,eAChBH,8DAjDAf,eACH,gDADGA,mBAEC"} \ No newline at end of file diff --git a/amd/build/append_lesson_grade_table.min.js b/amd/build/append_lesson_grade_table.min.js index 5f481a66..52a4b6ae 100644 --- a/amd/build/append_lesson_grade_table.min.js +++ b/amd/build/append_lesson_grade_table.min.js @@ -7,6 +7,6 @@ define("tiny_cursive/append_lesson_grade_table",["exports","./replay","jquery"," * @module tiny_cursive/append_lesson_grade_table * @copyright 2025 CTI * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_replay=_interopRequireDefault(_replay),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates),_analytic_events=_interopRequireDefault(_analytic_events),_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),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);_exports.init=(scoreSetting,showcomment,hasApiKey)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>((0,_jquery.default)("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var cmid=M.cfg.contextInstanceId,emailLink=(0,_jquery.default)('#page-content div[role="main"] td.lastcol a'),headcolumn=(0,_jquery.default)('#region-main div[role="main"] table thead tr');let url=new URL(window.location.href),mode=url.searchParams.get("mode"),user=url.searchParams.get("user");function analytics(userid,cmid,$emailLink,grade){let args={id:userid,modulename:"lesson",cmid:cmid},com=(0,_ajax.call)([{methodname:"cursive_get_lesson_submission_data",args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.res.filename&&(filepath=data.res.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.res.effort_ratio,userid)):(0,_jquery.default)(analyticButtonDiv).html((0,_replay_button.default)(userid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),grade?(analyticButtonDiv.classList.add("w-100"),(0,_jquery.default)("#fitem_id_response_editor .felement").prepend(analyticButtonDiv)):$emailLink.closest("tr").find("td:eq(1)").after(analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.res,formattime:myEvents.formatedTime(data.res),page:scoreSetting,userid:userid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.res.first_file,data.res.score,scoreSetting);myEvents.createModal(userid,context,"",replayInstances,authIcon),myEvents.analytics(userid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(userid,data.res.file_id,"",replayInstances),myEvents.replyWriting(userid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}Str.get_string("analytics","tiny_cursive").then((strs=>(headcolumn.each((function(){(0,_jquery.default)(this).find("th:eq(1)").after(`${strs}`)})),!0))).catch((e=>window.console.error(e))),mode&&"grade"===mode&&analytics(user,cmid,"",!0),emailLink.each((function(){let href=(0,_jquery.default)(this).attr("href");const $emailLink=(0,_jquery.default)(this);let userid=0;href&&(userid=parseInt(new URLSearchParams(href.split("?")[1]).get("userid")),userid?((0,_jquery.default)("#region-main").on("click","table tbody tr td.cell.c1 a",(function(e){e.preventDefault();const link=e.target.href,url=new URL(link);url.searchParams.append("user",userid),window.location.href=url.toString()})),analytics(userid,cmid,$emailLink,!1)):$emailLink.closest("tr").find("td:eq(1)").after(""))}))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_replay=_interopRequireDefault(_replay),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates),_analytic_events=_interopRequireDefault(_analytic_events),_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),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);_exports.init=(scoreSetting,showcomment,hasApiKey)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>((0,_jquery.default)("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var cmid=M.cfg.contextInstanceId,emailLink=(0,_jquery.default)('#page-content div[role="main"] td.lastcol a'),headcolumn=(0,_jquery.default)('#region-main div[role="main"] table thead tr');let url=new URL(window.location.href),mode=url.searchParams.get("mode"),user=url.searchParams.get("user");function analytics(userid,cmid,$emailLink,grade){let args={id:userid,modulename:"lesson",cmid:cmid},com=(0,_ajax.call)([{methodname:"cursive_get_lesson_submission_data",args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.res.filename&&(filepath=data.res.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.res.effort_ratio,userid)):(0,_jquery.default)(analyticButtonDiv).html((0,_replay_button.default)(userid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),grade?(analyticButtonDiv.classList.add("w-100"),(0,_jquery.default)("#fitem_id_response_editor .felement").prepend(analyticButtonDiv)):$emailLink.closest("tr").find("td:eq(1)").after(analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.res,formattime:myEvents.formatedTime(data.res),page:scoreSetting,userid:userid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.res.first_file,data.res.score,scoreSetting);myEvents.createModal(userid,context,"",replayInstances,authIcon),myEvents.analytics(userid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(userid,data.res.file_id,"",replayInstances),myEvents.replyWriting(userid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}Str.get_string("analytics","tiny_cursive").then((strs=>(headcolumn.each((function(){(0,_jquery.default)(this).find("th:eq(1)").after(''.concat(strs,""))})),!0))).catch((e=>window.console.error(e))),mode&&"grade"===mode&&analytics(user,cmid,"",!0),emailLink.each((function(){let href=(0,_jquery.default)(this).attr("href");const $emailLink=(0,_jquery.default)(this);let userid=0;href&&(userid=parseInt(new URLSearchParams(href.split("?")[1]).get("userid")),userid?((0,_jquery.default)("#region-main").on("click","table tbody tr td.cell.c1 a",(function(e){e.preventDefault();const link=e.target.href,url=new URL(link);url.searchParams.append("user",userid),window.location.href=url.toString()})),analytics(userid,cmid,$emailLink,!1)):$emailLink.closest("tr").find("td:eq(1)").after(""))}))}})); //# sourceMappingURL=append_lesson_grade_table.min.js.map \ No newline at end of file diff --git a/amd/build/append_lesson_grade_table.min.js.map b/amd/build/append_lesson_grade_table.min.js.map index ac5fede6..6ad80de5 100644 --- a/amd/build/append_lesson_grade_table.min.js.map +++ b/amd/build/append_lesson_grade_table.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_lesson_grade_table.min.js","sources":["../src/append_lesson_grade_table.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 to append analytics and grade table data for lesson submissions\n * Handles the display of analytics, replay functionality and grade information\n * for lesson submissions in the Moodle gradebook interface\n *\n * @module tiny_cursive/append_lesson_grade_table\n * @copyright 2025 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Replay from './replay';\nimport $ from 'jquery';\nimport {call as getData} from 'core/ajax';\nimport templates from 'core/templates';\nimport AnalyticEvents from './analytic_events';\nimport analyticButton from './analytic_button';\nimport replayButton from './replay_button';\nimport * as Str from 'core/str';\n\nexport const init = (scoreSetting, showcomment, hasApiKey) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n\n var cmid = M.cfg.contextInstanceId;\n var emailLink = $('#page-content div[role=\"main\"] td.lastcol a');\n var headcolumn = $('#region-main div[role=\"main\"] table thead tr');\n let url = new URL(window.location.href);\n let mode = url.searchParams.get('mode');\n let user = url.searchParams.get('user');\n\n Str.get_string('analytics', 'tiny_cursive').then((strs) => {\n headcolumn.each(function() {\n $(this).find('th:eq(1)').after(`${strs}`);\n });\n return true;\n }).catch(e => window.console.error(e));\n\n if (mode && mode === \"grade\") {\n analytics(user, cmid, \"\", true);\n }\n\n emailLink.each(function() {\n let href = $(this).attr('href');\n const $emailLink = $(this);\n let userid = 0;\n if (href) {\n userid = parseInt(new URLSearchParams(href.split('?')[1]).get('userid'));\n if (!userid) {\n $emailLink.closest('tr').find('td:eq(1)').after(\"\"); // For aligning the table column\n } else {\n\n $('#region-main').on('click', 'table tbody tr td.cell.c1 a', function(e) {\n e.preventDefault();\n const link = e.target.href;\n const url = new URL(link);\n url.searchParams.append('user', userid);\n window.location.href = url.toString();\n });\n\n analytics(userid, cmid, $emailLink, false);\n }\n }\n });\n\n /**\n * Fetches and displays analytics data for lesson submissions\n * @param {number} userid - The ID of the user whose analytics to fetch\n * @param {number} cmid - The course module ID\n * @param {jQuery|string} $emailLink - jQuery object of email link or empty string\n * @param {boolean} grade - Whether this is being called from grade view\n */\n function analytics(userid, cmid, $emailLink, grade) {\n\n let args = {id: userid, modulename: \"lesson\", cmid: cmid};\n let methodname = 'cursive_get_lesson_submission_data';\n let com = getData([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(userid));\n } else {\n analyticButtonDiv.append(analyticButton(data.res.effort_ratio, userid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n if (grade) {\n analyticButtonDiv.classList.add('w-100');\n $('#fitem_id_response_editor .felement').prepend(analyticButtonDiv);\n } else {\n $emailLink.closest('tr').find('td:eq(1)').after(analyticsColumn);\n }\n\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.res,\n formattime: myEvents.formatedTime(data.res),\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","showcomment","hasApiKey","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","catch","e","console","error","cmid","M","cfg","contextInstanceId","emailLink","headcolumn","url","URL","location","href","mode","searchParams","get","user","analytics","userid","$emailLink","grade","args","id","modulename","com","methodname","done","json","data","JSON","parse","res","filename","analyticButtonDiv","document","createElement","analyticsColumn","append","effort_ratio","dataset","region","classList","add","prepend","closest","find","after","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","templates","checkDiff","file_id","replyWriting","fail","Str","get_string","strs","each","this","attr","parseInt","URLSearchParams","split","on","preventDefault","link","target","toString"],"mappings":";;;;;;;;;8hCAkCoB,CAACA,aAAcC,YAAaC,mBACtCC,gBAAkB,GAExBC,OAAOC,eAAiB,SAASC,IAAKC,aACjB,KAAbA,SAAiB,OACXC,OAAS,IAAIC,gBACf,UAAYH,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,+BAEbE,OAAO,8BAA8BC,MAAKC,2BAC9C,WAAaN,KAAKM,KAAKA,OAClB,KACRC,OAAMC,GAAKV,OAAOW,QAAQC,MAAMF,YAEhC,OAKPG,KAAOC,EAAEC,IAAIC,kBACbC,WAAY,mBAAE,+CACdC,YAAa,mBAAE,oDACfC,IAAM,IAAIC,IAAIpB,OAAOqB,SAASC,MAC9BC,KAAOJ,IAAIK,aAAaC,IAAI,QAC5BC,KAAOP,IAAIK,aAAaC,IAAI,iBA2CvBE,UAAUC,OAAQf,KAAMgB,WAAYC,WAErCC,KAAO,CAACC,GAAIJ,OAAQK,WAAY,SAAUpB,KAAMA,MAEhDqB,KAAM,cAAQ,CAAC,CAACC,WADH,qCACeJ,KAAAA,QAChCG,IAAI,GAAGE,MAAK,SAASC,UACbC,KAAOC,KAAKC,MAAMH,MAClBlC,SAAW,GACXmC,KAAKG,IAAIC,WACTvC,SAAWmC,KAAKG,IAAIC,cAGpBC,kBAAoBC,SAASC,cAAc,OAC3CC,gBAAkBF,SAASC,cAAc,MAExC/C,UAGD6C,kBAAkBI,QAAO,4BAAeT,KAAKG,IAAIO,aAAcpB,6BAF7De,mBAAmBnC,MAAK,0BAAaoB,SAK3Ce,kBAAkBM,QAAQC,OAAS,eAAiBtB,OACpDkB,gBAAgBC,OAAOJ,mBACnBb,OACAa,kBAAkBQ,UAAUC,IAAI,6BAC9B,uCAAuCC,QAAQV,oBAEjDd,WAAWyB,QAAQ,MAAMC,KAAK,YAAYC,MAAMV,qBAIhDW,SAAW,IAAIC,6BACfC,QAAU,CACVC,UAAWtB,KAAKG,IAChBoB,WAAYJ,SAASK,aAAaxB,KAAKG,KACvCsB,KAAMnE,aACNgC,OAAQA,OACRoC,OAAQlE,eAGRmE,SAAWR,SAASS,iBAAiB5B,KAAKG,IAAI0B,WAAY7B,KAAKG,IAAI2B,MAAOxE,cAC9E6D,SAASY,YAAYzC,OAAQ+B,QAAS,GAAI5D,gBAAiBkE,UAC3DR,SAAS9B,UAAUC,OAAQ0C,mBAAWX,QAAS,GAAI5D,gBAAiBkE,UACpER,SAASc,UAAU3C,OAAQU,KAAKG,IAAI+B,QAAS,GAAIzE,iBACjD0D,SAASgB,aAAa7C,OAAQzB,SAAU,GAAIJ,oBAGhDmC,IAAI,GAAGwC,MAAM9D,QACTZ,OAAOW,QAAQC,MAAM,gCAAiCA,UAzF9D+D,IAAIC,WAAW,YAAa,gBAAgBrE,MAAMsE,OAC9C3D,WAAW4D,MAAK,+BACVC,MAAMxB,KAAK,YAAYC,MAAO,sBAAqBqB,iBAElD,KACRpE,OAAMC,GAAKV,OAAOW,QAAQC,MAAMF,KAE/Ba,MAAiB,UAATA,MACRI,UAAUD,KAAMb,KAAM,IAAI,GAG9BI,UAAU6D,MAAK,eACPxD,MAAO,mBAAEyD,MAAMC,KAAK,cAClBnD,YAAa,mBAAEkD,UACjBnD,OAAS,EACTN,OACAM,OAASqD,SAAS,IAAIC,gBAAgB5D,KAAK6D,MAAM,KAAK,IAAI1D,IAAI,WACzDG,4BAIC,gBAAgBwD,GAAG,QAAS,+BAA+B,SAAS1E,GAClEA,EAAE2E,uBACIC,KAAO5E,EAAE6E,OAAOjE,KAChBH,IAAM,IAAIC,IAAIkE,MACpBnE,IAAIK,aAAauB,OAAO,OAAQnB,QAChC5B,OAAOqB,SAASC,KAAOH,IAAIqE,cAG/B7D,UAAUC,OAAQf,KAAMgB,YAAY,IAXpCA,WAAWyB,QAAQ,MAAMC,KAAK,YAAYC,MAAM"} \ No newline at end of file +{"version":3,"file":"append_lesson_grade_table.min.js","sources":["../src/append_lesson_grade_table.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 to append analytics and grade table data for lesson submissions\n * Handles the display of analytics, replay functionality and grade information\n * for lesson submissions in the Moodle gradebook interface\n *\n * @module tiny_cursive/append_lesson_grade_table\n * @copyright 2025 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Replay from './replay';\nimport $ from 'jquery';\nimport {call as getData} from 'core/ajax';\nimport templates from 'core/templates';\nimport AnalyticEvents from './analytic_events';\nimport analyticButton from './analytic_button';\nimport replayButton from './replay_button';\nimport * as Str from 'core/str';\n\nexport const init = (scoreSetting, showcomment, hasApiKey) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n\n var cmid = M.cfg.contextInstanceId;\n var emailLink = $('#page-content div[role=\"main\"] td.lastcol a');\n var headcolumn = $('#region-main div[role=\"main\"] table thead tr');\n let url = new URL(window.location.href);\n let mode = url.searchParams.get('mode');\n let user = url.searchParams.get('user');\n\n Str.get_string('analytics', 'tiny_cursive').then((strs) => {\n headcolumn.each(function() {\n $(this).find('th:eq(1)').after(`${strs}`);\n });\n return true;\n }).catch(e => window.console.error(e));\n\n if (mode && mode === \"grade\") {\n analytics(user, cmid, \"\", true);\n }\n\n emailLink.each(function() {\n let href = $(this).attr('href');\n const $emailLink = $(this);\n let userid = 0;\n if (href) {\n userid = parseInt(new URLSearchParams(href.split('?')[1]).get('userid'));\n if (!userid) {\n $emailLink.closest('tr').find('td:eq(1)').after(\"\"); // For aligning the table column\n } else {\n\n $('#region-main').on('click', 'table tbody tr td.cell.c1 a', function(e) {\n e.preventDefault();\n const link = e.target.href;\n const url = new URL(link);\n url.searchParams.append('user', userid);\n window.location.href = url.toString();\n });\n\n analytics(userid, cmid, $emailLink, false);\n }\n }\n });\n\n /**\n * Fetches and displays analytics data for lesson submissions\n * @param {number} userid - The ID of the user whose analytics to fetch\n * @param {number} cmid - The course module ID\n * @param {jQuery|string} $emailLink - jQuery object of email link or empty string\n * @param {boolean} grade - Whether this is being called from grade view\n */\n function analytics(userid, cmid, $emailLink, grade) {\n\n let args = {id: userid, modulename: \"lesson\", cmid: cmid};\n let methodname = 'cursive_get_lesson_submission_data';\n let com = getData([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(userid));\n } else {\n analyticButtonDiv.append(analyticButton(data.res.effort_ratio, userid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n if (grade) {\n analyticButtonDiv.classList.add('w-100');\n $('#fitem_id_response_editor .felement').prepend(analyticButtonDiv);\n } else {\n $emailLink.closest('tr').find('td:eq(1)').after(analyticsColumn);\n }\n\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.res,\n formattime: myEvents.formatedTime(data.res),\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","showcomment","hasApiKey","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","catch","e","console","error","cmid","M","cfg","contextInstanceId","emailLink","headcolumn","url","URL","location","href","mode","searchParams","get","user","analytics","userid","$emailLink","grade","args","id","modulename","com","methodname","done","json","data","JSON","parse","res","filename","analyticButtonDiv","document","createElement","analyticsColumn","append","effort_ratio","dataset","region","classList","add","prepend","closest","find","after","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","templates","checkDiff","file_id","replyWriting","fail","Str","get_string","strs","each","this","attr","parseInt","URLSearchParams","split","on","preventDefault","link","target","toString"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/append_pdfannotator.min.js b/amd/build/append_pdfannotator.min.js index c1578298..5dd8e121 100644 --- a/amd/build/append_pdfannotator.min.js +++ b/amd/build/append_pdfannotator.min.js @@ -5,6 +5,6 @@ define("tiny_cursive/append_pdfannotator",["exports","core/ajax","tiny_cursive/a * @module tiny_cursive/append_pdfannotator * @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,_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),_analytic_events=_interopRequireDefault(_analytic_events),_templates=_interopRequireDefault(_templates),_replay=_interopRequireDefault(_replay);_exports.init=(scoreSetting,comments,hasApiKey,userid)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>(document.getElementById("content"+mid).innerHTML=html,!0))).catch((e=>window.console.error(e)));return!1};let container=document.querySelector(".comment-list-container");const overviewTable=document.querySelector('table[id^="mod-pdfannotator-"]');document.addEventListener("click",(function(e){"commentSubmit"===e.target.id&&(localStorage.removeItem("isEditing"),buttonElement=e.target.value,pendingSubmit=!0);"commentCancel"===e.target.id&&(localStorage.removeItem("isEditing"),pendingSubmit=!1)}));const moduleName=document.body.id.split("-")[2];var pendingSubmit=!1,buttonElement="";if(container){new MutationObserver((()=>{var _container$lastChild;null!=container&&null!==(_container$lastChild=container.lastChild)&&void 0!==_container$lastChild&&_container$lastChild.id&&function(id){if("Save"===buttonElement)return void(pendingSubmit=!1);let resourceId=parseInt(null==id?void 0:id.split("_")[1]);if(resourceId&&pendingSubmit){pendingSubmit=!1,(async(methodname,args)=>{try{await(0,_ajax.call)([{methodname:methodname,args:args}])[0]}catch(error){throw window.console.error("updating Entries:",error),error}})("cursive_update_pdf_annote_id",{cmid:M.cfg.contextInstanceId,userid:M.cfg.userId??0,courseid:M.cfg.courseId,modulename:moduleName,resourceid:resourceId})}}(container.lastChild.id)})).observe(container,{subtree:!0,childList:!0})}if(overviewTable){let newChild=document.createElement("th");newChild.textContent="Analytics",overviewTable.querySelector("thead>tr>th:first-child").insertAdjacentElement("afterend",newChild),function(overviewTable){const rows=overviewTable.querySelectorAll("tbody > tr"),action=new URL(window.location.href).searchParams.get("action");rows.forEach((row=>{var _cols$col,_cols$col2,_cols$col3,_links$link,_links$link2,_userLink;const cols={col1:row.querySelector("td:nth-child(1)"),col2:row.querySelector("td:nth-child(2)"),col3:row.querySelector("td:nth-child(3)")},links={link1:null===(_cols$col=cols.col1)||void 0===_cols$col?void 0:_cols$col.querySelector("a"),link2:null===(_cols$col2=cols.col2)||void 0===_cols$col2?void 0:_cols$col2.querySelector("a"),link3:null===(_cols$col3=cols.col3)||void 0===_cols$col3?void 0:_cols$col3.querySelector("a")},commentId=null!==(_links$link=links.link1)&&void 0!==_links$link&&_links$link.href?new URL(links.link1.href).searchParams.get("commid"):null,cmid=null!==(_links$link2=links.link1)&&void 0!==_links$link2&&_links$link2.href?new URL(links.link1.href).searchParams.get("id"):M.cfg.contextInstanceId;let userId=null,userLink=null;switch(action){case"overviewquestions":userLink=links.link2;break;case"overviewanswers":userLink=links.link3;break;default:userId=userid}if(null!==(_userLink=userLink)&&void 0!==_userLink&&_userLink.href)try{userId=new URL(userLink.href).searchParams.get("id")}catch(e){window.console.warn("Error parsing user URL:",e)}!function(userid,resourceid,cmid,place){let args={id:resourceid,modulename:"pdfannotator",cmid:cmid},methodname="cursive_get_forum_comment_link",com=(0,_ajax.call)([{methodname:methodname,args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.data.filename&&(filepath=data.data.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.data.effort_ratio,resourceid)):analyticButtonDiv.append((0,_replay_button.default)(resourceid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),place.insertAdjacentElement("afterend",analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.data,formattime:myEvents.formatedTime(data.data),page:scoreSetting,userid:resourceid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.data.first_file,data.data.score,scoreSetting);myEvents.createModal(resourceid,context,"",replayInstances,authIcon),myEvents.analytics(resourceid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(resourceid,data.data.file_id,"",replayInstances),myEvents.replyWriting(resourceid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}(userId,commentId,cmid,cols.col1)}))}(overviewTable)}}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),_analytic_events=_interopRequireDefault(_analytic_events),_templates=_interopRequireDefault(_templates),_replay=_interopRequireDefault(_replay);_exports.init=(scoreSetting,comments,hasApiKey,userid)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>(document.getElementById("content"+mid).innerHTML=html,!0))).catch((e=>window.console.error(e)));return!1};let container=document.querySelector(".comment-list-container");const overviewTable=document.querySelector('table[id^="mod-pdfannotator-"]');document.addEventListener("click",(function(e){"commentSubmit"===e.target.id&&(localStorage.removeItem("isEditing"),buttonElement=e.target.value,pendingSubmit=!0);"commentCancel"===e.target.id&&(localStorage.removeItem("isEditing"),pendingSubmit=!1)}));const moduleName=document.body.id.split("-")[2];var pendingSubmit=!1,buttonElement="";if(container){new MutationObserver((()=>{var _container$lastChild;null!=container&&null!==(_container$lastChild=container.lastChild)&&void 0!==_container$lastChild&&_container$lastChild.id&&function(id){if("Save"===buttonElement)return void(pendingSubmit=!1);let resourceId=parseInt(null==id?void 0:id.split("_")[1]);if(resourceId&&pendingSubmit){var _M$cfg$userId;pendingSubmit=!1,(async(methodname,args)=>{try{await(0,_ajax.call)([{methodname:methodname,args:args}])[0]}catch(error){throw window.console.error("updating Entries:",error),error}})("cursive_update_pdf_annote_id",{cmid:M.cfg.contextInstanceId,userid:null!==(_M$cfg$userId=M.cfg.userId)&&void 0!==_M$cfg$userId?_M$cfg$userId:0,courseid:M.cfg.courseId,modulename:moduleName,resourceid:resourceId})}}(container.lastChild.id)})).observe(container,{subtree:!0,childList:!0})}if(overviewTable){let newChild=document.createElement("th");newChild.textContent="Analytics",overviewTable.querySelector("thead>tr>th:first-child").insertAdjacentElement("afterend",newChild),function(overviewTable){const rows=overviewTable.querySelectorAll("tbody > tr"),action=new URL(window.location.href).searchParams.get("action");rows.forEach((row=>{var _cols$col,_cols$col2,_cols$col3,_links$link,_links$link2,_userLink;const cols={col1:row.querySelector("td:nth-child(1)"),col2:row.querySelector("td:nth-child(2)"),col3:row.querySelector("td:nth-child(3)")},links={link1:null===(_cols$col=cols.col1)||void 0===_cols$col?void 0:_cols$col.querySelector("a"),link2:null===(_cols$col2=cols.col2)||void 0===_cols$col2?void 0:_cols$col2.querySelector("a"),link3:null===(_cols$col3=cols.col3)||void 0===_cols$col3?void 0:_cols$col3.querySelector("a")},commentId=null!==(_links$link=links.link1)&&void 0!==_links$link&&_links$link.href?new URL(links.link1.href).searchParams.get("commid"):null,cmid=null!==(_links$link2=links.link1)&&void 0!==_links$link2&&_links$link2.href?new URL(links.link1.href).searchParams.get("id"):M.cfg.contextInstanceId;let userId=null,userLink=null;switch(action){case"overviewquestions":userLink=links.link2;break;case"overviewanswers":userLink=links.link3;break;default:userId=userid}if(null!==(_userLink=userLink)&&void 0!==_userLink&&_userLink.href)try{userId=new URL(userLink.href).searchParams.get("id")}catch(e){window.console.warn("Error parsing user URL:",e)}!function(userid,resourceid,cmid,place){let args={id:resourceid,modulename:"pdfannotator",cmid:cmid},methodname="cursive_get_forum_comment_link",com=(0,_ajax.call)([{methodname:methodname,args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.data.filename&&(filepath=data.data.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.data.effort_ratio,resourceid)):analyticButtonDiv.append((0,_replay_button.default)(resourceid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),place.insertAdjacentElement("afterend",analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.data,formattime:myEvents.formatedTime(data.data),page:scoreSetting,userid:resourceid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.data.first_file,data.data.score,scoreSetting);myEvents.createModal(resourceid,context,"",replayInstances,authIcon),myEvents.analytics(resourceid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(resourceid,data.data.file_id,"",replayInstances),myEvents.replyWriting(resourceid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}(userId,commentId,cmid,cols.col1)}))}(overviewTable)}}})); //# sourceMappingURL=append_pdfannotator.min.js.map \ No newline at end of file diff --git a/amd/build/append_pdfannotator.min.js.map b/amd/build/append_pdfannotator.min.js.map index efbbc64c..301b50c1 100644 --- a/amd/build/append_pdfannotator.min.js.map +++ b/amd/build/append_pdfannotator.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_pdfannotator.min.js","sources":["../src/append_pdfannotator.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 PDF annotator functionality,\n *\n * @module tiny_cursive/append_pdfannotator\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call} from 'core/ajax';\nimport analyticButton from 'tiny_cursive/analytic_button';\nimport replayButton from 'tiny_cursive/replay_button';\nimport AnalyticEvents from 'tiny_cursive/analytic_events';\nimport templates from 'core/templates';\nimport Replay from 'tiny_cursive/replay';\nexport const init = (scoreSetting, comments, hasApiKey, userid) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n document.getElementById('content' + mid).innerHTML = html;\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n };\n\n let container = document.querySelector('.comment-list-container');\n const overviewTable = document.querySelector('table[id^=\"mod-pdfannotator-\"]');\n\n document.addEventListener('click', handleSubmit);\n const moduleName = document.body.id.split('-')[2];\n var pendingSubmit = false;\n var buttonElement = \"\";\n\n if (container) {\n const observer = new MutationObserver(() => {\n if (container?.lastChild?.id) {\n extractResourceId(container.lastChild.id);\n }\n });\n\n observer.observe(container, {\n subtree: true,\n childList: true\n });\n }\n\n if (overviewTable) {\n let newChild = document.createElement('th');\n newChild.textContent = 'Analytics';\n let header = overviewTable.querySelector('thead>tr>th:first-child');\n header.insertAdjacentElement('afterend', newChild);\n setReplayButton(overviewTable);\n }\n\n /**\n * Sets up replay buttons and analytics for each row in the overview table\n * @param {HTMLTableElement} overviewTable - The table element containing the overview data\n * @description This function:\n * 1. Gets all rows from the table\n * 2. For each row:\n * - Extracts comment ID and user ID from relevant links\n * - Adds analytics column with replay/analytics buttons\n * - Sets up cursive analytics functionality\n */\n function setReplayButton(overviewTable) {\n const rows = overviewTable.querySelectorAll('tbody > tr');\n const action = new URL(window.location.href).searchParams.get('action');\n\n rows.forEach(row => {\n const cols = {\n col1: row.querySelector('td:nth-child(1)'),\n col2: row.querySelector('td:nth-child(2)'),\n col3: row.querySelector('td:nth-child(3)')\n };\n\n const links = {\n link1: cols.col1?.querySelector('a'),\n link2: cols.col2?.querySelector('a'),\n link3: cols.col3?.querySelector('a')\n };\n\n // Extract comment ID safely\n const commentId = links.link1?.href ?\n new URL(links.link1.href).searchParams.get('commid') : null;\n const cmid = links.link1?.href ?\n new URL(links.link1.href).searchParams.get('id') : M.cfg.contextInstanceId;\n // Extract user ID based on action\n let userId = null;\n let userLink = null;\n\n switch (action) {\n case 'overviewquestions':\n userLink = links.link2;\n break;\n case 'overviewanswers':\n userLink = links.link3;\n break;\n default:\n userId = userid;\n }\n\n if (userLink?.href) {\n try {\n userId = new URL(userLink.href).searchParams.get('id');\n } catch (e) {\n window.console.warn('Error parsing user URL:', e);\n }\n }\n\n getCursiveAnalytics(userId, commentId, cmid, cols.col1);\n });\n }\n\n /**\n * Handles the submission and cancellation of comments\n * @param {Event} e - The click event object\n * @description When comment is submitted or cancelled:\n * - Removes 'isEditing' flag from localStorage\n * - Sets pendingSubmit flag appropriately (true for submit, false for cancel)\n */\n function handleSubmit(e) {\n if (e.target.id === 'commentSubmit') {\n localStorage.removeItem('isEditing');\n buttonElement = e.target.value;\n pendingSubmit = true;\n }\n if (e.target.id === 'commentCancel') {\n localStorage.removeItem('isEditing');\n pendingSubmit = false;\n }\n }\n\n const updateEntries = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n return response;\n } catch (error) {\n window.console.error('updating Entries:', error);\n throw error;\n }\n };\n\n /**\n * Extracts the resource ID from a comment ID and updates entries if submission is pending\n * @param {string} id - The ID string to extract resource ID from, expected format: 'prefix_number'\n * @description This function:\n * 1. Parses the resource ID from the given ID string\n * 2. If resource ID exists and there's a pending submission:\n * - Resets the pending submission flag\n * - Constructs arguments with context info\n * - Calls updateEntries to process the PDF annotation\n */\n function extractResourceId(id) {\n\n // Prevent updating ID while editing a existing entry.\n if (buttonElement === 'Save') {\n\n pendingSubmit = false;\n return;\n }\n\n let resourceId = parseInt(id?.split('_')[1]);\n if (resourceId && pendingSubmit) {\n pendingSubmit = false;\n let args = {\n cmid: M.cfg.contextInstanceId,\n userid: M.cfg.userId ?? 0,\n courseid: M.cfg.courseId,\n modulename: moduleName,\n resourceid: resourceId\n };\n updateEntries('cursive_update_pdf_annote_id', args);\n }\n }\n\n /**\n * Retrieves and displays cursive analytics for a given resource\n * @param {number} userid - The ID of the user\n * @param {number} resourceid - The ID of the resource to get analytics for\n * @param {number} cmid - The course module ID\n * @param {HTMLElement} place - The DOM element where analytics should be placed\n * @description This function:\n * 1. Makes an AJAX call to get forum comment data\n * 2. Creates and inserts analytics/replay buttons\n * 3. Sets up analytics events and modal functionality\n * 4. Handles both API key and non-API key scenarios\n */\n function getCursiveAnalytics(userid, resourceid, cmid, place) {\n let args = {id: resourceid, modulename: \"pdfannotator\", cmid: cmid};\n let methodname = 'cursive_get_forum_comment_link';\n let com = call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n analyticButtonDiv.append(replayButton(resourceid));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, resourceid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n place.insertAdjacentElement('afterend', analyticsColumn);\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: resourceid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(resourceid, context, '', replayInstances, authIcon);\n myEvents.analytics(resourceid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(resourceid, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(resourceid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","comments","hasApiKey","userid","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","document","getElementById","innerHTML","catch","e","console","error","container","querySelector","overviewTable","addEventListener","target","id","localStorage","removeItem","buttonElement","value","pendingSubmit","moduleName","body","split","MutationObserver","lastChild","_container$lastChild","resourceId","parseInt","async","methodname","args","updateEntries","cmid","M","cfg","contextInstanceId","userId","courseid","courseId","modulename","resourceid","extractResourceId","observe","subtree","childList","newChild","createElement","textContent","insertAdjacentElement","rows","querySelectorAll","action","URL","location","href","searchParams","get","forEach","row","cols","col1","col2","col3","links","link1","_cols$col","link2","_cols$col2","link3","_cols$col3","commentId","userLink","_userLink","warn","place","com","done","json","data","JSON","parse","filename","analyticButtonDiv","analyticsColumn","append","effort_ratio","dataset","region","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","templates","checkDiff","file_id","replyWriting","fail","getCursiveAnalytics","setReplayButton"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"append_pdfannotator.min.js","sources":["../src/append_pdfannotator.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 PDF annotator functionality,\n *\n * @module tiny_cursive/append_pdfannotator\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call} from 'core/ajax';\nimport analyticButton from 'tiny_cursive/analytic_button';\nimport replayButton from 'tiny_cursive/replay_button';\nimport AnalyticEvents from 'tiny_cursive/analytic_events';\nimport templates from 'core/templates';\nimport Replay from 'tiny_cursive/replay';\nexport const init = (scoreSetting, comments, hasApiKey, userid) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n document.getElementById('content' + mid).innerHTML = html;\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n };\n\n let container = document.querySelector('.comment-list-container');\n const overviewTable = document.querySelector('table[id^=\"mod-pdfannotator-\"]');\n\n document.addEventListener('click', handleSubmit);\n const moduleName = document.body.id.split('-')[2];\n var pendingSubmit = false;\n var buttonElement = \"\";\n\n if (container) {\n const observer = new MutationObserver(() => {\n if (container?.lastChild?.id) {\n extractResourceId(container.lastChild.id);\n }\n });\n\n observer.observe(container, {\n subtree: true,\n childList: true\n });\n }\n\n if (overviewTable) {\n let newChild = document.createElement('th');\n newChild.textContent = 'Analytics';\n let header = overviewTable.querySelector('thead>tr>th:first-child');\n header.insertAdjacentElement('afterend', newChild);\n setReplayButton(overviewTable);\n }\n\n /**\n * Sets up replay buttons and analytics for each row in the overview table\n * @param {HTMLTableElement} overviewTable - The table element containing the overview data\n * @description This function:\n * 1. Gets all rows from the table\n * 2. For each row:\n * - Extracts comment ID and user ID from relevant links\n * - Adds analytics column with replay/analytics buttons\n * - Sets up cursive analytics functionality\n */\n function setReplayButton(overviewTable) {\n const rows = overviewTable.querySelectorAll('tbody > tr');\n const action = new URL(window.location.href).searchParams.get('action');\n\n rows.forEach(row => {\n const cols = {\n col1: row.querySelector('td:nth-child(1)'),\n col2: row.querySelector('td:nth-child(2)'),\n col3: row.querySelector('td:nth-child(3)')\n };\n\n const links = {\n link1: cols.col1?.querySelector('a'),\n link2: cols.col2?.querySelector('a'),\n link3: cols.col3?.querySelector('a')\n };\n\n // Extract comment ID safely\n const commentId = links.link1?.href ?\n new URL(links.link1.href).searchParams.get('commid') : null;\n const cmid = links.link1?.href ?\n new URL(links.link1.href).searchParams.get('id') : M.cfg.contextInstanceId;\n // Extract user ID based on action\n let userId = null;\n let userLink = null;\n\n switch (action) {\n case 'overviewquestions':\n userLink = links.link2;\n break;\n case 'overviewanswers':\n userLink = links.link3;\n break;\n default:\n userId = userid;\n }\n\n if (userLink?.href) {\n try {\n userId = new URL(userLink.href).searchParams.get('id');\n } catch (e) {\n window.console.warn('Error parsing user URL:', e);\n }\n }\n\n getCursiveAnalytics(userId, commentId, cmid, cols.col1);\n });\n }\n\n /**\n * Handles the submission and cancellation of comments\n * @param {Event} e - The click event object\n * @description When comment is submitted or cancelled:\n * - Removes 'isEditing' flag from localStorage\n * - Sets pendingSubmit flag appropriately (true for submit, false for cancel)\n */\n function handleSubmit(e) {\n if (e.target.id === 'commentSubmit') {\n localStorage.removeItem('isEditing');\n buttonElement = e.target.value;\n pendingSubmit = true;\n }\n if (e.target.id === 'commentCancel') {\n localStorage.removeItem('isEditing');\n pendingSubmit = false;\n }\n }\n\n const updateEntries = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n return response;\n } catch (error) {\n window.console.error('updating Entries:', error);\n throw error;\n }\n };\n\n /**\n * Extracts the resource ID from a comment ID and updates entries if submission is pending\n * @param {string} id - The ID string to extract resource ID from, expected format: 'prefix_number'\n * @description This function:\n * 1. Parses the resource ID from the given ID string\n * 2. If resource ID exists and there's a pending submission:\n * - Resets the pending submission flag\n * - Constructs arguments with context info\n * - Calls updateEntries to process the PDF annotation\n */\n function extractResourceId(id) {\n\n // Prevent updating ID while editing a existing entry.\n if (buttonElement === 'Save') {\n\n pendingSubmit = false;\n return;\n }\n\n let resourceId = parseInt(id?.split('_')[1]);\n if (resourceId && pendingSubmit) {\n pendingSubmit = false;\n let args = {\n cmid: M.cfg.contextInstanceId,\n userid: M.cfg.userId ?? 0,\n courseid: M.cfg.courseId,\n modulename: moduleName,\n resourceid: resourceId\n };\n updateEntries('cursive_update_pdf_annote_id', args);\n }\n }\n\n /**\n * Retrieves and displays cursive analytics for a given resource\n * @param {number} userid - The ID of the user\n * @param {number} resourceid - The ID of the resource to get analytics for\n * @param {number} cmid - The course module ID\n * @param {HTMLElement} place - The DOM element where analytics should be placed\n * @description This function:\n * 1. Makes an AJAX call to get forum comment data\n * 2. Creates and inserts analytics/replay buttons\n * 3. Sets up analytics events and modal functionality\n * 4. Handles both API key and non-API key scenarios\n */\n function getCursiveAnalytics(userid, resourceid, cmid, place) {\n let args = {id: resourceid, modulename: \"pdfannotator\", cmid: cmid};\n let methodname = 'cursive_get_forum_comment_link';\n let com = call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n analyticButtonDiv.append(replayButton(resourceid));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, resourceid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n place.insertAdjacentElement('afterend', analyticsColumn);\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: resourceid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(resourceid, context, '', replayInstances, authIcon);\n myEvents.analytics(resourceid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(resourceid, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(resourceid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","comments","hasApiKey","userid","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","document","getElementById","innerHTML","catch","e","console","error","container","querySelector","overviewTable","addEventListener","target","id","localStorage","removeItem","buttonElement","value","pendingSubmit","moduleName","body","split","MutationObserver","lastChild","_container$lastChild","resourceId","parseInt","async","methodname","args","updateEntries","cmid","M","cfg","contextInstanceId","userId","courseid","courseId","modulename","resourceid","extractResourceId","observe","subtree","childList","newChild","createElement","textContent","insertAdjacentElement","rows","querySelectorAll","action","URL","location","href","searchParams","get","forEach","row","cols","col1","col2","col3","links","link1","_cols$col","link2","_cols$col2","link3","_cols$col3","commentId","userLink","_userLink","warn","place","com","done","json","data","JSON","parse","filename","analyticButtonDiv","analyticsColumn","append","effort_ratio","dataset","region","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","templates","checkDiff","file_id","replyWriting","fail","getCursiveAnalytics","setReplayButton"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/autosaver.min.js b/amd/build/autosaver.min.js index 5c4badae..93db953f 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","jquery","tiny_cursive/common","tiny_cursive/cursive_autosave","tiny_cursive/document_view"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_jquery,_common,_cursive_autosave,_document_view){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,_jquery=_interopRequireDefault(_jquery),_cursive_autosave=_interopRequireDefault(_cursive_autosave),_document_view=_interopRequireDefault(_document_view);_exports.register=(editor,interval,userId,hasApiKey,MODULES,Rubrics,submission,quizInfo,pasteSetting)=>{var isStudent=!(0,_jquery.default)("#body").hasClass("teacher_admin"),intervention=(0,_jquery.default)("#body").hasClass("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="",questionid=0,quizSubmit=(0,_jquery.default)("#mod_quiz-next-nav");let assignSubmit=(0,_jquery.default)("#id_submitbutton");var syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let aiContents=[];var isFullScreen=!1,user=null;let ur=window.location.href,parm=new URL(ur),modulesInfo=function(ur,parm,MODULES){if(function(){localStorage.getItem("sbTitle")||Promise.all([(0,_str.get_string)("assignment","tiny_cursive"),(0,_str.get_string)("discussion","tiny_cursive"),(0,_str.get_string)("pluginname","mod_quiz"),(0,_str.get_string)("pluginname","mod_lesson"),(0,_str.get_string)("description","tiny_cursive")]).then((function(strings){return localStorage.setItem("sbTitle",JSON.stringify(strings))})).catch((error=>window.console.error(error)));localStorage.getItem("docSideBar")||Promise.all([(0,_str.get_string)("details","tiny_cursive"),(0,_str.get_string)("student_info","tiny_cursive"),(0,_str.get_string)("progress","tiny_cursive"),(0,_str.get_string)("description","tiny_cursive"),(0,_str.get_string)("replyingto","tiny_cursive"),(0,_str.get_string)("answeringto","tiny_cursive"),(0,_str.get_string)("importantdates","tiny_cursive"),(0,_str.get_string)("rubrics","tiny_cursive"),(0,_str.get_string)("submission_status","tiny_cursive"),(0,_str.get_string)("status","tiny_cursive"),(0,_str.get_string)("draft","tiny_cursive"),(0,_str.get_string)("draftnot","tiny_cursive"),(0,_str.get_string)("last_modified","tiny_cursive"),(0,_str.get_string)("gradings","tiny_cursive"),(0,_str.get_string)("gradenot","tiny_cursive"),(0,_str.get_string)("word_count","tiny_cursive"),(0,_str.get_string)("timeleft","tiny_cursive"),(0,_str.get_string)("nolimit","tiny_cursive"),(0,_str.get_string)("name","tiny_cursive"),(0,_str.get_string)("userename","tiny_cursive"),(0,_str.get_string)("course","tiny_cursive"),(0,_str.get_string)("opened","tiny_cursive"),(0,_str.get_string)("due","tiny_cursive"),(0,_str.get_string)("overdue","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive"),(0,_str.get_string)("savechanges","tiny_cursive"),(0,_str.get_string)("subjectnot","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive")]).then((function(strings){return localStorage.setItem("docSideBar",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}(),!MODULES.some((module=>ur.includes(module))))return!1;resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt");null===resourceId&&(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 checkIsPdfAnnotator(),{resourceId:resourceId,name:modulename}}(ur,parm,MODULES);var resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,errorAlert=!0;let PASTE_SETTING=pasteSetting||"allow",shouldBlockPaste=!1,isPasteAllowed=!1;ur.includes("pdfannotator")&&document.addEventListener("click",(e=>{if("dropdown-item comment-edit-a"===e.target.className){let id=e.target.id;resourceId=id.replace("editButton",""),localStorage.setItem("isEditing","1")}"commentSubmit"===e.target.id&&syncData()}));const postOne=async(methodname,args)=>{try{const response=await(0,_ajax.call)([{methodname:methodname,args:args}])[0];return response&&setTimeout((()=>{_cursive_autosave.default.updateSavingState("saved")}),1e3),response}catch(error){throw _cursive_autosave.default.updateSavingState("offline"),window.console.error("Error in postOne:",error),error}};(0,_ajax.call)([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[userid]}}])[0].done((response=>{user=response[0]})).fail((ex=>{window.console.error("Error fetching user data:",ex)})),assignSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{assignSubmit.off("click").click()})):assignSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")})),quizSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{quizSubmit.off("click").click()})):quizSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")}));const getModal=()=>{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();""===number||null==number?(editor.execCommand("Undo"),(0,_str.get_string)("pastewarning","tiny_cursive").then((str=>alert(str)))):editor.execCommand("Paste"),postOne("cursive_user_comments",{modulename:modulename,cmid:cmid,resourceid:resourceId,courseid:courseid,usercomment:number,timemodified:Date.now(),editorid:editorid||""}),lastEvent="save",modal.destroy()})),modal.getRoot().on(_modal_events.cancel,(function(){editor.execCommand("Undo"),lastEvent="cancel"})),modal.getRoot().on(_modal_events.hidden,(function(){"cancel"!=lastEvent&&"save"!=lastEvent&&editor.execCommand("Undo")})),modal}))})).catch((error=>window.console.error(error)))},sendKeyEvent=(events,editor)=>{if(ed=editor,event=events,filename=`${userid}_${resourceId}_${cmid}_${modulename}_attempt`,"quiz"===modulename&&(questionid=editorid.split(":")[1].split("_")[0],filename=`${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`),localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,editor.key=function(editor){switch(editor.button){case 0:return"left";case 1:return"middle";case 2:return"right"}return null}(editor),editor.keyCode=editor.button}function getCaretPosition(){let skip=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{if(!editor||!editor.selection)return{caretPosition:0,rePosition:0};const range=editor.selection.getRng(),body=editor.getBody(),preCaretRange=range.cloneRange();preCaretRange.selectNodeContents(body),preCaretRange.setEnd(range.endContainer,range.endOffset);const fragment=preCaretRange.cloneContents(),tempDiv=document.createElement("div");tempDiv.appendChild(fragment);let textBeforeCursor=tempDiv.innerText||"";const endContainer=range.endContainer;0===range.endOffset&&endContainer.nodeType===Node.ELEMENT_NODE&&editor.dom.isBlock(endContainer)&&endContainer.previousSibling&&(textBeforeCursor+="\n");const blockElements=tempDiv.querySelectorAll("p, div, h1, h2, h3, h4, h5, h6, li");let emptyBlockCount=0;blockElements.forEach((block=>{""===(block.innerText||block.textContent||"").trim()&&1===block.childNodes.length&&"BR"===block.childNodes[0].nodeName&&emptyBlockCount++})),emptyBlockCount>0&&(textBeforeCursor+="\n".repeat(emptyBlockCount));const absolutePosition=textBeforeCursor.length;if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey=`${userid}_${resourceId}_${cmid}_position`;let storedPos=parseInt(sessionStorage.getItem(storageKey),10);return isNaN(storedPos)&&(storedPos=0),storedPos++,lastCaretPos=storedPos,sessionStorage.setItem(storageKey,storedPos),{caretPosition:storedPos,rePosition:absolutePosition}}catch(e){return window.console.warn("Error getting caret position:",e),{caretPosition:lastCaretPos||1,rePosition:0}}}async function syncData(){checkIsPdfAnnotator();let data=localStorage.getItem(filename);if(data&&0!==data.length){localStorage.removeItem(filename),editor.fire("change");let originalText=editor.getContent({format:"text"});originalText||(originalText=function(editor){let editorId=null==editor?void 0:editor.id;if(editorId){var _iframe$contentDocume,_iframe$contentWindow,_iframe$contentWindow2;let iframe=document.querySelector(`#${editorId}_ifr`),iframeBody=(null===(_iframe$contentDocume=iframe.contentDocument)||void 0===_iframe$contentDocume?void 0:_iframe$contentDocume.body)||(null===(_iframe$contentWindow=iframe.contentWindow)||void 0===_iframe$contentWindow||null===(_iframe$contentWindow2=_iframe$contentWindow.document)||void 0===_iframe$contentWindow2?void 0:_iframe$contentWindow2.body);return null==iframeBody?void 0:iframeBody.textContent}return""}(editor));try{return _cursive_autosave.default.updateSavingState("saving"),await postOne("cursive_write_local_to_json",{key:ed.key,event:event,keyCode:ed.keyCode,resourceId:resourceId,cmid:cmid,modulename:modulename,editorid:editorid,json_data:data,originalText:originalText})}catch(error){window.console.error("Error submitting data:",error)}}}function customTooltip(){try{const tooltipText=async function(){const[buttonTitle,buttonDes]=await Promise.all([(0,_str.get_string)("cursive:state:active","tiny_cursive"),(0,_str.get_string)("cursive:state:active:des","tiny_cursive")]);return{buttonTitle:buttonTitle,buttonDes:buttonDes}}(),menubarDiv=document.querySelectorAll('div[role="menubar"].tox-menubar');let classArray=[];menubarDiv.length&&menubarDiv.forEach((function(element,index){let className="cursive-menu-"+(index+=1);element.classList.add(className),classArray.push(className)}));const cursiveIcon=document.createElement("img");cursiveIcon.src=hasApiKey?_common.iconUrl:_common.iconGrayUrl,cursiveIcon.setAttribute("class","tiny_cursive_StateButton"),cursiveIcon.style.display="inline-block",function(cursiveIcon,menubarDiv,classArray){if(!menubarDiv)return;for(let index in classArray){const rightWrapper=document.createElement("div"),imgWrapper=document.createElement("span"),iconClone=cursiveIcon.cloneNode(!0),targetMenu=document.querySelector("."+classArray[index]);let elementId="tiny_cursive_StateIcon"+index;rightWrapper.style.cssText="\n margin-left: auto;\n display: flex;\n align-items: center;\n ",imgWrapper.id=elementId,imgWrapper.style.marginLeft=".2rem",imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper);let moduleIds={resourceId:resourceId,cmid:cmid,modulename:modulename,questionid:questionid,userid:userid,courseid:courseid};if(!isFullScreen||"assign"!==modulename&&"forum"!==modulename&&"lesson"!==modulename)if(isFullScreen&&"quiz"===modulename){var _editor$container,_editor$container$chi,_editor$container$chi2,_editor$container$chi3,_editor$container2;let existingElement=null===(_editor$container=editor.container)||void 0===_editor$container||null===(_editor$container$chi=_editor$container.childNodes[1])||void 0===_editor$container$chi||null===(_editor$container$chi2=_editor$container$chi.childNodes[0])||void 0===_editor$container$chi2||null===(_editor$container$chi3=_editor$container$chi2.childNodes[0])||void 0===_editor$container$chi3?void 0:_editor$container$chi3.childNodes[7],newHeader=null===(_editor$container2=editor.container)||void 0===_editor$container2?void 0:_editor$container2.childNodes[0];existingElement&&existingElement.remove(),newHeader&&!newHeader.querySelector("span[id*=tiny_cursive_StateIcon]")&&(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{var _editor$container3,_editor$container3$ch,_editor$container3$ch2;let menubar=null==editor||null===(_editor$container3=editor.container)||void 0===_editor$container3||null===(_editor$container3$ch=_editor$container3.children[0])||void 0===_editor$container3$ch||null===(_editor$container3$ch2=_editor$container3$ch.childNodes[0])||void 0===_editor$container3$ch2?void 0:_editor$container3$ch2.childNodes[0];if(targetMenu&&!targetMenu.querySelector(`#${elementId}`)&&targetMenu.appendChild(rightWrapper),"quiz"===modulename&&menubar){let wrapper=menubar.querySelector('span[id*="tiny_cursive_StateIcon"]');wrapper&&(_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,null==wrapper?void 0:wrapper.parentElement,moduleIds,isFullScreen))}else _cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{let existsElement=document.querySelector('.tox-menubar[class*="cursive-menu-"] > div');existsElement&&existsElement.remove(),document.querySelector(`#${elementId}`)||(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId=`tiny_cursive_tooltip${index}`;tooltipText.then((text=>setTooltip(text,document.querySelector(`#${elementId}`),tooltipId))).catch((error=>window.console.error(error))),(0,_jquery.default)(`#${elementId}`).on("mouseenter",(function(){(0,_jquery.default)(this).css("position","relative"),(0,_jquery.default)(`#${tooltipId}`).css(_common.tooltipCss)})),(0,_jquery.default)(`#${elementId}`).on("mouseleave",(function(){(0,_jquery.default)(`#${tooltipId}`).css("display","none")}))}}catch(error){window.console.error("Error setting up custom tooltip:",error)}}function setTooltip(text,cursiveIcon,tooltipId){if(!document.querySelector(`#${tooltipId}`)&&cursiveIcon){const tooltipSpan=document.createElement("span"),description=document.createElement("span"),linebreak=document.createElement("br"),tooltipTitle=document.createElement("strong");tooltipSpan.style.display="none",tooltipTitle.textContent=text.buttonTitle,tooltipTitle.style.fontSize="16px",tooltipTitle.style.fontWeight="bold",description.textContent=text.buttonDes,description.style.fontSize="14px",tooltipSpan.id=tooltipId,tooltipSpan.classList.add("shadow"),tooltipSpan.appendChild(tooltipTitle),tooltipSpan.appendChild(linebreak),tooltipSpan.appendChild(description),cursiveIcon.appendChild(tooltipSpan)}}function checkIsPdfAnnotator(){ur.includes("pdfannotator")&&(resourceId="id_pdfannotator_content"!==editor.id&&parseInt(localStorage.getItem("isEditing"))?parseInt(null==editor?void 0:editor.id.replace("editarea","")):0)}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyUp",editor)})),editor.on("Paste",(async e=>{customTooltip();const pastedContent=(e.clipboardData||e.originalEvent.clipboardData).getData("text");if(!pastedContent)return;const trimmedPastedContent=pastedContent.trim(),lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&trimmedPastedContent===lastCopyCutContent;if(isStudent&&intervention){if("block"===PASTE_SETTING)return isFromOwnEditor?(shouldBlockPaste=!1,void(isPasteAllowed=!0)):(e.preventDefault(),shouldBlockPaste=!0,isPasteAllowed=!1,e.stopPropagation(),e.stopImmediatePropagation(),(0,_str.get_string)("paste_blocked","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error))),void setTimeout((()=>{isPasteAllowed=!0,shouldBlockPaste=!1}),100));if("cite_source"===PASTE_SETTING)return isFromOwnEditor||(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),getModal()),void(isPasteAllowed=!0)}isPasteAllowed=!0})),editor.on("Redo",(async e=>{customTooltip(),isStudent&&intervention&&getModal()})),editor.on("keyDown",(editor=>{customTooltip();if(("v"===editor.key||"V"===editor.key)&&(editor.ctrlKey||editor.metaKey)&&isStudent&&intervention&&"block"===PASTE_SETTING&&!isPasteAllowed)return void setTimeout((()=>{isPasteAllowed=!0}),100);let position=getCaretPosition();editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyDown",editor)})),editor.on("Cut",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("Copy",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("mouseDown",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)}),0)})),editor.on("mouseUp",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)}),10)})),editor.on("init",(()=>{customTooltip(),localStorage.removeItem("lastCopyCutContent")})),editor.on("SetContent",(()=>{customTooltip()})),editor.on("FullscreenStateChanged",(e=>{let view=new _document_view.default(user,Rubrics,submission,modulename,editor,quizInfo);isFullScreen=e.state;try{e.state?view.fullPageMode():view.normalMode()}catch(error){errorAlert&&(errorAlert=!1,(0,_str.get_string)("fullmodeerror","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))),view.normalMode(),window.console.error("Error ResizeEditor event:",error)}})),editor.on("execcommand",(function(e){if("mceInsertContent"===e.command){const contentObj=e.value,isPaste=contentObj&&"object"==typeof contentObj&&!0===contentObj.paste;let insertedContent=contentObj.content||contentObj,tempDiv=document.createElement("div");tempDiv.innerHTML=insertedContent;let text=tempDiv.textContent||tempDiv.innerText||"",pastedText=tempDiv.textContent||tempDiv.innerText||"",position=getCaretPosition(!0);if(editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,isPaste){if(shouldBlockPaste)return shouldBlockPaste=!1,e.preventDefault(),void editor.undoManager.undo();const lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&pastedText.trim()===lastCopyCutContent;if(isStudent&&intervention&&"block"===PASTE_SETTING&&!isFromOwnEditor)return isPasteAllowed=!1,void editor.undoManager.undo();sendKeyEvent("Paste",{key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition,pastedContent:pastedText,srcElement:{baseURI:window.location.href}})}else aiContents.push(text),sendKeyEvent("aiInsert",{key:"ai",keyCode:0,caretPosition:editor.caretPosition,rePosition:editor.rePosition,aiContent:text,srcElement:{baseURI:window.location.href}})}})),editor.on("input",(function(e){let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition;let aiContent=e.data;("insertReplacementText"===e.inputType||"insertText"===e.inputType&&aiContent&&aiContent.length>1)&&(aiContents.push(aiContent),e.key="ai",e.keyCode=0,e.caretPosition=position.caretPosition,e.rePosition=position.rePosition,e.aiContent=aiContent,sendKeyEvent("aiInsert",e))})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}})); +define("tiny_cursive/autosaver",["exports","core/ajax","core/modal_factory","core/str","core/modal_events","jquery","tiny_cursive/common","tiny_cursive/cursive_autosave","tiny_cursive/document_view"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_jquery,_common,_cursive_autosave,_document_view){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,_jquery=_interopRequireDefault(_jquery),_cursive_autosave=_interopRequireDefault(_cursive_autosave),_document_view=_interopRequireDefault(_document_view);_exports.register=(editor,interval,userId,hasApiKey,MODULES,Rubrics,submission,quizInfo,pasteSetting)=>{var isStudent=!(0,_jquery.default)("#body").hasClass("teacher_admin"),intervention=(0,_jquery.default)("#body").hasClass("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="",questionid=0,quizSubmit=(0,_jquery.default)("#mod_quiz-next-nav");let assignSubmit=(0,_jquery.default)("#id_submitbutton");var syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let aiContents=[];var isFullScreen=!1,user=null;let ur=window.location.href,parm=new URL(ur),modulesInfo=function(ur,parm,MODULES){if(function(){localStorage.getItem("sbTitle")||Promise.all([(0,_str.get_string)("assignment","tiny_cursive"),(0,_str.get_string)("discussion","tiny_cursive"),(0,_str.get_string)("pluginname","mod_quiz"),(0,_str.get_string)("pluginname","mod_lesson"),(0,_str.get_string)("description","tiny_cursive")]).then((function(strings){return localStorage.setItem("sbTitle",JSON.stringify(strings))})).catch((error=>window.console.error(error)));localStorage.getItem("docSideBar")||Promise.all([(0,_str.get_string)("details","tiny_cursive"),(0,_str.get_string)("student_info","tiny_cursive"),(0,_str.get_string)("progress","tiny_cursive"),(0,_str.get_string)("description","tiny_cursive"),(0,_str.get_string)("replyingto","tiny_cursive"),(0,_str.get_string)("answeringto","tiny_cursive"),(0,_str.get_string)("importantdates","tiny_cursive"),(0,_str.get_string)("rubrics","tiny_cursive"),(0,_str.get_string)("submission_status","tiny_cursive"),(0,_str.get_string)("status","tiny_cursive"),(0,_str.get_string)("draft","tiny_cursive"),(0,_str.get_string)("draftnot","tiny_cursive"),(0,_str.get_string)("last_modified","tiny_cursive"),(0,_str.get_string)("gradings","tiny_cursive"),(0,_str.get_string)("gradenot","tiny_cursive"),(0,_str.get_string)("word_count","tiny_cursive"),(0,_str.get_string)("timeleft","tiny_cursive"),(0,_str.get_string)("nolimit","tiny_cursive"),(0,_str.get_string)("name","tiny_cursive"),(0,_str.get_string)("userename","tiny_cursive"),(0,_str.get_string)("course","tiny_cursive"),(0,_str.get_string)("opened","tiny_cursive"),(0,_str.get_string)("due","tiny_cursive"),(0,_str.get_string)("overdue","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive"),(0,_str.get_string)("savechanges","tiny_cursive"),(0,_str.get_string)("subjectnot","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive")]).then((function(strings){return localStorage.setItem("docSideBar",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}(),!MODULES.some((module=>ur.includes(module))))return!1;resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt");null===resourceId&&(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 checkIsPdfAnnotator(),{resourceId:resourceId,name:modulename}}(ur,parm,MODULES);var resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,errorAlert=!0;let PASTE_SETTING=pasteSetting||"allow",shouldBlockPaste=!1,isPasteAllowed=!1;ur.includes("pdfannotator")&&document.addEventListener("click",(e=>{if("dropdown-item comment-edit-a"===e.target.className){let id=e.target.id;resourceId=id.replace("editButton",""),localStorage.setItem("isEditing","1")}"commentSubmit"===e.target.id&&syncData()}));const postOne=async(methodname,args)=>{try{const response=await(0,_ajax.call)([{methodname:methodname,args:args}])[0];return response&&setTimeout((()=>{_cursive_autosave.default.updateSavingState("saved")}),1e3),response}catch(error){throw _cursive_autosave.default.updateSavingState("offline"),window.console.error("Error in postOne:",error),error}};(0,_ajax.call)([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[userid]}}])[0].done((response=>{user=response[0]})).fail((ex=>{window.console.error("Error fetching user data:",ex)})),assignSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{assignSubmit.off("click").click()})):assignSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")})),quizSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{quizSubmit.off("click").click()})):quizSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")}));const getModal=()=>{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();""===number||null==number?(editor.execCommand("Undo"),(0,_str.get_string)("pastewarning","tiny_cursive").then((str=>alert(str)))):editor.execCommand("Paste"),postOne("cursive_user_comments",{modulename:modulename,cmid:cmid,resourceid:resourceId,courseid:courseid,usercomment:number,timemodified:Date.now(),editorid:editorid||""}),lastEvent="save",modal.destroy()})),modal.getRoot().on(_modal_events.cancel,(function(){editor.execCommand("Undo"),lastEvent="cancel"})),modal.getRoot().on(_modal_events.hidden,(function(){"cancel"!=lastEvent&&"save"!=lastEvent&&editor.execCommand("Undo")})),modal}))})).catch((error=>window.console.error(error)))},sendKeyEvent=(events,editor)=>{if(ed=editor,event=events,filename="".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")),localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,editor.key=function(editor){switch(editor.button){case 0:return"left";case 1:return"middle";case 2:return"right"}return null}(editor),editor.keyCode=editor.button}function getCaretPosition(){let skip=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{if(!editor||!editor.selection)return{caretPosition:0,rePosition:0};const range=editor.selection.getRng(),body=editor.getBody(),preCaretRange=range.cloneRange();preCaretRange.selectNodeContents(body),preCaretRange.setEnd(range.endContainer,range.endOffset);const fragment=preCaretRange.cloneContents(),tempDiv=document.createElement("div");tempDiv.appendChild(fragment);let textBeforeCursor=tempDiv.innerText||"";const endContainer=range.endContainer;0===range.endOffset&&endContainer.nodeType===Node.ELEMENT_NODE&&editor.dom.isBlock(endContainer)&&endContainer.previousSibling&&(textBeforeCursor+="\n");const blockElements=tempDiv.querySelectorAll("p, div, h1, h2, h3, h4, h5, h6, li");let emptyBlockCount=0;blockElements.forEach((block=>{""===(block.innerText||block.textContent||"").trim()&&1===block.childNodes.length&&"BR"===block.childNodes[0].nodeName&&emptyBlockCount++})),emptyBlockCount>0&&(textBeforeCursor+="\n".repeat(emptyBlockCount));const absolutePosition=textBeforeCursor.length;if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey="".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:lastCaretPos||1,rePosition:0}}}async function syncData(){checkIsPdfAnnotator();let data=localStorage.getItem(filename);if(data&&0!==data.length){localStorage.removeItem(filename),editor.fire("change");let originalText=editor.getContent({format:"text"});originalText||(originalText=function(editor){let editorId=null==editor?void 0:editor.id;if(editorId){var _iframe$contentDocume,_iframe$contentWindow,_iframe$contentWindow2;let iframe=document.querySelector("#".concat(editorId,"_ifr")),iframeBody=(null===(_iframe$contentDocume=iframe.contentDocument)||void 0===_iframe$contentDocume?void 0:_iframe$contentDocume.body)||(null===(_iframe$contentWindow=iframe.contentWindow)||void 0===_iframe$contentWindow||null===(_iframe$contentWindow2=_iframe$contentWindow.document)||void 0===_iframe$contentWindow2?void 0:_iframe$contentWindow2.body);return null==iframeBody?void 0:iframeBody.textContent}return""}(editor));try{return _cursive_autosave.default.updateSavingState("saving"),await postOne("cursive_write_local_to_json",{key:ed.key,event:event,keyCode:ed.keyCode,resourceId:resourceId,cmid:cmid,modulename:modulename,editorid:editorid,json_data:data,originalText:originalText})}catch(error){window.console.error("Error submitting data:",error)}}}function customTooltip(){try{const tooltipText=async function(){const[buttonTitle,buttonDes]=await Promise.all([(0,_str.get_string)("cursive:state:active","tiny_cursive"),(0,_str.get_string)("cursive:state:active:des","tiny_cursive")]);return{buttonTitle:buttonTitle,buttonDes:buttonDes}}(),menubarDiv=document.querySelectorAll('div[role="menubar"].tox-menubar');let classArray=[];menubarDiv.length&&menubarDiv.forEach((function(element,index){let className="cursive-menu-"+(index+=1);element.classList.add(className),classArray.push(className)}));const cursiveIcon=document.createElement("img");cursiveIcon.src=hasApiKey?_common.iconUrl:_common.iconGrayUrl,cursiveIcon.setAttribute("class","tiny_cursive_StateButton"),cursiveIcon.style.display="inline-block",function(cursiveIcon,menubarDiv,classArray){if(!menubarDiv)return;for(let index in classArray){const rightWrapper=document.createElement("div"),imgWrapper=document.createElement("span"),iconClone=cursiveIcon.cloneNode(!0),targetMenu=document.querySelector("."+classArray[index]);let elementId="tiny_cursive_StateIcon"+index;rightWrapper.style.cssText="\n margin-left: auto;\n display: flex;\n align-items: center;\n ",imgWrapper.id=elementId,imgWrapper.style.marginLeft=".2rem",imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper);let moduleIds={resourceId:resourceId,cmid:cmid,modulename:modulename,questionid:questionid,userid:userid,courseid:courseid};if(!isFullScreen||"assign"!==modulename&&"forum"!==modulename&&"lesson"!==modulename)if(isFullScreen&&"quiz"===modulename){var _editor$container,_editor$container$chi,_editor$container$chi2,_editor$container$chi3,_editor$container2;let existingElement=null===(_editor$container=editor.container)||void 0===_editor$container||null===(_editor$container$chi=_editor$container.childNodes[1])||void 0===_editor$container$chi||null===(_editor$container$chi2=_editor$container$chi.childNodes[0])||void 0===_editor$container$chi2||null===(_editor$container$chi3=_editor$container$chi2.childNodes[0])||void 0===_editor$container$chi3?void 0:_editor$container$chi3.childNodes[7],newHeader=null===(_editor$container2=editor.container)||void 0===_editor$container2?void 0:_editor$container2.childNodes[0];existingElement&&existingElement.remove(),newHeader&&!newHeader.querySelector("span[id*=tiny_cursive_StateIcon]")&&(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{var _editor$container3,_editor$container3$ch,_editor$container3$ch2;let menubar=null==editor||null===(_editor$container3=editor.container)||void 0===_editor$container3||null===(_editor$container3$ch=_editor$container3.children[0])||void 0===_editor$container3$ch||null===(_editor$container3$ch2=_editor$container3$ch.childNodes[0])||void 0===_editor$container3$ch2?void 0:_editor$container3$ch2.childNodes[0];if(targetMenu&&!targetMenu.querySelector("#".concat(elementId))&&targetMenu.appendChild(rightWrapper),"quiz"===modulename&&menubar){let wrapper=menubar.querySelector('span[id*="tiny_cursive_StateIcon"]');wrapper&&(_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,null==wrapper?void 0:wrapper.parentElement,moduleIds,isFullScreen))}else _cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{let existsElement=document.querySelector('.tox-menubar[class*="cursive-menu-"] > div');existsElement&&existsElement.remove(),document.querySelector("#".concat(elementId))||(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId="tiny_cursive_tooltip".concat(index);tooltipText.then((text=>setTooltip(text,document.querySelector("#".concat(elementId)),tooltipId))).catch((error=>window.console.error(error))),(0,_jquery.default)("#".concat(elementId)).on("mouseenter",(function(){(0,_jquery.default)(this).css("position","relative"),(0,_jquery.default)("#".concat(tooltipId)).css(_common.tooltipCss)})),(0,_jquery.default)("#".concat(elementId)).on("mouseleave",(function(){(0,_jquery.default)("#".concat(tooltipId)).css("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 checkIsPdfAnnotator(){ur.includes("pdfannotator")&&(resourceId="id_pdfannotator_content"!==editor.id&&parseInt(localStorage.getItem("isEditing"))?parseInt(null==editor?void 0:editor.id.replace("editarea","")):0)}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyUp",editor)})),editor.on("Paste",(async e=>{customTooltip();const pastedContent=(e.clipboardData||e.originalEvent.clipboardData).getData("text");if(!pastedContent)return;const trimmedPastedContent=pastedContent.trim(),lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&trimmedPastedContent===lastCopyCutContent;if(isStudent&&intervention){if("block"===PASTE_SETTING)return isFromOwnEditor?(shouldBlockPaste=!1,void(isPasteAllowed=!0)):(e.preventDefault(),shouldBlockPaste=!0,isPasteAllowed=!1,e.stopPropagation(),e.stopImmediatePropagation(),(0,_str.get_string)("paste_blocked","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error))),void setTimeout((()=>{isPasteAllowed=!0,shouldBlockPaste=!1}),100));if("cite_source"===PASTE_SETTING)return isFromOwnEditor||(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),getModal()),void(isPasteAllowed=!0)}isPasteAllowed=!0})),editor.on("Redo",(async e=>{customTooltip(),isStudent&&intervention&&getModal()})),editor.on("keyDown",(editor=>{customTooltip();if(("v"===editor.key||"V"===editor.key)&&(editor.ctrlKey||editor.metaKey)&&isStudent&&intervention&&"block"===PASTE_SETTING&&!isPasteAllowed)return void setTimeout((()=>{isPasteAllowed=!0}),100);let position=getCaretPosition();editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyDown",editor)})),editor.on("Cut",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("Copy",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("mouseDown",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)}),0)})),editor.on("mouseUp",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)}),10)})),editor.on("init",(()=>{customTooltip(),localStorage.removeItem("lastCopyCutContent")})),editor.on("SetContent",(()=>{customTooltip()})),editor.on("FullscreenStateChanged",(e=>{let view=new _document_view.default(user,Rubrics,submission,modulename,editor,quizInfo);isFullScreen=e.state;try{e.state?view.fullPageMode():view.normalMode()}catch(error){errorAlert&&(errorAlert=!1,(0,_str.get_string)("fullmodeerror","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))),view.normalMode(),window.console.error("Error ResizeEditor event:",error)}})),editor.on("execcommand",(function(e){if("mceInsertContent"===e.command){const contentObj=e.value,isPaste=contentObj&&"object"==typeof contentObj&&!0===contentObj.paste;let insertedContent=contentObj.content||contentObj,tempDiv=document.createElement("div");tempDiv.innerHTML=insertedContent;let text=tempDiv.textContent||tempDiv.innerText||"",pastedText=tempDiv.textContent||tempDiv.innerText||"",position=getCaretPosition(!0);if(editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,isPaste){if(shouldBlockPaste)return shouldBlockPaste=!1,e.preventDefault(),void editor.undoManager.undo();const lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&pastedText.trim()===lastCopyCutContent;if(isStudent&&intervention&&"block"===PASTE_SETTING&&!isFromOwnEditor)return isPasteAllowed=!1,void editor.undoManager.undo();sendKeyEvent("Paste",{key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition,pastedContent:pastedText,srcElement:{baseURI:window.location.href}})}else aiContents.push(text),sendKeyEvent("aiInsert",{key:"ai",keyCode:0,caretPosition:editor.caretPosition,rePosition:editor.rePosition,aiContent:text,srcElement:{baseURI:window.location.href}})}})),editor.on("input",(function(e){let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition;let aiContent=e.data;("insertReplacementText"===e.inputType||"insertText"===e.inputType&&aiContent&&aiContent.length>1)&&(aiContents.push(aiContent),e.key="ai",e.keyCode=0,e.caretPosition=position.caretPosition,e.rePosition=position.rePosition,e.aiContent=aiContent,sendKeyEvent("aiInsert",e))})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}})); //# sourceMappingURL=autosaver.min.js.map \ No newline at end of file diff --git a/amd/build/autosaver.min.js.map b/amd/build/autosaver.min.js.map index e0c9b4de..884dac5c 100644 --- a/amd/build/autosaver.min.js.map +++ b/amd/build/autosaver.min.js.map @@ -1 +1 @@ -{"version":3,"file":"autosaver.min.js","sources":["../src/autosaver.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module tiny_cursive/autosaver\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\nimport {call} from 'core/ajax';\nimport {create} from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\nimport {save, cancel, hidden} from 'core/modal_events';\nimport $ from 'jquery';\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\nimport Autosave from 'tiny_cursive/cursive_autosave';\nimport DocumentView from 'tiny_cursive/document_view';\nimport {call as getUser} from \"core/ajax\";\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\n\n var isStudent = !($('#body').hasClass('teacher_admin'));\n var intervention = $('#body').hasClass('intervention');\n var host = M.cfg.wwwroot;\n var userid = userId;\n var courseid = M.cfg.courseId;\n var editorid = editor?.id;\n var cmid = M.cfg.contextInstanceId;\n var ed = \"\";\n var event = \"\";\n var filename = \"\";\n var questionid = 0;\n var quizSubmit = $('#mod_quiz-next-nav');\n let assignSubmit = $('#id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let aiContents = [];\n var isFullScreen = false;\n var user = null;\n let ur = window.location.href;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n var resourceId = modulesInfo.resourceId;\n var modulename = modulesInfo.name;\n var errorAlert = true;\n let PASTE_SETTING = pasteSetting || 'allow';\n let shouldBlockPaste = false;\n let isPasteAllowed = false;\n\n if (ur.includes('pdfannotator')) {\n document.addEventListener('click', e => {\n if (e.target.className === \"dropdown-item comment-edit-a\") {\n let id = e.target.id;\n resourceId = id.replace('editButton', '');\n localStorage.setItem('isEditing', '1');\n }\n if (e.target.id === 'commentSubmit') {\n syncData();\n }\n });\n }\n\n const postOne = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n if (response) {\n setTimeout(() => {\n Autosave.updateSavingState('saved');\n }, 1000);\n }\n return response;\n } catch (error) {\n Autosave.updateSavingState('offline');\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n getUser([{\n methodname: 'core_user_get_users_by_field',\n args: {field: 'id', values: [userid]},\n }])[0].done(response => {\n user = response[0];\n }).fail((ex) => {\n window.console.error('Error fetching user data:', ex);\n });\n\n assignSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n assignSubmit.off('click').click();\n });\n } else {\n assignSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n quizSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n quizSubmit.off('click').click();\n });\n } else {\n quizSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n const getModal = () => {\n\n Promise.all([\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\n getString('tiny_cursive_placeholder', 'tiny_cursive')\n ]).then(function([title, titledes, placeholder]) {\n\n return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n })\n .done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n\n modal.getRoot().on(save, function() {\n\n var number = document.getElementById(\"inputUrl\").value.trim();\n\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n // eslint-disable-next-line\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\n }\n\n postOne('cursive_user_comments', {\n modulename: modulename,\n cmid: cmid,\n resourceid: resourceId,\n courseid: courseid,\n usercomment: number,\n timemodified: Date.now(),\n editorid: editorid ? editorid : \"\"\n });\n\n lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\n });\n\n modal.getRoot().on(hidden, function() {\n if (lastEvent != 'cancel' && lastEvent != 'save') {\n editor.execCommand('Undo');\n }\n });\n return modal;\n });\n }).catch(error => window.console.error(error));\n\n };\n\n const sendKeyEvent = (events, editor) => {\n ed = editor;\n event = events;\n\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\n editor.on('Paste', async(e) => {\n customTooltip();\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\n if (!pastedContent) {\n return;\n }\n // Trim both values for consistent comparison\n const trimmedPastedContent = pastedContent.trim();\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\n\n if (isStudent && intervention) {\n\n if (PASTE_SETTING === 'block') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n shouldBlockPaste = true;\n isPasteAllowed = false;\n e.stopPropagation();\n e.stopImmediatePropagation();\n getString('paste_blocked', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n setTimeout(() => {\n isPasteAllowed = true;\n shouldBlockPaste = false;\n }, 100);\n return;\n }\n shouldBlockPaste = false;\n isPasteAllowed = true;\n return;\n }\n if (PASTE_SETTING === 'cite_source') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n getModal(e);\n }\n isPasteAllowed = true;\n return;\n }\n }\n isPasteAllowed = true;\n });\n editor.on('Redo', async(e) => {\n customTooltip();\n if (isStudent && intervention) {\n getModal(e);\n }\n });\n editor.on('keyDown', (editor) => {\n customTooltip();\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\n (editor.ctrlKey || editor.metaKey);\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\n setTimeout(() => {\n isPasteAllowed = true;\n }, 100);\n return;\n }\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('mouseDown', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n }, 0);\n });\n editor.on('mouseUp', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n }, 10);\n });\n editor.on('init', () => {\n customTooltip();\n localStorage.removeItem('lastCopyCutContent');\n });\n editor.on('SetContent', () => {\n customTooltip();\n });\n editor.on('FullscreenStateChanged', (e) => {\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\n isFullScreen = e.state;\n try {\n if (!e.state) {\n view.normalMode();\n } else {\n view.fullPageMode();\n }\n } catch (error) {\n if (errorAlert) {\n errorAlert = false;\n getString('fullmodeerror', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n view.normalMode();\n window.console.error('Error ResizeEditor event:', error);\n }\n });\n\n editor.on('execcommand', function(e) {\n if (e.command === \"mceInsertContent\") {\n const contentObj = e.value;\n\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\n\n let insertedContent = contentObj.content || contentObj;\n let tempDiv = document.createElement('div');\n tempDiv.innerHTML = insertedContent;\n let text = tempDiv.textContent || tempDiv.innerText || '';\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\n\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n\n if (isPaste) {\n if (shouldBlockPaste) {\n shouldBlockPaste = false;\n e.preventDefault();\n editor.undoManager.undo();\n return;\n }\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\n\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\n isPasteAllowed = false;\n editor.undoManager.undo();\n return;\n }\n\n sendKeyEvent(\"Paste\", {\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n pastedContent: pastedText,\n srcElement: {baseURI: window.location.href}\n });\n } else {\n aiContents.push(text);\n\n sendKeyEvent(\"aiInsert\", {\n key: \"ai\",\n keyCode: 0,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n aiContent: text,\n srcElement: {baseURI: window.location.href}\n });\n }\n }\n });\n\n editor.on('input', function(e) {\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n let aiContent = e.data;\n\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\n\n aiContents.push(aiContent);\n\n e.key = \"ai\";\n e.keyCode = 0;\n e.caretPosition = position.caretPosition;\n e.rePosition = position.rePosition;\n e.aiContent = aiContent;\n\n sendKeyEvent(\"aiInsert\", e);\n }\n });\n\n\n /**\n * Constructs a mouse event object with caret position and button information\n * @param {Object} editor - The TinyMCE editor instance\n * @function constructMouseEvent\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\n */\n function constructMouseEvent(editor) {\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n editor.key = getMouseButton(editor);\n editor.keyCode = editor.button;\n }\n\n /**\n * Gets the string representation of a mouse button based on its numeric value\n * @param {Object} editor - The editor object containing button information\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\n */\n function getMouseButton(editor) {\n\n switch (editor.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n }\n return null;\n }\n\n /**\n * Gets the current caret position in the editor\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\n * @returns {Object} Object containing:\n * - caretPosition: Sequential position number stored in session\n * - rePosition: Absolute character offset from start of content\n * @throws {Error} Logs warning to console if error occurs during calculation\n */\n function getCaretPosition(skip = false) {\n try {\n if (!editor || !editor.selection) {\n return {caretPosition: 0, rePosition: 0};\n }\n\n const range = editor.selection.getRng();\n const body = editor.getBody();\n\n // Create a range from start of document to current caret\n const preCaretRange = range.cloneRange();\n preCaretRange.selectNodeContents(body);\n preCaretRange.setEnd(range.endContainer, range.endOffset);\n\n const fragment = preCaretRange.cloneContents();\n const tempDiv = document.createElement('div');\n tempDiv.appendChild(fragment);\n let textBeforeCursor = tempDiv.innerText || '';\n\n const endContainer = range.endContainer;\n const endOffset = range.endOffset;\n\n if (endOffset === 0 &&\n endContainer.nodeType === Node.ELEMENT_NODE &&\n editor.dom.isBlock(endContainer) &&\n endContainer.previousSibling) {\n textBeforeCursor += '\\n';\n }\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\n let emptyBlockCount = 0;\n blockElements.forEach(block => {\n const text = block.innerText || block.textContent || '';\n if (text.trim() === '' && block.childNodes.length === 1 &&\n block.childNodes[0].nodeName === 'BR') {\n emptyBlockCount++;\n }\n });\n\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\n if (emptyBlockCount > 0) {\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\n }\n\n const absolutePosition = textBeforeCursor.length;\n\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\n // Increment sequential caretPosition\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\n if (isNaN(storedPos)) {\n storedPos = 0;\n }\n storedPos++;\n lastCaretPos = storedPos;\n sessionStorage.setItem(storageKey, storedPos);\n\n return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\n }\n }\n\n\n /**\n * Synchronizes data from localStorage to server\n * @async\n * @function SyncData\n * @description Retrieves stored keypress data from localStorage and sends it to server\n * @returns {Promise} Returns response from server if data exists and is successfully sent\n * @throws {Error} Logs error to console if data submission fails\n */\n async function syncData() {\n checkIsPdfAnnotator();\n let data = localStorage.getItem(filename);\n\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n editor.fire('change');\n let originalText = editor.getContent({format: 'text'});\n if (!originalText) {\n originalText = getRawText(editor);\n }\n try {\n Autosave.updateSavingState('saving');\n // eslint-disable-next-line\n return await postOne('cursive_write_local_to_json', {\n key: ed.key,\n event: event,\n keyCode: ed.keyCode,\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n editorid: editorid,\n \"json_data\": data,\n originalText: originalText\n });\n } catch (error) {\n window.console.error('Error submitting data:', error);\n }\n }\n }\n\n /**\n * Gets the raw text content from a TinyMCE editor iframe\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {string} The raw text content of the editor body, or empty string if not found\n * @description Attempts to get the raw text content from the editor's iframe body by:\n * 1. Getting the editor ID\n * 2. Finding the associated iframe element\n * 3. Accessing the iframe's document body\n * 4. Returning the text content\n * Returns empty string if any step fails\n */\n function getRawText(editor) {\n let editorId = editor?.id;\n if (editorId) {\n let iframe = document.querySelector(`#${editorId}_ifr`);\n let iframeBody = iframe.contentDocument?.body || iframe.contentWindow?.document?.body;\n return iframeBody?.textContent;\n }\n return \"\";\n }\n\n /**\n * Sets up custom tooltip functionality for the Cursive icon\n * Initializes tooltip text, positions the icon in the menubar,\n * and sets up mouse event handlers for showing/hiding the tooltip\n * @function customTooltip\n */\n function customTooltip() {\n try {\n const tooltipText = getTooltipText();\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\n let classArray = [];\n\n if (menubarDiv.length) {\n menubarDiv.forEach(function(element, index) {\n index += 1;\n let className = 'cursive-menu-' + index;\n element.classList.add(className);\n classArray.push(className);\n });\n }\n\n const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n\n cursiveState(cursiveIcon, menubarDiv, classArray);\n\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n\n $(`#${elementId}`).on('mouseenter', function() {\n $(this).css('position', 'relative');\n $(`#${tooltipId}`).css(tooltipCss);\n });\n\n $(`#${elementId}`).on('mouseleave', function() {\n $(`#${tooltipId}`).css('display', 'none');\n });\n }\n } catch (error) {\n window.console.error('Error setting up custom tooltip:', error);\n }\n }\n\n /**\n * Retrieves tooltip text strings from language files\n * @async\n * @function getTooltipText\n * @returns {Promise} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [\n buttonTitle,\n buttonDes,\n ] = 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 return;\n }\n\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\n rightWrapper.style.cssText = `\n margin-left: auto;\n display: flex;\n align-items: center;\n `;\n\n imgWrapper.id = elementId;\n imgWrapper.style.marginLeft = '.2rem';\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n\n let moduleIds = {\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n questionid: questionid,\n userid: userid,\n courseid: courseid};\n // Document mode, other modules single editor instances\n if (isFullScreen && (modulename === 'assign' || modulename === 'forum'\n || modulename === 'lesson')) {\n let existsElement = document.querySelector('.tox-menubar[class*=\"cursive-menu-\"] > div');\n if (existsElement) {\n existsElement.remove();\n }\n\n if (!document.querySelector(`#${elementId}`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else if (isFullScreen && modulename === 'quiz') { // Document mode, quiz multiple editor instances\n let existingElement = editor.container?.childNodes[1]?.childNodes[0]?.childNodes[0]?.childNodes[7];\n let newHeader = editor.container?.childNodes[0];\n if (existingElement) {\n existingElement.remove();\n }\n\n if (newHeader && !newHeader.querySelector(`span[id*=tiny_cursive_StateIcon]`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else { // Regular view\n let menubar = editor?.container?.children[0]?.childNodes[0]?.childNodes[0];\n\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n // Regular view, multiple editor instances\n if (modulename === 'quiz' && menubar) {\n let wrapper = menubar.querySelector('span[id*=\"tiny_cursive_StateIcon\"]');\n\n if (wrapper) {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, wrapper?.parentElement, moduleIds, isFullScreen);\n }\n } else {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\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\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n\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\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\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 fetchStrings();\n\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\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\n if (resourceId === null) {\n resourceId = 0;\n }\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\n checkIsPdfAnnotator();\n\n return {resourceId: resourceId, name: modulename};\n }\n\n /**\n * Fetches and caches localized strings used in the UI\n * @function fetchStrings\n * @description Retrieves strings for sidebar titles and document sidebar elements if not already cached in localStorage\n * Uses Promise.all to fetch multiple strings in parallel for better performance\n * Stores the fetched strings in localStorage under 'sbTitle' and 'docSideBar' keys\n */\n function fetchStrings() {\n if (!localStorage.getItem('sbTitle')) {\n Promise.all([\n getString('assignment', 'tiny_cursive'),\n getString('discussion', 'tiny_cursive'),\n getString('pluginname', 'mod_quiz'),\n getString('pluginname', 'mod_lesson'),\n getString('description', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('sbTitle', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n if (!localStorage.getItem('docSideBar')) {\n Promise.all([\n getString('details', 'tiny_cursive'),\n getString('student_info', 'tiny_cursive'),\n getString('progress', 'tiny_cursive'),\n getString('description', 'tiny_cursive'),\n getString('replyingto', 'tiny_cursive'),\n getString('answeringto', 'tiny_cursive'),\n getString('importantdates', 'tiny_cursive'),\n getString('rubrics', 'tiny_cursive'),\n getString('submission_status', 'tiny_cursive'),\n getString('status', 'tiny_cursive'),\n getString('draft', 'tiny_cursive'),\n getString('draftnot', 'tiny_cursive'),\n getString('last_modified', 'tiny_cursive'),\n getString('gradings', 'tiny_cursive'),\n getString('gradenot', 'tiny_cursive'),\n getString('word_count', 'tiny_cursive'),\n getString('timeleft', 'tiny_cursive'),\n getString('nolimit', 'tiny_cursive'),\n getString('name', 'tiny_cursive'),\n getString('userename', 'tiny_cursive'),\n getString('course', 'tiny_cursive'),\n getString('opened', 'tiny_cursive'),\n getString('due', 'tiny_cursive'),\n getString('overdue', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n getString('savechanges', 'tiny_cursive'),\n getString('subjectnot', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('docSideBar', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n\n }\n\n /**\n * Checks if the current page is a PDF annotator and updates the resourceId accordingly\n * @function checkIsPdfAnnotator\n * @description Checks if URL contains 'pdfannotator' and sets resourceId based on editor ID and editing state:\n * - If editing an existing annotation (editor.id !== 'id_pdfannotator_content' and isEditing is true):\n * Sets resourceId to the annotation ID extracted from editor.id\n * - Otherwise: Sets resourceId to 0\n */\n function checkIsPdfAnnotator() {\n if (ur.includes('pdfannotator')) {\n if (editor.id !== 'id_pdfannotator_content' && parseInt(localStorage.getItem('isEditing'))) {\n resourceId = parseInt(editor?.id.replace('editarea', ''));\n } else {\n resourceId = 0;\n }\n }\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","Rubrics","submission","quizInfo","pasteSetting","isStudent","hasClass","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","questionid","quizSubmit","assignSubmit","syncInterval","lastCaretPos","aiContents","isFullScreen","user","ur","window","location","href","parm","URL","modulesInfo","localStorage","getItem","Promise","all","then","strings","setItem","JSON","stringify","catch","error","console","fetchStrings","some","module","includes","resourceId","searchParams","get","modulename","checkIsPdfAnnotator","name","getModulesInfo","errorAlert","PASTE_SETTING","shouldBlockPaste","isPasteAllowed","document","addEventListener","e","target","className","replace","syncData","postOne","async","methodname","args","response","setTimeout","updateSavingState","field","values","done","fail","ex","on","preventDefault","off","click","removeItem","getModal","title","titledes","placeholder","type","body","removeOnClose","modal","getRoot","addClass","show","lastEvent","save","number","getElementById","value","trim","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","sendKeyEvent","events","split","data","parse","push","key","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","aiContent","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","range","getRng","getBody","preCaretRange","cloneRange","selectNodeContents","setEnd","endContainer","endOffset","fragment","cloneContents","tempDiv","createElement","appendChild","textBeforeCursor","innerText","nodeType","Node","ELEMENT_NODE","dom","isBlock","previousSibling","blockElements","querySelectorAll","emptyBlockCount","forEach","block","textContent","childNodes","length","nodeName","repeat","absolutePosition","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","fire","originalText","getContent","format","editorId","iframe","querySelector","iframeBody","contentDocument","contentWindow","_iframe$contentWindow","_iframe$contentWindow2","getRawText","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","classArray","element","index","classList","add","cursiveIcon","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","elementId","cssText","marginLeft","moduleIds","existingElement","container","_editor$container","_editor$container$chi","_editor$container$chi2","_editor$container$chi3","newHeader","_editor$container2","remove","marginTop","prepend","destroyInstance","getInstance","menubar","_editor$container3","children","_editor$container3$ch","_editor$container3$ch2","wrapper","parentElement","existsElement","cursiveState","tooltipId","text","setTooltip","this","css","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","clipboardData","originalEvent","getData","trimmedPastedContent","lastCopyCutContent","isFromOwnEditor","stopPropagation","stopImmediatePropagation","windowManager","ctrlKey","metaKey","selectedContent","view","DocumentView","state","fullPageMode","normalMode","command","contentObj","isPaste","paste","insertedContent","content","innerHTML","pastedText","undoManager","undo","srcElement","baseURI","inputType","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 $ from 'jquery';\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\nimport Autosave from 'tiny_cursive/cursive_autosave';\nimport DocumentView from 'tiny_cursive/document_view';\nimport {call as getUser} from \"core/ajax\";\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\n\n var isStudent = !($('#body').hasClass('teacher_admin'));\n var intervention = $('#body').hasClass('intervention');\n var host = M.cfg.wwwroot;\n var userid = userId;\n var courseid = M.cfg.courseId;\n var editorid = editor?.id;\n var cmid = M.cfg.contextInstanceId;\n var ed = \"\";\n var event = \"\";\n var filename = \"\";\n var questionid = 0;\n var quizSubmit = $('#mod_quiz-next-nav');\n let assignSubmit = $('#id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let aiContents = [];\n var isFullScreen = false;\n var user = null;\n let ur = window.location.href;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n var resourceId = modulesInfo.resourceId;\n var modulename = modulesInfo.name;\n var errorAlert = true;\n let PASTE_SETTING = pasteSetting || 'allow';\n let shouldBlockPaste = false;\n let isPasteAllowed = false;\n\n if (ur.includes('pdfannotator')) {\n document.addEventListener('click', e => {\n if (e.target.className === \"dropdown-item comment-edit-a\") {\n let id = e.target.id;\n resourceId = id.replace('editButton', '');\n localStorage.setItem('isEditing', '1');\n }\n if (e.target.id === 'commentSubmit') {\n syncData();\n }\n });\n }\n\n const postOne = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n if (response) {\n setTimeout(() => {\n Autosave.updateSavingState('saved');\n }, 1000);\n }\n return response;\n } catch (error) {\n Autosave.updateSavingState('offline');\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n getUser([{\n methodname: 'core_user_get_users_by_field',\n args: {field: 'id', values: [userid]},\n }])[0].done(response => {\n user = response[0];\n }).fail((ex) => {\n window.console.error('Error fetching user data:', ex);\n });\n\n assignSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n assignSubmit.off('click').click();\n });\n } else {\n assignSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n quizSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n quizSubmit.off('click').click();\n });\n } else {\n quizSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n const getModal = () => {\n\n Promise.all([\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\n getString('tiny_cursive_placeholder', 'tiny_cursive')\n ]).then(function([title, titledes, placeholder]) {\n\n return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n })\n .done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n\n modal.getRoot().on(save, function() {\n\n var number = document.getElementById(\"inputUrl\").value.trim();\n\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n // eslint-disable-next-line\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\n }\n\n postOne('cursive_user_comments', {\n modulename: modulename,\n cmid: cmid,\n resourceid: resourceId,\n courseid: courseid,\n usercomment: number,\n timemodified: Date.now(),\n editorid: editorid ? editorid : \"\"\n });\n\n lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\n });\n\n modal.getRoot().on(hidden, function() {\n if (lastEvent != 'cancel' && lastEvent != 'save') {\n editor.execCommand('Undo');\n }\n });\n return modal;\n });\n }).catch(error => window.console.error(error));\n\n };\n\n const sendKeyEvent = (events, editor) => {\n ed = editor;\n event = events;\n\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\n editor.on('Paste', async(e) => {\n customTooltip();\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\n if (!pastedContent) {\n return;\n }\n // Trim both values for consistent comparison\n const trimmedPastedContent = pastedContent.trim();\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\n\n if (isStudent && intervention) {\n\n if (PASTE_SETTING === 'block') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n shouldBlockPaste = true;\n isPasteAllowed = false;\n e.stopPropagation();\n e.stopImmediatePropagation();\n getString('paste_blocked', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n setTimeout(() => {\n isPasteAllowed = true;\n shouldBlockPaste = false;\n }, 100);\n return;\n }\n shouldBlockPaste = false;\n isPasteAllowed = true;\n return;\n }\n if (PASTE_SETTING === 'cite_source') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n getModal(e);\n }\n isPasteAllowed = true;\n return;\n }\n }\n isPasteAllowed = true;\n });\n editor.on('Redo', async(e) => {\n customTooltip();\n if (isStudent && intervention) {\n getModal(e);\n }\n });\n editor.on('keyDown', (editor) => {\n customTooltip();\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\n (editor.ctrlKey || editor.metaKey);\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\n setTimeout(() => {\n isPasteAllowed = true;\n }, 100);\n return;\n }\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('mouseDown', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n }, 0);\n });\n editor.on('mouseUp', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n }, 10);\n });\n editor.on('init', () => {\n customTooltip();\n localStorage.removeItem('lastCopyCutContent');\n });\n editor.on('SetContent', () => {\n customTooltip();\n });\n editor.on('FullscreenStateChanged', (e) => {\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\n isFullScreen = e.state;\n try {\n if (!e.state) {\n view.normalMode();\n } else {\n view.fullPageMode();\n }\n } catch (error) {\n if (errorAlert) {\n errorAlert = false;\n getString('fullmodeerror', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n view.normalMode();\n window.console.error('Error ResizeEditor event:', error);\n }\n });\n\n editor.on('execcommand', function(e) {\n if (e.command === \"mceInsertContent\") {\n const contentObj = e.value;\n\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\n\n let insertedContent = contentObj.content || contentObj;\n let tempDiv = document.createElement('div');\n tempDiv.innerHTML = insertedContent;\n let text = tempDiv.textContent || tempDiv.innerText || '';\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\n\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n\n if (isPaste) {\n if (shouldBlockPaste) {\n shouldBlockPaste = false;\n e.preventDefault();\n editor.undoManager.undo();\n return;\n }\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\n\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\n isPasteAllowed = false;\n editor.undoManager.undo();\n return;\n }\n\n sendKeyEvent(\"Paste\", {\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n pastedContent: pastedText,\n srcElement: {baseURI: window.location.href}\n });\n } else {\n aiContents.push(text);\n\n sendKeyEvent(\"aiInsert\", {\n key: \"ai\",\n keyCode: 0,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n aiContent: text,\n srcElement: {baseURI: window.location.href}\n });\n }\n }\n });\n\n editor.on('input', function(e) {\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n let aiContent = e.data;\n\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\n\n aiContents.push(aiContent);\n\n e.key = \"ai\";\n e.keyCode = 0;\n e.caretPosition = position.caretPosition;\n e.rePosition = position.rePosition;\n e.aiContent = aiContent;\n\n sendKeyEvent(\"aiInsert\", e);\n }\n });\n\n\n /**\n * Constructs a mouse event object with caret position and button information\n * @param {Object} editor - The TinyMCE editor instance\n * @function constructMouseEvent\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\n */\n function constructMouseEvent(editor) {\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n editor.key = getMouseButton(editor);\n editor.keyCode = editor.button;\n }\n\n /**\n * Gets the string representation of a mouse button based on its numeric value\n * @param {Object} editor - The editor object containing button information\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\n */\n function getMouseButton(editor) {\n\n switch (editor.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n }\n return null;\n }\n\n /**\n * Gets the current caret position in the editor\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\n * @returns {Object} Object containing:\n * - caretPosition: Sequential position number stored in session\n * - rePosition: Absolute character offset from start of content\n * @throws {Error} Logs warning to console if error occurs during calculation\n */\n function getCaretPosition(skip = false) {\n try {\n if (!editor || !editor.selection) {\n return {caretPosition: 0, rePosition: 0};\n }\n\n const range = editor.selection.getRng();\n const body = editor.getBody();\n\n // Create a range from start of document to current caret\n const preCaretRange = range.cloneRange();\n preCaretRange.selectNodeContents(body);\n preCaretRange.setEnd(range.endContainer, range.endOffset);\n\n const fragment = preCaretRange.cloneContents();\n const tempDiv = document.createElement('div');\n tempDiv.appendChild(fragment);\n let textBeforeCursor = tempDiv.innerText || '';\n\n const endContainer = range.endContainer;\n const endOffset = range.endOffset;\n\n if (endOffset === 0 &&\n endContainer.nodeType === Node.ELEMENT_NODE &&\n editor.dom.isBlock(endContainer) &&\n endContainer.previousSibling) {\n textBeforeCursor += '\\n';\n }\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\n let emptyBlockCount = 0;\n blockElements.forEach(block => {\n const text = block.innerText || block.textContent || '';\n if (text.trim() === '' && block.childNodes.length === 1 &&\n block.childNodes[0].nodeName === 'BR') {\n emptyBlockCount++;\n }\n });\n\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\n if (emptyBlockCount > 0) {\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\n }\n\n const absolutePosition = textBeforeCursor.length;\n\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\n // Increment sequential caretPosition\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\n if (isNaN(storedPos)) {\n storedPos = 0;\n }\n storedPos++;\n lastCaretPos = storedPos;\n sessionStorage.setItem(storageKey, storedPos);\n\n return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\n }\n }\n\n\n /**\n * Synchronizes data from localStorage to server\n * @async\n * @function SyncData\n * @description Retrieves stored keypress data from localStorage and sends it to server\n * @returns {Promise} Returns response from server if data exists and is successfully sent\n * @throws {Error} Logs error to console if data submission fails\n */\n async function syncData() {\n checkIsPdfAnnotator();\n let data = localStorage.getItem(filename);\n\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n editor.fire('change');\n let originalText = editor.getContent({format: 'text'});\n if (!originalText) {\n originalText = getRawText(editor);\n }\n try {\n Autosave.updateSavingState('saving');\n // eslint-disable-next-line\n return await postOne('cursive_write_local_to_json', {\n key: ed.key,\n event: event,\n keyCode: ed.keyCode,\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n editorid: editorid,\n \"json_data\": data,\n originalText: originalText\n });\n } catch (error) {\n window.console.error('Error submitting data:', error);\n }\n }\n }\n\n /**\n * Gets the raw text content from a TinyMCE editor iframe\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {string} The raw text content of the editor body, or empty string if not found\n * @description Attempts to get the raw text content from the editor's iframe body by:\n * 1. Getting the editor ID\n * 2. Finding the associated iframe element\n * 3. Accessing the iframe's document body\n * 4. Returning the text content\n * Returns empty string if any step fails\n */\n function getRawText(editor) {\n let editorId = editor?.id;\n if (editorId) {\n let iframe = document.querySelector(`#${editorId}_ifr`);\n let iframeBody = iframe.contentDocument?.body || iframe.contentWindow?.document?.body;\n return iframeBody?.textContent;\n }\n return \"\";\n }\n\n /**\n * Sets up custom tooltip functionality for the Cursive icon\n * Initializes tooltip text, positions the icon in the menubar,\n * and sets up mouse event handlers for showing/hiding the tooltip\n * @function customTooltip\n */\n function customTooltip() {\n try {\n const tooltipText = getTooltipText();\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\n let classArray = [];\n\n if (menubarDiv.length) {\n menubarDiv.forEach(function(element, index) {\n index += 1;\n let className = 'cursive-menu-' + index;\n element.classList.add(className);\n classArray.push(className);\n });\n }\n\n const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n\n cursiveState(cursiveIcon, menubarDiv, classArray);\n\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n\n $(`#${elementId}`).on('mouseenter', function() {\n $(this).css('position', 'relative');\n $(`#${tooltipId}`).css(tooltipCss);\n });\n\n $(`#${elementId}`).on('mouseleave', function() {\n $(`#${tooltipId}`).css('display', 'none');\n });\n }\n } catch (error) {\n window.console.error('Error setting up custom tooltip:', error);\n }\n }\n\n /**\n * Retrieves tooltip text strings from language files\n * @async\n * @function getTooltipText\n * @returns {Promise} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [\n buttonTitle,\n buttonDes,\n ] = 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 return;\n }\n\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\n rightWrapper.style.cssText = `\n margin-left: auto;\n display: flex;\n align-items: center;\n `;\n\n imgWrapper.id = elementId;\n imgWrapper.style.marginLeft = '.2rem';\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n\n let moduleIds = {\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n questionid: questionid,\n userid: userid,\n courseid: courseid};\n // Document mode, other modules single editor instances\n if (isFullScreen && (modulename === 'assign' || modulename === 'forum'\n || modulename === 'lesson')) {\n let existsElement = document.querySelector('.tox-menubar[class*=\"cursive-menu-\"] > div');\n if (existsElement) {\n existsElement.remove();\n }\n\n if (!document.querySelector(`#${elementId}`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else if (isFullScreen && modulename === 'quiz') { // Document mode, quiz multiple editor instances\n let existingElement = editor.container?.childNodes[1]?.childNodes[0]?.childNodes[0]?.childNodes[7];\n let newHeader = editor.container?.childNodes[0];\n if (existingElement) {\n existingElement.remove();\n }\n\n if (newHeader && !newHeader.querySelector(`span[id*=tiny_cursive_StateIcon]`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else { // Regular view\n let menubar = editor?.container?.children[0]?.childNodes[0]?.childNodes[0];\n\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n // Regular view, multiple editor instances\n if (modulename === 'quiz' && menubar) {\n let wrapper = menubar.querySelector('span[id*=\"tiny_cursive_StateIcon\"]');\n\n if (wrapper) {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, wrapper?.parentElement, moduleIds, isFullScreen);\n }\n } else {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\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\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n\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\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\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 fetchStrings();\n\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\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\n if (resourceId === null) {\n resourceId = 0;\n }\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\n checkIsPdfAnnotator();\n\n return {resourceId: resourceId, name: modulename};\n }\n\n /**\n * Fetches and caches localized strings used in the UI\n * @function fetchStrings\n * @description Retrieves strings for sidebar titles and document sidebar elements if not already cached in localStorage\n * Uses Promise.all to fetch multiple strings in parallel for better performance\n * Stores the fetched strings in localStorage under 'sbTitle' and 'docSideBar' keys\n */\n function fetchStrings() {\n if (!localStorage.getItem('sbTitle')) {\n Promise.all([\n getString('assignment', 'tiny_cursive'),\n getString('discussion', 'tiny_cursive'),\n getString('pluginname', 'mod_quiz'),\n getString('pluginname', 'mod_lesson'),\n getString('description', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('sbTitle', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n if (!localStorage.getItem('docSideBar')) {\n Promise.all([\n getString('details', 'tiny_cursive'),\n getString('student_info', 'tiny_cursive'),\n getString('progress', 'tiny_cursive'),\n getString('description', 'tiny_cursive'),\n getString('replyingto', 'tiny_cursive'),\n getString('answeringto', 'tiny_cursive'),\n getString('importantdates', 'tiny_cursive'),\n getString('rubrics', 'tiny_cursive'),\n getString('submission_status', 'tiny_cursive'),\n getString('status', 'tiny_cursive'),\n getString('draft', 'tiny_cursive'),\n getString('draftnot', 'tiny_cursive'),\n getString('last_modified', 'tiny_cursive'),\n getString('gradings', 'tiny_cursive'),\n getString('gradenot', 'tiny_cursive'),\n getString('word_count', 'tiny_cursive'),\n getString('timeleft', 'tiny_cursive'),\n getString('nolimit', 'tiny_cursive'),\n getString('name', 'tiny_cursive'),\n getString('userename', 'tiny_cursive'),\n getString('course', 'tiny_cursive'),\n getString('opened', 'tiny_cursive'),\n getString('due', 'tiny_cursive'),\n getString('overdue', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n getString('savechanges', 'tiny_cursive'),\n getString('subjectnot', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('docSideBar', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n\n }\n\n /**\n * Checks if the current page is a PDF annotator and updates the resourceId accordingly\n * @function checkIsPdfAnnotator\n * @description Checks if URL contains 'pdfannotator' and sets resourceId based on editor ID and editing state:\n * - If editing an existing annotation (editor.id !== 'id_pdfannotator_content' and isEditing is true):\n * Sets resourceId to the annotation ID extracted from editor.id\n * - Otherwise: Sets resourceId to 0\n */\n function checkIsPdfAnnotator() {\n if (ur.includes('pdfannotator')) {\n if (editor.id !== 'id_pdfannotator_content' && parseInt(localStorage.getItem('isEditing'))) {\n resourceId = parseInt(editor?.id.replace('editarea', ''));\n } else {\n resourceId = 0;\n }\n }\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","Rubrics","submission","quizInfo","pasteSetting","isStudent","hasClass","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","questionid","quizSubmit","assignSubmit","syncInterval","lastCaretPos","aiContents","isFullScreen","user","ur","window","location","href","parm","URL","modulesInfo","localStorage","getItem","Promise","all","then","strings","setItem","JSON","stringify","catch","error","console","fetchStrings","some","module","includes","resourceId","searchParams","get","modulename","checkIsPdfAnnotator","name","getModulesInfo","errorAlert","PASTE_SETTING","shouldBlockPaste","isPasteAllowed","document","addEventListener","e","target","className","replace","syncData","postOne","async","methodname","args","response","setTimeout","updateSavingState","field","values","done","fail","ex","on","preventDefault","off","click","removeItem","getModal","title","titledes","placeholder","type","body","removeOnClose","modal","getRoot","addClass","show","lastEvent","save","number","getElementById","value","trim","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","sendKeyEvent","events","split","data","parse","push","key","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","aiContent","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","range","getRng","getBody","preCaretRange","cloneRange","selectNodeContents","setEnd","endContainer","endOffset","fragment","cloneContents","tempDiv","createElement","appendChild","textBeforeCursor","innerText","nodeType","Node","ELEMENT_NODE","dom","isBlock","previousSibling","blockElements","querySelectorAll","emptyBlockCount","forEach","block","textContent","childNodes","length","nodeName","repeat","absolutePosition","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","fire","originalText","getContent","format","editorId","iframe","querySelector","iframeBody","contentDocument","contentWindow","_iframe$contentWindow","_iframe$contentWindow2","getRawText","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","classArray","element","index","classList","add","cursiveIcon","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","elementId","cssText","marginLeft","moduleIds","existingElement","container","_editor$container","_editor$container$chi","_editor$container$chi2","_editor$container$chi3","newHeader","_editor$container2","remove","marginTop","prepend","destroyInstance","getInstance","menubar","_editor$container3","children","_editor$container3$ch","_editor$container3$ch2","wrapper","parentElement","existsElement","cursiveState","tooltipId","text","setTooltip","this","css","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","clipboardData","originalEvent","getData","trimmedPastedContent","lastCopyCutContent","isFromOwnEditor","stopPropagation","stopImmediatePropagation","windowManager","ctrlKey","metaKey","selectedContent","view","DocumentView","state","fullPageMode","normalMode","command","contentObj","isPaste","paste","insertedContent","content","innerHTML","pastedText","undoManager","undo","srcElement","baseURI","inputType","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 c2b8ed1c..408c6660 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"),iconSaving:M.util.image_url("rotate","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"),iconSaving:M.util.image_url("rotate","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 ddc7a5f5..a8da292b 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 iconSaving: M.util.image_url('rotate', '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","iconSaving","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,WAAYH,EAAEC,KAAKC,UAAU,SAAU,gBACvCE,YAAaJ,EAAEC,KAAKC,UAAU,eAAgB,gBAC9CG,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 iconSaving: M.util.image_url('rotate', '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","iconSaving","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,WAAYH,EAAEC,KAAKC,UAAU,SAAU,gBACvCE,YAAaJ,EAAEC,KAAKC,UAAU,eAAgB,gBAC9CG,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/cursive_autosave.min.js b/amd/build/cursive_autosave.min.js index 2ff57d50..17a4fbb5 100644 --- a/amd/build/cursive_autosave.min.js +++ b/amd/build/cursive_autosave.min.js @@ -1,10 +1,3 @@ -define("tiny_cursive/cursive_autosave",["exports","core/templates","core/ajax","tiny_cursive/svg_repo","core/str"],(function(_exports,_templates,_ajax,_svg_repo,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} -/** - * TODO describe module cursive_autosave - * - * @module tiny_cursive/cursive_autosave - * @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.default=void 0,_templates=_interopRequireDefault(_templates),_svg_repo=_interopRequireDefault(_svg_repo);class CursiveAutosave{static instance=null;constructor(editor,rightWrapper,modules,isFullScreen){if(CursiveAutosave.instance)return CursiveAutosave.instance;this.editor=editor,this.module=modules,this.savingState="",this.rightWrapper=rightWrapper,this.isFullScreen=isFullScreen,this.fetchSavedContent=this.fetchSavedContent.bind(this),this.handleEscapeKey=this.handleEscapeKey.bind(this),this._savingTimer=null,CursiveAutosave.instance=this,this.fetchStrings()}static getInstance(editor,rightWrapper,modules,isFullScreen){this.instance||(this.instance=new CursiveAutosave(editor,rightWrapper,modules,isFullScreen)),this.instance.isFullScreen=isFullScreen;return("quiz"===modules.modulename?document.querySelector(`#tiny_cursive_savingState${modules.questionid}`):document.querySelector("#tiny_cursive_savingState"))||this.instance.init(),this.instance}init(){const stateWrapper=this.cursiveSavingState(this.savingState);stateWrapper.classList.add("tiny_cursive_savingState","btn"),"quiz"===this.module.modulename?stateWrapper.id=`tiny_cursive_savingState${this.module.questionid}`:stateWrapper.id="tiny_cursive_savingState",this.rightWrapper.prepend(stateWrapper),stateWrapper.addEventListener("click",this.fetchSavedContent)}destroy(){CursiveAutosave.instance=null}static destroyInstance(){this.instance&&(this.instance.destroy(),this.instance=null)}cursiveSavingState(state){let wrapperDiv=document.createElement("div"),textSpan=document.createElement("span"),button=document.createElement("button"),iconSpan=document.createElement("span");return button.style.padding=".3rem",textSpan.style.fontSize="0.75rem",textSpan.style.color="gray","quiz"===this.module.modulename?(iconSpan.id=`CursiveCloudIcon${this.module.questionid}`,textSpan.id=`CursiveStateText${this.module.questionid}`):(iconSpan.id="CursiveCloudIcon",textSpan.id="CursiveStateText"),state&&(textSpan.textContent=this.getStateText(state),iconSpan.innerHTML=this.getStateIcon(state)),wrapperDiv.style.verticalAlign="middle",wrapperDiv.appendChild(iconSpan),wrapperDiv.appendChild(textSpan),button.appendChild(wrapperDiv),button}static updateSavingState(state){const instance=this.instance;instance.savingState=state;let stateWrapper=null;stateWrapper="quiz"===instance.module.modulename?document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`):document.querySelector("#tiny_cursive_savingState");let iconSpan="",stateTextEl="";stateWrapper&&("quiz"===instance.module.modulename?(iconSpan=stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`),stateTextEl=stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`)):(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon"),stateTextEl=stateWrapper.querySelector("#CursiveStateText")),stateTextEl&&iconSpan&&(stateTextEl.textContent=instance.getStateText(state),iconSpan.innerHTML=instance.getStateIcon(state)),instance._savingTimer&&clearTimeout(instance._savingTimer),"saved"===state&&stateTextEl&&(instance._savingTimer=setTimeout((()=>{stateTextEl.textContent=""}),5e3)))}getStateText(state){const[saving,saved,offline]=this.getText("state");switch(state){case"saving":return saving;case"saved":return saved;case"offline":return offline;default:return""}}getStateIcon(state){switch(state){case"saving":case"saved":return _svg_repo.default.cloudSave;case"offline":return"data:image/svg+xml;base64,"+btoa(_svg_repo.default.offline);default:return""}}async fetchSavedContent(e){var _dropdown$classList,_this$editor;e.preventDefault();let dropdown=document.querySelector("#savedDropdown");if(null==dropdown||null===(_dropdown$classList=dropdown.classList)||void 0===_dropdown$classList?void 0:_dropdown$classList.contains("show"))return void this.closeSavedDropdown();let editorWrapper=null;editorWrapper="quiz"===this.module.modulename?document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`):document.querySelector("#tiny_cursive_savingState");let args={id:this.module.resourceId,cmid:this.module.cmid,modulename:`${this.module.modulename}_autosave`,editorid:null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id,userid:this.module.userid,courseid:this.module.courseid};(0,_ajax.call)([{methodname:"cursive_get_autosave_content",args:args}])[0].done((data=>{let context={comments:JSON.parse(data)};Object.values(context.comments).forEach((content=>{content.time=this.timeAgo(content.timemodified)})),this.renderCommentList(context,editorWrapper)})).fail((error=>{this.throwWarning("fullmodeerrorr",this.editor),window.console.error("Error fetching saved content:",error)}))}toggleSavedDropdown(){var _dropdown$classList2;const dropdown=document.querySelector("#savedDropdown");(null==dropdown||null===(_dropdown$classList2=dropdown.classList)||void 0===_dropdown$classList2?void 0:_dropdown$classList2.contains("show"))?this.closeSavedDropdown():this.openSavedDropdown()}openSavedDropdown(){document.querySelector("#savedDropdown").classList.add("show"),document.removeEventListener("keydown",this.handleEscapeKey),document.addEventListener("keydown",this.handleEscapeKey)}closeSavedDropdown(){const dropdown=document.querySelector("#savedDropdown");dropdown&&(dropdown.classList.remove("show"),dropdown.remove(),document.removeEventListener("keydown",this.handleEscapeKey))}handleEscapeKey(event){"Escape"===event.key&&this.closeSavedDropdown()}timeAgo(unixTime){const seconds=Math.floor(Date.now()/1e3)-unixTime;if(seconds<5)return"just now";if(seconds<60)return`${seconds} sec ago`;const minutes=Math.floor(seconds/60);if(minutes<60)return`${minutes} min ago`;const hours=Math.floor(minutes/60);if(hours<24)return`${hours} hour${hours>1?"s":""} ago`;const days=Math.floor(hours/24);if(days<7)return`${days} day${days>1?"s":""} ago`;const weeks=Math.floor(days/7);if(weeks<4)return`${weeks} week${weeks>1?"s":""} ago`;const months=Math.floor(days/30);if(months<12)return`${months} month${months>1?"s":""} ago`;const years=Math.floor(days/365);return`${years} year${years>1?"s":""} ago`}renderCommentList(context,editorWrapper){_templates.default.render("tiny_cursive/saved_content",context).then((html=>{editorWrapper.style.position="relative";const tempDiv=document.createElement("div");if(tempDiv.innerHTML=html.trim(),tempDiv.id="savedDropdown",tempDiv.classList.add("tiny_cursive-saved-dropdown"),!tempDiv)return window.console.error("Saved content template rendered empty or invalid HTML."),!1;let existingPanel=document.querySelector("#savedDropdown");return existingPanel||(editorWrapper.appendChild(tempDiv),existingPanel=tempDiv),existingPanel.classList.toggle("active"),this.openSavedDropdown(),this.insertSavedItems(this.editor),!0})).catch((error=>window.console.error(error)))}fetchStrings(){localStorage.getItem("state")||Promise.all([(0,_str.get_string)("saving","tiny_cursive"),(0,_str.get_string)("saved","tiny_cursive"),(0,_str.get_string)("offline","tiny_cursive")]).then((function(strings){return localStorage.setItem("state",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}throwWarning(str,editor){(0,_str.get_string)(str,"tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))}getText(key){return JSON.parse(localStorage.getItem(key))}insertSavedItems(editor){document.querySelectorAll(".tiny_cursive-item-preview").forEach((element=>{element.addEventListener("click",(function(){editor.insertContent(" "+this.textContent)}))}))}}return _exports.default=CursiveAutosave,_exports.default})); +define("tiny_cursive/cursive_autosave",["exports","core/templates","core/ajax","tiny_cursive/svg_repo","core/str"],(function(_exports,_templates,_ajax,_svg_repo,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_svg_repo=_interopRequireDefault(_svg_repo);class CursiveAutosave{constructor(editor,rightWrapper,modules,isFullScreen){if(CursiveAutosave.instance)return CursiveAutosave.instance;this.editor=editor,this.module=modules,this.savingState="",this.rightWrapper=rightWrapper,this.isFullScreen=isFullScreen,this.fetchSavedContent=this.fetchSavedContent.bind(this),this.handleEscapeKey=this.handleEscapeKey.bind(this),this._savingTimer=null,CursiveAutosave.instance=this,this.fetchStrings()}static getInstance(editor,rightWrapper,modules,isFullScreen){this.instance||(this.instance=new CursiveAutosave(editor,rightWrapper,modules,isFullScreen)),this.instance.isFullScreen=isFullScreen;return("quiz"===modules.modulename?document.querySelector("#tiny_cursive_savingState".concat(modules.questionid)):document.querySelector("#tiny_cursive_savingState"))||this.instance.init(),this.instance}init(){const stateWrapper=this.cursiveSavingState(this.savingState);stateWrapper.classList.add("tiny_cursive_savingState","btn"),"quiz"===this.module.modulename?stateWrapper.id="tiny_cursive_savingState".concat(this.module.questionid):stateWrapper.id="tiny_cursive_savingState",this.rightWrapper.prepend(stateWrapper),stateWrapper.addEventListener("click",this.fetchSavedContent)}destroy(){CursiveAutosave.instance=null}static destroyInstance(){this.instance&&(this.instance.destroy(),this.instance=null)}cursiveSavingState(state){let wrapperDiv=document.createElement("div"),textSpan=document.createElement("span"),button=document.createElement("button"),iconSpan=document.createElement("span");return button.style.padding=".3rem",textSpan.style.fontSize="0.75rem",textSpan.style.color="gray","quiz"===this.module.modulename?(iconSpan.id="CursiveCloudIcon".concat(this.module.questionid),textSpan.id="CursiveStateText".concat(this.module.questionid)):(iconSpan.id="CursiveCloudIcon",textSpan.id="CursiveStateText"),state&&(textSpan.textContent=this.getStateText(state),iconSpan.innerHTML=this.getStateIcon(state)),wrapperDiv.style.verticalAlign="middle",wrapperDiv.appendChild(iconSpan),wrapperDiv.appendChild(textSpan),button.appendChild(wrapperDiv),button}static updateSavingState(state){const instance=this.instance;instance.savingState=state;let stateWrapper=null;stateWrapper="quiz"===instance.module.modulename?document.querySelector("#tiny_cursive_savingState".concat(instance.module.questionid)):document.querySelector("#tiny_cursive_savingState");let iconSpan="",stateTextEl="";stateWrapper&&("quiz"===instance.module.modulename?(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon".concat(instance.module.questionid)),stateTextEl=stateWrapper.querySelector("#CursiveStateText".concat(instance.module.questionid))):(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon"),stateTextEl=stateWrapper.querySelector("#CursiveStateText")),stateTextEl&&iconSpan&&(stateTextEl.textContent=instance.getStateText(state),iconSpan.innerHTML=instance.getStateIcon(state)),instance._savingTimer&&clearTimeout(instance._savingTimer),"saved"===state&&stateTextEl&&(instance._savingTimer=setTimeout((()=>{stateTextEl.textContent=""}),5e3)))}getStateText(state){const[saving,saved,offline]=this.getText("state");switch(state){case"saving":return saving;case"saved":return saved;case"offline":return offline;default:return""}}getStateIcon(state){switch(state){case"saving":case"saved":return _svg_repo.default.cloudSave;case"offline":return"data:image/svg+xml;base64,"+btoa(_svg_repo.default.offline);default:return""}}async fetchSavedContent(e){var _dropdown$classList,_this$editor;e.preventDefault();let dropdown=document.querySelector("#savedDropdown");if(null==dropdown||null===(_dropdown$classList=dropdown.classList)||void 0===_dropdown$classList?void 0:_dropdown$classList.contains("show"))return void this.closeSavedDropdown();let editorWrapper=null;editorWrapper="quiz"===this.module.modulename?document.querySelector("#tiny_cursive_savingState".concat(this.module.questionid)):document.querySelector("#tiny_cursive_savingState");let args={id:this.module.resourceId,cmid:this.module.cmid,modulename:"".concat(this.module.modulename,"_autosave"),editorid:null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id,userid:this.module.userid,courseid:this.module.courseid};(0,_ajax.call)([{methodname:"cursive_get_autosave_content",args:args}])[0].done((data=>{let context={comments:JSON.parse(data)};Object.values(context.comments).forEach((content=>{content.time=this.timeAgo(content.timemodified)})),this.renderCommentList(context,editorWrapper)})).fail((error=>{this.throwWarning("fullmodeerrorr",this.editor),window.console.error("Error fetching saved content:",error)}))}toggleSavedDropdown(){var _dropdown$classList2;const dropdown=document.querySelector("#savedDropdown");(null==dropdown||null===(_dropdown$classList2=dropdown.classList)||void 0===_dropdown$classList2?void 0:_dropdown$classList2.contains("show"))?this.closeSavedDropdown():this.openSavedDropdown()}openSavedDropdown(){document.querySelector("#savedDropdown").classList.add("show"),document.removeEventListener("keydown",this.handleEscapeKey),document.addEventListener("keydown",this.handleEscapeKey)}closeSavedDropdown(){const dropdown=document.querySelector("#savedDropdown");dropdown&&(dropdown.classList.remove("show"),dropdown.remove(),document.removeEventListener("keydown",this.handleEscapeKey))}handleEscapeKey(event){"Escape"===event.key&&this.closeSavedDropdown()}timeAgo(unixTime){const seconds=Math.floor(Date.now()/1e3)-unixTime;if(seconds<5)return"just now";if(seconds<60)return"".concat(seconds," sec ago");const minutes=Math.floor(seconds/60);if(minutes<60)return"".concat(minutes," min ago");const hours=Math.floor(minutes/60);if(hours<24)return"".concat(hours," hour").concat(hours>1?"s":""," ago");const days=Math.floor(hours/24);if(days<7)return"".concat(days," day").concat(days>1?"s":""," ago");const weeks=Math.floor(days/7);if(weeks<4)return"".concat(weeks," week").concat(weeks>1?"s":""," ago");const months=Math.floor(days/30);if(months<12)return"".concat(months," month").concat(months>1?"s":""," ago");const years=Math.floor(days/365);return"".concat(years," year").concat(years>1?"s":""," ago")}renderCommentList(context,editorWrapper){_templates.default.render("tiny_cursive/saved_content",context).then((html=>{editorWrapper.style.position="relative";const tempDiv=document.createElement("div");if(tempDiv.innerHTML=html.trim(),tempDiv.id="savedDropdown",tempDiv.classList.add("tiny_cursive-saved-dropdown"),!tempDiv)return window.console.error("Saved content template rendered empty or invalid HTML."),!1;let existingPanel=document.querySelector("#savedDropdown");return existingPanel||(editorWrapper.appendChild(tempDiv),existingPanel=tempDiv),existingPanel.classList.toggle("active"),this.openSavedDropdown(),this.insertSavedItems(this.editor),!0})).catch((error=>window.console.error(error)))}fetchStrings(){localStorage.getItem("state")||Promise.all([(0,_str.get_string)("saving","tiny_cursive"),(0,_str.get_string)("saved","tiny_cursive"),(0,_str.get_string)("offline","tiny_cursive")]).then((function(strings){return localStorage.setItem("state",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}throwWarning(str,editor){(0,_str.get_string)(str,"tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))}getText(key){return JSON.parse(localStorage.getItem(key))}insertSavedItems(editor){document.querySelectorAll(".tiny_cursive-item-preview").forEach((element=>{element.addEventListener("click",(function(){editor.insertContent(" "+this.textContent)}))}))}}var obj,key,value;return _exports.default=CursiveAutosave,value=null,(key="instance")in(obj=CursiveAutosave)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,_exports.default})); //# sourceMappingURL=cursive_autosave.min.js.map \ No newline at end of file diff --git a/amd/build/cursive_autosave.min.js.map b/amd/build/cursive_autosave.min.js.map index f0ea83bb..56db2bab 100644 --- a/amd/build/cursive_autosave.min.js.map +++ b/amd/build/cursive_autosave.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cursive_autosave.min.js","sources":["../src/cursive_autosave.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 * TODO describe module cursive_autosave\n *\n * @module tiny_cursive/cursive_autosave\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport templates from 'core/templates';\nimport {call} from 'core/ajax';\nimport Icons from 'tiny_cursive/svg_repo';\nimport {get_string as getString} from 'core/str';\n\nexport default class CursiveAutosave {\n\n static instance = null;\n\n\n constructor(editor, rightWrapper, modules, isFullScreen) {\n if (CursiveAutosave.instance) {\n return CursiveAutosave.instance;\n }\n\n this.editor = editor;\n this.module = modules;\n this.savingState = '';\n this.rightWrapper = rightWrapper;\n this.isFullScreen = isFullScreen;\n // Bind methods that will be used as event listener\n this.fetchSavedContent = this.fetchSavedContent.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this._savingTimer = null;\n CursiveAutosave.instance = this;\n this.fetchStrings();\n }\n\n static getInstance(editor, rightWrapper, modules, isFullScreen) {\n if (!this.instance) {\n this.instance = new CursiveAutosave(editor, rightWrapper, modules, isFullScreen);\n }\n this.instance.isFullScreen = isFullScreen;\n const hasState = modules.modulename === 'quiz'\n ? document.querySelector(`#tiny_cursive_savingState${modules.questionid}`)\n : document.querySelector('#tiny_cursive_savingState');\n if (!hasState) {\n this.instance.init();\n }\n\n return this.instance;\n }\n\n init() {\n const stateWrapper = this.cursiveSavingState(this.savingState);\n stateWrapper.classList.add('tiny_cursive_savingState', 'btn');\n if (this.module.modulename === 'quiz') {\n stateWrapper.id = `tiny_cursive_savingState${this.module.questionid}`;\n } else {\n stateWrapper.id = 'tiny_cursive_savingState';\n }\n\n this.rightWrapper.prepend(stateWrapper);\n stateWrapper.addEventListener('click', this.fetchSavedContent);\n }\n\n destroy() {\n CursiveAutosave.instance = null;\n }\n\n static destroyInstance() {\n if (this.instance) {\n this.instance.destroy();\n this.instance = null;\n }\n }\n\n /**\n * Creates a wrapper div containing an icon and text to display the saving state\n * @param {string} state - The current saving state ('saving', 'saved', or 'offline')\n * @returns {HTMLElement} A div element containing the state icon and text\n * @description Creates and returns a div element with an icon and text span to show the current saving state.\n * The icon and text are updated based on the provided state parameter.\n */\n cursiveSavingState(state) {\n let wrapperDiv = document.createElement('div');\n let textSpan = document.createElement('span');\n let button = document.createElement('button');\n let iconSpan = document.createElement('span');\n\n button.style.padding = '.3rem';\n textSpan.style.fontSize = '0.75rem';\n textSpan.style.color = 'gray';\n\n if (this.module.modulename === 'quiz') {\n iconSpan.id = `CursiveCloudIcon${this.module.questionid}`;\n textSpan.id = `CursiveStateText${this.module.questionid}`;\n } else {\n iconSpan.id = 'CursiveCloudIcon';\n textSpan.id = 'CursiveStateText';\n }\n if (state) {\n textSpan.textContent = this.getStateText(state);\n iconSpan.innerHTML = this.getStateIcon(state);\n }\n\n wrapperDiv.style.verticalAlign = 'middle';\n\n wrapperDiv.appendChild(iconSpan);\n wrapperDiv.appendChild(textSpan);\n button.appendChild(wrapperDiv);\n\n return button;\n }\n\n /**\n * Updates the saving state icon and text in the editor\n * @param {string} state - The state to update to ('saving', 'saved', or 'offline')\n * @description Updates the global saving state and modifies the UI elements to reflect the new state\n */\n static updateSavingState(state) {\n const instance = this.instance;\n instance.savingState = state;\n let stateWrapper = null;\n if (instance.module.modulename === 'quiz') {\n stateWrapper = document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`);\n } else {\n stateWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let iconSpan = '';\n let stateTextEl = '';\n\n if (!stateWrapper) {\n return;\n }\n\n if (instance.module.modulename === 'quiz') {\n iconSpan = stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`);\n stateTextEl = stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`);\n } else {\n iconSpan = stateWrapper.querySelector('#CursiveCloudIcon');\n stateTextEl = stateWrapper.querySelector('#CursiveStateText');\n }\n\n if (stateTextEl && iconSpan) {\n stateTextEl.textContent = instance.getStateText(state);\n iconSpan.innerHTML = instance.getStateIcon(state);\n }\n\n\n if (instance._savingTimer) {\n clearTimeout(instance._savingTimer);\n }\n\n if (state === 'saved' && stateTextEl) {\n instance._savingTimer = setTimeout(() => {\n stateTextEl.textContent = '';\n }, 5000);\n }\n }\n\n /**\n * Gets the display text for a given saving state\n * @param {string} state - The state to get text for ('saving', 'saved', or 'offline')\n * @returns {string} The text to display for the given state\n * @description Returns appropriate text label based on the current saving state\n */\n getStateText(state) {\n const [saving, saved, offline] = this.getText('state');\n switch (state) {\n case 'saving': return saving;\n case 'saved': return saved;\n case 'offline': return offline;\n default: return '';\n }\n }\n /**\n * Gets the icon URL for a given saving state\n * @param {string} state - The state to get icon for ('saving', 'saved', or 'offline')\n * @returns {string} The URL of the icon image for the given state\n * @description Returns appropriate icon URL based on the current saving state\n */\n getStateIcon(state) {\n switch (state) {\n case 'saving': return Icons.cloudSave;\n case 'saved': return Icons.cloudSave;\n case 'offline': return 'data:image/svg+xml;base64,' + btoa(Icons.offline);\n default: return '';\n }\n }\n\n /**\n * Fetches and displays saved content in a dropdown\n * @async\n * @param {Event} e - The event object\n * @description Handles fetching and displaying saved content when the save state button is clicked.\n * If the dropdown is already visible, it will be closed. Otherwise it will fetch saved content\n * from the server (or use cached content if available) and display it in a dropdown panel.\n * @throws {Error} Logs error to console if fetching content fails\n */\n async fetchSavedContent(e) {\n e.preventDefault();\n\n let dropdown = document.querySelector('#savedDropdown');\n let isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n return;\n }\n let editorWrapper = null;\n if (this.module.modulename === 'quiz') {\n editorWrapper = document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`);\n } else {\n editorWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let args = {\n id: this.module.resourceId,\n cmid: this.module.cmid,\n modulename: `${this.module.modulename}_autosave`,\n editorid: this.editor?.id,\n userid: this.module.userid,\n courseid: this.module.courseid\n };\n\n call([{\n methodname: \"cursive_get_autosave_content\",\n args: args\n }])[0].done((data) => {\n let context = {comments: JSON.parse(data)};\n Object.values(context.comments).forEach(content => {\n content.time = this.timeAgo(content.timemodified);\n });\n this.renderCommentList(context, editorWrapper);\n\n }).fail((error) => {\n this.throwWarning('fullmodeerrorr', this.editor);\n window.console.error('Error fetching saved content:', error);\n });\n }\n\n /**\n * Toggles the visibility of the saved content dropdown\n * @description Checks if the saved content dropdown is currently visible and either closes or opens it accordingly.\n * If visible, calls closeSavedDropdown(). If hidden, calls openSavedDropdown().\n */\n toggleSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n const isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n } else {\n this.openSavedDropdown();\n }\n }\n\n /**\n * Opens the saved content dropdown panel\n * @description Shows the saved content dropdown by adding the 'show' class and sets up an event listener\n * for the Escape key to allow closing the dropdown. This is called when toggling the dropdown open.\n */\n openSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n dropdown.classList.add('show');\n\n // Add event listener to close on Escape key\n document.removeEventListener('keydown', this.handleEscapeKey);\n document.addEventListener('keydown', this.handleEscapeKey);\n }\n\n /**\n * Closes the saved content dropdown panel\n * @description Removes the 'show' class from the dropdown to hide it, removes the dropdown element from the DOM,\n * and removes the Escape key event listener. This is called when toggling the dropdown closed or when\n * clicking outside the dropdown.\n */\n closeSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n if (dropdown) {\n dropdown.classList.remove('show');\n dropdown.remove();\n document.removeEventListener('keydown', this.handleEscapeKey);\n }\n }\n\n /**\n * Handles the Escape key press event for closing the saved content dropdown\n * @param {KeyboardEvent} event - The keyboard event object\n * @description Event handler that checks if the Escape key was pressed and closes the saved content dropdown if it was\n */\n handleEscapeKey(event) {\n if (event.key === 'Escape') {\n this.closeSavedDropdown();\n }\n }\n\n timeAgo(unixTime) {\n const seconds = Math.floor(Date.now() / 1000) - unixTime;\n\n if (seconds < 5) {\n return \"just now\";\n }\n if (seconds < 60) {\n return `${seconds} sec ago`;\n }\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes} min ago`;\n }\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} hour${hours > 1 ? \"s\" : \"\"} ago`;\n }\n\n const days = Math.floor(hours / 24);\n if (days < 7) {\n return `${days} day${days > 1 ? \"s\" : \"\"} ago`;\n }\n\n const weeks = Math.floor(days / 7);\n if (weeks < 4) {\n return `${weeks} week${weeks > 1 ? \"s\" : \"\"} ago`;\n }\n\n const months = Math.floor(days / 30);\n if (months < 12) {\n return `${months} month${months > 1 ? \"s\" : \"\"} ago`;\n }\n\n const years = Math.floor(days / 365);\n return `${years} year${years > 1 ? \"s\" : \"\"} ago`;\n }\n\n\n /**\n * Renders the saved content dropdown list using a template\n * @param {Object} context - The context object containing saved comments data to render\n * @param {HTMLElement} editorWrapper - The wrapper element to attach the dropdown to\n * @description Renders the saved content dropdown using the tiny_cursive/saved_content template.\n * Creates and positions the dropdown relative to the editor wrapper element.\n * Handles toggling visibility and caching of the saved content.\n * @throws {Error} Logs error to console if template rendering fails\n */\n renderCommentList(context, editorWrapper) {\n templates.render('tiny_cursive/saved_content', context).then(html => {\n editorWrapper.style.position = 'relative';\n\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = html.trim();\n tempDiv.id = 'savedDropdown';\n tempDiv.classList.add('tiny_cursive-saved-dropdown');\n\n if (!tempDiv) {\n window.console.error(\"Saved content template rendered empty or invalid HTML.\");\n return false;\n }\n\n // Add to DOM if not already added\n let existingPanel = document.querySelector('#savedDropdown');\n\n if (!existingPanel) {\n editorWrapper.appendChild(tempDiv);\n existingPanel = tempDiv;\n }\n\n // Toggle visibility\n existingPanel.classList.toggle('active');\n this.openSavedDropdown();\n\n this.insertSavedItems(this.editor);\n\n return true;\n\n }).catch(error => window.console.error(error));\n }\n\n fetchStrings() {\n if (!localStorage.getItem('state')) {\n\n Promise.all([\n getString('saving', 'tiny_cursive'),\n getString('saved', 'tiny_cursive'),\n getString('offline', 'tiny_cursive')\n ]).then(function(strings) {\n return localStorage.setItem('state', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n }\n\n throwWarning(str, editor) {\n getString(str, 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key));\n }\n\n /**\n * Adds click event listeners to saved content items to insert them into the editor\n * @description Finds all elements with class 'tiny_cursive-item-preview' and adds click handlers that will\n * insert the element's text content into the editor when clicked. The text is inserted with\n * a leading space.\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {void}\n */\n insertSavedItems(editor) {\n const items = document.querySelectorAll('.tiny_cursive-item-preview');\n items.forEach(element => {\n element.addEventListener('click', function() {\n editor.insertContent(\" \" + this.textContent);\n });\n });\n }\n\n}"],"names":["CursiveAutosave","constructor","editor","rightWrapper","modules","isFullScreen","instance","module","savingState","fetchSavedContent","this","bind","handleEscapeKey","_savingTimer","fetchStrings","modulename","document","querySelector","questionid","init","stateWrapper","cursiveSavingState","classList","add","id","prepend","addEventListener","destroy","state","wrapperDiv","createElement","textSpan","button","iconSpan","style","padding","fontSize","color","textContent","getStateText","innerHTML","getStateIcon","verticalAlign","appendChild","stateTextEl","clearTimeout","setTimeout","saving","saved","offline","getText","Icons","cloudSave","btoa","e","preventDefault","dropdown","_dropdown$classList","contains","closeSavedDropdown","editorWrapper","args","resourceId","cmid","editorid","_this$editor","userid","courseid","methodname","done","data","context","comments","JSON","parse","Object","values","forEach","content","time","timeAgo","timemodified","renderCommentList","fail","error","throwWarning","window","console","toggleSavedDropdown","_dropdown$classList2","openSavedDropdown","removeEventListener","remove","event","key","unixTime","seconds","Math","floor","Date","now","minutes","hours","days","weeks","months","years","render","then","html","position","tempDiv","trim","existingPanel","toggle","insertSavedItems","catch","localStorage","getItem","Promise","all","strings","setItem","stringify","str","windowManager","alert","querySelectorAll","element","insertContent"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cursive_autosave.min.js","sources":["../src/cursive_autosave.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 * TODO describe module cursive_autosave\n *\n * @module tiny_cursive/cursive_autosave\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport templates from 'core/templates';\nimport {call} from 'core/ajax';\nimport Icons from 'tiny_cursive/svg_repo';\nimport {get_string as getString} from 'core/str';\n\nexport default class CursiveAutosave {\n\n static instance = null;\n\n\n constructor(editor, rightWrapper, modules, isFullScreen) {\n if (CursiveAutosave.instance) {\n return CursiveAutosave.instance;\n }\n\n this.editor = editor;\n this.module = modules;\n this.savingState = '';\n this.rightWrapper = rightWrapper;\n this.isFullScreen = isFullScreen;\n // Bind methods that will be used as event listener\n this.fetchSavedContent = this.fetchSavedContent.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this._savingTimer = null;\n CursiveAutosave.instance = this;\n this.fetchStrings();\n }\n\n static getInstance(editor, rightWrapper, modules, isFullScreen) {\n if (!this.instance) {\n this.instance = new CursiveAutosave(editor, rightWrapper, modules, isFullScreen);\n }\n this.instance.isFullScreen = isFullScreen;\n const hasState = modules.modulename === 'quiz'\n ? document.querySelector(`#tiny_cursive_savingState${modules.questionid}`)\n : document.querySelector('#tiny_cursive_savingState');\n if (!hasState) {\n this.instance.init();\n }\n\n return this.instance;\n }\n\n init() {\n const stateWrapper = this.cursiveSavingState(this.savingState);\n stateWrapper.classList.add('tiny_cursive_savingState', 'btn');\n if (this.module.modulename === 'quiz') {\n stateWrapper.id = `tiny_cursive_savingState${this.module.questionid}`;\n } else {\n stateWrapper.id = 'tiny_cursive_savingState';\n }\n\n this.rightWrapper.prepend(stateWrapper);\n stateWrapper.addEventListener('click', this.fetchSavedContent);\n }\n\n destroy() {\n CursiveAutosave.instance = null;\n }\n\n static destroyInstance() {\n if (this.instance) {\n this.instance.destroy();\n this.instance = null;\n }\n }\n\n /**\n * Creates a wrapper div containing an icon and text to display the saving state\n * @param {string} state - The current saving state ('saving', 'saved', or 'offline')\n * @returns {HTMLElement} A div element containing the state icon and text\n * @description Creates and returns a div element with an icon and text span to show the current saving state.\n * The icon and text are updated based on the provided state parameter.\n */\n cursiveSavingState(state) {\n let wrapperDiv = document.createElement('div');\n let textSpan = document.createElement('span');\n let button = document.createElement('button');\n let iconSpan = document.createElement('span');\n\n button.style.padding = '.3rem';\n textSpan.style.fontSize = '0.75rem';\n textSpan.style.color = 'gray';\n\n if (this.module.modulename === 'quiz') {\n iconSpan.id = `CursiveCloudIcon${this.module.questionid}`;\n textSpan.id = `CursiveStateText${this.module.questionid}`;\n } else {\n iconSpan.id = 'CursiveCloudIcon';\n textSpan.id = 'CursiveStateText';\n }\n if (state) {\n textSpan.textContent = this.getStateText(state);\n iconSpan.innerHTML = this.getStateIcon(state);\n }\n\n wrapperDiv.style.verticalAlign = 'middle';\n\n wrapperDiv.appendChild(iconSpan);\n wrapperDiv.appendChild(textSpan);\n button.appendChild(wrapperDiv);\n\n return button;\n }\n\n /**\n * Updates the saving state icon and text in the editor\n * @param {string} state - The state to update to ('saving', 'saved', or 'offline')\n * @description Updates the global saving state and modifies the UI elements to reflect the new state\n */\n static updateSavingState(state) {\n const instance = this.instance;\n instance.savingState = state;\n let stateWrapper = null;\n if (instance.module.modulename === 'quiz') {\n stateWrapper = document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`);\n } else {\n stateWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let iconSpan = '';\n let stateTextEl = '';\n\n if (!stateWrapper) {\n return;\n }\n\n if (instance.module.modulename === 'quiz') {\n iconSpan = stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`);\n stateTextEl = stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`);\n } else {\n iconSpan = stateWrapper.querySelector('#CursiveCloudIcon');\n stateTextEl = stateWrapper.querySelector('#CursiveStateText');\n }\n\n if (stateTextEl && iconSpan) {\n stateTextEl.textContent = instance.getStateText(state);\n iconSpan.innerHTML = instance.getStateIcon(state);\n }\n\n\n if (instance._savingTimer) {\n clearTimeout(instance._savingTimer);\n }\n\n if (state === 'saved' && stateTextEl) {\n instance._savingTimer = setTimeout(() => {\n stateTextEl.textContent = '';\n }, 5000);\n }\n }\n\n /**\n * Gets the display text for a given saving state\n * @param {string} state - The state to get text for ('saving', 'saved', or 'offline')\n * @returns {string} The text to display for the given state\n * @description Returns appropriate text label based on the current saving state\n */\n getStateText(state) {\n const [saving, saved, offline] = this.getText('state');\n switch (state) {\n case 'saving': return saving;\n case 'saved': return saved;\n case 'offline': return offline;\n default: return '';\n }\n }\n /**\n * Gets the icon URL for a given saving state\n * @param {string} state - The state to get icon for ('saving', 'saved', or 'offline')\n * @returns {string} The URL of the icon image for the given state\n * @description Returns appropriate icon URL based on the current saving state\n */\n getStateIcon(state) {\n switch (state) {\n case 'saving': return Icons.cloudSave;\n case 'saved': return Icons.cloudSave;\n case 'offline': return 'data:image/svg+xml;base64,' + btoa(Icons.offline);\n default: return '';\n }\n }\n\n /**\n * Fetches and displays saved content in a dropdown\n * @async\n * @param {Event} e - The event object\n * @description Handles fetching and displaying saved content when the save state button is clicked.\n * If the dropdown is already visible, it will be closed. Otherwise it will fetch saved content\n * from the server (or use cached content if available) and display it in a dropdown panel.\n * @throws {Error} Logs error to console if fetching content fails\n */\n async fetchSavedContent(e) {\n e.preventDefault();\n\n let dropdown = document.querySelector('#savedDropdown');\n let isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n return;\n }\n let editorWrapper = null;\n if (this.module.modulename === 'quiz') {\n editorWrapper = document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`);\n } else {\n editorWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let args = {\n id: this.module.resourceId,\n cmid: this.module.cmid,\n modulename: `${this.module.modulename}_autosave`,\n editorid: this.editor?.id,\n userid: this.module.userid,\n courseid: this.module.courseid\n };\n\n call([{\n methodname: \"cursive_get_autosave_content\",\n args: args\n }])[0].done((data) => {\n let context = {comments: JSON.parse(data)};\n Object.values(context.comments).forEach(content => {\n content.time = this.timeAgo(content.timemodified);\n });\n this.renderCommentList(context, editorWrapper);\n\n }).fail((error) => {\n this.throwWarning('fullmodeerrorr', this.editor);\n window.console.error('Error fetching saved content:', error);\n });\n }\n\n /**\n * Toggles the visibility of the saved content dropdown\n * @description Checks if the saved content dropdown is currently visible and either closes or opens it accordingly.\n * If visible, calls closeSavedDropdown(). If hidden, calls openSavedDropdown().\n */\n toggleSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n const isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n } else {\n this.openSavedDropdown();\n }\n }\n\n /**\n * Opens the saved content dropdown panel\n * @description Shows the saved content dropdown by adding the 'show' class and sets up an event listener\n * for the Escape key to allow closing the dropdown. This is called when toggling the dropdown open.\n */\n openSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n dropdown.classList.add('show');\n\n // Add event listener to close on Escape key\n document.removeEventListener('keydown', this.handleEscapeKey);\n document.addEventListener('keydown', this.handleEscapeKey);\n }\n\n /**\n * Closes the saved content dropdown panel\n * @description Removes the 'show' class from the dropdown to hide it, removes the dropdown element from the DOM,\n * and removes the Escape key event listener. This is called when toggling the dropdown closed or when\n * clicking outside the dropdown.\n */\n closeSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n if (dropdown) {\n dropdown.classList.remove('show');\n dropdown.remove();\n document.removeEventListener('keydown', this.handleEscapeKey);\n }\n }\n\n /**\n * Handles the Escape key press event for closing the saved content dropdown\n * @param {KeyboardEvent} event - The keyboard event object\n * @description Event handler that checks if the Escape key was pressed and closes the saved content dropdown if it was\n */\n handleEscapeKey(event) {\n if (event.key === 'Escape') {\n this.closeSavedDropdown();\n }\n }\n\n timeAgo(unixTime) {\n const seconds = Math.floor(Date.now() / 1000) - unixTime;\n\n if (seconds < 5) {\n return \"just now\";\n }\n if (seconds < 60) {\n return `${seconds} sec ago`;\n }\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes} min ago`;\n }\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} hour${hours > 1 ? \"s\" : \"\"} ago`;\n }\n\n const days = Math.floor(hours / 24);\n if (days < 7) {\n return `${days} day${days > 1 ? \"s\" : \"\"} ago`;\n }\n\n const weeks = Math.floor(days / 7);\n if (weeks < 4) {\n return `${weeks} week${weeks > 1 ? \"s\" : \"\"} ago`;\n }\n\n const months = Math.floor(days / 30);\n if (months < 12) {\n return `${months} month${months > 1 ? \"s\" : \"\"} ago`;\n }\n\n const years = Math.floor(days / 365);\n return `${years} year${years > 1 ? \"s\" : \"\"} ago`;\n }\n\n\n /**\n * Renders the saved content dropdown list using a template\n * @param {Object} context - The context object containing saved comments data to render\n * @param {HTMLElement} editorWrapper - The wrapper element to attach the dropdown to\n * @description Renders the saved content dropdown using the tiny_cursive/saved_content template.\n * Creates and positions the dropdown relative to the editor wrapper element.\n * Handles toggling visibility and caching of the saved content.\n * @throws {Error} Logs error to console if template rendering fails\n */\n renderCommentList(context, editorWrapper) {\n templates.render('tiny_cursive/saved_content', context).then(html => {\n editorWrapper.style.position = 'relative';\n\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = html.trim();\n tempDiv.id = 'savedDropdown';\n tempDiv.classList.add('tiny_cursive-saved-dropdown');\n\n if (!tempDiv) {\n window.console.error(\"Saved content template rendered empty or invalid HTML.\");\n return false;\n }\n\n // Add to DOM if not already added\n let existingPanel = document.querySelector('#savedDropdown');\n\n if (!existingPanel) {\n editorWrapper.appendChild(tempDiv);\n existingPanel = tempDiv;\n }\n\n // Toggle visibility\n existingPanel.classList.toggle('active');\n this.openSavedDropdown();\n\n this.insertSavedItems(this.editor);\n\n return true;\n\n }).catch(error => window.console.error(error));\n }\n\n fetchStrings() {\n if (!localStorage.getItem('state')) {\n\n Promise.all([\n getString('saving', 'tiny_cursive'),\n getString('saved', 'tiny_cursive'),\n getString('offline', 'tiny_cursive')\n ]).then(function(strings) {\n return localStorage.setItem('state', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n }\n\n throwWarning(str, editor) {\n getString(str, 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key));\n }\n\n /**\n * Adds click event listeners to saved content items to insert them into the editor\n * @description Finds all elements with class 'tiny_cursive-item-preview' and adds click handlers that will\n * insert the element's text content into the editor when clicked. The text is inserted with\n * a leading space.\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {void}\n */\n insertSavedItems(editor) {\n const items = document.querySelectorAll('.tiny_cursive-item-preview');\n items.forEach(element => {\n element.addEventListener('click', function() {\n editor.insertContent(\" \" + this.textContent);\n });\n });\n }\n\n}"],"names":["CursiveAutosave","constructor","editor","rightWrapper","modules","isFullScreen","instance","module","savingState","fetchSavedContent","this","bind","handleEscapeKey","_savingTimer","fetchStrings","modulename","document","querySelector","questionid","init","stateWrapper","cursiveSavingState","classList","add","id","prepend","addEventListener","destroy","state","wrapperDiv","createElement","textSpan","button","iconSpan","style","padding","fontSize","color","textContent","getStateText","innerHTML","getStateIcon","verticalAlign","appendChild","stateTextEl","clearTimeout","setTimeout","saving","saved","offline","getText","Icons","cloudSave","btoa","e","preventDefault","dropdown","_dropdown$classList","contains","closeSavedDropdown","editorWrapper","args","resourceId","cmid","editorid","_this$editor","userid","courseid","methodname","done","data","context","comments","JSON","parse","Object","values","forEach","content","time","timeAgo","timemodified","renderCommentList","fail","error","throwWarning","window","console","toggleSavedDropdown","_dropdown$classList2","openSavedDropdown","removeEventListener","remove","event","key","unixTime","seconds","Math","floor","Date","now","minutes","hours","days","weeks","months","years","render","then","html","position","tempDiv","trim","existingPanel","toggle","insertSavedItems","catch","localStorage","getItem","Promise","all","strings","setItem","stringify","str","windowManager","alert","querySelectorAll","element","insertContent"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/document_view.min.js b/amd/build/document_view.min.js index dfc9c4cb..34751df1 100644 --- a/amd/build/document_view.min.js +++ b/amd/build/document_view.min.js @@ -5,6 +5,6 @@ define("tiny_cursive/document_view",["exports","tiny_cursive/svg_repo"],(functio * @module tiny_cursive/document_view * @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.default=void 0,_svg_repo=(obj=_svg_repo)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(User,Rubrics,submission,modulename,editor,quizInfo){this.User=User,this.Rubrics=Rubrics,this.submission=submission,this.module=modulename,this.editor=editor,this.moduleIcon=_svg_repo.default.assignment,this.quizInfo=quizInfo,this.initStrings()}normalMode(){var _this$editor;let id=(null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id)+"_ifr";("assign"===this.module||"quiz"===this.module||"forum"===this.module||"lesson"===this.module||"pdfannotator"===this.module)&&this.normalizePage(id)}fullPageMode(){var _this$editor4,_this$editor2;if("assign"===this.module)this.moduleIcon=_svg_repo.default.assignment,this.fullPageModule(null===(_this$editor2=this.editor)||void 0===_this$editor2?void 0:_this$editor2.id);else if("forum"===this.module){var _this$editor3;this.moduleIcon=_svg_repo.default.forum,this.fullPageModule(null===(_this$editor3=this.editor)||void 0===_this$editor3?void 0:_this$editor3.id)}else if("quiz"===this.module&&null!==(_this$editor4=this.editor)&&void 0!==_this$editor4&&_this$editor4.id){var _this$editor5;this.moduleIcon=_svg_repo.default.quiz,this.fullPageModule(null===(_this$editor5=this.editor)||void 0===_this$editor5?void 0:_this$editor5.id)}else if("lesson"===this.module){var _this$editor6;this.moduleIcon=_svg_repo.default.lesson,this.fullPageModule(null===(_this$editor6=this.editor)||void 0===_this$editor6?void 0:_this$editor6.id)}else if("pdfannotator"===this.module){var _this$editor7;this.moduleIcon=_svg_repo.default.pdfannotator,this.fullPageModule(null===(_this$editor7=this.editor)||void 0===_this$editor7?void 0:_this$editor7.id)}}docSideBar(status){var _this$editor8;const replyId=new URL(window.location.href).searchParams.get("reply"),toggle=document.querySelector("#cursive-fullpagemode-sidebar-toggle"),timelimitBlock=this.getTimerBlock(this.module),headerInfo=this.getSidebarTitle(),progressBar=document.querySelector(".box.progress_bar"),courseName=document.querySelector("#page-navbar > nav > ol > li:nth-child(1) > a"),courseDes=document.querySelector("#intro"),Dates=document.querySelector(".activity-dates");let openDate=null==Dates?void 0:Dates.querySelector("div:nth-child(1)"),dueDate=null==Dates?void 0:Dates.querySelector("div:nth-child(2)");const container=this.create("div");Object.assign(container,{id:"cursive-fullpagemode-sidebar",className:"bg-white h-100 shadow"}),Object.assign(container.style,{width:"300px",overflow:"auto"});const crossBtn=this.create("span");Object.assign(crossBtn,{id:"cursive-collapse-sidebar",className:"btn p-2",innerHTML:_svg_repo.default.close}),crossBtn.addEventListener("click",(()=>{container.style.transition="width 0.3s ease",container.style.width="0",toggle.style.display="flex"})),null==toggle||toggle.addEventListener("click",(function(){toggle.style.display="none",container.style.width="300px"}));const btnWrapper=this.create("div");Object.assign(btnWrapper,{padding:"0 1rem",position:"sticky",top:"0",backgroundColor:"white"}),btnWrapper.append(crossBtn);const header=this.create("div");header.className="border-bottom p-3 bg-light",Object.assign(header.style,{position:"sticky",top:"0"});const headerTitle=this.create("h3");headerTitle.className="mb-3 d-flex align-items-center",headerTitle.textContent=`${headerInfo.title} ${this.details}`,headerTitle.style.fontWeight="600";const headerIcon=document.querySelector(".page-header-image > div");headerIcon&&headerTitle.prepend(headerIcon.cloneNode(!0));let wordCount=this.wordCounter(status);null!=timelimitBlock&&timelimitBlock.textContent?header.append(headerTitle,wordCount,this.timerCountDown(timelimitBlock)):header.append(headerTitle,wordCount);const content=this.create("div");if(content.className="p-3",content.append(this.createBox({bg:"bg-info",titleColor:"text-info",icon:_svg_repo.default.people,title:this.studentInfo,bodyHTML:this.generateStudentInfo(this.User,courseName)})),"lesson"===this.module&&progressBar&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.progress,bodyHTML:progressBar.innerHTML})),courseDes&&""!==(null==courseDes?void 0:courseDes.textContent.trim())){let fileSubDiv=document.querySelectorAll(".fileuploadsubmission");fileSubDiv&&fileSubDiv.forEach((Element=>{Element.style.verticalAlign="middle"})),content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:`${this.getSidebarTitle().title} ${this.description}`,bodyHTML:courseDes.innerHTML}))}if("forum"===this.module&&replyId){this.checkForumSubject();let replyPost=document.querySelector(`#post-content-${replyId}`);null!=replyPost&&replyPost.textContent.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.replyingto,bodyHTML:replyPost.textContent.trim()}))}if("quiz"===this.module&&null!==(_this$editor8=this.editor)&&void 0!==_this$editor8&&_this$editor8.id){var _this$editor9;let questionId=this.getQuestionId(null===(_this$editor9=this.editor)||void 0===_this$editor9?void 0:_this$editor9.id),question=document.querySelector(`#question-${questionId} .qtext`),intro=atob(this.quizInfo.intro);null!=question&&question.textContent.trim()&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:this.moduleIcon,title:this.answeringto,bodyHTML:question.textContent})),intro&&""!==intro.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:`${this.quiz} ${this.description}`,bodyHTML:intro})),Number(this.quizInfo.open)&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(Number(this.quizInfo.open),Number(this.quizInfo.close))}))}return Object.keys(this.Rubrics).length&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.rubrics,bodyHTML:this.generateRubrics(this.Rubrics)})),Dates&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(openDate,dueDate)})),"assign"===this.module&&content.append(this.createBox({bg:"bg-green",titleColor:"text-success",icon:this.moduleIcon,title:this.subStatus,bodyHTML:this.submissionStatus(this.submission)})),container.append(btnWrapper,header,content),container}createBox(_ref){let{bg:bg,titleColor:titleColor,icon:icon,title:title,bodyHTML:bodyHTML}=_ref;const box=this.create("div");box.className=`tiny_cursive-fullpage-card ${bg}`;const heading=this.create("h4");heading.className=`tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`,heading.innerHTML=`${icon} ${title}`;const body=this.create("div");return body.className="tiny_cursive-fullpage-card-body",body.innerHTML=bodyHTML,box.append(heading,body),box}generateRubrics(Rubrics){const wrapper=this.create("div");return Rubrics.forEach((rubric=>{const rubricDiv=this.create("div");rubricDiv.className="tiny_cursive-rubric-card";const title=this.create("h3");title.className="tiny_cursive-rubric-title",title.textContent=rubric.description,rubricDiv.appendChild(title),Object.values(rubric.levels).forEach((level=>{const levelDiv=this.create("div"),score=Number(level.score);levelDiv.className=0===score?"tiny_cursive-rubric-level tiny_cursive-rubric-low":score<=2?"tiny_cursive-rubric-level tiny_cursive-rubric-mid":"tiny_cursive-rubric-level tiny_cursive-rubric-high",levelDiv.textContent=`${level.definition} / ${level.score}`,rubricDiv.appendChild(levelDiv)})),wrapper.appendChild(rubricDiv)})),wrapper.innerHTML}submissionStatus(submission){var _submission$current,_submission$current2;const wrapper=this.create("div"),statusWrapper=this.create("div");statusWrapper.className="tiny_cursive-status-row";const statusName=this.create("span");statusName.textContent=`${this.status}:`;const statusValue=this.create("span"),isNew="new"===(null==submission||null===(_submission$current=submission.current)||void 0===_submission$current?void 0:_submission$current.status);statusValue.textContent=isNew?this.draftnot:this.draft,statusValue.className="tiny_cursive-status-value "+(isNew?"tiny_cursive-status-red":"tiny_cursive-status-green"),statusWrapper.append(statusName,statusValue);const modifiedWrapper=this.create("div");modifiedWrapper.className="tiny_cursive-status-row";const modifiedName=this.create("span");modifiedName.textContent=`${this.lastModified}: `;const modifiedValue=this.create("span");if(null!=submission&&null!==(_submission$current2=submission.current)&&void 0!==_submission$current2&&_submission$current2.timemodified){const date=new Date(1e3*submission.current.timemodified);modifiedValue.textContent=this.formatDate(date)}else modifiedValue.textContent="N/A";modifiedWrapper.append(modifiedName,modifiedValue);const gradeWrapper=this.create("div");gradeWrapper.className="tiny_cursive-status-row";const gradeName=this.create("span");gradeName.textContent=`${this.gradings}: `;const gradeValue=this.create("span");return null!=submission&&submission.grade?gradeValue.textContent=Number(submission.grade.grade)>0?submission.grade.grade:this.gradenot:gradeValue.textContent=this.gradenot,gradeWrapper.append(gradeName,gradeValue),wrapper.append(statusWrapper,gradeWrapper,modifiedWrapper),wrapper.innerHTML}wordCounter(status){const wordCount=this.create("div"),labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");icon.className="me-2",icon.innerHTML=_svg_repo.default.assignment,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent=`${this.wordCount}:`,value.textContent="0",value.className="text-primary",value.style.fontWeight="600",value.style.fontSize="14px",wordCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2",wordCount.append(labelDiv,value),wordCount.style.fontSize="12px";return new MutationObserver((()=>{const newText=status.textContent.trim();value.textContent=`${newText.replace("words","")}`})).observe(status,{characterData:!0,subtree:!0,childList:!0}),wordCount}timerCountDown(timer){let warningDiv=document.querySelector("#user-notifications > div");if(warningDiv){var _clone$querySelector;let clone=warningDiv.cloneNode(!0);null===(_clone$querySelector=clone.querySelector("button"))||void 0===_clone$querySelector||_clone$querySelector.remove(),this.editor.notificationManager.open({text:clone.textContent,type:"error"})}const timerCount=this.create("div");timerCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2";const labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");if(icon.innerHTML=_svg_repo.default.time,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent=`${this.timeleft}:`,value.textContent="00:00:00",value.className=warningDiv?"text-danger":"text-primary",Object.assign(value.style,{fontWeight:"600",fontSize:"14px"}),timerCount.append(labelDiv,value),timerCount.style.fontSize="12px",timer){new MutationObserver((()=>{const newText=timer.textContent.trim();value.textContent=`${newText}`})).observe(timer,{characterData:!0,subtree:!0,childList:!0})}else value.textContent=this.nolimit;return timerCount}generateStudentInfo(user,course){const wrapper=this.create("div"),nameWrapper=this.create("div"),usernameWrapper=this.create("div"),courseWrapper=this.create("div"),nameLabel=this.create("strong"),nameValue=this.create("span"),usernameLabel=this.create("strong"),usernameValue=this.create("span"),courseLabel=this.create("strong"),courseValue=this.create("span");return nameLabel.textContent=`${this.name}`,nameValue.textContent=user.fullname,usernameLabel.textContent=`${this.userename}: `,usernameValue.textContent=user.username,courseLabel.textContent=`${this.course}: `,courseValue.textContent=course.title,usernameLabel.className="cfw-bold me-2",usernameValue.className="cursiveFw-wrap",courseLabel.className="cfw-bold me-2",courseValue.className="cursiveFw-wrap",nameLabel.className="cfw-bold me-2",nameValue.className="cursiveFw-wrap",nameWrapper.append(nameLabel,nameValue),usernameWrapper.append(usernameLabel,usernameValue),courseWrapper.append(courseLabel,courseValue),wrapper.append(nameWrapper,usernameWrapper,courseWrapper),wrapper.innerHTML}generateImportantDates(open,due){const wrapper=this.create("div");let openDate=null,dueDate=null;const openedWrapper=this.create("div"),dueWrapper=this.create("div"),remainingWrapper=this.create("div"),openedLabel=this.create("span"),openedValue=this.create("span"),dueLabel=this.create("span"),dueValue=this.create("span"),remainingLabel=this.create("span"),remainingValue=this.create("span");return"quiz"===this.module?(openDate=1e3*open,dueDate=1e3*due):(openDate=this.extractDate(null==open?void 0:open.textContent),dueDate=this.extractDate(null==due?void 0:due.textContent)),openedLabel.textContent=`${this.opened}: `,openedValue.textContent=this.formatDate(openDate?new Date(openDate):null),openedValue.className="text-dark",dueLabel.textContent=`${this.due}: `,dueValue.textContent=this.formatDate(dueDate?new Date(dueDate):null),dueValue.className="text-danger",remainingLabel.textContent=`${this.remaining}: `,remainingValue.textContent=this.calculateDate(dueDate),remainingValue.className="text-danger",openedWrapper.className="d-flex justify-content-between",dueWrapper.className="d-flex justify-content-between",remainingWrapper.className="d-flex align-items-center justify-content-between mt-2 pt-2 border-top",openedWrapper.append(openedLabel,openedValue),dueWrapper.append(dueLabel,dueValue),remainingWrapper.append(remainingLabel,remainingValue),wrapper.append(openedWrapper,dueWrapper,remainingWrapper),wrapper.innerHTML}formatDate(date){if(!date)return"-";return date.toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"numeric",hour12:!0})}extractDate(text){if(!text)return"-";const parts=null==text?void 0:text.split(":");return parts.length>1?parts.slice(1).join(":").trim():text.trim()}calculateDate(date){if(!date)return"-";const diffMs=new Date(date)-new Date;if(diffMs<=0)return"Overdue";return`${Math.floor(diffMs/864e5)} days, ${Math.floor(diffMs/36e5%24)} hours`}fullPageModule(module){var _current$contentDocum,_current$contentWindo,_current$contentWindo2,_document$getElementB;let current="quiz"===this.module?document.getElementById(`${module}_ifr`):document.querySelector(`#${module}_ifr`),p1=current.parentElement,p2=p1.parentElement,p4=p2.parentElement.parentElement,statusBar=document.querySelector(".tox-statusbar__right-container > button"),assignName=document.querySelector(".page-context-header"),header=this.create("div"),btn=null;if(assignName.classList.remove("mb-2"),header.id="tiny_cursive-fullpage-custom-header",Object.assign(header.style,{backgroundColor:"white",display:"flex",justifyContent:"space-between"}),"quiz"===this.module?(btn=document.querySelector("#mod_quiz-next-nav").cloneNode(!0),btn.className="tiny_cursive-fullpage-submit-btn",btn.style.margin=".5rem"):(btn=this.create("input"),btn.className="tiny_cursive-fullpage-submit-btn",btn.value=this.savechanges,btn.type="submit",btn.style.margin=".5rem"),"pdfannotator"===this.module){const style=document.createElement("style");style.id="cursiveForceStyle",style.textContent="\n .path-mod-pdfannotator #comment-wrapper h4,\n .path-mod-pdfannotator #comment-nav {\n margin: 0 !important;\n }\n ",document.head.appendChild(style)}const leftSide=this.create("div"),rightSide=this.create("div");let commonStyle={display:"flex",alignItems:"center",margin:"0 1rem"};Object.assign(leftSide.style,commonStyle),rightSide.id="tiny_cursive-fullpage-right-wrapper",Object.assign(rightSide.style,commonStyle),rightSide.appendChild(btn),leftSide.appendChild(assignName.cloneNode(!0)),header.appendChild(leftSide),header.appendChild(rightSide),p4.insertBefore(header,p4.firstChild),p2.style.backgroundColor="#efefef",Object.assign(current.style,{width:"750px",minWidth:"750px",boxShadow:"0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)"}),Object.assign(p1.style,{display:"flex",justifyContent:"center",outline:"none",margin:"2rem 0 0"});const style=this.create("style");style.id="tiny_cursive-fullpage-mode-style",style.textContent="\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }",document.head.appendChild(style);let iframeBody=(null===(_current$contentDocum=current.contentDocument)||void 0===_current$contentDocum?void 0:_current$contentDocum.body)||(null===(_current$contentWindo=current.contentWindow)||void 0===_current$contentWindo||null===(_current$contentWindo2=_current$contentWindo.document)||void 0===_current$contentWindo2?void 0:_current$contentWindo2.body);iframeBody&&(iframeBody.style.padding="0.5in"),p2.style.position="relative",null===(_document$getElementB=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB||_document$getElementB.remove();let toggle=this.create("div");toggle.id="cursive-fullpagemode-sidebar-toggle",toggle.innerHTML=_svg_repo.default.hamburger,p2.appendChild(toggle),p2.appendChild(this.docSideBar(statusBar))}normalizePage(editorId){var _document$getElementB2,_document$getElementB3,_current$contentDocum2,_current$contentWindo3,_current$contentWindo4,_document$head$queryS,_document$head$queryS2;null===(_document$getElementB2=document.getElementById("tiny_cursive-fullpage-custom-header"))||void 0===_document$getElementB2||_document$getElementB2.remove(),null===(_document$getElementB3=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB3||_document$getElementB3.remove();let current=document.getElementById(editorId),p1=current.parentElement,p2=p1.parentElement;Object.assign(p2.style,{backgroundColor:"",position:""}),Object.assign(current.style,{width:"",minWidth:"",boxShadow:""}),Object.assign(p1.style,{display:"",justifyContent:"",outline:"",margin:""}),p1.classList.remove("tiny-cursive-editor-container");let iframeBody=(null===(_current$contentDocum2=current.contentDocument)||void 0===_current$contentDocum2?void 0:_current$contentDocum2.body)||(null===(_current$contentWindo3=current.contentWindow)||void 0===_current$contentWindo3||null===(_current$contentWindo4=_current$contentWindo3.document)||void 0===_current$contentWindo4?void 0:_current$contentWindo4.body);iframeBody&&(iframeBody.style.padding="0"),null===(_document$head$queryS=document.head.querySelector("#tiny_cursive-fullpage-mode-style"))||void 0===_document$head$queryS||_document$head$queryS.remove(),null===(_document$head$queryS2=document.head.querySelector("#cursiveForceStyle"))||void 0===_document$head$queryS2||_document$head$queryS2.remove()}checkForumSubject(){const form=document.querySelector("#tiny_cursive-fullpage-right-wrapper > input"),msg=this.subjectnot;form&&form.addEventListener("click",(e=>{const subjectInput=document.getElementById("id_subject");let content=this.editor.getContent().trim();subjectInput&&""!==subjectInput.value.trim()&&""!==content||(e.preventDefault(),e.stopPropagation(),this.editor.windowManager.alert(msg))}))}getSidebarTitle(){const[assign,discus,quiz,lesson]=this.getText("sbTitle");switch(this.module){case"assign":return{title:assign,icon:_svg_repo.default.assignment};case"forum":return{title:discus,icon:_svg_repo.default.forum};case"lesson":return{title:lesson,icon:_svg_repo.default.forum};case"quiz":return{title:quiz,icon:_svg_repo.default.quiz};case"pdfannotator":return{title:"PDF Annotation",icon:_svg_repo.default.pdfannotator};default:return{title:"Page",icon:_svg_repo.default.quiz}}}getTimerBlock(module){switch(module){case"assign":return document.querySelector("#mod_assign_timelimit_block > div > div");case"forum":return document.querySelector("#mod_forum_timelimit_block");case"lesson":return document.querySelector("#lesson-timer");case"quiz":return document.querySelector("#quiz-time-left");default:return null}}getQuestionId(editoId){try{return editoId&&"string"==typeof editoId?editoId.replace(/^q(\d+):(\d+)_.*$/,"$1-$2"):""}catch(error){return window.console.error("Error getting question ID:",error),""}}initStrings(){[this.details,this.studentInfo,this.progress,this.description,this.replyingto,this.answeringto,this.importantdates,this.rubrics,this.subStatus,this.status,this.draft,this.draftnot,this.lastModified,this.gradings,this.gradenot,this.wordCount,this.timeleft,this.nolimit,this.name,this.userename,this.course,this.opened,this.due,this.overdue,this.remaining,this.savechanges,this.subjectnot]=this.getText("docSideBar")}getText(key){return JSON.parse(localStorage.getItem(key))||[]}create(tag){return document.createElement(tag)}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_svg_repo=(obj=_svg_repo)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(User,Rubrics,submission,modulename,editor,quizInfo){this.User=User,this.Rubrics=Rubrics,this.submission=submission,this.module=modulename,this.editor=editor,this.moduleIcon=_svg_repo.default.assignment,this.quizInfo=quizInfo,this.initStrings()}normalMode(){var _this$editor;let id=(null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id)+"_ifr";("assign"===this.module||"quiz"===this.module||"forum"===this.module||"lesson"===this.module||"pdfannotator"===this.module)&&this.normalizePage(id)}fullPageMode(){var _this$editor4,_this$editor2;if("assign"===this.module)this.moduleIcon=_svg_repo.default.assignment,this.fullPageModule(null===(_this$editor2=this.editor)||void 0===_this$editor2?void 0:_this$editor2.id);else if("forum"===this.module){var _this$editor3;this.moduleIcon=_svg_repo.default.forum,this.fullPageModule(null===(_this$editor3=this.editor)||void 0===_this$editor3?void 0:_this$editor3.id)}else if("quiz"===this.module&&null!==(_this$editor4=this.editor)&&void 0!==_this$editor4&&_this$editor4.id){var _this$editor5;this.moduleIcon=_svg_repo.default.quiz,this.fullPageModule(null===(_this$editor5=this.editor)||void 0===_this$editor5?void 0:_this$editor5.id)}else if("lesson"===this.module){var _this$editor6;this.moduleIcon=_svg_repo.default.lesson,this.fullPageModule(null===(_this$editor6=this.editor)||void 0===_this$editor6?void 0:_this$editor6.id)}else if("pdfannotator"===this.module){var _this$editor7;this.moduleIcon=_svg_repo.default.pdfannotator,this.fullPageModule(null===(_this$editor7=this.editor)||void 0===_this$editor7?void 0:_this$editor7.id)}}docSideBar(status){var _this$editor8;const replyId=new URL(window.location.href).searchParams.get("reply"),toggle=document.querySelector("#cursive-fullpagemode-sidebar-toggle"),timelimitBlock=this.getTimerBlock(this.module),headerInfo=this.getSidebarTitle(),progressBar=document.querySelector(".box.progress_bar"),courseName=document.querySelector("#page-navbar > nav > ol > li:nth-child(1) > a"),courseDes=document.querySelector("#intro"),Dates=document.querySelector(".activity-dates");let openDate=null==Dates?void 0:Dates.querySelector("div:nth-child(1)"),dueDate=null==Dates?void 0:Dates.querySelector("div:nth-child(2)");const container=this.create("div");Object.assign(container,{id:"cursive-fullpagemode-sidebar",className:"bg-white h-100 shadow"}),Object.assign(container.style,{width:"300px",overflow:"auto"});const crossBtn=this.create("span");Object.assign(crossBtn,{id:"cursive-collapse-sidebar",className:"btn p-2",innerHTML:_svg_repo.default.close}),crossBtn.addEventListener("click",(()=>{container.style.transition="width 0.3s ease",container.style.width="0",toggle.style.display="flex"})),null==toggle||toggle.addEventListener("click",(function(){toggle.style.display="none",container.style.width="300px"}));const btnWrapper=this.create("div");Object.assign(btnWrapper,{padding:"0 1rem",position:"sticky",top:"0",backgroundColor:"white"}),btnWrapper.append(crossBtn);const header=this.create("div");header.className="border-bottom p-3 bg-light",Object.assign(header.style,{position:"sticky",top:"0"});const headerTitle=this.create("h3");headerTitle.className="mb-3 d-flex align-items-center",headerTitle.textContent="".concat(headerInfo.title," ").concat(this.details),headerTitle.style.fontWeight="600";const headerIcon=document.querySelector(".page-header-image > div");headerIcon&&headerTitle.prepend(headerIcon.cloneNode(!0));let wordCount=this.wordCounter(status);null!=timelimitBlock&&timelimitBlock.textContent?header.append(headerTitle,wordCount,this.timerCountDown(timelimitBlock)):header.append(headerTitle,wordCount);const content=this.create("div");if(content.className="p-3",content.append(this.createBox({bg:"bg-info",titleColor:"text-info",icon:_svg_repo.default.people,title:this.studentInfo,bodyHTML:this.generateStudentInfo(this.User,courseName)})),"lesson"===this.module&&progressBar&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.progress,bodyHTML:progressBar.innerHTML})),courseDes&&""!==(null==courseDes?void 0:courseDes.textContent.trim())){let fileSubDiv=document.querySelectorAll(".fileuploadsubmission");fileSubDiv&&fileSubDiv.forEach((Element=>{Element.style.verticalAlign="middle"})),content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:"".concat(this.getSidebarTitle().title," ").concat(this.description),bodyHTML:courseDes.innerHTML}))}if("forum"===this.module&&replyId){this.checkForumSubject();let replyPost=document.querySelector("#post-content-".concat(replyId));null!=replyPost&&replyPost.textContent.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.replyingto,bodyHTML:replyPost.textContent.trim()}))}if("quiz"===this.module&&null!==(_this$editor8=this.editor)&&void 0!==_this$editor8&&_this$editor8.id){var _this$editor9;let questionId=this.getQuestionId(null===(_this$editor9=this.editor)||void 0===_this$editor9?void 0:_this$editor9.id),question=document.querySelector("#question-".concat(questionId," .qtext")),intro=atob(this.quizInfo.intro);null!=question&&question.textContent.trim()&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:this.moduleIcon,title:this.answeringto,bodyHTML:question.textContent})),intro&&""!==intro.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:"".concat(this.quiz," ").concat(this.description),bodyHTML:intro})),Number(this.quizInfo.open)&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(Number(this.quizInfo.open),Number(this.quizInfo.close))}))}return Object.keys(this.Rubrics).length&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.rubrics,bodyHTML:this.generateRubrics(this.Rubrics)})),Dates&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(openDate,dueDate)})),"assign"===this.module&&content.append(this.createBox({bg:"bg-green",titleColor:"text-success",icon:this.moduleIcon,title:this.subStatus,bodyHTML:this.submissionStatus(this.submission)})),container.append(btnWrapper,header,content),container}createBox(_ref){let{bg:bg,titleColor:titleColor,icon:icon,title:title,bodyHTML:bodyHTML}=_ref;const box=this.create("div");box.className="tiny_cursive-fullpage-card ".concat(bg);const heading=this.create("h4");heading.className="tiny_cursive-fullpage-card-header ".concat(titleColor," d-flex align-items-center"),heading.innerHTML="".concat(icon," ").concat(title);const body=this.create("div");return body.className="tiny_cursive-fullpage-card-body",body.innerHTML=bodyHTML,box.append(heading,body),box}generateRubrics(Rubrics){const wrapper=this.create("div");return Rubrics.forEach((rubric=>{const rubricDiv=this.create("div");rubricDiv.className="tiny_cursive-rubric-card";const title=this.create("h3");title.className="tiny_cursive-rubric-title",title.textContent=rubric.description,rubricDiv.appendChild(title),Object.values(rubric.levels).forEach((level=>{const levelDiv=this.create("div"),score=Number(level.score);levelDiv.className=0===score?"tiny_cursive-rubric-level tiny_cursive-rubric-low":score<=2?"tiny_cursive-rubric-level tiny_cursive-rubric-mid":"tiny_cursive-rubric-level tiny_cursive-rubric-high",levelDiv.textContent="".concat(level.definition," / ").concat(level.score),rubricDiv.appendChild(levelDiv)})),wrapper.appendChild(rubricDiv)})),wrapper.innerHTML}submissionStatus(submission){var _submission$current,_submission$current2;const wrapper=this.create("div"),statusWrapper=this.create("div");statusWrapper.className="tiny_cursive-status-row";const statusName=this.create("span");statusName.textContent="".concat(this.status,":");const statusValue=this.create("span"),isNew="new"===(null==submission||null===(_submission$current=submission.current)||void 0===_submission$current?void 0:_submission$current.status);statusValue.textContent=isNew?this.draftnot:this.draft,statusValue.className="tiny_cursive-status-value ".concat(isNew?"tiny_cursive-status-red":"tiny_cursive-status-green"),statusWrapper.append(statusName,statusValue);const modifiedWrapper=this.create("div");modifiedWrapper.className="tiny_cursive-status-row";const modifiedName=this.create("span");modifiedName.textContent="".concat(this.lastModified,": ");const modifiedValue=this.create("span");if(null!=submission&&null!==(_submission$current2=submission.current)&&void 0!==_submission$current2&&_submission$current2.timemodified){const date=new Date(1e3*submission.current.timemodified);modifiedValue.textContent=this.formatDate(date)}else modifiedValue.textContent="N/A";modifiedWrapper.append(modifiedName,modifiedValue);const gradeWrapper=this.create("div");gradeWrapper.className="tiny_cursive-status-row";const gradeName=this.create("span");gradeName.textContent="".concat(this.gradings,": ");const gradeValue=this.create("span");return null!=submission&&submission.grade?gradeValue.textContent=Number(submission.grade.grade)>0?submission.grade.grade:this.gradenot:gradeValue.textContent=this.gradenot,gradeWrapper.append(gradeName,gradeValue),wrapper.append(statusWrapper,gradeWrapper,modifiedWrapper),wrapper.innerHTML}wordCounter(status){const wordCount=this.create("div"),labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");icon.className="me-2",icon.innerHTML=_svg_repo.default.assignment,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent="".concat(this.wordCount,":"),value.textContent="0",value.className="text-primary",value.style.fontWeight="600",value.style.fontSize="14px",wordCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2",wordCount.append(labelDiv,value),wordCount.style.fontSize="12px";return new MutationObserver((()=>{const newText=status.textContent.trim();value.textContent="".concat(newText.replace("words",""))})).observe(status,{characterData:!0,subtree:!0,childList:!0}),wordCount}timerCountDown(timer){let warningDiv=document.querySelector("#user-notifications > div");if(warningDiv){var _clone$querySelector;let clone=warningDiv.cloneNode(!0);null===(_clone$querySelector=clone.querySelector("button"))||void 0===_clone$querySelector||_clone$querySelector.remove(),this.editor.notificationManager.open({text:clone.textContent,type:"error"})}const timerCount=this.create("div");timerCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2";const labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");if(icon.innerHTML=_svg_repo.default.time,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent="".concat(this.timeleft,":"),value.textContent="00:00:00",value.className=warningDiv?"text-danger":"text-primary",Object.assign(value.style,{fontWeight:"600",fontSize:"14px"}),timerCount.append(labelDiv,value),timerCount.style.fontSize="12px",timer){new MutationObserver((()=>{const newText=timer.textContent.trim();value.textContent="".concat(newText)})).observe(timer,{characterData:!0,subtree:!0,childList:!0})}else value.textContent=this.nolimit;return timerCount}generateStudentInfo(user,course){const wrapper=this.create("div"),nameWrapper=this.create("div"),usernameWrapper=this.create("div"),courseWrapper=this.create("div"),nameLabel=this.create("strong"),nameValue=this.create("span"),usernameLabel=this.create("strong"),usernameValue=this.create("span"),courseLabel=this.create("strong"),courseValue=this.create("span");return nameLabel.textContent="".concat(this.name),nameValue.textContent=user.fullname,usernameLabel.textContent="".concat(this.userename,": "),usernameValue.textContent=user.username,courseLabel.textContent="".concat(this.course,": "),courseValue.textContent=course.title,usernameLabel.className="cfw-bold me-2",usernameValue.className="cursiveFw-wrap",courseLabel.className="cfw-bold me-2",courseValue.className="cursiveFw-wrap",nameLabel.className="cfw-bold me-2",nameValue.className="cursiveFw-wrap",nameWrapper.append(nameLabel,nameValue),usernameWrapper.append(usernameLabel,usernameValue),courseWrapper.append(courseLabel,courseValue),wrapper.append(nameWrapper,usernameWrapper,courseWrapper),wrapper.innerHTML}generateImportantDates(open,due){const wrapper=this.create("div");let openDate=null,dueDate=null;const openedWrapper=this.create("div"),dueWrapper=this.create("div"),remainingWrapper=this.create("div"),openedLabel=this.create("span"),openedValue=this.create("span"),dueLabel=this.create("span"),dueValue=this.create("span"),remainingLabel=this.create("span"),remainingValue=this.create("span");return"quiz"===this.module?(openDate=1e3*open,dueDate=1e3*due):(openDate=this.extractDate(null==open?void 0:open.textContent),dueDate=this.extractDate(null==due?void 0:due.textContent)),openedLabel.textContent="".concat(this.opened,": "),openedValue.textContent=this.formatDate(openDate?new Date(openDate):null),openedValue.className="text-dark",dueLabel.textContent="".concat(this.due,": "),dueValue.textContent=this.formatDate(dueDate?new Date(dueDate):null),dueValue.className="text-danger",remainingLabel.textContent="".concat(this.remaining,": "),remainingValue.textContent=this.calculateDate(dueDate),remainingValue.className="text-danger",openedWrapper.className="d-flex justify-content-between",dueWrapper.className="d-flex justify-content-between",remainingWrapper.className="d-flex align-items-center justify-content-between mt-2 pt-2 border-top",openedWrapper.append(openedLabel,openedValue),dueWrapper.append(dueLabel,dueValue),remainingWrapper.append(remainingLabel,remainingValue),wrapper.append(openedWrapper,dueWrapper,remainingWrapper),wrapper.innerHTML}formatDate(date){if(!date)return"-";return date.toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"numeric",hour12:!0})}extractDate(text){if(!text)return"-";const parts=null==text?void 0:text.split(":");return parts.length>1?parts.slice(1).join(":").trim():text.trim()}calculateDate(date){if(!date)return"-";const diffMs=new Date(date)-new Date;if(diffMs<=0)return"Overdue";{const diffDays=Math.floor(diffMs/864e5),diffHours=Math.floor(diffMs/36e5%24);return"".concat(diffDays," days, ").concat(diffHours," hours")}}fullPageModule(module){var _current$contentDocum,_current$contentWindo,_current$contentWindo2,_document$getElementB;let current="quiz"===this.module?document.getElementById("".concat(module,"_ifr")):document.querySelector("#".concat(module,"_ifr")),p1=current.parentElement,p2=p1.parentElement,p4=p2.parentElement.parentElement,statusBar=document.querySelector(".tox-statusbar__right-container > button"),assignName=document.querySelector(".page-context-header"),header=this.create("div"),btn=null;if(assignName.classList.remove("mb-2"),header.id="tiny_cursive-fullpage-custom-header",Object.assign(header.style,{backgroundColor:"white",display:"flex",justifyContent:"space-between"}),"quiz"===this.module?(btn=document.querySelector("#mod_quiz-next-nav").cloneNode(!0),btn.className="tiny_cursive-fullpage-submit-btn",btn.style.margin=".5rem"):(btn=this.create("input"),btn.className="tiny_cursive-fullpage-submit-btn",btn.value=this.savechanges,btn.type="submit",btn.style.margin=".5rem"),"pdfannotator"===this.module){const style=document.createElement("style");style.id="cursiveForceStyle",style.textContent="\n .path-mod-pdfannotator #comment-wrapper h4,\n .path-mod-pdfannotator #comment-nav {\n margin: 0 !important;\n }\n ",document.head.appendChild(style)}const leftSide=this.create("div"),rightSide=this.create("div");let commonStyle={display:"flex",alignItems:"center",margin:"0 1rem"};Object.assign(leftSide.style,commonStyle),rightSide.id="tiny_cursive-fullpage-right-wrapper",Object.assign(rightSide.style,commonStyle),rightSide.appendChild(btn),leftSide.appendChild(assignName.cloneNode(!0)),header.appendChild(leftSide),header.appendChild(rightSide),p4.insertBefore(header,p4.firstChild),p2.style.backgroundColor="#efefef",Object.assign(current.style,{width:"750px",minWidth:"750px",boxShadow:"0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)"}),Object.assign(p1.style,{display:"flex",justifyContent:"center",outline:"none",margin:"2rem 0 0"});const style=this.create("style");style.id="tiny_cursive-fullpage-mode-style",style.textContent="\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }",document.head.appendChild(style);let iframeBody=(null===(_current$contentDocum=current.contentDocument)||void 0===_current$contentDocum?void 0:_current$contentDocum.body)||(null===(_current$contentWindo=current.contentWindow)||void 0===_current$contentWindo||null===(_current$contentWindo2=_current$contentWindo.document)||void 0===_current$contentWindo2?void 0:_current$contentWindo2.body);iframeBody&&(iframeBody.style.padding="0.5in"),p2.style.position="relative",null===(_document$getElementB=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB||_document$getElementB.remove();let toggle=this.create("div");toggle.id="cursive-fullpagemode-sidebar-toggle",toggle.innerHTML=_svg_repo.default.hamburger,p2.appendChild(toggle),p2.appendChild(this.docSideBar(statusBar))}normalizePage(editorId){var _document$getElementB2,_document$getElementB3,_current$contentDocum2,_current$contentWindo3,_current$contentWindo4,_document$head$queryS,_document$head$queryS2;null===(_document$getElementB2=document.getElementById("tiny_cursive-fullpage-custom-header"))||void 0===_document$getElementB2||_document$getElementB2.remove(),null===(_document$getElementB3=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB3||_document$getElementB3.remove();let current=document.getElementById(editorId),p1=current.parentElement,p2=p1.parentElement;Object.assign(p2.style,{backgroundColor:"",position:""}),Object.assign(current.style,{width:"",minWidth:"",boxShadow:""}),Object.assign(p1.style,{display:"",justifyContent:"",outline:"",margin:""}),p1.classList.remove("tiny-cursive-editor-container");let iframeBody=(null===(_current$contentDocum2=current.contentDocument)||void 0===_current$contentDocum2?void 0:_current$contentDocum2.body)||(null===(_current$contentWindo3=current.contentWindow)||void 0===_current$contentWindo3||null===(_current$contentWindo4=_current$contentWindo3.document)||void 0===_current$contentWindo4?void 0:_current$contentWindo4.body);iframeBody&&(iframeBody.style.padding="0"),null===(_document$head$queryS=document.head.querySelector("#tiny_cursive-fullpage-mode-style"))||void 0===_document$head$queryS||_document$head$queryS.remove(),null===(_document$head$queryS2=document.head.querySelector("#cursiveForceStyle"))||void 0===_document$head$queryS2||_document$head$queryS2.remove()}checkForumSubject(){const form=document.querySelector("#tiny_cursive-fullpage-right-wrapper > input"),msg=this.subjectnot;form&&form.addEventListener("click",(e=>{const subjectInput=document.getElementById("id_subject");let content=this.editor.getContent().trim();subjectInput&&""!==subjectInput.value.trim()&&""!==content||(e.preventDefault(),e.stopPropagation(),this.editor.windowManager.alert(msg))}))}getSidebarTitle(){const[assign,discus,quiz,lesson]=this.getText("sbTitle");switch(this.module){case"assign":return{title:assign,icon:_svg_repo.default.assignment};case"forum":return{title:discus,icon:_svg_repo.default.forum};case"lesson":return{title:lesson,icon:_svg_repo.default.forum};case"quiz":return{title:quiz,icon:_svg_repo.default.quiz};case"pdfannotator":return{title:"PDF Annotation",icon:_svg_repo.default.pdfannotator};default:return{title:"Page",icon:_svg_repo.default.quiz}}}getTimerBlock(module){switch(module){case"assign":return document.querySelector("#mod_assign_timelimit_block > div > div");case"forum":return document.querySelector("#mod_forum_timelimit_block");case"lesson":return document.querySelector("#lesson-timer");case"quiz":return document.querySelector("#quiz-time-left");default:return null}}getQuestionId(editoId){try{return editoId&&"string"==typeof editoId?editoId.replace(/^q(\d+):(\d+)_.*$/,"$1-$2"):""}catch(error){return window.console.error("Error getting question ID:",error),""}}initStrings(){[this.details,this.studentInfo,this.progress,this.description,this.replyingto,this.answeringto,this.importantdates,this.rubrics,this.subStatus,this.status,this.draft,this.draftnot,this.lastModified,this.gradings,this.gradenot,this.wordCount,this.timeleft,this.nolimit,this.name,this.userename,this.course,this.opened,this.due,this.overdue,this.remaining,this.savechanges,this.subjectnot]=this.getText("docSideBar")}getText(key){return JSON.parse(localStorage.getItem(key))||[]}create(tag){return document.createElement(tag)}},_exports.default})); //# sourceMappingURL=document_view.min.js.map \ No newline at end of file diff --git a/amd/build/document_view.min.js.map b/amd/build/document_view.min.js.map index e291a61f..757f2c43 100644 --- a/amd/build/document_view.min.js.map +++ b/amd/build/document_view.min.js.map @@ -1 +1 @@ -{"version":3,"file":"document_view.min.js","sources":["../src/document_view.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 provides functionality for document view management in the Tiny editor,\n * including full page mode display and sidebar information\n * @module tiny_cursive/document_view\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Icons from 'tiny_cursive/svg_repo';\nexport default class DocumentView {\n\n constructor(User, Rubrics, submission, modulename, editor, quizInfo) {\n this.User = User;\n this.Rubrics = Rubrics;\n this.submission = submission;\n this.module = modulename;\n this.editor = editor;\n this.moduleIcon = Icons.assignment;\n this.quizInfo = quizInfo;\n this.initStrings();\n }\n\n normalMode() {\n let id = this.editor?.id + \"_ifr\";\n if (this.module === 'assign') {\n this.normalizePage(id);\n } else if (this.module === 'quiz') {\n this.normalizePage(id);\n } else if (this.module === 'forum') {\n this.normalizePage(id);\n } else if (this.module === 'lesson') {\n this.normalizePage(id);\n } else if(this.module === 'pdfannotator') {\n this.normalizePage(id);\n }\n }\n\n fullPageMode() {\n\n if (this.module === 'assign') {\n this.moduleIcon = Icons.assignment;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'forum') {\n this.moduleIcon = Icons.forum;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'quiz' && this.editor?.id) {\n this.moduleIcon = Icons.quiz;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'lesson') {\n this.moduleIcon = Icons.lesson;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'pdfannotator') {\n this.moduleIcon = Icons.pdfannotator;\n this.fullPageModule(this.editor?.id);\n }\n }\n\n docSideBar(status) {\n\n const url = new URL(window.location.href);\n const replyId = url.searchParams.get(\"reply\");\n const toggle = document.querySelector('#cursive-fullpagemode-sidebar-toggle');\n const timelimitBlock = this.getTimerBlock(this.module);\n const headerInfo = this.getSidebarTitle();\n const progressBar = document.querySelector('.box.progress_bar');\n\n const courseName = document.querySelector('#page-navbar > nav > ol > li:nth-child(1) > a');\n const courseDes = document.querySelector('#intro');\n const Dates = document.querySelector('.activity-dates');\n\n let openDate = Dates?.querySelector('div:nth-child(1)');\n let dueDate = Dates?.querySelector('div:nth-child(2)');\n\n const container = this.create('div');\n Object.assign(container, {\n id: 'cursive-fullpagemode-sidebar',\n className: 'bg-white h-100 shadow'\n });\n Object.assign(container.style, {\n width: '300px',\n overflow: 'auto'\n });\n\n const crossBtn = this.create('span');\n Object.assign(crossBtn, {\n id: 'cursive-collapse-sidebar',\n className: 'btn p-2',\n innerHTML: Icons.close\n });\n\n crossBtn.addEventListener('click', () => {\n container.style.transition = 'width 0.3s ease';\n container.style.width = '0';\n toggle.style.display = 'flex';\n });\n toggle?.addEventListener('click', function() {\n toggle.style.display = 'none';\n container.style.width = '300px';\n });\n\n const btnWrapper = this.create('div');\n Object.assign(btnWrapper, {\n padding: '0 1rem',\n position: 'sticky',\n top: '0',\n backgroundColor: 'white'\n });\n btnWrapper.append(crossBtn);\n\n\n const header = this.create('div');\n header.className = 'border-bottom p-3 bg-light';\n Object.assign(header.style, {\n position: 'sticky',\n top: '0'\n });\n\n const headerTitle = this.create('h3');\n headerTitle.className = 'mb-3 d-flex align-items-center';\n headerTitle.textContent = `${headerInfo.title} ${this.details}`;\n headerTitle.style.fontWeight = '600';\n\n const headerIcon = document.querySelector('.page-header-image > div');\n if (headerIcon) {\n headerTitle.prepend(headerIcon.cloneNode(true));\n }\n\n let wordCount = this.wordCounter(status);\n if (timelimitBlock?.textContent) {\n header.append(headerTitle, wordCount, this.timerCountDown(timelimitBlock));\n } else {\n header.append(headerTitle, wordCount);\n }\n\n const content = this.create('div');\n content.className = 'p-3';\n\n content.append(\n this.createBox({\n bg: 'bg-info',\n titleColor: 'text-info',\n icon: Icons.people,\n title: this.studentInfo,\n bodyHTML: this.generateStudentInfo(this.User, courseName)\n })\n );\n\n if (this.module === 'lesson' && progressBar) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.progress,\n bodyHTML: progressBar.innerHTML\n })\n );\n }\n\n if (courseDes && courseDes?.textContent.trim() !== '') {\n let fileSubDiv = document.querySelectorAll('.fileuploadsubmission');\n if (fileSubDiv) {\n fileSubDiv.forEach(Element => {\n Element.style.verticalAlign = 'middle';\n });\n }\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.getSidebarTitle().title} ${this.description}`,\n bodyHTML: courseDes.innerHTML\n })\n );\n }\n\n if (this.module === 'forum' && replyId) {\n this.checkForumSubject();\n let replyPost = document.querySelector(`#post-content-${replyId}`);\n if (replyPost?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.replyingto,\n bodyHTML: replyPost.textContent.trim()\n })\n );\n }\n }\n\n if (this.module === 'quiz' && this.editor?.id) {\n\n let questionId = this.getQuestionId(this.editor?.id);\n let question = document.querySelector(`#question-${questionId} .qtext`);\n let intro = atob(this.quizInfo.intro);\n\n if (question?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.answeringto,\n bodyHTML: question.textContent\n })\n );\n }\n\n if (intro && intro.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.quiz} ${this.description}`,\n bodyHTML: intro\n })\n );\n }\n\n if (Number(this.quizInfo.open)) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(Number(this.quizInfo.open), Number(this.quizInfo.close))\n })\n );\n }\n }\n\n if (Object.keys(this.Rubrics).length) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.rubrics,\n bodyHTML: this.generateRubrics(this.Rubrics)\n })\n );\n }\n\n if (Dates) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(openDate, dueDate)\n })\n );\n }\n if (this.module === 'assign') {\n content.append(\n this.createBox({\n bg: 'bg-green',\n titleColor: 'text-success',\n icon: this.moduleIcon,\n title: this.subStatus,\n bodyHTML: this.submissionStatus(this.submission)\n })\n );\n }\n\n container.append(btnWrapper, header, content);\n return container;\n\n }\n // Helper to create info boxes\n createBox({bg, titleColor, icon, title, bodyHTML}) {\n const box = this.create('div');\n box.className = `tiny_cursive-fullpage-card ${bg}`;\n\n const heading = this.create('h4');\n heading.className = `tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`;\n heading.innerHTML = `${icon} ${title}`;\n\n const body = this.create('div');\n body.className = `tiny_cursive-fullpage-card-body`;\n body.innerHTML = bodyHTML;\n\n box.append(heading, body);\n return box;\n }\n\n generateRubrics(Rubrics) {\n const wrapper = this.create('div');\n\n Rubrics.forEach(rubric => {\n const rubricDiv = this.create('div');\n rubricDiv.className = 'tiny_cursive-rubric-card';\n\n const title = this.create('h3');\n title.className = 'tiny_cursive-rubric-title';\n title.textContent = rubric.description;\n rubricDiv.appendChild(title);\n\n Object.values(rubric.levels).forEach(level => {\n const levelDiv = this.create('div');\n const score = Number(level.score);\n\n // Assign background color class based on score\n if (score === 0) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-low';\n } else if (score <= 2) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-mid';\n } else {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-high';\n }\n\n levelDiv.textContent = `${level.definition} / ${level.score}`;\n rubricDiv.appendChild(levelDiv);\n });\n\n wrapper.appendChild(rubricDiv);\n });\n\n return wrapper.innerHTML;\n }\n\n submissionStatus(submission) {\n const wrapper = this.create('div');\n\n const statusWrapper = this.create('div');\n statusWrapper.className = 'tiny_cursive-status-row';\n\n const statusName = this.create('span');\n statusName.textContent = `${this.status}:`;\n\n const statusValue = this.create('span');\n const isNew = submission?.current?.status === 'new';\n statusValue.textContent = isNew ? this.draftnot : this.draft;\n statusValue.className = `tiny_cursive-status-value ${isNew ? 'tiny_cursive-status-red' : 'tiny_cursive-status-green'}`;\n\n statusWrapper.append(statusName, statusValue);\n\n const modifiedWrapper = this.create('div');\n modifiedWrapper.className = 'tiny_cursive-status-row';\n\n const modifiedName = this.create('span');\n modifiedName.textContent = `${this.lastModified}: `;\n\n const modifiedValue = this.create('span');\n if (submission?.current?.timemodified) {\n const date = new Date(submission.current.timemodified * 1000);\n modifiedValue.textContent = this.formatDate(date);\n } else {\n modifiedValue.textContent = 'N/A';\n }\n modifiedWrapper.append(modifiedName, modifiedValue);\n\n const gradeWrapper = this.create('div');\n gradeWrapper.className = 'tiny_cursive-status-row';\n\n const gradeName = this.create('span');\n gradeName.textContent = `${this.gradings}: `;\n\n const gradeValue = this.create('span');\n\n if (submission?.grade) {\n gradeValue.textContent = Number(submission.grade.grade) > 0\n ? submission.grade.grade\n : this.gradenot;\n } else {\n gradeValue.textContent = this.gradenot;\n }\n\n gradeWrapper.append(gradeName, gradeValue);\n wrapper.append(statusWrapper, gradeWrapper, modifiedWrapper);\n return wrapper.innerHTML;\n }\n\n wordCounter(status) {\n const wordCount = this.create('div');\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n\n icon.className = 'me-2';\n icon.innerHTML = Icons.assignment;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.wordCount}:`;\n value.textContent = '0';\n value.className = 'text-primary';\n value.style.fontWeight = '600';\n value.style.fontSize = '14px';\n\n wordCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n wordCount.append(labelDiv, value);\n wordCount.style.fontSize = '12px';\n\n const observer = new MutationObserver(() => {\n const newText = status.textContent.trim();\n value.textContent = `${newText.replace('words', '')}`;\n });\n\n observer.observe(status, {\n characterData: true,\n subtree: true,\n childList: true\n });\n\n return wordCount;\n }\n\n\n timerCountDown(timer) {\n\n let warningDiv = document.querySelector('#user-notifications > div');\n if (warningDiv) {\n let clone = warningDiv.cloneNode(true);\n clone.querySelector('button')?.remove();\n this.editor.notificationManager.open({\n text: clone.textContent,\n type: 'error'\n });\n }\n\n\n const timerCount = this.create('div');\n timerCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n icon.innerHTML = Icons.time;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.timeleft}:`;\n value.textContent = '00:00:00';\n value.className = warningDiv ? 'text-danger' : 'text-primary';\n Object.assign(value.style, {\n fontWeight: '600',\n fontSize: '14px'\n });\n\n\n timerCount.append(labelDiv, value);\n timerCount.style.fontSize = '12px';\n if (timer) {\n const observer = new MutationObserver(() => {\n const newText = timer.textContent.trim();\n value.textContent = `${newText}`;\n });\n observer.observe(timer, {\n characterData: true,\n subtree: true,\n childList: true\n });\n } else {\n value.textContent = this.nolimit;\n }\n\n\n return timerCount;\n }\n\n\n generateStudentInfo(user, course) {\n\n const wrapper = this.create('div');\n\n const nameWrapper = this.create('div');\n const usernameWrapper = this.create('div');\n const courseWrapper = this.create('div');\n\n const nameLabel = this.create('strong');\n const nameValue = this.create('span');\n const usernameLabel = this.create('strong');\n const usernameValue = this.create('span');\n const courseLabel = this.create('strong');\n const courseValue = this.create('span');\n\n nameLabel.textContent = `${this.name}`;\n nameValue.textContent = user.fullname;\n\n usernameLabel.textContent = `${this.userename}: `;\n usernameValue.textContent = user.username;\n\n courseLabel.textContent = `${this.course}: `;\n courseValue.textContent = course.title;\n\n usernameLabel.className = 'cfw-bold me-2';\n usernameValue.className = 'cursiveFw-wrap';\n courseLabel.className = 'cfw-bold me-2';\n courseValue.className = 'cursiveFw-wrap';\n nameLabel.className = 'cfw-bold me-2';\n nameValue.className = 'cursiveFw-wrap';\n\n nameWrapper.append(nameLabel, nameValue);\n usernameWrapper.append(usernameLabel, usernameValue);\n courseWrapper.append(courseLabel, courseValue);\n\n wrapper.append(nameWrapper, usernameWrapper, courseWrapper);\n\n return wrapper.innerHTML;\n\n }\n\n generateImportantDates(open, due) {\n\n const wrapper = this.create('div');\n let openDate = null;\n let dueDate = null;\n\n const openedWrapper = this.create('div');\n const dueWrapper = this.create('div');\n const remainingWrapper = this.create('div');\n\n const openedLabel = this.create('span');\n const openedValue = this.create('span');\n const dueLabel = this.create('span');\n const dueValue = this.create('span');\n const remainingLabel = this.create('span');\n const remainingValue = this.create('span');\n if (this.module === 'quiz') {\n openDate = open * 1000;\n dueDate = due * 1000;\n } else {\n openDate = this.extractDate(open?.textContent);\n dueDate = this.extractDate(due?.textContent);\n }\n\n openedLabel.textContent = `${this.opened}: `;\n openedValue.textContent = this.formatDate(openDate ? new Date(openDate) : null);\n openedValue.className = 'text-dark';\n\n dueLabel.textContent = `${this.due}: `;\n dueValue.textContent = this.formatDate(dueDate ? new Date(dueDate) : null);\n dueValue.className = 'text-danger';\n\n remainingLabel.textContent = `${this.remaining}: `;\n remainingValue.textContent = this.calculateDate(dueDate);\n remainingValue.className = 'text-danger';\n\n openedWrapper.className = 'd-flex justify-content-between';\n dueWrapper.className = 'd-flex justify-content-between';\n remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top';\n\n openedWrapper.append(openedLabel, openedValue);\n dueWrapper.append(dueLabel, dueValue);\n remainingWrapper.append(remainingLabel, remainingValue);\n\n wrapper.append(openedWrapper, dueWrapper, remainingWrapper);\n\n return wrapper.innerHTML;\n }\n\n formatDate(date) {\n if (!date) {\n return '-';\n }\n\n let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true};\n return date.toLocaleString('en-US', options);\n }\n\n extractDate(text) {\n if (!text) {\n return '-';\n }\n // Split on first colon and return the right part\n const parts = text?.split(':');\n if (parts.length > 1) {\n return parts.slice(1).join(':').trim();\n }\n\n return text.trim();\n }\n\n\n calculateDate(date) {\n if (!date) {\n return '-';\n }\n const date1 = new Date(date); // Due date (local time)\n const now = new Date(); // Current date/time\n\n // Calculate the difference in milliseconds\n const diffMs = date1 - now;\n\n // Convert to days, hours, minutes\n if (diffMs <= 0) {\n return \"Overdue\";\n } else {\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n const diffHours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);\n\n return `${diffDays} days, ${diffHours} hours`;\n }\n\n }\n\n fullPageModule(module) {\n let current = this.module === 'quiz' ?\n document.getElementById(`${module}_ifr`) : document.querySelector(`#${module}_ifr`);\n\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n let p3 = p2.parentElement;\n let p4 = p3.parentElement;\n\n let statusBar = document.querySelector('.tox-statusbar__right-container > button');\n let assignName = document.querySelector('.page-context-header');\n let header = this.create('div');\n let btn = null;\n\n assignName.classList.remove('mb-2');\n header.id = 'tiny_cursive-fullpage-custom-header';\n Object.assign(header.style, {\n backgroundColor: 'white',\n display: 'flex',\n justifyContent: 'space-between'\n });\n\n if (this.module === 'quiz') {\n btn = document.querySelector('#mod_quiz-next-nav').cloneNode(true);\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.style.margin = '.5rem';\n } else {\n btn = this.create('input');\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.value = this.savechanges;\n btn.type = 'submit';\n btn.style.margin = '.5rem';\n }\n\n if (this.module === 'pdfannotator') {\n const style = document.createElement('style');\n style.id = 'cursiveForceStyle';\n style.textContent = `\n .path-mod-pdfannotator #comment-wrapper h4,\n .path-mod-pdfannotator #comment-nav {\n margin: 0 !important;\n }\n `;\n document.head.appendChild(style);\n }\n\n const leftSide = this.create('div');\n const rightSide = this.create('div');\n let commonStyle = {\n display: 'flex',\n alignItems: 'center',\n margin: '0 1rem'\n };\n\n Object.assign(leftSide.style, commonStyle);\n rightSide.id = 'tiny_cursive-fullpage-right-wrapper';\n Object.assign(rightSide.style, commonStyle);\n\n rightSide.appendChild(btn);\n leftSide.appendChild(assignName.cloneNode(true));\n\n header.appendChild(leftSide);\n header.appendChild(rightSide);\n\n p4.insertBefore(header, p4.firstChild);\n p2.style.backgroundColor = '#efefef';\n Object.assign(current.style, {\n width: '750px',\n minWidth: '750px',\n boxShadow: '0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)'\n });\n\n Object.assign(p1.style, {\n display: 'flex',\n justifyContent: 'center',\n outline: 'none',\n margin: '2rem 0 0'\n });\n const style = this.create('style');\n style.id = 'tiny_cursive-fullpage-mode-style';\n style.textContent = `\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }`;\n document.head.appendChild(style);\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n\n if (iframeBody) {\n iframeBody.style.padding = '0.5in';\n }\n p2.style.position = 'relative';\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let toggle = this.create('div');\n toggle.id = 'cursive-fullpagemode-sidebar-toggle';\n toggle.innerHTML = Icons.hamburger;\n p2.appendChild(toggle);\n p2.appendChild(this.docSideBar(statusBar));\n }\n\n normalizePage(editorId) {\n document.getElementById('tiny_cursive-fullpage-custom-header')?.remove();\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let current = document.getElementById(editorId);\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n\n Object.assign(p2.style, {\n backgroundColor: \"\",\n position: \"\"\n });\n\n Object.assign(current.style, {\n width: '',\n minWidth: '',\n boxShadow: '',\n });\n\n Object.assign(p1.style, {\n display: '',\n justifyContent: '',\n outline: '',\n margin: ''\n });\n\n p1.classList.remove('tiny-cursive-editor-container');\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n if (iframeBody) {\n iframeBody.style.padding = '0';\n }\n document.head.querySelector('#tiny_cursive-fullpage-mode-style')?.remove();\n document.head.querySelector('#cursiveForceStyle')?.remove();\n }\n\n checkForumSubject() {\n const form = document.querySelector('#tiny_cursive-fullpage-right-wrapper > input');\n const msg = this.subjectnot;\n\n if (form) {\n form.addEventListener('click', (e) => {\n const subjectInput = document.getElementById('id_subject');\n let content = this.editor.getContent().trim();\n if (!subjectInput || subjectInput.value.trim() === '' || content === '') {\n e.preventDefault();\n e.stopPropagation();\n this.editor.windowManager.alert(msg);\n }\n });\n }\n }\n\n getSidebarTitle() {\n const [assign, discus, quiz, lesson] = this.getText('sbTitle');\n switch (this.module) {\n case 'assign':\n return {title: assign, icon: Icons.assignment};\n case 'forum':\n return {title: discus, icon: Icons.forum};\n case 'lesson':\n return {title: lesson, icon: Icons.forum};\n case 'quiz':\n return {title: quiz, icon: Icons.quiz};\n case 'pdfannotator':\n return {title: 'PDF Annotation', icon: Icons.pdfannotator};\n default:\n return {title: 'Page', icon: Icons.quiz};\n }\n }\n\n getTimerBlock(module) {\n switch (module) {\n case 'assign':\n return document.querySelector('#mod_assign_timelimit_block > div > div');\n case 'forum':\n return document.querySelector('#mod_forum_timelimit_block');\n case 'lesson':\n return document.querySelector('#lesson-timer');\n case 'quiz':\n return document.querySelector('#quiz-time-left');\n default:\n return null;\n }\n }\n\n getQuestionId(editoId) {\n try {\n if (!editoId || typeof editoId !== 'string') {\n return '';\n }\n return editoId.replace(/^q(\\d+):(\\d+)_.*$/, \"$1-$2\");\n } catch (error) {\n window.console.error('Error getting question ID:', error);\n return '';\n }\n }\n\n initStrings() {\n [\n this.details,\n this.studentInfo,\n this.progress,\n this.description,\n this.replyingto,\n this.answeringto,\n this.importantdates,\n this.rubrics,\n this.subStatus,\n this.status,\n this.draft,\n this.draftnot,\n this.lastModified,\n this.gradings,\n this.gradenot,\n this.wordCount,\n this.timeleft,\n this.nolimit,\n this.name,\n this.userename,\n this.course,\n this.opened,\n this.due,\n this.overdue,\n this.remaining,\n this.savechanges,\n this.subjectnot\n ] = this.getText('docSideBar');\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key)) || [];\n }\n\n create(tag) {\n return document.createElement(tag);\n }\n\n}"],"names":["constructor","User","Rubrics","submission","modulename","editor","quizInfo","module","moduleIcon","Icons","assignment","initStrings","normalMode","id","this","normalizePage","fullPageMode","fullPageModule","_this$editor2","forum","_this$editor3","_this$editor4","quiz","_this$editor5","lesson","_this$editor6","pdfannotator","_this$editor7","docSideBar","status","replyId","URL","window","location","href","searchParams","get","toggle","document","querySelector","timelimitBlock","getTimerBlock","headerInfo","getSidebarTitle","progressBar","courseName","courseDes","Dates","openDate","dueDate","container","create","Object","assign","className","style","width","overflow","crossBtn","innerHTML","close","addEventListener","transition","display","btnWrapper","padding","position","top","backgroundColor","append","header","headerTitle","textContent","title","details","fontWeight","headerIcon","prepend","cloneNode","wordCount","wordCounter","timerCountDown","content","createBox","bg","titleColor","icon","people","studentInfo","bodyHTML","generateStudentInfo","progress","trim","fileSubDiv","querySelectorAll","forEach","Element","verticalAlign","description","checkForumSubject","replyPost","replyingto","_this$editor8","questionId","getQuestionId","_this$editor9","question","intro","atob","answeringto","Number","open","time","importantdates","generateImportantDates","keys","length","rubrics","generateRubrics","subStatus","submissionStatus","box","heading","body","wrapper","rubric","rubricDiv","appendChild","values","levels","level","levelDiv","score","definition","statusWrapper","statusName","statusValue","isNew","current","draftnot","draft","modifiedWrapper","modifiedName","lastModified","modifiedValue","_submission$current2","timemodified","date","Date","formatDate","gradeWrapper","gradeName","gradings","gradeValue","grade","gradenot","labelDiv","label","value","fontSize","MutationObserver","newText","replace","observe","characterData","subtree","childList","timer","warningDiv","clone","remove","notificationManager","text","type","timerCount","timeleft","nolimit","user","course","nameWrapper","usernameWrapper","courseWrapper","nameLabel","nameValue","usernameLabel","usernameValue","courseLabel","courseValue","name","fullname","userename","username","due","openedWrapper","dueWrapper","remainingWrapper","openedLabel","openedValue","dueLabel","dueValue","remainingLabel","remainingValue","extractDate","opened","remaining","calculateDate","toLocaleString","year","month","day","hour","minute","hour12","parts","split","slice","join","diffMs","Math","floor","getElementById","p1","parentElement","p2","p4","statusBar","assignName","btn","classList","justifyContent","margin","savechanges","createElement","head","leftSide","rightSide","commonStyle","alignItems","insertBefore","firstChild","minWidth","boxShadow","outline","iframeBody","contentDocument","contentWindow","_current$contentWindo","_current$contentWindo2","hamburger","editorId","_current$contentWindo3","_current$contentWindo4","form","msg","subjectnot","e","subjectInput","getContent","preventDefault","stopPropagation","windowManager","alert","discus","getText","editoId","error","console","overdue","key","JSON","parse","localStorage","getItem","tag"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"document_view.min.js","sources":["../src/document_view.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 provides functionality for document view management in the Tiny editor,\n * including full page mode display and sidebar information\n * @module tiny_cursive/document_view\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Icons from 'tiny_cursive/svg_repo';\nexport default class DocumentView {\n\n constructor(User, Rubrics, submission, modulename, editor, quizInfo) {\n this.User = User;\n this.Rubrics = Rubrics;\n this.submission = submission;\n this.module = modulename;\n this.editor = editor;\n this.moduleIcon = Icons.assignment;\n this.quizInfo = quizInfo;\n this.initStrings();\n }\n\n normalMode() {\n let id = this.editor?.id + \"_ifr\";\n if (this.module === 'assign') {\n this.normalizePage(id);\n } else if (this.module === 'quiz') {\n this.normalizePage(id);\n } else if (this.module === 'forum') {\n this.normalizePage(id);\n } else if (this.module === 'lesson') {\n this.normalizePage(id);\n } else if(this.module === 'pdfannotator') {\n this.normalizePage(id);\n }\n }\n\n fullPageMode() {\n\n if (this.module === 'assign') {\n this.moduleIcon = Icons.assignment;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'forum') {\n this.moduleIcon = Icons.forum;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'quiz' && this.editor?.id) {\n this.moduleIcon = Icons.quiz;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'lesson') {\n this.moduleIcon = Icons.lesson;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'pdfannotator') {\n this.moduleIcon = Icons.pdfannotator;\n this.fullPageModule(this.editor?.id);\n }\n }\n\n docSideBar(status) {\n\n const url = new URL(window.location.href);\n const replyId = url.searchParams.get(\"reply\");\n const toggle = document.querySelector('#cursive-fullpagemode-sidebar-toggle');\n const timelimitBlock = this.getTimerBlock(this.module);\n const headerInfo = this.getSidebarTitle();\n const progressBar = document.querySelector('.box.progress_bar');\n\n const courseName = document.querySelector('#page-navbar > nav > ol > li:nth-child(1) > a');\n const courseDes = document.querySelector('#intro');\n const Dates = document.querySelector('.activity-dates');\n\n let openDate = Dates?.querySelector('div:nth-child(1)');\n let dueDate = Dates?.querySelector('div:nth-child(2)');\n\n const container = this.create('div');\n Object.assign(container, {\n id: 'cursive-fullpagemode-sidebar',\n className: 'bg-white h-100 shadow'\n });\n Object.assign(container.style, {\n width: '300px',\n overflow: 'auto'\n });\n\n const crossBtn = this.create('span');\n Object.assign(crossBtn, {\n id: 'cursive-collapse-sidebar',\n className: 'btn p-2',\n innerHTML: Icons.close\n });\n\n crossBtn.addEventListener('click', () => {\n container.style.transition = 'width 0.3s ease';\n container.style.width = '0';\n toggle.style.display = 'flex';\n });\n toggle?.addEventListener('click', function() {\n toggle.style.display = 'none';\n container.style.width = '300px';\n });\n\n const btnWrapper = this.create('div');\n Object.assign(btnWrapper, {\n padding: '0 1rem',\n position: 'sticky',\n top: '0',\n backgroundColor: 'white'\n });\n btnWrapper.append(crossBtn);\n\n\n const header = this.create('div');\n header.className = 'border-bottom p-3 bg-light';\n Object.assign(header.style, {\n position: 'sticky',\n top: '0'\n });\n\n const headerTitle = this.create('h3');\n headerTitle.className = 'mb-3 d-flex align-items-center';\n headerTitle.textContent = `${headerInfo.title} ${this.details}`;\n headerTitle.style.fontWeight = '600';\n\n const headerIcon = document.querySelector('.page-header-image > div');\n if (headerIcon) {\n headerTitle.prepend(headerIcon.cloneNode(true));\n }\n\n let wordCount = this.wordCounter(status);\n if (timelimitBlock?.textContent) {\n header.append(headerTitle, wordCount, this.timerCountDown(timelimitBlock));\n } else {\n header.append(headerTitle, wordCount);\n }\n\n const content = this.create('div');\n content.className = 'p-3';\n\n content.append(\n this.createBox({\n bg: 'bg-info',\n titleColor: 'text-info',\n icon: Icons.people,\n title: this.studentInfo,\n bodyHTML: this.generateStudentInfo(this.User, courseName)\n })\n );\n\n if (this.module === 'lesson' && progressBar) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.progress,\n bodyHTML: progressBar.innerHTML\n })\n );\n }\n\n if (courseDes && courseDes?.textContent.trim() !== '') {\n let fileSubDiv = document.querySelectorAll('.fileuploadsubmission');\n if (fileSubDiv) {\n fileSubDiv.forEach(Element => {\n Element.style.verticalAlign = 'middle';\n });\n }\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.getSidebarTitle().title} ${this.description}`,\n bodyHTML: courseDes.innerHTML\n })\n );\n }\n\n if (this.module === 'forum' && replyId) {\n this.checkForumSubject();\n let replyPost = document.querySelector(`#post-content-${replyId}`);\n if (replyPost?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.replyingto,\n bodyHTML: replyPost.textContent.trim()\n })\n );\n }\n }\n\n if (this.module === 'quiz' && this.editor?.id) {\n\n let questionId = this.getQuestionId(this.editor?.id);\n let question = document.querySelector(`#question-${questionId} .qtext`);\n let intro = atob(this.quizInfo.intro);\n\n if (question?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.answeringto,\n bodyHTML: question.textContent\n })\n );\n }\n\n if (intro && intro.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.quiz} ${this.description}`,\n bodyHTML: intro\n })\n );\n }\n\n if (Number(this.quizInfo.open)) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(Number(this.quizInfo.open), Number(this.quizInfo.close))\n })\n );\n }\n }\n\n if (Object.keys(this.Rubrics).length) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.rubrics,\n bodyHTML: this.generateRubrics(this.Rubrics)\n })\n );\n }\n\n if (Dates) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(openDate, dueDate)\n })\n );\n }\n if (this.module === 'assign') {\n content.append(\n this.createBox({\n bg: 'bg-green',\n titleColor: 'text-success',\n icon: this.moduleIcon,\n title: this.subStatus,\n bodyHTML: this.submissionStatus(this.submission)\n })\n );\n }\n\n container.append(btnWrapper, header, content);\n return container;\n\n }\n // Helper to create info boxes\n createBox({bg, titleColor, icon, title, bodyHTML}) {\n const box = this.create('div');\n box.className = `tiny_cursive-fullpage-card ${bg}`;\n\n const heading = this.create('h4');\n heading.className = `tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`;\n heading.innerHTML = `${icon} ${title}`;\n\n const body = this.create('div');\n body.className = `tiny_cursive-fullpage-card-body`;\n body.innerHTML = bodyHTML;\n\n box.append(heading, body);\n return box;\n }\n\n generateRubrics(Rubrics) {\n const wrapper = this.create('div');\n\n Rubrics.forEach(rubric => {\n const rubricDiv = this.create('div');\n rubricDiv.className = 'tiny_cursive-rubric-card';\n\n const title = this.create('h3');\n title.className = 'tiny_cursive-rubric-title';\n title.textContent = rubric.description;\n rubricDiv.appendChild(title);\n\n Object.values(rubric.levels).forEach(level => {\n const levelDiv = this.create('div');\n const score = Number(level.score);\n\n // Assign background color class based on score\n if (score === 0) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-low';\n } else if (score <= 2) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-mid';\n } else {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-high';\n }\n\n levelDiv.textContent = `${level.definition} / ${level.score}`;\n rubricDiv.appendChild(levelDiv);\n });\n\n wrapper.appendChild(rubricDiv);\n });\n\n return wrapper.innerHTML;\n }\n\n submissionStatus(submission) {\n const wrapper = this.create('div');\n\n const statusWrapper = this.create('div');\n statusWrapper.className = 'tiny_cursive-status-row';\n\n const statusName = this.create('span');\n statusName.textContent = `${this.status}:`;\n\n const statusValue = this.create('span');\n const isNew = submission?.current?.status === 'new';\n statusValue.textContent = isNew ? this.draftnot : this.draft;\n statusValue.className = `tiny_cursive-status-value ${isNew ? 'tiny_cursive-status-red' : 'tiny_cursive-status-green'}`;\n\n statusWrapper.append(statusName, statusValue);\n\n const modifiedWrapper = this.create('div');\n modifiedWrapper.className = 'tiny_cursive-status-row';\n\n const modifiedName = this.create('span');\n modifiedName.textContent = `${this.lastModified}: `;\n\n const modifiedValue = this.create('span');\n if (submission?.current?.timemodified) {\n const date = new Date(submission.current.timemodified * 1000);\n modifiedValue.textContent = this.formatDate(date);\n } else {\n modifiedValue.textContent = 'N/A';\n }\n modifiedWrapper.append(modifiedName, modifiedValue);\n\n const gradeWrapper = this.create('div');\n gradeWrapper.className = 'tiny_cursive-status-row';\n\n const gradeName = this.create('span');\n gradeName.textContent = `${this.gradings}: `;\n\n const gradeValue = this.create('span');\n\n if (submission?.grade) {\n gradeValue.textContent = Number(submission.grade.grade) > 0\n ? submission.grade.grade\n : this.gradenot;\n } else {\n gradeValue.textContent = this.gradenot;\n }\n\n gradeWrapper.append(gradeName, gradeValue);\n wrapper.append(statusWrapper, gradeWrapper, modifiedWrapper);\n return wrapper.innerHTML;\n }\n\n wordCounter(status) {\n const wordCount = this.create('div');\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n\n icon.className = 'me-2';\n icon.innerHTML = Icons.assignment;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.wordCount}:`;\n value.textContent = '0';\n value.className = 'text-primary';\n value.style.fontWeight = '600';\n value.style.fontSize = '14px';\n\n wordCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n wordCount.append(labelDiv, value);\n wordCount.style.fontSize = '12px';\n\n const observer = new MutationObserver(() => {\n const newText = status.textContent.trim();\n value.textContent = `${newText.replace('words', '')}`;\n });\n\n observer.observe(status, {\n characterData: true,\n subtree: true,\n childList: true\n });\n\n return wordCount;\n }\n\n\n timerCountDown(timer) {\n\n let warningDiv = document.querySelector('#user-notifications > div');\n if (warningDiv) {\n let clone = warningDiv.cloneNode(true);\n clone.querySelector('button')?.remove();\n this.editor.notificationManager.open({\n text: clone.textContent,\n type: 'error'\n });\n }\n\n\n const timerCount = this.create('div');\n timerCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n icon.innerHTML = Icons.time;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.timeleft}:`;\n value.textContent = '00:00:00';\n value.className = warningDiv ? 'text-danger' : 'text-primary';\n Object.assign(value.style, {\n fontWeight: '600',\n fontSize: '14px'\n });\n\n\n timerCount.append(labelDiv, value);\n timerCount.style.fontSize = '12px';\n if (timer) {\n const observer = new MutationObserver(() => {\n const newText = timer.textContent.trim();\n value.textContent = `${newText}`;\n });\n observer.observe(timer, {\n characterData: true,\n subtree: true,\n childList: true\n });\n } else {\n value.textContent = this.nolimit;\n }\n\n\n return timerCount;\n }\n\n\n generateStudentInfo(user, course) {\n\n const wrapper = this.create('div');\n\n const nameWrapper = this.create('div');\n const usernameWrapper = this.create('div');\n const courseWrapper = this.create('div');\n\n const nameLabel = this.create('strong');\n const nameValue = this.create('span');\n const usernameLabel = this.create('strong');\n const usernameValue = this.create('span');\n const courseLabel = this.create('strong');\n const courseValue = this.create('span');\n\n nameLabel.textContent = `${this.name}`;\n nameValue.textContent = user.fullname;\n\n usernameLabel.textContent = `${this.userename}: `;\n usernameValue.textContent = user.username;\n\n courseLabel.textContent = `${this.course}: `;\n courseValue.textContent = course.title;\n\n usernameLabel.className = 'cfw-bold me-2';\n usernameValue.className = 'cursiveFw-wrap';\n courseLabel.className = 'cfw-bold me-2';\n courseValue.className = 'cursiveFw-wrap';\n nameLabel.className = 'cfw-bold me-2';\n nameValue.className = 'cursiveFw-wrap';\n\n nameWrapper.append(nameLabel, nameValue);\n usernameWrapper.append(usernameLabel, usernameValue);\n courseWrapper.append(courseLabel, courseValue);\n\n wrapper.append(nameWrapper, usernameWrapper, courseWrapper);\n\n return wrapper.innerHTML;\n\n }\n\n generateImportantDates(open, due) {\n\n const wrapper = this.create('div');\n let openDate = null;\n let dueDate = null;\n\n const openedWrapper = this.create('div');\n const dueWrapper = this.create('div');\n const remainingWrapper = this.create('div');\n\n const openedLabel = this.create('span');\n const openedValue = this.create('span');\n const dueLabel = this.create('span');\n const dueValue = this.create('span');\n const remainingLabel = this.create('span');\n const remainingValue = this.create('span');\n if (this.module === 'quiz') {\n openDate = open * 1000;\n dueDate = due * 1000;\n } else {\n openDate = this.extractDate(open?.textContent);\n dueDate = this.extractDate(due?.textContent);\n }\n\n openedLabel.textContent = `${this.opened}: `;\n openedValue.textContent = this.formatDate(openDate ? new Date(openDate) : null);\n openedValue.className = 'text-dark';\n\n dueLabel.textContent = `${this.due}: `;\n dueValue.textContent = this.formatDate(dueDate ? new Date(dueDate) : null);\n dueValue.className = 'text-danger';\n\n remainingLabel.textContent = `${this.remaining}: `;\n remainingValue.textContent = this.calculateDate(dueDate);\n remainingValue.className = 'text-danger';\n\n openedWrapper.className = 'd-flex justify-content-between';\n dueWrapper.className = 'd-flex justify-content-between';\n remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top';\n\n openedWrapper.append(openedLabel, openedValue);\n dueWrapper.append(dueLabel, dueValue);\n remainingWrapper.append(remainingLabel, remainingValue);\n\n wrapper.append(openedWrapper, dueWrapper, remainingWrapper);\n\n return wrapper.innerHTML;\n }\n\n formatDate(date) {\n if (!date) {\n return '-';\n }\n\n let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true};\n return date.toLocaleString('en-US', options);\n }\n\n extractDate(text) {\n if (!text) {\n return '-';\n }\n // Split on first colon and return the right part\n const parts = text?.split(':');\n if (parts.length > 1) {\n return parts.slice(1).join(':').trim();\n }\n\n return text.trim();\n }\n\n\n calculateDate(date) {\n if (!date) {\n return '-';\n }\n const date1 = new Date(date); // Due date (local time)\n const now = new Date(); // Current date/time\n\n // Calculate the difference in milliseconds\n const diffMs = date1 - now;\n\n // Convert to days, hours, minutes\n if (diffMs <= 0) {\n return \"Overdue\";\n } else {\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n const diffHours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);\n\n return `${diffDays} days, ${diffHours} hours`;\n }\n\n }\n\n fullPageModule(module) {\n let current = this.module === 'quiz' ?\n document.getElementById(`${module}_ifr`) : document.querySelector(`#${module}_ifr`);\n\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n let p3 = p2.parentElement;\n let p4 = p3.parentElement;\n\n let statusBar = document.querySelector('.tox-statusbar__right-container > button');\n let assignName = document.querySelector('.page-context-header');\n let header = this.create('div');\n let btn = null;\n\n assignName.classList.remove('mb-2');\n header.id = 'tiny_cursive-fullpage-custom-header';\n Object.assign(header.style, {\n backgroundColor: 'white',\n display: 'flex',\n justifyContent: 'space-between'\n });\n\n if (this.module === 'quiz') {\n btn = document.querySelector('#mod_quiz-next-nav').cloneNode(true);\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.style.margin = '.5rem';\n } else {\n btn = this.create('input');\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.value = this.savechanges;\n btn.type = 'submit';\n btn.style.margin = '.5rem';\n }\n\n if (this.module === 'pdfannotator') {\n const style = document.createElement('style');\n style.id = 'cursiveForceStyle';\n style.textContent = `\n .path-mod-pdfannotator #comment-wrapper h4,\n .path-mod-pdfannotator #comment-nav {\n margin: 0 !important;\n }\n `;\n document.head.appendChild(style);\n }\n\n const leftSide = this.create('div');\n const rightSide = this.create('div');\n let commonStyle = {\n display: 'flex',\n alignItems: 'center',\n margin: '0 1rem'\n };\n\n Object.assign(leftSide.style, commonStyle);\n rightSide.id = 'tiny_cursive-fullpage-right-wrapper';\n Object.assign(rightSide.style, commonStyle);\n\n rightSide.appendChild(btn);\n leftSide.appendChild(assignName.cloneNode(true));\n\n header.appendChild(leftSide);\n header.appendChild(rightSide);\n\n p4.insertBefore(header, p4.firstChild);\n p2.style.backgroundColor = '#efefef';\n Object.assign(current.style, {\n width: '750px',\n minWidth: '750px',\n boxShadow: '0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)'\n });\n\n Object.assign(p1.style, {\n display: 'flex',\n justifyContent: 'center',\n outline: 'none',\n margin: '2rem 0 0'\n });\n const style = this.create('style');\n style.id = 'tiny_cursive-fullpage-mode-style';\n style.textContent = `\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }`;\n document.head.appendChild(style);\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n\n if (iframeBody) {\n iframeBody.style.padding = '0.5in';\n }\n p2.style.position = 'relative';\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let toggle = this.create('div');\n toggle.id = 'cursive-fullpagemode-sidebar-toggle';\n toggle.innerHTML = Icons.hamburger;\n p2.appendChild(toggle);\n p2.appendChild(this.docSideBar(statusBar));\n }\n\n normalizePage(editorId) {\n document.getElementById('tiny_cursive-fullpage-custom-header')?.remove();\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let current = document.getElementById(editorId);\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n\n Object.assign(p2.style, {\n backgroundColor: \"\",\n position: \"\"\n });\n\n Object.assign(current.style, {\n width: '',\n minWidth: '',\n boxShadow: '',\n });\n\n Object.assign(p1.style, {\n display: '',\n justifyContent: '',\n outline: '',\n margin: ''\n });\n\n p1.classList.remove('tiny-cursive-editor-container');\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n if (iframeBody) {\n iframeBody.style.padding = '0';\n }\n document.head.querySelector('#tiny_cursive-fullpage-mode-style')?.remove();\n document.head.querySelector('#cursiveForceStyle')?.remove();\n }\n\n checkForumSubject() {\n const form = document.querySelector('#tiny_cursive-fullpage-right-wrapper > input');\n const msg = this.subjectnot;\n\n if (form) {\n form.addEventListener('click', (e) => {\n const subjectInput = document.getElementById('id_subject');\n let content = this.editor.getContent().trim();\n if (!subjectInput || subjectInput.value.trim() === '' || content === '') {\n e.preventDefault();\n e.stopPropagation();\n this.editor.windowManager.alert(msg);\n }\n });\n }\n }\n\n getSidebarTitle() {\n const [assign, discus, quiz, lesson] = this.getText('sbTitle');\n switch (this.module) {\n case 'assign':\n return {title: assign, icon: Icons.assignment};\n case 'forum':\n return {title: discus, icon: Icons.forum};\n case 'lesson':\n return {title: lesson, icon: Icons.forum};\n case 'quiz':\n return {title: quiz, icon: Icons.quiz};\n case 'pdfannotator':\n return {title: 'PDF Annotation', icon: Icons.pdfannotator};\n default:\n return {title: 'Page', icon: Icons.quiz};\n }\n }\n\n getTimerBlock(module) {\n switch (module) {\n case 'assign':\n return document.querySelector('#mod_assign_timelimit_block > div > div');\n case 'forum':\n return document.querySelector('#mod_forum_timelimit_block');\n case 'lesson':\n return document.querySelector('#lesson-timer');\n case 'quiz':\n return document.querySelector('#quiz-time-left');\n default:\n return null;\n }\n }\n\n getQuestionId(editoId) {\n try {\n if (!editoId || typeof editoId !== 'string') {\n return '';\n }\n return editoId.replace(/^q(\\d+):(\\d+)_.*$/, \"$1-$2\");\n } catch (error) {\n window.console.error('Error getting question ID:', error);\n return '';\n }\n }\n\n initStrings() {\n [\n this.details,\n this.studentInfo,\n this.progress,\n this.description,\n this.replyingto,\n this.answeringto,\n this.importantdates,\n this.rubrics,\n this.subStatus,\n this.status,\n this.draft,\n this.draftnot,\n this.lastModified,\n this.gradings,\n this.gradenot,\n this.wordCount,\n this.timeleft,\n this.nolimit,\n this.name,\n this.userename,\n this.course,\n this.opened,\n this.due,\n this.overdue,\n this.remaining,\n this.savechanges,\n this.subjectnot\n ] = this.getText('docSideBar');\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key)) || [];\n }\n\n create(tag) {\n return document.createElement(tag);\n }\n\n}"],"names":["constructor","User","Rubrics","submission","modulename","editor","quizInfo","module","moduleIcon","Icons","assignment","initStrings","normalMode","id","this","normalizePage","fullPageMode","fullPageModule","_this$editor2","forum","_this$editor3","_this$editor4","quiz","_this$editor5","lesson","_this$editor6","pdfannotator","_this$editor7","docSideBar","status","replyId","URL","window","location","href","searchParams","get","toggle","document","querySelector","timelimitBlock","getTimerBlock","headerInfo","getSidebarTitle","progressBar","courseName","courseDes","Dates","openDate","dueDate","container","create","Object","assign","className","style","width","overflow","crossBtn","innerHTML","close","addEventListener","transition","display","btnWrapper","padding","position","top","backgroundColor","append","header","headerTitle","textContent","title","details","fontWeight","headerIcon","prepend","cloneNode","wordCount","wordCounter","timerCountDown","content","createBox","bg","titleColor","icon","people","studentInfo","bodyHTML","generateStudentInfo","progress","trim","fileSubDiv","querySelectorAll","forEach","Element","verticalAlign","description","checkForumSubject","replyPost","replyingto","_this$editor8","questionId","getQuestionId","_this$editor9","question","intro","atob","answeringto","Number","open","time","importantdates","generateImportantDates","keys","length","rubrics","generateRubrics","subStatus","submissionStatus","box","heading","body","wrapper","rubric","rubricDiv","appendChild","values","levels","level","levelDiv","score","definition","statusWrapper","statusName","statusValue","isNew","current","draftnot","draft","modifiedWrapper","modifiedName","lastModified","modifiedValue","_submission$current2","timemodified","date","Date","formatDate","gradeWrapper","gradeName","gradings","gradeValue","grade","gradenot","labelDiv","label","value","fontSize","MutationObserver","newText","replace","observe","characterData","subtree","childList","timer","warningDiv","clone","remove","notificationManager","text","type","timerCount","timeleft","nolimit","user","course","nameWrapper","usernameWrapper","courseWrapper","nameLabel","nameValue","usernameLabel","usernameValue","courseLabel","courseValue","name","fullname","userename","username","due","openedWrapper","dueWrapper","remainingWrapper","openedLabel","openedValue","dueLabel","dueValue","remainingLabel","remainingValue","extractDate","opened","remaining","calculateDate","toLocaleString","year","month","day","hour","minute","hour12","parts","split","slice","join","diffMs","diffDays","Math","floor","diffHours","getElementById","p1","parentElement","p2","p4","statusBar","assignName","btn","classList","justifyContent","margin","savechanges","createElement","head","leftSide","rightSide","commonStyle","alignItems","insertBefore","firstChild","minWidth","boxShadow","outline","iframeBody","contentDocument","contentWindow","_current$contentWindo","_current$contentWindo2","hamburger","editorId","_current$contentWindo3","_current$contentWindo4","form","msg","subjectnot","e","subjectInput","getContent","preventDefault","stopPropagation","windowManager","alert","discus","getText","editoId","error","console","overdue","key","JSON","parse","localStorage","getItem","tag"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/replay.min.js b/amd/build/replay.min.js index 0b222771..f7306974 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","jquery","core/str"],(function(_exports,_ajax,_templates,_jquery,Str){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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_jquery=_interopRequireDefault(_jquery),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.isMetaKeyPressed=!1,this.text="",this.pastedEvents=[],this.currentPasteIndex=0,this.pastedChars=[],this.aiEvents=[],this.currentAiIndex=0,this.aiChars=[],this.undoTimestamps=[],this.undoChars=[];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.identifyUndoEvents(),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))).catch((error=>window.console.log(error))),Str.get_string("pasteEvent","tiny_cursive").then((str=>(localStorage.setItem("pasteEvent",str),str))).catch((error=>window.console.log(error))))}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")]),newElement=(0,_jquery.default)(html).text(str);return(0,_jquery.default)(".tiny_cursive").html(newElement)}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()):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),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)('a[id^="rep"]').addClass("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,metaPressed=!1,shiftPressed=!1,pasteCount=0;for(let i=0;i';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.isMetaKeyPressed=!1,this.currentPasteIndex=0,this.pastedChars=[],this.currentAiIndex=0,this.aiChars=[]),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$event3=event.event)||void 0===_event$event3?void 0:_event$event3.toLowerCase())?({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processKeydownEvent(event,text,cursor,updatedHighlights,updatedDeleted)):"aiInsert"===event.event&&({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processAiInsertEvent(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}}processAiInsertEvent(event,text,cursor,highlights,deletions){if(this.aiEvents&&this.currentAiIndex1,isNewLineInsertion=insertText.startsWith("\n")||insertText.endsWith("\n"),{wordStart:wordStart,wordEnd:wordEnd}=this.findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion),wordToReplace=text.substring(wordStart,wordEnd);this.markCharsAsDeleted(wordToReplace,wordStart,deletions);const replacedLength=wordToReplace.length;text=text.substring(0,wordStart)+insertText+text.substring(wordEnd);const positionDiff=insertText.length-replacedLength,newCursor=this.calculateNewCursorPosition(currentCursor,targetPosition,wordStart,wordEnd,insertText,isNewLineInsertion);return this.updateCharacterIndices(wordStart,wordEnd,positionDiff,insertText),{text:text,cursor:newCursor}}findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion){if(isNewLineInsertion)return{wordStart:currentCursor,wordEnd:currentCursor};const{lineStart:lineStart,lineEnd:lineEnd}=this.findLineRange(text,targetPosition),lineText=text.substring(lineStart,lineEnd),words=this.extractWordsFromLine(lineText,lineStart);return 0===words.length?{wordStart:currentCursor,wordEnd:currentCursor}:isMultiWord?this.findMultiWordMatch(words,aiWords,targetPosition):this.findSingleWordMatch(words,aiWords[0],targetPosition)}findLineRange(text,targetPosition){let lineStart=0;for(let i=targetPosition-1;i>=0;i--)if("\n"===text[i]){lineStart=i+1;break}let lineEnd=text.length;for(let i=targetPosition;i=lineText.length)break;const start=pos;for(;posstart&&words.push({text:lineText.substring(start,pos),start:lineStart+start,end:lineStart+pos})}return words}findMultiWordMatch(words,aiWords,targetPosition){let bestMatch={start:-1,end:-1,score:-1,wordCount:0,similarityScore:0};for(let i=0;ibestMatch.score||matchResult.totalScore===bestMatch.score&&matchResult.similarityScore>bestMatch.similarityScore)&&(bestMatch=matchResult)}if(bestMatch.score>10)return{wordStart:bestMatch.start,wordEnd:bestMatch.end};{const closest=this.findClosestWord(words,targetPosition);return{wordStart:closest.start,wordEnd:closest.end}}}evaluateMultiWordSequence(words,aiWords,startIndex,targetPosition){const seqWords=[];for(let j=0;j=seqStart&&targetPosition<=seqEndPos&&(positionScore+=10,targetPosition>=seqWords[0].start&&targetPosition<=seqWords[0].end&&(positionScore+=5)),positionScore}findSingleWordMatch(words,aiWord,targetPosition){const aiWordLower=aiWord.toLowerCase(),bestSimilarityMatch=this.findBestSimilarityMatch(words,aiWordLower);if(bestSimilarityMatch.score>.5)return{wordStart:bestSimilarityMatch.word.start,wordEnd:bestSimilarityMatch.word.end};const bestPositionMatch=this.findBestPositionMatch(words,aiWordLower,targetPosition);return bestPositionMatch.word?{wordStart:bestPositionMatch.word.start,wordEnd:bestPositionMatch.word.end}:this.findWordBoundaryAtPosition(words[0].start,words[words.length-1].end,targetPosition,this.text)}findBestSimilarityMatch(words,aiWordLower){let bestMatch={word:null,score:0};for(const word of words){let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),similarity>bestMatch.score&&(bestMatch={word:word,score:similarity})}return bestMatch}findBestPositionMatch(words,aiWordLower,targetPosition){let bestMatch={word:null,score:-1};for(const word of words){let score=this.calculateWordScore(word,aiWordLower,targetPosition);score>bestMatch.score&&(bestMatch={word:word,score:score})}return bestMatch}calculateWordScore(word,aiWordLower,targetPosition){let score=0;if(targetPosition>=word.start&&targetPosition<=word.end)score+=30;else{const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));score+=Math.max(0,20-distance)}let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();return wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),score+=10*similarity,score}findWordBoundaryAtPosition(lineStart,lineEnd,targetPosition,text){let wordStart=targetPosition;for(;wordStart>lineStart&&" "!==text[wordStart-1]&&"\n"!==text[wordStart-1];)wordStart--;let wordEnd=targetPosition;for(;wordEnd0)for(let i=0;i=wordStart&&targetPosition<=wordEnd)return wordStart+insertText.length;const positionDiff=insertText.length-(wordEnd-wordStart);return currentCursor>=wordEnd?currentCursor+positionDiff:currentCursor>wordStart&¤tCursorp.index>=wordEnd?{...p,index:p.index+positionDiff}:p.index>=wordStart&&p.indexnull!==p)))}markCharsAsAiInserted(wordStart,insertText){if(this.aiChars||(this.aiChars=[]),""!==insertText.trim())for(let i=0;i{if(!justAddedIndices.has(p.index)){if(p.index>=wordEnd)return{...p,index:p.index+positionDiff};if(p.index>=wordStart&&p.indexnull!==p))}calculateSimilarity(str1,str2){if(str1===str2)return 1;if(0===str1.length||0===str2.length)return 0;if(str1.startsWith(str2)||str2.startsWith(str1))return.8;const len1=str1.length,len2=str2.length,matrix=Array(len2+1).fill(null).map((()=>Array(len1+1).fill(0)));for(let i=0;i<=len1;i++)matrix[0][i]=i;for(let j=0;j<=len2;j++)matrix[j][0]=j;for(let j=1;j<=len2;j++)for(let i=1;i<=len1;i++){const cost=str1[i-1]===str2[j-1]?0:1;matrix[j][i]=Math.min(matrix[j][i-1]+1,matrix[j-1][i]+1,matrix[j-1][i-1]+cost)}const maxLen=Math.max(len1,len2);return 1-matrix[len2][len1]/maxLen}findClosestWord(words,targetPosition){if(0===words.length)return{start:targetPosition,end:targetPosition};let closest=words[0],minDistance=Math.min(Math.abs(targetPosition-words[0].start),Math.abs(targetPosition-words[0].end));for(const word of words){if(targetPosition>=word.start&&targetPosition<=word.end)return word;const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));distance0){const textBeforeUndo=text;text=text.substring(0,newPosition)+text.substring(cursor),cursor=newPosition;for(let i=0;i1}processKeyOperation(key,charToInsert,text,cursor,highlights,deletions,selection){return this.isCtrlBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleCtrlBackspace(text,cursor,deletions)):this.isCtrlDelete(key,cursor,text)?({text:text}=this.handleCtrlDelete(text,cursor,deletions)):this.isCtrlArrowMove(key)?cursor=this.handleCtrlArrowMove(key,text,cursor):this.isRegularBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleBackspace(text,cursor,deletions)):this.isRegularDelete(key,cursor,text)?({text:text}=this.handleDelete(text,cursor,deletions)):this.isArrowUp(key)?cursor=this.handleArrowUp(text,cursor):this.isArrowDown(key)?cursor=this.handleArrowDown(text,cursor):this.isRegularArrowMove(key)?cursor=this.handleArrowMove(key,text,cursor):charToInsert&&charToInsert.length>0&&(selection&&selection.length>0&&({text:text,cursor:cursor}=this.handleSelectionDeletion(selection,text,cursor,deletions)),({text:text,cursor:cursor}=this.handleCharacterInsert(charToInsert,text,cursor,highlights))),{text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}}detectSelection(eventIndex){var _currentEvent$event;const currentEvent=this.logData[eventIndex];if("keydown"===(null===(_currentEvent$event=currentEvent.event)||void 0===_currentEvent$event?void 0:_currentEvent$event.toLowerCase())&&("Backspace"===currentEvent.key||"Delete"===currentEvent.key)){const currentPos=currentEvent.rePosition;return this.processDetection(currentPos,currentEvent,eventIndex)}return null}processDetection(currentPos,currentEvent,eventIndex){for(let i=eventIndex+1;i1)return{start:Math.min(currentPos,nextPos),end:Math.max(currentPos,nextPos),length:positionDiff};if(1===positionDiff)return"Backspace"===currentEvent.key?{start:nextPos,end:currentPos,length:1}:{start:currentPos,end:nextPos,length:1};break}}return null}handleSelectionDeletion(selection,text,cursor,deletions){const{start:start,end:end,length:length}=selection;for(let i=start;ip.index>=startIndex+numDeleted?{...p,index:p.index-numDeleted}:p.index>=startIndex&&p.indexnull!==p)),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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:"Meta"===key?this.isMetaKeyPressed=!0:"v"!==key&&"V"!==key||!this.isControlKeyPressed&&!this.isMetaKeyPressed?["Control","Meta","Backspace","Delete","ArrowLeft","ArrowRight"].includes(key)||(this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.isMetaKeyPressed=!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))),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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.isMetaKeyPressed=!1,this.isPasteEvent=!1,this.pastedChars=[],this.currentPasteIndex=0,this.currentAiIndex=0,this.aiChars=[];let text="",cursor=0,highlights=[],deletions=[],pasteIndex=0,aiIndex=0;for(let i=0;itargetTime){this.currentEventIndex=i;break}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$event4=event.event)||void 0===_event$event4?void 0:_event$event4.toLowerCase())?(this.currentPasteIndex=pasteIndex,"v"!==event.key&&"V"!==event.key||!this.isControlKeyPressed&&!this.isMetaKeyPressed||pasteIndex++,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processKeydownEvent(event,text,cursor,highlights,deletions))):"aiInsert"===event.event&&(this.currentAiIndex=aiIndex,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processAiInsertEvent(event,text,cursor,highlights,deletions)),aiIndex++),this.currentEventIndex=i+1}this.currentPasteIndex=pasteIndex,this.currentAiIndex=aiIndex,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={},aiMap={},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.index{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],isAi=aiMap[currentPosition],isHighlighted=highlightMap[currentPosition]&&" "!==char;html+=isPasted&&isHighlighted?`${char}`:isAi&&isHighlighted?`${char}`:isPasted?`${" "===char?" ":this.escapeHtml(char)}`:isAi?`${" "===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","jquery","core/str"],(function(_exports,_ajax,_templates,_jquery,Str){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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_jquery=_interopRequireDefault(_jquery),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.isMetaKeyPressed=!1,this.text="",this.pastedEvents=[],this.currentPasteIndex=0,this.pastedChars=[],this.aiEvents=[],this.currentAiIndex=0,this.aiChars=[],this.undoTimestamps=[],this.undoChars=[];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.identifyUndoEvents(),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))).catch((error=>window.console.log(error))),Str.get_string("pasteEvent","tiny_cursive").then((str=>(localStorage.setItem("pasteEvent",str),str))).catch((error=>window.console.log(error))))}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")]),newElement=(0,_jquery.default)(html).text(str);return(0,_jquery.default)(".tiny_cursive").html(newElement)}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()):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),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)('a[id^="rep"]').addClass("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,metaPressed=!1,shiftPressed=!1,pasteCount=0;for(let i=0;i';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.isMetaKeyPressed=!1,this.currentPasteIndex=0,this.pastedChars=[],this.currentAiIndex=0,this.aiChars=[]),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$event3=event.event)||void 0===_event$event3?void 0:_event$event3.toLowerCase())?({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processKeydownEvent(event,text,cursor,updatedHighlights,updatedDeleted)):"aiInsert"===event.event&&({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processAiInsertEvent(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}}processAiInsertEvent(event,text,cursor,highlights,deletions){if(this.aiEvents&&this.currentAiIndex1,isNewLineInsertion=insertText.startsWith("\n")||insertText.endsWith("\n"),{wordStart:wordStart,wordEnd:wordEnd}=this.findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion),wordToReplace=text.substring(wordStart,wordEnd);this.markCharsAsDeleted(wordToReplace,wordStart,deletions);const replacedLength=wordToReplace.length;text=text.substring(0,wordStart)+insertText+text.substring(wordEnd);const positionDiff=insertText.length-replacedLength,newCursor=this.calculateNewCursorPosition(currentCursor,targetPosition,wordStart,wordEnd,insertText,isNewLineInsertion);return this.updateCharacterIndices(wordStart,wordEnd,positionDiff,insertText),{text:text,cursor:newCursor}}findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion){if(isNewLineInsertion)return{wordStart:currentCursor,wordEnd:currentCursor};const{lineStart:lineStart,lineEnd:lineEnd}=this.findLineRange(text,targetPosition),lineText=text.substring(lineStart,lineEnd),words=this.extractWordsFromLine(lineText,lineStart);return 0===words.length?{wordStart:currentCursor,wordEnd:currentCursor}:isMultiWord?this.findMultiWordMatch(words,aiWords,targetPosition):this.findSingleWordMatch(words,aiWords[0],targetPosition)}findLineRange(text,targetPosition){let lineStart=0;for(let i=targetPosition-1;i>=0;i--)if("\n"===text[i]){lineStart=i+1;break}let lineEnd=text.length;for(let i=targetPosition;i=lineText.length)break;const start=pos;for(;posstart&&words.push({text:lineText.substring(start,pos),start:lineStart+start,end:lineStart+pos})}return words}findMultiWordMatch(words,aiWords,targetPosition){let bestMatch={start:-1,end:-1,score:-1,wordCount:0,similarityScore:0};for(let i=0;ibestMatch.score||matchResult.totalScore===bestMatch.score&&matchResult.similarityScore>bestMatch.similarityScore)&&(bestMatch=matchResult)}if(bestMatch.score>10)return{wordStart:bestMatch.start,wordEnd:bestMatch.end};{const closest=this.findClosestWord(words,targetPosition);return{wordStart:closest.start,wordEnd:closest.end}}}evaluateMultiWordSequence(words,aiWords,startIndex,targetPosition){const seqWords=[];for(let j=0;j=seqStart&&targetPosition<=seqEndPos&&(positionScore+=10,targetPosition>=seqWords[0].start&&targetPosition<=seqWords[0].end&&(positionScore+=5)),positionScore}findSingleWordMatch(words,aiWord,targetPosition){const aiWordLower=aiWord.toLowerCase(),bestSimilarityMatch=this.findBestSimilarityMatch(words,aiWordLower);if(bestSimilarityMatch.score>.5)return{wordStart:bestSimilarityMatch.word.start,wordEnd:bestSimilarityMatch.word.end};const bestPositionMatch=this.findBestPositionMatch(words,aiWordLower,targetPosition);return bestPositionMatch.word?{wordStart:bestPositionMatch.word.start,wordEnd:bestPositionMatch.word.end}:this.findWordBoundaryAtPosition(words[0].start,words[words.length-1].end,targetPosition,this.text)}findBestSimilarityMatch(words,aiWordLower){let bestMatch={word:null,score:0};for(const word of words){let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),similarity>bestMatch.score&&(bestMatch={word:word,score:similarity})}return bestMatch}findBestPositionMatch(words,aiWordLower,targetPosition){let bestMatch={word:null,score:-1};for(const word of words){let score=this.calculateWordScore(word,aiWordLower,targetPosition);score>bestMatch.score&&(bestMatch={word:word,score:score})}return bestMatch}calculateWordScore(word,aiWordLower,targetPosition){let score=0;if(targetPosition>=word.start&&targetPosition<=word.end)score+=30;else{const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));score+=Math.max(0,20-distance)}let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();return wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),score+=10*similarity,score}findWordBoundaryAtPosition(lineStart,lineEnd,targetPosition,text){let wordStart=targetPosition;for(;wordStart>lineStart&&" "!==text[wordStart-1]&&"\n"!==text[wordStart-1];)wordStart--;let wordEnd=targetPosition;for(;wordEnd0)for(let i=0;i=wordStart&&targetPosition<=wordEnd)return wordStart+insertText.length;const positionDiff=insertText.length-(wordEnd-wordStart);return currentCursor>=wordEnd?currentCursor+positionDiff:currentCursor>wordStart&¤tCursorp.index>=wordEnd?{...p,index:p.index+positionDiff}:p.index>=wordStart&&p.indexnull!==p)))}markCharsAsAiInserted(wordStart,insertText){if(this.aiChars||(this.aiChars=[]),""!==insertText.trim())for(let i=0;i{if(!justAddedIndices.has(p.index)){if(p.index>=wordEnd)return{...p,index:p.index+positionDiff};if(p.index>=wordStart&&p.indexnull!==p))}calculateSimilarity(str1,str2){if(str1===str2)return 1;if(0===str1.length||0===str2.length)return 0;if(str1.startsWith(str2)||str2.startsWith(str1))return.8;const len1=str1.length,len2=str2.length,matrix=Array(len2+1).fill(null).map((()=>Array(len1+1).fill(0)));for(let i=0;i<=len1;i++)matrix[0][i]=i;for(let j=0;j<=len2;j++)matrix[j][0]=j;for(let j=1;j<=len2;j++)for(let i=1;i<=len1;i++){const cost=str1[i-1]===str2[j-1]?0:1;matrix[j][i]=Math.min(matrix[j][i-1]+1,matrix[j-1][i]+1,matrix[j-1][i-1]+cost)}const maxLen=Math.max(len1,len2);return 1-matrix[len2][len1]/maxLen}findClosestWord(words,targetPosition){if(0===words.length)return{start:targetPosition,end:targetPosition};let closest=words[0],minDistance=Math.min(Math.abs(targetPosition-words[0].start),Math.abs(targetPosition-words[0].end));for(const word of words){if(targetPosition>=word.start&&targetPosition<=word.end)return word;const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));distance0){const textBeforeUndo=text;text=text.substring(0,newPosition)+text.substring(cursor),cursor=newPosition;for(let i=0;i1}processKeyOperation(key,charToInsert,text,cursor,highlights,deletions,selection){return this.isCtrlBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleCtrlBackspace(text,cursor,deletions)):this.isCtrlDelete(key,cursor,text)?({text:text}=this.handleCtrlDelete(text,cursor,deletions)):this.isCtrlArrowMove(key)?cursor=this.handleCtrlArrowMove(key,text,cursor):this.isRegularBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleBackspace(text,cursor,deletions)):this.isRegularDelete(key,cursor,text)?({text:text}=this.handleDelete(text,cursor,deletions)):this.isArrowUp(key)?cursor=this.handleArrowUp(text,cursor):this.isArrowDown(key)?cursor=this.handleArrowDown(text,cursor):this.isRegularArrowMove(key)?cursor=this.handleArrowMove(key,text,cursor):charToInsert&&charToInsert.length>0&&(selection&&selection.length>0&&({text:text,cursor:cursor}=this.handleSelectionDeletion(selection,text,cursor,deletions)),({text:text,cursor:cursor}=this.handleCharacterInsert(charToInsert,text,cursor,highlights))),{text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}}detectSelection(eventIndex){var _currentEvent$event;const currentEvent=this.logData[eventIndex];if("keydown"===(null===(_currentEvent$event=currentEvent.event)||void 0===_currentEvent$event?void 0:_currentEvent$event.toLowerCase())&&("Backspace"===currentEvent.key||"Delete"===currentEvent.key)){const currentPos=currentEvent.rePosition;return this.processDetection(currentPos,currentEvent,eventIndex)}return null}processDetection(currentPos,currentEvent,eventIndex){for(let i=eventIndex+1;i1)return{start:Math.min(currentPos,nextPos),end:Math.max(currentPos,nextPos),length:positionDiff};if(1===positionDiff)return"Backspace"===currentEvent.key?{start:nextPos,end:currentPos,length:1}:{start:currentPos,end:nextPos,length:1};break}}return null}handleSelectionDeletion(selection,text,cursor,deletions){const{start:start,end:end,length:length}=selection;for(let i=start;ip.index>=startIndex+numDeleted?{...p,index:p.index-numDeleted}:p.index>=startIndex&&p.indexnull!==p)),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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:"Meta"===key?this.isMetaKeyPressed=!0:"v"!==key&&"V"!==key||!this.isControlKeyPressed&&!this.isMetaKeyPressed?["Control","Meta","Backspace","Delete","ArrowLeft","ArrowRight"].includes(key)||(this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.isMetaKeyPressed=!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))),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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.isMetaKeyPressed=!1,this.isPasteEvent=!1,this.pastedChars=[],this.currentPasteIndex=0,this.currentAiIndex=0,this.aiChars=[];let text="",cursor=0,highlights=[],deletions=[],pasteIndex=0,aiIndex=0;for(let i=0;itargetTime){this.currentEventIndex=i;break}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$event4=event.event)||void 0===_event$event4?void 0:_event$event4.toLowerCase())?(this.currentPasteIndex=pasteIndex,"v"!==event.key&&"V"!==event.key||!this.isControlKeyPressed&&!this.isMetaKeyPressed||pasteIndex++,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processKeydownEvent(event,text,cursor,highlights,deletions))):"aiInsert"===event.event&&(this.currentAiIndex=aiIndex,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processAiInsertEvent(event,text,cursor,highlights,deletions)),aiIndex++),this.currentEventIndex=i+1}this.currentPasteIndex=pasteIndex,this.currentAiIndex=aiIndex,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={},aiMap={},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.index{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],isAi=aiMap[currentPosition],isHighlighted=highlightMap[currentPosition]&&" "!==char;html+=isPasted&&isHighlighted?'').concat(char,""):isAi&&isHighlighted?'').concat(char,""):isPasted?''.concat(" "===char?" ":this.escapeHtml(char),""):isAi?''.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 126f99bc..681f26ef 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 kuldeep singh \n */\n\nimport {call as fetchJson} from 'core/ajax';\nimport templates from 'core/templates';\nimport $ from 'jquery';\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.isMetaKeyPressed = false;\n this.text = '';\n this.pastedEvents = [];\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.aiEvents = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\n this.undoTimestamps = [];\n this.undoChars = [];\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 this.identifyUndoEvents();\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 }).catch(error => window.console.log(error));\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\n localStorage.setItem('pasteEvent', str);\n return str;\n }).catch(error => window.console.log(error));\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') {\n if (typeof event.pastedContent === 'string' && event.pastedContent.trim() !== '') {\n this.pastedEvents.push(event.pastedContent);\n }\n }\n if (event.event === 'aiInsert' && event.aiContent) {\n this.aiEvents.push(event.aiContent);\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 newElement = $(html).text(str);\n return $('.tiny_cursive').html(newElement);\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\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 $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('a[id^=\"rep\"]').addClass('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 let metaPressed = 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 === 'Meta') {\n metaPressed = true;\n } else if (event.key === 'Shift') {\n shiftPressed = true;\n } else if ((event.key === 'v' || event.key === 'V') && (controlPressed || metaPressed)) {\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 metaPressed = false;\n } else {\n controlPressed = false;\n shiftPressed = false;\n metaPressed = false;\n }\n }\n }\n\n if (this.pasteEventsPanel) {\n this.populatePasteEventsPanel(this.pasteEventsPanel);\n }\n }\n\n identifyUndoEvents() {\n this.undoTimestamps = [];\n let controlPressed = false;\n let metaPressed = false;\n let undoCount = 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 === 'Meta') {\n metaPressed = true;\n } else if ((event.key === 'z' || event.key === 'Z') && (controlPressed || metaPressed)) {\n const timestamp = event.normalizedTime || 0;\n this.undoTimestamps.push({\n index: undoCount,\n time: timestamp,\n formattedTime: this.formatTime(timestamp),\n timestamp\n });\n undoCount++;\n controlPressed = false;\n metaPressed = false;\n } else {\n controlPressed = false;\n metaPressed = false;\n }\n }\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.isMetaKeyPressed = false;\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\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 } else if (event.event === 'aiInsert') {\n ({text, cursor, updatedHighlights, updatedDeleted} =\n this.processAiInsertEvent(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 processAiInsertEvent(event, text, cursor, highlights, deletions) {\n if (this.aiEvents && this.currentAiIndex < this.aiEvents.length) {\n const aiContent = this.aiEvents[this.currentAiIndex];\n // Use event.rePosition which points to where the word to replace is\n const targetPosition = event.rePosition;\n\n ({text, cursor} = this.handleAiReplacement(aiContent, text, targetPosition, cursor, deletions));\n this.currentAiIndex++;\n }\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n\n handleAiReplacement(aiContent, text, targetPosition, currentCursor, deletions) {\n const insertText = aiContent || '';\n const aiWords = insertText.trim().split(/\\s+/);\n const isMultiWord = aiWords.length > 1;\n const isNewLineInsertion = insertText.startsWith('\\n') || insertText.endsWith('\\n');\n\n const {wordStart, wordEnd} = this.findWordToReplace(\n text,\n targetPosition,\n currentCursor,\n aiWords,\n isMultiWord,\n isNewLineInsertion\n );\n\n const wordToReplace = text.substring(wordStart, wordEnd);\n\n // Mark replaced characters as deleted\n this.markCharsAsDeleted(wordToReplace, wordStart, deletions);\n\n // Perform the replacement\n const replacedLength = wordToReplace.length;\n text = text.substring(0, wordStart) + insertText + text.substring(wordEnd);\n const positionDiff = insertText.length - replacedLength;\n\n // Calculate new cursor position\n const newCursor = this.calculateNewCursorPosition(\n currentCursor,\n targetPosition,\n wordStart,\n wordEnd,\n insertText,\n isNewLineInsertion\n );\n\n // Update character tracking arrays\n this.updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText);\n\n return {text, cursor: newCursor};\n }\n\n findWordToReplace(text, targetPosition, currentCursor, aiWords, isMultiWord, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n const {lineStart, lineEnd} = this.findLineRange(text, targetPosition);\n const lineText = text.substring(lineStart, lineEnd);\n const words = this.extractWordsFromLine(lineText, lineStart);\n\n if (words.length === 0) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n if (isMultiWord) {\n return this.findMultiWordMatch(words, aiWords, targetPosition);\n } else {\n return this.findSingleWordMatch(words, aiWords[0], targetPosition);\n }\n }\n\n findLineRange(text, targetPosition) {\n let lineStart = 0;\n for (let i = targetPosition - 1; i >= 0; i--) {\n if (text[i] === '\\n') {\n lineStart = i + 1;\n break;\n }\n }\n\n let lineEnd = text.length;\n for (let i = targetPosition; i < text.length; i++) {\n if (text[i] === '\\n') {\n lineEnd = i;\n break;\n }\n }\n\n return {lineStart, lineEnd};\n }\n\n extractWordsFromLine(lineText, lineStart) {\n const words = [];\n let pos = 0;\n\n while (pos < lineText.length) {\n // Skip spaces\n while (pos < lineText.length && lineText[pos] === ' ') {\n pos++;\n }\n if (pos >= lineText.length) {\n break;\n }\n\n // Extract word\n const start = pos;\n while (pos < lineText.length && lineText[pos] !== ' ') {\n pos++;\n }\n\n if (pos > start) {\n words.push({\n text: lineText.substring(start, pos),\n start: lineStart + start,\n end: lineStart + pos\n });\n }\n }\n\n return words;\n }\n\n findMultiWordMatch(words, aiWords, targetPosition) {\n let bestMatch = {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n\n for (let i = 0; i < words.length; i++) {\n const matchResult = this.evaluateMultiWordSequence(words, aiWords, i, targetPosition);\n\n if (matchResult.totalScore > bestMatch.score ||\n (matchResult.totalScore === bestMatch.score &&\n matchResult.similarityScore > bestMatch.similarityScore)) {\n bestMatch = matchResult;\n }\n }\n\n if (bestMatch.score > 10) {\n return {wordStart: bestMatch.start, wordEnd: bestMatch.end};\n } else {\n const closest = this.findClosestWord(words, targetPosition);\n return {wordStart: closest.start, wordEnd: closest.end};\n }\n }\n\n evaluateMultiWordSequence(words, aiWords, startIndex, targetPosition) {\n const seqWords = [];\n for (let j = 0; j < aiWords.length && startIndex + j < words.length; j++) {\n seqWords.push(words[startIndex + j]);\n }\n\n if (seqWords.length === 0) {\n return {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n }\n\n const similarityScore = this.calculateSequenceSimilarity(aiWords, seqWords);\n const positionScore = this.calculatePositionScore(seqWords, targetPosition);\n const totalScore = similarityScore + positionScore + seqWords.length;\n\n return {\n start: seqWords[0].start,\n end: seqWords[seqWords.length - 1].end,\n score: totalScore,\n wordCount: seqWords.length,\n similarityScore: similarityScore\n };\n }\n\n calculateSequenceSimilarity(aiWords, seqWords) {\n let similarityScore = 0;\n const compareLength = Math.min(seqWords.length, aiWords.length);\n\n for (let k = 0; k < compareLength; k++) {\n const ai = aiWords[k].toLowerCase();\n const seq = seqWords[k].text.toLowerCase();\n\n if (ai === seq) {\n similarityScore += 10;\n } else {\n const similarity = this.calculateSimilarity(ai, seq);\n similarityScore += similarity * 10;\n }\n }\n\n return similarityScore;\n }\n\n calculatePositionScore(seqWords, targetPosition) {\n let positionScore = 0;\n const seqStart = seqWords[0].start;\n const seqEndPos = seqWords[seqWords.length - 1].end;\n\n if (targetPosition >= seqStart && targetPosition <= seqEndPos) {\n positionScore += 10;\n if (targetPosition >= seqWords[0].start && targetPosition <= seqWords[0].end) {\n positionScore += 5;\n }\n }\n\n return positionScore;\n }\n\n findSingleWordMatch(words, aiWord, targetPosition) {\n const aiWordLower = aiWord.toLowerCase();\n const bestSimilarityMatch = this.findBestSimilarityMatch(words, aiWordLower);\n\n if (bestSimilarityMatch.score > 0.5) {\n return {wordStart: bestSimilarityMatch.word.start, wordEnd: bestSimilarityMatch.word.end};\n }\n\n const bestPositionMatch = this.findBestPositionMatch(words, aiWordLower, targetPosition);\n\n if (bestPositionMatch.word) {\n return {wordStart: bestPositionMatch.word.start, wordEnd: bestPositionMatch.word.end};\n }\n\n // Fallback to position-based word boundary\n return this.findWordBoundaryAtPosition(words[0].start, words[words.length - 1].end,\n targetPosition, this.text);\n }\n\n findBestSimilarityMatch(words, aiWordLower) {\n let bestMatch = {word: null, score: 0};\n\n for (const word of words) {\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n\n // Penalize short prefix matches\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n\n if (similarity > bestMatch.score) {\n bestMatch = {word, score: similarity};\n }\n }\n\n return bestMatch;\n }\n\n findBestPositionMatch(words, aiWordLower, targetPosition) {\n let bestMatch = {word: null, score: -1};\n\n for (const word of words) {\n let score = this.calculateWordScore(word, aiWordLower, targetPosition);\n\n if (score > bestMatch.score) {\n bestMatch = {word, score};\n }\n }\n\n return bestMatch;\n }\n\n calculateWordScore(word, aiWordLower, targetPosition) {\n let score = 0;\n\n // Position score\n if (targetPosition >= word.start && targetPosition <= word.end) {\n score += 30;\n } else {\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n score += Math.max(0, 20 - distance);\n }\n\n // Similarity score with penalty\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n score += similarity * 10;\n\n return score;\n }\n\n findWordBoundaryAtPosition(lineStart, lineEnd, targetPosition, text) {\n let wordStart = targetPosition;\n while (wordStart > lineStart && text[wordStart - 1] !== ' ' && text[wordStart - 1] !== '\\n') {\n wordStart--;\n }\n let wordEnd = targetPosition;\n while (wordEnd < lineEnd && text[wordEnd] !== ' ' && text[wordEnd] !== '\\n') {\n wordEnd++;\n }\n return {wordStart, wordEnd};\n }\n\n markCharsAsDeleted(wordToReplace, wordStart, deletions) {\n if (wordToReplace.length > 0) {\n for (let i = 0; i < wordToReplace.length; i++) {\n deletions.push({\n index: wordStart + i,\n chars: wordToReplace[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n\n calculateNewCursorPosition(currentCursor, targetPosition, wordStart, wordEnd, insertText, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return wordStart + insertText.length;\n }\n\n if (targetPosition >= wordStart && targetPosition <= wordEnd) {\n return wordStart + insertText.length;\n }\n\n const positionDiff = insertText.length - (wordEnd - wordStart);\n\n if (currentCursor >= wordEnd) {\n return currentCursor + positionDiff;\n } else if (currentCursor > wordStart && currentCursor < wordEnd) {\n return wordStart + insertText.length;\n }\n\n return currentCursor;\n }\n\n updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText) {\n // Update pasted character indices\n this.updatePastedCharIndices(wordStart, wordEnd, positionDiff);\n\n // Mark characters as AI-inserted\n this.markCharsAsAiInserted(wordStart, insertText);\n\n // Update AI character indices\n this.updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText);\n }\n\n updatePastedCharIndices(wordStart, wordEnd, positionDiff) {\n if (this.pastedChars) {\n this.pastedChars = this.pastedChars.map(p => {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n return p;\n }).filter(p => p !== null);\n }\n }\n\n markCharsAsAiInserted(wordStart, insertText) {\n if (!this.aiChars) {\n this.aiChars = [];\n }\n\n if (insertText.trim() !== '') {\n for (let i = 0; i < insertText.length; i++) {\n this.aiChars.push({\n index: wordStart + i,\n chars: insertText[i]\n });\n }\n }\n }\n\n updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText) {\n const justAddedIndices = new Set();\n for (let i = 0; i < insertText.length; i++) {\n justAddedIndices.add(wordStart + i);\n }\n\n this.aiChars = this.aiChars.map(p => {\n if (!justAddedIndices.has(p.index)) {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n }\n return p;\n }).filter(p => p !== null);\n }\n\n // Calculate similarity between two strings\n calculateSimilarity(str1, str2) {\n if (str1 === str2) {\n return 1;\n }\n if (str1.length === 0 || str2.length === 0) {\n return 0;\n }\n\n // Check if one string is a prefix of the other\n if (str1.startsWith(str2) || str2.startsWith(str1)) {\n return 0.8;\n }\n\n // Levenshtein distance\n const len1 = str1.length;\n const len2 = str2.length;\n const matrix = Array(len2 + 1).fill(null).map(() => Array(len1 + 1).fill(0));\n\n for (let i = 0; i <= len1; i++) {\n matrix[0][i] = i;\n }\n for (let j = 0; j <= len2; j++) {\n matrix[j][0] = j;\n }\n\n for (let j = 1; j <= len2; j++) {\n for (let i = 1; i <= len1; i++) {\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\n matrix[j][i] = Math.min(\n matrix[j][i - 1] + 1,\n matrix[j - 1][i] + 1,\n matrix[j - 1][i - 1] + cost\n );\n }\n }\n\n const maxLen = Math.max(len1, len2);\n return 1 - (matrix[len2][len1] / maxLen);\n }\n\n // Find the word closest to a target position\n findClosestWord(words, targetPosition) {\n if (words.length === 0) {\n return {start: targetPosition, end: targetPosition};\n }\n\n let closest = words[0];\n let minDistance = Math.min(\n Math.abs(targetPosition - words[0].start),\n Math.abs(targetPosition - words[0].end)\n );\n\n for (const word of words) {\n if (targetPosition >= word.start && targetPosition <= word.end) {\n return word;\n }\n\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n\n if (distance < minDistance) {\n minDistance = distance;\n closest = word;\n }\n }\n\n return closest;\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\n // Handle copy operation (Ctrl+C)\n if (this.isCopyOperation(key)) {\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Handle undo operation (Ctrl+Z)\n if (this.isUndoOperation(key)) {\n return this.handleUndoOperation(event, text, cursor, highlights, deletions);\n }\n\n // Detect selection for current event\n const currentEventIndex = this.currentEventIndex;\n const selection = this.detectSelection(currentEventIndex);\n\n // Handle paste operation (Ctrl+V)\n if (this.isPasteOperation(key, event)) {\n return this.handlePasteOperation(event, selection, text, cursor, highlights, deletions);\n }\n\n // Update modifier key states\n this.updateModifierStates(key);\n\n // Handle selection deletion with Backspace/Delete\n if (this.isSelectionDeletion(key, selection)) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Process various key operations\n return this.processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection);\n }\n\n isCopyOperation(key) {\n return (key === 'c' || key === 'C') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n isUndoOperation(key) {\n return (key === 'z' || key === 'Z') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n handleUndoOperation(event, text, cursor, highlights, deletions) {\n const nextEventIndex = this.currentEventIndex + 1;\n if (nextEventIndex < this.logData.length) {\n const nextEvent = this.logData[nextEventIndex];\n\n if (nextEvent.event === 'keyUp' && (nextEvent.key === 'z' || nextEvent.key === 'Z')) {\n const newPosition = nextEvent.rePosition;\n if (newPosition < cursor && text.length > 0) {\n const textBeforeUndo = text;\n text = text.substring(0, newPosition) + text.substring(cursor);\n cursor = newPosition;\n\n // Mark as deleted for visual effect\n for (let i = 0; i < textBeforeUndo.length && i < cursor; i++) {\n deletions.push({\n index: newPosition,\n chars: textBeforeUndo[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n }\n\n this.isControlKeyPressed = false;\n this.isMetaKeyPressed = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n isPasteOperation(key, event) {\n if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n return (event.pastedContent && event.pastedContent.trim() !== '') ||\n (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length);\n }\n return false;\n }\n\n handlePasteOperation(event, selection, text, cursor, highlights, deletions) {\n const pastedContent = event.pastedContent || this.pastedEvents[this.currentPasteIndex];\n\n if (selection) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\n this.currentPasteIndex++;\n this.resetModifierStates();\n this.isPasteEvent = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n resetModifierStates() {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = false;\n }\n\n isSelectionDeletion(key, selection) {\n return (key === 'Backspace' || key === 'Delete') && selection && selection.length > 1;\n }\n\n processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection) {\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 if (selection && selection.length > 0) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\n }\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n detectSelection(eventIndex) {\n const currentEvent = this.logData[eventIndex];\n\n if (currentEvent.event?.toLowerCase() === 'keydown' &&\n (currentEvent.key === 'Backspace' || currentEvent.key === 'Delete')) {\n\n const currentPos = currentEvent.rePosition;\n return this.processDetection(currentPos, currentEvent, eventIndex);\n }\n return null;\n }\n\n processDetection(currentPos, currentEvent, eventIndex) {\n for (let i = eventIndex + 1; i < this.logData.length; i++) {\n const nextEvent = this.logData[i];\n\n if (nextEvent.event?.toLowerCase() === 'keyup' &&\n nextEvent.key === currentEvent.key) {\n\n const nextPos = nextEvent.rePosition;\n\n // Calculate the difference in positions\n const positionDiff = Math.abs(currentPos - nextPos);\n\n if (positionDiff > 1) {\n return {\n start: Math.min(currentPos, nextPos),\n end: Math.max(currentPos, nextPos),\n length: positionDiff\n };\n } else if (positionDiff === 1) {\n if (currentEvent.key === 'Backspace') {\n return {\n start: nextPos,\n end: currentPos,\n length: 1\n };\n } else {\n return {\n start: currentPos,\n end: nextPos,\n length: 1\n };\n }\n }\n break;\n }\n }\n return null;\n }\n\n handleSelectionDeletion(selection, text, cursor, deletions) {\n const {start, end, length} = selection;\n\n // Add each character in the selection to the deletions array\n for (let i = start; i < end && i < text.length; i++) {\n deletions.push({\n index: start,\n chars: text[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n\n text = text.substring(0, start) + text.substring(end);\n\n this.shiftPastedCharsIndices(start, length);\n\n cursor = start;\n\n return {text, cursor};\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 if (this.aiChars) {\n this.aiChars = this.aiChars.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 return null;\n }\n return p;\n }).filter(p => p !== null);\n }\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 === 'Meta') {\n this.isMetaKeyPressed = true;\n } else if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n this.isPasteEvent = true;\n } else if (!['Control', 'Meta', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = 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 (this.aiChars) {\n this.aiChars = this.aiChars.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.isMetaKeyPressed = false;\n this.isPasteEvent = false;\n this.pastedChars = [];\n this.currentPasteIndex = 0;\n this.currentAiIndex = 0;\n this.aiChars = [];\n let text = '';\n let cursor = 0;\n let highlights = [];\n let deletions = [];\n let pasteIndex = 0;\n let aiIndex = 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 && (this.currentEventIndex === 0 ||\n 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 || this.isMetaKeyPressed)) {\n pasteIndex++;\n }\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\n } else if (event.event === 'aiInsert') {\n this.currentAiIndex = aiIndex;\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processAiInsertEvent(event, text, cursor, highlights, deletions));\n aiIndex++;\n }\n this.currentEventIndex = i + 1;\n }\n\n this.currentPasteIndex = pasteIndex;\n this.currentAiIndex = aiIndex;\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 // eslint-disable-next-line complexity\n updateDisplayText(text, cursorPosition, highlights, deletions) {\n let html = '';\n const highlightMap = {};\n const deletionMap = {};\n const pastedMap = {};\n const aiMap = {};\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 // Process AI characters for styling\n if (this.aiChars) {\n this.aiChars.forEach(p => {\n if (p.index < text.length) {\n aiMap[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 isAi = aiMap[currentPosition];\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\n\n if (isPasted && isHighlighted) {\n html += `${char}`;\n } else if (isAi && isHighlighted) {\n html += `${char}`;\n } else if (isPasted) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isAi) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isHighlighted) {\n html += `${char}`;\n } else {\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","isMetaKeyPressed","text","pastedEvents","currentPasteIndex","pastedChars","aiEvents","currentAiIndex","aiChars","undoTimestamps","undoChars","element","document","getElementById","Error","outputElement","loadJSON","then","data","status","processData","this","logData","length","identifyPasteEvents","identifyUndoEvents","constructController","startReplay","handleNoSubmission","catch","error","window","console","message","localStorage","getItem","Str","get_string","str","setItem","log","JSON","parse","comments","Array","isArray","payload","i","event","pastedContent","trim","push","aiContent","unixTimestamp","startTime","map","normalizedTime","html","Promise","all","templates","render","newElement","stopReplay","clearTimeout","replayTimeout","playButton","playSvg","createElement","src","M","util","image_url","querySelector","innerHTML","outerHTML","currentPosition","replayIntervalId","clearInterval","container","controlContainer","buildControllerUI","remove","topRow","classList","add","createPlayButton","appendChild","scrubberContainer","createScrubberContainer","timeDisplay","createTimeDisplay","bottomRow","speedContainer","createSpeedControls","pasteEventsToggle","createPasteEventsToggle","pasteEventsPanel","addEventListener","find","removeClass","addClass","scrubberElement","type","max","min","value","skipToTime","parseInt","textContent","speedLabel","speedGroup","forEach","speedBtn","dataset","querySelectorAll","btn","pasteEventsIcon","pasteIcon","pasteEventsText","pasteEventCount","className","style","marginLeft","chevronIcon","chevron","transition","createPasteEventsPanel","isHidden","display","transform","existingPanel","populatePasteEventsPanel","controlPressed","metaPressed","shiftPressed","pasteCount","toLowerCase","key","timestamp","index","time","formattedTime","formatTime","pastedText","undoCount","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","processAiInsertEvent","filter","h","expiresAt","d","updateDisplayText","percentComplete","baseIncrement","incrementTime","setTimeout","getLineAndColumn","pos","before","substring","lineIndex","split","col","lastIndexOf","highlights","deletions","targetPosition","handleAiReplacement","currentCursor","insertText","aiWords","isMultiWord","isNewLineInsertion","startsWith","endsWith","wordStart","wordEnd","findWordToReplace","wordToReplace","markCharsAsDeleted","replacedLength","positionDiff","newCursor","calculateNewCursorPosition","updateCharacterIndices","lineStart","lineEnd","findLineRange","lineText","words","extractWordsFromLine","findMultiWordMatch","findSingleWordMatch","start","end","bestMatch","score","wordCount","similarityScore","matchResult","evaluateMultiWordSequence","totalScore","closest","findClosestWord","startIndex","seqWords","j","calculateSequenceSimilarity","calculatePositionScore","compareLength","k","ai","seq","calculateSimilarity","positionScore","seqStart","seqEndPos","aiWord","aiWordLower","bestSimilarityMatch","findBestSimilarityMatch","word","bestPositionMatch","findBestPositionMatch","findWordBoundaryAtPosition","similarity","wordLower","calculateWordScore","distance","abs","chars","updatePastedCharIndices","markCharsAsAiInserted","updateAiCharIndices","p","justAddedIndices","Set","has","str1","str2","len1","len2","matrix","fill","cost","maxLen","minDistance","charToInsert","applyKey","isCopyOperation","isUndoOperation","handleUndoOperation","selection","detectSelection","isPasteOperation","handlePasteOperation","updateModifierStates","isSelectionDeletion","handleSelectionDeletion","processKeyOperation","nextEventIndex","nextEvent","newPosition","textBeforeUndo","handlePasteInsert","resetModifierStates","isCtrlBackspace","handleCtrlBackspace","isCtrlDelete","handleCtrlDelete","isCtrlArrowMove","handleCtrlArrowMove","isRegularBackspace","handleBackspace","isRegularDelete","handleDelete","isArrowUp","handleArrowUp","isArrowDown","handleArrowDown","isRegularArrowMove","handleArrowMove","handleCharacterInsert","eventIndex","currentEvent","currentPos","processDetection","nextPos","shiftPastedCharsIndices","numDeleted","includes","findPreviousWordBoundary","findNextWordBoundary","wordToDelete","lines","prevLine","slice","join","nextLine","lastNonSpace","skipToEnd","textOutput","wasPlaying","targetTime","pasteIndex","aiIndex","highlightMap","deletionMap","pastedMap","aiMap","outOfRangeDeletions","textLines","line","char","isPasted","isAi","isHighlighted","escapeHtml","sort","a","b","cursorHTML","cursorPos","deletedWordHTML","wasScrolledToBottom","scrollHeight","clientHeight","scrollTop","isCursorBelowViewport","cursorElement","cursorRect","getBoundingClientRect","outputRect","bottom","unsafe","replace"],"mappings":"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"} \ 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 kuldeep singh \n */\n\nimport {call as fetchJson} from 'core/ajax';\nimport templates from 'core/templates';\nimport $ from 'jquery';\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.isMetaKeyPressed = false;\n this.text = '';\n this.pastedEvents = [];\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.aiEvents = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\n this.undoTimestamps = [];\n this.undoChars = [];\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 this.identifyUndoEvents();\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 }).catch(error => window.console.log(error));\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\n localStorage.setItem('pasteEvent', str);\n return str;\n }).catch(error => window.console.log(error));\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') {\n if (typeof event.pastedContent === 'string' && event.pastedContent.trim() !== '') {\n this.pastedEvents.push(event.pastedContent);\n }\n }\n if (event.event === 'aiInsert' && event.aiContent) {\n this.aiEvents.push(event.aiContent);\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 newElement = $(html).text(str);\n return $('.tiny_cursive').html(newElement);\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\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 $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('a[id^=\"rep\"]').addClass('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 let metaPressed = 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 === 'Meta') {\n metaPressed = true;\n } else if (event.key === 'Shift') {\n shiftPressed = true;\n } else if ((event.key === 'v' || event.key === 'V') && (controlPressed || metaPressed)) {\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 metaPressed = false;\n } else {\n controlPressed = false;\n shiftPressed = false;\n metaPressed = false;\n }\n }\n }\n\n if (this.pasteEventsPanel) {\n this.populatePasteEventsPanel(this.pasteEventsPanel);\n }\n }\n\n identifyUndoEvents() {\n this.undoTimestamps = [];\n let controlPressed = false;\n let metaPressed = false;\n let undoCount = 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 === 'Meta') {\n metaPressed = true;\n } else if ((event.key === 'z' || event.key === 'Z') && (controlPressed || metaPressed)) {\n const timestamp = event.normalizedTime || 0;\n this.undoTimestamps.push({\n index: undoCount,\n time: timestamp,\n formattedTime: this.formatTime(timestamp),\n timestamp\n });\n undoCount++;\n controlPressed = false;\n metaPressed = false;\n } else {\n controlPressed = false;\n metaPressed = false;\n }\n }\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.isMetaKeyPressed = false;\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\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 } else if (event.event === 'aiInsert') {\n ({text, cursor, updatedHighlights, updatedDeleted} =\n this.processAiInsertEvent(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 processAiInsertEvent(event, text, cursor, highlights, deletions) {\n if (this.aiEvents && this.currentAiIndex < this.aiEvents.length) {\n const aiContent = this.aiEvents[this.currentAiIndex];\n // Use event.rePosition which points to where the word to replace is\n const targetPosition = event.rePosition;\n\n ({text, cursor} = this.handleAiReplacement(aiContent, text, targetPosition, cursor, deletions));\n this.currentAiIndex++;\n }\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n\n handleAiReplacement(aiContent, text, targetPosition, currentCursor, deletions) {\n const insertText = aiContent || '';\n const aiWords = insertText.trim().split(/\\s+/);\n const isMultiWord = aiWords.length > 1;\n const isNewLineInsertion = insertText.startsWith('\\n') || insertText.endsWith('\\n');\n\n const {wordStart, wordEnd} = this.findWordToReplace(\n text,\n targetPosition,\n currentCursor,\n aiWords,\n isMultiWord,\n isNewLineInsertion\n );\n\n const wordToReplace = text.substring(wordStart, wordEnd);\n\n // Mark replaced characters as deleted\n this.markCharsAsDeleted(wordToReplace, wordStart, deletions);\n\n // Perform the replacement\n const replacedLength = wordToReplace.length;\n text = text.substring(0, wordStart) + insertText + text.substring(wordEnd);\n const positionDiff = insertText.length - replacedLength;\n\n // Calculate new cursor position\n const newCursor = this.calculateNewCursorPosition(\n currentCursor,\n targetPosition,\n wordStart,\n wordEnd,\n insertText,\n isNewLineInsertion\n );\n\n // Update character tracking arrays\n this.updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText);\n\n return {text, cursor: newCursor};\n }\n\n findWordToReplace(text, targetPosition, currentCursor, aiWords, isMultiWord, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n const {lineStart, lineEnd} = this.findLineRange(text, targetPosition);\n const lineText = text.substring(lineStart, lineEnd);\n const words = this.extractWordsFromLine(lineText, lineStart);\n\n if (words.length === 0) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n if (isMultiWord) {\n return this.findMultiWordMatch(words, aiWords, targetPosition);\n } else {\n return this.findSingleWordMatch(words, aiWords[0], targetPosition);\n }\n }\n\n findLineRange(text, targetPosition) {\n let lineStart = 0;\n for (let i = targetPosition - 1; i >= 0; i--) {\n if (text[i] === '\\n') {\n lineStart = i + 1;\n break;\n }\n }\n\n let lineEnd = text.length;\n for (let i = targetPosition; i < text.length; i++) {\n if (text[i] === '\\n') {\n lineEnd = i;\n break;\n }\n }\n\n return {lineStart, lineEnd};\n }\n\n extractWordsFromLine(lineText, lineStart) {\n const words = [];\n let pos = 0;\n\n while (pos < lineText.length) {\n // Skip spaces\n while (pos < lineText.length && lineText[pos] === ' ') {\n pos++;\n }\n if (pos >= lineText.length) {\n break;\n }\n\n // Extract word\n const start = pos;\n while (pos < lineText.length && lineText[pos] !== ' ') {\n pos++;\n }\n\n if (pos > start) {\n words.push({\n text: lineText.substring(start, pos),\n start: lineStart + start,\n end: lineStart + pos\n });\n }\n }\n\n return words;\n }\n\n findMultiWordMatch(words, aiWords, targetPosition) {\n let bestMatch = {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n\n for (let i = 0; i < words.length; i++) {\n const matchResult = this.evaluateMultiWordSequence(words, aiWords, i, targetPosition);\n\n if (matchResult.totalScore > bestMatch.score ||\n (matchResult.totalScore === bestMatch.score &&\n matchResult.similarityScore > bestMatch.similarityScore)) {\n bestMatch = matchResult;\n }\n }\n\n if (bestMatch.score > 10) {\n return {wordStart: bestMatch.start, wordEnd: bestMatch.end};\n } else {\n const closest = this.findClosestWord(words, targetPosition);\n return {wordStart: closest.start, wordEnd: closest.end};\n }\n }\n\n evaluateMultiWordSequence(words, aiWords, startIndex, targetPosition) {\n const seqWords = [];\n for (let j = 0; j < aiWords.length && startIndex + j < words.length; j++) {\n seqWords.push(words[startIndex + j]);\n }\n\n if (seqWords.length === 0) {\n return {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n }\n\n const similarityScore = this.calculateSequenceSimilarity(aiWords, seqWords);\n const positionScore = this.calculatePositionScore(seqWords, targetPosition);\n const totalScore = similarityScore + positionScore + seqWords.length;\n\n return {\n start: seqWords[0].start,\n end: seqWords[seqWords.length - 1].end,\n score: totalScore,\n wordCount: seqWords.length,\n similarityScore: similarityScore\n };\n }\n\n calculateSequenceSimilarity(aiWords, seqWords) {\n let similarityScore = 0;\n const compareLength = Math.min(seqWords.length, aiWords.length);\n\n for (let k = 0; k < compareLength; k++) {\n const ai = aiWords[k].toLowerCase();\n const seq = seqWords[k].text.toLowerCase();\n\n if (ai === seq) {\n similarityScore += 10;\n } else {\n const similarity = this.calculateSimilarity(ai, seq);\n similarityScore += similarity * 10;\n }\n }\n\n return similarityScore;\n }\n\n calculatePositionScore(seqWords, targetPosition) {\n let positionScore = 0;\n const seqStart = seqWords[0].start;\n const seqEndPos = seqWords[seqWords.length - 1].end;\n\n if (targetPosition >= seqStart && targetPosition <= seqEndPos) {\n positionScore += 10;\n if (targetPosition >= seqWords[0].start && targetPosition <= seqWords[0].end) {\n positionScore += 5;\n }\n }\n\n return positionScore;\n }\n\n findSingleWordMatch(words, aiWord, targetPosition) {\n const aiWordLower = aiWord.toLowerCase();\n const bestSimilarityMatch = this.findBestSimilarityMatch(words, aiWordLower);\n\n if (bestSimilarityMatch.score > 0.5) {\n return {wordStart: bestSimilarityMatch.word.start, wordEnd: bestSimilarityMatch.word.end};\n }\n\n const bestPositionMatch = this.findBestPositionMatch(words, aiWordLower, targetPosition);\n\n if (bestPositionMatch.word) {\n return {wordStart: bestPositionMatch.word.start, wordEnd: bestPositionMatch.word.end};\n }\n\n // Fallback to position-based word boundary\n return this.findWordBoundaryAtPosition(words[0].start, words[words.length - 1].end,\n targetPosition, this.text);\n }\n\n findBestSimilarityMatch(words, aiWordLower) {\n let bestMatch = {word: null, score: 0};\n\n for (const word of words) {\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n\n // Penalize short prefix matches\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n\n if (similarity > bestMatch.score) {\n bestMatch = {word, score: similarity};\n }\n }\n\n return bestMatch;\n }\n\n findBestPositionMatch(words, aiWordLower, targetPosition) {\n let bestMatch = {word: null, score: -1};\n\n for (const word of words) {\n let score = this.calculateWordScore(word, aiWordLower, targetPosition);\n\n if (score > bestMatch.score) {\n bestMatch = {word, score};\n }\n }\n\n return bestMatch;\n }\n\n calculateWordScore(word, aiWordLower, targetPosition) {\n let score = 0;\n\n // Position score\n if (targetPosition >= word.start && targetPosition <= word.end) {\n score += 30;\n } else {\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n score += Math.max(0, 20 - distance);\n }\n\n // Similarity score with penalty\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n score += similarity * 10;\n\n return score;\n }\n\n findWordBoundaryAtPosition(lineStart, lineEnd, targetPosition, text) {\n let wordStart = targetPosition;\n while (wordStart > lineStart && text[wordStart - 1] !== ' ' && text[wordStart - 1] !== '\\n') {\n wordStart--;\n }\n let wordEnd = targetPosition;\n while (wordEnd < lineEnd && text[wordEnd] !== ' ' && text[wordEnd] !== '\\n') {\n wordEnd++;\n }\n return {wordStart, wordEnd};\n }\n\n markCharsAsDeleted(wordToReplace, wordStart, deletions) {\n if (wordToReplace.length > 0) {\n for (let i = 0; i < wordToReplace.length; i++) {\n deletions.push({\n index: wordStart + i,\n chars: wordToReplace[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n\n calculateNewCursorPosition(currentCursor, targetPosition, wordStart, wordEnd, insertText, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return wordStart + insertText.length;\n }\n\n if (targetPosition >= wordStart && targetPosition <= wordEnd) {\n return wordStart + insertText.length;\n }\n\n const positionDiff = insertText.length - (wordEnd - wordStart);\n\n if (currentCursor >= wordEnd) {\n return currentCursor + positionDiff;\n } else if (currentCursor > wordStart && currentCursor < wordEnd) {\n return wordStart + insertText.length;\n }\n\n return currentCursor;\n }\n\n updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText) {\n // Update pasted character indices\n this.updatePastedCharIndices(wordStart, wordEnd, positionDiff);\n\n // Mark characters as AI-inserted\n this.markCharsAsAiInserted(wordStart, insertText);\n\n // Update AI character indices\n this.updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText);\n }\n\n updatePastedCharIndices(wordStart, wordEnd, positionDiff) {\n if (this.pastedChars) {\n this.pastedChars = this.pastedChars.map(p => {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n return p;\n }).filter(p => p !== null);\n }\n }\n\n markCharsAsAiInserted(wordStart, insertText) {\n if (!this.aiChars) {\n this.aiChars = [];\n }\n\n if (insertText.trim() !== '') {\n for (let i = 0; i < insertText.length; i++) {\n this.aiChars.push({\n index: wordStart + i,\n chars: insertText[i]\n });\n }\n }\n }\n\n updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText) {\n const justAddedIndices = new Set();\n for (let i = 0; i < insertText.length; i++) {\n justAddedIndices.add(wordStart + i);\n }\n\n this.aiChars = this.aiChars.map(p => {\n if (!justAddedIndices.has(p.index)) {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n }\n return p;\n }).filter(p => p !== null);\n }\n\n // Calculate similarity between two strings\n calculateSimilarity(str1, str2) {\n if (str1 === str2) {\n return 1;\n }\n if (str1.length === 0 || str2.length === 0) {\n return 0;\n }\n\n // Check if one string is a prefix of the other\n if (str1.startsWith(str2) || str2.startsWith(str1)) {\n return 0.8;\n }\n\n // Levenshtein distance\n const len1 = str1.length;\n const len2 = str2.length;\n const matrix = Array(len2 + 1).fill(null).map(() => Array(len1 + 1).fill(0));\n\n for (let i = 0; i <= len1; i++) {\n matrix[0][i] = i;\n }\n for (let j = 0; j <= len2; j++) {\n matrix[j][0] = j;\n }\n\n for (let j = 1; j <= len2; j++) {\n for (let i = 1; i <= len1; i++) {\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\n matrix[j][i] = Math.min(\n matrix[j][i - 1] + 1,\n matrix[j - 1][i] + 1,\n matrix[j - 1][i - 1] + cost\n );\n }\n }\n\n const maxLen = Math.max(len1, len2);\n return 1 - (matrix[len2][len1] / maxLen);\n }\n\n // Find the word closest to a target position\n findClosestWord(words, targetPosition) {\n if (words.length === 0) {\n return {start: targetPosition, end: targetPosition};\n }\n\n let closest = words[0];\n let minDistance = Math.min(\n Math.abs(targetPosition - words[0].start),\n Math.abs(targetPosition - words[0].end)\n );\n\n for (const word of words) {\n if (targetPosition >= word.start && targetPosition <= word.end) {\n return word;\n }\n\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n\n if (distance < minDistance) {\n minDistance = distance;\n closest = word;\n }\n }\n\n return closest;\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\n // Handle copy operation (Ctrl+C)\n if (this.isCopyOperation(key)) {\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Handle undo operation (Ctrl+Z)\n if (this.isUndoOperation(key)) {\n return this.handleUndoOperation(event, text, cursor, highlights, deletions);\n }\n\n // Detect selection for current event\n const currentEventIndex = this.currentEventIndex;\n const selection = this.detectSelection(currentEventIndex);\n\n // Handle paste operation (Ctrl+V)\n if (this.isPasteOperation(key, event)) {\n return this.handlePasteOperation(event, selection, text, cursor, highlights, deletions);\n }\n\n // Update modifier key states\n this.updateModifierStates(key);\n\n // Handle selection deletion with Backspace/Delete\n if (this.isSelectionDeletion(key, selection)) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Process various key operations\n return this.processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection);\n }\n\n isCopyOperation(key) {\n return (key === 'c' || key === 'C') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n isUndoOperation(key) {\n return (key === 'z' || key === 'Z') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n handleUndoOperation(event, text, cursor, highlights, deletions) {\n const nextEventIndex = this.currentEventIndex + 1;\n if (nextEventIndex < this.logData.length) {\n const nextEvent = this.logData[nextEventIndex];\n\n if (nextEvent.event === 'keyUp' && (nextEvent.key === 'z' || nextEvent.key === 'Z')) {\n const newPosition = nextEvent.rePosition;\n if (newPosition < cursor && text.length > 0) {\n const textBeforeUndo = text;\n text = text.substring(0, newPosition) + text.substring(cursor);\n cursor = newPosition;\n\n // Mark as deleted for visual effect\n for (let i = 0; i < textBeforeUndo.length && i < cursor; i++) {\n deletions.push({\n index: newPosition,\n chars: textBeforeUndo[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n }\n\n this.isControlKeyPressed = false;\n this.isMetaKeyPressed = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n isPasteOperation(key, event) {\n if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n return (event.pastedContent && event.pastedContent.trim() !== '') ||\n (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length);\n }\n return false;\n }\n\n handlePasteOperation(event, selection, text, cursor, highlights, deletions) {\n const pastedContent = event.pastedContent || this.pastedEvents[this.currentPasteIndex];\n\n if (selection) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\n this.currentPasteIndex++;\n this.resetModifierStates();\n this.isPasteEvent = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n resetModifierStates() {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = false;\n }\n\n isSelectionDeletion(key, selection) {\n return (key === 'Backspace' || key === 'Delete') && selection && selection.length > 1;\n }\n\n processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection) {\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 if (selection && selection.length > 0) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\n }\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n detectSelection(eventIndex) {\n const currentEvent = this.logData[eventIndex];\n\n if (currentEvent.event?.toLowerCase() === 'keydown' &&\n (currentEvent.key === 'Backspace' || currentEvent.key === 'Delete')) {\n\n const currentPos = currentEvent.rePosition;\n return this.processDetection(currentPos, currentEvent, eventIndex);\n }\n return null;\n }\n\n processDetection(currentPos, currentEvent, eventIndex) {\n for (let i = eventIndex + 1; i < this.logData.length; i++) {\n const nextEvent = this.logData[i];\n\n if (nextEvent.event?.toLowerCase() === 'keyup' &&\n nextEvent.key === currentEvent.key) {\n\n const nextPos = nextEvent.rePosition;\n\n // Calculate the difference in positions\n const positionDiff = Math.abs(currentPos - nextPos);\n\n if (positionDiff > 1) {\n return {\n start: Math.min(currentPos, nextPos),\n end: Math.max(currentPos, nextPos),\n length: positionDiff\n };\n } else if (positionDiff === 1) {\n if (currentEvent.key === 'Backspace') {\n return {\n start: nextPos,\n end: currentPos,\n length: 1\n };\n } else {\n return {\n start: currentPos,\n end: nextPos,\n length: 1\n };\n }\n }\n break;\n }\n }\n return null;\n }\n\n handleSelectionDeletion(selection, text, cursor, deletions) {\n const {start, end, length} = selection;\n\n // Add each character in the selection to the deletions array\n for (let i = start; i < end && i < text.length; i++) {\n deletions.push({\n index: start,\n chars: text[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n\n text = text.substring(0, start) + text.substring(end);\n\n this.shiftPastedCharsIndices(start, length);\n\n cursor = start;\n\n return {text, cursor};\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 if (this.aiChars) {\n this.aiChars = this.aiChars.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 return null;\n }\n return p;\n }).filter(p => p !== null);\n }\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 === 'Meta') {\n this.isMetaKeyPressed = true;\n } else if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n this.isPasteEvent = true;\n } else if (!['Control', 'Meta', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = 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 (this.aiChars) {\n this.aiChars = this.aiChars.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.isMetaKeyPressed = false;\n this.isPasteEvent = false;\n this.pastedChars = [];\n this.currentPasteIndex = 0;\n this.currentAiIndex = 0;\n this.aiChars = [];\n let text = '';\n let cursor = 0;\n let highlights = [];\n let deletions = [];\n let pasteIndex = 0;\n let aiIndex = 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 && (this.currentEventIndex === 0 ||\n 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 || this.isMetaKeyPressed)) {\n pasteIndex++;\n }\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\n } else if (event.event === 'aiInsert') {\n this.currentAiIndex = aiIndex;\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processAiInsertEvent(event, text, cursor, highlights, deletions));\n aiIndex++;\n }\n this.currentEventIndex = i + 1;\n }\n\n this.currentPasteIndex = pasteIndex;\n this.currentAiIndex = aiIndex;\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 // eslint-disable-next-line complexity\n updateDisplayText(text, cursorPosition, highlights, deletions) {\n let html = '';\n const highlightMap = {};\n const deletionMap = {};\n const pastedMap = {};\n const aiMap = {};\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 // Process AI characters for styling\n if (this.aiChars) {\n this.aiChars.forEach(p => {\n if (p.index < text.length) {\n aiMap[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 isAi = aiMap[currentPosition];\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\n\n if (isPasted && isHighlighted) {\n html += `${char}`;\n } else if (isAi && isHighlighted) {\n html += `${char}`;\n } else if (isPasted) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isAi) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isHighlighted) {\n html += `${char}`;\n } else {\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","isMetaKeyPressed","text","pastedEvents","currentPasteIndex","pastedChars","aiEvents","currentAiIndex","aiChars","undoTimestamps","undoChars","element","document","getElementById","Error","outputElement","loadJSON","then","data","status","processData","this","logData","length","identifyPasteEvents","identifyUndoEvents","constructController","startReplay","handleNoSubmission","catch","error","window","console","message","localStorage","getItem","Str","get_string","str","setItem","log","JSON","parse","comments","Array","isArray","payload","i","event","pastedContent","trim","push","aiContent","unixTimestamp","startTime","map","normalizedTime","html","Promise","all","templates","render","newElement","stopReplay","clearTimeout","replayTimeout","playButton","playSvg","createElement","src","M","util","image_url","querySelector","innerHTML","outerHTML","currentPosition","replayIntervalId","clearInterval","container","controlContainer","buildControllerUI","remove","topRow","classList","add","createPlayButton","appendChild","scrubberContainer","createScrubberContainer","timeDisplay","createTimeDisplay","bottomRow","speedContainer","createSpeedControls","pasteEventsToggle","createPasteEventsToggle","pasteEventsPanel","addEventListener","find","removeClass","addClass","scrubberElement","type","max","min","value","skipToTime","parseInt","textContent","speedLabel","speedGroup","forEach","speedBtn","dataset","querySelectorAll","btn","pasteEventsIcon","pasteIcon","pasteEventsText","pasteEventCount","className","style","marginLeft","chevronIcon","chevron","transition","createPasteEventsPanel","isHidden","display","transform","existingPanel","populatePasteEventsPanel","controlPressed","metaPressed","shiftPressed","pasteCount","toLowerCase","key","timestamp","index","time","formattedTime","formatTime","pastedText","undoCount","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","processAiInsertEvent","filter","h","expiresAt","d","updateDisplayText","percentComplete","baseIncrement","incrementTime","setTimeout","getLineAndColumn","pos","before","substring","lineIndex","split","col","lastIndexOf","highlights","deletions","targetPosition","handleAiReplacement","currentCursor","insertText","aiWords","isMultiWord","isNewLineInsertion","startsWith","endsWith","wordStart","wordEnd","findWordToReplace","wordToReplace","markCharsAsDeleted","replacedLength","positionDiff","newCursor","calculateNewCursorPosition","updateCharacterIndices","lineStart","lineEnd","findLineRange","lineText","words","extractWordsFromLine","findMultiWordMatch","findSingleWordMatch","start","end","bestMatch","score","wordCount","similarityScore","matchResult","evaluateMultiWordSequence","totalScore","closest","findClosestWord","startIndex","seqWords","j","calculateSequenceSimilarity","calculatePositionScore","compareLength","k","ai","seq","calculateSimilarity","positionScore","seqStart","seqEndPos","aiWord","aiWordLower","bestSimilarityMatch","findBestSimilarityMatch","word","bestPositionMatch","findBestPositionMatch","findWordBoundaryAtPosition","similarity","wordLower","calculateWordScore","distance","abs","chars","updatePastedCharIndices","markCharsAsAiInserted","updateAiCharIndices","p","justAddedIndices","Set","has","str1","str2","len1","len2","matrix","fill","cost","maxLen","minDistance","charToInsert","applyKey","isCopyOperation","isUndoOperation","handleUndoOperation","selection","detectSelection","isPasteOperation","handlePasteOperation","updateModifierStates","isSelectionDeletion","handleSelectionDeletion","processKeyOperation","nextEventIndex","nextEvent","newPosition","textBeforeUndo","handlePasteInsert","resetModifierStates","isCtrlBackspace","handleCtrlBackspace","isCtrlDelete","handleCtrlDelete","isCtrlArrowMove","handleCtrlArrowMove","isRegularBackspace","handleBackspace","isRegularDelete","handleDelete","isArrowUp","handleArrowUp","isArrowDown","handleArrowDown","isRegularArrowMove","handleArrowMove","handleCharacterInsert","eventIndex","currentEvent","currentPos","processDetection","nextPos","shiftPastedCharsIndices","numDeleted","includes","findPreviousWordBoundary","findNextWordBoundary","wordToDelete","lines","prevLine","slice","join","nextLine","lastNonSpace","skipToEnd","textOutput","wasPlaying","targetTime","pasteIndex","aiIndex","highlightMap","deletionMap","pastedMap","aiMap","outOfRangeDeletions","textLines","line","char","isPasted","isAi","isHighlighted","escapeHtml","sort","a","b","cursorHTML","cursorPos","deletedWordHTML","wasScrolledToBottom","scrollHeight","clientHeight","scrollTop","isCursorBelowViewport","cursorElement","cursorRect","getBoundingClientRect","outputRect","bottom","unsafe","replace"],"mappings":"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"} \ 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 8b434228..b072c128 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,SAASmB,gBACFA,QAAQ,GAAGC,IACZC,OAEbA,MAAO,SAASF,eACNG,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,SAAS1D,cACRD,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,SAASmB,gBACFA,QAAQ,GAAGC,IACZC,OAEbA,MAAO,SAASF,eACNG,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,SAAS1D,cACRD,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/svg_repo.min.js b/amd/build/svg_repo.min.js index e4901924..5f19b236 100644 --- a/amd/build/svg_repo.min.js +++ b/amd/build/svg_repo.min.js @@ -1,11 +1,3 @@ -define("tiny_cursive/svg_repo",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0; -/** - * SVG repository for icons used in the Curs - * - * @module tiny_cursive/svg_repo - * @copyright 2025 Cursive Technology, Inc. - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -var _default={people:'\n \n \n \n \n ',assignment:'\n \n \n \n ',time:'\n \n \n ',offline:'',forum:'',close:'\n \n ',hamburger:'\n \n ',quiz:'',cloudSave:'',lesson:'',pdfannotator:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n '};return _exports.default=_default,_exports.default})); +define("tiny_cursive/svg_repo",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={people:'\n \n \n \n \n ',assignment:'\n \n \n \n ',time:'\n \n \n ',offline:'',forum:'',close:'\n \n ',hamburger:'\n \n ',quiz:'',cloudSave:'',lesson:'',pdfannotator:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n '},_exports.default})); //# sourceMappingURL=svg_repo.min.js.map \ No newline at end of file diff --git a/amd/build/svg_repo.min.js.map b/amd/build/svg_repo.min.js.map index 505312fd..8a0980ee 100644 --- a/amd/build/svg_repo.min.js.map +++ b/amd/build/svg_repo.min.js.map @@ -1 +1 @@ -{"version":3,"file":"svg_repo.min.js","sources":["../src/svg_repo.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 * SVG repository for icons used in the Curs\n *\n * @module tiny_cursive/svg_repo\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n people: `\n \n \n \n \n `,\n assignment: `\n \n \n \n `,\n time: `\n \n \n `,\n offline: ``,\n forum: ``,\n close: `\n \n `,\n hamburger: `\n \n `,\n quiz: ``,\n cloudSave: ``,\n lesson: ``,\n pdfannotator: `\n \n \n \n \n \n \n \n \n \n \n \n \n \n `\n};\n"],"names":["people","assignment","time","offline","forum","close","hamburger","quiz","cloudSave","lesson","pdfannotator"],"mappings":";;;;;;;;aAuBe,CACXA,OAAS,ijBASTC,WAAa,siBAQbC,KAAO,qaAOPC,QAAU,qgBAMVC,MAAQ,mtDAeRC,MAAQ,85BAQRC,UAAY,6jBAKZC,KAAO,ksFAuBPC,UAAY,qzBAOZC,OAAS,4/EAqBTC,aAAe"} \ No newline at end of file +{"version":3,"file":"svg_repo.min.js","sources":["../src/svg_repo.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 * SVG repository for icons used in the Curs\n *\n * @module tiny_cursive/svg_repo\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n people: `\n \n \n \n \n `,\n assignment: `\n \n \n \n `,\n time: `\n \n \n `,\n offline: ``,\n forum: ``,\n close: `\n \n `,\n hamburger: `\n \n `,\n quiz: ``,\n cloudSave: ``,\n lesson: ``,\n pdfannotator: `\n \n \n \n \n \n \n \n \n \n \n \n \n \n `\n};\n"],"names":["people","assignment","time","offline","forum","close","hamburger","quiz","cloudSave","lesson","pdfannotator"],"mappings":"uKAuBe,CACXA,wjBASAC,ijBAQAC,0aAOAC,6gBAMAC,ytDAeAC,o6BAQAC,ukBAKAC,usFAuBAC,+zBAOAC,mgFAqBAC"} \ 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 96a5ed03..20e600ba 100644 --- a/amd/build/token_approve.min.js +++ b/amd/build/token_approve.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/token_approve",["jquery","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(){$("#approve_token").click((function(){var token=$("#id_s_tiny_cursive_secretkey").val();AJAX.call([{methodname:"cursive_approve_token",args:{token:token}}])[0].done((function(json){var data=JSON.parse(json),messageAlert="";messageAlert=1==data.status?""+data.message+"":""+data.message+"",$("#token_message").html(messageAlert)}))}))},generateToken(){const generateToken=$("#generate_cursivetoken"),cursiveDisable=$("#cursivedisable"),cursiveEnable=$("#cursiveenable");generateToken.on("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;return data.token?($("#id_s_tiny_cursive_cursivetoken").val(data.token),messageAlert=`${success}`):messageAlert=`${fail}`,$("#cursivetoken_").html(messageAlert),setTimeout((()=>{$("#cursivetoken_").empty()}),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+"",$("#cursivetoken_").html(errorMessage),setTimeout((function(){$("#cursivetoken_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveDisable.on("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;return messageAlert=data?`${success}`:`${fail}`,$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveEnable.on("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;return messageAlert=data?`${success}`:`${fail}`,$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))}))}};return usersTable})); +define("tiny_cursive/token_approve",["jquery","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(){$("#approve_token").click((function(){var token=$("#id_s_tiny_cursive_secretkey").val();AJAX.call([{methodname:"cursive_approve_token",args:{token:token}}])[0].done((function(json){var data=JSON.parse(json),messageAlert="";messageAlert=1==data.status?""+data.message+"":""+data.message+"",$("#token_message").html(messageAlert)}))}))},generateToken(){const generateToken=$("#generate_cursivetoken"),cursiveDisable=$("#cursivedisable"),cursiveEnable=$("#cursiveenable");generateToken.on("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;return data.token?($("#id_s_tiny_cursive_cursivetoken").val(data.token),messageAlert="".concat(success,"")):messageAlert="".concat(fail,""),$("#cursivetoken_").html(messageAlert),setTimeout((()=>{$("#cursivetoken_").empty()}),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+"",$("#cursivetoken_").html(errorMessage),setTimeout((function(){$("#cursivetoken_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveDisable.on("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;return messageAlert=data?"".concat(success,""):"".concat(fail,""),$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveEnable.on("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;return messageAlert=data?"".concat(success,""):"".concat(fail,""),$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),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 44d98c9a..2236c250 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 kuldeep singh \n */\n\ndefine([\"jquery\", \"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 $(\"#approve_token\").click(function() {\n var token = $(\"#id_s_tiny_cursive_secretkey\").val();\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 == true) {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n } else {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n }\n $(\"#token_message\").html(messageAlert);\n });\n });\n },\n\n generateToken() {\n const generateToken = $(\"#generate_cursivetoken\");\n const cursiveDisable = $(\"#cursivedisable\");\n const cursiveEnable = $(\"#cursiveenable\");\n\n generateToken.on(\"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 $(\"#id_s_tiny_cursive_cursivetoken\").val(data.token);\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n $(\"#cursivetoken_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivetoken_\").empty();\n }, 3000);\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 $(\"#cursivetoken_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivetoken_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n\n cursiveDisable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n cursiveEnable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","usersTable","init","page","get_strings","key","component","done","getToken","generateToken","click","token","val","call","methodname","args","json","data","JSON","parse","messageAlert","status","message","html","cursiveDisable","cursiveEnable","on","e","preventDefault","promise1","then","success","fail","setTimeout","empty","catch","error","window","console","textStatus","errorMessage","get_string","disable"],"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 kuldeep singh \n */\n\ndefine([\"jquery\", \"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 $(\"#approve_token\").click(function() {\n var token = $(\"#id_s_tiny_cursive_secretkey\").val();\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 == true) {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n } else {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n }\n $(\"#token_message\").html(messageAlert);\n });\n });\n },\n\n generateToken() {\n const generateToken = $(\"#generate_cursivetoken\");\n const cursiveDisable = $(\"#cursivedisable\");\n const cursiveEnable = $(\"#cursiveenable\");\n\n generateToken.on(\"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 $(\"#id_s_tiny_cursive_cursivetoken\").val(data.token);\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n $(\"#cursivetoken_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivetoken_\").empty();\n }, 3000);\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 $(\"#cursivetoken_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivetoken_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n\n cursiveDisable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n cursiveEnable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","usersTable","init","page","get_strings","key","component","done","getToken","generateToken","click","token","val","call","methodname","args","json","data","JSON","parse","messageAlert","status","message","html","cursiveDisable","cursiveEnable","on","e","preventDefault","promise1","then","success","fail","setTimeout","empty","catch","error","window","console","textStatus","errorMessage","get_string","disable"],"mappings":"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"} \ No newline at end of file diff --git a/db/install.xml b/db/install.xml index 8c520160..b714676c 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 @@ + + +
@@ -100,6 +119,9 @@ + + +
diff --git a/db/upgrade.php b/db/upgrade.php index dd3565b0..e8b6beaa 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -224,5 +224,97 @@ function xmldb_tiny_cursive_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2024062004, 'tiny', 'cursive'); } + if ($oldversion < 2026013002) { + + $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, 2026013002, 'tiny', 'cursive'); + } + return true; -} +} \ No newline at end of file diff --git a/version.php b/version.php index d8027c3e..f727d3c7 100644 --- a/version.php +++ b/version.php @@ -29,6 +29,6 @@ $plugin->component = 'tiny_cursive'; $plugin->release = '2.1.3'; -$plugin->version = 2026013000; +$plugin->version = 2026013002; $plugin->requires = 2022041912; $plugin->maturity = MATURITY_STABLE; From 7f0b07226ddedf4ffca84b7d21896e20a66dd486 Mon Sep 17 00:00:00 2001 From: Tareq-Adnan Date: Mon, 9 Feb 2026 15:01:27 +0600 Subject: [PATCH 3/5] github feedback resolved --- 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/append_fourm_post.min.js.map | 2 +- amd/build/append_lesson_grade_table.min.js | 2 +- .../append_lesson_grade_table.min.js.map | 2 +- amd/build/append_pdfannotator.min.js | 2 +- amd/build/append_pdfannotator.min.js.map | 2 +- amd/build/append_submissions_table.min.js | 2 +- amd/build/append_submissions_table.min.js.map | 2 +- amd/build/autosaver.min.js | 6 +---- amd/build/autosaver.min.js.map | 6 +---- amd/build/common.min.js | 2 +- amd/build/common.min.js.map | 2 +- amd/build/cursive_autosave.min.js | 9 ++++++- amd/build/cursive_autosave.min.js.map | 2 +- amd/build/document_view.min.js | 6 +---- amd/build/document_view.min.js.map | 6 +---- 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/svg_repo.min.js | 14 +++++++---- amd/build/svg_repo.min.js.map | 6 +---- 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 +- amd/src/append_submissions_table.js | 4 +-- amd/src/scatter_chart.js | 25 +++++++++++++------ classes/constants.php | 3 ++- classes/{ => local}/page/pdfexport.php | 2 +- classes/{ => local}/page/visualization.php | 2 +- classes/privacy/provider.php | 2 +- db/install.php | 8 +++--- lang/en/tiny_cursive.php | 7 +++++- lib.php | 2 +- my_writing_report.php | 2 +- pdfexport.php | 2 +- thirdpartylibs.xml | 2 +- tiny_cursive_report.php | 4 +-- version.php | 2 +- 43 files changed, 93 insertions(+), 78 deletions(-) rename classes/{ => local}/page/pdfexport.php (99%) rename classes/{ => local}/page/visualization.php (99%) diff --git a/amd/build/analytic_events.min.js b/amd/build/analytic_events.min.js index a771d356..7a33c868 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=_interopRequireDefault(_analytic_modal),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates);return _exports.default=class{constructor(){(0,_str.get_string)("notenoughtinfo","tiny_cursive").then((str=>(localStorage.setItem("notenoughtinfo",str),str))).catch((error=>window.console.log(error)))}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 self=this;(0,_jquery.default)("#analytics"+userid+questionid).on("click",(function(e){e.preventDefault();const isReplayButton=(0,_jquery.default)(this).find(".tiny_cursive-replay-button").length>0;_analytic_modal.default.create({templateContext:context}).then((modal=>{(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),modal.show(),isReplayButton&&setTimeout((()=>{(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active");const replayTab=(0,_jquery.default)("#rep"+userid+questionid);replayTab.length&&(replayTab.trigger("click"),replayTab.addClass("active"))}),50);let moreBtn=(0,_jquery.default)("body #more"+userid+questionid);return moreBtn.length>0&&((0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)("#analytic"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#diff"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#analytic"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),(0,_jquery.default)("#diff"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),moreBtn.on("click",(function(e){e.preventDefault(),self.learnMore((0,_jquery.default)(this),context,userid,questionid,replayInstances)}))),!0})).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;(0,_jquery.default)("body").on("click","#analytic"+userid+questionid,(function(e){(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","analytics"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","analytics"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),templates.render("tiny_cursive/analytics_table",context).then((function(html){return(0,_jquery.default)("#content"+userid).html(html),(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),!0})).fail((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))),(0,_jquery.default)("body").on("click","#diff"+userid+questionid,(function(e){if((0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","diff"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","diff"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),!fileid)throw(0,_jquery.default)("#content"+userid).html(nodata),new Error("Missing file id or Difference Content not received yet");(0,_ajax.call)([{methodname:"cursive_get_writing_differences",args:{fileid:fileid}}])[0].done((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"}]).done((strings=>{const originalTextString=strings[0],editsPastesAIString=strings[1],commentBox=(0,_jquery.default)('
');var pasteCountDiv=(0,_jquery.default)("
");(0,_str.get_string)("pastecount","tiny_cursive").then((str=>(pasteCountDiv.append("
"+str+" : "+responsedata.commentscount+"
"),!0))).catch((error=>window.console.log(error)));var commentsDiv=(0,_jquery.default)('
');(0,_str.get_string)("comments","tiny_cursive").then((str=>(commentsDiv.append(""+str+""),!0))).catch((error=>window.console.error(error)));var commentsList=(0,_jquery.default)("
");let comments=responsedata.comments;for(let index in comments){var commentDiv=(0,_jquery.default)('
').text(comments[index].usercomment);commentsList.append(commentDiv)}commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);const $legend=(0,_jquery.default)('
'),$attributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$attributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box attributed"}),$attributedText=(0,_jquery.default)("").text(originalTextString);$attributedItem.append($attributedBox).append($attributedText);const $unattributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$unattributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box tiny_cursive_added"}),$unattributedText=(0,_jquery.default)("").text(editsPastesAIString);$unattributedItem.append($unattributedBox).append($unattributedText),$legend.append($attributedItem).append($unattributedItem);let contents=(0,_jquery.default)("
").addClass("tiny_cursive-comparison-content"),textBlock2=(0,_jquery.default)("
").addClass("tiny_cursive-text-block").append((0,_jquery.default)("
").attr("id","tiny_cursive-reconstructed_text").html(JSON.parse(submittedText)));contents.append(commentBox,$legend,textBlock2),(0,_jquery.default)("#content"+userid).html(contents)})).fail((error=>{window.console.error("Failed to load language strings:",error),(0,_jquery.default)("#content"+userid).html(nodata)}))}else(0,_jquery.default)("#content"+userid).html(nodata)})).fail((error=>{throw(0,_jquery.default)("#content"+userid).html(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;(0,_jquery.default)("body").on("click","#rep"+userid+questionid,(function(e){filepath&&((0,_jquery.default)("#replayControls_"+userid+questionid).removeClass("d-none"),(0,_jquery.default)("#content"+userid).addClass("tiny_cursive_outputElement")),(0,_jquery.default)(this).prop("disabled",!0),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","replay"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"block","padding-right":"8px"}),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),questionid?video_playback(userid,filepath,questionid):video_playback(userid,filepath)}))}learnMore(moreBtn,context,userid,questionid,replayInstances){(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),moreBtn.addClass("active"),(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)("#content"+userid+questionid).removeClass("tiny_cursive_outputElement"),(0,_jquery.default)("#replayControls_"+userid+questionid).addClass("d-none"),_templates.default.render("tiny_cursive/learn_more",context).then((function(html){return(0,_jquery.default)("#content"+userid+questionid).html(html),!0})).fail((function(error){window.console.error("Failed to render template:",error)}))}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";return 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"),score").addClass(icon).attr("style",color).attr("title",localStorage.getItem("notenoughtinfo"))):(0,_jquery.default)("").addClass(icon).attr("style",color)}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_analytic_modal=_interopRequireDefault(_analytic_modal),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates);return _exports.default=class{constructor(){(0,_str.get_string)("notenoughtinfo","tiny_cursive").then((str=>(localStorage.setItem("notenoughtinfo",str),str))).catch((error=>window.console.log(error)))}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 self=this;(0,_jquery.default)("#analytics"+userid+questionid).on("click",(function(e){e.preventDefault();const isReplayButton=(0,_jquery.default)(this).find(".tiny_cursive-replay-button").length>0;_analytic_modal.default.create({templateContext:context}).then((modal=>{(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),modal.show(),isReplayButton&&setTimeout((()=>{(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active");const replayTab=(0,_jquery.default)("#rep"+userid+questionid);replayTab.length&&(replayTab.trigger("click"),replayTab.addClass("active"))}),50);let moreBtn=(0,_jquery.default)("body #more"+userid+questionid);return moreBtn.length>0&&((0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)("#analytic"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#diff"+userid+questionid).prop("disabled",!0),(0,_jquery.default)("#analytic"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),(0,_jquery.default)("#diff"+userid+questionid).css({"background-color":"rgba(168, 168, 168, 0.133)",cursor:"not-allowed"}),moreBtn.on("click",(function(e){e.preventDefault(),self.learnMore((0,_jquery.default)(this),context,userid,questionid,replayInstances)}))),!0})).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;(0,_jquery.default)("body").on("click","#analytic"+userid+questionid,(function(e){(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","analytics"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","analytics"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),templates.render("tiny_cursive/analytics_table",context).then((function(html){return(0,_jquery.default)("#content"+userid).html(html),(0,_jquery.default)("#content"+userid+" .tiny_cursive_table tbody tr:first-child td:nth-child(2)").html(authIcon),!0})).fail((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))),(0,_jquery.default)("body").on("click","#diff"+userid+questionid,(function(e){if((0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","diff"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"none"}),(0,_jquery.default)("#content"+userid).removeClass("tiny_cursive_outputElement").addClass("tiny_cursive").attr("data-label","diff"),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),!fileid)throw(0,_jquery.default)("#content"+userid).html(nodata),new Error("Missing file id or Difference Content not received yet");(0,_ajax.call)([{methodname:"cursive_get_writing_differences",args:{fileid:fileid}}])[0].done((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"}]).done((strings=>{const originalTextString=strings[0],editsPastesAIString=strings[1],commentBox=(0,_jquery.default)('
');var pasteCountDiv=(0,_jquery.default)("
");(0,_str.get_string)("pastecount","tiny_cursive").then((str=>(pasteCountDiv.append("
"+str+" : "+responsedata.commentscount+"
"),!0))).catch((error=>window.console.log(error)));var commentsDiv=(0,_jquery.default)('
');(0,_str.get_string)("comments","tiny_cursive").then((str=>(commentsDiv.append(""+str+""),!0))).catch((error=>window.console.error(error)));var commentsList=(0,_jquery.default)("
");let comments=responsedata.comments;for(let index in comments){var commentDiv=(0,_jquery.default)('
').text(comments[index].usercomment);commentsList.append(commentDiv)}commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);const $legend=(0,_jquery.default)('
'),$attributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$attributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box attributed"}),$attributedText=(0,_jquery.default)("").text(originalTextString);$attributedItem.append($attributedBox).append($attributedText);const $unattributedItem=(0,_jquery.default)("
",{class:"tiny_cursive-legend-item"}),$unattributedBox=(0,_jquery.default)("
",{class:"tiny_cursive-box tiny_cursive_added"}),$unattributedText=(0,_jquery.default)("").text(editsPastesAIString);$unattributedItem.append($unattributedBox).append($unattributedText),$legend.append($attributedItem).append($unattributedItem);let contents=(0,_jquery.default)("
").addClass("tiny_cursive-comparison-content"),textBlock2=(0,_jquery.default)("
").addClass("tiny_cursive-text-block").append((0,_jquery.default)("
").attr("id","tiny_cursive-reconstructed_text").html(JSON.parse(submittedText)));contents.append(commentBox,$legend,textBlock2),(0,_jquery.default)("#content"+userid).html(contents)})).fail((error=>{window.console.error("Failed to load language strings:",error),(0,_jquery.default)("#content"+userid).html(nodata)}))}else(0,_jquery.default)("#content"+userid).html(nodata)})).fail((error=>{throw(0,_jquery.default)("#content"+userid).html(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;(0,_jquery.default)("body").on("click","#rep"+userid+questionid,(function(e){filepath&&((0,_jquery.default)("#replayControls_"+userid+questionid).removeClass("d-none"),(0,_jquery.default)("#content"+userid).addClass("tiny_cursive_outputElement")),(0,_jquery.default)(this).prop("disabled",!0),(0,_jquery.default)("#quality"+userid+questionid).prop("disabled",!1),(0,_jquery.default)("#content"+userid).attr("data-label","replay"),(0,_jquery.default)("#player_"+userid+questionid).css({display:"block","padding-right":"8px"}),e.preventDefault(),(0,_jquery.default)("#content"+userid).html((0,_jquery.default)("
").addClass("d-flex justify-content-center my-5").append((0,_jquery.default)("
").addClass("tiny_cursive-loader"))),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)(this).addClass("active"),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),questionid?video_playback(userid,filepath,questionid):video_playback(userid,filepath)}))}learnMore(moreBtn,context,userid,questionid,replayInstances){(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),moreBtn.addClass("active"),(0,_jquery.default)("#rep"+userid+questionid).prop("disabled",!1),replayInstances&&replayInstances[userid]&&replayInstances[userid].stopReplay(),(0,_jquery.default)("#content"+userid+questionid).removeClass("tiny_cursive_outputElement"),(0,_jquery.default)("#replayControls_"+userid+questionid).addClass("d-none"),_templates.default.render("tiny_cursive/learn_more",context).then((function(html){return(0,_jquery.default)("#content"+userid+questionid).html(html),!0})).fail((function(error){window.console.error("Failed to render template:",error)}))}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";return 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"),score").addClass(icon).attr("style",color).attr("title",localStorage.getItem("notenoughtinfo"))):(0,_jquery.default)("").addClass(icon).attr("style",color)}},_exports.default})); //# sourceMappingURL=analytic_events.min.js.map \ No newline at end of file diff --git a/amd/build/analytic_events.min.js.map b/amd/build/analytic_events.min.js.map index 536eeb19..321173da 100644 --- a/amd/build/analytic_events.min.js.map +++ b/amd/build/analytic_events.min.js.map @@ -1 +1 @@ -{"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 and showing quality metrics.\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 $ from 'jquery';\nimport {get_string as getString} from 'core/str';\nimport {get_strings as getStrings} from 'core/str';\nimport template from 'core/templates';\n\nexport default class AnalyticEvents {\n\n constructor() {\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\n localStorage.setItem('notenoughtinfo', str);\n return str;\n }).catch(error => window.console.log(error));\n }\n\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\n const self = this;\n $('#analytics' + userid + questionid).on('click', function(e) {\n e.preventDefault();\n\n const isReplayButton = $(this).find('.tiny_cursive-replay-button').length > 0;\n // Create Moodle modal\n myModal.create({templateContext: context}).then(modal => {\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n modal.show();\n\n if (isReplayButton) {\n setTimeout(() => {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n\n const replayTab = $('#rep' + userid + questionid);\n if (replayTab.length) {\n replayTab.trigger('click');\n replayTab.addClass('active');\n }\n }, 50);\n }\n\n let moreBtn = $('body #more' + userid + questionid);\n if (moreBtn.length > 0) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('#analytic' + userid + questionid).prop('disabled', true);\n $('#diff' + userid + questionid).prop('disabled', true);\n $('#analytic' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n $('#diff' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n moreBtn.on('click', function(e) {\n e.preventDefault();\n self.learnMore($(this), context, userid, questionid, replayInstances);\n });\n }\n\n return true;\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\n $('body').on('click', '#analytic' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'analytics');\n $('#player_' + userid + questionid).css({'display': 'none'});\n $('#content' + userid).removeClass('tiny_cursive_outputElement')\n .addClass('tiny_cursive').attr('data-label', 'analytics');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n\n templates.render('tiny_cursive/analytics_table', context).then(function(html) {\n $('#content' + userid).html(html);\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 getString('nopaylod', 'tiny_cursive').then(str => {\n nodata.textContent = str;\n return true;\n }).catch(error => window.console.log(error));\n $('body').on('click', '#diff' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'diff');\n $('#player_' + userid + questionid).css({\n 'display': 'none'\n });\n $('#content' + userid).removeClass('tiny_cursive_outputElement').addClass('tiny_cursive').attr('data-label', 'diff');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active');\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n if (!fileid) {\n $('#content' + userid).html(nodata);\n throw new Error('Missing file id or Difference Content not received yet');\n }\n getContent([{\n methodname: 'cursive_get_writing_differences',\n args: {fileid: fileid},\n }])[0].done(response => {\n let responsedata = JSON.parse(response.data);\n if (responsedata) {\n let submittedText = atob(responsedata.submitted_text);\n\n // Fetch the dynamic strings.\n getStrings([\n {key: 'original_text', component: 'tiny_cursive'},\n {key: 'editspastesai', component: 'tiny_cursive'}\n ]).done(strings => {\n const originalTextString = strings[0];\n const editsPastesAIString = strings[1];\n\n const commentBox = $('
');\n var pasteCountDiv = $('
');\n getString('pastecount', 'tiny_cursive').then(str => {\n pasteCountDiv.append('
' + str + ' : ' + responsedata.commentscount + '
');\n return true;\n }).catch(error => window.console.log(error));\n\n var commentsDiv = $('
');\n getString('comments', 'tiny_cursive').then(str => {\n commentsDiv.append('' + str + '');\n return true;\n }).catch(error => window.console.error(error));\n\n var commentsList = $('
');\n\n let comments = responsedata.comments;\n for (let index in comments) {\n var commentDiv = $(`
`).text(comments[index].usercomment);\n commentsList.append(commentDiv);\n }\n commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);\n\n const $legend = $('
');\n\n // Create the first legend item\n const $attributedItem = $('
', {\"class\": \"tiny_cursive-legend-item\"});\n const $attributedBox = $('
', {\"class\": \"tiny_cursive-box attributed\"});\n const $attributedText = $('').text(originalTextString);\n $attributedItem.append($attributedBox).append($attributedText);\n\n // Create the second legend item\n const $unattributedItem = $('
', {\"class\": 'tiny_cursive-legend-item'});\n const $unattributedBox = $('
', {\"class\": 'tiny_cursive-box tiny_cursive_added'});\n const $unattributedText = $('').text(editsPastesAIString);\n $unattributedItem.append($unattributedBox).append($unattributedText);\n\n // Append the legend items to the legend container.\n $legend.append($attributedItem).append($unattributedItem);\n\n let contents = $('
').addClass('tiny_cursive-comparison-content');\n let textBlock2 = $('
').addClass('tiny_cursive-text-block').append(\n $('
').attr('id', 'tiny_cursive-reconstructed_text').html(JSON.parse(submittedText))\n );\n\n contents.append(commentBox, $legend, textBlock2);\n $('#content' + userid).html(contents); // Update content.\n }).fail(error => {\n window.console.error(\"Failed to load language strings:\", error);\n $('#content' + userid).html(nodata);\n });\n } else {\n $('#content' + userid).html(nodata);\n }\n }).fail(error => {\n $('#content' + userid).html(nodata);\n throw new Error('Error loading JSON file: ' + error.message);\n });\n });\n }\n\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\n $('body').on('click', '#rep' + userid + questionid, function(e) {\n\n if (filepath) {\n $('#replayControls_' + userid + questionid).removeClass('d-none');\n $('#content' + userid).addClass('tiny_cursive_outputElement');\n }\n\n $(this).prop('disabled', true);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'replay');\n $('#player_' + userid + questionid).css({\n 'display': 'block',\n 'padding-right': '8px'\n });\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\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 learnMore(moreBtn, context, userid, questionid, replayInstances) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n moreBtn.addClass('active');\n $('#rep' + userid + questionid).prop('disabled', false);\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('#content' + userid + questionid).removeClass('tiny_cursive_outputElement');\n $('#replayControls_' + userid + questionid).addClass('d-none');\n template.render('tiny_cursive/learn_more', context).then(function(html) {\n $('#content' + userid + questionid).html(html);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 if (score < scoreSetting) {\n icon = 'fa fa-question-circle';\n color = 'font-size:32px;color:#A9A9A9';\n return $('').addClass(icon).attr('style', color).attr('title', localStorage.getItem('notenoughtinfo'));\n } else {\n return $('').addClass(icon).attr('style', color);\n }\n\n }\n}\n"],"names":["constructor","then","str","localStorage","setItem","catch","error","window","console","log","createModal","userid","context","questionid","replayInstances","authIcon","self","this","on","e","preventDefault","isReplayButton","find","length","create","templateContext","modal","html","show","setTimeout","removeClass","replayTab","trigger","addClass","moreBtn","prop","css","learnMore","analytics","templates","attr","append","stopReplay","render","fail","checkDiff","fileid","nodata","document","createElement","classList","add","textContent","Error","methodname","args","done","response","responsedata","JSON","parse","data","submittedText","atob","submitted_text","key","component","strings","originalTextString","editsPastesAIString","commentBox","pasteCountDiv","commentscount","commentsDiv","commentsList","comments","index","commentDiv","text","usercomment","$legend","$attributedItem","$attributedBox","$attributedText","$unattributedItem","$unattributedBox","$unattributedText","contents","textBlock2","message","replyWriting","filepath","video_playback","formatedTime","total_time_seconds","totalTimeSeconds","hours","Math","floor","toString","padStart","minutes","seconds","authorshipStatus","firstFile","score","scoreSetting","icon","color","parseFloat","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 and showing quality metrics.\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 $ from 'jquery';\nimport {get_string as getString} from 'core/str';\nimport {get_strings as getStrings} from 'core/str';\nimport template from 'core/templates';\n\nexport default class AnalyticEvents {\n\n constructor() {\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\n localStorage.setItem('notenoughtinfo', str);\n return str;\n }).catch(error => window.console.log(error));\n }\n\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\n const self = this;\n $('#analytics' + userid + questionid).on('click', function(e) {\n e.preventDefault();\n\n const isReplayButton = $(this).find('.tiny_cursive-replay-button').length > 0;\n // Create Moodle modal\n myModal.create({templateContext: context}).then(modal => {\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n modal.show();\n\n if (isReplayButton) {\n setTimeout(() => {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n\n const replayTab = $('#rep' + userid + questionid);\n if (replayTab.length) {\n replayTab.trigger('click');\n replayTab.addClass('active');\n }\n }, 50);\n }\n\n let moreBtn = $('body #more' + userid + questionid);\n if (moreBtn.length > 0) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('#analytic' + userid + questionid).prop('disabled', true);\n $('#diff' + userid + questionid).prop('disabled', true);\n $('#analytic' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n $('#diff' + userid + questionid).css({\n 'background-color': 'rgba(168, 168, 168, 0.133)',\n 'cursor': 'not-allowed'\n });\n moreBtn.on('click', function(e) {\n e.preventDefault();\n self.learnMore($(this), context, userid, questionid, replayInstances);\n });\n }\n\n return true;\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\n $('body').on('click', '#analytic' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'analytics');\n $('#player_' + userid + questionid).css({'display': 'none'});\n $('#content' + userid).removeClass('tiny_cursive_outputElement')\n .addClass('tiny_cursive').attr('data-label', 'analytics');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n\n templates.render('tiny_cursive/analytics_table', context).then(function(html) {\n $('#content' + userid).html(html);\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 getString('nopaylod', 'tiny_cursive').then(str => {\n nodata.textContent = str;\n return true;\n }).catch(error => window.console.log(error));\n $('body').on('click', '#diff' + userid + questionid, function(e) {\n $('#rep' + userid + questionid).prop('disabled', false);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'diff');\n $('#player_' + userid + questionid).css({\n 'display': 'none'\n });\n $('#content' + userid).removeClass('tiny_cursive_outputElement').addClass('tiny_cursive').attr('data-label', 'diff');\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active');\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n if (!fileid) {\n $('#content' + userid).html(nodata);\n throw new Error('Missing file id or Difference Content not received yet');\n }\n getContent([{\n methodname: 'cursive_get_writing_differences',\n args: {fileid: fileid},\n }])[0].done(response => {\n let responsedata = JSON.parse(response.data);\n if (responsedata) {\n let submittedText = atob(responsedata.submitted_text);\n\n // Fetch the dynamic strings.\n getStrings([\n {key: 'original_text', component: 'tiny_cursive'},\n {key: 'editspastesai', component: 'tiny_cursive'}\n ]).done(strings => {\n const originalTextString = strings[0];\n const editsPastesAIString = strings[1];\n\n const commentBox = $('
');\n var pasteCountDiv = $('
');\n getString('pastecount', 'tiny_cursive').then(str => {\n pasteCountDiv.append('
' + str + ' : ' + responsedata.commentscount + '
');\n return true;\n }).catch(error => window.console.log(error));\n\n var commentsDiv = $('
');\n getString('comments', 'tiny_cursive').then(str => {\n commentsDiv.append('' + str + '');\n return true;\n }).catch(error => window.console.error(error));\n\n var commentsList = $('
');\n\n let comments = responsedata.comments;\n for (let index in comments) {\n var commentDiv = $(`
`).text(comments[index].usercomment);\n commentsList.append(commentDiv);\n }\n commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);\n\n const $legend = $('
');\n\n // Create the first legend item\n const $attributedItem = $('
', {\"class\": \"tiny_cursive-legend-item\"});\n const $attributedBox = $('
', {\"class\": \"tiny_cursive-box attributed\"});\n const $attributedText = $('').text(originalTextString);\n $attributedItem.append($attributedBox).append($attributedText);\n\n // Create the second legend item\n const $unattributedItem = $('
', {\"class\": 'tiny_cursive-legend-item'});\n const $unattributedBox = $('
', {\"class\": 'tiny_cursive-box tiny_cursive_added'});\n const $unattributedText = $('').text(editsPastesAIString);\n $unattributedItem.append($unattributedBox).append($unattributedText);\n\n // Append the legend items to the legend container.\n $legend.append($attributedItem).append($unattributedItem);\n\n let contents = $('
').addClass('tiny_cursive-comparison-content');\n let textBlock2 = $('
').addClass('tiny_cursive-text-block').append(\n $('
').attr('id', 'tiny_cursive-reconstructed_text').html(JSON.parse(submittedText))\n );\n\n contents.append(commentBox, $legend, textBlock2);\n $('#content' + userid).html(contents); // Update content.\n }).fail(error => {\n window.console.error(\"Failed to load language strings:\", error);\n $('#content' + userid).html(nodata);\n });\n } else {\n $('#content' + userid).html(nodata);\n }\n }).fail(error => {\n $('#content' + userid).html(nodata);\n throw new Error('Error loading JSON file: ' + error.message);\n });\n });\n }\n\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\n $('body').on('click', '#rep' + userid + questionid, function(e) {\n\n if (filepath) {\n $('#replayControls_' + userid + questionid).removeClass('d-none');\n $('#content' + userid).addClass('tiny_cursive_outputElement');\n }\n\n $(this).prop('disabled', true);\n $('#quality' + userid + questionid).prop('disabled', false);\n $('#content' + userid).attr('data-label', 'replay');\n $('#player_' + userid + questionid).css({\n 'display': 'block',\n 'padding-right': '8px'\n });\n e.preventDefault();\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\n .append($('
').addClass('tiny_cursive-loader')));\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $(this).addClass('active'); // Add 'active' class to the clicked element\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\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 learnMore(moreBtn, context, userid, questionid, replayInstances) {\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n moreBtn.addClass('active');\n $('#rep' + userid + questionid).prop('disabled', false);\n if (replayInstances && replayInstances[userid]) {\n replayInstances[userid].stopReplay();\n }\n $('#content' + userid + questionid).removeClass('tiny_cursive_outputElement');\n $('#replayControls_' + userid + questionid).addClass('d-none');\n template.render('tiny_cursive/learn_more', context).then(function(html) {\n $('#content' + userid + questionid).html(html);\n return true;\n }).fail(function(error) {\n window.console.error(\"Failed to render template:\", error);\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 if (score < scoreSetting) {\n icon = 'fa fa-question-circle';\n color = 'font-size:32px;color:#A9A9A9';\n return $('').addClass(icon).attr('style', color).attr('title', localStorage.getItem('notenoughtinfo'));\n } else {\n return $('').addClass(icon).attr('style', color);\n }\n\n }\n}\n"],"names":["constructor","then","str","localStorage","setItem","catch","error","window","console","log","createModal","userid","context","questionid","replayInstances","authIcon","self","this","on","e","preventDefault","isReplayButton","find","length","create","templateContext","modal","html","show","setTimeout","removeClass","replayTab","trigger","addClass","moreBtn","prop","css","learnMore","analytics","templates","attr","append","stopReplay","render","fail","checkDiff","fileid","nodata","document","createElement","classList","add","textContent","Error","methodname","args","done","response","responsedata","JSON","parse","data","submittedText","atob","submitted_text","key","component","strings","originalTextString","editsPastesAIString","commentBox","pasteCountDiv","commentscount","commentsDiv","commentsList","comments","index","commentDiv","text","usercomment","$legend","$attributedItem","$attributedBox","$attributedText","$unattributedItem","$unattributedBox","$unattributedText","contents","textBlock2","message","replyWriting","filepath","video_playback","formatedTime","total_time_seconds","totalTimeSeconds","Math","floor","toString","padStart","authorshipStatus","firstFile","score","scoreSetting","icon","color","parseFloat","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 7444b4d6..b0b20b2d 100644 --- a/amd/build/analytic_modal.min.js +++ b/amd/build/analytic_modal.min.js @@ -1,3 +1,10 @@ -define("tiny_cursive/analytic_modal",["exports","core/modal","jquery"],(function(_exports,_modal,_jquery){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default: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=_interopRequireDefault(_modal),_jquery=_interopRequireDefault(_jquery);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",overflow:"auto","scrollbar-width":"none"}),root.find(".modal-dialog").css({"max-width":"800px","background-color":"transparent"}),root.find("#analytic-close").on("click",(()=>{this.destroy()})),root.on("click",(e=>{(0,_jquery.default)(e.target).hasClass("modal")&&this.destroy()}))}}return _exports.default=MyModal,_defineProperty(MyModal,"TYPE","tiny_cursive/analytics_modal"),_defineProperty(MyModal,"TEMPLATE","tiny_cursive/analytics_modal"),_exports.default})); +define("tiny_cursive/analytic_modal",["exports","core/modal","jquery"],(function(_exports,_modal,_jquery){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default: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=_interopRequireDefault(_modal),_jquery=_interopRequireDefault(_jquery);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",overflow:"auto","scrollbar-width":"none"}),root.find(".modal-dialog").css({"max-width":"800px","background-color":"transparent"}),root.find("#analytic-close").on("click",(()=>{this.destroy()})),root.on("click",(e=>{(0,_jquery.default)(e.target).hasClass("modal")&&this.destroy()}))}}return _exports.default=MyModal,_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 0e95843c..068d5e6b 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';\nimport $ from 'jquery';\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 'overflow': 'auto',\n 'scrollbar-width': 'none'\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).hasClass('modal')) {\n this.destroy();\n }\n });\n }\n}\n"],"names":["MyModal","Modal","configure","modalConfig","show","removeOnClose","backdrop","root","this","getRoot","find","remove","css","addClass","on","destroy","e","target","hasClass"],"mappings":"ogBAyBqBA,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,gBACL,yBACO,SAEvBL,KAAKG,KAAK,iBAAiBE,IAAI,aACd,2BACO,gBAIxBL,KAAKG,KAAK,mBAAmBI,GAAG,SAAS,UAChCC,aAITR,KAAKO,GAAG,SAAUE,KACV,mBAAEA,EAAEC,QAAQC,SAAS,eAChBH,8DAjDAf,eACH,gDADGA,mBAEC"} \ 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';\nimport $ from 'jquery';\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 'overflow': 'auto',\n 'scrollbar-width': 'none'\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).hasClass('modal')) {\n this.destroy();\n }\n });\n }\n}\n"],"names":["MyModal","Modal","configure","modalConfig","show","removeOnClose","backdrop","root","this","getRoot","find","remove","css","addClass","on","destroy","e","target","hasClass"],"mappings":";;;;;;;yKAyBqBA,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,gBACL,yBACO,SAEvBL,KAAKG,KAAK,iBAAiBE,IAAI,aACd,2BACO,gBAIxBL,KAAKG,KAAK,mBAAmBI,GAAG,SAAS,UAChCC,aAITR,KAAKO,GAAG,SAAUE,KACV,mBAAEA,EAAEC,QAAQC,SAAS,eAChBH"} \ No newline at end of file diff --git a/amd/build/append_fourm_post.min.js.map b/amd/build/append_fourm_post.min.js.map index ff2485a3..bada2509 100644 --- a/amd/build/append_fourm_post.min.js.map +++ b/amd/build/append_fourm_post.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_fourm_post.min.js","sources":["../src/append_fourm_post.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/append_fourm_post\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\",\n \"./replay_button\", \"./analytic_events\"], function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n replayButton,\n AnalyticEvents\n) {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n var usersTable = {\n init: function(scoreSetting, showcomment, hasApiKey) {\n str\n .get_strings([\n {key: \"field_require\", component: \"tiny_cursive\"},\n ])\n .done(function() {\n usersTable.getToken(scoreSetting, showcomment, hasApiKey);\n });\n },\n getToken: function(scoreSetting, showcomment, hasApiKey) {\n $('#page-mod-forum-discuss').find(\"article\").get().forEach(function(entry) {\n var replyButton = $('a[data-region=\"post-action\"][title=\"Reply\"]');\n if (replyButton.length > 0) {\n replyButton.on('click', function(event) {\n // Check if user is a teacher/admin\n var isTeacher = $('#body').hasClass('teacher_admin');\n if (isTeacher) {\n return true;\n }\n event.preventDefault();\n var url = $(this).attr('href');\n\n // Split URL into base and hash\n var urlParts = url.split('#');\n var baseUrl = urlParts[0];\n var hash = urlParts.length > 1 ? '#' + urlParts[1] : '';\n\n if (baseUrl.indexOf('setformat=') > -1) {\n baseUrl = baseUrl.replace(/setformat=\\d/, 'setformat=1');\n } else if (baseUrl.indexOf('?') > -1) {\n baseUrl += '&setformat=1';\n } else {\n baseUrl += '?setformat=1';\n }\n var finalUrl = baseUrl + hash;\n\n window.location.href = finalUrl;\n });\n }\n\n var ids = $(\"#\" + entry.id).data(\"post-id\");\n var cmid = M.cfg.contextInstanceId;\n\n let args = {id: ids, modulename: \"forum\", cmid: cmid};\n let methodname = 'cursive_get_forum_comment_link';\n let com = AJAX.call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n if (filepath) {\n\n let analyticButtonDiv = document.createElement('div');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(ids));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, ids));\n }\n\n analyticButtonDiv.classList.add('text-center', 'my-2');\n analyticButtonDiv.dataset.region = \"analytic-div\" + ids;\n\n $(\"#\" + entry.id).find('#post-content-' + ids).prepend(analyticButtonDiv);\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: ids,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(ids, context, '', replayInstances, authIcon);\n myEvents.analytics(ids, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(ids, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(ids, filepath, '', replayInstances);\n }\n\n });\n return com.usercomment;\n });\n },\n };\n return usersTable;\n\n\n});"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","replayButton","AnalyticEvents","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","get_strings","key","component","done","getToken","find","get","forEach","entry","replyButton","length","on","event","hasClass","preventDefault","urlParts","this","attr","split","baseUrl","hash","indexOf","replace","finalUrl","location","href","ids","id","data","cmid","M","cfg","contextInstanceId","args","modulename","com","call","methodname","json","JSON","parse","filename","analyticButtonDiv","document","createElement","append","effort_ratio","classList","add","dataset","region","prepend","myEvents","context","tabledata","formattime","formatedTime","page","userid","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","usercomment"],"mappings":"AAsBAA,wCAAO,CAAC,SAAU,YAAa,WAAY,iBAAkB,WAAY,oBACrE,kBAAmB,sBAAsB,SACzCC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,aACAC,sBAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAASC,IAAKC,aACjB,KAAbA,SAAiB,OACXC,OAAS,IAAIT,OACf,UAAYO,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,YAEvBV,UAAUW,OAAO,8BAA8BC,MAAKC,OAChDhB,EAAE,WAAaW,KAAKK,KAAKA,OAClB,KACRC,OAAMC,GAAKT,OAAOU,QAAQC,MAAMF,YAEhC,OAIPG,WAAa,CACbC,KAAM,SAASC,aAAcC,YAAaC,WACtCvB,IACKwB,YAAY,CACT,CAACC,IAAK,gBAAiBC,UAAW,kBAErCC,MAAK,WACFR,WAAWS,SAASP,aAAcC,YAAaC,eAG3DK,SAAU,SAASP,aAAcC,YAAaC,WAC1CzB,EAAE,2BAA2B+B,KAAK,WAAWC,MAAMC,SAAQ,SAASC,WAC5DC,YAAcnC,EAAE,+CAChBmC,YAAYC,OAAS,GACrBD,YAAYE,GAAG,SAAS,SAASC,UAEbtC,EAAE,SAASuC,SAAS,wBAEzB,EAEXD,MAAME,qBAIFC,SAHMzC,EAAE0C,MAAMC,KAAK,QAGJC,MAAM,KACrBC,QAAUJ,SAAS,GACnBK,KAAOL,SAASL,OAAS,EAAI,IAAMK,SAAS,GAAK,GAEjDI,QAAQE,QAAQ,eAAiB,EACjCF,QAAUA,QAAQG,QAAQ,eAAgB,eACnCH,QAAQE,QAAQ,MAAQ,EAC/BF,SAAW,eAEXA,SAAW,mBAEXI,SAAWJ,QAAUC,KAEzBrC,OAAOyC,SAASC,KAAOF,gBAI3BG,IAAMpD,EAAE,IAAMkC,MAAMmB,IAAIC,KAAK,WAC7BC,KAAOC,EAAEC,IAAIC,sBAEbC,KAAO,CAACN,GAAID,IAAKQ,WAAY,QAASL,KAAMA,MAE5CM,IAAM5D,KAAK6D,KAAK,CAAC,CAACC,WADL,iCACiBJ,KAAAA,eAClCE,IAAI,GAAGhC,MAAK,SAASmC,UACbV,KAAOW,KAAKC,MAAMF,MAElBpD,SAAW,MACX0C,KAAKA,KAAKa,WACVvD,SAAW0C,KAAKA,KAAKa,UAErBvD,SAAU,KAENwD,kBAAoBC,SAASC,cAAc,OAE1C7C,UAGD2C,kBAAkBG,OAAOlE,eAAeiD,KAAKA,KAAKkB,aAAcpB,MAFhEpD,EAAEoE,mBAAmBpD,KAAKV,aAAa8C,MAK3CgB,kBAAkBK,UAAUC,IAAI,cAAe,QAC/CN,kBAAkBO,QAAQC,OAAS,eAAiBxB,IAEpDpD,EAAE,IAAMkC,MAAMmB,IAAItB,KAAK,iBAAmBqB,KAAKyB,QAAQT,uBAEnDU,SAAW,IAAIvE,mBACfwE,QAAU,CACVC,UAAW1B,KAAKA,KAChB2B,WAAYH,SAASI,aAAa5B,KAAKA,MACvC6B,KAAM5D,aACN6D,OAAQhC,IACRiC,OAAQ5D,eAGR6D,SAAWR,SAASS,iBAAiBjC,KAAKA,KAAKkC,WAAYlC,KAAKA,KAAKmC,MAAOlE,cAChFuD,SAASY,YAAYtC,IAAK2B,QAAS,GAAIvE,gBAAiB8E,UACxDR,SAASa,UAAUvC,IAAKjD,UAAW4E,QAAS,GAAIvE,gBAAiB8E,UACjER,SAASc,UAAUxC,IAAKE,KAAKA,KAAKuC,QAAS,GAAIrF,iBAC/CsE,SAASgB,aAAa1C,IAAKxC,SAAU,GAAIJ,qBAI1CqD,IAAIkC,wBAIhB1E"} \ No newline at end of file +{"version":3,"file":"append_fourm_post.min.js","sources":["../src/append_fourm_post.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/append_fourm_post\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\",\n \"./replay_button\", \"./analytic_events\"], function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n replayButton,\n AnalyticEvents\n) {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n var usersTable = {\n init: function(scoreSetting, showcomment, hasApiKey) {\n str\n .get_strings([\n {key: \"field_require\", component: \"tiny_cursive\"},\n ])\n .done(function() {\n usersTable.getToken(scoreSetting, showcomment, hasApiKey);\n });\n },\n getToken: function(scoreSetting, showcomment, hasApiKey) {\n $('#page-mod-forum-discuss').find(\"article\").get().forEach(function(entry) {\n var replyButton = $('a[data-region=\"post-action\"][title=\"Reply\"]');\n if (replyButton.length > 0) {\n replyButton.on('click', function(event) {\n var isTeacher = $('#body').hasClass('teacher_admin');\n if (isTeacher) {\n return true;\n }\n event.preventDefault();\n var url = $(this).attr('href');\n\n var urlParts = url.split('#');\n var baseUrl = urlParts[0];\n var hash = urlParts.length > 1 ? '#' + urlParts[1] : '';\n\n if (baseUrl.indexOf('setformat=') > -1) {\n baseUrl = baseUrl.replace(/setformat=\\d/, 'setformat=1');\n } else if (baseUrl.indexOf('?') > -1) {\n baseUrl += '&setformat=1';\n } else {\n baseUrl += '?setformat=1';\n }\n var finalUrl = baseUrl + hash;\n\n window.location.href = finalUrl;\n });\n }\n\n var ids = $(\"#\" + entry.id).data(\"post-id\");\n var cmid = M.cfg.contextInstanceId;\n\n let args = {id: ids, modulename: \"forum\", cmid: cmid};\n let methodname = 'cursive_get_forum_comment_link';\n let com = AJAX.call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n if (filepath) {\n\n let analyticButtonDiv = document.createElement('div');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(ids));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, ids));\n }\n\n analyticButtonDiv.classList.add('text-center', 'my-2');\n analyticButtonDiv.dataset.region = \"analytic-div\" + ids;\n\n $(\"#\" + entry.id).find('#post-content-' + ids).prepend(analyticButtonDiv);\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: ids,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(ids, context, '', replayInstances, authIcon);\n myEvents.analytics(ids, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(ids, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(ids, filepath, '', replayInstances);\n }\n\n });\n return com.usercomment;\n });\n },\n };\n return usersTable;\n\n\n});"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","replayButton","AnalyticEvents","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","get_strings","key","component","done","getToken","find","get","forEach","entry","replyButton","length","on","event","hasClass","preventDefault","urlParts","this","attr","split","baseUrl","hash","indexOf","replace","finalUrl","location","href","ids","id","data","cmid","M","cfg","contextInstanceId","args","modulename","com","call","methodname","json","JSON","parse","filename","analyticButtonDiv","document","createElement","append","effort_ratio","classList","add","dataset","region","prepend","myEvents","context","tabledata","formattime","formatedTime","page","userid","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","usercomment"],"mappings":"AAsBAA,wCAAO,CAAC,SAAU,YAAa,WAAY,iBAAkB,WAAY,oBACrE,kBAAmB,sBAAsB,SACzCC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,aACAC,sBAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAASC,IAAKC,aACjB,KAAbA,SAAiB,OACXC,OAAS,IAAIT,OACf,UAAYO,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,YAEvBV,UAAUW,OAAO,8BAA8BC,MAAKC,OAChDhB,EAAE,WAAaW,KAAKK,KAAKA,OAClB,KACRC,OAAMC,GAAKT,OAAOU,QAAQC,MAAMF,YAEhC,OAIPG,WAAa,CACbC,KAAM,SAASC,aAAcC,YAAaC,WACtCvB,IACKwB,YAAY,CACT,CAACC,IAAK,gBAAiBC,UAAW,kBAErCC,MAAK,WACFR,WAAWS,SAASP,aAAcC,YAAaC,eAG3DK,SAAU,SAASP,aAAcC,YAAaC,WAC1CzB,EAAE,2BAA2B+B,KAAK,WAAWC,MAAMC,SAAQ,SAASC,WAC5DC,YAAcnC,EAAE,+CAChBmC,YAAYC,OAAS,GACrBD,YAAYE,GAAG,SAAS,SAASC,UACbtC,EAAE,SAASuC,SAAS,wBAEzB,EAEXD,MAAME,qBAGFC,SAFMzC,EAAE0C,MAAMC,KAAK,QAEJC,MAAM,KACrBC,QAAUJ,SAAS,GACnBK,KAAOL,SAASL,OAAS,EAAI,IAAMK,SAAS,GAAK,GAEjDI,QAAQE,QAAQ,eAAiB,EACjCF,QAAUA,QAAQG,QAAQ,eAAgB,eACnCH,QAAQE,QAAQ,MAAQ,EAC/BF,SAAW,eAEXA,SAAW,mBAEXI,SAAWJ,QAAUC,KAEzBrC,OAAOyC,SAASC,KAAOF,gBAI3BG,IAAMpD,EAAE,IAAMkC,MAAMmB,IAAIC,KAAK,WAC7BC,KAAOC,EAAEC,IAAIC,sBAEbC,KAAO,CAACN,GAAID,IAAKQ,WAAY,QAASL,KAAMA,MAE5CM,IAAM5D,KAAK6D,KAAK,CAAC,CAACC,WADL,iCACiBJ,KAAAA,eAClCE,IAAI,GAAGhC,MAAK,SAASmC,UACbV,KAAOW,KAAKC,MAAMF,MAElBpD,SAAW,MACX0C,KAAKA,KAAKa,WACVvD,SAAW0C,KAAKA,KAAKa,UAErBvD,SAAU,KAENwD,kBAAoBC,SAASC,cAAc,OAE1C7C,UAGD2C,kBAAkBG,OAAOlE,eAAeiD,KAAKA,KAAKkB,aAAcpB,MAFhEpD,EAAEoE,mBAAmBpD,KAAKV,aAAa8C,MAK3CgB,kBAAkBK,UAAUC,IAAI,cAAe,QAC/CN,kBAAkBO,QAAQC,OAAS,eAAiBxB,IAEpDpD,EAAE,IAAMkC,MAAMmB,IAAItB,KAAK,iBAAmBqB,KAAKyB,QAAQT,uBAEnDU,SAAW,IAAIvE,mBACfwE,QAAU,CACVC,UAAW1B,KAAKA,KAChB2B,WAAYH,SAASI,aAAa5B,KAAKA,MACvC6B,KAAM5D,aACN6D,OAAQhC,IACRiC,OAAQ5D,eAGR6D,SAAWR,SAASS,iBAAiBjC,KAAKA,KAAKkC,WAAYlC,KAAKA,KAAKmC,MAAOlE,cAChFuD,SAASY,YAAYtC,IAAK2B,QAAS,GAAIvE,gBAAiB8E,UACxDR,SAASa,UAAUvC,IAAKjD,UAAW4E,QAAS,GAAIvE,gBAAiB8E,UACjER,SAASc,UAAUxC,IAAKE,KAAKA,KAAKuC,QAAS,GAAIrF,iBAC/CsE,SAASgB,aAAa1C,IAAKxC,SAAU,GAAIJ,qBAI1CqD,IAAIkC,wBAIhB1E"} \ No newline at end of file diff --git a/amd/build/append_lesson_grade_table.min.js b/amd/build/append_lesson_grade_table.min.js index 52a4b6ae..5f481a66 100644 --- a/amd/build/append_lesson_grade_table.min.js +++ b/amd/build/append_lesson_grade_table.min.js @@ -7,6 +7,6 @@ define("tiny_cursive/append_lesson_grade_table",["exports","./replay","jquery"," * @module tiny_cursive/append_lesson_grade_table * @copyright 2025 CTI * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_replay=_interopRequireDefault(_replay),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates),_analytic_events=_interopRequireDefault(_analytic_events),_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),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);_exports.init=(scoreSetting,showcomment,hasApiKey)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>((0,_jquery.default)("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var cmid=M.cfg.contextInstanceId,emailLink=(0,_jquery.default)('#page-content div[role="main"] td.lastcol a'),headcolumn=(0,_jquery.default)('#region-main div[role="main"] table thead tr');let url=new URL(window.location.href),mode=url.searchParams.get("mode"),user=url.searchParams.get("user");function analytics(userid,cmid,$emailLink,grade){let args={id:userid,modulename:"lesson",cmid:cmid},com=(0,_ajax.call)([{methodname:"cursive_get_lesson_submission_data",args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.res.filename&&(filepath=data.res.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.res.effort_ratio,userid)):(0,_jquery.default)(analyticButtonDiv).html((0,_replay_button.default)(userid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),grade?(analyticButtonDiv.classList.add("w-100"),(0,_jquery.default)("#fitem_id_response_editor .felement").prepend(analyticButtonDiv)):$emailLink.closest("tr").find("td:eq(1)").after(analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.res,formattime:myEvents.formatedTime(data.res),page:scoreSetting,userid:userid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.res.first_file,data.res.score,scoreSetting);myEvents.createModal(userid,context,"",replayInstances,authIcon),myEvents.analytics(userid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(userid,data.res.file_id,"",replayInstances),myEvents.replyWriting(userid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}Str.get_string("analytics","tiny_cursive").then((strs=>(headcolumn.each((function(){(0,_jquery.default)(this).find("th:eq(1)").after(''.concat(strs,""))})),!0))).catch((e=>window.console.error(e))),mode&&"grade"===mode&&analytics(user,cmid,"",!0),emailLink.each((function(){let href=(0,_jquery.default)(this).attr("href");const $emailLink=(0,_jquery.default)(this);let userid=0;href&&(userid=parseInt(new URLSearchParams(href.split("?")[1]).get("userid")),userid?((0,_jquery.default)("#region-main").on("click","table tbody tr td.cell.c1 a",(function(e){e.preventDefault();const link=e.target.href,url=new URL(link);url.searchParams.append("user",userid),window.location.href=url.toString()})),analytics(userid,cmid,$emailLink,!1)):$emailLink.closest("tr").find("td:eq(1)").after(""))}))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_replay=_interopRequireDefault(_replay),_jquery=_interopRequireDefault(_jquery),_templates=_interopRequireDefault(_templates),_analytic_events=_interopRequireDefault(_analytic_events),_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),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);_exports.init=(scoreSetting,showcomment,hasApiKey)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>((0,_jquery.default)("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var cmid=M.cfg.contextInstanceId,emailLink=(0,_jquery.default)('#page-content div[role="main"] td.lastcol a'),headcolumn=(0,_jquery.default)('#region-main div[role="main"] table thead tr');let url=new URL(window.location.href),mode=url.searchParams.get("mode"),user=url.searchParams.get("user");function analytics(userid,cmid,$emailLink,grade){let args={id:userid,modulename:"lesson",cmid:cmid},com=(0,_ajax.call)([{methodname:"cursive_get_lesson_submission_data",args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.res.filename&&(filepath=data.res.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.res.effort_ratio,userid)):(0,_jquery.default)(analyticButtonDiv).html((0,_replay_button.default)(userid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),grade?(analyticButtonDiv.classList.add("w-100"),(0,_jquery.default)("#fitem_id_response_editor .felement").prepend(analyticButtonDiv)):$emailLink.closest("tr").find("td:eq(1)").after(analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.res,formattime:myEvents.formatedTime(data.res),page:scoreSetting,userid:userid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.res.first_file,data.res.score,scoreSetting);myEvents.createModal(userid,context,"",replayInstances,authIcon),myEvents.analytics(userid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(userid,data.res.file_id,"",replayInstances),myEvents.replyWriting(userid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}Str.get_string("analytics","tiny_cursive").then((strs=>(headcolumn.each((function(){(0,_jquery.default)(this).find("th:eq(1)").after(`${strs}`)})),!0))).catch((e=>window.console.error(e))),mode&&"grade"===mode&&analytics(user,cmid,"",!0),emailLink.each((function(){let href=(0,_jquery.default)(this).attr("href");const $emailLink=(0,_jquery.default)(this);let userid=0;href&&(userid=parseInt(new URLSearchParams(href.split("?")[1]).get("userid")),userid?((0,_jquery.default)("#region-main").on("click","table tbody tr td.cell.c1 a",(function(e){e.preventDefault();const link=e.target.href,url=new URL(link);url.searchParams.append("user",userid),window.location.href=url.toString()})),analytics(userid,cmid,$emailLink,!1)):$emailLink.closest("tr").find("td:eq(1)").after(""))}))}})); //# sourceMappingURL=append_lesson_grade_table.min.js.map \ No newline at end of file diff --git a/amd/build/append_lesson_grade_table.min.js.map b/amd/build/append_lesson_grade_table.min.js.map index 6ad80de5..ac5fede6 100644 --- a/amd/build/append_lesson_grade_table.min.js.map +++ b/amd/build/append_lesson_grade_table.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_lesson_grade_table.min.js","sources":["../src/append_lesson_grade_table.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 to append analytics and grade table data for lesson submissions\n * Handles the display of analytics, replay functionality and grade information\n * for lesson submissions in the Moodle gradebook interface\n *\n * @module tiny_cursive/append_lesson_grade_table\n * @copyright 2025 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Replay from './replay';\nimport $ from 'jquery';\nimport {call as getData} from 'core/ajax';\nimport templates from 'core/templates';\nimport AnalyticEvents from './analytic_events';\nimport analyticButton from './analytic_button';\nimport replayButton from './replay_button';\nimport * as Str from 'core/str';\n\nexport const init = (scoreSetting, showcomment, hasApiKey) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n\n var cmid = M.cfg.contextInstanceId;\n var emailLink = $('#page-content div[role=\"main\"] td.lastcol a');\n var headcolumn = $('#region-main div[role=\"main\"] table thead tr');\n let url = new URL(window.location.href);\n let mode = url.searchParams.get('mode');\n let user = url.searchParams.get('user');\n\n Str.get_string('analytics', 'tiny_cursive').then((strs) => {\n headcolumn.each(function() {\n $(this).find('th:eq(1)').after(`${strs}`);\n });\n return true;\n }).catch(e => window.console.error(e));\n\n if (mode && mode === \"grade\") {\n analytics(user, cmid, \"\", true);\n }\n\n emailLink.each(function() {\n let href = $(this).attr('href');\n const $emailLink = $(this);\n let userid = 0;\n if (href) {\n userid = parseInt(new URLSearchParams(href.split('?')[1]).get('userid'));\n if (!userid) {\n $emailLink.closest('tr').find('td:eq(1)').after(\"\"); // For aligning the table column\n } else {\n\n $('#region-main').on('click', 'table tbody tr td.cell.c1 a', function(e) {\n e.preventDefault();\n const link = e.target.href;\n const url = new URL(link);\n url.searchParams.append('user', userid);\n window.location.href = url.toString();\n });\n\n analytics(userid, cmid, $emailLink, false);\n }\n }\n });\n\n /**\n * Fetches and displays analytics data for lesson submissions\n * @param {number} userid - The ID of the user whose analytics to fetch\n * @param {number} cmid - The course module ID\n * @param {jQuery|string} $emailLink - jQuery object of email link or empty string\n * @param {boolean} grade - Whether this is being called from grade view\n */\n function analytics(userid, cmid, $emailLink, grade) {\n\n let args = {id: userid, modulename: \"lesson\", cmid: cmid};\n let methodname = 'cursive_get_lesson_submission_data';\n let com = getData([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(userid));\n } else {\n analyticButtonDiv.append(analyticButton(data.res.effort_ratio, userid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n if (grade) {\n analyticButtonDiv.classList.add('w-100');\n $('#fitem_id_response_editor .felement').prepend(analyticButtonDiv);\n } else {\n $emailLink.closest('tr').find('td:eq(1)').after(analyticsColumn);\n }\n\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.res,\n formattime: myEvents.formatedTime(data.res),\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","showcomment","hasApiKey","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","catch","e","console","error","cmid","M","cfg","contextInstanceId","emailLink","headcolumn","url","URL","location","href","mode","searchParams","get","user","analytics","userid","$emailLink","grade","args","id","modulename","com","methodname","done","json","data","JSON","parse","res","filename","analyticButtonDiv","document","createElement","analyticsColumn","append","effort_ratio","dataset","region","classList","add","prepend","closest","find","after","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","templates","checkDiff","file_id","replyWriting","fail","Str","get_string","strs","each","this","attr","parseInt","URLSearchParams","split","on","preventDefault","link","target","toString"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"append_lesson_grade_table.min.js","sources":["../src/append_lesson_grade_table.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 to append analytics and grade table data for lesson submissions\n * Handles the display of analytics, replay functionality and grade information\n * for lesson submissions in the Moodle gradebook interface\n *\n * @module tiny_cursive/append_lesson_grade_table\n * @copyright 2025 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Replay from './replay';\nimport $ from 'jquery';\nimport {call as getData} from 'core/ajax';\nimport templates from 'core/templates';\nimport AnalyticEvents from './analytic_events';\nimport analyticButton from './analytic_button';\nimport replayButton from './replay_button';\nimport * as Str from 'core/str';\n\nexport const init = (scoreSetting, showcomment, hasApiKey) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n\n var cmid = M.cfg.contextInstanceId;\n var emailLink = $('#page-content div[role=\"main\"] td.lastcol a');\n var headcolumn = $('#region-main div[role=\"main\"] table thead tr');\n let url = new URL(window.location.href);\n let mode = url.searchParams.get('mode');\n let user = url.searchParams.get('user');\n\n Str.get_string('analytics', 'tiny_cursive').then((strs) => {\n headcolumn.each(function() {\n $(this).find('th:eq(1)').after(`${strs}`);\n });\n return true;\n }).catch(e => window.console.error(e));\n\n if (mode && mode === \"grade\") {\n analytics(user, cmid, \"\", true);\n }\n\n emailLink.each(function() {\n let href = $(this).attr('href');\n const $emailLink = $(this);\n let userid = 0;\n if (href) {\n userid = parseInt(new URLSearchParams(href.split('?')[1]).get('userid'));\n if (!userid) {\n $emailLink.closest('tr').find('td:eq(1)').after(\"\"); // For aligning the table column\n } else {\n\n $('#region-main').on('click', 'table tbody tr td.cell.c1 a', function(e) {\n e.preventDefault();\n const link = e.target.href;\n const url = new URL(link);\n url.searchParams.append('user', userid);\n window.location.href = url.toString();\n });\n\n analytics(userid, cmid, $emailLink, false);\n }\n }\n });\n\n /**\n * Fetches and displays analytics data for lesson submissions\n * @param {number} userid - The ID of the user whose analytics to fetch\n * @param {number} cmid - The course module ID\n * @param {jQuery|string} $emailLink - jQuery object of email link or empty string\n * @param {boolean} grade - Whether this is being called from grade view\n */\n function analytics(userid, cmid, $emailLink, grade) {\n\n let args = {id: userid, modulename: \"lesson\", cmid: cmid};\n let methodname = 'cursive_get_lesson_submission_data';\n let com = getData([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(userid));\n } else {\n analyticButtonDiv.append(analyticButton(data.res.effort_ratio, userid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n if (grade) {\n analyticButtonDiv.classList.add('w-100');\n $('#fitem_id_response_editor .felement').prepend(analyticButtonDiv);\n } else {\n $emailLink.closest('tr').find('td:eq(1)').after(analyticsColumn);\n }\n\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.res,\n formattime: myEvents.formatedTime(data.res),\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","showcomment","hasApiKey","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","catch","e","console","error","cmid","M","cfg","contextInstanceId","emailLink","headcolumn","url","URL","location","href","mode","searchParams","get","user","analytics","userid","$emailLink","grade","args","id","modulename","com","methodname","done","json","data","JSON","parse","res","filename","analyticButtonDiv","document","createElement","analyticsColumn","append","effort_ratio","dataset","region","classList","add","prepend","closest","find","after","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","templates","checkDiff","file_id","replyWriting","fail","Str","get_string","strs","each","this","attr","parseInt","URLSearchParams","split","on","preventDefault","link","target","toString"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/append_pdfannotator.min.js b/amd/build/append_pdfannotator.min.js index 5dd8e121..c1578298 100644 --- a/amd/build/append_pdfannotator.min.js +++ b/amd/build/append_pdfannotator.min.js @@ -5,6 +5,6 @@ define("tiny_cursive/append_pdfannotator",["exports","core/ajax","tiny_cursive/a * @module tiny_cursive/append_pdfannotator * @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,_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),_analytic_events=_interopRequireDefault(_analytic_events),_templates=_interopRequireDefault(_templates),_replay=_interopRequireDefault(_replay);_exports.init=(scoreSetting,comments,hasApiKey,userid)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>(document.getElementById("content"+mid).innerHTML=html,!0))).catch((e=>window.console.error(e)));return!1};let container=document.querySelector(".comment-list-container");const overviewTable=document.querySelector('table[id^="mod-pdfannotator-"]');document.addEventListener("click",(function(e){"commentSubmit"===e.target.id&&(localStorage.removeItem("isEditing"),buttonElement=e.target.value,pendingSubmit=!0);"commentCancel"===e.target.id&&(localStorage.removeItem("isEditing"),pendingSubmit=!1)}));const moduleName=document.body.id.split("-")[2];var pendingSubmit=!1,buttonElement="";if(container){new MutationObserver((()=>{var _container$lastChild;null!=container&&null!==(_container$lastChild=container.lastChild)&&void 0!==_container$lastChild&&_container$lastChild.id&&function(id){if("Save"===buttonElement)return void(pendingSubmit=!1);let resourceId=parseInt(null==id?void 0:id.split("_")[1]);if(resourceId&&pendingSubmit){var _M$cfg$userId;pendingSubmit=!1,(async(methodname,args)=>{try{await(0,_ajax.call)([{methodname:methodname,args:args}])[0]}catch(error){throw window.console.error("updating Entries:",error),error}})("cursive_update_pdf_annote_id",{cmid:M.cfg.contextInstanceId,userid:null!==(_M$cfg$userId=M.cfg.userId)&&void 0!==_M$cfg$userId?_M$cfg$userId:0,courseid:M.cfg.courseId,modulename:moduleName,resourceid:resourceId})}}(container.lastChild.id)})).observe(container,{subtree:!0,childList:!0})}if(overviewTable){let newChild=document.createElement("th");newChild.textContent="Analytics",overviewTable.querySelector("thead>tr>th:first-child").insertAdjacentElement("afterend",newChild),function(overviewTable){const rows=overviewTable.querySelectorAll("tbody > tr"),action=new URL(window.location.href).searchParams.get("action");rows.forEach((row=>{var _cols$col,_cols$col2,_cols$col3,_links$link,_links$link2,_userLink;const cols={col1:row.querySelector("td:nth-child(1)"),col2:row.querySelector("td:nth-child(2)"),col3:row.querySelector("td:nth-child(3)")},links={link1:null===(_cols$col=cols.col1)||void 0===_cols$col?void 0:_cols$col.querySelector("a"),link2:null===(_cols$col2=cols.col2)||void 0===_cols$col2?void 0:_cols$col2.querySelector("a"),link3:null===(_cols$col3=cols.col3)||void 0===_cols$col3?void 0:_cols$col3.querySelector("a")},commentId=null!==(_links$link=links.link1)&&void 0!==_links$link&&_links$link.href?new URL(links.link1.href).searchParams.get("commid"):null,cmid=null!==(_links$link2=links.link1)&&void 0!==_links$link2&&_links$link2.href?new URL(links.link1.href).searchParams.get("id"):M.cfg.contextInstanceId;let userId=null,userLink=null;switch(action){case"overviewquestions":userLink=links.link2;break;case"overviewanswers":userLink=links.link3;break;default:userId=userid}if(null!==(_userLink=userLink)&&void 0!==_userLink&&_userLink.href)try{userId=new URL(userLink.href).searchParams.get("id")}catch(e){window.console.warn("Error parsing user URL:",e)}!function(userid,resourceid,cmid,place){let args={id:resourceid,modulename:"pdfannotator",cmid:cmid},methodname="cursive_get_forum_comment_link",com=(0,_ajax.call)([{methodname:methodname,args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.data.filename&&(filepath=data.data.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.data.effort_ratio,resourceid)):analyticButtonDiv.append((0,_replay_button.default)(resourceid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),place.insertAdjacentElement("afterend",analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.data,formattime:myEvents.formatedTime(data.data),page:scoreSetting,userid:resourceid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.data.first_file,data.data.score,scoreSetting);myEvents.createModal(resourceid,context,"",replayInstances,authIcon),myEvents.analytics(resourceid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(resourceid,data.data.file_id,"",replayInstances),myEvents.replyWriting(resourceid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}(userId,commentId,cmid,cols.col1)}))}(overviewTable)}}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_analytic_button=_interopRequireDefault(_analytic_button),_replay_button=_interopRequireDefault(_replay_button),_analytic_events=_interopRequireDefault(_analytic_events),_templates=_interopRequireDefault(_templates),_replay=_interopRequireDefault(_replay);_exports.init=(scoreSetting,comments,hasApiKey,userid)=>{const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new _replay.default("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else _templates.default.render("tiny_cursive/no_submission").then((html=>(document.getElementById("content"+mid).innerHTML=html,!0))).catch((e=>window.console.error(e)));return!1};let container=document.querySelector(".comment-list-container");const overviewTable=document.querySelector('table[id^="mod-pdfannotator-"]');document.addEventListener("click",(function(e){"commentSubmit"===e.target.id&&(localStorage.removeItem("isEditing"),buttonElement=e.target.value,pendingSubmit=!0);"commentCancel"===e.target.id&&(localStorage.removeItem("isEditing"),pendingSubmit=!1)}));const moduleName=document.body.id.split("-")[2];var pendingSubmit=!1,buttonElement="";if(container){new MutationObserver((()=>{var _container$lastChild;null!=container&&null!==(_container$lastChild=container.lastChild)&&void 0!==_container$lastChild&&_container$lastChild.id&&function(id){if("Save"===buttonElement)return void(pendingSubmit=!1);let resourceId=parseInt(null==id?void 0:id.split("_")[1]);if(resourceId&&pendingSubmit){pendingSubmit=!1,(async(methodname,args)=>{try{await(0,_ajax.call)([{methodname:methodname,args:args}])[0]}catch(error){throw window.console.error("updating Entries:",error),error}})("cursive_update_pdf_annote_id",{cmid:M.cfg.contextInstanceId,userid:M.cfg.userId??0,courseid:M.cfg.courseId,modulename:moduleName,resourceid:resourceId})}}(container.lastChild.id)})).observe(container,{subtree:!0,childList:!0})}if(overviewTable){let newChild=document.createElement("th");newChild.textContent="Analytics",overviewTable.querySelector("thead>tr>th:first-child").insertAdjacentElement("afterend",newChild),function(overviewTable){const rows=overviewTable.querySelectorAll("tbody > tr"),action=new URL(window.location.href).searchParams.get("action");rows.forEach((row=>{var _cols$col,_cols$col2,_cols$col3,_links$link,_links$link2,_userLink;const cols={col1:row.querySelector("td:nth-child(1)"),col2:row.querySelector("td:nth-child(2)"),col3:row.querySelector("td:nth-child(3)")},links={link1:null===(_cols$col=cols.col1)||void 0===_cols$col?void 0:_cols$col.querySelector("a"),link2:null===(_cols$col2=cols.col2)||void 0===_cols$col2?void 0:_cols$col2.querySelector("a"),link3:null===(_cols$col3=cols.col3)||void 0===_cols$col3?void 0:_cols$col3.querySelector("a")},commentId=null!==(_links$link=links.link1)&&void 0!==_links$link&&_links$link.href?new URL(links.link1.href).searchParams.get("commid"):null,cmid=null!==(_links$link2=links.link1)&&void 0!==_links$link2&&_links$link2.href?new URL(links.link1.href).searchParams.get("id"):M.cfg.contextInstanceId;let userId=null,userLink=null;switch(action){case"overviewquestions":userLink=links.link2;break;case"overviewanswers":userLink=links.link3;break;default:userId=userid}if(null!==(_userLink=userLink)&&void 0!==_userLink&&_userLink.href)try{userId=new URL(userLink.href).searchParams.get("id")}catch(e){window.console.warn("Error parsing user URL:",e)}!function(userid,resourceid,cmid,place){let args={id:resourceid,modulename:"pdfannotator",cmid:cmid},methodname="cursive_get_forum_comment_link",com=(0,_ajax.call)([{methodname:methodname,args:args}]);com[0].done((function(json){var data=JSON.parse(json),filepath="";data.data.filename&&(filepath=data.data.filename);let analyticButtonDiv=document.createElement("div"),analyticsColumn=document.createElement("td");hasApiKey?analyticButtonDiv.append((0,_analytic_button.default)(data.data.effort_ratio,resourceid)):analyticButtonDiv.append((0,_replay_button.default)(resourceid)),analyticButtonDiv.dataset.region="analytic-div"+userid,analyticsColumn.append(analyticButtonDiv),place.insertAdjacentElement("afterend",analyticsColumn);let myEvents=new _analytic_events.default;var context={tabledata:data.data,formattime:myEvents.formatedTime(data.data),page:scoreSetting,userid:resourceid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.data.first_file,data.data.score,scoreSetting);myEvents.createModal(resourceid,context,"",replayInstances,authIcon),myEvents.analytics(resourceid,_templates.default,context,"",replayInstances,authIcon),myEvents.checkDiff(resourceid,data.data.file_id,"",replayInstances),myEvents.replyWriting(resourceid,filepath,"",replayInstances)})),com[0].fail((error=>{window.console.error("Error getting cursive config:",error)}))}(userId,commentId,cmid,cols.col1)}))}(overviewTable)}}})); //# sourceMappingURL=append_pdfannotator.min.js.map \ No newline at end of file diff --git a/amd/build/append_pdfannotator.min.js.map b/amd/build/append_pdfannotator.min.js.map index 301b50c1..efbbc64c 100644 --- a/amd/build/append_pdfannotator.min.js.map +++ b/amd/build/append_pdfannotator.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_pdfannotator.min.js","sources":["../src/append_pdfannotator.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 PDF annotator functionality,\n *\n * @module tiny_cursive/append_pdfannotator\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call} from 'core/ajax';\nimport analyticButton from 'tiny_cursive/analytic_button';\nimport replayButton from 'tiny_cursive/replay_button';\nimport AnalyticEvents from 'tiny_cursive/analytic_events';\nimport templates from 'core/templates';\nimport Replay from 'tiny_cursive/replay';\nexport const init = (scoreSetting, comments, hasApiKey, userid) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n document.getElementById('content' + mid).innerHTML = html;\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n };\n\n let container = document.querySelector('.comment-list-container');\n const overviewTable = document.querySelector('table[id^=\"mod-pdfannotator-\"]');\n\n document.addEventListener('click', handleSubmit);\n const moduleName = document.body.id.split('-')[2];\n var pendingSubmit = false;\n var buttonElement = \"\";\n\n if (container) {\n const observer = new MutationObserver(() => {\n if (container?.lastChild?.id) {\n extractResourceId(container.lastChild.id);\n }\n });\n\n observer.observe(container, {\n subtree: true,\n childList: true\n });\n }\n\n if (overviewTable) {\n let newChild = document.createElement('th');\n newChild.textContent = 'Analytics';\n let header = overviewTable.querySelector('thead>tr>th:first-child');\n header.insertAdjacentElement('afterend', newChild);\n setReplayButton(overviewTable);\n }\n\n /**\n * Sets up replay buttons and analytics for each row in the overview table\n * @param {HTMLTableElement} overviewTable - The table element containing the overview data\n * @description This function:\n * 1. Gets all rows from the table\n * 2. For each row:\n * - Extracts comment ID and user ID from relevant links\n * - Adds analytics column with replay/analytics buttons\n * - Sets up cursive analytics functionality\n */\n function setReplayButton(overviewTable) {\n const rows = overviewTable.querySelectorAll('tbody > tr');\n const action = new URL(window.location.href).searchParams.get('action');\n\n rows.forEach(row => {\n const cols = {\n col1: row.querySelector('td:nth-child(1)'),\n col2: row.querySelector('td:nth-child(2)'),\n col3: row.querySelector('td:nth-child(3)')\n };\n\n const links = {\n link1: cols.col1?.querySelector('a'),\n link2: cols.col2?.querySelector('a'),\n link3: cols.col3?.querySelector('a')\n };\n\n // Extract comment ID safely\n const commentId = links.link1?.href ?\n new URL(links.link1.href).searchParams.get('commid') : null;\n const cmid = links.link1?.href ?\n new URL(links.link1.href).searchParams.get('id') : M.cfg.contextInstanceId;\n // Extract user ID based on action\n let userId = null;\n let userLink = null;\n\n switch (action) {\n case 'overviewquestions':\n userLink = links.link2;\n break;\n case 'overviewanswers':\n userLink = links.link3;\n break;\n default:\n userId = userid;\n }\n\n if (userLink?.href) {\n try {\n userId = new URL(userLink.href).searchParams.get('id');\n } catch (e) {\n window.console.warn('Error parsing user URL:', e);\n }\n }\n\n getCursiveAnalytics(userId, commentId, cmid, cols.col1);\n });\n }\n\n /**\n * Handles the submission and cancellation of comments\n * @param {Event} e - The click event object\n * @description When comment is submitted or cancelled:\n * - Removes 'isEditing' flag from localStorage\n * - Sets pendingSubmit flag appropriately (true for submit, false for cancel)\n */\n function handleSubmit(e) {\n if (e.target.id === 'commentSubmit') {\n localStorage.removeItem('isEditing');\n buttonElement = e.target.value;\n pendingSubmit = true;\n }\n if (e.target.id === 'commentCancel') {\n localStorage.removeItem('isEditing');\n pendingSubmit = false;\n }\n }\n\n const updateEntries = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n return response;\n } catch (error) {\n window.console.error('updating Entries:', error);\n throw error;\n }\n };\n\n /**\n * Extracts the resource ID from a comment ID and updates entries if submission is pending\n * @param {string} id - The ID string to extract resource ID from, expected format: 'prefix_number'\n * @description This function:\n * 1. Parses the resource ID from the given ID string\n * 2. If resource ID exists and there's a pending submission:\n * - Resets the pending submission flag\n * - Constructs arguments with context info\n * - Calls updateEntries to process the PDF annotation\n */\n function extractResourceId(id) {\n\n // Prevent updating ID while editing a existing entry.\n if (buttonElement === 'Save') {\n\n pendingSubmit = false;\n return;\n }\n\n let resourceId = parseInt(id?.split('_')[1]);\n if (resourceId && pendingSubmit) {\n pendingSubmit = false;\n let args = {\n cmid: M.cfg.contextInstanceId,\n userid: M.cfg.userId ?? 0,\n courseid: M.cfg.courseId,\n modulename: moduleName,\n resourceid: resourceId\n };\n updateEntries('cursive_update_pdf_annote_id', args);\n }\n }\n\n /**\n * Retrieves and displays cursive analytics for a given resource\n * @param {number} userid - The ID of the user\n * @param {number} resourceid - The ID of the resource to get analytics for\n * @param {number} cmid - The course module ID\n * @param {HTMLElement} place - The DOM element where analytics should be placed\n * @description This function:\n * 1. Makes an AJAX call to get forum comment data\n * 2. Creates and inserts analytics/replay buttons\n * 3. Sets up analytics events and modal functionality\n * 4. Handles both API key and non-API key scenarios\n */\n function getCursiveAnalytics(userid, resourceid, cmid, place) {\n let args = {id: resourceid, modulename: \"pdfannotator\", cmid: cmid};\n let methodname = 'cursive_get_forum_comment_link';\n let com = call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n analyticButtonDiv.append(replayButton(resourceid));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, resourceid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n place.insertAdjacentElement('afterend', analyticsColumn);\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: resourceid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(resourceid, context, '', replayInstances, authIcon);\n myEvents.analytics(resourceid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(resourceid, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(resourceid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","comments","hasApiKey","userid","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","document","getElementById","innerHTML","catch","e","console","error","container","querySelector","overviewTable","addEventListener","target","id","localStorage","removeItem","buttonElement","value","pendingSubmit","moduleName","body","split","MutationObserver","lastChild","_container$lastChild","resourceId","parseInt","async","methodname","args","updateEntries","cmid","M","cfg","contextInstanceId","userId","courseid","courseId","modulename","resourceid","extractResourceId","observe","subtree","childList","newChild","createElement","textContent","insertAdjacentElement","rows","querySelectorAll","action","URL","location","href","searchParams","get","forEach","row","cols","col1","col2","col3","links","link1","_cols$col","link2","_cols$col2","link3","_cols$col3","commentId","userLink","_userLink","warn","place","com","done","json","data","JSON","parse","filename","analyticButtonDiv","analyticsColumn","append","effort_ratio","dataset","region","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","templates","checkDiff","file_id","replyWriting","fail","getCursiveAnalytics","setReplayButton"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"append_pdfannotator.min.js","sources":["../src/append_pdfannotator.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 PDF annotator functionality,\n *\n * @module tiny_cursive/append_pdfannotator\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call} from 'core/ajax';\nimport analyticButton from 'tiny_cursive/analytic_button';\nimport replayButton from 'tiny_cursive/replay_button';\nimport AnalyticEvents from 'tiny_cursive/analytic_events';\nimport templates from 'core/templates';\nimport Replay from 'tiny_cursive/replay';\nexport const init = (scoreSetting, comments, hasApiKey, userid) => {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n document.getElementById('content' + mid).innerHTML = html;\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n };\n\n let container = document.querySelector('.comment-list-container');\n const overviewTable = document.querySelector('table[id^=\"mod-pdfannotator-\"]');\n\n document.addEventListener('click', handleSubmit);\n const moduleName = document.body.id.split('-')[2];\n var pendingSubmit = false;\n var buttonElement = \"\";\n\n if (container) {\n const observer = new MutationObserver(() => {\n if (container?.lastChild?.id) {\n extractResourceId(container.lastChild.id);\n }\n });\n\n observer.observe(container, {\n subtree: true,\n childList: true\n });\n }\n\n if (overviewTable) {\n let newChild = document.createElement('th');\n newChild.textContent = 'Analytics';\n let header = overviewTable.querySelector('thead>tr>th:first-child');\n header.insertAdjacentElement('afterend', newChild);\n setReplayButton(overviewTable);\n }\n\n /**\n * Sets up replay buttons and analytics for each row in the overview table\n * @param {HTMLTableElement} overviewTable - The table element containing the overview data\n * @description This function:\n * 1. Gets all rows from the table\n * 2. For each row:\n * - Extracts comment ID and user ID from relevant links\n * - Adds analytics column with replay/analytics buttons\n * - Sets up cursive analytics functionality\n */\n function setReplayButton(overviewTable) {\n const rows = overviewTable.querySelectorAll('tbody > tr');\n const action = new URL(window.location.href).searchParams.get('action');\n\n rows.forEach(row => {\n const cols = {\n col1: row.querySelector('td:nth-child(1)'),\n col2: row.querySelector('td:nth-child(2)'),\n col3: row.querySelector('td:nth-child(3)')\n };\n\n const links = {\n link1: cols.col1?.querySelector('a'),\n link2: cols.col2?.querySelector('a'),\n link3: cols.col3?.querySelector('a')\n };\n\n // Extract comment ID safely\n const commentId = links.link1?.href ?\n new URL(links.link1.href).searchParams.get('commid') : null;\n const cmid = links.link1?.href ?\n new URL(links.link1.href).searchParams.get('id') : M.cfg.contextInstanceId;\n // Extract user ID based on action\n let userId = null;\n let userLink = null;\n\n switch (action) {\n case 'overviewquestions':\n userLink = links.link2;\n break;\n case 'overviewanswers':\n userLink = links.link3;\n break;\n default:\n userId = userid;\n }\n\n if (userLink?.href) {\n try {\n userId = new URL(userLink.href).searchParams.get('id');\n } catch (e) {\n window.console.warn('Error parsing user URL:', e);\n }\n }\n\n getCursiveAnalytics(userId, commentId, cmid, cols.col1);\n });\n }\n\n /**\n * Handles the submission and cancellation of comments\n * @param {Event} e - The click event object\n * @description When comment is submitted or cancelled:\n * - Removes 'isEditing' flag from localStorage\n * - Sets pendingSubmit flag appropriately (true for submit, false for cancel)\n */\n function handleSubmit(e) {\n if (e.target.id === 'commentSubmit') {\n localStorage.removeItem('isEditing');\n buttonElement = e.target.value;\n pendingSubmit = true;\n }\n if (e.target.id === 'commentCancel') {\n localStorage.removeItem('isEditing');\n pendingSubmit = false;\n }\n }\n\n const updateEntries = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n return response;\n } catch (error) {\n window.console.error('updating Entries:', error);\n throw error;\n }\n };\n\n /**\n * Extracts the resource ID from a comment ID and updates entries if submission is pending\n * @param {string} id - The ID string to extract resource ID from, expected format: 'prefix_number'\n * @description This function:\n * 1. Parses the resource ID from the given ID string\n * 2. If resource ID exists and there's a pending submission:\n * - Resets the pending submission flag\n * - Constructs arguments with context info\n * - Calls updateEntries to process the PDF annotation\n */\n function extractResourceId(id) {\n\n // Prevent updating ID while editing a existing entry.\n if (buttonElement === 'Save') {\n\n pendingSubmit = false;\n return;\n }\n\n let resourceId = parseInt(id?.split('_')[1]);\n if (resourceId && pendingSubmit) {\n pendingSubmit = false;\n let args = {\n cmid: M.cfg.contextInstanceId,\n userid: M.cfg.userId ?? 0,\n courseid: M.cfg.courseId,\n modulename: moduleName,\n resourceid: resourceId\n };\n updateEntries('cursive_update_pdf_annote_id', args);\n }\n }\n\n /**\n * Retrieves and displays cursive analytics for a given resource\n * @param {number} userid - The ID of the user\n * @param {number} resourceid - The ID of the resource to get analytics for\n * @param {number} cmid - The course module ID\n * @param {HTMLElement} place - The DOM element where analytics should be placed\n * @description This function:\n * 1. Makes an AJAX call to get forum comment data\n * 2. Creates and inserts analytics/replay buttons\n * 3. Sets up analytics events and modal functionality\n * 4. Handles both API key and non-API key scenarios\n */\n function getCursiveAnalytics(userid, resourceid, cmid, place) {\n let args = {id: resourceid, modulename: \"pdfannotator\", cmid: cmid};\n let methodname = 'cursive_get_forum_comment_link';\n let com = call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n let analyticsColumn = document.createElement('td');\n\n if (!hasApiKey) {\n analyticButtonDiv.append(replayButton(resourceid));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, resourceid));\n }\n\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\n analyticsColumn.append(analyticButtonDiv);\n place.insertAdjacentElement('afterend', analyticsColumn);\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: resourceid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(resourceid, context, '', replayInstances, authIcon);\n myEvents.analytics(resourceid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(resourceid, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(resourceid, filepath, '', replayInstances);\n\n });\n com[0].fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n }\n};"],"names":["scoreSetting","comments","hasApiKey","userid","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","document","getElementById","innerHTML","catch","e","console","error","container","querySelector","overviewTable","addEventListener","target","id","localStorage","removeItem","buttonElement","value","pendingSubmit","moduleName","body","split","MutationObserver","lastChild","_container$lastChild","resourceId","parseInt","async","methodname","args","updateEntries","cmid","M","cfg","contextInstanceId","userId","courseid","courseId","modulename","resourceid","extractResourceId","observe","subtree","childList","newChild","createElement","textContent","insertAdjacentElement","rows","querySelectorAll","action","URL","location","href","searchParams","get","forEach","row","cols","col1","col2","col3","links","link1","_cols$col","link2","_cols$col2","link3","_cols$col3","commentId","userLink","_userLink","warn","place","com","done","json","data","JSON","parse","filename","analyticButtonDiv","analyticsColumn","append","effort_ratio","dataset","region","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","templates","checkDiff","file_id","replyWriting","fail","getCursiveAnalytics","setReplayButton"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/append_submissions_table.min.js b/amd/build/append_submissions_table.min.js index 50a9c343..6fa7b33d 100644 --- a/amd/build/append_submissions_table.min.js +++ b/amd/build/append_submissions_table.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/append_submissions_table",["jquery","core/ajax","core/str","core/templates","./replay","./analytic_button","./replay_button","./analytic_events","core/str"],(function($,AJAX,str,templates,Replay,analyticButton,replayButton,AnalyticEvents,Str){const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new Replay("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else templates.render("tiny_cursive/no_submission").then((html=>($("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var usersTable={init:function(scoreSetting,showcomment,hasApiKey){str.get_strings([{key:"confidence_threshold",component:"tiny_cursive"}]).done((function(){usersTable.appendTable(scoreSetting,hasApiKey)}))},appendTable:function(scoreSetting,hasApiKey){let subUrl=window.location.href,parm=new URL(subUrl),hTr=$("thead").find("tr").get()[0];Str.get_string("analytics","tiny_cursive").then((analyticString=>($(hTr).find("th").eq(3).after(''+analyticString+'
'),$("tbody").find("tr").get().forEach((function(tr){var _$$find,_$$find$get$;let tdUser=$(tr).find("td").get()[0],userid=null===(_$$find=$(tdUser).find("input[type='checkbox']"))||void 0===_$$find||null===(_$$find$get$=_$$find.get()[0])||void 0===_$$find$get$?void 0:_$$find$get$.value,cmid=parm.searchParams.get("id"),args={id:userid,modulename:"assign",cmid:cmid},com=AJAX.call([{methodname:"cursive_user_list_submission_stats",args:args}]);try{com[0].done((function(json){var data=JSON.parse(json),filepath="";data.res.filename&&(filepath=data.res.filename);const tableCell=document.createElement("td");hasApiKey?tableCell.appendChild(analyticButton(data.res.effort_ratio,userid)):$(tableCell).html(replayButton(userid)),$(tr).find("td").eq(3).after(tableCell);let textContent=document.querySelector(".page-header-headings h1").textContent,myEvents=new AnalyticEvents;var context={tabledata:data.res,formattime:myEvents.formatedTime(data.res),moduletitle:textContent,page:scoreSetting,userid:userid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.res.first_file,data.res.score,scoreSetting);myEvents.createModal(userid,context,"",replayInstances,authIcon),myEvents.analytics(userid,templates,context,"",replayInstances,authIcon),myEvents.checkDiff(userid,data.res.file_id,"",replayInstances),myEvents.replyWriting(userid,filepath,"",replayInstances)})).fail((function(error){window.console.error("AJAX request failed:",error)}))}catch(error){window.console.error("Error processing data:",error)}return com.usercomment})),!0))).catch((error=>{window.console.error("Failed to get analytics string:",error)}))}};return usersTable})); +define("tiny_cursive/append_submissions_table",["jquery","core/ajax","core/str","core/templates","./replay","./analytic_button","./replay_button","./analytic_events","core/str"],(function($,AJAX,str,templates,Replay,analyticButton,replayButton,AnalyticEvents,Str){const replayInstances={};window.video_playback=function(mid,filepath){if(""!==filepath){const replay=new Replay("content"+mid,filepath,10,!1,"player_"+mid);replayInstances[mid]=replay}else templates.render("tiny_cursive/no_submission").then((html=>($("#content"+mid).html(html),!0))).catch((e=>window.console.error(e)));return!1};var usersTable={init:function(scoreSetting,showcomment,hasApiKey){str.get_strings([{key:"confidence_threshold",component:"tiny_cursive"}]).done((function(){usersTable.appendTable(scoreSetting,hasApiKey)}))},appendTable:function(scoreSetting,hasApiKey){let subUrl=window.location.href,parm=new URL(subUrl),hTr=$("table#submissions thead").find("tr").get()[0];Str.get_string("analytics","tiny_cursive").then((analyticString=>($(hTr).find("th").eq(3).after(''+analyticString+'
'),$("table#submissions tbody").find("tr").get().forEach((function(tr){var _$$find,_$$find$get$;let tdUser=$(tr).find("td").get()[0],userid=null===(_$$find=$(tdUser).find("input[type='checkbox']"))||void 0===_$$find||null===(_$$find$get$=_$$find.get()[0])||void 0===_$$find$get$?void 0:_$$find$get$.value,cmid=parm.searchParams.get("id"),args={id:userid,modulename:"assign",cmid:cmid},com=AJAX.call([{methodname:"cursive_user_list_submission_stats",args:args}]);try{com[0].done((function(json){var data=JSON.parse(json),filepath="";data.res.filename&&(filepath=data.res.filename);const tableCell=document.createElement("td");hasApiKey?tableCell.appendChild(analyticButton(data.res.effort_ratio,userid)):$(tableCell).html(replayButton(userid)),$(tr).find("td").eq(3).after(tableCell);let textContent=document.querySelector(".page-header-headings h1").textContent,myEvents=new AnalyticEvents;var context={tabledata:data.res,formattime:myEvents.formatedTime(data.res),moduletitle:textContent,page:scoreSetting,userid:userid,apikey:hasApiKey};let authIcon=myEvents.authorshipStatus(data.res.first_file,data.res.score,scoreSetting);myEvents.createModal(userid,context,"",replayInstances,authIcon),myEvents.analytics(userid,templates,context,"",replayInstances,authIcon),myEvents.checkDiff(userid,data.res.file_id,"",replayInstances),myEvents.replyWriting(userid,filepath,"",replayInstances)})).fail((function(error){window.console.error("AJAX request failed:",error)}))}catch(error){window.console.error("Error processing data:",error)}return com.usercomment})),!0))).catch((error=>{window.console.error("Failed to get analytics string:",error)}))}};return usersTable})); //# sourceMappingURL=append_submissions_table.min.js.map \ No newline at end of file diff --git a/amd/build/append_submissions_table.min.js.map b/amd/build/append_submissions_table.min.js.map index 0ee320af..cbdf1a12 100644 --- a/amd/build/append_submissions_table.min.js.map +++ b/amd/build/append_submissions_table.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_submissions_table.min.js","sources":["../src/append_submissions_table.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/append_submissions_table\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\n \"jquery\",\n \"core/ajax\",\n \"core/str\",\n \"core/templates\",\n \"./replay\",\n './analytic_button',\n './replay_button',\n './analytic_events',\n 'core/str'], function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n replayButton,\n AnalyticEvents,\n Str\n) {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n var usersTable = {\n init: function(scoreSetting, showcomment, hasApiKey) {\n str\n .get_strings([\n {key: \"confidence_threshold\", component: \"tiny_cursive\"},\n ]).done(function() {\n usersTable.appendTable(scoreSetting, hasApiKey);\n });\n },\n appendTable: function(scoreSetting, hasApiKey) {\n let subUrl = window.location.href;\n let parm = new URL(subUrl);\n let hTr = $('thead').find('tr').get()[0];\n\n Str.get_string('analytics', 'tiny_cursive')\n .then(analyticString => {\n $(hTr).find('th').eq(3).after(''\n + analyticString + '
' +\n '
');\n $('tbody').find(\"tr\").get().forEach(function(tr) {\n let tdUser = $(tr).find(\"td\").get()[0];\n let userid = $(tdUser).find(\"input[type='checkbox']\")?.get()[0]?.value;\n let cmid = parm.searchParams.get('id');\n // Create the table cell element and append the anchor.\n\n let args = {id: userid, modulename: \"assign\", cmid: cmid};\n let methodname = 'cursive_user_list_submission_stats';\n let com = AJAX.call([{methodname, args}]);\n try {\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n const tableCell = document.createElement('td');\n\n if (!hasApiKey) {\n $(tableCell).html(replayButton(userid));\n } else {\n tableCell.appendChild(analyticButton(data.res.effort_ratio, userid));\n }\n $(tr).find('td').eq(3).after(tableCell);\n\n // Get Module Name from element.\n let element = document.querySelector('.page-header-headings h1');\n // Selects the h1 element within the .page-header-headings class\n let textContent = element.textContent; // Extracts the text content from the h1 element\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.res,\n formattime: myEvents.formatedTime(data.res),\n moduletitle: textContent,\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n }).fail(function(error) {\n window.console.error('AJAX request failed:', error);\n });\n } catch (error) {\n window.console.error('Error processing data:', error);\n }\n return com.usercomment;\n });\n return true;\n })\n .catch(error => {\n window.console.error('Failed to get analytics string:', error);\n });\n }\n };\n\n return usersTable;\n});"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","replayButton","AnalyticEvents","Str","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","get_strings","key","component","done","appendTable","subUrl","location","href","parm","URL","hTr","find","get","get_string","analyticString","eq","after","forEach","tr","tdUser","userid","_$$find","_$$find$get$","value","cmid","searchParams","args","id","modulename","com","call","methodname","json","data","JSON","parse","res","filename","tableCell","document","createElement","appendChild","effort_ratio","textContent","querySelector","myEvents","context","tabledata","formattime","formatedTime","moduletitle","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","fail","usercomment"],"mappings":"AAsBAA,+CAAO,CACH,SACA,YACA,WACA,iBACA,WACA,oBACA,kBACA,oBACA,aAAa,SACbC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,aACAC,eACAC,WAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAASC,IAAKC,aAEjB,KAAbA,SAAiB,OACXC,OAAS,IAAIV,OACf,UAAYQ,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,YAEvBX,UAAUY,OAAO,8BAA8BC,MAAKC,OAChDjB,EAAE,WAAaY,KAAKK,KAAKA,OAClB,KACRC,OAAMC,GAAKT,OAAOU,QAAQC,MAAMF,YAEhC,OAIPG,WAAa,CACbC,KAAM,SAASC,aAAcC,YAAaC,WACtCxB,IACKyB,YAAY,CACT,CAACC,IAAK,uBAAwBC,UAAW,kBAC1CC,MAAK,WACRR,WAAWS,YAAYP,aAAcE,eAG7CK,YAAa,SAASP,aAAcE,eAC5BM,OAAStB,OAAOuB,SAASC,KACzBC,KAAO,IAAIC,IAAIJ,QACfK,IAAMrC,EAAE,SAASsC,KAAK,MAAMC,MAAM,GAEtC/B,IAAIgC,WAAW,YAAa,gBACvBxB,MAAKyB,iBACFzC,EAAEqC,KAAKC,KAAK,MAAMI,GAAG,GAAGC,MAAM,qCACxBF,eADwB,+FAG9BzC,EAAE,SAASsC,KAAK,MAAMC,MAAMK,SAAQ,SAASC,iCACrCC,OAAS9C,EAAE6C,IAAIP,KAAK,MAAMC,MAAM,GAChCQ,uBAAS/C,EAAE8C,QAAQR,KAAK,mEAAfU,QAA0CT,MAAM,kCAAhDU,aAAoDC,MAC7DC,KAAOhB,KAAKiB,aAAab,IAAI,MAG7Bc,KAAO,CAACC,GAAIP,OAAQQ,WAAY,SAAUJ,KAAMA,MAEhDK,IAAMvD,KAAKwD,KAAK,CAAC,CAACC,WADL,qCACiBL,KAAAA,YAE9BG,IAAI,GAAG1B,MAAK,SAAS6B,UACbC,KAAOC,KAAKC,MAAMH,MAClB9C,SAAW,GACX+C,KAAKG,IAAIC,WACTnD,SAAW+C,KAAKG,IAAIC,gBAGlBC,UAAYC,SAASC,cAAc,MAEpCzC,UAGDuC,UAAUG,YAAY/D,eAAeuD,KAAKG,IAAIM,aAActB,SAF5D/C,EAAEiE,WAAWhD,KAAKX,aAAayC,SAInC/C,EAAE6C,IAAIP,KAAK,MAAMI,GAAG,GAAGC,MAAMsB,eAKzBK,YAFUJ,SAASK,cAAc,4BAEXD,YAEtBE,SAAW,IAAIjE,mBACfkE,QAAU,CACVC,UAAWd,KAAKG,IAChBY,WAAYH,SAASI,aAAahB,KAAKG,KACvCc,YAAaP,YACbQ,KAAMtD,aACNuB,OAAQA,OACRgC,OAAQrD,eAGRsD,SAAWR,SAASS,iBAAiBrB,KAAKG,IAAImB,WAAYtB,KAAKG,IAAIoB,MAAO3D,cAC9EgD,SAASY,YAAYrC,OAAQ0B,QAAS,GAAIhE,gBAAiBuE,UAC3DR,SAASa,UAAUtC,OAAQ5C,UAAWsE,QAAS,GAAIhE,gBAAiBuE,UACpER,SAASc,UAAUvC,OAAQa,KAAKG,IAAIwB,QAAS,GAAI9E,iBACjD+D,SAASgB,aAAazC,OAAQlC,SAAU,GAAIJ,oBAE7CgF,MAAK,SAASpE,OACbX,OAAOU,QAAQC,MAAM,uBAAwBA,UAEnD,MAAOA,OACLX,OAAOU,QAAQC,MAAM,yBAA0BA,cAE5CmC,IAAIkC,gBAER,KAEVxE,OAAMG,QACHX,OAAOU,QAAQC,MAAM,kCAAmCA,mBAKjEC"} \ No newline at end of file +{"version":3,"file":"append_submissions_table.min.js","sources":["../src/append_submissions_table.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/append_submissions_table\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\n \"jquery\",\n \"core/ajax\",\n \"core/str\",\n \"core/templates\",\n \"./replay\",\n './analytic_button',\n './replay_button',\n './analytic_events',\n 'core/str'], function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n replayButton,\n AnalyticEvents,\n Str\n) {\n const replayInstances = {};\n // eslint-disable-next-line camelcase\n window.video_playback = function(mid, filepath) {\n\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid\n );\n replayInstances[mid] = replay;\n } else {\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n return true;\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n var usersTable = {\n init: function(scoreSetting, showcomment, hasApiKey) {\n str\n .get_strings([\n {key: \"confidence_threshold\", component: \"tiny_cursive\"},\n ]).done(function() {\n usersTable.appendTable(scoreSetting, hasApiKey);\n });\n },\n appendTable: function(scoreSetting, hasApiKey) {\n let subUrl = window.location.href;\n let parm = new URL(subUrl);\n let hTr = $('table#submissions thead').find('tr').get()[0];\n\n Str.get_string('analytics', 'tiny_cursive')\n .then(analyticString => {\n $(hTr).find('th').eq(3).after(''\n + analyticString + '
' +\n '
');\n $('table#submissions tbody').find(\"tr\").get().forEach(function(tr) {\n let tdUser = $(tr).find(\"td\").get()[0];\n let userid = $(tdUser).find(\"input[type='checkbox']\")?.get()[0]?.value;\n let cmid = parm.searchParams.get('id');\n // Create the table cell element and append the anchor.\n\n let args = {id: userid, modulename: \"assign\", cmid: cmid};\n let methodname = 'cursive_user_list_submission_stats';\n let com = AJAX.call([{methodname, args}]);\n try {\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n const tableCell = document.createElement('td');\n\n if (!hasApiKey) {\n $(tableCell).html(replayButton(userid));\n } else {\n tableCell.appendChild(analyticButton(data.res.effort_ratio, userid));\n }\n $(tr).find('td').eq(3).after(tableCell);\n\n // Get Module Name from element.\n let element = document.querySelector('.page-header-headings h1');\n // Selects the h1 element within the .page-header-headings class\n let textContent = element.textContent; // Extracts the text content from the h1 element\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.res,\n formattime: myEvents.formatedTime(data.res),\n moduletitle: textContent,\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n }).fail(function(error) {\n window.console.error('AJAX request failed:', error);\n });\n } catch (error) {\n window.console.error('Error processing data:', error);\n }\n return com.usercomment;\n });\n return true;\n })\n .catch(error => {\n window.console.error('Failed to get analytics string:', error);\n });\n }\n };\n\n return usersTable;\n});"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","replayButton","AnalyticEvents","Str","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","get_strings","key","component","done","appendTable","subUrl","location","href","parm","URL","hTr","find","get","get_string","analyticString","eq","after","forEach","tr","tdUser","userid","_$$find","_$$find$get$","value","cmid","searchParams","args","id","modulename","com","call","methodname","json","data","JSON","parse","res","filename","tableCell","document","createElement","appendChild","effort_ratio","textContent","querySelector","myEvents","context","tabledata","formattime","formatedTime","moduletitle","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","fail","usercomment"],"mappings":"AAsBAA,+CAAO,CACH,SACA,YACA,WACA,iBACA,WACA,oBACA,kBACA,oBACA,aAAa,SACbC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,aACAC,eACAC,WAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAASC,IAAKC,aAEjB,KAAbA,SAAiB,OACXC,OAAS,IAAIV,OACf,UAAYQ,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,YAEvBX,UAAUY,OAAO,8BAA8BC,MAAKC,OAChDjB,EAAE,WAAaY,KAAKK,KAAKA,OAClB,KACRC,OAAMC,GAAKT,OAAOU,QAAQC,MAAMF,YAEhC,OAIPG,WAAa,CACbC,KAAM,SAASC,aAAcC,YAAaC,WACtCxB,IACKyB,YAAY,CACT,CAACC,IAAK,uBAAwBC,UAAW,kBAC1CC,MAAK,WACRR,WAAWS,YAAYP,aAAcE,eAG7CK,YAAa,SAASP,aAAcE,eAC5BM,OAAStB,OAAOuB,SAASC,KACzBC,KAAO,IAAIC,IAAIJ,QACfK,IAAMrC,EAAE,2BAA2BsC,KAAK,MAAMC,MAAM,GAExD/B,IAAIgC,WAAW,YAAa,gBACvBxB,MAAKyB,iBACFzC,EAAEqC,KAAKC,KAAK,MAAMI,GAAG,GAAGC,MAAM,qCACxBF,eADwB,+FAG9BzC,EAAE,2BAA2BsC,KAAK,MAAMC,MAAMK,SAAQ,SAASC,iCACvDC,OAAS9C,EAAE6C,IAAIP,KAAK,MAAMC,MAAM,GAChCQ,uBAAS/C,EAAE8C,QAAQR,KAAK,mEAAfU,QAA0CT,MAAM,kCAAhDU,aAAoDC,MAC7DC,KAAOhB,KAAKiB,aAAab,IAAI,MAG7Bc,KAAO,CAACC,GAAIP,OAAQQ,WAAY,SAAUJ,KAAMA,MAEhDK,IAAMvD,KAAKwD,KAAK,CAAC,CAACC,WADL,qCACiBL,KAAAA,YAE9BG,IAAI,GAAG1B,MAAK,SAAS6B,UACbC,KAAOC,KAAKC,MAAMH,MAClB9C,SAAW,GACX+C,KAAKG,IAAIC,WACTnD,SAAW+C,KAAKG,IAAIC,gBAGlBC,UAAYC,SAASC,cAAc,MAEpCzC,UAGDuC,UAAUG,YAAY/D,eAAeuD,KAAKG,IAAIM,aAActB,SAF5D/C,EAAEiE,WAAWhD,KAAKX,aAAayC,SAInC/C,EAAE6C,IAAIP,KAAK,MAAMI,GAAG,GAAGC,MAAMsB,eAKzBK,YAFUJ,SAASK,cAAc,4BAEXD,YAEtBE,SAAW,IAAIjE,mBACfkE,QAAU,CACVC,UAAWd,KAAKG,IAChBY,WAAYH,SAASI,aAAahB,KAAKG,KACvCc,YAAaP,YACbQ,KAAMtD,aACNuB,OAAQA,OACRgC,OAAQrD,eAGRsD,SAAWR,SAASS,iBAAiBrB,KAAKG,IAAImB,WAAYtB,KAAKG,IAAIoB,MAAO3D,cAC9EgD,SAASY,YAAYrC,OAAQ0B,QAAS,GAAIhE,gBAAiBuE,UAC3DR,SAASa,UAAUtC,OAAQ5C,UAAWsE,QAAS,GAAIhE,gBAAiBuE,UACpER,SAASc,UAAUvC,OAAQa,KAAKG,IAAIwB,QAAS,GAAI9E,iBACjD+D,SAASgB,aAAazC,OAAQlC,SAAU,GAAIJ,oBAE7CgF,MAAK,SAASpE,OACbX,OAAOU,QAAQC,MAAM,uBAAwBA,UAEnD,MAAOA,OACLX,OAAOU,QAAQC,MAAM,yBAA0BA,cAE5CmC,IAAIkC,gBAER,KAEVxE,OAAMG,QACHX,OAAOU,QAAQC,MAAM,kCAAmCA,mBAKjEC"} \ No newline at end of file diff --git a/amd/build/autosaver.min.js b/amd/build/autosaver.min.js index aa23ba60..5c4badae 100644 --- a/amd/build/autosaver.min.js +++ b/amd/build/autosaver.min.js @@ -1,7 +1,3 @@ -<<<<<<< HEAD -define("tiny_cursive/autosaver",["exports","core/ajax","core/modal_factory","core/str","core/modal_events","jquery","tiny_cursive/common","tiny_cursive/cursive_autosave","tiny_cursive/document_view"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_jquery,_common,_cursive_autosave,_document_view){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,_jquery=_interopRequireDefault(_jquery),_cursive_autosave=_interopRequireDefault(_cursive_autosave),_document_view=_interopRequireDefault(_document_view);_exports.register=(editor,interval,userId,hasApiKey,MODULES,Rubrics,submission,quizInfo,pasteSetting)=>{var isStudent=!(0,_jquery.default)("#body").hasClass("teacher_admin"),intervention=(0,_jquery.default)("#body").hasClass("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="",questionid=0,quizSubmit=(0,_jquery.default)("#mod_quiz-next-nav");let assignSubmit=(0,_jquery.default)("#id_submitbutton");var syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let aiContents=[];var isFullScreen=!1,user=null;let ur=window.location.href,parm=new URL(ur),modulesInfo=function(ur,parm,MODULES){if(function(){localStorage.getItem("sbTitle")||Promise.all([(0,_str.get_string)("assignment","tiny_cursive"),(0,_str.get_string)("discussion","tiny_cursive"),(0,_str.get_string)("pluginname","mod_quiz"),(0,_str.get_string)("pluginname","mod_lesson"),(0,_str.get_string)("description","tiny_cursive")]).then((function(strings){return localStorage.setItem("sbTitle",JSON.stringify(strings))})).catch((error=>window.console.error(error)));localStorage.getItem("docSideBar")||Promise.all([(0,_str.get_string)("details","tiny_cursive"),(0,_str.get_string)("student_info","tiny_cursive"),(0,_str.get_string)("progress","tiny_cursive"),(0,_str.get_string)("description","tiny_cursive"),(0,_str.get_string)("replyingto","tiny_cursive"),(0,_str.get_string)("answeringto","tiny_cursive"),(0,_str.get_string)("importantdates","tiny_cursive"),(0,_str.get_string)("rubrics","tiny_cursive"),(0,_str.get_string)("submission_status","tiny_cursive"),(0,_str.get_string)("status","tiny_cursive"),(0,_str.get_string)("draft","tiny_cursive"),(0,_str.get_string)("draftnot","tiny_cursive"),(0,_str.get_string)("last_modified","tiny_cursive"),(0,_str.get_string)("gradings","tiny_cursive"),(0,_str.get_string)("gradenot","tiny_cursive"),(0,_str.get_string)("word_count","tiny_cursive"),(0,_str.get_string)("timeleft","tiny_cursive"),(0,_str.get_string)("nolimit","tiny_cursive"),(0,_str.get_string)("name","tiny_cursive"),(0,_str.get_string)("userename","tiny_cursive"),(0,_str.get_string)("course","tiny_cursive"),(0,_str.get_string)("opened","tiny_cursive"),(0,_str.get_string)("due","tiny_cursive"),(0,_str.get_string)("overdue","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive"),(0,_str.get_string)("savechanges","tiny_cursive"),(0,_str.get_string)("subjectnot","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive")]).then((function(strings){return localStorage.setItem("docSideBar",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}(),!MODULES.some((module=>ur.includes(module))))return!1;resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt");null===resourceId&&(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 checkIsPdfAnnotator(),{resourceId:resourceId,name:modulename}}(ur,parm,MODULES);var resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,errorAlert=!0;let PASTE_SETTING=pasteSetting||"allow",shouldBlockPaste=!1,isPasteAllowed=!1;ur.includes("pdfannotator")&&document.addEventListener("click",(e=>{if("dropdown-item comment-edit-a"===e.target.className){let id=e.target.id;resourceId=id.replace("editButton",""),localStorage.setItem("isEditing","1")}"commentSubmit"===e.target.id&&syncData()}));const postOne=async(methodname,args)=>{try{const response=await(0,_ajax.call)([{methodname:methodname,args:args}])[0];return response&&setTimeout((()=>{_cursive_autosave.default.updateSavingState("saved")}),1e3),response}catch(error){throw _cursive_autosave.default.updateSavingState("offline"),window.console.error("Error in postOne:",error),error}};(0,_ajax.call)([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[userid]}}])[0].done((response=>{user=response[0]})).fail((ex=>{window.console.error("Error fetching user data:",ex)})),assignSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{assignSubmit.off("click").click()})):assignSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")})),quizSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{quizSubmit.off("click").click()})):quizSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")}));const getModal=()=>{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();""===number||null==number?(editor.execCommand("Undo"),(0,_str.get_string)("pastewarning","tiny_cursive").then((str=>alert(str)))):editor.execCommand("Paste"),postOne("cursive_user_comments",{modulename:modulename,cmid:cmid,resourceid:resourceId,courseid:courseid,usercomment:number,timemodified:Date.now(),editorid:editorid||""}),lastEvent="save",modal.destroy()})),modal.getRoot().on(_modal_events.cancel,(function(){editor.execCommand("Undo"),lastEvent="cancel"})),modal.getRoot().on(_modal_events.hidden,(function(){"cancel"!=lastEvent&&"save"!=lastEvent&&editor.execCommand("Undo")})),modal}))})).catch((error=>window.console.error(error)))},sendKeyEvent=(events,editor)=>{if(ed=editor,event=events,filename="".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")),localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,editor.key=function(editor){switch(editor.button){case 0:return"left";case 1:return"middle";case 2:return"right"}return null}(editor),editor.keyCode=editor.button}function getCaretPosition(){let skip=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{if(!editor||!editor.selection)return{caretPosition:0,rePosition:0};const range=editor.selection.getRng(),body=editor.getBody(),preCaretRange=range.cloneRange();preCaretRange.selectNodeContents(body),preCaretRange.setEnd(range.endContainer,range.endOffset);const fragment=preCaretRange.cloneContents(),tempDiv=document.createElement("div");tempDiv.appendChild(fragment);let textBeforeCursor=tempDiv.innerText||"";const endContainer=range.endContainer;0===range.endOffset&&endContainer.nodeType===Node.ELEMENT_NODE&&editor.dom.isBlock(endContainer)&&endContainer.previousSibling&&(textBeforeCursor+="\n");const blockElements=tempDiv.querySelectorAll("p, div, h1, h2, h3, h4, h5, h6, li");let emptyBlockCount=0;blockElements.forEach((block=>{""===(block.innerText||block.textContent||"").trim()&&1===block.childNodes.length&&"BR"===block.childNodes[0].nodeName&&emptyBlockCount++})),emptyBlockCount>0&&(textBeforeCursor+="\n".repeat(emptyBlockCount));const absolutePosition=textBeforeCursor.length;if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey="".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:lastCaretPos||1,rePosition:0}}}async function syncData(){checkIsPdfAnnotator();let data=localStorage.getItem(filename);if(data&&0!==data.length){localStorage.removeItem(filename),editor.fire("change");let originalText=editor.getContent({format:"text"});originalText||(originalText=function(editor){let editorId=null==editor?void 0:editor.id;if(editorId){var _iframe$contentDocume,_iframe$contentWindow,_iframe$contentWindow2;let iframe=document.querySelector("#".concat(editorId,"_ifr")),iframeBody=(null===(_iframe$contentDocume=iframe.contentDocument)||void 0===_iframe$contentDocume?void 0:_iframe$contentDocume.body)||(null===(_iframe$contentWindow=iframe.contentWindow)||void 0===_iframe$contentWindow||null===(_iframe$contentWindow2=_iframe$contentWindow.document)||void 0===_iframe$contentWindow2?void 0:_iframe$contentWindow2.body);return null==iframeBody?void 0:iframeBody.textContent}return""}(editor));try{return _cursive_autosave.default.updateSavingState("saving"),await postOne("cursive_write_local_to_json",{key:ed.key,event:event,keyCode:ed.keyCode,resourceId:resourceId,cmid:cmid,modulename:modulename,editorid:editorid,json_data:data,originalText:originalText})}catch(error){window.console.error("Error submitting data:",error)}}}function customTooltip(){try{const tooltipText=async function(){const[buttonTitle,buttonDes]=await Promise.all([(0,_str.get_string)("cursive:state:active","tiny_cursive"),(0,_str.get_string)("cursive:state:active:des","tiny_cursive")]);return{buttonTitle:buttonTitle,buttonDes:buttonDes}}(),menubarDiv=document.querySelectorAll('div[role="menubar"].tox-menubar');let classArray=[];menubarDiv.length&&menubarDiv.forEach((function(element,index){let className="cursive-menu-"+(index+=1);element.classList.add(className),classArray.push(className)}));const cursiveIcon=document.createElement("img");cursiveIcon.src=hasApiKey?_common.iconUrl:_common.iconGrayUrl,cursiveIcon.setAttribute("class","tiny_cursive_StateButton"),cursiveIcon.style.display="inline-block",function(cursiveIcon,menubarDiv,classArray){if(!menubarDiv)return;for(let index in classArray){const rightWrapper=document.createElement("div"),imgWrapper=document.createElement("span"),iconClone=cursiveIcon.cloneNode(!0),targetMenu=document.querySelector("."+classArray[index]);let elementId="tiny_cursive_StateIcon"+index;rightWrapper.style.cssText="\n margin-left: auto;\n display: flex;\n align-items: center;\n ",imgWrapper.id=elementId,imgWrapper.style.marginLeft=".2rem",imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper);let moduleIds={resourceId:resourceId,cmid:cmid,modulename:modulename,questionid:questionid,userid:userid,courseid:courseid};if(!isFullScreen||"assign"!==modulename&&"forum"!==modulename&&"lesson"!==modulename)if(isFullScreen&&"quiz"===modulename){var _editor$container,_editor$container$chi,_editor$container$chi2,_editor$container$chi3,_editor$container2;let existingElement=null===(_editor$container=editor.container)||void 0===_editor$container||null===(_editor$container$chi=_editor$container.childNodes[1])||void 0===_editor$container$chi||null===(_editor$container$chi2=_editor$container$chi.childNodes[0])||void 0===_editor$container$chi2||null===(_editor$container$chi3=_editor$container$chi2.childNodes[0])||void 0===_editor$container$chi3?void 0:_editor$container$chi3.childNodes[7],newHeader=null===(_editor$container2=editor.container)||void 0===_editor$container2?void 0:_editor$container2.childNodes[0];existingElement&&existingElement.remove(),newHeader&&!newHeader.querySelector("span[id*=tiny_cursive_StateIcon]")&&(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{var _editor$container3,_editor$container3$ch,_editor$container3$ch2;let menubar=null==editor||null===(_editor$container3=editor.container)||void 0===_editor$container3||null===(_editor$container3$ch=_editor$container3.children[0])||void 0===_editor$container3$ch||null===(_editor$container3$ch2=_editor$container3$ch.childNodes[0])||void 0===_editor$container3$ch2?void 0:_editor$container3$ch2.childNodes[0];if(targetMenu&&!targetMenu.querySelector("#".concat(elementId))&&targetMenu.appendChild(rightWrapper),"quiz"===modulename&&menubar){let wrapper=menubar.querySelector('span[id*="tiny_cursive_StateIcon"]');wrapper&&(_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,null==wrapper?void 0:wrapper.parentElement,moduleIds,isFullScreen))}else _cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{let existsElement=document.querySelector('.tox-menubar[class*="cursive-menu-"] > div');existsElement&&existsElement.remove(),document.querySelector("#".concat(elementId))||(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId="tiny_cursive_tooltip".concat(index);tooltipText.then((text=>setTooltip(text,document.querySelector("#".concat(elementId)),tooltipId))).catch((error=>window.console.error(error))),(0,_jquery.default)("#".concat(elementId)).on("mouseenter",(function(){(0,_jquery.default)(this).css("position","relative"),(0,_jquery.default)("#".concat(tooltipId)).css(_common.tooltipCss)})),(0,_jquery.default)("#".concat(elementId)).on("mouseleave",(function(){(0,_jquery.default)("#".concat(tooltipId)).css("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 checkIsPdfAnnotator(){ur.includes("pdfannotator")&&(resourceId="id_pdfannotator_content"!==editor.id&&parseInt(localStorage.getItem("isEditing"))?parseInt(null==editor?void 0:editor.id.replace("editarea","")):0)}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyUp",editor)})),editor.on("Paste",(async e=>{customTooltip();const pastedContent=(e.clipboardData||e.originalEvent.clipboardData).getData("text");if(!pastedContent)return;const trimmedPastedContent=pastedContent.trim(),lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&trimmedPastedContent===lastCopyCutContent;if(isStudent&&intervention){if("block"===PASTE_SETTING)return isFromOwnEditor?(shouldBlockPaste=!1,void(isPasteAllowed=!0)):(e.preventDefault(),shouldBlockPaste=!0,isPasteAllowed=!1,e.stopPropagation(),e.stopImmediatePropagation(),(0,_str.get_string)("paste_blocked","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error))),void setTimeout((()=>{isPasteAllowed=!0,shouldBlockPaste=!1}),100));if("cite_source"===PASTE_SETTING)return isFromOwnEditor||(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),getModal()),void(isPasteAllowed=!0)}isPasteAllowed=!0})),editor.on("Redo",(async e=>{customTooltip(),isStudent&&intervention&&getModal()})),editor.on("keyDown",(editor=>{customTooltip();if(("v"===editor.key||"V"===editor.key)&&(editor.ctrlKey||editor.metaKey)&&isStudent&&intervention&&"block"===PASTE_SETTING&&!isPasteAllowed)return void setTimeout((()=>{isPasteAllowed=!0}),100);let position=getCaretPosition();editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyDown",editor)})),editor.on("Cut",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("Copy",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("mouseDown",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)}),0)})),editor.on("mouseUp",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)}),10)})),editor.on("init",(()=>{customTooltip(),localStorage.removeItem("lastCopyCutContent")})),editor.on("SetContent",(()=>{customTooltip()})),editor.on("FullscreenStateChanged",(e=>{let view=new _document_view.default(user,Rubrics,submission,modulename,editor,quizInfo);isFullScreen=e.state;try{e.state?view.fullPageMode():view.normalMode()}catch(error){errorAlert&&(errorAlert=!1,(0,_str.get_string)("fullmodeerror","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))),view.normalMode(),window.console.error("Error ResizeEditor event:",error)}})),editor.on("execcommand",(function(e){if("mceInsertContent"===e.command){const contentObj=e.value,isPaste=contentObj&&"object"==typeof contentObj&&!0===contentObj.paste;let insertedContent=contentObj.content||contentObj,tempDiv=document.createElement("div");tempDiv.innerHTML=insertedContent;let text=tempDiv.textContent||tempDiv.innerText||"",pastedText=tempDiv.textContent||tempDiv.innerText||"",position=getCaretPosition(!0);if(editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,isPaste){if(shouldBlockPaste)return shouldBlockPaste=!1,e.preventDefault(),void editor.undoManager.undo();const lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&pastedText.trim()===lastCopyCutContent;if(isStudent&&intervention&&"block"===PASTE_SETTING&&!isFromOwnEditor)return isPasteAllowed=!1,void editor.undoManager.undo();sendKeyEvent("Paste",{key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition,pastedContent:pastedText,srcElement:{baseURI:window.location.href}})}else aiContents.push(text),sendKeyEvent("aiInsert",{key:"ai",keyCode:0,caretPosition:editor.caretPosition,rePosition:editor.rePosition,aiContent:text,srcElement:{baseURI:window.location.href}})}})),editor.on("input",(function(e){let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition;let aiContent=e.data;("insertReplacementText"===e.inputType||"insertText"===e.inputType&&aiContent&&aiContent.length>1)&&(aiContents.push(aiContent),e.key="ai",e.keyCode=0,e.caretPosition=position.caretPosition,e.rePosition=position.rePosition,e.aiContent=aiContent,sendKeyEvent("aiInsert",e))})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}})); -======= -define("tiny_cursive/autosaver",["exports","core/ajax","core/modal_factory","core/str","core/modal_events","jquery","tiny_cursive/common","tiny_cursive/cursive_autosave","tiny_cursive/document_view"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_jquery,_common,_cursive_autosave,_document_view){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,_jquery=_interopRequireDefault(_jquery),_cursive_autosave=_interopRequireDefault(_cursive_autosave),_document_view=_interopRequireDefault(_document_view);_exports.register=(editor,interval,userId,hasApiKey,MODULES,Rubrics,submission,quizInfo,pasteSetting)=>{var isStudent=!(0,_jquery.default)("#body").hasClass("teacher_admin"),intervention=(0,_jquery.default)("#body").hasClass("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="",questionid=0,quizSubmit=(0,_jquery.default)("#mod_quiz-next-nav");let assignSubmit=(0,_jquery.default)("#id_submitbutton");var syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let aiContents=[];var isFullScreen=!1,user=null;let ur=window.location.href,modulesInfo=function(ur,parm,MODULES){if(function(){localStorage.getItem("sbTitle")||Promise.all([(0,_str.get_string)("assignment","tiny_cursive"),(0,_str.get_string)("discussion","tiny_cursive"),(0,_str.get_string)("pluginname","mod_quiz"),(0,_str.get_string)("pluginname","mod_lesson"),(0,_str.get_string)("description","tiny_cursive")]).then((function(strings){return localStorage.setItem("sbTitle",JSON.stringify(strings))})).catch((error=>window.console.error(error)));localStorage.getItem("docSideBar")||Promise.all([(0,_str.get_string)("details","tiny_cursive"),(0,_str.get_string)("student_info","tiny_cursive"),(0,_str.get_string)("progress","tiny_cursive"),(0,_str.get_string)("description","tiny_cursive"),(0,_str.get_string)("replyingto","tiny_cursive"),(0,_str.get_string)("answeringto","tiny_cursive"),(0,_str.get_string)("importantdates","tiny_cursive"),(0,_str.get_string)("rubrics","tiny_cursive"),(0,_str.get_string)("submission_status","tiny_cursive"),(0,_str.get_string)("status","tiny_cursive"),(0,_str.get_string)("draft","tiny_cursive"),(0,_str.get_string)("draftnot","tiny_cursive"),(0,_str.get_string)("last_modified","tiny_cursive"),(0,_str.get_string)("gradings","tiny_cursive"),(0,_str.get_string)("gradenot","tiny_cursive"),(0,_str.get_string)("word_count","tiny_cursive"),(0,_str.get_string)("timeleft","tiny_cursive"),(0,_str.get_string)("nolimit","tiny_cursive"),(0,_str.get_string)("name","tiny_cursive"),(0,_str.get_string)("userename","tiny_cursive"),(0,_str.get_string)("course","tiny_cursive"),(0,_str.get_string)("opened","tiny_cursive"),(0,_str.get_string)("due","tiny_cursive"),(0,_str.get_string)("overdue","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive"),(0,_str.get_string)("savechanges","tiny_cursive"),(0,_str.get_string)("subjectnot","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive")]).then((function(strings){return localStorage.setItem("docSideBar",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}(),!MODULES.some((module=>ur.includes(module))))return!1;resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt");null===resourceId&&(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}}(ur,new URL(ur),MODULES);var resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,errorAlert=!0;let PASTE_SETTING=pasteSetting||"allow",shouldBlockPaste=!1,isPasteAllowed=!1;"assign"!==modulename&&(PASTE_SETTING="cite_source");const postOne=async(methodname,args)=>{try{const response=await(0,_ajax.call)([{methodname:methodname,args:args}])[0];return response&&setTimeout((()=>{_cursive_autosave.default.updateSavingState("saved")}),1e3),response}catch(error){throw _cursive_autosave.default.updateSavingState("offline"),window.console.error("Error in postOne:",error),error}};(0,_ajax.call)([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[M.cfg.userId]}}])[0].done((response=>{user=response[0]})).fail((ex=>{window.console.error("Error fetching user data:",ex)})),assignSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{assignSubmit.off("click").click()})):assignSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")})),quizSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{quizSubmit.off("click").click()})):quizSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")}));const getModal=()=>{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();""===number||null==number?(editor.execCommand("Undo"),(0,_str.get_string)("pastewarning","tiny_cursive").then((str=>alert(str)))):editor.execCommand("Paste"),postOne("cursive_user_comments",{modulename:modulename,cmid:cmid,resourceid:resourceId,courseid:courseid,usercomment:number,timemodified:Date.now(),editorid:editorid||""}),lastEvent="save",modal.destroy()})),modal.getRoot().on(_modal_events.cancel,(function(){editor.execCommand("Undo"),lastEvent="cancel"})),modal.getRoot().on(_modal_events.hidden,(function(){"cancel"!=lastEvent&&"save"!=lastEvent&&editor.execCommand("Undo")})),modal}))})).catch((error=>window.console.error(error)))},sendKeyEvent=(events,editor)=>{if(ed=editor,event=events,filename="".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")),localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,editor.key=function(editor){switch(editor.button){case 0:return"left";case 1:return"middle";case 2:return"right"}return null}(editor),editor.keyCode=editor.button}function getCaretPosition(){let skip=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{if(!editor||!editor.selection)return{caretPosition:0,rePosition:0};const range=editor.selection.getRng(),body=editor.getBody(),preCaretRange=range.cloneRange();preCaretRange.selectNodeContents(body),preCaretRange.setEnd(range.endContainer,range.endOffset);const fragment=preCaretRange.cloneContents(),tempDiv=document.createElement("div");tempDiv.appendChild(fragment);let textBeforeCursor=tempDiv.innerText||"";const endContainer=range.endContainer;0===range.endOffset&&endContainer.nodeType===Node.ELEMENT_NODE&&editor.dom.isBlock(endContainer)&&endContainer.previousSibling&&(textBeforeCursor+="\n");const blockElements=tempDiv.querySelectorAll("p, div, h1, h2, h3, h4, h5, h6, li");let emptyBlockCount=0;blockElements.forEach((block=>{""===(block.innerText||block.textContent||"").trim()&&1===block.childNodes.length&&"BR"===block.childNodes[0].nodeName&&emptyBlockCount++})),emptyBlockCount>0&&(textBeforeCursor+="\n".repeat(emptyBlockCount));const absolutePosition=textBeforeCursor.length;if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey="".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:lastCaretPos||1,rePosition:0}}}async function syncData(){let data=localStorage.getItem(filename);if(data&&0!==data.length){localStorage.removeItem(filename);let originalText=editor.getContent({format:"text"});try{return _cursive_autosave.default.updateSavingState("saving"),await postOne("cursive_write_local_to_json",{key:ed.key,event:event,keyCode:ed.keyCode,resourceId:resourceId,cmid:cmid,modulename:modulename,editorid:editorid,json_data:data,originalText:originalText})}catch(error){window.console.error("Error submitting data:",error)}}}function customTooltip(){try{const tooltipText=async function(){const[buttonTitle,buttonDes]=await Promise.all([(0,_str.get_string)("cursive:state:active","tiny_cursive"),(0,_str.get_string)("cursive:state:active:des","tiny_cursive")]);return{buttonTitle:buttonTitle,buttonDes:buttonDes}}(),menubarDiv=document.querySelectorAll('div[role="menubar"].tox-menubar');let classArray=[];menubarDiv.length&&menubarDiv.forEach((function(element,index){let className="cursive-menu-"+(index+=1);element.classList.add(className),classArray.push(className)}));const cursiveIcon=document.createElement("img");cursiveIcon.src=hasApiKey?_common.iconUrl:_common.iconGrayUrl,cursiveIcon.setAttribute("class","tiny_cursive_StateButton"),cursiveIcon.style.display="inline-block",function(cursiveIcon,menubarDiv,classArray){if(!menubarDiv)return;for(let index in classArray){const rightWrapper=document.createElement("div"),imgWrapper=document.createElement("span"),iconClone=cursiveIcon.cloneNode(!0),targetMenu=document.querySelector("."+classArray[index]);let elementId="tiny_cursive_StateIcon"+index;rightWrapper.style.cssText="\n margin-left: auto;\n display: flex;\n align-items: center;\n ",imgWrapper.id=elementId,imgWrapper.style.marginLeft=".2rem",imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper);let moduleIds={resourceId:resourceId,cmid:cmid,modulename:modulename,questionid:questionid,userid:userid,courseid:courseid};if(!isFullScreen||"assign"!==modulename&&"forum"!==modulename&&"lesson"!==modulename)if(isFullScreen&&"quiz"===modulename){var _editor$container,_editor$container$chi,_editor$container$chi2,_editor$container$chi3,_editor$container2;let existingElement=null===(_editor$container=editor.container)||void 0===_editor$container||null===(_editor$container$chi=_editor$container.childNodes[1])||void 0===_editor$container$chi||null===(_editor$container$chi2=_editor$container$chi.childNodes[0])||void 0===_editor$container$chi2||null===(_editor$container$chi3=_editor$container$chi2.childNodes[0])||void 0===_editor$container$chi3?void 0:_editor$container$chi3.childNodes[7],newHeader=null===(_editor$container2=editor.container)||void 0===_editor$container2?void 0:_editor$container2.childNodes[0];existingElement&&existingElement.remove(),newHeader&&!newHeader.querySelector("span[id*=tiny_cursive_StateIcon]")&&(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{var _editor$container3,_editor$container3$ch,_editor$container3$ch2;let menubar=null==editor||null===(_editor$container3=editor.container)||void 0===_editor$container3||null===(_editor$container3$ch=_editor$container3.children[0])||void 0===_editor$container3$ch||null===(_editor$container3$ch2=_editor$container3$ch.childNodes[0])||void 0===_editor$container3$ch2?void 0:_editor$container3$ch2.childNodes[0];if(targetMenu&&!targetMenu.querySelector("#".concat(elementId))&&targetMenu.appendChild(rightWrapper),"quiz"===modulename&&menubar){let wrapper=menubar.querySelector('span[id*="tiny_cursive_StateIcon"]');wrapper&&(_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,null==wrapper?void 0:wrapper.parentElement,moduleIds,isFullScreen))}else _cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{let existsElement=document.querySelector('.tox-menubar[class*="cursive-menu-"] > div');existsElement&&existsElement.remove(),document.querySelector("#".concat(elementId))||(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId="tiny_cursive_tooltip".concat(index);tooltipText.then((text=>setTooltip(text,document.querySelector("#".concat(elementId)),tooltipId))).catch((error=>window.console.error(error))),(0,_jquery.default)("#".concat(elementId)).on("mouseenter",(function(){(0,_jquery.default)(this).css("position","relative"),(0,_jquery.default)("#".concat(tooltipId)).css(_common.tooltipCss)})),(0,_jquery.default)("#".concat(elementId)).on("mouseleave",(function(){(0,_jquery.default)("#".concat(tooltipId)).css("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)}}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyUp",editor)})),editor.on("Paste",(async e=>{customTooltip();const pastedContent=(e.clipboardData||e.originalEvent.clipboardData).getData("text");if(!pastedContent)return;const trimmedPastedContent=pastedContent.trim(),lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&trimmedPastedContent===lastCopyCutContent;if(isStudent&&intervention){if("block"===PASTE_SETTING)return isFromOwnEditor?(shouldBlockPaste=!1,void(isPasteAllowed=!0)):(e.preventDefault(),shouldBlockPaste=!0,isPasteAllowed=!1,e.stopPropagation(),e.stopImmediatePropagation(),(0,_str.get_string)("paste_blocked","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error))),void setTimeout((()=>{isPasteAllowed=!0,shouldBlockPaste=!1}),100));if("cite_source"===PASTE_SETTING)return isFromOwnEditor||(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),getModal()),void(isPasteAllowed=!0)}isPasteAllowed=!0})),editor.on("Redo",(async e=>{customTooltip(),isStudent&&intervention&&getModal()})),editor.on("keyDown",(editor=>{customTooltip();if(("v"===editor.key||"V"===editor.key)&&(editor.ctrlKey||editor.metaKey)&&isStudent&&intervention&&"block"===PASTE_SETTING&&!isPasteAllowed)return void setTimeout((()=>{isPasteAllowed=!0}),100);let position=getCaretPosition();editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyDown",editor)})),editor.on("Cut",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("Copy",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("mouseDown",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)}),0)})),editor.on("mouseUp",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)}),10)})),editor.on("init",(()=>{customTooltip(),localStorage.removeItem("lastCopyCutContent")})),editor.on("SetContent",(()=>{customTooltip()})),editor.on("FullscreenStateChanged",(e=>{let view=new _document_view.default(user,Rubrics,submission,modulename,editor,quizInfo);isFullScreen=e.state;try{e.state?view.fullPageMode():view.normalMode()}catch(error){errorAlert&&(errorAlert=!1,(0,_str.get_string)("fullmodeerror","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))),view.normalMode(),window.console.error("Error ResizeEditor event:",error)}})),editor.on("execcommand",(function(e){if("mceInsertContent"===e.command){const contentObj=e.value,isPaste=contentObj&&"object"==typeof contentObj&&!0===contentObj.paste;let insertedContent=contentObj.content||contentObj,tempDiv=document.createElement("div");tempDiv.innerHTML=insertedContent;let text=tempDiv.textContent||tempDiv.innerText||"",pastedText=tempDiv.textContent||tempDiv.innerText||"",position=getCaretPosition(!0);if(editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,isPaste){if(shouldBlockPaste)return shouldBlockPaste=!1,e.preventDefault(),void editor.undoManager.undo();const lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&pastedText.trim()===lastCopyCutContent;if(isStudent&&intervention&&"block"===PASTE_SETTING&&!isFromOwnEditor)return isPasteAllowed=!1,void editor.undoManager.undo();sendKeyEvent("Paste",{key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition,pastedContent:pastedText,srcElement:{baseURI:window.location.href}})}else aiContents.push(text),sendKeyEvent("aiInsert",{key:"ai",keyCode:0,caretPosition:editor.caretPosition,rePosition:editor.rePosition,aiContent:text,srcElement:{baseURI:window.location.href}})}})),editor.on("input",(function(e){let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition;let aiContent=e.data;("insertReplacementText"===e.inputType||"insertText"===e.inputType&&aiContent&&aiContent.length>1)&&(aiContents.push(aiContent),e.key="ai",e.keyCode=0,e.caretPosition=position.caretPosition,e.rePosition=position.rePosition,e.aiContent=aiContent,sendKeyEvent("aiInsert",e))})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}})); ->>>>>>> 8b275778ba140d4ec01f088ecfc426293a0a3296 +define("tiny_cursive/autosaver",["exports","core/ajax","core/modal_factory","core/str","core/modal_events","jquery","tiny_cursive/common","tiny_cursive/cursive_autosave","tiny_cursive/document_view"],(function(_exports,_ajax,_modal_factory,_str,_modal_events,_jquery,_common,_cursive_autosave,_document_view){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,_jquery=_interopRequireDefault(_jquery),_cursive_autosave=_interopRequireDefault(_cursive_autosave),_document_view=_interopRequireDefault(_document_view);_exports.register=(editor,interval,userId,hasApiKey,MODULES,Rubrics,submission,quizInfo,pasteSetting)=>{var isStudent=!(0,_jquery.default)("#body").hasClass("teacher_admin"),intervention=(0,_jquery.default)("#body").hasClass("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="",questionid=0,quizSubmit=(0,_jquery.default)("#mod_quiz-next-nav");let assignSubmit=(0,_jquery.default)("#id_submitbutton");var syncInterval=interval?1e3*interval:1e4,lastCaretPos=1;let aiContents=[];var isFullScreen=!1,user=null;let ur=window.location.href,parm=new URL(ur),modulesInfo=function(ur,parm,MODULES){if(function(){localStorage.getItem("sbTitle")||Promise.all([(0,_str.get_string)("assignment","tiny_cursive"),(0,_str.get_string)("discussion","tiny_cursive"),(0,_str.get_string)("pluginname","mod_quiz"),(0,_str.get_string)("pluginname","mod_lesson"),(0,_str.get_string)("description","tiny_cursive")]).then((function(strings){return localStorage.setItem("sbTitle",JSON.stringify(strings))})).catch((error=>window.console.error(error)));localStorage.getItem("docSideBar")||Promise.all([(0,_str.get_string)("details","tiny_cursive"),(0,_str.get_string)("student_info","tiny_cursive"),(0,_str.get_string)("progress","tiny_cursive"),(0,_str.get_string)("description","tiny_cursive"),(0,_str.get_string)("replyingto","tiny_cursive"),(0,_str.get_string)("answeringto","tiny_cursive"),(0,_str.get_string)("importantdates","tiny_cursive"),(0,_str.get_string)("rubrics","tiny_cursive"),(0,_str.get_string)("submission_status","tiny_cursive"),(0,_str.get_string)("status","tiny_cursive"),(0,_str.get_string)("draft","tiny_cursive"),(0,_str.get_string)("draftnot","tiny_cursive"),(0,_str.get_string)("last_modified","tiny_cursive"),(0,_str.get_string)("gradings","tiny_cursive"),(0,_str.get_string)("gradenot","tiny_cursive"),(0,_str.get_string)("word_count","tiny_cursive"),(0,_str.get_string)("timeleft","tiny_cursive"),(0,_str.get_string)("nolimit","tiny_cursive"),(0,_str.get_string)("name","tiny_cursive"),(0,_str.get_string)("userename","tiny_cursive"),(0,_str.get_string)("course","tiny_cursive"),(0,_str.get_string)("opened","tiny_cursive"),(0,_str.get_string)("due","tiny_cursive"),(0,_str.get_string)("overdue","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive"),(0,_str.get_string)("savechanges","tiny_cursive"),(0,_str.get_string)("subjectnot","tiny_cursive"),(0,_str.get_string)("remaining","tiny_cursive")]).then((function(strings){return localStorage.setItem("docSideBar",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}(),!MODULES.some((module=>ur.includes(module))))return!1;resourceId=ur.includes("forum")&&!ur.includes("assign")?parm.searchParams.get("edit"):parm.searchParams.get("attempt");null===resourceId&&(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 checkIsPdfAnnotator(),{resourceId:resourceId,name:modulename}}(ur,parm,MODULES);var resourceId=modulesInfo.resourceId,modulename=modulesInfo.name,errorAlert=!0;let PASTE_SETTING=pasteSetting||"allow",shouldBlockPaste=!1,isPasteAllowed=!1;ur.includes("pdfannotator")&&document.addEventListener("click",(e=>{if("dropdown-item comment-edit-a"===e.target.className){let id=e.target.id;resourceId=id.replace("editButton",""),localStorage.setItem("isEditing","1")}"commentSubmit"===e.target.id&&syncData()}));const postOne=async(methodname,args)=>{try{const response=await(0,_ajax.call)([{methodname:methodname,args:args}])[0];return response&&setTimeout((()=>{_cursive_autosave.default.updateSavingState("saved")}),1e3),response}catch(error){throw _cursive_autosave.default.updateSavingState("offline"),window.console.error("Error in postOne:",error),error}};(0,_ajax.call)([{methodname:"core_user_get_users_by_field",args:{field:"id",values:[userid]}}])[0].done((response=>{user=response[0]})).fail((ex=>{window.console.error("Error fetching user data:",ex)})),assignSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{assignSubmit.off("click").click()})):assignSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")})),quizSubmit.on("click",(async function(e){e.preventDefault(),filename?syncData().then((()=>{quizSubmit.off("click").click()})):quizSubmit.off("click").click(),localStorage.removeItem("lastCopyCutContent")}));const getModal=()=>{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();""===number||null==number?(editor.execCommand("Undo"),(0,_str.get_string)("pastewarning","tiny_cursive").then((str=>alert(str)))):editor.execCommand("Paste"),postOne("cursive_user_comments",{modulename:modulename,cmid:cmid,resourceid:resourceId,courseid:courseid,usercomment:number,timemodified:Date.now(),editorid:editorid||""}),lastEvent="save",modal.destroy()})),modal.getRoot().on(_modal_events.cancel,(function(){editor.execCommand("Undo"),lastEvent="cancel"})),modal.getRoot().on(_modal_events.hidden,(function(){"cancel"!=lastEvent&&"save"!=lastEvent&&editor.execCommand("Undo")})),modal}))})).catch((error=>window.console.error(error)))},sendKeyEvent=(events,editor)=>{if(ed=editor,event=events,filename=`${userid}_${resourceId}_${cmid}_${modulename}_attempt`,"quiz"===modulename&&(questionid=editorid.split(":")[1].split("_")[0],filename=`${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`),localStorage.getItem(filename)){let data=JSON.parse(localStorage.getItem(filename));data.push({resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}),localStorage.setItem(filename,JSON.stringify(data))}else{let data=[{resourceId:resourceId,key:editor.key,keyCode:editor.keyCode,event:event,courseId:courseid,unixTimestamp:Date.now(),clientId:host,personId:userid,position:ed.caretPosition,rePosition:ed.rePosition,pastedContent:editor.pastedContent,aiContent:editor.aiContent}];localStorage.setItem(filename,JSON.stringify(data))}};function constructMouseEvent(editor){let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,editor.key=function(editor){switch(editor.button){case 0:return"left";case 1:return"middle";case 2:return"right"}return null}(editor),editor.keyCode=editor.button}function getCaretPosition(){let skip=arguments.length>0&&void 0!==arguments[0]&&arguments[0];try{if(!editor||!editor.selection)return{caretPosition:0,rePosition:0};const range=editor.selection.getRng(),body=editor.getBody(),preCaretRange=range.cloneRange();preCaretRange.selectNodeContents(body),preCaretRange.setEnd(range.endContainer,range.endOffset);const fragment=preCaretRange.cloneContents(),tempDiv=document.createElement("div");tempDiv.appendChild(fragment);let textBeforeCursor=tempDiv.innerText||"";const endContainer=range.endContainer;0===range.endOffset&&endContainer.nodeType===Node.ELEMENT_NODE&&editor.dom.isBlock(endContainer)&&endContainer.previousSibling&&(textBeforeCursor+="\n");const blockElements=tempDiv.querySelectorAll("p, div, h1, h2, h3, h4, h5, h6, li");let emptyBlockCount=0;blockElements.forEach((block=>{""===(block.innerText||block.textContent||"").trim()&&1===block.childNodes.length&&"BR"===block.childNodes[0].nodeName&&emptyBlockCount++})),emptyBlockCount>0&&(textBeforeCursor+="\n".repeat(emptyBlockCount));const absolutePosition=textBeforeCursor.length;if(skip)return{caretPosition:lastCaretPos,rePosition:absolutePosition};const storageKey=`${userid}_${resourceId}_${cmid}_position`;let storedPos=parseInt(sessionStorage.getItem(storageKey),10);return isNaN(storedPos)&&(storedPos=0),storedPos++,lastCaretPos=storedPos,sessionStorage.setItem(storageKey,storedPos),{caretPosition:storedPos,rePosition:absolutePosition}}catch(e){return window.console.warn("Error getting caret position:",e),{caretPosition:lastCaretPos||1,rePosition:0}}}async function syncData(){checkIsPdfAnnotator();let data=localStorage.getItem(filename);if(data&&0!==data.length){localStorage.removeItem(filename),editor.fire("change");let originalText=editor.getContent({format:"text"});originalText||(originalText=function(editor){let editorId=null==editor?void 0:editor.id;if(editorId){var _iframe$contentDocume,_iframe$contentWindow,_iframe$contentWindow2;let iframe=document.querySelector(`#${editorId}_ifr`),iframeBody=(null===(_iframe$contentDocume=iframe.contentDocument)||void 0===_iframe$contentDocume?void 0:_iframe$contentDocume.body)||(null===(_iframe$contentWindow=iframe.contentWindow)||void 0===_iframe$contentWindow||null===(_iframe$contentWindow2=_iframe$contentWindow.document)||void 0===_iframe$contentWindow2?void 0:_iframe$contentWindow2.body);return null==iframeBody?void 0:iframeBody.textContent}return""}(editor));try{return _cursive_autosave.default.updateSavingState("saving"),await postOne("cursive_write_local_to_json",{key:ed.key,event:event,keyCode:ed.keyCode,resourceId:resourceId,cmid:cmid,modulename:modulename,editorid:editorid,json_data:data,originalText:originalText})}catch(error){window.console.error("Error submitting data:",error)}}}function customTooltip(){try{const tooltipText=async function(){const[buttonTitle,buttonDes]=await Promise.all([(0,_str.get_string)("cursive:state:active","tiny_cursive"),(0,_str.get_string)("cursive:state:active:des","tiny_cursive")]);return{buttonTitle:buttonTitle,buttonDes:buttonDes}}(),menubarDiv=document.querySelectorAll('div[role="menubar"].tox-menubar');let classArray=[];menubarDiv.length&&menubarDiv.forEach((function(element,index){let className="cursive-menu-"+(index+=1);element.classList.add(className),classArray.push(className)}));const cursiveIcon=document.createElement("img");cursiveIcon.src=hasApiKey?_common.iconUrl:_common.iconGrayUrl,cursiveIcon.setAttribute("class","tiny_cursive_StateButton"),cursiveIcon.style.display="inline-block",function(cursiveIcon,menubarDiv,classArray){if(!menubarDiv)return;for(let index in classArray){const rightWrapper=document.createElement("div"),imgWrapper=document.createElement("span"),iconClone=cursiveIcon.cloneNode(!0),targetMenu=document.querySelector("."+classArray[index]);let elementId="tiny_cursive_StateIcon"+index;rightWrapper.style.cssText="\n margin-left: auto;\n display: flex;\n align-items: center;\n ",imgWrapper.id=elementId,imgWrapper.style.marginLeft=".2rem",imgWrapper.appendChild(iconClone),rightWrapper.appendChild(imgWrapper);let moduleIds={resourceId:resourceId,cmid:cmid,modulename:modulename,questionid:questionid,userid:userid,courseid:courseid};if(!isFullScreen||"assign"!==modulename&&"forum"!==modulename&&"lesson"!==modulename)if(isFullScreen&&"quiz"===modulename){var _editor$container,_editor$container$chi,_editor$container$chi2,_editor$container$chi3,_editor$container2;let existingElement=null===(_editor$container=editor.container)||void 0===_editor$container||null===(_editor$container$chi=_editor$container.childNodes[1])||void 0===_editor$container$chi||null===(_editor$container$chi2=_editor$container$chi.childNodes[0])||void 0===_editor$container$chi2||null===(_editor$container$chi3=_editor$container$chi2.childNodes[0])||void 0===_editor$container$chi3?void 0:_editor$container$chi3.childNodes[7],newHeader=null===(_editor$container2=editor.container)||void 0===_editor$container2?void 0:_editor$container2.childNodes[0];existingElement&&existingElement.remove(),newHeader&&!newHeader.querySelector("span[id*=tiny_cursive_StateIcon]")&&(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{var _editor$container3,_editor$container3$ch,_editor$container3$ch2;let menubar=null==editor||null===(_editor$container3=editor.container)||void 0===_editor$container3||null===(_editor$container3$ch=_editor$container3.children[0])||void 0===_editor$container3$ch||null===(_editor$container3$ch2=_editor$container3$ch.childNodes[0])||void 0===_editor$container3$ch2?void 0:_editor$container3$ch2.childNodes[0];if(targetMenu&&!targetMenu.querySelector(`#${elementId}`)&&targetMenu.appendChild(rightWrapper),"quiz"===modulename&&menubar){let wrapper=menubar.querySelector('span[id*="tiny_cursive_StateIcon"]');wrapper&&(_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,null==wrapper?void 0:wrapper.parentElement,moduleIds,isFullScreen))}else _cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}else{let existsElement=document.querySelector('.tox-menubar[class*="cursive-menu-"] > div');existsElement&&existsElement.remove(),document.querySelector(`#${elementId}`)||(rightWrapper.style.marginTop="3px",document.querySelector("#tiny_cursive-fullpage-right-wrapper").prepend(rightWrapper)),_cursive_autosave.default.destroyInstance(),_cursive_autosave.default.getInstance(editor,rightWrapper,moduleIds,isFullScreen)}}}(cursiveIcon,menubarDiv,classArray);for(let index in classArray){const elementId="tiny_cursive_StateIcon"+index,tooltipId=`tiny_cursive_tooltip${index}`;tooltipText.then((text=>setTooltip(text,document.querySelector(`#${elementId}`),tooltipId))).catch((error=>window.console.error(error))),(0,_jquery.default)(`#${elementId}`).on("mouseenter",(function(){(0,_jquery.default)(this).css("position","relative"),(0,_jquery.default)(`#${tooltipId}`).css(_common.tooltipCss)})),(0,_jquery.default)(`#${elementId}`).on("mouseleave",(function(){(0,_jquery.default)(`#${tooltipId}`).css("display","none")}))}}catch(error){window.console.error("Error setting up custom tooltip:",error)}}function setTooltip(text,cursiveIcon,tooltipId){if(!document.querySelector(`#${tooltipId}`)&&cursiveIcon){const tooltipSpan=document.createElement("span"),description=document.createElement("span"),linebreak=document.createElement("br"),tooltipTitle=document.createElement("strong");tooltipSpan.style.display="none",tooltipTitle.textContent=text.buttonTitle,tooltipTitle.style.fontSize="16px",tooltipTitle.style.fontWeight="bold",description.textContent=text.buttonDes,description.style.fontSize="14px",tooltipSpan.id=tooltipId,tooltipSpan.classList.add("shadow"),tooltipSpan.appendChild(tooltipTitle),tooltipSpan.appendChild(linebreak),tooltipSpan.appendChild(description),cursiveIcon.appendChild(tooltipSpan)}}function checkIsPdfAnnotator(){ur.includes("pdfannotator")&&(resourceId="id_pdfannotator_content"!==editor.id&&parseInt(localStorage.getItem("isEditing"))?parseInt(null==editor?void 0:editor.id.replace("editarea","")):0)}editor.on("keyUp",(editor=>{customTooltip();let position=getCaretPosition(!1);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyUp",editor)})),editor.on("Paste",(async e=>{customTooltip();const pastedContent=(e.clipboardData||e.originalEvent.clipboardData).getData("text");if(!pastedContent)return;const trimmedPastedContent=pastedContent.trim(),lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&trimmedPastedContent===lastCopyCutContent;if(isStudent&&intervention){if("block"===PASTE_SETTING)return isFromOwnEditor?(shouldBlockPaste=!1,void(isPasteAllowed=!0)):(e.preventDefault(),shouldBlockPaste=!0,isPasteAllowed=!1,e.stopPropagation(),e.stopImmediatePropagation(),(0,_str.get_string)("paste_blocked","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error))),void setTimeout((()=>{isPasteAllowed=!0,shouldBlockPaste=!1}),100));if("cite_source"===PASTE_SETTING)return isFromOwnEditor||(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),getModal()),void(isPasteAllowed=!0)}isPasteAllowed=!0})),editor.on("Redo",(async e=>{customTooltip(),isStudent&&intervention&&getModal()})),editor.on("keyDown",(editor=>{customTooltip();if(("v"===editor.key||"V"===editor.key)&&(editor.ctrlKey||editor.metaKey)&&isStudent&&intervention&&"block"===PASTE_SETTING&&!isPasteAllowed)return void setTimeout((()=>{isPasteAllowed=!0}),100);let position=getCaretPosition();editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,sendKeyEvent("keyDown",editor)})),editor.on("Cut",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("Copy",(()=>{const selectedContent=editor.selection.getContent({format:"text"});localStorage.setItem("lastCopyCutContent",selectedContent.trim())})),editor.on("mouseDown",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseDown",editor)}),0)})),editor.on("mouseUp",(async editor=>{setTimeout((()=>{constructMouseEvent(editor),sendKeyEvent("mouseUp",editor)}),10)})),editor.on("init",(()=>{customTooltip(),localStorage.removeItem("lastCopyCutContent")})),editor.on("SetContent",(()=>{customTooltip()})),editor.on("FullscreenStateChanged",(e=>{let view=new _document_view.default(user,Rubrics,submission,modulename,editor,quizInfo);isFullScreen=e.state;try{e.state?view.fullPageMode():view.normalMode()}catch(error){errorAlert&&(errorAlert=!1,(0,_str.get_string)("fullmodeerror","tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))),view.normalMode(),window.console.error("Error ResizeEditor event:",error)}})),editor.on("execcommand",(function(e){if("mceInsertContent"===e.command){const contentObj=e.value,isPaste=contentObj&&"object"==typeof contentObj&&!0===contentObj.paste;let insertedContent=contentObj.content||contentObj,tempDiv=document.createElement("div");tempDiv.innerHTML=insertedContent;let text=tempDiv.textContent||tempDiv.innerText||"",pastedText=tempDiv.textContent||tempDiv.innerText||"",position=getCaretPosition(!0);if(editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition,isPaste){if(shouldBlockPaste)return shouldBlockPaste=!1,e.preventDefault(),void editor.undoManager.undo();const lastCopyCutContent=localStorage.getItem("lastCopyCutContent"),isFromOwnEditor=lastCopyCutContent&&pastedText.trim()===lastCopyCutContent;if(isStudent&&intervention&&"block"===PASTE_SETTING&&!isFromOwnEditor)return isPasteAllowed=!1,void editor.undoManager.undo();sendKeyEvent("Paste",{key:"v",keyCode:86,caretPosition:editor.caretPosition,rePosition:editor.rePosition,pastedContent:pastedText,srcElement:{baseURI:window.location.href}})}else aiContents.push(text),sendKeyEvent("aiInsert",{key:"ai",keyCode:0,caretPosition:editor.caretPosition,rePosition:editor.rePosition,aiContent:text,srcElement:{baseURI:window.location.href}})}})),editor.on("input",(function(e){let position=getCaretPosition(!0);editor.caretPosition=position.caretPosition,editor.rePosition=position.rePosition;let aiContent=e.data;("insertReplacementText"===e.inputType||"insertText"===e.inputType&&aiContent&&aiContent.length>1)&&(aiContents.push(aiContent),e.key="ai",e.keyCode=0,e.caretPosition=position.caretPosition,e.rePosition=position.rePosition,e.aiContent=aiContent,sendKeyEvent("aiInsert",e))})),window.addEventListener("unload",(()=>{syncData()})),setInterval(syncData,syncInterval)}})); //# sourceMappingURL=autosaver.min.js.map \ No newline at end of file diff --git a/amd/build/autosaver.min.js.map b/amd/build/autosaver.min.js.map index e250a979..e0c9b4de 100644 --- a/amd/build/autosaver.min.js.map +++ b/amd/build/autosaver.min.js.map @@ -1,5 +1 @@ -<<<<<<< HEAD -{"version":3,"file":"autosaver.min.js","sources":["../src/autosaver.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module tiny_cursive/autosaver\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\nimport {call} from 'core/ajax';\nimport {create} from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\nimport {save, cancel, hidden} from 'core/modal_events';\nimport $ from 'jquery';\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\nimport Autosave from 'tiny_cursive/cursive_autosave';\nimport DocumentView from 'tiny_cursive/document_view';\nimport {call as getUser} from \"core/ajax\";\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\n\n var isStudent = !($('#body').hasClass('teacher_admin'));\n var intervention = $('#body').hasClass('intervention');\n var host = M.cfg.wwwroot;\n var userid = userId;\n var courseid = M.cfg.courseId;\n var editorid = editor?.id;\n var cmid = M.cfg.contextInstanceId;\n var ed = \"\";\n var event = \"\";\n var filename = \"\";\n var questionid = 0;\n var quizSubmit = $('#mod_quiz-next-nav');\n let assignSubmit = $('#id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let aiContents = [];\n var isFullScreen = false;\n var user = null;\n let ur = window.location.href;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n var resourceId = modulesInfo.resourceId;\n var modulename = modulesInfo.name;\n var errorAlert = true;\n let PASTE_SETTING = pasteSetting || 'allow';\n let shouldBlockPaste = false;\n let isPasteAllowed = false;\n\n if (ur.includes('pdfannotator')) {\n document.addEventListener('click', e => {\n if (e.target.className === \"dropdown-item comment-edit-a\") {\n let id = e.target.id;\n resourceId = id.replace('editButton', '');\n localStorage.setItem('isEditing', '1');\n }\n if (e.target.id === 'commentSubmit') {\n syncData();\n }\n });\n }\n\n const postOne = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n if (response) {\n setTimeout(() => {\n Autosave.updateSavingState('saved');\n }, 1000);\n }\n return response;\n } catch (error) {\n Autosave.updateSavingState('offline');\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n getUser([{\n methodname: 'core_user_get_users_by_field',\n args: {field: 'id', values: [userid]},\n }])[0].done(response => {\n user = response[0];\n }).fail((ex) => {\n window.console.error('Error fetching user data:', ex);\n });\n\n assignSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n assignSubmit.off('click').click();\n });\n } else {\n assignSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n quizSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n quizSubmit.off('click').click();\n });\n } else {\n quizSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n const getModal = () => {\n\n Promise.all([\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\n getString('tiny_cursive_placeholder', 'tiny_cursive')\n ]).then(function([title, titledes, placeholder]) {\n\n return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n })\n .done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n\n modal.getRoot().on(save, function() {\n\n var number = document.getElementById(\"inputUrl\").value.trim();\n\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n // eslint-disable-next-line\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\n }\n\n postOne('cursive_user_comments', {\n modulename: modulename,\n cmid: cmid,\n resourceid: resourceId,\n courseid: courseid,\n usercomment: number,\n timemodified: Date.now(),\n editorid: editorid ? editorid : \"\"\n });\n\n lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\n });\n\n modal.getRoot().on(hidden, function() {\n if (lastEvent != 'cancel' && lastEvent != 'save') {\n editor.execCommand('Undo');\n }\n });\n return modal;\n });\n }).catch(error => window.console.error(error));\n\n };\n\n const sendKeyEvent = (events, editor) => {\n ed = editor;\n event = events;\n\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\n editor.on('Paste', async(e) => {\n customTooltip();\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\n if (!pastedContent) {\n return;\n }\n // Trim both values for consistent comparison\n const trimmedPastedContent = pastedContent.trim();\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\n\n if (isStudent && intervention) {\n\n if (PASTE_SETTING === 'block') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n shouldBlockPaste = true;\n isPasteAllowed = false;\n e.stopPropagation();\n e.stopImmediatePropagation();\n getString('paste_blocked', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n setTimeout(() => {\n isPasteAllowed = true;\n shouldBlockPaste = false;\n }, 100);\n return;\n }\n shouldBlockPaste = false;\n isPasteAllowed = true;\n return;\n }\n if (PASTE_SETTING === 'cite_source') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n getModal(e);\n }\n isPasteAllowed = true;\n return;\n }\n }\n isPasteAllowed = true;\n });\n editor.on('Redo', async(e) => {\n customTooltip();\n if (isStudent && intervention) {\n getModal(e);\n }\n });\n editor.on('keyDown', (editor) => {\n customTooltip();\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\n (editor.ctrlKey || editor.metaKey);\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\n setTimeout(() => {\n isPasteAllowed = true;\n }, 100);\n return;\n }\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('mouseDown', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n }, 0);\n });\n editor.on('mouseUp', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n }, 10);\n });\n editor.on('init', () => {\n customTooltip();\n localStorage.removeItem('lastCopyCutContent');\n });\n editor.on('SetContent', () => {\n customTooltip();\n });\n editor.on('FullscreenStateChanged', (e) => {\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\n isFullScreen = e.state;\n try {\n if (!e.state) {\n view.normalMode();\n } else {\n view.fullPageMode();\n }\n } catch (error) {\n if (errorAlert) {\n errorAlert = false;\n getString('fullmodeerror', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n view.normalMode();\n window.console.error('Error ResizeEditor event:', error);\n }\n });\n\n editor.on('execcommand', function(e) {\n if (e.command === \"mceInsertContent\") {\n const contentObj = e.value;\n\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\n\n let insertedContent = contentObj.content || contentObj;\n let tempDiv = document.createElement('div');\n tempDiv.innerHTML = insertedContent;\n let text = tempDiv.textContent || tempDiv.innerText || '';\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\n\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n\n if (isPaste) {\n if (shouldBlockPaste) {\n shouldBlockPaste = false;\n e.preventDefault();\n editor.undoManager.undo();\n return;\n }\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\n\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\n isPasteAllowed = false;\n editor.undoManager.undo();\n return;\n }\n\n sendKeyEvent(\"Paste\", {\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n pastedContent: pastedText,\n srcElement: {baseURI: window.location.href}\n });\n } else {\n aiContents.push(text);\n\n sendKeyEvent(\"aiInsert\", {\n key: \"ai\",\n keyCode: 0,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n aiContent: text,\n srcElement: {baseURI: window.location.href}\n });\n }\n }\n });\n\n editor.on('input', function(e) {\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n let aiContent = e.data;\n\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\n\n aiContents.push(aiContent);\n\n e.key = \"ai\";\n e.keyCode = 0;\n e.caretPosition = position.caretPosition;\n e.rePosition = position.rePosition;\n e.aiContent = aiContent;\n\n sendKeyEvent(\"aiInsert\", e);\n }\n });\n\n\n /**\n * Constructs a mouse event object with caret position and button information\n * @param {Object} editor - The TinyMCE editor instance\n * @function constructMouseEvent\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\n */\n function constructMouseEvent(editor) {\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n editor.key = getMouseButton(editor);\n editor.keyCode = editor.button;\n }\n\n /**\n * Gets the string representation of a mouse button based on its numeric value\n * @param {Object} editor - The editor object containing button information\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\n */\n function getMouseButton(editor) {\n\n switch (editor.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n }\n return null;\n }\n\n /**\n * Gets the current caret position in the editor\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\n * @returns {Object} Object containing:\n * - caretPosition: Sequential position number stored in session\n * - rePosition: Absolute character offset from start of content\n * @throws {Error} Logs warning to console if error occurs during calculation\n */\n function getCaretPosition(skip = false) {\n try {\n if (!editor || !editor.selection) {\n return {caretPosition: 0, rePosition: 0};\n }\n\n const range = editor.selection.getRng();\n const body = editor.getBody();\n\n // Create a range from start of document to current caret\n const preCaretRange = range.cloneRange();\n preCaretRange.selectNodeContents(body);\n preCaretRange.setEnd(range.endContainer, range.endOffset);\n\n const fragment = preCaretRange.cloneContents();\n const tempDiv = document.createElement('div');\n tempDiv.appendChild(fragment);\n let textBeforeCursor = tempDiv.innerText || '';\n\n const endContainer = range.endContainer;\n const endOffset = range.endOffset;\n\n if (endOffset === 0 &&\n endContainer.nodeType === Node.ELEMENT_NODE &&\n editor.dom.isBlock(endContainer) &&\n endContainer.previousSibling) {\n textBeforeCursor += '\\n';\n }\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\n let emptyBlockCount = 0;\n blockElements.forEach(block => {\n const text = block.innerText || block.textContent || '';\n if (text.trim() === '' && block.childNodes.length === 1 &&\n block.childNodes[0].nodeName === 'BR') {\n emptyBlockCount++;\n }\n });\n\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\n if (emptyBlockCount > 0) {\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\n }\n\n const absolutePosition = textBeforeCursor.length;\n\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\n // Increment sequential caretPosition\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\n if (isNaN(storedPos)) {\n storedPos = 0;\n }\n storedPos++;\n lastCaretPos = storedPos;\n sessionStorage.setItem(storageKey, storedPos);\n\n return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\n }\n }\n\n\n /**\n * Synchronizes data from localStorage to server\n * @async\n * @function SyncData\n * @description Retrieves stored keypress data from localStorage and sends it to server\n * @returns {Promise} Returns response from server if data exists and is successfully sent\n * @throws {Error} Logs error to console if data submission fails\n */\n async function syncData() {\n checkIsPdfAnnotator();\n let data = localStorage.getItem(filename);\n\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n editor.fire('change');\n let originalText = editor.getContent({format: 'text'});\n if (!originalText) {\n originalText = getRawText(editor);\n }\n try {\n Autosave.updateSavingState('saving');\n // eslint-disable-next-line\n return await postOne('cursive_write_local_to_json', {\n key: ed.key,\n event: event,\n keyCode: ed.keyCode,\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n editorid: editorid,\n \"json_data\": data,\n originalText: originalText\n });\n } catch (error) {\n window.console.error('Error submitting data:', error);\n }\n }\n }\n\n /**\n * Gets the raw text content from a TinyMCE editor iframe\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {string} The raw text content of the editor body, or empty string if not found\n * @description Attempts to get the raw text content from the editor's iframe body by:\n * 1. Getting the editor ID\n * 2. Finding the associated iframe element\n * 3. Accessing the iframe's document body\n * 4. Returning the text content\n * Returns empty string if any step fails\n */\n function getRawText(editor) {\n let editorId = editor?.id;\n if (editorId) {\n let iframe = document.querySelector(`#${editorId}_ifr`);\n let iframeBody = iframe.contentDocument?.body || iframe.contentWindow?.document?.body;\n return iframeBody?.textContent;\n }\n return \"\";\n }\n\n /**\n * Sets up custom tooltip functionality for the Cursive icon\n * Initializes tooltip text, positions the icon in the menubar,\n * and sets up mouse event handlers for showing/hiding the tooltip\n * @function customTooltip\n */\n function customTooltip() {\n try {\n const tooltipText = getTooltipText();\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\n let classArray = [];\n\n if (menubarDiv.length) {\n menubarDiv.forEach(function(element, index) {\n index += 1;\n let className = 'cursive-menu-' + index;\n element.classList.add(className);\n classArray.push(className);\n });\n }\n\n const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n\n cursiveState(cursiveIcon, menubarDiv, classArray);\n\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n\n $(`#${elementId}`).on('mouseenter', function() {\n $(this).css('position', 'relative');\n $(`#${tooltipId}`).css(tooltipCss);\n });\n\n $(`#${elementId}`).on('mouseleave', function() {\n $(`#${tooltipId}`).css('display', 'none');\n });\n }\n } catch (error) {\n window.console.error('Error setting up custom tooltip:', error);\n }\n }\n\n /**\n * Retrieves tooltip text strings from language files\n * @async\n * @function getTooltipText\n * @returns {Promise} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [\n buttonTitle,\n buttonDes,\n ] = 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 return;\n }\n\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\n rightWrapper.style.cssText = `\n margin-left: auto;\n display: flex;\n align-items: center;\n `;\n\n imgWrapper.id = elementId;\n imgWrapper.style.marginLeft = '.2rem';\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n\n let moduleIds = {\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n questionid: questionid,\n userid: userid,\n courseid: courseid};\n // Document mode, other modules single editor instances\n if (isFullScreen && (modulename === 'assign' || modulename === 'forum'\n || modulename === 'lesson')) {\n let existsElement = document.querySelector('.tox-menubar[class*=\"cursive-menu-\"] > div');\n if (existsElement) {\n existsElement.remove();\n }\n\n if (!document.querySelector(`#${elementId}`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else if (isFullScreen && modulename === 'quiz') { // Document mode, quiz multiple editor instances\n let existingElement = editor.container?.childNodes[1]?.childNodes[0]?.childNodes[0]?.childNodes[7];\n let newHeader = editor.container?.childNodes[0];\n if (existingElement) {\n existingElement.remove();\n }\n\n if (newHeader && !newHeader.querySelector(`span[id*=tiny_cursive_StateIcon]`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else { // Regular view\n let menubar = editor?.container?.children[0]?.childNodes[0]?.childNodes[0];\n\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n // Regular view, multiple editor instances\n if (modulename === 'quiz' && menubar) {\n let wrapper = menubar.querySelector('span[id*=\"tiny_cursive_StateIcon\"]');\n\n if (wrapper) {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, wrapper?.parentElement, moduleIds, isFullScreen);\n }\n } else {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\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\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n\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\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\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 fetchStrings();\n\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\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\n if (resourceId === null) {\n resourceId = 0;\n }\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\n checkIsPdfAnnotator();\n\n return {resourceId: resourceId, name: modulename};\n }\n\n /**\n * Fetches and caches localized strings used in the UI\n * @function fetchStrings\n * @description Retrieves strings for sidebar titles and document sidebar elements if not already cached in localStorage\n * Uses Promise.all to fetch multiple strings in parallel for better performance\n * Stores the fetched strings in localStorage under 'sbTitle' and 'docSideBar' keys\n */\n function fetchStrings() {\n if (!localStorage.getItem('sbTitle')) {\n Promise.all([\n getString('assignment', 'tiny_cursive'),\n getString('discussion', 'tiny_cursive'),\n getString('pluginname', 'mod_quiz'),\n getString('pluginname', 'mod_lesson'),\n getString('description', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('sbTitle', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n if (!localStorage.getItem('docSideBar')) {\n Promise.all([\n getString('details', 'tiny_cursive'),\n getString('student_info', 'tiny_cursive'),\n getString('progress', 'tiny_cursive'),\n getString('description', 'tiny_cursive'),\n getString('replyingto', 'tiny_cursive'),\n getString('answeringto', 'tiny_cursive'),\n getString('importantdates', 'tiny_cursive'),\n getString('rubrics', 'tiny_cursive'),\n getString('submission_status', 'tiny_cursive'),\n getString('status', 'tiny_cursive'),\n getString('draft', 'tiny_cursive'),\n getString('draftnot', 'tiny_cursive'),\n getString('last_modified', 'tiny_cursive'),\n getString('gradings', 'tiny_cursive'),\n getString('gradenot', 'tiny_cursive'),\n getString('word_count', 'tiny_cursive'),\n getString('timeleft', 'tiny_cursive'),\n getString('nolimit', 'tiny_cursive'),\n getString('name', 'tiny_cursive'),\n getString('userename', 'tiny_cursive'),\n getString('course', 'tiny_cursive'),\n getString('opened', 'tiny_cursive'),\n getString('due', 'tiny_cursive'),\n getString('overdue', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n getString('savechanges', 'tiny_cursive'),\n getString('subjectnot', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('docSideBar', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n\n }\n\n /**\n * Checks if the current page is a PDF annotator and updates the resourceId accordingly\n * @function checkIsPdfAnnotator\n * @description Checks if URL contains 'pdfannotator' and sets resourceId based on editor ID and editing state:\n * - If editing an existing annotation (editor.id !== 'id_pdfannotator_content' and isEditing is true):\n * Sets resourceId to the annotation ID extracted from editor.id\n * - Otherwise: Sets resourceId to 0\n */\n function checkIsPdfAnnotator() {\n if (ur.includes('pdfannotator')) {\n if (editor.id !== 'id_pdfannotator_content' && parseInt(localStorage.getItem('isEditing'))) {\n resourceId = parseInt(editor?.id.replace('editarea', ''));\n } else {\n resourceId = 0;\n }\n }\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","Rubrics","submission","quizInfo","pasteSetting","isStudent","hasClass","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","questionid","quizSubmit","assignSubmit","syncInterval","lastCaretPos","aiContents","isFullScreen","user","ur","window","location","href","parm","URL","modulesInfo","localStorage","getItem","Promise","all","then","strings","setItem","JSON","stringify","catch","error","console","fetchStrings","some","module","includes","resourceId","searchParams","get","modulename","checkIsPdfAnnotator","name","getModulesInfo","errorAlert","PASTE_SETTING","shouldBlockPaste","isPasteAllowed","document","addEventListener","e","target","className","replace","syncData","postOne","async","methodname","args","response","setTimeout","updateSavingState","field","values","done","fail","ex","on","preventDefault","off","click","removeItem","getModal","title","titledes","placeholder","type","body","removeOnClose","modal","getRoot","addClass","show","lastEvent","save","number","getElementById","value","trim","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","sendKeyEvent","events","split","data","parse","push","key","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","aiContent","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","range","getRng","getBody","preCaretRange","cloneRange","selectNodeContents","setEnd","endContainer","endOffset","fragment","cloneContents","tempDiv","createElement","appendChild","textBeforeCursor","innerText","nodeType","Node","ELEMENT_NODE","dom","isBlock","previousSibling","blockElements","querySelectorAll","emptyBlockCount","forEach","block","textContent","childNodes","length","nodeName","repeat","absolutePosition","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","fire","originalText","getContent","format","editorId","iframe","querySelector","iframeBody","contentDocument","contentWindow","_iframe$contentWindow","_iframe$contentWindow2","getRawText","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","classArray","element","index","classList","add","cursiveIcon","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","elementId","cssText","marginLeft","moduleIds","existingElement","container","_editor$container","_editor$container$chi","_editor$container$chi2","_editor$container$chi3","newHeader","_editor$container2","remove","marginTop","prepend","destroyInstance","getInstance","menubar","_editor$container3","children","_editor$container3$ch","_editor$container3$ch2","wrapper","parentElement","existsElement","cursiveState","tooltipId","text","setTooltip","this","css","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","clipboardData","originalEvent","getData","trimmedPastedContent","lastCopyCutContent","isFromOwnEditor","stopPropagation","stopImmediatePropagation","windowManager","ctrlKey","metaKey","selectedContent","view","DocumentView","state","fullPageMode","normalMode","command","contentObj","isPaste","paste","insertedContent","content","innerHTML","pastedText","undoManager","undo","srcElement","baseURI","inputType","setInterval"],"mappings":"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"} -======= -{"version":3,"file":"autosaver.min.js","sources":["../src/autosaver.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module tiny_cursive/autosaver\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\nimport {call} from 'core/ajax';\nimport {create} from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\nimport {save, cancel, hidden} from 'core/modal_events';\nimport $ from 'jquery';\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\nimport Autosave from 'tiny_cursive/cursive_autosave';\nimport DocumentView from 'tiny_cursive/document_view';\nimport {call as getUser} from \"core/ajax\";\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\n\n var isStudent = !($('#body').hasClass('teacher_admin'));\n var intervention = $('#body').hasClass('intervention');\n var host = M.cfg.wwwroot;\n var userid = userId;\n var courseid = M.cfg.courseId;\n var editorid = editor?.id;\n var cmid = M.cfg.contextInstanceId;\n var ed = \"\";\n var event = \"\";\n var filename = \"\";\n var questionid = 0;\n var quizSubmit = $('#mod_quiz-next-nav');\n let assignSubmit = $('#id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let aiContents = [];\n var isFullScreen = false;\n var user = null;\n let ur = window.location.href;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n var resourceId = modulesInfo.resourceId;\n var modulename = modulesInfo.name;\n var errorAlert = true;\n let PASTE_SETTING = pasteSetting || 'allow';\n let shouldBlockPaste = false;\n let isPasteAllowed = false;\n\n if (modulename !== 'assign') {\n PASTE_SETTING = 'cite_source';\n }\n const postOne = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n if (response) {\n setTimeout(() => {\n Autosave.updateSavingState('saved');\n }, 1000);\n }\n return response;\n } catch (error) {\n Autosave.updateSavingState('offline');\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n getUser([{\n methodname: 'core_user_get_users_by_field',\n args: {field: 'id', values: [M.cfg.userId]},\n }])[0].done(response => {\n user = response[0];\n }).fail((ex) => {\n window.console.error('Error fetching user data:', ex);\n });\n\n assignSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n assignSubmit.off('click').click();\n });\n } else {\n assignSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n quizSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n quizSubmit.off('click').click();\n });\n } else {\n quizSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n const getModal = () => {\n\n Promise.all([\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\n getString('tiny_cursive_placeholder', 'tiny_cursive')\n ]).then(function([title, titledes, placeholder]) {\n\n return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n })\n .done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n\n modal.getRoot().on(save, function() {\n\n var number = document.getElementById(\"inputUrl\").value.trim();\n\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n // eslint-disable-next-line\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\n }\n\n postOne('cursive_user_comments', {\n modulename: modulename,\n cmid: cmid,\n resourceid: resourceId,\n courseid: courseid,\n usercomment: number,\n timemodified: Date.now(),\n editorid: editorid ? editorid : \"\"\n });\n\n lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\n });\n\n modal.getRoot().on(hidden, function() {\n if (lastEvent != 'cancel' && lastEvent != 'save') {\n editor.execCommand('Undo');\n }\n });\n return modal;\n });\n }).catch(error => window.console.error(error));\n\n };\n\n const sendKeyEvent = (events, editor) => {\n ed = editor;\n event = events;\n\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\n editor.on('Paste', async(e) => {\n customTooltip();\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\n if (!pastedContent) {\n return;\n }\n // Trim both values for consistent comparison\n const trimmedPastedContent = pastedContent.trim();\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\n\n if (isStudent && intervention) {\n\n if (PASTE_SETTING === 'block') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n shouldBlockPaste = true;\n isPasteAllowed = false;\n e.stopPropagation();\n e.stopImmediatePropagation();\n getString('paste_blocked', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n setTimeout(() => {\n isPasteAllowed = true;\n shouldBlockPaste = false;\n }, 100);\n return;\n }\n shouldBlockPaste = false;\n isPasteAllowed = true;\n return;\n }\n if (PASTE_SETTING === 'cite_source') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n getModal(e);\n }\n isPasteAllowed = true;\n return;\n }\n }\n isPasteAllowed = true;\n });\n editor.on('Redo', async(e) => {\n customTooltip();\n if (isStudent && intervention) {\n getModal(e);\n }\n });\n editor.on('keyDown', (editor) => {\n customTooltip();\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\n (editor.ctrlKey || editor.metaKey);\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\n setTimeout(() => {\n isPasteAllowed = true;\n }, 100);\n return;\n }\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('mouseDown', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n }, 0);\n });\n editor.on('mouseUp', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n }, 10);\n });\n editor.on('init', () => {\n customTooltip();\n localStorage.removeItem('lastCopyCutContent');\n });\n editor.on('SetContent', () => {\n customTooltip();\n });\n editor.on('FullscreenStateChanged', (e) => {\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\n isFullScreen = e.state;\n try {\n if (!e.state) {\n view.normalMode();\n } else {\n view.fullPageMode();\n }\n } catch (error) {\n if (errorAlert) {\n errorAlert = false;\n getString('fullmodeerror', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n view.normalMode();\n window.console.error('Error ResizeEditor event:', error);\n }\n });\n\n editor.on('execcommand', function(e) {\n if (e.command === \"mceInsertContent\") {\n const contentObj = e.value;\n\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\n\n let insertedContent = contentObj.content || contentObj;\n let tempDiv = document.createElement('div');\n tempDiv.innerHTML = insertedContent;\n let text = tempDiv.textContent || tempDiv.innerText || '';\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\n\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n\n if (isPaste) {\n if (shouldBlockPaste) {\n shouldBlockPaste = false;\n e.preventDefault();\n editor.undoManager.undo();\n return;\n }\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\n\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\n isPasteAllowed = false;\n editor.undoManager.undo();\n return;\n }\n\n sendKeyEvent(\"Paste\", {\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n pastedContent: pastedText,\n srcElement: {baseURI: window.location.href}\n });\n } else {\n aiContents.push(text);\n\n sendKeyEvent(\"aiInsert\", {\n key: \"ai\",\n keyCode: 0,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n aiContent: text,\n srcElement: {baseURI: window.location.href}\n });\n }\n }\n });\n\n editor.on('input', function(e) {\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n let aiContent = e.data;\n\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\n\n aiContents.push(aiContent);\n\n e.key = \"ai\";\n e.keyCode = 0;\n e.caretPosition = position.caretPosition;\n e.rePosition = position.rePosition;\n e.aiContent = aiContent;\n\n sendKeyEvent(\"aiInsert\", e);\n }\n });\n\n\n /**\n * Constructs a mouse event object with caret position and button information\n * @param {Object} editor - The TinyMCE editor instance\n * @function constructMouseEvent\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\n */\n function constructMouseEvent(editor) {\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n editor.key = getMouseButton(editor);\n editor.keyCode = editor.button;\n }\n\n /**\n * Gets the string representation of a mouse button based on its numeric value\n * @param {Object} editor - The editor object containing button information\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\n */\n function getMouseButton(editor) {\n\n switch (editor.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n }\n return null;\n }\n\n /**\n * Gets the current caret position in the editor\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\n * @returns {Object} Object containing:\n * - caretPosition: Sequential position number stored in session\n * - rePosition: Absolute character offset from start of content\n * @throws {Error} Logs warning to console if error occurs during calculation\n */\n function getCaretPosition(skip = false) {\n try {\n if (!editor || !editor.selection) {\n return {caretPosition: 0, rePosition: 0};\n }\n\n const range = editor.selection.getRng();\n const body = editor.getBody();\n\n // Create a range from start of document to current caret\n const preCaretRange = range.cloneRange();\n preCaretRange.selectNodeContents(body);\n preCaretRange.setEnd(range.endContainer, range.endOffset);\n\n const fragment = preCaretRange.cloneContents();\n const tempDiv = document.createElement('div');\n tempDiv.appendChild(fragment);\n let textBeforeCursor = tempDiv.innerText || '';\n\n const endContainer = range.endContainer;\n const endOffset = range.endOffset;\n\n if (endOffset === 0 &&\n endContainer.nodeType === Node.ELEMENT_NODE &&\n editor.dom.isBlock(endContainer) &&\n endContainer.previousSibling) {\n textBeforeCursor += '\\n';\n }\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\n let emptyBlockCount = 0;\n blockElements.forEach(block => {\n const text = block.innerText || block.textContent || '';\n if (text.trim() === '' && block.childNodes.length === 1 &&\n block.childNodes[0].nodeName === 'BR') {\n emptyBlockCount++;\n }\n });\n\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\n if (emptyBlockCount > 0) {\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\n }\n\n const absolutePosition = textBeforeCursor.length;\n\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\n // Increment sequential caretPosition\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\n if (isNaN(storedPos)) {\n storedPos = 0;\n }\n storedPos++;\n lastCaretPos = storedPos;\n sessionStorage.setItem(storageKey, storedPos);\n\n return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\n }\n }\n\n\n /**\n * Synchronizes data from localStorage to server\n * @async\n * @function SyncData\n * @description Retrieves stored keypress data from localStorage and sends it to server\n * @returns {Promise} Returns response from server if data exists and is successfully sent\n * @throws {Error} Logs error to console if data submission fails\n */\n async function syncData() {\n\n let data = localStorage.getItem(filename);\n\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n let originalText = editor.getContent({format: 'text'});\n try {\n Autosave.updateSavingState('saving');\n // eslint-disable-next-line\n return await postOne('cursive_write_local_to_json', {\n key: ed.key,\n event: event,\n keyCode: ed.keyCode,\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n editorid: editorid,\n \"json_data\": data,\n originalText: originalText\n });\n } catch (error) {\n window.console.error('Error submitting data:', error);\n }\n }\n }\n /**\n * Sets up custom tooltip functionality for the Cursive icon\n * Initializes tooltip text, positions the icon in the menubar,\n * and sets up mouse event handlers for showing/hiding the tooltip\n * @function customTooltip\n */\n function customTooltip() {\n try {\n const tooltipText = getTooltipText();\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\n let classArray = [];\n\n if (menubarDiv.length) {\n menubarDiv.forEach(function(element, index) {\n index += 1;\n let className = 'cursive-menu-' + index;\n element.classList.add(className);\n classArray.push(className);\n });\n }\n\n const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n\n cursiveState(cursiveIcon, menubarDiv, classArray);\n\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n\n $(`#${elementId}`).on('mouseenter', function() {\n $(this).css('position', 'relative');\n $(`#${tooltipId}`).css(tooltipCss);\n });\n\n $(`#${elementId}`).on('mouseleave', function() {\n $(`#${tooltipId}`).css('display', 'none');\n });\n }\n } catch (error) {\n window.console.error('Error setting up custom tooltip:', error);\n }\n }\n\n /**\n * Retrieves tooltip text strings from language files\n * @async\n * @function getTooltipText\n * @returns {Promise} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [\n buttonTitle,\n buttonDes,\n ] = 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 return;\n }\n\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\n rightWrapper.style.cssText = `\n margin-left: auto;\n display: flex;\n align-items: center;\n `;\n\n imgWrapper.id = elementId;\n imgWrapper.style.marginLeft = '.2rem';\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n\n let moduleIds = {\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n questionid: questionid,\n userid: userid,\n courseid: courseid};\n // Document mode, other modules single editor instances\n if (isFullScreen && (modulename === 'assign' || modulename === 'forum'\n || modulename === 'lesson')) {\n let existsElement = document.querySelector('.tox-menubar[class*=\"cursive-menu-\"] > div');\n if (existsElement) {\n existsElement.remove();\n }\n\n if (!document.querySelector(`#${elementId}`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else if (isFullScreen && modulename === 'quiz') { // Document mode, quiz multiple editor instances\n let existingElement = editor.container?.childNodes[1]?.childNodes[0]?.childNodes[0]?.childNodes[7];\n let newHeader = editor.container?.childNodes[0];\n if (existingElement) {\n existingElement.remove();\n }\n\n if (newHeader && !newHeader.querySelector(`span[id*=tiny_cursive_StateIcon]`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else { // Regular view\n let menubar = editor?.container?.children[0]?.childNodes[0]?.childNodes[0];\n\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n // Regular view, multiple editor instances\n if (modulename === 'quiz' && menubar) {\n let wrapper = menubar.querySelector('span[id*=\"tiny_cursive_StateIcon\"]');\n\n if (wrapper) {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, wrapper?.parentElement, moduleIds, isFullScreen);\n }\n } else {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\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\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n\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\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\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 fetchStrings();\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\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\n if (resourceId === null) {\n resourceId = 0;\n }\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\n return {resourceId: resourceId, name: modulename};\n }\n\n /**\n * Fetches and caches localized strings used in the UI\n * @function fetchStrings\n * @description Retrieves strings for sidebar titles and document sidebar elements if not already cached in localStorage\n * Uses Promise.all to fetch multiple strings in parallel for better performance\n * Stores the fetched strings in localStorage under 'sbTitle' and 'docSideBar' keys\n */\n function fetchStrings() {\n if (!localStorage.getItem('sbTitle')) {\n Promise.all([\n getString('assignment', 'tiny_cursive'),\n getString('discussion', 'tiny_cursive'),\n getString('pluginname', 'mod_quiz'),\n getString('pluginname', 'mod_lesson'),\n getString('description', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('sbTitle', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n if (!localStorage.getItem('docSideBar')) {\n Promise.all([\n getString('details', 'tiny_cursive'),\n getString('student_info', 'tiny_cursive'),\n getString('progress', 'tiny_cursive'),\n getString('description', 'tiny_cursive'),\n getString('replyingto', 'tiny_cursive'),\n getString('answeringto', 'tiny_cursive'),\n getString('importantdates', 'tiny_cursive'),\n getString('rubrics', 'tiny_cursive'),\n getString('submission_status', 'tiny_cursive'),\n getString('status', 'tiny_cursive'),\n getString('draft', 'tiny_cursive'),\n getString('draftnot', 'tiny_cursive'),\n getString('last_modified', 'tiny_cursive'),\n getString('gradings', 'tiny_cursive'),\n getString('gradenot', 'tiny_cursive'),\n getString('word_count', 'tiny_cursive'),\n getString('timeleft', 'tiny_cursive'),\n getString('nolimit', 'tiny_cursive'),\n getString('name', 'tiny_cursive'),\n getString('userename', 'tiny_cursive'),\n getString('course', 'tiny_cursive'),\n getString('opened', 'tiny_cursive'),\n getString('due', 'tiny_cursive'),\n getString('overdue', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n getString('savechanges', 'tiny_cursive'),\n getString('subjectnot', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('docSideBar', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","Rubrics","submission","quizInfo","pasteSetting","isStudent","hasClass","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","questionid","quizSubmit","assignSubmit","syncInterval","lastCaretPos","aiContents","isFullScreen","user","ur","window","location","href","modulesInfo","parm","localStorage","getItem","Promise","all","then","strings","setItem","JSON","stringify","catch","error","console","fetchStrings","some","module","includes","resourceId","searchParams","get","modulename","name","getModulesInfo","URL","errorAlert","PASTE_SETTING","shouldBlockPaste","isPasteAllowed","postOne","async","methodname","args","response","setTimeout","updateSavingState","field","values","done","fail","ex","on","e","preventDefault","syncData","off","click","removeItem","getModal","title","titledes","placeholder","type","body","removeOnClose","modal","getRoot","addClass","show","lastEvent","save","number","document","getElementById","value","trim","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","sendKeyEvent","events","split","data","parse","push","key","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","aiContent","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","range","getRng","getBody","preCaretRange","cloneRange","selectNodeContents","setEnd","endContainer","endOffset","fragment","cloneContents","tempDiv","createElement","appendChild","textBeforeCursor","innerText","nodeType","Node","ELEMENT_NODE","dom","isBlock","previousSibling","blockElements","querySelectorAll","emptyBlockCount","forEach","block","textContent","childNodes","length","nodeName","repeat","absolutePosition","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","originalText","getContent","format","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","classArray","element","index","className","classList","add","cursiveIcon","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","querySelector","elementId","cssText","marginLeft","moduleIds","existingElement","container","_editor$container","_editor$container$chi","_editor$container$chi2","_editor$container$chi3","newHeader","_editor$container2","remove","marginTop","prepend","destroyInstance","getInstance","menubar","_editor$container3","children","_editor$container3$ch","_editor$container3$ch2","wrapper","parentElement","existsElement","cursiveState","tooltipId","text","setTooltip","this","css","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","clipboardData","originalEvent","getData","trimmedPastedContent","lastCopyCutContent","isFromOwnEditor","stopPropagation","stopImmediatePropagation","windowManager","ctrlKey","metaKey","selectedContent","view","DocumentView","state","fullPageMode","normalMode","command","contentObj","isPaste","paste","insertedContent","content","innerHTML","pastedText","undoManager","undo","srcElement","baseURI","inputType","addEventListener","setInterval"],"mappings":"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"} ->>>>>>> 8b275778ba140d4ec01f088ecfc426293a0a3296 +{"version":3,"file":"autosaver.min.js","sources":["../src/autosaver.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module tiny_cursive/autosaver\n * @category TinyMCE Editor\n * @copyright CTI \n * @author Brain Station 23 \n */\n\nimport {call} from 'core/ajax';\nimport {create} from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\nimport {save, cancel, hidden} from 'core/modal_events';\nimport $ from 'jquery';\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\nimport Autosave from 'tiny_cursive/cursive_autosave';\nimport DocumentView from 'tiny_cursive/document_view';\nimport {call as getUser} from \"core/ajax\";\n\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\n\n var isStudent = !($('#body').hasClass('teacher_admin'));\n var intervention = $('#body').hasClass('intervention');\n var host = M.cfg.wwwroot;\n var userid = userId;\n var courseid = M.cfg.courseId;\n var editorid = editor?.id;\n var cmid = M.cfg.contextInstanceId;\n var ed = \"\";\n var event = \"\";\n var filename = \"\";\n var questionid = 0;\n var quizSubmit = $('#mod_quiz-next-nav');\n let assignSubmit = $('#id_submitbutton');\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\n var lastCaretPos = 1;\n let aiContents = [];\n var isFullScreen = false;\n var user = null;\n let ur = window.location.href;\n let parm = new URL(ur);\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\n var resourceId = modulesInfo.resourceId;\n var modulename = modulesInfo.name;\n var errorAlert = true;\n let PASTE_SETTING = pasteSetting || 'allow';\n let shouldBlockPaste = false;\n let isPasteAllowed = false;\n\n if (ur.includes('pdfannotator')) {\n document.addEventListener('click', e => {\n if (e.target.className === \"dropdown-item comment-edit-a\") {\n let id = e.target.id;\n resourceId = id.replace('editButton', '');\n localStorage.setItem('isEditing', '1');\n }\n if (e.target.id === 'commentSubmit') {\n syncData();\n }\n });\n }\n\n const postOne = async(methodname, args) => {\n try {\n const response = await call([{\n methodname,\n args,\n }])[0];\n if (response) {\n setTimeout(() => {\n Autosave.updateSavingState('saved');\n }, 1000);\n }\n return response;\n } catch (error) {\n Autosave.updateSavingState('offline');\n window.console.error('Error in postOne:', error);\n throw error;\n }\n };\n\n getUser([{\n methodname: 'core_user_get_users_by_field',\n args: {field: 'id', values: [userid]},\n }])[0].done(response => {\n user = response[0];\n }).fail((ex) => {\n window.console.error('Error fetching user data:', ex);\n });\n\n assignSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n assignSubmit.off('click').click();\n });\n } else {\n assignSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n quizSubmit.on('click', async function(e) {\n e.preventDefault();\n if (filename) {\n // eslint-disable-next-line\n syncData().then(() => {\n quizSubmit.off('click').click();\n });\n } else {\n quizSubmit.off('click').click();\n }\n localStorage.removeItem('lastCopyCutContent');\n });\n\n const getModal = () => {\n\n Promise.all([\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\n getString('tiny_cursive_placeholder', 'tiny_cursive')\n ]).then(function([title, titledes, placeholder]) {\n\n return create({\n type: 'SAVE_CANCEL',\n title: `
${title}
\n ${titledes}
`,\n body: ``,\n removeOnClose: true,\n })\n .done(modal => {\n modal.getRoot().addClass('tiny-cursive-modal');\n modal.show();\n var lastEvent = '';\n\n modal.getRoot().on(save, function() {\n\n var number = document.getElementById(\"inputUrl\").value.trim();\n\n if (number === \"\" || number === null || number === undefined) {\n editor.execCommand('Undo');\n // eslint-disable-next-line\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\n } else {\n editor.execCommand('Paste');\n }\n\n postOne('cursive_user_comments', {\n modulename: modulename,\n cmid: cmid,\n resourceid: resourceId,\n courseid: courseid,\n usercomment: number,\n timemodified: Date.now(),\n editorid: editorid ? editorid : \"\"\n });\n\n lastEvent = 'save';\n modal.destroy();\n });\n modal.getRoot().on(cancel, function() {\n editor.execCommand('Undo');\n lastEvent = 'cancel';\n });\n\n modal.getRoot().on(hidden, function() {\n if (lastEvent != 'cancel' && lastEvent != 'save') {\n editor.execCommand('Undo');\n }\n });\n return modal;\n });\n }).catch(error => window.console.error(error));\n\n };\n\n const sendKeyEvent = (events, editor) => {\n ed = editor;\n event = events;\n\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\n\n if (modulename === 'quiz') {\n questionid = editorid.split(':')[1].split('_')[0];\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\n }\n\n if (localStorage.getItem(filename)) {\n let data = JSON.parse(localStorage.getItem(filename));\n data.push({\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n });\n localStorage.setItem(filename, JSON.stringify(data));\n } else {\n let data = [{\n resourceId: resourceId,\n key: editor.key,\n keyCode: editor.keyCode,\n event: event,\n courseId: courseid,\n unixTimestamp: Date.now(),\n clientId: host,\n personId: userid,\n position: ed.caretPosition,\n rePosition: ed.rePosition,\n pastedContent: editor.pastedContent,\n aiContent: editor.aiContent\n }];\n localStorage.setItem(filename, JSON.stringify(data));\n }\n };\n\n editor.on('keyUp', (editor) => {\n customTooltip();\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyUp\", editor);\n });\n editor.on('Paste', async(e) => {\n customTooltip();\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\n if (!pastedContent) {\n return;\n }\n // Trim both values for consistent comparison\n const trimmedPastedContent = pastedContent.trim();\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\n\n if (isStudent && intervention) {\n\n if (PASTE_SETTING === 'block') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n shouldBlockPaste = true;\n isPasteAllowed = false;\n e.stopPropagation();\n e.stopImmediatePropagation();\n getString('paste_blocked', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n setTimeout(() => {\n isPasteAllowed = true;\n shouldBlockPaste = false;\n }, 100);\n return;\n }\n shouldBlockPaste = false;\n isPasteAllowed = true;\n return;\n }\n if (PASTE_SETTING === 'cite_source') {\n if (!isFromOwnEditor) {\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n getModal(e);\n }\n isPasteAllowed = true;\n return;\n }\n }\n isPasteAllowed = true;\n });\n editor.on('Redo', async(e) => {\n customTooltip();\n if (isStudent && intervention) {\n getModal(e);\n }\n });\n editor.on('keyDown', (editor) => {\n customTooltip();\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\n (editor.ctrlKey || editor.metaKey);\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\n setTimeout(() => {\n isPasteAllowed = true;\n }, 100);\n return;\n }\n let position = getCaretPosition();\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n sendKeyEvent(\"keyDown\", editor);\n });\n editor.on('Cut', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('Copy', () => {\n const selectedContent = editor.selection.getContent({format: 'text'});\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\n });\n editor.on('mouseDown', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseDown\", editor);\n }, 0);\n });\n editor.on('mouseUp', async(editor) => {\n setTimeout(() => {\n constructMouseEvent(editor);\n sendKeyEvent(\"mouseUp\", editor);\n }, 10);\n });\n editor.on('init', () => {\n customTooltip();\n localStorage.removeItem('lastCopyCutContent');\n });\n editor.on('SetContent', () => {\n customTooltip();\n });\n editor.on('FullscreenStateChanged', (e) => {\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\n isFullScreen = e.state;\n try {\n if (!e.state) {\n view.normalMode();\n } else {\n view.fullPageMode();\n }\n } catch (error) {\n if (errorAlert) {\n errorAlert = false;\n getString('fullmodeerror', 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n view.normalMode();\n window.console.error('Error ResizeEditor event:', error);\n }\n });\n\n editor.on('execcommand', function(e) {\n if (e.command === \"mceInsertContent\") {\n const contentObj = e.value;\n\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\n\n let insertedContent = contentObj.content || contentObj;\n let tempDiv = document.createElement('div');\n tempDiv.innerHTML = insertedContent;\n let text = tempDiv.textContent || tempDiv.innerText || '';\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\n\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n\n if (isPaste) {\n if (shouldBlockPaste) {\n shouldBlockPaste = false;\n e.preventDefault();\n editor.undoManager.undo();\n return;\n }\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\n\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\n isPasteAllowed = false;\n editor.undoManager.undo();\n return;\n }\n\n sendKeyEvent(\"Paste\", {\n key: \"v\",\n keyCode: 86,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n pastedContent: pastedText,\n srcElement: {baseURI: window.location.href}\n });\n } else {\n aiContents.push(text);\n\n sendKeyEvent(\"aiInsert\", {\n key: \"ai\",\n keyCode: 0,\n caretPosition: editor.caretPosition,\n rePosition: editor.rePosition,\n aiContent: text,\n srcElement: {baseURI: window.location.href}\n });\n }\n }\n });\n\n editor.on('input', function(e) {\n let position = getCaretPosition(true);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n let aiContent = e.data;\n\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\n\n aiContents.push(aiContent);\n\n e.key = \"ai\";\n e.keyCode = 0;\n e.caretPosition = position.caretPosition;\n e.rePosition = position.rePosition;\n e.aiContent = aiContent;\n\n sendKeyEvent(\"aiInsert\", e);\n }\n });\n\n\n /**\n * Constructs a mouse event object with caret position and button information\n * @param {Object} editor - The TinyMCE editor instance\n * @function constructMouseEvent\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\n */\n function constructMouseEvent(editor) {\n let position = getCaretPosition(false);\n editor.caretPosition = position.caretPosition;\n editor.rePosition = position.rePosition;\n editor.key = getMouseButton(editor);\n editor.keyCode = editor.button;\n }\n\n /**\n * Gets the string representation of a mouse button based on its numeric value\n * @param {Object} editor - The editor object containing button information\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\n */\n function getMouseButton(editor) {\n\n switch (editor.button) {\n case 0:\n return 'left';\n case 1:\n return 'middle';\n case 2:\n return 'right';\n }\n return null;\n }\n\n /**\n * Gets the current caret position in the editor\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\n * @returns {Object} Object containing:\n * - caretPosition: Sequential position number stored in session\n * - rePosition: Absolute character offset from start of content\n * @throws {Error} Logs warning to console if error occurs during calculation\n */\n function getCaretPosition(skip = false) {\n try {\n if (!editor || !editor.selection) {\n return {caretPosition: 0, rePosition: 0};\n }\n\n const range = editor.selection.getRng();\n const body = editor.getBody();\n\n // Create a range from start of document to current caret\n const preCaretRange = range.cloneRange();\n preCaretRange.selectNodeContents(body);\n preCaretRange.setEnd(range.endContainer, range.endOffset);\n\n const fragment = preCaretRange.cloneContents();\n const tempDiv = document.createElement('div');\n tempDiv.appendChild(fragment);\n let textBeforeCursor = tempDiv.innerText || '';\n\n const endContainer = range.endContainer;\n const endOffset = range.endOffset;\n\n if (endOffset === 0 &&\n endContainer.nodeType === Node.ELEMENT_NODE &&\n editor.dom.isBlock(endContainer) &&\n endContainer.previousSibling) {\n textBeforeCursor += '\\n';\n }\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\n let emptyBlockCount = 0;\n blockElements.forEach(block => {\n const text = block.innerText || block.textContent || '';\n if (text.trim() === '' && block.childNodes.length === 1 &&\n block.childNodes[0].nodeName === 'BR') {\n emptyBlockCount++;\n }\n });\n\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\n if (emptyBlockCount > 0) {\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\n }\n\n const absolutePosition = textBeforeCursor.length;\n\n if (skip) {\n return {\n caretPosition: lastCaretPos,\n rePosition: absolutePosition\n };\n }\n // Increment sequential caretPosition\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\n if (isNaN(storedPos)) {\n storedPos = 0;\n }\n storedPos++;\n lastCaretPos = storedPos;\n sessionStorage.setItem(storageKey, storedPos);\n\n return {\n caretPosition: storedPos,\n rePosition: absolutePosition\n };\n\n } catch (e) {\n window.console.warn('Error getting caret position:', e);\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\n }\n }\n\n\n /**\n * Synchronizes data from localStorage to server\n * @async\n * @function SyncData\n * @description Retrieves stored keypress data from localStorage and sends it to server\n * @returns {Promise} Returns response from server if data exists and is successfully sent\n * @throws {Error} Logs error to console if data submission fails\n */\n async function syncData() {\n checkIsPdfAnnotator();\n let data = localStorage.getItem(filename);\n\n if (!data || data.length === 0) {\n return;\n } else {\n localStorage.removeItem(filename);\n editor.fire('change');\n let originalText = editor.getContent({format: 'text'});\n if (!originalText) {\n originalText = getRawText(editor);\n }\n try {\n Autosave.updateSavingState('saving');\n // eslint-disable-next-line\n return await postOne('cursive_write_local_to_json', {\n key: ed.key,\n event: event,\n keyCode: ed.keyCode,\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n editorid: editorid,\n \"json_data\": data,\n originalText: originalText\n });\n } catch (error) {\n window.console.error('Error submitting data:', error);\n }\n }\n }\n\n /**\n * Gets the raw text content from a TinyMCE editor iframe\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {string} The raw text content of the editor body, or empty string if not found\n * @description Attempts to get the raw text content from the editor's iframe body by:\n * 1. Getting the editor ID\n * 2. Finding the associated iframe element\n * 3. Accessing the iframe's document body\n * 4. Returning the text content\n * Returns empty string if any step fails\n */\n function getRawText(editor) {\n let editorId = editor?.id;\n if (editorId) {\n let iframe = document.querySelector(`#${editorId}_ifr`);\n let iframeBody = iframe.contentDocument?.body || iframe.contentWindow?.document?.body;\n return iframeBody?.textContent;\n }\n return \"\";\n }\n\n /**\n * Sets up custom tooltip functionality for the Cursive icon\n * Initializes tooltip text, positions the icon in the menubar,\n * and sets up mouse event handlers for showing/hiding the tooltip\n * @function customTooltip\n */\n function customTooltip() {\n try {\n const tooltipText = getTooltipText();\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\n let classArray = [];\n\n if (menubarDiv.length) {\n menubarDiv.forEach(function(element, index) {\n index += 1;\n let className = 'cursive-menu-' + index;\n element.classList.add(className);\n classArray.push(className);\n });\n }\n\n const cursiveIcon = document.createElement('img');\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\n\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\n cursiveIcon.style.display = 'inline-block';\n\n cursiveState(cursiveIcon, menubarDiv, classArray);\n\n for (let index in classArray) {\n const elementId = \"tiny_cursive_StateIcon\" + index;\n const tooltipId = `tiny_cursive_tooltip${index}`;\n\n tooltipText.then((text) => {\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\n }).catch(error => window.console.error(error));\n\n $(`#${elementId}`).on('mouseenter', function() {\n $(this).css('position', 'relative');\n $(`#${tooltipId}`).css(tooltipCss);\n });\n\n $(`#${elementId}`).on('mouseleave', function() {\n $(`#${tooltipId}`).css('display', 'none');\n });\n }\n } catch (error) {\n window.console.error('Error setting up custom tooltip:', error);\n }\n }\n\n /**\n * Retrieves tooltip text strings from language files\n * @async\n * @function getTooltipText\n * @returns {Promise} Object containing buttonTitle and buttonDes strings\n */\n async function getTooltipText() {\n const [\n buttonTitle,\n buttonDes,\n ] = 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 return;\n }\n\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\n rightWrapper.style.cssText = `\n margin-left: auto;\n display: flex;\n align-items: center;\n `;\n\n imgWrapper.id = elementId;\n imgWrapper.style.marginLeft = '.2rem';\n imgWrapper.appendChild(iconClone);\n rightWrapper.appendChild(imgWrapper);\n\n let moduleIds = {\n resourceId: resourceId,\n cmid: cmid,\n modulename: modulename,\n questionid: questionid,\n userid: userid,\n courseid: courseid};\n // Document mode, other modules single editor instances\n if (isFullScreen && (modulename === 'assign' || modulename === 'forum'\n || modulename === 'lesson')) {\n let existsElement = document.querySelector('.tox-menubar[class*=\"cursive-menu-\"] > div');\n if (existsElement) {\n existsElement.remove();\n }\n\n if (!document.querySelector(`#${elementId}`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else if (isFullScreen && modulename === 'quiz') { // Document mode, quiz multiple editor instances\n let existingElement = editor.container?.childNodes[1]?.childNodes[0]?.childNodes[0]?.childNodes[7];\n let newHeader = editor.container?.childNodes[0];\n if (existingElement) {\n existingElement.remove();\n }\n\n if (newHeader && !newHeader.querySelector(`span[id*=tiny_cursive_StateIcon]`)) {\n rightWrapper.style.marginTop = '3px';\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\n }\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\n } else { // Regular view\n let menubar = editor?.container?.children[0]?.childNodes[0]?.childNodes[0];\n\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\n targetMenu.appendChild(rightWrapper);\n }\n // Regular view, multiple editor instances\n if (modulename === 'quiz' && menubar) {\n let wrapper = menubar.querySelector('span[id*=\"tiny_cursive_StateIcon\"]');\n\n if (wrapper) {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, wrapper?.parentElement, moduleIds, isFullScreen);\n }\n } else {\n Autosave.destroyInstance();\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\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\n if (document.querySelector(`#${tooltipId}`)) {\n return;\n }\n if (cursiveIcon) {\n\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\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\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 fetchStrings();\n\n if (!MODULES.some(module => ur.includes(module))) {\n return false;\n }\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\n if (resourceId === null) {\n resourceId = 0;\n }\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\n checkIsPdfAnnotator();\n\n return {resourceId: resourceId, name: modulename};\n }\n\n /**\n * Fetches and caches localized strings used in the UI\n * @function fetchStrings\n * @description Retrieves strings for sidebar titles and document sidebar elements if not already cached in localStorage\n * Uses Promise.all to fetch multiple strings in parallel for better performance\n * Stores the fetched strings in localStorage under 'sbTitle' and 'docSideBar' keys\n */\n function fetchStrings() {\n if (!localStorage.getItem('sbTitle')) {\n Promise.all([\n getString('assignment', 'tiny_cursive'),\n getString('discussion', 'tiny_cursive'),\n getString('pluginname', 'mod_quiz'),\n getString('pluginname', 'mod_lesson'),\n getString('description', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('sbTitle', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n if (!localStorage.getItem('docSideBar')) {\n Promise.all([\n getString('details', 'tiny_cursive'),\n getString('student_info', 'tiny_cursive'),\n getString('progress', 'tiny_cursive'),\n getString('description', 'tiny_cursive'),\n getString('replyingto', 'tiny_cursive'),\n getString('answeringto', 'tiny_cursive'),\n getString('importantdates', 'tiny_cursive'),\n getString('rubrics', 'tiny_cursive'),\n getString('submission_status', 'tiny_cursive'),\n getString('status', 'tiny_cursive'),\n getString('draft', 'tiny_cursive'),\n getString('draftnot', 'tiny_cursive'),\n getString('last_modified', 'tiny_cursive'),\n getString('gradings', 'tiny_cursive'),\n getString('gradenot', 'tiny_cursive'),\n getString('word_count', 'tiny_cursive'),\n getString('timeleft', 'tiny_cursive'),\n getString('nolimit', 'tiny_cursive'),\n getString('name', 'tiny_cursive'),\n getString('userename', 'tiny_cursive'),\n getString('course', 'tiny_cursive'),\n getString('opened', 'tiny_cursive'),\n getString('due', 'tiny_cursive'),\n getString('overdue', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n getString('savechanges', 'tiny_cursive'),\n getString('subjectnot', 'tiny_cursive'),\n getString('remaining', 'tiny_cursive'),\n ]).then(function(strings) {\n return localStorage.setItem('docSideBar', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n\n }\n\n /**\n * Checks if the current page is a PDF annotator and updates the resourceId accordingly\n * @function checkIsPdfAnnotator\n * @description Checks if URL contains 'pdfannotator' and sets resourceId based on editor ID and editing state:\n * - If editing an existing annotation (editor.id !== 'id_pdfannotator_content' and isEditing is true):\n * Sets resourceId to the annotation ID extracted from editor.id\n * - Otherwise: Sets resourceId to 0\n */\n function checkIsPdfAnnotator() {\n if (ur.includes('pdfannotator')) {\n if (editor.id !== 'id_pdfannotator_content' && parseInt(localStorage.getItem('isEditing'))) {\n resourceId = parseInt(editor?.id.replace('editarea', ''));\n } else {\n resourceId = 0;\n }\n }\n }\n\n window.addEventListener('unload', () => {\n syncData();\n });\n\n setInterval(syncData, syncInterval);\n};\n"],"names":["editor","interval","userId","hasApiKey","MODULES","Rubrics","submission","quizInfo","pasteSetting","isStudent","hasClass","intervention","host","M","cfg","wwwroot","userid","courseid","courseId","editorid","id","cmid","contextInstanceId","ed","event","filename","questionid","quizSubmit","assignSubmit","syncInterval","lastCaretPos","aiContents","isFullScreen","user","ur","window","location","href","parm","URL","modulesInfo","localStorage","getItem","Promise","all","then","strings","setItem","JSON","stringify","catch","error","console","fetchStrings","some","module","includes","resourceId","searchParams","get","modulename","checkIsPdfAnnotator","name","getModulesInfo","errorAlert","PASTE_SETTING","shouldBlockPaste","isPasteAllowed","document","addEventListener","e","target","className","replace","syncData","postOne","async","methodname","args","response","setTimeout","updateSavingState","field","values","done","fail","ex","on","preventDefault","off","click","removeItem","getModal","title","titledes","placeholder","type","body","removeOnClose","modal","getRoot","addClass","show","lastEvent","save","number","getElementById","value","trim","execCommand","str","alert","resourceid","usercomment","timemodified","Date","now","destroy","cancel","hidden","sendKeyEvent","events","split","data","parse","push","key","keyCode","unixTimestamp","clientId","personId","position","caretPosition","rePosition","pastedContent","aiContent","constructMouseEvent","getCaretPosition","button","getMouseButton","skip","selection","range","getRng","getBody","preCaretRange","cloneRange","selectNodeContents","setEnd","endContainer","endOffset","fragment","cloneContents","tempDiv","createElement","appendChild","textBeforeCursor","innerText","nodeType","Node","ELEMENT_NODE","dom","isBlock","previousSibling","blockElements","querySelectorAll","emptyBlockCount","forEach","block","textContent","childNodes","length","nodeName","repeat","absolutePosition","storageKey","storedPos","parseInt","sessionStorage","isNaN","warn","fire","originalText","getContent","format","editorId","iframe","querySelector","iframeBody","contentDocument","contentWindow","_iframe$contentWindow","_iframe$contentWindow2","getRawText","customTooltip","tooltipText","buttonTitle","buttonDes","getTooltipText","menubarDiv","classArray","element","index","classList","add","cursiveIcon","src","iconUrl","iconGrayUrl","setAttribute","style","display","rightWrapper","imgWrapper","iconClone","cloneNode","targetMenu","elementId","cssText","marginLeft","moduleIds","existingElement","container","_editor$container","_editor$container$chi","_editor$container$chi2","_editor$container$chi3","newHeader","_editor$container2","remove","marginTop","prepend","destroyInstance","getInstance","menubar","_editor$container3","children","_editor$container3$ch","_editor$container3$ch2","wrapper","parentElement","existsElement","cursiveState","tooltipId","text","setTooltip","this","css","tooltipCss","tooltipSpan","description","linebreak","tooltipTitle","fontSize","fontWeight","clipboardData","originalEvent","getData","trimmedPastedContent","lastCopyCutContent","isFromOwnEditor","stopPropagation","stopImmediatePropagation","windowManager","ctrlKey","metaKey","selectedContent","view","DocumentView","state","fullPageMode","normalMode","command","contentObj","isPaste","paste","insertedContent","content","innerHTML","pastedText","undoManager","undo","srcElement","baseURI","inputType","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 408c6660..c2b8ed1c 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:"".concat("tiny_cursive","/plugin"),iconUrl:M.util.image_url("cursive","tiny_cursive"),iconSaving:M.util.image_url("rotate","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:"tiny_cursive/plugin",iconUrl:M.util.image_url("cursive","tiny_cursive"),iconSaving:M.util.image_url("rotate","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 a8da292b..ddc7a5f5 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 iconSaving: M.util.image_url('rotate', '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","iconSaving","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,WAAYH,EAAEC,KAAKC,UAAU,SAAU,gBACvCE,YAAaJ,EAAEC,KAAKC,UAAU,eAAgB,gBAC9CG,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 iconSaving: M.util.image_url('rotate', '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","iconSaving","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,WAAYH,EAAEC,KAAKC,UAAU,SAAU,gBACvCE,YAAaJ,EAAEC,KAAKC,UAAU,eAAgB,gBAC9CG,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/cursive_autosave.min.js b/amd/build/cursive_autosave.min.js index 17a4fbb5..2ff57d50 100644 --- a/amd/build/cursive_autosave.min.js +++ b/amd/build/cursive_autosave.min.js @@ -1,3 +1,10 @@ -define("tiny_cursive/cursive_autosave",["exports","core/templates","core/ajax","tiny_cursive/svg_repo","core/str"],(function(_exports,_templates,_ajax,_svg_repo,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_svg_repo=_interopRequireDefault(_svg_repo);class CursiveAutosave{constructor(editor,rightWrapper,modules,isFullScreen){if(CursiveAutosave.instance)return CursiveAutosave.instance;this.editor=editor,this.module=modules,this.savingState="",this.rightWrapper=rightWrapper,this.isFullScreen=isFullScreen,this.fetchSavedContent=this.fetchSavedContent.bind(this),this.handleEscapeKey=this.handleEscapeKey.bind(this),this._savingTimer=null,CursiveAutosave.instance=this,this.fetchStrings()}static getInstance(editor,rightWrapper,modules,isFullScreen){this.instance||(this.instance=new CursiveAutosave(editor,rightWrapper,modules,isFullScreen)),this.instance.isFullScreen=isFullScreen;return("quiz"===modules.modulename?document.querySelector("#tiny_cursive_savingState".concat(modules.questionid)):document.querySelector("#tiny_cursive_savingState"))||this.instance.init(),this.instance}init(){const stateWrapper=this.cursiveSavingState(this.savingState);stateWrapper.classList.add("tiny_cursive_savingState","btn"),"quiz"===this.module.modulename?stateWrapper.id="tiny_cursive_savingState".concat(this.module.questionid):stateWrapper.id="tiny_cursive_savingState",this.rightWrapper.prepend(stateWrapper),stateWrapper.addEventListener("click",this.fetchSavedContent)}destroy(){CursiveAutosave.instance=null}static destroyInstance(){this.instance&&(this.instance.destroy(),this.instance=null)}cursiveSavingState(state){let wrapperDiv=document.createElement("div"),textSpan=document.createElement("span"),button=document.createElement("button"),iconSpan=document.createElement("span");return button.style.padding=".3rem",textSpan.style.fontSize="0.75rem",textSpan.style.color="gray","quiz"===this.module.modulename?(iconSpan.id="CursiveCloudIcon".concat(this.module.questionid),textSpan.id="CursiveStateText".concat(this.module.questionid)):(iconSpan.id="CursiveCloudIcon",textSpan.id="CursiveStateText"),state&&(textSpan.textContent=this.getStateText(state),iconSpan.innerHTML=this.getStateIcon(state)),wrapperDiv.style.verticalAlign="middle",wrapperDiv.appendChild(iconSpan),wrapperDiv.appendChild(textSpan),button.appendChild(wrapperDiv),button}static updateSavingState(state){const instance=this.instance;instance.savingState=state;let stateWrapper=null;stateWrapper="quiz"===instance.module.modulename?document.querySelector("#tiny_cursive_savingState".concat(instance.module.questionid)):document.querySelector("#tiny_cursive_savingState");let iconSpan="",stateTextEl="";stateWrapper&&("quiz"===instance.module.modulename?(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon".concat(instance.module.questionid)),stateTextEl=stateWrapper.querySelector("#CursiveStateText".concat(instance.module.questionid))):(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon"),stateTextEl=stateWrapper.querySelector("#CursiveStateText")),stateTextEl&&iconSpan&&(stateTextEl.textContent=instance.getStateText(state),iconSpan.innerHTML=instance.getStateIcon(state)),instance._savingTimer&&clearTimeout(instance._savingTimer),"saved"===state&&stateTextEl&&(instance._savingTimer=setTimeout((()=>{stateTextEl.textContent=""}),5e3)))}getStateText(state){const[saving,saved,offline]=this.getText("state");switch(state){case"saving":return saving;case"saved":return saved;case"offline":return offline;default:return""}}getStateIcon(state){switch(state){case"saving":case"saved":return _svg_repo.default.cloudSave;case"offline":return"data:image/svg+xml;base64,"+btoa(_svg_repo.default.offline);default:return""}}async fetchSavedContent(e){var _dropdown$classList,_this$editor;e.preventDefault();let dropdown=document.querySelector("#savedDropdown");if(null==dropdown||null===(_dropdown$classList=dropdown.classList)||void 0===_dropdown$classList?void 0:_dropdown$classList.contains("show"))return void this.closeSavedDropdown();let editorWrapper=null;editorWrapper="quiz"===this.module.modulename?document.querySelector("#tiny_cursive_savingState".concat(this.module.questionid)):document.querySelector("#tiny_cursive_savingState");let args={id:this.module.resourceId,cmid:this.module.cmid,modulename:"".concat(this.module.modulename,"_autosave"),editorid:null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id,userid:this.module.userid,courseid:this.module.courseid};(0,_ajax.call)([{methodname:"cursive_get_autosave_content",args:args}])[0].done((data=>{let context={comments:JSON.parse(data)};Object.values(context.comments).forEach((content=>{content.time=this.timeAgo(content.timemodified)})),this.renderCommentList(context,editorWrapper)})).fail((error=>{this.throwWarning("fullmodeerrorr",this.editor),window.console.error("Error fetching saved content:",error)}))}toggleSavedDropdown(){var _dropdown$classList2;const dropdown=document.querySelector("#savedDropdown");(null==dropdown||null===(_dropdown$classList2=dropdown.classList)||void 0===_dropdown$classList2?void 0:_dropdown$classList2.contains("show"))?this.closeSavedDropdown():this.openSavedDropdown()}openSavedDropdown(){document.querySelector("#savedDropdown").classList.add("show"),document.removeEventListener("keydown",this.handleEscapeKey),document.addEventListener("keydown",this.handleEscapeKey)}closeSavedDropdown(){const dropdown=document.querySelector("#savedDropdown");dropdown&&(dropdown.classList.remove("show"),dropdown.remove(),document.removeEventListener("keydown",this.handleEscapeKey))}handleEscapeKey(event){"Escape"===event.key&&this.closeSavedDropdown()}timeAgo(unixTime){const seconds=Math.floor(Date.now()/1e3)-unixTime;if(seconds<5)return"just now";if(seconds<60)return"".concat(seconds," sec ago");const minutes=Math.floor(seconds/60);if(minutes<60)return"".concat(minutes," min ago");const hours=Math.floor(minutes/60);if(hours<24)return"".concat(hours," hour").concat(hours>1?"s":""," ago");const days=Math.floor(hours/24);if(days<7)return"".concat(days," day").concat(days>1?"s":""," ago");const weeks=Math.floor(days/7);if(weeks<4)return"".concat(weeks," week").concat(weeks>1?"s":""," ago");const months=Math.floor(days/30);if(months<12)return"".concat(months," month").concat(months>1?"s":""," ago");const years=Math.floor(days/365);return"".concat(years," year").concat(years>1?"s":""," ago")}renderCommentList(context,editorWrapper){_templates.default.render("tiny_cursive/saved_content",context).then((html=>{editorWrapper.style.position="relative";const tempDiv=document.createElement("div");if(tempDiv.innerHTML=html.trim(),tempDiv.id="savedDropdown",tempDiv.classList.add("tiny_cursive-saved-dropdown"),!tempDiv)return window.console.error("Saved content template rendered empty or invalid HTML."),!1;let existingPanel=document.querySelector("#savedDropdown");return existingPanel||(editorWrapper.appendChild(tempDiv),existingPanel=tempDiv),existingPanel.classList.toggle("active"),this.openSavedDropdown(),this.insertSavedItems(this.editor),!0})).catch((error=>window.console.error(error)))}fetchStrings(){localStorage.getItem("state")||Promise.all([(0,_str.get_string)("saving","tiny_cursive"),(0,_str.get_string)("saved","tiny_cursive"),(0,_str.get_string)("offline","tiny_cursive")]).then((function(strings){return localStorage.setItem("state",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}throwWarning(str,editor){(0,_str.get_string)(str,"tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))}getText(key){return JSON.parse(localStorage.getItem(key))}insertSavedItems(editor){document.querySelectorAll(".tiny_cursive-item-preview").forEach((element=>{element.addEventListener("click",(function(){editor.insertContent(" "+this.textContent)}))}))}}var obj,key,value;return _exports.default=CursiveAutosave,value=null,(key="instance")in(obj=CursiveAutosave)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,_exports.default})); +define("tiny_cursive/cursive_autosave",["exports","core/templates","core/ajax","tiny_cursive/svg_repo","core/str"],(function(_exports,_templates,_ajax,_svg_repo,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * TODO describe module cursive_autosave + * + * @module tiny_cursive/cursive_autosave + * @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.default=void 0,_templates=_interopRequireDefault(_templates),_svg_repo=_interopRequireDefault(_svg_repo);class CursiveAutosave{static instance=null;constructor(editor,rightWrapper,modules,isFullScreen){if(CursiveAutosave.instance)return CursiveAutosave.instance;this.editor=editor,this.module=modules,this.savingState="",this.rightWrapper=rightWrapper,this.isFullScreen=isFullScreen,this.fetchSavedContent=this.fetchSavedContent.bind(this),this.handleEscapeKey=this.handleEscapeKey.bind(this),this._savingTimer=null,CursiveAutosave.instance=this,this.fetchStrings()}static getInstance(editor,rightWrapper,modules,isFullScreen){this.instance||(this.instance=new CursiveAutosave(editor,rightWrapper,modules,isFullScreen)),this.instance.isFullScreen=isFullScreen;return("quiz"===modules.modulename?document.querySelector(`#tiny_cursive_savingState${modules.questionid}`):document.querySelector("#tiny_cursive_savingState"))||this.instance.init(),this.instance}init(){const stateWrapper=this.cursiveSavingState(this.savingState);stateWrapper.classList.add("tiny_cursive_savingState","btn"),"quiz"===this.module.modulename?stateWrapper.id=`tiny_cursive_savingState${this.module.questionid}`:stateWrapper.id="tiny_cursive_savingState",this.rightWrapper.prepend(stateWrapper),stateWrapper.addEventListener("click",this.fetchSavedContent)}destroy(){CursiveAutosave.instance=null}static destroyInstance(){this.instance&&(this.instance.destroy(),this.instance=null)}cursiveSavingState(state){let wrapperDiv=document.createElement("div"),textSpan=document.createElement("span"),button=document.createElement("button"),iconSpan=document.createElement("span");return button.style.padding=".3rem",textSpan.style.fontSize="0.75rem",textSpan.style.color="gray","quiz"===this.module.modulename?(iconSpan.id=`CursiveCloudIcon${this.module.questionid}`,textSpan.id=`CursiveStateText${this.module.questionid}`):(iconSpan.id="CursiveCloudIcon",textSpan.id="CursiveStateText"),state&&(textSpan.textContent=this.getStateText(state),iconSpan.innerHTML=this.getStateIcon(state)),wrapperDiv.style.verticalAlign="middle",wrapperDiv.appendChild(iconSpan),wrapperDiv.appendChild(textSpan),button.appendChild(wrapperDiv),button}static updateSavingState(state){const instance=this.instance;instance.savingState=state;let stateWrapper=null;stateWrapper="quiz"===instance.module.modulename?document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`):document.querySelector("#tiny_cursive_savingState");let iconSpan="",stateTextEl="";stateWrapper&&("quiz"===instance.module.modulename?(iconSpan=stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`),stateTextEl=stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`)):(iconSpan=stateWrapper.querySelector("#CursiveCloudIcon"),stateTextEl=stateWrapper.querySelector("#CursiveStateText")),stateTextEl&&iconSpan&&(stateTextEl.textContent=instance.getStateText(state),iconSpan.innerHTML=instance.getStateIcon(state)),instance._savingTimer&&clearTimeout(instance._savingTimer),"saved"===state&&stateTextEl&&(instance._savingTimer=setTimeout((()=>{stateTextEl.textContent=""}),5e3)))}getStateText(state){const[saving,saved,offline]=this.getText("state");switch(state){case"saving":return saving;case"saved":return saved;case"offline":return offline;default:return""}}getStateIcon(state){switch(state){case"saving":case"saved":return _svg_repo.default.cloudSave;case"offline":return"data:image/svg+xml;base64,"+btoa(_svg_repo.default.offline);default:return""}}async fetchSavedContent(e){var _dropdown$classList,_this$editor;e.preventDefault();let dropdown=document.querySelector("#savedDropdown");if(null==dropdown||null===(_dropdown$classList=dropdown.classList)||void 0===_dropdown$classList?void 0:_dropdown$classList.contains("show"))return void this.closeSavedDropdown();let editorWrapper=null;editorWrapper="quiz"===this.module.modulename?document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`):document.querySelector("#tiny_cursive_savingState");let args={id:this.module.resourceId,cmid:this.module.cmid,modulename:`${this.module.modulename}_autosave`,editorid:null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id,userid:this.module.userid,courseid:this.module.courseid};(0,_ajax.call)([{methodname:"cursive_get_autosave_content",args:args}])[0].done((data=>{let context={comments:JSON.parse(data)};Object.values(context.comments).forEach((content=>{content.time=this.timeAgo(content.timemodified)})),this.renderCommentList(context,editorWrapper)})).fail((error=>{this.throwWarning("fullmodeerrorr",this.editor),window.console.error("Error fetching saved content:",error)}))}toggleSavedDropdown(){var _dropdown$classList2;const dropdown=document.querySelector("#savedDropdown");(null==dropdown||null===(_dropdown$classList2=dropdown.classList)||void 0===_dropdown$classList2?void 0:_dropdown$classList2.contains("show"))?this.closeSavedDropdown():this.openSavedDropdown()}openSavedDropdown(){document.querySelector("#savedDropdown").classList.add("show"),document.removeEventListener("keydown",this.handleEscapeKey),document.addEventListener("keydown",this.handleEscapeKey)}closeSavedDropdown(){const dropdown=document.querySelector("#savedDropdown");dropdown&&(dropdown.classList.remove("show"),dropdown.remove(),document.removeEventListener("keydown",this.handleEscapeKey))}handleEscapeKey(event){"Escape"===event.key&&this.closeSavedDropdown()}timeAgo(unixTime){const seconds=Math.floor(Date.now()/1e3)-unixTime;if(seconds<5)return"just now";if(seconds<60)return`${seconds} sec ago`;const minutes=Math.floor(seconds/60);if(minutes<60)return`${minutes} min ago`;const hours=Math.floor(minutes/60);if(hours<24)return`${hours} hour${hours>1?"s":""} ago`;const days=Math.floor(hours/24);if(days<7)return`${days} day${days>1?"s":""} ago`;const weeks=Math.floor(days/7);if(weeks<4)return`${weeks} week${weeks>1?"s":""} ago`;const months=Math.floor(days/30);if(months<12)return`${months} month${months>1?"s":""} ago`;const years=Math.floor(days/365);return`${years} year${years>1?"s":""} ago`}renderCommentList(context,editorWrapper){_templates.default.render("tiny_cursive/saved_content",context).then((html=>{editorWrapper.style.position="relative";const tempDiv=document.createElement("div");if(tempDiv.innerHTML=html.trim(),tempDiv.id="savedDropdown",tempDiv.classList.add("tiny_cursive-saved-dropdown"),!tempDiv)return window.console.error("Saved content template rendered empty or invalid HTML."),!1;let existingPanel=document.querySelector("#savedDropdown");return existingPanel||(editorWrapper.appendChild(tempDiv),existingPanel=tempDiv),existingPanel.classList.toggle("active"),this.openSavedDropdown(),this.insertSavedItems(this.editor),!0})).catch((error=>window.console.error(error)))}fetchStrings(){localStorage.getItem("state")||Promise.all([(0,_str.get_string)("saving","tiny_cursive"),(0,_str.get_string)("saved","tiny_cursive"),(0,_str.get_string)("offline","tiny_cursive")]).then((function(strings){return localStorage.setItem("state",JSON.stringify(strings))})).catch((error=>window.console.error(error)))}throwWarning(str,editor){(0,_str.get_string)(str,"tiny_cursive").then((str=>editor.windowManager.alert(str))).catch((error=>window.console.error(error)))}getText(key){return JSON.parse(localStorage.getItem(key))}insertSavedItems(editor){document.querySelectorAll(".tiny_cursive-item-preview").forEach((element=>{element.addEventListener("click",(function(){editor.insertContent(" "+this.textContent)}))}))}}return _exports.default=CursiveAutosave,_exports.default})); //# sourceMappingURL=cursive_autosave.min.js.map \ No newline at end of file diff --git a/amd/build/cursive_autosave.min.js.map b/amd/build/cursive_autosave.min.js.map index 56db2bab..f0ea83bb 100644 --- a/amd/build/cursive_autosave.min.js.map +++ b/amd/build/cursive_autosave.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cursive_autosave.min.js","sources":["../src/cursive_autosave.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 * TODO describe module cursive_autosave\n *\n * @module tiny_cursive/cursive_autosave\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport templates from 'core/templates';\nimport {call} from 'core/ajax';\nimport Icons from 'tiny_cursive/svg_repo';\nimport {get_string as getString} from 'core/str';\n\nexport default class CursiveAutosave {\n\n static instance = null;\n\n\n constructor(editor, rightWrapper, modules, isFullScreen) {\n if (CursiveAutosave.instance) {\n return CursiveAutosave.instance;\n }\n\n this.editor = editor;\n this.module = modules;\n this.savingState = '';\n this.rightWrapper = rightWrapper;\n this.isFullScreen = isFullScreen;\n // Bind methods that will be used as event listener\n this.fetchSavedContent = this.fetchSavedContent.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this._savingTimer = null;\n CursiveAutosave.instance = this;\n this.fetchStrings();\n }\n\n static getInstance(editor, rightWrapper, modules, isFullScreen) {\n if (!this.instance) {\n this.instance = new CursiveAutosave(editor, rightWrapper, modules, isFullScreen);\n }\n this.instance.isFullScreen = isFullScreen;\n const hasState = modules.modulename === 'quiz'\n ? document.querySelector(`#tiny_cursive_savingState${modules.questionid}`)\n : document.querySelector('#tiny_cursive_savingState');\n if (!hasState) {\n this.instance.init();\n }\n\n return this.instance;\n }\n\n init() {\n const stateWrapper = this.cursiveSavingState(this.savingState);\n stateWrapper.classList.add('tiny_cursive_savingState', 'btn');\n if (this.module.modulename === 'quiz') {\n stateWrapper.id = `tiny_cursive_savingState${this.module.questionid}`;\n } else {\n stateWrapper.id = 'tiny_cursive_savingState';\n }\n\n this.rightWrapper.prepend(stateWrapper);\n stateWrapper.addEventListener('click', this.fetchSavedContent);\n }\n\n destroy() {\n CursiveAutosave.instance = null;\n }\n\n static destroyInstance() {\n if (this.instance) {\n this.instance.destroy();\n this.instance = null;\n }\n }\n\n /**\n * Creates a wrapper div containing an icon and text to display the saving state\n * @param {string} state - The current saving state ('saving', 'saved', or 'offline')\n * @returns {HTMLElement} A div element containing the state icon and text\n * @description Creates and returns a div element with an icon and text span to show the current saving state.\n * The icon and text are updated based on the provided state parameter.\n */\n cursiveSavingState(state) {\n let wrapperDiv = document.createElement('div');\n let textSpan = document.createElement('span');\n let button = document.createElement('button');\n let iconSpan = document.createElement('span');\n\n button.style.padding = '.3rem';\n textSpan.style.fontSize = '0.75rem';\n textSpan.style.color = 'gray';\n\n if (this.module.modulename === 'quiz') {\n iconSpan.id = `CursiveCloudIcon${this.module.questionid}`;\n textSpan.id = `CursiveStateText${this.module.questionid}`;\n } else {\n iconSpan.id = 'CursiveCloudIcon';\n textSpan.id = 'CursiveStateText';\n }\n if (state) {\n textSpan.textContent = this.getStateText(state);\n iconSpan.innerHTML = this.getStateIcon(state);\n }\n\n wrapperDiv.style.verticalAlign = 'middle';\n\n wrapperDiv.appendChild(iconSpan);\n wrapperDiv.appendChild(textSpan);\n button.appendChild(wrapperDiv);\n\n return button;\n }\n\n /**\n * Updates the saving state icon and text in the editor\n * @param {string} state - The state to update to ('saving', 'saved', or 'offline')\n * @description Updates the global saving state and modifies the UI elements to reflect the new state\n */\n static updateSavingState(state) {\n const instance = this.instance;\n instance.savingState = state;\n let stateWrapper = null;\n if (instance.module.modulename === 'quiz') {\n stateWrapper = document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`);\n } else {\n stateWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let iconSpan = '';\n let stateTextEl = '';\n\n if (!stateWrapper) {\n return;\n }\n\n if (instance.module.modulename === 'quiz') {\n iconSpan = stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`);\n stateTextEl = stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`);\n } else {\n iconSpan = stateWrapper.querySelector('#CursiveCloudIcon');\n stateTextEl = stateWrapper.querySelector('#CursiveStateText');\n }\n\n if (stateTextEl && iconSpan) {\n stateTextEl.textContent = instance.getStateText(state);\n iconSpan.innerHTML = instance.getStateIcon(state);\n }\n\n\n if (instance._savingTimer) {\n clearTimeout(instance._savingTimer);\n }\n\n if (state === 'saved' && stateTextEl) {\n instance._savingTimer = setTimeout(() => {\n stateTextEl.textContent = '';\n }, 5000);\n }\n }\n\n /**\n * Gets the display text for a given saving state\n * @param {string} state - The state to get text for ('saving', 'saved', or 'offline')\n * @returns {string} The text to display for the given state\n * @description Returns appropriate text label based on the current saving state\n */\n getStateText(state) {\n const [saving, saved, offline] = this.getText('state');\n switch (state) {\n case 'saving': return saving;\n case 'saved': return saved;\n case 'offline': return offline;\n default: return '';\n }\n }\n /**\n * Gets the icon URL for a given saving state\n * @param {string} state - The state to get icon for ('saving', 'saved', or 'offline')\n * @returns {string} The URL of the icon image for the given state\n * @description Returns appropriate icon URL based on the current saving state\n */\n getStateIcon(state) {\n switch (state) {\n case 'saving': return Icons.cloudSave;\n case 'saved': return Icons.cloudSave;\n case 'offline': return 'data:image/svg+xml;base64,' + btoa(Icons.offline);\n default: return '';\n }\n }\n\n /**\n * Fetches and displays saved content in a dropdown\n * @async\n * @param {Event} e - The event object\n * @description Handles fetching and displaying saved content when the save state button is clicked.\n * If the dropdown is already visible, it will be closed. Otherwise it will fetch saved content\n * from the server (or use cached content if available) and display it in a dropdown panel.\n * @throws {Error} Logs error to console if fetching content fails\n */\n async fetchSavedContent(e) {\n e.preventDefault();\n\n let dropdown = document.querySelector('#savedDropdown');\n let isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n return;\n }\n let editorWrapper = null;\n if (this.module.modulename === 'quiz') {\n editorWrapper = document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`);\n } else {\n editorWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let args = {\n id: this.module.resourceId,\n cmid: this.module.cmid,\n modulename: `${this.module.modulename}_autosave`,\n editorid: this.editor?.id,\n userid: this.module.userid,\n courseid: this.module.courseid\n };\n\n call([{\n methodname: \"cursive_get_autosave_content\",\n args: args\n }])[0].done((data) => {\n let context = {comments: JSON.parse(data)};\n Object.values(context.comments).forEach(content => {\n content.time = this.timeAgo(content.timemodified);\n });\n this.renderCommentList(context, editorWrapper);\n\n }).fail((error) => {\n this.throwWarning('fullmodeerrorr', this.editor);\n window.console.error('Error fetching saved content:', error);\n });\n }\n\n /**\n * Toggles the visibility of the saved content dropdown\n * @description Checks if the saved content dropdown is currently visible and either closes or opens it accordingly.\n * If visible, calls closeSavedDropdown(). If hidden, calls openSavedDropdown().\n */\n toggleSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n const isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n } else {\n this.openSavedDropdown();\n }\n }\n\n /**\n * Opens the saved content dropdown panel\n * @description Shows the saved content dropdown by adding the 'show' class and sets up an event listener\n * for the Escape key to allow closing the dropdown. This is called when toggling the dropdown open.\n */\n openSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n dropdown.classList.add('show');\n\n // Add event listener to close on Escape key\n document.removeEventListener('keydown', this.handleEscapeKey);\n document.addEventListener('keydown', this.handleEscapeKey);\n }\n\n /**\n * Closes the saved content dropdown panel\n * @description Removes the 'show' class from the dropdown to hide it, removes the dropdown element from the DOM,\n * and removes the Escape key event listener. This is called when toggling the dropdown closed or when\n * clicking outside the dropdown.\n */\n closeSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n if (dropdown) {\n dropdown.classList.remove('show');\n dropdown.remove();\n document.removeEventListener('keydown', this.handleEscapeKey);\n }\n }\n\n /**\n * Handles the Escape key press event for closing the saved content dropdown\n * @param {KeyboardEvent} event - The keyboard event object\n * @description Event handler that checks if the Escape key was pressed and closes the saved content dropdown if it was\n */\n handleEscapeKey(event) {\n if (event.key === 'Escape') {\n this.closeSavedDropdown();\n }\n }\n\n timeAgo(unixTime) {\n const seconds = Math.floor(Date.now() / 1000) - unixTime;\n\n if (seconds < 5) {\n return \"just now\";\n }\n if (seconds < 60) {\n return `${seconds} sec ago`;\n }\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes} min ago`;\n }\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} hour${hours > 1 ? \"s\" : \"\"} ago`;\n }\n\n const days = Math.floor(hours / 24);\n if (days < 7) {\n return `${days} day${days > 1 ? \"s\" : \"\"} ago`;\n }\n\n const weeks = Math.floor(days / 7);\n if (weeks < 4) {\n return `${weeks} week${weeks > 1 ? \"s\" : \"\"} ago`;\n }\n\n const months = Math.floor(days / 30);\n if (months < 12) {\n return `${months} month${months > 1 ? \"s\" : \"\"} ago`;\n }\n\n const years = Math.floor(days / 365);\n return `${years} year${years > 1 ? \"s\" : \"\"} ago`;\n }\n\n\n /**\n * Renders the saved content dropdown list using a template\n * @param {Object} context - The context object containing saved comments data to render\n * @param {HTMLElement} editorWrapper - The wrapper element to attach the dropdown to\n * @description Renders the saved content dropdown using the tiny_cursive/saved_content template.\n * Creates and positions the dropdown relative to the editor wrapper element.\n * Handles toggling visibility and caching of the saved content.\n * @throws {Error} Logs error to console if template rendering fails\n */\n renderCommentList(context, editorWrapper) {\n templates.render('tiny_cursive/saved_content', context).then(html => {\n editorWrapper.style.position = 'relative';\n\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = html.trim();\n tempDiv.id = 'savedDropdown';\n tempDiv.classList.add('tiny_cursive-saved-dropdown');\n\n if (!tempDiv) {\n window.console.error(\"Saved content template rendered empty or invalid HTML.\");\n return false;\n }\n\n // Add to DOM if not already added\n let existingPanel = document.querySelector('#savedDropdown');\n\n if (!existingPanel) {\n editorWrapper.appendChild(tempDiv);\n existingPanel = tempDiv;\n }\n\n // Toggle visibility\n existingPanel.classList.toggle('active');\n this.openSavedDropdown();\n\n this.insertSavedItems(this.editor);\n\n return true;\n\n }).catch(error => window.console.error(error));\n }\n\n fetchStrings() {\n if (!localStorage.getItem('state')) {\n\n Promise.all([\n getString('saving', 'tiny_cursive'),\n getString('saved', 'tiny_cursive'),\n getString('offline', 'tiny_cursive')\n ]).then(function(strings) {\n return localStorage.setItem('state', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n }\n\n throwWarning(str, editor) {\n getString(str, 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key));\n }\n\n /**\n * Adds click event listeners to saved content items to insert them into the editor\n * @description Finds all elements with class 'tiny_cursive-item-preview' and adds click handlers that will\n * insert the element's text content into the editor when clicked. The text is inserted with\n * a leading space.\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {void}\n */\n insertSavedItems(editor) {\n const items = document.querySelectorAll('.tiny_cursive-item-preview');\n items.forEach(element => {\n element.addEventListener('click', function() {\n editor.insertContent(\" \" + this.textContent);\n });\n });\n }\n\n}"],"names":["CursiveAutosave","constructor","editor","rightWrapper","modules","isFullScreen","instance","module","savingState","fetchSavedContent","this","bind","handleEscapeKey","_savingTimer","fetchStrings","modulename","document","querySelector","questionid","init","stateWrapper","cursiveSavingState","classList","add","id","prepend","addEventListener","destroy","state","wrapperDiv","createElement","textSpan","button","iconSpan","style","padding","fontSize","color","textContent","getStateText","innerHTML","getStateIcon","verticalAlign","appendChild","stateTextEl","clearTimeout","setTimeout","saving","saved","offline","getText","Icons","cloudSave","btoa","e","preventDefault","dropdown","_dropdown$classList","contains","closeSavedDropdown","editorWrapper","args","resourceId","cmid","editorid","_this$editor","userid","courseid","methodname","done","data","context","comments","JSON","parse","Object","values","forEach","content","time","timeAgo","timemodified","renderCommentList","fail","error","throwWarning","window","console","toggleSavedDropdown","_dropdown$classList2","openSavedDropdown","removeEventListener","remove","event","key","unixTime","seconds","Math","floor","Date","now","minutes","hours","days","weeks","months","years","render","then","html","position","tempDiv","trim","existingPanel","toggle","insertSavedItems","catch","localStorage","getItem","Promise","all","strings","setItem","stringify","str","windowManager","alert","querySelectorAll","element","insertContent"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cursive_autosave.min.js","sources":["../src/cursive_autosave.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 * TODO describe module cursive_autosave\n *\n * @module tiny_cursive/cursive_autosave\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport templates from 'core/templates';\nimport {call} from 'core/ajax';\nimport Icons from 'tiny_cursive/svg_repo';\nimport {get_string as getString} from 'core/str';\n\nexport default class CursiveAutosave {\n\n static instance = null;\n\n\n constructor(editor, rightWrapper, modules, isFullScreen) {\n if (CursiveAutosave.instance) {\n return CursiveAutosave.instance;\n }\n\n this.editor = editor;\n this.module = modules;\n this.savingState = '';\n this.rightWrapper = rightWrapper;\n this.isFullScreen = isFullScreen;\n // Bind methods that will be used as event listener\n this.fetchSavedContent = this.fetchSavedContent.bind(this);\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\n this._savingTimer = null;\n CursiveAutosave.instance = this;\n this.fetchStrings();\n }\n\n static getInstance(editor, rightWrapper, modules, isFullScreen) {\n if (!this.instance) {\n this.instance = new CursiveAutosave(editor, rightWrapper, modules, isFullScreen);\n }\n this.instance.isFullScreen = isFullScreen;\n const hasState = modules.modulename === 'quiz'\n ? document.querySelector(`#tiny_cursive_savingState${modules.questionid}`)\n : document.querySelector('#tiny_cursive_savingState');\n if (!hasState) {\n this.instance.init();\n }\n\n return this.instance;\n }\n\n init() {\n const stateWrapper = this.cursiveSavingState(this.savingState);\n stateWrapper.classList.add('tiny_cursive_savingState', 'btn');\n if (this.module.modulename === 'quiz') {\n stateWrapper.id = `tiny_cursive_savingState${this.module.questionid}`;\n } else {\n stateWrapper.id = 'tiny_cursive_savingState';\n }\n\n this.rightWrapper.prepend(stateWrapper);\n stateWrapper.addEventListener('click', this.fetchSavedContent);\n }\n\n destroy() {\n CursiveAutosave.instance = null;\n }\n\n static destroyInstance() {\n if (this.instance) {\n this.instance.destroy();\n this.instance = null;\n }\n }\n\n /**\n * Creates a wrapper div containing an icon and text to display the saving state\n * @param {string} state - The current saving state ('saving', 'saved', or 'offline')\n * @returns {HTMLElement} A div element containing the state icon and text\n * @description Creates and returns a div element with an icon and text span to show the current saving state.\n * The icon and text are updated based on the provided state parameter.\n */\n cursiveSavingState(state) {\n let wrapperDiv = document.createElement('div');\n let textSpan = document.createElement('span');\n let button = document.createElement('button');\n let iconSpan = document.createElement('span');\n\n button.style.padding = '.3rem';\n textSpan.style.fontSize = '0.75rem';\n textSpan.style.color = 'gray';\n\n if (this.module.modulename === 'quiz') {\n iconSpan.id = `CursiveCloudIcon${this.module.questionid}`;\n textSpan.id = `CursiveStateText${this.module.questionid}`;\n } else {\n iconSpan.id = 'CursiveCloudIcon';\n textSpan.id = 'CursiveStateText';\n }\n if (state) {\n textSpan.textContent = this.getStateText(state);\n iconSpan.innerHTML = this.getStateIcon(state);\n }\n\n wrapperDiv.style.verticalAlign = 'middle';\n\n wrapperDiv.appendChild(iconSpan);\n wrapperDiv.appendChild(textSpan);\n button.appendChild(wrapperDiv);\n\n return button;\n }\n\n /**\n * Updates the saving state icon and text in the editor\n * @param {string} state - The state to update to ('saving', 'saved', or 'offline')\n * @description Updates the global saving state and modifies the UI elements to reflect the new state\n */\n static updateSavingState(state) {\n const instance = this.instance;\n instance.savingState = state;\n let stateWrapper = null;\n if (instance.module.modulename === 'quiz') {\n stateWrapper = document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`);\n } else {\n stateWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let iconSpan = '';\n let stateTextEl = '';\n\n if (!stateWrapper) {\n return;\n }\n\n if (instance.module.modulename === 'quiz') {\n iconSpan = stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`);\n stateTextEl = stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`);\n } else {\n iconSpan = stateWrapper.querySelector('#CursiveCloudIcon');\n stateTextEl = stateWrapper.querySelector('#CursiveStateText');\n }\n\n if (stateTextEl && iconSpan) {\n stateTextEl.textContent = instance.getStateText(state);\n iconSpan.innerHTML = instance.getStateIcon(state);\n }\n\n\n if (instance._savingTimer) {\n clearTimeout(instance._savingTimer);\n }\n\n if (state === 'saved' && stateTextEl) {\n instance._savingTimer = setTimeout(() => {\n stateTextEl.textContent = '';\n }, 5000);\n }\n }\n\n /**\n * Gets the display text for a given saving state\n * @param {string} state - The state to get text for ('saving', 'saved', or 'offline')\n * @returns {string} The text to display for the given state\n * @description Returns appropriate text label based on the current saving state\n */\n getStateText(state) {\n const [saving, saved, offline] = this.getText('state');\n switch (state) {\n case 'saving': return saving;\n case 'saved': return saved;\n case 'offline': return offline;\n default: return '';\n }\n }\n /**\n * Gets the icon URL for a given saving state\n * @param {string} state - The state to get icon for ('saving', 'saved', or 'offline')\n * @returns {string} The URL of the icon image for the given state\n * @description Returns appropriate icon URL based on the current saving state\n */\n getStateIcon(state) {\n switch (state) {\n case 'saving': return Icons.cloudSave;\n case 'saved': return Icons.cloudSave;\n case 'offline': return 'data:image/svg+xml;base64,' + btoa(Icons.offline);\n default: return '';\n }\n }\n\n /**\n * Fetches and displays saved content in a dropdown\n * @async\n * @param {Event} e - The event object\n * @description Handles fetching and displaying saved content when the save state button is clicked.\n * If the dropdown is already visible, it will be closed. Otherwise it will fetch saved content\n * from the server (or use cached content if available) and display it in a dropdown panel.\n * @throws {Error} Logs error to console if fetching content fails\n */\n async fetchSavedContent(e) {\n e.preventDefault();\n\n let dropdown = document.querySelector('#savedDropdown');\n let isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n return;\n }\n let editorWrapper = null;\n if (this.module.modulename === 'quiz') {\n editorWrapper = document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`);\n } else {\n editorWrapper = document.querySelector('#tiny_cursive_savingState');\n }\n\n let args = {\n id: this.module.resourceId,\n cmid: this.module.cmid,\n modulename: `${this.module.modulename}_autosave`,\n editorid: this.editor?.id,\n userid: this.module.userid,\n courseid: this.module.courseid\n };\n\n call([{\n methodname: \"cursive_get_autosave_content\",\n args: args\n }])[0].done((data) => {\n let context = {comments: JSON.parse(data)};\n Object.values(context.comments).forEach(content => {\n content.time = this.timeAgo(content.timemodified);\n });\n this.renderCommentList(context, editorWrapper);\n\n }).fail((error) => {\n this.throwWarning('fullmodeerrorr', this.editor);\n window.console.error('Error fetching saved content:', error);\n });\n }\n\n /**\n * Toggles the visibility of the saved content dropdown\n * @description Checks if the saved content dropdown is currently visible and either closes or opens it accordingly.\n * If visible, calls closeSavedDropdown(). If hidden, calls openSavedDropdown().\n */\n toggleSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n const isVisible = dropdown?.classList?.contains('show');\n\n if (isVisible) {\n this.closeSavedDropdown();\n } else {\n this.openSavedDropdown();\n }\n }\n\n /**\n * Opens the saved content dropdown panel\n * @description Shows the saved content dropdown by adding the 'show' class and sets up an event listener\n * for the Escape key to allow closing the dropdown. This is called when toggling the dropdown open.\n */\n openSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n dropdown.classList.add('show');\n\n // Add event listener to close on Escape key\n document.removeEventListener('keydown', this.handleEscapeKey);\n document.addEventListener('keydown', this.handleEscapeKey);\n }\n\n /**\n * Closes the saved content dropdown panel\n * @description Removes the 'show' class from the dropdown to hide it, removes the dropdown element from the DOM,\n * and removes the Escape key event listener. This is called when toggling the dropdown closed or when\n * clicking outside the dropdown.\n */\n closeSavedDropdown() {\n const dropdown = document.querySelector('#savedDropdown');\n if (dropdown) {\n dropdown.classList.remove('show');\n dropdown.remove();\n document.removeEventListener('keydown', this.handleEscapeKey);\n }\n }\n\n /**\n * Handles the Escape key press event for closing the saved content dropdown\n * @param {KeyboardEvent} event - The keyboard event object\n * @description Event handler that checks if the Escape key was pressed and closes the saved content dropdown if it was\n */\n handleEscapeKey(event) {\n if (event.key === 'Escape') {\n this.closeSavedDropdown();\n }\n }\n\n timeAgo(unixTime) {\n const seconds = Math.floor(Date.now() / 1000) - unixTime;\n\n if (seconds < 5) {\n return \"just now\";\n }\n if (seconds < 60) {\n return `${seconds} sec ago`;\n }\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes} min ago`;\n }\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} hour${hours > 1 ? \"s\" : \"\"} ago`;\n }\n\n const days = Math.floor(hours / 24);\n if (days < 7) {\n return `${days} day${days > 1 ? \"s\" : \"\"} ago`;\n }\n\n const weeks = Math.floor(days / 7);\n if (weeks < 4) {\n return `${weeks} week${weeks > 1 ? \"s\" : \"\"} ago`;\n }\n\n const months = Math.floor(days / 30);\n if (months < 12) {\n return `${months} month${months > 1 ? \"s\" : \"\"} ago`;\n }\n\n const years = Math.floor(days / 365);\n return `${years} year${years > 1 ? \"s\" : \"\"} ago`;\n }\n\n\n /**\n * Renders the saved content dropdown list using a template\n * @param {Object} context - The context object containing saved comments data to render\n * @param {HTMLElement} editorWrapper - The wrapper element to attach the dropdown to\n * @description Renders the saved content dropdown using the tiny_cursive/saved_content template.\n * Creates and positions the dropdown relative to the editor wrapper element.\n * Handles toggling visibility and caching of the saved content.\n * @throws {Error} Logs error to console if template rendering fails\n */\n renderCommentList(context, editorWrapper) {\n templates.render('tiny_cursive/saved_content', context).then(html => {\n editorWrapper.style.position = 'relative';\n\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = html.trim();\n tempDiv.id = 'savedDropdown';\n tempDiv.classList.add('tiny_cursive-saved-dropdown');\n\n if (!tempDiv) {\n window.console.error(\"Saved content template rendered empty or invalid HTML.\");\n return false;\n }\n\n // Add to DOM if not already added\n let existingPanel = document.querySelector('#savedDropdown');\n\n if (!existingPanel) {\n editorWrapper.appendChild(tempDiv);\n existingPanel = tempDiv;\n }\n\n // Toggle visibility\n existingPanel.classList.toggle('active');\n this.openSavedDropdown();\n\n this.insertSavedItems(this.editor);\n\n return true;\n\n }).catch(error => window.console.error(error));\n }\n\n fetchStrings() {\n if (!localStorage.getItem('state')) {\n\n Promise.all([\n getString('saving', 'tiny_cursive'),\n getString('saved', 'tiny_cursive'),\n getString('offline', 'tiny_cursive')\n ]).then(function(strings) {\n return localStorage.setItem('state', JSON.stringify(strings));\n }).catch(error => window.console.error(error));\n }\n }\n\n throwWarning(str, editor) {\n getString(str, 'tiny_cursive').then(str => {\n return editor.windowManager.alert(str);\n }).catch(error => window.console.error(error));\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key));\n }\n\n /**\n * Adds click event listeners to saved content items to insert them into the editor\n * @description Finds all elements with class 'tiny_cursive-item-preview' and adds click handlers that will\n * insert the element's text content into the editor when clicked. The text is inserted with\n * a leading space.\n * @param {Object} editor - The TinyMCE editor instance\n * @returns {void}\n */\n insertSavedItems(editor) {\n const items = document.querySelectorAll('.tiny_cursive-item-preview');\n items.forEach(element => {\n element.addEventListener('click', function() {\n editor.insertContent(\" \" + this.textContent);\n });\n });\n }\n\n}"],"names":["CursiveAutosave","constructor","editor","rightWrapper","modules","isFullScreen","instance","module","savingState","fetchSavedContent","this","bind","handleEscapeKey","_savingTimer","fetchStrings","modulename","document","querySelector","questionid","init","stateWrapper","cursiveSavingState","classList","add","id","prepend","addEventListener","destroy","state","wrapperDiv","createElement","textSpan","button","iconSpan","style","padding","fontSize","color","textContent","getStateText","innerHTML","getStateIcon","verticalAlign","appendChild","stateTextEl","clearTimeout","setTimeout","saving","saved","offline","getText","Icons","cloudSave","btoa","e","preventDefault","dropdown","_dropdown$classList","contains","closeSavedDropdown","editorWrapper","args","resourceId","cmid","editorid","_this$editor","userid","courseid","methodname","done","data","context","comments","JSON","parse","Object","values","forEach","content","time","timeAgo","timemodified","renderCommentList","fail","error","throwWarning","window","console","toggleSavedDropdown","_dropdown$classList2","openSavedDropdown","removeEventListener","remove","event","key","unixTime","seconds","Math","floor","Date","now","minutes","hours","days","weeks","months","years","render","then","html","position","tempDiv","trim","existingPanel","toggle","insertSavedItems","catch","localStorage","getItem","Promise","all","strings","setItem","stringify","str","windowManager","alert","querySelectorAll","element","insertContent"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/document_view.min.js b/amd/build/document_view.min.js index 381301c9..dfc9c4cb 100644 --- a/amd/build/document_view.min.js +++ b/amd/build/document_view.min.js @@ -5,10 +5,6 @@ define("tiny_cursive/document_view",["exports","tiny_cursive/svg_repo"],(functio * @module tiny_cursive/document_view * @copyright 2025 Cursive Technology, Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later -<<<<<<< HEAD - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_svg_repo=(obj=_svg_repo)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(User,Rubrics,submission,modulename,editor,quizInfo){this.User=User,this.Rubrics=Rubrics,this.submission=submission,this.module=modulename,this.editor=editor,this.moduleIcon=_svg_repo.default.assignment,this.quizInfo=quizInfo,this.initStrings()}normalMode(){var _this$editor;let id=(null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id)+"_ifr";("assign"===this.module||"quiz"===this.module||"forum"===this.module||"lesson"===this.module||"pdfannotator"===this.module)&&this.normalizePage(id)}fullPageMode(){var _this$editor4,_this$editor2;if("assign"===this.module)this.moduleIcon=_svg_repo.default.assignment,this.fullPageModule(null===(_this$editor2=this.editor)||void 0===_this$editor2?void 0:_this$editor2.id);else if("forum"===this.module){var _this$editor3;this.moduleIcon=_svg_repo.default.forum,this.fullPageModule(null===(_this$editor3=this.editor)||void 0===_this$editor3?void 0:_this$editor3.id)}else if("quiz"===this.module&&null!==(_this$editor4=this.editor)&&void 0!==_this$editor4&&_this$editor4.id){var _this$editor5;this.moduleIcon=_svg_repo.default.quiz,this.fullPageModule(null===(_this$editor5=this.editor)||void 0===_this$editor5?void 0:_this$editor5.id)}else if("lesson"===this.module){var _this$editor6;this.moduleIcon=_svg_repo.default.lesson,this.fullPageModule(null===(_this$editor6=this.editor)||void 0===_this$editor6?void 0:_this$editor6.id)}else if("pdfannotator"===this.module){var _this$editor7;this.moduleIcon=_svg_repo.default.pdfannotator,this.fullPageModule(null===(_this$editor7=this.editor)||void 0===_this$editor7?void 0:_this$editor7.id)}}docSideBar(status){var _this$editor8;const replyId=new URL(window.location.href).searchParams.get("reply"),toggle=document.querySelector("#cursive-fullpagemode-sidebar-toggle"),timelimitBlock=this.getTimerBlock(this.module),headerInfo=this.getSidebarTitle(),progressBar=document.querySelector(".box.progress_bar"),courseName=document.querySelector("#page-navbar > nav > ol > li:nth-child(1) > a"),courseDes=document.querySelector("#intro"),Dates=document.querySelector(".activity-dates");let openDate=null==Dates?void 0:Dates.querySelector("div:nth-child(1)"),dueDate=null==Dates?void 0:Dates.querySelector("div:nth-child(2)");const container=this.create("div");Object.assign(container,{id:"cursive-fullpagemode-sidebar",className:"bg-white h-100 shadow"}),Object.assign(container.style,{width:"300px",overflow:"auto"});const crossBtn=this.create("span");Object.assign(crossBtn,{id:"cursive-collapse-sidebar",className:"btn p-2",innerHTML:_svg_repo.default.close}),crossBtn.addEventListener("click",(()=>{container.style.transition="width 0.3s ease",container.style.width="0",toggle.style.display="flex"})),null==toggle||toggle.addEventListener("click",(function(){toggle.style.display="none",container.style.width="300px"}));const btnWrapper=this.create("div");Object.assign(btnWrapper,{padding:"0 1rem",position:"sticky",top:"0",backgroundColor:"white"}),btnWrapper.append(crossBtn);const header=this.create("div");header.className="border-bottom p-3 bg-light",Object.assign(header.style,{position:"sticky",top:"0"});const headerTitle=this.create("h3");headerTitle.className="mb-3 d-flex align-items-center",headerTitle.textContent="".concat(headerInfo.title," ").concat(this.details),headerTitle.style.fontWeight="600";const headerIcon=document.querySelector(".page-header-image > div");headerIcon&&headerTitle.prepend(headerIcon.cloneNode(!0));let wordCount=this.wordCounter(status);null!=timelimitBlock&&timelimitBlock.textContent?header.append(headerTitle,wordCount,this.timerCountDown(timelimitBlock)):header.append(headerTitle,wordCount);const content=this.create("div");if(content.className="p-3",content.append(this.createBox({bg:"bg-info",titleColor:"text-info",icon:_svg_repo.default.people,title:this.studentInfo,bodyHTML:this.generateStudentInfo(this.User,courseName)})),"lesson"===this.module&&progressBar&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.progress,bodyHTML:progressBar.innerHTML})),courseDes&&""!==(null==courseDes?void 0:courseDes.textContent.trim())){let fileSubDiv=document.querySelectorAll(".fileuploadsubmission");fileSubDiv&&fileSubDiv.forEach((Element=>{Element.style.verticalAlign="middle"})),content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:"".concat(this.getSidebarTitle().title," ").concat(this.description),bodyHTML:courseDes.innerHTML}))}if("forum"===this.module&&replyId){this.checkForumSubject();let replyPost=document.querySelector("#post-content-".concat(replyId));null!=replyPost&&replyPost.textContent.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.replyingto,bodyHTML:replyPost.textContent.trim()}))}if("quiz"===this.module&&null!==(_this$editor8=this.editor)&&void 0!==_this$editor8&&_this$editor8.id){var _this$editor9;let questionId=this.getQuestionId(null===(_this$editor9=this.editor)||void 0===_this$editor9?void 0:_this$editor9.id),question=document.querySelector("#question-".concat(questionId," .qtext")),intro=atob(this.quizInfo.intro);null!=question&&question.textContent.trim()&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:this.moduleIcon,title:this.answeringto,bodyHTML:question.textContent})),intro&&""!==intro.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:"".concat(this.quiz," ").concat(this.description),bodyHTML:intro})),Number(this.quizInfo.open)&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(Number(this.quizInfo.open),Number(this.quizInfo.close))}))}return Object.keys(this.Rubrics).length&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.rubrics,bodyHTML:this.generateRubrics(this.Rubrics)})),Dates&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(openDate,dueDate)})),"assign"===this.module&&content.append(this.createBox({bg:"bg-green",titleColor:"text-success",icon:this.moduleIcon,title:this.subStatus,bodyHTML:this.submissionStatus(this.submission)})),container.append(btnWrapper,header,content),container}createBox(_ref){let{bg:bg,titleColor:titleColor,icon:icon,title:title,bodyHTML:bodyHTML}=_ref;const box=this.create("div");box.className="tiny_cursive-fullpage-card ".concat(bg);const heading=this.create("h4");heading.className="tiny_cursive-fullpage-card-header ".concat(titleColor," d-flex align-items-center"),heading.innerHTML="".concat(icon," ").concat(title);const body=this.create("div");return body.className="tiny_cursive-fullpage-card-body",body.innerHTML=bodyHTML,box.append(heading,body),box}generateRubrics(Rubrics){const wrapper=this.create("div");return Rubrics.forEach((rubric=>{const rubricDiv=this.create("div");rubricDiv.className="tiny_cursive-rubric-card";const title=this.create("h3");title.className="tiny_cursive-rubric-title",title.textContent=rubric.description,rubricDiv.appendChild(title),Object.values(rubric.levels).forEach((level=>{const levelDiv=this.create("div"),score=Number(level.score);levelDiv.className=0===score?"tiny_cursive-rubric-level tiny_cursive-rubric-low":score<=2?"tiny_cursive-rubric-level tiny_cursive-rubric-mid":"tiny_cursive-rubric-level tiny_cursive-rubric-high",levelDiv.textContent="".concat(level.definition," / ").concat(level.score),rubricDiv.appendChild(levelDiv)})),wrapper.appendChild(rubricDiv)})),wrapper.innerHTML}submissionStatus(submission){var _submission$current,_submission$current2;const wrapper=this.create("div"),statusWrapper=this.create("div");statusWrapper.className="tiny_cursive-status-row";const statusName=this.create("span");statusName.textContent="".concat(this.status,":");const statusValue=this.create("span"),isNew="new"===(null==submission||null===(_submission$current=submission.current)||void 0===_submission$current?void 0:_submission$current.status);statusValue.textContent=isNew?this.draftnot:this.draft,statusValue.className="tiny_cursive-status-value ".concat(isNew?"tiny_cursive-status-red":"tiny_cursive-status-green"),statusWrapper.append(statusName,statusValue);const modifiedWrapper=this.create("div");modifiedWrapper.className="tiny_cursive-status-row";const modifiedName=this.create("span");modifiedName.textContent="".concat(this.lastModified,": ");const modifiedValue=this.create("span");if(null!=submission&&null!==(_submission$current2=submission.current)&&void 0!==_submission$current2&&_submission$current2.timemodified){const date=new Date(1e3*submission.current.timemodified);modifiedValue.textContent=this.formatDate(date)}else modifiedValue.textContent="N/A";modifiedWrapper.append(modifiedName,modifiedValue);const gradeWrapper=this.create("div");gradeWrapper.className="tiny_cursive-status-row";const gradeName=this.create("span");gradeName.textContent="".concat(this.gradings,": ");const gradeValue=this.create("span");return null!=submission&&submission.grade?gradeValue.textContent=Number(submission.grade.grade)>0?submission.grade.grade:this.gradenot:gradeValue.textContent=this.gradenot,gradeWrapper.append(gradeName,gradeValue),wrapper.append(statusWrapper,gradeWrapper,modifiedWrapper),wrapper.innerHTML}wordCounter(status){const wordCount=this.create("div"),labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");icon.className="me-2",icon.innerHTML=_svg_repo.default.assignment,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent="".concat(this.wordCount,":"),value.textContent="0",value.className="text-primary",value.style.fontWeight="600",value.style.fontSize="14px",wordCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2",wordCount.append(labelDiv,value),wordCount.style.fontSize="12px";return new MutationObserver((()=>{const newText=status.textContent.trim();value.textContent="".concat(newText.replace("words",""))})).observe(status,{characterData:!0,subtree:!0,childList:!0}),wordCount}timerCountDown(timer){let warningDiv=document.querySelector("#user-notifications > div");if(warningDiv){var _clone$querySelector;let clone=warningDiv.cloneNode(!0);null===(_clone$querySelector=clone.querySelector("button"))||void 0===_clone$querySelector||_clone$querySelector.remove(),this.editor.notificationManager.open({text:clone.textContent,type:"error"})}const timerCount=this.create("div");timerCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2";const labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");if(icon.innerHTML=_svg_repo.default.time,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent="".concat(this.timeleft,":"),value.textContent="00:00:00",value.className=warningDiv?"text-danger":"text-primary",Object.assign(value.style,{fontWeight:"600",fontSize:"14px"}),timerCount.append(labelDiv,value),timerCount.style.fontSize="12px",timer){new MutationObserver((()=>{const newText=timer.textContent.trim();value.textContent="".concat(newText)})).observe(timer,{characterData:!0,subtree:!0,childList:!0})}else value.textContent=this.nolimit;return timerCount}generateStudentInfo(user,course){const wrapper=this.create("div"),nameWrapper=this.create("div"),usernameWrapper=this.create("div"),courseWrapper=this.create("div"),nameLabel=this.create("strong"),nameValue=this.create("span"),usernameLabel=this.create("strong"),usernameValue=this.create("span"),courseLabel=this.create("strong"),courseValue=this.create("span");return nameLabel.textContent="".concat(this.name),nameValue.textContent=user.fullname,usernameLabel.textContent="".concat(this.userename,": "),usernameValue.textContent=user.username,courseLabel.textContent="".concat(this.course,": "),courseValue.textContent=course.title,usernameLabel.className="cfw-bold me-2",usernameValue.className="cursiveFw-wrap",courseLabel.className="cfw-bold me-2",courseValue.className="cursiveFw-wrap",nameLabel.className="cfw-bold me-2",nameValue.className="cursiveFw-wrap",nameWrapper.append(nameLabel,nameValue),usernameWrapper.append(usernameLabel,usernameValue),courseWrapper.append(courseLabel,courseValue),wrapper.append(nameWrapper,usernameWrapper,courseWrapper),wrapper.innerHTML}generateImportantDates(open,due){const wrapper=this.create("div");let openDate=null,dueDate=null;const openedWrapper=this.create("div"),dueWrapper=this.create("div"),remainingWrapper=this.create("div"),openedLabel=this.create("span"),openedValue=this.create("span"),dueLabel=this.create("span"),dueValue=this.create("span"),remainingLabel=this.create("span"),remainingValue=this.create("span");return"quiz"===this.module?(openDate=1e3*open,dueDate=1e3*due):(openDate=this.extractDate(null==open?void 0:open.textContent),dueDate=this.extractDate(null==due?void 0:due.textContent)),openedLabel.textContent="".concat(this.opened,": "),openedValue.textContent=this.formatDate(openDate?new Date(openDate):null),openedValue.className="text-dark",dueLabel.textContent="".concat(this.due,": "),dueValue.textContent=this.formatDate(dueDate?new Date(dueDate):null),dueValue.className="text-danger",remainingLabel.textContent="".concat(this.remaining,": "),remainingValue.textContent=this.calculateDate(dueDate),remainingValue.className="text-danger",openedWrapper.className="d-flex justify-content-between",dueWrapper.className="d-flex justify-content-between",remainingWrapper.className="d-flex align-items-center justify-content-between mt-2 pt-2 border-top",openedWrapper.append(openedLabel,openedValue),dueWrapper.append(dueLabel,dueValue),remainingWrapper.append(remainingLabel,remainingValue),wrapper.append(openedWrapper,dueWrapper,remainingWrapper),wrapper.innerHTML}formatDate(date){if(!date)return"-";return date.toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"numeric",hour12:!0})}extractDate(text){if(!text)return"-";const parts=null==text?void 0:text.split(":");return parts.length>1?parts.slice(1).join(":").trim():text.trim()}calculateDate(date){if(!date)return"-";const diffMs=new Date(date)-new Date;if(diffMs<=0)return"Overdue";{const diffDays=Math.floor(diffMs/864e5),diffHours=Math.floor(diffMs/36e5%24);return"".concat(diffDays," days, ").concat(diffHours," hours")}}fullPageModule(module){var _current$contentDocum,_current$contentWindo,_current$contentWindo2,_document$getElementB;let current="quiz"===this.module?document.getElementById("".concat(module,"_ifr")):document.querySelector("#".concat(module,"_ifr")),p1=current.parentElement,p2=p1.parentElement,p4=p2.parentElement.parentElement,statusBar=document.querySelector(".tox-statusbar__right-container > button"),assignName=document.querySelector(".page-context-header"),header=this.create("div"),btn=null;if(assignName.classList.remove("mb-2"),header.id="tiny_cursive-fullpage-custom-header",Object.assign(header.style,{backgroundColor:"white",display:"flex",justifyContent:"space-between"}),"quiz"===this.module?(btn=document.querySelector("#mod_quiz-next-nav").cloneNode(!0),btn.className="tiny_cursive-fullpage-submit-btn",btn.style.margin=".5rem"):(btn=this.create("input"),btn.className="tiny_cursive-fullpage-submit-btn",btn.value=this.savechanges,btn.type="submit",btn.style.margin=".5rem"),"pdfannotator"===this.module){const style=document.createElement("style");style.id="cursiveForceStyle",style.textContent="\n .path-mod-pdfannotator #comment-wrapper h4,\n .path-mod-pdfannotator #comment-nav {\n margin: 0 !important;\n }\n ",document.head.appendChild(style)}const leftSide=this.create("div"),rightSide=this.create("div");let commonStyle={display:"flex",alignItems:"center",margin:"0 1rem"};Object.assign(leftSide.style,commonStyle),rightSide.id="tiny_cursive-fullpage-right-wrapper",Object.assign(rightSide.style,commonStyle),rightSide.appendChild(btn),leftSide.appendChild(assignName.cloneNode(!0)),header.appendChild(leftSide),header.appendChild(rightSide),p4.insertBefore(header,p4.firstChild),p2.style.backgroundColor="#efefef",Object.assign(current.style,{width:"750px",minWidth:"750px",boxShadow:"0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)"}),Object.assign(p1.style,{display:"flex",justifyContent:"center",outline:"none",margin:"2rem 0 0"});const style=this.create("style");style.id="tiny_cursive-fullpage-mode-style",style.textContent="\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }",document.head.appendChild(style);let iframeBody=(null===(_current$contentDocum=current.contentDocument)||void 0===_current$contentDocum?void 0:_current$contentDocum.body)||(null===(_current$contentWindo=current.contentWindow)||void 0===_current$contentWindo||null===(_current$contentWindo2=_current$contentWindo.document)||void 0===_current$contentWindo2?void 0:_current$contentWindo2.body);iframeBody&&(iframeBody.style.padding="0.5in"),p2.style.position="relative",null===(_document$getElementB=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB||_document$getElementB.remove();let toggle=this.create("div");toggle.id="cursive-fullpagemode-sidebar-toggle",toggle.innerHTML=_svg_repo.default.hamburger,p2.appendChild(toggle),p2.appendChild(this.docSideBar(statusBar))}normalizePage(editorId){var _document$getElementB2,_document$getElementB3,_current$contentDocum2,_current$contentWindo3,_current$contentWindo4,_document$head$queryS,_document$head$queryS2;null===(_document$getElementB2=document.getElementById("tiny_cursive-fullpage-custom-header"))||void 0===_document$getElementB2||_document$getElementB2.remove(),null===(_document$getElementB3=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB3||_document$getElementB3.remove();let current=document.getElementById(editorId),p1=current.parentElement,p2=p1.parentElement;Object.assign(p2.style,{backgroundColor:"",position:""}),Object.assign(current.style,{width:"",minWidth:"",boxShadow:""}),Object.assign(p1.style,{display:"",justifyContent:"",outline:"",margin:""}),p1.classList.remove("tiny-cursive-editor-container");let iframeBody=(null===(_current$contentDocum2=current.contentDocument)||void 0===_current$contentDocum2?void 0:_current$contentDocum2.body)||(null===(_current$contentWindo3=current.contentWindow)||void 0===_current$contentWindo3||null===(_current$contentWindo4=_current$contentWindo3.document)||void 0===_current$contentWindo4?void 0:_current$contentWindo4.body);iframeBody&&(iframeBody.style.padding="0"),null===(_document$head$queryS=document.head.querySelector("#tiny_cursive-fullpage-mode-style"))||void 0===_document$head$queryS||_document$head$queryS.remove(),null===(_document$head$queryS2=document.head.querySelector("#cursiveForceStyle"))||void 0===_document$head$queryS2||_document$head$queryS2.remove()}checkForumSubject(){const form=document.querySelector("#tiny_cursive-fullpage-right-wrapper > input"),msg=this.subjectnot;form&&form.addEventListener("click",(e=>{const subjectInput=document.getElementById("id_subject");let content=this.editor.getContent().trim();subjectInput&&""!==subjectInput.value.trim()&&""!==content||(e.preventDefault(),e.stopPropagation(),this.editor.windowManager.alert(msg))}))}getSidebarTitle(){const[assign,discus,quiz,lesson]=this.getText("sbTitle");switch(this.module){case"assign":return{title:assign,icon:_svg_repo.default.assignment};case"forum":return{title:discus,icon:_svg_repo.default.forum};case"lesson":return{title:lesson,icon:_svg_repo.default.forum};case"quiz":return{title:quiz,icon:_svg_repo.default.quiz};case"pdfannotator":return{title:"PDF Annotation",icon:_svg_repo.default.pdfannotator};default:return{title:"Page",icon:_svg_repo.default.quiz}}}getTimerBlock(module){switch(module){case"assign":return document.querySelector("#mod_assign_timelimit_block > div > div");case"forum":return document.querySelector("#mod_forum_timelimit_block");case"lesson":return document.querySelector("#lesson-timer");case"quiz":return document.querySelector("#quiz-time-left");default:return null}}getQuestionId(editoId){try{return editoId&&"string"==typeof editoId?editoId.replace(/^q(\d+):(\d+)_.*$/,"$1-$2"):""}catch(error){return window.console.error("Error getting question ID:",error),""}}initStrings(){[this.details,this.studentInfo,this.progress,this.description,this.replyingto,this.answeringto,this.importantdates,this.rubrics,this.subStatus,this.status,this.draft,this.draftnot,this.lastModified,this.gradings,this.gradenot,this.wordCount,this.timeleft,this.nolimit,this.name,this.userename,this.course,this.opened,this.due,this.overdue,this.remaining,this.savechanges,this.subjectnot]=this.getText("docSideBar")}getText(key){return JSON.parse(localStorage.getItem(key))||[]}create(tag){return document.createElement(tag)}},_exports.default})); -======= - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_svg_repo=(obj=_svg_repo)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(User,Rubrics,submission,modulename,editor,quizInfo){this.User=User,this.Rubrics=Rubrics,this.submission=submission,this.module=modulename,this.editor=editor,this.moduleIcon=_svg_repo.default.assignment,this.quizInfo=quizInfo,this.initStrings()}normalMode(){var _this$editor;let id=(null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id)+"_ifr";("assign"===this.module||"quiz"===this.module||"forum"===this.module||"lesson"===this.module)&&this.normalizePage(id)}fullPageMode(){var _this$editor4,_this$editor2;if("assign"===this.module)this.moduleIcon=_svg_repo.default.assignment,this.fullPageModule(null===(_this$editor2=this.editor)||void 0===_this$editor2?void 0:_this$editor2.id);else if("forum"===this.module){var _this$editor3;this.moduleIcon=_svg_repo.default.forum,this.fullPageModule(null===(_this$editor3=this.editor)||void 0===_this$editor3?void 0:_this$editor3.id)}else if("quiz"===this.module&&null!==(_this$editor4=this.editor)&&void 0!==_this$editor4&&_this$editor4.id){var _this$editor5;this.moduleIcon=_svg_repo.default.quiz,this.fullPageModule(null===(_this$editor5=this.editor)||void 0===_this$editor5?void 0:_this$editor5.id)}else if("lesson"===this.module){var _this$editor6;this.moduleIcon=_svg_repo.default.lesson,this.fullPageModule(null===(_this$editor6=this.editor)||void 0===_this$editor6?void 0:_this$editor6.id)}}docSideBar(status){var _this$editor7;const replyId=new URL(window.location.href).searchParams.get("reply"),toggle=document.querySelector("#cursive-fullpagemode-sidebar-toggle"),timelimitBlock=this.getTimerBlock(this.module),headerInfo=this.getSidebarTitle(),progressBar=document.querySelector(".box.progress_bar"),courseName=document.querySelector("#page-navbar > nav > ol > li:nth-child(1) > a"),courseDes=document.querySelector("#intro"),Dates=document.querySelector(".activity-dates");let openDate=null==Dates?void 0:Dates.querySelector("div:nth-child(1)"),dueDate=null==Dates?void 0:Dates.querySelector("div:nth-child(2)");const container=this.create("div");Object.assign(container,{id:"cursive-fullpagemode-sidebar",className:"bg-white h-100 shadow"}),Object.assign(container.style,{width:"300px",overflow:"auto"});const crossBtn=this.create("span");Object.assign(crossBtn,{id:"cursive-collapse-sidebar",className:"btn p-2",innerHTML:_svg_repo.default.close}),crossBtn.addEventListener("click",(()=>{container.style.transition="width 0.3s ease",container.style.width="0",toggle.style.display="flex"})),null==toggle||toggle.addEventListener("click",(function(){toggle.style.display="none",container.style.width="300px"}));const btnWrapper=this.create("div");Object.assign(btnWrapper,{padding:"0 1rem",position:"sticky",top:"0",backgroundColor:"white"}),btnWrapper.append(crossBtn);const header=this.create("div");header.className="border-bottom p-3 bg-light",Object.assign(header.style,{position:"sticky",top:"0"});const headerTitle=this.create("h3");headerTitle.className="mb-3 d-flex align-items-center",headerTitle.textContent="".concat(headerInfo.title," ").concat(this.details),headerTitle.style.fontWeight="600";const headerIcon=document.querySelector(".page-header-image > div");headerIcon&&headerTitle.prepend(headerIcon.cloneNode(!0));let wordCount=this.wordCounter(status);null!=timelimitBlock&&timelimitBlock.textContent?header.append(headerTitle,wordCount,this.timerCountDown(timelimitBlock)):header.append(headerTitle,wordCount);const content=this.create("div");if(content.className="p-3",content.append(this.createBox({bg:"bg-info",titleColor:"text-info",icon:_svg_repo.default.people,title:this.studentInfo,bodyHTML:this.generateStudentInfo(this.User,courseName)})),"lesson"===this.module&&progressBar&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.progress,bodyHTML:progressBar.innerHTML})),courseDes&&""!==(null==courseDes?void 0:courseDes.textContent.trim())&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:"".concat(this.getSidebarTitle().title," ").concat(this.description),bodyHTML:courseDes.innerHTML})),"forum"===this.module&&replyId){this.checkForumSubject();let replyPost=document.querySelector("#post-content-".concat(replyId));null!=replyPost&&replyPost.textContent.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.replyingto,bodyHTML:replyPost.textContent.trim()}))}if("quiz"===this.module&&null!==(_this$editor7=this.editor)&&void 0!==_this$editor7&&_this$editor7.id){var _this$editor8;let questionId=this.getQuestionId(null===(_this$editor8=this.editor)||void 0===_this$editor8?void 0:_this$editor8.id),question=document.querySelector("#question-".concat(questionId," .qtext")),intro=atob(this.quizInfo.intro);null!=question&&question.textContent.trim()&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:this.moduleIcon,title:this.answeringto,bodyHTML:question.textContent})),intro&&""!==intro.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:"".concat(this.quiz," ").concat(this.description),bodyHTML:intro})),Number(this.quizInfo.open)&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(Number(this.quizInfo.open),Number(this.quizInfo.close))}))}return Object.keys(this.Rubrics).length&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.rubrics,bodyHTML:this.generateRubrics(this.Rubrics)})),Dates&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(openDate,dueDate)})),"assign"===this.module&&content.append(this.createBox({bg:"bg-green",titleColor:"text-success",icon:this.moduleIcon,title:this.subStatus,bodyHTML:this.submissionStatus(this.submission)})),container.append(btnWrapper,header,content),container}createBox(_ref){let{bg:bg,titleColor:titleColor,icon:icon,title:title,bodyHTML:bodyHTML}=_ref;const box=this.create("div");box.className="tiny_cursive-fullpage-card ".concat(bg);const heading=this.create("h4");heading.className="tiny_cursive-fullpage-card-header ".concat(titleColor," d-flex align-items-center"),heading.innerHTML="".concat(icon," ").concat(title);const body=this.create("div");return body.className="tiny_cursive-fullpage-card-body",body.innerHTML=bodyHTML,box.append(heading,body),box}generateRubrics(Rubrics){const wrapper=this.create("div");return Rubrics.forEach((rubric=>{const rubricDiv=this.create("div");rubricDiv.className="tiny_cursive-rubric-card";const title=this.create("h3");title.className="tiny_cursive-rubric-title",title.textContent=rubric.description,rubricDiv.appendChild(title),Object.values(rubric.levels).forEach((level=>{const levelDiv=this.create("div"),score=Number(level.score);levelDiv.className=0===score?"tiny_cursive-rubric-level tiny_cursive-rubric-low":score<=2?"tiny_cursive-rubric-level tiny_cursive-rubric-mid":"tiny_cursive-rubric-level tiny_cursive-rubric-high",levelDiv.textContent="".concat(level.definition," / ").concat(level.score),rubricDiv.appendChild(levelDiv)})),wrapper.appendChild(rubricDiv)})),wrapper.innerHTML}submissionStatus(submission){var _submission$current,_submission$current2;const wrapper=this.create("div"),statusWrapper=this.create("div");statusWrapper.className="tiny_cursive-status-row";const statusName=this.create("span");statusName.textContent="".concat(this.status,":");const statusValue=this.create("span"),isNew="new"===(null==submission||null===(_submission$current=submission.current)||void 0===_submission$current?void 0:_submission$current.status);statusValue.textContent=isNew?this.draftnot:this.draft,statusValue.className="tiny_cursive-status-value ".concat(isNew?"tiny_cursive-status-red":"tiny_cursive-status-green"),statusWrapper.append(statusName,statusValue);const modifiedWrapper=this.create("div");modifiedWrapper.className="tiny_cursive-status-row";const modifiedName=this.create("span");modifiedName.textContent="".concat(this.lastModified,": ");const modifiedValue=this.create("span");if(null!=submission&&null!==(_submission$current2=submission.current)&&void 0!==_submission$current2&&_submission$current2.timemodified){const date=new Date(1e3*submission.current.timemodified);modifiedValue.textContent=this.formatDate(date)}else modifiedValue.textContent="N/A";modifiedWrapper.append(modifiedName,modifiedValue);const gradeWrapper=this.create("div");gradeWrapper.className="tiny_cursive-status-row";const gradeName=this.create("span");gradeName.textContent="".concat(this.gradings,": ");const gradeValue=this.create("span");return null!=submission&&submission.grade?gradeValue.textContent=Number(submission.grade.grade)>0?submission.grade.grade:this.gradenot:gradeValue.textContent=this.gradenot,gradeWrapper.append(gradeName,gradeValue),wrapper.append(statusWrapper,gradeWrapper,modifiedWrapper),wrapper.innerHTML}wordCounter(status){const wordCount=this.create("div"),labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");icon.className="me-2",icon.innerHTML=_svg_repo.default.assignment,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent="".concat(this.wordCount,":"),value.textContent="0",value.className="text-primary",value.style.fontWeight="600",value.style.fontSize="14px",wordCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2",wordCount.append(labelDiv,value),wordCount.style.fontSize="12px";return new MutationObserver((()=>{const newText=status.textContent.trim();value.textContent="".concat(newText.replace("words",""))})).observe(status,{characterData:!0,subtree:!0,childList:!0}),wordCount}timerCountDown(timer){let warningDiv=document.querySelector("#user-notifications > div");if(warningDiv){var _clone$querySelector;let clone=warningDiv.cloneNode(!0);null===(_clone$querySelector=clone.querySelector("button"))||void 0===_clone$querySelector||_clone$querySelector.remove(),this.editor.notificationManager.open({text:clone.textContent,type:"error"})}const timerCount=this.create("div");timerCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2";const labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");if(icon.innerHTML=_svg_repo.default.time,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent="".concat(this.timeleft,": }"),value.textContent="00:00:00",value.className=warningDiv?"text-danger":"text-primary",Object.assign(value.style,{fontWeight:"600",fontSize:"14px"}),timerCount.append(labelDiv,value),timerCount.style.fontSize="12px",timer){new MutationObserver((()=>{const newText=timer.textContent.trim();value.textContent="".concat(newText)})).observe(timer,{characterData:!0,subtree:!0,childList:!0})}else value.textContent=this.nolimit;return timerCount}generateStudentInfo(user,course){const wrapper=this.create("div"),nameWrapper=this.create("div"),usernameWrapper=this.create("div"),courseWrapper=this.create("div"),nameLabel=this.create("span"),nameValue=this.create("span"),usernameLabel=this.create("span"),usernameValue=this.create("span"),courseLabel=this.create("span"),courseValue=this.create("span");return nameLabel.textContent="".concat(this.name),nameValue.textContent=user.fullname,usernameLabel.textContent="".concat(this.userename,": "),usernameValue.textContent=user.username,courseLabel.textContent="".concat(this.course,": "),courseValue.textContent=course.title,nameWrapper.className="d-flex justify-content-between",usernameWrapper.className="d-flex justify-content-between",courseWrapper.className="d-flex justify-content-between",nameWrapper.append(nameLabel,nameValue),usernameWrapper.append(usernameLabel,usernameValue),courseWrapper.append(courseLabel,courseValue),wrapper.append(nameWrapper,usernameWrapper,courseWrapper),wrapper.innerHTML}generateImportantDates(open,due){const wrapper=this.create("div");let openDate=null,dueDate=null;const openedWrapper=this.create("div"),dueWrapper=this.create("div"),remainingWrapper=this.create("div"),openedLabel=this.create("span"),openedValue=this.create("span"),dueLabel=this.create("span"),dueValue=this.create("span"),remainingLabel=this.create("span"),remainingValue=this.create("span");return"quiz"===this.module?(openDate=1e3*open,dueDate=1e3*due):(openDate=this.extractDate(null==open?void 0:open.textContent),dueDate=this.extractDate(null==due?void 0:due.textContent)),openedLabel.textContent="".concat(this.opened,": "),openedValue.textContent=this.formatDate(openDate?new Date(openDate):null),openedValue.className="text-dark",dueLabel.textContent="".concat(this.due,": "),dueValue.textContent=this.formatDate(dueDate?new Date(dueDate):null),dueValue.className="text-danger",remainingLabel.textContent="".concat(this.remaining,": "),remainingValue.textContent=this.calculateDate(dueDate),remainingValue.className="text-danger",openedWrapper.className="d-flex justify-content-between",dueWrapper.className="d-flex justify-content-between",remainingWrapper.className="d-flex align-items-center justify-content-between mt-2 pt-2 border-top",openedWrapper.append(openedLabel,openedValue),dueWrapper.append(dueLabel,dueValue),remainingWrapper.append(remainingLabel,remainingValue),wrapper.append(openedWrapper,dueWrapper,remainingWrapper),wrapper.innerHTML}formatDate(date){if(!date)return"-";return date.toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"numeric",hour12:!0})}extractDate(text){if(!text)return"-";const parts=null==text?void 0:text.split(":");return parts.length>1?parts.slice(1).join(":").trim():text.trim()}calculateDate(date){if(!date)return"-";const diffMs=new Date(date)-new Date;if(diffMs<=0)return"Overdue";{const diffDays=Math.floor(diffMs/864e5),diffHours=Math.floor(diffMs/36e5%24);return"".concat(diffDays," days, ").concat(diffHours," hours")}}fullPageModule(module){var _current$contentDocum,_current$contentWindo,_current$contentWindo2,_document$getElementB;let current="quiz"===this.module?document.getElementById("".concat(module,"_ifr")):document.querySelector("#".concat(module,"_ifr")),p1=current.parentElement,p2=p1.parentElement,p4=p2.parentElement.parentElement,statusBar=document.querySelector(".tox-statusbar__right-container > button"),assignName=document.querySelector(".page-context-header"),header=this.create("div"),btn=null;assignName.classList.remove("mb-2"),header.id="tiny_cursive-fullpage-custom-header",Object.assign(header.style,{backgroundColor:"white",display:"flex",justifyContent:"space-between"}),"quiz"===this.module?(btn=document.querySelector("#mod_quiz-next-nav").cloneNode(!0),btn.className="tiny_cursive-fullpage-submit-btn",btn.style.margin=".5rem"):(btn=this.create("input"),btn.className="tiny_cursive-fullpage-submit-btn",btn.value=this.savechanges,btn.type="submit",btn.style.margin=".5rem");const leftSide=this.create("div"),rightSide=this.create("div");let commonStyle={display:"flex",alignItems:"center",margin:"0 1rem"};Object.assign(leftSide.style,commonStyle),rightSide.id="tiny_cursive-fullpage-right-wrapper",Object.assign(rightSide.style,commonStyle),rightSide.appendChild(btn),leftSide.appendChild(assignName.cloneNode(!0)),header.appendChild(leftSide),header.appendChild(rightSide),p4.insertBefore(header,p4.firstChild),p2.style.backgroundColor="#efefef",Object.assign(current.style,{width:"750px",minWidth:"750px",boxShadow:"0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)"}),Object.assign(p1.style,{display:"flex",justifyContent:"center",outline:"none",margin:"2rem 0 0"});const style=this.create("style");style.id="tiny_cursive-fullpage-mode-style",style.textContent="\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }",document.head.appendChild(style);let iframeBody=(null===(_current$contentDocum=current.contentDocument)||void 0===_current$contentDocum?void 0:_current$contentDocum.body)||(null===(_current$contentWindo=current.contentWindow)||void 0===_current$contentWindo||null===(_current$contentWindo2=_current$contentWindo.document)||void 0===_current$contentWindo2?void 0:_current$contentWindo2.body);iframeBody&&(iframeBody.style.padding="0.5in"),p2.style.position="relative",null===(_document$getElementB=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB||_document$getElementB.remove();let toggle=this.create("div");toggle.id="cursive-fullpagemode-sidebar-toggle",toggle.innerHTML=_svg_repo.default.hamburger,p2.appendChild(toggle),p2.appendChild(this.docSideBar(statusBar))}normalizePage(editorId){var _document$getElementB2,_document$getElementB3,_current$contentDocum2,_current$contentWindo3,_current$contentWindo4,_document$head$queryS;null===(_document$getElementB2=document.getElementById("tiny_cursive-fullpage-custom-header"))||void 0===_document$getElementB2||_document$getElementB2.remove(),null===(_document$getElementB3=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB3||_document$getElementB3.remove();let current=document.getElementById(editorId),p1=current.parentElement,p2=p1.parentElement;Object.assign(p2.style,{backgroundColor:"",position:""}),Object.assign(current.style,{width:"",minWidth:"",boxShadow:""}),Object.assign(p1.style,{display:"",justifyContent:"",outline:"",margin:""}),p1.classList.remove("tiny-cursive-editor-container");let iframeBody=(null===(_current$contentDocum2=current.contentDocument)||void 0===_current$contentDocum2?void 0:_current$contentDocum2.body)||(null===(_current$contentWindo3=current.contentWindow)||void 0===_current$contentWindo3||null===(_current$contentWindo4=_current$contentWindo3.document)||void 0===_current$contentWindo4?void 0:_current$contentWindo4.body);iframeBody&&(iframeBody.style.padding="0"),null===(_document$head$queryS=document.head.querySelector("#tiny_cursive-fullpage-mode-style"))||void 0===_document$head$queryS||_document$head$queryS.remove()}checkForumSubject(){const form=document.querySelector("#tiny_cursive-fullpage-right-wrapper > input"),msg=this.subjectnot;form&&form.addEventListener("click",(e=>{const subjectInput=document.getElementById("id_subject");let content=this.editor.getContent().trim();subjectInput&&""!==subjectInput.value.trim()&&""!==content||(e.preventDefault(),e.stopPropagation(),this.editor.windowManager.alert(msg))}))}getSidebarTitle(){const[assign,discus,quiz,lesson]=this.getText("sbTitle");switch(this.module){case"assign":return{title:assign,icon:_svg_repo.default.assignment};case"forum":return{title:discus,icon:_svg_repo.default.forum};case"lesson":return{title:lesson,icon:_svg_repo.default.forum};case"quiz":return{title:quiz,icon:_svg_repo.default.quiz};default:return{title:"Page",icon:_svg_repo.default.quiz}}}getTimerBlock(module){switch(module){case"assign":return document.querySelector("#mod_assign_timelimit_block > div > div");case"forum":return document.querySelector("#mod_forum_timelimit_block");case"lesson":return document.querySelector("#lesson-timer");case"quiz":return document.querySelector("#quiz-time-left");default:return null}}getQuestionId(editoId){try{return editoId&&"string"==typeof editoId?editoId.replace(/^q(\d+):(\d+)_.*$/,"$1-$2"):""}catch(error){return window.console.error("Error getting question ID:",error),""}}initStrings(){[this.details,this.studentInfo,this.progress,this.description,this.replyingto,this.answeringto,this.importantdates,this.rubrics,this.subStatus,this.status,this.draft,this.draftnot,this.lastModified,this.gradings,this.gradenot,this.wordCount,this.timeleft,this.nolimit,this.name,this.userename,this.course,this.opened,this.due,this.overdue,this.remaining,this.savechanges,this.subjectnot]=this.getText("docSideBar")}getText(key){return JSON.parse(localStorage.getItem(key))||[]}create(tag){return document.createElement(tag)}},_exports.default})); ->>>>>>> 8b275778ba140d4ec01f088ecfc426293a0a3296 + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_svg_repo=(obj=_svg_repo)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(User,Rubrics,submission,modulename,editor,quizInfo){this.User=User,this.Rubrics=Rubrics,this.submission=submission,this.module=modulename,this.editor=editor,this.moduleIcon=_svg_repo.default.assignment,this.quizInfo=quizInfo,this.initStrings()}normalMode(){var _this$editor;let id=(null===(_this$editor=this.editor)||void 0===_this$editor?void 0:_this$editor.id)+"_ifr";("assign"===this.module||"quiz"===this.module||"forum"===this.module||"lesson"===this.module||"pdfannotator"===this.module)&&this.normalizePage(id)}fullPageMode(){var _this$editor4,_this$editor2;if("assign"===this.module)this.moduleIcon=_svg_repo.default.assignment,this.fullPageModule(null===(_this$editor2=this.editor)||void 0===_this$editor2?void 0:_this$editor2.id);else if("forum"===this.module){var _this$editor3;this.moduleIcon=_svg_repo.default.forum,this.fullPageModule(null===(_this$editor3=this.editor)||void 0===_this$editor3?void 0:_this$editor3.id)}else if("quiz"===this.module&&null!==(_this$editor4=this.editor)&&void 0!==_this$editor4&&_this$editor4.id){var _this$editor5;this.moduleIcon=_svg_repo.default.quiz,this.fullPageModule(null===(_this$editor5=this.editor)||void 0===_this$editor5?void 0:_this$editor5.id)}else if("lesson"===this.module){var _this$editor6;this.moduleIcon=_svg_repo.default.lesson,this.fullPageModule(null===(_this$editor6=this.editor)||void 0===_this$editor6?void 0:_this$editor6.id)}else if("pdfannotator"===this.module){var _this$editor7;this.moduleIcon=_svg_repo.default.pdfannotator,this.fullPageModule(null===(_this$editor7=this.editor)||void 0===_this$editor7?void 0:_this$editor7.id)}}docSideBar(status){var _this$editor8;const replyId=new URL(window.location.href).searchParams.get("reply"),toggle=document.querySelector("#cursive-fullpagemode-sidebar-toggle"),timelimitBlock=this.getTimerBlock(this.module),headerInfo=this.getSidebarTitle(),progressBar=document.querySelector(".box.progress_bar"),courseName=document.querySelector("#page-navbar > nav > ol > li:nth-child(1) > a"),courseDes=document.querySelector("#intro"),Dates=document.querySelector(".activity-dates");let openDate=null==Dates?void 0:Dates.querySelector("div:nth-child(1)"),dueDate=null==Dates?void 0:Dates.querySelector("div:nth-child(2)");const container=this.create("div");Object.assign(container,{id:"cursive-fullpagemode-sidebar",className:"bg-white h-100 shadow"}),Object.assign(container.style,{width:"300px",overflow:"auto"});const crossBtn=this.create("span");Object.assign(crossBtn,{id:"cursive-collapse-sidebar",className:"btn p-2",innerHTML:_svg_repo.default.close}),crossBtn.addEventListener("click",(()=>{container.style.transition="width 0.3s ease",container.style.width="0",toggle.style.display="flex"})),null==toggle||toggle.addEventListener("click",(function(){toggle.style.display="none",container.style.width="300px"}));const btnWrapper=this.create("div");Object.assign(btnWrapper,{padding:"0 1rem",position:"sticky",top:"0",backgroundColor:"white"}),btnWrapper.append(crossBtn);const header=this.create("div");header.className="border-bottom p-3 bg-light",Object.assign(header.style,{position:"sticky",top:"0"});const headerTitle=this.create("h3");headerTitle.className="mb-3 d-flex align-items-center",headerTitle.textContent=`${headerInfo.title} ${this.details}`,headerTitle.style.fontWeight="600";const headerIcon=document.querySelector(".page-header-image > div");headerIcon&&headerTitle.prepend(headerIcon.cloneNode(!0));let wordCount=this.wordCounter(status);null!=timelimitBlock&&timelimitBlock.textContent?header.append(headerTitle,wordCount,this.timerCountDown(timelimitBlock)):header.append(headerTitle,wordCount);const content=this.create("div");if(content.className="p-3",content.append(this.createBox({bg:"bg-info",titleColor:"text-info",icon:_svg_repo.default.people,title:this.studentInfo,bodyHTML:this.generateStudentInfo(this.User,courseName)})),"lesson"===this.module&&progressBar&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.progress,bodyHTML:progressBar.innerHTML})),courseDes&&""!==(null==courseDes?void 0:courseDes.textContent.trim())){let fileSubDiv=document.querySelectorAll(".fileuploadsubmission");fileSubDiv&&fileSubDiv.forEach((Element=>{Element.style.verticalAlign="middle"})),content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:`${this.getSidebarTitle().title} ${this.description}`,bodyHTML:courseDes.innerHTML}))}if("forum"===this.module&&replyId){this.checkForumSubject();let replyPost=document.querySelector(`#post-content-${replyId}`);null!=replyPost&&replyPost.textContent.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.replyingto,bodyHTML:replyPost.textContent.trim()}))}if("quiz"===this.module&&null!==(_this$editor8=this.editor)&&void 0!==_this$editor8&&_this$editor8.id){var _this$editor9;let questionId=this.getQuestionId(null===(_this$editor9=this.editor)||void 0===_this$editor9?void 0:_this$editor9.id),question=document.querySelector(`#question-${questionId} .qtext`),intro=atob(this.quizInfo.intro);null!=question&&question.textContent.trim()&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:this.moduleIcon,title:this.answeringto,bodyHTML:question.textContent})),intro&&""!==intro.trim()&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:`${this.quiz} ${this.description}`,bodyHTML:intro})),Number(this.quizInfo.open)&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(Number(this.quizInfo.open),Number(this.quizInfo.close))}))}return Object.keys(this.Rubrics).length&&content.append(this.createBox({bg:"bg-gray",titleColor:"text-dark",icon:this.moduleIcon,title:this.rubrics,bodyHTML:this.generateRubrics(this.Rubrics)})),Dates&&content.append(this.createBox({bg:"bg-amber",titleColor:"text-dark",icon:_svg_repo.default.time,title:this.importantdates,bodyHTML:this.generateImportantDates(openDate,dueDate)})),"assign"===this.module&&content.append(this.createBox({bg:"bg-green",titleColor:"text-success",icon:this.moduleIcon,title:this.subStatus,bodyHTML:this.submissionStatus(this.submission)})),container.append(btnWrapper,header,content),container}createBox(_ref){let{bg:bg,titleColor:titleColor,icon:icon,title:title,bodyHTML:bodyHTML}=_ref;const box=this.create("div");box.className=`tiny_cursive-fullpage-card ${bg}`;const heading=this.create("h4");heading.className=`tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`,heading.innerHTML=`${icon} ${title}`;const body=this.create("div");return body.className="tiny_cursive-fullpage-card-body",body.innerHTML=bodyHTML,box.append(heading,body),box}generateRubrics(Rubrics){const wrapper=this.create("div");return Rubrics.forEach((rubric=>{const rubricDiv=this.create("div");rubricDiv.className="tiny_cursive-rubric-card";const title=this.create("h3");title.className="tiny_cursive-rubric-title",title.textContent=rubric.description,rubricDiv.appendChild(title),Object.values(rubric.levels).forEach((level=>{const levelDiv=this.create("div"),score=Number(level.score);levelDiv.className=0===score?"tiny_cursive-rubric-level tiny_cursive-rubric-low":score<=2?"tiny_cursive-rubric-level tiny_cursive-rubric-mid":"tiny_cursive-rubric-level tiny_cursive-rubric-high",levelDiv.textContent=`${level.definition} / ${level.score}`,rubricDiv.appendChild(levelDiv)})),wrapper.appendChild(rubricDiv)})),wrapper.innerHTML}submissionStatus(submission){var _submission$current,_submission$current2;const wrapper=this.create("div"),statusWrapper=this.create("div");statusWrapper.className="tiny_cursive-status-row";const statusName=this.create("span");statusName.textContent=`${this.status}:`;const statusValue=this.create("span"),isNew="new"===(null==submission||null===(_submission$current=submission.current)||void 0===_submission$current?void 0:_submission$current.status);statusValue.textContent=isNew?this.draftnot:this.draft,statusValue.className="tiny_cursive-status-value "+(isNew?"tiny_cursive-status-red":"tiny_cursive-status-green"),statusWrapper.append(statusName,statusValue);const modifiedWrapper=this.create("div");modifiedWrapper.className="tiny_cursive-status-row";const modifiedName=this.create("span");modifiedName.textContent=`${this.lastModified}: `;const modifiedValue=this.create("span");if(null!=submission&&null!==(_submission$current2=submission.current)&&void 0!==_submission$current2&&_submission$current2.timemodified){const date=new Date(1e3*submission.current.timemodified);modifiedValue.textContent=this.formatDate(date)}else modifiedValue.textContent="N/A";modifiedWrapper.append(modifiedName,modifiedValue);const gradeWrapper=this.create("div");gradeWrapper.className="tiny_cursive-status-row";const gradeName=this.create("span");gradeName.textContent=`${this.gradings}: `;const gradeValue=this.create("span");return null!=submission&&submission.grade?gradeValue.textContent=Number(submission.grade.grade)>0?submission.grade.grade:this.gradenot:gradeValue.textContent=this.gradenot,gradeWrapper.append(gradeName,gradeValue),wrapper.append(statusWrapper,gradeWrapper,modifiedWrapper),wrapper.innerHTML}wordCounter(status){const wordCount=this.create("div"),labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");icon.className="me-2",icon.innerHTML=_svg_repo.default.assignment,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent=`${this.wordCount}:`,value.textContent="0",value.className="text-primary",value.style.fontWeight="600",value.style.fontSize="14px",wordCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2",wordCount.append(labelDiv,value),wordCount.style.fontSize="12px";return new MutationObserver((()=>{const newText=status.textContent.trim();value.textContent=`${newText.replace("words","")}`})).observe(status,{characterData:!0,subtree:!0,childList:!0}),wordCount}timerCountDown(timer){let warningDiv=document.querySelector("#user-notifications > div");if(warningDiv){var _clone$querySelector;let clone=warningDiv.cloneNode(!0);null===(_clone$querySelector=clone.querySelector("button"))||void 0===_clone$querySelector||_clone$querySelector.remove(),this.editor.notificationManager.open({text:clone.textContent,type:"error"})}const timerCount=this.create("div");timerCount.className="bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2";const labelDiv=this.create("div"),label=this.create("span"),value=this.create("span"),icon=this.create("span");if(icon.innerHTML=_svg_repo.default.time,labelDiv.appendChild(icon),labelDiv.append(label),label.textContent=`${this.timeleft}:`,value.textContent="00:00:00",value.className=warningDiv?"text-danger":"text-primary",Object.assign(value.style,{fontWeight:"600",fontSize:"14px"}),timerCount.append(labelDiv,value),timerCount.style.fontSize="12px",timer){new MutationObserver((()=>{const newText=timer.textContent.trim();value.textContent=`${newText}`})).observe(timer,{characterData:!0,subtree:!0,childList:!0})}else value.textContent=this.nolimit;return timerCount}generateStudentInfo(user,course){const wrapper=this.create("div"),nameWrapper=this.create("div"),usernameWrapper=this.create("div"),courseWrapper=this.create("div"),nameLabel=this.create("strong"),nameValue=this.create("span"),usernameLabel=this.create("strong"),usernameValue=this.create("span"),courseLabel=this.create("strong"),courseValue=this.create("span");return nameLabel.textContent=`${this.name}`,nameValue.textContent=user.fullname,usernameLabel.textContent=`${this.userename}: `,usernameValue.textContent=user.username,courseLabel.textContent=`${this.course}: `,courseValue.textContent=course.title,usernameLabel.className="cfw-bold me-2",usernameValue.className="cursiveFw-wrap",courseLabel.className="cfw-bold me-2",courseValue.className="cursiveFw-wrap",nameLabel.className="cfw-bold me-2",nameValue.className="cursiveFw-wrap",nameWrapper.append(nameLabel,nameValue),usernameWrapper.append(usernameLabel,usernameValue),courseWrapper.append(courseLabel,courseValue),wrapper.append(nameWrapper,usernameWrapper,courseWrapper),wrapper.innerHTML}generateImportantDates(open,due){const wrapper=this.create("div");let openDate=null,dueDate=null;const openedWrapper=this.create("div"),dueWrapper=this.create("div"),remainingWrapper=this.create("div"),openedLabel=this.create("span"),openedValue=this.create("span"),dueLabel=this.create("span"),dueValue=this.create("span"),remainingLabel=this.create("span"),remainingValue=this.create("span");return"quiz"===this.module?(openDate=1e3*open,dueDate=1e3*due):(openDate=this.extractDate(null==open?void 0:open.textContent),dueDate=this.extractDate(null==due?void 0:due.textContent)),openedLabel.textContent=`${this.opened}: `,openedValue.textContent=this.formatDate(openDate?new Date(openDate):null),openedValue.className="text-dark",dueLabel.textContent=`${this.due}: `,dueValue.textContent=this.formatDate(dueDate?new Date(dueDate):null),dueValue.className="text-danger",remainingLabel.textContent=`${this.remaining}: `,remainingValue.textContent=this.calculateDate(dueDate),remainingValue.className="text-danger",openedWrapper.className="d-flex justify-content-between",dueWrapper.className="d-flex justify-content-between",remainingWrapper.className="d-flex align-items-center justify-content-between mt-2 pt-2 border-top",openedWrapper.append(openedLabel,openedValue),dueWrapper.append(dueLabel,dueValue),remainingWrapper.append(remainingLabel,remainingValue),wrapper.append(openedWrapper,dueWrapper,remainingWrapper),wrapper.innerHTML}formatDate(date){if(!date)return"-";return date.toLocaleString("en-US",{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"numeric",hour12:!0})}extractDate(text){if(!text)return"-";const parts=null==text?void 0:text.split(":");return parts.length>1?parts.slice(1).join(":").trim():text.trim()}calculateDate(date){if(!date)return"-";const diffMs=new Date(date)-new Date;if(diffMs<=0)return"Overdue";return`${Math.floor(diffMs/864e5)} days, ${Math.floor(diffMs/36e5%24)} hours`}fullPageModule(module){var _current$contentDocum,_current$contentWindo,_current$contentWindo2,_document$getElementB;let current="quiz"===this.module?document.getElementById(`${module}_ifr`):document.querySelector(`#${module}_ifr`),p1=current.parentElement,p2=p1.parentElement,p4=p2.parentElement.parentElement,statusBar=document.querySelector(".tox-statusbar__right-container > button"),assignName=document.querySelector(".page-context-header"),header=this.create("div"),btn=null;if(assignName.classList.remove("mb-2"),header.id="tiny_cursive-fullpage-custom-header",Object.assign(header.style,{backgroundColor:"white",display:"flex",justifyContent:"space-between"}),"quiz"===this.module?(btn=document.querySelector("#mod_quiz-next-nav").cloneNode(!0),btn.className="tiny_cursive-fullpage-submit-btn",btn.style.margin=".5rem"):(btn=this.create("input"),btn.className="tiny_cursive-fullpage-submit-btn",btn.value=this.savechanges,btn.type="submit",btn.style.margin=".5rem"),"pdfannotator"===this.module){const style=document.createElement("style");style.id="cursiveForceStyle",style.textContent="\n .path-mod-pdfannotator #comment-wrapper h4,\n .path-mod-pdfannotator #comment-nav {\n margin: 0 !important;\n }\n ",document.head.appendChild(style)}const leftSide=this.create("div"),rightSide=this.create("div");let commonStyle={display:"flex",alignItems:"center",margin:"0 1rem"};Object.assign(leftSide.style,commonStyle),rightSide.id="tiny_cursive-fullpage-right-wrapper",Object.assign(rightSide.style,commonStyle),rightSide.appendChild(btn),leftSide.appendChild(assignName.cloneNode(!0)),header.appendChild(leftSide),header.appendChild(rightSide),p4.insertBefore(header,p4.firstChild),p2.style.backgroundColor="#efefef",Object.assign(current.style,{width:"750px",minWidth:"750px",boxShadow:"0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)"}),Object.assign(p1.style,{display:"flex",justifyContent:"center",outline:"none",margin:"2rem 0 0"});const style=this.create("style");style.id="tiny_cursive-fullpage-mode-style",style.textContent="\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }",document.head.appendChild(style);let iframeBody=(null===(_current$contentDocum=current.contentDocument)||void 0===_current$contentDocum?void 0:_current$contentDocum.body)||(null===(_current$contentWindo=current.contentWindow)||void 0===_current$contentWindo||null===(_current$contentWindo2=_current$contentWindo.document)||void 0===_current$contentWindo2?void 0:_current$contentWindo2.body);iframeBody&&(iframeBody.style.padding="0.5in"),p2.style.position="relative",null===(_document$getElementB=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB||_document$getElementB.remove();let toggle=this.create("div");toggle.id="cursive-fullpagemode-sidebar-toggle",toggle.innerHTML=_svg_repo.default.hamburger,p2.appendChild(toggle),p2.appendChild(this.docSideBar(statusBar))}normalizePage(editorId){var _document$getElementB2,_document$getElementB3,_current$contentDocum2,_current$contentWindo3,_current$contentWindo4,_document$head$queryS,_document$head$queryS2;null===(_document$getElementB2=document.getElementById("tiny_cursive-fullpage-custom-header"))||void 0===_document$getElementB2||_document$getElementB2.remove(),null===(_document$getElementB3=document.getElementById("cursive-fullpagemode-sidebar"))||void 0===_document$getElementB3||_document$getElementB3.remove();let current=document.getElementById(editorId),p1=current.parentElement,p2=p1.parentElement;Object.assign(p2.style,{backgroundColor:"",position:""}),Object.assign(current.style,{width:"",minWidth:"",boxShadow:""}),Object.assign(p1.style,{display:"",justifyContent:"",outline:"",margin:""}),p1.classList.remove("tiny-cursive-editor-container");let iframeBody=(null===(_current$contentDocum2=current.contentDocument)||void 0===_current$contentDocum2?void 0:_current$contentDocum2.body)||(null===(_current$contentWindo3=current.contentWindow)||void 0===_current$contentWindo3||null===(_current$contentWindo4=_current$contentWindo3.document)||void 0===_current$contentWindo4?void 0:_current$contentWindo4.body);iframeBody&&(iframeBody.style.padding="0"),null===(_document$head$queryS=document.head.querySelector("#tiny_cursive-fullpage-mode-style"))||void 0===_document$head$queryS||_document$head$queryS.remove(),null===(_document$head$queryS2=document.head.querySelector("#cursiveForceStyle"))||void 0===_document$head$queryS2||_document$head$queryS2.remove()}checkForumSubject(){const form=document.querySelector("#tiny_cursive-fullpage-right-wrapper > input"),msg=this.subjectnot;form&&form.addEventListener("click",(e=>{const subjectInput=document.getElementById("id_subject");let content=this.editor.getContent().trim();subjectInput&&""!==subjectInput.value.trim()&&""!==content||(e.preventDefault(),e.stopPropagation(),this.editor.windowManager.alert(msg))}))}getSidebarTitle(){const[assign,discus,quiz,lesson]=this.getText("sbTitle");switch(this.module){case"assign":return{title:assign,icon:_svg_repo.default.assignment};case"forum":return{title:discus,icon:_svg_repo.default.forum};case"lesson":return{title:lesson,icon:_svg_repo.default.forum};case"quiz":return{title:quiz,icon:_svg_repo.default.quiz};case"pdfannotator":return{title:"PDF Annotation",icon:_svg_repo.default.pdfannotator};default:return{title:"Page",icon:_svg_repo.default.quiz}}}getTimerBlock(module){switch(module){case"assign":return document.querySelector("#mod_assign_timelimit_block > div > div");case"forum":return document.querySelector("#mod_forum_timelimit_block");case"lesson":return document.querySelector("#lesson-timer");case"quiz":return document.querySelector("#quiz-time-left");default:return null}}getQuestionId(editoId){try{return editoId&&"string"==typeof editoId?editoId.replace(/^q(\d+):(\d+)_.*$/,"$1-$2"):""}catch(error){return window.console.error("Error getting question ID:",error),""}}initStrings(){[this.details,this.studentInfo,this.progress,this.description,this.replyingto,this.answeringto,this.importantdates,this.rubrics,this.subStatus,this.status,this.draft,this.draftnot,this.lastModified,this.gradings,this.gradenot,this.wordCount,this.timeleft,this.nolimit,this.name,this.userename,this.course,this.opened,this.due,this.overdue,this.remaining,this.savechanges,this.subjectnot]=this.getText("docSideBar")}getText(key){return JSON.parse(localStorage.getItem(key))||[]}create(tag){return document.createElement(tag)}},_exports.default})); //# sourceMappingURL=document_view.min.js.map \ No newline at end of file diff --git a/amd/build/document_view.min.js.map b/amd/build/document_view.min.js.map index fcec9724..e291a61f 100644 --- a/amd/build/document_view.min.js.map +++ b/amd/build/document_view.min.js.map @@ -1,5 +1 @@ -<<<<<<< HEAD -{"version":3,"file":"document_view.min.js","sources":["../src/document_view.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 provides functionality for document view management in the Tiny editor,\n * including full page mode display and sidebar information\n * @module tiny_cursive/document_view\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Icons from 'tiny_cursive/svg_repo';\nexport default class DocumentView {\n\n constructor(User, Rubrics, submission, modulename, editor, quizInfo) {\n this.User = User;\n this.Rubrics = Rubrics;\n this.submission = submission;\n this.module = modulename;\n this.editor = editor;\n this.moduleIcon = Icons.assignment;\n this.quizInfo = quizInfo;\n this.initStrings();\n }\n\n normalMode() {\n let id = this.editor?.id + \"_ifr\";\n if (this.module === 'assign') {\n this.normalizePage(id);\n } else if (this.module === 'quiz') {\n this.normalizePage(id);\n } else if (this.module === 'forum') {\n this.normalizePage(id);\n } else if (this.module === 'lesson') {\n this.normalizePage(id);\n } else if(this.module === 'pdfannotator') {\n this.normalizePage(id);\n }\n }\n\n fullPageMode() {\n\n if (this.module === 'assign') {\n this.moduleIcon = Icons.assignment;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'forum') {\n this.moduleIcon = Icons.forum;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'quiz' && this.editor?.id) {\n this.moduleIcon = Icons.quiz;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'lesson') {\n this.moduleIcon = Icons.lesson;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'pdfannotator') {\n this.moduleIcon = Icons.pdfannotator;\n this.fullPageModule(this.editor?.id);\n }\n }\n\n docSideBar(status) {\n\n const url = new URL(window.location.href);\n const replyId = url.searchParams.get(\"reply\");\n const toggle = document.querySelector('#cursive-fullpagemode-sidebar-toggle');\n const timelimitBlock = this.getTimerBlock(this.module);\n const headerInfo = this.getSidebarTitle();\n const progressBar = document.querySelector('.box.progress_bar');\n\n const courseName = document.querySelector('#page-navbar > nav > ol > li:nth-child(1) > a');\n const courseDes = document.querySelector('#intro');\n const Dates = document.querySelector('.activity-dates');\n\n let openDate = Dates?.querySelector('div:nth-child(1)');\n let dueDate = Dates?.querySelector('div:nth-child(2)');\n\n const container = this.create('div');\n Object.assign(container, {\n id: 'cursive-fullpagemode-sidebar',\n className: 'bg-white h-100 shadow'\n });\n Object.assign(container.style, {\n width: '300px',\n overflow: 'auto'\n });\n\n const crossBtn = this.create('span');\n Object.assign(crossBtn, {\n id: 'cursive-collapse-sidebar',\n className: 'btn p-2',\n innerHTML: Icons.close\n });\n\n crossBtn.addEventListener('click', () => {\n container.style.transition = 'width 0.3s ease';\n container.style.width = '0';\n toggle.style.display = 'flex';\n });\n toggle?.addEventListener('click', function() {\n toggle.style.display = 'none';\n container.style.width = '300px';\n });\n\n const btnWrapper = this.create('div');\n Object.assign(btnWrapper, {\n padding: '0 1rem',\n position: 'sticky',\n top: '0',\n backgroundColor: 'white'\n });\n btnWrapper.append(crossBtn);\n\n\n const header = this.create('div');\n header.className = 'border-bottom p-3 bg-light';\n Object.assign(header.style, {\n position: 'sticky',\n top: '0'\n });\n\n const headerTitle = this.create('h3');\n headerTitle.className = 'mb-3 d-flex align-items-center';\n headerTitle.textContent = `${headerInfo.title} ${this.details}`;\n headerTitle.style.fontWeight = '600';\n\n const headerIcon = document.querySelector('.page-header-image > div');\n if (headerIcon) {\n headerTitle.prepend(headerIcon.cloneNode(true));\n }\n\n let wordCount = this.wordCounter(status);\n if (timelimitBlock?.textContent) {\n header.append(headerTitle, wordCount, this.timerCountDown(timelimitBlock));\n } else {\n header.append(headerTitle, wordCount);\n }\n\n const content = this.create('div');\n content.className = 'p-3';\n\n content.append(\n this.createBox({\n bg: 'bg-info',\n titleColor: 'text-info',\n icon: Icons.people,\n title: this.studentInfo,\n bodyHTML: this.generateStudentInfo(this.User, courseName)\n })\n );\n\n if (this.module === 'lesson' && progressBar) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.progress,\n bodyHTML: progressBar.innerHTML\n })\n );\n }\n\n if (courseDes && courseDes?.textContent.trim() !== '') {\n let fileSubDiv = document.querySelectorAll('.fileuploadsubmission');\n if (fileSubDiv) {\n fileSubDiv.forEach(Element => {\n Element.style.verticalAlign = 'middle';\n });\n }\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.getSidebarTitle().title} ${this.description}`,\n bodyHTML: courseDes.innerHTML\n })\n );\n }\n\n if (this.module === 'forum' && replyId) {\n this.checkForumSubject();\n let replyPost = document.querySelector(`#post-content-${replyId}`);\n if (replyPost?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.replyingto,\n bodyHTML: replyPost.textContent.trim()\n })\n );\n }\n }\n\n if (this.module === 'quiz' && this.editor?.id) {\n\n let questionId = this.getQuestionId(this.editor?.id);\n let question = document.querySelector(`#question-${questionId} .qtext`);\n let intro = atob(this.quizInfo.intro);\n\n if (question?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.answeringto,\n bodyHTML: question.textContent\n })\n );\n }\n\n if (intro && intro.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.quiz} ${this.description}`,\n bodyHTML: intro\n })\n );\n }\n\n if (Number(this.quizInfo.open)) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(Number(this.quizInfo.open), Number(this.quizInfo.close))\n })\n );\n }\n }\n\n if (Object.keys(this.Rubrics).length) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.rubrics,\n bodyHTML: this.generateRubrics(this.Rubrics)\n })\n );\n }\n\n if (Dates) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(openDate, dueDate)\n })\n );\n }\n if (this.module === 'assign') {\n content.append(\n this.createBox({\n bg: 'bg-green',\n titleColor: 'text-success',\n icon: this.moduleIcon,\n title: this.subStatus,\n bodyHTML: this.submissionStatus(this.submission)\n })\n );\n }\n\n container.append(btnWrapper, header, content);\n return container;\n\n }\n // Helper to create info boxes\n createBox({bg, titleColor, icon, title, bodyHTML}) {\n const box = this.create('div');\n box.className = `tiny_cursive-fullpage-card ${bg}`;\n\n const heading = this.create('h4');\n heading.className = `tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`;\n heading.innerHTML = `${icon} ${title}`;\n\n const body = this.create('div');\n body.className = `tiny_cursive-fullpage-card-body`;\n body.innerHTML = bodyHTML;\n\n box.append(heading, body);\n return box;\n }\n\n generateRubrics(Rubrics) {\n const wrapper = this.create('div');\n\n Rubrics.forEach(rubric => {\n const rubricDiv = this.create('div');\n rubricDiv.className = 'tiny_cursive-rubric-card';\n\n const title = this.create('h3');\n title.className = 'tiny_cursive-rubric-title';\n title.textContent = rubric.description;\n rubricDiv.appendChild(title);\n\n Object.values(rubric.levels).forEach(level => {\n const levelDiv = this.create('div');\n const score = Number(level.score);\n\n // Assign background color class based on score\n if (score === 0) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-low';\n } else if (score <= 2) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-mid';\n } else {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-high';\n }\n\n levelDiv.textContent = `${level.definition} / ${level.score}`;\n rubricDiv.appendChild(levelDiv);\n });\n\n wrapper.appendChild(rubricDiv);\n });\n\n return wrapper.innerHTML;\n }\n\n submissionStatus(submission) {\n const wrapper = this.create('div');\n\n const statusWrapper = this.create('div');\n statusWrapper.className = 'tiny_cursive-status-row';\n\n const statusName = this.create('span');\n statusName.textContent = `${this.status}:`;\n\n const statusValue = this.create('span');\n const isNew = submission?.current?.status === 'new';\n statusValue.textContent = isNew ? this.draftnot : this.draft;\n statusValue.className = `tiny_cursive-status-value ${isNew ? 'tiny_cursive-status-red' : 'tiny_cursive-status-green'}`;\n\n statusWrapper.append(statusName, statusValue);\n\n const modifiedWrapper = this.create('div');\n modifiedWrapper.className = 'tiny_cursive-status-row';\n\n const modifiedName = this.create('span');\n modifiedName.textContent = `${this.lastModified}: `;\n\n const modifiedValue = this.create('span');\n if (submission?.current?.timemodified) {\n const date = new Date(submission.current.timemodified * 1000);\n modifiedValue.textContent = this.formatDate(date);\n } else {\n modifiedValue.textContent = 'N/A';\n }\n modifiedWrapper.append(modifiedName, modifiedValue);\n\n const gradeWrapper = this.create('div');\n gradeWrapper.className = 'tiny_cursive-status-row';\n\n const gradeName = this.create('span');\n gradeName.textContent = `${this.gradings}: `;\n\n const gradeValue = this.create('span');\n\n if (submission?.grade) {\n gradeValue.textContent = Number(submission.grade.grade) > 0\n ? submission.grade.grade\n : this.gradenot;\n } else {\n gradeValue.textContent = this.gradenot;\n }\n\n gradeWrapper.append(gradeName, gradeValue);\n wrapper.append(statusWrapper, gradeWrapper, modifiedWrapper);\n return wrapper.innerHTML;\n }\n\n wordCounter(status) {\n const wordCount = this.create('div');\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n\n icon.className = 'me-2';\n icon.innerHTML = Icons.assignment;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.wordCount}:`;\n value.textContent = '0';\n value.className = 'text-primary';\n value.style.fontWeight = '600';\n value.style.fontSize = '14px';\n\n wordCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n wordCount.append(labelDiv, value);\n wordCount.style.fontSize = '12px';\n\n const observer = new MutationObserver(() => {\n const newText = status.textContent.trim();\n value.textContent = `${newText.replace('words', '')}`;\n });\n\n observer.observe(status, {\n characterData: true,\n subtree: true,\n childList: true\n });\n\n return wordCount;\n }\n\n\n timerCountDown(timer) {\n\n let warningDiv = document.querySelector('#user-notifications > div');\n if (warningDiv) {\n let clone = warningDiv.cloneNode(true);\n clone.querySelector('button')?.remove();\n this.editor.notificationManager.open({\n text: clone.textContent,\n type: 'error'\n });\n }\n\n\n const timerCount = this.create('div');\n timerCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n icon.innerHTML = Icons.time;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.timeleft}:`;\n value.textContent = '00:00:00';\n value.className = warningDiv ? 'text-danger' : 'text-primary';\n Object.assign(value.style, {\n fontWeight: '600',\n fontSize: '14px'\n });\n\n\n timerCount.append(labelDiv, value);\n timerCount.style.fontSize = '12px';\n if (timer) {\n const observer = new MutationObserver(() => {\n const newText = timer.textContent.trim();\n value.textContent = `${newText}`;\n });\n observer.observe(timer, {\n characterData: true,\n subtree: true,\n childList: true\n });\n } else {\n value.textContent = this.nolimit;\n }\n\n\n return timerCount;\n }\n\n\n generateStudentInfo(user, course) {\n\n const wrapper = this.create('div');\n\n const nameWrapper = this.create('div');\n const usernameWrapper = this.create('div');\n const courseWrapper = this.create('div');\n\n const nameLabel = this.create('strong');\n const nameValue = this.create('span');\n const usernameLabel = this.create('strong');\n const usernameValue = this.create('span');\n const courseLabel = this.create('strong');\n const courseValue = this.create('span');\n\n nameLabel.textContent = `${this.name}`;\n nameValue.textContent = user.fullname;\n\n usernameLabel.textContent = `${this.userename}: `;\n usernameValue.textContent = user.username;\n\n courseLabel.textContent = `${this.course}: `;\n courseValue.textContent = course.title;\n\n usernameLabel.className = 'cfw-bold me-2';\n usernameValue.className = 'cursiveFw-wrap';\n courseLabel.className = 'cfw-bold me-2';\n courseValue.className = 'cursiveFw-wrap';\n nameLabel.className = 'cfw-bold me-2';\n nameValue.className = 'cursiveFw-wrap';\n\n nameWrapper.append(nameLabel, nameValue);\n usernameWrapper.append(usernameLabel, usernameValue);\n courseWrapper.append(courseLabel, courseValue);\n\n wrapper.append(nameWrapper, usernameWrapper, courseWrapper);\n\n return wrapper.innerHTML;\n\n }\n\n generateImportantDates(open, due) {\n\n const wrapper = this.create('div');\n let openDate = null;\n let dueDate = null;\n\n const openedWrapper = this.create('div');\n const dueWrapper = this.create('div');\n const remainingWrapper = this.create('div');\n\n const openedLabel = this.create('span');\n const openedValue = this.create('span');\n const dueLabel = this.create('span');\n const dueValue = this.create('span');\n const remainingLabel = this.create('span');\n const remainingValue = this.create('span');\n if (this.module === 'quiz') {\n openDate = open * 1000;\n dueDate = due * 1000;\n } else {\n openDate = this.extractDate(open?.textContent);\n dueDate = this.extractDate(due?.textContent);\n }\n\n openedLabel.textContent = `${this.opened}: `;\n openedValue.textContent = this.formatDate(openDate ? new Date(openDate) : null);\n openedValue.className = 'text-dark';\n\n dueLabel.textContent = `${this.due}: `;\n dueValue.textContent = this.formatDate(dueDate ? new Date(dueDate) : null);\n dueValue.className = 'text-danger';\n\n remainingLabel.textContent = `${this.remaining}: `;\n remainingValue.textContent = this.calculateDate(dueDate);\n remainingValue.className = 'text-danger';\n\n openedWrapper.className = 'd-flex justify-content-between';\n dueWrapper.className = 'd-flex justify-content-between';\n remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top';\n\n openedWrapper.append(openedLabel, openedValue);\n dueWrapper.append(dueLabel, dueValue);\n remainingWrapper.append(remainingLabel, remainingValue);\n\n wrapper.append(openedWrapper, dueWrapper, remainingWrapper);\n\n return wrapper.innerHTML;\n }\n\n formatDate(date) {\n if (!date) {\n return '-';\n }\n\n let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true};\n return date.toLocaleString('en-US', options);\n }\n\n extractDate(text) {\n if (!text) {\n return '-';\n }\n // Split on first colon and return the right part\n const parts = text?.split(':');\n if (parts.length > 1) {\n return parts.slice(1).join(':').trim();\n }\n\n return text.trim();\n }\n\n\n calculateDate(date) {\n if (!date) {\n return '-';\n }\n const date1 = new Date(date); // Due date (local time)\n const now = new Date(); // Current date/time\n\n // Calculate the difference in milliseconds\n const diffMs = date1 - now;\n\n // Convert to days, hours, minutes\n if (diffMs <= 0) {\n return \"Overdue\";\n } else {\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n const diffHours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);\n\n return `${diffDays} days, ${diffHours} hours`;\n }\n\n }\n\n fullPageModule(module) {\n let current = this.module === 'quiz' ?\n document.getElementById(`${module}_ifr`) : document.querySelector(`#${module}_ifr`);\n\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n let p3 = p2.parentElement;\n let p4 = p3.parentElement;\n\n let statusBar = document.querySelector('.tox-statusbar__right-container > button');\n let assignName = document.querySelector('.page-context-header');\n let header = this.create('div');\n let btn = null;\n\n assignName.classList.remove('mb-2');\n header.id = 'tiny_cursive-fullpage-custom-header';\n Object.assign(header.style, {\n backgroundColor: 'white',\n display: 'flex',\n justifyContent: 'space-between'\n });\n\n if (this.module === 'quiz') {\n btn = document.querySelector('#mod_quiz-next-nav').cloneNode(true);\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.style.margin = '.5rem';\n } else {\n btn = this.create('input');\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.value = this.savechanges;\n btn.type = 'submit';\n btn.style.margin = '.5rem';\n }\n\n if (this.module === 'pdfannotator') {\n const style = document.createElement('style');\n style.id = 'cursiveForceStyle';\n style.textContent = `\n .path-mod-pdfannotator #comment-wrapper h4,\n .path-mod-pdfannotator #comment-nav {\n margin: 0 !important;\n }\n `;\n document.head.appendChild(style);\n }\n\n const leftSide = this.create('div');\n const rightSide = this.create('div');\n let commonStyle = {\n display: 'flex',\n alignItems: 'center',\n margin: '0 1rem'\n };\n\n Object.assign(leftSide.style, commonStyle);\n rightSide.id = 'tiny_cursive-fullpage-right-wrapper';\n Object.assign(rightSide.style, commonStyle);\n\n rightSide.appendChild(btn);\n leftSide.appendChild(assignName.cloneNode(true));\n\n header.appendChild(leftSide);\n header.appendChild(rightSide);\n\n p4.insertBefore(header, p4.firstChild);\n p2.style.backgroundColor = '#efefef';\n Object.assign(current.style, {\n width: '750px',\n minWidth: '750px',\n boxShadow: '0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)'\n });\n\n Object.assign(p1.style, {\n display: 'flex',\n justifyContent: 'center',\n outline: 'none',\n margin: '2rem 0 0'\n });\n const style = this.create('style');\n style.id = 'tiny_cursive-fullpage-mode-style';\n style.textContent = `\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }`;\n document.head.appendChild(style);\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n\n if (iframeBody) {\n iframeBody.style.padding = '0.5in';\n }\n p2.style.position = 'relative';\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let toggle = this.create('div');\n toggle.id = 'cursive-fullpagemode-sidebar-toggle';\n toggle.innerHTML = Icons.hamburger;\n p2.appendChild(toggle);\n p2.appendChild(this.docSideBar(statusBar));\n }\n\n normalizePage(editorId) {\n document.getElementById('tiny_cursive-fullpage-custom-header')?.remove();\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let current = document.getElementById(editorId);\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n\n Object.assign(p2.style, {\n backgroundColor: \"\",\n position: \"\"\n });\n\n Object.assign(current.style, {\n width: '',\n minWidth: '',\n boxShadow: '',\n });\n\n Object.assign(p1.style, {\n display: '',\n justifyContent: '',\n outline: '',\n margin: ''\n });\n\n p1.classList.remove('tiny-cursive-editor-container');\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n if (iframeBody) {\n iframeBody.style.padding = '0';\n }\n document.head.querySelector('#tiny_cursive-fullpage-mode-style')?.remove();\n document.head.querySelector('#cursiveForceStyle')?.remove();\n }\n\n checkForumSubject() {\n const form = document.querySelector('#tiny_cursive-fullpage-right-wrapper > input');\n const msg = this.subjectnot;\n\n if (form) {\n form.addEventListener('click', (e) => {\n const subjectInput = document.getElementById('id_subject');\n let content = this.editor.getContent().trim();\n if (!subjectInput || subjectInput.value.trim() === '' || content === '') {\n e.preventDefault();\n e.stopPropagation();\n this.editor.windowManager.alert(msg);\n }\n });\n }\n }\n\n getSidebarTitle() {\n const [assign, discus, quiz, lesson] = this.getText('sbTitle');\n switch (this.module) {\n case 'assign':\n return {title: assign, icon: Icons.assignment};\n case 'forum':\n return {title: discus, icon: Icons.forum};\n case 'lesson':\n return {title: lesson, icon: Icons.forum};\n case 'quiz':\n return {title: quiz, icon: Icons.quiz};\n case 'pdfannotator':\n return {title: 'PDF Annotation', icon: Icons.pdfannotator};\n default:\n return {title: 'Page', icon: Icons.quiz};\n }\n }\n\n getTimerBlock(module) {\n switch (module) {\n case 'assign':\n return document.querySelector('#mod_assign_timelimit_block > div > div');\n case 'forum':\n return document.querySelector('#mod_forum_timelimit_block');\n case 'lesson':\n return document.querySelector('#lesson-timer');\n case 'quiz':\n return document.querySelector('#quiz-time-left');\n default:\n return null;\n }\n }\n\n getQuestionId(editoId) {\n try {\n if (!editoId || typeof editoId !== 'string') {\n return '';\n }\n return editoId.replace(/^q(\\d+):(\\d+)_.*$/, \"$1-$2\");\n } catch (error) {\n window.console.error('Error getting question ID:', error);\n return '';\n }\n }\n\n initStrings() {\n [\n this.details,\n this.studentInfo,\n this.progress,\n this.description,\n this.replyingto,\n this.answeringto,\n this.importantdates,\n this.rubrics,\n this.subStatus,\n this.status,\n this.draft,\n this.draftnot,\n this.lastModified,\n this.gradings,\n this.gradenot,\n this.wordCount,\n this.timeleft,\n this.nolimit,\n this.name,\n this.userename,\n this.course,\n this.opened,\n this.due,\n this.overdue,\n this.remaining,\n this.savechanges,\n this.subjectnot\n ] = this.getText('docSideBar');\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key)) || [];\n }\n\n create(tag) {\n return document.createElement(tag);\n }\n\n}"],"names":["constructor","User","Rubrics","submission","modulename","editor","quizInfo","module","moduleIcon","Icons","assignment","initStrings","normalMode","id","this","normalizePage","fullPageMode","fullPageModule","_this$editor2","forum","_this$editor3","_this$editor4","quiz","_this$editor5","lesson","_this$editor6","pdfannotator","_this$editor7","docSideBar","status","replyId","URL","window","location","href","searchParams","get","toggle","document","querySelector","timelimitBlock","getTimerBlock","headerInfo","getSidebarTitle","progressBar","courseName","courseDes","Dates","openDate","dueDate","container","create","Object","assign","className","style","width","overflow","crossBtn","innerHTML","close","addEventListener","transition","display","btnWrapper","padding","position","top","backgroundColor","append","header","headerTitle","textContent","title","details","fontWeight","headerIcon","prepend","cloneNode","wordCount","wordCounter","timerCountDown","content","createBox","bg","titleColor","icon","people","studentInfo","bodyHTML","generateStudentInfo","progress","trim","fileSubDiv","querySelectorAll","forEach","Element","verticalAlign","description","checkForumSubject","replyPost","replyingto","_this$editor8","questionId","getQuestionId","_this$editor9","question","intro","atob","answeringto","Number","open","time","importantdates","generateImportantDates","keys","length","rubrics","generateRubrics","subStatus","submissionStatus","box","heading","body","wrapper","rubric","rubricDiv","appendChild","values","levels","level","levelDiv","score","definition","statusWrapper","statusName","statusValue","isNew","current","draftnot","draft","modifiedWrapper","modifiedName","lastModified","modifiedValue","_submission$current2","timemodified","date","Date","formatDate","gradeWrapper","gradeName","gradings","gradeValue","grade","gradenot","labelDiv","label","value","fontSize","MutationObserver","newText","replace","observe","characterData","subtree","childList","timer","warningDiv","clone","remove","notificationManager","text","type","timerCount","timeleft","nolimit","user","course","nameWrapper","usernameWrapper","courseWrapper","nameLabel","nameValue","usernameLabel","usernameValue","courseLabel","courseValue","name","fullname","userename","username","due","openedWrapper","dueWrapper","remainingWrapper","openedLabel","openedValue","dueLabel","dueValue","remainingLabel","remainingValue","extractDate","opened","remaining","calculateDate","toLocaleString","year","month","day","hour","minute","hour12","parts","split","slice","join","diffMs","diffDays","Math","floor","diffHours","getElementById","p1","parentElement","p2","p4","statusBar","assignName","btn","classList","justifyContent","margin","savechanges","createElement","head","leftSide","rightSide","commonStyle","alignItems","insertBefore","firstChild","minWidth","boxShadow","outline","iframeBody","contentDocument","contentWindow","_current$contentWindo","_current$contentWindo2","hamburger","editorId","_current$contentWindo3","_current$contentWindo4","form","msg","subjectnot","e","subjectInput","getContent","preventDefault","stopPropagation","windowManager","alert","discus","getText","editoId","error","console","overdue","key","JSON","parse","localStorage","getItem","tag"],"mappings":"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"} -======= -{"version":3,"file":"document_view.min.js","sources":["../src/document_view.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 provides functionality for document view management in the Tiny editor,\n * including full page mode display and sidebar information\n * @module tiny_cursive/document_view\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Icons from 'tiny_cursive/svg_repo';\nexport default class DocumentView {\n\n constructor(User, Rubrics, submission, modulename, editor, quizInfo) {\n this.User = User;\n this.Rubrics = Rubrics;\n this.submission = submission;\n this.module = modulename;\n this.editor = editor;\n this.moduleIcon = Icons.assignment;\n this.quizInfo = quizInfo;\n this.initStrings();\n }\n\n normalMode() {\n let id = this.editor?.id + \"_ifr\";\n if (this.module === 'assign') {\n this.normalizePage(id);\n } else if (this.module === 'quiz') {\n this.normalizePage(id);\n } else if (this.module === 'forum') {\n this.normalizePage(id);\n } else if (this.module === 'lesson') {\n this.normalizePage(id);\n }\n }\n\n fullPageMode() {\n\n if (this.module === 'assign') {\n this.moduleIcon = Icons.assignment;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'forum') {\n this.moduleIcon = Icons.forum;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'quiz' && this.editor?.id) {\n this.moduleIcon = Icons.quiz;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'lesson') {\n this.moduleIcon = Icons.lesson;\n this.fullPageModule(this.editor?.id);\n }\n }\n\n docSideBar(status) {\n\n const url = new URL(window.location.href);\n const replyId = url.searchParams.get(\"reply\");\n const toggle = document.querySelector('#cursive-fullpagemode-sidebar-toggle');\n const timelimitBlock = this.getTimerBlock(this.module);\n const headerInfo = this.getSidebarTitle();\n const progressBar = document.querySelector('.box.progress_bar');\n\n const courseName = document.querySelector('#page-navbar > nav > ol > li:nth-child(1) > a');\n const courseDes = document.querySelector('#intro');\n const Dates = document.querySelector('.activity-dates');\n\n let openDate = Dates?.querySelector('div:nth-child(1)');\n let dueDate = Dates?.querySelector('div:nth-child(2)');\n\n const container = this.create('div');\n Object.assign(container, {\n id: 'cursive-fullpagemode-sidebar',\n className: 'bg-white h-100 shadow'\n });\n Object.assign(container.style, {\n width: '300px',\n overflow: 'auto'\n });\n\n const crossBtn = this.create('span');\n Object.assign(crossBtn, {\n id: 'cursive-collapse-sidebar',\n className: 'btn p-2',\n innerHTML: Icons.close\n });\n\n crossBtn.addEventListener('click', () => {\n container.style.transition = 'width 0.3s ease';\n container.style.width = '0';\n toggle.style.display = 'flex';\n });\n toggle?.addEventListener('click', function() {\n toggle.style.display = 'none';\n container.style.width = '300px';\n });\n\n const btnWrapper = this.create('div');\n Object.assign(btnWrapper, {\n padding: '0 1rem',\n position: 'sticky',\n top: '0',\n backgroundColor: 'white'\n });\n btnWrapper.append(crossBtn);\n\n\n const header = this.create('div');\n header.className = 'border-bottom p-3 bg-light';\n Object.assign(header.style, {\n position: 'sticky',\n top: '0'\n });\n\n const headerTitle = this.create('h3');\n headerTitle.className = 'mb-3 d-flex align-items-center';\n headerTitle.textContent = `${headerInfo.title} ${this.details}`;\n headerTitle.style.fontWeight = '600';\n\n const headerIcon = document.querySelector('.page-header-image > div');\n if (headerIcon) {\n headerTitle.prepend(headerIcon.cloneNode(true));\n }\n\n let wordCount = this.wordCounter(status);\n if (timelimitBlock?.textContent) {\n header.append(headerTitle, wordCount, this.timerCountDown(timelimitBlock));\n } else {\n header.append(headerTitle, wordCount);\n }\n\n const content = this.create('div');\n content.className = 'p-3';\n\n content.append(\n this.createBox({\n bg: 'bg-info',\n titleColor: 'text-info',\n icon: Icons.people,\n title: this.studentInfo,\n bodyHTML: this.generateStudentInfo(this.User, courseName)\n })\n );\n\n if (this.module === 'lesson' && progressBar) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.progress,\n bodyHTML: progressBar.innerHTML\n })\n );\n }\n\n if (courseDes && courseDes?.textContent.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.getSidebarTitle().title} ${this.description}`,\n bodyHTML: courseDes.innerHTML\n })\n );\n }\n\n if (this.module === 'forum' && replyId) {\n this.checkForumSubject();\n let replyPost = document.querySelector(`#post-content-${replyId}`);\n if (replyPost?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.replyingto,\n bodyHTML: replyPost.textContent.trim()\n })\n );\n }\n }\n\n if (this.module === 'quiz' && this.editor?.id) {\n\n let questionId = this.getQuestionId(this.editor?.id);\n let question = document.querySelector(`#question-${questionId} .qtext`);\n let intro = atob(this.quizInfo.intro);\n\n if (question?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.answeringto,\n bodyHTML: question.textContent\n })\n );\n }\n\n if (intro && intro.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.quiz} ${this.description}`,\n bodyHTML: intro\n })\n );\n }\n\n if (Number(this.quizInfo.open)) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(Number(this.quizInfo.open), Number(this.quizInfo.close))\n })\n );\n }\n }\n\n if (Object.keys(this.Rubrics).length) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.rubrics,\n bodyHTML: this.generateRubrics(this.Rubrics)\n })\n );\n }\n\n if (Dates) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(openDate, dueDate)\n })\n );\n }\n if (this.module === 'assign') {\n content.append(\n this.createBox({\n bg: 'bg-green',\n titleColor: 'text-success',\n icon: this.moduleIcon,\n title: this.subStatus,\n bodyHTML: this.submissionStatus(this.submission)\n })\n );\n }\n\n container.append(btnWrapper, header, content);\n return container;\n\n }\n // Helper to create info boxes\n createBox({bg, titleColor, icon, title, bodyHTML}) {\n const box = this.create('div');\n box.className = `tiny_cursive-fullpage-card ${bg}`;\n\n const heading = this.create('h4');\n heading.className = `tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`;\n heading.innerHTML = `${icon} ${title}`;\n\n const body = this.create('div');\n body.className = `tiny_cursive-fullpage-card-body`;\n body.innerHTML = bodyHTML;\n\n box.append(heading, body);\n return box;\n }\n\n generateRubrics(Rubrics) {\n const wrapper = this.create('div');\n\n Rubrics.forEach(rubric => {\n const rubricDiv = this.create('div');\n rubricDiv.className = 'tiny_cursive-rubric-card';\n\n const title = this.create('h3');\n title.className = 'tiny_cursive-rubric-title';\n title.textContent = rubric.description;\n rubricDiv.appendChild(title);\n\n Object.values(rubric.levels).forEach(level => {\n const levelDiv = this.create('div');\n const score = Number(level.score);\n\n // Assign background color class based on score\n if (score === 0) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-low';\n } else if (score <= 2) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-mid';\n } else {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-high';\n }\n\n levelDiv.textContent = `${level.definition} / ${level.score}`;\n rubricDiv.appendChild(levelDiv);\n });\n\n wrapper.appendChild(rubricDiv);\n });\n\n return wrapper.innerHTML;\n }\n\n submissionStatus(submission) {\n const wrapper = this.create('div');\n\n const statusWrapper = this.create('div');\n statusWrapper.className = 'tiny_cursive-status-row';\n\n const statusName = this.create('span');\n statusName.textContent = `${this.status}:`;\n\n const statusValue = this.create('span');\n const isNew = submission?.current?.status === 'new';\n statusValue.textContent = isNew ? this.draftnot : this.draft;\n statusValue.className = `tiny_cursive-status-value ${isNew ? 'tiny_cursive-status-red' : 'tiny_cursive-status-green'}`;\n\n statusWrapper.append(statusName, statusValue);\n\n const modifiedWrapper = this.create('div');\n modifiedWrapper.className = 'tiny_cursive-status-row';\n\n const modifiedName = this.create('span');\n modifiedName.textContent = `${this.lastModified}: `;\n\n const modifiedValue = this.create('span');\n if (submission?.current?.timemodified) {\n const date = new Date(submission.current.timemodified * 1000);\n modifiedValue.textContent = this.formatDate(date);\n } else {\n modifiedValue.textContent = 'N/A';\n }\n modifiedWrapper.append(modifiedName, modifiedValue);\n\n const gradeWrapper = this.create('div');\n gradeWrapper.className = 'tiny_cursive-status-row';\n\n const gradeName = this.create('span');\n gradeName.textContent = `${this.gradings}: `;\n\n const gradeValue = this.create('span');\n\n if (submission?.grade) {\n gradeValue.textContent = Number(submission.grade.grade) > 0\n ? submission.grade.grade\n : this.gradenot;\n } else {\n gradeValue.textContent = this.gradenot;\n }\n\n gradeWrapper.append(gradeName, gradeValue);\n wrapper.append(statusWrapper, gradeWrapper, modifiedWrapper);\n return wrapper.innerHTML;\n }\n\n wordCounter(status) {\n const wordCount = this.create('div');\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n\n icon.className = 'me-2';\n icon.innerHTML = Icons.assignment;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.wordCount}:`;\n value.textContent = '0';\n value.className = 'text-primary';\n value.style.fontWeight = '600';\n value.style.fontSize = '14px';\n\n wordCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n wordCount.append(labelDiv, value);\n wordCount.style.fontSize = '12px';\n\n const observer = new MutationObserver(() => {\n const newText = status.textContent.trim();\n value.textContent = `${newText.replace('words', '')}`;\n });\n\n observer.observe(status, {\n characterData: true,\n subtree: true,\n childList: true\n });\n\n return wordCount;\n }\n\n\n timerCountDown(timer) {\n\n let warningDiv = document.querySelector('#user-notifications > div');\n if (warningDiv) {\n let clone = warningDiv.cloneNode(true);\n clone.querySelector('button')?.remove();\n this.editor.notificationManager.open({\n text: clone.textContent,\n type: 'error'\n });\n }\n\n\n const timerCount = this.create('div');\n timerCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n icon.innerHTML = Icons.time;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.timeleft}: }`;\n value.textContent = '00:00:00';\n value.className = warningDiv ? 'text-danger' : 'text-primary';\n Object.assign(value.style, {\n fontWeight: '600',\n fontSize: '14px'\n });\n\n\n timerCount.append(labelDiv, value);\n timerCount.style.fontSize = '12px';\n if (timer) {\n const observer = new MutationObserver(() => {\n const newText = timer.textContent.trim();\n value.textContent = `${newText}`;\n });\n observer.observe(timer, {\n characterData: true,\n subtree: true,\n childList: true\n });\n } else {\n value.textContent = this.nolimit;\n }\n\n\n return timerCount;\n }\n\n\n generateStudentInfo(user, course) {\n\n const wrapper = this.create('div');\n\n const nameWrapper = this.create('div');\n const usernameWrapper = this.create('div');\n const courseWrapper = this.create('div');\n\n const nameLabel = this.create('span');\n const nameValue = this.create('span');\n const usernameLabel = this.create('span');\n const usernameValue = this.create('span');\n const courseLabel = this.create('span');\n const courseValue = this.create('span');\n\n nameLabel.textContent = `${this.name}`;\n nameValue.textContent = user.fullname;\n\n usernameLabel.textContent = `${this.userename}: `;\n usernameValue.textContent = user.username;\n\n courseLabel.textContent = `${this.course}: `;\n courseValue.textContent = course.title;\n\n nameWrapper.className = 'd-flex justify-content-between';\n usernameWrapper.className = 'd-flex justify-content-between';\n courseWrapper.className = 'd-flex justify-content-between';\n\n nameWrapper.append(nameLabel, nameValue);\n usernameWrapper.append(usernameLabel, usernameValue);\n courseWrapper.append(courseLabel, courseValue);\n\n wrapper.append(nameWrapper, usernameWrapper, courseWrapper);\n\n return wrapper.innerHTML;\n\n }\n\n generateImportantDates(open, due) {\n\n const wrapper = this.create('div');\n let openDate = null;\n let dueDate = null;\n\n const openedWrapper = this.create('div');\n const dueWrapper = this.create('div');\n const remainingWrapper = this.create('div');\n\n const openedLabel = this.create('span');\n const openedValue = this.create('span');\n const dueLabel = this.create('span');\n const dueValue = this.create('span');\n const remainingLabel = this.create('span');\n const remainingValue = this.create('span');\n if (this.module === 'quiz') {\n openDate = open * 1000;\n dueDate = due * 1000;\n } else {\n openDate = this.extractDate(open?.textContent);\n dueDate = this.extractDate(due?.textContent);\n }\n\n openedLabel.textContent = `${this.opened}: `;\n openedValue.textContent = this.formatDate(openDate ? new Date(openDate) : null);\n openedValue.className = 'text-dark';\n\n dueLabel.textContent = `${this.due}: `;\n dueValue.textContent = this.formatDate(dueDate ? new Date(dueDate) : null);\n dueValue.className = 'text-danger';\n\n remainingLabel.textContent = `${this.remaining}: `;\n remainingValue.textContent = this.calculateDate(dueDate);\n remainingValue.className = 'text-danger';\n\n openedWrapper.className = 'd-flex justify-content-between';\n dueWrapper.className = 'd-flex justify-content-between';\n remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top';\n\n openedWrapper.append(openedLabel, openedValue);\n dueWrapper.append(dueLabel, dueValue);\n remainingWrapper.append(remainingLabel, remainingValue);\n\n wrapper.append(openedWrapper, dueWrapper, remainingWrapper);\n\n return wrapper.innerHTML;\n }\n\n formatDate(date) {\n if (!date) {\n return '-';\n }\n\n let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true};\n return date.toLocaleString('en-US', options);\n }\n\n extractDate(text) {\n if (!text) {\n return '-';\n }\n // Split on first colon and return the right part\n const parts = text?.split(':');\n if (parts.length > 1) {\n return parts.slice(1).join(':').trim();\n }\n\n return text.trim();\n }\n\n\n calculateDate(date) {\n if (!date) {\n return '-';\n }\n const date1 = new Date(date); // Due date (local time)\n const now = new Date(); // Current date/time\n\n // Calculate the difference in milliseconds\n const diffMs = date1 - now;\n\n // Convert to days, hours, minutes\n if (diffMs <= 0) {\n return \"Overdue\";\n } else {\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n const diffHours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);\n\n return `${diffDays} days, ${diffHours} hours`;\n }\n\n }\n\n fullPageModule(module) {\n let current = this.module === 'quiz' ?\n document.getElementById(`${module}_ifr`) : document.querySelector(`#${module}_ifr`);\n\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n let p3 = p2.parentElement;\n let p4 = p3.parentElement;\n\n let statusBar = document.querySelector('.tox-statusbar__right-container > button');\n let assignName = document.querySelector('.page-context-header');\n let header = this.create('div');\n let btn = null;\n\n assignName.classList.remove('mb-2');\n header.id = 'tiny_cursive-fullpage-custom-header';\n Object.assign(header.style, {\n backgroundColor: 'white',\n display: 'flex',\n justifyContent: 'space-between'\n });\n\n if (this.module === 'quiz') {\n btn = document.querySelector('#mod_quiz-next-nav').cloneNode(true);\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.style.margin = '.5rem';\n } else {\n btn = this.create('input');\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.value = this.savechanges;\n btn.type = 'submit';\n btn.style.margin = '.5rem';\n }\n\n\n const leftSide = this.create('div');\n const rightSide = this.create('div');\n let commonStyle = {\n display: 'flex',\n alignItems: 'center',\n margin: '0 1rem'\n };\n\n Object.assign(leftSide.style, commonStyle);\n rightSide.id = 'tiny_cursive-fullpage-right-wrapper';\n Object.assign(rightSide.style, commonStyle);\n\n rightSide.appendChild(btn);\n leftSide.appendChild(assignName.cloneNode(true));\n\n header.appendChild(leftSide);\n header.appendChild(rightSide);\n\n p4.insertBefore(header, p4.firstChild);\n p2.style.backgroundColor = '#efefef';\n Object.assign(current.style, {\n width: '750px',\n minWidth: '750px',\n boxShadow: '0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)'\n });\n\n Object.assign(p1.style, {\n display: 'flex',\n justifyContent: 'center',\n outline: 'none',\n margin: '2rem 0 0'\n });\n const style = this.create('style');\n style.id = 'tiny_cursive-fullpage-mode-style';\n style.textContent = `\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }`;\n document.head.appendChild(style);\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n\n if (iframeBody) {\n iframeBody.style.padding = '0.5in';\n }\n p2.style.position = 'relative';\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let toggle = this.create('div');\n toggle.id = 'cursive-fullpagemode-sidebar-toggle';\n toggle.innerHTML = Icons.hamburger;\n p2.appendChild(toggle);\n p2.appendChild(this.docSideBar(statusBar));\n }\n\n normalizePage(editorId) {\n document.getElementById('tiny_cursive-fullpage-custom-header')?.remove();\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let current = document.getElementById(editorId);\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n\n Object.assign(p2.style, {\n backgroundColor: \"\",\n position: \"\"\n });\n\n Object.assign(current.style, {\n width: '',\n minWidth: '',\n boxShadow: '',\n });\n\n Object.assign(p1.style, {\n display: '',\n justifyContent: '',\n outline: '',\n margin: ''\n });\n\n p1.classList.remove('tiny-cursive-editor-container');\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n if (iframeBody) {\n iframeBody.style.padding = '0';\n }\n document.head.querySelector('#tiny_cursive-fullpage-mode-style')?.remove();\n }\n\n checkForumSubject() {\n const form = document.querySelector('#tiny_cursive-fullpage-right-wrapper > input');\n const msg = this.subjectnot;\n\n if (form) {\n form.addEventListener('click', (e) => {\n const subjectInput = document.getElementById('id_subject');\n let content = this.editor.getContent().trim();\n if (!subjectInput || subjectInput.value.trim() === '' || content === '') {\n e.preventDefault();\n e.stopPropagation();\n this.editor.windowManager.alert(msg);\n }\n });\n }\n }\n\n getSidebarTitle() {\n const [assign, discus, quiz, lesson] = this.getText('sbTitle');\n switch (this.module) {\n case 'assign':\n return {title: assign, icon: Icons.assignment};\n case 'forum':\n return {title: discus, icon: Icons.forum};\n case 'lesson':\n return {title: lesson, icon: Icons.forum};\n case 'quiz':\n return {title: quiz, icon: Icons.quiz};\n default:\n return {title: 'Page', icon: Icons.quiz};\n }\n }\n\n getTimerBlock(module) {\n switch (module) {\n case 'assign':\n return document.querySelector('#mod_assign_timelimit_block > div > div');\n case 'forum':\n return document.querySelector('#mod_forum_timelimit_block');\n case 'lesson':\n return document.querySelector('#lesson-timer');\n case 'quiz':\n return document.querySelector('#quiz-time-left');\n default:\n return null;\n }\n }\n\n getQuestionId(editoId) {\n try {\n if (!editoId || typeof editoId !== 'string') {\n return '';\n }\n return editoId.replace(/^q(\\d+):(\\d+)_.*$/, \"$1-$2\");\n } catch (error) {\n window.console.error('Error getting question ID:', error);\n return '';\n }\n }\n\n initStrings() {\n [\n this.details,\n this.studentInfo,\n this.progress,\n this.description,\n this.replyingto,\n this.answeringto,\n this.importantdates,\n this.rubrics,\n this.subStatus,\n this.status,\n this.draft,\n this.draftnot,\n this.lastModified,\n this.gradings,\n this.gradenot,\n this.wordCount,\n this.timeleft,\n this.nolimit,\n this.name,\n this.userename,\n this.course,\n this.opened,\n this.due,\n this.overdue,\n this.remaining,\n this.savechanges,\n this.subjectnot\n ] = this.getText('docSideBar');\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key)) || [];\n }\n\n create(tag) {\n return document.createElement(tag);\n }\n\n}"],"names":["constructor","User","Rubrics","submission","modulename","editor","quizInfo","module","moduleIcon","Icons","assignment","initStrings","normalMode","id","this","normalizePage","fullPageMode","fullPageModule","_this$editor2","forum","_this$editor3","_this$editor4","quiz","_this$editor5","lesson","_this$editor6","docSideBar","status","replyId","URL","window","location","href","searchParams","get","toggle","document","querySelector","timelimitBlock","getTimerBlock","headerInfo","getSidebarTitle","progressBar","courseName","courseDes","Dates","openDate","dueDate","container","create","Object","assign","className","style","width","overflow","crossBtn","innerHTML","close","addEventListener","transition","display","btnWrapper","padding","position","top","backgroundColor","append","header","headerTitle","textContent","title","details","fontWeight","headerIcon","prepend","cloneNode","wordCount","wordCounter","timerCountDown","content","createBox","bg","titleColor","icon","people","studentInfo","bodyHTML","generateStudentInfo","progress","trim","description","checkForumSubject","replyPost","replyingto","_this$editor7","questionId","getQuestionId","_this$editor8","question","intro","atob","answeringto","Number","open","time","importantdates","generateImportantDates","keys","length","rubrics","generateRubrics","subStatus","submissionStatus","box","heading","body","wrapper","forEach","rubric","rubricDiv","appendChild","values","levels","level","levelDiv","score","definition","statusWrapper","statusName","statusValue","isNew","current","draftnot","draft","modifiedWrapper","modifiedName","lastModified","modifiedValue","_submission$current2","timemodified","date","Date","formatDate","gradeWrapper","gradeName","gradings","gradeValue","grade","gradenot","labelDiv","label","value","fontSize","MutationObserver","newText","replace","observe","characterData","subtree","childList","timer","warningDiv","clone","remove","notificationManager","text","type","timerCount","timeleft","nolimit","user","course","nameWrapper","usernameWrapper","courseWrapper","nameLabel","nameValue","usernameLabel","usernameValue","courseLabel","courseValue","name","fullname","userename","username","due","openedWrapper","dueWrapper","remainingWrapper","openedLabel","openedValue","dueLabel","dueValue","remainingLabel","remainingValue","extractDate","opened","remaining","calculateDate","toLocaleString","year","month","day","hour","minute","hour12","parts","split","slice","join","diffMs","diffDays","Math","floor","diffHours","getElementById","p1","parentElement","p2","p4","statusBar","assignName","btn","classList","justifyContent","margin","savechanges","leftSide","rightSide","commonStyle","alignItems","insertBefore","firstChild","minWidth","boxShadow","outline","head","iframeBody","contentDocument","contentWindow","_current$contentWindo","_current$contentWindo2","hamburger","editorId","_current$contentWindo3","_current$contentWindo4","form","msg","subjectnot","e","subjectInput","getContent","preventDefault","stopPropagation","windowManager","alert","discus","getText","editoId","error","console","overdue","key","JSON","parse","localStorage","getItem","tag","createElement"],"mappings":";;;;;;;+KA0BIA,YAAYC,KAAMC,QAASC,WAAYC,WAAYC,OAAQC,eAClDL,KAAOA,UACPC,QAAUA,aACVC,WAAaA,gBACbI,OAASH,gBACTC,OAASA,YACTG,WAAaC,kBAAMC,gBACnBJ,SAAWA,cACXK,cAGTC,kCACQC,8BAAUR,mDAAQQ,IAAK,QACP,WAAhBC,KAAKP,QAEkB,SAAhBO,KAAKP,QAEW,UAAhBO,KAAKP,QAEW,WAAhBO,KAAKP,cALPQ,cAAcF,IAU3BG,kDAEwB,WAAhBF,KAAKP,YACAC,WAAaC,kBAAMC,gBACnBO,qCAAeH,KAAKT,uCAALa,cAAaL,SAC9B,GAAoB,UAAhBC,KAAKP,OAAoB,wBAC3BC,WAAaC,kBAAMU,WACnBF,qCAAeH,KAAKT,uCAALe,cAAaP,SAC9B,GAAoB,SAAhBC,KAAKP,8BAAqBO,KAAKT,iCAALgB,cAAaR,GAAI,wBAC7CL,WAAaC,kBAAMa,UACnBL,qCAAeH,KAAKT,uCAALkB,cAAaV,SAC9B,GAAoB,WAAhBC,KAAKP,OAAqB,wBAC5BC,WAAaC,kBAAMe,YACnBP,qCAAeH,KAAKT,uCAALoB,cAAaZ,KAIzCa,WAAWC,gCAGDC,QADM,IAAIC,IAAIC,OAAOC,SAASC,MAChBC,aAAaC,IAAI,SAC/BC,OAASC,SAASC,cAAc,wCAChCC,eAAiBxB,KAAKyB,cAAczB,KAAKP,QACzCiC,WAAa1B,KAAK2B,kBAClBC,YAAcN,SAASC,cAAc,qBAErCM,WAAaP,SAASC,cAAc,iDACpCO,UAAYR,SAASC,cAAc,UACnCQ,MAAQT,SAASC,cAAc,uBAEjCS,SAAWD,MAAAA,aAAAA,MAAOR,cAAc,oBAChCU,QAAUF,MAAAA,aAAAA,MAAOR,cAAc,0BAE7BW,UAAYlC,KAAKmC,OAAO,OAC9BC,OAAOC,OAAOH,UAAW,CACrBnC,GAAI,+BACJuC,UAAW,0BAEfF,OAAOC,OAAOH,UAAUK,MAAO,CAC3BC,MAAO,QACPC,SAAU,eAGRC,SAAW1C,KAAKmC,OAAO,QAC7BC,OAAOC,OAAOK,SAAU,CACpB3C,GAAI,2BACJuC,UAAW,UACXK,UAAWhD,kBAAMiD,QAGrBF,SAASG,iBAAiB,SAAS,KAC/BX,UAAUK,MAAMO,WAAa,kBAC7BZ,UAAUK,MAAMC,MAAQ,IACxBnB,OAAOkB,MAAMQ,QAAU,UAE3B1B,MAAAA,QAAAA,OAAQwB,iBAAiB,SAAS,WAC9BxB,OAAOkB,MAAMQ,QAAU,OACvBb,UAAUK,MAAMC,MAAQ,iBAGtBQ,WAAahD,KAAKmC,OAAO,OAC/BC,OAAOC,OAAOW,WAAY,CACtBC,QAAS,SACTC,SAAU,SACVC,IAAK,IACLC,gBAAiB,UAErBJ,WAAWK,OAAOX,gBAGZY,OAAStD,KAAKmC,OAAO,OAC3BmB,OAAOhB,UAAY,6BACnBF,OAAOC,OAAOiB,OAAOf,MAAO,CACxBW,SAAU,SACVC,IAAK,YAGHI,YAAcvD,KAAKmC,OAAO,MAChCoB,YAAYjB,UAAY,iCACxBiB,YAAYC,sBAAiB9B,WAAW+B,kBAASzD,KAAK0D,SACtDH,YAAYhB,MAAMoB,WAAa,YAEzBC,WAAatC,SAASC,cAAc,4BACtCqC,YACAL,YAAYM,QAAQD,WAAWE,WAAU,QAGzCC,UAAY/D,KAAKgE,YAAYnD,QAC7BW,MAAAA,gBAAAA,eAAgBgC,YAChBF,OAAOD,OAAOE,YAAaQ,UAAW/D,KAAKiE,eAAezC,iBAE1D8B,OAAOD,OAAOE,YAAaQ,iBAGzBG,QAAUlE,KAAKmC,OAAO,UAC5B+B,QAAQ5B,UAAY,MAEpB4B,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAM3E,kBAAM4E,OACZd,MAAOzD,KAAKwE,YACZC,SAAUzE,KAAK0E,oBAAoB1E,KAAKb,KAAM0C,eAIlC,WAAhB7B,KAAKP,QAAuBmC,aAC5BsC,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMtE,KAAKN,WACX+D,MAAOzD,KAAK2E,SACZF,SAAU7C,YAAYe,aAK9Bb,WAA+C,MAAlCA,MAAAA,iBAAAA,UAAW0B,YAAYoB,SACpCV,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMtE,KAAKN,WACX+D,gBAAUzD,KAAK2B,kBAAkB8B,kBAASzD,KAAK6E,aAC/CJ,SAAU3C,UAAUa,aAKZ,UAAhB3C,KAAKP,QAAsBqB,QAAS,MAC/BgE,wBACDC,UAAYzD,SAASC,sCAA+BT,UACpDiE,MAAAA,WAAAA,UAAWvB,YAAYoB,QACvBV,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMtE,KAAKN,WACX+D,MAAOzD,KAAKgF,WACZP,SAAUM,UAAUvB,YAAYoB,aAM5B,SAAhB5E,KAAKP,8BAAqBO,KAAKT,iCAAL0F,cAAalF,GAAI,uBAEvCmF,WAAalF,KAAKmF,oCAAcnF,KAAKT,uCAAL6F,cAAarF,IAC7CsF,SAAW/D,SAASC,kCAA2B2D,uBAC/CI,MAAQC,KAAKvF,KAAKR,SAAS8F,OAE3BD,MAAAA,UAAAA,SAAU7B,YAAYoB,QACtBV,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,WACJC,WAAY,YACZC,KAAMtE,KAAKN,WACX+D,MAAOzD,KAAKwF,YACZf,SAAUY,SAAS7B,eAK3B8B,OAA0B,KAAjBA,MAAMV,QACfV,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMtE,KAAKN,WACX+D,gBAAUzD,KAAKQ,iBAAQR,KAAK6E,aAC5BJ,SAAUa,SAKlBG,OAAOzF,KAAKR,SAASkG,OACrBxB,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,WACJC,WAAY,YACZC,KAAM3E,kBAAMgG,KACZlC,MAAOzD,KAAK4F,eACZnB,SAAUzE,KAAK6F,uBAAuBJ,OAAOzF,KAAKR,SAASkG,MAAOD,OAAOzF,KAAKR,SAASoD,kBAMnGR,OAAO0D,KAAK9F,KAAKZ,SAAS2G,QAC1B7B,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMtE,KAAKN,WACX+D,MAAOzD,KAAKgG,QACZvB,SAAUzE,KAAKiG,gBAAgBjG,KAAKZ,YAK5C2C,OACAmC,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,WACJC,WAAY,YACZC,KAAM3E,kBAAMgG,KACZlC,MAAOzD,KAAK4F,eACZnB,SAAUzE,KAAK6F,uBAAuB7D,SAAUC,YAIxC,WAAhBjC,KAAKP,QACLyE,QAAQb,OACJrD,KAAKmE,UAAU,CACXC,GAAI,WACJC,WAAY,eACZC,KAAMtE,KAAKN,WACX+D,MAAOzD,KAAKkG,UACZzB,SAAUzE,KAAKmG,iBAAiBnG,KAAKX,eAKjD6C,UAAUmB,OAAOL,WAAYM,OAAQY,SAC9BhC,UAIXiC,oBAAUC,GAACA,GAADC,WAAKA,WAALC,KAAiBA,KAAjBb,MAAuBA,MAAvBgB,SAA8BA,qBAC9B2B,IAAMpG,KAAKmC,OAAO,OACxBiE,IAAI9D,+CAA0C8B,UAExCiC,QAAUrG,KAAKmC,OAAO,MAC5BkE,QAAQ/D,sDAAiD+B,yCACzDgC,QAAQ1D,oBAAe2B,iBAAQb,aAEzB6C,KAAOtG,KAAKmC,OAAO,cACzBmE,KAAKhE,4CACLgE,KAAK3D,UAAY8B,SAEjB2B,IAAI/C,OAAOgD,QAASC,MACbF,IAGXH,gBAAgB7G,eACNmH,QAAUvG,KAAKmC,OAAO,cAE5B/C,QAAQoH,SAAQC,eACNC,UAAY1G,KAAKmC,OAAO,OAC9BuE,UAAUpE,UAAY,iCAEhBmB,MAAQzD,KAAKmC,OAAO,MAC1BsB,MAAMnB,UAAY,4BAClBmB,MAAMD,YAAciD,OAAO5B,YAC3B6B,UAAUC,YAAYlD,OAEtBrB,OAAOwE,OAAOH,OAAOI,QAAQL,SAAQM,cAC3BC,SAAW/G,KAAKmC,OAAO,OACvB6E,MAAQvB,OAAOqB,MAAME,OAIvBD,SAASzE,UADC,IAAV0E,MACqB,oDACdA,OAAS,EACK,oDAEA,qDAGzBD,SAASvD,sBAAiBsD,MAAMG,yBAAgBH,MAAME,OACtDN,UAAUC,YAAYI,aAG1BR,QAAQI,YAAYD,cAGjBH,QAAQ5D,UAGnBwD,iBAAiB9G,+DACPkH,QAAUvG,KAAKmC,OAAO,OAEtB+E,cAAgBlH,KAAKmC,OAAO,OAClC+E,cAAc5E,UAAY,gCAEpB6E,WAAanH,KAAKmC,OAAO,QAC/BgF,WAAW3D,sBAAiBxD,KAAKa,kBAE3BuG,YAAcpH,KAAKmC,OAAO,QAC1BkF,MAAwC,SAAhChI,MAAAA,wCAAAA,WAAYiI,kEAASzG,QACnCuG,YAAY5D,YAAc6D,MAAQrH,KAAKuH,SAAWvH,KAAKwH,MACvDJ,YAAY9E,8CAAyC+E,MAAQ,0BAA4B,6BAEzFH,cAAc7D,OAAO8D,WAAYC,mBAE3BK,gBAAkBzH,KAAKmC,OAAO,OACpCsF,gBAAgBnF,UAAY,gCAEtBoF,aAAe1H,KAAKmC,OAAO,QACjCuF,aAAalE,sBAAiBxD,KAAK2H,yBAE7BC,cAAgB5H,KAAKmC,OAAO,WAC9B9C,MAAAA,yCAAAA,WAAYiI,yCAAZO,qBAAqBC,aAAc,OAC7BC,KAAO,IAAIC,KAAuC,IAAlC3I,WAAWiI,QAAQQ,cACzCF,cAAcpE,YAAcxD,KAAKiI,WAAWF,WAE5CH,cAAcpE,YAAc,MAEhCiE,gBAAgBpE,OAAOqE,aAAcE,qBAE/BM,aAAelI,KAAKmC,OAAO,OACjC+F,aAAa5F,UAAY,gCAEnB6F,UAAYnI,KAAKmC,OAAO,QAC9BgG,UAAU3E,sBAAiBxD,KAAKoI,qBAE1BC,WAAarI,KAAKmC,OAAO,eAE3B9C,MAAAA,YAAAA,WAAYiJ,MACZD,WAAW7E,YAAciC,OAAOpG,WAAWiJ,MAAMA,OAAS,EACpDjJ,WAAWiJ,MAAMA,MACjBtI,KAAKuI,SAEXF,WAAW7E,YAAcxD,KAAKuI,SAGlCL,aAAa7E,OAAO8E,UAAWE,YAC/B9B,QAAQlD,OAAO6D,cAAegB,aAAcT,iBACrClB,QAAQ5D,UAGnBqB,YAAYnD,cACFkD,UAAY/D,KAAKmC,OAAO,OACxBqG,SAAWxI,KAAKmC,OAAO,OACvBsG,MAAQzI,KAAKmC,OAAO,QACpBuG,MAAQ1I,KAAKmC,OAAO,QACpBmC,KAAOtE,KAAKmC,OAAO,QAEzBmC,KAAKhC,UAAY,OACjBgC,KAAK3B,UAAYhD,kBAAMC,WAEvB4I,SAAS7B,YAAYrC,MACrBkE,SAASnF,OAAOoF,OAEhBA,MAAMjF,sBAAiBxD,KAAK+D,eAC5B2E,MAAMlF,YAAc,IACpBkF,MAAMpG,UAAY,eAClBoG,MAAMnG,MAAMoB,WAAa,MACzB+E,MAAMnG,MAAMoG,SAAW,OAEvB5E,UAAUzB,UAAY,qEACtByB,UAAUV,OAAOmF,SAAUE,OAC3B3E,UAAUxB,MAAMoG,SAAW,cAEV,IAAIC,kBAAiB,WAC5BC,QAAUhI,OAAO2C,YAAYoB,OACnC8D,MAAMlF,sBAAiBqF,QAAQC,QAAQ,QAAS,QAG3CC,QAAQlI,OAAQ,CACrBmI,eAAe,EACfC,SAAS,EACTC,WAAW,IAGRnF,UAIXE,eAAekF,WAEPC,WAAa9H,SAASC,cAAc,gCACpC6H,WAAY,8BACRC,MAAQD,WAAWtF,WAAU,gCACjCuF,MAAM9H,cAAc,gEAAW+H,cAC1B/J,OAAOgK,oBAAoB7D,KAAK,CACjC8D,KAAMH,MAAM7F,YACZiG,KAAM,gBAKRC,WAAa1J,KAAKmC,OAAO,OAC/BuH,WAAWpH,UAAY,2EAEjBkG,SAAWxI,KAAKmC,OAAO,OACvBsG,MAAQzI,KAAKmC,OAAO,QACpBuG,MAAQ1I,KAAKmC,OAAO,QACpBmC,KAAOtE,KAAKmC,OAAO,WACzBmC,KAAK3B,UAAYhD,kBAAMgG,KAEvB6C,SAAS7B,YAAYrC,MACrBkE,SAASnF,OAAOoF,OAEhBA,MAAMjF,sBAAiBxD,KAAK2J,gBAC5BjB,MAAMlF,YAAc,WACpBkF,MAAMpG,UAAY8G,WAAa,cAAgB,eAC/ChH,OAAOC,OAAOqG,MAAMnG,MAAO,CACvBoB,WAAY,MACZgF,SAAU,SAIde,WAAWrG,OAAOmF,SAAUE,OAC5BgB,WAAWnH,MAAMoG,SAAW,OACxBQ,MAAO,CACU,IAAIP,kBAAiB,WAC5BC,QAAUM,MAAM3F,YAAYoB,OAClC8D,MAAMlF,sBAAiBqF,YAElBE,QAAQI,MAAO,CACpBH,eAAe,EACfC,SAAS,EACTC,WAAW,SAGfR,MAAMlF,YAAcxD,KAAK4J,eAItBF,WAIXhF,oBAAoBmF,KAAMC,cAEhBvD,QAAUvG,KAAKmC,OAAO,OAEtB4H,YAAc/J,KAAKmC,OAAO,OAC1B6H,gBAAkBhK,KAAKmC,OAAO,OAC9B8H,cAAgBjK,KAAKmC,OAAO,OAE5B+H,UAAYlK,KAAKmC,OAAO,QACxBgI,UAAYnK,KAAKmC,OAAO,QACxBiI,cAAgBpK,KAAKmC,OAAO,QAC5BkI,cAAgBrK,KAAKmC,OAAO,QAC5BmI,YAActK,KAAKmC,OAAO,QAC1BoI,YAAcvK,KAAKmC,OAAO,eAEhC+H,UAAU1G,sBAAiBxD,KAAKwK,MAChCL,UAAU3G,YAAcqG,KAAKY,SAE7BL,cAAc5G,sBAAiBxD,KAAK0K,gBACpCL,cAAc7G,YAAcqG,KAAKc,SAEjCL,YAAY9G,sBAAiBxD,KAAK8J,aAClCS,YAAY/G,YAAcsG,OAAOrG,MAEjCsG,YAAYzH,UAAY,iCACxB0H,gBAAgB1H,UAAY,iCAC5B2H,cAAc3H,UAAY,iCAE1ByH,YAAY1G,OAAO6G,UAAWC,WAC9BH,gBAAgB3G,OAAO+G,cAAeC,eACtCJ,cAAc5G,OAAOiH,YAAaC,aAElChE,QAAQlD,OAAO0G,YAAaC,gBAAiBC,eAEtC1D,QAAQ5D,UAInBkD,uBAAuBH,KAAMkF,WAEnBrE,QAAUvG,KAAKmC,OAAO,WACxBH,SAAW,KACXC,QAAU,WAER4I,cAAgB7K,KAAKmC,OAAO,OAC5B2I,WAAa9K,KAAKmC,OAAO,OACzB4I,iBAAmB/K,KAAKmC,OAAO,OAE/B6I,YAAchL,KAAKmC,OAAO,QAC1B8I,YAAcjL,KAAKmC,OAAO,QAC1B+I,SAAWlL,KAAKmC,OAAO,QACvBgJ,SAAWnL,KAAKmC,OAAO,QACvBiJ,eAAiBpL,KAAKmC,OAAO,QAC7BkJ,eAAiBrL,KAAKmC,OAAO,cACf,SAAhBnC,KAAKP,QACLuC,SAAkB,IAAP0D,KACXzD,QAAgB,IAAN2I,MAEV5I,SAAWhC,KAAKsL,YAAY5F,MAAAA,YAAAA,KAAMlC,aAClCvB,QAAUjC,KAAKsL,YAAYV,MAAAA,WAAAA,IAAKpH,cAGpCwH,YAAYxH,sBAAiBxD,KAAKuL,aAClCN,YAAYzH,YAAcxD,KAAKiI,WAAWjG,SAAW,IAAIgG,KAAKhG,UAAY,MAC1EiJ,YAAY3I,UAAY,YAExB4I,SAAS1H,sBAAiBxD,KAAK4K,UAC/BO,SAAS3H,YAAcxD,KAAKiI,WAAWhG,QAAU,IAAI+F,KAAK/F,SAAW,MACrEkJ,SAAS7I,UAAY,cAErB8I,eAAe5H,sBAAiBxD,KAAKwL,gBACrCH,eAAe7H,YAAcxD,KAAKyL,cAAcxJ,SAChDoJ,eAAe/I,UAAY,cAE3BuI,cAAcvI,UAAY,iCAC1BwI,WAAWxI,UAAY,iCACvByI,iBAAiBzI,UAAY,yEAE7BuI,cAAcxH,OAAO2H,YAAaC,aAClCH,WAAWzH,OAAO6H,SAAUC,UAC5BJ,iBAAiB1H,OAAO+H,eAAgBC,gBAExC9E,QAAQlD,OAAOwH,cAAeC,WAAYC,kBAEnCxE,QAAQ5D,UAGnBsF,WAAWF,UACFA,WACM,WAIJA,KAAK2D,eAAe,QADb,CAACC,KAAM,UAAWC,MAAO,QAASC,IAAK,UAAWC,KAAM,UAAWC,OAAQ,UAAWC,QAAQ,IAIhHV,YAAY9B,UACHA,WACM,UAGLyC,MAAQzC,MAAAA,YAAAA,KAAM0C,MAAM,YACtBD,MAAMlG,OAAS,EACRkG,MAAME,MAAM,GAAGC,KAAK,KAAKxH,OAG7B4E,KAAK5E,OAIhB6G,cAAc1D,UACLA,WACM,UAMLsE,OAJQ,IAAIrE,KAAKD,MACX,IAAIC,QAMZqE,QAAU,QACH,UACJ,OACGC,SAAWC,KAAKC,MAAMH,cACtBI,UAAYF,KAAKC,MAAOH,YAA6B,oBAEjDC,2BAAkBG,qBAKpCtM,eAAeV,yGACP6H,QAA0B,SAAhBtH,KAAKP,OACf6B,SAASoL,yBAAkBjN,gBAAgB6B,SAASC,yBAAkB9B,gBAEtEkN,GAAKrF,QAAQsF,cACbC,GAAKF,GAAGC,cAERE,GADKD,GAAGD,cACAA,cAERG,UAAYzL,SAASC,cAAc,4CACnCyL,WAAa1L,SAASC,cAAc,wBACpC+B,OAAStD,KAAKmC,OAAO,OACrB8K,IAAM,KAEVD,WAAWE,UAAU5D,OAAO,QAC5BhG,OAAOvD,GAAK,sCACZqC,OAAOC,OAAOiB,OAAOf,MAAO,CACxBa,gBAAiB,QACjBL,QAAS,OACToK,eAAgB,kBAGA,SAAhBnN,KAAKP,QACLwN,IAAM3L,SAASC,cAAc,sBAAsBuC,WAAU,GAC7DmJ,IAAI3K,UAAY,mCAChB2K,IAAI1K,MAAM6K,OAAS,UAEnBH,IAAMjN,KAAKmC,OAAO,SAClB8K,IAAI3K,UAAY,mCAChB2K,IAAIvE,MAAQ1I,KAAKqN,YACjBJ,IAAIxD,KAAO,SACXwD,IAAI1K,MAAM6K,OAAS,eAIjBE,SAAWtN,KAAKmC,OAAO,OACvBoL,UAAYvN,KAAKmC,OAAO,WAC1BqL,YAAc,CACdzK,QAAS,OACT0K,WAAY,SACZL,OAAQ,UAGZhL,OAAOC,OAAOiL,SAAS/K,MAAOiL,aAC9BD,UAAUxN,GAAK,sCACfqC,OAAOC,OAAOkL,UAAUhL,MAAOiL,aAE/BD,UAAU5G,YAAYsG,KACtBK,SAAS3G,YAAYqG,WAAWlJ,WAAU,IAE1CR,OAAOqD,YAAY2G,UACnBhK,OAAOqD,YAAY4G,WAEnBT,GAAGY,aAAapK,OAAQwJ,GAAGa,YAC3Bd,GAAGtK,MAAMa,gBAAkB,UAC3BhB,OAAOC,OAAOiF,QAAQ/E,MAAO,CACzBC,MAAO,QACPoL,SAAU,QACVC,UAAW,kEAGfzL,OAAOC,OAAOsK,GAAGpK,MAAO,CACpBQ,QAAS,OACToK,eAAgB,SAChBW,QAAS,OACTV,OAAQ,mBAEN7K,MAAQvC,KAAKmC,OAAO,SAC1BI,MAAMxC,GAAK,mCACXwC,MAAMiB,qHAINlC,SAASyM,KAAKpH,YAAYpE,WAEtByL,0CAAa1G,QAAQ2G,8EAAiB3H,sCAAQgB,QAAQ4G,+EAARC,sBAAuB7M,kDAAvB8M,uBAAiC9H,MAE/E0H,aACAA,WAAWzL,MAAMU,QAAU,SAE/B4J,GAAGtK,MAAMW,SAAW,yCACpB5B,SAASoL,eAAe,wFAAiCpD,aAErDjI,OAASrB,KAAKmC,OAAO,OACzBd,OAAOtB,GAAK,sCACZsB,OAAOsB,UAAYhD,kBAAM0O,UACzBxB,GAAGlG,YAAYtF,QACfwL,GAAGlG,YAAY3G,KAAKY,WAAWmM,YAGnC9M,cAAcqO,sLACVhN,SAASoL,eAAe,iGAAwCpD,wCAChEhI,SAASoL,eAAe,0FAAiCpD,aAErDhC,QAAUhG,SAASoL,eAAe4B,UAClC3B,GAAKrF,QAAQsF,cACbC,GAAKF,GAAGC,cAEZxK,OAAOC,OAAOwK,GAAGtK,MAAO,CACpBa,gBAAiB,GACjBF,SAAU,KAGdd,OAAOC,OAAOiF,QAAQ/E,MAAO,CACzBC,MAAO,GACPoL,SAAU,GACVC,UAAW,KAGfzL,OAAOC,OAAOsK,GAAGpK,MAAO,CACpBQ,QAAS,GACToK,eAAgB,GAChBW,QAAS,GACTV,OAAQ,KAGZT,GAAGO,UAAU5D,OAAO,qCAEhB0E,2CAAa1G,QAAQ2G,gFAAiB3H,uCAAQgB,QAAQ4G,gFAARK,uBAAuBjN,kDAAvBkN,uBAAiClI,MAC/E0H,aACAA,WAAWzL,MAAMU,QAAU,mCAE/B3B,SAASyM,KAAKxM,cAAc,6FAAsC+H,SAGtExE,0BACU2J,KAAOnN,SAASC,cAAc,gDAC9BmN,IAAM1O,KAAK2O,WAEbF,MACAA,KAAK5L,iBAAiB,SAAU+L,UACtBC,aAAevN,SAASoL,eAAe,kBACzCxI,QAAUlE,KAAKT,OAAOuP,aAAalK,OAClCiK,cAA8C,KAA9BA,aAAanG,MAAM9D,QAA6B,KAAZV,UACrD0K,EAAEG,iBACFH,EAAEI,uBACGzP,OAAO0P,cAAcC,MAAMR,SAMhD/M,wBACWU,OAAQ8M,OAAQ3O,KAAME,QAAUV,KAAKoP,QAAQ,kBAC5CpP,KAAKP,YACJ,eACM,CAACgE,MAAOpB,OAAQiC,KAAM3E,kBAAMC,gBAClC,cACM,CAAC6D,MAAO0L,OAAQ7K,KAAM3E,kBAAMU,WAClC,eACM,CAACoD,MAAO/C,OAAQ4D,KAAM3E,kBAAMU,WAClC,aACM,CAACoD,MAAOjD,KAAM8D,KAAM3E,kBAAMa,oBAE1B,CAACiD,MAAO,OAAQa,KAAM3E,kBAAMa,OAI/CiB,cAAchC,eACFA,YACC,gBACM6B,SAASC,cAAc,+CAC7B,eACMD,SAASC,cAAc,kCAC7B,gBACMD,SAASC,cAAc,qBAC7B,cACMD,SAASC,cAAc,kCAEvB,MAInB4D,cAAckK,oBAEDA,SAA8B,iBAAZA,QAGhBA,QAAQvG,QAAQ,oBAAqB,SAFjC,GAGb,MAAOwG,cACLtO,OAAOuO,QAAQD,MAAM,6BAA8BA,OAC5C,IAIfzP,eAEQG,KAAK0D,QACL1D,KAAKwE,YACLxE,KAAK2E,SACL3E,KAAK6E,YACL7E,KAAKgF,WACLhF,KAAKwF,YACLxF,KAAK4F,eACL5F,KAAKgG,QACLhG,KAAKkG,UACLlG,KAAKa,OACLb,KAAKwH,MACLxH,KAAKuH,SACLvH,KAAK2H,aACL3H,KAAKoI,SACLpI,KAAKuI,SACLvI,KAAK+D,UACL/D,KAAK2J,SACL3J,KAAK4J,QACL5J,KAAKwK,KACLxK,KAAK0K,UACL1K,KAAK8J,OACL9J,KAAKuL,OACLvL,KAAK4K,IACL5K,KAAKwP,QACLxP,KAAKwL,UACLxL,KAAKqN,YACLrN,KAAK2O,YACL3O,KAAKoP,QAAQ,cAGrBA,QAAQK,YACGC,KAAKC,MAAMC,aAAaC,QAAQJ,OAAS,GAGpDtN,OAAO2N,YACIxO,SAASyO,cAAcD"} ->>>>>>> 8b275778ba140d4ec01f088ecfc426293a0a3296 +{"version":3,"file":"document_view.min.js","sources":["../src/document_view.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 provides functionality for document view management in the Tiny editor,\n * including full page mode display and sidebar information\n * @module tiny_cursive/document_view\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Icons from 'tiny_cursive/svg_repo';\nexport default class DocumentView {\n\n constructor(User, Rubrics, submission, modulename, editor, quizInfo) {\n this.User = User;\n this.Rubrics = Rubrics;\n this.submission = submission;\n this.module = modulename;\n this.editor = editor;\n this.moduleIcon = Icons.assignment;\n this.quizInfo = quizInfo;\n this.initStrings();\n }\n\n normalMode() {\n let id = this.editor?.id + \"_ifr\";\n if (this.module === 'assign') {\n this.normalizePage(id);\n } else if (this.module === 'quiz') {\n this.normalizePage(id);\n } else if (this.module === 'forum') {\n this.normalizePage(id);\n } else if (this.module === 'lesson') {\n this.normalizePage(id);\n } else if(this.module === 'pdfannotator') {\n this.normalizePage(id);\n }\n }\n\n fullPageMode() {\n\n if (this.module === 'assign') {\n this.moduleIcon = Icons.assignment;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'forum') {\n this.moduleIcon = Icons.forum;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'quiz' && this.editor?.id) {\n this.moduleIcon = Icons.quiz;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'lesson') {\n this.moduleIcon = Icons.lesson;\n this.fullPageModule(this.editor?.id);\n } else if (this.module === 'pdfannotator') {\n this.moduleIcon = Icons.pdfannotator;\n this.fullPageModule(this.editor?.id);\n }\n }\n\n docSideBar(status) {\n\n const url = new URL(window.location.href);\n const replyId = url.searchParams.get(\"reply\");\n const toggle = document.querySelector('#cursive-fullpagemode-sidebar-toggle');\n const timelimitBlock = this.getTimerBlock(this.module);\n const headerInfo = this.getSidebarTitle();\n const progressBar = document.querySelector('.box.progress_bar');\n\n const courseName = document.querySelector('#page-navbar > nav > ol > li:nth-child(1) > a');\n const courseDes = document.querySelector('#intro');\n const Dates = document.querySelector('.activity-dates');\n\n let openDate = Dates?.querySelector('div:nth-child(1)');\n let dueDate = Dates?.querySelector('div:nth-child(2)');\n\n const container = this.create('div');\n Object.assign(container, {\n id: 'cursive-fullpagemode-sidebar',\n className: 'bg-white h-100 shadow'\n });\n Object.assign(container.style, {\n width: '300px',\n overflow: 'auto'\n });\n\n const crossBtn = this.create('span');\n Object.assign(crossBtn, {\n id: 'cursive-collapse-sidebar',\n className: 'btn p-2',\n innerHTML: Icons.close\n });\n\n crossBtn.addEventListener('click', () => {\n container.style.transition = 'width 0.3s ease';\n container.style.width = '0';\n toggle.style.display = 'flex';\n });\n toggle?.addEventListener('click', function() {\n toggle.style.display = 'none';\n container.style.width = '300px';\n });\n\n const btnWrapper = this.create('div');\n Object.assign(btnWrapper, {\n padding: '0 1rem',\n position: 'sticky',\n top: '0',\n backgroundColor: 'white'\n });\n btnWrapper.append(crossBtn);\n\n\n const header = this.create('div');\n header.className = 'border-bottom p-3 bg-light';\n Object.assign(header.style, {\n position: 'sticky',\n top: '0'\n });\n\n const headerTitle = this.create('h3');\n headerTitle.className = 'mb-3 d-flex align-items-center';\n headerTitle.textContent = `${headerInfo.title} ${this.details}`;\n headerTitle.style.fontWeight = '600';\n\n const headerIcon = document.querySelector('.page-header-image > div');\n if (headerIcon) {\n headerTitle.prepend(headerIcon.cloneNode(true));\n }\n\n let wordCount = this.wordCounter(status);\n if (timelimitBlock?.textContent) {\n header.append(headerTitle, wordCount, this.timerCountDown(timelimitBlock));\n } else {\n header.append(headerTitle, wordCount);\n }\n\n const content = this.create('div');\n content.className = 'p-3';\n\n content.append(\n this.createBox({\n bg: 'bg-info',\n titleColor: 'text-info',\n icon: Icons.people,\n title: this.studentInfo,\n bodyHTML: this.generateStudentInfo(this.User, courseName)\n })\n );\n\n if (this.module === 'lesson' && progressBar) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.progress,\n bodyHTML: progressBar.innerHTML\n })\n );\n }\n\n if (courseDes && courseDes?.textContent.trim() !== '') {\n let fileSubDiv = document.querySelectorAll('.fileuploadsubmission');\n if (fileSubDiv) {\n fileSubDiv.forEach(Element => {\n Element.style.verticalAlign = 'middle';\n });\n }\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.getSidebarTitle().title} ${this.description}`,\n bodyHTML: courseDes.innerHTML\n })\n );\n }\n\n if (this.module === 'forum' && replyId) {\n this.checkForumSubject();\n let replyPost = document.querySelector(`#post-content-${replyId}`);\n if (replyPost?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.replyingto,\n bodyHTML: replyPost.textContent.trim()\n })\n );\n }\n }\n\n if (this.module === 'quiz' && this.editor?.id) {\n\n let questionId = this.getQuestionId(this.editor?.id);\n let question = document.querySelector(`#question-${questionId} .qtext`);\n let intro = atob(this.quizInfo.intro);\n\n if (question?.textContent.trim()) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.answeringto,\n bodyHTML: question.textContent\n })\n );\n }\n\n if (intro && intro.trim() !== '') {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: `${this.quiz} ${this.description}`,\n bodyHTML: intro\n })\n );\n }\n\n if (Number(this.quizInfo.open)) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(Number(this.quizInfo.open), Number(this.quizInfo.close))\n })\n );\n }\n }\n\n if (Object.keys(this.Rubrics).length) {\n content.append(\n this.createBox({\n bg: 'bg-gray',\n titleColor: 'text-dark',\n icon: this.moduleIcon,\n title: this.rubrics,\n bodyHTML: this.generateRubrics(this.Rubrics)\n })\n );\n }\n\n if (Dates) {\n content.append(\n this.createBox({\n bg: 'bg-amber',\n titleColor: 'text-dark',\n icon: Icons.time,\n title: this.importantdates,\n bodyHTML: this.generateImportantDates(openDate, dueDate)\n })\n );\n }\n if (this.module === 'assign') {\n content.append(\n this.createBox({\n bg: 'bg-green',\n titleColor: 'text-success',\n icon: this.moduleIcon,\n title: this.subStatus,\n bodyHTML: this.submissionStatus(this.submission)\n })\n );\n }\n\n container.append(btnWrapper, header, content);\n return container;\n\n }\n // Helper to create info boxes\n createBox({bg, titleColor, icon, title, bodyHTML}) {\n const box = this.create('div');\n box.className = `tiny_cursive-fullpage-card ${bg}`;\n\n const heading = this.create('h4');\n heading.className = `tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`;\n heading.innerHTML = `${icon} ${title}`;\n\n const body = this.create('div');\n body.className = `tiny_cursive-fullpage-card-body`;\n body.innerHTML = bodyHTML;\n\n box.append(heading, body);\n return box;\n }\n\n generateRubrics(Rubrics) {\n const wrapper = this.create('div');\n\n Rubrics.forEach(rubric => {\n const rubricDiv = this.create('div');\n rubricDiv.className = 'tiny_cursive-rubric-card';\n\n const title = this.create('h3');\n title.className = 'tiny_cursive-rubric-title';\n title.textContent = rubric.description;\n rubricDiv.appendChild(title);\n\n Object.values(rubric.levels).forEach(level => {\n const levelDiv = this.create('div');\n const score = Number(level.score);\n\n // Assign background color class based on score\n if (score === 0) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-low';\n } else if (score <= 2) {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-mid';\n } else {\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-high';\n }\n\n levelDiv.textContent = `${level.definition} / ${level.score}`;\n rubricDiv.appendChild(levelDiv);\n });\n\n wrapper.appendChild(rubricDiv);\n });\n\n return wrapper.innerHTML;\n }\n\n submissionStatus(submission) {\n const wrapper = this.create('div');\n\n const statusWrapper = this.create('div');\n statusWrapper.className = 'tiny_cursive-status-row';\n\n const statusName = this.create('span');\n statusName.textContent = `${this.status}:`;\n\n const statusValue = this.create('span');\n const isNew = submission?.current?.status === 'new';\n statusValue.textContent = isNew ? this.draftnot : this.draft;\n statusValue.className = `tiny_cursive-status-value ${isNew ? 'tiny_cursive-status-red' : 'tiny_cursive-status-green'}`;\n\n statusWrapper.append(statusName, statusValue);\n\n const modifiedWrapper = this.create('div');\n modifiedWrapper.className = 'tiny_cursive-status-row';\n\n const modifiedName = this.create('span');\n modifiedName.textContent = `${this.lastModified}: `;\n\n const modifiedValue = this.create('span');\n if (submission?.current?.timemodified) {\n const date = new Date(submission.current.timemodified * 1000);\n modifiedValue.textContent = this.formatDate(date);\n } else {\n modifiedValue.textContent = 'N/A';\n }\n modifiedWrapper.append(modifiedName, modifiedValue);\n\n const gradeWrapper = this.create('div');\n gradeWrapper.className = 'tiny_cursive-status-row';\n\n const gradeName = this.create('span');\n gradeName.textContent = `${this.gradings}: `;\n\n const gradeValue = this.create('span');\n\n if (submission?.grade) {\n gradeValue.textContent = Number(submission.grade.grade) > 0\n ? submission.grade.grade\n : this.gradenot;\n } else {\n gradeValue.textContent = this.gradenot;\n }\n\n gradeWrapper.append(gradeName, gradeValue);\n wrapper.append(statusWrapper, gradeWrapper, modifiedWrapper);\n return wrapper.innerHTML;\n }\n\n wordCounter(status) {\n const wordCount = this.create('div');\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n\n icon.className = 'me-2';\n icon.innerHTML = Icons.assignment;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.wordCount}:`;\n value.textContent = '0';\n value.className = 'text-primary';\n value.style.fontWeight = '600';\n value.style.fontSize = '14px';\n\n wordCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n wordCount.append(labelDiv, value);\n wordCount.style.fontSize = '12px';\n\n const observer = new MutationObserver(() => {\n const newText = status.textContent.trim();\n value.textContent = `${newText.replace('words', '')}`;\n });\n\n observer.observe(status, {\n characterData: true,\n subtree: true,\n childList: true\n });\n\n return wordCount;\n }\n\n\n timerCountDown(timer) {\n\n let warningDiv = document.querySelector('#user-notifications > div');\n if (warningDiv) {\n let clone = warningDiv.cloneNode(true);\n clone.querySelector('button')?.remove();\n this.editor.notificationManager.open({\n text: clone.textContent,\n type: 'error'\n });\n }\n\n\n const timerCount = this.create('div');\n timerCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\n\n const labelDiv = this.create('div');\n const label = this.create('span');\n const value = this.create('span');\n const icon = this.create('span');\n icon.innerHTML = Icons.time;\n\n labelDiv.appendChild(icon);\n labelDiv.append(label);\n\n label.textContent = `${this.timeleft}:`;\n value.textContent = '00:00:00';\n value.className = warningDiv ? 'text-danger' : 'text-primary';\n Object.assign(value.style, {\n fontWeight: '600',\n fontSize: '14px'\n });\n\n\n timerCount.append(labelDiv, value);\n timerCount.style.fontSize = '12px';\n if (timer) {\n const observer = new MutationObserver(() => {\n const newText = timer.textContent.trim();\n value.textContent = `${newText}`;\n });\n observer.observe(timer, {\n characterData: true,\n subtree: true,\n childList: true\n });\n } else {\n value.textContent = this.nolimit;\n }\n\n\n return timerCount;\n }\n\n\n generateStudentInfo(user, course) {\n\n const wrapper = this.create('div');\n\n const nameWrapper = this.create('div');\n const usernameWrapper = this.create('div');\n const courseWrapper = this.create('div');\n\n const nameLabel = this.create('strong');\n const nameValue = this.create('span');\n const usernameLabel = this.create('strong');\n const usernameValue = this.create('span');\n const courseLabel = this.create('strong');\n const courseValue = this.create('span');\n\n nameLabel.textContent = `${this.name}`;\n nameValue.textContent = user.fullname;\n\n usernameLabel.textContent = `${this.userename}: `;\n usernameValue.textContent = user.username;\n\n courseLabel.textContent = `${this.course}: `;\n courseValue.textContent = course.title;\n\n usernameLabel.className = 'cfw-bold me-2';\n usernameValue.className = 'cursiveFw-wrap';\n courseLabel.className = 'cfw-bold me-2';\n courseValue.className = 'cursiveFw-wrap';\n nameLabel.className = 'cfw-bold me-2';\n nameValue.className = 'cursiveFw-wrap';\n\n nameWrapper.append(nameLabel, nameValue);\n usernameWrapper.append(usernameLabel, usernameValue);\n courseWrapper.append(courseLabel, courseValue);\n\n wrapper.append(nameWrapper, usernameWrapper, courseWrapper);\n\n return wrapper.innerHTML;\n\n }\n\n generateImportantDates(open, due) {\n\n const wrapper = this.create('div');\n let openDate = null;\n let dueDate = null;\n\n const openedWrapper = this.create('div');\n const dueWrapper = this.create('div');\n const remainingWrapper = this.create('div');\n\n const openedLabel = this.create('span');\n const openedValue = this.create('span');\n const dueLabel = this.create('span');\n const dueValue = this.create('span');\n const remainingLabel = this.create('span');\n const remainingValue = this.create('span');\n if (this.module === 'quiz') {\n openDate = open * 1000;\n dueDate = due * 1000;\n } else {\n openDate = this.extractDate(open?.textContent);\n dueDate = this.extractDate(due?.textContent);\n }\n\n openedLabel.textContent = `${this.opened}: `;\n openedValue.textContent = this.formatDate(openDate ? new Date(openDate) : null);\n openedValue.className = 'text-dark';\n\n dueLabel.textContent = `${this.due}: `;\n dueValue.textContent = this.formatDate(dueDate ? new Date(dueDate) : null);\n dueValue.className = 'text-danger';\n\n remainingLabel.textContent = `${this.remaining}: `;\n remainingValue.textContent = this.calculateDate(dueDate);\n remainingValue.className = 'text-danger';\n\n openedWrapper.className = 'd-flex justify-content-between';\n dueWrapper.className = 'd-flex justify-content-between';\n remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top';\n\n openedWrapper.append(openedLabel, openedValue);\n dueWrapper.append(dueLabel, dueValue);\n remainingWrapper.append(remainingLabel, remainingValue);\n\n wrapper.append(openedWrapper, dueWrapper, remainingWrapper);\n\n return wrapper.innerHTML;\n }\n\n formatDate(date) {\n if (!date) {\n return '-';\n }\n\n let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true};\n return date.toLocaleString('en-US', options);\n }\n\n extractDate(text) {\n if (!text) {\n return '-';\n }\n // Split on first colon and return the right part\n const parts = text?.split(':');\n if (parts.length > 1) {\n return parts.slice(1).join(':').trim();\n }\n\n return text.trim();\n }\n\n\n calculateDate(date) {\n if (!date) {\n return '-';\n }\n const date1 = new Date(date); // Due date (local time)\n const now = new Date(); // Current date/time\n\n // Calculate the difference in milliseconds\n const diffMs = date1 - now;\n\n // Convert to days, hours, minutes\n if (diffMs <= 0) {\n return \"Overdue\";\n } else {\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n const diffHours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);\n\n return `${diffDays} days, ${diffHours} hours`;\n }\n\n }\n\n fullPageModule(module) {\n let current = this.module === 'quiz' ?\n document.getElementById(`${module}_ifr`) : document.querySelector(`#${module}_ifr`);\n\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n let p3 = p2.parentElement;\n let p4 = p3.parentElement;\n\n let statusBar = document.querySelector('.tox-statusbar__right-container > button');\n let assignName = document.querySelector('.page-context-header');\n let header = this.create('div');\n let btn = null;\n\n assignName.classList.remove('mb-2');\n header.id = 'tiny_cursive-fullpage-custom-header';\n Object.assign(header.style, {\n backgroundColor: 'white',\n display: 'flex',\n justifyContent: 'space-between'\n });\n\n if (this.module === 'quiz') {\n btn = document.querySelector('#mod_quiz-next-nav').cloneNode(true);\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.style.margin = '.5rem';\n } else {\n btn = this.create('input');\n btn.className = 'tiny_cursive-fullpage-submit-btn';\n btn.value = this.savechanges;\n btn.type = 'submit';\n btn.style.margin = '.5rem';\n }\n\n if (this.module === 'pdfannotator') {\n const style = document.createElement('style');\n style.id = 'cursiveForceStyle';\n style.textContent = `\n .path-mod-pdfannotator #comment-wrapper h4,\n .path-mod-pdfannotator #comment-nav {\n margin: 0 !important;\n }\n `;\n document.head.appendChild(style);\n }\n\n const leftSide = this.create('div');\n const rightSide = this.create('div');\n let commonStyle = {\n display: 'flex',\n alignItems: 'center',\n margin: '0 1rem'\n };\n\n Object.assign(leftSide.style, commonStyle);\n rightSide.id = 'tiny_cursive-fullpage-right-wrapper';\n Object.assign(rightSide.style, commonStyle);\n\n rightSide.appendChild(btn);\n leftSide.appendChild(assignName.cloneNode(true));\n\n header.appendChild(leftSide);\n header.appendChild(rightSide);\n\n p4.insertBefore(header, p4.firstChild);\n p2.style.backgroundColor = '#efefef';\n Object.assign(current.style, {\n width: '750px',\n minWidth: '750px',\n boxShadow: '0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)'\n });\n\n Object.assign(p1.style, {\n display: 'flex',\n justifyContent: 'center',\n outline: 'none',\n margin: '2rem 0 0'\n });\n const style = this.create('style');\n style.id = 'tiny_cursive-fullpage-mode-style';\n style.textContent = `\n .tox.tox-edit-focus .tox-edit-area::before {\n opacity: 0;\n }`;\n document.head.appendChild(style);\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n\n if (iframeBody) {\n iframeBody.style.padding = '0.5in';\n }\n p2.style.position = 'relative';\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let toggle = this.create('div');\n toggle.id = 'cursive-fullpagemode-sidebar-toggle';\n toggle.innerHTML = Icons.hamburger;\n p2.appendChild(toggle);\n p2.appendChild(this.docSideBar(statusBar));\n }\n\n normalizePage(editorId) {\n document.getElementById('tiny_cursive-fullpage-custom-header')?.remove();\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\n\n let current = document.getElementById(editorId);\n let p1 = current.parentElement;\n let p2 = p1.parentElement;\n\n Object.assign(p2.style, {\n backgroundColor: \"\",\n position: \"\"\n });\n\n Object.assign(current.style, {\n width: '',\n minWidth: '',\n boxShadow: '',\n });\n\n Object.assign(p1.style, {\n display: '',\n justifyContent: '',\n outline: '',\n margin: ''\n });\n\n p1.classList.remove('tiny-cursive-editor-container');\n\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\n if (iframeBody) {\n iframeBody.style.padding = '0';\n }\n document.head.querySelector('#tiny_cursive-fullpage-mode-style')?.remove();\n document.head.querySelector('#cursiveForceStyle')?.remove();\n }\n\n checkForumSubject() {\n const form = document.querySelector('#tiny_cursive-fullpage-right-wrapper > input');\n const msg = this.subjectnot;\n\n if (form) {\n form.addEventListener('click', (e) => {\n const subjectInput = document.getElementById('id_subject');\n let content = this.editor.getContent().trim();\n if (!subjectInput || subjectInput.value.trim() === '' || content === '') {\n e.preventDefault();\n e.stopPropagation();\n this.editor.windowManager.alert(msg);\n }\n });\n }\n }\n\n getSidebarTitle() {\n const [assign, discus, quiz, lesson] = this.getText('sbTitle');\n switch (this.module) {\n case 'assign':\n return {title: assign, icon: Icons.assignment};\n case 'forum':\n return {title: discus, icon: Icons.forum};\n case 'lesson':\n return {title: lesson, icon: Icons.forum};\n case 'quiz':\n return {title: quiz, icon: Icons.quiz};\n case 'pdfannotator':\n return {title: 'PDF Annotation', icon: Icons.pdfannotator};\n default:\n return {title: 'Page', icon: Icons.quiz};\n }\n }\n\n getTimerBlock(module) {\n switch (module) {\n case 'assign':\n return document.querySelector('#mod_assign_timelimit_block > div > div');\n case 'forum':\n return document.querySelector('#mod_forum_timelimit_block');\n case 'lesson':\n return document.querySelector('#lesson-timer');\n case 'quiz':\n return document.querySelector('#quiz-time-left');\n default:\n return null;\n }\n }\n\n getQuestionId(editoId) {\n try {\n if (!editoId || typeof editoId !== 'string') {\n return '';\n }\n return editoId.replace(/^q(\\d+):(\\d+)_.*$/, \"$1-$2\");\n } catch (error) {\n window.console.error('Error getting question ID:', error);\n return '';\n }\n }\n\n initStrings() {\n [\n this.details,\n this.studentInfo,\n this.progress,\n this.description,\n this.replyingto,\n this.answeringto,\n this.importantdates,\n this.rubrics,\n this.subStatus,\n this.status,\n this.draft,\n this.draftnot,\n this.lastModified,\n this.gradings,\n this.gradenot,\n this.wordCount,\n this.timeleft,\n this.nolimit,\n this.name,\n this.userename,\n this.course,\n this.opened,\n this.due,\n this.overdue,\n this.remaining,\n this.savechanges,\n this.subjectnot\n ] = this.getText('docSideBar');\n }\n\n getText(key) {\n return JSON.parse(localStorage.getItem(key)) || [];\n }\n\n create(tag) {\n return document.createElement(tag);\n }\n\n}"],"names":["constructor","User","Rubrics","submission","modulename","editor","quizInfo","module","moduleIcon","Icons","assignment","initStrings","normalMode","id","this","normalizePage","fullPageMode","fullPageModule","_this$editor2","forum","_this$editor3","_this$editor4","quiz","_this$editor5","lesson","_this$editor6","pdfannotator","_this$editor7","docSideBar","status","replyId","URL","window","location","href","searchParams","get","toggle","document","querySelector","timelimitBlock","getTimerBlock","headerInfo","getSidebarTitle","progressBar","courseName","courseDes","Dates","openDate","dueDate","container","create","Object","assign","className","style","width","overflow","crossBtn","innerHTML","close","addEventListener","transition","display","btnWrapper","padding","position","top","backgroundColor","append","header","headerTitle","textContent","title","details","fontWeight","headerIcon","prepend","cloneNode","wordCount","wordCounter","timerCountDown","content","createBox","bg","titleColor","icon","people","studentInfo","bodyHTML","generateStudentInfo","progress","trim","fileSubDiv","querySelectorAll","forEach","Element","verticalAlign","description","checkForumSubject","replyPost","replyingto","_this$editor8","questionId","getQuestionId","_this$editor9","question","intro","atob","answeringto","Number","open","time","importantdates","generateImportantDates","keys","length","rubrics","generateRubrics","subStatus","submissionStatus","box","heading","body","wrapper","rubric","rubricDiv","appendChild","values","levels","level","levelDiv","score","definition","statusWrapper","statusName","statusValue","isNew","current","draftnot","draft","modifiedWrapper","modifiedName","lastModified","modifiedValue","_submission$current2","timemodified","date","Date","formatDate","gradeWrapper","gradeName","gradings","gradeValue","grade","gradenot","labelDiv","label","value","fontSize","MutationObserver","newText","replace","observe","characterData","subtree","childList","timer","warningDiv","clone","remove","notificationManager","text","type","timerCount","timeleft","nolimit","user","course","nameWrapper","usernameWrapper","courseWrapper","nameLabel","nameValue","usernameLabel","usernameValue","courseLabel","courseValue","name","fullname","userename","username","due","openedWrapper","dueWrapper","remainingWrapper","openedLabel","openedValue","dueLabel","dueValue","remainingLabel","remainingValue","extractDate","opened","remaining","calculateDate","toLocaleString","year","month","day","hour","minute","hour12","parts","split","slice","join","diffMs","Math","floor","getElementById","p1","parentElement","p2","p4","statusBar","assignName","btn","classList","justifyContent","margin","savechanges","createElement","head","leftSide","rightSide","commonStyle","alignItems","insertBefore","firstChild","minWidth","boxShadow","outline","iframeBody","contentDocument","contentWindow","_current$contentWindo","_current$contentWindo2","hamburger","editorId","_current$contentWindo3","_current$contentWindo4","form","msg","subjectnot","e","subjectInput","getContent","preventDefault","stopPropagation","windowManager","alert","discus","getText","editoId","error","console","overdue","key","JSON","parse","localStorage","getItem","tag"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/replay.min.js b/amd/build/replay.min.js index f7306974..0b222771 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","jquery","core/str"],(function(_exports,_ajax,_templates,_jquery,Str){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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_jquery=_interopRequireDefault(_jquery),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.isMetaKeyPressed=!1,this.text="",this.pastedEvents=[],this.currentPasteIndex=0,this.pastedChars=[],this.aiEvents=[],this.currentAiIndex=0,this.aiChars=[],this.undoTimestamps=[],this.undoChars=[];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.identifyUndoEvents(),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))).catch((error=>window.console.log(error))),Str.get_string("pasteEvent","tiny_cursive").then((str=>(localStorage.setItem("pasteEvent",str),str))).catch((error=>window.console.log(error))))}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")]),newElement=(0,_jquery.default)(html).text(str);return(0,_jquery.default)(".tiny_cursive").html(newElement)}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()):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),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)('a[id^="rep"]').addClass("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,metaPressed=!1,shiftPressed=!1,pasteCount=0;for(let i=0;i';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.isMetaKeyPressed=!1,this.currentPasteIndex=0,this.pastedChars=[],this.currentAiIndex=0,this.aiChars=[]),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$event3=event.event)||void 0===_event$event3?void 0:_event$event3.toLowerCase())?({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processKeydownEvent(event,text,cursor,updatedHighlights,updatedDeleted)):"aiInsert"===event.event&&({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processAiInsertEvent(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}}processAiInsertEvent(event,text,cursor,highlights,deletions){if(this.aiEvents&&this.currentAiIndex1,isNewLineInsertion=insertText.startsWith("\n")||insertText.endsWith("\n"),{wordStart:wordStart,wordEnd:wordEnd}=this.findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion),wordToReplace=text.substring(wordStart,wordEnd);this.markCharsAsDeleted(wordToReplace,wordStart,deletions);const replacedLength=wordToReplace.length;text=text.substring(0,wordStart)+insertText+text.substring(wordEnd);const positionDiff=insertText.length-replacedLength,newCursor=this.calculateNewCursorPosition(currentCursor,targetPosition,wordStart,wordEnd,insertText,isNewLineInsertion);return this.updateCharacterIndices(wordStart,wordEnd,positionDiff,insertText),{text:text,cursor:newCursor}}findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion){if(isNewLineInsertion)return{wordStart:currentCursor,wordEnd:currentCursor};const{lineStart:lineStart,lineEnd:lineEnd}=this.findLineRange(text,targetPosition),lineText=text.substring(lineStart,lineEnd),words=this.extractWordsFromLine(lineText,lineStart);return 0===words.length?{wordStart:currentCursor,wordEnd:currentCursor}:isMultiWord?this.findMultiWordMatch(words,aiWords,targetPosition):this.findSingleWordMatch(words,aiWords[0],targetPosition)}findLineRange(text,targetPosition){let lineStart=0;for(let i=targetPosition-1;i>=0;i--)if("\n"===text[i]){lineStart=i+1;break}let lineEnd=text.length;for(let i=targetPosition;i=lineText.length)break;const start=pos;for(;posstart&&words.push({text:lineText.substring(start,pos),start:lineStart+start,end:lineStart+pos})}return words}findMultiWordMatch(words,aiWords,targetPosition){let bestMatch={start:-1,end:-1,score:-1,wordCount:0,similarityScore:0};for(let i=0;ibestMatch.score||matchResult.totalScore===bestMatch.score&&matchResult.similarityScore>bestMatch.similarityScore)&&(bestMatch=matchResult)}if(bestMatch.score>10)return{wordStart:bestMatch.start,wordEnd:bestMatch.end};{const closest=this.findClosestWord(words,targetPosition);return{wordStart:closest.start,wordEnd:closest.end}}}evaluateMultiWordSequence(words,aiWords,startIndex,targetPosition){const seqWords=[];for(let j=0;j=seqStart&&targetPosition<=seqEndPos&&(positionScore+=10,targetPosition>=seqWords[0].start&&targetPosition<=seqWords[0].end&&(positionScore+=5)),positionScore}findSingleWordMatch(words,aiWord,targetPosition){const aiWordLower=aiWord.toLowerCase(),bestSimilarityMatch=this.findBestSimilarityMatch(words,aiWordLower);if(bestSimilarityMatch.score>.5)return{wordStart:bestSimilarityMatch.word.start,wordEnd:bestSimilarityMatch.word.end};const bestPositionMatch=this.findBestPositionMatch(words,aiWordLower,targetPosition);return bestPositionMatch.word?{wordStart:bestPositionMatch.word.start,wordEnd:bestPositionMatch.word.end}:this.findWordBoundaryAtPosition(words[0].start,words[words.length-1].end,targetPosition,this.text)}findBestSimilarityMatch(words,aiWordLower){let bestMatch={word:null,score:0};for(const word of words){let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),similarity>bestMatch.score&&(bestMatch={word:word,score:similarity})}return bestMatch}findBestPositionMatch(words,aiWordLower,targetPosition){let bestMatch={word:null,score:-1};for(const word of words){let score=this.calculateWordScore(word,aiWordLower,targetPosition);score>bestMatch.score&&(bestMatch={word:word,score:score})}return bestMatch}calculateWordScore(word,aiWordLower,targetPosition){let score=0;if(targetPosition>=word.start&&targetPosition<=word.end)score+=30;else{const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));score+=Math.max(0,20-distance)}let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();return wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),score+=10*similarity,score}findWordBoundaryAtPosition(lineStart,lineEnd,targetPosition,text){let wordStart=targetPosition;for(;wordStart>lineStart&&" "!==text[wordStart-1]&&"\n"!==text[wordStart-1];)wordStart--;let wordEnd=targetPosition;for(;wordEnd0)for(let i=0;i=wordStart&&targetPosition<=wordEnd)return wordStart+insertText.length;const positionDiff=insertText.length-(wordEnd-wordStart);return currentCursor>=wordEnd?currentCursor+positionDiff:currentCursor>wordStart&¤tCursorp.index>=wordEnd?{...p,index:p.index+positionDiff}:p.index>=wordStart&&p.indexnull!==p)))}markCharsAsAiInserted(wordStart,insertText){if(this.aiChars||(this.aiChars=[]),""!==insertText.trim())for(let i=0;i{if(!justAddedIndices.has(p.index)){if(p.index>=wordEnd)return{...p,index:p.index+positionDiff};if(p.index>=wordStart&&p.indexnull!==p))}calculateSimilarity(str1,str2){if(str1===str2)return 1;if(0===str1.length||0===str2.length)return 0;if(str1.startsWith(str2)||str2.startsWith(str1))return.8;const len1=str1.length,len2=str2.length,matrix=Array(len2+1).fill(null).map((()=>Array(len1+1).fill(0)));for(let i=0;i<=len1;i++)matrix[0][i]=i;for(let j=0;j<=len2;j++)matrix[j][0]=j;for(let j=1;j<=len2;j++)for(let i=1;i<=len1;i++){const cost=str1[i-1]===str2[j-1]?0:1;matrix[j][i]=Math.min(matrix[j][i-1]+1,matrix[j-1][i]+1,matrix[j-1][i-1]+cost)}const maxLen=Math.max(len1,len2);return 1-matrix[len2][len1]/maxLen}findClosestWord(words,targetPosition){if(0===words.length)return{start:targetPosition,end:targetPosition};let closest=words[0],minDistance=Math.min(Math.abs(targetPosition-words[0].start),Math.abs(targetPosition-words[0].end));for(const word of words){if(targetPosition>=word.start&&targetPosition<=word.end)return word;const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));distance0){const textBeforeUndo=text;text=text.substring(0,newPosition)+text.substring(cursor),cursor=newPosition;for(let i=0;i1}processKeyOperation(key,charToInsert,text,cursor,highlights,deletions,selection){return this.isCtrlBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleCtrlBackspace(text,cursor,deletions)):this.isCtrlDelete(key,cursor,text)?({text:text}=this.handleCtrlDelete(text,cursor,deletions)):this.isCtrlArrowMove(key)?cursor=this.handleCtrlArrowMove(key,text,cursor):this.isRegularBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleBackspace(text,cursor,deletions)):this.isRegularDelete(key,cursor,text)?({text:text}=this.handleDelete(text,cursor,deletions)):this.isArrowUp(key)?cursor=this.handleArrowUp(text,cursor):this.isArrowDown(key)?cursor=this.handleArrowDown(text,cursor):this.isRegularArrowMove(key)?cursor=this.handleArrowMove(key,text,cursor):charToInsert&&charToInsert.length>0&&(selection&&selection.length>0&&({text:text,cursor:cursor}=this.handleSelectionDeletion(selection,text,cursor,deletions)),({text:text,cursor:cursor}=this.handleCharacterInsert(charToInsert,text,cursor,highlights))),{text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}}detectSelection(eventIndex){var _currentEvent$event;const currentEvent=this.logData[eventIndex];if("keydown"===(null===(_currentEvent$event=currentEvent.event)||void 0===_currentEvent$event?void 0:_currentEvent$event.toLowerCase())&&("Backspace"===currentEvent.key||"Delete"===currentEvent.key)){const currentPos=currentEvent.rePosition;return this.processDetection(currentPos,currentEvent,eventIndex)}return null}processDetection(currentPos,currentEvent,eventIndex){for(let i=eventIndex+1;i1)return{start:Math.min(currentPos,nextPos),end:Math.max(currentPos,nextPos),length:positionDiff};if(1===positionDiff)return"Backspace"===currentEvent.key?{start:nextPos,end:currentPos,length:1}:{start:currentPos,end:nextPos,length:1};break}}return null}handleSelectionDeletion(selection,text,cursor,deletions){const{start:start,end:end,length:length}=selection;for(let i=start;ip.index>=startIndex+numDeleted?{...p,index:p.index-numDeleted}:p.index>=startIndex&&p.indexnull!==p)),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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:"Meta"===key?this.isMetaKeyPressed=!0:"v"!==key&&"V"!==key||!this.isControlKeyPressed&&!this.isMetaKeyPressed?["Control","Meta","Backspace","Delete","ArrowLeft","ArrowRight"].includes(key)||(this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.isMetaKeyPressed=!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))),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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.isMetaKeyPressed=!1,this.isPasteEvent=!1,this.pastedChars=[],this.currentPasteIndex=0,this.currentAiIndex=0,this.aiChars=[];let text="",cursor=0,highlights=[],deletions=[],pasteIndex=0,aiIndex=0;for(let i=0;itargetTime){this.currentEventIndex=i;break}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$event4=event.event)||void 0===_event$event4?void 0:_event$event4.toLowerCase())?(this.currentPasteIndex=pasteIndex,"v"!==event.key&&"V"!==event.key||!this.isControlKeyPressed&&!this.isMetaKeyPressed||pasteIndex++,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processKeydownEvent(event,text,cursor,highlights,deletions))):"aiInsert"===event.event&&(this.currentAiIndex=aiIndex,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processAiInsertEvent(event,text,cursor,highlights,deletions)),aiIndex++),this.currentEventIndex=i+1}this.currentPasteIndex=pasteIndex,this.currentAiIndex=aiIndex,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={},aiMap={},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.index{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],isAi=aiMap[currentPosition],isHighlighted=highlightMap[currentPosition]&&" "!==char;html+=isPasted&&isHighlighted?'').concat(char,""):isAi&&isHighlighted?'').concat(char,""):isPasted?''.concat(" "===char?" ":this.escapeHtml(char),""):isAi?''.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})); +define("tiny_cursive/replay",["exports","core/ajax","core/templates","jquery","core/str"],(function(_exports,_ajax,_templates,_jquery,Str){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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_jquery=_interopRequireDefault(_jquery),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.isMetaKeyPressed=!1,this.text="",this.pastedEvents=[],this.currentPasteIndex=0,this.pastedChars=[],this.aiEvents=[],this.currentAiIndex=0,this.aiChars=[],this.undoTimestamps=[],this.undoChars=[];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.identifyUndoEvents(),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))).catch((error=>window.console.log(error))),Str.get_string("pasteEvent","tiny_cursive").then((str=>(localStorage.setItem("pasteEvent",str),str))).catch((error=>window.console.log(error))))}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")]),newElement=(0,_jquery.default)(html).text(str);return(0,_jquery.default)(".tiny_cursive").html(newElement)}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()):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),(0,_jquery.default)(".tiny_cursive-nav-tab").find(".active").removeClass("active"),(0,_jquery.default)('a[id^="rep"]').addClass("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,metaPressed=!1,shiftPressed=!1,pasteCount=0;for(let i=0;i';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.isMetaKeyPressed=!1,this.currentPasteIndex=0,this.pastedChars=[],this.currentAiIndex=0,this.aiChars=[]),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$event3=event.event)||void 0===_event$event3?void 0:_event$event3.toLowerCase())?({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processKeydownEvent(event,text,cursor,updatedHighlights,updatedDeleted)):"aiInsert"===event.event&&({text:text,cursor:cursor,updatedHighlights:updatedHighlights,updatedDeleted:updatedDeleted}=this.processAiInsertEvent(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}}processAiInsertEvent(event,text,cursor,highlights,deletions){if(this.aiEvents&&this.currentAiIndex1,isNewLineInsertion=insertText.startsWith("\n")||insertText.endsWith("\n"),{wordStart:wordStart,wordEnd:wordEnd}=this.findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion),wordToReplace=text.substring(wordStart,wordEnd);this.markCharsAsDeleted(wordToReplace,wordStart,deletions);const replacedLength=wordToReplace.length;text=text.substring(0,wordStart)+insertText+text.substring(wordEnd);const positionDiff=insertText.length-replacedLength,newCursor=this.calculateNewCursorPosition(currentCursor,targetPosition,wordStart,wordEnd,insertText,isNewLineInsertion);return this.updateCharacterIndices(wordStart,wordEnd,positionDiff,insertText),{text:text,cursor:newCursor}}findWordToReplace(text,targetPosition,currentCursor,aiWords,isMultiWord,isNewLineInsertion){if(isNewLineInsertion)return{wordStart:currentCursor,wordEnd:currentCursor};const{lineStart:lineStart,lineEnd:lineEnd}=this.findLineRange(text,targetPosition),lineText=text.substring(lineStart,lineEnd),words=this.extractWordsFromLine(lineText,lineStart);return 0===words.length?{wordStart:currentCursor,wordEnd:currentCursor}:isMultiWord?this.findMultiWordMatch(words,aiWords,targetPosition):this.findSingleWordMatch(words,aiWords[0],targetPosition)}findLineRange(text,targetPosition){let lineStart=0;for(let i=targetPosition-1;i>=0;i--)if("\n"===text[i]){lineStart=i+1;break}let lineEnd=text.length;for(let i=targetPosition;i=lineText.length)break;const start=pos;for(;posstart&&words.push({text:lineText.substring(start,pos),start:lineStart+start,end:lineStart+pos})}return words}findMultiWordMatch(words,aiWords,targetPosition){let bestMatch={start:-1,end:-1,score:-1,wordCount:0,similarityScore:0};for(let i=0;ibestMatch.score||matchResult.totalScore===bestMatch.score&&matchResult.similarityScore>bestMatch.similarityScore)&&(bestMatch=matchResult)}if(bestMatch.score>10)return{wordStart:bestMatch.start,wordEnd:bestMatch.end};{const closest=this.findClosestWord(words,targetPosition);return{wordStart:closest.start,wordEnd:closest.end}}}evaluateMultiWordSequence(words,aiWords,startIndex,targetPosition){const seqWords=[];for(let j=0;j=seqStart&&targetPosition<=seqEndPos&&(positionScore+=10,targetPosition>=seqWords[0].start&&targetPosition<=seqWords[0].end&&(positionScore+=5)),positionScore}findSingleWordMatch(words,aiWord,targetPosition){const aiWordLower=aiWord.toLowerCase(),bestSimilarityMatch=this.findBestSimilarityMatch(words,aiWordLower);if(bestSimilarityMatch.score>.5)return{wordStart:bestSimilarityMatch.word.start,wordEnd:bestSimilarityMatch.word.end};const bestPositionMatch=this.findBestPositionMatch(words,aiWordLower,targetPosition);return bestPositionMatch.word?{wordStart:bestPositionMatch.word.start,wordEnd:bestPositionMatch.word.end}:this.findWordBoundaryAtPosition(words[0].start,words[words.length-1].end,targetPosition,this.text)}findBestSimilarityMatch(words,aiWordLower){let bestMatch={word:null,score:0};for(const word of words){let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),similarity>bestMatch.score&&(bestMatch={word:word,score:similarity})}return bestMatch}findBestPositionMatch(words,aiWordLower,targetPosition){let bestMatch={word:null,score:-1};for(const word of words){let score=this.calculateWordScore(word,aiWordLower,targetPosition);score>bestMatch.score&&(bestMatch={word:word,score:score})}return bestMatch}calculateWordScore(word,aiWordLower,targetPosition){let score=0;if(targetPosition>=word.start&&targetPosition<=word.end)score+=30;else{const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));score+=Math.max(0,20-distance)}let similarity=this.calculateSimilarity(aiWordLower,word.text.toLowerCase());const wordLower=word.text.toLowerCase();return wordLower.length<.5*aiWordLower.length&&aiWordLower.startsWith(wordLower)&&(similarity*=.3),score+=10*similarity,score}findWordBoundaryAtPosition(lineStart,lineEnd,targetPosition,text){let wordStart=targetPosition;for(;wordStart>lineStart&&" "!==text[wordStart-1]&&"\n"!==text[wordStart-1];)wordStart--;let wordEnd=targetPosition;for(;wordEnd0)for(let i=0;i=wordStart&&targetPosition<=wordEnd)return wordStart+insertText.length;const positionDiff=insertText.length-(wordEnd-wordStart);return currentCursor>=wordEnd?currentCursor+positionDiff:currentCursor>wordStart&¤tCursorp.index>=wordEnd?{...p,index:p.index+positionDiff}:p.index>=wordStart&&p.indexnull!==p)))}markCharsAsAiInserted(wordStart,insertText){if(this.aiChars||(this.aiChars=[]),""!==insertText.trim())for(let i=0;i{if(!justAddedIndices.has(p.index)){if(p.index>=wordEnd)return{...p,index:p.index+positionDiff};if(p.index>=wordStart&&p.indexnull!==p))}calculateSimilarity(str1,str2){if(str1===str2)return 1;if(0===str1.length||0===str2.length)return 0;if(str1.startsWith(str2)||str2.startsWith(str1))return.8;const len1=str1.length,len2=str2.length,matrix=Array(len2+1).fill(null).map((()=>Array(len1+1).fill(0)));for(let i=0;i<=len1;i++)matrix[0][i]=i;for(let j=0;j<=len2;j++)matrix[j][0]=j;for(let j=1;j<=len2;j++)for(let i=1;i<=len1;i++){const cost=str1[i-1]===str2[j-1]?0:1;matrix[j][i]=Math.min(matrix[j][i-1]+1,matrix[j-1][i]+1,matrix[j-1][i-1]+cost)}const maxLen=Math.max(len1,len2);return 1-matrix[len2][len1]/maxLen}findClosestWord(words,targetPosition){if(0===words.length)return{start:targetPosition,end:targetPosition};let closest=words[0],minDistance=Math.min(Math.abs(targetPosition-words[0].start),Math.abs(targetPosition-words[0].end));for(const word of words){if(targetPosition>=word.start&&targetPosition<=word.end)return word;const distance=Math.min(Math.abs(targetPosition-word.start),Math.abs(targetPosition-word.end));distance0){const textBeforeUndo=text;text=text.substring(0,newPosition)+text.substring(cursor),cursor=newPosition;for(let i=0;i1}processKeyOperation(key,charToInsert,text,cursor,highlights,deletions,selection){return this.isCtrlBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleCtrlBackspace(text,cursor,deletions)):this.isCtrlDelete(key,cursor,text)?({text:text}=this.handleCtrlDelete(text,cursor,deletions)):this.isCtrlArrowMove(key)?cursor=this.handleCtrlArrowMove(key,text,cursor):this.isRegularBackspace(key,cursor)?({text:text,cursor:cursor}=this.handleBackspace(text,cursor,deletions)):this.isRegularDelete(key,cursor,text)?({text:text}=this.handleDelete(text,cursor,deletions)):this.isArrowUp(key)?cursor=this.handleArrowUp(text,cursor):this.isArrowDown(key)?cursor=this.handleArrowDown(text,cursor):this.isRegularArrowMove(key)?cursor=this.handleArrowMove(key,text,cursor):charToInsert&&charToInsert.length>0&&(selection&&selection.length>0&&({text:text,cursor:cursor}=this.handleSelectionDeletion(selection,text,cursor,deletions)),({text:text,cursor:cursor}=this.handleCharacterInsert(charToInsert,text,cursor,highlights))),{text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}}detectSelection(eventIndex){var _currentEvent$event;const currentEvent=this.logData[eventIndex];if("keydown"===(null===(_currentEvent$event=currentEvent.event)||void 0===_currentEvent$event?void 0:_currentEvent$event.toLowerCase())&&("Backspace"===currentEvent.key||"Delete"===currentEvent.key)){const currentPos=currentEvent.rePosition;return this.processDetection(currentPos,currentEvent,eventIndex)}return null}processDetection(currentPos,currentEvent,eventIndex){for(let i=eventIndex+1;i1)return{start:Math.min(currentPos,nextPos),end:Math.max(currentPos,nextPos),length:positionDiff};if(1===positionDiff)return"Backspace"===currentEvent.key?{start:nextPos,end:currentPos,length:1}:{start:currentPos,end:nextPos,length:1};break}}return null}handleSelectionDeletion(selection,text,cursor,deletions){const{start:start,end:end,length:length}=selection;for(let i=start;ip.index>=startIndex+numDeleted?{...p,index:p.index-numDeleted}:p.index>=startIndex&&p.indexnull!==p)),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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:"Meta"===key?this.isMetaKeyPressed=!0:"v"!==key&&"V"!==key||!this.isControlKeyPressed&&!this.isMetaKeyPressed?["Control","Meta","Backspace","Delete","ArrowLeft","ArrowRight"].includes(key)||(this.isControlKeyPressed=!1,this.isShiftKeyPressed=!1,this.isMetaKeyPressed=!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))),this.aiChars&&(this.aiChars=this.aiChars.map((p=>p.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.isMetaKeyPressed=!1,this.isPasteEvent=!1,this.pastedChars=[],this.currentPasteIndex=0,this.currentAiIndex=0,this.aiChars=[];let text="",cursor=0,highlights=[],deletions=[],pasteIndex=0,aiIndex=0;for(let i=0;itargetTime){this.currentEventIndex=i;break}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$event4=event.event)||void 0===_event$event4?void 0:_event$event4.toLowerCase())?(this.currentPasteIndex=pasteIndex,"v"!==event.key&&"V"!==event.key||!this.isControlKeyPressed&&!this.isMetaKeyPressed||pasteIndex++,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processKeydownEvent(event,text,cursor,highlights,deletions))):"aiInsert"===event.event&&(this.currentAiIndex=aiIndex,({text:text,cursor:cursor,updatedHighlights:highlights,updatedDeleted:deletions}=this.processAiInsertEvent(event,text,cursor,highlights,deletions)),aiIndex++),this.currentEventIndex=i+1}this.currentPasteIndex=pasteIndex,this.currentAiIndex=aiIndex,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={},aiMap={},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.index{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],isAi=aiMap[currentPosition],isHighlighted=highlightMap[currentPosition]&&" "!==char;html+=isPasted&&isHighlighted?`${char}`:isAi&&isHighlighted?`${char}`:isPasted?`${" "===char?" ":this.escapeHtml(char)}`:isAi?`${" "===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})); //# 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 681f26ef..126f99bc 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 kuldeep singh \n */\n\nimport {call as fetchJson} from 'core/ajax';\nimport templates from 'core/templates';\nimport $ from 'jquery';\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.isMetaKeyPressed = false;\n this.text = '';\n this.pastedEvents = [];\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.aiEvents = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\n this.undoTimestamps = [];\n this.undoChars = [];\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 this.identifyUndoEvents();\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 }).catch(error => window.console.log(error));\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\n localStorage.setItem('pasteEvent', str);\n return str;\n }).catch(error => window.console.log(error));\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') {\n if (typeof event.pastedContent === 'string' && event.pastedContent.trim() !== '') {\n this.pastedEvents.push(event.pastedContent);\n }\n }\n if (event.event === 'aiInsert' && event.aiContent) {\n this.aiEvents.push(event.aiContent);\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 newElement = $(html).text(str);\n return $('.tiny_cursive').html(newElement);\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\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 $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('a[id^=\"rep\"]').addClass('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 let metaPressed = 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 === 'Meta') {\n metaPressed = true;\n } else if (event.key === 'Shift') {\n shiftPressed = true;\n } else if ((event.key === 'v' || event.key === 'V') && (controlPressed || metaPressed)) {\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 metaPressed = false;\n } else {\n controlPressed = false;\n shiftPressed = false;\n metaPressed = false;\n }\n }\n }\n\n if (this.pasteEventsPanel) {\n this.populatePasteEventsPanel(this.pasteEventsPanel);\n }\n }\n\n identifyUndoEvents() {\n this.undoTimestamps = [];\n let controlPressed = false;\n let metaPressed = false;\n let undoCount = 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 === 'Meta') {\n metaPressed = true;\n } else if ((event.key === 'z' || event.key === 'Z') && (controlPressed || metaPressed)) {\n const timestamp = event.normalizedTime || 0;\n this.undoTimestamps.push({\n index: undoCount,\n time: timestamp,\n formattedTime: this.formatTime(timestamp),\n timestamp\n });\n undoCount++;\n controlPressed = false;\n metaPressed = false;\n } else {\n controlPressed = false;\n metaPressed = false;\n }\n }\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.isMetaKeyPressed = false;\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\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 } else if (event.event === 'aiInsert') {\n ({text, cursor, updatedHighlights, updatedDeleted} =\n this.processAiInsertEvent(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 processAiInsertEvent(event, text, cursor, highlights, deletions) {\n if (this.aiEvents && this.currentAiIndex < this.aiEvents.length) {\n const aiContent = this.aiEvents[this.currentAiIndex];\n // Use event.rePosition which points to where the word to replace is\n const targetPosition = event.rePosition;\n\n ({text, cursor} = this.handleAiReplacement(aiContent, text, targetPosition, cursor, deletions));\n this.currentAiIndex++;\n }\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n\n handleAiReplacement(aiContent, text, targetPosition, currentCursor, deletions) {\n const insertText = aiContent || '';\n const aiWords = insertText.trim().split(/\\s+/);\n const isMultiWord = aiWords.length > 1;\n const isNewLineInsertion = insertText.startsWith('\\n') || insertText.endsWith('\\n');\n\n const {wordStart, wordEnd} = this.findWordToReplace(\n text,\n targetPosition,\n currentCursor,\n aiWords,\n isMultiWord,\n isNewLineInsertion\n );\n\n const wordToReplace = text.substring(wordStart, wordEnd);\n\n // Mark replaced characters as deleted\n this.markCharsAsDeleted(wordToReplace, wordStart, deletions);\n\n // Perform the replacement\n const replacedLength = wordToReplace.length;\n text = text.substring(0, wordStart) + insertText + text.substring(wordEnd);\n const positionDiff = insertText.length - replacedLength;\n\n // Calculate new cursor position\n const newCursor = this.calculateNewCursorPosition(\n currentCursor,\n targetPosition,\n wordStart,\n wordEnd,\n insertText,\n isNewLineInsertion\n );\n\n // Update character tracking arrays\n this.updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText);\n\n return {text, cursor: newCursor};\n }\n\n findWordToReplace(text, targetPosition, currentCursor, aiWords, isMultiWord, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n const {lineStart, lineEnd} = this.findLineRange(text, targetPosition);\n const lineText = text.substring(lineStart, lineEnd);\n const words = this.extractWordsFromLine(lineText, lineStart);\n\n if (words.length === 0) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n if (isMultiWord) {\n return this.findMultiWordMatch(words, aiWords, targetPosition);\n } else {\n return this.findSingleWordMatch(words, aiWords[0], targetPosition);\n }\n }\n\n findLineRange(text, targetPosition) {\n let lineStart = 0;\n for (let i = targetPosition - 1; i >= 0; i--) {\n if (text[i] === '\\n') {\n lineStart = i + 1;\n break;\n }\n }\n\n let lineEnd = text.length;\n for (let i = targetPosition; i < text.length; i++) {\n if (text[i] === '\\n') {\n lineEnd = i;\n break;\n }\n }\n\n return {lineStart, lineEnd};\n }\n\n extractWordsFromLine(lineText, lineStart) {\n const words = [];\n let pos = 0;\n\n while (pos < lineText.length) {\n // Skip spaces\n while (pos < lineText.length && lineText[pos] === ' ') {\n pos++;\n }\n if (pos >= lineText.length) {\n break;\n }\n\n // Extract word\n const start = pos;\n while (pos < lineText.length && lineText[pos] !== ' ') {\n pos++;\n }\n\n if (pos > start) {\n words.push({\n text: lineText.substring(start, pos),\n start: lineStart + start,\n end: lineStart + pos\n });\n }\n }\n\n return words;\n }\n\n findMultiWordMatch(words, aiWords, targetPosition) {\n let bestMatch = {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n\n for (let i = 0; i < words.length; i++) {\n const matchResult = this.evaluateMultiWordSequence(words, aiWords, i, targetPosition);\n\n if (matchResult.totalScore > bestMatch.score ||\n (matchResult.totalScore === bestMatch.score &&\n matchResult.similarityScore > bestMatch.similarityScore)) {\n bestMatch = matchResult;\n }\n }\n\n if (bestMatch.score > 10) {\n return {wordStart: bestMatch.start, wordEnd: bestMatch.end};\n } else {\n const closest = this.findClosestWord(words, targetPosition);\n return {wordStart: closest.start, wordEnd: closest.end};\n }\n }\n\n evaluateMultiWordSequence(words, aiWords, startIndex, targetPosition) {\n const seqWords = [];\n for (let j = 0; j < aiWords.length && startIndex + j < words.length; j++) {\n seqWords.push(words[startIndex + j]);\n }\n\n if (seqWords.length === 0) {\n return {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n }\n\n const similarityScore = this.calculateSequenceSimilarity(aiWords, seqWords);\n const positionScore = this.calculatePositionScore(seqWords, targetPosition);\n const totalScore = similarityScore + positionScore + seqWords.length;\n\n return {\n start: seqWords[0].start,\n end: seqWords[seqWords.length - 1].end,\n score: totalScore,\n wordCount: seqWords.length,\n similarityScore: similarityScore\n };\n }\n\n calculateSequenceSimilarity(aiWords, seqWords) {\n let similarityScore = 0;\n const compareLength = Math.min(seqWords.length, aiWords.length);\n\n for (let k = 0; k < compareLength; k++) {\n const ai = aiWords[k].toLowerCase();\n const seq = seqWords[k].text.toLowerCase();\n\n if (ai === seq) {\n similarityScore += 10;\n } else {\n const similarity = this.calculateSimilarity(ai, seq);\n similarityScore += similarity * 10;\n }\n }\n\n return similarityScore;\n }\n\n calculatePositionScore(seqWords, targetPosition) {\n let positionScore = 0;\n const seqStart = seqWords[0].start;\n const seqEndPos = seqWords[seqWords.length - 1].end;\n\n if (targetPosition >= seqStart && targetPosition <= seqEndPos) {\n positionScore += 10;\n if (targetPosition >= seqWords[0].start && targetPosition <= seqWords[0].end) {\n positionScore += 5;\n }\n }\n\n return positionScore;\n }\n\n findSingleWordMatch(words, aiWord, targetPosition) {\n const aiWordLower = aiWord.toLowerCase();\n const bestSimilarityMatch = this.findBestSimilarityMatch(words, aiWordLower);\n\n if (bestSimilarityMatch.score > 0.5) {\n return {wordStart: bestSimilarityMatch.word.start, wordEnd: bestSimilarityMatch.word.end};\n }\n\n const bestPositionMatch = this.findBestPositionMatch(words, aiWordLower, targetPosition);\n\n if (bestPositionMatch.word) {\n return {wordStart: bestPositionMatch.word.start, wordEnd: bestPositionMatch.word.end};\n }\n\n // Fallback to position-based word boundary\n return this.findWordBoundaryAtPosition(words[0].start, words[words.length - 1].end,\n targetPosition, this.text);\n }\n\n findBestSimilarityMatch(words, aiWordLower) {\n let bestMatch = {word: null, score: 0};\n\n for (const word of words) {\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n\n // Penalize short prefix matches\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n\n if (similarity > bestMatch.score) {\n bestMatch = {word, score: similarity};\n }\n }\n\n return bestMatch;\n }\n\n findBestPositionMatch(words, aiWordLower, targetPosition) {\n let bestMatch = {word: null, score: -1};\n\n for (const word of words) {\n let score = this.calculateWordScore(word, aiWordLower, targetPosition);\n\n if (score > bestMatch.score) {\n bestMatch = {word, score};\n }\n }\n\n return bestMatch;\n }\n\n calculateWordScore(word, aiWordLower, targetPosition) {\n let score = 0;\n\n // Position score\n if (targetPosition >= word.start && targetPosition <= word.end) {\n score += 30;\n } else {\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n score += Math.max(0, 20 - distance);\n }\n\n // Similarity score with penalty\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n score += similarity * 10;\n\n return score;\n }\n\n findWordBoundaryAtPosition(lineStart, lineEnd, targetPosition, text) {\n let wordStart = targetPosition;\n while (wordStart > lineStart && text[wordStart - 1] !== ' ' && text[wordStart - 1] !== '\\n') {\n wordStart--;\n }\n let wordEnd = targetPosition;\n while (wordEnd < lineEnd && text[wordEnd] !== ' ' && text[wordEnd] !== '\\n') {\n wordEnd++;\n }\n return {wordStart, wordEnd};\n }\n\n markCharsAsDeleted(wordToReplace, wordStart, deletions) {\n if (wordToReplace.length > 0) {\n for (let i = 0; i < wordToReplace.length; i++) {\n deletions.push({\n index: wordStart + i,\n chars: wordToReplace[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n\n calculateNewCursorPosition(currentCursor, targetPosition, wordStart, wordEnd, insertText, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return wordStart + insertText.length;\n }\n\n if (targetPosition >= wordStart && targetPosition <= wordEnd) {\n return wordStart + insertText.length;\n }\n\n const positionDiff = insertText.length - (wordEnd - wordStart);\n\n if (currentCursor >= wordEnd) {\n return currentCursor + positionDiff;\n } else if (currentCursor > wordStart && currentCursor < wordEnd) {\n return wordStart + insertText.length;\n }\n\n return currentCursor;\n }\n\n updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText) {\n // Update pasted character indices\n this.updatePastedCharIndices(wordStart, wordEnd, positionDiff);\n\n // Mark characters as AI-inserted\n this.markCharsAsAiInserted(wordStart, insertText);\n\n // Update AI character indices\n this.updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText);\n }\n\n updatePastedCharIndices(wordStart, wordEnd, positionDiff) {\n if (this.pastedChars) {\n this.pastedChars = this.pastedChars.map(p => {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n return p;\n }).filter(p => p !== null);\n }\n }\n\n markCharsAsAiInserted(wordStart, insertText) {\n if (!this.aiChars) {\n this.aiChars = [];\n }\n\n if (insertText.trim() !== '') {\n for (let i = 0; i < insertText.length; i++) {\n this.aiChars.push({\n index: wordStart + i,\n chars: insertText[i]\n });\n }\n }\n }\n\n updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText) {\n const justAddedIndices = new Set();\n for (let i = 0; i < insertText.length; i++) {\n justAddedIndices.add(wordStart + i);\n }\n\n this.aiChars = this.aiChars.map(p => {\n if (!justAddedIndices.has(p.index)) {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n }\n return p;\n }).filter(p => p !== null);\n }\n\n // Calculate similarity between two strings\n calculateSimilarity(str1, str2) {\n if (str1 === str2) {\n return 1;\n }\n if (str1.length === 0 || str2.length === 0) {\n return 0;\n }\n\n // Check if one string is a prefix of the other\n if (str1.startsWith(str2) || str2.startsWith(str1)) {\n return 0.8;\n }\n\n // Levenshtein distance\n const len1 = str1.length;\n const len2 = str2.length;\n const matrix = Array(len2 + 1).fill(null).map(() => Array(len1 + 1).fill(0));\n\n for (let i = 0; i <= len1; i++) {\n matrix[0][i] = i;\n }\n for (let j = 0; j <= len2; j++) {\n matrix[j][0] = j;\n }\n\n for (let j = 1; j <= len2; j++) {\n for (let i = 1; i <= len1; i++) {\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\n matrix[j][i] = Math.min(\n matrix[j][i - 1] + 1,\n matrix[j - 1][i] + 1,\n matrix[j - 1][i - 1] + cost\n );\n }\n }\n\n const maxLen = Math.max(len1, len2);\n return 1 - (matrix[len2][len1] / maxLen);\n }\n\n // Find the word closest to a target position\n findClosestWord(words, targetPosition) {\n if (words.length === 0) {\n return {start: targetPosition, end: targetPosition};\n }\n\n let closest = words[0];\n let minDistance = Math.min(\n Math.abs(targetPosition - words[0].start),\n Math.abs(targetPosition - words[0].end)\n );\n\n for (const word of words) {\n if (targetPosition >= word.start && targetPosition <= word.end) {\n return word;\n }\n\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n\n if (distance < minDistance) {\n minDistance = distance;\n closest = word;\n }\n }\n\n return closest;\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\n // Handle copy operation (Ctrl+C)\n if (this.isCopyOperation(key)) {\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Handle undo operation (Ctrl+Z)\n if (this.isUndoOperation(key)) {\n return this.handleUndoOperation(event, text, cursor, highlights, deletions);\n }\n\n // Detect selection for current event\n const currentEventIndex = this.currentEventIndex;\n const selection = this.detectSelection(currentEventIndex);\n\n // Handle paste operation (Ctrl+V)\n if (this.isPasteOperation(key, event)) {\n return this.handlePasteOperation(event, selection, text, cursor, highlights, deletions);\n }\n\n // Update modifier key states\n this.updateModifierStates(key);\n\n // Handle selection deletion with Backspace/Delete\n if (this.isSelectionDeletion(key, selection)) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Process various key operations\n return this.processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection);\n }\n\n isCopyOperation(key) {\n return (key === 'c' || key === 'C') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n isUndoOperation(key) {\n return (key === 'z' || key === 'Z') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n handleUndoOperation(event, text, cursor, highlights, deletions) {\n const nextEventIndex = this.currentEventIndex + 1;\n if (nextEventIndex < this.logData.length) {\n const nextEvent = this.logData[nextEventIndex];\n\n if (nextEvent.event === 'keyUp' && (nextEvent.key === 'z' || nextEvent.key === 'Z')) {\n const newPosition = nextEvent.rePosition;\n if (newPosition < cursor && text.length > 0) {\n const textBeforeUndo = text;\n text = text.substring(0, newPosition) + text.substring(cursor);\n cursor = newPosition;\n\n // Mark as deleted for visual effect\n for (let i = 0; i < textBeforeUndo.length && i < cursor; i++) {\n deletions.push({\n index: newPosition,\n chars: textBeforeUndo[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n }\n\n this.isControlKeyPressed = false;\n this.isMetaKeyPressed = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n isPasteOperation(key, event) {\n if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n return (event.pastedContent && event.pastedContent.trim() !== '') ||\n (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length);\n }\n return false;\n }\n\n handlePasteOperation(event, selection, text, cursor, highlights, deletions) {\n const pastedContent = event.pastedContent || this.pastedEvents[this.currentPasteIndex];\n\n if (selection) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\n this.currentPasteIndex++;\n this.resetModifierStates();\n this.isPasteEvent = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n resetModifierStates() {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = false;\n }\n\n isSelectionDeletion(key, selection) {\n return (key === 'Backspace' || key === 'Delete') && selection && selection.length > 1;\n }\n\n processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection) {\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 if (selection && selection.length > 0) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\n }\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n detectSelection(eventIndex) {\n const currentEvent = this.logData[eventIndex];\n\n if (currentEvent.event?.toLowerCase() === 'keydown' &&\n (currentEvent.key === 'Backspace' || currentEvent.key === 'Delete')) {\n\n const currentPos = currentEvent.rePosition;\n return this.processDetection(currentPos, currentEvent, eventIndex);\n }\n return null;\n }\n\n processDetection(currentPos, currentEvent, eventIndex) {\n for (let i = eventIndex + 1; i < this.logData.length; i++) {\n const nextEvent = this.logData[i];\n\n if (nextEvent.event?.toLowerCase() === 'keyup' &&\n nextEvent.key === currentEvent.key) {\n\n const nextPos = nextEvent.rePosition;\n\n // Calculate the difference in positions\n const positionDiff = Math.abs(currentPos - nextPos);\n\n if (positionDiff > 1) {\n return {\n start: Math.min(currentPos, nextPos),\n end: Math.max(currentPos, nextPos),\n length: positionDiff\n };\n } else if (positionDiff === 1) {\n if (currentEvent.key === 'Backspace') {\n return {\n start: nextPos,\n end: currentPos,\n length: 1\n };\n } else {\n return {\n start: currentPos,\n end: nextPos,\n length: 1\n };\n }\n }\n break;\n }\n }\n return null;\n }\n\n handleSelectionDeletion(selection, text, cursor, deletions) {\n const {start, end, length} = selection;\n\n // Add each character in the selection to the deletions array\n for (let i = start; i < end && i < text.length; i++) {\n deletions.push({\n index: start,\n chars: text[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n\n text = text.substring(0, start) + text.substring(end);\n\n this.shiftPastedCharsIndices(start, length);\n\n cursor = start;\n\n return {text, cursor};\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 if (this.aiChars) {\n this.aiChars = this.aiChars.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 return null;\n }\n return p;\n }).filter(p => p !== null);\n }\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 === 'Meta') {\n this.isMetaKeyPressed = true;\n } else if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n this.isPasteEvent = true;\n } else if (!['Control', 'Meta', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = 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 (this.aiChars) {\n this.aiChars = this.aiChars.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.isMetaKeyPressed = false;\n this.isPasteEvent = false;\n this.pastedChars = [];\n this.currentPasteIndex = 0;\n this.currentAiIndex = 0;\n this.aiChars = [];\n let text = '';\n let cursor = 0;\n let highlights = [];\n let deletions = [];\n let pasteIndex = 0;\n let aiIndex = 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 && (this.currentEventIndex === 0 ||\n 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 || this.isMetaKeyPressed)) {\n pasteIndex++;\n }\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\n } else if (event.event === 'aiInsert') {\n this.currentAiIndex = aiIndex;\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processAiInsertEvent(event, text, cursor, highlights, deletions));\n aiIndex++;\n }\n this.currentEventIndex = i + 1;\n }\n\n this.currentPasteIndex = pasteIndex;\n this.currentAiIndex = aiIndex;\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 // eslint-disable-next-line complexity\n updateDisplayText(text, cursorPosition, highlights, deletions) {\n let html = '';\n const highlightMap = {};\n const deletionMap = {};\n const pastedMap = {};\n const aiMap = {};\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 // Process AI characters for styling\n if (this.aiChars) {\n this.aiChars.forEach(p => {\n if (p.index < text.length) {\n aiMap[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 isAi = aiMap[currentPosition];\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\n\n if (isPasted && isHighlighted) {\n html += `${char}`;\n } else if (isAi && isHighlighted) {\n html += `${char}`;\n } else if (isPasted) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isAi) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isHighlighted) {\n html += `${char}`;\n } else {\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","isMetaKeyPressed","text","pastedEvents","currentPasteIndex","pastedChars","aiEvents","currentAiIndex","aiChars","undoTimestamps","undoChars","element","document","getElementById","Error","outputElement","loadJSON","then","data","status","processData","this","logData","length","identifyPasteEvents","identifyUndoEvents","constructController","startReplay","handleNoSubmission","catch","error","window","console","message","localStorage","getItem","Str","get_string","str","setItem","log","JSON","parse","comments","Array","isArray","payload","i","event","pastedContent","trim","push","aiContent","unixTimestamp","startTime","map","normalizedTime","html","Promise","all","templates","render","newElement","stopReplay","clearTimeout","replayTimeout","playButton","playSvg","createElement","src","M","util","image_url","querySelector","innerHTML","outerHTML","currentPosition","replayIntervalId","clearInterval","container","controlContainer","buildControllerUI","remove","topRow","classList","add","createPlayButton","appendChild","scrubberContainer","createScrubberContainer","timeDisplay","createTimeDisplay","bottomRow","speedContainer","createSpeedControls","pasteEventsToggle","createPasteEventsToggle","pasteEventsPanel","addEventListener","find","removeClass","addClass","scrubberElement","type","max","min","value","skipToTime","parseInt","textContent","speedLabel","speedGroup","forEach","speedBtn","dataset","querySelectorAll","btn","pasteEventsIcon","pasteIcon","pasteEventsText","pasteEventCount","className","style","marginLeft","chevronIcon","chevron","transition","createPasteEventsPanel","isHidden","display","transform","existingPanel","populatePasteEventsPanel","controlPressed","metaPressed","shiftPressed","pasteCount","toLowerCase","key","timestamp","index","time","formattedTime","formatTime","pastedText","undoCount","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","processAiInsertEvent","filter","h","expiresAt","d","updateDisplayText","percentComplete","baseIncrement","incrementTime","setTimeout","getLineAndColumn","pos","before","substring","lineIndex","split","col","lastIndexOf","highlights","deletions","targetPosition","handleAiReplacement","currentCursor","insertText","aiWords","isMultiWord","isNewLineInsertion","startsWith","endsWith","wordStart","wordEnd","findWordToReplace","wordToReplace","markCharsAsDeleted","replacedLength","positionDiff","newCursor","calculateNewCursorPosition","updateCharacterIndices","lineStart","lineEnd","findLineRange","lineText","words","extractWordsFromLine","findMultiWordMatch","findSingleWordMatch","start","end","bestMatch","score","wordCount","similarityScore","matchResult","evaluateMultiWordSequence","totalScore","closest","findClosestWord","startIndex","seqWords","j","calculateSequenceSimilarity","calculatePositionScore","compareLength","k","ai","seq","calculateSimilarity","positionScore","seqStart","seqEndPos","aiWord","aiWordLower","bestSimilarityMatch","findBestSimilarityMatch","word","bestPositionMatch","findBestPositionMatch","findWordBoundaryAtPosition","similarity","wordLower","calculateWordScore","distance","abs","chars","updatePastedCharIndices","markCharsAsAiInserted","updateAiCharIndices","p","justAddedIndices","Set","has","str1","str2","len1","len2","matrix","fill","cost","maxLen","minDistance","charToInsert","applyKey","isCopyOperation","isUndoOperation","handleUndoOperation","selection","detectSelection","isPasteOperation","handlePasteOperation","updateModifierStates","isSelectionDeletion","handleSelectionDeletion","processKeyOperation","nextEventIndex","nextEvent","newPosition","textBeforeUndo","handlePasteInsert","resetModifierStates","isCtrlBackspace","handleCtrlBackspace","isCtrlDelete","handleCtrlDelete","isCtrlArrowMove","handleCtrlArrowMove","isRegularBackspace","handleBackspace","isRegularDelete","handleDelete","isArrowUp","handleArrowUp","isArrowDown","handleArrowDown","isRegularArrowMove","handleArrowMove","handleCharacterInsert","eventIndex","currentEvent","currentPos","processDetection","nextPos","shiftPastedCharsIndices","numDeleted","includes","findPreviousWordBoundary","findNextWordBoundary","wordToDelete","lines","prevLine","slice","join","nextLine","lastNonSpace","skipToEnd","textOutput","wasPlaying","targetTime","pasteIndex","aiIndex","highlightMap","deletionMap","pastedMap","aiMap","outOfRangeDeletions","textLines","line","char","isPasted","isAi","isHighlighted","escapeHtml","sort","a","b","cursorHTML","cursorPos","deletedWordHTML","wasScrolledToBottom","scrollHeight","clientHeight","scrollTop","isCursorBelowViewport","cursorElement","cursorRect","getBoundingClientRect","outputRect","bottom","unsafe","replace"],"mappings":"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"} \ 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 kuldeep singh \n */\n\nimport {call as fetchJson} from 'core/ajax';\nimport templates from 'core/templates';\nimport $ from 'jquery';\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.isMetaKeyPressed = false;\n this.text = '';\n this.pastedEvents = [];\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.aiEvents = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\n this.undoTimestamps = [];\n this.undoChars = [];\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 this.identifyUndoEvents();\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 }).catch(error => window.console.log(error));\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\n localStorage.setItem('pasteEvent', str);\n return str;\n }).catch(error => window.console.log(error));\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') {\n if (typeof event.pastedContent === 'string' && event.pastedContent.trim() !== '') {\n this.pastedEvents.push(event.pastedContent);\n }\n }\n if (event.event === 'aiInsert' && event.aiContent) {\n this.aiEvents.push(event.aiContent);\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 newElement = $(html).text(str);\n return $('.tiny_cursive').html(newElement);\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\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 $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\n $('a[id^=\"rep\"]').addClass('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 let metaPressed = 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 === 'Meta') {\n metaPressed = true;\n } else if (event.key === 'Shift') {\n shiftPressed = true;\n } else if ((event.key === 'v' || event.key === 'V') && (controlPressed || metaPressed)) {\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 metaPressed = false;\n } else {\n controlPressed = false;\n shiftPressed = false;\n metaPressed = false;\n }\n }\n }\n\n if (this.pasteEventsPanel) {\n this.populatePasteEventsPanel(this.pasteEventsPanel);\n }\n }\n\n identifyUndoEvents() {\n this.undoTimestamps = [];\n let controlPressed = false;\n let metaPressed = false;\n let undoCount = 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 === 'Meta') {\n metaPressed = true;\n } else if ((event.key === 'z' || event.key === 'Z') && (controlPressed || metaPressed)) {\n const timestamp = event.normalizedTime || 0;\n this.undoTimestamps.push({\n index: undoCount,\n time: timestamp,\n formattedTime: this.formatTime(timestamp),\n timestamp\n });\n undoCount++;\n controlPressed = false;\n metaPressed = false;\n } else {\n controlPressed = false;\n metaPressed = false;\n }\n }\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.isMetaKeyPressed = false;\n this.currentPasteIndex = 0;\n this.pastedChars = [];\n this.currentAiIndex = 0;\n this.aiChars = [];\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 } else if (event.event === 'aiInsert') {\n ({text, cursor, updatedHighlights, updatedDeleted} =\n this.processAiInsertEvent(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 processAiInsertEvent(event, text, cursor, highlights, deletions) {\n if (this.aiEvents && this.currentAiIndex < this.aiEvents.length) {\n const aiContent = this.aiEvents[this.currentAiIndex];\n // Use event.rePosition which points to where the word to replace is\n const targetPosition = event.rePosition;\n\n ({text, cursor} = this.handleAiReplacement(aiContent, text, targetPosition, cursor, deletions));\n this.currentAiIndex++;\n }\n return {\n text,\n cursor,\n updatedHighlights: highlights,\n updatedDeleted: deletions\n };\n }\n\n handleAiReplacement(aiContent, text, targetPosition, currentCursor, deletions) {\n const insertText = aiContent || '';\n const aiWords = insertText.trim().split(/\\s+/);\n const isMultiWord = aiWords.length > 1;\n const isNewLineInsertion = insertText.startsWith('\\n') || insertText.endsWith('\\n');\n\n const {wordStart, wordEnd} = this.findWordToReplace(\n text,\n targetPosition,\n currentCursor,\n aiWords,\n isMultiWord,\n isNewLineInsertion\n );\n\n const wordToReplace = text.substring(wordStart, wordEnd);\n\n // Mark replaced characters as deleted\n this.markCharsAsDeleted(wordToReplace, wordStart, deletions);\n\n // Perform the replacement\n const replacedLength = wordToReplace.length;\n text = text.substring(0, wordStart) + insertText + text.substring(wordEnd);\n const positionDiff = insertText.length - replacedLength;\n\n // Calculate new cursor position\n const newCursor = this.calculateNewCursorPosition(\n currentCursor,\n targetPosition,\n wordStart,\n wordEnd,\n insertText,\n isNewLineInsertion\n );\n\n // Update character tracking arrays\n this.updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText);\n\n return {text, cursor: newCursor};\n }\n\n findWordToReplace(text, targetPosition, currentCursor, aiWords, isMultiWord, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n const {lineStart, lineEnd} = this.findLineRange(text, targetPosition);\n const lineText = text.substring(lineStart, lineEnd);\n const words = this.extractWordsFromLine(lineText, lineStart);\n\n if (words.length === 0) {\n return {wordStart: currentCursor, wordEnd: currentCursor};\n }\n\n if (isMultiWord) {\n return this.findMultiWordMatch(words, aiWords, targetPosition);\n } else {\n return this.findSingleWordMatch(words, aiWords[0], targetPosition);\n }\n }\n\n findLineRange(text, targetPosition) {\n let lineStart = 0;\n for (let i = targetPosition - 1; i >= 0; i--) {\n if (text[i] === '\\n') {\n lineStart = i + 1;\n break;\n }\n }\n\n let lineEnd = text.length;\n for (let i = targetPosition; i < text.length; i++) {\n if (text[i] === '\\n') {\n lineEnd = i;\n break;\n }\n }\n\n return {lineStart, lineEnd};\n }\n\n extractWordsFromLine(lineText, lineStart) {\n const words = [];\n let pos = 0;\n\n while (pos < lineText.length) {\n // Skip spaces\n while (pos < lineText.length && lineText[pos] === ' ') {\n pos++;\n }\n if (pos >= lineText.length) {\n break;\n }\n\n // Extract word\n const start = pos;\n while (pos < lineText.length && lineText[pos] !== ' ') {\n pos++;\n }\n\n if (pos > start) {\n words.push({\n text: lineText.substring(start, pos),\n start: lineStart + start,\n end: lineStart + pos\n });\n }\n }\n\n return words;\n }\n\n findMultiWordMatch(words, aiWords, targetPosition) {\n let bestMatch = {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n\n for (let i = 0; i < words.length; i++) {\n const matchResult = this.evaluateMultiWordSequence(words, aiWords, i, targetPosition);\n\n if (matchResult.totalScore > bestMatch.score ||\n (matchResult.totalScore === bestMatch.score &&\n matchResult.similarityScore > bestMatch.similarityScore)) {\n bestMatch = matchResult;\n }\n }\n\n if (bestMatch.score > 10) {\n return {wordStart: bestMatch.start, wordEnd: bestMatch.end};\n } else {\n const closest = this.findClosestWord(words, targetPosition);\n return {wordStart: closest.start, wordEnd: closest.end};\n }\n }\n\n evaluateMultiWordSequence(words, aiWords, startIndex, targetPosition) {\n const seqWords = [];\n for (let j = 0; j < aiWords.length && startIndex + j < words.length; j++) {\n seqWords.push(words[startIndex + j]);\n }\n\n if (seqWords.length === 0) {\n return {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\n }\n\n const similarityScore = this.calculateSequenceSimilarity(aiWords, seqWords);\n const positionScore = this.calculatePositionScore(seqWords, targetPosition);\n const totalScore = similarityScore + positionScore + seqWords.length;\n\n return {\n start: seqWords[0].start,\n end: seqWords[seqWords.length - 1].end,\n score: totalScore,\n wordCount: seqWords.length,\n similarityScore: similarityScore\n };\n }\n\n calculateSequenceSimilarity(aiWords, seqWords) {\n let similarityScore = 0;\n const compareLength = Math.min(seqWords.length, aiWords.length);\n\n for (let k = 0; k < compareLength; k++) {\n const ai = aiWords[k].toLowerCase();\n const seq = seqWords[k].text.toLowerCase();\n\n if (ai === seq) {\n similarityScore += 10;\n } else {\n const similarity = this.calculateSimilarity(ai, seq);\n similarityScore += similarity * 10;\n }\n }\n\n return similarityScore;\n }\n\n calculatePositionScore(seqWords, targetPosition) {\n let positionScore = 0;\n const seqStart = seqWords[0].start;\n const seqEndPos = seqWords[seqWords.length - 1].end;\n\n if (targetPosition >= seqStart && targetPosition <= seqEndPos) {\n positionScore += 10;\n if (targetPosition >= seqWords[0].start && targetPosition <= seqWords[0].end) {\n positionScore += 5;\n }\n }\n\n return positionScore;\n }\n\n findSingleWordMatch(words, aiWord, targetPosition) {\n const aiWordLower = aiWord.toLowerCase();\n const bestSimilarityMatch = this.findBestSimilarityMatch(words, aiWordLower);\n\n if (bestSimilarityMatch.score > 0.5) {\n return {wordStart: bestSimilarityMatch.word.start, wordEnd: bestSimilarityMatch.word.end};\n }\n\n const bestPositionMatch = this.findBestPositionMatch(words, aiWordLower, targetPosition);\n\n if (bestPositionMatch.word) {\n return {wordStart: bestPositionMatch.word.start, wordEnd: bestPositionMatch.word.end};\n }\n\n // Fallback to position-based word boundary\n return this.findWordBoundaryAtPosition(words[0].start, words[words.length - 1].end,\n targetPosition, this.text);\n }\n\n findBestSimilarityMatch(words, aiWordLower) {\n let bestMatch = {word: null, score: 0};\n\n for (const word of words) {\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n\n // Penalize short prefix matches\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n\n if (similarity > bestMatch.score) {\n bestMatch = {word, score: similarity};\n }\n }\n\n return bestMatch;\n }\n\n findBestPositionMatch(words, aiWordLower, targetPosition) {\n let bestMatch = {word: null, score: -1};\n\n for (const word of words) {\n let score = this.calculateWordScore(word, aiWordLower, targetPosition);\n\n if (score > bestMatch.score) {\n bestMatch = {word, score};\n }\n }\n\n return bestMatch;\n }\n\n calculateWordScore(word, aiWordLower, targetPosition) {\n let score = 0;\n\n // Position score\n if (targetPosition >= word.start && targetPosition <= word.end) {\n score += 30;\n } else {\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n score += Math.max(0, 20 - distance);\n }\n\n // Similarity score with penalty\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\n const wordLower = word.text.toLowerCase();\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\n similarity = similarity * 0.3;\n }\n score += similarity * 10;\n\n return score;\n }\n\n findWordBoundaryAtPosition(lineStart, lineEnd, targetPosition, text) {\n let wordStart = targetPosition;\n while (wordStart > lineStart && text[wordStart - 1] !== ' ' && text[wordStart - 1] !== '\\n') {\n wordStart--;\n }\n let wordEnd = targetPosition;\n while (wordEnd < lineEnd && text[wordEnd] !== ' ' && text[wordEnd] !== '\\n') {\n wordEnd++;\n }\n return {wordStart, wordEnd};\n }\n\n markCharsAsDeleted(wordToReplace, wordStart, deletions) {\n if (wordToReplace.length > 0) {\n for (let i = 0; i < wordToReplace.length; i++) {\n deletions.push({\n index: wordStart + i,\n chars: wordToReplace[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n\n calculateNewCursorPosition(currentCursor, targetPosition, wordStart, wordEnd, insertText, isNewLineInsertion) {\n if (isNewLineInsertion) {\n return wordStart + insertText.length;\n }\n\n if (targetPosition >= wordStart && targetPosition <= wordEnd) {\n return wordStart + insertText.length;\n }\n\n const positionDiff = insertText.length - (wordEnd - wordStart);\n\n if (currentCursor >= wordEnd) {\n return currentCursor + positionDiff;\n } else if (currentCursor > wordStart && currentCursor < wordEnd) {\n return wordStart + insertText.length;\n }\n\n return currentCursor;\n }\n\n updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText) {\n // Update pasted character indices\n this.updatePastedCharIndices(wordStart, wordEnd, positionDiff);\n\n // Mark characters as AI-inserted\n this.markCharsAsAiInserted(wordStart, insertText);\n\n // Update AI character indices\n this.updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText);\n }\n\n updatePastedCharIndices(wordStart, wordEnd, positionDiff) {\n if (this.pastedChars) {\n this.pastedChars = this.pastedChars.map(p => {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n return p;\n }).filter(p => p !== null);\n }\n }\n\n markCharsAsAiInserted(wordStart, insertText) {\n if (!this.aiChars) {\n this.aiChars = [];\n }\n\n if (insertText.trim() !== '') {\n for (let i = 0; i < insertText.length; i++) {\n this.aiChars.push({\n index: wordStart + i,\n chars: insertText[i]\n });\n }\n }\n }\n\n updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText) {\n const justAddedIndices = new Set();\n for (let i = 0; i < insertText.length; i++) {\n justAddedIndices.add(wordStart + i);\n }\n\n this.aiChars = this.aiChars.map(p => {\n if (!justAddedIndices.has(p.index)) {\n if (p.index >= wordEnd) {\n return {...p, index: p.index + positionDiff};\n } else if (p.index >= wordStart && p.index < wordEnd) {\n return null;\n }\n }\n return p;\n }).filter(p => p !== null);\n }\n\n // Calculate similarity between two strings\n calculateSimilarity(str1, str2) {\n if (str1 === str2) {\n return 1;\n }\n if (str1.length === 0 || str2.length === 0) {\n return 0;\n }\n\n // Check if one string is a prefix of the other\n if (str1.startsWith(str2) || str2.startsWith(str1)) {\n return 0.8;\n }\n\n // Levenshtein distance\n const len1 = str1.length;\n const len2 = str2.length;\n const matrix = Array(len2 + 1).fill(null).map(() => Array(len1 + 1).fill(0));\n\n for (let i = 0; i <= len1; i++) {\n matrix[0][i] = i;\n }\n for (let j = 0; j <= len2; j++) {\n matrix[j][0] = j;\n }\n\n for (let j = 1; j <= len2; j++) {\n for (let i = 1; i <= len1; i++) {\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\n matrix[j][i] = Math.min(\n matrix[j][i - 1] + 1,\n matrix[j - 1][i] + 1,\n matrix[j - 1][i - 1] + cost\n );\n }\n }\n\n const maxLen = Math.max(len1, len2);\n return 1 - (matrix[len2][len1] / maxLen);\n }\n\n // Find the word closest to a target position\n findClosestWord(words, targetPosition) {\n if (words.length === 0) {\n return {start: targetPosition, end: targetPosition};\n }\n\n let closest = words[0];\n let minDistance = Math.min(\n Math.abs(targetPosition - words[0].start),\n Math.abs(targetPosition - words[0].end)\n );\n\n for (const word of words) {\n if (targetPosition >= word.start && targetPosition <= word.end) {\n return word;\n }\n\n const distance = Math.min(\n Math.abs(targetPosition - word.start),\n Math.abs(targetPosition - word.end)\n );\n\n if (distance < minDistance) {\n minDistance = distance;\n closest = word;\n }\n }\n\n return closest;\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\n // Handle copy operation (Ctrl+C)\n if (this.isCopyOperation(key)) {\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Handle undo operation (Ctrl+Z)\n if (this.isUndoOperation(key)) {\n return this.handleUndoOperation(event, text, cursor, highlights, deletions);\n }\n\n // Detect selection for current event\n const currentEventIndex = this.currentEventIndex;\n const selection = this.detectSelection(currentEventIndex);\n\n // Handle paste operation (Ctrl+V)\n if (this.isPasteOperation(key, event)) {\n return this.handlePasteOperation(event, selection, text, cursor, highlights, deletions);\n }\n\n // Update modifier key states\n this.updateModifierStates(key);\n\n // Handle selection deletion with Backspace/Delete\n if (this.isSelectionDeletion(key, selection)) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n // Process various key operations\n return this.processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection);\n }\n\n isCopyOperation(key) {\n return (key === 'c' || key === 'C') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n isUndoOperation(key) {\n return (key === 'z' || key === 'Z') && (this.isControlKeyPressed || this.isMetaKeyPressed);\n }\n\n handleUndoOperation(event, text, cursor, highlights, deletions) {\n const nextEventIndex = this.currentEventIndex + 1;\n if (nextEventIndex < this.logData.length) {\n const nextEvent = this.logData[nextEventIndex];\n\n if (nextEvent.event === 'keyUp' && (nextEvent.key === 'z' || nextEvent.key === 'Z')) {\n const newPosition = nextEvent.rePosition;\n if (newPosition < cursor && text.length > 0) {\n const textBeforeUndo = text;\n text = text.substring(0, newPosition) + text.substring(cursor);\n cursor = newPosition;\n\n // Mark as deleted for visual effect\n for (let i = 0; i < textBeforeUndo.length && i < cursor; i++) {\n deletions.push({\n index: newPosition,\n chars: textBeforeUndo[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n }\n }\n }\n\n this.isControlKeyPressed = false;\n this.isMetaKeyPressed = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n isPasteOperation(key, event) {\n if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n return (event.pastedContent && event.pastedContent.trim() !== '') ||\n (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length);\n }\n return false;\n }\n\n handlePasteOperation(event, selection, text, cursor, highlights, deletions) {\n const pastedContent = event.pastedContent || this.pastedEvents[this.currentPasteIndex];\n\n if (selection) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\n this.currentPasteIndex++;\n this.resetModifierStates();\n this.isPasteEvent = false;\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n resetModifierStates() {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = false;\n }\n\n isSelectionDeletion(key, selection) {\n return (key === 'Backspace' || key === 'Delete') && selection && selection.length > 1;\n }\n\n processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection) {\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 if (selection && selection.length > 0) {\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\n }\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\n }\n\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\n }\n\n detectSelection(eventIndex) {\n const currentEvent = this.logData[eventIndex];\n\n if (currentEvent.event?.toLowerCase() === 'keydown' &&\n (currentEvent.key === 'Backspace' || currentEvent.key === 'Delete')) {\n\n const currentPos = currentEvent.rePosition;\n return this.processDetection(currentPos, currentEvent, eventIndex);\n }\n return null;\n }\n\n processDetection(currentPos, currentEvent, eventIndex) {\n for (let i = eventIndex + 1; i < this.logData.length; i++) {\n const nextEvent = this.logData[i];\n\n if (nextEvent.event?.toLowerCase() === 'keyup' &&\n nextEvent.key === currentEvent.key) {\n\n const nextPos = nextEvent.rePosition;\n\n // Calculate the difference in positions\n const positionDiff = Math.abs(currentPos - nextPos);\n\n if (positionDiff > 1) {\n return {\n start: Math.min(currentPos, nextPos),\n end: Math.max(currentPos, nextPos),\n length: positionDiff\n };\n } else if (positionDiff === 1) {\n if (currentEvent.key === 'Backspace') {\n return {\n start: nextPos,\n end: currentPos,\n length: 1\n };\n } else {\n return {\n start: currentPos,\n end: nextPos,\n length: 1\n };\n }\n }\n break;\n }\n }\n return null;\n }\n\n handleSelectionDeletion(selection, text, cursor, deletions) {\n const {start, end, length} = selection;\n\n // Add each character in the selection to the deletions array\n for (let i = start; i < end && i < text.length; i++) {\n deletions.push({\n index: start,\n chars: text[i],\n time: this.currentTime,\n expiresAt: this.currentTime + 2000\n });\n }\n\n text = text.substring(0, start) + text.substring(end);\n\n this.shiftPastedCharsIndices(start, length);\n\n cursor = start;\n\n return {text, cursor};\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 if (this.aiChars) {\n this.aiChars = this.aiChars.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 return null;\n }\n return p;\n }).filter(p => p !== null);\n }\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 === 'Meta') {\n this.isMetaKeyPressed = true;\n } else if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\n this.isPasteEvent = true;\n } else if (!['Control', 'Meta', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\n this.isControlKeyPressed = false;\n this.isShiftKeyPressed = false;\n this.isMetaKeyPressed = 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 (this.aiChars) {\n this.aiChars = this.aiChars.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.isMetaKeyPressed = false;\n this.isPasteEvent = false;\n this.pastedChars = [];\n this.currentPasteIndex = 0;\n this.currentAiIndex = 0;\n this.aiChars = [];\n let text = '';\n let cursor = 0;\n let highlights = [];\n let deletions = [];\n let pasteIndex = 0;\n let aiIndex = 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 && (this.currentEventIndex === 0 ||\n 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 || this.isMetaKeyPressed)) {\n pasteIndex++;\n }\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\n } else if (event.event === 'aiInsert') {\n this.currentAiIndex = aiIndex;\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\n this.processAiInsertEvent(event, text, cursor, highlights, deletions));\n aiIndex++;\n }\n this.currentEventIndex = i + 1;\n }\n\n this.currentPasteIndex = pasteIndex;\n this.currentAiIndex = aiIndex;\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 // eslint-disable-next-line complexity\n updateDisplayText(text, cursorPosition, highlights, deletions) {\n let html = '';\n const highlightMap = {};\n const deletionMap = {};\n const pastedMap = {};\n const aiMap = {};\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 // Process AI characters for styling\n if (this.aiChars) {\n this.aiChars.forEach(p => {\n if (p.index < text.length) {\n aiMap[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 isAi = aiMap[currentPosition];\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\n\n if (isPasted && isHighlighted) {\n html += `${char}`;\n } else if (isAi && isHighlighted) {\n html += `${char}`;\n } else if (isPasted) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isAi) {\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\n } else if (isHighlighted) {\n html += `${char}`;\n } else {\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","isMetaKeyPressed","text","pastedEvents","currentPasteIndex","pastedChars","aiEvents","currentAiIndex","aiChars","undoTimestamps","undoChars","element","document","getElementById","Error","outputElement","loadJSON","then","data","status","processData","this","logData","length","identifyPasteEvents","identifyUndoEvents","constructController","startReplay","handleNoSubmission","catch","error","window","console","message","localStorage","getItem","Str","get_string","str","setItem","log","JSON","parse","comments","Array","isArray","payload","i","event","pastedContent","trim","push","aiContent","unixTimestamp","startTime","map","normalizedTime","html","Promise","all","templates","render","newElement","stopReplay","clearTimeout","replayTimeout","playButton","playSvg","createElement","src","M","util","image_url","querySelector","innerHTML","outerHTML","currentPosition","replayIntervalId","clearInterval","container","controlContainer","buildControllerUI","remove","topRow","classList","add","createPlayButton","appendChild","scrubberContainer","createScrubberContainer","timeDisplay","createTimeDisplay","bottomRow","speedContainer","createSpeedControls","pasteEventsToggle","createPasteEventsToggle","pasteEventsPanel","addEventListener","find","removeClass","addClass","scrubberElement","type","max","min","value","skipToTime","parseInt","textContent","speedLabel","speedGroup","forEach","speedBtn","dataset","querySelectorAll","btn","pasteEventsIcon","pasteIcon","pasteEventsText","pasteEventCount","className","style","marginLeft","chevronIcon","chevron","transition","createPasteEventsPanel","isHidden","display","transform","existingPanel","populatePasteEventsPanel","controlPressed","metaPressed","shiftPressed","pasteCount","toLowerCase","key","timestamp","index","time","formattedTime","formatTime","pastedText","undoCount","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","processAiInsertEvent","filter","h","expiresAt","d","updateDisplayText","percentComplete","baseIncrement","incrementTime","setTimeout","getLineAndColumn","pos","before","substring","lineIndex","split","col","lastIndexOf","highlights","deletions","targetPosition","handleAiReplacement","currentCursor","insertText","aiWords","isMultiWord","isNewLineInsertion","startsWith","endsWith","wordStart","wordEnd","findWordToReplace","wordToReplace","markCharsAsDeleted","replacedLength","positionDiff","newCursor","calculateNewCursorPosition","updateCharacterIndices","lineStart","lineEnd","findLineRange","lineText","words","extractWordsFromLine","findMultiWordMatch","findSingleWordMatch","start","end","bestMatch","score","wordCount","similarityScore","matchResult","evaluateMultiWordSequence","totalScore","closest","findClosestWord","startIndex","seqWords","j","calculateSequenceSimilarity","calculatePositionScore","compareLength","k","ai","seq","calculateSimilarity","positionScore","seqStart","seqEndPos","aiWord","aiWordLower","bestSimilarityMatch","findBestSimilarityMatch","word","bestPositionMatch","findBestPositionMatch","findWordBoundaryAtPosition","similarity","wordLower","calculateWordScore","distance","abs","chars","updatePastedCharIndices","markCharsAsAiInserted","updateAiCharIndices","p","justAddedIndices","Set","has","str1","str2","len1","len2","matrix","fill","cost","maxLen","minDistance","charToInsert","applyKey","isCopyOperation","isUndoOperation","handleUndoOperation","selection","detectSelection","isPasteOperation","handlePasteOperation","updateModifierStates","isSelectionDeletion","handleSelectionDeletion","processKeyOperation","nextEventIndex","nextEvent","newPosition","textBeforeUndo","handlePasteInsert","resetModifierStates","isCtrlBackspace","handleCtrlBackspace","isCtrlDelete","handleCtrlDelete","isCtrlArrowMove","handleCtrlArrowMove","isRegularBackspace","handleBackspace","isRegularDelete","handleDelete","isArrowUp","handleArrowUp","isArrowDown","handleArrowDown","isRegularArrowMove","handleArrowMove","handleCharacterInsert","eventIndex","currentEvent","currentPos","processDetection","nextPos","shiftPastedCharsIndices","numDeleted","includes","findPreviousWordBoundary","findNextWordBoundary","wordToDelete","lines","prevLine","slice","join","nextLine","lastNonSpace","skipToEnd","textOutput","wasPlaying","targetTime","pasteIndex","aiIndex","highlightMap","deletionMap","pastedMap","aiMap","outOfRangeDeletions","textLines","line","char","isPasted","isAi","isHighlighted","escapeHtml","sort","a","b","cursorHTML","cursorPos","deletedWordHTML","wasScrolledToBottom","scrollHeight","clientHeight","scrollTop","isCursorBelowViewport","cursorElement","cursorRect","getBoundingClientRect","outputRect","bottom","unsafe","replace"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/scatter_chart.min.js b/amd/build/scatter_chart.min.js index 4fc870b1..950f6fde 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"".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]})}})); + */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,time,words,effortratio,wpm,effortscore,timespent]=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:"time",component:"tiny_cursive"},{key:"words",component:"tiny_cursive"},{key:"effort_ratio",component:"tiny_cursive"},{key:"wpm",component:"tiny_cursive"},{key:"effort_score",component:"tiny_cursive"},{key:"timespent",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)}`,`${effortratio}: ${Math.round(100*d.effort*100)/100}%`,`${words}: ${d.words}`,`${wpm}: ${d.wpm}`]}}}},scales:{x:{title:{display:!0,text:timespent},min:0,ticks:{callback:function(value){return formatTime(value)}}},y:{title:{display:!0,text:effortscore},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 b072c128..28cfaef5 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,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,SAASmB,gBACFA,QAAQ,GAAGC,IACZC,OAEbA,MAAO,SAASF,eACNG,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,SAAS1D,cACRD,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 time,\n words,\n effortratio,\n wpm,\n effortscore,\n timespent,\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: 'time', component: 'tiny_cursive'},\n {key: 'words', component: 'tiny_cursive'},\n {key: 'effort_ratio', component: 'tiny_cursive'},\n {key: 'wpm', component: 'tiny_cursive'},\n {key: 'effort_score', component: 'tiny_cursive'},\n {key: 'timespent', 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 `${effortratio}: ${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: timespent\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: effortscore\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","time","words","effortratio","wpm","effortscore","timespent","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","scales","min","ticks","callback","y","stepSize"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/svg_repo.min.js b/amd/build/svg_repo.min.js index a483299a..e4901924 100644 --- a/amd/build/svg_repo.min.js +++ b/amd/build/svg_repo.min.js @@ -1,7 +1,11 @@ -<<<<<<< HEAD -define("tiny_cursive/svg_repo",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={people:'\n \n \n \n \n ',assignment:'\n \n \n \n ',time:'\n \n \n ',offline:'',forum:'',close:'\n \n ',hamburger:'\n \n ',quiz:'',cloudSave:'',lesson:'',pdfannotator:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n '},_exports.default})); -======= -define("tiny_cursive/svg_repo",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={people:'\n \n \n \n \n ',assignment:'\n \n \n \n ',time:'\n \n \n ',offline:'',forum:'',close:'\n \n ',hamburger:'\n \n ',quiz:'',cloudSave:'',lesson:''},_exports.default})); ->>>>>>> 8b275778ba140d4ec01f088ecfc426293a0a3296 +define("tiny_cursive/svg_repo",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0; +/** + * SVG repository for icons used in the Curs + * + * @module tiny_cursive/svg_repo + * @copyright 2025 Cursive Technology, Inc. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +var _default={people:'\n \n \n \n \n ',assignment:'\n \n \n \n ',time:'\n \n \n ',offline:'',forum:'',close:'\n \n ',hamburger:'\n \n ',quiz:'',cloudSave:'',lesson:'',pdfannotator:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n '};return _exports.default=_default,_exports.default})); //# sourceMappingURL=svg_repo.min.js.map \ No newline at end of file diff --git a/amd/build/svg_repo.min.js.map b/amd/build/svg_repo.min.js.map index 8750f4d6..505312fd 100644 --- a/amd/build/svg_repo.min.js.map +++ b/amd/build/svg_repo.min.js.map @@ -1,5 +1 @@ -<<<<<<< HEAD -{"version":3,"file":"svg_repo.min.js","sources":["../src/svg_repo.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 * SVG repository for icons used in the Curs\n *\n * @module tiny_cursive/svg_repo\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n people: `\n \n \n \n \n `,\n assignment: `\n \n \n \n `,\n time: `\n \n \n `,\n offline: ``,\n forum: ``,\n close: `\n \n `,\n hamburger: `\n \n `,\n quiz: ``,\n cloudSave: ``,\n lesson: ``,\n pdfannotator: `\n \n \n \n \n \n \n \n \n \n \n \n \n \n `\n};\n"],"names":["people","assignment","time","offline","forum","close","hamburger","quiz","cloudSave","lesson","pdfannotator"],"mappings":"uKAuBe,CACXA,wjBASAC,ijBAQAC,0aAOAC,6gBAMAC,ytDAeAC,o6BAQAC,ukBAKAC,usFAuBAC,+zBAOAC,mgFAqBAC"} -======= -{"version":3,"file":"svg_repo.min.js","sources":["../src/svg_repo.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 * SVG repository for icons used in the Curs\n *\n * @module tiny_cursive/svg_repo\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n people: `\n \n \n \n \n `,\n assignment: `\n \n \n \n `,\n time: `\n \n \n `,\n offline: ``,\n forum: ``,\n close: `\n \n `,\n hamburger: `\n \n `,\n quiz: ``,\n cloudSave: ``,\n lesson: ``\n};\n"],"names":["people","assignment","time","offline","forum","close","hamburger","quiz","cloudSave","lesson"],"mappings":"uKAuBe,CACbA,wjBASAC,ijBAQAC,0aAOAC,6gBAMAC,ytDAeEC,o6BAQAC,ukBAKAC,usFAuBAC,+zBAOAC"} ->>>>>>> 8b275778ba140d4ec01f088ecfc426293a0a3296 +{"version":3,"file":"svg_repo.min.js","sources":["../src/svg_repo.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 * SVG repository for icons used in the Curs\n *\n * @module tiny_cursive/svg_repo\n * @copyright 2025 Cursive Technology, Inc. \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n people: `\n \n \n \n \n `,\n assignment: `\n \n \n \n `,\n time: `\n \n \n `,\n offline: ``,\n forum: ``,\n close: `\n \n `,\n hamburger: `\n \n `,\n quiz: ``,\n cloudSave: ``,\n lesson: ``,\n pdfannotator: `\n \n \n \n \n \n \n \n \n \n \n \n \n \n `\n};\n"],"names":["people","assignment","time","offline","forum","close","hamburger","quiz","cloudSave","lesson","pdfannotator"],"mappings":";;;;;;;;aAuBe,CACXA,OAAS,ijBASTC,WAAa,siBAQbC,KAAO,qaAOPC,QAAU,qgBAMVC,MAAQ,mtDAeRC,MAAQ,85BAQRC,UAAY,6jBAKZC,KAAO,ksFAuBPC,UAAY,qzBAOZC,OAAS,4/EAqBTC,aAAe"} \ No newline at end of file diff --git a/amd/build/texteditor.min.js b/amd/build/texteditor.min.js index 3e4318a3..d78e256c 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,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)})); +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)})); //# 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 cd6b3f19..053cad8b 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","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 +{"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 diff --git a/amd/build/token_approve.min.js b/amd/build/token_approve.min.js index 20e600ba..96a5ed03 100644 --- a/amd/build/token_approve.min.js +++ b/amd/build/token_approve.min.js @@ -1,3 +1,3 @@ -define("tiny_cursive/token_approve",["jquery","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(){$("#approve_token").click((function(){var token=$("#id_s_tiny_cursive_secretkey").val();AJAX.call([{methodname:"cursive_approve_token",args:{token:token}}])[0].done((function(json){var data=JSON.parse(json),messageAlert="";messageAlert=1==data.status?""+data.message+"":""+data.message+"",$("#token_message").html(messageAlert)}))}))},generateToken(){const generateToken=$("#generate_cursivetoken"),cursiveDisable=$("#cursivedisable"),cursiveEnable=$("#cursiveenable");generateToken.on("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;return data.token?($("#id_s_tiny_cursive_cursivetoken").val(data.token),messageAlert="".concat(success,"")):messageAlert="".concat(fail,""),$("#cursivetoken_").html(messageAlert),setTimeout((()=>{$("#cursivetoken_").empty()}),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+"",$("#cursivetoken_").html(errorMessage),setTimeout((function(){$("#cursivetoken_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveDisable.on("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;return messageAlert=data?"".concat(success,""):"".concat(fail,""),$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveEnable.on("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;return messageAlert=data?"".concat(success,""):"".concat(fail,""),$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))}))}};return usersTable})); +define("tiny_cursive/token_approve",["jquery","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(){$("#approve_token").click((function(){var token=$("#id_s_tiny_cursive_secretkey").val();AJAX.call([{methodname:"cursive_approve_token",args:{token:token}}])[0].done((function(json){var data=JSON.parse(json),messageAlert="";messageAlert=1==data.status?""+data.message+"":""+data.message+"",$("#token_message").html(messageAlert)}))}))},generateToken(){const generateToken=$("#generate_cursivetoken"),cursiveDisable=$("#cursivedisable"),cursiveEnable=$("#cursiveenable");generateToken.on("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;return data.token?($("#id_s_tiny_cursive_cursivetoken").val(data.token),messageAlert=`${success}`):messageAlert=`${fail}`,$("#cursivetoken_").html(messageAlert),setTimeout((()=>{$("#cursivetoken_").empty()}),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+"",$("#cursivetoken_").html(errorMessage),setTimeout((function(){$("#cursivetoken_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveDisable.on("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;return messageAlert=data?`${success}`:`${fail}`,$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),3e3),!0))).catch((error=>window.console.error(error)))}))})),cursiveEnable.on("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;return messageAlert=data?`${success}`:`${fail}`,$("#cursivedisable_").html(messageAlert),setTimeout((()=>{$("#cursivedisable_").empty()}),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+"",$("#cursivedisable_").html(errorMessage),setTimeout((function(){$("#cursivedisable_").empty()}),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 2236c250..44d98c9a 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 kuldeep singh \n */\n\ndefine([\"jquery\", \"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 $(\"#approve_token\").click(function() {\n var token = $(\"#id_s_tiny_cursive_secretkey\").val();\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 == true) {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n } else {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n }\n $(\"#token_message\").html(messageAlert);\n });\n });\n },\n\n generateToken() {\n const generateToken = $(\"#generate_cursivetoken\");\n const cursiveDisable = $(\"#cursivedisable\");\n const cursiveEnable = $(\"#cursiveenable\");\n\n generateToken.on(\"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 $(\"#id_s_tiny_cursive_cursivetoken\").val(data.token);\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n $(\"#cursivetoken_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivetoken_\").empty();\n }, 3000);\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 $(\"#cursivetoken_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivetoken_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n\n cursiveDisable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n cursiveEnable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","usersTable","init","page","get_strings","key","component","done","getToken","generateToken","click","token","val","call","methodname","args","json","data","JSON","parse","messageAlert","status","message","html","cursiveDisable","cursiveEnable","on","e","preventDefault","promise1","then","success","fail","setTimeout","empty","catch","error","window","console","textStatus","errorMessage","get_string","disable"],"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 kuldeep singh \n */\n\ndefine([\"jquery\", \"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 $(\"#approve_token\").click(function() {\n var token = $(\"#id_s_tiny_cursive_secretkey\").val();\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 == true) {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n } else {\n messageAlert =\n \"\" +\n data.message +\n \"\";\n }\n $(\"#token_message\").html(messageAlert);\n });\n });\n },\n\n generateToken() {\n const generateToken = $(\"#generate_cursivetoken\");\n const cursiveDisable = $(\"#cursivedisable\");\n const cursiveEnable = $(\"#cursiveenable\");\n\n generateToken.on(\"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 $(\"#id_s_tiny_cursive_cursivetoken\").val(data.token);\n messageAlert = `${success}`;\n } else {\n messageAlert = `${fail}`;\n }\n $(\"#cursivetoken_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivetoken_\").empty();\n }, 3000);\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 $(\"#cursivetoken_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivetoken_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n\n cursiveDisable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n cursiveEnable.on(\"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 $(\"#cursivedisable_\").html(messageAlert);\n setTimeout(() => {\n $(\"#cursivedisable_\").empty();\n }, 3000);\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 $(\"#cursivedisable_\").html(errorMessage);\n // Clear the error message after 3 seconds.\n setTimeout(function() {\n $(\"#cursivedisable_\").empty();\n }, 3000);\n return true;\n }).catch(error => window.console.error(error));\n });\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","usersTable","init","page","get_strings","key","component","done","getToken","generateToken","click","token","val","call","methodname","args","json","data","JSON","parse","messageAlert","status","message","html","cursiveDisable","cursiveEnable","on","e","preventDefault","promise1","then","success","fail","setTimeout","empty","catch","error","window","console","textStatus","errorMessage","get_string","disable"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/append_submissions_table.js b/amd/src/append_submissions_table.js index 08d00917..7cd36932 100644 --- a/amd/src/append_submissions_table.js +++ b/amd/src/append_submissions_table.js @@ -75,14 +75,14 @@ define([ appendTable: function(scoreSetting, hasApiKey) { let subUrl = window.location.href; let parm = new URL(subUrl); - let hTr = $('thead').find('tr').get()[0]; + let hTr = $('table#submissions thead').find('tr').get()[0]; Str.get_string('analytics', 'tiny_cursive') .then(analyticString => { $(hTr).find('th').eq(3).after('' + analyticString + '
' + '
'); - $('tbody').find("tr").get().forEach(function(tr) { + $('table#submissions tbody').find("tr").get().forEach(function(tr) { let tdUser = $(tr).find("td").get()[0]; let userid = $(tdUser).find("input[type='checkbox']")?.get()[0]?.value; let cmid = parm.searchParams.get('id'); diff --git a/amd/src/scatter_chart.js b/amd/src/scatter_chart.js index 47e933d0..60f40371 100644 --- a/amd/src/scatter_chart.js +++ b/amd/src/scatter_chart.js @@ -40,12 +40,23 @@ export const init = async(data, apiKey, caption) => { noSubmission, noPayload, freemium, + time, + words, + effortratio, + wpm, + effortscore, + timespent, ] = await getStrings([ {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'} + {key: 'time', component: 'tiny_cursive'}, + {key: 'words', component: 'tiny_cursive'}, + {key: 'effort_ratio', component: 'tiny_cursive'}, + {key: 'wpm', component: 'tiny_cursive'}, + {key: 'effort_score', component: 'tiny_cursive'}, + {key: 'timespent', component: 'tiny_cursive'}, ]); if (Array.isArray(data) && !data.state && apiKey) { @@ -132,10 +143,10 @@ export const init = async(data, apiKey, caption) => { label: function(context) { const d = context.raw; return [ - `Time: ${formatTime(d.x)}`, - `Effort: ${Math.round(d.effort * 100 * 100) / 100}%`, - `Words: ${d.words}`, - `WPM: ${d.wpm}` + `${time}: ${formatTime(d.x)}`, + `${effortratio}: ${Math.round(d.effort * 100 * 100) / 100}%`, + `${words}: ${d.words}`, + `${wpm}: ${d.wpm}` ]; } } @@ -145,7 +156,7 @@ export const init = async(data, apiKey, caption) => { x: { title: { display: true, - text: 'Time Spent (mm:ss)' + text: timespent }, min: 0, ticks: { @@ -157,7 +168,7 @@ export const init = async(data, apiKey, caption) => { y: { title: { display: true, - text: 'Effort Score' + text: effortscore }, min: 0, ticks: { diff --git a/classes/constants.php b/classes/constants.php index c03270e6..6d822e4b 100644 --- a/classes/constants.php +++ b/classes/constants.php @@ -41,7 +41,7 @@ class constants { * const array RUBRIC_AREA Mapping of module names to rubric areas */ public const RUBRIC_AREA = ['assign' => 'submissions', 'forum' => 'forum', 'quiz' => 'quiz', 'lesson' => - 'lesson']; + 'lesson']; /** * Array mapping page body IDs to their corresponding handler functions and module types. @@ -144,6 +144,7 @@ public static function has_api_key() { $nextsync = strtotime('+5 minutes'); if (empty($secret)) { + return true; if ($apikey !== false || $apikey !== "0") { set_config('apiKey', false, 'tiny_cursive'); } diff --git a/classes/page/pdfexport.php b/classes/local/page/pdfexport.php similarity index 99% rename from classes/page/pdfexport.php rename to classes/local/page/pdfexport.php index 073ce316..dca72fc9 100644 --- a/classes/page/pdfexport.php +++ b/classes/local/page/pdfexport.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace tiny_cursive\page; +namespace tiny_cursive\local\page; use context_course; use context_module; use html_writer; diff --git a/classes/page/visualization.php b/classes/local/page/visualization.php similarity index 99% rename from classes/page/visualization.php rename to classes/local/page/visualization.php index 3aed20fd..0efb63a2 100644 --- a/classes/page/visualization.php +++ b/classes/local/page/visualization.php @@ -21,7 +21,7 @@ * @copyright 2025 Cursive Technology, Inc. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace tiny_cursive\page; +namespace tiny_cursive\local\page; use html_writer; use moodle_url; diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 46e2f63b..827974d7 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -66,7 +66,7 @@ public static function get_metadata(collection $collection): collection { 'timemodified' => 'privacy:metadata:database:tiny_cursive_comments:timemodified', ], 'privacy:metadata:database:tiny_cursive_comments'); - $collection->add_external_location_link('tiny_cursive_files', [ + $collection->add_external_location_link('api.cursivetechnology.net', [ 'userid' => 'privacy:metadata:database:tiny_cursive:userid', 'content' => 'privacy:metadata:database:tiny_cursive:content', 'original_content' => 'privacy:metadata:database:tiny_cursive:original_content', diff --git a/db/install.php b/db/install.php index 4036b797..973854f1 100644 --- a/db/install.php +++ b/db/install.php @@ -30,15 +30,15 @@ */ function xmldb_tiny_cursive_install() { - enable_webservice(); - enable_webservice_protocol(); + tiny_cursive_enable_webservice(); + tiny_cursive_enable_webservice_protocol(); } /** * Enable web services in Moodle * * @package tiny_cursive */ -function enable_webservice() { +function tiny_cursive_enable_webservice() { set_config('enablewebservices', 1); } @@ -47,6 +47,6 @@ function enable_webservice() { * * @package tiny_cursive */ -function enable_webservice_protocol() { +function tiny_cursive_enable_webservice_protocol() { set_config('webserviceprotocols', 'rest'); } diff --git a/lang/en/tiny_cursive.php b/lang/en/tiny_cursive.php index ac751902..3d37b540 100644 --- a/lang/en/tiny_cursive.php +++ b/lang/en/tiny_cursive.php @@ -98,6 +98,7 @@ $string['editspastesai'] = 'Edits/Pastes/AI'; $string['effort_ratio'] = 'Effort'; $string['effort_ratio_desc'] = 'Total characters from verified effort / total characters in submission'; +$string['effort_score'] = "Effort Score"; $string['email'] = 'Email'; $string['enable'] = 'Enable'; $string['enabled'] = 'Enabled'; @@ -237,10 +238,12 @@ $string['syncinterval_des'] = 'Specify how frequently (in seconds) the user\'s writing keystrokes should be synchronized with the server. A lower value provides more real-time tracking but may increase server load. Recommended range is 10-30 seconds.'; $string['test_token'] = 'Test token'; $string['thresold_description'] = 'Each site may set its threshold for providing the successful match “green check” to the TypeID column for student submissions. We recommend .65. However, there may be arguments for lower or higher thresholds depending on your experience or academic honesty policy.'; -$string['time_writing'] = 'Time writing '; +$string['time'] = "Time"; +$string['time_writing'] = 'Time writing'; $string['time_writing_desc'] = 'Total duration less inactive periods'; $string['timeleft'] = 'Time Left'; $string['timesave_success'] = 'Time saved successfully'; +$string['timespent'] = "Time Spent (mm ss)"; $string['tiny_cursive'] = 'Authorship and Analytics'; $string['tiny_cursive_placeholder'] = 'Write your comment or paste your link here…'; $string['tiny_cursive_srcurl'] = 'Please provide a comment'; @@ -270,7 +273,9 @@ $string['word_count_des'] = 'How many words you typed is estimated based on your usage of the space bar.'; $string['word_len_mean'] = 'Average word length'; $string['word_len_mean_des'] = 'Average word length is calculated by dividing the estimated word count by the total number of characters. Word length varies based on your vocabulary, the audience that you\'re writing for, and the subject. Longer word lengths have an impact on readability and grade-level readability estimates.'; +$string['words'] = "Words"; $string['words_per_minute'] = 'Writing speed'; $string['words_per_minute_desc'] = 'Words per minute'; +$string['wpm'] = "WPM"; $string['wractivityreport'] = 'Writing activity report'; $string['writing_analytics'] = "Writing Analytics"; diff --git a/lib.php b/lib.php index e96bba8b..a85686fc 100644 --- a/lib.php +++ b/lib.php @@ -302,7 +302,7 @@ function tiny_cursive_upload_multipart_record($filerecord, $filenamewithfullpath $remoteurl = get_config('tiny_cursive', 'python_server') . "/upload_file"; $filetosend = ''; - $tempfilepath = tempnam(sys_get_temp_dir(), 'upload'); + $tempfilepath = make_temp_directory('tiny_cursive') . '/' . uniqid('upload_', true); $jsoncontent = json_decode($filerecord->content, true); diff --git a/my_writing_report.php b/my_writing_report.php index c70bba3c..5526d31c 100644 --- a/my_writing_report.php +++ b/my_writing_report.php @@ -89,7 +89,7 @@ $PAGE->requires->js_call_amd('tiny_cursive/key_logger', 'init', [1]); $PAGE->requires->js_call_amd('tiny_cursive/cursive_writing_reports', 'init', ["", constants::has_api_key(), - get_config('tiny_cursive', 'json_download')]); + get_config('tiny_cursive', 'json_download')]); $PAGE->set_context(context_system::instance()); $PAGE->set_url($url); diff --git a/pdfexport.php b/pdfexport.php index 55bfe13d..6c6780a4 100644 --- a/pdfexport.php +++ b/pdfexport.php @@ -32,5 +32,5 @@ $cmid = required_param('cmid', PARAM_INT); $course = required_param('course', PARAM_INT); $qid = optional_param('qid', 0, PARAM_INT); -$page = new \tiny_cursive\page\pdfexport($course, $cmid, $id, $qid, $file); +$page = new \tiny_cursive\local\page\pdfexport($course, $cmid, $id, $qid, $file); $page->download(); diff --git a/thirdpartylibs.xml b/thirdpartylibs.xml index faa20d1b..3cc20824 100644 --- a/thirdpartylibs.xml +++ b/thirdpartylibs.xml @@ -1,7 +1,7 @@ - amd/src/html2pdf.js + amd/js/html2pdf.js html2pdf 0.10.1 MIT diff --git a/tiny_cursive_report.php b/tiny_cursive_report.php index 6df7a32d..b14f40b8 100644 --- a/tiny_cursive_report.php +++ b/tiny_cursive_report.php @@ -125,7 +125,7 @@ $moduleid, $userid ); - $chart = new \tiny_cursive\page\visualization($courseid, "", $moduleid, $formdata->userid); + $chart = new \tiny_cursive\local\page\visualization($courseid, "", $moduleid, $formdata->userid); $chart->render(); } else { $users = tiny_cursive_get_user_attempts_data( @@ -146,7 +146,7 @@ $moduleid, $userid ); - $chart = new \tiny_cursive\page\visualization($courseid, "", $moduleid, $userid); + $chart = new \tiny_cursive\local\page\visualization($courseid, "", $moduleid, $userid); $chart->render(); } diff --git a/version.php b/version.php index f727d3c7..70f17b10 100644 --- a/version.php +++ b/version.php @@ -29,6 +29,6 @@ $plugin->component = 'tiny_cursive'; $plugin->release = '2.1.3'; -$plugin->version = 2026013002; +$plugin->version = 2026013005; $plugin->requires = 2022041912; $plugin->maturity = MATURITY_STABLE; From eb605b815d295cdaa1ed34086f580fced5993cc5 Mon Sep 17 00:00:00 2001 From: Tareq-Adnan Date: Tue, 10 Feb 2026 16:24:30 +0600 Subject: [PATCH 4/5] clean --- classes/constants.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/constants.php b/classes/constants.php index 6d822e4b..073166fd 100644 --- a/classes/constants.php +++ b/classes/constants.php @@ -144,7 +144,6 @@ public static function has_api_key() { $nextsync = strtotime('+5 minutes'); if (empty($secret)) { - return true; if ($apikey !== false || $apikey !== "0") { set_config('apiKey', false, 'tiny_cursive'); } From e7cbba2198e91cab71b5080bf4735a2d194dadef Mon Sep 17 00:00:00 2001 From: Tareq-Adnan Date: Wed, 11 Feb 2026 18:02:49 +0600 Subject: [PATCH 5/5] Feedback and performance improvement --- version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.php b/version.php index 70f17b10..ace6bacb 100644 --- a/version.php +++ b/version.php @@ -29,6 +29,6 @@ $plugin->component = 'tiny_cursive'; $plugin->release = '2.1.3'; -$plugin->version = 2026013005; +$plugin->version = 2026013100; $plugin->requires = 2022041912; $plugin->maturity = MATURITY_STABLE;