@themarioga/grid-editor 8.6.0 → 8.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -297,11 +297,26 @@ GridEditor.features.autosave = function(ge) {
297
297
  var found = readDraft(false);
298
298
  return found ? { html: found.html, savedAt: found.savedAt } : null;
299
299
  },
300
+ // What there is now is not a draft any more - a page calls this
301
+ // once it has saved to its server - so the save waiting does not
302
+ // write it back, and only a change from here makes a new draft
300
303
  clearDraft: function() {
301
304
  if (state === "unavailable") {
302
305
  return false;
303
306
  }
307
+ window.clearTimeout(timer);
308
+ timer = null;
304
309
  remove();
310
+ if (base !== null && state !== "asking" && !sourceOpen) {
311
+ var watching = !!observer && state === "enabled";
312
+ if (watching) {
313
+ observer.disconnect();
314
+ }
315
+ last = ge.snapshotHtml();
316
+ if (watching) {
317
+ watch();
318
+ }
319
+ }
305
320
  return true;
306
321
  }
307
322
  }
@@ -303,11 +303,26 @@
303
303
  var found = readDraft(false);
304
304
  return found ? { html: found.html, savedAt: found.savedAt } : null;
305
305
  },
306
+ // What there is now is not a draft any more - a page calls this
307
+ // once it has saved to its server - so the save waiting does not
308
+ // write it back, and only a change from here makes a new draft
306
309
  clearDraft: function() {
307
310
  if (state === "unavailable") {
308
311
  return false;
309
312
  }
313
+ window.clearTimeout(timer);
314
+ timer = null;
310
315
  remove();
316
+ if (base !== null && state !== "asking" && !sourceOpen) {
317
+ var watching = !!observer && state === "enabled";
318
+ if (watching) {
319
+ observer.disconnect();
320
+ }
321
+ last = ge.snapshotHtml();
322
+ if (watching) {
323
+ watch();
324
+ }
325
+ }
311
326
  return true;
312
327
  }
313
328
  }
