From 9b31a036eabbc3df4a78139bca0566c67c52b807 Mon Sep 17 00:00:00 2001 From: Simeon Naydenov Date: Wed, 4 Sep 2024 11:13:36 +0300 Subject: [PATCH 1/2] fix some get_changes problems --- .../amd/build/collaborater.min.js | 2 +- .../amd/build/collaborater.min.js.map | 2 +- .../collaborative/amd/src/collaborater.js | 46 ++++++++++++++++--- .../collaborative/classes/change_manager.php | 4 +- .../classes/external/get_changes.php | 2 +- 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js b/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js index ab00506d81e9d..1edd07e977ad7 100644 --- a/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js +++ b/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js @@ -5,6 +5,6 @@ define("tiny_collaborative/collaborater",["exports","./jsdiff/index","core/ajax" * @module tiny_autosave/autosaver * @copyright 2022 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,jsDiff=_interopRequireWildcard(jsDiff),Options=_interopRequireWildcard(Options);let currentContent="",currentHash="",newHash="";const HEADER="changes Index: a\n===================================================================\n",fetchOne=(methodname,args)=>(0,_ajax.call)([{methodname:methodname,args:args}])[0];async function sha1(message){const data=(new TextEncoder).encode(message),hashBuffer=await crypto.subtle.digest("SHA-1",data);return Array.from(new Uint8Array(hashBuffer)).map((byte=>byte.toString(16).padStart(2,"0"))).join("")}_exports.register=editor=>{const c=window.console;editor.on("init",(()=>{setInterval((()=>{const newContent=editor.getContent();sha1(newContent).then((hash=>{if(newHash=hash,""===currentHash&&(currentHash=newHash),newHash!==currentHash){let patch=jsDiff.createPatch("a",currentContent,newContent);return patch=patch.substring(85),fetchOne("tiny_collaborate_save_changes",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id,oldcontenthash:currentHash,newcontenthash:newHash,changes:patch}).then((result=>(currentContent=newContent,currentHash=newHash,result)))}}));let newContent2=editor.getContent();fetchOne("tiny_collaborate_get_changes",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id,currenthash:currentHash}).then((result=>{if(result){for(let i in result){let change=result[i],patch=HEADER+change;c.log("changes",patch),newContent2=jsDiff.applyPatch(newContent2,patch)}editor.setContent(newContent2),sha1(newContent2).then((hash=>{currentHash=hash}))}}))}),1e3)}))}})); + */}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,jsDiff=_interopRequireWildcard(jsDiff),Options=_interopRequireWildcard(Options);let currentContent="",currentHash="",newHash="";const HEADER="Index: a\n===================================================================\n",fetchOne=(methodname,args)=>(0,_ajax.call)([{methodname:methodname,args:args}])[0];_exports.register=editor=>{const c=window.console;editor.on("init",(()=>{setInterval((()=>{const newContent=editor.getContent();if(async function(message){const data=(new TextEncoder).encode(message),hashBuffer=await crypto.subtle.digest("SHA-1",data);return Array.from(new Uint8Array(hashBuffer)).map((byte=>byte.toString(16).padStart(2,"0"))).join("")}(newContent).then((hash=>{if(newHash=hash,""!==currentHash){if(newHash!==currentHash){let patch=jsDiff.createPatch("a",currentContent,newContent);return patch=patch.substring(77),fetchOne("tiny_collaborate_save_changes",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),elementid:editor.targetElm.id,oldcontenthash:currentHash,newcontenthash:newHash,changes:patch}).then((result=>(currentContent=newContent,currentHash=newHash,result)))}}else currentHash=newHash})),""===currentHash)return;let newContent2=editor.getContent();fetchOne("tiny_collaborate_get_changes",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),elementid:editor.targetElm.id,currenthash:currentHash}).then((result=>{if(result){for(let i in result){let change=result[i];c.log("shorthcange",change);let patch=HEADER+change;c.log("changes",patch),newContent2=jsDiff.applyPatch(newContent2,patch)}c.log("newContent2",newContent2),editor.setContent(newContent2),clearInterval(null)}}))}),1e3)}))}})); //# sourceMappingURL=collaborater.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js.map b/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js.map index 4b997fe4a622e..7de6b0a0b45da 100644 --- a/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js.map +++ b/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js.map @@ -1 +1 @@ -{"version":3,"file":"collaborater.min.js","sources":["../src/collaborater.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 * Storage helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/autosaver\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n//import * as Options from './options';\n//import * as Storage from './storage';mm\n//import Log from 'core/log';\n//import {eventTypes} from 'core_form/events';\n//import {getLogSource} from './common';\nimport * as jsDiff from './jsdiff/index';\nimport {call} from 'core/ajax';\nimport * as Options from \"./options\";\n\n\nlet currentContent = '';\nlet currentHash = '';\nlet newHash = '';\n//let newContent = '';\n//let lastHash = '';\nconst INTERVALTIMEOUT = 1000;\n\nconst fetchOne = (methodname, args) => call([{\n methodname,\n args,\n}])[0];\n\nasync function sha1(message) {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-1', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');\n return hashHex;\n}\n\n// Example usage:\n\n\n\nexport const register = (editor) => {\n // Attempt to store the draft one final time before the page unloads.\n // Note: This may need to be sent as a beacon instead.\n // document.addEventListener('visibilitychange', visibilityChangedHandler);\n\n // When the page is submitted as a form, remove the draft.\n // editor.on('submit', removeAutoSaveSession);\n // document.addEventListener(eventTypes.formSubmittedByJavascript, handleFormSubmittedByJavascript);\n const c = window.console;\n editor.on('init', () => {\n setInterval(() => {\n const newContent = editor.getContent();\n sha1(newContent).then(hash => {\n newHash = hash;\n if (currentHash === '') {\n currentHash = newHash;\n }\n if (newHash !== currentHash) {\n const patch = jsDiff.createPatch('a', currentContent, newContent);\n\n c.log('contextid', Options.getContextId(editor));\n c.log('pagehash', Options.getPageHash(editor));\n c.log('pageinstance', Options.getPageInstance(editor));\n c.log('elementid', editor.targetElm.id);\n c.log('oldcontenthash', currentHash);\n c.log('newcontenthash', newHash);\n c.log('changes', patch);\n return fetchOne('tiny_collaborate_save_changes', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n /*drftid: Options.getDraftItemId(editor),*/\n oldcontenthash: currentHash,\n newcontenthash: newHash,\n changes: patch,\n\n })\n .then((result) => {\n //* pendingPromise.resolve();\n // c.log('new hash', newHash);\n // c.log('diff', patch);\n currentContent = newContent;\n currentHash = newHash;\n return result;\n });\n }\n });\n }, INTERVALTIMEOUT);\n /*editor.on('Change', (event) => {\n c.log('Change collaborative', event);\n });*/\n // Setup the Undo handler.\n //editor.on('AddUndo', undoHandler);\n\n /* if (editor.dom.isEmpty(editor.getBody())) {\n Log.info(`Attempting to restore draft`, getLogSource(editor));\n Storage.restoreDraft(editor);\n } else {\n // There was nothing to restore, so we can mark the editor as initialised.\n Log.warn(`Skipping draft restoration. The editor is not empty.`, getLogSource(editor));\n Options.markInitialised(editor);\n }*/\n });\n};\n"],"names":["_getRequireWildcardCache","e","WeakMap","r","t","_interopRequireWildcard","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","jsDiff","Options","currentContent","currentHash","newHash","_exports","register","editor","c","window","console","on","setInterval","newContent","getContent","async","message","data","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","byte","toString","padStart","join","sha1","then","hash","patch","createPatch","log","getContextId","getPageHash","getPageInstance","targetElm","id","methodname","args","contextid","pagehash","pageinstance","elementid","oldcontenthash","newcontenthash","changes","result","fetchOne"],"mappings":"uIA8BqC,SAAAA,yBAAAC,GAAA,GAAA,mBAAAC,QAAA,OAAA,KAAA,IAAAC,EAAAD,IAAAA,QAAAE,EAAAF,IAAAA,eAAAF,yBAAA,SAAAC,GAAAA,OAAAA,EAAAG,EAAAD,IAAAF,EAAA,CAAA,SAAAI,wBAAAJ,EAAAE,GAAAA,IAAAA,GAAAF,GAAAA,EAAAK,WAAAL,OAAAA,EAAAA,GAAAA,OAAAA,GAAAA,iBAAAA,GAAAA,mBAAAA,EAAAM,MAAAA,CAAAA,QAAAN,GAAAG,IAAAA,EAAAJ,yBAAAG,GAAA,GAAAC,GAAAA,EAAAI,IAAAP,GAAA,OAAAG,EAAAK,IAAAR,GAAA,IAAAS,EAAA,CAAAC,UAAA,MAAAC,EAAAC,OAAAC,gBAAAD,OAAAE,yBAAA,IAAA,IAAAC,KAAAf,EAAAe,GAAAA,YAAAA,GAAAC,CAAAA,EAAAA,eAAAC,KAAAjB,EAAAe,GAAAG,CAAAA,IAAAA,EAAAP,EAAAC,OAAAE,yBAAAd,EAAAe,GAAAG,KAAAA,IAAAA,EAAAV,KAAAU,EAAAC,KAAAP,OAAAC,eAAAJ,EAAAM,EAAAG,GAAAT,EAAAM,GAAAf,EAAAe,GAAAN,OAAAA,EAAAH,QAAAN,EAAAG,GAAAA,EAAAgB,IAAAnB,EAAAS,GAAAA;;;;;;;KAAA,kFAFrCW,OAAAhB,wBAAAgB,QAEAC,QAAAjB,wBAAAiB,SAGA,IAAIC,eAAiB,GACjBC,YAAc,GACdC,QAAU,GAuFZC,SAAAC,SAhEuBC,SAQrB,MAAMC,EAAIC,OAAOC,QACjBH,OAAOI,GAAG,QAAQ,KACdC,aAAY,KACR,MAAMC,WAAaN,OAAOO,cAxBtCC,eAAoBC,SAChB,MACMC,MADU,IAAIC,aACCC,OAAOH,SACtBI,iBAAmBC,OAAOC,OAAOC,OAAO,QAASN,MAGvD,OAFkBO,MAAMC,KAAK,IAAIC,WAAWN,aAClBO,KAAIC,MAAQA,KAAKC,SAAS,IAAIC,SAAS,EAAG,OAAMC,KAAK,GAEnF,EAkBYC,CAAKnB,YAAYoB,MAAKC,OAKlB,GAJA9B,QAAU8B,KACU,KAAhB/B,cACAA,YAAcC,SAEdA,UAAYD,YAAa,CACzB,MAAMgC,MAAQnC,OAAOoC,YAAY,IAAKlC,eAAgBW,YAStD,OAPAL,EAAE6B,IAAI,YAAapC,QAAQqC,aAAa/B,SACxCC,EAAE6B,IAAI,WAAYpC,QAAQsC,YAAYhC,SACtCC,EAAE6B,IAAI,eAAgBpC,QAAQuC,gBAAgBjC,SAC9CC,EAAE6B,IAAI,YAAa9B,OAAOkC,UAAUC,IACpClC,EAAE6B,IAAI,iBAAkBlC,aACxBK,EAAE6B,IAAI,iBAAkBjC,SACxBI,EAAE6B,IAAI,UAAWF,QA5CnBQ,WA6CkB,gCA7CNC,KA6CuC,CAC7CC,UAAW5C,QAAQqC,aAAa/B,QAChCuC,SAAU7C,QAAQsC,YAAYhC,QAC9BwC,aAAc9C,QAAQuC,gBAAgBjC,QACtCyC,UAAWzC,OAAOkC,UAAUC,GAE5BO,eAAgB9C,YAChB+C,eAAgB9C,QAChB+C,QAAShB,QArDM,EAAAtC,MAAIA,MAAC,CAAC,CACzC8C,sBACAC,aACA,IAqDiBX,MAAMmB,SAIHlD,eAAiBW,WACjBV,YAAcC,QACPgD,SAEf,CAhECC,IAACV,WAAYC,IAgEd,GACF,GAnEU,IAoEG,GAerB,CACJ"} \ No newline at end of file +{"version":3,"file":"collaborater.min.js","sources":["../src/collaborater.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 * Storage helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/autosaver\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n//import * as Options from './options';\n//import * as Storage from './storage';mm\n//import Log from 'core/log';\n//import {eventTypes} from 'core_form/events';\n//import {getLogSource} from './common';\nimport * as jsDiff from './jsdiff/index';\nimport {call} from 'core/ajax';\nimport * as Options from \"./options\";\n\n\nlet currentContent = '';\nlet currentHash = '';\nlet newHash = '';\n//let newContent = '';\n//let lastHash = '';\nconst INTERVALTIMEOUT = 1000;\nconst HEADER = \"Index: a\\n===================================================================\\n\";\n\nconst fetchOne = (methodname, args) => call([{\n methodname,\n args,\n}])[0];\nlet intervalId = null;\n\nasync function sha1(message) {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-1', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');\n return hashHex;\n}\n\n// Example usage:\nconst insertCursorMarker = (editor) => {\n const markerId = 'cursor-marker-' + new Date().getTime();\n editor.selection.collapse();\n editor.selection.setContent(``);\n return markerId;\n};\nconst restoreCursorPositionFromMarker = (editor, markerId) => {\n const markerElement = editor.getBody().querySelector(`#${markerId}`);\n if (markerElement) {\n const range = editor.dom.createRng();\n range.setStartAfter(markerElement);\n range.collapse(true);\n editor.selection.setRng(range);\n editor.dom.remove(markerElement); // Clean up marker\n // editor.focus();\n } else {\n // Fallback if marker not found\n // editor.focus();\n editor.selection.select(editor.getBody(), true);\n editor.selection.collapse(false);\n }\n};\n\n\nexport const register = (editor) => {\n // Attempt to store the draft one final time before the page unloads.\n // Note: This may need to be sent as a beacon instead.\n // document.addEventListener('visibilitychange', visibilityChangedHandler);\n\n // When the page is submitted as a form, remove the draft.\n // editor.on('submit', removeAutoSaveSession);\n // document.addEventListener(eventTypes.formSubmittedByJavascript, handleFormSubmittedByJavascript);\n const c = window.console;\n editor.on('init', () => {\n setInterval(() => {\n const newContent = editor.getContent();\n sha1(newContent).then(hash => {\n newHash = hash;\n if (currentHash === '') {\n currentHash = newHash;\n return;\n }\n if (newHash !== currentHash) {\n let patch = jsDiff.createPatch('a', currentContent, newContent);\n patch = patch.substring(HEADER.length);\n /* c.log('contextid', Options.getContextId(editor));\n c.log('pagehash', Options.getPageHash(editor));\n c.log('pageinstance', Options.getPageInstance(editor));\n c.log('elementid', editor.targetElm.id);\n c.log('oldcontenthash', currentHash);\n c.log('newcontenthash', newHash);\n c.log('changes', patch);*/\n return fetchOne('tiny_collaborate_save_changes', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n //pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n /*drftid: Options.getDraftItemId(editor),*/\n oldcontenthash: currentHash,\n newcontenthash: newHash,\n changes: patch,\n\n })\n .then((result) => {\n //* pendingPromise.resolve();\n // c.log('new hash', newHash);\n // c.log('diff', patch);\n currentContent = newContent;\n currentHash = newHash;\n return result;\n });\n }\n });\n if (currentHash === '') {\n return;\n }\n // const markerId = insertCursorMarker(editor);\n let newContent2 = editor.getContent();\n fetchOne('tiny_collaborate_get_changes', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n // pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n currenthash: currentHash,\n }).then((result) => {\n if (result) {\n for (let i in result) {\n let change = result[i];\n c.log('shorthcange', change);\n let patch = HEADER + change;\n c.log('changes', patch);\n newContent2 = jsDiff.applyPatch(newContent2, patch);\n }\n c.log('newContent2', newContent2);\n editor.setContent(newContent2);\n clearInterval(intervalId);\n /*sha1(newContent2).then(hash => {\n currentHash = hash;\n });*/\n }\n\n // restoreCursorPositionFromMarker(editor, markerId);\n });\n\n }, INTERVALTIMEOUT);\n\n /*editor.on('Change', (event) => {\n c.log('Change collaborative', event);\n });*/\n // Setup the Undo handler.\n //editor.on('AddUndo', undoHandler);\n\n /* if (editor.dom.isEmpty(editor.getBody())) {\n Log.info(`Attempting to restore draft`, getLogSource(editor));\n Storage.restoreDraft(editor);\n } else {\n // There was nothing to restore, so we can mark the editor as initialised.\n Log.warn(`Skipping draft restoration. The editor is not empty.`, getLogSource(editor));\n Options.markInitialised(editor);\n }*/\n });\n};\n"],"names":["_getRequireWildcardCache","e","WeakMap","r","t","_interopRequireWildcard","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","jsDiff","Options","currentContent","currentHash","newHash","HEADER","fetchOne","methodname","args","_exports","register","editor","c","window","console","on","setInterval","newContent","getContent","async","message","data","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","byte","toString","padStart","join","sha1","then","hash","patch","createPatch","substring","contextid","getContextId","pagehash","getPageHash","elementid","targetElm","id","oldcontenthash","newcontenthash","changes","result","newContent2","currenthash","change","log","applyPatch","setContent","clearInterval"],"mappings":"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"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/collaborative/amd/src/collaborater.js b/lib/editor/tiny/plugins/collaborative/amd/src/collaborater.js index f6c67591e6fee..a3b84ca884dc7 100644 --- a/lib/editor/tiny/plugins/collaborative/amd/src/collaborater.js +++ b/lib/editor/tiny/plugins/collaborative/amd/src/collaborater.js @@ -37,12 +37,13 @@ let newHash = ''; //let newContent = ''; //let lastHash = ''; const INTERVALTIMEOUT = 1000; -const HEADER = "changes Index: a\n===================================================================\n"; +const HEADER = "Index: a\n===================================================================\n"; const fetchOne = (methodname, args) => call([{ methodname, args, }])[0]; +let intervalId = null; async function sha1(message) { const encoder = new TextEncoder(); @@ -54,7 +55,28 @@ async function sha1(message) { } // Example usage: - +const insertCursorMarker = (editor) => { + const markerId = 'cursor-marker-' + new Date().getTime(); + editor.selection.collapse(); + editor.selection.setContent(``); + return markerId; +}; +const restoreCursorPositionFromMarker = (editor, markerId) => { + const markerElement = editor.getBody().querySelector(`#${markerId}`); + if (markerElement) { + const range = editor.dom.createRng(); + range.setStartAfter(markerElement); + range.collapse(true); + editor.selection.setRng(range); + editor.dom.remove(markerElement); // Clean up marker + // editor.focus(); + } else { + // Fallback if marker not found + // editor.focus(); + editor.selection.select(editor.getBody(), true); + editor.selection.collapse(false); + } +}; export const register = (editor) => { @@ -73,6 +95,7 @@ export const register = (editor) => { newHash = hash; if (currentHash === '') { currentHash = newHash; + return; } if (newHash !== currentHash) { let patch = jsDiff.createPatch('a', currentContent, newContent); @@ -87,7 +110,7 @@ export const register = (editor) => { return fetchOne('tiny_collaborate_save_changes', { contextid: Options.getContextId(editor), pagehash: Options.getPageHash(editor), - pageinstance: Options.getPageInstance(editor), + //pageinstance: Options.getPageInstance(editor), elementid: editor.targetElm.id, /*drftid: Options.getDraftItemId(editor),*/ oldcontenthash: currentHash, @@ -105,26 +128,35 @@ export const register = (editor) => { }); } }); + if (currentHash === '') { + return; + } + // const markerId = insertCursorMarker(editor); let newContent2 = editor.getContent(); fetchOne('tiny_collaborate_get_changes', { contextid: Options.getContextId(editor), pagehash: Options.getPageHash(editor), - pageinstance: Options.getPageInstance(editor), + // pageinstance: Options.getPageInstance(editor), elementid: editor.targetElm.id, currenthash: currentHash, }).then((result) => { if (result) { for (let i in result) { let change = result[i]; + c.log('shorthcange', change); let patch = HEADER + change; c.log('changes', patch); newContent2 = jsDiff.applyPatch(newContent2, patch); } + c.log('newContent2', newContent2); editor.setContent(newContent2); - sha1(newContent2).then(hash => { - currentHash = hash; - }); + clearInterval(intervalId); + /*sha1(newContent2).then(hash => { + currentHash = hash; + });*/ } + + // restoreCursorPositionFromMarker(editor, markerId); }); }, INTERVALTIMEOUT); diff --git a/lib/editor/tiny/plugins/collaborative/classes/change_manager.php b/lib/editor/tiny/plugins/collaborative/classes/change_manager.php index c902cc23d560f..d8304e41ba57d 100644 --- a/lib/editor/tiny/plugins/collaborative/classes/change_manager.php +++ b/lib/editor/tiny/plugins/collaborative/classes/change_manager.php @@ -74,8 +74,8 @@ public function add_collaborative_record($newcontenthash, $changes) { $record->timemodified = time(); $record->changes = $changes; $record->pagehash = $this->pagehash; + $record->contextid = $this->contextid; $record->elementid = $this->elementid; - $record->oldcontenthash = $this->oldcontenthash; $record->userid = $USER->id; // try { @@ -91,7 +91,7 @@ public function get_changes() { $changesarray = []; $currenthash = $this->oldcontenthash; while ($change = $DB->get_record('tiny_collaborative_changes', ['oldcontenthash' => $currenthash, - 'pagehash' => $this->$pagehash, + 'pagehash' => $this->pagehash, 'elementid' => $this->elementid, 'contextid' => $this->contextid ] diff --git a/lib/editor/tiny/plugins/collaborative/classes/external/get_changes.php b/lib/editor/tiny/plugins/collaborative/classes/external/get_changes.php index dccbc7536f458..0d5f2d27a5b4c 100644 --- a/lib/editor/tiny/plugins/collaborative/classes/external/get_changes.php +++ b/lib/editor/tiny/plugins/collaborative/classes/external/get_changes.php @@ -80,7 +80,7 @@ public static function execute( // May have been called by a non-logged in user. if (isloggedin() && !isguestuser()) { - $manager = new \tiny_collaborative\change_manager($contextid, $pagehash, $pageinstance, $elementid, $currenthash); + $manager = new \tiny_collaborative\change_manager($contextid, $pagehash, $elementid, $currenthash); $changes = $manager->get_changes(); } return $changes; From 1e0e3410ddc5adbefbaf0a5f8e6d5f0d0068ef68 Mon Sep 17 00:00:00 2001 From: Simeon Naydenov Date: Wed, 4 Sep 2024 11:51:26 +0300 Subject: [PATCH 2/2] working code --- .../amd/build/collaborater.min.js | 2 +- .../amd/build/collaborater.min.js.map | 2 +- .../collaborative/amd/src/collaborater.js | 24 ++++++++++++++----- .../collaborative/classes/change_manager.php | 16 +++++++++---- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js b/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js index 1edd07e977ad7..a5ab2089e9e14 100644 --- a/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js +++ b/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js @@ -5,6 +5,6 @@ define("tiny_collaborative/collaborater",["exports","./jsdiff/index","core/ajax" * @module tiny_autosave/autosaver * @copyright 2022 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,jsDiff=_interopRequireWildcard(jsDiff),Options=_interopRequireWildcard(Options);let currentContent="",currentHash="",newHash="";const HEADER="Index: a\n===================================================================\n",fetchOne=(methodname,args)=>(0,_ajax.call)([{methodname:methodname,args:args}])[0];_exports.register=editor=>{const c=window.console;editor.on("init",(()=>{setInterval((()=>{const newContent=editor.getContent();if(async function(message){const data=(new TextEncoder).encode(message),hashBuffer=await crypto.subtle.digest("SHA-1",data);return Array.from(new Uint8Array(hashBuffer)).map((byte=>byte.toString(16).padStart(2,"0"))).join("")}(newContent).then((hash=>{if(newHash=hash,""!==currentHash){if(newHash!==currentHash){let patch=jsDiff.createPatch("a",currentContent,newContent);return patch=patch.substring(77),fetchOne("tiny_collaborate_save_changes",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),elementid:editor.targetElm.id,oldcontenthash:currentHash,newcontenthash:newHash,changes:patch}).then((result=>(currentContent=newContent,currentHash=newHash,result)))}}else currentHash=newHash})),""===currentHash)return;let newContent2=editor.getContent();fetchOne("tiny_collaborate_get_changes",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),elementid:editor.targetElm.id,currenthash:currentHash}).then((result=>{if(result){for(let i in result){let change=result[i];c.log("shorthcange",change);let patch=HEADER+change;c.log("changes",patch),newContent2=jsDiff.applyPatch(newContent2,patch)}c.log("newContent2",newContent2),editor.setContent(newContent2),clearInterval(null)}}))}),1e3)}))}})); + */}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,jsDiff=_interopRequireWildcard(jsDiff),Options=_interopRequireWildcard(Options);let currentContent="",currentHash="",newHash="";const HEADER="Index: a\n===================================================================\n",fetchOne=(methodname,args)=>(0,_ajax.call)([{methodname:methodname,args:args}])[0];async function sha1(message){const data=(new TextEncoder).encode(message),hashBuffer=await crypto.subtle.digest("SHA-1",data);return Array.from(new Uint8Array(hashBuffer)).map((byte=>byte.toString(16).padStart(2,"0"))).join("")}_exports.register=editor=>{const c=window.console;editor.on("init",(()=>{setInterval((()=>{const newContent=editor.getContent();if(sha1(newContent).then((hash=>{if(newHash=hash,""===currentHash)return currentContent=newContent,void(currentHash=newHash);if(newHash!==currentHash){let patch=jsDiff.createPatch("a",currentContent,newContent);return patch=patch.substring(77),fetchOne("tiny_collaborate_save_changes",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),elementid:editor.targetElm.id,oldcontenthash:currentHash,newcontenthash:newHash,changes:patch}).then((result=>(currentContent=newContent,currentHash=newHash,result)))}})),""===currentHash)return;let newContent2=editor.getContent();fetchOne("tiny_collaborate_get_changes",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),elementid:editor.targetElm.id,currenthash:currentHash}).then((result=>{if(result){let changesMade=!1;for(let i in result){let change=result[i];c.log("shorthcange",change);let patch=HEADER+change;c.log("changes",patch),c.log("parsedPatch",jsDiff.parsePatch(patch)),newContent2=jsDiff.applyPatch(newContent2,patch),changesMade=!0}changesMade&&(!1===newContent2?c.log("Patch FAILED"):(c.log("newContent2",newContent2),editor.setContent(newContent2),currentContent=newContent2,sha1(newContent2).then((hash=>{currentHash=hash,c.log("new hash",currentHash)}))))}}))}),1e3)}))}})); //# sourceMappingURL=collaborater.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js.map b/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js.map index 7de6b0a0b45da..bac06699ad404 100644 --- a/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js.map +++ b/lib/editor/tiny/plugins/collaborative/amd/build/collaborater.min.js.map @@ -1 +1 @@ -{"version":3,"file":"collaborater.min.js","sources":["../src/collaborater.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 * Storage helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/autosaver\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n//import * as Options from './options';\n//import * as Storage from './storage';mm\n//import Log from 'core/log';\n//import {eventTypes} from 'core_form/events';\n//import {getLogSource} from './common';\nimport * as jsDiff from './jsdiff/index';\nimport {call} from 'core/ajax';\nimport * as Options from \"./options\";\n\n\nlet currentContent = '';\nlet currentHash = '';\nlet newHash = '';\n//let newContent = '';\n//let lastHash = '';\nconst INTERVALTIMEOUT = 1000;\nconst HEADER = \"Index: a\\n===================================================================\\n\";\n\nconst fetchOne = (methodname, args) => call([{\n methodname,\n args,\n}])[0];\nlet intervalId = null;\n\nasync function sha1(message) {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-1', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');\n return hashHex;\n}\n\n// Example usage:\nconst insertCursorMarker = (editor) => {\n const markerId = 'cursor-marker-' + new Date().getTime();\n editor.selection.collapse();\n editor.selection.setContent(``);\n return markerId;\n};\nconst restoreCursorPositionFromMarker = (editor, markerId) => {\n const markerElement = editor.getBody().querySelector(`#${markerId}`);\n if (markerElement) {\n const range = editor.dom.createRng();\n range.setStartAfter(markerElement);\n range.collapse(true);\n editor.selection.setRng(range);\n editor.dom.remove(markerElement); // Clean up marker\n // editor.focus();\n } else {\n // Fallback if marker not found\n // editor.focus();\n editor.selection.select(editor.getBody(), true);\n editor.selection.collapse(false);\n }\n};\n\n\nexport const register = (editor) => {\n // Attempt to store the draft one final time before the page unloads.\n // Note: This may need to be sent as a beacon instead.\n // document.addEventListener('visibilitychange', visibilityChangedHandler);\n\n // When the page is submitted as a form, remove the draft.\n // editor.on('submit', removeAutoSaveSession);\n // document.addEventListener(eventTypes.formSubmittedByJavascript, handleFormSubmittedByJavascript);\n const c = window.console;\n editor.on('init', () => {\n setInterval(() => {\n const newContent = editor.getContent();\n sha1(newContent).then(hash => {\n newHash = hash;\n if (currentHash === '') {\n currentHash = newHash;\n return;\n }\n if (newHash !== currentHash) {\n let patch = jsDiff.createPatch('a', currentContent, newContent);\n patch = patch.substring(HEADER.length);\n /* c.log('contextid', Options.getContextId(editor));\n c.log('pagehash', Options.getPageHash(editor));\n c.log('pageinstance', Options.getPageInstance(editor));\n c.log('elementid', editor.targetElm.id);\n c.log('oldcontenthash', currentHash);\n c.log('newcontenthash', newHash);\n c.log('changes', patch);*/\n return fetchOne('tiny_collaborate_save_changes', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n //pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n /*drftid: Options.getDraftItemId(editor),*/\n oldcontenthash: currentHash,\n newcontenthash: newHash,\n changes: patch,\n\n })\n .then((result) => {\n //* pendingPromise.resolve();\n // c.log('new hash', newHash);\n // c.log('diff', patch);\n currentContent = newContent;\n currentHash = newHash;\n return result;\n });\n }\n });\n if (currentHash === '') {\n return;\n }\n // const markerId = insertCursorMarker(editor);\n let newContent2 = editor.getContent();\n fetchOne('tiny_collaborate_get_changes', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n // pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n currenthash: currentHash,\n }).then((result) => {\n if (result) {\n for (let i in result) {\n let change = result[i];\n c.log('shorthcange', change);\n let patch = HEADER + change;\n c.log('changes', patch);\n newContent2 = jsDiff.applyPatch(newContent2, patch);\n }\n c.log('newContent2', newContent2);\n editor.setContent(newContent2);\n clearInterval(intervalId);\n /*sha1(newContent2).then(hash => {\n currentHash = hash;\n });*/\n }\n\n // restoreCursorPositionFromMarker(editor, markerId);\n });\n\n }, INTERVALTIMEOUT);\n\n /*editor.on('Change', (event) => {\n c.log('Change collaborative', event);\n });*/\n // Setup the Undo handler.\n //editor.on('AddUndo', undoHandler);\n\n /* if (editor.dom.isEmpty(editor.getBody())) {\n Log.info(`Attempting to restore draft`, getLogSource(editor));\n Storage.restoreDraft(editor);\n } else {\n // There was nothing to restore, so we can mark the editor as initialised.\n Log.warn(`Skipping draft restoration. The editor is not empty.`, getLogSource(editor));\n Options.markInitialised(editor);\n }*/\n });\n};\n"],"names":["_getRequireWildcardCache","e","WeakMap","r","t","_interopRequireWildcard","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","jsDiff","Options","currentContent","currentHash","newHash","HEADER","fetchOne","methodname","args","_exports","register","editor","c","window","console","on","setInterval","newContent","getContent","async","message","data","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","byte","toString","padStart","join","sha1","then","hash","patch","createPatch","substring","contextid","getContextId","pagehash","getPageHash","elementid","targetElm","id","oldcontenthash","newcontenthash","changes","result","newContent2","currenthash","change","log","applyPatch","setContent","clearInterval"],"mappings":"uIA8BqC,SAAAA,yBAAAC,GAAA,GAAA,mBAAAC,QAAA,OAAA,KAAA,IAAAC,EAAAD,IAAAA,QAAAE,EAAAF,IAAAA,eAAAF,yBAAA,SAAAC,GAAAA,OAAAA,EAAAG,EAAAD,IAAAF,EAAA,CAAA,SAAAI,wBAAAJ,EAAAE,GAAAA,IAAAA,GAAAF,GAAAA,EAAAK,WAAAL,OAAAA,EAAAA,GAAAA,OAAAA,GAAAA,iBAAAA,GAAAA,mBAAAA,EAAAM,MAAAA,CAAAA,QAAAN,GAAAG,IAAAA,EAAAJ,yBAAAG,GAAA,GAAAC,GAAAA,EAAAI,IAAAP,GAAA,OAAAG,EAAAK,IAAAR,GAAA,IAAAS,EAAA,CAAAC,UAAA,MAAAC,EAAAC,OAAAC,gBAAAD,OAAAE,yBAAA,IAAA,IAAAC,KAAAf,EAAAe,GAAAA,YAAAA,GAAAC,CAAAA,EAAAA,eAAAC,KAAAjB,EAAAe,GAAAG,CAAAA,IAAAA,EAAAP,EAAAC,OAAAE,yBAAAd,EAAAe,GAAAG,KAAAA,IAAAA,EAAAV,KAAAU,EAAAC,KAAAP,OAAAC,eAAAJ,EAAAM,EAAAG,GAAAT,EAAAM,GAAAf,EAAAe,GAAAN,OAAAA,EAAAH,QAAAN,EAAAG,GAAAA,EAAAgB,IAAAnB,EAAAS,GAAAA;;;;;;;KAAA,kFAFrCW,OAAAhB,wBAAAgB,QAEAC,QAAAjB,wBAAAiB,SAGA,IAAIC,eAAiB,GACjBC,YAAc,GACdC,QAAU,GAGd,MACMC,OAAS,kFAETC,SAAWA,CAACC,WAAYC,QAAS,EAAAX,MAAIA,MAAC,CAAC,CACzCU,sBACAC,aACA,GAsIFC,SAAAC,SAjGuBC,SAQrB,MAAMC,EAAIC,OAAOC,QACjBH,OAAOI,GAAG,QAAQ,KACdC,aAAY,KACR,MAAMC,WAAaN,OAAOO,aAsC1B,GAnFZC,eAAoBC,SAChB,MACMC,MADU,IAAIC,aACCC,OAAOH,SACtBI,iBAAmBC,OAAOC,OAAOC,OAAO,QAASN,MAGvD,OAFkBO,MAAMC,KAAK,IAAIC,WAAWN,aAClBO,KAAIC,MAAQA,KAAKC,SAAS,IAAIC,SAAS,EAAG,OAAMC,KAAK,GAEnF,CAuCYC,CAAKnB,YAAYoB,MAAKC,OAElB,GADAlC,QAAUkC,KACU,KAAhBnC,aAIJ,GAAIC,UAAYD,YAAa,CACzB,IAAIoC,MAAQvC,OAAOwC,YAAY,IAAKtC,eAAgBe,YASpD,OARAsB,MAAQA,MAAME,UAAUpC,IAQjBC,SAAS,gCAAiC,CAC7CoC,UAAWzC,QAAQ0C,aAAahC,QAChCiC,SAAU3C,QAAQ4C,YAAYlC,QAE9BmC,UAAWnC,OAAOoC,UAAUC,GAE5BC,eAAgB9C,YAChB+C,eAAgB9C,QAChB+C,QAASZ,QAGZF,MAAMe,SAIHlD,eAAiBe,WACjBd,YAAcC,QACPgD,SAEf,OAhCIjD,YAAcC,OAgClB,IAEgB,KAAhBD,YACA,OAGJ,IAAIkD,YAAc1C,OAAOO,aACzBZ,SAAS,+BAAgC,CACrCoC,UAAWzC,QAAQ0C,aAAahC,QAChCiC,SAAU3C,QAAQ4C,YAAYlC,QAE9BmC,UAAWnC,OAAOoC,UAAUC,GAC5BM,YAAanD,cACdkC,MAAMe,SACL,GAAIA,OAAQ,CACR,IAAK,IAAItD,KAAKsD,OAAQ,CAClB,IAAIG,OAASH,OAAOtD,GACpBc,EAAE4C,IAAI,cAAeD,QACrB,IAAIhB,MAAQlC,OAASkD,OACrB3C,EAAE4C,IAAI,UAAWjB,OACjBc,YAAcrD,OAAOyD,WAAWJ,YAAad,MACjD,CACA3B,EAAE4C,IAAI,cAAeH,aACrB1C,OAAO+C,WAAWL,aAClBM,cA3GH,KA+GD,IAGF,GAzHU,IA2HG,GAgBrB,CACJ"} \ No newline at end of file +{"version":3,"file":"collaborater.min.js","sources":["../src/collaborater.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 * Storage helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/autosaver\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n//import * as Options from './options';\n//import * as Storage from './storage';mm\n//import Log from 'core/log';\n//import {eventTypes} from 'core_form/events';\n//import {getLogSource} from './common';\nimport * as jsDiff from './jsdiff/index';\nimport {call} from 'core/ajax';\nimport * as Options from \"./options\";\n\n\nlet currentContent = '';\nlet currentHash = '';\nlet newHash = '';\n//let newContent = '';\n//let lastHash = '';\nconst INTERVALTIMEOUT = 1000;\nconst HEADER = \"Index: a\\n===================================================================\\n\";\n\nconst fetchOne = (methodname, args) => call([{\n methodname,\n args,\n}])[0];\nlet intervalId = null;\n\nasync function sha1(message) {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hashBuffer = await crypto.subtle.digest('SHA-1', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');\n return hashHex;\n}\n\n// Example usage:\nconst insertCursorMarker = (editor) => {\n const markerId = 'cursor-marker-' + new Date().getTime();\n editor.selection.collapse();\n editor.selection.setContent(``);\n return markerId;\n};\nconst restoreCursorPositionFromMarker = (editor, markerId) => {\n const markerElement = editor.getBody().querySelector(`#${markerId}`);\n if (markerElement) {\n const range = editor.dom.createRng();\n range.setStartAfter(markerElement);\n range.collapse(true);\n editor.selection.setRng(range);\n editor.dom.remove(markerElement); // Clean up marker\n // editor.focus();\n } else {\n // Fallback if marker not found\n // editor.focus();\n editor.selection.select(editor.getBody(), true);\n editor.selection.collapse(false);\n }\n};\n\n\nexport const register = (editor) => {\n // Attempt to store the draft one final time before the page unloads.\n // Note: This may need to be sent as a beacon instead.\n // document.addEventListener('visibilitychange', visibilityChangedHandler);\n\n // When the page is submitted as a form, remove the draft.\n // editor.on('submit', removeAutoSaveSession);\n // document.addEventListener(eventTypes.formSubmittedByJavascript, handleFormSubmittedByJavascript);\n const c = window.console;\n editor.on('init', () => {\n setInterval(() => {\n const newContent = editor.getContent();\n sha1(newContent).then(hash => {\n newHash = hash;\n if (currentHash === '') {\n currentContent = newContent;\n currentHash = newHash;\n return;\n }\n if (newHash !== currentHash) {\n let patch = jsDiff.createPatch('a', currentContent, newContent);\n patch = patch.substring(HEADER.length);\n /* c.log('contextid', Options.getContextId(editor));\n c.log('pagehash', Options.getPageHash(editor));\n c.log('pageinstance', Options.getPageInstance(editor));\n c.log('elementid', editor.targetElm.id);\n c.log('oldcontenthash', currentHash);\n c.log('newcontenthash', newHash);\n c.log('changes', patch);*/\n return fetchOne('tiny_collaborate_save_changes', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n //pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n /*drftid: Options.getDraftItemId(editor),*/\n oldcontenthash: currentHash,\n newcontenthash: newHash,\n changes: patch,\n\n })\n .then((result) => {\n //* pendingPromise.resolve();\n // c.log('new hash', newHash);\n // c.log('diff', patch);\n currentContent = newContent;\n currentHash = newHash;\n return result;\n });\n }\n });\n if (currentHash === '') {\n return;\n }\n // const markerId = insertCursorMarker(editor);\n let newContent2 = editor.getContent();\n fetchOne('tiny_collaborate_get_changes', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n // pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n currenthash: currentHash,\n }).then((result) => {\n if (result) {\n let changesMade = false;\n for (let i in result) {\n let change = result[i];\n c.log('shorthcange', change);\n let patch = HEADER + change;\n c.log('changes', patch);\n c.log('parsedPatch', jsDiff.parsePatch(patch));\n newContent2 = jsDiff.applyPatch(newContent2, patch);\n changesMade = true;\n }\n if (changesMade) {\n if (newContent2 === false) {\n c.log('Patch FAILED');\n } else {\n c.log('newContent2', newContent2);\n editor.setContent(newContent2);\n currentContent = newContent2;\n sha1(newContent2).then(hash => {\n currentHash = hash;\n c.log('new hash', currentHash);\n });\n }\n }\n //clearInterval(intervalId);\n }\n\n // restoreCursorPositionFromMarker(editor, markerId);\n });\n\n }, INTERVALTIMEOUT);\n\n /*editor.on('Change', (event) => {\n c.log('Change collaborative', event);\n });*/\n // Setup the Undo handler.\n //editor.on('AddUndo', undoHandler);\n\n /* if (editor.dom.isEmpty(editor.getBody())) {\n Log.info(`Attempting to restore draft`, getLogSource(editor));\n Storage.restoreDraft(editor);\n } else {\n // There was nothing to restore, so we can mark the editor as initialised.\n Log.warn(`Skipping draft restoration. The editor is not empty.`, getLogSource(editor));\n Options.markInitialised(editor);\n }*/\n });\n};\n"],"names":["_getRequireWildcardCache","e","WeakMap","r","t","_interopRequireWildcard","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","jsDiff","Options","currentContent","currentHash","newHash","HEADER","fetchOne","methodname","args","async","sha1","message","data","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","byte","toString","padStart","join","_exports","register","editor","c","window","console","on","setInterval","newContent","getContent","then","hash","patch","createPatch","substring","contextid","getContextId","pagehash","getPageHash","elementid","targetElm","id","oldcontenthash","newcontenthash","changes","result","newContent2","currenthash","changesMade","change","log","parsePatch","applyPatch","setContent"],"mappings":"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"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/collaborative/amd/src/collaborater.js b/lib/editor/tiny/plugins/collaborative/amd/src/collaborater.js index a3b84ca884dc7..5f41090694c76 100644 --- a/lib/editor/tiny/plugins/collaborative/amd/src/collaborater.js +++ b/lib/editor/tiny/plugins/collaborative/amd/src/collaborater.js @@ -94,6 +94,7 @@ export const register = (editor) => { sha1(newContent).then(hash => { newHash = hash; if (currentHash === '') { + currentContent = newContent; currentHash = newHash; return; } @@ -141,19 +142,30 @@ export const register = (editor) => { currenthash: currentHash, }).then((result) => { if (result) { + let changesMade = false; for (let i in result) { let change = result[i]; c.log('shorthcange', change); let patch = HEADER + change; c.log('changes', patch); + c.log('parsedPatch', jsDiff.parsePatch(patch)); newContent2 = jsDiff.applyPatch(newContent2, patch); + changesMade = true; } - c.log('newContent2', newContent2); - editor.setContent(newContent2); - clearInterval(intervalId); - /*sha1(newContent2).then(hash => { - currentHash = hash; - });*/ + if (changesMade) { + if (newContent2 === false) { + c.log('Patch FAILED'); + } else { + c.log('newContent2', newContent2); + editor.setContent(newContent2); + currentContent = newContent2; + sha1(newContent2).then(hash => { + currentHash = hash; + c.log('new hash', currentHash); + }); + } + } + //clearInterval(intervalId); } // restoreCursorPositionFromMarker(editor, markerId); diff --git a/lib/editor/tiny/plugins/collaborative/classes/change_manager.php b/lib/editor/tiny/plugins/collaborative/classes/change_manager.php index d8304e41ba57d..0f3deb37c09a8 100644 --- a/lib/editor/tiny/plugins/collaborative/classes/change_manager.php +++ b/lib/editor/tiny/plugins/collaborative/classes/change_manager.php @@ -68,6 +68,15 @@ public function __construct( public function add_collaborative_record($newcontenthash, $changes) { global $DB,$USER; + if ($record = $DB->get_record('tiny_collaborative_changes', [ + 'newcontenthash' => $newcontenthash, + // 'pagehash' => $this->pagehash, + 'elementid' => $this->elementid, + 'contextid' => $this->contextid + ])) { + return $record->id; + } + $record = new \stdClass(); $record->oldcontenthash = $this->oldcontenthash; $record->newcontenthash = $newcontenthash; @@ -79,7 +88,7 @@ public function add_collaborative_record($newcontenthash, $changes) { $record->userid = $USER->id; // try { - $record->id = $DB->insert_record('tiny_collaborative_changes', $record); + $record->id = $DB->insert_record('tiny_collaborative_changes', $record); // } catch(\Exception $e) { // return "-1"; // } @@ -91,11 +100,10 @@ public function get_changes() { $changesarray = []; $currenthash = $this->oldcontenthash; while ($change = $DB->get_record('tiny_collaborative_changes', ['oldcontenthash' => $currenthash, - 'pagehash' => $this->pagehash, + // 'pagehash' => $this->pagehash, 'elementid' => $this->elementid, 'contextid' => $this->contextid - ] - )) { + ])) { $changesarray[] = $change->changes; $currenthash = $change->newcontenthash; }