@creaditor/web-starterkit 1.0.31 → 1.0.32
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.
- package/PUBLISH-HOOK.md +136 -0
- package/README.md +8 -0
- package/dist/5909.creaditor.bundle.js +2 -2
- package/dist/5909.creaditor.bundle.js.gz +0 -0
- package/dist/creaditor.bundle.js +1 -1
- package/dist/creaditor.bundle.js.gz +0 -0
- package/dist/service-worker.js +1 -1
- package/dist/service-worker.js.gz +0 -0
- package/package.json +3 -2
package/PUBLISH-HOOK.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Publish hook — `onPublish`
|
|
2
|
+
|
|
3
|
+
On the **web** channel, clicking **Publish** runs a fixed sequence:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
SEO modal → loader up → editor publishes → onPublish(...) → loader down → confetti
|
|
7
|
+
└── your work happens here ──┘
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The **editor owns the publish**. `onPublish` is the notification that it
|
|
11
|
+
succeeded, fired while the loader is still up, so you can do your own follow-up
|
|
12
|
+
before the user sees the success card. Nothing is celebrated until you say you
|
|
13
|
+
are finished.
|
|
14
|
+
|
|
15
|
+
If you do not supply `onPublish`, the editor simply publishes and celebrates.
|
|
16
|
+
|
|
17
|
+
## Signature
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
onPublish(details, { loading, success, confetti, error, commands })
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### `details` — what the user typed in the SEO modal
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
{ title: string, description: string, keywords: string[] }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The editor has already saved this via `patchSite` before calling you; it is
|
|
30
|
+
passed along so you can mirror it into your own system.
|
|
31
|
+
|
|
32
|
+
### Closing the loop — required
|
|
33
|
+
|
|
34
|
+
The loader stays up until you signal. Do exactly one of:
|
|
35
|
+
|
|
36
|
+
| Signal | Effect |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `success()` or `confetti()` | Loader down, success card + confetti, `creaditor:publish` fires. |
|
|
39
|
+
| `error(message?)` | Loader down, failure snackbar, `creaditor:publish-error` fires. |
|
|
40
|
+
| return a promise | Resolution counts as `success()`, rejection as `error()`. |
|
|
41
|
+
|
|
42
|
+
`success()` and `confetti()` are the same call — invoking both yields one card.
|
|
43
|
+
Whichever signal lands first wins; the rest are ignored, so exactly one terminal
|
|
44
|
+
event fires per publish.
|
|
45
|
+
|
|
46
|
+
**If you never signal**, the editor gives up after 60 seconds, drops the loader
|
|
47
|
+
and celebrates anyway, with a console warning. Do not rely on this.
|
|
48
|
+
|
|
49
|
+
### `loading(on = true)`
|
|
50
|
+
|
|
51
|
+
Mounts / dismisses the loader. You rarely need it: the loader is already up when
|
|
52
|
+
you are called, and the editor drops it on your first completion signal. A
|
|
53
|
+
trailing `loading(false)` after `success()` is a harmless no-op.
|
|
54
|
+
|
|
55
|
+
### `commands` — offline hosts only
|
|
56
|
+
|
|
57
|
+
The editor can only publish when it has an access token (online mode). Hosts
|
|
58
|
+
running offline/embedded must publish themselves:
|
|
59
|
+
|
|
60
|
+
| Command | What it does |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `patchSite(props)` | PATCH the site record. Pass `{ seo }` to store the SEO. |
|
|
63
|
+
| `publishSite()` | Publish the saved state. No argument needed — the endpoint takes the site id from the URL and has no body schema. |
|
|
64
|
+
| `toHTML()` / `toJSON()` | The same serializers handed to `onNextStep`. |
|
|
65
|
+
|
|
66
|
+
`patchSite` must land **before** `publishSite`. Online hosts should ignore
|
|
67
|
+
`commands` entirely — calling them there publishes a second time.
|
|
68
|
+
|
|
69
|
+
## Example
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
onPublish: (details, { confetti, success, loading, error }) => {
|
|
73
|
+
// the site is already live; rebuild it on our side, then celebrate
|
|
74
|
+
fetch(`${myHost}/CreV3.ashx?mn=rebuildPage&pageLink=${showInMyDomain}`)
|
|
75
|
+
.then(() => { success(); confetti(); loading(false); })
|
|
76
|
+
.catch(() => { error(); loading(false); });
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or, equivalently, by returning a promise:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
onPublish: async () => {
|
|
84
|
+
await fetch(`${myHost}/CreV3.ashx?mn=rebuildPage&pageLink=${showInMyDomain}`);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Testing it without a host page
|
|
89
|
+
|
|
90
|
+
`packages/core/publish-harness.html` boots the starterkit with an `onPublish`
|
|
91
|
+
handler and prints a timestamped timeline of the whole flow, so the hook can be
|
|
92
|
+
exercised without the ASPX host:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
cd packages/core
|
|
96
|
+
npm run build:web:starterkit
|
|
97
|
+
python3 -m http.server 8899 --directory .
|
|
98
|
+
# open http://localhost:8899/publish-harness.html
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Pick what the host does — `success()`, `error()`, return a promise, or signal
|
|
102
|
+
nothing (which exercises the 60s guard) — and watch the loader/card state at
|
|
103
|
+
each step. Leave the token box empty to run offline: everything except the
|
|
104
|
+
network publish still happens.
|
|
105
|
+
|
|
106
|
+
To exercise the real thing, paste an edit token. The production bundle targets
|
|
107
|
+
`api.creaditor.ai` regardless of where the page is served from (`isLocalhost()`
|
|
108
|
+
is a build-time `NODE_ENV` flag, not the hostname), so a local page plus a real
|
|
109
|
+
token is a genuine publish — **use a throwaway site**. The page documents both
|
|
110
|
+
ways to get a token.
|
|
111
|
+
|
|
112
|
+
## Window events
|
|
113
|
+
|
|
114
|
+
Fired whether or not `onPublish` is wired, for hosts that would rather listen:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
window.addEventListener("creaditor:publish", (e) => {
|
|
118
|
+
e.detail.seo; // { title, description, keywords }
|
|
119
|
+
e.detail.url; // live site URL, when the editor knows it
|
|
120
|
+
e.detail.channel; // "web"
|
|
121
|
+
e.detail.selfPublish; // true when the editor did the publishing
|
|
122
|
+
e.detail.toHTML; // serializers
|
|
123
|
+
e.detail.toJSON;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
window.addEventListener("creaditor:publish-error", (e) => e.detail.error);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Gotchas
|
|
130
|
+
|
|
131
|
+
- **Web channel only.** Newsletter/email keeps `onNextStep`; no SEO modal.
|
|
132
|
+
- **Cancelling the SEO modal aborts the publish.** `onPublish` is not called.
|
|
133
|
+
- **`onPublish` wins over `onNextStep`** on the web channel — if you supply
|
|
134
|
+
both, only `onPublish` runs.
|
|
135
|
+
- **Do not call `commands.publishSite()` from an online host.** The editor
|
|
136
|
+
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`:
|
|
@@ -582,7 +582,7 @@
|
|
|
582
582
|
width: 15px;
|
|
583
583
|
height: 15px;
|
|
584
584
|
}
|
|
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),
|
|
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.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 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();let t=!1;try{i&&o&&(await(0,n.YE)({seo:o}),await(0,n.bK)({})),"function"==typeof this.onPublish?t=await this.runHostPublish(this.onPublish,o,i,e):"function"==typeof this.onNextStep&&await this.onNextStep({toHTML:r.FE,toJSON:r.U2,seo:o??void 0})}finally{e()}return void(t||(this.emitPublished(i,o),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}}}runHostPublish(e,t,i,o){var a=this;return new Promise(l=>{let c=!1,d=o;const p=function(){arguments.length>0&&void 0!==arguments[0]&&!arguments[0]?(d?.(),d=null):d||(d=a.showBuildingLoader())},h=(e,o,r)=>{c||(c=!0,clearTimeout(u),p(!1),e?(this.celebratePublish(t),this.emitPublished(i,t)):(this.snackbar(r||s.M.get("lang","publishError")||"Publish failed"),this.emitPublishError(i,t,o)),l(!0))},u=setTimeout(()=>{c||(console.warn("config.onPublish never signalled completion — finishing the publish without it. Call success() or error() (or return a promise) so the loader can come down."),c=!0,p(!1),l(!1))},6e4),f={title:t.title,description:t.description,keywords:t.keywords},g={loading:p,success:()=>h(!0),confetti:()=>h(!0),error:e=>h(!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 b;try{b=e(f,g)}catch(e){return console.error("Error in config.onPublish:",e),void h(!1,e)}b&&"function"==typeof b.then&&Promise.resolve(b).then(()=>h(!0),e=>{console.error("Error in config.onPublish:",e),h(!1,e)})})}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||"function"==typeof this.onPublish||"web"===this.channel&&e))return null;const t="web"===this.channel?"publish":"next";return o.qy`
|
|
586
586
|
<paper-button
|
|
587
587
|
@click=${this.handleClick}
|
|
588
588
|
class="next-button"
|
|
@@ -648,4 +648,4 @@
|
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
|
|
651
|
-
`,b.properties={onNextStep:{type:Function},channel:{type:String},isLoading:{type:Boolean},isLocked:{type:Boolean},lockTimer:{type:Number}},customElements.define("cdtr-next-button",b)}}]);
|
|
651
|
+
`,b.properties={onNextStep:{type:Function},onPublish:{type:Function},channel:{type:String},isLoading:{type:Boolean},isLocked:{type:Boolean},lockTimer:{type:Number}},customElements.define("cdtr-next-button",b)}}]);
|
|
Binary file
|