@@ -1 +1 @@
1
- (()=>{var c=window.GridEditor;if(!c||typeof c.get!="function")throw new Error("grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, its locales and grideditor.jquery.js");Object.assign(c.locales.en,{"autosave.restore_title":"Restore the draft?","autosave.restore_message":"There is a draft of this page saved on {date}, with changes that were not published. Restore it?","autosave.restore":"Restore","autosave.discard":"Discard"});var L=1,A={local:"localStorage",session:"sessionStorage"},k=1e3,D=new Set;function G(e){for(var s=2166136261,n=0;n<e.length;n++)s^=e.charCodeAt(n),s=Math.imul(s,16777619);return(s>>>0).toString(16).padStart(8,"0")}function H(e){return!!e&&typeof e=="object"&&e.version===L&&typeof e.html=="string"&&typeof e.base=="string"&&typeof e.savedAt=="string"&&!isNaN(Date.parse(e.savedAt))}c.features.autosave=function(e){var s=j(e.settings.autosave||{}),n=s.key||"grideditor.autosave:"+window.location.pathname+"#"+e.canvas.id,v=N(s.storage),r="disabled",g=!0,S=!1,l=null,f=null,d=null,u=null,i=null,p=!1,x=!1;v?D.has(n)?(e.warn('autosave: another editor on this page already saves to "'+n+'", so this one does not; give each its own autosave.key'),r="unavailable"):(D.add(n),s.enabled&&(r="enabled",l=E(!0))):(e.warn("autosave: the browser will not give this page its "+A[s.storage]+", so nothing is saved"),r="unavailable");function j(t){var a={enabled:t.enabled===void 0?!0:!!t.enabled,storage:t.storage===void 0?"local":t.storage,key:t.key===void 0?null:t.key,delay:t.delay===void 0?k:t.delay,maxAge:t.maxAge===void 0?null:t.maxAge};return A[a.storage]||(e.warn('autosave.storage "'+a.storage+'" is not local or session: local is used'),a.storage="local"),a.key!==null&&(typeof a.key!="string"||a.key==="")&&(e.warn("autosave.key must be a string: the page's own key is used"),a.key=null),(typeof a.delay!="number"||!Number.isInteger(a.delay)||a.delay<0)&&(e.warn("autosave.delay must be a whole number of milliseconds, 0 or more: "+k+" is used"),a.delay=k),a.maxAge!==null&&!(typeof a.maxAge=="number"&&isFinite(a.maxAge)&&a.maxAge>0)&&(e.warn("autosave.maxAge must be a number of milliseconds over 0, or null: drafts do not expire"),a.maxAge=null),a}function N(t){try{var a=window[A[t]];return a.getItem(n),a}catch(o){return null}}function E(t){var a;try{a=v.getItem(n)}catch(w){return null}if(a===null)return null;var o=null;try{o=JSON.parse(a)}catch(w){}return H(o)?t&&s.maxAge!==null&&Date.now()-Date.parse(o.savedAt)>s.maxAge?(m(),null):o:(t&&(e.warn('autosave: what is saved under "'+n+'" is not a draft this version can read: it is discarded'),m()),null)}function m(){try{v.removeItem(n)}catch(t){}}function h(t){if(window.clearTimeout(u),u=null,p||f===null)return!1;var a=!!i;a&&i.disconnect();var o=e.snapshotHtml();if(a&&b(),o===d)return!1;var w={version:L,html:o,savedAt:new Date().toISOString(),base:f};try{v.setItem(n,JSON.stringify(w))}catch(y){return x||(x=!0,e.warn("autosave: the draft could not be saved ("+(y&&y.message||y)+")")),e.emit("autosave-error",{canvas:e.canvas,error:y}),!1}return d=o,e.emit("after-autosave",{canvas:e.canvas,html:o,savedAt:w.savedAt,source:t}),!0}function R(){window.clearTimeout(u),u=window.setTimeout(function(){h("change")},s.delay)}function b(){i||(i=new MutationObserver(R)),i.observe(e.canvas,{subtree:!0,childList:!0,attributes:!0,characterData:!0})}function O(){window.clearTimeout(u),u=null,i&&i.disconnect()}function I(){r==="enabled"&&h("pagehide")}function _(){if(!(r==="destroyed"||r==="unavailable")){if(d=e.snapshotHtml(),f=G(d),window.addEventListener("pagehide",I),r==="enabled"&&l&&l.base===f&&l.html!==d){T(l);return}l=null,r==="enabled"&&b()}}function F(t){try{return new Date(t).toLocaleString(e.settings.locale||void 0)}catch(a){return new Date(t).toLocaleString()}}function T(t){r="asking",e.confirm(e.t("autosave.restore_message",{date:F(t.savedAt)}),{title:e.t("autosave.restore_title"),ok:e.t("autosave.restore"),cancel:e.t("autosave.discard")},function(a){if(r!=="destroyed"){if(a===null){window.setTimeout(function(){r==="asking"&&T(t)},0);return}l=null,a?(d=t.html,e.setHtml(t.html),e.emit("after-restore-draft",{canvas:e.canvas,html:t.html,savedAt:t.savedAt})):m(),r=g?"enabled":"disabled",r==="enabled"&&b()}})}return{onInit:function(){S||(S=!0,Promise.resolve().then(_))},onSourceOpen:function(){p=!0},onSourceClose:function(){p=!1},onDestroy:function(){r==="enabled"&&u&&h("destroy"),O(),i=null,window.removeEventListener("pagehide",I),r!=="unavailable"&&D.delete(n),r="destroyed"},methods:{enableAutosave:function(){return r==="unavailable"?!1:r==="asking"?(g=!0,!0):(r!=="enabled"&&(r="enabled",f!==null&&b()),!0)},disableAutosave:function(){return r==="unavailable"?!1:r==="asking"?(g=!1,!0):(O(),r="disabled",!0)},saveDraft:function(){return r==="unavailable"||r==="asking"?!1:h("api")},getDraft:function(){if(r==="unavailable")return null;var t=E(!1);return t?{html:t.html,savedAt:t.savedAt}:null},clearDraft:function(){return r==="unavailable"?!1:(m(),!0)}}}};})();
1
+ (()=>{var v=window.GridEditor;if(!v||typeof v.get!="function")throw new Error("grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, its locales and grideditor.jquery.js");Object.assign(v.locales.en,{"autosave.restore_title":"Restore the draft?","autosave.restore_message":"There is a draft of this page saved on {date}, with changes that were not published. Restore it?","autosave.restore":"Restore","autosave.discard":"Discard"});var L=1,A={local:"localStorage",session:"sessionStorage"},k=1e3,D=new Set;function F(e){for(var s=2166136261,n=0;n<e.length;n++)s^=e.charCodeAt(n),s=Math.imul(s,16777619);return(s>>>0).toString(16).padStart(8,"0")}function G(e){return!!e&&typeof e=="object"&&e.version===L&&typeof e.html=="string"&&typeof e.base=="string"&&typeof e.savedAt=="string"&&!isNaN(Date.parse(e.savedAt))}v.features.autosave=function(e){var s=j(e.settings.autosave||{}),n=s.key||"grideditor.autosave:"+window.location.pathname+"#"+e.canvas.id,m=N(s.storage),r="disabled",p=!0,S=!1,u=null,f=null,d=null,l=null,o=null,h=!1,x=!1;m?D.has(n)?(e.warn('autosave: another editor on this page already saves to "'+n+'", so this one does not; give each its own autosave.key'),r="unavailable"):(D.add(n),s.enabled&&(r="enabled",u=E(!0))):(e.warn("autosave: the browser will not give this page its "+A[s.storage]+", so nothing is saved"),r="unavailable");function j(t){var a={enabled:t.enabled===void 0?!0:!!t.enabled,storage:t.storage===void 0?"local":t.storage,key:t.key===void 0?null:t.key,delay:t.delay===void 0?k:t.delay,maxAge:t.maxAge===void 0?null:t.maxAge};return A[a.storage]||(e.warn('autosave.storage "'+a.storage+'" is not local or session: local is used'),a.storage="local"),a.key!==null&&(typeof a.key!="string"||a.key==="")&&(e.warn("autosave.key must be a string: the page's own key is used"),a.key=null),(typeof a.delay!="number"||!Number.isInteger(a.delay)||a.delay<0)&&(e.warn("autosave.delay must be a whole number of milliseconds, 0 or more: "+k+" is used"),a.delay=k),a.maxAge!==null&&!(typeof a.maxAge=="number"&&isFinite(a.maxAge)&&a.maxAge>0)&&(e.warn("autosave.maxAge must be a number of milliseconds over 0, or null: drafts do not expire"),a.maxAge=null),a}function N(t){try{var a=window[A[t]];return a.getItem(n),a}catch(i){return null}}function E(t){var a;try{a=m.getItem(n)}catch(y){return null}if(a===null)return null;var i=null;try{i=JSON.parse(a)}catch(y){}return G(i)?t&&s.maxAge!==null&&Date.now()-Date.parse(i.savedAt)>s.maxAge?(b(),null):i:(t&&(e.warn('autosave: what is saved under "'+n+'" is not a draft this version can read: it is discarded'),b()),null)}function b(){try{m.removeItem(n)}catch(t){}}function w(t){if(window.clearTimeout(l),l=null,h||f===null)return!1;var a=!!o;a&&o.disconnect();var i=e.snapshotHtml();if(a&&c(),i===d)return!1;var y={version:L,html:i,savedAt:new Date().toISOString(),base:f};try{m.setItem(n,JSON.stringify(y))}catch(g){return x||(x=!0,e.warn("autosave: the draft could not be saved ("+(g&&g.message||g)+")")),e.emit("autosave-error",{canvas:e.canvas,error:g}),!1}return d=i,e.emit("after-autosave",{canvas:e.canvas,html:i,savedAt:y.savedAt,source:t}),!0}function H(){window.clearTimeout(l),l=window.setTimeout(function(){w("change")},s.delay)}function c(){o||(o=new MutationObserver(H)),o.observe(e.canvas,{subtree:!0,childList:!0,attributes:!0,characterData:!0})}function O(){window.clearTimeout(l),l=null,o&&o.disconnect()}function T(){r==="enabled"&&w("pagehide")}function R(){if(!(r==="destroyed"||r==="unavailable")){if(d=e.snapshotHtml(),f=F(d),window.addEventListener("pagehide",T),r==="enabled"&&u&&u.base===f&&u.html!==d){I(u);return}u=null,r==="enabled"&&c()}}function _(t){try{return new Date(t).toLocaleString(e.settings.locale||void 0)}catch(a){return new Date(t).toLocaleString()}}function I(t){r="asking",e.confirm(e.t("autosave.restore_message",{date:_(t.savedAt)}),{title:e.t("autosave.restore_title"),ok:e.t("autosave.restore"),cancel:e.t("autosave.discard")},function(a){if(r!=="destroyed"){if(a===null){window.setTimeout(function(){r==="asking"&&I(t)},0);return}u=null,a?(d=t.html,e.setHtml(t.html),e.emit("after-restore-draft",{canvas:e.canvas,html:t.html,savedAt:t.savedAt})):b(),r=p?"enabled":"disabled",r==="enabled"&&c()}})}return{onInit:function(){S||(S=!0,Promise.resolve().then(R))},onSourceOpen:function(){h=!0},onSourceClose:function(){h=!1},onDestroy:function(){r==="enabled"&&l&&w("destroy"),O(),o=null,window.removeEventListener("pagehide",T),r!=="unavailable"&&D.delete(n),r="destroyed"},methods:{enableAutosave:function(){return r==="unavailable"?!1:r==="asking"?(p=!0,!0):(r!=="enabled"&&(r="enabled",f!==null&&c()),!0)},disableAutosave:function(){return r==="unavailable"?!1:r==="asking"?(p=!1,!0):(O(),r="disabled",!0)},saveDraft:function(){return r==="unavailable"||r==="asking"?!1:w("api")},getDraft:function(){if(r==="unavailable")return null;var t=E(!1);return t?{html:t.html,savedAt:t.savedAt}:null},clearDraft:function(){if(r==="unavailable")return!1;if(window.clearTimeout(l),l=null,b(),f!==null&&r!=="asking"&&!h){var t=!!o&&r==="enabled";t&&o.disconnect(),d=e.snapshotHtml(),t&&c()}return!0}}}};})();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/js/register.js", "../../src/js/plugins/grideditor.autosave.js"],
4
- "sourcesContent": ["/**\n * What a plugin's or a locale's classic script imports in place of the\n * editor: the GridEditor the page already loaded.\n *\n * The build points `import { GridEditor } from '../grideditor.js'` here for\n * the classic scripts, so each registers on the editor the page has rather\n * than bringing its own. Loaded before the editor there is nothing to\n * register on, and saying so is better than a TypeError three calls later.\n */\nvar GridEditor = window.GridEditor;\n\nif (!GridEditor || typeof GridEditor.get !== 'function') {\n throw new Error('grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, ' +\n 'its locales and grideditor.jquery.js');\n}\n\nexport { GridEditor };\nexport default GridEditor;\n", "/**\n * Autosave for grid-editor.\n *\n * A feature plugin: load this file after the editor and what is being edited\n * is kept in the browser's storage as it changes, and offered back the next\n * time an editor starts from the same html. What it can ask the editor for is\n * the handle its factory is called with, described in docs/plugins.md.\n *\n * <script src=\"dist/grideditor.min.js\"></script>\n * <script src=\"dist/plugins/grideditor.autosave.min.js\"></script>\n *\n * It reads the canvas with ge.snapshotHtml(), which leaves the canvas\n * editing: the text being typed into stays open, with its cursor. A save\n * comes `delay` ms after the last change, and when the page is left.\n *\n * A draft remembers the html its editor started from, and is only offered\n * to an editor that starts from the same html: a page that loads one\n * document after another is not offered one document's draft over another.\n * Such a page gives each document a key of its own, and each keeps its draft.\n *\n * new GridEditor('#myGrid', { autosave: { key: 'page-42', storage: 'session' } });\n */\nimport { GridEditor } from '../grideditor.js';\n\nObject.assign(GridEditor.locales.en, {\n 'autosave.restore_title': 'Restore the draft?',\n 'autosave.restore_message': 'There is a draft of this page saved on {date}, with changes that were not published. Restore it?',\n 'autosave.restore': 'Restore',\n 'autosave.discard': 'Discard',\n});\n\nvar VERSION = 1;\nvar STORAGES = { local: 'localStorage', session: 'sessionStorage' };\nvar DEFAULT_DELAY = 1000;\n\n/** The keys the editors on this page save to: one editor to a key. */\nvar liveKeys = new Set();\n\n/** FNV-1a, 32 bits, as hex: enough to tell one document's html from another's. */\nfunction fingerprint(text) {\n var hash = 0x811c9dc5;\n\n for (var i = 0; i < text.length; i++) {\n hash ^= text.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/** A stored value that is a draft this file wrote, or null. */\nfunction validDraft(value) {\n return !!value && typeof value === 'object' && value.version === VERSION &&\n typeof value.html === 'string' && typeof value.base === 'string' &&\n typeof value.savedAt === 'string' && !isNaN(Date.parse(value.savedAt));\n}\n\nGridEditor.features.autosave = function(ge) {\n\n var options = optionsFrom(ge.settings.autosave || {});\n var key = options.key || 'grideditor.autosave:' + window.location.pathname + '#' + ge.canvas.id;\n var storage = storageFor(options.storage);\n\n // unavailable | disabled | asking | enabled | destroyed\n var state = 'disabled';\n var enabledAfterAsking = true; // What asking ends in: a method may have changed it meanwhile\n var started = false; // Whether the first init has been looked at\n var draft = null; // The draft found when the editor was made, while it may be offered\n var base = null; // The fingerprint of the html the editor started from\n var last = null; // The html saved last, or the one there was to start from\n var timer = null;\n var observer = null;\n var sourceOpen = false;\n var warnedWrite = false;\n\n if (!storage) {\n ge.warn('autosave: the browser will not give this page its ' + STORAGES[options.storage] +\n ', so nothing is saved');\n state = 'unavailable';\n } else if (liveKeys.has(key)) {\n ge.warn('autosave: another editor on this page already saves to \"' + key + '\", so this one does not; ' +\n 'give each its own autosave.key');\n state = 'unavailable';\n } else {\n liveKeys.add(key);\n if (options.enabled) {\n state = 'enabled';\n draft = readDraft(true);\n }\n }\n\n function optionsFrom(given) {\n var result = {\n enabled: given.enabled === undefined ? true : !!given.enabled,\n storage: given.storage === undefined ? 'local' : given.storage,\n key: given.key === undefined ? null : given.key,\n delay: given.delay === undefined ? DEFAULT_DELAY : given.delay,\n maxAge: given.maxAge === undefined ? null : given.maxAge,\n };\n\n if (!STORAGES[result.storage]) {\n ge.warn('autosave.storage \"' + result.storage + '\" is not local or session: local is used');\n result.storage = 'local';\n }\n if (result.key !== null && (typeof result.key !== 'string' || result.key === '')) {\n ge.warn('autosave.key must be a string: the page\\'s own key is used');\n result.key = null;\n }\n if (typeof result.delay !== 'number' || !Number.isInteger(result.delay) || result.delay < 0) {\n ge.warn('autosave.delay must be a whole number of milliseconds, 0 or more: ' + DEFAULT_DELAY + ' is used');\n result.delay = DEFAULT_DELAY;\n }\n if (result.maxAge !== null && !(typeof result.maxAge === 'number' && isFinite(result.maxAge) && result.maxAge > 0)) {\n ge.warn('autosave.maxAge must be a number of milliseconds over 0, or null: drafts do not expire');\n result.maxAge = null;\n }\n\n return result;\n }\n\n /** The storage, if the browser lets the page have it. */\n function storageFor(name) {\n try {\n var found = window[STORAGES[name]];\n found.getItem(key);\n return found;\n } catch (error) {\n return null;\n }\n }\n\n /**\n * The draft saved under the key, or null. Starting, one that cannot be\n * read or has expired is taken away: nothing will ever offer it.\n */\n function readDraft(starting) {\n var raw;\n\n try {\n raw = storage.getItem(key);\n } catch (error) {\n return null;\n }\n if (raw === null) { return null; }\n\n var value = null;\n try { value = JSON.parse(raw); } catch (error) { /* not ours, or mangled */ }\n\n if (!validDraft(value)) {\n if (starting) {\n ge.warn('autosave: what is saved under \"' + key + '\" is not a draft this version can read: it is discarded');\n remove();\n }\n return null;\n }\n\n if (starting && options.maxAge !== null && Date.now() - Date.parse(value.savedAt) > options.maxAge) {\n remove();\n return null;\n }\n\n return value;\n }\n\n function remove() {\n try { storage.removeItem(key); } catch (error) { /* nothing to take away */ }\n }\n\n /** Write the canvas as it is, if it changed. True when it was written. */\n function save(source) {\n window.clearTimeout(timer);\n timer = null;\n\n if (sourceOpen || base === null) { return false; }\n\n // The read itself may change the canvas - getHtml, when a plugin\n // cannot be read on a copy - and that is not a change to save\n var watching = !!observer;\n if (watching) { observer.disconnect(); }\n var html = ge.snapshotHtml();\n if (watching) { watch(); }\n\n if (html === last) { return false; }\n\n var value = { version: VERSION, html: html, savedAt: new Date().toISOString(), base: base };\n\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch (error) {\n if (!warnedWrite) {\n warnedWrite = true;\n ge.warn('autosave: the draft could not be saved (' + (error && error.message || error) + ')');\n }\n ge.emit('autosave-error', { canvas: ge.canvas, error: error });\n return false;\n }\n\n last = html;\n ge.emit('after-autosave', { canvas: ge.canvas, html: html, savedAt: value.savedAt, source: source });\n return true;\n }\n\n function schedule() {\n window.clearTimeout(timer);\n timer = window.setTimeout(function() { save('change'); }, options.delay);\n }\n\n function watch() {\n if (!observer) { observer = new MutationObserver(schedule); }\n observer.observe(ge.canvas, { subtree: true, childList: true, attributes: true, characterData: true });\n }\n\n function unwatch() {\n window.clearTimeout(timer);\n timer = null;\n if (observer) { observer.disconnect(); }\n }\n\n function onPageHide() {\n if (state === 'enabled') { save('pagehide'); }\n }\n\n /** After the first init has finished: what there is to start from, and the draft offered. */\n function start() {\n if (state === 'destroyed' || state === 'unavailable') { return; }\n\n last = ge.snapshotHtml();\n base = fingerprint(last);\n window.addEventListener('pagehide', onPageHide);\n\n // One of another document, or the same as what is there, is not offered\n if (state === 'enabled' && draft && draft.base === base && draft.html !== last) {\n ask(draft);\n return;\n }\n\n draft = null;\n if (state === 'enabled') { watch(); }\n }\n\n function dateOf(savedAt) {\n try {\n return new Date(savedAt).toLocaleString(ge.settings.locale || undefined);\n } catch (error) {\n return new Date(savedAt).toLocaleString();\n }\n }\n\n function ask(found) {\n state = 'asking';\n\n ge.confirm(ge.t('autosave.restore_message', { date: dateOf(found.savedAt) }), {\n title: ge.t('autosave.restore_title'),\n ok: ge.t('autosave.restore'),\n cancel: ge.t('autosave.discard'),\n }, function(restore) {\n if (state === 'destroyed') { return; }\n\n // Taken away unanswered - setLocale - so asked again, in the new\n // language, once the old modal is out of the way\n if (restore === null) {\n window.setTimeout(function() {\n if (state === 'asking') { ask(found); }\n }, 0);\n return;\n }\n\n draft = null;\n\n if (restore) {\n last = found.html;\n ge.setHtml(found.html);\n ge.emit('after-restore-draft', { canvas: ge.canvas, html: found.html, savedAt: found.savedAt });\n } else {\n remove();\n }\n\n state = enabledAfterAsking ? 'enabled' : 'disabled';\n if (state === 'enabled') { watch(); }\n });\n }\n\n return {\n // The first init, once it has finished: the canvas is ready to be read\n onInit: function() {\n if (started) { return; }\n started = true;\n Promise.resolve().then(start);\n },\n\n onSourceOpen: function() { sourceOpen = true; },\n onSourceClose: function() { sourceOpen = false; },\n\n onDestroy: function() {\n if (state === 'enabled' && timer) { save('destroy'); }\n\n unwatch();\n observer = null;\n window.removeEventListener('pagehide', onPageHide);\n if (state !== 'unavailable') { liveKeys.delete(key); }\n state = 'destroyed';\n },\n\n methods: {\n enableAutosave: function() {\n if (state === 'unavailable') { return false; }\n if (state === 'asking') {\n enabledAfterAsking = true;\n return true;\n }\n if (state !== 'enabled') {\n state = 'enabled';\n if (base !== null) { watch(); }\n }\n return true;\n },\n\n disableAutosave: function() {\n if (state === 'unavailable') { return false; }\n if (state === 'asking') {\n enabledAfterAsking = false;\n return true;\n }\n unwatch();\n state = 'disabled';\n return true;\n },\n\n saveDraft: function() {\n if (state === 'unavailable' || state === 'asking') { return false; }\n return save('api');\n },\n\n getDraft: function() {\n if (state === 'unavailable') { return null; }\n\n var found = readDraft(false);\n return found ? { html: found.html, savedAt: found.savedAt } : null;\n },\n\n clearDraft: function() {\n if (state === 'unavailable') { return false; }\n\n remove();\n return true;\n },\n },\n };\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * What a plugin's or a locale's classic script imports in place of the\n * editor: the GridEditor the page already loaded.\n *\n * The build points `import { GridEditor } from '../grideditor.js'` here for\n * the classic scripts, so each registers on the editor the page has rather\n * than bringing its own. Loaded before the editor there is nothing to\n * register on, and saying so is better than a TypeError three calls later.\n */\nvar GridEditor = window.GridEditor;\n\nif (!GridEditor || typeof GridEditor.get !== 'function') {\n throw new Error('grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, ' +\n 'its locales and grideditor.jquery.js');\n}\n\nexport { GridEditor };\nexport default GridEditor;\n", "/**\n * Autosave for grid-editor.\n *\n * A feature plugin: load this file after the editor and what is being edited\n * is kept in the browser's storage as it changes, and offered back the next\n * time an editor starts from the same html. What it can ask the editor for is\n * the handle its factory is called with, described in docs/plugins.md.\n *\n * <script src=\"dist/grideditor.min.js\"></script>\n * <script src=\"dist/plugins/grideditor.autosave.min.js\"></script>\n *\n * It reads the canvas with ge.snapshotHtml(), which leaves the canvas\n * editing: the text being typed into stays open, with its cursor. A save\n * comes `delay` ms after the last change, and when the page is left.\n *\n * A draft remembers the html its editor started from, and is only offered\n * to an editor that starts from the same html: a page that loads one\n * document after another is not offered one document's draft over another.\n * Such a page gives each document a key of its own, and each keeps its draft.\n *\n * new GridEditor('#myGrid', { autosave: { key: 'page-42', storage: 'session' } });\n */\nimport { GridEditor } from '../grideditor.js';\n\nObject.assign(GridEditor.locales.en, {\n 'autosave.restore_title': 'Restore the draft?',\n 'autosave.restore_message': 'There is a draft of this page saved on {date}, with changes that were not published. Restore it?',\n 'autosave.restore': 'Restore',\n 'autosave.discard': 'Discard',\n});\n\nvar VERSION = 1;\nvar STORAGES = { local: 'localStorage', session: 'sessionStorage' };\nvar DEFAULT_DELAY = 1000;\n\n/** The keys the editors on this page save to: one editor to a key. */\nvar liveKeys = new Set();\n\n/** FNV-1a, 32 bits, as hex: enough to tell one document's html from another's. */\nfunction fingerprint(text) {\n var hash = 0x811c9dc5;\n\n for (var i = 0; i < text.length; i++) {\n hash ^= text.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/** A stored value that is a draft this file wrote, or null. */\nfunction validDraft(value) {\n return !!value && typeof value === 'object' && value.version === VERSION &&\n typeof value.html === 'string' && typeof value.base === 'string' &&\n typeof value.savedAt === 'string' && !isNaN(Date.parse(value.savedAt));\n}\n\nGridEditor.features.autosave = function(ge) {\n\n var options = optionsFrom(ge.settings.autosave || {});\n var key = options.key || 'grideditor.autosave:' + window.location.pathname + '#' + ge.canvas.id;\n var storage = storageFor(options.storage);\n\n // unavailable | disabled | asking | enabled | destroyed\n var state = 'disabled';\n var enabledAfterAsking = true; // What asking ends in: a method may have changed it meanwhile\n var started = false; // Whether the first init has been looked at\n var draft = null; // The draft found when the editor was made, while it may be offered\n var base = null; // The fingerprint of the html the editor started from\n var last = null; // The html saved last, or the one there was to start from\n var timer = null;\n var observer = null;\n var sourceOpen = false;\n var warnedWrite = false;\n\n if (!storage) {\n ge.warn('autosave: the browser will not give this page its ' + STORAGES[options.storage] +\n ', so nothing is saved');\n state = 'unavailable';\n } else if (liveKeys.has(key)) {\n ge.warn('autosave: another editor on this page already saves to \"' + key + '\", so this one does not; ' +\n 'give each its own autosave.key');\n state = 'unavailable';\n } else {\n liveKeys.add(key);\n if (options.enabled) {\n state = 'enabled';\n draft = readDraft(true);\n }\n }\n\n function optionsFrom(given) {\n var result = {\n enabled: given.enabled === undefined ? true : !!given.enabled,\n storage: given.storage === undefined ? 'local' : given.storage,\n key: given.key === undefined ? null : given.key,\n delay: given.delay === undefined ? DEFAULT_DELAY : given.delay,\n maxAge: given.maxAge === undefined ? null : given.maxAge,\n };\n\n if (!STORAGES[result.storage]) {\n ge.warn('autosave.storage \"' + result.storage + '\" is not local or session: local is used');\n result.storage = 'local';\n }\n if (result.key !== null && (typeof result.key !== 'string' || result.key === '')) {\n ge.warn('autosave.key must be a string: the page\\'s own key is used');\n result.key = null;\n }\n if (typeof result.delay !== 'number' || !Number.isInteger(result.delay) || result.delay < 0) {\n ge.warn('autosave.delay must be a whole number of milliseconds, 0 or more: ' + DEFAULT_DELAY + ' is used');\n result.delay = DEFAULT_DELAY;\n }\n if (result.maxAge !== null && !(typeof result.maxAge === 'number' && isFinite(result.maxAge) && result.maxAge > 0)) {\n ge.warn('autosave.maxAge must be a number of milliseconds over 0, or null: drafts do not expire');\n result.maxAge = null;\n }\n\n return result;\n }\n\n /** The storage, if the browser lets the page have it. */\n function storageFor(name) {\n try {\n var found = window[STORAGES[name]];\n found.getItem(key);\n return found;\n } catch (error) {\n return null;\n }\n }\n\n /**\n * The draft saved under the key, or null. Starting, one that cannot be\n * read or has expired is taken away: nothing will ever offer it.\n */\n function readDraft(starting) {\n var raw;\n\n try {\n raw = storage.getItem(key);\n } catch (error) {\n return null;\n }\n if (raw === null) { return null; }\n\n var value = null;\n try { value = JSON.parse(raw); } catch (error) { /* not ours, or mangled */ }\n\n if (!validDraft(value)) {\n if (starting) {\n ge.warn('autosave: what is saved under \"' + key + '\" is not a draft this version can read: it is discarded');\n remove();\n }\n return null;\n }\n\n if (starting && options.maxAge !== null && Date.now() - Date.parse(value.savedAt) > options.maxAge) {\n remove();\n return null;\n }\n\n return value;\n }\n\n function remove() {\n try { storage.removeItem(key); } catch (error) { /* nothing to take away */ }\n }\n\n /** Write the canvas as it is, if it changed. True when it was written. */\n function save(source) {\n window.clearTimeout(timer);\n timer = null;\n\n if (sourceOpen || base === null) { return false; }\n\n // The read itself may change the canvas - getHtml, when a plugin\n // cannot be read on a copy - and that is not a change to save\n var watching = !!observer;\n if (watching) { observer.disconnect(); }\n var html = ge.snapshotHtml();\n if (watching) { watch(); }\n\n if (html === last) { return false; }\n\n var value = { version: VERSION, html: html, savedAt: new Date().toISOString(), base: base };\n\n try {\n storage.setItem(key, JSON.stringify(value));\n } catch (error) {\n if (!warnedWrite) {\n warnedWrite = true;\n ge.warn('autosave: the draft could not be saved (' + (error && error.message || error) + ')');\n }\n ge.emit('autosave-error', { canvas: ge.canvas, error: error });\n return false;\n }\n\n last = html;\n ge.emit('after-autosave', { canvas: ge.canvas, html: html, savedAt: value.savedAt, source: source });\n return true;\n }\n\n function schedule() {\n window.clearTimeout(timer);\n timer = window.setTimeout(function() { save('change'); }, options.delay);\n }\n\n function watch() {\n if (!observer) { observer = new MutationObserver(schedule); }\n observer.observe(ge.canvas, { subtree: true, childList: true, attributes: true, characterData: true });\n }\n\n function unwatch() {\n window.clearTimeout(timer);\n timer = null;\n if (observer) { observer.disconnect(); }\n }\n\n function onPageHide() {\n if (state === 'enabled') { save('pagehide'); }\n }\n\n /** After the first init has finished: what there is to start from, and the draft offered. */\n function start() {\n if (state === 'destroyed' || state === 'unavailable') { return; }\n\n last = ge.snapshotHtml();\n base = fingerprint(last);\n window.addEventListener('pagehide', onPageHide);\n\n // One of another document, or the same as what is there, is not offered\n if (state === 'enabled' && draft && draft.base === base && draft.html !== last) {\n ask(draft);\n return;\n }\n\n draft = null;\n if (state === 'enabled') { watch(); }\n }\n\n function dateOf(savedAt) {\n try {\n return new Date(savedAt).toLocaleString(ge.settings.locale || undefined);\n } catch (error) {\n return new Date(savedAt).toLocaleString();\n }\n }\n\n function ask(found) {\n state = 'asking';\n\n ge.confirm(ge.t('autosave.restore_message', { date: dateOf(found.savedAt) }), {\n title: ge.t('autosave.restore_title'),\n ok: ge.t('autosave.restore'),\n cancel: ge.t('autosave.discard'),\n }, function(restore) {\n if (state === 'destroyed') { return; }\n\n // Taken away unanswered - setLocale - so asked again, in the new\n // language, once the old modal is out of the way\n if (restore === null) {\n window.setTimeout(function() {\n if (state === 'asking') { ask(found); }\n }, 0);\n return;\n }\n\n draft = null;\n\n if (restore) {\n last = found.html;\n ge.setHtml(found.html);\n ge.emit('after-restore-draft', { canvas: ge.canvas, html: found.html, savedAt: found.savedAt });\n } else {\n remove();\n }\n\n state = enabledAfterAsking ? 'enabled' : 'disabled';\n if (state === 'enabled') { watch(); }\n });\n }\n\n return {\n // The first init, once it has finished: the canvas is ready to be read\n onInit: function() {\n if (started) { return; }\n started = true;\n Promise.resolve().then(start);\n },\n\n onSourceOpen: function() { sourceOpen = true; },\n onSourceClose: function() { sourceOpen = false; },\n\n onDestroy: function() {\n if (state === 'enabled' && timer) { save('destroy'); }\n\n unwatch();\n observer = null;\n window.removeEventListener('pagehide', onPageHide);\n if (state !== 'unavailable') { liveKeys.delete(key); }\n state = 'destroyed';\n },\n\n methods: {\n enableAutosave: function() {\n if (state === 'unavailable') { return false; }\n if (state === 'asking') {\n enabledAfterAsking = true;\n return true;\n }\n if (state !== 'enabled') {\n state = 'enabled';\n if (base !== null) { watch(); }\n }\n return true;\n },\n\n disableAutosave: function() {\n if (state === 'unavailable') { return false; }\n if (state === 'asking') {\n enabledAfterAsking = false;\n return true;\n }\n unwatch();\n state = 'disabled';\n return true;\n },\n\n saveDraft: function() {\n if (state === 'unavailable' || state === 'asking') { return false; }\n return save('api');\n },\n\n getDraft: function() {\n if (state === 'unavailable') { return null; }\n\n var found = readDraft(false);\n return found ? { html: found.html, savedAt: found.savedAt } : null;\n },\n\n // What there is now is not a draft any more - a page calls this\n // once it has saved to its server - so the save waiting does not\n // write it back, and only a change from here makes a new draft\n clearDraft: function() {\n if (state === 'unavailable') { return false; }\n\n window.clearTimeout(timer);\n timer = null;\n remove();\n\n // Not while it asks: the canvas is not what was offered yet\n if (base !== null && state !== 'asking' && !sourceOpen) {\n var watching = !!observer && state === 'enabled';\n if (watching) { observer.disconnect(); }\n last = ge.snapshotHtml();\n if (watching) { watch(); }\n }\n return true;\n },\n },\n };\n};\n"],
5
+ "mappings": "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",
6
6
  "names": ["GridEditor", "GridEditor", "VERSION", "STORAGES", "DEFAULT_DELAY", "liveKeys", "fingerprint", "text", "hash", "i", "validDraft", "value", "ge", "options", "optionsFrom", "key", "storage", "storageFor", "state", "enabledAfterAsking", "started", "draft", "base", "last", "timer", "observer", "sourceOpen", "warnedWrite", "readDraft", "given", "result", "name", "found", "error", "starting", "raw", "remove", "save", "source", "watching", "html", "watch", "schedule", "unwatch", "onPageHide", "start", "ask", "dateOf", "savedAt", "restore"]
