@innovastudio/contentbuilder-interactive-runtime 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10754,6 +10754,8 @@ class vn {
10754
10754
  // Simple on/off for external plugins
10755
10755
  trustedDomains: [],
10756
10756
  // Optional: whitelist specific domains
10757
+ onReInit: () => {
10758
+ },
10757
10759
  ...e
10758
10760
  }, this.plugins = /* @__PURE__ */ new Map(), this.loadedStyles = /* @__PURE__ */ new Set(), this.components = /* @__PURE__ */ new Map(), this.externalPlugins = /* @__PURE__ */ new Map();
10759
10761
  }
@@ -10846,7 +10848,7 @@ class vn {
10846
10848
  u.setAttribute("data-edit-id", h);
10847
10849
  }), t.hasAttribute("data-cb-original-content") || t.setAttribute("data-cb-original-content", t.innerHTML);
10848
10850
  const d = s.mount(t, r);
10849
- this.components.set(t, d), this.setupInlineEditing(t, r), t.setAttribute("data-cb-loaded", "true");
10851
+ this.components.set(t, d), this.setupInlineEditing(t, r), t.setAttribute("data-cb-loaded", "true"), typeof t.mount == "function" && t.mount();
10850
10852
  } catch (r) {
10851
10853
  console.error(`[PluginManager] Failed to mount component "${n}":`, r);
10852
10854
  }
