MDL-75263 tiny_autosave: Implement Autosave for TinyMCE
Part of MDL-75966
This commit is contained in:
@@ -2029,6 +2029,7 @@ class core_plugin_manager {
|
||||
),
|
||||
|
||||
'tiny' => [
|
||||
'autosave',
|
||||
'h5p',
|
||||
],
|
||||
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
define("editor_tiny/utils",["exports","core/templates","./options","core/str"],(function(_exports,_templates,_options,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getPluginMetadata=_exports.getImagePath=_exports.getDocumentationLink=_exports.getButtonImage=_exports.displayFilepicker=_exports.addToolbarSection=_exports.addToolbarButtons=_exports.addToolbarButton=_exports.addQuickbarsToolbarItem=_exports.addMenubarItem=_exports.addContextmenuItem=void 0;const getImagePath=function(identifier){let component=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"editor_tiny";return Promise.resolve(M.util.image_url(identifier,component))};_exports.getImagePath=getImagePath;_exports.getButtonImage=async function(identifier){let component=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"editor_tiny";return(0,_templates.renderForPromise)("editor_tiny/toolbar_button",{image:await getImagePath(identifier,component)})};_exports.displayFilepicker=(editor,filetype)=>new Promise(((resolve,reject)=>{const configuration=(0,_options.getFilePicker)(editor,filetype);if(configuration){const options={...configuration,formcallback:resolve};M.core_filepicker.show(Y,options)}else reject("Unknown filetype ".concat(filetype))}));_exports.addToolbarButton=(toolbar,section,button)=>{if(!toolbar)return[{name:section,items:[button]}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&item.items.push(button),item)))};_exports.addToolbarButtons=(toolbar,section,buttons)=>{if(!toolbar)return[{name:section,items:buttons}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&buttons.forEach((button=>item.items.push(button))),item)))};_exports.addToolbarSection=function(toolbar,name,relativeTo){let append=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];const newSection={name:name,items:[]},sectionInserted=toolbar.some(((section,index)=>section.name===relativeTo&&(append?toolbar.splice(index+1,0,newSection):toolbar.splice(index,0,newSection),!0)));return sectionInserted||(append?toolbar.push(newSection):toolbar.unshift(newSection)),toolbar};_exports.addMenubarItem=(menubar,section,menuitem)=>{if(!menubar){({})[section]={title:section,items:menuitem}}const mutatedMenubar=JSON.parse(JSON.stringify(menubar));return Array.from(Object.entries(mutatedMenubar)).forEach((_ref=>{let[name,menu]=_ref;name===section&&(menu.items="".concat(menu.items," ").concat(menuitem))})),mutatedMenubar};_exports.addContextmenuItem=function(contextmenu){const contextmenuItems=(null!=contextmenu?contextmenu:"").split(" ");for(var _len=arguments.length,menuitems=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)menuitems[_key-1]=arguments[_key];return contextmenuItems.concat(menuitems).filter((item=>""!==item)).join(" ")};_exports.addQuickbarsToolbarItem=function(toolbar){return toolbar};const getDocumentationLink=pluginName=>"https://docs.moodle.org/en/editor_tiny/".concat(pluginName);_exports.getDocumentationLink=getDocumentationLink;_exports.getPluginMetadata=async function(component,pluginName){let url=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const name=await(0,_str.get_string)("helplinktext",component);return{getMetadata:()=>({name:name,url:null!=url?url:getDocumentationLink(pluginName)})}}}));
|
||||
define("editor_tiny/utils",["exports","core/templates","./options","core/str"],(function(_exports,_templates,_options,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getPluginMetadata=_exports.getImagePath=_exports.getDocumentationLink=_exports.getButtonImage=_exports.ensureEditorIsValid=_exports.displayFilepicker=_exports.addToolbarSection=_exports.addToolbarButtons=_exports.addToolbarButton=_exports.addQuickbarsToolbarItem=_exports.addMenubarItem=_exports.addContextmenuItem=void 0;const getImagePath=function(identifier){let component=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"editor_tiny";return Promise.resolve(M.util.image_url(identifier,component))};_exports.getImagePath=getImagePath;_exports.getButtonImage=async function(identifier){let component=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"editor_tiny";return(0,_templates.renderForPromise)("editor_tiny/toolbar_button",{image:await getImagePath(identifier,component)})};_exports.displayFilepicker=(editor,filetype)=>new Promise(((resolve,reject)=>{const configuration=(0,_options.getFilePicker)(editor,filetype);if(configuration){const options={...configuration,formcallback:resolve};M.core_filepicker.show(Y,options)}else reject("Unknown filetype ".concat(filetype))}));_exports.addToolbarButton=(toolbar,section,button)=>{if(!toolbar)return[{name:section,items:[button]}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&item.items.push(button),item)))};_exports.addToolbarButtons=(toolbar,section,buttons)=>{if(!toolbar)return[{name:section,items:buttons}];return JSON.parse(JSON.stringify(toolbar)).map((item=>(item.name===section&&buttons.forEach((button=>item.items.push(button))),item)))};_exports.addToolbarSection=function(toolbar,name,relativeTo){let append=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];const newSection={name:name,items:[]},sectionInserted=toolbar.some(((section,index)=>section.name===relativeTo&&(append?toolbar.splice(index+1,0,newSection):toolbar.splice(index,0,newSection),!0)));return sectionInserted||(append?toolbar.push(newSection):toolbar.unshift(newSection)),toolbar};_exports.addMenubarItem=(menubar,section,menuitem)=>{if(!menubar){({})[section]={title:section,items:menuitem}}const mutatedMenubar=JSON.parse(JSON.stringify(menubar));return Array.from(Object.entries(mutatedMenubar)).forEach((_ref=>{let[name,menu]=_ref;name===section&&(menu.items="".concat(menu.items," ").concat(menuitem))})),mutatedMenubar};_exports.addContextmenuItem=function(contextmenu){const contextmenuItems=(null!=contextmenu?contextmenu:"").split(" ");for(var _len=arguments.length,menuitems=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)menuitems[_key-1]=arguments[_key];return contextmenuItems.concat(menuitems).filter((item=>""!==item)).join(" ")};_exports.addQuickbarsToolbarItem=function(toolbar){return toolbar};const getDocumentationLink=pluginName=>"https://docs.moodle.org/en/editor_tiny/".concat(pluginName);_exports.getDocumentationLink=getDocumentationLink;_exports.getPluginMetadata=async function(component,pluginName){let url=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const name=await(0,_str.get_string)("helplinktext",component);return{getMetadata:()=>({name:name,url:null!=url?url:getDocumentationLink(pluginName)})}};_exports.ensureEditorIsValid=editor=>{var _editor$targetElm;return null!=editor&&null!==(_editor$targetElm=editor.targetElm)&&void 0!==_editor$targetElm&&_editor$targetElm.closest("body")?editor:(editor.destroy(),null)}}));
|
||||
|
||||
//# sourceMappingURL=utils.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -225,3 +225,23 @@ export const getPluginMetadata = async(component, pluginName, url = null) => {
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that the editor is still in the DOM, removing it if it is not.
|
||||
*
|
||||
* @param {TinyMCE} editor
|
||||
* @returns {TinyMCE|null}
|
||||
*/
|
||||
export const ensureEditorIsValid = (editor) => {
|
||||
// TinyMCE uses the element ID as a map key internally, even if the target has changed.
|
||||
// In cases such as where an editor is in a modal form which has been detached from the DOM, but the editor not removed,
|
||||
// we need to manually destroy the editor.
|
||||
// We could theoretically do this with a Mutation Observer, but in some cases the Node may be moved,
|
||||
// or added back elsewhere in the DOM.
|
||||
if (!editor?.targetElm?.closest('body')) {
|
||||
editor.destroy();
|
||||
return null;
|
||||
}
|
||||
|
||||
return editor;
|
||||
};
|
||||
|
||||
@@ -491,6 +491,9 @@ class manager {
|
||||
'image',
|
||||
'media',
|
||||
|
||||
// Use the Moodle autosave plugin instead.
|
||||
'autosave',
|
||||
|
||||
// Disable the Template plugin for now.
|
||||
'template',
|
||||
];
|
||||
|
||||
@@ -31,6 +31,6 @@ class provider implements \core_privacy\local\metadata\null_provider {
|
||||
* @return string
|
||||
*/
|
||||
public static function get_reason(): string {
|
||||
return 'privacy:metadata';
|
||||
return 'privacy:reason';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
define("tiny_autosave/autosaver",["exports","./options","./storage","core/log","./common"],(function(_exports,Options,Storage,_log,_common){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
|
||||
/**
|
||||
* Storage helper for the Moodle Tiny Autosave plugin.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=void 0,Options=_interopRequireWildcard(Options),Storage=_interopRequireWildcard(Storage),_log=(obj=_log)&&obj.__esModule?obj:{default:obj};_exports.register=editor=>{const undoHandler=()=>{editor.undoManager.hasUndo()?Storage.saveDraft(editor):_log.default.debug("Ignoring undo event as there is no undo history",(0,_common.getLogSource)(editor))},visibilityChangedHandler=()=>{"hidden"===document.visibilityState&&Options.isInitialised(editor)&&Storage.saveDraft(editor)};document.addEventListener("visibilitychange",visibilityChangedHandler),editor.on("submit",(()=>{document.removeEventListener("visibilitychange",visibilityChangedHandler),Storage.removeAutosaveSession(editor)})),editor.on("init",(()=>{editor.on("AddUndo",undoHandler),editor.dom.isEmpty(editor.getBody())?(_log.default.info("Attempting to restore draft",(0,_common.getLogSource)(editor)),Storage.restoreDraft(editor)):(_log.default.warn("Skipping draft restoration. The editor is not empty.",(0,_common.getLogSource)(editor)),Options.markInitialised(editor))}))}}));
|
||||
|
||||
//# sourceMappingURL=autosaver.min.js.map
|
||||
@@ -0,0 +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 <http://www.gnu.org/licenses/>.\n\n/**\n * Storage helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/plugin\n * @copyright 2022 Andrew Lyons <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Options from './options';\nimport * as Storage from './storage';\nimport Log from 'core/log';\nimport {getLogSource} from './common';\n\nexport const register = (editor) => {\n const undoHandler = () => {\n if (!editor.undoManager.hasUndo()) {\n Log.debug(`Ignoring undo event as there is no undo history`, getLogSource(editor));\n return;\n }\n Storage.saveDraft(editor);\n };\n\n const visibilityChangedHandler = () => {\n if (document.visibilityState === 'hidden') {\n if (Options.isInitialised(editor)) {\n Storage.saveDraft(editor);\n }\n }\n };\n\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', () => {\n document.removeEventListener('visibilitychange', visibilityChangedHandler);\n Storage.removeAutosaveSession(editor);\n });\n\n\n editor.on('init', () => {\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":["editor","undoHandler","undoManager","hasUndo","Storage","saveDraft","debug","visibilityChangedHandler","document","visibilityState","Options","isInitialised","addEventListener","on","removeEventListener","removeAutosaveSession","dom","isEmpty","getBody","info","restoreDraft","warn","markInitialised"],"mappings":";;;;;;;4OA4ByBA,eACfC,YAAc,KACXD,OAAOE,YAAYC,UAIxBC,QAAQC,UAAUL,qBAHVM,yDAAyD,wBAAaN,UAM5EO,yBAA2B,KACI,WAA7BC,SAASC,iBACLC,QAAQC,cAAcX,SACtBI,QAAQC,UAAUL,SAO9BQ,SAASI,iBAAiB,mBAAoBL,0BAG9CP,OAAOa,GAAG,UAAU,KAChBL,SAASM,oBAAoB,mBAAoBP,0BACjDH,QAAQW,sBAAsBf,WAIlCA,OAAOa,GAAG,QAAQ,KAEdb,OAAOa,GAAG,UAAWZ,aAEjBD,OAAOgB,IAAIC,QAAQjB,OAAOkB,yBACtBC,oCAAoC,wBAAanB,SACrDI,QAAQgB,aAAapB,uBAGjBqB,6DAA6D,wBAAarB,SAC9EU,QAAQY,gBAAgBtB"}
|
||||
@@ -0,0 +1,3 @@
|
||||
define("tiny_autosave/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={component:"tiny_autosave",pluginName:"tiny_autosave/plugin",getLogSource:editor=>"tiny_autosave/".concat(editor.id)},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=common.min.js.map
|
||||
@@ -0,0 +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 <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny Autosave plugin for Moodle.\n *\n * @module tiny_autosave/plugin\n * @copyright 2022 Andrew Lyons <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n component: 'tiny_autosave',\n pluginName: 'tiny_autosave/plugin',\n getLogSource: (editor) => `tiny_autosave/${editor.id}`,\n};\n"],"names":["component","pluginName","getLogSource","editor","id"],"mappings":"sKAuBe,CACXA,UAAW,gBACXC,WAAY,uBACZC,aAAeC,gCAA4BA,OAAOC"}
|
||||
@@ -0,0 +1,11 @@
|
||||
define("tiny_autosave/options",["exports","./common","editor_tiny/options","editor_tiny/utils"],(function(_exports,_common,_options,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getBackoffTime=void 0,Object.defineProperty(_exports,"getContextId",{enumerable:!0,get:function(){return _options.getContextId}}),Object.defineProperty(_exports,"getDraftItemId",{enumerable:!0,get:function(){return _options.getDraftItemId}}),_exports.register=_exports.markInitialised=_exports.isInitialised=_exports.getPageInstance=_exports.getPageHash=void 0;
|
||||
/**
|
||||
* Options helper for the Moodle Tiny Autosave plugin.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
const initialisedOptionName=(0,_options.getPluginOptionName)(_common.pluginName,"initialised"),pageHashName=(0,_options.getPluginOptionName)(_common.pluginName,"pagehash"),pageInstanceName=(0,_options.getPluginOptionName)(_common.pluginName,"pageinstance"),backoffTime=(0,_options.getPluginOptionName)(_common.pluginName,"backoffTime");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(initialisedOptionName,{processor:"boolean",default:!1}),registerOption(pageHashName,{processor:"string",default:""}),registerOption(pageInstanceName,{processor:"string",default:""}),registerOption(pageInstanceName,{processor:"string",default:""}),registerOption(backoffTime,{processor:"number",default:500})};_exports.isInitialised=editor=>!!(0,_utils.ensureEditorIsValid)(editor)&&editor.options.get(initialisedOptionName);_exports.markInitialised=editor=>editor.options.set(initialisedOptionName,!0);_exports.getPageHash=editor=>editor.options.get(pageHashName);_exports.getPageInstance=editor=>editor.options.get(pageInstanceName);_exports.getBackoffTime=editor=>editor.options.get(backoffTime)}));
|
||||
|
||||
//# sourceMappingURL=options.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"options.min.js","sources":["../src/options.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Options helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/plugin\n * @copyright 2022 Andrew Lyons <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {pluginName} from './common';\nimport {\n getContextId,\n getDraftItemId,\n getPluginOptionName,\n} from 'editor_tiny/options';\nimport {ensureEditorIsValid} from 'editor_tiny/utils';\n\nconst initialisedOptionName = getPluginOptionName(pluginName, 'initialised');\nconst pageHashName = getPluginOptionName(pluginName, 'pagehash');\nconst pageInstanceName = getPluginOptionName(pluginName, 'pageinstance');\nconst backoffTime = getPluginOptionName(pluginName, 'backoffTime');\n\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n registerOption(initialisedOptionName, {\n processor: 'boolean',\n \"default\": false,\n });\n\n registerOption(pageHashName, {\n processor: 'string',\n \"default\": '',\n });\n\n registerOption(pageInstanceName, {\n processor: 'string',\n \"default\": '',\n });\n registerOption(pageInstanceName, {\n processor: 'string',\n \"default\": '',\n });\n registerOption(backoffTime, {\n processor: 'number',\n \"default\": 500,\n });\n};\n\nexport const isInitialised = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n return false;\n }\n\n return editor.options.get(initialisedOptionName);\n};\nexport const markInitialised = (editor) => editor.options.set(initialisedOptionName, true);\nexport const getPageHash = (editor) => editor.options.get(pageHashName);\nexport const getPageInstance = (editor) => editor.options.get(pageInstanceName);\nexport const getBackoffTime = (editor) => editor.options.get(backoffTime);\nexport {\n getContextId,\n getDraftItemId,\n};\n"],"names":["initialisedOptionName","pluginName","pageHashName","pageInstanceName","backoffTime","editor","registerOption","options","register","processor","get","set"],"mappings":";;;;;;;;MA+BMA,uBAAwB,gCAAoBC,mBAAY,eACxDC,cAAe,gCAAoBD,mBAAY,YAC/CE,kBAAmB,gCAAoBF,mBAAY,gBACnDG,aAAc,gCAAoBH,mBAAY,iCAE3BI,eACfC,eAAiBD,OAAOE,QAAQC,SACtCF,eAAeN,sBAAuB,CAClCS,UAAW,mBACA,IAGfH,eAAeJ,aAAc,CACzBO,UAAW,iBACA,KAGfH,eAAeH,iBAAkB,CAC7BM,UAAW,iBACA,KAEfH,eAAeH,iBAAkB,CAC7BM,UAAW,iBACA,KAEfH,eAAeF,YAAa,CACxBK,UAAW,iBACA,8BAIWJ,WACrB,8BAAoBA,SAIlBA,OAAOE,QAAQG,IAAIV,gDAEEK,QAAWA,OAAOE,QAAQI,IAAIX,uBAAuB,wBACzDK,QAAWA,OAAOE,QAAQG,IAAIR,uCAC1BG,QAAWA,OAAOE,QAAQG,IAAIP,0CAC/BE,QAAWA,OAAOE,QAAQG,IAAIN"}
|
||||
@@ -0,0 +1,10 @@
|
||||
define("tiny_autosave/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./common","./options","./autosaver"],(function(_exports,_loader,_utils,_common,Options,Autosaver){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
|
||||
/**
|
||||
* Tiny Autosave plugin for Moodle.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Options=_interopRequireWildcard(Options),Autosaver=_interopRequireWildcard(Autosaver);var _default=new Promise((async resolve=>{const[tinyMCE,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add(_common.pluginName,(editor=>(Options.register(editor),Autosaver.register(editor),pluginMetadata))),resolve(_common.pluginName)}));return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=plugin.min.js.map
|
||||
@@ -0,0 +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 <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny Autosave plugin for Moodle.\n *\n * @module tiny_autosave/plugin\n * @copyright 2022 Andrew Lyons <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from './common';\nimport * as Options from './options';\nimport * as Autosaver from './autosaver';\n\n// Setup the autosave Plugin.\nexport default new Promise(async(resolve) => {\n const [tinyMCE, pluginMetadata] = await Promise.all([\n getTinyMCE(),\n getPluginMetadata(component, pluginName),\n ]);\n\n // Note: The PluginManager.add function does not accept a Promise.\n // Any asynchronous code must be run before this point.\n tinyMCE.PluginManager.add(pluginName, (editor) => {\n // Register options.\n Options.register(editor);\n\n // Register the Autosaver.\n Autosaver.register(editor);\n\n return pluginMetadata;\n });\n\n resolve(pluginName);\n});\n"],"names":["Promise","async","tinyMCE","pluginMetadata","all","component","pluginName","PluginManager","add","editor","Options","register","Autosaver","resolve"],"mappings":";;;;;;;wLA8Be,IAAIA,SAAQC,MAAAA,gBAChBC,QAASC,sBAAwBH,QAAQI,IAAI,EAChD,yBACA,4BAAkBC,kBAAWC,sBAKjCJ,QAAQK,cAAcC,IAAIF,oBAAaG,SAEnCC,QAAQC,SAASF,QAGjBG,UAAUD,SAASF,QAEZN,kBAGXU,QAAQP"}
|
||||
@@ -0,0 +1,10 @@
|
||||
define("tiny_autosave/repository",["exports","core/ajax","./options","core/pending","editor_tiny/utils"],(function(_exports,_ajax,Options,_pending,_utils){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateAutosaveSession=_exports.resumeAutosaveSession=_exports.removeAutosaveSession=void 0,Options=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}
|
||||
/**
|
||||
* Repository helper for the Moodle Tiny Autosave plugin.
|
||||
*
|
||||
* @module tiny_autosave/repository
|
||||
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/(Options),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};const fetchOne=(methodname,args)=>(0,_ajax.call)([{methodname:methodname,args:args}])[0];_exports.resumeAutosaveSession=editor=>{if(!(0,_utils.ensureEditorIsValid)(editor))return Promise.reject("Invalid editor");const pendingPromise=new _pending.default("tiny_autosave/repository:resumeAutosaveSession");return fetchOne("tiny_autosave_resume_session",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id,draftid:Options.getDraftItemId(editor)}).then((result=>(pendingPromise.resolve(),result)))};_exports.updateAutosaveSession=editor=>{if(!(0,_utils.ensureEditorIsValid)(editor))return Promise.reject("Invalid editor");const pendingPromise=new _pending.default("tiny_autosave/repository:updateAutosaveSession");return fetchOne("tiny_autosave_update_session",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id,drafttext:editor.getContent()}).then((result=>(pendingPromise.resolve(),result)))};_exports.removeAutosaveSession=editor=>(0,_utils.ensureEditorIsValid)(editor)?fetchOne("tiny_autosave_reset_session",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id}):Promise.reject("Invalid editor")}));
|
||||
|
||||
//# sourceMappingURL=repository.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"repository.min.js","sources":["../src/repository.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Repository helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/repository\n * @copyright 2022 Andrew Lyons <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call} from 'core/ajax';\nimport * as Options from './options';\nimport Pending from 'core/pending';\nimport {ensureEditorIsValid} from 'editor_tiny/utils';\n\nconst fetchOne = (methodname, args) => call([{\n methodname,\n args,\n}])[0];\n\n/**\n * Resume an Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n * @returns {Promise<AutosaveSession>} The Autosave session\n */\nexport const resumeAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n return Promise.reject('Invalid editor');\n }\n const pendingPromise = new Pending('tiny_autosave/repository:resumeAutosaveSession');\n return fetchOne('tiny_autosave_resume_session', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n draftid: Options.getDraftItemId(editor),\n })\n .then((result) => {\n pendingPromise.resolve();\n return result;\n });\n};\n\n/**\n * Update the content of the Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n * @returns {Promise<AutosaveSession>} The Autosave session\n */\nexport const updateAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n return Promise.reject('Invalid editor');\n }\n\n const pendingPromise = new Pending('tiny_autosave/repository:updateAutosaveSession');\n\n return fetchOne('tiny_autosave_update_session', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n drafttext: editor.getContent(),\n })\n .then((result) => {\n pendingPromise.resolve();\n return result;\n });\n};\n\n/**\n * Remove the Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n * @returns {Promise<AutosaveSession>} The Autosave session\n */\nexport const removeAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n return Promise.reject('Invalid editor');\n }\n\n return fetchOne('tiny_autosave_reset_session', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n });\n};\n"],"names":["fetchOne","methodname","args","editor","Promise","reject","pendingPromise","Pending","contextid","Options","getContextId","pagehash","getPageHash","pageinstance","getPageInstance","elementid","targetElm","id","draftid","getDraftItemId","then","result","resolve","drafttext","getContent"],"mappings":";;;;;;;+EA4BMA,SAAW,CAACC,WAAYC,QAAS,cAAK,CAAC,CACzCD,WAAAA,WACAC,KAAAA,QACA,kCAQkCC,cAC7B,8BAAoBA,eACdC,QAAQC,OAAO,wBAEpBC,eAAiB,IAAIC,iBAAQ,yDAC5BP,SAAS,+BAAgC,CAC5CQ,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,GAC5BC,QAAST,QAAQU,eAAehB,UAEnCiB,MAAMC,SACHf,eAAegB,UACRD,0CAUuBlB,cAC7B,8BAAoBA,eACdC,QAAQC,OAAO,wBAGpBC,eAAiB,IAAIC,iBAAQ,yDAE5BP,SAAS,+BAAgC,CAC5CQ,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,GAC5BM,UAAWpB,OAAOqB,eAErBJ,MAAMC,SACHf,eAAegB,UACRD,0CAUuBlB,SAC7B,8BAAoBA,QAIlBH,SAAS,8BAA+B,CAC3CQ,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,KAPrBb,QAAQC,OAAO"}
|
||||
@@ -0,0 +1,10 @@
|
||||
define("tiny_autosave/storage",["exports","./repository","core/pending","./options","core/log","./common"],(function(_exports,Repository,_pending,_options,_log,_common){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.saveDraft=_exports.restoreDraft=_exports.removeAutosaveSession=void 0,Repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}
|
||||
/**
|
||||
* Storage helper for the Moodle Tiny Autosave plugin.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/(Repository),_pending=_interopRequireDefault(_pending),_log=_interopRequireDefault(_log);const saveDebounceMap=new Map;_exports.restoreDraft=async editor=>{const pendingPromise=new _pending.default("tiny_autosave/restoreDraft");try{const session=await Repository.resumeAutosaveSession(editor);session&&session.drafttext&&editor.undoManager.ignore((()=>{editor.setContent(session.drafttext),editor.save()}))}catch(error){_log.default.warn("Failed to restore draft: ".concat(error),(0,_common.getLogSource)(editor))}(0,_options.markInitialised)(editor),pendingPromise.resolve()};_exports.saveDraft=editor=>{const timerId=saveDebounceMap.get(editor);timerId&&clearTimeout(timerId),saveDebounceMap.set(editor,setTimeout((()=>{_log.default.debug("Saving draft",(0,_common.getLogSource)(editor)),Repository.updateAutosaveSession(editor)}),(0,_options.getBackoffTime)(editor)))};_exports.removeAutosaveSession=editor=>{_log.default.debug("Removing Autosave session",(0,_common.getLogSource)(editor)),Repository.removeAutosaveSession(editor)}}));
|
||||
|
||||
//# sourceMappingURL=storage.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"storage.min.js","sources":["../src/storage.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Storage helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/plugin\n * @copyright 2022 Andrew Lyons <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Repository from \"./repository\";\nimport Pending from 'core/pending';\nimport {\n markInitialised,\n getBackoffTime,\n} from \"./options\";\nimport Log from 'core/log';\nimport {getLogSource} from './common';\n\n/** @property {Map} A map of debounced draft saves */\nconst saveDebounceMap = new Map();\n\n/**\n * Attempt to restore a draft into the editor\n *\n * @param {TinyMCE} editor The Editor to restore a draft for\n */\nexport const restoreDraft = async(editor) => {\n const pendingPromise = new Pending('tiny_autosave/restoreDraft');\n try {\n const session = await Repository.resumeAutosaveSession(editor);\n if (session && session.drafttext) {\n editor.undoManager.ignore(() => {\n editor.setContent(session.drafttext);\n editor.save();\n });\n }\n } catch (error) {\n // Ignore any errors as drafts are optional.\n Log.warn(`Failed to restore draft: ${error}`, getLogSource(editor));\n }\n markInitialised(editor);\n pendingPromise.resolve();\n};\n\n/**\n * Save the current content of the editor as a draft.\n *\n * @param {TinyMCE} editor\n */\nexport const saveDraft = (editor) => {\n const timerId = saveDebounceMap.get(editor);\n if (timerId) {\n clearTimeout(timerId);\n }\n saveDebounceMap.set(editor, setTimeout(() => {\n Log.debug(`Saving draft`, getLogSource(editor));\n Repository.updateAutosaveSession(editor);\n }, getBackoffTime(editor)));\n};\n\n/**\n * Delete the draft for the current editor.\n *\n * @param {TinyMCE} editor\n */\nexport const removeAutosaveSession = (editor) => {\n Log.debug(`Removing Autosave session`, getLogSource(editor));\n Repository.removeAutosaveSession(editor);\n};\n"],"names":["saveDebounceMap","Map","async","pendingPromise","Pending","session","Repository","resumeAutosaveSession","editor","drafttext","undoManager","ignore","setContent","save","error","warn","resolve","timerId","get","clearTimeout","set","setTimeout","debug","updateAutosaveSession","removeAutosaveSession"],"mappings":";;;;;;;oGAiCMA,gBAAkB,IAAIC,0BAOAC,MAAAA,eAClBC,eAAiB,IAAIC,iBAAQ,wCAEzBC,cAAgBC,WAAWC,sBAAsBC,QACnDH,SAAWA,QAAQI,WACnBD,OAAOE,YAAYC,QAAO,KACtBH,OAAOI,WAAWP,QAAQI,WAC1BD,OAAOK,UAGjB,MAAOC,oBAEDC,wCAAiCD,QAAS,wBAAaN,sCAE/CA,QAChBL,eAAea,8BAQOR,eAChBS,QAAUjB,gBAAgBkB,IAAIV,QAChCS,SACAE,aAAaF,SAEjBjB,gBAAgBoB,IAAIZ,OAAQa,YAAW,kBAC/BC,sBAAsB,wBAAad,SACvCF,WAAWiB,sBAAsBf,WAClC,2BAAeA,0CAQgBA,sBAC9Bc,mCAAmC,wBAAad,SACpDF,WAAWkB,sBAAsBhB"}
|
||||
@@ -0,0 +1,70 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Storage helper for the Moodle Tiny Autosave plugin.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import * as Options from './options';
|
||||
import * as Storage from './storage';
|
||||
import Log from 'core/log';
|
||||
import {getLogSource} from './common';
|
||||
|
||||
export const register = (editor) => {
|
||||
const undoHandler = () => {
|
||||
if (!editor.undoManager.hasUndo()) {
|
||||
Log.debug(`Ignoring undo event as there is no undo history`, getLogSource(editor));
|
||||
return;
|
||||
}
|
||||
Storage.saveDraft(editor);
|
||||
};
|
||||
|
||||
const visibilityChangedHandler = () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
if (Options.isInitialised(editor)) {
|
||||
Storage.saveDraft(editor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Attempt to store the draft one final time before the page unloads.
|
||||
// Note: This may need to be sent as a beacon instead.
|
||||
document.addEventListener('visibilitychange', visibilityChangedHandler);
|
||||
|
||||
// When the page is submitted as a form, remove the draft.
|
||||
editor.on('submit', () => {
|
||||
document.removeEventListener('visibilitychange', visibilityChangedHandler);
|
||||
Storage.removeAutosaveSession(editor);
|
||||
});
|
||||
|
||||
|
||||
editor.on('init', () => {
|
||||
// Setup the Undo handler.
|
||||
editor.on('AddUndo', undoHandler);
|
||||
|
||||
if (editor.dom.isEmpty(editor.getBody())) {
|
||||
Log.info(`Attempting to restore draft`, getLogSource(editor));
|
||||
Storage.restoreDraft(editor);
|
||||
} else {
|
||||
// There was nothing to restore, so we can mark the editor as initialised.
|
||||
Log.warn(`Skipping draft restoration. The editor is not empty.`, getLogSource(editor));
|
||||
Options.markInitialised(editor);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tiny Autosave plugin for Moodle.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
export default {
|
||||
component: 'tiny_autosave',
|
||||
pluginName: 'tiny_autosave/plugin',
|
||||
getLogSource: (editor) => `tiny_autosave/${editor.id}`,
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Options helper for the Moodle Tiny Autosave plugin.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {pluginName} from './common';
|
||||
import {
|
||||
getContextId,
|
||||
getDraftItemId,
|
||||
getPluginOptionName,
|
||||
} from 'editor_tiny/options';
|
||||
import {ensureEditorIsValid} from 'editor_tiny/utils';
|
||||
|
||||
const initialisedOptionName = getPluginOptionName(pluginName, 'initialised');
|
||||
const pageHashName = getPluginOptionName(pluginName, 'pagehash');
|
||||
const pageInstanceName = getPluginOptionName(pluginName, 'pageinstance');
|
||||
const backoffTime = getPluginOptionName(pluginName, 'backoffTime');
|
||||
|
||||
export const register = (editor) => {
|
||||
const registerOption = editor.options.register;
|
||||
registerOption(initialisedOptionName, {
|
||||
processor: 'boolean',
|
||||
"default": false,
|
||||
});
|
||||
|
||||
registerOption(pageHashName, {
|
||||
processor: 'string',
|
||||
"default": '',
|
||||
});
|
||||
|
||||
registerOption(pageInstanceName, {
|
||||
processor: 'string',
|
||||
"default": '',
|
||||
});
|
||||
registerOption(pageInstanceName, {
|
||||
processor: 'string',
|
||||
"default": '',
|
||||
});
|
||||
registerOption(backoffTime, {
|
||||
processor: 'number',
|
||||
"default": 500,
|
||||
});
|
||||
};
|
||||
|
||||
export const isInitialised = (editor) => {
|
||||
if (!ensureEditorIsValid(editor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return editor.options.get(initialisedOptionName);
|
||||
};
|
||||
export const markInitialised = (editor) => editor.options.set(initialisedOptionName, true);
|
||||
export const getPageHash = (editor) => editor.options.get(pageHashName);
|
||||
export const getPageInstance = (editor) => editor.options.get(pageInstanceName);
|
||||
export const getBackoffTime = (editor) => editor.options.get(backoffTime);
|
||||
export {
|
||||
getContextId,
|
||||
getDraftItemId,
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tiny Autosave plugin for Moodle.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
import {getTinyMCE} from 'editor_tiny/loader';
|
||||
import {getPluginMetadata} from 'editor_tiny/utils';
|
||||
|
||||
import {component, pluginName} from './common';
|
||||
import * as Options from './options';
|
||||
import * as Autosaver from './autosaver';
|
||||
|
||||
// Setup the autosave Plugin.
|
||||
export default new Promise(async(resolve) => {
|
||||
const [tinyMCE, pluginMetadata] = await Promise.all([
|
||||
getTinyMCE(),
|
||||
getPluginMetadata(component, pluginName),
|
||||
]);
|
||||
|
||||
// Note: The PluginManager.add function does not accept a Promise.
|
||||
// Any asynchronous code must be run before this point.
|
||||
tinyMCE.PluginManager.add(pluginName, (editor) => {
|
||||
// Register options.
|
||||
Options.register(editor);
|
||||
|
||||
// Register the Autosaver.
|
||||
Autosaver.register(editor);
|
||||
|
||||
return pluginMetadata;
|
||||
});
|
||||
|
||||
resolve(pluginName);
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Repository helper for the Moodle Tiny Autosave plugin.
|
||||
*
|
||||
* @module tiny_autosave/repository
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {call} from 'core/ajax';
|
||||
import * as Options from './options';
|
||||
import Pending from 'core/pending';
|
||||
import {ensureEditorIsValid} from 'editor_tiny/utils';
|
||||
|
||||
const fetchOne = (methodname, args) => call([{
|
||||
methodname,
|
||||
args,
|
||||
}])[0];
|
||||
|
||||
/**
|
||||
* Resume an Autosave session.
|
||||
*
|
||||
* @param {TinyMCE} editor The TinyMCE editor instance
|
||||
* @returns {Promise<AutosaveSession>} The Autosave session
|
||||
*/
|
||||
export const resumeAutosaveSession = (editor) => {
|
||||
if (!ensureEditorIsValid(editor)) {
|
||||
return Promise.reject('Invalid editor');
|
||||
}
|
||||
const pendingPromise = new Pending('tiny_autosave/repository:resumeAutosaveSession');
|
||||
return fetchOne('tiny_autosave_resume_session', {
|
||||
contextid: Options.getContextId(editor),
|
||||
pagehash: Options.getPageHash(editor),
|
||||
pageinstance: Options.getPageInstance(editor),
|
||||
elementid: editor.targetElm.id,
|
||||
draftid: Options.getDraftItemId(editor),
|
||||
})
|
||||
.then((result) => {
|
||||
pendingPromise.resolve();
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the content of the Autosave session.
|
||||
*
|
||||
* @param {TinyMCE} editor The TinyMCE editor instance
|
||||
* @returns {Promise<AutosaveSession>} The Autosave session
|
||||
*/
|
||||
export const updateAutosaveSession = (editor) => {
|
||||
if (!ensureEditorIsValid(editor)) {
|
||||
return Promise.reject('Invalid editor');
|
||||
}
|
||||
|
||||
const pendingPromise = new Pending('tiny_autosave/repository:updateAutosaveSession');
|
||||
|
||||
return fetchOne('tiny_autosave_update_session', {
|
||||
contextid: Options.getContextId(editor),
|
||||
pagehash: Options.getPageHash(editor),
|
||||
pageinstance: Options.getPageInstance(editor),
|
||||
elementid: editor.targetElm.id,
|
||||
drafttext: editor.getContent(),
|
||||
})
|
||||
.then((result) => {
|
||||
pendingPromise.resolve();
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the Autosave session.
|
||||
*
|
||||
* @param {TinyMCE} editor The TinyMCE editor instance
|
||||
* @returns {Promise<AutosaveSession>} The Autosave session
|
||||
*/
|
||||
export const removeAutosaveSession = (editor) => {
|
||||
if (!ensureEditorIsValid(editor)) {
|
||||
return Promise.reject('Invalid editor');
|
||||
}
|
||||
|
||||
return fetchOne('tiny_autosave_reset_session', {
|
||||
contextid: Options.getContextId(editor),
|
||||
pagehash: Options.getPageHash(editor),
|
||||
pageinstance: Options.getPageInstance(editor),
|
||||
elementid: editor.targetElm.id,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Storage helper for the Moodle Tiny Autosave plugin.
|
||||
*
|
||||
* @module tiny_autosave/plugin
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import * as Repository from "./repository";
|
||||
import Pending from 'core/pending';
|
||||
import {
|
||||
markInitialised,
|
||||
getBackoffTime,
|
||||
} from "./options";
|
||||
import Log from 'core/log';
|
||||
import {getLogSource} from './common';
|
||||
|
||||
/** @property {Map} A map of debounced draft saves */
|
||||
const saveDebounceMap = new Map();
|
||||
|
||||
/**
|
||||
* Attempt to restore a draft into the editor
|
||||
*
|
||||
* @param {TinyMCE} editor The Editor to restore a draft for
|
||||
*/
|
||||
export const restoreDraft = async(editor) => {
|
||||
const pendingPromise = new Pending('tiny_autosave/restoreDraft');
|
||||
try {
|
||||
const session = await Repository.resumeAutosaveSession(editor);
|
||||
if (session && session.drafttext) {
|
||||
editor.undoManager.ignore(() => {
|
||||
editor.setContent(session.drafttext);
|
||||
editor.save();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore any errors as drafts are optional.
|
||||
Log.warn(`Failed to restore draft: ${error}`, getLogSource(editor));
|
||||
}
|
||||
markInitialised(editor);
|
||||
pendingPromise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the current content of the editor as a draft.
|
||||
*
|
||||
* @param {TinyMCE} editor
|
||||
*/
|
||||
export const saveDraft = (editor) => {
|
||||
const timerId = saveDebounceMap.get(editor);
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
saveDebounceMap.set(editor, setTimeout(() => {
|
||||
Log.debug(`Saving draft`, getLogSource(editor));
|
||||
Repository.updateAutosaveSession(editor);
|
||||
}, getBackoffTime(editor)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the draft for the current editor.
|
||||
*
|
||||
* @param {TinyMCE} editor
|
||||
*/
|
||||
export const removeAutosaveSession = (editor) => {
|
||||
Log.debug(`Removing Autosave session`, getLogSource(editor));
|
||||
Repository.removeAutosaveSession(editor);
|
||||
};
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tiny_autosave;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Autosave Manager.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class autosave_manager {
|
||||
|
||||
/** @var int The contextid */
|
||||
protected $contextid;
|
||||
|
||||
/** @var string The page hash reference */
|
||||
protected $pagehash;
|
||||
|
||||
/** @var string The page instance reference */
|
||||
protected $pageinstance;
|
||||
|
||||
/** @var string The elementid for this editor */
|
||||
protected $elementid;
|
||||
|
||||
/** @var stdClass The user record */
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* Constructor for the autosave manager.
|
||||
*
|
||||
* @param int $contextid The contextid of the session
|
||||
* @param string $pagehash The page hash
|
||||
* @param string $pageinstance The page instance
|
||||
* @param string $elementid The element id
|
||||
* @param null|stdClass $user The user object for the owner of the autosave
|
||||
*/
|
||||
public function __construct(
|
||||
int $contextid,
|
||||
string $pagehash,
|
||||
string $pageinstance,
|
||||
string $elementid,
|
||||
?stdClass $user = null
|
||||
) {
|
||||
global $USER;
|
||||
|
||||
$this->contextid = $contextid;
|
||||
$this->pagehash = $pagehash;
|
||||
$this->pageinstance = $pageinstance;
|
||||
$this->elementid = $elementid;
|
||||
$this->user = $user ?? $USER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the autosave record for this session.
|
||||
*
|
||||
* @return stdClass|null
|
||||
*/
|
||||
public function get_autosave_record(): ?stdClass {
|
||||
global $DB;
|
||||
|
||||
$record = $DB->get_record('tiny_autosave', [
|
||||
'contextid' => $this->contextid,
|
||||
'userid' => $this->user->id,
|
||||
'pagehash' => $this->pagehash,
|
||||
'elementid' => $this->elementid,
|
||||
]);
|
||||
|
||||
if (empty($record)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an autosave record for the session.
|
||||
*
|
||||
* @param string $drafttext The draft text to save
|
||||
* @param null|int $draftid The draft file area if one is used
|
||||
* @return stdClass The autosave record
|
||||
*/
|
||||
public function create_autosave_record(string $drafttext, ?int $draftid = null): stdClass {
|
||||
global $DB;
|
||||
$record = (object) [
|
||||
'userid' => $this->user->id,
|
||||
'contextid' => $this->contextid,
|
||||
'pagehash' => $this->pagehash,
|
||||
'pageinstance' => $this->pageinstance,
|
||||
'elementid' => $this->elementid,
|
||||
'drafttext' => $drafttext,
|
||||
'timemodified' => time(),
|
||||
];
|
||||
|
||||
if ($draftid) {
|
||||
$record->draftid = $draftid;
|
||||
}
|
||||
|
||||
$record->id = $DB->insert_record('tiny_autosave', $record);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the text of the autosave session.
|
||||
*
|
||||
* @param string $drafttext The text to save
|
||||
* @return stdClass The updated record
|
||||
*/
|
||||
public function update_autosave_record(string $drafttext): stdClass {
|
||||
global $DB;
|
||||
|
||||
$record = $this->get_autosave_record();
|
||||
if ($record) {
|
||||
$record->drafttext = $drafttext;
|
||||
$record->timemodified = time();
|
||||
$DB->update_record('tiny_autosave', $record);
|
||||
|
||||
return $record;
|
||||
} else {
|
||||
return $this->create_autosave_record($drafttext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume an autosave session, updating the draft file area if relevant.
|
||||
*
|
||||
* @param null|int $draftid The draft file area to update
|
||||
* @return stdClass The updated autosave record
|
||||
*/
|
||||
public function resume_autosave_session(?int $draftid = null): stdClass {
|
||||
$record = $this->get_autosave_record();
|
||||
if (!$record) {
|
||||
return $this->create_autosave_record('', $draftid);
|
||||
}
|
||||
|
||||
if ($this->is_autosave_stale($record)) {
|
||||
// If the autosave record it stale, remove it and create a new, blank, record.
|
||||
$this->remove_autosave_record();
|
||||
|
||||
return $this->create_autosave_record('', $draftid);
|
||||
}
|
||||
|
||||
if (empty($draftid)) {
|
||||
// There is no file area to handle, so just return the record without any further changes.
|
||||
return $record;
|
||||
}
|
||||
|
||||
// This autosave is not stale, so update the draftid and move any files over to the new draft file area.
|
||||
return $this->update_draftid_for_record($record, $draftid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the autosave data is stale.
|
||||
*
|
||||
* Records are considered stale if either of the following conditions are true:
|
||||
* - The record is older than the stale period
|
||||
* - Any of the files in the draft area are newer than the autosave data itself
|
||||
*
|
||||
* @param stdClass $record The autosave record
|
||||
* @return bool Whether the record is stale
|
||||
*/
|
||||
protected function is_autosave_stale(stdClass $record): bool {
|
||||
$timemodified = $record->timemodified;
|
||||
// TODO Create the UI for the stale period.
|
||||
$staleperiod = get_config('tiny_autosave', 'staleperiod');
|
||||
if (empty($staleperiod)) {
|
||||
$staleperiod = (4 * DAYSECS);
|
||||
}
|
||||
|
||||
$stale = $timemodified < (time() - $staleperiod);
|
||||
|
||||
if (empty($record->draftid)) {
|
||||
return $stale;
|
||||
}
|
||||
|
||||
$fs = get_file_storage();
|
||||
$files = $fs->get_directory_files($record->contextid, 'user', 'draft', $record->draftid, '/', true, true);
|
||||
|
||||
$lastfilemodified = 0;
|
||||
foreach ($files as $file) {
|
||||
if ($record->timemodified < $file->get_timemodified()) {
|
||||
$stale = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $stale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the files relating to the autosave session to a new draft file area.
|
||||
*
|
||||
* @param stdClass $record The autosave record
|
||||
* @param int $newdraftid The new draftid to move files to
|
||||
* @return stdClass The updated autosave record
|
||||
*/
|
||||
protected function update_draftid_for_record(stdClass $record, int $newdraftid): stdClass {
|
||||
global $CFG, $DB;
|
||||
|
||||
require_once("{$CFG->libdir}/filelib.php");
|
||||
|
||||
// Copy all draft files from the old draft area.
|
||||
$usercontext = \context_user::instance($this->user->id);
|
||||
|
||||
// This function copies all the files in one draft area, to another area (in this case it's
|
||||
// another draft area). It also rewrites the text to @@PLUGINFILE@@ links.
|
||||
$record->drafttext = file_save_draft_area_files(
|
||||
$record->draftid,
|
||||
$usercontext->id,
|
||||
'user',
|
||||
'draft',
|
||||
$newdraftid,
|
||||
[],
|
||||
$record->drafttext
|
||||
);
|
||||
|
||||
// Final rewrite to the new draft area (convert the @@PLUGINFILES@@ again).
|
||||
$record->drafttext = file_rewrite_pluginfile_urls(
|
||||
$record->drafttext,
|
||||
'draftfile.php',
|
||||
$usercontext->id,
|
||||
'user',
|
||||
'draft',
|
||||
$newdraftid
|
||||
);
|
||||
|
||||
$record->draftid = $newdraftid;
|
||||
$record->pageinstance = $this->pageinstance;
|
||||
$record->timemodified = time();
|
||||
|
||||
$DB->update_record('tiny_autosave', $record);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the autosave record.
|
||||
*/
|
||||
public function remove_autosave_record(): void {
|
||||
global $DB;
|
||||
|
||||
$DB->delete_records('tiny_autosave', [
|
||||
'contextid' => $this->contextid,
|
||||
'userid' => $this->user->id,
|
||||
'pagehash' => $this->pagehash,
|
||||
'elementid' => $this->elementid,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tiny_autosave\external;
|
||||
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_single_structure;
|
||||
use external_value;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once("{$CFG->libdir}/externallib.php");
|
||||
|
||||
/**
|
||||
* Web Service to reset the autosave session.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @category external
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class reset_autosave_session extends external_api {
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'contextid' => new external_value(PARAM_INT, 'The context id that owns the editor', VALUE_REQUIRED),
|
||||
'pagehash' => new external_value(PARAM_ALPHANUMEXT, 'The page hash', VALUE_REQUIRED),
|
||||
'pageinstance' => new external_value(PARAM_ALPHANUMEXT, 'The page instance', VALUE_REQUIRED),
|
||||
'elementid' => new external_value(PARAM_RAW, 'The ID of the element', VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the autosave entry for this autosave instance.
|
||||
*
|
||||
* If not matching autosave area could be found, the function will
|
||||
* silently return and this is not treated as an error condition.
|
||||
*
|
||||
* @param int $contextid The context id of the owner
|
||||
* @param string $pagehash The hash of the page
|
||||
* @param string $pageinstance The instance id of the page
|
||||
* @param string $elementid The id of the element
|
||||
* @return null
|
||||
*/
|
||||
public static function execute(
|
||||
int $contextid,
|
||||
string $pagehash,
|
||||
string $pageinstance,
|
||||
string $elementid
|
||||
): array {
|
||||
global $DB, $USER;
|
||||
|
||||
[
|
||||
'contextid' => $contextid,
|
||||
'pagehash' => $pagehash,
|
||||
'elementid' => $elementid,
|
||||
'pageinstance' => $pageinstance,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'contextid' => $contextid,
|
||||
'pagehash' => $pagehash,
|
||||
'elementid' => $elementid,
|
||||
'pageinstance' => $pageinstance,
|
||||
|
||||
]);
|
||||
|
||||
$manager = new \tiny_autosave\autosave_manager($contextid, $pagehash, $pageinstance, $elementid);
|
||||
$manager->remove_autosave_record();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the return structure of the external service.
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([]);
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tiny_autosave\external;
|
||||
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_single_structure;
|
||||
use external_value;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once("{$CFG->libdir}/externallib.php");
|
||||
|
||||
/**
|
||||
* Web Service to resume an autosave session.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @category external
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class resume_autosave_session extends external_api {
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'contextid' => new external_value(PARAM_INT, 'The context id that owns the editor', VALUE_REQUIRED),
|
||||
'pagehash' => new external_value(PARAM_ALPHANUMEXT, 'The page hash', VALUE_REQUIRED),
|
||||
'pageinstance' => new external_value(PARAM_ALPHANUMEXT, 'The page instance', VALUE_REQUIRED),
|
||||
'elementid' => new external_value(PARAM_RAW, 'The ID of the element', VALUE_REQUIRED),
|
||||
'draftid' => new external_value(
|
||||
PARAM_INT,
|
||||
'The new draft item id to resume files to',
|
||||
VALUE_REQUIRED,
|
||||
null,
|
||||
NULL_ALLOWED
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the autosave entry for this autosave instance.
|
||||
*
|
||||
* If not matching autosave area could be found, the function will
|
||||
* silently return and this is not treated as an error condition.
|
||||
*
|
||||
* @param int $contextid The context id of the owner
|
||||
* @param string $pagehash The hash of the page
|
||||
* @param string $pageinstance The instance id of the page
|
||||
* @param string $elementid The id of the element
|
||||
* @param int $draftid The id of the draftid to resume to
|
||||
* @return null
|
||||
*/
|
||||
public static function execute(
|
||||
int $contextid,
|
||||
string $pagehash,
|
||||
string $pageinstance,
|
||||
string $elementid,
|
||||
?int $draftid
|
||||
): array {
|
||||
global $DB, $USER;
|
||||
|
||||
[
|
||||
'contextid' => $contextid,
|
||||
'pagehash' => $pagehash,
|
||||
'pageinstance' => $pageinstance,
|
||||
'elementid' => $elementid,
|
||||
'draftid' => $draftid,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'contextid' => $contextid,
|
||||
'pagehash' => $pagehash,
|
||||
'pageinstance' => $pageinstance,
|
||||
'elementid' => $elementid,
|
||||
'draftid' => $draftid,
|
||||
]);
|
||||
|
||||
$manager = new \tiny_autosave\autosave_manager($contextid, $pagehash, $pageinstance, $elementid);
|
||||
$sessiondata = $manager->resume_autosave_session($draftid);
|
||||
return [
|
||||
'drafttext' => $sessiondata->drafttext,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the return structure of the external service.
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'drafttext' => new external_value(PARAM_RAW, 'The draft text'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tiny_autosave\external;
|
||||
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_single_structure;
|
||||
use external_value;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once("{$CFG->libdir}/externallib.php");
|
||||
|
||||
/**
|
||||
* Web Service to update an autosave session's content.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @category external
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class update_autosave_session_content extends external_api {
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'contextid' => new external_value(PARAM_INT, 'The context id that owns the editor', VALUE_REQUIRED),
|
||||
'pagehash' => new external_value(PARAM_ALPHANUMEXT, 'The page hash', VALUE_REQUIRED),
|
||||
'pageinstance' => new external_value(PARAM_ALPHANUMEXT, 'The page instance', VALUE_REQUIRED),
|
||||
'elementid' => new external_value(PARAM_RAW, 'The ID of the element', VALUE_REQUIRED),
|
||||
'drafttext' => new external_value(PARAM_RAW, 'The draft text', VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the autosave entry for this autosave instance.
|
||||
*
|
||||
* If not matching autosave area could be found, the function will
|
||||
* silently return and this is not treated as an error condition.
|
||||
*
|
||||
* @param int $contextid The context id of the owner
|
||||
* @param string $pagehash The hash of the page
|
||||
* @param string $pageinstance The instance id of the page
|
||||
* @param string $elementid The id of the element
|
||||
* @param string $drafttext The text to store
|
||||
* @return null
|
||||
*/
|
||||
public static function execute(
|
||||
int $contextid,
|
||||
string $pagehash,
|
||||
string $pageinstance,
|
||||
string $elementid,
|
||||
string $drafttext
|
||||
): array {
|
||||
global $DB, $USER;
|
||||
|
||||
[
|
||||
'contextid' => $contextid,
|
||||
'pagehash' => $pagehash,
|
||||
'pageinstance' => $pageinstance,
|
||||
'elementid' => $elementid,
|
||||
'drafttext' => $drafttext,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'contextid' => $contextid,
|
||||
'pagehash' => $pagehash,
|
||||
'pageinstance' => $pageinstance,
|
||||
'elementid' => $elementid,
|
||||
'drafttext' => $drafttext,
|
||||
]);
|
||||
|
||||
$manager = new \tiny_autosave\autosave_manager($contextid, $pagehash, $pageinstance, $elementid);
|
||||
$manager->update_autosave_record($drafttext);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the return structure of the external service.
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tiny_autosave;
|
||||
|
||||
use context;
|
||||
use editor_tiny\plugin;
|
||||
use editor_tiny\plugin_with_configuration;
|
||||
|
||||
/**
|
||||
* Tiny autosave plugin for Moodle.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @copyright 2021 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class plugininfo extends plugin implements plugin_with_configuration {
|
||||
|
||||
public static function get_plugin_configuration_for_context(
|
||||
context $context,
|
||||
array $options,
|
||||
array $fpoptions,
|
||||
?\editor_tiny\editor $editor = null
|
||||
): array {
|
||||
global $PAGE;
|
||||
|
||||
if (empty($editor) || empty($options['autosave'])) {
|
||||
return [
|
||||
'autosave' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'pagehash' => sha1($PAGE->url . '<>' . s($editor->get_text())),
|
||||
'pageinstance' => bin2hex(random_bytes(16)),
|
||||
'backoffTime' => (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) ? 0 : 500,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tiny_autosave\privacy;
|
||||
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\writer;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\userlist;
|
||||
use core_privacy\local\request\approved_userlist;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Privacy Subsystem implementation for tiny_autosave.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @copyright 2022 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
// The tiny editor stores user provided data.
|
||||
\core_privacy\local\metadata\provider,
|
||||
|
||||
// The tiny editor provides data directly to core.
|
||||
\core_privacy\local\request\plugin\provider,
|
||||
|
||||
// The tiny editor is capable of determining which users have data within it.
|
||||
\core_privacy\local\request\core_userlist_provider {
|
||||
|
||||
/**
|
||||
* Returns information about how tiny_autosave stores its data.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection): collection {
|
||||
// There isn't much point giving details about the pageid, etc.
|
||||
$collection->add_database_table('tiny_autosave', [
|
||||
'userid' => 'privacy:metadata:database:tiny_autosave:userid',
|
||||
'drafttext' => 'privacy:metadata:database:tiny_autosave:drafttext',
|
||||
'timemodified' => 'privacy:metadata:database:tiny_autosave:timemodified',
|
||||
], 'privacy:metadata:database:tiny_autosave');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist {
|
||||
$contextlist = new \core_privacy\local\request\contextlist();
|
||||
|
||||
// Data may be saved in the user context.
|
||||
$sql = "SELECT
|
||||
c.id
|
||||
FROM {tiny_autosave} eas
|
||||
JOIN {context} c ON c.id = eas.contextid
|
||||
WHERE contextlevel = :contextuser AND c.instanceid = :userid";
|
||||
$contextlist->add_from_sql($sql, ['contextuser' => CONTEXT_USER, 'userid' => $userid]);
|
||||
|
||||
// Data may be saved against the userid.
|
||||
$sql = "SELECT contextid FROM {tiny_autosave} WHERE userid = :userid";
|
||||
$contextlist->add_from_sql($sql, ['userid' => $userid]);
|
||||
|
||||
return $contextlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of users within a specific context.
|
||||
*
|
||||
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
|
||||
*/
|
||||
public static function get_users_in_context(userlist $userlist) {
|
||||
$context = $userlist->get_context();
|
||||
|
||||
$params = [
|
||||
'contextid' => $context->id
|
||||
];
|
||||
|
||||
$sql = "SELECT userid
|
||||
FROM {tiny_autosave}
|
||||
WHERE contextid = :contextid";
|
||||
|
||||
$userlist->add_from_sql('userid', $sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
$user = $contextlist->get_user();
|
||||
|
||||
// Firstly export all autosave records from all contexts in the list owned by the given user.
|
||||
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
$contextparams['userid'] = $user->id;
|
||||
|
||||
$sql = "SELECT *
|
||||
FROM {tiny_autosave}
|
||||
WHERE userid = :userid AND contextid {$contextsql}";
|
||||
|
||||
$autosaves = $DB->get_recordset_sql($sql, $contextparams);
|
||||
self::export_autosaves($user, $autosaves);
|
||||
|
||||
// Additionally export all eventual records in the given user's context regardless the actual owner.
|
||||
// We still consider them to be the user's personal data even when edited by someone else.
|
||||
[$contextsql, $contextparams] = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
$contextparams['userid'] = $user->id;
|
||||
$contextparams['contextuser'] = CONTEXT_USER;
|
||||
|
||||
$sql = "SELECT eas.*
|
||||
FROM {tiny_autosave} eas
|
||||
JOIN {context} c ON c.id = eas.contextid
|
||||
WHERE c.id {$contextsql} AND c.contextlevel = :contextuser AND c.instanceid = :userid";
|
||||
|
||||
$autosaves = $DB->get_recordset_sql($sql, $contextparams);
|
||||
self::export_autosaves($user, $autosaves);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all autosave records in the recordset, and close the recordset when finished.
|
||||
*
|
||||
* @param stdClass $user The user whose data is to be exported
|
||||
* @param \moodle_recordset $autosaves The recordset containing the data to export
|
||||
*/
|
||||
protected static function export_autosaves(stdClass $user, \moodle_recordset $autosaves) {
|
||||
foreach ($autosaves as $autosave) {
|
||||
$context = \context::instance_by_id($autosave->contextid);
|
||||
$subcontext = [
|
||||
get_string('autosaves', 'tiny_autosave'),
|
||||
$autosave->id,
|
||||
];
|
||||
|
||||
$html = writer::with_context($context)
|
||||
->rewrite_pluginfile_urls($subcontext, 'user', 'draft', $autosave->draftid, $autosave->drafttext);
|
||||
|
||||
$data = (object) [
|
||||
'drafttext' => format_text($html, FORMAT_HTML, static::get_filter_options()),
|
||||
'timemodified' => \core_privacy\local\request\transform::datetime($autosave->timemodified),
|
||||
];
|
||||
|
||||
if ($autosave->userid != $user->id) {
|
||||
$data->author = \core_privacy\local\request\transform::user($autosave->userid);
|
||||
}
|
||||
|
||||
writer::with_context($context)
|
||||
->export_data($subcontext, $data)
|
||||
->export_area_files($subcontext, 'user', 'draft', $autosave->draftid);
|
||||
}
|
||||
$autosaves->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
global $DB;
|
||||
|
||||
$DB->delete_records('tiny_autosave', [
|
||||
'contextid' => $context->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete multiple users within a single context.
|
||||
*
|
||||
* @param approved_userlist $userlist The approved context and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_users(approved_userlist $userlist) {
|
||||
global $DB;
|
||||
|
||||
$context = $userlist->get_context();
|
||||
$userids = $userlist->get_userids();
|
||||
|
||||
[$useridsql, $useridsqlparams] = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
||||
$params = ['contextid' => $context->id] + $useridsqlparams;
|
||||
|
||||
$DB->delete_records_select('tiny_autosave', "contextid = :contextid AND userid {$useridsql}",
|
||||
$params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
$user = $contextlist->get_user();
|
||||
|
||||
[$contextsql, $contextparams] = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
$contextparams['userid'] = $user->id;
|
||||
|
||||
$sql = "SELECT * FROM {tiny_autosave} WHERE contextid {$contextsql}";
|
||||
$autosaves = $DB->delete_records_select(
|
||||
'tiny_autosave',
|
||||
"userid = :userid AND contextid {$contextsql}",
|
||||
$contextparams
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter options.
|
||||
*
|
||||
* This is shared to allow unit testing too.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public static function get_filter_options() {
|
||||
return (object) [
|
||||
'overflowdiv' => true,
|
||||
'noclean' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/editor/tiny/plugins/autosave/db" VERSION="20140819" COMMENT="XMLDB file for Moodle tiny_autosave"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
<TABLES>
|
||||
<TABLE NAME="tiny_autosave" COMMENT="The content of the textarea saved during autosave operations">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="elementid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The unique id for the text editor in the form."/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The contextid that the form was loaded with."/>
|
||||
<FIELD NAME="pagehash" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="The HTML DOM id of the page that loaded the form."/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the user that loaded the form."/>
|
||||
<FIELD NAME="drafttext" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The draft text"/>
|
||||
<FIELD NAME="draftid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Optional draft area id containing draft files."/>
|
||||
<FIELD NAME="pageinstance" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="The browser tab instance that last saved the draft text. This is to prevent multiple tabs from the same user saving different text alternately."/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Store the last modified time for the auto save text."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="autosave_uniq_key" TYPE="unique" FIELDS="elementid, contextid, userid, pagehash" COMMENT="Unique key for the user in the form in the page."/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* External service definitions for tiny_autosavse.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$functions = [
|
||||
'tiny_autosave_resume_session' => array(
|
||||
'classname' => 'tiny_autosave\external\resume_autosave_session',
|
||||
'methodname' => 'execute',
|
||||
'description' => 'Resume an autosave session',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
),
|
||||
'tiny_autosave_reset_session' => array(
|
||||
'classname' => 'tiny_autosave\external\reset_autosave_session',
|
||||
'methodname' => 'execute',
|
||||
'description' => 'Reset an autosave session',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
),
|
||||
'tiny_autosave_update_session' => array(
|
||||
'classname' => 'tiny_autosave\external\update_autosave_session_content',
|
||||
'methodname' => 'execute',
|
||||
'description' => 'Update an autosave session',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
),
|
||||
];
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Strings for component 'tiny_autosave', language 'en'.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Tiny Autosave';
|
||||
$string['helplinktext'] = 'Moodle Autosave';
|
||||
$string['privacy:metadata:database:tiny_autosave:userid'] = 'The user ID of the user who created the autosave session';
|
||||
$string['privacy:metadata:database:tiny_autosave:drafttext'] = 'The text content of the autosave session';
|
||||
$string['privacy:metadata:database:tiny_autosave:timemodified'] = 'The time that the autosave session was last modified';
|
||||
$string['privacy:metadata:database:tiny_autosave'] = 'A table storing autosave session data for the TinyMCE editor';
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tiny text editor Autosave Plugin version file.
|
||||
*
|
||||
* @package tiny_autosave
|
||||
* @copyright 2022 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2022080900;
|
||||
$plugin->requires = 2020061500;
|
||||
$plugin->component = 'tiny_autosave';
|
||||
Reference in New Issue
Block a user