7
7
  }
package/docs/plugins.md CHANGED
@@ -166,6 +166,7 @@ element, and where there may be several, an array.
166
166
  | `ge.snapshotHtml()` | The canvas's markup as `getHtml` gives it, read on a copy so the canvas never leaves editing: what `getHtml({ keepEditing: true })` is. See [Reading on a copy](#reading-on-a-copy) |
167
167
  | `ge.setHtml(html)` | The canvas made of other markup, and edited again: the source view closed if it is open, `deinit`, the markup in, `init`. No add or delete events: it is replaced as a whole. A `<script>` in it does not run in the editor |
168
168
  | `ge.confirm(message, options, answer)` | A question in the editor's confirm modal, the one deleting asks in. `options`: `title`, `ok`, `cancel` - the texts, the locale's by default - and `danger`, a red ok button. `answer(true)` for ok, `answer(false)` for cancel, the close button or Escape, `answer(null)` when the modal is taken away unanswered: `setLocale`, `destroy`, or another question. The browser's `confirm` without Bootstrap's javascript |
169
+ | `ge.refreshToolbar()` | The toolbar built again, from what the feature plugins' `toolbar`s hold now: for a plugin whose buttons come and go. The category shown with `toolbar_groups`, the preview and the source view stay as they are. Does nothing before the toolbar is first built, and after `destroy` |
169
170
  | `ge.toolbarItems(name)` | An array of the toolbar buttons the feature plugin `name` declared, for showing and hiding them |