@@ -10894,13 +10896,13 @@ class vn {
10894
10896
  const s = (r, o) => {
10895
10897
  const a = e.getAttribute("data-cb-original-content");
10896
10898
  if (!a) return;
10897
- const l = new DOMParser().parseFromString(a, "text/html").body;
10898
- if (!l) return;
10899
- const h = l.querySelector(`[data-edit-id="${r}"]`);
10900
- if (h) {
10901
- h.innerHTML = o;
10902
- const p = l.innerHTML;
10903
- e.setAttribute("data-cb-original-content", p);
10899
+ const d = document.createElement("template");
10900
+ d.innerHTML = a.trim();
10901
+ const l = d.content.querySelector(`[data-edit-id="${r}"]`);
10902
+ if (l) {
10903
+ l.innerHTML = o;
10904
+ const h = d.innerHTML;
10905
+ e.setAttribute("data-cb-original-content", h);
10904
10906
  }
10905
10907
  };
10906
10908
  i.forEach((r) => {
@@ -10990,19 +10992,24 @@ class vn {
10990
10992
  try {
10991
10993
  const u = this.parseOptions(o);
10992
10994
  let l = o.querySelector(".grid-sortable");
10993
- l || o.classList.contains("grid-sortable") && (l = o), l && Array.from(l.children).forEach((b, w) => {
10995
+ if (l || o.classList.contains("grid-sortable") && (l = o), l && Array.from(l.children).forEach((b, w) => {
10994
10996
  b.nodeType === 1 && b.tagName !== "STYLE" && b.tagName !== "SCRIPT" && b.setAttribute("data-index", w);
10995
10997
  }), o.querySelectorAll(".edit").forEach((b, w) => {
10996
10998
  const _ = `content-${Date.now()}-${r}-${w}`;
10997
10999
  b.setAttribute("data-edit-id", _);
10998
- }), o.hasAttribute("data-cb-original-content") ? o.innerHTML = o.getAttribute("data-cb-original-content") : o.setAttribute("data-cb-original-content", o.innerHTML);
11000
+ }), !o.hasAttribute("data-cb-original-content"))
11001
+ o.setAttribute("data-cb-original-content", o.innerHTML);
11002
+ else {
11003
+ let b = document.createRange();
11004
+ o.innerHTML = "", o.appendChild(b.createContextualFragment(o.getAttribute("data-cb-original-content")));
11005
+ }
10999
11006
  const p = await d.mount(o, u);
11000
- this.components.set(o, p), this.setupInlineEditing(o, u), o.setAttribute("data-cb-loaded", "true"), r++;
11007
+ this.components.set(o, p), this.setupInlineEditing(o, u), o.setAttribute("data-cb-loaded", "true"), typeof o.mount == "function" && o.mount(), r++;
11001
11008
  } catch (u) {
11002
11009
  console.error(`[PluginManager] Failed to mount component "${a}":`, u);
11003
11010
  }
11004
11011
  }
11005
- return this.emit("reinitialized", { container: e, count: r }), r;
11012
+ return typeof this.config.onReInit == "function" && this.config.onReInit(), this.emit("reinitialized", { container: e, count: r }), r;
11006
11013
  } finally {
11007
11014
  this.reinitializeInProgress = !1;
11008
11015
  }
@@ -508,4 +508,4 @@ ${q.data}
508
508
  `}}this.tokenInput+=y,this.tokenOutput+=k}return!0}catch(l){return l.name==="AbortError"?this.settings.consoleLog&&console.log("Request aborted by user."):console.error("Error:",l),!1}}async assistant(e,t,i,n){this.controller=new AbortController,this.signal=this.controller.signal;let s=.6,r=.9,o=1;n||(n=[]);let a=this.settings.model;n.length>0&&(a=this.settings.model2);const d={assistantId:this.settings.assistantId,question:e,context:t,system:i,functs:n,temperature:s,topP:r,num:o,model:a,customData:this.settings.customData};try{let u={"Content-Type":"application/json",...this.settings.headers},h=await(await fetch(this.settings.assistantUrl,{signal:this.signal,method:"POST",headers:u,body:JSON.stringify(d)})).json();return h.error?(console.log(`Error:
509
509
  `+h.error),!1):(h.usage&&(this.tokenInput+=h.usage.prompt_tokens,this.tokenOutput+=h.usage.completion_tokens),h.answer.content[0].text.value)}catch(u){return u.name==="AbortError"?this.settings.consoleLog&&console.log("Request aborted by user."):console.error("Error:",u),!1}}async assistantStream(e,t,i,n,s){this.controller=new AbortController,this.signal=this.controller.signal;let r=.6,o=.9,a=1;n||(n=[]);const d={assistantId:this.settings.assistantId,question:e,context:t,system:i,functs:n,temperature:r,topP:o,num:a,model:this.settings.model,customData:this.settings.customData};try{let u={"Content-Type":"application/json",...this.settings.headers};const l=await fetch(this.settings.assistantStreamUrl,{signal:this.signal,method:"POST",headers:u,body:JSON.stringify(d)});if(!l.ok)return console.error("Error:",l.statusText),!1;const h=l.body.getReader(),p=new TextDecoder;let b="",w=!1;for(;!w;){const{done:_,value:g}=await h.read();if(w=_,w)break;const v=p.decode(g,{stream:!0});b+=v;const f=b.split(`
510
510
  `);b="";for(const m of f)if(m.trim()!=="")if(m.startsWith("data:")){const y=m.slice(5).trim();y==="[DONE]"&&this.settings.consoleLog&&console.log("Stream completed.");try{const k=JSON.parse(y);if(k.choices&&k.choices[0].delta&&k.choices[0].delta.content){const x=k.choices[0].delta.content;s&&s(x)}}catch{b+=m+`
511
- `}}else{const y=JSON.parse(m);this.tokenInput+=y.prompt_tokens,this.tokenOutput+=y.completion_tokens;break}}return!0}catch(u){return u.name==="AbortError"?this.settings.consoleLog&&console.log("Request aborted by user."):console.error("Error:",u),!1}}hexToRgb(e){const t=e.replace("#",""),i=parseInt(t,16),n=i>>16&255,s=i>>8&255,r=i&255;return{red:n,green:s,blue:r}}getId(e="id"){return`${e}-${Math.random().toString(36).substr(2,9)}`}appendHtml(e,t){e&&t&&e.insertAdjacentHTML("beforeend",t)}cleanup(){if(this.element){let e=this.element.querySelectorAll('input[type="text"], input[type="number"], input[type="email"], input[type="tel"], input[type="url"], textarea');e.forEach(t=>{t.removeEventListener("input",this.inputListener)}),e=this.element.querySelectorAll('input[type="date"], input[type="radio"], input[type="checkbox"]'),e.forEach(t=>{t.removeEventListener("change",this.changeListener)}),this.debounceTimeout&&(clearTimeout(this.debounceTimeout),this.debounceTimeout=null)}}destroy(){try{if(this._scrollListener){let e;this.settings.isBuilder?e=document.querySelector(this.settings.previewSelector):e=this.element;const t=e.querySelector(".submit-container"),i=this.getScrollableParent(t);i?i.removeEventListener("scroll",this._scrollListener):window.removeEventListener("scroll",this._scrollListener),window.removeEventListener("resize",this._scrollListener)}}catch{}this.element&&(this.cleanup(),this.element.innerHTML="",this.element=null);for(const e in this.listeners)this.listeners[e].clear();this.listeners={}}out(e){let t=this.settings.lang[e];return t||e}}class mn{constructor(e={}){this.config={plugins:{},autoLoad:!0,pluginBaseUrl:"",allowExternalPlugins:!0,trustedDomains:[],...e},this.plugins=new Map,this.loadedStyles=new Set,this.components=new Map,this.externalPlugins=new Map}async autoLoadPlugins(){const t=this.detectComponentTypes().filter(i=>this.config.plugins[i]&&!this.plugins.has(i));return t.length>0&&await Promise.all(t.map(i=>this.loadPlugin(i))),t}detectComponentTypes(){const e=new Set;return document.querySelectorAll("[data-cb-type]").forEach(t=>{e.add(t.dataset.cbType)}),document.querySelectorAll('[class*="cb-"]').forEach(t=>{t.classList.forEach(i=>{if(i.startsWith("cb-")){const n=i.replace("cb-","");e.add(n)}})}),Array.from(e)}async loadPlugin(e){if(this.plugins.has(e))return this.plugins.get(e);const t=this.config.plugins[e];if(!t)return console.warn(`[PluginManager] Plugin "${e}" not found in registry`),null;try{t.css&&this.loadCSS(this.resolveUrl(t.css));const i=this.resolveUrl(t.url),n=await this.importModule(i),s=n.default||n;return this.plugins.set(e,s),typeof s.init=="function"&&await s.init(this),this.emit("plugin-loaded",{name:e,plugin:s}),s}catch(i){return console.error(`[PluginManager] Failed to load plugin "${e}":`,i),null}}async importModule(e){return import(e)}loadCSS(e){if(this.loadedStyles.has(e))return;const t=document.createElement("link");t.rel="stylesheet",t.href=e,t.onload=()=>{this.emit("css-loaded",{url:e})},t.onerror=()=>{console.error(`[PluginManager] Failed to load CSS: ${e}`)},document.head.appendChild(t),this.loadedStyles.add(e)}resolveUrl(e){return e.startsWith("http://")||e.startsWith("https://")||e.startsWith("//")?e:this.config.pluginBaseUrl+e}initializeComponents(){return document.querySelectorAll("[data-cb-type]").forEach((t,i)=>{const n=t.dataset.cbType,s=this.plugins.get(n);if(s&&typeof s.mount=="function")try{const r=this.parseOptions(t);let o=t.querySelector(".grid-sortable");o||t.classList.contains("grid-sortable")&&(o=t),o&&Array.from(o.children).forEach((u,l)=>{u.nodeType===1&&u.tagName!=="STYLE"&&u.tagName!=="SCRIPT"&&u.setAttribute("data-index",l)}),t.querySelectorAll(".edit").forEach((u,l)=>{const h=`content-${Date.now()}-${i}-${l}`;u.setAttribute("data-edit-id",h)}),t.hasAttribute("data-cb-original-content")||t.setAttribute("data-cb-original-content",t.innerHTML);const d=s.mount(t,r);this.components.set(t,d),this.setupInlineEditing(t,r),t.setAttribute("data-cb-loaded","true")}catch(r){console.error(`[PluginManager] Failed to mount component "${n}":`,r)}}),this.components.size}parseOptions(e){const t={};return Array.from(e.attributes).forEach(i=>{if(i.name.startsWith("data-cb-")){const n=i.name.replace("data-cb-","").replace(/-([a-z])/g,(s,r)=>r.toUpperCase());t[n]=this.parseValue(i.value)}}),t}parseValue(e){if(e==="true")return!0;if(e==="false")return!1;if(!isNaN(e)&&e!=="")return Number(e);try{return JSON.parse(e)}catch{return e}}setupInlineEditing(e,t){if(!e.closest(".data-editor"))return;const i=e.querySelectorAll(".edit[data-edit-id]");if(i.length===0)return;e.querySelectorAll('[contenteditable="true"]').forEach(r=>{r.removeAttribute("contenteditable"),r._cbObserver&&(r._cbObserver.disconnect(),delete r._cbObserver)});const s=(r,o)=>{const a=e.getAttribute("data-cb-original-content");if(!a)return;const l=new DOMParser().parseFromString(a,"text/html").body;if(!l)return;const h=l.querySelector(`[data-edit-id="${r}"]`);if(h){h.innerHTML=o;const p=l.innerHTML;e.setAttribute("data-cb-original-content",p)}};i.forEach(r=>{r.setAttribute("contenteditable","true");const o=new MutationObserver(()=>{const a=r.getAttribute("data-edit-id"),d=r.innerHTML;s(a,d)});o.observe(r,{childList:!0,characterData:!0,subtree:!0,attributes:!0,attributeFilter:["style"]}),r._cbObserver=o})}async use(e){return await this.loadPlugin(e)}getPlugin(e){return this.plugins.get(e)}getAllPlugins(){return Array.from(this.plugins.entries())}hasPlugin(e){return this.plugins.has(e)}getComponent(e){return this.components.get(e)}async reinitialize(e=document){if(this.reinitializeInProgress)return clearTimeout(this.pendingReinitialize),new Promise(t=>{this.pendingReinitialize=setTimeout(async()=>{this.pendingReinitialize=null;const i=await this.reinitialize(e);t(i)},100)});this.reinitializeInProgress=!0;try{const t=[];this.components.forEach((o,a)=>{(e===document||e.contains(a))&&t.push({element:a,instance:o})}),t.forEach(({element:o,instance:a})=>{const d=o.dataset.cbType,u=this.plugins.get(d);u&&typeof u.unmount=="function"&&u.unmount(o,a),this.components.delete(o)});const n=this.detectComponentTypesInContainer(e).filter(o=>this.config.plugins[o]&&!this.plugins.has(o));n.length>0&&await Promise.all(n.map(o=>this.loadPlugin(o)));const s=e.querySelectorAll("[data-cb-type]");let r=0;for(const o of s){const a=o.dataset.cbType,d=this.plugins.get(a);if(d&&typeof d.mount=="function")try{const u=this.parseOptions(o);let l=o.querySelector(".grid-sortable");l||o.classList.contains("grid-sortable")&&(l=o),l&&Array.from(l.children).forEach((b,w)=>{b.nodeType===1&&b.tagName!=="STYLE"&&b.tagName!=="SCRIPT"&&b.setAttribute("data-index",w)}),o.querySelectorAll(".edit").forEach((b,w)=>{const _=`content-${Date.now()}-${r}-${w}`;b.setAttribute("data-edit-id",_)}),o.hasAttribute("data-cb-original-content")?o.innerHTML=o.getAttribute("data-cb-original-content"):o.setAttribute("data-cb-original-content",o.innerHTML);const p=await d.mount(o,u);this.components.set(o,p),this.setupInlineEditing(o,u),o.setAttribute("data-cb-loaded","true"),r++}catch(u){console.error(`[PluginManager] Failed to mount component "${a}":`,u)}}return this.emit("reinitialized",{container:e,count:r}),r}finally{this.reinitializeInProgress=!1}}detectComponentTypesInContainer(e){const t=new Set;return e.querySelectorAll("[data-cb-type]").forEach(n=>{t.add(n.dataset.cbType)}),Array.from(t)}async loadExternalPlugin(e,t){if(!e||typeof e!="string")throw new Error("Plugin ID is required and must be a string");if(!t.url)throw new Error("Plugin URL is required");if(this.plugins.has(e))return console.warn(`[PluginManager] Plugin "${e}" is already loaded`),this.plugins.get(e);const i=this.validatePluginSource(t.url);if(!i.allowed)throw new Error(`Security: ${i.reason}`);i.warning&&(console.warn(`[PluginManager] ${i.warning}`),this.emit("plugin-security-warning",{pluginId:e,url:t.url,warning:i.warning}));try{this.config.plugins[e]={url:t.url,css:t.css||null,source:"external",loadedAt:new Date().toISOString()},this.externalPlugins.set(e,this.config.plugins[e]);const n=await this.loadPlugin(e);return this.emit("external-plugin-loaded",{pluginId:e,url:t.url}),n}catch(n){throw delete this.config.plugins[e],this.externalPlugins.delete(e),console.error(`[PluginManager] Failed to load external plugin "${e}":`,n),n}}validatePluginSource(e){const t=e.includes("localhost")||e.includes("127.0.0.1");if(!e.startsWith("https://")&&!t)return{allowed:!1,reason:"Only HTTPS URLs are allowed (except localhost for development)"};if(!this.config.allowExternalPlugins)return{allowed:!1,reason:"External plugins are disabled. Enable allowExternalPlugins in configuration."};if(this.config.trustedDomains.length>0){const n=new URL(e).hostname;if(!this.config.trustedDomains.some(r=>{if(r.startsWith("*.")){const o=r.slice(2);return n===o||n.endsWith("."+o)}return n===r}))return{allowed:!0,warning:`Loading plugin from external domain: ${n}. Only add plugins from sources you trust.`}}else return{allowed:!0,warning:`Loading external plugin from: ${new URL(e).hostname}. This plugin will have full access to your page.`};return{allowed:!0}}async loadExternalPlugins(e){const t=[];for(const[i,n]of Object.entries(e))try{const s=await this.loadExternalPlugin(i,n);t.push({pluginId:i,success:!0,plugin:s})}catch(s){t.push({pluginId:i,success:!1,error:s.message}),console.error(`[PluginManager] Failed to load external plugin "${i}":`,s)}return t}unregisterPlugin(e){if(!this.externalPlugins.has(e))return console.warn(`[PluginManager] Plugin "${e}" is not an external plugin`),!1;const t=this.plugins.get(e),i=[];return this.components.forEach((n,s)=>{s.dataset.cbType===e&&i.push({element:s,instance:n})}),i.forEach(({element:n,instance:s})=>{t&&typeof t.unmount=="function"&&t.unmount(n,s),this.components.delete(n)}),t&&typeof t.destroy=="function"&&t.destroy(this),this.plugins.delete(e),this.externalPlugins.delete(e),delete this.config.plugins[e],this.emit("plugin-unregistered",{pluginId:e}),!0}getExternalPlugins(){return Array.from(this.externalPlugins.entries()).map(([e,t])=>({id:e,...t}))}isExternalPlugin(e){return this.externalPlugins.has(e)}destroy(){this.components.forEach((t,i)=>{const n=i.dataset.cbType,s=this.plugins.get(n);s&&typeof s.unmount=="function"&&s.unmount(i,t)}),element.querySelectorAll('[contenteditable="true"]').forEach(t=>{t.removeAttribute("contenteditable"),t._cbObserver&&(t._cbObserver.disconnect(),delete t._cbObserver)}),this.plugins.forEach((t,i)=>{typeof t.destroy=="function"&&t.destroy(this)}),this.components.clear(),this.plugins.clear()}emit(e,t){if(this.runtime&&typeof this.runtime.emit=="function")this.runtime.emit(e,t);else{const i=new CustomEvent(`contentbox:${e}`,{detail:t});document.dispatchEvent(i)}}setRuntime(e){this.runtime=e}}class bn{constructor(e={}){const t={skin:"light",plugins:{},autoLoadPlugins:!0,pluginBaseUrl:"",allowExternalPlugins:!0,trustedDomains:[]};this.settings={...t,...e},this.initialized=!1,this.pluginManager=new mn({plugins:this.settings.plugins,autoLoad:this.settings.autoLoadPlugins,pluginBaseUrl:this.settings.pluginBaseUrl,allowExternalPlugins:this.settings.allowExternalPlugins,trustedDomains:this.settings.trustedDomains}),this.pluginManager.setRuntime(this),window.Glide=Rs,window.FormViewer=gn}async use(e){return await this.pluginManager.use(e)}getPlugin(e){return this.pluginManager.getPlugin(e)}getPlugins(){return this.pluginManager.getAllPlugins()}hasPlugin(e){return this.pluginManager.hasPlugin(e)}getComponent(e){return this.pluginManager.getComponent(e)}getComponents(){return this.pluginManager.components}async reinitialize(e){const t=await this.pluginManager.reinitialize(e);return this.initCore(),t}async loadExternalPlugin(e,t){return await this.pluginManager.loadExternalPlugin(e,t)}async loadExternalPlugins(e){return await this.pluginManager.loadExternalPlugins(e)}unregisterPlugin(e){return this.pluginManager.unregisterPlugin(e)}emit(e,t){const i=new CustomEvent(`contentbox:${e}`,{detail:t});document.dispatchEvent(i)}on(e,t){document.addEventListener(`contentbox:${e}`,t)}off(e,t){document.removeEventListener(`contentbox:${e}`,t)}init(){this.initialized||(this.initCore(),this.initPlugins(),this.initialized=!0,this.emit("init"))}async initPlugins(){this.settings.autoLoadPlugins&&await this.pluginManager.autoLoadPlugins(),this.pluginManager.initializeComponents()}initCore(){const e={touchNavigation:!0,selector:".glightbox",loop:!0,autoplayVideos:!0,zoomable:!0,slideEffect:"slide",descPosition:"bottom",skin:this.settings.skin};this.refreshLightbox(e),document.querySelectorAll(".is-lightbox,.block-click").forEach(i=>{const n=s=>{let r=i.getAttribute("href")||i.getAttribute("data-modal-url");this.openLightbox(r,i,e),s.preventDefault(),s.stopImmediatePropagation()};i.addEventListener("click",n),i._lightboxHandler=n})}refreshLightbox(e){this.glightbox&&this.glightbox.destroy(),this.glightbox=new jt(e),window.GLightbox=jt.default}openLightbox(e,t,i){let n=[],s=0;if(t){const o=t.closest("[data-gallery]");let a=[];o?a=o.querySelectorAll(".is-lightbox"):document.querySelectorAll(".is-lightbox,.block-click").forEach(u=>{u.closest("[data-gallery]")||a.push(u)}),a.forEach((d,u)=>{let l=d.getAttribute("href");l||(l=d.getAttribute("data-modal-url")),n.push({href:l}),e===l&&(s=u)})}const r=document.activeElement;r?.blur(),n.length>0?(this.glightbox.setElements(n),this.glightbox.openAt(s)):(this.glightbox.setElements([{href:e}]),this.glightbox.open()),this.glightbox.on("close",()=>{this.refreshLightbox(i),r?.focus()})}destroy(){this.glightbox&&(this.glightbox.destroy(),this.glightbox=null),document.querySelectorAll(".is-lightbox,.block-click").forEach(t=>{t._lightboxHandler&&(t.removeEventListener("click",t._lightboxHandler),delete t._lightboxHandler)}),this.pluginManager.destroy(),this.initialized=!1,this.emit("destroy")}}return bn})();
511
+ `}}else{const y=JSON.parse(m);this.tokenInput+=y.prompt_tokens,this.tokenOutput+=y.completion_tokens;break}}return!0}catch(u){return u.name==="AbortError"?this.settings.consoleLog&&console.log("Request aborted by user."):console.error("Error:",u),!1}}hexToRgb(e){const t=e.replace("#",""),i=parseInt(t,16),n=i>>16&255,s=i>>8&255,r=i&255;return{red:n,green:s,blue:r}}getId(e="id"){return`${e}-${Math.random().toString(36).substr(2,9)}`}appendHtml(e,t){e&&t&&e.insertAdjacentHTML("beforeend",t)}cleanup(){if(this.element){let e=this.element.querySelectorAll('input[type="text"], input[type="number"], input[type="email"], input[type="tel"], input[type="url"], textarea');e.forEach(t=>{t.removeEventListener("input",this.inputListener)}),e=this.element.querySelectorAll('input[type="date"], input[type="radio"], input[type="checkbox"]'),e.forEach(t=>{t.removeEventListener("change",this.changeListener)}),this.debounceTimeout&&(clearTimeout(this.debounceTimeout),this.debounceTimeout=null)}}destroy(){try{if(this._scrollListener){let e;this.settings.isBuilder?e=document.querySelector(this.settings.previewSelector):e=this.element;const t=e.querySelector(".submit-container"),i=this.getScrollableParent(t);i?i.removeEventListener("scroll",this._scrollListener):window.removeEventListener("scroll",this._scrollListener),window.removeEventListener("resize",this._scrollListener)}}catch{}this.element&&(this.cleanup(),this.element.innerHTML="",this.element=null);for(const e in this.listeners)this.listeners[e].clear();this.listeners={}}out(e){let t=this.settings.lang[e];return t||e}}class mn{constructor(e={}){this.config={plugins:{},autoLoad:!0,pluginBaseUrl:"",allowExternalPlugins:!0,trustedDomains:[],onReInit:()=>{},...e},this.plugins=new Map,this.loadedStyles=new Set,this.components=new Map,this.externalPlugins=new Map}async autoLoadPlugins(){const t=this.detectComponentTypes().filter(i=>this.config.plugins[i]&&!this.plugins.has(i));return t.length>0&&await Promise.all(t.map(i=>this.loadPlugin(i))),t}detectComponentTypes(){const e=new Set;return document.querySelectorAll("[data-cb-type]").forEach(t=>{e.add(t.dataset.cbType)}),document.querySelectorAll('[class*="cb-"]').forEach(t=>{t.classList.forEach(i=>{if(i.startsWith("cb-")){const n=i.replace("cb-","");e.add(n)}})}),Array.from(e)}async loadPlugin(e){if(this.plugins.has(e))return this.plugins.get(e);const t=this.config.plugins[e];if(!t)return console.warn(`[PluginManager] Plugin "${e}" not found in registry`),null;try{t.css&&this.loadCSS(this.resolveUrl(t.css));const i=this.resolveUrl(t.url),n=await this.importModule(i),s=n.default||n;return this.plugins.set(e,s),typeof s.init=="function"&&await s.init(this),this.emit("plugin-loaded",{name:e,plugin:s}),s}catch(i){return console.error(`[PluginManager] Failed to load plugin "${e}":`,i),null}}async importModule(e){return import(e)}loadCSS(e){if(this.loadedStyles.has(e))return;const t=document.createElement("link");t.rel="stylesheet",t.href=e,t.onload=()=>{this.emit("css-loaded",{url:e})},t.onerror=()=>{console.error(`[PluginManager] Failed to load CSS: ${e}`)},document.head.appendChild(t),this.loadedStyles.add(e)}resolveUrl(e){return e.startsWith("http://")||e.startsWith("https://")||e.startsWith("//")?e:this.config.pluginBaseUrl+e}initializeComponents(){return document.querySelectorAll("[data-cb-type]").forEach((t,i)=>{const n=t.dataset.cbType,s=this.plugins.get(n);if(s&&typeof s.mount=="function")try{const r=this.parseOptions(t);let o=t.querySelector(".grid-sortable");o||t.classList.contains("grid-sortable")&&(o=t),o&&Array.from(o.children).forEach((u,l)=>{u.nodeType===1&&u.tagName!=="STYLE"&&u.tagName!=="SCRIPT"&&u.setAttribute("data-index",l)}),t.querySelectorAll(".edit").forEach((u,l)=>{const h=`content-${Date.now()}-${i}-${l}`;u.setAttribute("data-edit-id",h)}),t.hasAttribute("data-cb-original-content")||t.setAttribute("data-cb-original-content",t.innerHTML);const d=s.mount(t,r);this.components.set(t,d),this.setupInlineEditing(t,r),t.setAttribute("data-cb-loaded","true"),typeof t.mount=="function"&&t.mount()}catch(r){console.error(`[PluginManager] Failed to mount component "${n}":`,r)}}),this.components.size}parseOptions(e){const t={};return Array.from(e.attributes).forEach(i=>{if(i.name.startsWith("data-cb-")){const n=i.name.replace("data-cb-","").replace(/-([a-z])/g,(s,r)=>r.toUpperCase());t[n]=this.parseValue(i.value)}}),t}parseValue(e){if(e==="true")return!0;if(e==="false")return!1;if(!isNaN(e)&&e!=="")return Number(e);try{return JSON.parse(e)}catch{return e}}setupInlineEditing(e,t){if(!e.closest(".data-editor"))return;const i=e.querySelectorAll(".edit[data-edit-id]");if(i.length===0)return;e.querySelectorAll('[contenteditable="true"]').forEach(r=>{r.removeAttribute("contenteditable"),r._cbObserver&&(r._cbObserver.disconnect(),delete r._cbObserver)});const s=(r,o)=>{const a=e.getAttribute("data-cb-original-content");if(!a)return;const d=document.createElement("template");d.innerHTML=a.trim();const l=d.content.querySelector(`[data-edit-id="${r}"]`);if(l){l.innerHTML=o;const h=d.innerHTML;e.setAttribute("data-cb-original-content",h)}};i.forEach(r=>{r.setAttribute("contenteditable","true");const o=new MutationObserver(()=>{const a=r.getAttribute("data-edit-id"),d=r.innerHTML;s(a,d)});o.observe(r,{childList:!0,characterData:!0,subtree:!0,attributes:!0,attributeFilter:["style"]}),r._cbObserver=o})}async use(e){return await this.loadPlugin(e)}getPlugin(e){return this.plugins.get(e)}getAllPlugins(){return Array.from(this.plugins.entries())}hasPlugin(e){return this.plugins.has(e)}getComponent(e){return this.components.get(e)}async reinitialize(e=document){if(this.reinitializeInProgress)return clearTimeout(this.pendingReinitialize),new Promise(t=>{this.pendingReinitialize=setTimeout(async()=>{this.pendingReinitialize=null;const i=await this.reinitialize(e);t(i)},100)});this.reinitializeInProgress=!0;try{const t=[];this.components.forEach((o,a)=>{(e===document||e.contains(a))&&t.push({element:a,instance:o})}),t.forEach(({element:o,instance:a})=>{const d=o.dataset.cbType,u=this.plugins.get(d);u&&typeof u.unmount=="function"&&u.unmount(o,a),this.components.delete(o)});const n=this.detectComponentTypesInContainer(e).filter(o=>this.config.plugins[o]&&!this.plugins.has(o));n.length>0&&await Promise.all(n.map(o=>this.loadPlugin(o)));const s=e.querySelectorAll("[data-cb-type]");let r=0;for(const o of s){const a=o.dataset.cbType,d=this.plugins.get(a);if(d&&typeof d.mount=="function")try{const u=this.parseOptions(o);let l=o.querySelector(".grid-sortable");if(l||o.classList.contains("grid-sortable")&&(l=o),l&&Array.from(l.children).forEach((b,w)=>{b.nodeType===1&&b.tagName!=="STYLE"&&b.tagName!=="SCRIPT"&&b.setAttribute("data-index",w)}),o.querySelectorAll(".edit").forEach((b,w)=>{const _=`content-${Date.now()}-${r}-${w}`;b.setAttribute("data-edit-id",_)}),!o.hasAttribute("data-cb-original-content"))o.setAttribute("data-cb-original-content",o.innerHTML);else{let b=document.createRange();o.innerHTML="",o.appendChild(b.createContextualFragment(o.getAttribute("data-cb-original-content")))}const p=await d.mount(o,u);this.components.set(o,p),this.setupInlineEditing(o,u),o.setAttribute("data-cb-loaded","true"),typeof o.mount=="function"&&o.mount(),r++}catch(u){console.error(`[PluginManager] Failed to mount component "${a}":`,u)}}return typeof this.config.onReInit=="function"&&this.config.onReInit(),this.emit("reinitialized",{container:e,count:r}),r}finally{this.reinitializeInProgress=!1}}detectComponentTypesInContainer(e){const t=new Set;return e.querySelectorAll("[data-cb-type]").forEach(n=>{t.add(n.dataset.cbType)}),Array.from(t)}async loadExternalPlugin(e,t){if(!e||typeof e!="string")throw new Error("Plugin ID is required and must be a string");if(!t.url)throw new Error("Plugin URL is required");if(this.plugins.has(e))return console.warn(`[PluginManager] Plugin "${e}" is already loaded`),this.plugins.get(e);const i=this.validatePluginSource(t.url);if(!i.allowed)throw new Error(`Security: ${i.reason}`);i.warning&&(console.warn(`[PluginManager] ${i.warning}`),this.emit("plugin-security-warning",{pluginId:e,url:t.url,warning:i.warning}));try{this.config.plugins[e]={url:t.url,css:t.css||null,source:"external",loadedAt:new Date().toISOString()},this.externalPlugins.set(e,this.config.plugins[e]);const n=await this.loadPlugin(e);return this.emit("external-plugin-loaded",{pluginId:e,url:t.url}),n}catch(n){throw delete this.config.plugins[e],this.externalPlugins.delete(e),console.error(`[PluginManager] Failed to load external plugin "${e}":`,n),n}}validatePluginSource(e){const t=e.includes("localhost")||e.includes("127.0.0.1");if(!e.startsWith("https://")&&!t)return{allowed:!1,reason:"Only HTTPS URLs are allowed (except localhost for development)"};if(!this.config.allowExternalPlugins)return{allowed:!1,reason:"External plugins are disabled. Enable allowExternalPlugins in configuration."};if(this.config.trustedDomains.length>0){const n=new URL(e).hostname;if(!this.config.trustedDomains.some(r=>{if(r.startsWith("*.")){const o=r.slice(2);return n===o||n.endsWith("."+o)}return n===r}))return{allowed:!0,warning:`Loading plugin from external domain: ${n}. Only add plugins from sources you trust.`}}else return{allowed:!0,warning:`Loading external plugin from: ${new URL(e).hostname}. This plugin will have full access to your page.`};return{allowed:!0}}async loadExternalPlugins(e){const t=[];for(const[i,n]of Object.entries(e))try{const s=await this.loadExternalPlugin(i,n);t.push({pluginId:i,success:!0,plugin:s})}catch(s){t.push({pluginId:i,success:!1,error:s.message}),console.error(`[PluginManager] Failed to load external plugin "${i}":`,s)}return t}unregisterPlugin(e){if(!this.externalPlugins.has(e))return console.warn(`[PluginManager] Plugin "${e}" is not an external plugin`),!1;const t=this.plugins.get(e),i=[];return this.components.forEach((n,s)=>{s.dataset.cbType===e&&i.push({element:s,instance:n})}),i.forEach(({element:n,instance:s})=>{t&&typeof t.unmount=="function"&&t.unmount(n,s),this.components.delete(n)}),t&&typeof t.destroy=="function"&&t.destroy(this),this.plugins.delete(e),this.externalPlugins.delete(e),delete this.config.plugins[e],this.emit("plugin-unregistered",{pluginId:e}),!0}getExternalPlugins(){return Array.from(this.externalPlugins.entries()).map(([e,t])=>({id:e,...t}))}isExternalPlugin(e){return this.externalPlugins.has(e)}destroy(){this.components.forEach((t,i)=>{const n=i.dataset.cbType,s=this.plugins.get(n);s&&typeof s.unmount=="function"&&s.unmount(i,t)}),element.querySelectorAll('[contenteditable="true"]').forEach(t=>{t.removeAttribute("contenteditable"),t._cbObserver&&(t._cbObserver.disconnect(),delete t._cbObserver)}),this.plugins.forEach((t,i)=>{typeof t.destroy=="function"&&t.destroy(this)}),this.components.clear(),this.plugins.clear()}emit(e,t){if(this.runtime&&typeof this.runtime.emit=="function")this.runtime.emit(e,t);else{const i=new CustomEvent(`contentbox:${e}`,{detail:t});document.dispatchEvent(i)}}setRuntime(e){this.runtime=e}}class bn{constructor(e={}){const t={skin:"light",plugins:{},autoLoadPlugins:!0,pluginBaseUrl:"",allowExternalPlugins:!0,trustedDomains:[]};this.settings={...t,...e},this.initialized=!1,this.pluginManager=new mn({plugins:this.settings.plugins,autoLoad:this.settings.autoLoadPlugins,pluginBaseUrl:this.settings.pluginBaseUrl,allowExternalPlugins:this.settings.allowExternalPlugins,trustedDomains:this.settings.trustedDomains}),this.pluginManager.setRuntime(this),window.Glide=Rs,window.FormViewer=gn}async use(e){return await this.pluginManager.use(e)}getPlugin(e){return this.pluginManager.getPlugin(e)}getPlugins(){return this.pluginManager.getAllPlugins()}hasPlugin(e){return this.pluginManager.hasPlugin(e)}getComponent(e){return this.pluginManager.getComponent(e)}getComponents(){return this.pluginManager.components}async reinitialize(e){const t=await this.pluginManager.reinitialize(e);return this.initCore(),t}async loadExternalPlugin(e,t){return await this.pluginManager.loadExternalPlugin(e,t)}async loadExternalPlugins(e){return await this.pluginManager.loadExternalPlugins(e)}unregisterPlugin(e){return this.pluginManager.unregisterPlugin(e)}emit(e,t){const i=new CustomEvent(`contentbox:${e}`,{detail:t});document.dispatchEvent(i)}on(e,t){document.addEventListener(`contentbox:${e}`,t)}off(e,t){document.removeEventListener(`contentbox:${e}`,t)}init(){this.initialized||(this.initCore(),this.initPlugins(),this.initialized=!0,this.emit("init"))}async initPlugins(){this.settings.autoLoadPlugins&&await this.pluginManager.autoLoadPlugins(),this.pluginManager.initializeComponents()}initCore(){const e={touchNavigation:!0,selector:".glightbox",loop:!0,autoplayVideos:!0,zoomable:!0,slideEffect:"slide",descPosition:"bottom",skin:this.settings.skin};this.refreshLightbox(e),document.querySelectorAll(".is-lightbox,.block-click").forEach(i=>{const n=s=>{let r=i.getAttribute("href")||i.getAttribute("data-modal-url");this.openLightbox(r,i,e),s.preventDefault(),s.stopImmediatePropagation()};i.addEventListener("click",n),i._lightboxHandler=n})}refreshLightbox(e){this.glightbox&&this.glightbox.destroy(),this.glightbox=new jt(e),window.GLightbox=jt.default}openLightbox(e,t,i){let n=[],s=0;if(t){const o=t.closest("[data-gallery]");let a=[];o?a=o.querySelectorAll(".is-lightbox"):document.querySelectorAll(".is-lightbox,.block-click").forEach(u=>{u.closest("[data-gallery]")||a.push(u)}),a.forEach((d,u)=>{let l=d.getAttribute("href");l||(l=d.getAttribute("data-modal-url")),n.push({href:l}),e===l&&(s=u)})}const r=document.activeElement;r?.blur(),n.length>0?(this.glightbox.setElements(n),this.glightbox.openAt(s)):(this.glightbox.setElements([{href:e}]),this.glightbox.open()),this.glightbox.on("close",()=>{this.refreshLightbox(i),r?.focus()})}destroy(){this.glightbox&&(this.glightbox.destroy(),this.glightbox=null),document.querySelectorAll(".is-lightbox,.block-click").forEach(t=>{t._lightboxHandler&&(t.removeEventListener("click",t._lightboxHandler),delete t._lightboxHandler)}),this.pluginManager.destroy(),this.initialized=!1,this.emit("destroy")}}return bn})();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@innovastudio/contentbuilder-interactive-runtime",
3
3
  "type": "module",
4
- "version": "1.0.9",
4
+ "version": "1.0.10",
5
5
  "description": "Runtime libraries for ContentBuilder generated content",
6
6
  "main": "dist/contentbuilder-interactive-runtime.esm.js",
7
7
  "module": "dist/contentbuilder-interactive-runtime.esm.js",