@creaditor/web-starterkit 1.0.41 → 1.0.43

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.
@@ -1,693 +0,0 @@
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 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`
2
- <svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true">
3
- <path
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
- />
6
- </svg>
7
- `;class h extends o.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",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.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=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"}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=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`
8
- <button
9
- class="magic"
10
- type="button"
11
- title=${this.t("seo_generate_tooltip","Generate with AI")}
12
- aria-label=${this.t("seo_generate_tooltip","Generate with AI")}
13
- ?disabled=${this.loading||!!this.generatingField}
14
- @click=${()=>this.generateField(e)}
15
- >
16
- ${t?o.qy`<span class="mini-spinner"></span>`:d}
17
- </button>
18
- `}render(){const e=this.direction;return o.qy`
19
- <div class="backdrop" @click=${this.onCancel}></div>
20
- <div class="card" dir=${e}>
21
- <h2 class="heading">${this.t("ready_to_publish","Ready to publish")}</h2>
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>
25
-
26
- <div class="field">
27
- <div class="label-row">
28
- <label>${this.t("seo_title_label","Page name for search engines and social networks (SEO)")}</label>
29
- ${this.magicButton("title")}
30
- </div>
31
- <input
32
- type="text"
33
- maxlength=${100}
34
- ?disabled=${this.loading}
35
- .value=${this.title}
36
- placeholder=${this.t("seo_title_placeholder","Page name as it will appear in search engines")}
37
- @input=${e=>this.title=e.target.value}
38
- />
39
- <div class="counter">${this.title.length}/${100}</div>
40
- </div>
41
-
42
- <div class="field">
43
- <div class="label-row">
44
- <label>${this.t("seo_description_label","Page description for search engine and social network promotion (SEO)")}</label>
45
- ${this.magicButton("description")}
46
- </div>
47
- <textarea
48
- maxlength=${255}
49
- ?disabled=${this.loading}
50
- .value=${this.description}
51
- placeholder=${this.t("seo_description_placeholder","This description will appear below the site title in search engines")}
52
- @input=${e=>this.description=e.target.value}
53
- ></textarea>
54
- <div class="counter">${this.description.length}/${255}</div>
55
- </div>
56
-
57
- <div class="field">
58
- <div class="label-row">
59
- <label>${this.t("seo_keywords_label","Keywords")}</label>
60
- ${this.magicButton("keywords")}
61
- </div>
62
- <div
63
- class="chips ${this.keywordsFocused?"focused":""}"
64
- @click=${e=>{const t=e.currentTarget.querySelector("input");t?.focus()}}
65
- >
66
- ${this.keywordList.map((e,t)=>o.qy`
67
- <span class="chip">
68
- ${e}
69
- <button
70
- type="button"
71
- aria-label="remove"
72
- @click=${e=>{e.stopPropagation(),this.removeKeyword(t)}}
73
- >
74
- ✕
75
- </button>
76
- </span>
77
- `)}
78
- <input
79
- type="text"
80
- ?disabled=${this.loading}
81
- .value=${this.keywordDraft}
82
- placeholder=${this.keywordList.length?"":this.t("seo_keywords_placeholder","For example: gift,woman,gift for woman")}
83
- @input=${e=>this.keywordDraft=e.target.value}
84
- @keydown=${this.onKeywordKeydown}
85
- @focus=${()=>this.keywordsFocused=!0}
86
- @blur=${()=>{this.keywordsFocused=!1,this.commitDraftKeyword()}}
87
- />
88
- </div>
89
- </div>
90
-
91
- <div class="actions">
92
- <button class="publish" ?disabled=${this.loading} @click=${this.onPublish}>
93
- ${this.loading?o.qy`<span class="spinner"></span>`:this.t("publish","Publish")}
94
- </button>
95
- <button class="cancel" ?disabled=${this.loading} @click=${this.onCancel}>
96
- ${this.t("cancel","Cancel")}
97
- </button>
98
- </div>
99
- </div>
100
- `}}h.styles=o.AH`
101
- :host {
102
- position: fixed;
103
- inset: 0;
104
- z-index: var(--z-blocking, 2147483646);
105
- display: flex;
106
- align-items: center;
107
- justify-content: center;
108
- font-family: var(--font-family, Arial, Helvetica, sans-serif);
109
- }
110
- .backdrop {
111
- position: absolute;
112
- inset: 0;
113
- background: rgba(26, 26, 46, 0.45);
114
- animation: fade 0.2s ease forwards;
115
- }
116
- @keyframes fade {
117
- from { opacity: 0; }
118
- to { opacity: 1; }
119
- }
120
- .card {
121
- position: relative;
122
- background: #fff;
123
- border-radius: 14px;
124
- box-shadow: 0 24px 60px rgba(26, 26, 46, 0.28);
125
- width: 560px;
126
- max-width: 92vw;
127
- max-height: 90vh;
128
- overflow-y: auto;
129
- padding: 34px 40px 30px;
130
- box-sizing: border-box;
131
- opacity: 0;
132
- transform: scale(0.96);
133
- animation: pop 0.28s cubic-bezier(0.18, 0.89, 0.32, 1.28) forwards;
134
- }
135
- @keyframes pop {
136
- to { opacity: 1; transform: scale(1); }
137
- }
138
- .heading {
139
- font-size: 26px;
140
- font-weight: 700;
141
- color: #1a1a2e;
142
- margin: 0;
143
- }
144
- .subheading {
145
- font-size: 17px;
146
- font-weight: 600;
147
- color: #8a8f9c;
148
- margin: 10px 0 0;
149
- }
150
- .field {
151
- margin-top: 26px;
152
- }
153
- label {
154
- display: block;
155
- font-size: 14px;
156
- color: #3a3a4a;
157
- margin-bottom: 8px;
158
- }
159
- .label-row {
160
- display: flex;
161
- align-items: center;
162
- justify-content: space-between;
163
- gap: 8px;
164
- margin-bottom: 8px;
165
- }
166
- .label-row label {
167
- margin-bottom: 0;
168
- }
169
- .magic {
170
- flex: none;
171
- display: inline-flex;
172
- align-items: center;
173
- justify-content: center;
174
- width: 30px;
175
- height: 30px;
176
- border: 1px solid #d5d7de;
177
- border-radius: 8px;
178
- background: #fff;
179
- color: var(--color-primary, #6366f1);
180
- cursor: pointer;
181
- line-height: 1;
182
- transition: border-color 0.15s ease, background 0.15s ease;
183
- }
184
- .magic svg {
185
- display: block;
186
- }
187
- .magic:hover:not(:disabled) {
188
- border-color: var(--color-primary, #6366f1);
189
- background: color-mix(in srgb, var(--primary, #6c5ce7) 10%, #fff);
190
- }
191
- .magic:disabled {
192
- opacity: 0.5;
193
- cursor: not-allowed;
194
- }
195
- .mini-spinner {
196
- width: 14px;
197
- height: 14px;
198
- border: 2px solid color-mix(in srgb, var(--color-primary, #6366f1) 35%, transparent);
199
- border-top-color: var(--color-primary, #6366f1);
200
- border-radius: 50%;
201
- animation: spin 0.8s linear infinite;
202
- }
203
- input,
204
- textarea {
205
- width: 100%;
206
- box-sizing: border-box;
207
- border: 1px solid #d5d7de;
208
- border-radius: 8px;
209
- padding: 12px 14px;
210
- font-size: 15px;
211
- font-family: inherit;
212
- color: #1a1a2e;
213
- outline: none;
214
- transition: border-color 0.15s ease, box-shadow 0.15s ease;
215
- }
216
- input:focus,
217
- textarea:focus {
218
- /* --primary is the host's brand colour and may be a gradient, which is
219
- invalid for border-color and color-mix — those hosts lost the focus
220
- indicator entirely. The interaction colour is always flat. */
221
- border-color: var(--color-primary, #6366f1);
222
- box-shadow: 0 0 0 3px var(--color-primary-ring, rgba(99, 102, 241, 0.15));
223
- }
224
- textarea {
225
- resize: vertical;
226
- min-height: 84px;
227
- }
228
- input::placeholder,
229
- textarea::placeholder {
230
- color: #a7abb5;
231
- }
232
- .counter {
233
- margin-top: 6px;
234
- font-size: 12px;
235
- color: #a7abb5;
236
- }
237
- .chips {
238
- display: flex;
239
- flex-wrap: wrap;
240
- align-items: center;
241
- gap: 8px;
242
- border: 1px solid #d5d7de;
243
- border-radius: 8px;
244
- padding: 8px 10px;
245
- transition: border-color 0.15s ease, box-shadow 0.15s ease;
246
- }
247
- .chips.focused {
248
- border-color: var(--color-primary, #6366f1);
249
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary, #6366f1) 25%, transparent);
250
- }
251
- .chip {
252
- display: inline-flex;
253
- align-items: center;
254
- gap: 6px;
255
- /* Solid fallback first: if --primary carries a value color-mix can't
256
- parse (e.g. a bare RGB triplet), the mix declaration is dropped and
257
- this fixed tint keeps the chip visible. */
258
- background: #eceafc;
259
- background: color-mix(in srgb, var(--primary, #6c5ce7) 15%, #fff);
260
- color: var(--color-primary, #6366f1);
261
- border-radius: 6px;
262
- padding: 5px 8px 5px 10px;
263
- font-size: 14px;
264
- line-height: 1;
265
- white-space: nowrap;
266
- }
267
- .chip button {
268
- display: inline-flex;
269
- align-items: center;
270
- justify-content: center;
271
- width: 16px;
272
- height: 16px;
273
- border: none;
274
- background: transparent;
275
- color: inherit;
276
- font-size: 15px;
277
- line-height: 1;
278
- cursor: pointer;
279
- padding: 0;
280
- opacity: 0.7;
281
- }
282
- .chip button:hover {
283
- opacity: 1;
284
- }
285
- .chips input {
286
- flex: 1;
287
- min-width: 120px;
288
- border: none;
289
- padding: 4px;
290
- box-shadow: none;
291
- }
292
- .chips input:focus {
293
- border: none;
294
- box-shadow: none;
295
- }
296
- .actions {
297
- display: flex;
298
- align-items: center;
299
- justify-content: space-between;
300
- margin-top: 34px;
301
- }
302
- .publish {
303
- background: var(--primary, #6c5ce7);
304
- color: #fff;
305
- border: none;
306
- border-radius: 8px;
307
- padding: 14px 34px;
308
- font-size: 16px;
309
- font-weight: 600;
310
- font-family: inherit;
311
- cursor: pointer;
312
- transition: filter 0.15s ease, opacity 0.15s ease;
313
- }
314
- .publish:hover {
315
- filter: brightness(1.05);
316
- }
317
- .publish:disabled {
318
- opacity: 0.6;
319
- cursor: not-allowed;
320
- }
321
- .cancel {
322
- background: none;
323
- border: none;
324
- color: #4a4a5a;
325
- font-size: 16px;
326
- font-family: inherit;
327
- cursor: pointer;
328
- padding: 8px;
329
- }
330
- .cancel:hover {
331
- color: #1a1a2e;
332
- }
333
- .spinner {
334
- display: inline-block;
335
- width: 18px;
336
- height: 18px;
337
- border: 2px solid rgba(255, 255, 255, 0.5);
338
- border-top-color: #fff;
339
- border-radius: 50%;
340
- animation: spin 0.8s linear infinite;
341
- vertical-align: middle;
342
- }
343
- @keyframes spin {
344
- to { transform: rotate(360deg); }
345
- }
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 o.WF{constructor(){super(),this.raf=0,this.dismissTimer=null,this.onKeyDown=e=>{"Escape"===e.key&&this.dismiss()},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}connectedCallback(){super.connectedCallback(),document.addEventListener("keydown",this.onKeyDown)}firstUpdated(){this.launchConfetti(),this.dismissTimer=setTimeout(()=>this.dismiss(),6500)}disconnectedCallback(){super.disconnectedCallback(),cancelAnimationFrame(this.raf),this.dismissTimer&&clearTimeout(this.dismissTimer),document.removeEventListener("keydown",this.onKeyDown)}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(),h=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(h):t.clearRect(0,0,o,s)};this.raf=requestAnimationFrame(h)}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`
347
- <div class="preview">
348
- <p class="preview-caption">${this.t("search_preview_label","How your page will look on Google")}</p>
349
- <div class="result-head">
350
- <span class="favicon">${s}</span>
351
- <div class="result-url">
352
- <div class="result-host">${e.split("/")[0]}</div>
353
- <div class="result-crumb">${e}</div>
354
- </div>
355
- </div>
356
- ${t?o.qy`<p class="result-title">${t}</p>`:o.s6}
357
- ${i?o.qy`<p class="result-desc">${i}</p>`:o.s6}
358
- </div>
359
- `}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`
360
- <canvas @click=${this.dismiss}></canvas>
361
- <div class="card" dir=${e}>
362
- <button
363
- class="close"
364
- type="button"
365
- aria-label=${this.t("close","Close")}
366
- title=${this.t("close","Close")}
367
- @click=${this.dismiss}
368
- >
369
- <svg viewBox="0 0 24 24" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" /></svg>
370
- </button>
371
- <div class="check">
372
- <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
373
- <path d="M5 13l4 4L19 7" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
374
- </svg>
375
- </div>
376
- <p class="title">${this.message||this.t("published","Published!")}</p>
377
- <p class="sub">${this.sub||this.t("publishedSub","Your site is live.")}</p>
378
-
379
- ${this.renderPreview()}
380
-
381
- ${this.url?o.qy`
382
- <div class="actions">
383
- <a class="view" href=${this.url} target="_blank" rel="noopener">
384
- <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>
385
- ${this.t("view_site","View site")}
386
- </a>
387
- <button class="copy" type="button" @click=${this.copyLink}>
388
- ${t}
389
- ${this.copied?this.t("link_copied","Link copied"):this.t("copy_link","Copy link")}
390
- </button>
391
- </div>
392
- `:o.s6}
393
- </div>
394
- `}}p.styles=o.AH`
395
- :host {
396
- position: fixed;
397
- inset: 0;
398
- z-index: var(--z-blocking, 2147483646);
399
- pointer-events: none;
400
- display: block;
401
- font-family: var(--font-family, Arial, Helvetica, sans-serif);
402
- }
403
- canvas {
404
- position: absolute;
405
- inset: 0;
406
- width: 100%;
407
- height: 100%;
408
- }
409
- .card {
410
- position: absolute;
411
- top: 50%;
412
- left: 50%;
413
- transform: translate(-50%, -50%) scale(0.9);
414
- background: #ffffff;
415
- border-radius: 20px;
416
- box-shadow: 0 30px 80px rgba(26, 26, 46, 0.32);
417
- padding: 36px 40px 32px;
418
- text-align: center;
419
- pointer-events: auto;
420
- opacity: 0;
421
- animation: pop 0.44s cubic-bezier(0.18, 0.89, 0.32, 1.28) forwards;
422
- width: 460px;
423
- max-width: 90vw;
424
- box-sizing: border-box;
425
- }
426
- @keyframes pop {
427
- to {
428
- opacity: 1;
429
- transform: translate(-50%, -50%) scale(1);
430
- }
431
- }
432
- /* Manual dismiss. The card also auto-closes, but the confetti + preview
433
- invite lingering — the user needs a way out on their own terms.
434
- inset-inline-end so it lands top-left in the RTL card, top-right in LTR. */
435
- .close {
436
- position: absolute;
437
- top: 12px;
438
- inset-inline-end: 12px;
439
- width: 32px;
440
- height: 32px;
441
- display: flex;
442
- align-items: center;
443
- justify-content: center;
444
- padding: 0;
445
- border: none;
446
- border-radius: 50%;
447
- background: transparent;
448
- color: #a7abb5;
449
- cursor: pointer;
450
- transition: background 0.15s ease, color 0.15s ease;
451
- }
452
- .close:hover {
453
- background: #f1f2f6;
454
- color: #1a1a2e;
455
- }
456
- .close svg {
457
- width: 16px;
458
- height: 16px;
459
- }
460
- .check {
461
- width: 68px;
462
- height: 68px;
463
- border-radius: 50%;
464
- background: #2ec7a9;
465
- margin: 0 auto 18px;
466
- display: flex;
467
- align-items: center;
468
- justify-content: center;
469
- box-shadow: 0 0 0 0 rgba(46, 199, 169, 0.45);
470
- animation: ring 1.6s ease-out 0.2s;
471
- }
472
- @keyframes ring {
473
- 0% { box-shadow: 0 0 0 0 rgba(46, 199, 169, 0.45); }
474
- 70% { box-shadow: 0 0 0 18px rgba(46, 199, 169, 0); }
475
- 100% { box-shadow: 0 0 0 0 rgba(46, 199, 169, 0); }
476
- }
477
- .check svg {
478
- width: 34px;
479
- height: 34px;
480
- }
481
- .title {
482
- font-size: 24px;
483
- font-weight: 700;
484
- color: #1a1a2e;
485
- margin: 0;
486
- }
487
- .sub {
488
- font-size: 14px;
489
- color: #8a8f9c;
490
- margin: 8px 0 0;
491
- }
492
-
493
- /* Google-style search-result preview — the payoff for the SEO step. */
494
- .preview {
495
- margin-top: 22px;
496
- border: 1px solid #e6e8ee;
497
- border-radius: 12px;
498
- padding: 14px 16px;
499
- text-align: start;
500
- background: #fbfbfd;
501
- }
502
- .preview-caption {
503
- font-size: 11px;
504
- font-weight: 600;
505
- letter-spacing: 0.02em;
506
- text-transform: uppercase;
507
- color: #a7abb5;
508
- margin: 0 0 10px;
509
- }
510
- .result-head {
511
- display: flex;
512
- align-items: center;
513
- gap: 8px;
514
- }
515
- .favicon {
516
- flex: none;
517
- width: 26px;
518
- height: 26px;
519
- border-radius: 50%;
520
- background: var(--primary, #6c5ce7);
521
- color: #fff;
522
- display: flex;
523
- align-items: center;
524
- justify-content: center;
525
- font-size: 13px;
526
- font-weight: 700;
527
- text-transform: uppercase;
528
- }
529
- .result-url {
530
- min-width: 0;
531
- }
532
- .result-host {
533
- font-size: 13px;
534
- color: #1a1a2e;
535
- line-height: 1.2;
536
- white-space: nowrap;
537
- overflow: hidden;
538
- text-overflow: ellipsis;
539
- /* URLs read left-to-right even in an RTL card. */
540
- direction: ltr;
541
- text-align: start;
542
- }
543
- .result-crumb {
544
- font-size: 12px;
545
- color: #6a7280;
546
- line-height: 1.2;
547
- white-space: nowrap;
548
- overflow: hidden;
549
- text-overflow: ellipsis;
550
- direction: ltr;
551
- text-align: start;
552
- }
553
- .result-title {
554
- margin: 8px 0 0;
555
- font-size: 18px;
556
- line-height: 1.3;
557
- color: #1a56db;
558
- font-weight: 500;
559
- overflow: hidden;
560
- text-overflow: ellipsis;
561
- display: -webkit-box;
562
- -webkit-line-clamp: 1;
563
- -webkit-box-orient: vertical;
564
- }
565
- .result-desc {
566
- margin: 4px 0 0;
567
- font-size: 13px;
568
- line-height: 1.5;
569
- color: #4d5562;
570
- overflow: hidden;
571
- text-overflow: ellipsis;
572
- display: -webkit-box;
573
- -webkit-line-clamp: 2;
574
- -webkit-box-orient: vertical;
575
- }
576
-
577
- .actions {
578
- display: flex;
579
- align-items: center;
580
- justify-content: center;
581
- gap: 10px;
582
- margin-top: 24px;
583
- }
584
- .view {
585
- display: inline-flex;
586
- align-items: center;
587
- gap: 8px;
588
- background: var(--primary, #6c5ce7);
589
- color: #fff;
590
- text-decoration: none;
591
- font-size: 15px;
592
- font-weight: 600;
593
- padding: 12px 26px;
594
- border-radius: 10px;
595
- transition: filter 0.15s ease;
596
- }
597
- .view:hover {
598
- filter: brightness(1.06);
599
- }
600
- .view svg {
601
- width: 16px;
602
- height: 16px;
603
- }
604
- .copy {
605
- display: inline-flex;
606
- align-items: center;
607
- gap: 6px;
608
- background: #fff;
609
- color: #4a4a5a;
610
- border: 1px solid #d5d7de;
611
- font-size: 15px;
612
- font-family: inherit;
613
- font-weight: 500;
614
- padding: 12px 18px;
615
- border-radius: 10px;
616
- cursor: pointer;
617
- transition: border-color 0.15s ease, color 0.15s ease;
618
- }
619
- .copy:hover {
620
- border-color: var(--color-primary, #6366f1);
621
- color: #1a1a2e;
622
- }
623
- .copy svg {
624
- width: 15px;
625
- height: 15px;
626
- }
627
- `,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 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.onPublish||(this.onPublish=s.M.get("config","onPublish")),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 h.open(),!o))return;const a=o?o.seo:null;this.isLoading=!0,this.isLocked=!0;const l=Date.now();this.lockTimer=setTimeout(()=>{this.clearTimers(),this.isLoading=!1,this.isLocked=!1},1e4);try{if(t&&o){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,o):"function"==typeof this.onNextStep&&await this.onNextStep({toHTML:r.FE,toJSON:r.U2,seo:a??void 0})}finally{o.done()}return void(e||(this.emitPublished(i,a),this.celebratePublish(a)))}const e=s.M.get("config","editor","props","onSave"),l=Date.now();"function"==typeof e&&await e({transactions:[],toJson:r.U2,toJSON:r.U2,toHTML:r.FE});const c=Date.now()-l;c<3e3&&await new Promise(e=>setTimeout(e,3e3-c)),"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(s.M.get("lang","publishError")||"Publish failed")}finally{const e=Date.now()-l;e<400&&await new Promise(t=>setTimeout(t,400-e)),this.clearTimers(),this.isLoading=!1,this.isLocked=!1}}runHostPublish(e,t,i,o){return new Promise(a=>{let l=!1;const c=(e,r,n)=>{l||(l=!0,clearTimeout(d),o.done(),e?(this.celebratePublish(t),this.emitPublished(i,t)):(this.snackbar(n||s.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,o.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 o.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=s.M.get("commands");t?.snackbar?.(e)}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(u,{detail:i}))}emitPublishError(e,t,i){const o={channel:this.channel,selfPublish:e,seo:t??void 0,error:i};window.dispatchEvent(new CustomEvent(f,{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||"function"==typeof this.onPublish||"web"===this.channel&&e))return null;const t="web"===this.channel?"publish":"next";return o.qy`
628
- <paper-button
629
- @click=${this.handleClick}
630
- class="next-button"
631
- ?disabled=${this.isLocked}
632
- >
633
- ${this.isLoading?o.qy`
634
- <div class="loading-spinner"></div>
635
- `:o.qy`
636
- <div class="button-content ${this.isLoading?"loading":""}">
637
- <lang-text text=${t}></lang-text>
638
- </div>
639
- `}
640
- </paper-button>
641
- `}}g.styles=o.AH`
642
- :host {
643
- display: block;
644
- }
645
-
646
- .next-button {
647
- background: var(--primary);
648
- color: var(--primary-text);
649
- fill: var(--primary-text);
650
- text-transform: none;
651
- color: #fff;
652
- height: 52px;
653
- border-radius: 0;
654
- width: 90px;
655
- transform: translateX(-4px);
656
- position: relative;
657
- overflow: hidden;
658
- }
659
-
660
- .next-button:disabled {
661
- opacity: 0.6;
662
- cursor: not-allowed;
663
- }
664
-
665
- .loading-spinner {
666
- position: absolute;
667
- top: 50%;
668
- left: 50%;
669
- transform: translate(-50%, -50%);
670
- width: 20px;
671
- height: 20px;
672
- border: 2px solid transparent;
673
- border-top: 2px solid #fff;
674
- border-radius: 50%;
675
- animation: spin 1s linear infinite;
676
- }
677
-
678
- @keyframes spin {
679
- 0% { transform: translate(-50%, -50%) rotate(0deg); }
680
- 100% { transform: translate(-50%, -50%) rotate(360deg); }
681
- }
682
-
683
- .button-content {
684
- opacity: 1;
685
- transition: opacity 0.3s ease;
686
- }
687
-
688
- .button-content.loading {
689
- opacity: 0;
690
- }
691
-
692
-
693
- `,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)}}]);