170
171
  | `ge.bareStyle(node, family, property)` | A css property's value on the node with none of the family's classes: what a preview shows when no class applies and that is not a constant |
171
172
  | `ge.hostStyle(node, property?)` | `{ value, priority }` of one property of the node's own style, as the host wrote it: never a breakpoint preview's. With no property, the whole of it as css text |
@@ -231,6 +232,37 @@ needs to put a new kind of block on the canvas:
231
232
  was dropped, or, when that region will not have it, on the canvas just after
232
233
  the block it was dropped in. A `create()` that returns `null` adds nothing:
233
234
  that is how a plugin says it could not make one this time.
235
+ - **`toolbar`** can also be a function returning those items, called whenever
236
+ the toolbar is built - when the editor starts, on `setLocale`, and on
237
+ `ge.refreshToolbar()`. That is how a plugin offers buttons that change: a
238
+ user's saved widgets, each a button that adds it again. Change the list,
239
+ then call `ge.refreshToolbar()`. A button not yet built again still makes
240
+ what it was built from.
241
+
242
+ ```javascript
243
+ GridEditor.features.widgets = function(ge) {
244
+ var saved = loadWidgets(); // the plugin's own: wherever it keeps them
245
+ return {
246
+ methods: {
247
+ saveWidget: function(widget) {
248
+ saved.push(widget);
249
+ ge.refreshToolbar();
250
+ },
251
+ },
252
+ toolbar: function() {
253
+ return saved.map(function(widget) {
254
+ return {
255
+ label: widget.name,
256
+ kind: 'element',
257
+ group: 'widgets',
258
+ inColumn: true,
259
+ create: function() { return widgetFrom(widget.html); },
260
+ };
261
+ });
262
+ },
263
+ };
264
+ };
265
+ ```
234
266
 
