@creaditor/web-starterkit 1.0.31 → 1.0.33

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.
@@ -0,0 +1,141 @@
1
+ # Publish hook — `onPublish`
2
+
3
+ On the **web** channel, clicking **Publish** runs a fixed sequence:
4
+
5
+ ```
6
+ SEO modal → same modal starts spinning → editor publishes → onPublish(...) → modal closes → confetti
7
+ └── your work here ──┘
8
+ ```
9
+
10
+ The **editor owns the publish**. `onPublish` is the notification that it
11
+ succeeded, fired while the modal is still spinning, so you can do your own
12
+ follow-up before the user sees the success card. Nothing is celebrated until
13
+ you say you are finished.
14
+
15
+ There is no full-screen loader: the SEO modal the user already has open stays
16
+ put and becomes the progress indicator — spinner in its Publish button, form
17
+ frozen, not dismissable — until you signal.
18
+
19
+ If you do not supply `onPublish`, the editor simply publishes and celebrates.
20
+
21
+ ## Signature
22
+
23
+ ```js
24
+ onPublish(details, { loading, success, confetti, error, commands })
25
+ ```
26
+
27
+ ### `details` — what the user typed in the SEO modal
28
+
29
+ ```js
30
+ { title: string, description: string, keywords: string[] }
31
+ ```
32
+
33
+ The editor has already saved this via `patchSite` before calling you; it is
34
+ passed along so you can mirror it into your own system.
35
+
36
+ ### Closing the loop — required
37
+
38
+ The modal stays up until you signal. Do exactly one of:
39
+
40
+ | Signal | Effect |
41
+ |---|---|
42
+ | `success()` or `confetti()` | Modal closes, success card + confetti, `creaditor:publish` fires. |
43
+ | `error(message?)` | Modal closes, failure snackbar, `creaditor:publish-error` fires. |
44
+ | return a promise | Resolution counts as `success()`, rejection as `error()`. |
45
+
46
+ `success()` and `confetti()` are the same call — invoking both yields one card.
47
+ Whichever signal lands first wins; the rest are ignored, so exactly one terminal
48
+ event fires per publish.
49
+
50
+ **If you never signal**, the editor gives up after 60 seconds, closes the modal
51
+ and celebrates anyway, with a console warning. Do not rely on this.
52
+
53
+ ### `loading(on = true)`
54
+
55
+ Toggles the modal's spinner. You rarely need it: the modal is already spinning
56
+ when you are called, and the editor closes it on your first completion signal.
57
+ A trailing `loading(false)` after `success()` is a harmless no-op, because by
58
+ then the modal is gone.
59
+
60
+ ### `commands` — offline hosts only
61
+
62
+ The editor can only publish when it has an access token (online mode). Hosts
63
+ running offline/embedded must publish themselves:
64
+
65
+ | Command | What it does |
66
+ |---|---|
67
+ | `patchSite(props)` | PATCH the site record. Pass `{ seo }` to store the SEO. |
68
+ | `publishSite()` | Publish the saved state. No argument needed — the endpoint takes the site id from the URL and has no body schema. |
69
+ | `toHTML()` / `toJSON()` | The same serializers handed to `onNextStep`. |
70
+
71
+ `patchSite` must land **before** `publishSite`. Online hosts should ignore
72
+ `commands` entirely — calling them there publishes a second time.
73
+
74
+ ## Example
75
+
76
+ ```js
77
+ onPublish: (details, { confetti, success, loading, error }) => {
78
+ // the site is already live; rebuild it on our side, then celebrate
79
+ fetch(`${myHost}/CreV3.ashx?mn=rebuildPage&pageLink=${showInMyDomain}`)
80
+ .then(() => { success(); confetti(); loading(false); })
81
+ .catch(() => { error(); loading(false); });
82
+ }
83
+ ```
84
+
85
+ Or, equivalently, by returning a promise:
86
+
87
+ ```js
88
+ onPublish: async () => {
89
+ await fetch(`${myHost}/CreV3.ashx?mn=rebuildPage&pageLink=${showInMyDomain}`);
90
+ }
91
+ ```
92
+
93
+ ## Testing it without a host page
94
+
95
+ `packages/core/publish-harness.html` boots the starterkit with an `onPublish`
96
+ handler and prints a timestamped timeline of the whole flow, so the hook can be
97
+ exercised without the ASPX host:
98
+
99
+ ```bash
100
+ cd packages/core
101
+ npm run build:web:starterkit
102
+ python3 -m http.server 8899 --directory .
103
+ # open http://localhost:8899/publish-harness.html
104
+ ```
105
+
106
+ Pick what the host does — `success()`, `error()`, return a promise, or signal
107
+ nothing (which exercises the 60s guard) — and watch the modal/card state at
108
+ each step. Leave the token box empty to run offline: everything except the
109
+ network publish still happens.
110
+
111
+ To exercise the real thing, paste an edit token. The production bundle targets
112
+ `api.creaditor.ai` regardless of where the page is served from (`isLocalhost()`
113
+ is a build-time `NODE_ENV` flag, not the hostname), so a local page plus a real
114
+ token is a genuine publish — **use a throwaway site**. The page documents both
115
+ ways to get a token.
116
+
117
+ ## Window events
118
+
119
+ Fired whether or not `onPublish` is wired, for hosts that would rather listen:
120
+
121
+ ```js
122
+ window.addEventListener("creaditor:publish", (e) => {
123
+ e.detail.seo; // { title, description, keywords }
124
+ e.detail.url; // live site URL, when the editor knows it
125
+ e.detail.channel; // "web"
126
+ e.detail.selfPublish; // true when the editor did the publishing
127
+ e.detail.toHTML; // serializers
128
+ e.detail.toJSON;
129
+ });
130
+
131
+ window.addEventListener("creaditor:publish-error", (e) => e.detail.error);
132
+ ```
133
+
134
+ ## Gotchas
135
+
136
+ - **Web channel only.** Newsletter/email keeps `onNextStep`; no SEO modal.
137
+ - **Cancelling the SEO modal aborts the publish.** `onPublish` is not called.
138
+ - **`onPublish` wins over `onNextStep`** on the web channel — if you supply
139
+ both, only `onPublish` runs.
140
+ - **Do not call `commands.publishSite()` from an online host.** The editor
141
+ already published before calling you.
package/README.md CHANGED
@@ -75,6 +75,14 @@ The key differences are:
75
75
  > sections). `sections` is currently empty for web and will be populated as
