From c6d7af050cb078d96423a1379311bc9de899be94 Mon Sep 17 00:00:00 2001 From: caub Date: Wed, 27 Sep 2023 22:56:17 +0200 Subject: [PATCH 1/9] return cached content and use onLoad for new content --- src/index.js | 25 ++++++++++++------------- src/index.spec.js | 34 ++++++++++++++++++++++++---------- src/store.js | 10 +++++----- src/store.spec.js | 4 +++- 4 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/index.js b/src/index.js index 053cbac..34f2cc6 100644 --- a/src/index.js +++ b/src/index.js @@ -118,29 +118,28 @@ class Wurd { // Check for cached sections const cachedContent = store.load(sections, { lang }); - const uncachedSections = sections.filter(section => cachedContent[section] === undefined); + const uncachedSections = cachedContent._expired + ? sections + : sections.filter(section => cachedContent[section] === undefined); if (debug) console.info('Wurd: from cache:', sections.filter(section => cachedContent[section] !== undefined)); - // Return now if all content was in cache - if (uncachedSections.length === 0) { - // Pass main content Block to callbacks - if (onLoad) onLoad(content); - return Promise.resolve(content); - } - - // Otherwise fetch remaining sections - return this._fetchSections(uncachedSections) + // If missing sections, refetch in background + if (uncachedSections.length) { + this._fetchSections(uncachedSections) .then(result => { // Cache for next time store.save(result, { lang }); // Pass main content Block to callbacks - if (onLoad) onLoad(content); - - return content; + if (onLoad) onLoad(store.get()); }); + } + + // Return content in all case + if (onLoad) onLoad(content); + return content; } _fetchSections(sectionNames) { diff --git a/src/index.spec.js b/src/index.spec.js index f7142e7..832d2f9 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -87,7 +87,8 @@ describe('Wurd', function() { }); it('resolves the main content Block', function (done) { - client.load(['lorem', 'ipsum']) + Promise.resolve() + .then(() => client.load(['lorem', 'ipsum'])) .then(content => { test.ok(content instanceof Block); @@ -99,8 +100,9 @@ describe('Wurd', function() { }); it('loads content from cache and server', function (done) { - client.load(['lorem','ipsum','dolor','amet']) - .then(content => { + Promise.resolve() + .then(() => client.load(['lorem','ipsum','dolor','amet'])) + .then(async content => { // Should only call to server for missing sections same(client._fetchSections.callCount, 1); test.deepEqual(client._fetchSections.args[0][0], ['ipsum', 'amet']); @@ -113,21 +115,31 @@ describe('Wurd', function() { // Should return the main content Block test.deepEqual(content.get(), { lorem: { title: 'Lorem' }, - ipsum: { title: 'Ipsum' }, dolor: { title: 'Dolor' }, - amet: { title: 'Amet' } }); // Should pass the main content Block to the onLoad() callback same(client.onLoad.callCount, 1); same(client.onLoad.args[0][0], content); + // wait another cycle + await new Promise(r => setTimeout(r, 2)); + + same(client.onLoad.callCount, 2); + test.deepStrictEqual(client.onLoad.args[1][0], { + lorem: { title: 'Lorem' }, + dolor: { title: 'Dolor' }, + ipsum: { title: 'Ipsum' }, + amet: { title: 'Amet' } + }); + done(); }).catch(done); }); it('does not fetch from server if all content is available', function (done) { - client.load(['lorem', 'dolor']) + Promise.resolve() + .then(() => client.load(['lorem', 'dolor'])) .then(content => { // Should not call to server same(client._fetchSections.callCount, 0); @@ -151,10 +163,11 @@ describe('Wurd', function() { }); it('works with an array of sectionNames', function (done) { - client.load(['lorem', 'ipsum']) + Promise.resolve() + .then(() => client.load(['lorem', 'ipsum'])) .then(content => { test.deepEqual(content.get('lorem.title'), 'Lorem'); - test.deepEqual(content.get('ipsum.title'), 'Ipsum'); + test.deepEqual(content.get('ipsum.title'), undefined); test.deepEqual(console.info.args, [ ['Wurd: from cache:', ['lorem']], @@ -166,10 +179,11 @@ describe('Wurd', function() { }) it('works with a comma separated string', function (done) { - client.load('dolor,amet') + Promise.resolve() + .then(() => client.load('dolor,amet')) .then(content => { test.deepEqual(content.get('dolor.title'), 'Dolor'); - test.deepEqual(content.get('amet.title'), 'Amet'); + test.deepEqual(content.get('amet.title'), undefined); test.deepEqual(console.info.args, [ ['Wurd: from cache:', ['dolor']], diff --git a/src/store.js b/src/store.js index 0afae65..739b5bd 100644 --- a/src/store.js +++ b/src/store.js @@ -40,14 +40,14 @@ export default class Store { const cachedContent = JSON.parse(localStorage.getItem(storageKey)); const metaData = cachedContent && cachedContent._wurd; - // Check if it has expired - if (!cachedContent || !metaData || (metaData.savedAt + ttl) < Date.now()) { + // Check it's in the correct language + if (!cachedContent || !metaData || metaData.lang !== lang) { return rawContent; } - // Check it's in the correct language - if (metaData.lang !== lang) { - return rawContent; + // Check if it has expired + if ((metaData.savedAt + ttl) < Date.now()) { + rawContent._expired = true; } // Remove metadata diff --git a/src/store.spec.js b/src/store.spec.js index 603ecfd..51d76a9 100644 --- a/src/store.spec.js +++ b/src/store.spec.js @@ -124,7 +124,7 @@ describe('store', function() { }); }); - it('returns memory content if localStorage has expired', function () { + it('returns all content if localStorage has expired and add _expired flag', function () { global.localStorage.getItem.returns(JSON.stringify({ b: { a: 'BA' }, _wurd: { @@ -134,6 +134,8 @@ describe('store', function() { test.deepEqual(store.load(), { a: { a: 'AA' }, + b: { a: 'BA' }, + _expired: true, }); }); From 80eb18644e887d1ebfcc4c6348079188b2e52415 Mon Sep 17 00:00:00 2001 From: Cyril Auburtin Date: Tue, 3 Sep 2024 18:01:42 +0200 Subject: [PATCH 2/9] update assets --- dist/wurd.js | 289 ++++++++++++++++++++--------------------------- dist/wurd.min.js | 2 +- 2 files changed, 124 insertions(+), 167 deletions(-) diff --git a/dist/wurd.js b/dist/wurd.js index 1e9c67e..15fe391 100644 --- a/dist/wurd.js +++ b/dist/wurd.js @@ -4,70 +4,62 @@ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.wurd = factory()); })(this, (function () { 'use strict'; - function ownKeys(object, enumerableOnly) { - var keys = Object.keys(object); - - if (Object.getOwnPropertySymbols) { - var symbols = Object.getOwnPropertySymbols(object); - enumerableOnly && (symbols = symbols.filter(function (sym) { - return Object.getOwnPropertyDescriptor(object, sym).enumerable; - })), keys.push.apply(keys, symbols); - } - - return keys; + function _classCallCheck(a, n) { + if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } - - function _objectSpread2(target) { - for (var i = 1; i < arguments.length; i++) { - var source = null != arguments[i] ? arguments[i] : {}; - i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { - _defineProperty(target, key, source[key]); - }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { - Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); - }); + function _defineProperties(e, r) { + for (var t = 0; t < r.length; t++) { + var o = r[t]; + o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } - - return target; } - - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } + function _createClass(e, r, t) { + return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { + writable: !1 + }), e; } - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } + function _defineProperty(e, r, t) { + return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { + value: t, + enumerable: !0, + configurable: !0, + writable: !0 + }) : e[r] = t, e; } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - Object.defineProperty(Constructor, "prototype", { - writable: false - }); - return Constructor; + function ownKeys(e, r) { + var t = Object.keys(e); + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e); + r && (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable; + })), t.push.apply(t, o); + } + return t; } - - function _defineProperty(obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true + function _objectSpread2(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {}; + r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]); + }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); - } else { - obj[key] = value; } - - return obj; + return e; + } + function _toPrimitive(t, r) { + if ("object" != typeof t || !t) return t; + var e = t[Symbol.toPrimitive]; + if (void 0 !== e) { + var i = e.call(t, r || "default"); + if ("object" != typeof i) return i; + throw new TypeError("@@toPrimitive must return a primitive value."); + } + return ("string" === r ? String : Number)(t); + } + function _toPropertyKey(t) { + var i = _toPrimitive(t, "string"); + return "symbol" == typeof i ? i : i + ""; } /** @@ -82,6 +74,7 @@ }); return parts.join('&'); } + /** * Replaces {{mustache}} style placeholders in text with variables * @@ -90,7 +83,6 @@ * * @return {String} */ - function replaceVars(text) { var vars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (typeof text !== 'string') return text; @@ -107,25 +99,21 @@ */ function Store() { var _opts$ttl; - var rawContent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - _classCallCheck(this, Store); - this.rawContent = rawContent; this.storageKey = opts.storageKey || 'wurdContent'; this.ttl = (_opts$ttl = opts.ttl) !== null && _opts$ttl !== void 0 ? _opts$ttl : 3600000; } + /** * Get a specific piece of content, top-level or nested * * @param {String} path e.g. 'section','section.subSection','a.b.c.d' * @return {Mixed} */ - - - _createClass(Store, [{ + return _createClass(Store, [{ key: "get", value: function get(path) { if (!path) return this.rawContent; @@ -133,6 +121,7 @@ return acc && acc[k]; }, this.rawContent); } + /** * Load top-level sections of content from localStorage * @@ -142,34 +131,33 @@ * @param {String} [options.lang] Language * @return {Object} content */ - }, { key: "load", value: function load(sectionNames) { var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, - lang = _ref.lang; - + lang = _ref.lang; var rawContent = this.rawContent, - storageKey = this.storageKey, - ttl = this.ttl; - + storageKey = this.storageKey, + ttl = this.ttl; try { // Find cached content var cachedContent = JSON.parse(localStorage.getItem(storageKey)); - var metaData = cachedContent && cachedContent._wurd; // Check if it has expired + var metaData = cachedContent && cachedContent._wurd; + // Check if it has expired if (!cachedContent || !metaData || metaData.savedAt + ttl < Date.now()) { return rawContent; - } // Check it's in the correct language - + } + // Check it's in the correct language if (metaData.lang !== lang) { return rawContent; - } // Remove metadata - + } - delete cachedContent['_wurd']; // Add cached content to memory content + // Remove metadata + delete cachedContent['_wurd']; + // Add cached content to memory content Object.assign(rawContent, cachedContent); return rawContent; } catch (err) { @@ -177,21 +165,20 @@ return rawContent; } } + /** * Save top-level sections of content to localStorage * * @param {Object} sections * @param {Boolean} [options.cache] Whether to save the content to cache */ - }, { key: "save", value: function save(sections) { var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, - lang = _ref2.lang; - + lang = _ref2.lang; var rawContent = this.rawContent, - storageKey = this.storageKey; + storageKey = this.storageKey; Object.assign(rawContent, sections); localStorage.setItem(storageKey, JSON.stringify(_objectSpread2(_objectSpread2({}, rawContent), {}, { _wurd: { @@ -200,41 +187,40 @@ } }))); } + /** * Clears the localStorage cache */ - }, { key: "clear", value: function clear() { localStorage.removeItem(this.storageKey); } }]); - - return Store; }(); var Block = /*#__PURE__*/function () { function Block(wurd, path) { var _this = this; - _classCallCheck(this, Block); - this.wurd = wurd; - this.path = path; // Private shortcut to the main content getter + this.path = path; + + // Private shortcut to the main content getter // TODO: Make a proper private variable // See http://voidcanvas.com/es6-private-variables/ - but could require Babel Polyfill to be included + this._get = wurd.store.get.bind(wurd.store); - this._get = wurd.store.get.bind(wurd.store); // Bind methods to the instance to enable 'this' to be available + // Bind methods to the instance to enable 'this' to be available // to own methods and added helper methods; // This also allows object destructuring, for example: // `const {text} = wurd.block('home')` - var methodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(this)); methodNames.forEach(function (name) { _this[name] = _this[name].bind(_this); }); } + /** * Gets the ID of a child content item by path (e.g. id('item') returns `block.item`) * @@ -242,14 +228,13 @@ * * @return {String} */ - - - _createClass(Block, [{ + return _createClass(Block, [{ key: "id", value: function id(path) { if (!path) return this.path; return this.path ? [this.path, path].join('.') : path; } + /** * Gets a content item by path (e.g. `section.item`). * Will return both text and/or objects, depending on the contents of the item @@ -258,23 +243,21 @@ * * @return {Mixed} */ - }, { key: "get", value: function get(path) { - var result = this._get(this.id(path)); // If an item is missing, check that the section has been loaded - + var result = this._get(this.id(path)); + // If an item is missing, check that the section has been loaded if (typeof result === 'undefined' && this.wurd.draft) { var section = path.split('.')[0]; - if (!this._get(section)) { console.warn("Tried to access unloaded section: ".concat(section)); } } - return result; } + /** * Gets text content of an item by path (e.g. `section.item`). * If the item is not a string, e.g. you have passed the path of an object, @@ -285,27 +268,23 @@ * * @return {Mixed} */ - }, { key: "text", value: function text(path, vars) { var text = this.get(path); - if (typeof text === 'undefined') { return this.wurd.draft ? "[".concat(path, "]") : ''; } - if (typeof text !== 'string') { console.warn("Tried to get object as string: ".concat(path)); return this.wurd.draft ? "[".concat(path, "]") : ''; } - if (vars) { text = replaceVars(text, vars); } - return text; } + /** * Gets HTML from Markdown content of an item by path (e.g. `section.item`). * If the item is not a string, e.g. you have passed the path of an object, @@ -317,51 +296,44 @@ * * @return {Mixed} */ - }, { key: "markdown", value: function markdown(path, vars, opts) { var _this$wurd$markdown = this.wurd.markdown, - parse = _this$wurd$markdown.parse, - parseInline = _this$wurd$markdown.parseInline; + parse = _this$wurd$markdown.parse, + parseInline = _this$wurd$markdown.parseInline; var text = this.text(path, vars); - if (opts !== null && opts !== void 0 && opts.inline && parseInline) { return parseInline(text); } - if (parse) { return parse(text); } - return text; } + /** * Iterates over a collection / list object with the given callback. * * @param {String} path * @param {Function} fn Callback function with signature ({Function} itemBlock, {Number} index) */ - }, { key: "map", value: function map(path, fn) { var _this2 = this; - var listContent = this.get(path) || _defineProperty({}, Date.now(), {}); - var index = 0; var keys = Object.keys(listContent).sort(); return keys.map(function (key) { var currentIndex = index; index++; var itemPath = [path, key].join('.'); - var itemBlock = _this2.block(itemPath); - return fn.call(undefined, itemBlock, currentIndex); }); } + /** * Creates a new Block scoped to the child content. * Optionally runs a callback with the block as the argument @@ -371,19 +343,17 @@ * * @return {Block} */ - }, { key: "block", value: function block(path, fn) { var blockPath = this.id(path); var childBlock = new Block(this.wurd, blockPath); - if (typeof fn === 'function') { return fn.call(undefined, childBlock); } - return childBlock; } + /** * Returns an HTML string for an editable element. * @@ -400,7 +370,6 @@ * * @return {String} */ - }, { key: "el", value: function el(path, vars) { @@ -408,15 +377,14 @@ var id = this.id(path); var text = options.markdown ? this.markdown(path, vars) : this.text(path, vars); var editor = vars || options.markdown ? 'data-wurd-md' : 'data-wurd'; - if (this.wurd.draft) { var type = options.type || 'span'; if (options.markdown) type = 'div'; return "<".concat(type, " ").concat(editor, "=\"").concat(id, "\">").concat(text, ""); } - return text; } + /** * Returns the block helpers, bound to the block instance. * This is useful if using object destructuring for shortcuts, @@ -424,7 +392,6 @@ * * @return {Object} */ - /* helpers(path) { const block = path ? this.block(path) : this; @@ -437,10 +404,7 @@ return boundMethods; } */ - }]); - - return Block; }(); var Wurd = /*#__PURE__*/function () { @@ -449,22 +413,21 @@ */ function Wurd(appName) { var _this = this; - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - _classCallCheck(this, Wurd); - this.widgetUrl = 'https://widget.wurd.io/widget.js'; this.apiUrl = 'https://api.wurd.io'; this.store = new Store(); - this.content = new Block(this, null); // Add block shortcut methods to the main Wurd instance + this.content = new Block(this, null); + // Add block shortcut methods to the main Wurd instance var methodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(this.content)); methodNames.forEach(function (name) { _this[name] = _this.content[name].bind(_this.content); }); this.connect(appName, options); } + /** * Sets up the default connection/instance * @@ -479,91 +442,87 @@ * @param {Object} [options.rawContent] Content to populate the store with * @param {Function} [options.onLoad] Callback that runs whenever load() completes. Signature: onLoad(content) => {} */ - - - _createClass(Wurd, [{ + return _createClass(Wurd, [{ key: "connect", value: function connect(appName) { var _this2 = this; - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; this.app = appName; this.draft = false; - this.editMode = false; // Set allowed options + this.editMode = false; + // Set allowed options ['draft', 'lang', 'markdown', 'debug', 'onLoad'].forEach(function (name) { var val = options[name]; if (typeof val !== 'undefined') _this2[name] = val; - }); // Activate edit mode if required + }); + // Activate edit mode if required switch (options.editMode) { // Edit mode always on case true: this.startEditor(); break; - // Activate edit mode if the querystring contains an 'edit' parameter e.g. '?edit' + // Activate edit mode if the querystring contains an 'edit' parameter e.g. '?edit' case 'querystring': if (/[?&]edit(&|$)/.test(location.search)) { this.startEditor(); } - break; } - if (options.rawContent) { this.store.save(options.rawContent, { lang: options.lang }); } - if (options.storageKey) this.store.storageKey = options.storageKey; if (options.ttl) this.store.ttl = options.ttl; - if (options.blockHelpers) { this.setBlockHelpers(options.blockHelpers); } - return this; } + /** * Loads sections of content so that items are ready to be accessed with #get(id) * * @param {String|Array} sectionNames Top-level sections to load e.g. `main,home` */ - }, { key: "load", value: function load(sectionNames) { var app = this.app, - store = this.store, - lang = this.lang, - editMode = this.editMode, - debug = this.debug, - onLoad = this.onLoad, - content = this.content; - + store = this.store, + lang = this.lang, + editMode = this.editMode, + debug = this.debug, + onLoad = this.onLoad, + content = this.content; if (!app) { return Promise.reject(new Error('Use wurd.connect(appName) before wurd.load()')); - } // Normalise string sectionNames to array - + } - var sections = typeof sectionNames === 'string' ? sectionNames.split(',') : sectionNames; // When in editMode we skip the cache completely + // Normalise string sectionNames to array + var sections = typeof sectionNames === 'string' ? sectionNames.split(',') : sectionNames; + // When in editMode we skip the cache completely if (editMode) { return this._fetchSections(sections).then(function (result) { store.save(result, { lang: lang - }); // Clear the cache so changes are reflected immediately when out of editMode + }); - store.clear(); // Pass main content Block to callbacks + // Clear the cache so changes are reflected immediately when out of editMode + store.clear(); + // Pass main content Block to callbacks if (onLoad) onLoad(content); return content; }); - } // Check for cached sections - + } + // Check for cached sections var cachedContent = store.load(sections, { lang: lang }); @@ -572,21 +531,23 @@ }); if (debug) console.info('Wurd: from cache:', sections.filter(function (section) { return cachedContent[section] !== undefined; - })); // Return now if all content was in cache + })); + // Return now if all content was in cache if (uncachedSections.length === 0) { // Pass main content Block to callbacks if (onLoad) onLoad(content); return Promise.resolve(content); - } // Otherwise fetch remaining sections - + } + // Otherwise fetch remaining sections return this._fetchSections(uncachedSections).then(function (result) { // Cache for next time store.save(result, { lang: lang - }); // Pass main content Block to callbacks + }); + // Pass main content Block to callbacks if (onLoad) onLoad(content); return content; }); @@ -595,12 +556,13 @@ key: "_fetchSections", value: function _fetchSections(sectionNames) { var _this3 = this; - var app = this.app, - debug = this.debug; // Some sections not in cache; fetch them from server + debug = this.debug; - if (debug) console.info('Wurd: from server:', sectionNames); // Build request URL + // Some sections not in cache; fetch them from server + if (debug) console.info('Wurd: from server:', sectionNames); + // Build request URL var params = ['draft', 'lang'].reduce(function (memo, param) { if (_this3[param]) memo[param] = _this3[param]; return memo; @@ -629,25 +591,22 @@ key: "startEditor", value: function startEditor() { var app = this.app, - lang = this.lang; // Draft mode is always on if in edit mode + lang = this.lang; + // Draft mode is always on if in edit mode this.editMode = true; this.draft = true; var script = document.createElement('script'); script.src = this.widgetUrl; script.async = true; script.setAttribute('data-app', app); - if (lang) { script.setAttribute('data-lang', lang); } - var prevScript = document.body.querySelector("script[src=\"".concat(this.widgetUrl, "\"]")); - if (prevScript) { document.body.removeChild(prevScript); } - document.body.appendChild(script); } }, { @@ -656,8 +615,6 @@ Object.assign(Block.prototype, helpers); } }]); - - return Wurd; }(); var instance = new Wurd(); instance.Wurd = Wurd; diff --git a/dist/wurd.min.js b/dist/wurd.min.js index fb500bb..69e164b 100644 --- a/dist/wurd.min.js +++ b/dist/wurd.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wurd=e()}(this,(function(){"use strict";function t(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.push.apply(r,n)}return r}function e(e){for(var r=1;r0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};r(this,t),this.rawContent=n,this.storageKey=o.storageKey||"wurdContent",this.ttl=null!==(e=o.ttl)&&void 0!==e?e:36e5}return o(t,[{key:"get",value:function(t){return t?t.split(".").reduce((function(t,e){return t&&t[e]}),this.rawContent):this.rawContent}},{key:"load",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=e.lang,n=this.rawContent,o=this.storageKey,i=this.ttl;try{var a=JSON.parse(localStorage.getItem(o)),c=a&&a._wurd;return!a||!c||c.savedAt+i1&&void 0!==arguments[1]?arguments[1]:{},n=r.lang,o=this.rawContent,i=this.storageKey;Object.assign(o,t),localStorage.setItem(i,JSON.stringify(e(e({},o),{},{_wurd:{savedAt:Date.now(),lang:n}})))}},{key:"clear",value:function(){localStorage.removeItem(this.storageKey)}}]),t}(),c=function(){function t(e,n){var o=this;r(this,t),this.wurd=e,this.path=n,this._get=e.store.get.bind(e.store),Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((function(t){o[t]=o[t].bind(o)}))}return o(t,[{key:"id",value:function(t){return t?this.path?[this.path,t].join("."):t:this.path}},{key:"get",value:function(t){var e=this._get(this.id(t));if(void 0===e&&this.wurd.draft){var r=t.split(".")[0];this._get(r)||console.warn("Tried to access unloaded section: ".concat(r))}return e}},{key:"text",value:function(t,e){var r=this.get(t);return void 0===r?this.wurd.draft?"[".concat(t,"]"):"":"string"!=typeof r?(console.warn("Tried to get object as string: ".concat(t)),this.wurd.draft?"[".concat(t,"]"):""):(e&&(r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"!=typeof t?t:t.replace(/{{([\w.-]+)}}/g,(function(t,r){return e[r]||""}))}(r,e)),r)}},{key:"markdown",value:function(t,e,r){var n=this.wurd.markdown,o=n.parse,i=n.parseInline,a=this.text(t,e);return null!=r&&r.inline&&i?i(a):o?o(a):a}},{key:"map",value:function(t,e){var r=this,n=this.get(t)||i({},Date.now(),{}),o=0;return Object.keys(n).sort().map((function(n){var i=o;o++;var a=[t,n].join("."),c=r.block(a);return e.call(void 0,c,i)}))}},{key:"block",value:function(e,r){var n=this.id(e),o=new t(this.wurd,n);return"function"==typeof r?r.call(void 0,o):o}},{key:"el",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this.id(t),o=r.markdown?this.markdown(t,e):this.text(t,e),i=e||r.markdown?"data-wurd-md":"data-wurd";if(this.wurd.draft){var a=r.type||"span";return r.markdown&&(a="div"),"<".concat(a," ").concat(i,'="').concat(n,'">').concat(o,"")}return o}}]),t}(),s=function(){function t(e){var n=this,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};r(this,t),this.widgetUrl="https://widget.wurd.io/widget.js",this.apiUrl="https://api.wurd.io",this.store=new a,this.content=new c(this,null);var i=Object.getOwnPropertyNames(Object.getPrototypeOf(this.content));i.forEach((function(t){n[t]=n.content[t].bind(n.content)})),this.connect(e,o)}return o(t,[{key:"connect",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(this.app=t,this.draft=!1,this.editMode=!1,["draft","lang","markdown","debug","onLoad"].forEach((function(t){var n=r[t];void 0!==n&&(e[t]=n)})),r.editMode){case!0:this.startEditor();break;case"querystring":/[?&]edit(&|$)/.test(location.search)&&this.startEditor()}return r.rawContent&&this.store.save(r.rawContent,{lang:r.lang}),r.storageKey&&(this.store.storageKey=r.storageKey),r.ttl&&(this.store.ttl=r.ttl),r.blockHelpers&&this.setBlockHelpers(r.blockHelpers),this}},{key:"load",value:function(t){var e=this.app,r=this.store,n=this.lang,o=this.editMode,i=this.debug,a=this.onLoad,c=this.content;if(!e)return Promise.reject(new Error("Use wurd.connect(appName) before wurd.load()"));var s="string"==typeof t?t.split(","):t;if(o)return this._fetchSections(s).then((function(t){return r.save(t,{lang:n}),r.clear(),a&&a(c),c}));var u=r.load(s,{lang:n}),l=s.filter((function(t){return void 0===u[t]}));return i&&console.info("Wurd: from cache:",s.filter((function(t){return void 0!==u[t]}))),0===l.length?(a&&a(c),Promise.resolve(c)):this._fetchSections(l).then((function(t){return r.save(t,{lang:n}),a&&a(c),c}))}},{key:"_fetchSections",value:function(t){var e=this,r=this.app;this.debug&&console.info("Wurd: from server:",t);var n,o=["draft","lang"].reduce((function(t,r){return e[r]&&(t[r]=e[r]),t}),{}),i="".concat(this.apiUrl,"/apps/").concat(r,"/content/").concat(t,"?").concat((n=o,Object.keys(n).map((function(t){var e=n[t];return encodeURIComponent(t)+"="+encodeURIComponent(e)})).join("&")));return this._fetch(i).then((function(e){if(e.error)throw e.error.message?new Error(e.error.message):new Error("Error loading ".concat(t));return e}))}},{key:"_fetch",value:function(t){return fetch(t).then((function(e){if(!e.ok)throw new Error("Error loading ".concat(t,": ").concat(e.statusText));return e.json()}))}},{key:"startEditor",value:function(){var t=this.app,e=this.lang;this.editMode=!0,this.draft=!0;var r=document.createElement("script");r.src=this.widgetUrl,r.async=!0,r.setAttribute("data-app",t),e&&r.setAttribute("data-lang",e);var n=document.body.querySelector('script[src="'.concat(this.widgetUrl,'"]'));n&&document.body.removeChild(n),document.body.appendChild(r)}},{key:"setBlockHelpers",value:function(t){Object.assign(c.prototype,t)}}]),t}(),u=new s;return u.Wurd=s,u})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wurd=e()}(this,(function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.rawContent=n,this.storageKey=o.storageKey||"wurdContent",this.ttl=null!==(r=o.ttl)&&void 0!==r?r:36e5}),[{key:"get",value:function(t){return t?t.split(".").reduce((function(t,e){return t&&t[e]}),this.rawContent):this.rawContent}},{key:"load",value:function(t){var e=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey,o=this.ttl;try{var i=JSON.parse(localStorage.getItem(n)),a=i&&i._wurd;return!i||!a||a.savedAt+o1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey;Object.assign(r,t),localStorage.setItem(n,JSON.stringify(i(i({},r),{},{_wurd:{savedAt:Date.now(),lang:e}})))}},{key:"clear",value:function(){localStorage.removeItem(this.storageKey)}}])}(),s=function(){function e(r,n){var o=this;t(this,e),this.wurd=r,this.path=n,this._get=r.store.get.bind(r.store),Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((function(t){o[t]=o[t].bind(o)}))}return r(e,[{key:"id",value:function(t){return t?this.path?[this.path,t].join("."):t:this.path}},{key:"get",value:function(t){var e=this._get(this.id(t));if(void 0===e&&this.wurd.draft){var r=t.split(".")[0];this._get(r)||console.warn("Tried to access unloaded section: ".concat(r))}return e}},{key:"text",value:function(t,e){var r=this.get(t);return void 0===r?this.wurd.draft?"[".concat(t,"]"):"":"string"!=typeof r?(console.warn("Tried to get object as string: ".concat(t)),this.wurd.draft?"[".concat(t,"]"):""):(e&&(r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"!=typeof t?t:t.replace(/{{([\w.-]+)}}/g,(function(t,r){return e[r]||""}))}(r,e)),r)}},{key:"markdown",value:function(t,e,r){var n=this.wurd.markdown,o=n.parse,i=n.parseInline,a=this.text(t,e);return null!=r&&r.inline&&i?i(a):o?o(a):a}},{key:"map",value:function(t,e){var r=this,o=this.get(t)||n({},Date.now(),{}),i=0;return Object.keys(o).sort().map((function(n){var o=i;i++;var a=[t,n].join("."),c=r.block(a);return e.call(void 0,c,o)}))}},{key:"block",value:function(t,r){var n=this.id(t),o=new e(this.wurd,n);return"function"==typeof r?r.call(void 0,o):o}},{key:"el",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this.id(t),o=r.markdown?this.markdown(t,e):this.text(t,e),i=e||r.markdown?"data-wurd-md":"data-wurd";if(this.wurd.draft){var a=r.type||"span";return r.markdown&&(a="div"),"<".concat(a," ").concat(i,'="').concat(n,'">').concat(o,"")}return o}}])}(),u=function(){return r((function e(r){var n=this,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.widgetUrl="https://widget.wurd.io/widget.js",this.apiUrl="https://api.wurd.io",this.store=new c,this.content=new s(this,null),Object.getOwnPropertyNames(Object.getPrototypeOf(this.content)).forEach((function(t){n[t]=n.content[t].bind(n.content)})),this.connect(r,o)}),[{key:"connect",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(this.app=t,this.draft=!1,this.editMode=!1,["draft","lang","markdown","debug","onLoad"].forEach((function(t){var n=r[t];void 0!==n&&(e[t]=n)})),r.editMode){case!0:this.startEditor();break;case"querystring":/[?&]edit(&|$)/.test(location.search)&&this.startEditor()}return r.rawContent&&this.store.save(r.rawContent,{lang:r.lang}),r.storageKey&&(this.store.storageKey=r.storageKey),r.ttl&&(this.store.ttl=r.ttl),r.blockHelpers&&this.setBlockHelpers(r.blockHelpers),this}},{key:"load",value:function(t){var e=this.app,r=this.store,n=this.lang,o=this.editMode,i=this.debug,a=this.onLoad,c=this.content;if(!e)return Promise.reject(new Error("Use wurd.connect(appName) before wurd.load()"));var s="string"==typeof t?t.split(","):t;if(o)return this._fetchSections(s).then((function(t){return r.save(t,{lang:n}),r.clear(),a&&a(c),c}));var u=r.load(s,{lang:n}),l=s.filter((function(t){return void 0===u[t]}));return i&&console.info("Wurd: from cache:",s.filter((function(t){return void 0!==u[t]}))),0===l.length?(a&&a(c),Promise.resolve(c)):this._fetchSections(l).then((function(t){return r.save(t,{lang:n}),a&&a(c),c}))}},{key:"_fetchSections",value:function(t){var e=this,r=this.app;this.debug&&console.info("Wurd: from server:",t);var n,o=["draft","lang"].reduce((function(t,r){return e[r]&&(t[r]=e[r]),t}),{}),i="".concat(this.apiUrl,"/apps/").concat(r,"/content/").concat(t,"?").concat((n=o,Object.keys(n).map((function(t){var e=n[t];return encodeURIComponent(t)+"="+encodeURIComponent(e)})).join("&")));return this._fetch(i).then((function(e){if(e.error)throw e.error.message?new Error(e.error.message):new Error("Error loading ".concat(t));return e}))}},{key:"_fetch",value:function(t){return fetch(t).then((function(e){if(!e.ok)throw new Error("Error loading ".concat(t,": ").concat(e.statusText));return e.json()}))}},{key:"startEditor",value:function(){var t=this.app,e=this.lang;this.editMode=!0,this.draft=!0;var r=document.createElement("script");r.src=this.widgetUrl,r.async=!0,r.setAttribute("data-app",t),e&&r.setAttribute("data-lang",e);var n=document.body.querySelector('script[src="'.concat(this.widgetUrl,'"]'));n&&document.body.removeChild(n),document.body.appendChild(r)}},{key:"setBlockHelpers",value:function(t){Object.assign(s.prototype,t)}}])}(),l=new u;return l.Wurd=u,l})); From d4f0f6d44eb371d327be942ff34002020b0adb90 Mon Sep 17 00:00:00 2001 From: Cyril Auburtin Date: Sat, 19 Apr 2025 19:31:46 +0200 Subject: [PATCH 3/9] no need for _get --- src/block.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/block.js b/src/block.js index 48fad0f..c8518db 100644 --- a/src/block.js +++ b/src/block.js @@ -7,11 +7,6 @@ export default class Block { this.wurd = wurd; this.path = path; - // Private shortcut to the main content getter - // TODO: Make a proper private variable - // See http://voidcanvas.com/es6-private-variables/ - but could require Babel Polyfill to be included - this._get = wurd.store.get.bind(wurd.store); - // Bind methods to the instance to enable 'this' to be available // to own methods and added helper methods; // This also allows object destructuring, for example: @@ -45,13 +40,13 @@ export default class Block { * @return {Mixed} */ get(path) { - const result = this._get(this.id(path)); + const result = this.wurd.store.get(this.id(path)); // If an item is missing, check that the section has been loaded if (typeof result === 'undefined' && this.wurd.draft) { const section = path.split('.')[0]; - if (!this._get(section)) { + if (!this.wurd.store.get(section)) { console.warn(`Tried to access unloaded section: ${section}`); } } From 855087190bc92d3656d9a0e91dba20f3ee83222d Mon Sep 17 00:00:00 2001 From: Cyril Auburtin Date: Sat, 19 Apr 2025 19:33:16 +0200 Subject: [PATCH 4/9] lint and build --- .eslintrc | 1 - dist/wurd.cjs.js | 52 +++++++++++++++++++++--------------------------- dist/wurd.esm.js | 52 +++++++++++++++++++++--------------------------- dist/wurd.js | 49 +++++++++++++++++++-------------------------- dist/wurd.min.js | 2 +- src/index.js | 12 +++++------ 6 files changed, 74 insertions(+), 94 deletions(-) diff --git a/.eslintrc b/.eslintrc index 89cb7da..ecaee5b 100755 --- a/.eslintrc +++ b/.eslintrc @@ -152,7 +152,6 @@ "no-with": 2, "one-var": 0, "operator-assignment": 0, - "operator-linebreak": [2, "after"], "padded-blocks": 0, "quote-props": 0, "quotes": [2, "single", "avoid-escape"], diff --git a/dist/wurd.cjs.js b/dist/wurd.cjs.js index 5a5d1b5..9be31f7 100644 --- a/dist/wurd.cjs.js +++ b/dist/wurd.cjs.js @@ -71,14 +71,14 @@ class Store { const cachedContent = JSON.parse(localStorage.getItem(storageKey)); const metaData = cachedContent && cachedContent._wurd; - // Check if it has expired - if (!cachedContent || !metaData || (metaData.savedAt + ttl) < Date.now()) { + // Check it's in the correct language + if (!cachedContent || !metaData || metaData.lang !== lang) { return rawContent; } - // Check it's in the correct language - if (metaData.lang !== lang) { - return rawContent; + // Check if it has expired + if ((metaData.savedAt + ttl) < Date.now()) { + rawContent._expired = true; } // Remove metadata @@ -130,11 +130,6 @@ class Block { this.wurd = wurd; this.path = path; - // Private shortcut to the main content getter - // TODO: Make a proper private variable - // See http://voidcanvas.com/es6-private-variables/ - but could require Babel Polyfill to be included - this._get = wurd.store.get.bind(wurd.store); - // Bind methods to the instance to enable 'this' to be available // to own methods and added helper methods; // This also allows object destructuring, for example: @@ -168,13 +163,13 @@ class Block { * @return {Mixed} */ get(path) { - const result = this._get(this.id(path)); + const result = this.wurd.store.get(this.id(path)); // If an item is missing, check that the section has been loaded if (typeof result === 'undefined' && this.wurd.draft) { const section = path.split('.')[0]; - if (!this._get(section)) { + if (!this.wurd.store.get(section)) { console.warn(`Tried to access unloaded section: ${section}`); } } @@ -453,29 +448,28 @@ class Wurd { // Check for cached sections const cachedContent = store.load(sections, { lang }); - const uncachedSections = sections.filter(section => cachedContent[section] === undefined); + const uncachedSections = cachedContent._expired + ? sections + : sections.filter(section => cachedContent[section] === undefined); if (debug) console.info('Wurd: from cache:', sections.filter(section => cachedContent[section] !== undefined)); - // Return now if all content was in cache - if (uncachedSections.length === 0) { - // Pass main content Block to callbacks - if (onLoad) onLoad(content); - - return Promise.resolve(content); - } - // Otherwise fetch remaining sections - return this._fetchSections(uncachedSections) - .then(result => { - // Cache for next time - store.save(result, { lang }); + // If missing sections, refetch in background + if (uncachedSections.length) { + this._fetchSections(uncachedSections) + .then(result => { + // Cache for next time + store.save(result, { lang }); - // Pass main content Block to callbacks - if (onLoad) onLoad(content); + // Pass main content Block to callbacks + if (onLoad) onLoad(store.get()); + }); + } - return content; - }); + // Return content in all case + if (onLoad) onLoad(content); + return content; } _fetchSections(sectionNames) { diff --git a/dist/wurd.esm.js b/dist/wurd.esm.js index ff76dec..f37cc2c 100644 --- a/dist/wurd.esm.js +++ b/dist/wurd.esm.js @@ -69,14 +69,14 @@ class Store { const cachedContent = JSON.parse(localStorage.getItem(storageKey)); const metaData = cachedContent && cachedContent._wurd; - // Check if it has expired - if (!cachedContent || !metaData || (metaData.savedAt + ttl) < Date.now()) { + // Check it's in the correct language + if (!cachedContent || !metaData || metaData.lang !== lang) { return rawContent; } - // Check it's in the correct language - if (metaData.lang !== lang) { - return rawContent; + // Check if it has expired + if ((metaData.savedAt + ttl) < Date.now()) { + rawContent._expired = true; } // Remove metadata @@ -128,11 +128,6 @@ class Block { this.wurd = wurd; this.path = path; - // Private shortcut to the main content getter - // TODO: Make a proper private variable - // See http://voidcanvas.com/es6-private-variables/ - but could require Babel Polyfill to be included - this._get = wurd.store.get.bind(wurd.store); - // Bind methods to the instance to enable 'this' to be available // to own methods and added helper methods; // This also allows object destructuring, for example: @@ -166,13 +161,13 @@ class Block { * @return {Mixed} */ get(path) { - const result = this._get(this.id(path)); + const result = this.wurd.store.get(this.id(path)); // If an item is missing, check that the section has been loaded if (typeof result === 'undefined' && this.wurd.draft) { const section = path.split('.')[0]; - if (!this._get(section)) { + if (!this.wurd.store.get(section)) { console.warn(`Tried to access unloaded section: ${section}`); } } @@ -451,29 +446,28 @@ class Wurd { // Check for cached sections const cachedContent = store.load(sections, { lang }); - const uncachedSections = sections.filter(section => cachedContent[section] === undefined); + const uncachedSections = cachedContent._expired + ? sections + : sections.filter(section => cachedContent[section] === undefined); if (debug) console.info('Wurd: from cache:', sections.filter(section => cachedContent[section] !== undefined)); - // Return now if all content was in cache - if (uncachedSections.length === 0) { - // Pass main content Block to callbacks - if (onLoad) onLoad(content); - - return Promise.resolve(content); - } - // Otherwise fetch remaining sections - return this._fetchSections(uncachedSections) - .then(result => { - // Cache for next time - store.save(result, { lang }); + // If missing sections, refetch in background + if (uncachedSections.length) { + this._fetchSections(uncachedSections) + .then(result => { + // Cache for next time + store.save(result, { lang }); - // Pass main content Block to callbacks - if (onLoad) onLoad(content); + // Pass main content Block to callbacks + if (onLoad) onLoad(store.get()); + }); + } - return content; - }); + // Return content in all case + if (onLoad) onLoad(content); + return content; } _fetchSections(sectionNames) { diff --git a/dist/wurd.js b/dist/wurd.js index 15fe391..e4f856b 100644 --- a/dist/wurd.js +++ b/dist/wurd.js @@ -144,14 +144,14 @@ var cachedContent = JSON.parse(localStorage.getItem(storageKey)); var metaData = cachedContent && cachedContent._wurd; - // Check if it has expired - if (!cachedContent || !metaData || metaData.savedAt + ttl < Date.now()) { + // Check it's in the correct language + if (!cachedContent || !metaData || metaData.lang !== lang) { return rawContent; } - // Check it's in the correct language - if (metaData.lang !== lang) { - return rawContent; + // Check if it has expired + if (metaData.savedAt + ttl < Date.now()) { + rawContent._expired = true; } // Remove metadata @@ -206,11 +206,6 @@ this.wurd = wurd; this.path = path; - // Private shortcut to the main content getter - // TODO: Make a proper private variable - // See http://voidcanvas.com/es6-private-variables/ - but could require Babel Polyfill to be included - this._get = wurd.store.get.bind(wurd.store); - // Bind methods to the instance to enable 'this' to be available // to own methods and added helper methods; // This also allows object destructuring, for example: @@ -246,12 +241,12 @@ }, { key: "get", value: function get(path) { - var result = this._get(this.id(path)); + var result = this.wurd.store.get(this.id(path)); // If an item is missing, check that the section has been loaded if (typeof result === 'undefined' && this.wurd.draft) { var section = path.split('.')[0]; - if (!this._get(section)) { + if (!this.wurd.store.get(section)) { console.warn("Tried to access unloaded section: ".concat(section)); } } @@ -526,31 +521,29 @@ var cachedContent = store.load(sections, { lang: lang }); - var uncachedSections = sections.filter(function (section) { + var uncachedSections = cachedContent._expired ? sections : sections.filter(function (section) { return cachedContent[section] === undefined; }); if (debug) console.info('Wurd: from cache:', sections.filter(function (section) { return cachedContent[section] !== undefined; })); - // Return now if all content was in cache - if (uncachedSections.length === 0) { - // Pass main content Block to callbacks - if (onLoad) onLoad(content); - return Promise.resolve(content); - } + // If missing sections, refetch in background + if (uncachedSections.length) { + this._fetchSections(uncachedSections).then(function (result) { + // Cache for next time + store.save(result, { + lang: lang + }); - // Otherwise fetch remaining sections - return this._fetchSections(uncachedSections).then(function (result) { - // Cache for next time - store.save(result, { - lang: lang + // Pass main content Block to callbacks + if (onLoad) onLoad(store.get()); }); + } - // Pass main content Block to callbacks - if (onLoad) onLoad(content); - return content; - }); + // Return content in all case + if (onLoad) onLoad(content); + return content; } }, { key: "_fetchSections", diff --git a/dist/wurd.min.js b/dist/wurd.min.js index 69e164b..0f4fdde 100644 --- a/dist/wurd.min.js +++ b/dist/wurd.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wurd=e()}(this,(function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.rawContent=n,this.storageKey=o.storageKey||"wurdContent",this.ttl=null!==(r=o.ttl)&&void 0!==r?r:36e5}),[{key:"get",value:function(t){return t?t.split(".").reduce((function(t,e){return t&&t[e]}),this.rawContent):this.rawContent}},{key:"load",value:function(t){var e=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey,o=this.ttl;try{var i=JSON.parse(localStorage.getItem(n)),a=i&&i._wurd;return!i||!a||a.savedAt+o1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey;Object.assign(r,t),localStorage.setItem(n,JSON.stringify(i(i({},r),{},{_wurd:{savedAt:Date.now(),lang:e}})))}},{key:"clear",value:function(){localStorage.removeItem(this.storageKey)}}])}(),s=function(){function e(r,n){var o=this;t(this,e),this.wurd=r,this.path=n,this._get=r.store.get.bind(r.store),Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((function(t){o[t]=o[t].bind(o)}))}return r(e,[{key:"id",value:function(t){return t?this.path?[this.path,t].join("."):t:this.path}},{key:"get",value:function(t){var e=this._get(this.id(t));if(void 0===e&&this.wurd.draft){var r=t.split(".")[0];this._get(r)||console.warn("Tried to access unloaded section: ".concat(r))}return e}},{key:"text",value:function(t,e){var r=this.get(t);return void 0===r?this.wurd.draft?"[".concat(t,"]"):"":"string"!=typeof r?(console.warn("Tried to get object as string: ".concat(t)),this.wurd.draft?"[".concat(t,"]"):""):(e&&(r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"!=typeof t?t:t.replace(/{{([\w.-]+)}}/g,(function(t,r){return e[r]||""}))}(r,e)),r)}},{key:"markdown",value:function(t,e,r){var n=this.wurd.markdown,o=n.parse,i=n.parseInline,a=this.text(t,e);return null!=r&&r.inline&&i?i(a):o?o(a):a}},{key:"map",value:function(t,e){var r=this,o=this.get(t)||n({},Date.now(),{}),i=0;return Object.keys(o).sort().map((function(n){var o=i;i++;var a=[t,n].join("."),c=r.block(a);return e.call(void 0,c,o)}))}},{key:"block",value:function(t,r){var n=this.id(t),o=new e(this.wurd,n);return"function"==typeof r?r.call(void 0,o):o}},{key:"el",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this.id(t),o=r.markdown?this.markdown(t,e):this.text(t,e),i=e||r.markdown?"data-wurd-md":"data-wurd";if(this.wurd.draft){var a=r.type||"span";return r.markdown&&(a="div"),"<".concat(a," ").concat(i,'="').concat(n,'">').concat(o,"")}return o}}])}(),u=function(){return r((function e(r){var n=this,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.widgetUrl="https://widget.wurd.io/widget.js",this.apiUrl="https://api.wurd.io",this.store=new c,this.content=new s(this,null),Object.getOwnPropertyNames(Object.getPrototypeOf(this.content)).forEach((function(t){n[t]=n.content[t].bind(n.content)})),this.connect(r,o)}),[{key:"connect",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(this.app=t,this.draft=!1,this.editMode=!1,["draft","lang","markdown","debug","onLoad"].forEach((function(t){var n=r[t];void 0!==n&&(e[t]=n)})),r.editMode){case!0:this.startEditor();break;case"querystring":/[?&]edit(&|$)/.test(location.search)&&this.startEditor()}return r.rawContent&&this.store.save(r.rawContent,{lang:r.lang}),r.storageKey&&(this.store.storageKey=r.storageKey),r.ttl&&(this.store.ttl=r.ttl),r.blockHelpers&&this.setBlockHelpers(r.blockHelpers),this}},{key:"load",value:function(t){var e=this.app,r=this.store,n=this.lang,o=this.editMode,i=this.debug,a=this.onLoad,c=this.content;if(!e)return Promise.reject(new Error("Use wurd.connect(appName) before wurd.load()"));var s="string"==typeof t?t.split(","):t;if(o)return this._fetchSections(s).then((function(t){return r.save(t,{lang:n}),r.clear(),a&&a(c),c}));var u=r.load(s,{lang:n}),l=s.filter((function(t){return void 0===u[t]}));return i&&console.info("Wurd: from cache:",s.filter((function(t){return void 0!==u[t]}))),0===l.length?(a&&a(c),Promise.resolve(c)):this._fetchSections(l).then((function(t){return r.save(t,{lang:n}),a&&a(c),c}))}},{key:"_fetchSections",value:function(t){var e=this,r=this.app;this.debug&&console.info("Wurd: from server:",t);var n,o=["draft","lang"].reduce((function(t,r){return e[r]&&(t[r]=e[r]),t}),{}),i="".concat(this.apiUrl,"/apps/").concat(r,"/content/").concat(t,"?").concat((n=o,Object.keys(n).map((function(t){var e=n[t];return encodeURIComponent(t)+"="+encodeURIComponent(e)})).join("&")));return this._fetch(i).then((function(e){if(e.error)throw e.error.message?new Error(e.error.message):new Error("Error loading ".concat(t));return e}))}},{key:"_fetch",value:function(t){return fetch(t).then((function(e){if(!e.ok)throw new Error("Error loading ".concat(t,": ").concat(e.statusText));return e.json()}))}},{key:"startEditor",value:function(){var t=this.app,e=this.lang;this.editMode=!0,this.draft=!0;var r=document.createElement("script");r.src=this.widgetUrl,r.async=!0,r.setAttribute("data-app",t),e&&r.setAttribute("data-lang",e);var n=document.body.querySelector('script[src="'.concat(this.widgetUrl,'"]'));n&&document.body.removeChild(n),document.body.appendChild(r)}},{key:"setBlockHelpers",value:function(t){Object.assign(s.prototype,t)}}])}(),l=new u;return l.Wurd=u,l})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wurd=e()}(this,(function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.rawContent=n,this.storageKey=o.storageKey||"wurdContent",this.ttl=null!==(r=o.ttl)&&void 0!==r?r:36e5}),[{key:"get",value:function(t){return t?t.split(".").reduce((function(t,e){return t&&t[e]}),this.rawContent):this.rawContent}},{key:"load",value:function(t){var e=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey,o=this.ttl;try{var i=JSON.parse(localStorage.getItem(n)),a=i&&i._wurd;return i&&a&&a.lang===e?(a.savedAt+o1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey;Object.assign(r,t),localStorage.setItem(n,JSON.stringify(i(i({},r),{},{_wurd:{savedAt:Date.now(),lang:e}})))}},{key:"clear",value:function(){localStorage.removeItem(this.storageKey)}}])}(),s=function(){function e(r,n){var o=this;t(this,e),this.wurd=r,this.path=n,Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((function(t){o[t]=o[t].bind(o)}))}return r(e,[{key:"id",value:function(t){return t?this.path?[this.path,t].join("."):t:this.path}},{key:"get",value:function(t){var e=this.wurd.store.get(this.id(t));if(void 0===e&&this.wurd.draft){var r=t.split(".")[0];this.wurd.store.get(r)||console.warn("Tried to access unloaded section: ".concat(r))}return e}},{key:"text",value:function(t,e){var r=this.get(t);return void 0===r?this.wurd.draft?"[".concat(t,"]"):"":"string"!=typeof r?(console.warn("Tried to get object as string: ".concat(t)),this.wurd.draft?"[".concat(t,"]"):""):(e&&(r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"!=typeof t?t:t.replace(/{{([\w.-]+)}}/g,(function(t,r){return e[r]||""}))}(r,e)),r)}},{key:"markdown",value:function(t,e,r){var n=this.wurd.markdown,o=n.parse,i=n.parseInline,a=this.text(t,e);return null!=r&&r.inline&&i?i(a):o?o(a):a}},{key:"map",value:function(t,e){var r=this,o=this.get(t)||n({},Date.now(),{}),i=0;return Object.keys(o).sort().map((function(n){var o=i;i++;var a=[t,n].join("."),c=r.block(a);return e.call(void 0,c,o)}))}},{key:"block",value:function(t,r){var n=this.id(t),o=new e(this.wurd,n);return"function"==typeof r?r.call(void 0,o):o}},{key:"el",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this.id(t),o=r.markdown?this.markdown(t,e):this.text(t,e),i=e||r.markdown?"data-wurd-md":"data-wurd";if(this.wurd.draft){var a=r.type||"span";return r.markdown&&(a="div"),"<".concat(a," ").concat(i,'="').concat(n,'">').concat(o,"")}return o}}])}(),u=function(){return r((function e(r){var n=this,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.widgetUrl="https://widget.wurd.io/widget.js",this.apiUrl="https://api.wurd.io",this.store=new c,this.content=new s(this,null),Object.getOwnPropertyNames(Object.getPrototypeOf(this.content)).forEach((function(t){n[t]=n.content[t].bind(n.content)})),this.connect(r,o)}),[{key:"connect",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(this.app=t,this.draft=!1,this.editMode=!1,["draft","lang","markdown","debug","onLoad"].forEach((function(t){var n=r[t];void 0!==n&&(e[t]=n)})),r.editMode){case!0:this.startEditor();break;case"querystring":/[?&]edit(&|$)/.test(location.search)&&this.startEditor()}return r.rawContent&&this.store.save(r.rawContent,{lang:r.lang}),r.storageKey&&(this.store.storageKey=r.storageKey),r.ttl&&(this.store.ttl=r.ttl),r.blockHelpers&&this.setBlockHelpers(r.blockHelpers),this}},{key:"load",value:function(t){var e=this.app,r=this.store,n=this.lang,o=this.editMode,i=this.debug,a=this.onLoad,c=this.content;if(!e)return Promise.reject(new Error("Use wurd.connect(appName) before wurd.load()"));var s="string"==typeof t?t.split(","):t;if(o)return this._fetchSections(s).then((function(t){return r.save(t,{lang:n}),r.clear(),a&&a(c),c}));var u=r.load(s,{lang:n}),l=u._expired?s:s.filter((function(t){return void 0===u[t]}));return i&&console.info("Wurd: from cache:",s.filter((function(t){return void 0!==u[t]}))),l.length&&this._fetchSections(l).then((function(t){r.save(t,{lang:n}),a&&a(r.get())})),a&&a(c),c}},{key:"_fetchSections",value:function(t){var e=this,r=this.app;this.debug&&console.info("Wurd: from server:",t);var n,o=["draft","lang"].reduce((function(t,r){return e[r]&&(t[r]=e[r]),t}),{}),i="".concat(this.apiUrl,"/apps/").concat(r,"/content/").concat(t,"?").concat((n=o,Object.keys(n).map((function(t){var e=n[t];return encodeURIComponent(t)+"="+encodeURIComponent(e)})).join("&")));return this._fetch(i).then((function(e){if(e.error)throw e.error.message?new Error(e.error.message):new Error("Error loading ".concat(t));return e}))}},{key:"_fetch",value:function(t){return fetch(t).then((function(e){if(!e.ok)throw new Error("Error loading ".concat(t,": ").concat(e.statusText));return e.json()}))}},{key:"startEditor",value:function(){var t=this.app,e=this.lang;this.editMode=!0,this.draft=!0;var r=document.createElement("script");r.src=this.widgetUrl,r.async=!0,r.setAttribute("data-app",t),e&&r.setAttribute("data-lang",e);var n=document.body.querySelector('script[src="'.concat(this.widgetUrl,'"]'));n&&document.body.removeChild(n),document.body.appendChild(r)}},{key:"setBlockHelpers",value:function(t){Object.assign(s.prototype,t)}}])}(),l=new u;return l.Wurd=u,l})); diff --git a/src/index.js b/src/index.js index 34f2cc6..267f03b 100644 --- a/src/index.js +++ b/src/index.js @@ -128,13 +128,13 @@ class Wurd { // If missing sections, refetch in background if (uncachedSections.length) { this._fetchSections(uncachedSections) - .then(result => { - // Cache for next time - store.save(result, { lang }); + .then(result => { + // Cache for next time + store.save(result, { lang }); - // Pass main content Block to callbacks - if (onLoad) onLoad(store.get()); - }); + // Pass main content Block to callbacks + if (onLoad) onLoad(store.get()); + }); } // Return content in all case From 52ceaf746871ea60c56fef48a748afc4a49791f7 Mon Sep 17 00:00:00 2001 From: Cyril Auburtin Date: Thu, 24 Apr 2025 20:21:48 +0200 Subject: [PATCH 5/9] do not save _expired in cache --- src/store.js | 5 ++++- src/store.spec.js | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/store.js b/src/store.js index 739b5bd..f7f9034 100644 --- a/src/store.js +++ b/src/store.js @@ -47,7 +47,10 @@ export default class Store { // Check if it has expired if ((metaData.savedAt + ttl) < Date.now()) { - rawContent._expired = true; + Object.defineProperty(cachedContent, '_expired', { + enumerable: false, + value: true, + }); } // Remove metadata diff --git a/src/store.spec.js b/src/store.spec.js index 51d76a9..bbff3d1 100644 --- a/src/store.spec.js +++ b/src/store.spec.js @@ -135,7 +135,6 @@ describe('store', function() { test.deepEqual(store.load(), { a: { a: 'AA' }, b: { a: 'BA' }, - _expired: true, }); }); From c13ae527476790c69d79cfdfecc2011abf96686c Mon Sep 17 00:00:00 2001 From: Cyril Auburtin Date: Thu, 24 Apr 2025 20:23:51 +0200 Subject: [PATCH 6/9] rebuild --- dist/wurd.cjs.js | 5 ++++- dist/wurd.esm.js | 5 ++++- dist/wurd.js | 5 ++++- dist/wurd.min.js | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/dist/wurd.cjs.js b/dist/wurd.cjs.js index 9be31f7..5aaa6b3 100644 --- a/dist/wurd.cjs.js +++ b/dist/wurd.cjs.js @@ -78,7 +78,10 @@ class Store { // Check if it has expired if ((metaData.savedAt + ttl) < Date.now()) { - rawContent._expired = true; + Object.defineProperty(cachedContent, '_expired', { + enumerable: false, + value: true, + }); } // Remove metadata diff --git a/dist/wurd.esm.js b/dist/wurd.esm.js index f37cc2c..0f80fc4 100644 --- a/dist/wurd.esm.js +++ b/dist/wurd.esm.js @@ -76,7 +76,10 @@ class Store { // Check if it has expired if ((metaData.savedAt + ttl) < Date.now()) { - rawContent._expired = true; + Object.defineProperty(cachedContent, '_expired', { + enumerable: false, + value: true, + }); } // Remove metadata diff --git a/dist/wurd.js b/dist/wurd.js index e4f856b..59e1f49 100644 --- a/dist/wurd.js +++ b/dist/wurd.js @@ -151,7 +151,10 @@ // Check if it has expired if (metaData.savedAt + ttl < Date.now()) { - rawContent._expired = true; + Object.defineProperty(cachedContent, '_expired', { + enumerable: false, + value: true + }); } // Remove metadata diff --git a/dist/wurd.min.js b/dist/wurd.min.js index 0f4fdde..1a1d362 100644 --- a/dist/wurd.min.js +++ b/dist/wurd.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wurd=e()}(this,(function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.rawContent=n,this.storageKey=o.storageKey||"wurdContent",this.ttl=null!==(r=o.ttl)&&void 0!==r?r:36e5}),[{key:"get",value:function(t){return t?t.split(".").reduce((function(t,e){return t&&t[e]}),this.rawContent):this.rawContent}},{key:"load",value:function(t){var e=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey,o=this.ttl;try{var i=JSON.parse(localStorage.getItem(n)),a=i&&i._wurd;return i&&a&&a.lang===e?(a.savedAt+o1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey;Object.assign(r,t),localStorage.setItem(n,JSON.stringify(i(i({},r),{},{_wurd:{savedAt:Date.now(),lang:e}})))}},{key:"clear",value:function(){localStorage.removeItem(this.storageKey)}}])}(),s=function(){function e(r,n){var o=this;t(this,e),this.wurd=r,this.path=n,Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((function(t){o[t]=o[t].bind(o)}))}return r(e,[{key:"id",value:function(t){return t?this.path?[this.path,t].join("."):t:this.path}},{key:"get",value:function(t){var e=this.wurd.store.get(this.id(t));if(void 0===e&&this.wurd.draft){var r=t.split(".")[0];this.wurd.store.get(r)||console.warn("Tried to access unloaded section: ".concat(r))}return e}},{key:"text",value:function(t,e){var r=this.get(t);return void 0===r?this.wurd.draft?"[".concat(t,"]"):"":"string"!=typeof r?(console.warn("Tried to get object as string: ".concat(t)),this.wurd.draft?"[".concat(t,"]"):""):(e&&(r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"!=typeof t?t:t.replace(/{{([\w.-]+)}}/g,(function(t,r){return e[r]||""}))}(r,e)),r)}},{key:"markdown",value:function(t,e,r){var n=this.wurd.markdown,o=n.parse,i=n.parseInline,a=this.text(t,e);return null!=r&&r.inline&&i?i(a):o?o(a):a}},{key:"map",value:function(t,e){var r=this,o=this.get(t)||n({},Date.now(),{}),i=0;return Object.keys(o).sort().map((function(n){var o=i;i++;var a=[t,n].join("."),c=r.block(a);return e.call(void 0,c,o)}))}},{key:"block",value:function(t,r){var n=this.id(t),o=new e(this.wurd,n);return"function"==typeof r?r.call(void 0,o):o}},{key:"el",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this.id(t),o=r.markdown?this.markdown(t,e):this.text(t,e),i=e||r.markdown?"data-wurd-md":"data-wurd";if(this.wurd.draft){var a=r.type||"span";return r.markdown&&(a="div"),"<".concat(a," ").concat(i,'="').concat(n,'">').concat(o,"")}return o}}])}(),u=function(){return r((function e(r){var n=this,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.widgetUrl="https://widget.wurd.io/widget.js",this.apiUrl="https://api.wurd.io",this.store=new c,this.content=new s(this,null),Object.getOwnPropertyNames(Object.getPrototypeOf(this.content)).forEach((function(t){n[t]=n.content[t].bind(n.content)})),this.connect(r,o)}),[{key:"connect",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(this.app=t,this.draft=!1,this.editMode=!1,["draft","lang","markdown","debug","onLoad"].forEach((function(t){var n=r[t];void 0!==n&&(e[t]=n)})),r.editMode){case!0:this.startEditor();break;case"querystring":/[?&]edit(&|$)/.test(location.search)&&this.startEditor()}return r.rawContent&&this.store.save(r.rawContent,{lang:r.lang}),r.storageKey&&(this.store.storageKey=r.storageKey),r.ttl&&(this.store.ttl=r.ttl),r.blockHelpers&&this.setBlockHelpers(r.blockHelpers),this}},{key:"load",value:function(t){var e=this.app,r=this.store,n=this.lang,o=this.editMode,i=this.debug,a=this.onLoad,c=this.content;if(!e)return Promise.reject(new Error("Use wurd.connect(appName) before wurd.load()"));var s="string"==typeof t?t.split(","):t;if(o)return this._fetchSections(s).then((function(t){return r.save(t,{lang:n}),r.clear(),a&&a(c),c}));var u=r.load(s,{lang:n}),l=u._expired?s:s.filter((function(t){return void 0===u[t]}));return i&&console.info("Wurd: from cache:",s.filter((function(t){return void 0!==u[t]}))),l.length&&this._fetchSections(l).then((function(t){r.save(t,{lang:n}),a&&a(r.get())})),a&&a(c),c}},{key:"_fetchSections",value:function(t){var e=this,r=this.app;this.debug&&console.info("Wurd: from server:",t);var n,o=["draft","lang"].reduce((function(t,r){return e[r]&&(t[r]=e[r]),t}),{}),i="".concat(this.apiUrl,"/apps/").concat(r,"/content/").concat(t,"?").concat((n=o,Object.keys(n).map((function(t){var e=n[t];return encodeURIComponent(t)+"="+encodeURIComponent(e)})).join("&")));return this._fetch(i).then((function(e){if(e.error)throw e.error.message?new Error(e.error.message):new Error("Error loading ".concat(t));return e}))}},{key:"_fetch",value:function(t){return fetch(t).then((function(e){if(!e.ok)throw new Error("Error loading ".concat(t,": ").concat(e.statusText));return e.json()}))}},{key:"startEditor",value:function(){var t=this.app,e=this.lang;this.editMode=!0,this.draft=!0;var r=document.createElement("script");r.src=this.widgetUrl,r.async=!0,r.setAttribute("data-app",t),e&&r.setAttribute("data-lang",e);var n=document.body.querySelector('script[src="'.concat(this.widgetUrl,'"]'));n&&document.body.removeChild(n),document.body.appendChild(r)}},{key:"setBlockHelpers",value:function(t){Object.assign(s.prototype,t)}}])}(),l=new u;return l.Wurd=u,l})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wurd=e()}(this,(function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.rawContent=n,this.storageKey=o.storageKey||"wurdContent",this.ttl=null!==(r=o.ttl)&&void 0!==r?r:36e5}),[{key:"get",value:function(t){return t?t.split(".").reduce((function(t,e){return t&&t[e]}),this.rawContent):this.rawContent}},{key:"load",value:function(t){var e=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey,o=this.ttl;try{var i=JSON.parse(localStorage.getItem(n)),a=i&&i._wurd;return i&&a&&a.lang===e?(a.savedAt+o1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey;Object.assign(r,t),localStorage.setItem(n,JSON.stringify(i(i({},r),{},{_wurd:{savedAt:Date.now(),lang:e}})))}},{key:"clear",value:function(){localStorage.removeItem(this.storageKey)}}])}(),s=function(){function e(r,n){var o=this;t(this,e),this.wurd=r,this.path=n,Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((function(t){o[t]=o[t].bind(o)}))}return r(e,[{key:"id",value:function(t){return t?this.path?[this.path,t].join("."):t:this.path}},{key:"get",value:function(t){var e=this.wurd.store.get(this.id(t));if(void 0===e&&this.wurd.draft){var r=t.split(".")[0];this.wurd.store.get(r)||console.warn("Tried to access unloaded section: ".concat(r))}return e}},{key:"text",value:function(t,e){var r=this.get(t);return void 0===r?this.wurd.draft?"[".concat(t,"]"):"":"string"!=typeof r?(console.warn("Tried to get object as string: ".concat(t)),this.wurd.draft?"[".concat(t,"]"):""):(e&&(r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"!=typeof t?t:t.replace(/{{([\w.-]+)}}/g,(function(t,r){return e[r]||""}))}(r,e)),r)}},{key:"markdown",value:function(t,e,r){var n=this.wurd.markdown,o=n.parse,i=n.parseInline,a=this.text(t,e);return null!=r&&r.inline&&i?i(a):o?o(a):a}},{key:"map",value:function(t,e){var r=this,o=this.get(t)||n({},Date.now(),{}),i=0;return Object.keys(o).sort().map((function(n){var o=i;i++;var a=[t,n].join("."),c=r.block(a);return e.call(void 0,c,o)}))}},{key:"block",value:function(t,r){var n=this.id(t),o=new e(this.wurd,n);return"function"==typeof r?r.call(void 0,o):o}},{key:"el",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this.id(t),o=r.markdown?this.markdown(t,e):this.text(t,e),i=e||r.markdown?"data-wurd-md":"data-wurd";if(this.wurd.draft){var a=r.type||"span";return r.markdown&&(a="div"),"<".concat(a," ").concat(i,'="').concat(n,'">').concat(o,"")}return o}}])}(),u=function(){return r((function e(r){var n=this,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.widgetUrl="https://widget.wurd.io/widget.js",this.apiUrl="https://api.wurd.io",this.store=new c,this.content=new s(this,null),Object.getOwnPropertyNames(Object.getPrototypeOf(this.content)).forEach((function(t){n[t]=n.content[t].bind(n.content)})),this.connect(r,o)}),[{key:"connect",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(this.app=t,this.draft=!1,this.editMode=!1,["draft","lang","markdown","debug","onLoad"].forEach((function(t){var n=r[t];void 0!==n&&(e[t]=n)})),r.editMode){case!0:this.startEditor();break;case"querystring":/[?&]edit(&|$)/.test(location.search)&&this.startEditor()}return r.rawContent&&this.store.save(r.rawContent,{lang:r.lang}),r.storageKey&&(this.store.storageKey=r.storageKey),r.ttl&&(this.store.ttl=r.ttl),r.blockHelpers&&this.setBlockHelpers(r.blockHelpers),this}},{key:"load",value:function(t){var e=this.app,r=this.store,n=this.lang,o=this.editMode,i=this.debug,a=this.onLoad,c=this.content;if(!e)return Promise.reject(new Error("Use wurd.connect(appName) before wurd.load()"));var s="string"==typeof t?t.split(","):t;if(o)return this._fetchSections(s).then((function(t){return r.save(t,{lang:n}),r.clear(),a&&a(c),c}));var u=r.load(s,{lang:n}),l=u._expired?s:s.filter((function(t){return void 0===u[t]}));return i&&console.info("Wurd: from cache:",s.filter((function(t){return void 0!==u[t]}))),l.length&&this._fetchSections(l).then((function(t){r.save(t,{lang:n}),a&&a(r.get())})),a&&a(c),c}},{key:"_fetchSections",value:function(t){var e=this,r=this.app;this.debug&&console.info("Wurd: from server:",t);var n,o=["draft","lang"].reduce((function(t,r){return e[r]&&(t[r]=e[r]),t}),{}),i="".concat(this.apiUrl,"/apps/").concat(r,"/content/").concat(t,"?").concat((n=o,Object.keys(n).map((function(t){var e=n[t];return encodeURIComponent(t)+"="+encodeURIComponent(e)})).join("&")));return this._fetch(i).then((function(e){if(e.error)throw e.error.message?new Error(e.error.message):new Error("Error loading ".concat(t));return e}))}},{key:"_fetch",value:function(t){return fetch(t).then((function(e){if(!e.ok)throw new Error("Error loading ".concat(t,": ").concat(e.statusText));return e.json()}))}},{key:"startEditor",value:function(){var t=this.app,e=this.lang;this.editMode=!0,this.draft=!0;var r=document.createElement("script");r.src=this.widgetUrl,r.async=!0,r.setAttribute("data-app",t),e&&r.setAttribute("data-lang",e);var n=document.body.querySelector('script[src="'.concat(this.widgetUrl,'"]'));n&&document.body.removeChild(n),document.body.appendChild(r)}},{key:"setBlockHelpers",value:function(t){Object.assign(s.prototype,t)}}])}(),l=new u;return l.Wurd=u,l})); From ebcf1fa3e3715de7305c23a7ef9f4e558be35854 Mon Sep 17 00:00:00 2001 From: Cyril Auburtin Date: Sun, 27 Apr 2025 18:24:13 +0200 Subject: [PATCH 7/9] revert and add catch to load() --- dist/wurd.cjs.js | 54 +++++++++++++++++++++++++++--------------------- dist/wurd.esm.js | 54 +++++++++++++++++++++++++++--------------------- dist/wurd.js | 49 +++++++++++++++++++++++-------------------- dist/wurd.min.js | 2 +- src/index.js | 41 ++++++++++++++++++++++-------------- src/store.js | 13 +++++------- 6 files changed, 118 insertions(+), 95 deletions(-) diff --git a/dist/wurd.cjs.js b/dist/wurd.cjs.js index 5aaa6b3..00968b4 100644 --- a/dist/wurd.cjs.js +++ b/dist/wurd.cjs.js @@ -71,17 +71,14 @@ class Store { const cachedContent = JSON.parse(localStorage.getItem(storageKey)); const metaData = cachedContent && cachedContent._wurd; - // Check it's in the correct language - if (!cachedContent || !metaData || metaData.lang !== lang) { + // Check if it has expired + if (!cachedContent || !metaData || (metaData.savedAt + ttl) < Date.now()) { return rawContent; } - // Check if it has expired - if ((metaData.savedAt + ttl) < Date.now()) { - Object.defineProperty(cachedContent, '_expired', { - enumerable: false, - value: true, - }); + // Check it's in the correct language + if (metaData.lang !== lang) { + return rawContent; } // Remove metadata @@ -451,28 +448,37 @@ class Wurd { // Check for cached sections const cachedContent = store.load(sections, { lang }); - const uncachedSections = cachedContent._expired - ? sections - : sections.filter(section => cachedContent[section] === undefined); + const uncachedSections = sections.filter(section => cachedContent[section] === undefined); if (debug) console.info('Wurd: from cache:', sections.filter(section => cachedContent[section] !== undefined)); + // Return now if all content was in cache + if (uncachedSections.length === 0) { + // Pass main content Block to callbacks + if (onLoad) onLoad(content); - // If missing sections, refetch in background - if (uncachedSections.length) { - this._fetchSections(uncachedSections) - .then(result => { - // Cache for next time - store.save(result, { lang }); - - // Pass main content Block to callbacks - if (onLoad) onLoad(store.get()); - }); + return Promise.resolve(content); } - // Return content in all case - if (onLoad) onLoad(content); - return content; + // Otherwise fetch remaining sections + return this._fetchSections(uncachedSections) + .then(result => { + // Cache for next time + store.save(result, { lang }); + + // Pass main content Block to callbacks + if (onLoad) onLoad(content); + + return content; + }) + .catch(err => { + if (debug) console.info('Wurd: load error:', err); + + // If content fails to load (wurd app offline), still return cache + if (onLoad) onLoad(content); + + return content; + }); } _fetchSections(sectionNames) { diff --git a/dist/wurd.esm.js b/dist/wurd.esm.js index 0f80fc4..01fa73d 100644 --- a/dist/wurd.esm.js +++ b/dist/wurd.esm.js @@ -69,17 +69,14 @@ class Store { const cachedContent = JSON.parse(localStorage.getItem(storageKey)); const metaData = cachedContent && cachedContent._wurd; - // Check it's in the correct language - if (!cachedContent || !metaData || metaData.lang !== lang) { + // Check if it has expired + if (!cachedContent || !metaData || (metaData.savedAt + ttl) < Date.now()) { return rawContent; } - // Check if it has expired - if ((metaData.savedAt + ttl) < Date.now()) { - Object.defineProperty(cachedContent, '_expired', { - enumerable: false, - value: true, - }); + // Check it's in the correct language + if (metaData.lang !== lang) { + return rawContent; } // Remove metadata @@ -449,28 +446,37 @@ class Wurd { // Check for cached sections const cachedContent = store.load(sections, { lang }); - const uncachedSections = cachedContent._expired - ? sections - : sections.filter(section => cachedContent[section] === undefined); + const uncachedSections = sections.filter(section => cachedContent[section] === undefined); if (debug) console.info('Wurd: from cache:', sections.filter(section => cachedContent[section] !== undefined)); + // Return now if all content was in cache + if (uncachedSections.length === 0) { + // Pass main content Block to callbacks + if (onLoad) onLoad(content); - // If missing sections, refetch in background - if (uncachedSections.length) { - this._fetchSections(uncachedSections) - .then(result => { - // Cache for next time - store.save(result, { lang }); - - // Pass main content Block to callbacks - if (onLoad) onLoad(store.get()); - }); + return Promise.resolve(content); } - // Return content in all case - if (onLoad) onLoad(content); - return content; + // Otherwise fetch remaining sections + return this._fetchSections(uncachedSections) + .then(result => { + // Cache for next time + store.save(result, { lang }); + + // Pass main content Block to callbacks + if (onLoad) onLoad(content); + + return content; + }) + .catch(err => { + if (debug) console.info('Wurd: load error:', err); + + // If content fails to load (wurd app offline), still return cache + if (onLoad) onLoad(content); + + return content; + }); } _fetchSections(sectionNames) { diff --git a/dist/wurd.js b/dist/wurd.js index 59e1f49..29ae07d 100644 --- a/dist/wurd.js +++ b/dist/wurd.js @@ -144,17 +144,14 @@ var cachedContent = JSON.parse(localStorage.getItem(storageKey)); var metaData = cachedContent && cachedContent._wurd; - // Check it's in the correct language - if (!cachedContent || !metaData || metaData.lang !== lang) { + // Check if it has expired + if (!cachedContent || !metaData || metaData.savedAt + ttl < Date.now()) { return rawContent; } - // Check if it has expired - if (metaData.savedAt + ttl < Date.now()) { - Object.defineProperty(cachedContent, '_expired', { - enumerable: false, - value: true - }); + // Check it's in the correct language + if (metaData.lang !== lang) { + return rawContent; } // Remove metadata @@ -524,29 +521,37 @@ var cachedContent = store.load(sections, { lang: lang }); - var uncachedSections = cachedContent._expired ? sections : sections.filter(function (section) { + var uncachedSections = sections.filter(function (section) { return cachedContent[section] === undefined; }); if (debug) console.info('Wurd: from cache:', sections.filter(function (section) { return cachedContent[section] !== undefined; })); - // If missing sections, refetch in background - if (uncachedSections.length) { - this._fetchSections(uncachedSections).then(function (result) { - // Cache for next time - store.save(result, { - lang: lang - }); + // Return now if all content was in cache + if (uncachedSections.length === 0) { + // Pass main content Block to callbacks + if (onLoad) onLoad(content); + return Promise.resolve(content); + } - // Pass main content Block to callbacks - if (onLoad) onLoad(store.get()); + // Otherwise fetch remaining sections + return this._fetchSections(uncachedSections).then(function (result) { + // Cache for next time + store.save(result, { + lang: lang }); - } - // Return content in all case - if (onLoad) onLoad(content); - return content; + // Pass main content Block to callbacks + if (onLoad) onLoad(content); + return content; + })["catch"](function (err) { + if (debug) console.info('Wurd: load error:', err); + + // If content fails to load (wurd app offline), still return cache + if (onLoad) onLoad(content); + return content; + }); } }, { key: "_fetchSections", diff --git a/dist/wurd.min.js b/dist/wurd.min.js index 1a1d362..5768eb5 100644 --- a/dist/wurd.min.js +++ b/dist/wurd.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wurd=e()}(this,(function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.rawContent=n,this.storageKey=o.storageKey||"wurdContent",this.ttl=null!==(r=o.ttl)&&void 0!==r?r:36e5}),[{key:"get",value:function(t){return t?t.split(".").reduce((function(t,e){return t&&t[e]}),this.rawContent):this.rawContent}},{key:"load",value:function(t){var e=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey,o=this.ttl;try{var i=JSON.parse(localStorage.getItem(n)),a=i&&i._wurd;return i&&a&&a.lang===e?(a.savedAt+o1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey;Object.assign(r,t),localStorage.setItem(n,JSON.stringify(i(i({},r),{},{_wurd:{savedAt:Date.now(),lang:e}})))}},{key:"clear",value:function(){localStorage.removeItem(this.storageKey)}}])}(),s=function(){function e(r,n){var o=this;t(this,e),this.wurd=r,this.path=n,Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((function(t){o[t]=o[t].bind(o)}))}return r(e,[{key:"id",value:function(t){return t?this.path?[this.path,t].join("."):t:this.path}},{key:"get",value:function(t){var e=this.wurd.store.get(this.id(t));if(void 0===e&&this.wurd.draft){var r=t.split(".")[0];this.wurd.store.get(r)||console.warn("Tried to access unloaded section: ".concat(r))}return e}},{key:"text",value:function(t,e){var r=this.get(t);return void 0===r?this.wurd.draft?"[".concat(t,"]"):"":"string"!=typeof r?(console.warn("Tried to get object as string: ".concat(t)),this.wurd.draft?"[".concat(t,"]"):""):(e&&(r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"!=typeof t?t:t.replace(/{{([\w.-]+)}}/g,(function(t,r){return e[r]||""}))}(r,e)),r)}},{key:"markdown",value:function(t,e,r){var n=this.wurd.markdown,o=n.parse,i=n.parseInline,a=this.text(t,e);return null!=r&&r.inline&&i?i(a):o?o(a):a}},{key:"map",value:function(t,e){var r=this,o=this.get(t)||n({},Date.now(),{}),i=0;return Object.keys(o).sort().map((function(n){var o=i;i++;var a=[t,n].join("."),c=r.block(a);return e.call(void 0,c,o)}))}},{key:"block",value:function(t,r){var n=this.id(t),o=new e(this.wurd,n);return"function"==typeof r?r.call(void 0,o):o}},{key:"el",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this.id(t),o=r.markdown?this.markdown(t,e):this.text(t,e),i=e||r.markdown?"data-wurd-md":"data-wurd";if(this.wurd.draft){var a=r.type||"span";return r.markdown&&(a="div"),"<".concat(a," ").concat(i,'="').concat(n,'">').concat(o,"")}return o}}])}(),u=function(){return r((function e(r){var n=this,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.widgetUrl="https://widget.wurd.io/widget.js",this.apiUrl="https://api.wurd.io",this.store=new c,this.content=new s(this,null),Object.getOwnPropertyNames(Object.getPrototypeOf(this.content)).forEach((function(t){n[t]=n.content[t].bind(n.content)})),this.connect(r,o)}),[{key:"connect",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(this.app=t,this.draft=!1,this.editMode=!1,["draft","lang","markdown","debug","onLoad"].forEach((function(t){var n=r[t];void 0!==n&&(e[t]=n)})),r.editMode){case!0:this.startEditor();break;case"querystring":/[?&]edit(&|$)/.test(location.search)&&this.startEditor()}return r.rawContent&&this.store.save(r.rawContent,{lang:r.lang}),r.storageKey&&(this.store.storageKey=r.storageKey),r.ttl&&(this.store.ttl=r.ttl),r.blockHelpers&&this.setBlockHelpers(r.blockHelpers),this}},{key:"load",value:function(t){var e=this.app,r=this.store,n=this.lang,o=this.editMode,i=this.debug,a=this.onLoad,c=this.content;if(!e)return Promise.reject(new Error("Use wurd.connect(appName) before wurd.load()"));var s="string"==typeof t?t.split(","):t;if(o)return this._fetchSections(s).then((function(t){return r.save(t,{lang:n}),r.clear(),a&&a(c),c}));var u=r.load(s,{lang:n}),l=u._expired?s:s.filter((function(t){return void 0===u[t]}));return i&&console.info("Wurd: from cache:",s.filter((function(t){return void 0!==u[t]}))),l.length&&this._fetchSections(l).then((function(t){r.save(t,{lang:n}),a&&a(r.get())})),a&&a(c),c}},{key:"_fetchSections",value:function(t){var e=this,r=this.app;this.debug&&console.info("Wurd: from server:",t);var n,o=["draft","lang"].reduce((function(t,r){return e[r]&&(t[r]=e[r]),t}),{}),i="".concat(this.apiUrl,"/apps/").concat(r,"/content/").concat(t,"?").concat((n=o,Object.keys(n).map((function(t){var e=n[t];return encodeURIComponent(t)+"="+encodeURIComponent(e)})).join("&")));return this._fetch(i).then((function(e){if(e.error)throw e.error.message?new Error(e.error.message):new Error("Error loading ".concat(t));return e}))}},{key:"_fetch",value:function(t){return fetch(t).then((function(e){if(!e.ok)throw new Error("Error loading ".concat(t,": ").concat(e.statusText));return e.json()}))}},{key:"startEditor",value:function(){var t=this.app,e=this.lang;this.editMode=!0,this.draft=!0;var r=document.createElement("script");r.src=this.widgetUrl,r.async=!0,r.setAttribute("data-app",t),e&&r.setAttribute("data-lang",e);var n=document.body.querySelector('script[src="'.concat(this.widgetUrl,'"]'));n&&document.body.removeChild(n),document.body.appendChild(r)}},{key:"setBlockHelpers",value:function(t){Object.assign(s.prototype,t)}}])}(),l=new u;return l.Wurd=u,l})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).wurd=e()}(this,(function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:{},o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.rawContent=n,this.storageKey=o.storageKey||"wurdContent",this.ttl=null!==(r=o.ttl)&&void 0!==r?r:36e5}),[{key:"get",value:function(t){return t?t.split(".").reduce((function(t,e){return t&&t[e]}),this.rawContent):this.rawContent}},{key:"load",value:function(t){var e=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey,o=this.ttl;try{var i=JSON.parse(localStorage.getItem(n)),a=i&&i._wurd;return!i||!a||a.savedAt+o1&&void 0!==arguments[1]?arguments[1]:{}).lang,r=this.rawContent,n=this.storageKey;Object.assign(r,t),localStorage.setItem(n,JSON.stringify(i(i({},r),{},{_wurd:{savedAt:Date.now(),lang:e}})))}},{key:"clear",value:function(){localStorage.removeItem(this.storageKey)}}])}(),s=function(){function e(r,n){var o=this;t(this,e),this.wurd=r,this.path=n,Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach((function(t){o[t]=o[t].bind(o)}))}return r(e,[{key:"id",value:function(t){return t?this.path?[this.path,t].join("."):t:this.path}},{key:"get",value:function(t){var e=this.wurd.store.get(this.id(t));if(void 0===e&&this.wurd.draft){var r=t.split(".")[0];this.wurd.store.get(r)||console.warn("Tried to access unloaded section: ".concat(r))}return e}},{key:"text",value:function(t,e){var r=this.get(t);return void 0===r?this.wurd.draft?"[".concat(t,"]"):"":"string"!=typeof r?(console.warn("Tried to get object as string: ".concat(t)),this.wurd.draft?"[".concat(t,"]"):""):(e&&(r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"string"!=typeof t?t:t.replace(/{{([\w.-]+)}}/g,(function(t,r){return e[r]||""}))}(r,e)),r)}},{key:"markdown",value:function(t,e,r){var n=this.wurd.markdown,o=n.parse,i=n.parseInline,a=this.text(t,e);return null!=r&&r.inline&&i?i(a):o?o(a):a}},{key:"map",value:function(t,e){var r=this,o=this.get(t)||n({},Date.now(),{}),i=0;return Object.keys(o).sort().map((function(n){var o=i;i++;var a=[t,n].join("."),c=r.block(a);return e.call(void 0,c,o)}))}},{key:"block",value:function(t,r){var n=this.id(t),o=new e(this.wurd,n);return"function"==typeof r?r.call(void 0,o):o}},{key:"el",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=this.id(t),o=r.markdown?this.markdown(t,e):this.text(t,e),i=e||r.markdown?"data-wurd-md":"data-wurd";if(this.wurd.draft){var a=r.type||"span";return r.markdown&&(a="div"),"<".concat(a," ").concat(i,'="').concat(n,'">').concat(o,"")}return o}}])}(),u=function(){return r((function e(r){var n=this,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.widgetUrl="https://widget.wurd.io/widget.js",this.apiUrl="https://api.wurd.io",this.store=new c,this.content=new s(this,null),Object.getOwnPropertyNames(Object.getPrototypeOf(this.content)).forEach((function(t){n[t]=n.content[t].bind(n.content)})),this.connect(r,o)}),[{key:"connect",value:function(t){var e=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(this.app=t,this.draft=!1,this.editMode=!1,["draft","lang","markdown","debug","onLoad"].forEach((function(t){var n=r[t];void 0!==n&&(e[t]=n)})),r.editMode){case!0:this.startEditor();break;case"querystring":/[?&]edit(&|$)/.test(location.search)&&this.startEditor()}return r.rawContent&&this.store.save(r.rawContent,{lang:r.lang}),r.storageKey&&(this.store.storageKey=r.storageKey),r.ttl&&(this.store.ttl=r.ttl),r.blockHelpers&&this.setBlockHelpers(r.blockHelpers),this}},{key:"load",value:function(t){var e=this.app,r=this.store,n=this.lang,o=this.editMode,i=this.debug,a=this.onLoad,c=this.content;if(!e)return Promise.reject(new Error("Use wurd.connect(appName) before wurd.load()"));var s="string"==typeof t?t.split(","):t;if(o)return this._fetchSections(s).then((function(t){return r.save(t,{lang:n}),r.clear(),a&&a(c),c}));var u=r.load(s,{lang:n}),l=s.filter((function(t){return void 0===u[t]}));return i&&console.info("Wurd: from cache:",s.filter((function(t){return void 0!==u[t]}))),0===l.length?(a&&a(c),Promise.resolve(c)):this._fetchSections(l).then((function(t){return r.save(t,{lang:n}),a&&a(c),c})).catch((function(t){return i&&console.info("Wurd: load error:",t),a&&a(c),c}))}},{key:"_fetchSections",value:function(t){var e=this,r=this.app;this.debug&&console.info("Wurd: from server:",t);var n,o=["draft","lang"].reduce((function(t,r){return e[r]&&(t[r]=e[r]),t}),{}),i="".concat(this.apiUrl,"/apps/").concat(r,"/content/").concat(t,"?").concat((n=o,Object.keys(n).map((function(t){var e=n[t];return encodeURIComponent(t)+"="+encodeURIComponent(e)})).join("&")));return this._fetch(i).then((function(e){if(e.error)throw e.error.message?new Error(e.error.message):new Error("Error loading ".concat(t));return e}))}},{key:"_fetch",value:function(t){return fetch(t).then((function(e){if(!e.ok)throw new Error("Error loading ".concat(t,": ").concat(e.statusText));return e.json()}))}},{key:"startEditor",value:function(){var t=this.app,e=this.lang;this.editMode=!0,this.draft=!0;var r=document.createElement("script");r.src=this.widgetUrl,r.async=!0,r.setAttribute("data-app",t),e&&r.setAttribute("data-lang",e);var n=document.body.querySelector('script[src="'.concat(this.widgetUrl,'"]'));n&&document.body.removeChild(n),document.body.appendChild(r)}},{key:"setBlockHelpers",value:function(t){Object.assign(s.prototype,t)}}])}(),l=new u;return l.Wurd=u,l})); diff --git a/src/index.js b/src/index.js index 267f03b..0f7e91f 100644 --- a/src/index.js +++ b/src/index.js @@ -118,28 +118,37 @@ class Wurd { // Check for cached sections const cachedContent = store.load(sections, { lang }); - const uncachedSections = cachedContent._expired - ? sections - : sections.filter(section => cachedContent[section] === undefined); + const uncachedSections = sections.filter(section => cachedContent[section] === undefined); if (debug) console.info('Wurd: from cache:', sections.filter(section => cachedContent[section] !== undefined)); + // Return now if all content was in cache + if (uncachedSections.length === 0) { + // Pass main content Block to callbacks + if (onLoad) onLoad(content); - // If missing sections, refetch in background - if (uncachedSections.length) { - this._fetchSections(uncachedSections) - .then(result => { - // Cache for next time - store.save(result, { lang }); - - // Pass main content Block to callbacks - if (onLoad) onLoad(store.get()); - }); + return Promise.resolve(content); } - // Return content in all case - if (onLoad) onLoad(content); - return content; + // Otherwise fetch remaining sections + return this._fetchSections(uncachedSections) + .then(result => { + // Cache for next time + store.save(result, { lang }); + + // Pass main content Block to callbacks + if (onLoad) onLoad(content); + + return content; + }) + .catch(err => { + if (debug) console.info('Wurd: load error:', err); + + // If content fails to load (wurd app offline), still return cache + if (onLoad) onLoad(content); + + return content; + }); } _fetchSections(sectionNames) { diff --git a/src/store.js b/src/store.js index f7f9034..0afae65 100644 --- a/src/store.js +++ b/src/store.js @@ -40,17 +40,14 @@ export default class Store { const cachedContent = JSON.parse(localStorage.getItem(storageKey)); const metaData = cachedContent && cachedContent._wurd; - // Check it's in the correct language - if (!cachedContent || !metaData || metaData.lang !== lang) { + // Check if it has expired + if (!cachedContent || !metaData || (metaData.savedAt + ttl) < Date.now()) { return rawContent; } - // Check if it has expired - if ((metaData.savedAt + ttl) < Date.now()) { - Object.defineProperty(cachedContent, '_expired', { - enumerable: false, - value: true, - }); + // Check it's in the correct language + if (metaData.lang !== lang) { + return rawContent; } // Remove metadata From 381e4f535f153362b19dfc26b003ea4a853efd3d Mon Sep 17 00:00:00 2001 From: Cyril Auburtin Date: Sun, 27 Apr 2025 19:44:16 +0200 Subject: [PATCH 8/9] fix tests --- src/index.spec.js | 17 ++++------------- src/store.spec.js | 3 +-- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/index.spec.js b/src/index.spec.js index 832d2f9..f5efff2 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -115,24 +115,15 @@ describe('Wurd', function() { // Should return the main content Block test.deepEqual(content.get(), { lorem: { title: 'Lorem' }, + ipsum: { title: 'Ipsum' }, dolor: { title: 'Dolor' }, + amet: { title: 'Amet' } }); // Should pass the main content Block to the onLoad() callback same(client.onLoad.callCount, 1); same(client.onLoad.args[0][0], content); - // wait another cycle - await new Promise(r => setTimeout(r, 2)); - - same(client.onLoad.callCount, 2); - test.deepStrictEqual(client.onLoad.args[1][0], { - lorem: { title: 'Lorem' }, - dolor: { title: 'Dolor' }, - ipsum: { title: 'Ipsum' }, - amet: { title: 'Amet' } - }); - done(); }).catch(done); }); @@ -167,7 +158,7 @@ describe('Wurd', function() { .then(() => client.load(['lorem', 'ipsum'])) .then(content => { test.deepEqual(content.get('lorem.title'), 'Lorem'); - test.deepEqual(content.get('ipsum.title'), undefined); + test.deepEqual(content.get('ipsum.title'), 'Ipsum'); test.deepEqual(console.info.args, [ ['Wurd: from cache:', ['lorem']], @@ -183,7 +174,7 @@ describe('Wurd', function() { .then(() => client.load('dolor,amet')) .then(content => { test.deepEqual(content.get('dolor.title'), 'Dolor'); - test.deepEqual(content.get('amet.title'), undefined); + test.deepEqual(content.get('amet.title'), 'Amet'); test.deepEqual(console.info.args, [ ['Wurd: from cache:', ['dolor']], diff --git a/src/store.spec.js b/src/store.spec.js index bbff3d1..603ecfd 100644 --- a/src/store.spec.js +++ b/src/store.spec.js @@ -124,7 +124,7 @@ describe('store', function() { }); }); - it('returns all content if localStorage has expired and add _expired flag', function () { + it('returns memory content if localStorage has expired', function () { global.localStorage.getItem.returns(JSON.stringify({ b: { a: 'BA' }, _wurd: { @@ -134,7 +134,6 @@ describe('store', function() { test.deepEqual(store.load(), { a: { a: 'AA' }, - b: { a: 'BA' }, }); }); From 8c2741a17607d767218a9c269b540c276a9e6386 Mon Sep 17 00:00:00 2001 From: Cyril Auburtin Date: Sun, 27 Apr 2025 19:49:34 +0200 Subject: [PATCH 9/9] add test for load failure --- src/index.spec.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/index.spec.js b/src/index.spec.js index f5efff2..3a344a0 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -153,6 +153,26 @@ describe('Wurd', function() { }).catch(done); }); + it('returns cached content if loading fails', function (done) { + client._fetch.rejects(new Error('timeout')); + Promise.resolve() + .then(() => client.load(['lorem','ipsum','dolor','amet'])) + .then(content => { + same(client._fetchSections.callCount, 1); + + test.deepEqual(content.get(), { + lorem: { title: 'Lorem' }, + dolor: { title: 'Dolor' }, + }); + + // Should pass the main content Block to the onLoad() callback + same(client.onLoad.callCount, 1); + same(client.onLoad.args[0][0], content); + + done(); + }).catch(done); + }); + it('works with an array of sectionNames', function (done) { Promise.resolve() .then(() => client.load(['lorem', 'ipsum']))