@jiggai/kitchen-plugin-marketing 0.2.9 → 0.2.11
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/dist/tabs/accounts.js +206 -157
- package/dist/tabs/content-library.js +226 -92
- package/package.json +3 -7
- package/dist/api/routes.js +0 -1486
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
const useMemo = R.useMemo;
|
|
9
9
|
const useState = R.useState;
|
|
10
10
|
const useCallback = R.useCallback;
|
|
11
|
+
const useRef = R.useRef;
|
|
11
12
|
const t = {
|
|
12
13
|
text: { color: "var(--ck-text-primary)" },
|
|
13
14
|
muted: { color: "var(--ck-text-secondary)" },
|
|
@@ -53,15 +54,16 @@
|
|
|
53
54
|
fontWeight: 700,
|
|
54
55
|
cursor: "pointer"
|
|
55
56
|
},
|
|
56
|
-
pill: (active) => ({
|
|
57
|
+
pill: (active, connected) => ({
|
|
57
58
|
background: active ? "rgba(99,179,237,0.16)" : "rgba(255,255,255,0.03)",
|
|
58
59
|
border: `1px solid ${active ? "rgba(99,179,237,0.45)" : "var(--ck-border-subtle)"}`,
|
|
59
60
|
borderRadius: "999px",
|
|
60
61
|
padding: "0.25rem 0.55rem",
|
|
61
62
|
fontSize: "0.8rem",
|
|
62
|
-
color: active ? "rgba(210,235,255,0.95)" : "var(--ck-text-secondary)",
|
|
63
|
-
cursor: "pointer",
|
|
64
|
-
userSelect: "none"
|
|
63
|
+
color: active ? "rgba(210,235,255,0.95)" : connected ? "var(--ck-text-secondary)" : "var(--ck-text-tertiary)",
|
|
64
|
+
cursor: connected ? "pointer" : "default",
|
|
65
|
+
userSelect: "none",
|
|
66
|
+
opacity: connected ? 1 : 0.5
|
|
65
67
|
}),
|
|
66
68
|
statusBadge: (status) => {
|
|
67
69
|
const colors = {
|
|
@@ -79,21 +81,46 @@
|
|
|
79
81
|
fontWeight: 600,
|
|
80
82
|
color: "white"
|
|
81
83
|
};
|
|
82
|
-
}
|
|
84
|
+
},
|
|
85
|
+
backendBadge: (backend) => {
|
|
86
|
+
const colors = {
|
|
87
|
+
postiz: "rgba(99,179,237,0.5)",
|
|
88
|
+
gateway: "rgba(134,239,172,0.5)",
|
|
89
|
+
direct: "rgba(251,191,36,0.5)"
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
display: "inline-block",
|
|
93
|
+
background: colors[backend] || "rgba(100,100,100,0.3)",
|
|
94
|
+
borderRadius: "999px",
|
|
95
|
+
padding: "0.05rem 0.35rem",
|
|
96
|
+
fontSize: "0.6rem",
|
|
97
|
+
fontWeight: 600,
|
|
98
|
+
color: "white",
|
|
99
|
+
marginLeft: "0.25rem"
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
charWarn: (pct) => ({
|
|
103
|
+
color: pct > 100 ? "rgba(248,113,113,0.95)" : pct > 90 ? "rgba(251,191,36,0.9)" : "var(--ck-text-tertiary)",
|
|
104
|
+
fontSize: "0.75rem"
|
|
105
|
+
})
|
|
83
106
|
};
|
|
84
107
|
function ContentLibrary(props) {
|
|
85
108
|
const teamId = String(props?.teamId || "default");
|
|
86
109
|
const apiBase = useMemo(() => `/api/plugins/marketing`, []);
|
|
110
|
+
const [drivers, setDrivers] = useState([]);
|
|
87
111
|
const [posts, setPosts] = useState([]);
|
|
88
|
-
const [providers, setProviders] = useState([]);
|
|
89
112
|
const [loading, setLoading] = useState(true);
|
|
90
113
|
const [saving, setSaving] = useState(false);
|
|
91
114
|
const [publishing, setPublishing] = useState(false);
|
|
92
115
|
const [error, setError] = useState(null);
|
|
93
116
|
const [success, setSuccess] = useState(null);
|
|
117
|
+
const [filterStatus, setFilterStatus] = useState("all");
|
|
94
118
|
const [content, setContent] = useState("");
|
|
95
|
-
const [
|
|
119
|
+
const [selectedPlatforms, setSelectedPlatforms] = useState([]);
|
|
96
120
|
const [scheduledAt, setScheduledAt] = useState("");
|
|
121
|
+
const [mediaUrl, setMediaUrl] = useState("");
|
|
122
|
+
const [showMedia, setShowMedia] = useState(false);
|
|
123
|
+
const successTimeout = useRef(null);
|
|
97
124
|
const postizHeaders = useMemo(() => {
|
|
98
125
|
try {
|
|
99
126
|
const stored = localStorage.getItem(`ck-postiz-${teamId}`);
|
|
@@ -110,44 +137,61 @@
|
|
|
110
137
|
}
|
|
111
138
|
return {};
|
|
112
139
|
}, [teamId]);
|
|
113
|
-
const
|
|
140
|
+
const loadDrivers = useCallback(async () => {
|
|
114
141
|
try {
|
|
115
|
-
const res = await fetch(`${apiBase}/
|
|
142
|
+
const res = await fetch(`${apiBase}/drivers?team=${encodeURIComponent(teamId)}`, { headers: postizHeaders });
|
|
116
143
|
const json = await res.json();
|
|
117
|
-
|
|
144
|
+
setDrivers(Array.isArray(json.drivers) ? json.drivers : []);
|
|
118
145
|
} catch {
|
|
119
146
|
}
|
|
120
|
-
}, [apiBase, teamId]);
|
|
121
|
-
const
|
|
147
|
+
}, [apiBase, teamId, postizHeaders]);
|
|
148
|
+
const loadPosts = useCallback(async () => {
|
|
122
149
|
try {
|
|
123
|
-
const
|
|
150
|
+
const url = `${apiBase}/posts?team=${encodeURIComponent(teamId)}&limit=50`;
|
|
151
|
+
const res = await fetch(url);
|
|
124
152
|
const json = await res.json();
|
|
125
|
-
|
|
126
|
-
setProviders(detected);
|
|
153
|
+
setPosts(Array.isArray(json.data) ? json.data : []);
|
|
127
154
|
} catch {
|
|
128
155
|
}
|
|
129
|
-
}, [apiBase, teamId
|
|
156
|
+
}, [apiBase, teamId]);
|
|
130
157
|
useEffect(() => {
|
|
131
158
|
setLoading(true);
|
|
132
|
-
Promise.all([
|
|
133
|
-
}, [
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
159
|
+
Promise.all([loadDrivers(), loadPosts()]).finally(() => setLoading(false));
|
|
160
|
+
}, [loadDrivers, loadPosts]);
|
|
161
|
+
const connectedDrivers = useMemo(() => drivers.filter((d) => d.connected), [drivers]);
|
|
162
|
+
const disconnectedDrivers = useMemo(() => drivers.filter((d) => !d.connected), [drivers]);
|
|
163
|
+
const togglePlatform = (platform) => {
|
|
164
|
+
const driver = drivers.find((d) => d.platform === platform);
|
|
165
|
+
if (!driver?.connected) return;
|
|
166
|
+
setSelectedPlatforms(
|
|
167
|
+
(prev) => prev.includes(platform) ? prev.filter((x) => x !== platform) : [...prev, platform]
|
|
137
168
|
);
|
|
138
169
|
};
|
|
170
|
+
const charLimit = useMemo(() => {
|
|
171
|
+
if (selectedPlatforms.length === 0) return void 0;
|
|
172
|
+
const limits = selectedPlatforms.map((p) => drivers.find((d) => d.platform === p)?.capabilities?.maxLength).filter((l) => l !== void 0);
|
|
173
|
+
return limits.length > 0 ? Math.min(...limits) : void 0;
|
|
174
|
+
}, [selectedPlatforms, drivers]);
|
|
175
|
+
const canSchedule = useMemo(() => {
|
|
176
|
+
return selectedPlatforms.some((p) => drivers.find((d) => d.platform === p)?.capabilities?.canSchedule);
|
|
177
|
+
}, [selectedPlatforms, drivers]);
|
|
178
|
+
const showSuccess = (msg) => {
|
|
179
|
+
setSuccess(msg);
|
|
180
|
+
if (successTimeout.current) clearTimeout(successTimeout.current);
|
|
181
|
+
successTimeout.current = setTimeout(() => setSuccess(null), 5e3);
|
|
182
|
+
};
|
|
139
183
|
const onSaveDraft = async () => {
|
|
140
184
|
if (!content.trim()) return;
|
|
141
185
|
setSaving(true);
|
|
142
186
|
setError(null);
|
|
143
187
|
try {
|
|
144
|
-
const platforms =
|
|
188
|
+
const platforms = selectedPlatforms.length > 0 ? selectedPlatforms : ["draft"];
|
|
145
189
|
const res = await fetch(`${apiBase}/posts?team=${encodeURIComponent(teamId)}`, {
|
|
146
190
|
method: "POST",
|
|
147
191
|
headers: { "content-type": "application/json" },
|
|
148
192
|
body: JSON.stringify({
|
|
149
193
|
content,
|
|
150
|
-
platforms
|
|
194
|
+
platforms,
|
|
151
195
|
status: scheduledAt ? "scheduled" : "draft",
|
|
152
196
|
scheduledAt: scheduledAt || void 0
|
|
153
197
|
})
|
|
@@ -155,7 +199,9 @@
|
|
|
155
199
|
if (!res.ok) throw new Error(`Save failed (${res.status})`);
|
|
156
200
|
setContent("");
|
|
157
201
|
setScheduledAt("");
|
|
158
|
-
|
|
202
|
+
setSelectedPlatforms([]);
|
|
203
|
+
setMediaUrl("");
|
|
204
|
+
showSuccess("Draft saved!");
|
|
159
205
|
await loadPosts();
|
|
160
206
|
} catch (e) {
|
|
161
207
|
setError(e?.message || "Failed to save");
|
|
@@ -164,40 +210,54 @@
|
|
|
164
210
|
}
|
|
165
211
|
};
|
|
166
212
|
const onPublish = async () => {
|
|
167
|
-
if (!content.trim() ||
|
|
213
|
+
if (!content.trim() || selectedPlatforms.length === 0) return;
|
|
168
214
|
setPublishing(true);
|
|
169
215
|
setError(null);
|
|
170
216
|
setSuccess(null);
|
|
171
|
-
const postizProviders = selectedProviders.filter((id) => id.startsWith("postiz:"));
|
|
172
|
-
const gatewayProviders = selectedProviders.filter((id) => id.startsWith("gateway:"));
|
|
173
217
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
throw new Error(err?.message || `Postiz publish failed (${res.status})`);
|
|
218
|
+
const res = await fetch(`${apiBase}/publish?team=${encodeURIComponent(teamId)}`, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: { "content-type": "application/json", ...postizHeaders },
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
content,
|
|
223
|
+
platforms: selectedPlatforms,
|
|
224
|
+
scheduledAt: scheduledAt || void 0,
|
|
225
|
+
mediaUrls: mediaUrl ? [mediaUrl] : void 0
|
|
226
|
+
})
|
|
227
|
+
});
|
|
228
|
+
const json = await res.json();
|
|
229
|
+
if (json.results) {
|
|
230
|
+
const succeeded = json.results.filter((r) => r.success);
|
|
231
|
+
const failed = json.results.filter((r) => !r.success);
|
|
232
|
+
if (failed.length > 0 && succeeded.length === 0) {
|
|
233
|
+
throw new Error(failed.map((f) => `${f.platform}: ${f.error}`).join("; "));
|
|
191
234
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
235
|
+
const parts = [];
|
|
236
|
+
if (succeeded.length > 0) {
|
|
237
|
+
parts.push(`${scheduledAt ? "Scheduled" : "Published"} to ${succeeded.map((s) => s.platform).join(", ")}`);
|
|
238
|
+
}
|
|
239
|
+
if (failed.length > 0) {
|
|
240
|
+
parts.push(`Failed: ${failed.map((f) => `${f.platform} (${f.error})`).join(", ")}`);
|
|
241
|
+
}
|
|
242
|
+
showSuccess(parts.join(" \xB7 "));
|
|
195
243
|
} else {
|
|
196
|
-
|
|
244
|
+
showSuccess(scheduledAt ? "Scheduled!" : "Published!");
|
|
197
245
|
}
|
|
246
|
+
await fetch(`${apiBase}/posts?team=${encodeURIComponent(teamId)}`, {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: { "content-type": "application/json" },
|
|
249
|
+
body: JSON.stringify({
|
|
250
|
+
content,
|
|
251
|
+
platforms: selectedPlatforms,
|
|
252
|
+
status: scheduledAt ? "scheduled" : "published",
|
|
253
|
+
scheduledAt: scheduledAt || void 0
|
|
254
|
+
})
|
|
255
|
+
}).catch(() => {
|
|
256
|
+
});
|
|
198
257
|
setContent("");
|
|
199
258
|
setScheduledAt("");
|
|
200
|
-
|
|
259
|
+
setSelectedPlatforms([]);
|
|
260
|
+
setMediaUrl("");
|
|
201
261
|
await loadPosts();
|
|
202
262
|
} catch (e) {
|
|
203
263
|
setError(e?.message || "Publish failed");
|
|
@@ -205,8 +265,12 @@
|
|
|
205
265
|
setPublishing(false);
|
|
206
266
|
}
|
|
207
267
|
};
|
|
208
|
-
const
|
|
209
|
-
const hasSelection =
|
|
268
|
+
const hasConnected = connectedDrivers.length > 0;
|
|
269
|
+
const hasSelection = selectedPlatforms.length > 0;
|
|
270
|
+
const filteredPosts = useMemo(() => {
|
|
271
|
+
if (filterStatus === "all") return posts;
|
|
272
|
+
return posts.filter((p) => p.status === filterStatus);
|
|
273
|
+
}, [posts, filterStatus]);
|
|
210
274
|
return h(
|
|
211
275
|
"div",
|
|
212
276
|
{ className: "space-y-3" },
|
|
@@ -223,60 +287,103 @@
|
|
|
223
287
|
onChange: (e) => setContent(e.target.value),
|
|
224
288
|
placeholder: "Write your post\u2026",
|
|
225
289
|
rows: 5,
|
|
226
|
-
style: { ...t.input, resize: "vertical", minHeight: "110px" }
|
|
290
|
+
style: { ...t.input, resize: "vertical", minHeight: "110px", fontFamily: "inherit" }
|
|
227
291
|
}),
|
|
228
|
-
//
|
|
229
|
-
|
|
292
|
+
// Character count
|
|
293
|
+
charLimit && content.length > 0 && h(
|
|
294
|
+
"div",
|
|
295
|
+
{ style: t.charWarn(content.length / charLimit * 100) },
|
|
296
|
+
`${content.length} / ${charLimit} characters`,
|
|
297
|
+
content.length > charLimit && " \u26A0 over limit"
|
|
298
|
+
),
|
|
299
|
+
!charLimit && content.length > 0 && h("div", { className: "text-xs", style: t.faint }, `${content.length} chars`),
|
|
300
|
+
// Platform selector — connected
|
|
301
|
+
h(
|
|
230
302
|
"div",
|
|
231
303
|
null,
|
|
232
304
|
h("div", { className: "text-xs font-medium mb-2", style: t.faint }, "Publish to"),
|
|
233
|
-
h(
|
|
305
|
+
connectedDrivers.length > 0 ? h(
|
|
234
306
|
"div",
|
|
235
307
|
{ className: "flex flex-wrap gap-2" },
|
|
236
|
-
...
|
|
237
|
-
(
|
|
308
|
+
...connectedDrivers.map(
|
|
309
|
+
(d) => h(
|
|
238
310
|
"span",
|
|
239
311
|
{
|
|
240
|
-
key:
|
|
241
|
-
onClick: () =>
|
|
242
|
-
style: t.pill(
|
|
312
|
+
key: d.platform,
|
|
313
|
+
onClick: () => togglePlatform(d.platform),
|
|
314
|
+
style: t.pill(selectedPlatforms.includes(d.platform), true),
|
|
243
315
|
role: "button",
|
|
244
|
-
tabIndex: 0
|
|
316
|
+
tabIndex: 0,
|
|
317
|
+
title: `${d.displayName} via ${d.backend}`
|
|
245
318
|
},
|
|
246
|
-
`${
|
|
319
|
+
`${d.icon} ${d.label}`,
|
|
320
|
+
h("span", { style: t.backendBadge(d.backend) }, d.backend)
|
|
321
|
+
)
|
|
322
|
+
),
|
|
323
|
+
...disconnectedDrivers.map(
|
|
324
|
+
(d) => h("span", {
|
|
325
|
+
key: d.platform,
|
|
326
|
+
style: t.pill(false, false),
|
|
327
|
+
title: `${d.label} \u2014 not connected`
|
|
328
|
+
}, `${d.icon} ${d.label}`)
|
|
329
|
+
)
|
|
330
|
+
) : h(
|
|
331
|
+
"div",
|
|
332
|
+
{ className: "flex flex-wrap gap-2" },
|
|
333
|
+
...drivers.map(
|
|
334
|
+
(d) => h(
|
|
335
|
+
"span",
|
|
336
|
+
{ key: d.platform, style: t.pill(false, false), title: "Not connected" },
|
|
337
|
+
`${d.icon} ${d.label}`
|
|
247
338
|
)
|
|
339
|
+
),
|
|
340
|
+
h(
|
|
341
|
+
"div",
|
|
342
|
+
{ className: "text-xs mt-1", style: t.faint },
|
|
343
|
+
"No platforms connected. Go to Accounts tab to set up Postiz or add accounts."
|
|
248
344
|
)
|
|
249
345
|
)
|
|
250
346
|
),
|
|
251
|
-
//
|
|
252
|
-
|
|
347
|
+
// Media URL (collapsible)
|
|
348
|
+
h(
|
|
253
349
|
"div",
|
|
254
|
-
|
|
255
|
-
"
|
|
350
|
+
null,
|
|
351
|
+
h("button", {
|
|
352
|
+
type: "button",
|
|
353
|
+
onClick: () => setShowMedia(!showMedia),
|
|
354
|
+
style: { ...t.btnGhost, padding: "0.3rem 0.55rem", fontSize: "0.8rem" }
|
|
355
|
+
}, showMedia ? "\u2212 Media" : "+ Media"),
|
|
356
|
+
showMedia && h(
|
|
357
|
+
"div",
|
|
358
|
+
{ className: "mt-2" },
|
|
359
|
+
h("input", {
|
|
360
|
+
type: "url",
|
|
361
|
+
value: mediaUrl,
|
|
362
|
+
onChange: (e) => setMediaUrl(e.target.value),
|
|
363
|
+
placeholder: "Paste image or video URL\u2026",
|
|
364
|
+
style: t.input
|
|
365
|
+
})
|
|
366
|
+
)
|
|
256
367
|
),
|
|
257
|
-
// Schedule
|
|
258
|
-
h(
|
|
368
|
+
// Schedule (only if any selected platform supports it)
|
|
369
|
+
(canSchedule || !hasSelection) && h(
|
|
259
370
|
"div",
|
|
260
371
|
{ className: "grid grid-cols-1 gap-2 sm:grid-cols-2" },
|
|
261
372
|
h(
|
|
262
373
|
"div",
|
|
263
374
|
null,
|
|
264
|
-
h(
|
|
375
|
+
h(
|
|
376
|
+
"div",
|
|
377
|
+
{ className: "text-xs font-medium mb-1", style: t.faint },
|
|
378
|
+
canSchedule ? "Schedule (optional)" : "Schedule (connect Postiz for scheduling)"
|
|
379
|
+
),
|
|
265
380
|
h("input", {
|
|
266
381
|
type: "datetime-local",
|
|
267
382
|
value: scheduledAt,
|
|
268
383
|
onChange: (e) => setScheduledAt(e.target.value),
|
|
269
|
-
style: t.input
|
|
384
|
+
style: { ...t.input, opacity: canSchedule || !hasSelection ? 1 : 0.5 },
|
|
385
|
+
disabled: hasSelection && !canSchedule
|
|
270
386
|
})
|
|
271
|
-
),
|
|
272
|
-
h(
|
|
273
|
-
"div",
|
|
274
|
-
{ className: "flex items-end" },
|
|
275
|
-
h(
|
|
276
|
-
"div",
|
|
277
|
-
{ className: "text-xs", style: t.faint },
|
|
278
|
-
content.length > 0 ? `${content.length} chars` : ""
|
|
279
|
-
)
|
|
280
387
|
)
|
|
281
388
|
),
|
|
282
389
|
// Actions
|
|
@@ -289,17 +396,12 @@
|
|
|
289
396
|
style: { ...t.btnGhost, opacity: saving ? 0.7 : 1 },
|
|
290
397
|
disabled: saving || !content.trim()
|
|
291
398
|
}, saving ? "Saving\u2026" : "Save draft"),
|
|
292
|
-
|
|
399
|
+
hasConnected && hasSelection && h("button", {
|
|
293
400
|
type: "button",
|
|
294
401
|
onClick: () => void onPublish(),
|
|
295
402
|
style: { ...t.btnPublish, opacity: publishing ? 0.7 : 1 },
|
|
296
403
|
disabled: publishing || !content.trim()
|
|
297
|
-
}, publishing ? "Publishing\u2026" : scheduledAt ? "\u23F1 Schedule" : "\u{1F4E4} Publish")
|
|
298
|
-
!postizAvailable && hasSelection && h(
|
|
299
|
-
"div",
|
|
300
|
-
{ className: "text-xs", style: t.faint },
|
|
301
|
-
"Connect Postiz on Accounts tab to publish directly."
|
|
302
|
-
)
|
|
404
|
+
}, publishing ? "Publishing\u2026" : scheduledAt ? "\u23F1 Schedule" : "\u{1F4E4} Publish now")
|
|
303
405
|
),
|
|
304
406
|
error && h("div", { className: "text-xs", style: { color: "rgba(248,113,113,0.95)" } }, error),
|
|
305
407
|
success && h("div", { className: "text-xs", style: { color: "rgba(74,222,128,0.9)" } }, success)
|
|
@@ -311,14 +413,36 @@
|
|
|
311
413
|
{ style: t.card },
|
|
312
414
|
h(
|
|
313
415
|
"div",
|
|
314
|
-
{ className: "flex items-center justify-between mb-
|
|
416
|
+
{ className: "flex items-center justify-between mb-3" },
|
|
315
417
|
h("div", { className: "text-sm font-medium", style: t.text }, "Posts"),
|
|
316
|
-
h(
|
|
418
|
+
h(
|
|
419
|
+
"div",
|
|
420
|
+
{ className: "flex items-center gap-2" },
|
|
421
|
+
...["all", "draft", "scheduled", "published", "failed"].map(
|
|
422
|
+
(s) => h("button", {
|
|
423
|
+
key: s,
|
|
424
|
+
type: "button",
|
|
425
|
+
onClick: () => setFilterStatus(s),
|
|
426
|
+
style: {
|
|
427
|
+
...t.btnGhost,
|
|
428
|
+
padding: "0.2rem 0.45rem",
|
|
429
|
+
fontSize: "0.7rem",
|
|
430
|
+
background: filterStatus === s ? "rgba(99,179,237,0.12)" : void 0,
|
|
431
|
+
borderColor: filterStatus === s ? "rgba(99,179,237,0.35)" : void 0
|
|
432
|
+
}
|
|
433
|
+
}, s)
|
|
434
|
+
),
|
|
435
|
+
h("button", { type: "button", onClick: () => void loadPosts(), style: { ...t.btnGhost, padding: "0.2rem 0.45rem", fontSize: "0.7rem" } }, "\u21BB")
|
|
436
|
+
)
|
|
317
437
|
),
|
|
318
|
-
loading ? h("div", { className: "py-6 text-center text-sm", style: t.faint }, "Loading\u2026") :
|
|
438
|
+
loading ? h("div", { className: "py-6 text-center text-sm", style: t.faint }, "Loading\u2026") : filteredPosts.length === 0 ? h(
|
|
439
|
+
"div",
|
|
440
|
+
{ className: "py-6 text-center text-sm", style: t.faint },
|
|
441
|
+
filterStatus === "all" ? "No posts yet. Compose your first post above!" : `No ${filterStatus} posts.`
|
|
442
|
+
) : h(
|
|
319
443
|
"div",
|
|
320
444
|
{ className: "space-y-2" },
|
|
321
|
-
...
|
|
445
|
+
...filteredPosts.map(
|
|
322
446
|
(p) => h(
|
|
323
447
|
"div",
|
|
324
448
|
{ key: p.id, style: { ...t.card, padding: "0.75rem" } },
|
|
@@ -333,11 +457,21 @@
|
|
|
333
457
|
),
|
|
334
458
|
p.scheduledAt && h("div", { className: "text-xs", style: t.muted }, `\u23F1 ${new Date(p.scheduledAt).toLocaleString()}`)
|
|
335
459
|
),
|
|
336
|
-
h("div", {
|
|
460
|
+
h("div", {
|
|
461
|
+
className: "mt-2 whitespace-pre-wrap text-sm",
|
|
462
|
+
style: { ...t.text, maxHeight: "120px", overflow: "hidden", textOverflow: "ellipsis" }
|
|
463
|
+
}, p.content),
|
|
337
464
|
p.platforms?.length > 0 && h(
|
|
338
465
|
"div",
|
|
339
466
|
{ className: "mt-2 flex flex-wrap gap-1" },
|
|
340
|
-
...p.platforms.map((pl) =>
|
|
467
|
+
...p.platforms.map((pl) => {
|
|
468
|
+
const driver = drivers.find((d) => d.platform === pl);
|
|
469
|
+
return h(
|
|
470
|
+
"span",
|
|
471
|
+
{ key: pl, style: t.pill(true, true) },
|
|
472
|
+
driver ? `${driver.icon} ${pl}` : pl
|
|
473
|
+
);
|
|
474
|
+
})
|
|
341
475
|
)
|
|
342
476
|
)
|
|
343
477
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jiggai/kitchen-plugin-marketing",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Marketing Suite plugin for ClawKitchen",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -58,15 +58,11 @@
|
|
|
58
58
|
"test": "echo \"No tests yet\" && exit 0"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"better-sqlite3": "^
|
|
62
|
-
"drizzle-orm": "^0.38.0"
|
|
63
|
-
"express": "^4.18.2",
|
|
64
|
-
"multer": "^1.4.4"
|
|
61
|
+
"better-sqlite3": "^11.0.0",
|
|
62
|
+
"drizzle-orm": "^0.38.0"
|
|
65
63
|
},
|
|
66
64
|
"devDependencies": {
|
|
67
65
|
"@types/better-sqlite3": "^7.6.10",
|
|
68
|
-
"@types/express": "^4.17.21",
|
|
69
|
-
"@types/multer": "^1.4.11",
|
|
70
66
|
"@types/node": "^20.0.0",
|
|
71
67
|
"drizzle-kit": "^0.20.0",
|
|
72
68
|
"esbuild": "^0.24.0",
|