235
267
  - **`drawerTools(drawer, node, kind)`** runs for every drawer that has a gear,
236
268
  as a utility plugin's does, so a tool can go on any node. The clipboard
package/package.json CHANGED
@@ -30,10 +30,9 @@
30
30
  },
31
31
  "homepage": "https://github.com/themarioga/grid-editor#readme",
32
32
  "publishConfig": {
33
- "comment": "A scoped package publishes restricted unless it is told otherwise.",
34
33
  "access": "public"
35
34
  },
36
- "version": "8.6.0",
35
+ "version": "8.7.1",
37
36
  "main": "dist/grideditor.js",
38
37
  "module": "dist/grideditor.esm.js",
39
38
  "types": "dist/grideditor.d.ts",
@@ -437,6 +437,7 @@ function build(instance, baseElem, optionsOrMethod) {
437
437
  var closeOverflowMenu = function() {}; // Folds the toolbar's overflow menu away, when it has one
438
438
  var refitToolbar = function() {}; // Works out again what the toolbar's overflow menu holds, when it has one
439
439
  var addButtons = []; // The toolbar's add buttons, in toolbar order: { button, home, group }
440
+ var shownToolbar = {}; // Each feature plugin's toolbar items, as the toolbar was last built with them
440
441
  var toolbarGroup = null; // The category shown, with toolbar_groups. Kept when the toolbar is rebuilt
441
442
  var warnedHere = {}; // Some warnings are worth saying once per instance, not once per call
442
443
  var sortables = []; // Every list made sortable, so deinit destroys exactly those
@@ -1189,8 +1190,10 @@ function build(instance, baseElem, optionsOrMethod) {
1189
1190
  // One with align 'end' goes on the right instead, as an icon,
1190
1191
  // beside the source and preview buttons.
1191
1192
  var endItems = [];
1193
+ shownToolbar = {};
1192
1194
  Object.keys(FEATURES).forEach(function(name) {
1193
- (FEATURES[name].toolbar || []).forEach(function(item, index) {
1195
+ shownToolbar[name] = toolbarOf(FEATURES[name]);
1196
+ shownToolbar[name].forEach(function(item, index) {
1194
1197
  var button = featureButton(name, item, index);
1195
1198
 
1196
1199
  if (item.align === 'end') {
@@ -1355,6 +1358,34 @@ function build(instance, baseElem, optionsOrMethod) {
1355
1358
  }
1356
1359
 
1357
1360
  /** The toolbar off the page, with what keeps its overflow menu up to date. */
1361
+ /** A feature plugin's toolbar items: its array, or what its function returns now. */
1362
+ function toolbarOf(feature) {
1363
+ var items = typeof feature.toolbar === 'function' ? feature.toolbar() : feature.toolbar;
1364
+ return Array.isArray(items) ? items : [];
1365
+ }
1366
+
1367
+ /**
1368
+ * ge.refreshToolbar(): the toolbar built again, from what the plugins'
1369
+ * toolbars hold now - a plugin whose buttons come and go calls it when
1370
+ * they have. The category shown, the preview and the source view stay
1371
+ * as they are; an open overflow menu folds away.
1372
+ */
1373
+ function refreshToolbar() {
1374
+ // Not built yet - the first one reads the toolbars as they are
1375
+ // then - or taken away for good
1376
+ if (!mainControls || destroyed) { return; }
1377
+
1378
+ var previewing = !!dom.one(mainControls, '.gm-preview.active');
1379
+
1380
+ removeMainControls();
1381
+ createMainControls();
1382
+
1383
+ if (previewing) {
1384
+ dom.addClass(dom.one(mainControls, '.gm-preview'), 'active btn-danger');
1385
+ }
1386
+ onScroll();
1387
+ }
1388
+
1358
1389
  function removeMainControls() {
1359
1390
  if (toolbarLifetime) { toolbarLifetime.abort(); }
1360
1391
  toolbarLifetime = null;
@@ -1655,7 +1686,10 @@ function build(instance, baseElem, optionsOrMethod) {
1655
1686
  * to the canvas, just after the top level block the pointer was in.
1656
1687
  */
1657
1688
  function insertFeatureFromToolbar(button, where, source) {
1658
- var item = FEATURES[button.getAttribute('data-ge-feature')].toolbar[parseInt(button.getAttribute('data-ge-item'), 10)];
1689
+ // The item the button was built from, whatever the plugin's list
1690
+ // says now: it may have changed since, and the toolbar not yet
1691
+ var item = (shownToolbar[button.getAttribute('data-ge-feature')] || [])[parseInt(button.getAttribute('data-ge-item'), 10)];
1692
+ if (!item) { return null; }
1659
1693
  var made = item.create();
1660
1694
  if (!made) { return null; }
1661
1695
  var placed = made;
@@ -2470,6 +2504,8 @@ function build(instance, baseElem, optionsOrMethod) {
2470
2504
  // A node's drawer: its first child, or for a content area the
2471
2505
  // one beside it in its text block
2472
2506
  drawerOf: drawerOf,
2507
+ // The toolbar built again, from the plugins' toolbars as they are now
2508
+ refreshToolbar: refreshToolbar,
2473
2509
  toolbarItems: function(name) {
2474
2510
  return mainControls
2475
2511
  ? dom.all(mainControls, '[data-ge-toolbar="feature"][data-ge-feature="' + name + '"]')
@@ -338,10 +338,23 @@ GridEditor.features.autosave = function(ge) {
338
338
  return found ? { html: found.html, savedAt: found.savedAt } : null;
339
339
  },
340
340
 
341
+ // What there is now is not a draft any more - a page calls this
342
+ // once it has saved to its server - so the save waiting does not
343
+ // write it back, and only a change from here makes a new draft
341
344
  clearDraft: function() {
342
345
  if (state === 'unavailable') { return false; }
343
346
 
347
+ window.clearTimeout(timer);
348
+ timer = null;
344
349
  remove();
350
+
351
+ // Not while it asks: the canvas is not what was offered yet
352
+ if (base !== null && state !== 'asking' && !sourceOpen) {
353
+ var watching = !!observer && state === 'enabled';
354
+ if (watching) { observer.disconnect(); }
355
+ last = ge.snapshotHtml();
356
+ if (watching) { watch(); }
357
+ }
345
358
  return true;
346
359
  },
347
360
  },