wiki-plugin-salon 0.0.1 → 0.0.3
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/client/salon.js +11 -1
- package/package.json +2 -4
- package/server/server.js +43 -30
package/client/salon.js
CHANGED
|
@@ -87,8 +87,18 @@ window.plugins.salon = (function() {
|
|
|
87
87
|
if (data.isOwner) {
|
|
88
88
|
const configDiv = document.createElement('div');
|
|
89
89
|
configDiv.style.cssText = 'border:1px solid rgba(196,181,253,0.2);border-radius:8px;padding:12px;margin-top:8px;background:rgba(124,58,237,0.06);font-family:system-ui;';
|
|
90
|
+
const pendingBadge = data.pendingCount > 0
|
|
91
|
+
? `<span style="background:#f59e0b;color:#000;border-radius:9px;font-size:0.7rem;padding:1px 6px;margin-left:6px;">${data.pendingCount} pending</span>`
|
|
92
|
+
: '';
|
|
90
93
|
configDiv.innerHTML = `
|
|
91
|
-
<div style="
|
|
94
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;">
|
|
95
|
+
<span style="font-size:0.8rem;color:#c4b5fd;font-weight:600;">Owner panel${pendingBadge}</span>
|
|
96
|
+
<a href="/plugin/salon/admin" target="_blank"
|
|
97
|
+
style="font-size:0.78rem;color:#7c3aed;text-decoration:none;border:1px solid rgba(124,58,237,0.4);border-radius:4px;padding:3px 8px;">
|
|
98
|
+
Admin ↗
|
|
99
|
+
</a>
|
|
100
|
+
</div>
|
|
101
|
+
<div style="font-size:0.8rem;color:#a0a0a0;margin-bottom:8px;">Allyabase URL</div>
|
|
92
102
|
<div style="display:flex;gap:6px;align-items:center;">
|
|
93
103
|
<input id="salon-url-input" value="${escapeHtml(data.allyabaseUrl || '')}" placeholder="https://dev.allyabase.com"
|
|
94
104
|
style="flex:1;background:#0d001a;border:1px solid rgba(196,181,253,0.3);border-radius:4px;padding:6px 8px;color:#e0d0ff;font-size:0.8rem;">
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-plugin-salon",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Federated Wiki plugin — community gathering space with self-registration and plugin event feeds",
|
|
5
5
|
"keywords": ["wiki", "plugin", "salon", "community", "planet-nine"],
|
|
6
6
|
"author": "Planet Nine",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"type": "commonjs",
|
|
9
9
|
"main": "index.js",
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"nodemailer": "^6.9.0"
|
|
12
|
-
}
|
|
10
|
+
"dependencies": {}
|
|
13
11
|
}
|
package/server/server.js
CHANGED
|
@@ -93,14 +93,21 @@ function buildEventEmail(config, event, detail) {
|
|
|
93
93
|
|
|
94
94
|
// ── Email ─────────────────────────────────────────────────────────────────────
|
|
95
95
|
|
|
96
|
-
function
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
function getMinnieUrl(config) {
|
|
97
|
+
const base = config.allyabaseUrl ||
|
|
98
|
+
(config.joanUrl ? config.joanUrl.replace(/\/plugin\/allyabase\/joan$/, '') : '');
|
|
99
|
+
return base ? base.replace(/\/$/, '') + '/plugin/allyabase/minnie' : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function sendViaMinnie(minnieUrl, { from, to, subject, text }) {
|
|
103
|
+
const resp = await fetch(`${minnieUrl}/send`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
106
|
+
body: JSON.stringify({ from, to, subject, text }),
|
|
107
|
+
signal: AbortSignal.timeout(10000)
|
|
103
108
|
});
|
|
109
|
+
if (!resp.ok) throw new Error(`Minnie /send failed: ${resp.status}`);
|
|
110
|
+
return resp.json();
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
async function notifyMembers(config, subject, text) {
|
|
@@ -108,12 +115,17 @@ async function notifyMembers(config, subject, text) {
|
|
|
108
115
|
const recipients = Object.values(tenants).filter(t => t.status === 'active' && t.email);
|
|
109
116
|
if (recipients.length === 0) return;
|
|
110
117
|
|
|
111
|
-
const
|
|
112
|
-
|
|
118
|
+
const minnieUrl = getMinnieUrl(config);
|
|
119
|
+
if (!minnieUrl) {
|
|
120
|
+
console.warn('salon: no Minnie URL configured — skipping email notifications');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const from = config.fromEmail || 'salon@planetnine.app';
|
|
113
125
|
|
|
114
126
|
for (const tenant of recipients) {
|
|
115
127
|
try {
|
|
116
|
-
await
|
|
128
|
+
await sendViaMinnie(minnieUrl, { from, to: tenant.email, subject, text });
|
|
117
129
|
} catch (err) {
|
|
118
130
|
console.error(`salon: email failed for ${tenant.email}:`, err.message);
|
|
119
131
|
}
|
|
@@ -632,15 +644,12 @@ function generateAdminPage(config, tenants) {
|
|
|
632
644
|
<label>From address</label>
|
|
633
645
|
<input name="fromEmail" value="${esc(config.fromEmail || 'salon@planetnine.app')}" placeholder="salon@planetnine.app">
|
|
634
646
|
|
|
635
|
-
<label>
|
|
636
|
-
<input name="
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
<label>Joan URL</label>
|
|
642
|
-
<input name="joanUrl" value="${esc(config.joanUrl || 'http://localhost:3008')}" placeholder="http://localhost:3008">
|
|
643
|
-
<div class="form-hint">Used to send OTP codes for member account recovery.</div>
|
|
647
|
+
<label>Allyabase URL</label>
|
|
648
|
+
<input name="allyabaseUrl" value="${esc(config.allyabaseUrl || (config.joanUrl ? config.joanUrl.replace(/\/plugin\/allyabase\/joan$/, '') : ''))}" placeholder="https://dev.allyabase.com">
|
|
649
|
+
<div class="form-hint">
|
|
650
|
+
Emails sent via <code>${esc((config.allyabaseUrl || (config.joanUrl ? config.joanUrl.replace(/\/plugin\/allyabase\/joan$/, '') : 'https://dev.allyabase.com')) + '/plugin/allyabase/minnie')}</code><br>
|
|
651
|
+
OTP codes sent via <code>${esc(config.joanUrl || 'https://dev.allyabase.com/plugin/allyabase/joan')}</code>
|
|
652
|
+
</div>
|
|
644
653
|
|
|
645
654
|
<div style="margin-top:16px;">
|
|
646
655
|
<button type="submit" class="btn btn-primary btn-sm">Save Settings</button>
|
|
@@ -1166,18 +1175,16 @@ function startServer(params) {
|
|
|
1166
1175
|
|
|
1167
1176
|
app.post('/plugin/salon/config', owner, json, (req, res) => {
|
|
1168
1177
|
const config = loadConfig();
|
|
1169
|
-
const { registrationMode, title, description, fromEmail,
|
|
1178
|
+
const { registrationMode, title, description, fromEmail, joanUrl, allyabaseUrl } = req.body;
|
|
1170
1179
|
const modes = ['open', 'closed', 'grant'];
|
|
1171
1180
|
if (registrationMode && modes.includes(registrationMode)) config.registrationMode = registrationMode;
|
|
1172
1181
|
if (title && title.trim()) config.title = title.trim();
|
|
1173
1182
|
if (description !== undefined) config.description = description.trim();
|
|
1174
1183
|
if (fromEmail !== undefined) config.fromEmail = fromEmail.trim();
|
|
1175
|
-
if (minnieHost !== undefined) config.minnieHost = minnieHost.trim();
|
|
1176
|
-
if (minniePort !== undefined) config.minniePort = parseInt(minniePort) || 2525;
|
|
1177
1184
|
if (joanUrl !== undefined) config.joanUrl = joanUrl.trim();
|
|
1178
|
-
if (allyabaseUrl !== undefined) {
|
|
1179
|
-
config.allyabaseUrl = allyabaseUrl.trim();
|
|
1180
|
-
config.joanUrl = allyabaseUrl
|
|
1185
|
+
if (allyabaseUrl !== undefined && allyabaseUrl.trim()) {
|
|
1186
|
+
config.allyabaseUrl = allyabaseUrl.trim().replace(/\/$/, '');
|
|
1187
|
+
config.joanUrl = config.allyabaseUrl + '/plugin/allyabase/joan';
|
|
1181
1188
|
}
|
|
1182
1189
|
saveConfig(config);
|
|
1183
1190
|
const derivedAllyabaseUrl = config.allyabaseUrl ||
|
|
@@ -1427,16 +1434,21 @@ function startServer(params) {
|
|
|
1427
1434
|
const recipients = Object.values(tenants).filter(t => t.status === 'active' && t.email);
|
|
1428
1435
|
|
|
1429
1436
|
if (recipients.length === 0) {
|
|
1430
|
-
return res.json({ success: true, sent: 0 });
|
|
1437
|
+
return res.json({ success: true, sent: 0, warning: 'No active members with email addresses found' });
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const minnieUrl = getMinnieUrl(config);
|
|
1441
|
+
if (!minnieUrl) {
|
|
1442
|
+
return res.status(503).json({ error: 'Email not configured — save your Allyabase URL in settings first' });
|
|
1431
1443
|
}
|
|
1432
1444
|
|
|
1433
|
-
const
|
|
1434
|
-
const from = `"${config.title}" <${config.fromEmail || 'salon@planetnine.app'}>`;
|
|
1445
|
+
const from = config.fromEmail || 'salon@planetnine.app';
|
|
1435
1446
|
let sent = 0;
|
|
1447
|
+
const errors = [];
|
|
1436
1448
|
|
|
1437
1449
|
for (const tenant of recipients) {
|
|
1438
1450
|
try {
|
|
1439
|
-
await
|
|
1451
|
+
await sendViaMinnie(minnieUrl, {
|
|
1440
1452
|
from,
|
|
1441
1453
|
to: tenant.email,
|
|
1442
1454
|
subject: subject.slice(0, 200),
|
|
@@ -1445,10 +1457,11 @@ function startServer(params) {
|
|
|
1445
1457
|
sent++;
|
|
1446
1458
|
} catch (err) {
|
|
1447
1459
|
console.error(`salon: announce email failed for ${tenant.email}:`, err.message);
|
|
1460
|
+
errors.push(tenant.email);
|
|
1448
1461
|
}
|
|
1449
1462
|
}
|
|
1450
1463
|
|
|
1451
|
-
res.json({ success: true, sent });
|
|
1464
|
+
res.json({ success: true, sent, ...(errors.length && { errors }) });
|
|
1452
1465
|
});
|
|
1453
1466
|
|
|
1454
1467
|
// ── Freyja federation page ──────────────────────────────────────────────────
|