76
76
  > web section templates are added under `engines/web/sections`.
77
77
 
78
+ ### Publish
79
+
80
+ On the web channel the **Publish** button opens the SEO modal, publishes, then
81
+ calls your `onPublish` hook — with the loader still up — so you can run your own
82
+ follow-up before the user sees the success card. See
83
+ [PUBLISH-HOOK.md](./PUBLISH-HOOK.md) for the signature, how to signal
84
+ completion, and the `creaditor:publish` window event.
85
+
78
86
  ## 🏗️ How it's built
79
87
 
80
88
  This package is a thin wrapper. The bundle is produced from `@creaditor/core`:
@@ -1,10 +1,10 @@
1
- "use strict";(("undefined"!=typeof self?self:this).webpackChunkCreaditor=("undefined"!=typeof self?self:this).webpackChunkCreaditor||[]).push([[5909],{65909(e,t,i){i.r(t),i.d(t,{NextButton:()=>b,PUBLISH_ERROR_EVENT:()=>g,PUBLISH_EVENT:()=>f});var o=i(59929),s=i(86319),r=i(93432),n=i(90090),a=i(86959),l=i(15604);const c=`${(0,a.Ay)()?"http://127.0.0.1:8000":"https://studio.creaditor.ai"}/api/seo-generate`;const d=o.qy`
1
+ "use strict";(("undefined"!=typeof self?self:this).webpackChunkCreaditor=("undefined"!=typeof self?self:this).webpackChunkCreaditor||[]).push([[5909],{65909(e,t,i){i.r(t),i.d(t,{NextButton:()=>g,PUBLISH_ERROR_EVENT:()=>f,PUBLISH_EVENT:()=>u});var s=i(59929),o=i(86319),r=i(93432),n=i(90090),a=i(86959),l=i(15604);const c=`${(0,a.Ay)()?"http://127.0.0.1:8000":"https://studio.creaditor.ai"}/api/seo-generate`;const d=s.qy`
2
2
  <svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true">
3
3
  <path
4
4
  d="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"
5
5
  />
6
6
  </svg>
7
- `;class p extends o.WF{constructor(){super(),this.resolver=null,this.onCancel=()=>{this.loading||this.close(null)},this.onKeywordKeydown=e=>{"Enter"===e.key||","===e.key?(e.preventDefault(),this.commitDraftKeyword()):"Backspace"===e.key&&""===this.keywordDraft&&this.keywordList.length&&(this.keywordList=this.keywordList.slice(0,-1))},this.generateField=async e=>{if(this.loading||this.generatingField)return;const t=function(){const e=document.getElementById("main-page-wrapper");return(e?.innerText||"").trim()}();if(t){this.generatingField=e;try{const i="rtl"===this.direction?"he":"en",o=await this.requestSeo(e,t,i);if("keywords"===e){const e=(Array.isArray(o)?o:String(o).split(",")).map(e=>e.trim()).filter(Boolean),t=[...this.keywordList];for(const i of e)t.includes(i)||t.push(i);this.keywordList=t}else"title"===e?this.title=String(o).slice(0,100):this.description=String(o).slice(0,255)}catch(e){console.error("[seo-generate] failed:",e),this.snackbar(this.t("seo_generate_failed","Couldn't generate. Please try again."))}finally{this.generatingField=""}}else this.snackbar(this.t("seo_generate_empty","Add some content to the page first."))},this.onPublish=()=>{this.loading||(this.commitDraftKeyword(),this.loading=!0,this.close({title:this.title.trim(),description:this.description.trim(),keywords:[...this.keywordList]}))},this.loading=!1,this.title="",this.description="",this.keywordList=[],this.keywordDraft="",this.keywordsFocused=!1,this.generatingField=""}static open(){const e=document.createElement("cdtr-publish-seo"),t=s.M.get("app","seo")||{};return e.title=t.title||"",e.description=t.description||"",e.keywordList=(Array.isArray(t.keywords)?t.keywords:String(t.keywords||"").split(",")).map(e=>String(e).trim()).filter(Boolean),document.body.appendChild(e),e.waitForChoice()}waitForChoice(){return new Promise(e=>{this.resolver=e})}t(e,t){return s.M.get("lang",e)||t}get direction(){const e=s.M.get("theme");return e?.direction||"rtl"}close(e){this.resolver&&(this.resolver(e),this.resolver=null),this.isConnected&&this.remove()}commitDraftKeyword(){const e=this.keywordDraft.split(",").map(e=>e.trim()).filter(Boolean);if(!e.length)return;const t=[...this.keywordList];for(const i of e)t.includes(i)||t.push(i);this.keywordList=t,this.keywordDraft=""}removeKeyword(e){this.keywordList=this.keywordList.filter((t,i)=>i!==e)}snackbar(e){const t=s.M.get("commands");t?.snackbar?.(e)}async requestSeo(e,t,i){const o=async o=>{const s=await async function(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const t=l.Ay;if("function"==typeof t.studioTokenProvider)try{const i=await t.studioTokenProvider({forceRefresh:e});if("string"==typeof i&&i.length>0)return i}catch{}return"string"==typeof t.studioToken&&t.studioToken.length>0?t.studioToken:null}(o),r={"Content-Type":"application/json",Accept:"application/json"};return s&&(r.Authorization=`Bearer ${s}`),fetch(c,{method:"POST",headers:r,body:JSON.stringify({content:t,field:e,language:i})})};let s=await o(!1);if(401===s.status&&(s=await o(!0)),!s.ok)throw new Error(`HTTP ${s.status}`);const r=await s.json();if(!r.ok||void 0===r.value)throw new Error("bad response");return r.value}magicButton(e){const t=this.generatingField===e;return o.qy`
7
+ `;class h extends s.WF{constructor(){super(),this.resolver=null,this.dismiss=()=>{this.isConnected&&this.remove()},this.onCancel=()=>{this.loading||(this.settle(null),this.dismiss())},this.onKeywordKeydown=e=>{"Enter"===e.key||","===e.key?(e.preventDefault(),this.commitDraftKeyword()):"Backspace"===e.key&&""===this.keywordDraft&&this.keywordList.length&&(this.keywordList=this.keywordList.slice(0,-1))},this.generateField=async e=>{if(this.loading||this.generatingField)return;const t=function(){const e=document.getElementById("main-page-wrapper");return(e?.innerText||"").trim()}();if(t){this.generatingField=e;try{const i="rtl"===this.direction?"he":"en",s=await this.requestSeo(e,t,i);if("keywords"===e){const e=(Array.isArray(s)?s:String(s).split(",")).map(e=>e.trim()).filter(Boolean),t=[...this.keywordList];for(const i of e)t.includes(i)||t.push(i);this.keywordList=t}else"title"===e?this.title=String(s).slice(0,100):this.description=String(s).slice(0,255)}catch(e){console.error("[seo-generate] failed:",e),this.snackbar(this.t("seo_generate_failed","Couldn't generate. Please try again."))}finally{this.generatingField=""}}else this.snackbar(this.t("seo_generate_empty","Add some content to the page first."))},this.onPublish=()=>{this.loading||(this.commitDraftKeyword(),this.loading=!0,this.settle({seo:{title:this.title.trim(),description:this.description.trim(),keywords:[...this.keywordList]},busy:e=>{this.isConnected&&(this.loading=e)},done:this.dismiss}))},this.loading=!1,this.title="",this.description="",this.keywordList=[],this.keywordDraft="",this.keywordsFocused=!1,this.generatingField=""}static open(){const e=document.createElement("cdtr-publish-seo"),t=o.M.get("app","seo")||{};return e.title=t.title||"",e.description=t.description||"",e.keywordList=(Array.isArray(t.keywords)?t.keywords:String(t.keywords||"").split(",")).map(e=>String(e).trim()).filter(Boolean),document.body.appendChild(e),e.waitForChoice()}waitForChoice(){return new Promise(e=>{this.resolver=e})}t(e,t){return o.M.get("lang",e)||t}get direction(){const e=o.M.get("theme");return e?.direction||"rtl"}settle(e){this.resolver&&(this.resolver(e),this.resolver=null)}commitDraftKeyword(){const e=this.keywordDraft.split(",").map(e=>e.trim()).filter(Boolean);if(!e.length)return;const t=[...this.keywordList];for(const i of e)t.includes(i)||t.push(i);this.keywordList=t,this.keywordDraft=""}removeKeyword(e){this.keywordList=this.keywordList.filter((t,i)=>i!==e)}snackbar(e){const t=o.M.get("commands");t?.snackbar?.(e)}async requestSeo(e,t,i){const s=async s=>{const o=await async function(){let e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const t=l.Ay;if("function"==typeof t.studioTokenProvider)try{const i=await t.studioTokenProvider({forceRefresh:e});if("string"==typeof i&&i.length>0)return i}catch{}return"string"==typeof t.studioToken&&t.studioToken.length>0?t.studioToken:null}(s),r={"Content-Type":"application/json",Accept:"application/json"};return o&&(r.Authorization=`Bearer ${o}`),fetch(c,{method:"POST",headers:r,body:JSON.stringify({content:t,field:e,language:i})})};let o=await s(!1);if(401===o.status&&(o=await s(!0)),!o.ok)throw new Error(`HTTP ${o.status}`);const r=await o.json();if(!r.ok||void 0===r.value)throw new Error("bad response");return r.value}magicButton(e){const t=this.generatingField===e;return s.qy`
8
8
  <button
9
9
  class="magic"
10
10
  type="button"
@@ -13,13 +13,15 @@
13
13
  ?disabled=${this.loading||!!this.generatingField}
14
14
  @click=${()=>this.generateField(e)}
15
15
  >
16
- ${t?o.qy`<span class="mini-spinner"></span>`:d}
16
+ ${t?s.qy`<span class="mini-spinner"></span>`:d}
17
17
  </button>
18
- `}render(){const e=this.direction;return o.qy`
18
+ `}render(){const e=this.direction;return s.qy`
19
19
  <div class="backdrop" @click=${this.onCancel}></div>
20
20
  <div class="card" dir=${e}>
21
21
  <h2 class="heading">${this.t("ready_to_publish","Ready to publish")}</h2>
22
- <p class="subheading">${this.t("lets_set_some_seo","Let's set some SEO")}</p>
22
+ <p class="subheading">
23
+ ${this.loading?this.t("publishing_your_site","Publishing your site…"):this.t("lets_set_some_seo","Let's set some SEO")}
24
+ </p>
23
25
 
24
26
  <div class="field">
25
27
  <div class="label-row">
@@ -29,6 +31,7 @@
29
31
  <input
30
32
  type="text"
31
33
  maxlength=${100}
34
+ ?disabled=${this.loading}
32
35
  .value=${this.title}
33
36
  placeholder=${this.t("seo_title_placeholder","Page name as it will appear in search engines")}
34
37
  @input=${e=>this.title=e.target.value}
@@ -43,6 +46,7 @@
43
46
  </div>
44
47
  <textarea
45
48
  maxlength=${255}
49
+ ?disabled=${this.loading}
46
50
  .value=${this.description}
47
51
  placeholder=${this.t("seo_description_placeholder","This description will appear below the site title in search engines")}
48
52
  @input=${e=>this.description=e.target.value}
@@ -59,7 +63,7 @@
59
63
  class="chips ${this.keywordsFocused?"focused":""}"
60
64
  @click=${e=>{const t=e.currentTarget.querySelector("input");t?.focus()}}
61
65
  >
62
- ${this.keywordList.map((e,t)=>o.qy`
66
+ ${this.keywordList.map((e,t)=>s.qy`
63
67
  <span class="chip">
64
68
  ${e}
65
69
  <button
@@ -73,6 +77,7 @@
73
77
  `)}
74
78
  <input
75
79
  type="text"
80
+ ?disabled=${this.loading}
76
81
  .value=${this.keywordDraft}
77
82
  placeholder=${this.keywordList.length?"":this.t("seo_keywords_placeholder","For example: gift,woman,gift for woman")}
78
83
  @input=${e=>this.keywordDraft=e.target.value}
@@ -85,14 +90,14 @@
85
90
 
86
91
  <div class="actions">
87
92
  <button class="publish" ?disabled=${this.loading} @click=${this.onPublish}>
88
- ${this.loading?o.qy`<span class="spinner"></span>`:this.t("publish","Publish")}
93
+ ${this.loading?s.qy`<span class="spinner"></span>`:this.t("publish","Publish")}
89
94
  </button>
90
95
  <button class="cancel" ?disabled=${this.loading} @click=${this.onCancel}>
91
96
  ${this.t("cancel","Cancel")}
92
97
  </button>
93
98
  </div>
94
99
  </div>
95
- `}}p.styles=o.AH`
100
+ `}}h.styles=s.AH`
96
101
  :host {
97
102
  position: fixed;
98
103
  inset: 0;
@@ -338,20 +343,20 @@
338
343
  @keyframes spin {
339
344
  to { transform: rotate(360deg); }
340
345
  }
341
- `,p.properties={loading:{type:Boolean},title:{type:String},description:{type:String},keywordList:{type:Array},keywordDraft:{type:String},keywordsFocused:{type:Boolean},generatingField:{type:String}},customElements.define("cdtr-publish-seo",p);var h=i(65550);class u extends o.WF{constructor(){super(),this.raf=0,this.dismissTimer=null,this.dismiss=()=>{this.isConnected&&this.remove()},this.copyLink=async()=>{if(this.url)try{await navigator.clipboard.writeText(this.url),this.copied=!0,setTimeout(()=>this.copied=!1,2e3)}catch{}},this.message="",this.sub="",this.url="",this.seoTitle="",this.seoDescription="",this.copied=!1}firstUpdated(){this.launchConfetti(),this.dismissTimer=setTimeout(()=>this.dismiss(),6500)}disconnectedCallback(){super.disconnectedCallback(),cancelAnimationFrame(this.raf),this.dismissTimer&&clearTimeout(this.dismissTimer)}t(e,t){return s.M.get("lang",e)||t}get direction(){const e=s.M.get("theme");return e?.direction||"rtl"}get host(){return this.url?this.url.replace(/^https?:\/\//,"").replace(/\/+$/,""):""}launchConfetti(){const e=this.renderRoot.querySelector("canvas");if(!e)return;const t=e.getContext("2d");if(!t)return;const i=window.devicePixelRatio||1,o=window.innerWidth,s=window.innerHeight;e.width=o*i,e.height=s*i,t.scale(i,i);const r=["#e94560","#1a1a2e","#f6c945","#2ec7a9","#4d7cfe","#ffffff"],n=o/2,a=.42*s,l=Math.min(220,Math.round(o/6)),c=Array.from({length:l},(e,t)=>{const i=2*Math.PI*t/l+t%3*.4,o=4+t%9;return{x:n,y:a,vx:Math.cos(i)*o,vy:Math.sin(i)*o-3,size:5+t%5,color:r[t%r.length],rot:t,vrot:(t%2?1:-1)*(.1+t%5*.03),life:1}}),d=performance.now(),p=e=>{const i=e-d;t.clearRect(0,0,o,s);let r=!1;for(const e of c)e.vy+=.16,e.x+=e.vx,e.y+=e.vy,e.rot+=e.vrot,i>2600*.55&&(e.life=Math.max(0,e.life-.02)),e.life>0&&e.y<s+40&&(r=!0),t.save(),t.globalAlpha=e.life,t.translate(e.x,e.y),t.rotate(e.rot),t.fillStyle=e.color,t.fillRect(-e.size/2,-e.size/2,e.size,.6*e.size),t.restore();r&&i<3e3?this.raf=requestAnimationFrame(p):t.clearRect(0,0,o,s)};this.raf=requestAnimationFrame(p)}renderPreview(){const e=this.host;if(!e)return o.s6;const t=(this.seoTitle||"").trim()||(this.seoDescription?"":e),i=(this.seoDescription||"").trim(),s=(t||e).charAt(0);return o.qy`
346
+ `,h.properties={loading:{type:Boolean},title:{type:String},description:{type:String},keywordList:{type:Array},keywordDraft:{type:String},keywordsFocused:{type:Boolean},generatingField:{type:String}},customElements.define("cdtr-publish-seo",h);class p extends s.WF{constructor(){super(),this.raf=0,this.dismissTimer=null,this.dismiss=()=>{this.isConnected&&this.remove()},this.copyLink=async()=>{if(this.url)try{await navigator.clipboard.writeText(this.url),this.copied=!0,setTimeout(()=>this.copied=!1,2e3)}catch{}},this.message="",this.sub="",this.url="",this.seoTitle="",this.seoDescription="",this.copied=!1}firstUpdated(){this.launchConfetti(),this.dismissTimer=setTimeout(()=>this.dismiss(),6500)}disconnectedCallback(){super.disconnectedCallback(),cancelAnimationFrame(this.raf),this.dismissTimer&&clearTimeout(this.dismissTimer)}t(e,t){return o.M.get("lang",e)||t}get direction(){const e=o.M.get("theme");return e?.direction||"rtl"}get host(){return this.url?this.url.replace(/^https?:\/\//,"").replace(/\/+$/,""):""}launchConfetti(){const e=this.renderRoot.querySelector("canvas");if(!e)return;const t=e.getContext("2d");if(!t)return;const i=window.devicePixelRatio||1,s=window.innerWidth,o=window.innerHeight;e.width=s*i,e.height=o*i,t.scale(i,i);const r=["#e94560","#1a1a2e","#f6c945","#2ec7a9","#4d7cfe","#ffffff"],n=s/2,a=.42*o,l=Math.min(220,Math.round(s/6)),c=Array.from({length:l},(e,t)=>{const i=2*Math.PI*t/l+t%3*.4,s=4+t%9;return{x:n,y:a,vx:Math.cos(i)*s,vy:Math.sin(i)*s-3,size:5+t%5,color:r[t%r.length],rot:t,vrot:(t%2?1:-1)*(.1+t%5*.03),life:1}}),d=performance.now(),h=e=>{const i=e-d;t.clearRect(0,0,s,o);let r=!1;for(const e of c)e.vy+=.16,e.x+=e.vx,e.y+=e.vy,e.rot+=e.vrot,i>2600*.55&&(e.life=Math.max(0,e.life-.02)),e.life>0&&e.y<o+40&&(r=!0),t.save(),t.globalAlpha=e.life,t.translate(e.x,e.y),t.rotate(e.rot),t.fillStyle=e.color,t.fillRect(-e.size/2,-e.size/2,e.size,.6*e.size),t.restore();r&&i<3e3?this.raf=requestAnimationFrame(h):t.clearRect(0,0,s,o)};this.raf=requestAnimationFrame(h)}renderPreview(){const e=this.host;if(!e)return s.s6;const t=(this.seoTitle||"").trim()||(this.seoDescription?"":e),i=(this.seoDescription||"").trim(),o=(t||e).charAt(0);return s.qy`
342
347
  <div class="preview">
343
348
  <p class="preview-caption">${this.t("search_preview_label","How your page will look on Google")}</p>
344
349
  <div class="result-head">
345
- <span class="favicon">${s}</span>
350
+ <span class="favicon">${o}</span>
346
351
  <div class="result-url">
347
352
  <div class="result-host">${e.split("/")[0]}</div>
348
353
  <div class="result-crumb">${e}</div>
349
354
  </div>
350
355
  </div>
351
- ${t?o.qy`<p class="result-title">${t}</p>`:o.s6}
352
- ${i?o.qy`<p class="result-desc">${i}</p>`:o.s6}
356
+ ${t?s.qy`<p class="result-title">${t}</p>`:s.s6}
357
+ ${i?s.qy`<p class="result-desc">${i}</p>`:s.s6}
353
358
  </div>
354
- `}render(){const e=this.direction,t=this.copied?o.qy`<svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" /></svg>`:o.qy`<svg viewBox="0 0 24 24" fill="none"><rect x="9" y="9" width="11" height="11" rx="2" stroke="currentColor" stroke-width="2" /><path d="M5 15V5a2 2 0 0 1 2-2h10" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>`;return o.qy`
359
+ `}render(){const e=this.direction,t=this.copied?s.qy`<svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" /></svg>`:s.qy`<svg viewBox="0 0 24 24" fill="none"><rect x="9" y="9" width="11" height="11" rx="2" stroke="currentColor" stroke-width="2" /><path d="M5 15V5a2 2 0 0 1 2-2h10" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>`;return s.qy`
355
360
  <canvas @click=${this.dismiss}></canvas>
356
361
  <div class="card" dir=${e}>
357
362
  <div class="check">
@@ -364,7 +369,7 @@
364
369
 
365
370
  ${this.renderPreview()}
366
371
 
367
- ${this.url?o.qy`
372
+ ${this.url?s.qy`
368
373
  <div class="actions">
369
374
  <a class="view" href=${this.url} target="_blank" rel="noopener">
370
375
  <svg viewBox="0 0 24 24" fill="none"><path d="M14 4h6v6M20 4l-9 9M18 13v5a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
@@ -375,9 +380,9 @@
375
380
  ${this.copied?this.t("link_copied","Link copied"):this.t("copy_link","Copy link")}
376
381
  </button>
377
382
  </div>
378
- `:o.s6}
383
+ `:s.s6}
379
384
  </div>
380
- `}}u.styles=o.AH`
385
+ `}}p.styles=s.AH`
381
386
  :host {
382
387
  position: fixed;
383
388
  inset: 0;
@@ -582,21 +587,21 @@
582
587
  width: 15px;
583
588
  height: 15px;
584
589
  }
585
- `,u.properties={message:{type:String},sub:{type:String},url:{type:String},seoTitle:{type:String},seoDescription:{type:String},copied:{type:Boolean}},customElements.define("cdtr-publish-success",u);const f="creaditor:publish",g="creaditor:publish-error";class b extends o.WF{constructor(){super(),this.channel="",this.isLoading=!1,this.isLocked=!1,this.lockTimer=null}connectedCallback(){super.connectedCallback(),this.onNextStep||(this.onNextStep=s.M.get("config","onNextStep")),this.channel||(this.channel=s.M.get("config","channel")||"")}disconnectedCallback(){super.disconnectedCallback(),this.clearTimers()}clearTimers(){this.lockTimer&&(clearTimeout(this.lockTimer),this.lockTimer=null)}async handleClick(){if(this.isLocked||this.isLoading)return;const e=!!s.M.get("config","access"),t="web"===this.channel,i=e&&t;let o=null;if(!t||(o=await p.open(),o)){this.isLoading=!0,this.isLocked=!0,this.lockTimer=setTimeout(()=>{this.clearTimers(),this.isLoading=!1,this.isLocked=!1},1e4);try{if(t){const e=this.showBuildingLoader();try{i&&o&&(await(0,n.YE)({seo:o}),await(0,n.bK)({})),"function"==typeof this.onNextStep&&await this.onNextStep({toHTML:r.FE,toJSON:r.U2,seo:o??void 0})}finally{e()}return this.emitPublished(i,o),void this.celebratePublish(o)}"function"==typeof this.onNextStep&&await this.onNextStep({toHTML:r.FE,toJSON:r.U2}),this.emitPublished(i,o)}catch(e){console.error("Error in publish/onNextStep:",e),this.emitPublishError(i,o,e),this.snackbar(s.M.get("lang","publishError")||"Publish failed")}finally{this.clearTimers(),this.isLoading=!1,this.isLocked=!1}}}snackbar(e){const t=s.M.get("commands");t?.snackbar?.(e)}showBuildingLoader(){const e=(0,h.A)();document.body.appendChild(e);let t=!1;return()=>{t||(t=!0,e.style.transition="opacity 300ms ease",e.style.opacity="0",e.style.pointerEvents="none",setTimeout(()=>e.remove(),320))}}siteUrl(){const e=s.M.get("app","domains"),t=e?.[0]?.name;if(t)return/^https?:\/\//.test(t)?t:`https://${t}`}emitPublished(e,t){const i={channel:this.channel,selfPublish:e,seo:t??void 0,url:this.siteUrl(),toHTML:r.FE,toJSON:r.U2};window.dispatchEvent(new CustomEvent(f,{detail:i}))}emitPublishError(e,t,i){const o={channel:this.channel,selfPublish:e,seo:t??void 0,error:i};window.dispatchEvent(new CustomEvent(g,{detail:o}))}celebratePublish(e){const t=document.createElement("cdtr-publish-success");e&&(t.seoTitle=e.title,t.seoDescription=e.description);const i=this.siteUrl();i&&(t.url=i),document.body.appendChild(t)}render(){const e=!!s.M.get("config","access");if(!(this.onNextStep||"web"===this.channel&&e))return null;const t="web"===this.channel?"publish":"next";return o.qy`
590
+ `,p.properties={message:{type:String},sub:{type:String},url:{type:String},seoTitle:{type:String},seoDescription:{type:String},copied:{type:Boolean}},customElements.define("cdtr-publish-success",p);const u="creaditor:publish",f="creaditor:publish-error";class g extends s.WF{constructor(){super(),this.channel="",this.isLoading=!1,this.isLocked=!1,this.lockTimer=null}connectedCallback(){super.connectedCallback(),this.onNextStep||(this.onNextStep=o.M.get("config","onNextStep")),this.onPublish||(this.onPublish=o.M.get("config","onPublish")),this.channel||(this.channel=o.M.get("config","channel")||"")}disconnectedCallback(){super.disconnectedCallback(),this.clearTimers()}clearTimers(){this.lockTimer&&(clearTimeout(this.lockTimer),this.lockTimer=null)}async handleClick(){if(this.isLocked||this.isLoading)return;const e=!!o.M.get("config","access"),t="web"===this.channel,i=e&&t;let s=null;if(t&&(s=await h.open(),!s))return;const a=s?s.seo:null;this.isLoading=!0,this.isLocked=!0,this.lockTimer=setTimeout(()=>{this.clearTimers(),this.isLoading=!1,this.isLocked=!1},1e4);try{if(t&&s){let e=!1;try{i&&a&&(await(0,n.YE)({seo:a}),await(0,n.bK)({})),"function"==typeof this.onPublish?e=await this.runHostPublish(this.onPublish,a,i,s):"function"==typeof this.onNextStep&&await this.onNextStep({toHTML:r.FE,toJSON:r.U2,seo:a??void 0})}finally{s.done()}return void(e||(this.emitPublished(i,a),this.celebratePublish(a)))}"function"==typeof this.onNextStep&&await this.onNextStep({toHTML:r.FE,toJSON:r.U2}),this.emitPublished(i,a)}catch(e){console.error("Error in publish/onNextStep:",e),this.emitPublishError(i,a,e),this.snackbar(o.M.get("lang","publishError")||"Publish failed")}finally{this.clearTimers(),this.isLoading=!1,this.isLocked=!1}}runHostPublish(e,t,i,s){return new Promise(a=>{let l=!1;const c=(e,r,n)=>{l||(l=!0,clearTimeout(d),s.done(),e?(this.celebratePublish(t),this.emitPublished(i,t)):(this.snackbar(n||o.M.get("lang","publishError")||"Publish failed"),this.emitPublishError(i,t,r)),a(!0))},d=setTimeout(()=>{l||(console.warn("config.onPublish never signalled completion — finishing the publish without it. Call success() or error() (or return a promise) so the modal can come down."),l=!0,s.done(),a(!1))},6e4),h={title:t.title,description:t.description,keywords:t.keywords},p={loading:function(){let e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];return s.busy(e)},success:()=>c(!0),confetti:()=>c(!0),error:e=>c(!1,new Error(e||"host publish failed"),e),commands:{patchSite:e=>(0,n.YE)(e??{}),publishSite:e=>(0,n.bK)(e??{}),toHTML:r.FE,toJSON:r.U2}};let u;try{u=e(h,p)}catch(e){return console.error("Error in config.onPublish:",e),void c(!1,e)}u&&"function"==typeof u.then&&Promise.resolve(u).then(()=>c(!0),e=>{console.error("Error in config.onPublish:",e),c(!1,e)})})}snackbar(e){const t=o.M.get("commands");t?.snackbar?.(e)}siteUrl(){const e=o.M.get("app","domains"),t=e?.[0]?.name;if(t)return/^https?:\/\//.test(t)?t:`https://${t}`}emitPublished(e,t){const i={channel:this.channel,selfPublish:e,seo:t??void 0,url:this.siteUrl(),toHTML:r.FE,toJSON:r.U2};window.dispatchEvent(new CustomEvent(u,{detail:i}))}emitPublishError(e,t,i){const s={channel:this.channel,selfPublish:e,seo:t??void 0,error:i};window.dispatchEvent(new CustomEvent(f,{detail:s}))}celebratePublish(e){const t=document.createElement("cdtr-publish-success");e&&(t.seoTitle=e.title,t.seoDescription=e.description);const i=this.siteUrl();i&&(t.url=i),document.body.appendChild(t)}render(){const e=!!o.M.get("config","access");if(!(this.onNextStep||"function"==typeof this.onPublish||"web"===this.channel&&e))return null;const t="web"===this.channel?"publish":"next";return s.qy`
586
591
  <paper-button
587
592
  @click=${this.handleClick}
588
593
  class="next-button"
589
594
  ?disabled=${this.isLocked}
590
595
  >
591
- ${this.isLoading?o.qy`
596
+ ${this.isLoading?s.qy`
592
597
  <div class="loading-spinner"></div>
593
- `:o.qy`
598
+ `:s.qy`
594
599
  <div class="button-content ${this.isLoading?"loading":""}">
595
600
  <lang-text text=${t}></lang-text>
596
601
  </div>
597
602
  `}
598
603
  </paper-button>
599
- `}}b.styles=o.AH`
604
+ `}}g.styles=s.AH`
600
605
  :host {
601
606
  display: block;
602
607
  }
@@ -648,4 +653,4 @@
648
653
  }
649
654
 
650
655
 
651
- `,b.properties={onNextStep:{type:Function},channel:{type:String},isLoading:{type:Boolean},isLocked:{type:Boolean},lockTimer:{type:Number}},customElements.define("cdtr-next-button",b)}}]);
656
+ `,g.properties={onNextStep:{type:Function},onPublish:{type:Function},channel:{type:String},isLoading:{type:Boolean},isLocked:{type:Boolean},lockTimer:{type:Number}},customElements.define("cdtr-next-button",g)}}]);
Binary file