Spaces:
Running
Running
add direct-connect guides + restyle setup page
Browse files- Add getDirectPlatformDetails(): guides for Bluesky, Mastodon,
Telegram, Dev.to, Hashnode, Nostr, Lemmy, Warpcast β each with
step-by-step instructions and field descriptions
- Sidebar split into "OAuth Platforms" + "Direct Connect" sections
- Remove all gradients: step-num and CTA use solid colors
- Single accent color (#3b82f6, drop --accent2)
- Primary CTA buttons: white background, dark text
- Copy URL button: white with dark text (btn-copy-white)
- Panel links: accent blue (was purple)
- Env-name pill: blue tint (was purple)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- health-server.js +270 -35
health-server.js
CHANGED
|
@@ -213,6 +213,150 @@ function getSocialPlatforms() {
|
|
| 213 |
];
|
| 214 |
}
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
// Returns detailed per-platform OAuth setup guide data.
|
| 217 |
// publicUrl: "https://somratpro-huggingpost.hf.space" (no trailing slash)
|
| 218 |
function getOAuthPlatformDetails(publicUrl) {
|
|
@@ -708,17 +852,20 @@ function renderSetupPage() {
|
|
| 708 |
const publicUrl = spaceHost
|
| 709 |
? `https://${spaceHost}`
|
| 710 |
: "http://localhost:7860";
|
|
|
|
| 711 |
const settingsUrl = spaceId
|
| 712 |
? `https://huggingface.co/spaces/${spaceId}/settings`
|
| 713 |
: "https://huggingface.co/settings/spaces";
|
| 714 |
|
| 715 |
const platforms = getOAuthPlatformDetails(publicUrl);
|
|
|
|
| 716 |
const configuredCount = platforms.filter((p) =>
|
| 717 |
p.envVars.every((v) => v.set),
|
| 718 |
).length;
|
|
|
|
| 719 |
|
| 720 |
-
// Build sidebar
|
| 721 |
-
const
|
| 722 |
const allSet = p.envVars.every((v) => v.set);
|
| 723 |
const anySet = p.envVars.some((v) => v.set);
|
| 724 |
const dot = allSet
|
|
@@ -733,8 +880,17 @@ function renderSetupPage() {
|
|
| 733 |
</button>`;
|
| 734 |
}).join("");
|
| 735 |
|
| 736 |
-
|
| 737 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 738 |
const allSet = p.envVars.every((v) => v.set);
|
| 739 |
const anySet = p.envVars.some((v) => v.set);
|
| 740 |
|
|
@@ -791,7 +947,7 @@ function renderSetupPage() {
|
|
| 791 |
</div>
|
| 792 |
<div class="copy-block">
|
| 793 |
<span class="copy-block-text">${p.callbackUrl}</span>
|
| 794 |
-
<button class="btn-copy btn-copy-
|
| 795 |
</div>
|
| 796 |
|
| 797 |
<div class="section-label">Space Secrets to Add
|
|
@@ -799,13 +955,70 @@ function renderSetupPage() {
|
|
| 799 |
</div>
|
| 800 |
<div class="env-list">${envRows}</div>
|
| 801 |
|
| 802 |
-
<a href="${settingsUrl}" target="_blank" rel="noopener" class="
|
| 803 |
Open Space Settings β Variables & Secrets
|
| 804 |
</a>
|
| 805 |
<p class="hint-final">After adding all secrets, click <strong>Restart Space</strong> for them to take effect.</p>
|
| 806 |
</div>`;
|
| 807 |
}).join("");
|
| 808 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 809 |
return `<!DOCTYPE html>
|
| 810 |
<html lang="en">
|
| 811 |
<head>
|
|
@@ -827,7 +1040,6 @@ function renderSetupPage() {
|
|
| 827 |
--warn: #f5c542;
|
| 828 |
--bad: #fb7185;
|
| 829 |
--accent: #3b82f6;
|
| 830 |
-
--accent2:#8b5cf6;
|
| 831 |
}
|
| 832 |
body {
|
| 833 |
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -905,10 +1117,15 @@ body {
|
|
| 905 |
color: var(--muted);
|
| 906 |
padding: 6px 10px 10px;
|
| 907 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 908 |
.plat-tab {
|
| 909 |
width: 100%;
|
| 910 |
background: none;
|
| 911 |
-
border:
|
| 912 |
color: var(--soft);
|
| 913 |
font: inherit;
|
| 914 |
font-size: .82rem;
|
|
@@ -921,7 +1138,6 @@ body {
|
|
| 921 |
cursor: pointer;
|
| 922 |
text-align: left;
|
| 923 |
transition: background .12s, color .12s;
|
| 924 |
-
border: 1px solid transparent;
|
| 925 |
}
|
| 926 |
.plat-tab:hover { background: var(--panel2); color: var(--text); }
|
| 927 |
.plat-tab.active {
|
|
@@ -952,7 +1168,7 @@ body {
|
|
| 952 |
.panel-title { font-size: 1.4rem; font-weight: 850; margin-bottom: 6px; }
|
| 953 |
.panel-links { display: flex; gap: 14px; flex-wrap: wrap; }
|
| 954 |
.panel-links a {
|
| 955 |
-
color: var(--
|
| 956 |
font-size: .76rem;
|
| 957 |
font-weight: 700;
|
| 958 |
text-decoration: none;
|
|
@@ -1008,7 +1224,7 @@ body {
|
|
| 1008 |
width: 22px;
|
| 1009 |
height: 22px;
|
| 1010 |
border-radius: 50%;
|
| 1011 |
-
background:
|
| 1012 |
color: #fff;
|
| 1013 |
font-size: .68rem;
|
| 1014 |
font-weight: 850;
|
|
@@ -1021,6 +1237,7 @@ body {
|
|
| 1021 |
.step-title { font-size: .82rem; font-weight: 800; margin-bottom: 3px; color: var(--text); }
|
| 1022 |
.step-body { font-size: .78rem; color: var(--soft); line-height: 1.6; }
|
| 1023 |
.step-body strong { color: var(--text); font-weight: 800; }
|
|
|
|
| 1024 |
.step-body code {
|
| 1025 |
background: var(--panel2);
|
| 1026 |
border: 1px solid var(--line);
|
|
@@ -1029,7 +1246,7 @@ body {
|
|
| 1029 |
font-size: .85em;
|
| 1030 |
color: var(--text);
|
| 1031 |
}
|
| 1032 |
-
.step-body a { color: var(--
|
| 1033 |
.step-body a:hover { text-decoration: underline; }
|
| 1034 |
|
| 1035 |
/* Callback copy block */
|
|
@@ -1046,12 +1263,12 @@ body {
|
|
| 1046 |
flex: 1;
|
| 1047 |
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, monospace;
|
| 1048 |
font-size: .78rem;
|
| 1049 |
-
color: var(--
|
| 1050 |
word-break: break-all;
|
| 1051 |
opacity: .9;
|
| 1052 |
}
|
| 1053 |
|
| 1054 |
-
/* Env var rows */
|
| 1055 |
.env-list { display: flex; flex-direction: column; gap: 5px; }
|
| 1056 |
.env-row {
|
| 1057 |
display: flex;
|
|
@@ -1066,9 +1283,9 @@ body {
|
|
| 1066 |
.env-name {
|
| 1067 |
font-family: ui-monospace, "Cascadia Code", monospace;
|
| 1068 |
font-size: .78rem;
|
| 1069 |
-
color: var(--
|
| 1070 |
-
background: rgba(
|
| 1071 |
-
border: 1px solid rgba(
|
| 1072 |
padding: 2px 7px;
|
| 1073 |
border-radius: 5px;
|
| 1074 |
width: fit-content;
|
|
@@ -1092,7 +1309,7 @@ body {
|
|
| 1092 |
.badge.ok { color: var(--good); border-color: rgba(34,197,94,.3); background: rgba(34,197,94,.1); }
|
| 1093 |
.badge.off { color: var(--bad); border-color: rgba(251,113,133,.3); background: rgba(251,113,133,.1); }
|
| 1094 |
|
| 1095 |
-
/* Buttons */
|
| 1096 |
.btn-copy {
|
| 1097 |
background: var(--panel2);
|
| 1098 |
border: 1px solid var(--line);
|
|
@@ -1109,30 +1326,43 @@ body {
|
|
| 1109 |
}
|
| 1110 |
.btn-copy:hover { background: var(--line); color: var(--text); }
|
| 1111 |
.btn-copy.copied { background: rgba(34,197,94,.12); border-color: rgba(34,197,94,.3); color: var(--good); }
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
|
|
|
|
|
|
|
|
|
| 1116 |
font-size: .76rem;
|
|
|
|
| 1117 |
padding: 6px 14px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1118 |
}
|
| 1119 |
-
.btn-copy-
|
|
|
|
| 1120 |
|
| 1121 |
-
/*
|
| 1122 |
-
.
|
| 1123 |
display: inline-flex;
|
| 1124 |
align-items: center;
|
| 1125 |
-
margin-top:
|
| 1126 |
-
background:
|
| 1127 |
-
color: #
|
| 1128 |
text-decoration: none;
|
| 1129 |
-
padding: 10px
|
| 1130 |
border-radius: 8px;
|
| 1131 |
font-size: .82rem;
|
| 1132 |
font-weight: 850;
|
| 1133 |
-
|
|
|
|
|
|
|
| 1134 |
}
|
| 1135 |
-
.
|
|
|
|
| 1136 |
.hint-final {
|
| 1137 |
margin-top: 10px;
|
| 1138 |
font-size: .74rem;
|
|
@@ -1157,6 +1387,7 @@ body {
|
|
| 1157 |
gap: 4px;
|
| 1158 |
}
|
| 1159 |
.sidebar-label { display: none; }
|
|
|
|
| 1160 |
.plat-tab { width: auto; flex: 0 0 auto; }
|
| 1161 |
.tab-name { display: none; }
|
| 1162 |
.main { padding: 16px; }
|
|
@@ -1167,19 +1398,23 @@ body {
|
|
| 1167 |
<div class="topbar">
|
| 1168 |
<a class="topbar-back" href="/">β Dashboard</a>
|
| 1169 |
<span class="topbar-title">Platform Setup Guide</span>
|
| 1170 |
-
<span class="topbar-count">${configuredCount} / ${platforms.length} configured</span>
|
| 1171 |
</div>
|
| 1172 |
<div class="layout">
|
| 1173 |
<nav class="sidebar">
|
| 1174 |
<div class="sidebar-label">OAuth Platforms</div>
|
| 1175 |
-
${
|
|
|
|
|
|
|
|
|
|
| 1176 |
</nav>
|
| 1177 |
<main class="main">
|
| 1178 |
-
${
|
|
|
|
| 1179 |
</main>
|
| 1180 |
</div>
|
| 1181 |
<script>
|
| 1182 |
-
const PLATFORM_IDS = ${JSON.stringify(
|
| 1183 |
function show(i) {
|
| 1184 |
document.querySelectorAll('.plat-tab').forEach((t,j)=>t.classList.toggle('active',j===i));
|
| 1185 |
document.querySelectorAll('.panel').forEach((p,j)=>p.classList.toggle('active',j===i));
|
|
|
|
| 213 |
];
|
| 214 |
}
|
| 215 |
|
| 216 |
+
// Returns detailed per-platform "direct connect" guide data (no OAuth / no env vars).
|
| 217 |
+
// These platforms connect entirely inside the Postiz UI β no developer portal needed.
|
| 218 |
+
function getDirectPlatformDetails(postizUrl) {
|
| 219 |
+
const postizIntegrations = `${postizUrl}/integrations`;
|
| 220 |
+
return [
|
| 221 |
+
{
|
| 222 |
+
id: "bluesky",
|
| 223 |
+
name: "Bluesky",
|
| 224 |
+
emoji: "π¦",
|
| 225 |
+
docsUrl: "https://bsky.social",
|
| 226 |
+
postizUrl: postizIntegrations,
|
| 227 |
+
fields: [
|
| 228 |
+
{ label: "Service", value: "https://bsky.social", hint: "Default for bsky.social users. Custom instance: use your instance URL." },
|
| 229 |
+
{ label: "Identifier", value: "yourname.bsky.social", hint: "Your full Bluesky handle." },
|
| 230 |
+
{ label: "Password", value: "App Password", hint: "NOT your login password β generate a dedicated App Password in Bluesky settings." },
|
| 231 |
+
],
|
| 232 |
+
steps: [
|
| 233 |
+
{ title: "Create a Bluesky account", body: "Sign up at <a href=\"https://bsky.app\" target=\"_blank\" rel=\"noopener\">bsky.app</a> if you don't have one. Custom PDS users: use your own instance URL." },
|
| 234 |
+
{ title: "Generate an App Password", body: "In Bluesky β <strong>Settings β Privacy and Security β App Passwords β Add App Password</strong>. Name it <em>HuggingPost</em>. Copy the generated password β it's shown only once." },
|
| 235 |
+
{ title: "Open Postiz Integrations", body: "Click the button below to go to Postiz β click <strong>Connect</strong> next to Bluesky." },
|
| 236 |
+
{ title: "Fill in credentials", body: "<strong>Service:</strong> <code>https://bsky.social</code> (or your instance URL)<br><strong>Identifier:</strong> your full handle (e.g. <code>yourname.bsky.social</code>)<br><strong>Password:</strong> the App Password from Step 2 β <em>not</em> your login password." },
|
| 237 |
+
{ title: "Click Connect", body: "Postiz authenticates and adds your Bluesky account. You can post immediately." },
|
| 238 |
+
],
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
id: "mastodon",
|
| 242 |
+
name: "Mastodon",
|
| 243 |
+
emoji: "π",
|
| 244 |
+
docsUrl: "https://joinmastodon.org",
|
| 245 |
+
postizUrl: postizIntegrations,
|
| 246 |
+
fields: [
|
| 247 |
+
{ label: "Service", value: "https://mastodon.social", hint: "Your Mastodon instance URL (e.g. https://fosstodon.org)." },
|
| 248 |
+
{ label: "Identifier", value: "yourhandle", hint: "Your username without the @instance part." },
|
| 249 |
+
{ label: "Password", value: "Your password", hint: "Your Mastodon account password." },
|
| 250 |
+
],
|
| 251 |
+
steps: [
|
| 252 |
+
{ title: "Find your Mastodon instance URL", body: "Check your profile URL β it's the domain part (e.g. <code>mastodon.social</code>, <code>fosstodon.org</code>)." },
|
| 253 |
+
{ title: "Open Postiz Integrations", body: "Click the button below β click <strong>Connect</strong> next to Mastodon." },
|
| 254 |
+
{ title: "Fill in credentials", body: "<strong>Service:</strong> your full instance URL with https (e.g. <code>https://mastodon.social</code>)<br><strong>Identifier:</strong> your username (the part before @instance)<br><strong>Password:</strong> your account password." },
|
| 255 |
+
{ title: "Click Connect", body: "Postiz will authenticate via Mastodon's API and add your account." },
|
| 256 |
+
],
|
| 257 |
+
},
|
| 258 |
+
{
|
| 259 |
+
id: "telegram",
|
| 260 |
+
name: "Telegram",
|
| 261 |
+
emoji: "βοΈ",
|
| 262 |
+
docsUrl: "https://core.telegram.org/bots#how-do-i-create-a-bot",
|
| 263 |
+
postizUrl: postizIntegrations,
|
| 264 |
+
fields: [
|
| 265 |
+
{ label: "Bot Token", value: "123456:ABC-DEF...", hint: "From @BotFather. Format: number:string." },
|
| 266 |
+
],
|
| 267 |
+
steps: [
|
| 268 |
+
{ title: "Create a Telegram Bot", body: "Open Telegram β message <strong>@BotFather</strong> β send <code>/newbot</code> β follow prompts β copy the <strong>Bot Token</strong> (format: <code>123456789:ABC-...</code>)." },
|
| 269 |
+
{ title: "(Optional) Add bot to a channel", body: "To post to a Telegram channel: add your bot as an <strong>Admin</strong> of the channel with post permission." },
|
| 270 |
+
{ title: "Open Postiz Integrations", body: "Click the button below β click <strong>Connect</strong> next to Telegram." },
|
| 271 |
+
{ title: "Enter Bot Token", body: "Paste your Bot Token from @BotFather. Click Connect." },
|
| 272 |
+
],
|
| 273 |
+
},
|
| 274 |
+
{
|
| 275 |
+
id: "devto",
|
| 276 |
+
name: "Dev.to",
|
| 277 |
+
emoji: "π»",
|
| 278 |
+
docsUrl: "https://dev.to/settings/extensions",
|
| 279 |
+
postizUrl: postizIntegrations,
|
| 280 |
+
fields: [
|
| 281 |
+
{ label: "API Key", value: "Your Dev.to API key", hint: "From dev.to β Settings β Extensions β DEV API Keys." },
|
| 282 |
+
],
|
| 283 |
+
steps: [
|
| 284 |
+
{ title: "Log in to Dev.to", body: "Go to <a href=\"https://dev.to\" target=\"_blank\" rel=\"noopener\">dev.to</a> and sign in." },
|
| 285 |
+
{ title: "Generate an API key", body: "Go to <strong>Settings β Extensions β DEV API Keys</strong>. Enter a description (e.g. <em>HuggingPost</em>) and click <strong>Generate API Key</strong>. Copy the key." },
|
| 286 |
+
{ title: "Open Postiz Integrations", body: "Click the button below β click <strong>Connect</strong> next to Dev.to." },
|
| 287 |
+
{ title: "Enter API key", body: "Paste your API key. Click Connect." },
|
| 288 |
+
],
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
id: "hashnode",
|
| 292 |
+
name: "Hashnode",
|
| 293 |
+
emoji: "π°",
|
| 294 |
+
docsUrl: "https://hashnode.com/settings/developer",
|
| 295 |
+
postizUrl: postizIntegrations,
|
| 296 |
+
fields: [
|
| 297 |
+
{ label: "Personal Access Token", value: "Your Hashnode token", hint: "From hashnode.com β Account Settings β Developer β Personal Access Token." },
|
| 298 |
+
],
|
| 299 |
+
steps: [
|
| 300 |
+
{ title: "Log in to Hashnode", body: "Go to <a href=\"https://hashnode.com\" target=\"_blank\" rel=\"noopener\">hashnode.com</a> and sign in." },
|
| 301 |
+
{ title: "Generate a Personal Access Token", body: "Go to <strong>Account Settings β Developer β Personal Access Token β Generate new token</strong>. Name it <em>HuggingPost</em>. Copy the token." },
|
| 302 |
+
{ title: "Open Postiz Integrations", body: "Click the button below β click <strong>Connect</strong> next to Hashnode." },
|
| 303 |
+
{ title: "Enter token", body: "Paste your Personal Access Token. Click Connect." },
|
| 304 |
+
],
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
id: "nostr",
|
| 308 |
+
name: "Nostr",
|
| 309 |
+
emoji: "π",
|
| 310 |
+
docsUrl: "https://nostr.how/en/get-started",
|
| 311 |
+
postizUrl: postizIntegrations,
|
| 312 |
+
fields: [
|
| 313 |
+
{ label: "Private Key", value: "nsec1...", hint: "Your Nostr private key in nsec (bech32) format." },
|
| 314 |
+
],
|
| 315 |
+
steps: [
|
| 316 |
+
{ title: "Get a Nostr keypair", body: "Use a Nostr client like <a href=\"https://snort.social\" target=\"_blank\" rel=\"noopener\">Snort</a> or <a href=\"https://primal.net\" target=\"_blank\" rel=\"noopener\">Primal</a> to create or export your keys. You need the <strong>private key</strong> in <code>nsec1...</code> format." },
|
| 317 |
+
{ title: "β οΈ Security note", body: "Your private key controls your entire Nostr identity. Only enter it in trusted apps. HuggingPost is self-hosted β your key stays in your container." },
|
| 318 |
+
{ title: "Open Postiz Integrations", body: "Click the button below β click <strong>Connect</strong> next to Nostr." },
|
| 319 |
+
{ title: "Enter private key", body: "Paste your <code>nsec1...</code> private key. Click Connect." },
|
| 320 |
+
],
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
id: "lemmy",
|
| 324 |
+
name: "Lemmy",
|
| 325 |
+
emoji: "πΎ",
|
| 326 |
+
docsUrl: "https://join-lemmy.org",
|
| 327 |
+
postizUrl: postizIntegrations,
|
| 328 |
+
fields: [
|
| 329 |
+
{ label: "Instance", value: "https://lemmy.world", hint: "Your Lemmy instance URL." },
|
| 330 |
+
{ label: "Username", value: "yourhandle", hint: "Your Lemmy username." },
|
| 331 |
+
{ label: "Password", value: "Your password", hint: "Your Lemmy account password." },
|
| 332 |
+
],
|
| 333 |
+
steps: [
|
| 334 |
+
{ title: "Find your Lemmy instance", body: "You need the full URL of your Lemmy instance (e.g. <code>https://lemmy.world</code>, <code>https://lemmy.ml</code>). Find it at <a href=\"https://join-lemmy.org\" target=\"_blank\" rel=\"noopener\">join-lemmy.org</a>." },
|
| 335 |
+
{ title: "Open Postiz Integrations", body: "Click the button below β click <strong>Connect</strong> next to Lemmy." },
|
| 336 |
+
{ title: "Fill in credentials", body: "<strong>Instance:</strong> your instance URL<br><strong>Username:</strong> your account username<br><strong>Password:</strong> your account password." },
|
| 337 |
+
{ title: "Click Connect", body: "Postiz authenticates via Lemmy's API." },
|
| 338 |
+
],
|
| 339 |
+
},
|
| 340 |
+
{
|
| 341 |
+
id: "warpcast",
|
| 342 |
+
name: "Warpcast",
|
| 343 |
+
emoji: "π£",
|
| 344 |
+
docsUrl: "https://docs.farcaster.xyz",
|
| 345 |
+
postizUrl: postizIntegrations,
|
| 346 |
+
fields: [
|
| 347 |
+
{ label: "FID", value: "12345", hint: "Your Farcaster user ID (numeric)." },
|
| 348 |
+
{ label: "Private Key", value: "0x...", hint: "Your Farcaster custody private key (hex, 0x-prefixed)." },
|
| 349 |
+
],
|
| 350 |
+
steps: [
|
| 351 |
+
{ title: "Find your FID", body: "Your Farcaster ID (FID) is a numeric value. In Warpcast β Profile β the number in your profile URL, or use <a href=\"https://www.farcaster.xyz\" target=\"_blank\" rel=\"noopener\">farcaster.xyz</a> to look it up." },
|
| 352 |
+
{ title: "Export your private key", body: "In Warpcast β <strong>Settings β Advanced β Recovery phrase</strong>, or use a Farcaster tool to derive your custody private key. The key is in hex format (<code>0x...</code>)." },
|
| 353 |
+
{ title: "Open Postiz Integrations", body: "Click the button below β click <strong>Connect</strong> next to Warpcast." },
|
| 354 |
+
{ title: "Enter FID and private key", body: "Paste your FID and private key. Click Connect." },
|
| 355 |
+
],
|
| 356 |
+
},
|
| 357 |
+
];
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
// Returns detailed per-platform OAuth setup guide data.
|
| 361 |
// publicUrl: "https://somratpro-huggingpost.hf.space" (no trailing slash)
|
| 362 |
function getOAuthPlatformDetails(publicUrl) {
|
|
|
|
| 852 |
const publicUrl = spaceHost
|
| 853 |
? `https://${spaceHost}`
|
| 854 |
: "http://localhost:7860";
|
| 855 |
+
const postizUrl = publicUrl + "/app";
|
| 856 |
const settingsUrl = spaceId
|
| 857 |
? `https://huggingface.co/spaces/${spaceId}/settings`
|
| 858 |
: "https://huggingface.co/settings/spaces";
|
| 859 |
|
| 860 |
const platforms = getOAuthPlatformDetails(publicUrl);
|
| 861 |
+
const directPlatforms = getDirectPlatformDetails(postizUrl);
|
| 862 |
const configuredCount = platforms.filter((p) =>
|
| 863 |
p.envVars.every((v) => v.set),
|
| 864 |
).length;
|
| 865 |
+
const totalPanels = platforms.length + directPlatforms.length;
|
| 866 |
|
| 867 |
+
// ββ Build sidebar ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 868 |
+
const oauthSidebarItems = platforms.map((p, i) => {
|
| 869 |
const allSet = p.envVars.every((v) => v.set);
|
| 870 |
const anySet = p.envVars.some((v) => v.set);
|
| 871 |
const dot = allSet
|
|
|
|
| 880 |
</button>`;
|
| 881 |
}).join("");
|
| 882 |
|
| 883 |
+
const directSidebarItems = directPlatforms.map((p, i) => {
|
| 884 |
+
const idx = platforms.length + i;
|
| 885 |
+
return `<button class="plat-tab" onclick="show(${idx})" id="tab-${idx}">
|
| 886 |
+
<span class="tab-emoji">${p.emoji}</span>
|
| 887 |
+
<span class="tab-name">${p.name}</span>
|
| 888 |
+
<span class="dot dot-ok"></span>
|
| 889 |
+
</button>`;
|
| 890 |
+
}).join("");
|
| 891 |
+
|
| 892 |
+
// ββ Build OAuth panels βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 893 |
+
const oauthPanels = platforms.map((p, i) => {
|
| 894 |
const allSet = p.envVars.every((v) => v.set);
|
| 895 |
const anySet = p.envVars.some((v) => v.set);
|
| 896 |
|
|
|
|
| 947 |
</div>
|
| 948 |
<div class="copy-block">
|
| 949 |
<span class="copy-block-text">${p.callbackUrl}</span>
|
| 950 |
+
<button class="btn-copy btn-copy-white" onclick="copy('${p.callbackUrl}',this)">Copy</button>
|
| 951 |
</div>
|
| 952 |
|
| 953 |
<div class="section-label">Space Secrets to Add
|
|
|
|
| 955 |
</div>
|
| 956 |
<div class="env-list">${envRows}</div>
|
| 957 |
|
| 958 |
+
<a href="${settingsUrl}" target="_blank" rel="noopener" class="cta-btn">
|
| 959 |
Open Space Settings β Variables & Secrets
|
| 960 |
</a>
|
| 961 |
<p class="hint-final">After adding all secrets, click <strong>Restart Space</strong> for them to take effect.</p>
|
| 962 |
</div>`;
|
| 963 |
}).join("");
|
| 964 |
|
| 965 |
+
// ββ Build direct-connect panels ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 966 |
+
const directPanels = directPlatforms.map((p, i) => {
|
| 967 |
+
const idx = platforms.length + i;
|
| 968 |
+
|
| 969 |
+
const stepsList = p.steps.map((s, si) =>
|
| 970 |
+
`<div class="step">
|
| 971 |
+
<div class="step-num">${si + 1}</div>
|
| 972 |
+
<div>
|
| 973 |
+
<div class="step-title">${s.title}</div>
|
| 974 |
+
<div class="step-body">${s.body}</div>
|
| 975 |
+
</div>
|
| 976 |
+
</div>`
|
| 977 |
+
).join("");
|
| 978 |
+
|
| 979 |
+
const fieldRows = (p.fields || []).map((f) =>
|
| 980 |
+
`<div class="env-row">
|
| 981 |
+
<div class="env-info">
|
| 982 |
+
<code class="env-name">${f.label}</code>
|
| 983 |
+
<span class="env-desc">${f.hint}</span>
|
| 984 |
+
</div>
|
| 985 |
+
<div class="env-actions">
|
| 986 |
+
<span class="badge ok">READY</span>
|
| 987 |
+
</div>
|
| 988 |
+
</div>`
|
| 989 |
+
).join("");
|
| 990 |
+
|
| 991 |
+
return `<div class="panel" id="panel-${idx}">
|
| 992 |
+
<div class="panel-head">
|
| 993 |
+
<span class="panel-emoji">${p.emoji}</span>
|
| 994 |
+
<div>
|
| 995 |
+
<div class="panel-title">${p.name}</div>
|
| 996 |
+
<div class="panel-links">
|
| 997 |
+
${p.docsUrl ? `<a href="${p.docsUrl}" target="_blank" rel="noopener">Official Docs β</a>` : ""}
|
| 998 |
+
</div>
|
| 999 |
+
</div>
|
| 1000 |
+
</div>
|
| 1001 |
+
|
| 1002 |
+
<div class="banner banner-ok">No developer portal needed β connects directly inside Postiz.</div>
|
| 1003 |
+
|
| 1004 |
+
<div class="section-label">Setup Steps</div>
|
| 1005 |
+
<div class="steps-list">${stepsList}</div>
|
| 1006 |
+
|
| 1007 |
+
${fieldRows ? `<div class="section-label">Fields Required in Postiz</div>
|
| 1008 |
+
<div class="env-list">${fieldRows}</div>` : ""}
|
| 1009 |
+
|
| 1010 |
+
<a href="${p.postizUrl}" target="_blank" rel="noopener" class="cta-btn">
|
| 1011 |
+
Open Postiz Integrations β
|
| 1012 |
+
</a>
|
| 1013 |
+
<p class="hint-final">Click <strong>Connect</strong> next to ${p.name} in Postiz, fill in the credentials above.</p>
|
| 1014 |
+
</div>`;
|
| 1015 |
+
}).join("");
|
| 1016 |
+
|
| 1017 |
+
const allPlatformIds = [
|
| 1018 |
+
...platforms.map((p) => p.id),
|
| 1019 |
+
...directPlatforms.map((p) => p.id),
|
| 1020 |
+
];
|
| 1021 |
+
|
| 1022 |
return `<!DOCTYPE html>
|
| 1023 |
<html lang="en">
|
| 1024 |
<head>
|
|
|
|
| 1040 |
--warn: #f5c542;
|
| 1041 |
--bad: #fb7185;
|
| 1042 |
--accent: #3b82f6;
|
|
|
|
| 1043 |
}
|
| 1044 |
body {
|
| 1045 |
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
|
|
| 1117 |
color: var(--muted);
|
| 1118 |
padding: 6px 10px 10px;
|
| 1119 |
}
|
| 1120 |
+
.sidebar-divider {
|
| 1121 |
+
height: 1px;
|
| 1122 |
+
background: var(--line);
|
| 1123 |
+
margin: 6px 4px 8px;
|
| 1124 |
+
}
|
| 1125 |
.plat-tab {
|
| 1126 |
width: 100%;
|
| 1127 |
background: none;
|
| 1128 |
+
border: 1px solid transparent;
|
| 1129 |
color: var(--soft);
|
| 1130 |
font: inherit;
|
| 1131 |
font-size: .82rem;
|
|
|
|
| 1138 |
cursor: pointer;
|
| 1139 |
text-align: left;
|
| 1140 |
transition: background .12s, color .12s;
|
|
|
|
| 1141 |
}
|
| 1142 |
.plat-tab:hover { background: var(--panel2); color: var(--text); }
|
| 1143 |
.plat-tab.active {
|
|
|
|
| 1168 |
.panel-title { font-size: 1.4rem; font-weight: 850; margin-bottom: 6px; }
|
| 1169 |
.panel-links { display: flex; gap: 14px; flex-wrap: wrap; }
|
| 1170 |
.panel-links a {
|
| 1171 |
+
color: var(--accent);
|
| 1172 |
font-size: .76rem;
|
| 1173 |
font-weight: 700;
|
| 1174 |
text-decoration: none;
|
|
|
|
| 1224 |
width: 22px;
|
| 1225 |
height: 22px;
|
| 1226 |
border-radius: 50%;
|
| 1227 |
+
background: var(--accent);
|
| 1228 |
color: #fff;
|
| 1229 |
font-size: .68rem;
|
| 1230 |
font-weight: 850;
|
|
|
|
| 1237 |
.step-title { font-size: .82rem; font-weight: 800; margin-bottom: 3px; color: var(--text); }
|
| 1238 |
.step-body { font-size: .78rem; color: var(--soft); line-height: 1.6; }
|
| 1239 |
.step-body strong { color: var(--text); font-weight: 800; }
|
| 1240 |
+
.step-body em { color: var(--soft); font-style: italic; }
|
| 1241 |
.step-body code {
|
| 1242 |
background: var(--panel2);
|
| 1243 |
border: 1px solid var(--line);
|
|
|
|
| 1246 |
font-size: .85em;
|
| 1247 |
color: var(--text);
|
| 1248 |
}
|
| 1249 |
+
.step-body a { color: var(--accent); text-decoration: none; }
|
| 1250 |
.step-body a:hover { text-decoration: underline; }
|
| 1251 |
|
| 1252 |
/* Callback copy block */
|
|
|
|
| 1263 |
flex: 1;
|
| 1264 |
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, monospace;
|
| 1265 |
font-size: .78rem;
|
| 1266 |
+
color: var(--accent);
|
| 1267 |
word-break: break-all;
|
| 1268 |
opacity: .9;
|
| 1269 |
}
|
| 1270 |
|
| 1271 |
+
/* Env var / field rows */
|
| 1272 |
.env-list { display: flex; flex-direction: column; gap: 5px; }
|
| 1273 |
.env-row {
|
| 1274 |
display: flex;
|
|
|
|
| 1283 |
.env-name {
|
| 1284 |
font-family: ui-monospace, "Cascadia Code", monospace;
|
| 1285 |
font-size: .78rem;
|
| 1286 |
+
color: var(--accent);
|
| 1287 |
+
background: rgba(59,130,246,.1);
|
| 1288 |
+
border: 1px solid rgba(59,130,246,.2);
|
| 1289 |
padding: 2px 7px;
|
| 1290 |
border-radius: 5px;
|
| 1291 |
width: fit-content;
|
|
|
|
| 1309 |
.badge.ok { color: var(--good); border-color: rgba(34,197,94,.3); background: rgba(34,197,94,.1); }
|
| 1310 |
.badge.off { color: var(--bad); border-color: rgba(251,113,133,.3); background: rgba(251,113,133,.1); }
|
| 1311 |
|
| 1312 |
+
/* Buttons β secondary (small copy buttons) */
|
| 1313 |
.btn-copy {
|
| 1314 |
background: var(--panel2);
|
| 1315 |
border: 1px solid var(--line);
|
|
|
|
| 1326 |
}
|
| 1327 |
.btn-copy:hover { background: var(--line); color: var(--text); }
|
| 1328 |
.btn-copy.copied { background: rgba(34,197,94,.12); border-color: rgba(34,197,94,.3); color: var(--good); }
|
| 1329 |
+
|
| 1330 |
+
/* White copy button (accent copy block) */
|
| 1331 |
+
.btn-copy-white {
|
| 1332 |
+
background: #ffffff;
|
| 1333 |
+
border: 1px solid #ffffff;
|
| 1334 |
+
color: #0d0c1a;
|
| 1335 |
+
font: inherit;
|
| 1336 |
font-size: .76rem;
|
| 1337 |
+
font-weight: 800;
|
| 1338 |
padding: 6px 14px;
|
| 1339 |
+
border-radius: 6px;
|
| 1340 |
+
cursor: pointer;
|
| 1341 |
+
transition: background .12s;
|
| 1342 |
+
flex-shrink: 0;
|
| 1343 |
+
white-space: nowrap;
|
| 1344 |
}
|
| 1345 |
+
.btn-copy-white:hover { background: #e8e8f0; border-color: #e8e8f0; }
|
| 1346 |
+
.btn-copy-white.copied { background: rgba(34,197,94,.15); border-color: rgba(34,197,94,.4); color: var(--good); }
|
| 1347 |
|
| 1348 |
+
/* Primary CTA button β white with dark text */
|
| 1349 |
+
.cta-btn {
|
| 1350 |
display: inline-flex;
|
| 1351 |
align-items: center;
|
| 1352 |
+
margin-top: 18px;
|
| 1353 |
+
background: #ffffff;
|
| 1354 |
+
color: #0d0c1a;
|
| 1355 |
text-decoration: none;
|
| 1356 |
+
padding: 10px 22px;
|
| 1357 |
border-radius: 8px;
|
| 1358 |
font-size: .82rem;
|
| 1359 |
font-weight: 850;
|
| 1360 |
+
border: none;
|
| 1361 |
+
transition: background .15s;
|
| 1362 |
+
letter-spacing: .01em;
|
| 1363 |
}
|
| 1364 |
+
.cta-btn:hover { background: #e8e8f0; }
|
| 1365 |
+
|
| 1366 |
.hint-final {
|
| 1367 |
margin-top: 10px;
|
| 1368 |
font-size: .74rem;
|
|
|
|
| 1387 |
gap: 4px;
|
| 1388 |
}
|
| 1389 |
.sidebar-label { display: none; }
|
| 1390 |
+
.sidebar-divider { display: none; }
|
| 1391 |
.plat-tab { width: auto; flex: 0 0 auto; }
|
| 1392 |
.tab-name { display: none; }
|
| 1393 |
.main { padding: 16px; }
|
|
|
|
| 1398 |
<div class="topbar">
|
| 1399 |
<a class="topbar-back" href="/">β Dashboard</a>
|
| 1400 |
<span class="topbar-title">Platform Setup Guide</span>
|
| 1401 |
+
<span class="topbar-count">${configuredCount} / ${platforms.length} OAuth configured</span>
|
| 1402 |
</div>
|
| 1403 |
<div class="layout">
|
| 1404 |
<nav class="sidebar">
|
| 1405 |
<div class="sidebar-label">OAuth Platforms</div>
|
| 1406 |
+
${oauthSidebarItems}
|
| 1407 |
+
<div class="sidebar-divider"></div>
|
| 1408 |
+
<div class="sidebar-label">Direct Connect</div>
|
| 1409 |
+
${directSidebarItems}
|
| 1410 |
</nav>
|
| 1411 |
<main class="main">
|
| 1412 |
+
${oauthPanels}
|
| 1413 |
+
${directPanels}
|
| 1414 |
</main>
|
| 1415 |
</div>
|
| 1416 |
<script>
|
| 1417 |
+
const PLATFORM_IDS = ${JSON.stringify(allPlatformIds)};
|
| 1418 |
function show(i) {
|
| 1419 |
document.querySelectorAll('.plat-tab').forEach((t,j)=>t.classList.toggle('active',j===i));
|
| 1420 |
document.querySelectorAll('.panel').forEach((p,j)=>p.classList.toggle('active',j===i));
|