diff --git a/amd/build/analytic_button.min.js.map b/amd/build/analytic_button.min.js.map index 36c975cc..105c1ce0 100644 --- a/amd/build/analytic_button.min.js.map +++ b/amd/build/analytic_button.min.js.map @@ -1 +1 @@ -{"version":3,"file":"analytic_button.min.js","sources":["../src/analytic_button.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 that creates an analytics button with an icon and text.\n * The button displays analytics information for a specific user and question.\n *\n * @module tiny_cursive/analytic_button\n * @copyright 2024 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([\"core/str\"], function(Str) {\n const analyticButton = (effort, userid, questionid = \"\") => {\n const anchor = document.createElement(\"a\");\n anchor.href = \"#\";\n anchor.id = \"analytics\" + userid + questionid;\n anchor.classList.add(\n \"d-inline-flex\",\n \"justify-content-center\",\n 'text-decoration-none'\n );\n\n const button = document.createElement('div');\n button.className = 'tiny_cursive-analytics-button';\n\n // Left side (icon + label)\n const left = document.createElement('div');\n left.className = 'tiny_cursive-analytics-left';\n\n const icon = document.createElement('img');\n icon.src = M.util.image_url('chart-column', 'tiny_cursive');\n icon.alt = 'Analytics Icon';\n icon.className = 'tiny_cursive-analytics-bar-icon';\n\n const label = document.createElement('span');\n label.className = 'tiny_cursive-analytics-label';\n label.textContent = 'Analytics';\n Str.get_string(\"analytics\", \"tiny_cursive\")\n .then((analyticsString) => {\n label.textContent = analyticsString;\n return analyticsString;\n })\n .catch((error) => {\n window.console.error(\"Error fetching string:\", error);\n });\n\n left.appendChild(icon);\n left.appendChild(label);\n button.appendChild(left);\n\n if (effort) {\n const right = document.createElement('div');\n right.className = 'tiny_cursive-analytics-right';\n right.textContent = effort + \"%\";\n right.title = \"Effort\";\n\n if (effort < 90) {\n right.style.backgroundColor = '#EAB308';\n }\n\n button.appendChild(right);\n }\n // Compose full button\n anchor.appendChild(button);\n\n return anchor;\n };\n\n return analyticButton;\n});\n"],"names":["define","Str","effort","userid","questionid","anchor","document","createElement","href","id","classList","add","button","className","left","icon","src","M","util","image_url","alt","label","textContent","get_string","then","analyticsString","catch","error","window","console","appendChild","right","title","style","backgroundColor"],"mappings":";;;;;;;;AAwBAA,sCAAO,CAAC,aAAa,SAASC,YACL,SAACC,OAAQC,YAAQC,kEAAa,SAC7CC,OAASC,SAASC,cAAc,KACtCF,OAAOG,KAAO,IACdH,OAAOI,GAAK,YAAcN,OAASC,WACnCC,OAAOK,UAAUC,IACf,gBACA,yBACA,8BAGIC,OAASN,SAASC,cAAc,OACtCK,OAAOC,UAAY,sCAGbC,KAAOR,SAASC,cAAc,OACpCO,KAAKD,UAAY,oCAEXE,KAAOT,SAASC,cAAc,OACpCQ,KAAKC,IAAMC,EAAEC,KAAKC,UAAU,eAAgB,gBAC5CJ,KAAKK,IAAM,iBACXL,KAAKF,UAAY,wCAEXQ,MAAQf,SAASC,cAAc,WACrCc,MAAMR,UAAY,+BAClBQ,MAAMC,YAAc,YACpBrB,IAAIsB,WAAW,YAAa,gBACzBC,MAAMC,kBACLJ,MAAMC,YAAcG,gBACbA,mBAERC,OAAOC,QACNC,OAAOC,QAAQF,MAAM,yBAA0BA,UAGnDb,KAAKgB,YAAYf,MACjBD,KAAKgB,YAAYT,OACjBT,OAAOkB,YAAYhB,MAEfZ,OAAQ,OACJ6B,MAAQzB,SAASC,cAAc,OACrCwB,MAAMlB,UAAY,+BAClBkB,MAAMT,YAAcpB,OAAS,IAC7B6B,MAAMC,MAAQ,SAEV9B,OAAS,KACX6B,MAAME,MAAMC,gBAAkB,WAGhCtB,OAAOkB,YAAYC,cAGrB1B,OAAOyB,YAAYlB,QAEZP"} \ No newline at end of file +{"version":3,"file":"analytic_button.min.js","sources":["../src/analytic_button.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * Module that creates an analytics button with an icon and text.\r\n * The button displays analytics information for a specific user and question.\r\n *\r\n * @module tiny_cursive/analytic_button\r\n * @copyright 2024 CTI \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\ndefine([\"core/str\"], function(Str) {\r\n const analyticButton = (effort, userid, questionid = \"\") => {\r\n const anchor = document.createElement(\"a\");\r\n anchor.href = \"#\";\r\n anchor.id = \"analytics\" + userid + questionid;\r\n anchor.classList.add(\r\n \"d-inline-flex\",\r\n \"justify-content-center\",\r\n 'text-decoration-none'\r\n );\r\n\r\n const button = document.createElement('div');\r\n button.className = 'tiny_cursive-analytics-button';\r\n\r\n // Left side (icon + label)\r\n const left = document.createElement('div');\r\n left.className = 'tiny_cursive-analytics-left';\r\n\r\n const icon = document.createElement('img');\r\n icon.src = M.util.image_url('chart-column', 'tiny_cursive');\r\n icon.alt = 'Analytics Icon';\r\n icon.className = 'tiny_cursive-analytics-bar-icon';\r\n\r\n const label = document.createElement('span');\r\n label.className = 'tiny_cursive-analytics-label';\r\n label.textContent = 'Analytics';\r\n Str.get_string(\"analytics\", \"tiny_cursive\")\r\n .then((analyticsString) => {\r\n label.textContent = analyticsString;\r\n return analyticsString;\r\n })\r\n .catch((error) => {\r\n window.console.error(\"Error fetching string:\", error);\r\n });\r\n\r\n left.appendChild(icon);\r\n left.appendChild(label);\r\n button.appendChild(left);\r\n\r\n if (effort) {\r\n const right = document.createElement('div');\r\n right.className = 'tiny_cursive-analytics-right';\r\n right.textContent = effort + \"%\";\r\n right.title = \"Effort\";\r\n\r\n if (effort < 90) {\r\n right.style.backgroundColor = '#EAB308';\r\n }\r\n\r\n button.appendChild(right);\r\n }\r\n // Compose full button\r\n anchor.appendChild(button);\r\n\r\n return anchor;\r\n };\r\n\r\n return analyticButton;\r\n});\r\n"],"names":["define","Str","effort","userid","questionid","anchor","document","createElement","href","id","classList","add","button","className","left","icon","src","M","util","image_url","alt","label","textContent","get_string","then","analyticsString","catch","error","window","console","appendChild","right","title","style","backgroundColor"],"mappings":";;;;;;;;AAwBAA,sCAAO,CAAC,aAAa,SAASC,YACL,SAACC,OAAQC,YAAQC,kEAAa,SAC7CC,OAASC,SAASC,cAAc,KACtCF,OAAOG,KAAO,IACdH,OAAOI,GAAK,YAAcN,OAASC,WACnCC,OAAOK,UAAUC,IACf,gBACA,yBACA,8BAGIC,OAASN,SAASC,cAAc,OACtCK,OAAOC,UAAY,sCAGbC,KAAOR,SAASC,cAAc,OACpCO,KAAKD,UAAY,oCAEXE,KAAOT,SAASC,cAAc,OACpCQ,KAAKC,IAAMC,EAAEC,KAAKC,UAAU,eAAgB,gBAC5CJ,KAAKK,IAAM,iBACXL,KAAKF,UAAY,wCAEXQ,MAAQf,SAASC,cAAc,WACrCc,MAAMR,UAAY,+BAClBQ,MAAMC,YAAc,YACpBrB,IAAIsB,WAAW,YAAa,gBACzBC,MAAMC,kBACLJ,MAAMC,YAAcG,gBACbA,mBAERC,OAAOC,QACNC,OAAOC,QAAQF,MAAM,yBAA0BA,UAGnDb,KAAKgB,YAAYf,MACjBD,KAAKgB,YAAYT,OACjBT,OAAOkB,YAAYhB,MAEfZ,OAAQ,OACJ6B,MAAQzB,SAASC,cAAc,OACrCwB,MAAMlB,UAAY,+BAClBkB,MAAMT,YAAcpB,OAAS,IAC7B6B,MAAMC,MAAQ,SAEV9B,OAAS,KACX6B,MAAME,MAAMC,gBAAkB,WAGhCtB,OAAOkB,YAAYC,cAGrB1B,OAAOyB,YAAYlB,QAEZP"} \ 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..cd731fab 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * Module for handling analytics events in the Tiny Cursive plugin.\r\n * Provides functionality for displaying analytics data, replaying writing,\r\n * checking differences and showing quality metrics.\r\n *\r\n * @module tiny_cursive/analytic_events\r\n * @copyright 2024 CTI \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport myModal from \"./analytic_modal\";\r\nimport {call as getContent} from \"core/ajax\";\r\nimport $ from 'jquery';\r\nimport {get_string as getString} from 'core/str';\r\nimport {get_strings as getStrings} from 'core/str';\r\nimport template from 'core/templates';\r\n\r\nexport default class AnalyticEvents {\r\n\r\n constructor() {\r\n getString('notenoughtinfo', 'tiny_cursive').then(str => {\r\n localStorage.setItem('notenoughtinfo', str);\r\n return str;\r\n }).catch(error => window.console.log(error));\r\n }\r\n\r\n createModal(userid, context, questionid = '', replayInstances = null, authIcon) {\r\n const self = this;\r\n $('#analytics' + userid + questionid).on('click', function(e) {\r\n e.preventDefault();\r\n\r\n const isReplayButton = $(this).find('.tiny_cursive-replay-button').length > 0;\r\n // Create Moodle modal\r\n myModal.create({templateContext: context}).then(modal => {\r\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\r\n modal.show();\r\n\r\n if (isReplayButton) {\r\n setTimeout(() => {\r\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\r\n\r\n const replayTab = $('#rep' + userid + questionid);\r\n if (replayTab.length) {\r\n replayTab.trigger('click');\r\n replayTab.addClass('active');\r\n }\r\n }, 50);\r\n }\r\n\r\n let moreBtn = $('body #more' + userid + questionid);\r\n if (moreBtn.length > 0) {\r\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\r\n $('#analytic' + userid + questionid).prop('disabled', true);\r\n $('#diff' + userid + questionid).prop('disabled', true);\r\n $('#analytic' + userid + questionid).css({\r\n 'background-color': 'rgba(168, 168, 168, 0.133)',\r\n 'cursor': 'not-allowed'\r\n });\r\n $('#diff' + userid + questionid).css({\r\n 'background-color': 'rgba(168, 168, 168, 0.133)',\r\n 'cursor': 'not-allowed'\r\n });\r\n moreBtn.on('click', function(e) {\r\n e.preventDefault();\r\n self.learnMore($(this), context, userid, questionid, replayInstances);\r\n });\r\n }\r\n\r\n return true;\r\n }).catch(error => {\r\n window.console.error(\"Failed to create modal:\", error);\r\n });\r\n\r\n });\r\n }\r\n\r\n analytics(userid, templates, context, questionid = '', replayInstances = null, authIcon) {\r\n\r\n $('body').on('click', '#analytic' + userid + questionid, function(e) {\r\n $('#rep' + userid + questionid).prop('disabled', false);\r\n $('#quality' + userid + questionid).prop('disabled', false);\r\n $('#content' + userid).attr('data-label', 'analytics');\r\n $('#player_' + userid + questionid).css({'display': 'none'});\r\n $('#content' + userid).removeClass('tiny_cursive_outputElement')\r\n .addClass('tiny_cursive').attr('data-label', 'analytics');\r\n e.preventDefault();\r\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\r\n .append($('
').addClass('tiny_cursive-loader')));\r\n if (replayInstances && replayInstances[userid]) {\r\n replayInstances[userid].stopReplay();\r\n }\r\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\r\n $(this).addClass('active'); // Add 'active' class to the clicked element\r\n\r\n templates.render('tiny_cursive/analytics_table', context).then(function(html) {\r\n $('#content' + userid).html(html);\r\n $('#content' + userid + ' .tiny_cursive_table tbody tr:first-child td:nth-child(2)').html(authIcon);\r\n return true;\r\n }).fail(function(error) {\r\n window.console.error(\"Failed to render template:\", error);\r\n });\r\n });\r\n }\r\n\r\n checkDiff(userid, fileid, questionid = '', replayInstances = null) {\r\n const nodata = document.createElement('p');\r\n nodata.classList.add('tiny_cursive_nopayload', 'bg-light');\r\n getString('nopaylod', 'tiny_cursive').then(str => {\r\n nodata.textContent = str;\r\n return true;\r\n }).catch(error => window.console.log(error));\r\n $('body').on('click', '#diff' + userid + questionid, function(e) {\r\n $('#rep' + userid + questionid).prop('disabled', false);\r\n $('#quality' + userid + questionid).prop('disabled', false);\r\n $('#content' + userid).attr('data-label', 'diff');\r\n $('#player_' + userid + questionid).css({\r\n 'display': 'none'\r\n });\r\n $('#content' + userid).removeClass('tiny_cursive_outputElement').addClass('tiny_cursive').attr('data-label', 'diff');\r\n e.preventDefault();\r\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\r\n .append($('
').addClass('tiny_cursive-loader')));\r\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\r\n $(this).addClass('active');\r\n if (replayInstances && replayInstances[userid]) {\r\n replayInstances[userid].stopReplay();\r\n }\r\n if (!fileid) {\r\n $('#content' + userid).html(nodata);\r\n throw new Error('Missing file id or Difference Content not received yet');\r\n }\r\n getContent([{\r\n methodname: 'cursive_get_writing_differences',\r\n args: {fileid: fileid},\r\n }])[0].done(response => {\r\n let responsedata = JSON.parse(response.data);\r\n if (responsedata) {\r\n let submittedText = atob(responsedata.submitted_text);\r\n\r\n // Fetch the dynamic strings.\r\n getStrings([\r\n {key: 'original_text', component: 'tiny_cursive'},\r\n {key: 'editspastesai', component: 'tiny_cursive'}\r\n ]).done(strings => {\r\n const originalTextString = strings[0];\r\n const editsPastesAIString = strings[1];\r\n\r\n const commentBox = $('
');\r\n var pasteCountDiv = $('
');\r\n getString('pastecount', 'tiny_cursive').then(str => {\r\n pasteCountDiv.append('
' + str + ' : ' + responsedata.commentscount + '
');\r\n return true;\r\n }).catch(error => window.console.log(error));\r\n\r\n var commentsDiv = $('
');\r\n getString('comments', 'tiny_cursive').then(str => {\r\n commentsDiv.append('' + str + '');\r\n return true;\r\n }).catch(error => window.console.error(error));\r\n\r\n var commentsList = $('
');\r\n\r\n let comments = responsedata.comments;\r\n for (let index in comments) {\r\n var commentDiv = $(`
`).text(comments[index].usercomment);\r\n commentsList.append(commentDiv);\r\n }\r\n commentBox.append(pasteCountDiv).append(commentsDiv).append(commentsList);\r\n\r\n const $legend = $('
');\r\n\r\n // Create the first legend item\r\n const $attributedItem = $('
', {\"class\": \"tiny_cursive-legend-item\"});\r\n const $attributedBox = $('
', {\"class\": \"tiny_cursive-box attributed\"});\r\n const $attributedText = $('').text(originalTextString);\r\n $attributedItem.append($attributedBox).append($attributedText);\r\n\r\n // Create the second legend item\r\n const $unattributedItem = $('
', {\"class\": 'tiny_cursive-legend-item'});\r\n const $unattributedBox = $('
', {\"class\": 'tiny_cursive-box tiny_cursive_added'});\r\n const $unattributedText = $('').text(editsPastesAIString);\r\n $unattributedItem.append($unattributedBox).append($unattributedText);\r\n\r\n // Append the legend items to the legend container.\r\n $legend.append($attributedItem).append($unattributedItem);\r\n\r\n let contents = $('
').addClass('tiny_cursive-comparison-content');\r\n let textBlock2 = $('
').addClass('tiny_cursive-text-block').append(\r\n $('
').attr('id', 'tiny_cursive-reconstructed_text').html(JSON.parse(submittedText))\r\n );\r\n\r\n contents.append(commentBox, $legend, textBlock2);\r\n $('#content' + userid).html(contents); // Update content.\r\n }).fail(error => {\r\n window.console.error(\"Failed to load language strings:\", error);\r\n $('#content' + userid).html(nodata);\r\n });\r\n } else {\r\n $('#content' + userid).html(nodata);\r\n }\r\n }).fail(error => {\r\n $('#content' + userid).html(nodata);\r\n throw new Error('Error loading JSON file: ' + error.message);\r\n });\r\n });\r\n }\r\n\r\n replyWriting(userid, filepath, questionid = '', replayInstances = null) {\r\n $('body').on('click', '#rep' + userid + questionid, function(e) {\r\n\r\n if (filepath) {\r\n $('#replayControls_' + userid + questionid).removeClass('d-none');\r\n $('#content' + userid).addClass('tiny_cursive_outputElement');\r\n }\r\n\r\n $(this).prop('disabled', true);\r\n $('#quality' + userid + questionid).prop('disabled', false);\r\n $('#content' + userid).attr('data-label', 'replay');\r\n $('#player_' + userid + questionid).css({\r\n 'display': 'block',\r\n 'padding-right': '8px'\r\n });\r\n e.preventDefault();\r\n $('#content' + userid).html($('
').addClass('d-flex justify-content-center my-5')\r\n .append($('
').addClass('tiny_cursive-loader')));\r\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\r\n $(this).addClass('active'); // Add 'active' class to the clicked element\r\n if (replayInstances && replayInstances[userid]) {\r\n replayInstances[userid].stopReplay();\r\n }\r\n if (questionid) {\r\n // eslint-disable-next-line\r\n video_playback(userid, filepath, questionid);\r\n } else {\r\n // eslint-disable-next-line\r\n video_playback(userid, filepath);\r\n }\r\n });\r\n }\r\n\r\n learnMore(moreBtn, context, userid, questionid, replayInstances) {\r\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\r\n moreBtn.addClass('active');\r\n $('#rep' + userid + questionid).prop('disabled', false);\r\n if (replayInstances && replayInstances[userid]) {\r\n replayInstances[userid].stopReplay();\r\n }\r\n $('#content' + userid + questionid).removeClass('tiny_cursive_outputElement');\r\n $('#replayControls_' + userid + questionid).addClass('d-none');\r\n template.render('tiny_cursive/learn_more', context).then(function(html) {\r\n $('#content' + userid + questionid).html(html);\r\n return true;\r\n }).fail(function(error) {\r\n window.console.error(\"Failed to render template:\", error);\r\n });\r\n }\r\n\r\n formatedTime(data) {\r\n if (data.total_time_seconds) {\r\n let totalTimeSeconds = data.total_time_seconds;\r\n let hours = Math.floor(totalTimeSeconds / 3600).toString().padStart(2, 0);\r\n let minutes = Math.floor((totalTimeSeconds % 3600) / 60).toString().padStart(2, 0);\r\n let seconds = (totalTimeSeconds % 60).toString().padStart(2, 0);\r\n return `${hours}h ${minutes}m ${seconds}s`;\r\n } else {\r\n return \"0h 0m 0s\";\r\n }\r\n }\r\n\r\n authorshipStatus(firstFile, score, scoreSetting) {\r\n var icon = 'fa fa-circle-o';\r\n var color = 'font-size:32px;color:black';\r\n score = parseFloat(score);\r\n\r\n if (firstFile) {\r\n icon = 'fa fa-solid fa-info-circle';\r\n color = 'font-size:32px;color:#000000';\r\n } else if (score >= scoreSetting) {\r\n icon = 'fa fa-check-circle';\r\n color = 'font-size:32px;color:green';\r\n }\r\n if (score < scoreSetting) {\r\n icon = 'fa fa-question-circle';\r\n color = 'font-size:32px;color:#A9A9A9';\r\n return $('').addClass(icon).attr('style', color).attr('title', localStorage.getItem('notenoughtinfo'));\r\n } else {\r\n return $('').addClass(icon).attr('style', color);\r\n }\r\n\r\n }\r\n}\r\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.map b/amd/build/analytic_modal.min.js.map index 068d5e6b..39960f9e 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * This module defines a custom modal for analytics.\r\n *\r\n * @module tiny_cursive/analytic_modal\r\n * @copyright 2024 CTI \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport Modal from 'core/modal';\r\nimport $ from 'jquery';\r\nexport default class MyModal extends Modal {\r\n static TYPE = \"tiny_cursive/analytics_modal\";\r\n static TEMPLATE = \"tiny_cursive/analytics_modal\";\r\n\r\n configure(modalConfig) {\r\n // Show this modal on instantiation.\r\n modalConfig.show = true;\r\n\r\n // Remove from the DOM on close.\r\n modalConfig.removeOnClose = true;\r\n modalConfig.backdrop = true;\r\n\r\n // Call the parent configure method.\r\n super.configure(modalConfig);\r\n }\r\n\r\n // Override the parent show method to add custom behavior.\r\n show() {\r\n super.show();\r\n\r\n const root = this.getRoot();\r\n\r\n\r\n // Hide the default modal header.\r\n root.find('.modal-header').remove();\r\n\r\n root.find('.modal-content').css({\r\n 'border-radius': '30px'\r\n }).addClass('shadow-none border-none');\r\n // Remove padding from the modal content.\r\n root.find('.modal-body').css({\r\n 'padding': '0',\r\n 'border-radius': '30px',\r\n 'overflow': 'auto',\r\n 'scrollbar-width': 'none'\r\n });\r\n root.find('.modal-dialog').css({\r\n 'max-width': '800px',\r\n 'background-color': 'transparent'\r\n });\r\n\r\n // Ensure modal closes on 'analytic-close' button click.\r\n root.find('#analytic-close').on('click', () => {\r\n this.destroy();\r\n });\r\n\r\n // Ensure modal closes on backdrop click.\r\n root.on('click', (e) => {\r\n if ($(e.target).hasClass('modal')) {\r\n this.destroy();\r\n }\r\n });\r\n }\r\n}\r\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 3f491c81..edfce93c 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/append_fourm_post\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\",\r\n \"./replay_button\", \"./analytic_events\"], function(\r\n $,\r\n AJAX,\r\n str,\r\n templates,\r\n Replay,\r\n analyticButton,\r\n replayButton,\r\n AnalyticEvents\r\n) {\r\n const replayInstances = {};\r\n // eslint-disable-next-line camelcase\r\n window.video_playback = function(mid, filepath) {\r\n if (filepath !== '') {\r\n const replay = new Replay(\r\n 'content' + mid,\r\n filepath,\r\n 10,\r\n false,\r\n 'player_' + mid\r\n );\r\n replayInstances[mid] = replay;\r\n } else {\r\n templates.render('tiny_cursive/no_submission').then(html => {\r\n $('#content' + mid).html(html);\r\n return true;\r\n }).catch(e => window.console.error(e));\r\n }\r\n return false;\r\n\r\n };\r\n\r\n var usersTable = {\r\n init: function(scoreSetting, showcomment, hasApiKey) {\r\n str\r\n .get_strings([\r\n {key: \"field_require\", component: \"tiny_cursive\"},\r\n ])\r\n .done(function() {\r\n usersTable.getToken(scoreSetting, showcomment, hasApiKey);\r\n });\r\n },\r\n getToken: function(scoreSetting, showcomment, hasApiKey) {\r\n $('#page-mod-forum-discuss').find(\"article\").get().forEach(function(entry) {\r\n var replyButton = $('a[data-region=\"post-action\"][title=\"Reply\"]');\r\n if (replyButton.length > 0) {\r\n replyButton.on('click', function(event) {\r\n event.preventDefault();\r\n var url = $(this).attr('href');\r\n window.location.href = url;\r\n });\r\n }\r\n\r\n var ids = $(\"#\" + entry.id).data(\"post-id\");\r\n var cmid = M.cfg.contextInstanceId;\r\n\r\n let args = {id: ids, modulename: \"forum\", cmid: cmid};\r\n let methodname = 'cursive_get_forum_comment_link';\r\n let com = AJAX.call([{methodname, args}]);\r\n com[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n\r\n var filepath = '';\r\n if (data.data.filename) {\r\n filepath = data.data.filename;\r\n }\r\n if (filepath) {\r\n\r\n let analyticButtonDiv = document.createElement('div');\r\n\r\n if (!hasApiKey) {\r\n $(analyticButtonDiv).html(replayButton(ids));\r\n } else {\r\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, ids));\r\n }\r\n\r\n analyticButtonDiv.classList.add('text-center', 'my-2');\r\n analyticButtonDiv.dataset.region = \"analytic-div\" + ids;\r\n\r\n $(\"#\" + entry.id).find('#post-content-' + ids).prepend(analyticButtonDiv);\r\n\r\n let myEvents = new AnalyticEvents();\r\n var context = {\r\n tabledata: data.data,\r\n formattime: myEvents.formatedTime(data.data),\r\n page: scoreSetting,\r\n userid: ids,\r\n apikey: hasApiKey\r\n };\r\n\r\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\r\n myEvents.createModal(ids, context, '', replayInstances, authIcon);\r\n myEvents.analytics(ids, templates, context, '', replayInstances, authIcon);\r\n myEvents.checkDiff(ids, data.data.file_id, '', replayInstances);\r\n myEvents.replyWriting(ids, filepath, '', replayInstances);\r\n }\r\n\r\n });\r\n return com.usercomment;\r\n });\r\n },\r\n };\r\n return usersTable;\r\n\r\n\r\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 diff --git a/amd/build/append_lesson_grade_table.min.js.map b/amd/build/append_lesson_grade_table.min.js.map index ac5fede6..14e460ed 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * Module to append analytics and grade table data for lesson submissions\r\n * Handles the display of analytics, replay functionality and grade information\r\n * for lesson submissions in the Moodle gradebook interface\r\n *\r\n * @module tiny_cursive/append_lesson_grade_table\r\n * @copyright 2025 CTI \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport Replay from './replay';\r\nimport $ from 'jquery';\r\nimport {call as getData} from 'core/ajax';\r\nimport templates from 'core/templates';\r\nimport AnalyticEvents from './analytic_events';\r\nimport analyticButton from './analytic_button';\r\nimport replayButton from './replay_button';\r\nimport * as Str from 'core/str';\r\n\r\nexport const init = (scoreSetting, showcomment, hasApiKey) => {\r\n const replayInstances = {};\r\n // eslint-disable-next-line camelcase\r\n window.video_playback = function(mid, filepath) {\r\n if (filepath !== '') {\r\n const replay = new Replay(\r\n 'content' + mid,\r\n filepath,\r\n 10,\r\n false,\r\n 'player_' + mid\r\n );\r\n replayInstances[mid] = replay;\r\n } else {\r\n templates.render('tiny_cursive/no_submission').then(html => {\r\n $('#content' + mid).html(html);\r\n return true;\r\n }).catch(e => window.console.error(e));\r\n }\r\n return false;\r\n\r\n };\r\n\r\n\r\n var cmid = M.cfg.contextInstanceId;\r\n var emailLink = $('#page-content div[role=\"main\"] td.lastcol a');\r\n var headcolumn = $('#region-main div[role=\"main\"] table thead tr');\r\n let url = new URL(window.location.href);\r\n let mode = url.searchParams.get('mode');\r\n let user = url.searchParams.get('user');\r\n\r\n Str.get_string('analytics', 'tiny_cursive').then((strs) => {\r\n headcolumn.each(function() {\r\n $(this).find('th:eq(1)').after(`${strs}`);\r\n });\r\n return true;\r\n }).catch(e => window.console.error(e));\r\n\r\n if (mode && mode === \"grade\") {\r\n analytics(user, cmid, \"\", true);\r\n }\r\n\r\n emailLink.each(function() {\r\n let href = $(this).attr('href');\r\n const $emailLink = $(this);\r\n let userid = 0;\r\n if (href) {\r\n userid = parseInt(new URLSearchParams(href.split('?')[1]).get('userid'));\r\n if (!userid) {\r\n $emailLink.closest('tr').find('td:eq(1)').after(\"\"); // For aligning the table column\r\n } else {\r\n\r\n $('#region-main').on('click', 'table tbody tr td.cell.c1 a', function(e) {\r\n e.preventDefault();\r\n const link = e.target.href;\r\n const url = new URL(link);\r\n url.searchParams.append('user', userid);\r\n window.location.href = url.toString();\r\n });\r\n\r\n analytics(userid, cmid, $emailLink, false);\r\n }\r\n }\r\n });\r\n\r\n /**\r\n * Fetches and displays analytics data for lesson submissions\r\n * @param {number} userid - The ID of the user whose analytics to fetch\r\n * @param {number} cmid - The course module ID\r\n * @param {jQuery|string} $emailLink - jQuery object of email link or empty string\r\n * @param {boolean} grade - Whether this is being called from grade view\r\n */\r\n function analytics(userid, cmid, $emailLink, grade) {\r\n\r\n let args = {id: userid, modulename: \"lesson\", cmid: cmid};\r\n let methodname = 'cursive_get_lesson_submission_data';\r\n let com = getData([{methodname, args}]);\r\n com[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n var filepath = '';\r\n if (data.res.filename) {\r\n filepath = data.res.filename;\r\n }\r\n\r\n let analyticButtonDiv = document.createElement('div');\r\n let analyticsColumn = document.createElement('td');\r\n\r\n if (!hasApiKey) {\r\n $(analyticButtonDiv).html(replayButton(userid));\r\n } else {\r\n analyticButtonDiv.append(analyticButton(data.res.effort_ratio, userid));\r\n }\r\n\r\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\r\n analyticsColumn.append(analyticButtonDiv);\r\n if (grade) {\r\n analyticButtonDiv.classList.add('w-100');\r\n $('#fitem_id_response_editor .felement').prepend(analyticButtonDiv);\r\n } else {\r\n $emailLink.closest('tr').find('td:eq(1)').after(analyticsColumn);\r\n }\r\n\r\n\r\n let myEvents = new AnalyticEvents();\r\n var context = {\r\n tabledata: data.res,\r\n formattime: myEvents.formatedTime(data.res),\r\n page: scoreSetting,\r\n userid: userid,\r\n apikey: hasApiKey\r\n };\r\n\r\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\r\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\r\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\r\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\r\n myEvents.replyWriting(userid, filepath, '', replayInstances);\r\n\r\n });\r\n com[0].fail((error) => {\r\n window.console.error('Error getting cursive config:', error);\r\n });\r\n }\r\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_oublogs_post.min.js.map b/amd/build/append_oublogs_post.min.js.map index 443769ab..1b309439 100644 --- a/amd/build/append_oublogs_post.min.js.map +++ b/amd/build/append_oublogs_post.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_oublogs_post.min.js","sources":["../src/append_oublogs_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 * This module provides functionality to append blog posts in the OU Blogs plugin for TinyMCE editor\n *\n * @module tiny_cursive/append_oublogs_post\n * @copyright 2025 CTI \n * @author Brain Station 23 \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\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 Replay from './replay';\n\nexport const init = (scoreSetting, comments, 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 const ouBlogPosts = $('.oublog-post');\n const cmid = M.cfg.contextInstanceId;\n ouBlogPosts.each(function() {\n\n var Element = $(this);\n var postedByLink = Element.find('.oublog-postedby a').attr('href');\n var permalink = Element.find('.oublog-post-links a').attr('href');\n var resourceId = parseInt(new URLSearchParams(permalink.split('?')[1]).get('post'));\n var userid = parseInt(new URLSearchParams(postedByLink.split('?')[1]).get('id'));\n var filepath = '';\n\n if (userid && resourceId) {\n let args = {id: userid, resourceid: resourceId, modulename: \"oublog\", cmid: cmid};\n let methodname = 'cursive_get_oublog_submission_data';\n let com = getData([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n if (data.res.filename) {\n filepath = data.res.filename;\n }\n\n if (!hasApiKey) {\n Element.find('.oublog-post-links').append(replayButton(userid));\n } else {\n Element.find('.oublog-post-links').append(analyticButton(data.res.effort_ratio, userid));\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);\n });\n }\n });\n\n};"],"names":["scoreSetting","comments","hasApiKey","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","catch","e","console","error","ouBlogPosts","cmid","M","cfg","contextInstanceId","each","Element","this","postedByLink","find","attr","permalink","resourceId","parseInt","URLSearchParams","split","get","userid","args","id","resourceid","modulename","methodname","com","done","json","data","JSON","parse","res","filename","append","effort_ratio","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","templates","checkDiff","file_id","replyWriting","fail"],"mappings":";;;;;;;;wYAgCoB,CAACA,aAAcC,SAAUC,mBACnCC,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,SAILG,aAAc,mBAAE,gBAChBC,KAAOC,EAAEC,IAAIC,kBACnBJ,YAAYK,MAAK,eAETC,SAAU,mBAAEC,MACZC,aAAeF,QAAQG,KAAK,sBAAsBC,KAAK,QACvDC,UAAYL,QAAQG,KAAK,wBAAwBC,KAAK,QACtDE,WAAaC,SAAS,IAAIC,gBAAgBH,UAAUI,MAAM,KAAK,IAAIC,IAAI,SACvEC,OAASJ,SAAS,IAAIC,gBAAgBN,aAAaO,MAAM,KAAK,IAAIC,IAAI,OACtE1B,SAAW,MAEX2B,QAAUL,WAAY,KAClBM,KAAO,CAACC,GAAIF,OAAQG,WAAYR,WAAYS,WAAY,SAAUpB,KAAMA,MACxEqB,WAAa,qCACbC,KAAM,cAAQ,CAAC,CAACD,WAAAA,WAAYJ,KAAAA,QAChCK,IAAI,GAAGC,MAAK,SAASC,UACbC,KAAOC,KAAKC,MAAMH,MAElBC,KAAKG,IAAIC,WACTxC,SAAWoC,KAAKG,IAAIC,UAGnB7C,UAGDqB,QAAQG,KAAK,sBAAsBsB,QAAO,4BAAeL,KAAKG,IAAIG,aAAcf,SAFhFX,QAAQG,KAAK,sBAAsBsB,QAAO,0BAAad,aAKvDgB,SAAW,IAAIC,6BACfC,QAAU,CACVC,UAAWV,KAAKG,IAChBQ,WAAYJ,SAASK,aAAaZ,KAAKG,KACvCU,KAAMxD,aACNkC,OAAQA,OACRuB,OAAQvD,eAGRwD,SAAWR,SAASS,iBAAiBhB,KAAKG,IAAIc,WAAYjB,KAAKG,IAAIe,MAAO7D,cAC9EkD,SAASY,YAAY5B,OAAQkB,QAAS,GAAIjD,gBAAiBuD,UAC3DR,SAASa,UAAU7B,OAAQ8B,mBAAWZ,QAAS,GAAIjD,gBAAiBuD,UACpER,SAASe,UAAU/B,OAAQS,KAAKG,IAAIoB,QAAS,GAAI/D,iBACjD+C,SAASiB,aAAajC,OAAQ3B,SAAU,GAAIJ,oBAGhDqC,IAAI,GAAG4B,MAAMpD,QACTZ,OAAOW,QAAQC,MAAMA"} \ No newline at end of file +{"version":3,"file":"append_oublogs_post.min.js","sources":["../src/append_oublogs_post.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * This module provides functionality to append blog posts in the OU Blogs plugin for TinyMCE editor\r\n *\r\n * @module tiny_cursive/append_oublogs_post\r\n * @copyright 2025 CTI \r\n * @author Brain Station 23 \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport $ from 'jquery';\r\nimport {call as getData} from 'core/ajax';\r\nimport templates from 'core/templates';\r\nimport AnalyticEvents from './analytic_events';\r\nimport analyticButton from './analytic_button';\r\nimport replayButton from './replay_button';\r\nimport Replay from './replay';\r\n\r\nexport const init = (scoreSetting, comments, hasApiKey) => {\r\n const replayInstances = {};\r\n // eslint-disable-next-line camelcase\r\n window.video_playback = function(mid, filepath) {\r\n if (filepath !== '') {\r\n const replay = new Replay(\r\n 'content' + mid,\r\n filepath,\r\n 10,\r\n false,\r\n 'player_' + mid\r\n );\r\n replayInstances[mid] = replay;\r\n } else {\r\n templates.render('tiny_cursive/no_submission').then(html => {\r\n $('#content' + mid).html(html);\r\n return true;\r\n }).catch(e => window.console.error(e));\r\n }\r\n return false;\r\n\r\n };\r\n\r\n const ouBlogPosts = $('.oublog-post');\r\n const cmid = M.cfg.contextInstanceId;\r\n ouBlogPosts.each(function() {\r\n\r\n var Element = $(this);\r\n var postedByLink = Element.find('.oublog-postedby a').attr('href');\r\n var permalink = Element.find('.oublog-post-links a').attr('href');\r\n var resourceId = parseInt(new URLSearchParams(permalink.split('?')[1]).get('post'));\r\n var userid = parseInt(new URLSearchParams(postedByLink.split('?')[1]).get('id'));\r\n var filepath = '';\r\n\r\n if (userid && resourceId) {\r\n let args = {id: userid, resourceid: resourceId, modulename: \"oublog\", cmid: cmid};\r\n let methodname = 'cursive_get_oublog_submission_data';\r\n let com = getData([{methodname, args}]);\r\n com[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n\r\n if (data.res.filename) {\r\n filepath = data.res.filename;\r\n }\r\n\r\n if (!hasApiKey) {\r\n Element.find('.oublog-post-links').append(replayButton(userid));\r\n } else {\r\n Element.find('.oublog-post-links').append(analyticButton(data.res.effort_ratio, userid));\r\n }\r\n\r\n let myEvents = new AnalyticEvents();\r\n var context = {\r\n tabledata: data.res,\r\n formattime: myEvents.formatedTime(data.res),\r\n page: scoreSetting,\r\n userid: userid,\r\n apikey: hasApiKey\r\n };\r\n\r\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\r\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\r\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\r\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\r\n myEvents.replyWriting(userid, filepath, '', replayInstances);\r\n\r\n });\r\n com[0].fail((error) => {\r\n window.console.error(error);\r\n });\r\n }\r\n });\r\n\r\n};"],"names":["scoreSetting","comments","hasApiKey","replayInstances","window","video_playback","mid","filepath","replay","Replay","render","then","html","catch","e","console","error","ouBlogPosts","cmid","M","cfg","contextInstanceId","each","Element","this","postedByLink","find","attr","permalink","resourceId","parseInt","URLSearchParams","split","get","userid","args","id","resourceid","modulename","methodname","com","done","json","data","JSON","parse","res","filename","append","effort_ratio","myEvents","AnalyticEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","templates","checkDiff","file_id","replyWriting","fail"],"mappings":";;;;;;;;wYAgCoB,CAACA,aAAcC,SAAUC,mBACnCC,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,SAILG,aAAc,mBAAE,gBAChBC,KAAOC,EAAEC,IAAIC,kBACnBJ,YAAYK,MAAK,eAETC,SAAU,mBAAEC,MACZC,aAAeF,QAAQG,KAAK,sBAAsBC,KAAK,QACvDC,UAAYL,QAAQG,KAAK,wBAAwBC,KAAK,QACtDE,WAAaC,SAAS,IAAIC,gBAAgBH,UAAUI,MAAM,KAAK,IAAIC,IAAI,SACvEC,OAASJ,SAAS,IAAIC,gBAAgBN,aAAaO,MAAM,KAAK,IAAIC,IAAI,OACtE1B,SAAW,MAEX2B,QAAUL,WAAY,KAClBM,KAAO,CAACC,GAAIF,OAAQG,WAAYR,WAAYS,WAAY,SAAUpB,KAAMA,MACxEqB,WAAa,qCACbC,KAAM,cAAQ,CAAC,CAACD,WAAAA,WAAYJ,KAAAA,QAChCK,IAAI,GAAGC,MAAK,SAASC,UACbC,KAAOC,KAAKC,MAAMH,MAElBC,KAAKG,IAAIC,WACTxC,SAAWoC,KAAKG,IAAIC,UAGnB7C,UAGDqB,QAAQG,KAAK,sBAAsBsB,QAAO,4BAAeL,KAAKG,IAAIG,aAAcf,SAFhFX,QAAQG,KAAK,sBAAsBsB,QAAO,0BAAad,aAKvDgB,SAAW,IAAIC,6BACfC,QAAU,CACVC,UAAWV,KAAKG,IAChBQ,WAAYJ,SAASK,aAAaZ,KAAKG,KACvCU,KAAMxD,aACNkC,OAAQA,OACRuB,OAAQvD,eAGRwD,SAAWR,SAASS,iBAAiBhB,KAAKG,IAAIc,WAAYjB,KAAKG,IAAIe,MAAO7D,cAC9EkD,SAASY,YAAY5B,OAAQkB,QAAS,GAAIjD,gBAAiBuD,UAC3DR,SAASa,UAAU7B,OAAQ8B,mBAAWZ,QAAS,GAAIjD,gBAAiBuD,UACpER,SAASe,UAAU/B,OAAQS,KAAKG,IAAIoB,QAAS,GAAI/D,iBACjD+C,SAASiB,aAAajC,OAAQ3B,SAAU,GAAIJ,oBAGhDqC,IAAI,GAAG4B,MAAMpD,QACTZ,OAAOW,QAAQC,MAAMA"} \ No newline at end of file diff --git a/amd/build/append_participants_table.min.js.map b/amd/build/append_participants_table.min.js.map index f5630dc2..d0197189 100644 --- a/amd/build/append_participants_table.min.js.map +++ b/amd/build/append_participants_table.min.js.map @@ -1 +1 @@ -{"version":3,"file":"append_participants_table.min.js","sources":["../src/append_participants_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_participants_table\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/config\", \"core/str\"], function($, mdlcfg, Str) {\n var usersTable = {\n init: async function(page) {\n await usersTable.appendTable(page);\n },\n appendTable: async function() {\n $(document).ready(async function($) {\n // Get the first row in the table header\n let hTr = $('thead').find('tr').get()[0];\n let courseid = M.cfg.courseId;\n\n // Fetch string for stats header\n let statsString = await Str.get_string('stats', 'tiny_cursive');\n\n // Add the stats header if it doesn't already exist\n if (!$(hTr).find('#stats').length) {\n $(hTr).find('th').eq(6).after('' + statsString + '');\n }\n\n // Iterate over each row in the table body\n $('tbody').find(\"tr\").get().forEach(function(tr) {\n let tdUser = $(tr).find(\"td\").get()[0];\n let userid = $(tdUser).find(\"input\").get()[0]?.id;\n userid = userid?.slice(4); // Extract userid from input id\n\n if (userid) {\n // Avoid duplicating the icon by checking if the icon is already added\n if (!$(tr).find('td').eq(6).find('i').length) {\n let color = 'font-size:24px;color:black;text-decoration:none';\n let link = mdlcfg.wwwroot + \"/lib/editor/tiny/plugins/cursive/writing_report.php?userid=\"\n + userid + \"&courseid=\" + courseid;\n let icon = 'fa fa-area-chart';\n\n // Add the icon link to the 6th column\n let thunderIcon = '' +\n '';\n $(tr).find('td').eq(5).after(thunderIcon); // Insert after the 5th column\n }\n }\n });\n\n // Add event listener for page change or other events triggering table update\n $(\".page-item, .header\").on('click', function() {\n setTimeout(() => {\n // Prevent multiple initializations by checking if already initialized\n if (!$('#stats').length) {\n usersTable.init(); // Initialize the table if needed\n }\n }, 1800); // Slight delay to ensure the DOM is fully updated\n });\n });\n }\n };\n return usersTable;\n});\n"],"names":["define","$","mdlcfg","Str","usersTable","init","async","page","appendTable","document","ready","hTr","find","get","courseid","M","cfg","courseId","statsString","get_string","length","eq","after","forEach","tr","tdUser","userid","_$$find$get$","id","_userid","slice","color","thunderIcon","wwwroot","on","setTimeout"],"mappings":"AAsBAA,gDAAO,CAAC,SAAU,cAAe,aAAa,SAASC,EAAGC,OAAQC,SAC1DC,WAAa,CACbC,KAAMC,eAAeC,YACXH,WAAWI,YAAYD,OAEjCC,YAAaF,iBACTL,EAAEQ,UAAUC,OAAMJ,eAAeL,OAEzBU,IAAMV,EAAE,SAASW,KAAK,MAAMC,MAAM,GAClCC,SAAWC,EAAEC,IAAIC,SAGjBC,kBAAoBf,IAAIgB,WAAW,QAAS,gBAG3ClB,EAAEU,KAAKC,KAAK,UAAUQ,QACvBnB,EAAEU,KAAKC,KAAK,MAAMS,GAAG,GAAGC,MAAM,oCAAsCJ,YAAc,SAItFjB,EAAE,SAASW,KAAK,MAAMC,MAAMU,SAAQ,SAASC,iCACrCC,OAASxB,EAAEuB,IAAIZ,KAAK,MAAMC,MAAM,GAChCa,4BAASzB,EAAEwB,QAAQb,KAAK,SAASC,MAAM,kCAA9Bc,aAAkCC,MAC/CF,uBAASA,iCAAAG,QAAQC,MAAM,GAEnBJ,SAEKzB,EAAEuB,IAAIZ,KAAK,MAAMS,GAAG,GAAGT,KAAK,KAAKQ,OAAQ,KACtCW,MAAQ,kDAMRC,YAAc,iBALP9B,OAAO+B,QAAU,8DACtBP,OAAS,aAAeZ,UAIa,aAAeY,OAAxC,cAHP,mBAIe,+BAAiCK,MAAQ,kBACnE9B,EAAEuB,IAAIZ,KAAK,MAAMS,GAAG,GAAGC,MAAMU,iBAMzC/B,EAAE,uBAAuBiC,GAAG,SAAS,WACjCC,YAAW,KAEFlC,EAAE,UAAUmB,QACbhB,WAAWC,SAEhB,qBAKZD"} \ No newline at end of file +{"version":3,"file":"append_participants_table.min.js","sources":["../src/append_participants_table.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/append_participants_table\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\"jquery\", \"core/config\", \"core/str\"], function($, mdlcfg, Str) {\r\n var usersTable = {\r\n init: async function(page) {\r\n await usersTable.appendTable(page);\r\n },\r\n appendTable: async function() {\r\n $(document).ready(async function($) {\r\n // Get the first row in the table header\r\n let hTr = $('thead').find('tr').get()[0];\r\n let courseid = M.cfg.courseId;\r\n\r\n // Fetch string for stats header\r\n let statsString = await Str.get_string('stats', 'tiny_cursive');\r\n\r\n // Add the stats header if it doesn't already exist\r\n if (!$(hTr).find('#stats').length) {\r\n $(hTr).find('th').eq(6).after('' + statsString + '');\r\n }\r\n\r\n // Iterate over each row in the table body\r\n $('tbody').find(\"tr\").get().forEach(function(tr) {\r\n let tdUser = $(tr).find(\"td\").get()[0];\r\n let userid = $(tdUser).find(\"input\").get()[0]?.id;\r\n userid = userid?.slice(4); // Extract userid from input id\r\n\r\n if (userid) {\r\n // Avoid duplicating the icon by checking if the icon is already added\r\n if (!$(tr).find('td').eq(6).find('i').length) {\r\n let color = 'font-size:24px;color:black;text-decoration:none';\r\n let link = mdlcfg.wwwroot + \"/lib/editor/tiny/plugins/cursive/writing_report.php?userid=\"\r\n + userid + \"&courseid=\" + courseid;\r\n let icon = 'fa fa-area-chart';\r\n\r\n // Add the icon link to the 6th column\r\n let thunderIcon = '' +\r\n '';\r\n $(tr).find('td').eq(5).after(thunderIcon); // Insert after the 5th column\r\n }\r\n }\r\n });\r\n\r\n // Add event listener for page change or other events triggering table update\r\n $(\".page-item, .header\").on('click', function() {\r\n setTimeout(() => {\r\n // Prevent multiple initializations by checking if already initialized\r\n if (!$('#stats').length) {\r\n usersTable.init(); // Initialize the table if needed\r\n }\r\n }, 1800); // Slight delay to ensure the DOM is fully updated\r\n });\r\n });\r\n }\r\n };\r\n return usersTable;\r\n});\r\n"],"names":["define","$","mdlcfg","Str","usersTable","init","async","page","appendTable","document","ready","hTr","find","get","courseid","M","cfg","courseId","statsString","get_string","length","eq","after","forEach","tr","tdUser","userid","_$$find$get$","id","_userid","slice","color","thunderIcon","wwwroot","on","setTimeout"],"mappings":"AAsBAA,gDAAO,CAAC,SAAU,cAAe,aAAa,SAASC,EAAGC,OAAQC,SAC1DC,WAAa,CACbC,KAAMC,eAAeC,YACXH,WAAWI,YAAYD,OAEjCC,YAAaF,iBACTL,EAAEQ,UAAUC,OAAMJ,eAAeL,OAEzBU,IAAMV,EAAE,SAASW,KAAK,MAAMC,MAAM,GAClCC,SAAWC,EAAEC,IAAIC,SAGjBC,kBAAoBf,IAAIgB,WAAW,QAAS,gBAG3ClB,EAAEU,KAAKC,KAAK,UAAUQ,QACvBnB,EAAEU,KAAKC,KAAK,MAAMS,GAAG,GAAGC,MAAM,oCAAsCJ,YAAc,SAItFjB,EAAE,SAASW,KAAK,MAAMC,MAAMU,SAAQ,SAASC,iCACrCC,OAASxB,EAAEuB,IAAIZ,KAAK,MAAMC,MAAM,GAChCa,4BAASzB,EAAEwB,QAAQb,KAAK,SAASC,MAAM,kCAA9Bc,aAAkCC,MAC/CF,uBAASA,iCAAAG,QAAQC,MAAM,GAEnBJ,SAEKzB,EAAEuB,IAAIZ,KAAK,MAAMS,GAAG,GAAGT,KAAK,KAAKQ,OAAQ,KACtCW,MAAQ,kDAMRC,YAAc,iBALP9B,OAAO+B,QAAU,8DACtBP,OAAS,aAAeZ,UAIa,aAAeY,OAAxC,cAHP,mBAIe,+BAAiCK,MAAQ,kBACnE9B,EAAEuB,IAAIZ,KAAK,MAAMS,GAAG,GAAGC,MAAMU,iBAMzC/B,EAAE,uBAAuBiC,GAAG,SAAS,WACjCC,YAAW,KAEFlC,EAAE,UAAUmB,QACbhB,WAAWC,SAEhB,qBAKZD"} \ 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..4d8a05e5 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * Module for handling PDF annotator functionality,\r\n *\r\n * @module tiny_cursive/append_pdfannotator\r\n * @copyright 2025 Cursive Technology, Inc. \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport {call} from 'core/ajax';\r\nimport analyticButton from 'tiny_cursive/analytic_button';\r\nimport replayButton from 'tiny_cursive/replay_button';\r\nimport AnalyticEvents from 'tiny_cursive/analytic_events';\r\nimport templates from 'core/templates';\r\nimport Replay from 'tiny_cursive/replay';\r\nexport const init = (scoreSetting, comments, hasApiKey, userid) => {\r\n const replayInstances = {};\r\n // eslint-disable-next-line camelcase\r\n window.video_playback = function(mid, filepath) {\r\n if (filepath !== '') {\r\n const replay = new Replay(\r\n 'content' + mid,\r\n filepath,\r\n 10,\r\n false,\r\n 'player_' + mid\r\n );\r\n replayInstances[mid] = replay;\r\n } else {\r\n templates.render('tiny_cursive/no_submission').then(html => {\r\n document.getElementById('content' + mid).innerHTML = html;\r\n return true;\r\n }).catch(e => window.console.error(e));\r\n }\r\n return false;\r\n };\r\n\r\n let container = document.querySelector('.comment-list-container');\r\n const overviewTable = document.querySelector('table[id^=\"mod-pdfannotator-\"]');\r\n\r\n document.addEventListener('click', handleSubmit);\r\n const moduleName = document.body.id.split('-')[2];\r\n var pendingSubmit = false;\r\n var buttonElement = \"\";\r\n\r\n if (container) {\r\n const observer = new MutationObserver(() => {\r\n if (container?.lastChild?.id) {\r\n extractResourceId(container.lastChild.id);\r\n }\r\n });\r\n\r\n observer.observe(container, {\r\n subtree: true,\r\n childList: true\r\n });\r\n }\r\n\r\n if (overviewTable) {\r\n let newChild = document.createElement('th');\r\n newChild.textContent = 'Analytics';\r\n let header = overviewTable.querySelector('thead>tr>th:first-child');\r\n header.insertAdjacentElement('afterend', newChild);\r\n setReplayButton(overviewTable);\r\n }\r\n\r\n /**\r\n * Sets up replay buttons and analytics for each row in the overview table\r\n * @param {HTMLTableElement} overviewTable - The table element containing the overview data\r\n * @description This function:\r\n * 1. Gets all rows from the table\r\n * 2. For each row:\r\n * - Extracts comment ID and user ID from relevant links\r\n * - Adds analytics column with replay/analytics buttons\r\n * - Sets up cursive analytics functionality\r\n */\r\n function setReplayButton(overviewTable) {\r\n const rows = overviewTable.querySelectorAll('tbody > tr');\r\n const action = new URL(window.location.href).searchParams.get('action');\r\n\r\n rows.forEach(row => {\r\n const cols = {\r\n col1: row.querySelector('td:nth-child(1)'),\r\n col2: row.querySelector('td:nth-child(2)'),\r\n col3: row.querySelector('td:nth-child(3)')\r\n };\r\n\r\n const links = {\r\n link1: cols.col1?.querySelector('a'),\r\n link2: cols.col2?.querySelector('a'),\r\n link3: cols.col3?.querySelector('a')\r\n };\r\n\r\n // Extract comment ID safely\r\n const commentId = links.link1?.href ?\r\n new URL(links.link1.href).searchParams.get('commid') : null;\r\n const cmid = links.link1?.href ?\r\n new URL(links.link1.href).searchParams.get('id') : M.cfg.contextInstanceId;\r\n // Extract user ID based on action\r\n let userId = null;\r\n let userLink = null;\r\n\r\n switch (action) {\r\n case 'overviewquestions':\r\n userLink = links.link2;\r\n break;\r\n case 'overviewanswers':\r\n userLink = links.link3;\r\n break;\r\n default:\r\n userId = userid;\r\n }\r\n\r\n if (userLink?.href) {\r\n try {\r\n userId = new URL(userLink.href).searchParams.get('id');\r\n } catch (e) {\r\n window.console.warn('Error parsing user URL:', e);\r\n }\r\n }\r\n\r\n getCursiveAnalytics(userId, commentId, cmid, cols.col1);\r\n });\r\n }\r\n\r\n /**\r\n * Handles the submission and cancellation of comments\r\n * @param {Event} e - The click event object\r\n * @description When comment is submitted or cancelled:\r\n * - Removes 'isEditing' flag from localStorage\r\n * - Sets pendingSubmit flag appropriately (true for submit, false for cancel)\r\n */\r\n function handleSubmit(e) {\r\n if (e.target.id === 'commentSubmit') {\r\n localStorage.removeItem('isEditing');\r\n buttonElement = e.target.value;\r\n pendingSubmit = true;\r\n }\r\n if (e.target.id === 'commentCancel') {\r\n localStorage.removeItem('isEditing');\r\n pendingSubmit = false;\r\n }\r\n }\r\n\r\n const updateEntries = async(methodname, args) => {\r\n try {\r\n const response = await call([{\r\n methodname,\r\n args,\r\n }])[0];\r\n return response;\r\n } catch (error) {\r\n window.console.error('updating Entries:', error);\r\n throw error;\r\n }\r\n };\r\n\r\n /**\r\n * Extracts the resource ID from a comment ID and updates entries if submission is pending\r\n * @param {string} id - The ID string to extract resource ID from, expected format: 'prefix_number'\r\n * @description This function:\r\n * 1. Parses the resource ID from the given ID string\r\n * 2. If resource ID exists and there's a pending submission:\r\n * - Resets the pending submission flag\r\n * - Constructs arguments with context info\r\n * - Calls updateEntries to process the PDF annotation\r\n */\r\n function extractResourceId(id) {\r\n\r\n // Prevent updating ID while editing a existing entry.\r\n if (buttonElement === 'Save') {\r\n\r\n pendingSubmit = false;\r\n return;\r\n }\r\n\r\n let resourceId = parseInt(id?.split('_')[1]);\r\n if (resourceId && pendingSubmit) {\r\n pendingSubmit = false;\r\n let args = {\r\n cmid: M.cfg.contextInstanceId,\r\n userid: M.cfg.userId ?? 0,\r\n courseid: M.cfg.courseId,\r\n modulename: moduleName,\r\n resourceid: resourceId\r\n };\r\n updateEntries('cursive_update_pdf_annote_id', args);\r\n }\r\n }\r\n\r\n /**\r\n * Retrieves and displays cursive analytics for a given resource\r\n * @param {number} userid - The ID of the user\r\n * @param {number} resourceid - The ID of the resource to get analytics for\r\n * @param {number} cmid - The course module ID\r\n * @param {HTMLElement} place - The DOM element where analytics should be placed\r\n * @description This function:\r\n * 1. Makes an AJAX call to get forum comment data\r\n * 2. Creates and inserts analytics/replay buttons\r\n * 3. Sets up analytics events and modal functionality\r\n * 4. Handles both API key and non-API key scenarios\r\n */\r\n function getCursiveAnalytics(userid, resourceid, cmid, place) {\r\n let args = {id: resourceid, modulename: \"pdfannotator\", cmid: cmid};\r\n let methodname = 'cursive_get_forum_comment_link';\r\n let com = call([{methodname, args}]);\r\n com[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n\r\n var filepath = '';\r\n if (data.data.filename) {\r\n filepath = data.data.filename;\r\n }\r\n\r\n let analyticButtonDiv = document.createElement('div');\r\n let analyticsColumn = document.createElement('td');\r\n\r\n if (!hasApiKey) {\r\n analyticButtonDiv.append(replayButton(resourceid));\r\n } else {\r\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, resourceid));\r\n }\r\n\r\n analyticButtonDiv.dataset.region = \"analytic-div\" + userid;\r\n analyticsColumn.append(analyticButtonDiv);\r\n place.insertAdjacentElement('afterend', analyticsColumn);\r\n\r\n let myEvents = new AnalyticEvents();\r\n var context = {\r\n tabledata: data.data,\r\n formattime: myEvents.formatedTime(data.data),\r\n page: scoreSetting,\r\n userid: resourceid,\r\n apikey: hasApiKey\r\n };\r\n\r\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\r\n myEvents.createModal(resourceid, context, '', replayInstances, authIcon);\r\n myEvents.analytics(resourceid, templates, context, '', replayInstances, authIcon);\r\n myEvents.checkDiff(resourceid, data.data.file_id, '', replayInstances);\r\n myEvents.replyWriting(resourceid, filepath, '', replayInstances);\r\n\r\n });\r\n com[0].fail((error) => {\r\n window.console.error('Error getting cursive config:', error);\r\n });\r\n }\r\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":";;;;;;;gWA6BoB,CAACA,aAAcC,SAAUC,UAAWC,gBAC9CC,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,OAChDC,SAASC,eAAe,UAAYR,KAAKS,UAAYH,MAC9C,KACRI,OAAMC,GAAKb,OAAOc,QAAQC,MAAMF,YAEhC,OAGPG,UAAYP,SAASQ,cAAc,iCACjCC,cAAgBT,SAASQ,cAAc,kCAE7CR,SAASU,iBAAiB,kBA4FJN,GACE,kBAAhBA,EAAEO,OAAOC,KACTC,aAAaC,WAAW,aACxBC,cAAgBX,EAAEO,OAAOK,MACzBC,eAAgB,GAEA,kBAAhBb,EAAEO,OAAOC,KACTC,aAAaC,WAAW,aACxBG,eAAgB,YAnGlBC,WAAalB,SAASmB,KAAKP,GAAGQ,MAAM,KAAK,OAC3CH,eAAgB,EAChBF,cAAgB,MAEhBR,UAAW,CACM,IAAIc,kBAAiB,8BAC9Bd,MAAAA,wCAAAA,UAAWe,2CAAXC,qBAAsBX,aAwHPA,OAGD,SAAlBG,0BAEAE,eAAgB,OAIhBO,WAAaC,SAASb,MAAAA,UAAAA,GAAIQ,MAAM,KAAK,OACrCI,YAAcP,cAAe,CAC7BA,eAAgB,EAlCFS,OAAMC,WAAYC,kBAET,cAAK,CAAC,CACzBD,WAAAA,WACAC,KAAAA,QACA,GAEN,MAAOtB,aACLf,OAAOc,QAAQC,MAAM,oBAAqBA,OACpCA,QAiCNuB,CAAc,+BAPH,CACPC,KAAMC,EAAEC,IAAIC,kBACZ5C,OAAQ0C,EAAEC,IAAIE,QAAU,EACxBC,SAAUJ,EAAEC,IAAII,SAChBC,WAAYnB,WACZoB,WAAYd,cAxIZe,CAAkBhC,UAAUe,UAAUV,OAIrC4B,QAAQjC,UAAW,CACxBkC,SAAS,EACTC,WAAW,OAIfjC,cAAe,KACXkC,SAAW3C,SAAS4C,cAAc,MACtCD,SAASE,YAAc,YACVpC,cAAcD,cAAc,2BAClCsC,sBAAsB,WAAYH,mBAcpBlC,qBACfsC,KAAOtC,cAAcuC,iBAAiB,cACtCC,OAAS,IAAIC,IAAI3D,OAAO4D,SAASC,MAAMC,aAAaC,IAAI,UAE9DP,KAAKQ,SAAQC,mFACHC,KAAO,CACTC,KAAMF,IAAIhD,cAAc,mBACxBmD,KAAMH,IAAIhD,cAAc,mBACxBoD,KAAMJ,IAAIhD,cAAc,oBAGtBqD,MAAQ,CACVC,wBAAOL,KAAKC,iCAALK,UAAWvD,cAAc,KAChCwD,yBAAOP,KAAKE,kCAALM,WAAWzD,cAAc,KAChC0D,yBAAOT,KAAKG,kCAALO,WAAW3D,cAAc,MAI9B4D,8BAAYP,MAAMC,0CAAOV,KAC3B,IAAIF,IAAIW,MAAMC,MAAMV,MAAMC,aAAaC,IAAI,UAAY,KACrDxB,0BAAO+B,MAAMC,4CAAOV,KACtB,IAAIF,IAAIW,MAAMC,MAAMV,MAAMC,aAAaC,IAAI,MAAQvB,EAAEC,IAAIC,sBAEzDC,OAAS,KACTmC,SAAW,YAEPpB,YACC,oBACDoB,SAAWR,MAAMG,gBAEhB,kBACDK,SAAWR,MAAMK,oBAGjBhC,OAAS7C,4BAGbgF,+BAAAC,UAAUlB,SAENlB,OAAS,IAAIgB,IAAImB,SAASjB,MAAMC,aAAaC,IAAI,MACnD,MAAOlD,GACLb,OAAOc,QAAQkE,KAAK,0BAA2BnE,aAqFlCf,OAAQiD,WAAYR,KAAM0C,WAC/C5C,KAAO,CAAChB,GAAI0B,WAAYD,WAAY,eAAgBP,KAAMA,MAC1DH,WAAa,iCACb8C,KAAM,cAAK,CAAC,CAAC9C,WAAAA,WAAYC,KAAAA,QAC7B6C,IAAI,GAAGC,MAAK,SAASC,UACbC,KAAOC,KAAKC,MAAMH,MAElBjF,SAAW,GACXkF,KAAKA,KAAKG,WACVrF,SAAWkF,KAAKA,KAAKG,cAGrBC,kBAAoBhF,SAAS4C,cAAc,OAC3CqC,gBAAkBjF,SAAS4C,cAAc,MAExCxD,UAGD4F,kBAAkBE,QAAO,4BAAeN,KAAKA,KAAKO,aAAc7C,aAFhE0C,kBAAkBE,QAAO,0BAAa5C,aAK1C0C,kBAAkBI,QAAQC,OAAS,eAAiBhG,OACpD4F,gBAAgBC,OAAOF,mBACvBR,MAAM1B,sBAAsB,WAAYmC,qBAEpCK,SAAW,IAAIC,6BACfC,QAAU,CACVC,UAAWb,KAAKA,KAChBc,WAAYJ,SAASK,aAAaf,KAAKA,MACvCgB,KAAM1G,aACNG,OAAQiD,WACRuD,OAAQzG,eAGR0G,SAAWR,SAASS,iBAAiBnB,KAAKA,KAAKoB,WAAYpB,KAAKA,KAAKqB,MAAO/G,cAChFoG,SAASY,YAAY5D,WAAYkD,QAAS,GAAIlG,gBAAiBwG,UAC/DR,SAASa,UAAU7D,WAAY8D,mBAAWZ,QAAS,GAAIlG,gBAAiBwG,UACxER,SAASe,UAAU/D,WAAYsC,KAAKA,KAAK0B,QAAS,GAAIhH,iBACtDgG,SAASiB,aAAajE,WAAY5C,SAAU,GAAIJ,oBAGpDmF,IAAI,GAAG+B,MAAMlG,QACTf,OAAOc,QAAQC,MAAM,gCAAiCA,UA3HtDmG,CAAoBvE,OAAQkC,UAAWtC,KAAM2B,KAAKC,SA1DtDgD,CAAgBjG"} \ 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..5b06ff1f 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/append_submissions_table\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\r\n \"jquery\",\r\n \"core/ajax\",\r\n \"core/str\",\r\n \"core/templates\",\r\n \"./replay\",\r\n './analytic_button',\r\n './replay_button',\r\n './analytic_events',\r\n 'core/str'], function(\r\n $,\r\n AJAX,\r\n str,\r\n templates,\r\n Replay,\r\n analyticButton,\r\n replayButton,\r\n AnalyticEvents,\r\n Str\r\n) {\r\n const replayInstances = {};\r\n // eslint-disable-next-line camelcase\r\n window.video_playback = function(mid, filepath) {\r\n\r\n if (filepath !== '') {\r\n const replay = new Replay(\r\n 'content' + mid,\r\n filepath,\r\n 10,\r\n false,\r\n 'player_' + mid\r\n );\r\n replayInstances[mid] = replay;\r\n } else {\r\n templates.render('tiny_cursive/no_submission').then(html => {\r\n $('#content' + mid).html(html);\r\n return true;\r\n }).catch(e => window.console.error(e));\r\n }\r\n return false;\r\n\r\n };\r\n\r\n var usersTable = {\r\n init: function(scoreSetting, showcomment, hasApiKey) {\r\n str\r\n .get_strings([\r\n {key: \"confidence_threshold\", component: \"tiny_cursive\"},\r\n ]).done(function() {\r\n usersTable.appendTable(scoreSetting, hasApiKey);\r\n });\r\n },\r\n appendTable: function(scoreSetting, hasApiKey) {\r\n let subUrl = window.location.href;\r\n let parm = new URL(subUrl);\r\n let hTr = $('thead').find('tr').get()[0];\r\n\r\n Str.get_string('analytics', 'tiny_cursive')\r\n .then(analyticString => {\r\n $(hTr).find('th').eq(3).after(''\r\n + analyticString + '
' +\r\n '
');\r\n $('tbody').find(\"tr\").get().forEach(function(tr) {\r\n let tdUser = $(tr).find(\"td\").get()[0];\r\n let userid = $(tdUser).find(\"input[type='checkbox']\")?.get()[0]?.value;\r\n let cmid = parm.searchParams.get('id');\r\n // Create the table cell element and append the anchor.\r\n\r\n let args = {id: userid, modulename: \"assign\", cmid: cmid};\r\n let methodname = 'cursive_user_list_submission_stats';\r\n let com = AJAX.call([{methodname, args}]);\r\n try {\r\n com[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n var filepath = '';\r\n if (data.res.filename) {\r\n filepath = data.res.filename;\r\n }\r\n\r\n const tableCell = document.createElement('td');\r\n\r\n if (!hasApiKey) {\r\n $(tableCell).html(replayButton(userid));\r\n } else {\r\n tableCell.appendChild(analyticButton(data.res.effort_ratio, userid));\r\n }\r\n $(tr).find('td').eq(3).after(tableCell);\r\n\r\n // Get Module Name from element.\r\n let element = document.querySelector('.page-header-headings h1');\r\n // Selects the h1 element within the .page-header-headings class\r\n let textContent = element.textContent; // Extracts the text content from the h1 element\r\n\r\n let myEvents = new AnalyticEvents();\r\n var context = {\r\n tabledata: data.res,\r\n formattime: myEvents.formatedTime(data.res),\r\n moduletitle: textContent,\r\n page: scoreSetting,\r\n userid: userid,\r\n apikey: hasApiKey\r\n };\r\n\r\n let authIcon = myEvents.authorshipStatus(data.res.first_file, data.res.score, scoreSetting);\r\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\r\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\r\n myEvents.checkDiff(userid, data.res.file_id, '', replayInstances);\r\n myEvents.replyWriting(userid, filepath, '', replayInstances);\r\n\r\n }).fail(function(error) {\r\n window.console.error('AJAX request failed:', error);\r\n });\r\n } catch (error) {\r\n window.console.error('Error processing data:', error);\r\n }\r\n return com.usercomment;\r\n });\r\n return true;\r\n })\r\n .catch(error => {\r\n window.console.error('Failed to get analytics string:', error);\r\n });\r\n }\r\n };\r\n\r\n return usersTable;\r\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 diff --git a/amd/build/autosaver.min.js.map b/amd/build/autosaver.min.js.map index e0c9b4de..c5141173 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/autosaver\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author Brain Station 23 \r\n */\r\n\r\nimport {call} from 'core/ajax';\r\nimport {create} from 'core/modal_factory';\r\nimport {get_string as getString} from 'core/str';\r\nimport {save, cancel, hidden} from 'core/modal_events';\r\nimport $ from 'jquery';\r\nimport {iconUrl, iconGrayUrl, tooltipCss} from 'tiny_cursive/common';\r\nimport Autosave from 'tiny_cursive/cursive_autosave';\r\nimport DocumentView from 'tiny_cursive/document_view';\r\nimport {call as getUser} from \"core/ajax\";\r\n\r\nexport const register = (editor, interval, userId, hasApiKey, MODULES, Rubrics, submission, quizInfo, pasteSetting) => {\r\n\r\n var isStudent = !($('#body').hasClass('teacher_admin'));\r\n var intervention = $('#body').hasClass('intervention');\r\n var host = M.cfg.wwwroot;\r\n var userid = userId;\r\n var courseid = M.cfg.courseId;\r\n var editorid = editor?.id;\r\n var cmid = M.cfg.contextInstanceId;\r\n var ed = \"\";\r\n var event = \"\";\r\n var filename = \"\";\r\n var questionid = 0;\r\n var quizSubmit = $('#mod_quiz-next-nav');\r\n let assignSubmit = $('#id_submitbutton');\r\n var syncInterval = interval ? interval * 1000 : 10000; // Default: Sync Every 10s.\r\n var lastCaretPos = 1;\r\n let aiContents = [];\r\n var isFullScreen = false;\r\n var user = null;\r\n let ur = window.location.href;\r\n let parm = new URL(ur);\r\n let modulesInfo = getModulesInfo(ur, parm, MODULES);\r\n var resourceId = modulesInfo.resourceId;\r\n var modulename = modulesInfo.name;\r\n var errorAlert = true;\r\n let PASTE_SETTING = pasteSetting || 'allow';\r\n let shouldBlockPaste = false;\r\n let isPasteAllowed = false;\r\n\r\n if (ur.includes('pdfannotator')) {\r\n document.addEventListener('click', e => {\r\n if (e.target.className === \"dropdown-item comment-edit-a\") {\r\n let id = e.target.id;\r\n resourceId = id.replace('editButton', '');\r\n localStorage.setItem('isEditing', '1');\r\n }\r\n if (e.target.id === 'commentSubmit') {\r\n syncData();\r\n }\r\n });\r\n }\r\n\r\n const postOne = async(methodname, args) => {\r\n try {\r\n const response = await call([{\r\n methodname,\r\n args,\r\n }])[0];\r\n if (response) {\r\n setTimeout(() => {\r\n Autosave.updateSavingState('saved');\r\n }, 1000);\r\n }\r\n return response;\r\n } catch (error) {\r\n Autosave.updateSavingState('offline');\r\n window.console.error('Error in postOne:', error);\r\n throw error;\r\n }\r\n };\r\n\r\n getUser([{\r\n methodname: 'core_user_get_users_by_field',\r\n args: {field: 'id', values: [userid]},\r\n }])[0].done(response => {\r\n user = response[0];\r\n }).fail((ex) => {\r\n window.console.error('Error fetching user data:', ex);\r\n });\r\n\r\n assignSubmit.on('click', async function(e) {\r\n e.preventDefault();\r\n if (filename) {\r\n // eslint-disable-next-line\r\n syncData().then(() => {\r\n assignSubmit.off('click').click();\r\n });\r\n } else {\r\n assignSubmit.off('click').click();\r\n }\r\n localStorage.removeItem('lastCopyCutContent');\r\n });\r\n\r\n quizSubmit.on('click', async function(e) {\r\n e.preventDefault();\r\n if (filename) {\r\n // eslint-disable-next-line\r\n syncData().then(() => {\r\n quizSubmit.off('click').click();\r\n });\r\n } else {\r\n quizSubmit.off('click').click();\r\n }\r\n localStorage.removeItem('lastCopyCutContent');\r\n });\r\n\r\n const getModal = () => {\r\n\r\n Promise.all([\r\n getString('tiny_cursive_srcurl', 'tiny_cursive'),\r\n getString('tiny_cursive_srcurl_des', 'tiny_cursive'),\r\n getString('tiny_cursive_placeholder', 'tiny_cursive')\r\n ]).then(function([title, titledes, placeholder]) {\r\n\r\n return create({\r\n type: 'SAVE_CANCEL',\r\n title: `
${title}
\r\n ${titledes}
`,\r\n body: ``,\r\n removeOnClose: true,\r\n })\r\n .done(modal => {\r\n modal.getRoot().addClass('tiny-cursive-modal');\r\n modal.show();\r\n var lastEvent = '';\r\n\r\n modal.getRoot().on(save, function() {\r\n\r\n var number = document.getElementById(\"inputUrl\").value.trim();\r\n\r\n if (number === \"\" || number === null || number === undefined) {\r\n editor.execCommand('Undo');\r\n // eslint-disable-next-line\r\n getString('pastewarning', 'tiny_cursive').then(str => alert(str));\r\n } else {\r\n editor.execCommand('Paste');\r\n }\r\n\r\n postOne('cursive_user_comments', {\r\n modulename: modulename,\r\n cmid: cmid,\r\n resourceid: resourceId,\r\n courseid: courseid,\r\n usercomment: number,\r\n timemodified: Date.now(),\r\n editorid: editorid ? editorid : \"\"\r\n });\r\n\r\n lastEvent = 'save';\r\n modal.destroy();\r\n });\r\n modal.getRoot().on(cancel, function() {\r\n editor.execCommand('Undo');\r\n lastEvent = 'cancel';\r\n });\r\n\r\n modal.getRoot().on(hidden, function() {\r\n if (lastEvent != 'cancel' && lastEvent != 'save') {\r\n editor.execCommand('Undo');\r\n }\r\n });\r\n return modal;\r\n });\r\n }).catch(error => window.console.error(error));\r\n\r\n };\r\n\r\n const sendKeyEvent = (events, editor) => {\r\n ed = editor;\r\n event = events;\r\n\r\n filename = `${userid}_${resourceId}_${cmid}_${modulename}_attempt`;\r\n\r\n if (modulename === 'quiz') {\r\n questionid = editorid.split(':')[1].split('_')[0];\r\n filename = `${userid}_${resourceId}_${cmid}_${questionid}_${modulename}_attempt`;\r\n }\r\n\r\n if (localStorage.getItem(filename)) {\r\n let data = JSON.parse(localStorage.getItem(filename));\r\n data.push({\r\n resourceId: resourceId,\r\n key: editor.key,\r\n keyCode: editor.keyCode,\r\n event: event,\r\n courseId: courseid,\r\n unixTimestamp: Date.now(),\r\n clientId: host,\r\n personId: userid,\r\n position: ed.caretPosition,\r\n rePosition: ed.rePosition,\r\n pastedContent: editor.pastedContent,\r\n aiContent: editor.aiContent\r\n });\r\n localStorage.setItem(filename, JSON.stringify(data));\r\n } else {\r\n let data = [{\r\n resourceId: resourceId,\r\n key: editor.key,\r\n keyCode: editor.keyCode,\r\n event: event,\r\n courseId: courseid,\r\n unixTimestamp: Date.now(),\r\n clientId: host,\r\n personId: userid,\r\n position: ed.caretPosition,\r\n rePosition: ed.rePosition,\r\n pastedContent: editor.pastedContent,\r\n aiContent: editor.aiContent\r\n }];\r\n localStorage.setItem(filename, JSON.stringify(data));\r\n }\r\n };\r\n\r\n editor.on('keyUp', (editor) => {\r\n customTooltip();\r\n let position = getCaretPosition(false);\r\n editor.caretPosition = position.caretPosition;\r\n editor.rePosition = position.rePosition;\r\n sendKeyEvent(\"keyUp\", editor);\r\n });\r\n editor.on('Paste', async(e) => {\r\n customTooltip();\r\n const pastedContent = (e.clipboardData || e.originalEvent.clipboardData).getData('text');\r\n if (!pastedContent) {\r\n return;\r\n }\r\n // Trim both values for consistent comparison\r\n const trimmedPastedContent = pastedContent.trim();\r\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\r\n const isFromOwnEditor = lastCopyCutContent && trimmedPastedContent === lastCopyCutContent;\r\n\r\n if (isStudent && intervention) {\r\n\r\n if (PASTE_SETTING === 'block') {\r\n if (!isFromOwnEditor) {\r\n e.preventDefault();\r\n shouldBlockPaste = true;\r\n isPasteAllowed = false;\r\n e.stopPropagation();\r\n e.stopImmediatePropagation();\r\n getString('paste_blocked', 'tiny_cursive').then(str => {\r\n return editor.windowManager.alert(str);\r\n }).catch(error => window.console.error(error));\r\n setTimeout(() => {\r\n isPasteAllowed = true;\r\n shouldBlockPaste = false;\r\n }, 100);\r\n return;\r\n }\r\n shouldBlockPaste = false;\r\n isPasteAllowed = true;\r\n return;\r\n }\r\n if (PASTE_SETTING === 'cite_source') {\r\n if (!isFromOwnEditor) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n e.stopImmediatePropagation();\r\n getModal(e);\r\n }\r\n isPasteAllowed = true;\r\n return;\r\n }\r\n }\r\n isPasteAllowed = true;\r\n });\r\n editor.on('Redo', async(e) => {\r\n customTooltip();\r\n if (isStudent && intervention) {\r\n getModal(e);\r\n }\r\n });\r\n editor.on('keyDown', (editor) => {\r\n customTooltip();\r\n const isPasteAttempt = (editor.key === 'v' || editor.key === 'V') &&\r\n (editor.ctrlKey || editor.metaKey);\r\n if (isPasteAttempt && isStudent && intervention && PASTE_SETTING === 'block' && !isPasteAllowed) {\r\n setTimeout(() => {\r\n isPasteAllowed = true;\r\n }, 100);\r\n return;\r\n }\r\n let position = getCaretPosition();\r\n editor.caretPosition = position.caretPosition;\r\n editor.rePosition = position.rePosition;\r\n sendKeyEvent(\"keyDown\", editor);\r\n });\r\n editor.on('Cut', () => {\r\n const selectedContent = editor.selection.getContent({format: 'text'});\r\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\r\n });\r\n editor.on('Copy', () => {\r\n const selectedContent = editor.selection.getContent({format: 'text'});\r\n localStorage.setItem('lastCopyCutContent', selectedContent.trim());\r\n });\r\n editor.on('mouseDown', async(editor) => {\r\n setTimeout(() => {\r\n constructMouseEvent(editor);\r\n sendKeyEvent(\"mouseDown\", editor);\r\n }, 0);\r\n });\r\n editor.on('mouseUp', async(editor) => {\r\n setTimeout(() => {\r\n constructMouseEvent(editor);\r\n sendKeyEvent(\"mouseUp\", editor);\r\n }, 10);\r\n });\r\n editor.on('init', () => {\r\n customTooltip();\r\n localStorage.removeItem('lastCopyCutContent');\r\n });\r\n editor.on('SetContent', () => {\r\n customTooltip();\r\n });\r\n editor.on('FullscreenStateChanged', (e) => {\r\n let view = new DocumentView(user, Rubrics, submission, modulename, editor, quizInfo);\r\n isFullScreen = e.state;\r\n try {\r\n if (!e.state) {\r\n view.normalMode();\r\n } else {\r\n view.fullPageMode();\r\n }\r\n } catch (error) {\r\n if (errorAlert) {\r\n errorAlert = false;\r\n getString('fullmodeerror', 'tiny_cursive').then(str => {\r\n return editor.windowManager.alert(str);\r\n }).catch(error => window.console.error(error));\r\n }\r\n view.normalMode();\r\n window.console.error('Error ResizeEditor event:', error);\r\n }\r\n });\r\n\r\n editor.on('execcommand', function(e) {\r\n if (e.command === \"mceInsertContent\") {\r\n const contentObj = e.value;\r\n\r\n const isPaste = contentObj && typeof contentObj === 'object' && contentObj.paste === true;\r\n\r\n let insertedContent = contentObj.content || contentObj;\r\n let tempDiv = document.createElement('div');\r\n tempDiv.innerHTML = insertedContent;\r\n let text = tempDiv.textContent || tempDiv.innerText || '';\r\n let pastedText = tempDiv.textContent || tempDiv.innerText || '';\r\n\r\n let position = getCaretPosition(true);\r\n editor.caretPosition = position.caretPosition;\r\n editor.rePosition = position.rePosition;\r\n\r\n if (isPaste) {\r\n if (shouldBlockPaste) {\r\n shouldBlockPaste = false;\r\n e.preventDefault();\r\n editor.undoManager.undo();\r\n return;\r\n }\r\n const lastCopyCutContent = localStorage.getItem('lastCopyCutContent');\r\n const isFromOwnEditor = lastCopyCutContent && pastedText.trim() === lastCopyCutContent;\r\n\r\n if (isStudent && intervention && PASTE_SETTING === 'block' && !isFromOwnEditor) {\r\n isPasteAllowed = false;\r\n editor.undoManager.undo();\r\n return;\r\n }\r\n\r\n sendKeyEvent(\"Paste\", {\r\n key: \"v\",\r\n keyCode: 86,\r\n caretPosition: editor.caretPosition,\r\n rePosition: editor.rePosition,\r\n pastedContent: pastedText,\r\n srcElement: {baseURI: window.location.href}\r\n });\r\n } else {\r\n aiContents.push(text);\r\n\r\n sendKeyEvent(\"aiInsert\", {\r\n key: \"ai\",\r\n keyCode: 0,\r\n caretPosition: editor.caretPosition,\r\n rePosition: editor.rePosition,\r\n aiContent: text,\r\n srcElement: {baseURI: window.location.href}\r\n });\r\n }\r\n }\r\n });\r\n\r\n editor.on('input', function(e) {\r\n let position = getCaretPosition(true);\r\n editor.caretPosition = position.caretPosition;\r\n editor.rePosition = position.rePosition;\r\n let aiContent = e.data;\r\n\r\n if (e.inputType === 'insertReplacementText' || (e.inputType === 'insertText' && aiContent && aiContent.length > 1)) {\r\n\r\n aiContents.push(aiContent);\r\n\r\n e.key = \"ai\";\r\n e.keyCode = 0;\r\n e.caretPosition = position.caretPosition;\r\n e.rePosition = position.rePosition;\r\n e.aiContent = aiContent;\r\n\r\n sendKeyEvent(\"aiInsert\", e);\r\n }\r\n });\r\n\r\n\r\n /**\r\n * Constructs a mouse event object with caret position and button information\r\n * @param {Object} editor - The TinyMCE editor instance\r\n * @function constructMouseEvent\r\n * @description Sets caret position, reposition, key and keyCode properties on the editor object based on current mouse state\r\n */\r\n function constructMouseEvent(editor) {\r\n let position = getCaretPosition(false);\r\n editor.caretPosition = position.caretPosition;\r\n editor.rePosition = position.rePosition;\r\n editor.key = getMouseButton(editor);\r\n editor.keyCode = editor.button;\r\n }\r\n\r\n /**\r\n * Gets the string representation of a mouse button based on its numeric value\r\n * @param {Object} editor - The editor object containing button information\r\n * @returns {string} The string representation of the mouse button ('left', 'middle', or 'right')\r\n */\r\n function getMouseButton(editor) {\r\n\r\n switch (editor.button) {\r\n case 0:\r\n return 'left';\r\n case 1:\r\n return 'middle';\r\n case 2:\r\n return 'right';\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * Gets the current caret position in the editor\r\n * @param {boolean} skip - If true, returns the last known caret position instead of calculating a new one\r\n * @returns {Object} Object containing:\r\n * - caretPosition: Sequential position number stored in session\r\n * - rePosition: Absolute character offset from start of content\r\n * @throws {Error} Logs warning to console if error occurs during calculation\r\n */\r\n function getCaretPosition(skip = false) {\r\n try {\r\n if (!editor || !editor.selection) {\r\n return {caretPosition: 0, rePosition: 0};\r\n }\r\n\r\n const range = editor.selection.getRng();\r\n const body = editor.getBody();\r\n\r\n // Create a range from start of document to current caret\r\n const preCaretRange = range.cloneRange();\r\n preCaretRange.selectNodeContents(body);\r\n preCaretRange.setEnd(range.endContainer, range.endOffset);\r\n\r\n const fragment = preCaretRange.cloneContents();\r\n const tempDiv = document.createElement('div');\r\n tempDiv.appendChild(fragment);\r\n let textBeforeCursor = tempDiv.innerText || '';\r\n\r\n const endContainer = range.endContainer;\r\n const endOffset = range.endOffset;\r\n\r\n if (endOffset === 0 &&\r\n endContainer.nodeType === Node.ELEMENT_NODE &&\r\n editor.dom.isBlock(endContainer) &&\r\n endContainer.previousSibling) {\r\n textBeforeCursor += '\\n';\r\n }\r\n const blockElements = tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, li');\r\n let emptyBlockCount = 0;\r\n blockElements.forEach(block => {\r\n const text = block.innerText || block.textContent || '';\r\n if (text.trim() === '' && block.childNodes.length === 1 &&\r\n block.childNodes[0].nodeName === 'BR') {\r\n emptyBlockCount++;\r\n }\r\n });\r\n\r\n // Add newlines for empty blocks (these represent Enter presses that created empty lines)\r\n if (emptyBlockCount > 0) {\r\n textBeforeCursor += '\\n'.repeat(emptyBlockCount);\r\n }\r\n\r\n const absolutePosition = textBeforeCursor.length;\r\n\r\n if (skip) {\r\n return {\r\n caretPosition: lastCaretPos,\r\n rePosition: absolutePosition\r\n };\r\n }\r\n // Increment sequential caretPosition\r\n const storageKey = `${userid}_${resourceId}_${cmid}_position`;\r\n let storedPos = parseInt(sessionStorage.getItem(storageKey), 10);\r\n if (isNaN(storedPos)) {\r\n storedPos = 0;\r\n }\r\n storedPos++;\r\n lastCaretPos = storedPos;\r\n sessionStorage.setItem(storageKey, storedPos);\r\n\r\n return {\r\n caretPosition: storedPos,\r\n rePosition: absolutePosition\r\n };\r\n\r\n } catch (e) {\r\n window.console.warn('Error getting caret position:', e);\r\n return {caretPosition: lastCaretPos || 1, rePosition: 0};\r\n }\r\n }\r\n\r\n\r\n /**\r\n * Synchronizes data from localStorage to server\r\n * @async\r\n * @function SyncData\r\n * @description Retrieves stored keypress data from localStorage and sends it to server\r\n * @returns {Promise} Returns response from server if data exists and is successfully sent\r\n * @throws {Error} Logs error to console if data submission fails\r\n */\r\n async function syncData() {\r\n checkIsPdfAnnotator();\r\n let data = localStorage.getItem(filename);\r\n\r\n if (!data || data.length === 0) {\r\n return;\r\n } else {\r\n localStorage.removeItem(filename);\r\n editor.fire('change');\r\n let originalText = editor.getContent({format: 'text'});\r\n if (!originalText) {\r\n originalText = getRawText(editor);\r\n }\r\n try {\r\n Autosave.updateSavingState('saving');\r\n // eslint-disable-next-line\r\n return await postOne('cursive_write_local_to_json', {\r\n key: ed.key,\r\n event: event,\r\n keyCode: ed.keyCode,\r\n resourceId: resourceId,\r\n cmid: cmid,\r\n modulename: modulename,\r\n editorid: editorid,\r\n \"json_data\": data,\r\n originalText: originalText\r\n });\r\n } catch (error) {\r\n window.console.error('Error submitting data:', error);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gets the raw text content from a TinyMCE editor iframe\r\n * @param {Object} editor - The TinyMCE editor instance\r\n * @returns {string} The raw text content of the editor body, or empty string if not found\r\n * @description Attempts to get the raw text content from the editor's iframe body by:\r\n * 1. Getting the editor ID\r\n * 2. Finding the associated iframe element\r\n * 3. Accessing the iframe's document body\r\n * 4. Returning the text content\r\n * Returns empty string if any step fails\r\n */\r\n function getRawText(editor) {\r\n let editorId = editor?.id;\r\n if (editorId) {\r\n let iframe = document.querySelector(`#${editorId}_ifr`);\r\n let iframeBody = iframe.contentDocument?.body || iframe.contentWindow?.document?.body;\r\n return iframeBody?.textContent;\r\n }\r\n return \"\";\r\n }\r\n\r\n /**\r\n * Sets up custom tooltip functionality for the Cursive icon\r\n * Initializes tooltip text, positions the icon in the menubar,\r\n * and sets up mouse event handlers for showing/hiding the tooltip\r\n * @function customTooltip\r\n */\r\n function customTooltip() {\r\n try {\r\n const tooltipText = getTooltipText();\r\n const menubarDiv = document.querySelectorAll('div[role=\"menubar\"].tox-menubar');\r\n let classArray = [];\r\n\r\n if (menubarDiv.length) {\r\n menubarDiv.forEach(function(element, index) {\r\n index += 1;\r\n let className = 'cursive-menu-' + index;\r\n element.classList.add(className);\r\n classArray.push(className);\r\n });\r\n }\r\n\r\n const cursiveIcon = document.createElement('img');\r\n cursiveIcon.src = hasApiKey ? iconUrl : iconGrayUrl;\r\n\r\n cursiveIcon.setAttribute('class', 'tiny_cursive_StateButton');\r\n cursiveIcon.style.display = 'inline-block';\r\n\r\n cursiveState(cursiveIcon, menubarDiv, classArray);\r\n\r\n for (let index in classArray) {\r\n const elementId = \"tiny_cursive_StateIcon\" + index;\r\n const tooltipId = `tiny_cursive_tooltip${index}`;\r\n\r\n tooltipText.then((text) => {\r\n return setTooltip(text, document.querySelector(`#${elementId}`), tooltipId);\r\n }).catch(error => window.console.error(error));\r\n\r\n $(`#${elementId}`).on('mouseenter', function() {\r\n $(this).css('position', 'relative');\r\n $(`#${tooltipId}`).css(tooltipCss);\r\n });\r\n\r\n $(`#${elementId}`).on('mouseleave', function() {\r\n $(`#${tooltipId}`).css('display', 'none');\r\n });\r\n }\r\n } catch (error) {\r\n window.console.error('Error setting up custom tooltip:', error);\r\n }\r\n }\r\n\r\n /**\r\n * Retrieves tooltip text strings from language files\r\n * @async\r\n * @function getTooltipText\r\n * @returns {Promise} Object containing buttonTitle and buttonDes strings\r\n */\r\n async function getTooltipText() {\r\n const [\r\n buttonTitle,\r\n buttonDes,\r\n ] = await Promise.all([\r\n getString('cursive:state:active', 'tiny_cursive'),\r\n getString('cursive:state:active:des', 'tiny_cursive'),\r\n ]);\r\n return {buttonTitle, buttonDes};\r\n }\r\n\r\n /**\r\n * Updates the Cursive icon state and positions it in the menubar\r\n * @param {HTMLElement} cursiveIcon - The Cursive icon element to modify\r\n * @param {HTMLElement} menubarDiv - The menubar div element\r\n * @param {Array} classArray - Array of class names for the menubar div elements\r\n */\r\n function cursiveState(cursiveIcon, menubarDiv, classArray) {\r\n if (!menubarDiv) {\r\n return;\r\n }\r\n\r\n for (let index in classArray) {\r\n const rightWrapper = document.createElement('div');\r\n const imgWrapper = document.createElement('span');\r\n const iconClone = cursiveIcon.cloneNode(true);\r\n const targetMenu = document.querySelector('.' + classArray[index]);\r\n let elementId = \"tiny_cursive_StateIcon\" + index;\r\n\r\n rightWrapper.style.cssText = `\r\n margin-left: auto;\r\n display: flex;\r\n align-items: center;\r\n `;\r\n\r\n imgWrapper.id = elementId;\r\n imgWrapper.style.marginLeft = '.2rem';\r\n imgWrapper.appendChild(iconClone);\r\n rightWrapper.appendChild(imgWrapper);\r\n\r\n let moduleIds = {\r\n resourceId: resourceId,\r\n cmid: cmid,\r\n modulename: modulename,\r\n questionid: questionid,\r\n userid: userid,\r\n courseid: courseid};\r\n // Document mode, other modules single editor instances\r\n if (isFullScreen && (modulename === 'assign' || modulename === 'forum'\r\n || modulename === 'lesson')) {\r\n let existsElement = document.querySelector('.tox-menubar[class*=\"cursive-menu-\"] > div');\r\n if (existsElement) {\r\n existsElement.remove();\r\n }\r\n\r\n if (!document.querySelector(`#${elementId}`)) {\r\n rightWrapper.style.marginTop = '3px';\r\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\r\n }\r\n\r\n Autosave.destroyInstance();\r\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\r\n } else if (isFullScreen && modulename === 'quiz') { // Document mode, quiz multiple editor instances\r\n let existingElement = editor.container?.childNodes[1]?.childNodes[0]?.childNodes[0]?.childNodes[7];\r\n let newHeader = editor.container?.childNodes[0];\r\n if (existingElement) {\r\n existingElement.remove();\r\n }\r\n\r\n if (newHeader && !newHeader.querySelector(`span[id*=tiny_cursive_StateIcon]`)) {\r\n rightWrapper.style.marginTop = '3px';\r\n document.querySelector('#tiny_cursive-fullpage-right-wrapper').prepend(rightWrapper);\r\n }\r\n Autosave.destroyInstance();\r\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\r\n } else { // Regular view\r\n let menubar = editor?.container?.children[0]?.childNodes[0]?.childNodes[0];\r\n\r\n if (targetMenu && !targetMenu.querySelector(`#${elementId}`)) {\r\n targetMenu.appendChild(rightWrapper);\r\n }\r\n // Regular view, multiple editor instances\r\n if (modulename === 'quiz' && menubar) {\r\n let wrapper = menubar.querySelector('span[id*=\"tiny_cursive_StateIcon\"]');\r\n\r\n if (wrapper) {\r\n Autosave.destroyInstance();\r\n Autosave.getInstance(editor, wrapper?.parentElement, moduleIds, isFullScreen);\r\n }\r\n } else {\r\n Autosave.destroyInstance();\r\n Autosave.getInstance(editor, rightWrapper, moduleIds, isFullScreen);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Sets up tooltip content and styling for the Cursive icon\r\n * @param {Object} text - Object containing tooltip text strings\r\n * @param {string} text.buttonTitle - Title text for the tooltip\r\n * @param {string} text.buttonDes - Description text for the tooltip\r\n * @param {HTMLElement} cursiveIcon - The Cursive icon element to attach tooltip to\r\n * @param {string} tooltipId - ID for the tooltip element\r\n */\r\n function setTooltip(text, cursiveIcon, tooltipId) {\r\n\r\n if (document.querySelector(`#${tooltipId}`)) {\r\n return;\r\n }\r\n if (cursiveIcon) {\r\n\r\n const tooltipSpan = document.createElement('span');\r\n const description = document.createElement('span');\r\n const linebreak = document.createElement('br');\r\n const tooltipTitle = document.createElement('strong');\r\n\r\n tooltipSpan.style.display = 'none';\r\n tooltipTitle.textContent = text.buttonTitle;\r\n tooltipTitle.style.fontSize = '16px';\r\n tooltipTitle.style.fontWeight = 'bold';\r\n description.textContent = text.buttonDes;\r\n description.style.fontSize = '14px';\r\n\r\n tooltipSpan.id = tooltipId;\r\n tooltipSpan.classList.add(`shadow`);\r\n tooltipSpan.appendChild(tooltipTitle);\r\n tooltipSpan.appendChild(linebreak);\r\n tooltipSpan.appendChild(description);\r\n cursiveIcon.appendChild(tooltipSpan);\r\n }\r\n }\r\n\r\n /**\r\n * Extracts module information from URL parameters\r\n * @param {string} ur - The base URL to analyze\r\n * @param {URL} parm - URL object containing search parameters\r\n * @param {Array} MODULES - Array of valid module names to check against\r\n * @returns {Object|boolean} Object containing resourceId and module name if found, false if no valid module\r\n */\r\n function getModulesInfo(ur, parm, MODULES) {\r\n fetchStrings();\r\n\r\n if (!MODULES.some(module => ur.includes(module))) {\r\n return false;\r\n }\r\n\r\n if (ur.includes(\"forum\") && !ur.includes(\"assign\")) {\r\n resourceId = parm.searchParams.get('edit');\r\n } else {\r\n resourceId = parm.searchParams.get('attempt');\r\n }\r\n\r\n if (resourceId === null) {\r\n resourceId = 0;\r\n }\r\n\r\n for (const module of MODULES) {\r\n if (ur.includes(module)) {\r\n modulename = module;\r\n if (module === \"lesson\" || module === \"assign\") {\r\n resourceId = cmid;\r\n } else if (module === \"oublog\") {\r\n resourceId = 0;\r\n }\r\n break;\r\n }\r\n }\r\n\r\n checkIsPdfAnnotator();\r\n\r\n return {resourceId: resourceId, name: modulename};\r\n }\r\n\r\n /**\r\n * Fetches and caches localized strings used in the UI\r\n * @function fetchStrings\r\n * @description Retrieves strings for sidebar titles and document sidebar elements if not already cached in localStorage\r\n * Uses Promise.all to fetch multiple strings in parallel for better performance\r\n * Stores the fetched strings in localStorage under 'sbTitle' and 'docSideBar' keys\r\n */\r\n function fetchStrings() {\r\n if (!localStorage.getItem('sbTitle')) {\r\n Promise.all([\r\n getString('assignment', 'tiny_cursive'),\r\n getString('discussion', 'tiny_cursive'),\r\n getString('pluginname', 'mod_quiz'),\r\n getString('pluginname', 'mod_lesson'),\r\n getString('description', 'tiny_cursive'),\r\n ]).then(function(strings) {\r\n return localStorage.setItem('sbTitle', JSON.stringify(strings));\r\n }).catch(error => window.console.error(error));\r\n }\r\n if (!localStorage.getItem('docSideBar')) {\r\n Promise.all([\r\n getString('details', 'tiny_cursive'),\r\n getString('student_info', 'tiny_cursive'),\r\n getString('progress', 'tiny_cursive'),\r\n getString('description', 'tiny_cursive'),\r\n getString('replyingto', 'tiny_cursive'),\r\n getString('answeringto', 'tiny_cursive'),\r\n getString('importantdates', 'tiny_cursive'),\r\n getString('rubrics', 'tiny_cursive'),\r\n getString('submission_status', 'tiny_cursive'),\r\n getString('status', 'tiny_cursive'),\r\n getString('draft', 'tiny_cursive'),\r\n getString('draftnot', 'tiny_cursive'),\r\n getString('last_modified', 'tiny_cursive'),\r\n getString('gradings', 'tiny_cursive'),\r\n getString('gradenot', 'tiny_cursive'),\r\n getString('word_count', 'tiny_cursive'),\r\n getString('timeleft', 'tiny_cursive'),\r\n getString('nolimit', 'tiny_cursive'),\r\n getString('name', 'tiny_cursive'),\r\n getString('userename', 'tiny_cursive'),\r\n getString('course', 'tiny_cursive'),\r\n getString('opened', 'tiny_cursive'),\r\n getString('due', 'tiny_cursive'),\r\n getString('overdue', 'tiny_cursive'),\r\n getString('remaining', 'tiny_cursive'),\r\n getString('savechanges', 'tiny_cursive'),\r\n getString('subjectnot', 'tiny_cursive'),\r\n getString('remaining', 'tiny_cursive'),\r\n ]).then(function(strings) {\r\n return localStorage.setItem('docSideBar', JSON.stringify(strings));\r\n }).catch(error => window.console.error(error));\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Checks if the current page is a PDF annotator and updates the resourceId accordingly\r\n * @function checkIsPdfAnnotator\r\n * @description Checks if URL contains 'pdfannotator' and sets resourceId based on editor ID and editing state:\r\n * - If editing an existing annotation (editor.id !== 'id_pdfannotator_content' and isEditing is true):\r\n * Sets resourceId to the annotation ID extracted from editor.id\r\n * - Otherwise: Sets resourceId to 0\r\n */\r\n function checkIsPdfAnnotator() {\r\n if (ur.includes('pdfannotator')) {\r\n if (editor.id !== 'id_pdfannotator_content' && parseInt(localStorage.getItem('isEditing'))) {\r\n resourceId = parseInt(editor?.id.replace('editarea', ''));\r\n } else {\r\n resourceId = 0;\r\n }\r\n }\r\n }\r\n\r\n window.addEventListener('unload', () => {\r\n syncData();\r\n });\r\n\r\n setInterval(syncData, syncInterval);\r\n};\r\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.map b/amd/build/common.min.js.map index ddc7a5f5..da181a83 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/common\r\n * @category TinyMCE Editor\r\n * @copyright 2025 CTI \r\n * @author Brain Station 23 \r\n */\r\n\r\n\r\nconst component = 'tiny_cursive';\r\n\r\nexport default {\r\n component,\r\n pluginName: `${component}/plugin`,\r\n iconUrl: M.util.image_url('cursive', 'tiny_cursive'),\r\n iconSaving: M.util.image_url('rotate', 'tiny_cursive'),\r\n iconGrayUrl: M.util.image_url('cursive_gray', 'tiny_cursive'),\r\n tooltipCss: {\r\n display: 'block',\r\n position: 'absolute',\r\n transform: 'translateX(-100%)',\r\n backgroundColor: 'white',\r\n color: 'black',\r\n border: '1px solid #ccc',\r\n marginBottom: '6px',\r\n padding: '10px',\r\n textAlign: 'justify',\r\n minWidth: '200px',\r\n borderRadius: '1px',\r\n pointerEvents: 'none',\r\n zIndex: 10000\r\n }\r\n};\r\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.map b/amd/build/cursive_autosave.min.js.map index f0ea83bb..aebc38e8 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * TODO describe module cursive_autosave\r\n *\r\n * @module tiny_cursive/cursive_autosave\r\n * @copyright 2025 Cursive Technology, Inc. \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport templates from 'core/templates';\r\nimport {call} from 'core/ajax';\r\nimport Icons from 'tiny_cursive/svg_repo';\r\nimport {get_string as getString} from 'core/str';\r\n\r\nexport default class CursiveAutosave {\r\n\r\n static instance = null;\r\n\r\n\r\n constructor(editor, rightWrapper, modules, isFullScreen) {\r\n if (CursiveAutosave.instance) {\r\n return CursiveAutosave.instance;\r\n }\r\n\r\n this.editor = editor;\r\n this.module = modules;\r\n this.savingState = '';\r\n this.rightWrapper = rightWrapper;\r\n this.isFullScreen = isFullScreen;\r\n // Bind methods that will be used as event listener\r\n this.fetchSavedContent = this.fetchSavedContent.bind(this);\r\n this.handleEscapeKey = this.handleEscapeKey.bind(this);\r\n this._savingTimer = null;\r\n CursiveAutosave.instance = this;\r\n this.fetchStrings();\r\n }\r\n\r\n static getInstance(editor, rightWrapper, modules, isFullScreen) {\r\n if (!this.instance) {\r\n this.instance = new CursiveAutosave(editor, rightWrapper, modules, isFullScreen);\r\n }\r\n this.instance.isFullScreen = isFullScreen;\r\n const hasState = modules.modulename === 'quiz'\r\n ? document.querySelector(`#tiny_cursive_savingState${modules.questionid}`)\r\n : document.querySelector('#tiny_cursive_savingState');\r\n if (!hasState) {\r\n this.instance.init();\r\n }\r\n\r\n return this.instance;\r\n }\r\n\r\n init() {\r\n const stateWrapper = this.cursiveSavingState(this.savingState);\r\n stateWrapper.classList.add('tiny_cursive_savingState', 'btn');\r\n if (this.module.modulename === 'quiz') {\r\n stateWrapper.id = `tiny_cursive_savingState${this.module.questionid}`;\r\n } else {\r\n stateWrapper.id = 'tiny_cursive_savingState';\r\n }\r\n\r\n this.rightWrapper.prepend(stateWrapper);\r\n stateWrapper.addEventListener('click', this.fetchSavedContent);\r\n }\r\n\r\n destroy() {\r\n CursiveAutosave.instance = null;\r\n }\r\n\r\n static destroyInstance() {\r\n if (this.instance) {\r\n this.instance.destroy();\r\n this.instance = null;\r\n }\r\n }\r\n\r\n /**\r\n * Creates a wrapper div containing an icon and text to display the saving state\r\n * @param {string} state - The current saving state ('saving', 'saved', or 'offline')\r\n * @returns {HTMLElement} A div element containing the state icon and text\r\n * @description Creates and returns a div element with an icon and text span to show the current saving state.\r\n * The icon and text are updated based on the provided state parameter.\r\n */\r\n cursiveSavingState(state) {\r\n let wrapperDiv = document.createElement('div');\r\n let textSpan = document.createElement('span');\r\n let button = document.createElement('button');\r\n let iconSpan = document.createElement('span');\r\n\r\n button.style.padding = '.3rem';\r\n textSpan.style.fontSize = '0.75rem';\r\n textSpan.style.color = 'gray';\r\n\r\n if (this.module.modulename === 'quiz') {\r\n iconSpan.id = `CursiveCloudIcon${this.module.questionid}`;\r\n textSpan.id = `CursiveStateText${this.module.questionid}`;\r\n } else {\r\n iconSpan.id = 'CursiveCloudIcon';\r\n textSpan.id = 'CursiveStateText';\r\n }\r\n if (state) {\r\n textSpan.textContent = this.getStateText(state);\r\n iconSpan.innerHTML = this.getStateIcon(state);\r\n }\r\n\r\n wrapperDiv.style.verticalAlign = 'middle';\r\n\r\n wrapperDiv.appendChild(iconSpan);\r\n wrapperDiv.appendChild(textSpan);\r\n button.appendChild(wrapperDiv);\r\n\r\n return button;\r\n }\r\n\r\n /**\r\n * Updates the saving state icon and text in the editor\r\n * @param {string} state - The state to update to ('saving', 'saved', or 'offline')\r\n * @description Updates the global saving state and modifies the UI elements to reflect the new state\r\n */\r\n static updateSavingState(state) {\r\n const instance = this.instance;\r\n instance.savingState = state;\r\n let stateWrapper = null;\r\n if (instance.module.modulename === 'quiz') {\r\n stateWrapper = document.querySelector(`#tiny_cursive_savingState${instance.module.questionid}`);\r\n } else {\r\n stateWrapper = document.querySelector('#tiny_cursive_savingState');\r\n }\r\n\r\n let iconSpan = '';\r\n let stateTextEl = '';\r\n\r\n if (!stateWrapper) {\r\n return;\r\n }\r\n\r\n if (instance.module.modulename === 'quiz') {\r\n iconSpan = stateWrapper.querySelector(`#CursiveCloudIcon${instance.module.questionid}`);\r\n stateTextEl = stateWrapper.querySelector(`#CursiveStateText${instance.module.questionid}`);\r\n } else {\r\n iconSpan = stateWrapper.querySelector('#CursiveCloudIcon');\r\n stateTextEl = stateWrapper.querySelector('#CursiveStateText');\r\n }\r\n\r\n if (stateTextEl && iconSpan) {\r\n stateTextEl.textContent = instance.getStateText(state);\r\n iconSpan.innerHTML = instance.getStateIcon(state);\r\n }\r\n\r\n\r\n if (instance._savingTimer) {\r\n clearTimeout(instance._savingTimer);\r\n }\r\n\r\n if (state === 'saved' && stateTextEl) {\r\n instance._savingTimer = setTimeout(() => {\r\n stateTextEl.textContent = '';\r\n }, 5000);\r\n }\r\n }\r\n\r\n /**\r\n * Gets the display text for a given saving state\r\n * @param {string} state - The state to get text for ('saving', 'saved', or 'offline')\r\n * @returns {string} The text to display for the given state\r\n * @description Returns appropriate text label based on the current saving state\r\n */\r\n getStateText(state) {\r\n const [saving, saved, offline] = this.getText('state');\r\n switch (state) {\r\n case 'saving': return saving;\r\n case 'saved': return saved;\r\n case 'offline': return offline;\r\n default: return '';\r\n }\r\n }\r\n /**\r\n * Gets the icon URL for a given saving state\r\n * @param {string} state - The state to get icon for ('saving', 'saved', or 'offline')\r\n * @returns {string} The URL of the icon image for the given state\r\n * @description Returns appropriate icon URL based on the current saving state\r\n */\r\n getStateIcon(state) {\r\n switch (state) {\r\n case 'saving': return Icons.cloudSave;\r\n case 'saved': return Icons.cloudSave;\r\n case 'offline': return 'data:image/svg+xml;base64,' + btoa(Icons.offline);\r\n default: return '';\r\n }\r\n }\r\n\r\n /**\r\n * Fetches and displays saved content in a dropdown\r\n * @async\r\n * @param {Event} e - The event object\r\n * @description Handles fetching and displaying saved content when the save state button is clicked.\r\n * If the dropdown is already visible, it will be closed. Otherwise it will fetch saved content\r\n * from the server (or use cached content if available) and display it in a dropdown panel.\r\n * @throws {Error} Logs error to console if fetching content fails\r\n */\r\n async fetchSavedContent(e) {\r\n e.preventDefault();\r\n\r\n let dropdown = document.querySelector('#savedDropdown');\r\n let isVisible = dropdown?.classList?.contains('show');\r\n\r\n if (isVisible) {\r\n this.closeSavedDropdown();\r\n return;\r\n }\r\n let editorWrapper = null;\r\n if (this.module.modulename === 'quiz') {\r\n editorWrapper = document.querySelector(`#tiny_cursive_savingState${this.module.questionid}`);\r\n } else {\r\n editorWrapper = document.querySelector('#tiny_cursive_savingState');\r\n }\r\n\r\n let args = {\r\n id: this.module.resourceId,\r\n cmid: this.module.cmid,\r\n modulename: `${this.module.modulename}_autosave`,\r\n editorid: this.editor?.id,\r\n userid: this.module.userid,\r\n courseid: this.module.courseid\r\n };\r\n\r\n call([{\r\n methodname: \"cursive_get_autosave_content\",\r\n args: args\r\n }])[0].done((data) => {\r\n let context = {comments: JSON.parse(data)};\r\n Object.values(context.comments).forEach(content => {\r\n content.time = this.timeAgo(content.timemodified);\r\n });\r\n this.renderCommentList(context, editorWrapper);\r\n\r\n }).fail((error) => {\r\n this.throwWarning('fullmodeerrorr', this.editor);\r\n window.console.error('Error fetching saved content:', error);\r\n });\r\n }\r\n\r\n /**\r\n * Toggles the visibility of the saved content dropdown\r\n * @description Checks if the saved content dropdown is currently visible and either closes or opens it accordingly.\r\n * If visible, calls closeSavedDropdown(). If hidden, calls openSavedDropdown().\r\n */\r\n toggleSavedDropdown() {\r\n const dropdown = document.querySelector('#savedDropdown');\r\n const isVisible = dropdown?.classList?.contains('show');\r\n\r\n if (isVisible) {\r\n this.closeSavedDropdown();\r\n } else {\r\n this.openSavedDropdown();\r\n }\r\n }\r\n\r\n /**\r\n * Opens the saved content dropdown panel\r\n * @description Shows the saved content dropdown by adding the 'show' class and sets up an event listener\r\n * for the Escape key to allow closing the dropdown. This is called when toggling the dropdown open.\r\n */\r\n openSavedDropdown() {\r\n const dropdown = document.querySelector('#savedDropdown');\r\n dropdown.classList.add('show');\r\n\r\n // Add event listener to close on Escape key\r\n document.removeEventListener('keydown', this.handleEscapeKey);\r\n document.addEventListener('keydown', this.handleEscapeKey);\r\n }\r\n\r\n /**\r\n * Closes the saved content dropdown panel\r\n * @description Removes the 'show' class from the dropdown to hide it, removes the dropdown element from the DOM,\r\n * and removes the Escape key event listener. This is called when toggling the dropdown closed or when\r\n * clicking outside the dropdown.\r\n */\r\n closeSavedDropdown() {\r\n const dropdown = document.querySelector('#savedDropdown');\r\n if (dropdown) {\r\n dropdown.classList.remove('show');\r\n dropdown.remove();\r\n document.removeEventListener('keydown', this.handleEscapeKey);\r\n }\r\n }\r\n\r\n /**\r\n * Handles the Escape key press event for closing the saved content dropdown\r\n * @param {KeyboardEvent} event - The keyboard event object\r\n * @description Event handler that checks if the Escape key was pressed and closes the saved content dropdown if it was\r\n */\r\n handleEscapeKey(event) {\r\n if (event.key === 'Escape') {\r\n this.closeSavedDropdown();\r\n }\r\n }\r\n\r\n timeAgo(unixTime) {\r\n const seconds = Math.floor(Date.now() / 1000) - unixTime;\r\n\r\n if (seconds < 5) {\r\n return \"just now\";\r\n }\r\n if (seconds < 60) {\r\n return `${seconds} sec ago`;\r\n }\r\n\r\n const minutes = Math.floor(seconds / 60);\r\n if (minutes < 60) {\r\n return `${minutes} min ago`;\r\n }\r\n\r\n const hours = Math.floor(minutes / 60);\r\n if (hours < 24) {\r\n return `${hours} hour${hours > 1 ? \"s\" : \"\"} ago`;\r\n }\r\n\r\n const days = Math.floor(hours / 24);\r\n if (days < 7) {\r\n return `${days} day${days > 1 ? \"s\" : \"\"} ago`;\r\n }\r\n\r\n const weeks = Math.floor(days / 7);\r\n if (weeks < 4) {\r\n return `${weeks} week${weeks > 1 ? \"s\" : \"\"} ago`;\r\n }\r\n\r\n const months = Math.floor(days / 30);\r\n if (months < 12) {\r\n return `${months} month${months > 1 ? \"s\" : \"\"} ago`;\r\n }\r\n\r\n const years = Math.floor(days / 365);\r\n return `${years} year${years > 1 ? \"s\" : \"\"} ago`;\r\n }\r\n\r\n\r\n /**\r\n * Renders the saved content dropdown list using a template\r\n * @param {Object} context - The context object containing saved comments data to render\r\n * @param {HTMLElement} editorWrapper - The wrapper element to attach the dropdown to\r\n * @description Renders the saved content dropdown using the tiny_cursive/saved_content template.\r\n * Creates and positions the dropdown relative to the editor wrapper element.\r\n * Handles toggling visibility and caching of the saved content.\r\n * @throws {Error} Logs error to console if template rendering fails\r\n */\r\n renderCommentList(context, editorWrapper) {\r\n templates.render('tiny_cursive/saved_content', context).then(html => {\r\n editorWrapper.style.position = 'relative';\r\n\r\n const tempDiv = document.createElement('div');\r\n tempDiv.innerHTML = html.trim();\r\n tempDiv.id = 'savedDropdown';\r\n tempDiv.classList.add('tiny_cursive-saved-dropdown');\r\n\r\n if (!tempDiv) {\r\n window.console.error(\"Saved content template rendered empty or invalid HTML.\");\r\n return false;\r\n }\r\n\r\n // Add to DOM if not already added\r\n let existingPanel = document.querySelector('#savedDropdown');\r\n\r\n if (!existingPanel) {\r\n editorWrapper.appendChild(tempDiv);\r\n existingPanel = tempDiv;\r\n }\r\n\r\n // Toggle visibility\r\n existingPanel.classList.toggle('active');\r\n this.openSavedDropdown();\r\n\r\n this.insertSavedItems(this.editor);\r\n\r\n return true;\r\n\r\n }).catch(error => window.console.error(error));\r\n }\r\n\r\n fetchStrings() {\r\n if (!localStorage.getItem('state')) {\r\n\r\n Promise.all([\r\n getString('saving', 'tiny_cursive'),\r\n getString('saved', 'tiny_cursive'),\r\n getString('offline', 'tiny_cursive')\r\n ]).then(function(strings) {\r\n return localStorage.setItem('state', JSON.stringify(strings));\r\n }).catch(error => window.console.error(error));\r\n }\r\n }\r\n\r\n throwWarning(str, editor) {\r\n getString(str, 'tiny_cursive').then(str => {\r\n return editor.windowManager.alert(str);\r\n }).catch(error => window.console.error(error));\r\n }\r\n\r\n getText(key) {\r\n return JSON.parse(localStorage.getItem(key));\r\n }\r\n\r\n /**\r\n * Adds click event listeners to saved content items to insert them into the editor\r\n * @description Finds all elements with class 'tiny_cursive-item-preview' and adds click handlers that will\r\n * insert the element's text content into the editor when clicked. The text is inserted with\r\n * a leading space.\r\n * @param {Object} editor - The TinyMCE editor instance\r\n * @returns {void}\r\n */\r\n insertSavedItems(editor) {\r\n const items = document.querySelectorAll('.tiny_cursive-item-preview');\r\n items.forEach(element => {\r\n element.addEventListener('click', function() {\r\n editor.insertContent(\" \" + this.textContent);\r\n });\r\n });\r\n }\r\n\r\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/cursive_writing_reports.min.js.map b/amd/build/cursive_writing_reports.min.js.map index 373a386e..83254144 100644 --- a/amd/build/cursive_writing_reports.min.js.map +++ b/amd/build/cursive_writing_reports.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cursive_writing_reports.min.js","sources":["../src/cursive_writing_reports.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/cursive_writing_reports\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", './analytic_button', './replay_button',\n\"./analytic_events\", \"core/modal_events\", 'core/modal_save_cancel', 'core/modal_factory', 'core/modal'],\n function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n replayButton,\n AnalyticEvents,\n Events,\n Modal,\n Factory,\n Alert\n) {\n const replayInstances = {};\n // eslint-disable-next-line\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 // eslint-disable-next-line\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n var usersTable = {\n init: function(page, hasApiKey, csvOption) {\n str\n .get_strings([\n {key: \"field_require\", component: \"tiny_cursive\"},\n ])\n .done(function() {\n usersTable.getusers(page);\n });\n\n let myEvents = new AnalyticEvents();\n (async function() {\n try {\n let scoreSetting = await str.get_string('confidence_threshold', 'tiny_cursive');\n analyticsEvents(scoreSetting, hasApiKey, parseInt(csvOption));\n } catch (error) {\n window.console.error('Error fetching string:', error);\n }\n })();\n\n /**\n * Handles the analytics events for each modal on the page.\n *\n * This function iterates over each element with the class `analytic-modal`,\n * retrieves necessary data attributes, and makes an AJAX call to get writing\n * statistics. Once the data is retrieved, it processes and displays it within\n * the modal.\n *\n * @param {Object} scoreSetting - Configuration settings related to scoring.\n * @param {boolean} hasApiKey - api key status\n * @param {boolean} csvOption - csv option status\n */\n function analyticsEvents(scoreSetting, hasApiKey, csvOption) {\n\n $(\".analytic-modal\").each(function() {\n var mid = $(this).data(\"id\");\n var filepath = $(this).data(\"filepath\");\n let context = {};\n context.userid = mid;\n let cmid = $(this).data(\"cmid\");\n\n AJAX.call([{\n methodname: 'cursive_get_writing_statistics',\n args: {\n cmid: cmid,\n fileid: mid,\n },\n }])[0].done(response => {\n let data = JSON.parse(response.data);\n\n // Show replay button if no API key, otherwise show analytics button\n if (!hasApiKey) {\n $(this).html(replayButton(mid));\n } else {\n $(this).html(analyticButton(data.effort_ratio, $(this).data('id')));\n }\n\n context.formattime = myEvents.formatedTime(data);\n context.tabledata = data;\n context.apikey = hasApiKey;\n // Perform actions that require context.tabledata\n let authIcon = myEvents.authorshipStatus(data.first_file, data.score, scoreSetting);\n myEvents.createModal(mid, context, '', replayInstances, authIcon);\n myEvents.analytics(mid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(mid, mid, '', replayInstances);\n myEvents.replyWriting(mid, filepath, '', replayInstances);\n\n }).fail(error => {\n throw new Error('Error: ' + error.message);\n });\n\n });\n\n $('.download-btn').on('click', async function(e) {\n e.preventDefault();\n\n const link1 = $(this).attr('href');\n const link2 = $(this).data('link');\n\n let type = Factory.types.SAVE_CANCEL;\n let optionModal = Modal;\n let select = document.createElement('select');\n select.id = \"download-type\";\n select.classList.add('form-control', 'inputUrl');\n\n let title = await str.get_string('download', 'tiny_cursive');\n\n // Add CSV option\n if (csvOption) {\n try {\n const text = await str.get_string('payloadjson', 'tiny_cursive');\n let option = document.createElement('option');\n option.text = text;\n option.value = 0;\n select.appendChild(option);\n } catch (error) {\n window.console.error(error);\n }\n }\n\n // Add API key option\n if (hasApiKey) {\n try {\n const text = await str.get_string('analyticspdf', 'tiny_cursive');\n let option2 = document.createElement('option');\n option2.text = text;\n option2.value = 1;\n select.appendChild(option2);\n } catch (error) {\n window.console.error(error);\n }\n }\n\n // If no options available\n if (!hasApiKey && !csvOption) {\n try {\n const noOptionText = await str.get_string('no_option', 'tiny_cursive');\n const messageText = await str.get_string('message', 'tool_dataprivacy');\n title = messageText;\n type = Factory.types.ALERT;\n optionModal = Alert;\n select = noOptionText;\n } catch (error) {\n window.console.error(error);\n }\n }\n\n optionModal.create({\n type: type,\n title: title,\n body: select,\n removeOnClose: true,\n buttons: type === Factory.types.SAVE_CANCEL ? [{\n text: 'OK',\n type: 'submit',\n primary: true\n }] : []\n }).then(modal => {\n modal.show();\n\n if (type === Factory.types.SAVE_CANCEL) {\n modal.getRoot().on(Events.save, function() {\n const data = document.getElementById('download-type');\n if (!data) {\n return;\n }\n if (parseInt(data.value)) {\n window.location.href = link2;\n } else {\n window.location.href = link1;\n }\n });\n }\n return modal;\n }).catch(error => {\n window.console.error('failed to open modal', error);\n });\n });\n\n }\n },\n getusers: function(page) {\n $(\"#id_coursename\").change(function() {\n var courseid = $(this).val();\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_get_user_list\",\n args: {\n courseid: courseid,\n },\n },\n ]);\n promise1[0].done(function(json) {\n var data = JSON.parse(json);\n var context = {\n tabledata: data,\n page: page,\n };\n // eslint-disable-next-line\n templates\n .render(\"tiny_cursive/user_list\", context)\n .then(function(html) {\n\n var filteredUser = $(\"#id_username\");\n filteredUser.html(html);\n return true;\n });\n });\n\n var promise2 = AJAX.call([\n {\n methodname: \"cursive_get_module_list\",\n args: {\n courseid: courseid,\n },\n },\n ]);\n promise2[0].done(function(json) {\n var data = JSON.parse(json);\n var context = {\n tabledata: data,\n page: page,\n };\n // eslint-disable-next-line\n templates\n .render(\"tiny_cursive/module_list\", context)\n .then(function(html) {\n\n var filteredUser = $(\"#id_modulename\");\n filteredUser.html(html);\n return true;\n });\n });\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","replayButton","AnalyticEvents","Events","Modal","Factory","Alert","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","page","hasApiKey","csvOption","get_strings","key","component","done","getusers","myEvents","scoreSetting","each","this","data","context","userid","cmid","call","methodname","args","fileid","response","JSON","parse","effort_ratio","formattime","formatedTime","tabledata","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","replyWriting","fail","Error","message","on","async","preventDefault","link1","attr","link2","type","types","SAVE_CANCEL","optionModal","select","document","createElement","id","classList","add","title","get_string","text","option","value","appendChild","option2","noOptionText","ALERT","create","body","removeOnClose","buttons","primary","modal","show","getRoot","save","getElementById","parseInt","location","href","analyticsEvents","change","courseid","val","json"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cursive_writing_reports.min.js","sources":["../src/cursive_writing_reports.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/cursive_writing_reports\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", './analytic_button', './replay_button',\r\n\"./analytic_events\", \"core/modal_events\", 'core/modal_save_cancel', 'core/modal_factory', 'core/modal'],\r\n function(\r\n $,\r\n AJAX,\r\n str,\r\n templates,\r\n Replay,\r\n analyticButton,\r\n replayButton,\r\n AnalyticEvents,\r\n Events,\r\n Modal,\r\n Factory,\r\n Alert\r\n) {\r\n const replayInstances = {};\r\n // eslint-disable-next-line\r\n window.video_playback = function (mid, filepath) {\r\n if (filepath !== '') {\r\n const replay = new Replay(\r\n 'content' + mid,\r\n filepath,\r\n 10,\r\n false,\r\n 'player_' + mid\r\n );\r\n replayInstances[mid] = replay;\r\n } else {\r\n // eslint-disable-next-line\r\n templates.render('tiny_cursive/no_submission').then(html => {\r\n $('#content' + mid).html(html);\r\n }).catch(e => window.console.error(e));\r\n }\r\n return false;\r\n\r\n };\r\n var usersTable = {\r\n init: function(page, hasApiKey, csvOption) {\r\n str\r\n .get_strings([\r\n {key: \"field_require\", component: \"tiny_cursive\"},\r\n ])\r\n .done(function() {\r\n usersTable.getusers(page);\r\n });\r\n\r\n let myEvents = new AnalyticEvents();\r\n (async function() {\r\n try {\r\n let scoreSetting = await str.get_string('confidence_threshold', 'tiny_cursive');\r\n analyticsEvents(scoreSetting, hasApiKey, parseInt(csvOption));\r\n } catch (error) {\r\n window.console.error('Error fetching string:', error);\r\n }\r\n })();\r\n\r\n /**\r\n * Handles the analytics events for each modal on the page.\r\n *\r\n * This function iterates over each element with the class `analytic-modal`,\r\n * retrieves necessary data attributes, and makes an AJAX call to get writing\r\n * statistics. Once the data is retrieved, it processes and displays it within\r\n * the modal.\r\n *\r\n * @param {Object} scoreSetting - Configuration settings related to scoring.\r\n * @param {boolean} hasApiKey - api key status\r\n * @param {boolean} csvOption - csv option status\r\n */\r\n function analyticsEvents(scoreSetting, hasApiKey, csvOption) {\r\n\r\n $(\".analytic-modal\").each(function() {\r\n var mid = $(this).data(\"id\");\r\n var filepath = $(this).data(\"filepath\");\r\n let context = {};\r\n context.userid = mid;\r\n let cmid = $(this).data(\"cmid\");\r\n\r\n AJAX.call([{\r\n methodname: 'cursive_get_writing_statistics',\r\n args: {\r\n cmid: cmid,\r\n fileid: mid,\r\n },\r\n }])[0].done(response => {\r\n let data = JSON.parse(response.data);\r\n\r\n // Show replay button if no API key, otherwise show analytics button\r\n if (!hasApiKey) {\r\n $(this).html(replayButton(mid));\r\n } else {\r\n $(this).html(analyticButton(data.effort_ratio, $(this).data('id')));\r\n }\r\n\r\n context.formattime = myEvents.formatedTime(data);\r\n context.tabledata = data;\r\n context.apikey = hasApiKey;\r\n // Perform actions that require context.tabledata\r\n let authIcon = myEvents.authorshipStatus(data.first_file, data.score, scoreSetting);\r\n myEvents.createModal(mid, context, '', replayInstances, authIcon);\r\n myEvents.analytics(mid, templates, context, '', replayInstances, authIcon);\r\n myEvents.checkDiff(mid, mid, '', replayInstances);\r\n myEvents.replyWriting(mid, filepath, '', replayInstances);\r\n\r\n }).fail(error => {\r\n throw new Error('Error: ' + error.message);\r\n });\r\n\r\n });\r\n\r\n $('.download-btn').on('click', async function(e) {\r\n e.preventDefault();\r\n\r\n const link1 = $(this).attr('href');\r\n const link2 = $(this).data('link');\r\n\r\n let type = Factory.types.SAVE_CANCEL;\r\n let optionModal = Modal;\r\n let select = document.createElement('select');\r\n select.id = \"download-type\";\r\n select.classList.add('form-control', 'inputUrl');\r\n\r\n let title = await str.get_string('download', 'tiny_cursive');\r\n\r\n // Add CSV option\r\n if (csvOption) {\r\n try {\r\n const text = await str.get_string('payloadjson', 'tiny_cursive');\r\n let option = document.createElement('option');\r\n option.text = text;\r\n option.value = 0;\r\n select.appendChild(option);\r\n } catch (error) {\r\n window.console.error(error);\r\n }\r\n }\r\n\r\n // Add API key option\r\n if (hasApiKey) {\r\n try {\r\n const text = await str.get_string('analyticspdf', 'tiny_cursive');\r\n let option2 = document.createElement('option');\r\n option2.text = text;\r\n option2.value = 1;\r\n select.appendChild(option2);\r\n } catch (error) {\r\n window.console.error(error);\r\n }\r\n }\r\n\r\n // If no options available\r\n if (!hasApiKey && !csvOption) {\r\n try {\r\n const noOptionText = await str.get_string('no_option', 'tiny_cursive');\r\n const messageText = await str.get_string('message', 'tool_dataprivacy');\r\n title = messageText;\r\n type = Factory.types.ALERT;\r\n optionModal = Alert;\r\n select = noOptionText;\r\n } catch (error) {\r\n window.console.error(error);\r\n }\r\n }\r\n\r\n optionModal.create({\r\n type: type,\r\n title: title,\r\n body: select,\r\n removeOnClose: true,\r\n buttons: type === Factory.types.SAVE_CANCEL ? [{\r\n text: 'OK',\r\n type: 'submit',\r\n primary: true\r\n }] : []\r\n }).then(modal => {\r\n modal.show();\r\n\r\n if (type === Factory.types.SAVE_CANCEL) {\r\n modal.getRoot().on(Events.save, function() {\r\n const data = document.getElementById('download-type');\r\n if (!data) {\r\n return;\r\n }\r\n if (parseInt(data.value)) {\r\n window.location.href = link2;\r\n } else {\r\n window.location.href = link1;\r\n }\r\n });\r\n }\r\n return modal;\r\n }).catch(error => {\r\n window.console.error('failed to open modal', error);\r\n });\r\n });\r\n\r\n }\r\n },\r\n getusers: function(page) {\r\n $(\"#id_coursename\").change(function() {\r\n var courseid = $(this).val();\r\n var promise1 = AJAX.call([\r\n {\r\n methodname: \"cursive_get_user_list\",\r\n args: {\r\n courseid: courseid,\r\n },\r\n },\r\n ]);\r\n promise1[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n var context = {\r\n tabledata: data,\r\n page: page,\r\n };\r\n // eslint-disable-next-line\r\n templates\r\n .render(\"tiny_cursive/user_list\", context)\r\n .then(function(html) {\r\n\r\n var filteredUser = $(\"#id_username\");\r\n filteredUser.html(html);\r\n return true;\r\n });\r\n });\r\n\r\n var promise2 = AJAX.call([\r\n {\r\n methodname: \"cursive_get_module_list\",\r\n args: {\r\n courseid: courseid,\r\n },\r\n },\r\n ]);\r\n promise2[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n var context = {\r\n tabledata: data,\r\n page: page,\r\n };\r\n // eslint-disable-next-line\r\n templates\r\n .render(\"tiny_cursive/module_list\", context)\r\n .then(function(html) {\r\n\r\n var filteredUser = $(\"#id_modulename\");\r\n filteredUser.html(html);\r\n return true;\r\n });\r\n });\r\n });\r\n },\r\n };\r\n return usersTable;\r\n});\r\n"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","replayButton","AnalyticEvents","Events","Modal","Factory","Alert","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","page","hasApiKey","csvOption","get_strings","key","component","done","getusers","myEvents","scoreSetting","each","this","data","context","userid","cmid","call","methodname","args","fileid","response","JSON","parse","effort_ratio","formattime","formatedTime","tabledata","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","replyWriting","fail","Error","message","on","async","preventDefault","link1","attr","link2","type","types","SAVE_CANCEL","optionModal","select","document","createElement","id","classList","add","title","get_string","text","option","value","appendChild","option2","noOptionText","ALERT","create","body","removeOnClose","buttons","primary","modal","show","getRoot","save","getElementById","parseInt","location","href","analyticsEvents","change","courseid","val","json"],"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..a0bf3fd9 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=`${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&&openDate&&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;if("quiz"===this.module)openDate=open?1e3*open:null,dueDate=due?1e3*due:null;else{var _open$textContent,_due$textContent;const openText=(null==open||null===(_open$textContent=open.textContent)||void 0===_open$textContent?void 0:_open$textContent.toLowerCase())||"",dueText=(null==due||null===(_due$textContent=due.textContent)||void 0===_due$textContent?void 0:_due$textContent.toLowerCase())||"";openText.includes("opened")||openText.includes("open")?openDate=this.extractDate(null==open?void 0:open.textContent):(openText.includes("due")||openText.includes("close"))&&(dueDate=this.extractDate(null==open?void 0:open.textContent)),due&&(dueText.includes("due")||dueText.includes("close"))&&(dueDate=this.extractDate(null==due?void 0:due.textContent))}if(openDate){const openedWrapper=this.create("div"),openedLabel=this.create("span"),openedValue=this.create("span");openedLabel.textContent=`${this.opened}: `,openedValue.textContent=this.formatDate(new Date(openDate)),openedValue.className="text-dark",openedWrapper.className="d-flex justify-content-between",openedWrapper.append(openedLabel,openedValue),wrapper.append(openedWrapper)}if(dueDate){const dueWrapper=this.create("div"),dueLabel=this.create("span"),dueValue=this.create("span");dueLabel.textContent=`${this.due}: `,dueValue.textContent=this.formatDate(new Date(dueDate)),dueValue.className="text-danger",dueWrapper.className="d-flex justify-content-between",dueWrapper.append(dueLabel,dueValue),wrapper.append(dueWrapper);const remainingWrapper=this.create("div"),remainingLabel=this.create("span"),remainingValue=this.create("span");remainingLabel.textContent=`${this.remaining}: `,remainingValue.textContent=this.calculateDate(dueDate),remainingValue.className="text-danger",remainingWrapper.className="d-flex align-items-center justify-content-between mt-2 pt-2 border-top",remainingWrapper.append(remainingLabel,remainingValue),wrapper.append(remainingWrapper)}return 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 null;const parts=null==text?void 0:text.split(":");if(parts.length>1){return parts.slice(1).join(":").trim()||null}return text.trim()||null}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 e291a61f..4dca6aa9 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":";;;;;;;+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,QAEU,iBAAhBO,KAAKP,cAPNQ,cAAcF,IAY3BG,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,SAC9B,GAAoB,iBAAhBC,KAAKP,OAA2B,wBAClCC,WAAaC,kBAAMiB,kBACnBT,qCAAeH,KAAKT,uCAALsB,cAAad,KAIzCe,WAAWC,gCAGDC,QADM,IAAIC,IAAIC,OAAOC,SAASC,MAChBC,aAAaC,IAAI,SAC/BC,OAASC,SAASC,cAAc,wCAChCC,eAAiB1B,KAAK2B,cAAc3B,KAAKP,QACzCmC,WAAa5B,KAAK6B,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,UAAYpC,KAAKqC,OAAO,OAC9BC,OAAOC,OAAOH,UAAW,CACrBrC,GAAI,+BACJyC,UAAW,0BAEfF,OAAOC,OAAOH,UAAUK,MAAO,CAC3BC,MAAO,QACPC,SAAU,eAGRC,SAAW5C,KAAKqC,OAAO,QAC7BC,OAAOC,OAAOK,SAAU,CACpB7C,GAAI,2BACJyC,UAAW,UACXK,UAAWlD,kBAAMmD,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,WAAalD,KAAKqC,OAAO,OAC/BC,OAAOC,OAAOW,WAAY,CACtBC,QAAS,SACTC,SAAU,SACVC,IAAK,IACLC,gBAAiB,UAErBJ,WAAWK,OAAOX,gBAGZY,OAASxD,KAAKqC,OAAO,OAC3BmB,OAAOhB,UAAY,6BACnBF,OAAOC,OAAOiB,OAAOf,MAAO,CACxBW,SAAU,SACVC,IAAK,YAGHI,YAAczD,KAAKqC,OAAO,MAChCoB,YAAYjB,UAAY,iCACxBiB,YAAYC,YAAe,GAAE9B,WAAW+B,SAAS3D,KAAK4D,UACtDH,YAAYhB,MAAMoB,WAAa,YAEzBC,WAAatC,SAASC,cAAc,4BACtCqC,YACAL,YAAYM,QAAQD,WAAWE,WAAU,QAGzCC,UAAYjE,KAAKkE,YAAYnD,QAC7BW,MAAAA,gBAAAA,eAAgBgC,YAChBF,OAAOD,OAAOE,YAAaQ,UAAWjE,KAAKmE,eAAezC,iBAE1D8B,OAAOD,OAAOE,YAAaQ,iBAGzBG,QAAUpE,KAAKqC,OAAO,UAC5B+B,QAAQ5B,UAAY,MAEpB4B,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAM7E,kBAAM8E,OACZd,MAAO3D,KAAK0E,YACZC,SAAU3E,KAAK4E,oBAAoB5E,KAAKb,KAAM4C,eAIlC,WAAhB/B,KAAKP,QAAuBqC,aAC5BsC,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMxE,KAAKN,WACXiE,MAAO3D,KAAK6E,SACZF,SAAU7C,YAAYe,aAK9Bb,WAA+C,MAAlCA,MAAAA,iBAAAA,UAAW0B,YAAYoB,QAAe,KAC/CC,WAAavD,SAASwD,iBAAiB,yBACvCD,YACAA,WAAWE,SAAQC,UACfA,QAAQzC,MAAM0C,cAAgB,YAGtCf,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMxE,KAAKN,WACXiE,MAAQ,GAAE3D,KAAK6B,kBAAkB8B,SAAS3D,KAAKoF,cAC/CT,SAAU3C,UAAUa,gBAKZ,UAAhB7C,KAAKP,QAAsBuB,QAAS,MAC/BqE,wBACDC,UAAY9D,SAASC,cAAe,iBAAgBT,WACpDsE,MAAAA,WAAAA,UAAW5B,YAAYoB,QACvBV,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMxE,KAAKN,WACXiE,MAAO3D,KAAKuF,WACZZ,SAAUW,UAAU5B,YAAYoB,aAM5B,SAAhB9E,KAAKP,8BAAqBO,KAAKT,iCAALiG,cAAazF,GAAI,uBAEvC0F,WAAazF,KAAK0F,oCAAc1F,KAAKT,uCAALoG,cAAa5F,IAC7C6F,SAAWpE,SAASC,cAAe,aAAYgE,qBAC/CI,MAAQC,KAAK9F,KAAKR,SAASqG,OAE3BD,MAAAA,UAAAA,SAAUlC,YAAYoB,QACtBV,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,WACJC,WAAY,YACZC,KAAMxE,KAAKN,WACXiE,MAAO3D,KAAK+F,YACZpB,SAAUiB,SAASlC,eAK3BmC,OAA0B,KAAjBA,MAAMf,QACfV,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMxE,KAAKN,WACXiE,MAAQ,GAAE3D,KAAKQ,QAAQR,KAAKoF,cAC5BT,SAAUkB,SAKlBG,OAAOhG,KAAKR,SAASyG,OACrB7B,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,WACJC,WAAY,YACZC,KAAM7E,kBAAMuG,KACZvC,MAAO3D,KAAKmG,eACZxB,SAAU3E,KAAKoG,uBAAuBJ,OAAOhG,KAAKR,SAASyG,MAAOD,OAAOhG,KAAKR,SAASsD,kBAMnGR,OAAO+D,KAAKrG,KAAKZ,SAASkH,QAC1BlC,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,UACJC,WAAY,YACZC,KAAMxE,KAAKN,WACXiE,MAAO3D,KAAKuG,QACZ5B,SAAU3E,KAAKwG,gBAAgBxG,KAAKZ,YAK5C6C,OACAmC,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,WACJC,WAAY,YACZC,KAAM7E,kBAAMuG,KACZvC,MAAO3D,KAAKmG,eACZxB,SAAU3E,KAAKoG,uBAAuBlE,SAAUC,YAIxC,WAAhBnC,KAAKP,QACL2E,QAAQb,OACJvD,KAAKqE,UAAU,CACXC,GAAI,WACJC,WAAY,eACZC,KAAMxE,KAAKN,WACXiE,MAAO3D,KAAKyG,UACZ9B,SAAU3E,KAAK0G,iBAAiB1G,KAAKX,eAKjD+C,UAAUmB,OAAOL,WAAYM,OAAQY,SAC9BhC,UAIXiC,oBAAUC,GAACA,GAADC,WAAKA,WAALC,KAAiBA,KAAjBb,MAAuBA,MAAvBgB,SAA8BA,qBAC9BgC,IAAM3G,KAAKqC,OAAO,OACxBsE,IAAInE,UAAa,8BAA6B8B,WAExCsC,QAAU5G,KAAKqC,OAAO,MAC5BuE,QAAQpE,UAAa,qCAAoC+B,uCACzDqC,QAAQ/D,UAAa,GAAE2B,QAAQb,cAEzBkD,KAAO7G,KAAKqC,OAAO,cACzBwE,KAAKrE,UAAa,kCAClBqE,KAAKhE,UAAY8B,SAEjBgC,IAAIpD,OAAOqD,QAASC,MACbF,IAGXH,gBAAgBpH,eACN0H,QAAU9G,KAAKqC,OAAO,cAE5BjD,QAAQ6F,SAAQ8B,eACNC,UAAYhH,KAAKqC,OAAO,OAC9B2E,UAAUxE,UAAY,iCAEhBmB,MAAQ3D,KAAKqC,OAAO,MAC1BsB,MAAMnB,UAAY,4BAClBmB,MAAMD,YAAcqD,OAAO3B,YAC3B4B,UAAUC,YAAYtD,OAEtBrB,OAAO4E,OAAOH,OAAOI,QAAQlC,SAAQmC,cAC3BC,SAAWrH,KAAKqC,OAAO,OACvBiF,MAAQtB,OAAOoB,MAAME,OAIvBD,SAAS7E,UADC,IAAV8E,MACqB,oDACdA,OAAS,EACK,oDAEA,qDAGzBD,SAAS3D,YAAe,GAAE0D,MAAMG,gBAAgBH,MAAME,QACtDN,UAAUC,YAAYI,aAG1BP,QAAQG,YAAYD,cAGjBF,QAAQjE,UAGnB6D,iBAAiBrH,+DACPyH,QAAU9G,KAAKqC,OAAO,OAEtBmF,cAAgBxH,KAAKqC,OAAO,OAClCmF,cAAchF,UAAY,gCAEpBiF,WAAazH,KAAKqC,OAAO,QAC/BoF,WAAW/D,YAAe,GAAE1D,KAAKe,gBAE3B2G,YAAc1H,KAAKqC,OAAO,QAC1BsF,MAAwC,SAAhCtI,MAAAA,wCAAAA,WAAYuI,kEAAS7G,QACnC2G,YAAYhE,YAAciE,MAAQ3H,KAAK6H,SAAW7H,KAAK8H,MACvDJ,YAAYlF,UAAa,8BAA4BmF,MAAQ,0BAA4B,6BAEzFH,cAAcjE,OAAOkE,WAAYC,mBAE3BK,gBAAkB/H,KAAKqC,OAAO,OACpC0F,gBAAgBvF,UAAY,gCAEtBwF,aAAehI,KAAKqC,OAAO,QACjC2F,aAAatE,YAAe,GAAE1D,KAAKiI,uBAE7BC,cAAgBlI,KAAKqC,OAAO,WAC9BhD,MAAAA,yCAAAA,WAAYuI,yCAAZO,qBAAqBC,aAAc,OAC7BC,KAAO,IAAIC,KAAuC,IAAlCjJ,WAAWuI,QAAQQ,cACzCF,cAAcxE,YAAc1D,KAAKuI,WAAWF,WAE5CH,cAAcxE,YAAc,MAEhCqE,gBAAgBxE,OAAOyE,aAAcE,qBAE/BM,aAAexI,KAAKqC,OAAO,OACjCmG,aAAahG,UAAY,gCAEnBiG,UAAYzI,KAAKqC,OAAO,QAC9BoG,UAAU/E,YAAe,GAAE1D,KAAK0I,mBAE1BC,WAAa3I,KAAKqC,OAAO,eAE3BhD,MAAAA,YAAAA,WAAYuJ,MACZD,WAAWjF,YAAcsC,OAAO3G,WAAWuJ,MAAMA,OAAS,EACpDvJ,WAAWuJ,MAAMA,MACjB5I,KAAK6I,SAEXF,WAAWjF,YAAc1D,KAAK6I,SAGlCL,aAAajF,OAAOkF,UAAWE,YAC/B7B,QAAQvD,OAAOiE,cAAegB,aAAcT,iBACrCjB,QAAQjE,UAGnBqB,YAAYnD,cACFkD,UAAYjE,KAAKqC,OAAO,OACxByG,SAAW9I,KAAKqC,OAAO,OACvB0G,MAAQ/I,KAAKqC,OAAO,QACpB2G,MAAQhJ,KAAKqC,OAAO,QACpBmC,KAAOxE,KAAKqC,OAAO,QAEzBmC,KAAKhC,UAAY,OACjBgC,KAAK3B,UAAYlD,kBAAMC,WAEvBkJ,SAAS7B,YAAYzC,MACrBsE,SAASvF,OAAOwF,OAEhBA,MAAMrF,YAAe,GAAE1D,KAAKiE,aAC5B+E,MAAMtF,YAAc,IACpBsF,MAAMxG,UAAY,eAClBwG,MAAMvG,MAAMoB,WAAa,MACzBmF,MAAMvG,MAAMwG,SAAW,OAEvBhF,UAAUzB,UAAY,qEACtByB,UAAUV,OAAOuF,SAAUE,OAC3B/E,UAAUxB,MAAMwG,SAAW,cAEV,IAAIC,kBAAiB,WAC5BC,QAAUpI,OAAO2C,YAAYoB,OACnCkE,MAAMtF,YAAe,GAAEyF,QAAQC,QAAQ,QAAS,SAG3CC,QAAQtI,OAAQ,CACrBuI,eAAe,EACfC,SAAS,EACTC,WAAW,IAGRvF,UAIXE,eAAesF,WAEPC,WAAalI,SAASC,cAAc,gCACpCiI,WAAY,8BACRC,MAAQD,WAAW1F,WAAU,gCACjC2F,MAAMlI,cAAc,gEAAWmI,cAC1BrK,OAAOsK,oBAAoB5D,KAAK,CACjC6D,KAAMH,MAAMjG,YACZqG,KAAM,gBAKRC,WAAahK,KAAKqC,OAAO,OAC/B2H,WAAWxH,UAAY,2EAEjBsG,SAAW9I,KAAKqC,OAAO,OACvB0G,MAAQ/I,KAAKqC,OAAO,QACpB2G,MAAQhJ,KAAKqC,OAAO,QACpBmC,KAAOxE,KAAKqC,OAAO,WACzBmC,KAAK3B,UAAYlD,kBAAMuG,KAEvB4C,SAAS7B,YAAYzC,MACrBsE,SAASvF,OAAOwF,OAEhBA,MAAMrF,YAAe,GAAE1D,KAAKiK,YAC5BjB,MAAMtF,YAAc,WACpBsF,MAAMxG,UAAYkH,WAAa,cAAgB,eAC/CpH,OAAOC,OAAOyG,MAAMvG,MAAO,CACvBoB,WAAY,MACZoF,SAAU,SAIde,WAAWzG,OAAOuF,SAAUE,OAC5BgB,WAAWvH,MAAMwG,SAAW,OACxBQ,MAAO,CACU,IAAIP,kBAAiB,WAC5BC,QAAUM,MAAM/F,YAAYoB,OAClCkE,MAAMtF,YAAe,GAAEyF,aAElBE,QAAQI,MAAO,CACpBH,eAAe,EACfC,SAAS,EACTC,WAAW,SAGfR,MAAMtF,YAAc1D,KAAKkK,eAItBF,WAIXpF,oBAAoBuF,KAAMC,cAEhBtD,QAAU9G,KAAKqC,OAAO,OAEtBgI,YAAcrK,KAAKqC,OAAO,OAC1BiI,gBAAkBtK,KAAKqC,OAAO,OAC9BkI,cAAgBvK,KAAKqC,OAAO,OAE5BmI,UAAYxK,KAAKqC,OAAO,UACxBoI,UAAYzK,KAAKqC,OAAO,QACxBqI,cAAgB1K,KAAKqC,OAAO,UAC5BsI,cAAgB3K,KAAKqC,OAAO,QAC5BuI,YAAc5K,KAAKqC,OAAO,UAC1BwI,YAAc7K,KAAKqC,OAAO,eAEhCmI,UAAU9G,YAAe,GAAE1D,KAAK8K,OAChCL,UAAU/G,YAAcyG,KAAKY,SAE7BL,cAAchH,YAAe,GAAE1D,KAAKgL,cACpCL,cAAcjH,YAAcyG,KAAKc,SAEjCL,YAAYlH,YAAe,GAAE1D,KAAKoK,WAClCS,YAAYnH,YAAc0G,OAAOzG,MAEjC+G,cAAclI,UAAY,gBAC1BmI,cAAcnI,UAAY,iBAC1BoI,YAAYpI,UAAY,gBACxBqI,YAAYrI,UAAY,iBACxBgI,UAAUhI,UAAY,gBACtBiI,UAAUjI,UAAY,iBAEtB6H,YAAY9G,OAAOiH,UAAWC,WAC9BH,gBAAgB/G,OAAOmH,cAAeC,eACtCJ,cAAchH,OAAOqH,YAAaC,aAElC/D,QAAQvD,OAAO8G,YAAaC,gBAAiBC,eAEtCzD,QAAQjE,UAInBuD,uBAAuBH,KAAMiF,WAEnBpE,QAAU9G,KAAKqC,OAAO,WACxBH,SAAW,KACXC,QAAU,WAERgJ,cAAgBnL,KAAKqC,OAAO,OAC5B+I,WAAapL,KAAKqC,OAAO,OACzBgJ,iBAAmBrL,KAAKqC,OAAO,OAE/BiJ,YAActL,KAAKqC,OAAO,QAC1BkJ,YAAcvL,KAAKqC,OAAO,QAC1BmJ,SAAWxL,KAAKqC,OAAO,QACvBoJ,SAAWzL,KAAKqC,OAAO,QACvBqJ,eAAiB1L,KAAKqC,OAAO,QAC7BsJ,eAAiB3L,KAAKqC,OAAO,cACf,SAAhBrC,KAAKP,QACLyC,SAAkB,IAAP+D,KACX9D,QAAgB,IAAN+I,MAEVhJ,SAAWlC,KAAK4L,YAAY3F,MAAAA,YAAAA,KAAMvC,aAClCvB,QAAUnC,KAAK4L,YAAYV,MAAAA,WAAAA,IAAKxH,cAGpC4H,YAAY5H,YAAe,GAAE1D,KAAK6L,WAClCN,YAAY7H,YAAc1D,KAAKuI,WAAWrG,SAAW,IAAIoG,KAAKpG,UAAY,MAC1EqJ,YAAY/I,UAAY,YAExBgJ,SAAS9H,YAAe,GAAE1D,KAAKkL,QAC/BO,SAAS/H,YAAc1D,KAAKuI,WAAWpG,QAAU,IAAImG,KAAKnG,SAAW,MACrEsJ,SAASjJ,UAAY,cAErBkJ,eAAehI,YAAe,GAAE1D,KAAK8L,cACrCH,eAAejI,YAAc1D,KAAK+L,cAAc5J,SAChDwJ,eAAenJ,UAAY,cAE3B2I,cAAc3I,UAAY,iCAC1B4I,WAAW5I,UAAY,iCACvB6I,iBAAiB7I,UAAY,yEAE7B2I,cAAc5H,OAAO+H,YAAaC,aAClCH,WAAW7H,OAAOiI,SAAUC,UAC5BJ,iBAAiB9H,OAAOmI,eAAgBC,gBAExC7E,QAAQvD,OAAO4H,cAAeC,WAAYC,kBAEnCvE,QAAQjE,UAGnB0F,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,MAAMjG,OAAS,EACRiG,MAAME,MAAM,GAAGC,KAAK,KAAK5H,OAG7BgF,KAAKhF,OAIhBiH,cAAc1D,UACLA,WACM,UAMLsE,OAJQ,IAAIrE,KAAKD,MACX,IAAIC,QAMZqE,QAAU,QACH,gBAKC,GAHSC,KAAKC,MAAMF,uBACVC,KAAKC,MAAOF,YAA6B,YAOnExM,eAAeV,yGACPmI,QAA0B,SAAhB5H,KAAKP,OACf+B,SAASsL,eAAgB,GAAErN,cAAgB+B,SAASC,cAAe,IAAGhC,cAEtEsN,GAAKnF,QAAQoF,cACbC,GAAKF,GAAGC,cAERE,GADKD,GAAGD,cACAA,cAERG,UAAY3L,SAASC,cAAc,4CACnC2L,WAAa5L,SAASC,cAAc,wBACpC+B,OAASxD,KAAKqC,OAAO,OACrBgL,IAAM,QAEVD,WAAWE,UAAU1D,OAAO,QAC5BpG,OAAOzD,GAAK,sCACZuC,OAAOC,OAAOiB,OAAOf,MAAO,CACxBa,gBAAiB,QACjBL,QAAS,OACTsK,eAAgB,kBAGA,SAAhBvN,KAAKP,QACL4N,IAAM7L,SAASC,cAAc,sBAAsBuC,WAAU,GAC7DqJ,IAAI7K,UAAY,mCAChB6K,IAAI5K,MAAM+K,OAAS,UAEnBH,IAAMrN,KAAKqC,OAAO,SAClBgL,IAAI7K,UAAY,mCAChB6K,IAAIrE,MAAQhJ,KAAKyN,YACjBJ,IAAItD,KAAO,SACXsD,IAAI5K,MAAM+K,OAAS,SAGH,iBAAhBxN,KAAKP,OAA2B,OAC1BgD,MAAQjB,SAASkM,cAAc,SACrCjL,MAAM1C,GAAK,oBACX0C,MAAMiB,YAAe,mMAMrBlC,SAASmM,KAAK1G,YAAYxE,aAGxBmL,SAAW5N,KAAKqC,OAAO,OACvBwL,UAAY7N,KAAKqC,OAAO,WAC1ByL,YAAc,CACd7K,QAAS,OACT8K,WAAY,SACZP,OAAQ,UAGZlL,OAAOC,OAAOqL,SAASnL,MAAOqL,aAC9BD,UAAU9N,GAAK,sCACfuC,OAAOC,OAAOsL,UAAUpL,MAAOqL,aAE/BD,UAAU5G,YAAYoG,KACtBO,SAAS3G,YAAYmG,WAAWpJ,WAAU,IAE1CR,OAAOyD,YAAY2G,UACnBpK,OAAOyD,YAAY4G,WAEnBX,GAAGc,aAAaxK,OAAQ0J,GAAGe,YAC3BhB,GAAGxK,MAAMa,gBAAkB,UAC3BhB,OAAOC,OAAOqF,QAAQnF,MAAO,CACzBC,MAAO,QACPwL,SAAU,QACVC,UAAW,kEAGf7L,OAAOC,OAAOwK,GAAGtK,MAAO,CACpBQ,QAAS,OACTsK,eAAgB,SAChBa,QAAS,OACTZ,OAAQ,mBAEN/K,MAAQzC,KAAKqC,OAAO,SAC1BI,MAAM1C,GAAK,mCACX0C,MAAMiB,YAAe,yGAIrBlC,SAASmM,KAAK1G,YAAYxE,WAEtB4L,0CAAazG,QAAQ0G,8EAAiBzH,sCAAQe,QAAQ2G,+EAARC,sBAAuBhN,kDAAvBiN,uBAAiC5H,MAE/EwH,aACAA,WAAW5L,MAAMU,QAAU,SAE/B8J,GAAGxK,MAAMW,SAAW,yCACpB5B,SAASsL,eAAe,wFAAiClD,aAErDrI,OAASvB,KAAKqC,OAAO,OACzBd,OAAOxB,GAAK,sCACZwB,OAAOsB,UAAYlD,kBAAM+O,UACzBzB,GAAGhG,YAAY1F,QACf0L,GAAGhG,YAAYjH,KAAKc,WAAWqM,YAGnClN,cAAc0O,6MACVnN,SAASsL,eAAe,iGAAwClD,wCAChEpI,SAASsL,eAAe,0FAAiClD,aAErDhC,QAAUpG,SAASsL,eAAe6B,UAClC5B,GAAKnF,QAAQoF,cACbC,GAAKF,GAAGC,cAEZ1K,OAAOC,OAAO0K,GAAGxK,MAAO,CACpBa,gBAAiB,GACjBF,SAAU,KAGdd,OAAOC,OAAOqF,QAAQnF,MAAO,CACzBC,MAAO,GACPwL,SAAU,GACVC,UAAW,KAGf7L,OAAOC,OAAOwK,GAAGtK,MAAO,CACpBQ,QAAS,GACTsK,eAAgB,GAChBa,QAAS,GACTZ,OAAQ,KAGZT,GAAGO,UAAU1D,OAAO,qCAEhByE,2CAAazG,QAAQ0G,gFAAiBzH,uCAAQe,QAAQ2G,gFAARK,uBAAuBpN,kDAAvBqN,uBAAiChI,MAC/EwH,aACAA,WAAW5L,MAAMU,QAAU,mCAE/B3B,SAASmM,KAAKlM,cAAc,6FAAsCmI,wCAClEpI,SAASmM,KAAKlM,cAAc,gFAAuBmI,SAGvDvE,0BACUyJ,KAAOtN,SAASC,cAAc,gDAC9BsN,IAAM/O,KAAKgP,WAEbF,MACAA,KAAK/L,iBAAiB,SAAUkM,UACtBC,aAAe1N,SAASsL,eAAe,kBACzC1I,QAAUpE,KAAKT,OAAO4P,aAAarK,OAClCoK,cAA8C,KAA9BA,aAAalG,MAAMlE,QAA6B,KAAZV,UACrD6K,EAAEG,iBACFH,EAAEI,uBACG9P,OAAO+P,cAAcC,MAAMR,SAMhDlN,wBACWU,OAAQiN,OAAQhP,KAAME,QAAUV,KAAKyP,QAAQ,kBAC5CzP,KAAKP,YACJ,eACM,CAACkE,MAAOpB,OAAQiC,KAAM7E,kBAAMC,gBAClC,cACM,CAAC+D,MAAO6L,OAAQhL,KAAM7E,kBAAMU,WAClC,eACM,CAACsD,MAAOjD,OAAQ8D,KAAM7E,kBAAMU,WAClC,aACM,CAACsD,MAAOnD,KAAMgE,KAAM7E,kBAAMa,UAChC,qBACM,CAACmD,MAAO,iBAAkBa,KAAM7E,kBAAMiB,4BAEtC,CAAC+C,MAAO,OAAQa,KAAM7E,kBAAMa,OAI/CmB,cAAclC,eACFA,YACC,gBACM+B,SAASC,cAAc,+CAC7B,eACMD,SAASC,cAAc,kCAC7B,gBACMD,SAASC,cAAc,qBAC7B,cACMD,SAASC,cAAc,kCAEvB,MAInBiE,cAAcgK,oBAEDA,SAA8B,iBAAZA,QAGhBA,QAAQtG,QAAQ,oBAAqB,SAFjC,GAGb,MAAOuG,cACLzO,OAAO0O,QAAQD,MAAM,6BAA8BA,OAC5C,IAIf9P,eAEQG,KAAK4D,QACL5D,KAAK0E,YACL1E,KAAK6E,SACL7E,KAAKoF,YACLpF,KAAKuF,WACLvF,KAAK+F,YACL/F,KAAKmG,eACLnG,KAAKuG,QACLvG,KAAKyG,UACLzG,KAAKe,OACLf,KAAK8H,MACL9H,KAAK6H,SACL7H,KAAKiI,aACLjI,KAAK0I,SACL1I,KAAK6I,SACL7I,KAAKiE,UACLjE,KAAKiK,SACLjK,KAAKkK,QACLlK,KAAK8K,KACL9K,KAAKgL,UACLhL,KAAKoK,OACLpK,KAAK6L,OACL7L,KAAKkL,IACLlL,KAAK6P,QACL7P,KAAK8L,UACL9L,KAAKyN,YACLzN,KAAKgP,YACLhP,KAAKyP,QAAQ,cAGrBA,QAAQK,YACGC,KAAKC,MAAMC,aAAaC,QAAQJ,OAAS,GAGpDzN,OAAO8N,YACI3O,SAASkM,cAAcyC"} \ 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * This module provides functionality for document view management in the Tiny editor,\r\n * including full page mode display and sidebar information\r\n * @module tiny_cursive/document_view\r\n * @copyright 2025 Cursive Technology, Inc. \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport Icons from 'tiny_cursive/svg_repo';\r\nexport default class DocumentView {\r\n\r\n constructor(User, Rubrics, submission, modulename, editor, quizInfo) {\r\n this.User = User;\r\n this.Rubrics = Rubrics;\r\n this.submission = submission;\r\n this.module = modulename;\r\n this.editor = editor;\r\n this.moduleIcon = Icons.assignment;\r\n this.quizInfo = quizInfo;\r\n this.initStrings();\r\n }\r\n\r\n normalMode() {\r\n let id = this.editor?.id + \"_ifr\";\r\n if (this.module === 'assign') {\r\n this.normalizePage(id);\r\n } else if (this.module === 'quiz') {\r\n this.normalizePage(id);\r\n } else if (this.module === 'forum') {\r\n this.normalizePage(id);\r\n } else if (this.module === 'lesson') {\r\n this.normalizePage(id);\r\n } else if(this.module === 'pdfannotator') {\r\n this.normalizePage(id);\r\n }\r\n }\r\n\r\n fullPageMode() {\r\n\r\n if (this.module === 'assign') {\r\n this.moduleIcon = Icons.assignment;\r\n this.fullPageModule(this.editor?.id);\r\n } else if (this.module === 'forum') {\r\n this.moduleIcon = Icons.forum;\r\n this.fullPageModule(this.editor?.id);\r\n } else if (this.module === 'quiz' && this.editor?.id) {\r\n this.moduleIcon = Icons.quiz;\r\n this.fullPageModule(this.editor?.id);\r\n } else if (this.module === 'lesson') {\r\n this.moduleIcon = Icons.lesson;\r\n this.fullPageModule(this.editor?.id);\r\n } else if (this.module === 'pdfannotator') {\r\n this.moduleIcon = Icons.pdfannotator;\r\n this.fullPageModule(this.editor?.id);\r\n }\r\n }\r\n\r\n docSideBar(status) {\r\n\r\n const url = new URL(window.location.href);\r\n const replyId = url.searchParams.get(\"reply\");\r\n const toggle = document.querySelector('#cursive-fullpagemode-sidebar-toggle');\r\n const timelimitBlock = this.getTimerBlock(this.module);\r\n const headerInfo = this.getSidebarTitle();\r\n const progressBar = document.querySelector('.box.progress_bar');\r\n\r\n const courseName = document.querySelector('#page-navbar > nav > ol > li:nth-child(1) > a');\r\n const courseDes = document.querySelector('#intro');\r\n const Dates = document.querySelector('.activity-dates');\r\n\r\n let openDate = Dates?.querySelector('div:nth-child(1)');\r\n let dueDate = Dates?.querySelector('div:nth-child(2)');\r\n\r\n const container = this.create('div');\r\n Object.assign(container, {\r\n id: 'cursive-fullpagemode-sidebar',\r\n className: 'bg-white h-100 shadow'\r\n });\r\n Object.assign(container.style, {\r\n width: '300px',\r\n overflow: 'auto'\r\n });\r\n\r\n const crossBtn = this.create('span');\r\n Object.assign(crossBtn, {\r\n id: 'cursive-collapse-sidebar',\r\n className: 'btn p-2',\r\n innerHTML: Icons.close\r\n });\r\n\r\n crossBtn.addEventListener('click', () => {\r\n container.style.transition = 'width 0.3s ease';\r\n container.style.width = '0';\r\n toggle.style.display = 'flex';\r\n });\r\n toggle?.addEventListener('click', function() {\r\n toggle.style.display = 'none';\r\n container.style.width = '300px';\r\n });\r\n\r\n const btnWrapper = this.create('div');\r\n Object.assign(btnWrapper, {\r\n padding: '0 1rem',\r\n position: 'sticky',\r\n top: '0',\r\n backgroundColor: 'white'\r\n });\r\n btnWrapper.append(crossBtn);\r\n\r\n\r\n const header = this.create('div');\r\n header.className = 'border-bottom p-3 bg-light';\r\n Object.assign(header.style, {\r\n position: 'sticky',\r\n top: '0'\r\n });\r\n\r\n const headerTitle = this.create('h3');\r\n headerTitle.className = 'mb-3 d-flex align-items-center';\r\n headerTitle.textContent = `${headerInfo.title} ${this.details}`;\r\n headerTitle.style.fontWeight = '600';\r\n\r\n const headerIcon = document.querySelector('.page-header-image > div');\r\n if (headerIcon) {\r\n headerTitle.prepend(headerIcon.cloneNode(true));\r\n }\r\n\r\n let wordCount = this.wordCounter(status);\r\n if (timelimitBlock?.textContent) {\r\n header.append(headerTitle, wordCount, this.timerCountDown(timelimitBlock));\r\n } else {\r\n header.append(headerTitle, wordCount);\r\n }\r\n\r\n const content = this.create('div');\r\n content.className = 'p-3';\r\n\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-info',\r\n titleColor: 'text-info',\r\n icon: Icons.people,\r\n title: this.studentInfo,\r\n bodyHTML: this.generateStudentInfo(this.User, courseName)\r\n })\r\n );\r\n\r\n if (this.module === 'lesson' && progressBar) {\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-gray',\r\n titleColor: 'text-dark',\r\n icon: this.moduleIcon,\r\n title: this.progress,\r\n bodyHTML: progressBar.innerHTML\r\n })\r\n );\r\n }\r\n\r\n if (courseDes && courseDes?.textContent.trim() !== '') {\r\n let fileSubDiv = document.querySelectorAll('.fileuploadsubmission');\r\n if (fileSubDiv) {\r\n fileSubDiv.forEach(Element => {\r\n Element.style.verticalAlign = 'middle';\r\n });\r\n }\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-gray',\r\n titleColor: 'text-dark',\r\n icon: this.moduleIcon,\r\n title: `${this.getSidebarTitle().title} ${this.description}`,\r\n bodyHTML: courseDes.innerHTML\r\n })\r\n );\r\n }\r\n\r\n if (this.module === 'forum' && replyId) {\r\n this.checkForumSubject();\r\n let replyPost = document.querySelector(`#post-content-${replyId}`);\r\n if (replyPost?.textContent.trim()) {\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-gray',\r\n titleColor: 'text-dark',\r\n icon: this.moduleIcon,\r\n title: this.replyingto,\r\n bodyHTML: replyPost.textContent.trim()\r\n })\r\n );\r\n }\r\n }\r\n\r\n if (this.module === 'quiz' && this.editor?.id) {\r\n\r\n let questionId = this.getQuestionId(this.editor?.id);\r\n let question = document.querySelector(`#question-${questionId} .qtext`);\r\n let intro = atob(this.quizInfo.intro);\r\n\r\n if (question?.textContent.trim()) {\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-amber',\r\n titleColor: 'text-dark',\r\n icon: this.moduleIcon,\r\n title: this.answeringto,\r\n bodyHTML: question.textContent\r\n })\r\n );\r\n }\r\n\r\n if (intro && intro.trim() !== '') {\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-gray',\r\n titleColor: 'text-dark',\r\n icon: this.moduleIcon,\r\n title: `${this.quiz} ${this.description}`,\r\n bodyHTML: intro\r\n })\r\n );\r\n }\r\n\r\n if (Number(this.quizInfo.open)) {\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-amber',\r\n titleColor: 'text-dark',\r\n icon: Icons.time,\r\n title: this.importantdates,\r\n bodyHTML: this.generateImportantDates(Number(this.quizInfo.open), Number(this.quizInfo.close))\r\n })\r\n );\r\n }\r\n }\r\n\r\n if (Object.keys(this.Rubrics).length) {\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-gray',\r\n titleColor: 'text-dark',\r\n icon: this.moduleIcon,\r\n title: this.rubrics,\r\n bodyHTML: this.generateRubrics(this.Rubrics)\r\n })\r\n );\r\n }\r\n\r\n if (Dates && openDate) {\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-amber',\r\n titleColor: 'text-dark',\r\n icon: Icons.time,\r\n title: this.importantdates,\r\n bodyHTML: this.generateImportantDates(openDate, dueDate)\r\n })\r\n );\r\n }\r\n if (this.module === 'assign') {\r\n content.append(\r\n this.createBox({\r\n bg: 'bg-green',\r\n titleColor: 'text-success',\r\n icon: this.moduleIcon,\r\n title: this.subStatus,\r\n bodyHTML: this.submissionStatus(this.submission)\r\n })\r\n );\r\n }\r\n\r\n container.append(btnWrapper, header, content);\r\n return container;\r\n\r\n }\r\n // Helper to create info boxes\r\n createBox({bg, titleColor, icon, title, bodyHTML}) {\r\n const box = this.create('div');\r\n box.className = `tiny_cursive-fullpage-card ${bg}`;\r\n\r\n const heading = this.create('h4');\r\n heading.className = `tiny_cursive-fullpage-card-header ${titleColor} d-flex align-items-center`;\r\n heading.innerHTML = `${icon} ${title}`;\r\n\r\n const body = this.create('div');\r\n body.className = `tiny_cursive-fullpage-card-body`;\r\n body.innerHTML = bodyHTML;\r\n\r\n box.append(heading, body);\r\n return box;\r\n }\r\n\r\n generateRubrics(Rubrics) {\r\n const wrapper = this.create('div');\r\n\r\n Rubrics.forEach(rubric => {\r\n const rubricDiv = this.create('div');\r\n rubricDiv.className = 'tiny_cursive-rubric-card';\r\n\r\n const title = this.create('h3');\r\n title.className = 'tiny_cursive-rubric-title';\r\n title.textContent = rubric.description;\r\n rubricDiv.appendChild(title);\r\n\r\n Object.values(rubric.levels).forEach(level => {\r\n const levelDiv = this.create('div');\r\n const score = Number(level.score);\r\n\r\n // Assign background color class based on score\r\n if (score === 0) {\r\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-low';\r\n } else if (score <= 2) {\r\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-mid';\r\n } else {\r\n levelDiv.className = 'tiny_cursive-rubric-level tiny_cursive-rubric-high';\r\n }\r\n\r\n levelDiv.textContent = `${level.definition} / ${level.score}`;\r\n rubricDiv.appendChild(levelDiv);\r\n });\r\n\r\n wrapper.appendChild(rubricDiv);\r\n });\r\n\r\n return wrapper.innerHTML;\r\n }\r\n\r\n submissionStatus(submission) {\r\n const wrapper = this.create('div');\r\n\r\n const statusWrapper = this.create('div');\r\n statusWrapper.className = 'tiny_cursive-status-row';\r\n\r\n const statusName = this.create('span');\r\n statusName.textContent = `${this.status}:`;\r\n\r\n const statusValue = this.create('span');\r\n const isNew = submission?.current?.status === 'new';\r\n statusValue.textContent = isNew ? this.draftnot : this.draft;\r\n statusValue.className = `tiny_cursive-status-value ${isNew ? 'tiny_cursive-status-red' : 'tiny_cursive-status-green'}`;\r\n\r\n statusWrapper.append(statusName, statusValue);\r\n\r\n const modifiedWrapper = this.create('div');\r\n modifiedWrapper.className = 'tiny_cursive-status-row';\r\n\r\n const modifiedName = this.create('span');\r\n modifiedName.textContent = `${this.lastModified}: `;\r\n\r\n const modifiedValue = this.create('span');\r\n if (submission?.current?.timemodified) {\r\n const date = new Date(submission.current.timemodified * 1000);\r\n modifiedValue.textContent = this.formatDate(date);\r\n } else {\r\n modifiedValue.textContent = 'N/A';\r\n }\r\n modifiedWrapper.append(modifiedName, modifiedValue);\r\n\r\n const gradeWrapper = this.create('div');\r\n gradeWrapper.className = 'tiny_cursive-status-row';\r\n\r\n const gradeName = this.create('span');\r\n gradeName.textContent = `${this.gradings}: `;\r\n\r\n const gradeValue = this.create('span');\r\n\r\n if (submission?.grade) {\r\n gradeValue.textContent = Number(submission.grade.grade) > 0\r\n ? submission.grade.grade\r\n : this.gradenot;\r\n } else {\r\n gradeValue.textContent = this.gradenot;\r\n }\r\n\r\n gradeWrapper.append(gradeName, gradeValue);\r\n wrapper.append(statusWrapper, gradeWrapper, modifiedWrapper);\r\n return wrapper.innerHTML;\r\n }\r\n\r\n wordCounter(status) {\r\n const wordCount = this.create('div');\r\n const labelDiv = this.create('div');\r\n const label = this.create('span');\r\n const value = this.create('span');\r\n const icon = this.create('span');\r\n\r\n icon.className = 'me-2';\r\n icon.innerHTML = Icons.assignment;\r\n\r\n labelDiv.appendChild(icon);\r\n labelDiv.append(label);\r\n\r\n label.textContent = `${this.wordCount}:`;\r\n value.textContent = '0';\r\n value.className = 'text-primary';\r\n value.style.fontWeight = '600';\r\n value.style.fontSize = '14px';\r\n\r\n wordCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\r\n wordCount.append(labelDiv, value);\r\n wordCount.style.fontSize = '12px';\r\n\r\n const observer = new MutationObserver(() => {\r\n const newText = status.textContent.trim();\r\n value.textContent = `${newText.replace('words', '')}`;\r\n });\r\n\r\n observer.observe(status, {\r\n characterData: true,\r\n subtree: true,\r\n childList: true\r\n });\r\n\r\n return wordCount;\r\n }\r\n\r\n\r\n timerCountDown(timer) {\r\n\r\n let warningDiv = document.querySelector('#user-notifications > div');\r\n if (warningDiv) {\r\n let clone = warningDiv.cloneNode(true);\r\n clone.querySelector('button')?.remove();\r\n this.editor.notificationManager.open({\r\n text: clone.textContent,\r\n type: 'error'\r\n });\r\n }\r\n\r\n\r\n const timerCount = this.create('div');\r\n timerCount.className = 'bg-white rounded shadow-sm p-2 d-flex justify-content-between my-2';\r\n\r\n const labelDiv = this.create('div');\r\n const label = this.create('span');\r\n const value = this.create('span');\r\n const icon = this.create('span');\r\n icon.innerHTML = Icons.time;\r\n\r\n labelDiv.appendChild(icon);\r\n labelDiv.append(label);\r\n\r\n label.textContent = `${this.timeleft}:`;\r\n value.textContent = '00:00:00';\r\n value.className = warningDiv ? 'text-danger' : 'text-primary';\r\n Object.assign(value.style, {\r\n fontWeight: '600',\r\n fontSize: '14px'\r\n });\r\n\r\n\r\n timerCount.append(labelDiv, value);\r\n timerCount.style.fontSize = '12px';\r\n if (timer) {\r\n const observer = new MutationObserver(() => {\r\n const newText = timer.textContent.trim();\r\n value.textContent = `${newText}`;\r\n });\r\n observer.observe(timer, {\r\n characterData: true,\r\n subtree: true,\r\n childList: true\r\n });\r\n } else {\r\n value.textContent = this.nolimit;\r\n }\r\n\r\n\r\n return timerCount;\r\n }\r\n\r\n\r\n generateStudentInfo(user, course) {\r\n\r\n const wrapper = this.create('div');\r\n\r\n const nameWrapper = this.create('div');\r\n const usernameWrapper = this.create('div');\r\n const courseWrapper = this.create('div');\r\n\r\n const nameLabel = this.create('strong');\r\n const nameValue = this.create('span');\r\n const usernameLabel = this.create('strong');\r\n const usernameValue = this.create('span');\r\n const courseLabel = this.create('strong');\r\n const courseValue = this.create('span');\r\n\r\n nameLabel.textContent = `${this.name}`;\r\n nameValue.textContent = user.fullname;\r\n\r\n usernameLabel.textContent = `${this.userename}: `;\r\n usernameValue.textContent = user.username;\r\n\r\n courseLabel.textContent = `${this.course}: `;\r\n courseValue.textContent = course.title;\r\n\r\n usernameLabel.className = 'cfw-bold me-2';\r\n usernameValue.className = 'cursiveFw-wrap';\r\n courseLabel.className = 'cfw-bold me-2';\r\n courseValue.className = 'cursiveFw-wrap';\r\n nameLabel.className = 'cfw-bold me-2';\r\n nameValue.className = 'cursiveFw-wrap';\r\n\r\n nameWrapper.append(nameLabel, nameValue);\r\n usernameWrapper.append(usernameLabel, usernameValue);\r\n courseWrapper.append(courseLabel, courseValue);\r\n\r\n wrapper.append(nameWrapper, usernameWrapper, courseWrapper);\r\n\r\n return wrapper.innerHTML;\r\n\r\n }\r\n\r\n generateImportantDates(open, due) {\r\n\r\n const wrapper = this.create('div');\r\n let openDate = null;\r\n let dueDate = null;\r\n\r\n if (this.module === 'quiz') {\r\n // For quiz, open/due are timestamps - only set if non-zero\r\n openDate = open ? open * 1000 : null;\r\n dueDate = due ? due * 1000 : null;\r\n } else {\r\n // For other modules, check the text content to determine which is which\r\n // Moodle only renders divs for dates that are set, so we need to check labels\r\n const openText = open?.textContent?.toLowerCase() || '';\r\n const dueText = due?.textContent?.toLowerCase() || '';\r\n\r\n // Check if 'open' div actually contains an open date or a due date\r\n if (openText.includes('opened') || openText.includes('open')) {\r\n openDate = this.extractDate(open?.textContent);\r\n } else if (openText.includes('due') || openText.includes('close')) {\r\n // First div is actually the due date (no open date was set)\r\n dueDate = this.extractDate(open?.textContent);\r\n }\r\n\r\n // Check 'due' div if it exists\r\n if (due && (dueText.includes('due') || dueText.includes('close'))) {\r\n dueDate = this.extractDate(due?.textContent);\r\n }\r\n }\r\n\r\n // Only show open date if it exists\r\n if (openDate) {\r\n const openedWrapper = this.create('div');\r\n const openedLabel = this.create('span');\r\n const openedValue = this.create('span');\r\n\r\n openedLabel.textContent = `${this.opened}: `;\r\n openedValue.textContent = this.formatDate(new Date(openDate));\r\n openedValue.className = 'text-dark';\r\n\r\n openedWrapper.className = 'd-flex justify-content-between';\r\n openedWrapper.append(openedLabel, openedValue);\r\n wrapper.append(openedWrapper);\r\n }\r\n\r\n // Only show due date and remaining time if due date exists\r\n if (dueDate) {\r\n const dueWrapper = this.create('div');\r\n const dueLabel = this.create('span');\r\n const dueValue = this.create('span');\r\n\r\n dueLabel.textContent = `${this.due}: `;\r\n dueValue.textContent = this.formatDate(new Date(dueDate));\r\n dueValue.className = 'text-danger';\r\n\r\n dueWrapper.className = 'd-flex justify-content-between';\r\n dueWrapper.append(dueLabel, dueValue);\r\n wrapper.append(dueWrapper);\r\n\r\n // Remaining time - only show if due date exists\r\n const remainingWrapper = this.create('div');\r\n const remainingLabel = this.create('span');\r\n const remainingValue = this.create('span');\r\n\r\n remainingLabel.textContent = `${this.remaining}: `;\r\n remainingValue.textContent = this.calculateDate(dueDate);\r\n remainingValue.className = 'text-danger';\r\n\r\n remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top';\r\n remainingWrapper.append(remainingLabel, remainingValue);\r\n wrapper.append(remainingWrapper);\r\n }\r\n\r\n return wrapper.innerHTML;\r\n }\r\n\r\n formatDate(date) {\r\n if (!date) {\r\n return '-';\r\n }\r\n\r\n let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true};\r\n return date.toLocaleString('en-US', options);\r\n }\r\n\r\n extractDate(text) {\r\n if (!text) {\r\n return null;\r\n }\r\n // Split on first colon and return the right part\r\n const parts = text?.split(':');\r\n if (parts.length > 1) {\r\n const dateStr = parts.slice(1).join(':').trim();\r\n // Return null if empty or invalid\r\n return dateStr || null;\r\n }\r\n\r\n return text.trim() || null;\r\n }\r\n\r\n\r\n calculateDate(date) {\r\n if (!date) {\r\n return '-';\r\n }\r\n const date1 = new Date(date); // Due date (local time)\r\n const now = new Date(); // Current date/time\r\n\r\n // Calculate the difference in milliseconds\r\n const diffMs = date1 - now;\r\n\r\n // Convert to days, hours, minutes\r\n if (diffMs <= 0) {\r\n return \"Overdue\";\r\n } else {\r\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\r\n const diffHours = Math.floor((diffMs / (1000 * 60 * 60)) % 24);\r\n\r\n return `${diffDays} days, ${diffHours} hours`;\r\n }\r\n\r\n }\r\n\r\n fullPageModule(module) {\r\n let current = this.module === 'quiz' ?\r\n document.getElementById(`${module}_ifr`) : document.querySelector(`#${module}_ifr`);\r\n\r\n let p1 = current.parentElement;\r\n let p2 = p1.parentElement;\r\n let p3 = p2.parentElement;\r\n let p4 = p3.parentElement;\r\n\r\n let statusBar = document.querySelector('.tox-statusbar__right-container > button');\r\n let assignName = document.querySelector('.page-context-header');\r\n let header = this.create('div');\r\n let btn = null;\r\n\r\n assignName.classList.remove('mb-2');\r\n header.id = 'tiny_cursive-fullpage-custom-header';\r\n Object.assign(header.style, {\r\n backgroundColor: 'white',\r\n display: 'flex',\r\n justifyContent: 'space-between'\r\n });\r\n\r\n if (this.module === 'quiz') {\r\n btn = document.querySelector('#mod_quiz-next-nav').cloneNode(true);\r\n btn.className = 'tiny_cursive-fullpage-submit-btn';\r\n btn.style.margin = '.5rem';\r\n } else {\r\n btn = this.create('input');\r\n btn.className = 'tiny_cursive-fullpage-submit-btn';\r\n btn.value = this.savechanges;\r\n btn.type = 'submit';\r\n btn.style.margin = '.5rem';\r\n }\r\n\r\n if (this.module === 'pdfannotator') {\r\n const style = document.createElement('style');\r\n style.id = 'cursiveForceStyle';\r\n style.textContent = `\r\n .path-mod-pdfannotator #comment-wrapper h4,\r\n .path-mod-pdfannotator #comment-nav {\r\n margin: 0 !important;\r\n }\r\n `;\r\n document.head.appendChild(style);\r\n }\r\n\r\n const leftSide = this.create('div');\r\n const rightSide = this.create('div');\r\n let commonStyle = {\r\n display: 'flex',\r\n alignItems: 'center',\r\n margin: '0 1rem'\r\n };\r\n\r\n Object.assign(leftSide.style, commonStyle);\r\n rightSide.id = 'tiny_cursive-fullpage-right-wrapper';\r\n Object.assign(rightSide.style, commonStyle);\r\n\r\n rightSide.appendChild(btn);\r\n leftSide.appendChild(assignName.cloneNode(true));\r\n\r\n header.appendChild(leftSide);\r\n header.appendChild(rightSide);\r\n\r\n p4.insertBefore(header, p4.firstChild);\r\n p2.style.backgroundColor = '#efefef';\r\n Object.assign(current.style, {\r\n width: '750px',\r\n minWidth: '750px',\r\n boxShadow: '0 10px 15px -3px rgb(0 0 0/0.1),0 4px 6px -4px rgb(0 0 0/0.1)'\r\n });\r\n\r\n Object.assign(p1.style, {\r\n display: 'flex',\r\n justifyContent: 'center',\r\n outline: 'none',\r\n margin: '2rem 0 0'\r\n });\r\n const style = this.create('style');\r\n style.id = 'tiny_cursive-fullpage-mode-style';\r\n style.textContent = `\r\n .tox.tox-edit-focus .tox-edit-area::before {\r\n opacity: 0;\r\n }`;\r\n document.head.appendChild(style);\r\n\r\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\r\n\r\n if (iframeBody) {\r\n iframeBody.style.padding = '0.5in';\r\n }\r\n p2.style.position = 'relative';\r\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\r\n\r\n let toggle = this.create('div');\r\n toggle.id = 'cursive-fullpagemode-sidebar-toggle';\r\n toggle.innerHTML = Icons.hamburger;\r\n p2.appendChild(toggle);\r\n p2.appendChild(this.docSideBar(statusBar));\r\n }\r\n\r\n normalizePage(editorId) {\r\n document.getElementById('tiny_cursive-fullpage-custom-header')?.remove();\r\n document.getElementById('cursive-fullpagemode-sidebar')?.remove();\r\n\r\n let current = document.getElementById(editorId);\r\n let p1 = current.parentElement;\r\n let p2 = p1.parentElement;\r\n\r\n Object.assign(p2.style, {\r\n backgroundColor: \"\",\r\n position: \"\"\r\n });\r\n\r\n Object.assign(current.style, {\r\n width: '',\r\n minWidth: '',\r\n boxShadow: '',\r\n });\r\n\r\n Object.assign(p1.style, {\r\n display: '',\r\n justifyContent: '',\r\n outline: '',\r\n margin: ''\r\n });\r\n\r\n p1.classList.remove('tiny-cursive-editor-container');\r\n\r\n let iframeBody = current.contentDocument?.body || current.contentWindow?.document?.body;\r\n if (iframeBody) {\r\n iframeBody.style.padding = '0';\r\n }\r\n document.head.querySelector('#tiny_cursive-fullpage-mode-style')?.remove();\r\n document.head.querySelector('#cursiveForceStyle')?.remove();\r\n }\r\n\r\n checkForumSubject() {\r\n const form = document.querySelector('#tiny_cursive-fullpage-right-wrapper > input');\r\n const msg = this.subjectnot;\r\n\r\n if (form) {\r\n form.addEventListener('click', (e) => {\r\n const subjectInput = document.getElementById('id_subject');\r\n let content = this.editor.getContent().trim();\r\n if (!subjectInput || subjectInput.value.trim() === '' || content === '') {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n this.editor.windowManager.alert(msg);\r\n }\r\n });\r\n }\r\n }\r\n\r\n getSidebarTitle() {\r\n const [assign, discus, quiz, lesson] = this.getText('sbTitle');\r\n switch (this.module) {\r\n case 'assign':\r\n return {title: assign, icon: Icons.assignment};\r\n case 'forum':\r\n return {title: discus, icon: Icons.forum};\r\n case 'lesson':\r\n return {title: lesson, icon: Icons.forum};\r\n case 'quiz':\r\n return {title: quiz, icon: Icons.quiz};\r\n case 'pdfannotator':\r\n return {title: 'PDF Annotation', icon: Icons.pdfannotator};\r\n default:\r\n return {title: 'Page', icon: Icons.quiz};\r\n }\r\n }\r\n\r\n getTimerBlock(module) {\r\n switch (module) {\r\n case 'assign':\r\n return document.querySelector('#mod_assign_timelimit_block > div > div');\r\n case 'forum':\r\n return document.querySelector('#mod_forum_timelimit_block');\r\n case 'lesson':\r\n return document.querySelector('#lesson-timer');\r\n case 'quiz':\r\n return document.querySelector('#quiz-time-left');\r\n default:\r\n return null;\r\n }\r\n }\r\n\r\n getQuestionId(editoId) {\r\n try {\r\n if (!editoId || typeof editoId !== 'string') {\r\n return '';\r\n }\r\n return editoId.replace(/^q(\\d+):(\\d+)_.*$/, \"$1-$2\");\r\n } catch (error) {\r\n window.console.error('Error getting question ID:', error);\r\n return '';\r\n }\r\n }\r\n\r\n initStrings() {\r\n [\r\n this.details,\r\n this.studentInfo,\r\n this.progress,\r\n this.description,\r\n this.replyingto,\r\n this.answeringto,\r\n this.importantdates,\r\n this.rubrics,\r\n this.subStatus,\r\n this.status,\r\n this.draft,\r\n this.draftnot,\r\n this.lastModified,\r\n this.gradings,\r\n this.gradenot,\r\n this.wordCount,\r\n this.timeleft,\r\n this.nolimit,\r\n this.name,\r\n this.userename,\r\n this.course,\r\n this.opened,\r\n this.due,\r\n this.overdue,\r\n this.remaining,\r\n this.savechanges,\r\n this.subjectnot\r\n ] = this.getText('docSideBar');\r\n }\r\n\r\n getText(key) {\r\n return JSON.parse(localStorage.getItem(key)) || [];\r\n }\r\n\r\n create(tag) {\r\n return document.createElement(tag);\r\n }\r\n\r\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","openText","toLowerCase","dueText","includes","extractDate","openedWrapper","openedLabel","openedValue","opened","dueWrapper","dueLabel","dueValue","remainingWrapper","remainingLabel","remainingValue","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/key_logger.min.js.map b/amd/build/key_logger.min.js.map index 1032e107..a009a1db 100644 --- a/amd/build/key_logger.min.js.map +++ b/amd/build/key_logger.min.js.map @@ -1 +1 @@ -{"version":3,"file":"key_logger.min.js","sources":["../src/key_logger.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/key_logger\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\"], function(\n $,\n AJAX,\n str,\n templates) {\n\n var usersTable = {\n init: function(page) {\n str\n .get_strings([\n {key: \"field_require\", component: \"tiny_cursive\"}\n ])\n .done(function() {\n $(document).ready(function($) {\n $(\".popup_item\").on('click', function() {\n var mid = $(this).data(\"id\");\n $(\"#\" + mid).show();\n });\n $(\".link_icon\").on('click', function() {\n var smid = $(this).data(\"id\");\n $(\"#\" + smid).show();\n });\n $(\".modal-close \").on('click', function() {\n $(\".modal\").hide();\n });\n });\n usersTable.getusers(page);\n });\n },\n getusers: function(page) {\n $(\"#fgroup_id_buttonar\").hide();\n $(\"#id_coursename\").change(function() {\n var courseid = $(this).val();\n var promise1 = AJAX.call([\n {\n methodname: \"cursive_get_user_list\",\n args: {\n courseid: courseid,\n },\n },\n ]);\n promise1[0].done(function(json) {\n var data = JSON.parse(json);\n var context = {\n tabledata: data,\n page: page,\n };\n // eslint-disable-next-line\n templates\n .render(\"tiny_cursive/user_list\", context)\n .then(function(html) {\n var filteredUser = $(\"#id_username\");\n filteredUser.html(html);\n return true;\n });\n });\n\n var promise2 = AJAX.call([\n {\n methodname: \"cursive_get_module_list\",\n args: {\n courseid: courseid,\n },\n }\n ]);\n promise2[0].done(function(json) {\n var data = JSON.parse(json);\n var context = {\n tabledata: data,\n page: page,\n };\n // eslint-disable-next-line\n templates\n .render(\"tiny_cursive/module_list\", context)\n .then(function(html) {\n\n var filteredUser = $(\"#id_modulename\");\n filteredUser.html(html);\n return true;\n });\n });\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","templates","usersTable","init","page","get_strings","key","component","done","document","ready","on","mid","this","data","show","smid","hide","getusers","change","courseid","val","call","methodname","args","json","context","tabledata","JSON","parse","render","then","html"],"mappings":"AAsBAA,iCAAO,CAAC,SAAU,YAAa,WAAY,mBAAmB,SAC5DC,EACAC,KACAC,IACAC,eAEIC,WAAa,CACfC,KAAM,SAASC,MACbJ,IACGK,YAAY,CACX,CAACC,IAAK,gBAAiBC,UAAW,kBAEnCC,MAAK,WACJV,EAAEW,UAAUC,OAAM,SAASZ,GACzBA,EAAE,eAAea,GAAG,SAAS,eACvBC,IAAMd,EAAEe,MAAMC,KAAK,MACvBhB,EAAE,IAAMc,KAAKG,UAEfjB,EAAE,cAAca,GAAG,SAAS,eACtBK,KAAOlB,EAAEe,MAAMC,KAAK,MACxBhB,EAAE,IAAMkB,MAAMD,UAEhBjB,EAAE,iBAAiBa,GAAG,SAAS,WAC7Bb,EAAE,UAAUmB,aAGhBf,WAAWgB,SAASd,UAG1Bc,SAAU,SAASd,MACjBN,EAAE,uBAAuBmB,OACzBnB,EAAE,kBAAkBqB,QAAO,eACrBC,SAAWtB,EAAEe,MAAMQ,MACRtB,KAAKuB,KAAK,CACvB,CACEC,WAAY,wBACZC,KAAM,CACJJ,SAAUA,aAIP,GAAGZ,MAAK,SAASiB,UAEpBC,QAAU,CACZC,UAFSC,KAAKC,MAAMJ,MAGpBrB,KAAMA,MAGRH,UACG6B,OAAO,yBAA0BJ,SACjCK,MAAK,SAASC,aACMlC,EAAE,gBACRkC,KAAKA,OACX,QAIEjC,KAAKuB,KAAK,CACvB,CACEC,WAAY,0BACZC,KAAM,CACJJ,SAAUA,aAIP,GAAGZ,MAAK,SAASiB,UAEpBC,QAAU,CACZC,UAFSC,KAAKC,MAAMJ,MAGpBrB,KAAMA,MAGRH,UACG6B,OAAO,2BAA4BJ,SACnCK,MAAK,SAASC,aAEMlC,EAAE,kBACRkC,KAAKA,OACX,oBAMZ9B"} \ No newline at end of file +{"version":3,"file":"key_logger.min.js","sources":["../src/key_logger.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/key_logger\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\"], function(\r\n $,\r\n AJAX,\r\n str,\r\n templates) {\r\n\r\n var usersTable = {\r\n init: function(page) {\r\n str\r\n .get_strings([\r\n {key: \"field_require\", component: \"tiny_cursive\"}\r\n ])\r\n .done(function() {\r\n $(document).ready(function($) {\r\n $(\".popup_item\").on('click', function() {\r\n var mid = $(this).data(\"id\");\r\n $(\"#\" + mid).show();\r\n });\r\n $(\".link_icon\").on('click', function() {\r\n var smid = $(this).data(\"id\");\r\n $(\"#\" + smid).show();\r\n });\r\n $(\".modal-close \").on('click', function() {\r\n $(\".modal\").hide();\r\n });\r\n });\r\n usersTable.getusers(page);\r\n });\r\n },\r\n getusers: function(page) {\r\n $(\"#fgroup_id_buttonar\").hide();\r\n $(\"#id_coursename\").change(function() {\r\n var courseid = $(this).val();\r\n var promise1 = AJAX.call([\r\n {\r\n methodname: \"cursive_get_user_list\",\r\n args: {\r\n courseid: courseid,\r\n },\r\n },\r\n ]);\r\n promise1[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n var context = {\r\n tabledata: data,\r\n page: page,\r\n };\r\n // eslint-disable-next-line\r\n templates\r\n .render(\"tiny_cursive/user_list\", context)\r\n .then(function(html) {\r\n var filteredUser = $(\"#id_username\");\r\n filteredUser.html(html);\r\n return true;\r\n });\r\n });\r\n\r\n var promise2 = AJAX.call([\r\n {\r\n methodname: \"cursive_get_module_list\",\r\n args: {\r\n courseid: courseid,\r\n },\r\n }\r\n ]);\r\n promise2[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n var context = {\r\n tabledata: data,\r\n page: page,\r\n };\r\n // eslint-disable-next-line\r\n templates\r\n .render(\"tiny_cursive/module_list\", context)\r\n .then(function(html) {\r\n\r\n var filteredUser = $(\"#id_modulename\");\r\n filteredUser.html(html);\r\n return true;\r\n });\r\n });\r\n });\r\n },\r\n };\r\n return usersTable;\r\n});\r\n"],"names":["define","$","AJAX","str","templates","usersTable","init","page","get_strings","key","component","done","document","ready","on","mid","this","data","show","smid","hide","getusers","change","courseid","val","call","methodname","args","json","context","tabledata","JSON","parse","render","then","html"],"mappings":"AAsBAA,iCAAO,CAAC,SAAU,YAAa,WAAY,mBAAmB,SAC5DC,EACAC,KACAC,IACAC,eAEIC,WAAa,CACfC,KAAM,SAASC,MACbJ,IACGK,YAAY,CACX,CAACC,IAAK,gBAAiBC,UAAW,kBAEnCC,MAAK,WACJV,EAAEW,UAAUC,OAAM,SAASZ,GACzBA,EAAE,eAAea,GAAG,SAAS,eACvBC,IAAMd,EAAEe,MAAMC,KAAK,MACvBhB,EAAE,IAAMc,KAAKG,UAEfjB,EAAE,cAAca,GAAG,SAAS,eACtBK,KAAOlB,EAAEe,MAAMC,KAAK,MACxBhB,EAAE,IAAMkB,MAAMD,UAEhBjB,EAAE,iBAAiBa,GAAG,SAAS,WAC7Bb,EAAE,UAAUmB,aAGhBf,WAAWgB,SAASd,UAG1Bc,SAAU,SAASd,MACjBN,EAAE,uBAAuBmB,OACzBnB,EAAE,kBAAkBqB,QAAO,eACrBC,SAAWtB,EAAEe,MAAMQ,MACRtB,KAAKuB,KAAK,CACvB,CACEC,WAAY,wBACZC,KAAM,CACJJ,SAAUA,aAIP,GAAGZ,MAAK,SAASiB,UAEpBC,QAAU,CACZC,UAFSC,KAAKC,MAAMJ,MAGpBrB,KAAMA,MAGRH,UACG6B,OAAO,yBAA0BJ,SACjCK,MAAK,SAASC,aACMlC,EAAE,gBACRkC,KAAKA,OACX,QAIEjC,KAAKuB,KAAK,CACvB,CACEC,WAAY,0BACZC,KAAM,CACJJ,SAAUA,aAIP,GAAGZ,MAAK,SAASiB,UAEpBC,QAAU,CACZC,UAFSC,KAAKC,MAAMJ,MAGpBrB,KAAMA,MAGRH,UACG6B,OAAO,2BAA4BJ,SACnCK,MAAK,SAASC,aAEMlC,EAAE,kBACRkC,KAAKA,OACX,oBAMZ9B"} \ No newline at end of file diff --git a/amd/build/pdfexport.min.js.map b/amd/build/pdfexport.min.js.map index 40333a1a..a380df2c 100644 --- a/amd/build/pdfexport.min.js.map +++ b/amd/build/pdfexport.min.js.map @@ -1 +1 @@ -{"version":3,"file":"pdfexport.min.js","sources":["../src/pdfexport.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/**\n * Module for exporting PDF documents with cursive content. Handles PDF generation with custom formatting,\n * including margins, image quality settings and page orientation. Provides user feedback during export process.\n *\n * @module tiny_cursive/pdfexport\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 $ from 'jquery';\nimport Alert from 'core/modal';\nimport Factory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport * as str from 'core/str';\n\nexport const init = (data) => {\n\n if (data) {\n data = JSON.parse(document.getElementById('CursiveStudentData')?.dataset.submission || '{}');\n }\n\n if (!Object.keys(data).length) {\n Alert.create({\n type: Factory.types.ALERT,\n title: str.get_string('message', 'tool_dataprivacy'),\n body: str.get_string('nopaylod', 'tiny_cursive'),\n cssClass: 'modal-dialog modal-dialog-centered'\n }).then(modal => {\n modal.show();\n modal.getRoot().on(ModalEvents.hidden, () => {\n window.history.back();\n });\n return modal;\n }).catch(e => window.console.error(e));\n\n return;\n }\n\n $(function() {\n\n let option = {\n margin: [10, 6, 10, 6],\n filename: data.filename,\n image: {type: 'jpeg', quality: 1},\n html2canvas: {scale: 2},\n jsPDF: {unit: 'mm', format: 'a4', orientation: 'portrait'}};\n\n templates.render('tiny_cursive/export_pdf', data).then(html => {\n // eslint-disable-next-line\n return html2pdf().set(option).from(html).save().then(() => {\n // eslint-disable-next-line\n str.get_string('download_pdf_message', 'tiny_cursive').then(str =>{\n document.getElementById('loadermessage').textContent = str;\n return str;\n });\n\n return setTimeout(() => {\n document.getElementById('pdfexportLoader').remove();\n window.history.back();\n }, 4000);\n\n });\n\n }).catch(e => window.console.error(e));\n });\n};"],"names":["data","JSON","parse","document","getElementById","dataset","submission","Object","keys","length","option","margin","filename","image","type","quality","html2canvas","scale","jsPDF","unit","format","orientation","render","then","html","html2pdf","set","from","save","str","get_string","textContent","setTimeout","remove","window","history","back","catch","e","console","error","create","Factory","types","ALERT","title","body","cssClass","modal","show","getRoot","on","ModalEvents","hidden"],"mappings":";;;;;;;;49BAgCqBA,iCAEbA,OACAA,KAAOC,KAAKC,qCAAMC,SAASC,eAAe,oFAAuBC,QAAQC,aAAc,OAGtFC,OAAOC,KAAKR,MAAMS,4BAiBrB,eAEMC,OAAS,CACDC,OAAc,CAAC,GAAI,EAAG,GAAI,GAC1BC,SAAcZ,KAAKY,SACnBC,MAAc,CAACC,KAAM,OAAQC,QAAS,GACtCC,YAAc,CAACC,MAAO,GACtBC,MAAc,CAACC,KAAM,KAAMC,OAAQ,KAAMC,YAAa,gCAExDC,OAAO,0BAA2BtB,MAAMuB,MAAKC,MAE5CC,WAAWC,IAAIhB,QAAQiB,KAAKH,MAAMI,OAAOL,MAAK,KAEjDM,IAAIC,WAAW,uBAAwB,gBAAgBP,MAAKM,MACzD1B,SAASC,eAAe,iBAAiB2B,YAAcF,IAChDA,OAGJG,YAAW,KACb7B,SAASC,eAAe,mBAAmB6B,SAC3CC,OAAOC,QAAQC,SAChB,UAIRC,OAAMC,GAAKJ,OAAOK,QAAQC,MAAMF,uBAzC7BG,OAAO,CACT3B,KAAM4B,uBAAQC,MAAMC,MACpBC,MAAOhB,IAAIC,WAAW,UAAW,oBACjCgB,KAAMjB,IAAIC,WAAW,WAAY,gBACjCiB,SAAU,uCACXxB,MAAKyB,QACJA,MAAMC,OACND,MAAME,UAAUC,GAAGC,sBAAYC,QAAQ,KACnCnB,OAAOC,QAAQC,UAEZY,SACRX,OAAMC,GAAKJ,OAAOK,QAAQC,MAAMF"} \ No newline at end of file +{"version":3,"file":"pdfexport.min.js","sources":["../src/pdfexport.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n\r\n/**\r\n * Module for exporting PDF documents with cursive content. Handles PDF generation with custom formatting,\r\n * including margins, image quality settings and page orientation. Provides user feedback during export process.\r\n *\r\n * @module tiny_cursive/pdfexport\r\n * @copyright 2025 Cursive Technology, Inc. \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport templates from \"core/templates\";\r\nimport $ from 'jquery';\r\nimport Alert from 'core/modal';\r\nimport Factory from 'core/modal_factory';\r\nimport ModalEvents from 'core/modal_events';\r\nimport * as str from 'core/str';\r\n\r\nexport const init = (data) => {\r\n\r\n if (data) {\r\n data = JSON.parse(document.getElementById('CursiveStudentData')?.dataset.submission || '{}');\r\n }\r\n\r\n if (!Object.keys(data).length) {\r\n Alert.create({\r\n type: Factory.types.ALERT,\r\n title: str.get_string('message', 'tool_dataprivacy'),\r\n body: str.get_string('nopaylod', 'tiny_cursive'),\r\n cssClass: 'modal-dialog modal-dialog-centered'\r\n }).then(modal => {\r\n modal.show();\r\n modal.getRoot().on(ModalEvents.hidden, () => {\r\n window.history.back();\r\n });\r\n return modal;\r\n }).catch(e => window.console.error(e));\r\n\r\n return;\r\n }\r\n\r\n $(function() {\r\n\r\n let option = {\r\n margin: [10, 6, 10, 6],\r\n filename: data.filename,\r\n image: {type: 'jpeg', quality: 1},\r\n html2canvas: {scale: 2},\r\n jsPDF: {unit: 'mm', format: 'a4', orientation: 'portrait'}};\r\n\r\n templates.render('tiny_cursive/export_pdf', data).then(html => {\r\n // eslint-disable-next-line\r\n return html2pdf().set(option).from(html).save().then(() => {\r\n // eslint-disable-next-line\r\n str.get_string('download_pdf_message', 'tiny_cursive').then(str =>{\r\n document.getElementById('loadermessage').textContent = str;\r\n return str;\r\n });\r\n\r\n return setTimeout(() => {\r\n document.getElementById('pdfexportLoader').remove();\r\n window.history.back();\r\n }, 4000);\r\n\r\n });\r\n\r\n }).catch(e => window.console.error(e));\r\n });\r\n};"],"names":["data","JSON","parse","document","getElementById","dataset","submission","Object","keys","length","option","margin","filename","image","type","quality","html2canvas","scale","jsPDF","unit","format","orientation","render","then","html","html2pdf","set","from","save","str","get_string","textContent","setTimeout","remove","window","history","back","catch","e","console","error","create","Factory","types","ALERT","title","body","cssClass","modal","show","getRoot","on","ModalEvents","hidden"],"mappings":";;;;;;;;49BAgCqBA,iCAEbA,OACAA,KAAOC,KAAKC,qCAAMC,SAASC,eAAe,oFAAuBC,QAAQC,aAAc,OAGtFC,OAAOC,KAAKR,MAAMS,4BAiBrB,eAEMC,OAAS,CACDC,OAAc,CAAC,GAAI,EAAG,GAAI,GAC1BC,SAAcZ,KAAKY,SACnBC,MAAc,CAACC,KAAM,OAAQC,QAAS,GACtCC,YAAc,CAACC,MAAO,GACtBC,MAAc,CAACC,KAAM,KAAMC,OAAQ,KAAMC,YAAa,gCAExDC,OAAO,0BAA2BtB,MAAMuB,MAAKC,MAE5CC,WAAWC,IAAIhB,QAAQiB,KAAKH,MAAMI,OAAOL,MAAK,KAEjDM,IAAIC,WAAW,uBAAwB,gBAAgBP,MAAKM,MACzD1B,SAASC,eAAe,iBAAiB2B,YAAcF,IAChDA,OAGJG,YAAW,KACb7B,SAASC,eAAe,mBAAmB6B,SAC3CC,OAAOC,QAAQC,SAChB,UAIRC,OAAMC,GAAKJ,OAAOK,QAAQC,MAAMF,uBAzC7BG,OAAO,CACT3B,KAAM4B,uBAAQC,MAAMC,MACpBC,MAAOhB,IAAIC,WAAW,UAAW,oBACjCgB,KAAMjB,IAAIC,WAAW,WAAY,gBACjCiB,SAAU,uCACXxB,MAAKyB,QACJA,MAAMC,OACND,MAAME,UAAUC,GAAGC,sBAAYC,QAAQ,KACnCnB,OAAOC,QAAQC,UAEZY,SACRX,OAAMC,GAAKJ,OAAOK,QAAQC,MAAMF"} \ No newline at end of file diff --git a/amd/build/plugin.min.js.map b/amd/build/plugin.min.js.map index 30e6bff0..e01a29ff 100644 --- a/amd/build/plugin.min.js.map +++ b/amd/build/plugin.min.js.map @@ -1 +1 @@ -{"version":3,"file":"plugin.min.js","sources":["../src/plugin.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/plugin\n * @category TinyMCE Editor\n * @copyright 2025 CTI \n * @author Brain Station 23 \n */\n\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\nimport {component, pluginName} from './common';\nimport * as Autosaver from './autosaver';\nimport getConfig from 'core/ajax';\n\nexport default new Promise((resolve, reject) => {\n const page = [\n 'page-mod-assign-editsubmission',\n 'page-mod-quiz-attempt',\n 'page-mod-forum-view',\n 'page-mod-forum-post',\n 'page-mod-lesson-view',\n 'page-mod-pdfannotator-view']; // 'page-mod-oublog-editpost' excluded\n\n Promise.all([\n getTinyMCE(),\n getPluginMetadata(component, pluginName),\n ])\n .then(([tinyMCE, pluginMetadata]) => {\n tinyMCE.PluginManager.add(pluginName, (editor) => {\n\n getConfig.call([{\n methodname: \"cursive_get_config\",\n args: {courseid: M.cfg.courseId, cmid: M.cfg.contextInstanceId}\n }])[0].done((data) => {\n if (data.status && page.includes(document.body.id) && data.mod_state) {\n\n Autosaver.register(\n editor,\n data.sync_interval,\n data.userid,\n data.apikey_status,\n JSON.parse(data.plugins),\n JSON.parse(data.rubrics),\n JSON.parse(data.submission),\n JSON.parse(data.quizinfo),\n data.pastesetting\n );\n }\n }).fail((error) => {\n window.console.error('Error getting cursive config:', error);\n });\n\n return pluginMetadata;\n });\n return resolve(pluginName);\n })\n .catch((error) => {\n reject(error);\n });\n});\n"],"names":["Promise","resolve","reject","page","all","component","pluginName","then","_ref","tinyMCE","pluginMetadata","PluginManager","add","editor","call","methodname","args","courseid","M","cfg","courseId","cmid","contextInstanceId","done","data","status","includes","document","body","id","mod_state","Autosaver","register","sync_interval","userid","apikey_status","JSON","parse","plugins","rubrics","submission","quizinfo","pastesetting","fail","error","window","console","catch"],"mappings":"gwCA4Be,IAAIA,SAAQ,CAACC,QAASC,gBAC3BC,KAAO,CACT,iCACA,wBACA,sBACA,sBACA,uBACA,8BAEJH,QAAQI,IAAI,EACR,yBACA,4BAAkBC,kBAAWC,sBAE5BC,MAAKC,WAAEC,QAASC,4BACbD,QAAQE,cAAcC,IAAIN,oBAAaO,uBAEzBC,KAAK,CAAC,CACZC,WAAY,qBACZC,KAAM,CAACC,SAAUC,EAAEC,IAAIC,SAAUC,KAAMH,EAAEC,IAAIG,sBAC7C,GAAGC,MAAMC,OACLA,KAAKC,QAAUtB,KAAKuB,SAASC,SAASC,KAAKC,KAAOL,KAAKM,WAEvDC,UAAUC,SACNnB,OACAW,KAAKS,cACLT,KAAKU,OACLV,KAAKW,cACLC,KAAKC,MAAMb,KAAKc,SAChBF,KAAKC,MAAMb,KAAKe,SAChBH,KAAKC,MAAMb,KAAKgB,YAChBJ,KAAKC,MAAMb,KAAKiB,UAChBjB,KAAKkB,iBAGdC,MAAMC,QACLC,OAAOC,QAAQF,MAAM,gCAAiCA,UAGnDlC,kBAEJT,QAAQK,uBAElByC,OAAOH,QACJ1C,OAAO0C"} \ No newline at end of file +{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/plugin\r\n * @category TinyMCE Editor\r\n * @copyright 2025 CTI \r\n * @author Brain Station 23 \r\n */\r\n\r\nimport {getTinyMCE} from 'editor_tiny/loader';\r\nimport {getPluginMetadata} from 'editor_tiny/utils';\r\nimport {component, pluginName} from './common';\r\nimport * as Autosaver from './autosaver';\r\nimport getConfig from 'core/ajax';\r\n\r\nexport default new Promise((resolve, reject) => {\r\n const page = [\r\n 'page-mod-assign-editsubmission',\r\n 'page-mod-quiz-attempt',\r\n 'page-mod-forum-view',\r\n 'page-mod-forum-post',\r\n 'page-mod-lesson-view',\r\n 'page-mod-pdfannotator-view']; // 'page-mod-oublog-editpost' excluded\r\n\r\n Promise.all([\r\n getTinyMCE(),\r\n getPluginMetadata(component, pluginName),\r\n ])\r\n .then(([tinyMCE, pluginMetadata]) => {\r\n tinyMCE.PluginManager.add(pluginName, (editor) => {\r\n\r\n getConfig.call([{\r\n methodname: \"cursive_get_config\",\r\n args: {courseid: M.cfg.courseId, cmid: M.cfg.contextInstanceId}\r\n }])[0].done((data) => {\r\n if (data.status && page.includes(document.body.id) && data.mod_state) {\r\n\r\n Autosaver.register(\r\n editor,\r\n data.sync_interval,\r\n data.userid,\r\n data.apikey_status,\r\n JSON.parse(data.plugins),\r\n JSON.parse(data.rubrics),\r\n JSON.parse(data.submission),\r\n JSON.parse(data.quizinfo),\r\n data.pastesetting\r\n );\r\n }\r\n }).fail((error) => {\r\n window.console.error('Error getting cursive config:', error);\r\n });\r\n\r\n return pluginMetadata;\r\n });\r\n return resolve(pluginName);\r\n })\r\n .catch((error) => {\r\n reject(error);\r\n });\r\n});\r\n"],"names":["Promise","resolve","reject","page","all","component","pluginName","then","_ref","tinyMCE","pluginMetadata","PluginManager","add","editor","call","methodname","args","courseid","M","cfg","courseId","cmid","contextInstanceId","done","data","status","includes","document","body","id","mod_state","Autosaver","register","sync_interval","userid","apikey_status","JSON","parse","plugins","rubrics","submission","quizinfo","pastesetting","fail","error","window","console","catch"],"mappings":"gwCA4Be,IAAIA,SAAQ,CAACC,QAASC,gBAC3BC,KAAO,CACT,iCACA,wBACA,sBACA,sBACA,uBACA,8BAEJH,QAAQI,IAAI,EACR,yBACA,4BAAkBC,kBAAWC,sBAE5BC,MAAKC,WAAEC,QAASC,4BACbD,QAAQE,cAAcC,IAAIN,oBAAaO,uBAEzBC,KAAK,CAAC,CACZC,WAAY,qBACZC,KAAM,CAACC,SAAUC,EAAEC,IAAIC,SAAUC,KAAMH,EAAEC,IAAIG,sBAC7C,GAAGC,MAAMC,OACLA,KAAKC,QAAUtB,KAAKuB,SAASC,SAASC,KAAKC,KAAOL,KAAKM,WAEvDC,UAAUC,SACNnB,OACAW,KAAKS,cACLT,KAAKU,OACLV,KAAKW,cACLC,KAAKC,MAAMb,KAAKc,SAChBF,KAAKC,MAAMb,KAAKe,SAChBH,KAAKC,MAAMb,KAAKgB,YAChBJ,KAAKC,MAAMb,KAAKiB,UAChBjB,KAAKkB,iBAGdC,MAAMC,QACLC,OAAOC,QAAQF,MAAM,gCAAiCA,UAGnDlC,kBAEJT,QAAQK,uBAElByC,OAAOH,QACJ1C,OAAO0C"} \ No newline at end of file diff --git a/amd/build/replay.min.js.map b/amd/build/replay.min.js.map index 126f99bc..367381f1 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/replay\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\nimport {call as fetchJson} from 'core/ajax';\r\nimport templates from 'core/templates';\r\nimport $ from 'jquery';\r\nimport * as Str from 'core/str';\r\n\r\nexport default class Replay {\r\n constructor(elementId, filePath, speed = 1, loop = false, controllerId) {\r\n // Initialize core properties\r\n this.controllerId = controllerId || '';\r\n this.replayInProgress = false;\r\n this.speed = parseFloat(speed);\r\n this.loop = loop;\r\n this.highlightedChars = [];\r\n this.deletedChars = [];\r\n this.cursorPosition = 0;\r\n this.currentEventIndex = 0;\r\n this.totalEvents = 0;\r\n this.currentTime = 0;\r\n this.totalDuration = 0;\r\n this.usercomments = [];\r\n this.pasteTimestamps = [];\r\n this.isPasteEvent = false;\r\n this.isControlKeyPressed = false;\r\n this.isShiftKeyPressed = false;\r\n this.isMetaKeyPressed = false;\r\n this.text = '';\r\n this.pastedEvents = [];\r\n this.currentPasteIndex = 0;\r\n this.pastedChars = [];\r\n this.aiEvents = [];\r\n this.currentAiIndex = 0;\r\n this.aiChars = [];\r\n this.undoTimestamps = [];\r\n this.undoChars = [];\r\n\r\n const element = document.getElementById(elementId);\r\n if (!element) {\r\n throw new Error(`Element with id '${elementId}' not found`);\r\n }\r\n this.outputElement = element;\r\n\r\n // Load JSON data and initialize replay\r\n this.loadJSON(filePath).then(data => {\r\n if (data.status) {\r\n this.processData(data);\r\n this.totalEvents = this.logData.length;\r\n this.identifyPasteEvents();\r\n this.identifyUndoEvents();\r\n if (this.controllerId && this.logData) {\r\n this.constructController(this.controllerId);\r\n }\r\n this.startReplay();\r\n } else {\r\n this.handleNoSubmission();\r\n }\r\n return data;\r\n }).catch(error => {\r\n this.handleNoSubmission();\r\n window.console.error('Error loading JSON file:', error.message);\r\n });\r\n if (!localStorage.getItem('nopasteevent') || !localStorage.getItem('pasteEvent')) {\r\n Str.get_string('nopasteevent', 'tiny_cursive').then(str => {\r\n localStorage.setItem('nopasteevent', str);\r\n return str;\r\n }).catch(error => window.console.log(error));\r\n Str.get_string('pasteEvent', 'tiny_cursive').then(str => {\r\n localStorage.setItem('pasteEvent', str);\r\n return str;\r\n }).catch(error => window.console.log(error));\r\n }\r\n }\r\n\r\n // Process JSON data and normalize timestamps\r\n processData(data) {\r\n this.logData = JSON.parse(data.data);\r\n if (data.comments) {\r\n this.usercomments = Array.isArray(JSON.parse(data.comments)) ? JSON.parse(data.comments) : [];\r\n }\r\n if ('data' in this.logData) {\r\n this.logData = this.logData.data;\r\n }\r\n if ('payload' in this.logData) {\r\n this.logData = this.logData.payload;\r\n }\r\n for (let i = 0; i < this.logData.length; i++) {\r\n const event = this.logData[i];\r\n if (event.event === 'Paste') {\r\n if (typeof event.pastedContent === 'string' && event.pastedContent.trim() !== '') {\r\n this.pastedEvents.push(event.pastedContent);\r\n }\r\n }\r\n if (event.event === 'aiInsert' && event.aiContent) {\r\n this.aiEvents.push(event.aiContent);\r\n }\r\n }\r\n if (this.logData.length > 0 && this.logData[0].unixTimestamp) {\r\n const startTime = this.logData[0].unixTimestamp;\r\n this.logData = this.logData.map(event => ({\r\n ...event,\r\n normalizedTime: event.unixTimestamp - startTime\r\n }));\r\n this.totalDuration = this.logData[this.logData.length - 1].normalizedTime;\r\n }\r\n }\r\n\r\n async handleNoSubmission() {\r\n try {\r\n const [html, str] = await Promise.all([\r\n templates.render('tiny_cursive/no_submission'),\r\n Str.get_string('warningpayload', 'tiny_cursive')\r\n ]);\r\n const newElement = $(html).text(str);\r\n return $('.tiny_cursive').html(newElement);\r\n } catch (error) {\r\n window.console.error(error);\r\n return false;\r\n }\r\n }\r\n\r\n // Stop the replay and update play button icon\r\n stopReplay() {\r\n if (this.replayInProgress) {\r\n clearTimeout(this.replayTimeout);\r\n this.replayInProgress = false;\r\n if (this.playButton) {\r\n const playSvg = document.createElement('img');\r\n playSvg.src = M.util.image_url('playicon', 'tiny_cursive');\r\n this.playButton.querySelector('.play-icon').innerHTML = playSvg.outerHTML;\r\n }\r\n }\r\n }\r\n\r\n // Build the replay control UI (play button, scrubber, speed controls)\r\n constructController(controllerId) {\r\n this.replayInProgress = false;\r\n this.currentPosition = 0;\r\n this.speed = 1;\r\n if (this.replayIntervalId) {\r\n clearInterval(this.replayIntervalId);\r\n this.replayIntervalId = null;\r\n }\r\n\r\n const container = document.getElementById(controllerId);\r\n if (!container) {\r\n window.console.error('Container not found with ID:', controllerId);\r\n return;\r\n }\r\n\r\n const controlContainer = container.querySelector('.tiny_cursive_replay_control');\r\n if (!controlContainer) {\r\n window.console.error('Replay control container not found in:', controllerId);\r\n return;\r\n }\r\n controlContainer.innerHTML = '';\r\n\r\n this.buildControllerUI(controlContainer, container);\r\n controlContainer.querySelector('.tiny_cursive_loading_spinner')?.remove();\r\n }\r\n\r\n buildControllerUI(controlContainer, container) {\r\n const topRow = document.createElement('div');\r\n topRow.classList.add('tiny_cursive_top_row');\r\n\r\n this.playButton = this.createPlayButton();\r\n topRow.appendChild(this.playButton);\r\n\r\n const scrubberContainer = this.createScrubberContainer();\r\n topRow.appendChild(scrubberContainer);\r\n\r\n this.timeDisplay = this.createTimeDisplay();\r\n topRow.appendChild(this.timeDisplay);\r\n\r\n const bottomRow = document.createElement('div');\r\n bottomRow.classList.add('tiny_cursive_bottom_row');\r\n\r\n const speedContainer = this.createSpeedControls();\r\n bottomRow.appendChild(speedContainer);\r\n\r\n const pasteEventsToggle = this.createPasteEventsToggle(container);\r\n bottomRow.appendChild(pasteEventsToggle);\r\n\r\n controlContainer.appendChild(topRow);\r\n controlContainer.appendChild(bottomRow);\r\n container.appendChild(this.pasteEventsPanel);\r\n }\r\n\r\n createPlayButton() {\r\n const playButton = document.createElement('button');\r\n playButton.classList.add('tiny_cursive_play_button');\r\n const playSvg = document.createElement('i');\r\n playButton.innerHTML = `${playSvg.outerHTML}`;\r\n playButton.addEventListener('click', () => {\r\n if (this.replayInProgress) {\r\n this.stopReplay();\r\n } else {\r\n this.startReplay(false);\r\n }\r\n $('.tiny_cursive-nav-tab').find('.active').removeClass('active');\r\n $('a[id^=\"rep\"]').addClass('active');\r\n });\r\n return playButton;\r\n }\r\n\r\n createScrubberContainer() {\r\n const scrubberContainer = document.createElement('div');\r\n scrubberContainer.classList.add('tiny_cursive_scrubber_container');\r\n this.scrubberElement = document.createElement('input');\r\n this.scrubberElement.classList.add('tiny_cursive_timeline_scrubber', 'timeline-scrubber');\r\n this.scrubberElement.type = 'range';\r\n this.scrubberElement.max = '100';\r\n this.scrubberElement.min = '0';\r\n this.scrubberElement.value = '0';\r\n this.scrubberElement.addEventListener('input', () => {\r\n this.skipToTime(parseInt(this.scrubberElement.value, 10));\r\n });\r\n scrubberContainer.appendChild(this.scrubberElement);\r\n return scrubberContainer;\r\n }\r\n\r\n createTimeDisplay() {\r\n const timeDisplay = document.createElement('div');\r\n timeDisplay.classList.add('tiny_cursive_time_display');\r\n timeDisplay.textContent = '00:00 / 00:00';\r\n return timeDisplay;\r\n }\r\n\r\n createSpeedControls() {\r\n const speedContainer = document.createElement('div');\r\n speedContainer.classList.add('tiny_cursive_speed_controls', 'speed-controls');\r\n const speedLabel = document.createElement('span');\r\n speedLabel.classList.add('tiny_cursive_speed_label');\r\n speedLabel.textContent = 'Speed: ';\r\n speedContainer.appendChild(speedLabel);\r\n\r\n const speedGroup = document.createElement('div');\r\n speedGroup.classList.add('tiny_cursive_speed_group');\r\n [1, 1.5, 2, 5, 10].forEach(speed => {\r\n const speedBtn = document.createElement('button');\r\n speedBtn.textContent = `${speed}x`;\r\n speedBtn.classList.add('tiny_cursive_speed_btn', 'speed-btn');\r\n if (parseFloat(speed) === this.speed) {\r\n speedBtn.classList.add('active');\r\n }\r\n speedBtn.dataset.speed = speed;\r\n speedBtn.addEventListener('click', () => {\r\n document.querySelectorAll('.tiny_cursive_speed_btn').forEach(btn => btn.classList.remove('active'));\r\n speedBtn.classList.add('active');\r\n this.speed = parseFloat(speedBtn.dataset.speed);\r\n if (this.replayInProgress) {\r\n this.stopReplay();\r\n this.startReplay(false);\r\n }\r\n });\r\n speedGroup.appendChild(speedBtn);\r\n });\r\n speedContainer.appendChild(speedGroup);\r\n return speedContainer;\r\n }\r\n\r\n createPasteEventsToggle(container) {\r\n const pasteEventsToggle = document.createElement('div');\r\n pasteEventsToggle.classList.add('tiny_cursive_paste_events_toggle', 'paste-events-toggle');\r\n\r\n const pasteEventsIcon = document.createElement('span');\r\n const pasteIcon = document.createElement('img');\r\n pasteIcon.src = M.util.image_url('pasteicon', 'tiny_cursive');\r\n pasteEventsIcon.innerHTML = pasteIcon.outerHTML;\r\n pasteEventsIcon.classList.add('tiny_cursive_paste_events_icon');\r\n\r\n const pasteEventsText = document.createElement('span');\r\n pasteEventsText.textContent = localStorage.getItem('pasteEvent');\r\n\r\n this.pasteEventCount = document.createElement('span');\r\n this.pasteEventCount.textContent = `(${this.pasteTimestamps.length})`;\r\n this.pasteEventCount.className = 'paste-event-count';\r\n this.pasteEventCount.style.marginLeft = '2px';\r\n\r\n const chevronIcon = document.createElement('span');\r\n const chevron = document.createElement('i');\r\n chevron.className = 'fa fa-chevron-down';\r\n chevronIcon.innerHTML = chevron.outerHTML;\r\n chevronIcon.style.marginLeft = '5px';\r\n chevronIcon.style.transition = 'transform 0.3s ease';\r\n\r\n pasteEventsToggle.appendChild(pasteEventsIcon);\r\n pasteEventsToggle.appendChild(pasteEventsText);\r\n pasteEventsToggle.appendChild(this.pasteEventCount);\r\n pasteEventsToggle.appendChild(chevronIcon);\r\n\r\n this.pasteEventsPanel = this.createPasteEventsPanel(container);\r\n pasteEventsToggle.addEventListener('click', () => {\r\n const isHidden = this.pasteEventsPanel.style.display === 'none';\r\n this.pasteEventsPanel.style.display = isHidden ? 'block' : 'none';\r\n chevronIcon.style.transform = isHidden ? 'rotate(180deg)' : 'rotate(0deg)';\r\n });\r\n\r\n return pasteEventsToggle;\r\n }\r\n\r\n createPasteEventsPanel(container) {\r\n const existingPanel = container.querySelector('.paste-events-panel');\r\n if (existingPanel) {\r\n existingPanel.remove();\r\n }\r\n const pasteEventsPanel = document.createElement('div');\r\n pasteEventsPanel.classList.add('tiny_cursive_paste_events_panel', 'paste-events-panel');\r\n pasteEventsPanel.style.display = 'none';\r\n this.populatePasteEventsPanel(pasteEventsPanel);\r\n return pasteEventsPanel;\r\n }\r\n\r\n // Detect Ctrl+V paste events and sync with user comments\r\n identifyPasteEvents() {\r\n this.pasteTimestamps = [];\r\n let controlPressed = false;\r\n let metaPressed = false;\r\n /* eslint-disable no-unused-vars */\r\n let shiftPressed = false;\r\n let pasteCount = 0;\r\n\r\n for (let i = 0; i < this.logData.length; i++) {\r\n const event = this.logData[i];\r\n if (event.event?.toLowerCase() === 'keydown') {\r\n if (event.key === 'Control') {\r\n controlPressed = true;\r\n } else if (event.key === 'Meta') {\r\n metaPressed = true;\r\n } else if (event.key === 'Shift') {\r\n shiftPressed = true;\r\n } else if ((event.key === 'v' || event.key === 'V') && (controlPressed || metaPressed)) {\r\n if (this.pastedEvents[pasteCount]) {\r\n const timestamp = event.normalizedTime || 0;\r\n this.pasteTimestamps.push({\r\n index: pasteCount,\r\n time: timestamp,\r\n formattedTime: this.formatTime(timestamp),\r\n pastedText: this.pastedEvents[pasteCount],\r\n timestamp\r\n });\r\n }\r\n pasteCount++;\r\n controlPressed = false;\r\n shiftPressed = false;\r\n metaPressed = false;\r\n } else {\r\n controlPressed = false;\r\n shiftPressed = false;\r\n metaPressed = false;\r\n }\r\n }\r\n }\r\n\r\n if (this.pasteEventsPanel) {\r\n this.populatePasteEventsPanel(this.pasteEventsPanel);\r\n }\r\n }\r\n\r\n identifyUndoEvents() {\r\n this.undoTimestamps = [];\r\n let controlPressed = false;\r\n let metaPressed = false;\r\n let undoCount = 0;\r\n\r\n for (let i = 0; i < this.logData.length; i++) {\r\n const event = this.logData[i];\r\n if (event.event?.toLowerCase() === 'keydown') {\r\n if (event.key === 'Control') {\r\n controlPressed = true;\r\n } else if (event.key === 'Meta') {\r\n metaPressed = true;\r\n } else if ((event.key === 'z' || event.key === 'Z') && (controlPressed || metaPressed)) {\r\n const timestamp = event.normalizedTime || 0;\r\n this.undoTimestamps.push({\r\n index: undoCount,\r\n time: timestamp,\r\n formattedTime: this.formatTime(timestamp),\r\n timestamp\r\n });\r\n undoCount++;\r\n controlPressed = false;\r\n metaPressed = false;\r\n } else {\r\n controlPressed = false;\r\n metaPressed = false;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Populate the paste events panel with navigation\r\n populatePasteEventsPanel(panel) {\r\n panel.innerHTML = '';\r\n panel.classList.add('tiny_cursive_event_panel');\r\n\r\n if (!this.pasteTimestamps.length) {\r\n const noEventsMessage = document.createElement('div');\r\n noEventsMessage.className = 'no-paste-events-message p-3';\r\n noEventsMessage.textContent = localStorage.getItem('nopasteevent');\r\n panel.appendChild(noEventsMessage);\r\n return;\r\n }\r\n\r\n const carouselContainer = document.createElement('div');\r\n carouselContainer.classList.add('tiny_cursive_paste_events_carousel', 'paste-events-carousel');\r\n\r\n const navigationRow = document.createElement('div');\r\n navigationRow.classList.add('paste-events-navigation', 'tiny_cursive_navigation_row');\r\n\r\n const counterDisplay = document.createElement('div');\r\n counterDisplay.classList.add('paste-events-counter', 'tiny_cursive_counter_display');\r\n counterDisplay.textContent = 'Paste Events';\r\n\r\n const navButtons = document.createElement('div');\r\n navButtons.classList.add('tiny_cursive_nav_buttons');\r\n const prevButton = document.createElement('button');\r\n prevButton.classList.add('paste-event-prev-btn', 'tiny_cursive_nav_button');\r\n prevButton.innerHTML = '';\r\n\r\n const nextButton = document.createElement('button');\r\n nextButton.classList.add('paste-event-next-btn', 'tiny_cursive_nav_button');\r\n nextButton.innerHTML = '';\r\n nextButton.disabled = this.pasteTimestamps.length <= 1;\r\n\r\n navButtons.appendChild(prevButton);\r\n navButtons.appendChild(nextButton);\r\n navigationRow.appendChild(counterDisplay);\r\n navigationRow.appendChild(navButtons);\r\n\r\n const contentContainer = document.createElement('div');\r\n contentContainer.className = 'paste-events-content tiny_cursive_content_container';\r\n contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[0]));\r\n\r\n carouselContainer.appendChild(navigationRow);\r\n carouselContainer.appendChild(contentContainer);\r\n panel.appendChild(carouselContainer);\r\n\r\n let currentIndex = 0;\r\n const updateDisplay = () => {\r\n contentContainer.innerHTML = '';\r\n contentContainer.appendChild(this.createPasteEventDisplay(this.pasteTimestamps[currentIndex]));\r\n counterDisplay.textContent = 'Paste Events';\r\n prevButton.disabled = currentIndex === 0;\r\n prevButton.style.opacity = currentIndex === 0 ? '0.5' : '1';\r\n nextButton.disabled = currentIndex === this.pasteTimestamps.length - 1;\r\n nextButton.style.opacity = currentIndex === this.pasteTimestamps.length - 1 ? '0.5' : '1';\r\n };\r\n\r\n prevButton.addEventListener('click', () => {\r\n if (currentIndex > 0) {\r\n currentIndex--;\r\n updateDisplay();\r\n }\r\n });\r\n\r\n nextButton.addEventListener('click', () => {\r\n if (currentIndex < this.pasteTimestamps.length - 1) {\r\n currentIndex++;\r\n updateDisplay();\r\n }\r\n });\r\n }\r\n\r\n createPasteEventDisplay(pasteEvent) {\r\n const eventRow = document.createElement('div');\r\n eventRow.className = 'tiny_cursive_event_row';\r\n\r\n const headerRow = document.createElement('div');\r\n headerRow.className = 'tiny_cursive_header_row';\r\n\r\n const textContainer = document.createElement('div');\r\n textContainer.className = 'tiny_cursive_text_container';\r\n\r\n const timestampContainer = document.createElement('div');\r\n timestampContainer.className = 'paste-event-timestamp tiny_cursive_paste_event_timestamp';\r\n timestampContainer.textContent = pasteEvent.formattedTime;\r\n\r\n const pastedTextContainer = document.createElement('div');\r\n pastedTextContainer.className = 'paste-event-text tiny_cursive_pasted_text_container';\r\n pastedTextContainer.textContent = pasteEvent.pastedText;\r\n\r\n textContainer.appendChild(timestampContainer);\r\n textContainer.appendChild(pastedTextContainer);\r\n\r\n const playButton = document.createElement('button');\r\n playButton.className = 'paste-event-play-btn tiny_cursive_seekplay_button';\r\n const playIcon = document.createElement('img');\r\n playIcon.src = M.util.image_url('seekplayicon', 'tiny_cursive');\r\n playButton.innerHTML = playIcon.outerHTML;\r\n playButton.addEventListener('click', () => this.jumpToTimestamp(pasteEvent.timestamp));\r\n\r\n headerRow.appendChild(textContainer);\r\n headerRow.appendChild(playButton);\r\n eventRow.appendChild(headerRow);\r\n\r\n return eventRow;\r\n }\r\n\r\n // Jump to a specific timestamp in the replay\r\n jumpToTimestamp(timestamp) {\r\n const percentage = this.totalDuration > 0 ? (timestamp / this.totalDuration) * 100 : 0;\r\n this.skipToTime(percentage);\r\n if (!this.replayInProgress) {\r\n this.startReplay(false);\r\n }\r\n }\r\n\r\n setScrubberVal(value) {\r\n if (this.scrubberElement) {\r\n this.scrubberElement.value = String(value);\r\n if (this.timeDisplay) {\r\n const displayTime = Math.min(this.currentTime, this.totalDuration);\r\n this.timeDisplay.textContent = `${this.formatTime(displayTime)} / ${this.formatTime(this.totalDuration)}`;\r\n }\r\n }\r\n }\r\n\r\n loadJSON(filePath) {\r\n return fetchJson([{\r\n methodname: 'cursive_get_reply_json',\r\n args: {filepath: filePath}\r\n }])[0].done(response => response).fail(error => {\r\n throw new Error(`Error loading JSON file: ${error.message}`);\r\n });\r\n }\r\n\r\n formatTime(ms) {\r\n const seconds = Math.floor(ms / 1000);\r\n const minutes = Math.floor(seconds / 60);\r\n const remainingSeconds = seconds % 60;\r\n return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;\r\n }\r\n\r\n // Start or restart the replay\r\n startReplay(reset = true) {\r\n if (this.replayInProgress) {\r\n clearTimeout(this.replayTimeout);\r\n }\r\n const atEnd = (this.totalDuration > 0 && this.currentTime >= this.totalDuration) ||\r\n (this.currentEventIndex >= this.totalEvents);\r\n if (atEnd && !reset) {\r\n reset = true;\r\n }\r\n this.replayInProgress = true;\r\n if (reset) {\r\n this.outputElement.innerHTML = '';\r\n this.text = '';\r\n this.cursorPosition = 0;\r\n this.currentEventIndex = 0;\r\n this.currentTime = 0;\r\n this.highlightedChars = [];\r\n this.deletedChars = [];\r\n this.isControlKeyPressed = false;\r\n this.isMetaKeyPressed = false;\r\n this.currentPasteIndex = 0;\r\n this.pastedChars = [];\r\n this.currentAiIndex = 0;\r\n this.aiChars = [];\r\n }\r\n if (this.playButton) {\r\n const pauseSvg = document.createElement('i');\r\n pauseSvg.className = 'fa fa-pause';\r\n this.playButton.querySelector('.play-icon').innerHTML = pauseSvg.outerHTML;\r\n }\r\n this.replayLog();\r\n }\r\n\r\n // Process events in sequence to simulate typing\r\n replayLog() {\r\n if (!this.replayInProgress) {\r\n this.updateDisplayText(this.text, this.cursorPosition, [], []);\r\n return;\r\n }\r\n\r\n while (this.currentEventIndex < this.logData.length) {\r\n const event = this.logData[this.currentEventIndex];\r\n if (event.normalizedTime && event.normalizedTime > this.currentTime) {\r\n break;\r\n }\r\n\r\n let text = this.text || '';\r\n let cursor = this.cursorPosition;\r\n let updatedHighlights = [...this.highlightedChars];\r\n let updatedDeleted = [...this.deletedChars];\r\n\r\n if (event.rePosition !== undefined && (this.currentEventIndex === 0 ||\r\n event.event === 'mouseDown' || event.event === 'mouseUp')) {\r\n cursor = Math.max(0, Math.min(event.rePosition, text.length));\r\n }\r\n\r\n if (event.event?.toLowerCase() === 'keydown') {\r\n ({text, cursor, updatedHighlights, updatedDeleted} =\r\n this.processKeydownEvent(event, text, cursor, updatedHighlights, updatedDeleted));\r\n } else if (event.event === 'aiInsert') {\r\n ({text, cursor, updatedHighlights, updatedDeleted} =\r\n this.processAiInsertEvent(event, text, cursor, updatedHighlights, updatedDeleted));\r\n }\r\n\r\n this.text = text;\r\n this.cursorPosition = cursor;\r\n this.highlightedChars = updatedHighlights.filter(h => !h.expiresAt || h.expiresAt > this.currentTime);\r\n this.deletedChars = updatedDeleted.filter(d => !d.expiresAt || d.expiresAt > this.currentTime);\r\n\r\n this.currentEventIndex++;\r\n }\r\n\r\n this.updateDisplayText(this.text, this.cursorPosition, this.highlightedChars, this.deletedChars);\r\n if (this.totalDuration > 0) {\r\n const percentComplete = Math.min((this.currentTime / this.totalDuration) * 100, 100);\r\n this.setScrubberVal(percentComplete);\r\n }\r\n\r\n if (this.replayInProgress) {\r\n const baseIncrement = 100;\r\n const incrementTime = baseIncrement / this.speed;\r\n this.currentTime += baseIncrement;\r\n if (this.currentEventIndex >= this.totalEvents) {\r\n if (this.loop) {\r\n this.startReplay(true);\r\n } else {\r\n this.stopReplay();\r\n this.updateDisplayText(this.text, this.cursorPosition, [], []);\r\n }\r\n } else {\r\n this.replayTimeout = setTimeout(() => this.replayLog(), incrementTime);\r\n }\r\n }\r\n }\r\n\r\n getLineAndColumn(text, pos) {\r\n const before = text.substring(0, pos);\r\n const lineIndex = before.split('\\n').length - 1;\r\n const col = before.length - before.lastIndexOf('\\n') - 1;\r\n return {lineIndex, col};\r\n }\r\n\r\n processAiInsertEvent(event, text, cursor, highlights, deletions) {\r\n if (this.aiEvents && this.currentAiIndex < this.aiEvents.length) {\r\n const aiContent = this.aiEvents[this.currentAiIndex];\r\n // Use event.rePosition which points to where the word to replace is\r\n const targetPosition = event.rePosition;\r\n\r\n ({text, cursor} = this.handleAiReplacement(aiContent, text, targetPosition, cursor, deletions));\r\n this.currentAiIndex++;\r\n }\r\n return {\r\n text,\r\n cursor,\r\n updatedHighlights: highlights,\r\n updatedDeleted: deletions\r\n };\r\n }\r\n\r\n handleAiReplacement(aiContent, text, targetPosition, currentCursor, deletions) {\r\n const insertText = aiContent || '';\r\n const aiWords = insertText.trim().split(/\\s+/);\r\n const isMultiWord = aiWords.length > 1;\r\n const isNewLineInsertion = insertText.startsWith('\\n') || insertText.endsWith('\\n');\r\n\r\n const {wordStart, wordEnd} = this.findWordToReplace(\r\n text,\r\n targetPosition,\r\n currentCursor,\r\n aiWords,\r\n isMultiWord,\r\n isNewLineInsertion\r\n );\r\n\r\n const wordToReplace = text.substring(wordStart, wordEnd);\r\n\r\n // Mark replaced characters as deleted\r\n this.markCharsAsDeleted(wordToReplace, wordStart, deletions);\r\n\r\n // Perform the replacement\r\n const replacedLength = wordToReplace.length;\r\n text = text.substring(0, wordStart) + insertText + text.substring(wordEnd);\r\n const positionDiff = insertText.length - replacedLength;\r\n\r\n // Calculate new cursor position\r\n const newCursor = this.calculateNewCursorPosition(\r\n currentCursor,\r\n targetPosition,\r\n wordStart,\r\n wordEnd,\r\n insertText,\r\n isNewLineInsertion\r\n );\r\n\r\n // Update character tracking arrays\r\n this.updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText);\r\n\r\n return {text, cursor: newCursor};\r\n }\r\n\r\n findWordToReplace(text, targetPosition, currentCursor, aiWords, isMultiWord, isNewLineInsertion) {\r\n if (isNewLineInsertion) {\r\n return {wordStart: currentCursor, wordEnd: currentCursor};\r\n }\r\n\r\n const {lineStart, lineEnd} = this.findLineRange(text, targetPosition);\r\n const lineText = text.substring(lineStart, lineEnd);\r\n const words = this.extractWordsFromLine(lineText, lineStart);\r\n\r\n if (words.length === 0) {\r\n return {wordStart: currentCursor, wordEnd: currentCursor};\r\n }\r\n\r\n if (isMultiWord) {\r\n return this.findMultiWordMatch(words, aiWords, targetPosition);\r\n } else {\r\n return this.findSingleWordMatch(words, aiWords[0], targetPosition);\r\n }\r\n }\r\n\r\n findLineRange(text, targetPosition) {\r\n let lineStart = 0;\r\n for (let i = targetPosition - 1; i >= 0; i--) {\r\n if (text[i] === '\\n') {\r\n lineStart = i + 1;\r\n break;\r\n }\r\n }\r\n\r\n let lineEnd = text.length;\r\n for (let i = targetPosition; i < text.length; i++) {\r\n if (text[i] === '\\n') {\r\n lineEnd = i;\r\n break;\r\n }\r\n }\r\n\r\n return {lineStart, lineEnd};\r\n }\r\n\r\n extractWordsFromLine(lineText, lineStart) {\r\n const words = [];\r\n let pos = 0;\r\n\r\n while (pos < lineText.length) {\r\n // Skip spaces\r\n while (pos < lineText.length && lineText[pos] === ' ') {\r\n pos++;\r\n }\r\n if (pos >= lineText.length) {\r\n break;\r\n }\r\n\r\n // Extract word\r\n const start = pos;\r\n while (pos < lineText.length && lineText[pos] !== ' ') {\r\n pos++;\r\n }\r\n\r\n if (pos > start) {\r\n words.push({\r\n text: lineText.substring(start, pos),\r\n start: lineStart + start,\r\n end: lineStart + pos\r\n });\r\n }\r\n }\r\n\r\n return words;\r\n }\r\n\r\n findMultiWordMatch(words, aiWords, targetPosition) {\r\n let bestMatch = {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\r\n\r\n for (let i = 0; i < words.length; i++) {\r\n const matchResult = this.evaluateMultiWordSequence(words, aiWords, i, targetPosition);\r\n\r\n if (matchResult.totalScore > bestMatch.score ||\r\n (matchResult.totalScore === bestMatch.score &&\r\n matchResult.similarityScore > bestMatch.similarityScore)) {\r\n bestMatch = matchResult;\r\n }\r\n }\r\n\r\n if (bestMatch.score > 10) {\r\n return {wordStart: bestMatch.start, wordEnd: bestMatch.end};\r\n } else {\r\n const closest = this.findClosestWord(words, targetPosition);\r\n return {wordStart: closest.start, wordEnd: closest.end};\r\n }\r\n }\r\n\r\n evaluateMultiWordSequence(words, aiWords, startIndex, targetPosition) {\r\n const seqWords = [];\r\n for (let j = 0; j < aiWords.length && startIndex + j < words.length; j++) {\r\n seqWords.push(words[startIndex + j]);\r\n }\r\n\r\n if (seqWords.length === 0) {\r\n return {start: -1, end: -1, score: -1, wordCount: 0, similarityScore: 0};\r\n }\r\n\r\n const similarityScore = this.calculateSequenceSimilarity(aiWords, seqWords);\r\n const positionScore = this.calculatePositionScore(seqWords, targetPosition);\r\n const totalScore = similarityScore + positionScore + seqWords.length;\r\n\r\n return {\r\n start: seqWords[0].start,\r\n end: seqWords[seqWords.length - 1].end,\r\n score: totalScore,\r\n wordCount: seqWords.length,\r\n similarityScore: similarityScore\r\n };\r\n }\r\n\r\n calculateSequenceSimilarity(aiWords, seqWords) {\r\n let similarityScore = 0;\r\n const compareLength = Math.min(seqWords.length, aiWords.length);\r\n\r\n for (let k = 0; k < compareLength; k++) {\r\n const ai = aiWords[k].toLowerCase();\r\n const seq = seqWords[k].text.toLowerCase();\r\n\r\n if (ai === seq) {\r\n similarityScore += 10;\r\n } else {\r\n const similarity = this.calculateSimilarity(ai, seq);\r\n similarityScore += similarity * 10;\r\n }\r\n }\r\n\r\n return similarityScore;\r\n }\r\n\r\n calculatePositionScore(seqWords, targetPosition) {\r\n let positionScore = 0;\r\n const seqStart = seqWords[0].start;\r\n const seqEndPos = seqWords[seqWords.length - 1].end;\r\n\r\n if (targetPosition >= seqStart && targetPosition <= seqEndPos) {\r\n positionScore += 10;\r\n if (targetPosition >= seqWords[0].start && targetPosition <= seqWords[0].end) {\r\n positionScore += 5;\r\n }\r\n }\r\n\r\n return positionScore;\r\n }\r\n\r\n findSingleWordMatch(words, aiWord, targetPosition) {\r\n const aiWordLower = aiWord.toLowerCase();\r\n const bestSimilarityMatch = this.findBestSimilarityMatch(words, aiWordLower);\r\n\r\n if (bestSimilarityMatch.score > 0.5) {\r\n return {wordStart: bestSimilarityMatch.word.start, wordEnd: bestSimilarityMatch.word.end};\r\n }\r\n\r\n const bestPositionMatch = this.findBestPositionMatch(words, aiWordLower, targetPosition);\r\n\r\n if (bestPositionMatch.word) {\r\n return {wordStart: bestPositionMatch.word.start, wordEnd: bestPositionMatch.word.end};\r\n }\r\n\r\n // Fallback to position-based word boundary\r\n return this.findWordBoundaryAtPosition(words[0].start, words[words.length - 1].end,\r\n targetPosition, this.text);\r\n }\r\n\r\n findBestSimilarityMatch(words, aiWordLower) {\r\n let bestMatch = {word: null, score: 0};\r\n\r\n for (const word of words) {\r\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\r\n const wordLower = word.text.toLowerCase();\r\n\r\n // Penalize short prefix matches\r\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\r\n similarity = similarity * 0.3;\r\n }\r\n\r\n if (similarity > bestMatch.score) {\r\n bestMatch = {word, score: similarity};\r\n }\r\n }\r\n\r\n return bestMatch;\r\n }\r\n\r\n findBestPositionMatch(words, aiWordLower, targetPosition) {\r\n let bestMatch = {word: null, score: -1};\r\n\r\n for (const word of words) {\r\n let score = this.calculateWordScore(word, aiWordLower, targetPosition);\r\n\r\n if (score > bestMatch.score) {\r\n bestMatch = {word, score};\r\n }\r\n }\r\n\r\n return bestMatch;\r\n }\r\n\r\n calculateWordScore(word, aiWordLower, targetPosition) {\r\n let score = 0;\r\n\r\n // Position score\r\n if (targetPosition >= word.start && targetPosition <= word.end) {\r\n score += 30;\r\n } else {\r\n const distance = Math.min(\r\n Math.abs(targetPosition - word.start),\r\n Math.abs(targetPosition - word.end)\r\n );\r\n score += Math.max(0, 20 - distance);\r\n }\r\n\r\n // Similarity score with penalty\r\n let similarity = this.calculateSimilarity(aiWordLower, word.text.toLowerCase());\r\n const wordLower = word.text.toLowerCase();\r\n if (wordLower.length < aiWordLower.length * 0.5 && aiWordLower.startsWith(wordLower)) {\r\n similarity = similarity * 0.3;\r\n }\r\n score += similarity * 10;\r\n\r\n return score;\r\n }\r\n\r\n findWordBoundaryAtPosition(lineStart, lineEnd, targetPosition, text) {\r\n let wordStart = targetPosition;\r\n while (wordStart > lineStart && text[wordStart - 1] !== ' ' && text[wordStart - 1] !== '\\n') {\r\n wordStart--;\r\n }\r\n let wordEnd = targetPosition;\r\n while (wordEnd < lineEnd && text[wordEnd] !== ' ' && text[wordEnd] !== '\\n') {\r\n wordEnd++;\r\n }\r\n return {wordStart, wordEnd};\r\n }\r\n\r\n markCharsAsDeleted(wordToReplace, wordStart, deletions) {\r\n if (wordToReplace.length > 0) {\r\n for (let i = 0; i < wordToReplace.length; i++) {\r\n deletions.push({\r\n index: wordStart + i,\r\n chars: wordToReplace[i],\r\n time: this.currentTime,\r\n expiresAt: this.currentTime + 2000\r\n });\r\n }\r\n }\r\n }\r\n\r\n calculateNewCursorPosition(currentCursor, targetPosition, wordStart, wordEnd, insertText, isNewLineInsertion) {\r\n if (isNewLineInsertion) {\r\n return wordStart + insertText.length;\r\n }\r\n\r\n if (targetPosition >= wordStart && targetPosition <= wordEnd) {\r\n return wordStart + insertText.length;\r\n }\r\n\r\n const positionDiff = insertText.length - (wordEnd - wordStart);\r\n\r\n if (currentCursor >= wordEnd) {\r\n return currentCursor + positionDiff;\r\n } else if (currentCursor > wordStart && currentCursor < wordEnd) {\r\n return wordStart + insertText.length;\r\n }\r\n\r\n return currentCursor;\r\n }\r\n\r\n updateCharacterIndices(wordStart, wordEnd, positionDiff, insertText) {\r\n // Update pasted character indices\r\n this.updatePastedCharIndices(wordStart, wordEnd, positionDiff);\r\n\r\n // Mark characters as AI-inserted\r\n this.markCharsAsAiInserted(wordStart, insertText);\r\n\r\n // Update AI character indices\r\n this.updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText);\r\n }\r\n\r\n updatePastedCharIndices(wordStart, wordEnd, positionDiff) {\r\n if (this.pastedChars) {\r\n this.pastedChars = this.pastedChars.map(p => {\r\n if (p.index >= wordEnd) {\r\n return {...p, index: p.index + positionDiff};\r\n } else if (p.index >= wordStart && p.index < wordEnd) {\r\n return null;\r\n }\r\n return p;\r\n }).filter(p => p !== null);\r\n }\r\n }\r\n\r\n markCharsAsAiInserted(wordStart, insertText) {\r\n if (!this.aiChars) {\r\n this.aiChars = [];\r\n }\r\n\r\n if (insertText.trim() !== '') {\r\n for (let i = 0; i < insertText.length; i++) {\r\n this.aiChars.push({\r\n index: wordStart + i,\r\n chars: insertText[i]\r\n });\r\n }\r\n }\r\n }\r\n\r\n updateAiCharIndices(wordStart, wordEnd, positionDiff, insertText) {\r\n const justAddedIndices = new Set();\r\n for (let i = 0; i < insertText.length; i++) {\r\n justAddedIndices.add(wordStart + i);\r\n }\r\n\r\n this.aiChars = this.aiChars.map(p => {\r\n if (!justAddedIndices.has(p.index)) {\r\n if (p.index >= wordEnd) {\r\n return {...p, index: p.index + positionDiff};\r\n } else if (p.index >= wordStart && p.index < wordEnd) {\r\n return null;\r\n }\r\n }\r\n return p;\r\n }).filter(p => p !== null);\r\n }\r\n\r\n // Calculate similarity between two strings\r\n calculateSimilarity(str1, str2) {\r\n if (str1 === str2) {\r\n return 1;\r\n }\r\n if (str1.length === 0 || str2.length === 0) {\r\n return 0;\r\n }\r\n\r\n // Check if one string is a prefix of the other\r\n if (str1.startsWith(str2) || str2.startsWith(str1)) {\r\n return 0.8;\r\n }\r\n\r\n // Levenshtein distance\r\n const len1 = str1.length;\r\n const len2 = str2.length;\r\n const matrix = Array(len2 + 1).fill(null).map(() => Array(len1 + 1).fill(0));\r\n\r\n for (let i = 0; i <= len1; i++) {\r\n matrix[0][i] = i;\r\n }\r\n for (let j = 0; j <= len2; j++) {\r\n matrix[j][0] = j;\r\n }\r\n\r\n for (let j = 1; j <= len2; j++) {\r\n for (let i = 1; i <= len1; i++) {\r\n const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;\r\n matrix[j][i] = Math.min(\r\n matrix[j][i - 1] + 1,\r\n matrix[j - 1][i] + 1,\r\n matrix[j - 1][i - 1] + cost\r\n );\r\n }\r\n }\r\n\r\n const maxLen = Math.max(len1, len2);\r\n return 1 - (matrix[len2][len1] / maxLen);\r\n }\r\n\r\n // Find the word closest to a target position\r\n findClosestWord(words, targetPosition) {\r\n if (words.length === 0) {\r\n return {start: targetPosition, end: targetPosition};\r\n }\r\n\r\n let closest = words[0];\r\n let minDistance = Math.min(\r\n Math.abs(targetPosition - words[0].start),\r\n Math.abs(targetPosition - words[0].end)\r\n );\r\n\r\n for (const word of words) {\r\n if (targetPosition >= word.start && targetPosition <= word.end) {\r\n return word;\r\n }\r\n\r\n const distance = Math.min(\r\n Math.abs(targetPosition - word.start),\r\n Math.abs(targetPosition - word.end)\r\n );\r\n\r\n if (distance < minDistance) {\r\n minDistance = distance;\r\n closest = word;\r\n }\r\n }\r\n\r\n return closest;\r\n }\r\n\r\n // Handle keydown events (e.g., typing, backspace, Ctrl+V)\r\n processKeydownEvent(event, text, cursor, highlights, deletions) {\r\n const key = event.key;\r\n const charToInsert = this.applyKey(key);\r\n\r\n // Handle copy operation (Ctrl+C)\r\n if (this.isCopyOperation(key)) {\r\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\r\n }\r\n\r\n // Handle undo operation (Ctrl+Z)\r\n if (this.isUndoOperation(key)) {\r\n return this.handleUndoOperation(event, text, cursor, highlights, deletions);\r\n }\r\n\r\n // Detect selection for current event\r\n const currentEventIndex = this.currentEventIndex;\r\n const selection = this.detectSelection(currentEventIndex);\r\n\r\n // Handle paste operation (Ctrl+V)\r\n if (this.isPasteOperation(key, event)) {\r\n return this.handlePasteOperation(event, selection, text, cursor, highlights, deletions);\r\n }\r\n\r\n // Update modifier key states\r\n this.updateModifierStates(key);\r\n\r\n // Handle selection deletion with Backspace/Delete\r\n if (this.isSelectionDeletion(key, selection)) {\r\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\r\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\r\n }\r\n\r\n // Process various key operations\r\n return this.processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection);\r\n }\r\n\r\n isCopyOperation(key) {\r\n return (key === 'c' || key === 'C') && (this.isControlKeyPressed || this.isMetaKeyPressed);\r\n }\r\n\r\n isUndoOperation(key) {\r\n return (key === 'z' || key === 'Z') && (this.isControlKeyPressed || this.isMetaKeyPressed);\r\n }\r\n\r\n handleUndoOperation(event, text, cursor, highlights, deletions) {\r\n const nextEventIndex = this.currentEventIndex + 1;\r\n if (nextEventIndex < this.logData.length) {\r\n const nextEvent = this.logData[nextEventIndex];\r\n\r\n if (nextEvent.event === 'keyUp' && (nextEvent.key === 'z' || nextEvent.key === 'Z')) {\r\n const newPosition = nextEvent.rePosition;\r\n if (newPosition < cursor && text.length > 0) {\r\n const textBeforeUndo = text;\r\n text = text.substring(0, newPosition) + text.substring(cursor);\r\n cursor = newPosition;\r\n\r\n // Mark as deleted for visual effect\r\n for (let i = 0; i < textBeforeUndo.length && i < cursor; i++) {\r\n deletions.push({\r\n index: newPosition,\r\n chars: textBeforeUndo[i],\r\n time: this.currentTime,\r\n expiresAt: this.currentTime + 2000\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n this.isControlKeyPressed = false;\r\n this.isMetaKeyPressed = false;\r\n\r\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\r\n }\r\n\r\n isPasteOperation(key, event) {\r\n if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\r\n return (event.pastedContent && event.pastedContent.trim() !== '') ||\r\n (this.pastedEvents && this.currentPasteIndex < this.pastedEvents.length);\r\n }\r\n return false;\r\n }\r\n\r\n handlePasteOperation(event, selection, text, cursor, highlights, deletions) {\r\n const pastedContent = event.pastedContent || this.pastedEvents[this.currentPasteIndex];\r\n\r\n if (selection) {\r\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\r\n }\r\n\r\n ({text, cursor} = this.handlePasteInsert(pastedContent, text, cursor));\r\n this.currentPasteIndex++;\r\n this.resetModifierStates();\r\n this.isPasteEvent = false;\r\n\r\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\r\n }\r\n\r\n resetModifierStates() {\r\n this.isControlKeyPressed = false;\r\n this.isShiftKeyPressed = false;\r\n this.isMetaKeyPressed = false;\r\n }\r\n\r\n isSelectionDeletion(key, selection) {\r\n return (key === 'Backspace' || key === 'Delete') && selection && selection.length > 1;\r\n }\r\n\r\n processKeyOperation(key, charToInsert, text, cursor, highlights, deletions, selection) {\r\n if (this.isCtrlBackspace(key, cursor)) {\r\n ({text, cursor} = this.handleCtrlBackspace(text, cursor, deletions));\r\n } else if (this.isCtrlDelete(key, cursor, text)) {\r\n ({text} = this.handleCtrlDelete(text, cursor, deletions));\r\n } else if (this.isCtrlArrowMove(key)) {\r\n cursor = this.handleCtrlArrowMove(key, text, cursor);\r\n } else if (this.isRegularBackspace(key, cursor)) {\r\n ({text, cursor} = this.handleBackspace(text, cursor, deletions));\r\n } else if (this.isRegularDelete(key, cursor, text)) {\r\n ({text} = this.handleDelete(text, cursor, deletions));\r\n } else if (this.isArrowUp(key)) {\r\n cursor = this.handleArrowUp(text, cursor);\r\n } else if (this.isArrowDown(key)) {\r\n cursor = this.handleArrowDown(text, cursor);\r\n } else if (this.isRegularArrowMove(key)) {\r\n cursor = this.handleArrowMove(key, text, cursor);\r\n } else if (charToInsert && charToInsert.length > 0) {\r\n if (selection && selection.length > 0) {\r\n ({text, cursor} = this.handleSelectionDeletion(selection, text, cursor, deletions));\r\n }\r\n ({text, cursor} = this.handleCharacterInsert(charToInsert, text, cursor, highlights));\r\n }\r\n\r\n return {text, cursor, updatedHighlights: highlights, updatedDeleted: deletions};\r\n }\r\n\r\n detectSelection(eventIndex) {\r\n const currentEvent = this.logData[eventIndex];\r\n\r\n if (currentEvent.event?.toLowerCase() === 'keydown' &&\r\n (currentEvent.key === 'Backspace' || currentEvent.key === 'Delete')) {\r\n\r\n const currentPos = currentEvent.rePosition;\r\n return this.processDetection(currentPos, currentEvent, eventIndex);\r\n }\r\n return null;\r\n }\r\n\r\n processDetection(currentPos, currentEvent, eventIndex) {\r\n for (let i = eventIndex + 1; i < this.logData.length; i++) {\r\n const nextEvent = this.logData[i];\r\n\r\n if (nextEvent.event?.toLowerCase() === 'keyup' &&\r\n nextEvent.key === currentEvent.key) {\r\n\r\n const nextPos = nextEvent.rePosition;\r\n\r\n // Calculate the difference in positions\r\n const positionDiff = Math.abs(currentPos - nextPos);\r\n\r\n if (positionDiff > 1) {\r\n return {\r\n start: Math.min(currentPos, nextPos),\r\n end: Math.max(currentPos, nextPos),\r\n length: positionDiff\r\n };\r\n } else if (positionDiff === 1) {\r\n if (currentEvent.key === 'Backspace') {\r\n return {\r\n start: nextPos,\r\n end: currentPos,\r\n length: 1\r\n };\r\n } else {\r\n return {\r\n start: currentPos,\r\n end: nextPos,\r\n length: 1\r\n };\r\n }\r\n }\r\n break;\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n handleSelectionDeletion(selection, text, cursor, deletions) {\r\n const {start, end, length} = selection;\r\n\r\n // Add each character in the selection to the deletions array\r\n for (let i = start; i < end && i < text.length; i++) {\r\n deletions.push({\r\n index: start,\r\n chars: text[i],\r\n time: this.currentTime,\r\n expiresAt: this.currentTime + 2000\r\n });\r\n }\r\n\r\n text = text.substring(0, start) + text.substring(end);\r\n\r\n this.shiftPastedCharsIndices(start, length);\r\n\r\n cursor = start;\r\n\r\n return {text, cursor};\r\n }\r\n\r\n // Handle Paste events to highlight pasted text\r\n handlePasteInsert(pastedContent, text, cursor) {\r\n const insertText = pastedContent || '';\r\n text = text.substring(0, cursor) + insertText + text.substring(cursor);\r\n\r\n // Mark characters as pasted for bold styling\r\n if (insertText.trim() !== '') {\r\n for (let i = 0; i < insertText.length; i++) {\r\n if (!this.pastedChars) {\r\n this.pastedChars = [];\r\n }\r\n this.pastedChars.push({\r\n index: cursor + i,\r\n chars: insertText[i]\r\n });\r\n }\r\n }\r\n\r\n return {text, cursor: cursor + insertText.length};\r\n }\r\n\r\n // Adjusts pasted chars indices after deletion to maintain styling for pasted text\r\n shiftPastedCharsIndices(startIndex, numDeleted) {\r\n this.pastedChars = this.pastedChars.map(p => {\r\n if (p.index >= startIndex + numDeleted) {\r\n return {...p, index: p.index - numDeleted};\r\n } else if (p.index >= startIndex && p.index < startIndex + numDeleted) {\r\n // Remove pasted characters that were deleted\r\n return null;\r\n }\r\n return p;\r\n }).filter(p => p !== null);\r\n\r\n if (this.aiChars) {\r\n this.aiChars = this.aiChars.map(p => {\r\n if (p.index >= startIndex + numDeleted) {\r\n return {...p, index: p.index - numDeleted};\r\n } else if (p.index >= startIndex && p.index < startIndex + numDeleted) {\r\n return null;\r\n }\r\n return p;\r\n }).filter(p => p !== null);\r\n }\r\n }\r\n\r\n // Update state for modifier keys (Control, paste events)\r\n updateModifierStates(key) {\r\n if (key === 'Control') {\r\n this.isControlKeyPressed = true;\r\n } else if (key === 'Shift') {\r\n this.isShiftKeyPressed = true;\r\n } else if (key === 'Meta') {\r\n this.isMetaKeyPressed = true;\r\n } else if ((key === 'v' || key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\r\n this.isPasteEvent = true;\r\n } else if (!['Control', 'Meta', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight'].includes(key)) {\r\n this.isControlKeyPressed = false;\r\n this.isShiftKeyPressed = false;\r\n this.isMetaKeyPressed = false;\r\n this.isPasteEvent = false;\r\n }\r\n }\r\n\r\n isCtrlBackspace(key, cursor) {\r\n return key === 'Backspace' && this.isControlKeyPressed && cursor > 0;\r\n }\r\n\r\n isCtrlDelete(key, cursor, text) {\r\n return key === 'Delete' && this.isControlKeyPressed && cursor < text.length;\r\n }\r\n\r\n isCtrlArrowMove(key) {\r\n return this.isControlKeyPressed && (key === 'ArrowLeft' || key === 'ArrowRight');\r\n }\r\n\r\n isRegularBackspace(key, cursor) {\r\n return key === 'Backspace' && !this.isPasteEvent && cursor > 0;\r\n }\r\n\r\n isRegularDelete(key, cursor, text) {\r\n return key === 'Delete' && !this.isControlKeyPressed && cursor < text.length;\r\n }\r\n\r\n isRegularArrowMove(key) {\r\n return !this.isControlKeyPressed && (key === 'ArrowLeft' || key === 'ArrowRight');\r\n }\r\n\r\n isArrowUp(key) {\r\n return key === 'ArrowUp';\r\n }\r\n\r\n isArrowDown(key) {\r\n return key === 'ArrowDown';\r\n }\r\n\r\n handleCtrlArrowMove(key, text, cursor) {\r\n return key === 'ArrowLeft'\r\n ? this.findPreviousWordBoundary(text, cursor)\r\n : this.findNextWordBoundary(text, cursor);\r\n }\r\n\r\n handleBackspace(text, cursor, deletions) {\r\n deletions.push({\r\n index: cursor - 1,\r\n chars: text[cursor - 1],\r\n time: this.currentTime,\r\n expiresAt: this.currentTime + 2000\r\n });\r\n this.shiftPastedCharsIndices(cursor - 1, 1);\r\n return {\r\n text: text.substring(0, cursor - 1) + text.substring(cursor),\r\n cursor: cursor - 1\r\n };\r\n }\r\n\r\n handleDelete(text, cursor, deletions) {\r\n deletions.push({\r\n index: cursor,\r\n chars: text[cursor],\r\n time: this.currentTime,\r\n expiresAt: this.currentTime + 2000\r\n });\r\n this.shiftPastedCharsIndices(cursor, 1);\r\n return {\r\n text: text.substring(0, cursor) + text.substring(cursor + 1),\r\n cursor\r\n };\r\n }\r\n\r\n handleArrowMove(key, text, cursor) {\r\n return key === 'ArrowLeft'\r\n ? Math.max(0, cursor - 1)\r\n : Math.min(text.length, cursor + 1);\r\n }\r\n\r\n handleCharacterInsert(charToInsert, text, cursor, highlights) {\r\n text = text.substring(0, cursor) + charToInsert + text.substring(cursor);\r\n // Shift pasted chars indices after the insertion point\r\n if (this.pastedChars) {\r\n this.pastedChars = this.pastedChars.map(p => {\r\n return p.index >= cursor ? {...p, index: p.index + 1} : p;\r\n });\r\n }\r\n if (this.aiChars) {\r\n this.aiChars = this.aiChars.map(p => {\r\n return p.index >= cursor ? {...p, index: p.index + 1} : p;\r\n });\r\n }\r\n if (charToInsert.trim() !== '') {\r\n highlights.push({\r\n index: cursor,\r\n chars: charToInsert,\r\n time: this.currentTime,\r\n expiresAt: this.currentTime + 1500\r\n });\r\n }\r\n return {text, cursor: cursor + 1};\r\n }\r\n\r\n handleCtrlDelete(text, cursor, deletions) {\r\n const wordEnd = this.findNextWordBoundary(text, cursor);\r\n const wordToDelete = text.substring(cursor, wordEnd);\r\n for (let i = 0; i < wordToDelete.length; i++) {\r\n deletions.push({\r\n index: cursor + i,\r\n chars: wordToDelete[i],\r\n time: this.currentTime,\r\n expiresAt: this.currentTime + 2000\r\n });\r\n }\r\n this.shiftPastedCharsIndices(cursor, wordToDelete.length);\r\n return {\r\n text: text.substring(0, cursor) + text.substring(wordEnd),\r\n cursor\r\n };\r\n }\r\n\r\n handleArrowUp(text, cursor) {\r\n const lines = text.split('\\n');\r\n const {lineIndex, col} = this.getLineAndColumn(text, cursor);\r\n if (lineIndex > 0) {\r\n const prevLine = lines[lineIndex - 1];\r\n cursor = lines.slice(0, lineIndex - 1).join('\\n').length + 1 + Math.min(col, prevLine.length);\r\n } else {\r\n cursor = 0;\r\n }\r\n return cursor;\r\n }\r\n\r\n handleArrowDown(text, cursor) {\r\n const lines = text.split('\\n');\r\n const {lineIndex, col} = this.getLineAndColumn(text, cursor);\r\n if (lineIndex < lines.length - 1) {\r\n const nextLine = lines[lineIndex + 1];\r\n cursor = lines.slice(0, lineIndex + 1).join('\\n').length + 1 + Math.min(col, nextLine.length);\r\n } else {\r\n cursor = text.length;\r\n }\r\n return cursor;\r\n }\r\n\r\n handleCtrlBackspace(text, cursor, deletions) {\r\n let wordStart = cursor;\r\n while (wordStart > 0 && text[wordStart - 1] === ' ') {\r\n wordStart--;\r\n }\r\n while (wordStart > 0 && text[wordStart - 1] !== ' ') {\r\n wordStart--;\r\n }\r\n const wordToDelete = text.substring(wordStart, cursor);\r\n for (let i = 0; i < wordToDelete.length; i++) {\r\n deletions.push({\r\n index: wordStart + i,\r\n chars: wordToDelete[i],\r\n time: this.currentTime,\r\n expiresAt: this.currentTime + 2000\r\n });\r\n }\r\n this.shiftPastedCharsIndices(wordStart, wordToDelete.length);\r\n return {text: text.substring(0, wordStart) + text.substring(cursor), cursor: wordStart};\r\n }\r\n\r\n // Finds the index of the next word boundary after the cursor position\r\n findNextWordBoundary(text, cursor) {\r\n if (!text || cursor >= text.length) {\r\n return cursor;\r\n }\r\n if (text[cursor] === ' ') {\r\n while (cursor < text.length && text[cursor] === ' ') {\r\n cursor++;\r\n }\r\n }\r\n if (cursor >= text.length) {\r\n let lastNonSpace = text.length - 1;\r\n while (lastNonSpace >= 0 && text[lastNonSpace] === ' ') {\r\n lastNonSpace--;\r\n }\r\n return lastNonSpace + 1;\r\n }\r\n let wordEnd = cursor;\r\n while (wordEnd < text.length && text[wordEnd] !== ' ') {\r\n wordEnd++;\r\n }\r\n return wordEnd;\r\n }\r\n\r\n // Finds the index of the previous word boundary before the cursor position\r\n findPreviousWordBoundary(text, cursor) {\r\n if (cursor <= 0) {\r\n return 0;\r\n }\r\n let pos = cursor - 1;\r\n while (pos > 0 && (text[pos] === ' ' || text[pos] === '\\n')) {\r\n pos--;\r\n }\r\n while (pos > 0 && text[pos - 1] !== ' ' && text[pos - 1] !== '\\n') {\r\n pos--;\r\n }\r\n\r\n return pos;\r\n }\r\n\r\n skipToEnd() {\r\n if (this.replayInProgress) {\r\n this.replayInProgress = false;\r\n }\r\n let textOutput = \"\";\r\n this.logData.forEach(event => {\r\n if (event.event.toLowerCase() === 'keydown') {\r\n textOutput = this.applyKey(event.key, textOutput);\r\n }\r\n });\r\n this.outputElement.innerHTML = textOutput.slice(0, -1);\r\n this.setScrubberVal(100);\r\n }\r\n\r\n // Used by the scrubber to skip to a certain percentage of data\r\n skipToTime(percentage) {\r\n const wasPlaying = this.replayInProgress;\r\n this.stopReplay();\r\n\r\n const targetTime = (this.totalDuration * percentage) / 100;\r\n this.currentTime = targetTime;\r\n this.currentEventIndex = 0;\r\n this.text = '';\r\n this.cursorPosition = 0;\r\n this.highlightedChars = [];\r\n this.deletedChars = [];\r\n this.isControlKeyPressed = false;\r\n this.isMetaKeyPressed = false;\r\n this.isPasteEvent = false;\r\n this.pastedChars = [];\r\n this.currentPasteIndex = 0;\r\n this.currentAiIndex = 0;\r\n this.aiChars = [];\r\n let text = '';\r\n let cursor = 0;\r\n let highlights = [];\r\n let deletions = [];\r\n let pasteIndex = 0;\r\n let aiIndex = 0;\r\n\r\n for (let i = 0; i < this.logData.length; i++) {\r\n const event = this.logData[i];\r\n if (event.normalizedTime && event.normalizedTime > targetTime) {\r\n this.currentEventIndex = i;\r\n break;\r\n }\r\n if (event.rePosition !== undefined && (this.currentEventIndex === 0 ||\r\n event.event === 'mouseDown' || event.event === 'mouseUp')) {\r\n cursor = Math.max(0, Math.min(event.rePosition, text.length));\r\n }\r\n if (event.event?.toLowerCase() === 'keydown') {\r\n this.currentPasteIndex = pasteIndex;\r\n if ((event.key === 'v' || event.key === 'V') && (this.isControlKeyPressed || this.isMetaKeyPressed)) {\r\n pasteIndex++;\r\n }\r\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\r\n this.processKeydownEvent(event, text, cursor, highlights, deletions));\r\n } else if (event.event === 'aiInsert') {\r\n this.currentAiIndex = aiIndex;\r\n ({text, cursor, updatedHighlights: highlights, updatedDeleted: deletions} =\r\n this.processAiInsertEvent(event, text, cursor, highlights, deletions));\r\n aiIndex++;\r\n }\r\n this.currentEventIndex = i + 1;\r\n }\r\n\r\n this.currentPasteIndex = pasteIndex;\r\n this.currentAiIndex = aiIndex;\r\n this.text = text;\r\n this.cursorPosition = cursor;\r\n this.highlightedChars = highlights.filter(h => !h.expiresAt || h.expiresAt > targetTime);\r\n this.deletedChars = deletions.filter(d => !d.expiresAt || d.expiresAt > targetTime);\r\n this.updateDisplayText(this.text, this.cursorPosition, this.highlightedChars, this.deletedChars);\r\n this.setScrubberVal(percentage);\r\n\r\n if (wasPlaying) {\r\n this.replayInProgress = true;\r\n this.replayLog();\r\n }\r\n }\r\n\r\n // Update display with text, cursor, highlights and deletions.\r\n // eslint-disable-next-line complexity\r\n updateDisplayText(text, cursorPosition, highlights, deletions) {\r\n let html = '';\r\n const highlightMap = {};\r\n const deletionMap = {};\r\n const pastedMap = {};\r\n const aiMap = {};\r\n const currentTime = this.currentTime;\r\n\r\n highlights.forEach(h => {\r\n let opacity = 1;\r\n if (h.expiresAt && h.expiresAt - currentTime < 500) {\r\n opacity = Math.max(0, (h.expiresAt - currentTime) / 500);\r\n }\r\n highlightMap[h.index] = {chars: h.chars, opacity};\r\n });\r\n\r\n deletions.forEach(d => {\r\n let opacity = 0.5;\r\n if (d.expiresAt && d.expiresAt - currentTime < 500) {\r\n opacity = Math.max(0, ((d.expiresAt - currentTime) / 500) * 0.5);\r\n }\r\n deletionMap[d.index] = {chars: d.chars, opacity};\r\n });\r\n\r\n // Process pasted characters for bold styling\r\n if (this.pastedChars) {\r\n this.pastedChars.forEach(p => {\r\n if (p.index < text.length) {\r\n pastedMap[p.index] = true;\r\n }\r\n });\r\n }\r\n\r\n // Process AI characters for styling\r\n if (this.aiChars) {\r\n this.aiChars.forEach(p => {\r\n if (p.index < text.length) {\r\n aiMap[p.index] = true;\r\n }\r\n });\r\n }\r\n\r\n // Find if we have out-of-bounds deletions (from Control+Backspace)\r\n const outOfRangeDeletions = deletions.filter(d => d.index >= text.length);\r\n const textLines = text.split('\\n');\r\n let currentPosition = 0;\r\n\r\n for (let lineIndex = 0; lineIndex < textLines.length; lineIndex++) {\r\n const line = textLines[lineIndex];\r\n for (let i = 0; i < line.length; i++) {\r\n if (currentPosition === cursorPosition) {\r\n html += '';\r\n }\r\n const char = line[i];\r\n if (deletionMap[currentPosition]) {\r\n html += `${deletionMap[currentPosition].chars}`;\r\n }\r\n const isPasted = pastedMap[currentPosition];\r\n const isAi = aiMap[currentPosition];\r\n const isHighlighted = highlightMap[currentPosition] && char !== ' ';\r\n\r\n if (isPasted && isHighlighted) {\r\n html += `${char}`;\r\n } else if (isAi && isHighlighted) {\r\n html += `${char}`;\r\n } else if (isPasted) {\r\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\r\n } else if (isAi) {\r\n html += `${char === ' ' ? ' ' : this.escapeHtml(char)}`;\r\n } else if (isHighlighted) {\r\n html += `${char}`;\r\n } else {\r\n html += char === ' ' ? ' ' : this.escapeHtml(char);\r\n }\r\n currentPosition++;\r\n }\r\n if (currentPosition === cursorPosition) {\r\n html += '';\r\n }\r\n if (lineIndex < textLines.length - 1) {\r\n html += '
';\r\n currentPosition++;\r\n }\r\n }\r\n\r\n if (cursorPosition === text.length && !html.endsWith('')) {\r\n html += '';\r\n }\r\n\r\n if (outOfRangeDeletions.length > 0) {\r\n outOfRangeDeletions.sort((a, b) => a.index - b.index);\r\n const cursorHTML = '';\r\n const cursorPos = html.lastIndexOf(cursorHTML);\r\n if (cursorPos !== -1) {\r\n let deletedWordHTML = '';\r\n outOfRangeDeletions.forEach(d => {\r\n deletedWordHTML += d.chars;\r\n });\r\n deletedWordHTML += '';\r\n html = html.substring(0, cursorPos) + deletedWordHTML + html.substring(cursorPos);\r\n }\r\n }\r\n\r\n const wasScrolledToBottom = this.outputElement.scrollHeight -\r\n this.outputElement.clientHeight <= this.outputElement.scrollTop + 1;\r\n this.outputElement.innerHTML = html;\r\n\r\n if (wasScrolledToBottom || this.isCursorBelowViewport()) {\r\n this.outputElement.scrollTop = this.outputElement.scrollHeight;\r\n }\r\n }\r\n\r\n // Check if cursor is below visible viewport\r\n isCursorBelowViewport() {\r\n const cursorElement = this.outputElement.querySelector('.tiny_cursive-cursor:last-of-type');\r\n if (!cursorElement) {\r\n return false;\r\n }\r\n\r\n const cursorRect = cursorElement.getBoundingClientRect();\r\n const outputRect = this.outputElement.getBoundingClientRect();\r\n\r\n return cursorRect.bottom > outputRect.bottom;\r\n }\r\n\r\n escapeHtml(unsafe) {\r\n return unsafe\r\n .replace(/&/g, '&')\r\n .replace(//g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\r\n }\r\n\r\n // Used in various places to add a keydown, backspace, etc. to the output\r\n applyKey(key) {\r\n switch (key) {\r\n case 'Enter':\r\n return '\\n';\r\n case 'Backspace':\r\n case 'Delete':\r\n case 'ControlBackspace':\r\n return '';\r\n case ' ':\r\n return ' ';\r\n default:\r\n return !['Shift', 'Ctrl', 'Alt', 'ArrowDown', 'ArrowUp', 'Control', 'ArrowRight',\r\n 'ArrowLeft', 'Meta', 'CapsLock', 'Tab', 'Escape', 'Delete', 'PageUp', 'PageDown',\r\n 'Insert', 'Home', 'End', 'NumLock', 'AudioVolumeUp', 'AudioVolumeDown',\r\n 'MediaPlayPause', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10',\r\n 'F11', 'F12', 'PrintScreen', 'UnIdentified'].includes(key) ? key : '';\r\n }\r\n }\r\n}\r\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":"00CA4BIA,YAAYC,UAAWC,cAAUC,6DAAQ,EAAGC,6DAAcC,yDAEjDA,aAAeA,cAAgB,QAC/BC,kBAAmB,OACnBH,MAAQI,WAAWJ,YACnBC,KAAOA,UACPI,iBAAmB,QACnBC,aAAe,QACfC,eAAiB,OACjBC,kBAAoB,OACpBC,YAAc,OACdC,YAAc,OACdC,cAAgB,OAChBC,aAAe,QACfC,gBAAkB,QAClBC,cAAe,OACfC,qBAAsB,OACtBC,mBAAoB,OACpBC,kBAAmB,OACnBC,KAAO,QACPC,aAAe,QACfC,kBAAoB,OACpBC,YAAc,QACdC,SAAW,QACXC,eAAiB,OACjBC,QAAU,QACVC,eAAiB,QACjBC,UAAY,SAEXC,QAAUC,SAASC,eAAe/B,eACnC6B,cACK,IAAIG,MAAO,oBAAmBhC,6BAEnCiC,cAAgBJ,aAGhBK,SAASjC,UAAUkC,MAAKC,OACrBA,KAAKC,aACAC,YAAYF,WACZzB,YAAc4B,KAAKC,QAAQC,YAC3BC,2BACAC,qBACDJ,KAAKnC,cAAgBmC,KAAKC,cACrBI,oBAAoBL,KAAKnC,mBAE7ByC,oBAEAC,qBAEFV,QACRW,OAAMC,aACAF,qBACLG,OAAOC,QAAQF,MAAM,2BAA4BA,MAAMG,YAEtDC,aAAaC,QAAQ,iBAAoBD,aAAaC,QAAQ,gBAC/DC,IAAIC,WAAW,eAAgB,gBAAgBpB,MAAKqB,MAChDJ,aAAaK,QAAQ,eAAgBD,KAC9BA,OACRT,OAAMC,OAASC,OAAOC,QAAQQ,IAAIV,SACrCM,IAAIC,WAAW,aAAc,gBAAgBpB,MAAKqB,MAC9CJ,aAAaK,QAAQ,aAAcD,KAC5BA,OACRT,OAAMC,OAASC,OAAOC,QAAQQ,IAAIV,UAK7CV,YAAYF,WACHI,QAAUmB,KAAKC,MAAMxB,KAAKA,MAC3BA,KAAKyB,gBACA/C,aAAegD,MAAMC,QAAQJ,KAAKC,MAAMxB,KAAKyB,WAAaF,KAAKC,MAAMxB,KAAKyB,UAAY,IAE3F,SAAUtB,KAAKC,eACVA,QAAUD,KAAKC,QAAQJ,MAE5B,YAAaG,KAAKC,eACbA,QAAUD,KAAKC,QAAQwB,aAE3B,IAAIC,EAAI,EAAGA,EAAI1B,KAAKC,QAAQC,OAAQwB,IAAK,OACpCC,MAAQ3B,KAAKC,QAAQyB,GACP,UAAhBC,MAAMA,OAC6B,iBAAxBA,MAAMC,eAA6D,KAA/BD,MAAMC,cAAcC,aAC1D/C,aAAagD,KAAKH,MAAMC,eAGjB,aAAhBD,MAAMA,OAAwBA,MAAMI,gBAC/B9C,SAAS6C,KAAKH,MAAMI,cAG7B/B,KAAKC,QAAQC,OAAS,GAAKF,KAAKC,QAAQ,GAAG+B,cAAe,OACpDC,UAAYjC,KAAKC,QAAQ,GAAG+B,mBAC7B/B,QAAUD,KAAKC,QAAQiC,KAAIP,YACzBA,MACHQ,eAAgBR,MAAMK,cAAgBC,mBAErC3D,cAAgB0B,KAAKC,QAAQD,KAAKC,QAAQC,OAAS,GAAGiC,qDAMpDC,KAAMnB,WAAaoB,QAAQC,IAAI,CAClCC,mBAAUC,OAAO,8BACjBzB,IAAIC,WAAW,iBAAkB,kBAE/ByB,YAAa,mBAAEL,MAAMvD,KAAKoC,YACzB,mBAAE,iBAAiBmB,KAAKK,YACjC,MAAOhC,cACLC,OAAOC,QAAQF,MAAMA,QACd,GAKfiC,gBACQ1C,KAAKlC,mBACL6E,aAAa3C,KAAK4C,oBACb9E,kBAAmB,EACpBkC,KAAK6C,YAAY,OACXC,QAAUvD,SAASwD,cAAc,OACvCD,QAAQE,IAAMC,EAAEC,KAAKC,UAAU,WAAY,qBACtCN,WAAWO,cAAc,cAAcC,UAAYP,QAAQQ,WAM5EjD,oBAAoBxC,6CACXC,kBAAmB,OACnByF,gBAAkB,OAClB5F,MAAQ,EACTqC,KAAKwD,mBACLC,cAAczD,KAAKwD,uBACdA,iBAAmB,YAGtBE,UAAYnE,SAASC,eAAe3B,kBACrC6F,sBACDhD,OAAOC,QAAQF,MAAM,+BAAgC5C,oBAInD8F,iBAAmBD,UAAUN,cAAc,gCAC5CO,kBAILA,iBAAiBN,UAAY,0DAExBO,kBAAkBD,iBAAkBD,yCACzCC,iBAAiBP,cAAc,yFAAkCS,UAN7DnD,OAAOC,QAAQF,MAAM,yCAA0C5C,cASvE+F,kBAAkBD,iBAAkBD,iBAC1BI,OAASvE,SAASwD,cAAc,OACtCe,OAAOC,UAAUC,IAAI,6BAEhBnB,WAAa7C,KAAKiE,mBACvBH,OAAOI,YAAYlE,KAAK6C,kBAElBsB,kBAAoBnE,KAAKoE,0BAC/BN,OAAOI,YAAYC,wBAEdE,YAAcrE,KAAKsE,oBACxBR,OAAOI,YAAYlE,KAAKqE,mBAElBE,UAAYhF,SAASwD,cAAc,OACzCwB,UAAUR,UAAUC,IAAI,iCAElBQ,eAAiBxE,KAAKyE,sBAC5BF,UAAUL,YAAYM,sBAEhBE,kBAAoB1E,KAAK2E,wBAAwBjB,WACvDa,UAAUL,YAAYQ,mBAEtBf,iBAAiBO,YAAYJ,QAC7BH,iBAAiBO,YAAYK,WAC7Bb,UAAUQ,YAAYlE,KAAK4E,kBAG/BX,yBACUpB,WAAatD,SAASwD,cAAc,UAC1CF,WAAWkB,UAAUC,IAAI,kCACnBlB,QAAUvD,SAASwD,cAAc,YACvCF,WAAWQ,UAAa,2BAA0BP,QAAQQ,mBAC1DT,WAAWgC,iBAAiB,SAAS,KAC7B7E,KAAKlC,sBACA4E,kBAEApC,aAAY,uBAEnB,yBAAyBwE,KAAK,WAAWC,YAAY,8BACrD,gBAAgBC,SAAS,aAExBnC,WAGXuB,gCACUD,kBAAoB5E,SAASwD,cAAc,cACjDoB,kBAAkBJ,UAAUC,IAAI,wCAC3BiB,gBAAkB1F,SAASwD,cAAc,cACzCkC,gBAAgBlB,UAAUC,IAAI,iCAAkC,0BAChEiB,gBAAgBC,KAAO,aACvBD,gBAAgBE,IAAM,WACtBF,gBAAgBG,IAAM,SACtBH,gBAAgBI,MAAQ,SACxBJ,gBAAgBJ,iBAAiB,SAAS,UACtCS,WAAWC,SAASvF,KAAKiF,gBAAgBI,MAAO,QAEzDlB,kBAAkBD,YAAYlE,KAAKiF,iBAC5Bd,kBAGXG,0BACUD,YAAc9E,SAASwD,cAAc,cAC3CsB,YAAYN,UAAUC,IAAI,6BAC1BK,YAAYmB,YAAc,gBACnBnB,YAGXI,4BACUD,eAAiBjF,SAASwD,cAAc,OAC9CyB,eAAeT,UAAUC,IAAI,8BAA+B,wBACtDyB,WAAalG,SAASwD,cAAc,QAC1C0C,WAAW1B,UAAUC,IAAI,4BACzByB,WAAWD,YAAc,UACzBhB,eAAeN,YAAYuB,kBAErBC,WAAanG,SAASwD,cAAc,cAC1C2C,WAAW3B,UAAUC,IAAI,6BACxB,EAAG,IAAK,EAAG,EAAG,IAAI2B,SAAQhI,cACjBiI,SAAWrG,SAASwD,cAAc,UACxC6C,SAASJ,YAAe,GAAE7H,SAC1BiI,SAAS7B,UAAUC,IAAI,yBAA0B,aAC7CjG,WAAWJ,SAAWqC,KAAKrC,OAC3BiI,SAAS7B,UAAUC,IAAI,UAE3B4B,SAASC,QAAQlI,MAAQA,MACzBiI,SAASf,iBAAiB,SAAS,KAC/BtF,SAASuG,iBAAiB,2BAA2BH,SAAQI,KAAOA,IAAIhC,UAAUF,OAAO,YACzF+B,SAAS7B,UAAUC,IAAI,eAClBrG,MAAQI,WAAW6H,SAASC,QAAQlI,OACrCqC,KAAKlC,wBACA4E,kBACApC,aAAY,OAGzBoF,WAAWxB,YAAY0B,aAE3BpB,eAAeN,YAAYwB,YACpBlB,eAGXG,wBAAwBjB,iBACdgB,kBAAoBnF,SAASwD,cAAc,OACjD2B,kBAAkBX,UAAUC,IAAI,mCAAoC,6BAE9DgC,gBAAkBzG,SAASwD,cAAc,QACzCkD,UAAY1G,SAASwD,cAAc,OACzCkD,UAAUjD,IAAMC,EAAEC,KAAKC,UAAU,YAAa,gBAC9C6C,gBAAgB3C,UAAY4C,UAAU3C,UACtC0C,gBAAgBjC,UAAUC,IAAI,wCAExBkC,gBAAkB3G,SAASwD,cAAc,QAC/CmD,gBAAgBV,YAAc3E,aAAaC,QAAQ,mBAE9CqF,gBAAkB5G,SAASwD,cAAc,aACzCoD,gBAAgBX,YAAe,IAAGxF,KAAKxB,gBAAgB0B,eACvDiG,gBAAgBC,UAAY,yBAC5BD,gBAAgBE,MAAMC,WAAa,YAElCC,YAAchH,SAASwD,cAAc,QACrCyD,QAAUjH,SAASwD,cAAc,YACvCyD,QAAQJ,UAAY,qBACpBG,YAAYlD,UAAYmD,QAAQlD,UAChCiD,YAAYF,MAAMC,WAAa,MAC/BC,YAAYF,MAAMI,WAAa,sBAE/B/B,kBAAkBR,YAAY8B,iBAC9BtB,kBAAkBR,YAAYgC,iBAC9BxB,kBAAkBR,YAAYlE,KAAKmG,iBACnCzB,kBAAkBR,YAAYqC,kBAEzB3B,iBAAmB5E,KAAK0G,uBAAuBhD,WACpDgB,kBAAkBG,iBAAiB,SAAS,WAClC8B,SAAmD,SAAxC3G,KAAK4E,iBAAiByB,MAAMO,aACxChC,iBAAiByB,MAAMO,QAAUD,SAAW,QAAU,OAC3DJ,YAAYF,MAAMQ,UAAYF,SAAW,iBAAmB,kBAGzDjC,kBAGXgC,uBAAuBhD,iBACboD,cAAgBpD,UAAUN,cAAc,uBAC1C0D,eACAA,cAAcjD,eAEZe,iBAAmBrF,SAASwD,cAAc,cAChD6B,iBAAiBb,UAAUC,IAAI,kCAAmC,sBAClEY,iBAAiByB,MAAMO,QAAU,YAC5BG,yBAAyBnC,kBACvBA,iBAIXzE,2BACS3B,gBAAkB,OACnBwI,gBAAiB,EACjBC,aAAc,EAEdC,cAAe,EACfC,WAAa,MAEZ,IAAIzF,EAAI,EAAGA,EAAI1B,KAAKC,QAAQC,OAAQwB,IAAK,wBACpCC,MAAQ3B,KAAKC,QAAQyB,MACQ,kCAA/BC,MAAMA,kDAAOyF,kBACK,YAAdzF,MAAM0F,IACNL,gBAAiB,OACd,GAAkB,SAAdrF,MAAM0F,IACbJ,aAAc,OACX,GAAkB,UAAdtF,MAAM0F,IACbH,cAAe,OACZ,GAAmB,MAAdvF,MAAM0F,KAA6B,MAAd1F,MAAM0F,MAAiBL,iBAAkBC,YAgBtED,gBAAiB,EACjBE,cAAe,EACfD,aAAc,MAlBsE,IAChFjH,KAAKlB,aAAaqI,YAAa,OACzBG,UAAY3F,MAAMQ,gBAAkB,OACrC3D,gBAAgBsD,KAAK,CACtByF,MAAOJ,WACPK,KAAMF,UACNG,cAAezH,KAAK0H,WAAWJ,WAC/BK,WAAY3H,KAAKlB,aAAaqI,YAC9BG,UAAAA,YAGRH,aACAH,gBAAiB,EACjBE,cAAe,EACfD,aAAc,GAStBjH,KAAK4E,uBACAmC,yBAAyB/G,KAAK4E,kBAI3CxE,0BACShB,eAAiB,OAClB4H,gBAAiB,EACjBC,aAAc,EACdW,UAAY,MAEX,IAAIlG,EAAI,EAAGA,EAAI1B,KAAKC,QAAQC,OAAQwB,IAAK,yBACpCC,MAAQ3B,KAAKC,QAAQyB,MACQ,mCAA/BC,MAAMA,oDAAOyF,kBACK,YAAdzF,MAAM0F,IACNL,gBAAiB,OACd,GAAkB,SAAdrF,MAAM0F,IACbJ,aAAc,OACX,GAAmB,MAAdtF,MAAM0F,KAA6B,MAAd1F,MAAM0F,MAAiBL,iBAAkBC,YAYtED,gBAAiB,EACjBC,aAAc,MAbsE,OAC9EK,UAAY3F,MAAMQ,gBAAkB,OACrC/C,eAAe0C,KAAK,CACrByF,MAAOK,UACPJ,KAAMF,UACNG,cAAezH,KAAK0H,WAAWJ,WAC/BA,UAAAA,YAEJM,YACAZ,gBAAiB,EACjBC,aAAc,IAU9BF,yBAAyBc,UACrBA,MAAMxE,UAAY,GAClBwE,MAAM9D,UAAUC,IAAI,6BAEfhE,KAAKxB,gBAAgB0B,OAAQ,OACxB4H,gBAAkBvI,SAASwD,cAAc,cAC/C+E,gBAAgB1B,UAAY,8BAC5B0B,gBAAgBtC,YAAc3E,aAAaC,QAAQ,qBACnD+G,MAAM3D,YAAY4D,uBAIhBC,kBAAoBxI,SAASwD,cAAc,OACjDgF,kBAAkBhE,UAAUC,IAAI,qCAAsC,+BAEhEgE,cAAgBzI,SAASwD,cAAc,OAC7CiF,cAAcjE,UAAUC,IAAI,0BAA2B,qCAEjDiE,eAAiB1I,SAASwD,cAAc,OAC9CkF,eAAelE,UAAUC,IAAI,uBAAwB,gCACrDiE,eAAezC,YAAc,qBAEvB0C,WAAa3I,SAASwD,cAAc,OAC1CmF,WAAWnE,UAAUC,IAAI,kCACnBmE,WAAa5I,SAASwD,cAAc,UAC1CoF,WAAWpE,UAAUC,IAAI,uBAAwB,2BACjDmE,WAAW9E,UAAY,2CAEjB+E,WAAa7I,SAASwD,cAAc,UAC1CqF,WAAWrE,UAAUC,IAAI,uBAAwB,2BACjDoE,WAAW/E,UAAY,sCACvB+E,WAAWC,SAAWrI,KAAKxB,gBAAgB0B,QAAU,EAErDgI,WAAWhE,YAAYiE,YACvBD,WAAWhE,YAAYkE,YACvBJ,cAAc9D,YAAY+D,gBAC1BD,cAAc9D,YAAYgE,kBAEpBI,iBAAmB/I,SAASwD,cAAc,OAChDuF,iBAAiBlC,UAAY,sDAC7BkC,iBAAiBpE,YAAYlE,KAAKuI,wBAAwBvI,KAAKxB,gBAAgB,KAE/EuJ,kBAAkB7D,YAAY8D,eAC9BD,kBAAkB7D,YAAYoE,kBAC9BT,MAAM3D,YAAY6D,uBAEdS,aAAe,QACbC,cAAgB,KAClBH,iBAAiBjF,UAAY,GAC7BiF,iBAAiBpE,YAAYlE,KAAKuI,wBAAwBvI,KAAKxB,gBAAgBgK,gBAC/EP,eAAezC,YAAc,eAC7B2C,WAAWE,SAA4B,IAAjBG,aACtBL,WAAW9B,MAAMqC,QAA2B,IAAjBF,aAAqB,MAAQ,IACxDJ,WAAWC,SAAWG,eAAiBxI,KAAKxB,gBAAgB0B,OAAS,EACrEkI,WAAW/B,MAAMqC,QAAUF,eAAiBxI,KAAKxB,gBAAgB0B,OAAS,EAAI,MAAQ,KAG1FiI,WAAWtD,iBAAiB,SAAS,KAC7B2D,aAAe,IACfA,eACAC,oBAIRL,WAAWvD,iBAAiB,SAAS,KAC7B2D,aAAexI,KAAKxB,gBAAgB0B,OAAS,IAC7CsI,eACAC,oBAKZF,wBAAwBI,kBACdC,SAAWrJ,SAASwD,cAAc,OACxC6F,SAASxC,UAAY,+BAEfyC,UAAYtJ,SAASwD,cAAc,OACzC8F,UAAUzC,UAAY,gCAEhB0C,cAAgBvJ,SAASwD,cAAc,OAC7C+F,cAAc1C,UAAY,oCAEpB2C,mBAAqBxJ,SAASwD,cAAc,OAClDgG,mBAAmB3C,UAAY,2DAC/B2C,mBAAmBvD,YAAcmD,WAAWlB,oBAEtCuB,oBAAsBzJ,SAASwD,cAAc,OACnDiG,oBAAoB5C,UAAY,sDAChC4C,oBAAoBxD,YAAcmD,WAAWhB,WAE7CmB,cAAc5E,YAAY6E,oBAC1BD,cAAc5E,YAAY8E,2BAEpBnG,WAAatD,SAASwD,cAAc,UAC1CF,WAAWuD,UAAY,0DACjB6C,SAAW1J,SAASwD,cAAc,cACxCkG,SAASjG,IAAMC,EAAEC,KAAKC,UAAU,eAAgB,gBAChDN,WAAWQ,UAAY4F,SAAS3F,UAChCT,WAAWgC,iBAAiB,SAAS,IAAM7E,KAAKkJ,gBAAgBP,WAAWrB,aAE3EuB,UAAU3E,YAAY4E,eACtBD,UAAU3E,YAAYrB,YACtB+F,SAAS1E,YAAY2E,WAEdD,SAIXM,gBAAgB5B,iBACN6B,WAAanJ,KAAK1B,cAAgB,EAAKgJ,UAAYtH,KAAK1B,cAAiB,IAAM,OAChFgH,WAAW6D,YACXnJ,KAAKlC,uBACDwC,aAAY,GAIzB8I,eAAe/D,UACPrF,KAAKiF,uBACAA,gBAAgBI,MAAQgE,OAAOhE,OAChCrF,KAAKqE,aAAa,OACZiF,YAAcC,KAAKnE,IAAIpF,KAAK3B,YAAa2B,KAAK1B,oBAC/C+F,YAAYmB,YAAe,GAAExF,KAAK0H,WAAW4B,kBAAkBtJ,KAAK0H,WAAW1H,KAAK1B,kBAKrGqB,SAASjC,iBACE,cAAU,CAAC,CACd8L,WAAY,yBACZC,KAAM,CAACC,SAAUhM,aACjB,GAAGiM,MAAKC,UAAYA,WAAUC,MAAKpJ,cAC7B,IAAIhB,MAAO,4BAA2BgB,MAAMG,cAI1D8G,WAAWoC,UACDC,QAAUR,KAAKS,MAAMF,GAAK,KAE1BG,iBAAmBF,QAAU,SAC3B,GAFQR,KAAKS,MAAMD,QAAU,IAEnBG,WAAWC,SAAS,EAAG,QAAQF,iBAAiBC,WAAWC,SAAS,EAAG,OAI7F7J,kBAAY8J,iEACJpK,KAAKlC,kBACL6E,aAAa3C,KAAK4C,mBAEP5C,KAAK1B,cAAgB,GAAK0B,KAAK3B,aAAe2B,KAAK1B,eAC7D0B,KAAK7B,mBAAqB6B,KAAK5B,eACtBgM,QACVA,OAAQ,QAEPtM,kBAAmB,EACpBsM,aACK1K,cAAc2D,UAAY,QAC1BxE,KAAO,QACPX,eAAiB,OACjBC,kBAAoB,OACpBE,YAAc,OACdL,iBAAmB,QACnBC,aAAe,QACfS,qBAAsB,OACtBE,kBAAmB,OACnBG,kBAAoB,OACpBC,YAAc,QACdE,eAAiB,OACjBC,QAAU,IAEfa,KAAK6C,WAAY,OACXwH,SAAW9K,SAASwD,cAAc,KACxCsH,SAASjE,UAAY,mBAChBvD,WAAWO,cAAc,cAAcC,UAAYgH,SAAS/G,eAEhEgH,YAITA,eACStK,KAAKlC,uBAKHkC,KAAK7B,kBAAoB6B,KAAKC,QAAQC,QAAQ,yBAC3CyB,MAAQ3B,KAAKC,QAAQD,KAAK7B,sBAC5BwD,MAAMQ,gBAAkBR,MAAMQ,eAAiBnC,KAAK3B,sBAIpDQ,KAAOmB,KAAKnB,MAAQ,GACpB0L,OAASvK,KAAK9B,eACdsM,kBAAoB,IAAIxK,KAAKhC,kBAC7ByM,eAAiB,IAAIzK,KAAK/B,mBAELyM,IAArB/I,MAAMgJ,YAAwD,IAA3B3K,KAAK7B,mBACxB,cAAhBwD,MAAMA,OAAyC,YAAhBA,MAAMA,QACrC4I,OAAShB,KAAKpE,IAAI,EAAGoE,KAAKnE,IAAIzD,MAAMgJ,WAAY9L,KAAKqB,UAGtB,mCAA/ByB,MAAMA,oDAAOyF,iBACXvI,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAAA,kBAAmBC,eAAAA,gBAC/BzK,KAAK4K,oBAAoBjJ,MAAO9C,KAAM0L,OAAQC,kBAAmBC,iBAC9C,aAAhB9I,MAAMA,SACX9C,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAAA,kBAAmBC,eAAAA,gBAC/BzK,KAAK6K,qBAAqBlJ,MAAO9C,KAAM0L,OAAQC,kBAAmBC,sBAGrE5L,KAAOA,UACPX,eAAiBqM,YACjBvM,iBAAmBwM,kBAAkBM,QAAOC,IAAMA,EAAEC,WAAaD,EAAEC,UAAYhL,KAAK3B,mBACpFJ,aAAewM,eAAeK,QAAOG,IAAMA,EAAED,WAAaC,EAAED,UAAYhL,KAAK3B,mBAE7EF,4BAGJ+M,kBAAkBlL,KAAKnB,KAAMmB,KAAK9B,eAAgB8B,KAAKhC,iBAAkBgC,KAAK/B,cAC/E+B,KAAK1B,cAAgB,EAAG,OAClB6M,gBAAkB5B,KAAKnE,IAAKpF,KAAK3B,YAAc2B,KAAK1B,cAAiB,IAAK,UAC3E8K,eAAe+B,oBAGpBnL,KAAKlC,iBAAkB,OACjBsN,cAAgB,IAChBC,cAAgBD,cAAgBpL,KAAKrC,WACtCU,aAAe+M,cAChBpL,KAAK7B,mBAAqB6B,KAAK5B,YAC3B4B,KAAKpC,UACA0C,aAAY,SAEZoC,kBACAwI,kBAAkBlL,KAAKnB,KAAMmB,KAAK9B,eAAgB,GAAI,UAG1D0E,cAAgB0I,YAAW,IAAMtL,KAAKsK,aAAae,0BAtDvDH,kBAAkBlL,KAAKnB,KAAMmB,KAAK9B,eAAgB,GAAI,IA2DnEqN,iBAAiB1M,KAAM2M,WACbC,OAAS5M,KAAK6M,UAAU,EAAGF,WAG1B,CAACG,UAFUF,OAAOG,MAAM,MAAM1L,OAAS,EAE3B2L,IADPJ,OAAOvL,OAASuL,OAAOK,YAAY,MAAQ,GAI3DjB,qBAAqBlJ,MAAO9C,KAAM0L,OAAQwB,WAAYC,cAC9ChM,KAAKf,UAAYe,KAAKd,eAAiBc,KAAKf,SAASiB,OAAQ,OACvD6B,UAAY/B,KAAKf,SAASe,KAAKd,gBAE/B+M,eAAiBtK,MAAMgJ,aAE3B9L,KAAAA,KAAM0L,OAAAA,QAAUvK,KAAKkM,oBAAoBnK,UAAWlD,KAAMoN,eAAgB1B,OAAQyB,iBAC/E9M,uBAEF,CACHL,KAAAA,KACA0L,OAAAA,OACAC,kBAAmBuB,WACnBtB,eAAgBuB,WAIxBE,oBAAoBnK,UAAWlD,KAAMoN,eAAgBE,cAAeH,iBAC1DI,WAAarK,WAAa,GAC1BsK,QAAUD,WAAWvK,OAAO+J,MAAM,OAClCU,YAAcD,QAAQnM,OAAS,EAC/BqM,mBAAqBH,WAAWI,WAAW,OAASJ,WAAWK,SAAS,OAExEC,UAACA,UAADC,QAAYA,SAAW3M,KAAK4M,kBAC9B/N,KACAoN,eACAE,cACAE,QACAC,YACAC,oBAGEM,cAAgBhO,KAAK6M,UAAUgB,UAAWC,cAG3CG,mBAAmBD,cAAeH,UAAWV,iBAG5Ce,eAAiBF,cAAc3M,OACrCrB,KAAOA,KAAK6M,UAAU,EAAGgB,WAAaN,WAAavN,KAAK6M,UAAUiB,eAC5DK,aAAeZ,WAAWlM,OAAS6M,eAGnCE,UAAYjN,KAAKkN,2BACnBf,cACAF,eACAS,UACAC,QACAP,WACAG,gCAICY,uBAAuBT,UAAWC,QAASK,aAAcZ,YAEvD,CAACvN,KAAAA,KAAM0L,OAAQ0C,WAG1BL,kBAAkB/N,KAAMoN,eAAgBE,cAAeE,QAASC,YAAaC,uBACrEA,yBACO,CAACG,UAAWP,cAAeQ,QAASR,qBAGzCiB,UAACA,UAADC,QAAYA,SAAWrN,KAAKsN,cAAczO,KAAMoN,gBAChDsB,SAAW1O,KAAK6M,UAAU0B,UAAWC,SACrCG,MAAQxN,KAAKyN,qBAAqBF,SAAUH,kBAE7B,IAAjBI,MAAMtN,OACC,CAACwM,UAAWP,cAAeQ,QAASR,eAG3CG,YACOtM,KAAK0N,mBAAmBF,MAAOnB,QAASJ,gBAExCjM,KAAK2N,oBAAoBH,MAAOnB,QAAQ,GAAIJ,gBAI3DqB,cAAczO,KAAMoN,oBACZmB,UAAY,MACX,IAAI1L,EAAIuK,eAAiB,EAAGvK,GAAK,EAAGA,OACrB,OAAZ7C,KAAK6C,GAAa,CAClB0L,UAAY1L,EAAI,YAKpB2L,QAAUxO,KAAKqB,WACd,IAAIwB,EAAIuK,eAAgBvK,EAAI7C,KAAKqB,OAAQwB,OAC1B,OAAZ7C,KAAK6C,GAAa,CAClB2L,QAAU3L,cAKX,CAAC0L,UAAAA,UAAWC,QAAAA,SAGvBI,qBAAqBF,SAAUH,iBACrBI,MAAQ,OACVhC,IAAM,OAEHA,IAAM+B,SAASrN,QAAQ,MAEnBsL,IAAM+B,SAASrN,QAA4B,MAAlBqN,SAAS/B,MACrCA,SAEAA,KAAO+B,SAASrN,mBAKd0N,MAAQpC,SACPA,IAAM+B,SAASrN,QAA4B,MAAlBqN,SAAS/B,MACrCA,MAGAA,IAAMoC,OACNJ,MAAM1L,KAAK,CACPjD,KAAM0O,SAAS7B,UAAUkC,MAAOpC,KAChCoC,MAAOR,UAAYQ,MACnBC,IAAKT,UAAY5B,aAKtBgC,MAGXE,mBAAmBF,MAAOnB,QAASJ,oBAC3B6B,UAAY,CAACF,OAAQ,EAAGC,KAAM,EAAGE,OAAQ,EAAGC,UAAW,EAAGC,gBAAiB,OAE1E,IAAIvM,EAAI,EAAGA,EAAI8L,MAAMtN,OAAQwB,IAAK,OAC7BwM,YAAclO,KAAKmO,0BAA0BX,MAAOnB,QAAS3K,EAAGuK,iBAElEiC,YAAYE,WAAaN,UAAUC,OAClCG,YAAYE,aAAeN,UAAUC,OACrCG,YAAYD,gBAAkBH,UAAUG,mBACzCH,UAAYI,gBAIhBJ,UAAUC,MAAQ,SACX,CAACrB,UAAWoB,UAAUF,MAAOjB,QAASmB,UAAUD,KACpD,OACGQ,QAAUrO,KAAKsO,gBAAgBd,MAAOvB,sBACrC,CAACS,UAAW2B,QAAQT,MAAOjB,QAAS0B,QAAQR,MAI3DM,0BAA0BX,MAAOnB,QAASkC,WAAYtC,sBAC5CuC,SAAW,OACZ,IAAIC,EAAI,EAAGA,EAAIpC,QAAQnM,QAAUqO,WAAaE,EAAIjB,MAAMtN,OAAQuO,IACjED,SAAS1M,KAAK0L,MAAMe,WAAaE,OAGb,IAApBD,SAAStO,aACF,CAAC0N,OAAQ,EAAGC,KAAM,EAAGE,OAAQ,EAAGC,UAAW,EAAGC,gBAAiB,SAGpEA,gBAAkBjO,KAAK0O,4BAA4BrC,QAASmC,UAE5DJ,WAAaH,gBADGjO,KAAK2O,uBAAuBH,SAAUvC,gBACPuC,SAAStO,aAEvD,CACH0N,MAAOY,SAAS,GAAGZ,MACnBC,IAAKW,SAASA,SAAStO,OAAS,GAAG2N,IACnCE,MAAOK,WACPJ,UAAWQ,SAAStO,OACpB+N,gBAAiBA,iBAIzBS,4BAA4BrC,QAASmC,cAC7BP,gBAAkB,QAChBW,cAAgBrF,KAAKnE,IAAIoJ,SAAStO,OAAQmM,QAAQnM,YAEnD,IAAI2O,EAAI,EAAGA,EAAID,cAAeC,IAAK,OAC9BC,GAAKzC,QAAQwC,GAAGzH,cAChB2H,IAAMP,SAASK,GAAGhQ,KAAKuI,iBAEzB0H,KAAOC,IACPd,iBAAmB,OAChB,CAEHA,iBAAgC,GADbjO,KAAKgP,oBAAoBF,GAAIC,aAKjDd,gBAGXU,uBAAuBH,SAAUvC,oBACzBgD,cAAgB,QACdC,SAAWV,SAAS,GAAGZ,MACvBuB,UAAYX,SAASA,SAAStO,OAAS,GAAG2N,WAE5C5B,gBAAkBiD,UAAYjD,gBAAkBkD,YAChDF,eAAiB,GACbhD,gBAAkBuC,SAAS,GAAGZ,OAAS3B,gBAAkBuC,SAAS,GAAGX,MACrEoB,eAAiB,IAIlBA,cAGXtB,oBAAoBH,MAAO4B,OAAQnD,sBACzBoD,YAAcD,OAAOhI,cACrBkI,oBAAsBtP,KAAKuP,wBAAwB/B,MAAO6B,gBAE5DC,oBAAoBvB,MAAQ,SACrB,CAACrB,UAAW4C,oBAAoBE,KAAK5B,MAAOjB,QAAS2C,oBAAoBE,KAAK3B,WAGnF4B,kBAAoBzP,KAAK0P,sBAAsBlC,MAAO6B,YAAapD,uBAErEwD,kBAAkBD,KACX,CAAC9C,UAAW+C,kBAAkBD,KAAK5B,MAAOjB,QAAS8C,kBAAkBD,KAAK3B,KAI9E7N,KAAK2P,2BAA2BnC,MAAM,GAAGI,MAAOJ,MAAMA,MAAMtN,OAAS,GAAG2N,IACvC5B,eAAgBjM,KAAKnB,MAGjE0Q,wBAAwB/B,MAAO6B,iBACvBvB,UAAY,CAAC0B,KAAM,KAAMzB,MAAO,OAE/B,MAAMyB,QAAQhC,MAAO,KAClBoC,WAAa5P,KAAKgP,oBAAoBK,YAAaG,KAAK3Q,KAAKuI,qBAC3DyI,UAAYL,KAAK3Q,KAAKuI,cAGxByI,UAAU3P,OAA8B,GAArBmP,YAAYnP,QAAgBmP,YAAY7C,WAAWqD,aACtED,YAA0B,IAG1BA,WAAa9B,UAAUC,QACvBD,UAAY,CAAC0B,KAAAA,KAAMzB,MAAO6B,oBAI3B9B,UAGX4B,sBAAsBlC,MAAO6B,YAAapD,oBAClC6B,UAAY,CAAC0B,KAAM,KAAMzB,OAAQ,OAEhC,MAAMyB,QAAQhC,MAAO,KAClBO,MAAQ/N,KAAK8P,mBAAmBN,KAAMH,YAAapD,gBAEnD8B,MAAQD,UAAUC,QAClBD,UAAY,CAAC0B,KAAAA,KAAMzB,MAAAA,eAIpBD,UAGXgC,mBAAmBN,KAAMH,YAAapD,oBAC9B8B,MAAQ,KAGR9B,gBAAkBuD,KAAK5B,OAAS3B,gBAAkBuD,KAAK3B,IACvDE,OAAS,OACN,OACGgC,SAAWxG,KAAKnE,IAClBmE,KAAKyG,IAAI/D,eAAiBuD,KAAK5B,OAC/BrE,KAAKyG,IAAI/D,eAAiBuD,KAAK3B,MAEnCE,OAASxE,KAAKpE,IAAI,EAAG,GAAK4K,cAI1BH,WAAa5P,KAAKgP,oBAAoBK,YAAaG,KAAK3Q,KAAKuI,qBAC3DyI,UAAYL,KAAK3Q,KAAKuI,qBACxByI,UAAU3P,OAA8B,GAArBmP,YAAYnP,QAAgBmP,YAAY7C,WAAWqD,aACtED,YAA0B,IAE9B7B,OAAsB,GAAb6B,WAEF7B,MAGX4B,2BAA2BvC,UAAWC,QAASpB,eAAgBpN,UACvD6N,UAAYT,oBACTS,UAAYU,WAAqC,MAAxBvO,KAAK6N,UAAY,IAAsC,OAAxB7N,KAAK6N,UAAY,IAC5EA,gBAEAC,QAAUV,oBACPU,QAAUU,SAA6B,MAAlBxO,KAAK8N,UAAsC,OAAlB9N,KAAK8N,UACtDA,gBAEG,CAACD,UAAAA,UAAWC,QAAAA,SAGvBG,mBAAmBD,cAAeH,UAAWV,cACrCa,cAAc3M,OAAS,MAClB,IAAIwB,EAAI,EAAGA,EAAImL,cAAc3M,OAAQwB,IACtCsK,UAAUlK,KAAK,CACXyF,MAAOmF,UAAYhL,EACnBuO,MAAOpD,cAAcnL,GACrB8F,KAAMxH,KAAK3B,YACX2M,UAAWhL,KAAK3B,YAAc,MAM9C6O,2BAA2Bf,cAAeF,eAAgBS,UAAWC,QAASP,WAAYG,uBAClFA,0BACOG,UAAYN,WAAWlM,UAG9B+L,gBAAkBS,WAAaT,gBAAkBU,eAC1CD,UAAYN,WAAWlM,aAG5B8M,aAAeZ,WAAWlM,QAAUyM,QAAUD,kBAEhDP,eAAiBQ,QACVR,cAAgBa,aAChBb,cAAgBO,WAAaP,cAAgBQ,QAC7CD,UAAYN,WAAWlM,OAG3BiM,cAGXgB,uBAAuBT,UAAWC,QAASK,aAAcZ,iBAEhD8D,wBAAwBxD,UAAWC,QAASK,mBAG5CmD,sBAAsBzD,UAAWN,iBAGjCgE,oBAAoB1D,UAAWC,QAASK,aAAcZ,YAG/D8D,wBAAwBxD,UAAWC,QAASK,cACpChN,KAAKhB,mBACAA,YAAcgB,KAAKhB,YAAYkD,KAAImO,GAChCA,EAAE9I,OAASoF,QACJ,IAAI0D,EAAG9I,MAAO8I,EAAE9I,MAAQyF,cACxBqD,EAAE9I,OAASmF,WAAa2D,EAAE9I,MAAQoF,QAClC,KAEJ0D,IACRvF,QAAOuF,GAAW,OAANA,KAIvBF,sBAAsBzD,UAAWN,eACxBpM,KAAKb,eACDA,QAAU,IAGO,KAAtBiN,WAAWvK,WACN,IAAIH,EAAI,EAAGA,EAAI0K,WAAWlM,OAAQwB,SAC9BvC,QAAQ2C,KAAK,CACdyF,MAAOmF,UAAYhL,EACnBuO,MAAO7D,WAAW1K,KAMlC0O,oBAAoB1D,UAAWC,QAASK,aAAcZ,kBAC5CkE,iBAAmB,IAAIC,QACxB,IAAI7O,EAAI,EAAGA,EAAI0K,WAAWlM,OAAQwB,IACnC4O,iBAAiBtM,IAAI0I,UAAYhL,QAGhCvC,QAAUa,KAAKb,QAAQ+C,KAAImO,QACvBC,iBAAiBE,IAAIH,EAAE9I,OAAQ,IAC5B8I,EAAE9I,OAASoF,cACJ,IAAI0D,EAAG9I,MAAO8I,EAAE9I,MAAQyF,cAC5B,GAAIqD,EAAE9I,OAASmF,WAAa2D,EAAE9I,MAAQoF,eAClC,YAGR0D,KACRvF,QAAOuF,GAAW,OAANA,IAInBrB,oBAAoByB,KAAMC,SAClBD,OAASC,YACF,KAES,IAAhBD,KAAKvQ,QAAgC,IAAhBwQ,KAAKxQ,cACnB,KAIPuQ,KAAKjE,WAAWkE,OAASA,KAAKlE,WAAWiE,YAClC,SAILE,KAAOF,KAAKvQ,OACZ0Q,KAAOF,KAAKxQ,OACZ2Q,OAAStP,MAAMqP,KAAO,GAAGE,KAAK,MAAM5O,KAAI,IAAMX,MAAMoP,KAAO,GAAGG,KAAK,SAEpE,IAAIpP,EAAI,EAAGA,GAAKiP,KAAMjP,IACvBmP,OAAO,GAAGnP,GAAKA,MAEd,IAAI+M,EAAI,EAAGA,GAAKmC,KAAMnC,IACvBoC,OAAOpC,GAAG,GAAKA,MAGd,IAAIA,EAAI,EAAGA,GAAKmC,KAAMnC,QAClB,IAAI/M,EAAI,EAAGA,GAAKiP,KAAMjP,IAAK,OACtBqP,KAAON,KAAK/O,EAAI,KAAOgP,KAAKjC,EAAI,GAAK,EAAI,EAC/CoC,OAAOpC,GAAG/M,GAAK6H,KAAKnE,IAChByL,OAAOpC,GAAG/M,EAAI,GAAK,EACnBmP,OAAOpC,EAAI,GAAG/M,GAAK,EACnBmP,OAAOpC,EAAI,GAAG/M,EAAI,GAAKqP,YAK7BC,OAASzH,KAAKpE,IAAIwL,KAAMC,aACvB,EAAKC,OAAOD,MAAMD,MAAQK,OAIrC1C,gBAAgBd,MAAOvB,mBACE,IAAjBuB,MAAMtN,aACC,CAAC0N,MAAO3B,eAAgB4B,IAAK5B,oBAGpCoC,QAAUb,MAAM,GAChByD,YAAc1H,KAAKnE,IACnBmE,KAAKyG,IAAI/D,eAAiBuB,MAAM,GAAGI,OACnCrE,KAAKyG,IAAI/D,eAAiBuB,MAAM,GAAGK,UAGlC,MAAM2B,QAAQhC,MAAO,IAClBvB,gBAAkBuD,KAAK5B,OAAS3B,gBAAkBuD,KAAK3B,WAChD2B,WAGLO,SAAWxG,KAAKnE,IAClBmE,KAAKyG,IAAI/D,eAAiBuD,KAAK5B,OAC/BrE,KAAKyG,IAAI/D,eAAiBuD,KAAK3B,MAG/BkC,SAAWkB,cACXA,YAAclB,SACd1B,QAAUmB,aAIXnB,QAIXzD,oBAAoBjJ,MAAO9C,KAAM0L,OAAQwB,WAAYC,iBAC3C3E,IAAM1F,MAAM0F,IACZ6J,aAAelR,KAAKmR,SAAS9J,QAG/BrH,KAAKoR,gBAAgB/J,WACd,CAACxI,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAmBuB,WAAYtB,eAAgBuB,cAIrEhM,KAAKqR,gBAAgBhK,YACdrH,KAAKsR,oBAAoB3P,MAAO9C,KAAM0L,OAAQwB,WAAYC,iBAI/D7N,kBAAoB6B,KAAK7B,kBACzBoT,UAAYvR,KAAKwR,gBAAgBrT,0BAGnC6B,KAAKyR,iBAAiBpK,IAAK1F,OACpB3B,KAAK0R,qBAAqB/P,MAAO4P,UAAW1S,KAAM0L,OAAQwB,WAAYC,iBAI5E2F,qBAAqBtK,KAGtBrH,KAAK4R,oBAAoBvK,IAAKkK,cAC5B1S,KAAAA,KAAM0L,OAAAA,QAAUvK,KAAK6R,wBAAwBN,UAAW1S,KAAM0L,OAAQyB,YACjE,CAACnN,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAmBuB,WAAYtB,eAAgBuB,YAIlEhM,KAAK8R,oBAAoBzK,IAAK6J,aAAcrS,KAAM0L,OAAQwB,WAAYC,UAAWuF,YAG5FH,gBAAgB/J,YACI,MAARA,KAAuB,MAARA,OAAiBrH,KAAKtB,qBAAuBsB,KAAKpB,kBAG7EyS,gBAAgBhK,YACI,MAARA,KAAuB,MAARA,OAAiBrH,KAAKtB,qBAAuBsB,KAAKpB,kBAG7E0S,oBAAoB3P,MAAO9C,KAAM0L,OAAQwB,WAAYC,iBAC3C+F,eAAiB/R,KAAK7B,kBAAoB,KAC5C4T,eAAiB/R,KAAKC,QAAQC,OAAQ,OAChC8R,UAAYhS,KAAKC,QAAQ8R,mBAEP,UAApBC,UAAUrQ,QAAwC,MAAlBqQ,UAAU3K,KAAiC,MAAlB2K,UAAU3K,KAAc,OAC3E4K,YAAcD,UAAUrH,cAC1BsH,YAAc1H,QAAU1L,KAAKqB,OAAS,EAAG,OACnCgS,eAAiBrT,KACvBA,KAAOA,KAAK6M,UAAU,EAAGuG,aAAepT,KAAK6M,UAAUnB,QACvDA,OAAS0H,gBAGJ,IAAIvQ,EAAI,EAAGA,EAAIwQ,eAAehS,QAAUwB,EAAI6I,OAAQ7I,IACrDsK,UAAUlK,KAAK,CACXyF,MAAO0K,YACPhC,MAAOiC,eAAexQ,GACtB8F,KAAMxH,KAAK3B,YACX2M,UAAWhL,KAAK3B,YAAc,oBAO7CK,qBAAsB,OACtBE,kBAAmB,EAEjB,CAACC,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAmBuB,WAAYtB,eAAgBuB,WAGzEyF,iBAAiBpK,IAAK1F,eACL,MAAR0F,KAAuB,MAARA,MAAiBrH,KAAKtB,sBAAuBsB,KAAKpB,oBAC1D+C,MAAMC,eAAgD,KAA/BD,MAAMC,cAAcC,QAC3C7B,KAAKlB,cAAgBkB,KAAKjB,kBAAoBiB,KAAKlB,aAAaoB,QAKhFwR,qBAAqB/P,MAAO4P,UAAW1S,KAAM0L,OAAQwB,WAAYC,iBACvDpK,cAAgBD,MAAMC,eAAiB5B,KAAKlB,aAAakB,KAAKjB,0BAEhEwS,aACE1S,KAAAA,KAAM0L,OAAAA,QAAUvK,KAAK6R,wBAAwBN,UAAW1S,KAAM0L,OAAQyB,cAG1EnN,KAAAA,KAAM0L,OAAAA,QAAUvK,KAAKmS,kBAAkBvQ,cAAe/C,KAAM0L,cACzDxL,yBACAqT,2BACA3T,cAAe,EAEb,CAACI,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAmBuB,WAAYtB,eAAgBuB,WAGzEoG,2BACS1T,qBAAsB,OACtBC,mBAAoB,OACpBC,kBAAmB,EAG5BgT,oBAAoBvK,IAAKkK,kBACL,cAARlK,KAA+B,WAARA,MAAqBkK,WAAaA,UAAUrR,OAAS,EAGxF4R,oBAAoBzK,IAAK6J,aAAcrS,KAAM0L,OAAQwB,WAAYC,UAAWuF,kBACpEvR,KAAKqS,gBAAgBhL,IAAKkD,UACxB1L,KAAAA,KAAM0L,OAAAA,QAAUvK,KAAKsS,oBAAoBzT,KAAM0L,OAAQyB,YAClDhM,KAAKuS,aAAalL,IAAKkD,OAAQ1L,QACpCA,KAAAA,MAAQmB,KAAKwS,iBAAiB3T,KAAM0L,OAAQyB,YACvChM,KAAKyS,gBAAgBpL,KAC5BkD,OAASvK,KAAK0S,oBAAoBrL,IAAKxI,KAAM0L,QACtCvK,KAAK2S,mBAAmBtL,IAAKkD,UAClC1L,KAAAA,KAAM0L,OAAAA,QAAUvK,KAAK4S,gBAAgB/T,KAAM0L,OAAQyB,YAC9ChM,KAAK6S,gBAAgBxL,IAAKkD,OAAQ1L,QACvCA,KAAAA,MAAQmB,KAAK8S,aAAajU,KAAM0L,OAAQyB,YACnChM,KAAK+S,UAAU1L,KACtBkD,OAASvK,KAAKgT,cAAcnU,KAAM0L,QAC3BvK,KAAKiT,YAAY5L,KACxBkD,OAASvK,KAAKkT,gBAAgBrU,KAAM0L,QAC7BvK,KAAKmT,mBAAmB9L,KAC/BkD,OAASvK,KAAKoT,gBAAgB/L,IAAKxI,KAAM0L,QAClC2G,cAAgBA,aAAahR,OAAS,IACzCqR,WAAaA,UAAUrR,OAAS,KAC9BrB,KAAAA,KAAM0L,OAAAA,QAAUvK,KAAK6R,wBAAwBN,UAAW1S,KAAM0L,OAAQyB,cAE1EnN,KAAAA,KAAM0L,OAAAA,QAAUvK,KAAKqT,sBAAsBnC,aAAcrS,KAAM0L,OAAQwB,cAGtE,CAAClN,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAmBuB,WAAYtB,eAAgBuB,WAGzEwF,gBAAgB8B,0CACNC,aAAevT,KAAKC,QAAQqT,eAEQ,yCAAtCC,aAAa5R,gEAAOyF,iBACE,cAArBmM,aAAalM,KAA4C,WAArBkM,aAAalM,KAAmB,OAE/DmM,WAAaD,aAAa5I,kBACzB3K,KAAKyT,iBAAiBD,WAAYD,aAAcD,mBAEpD,KAGXG,iBAAiBD,WAAYD,aAAcD,gBAClC,IAAI5R,EAAI4R,WAAa,EAAG5R,EAAI1B,KAAKC,QAAQC,OAAQwB,IAAK,4BACjDsQ,UAAYhS,KAAKC,QAAQyB,MAEQ,oCAAnCsQ,UAAUrQ,0DAAOyF,gBACjB4K,UAAU3K,MAAQkM,aAAalM,IAAK,OAE9BqM,QAAU1B,UAAUrH,WAGpBqC,aAAezD,KAAKyG,IAAIwD,WAAaE,YAEvC1G,aAAe,QACR,CACHY,MAAOrE,KAAKnE,IAAIoO,WAAYE,SAC5B7F,IAAKtE,KAAKpE,IAAIqO,WAAYE,SAC1BxT,OAAQ8M,cAET,GAAqB,IAAjBA,mBACkB,cAArBuG,aAAalM,IACN,CACHuG,MAAO8F,QACP7F,IAAK2F,WACLtT,OAAQ,GAGL,CACH0N,MAAO4F,WACP3F,IAAK6F,QACLxT,OAAQ,iBAOrB,KAGX2R,wBAAwBN,UAAW1S,KAAM0L,OAAQyB,iBACvC4B,MAACA,MAADC,IAAQA,IAAR3N,OAAaA,QAAUqR,cAGxB,IAAI7P,EAAIkM,MAAOlM,EAAImM,KAAOnM,EAAI7C,KAAKqB,OAAQwB,IAC5CsK,UAAUlK,KAAK,CACXyF,MAAOqG,MACPqC,MAAOpR,KAAK6C,GACZ8F,KAAMxH,KAAK3B,YACX2M,UAAWhL,KAAK3B,YAAc,aAItCQ,KAAOA,KAAK6M,UAAU,EAAGkC,OAAS/O,KAAK6M,UAAUmC,UAE5C8F,wBAAwB/F,MAAO1N,QAI7B,CAACrB,KAAAA,KAAM0L,OAFLqD,OAMbuE,kBAAkBvQ,cAAe/C,KAAM0L,cAC7B6B,WAAaxK,eAAiB,MACpC/C,KAAOA,KAAK6M,UAAU,EAAGnB,QAAU6B,WAAavN,KAAK6M,UAAUnB,QAGrC,KAAtB6B,WAAWvK,WACN,IAAIH,EAAI,EAAGA,EAAI0K,WAAWlM,OAAQwB,IAC9B1B,KAAKhB,mBACDA,YAAc,SAElBA,YAAY8C,KAAK,CAClByF,MAAOgD,OAAS7I,EAChBuO,MAAO7D,WAAW1K,WAKvB,CAAC7C,KAAAA,KAAM0L,OAAQA,OAAS6B,WAAWlM,QAI9CyT,wBAAwBpF,WAAYqF,iBAC3B5U,YAAcgB,KAAKhB,YAAYkD,KAAImO,GAChCA,EAAE9I,OAASgH,WAAaqF,WACjB,IAAIvD,EAAG9I,MAAO8I,EAAE9I,MAAQqM,YACxBvD,EAAE9I,OAASgH,YAAc8B,EAAE9I,MAAQgH,WAAaqF,WAEhD,KAEJvD,IACRvF,QAAOuF,GAAW,OAANA,IAEXrQ,KAAKb,eACAA,QAAUa,KAAKb,QAAQ+C,KAAImO,GACxBA,EAAE9I,OAASgH,WAAaqF,WACjB,IAAIvD,EAAG9I,MAAO8I,EAAE9I,MAAQqM,YACxBvD,EAAE9I,OAASgH,YAAc8B,EAAE9I,MAAQgH,WAAaqF,WAChD,KAEJvD,IACRvF,QAAOuF,GAAW,OAANA,KAKvBsB,qBAAqBtK,KACL,YAARA,SACK3I,qBAAsB,EACZ,UAAR2I,SACF1I,mBAAoB,EACV,SAAR0I,SACFzI,kBAAmB,EACR,MAARyI,KAAuB,MAARA,MAAiBrH,KAAKtB,sBAAuBsB,KAAKpB,iBAEjE,CAAC,UAAW,OAAQ,YAAa,SAAU,YAAa,cAAciV,SAASxM,YAClF3I,qBAAsB,OACtBC,mBAAoB,OACpBC,kBAAmB,OACnBH,cAAe,QALfA,cAAe,EAS5B4T,gBAAgBhL,IAAKkD,cACF,cAARlD,KAAuBrH,KAAKtB,qBAAuB6L,OAAS,EAGvEgI,aAAalL,IAAKkD,OAAQ1L,YACP,WAARwI,KAAoBrH,KAAKtB,qBAAuB6L,OAAS1L,KAAKqB,OAGzEuS,gBAAgBpL,YACLrH,KAAKtB,sBAAgC,cAAR2I,KAA+B,eAARA,KAG/DsL,mBAAmBtL,IAAKkD,cACL,cAARlD,MAAwBrH,KAAKvB,cAAgB8L,OAAS,EAGjEsI,gBAAgBxL,IAAKkD,OAAQ1L,YACV,WAARwI,MAAqBrH,KAAKtB,qBAAuB6L,OAAS1L,KAAKqB,OAG1EiT,mBAAmB9L,YACPrH,KAAKtB,sBAAgC,cAAR2I,KAA+B,eAARA,KAGhE0L,UAAU1L,WACS,YAARA,IAGX4L,YAAY5L,WACO,cAARA,IAGXqL,oBAAoBrL,IAAKxI,KAAM0L,cACZ,cAARlD,IACDrH,KAAK8T,yBAAyBjV,KAAM0L,QACpCvK,KAAK+T,qBAAqBlV,KAAM0L,QAG1CqI,gBAAgB/T,KAAM0L,OAAQyB,kBAC1BA,UAAUlK,KAAK,CACXyF,MAAOgD,OAAS,EAChB0F,MAAOpR,KAAK0L,OAAS,GACrB/C,KAAMxH,KAAK3B,YACX2M,UAAWhL,KAAK3B,YAAc,WAE7BsV,wBAAwBpJ,OAAS,EAAG,GAClC,CACH1L,KAAMA,KAAK6M,UAAU,EAAGnB,OAAS,GAAK1L,KAAK6M,UAAUnB,QACrDA,OAAQA,OAAS,GAIzBuI,aAAajU,KAAM0L,OAAQyB,kBACvBA,UAAUlK,KAAK,CACXyF,MAAOgD,OACP0F,MAAOpR,KAAK0L,QACZ/C,KAAMxH,KAAK3B,YACX2M,UAAWhL,KAAK3B,YAAc,WAE7BsV,wBAAwBpJ,OAAQ,GAC9B,CACH1L,KAAMA,KAAK6M,UAAU,EAAGnB,QAAU1L,KAAK6M,UAAUnB,OAAS,GAC1DA,OAAAA,QAIR6I,gBAAgB/L,IAAKxI,KAAM0L,cACR,cAARlD,IACDkC,KAAKpE,IAAI,EAAGoF,OAAS,GACrBhB,KAAKnE,IAAIvG,KAAKqB,OAAQqK,OAAS,GAGzC8I,sBAAsBnC,aAAcrS,KAAM0L,OAAQwB,mBAC9ClN,KAAOA,KAAK6M,UAAU,EAAGnB,QAAU2G,aAAerS,KAAK6M,UAAUnB,QAE7DvK,KAAKhB,mBACAA,YAAcgB,KAAKhB,YAAYkD,KAAImO,GAC7BA,EAAE9I,OAASgD,OAAS,IAAI8F,EAAG9I,MAAO8I,EAAE9I,MAAQ,GAAK8I,KAG5DrQ,KAAKb,eACAA,QAAUa,KAAKb,QAAQ+C,KAAImO,GACrBA,EAAE9I,OAASgD,OAAS,IAAI8F,EAAG9I,MAAO8I,EAAE9I,MAAQ,GAAK8I,KAGpC,KAAxBa,aAAarP,QACbkK,WAAWjK,KAAK,CACZyF,MAAOgD,OACP0F,MAAOiB,aACP1J,KAAMxH,KAAK3B,YACX2M,UAAWhL,KAAK3B,YAAc,OAG/B,CAACQ,KAAAA,KAAM0L,OAAQA,OAAS,GAGnCiI,iBAAiB3T,KAAM0L,OAAQyB,iBACrBW,QAAU3M,KAAK+T,qBAAqBlV,KAAM0L,QAC1CyJ,aAAenV,KAAK6M,UAAUnB,OAAQoC,aACvC,IAAIjL,EAAI,EAAGA,EAAIsS,aAAa9T,OAAQwB,IACrCsK,UAAUlK,KAAK,CACXyF,MAAOgD,OAAS7I,EAChBuO,MAAO+D,aAAatS,GACpB8F,KAAMxH,KAAK3B,YACX2M,UAAWhL,KAAK3B,YAAc,kBAGjCsV,wBAAwBpJ,OAAQyJ,aAAa9T,QAC3C,CACHrB,KAAMA,KAAK6M,UAAU,EAAGnB,QAAU1L,KAAK6M,UAAUiB,SACjDpC,OAAAA,QAIRyI,cAAcnU,KAAM0L,cACV0J,MAAQpV,KAAK+M,MAAM,OACnBD,UAACA,UAADE,IAAYA,KAAO7L,KAAKuL,iBAAiB1M,KAAM0L,WACjDoB,UAAY,EAAG,OACTuI,SAAWD,MAAMtI,UAAY,GACnCpB,OAAS0J,MAAME,MAAM,EAAGxI,UAAY,GAAGyI,KAAK,MAAMlU,OAAS,EAAIqJ,KAAKnE,IAAIyG,IAAKqI,SAAShU,aAEtFqK,OAAS,SAENA,OAGX2I,gBAAgBrU,KAAM0L,cACZ0J,MAAQpV,KAAK+M,MAAM,OACnBD,UAACA,UAADE,IAAYA,KAAO7L,KAAKuL,iBAAiB1M,KAAM0L,WACjDoB,UAAYsI,MAAM/T,OAAS,EAAG,OACxBmU,SAAWJ,MAAMtI,UAAY,GACnCpB,OAAS0J,MAAME,MAAM,EAAGxI,UAAY,GAAGyI,KAAK,MAAMlU,OAAS,EAAIqJ,KAAKnE,IAAIyG,IAAKwI,SAASnU,aAEtFqK,OAAS1L,KAAKqB,cAEXqK,OAGX+H,oBAAoBzT,KAAM0L,OAAQyB,eAC1BU,UAAYnC,YACTmC,UAAY,GAA6B,MAAxB7N,KAAK6N,UAAY,IACrCA,iBAEGA,UAAY,GAA6B,MAAxB7N,KAAK6N,UAAY,IACrCA,kBAEEsH,aAAenV,KAAK6M,UAAUgB,UAAWnC,YAC1C,IAAI7I,EAAI,EAAGA,EAAIsS,aAAa9T,OAAQwB,IACrCsK,UAAUlK,KAAK,CACXyF,MAAOmF,UAAYhL,EACnBuO,MAAO+D,aAAatS,GACpB8F,KAAMxH,KAAK3B,YACX2M,UAAWhL,KAAK3B,YAAc,kBAGjCsV,wBAAwBjH,UAAWsH,aAAa9T,QAC9C,CAACrB,KAAMA,KAAK6M,UAAU,EAAGgB,WAAa7N,KAAK6M,UAAUnB,QAASA,OAAQmC,WAIjFqH,qBAAqBlV,KAAM0L,YAClB1L,MAAQ0L,QAAU1L,KAAKqB,cACjBqK,UAEU,MAAjB1L,KAAK0L,aACEA,OAAS1L,KAAKqB,QAA2B,MAAjBrB,KAAK0L,SAC/BA,YAGLA,QAAU1L,KAAKqB,OAAQ,KACnBoU,aAAezV,KAAKqB,OAAS,OAC1BoU,cAAgB,GAA4B,MAAvBzV,KAAKyV,eAC5BA,sBAEEA,aAAe,MAEtB3H,QAAUpC,YACPoC,QAAU9N,KAAKqB,QAA4B,MAAlBrB,KAAK8N,UAChCA,iBAEEA,QAIXmH,yBAAyBjV,KAAM0L,WACvBA,QAAU,SACH,MAEPiB,IAAMjB,OAAS,OACZiB,IAAM,IAAoB,MAAd3M,KAAK2M,MAA8B,OAAd3M,KAAK2M,OACxCA,WAEEA,IAAM,GAAuB,MAAlB3M,KAAK2M,IAAM,IAAgC,OAAlB3M,KAAK2M,IAAM,IACjDA,aAGEA,IAGX+I,YACQvU,KAAKlC,wBACAA,kBAAmB,OAExB0W,WAAa,QACZvU,QAAQ0F,SAAQhE,QACiB,YAA9BA,MAAMA,MAAMyF,gBACZoN,WAAaxU,KAAKmR,SAASxP,MAAM0F,IAAKmN,qBAGzC9U,cAAc2D,UAAYmR,WAAWL,MAAM,GAAI,QAC/C/K,eAAe,KAIxB9D,WAAW6D,kBACDsL,WAAazU,KAAKlC,sBACnB4E,mBAECgS,WAAc1U,KAAK1B,cAAgB6K,WAAc,SAClD9K,YAAcqW,gBACdvW,kBAAoB,OACpBU,KAAO,QACPX,eAAiB,OACjBF,iBAAmB,QACnBC,aAAe,QACfS,qBAAsB,OACtBE,kBAAmB,OACnBH,cAAe,OACfO,YAAc,QACdD,kBAAoB,OACpBG,eAAiB,OACjBC,QAAU,OACXN,KAAO,GACP0L,OAAS,EACTwB,WAAa,GACbC,UAAY,GACZ2I,WAAa,EACbC,QAAU,MAET,IAAIlT,EAAI,EAAGA,EAAI1B,KAAKC,QAAQC,OAAQwB,IAAK,yBACpCC,MAAQ3B,KAAKC,QAAQyB,MACvBC,MAAMQ,gBAAkBR,MAAMQ,eAAiBuS,WAAY,MACtDvW,kBAAoBuD,aAGJgJ,IAArB/I,MAAMgJ,YAAwD,IAA3B3K,KAAK7B,mBACxB,cAAhBwD,MAAMA,OAAyC,YAAhBA,MAAMA,QACrC4I,OAAShB,KAAKpE,IAAI,EAAGoE,KAAKnE,IAAIzD,MAAMgJ,WAAY9L,KAAKqB,UAEtB,mCAA/ByB,MAAMA,oDAAOyF,qBACRrI,kBAAoB4V,WACN,MAAdhT,MAAM0F,KAA6B,MAAd1F,MAAM0F,MAAiBrH,KAAKtB,sBAAuBsB,KAAKpB,kBAC9E+V,eAEF9V,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAmBuB,WAAYtB,eAAgBuB,WAC3DhM,KAAK4K,oBAAoBjJ,MAAO9C,KAAM0L,OAAQwB,WAAYC,aACvC,aAAhBrK,MAAMA,aACRzC,eAAiB0V,UACpB/V,KAAAA,KAAM0L,OAAAA,OAAQC,kBAAmBuB,WAAYtB,eAAgBuB,WAC3DhM,KAAK6K,qBAAqBlJ,MAAO9C,KAAM0L,OAAQwB,WAAYC,YAC/D4I,gBAECzW,kBAAoBuD,EAAI,OAG5B3C,kBAAoB4V,gBACpBzV,eAAiB0V,aACjB/V,KAAOA,UACPX,eAAiBqM,YACjBvM,iBAAmB+N,WAAWjB,QAAOC,IAAMA,EAAEC,WAAaD,EAAEC,UAAY0J,kBACxEzW,aAAe+N,UAAUlB,QAAOG,IAAMA,EAAED,WAAaC,EAAED,UAAY0J,kBACnExJ,kBAAkBlL,KAAKnB,KAAMmB,KAAK9B,eAAgB8B,KAAKhC,iBAAkBgC,KAAK/B,mBAC9EmL,eAAeD,YAEhBsL,kBACK3W,kBAAmB,OACnBwM,aAMbY,kBAAkBrM,KAAMX,eAAgB6N,WAAYC,eAC5C5J,KAAO,SACLyS,aAAe,GACfC,YAAc,GACdC,UAAY,GACZC,MAAQ,GACR3W,YAAc2B,KAAK3B,YAEzB0N,WAAWpG,SAAQoF,QACXrC,QAAU,EACVqC,EAAEC,WAAaD,EAAEC,UAAY3M,YAAc,MAC3CqK,QAAUa,KAAKpE,IAAI,GAAI4F,EAAEC,UAAY3M,aAAe,MAExDwW,aAAa9J,EAAExD,OAAS,CAAC0I,MAAOlF,EAAEkF,MAAOvH,QAAAA,YAG7CsD,UAAUrG,SAAQsF,QACVvC,QAAU,GACVuC,EAAED,WAAaC,EAAED,UAAY3M,YAAc,MAC3CqK,QAAUa,KAAKpE,IAAI,GAAK8F,EAAED,UAAY3M,aAAe,IAAO,KAEhEyW,YAAY7J,EAAE1D,OAAS,CAAC0I,MAAOhF,EAAEgF,MAAOvH,QAAAA,YAIxC1I,KAAKhB,kBACAA,YAAY2G,SAAQ0K,IACjBA,EAAE9I,MAAQ1I,KAAKqB,SACf6U,UAAU1E,EAAE9I,QAAS,MAM7BvH,KAAKb,cACAA,QAAQwG,SAAQ0K,IACbA,EAAE9I,MAAQ1I,KAAKqB,SACf8U,MAAM3E,EAAE9I,QAAS,YAMvB0N,oBAAsBjJ,UAAUlB,QAAOG,GAAKA,EAAE1D,OAAS1I,KAAKqB,SAC5DgV,UAAYrW,KAAK+M,MAAM,UACzBrI,gBAAkB,MAEjB,IAAIoI,UAAY,EAAGA,UAAYuJ,UAAUhV,OAAQyL,YAAa,OACzDwJ,KAAOD,UAAUvJ,eAClB,IAAIjK,EAAI,EAAGA,EAAIyT,KAAKjV,OAAQwB,IAAK,CAC9B6B,kBAAoBrF,iBACpBkE,MAAQ,mDAENgT,KAAOD,KAAKzT,GACdoT,YAAYvR,mBACZnB,MAAS,oFACH0S,YAAYvR,iBAAiBmF,aAAaoM,YAAYvR,iBAAiB0M,sBAE3EoF,SAAWN,UAAUxR,iBACrB+R,KAAON,MAAMzR,iBACbgS,cAAgBV,aAAatR,kBAA6B,MAAT6R,KAGnDhT,MADAiT,UAAYE,cACH,iHACHV,aAAatR,iBAAiBmF,aAAa0M,cAC1CE,MAAQC,cACN,6GACHV,aAAatR,iBAAiBmF,aAAa0M,cAC1CC,SACE,0CAAkD,MAATD,KAAe,IAAMpV,KAAKwV,WAAWJ,eAChFE,KACE,sCAA8C,MAATF,KAAe,IAAMpV,KAAKwV,WAAWJ,eAC5EG,cACE,wFACHV,aAAatR,iBAAiBmF,aAAa0M,cAEhC,MAATA,KAAe,IAAMpV,KAAKwV,WAAWJ,MAEjD7R,kBAEAA,kBAAoBrF,iBACpBkE,MAAQ,6CAERuJ,UAAYuJ,UAAUhV,OAAS,IAC/BkC,MAAQ,OACRmB,sBAIJrF,iBAAmBW,KAAKqB,QAAWkC,KAAKqK,SAAS,+CACjDrK,MAAQ,6CAGR6S,oBAAoB/U,OAAS,EAAG,CAChC+U,oBAAoBQ,MAAK,CAACC,EAAGC,IAAMD,EAAEnO,MAAQoO,EAAEpO,cACzCqO,WAAa,4CACbC,UAAYzT,KAAK0J,YAAY8J,gBAChB,IAAfC,UAAkB,KACdC,gBAAkB,iEACtBb,oBAAoBtP,SAAQsF,IACxB6K,iBAAmB7K,EAAEgF,SAEzB6F,iBAAmB,UACnB1T,KAAOA,KAAKsJ,UAAU,EAAGmK,WAAaC,gBAAkB1T,KAAKsJ,UAAUmK,kBAIzEE,oBAAsB/V,KAAKN,cAAcsW,aAC3ChW,KAAKN,cAAcuW,cAAgBjW,KAAKN,cAAcwW,UAAY,OACjExW,cAAc2D,UAAYjB,MAE3B2T,qBAAuB/V,KAAKmW,gCACvBzW,cAAcwW,UAAYlW,KAAKN,cAAcsW,cAK1DG,8BACUC,cAAgBpW,KAAKN,cAAc0D,cAAc,yCAClDgT,qBACM,QAGLC,WAAaD,cAAcE,wBAC3BC,WAAavW,KAAKN,cAAc4W,+BAE/BD,WAAWG,OAASD,WAAWC,OAG1ChB,WAAWiB,eACAA,OACFC,QAAQ,KAAM,SACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,QACdA,QAAQ,KAAM,UACdA,QAAQ,KAAM,UAIvBvF,SAAS9J,YACGA,SACC,cACM,SACN,gBACA,aACA,yBACM,OACN,UACO,kBAEA,CAAC,QAAS,OAAQ,MAAO,YAAa,UAAW,UAAW,aAChE,YAAa,OAAQ,WAAY,MAAO,SAAU,SAAU,SAAU,WACtE,SAAU,OAAQ,MAAO,UAAW,gBAAiB,kBACrD,iBAAkB,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,MACxE,MAAO,MAAO,cAAe,gBAAgBwM,SAASxM,KAAa,GAANA"} \ No newline at end of file diff --git a/amd/build/replay_button.min.js.map b/amd/build/replay_button.min.js.map index b939fdc0..32d88ec2 100644 --- a/amd/build/replay_button.min.js.map +++ b/amd/build/replay_button.min.js.map @@ -1 +1 @@ -{"version":3,"file":"replay_button.min.js","sources":["../src/replay_button.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 that creates a replay button.\n * The button displays replay functionality for a specific user.\n *\n * @module tiny_cursive/replay_button\n * @copyright 2024 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([\"core/str\"], function(Str) {\n const replayButton = (userid, questionid = \"\") => {\n const anchor = document.createElement(\"a\");\n anchor.href = \"#\";\n anchor.id = \"analytics\" + userid + questionid;\n anchor.classList.add(\n \"d-inline-flex\",\n \"justify-content-center\",\n 'text-decoration-none'\n );\n\n const button = document.createElement('div');\n button.className = 'tiny_cursive-replay-button';\n\n // Left side (icon + label)\n const left = document.createElement('div');\n left.className = 'tiny_cursive-replay-left';\n\n const icon = document.createElement('img');\n icon.src = M.util.image_url('playicon', 'tiny_cursive');\n icon.alt = 'Replay Icon';\n icon.className = 'tiny_cursive-replay-bar-icon';\n\n const label = document.createElement('span');\n label.className = 'tiny_cursive-replay-label';\n label.textContent = 'Replay';\n\n Str.get_string(\"replay\", \"tiny_cursive\")\n .then((replayString) => {\n label.textContent = replayString;\n return replayString;\n })\n .catch((error) => {\n window.console.error(\"Error fetching string:\", error);\n });\n\n left.appendChild(icon);\n left.appendChild(label);\n button.appendChild(left);\n\n anchor.appendChild(button);\n return anchor;\n };\n\n return replayButton;\n});"],"names":["define","Str","userid","questionid","anchor","document","createElement","href","id","classList","add","button","className","left","icon","src","M","util","image_url","alt","label","textContent","get_string","then","replayString","catch","error","window","console","appendChild"],"mappings":";;;;;;;;AAwBAA,oCAAO,CAAC,aAAa,SAASC,YACL,SAACC,YAAQC,kEAAa,SACjCC,OAASC,SAASC,cAAc,KACtCF,OAAOG,KAAO,IACdH,OAAOI,GAAK,YAAcN,OAASC,WACnCC,OAAOK,UAAUC,IACf,gBACA,yBACA,8BAGIC,OAASN,SAASC,cAAc,OACtCK,OAAOC,UAAY,mCAGbC,KAAOR,SAASC,cAAc,OACpCO,KAAKD,UAAY,iCAEXE,KAAOT,SAASC,cAAc,OACpCQ,KAAKC,IAAMC,EAAEC,KAAKC,UAAU,WAAY,gBACxCJ,KAAKK,IAAM,cACXL,KAAKF,UAAY,qCAEXQ,MAAQf,SAASC,cAAc,eACrCc,MAAMR,UAAY,4BAClBQ,MAAMC,YAAc,SAEpBpB,IAAIqB,WAAW,SAAU,gBACpBC,MAAMC,eACHJ,MAAMC,YAAcG,aACbA,gBAEVC,OAAOC,QACJC,OAAOC,QAAQF,MAAM,yBAA0BA,UAGvDb,KAAKgB,YAAYf,MACjBD,KAAKgB,YAAYT,OACjBT,OAAOkB,YAAYhB,MAEnBT,OAAOyB,YAAYlB,QACZP"} \ No newline at end of file +{"version":3,"file":"replay_button.min.js","sources":["../src/replay_button.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * Module that creates a replay button.\r\n * The button displays replay functionality for a specific user.\r\n *\r\n * @module tiny_cursive/replay_button\r\n * @copyright 2024 CTI \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\ndefine([\"core/str\"], function(Str) {\r\n const replayButton = (userid, questionid = \"\") => {\r\n const anchor = document.createElement(\"a\");\r\n anchor.href = \"#\";\r\n anchor.id = \"analytics\" + userid + questionid;\r\n anchor.classList.add(\r\n \"d-inline-flex\",\r\n \"justify-content-center\",\r\n 'text-decoration-none'\r\n );\r\n\r\n const button = document.createElement('div');\r\n button.className = 'tiny_cursive-replay-button';\r\n\r\n // Left side (icon + label)\r\n const left = document.createElement('div');\r\n left.className = 'tiny_cursive-replay-left';\r\n\r\n const icon = document.createElement('img');\r\n icon.src = M.util.image_url('playicon', 'tiny_cursive');\r\n icon.alt = 'Replay Icon';\r\n icon.className = 'tiny_cursive-replay-bar-icon';\r\n\r\n const label = document.createElement('span');\r\n label.className = 'tiny_cursive-replay-label';\r\n label.textContent = 'Replay';\r\n\r\n Str.get_string(\"replay\", \"tiny_cursive\")\r\n .then((replayString) => {\r\n label.textContent = replayString;\r\n return replayString;\r\n })\r\n .catch((error) => {\r\n window.console.error(\"Error fetching string:\", error);\r\n });\r\n\r\n left.appendChild(icon);\r\n left.appendChild(label);\r\n button.appendChild(left);\r\n\r\n anchor.appendChild(button);\r\n return anchor;\r\n };\r\n\r\n return replayButton;\r\n});"],"names":["define","Str","userid","questionid","anchor","document","createElement","href","id","classList","add","button","className","left","icon","src","M","util","image_url","alt","label","textContent","get_string","then","replayString","catch","error","window","console","appendChild"],"mappings":";;;;;;;;AAwBAA,oCAAO,CAAC,aAAa,SAASC,YACL,SAACC,YAAQC,kEAAa,SACjCC,OAASC,SAASC,cAAc,KACtCF,OAAOG,KAAO,IACdH,OAAOI,GAAK,YAAcN,OAASC,WACnCC,OAAOK,UAAUC,IACf,gBACA,yBACA,8BAGIC,OAASN,SAASC,cAAc,OACtCK,OAAOC,UAAY,mCAGbC,KAAOR,SAASC,cAAc,OACpCO,KAAKD,UAAY,iCAEXE,KAAOT,SAASC,cAAc,OACpCQ,KAAKC,IAAMC,EAAEC,KAAKC,UAAU,WAAY,gBACxCJ,KAAKK,IAAM,cACXL,KAAKF,UAAY,qCAEXQ,MAAQf,SAASC,cAAc,eACrCc,MAAMR,UAAY,4BAClBQ,MAAMC,YAAc,SAEpBpB,IAAIqB,WAAW,SAAU,gBACpBC,MAAMC,eACHJ,MAAMC,YAAcG,aACbA,gBAEVC,OAAOC,QACJC,OAAOC,QAAQF,MAAM,yBAA0BA,UAGvDb,KAAKgB,YAAYf,MACjBD,KAAKgB,YAAYT,OACjBT,OAAOkB,YAAYhB,MAEnBT,OAAOyB,YAAYlB,QACZP"} \ 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..b7aac630 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * A module that creates a scatter chart to visualize student effort data using Chart.js.\r\n * The chart displays effort scores against time spent, with tooltips showing additional metrics.\r\n *\r\n * @module tiny_cursive/scatter_chart\r\n * @copyright 2025 Cursive Technology, Inc. \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nimport Chart from 'core/chartjs';\r\nimport {get_strings as getStrings} from 'core/str';\r\nexport const init = async(data, apiKey, caption) => {\r\n\r\n const ctx = document.getElementById('effortScatterChart').getContext('2d');\r\n if (data) {\r\n data = JSON.parse(document.getElementById('scatter-chart-data').dataset.data);\r\n }\r\n\r\n let display = true;\r\n let isEmpty = \"\";\r\n var dataset = [];\r\n\r\n const [\r\n applyFilter,\r\n noSubmission,\r\n noPayload,\r\n freemium,\r\n ] = await getStrings([\r\n {key: 'apply_filter', component: 'tiny_cursive'},\r\n {key: 'no_submission', component: 'tiny_cursive'},\r\n {key: 'nopaylod', component: 'tiny_cursive'},\r\n {key: 'freemium', component: 'tiny_cursive'},\r\n {key: 'chart_result', component: 'tiny_cursive'}\r\n ]);\r\n\r\n if (Array.isArray(data) && !data.state && apiKey) {\r\n dataset = data;\r\n isEmpty = data.some(ds =>\r\n Array.isArray(ds.data) &&\r\n ds.data.some(point =>\r\n point && typeof point === 'object' && Object.keys(point).length > 0\r\n )\r\n );\r\n }\r\n\r\n if (!apiKey || data.length === 0 || !isEmpty || data === false) {\r\n display = false;\r\n }\r\n\r\n const fallbackMessagePlugin = {\r\n id: 'fallbackMessagePlugin',\r\n afterDraw(chart) {\r\n // âš  Case 1: Freemium user\r\n if (!apiKey) {\r\n drawMessage('âš  ' + freemium, chart);\r\n return;\r\n }\r\n // âš  Case 2: Apply filter (data is empty array)\r\n if (data.state == \"apply_filter\") {\r\n drawMessage('âš  ' + applyFilter, chart);\r\n return;\r\n }\r\n if (data.state === \"no_submission\") {\r\n drawMessage('âš  ' + noSubmission, chart);\r\n return;\r\n }\r\n // âš  Case 3: No payload data (all `data` arrays are empty or full of empty objects)\r\n if (!isEmpty && !data.state) {\r\n drawMessage('âš  ' + noPayload, chart);\r\n }\r\n\r\n }\r\n };\r\n\r\n new Chart(ctx, {\r\n type: 'scatter',\r\n data: {\r\n datasets: dataset,\r\n },\r\n options: {\r\n plugins: {\r\n title: {\r\n display: display,\r\n text: caption,\r\n font: {\r\n size: 16,\r\n weight: 'bold',\r\n },\r\n color: '#333',\r\n padding: {\r\n top: 10,\r\n bottom: 20\r\n },\r\n align: 'center'\r\n },\r\n legend: {\r\n display: true,\r\n position: 'bottom',\r\n labels: {\r\n usePointStyle: true,\r\n pointStyle: 'circle',\r\n padding: 20\r\n }\r\n },\r\n tooltip: {\r\n backgroundColor: 'rgba(252, 252, 252, 0.8)',\r\n titleColor: '#000',\r\n bodyColor: '#000',\r\n borderColor: '#cccccc',\r\n borderWidth: 1,\r\n displayColors: false,\r\n callbacks: {\r\n title: function(context) {\r\n const d = context[0].raw;\r\n return d.label; // This appears as bold title.\r\n },\r\n label: function(context) {\r\n const d = context.raw;\r\n return [\r\n `Time: ${formatTime(d.x)}`,\r\n `Effort: ${Math.round(d.effort * 100 * 100) / 100}%`,\r\n `Words: ${d.words}`,\r\n `WPM: ${d.wpm}`\r\n ];\r\n }\r\n }\r\n }\r\n },\r\n scales: {\r\n x: {\r\n title: {\r\n display: true,\r\n text: 'Time Spent (mm:ss)'\r\n },\r\n min: 0,\r\n ticks: {\r\n callback: function(value) {\r\n return formatTime(value);\r\n }\r\n }\r\n },\r\n y: {\r\n title: {\r\n display: true,\r\n text: 'Effort Score'\r\n },\r\n min: 0,\r\n ticks: {\r\n stepSize: 0.5\r\n }\r\n }\r\n }\r\n },\r\n plugins: [fallbackMessagePlugin]\r\n });\r\n\r\n /**\r\n * Formats a time value in seconds to a mm:ss string format\r\n * @param {number} value - The time value in seconds\r\n * @returns {string} The formatted time string in mm:ss format\r\n */\r\n function formatTime(value) {\r\n const minutes = Math.floor(value / 60);\r\n const seconds = value % 60;\r\n return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;\r\n }\r\n\r\n /**\r\n * Draws a message on the chart canvas\r\n * @param {string} text - The message to be displayed\r\n * @param {Chart} chart - The Chart.js chart object\r\n */\r\n function drawMessage(text, chart) {\r\n\r\n const {ctx, chartArea: {left, right, top, bottom}} = chart;\r\n ctx.save();\r\n ctx.textAlign = 'center';\r\n ctx.textBaseline = 'middle';\r\n ctx.font = 'bold 16px \"Segoe UI\", Arial';\r\n ctx.fillStyle = '#666';\r\n\r\n const centerX = (left + right) / 2;\r\n const centerY = (top + bottom) / 2;\r\n\r\n ctx.fillText(text, centerX, centerY);\r\n ctx.restore();\r\n }\r\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 diff --git a/amd/build/settings.min.js.map b/amd/build/settings.min.js.map index 603d078e..f60e261e 100644 --- a/amd/build/settings.min.js.map +++ b/amd/build/settings.min.js.map @@ -1 +1 @@ -{"version":3,"file":"settings.min.js","sources":["../src/settings.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/settings\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/str\"], function(\n $,\n str\n) {\n var usersTable = {\n init: function(showcomments, userRole) {\n str\n .get_strings([\n {key: \"tiny_cursive\", component: \"tiny_cursive\"},\n ])\n .done(function() {\n usersTable.getToken(showcomments, userRole);\n });\n },\n getToken: function(showcomments, userRole) {\n $(function() {\n var html = \"
\";\n $(\"body\").append(html);\n $('#body').prop(\"class\", userRole);\n if (showcomments == 1) {\n $('#body').prop(\"class\", 'intervention ' + userRole);\n }\n });\n },\n };\n return usersTable;\n});"],"names":["define","$","str","usersTable","init","showcomments","userRole","get_strings","key","component","done","getToken","append","prop"],"mappings":"AAsBAA,+BAAO,CAAC,SAAU,aAAa,SAC7BC,EACAC,SAEIC,WAAa,CACfC,KAAM,SAASC,aAAcC,UAC3BJ,IACGK,YAAY,CACX,CAACC,IAAK,eAAgBC,UAAW,kBAElCC,MAAK,WACJP,WAAWQ,SAASN,aAAcC,cAGxCK,SAAU,SAASN,aAAcC,UAC/BL,GAAE,WAEAA,EAAE,QAAQW,OADC,gCAEXX,EAAE,SAASY,KAAK,QAASP,UACL,GAAhBD,cACFJ,EAAE,SAASY,KAAK,QAAS,gBAAkBP,sBAK5CH"} \ No newline at end of file +{"version":3,"file":"settings.min.js","sources":["../src/settings.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/settings\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\"jquery\", \"core/str\"], function(\r\n $,\r\n str\r\n) {\r\n var usersTable = {\r\n init: function(showcomments, userRole) {\r\n str\r\n .get_strings([\r\n {key: \"tiny_cursive\", component: \"tiny_cursive\"},\r\n ])\r\n .done(function() {\r\n usersTable.getToken(showcomments, userRole);\r\n });\r\n },\r\n getToken: function(showcomments, userRole) {\r\n $(function() {\r\n var html = \"
\";\r\n $(\"body\").append(html);\r\n $('#body').prop(\"class\", userRole);\r\n if (showcomments == 1) {\r\n $('#body').prop(\"class\", 'intervention ' + userRole);\r\n }\r\n });\r\n },\r\n };\r\n return usersTable;\r\n});"],"names":["define","$","str","usersTable","init","showcomments","userRole","get_strings","key","component","done","getToken","append","prop"],"mappings":"AAsBAA,+BAAO,CAAC,SAAU,aAAa,SAC7BC,EACAC,SAEIC,WAAa,CACfC,KAAM,SAASC,aAAcC,UAC3BJ,IACGK,YAAY,CACX,CAACC,IAAK,eAAgBC,UAAW,kBAElCC,MAAK,WACJP,WAAWQ,SAASN,aAAcC,cAGxCK,SAAU,SAASN,aAAcC,UAC/BL,GAAE,WAEAA,EAAE,QAAQW,OADC,gCAEXX,EAAE,SAASY,KAAK,QAASP,UACL,GAAhBD,cACFJ,EAAE,SAASY,KAAK,QAAS,gBAAkBP,sBAK5CH"} \ No newline at end of file diff --git a/amd/build/show_url_in_quiz_detail.min.js.map b/amd/build/show_url_in_quiz_detail.min.js.map index 03e5b185..b5ed94ac 100644 --- a/amd/build/show_url_in_quiz_detail.min.js.map +++ b/amd/build/show_url_in_quiz_detail.min.js.map @@ -1 +1 @@ -{"version":3,"file":"show_url_in_quiz_detail.min.js","sources":["../src/show_url_in_quiz_detail.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/show_url_in_quiz_detail\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\", \"./analytic_events\",\n \"./replay_button\"], function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n AnalyticEvents,\n replayButton\n) {\n const replayInstances = {};\n // eslint-disable-next-line\n window.video_playback = function (mid, filepath, questionid) {\n if (filepath !== '') {\n const replay = new Replay(\n 'content' + mid,\n filepath,\n 10,\n false,\n 'player_' + mid + questionid\n );\n replayInstances[mid] = replay;\n } else {\n // eslint-disable-next-line\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n }).catch(e => window.console.error(e));\n }\n return false;\n };\n\n var usersTable = {\n\n init: function(scoreSetting, showcomment, hasApiKey) {\n str\n .get_strings([\n {key: \"field_require\", component: \"tiny_cursive\"},\n ])\n .done(function() {\n usersTable.appendSubmissionDetail(scoreSetting, hasApiKey);\n });\n },\n appendSubmissionDetail: function(scoreSetting, hasApiKey) {\n let subUrl = window.location.href;\n let parm = new URL(subUrl);\n let attemptId = parm.searchParams.get('attempt');\n let cmid = M.cfg.contextInstanceId;\n var userid = '';\n var tableRow = $('table.generaltable.generalbox.quizreviewsummary tbody tr');\n tableRow.each(function() {\n var href = $(this).find('a[href*=\"/user/view.php\"]').attr('href');\n if (href) {\n var id = href.match(/id=(\\d+)/);\n if (id) {\n userid = id[1];\n }\n }\n });\n\n $('#page-mod-quiz-review .info').each(function() {\n\n var editQuestionLink = $(this).find('.editquestion a[href*=\"question/bank/editquestion/question.php\"]');\n var questionid = 0;\n if (editQuestionLink.length > 0) {\n editQuestionLink = editQuestionLink.attr('href');\n questionid = editQuestionLink.match(/&id=(\\d+)/)[1];\n }\n\n let args = {id: attemptId, modulename: \"quiz\", \"cmid\": cmid, \"questionid\": questionid, \"userid\": userid};\n let methodname = 'cursive_get_comment_link';\n let com = AJAX.call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n\n if (data.data.filename) {\n\n var content = $('.que.essay .editquestion a[href*=\"question/bank/editquestion/question.php\"][href*=\"&id='\n + data.data.questionid + '\"]');\n if (content.length == 0) {\n content = $('.que.aitext .editquestion a[href*=\"question/bank/editquestion/question.php\"][href*=\"&id='\n + data.data.questionid + '\"]');\n }\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n let analyticButtonDiv = document.createElement('div');\n analyticButtonDiv.classList.add('text-center', 'mt-2');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(userid + questionid));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, userid, questionid));\n }\n\n content.parent().parent().parent().find('.qtext').append(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: userid,\n quizid: questionid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n myEvents.createModal(userid, context, questionid, replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, questionid, replayInstances, authIcon);\n myEvents.checkDiff(userid, data.data.file_id, questionid, replayInstances);\n myEvents.replyWriting(userid, filepath, questionid, replayInstances);\n\n }\n });\n return com.usercomment;\n });\n },\n };\n return usersTable;\n});\n"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","AnalyticEvents","replayButton","replayInstances","window","video_playback","mid","filepath","questionid","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","get_strings","key","component","done","appendSubmissionDetail","subUrl","location","href","attemptId","URL","searchParams","get","cmid","M","cfg","contextInstanceId","userid","each","this","find","attr","id","match","editQuestionLink","length","args","modulename","com","call","methodname","json","data","JSON","parse","filename","content","analyticButtonDiv","document","createElement","classList","add","append","effort_ratio","parent","myEvents","context","tabledata","formattime","formatedTime","page","quizid","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","usercomment"],"mappings":"AAsBAA,8CAAO,CAAC,SAAU,YAAa,WAAY,iBAAkB,WAAY,oBAAqB,oBAC1F,oBAAoB,SACpBC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,eACAC,oBAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAAUC,IAAKC,SAAUC,eAC5B,KAAbD,SAAiB,OACXE,OAAS,IAAIV,OACf,UAAYO,IACZC,SACA,IACA,EACA,UAAYD,IAAME,YAEtBL,gBAAgBG,KAAOG,YAGvBX,UAAUY,OAAO,8BAA8BC,MAAKC,OAChDjB,EAAE,WAAaW,KAAKM,KAAKA,SAC1BC,OAAMC,GAAKV,OAAOW,QAAQC,MAAMF,YAEhC,OAGPG,WAAa,CAEbC,KAAM,SAASC,aAAcC,YAAaC,WACtCxB,IACKyB,YAAY,CACT,CAACC,IAAK,gBAAiBC,UAAW,kBAErCC,MAAK,WACFR,WAAWS,uBAAuBP,aAAcE,eAG5DK,uBAAwB,SAASP,aAAcE,eACvCM,OAASvB,OAAOwB,SAASC,KAEzBC,UADO,IAAIC,IAAIJ,QACEK,aAAaC,IAAI,WAClCC,KAAOC,EAAEC,IAAIC,sBACbC,OAAS,GACE3C,EAAE,4DACR4C,MAAK,eACNV,KAAOlC,EAAE6C,MAAMC,KAAK,6BAA6BC,KAAK,WACtDb,KAAM,KACFc,GAAKd,KAAKe,MAAM,YAChBD,KACAL,OAASK,GAAG,QAKxBhD,EAAE,+BAA+B4C,MAAK,eAE9BM,iBAAmBlD,EAAE6C,MAAMC,KAAK,oEAChCjC,WAAa,EACbqC,iBAAiBC,OAAS,IAC1BD,iBAAmBA,iBAAiBH,KAAK,QACzClC,WAAaqC,iBAAiBD,MAAM,aAAa,QAGjDG,KAAO,CAACJ,GAAIb,UAAWkB,WAAY,YAAgBd,gBAAoB1B,kBAAsB8B,QAE7FW,IAAMrD,KAAKsD,KAAK,CAAC,CAACC,WADL,2BACiBJ,KAAAA,eAClCE,IAAI,GAAGxB,MAAK,SAAS2B,UACbC,KAAOC,KAAKC,MAAMH,SAElBC,KAAKA,KAAKG,SAAU,KAEhBC,QAAU9D,EAAE,0FACV0D,KAAKA,KAAK7C,WAAa,MACP,GAAlBiD,QAAQX,SACRW,QAAU9D,EAAE,2FACV0D,KAAKA,KAAK7C,WAAa,WAEzBD,SAAW,GACX8C,KAAKA,KAAKG,WACVjD,SAAW8C,KAAKA,KAAKG,cAErBE,kBAAoBC,SAASC,cAAc,OAC/CF,kBAAkBG,UAAUC,IAAI,cAAe,QAE1CzC,UAGDqC,kBAAkBK,OAAO/D,eAAeqD,KAAKA,KAAKW,aAAc1B,OAAQ9B,aAFxEb,EAAE+D,mBAAmB9C,KAAKV,aAAaoC,OAAS9B,aAKpDiD,QAAQQ,SAASA,SAASA,SAASxB,KAAK,UAAUsB,OAAOL,uBAErDQ,SAAW,IAAIjE,mBACfkE,QAAU,CACVC,UAAWf,KAAKA,KAChBgB,WAAYH,SAASI,aAAajB,KAAKA,MACvCkB,KAAMpD,aACNmB,OAAQA,OACRkC,OAAQhE,WACRiE,OAAQpD,eAGRqD,SAAWR,SAASS,iBAAiBtB,KAAKA,KAAKuB,WAAYvB,KAAKA,KAAKwB,MAAO1D,cAChF+C,SAASY,YAAYxC,OAAQ6B,QAAS3D,WAAYL,gBAAiBuE,UACnER,SAASa,UAAUzC,OAAQxC,UAAWqE,QAAS3D,WAAYL,gBAAiBuE,UAC5ER,SAASc,UAAU1C,OAAQe,KAAKA,KAAK4B,QAASzE,WAAYL,iBAC1D+D,SAASgB,aAAa5C,OAAQ/B,SAAUC,WAAYL,qBAIrD8C,IAAIkC,wBAIhBlE"} \ No newline at end of file +{"version":3,"file":"show_url_in_quiz_detail.min.js","sources":["../src/show_url_in_quiz_detail.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/show_url_in_quiz_detail\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\", \"./analytic_events\",\r\n \"./replay_button\"], function(\r\n $,\r\n AJAX,\r\n str,\r\n templates,\r\n Replay,\r\n analyticButton,\r\n AnalyticEvents,\r\n replayButton\r\n) {\r\n const replayInstances = {};\r\n // eslint-disable-next-line\r\n window.video_playback = function (mid, filepath, questionid) {\r\n if (filepath !== '') {\r\n const replay = new Replay(\r\n 'content' + mid,\r\n filepath,\r\n 10,\r\n false,\r\n 'player_' + mid + questionid\r\n );\r\n replayInstances[mid] = replay;\r\n } else {\r\n // eslint-disable-next-line\r\n templates.render('tiny_cursive/no_submission').then(html => {\r\n $('#content' + mid).html(html);\r\n }).catch(e => window.console.error(e));\r\n }\r\n return false;\r\n };\r\n\r\n var usersTable = {\r\n\r\n init: function(scoreSetting, showcomment, hasApiKey) {\r\n str\r\n .get_strings([\r\n {key: \"field_require\", component: \"tiny_cursive\"},\r\n ])\r\n .done(function() {\r\n usersTable.appendSubmissionDetail(scoreSetting, hasApiKey);\r\n });\r\n },\r\n appendSubmissionDetail: function(scoreSetting, hasApiKey) {\r\n let subUrl = window.location.href;\r\n let parm = new URL(subUrl);\r\n let attemptId = parm.searchParams.get('attempt');\r\n let cmid = M.cfg.contextInstanceId;\r\n var userid = '';\r\n var tableRow = $('table.generaltable.generalbox.quizreviewsummary tbody tr');\r\n tableRow.each(function() {\r\n var href = $(this).find('a[href*=\"/user/view.php\"]').attr('href');\r\n if (href) {\r\n var id = href.match(/id=(\\d+)/);\r\n if (id) {\r\n userid = id[1];\r\n }\r\n }\r\n });\r\n\r\n $('#page-mod-quiz-review .info').each(function() {\r\n\r\n var editQuestionLink = $(this).find('.editquestion a[href*=\"question/bank/editquestion/question.php\"]');\r\n var questionid = 0;\r\n if (editQuestionLink.length > 0) {\r\n editQuestionLink = editQuestionLink.attr('href');\r\n questionid = editQuestionLink.match(/&id=(\\d+)/)[1];\r\n }\r\n\r\n let args = {id: attemptId, modulename: \"quiz\", \"cmid\": cmid, \"questionid\": questionid, \"userid\": userid};\r\n let methodname = 'cursive_get_comment_link';\r\n let com = AJAX.call([{methodname, args}]);\r\n com[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n\r\n if (data.data.filename) {\r\n\r\n var content = $('.que.essay .editquestion a[href*=\"question/bank/editquestion/question.php\"][href*=\"&id='\r\n + data.data.questionid + '\"]');\r\n if (content.length == 0) {\r\n content = $('.que.aitext .editquestion a[href*=\"question/bank/editquestion/question.php\"][href*=\"&id='\r\n + data.data.questionid + '\"]');\r\n }\r\n var filepath = '';\r\n if (data.data.filename) {\r\n filepath = data.data.filename;\r\n }\r\n let analyticButtonDiv = document.createElement('div');\r\n analyticButtonDiv.classList.add('text-center', 'mt-2');\r\n\r\n if (!hasApiKey) {\r\n $(analyticButtonDiv).html(replayButton(userid + questionid));\r\n } else {\r\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, userid, questionid));\r\n }\r\n\r\n content.parent().parent().parent().find('.qtext').append(analyticButtonDiv);\r\n\r\n let myEvents = new AnalyticEvents();\r\n var context = {\r\n tabledata: data.data,\r\n formattime: myEvents.formatedTime(data.data),\r\n page: scoreSetting,\r\n userid: userid,\r\n quizid: questionid,\r\n apikey: hasApiKey\r\n };\r\n\r\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\r\n myEvents.createModal(userid, context, questionid, replayInstances, authIcon);\r\n myEvents.analytics(userid, templates, context, questionid, replayInstances, authIcon);\r\n myEvents.checkDiff(userid, data.data.file_id, questionid, replayInstances);\r\n myEvents.replyWriting(userid, filepath, questionid, replayInstances);\r\n\r\n }\r\n });\r\n return com.usercomment;\r\n });\r\n },\r\n };\r\n return usersTable;\r\n});\r\n"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","AnalyticEvents","replayButton","replayInstances","window","video_playback","mid","filepath","questionid","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","get_strings","key","component","done","appendSubmissionDetail","subUrl","location","href","attemptId","URL","searchParams","get","cmid","M","cfg","contextInstanceId","userid","each","this","find","attr","id","match","editQuestionLink","length","args","modulename","com","call","methodname","json","data","JSON","parse","filename","content","analyticButtonDiv","document","createElement","classList","add","append","effort_ratio","parent","myEvents","context","tabledata","formattime","formatedTime","page","quizid","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","usercomment"],"mappings":"AAsBAA,8CAAO,CAAC,SAAU,YAAa,WAAY,iBAAkB,WAAY,oBAAqB,oBAC1F,oBAAoB,SACpBC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,eACAC,oBAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAAUC,IAAKC,SAAUC,eAC5B,KAAbD,SAAiB,OACXE,OAAS,IAAIV,OACf,UAAYO,IACZC,SACA,IACA,EACA,UAAYD,IAAME,YAEtBL,gBAAgBG,KAAOG,YAGvBX,UAAUY,OAAO,8BAA8BC,MAAKC,OAChDjB,EAAE,WAAaW,KAAKM,KAAKA,SAC1BC,OAAMC,GAAKV,OAAOW,QAAQC,MAAMF,YAEhC,OAGPG,WAAa,CAEbC,KAAM,SAASC,aAAcC,YAAaC,WACtCxB,IACKyB,YAAY,CACT,CAACC,IAAK,gBAAiBC,UAAW,kBAErCC,MAAK,WACFR,WAAWS,uBAAuBP,aAAcE,eAG5DK,uBAAwB,SAASP,aAAcE,eACvCM,OAASvB,OAAOwB,SAASC,KAEzBC,UADO,IAAIC,IAAIJ,QACEK,aAAaC,IAAI,WAClCC,KAAOC,EAAEC,IAAIC,sBACbC,OAAS,GACE3C,EAAE,4DACR4C,MAAK,eACNV,KAAOlC,EAAE6C,MAAMC,KAAK,6BAA6BC,KAAK,WACtDb,KAAM,KACFc,GAAKd,KAAKe,MAAM,YAChBD,KACAL,OAASK,GAAG,QAKxBhD,EAAE,+BAA+B4C,MAAK,eAE9BM,iBAAmBlD,EAAE6C,MAAMC,KAAK,oEAChCjC,WAAa,EACbqC,iBAAiBC,OAAS,IAC1BD,iBAAmBA,iBAAiBH,KAAK,QACzClC,WAAaqC,iBAAiBD,MAAM,aAAa,QAGjDG,KAAO,CAACJ,GAAIb,UAAWkB,WAAY,YAAgBd,gBAAoB1B,kBAAsB8B,QAE7FW,IAAMrD,KAAKsD,KAAK,CAAC,CAACC,WADL,2BACiBJ,KAAAA,eAClCE,IAAI,GAAGxB,MAAK,SAAS2B,UACbC,KAAOC,KAAKC,MAAMH,SAElBC,KAAKA,KAAKG,SAAU,KAEhBC,QAAU9D,EAAE,0FACV0D,KAAKA,KAAK7C,WAAa,MACP,GAAlBiD,QAAQX,SACRW,QAAU9D,EAAE,2FACV0D,KAAKA,KAAK7C,WAAa,WAEzBD,SAAW,GACX8C,KAAKA,KAAKG,WACVjD,SAAW8C,KAAKA,KAAKG,cAErBE,kBAAoBC,SAASC,cAAc,OAC/CF,kBAAkBG,UAAUC,IAAI,cAAe,QAE1CzC,UAGDqC,kBAAkBK,OAAO/D,eAAeqD,KAAKA,KAAKW,aAAc1B,OAAQ9B,aAFxEb,EAAE+D,mBAAmB9C,KAAKV,aAAaoC,OAAS9B,aAKpDiD,QAAQQ,SAASA,SAASA,SAASxB,KAAK,UAAUsB,OAAOL,uBAErDQ,SAAW,IAAIjE,mBACfkE,QAAU,CACVC,UAAWf,KAAKA,KAChBgB,WAAYH,SAASI,aAAajB,KAAKA,MACvCkB,KAAMpD,aACNmB,OAAQA,OACRkC,OAAQhE,WACRiE,OAAQpD,eAGRqD,SAAWR,SAASS,iBAAiBtB,KAAKA,KAAKuB,WAAYvB,KAAKA,KAAKwB,MAAO1D,cAChF+C,SAASY,YAAYxC,OAAQ6B,QAAS3D,WAAYL,gBAAiBuE,UACnER,SAASa,UAAUzC,OAAQxC,UAAWqE,QAAS3D,WAAYL,gBAAiBuE,UAC5ER,SAASc,UAAU1C,OAAQe,KAAKA,KAAK4B,QAASzE,WAAYL,iBAC1D+D,SAASgB,aAAa5C,OAAQ/B,SAAUC,WAAYL,qBAIrD8C,IAAIkC,wBAIhBlE"} \ No newline at end of file diff --git a/amd/build/show_url_in_submission_grade.min.js.map b/amd/build/show_url_in_submission_grade.min.js.map index 37e64040..d1ba5957 100644 --- a/amd/build/show_url_in_submission_grade.min.js.map +++ b/amd/build/show_url_in_submission_grade.min.js.map @@ -1 +1 @@ -{"version":3,"file":"show_url_in_submission_grade.min.js","sources":["../src/show_url_in_submission_grade.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/show_url_in_submission_grade\n * @category TinyMCE Editor\n * @copyright CTI \n * @author kuldeep singh \n */\n\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\", \"./analytic_events\",\n \"./replay_button\"], function(\n $,\n AJAX,\n str,\n templates,\n Replay,\n analyticButton,\n AnalyticEvents,\n replayButton,\n) {\n const replayInstances = {};\n // eslint-disable-next-line\n window.video_playback = function (mid, filepath) {\n if (filepath !== '') {\n // $(\"#playback\" + mid).show();\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 // eslint-disable-next-line\n templates.render('tiny_cursive/no_submission').then(html => {\n $('#content' + mid).html(html);\n }).catch(e => window.console.error(e));\n }\n return false;\n\n };\n\n var usersTable = {\n init: function(scoreSetting, showcomment, hasApiKey) {\n $(document).ready(function() {\n $('#page-mod-assign-grader').addClass('tiny_cursive_mod_assign_grader');\n });\n str\n .get_strings([\n {key: \"field_require\", component: \"tiny_cursive\"},\n ])\n .done(function() {\n usersTable.appendSubmissionDetail(scoreSetting, showcomment, hasApiKey);\n });\n },\n\n appendSubmissionDetail: function(scoreSetting, showcomment, hasApiKey) {\n $(document).ready(function($) {\n\n var divElement = $('.path-mod-assign [data-region=\"grade-panel\"]')[0];\n var previousContextId = window.location.href;\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function() {\n\n var currentContextId = window.location.href;\n if (currentContextId !== previousContextId) {\n window.location.reload();\n previousContextId = currentContextId;\n }\n });\n });\n // Configuration of the observer:\n var config = {childList: true, subtree: true};\n // Start observing the target node for configured mutations\n observer.observe(divElement, config);\n\n let subUrl = window.location.href;\n let parm = new URL(subUrl);\n let userid = parm.searchParams.get('userid');\n var cmid = parm.searchParams.get('id');\n\n let args = {id: userid, modulename: \"assign\", 'cmid': cmid};\n let methodname = 'cursive_get_assign_grade_comment';\n let com = AJAX.call([{methodname, args}]);\n com[0].done(function(json) {\n var data = JSON.parse(json);\n var filepath = '';\n if (data.data.filename) {\n filepath = data.data.filename;\n }\n\n let analyticButtonDiv = document.createElement('div');\n analyticButtonDiv.classList.add('text-center', 'mt-2');\n\n $('div[data-region=\"grade-actions\"]').before(analyticButtonDiv);\n\n $('div[data-region=\"review-panel\"]').addClass('cursive_review_panel_path_mod_assign');\n\n $('div[data-region=\"grading-navigation-panel\"]').addClass('cursive_grading-navigation-panel_path_mod_assign');\n\n $('div[data-region=\"grade-panel\"]').addClass('cursive_grade-panel_path_mod_assign');\n\n $('div[data-region=\"grade-actions-panel\"]').addClass('cursive_grade-actions-panel_path_mod_assign');\n\n if (!hasApiKey) {\n $(analyticButtonDiv).html(replayButton(userid));\n } else {\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, userid));\n }\n\n let myEvents = new AnalyticEvents();\n var context = {\n tabledata: data.data,\n formattime: myEvents.formatedTime(data.data),\n page: scoreSetting,\n userid: userid,\n apikey: hasApiKey\n };\n\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\n\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\n myEvents.checkDiff(userid, data.data.file_id, '', replayInstances);\n myEvents.replyWriting(userid, filepath, '', replayInstances);\n\n\n });\n return com.usercomment;\n });\n },\n };\n return usersTable;\n});\n\n\n"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","AnalyticEvents","replayButton","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","document","ready","addClass","get_strings","key","component","done","appendSubmissionDetail","divElement","previousContextId","location","href","MutationObserver","mutations","forEach","currentContextId","reload","observe","childList","subtree","subUrl","parm","URL","userid","searchParams","get","cmid","args","id","modulename","com","call","methodname","json","data","JSON","parse","filename","analyticButtonDiv","createElement","classList","add","before","append","effort_ratio","myEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","usercomment"],"mappings":"AAsBAA,mDAAO,CAAC,SAAU,YAAa,WAAY,iBAAkB,WAAY,oBAAqB,oBAC1F,oBAAoB,SACpBC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,eACAC,oBAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAAUC,IAAKC,aAClB,KAAbA,SAAiB,OAEXC,OAAS,IAAIT,OACf,UAAYO,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,YAGvBV,UAAUW,OAAO,8BAA8BC,MAAKC,OAChDhB,EAAE,WAAaW,KAAKK,KAAKA,SAC1BC,OAAMC,GAAKT,OAAOU,QAAQC,MAAMF,YAEhC,OAIPG,WAAa,CACbC,KAAM,SAASC,aAAcC,YAAaC,WACtCzB,EAAE0B,UAAUC,OAAM,WACd3B,EAAE,2BAA2B4B,SAAS,qCAE1C1B,IACK2B,YAAY,CACT,CAACC,IAAK,gBAAiBC,UAAW,kBAErCC,MAAK,WACFX,WAAWY,uBAAuBV,aAAcC,YAAaC,eAIzEQ,uBAAwB,SAASV,aAAcC,YAAaC,WACxDzB,EAAE0B,UAAUC,OAAM,SAAS3B,OAEnBkC,WAAalC,EAAE,gDAAgD,GAC/DmC,kBAAoB1B,OAAO2B,SAASC,KACzB,IAAIC,kBAAiB,SAASC,WACzCA,UAAUC,SAAQ,eAEVC,iBAAmBhC,OAAO2B,SAASC,KACnCI,mBAAqBN,oBACrB1B,OAAO2B,SAASM,SAChBP,kBAAoBM,wBAOvBE,QAAQT,WAFJ,CAACU,WAAW,EAAMC,SAAS,QAIpCC,OAASrC,OAAO2B,SAASC,KACzBU,KAAO,IAAIC,IAAIF,QACfG,OAASF,KAAKG,aAAaC,IAAI,cAC/BC,KAAOL,KAAKG,aAAaC,IAAI,UAE7BE,KAAO,CAACC,GAAIL,OAAQM,WAAY,cAAkBH,MAElDI,IAAMvD,KAAKwD,KAAK,CAAC,CAACC,WADL,mCACiBL,KAAAA,eAClCG,IAAI,GAAGxB,MAAK,SAAS2B,UACbC,KAAOC,KAAKC,MAAMH,MAClB/C,SAAW,GACXgD,KAAKA,KAAKG,WACVnD,SAAWgD,KAAKA,KAAKG,cAGrBC,kBAAoBtC,SAASuC,cAAc,OAC/CD,kBAAkBE,UAAUC,IAAI,cAAe,QAE/CnE,EAAE,oCAAoCoE,OAAOJ,mBAE7ChE,EAAE,mCAAmC4B,SAAS,wCAE9C5B,EAAE,+CAA+C4B,SAAS,oDAE1D5B,EAAE,kCAAkC4B,SAAS,uCAE7C5B,EAAE,0CAA0C4B,SAAS,+CAEhDH,UAGDuC,kBAAkBK,OAAOhE,eAAeuD,KAAKA,KAAKU,aAAcrB,SAFhEjD,EAAEgE,mBAAmBhD,KAAKT,aAAa0C,aAKvCsB,SAAW,IAAIjE,mBACfkE,QAAU,CACVC,UAAWb,KAAKA,KAChBc,WAAYH,SAASI,aAAaf,KAAKA,MACvCgB,KAAMrD,aACN0B,OAAQA,OACR4B,OAAQpD,eAGRqD,SAAWP,SAASQ,iBAAiBnB,KAAKA,KAAKoB,WAAYpB,KAAKA,KAAKqB,MAAO1D,cAEhFgD,SAASW,YAAYjC,OAAQuB,QAAS,GAAIhE,gBAAiBsE,UAC3DP,SAASY,UAAUlC,OAAQ9C,UAAWqE,QAAS,GAAIhE,gBAAiBsE,UACpEP,SAASa,UAAUnC,OAAQW,KAAKA,KAAKyB,QAAS,GAAI7E,iBAClD+D,SAASe,aAAarC,OAAQrC,SAAU,GAAIJ,oBAIzCgD,IAAI+B,wBAIhBlE"} \ No newline at end of file +{"version":3,"file":"show_url_in_submission_grade.min.js","sources":["../src/show_url_in_submission_grade.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/show_url_in_submission_grade\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\"jquery\", \"core/ajax\", \"core/str\", \"core/templates\", \"./replay\", \"./analytic_button\", \"./analytic_events\",\r\n \"./replay_button\"], function(\r\n $,\r\n AJAX,\r\n str,\r\n templates,\r\n Replay,\r\n analyticButton,\r\n AnalyticEvents,\r\n replayButton,\r\n) {\r\n const replayInstances = {};\r\n // eslint-disable-next-line\r\n window.video_playback = function (mid, filepath) {\r\n if (filepath !== '') {\r\n // $(\"#playback\" + mid).show();\r\n const replay = new Replay(\r\n 'content' + mid,\r\n filepath,\r\n 10,\r\n false,\r\n 'player_' + mid\r\n );\r\n replayInstances[mid] = replay;\r\n } else {\r\n // eslint-disable-next-line\r\n templates.render('tiny_cursive/no_submission').then(html => {\r\n $('#content' + mid).html(html);\r\n }).catch(e => window.console.error(e));\r\n }\r\n return false;\r\n\r\n };\r\n\r\n var usersTable = {\r\n init: function(scoreSetting, showcomment, hasApiKey) {\r\n $(document).ready(function() {\r\n $('#page-mod-assign-grader').addClass('tiny_cursive_mod_assign_grader');\r\n });\r\n str\r\n .get_strings([\r\n {key: \"field_require\", component: \"tiny_cursive\"},\r\n ])\r\n .done(function() {\r\n usersTable.appendSubmissionDetail(scoreSetting, showcomment, hasApiKey);\r\n });\r\n },\r\n\r\n appendSubmissionDetail: function(scoreSetting, showcomment, hasApiKey) {\r\n $(document).ready(function($) {\r\n\r\n var divElement = $('.path-mod-assign [data-region=\"grade-panel\"]')[0];\r\n var previousContextId = window.location.href;\r\n var observer = new MutationObserver(function(mutations) {\r\n mutations.forEach(function() {\r\n\r\n var currentContextId = window.location.href;\r\n if (currentContextId !== previousContextId) {\r\n window.location.reload();\r\n previousContextId = currentContextId;\r\n }\r\n });\r\n });\r\n // Configuration of the observer:\r\n var config = {childList: true, subtree: true};\r\n // Start observing the target node for configured mutations\r\n observer.observe(divElement, config);\r\n\r\n let subUrl = window.location.href;\r\n let parm = new URL(subUrl);\r\n let userid = parm.searchParams.get('userid');\r\n var cmid = parm.searchParams.get('id');\r\n\r\n let args = {id: userid, modulename: \"assign\", 'cmid': cmid};\r\n let methodname = 'cursive_get_assign_grade_comment';\r\n let com = AJAX.call([{methodname, args}]);\r\n com[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n var filepath = '';\r\n if (data.data.filename) {\r\n filepath = data.data.filename;\r\n }\r\n\r\n let analyticButtonDiv = document.createElement('div');\r\n analyticButtonDiv.classList.add('text-center', 'mt-2');\r\n\r\n $('div[data-region=\"grade-actions\"]').before(analyticButtonDiv);\r\n\r\n $('div[data-region=\"review-panel\"]').addClass('cursive_review_panel_path_mod_assign');\r\n\r\n $('div[data-region=\"grading-navigation-panel\"]').addClass('cursive_grading-navigation-panel_path_mod_assign');\r\n\r\n $('div[data-region=\"grade-panel\"]').addClass('cursive_grade-panel_path_mod_assign');\r\n\r\n $('div[data-region=\"grade-actions-panel\"]').addClass('cursive_grade-actions-panel_path_mod_assign');\r\n\r\n if (!hasApiKey) {\r\n $(analyticButtonDiv).html(replayButton(userid));\r\n } else {\r\n analyticButtonDiv.append(analyticButton(data.data.effort_ratio, userid));\r\n }\r\n\r\n let myEvents = new AnalyticEvents();\r\n var context = {\r\n tabledata: data.data,\r\n formattime: myEvents.formatedTime(data.data),\r\n page: scoreSetting,\r\n userid: userid,\r\n apikey: hasApiKey\r\n };\r\n\r\n let authIcon = myEvents.authorshipStatus(data.data.first_file, data.data.score, scoreSetting);\r\n\r\n myEvents.createModal(userid, context, '', replayInstances, authIcon);\r\n myEvents.analytics(userid, templates, context, '', replayInstances, authIcon);\r\n myEvents.checkDiff(userid, data.data.file_id, '', replayInstances);\r\n myEvents.replyWriting(userid, filepath, '', replayInstances);\r\n\r\n\r\n });\r\n return com.usercomment;\r\n });\r\n },\r\n };\r\n return usersTable;\r\n});\r\n\r\n\r\n"],"names":["define","$","AJAX","str","templates","Replay","analyticButton","AnalyticEvents","replayButton","replayInstances","window","video_playback","mid","filepath","replay","render","then","html","catch","e","console","error","usersTable","init","scoreSetting","showcomment","hasApiKey","document","ready","addClass","get_strings","key","component","done","appendSubmissionDetail","divElement","previousContextId","location","href","MutationObserver","mutations","forEach","currentContextId","reload","observe","childList","subtree","subUrl","parm","URL","userid","searchParams","get","cmid","args","id","modulename","com","call","methodname","json","data","JSON","parse","filename","analyticButtonDiv","createElement","classList","add","before","append","effort_ratio","myEvents","context","tabledata","formattime","formatedTime","page","apikey","authIcon","authorshipStatus","first_file","score","createModal","analytics","checkDiff","file_id","replyWriting","usercomment"],"mappings":"AAsBAA,mDAAO,CAAC,SAAU,YAAa,WAAY,iBAAkB,WAAY,oBAAqB,oBAC1F,oBAAoB,SACpBC,EACAC,KACAC,IACAC,UACAC,OACAC,eACAC,eACAC,oBAEMC,gBAAkB,GAExBC,OAAOC,eAAiB,SAAUC,IAAKC,aAClB,KAAbA,SAAiB,OAEXC,OAAS,IAAIT,OACf,UAAYO,IACZC,SACA,IACA,EACA,UAAYD,KAEhBH,gBAAgBG,KAAOE,YAGvBV,UAAUW,OAAO,8BAA8BC,MAAKC,OAChDhB,EAAE,WAAaW,KAAKK,KAAKA,SAC1BC,OAAMC,GAAKT,OAAOU,QAAQC,MAAMF,YAEhC,OAIPG,WAAa,CACbC,KAAM,SAASC,aAAcC,YAAaC,WACtCzB,EAAE0B,UAAUC,OAAM,WACd3B,EAAE,2BAA2B4B,SAAS,qCAE1C1B,IACK2B,YAAY,CACT,CAACC,IAAK,gBAAiBC,UAAW,kBAErCC,MAAK,WACFX,WAAWY,uBAAuBV,aAAcC,YAAaC,eAIzEQ,uBAAwB,SAASV,aAAcC,YAAaC,WACxDzB,EAAE0B,UAAUC,OAAM,SAAS3B,OAEnBkC,WAAalC,EAAE,gDAAgD,GAC/DmC,kBAAoB1B,OAAO2B,SAASC,KACzB,IAAIC,kBAAiB,SAASC,WACzCA,UAAUC,SAAQ,eAEVC,iBAAmBhC,OAAO2B,SAASC,KACnCI,mBAAqBN,oBACrB1B,OAAO2B,SAASM,SAChBP,kBAAoBM,wBAOvBE,QAAQT,WAFJ,CAACU,WAAW,EAAMC,SAAS,QAIpCC,OAASrC,OAAO2B,SAASC,KACzBU,KAAO,IAAIC,IAAIF,QACfG,OAASF,KAAKG,aAAaC,IAAI,cAC/BC,KAAOL,KAAKG,aAAaC,IAAI,UAE7BE,KAAO,CAACC,GAAIL,OAAQM,WAAY,cAAkBH,MAElDI,IAAMvD,KAAKwD,KAAK,CAAC,CAACC,WADL,mCACiBL,KAAAA,eAClCG,IAAI,GAAGxB,MAAK,SAAS2B,UACbC,KAAOC,KAAKC,MAAMH,MAClB/C,SAAW,GACXgD,KAAKA,KAAKG,WACVnD,SAAWgD,KAAKA,KAAKG,cAGrBC,kBAAoBtC,SAASuC,cAAc,OAC/CD,kBAAkBE,UAAUC,IAAI,cAAe,QAE/CnE,EAAE,oCAAoCoE,OAAOJ,mBAE7ChE,EAAE,mCAAmC4B,SAAS,wCAE9C5B,EAAE,+CAA+C4B,SAAS,oDAE1D5B,EAAE,kCAAkC4B,SAAS,uCAE7C5B,EAAE,0CAA0C4B,SAAS,+CAEhDH,UAGDuC,kBAAkBK,OAAOhE,eAAeuD,KAAKA,KAAKU,aAAcrB,SAFhEjD,EAAEgE,mBAAmBhD,KAAKT,aAAa0C,aAKvCsB,SAAW,IAAIjE,mBACfkE,QAAU,CACVC,UAAWb,KAAKA,KAChBc,WAAYH,SAASI,aAAaf,KAAKA,MACvCgB,KAAMrD,aACN0B,OAAQA,OACR4B,OAAQpD,eAGRqD,SAAWP,SAASQ,iBAAiBnB,KAAKA,KAAKoB,WAAYpB,KAAKA,KAAKqB,MAAO1D,cAEhFgD,SAASW,YAAYjC,OAAQuB,QAAS,GAAIhE,gBAAiBsE,UAC3DP,SAASY,UAAUlC,OAAQ9C,UAAWqE,QAAS,GAAIhE,gBAAiBsE,UACpEP,SAASa,UAAUnC,OAAQW,KAAKA,KAAKyB,QAAS,GAAI7E,iBAClD+D,SAASe,aAAarC,OAAQrC,SAAU,GAAIJ,oBAIzCgD,IAAI+B,wBAIhBlE"} \ 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..12c5e5b2 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * SVG repository for icons used in the Curs\r\n *\r\n * @module tiny_cursive/svg_repo\r\n * @copyright 2025 Cursive Technology, Inc. \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nexport default {\r\n people: `\r\n \r\n \r\n \r\n \r\n `,\r\n assignment: `\r\n \r\n \r\n \r\n `,\r\n time: `\r\n \r\n \r\n `,\r\n offline: ``,\r\n forum: ``,\r\n close: `\r\n \r\n `,\r\n hamburger: `\r\n \r\n `,\r\n quiz: ``,\r\n cloudSave: ``,\r\n lesson: ``,\r\n pdfannotator: `\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n `\r\n};\r\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.map b/amd/build/texteditor.min.js.map index 053cad8b..b688dfac 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * Tiny Loader for Moodle\r\n *\r\n * @module tiny_cursive/texteditor\r\n * @copyright 2022 CTI \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\nlet tinyMCEPromise;\r\n\r\nimport * as Config from 'core/config';\r\n\r\nexport const baseUrl = `${Config.wwwroot}/lib/editor/tiny/loader.php/${M.cfg.jsrev}`;\r\n\r\n/**\r\n * Get the TinyMCE API Object.\r\n *\r\n * @returns {Promise} The TinyMCE API Object\r\n */\r\nexport const getTinyMCE = () => {\r\n if (tinyMCEPromise) {\r\n return tinyMCEPromise;\r\n }\r\n\r\n tinyMCEPromise = new Promise((resolve, reject) => {\r\n const head = document.querySelector('head');\r\n let script = head.querySelector('script[data-tinymce=\"tinymce\"]');\r\n if (script) {\r\n resolve(window.tinyMCE);\r\n }\r\n\r\n script = document.createElement('script');\r\n script.dataset.tinymce = 'tinymce';\r\n script.src = `${baseUrl}/tinymce.js`;\r\n script.async = true;\r\n\r\n script.addEventListener('load', () => {\r\n resolve(window.tinyMCE);\r\n }, false);\r\n\r\n script.addEventListener('error', (err) => {\r\n reject(err);\r\n }, false);\r\n\r\n head.append(script);\r\n });\r\n\r\n return tinyMCEPromise;\r\n\r\n};\r\n\r\n\r\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.map b/amd/build/token_approve.min.js.map index 44d98c9a..a6b42c7b 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/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * @module tiny_cursive/token_approve\r\n * @category TinyMCE Editor\r\n * @copyright CTI \r\n * @author kuldeep singh \r\n */\r\n\r\ndefine([\"jquery\", \"core/ajax\", \"core/str\"], function($, AJAX, str) {\r\n var usersTable = {\r\n init: function(page) {\r\n str\r\n .get_strings([{key: \"field_require\", component: \"tiny_cursive\"}])\r\n .done(function() {\r\n usersTable.getToken(page);\r\n usersTable.generateToken();\r\n });\r\n },\r\n getToken: function() {\r\n $(\"#approve_token\").click(function() {\r\n var token = $(\"#id_s_tiny_cursive_secretkey\").val();\r\n var promise1 = AJAX.call([\r\n {\r\n methodname: \"cursive_approve_token\",\r\n args: {\r\n token: token,\r\n },\r\n },\r\n ]);\r\n promise1[0].done(function(json) {\r\n var data = JSON.parse(json);\r\n var messageAlert = \"\";\r\n if (data.status == true) {\r\n messageAlert =\r\n \"\" +\r\n data.message +\r\n \"\";\r\n } else {\r\n messageAlert =\r\n \"\" +\r\n data.message +\r\n \"\";\r\n }\r\n $(\"#token_message\").html(messageAlert);\r\n });\r\n });\r\n },\r\n\r\n generateToken() {\r\n const generateToken = $(\"#generate_cursivetoken\");\r\n const cursiveDisable = $(\"#cursivedisable\");\r\n const cursiveEnable = $(\"#cursiveenable\");\r\n\r\n generateToken.on(\"click\", function(e) {\r\n e.preventDefault();\r\n var promise1 = AJAX.call([\r\n {\r\n methodname: \"cursive_generate_webtoken\",\r\n args: [],\r\n },\r\n ]);\r\n promise1[0].done(function(data) {\r\n var messageAlert = \"\";\r\n str.get_strings([\r\n {key: \"webservtokengensucc\", component: \"tiny_cursive\"},\r\n {key: \"webservtokengenfail\", component: \"tiny_cursive\"}\r\n ]).then(function([success, fail]) {\r\n\r\n if (data.token) {\r\n $(\"#id_s_tiny_cursive_cursivetoken\").val(data.token);\r\n messageAlert = `${success}`;\r\n } else {\r\n messageAlert = `${fail}`;\r\n }\r\n $(\"#cursivetoken_\").html(messageAlert);\r\n setTimeout(() => {\r\n $(\"#cursivetoken_\").empty();\r\n }, 3000);\r\n return true;\r\n }).catch(error => window.console.error(error));\r\n });\r\n promise1[0].fail(function(textStatus) {\r\n var errorMessage = \"\";\r\n str\r\n .get_string(\"webservtokenerror\", \"tiny_cursive\")\r\n .then((str) => {\r\n errorMessage += str + \" \" + textStatus.error + \"\";\r\n\r\n $(\"#cursivetoken_\").html(errorMessage);\r\n // Clear the error message after 3 seconds.\r\n setTimeout(function() {\r\n $(\"#cursivetoken_\").empty();\r\n }, 3000);\r\n return true;\r\n }).catch(error => window.console.error(error));\r\n });\r\n });\r\n\r\n cursiveDisable.on(\"click\", function(e) {\r\n e.preventDefault();\r\n\r\n var promise1 = AJAX.call([\r\n {\r\n methodname: \"cursive_disable_all_course\",\r\n args: {\r\n disable: true,\r\n },\r\n },\r\n ]);\r\n promise1[0].done(function(data) {\r\n var messageAlert = \"\";\r\n str.get_strings([\r\n {key: \"cursive:dis:succ\", component: \"tiny_cursive\"},\r\n {key: \"cursive:dis:fail\", component: \"tiny_cursive\"}\r\n ]).then(function([success, fail]) {\r\n if (data) {\r\n messageAlert = `${success}`;\r\n } else {\r\n messageAlert = `${fail}`;\r\n }\r\n\r\n $(\"#cursivedisable_\").html(messageAlert);\r\n setTimeout(() => {\r\n $(\"#cursivedisable_\").empty();\r\n }, 3000);\r\n return true;\r\n }).catch(error => window.console.error(error));\r\n });\r\n promise1[0].fail(function(textStatus) {\r\n var errorMessage = \"\";\r\n str\r\n .get_string(\"cursive:status\", \"tiny_cursive\")\r\n .then((str) => {\r\n errorMessage += str + \" \" + textStatus.error + \"\";\r\n\r\n $(\"#cursivedisable_\").html(errorMessage);\r\n // Clear the error message after 3 seconds.\r\n setTimeout(function() {\r\n $(\"#cursivedisable_\").empty();\r\n }, 3000);\r\n return true;\r\n }).catch(error => window.console.error(error));\r\n });\r\n });\r\n cursiveEnable.on(\"click\", function(e) {\r\n e.preventDefault();\r\n\r\n var promise1 = AJAX.call([\r\n {\r\n methodname: \"cursive_disable_all_course\",\r\n args: {\r\n disable: false,\r\n },\r\n },\r\n ]);\r\n promise1[0].done(function(data) {\r\n var messageAlert = \"\";\r\n str.get_strings([\r\n {key: \"cursive:ena:succ\", component: \"tiny_cursive\"},\r\n {key: \"cursive:ena:fail\", component: \"tiny_cursive\"}\r\n ]).then(function([success, fail]) {\r\n if (data) {\r\n messageAlert = `${success}`;\r\n } else {\r\n messageAlert = `${fail}`;\r\n }\r\n\r\n $(\"#cursivedisable_\").html(messageAlert);\r\n setTimeout(() => {\r\n $(\"#cursivedisable_\").empty();\r\n }, 3000);\r\n return true;\r\n }).catch(error => window.console.error(error));\r\n });\r\n promise1[0].fail(function(textStatus) {\r\n var errorMessage = \"\";\r\n str.get_string(\"cursive:status\", \"tiny_cursive\")\r\n .then((str) => {\r\n errorMessage += str + \" \" + textStatus.error + \"\";\r\n\r\n $(\"#cursivedisable_\").html(errorMessage);\r\n // Clear the error message after 3 seconds.\r\n setTimeout(function() {\r\n $(\"#cursivedisable_\").empty();\r\n }, 3000);\r\n return true;\r\n }).catch(error => window.console.error(error));\r\n });\r\n });\r\n },\r\n };\r\n return usersTable;\r\n});\r\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/build/user_report_addition.min.js.map b/amd/build/user_report_addition.min.js.map index 35237a60..789102ff 100644 --- a/amd/build/user_report_addition.min.js.map +++ b/amd/build/user_report_addition.min.js.map @@ -1 +1 @@ -{"version":3,"file":"user_report_addition.min.js","sources":["../src/user_report_addition.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 handle user report additions in the Tiny editor.\n * This module manages the display of course information in the report interface.\n *\n * @module tiny_cursive/user_report_addition\n * @copyright 2024 CTI \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {get_string as getString} from 'core/str';\nexport const init = () => {\n const courseNameElement = document.querySelector('#id_courseid option[selected]');\n\n if (!courseNameElement) {\n window.history.back();\n }\n getString('coursename', 'tiny_cursive').then(courseName => {\n const forumTestingText = courseNameElement.textContent.trim();\n const h5Element = document.createElement('div');\n\n h5Element.classList.add('row', 'align-items-center', 'pb-4');\n const label = document.createElement('label');\n\n label.textContent = courseName;\n label.classList.add('col-md-3', 'col-form-label', 'd-flex', 'pb-0', 'pr-md-0');\n h5Element.appendChild(label);\n\n const label2 = document.createElement('label');\n label2.textContent = forumTestingText;\n label2.classList.add('col-md-9', 'col-form-label', 'd-flex', 'pb-0', 'pr-md-0');\n h5Element.appendChild(label2);\n\n const moduleIdElement = document.getElementById('fitem_id_moduleid');\n const parentElement = moduleIdElement.parentElement;\n parentElement.insertBefore(h5Element, moduleIdElement);\n document.getElementById('fitem_id_courseid').style.display = 'none';\n return h5Element;\n }).catch(error =>{\n window.console.log(error);\n });\n\n};"],"names":["courseNameElement","document","querySelector","window","history","back","then","courseName","forumTestingText","textContent","trim","h5Element","createElement","classList","add","label","appendChild","label2","moduleIdElement","getElementById","parentElement","insertBefore","style","display","catch","error","console","log"],"mappings":"sLAwBoB,WACVA,kBAAoBC,SAASC,cAAc,iCAE5CF,mBACDG,OAAOC,QAAQC,2BAET,aAAc,gBAAgBC,MAAKC,mBACnCC,iBAAmBR,kBAAkBS,YAAYC,OACjDC,UAAYV,SAASW,cAAc,OAEzCD,UAAUE,UAAUC,IAAI,MAAO,qBAAsB,cAC/CC,MAAQd,SAASW,cAAc,SAErCG,MAAMN,YAAcF,WACpBQ,MAAMF,UAAUC,IAAI,WAAY,iBAAkB,SAAU,OAAQ,WACpEH,UAAUK,YAAYD,aAEhBE,OAAShB,SAASW,cAAc,SACtCK,OAAOR,YAAcD,iBACrBS,OAAOJ,UAAUC,IAAI,WAAY,iBAAkB,SAAU,OAAQ,WACrEH,UAAUK,YAAYC,cAEhBC,gBAAkBjB,SAASkB,eAAe,4BAC1BD,gBAAgBE,cACxBC,aAAaV,UAAWO,iBACtCjB,SAASkB,eAAe,qBAAqBG,MAAMC,QAAU,OACtDZ,aACRa,OAAMC,QACLtB,OAAOuB,QAAQC,IAAIF"} \ No newline at end of file +{"version":3,"file":"user_report_addition.min.js","sources":["../src/user_report_addition.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/**\r\n * Module to handle user report additions in the Tiny editor.\r\n * This module manages the display of course information in the report interface.\r\n *\r\n * @module tiny_cursive/user_report_addition\r\n * @copyright 2024 CTI \r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\nimport {get_string as getString} from 'core/str';\r\nexport const init = () => {\r\n const courseNameElement = document.querySelector('#id_courseid option[selected]');\r\n\r\n if (!courseNameElement) {\r\n window.history.back();\r\n }\r\n getString('coursename', 'tiny_cursive').then(courseName => {\r\n const forumTestingText = courseNameElement.textContent.trim();\r\n const h5Element = document.createElement('div');\r\n\r\n h5Element.classList.add('row', 'align-items-center', 'pb-4');\r\n const label = document.createElement('label');\r\n\r\n label.textContent = courseName;\r\n label.classList.add('col-md-3', 'col-form-label', 'd-flex', 'pb-0', 'pr-md-0');\r\n h5Element.appendChild(label);\r\n\r\n const label2 = document.createElement('label');\r\n label2.textContent = forumTestingText;\r\n label2.classList.add('col-md-9', 'col-form-label', 'd-flex', 'pb-0', 'pr-md-0');\r\n h5Element.appendChild(label2);\r\n\r\n const moduleIdElement = document.getElementById('fitem_id_moduleid');\r\n const parentElement = moduleIdElement.parentElement;\r\n parentElement.insertBefore(h5Element, moduleIdElement);\r\n document.getElementById('fitem_id_courseid').style.display = 'none';\r\n return h5Element;\r\n }).catch(error =>{\r\n window.console.log(error);\r\n });\r\n\r\n};"],"names":["courseNameElement","document","querySelector","window","history","back","then","courseName","forumTestingText","textContent","trim","h5Element","createElement","classList","add","label","appendChild","label2","moduleIdElement","getElementById","parentElement","insertBefore","style","display","catch","error","console","log"],"mappings":"sLAwBoB,WACVA,kBAAoBC,SAASC,cAAc,iCAE5CF,mBACDG,OAAOC,QAAQC,2BAET,aAAc,gBAAgBC,MAAKC,mBACnCC,iBAAmBR,kBAAkBS,YAAYC,OACjDC,UAAYV,SAASW,cAAc,OAEzCD,UAAUE,UAAUC,IAAI,MAAO,qBAAsB,cAC/CC,MAAQd,SAASW,cAAc,SAErCG,MAAMN,YAAcF,WACpBQ,MAAMF,UAAUC,IAAI,WAAY,iBAAkB,SAAU,OAAQ,WACpEH,UAAUK,YAAYD,aAEhBE,OAAShB,SAASW,cAAc,SACtCK,OAAOR,YAAcD,iBACrBS,OAAOJ,UAAUC,IAAI,WAAY,iBAAkB,SAAU,OAAQ,WACrEH,UAAUK,YAAYC,cAEhBC,gBAAkBjB,SAASkB,eAAe,4BAC1BD,gBAAgBE,cACxBC,aAAaV,UAAWO,iBACtCjB,SAASkB,eAAe,qBAAqBG,MAAMC,QAAU,OACtDZ,aACRa,OAAMC,QACLtB,OAAOuB,QAAQC,IAAIF"} \ No newline at end of file diff --git a/amd/src/document_view.js b/amd/src/document_view.js index 14eeb59a..d25fcee6 100644 --- a/amd/src/document_view.js +++ b/amd/src/document_view.js @@ -261,7 +261,7 @@ export default class DocumentView { ); } - if (Dates) { + if (Dates && openDate) { content.append( this.createBox({ bg: 'bg-amber', @@ -532,45 +532,72 @@ export default class DocumentView { let openDate = null; let dueDate = null; - const openedWrapper = this.create('div'); - const dueWrapper = this.create('div'); - const remainingWrapper = this.create('div'); - - const openedLabel = this.create('span'); - const openedValue = this.create('span'); - const dueLabel = this.create('span'); - const dueValue = this.create('span'); - const remainingLabel = this.create('span'); - const remainingValue = this.create('span'); if (this.module === 'quiz') { - openDate = open * 1000; - dueDate = due * 1000; + // For quiz, open/due are timestamps - only set if non-zero + openDate = open ? open * 1000 : null; + dueDate = due ? due * 1000 : null; } else { - openDate = this.extractDate(open?.textContent); - dueDate = this.extractDate(due?.textContent); + // For other modules, check the text content to determine which is which + // Moodle only renders divs for dates that are set, so we need to check labels + const openText = open?.textContent?.toLowerCase() || ''; + const dueText = due?.textContent?.toLowerCase() || ''; + + // Check if 'open' div actually contains an open date or a due date + if (openText.includes('opened') || openText.includes('open')) { + openDate = this.extractDate(open?.textContent); + } else if (openText.includes('due') || openText.includes('close')) { + // First div is actually the due date (no open date was set) + dueDate = this.extractDate(open?.textContent); + } + + // Check 'due' div if it exists + if (due && (dueText.includes('due') || dueText.includes('close'))) { + dueDate = this.extractDate(due?.textContent); + } + } + + // Only show open date if it exists + if (openDate) { + const openedWrapper = this.create('div'); + const openedLabel = this.create('span'); + const openedValue = this.create('span'); + + openedLabel.textContent = `${this.opened}: `; + openedValue.textContent = this.formatDate(new Date(openDate)); + openedValue.className = 'text-dark'; + + openedWrapper.className = 'd-flex justify-content-between'; + openedWrapper.append(openedLabel, openedValue); + wrapper.append(openedWrapper); } - openedLabel.textContent = `${this.opened}: `; - openedValue.textContent = this.formatDate(openDate ? new Date(openDate) : null); - openedValue.className = 'text-dark'; + // Only show due date and remaining time if due date exists + if (dueDate) { + const dueWrapper = this.create('div'); + const dueLabel = this.create('span'); + const dueValue = this.create('span'); - dueLabel.textContent = `${this.due}: `; - dueValue.textContent = this.formatDate(dueDate ? new Date(dueDate) : null); - dueValue.className = 'text-danger'; + dueLabel.textContent = `${this.due}: `; + dueValue.textContent = this.formatDate(new Date(dueDate)); + dueValue.className = 'text-danger'; - remainingLabel.textContent = `${this.remaining}: `; - remainingValue.textContent = this.calculateDate(dueDate); - remainingValue.className = 'text-danger'; + dueWrapper.className = 'd-flex justify-content-between'; + dueWrapper.append(dueLabel, dueValue); + wrapper.append(dueWrapper); - 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'; + // Remaining time - only show if due date exists + const remainingWrapper = this.create('div'); + const remainingLabel = this.create('span'); + const remainingValue = this.create('span'); - openedWrapper.append(openedLabel, openedValue); - dueWrapper.append(dueLabel, dueValue); - remainingWrapper.append(remainingLabel, remainingValue); + remainingLabel.textContent = `${this.remaining}: `; + remainingValue.textContent = this.calculateDate(dueDate); + remainingValue.className = 'text-danger'; - wrapper.append(openedWrapper, dueWrapper, remainingWrapper); + remainingWrapper.className = 'd-flex align-items-center justify-content-between mt-2 pt-2 border-top'; + remainingWrapper.append(remainingLabel, remainingValue); + wrapper.append(remainingWrapper); + } return wrapper.innerHTML; } @@ -586,15 +613,17 @@ export default class DocumentView { extractDate(text) { if (!text) { - return '-'; + return null; } // Split on first colon and return the right part const parts = text?.split(':'); if (parts.length > 1) { - return parts.slice(1).join(':').trim(); + const dateStr = parts.slice(1).join(':').trim(); + // Return null if empty or invalid + return dateStr || null; } - return text.trim(); + return text.trim() || null; }