wiki-plugin-shoppe 0.0.25 → 0.0.27

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/shoppe.js CHANGED
@@ -22,8 +22,11 @@
22
22
  .sw-shoppe-left { display: flex; flex-direction: column; gap: 2px; }
23
23
  .sw-shoppe-name { font-weight: 600; font-size: 15px; }
24
24
  .sw-shoppe-code { font-size: 18px; letter-spacing: 4px; }
25
+ .sw-shoppe-actions { display: flex; align-items: center; gap: 12px; }
25
26
  .sw-link { font-size: 13px; color: #0066cc; text-decoration: none; white-space: nowrap; }
26
27
  .sw-link:hover { text-decoration: underline; }
28
+ .sw-btn-delete { background: none; border: none; color: #cc0000; font-size: 13px; cursor: pointer; padding: 0; white-space: nowrap; }
29
+ .sw-btn-delete:hover { text-decoration: underline; }
27
30
  .sw-empty { font-size: 13px; color: #999; font-style: italic; }
28
31
  .sw-drop { border: 2px dashed #ccc; border-radius: 12px; padding: 28px 20px; text-align: center; background: #fafafa; transition: border-color 0.2s, background 0.2s; cursor: pointer; }
29
32
  .sw-drop.dragover { border-color: #0066cc; background: #e8f0fe; }
@@ -41,6 +44,8 @@
41
44
  .sw-status.success { background: #d1fae5; color: #065f46; display: block; }
42
45
  .sw-status.error { background: #fee2e2; color: #991b1b; display: block; }
43
46
  .sw-status code { background: rgba(0,0,0,0.08); border-radius: 4px; padding: 1px 5px; font-size: 12px; }
47
+ .sw-remove { display: block; width: 100%; margin-top: 24px; padding: 8px; background: none; border: 1px solid #e5e5ea; border-radius: 8px; font-size: 12px; color: #aaa; cursor: pointer; text-align: center; }
48
+ .sw-remove:hover { border-color: #cc0000; color: #cc0000; }
44
49
  </style>
45
50
 
46
51
  <!-- Directory -->
@@ -161,37 +166,70 @@
161
166
  <div id="sw-register-status" class="sw-status"></div>
162
167
  </div>
163
168
 
169
+ <button class="sw-remove" id="sw-remove-btn">Remove plugin from page</button>
170
+
164
171
  </div>
165
172
  `;
166
173
 
174
+ div.querySelector('#sw-remove-btn').addEventListener('click', () => {
175
+ const $page = $item.parents('.page');
176
+ if (window.wiki && wiki.pageHandler && $page.length) {
177
+ wiki.pageHandler.put($page, { type: 'remove', id: item.id });
178
+ $item.remove();
179
+ } else if (window.wiki && wiki.textEditor) {
180
+ wiki.textEditor($item, item);
181
+ }
182
+ });
183
+
167
184
  setupListeners(div);
168
- loadDirectory(div);
169
- checkOwner(div);
185
+ checkOwner(div).then(isOwner => {
186
+ if (!isOwner) loadDirectory(div, false);
187
+ });
170
188
  },
171
189
 
172
- bind: function($item, item) {}
190
+ bind: function($item, item) {
191
+ $item.on('dblclick', () => {
192
+ const $page = $item.parents('.page');
193
+ if (window.wiki && wiki.pageHandler && $page.length) {
194
+ wiki.pageHandler.put($page, { type: 'remove', id: item.id });
195
+ $item.remove();
196
+ } else if (window.wiki && wiki.textEditor) {
197
+ wiki.textEditor($item, item);
198
+ }
199
+ });
200
+ }
173
201
  };
174
202
 
175
- // ── Directory (public) ──────────────────────────────────────────────────────
203
+ // ── Directory ────────────────────────────────────────────────────────────────
176
204
 
177
- async function loadDirectory(container) {
205
+ async function loadDirectory(container, isOwner = false) {
178
206
  const el = container.querySelector('#sw-directory');
179
207
  try {
180
- const resp = await fetch('/plugin/shoppe/directory');
208
+ const resp = await fetch(isOwner ? '/plugin/shoppe/tenants' : '/plugin/shoppe/directory');
181
209
  const result = await resp.json();
182
- if (!result.success || result.shoppes.length === 0) {
210
+ const shoppes = isOwner ? result.tenants : result.shoppes;
211
+ if (!result.success || !shoppes || shoppes.length === 0) {
183
212
  el.innerHTML = '<em class="sw-empty">No shoppes yet — be the first!</em>';
184
213
  return;
185
214
  }
186
- el.innerHTML = result.shoppes.map(s => `
187
- <div class="sw-shoppe">
215
+ el.innerHTML = shoppes.map(s => `
216
+ <div class="sw-shoppe" id="sw-shoppe-${s.uuid}">
188
217
  <div class="sw-shoppe-left">
189
218
  <span class="sw-shoppe-name">${s.name}</span>
190
219
  <span class="sw-shoppe-code">${s.emojicode}</span>
191
220
  </div>
192
- <a class="sw-link" href="${s.url}" target="_blank">Visit shoppe →</a>
221
+ <div class="sw-shoppe-actions">
222
+ <a class="sw-link" href="${s.url}" target="_blank">Visit shoppe →</a>
223
+ ${isOwner ? `<button class="sw-btn-delete" data-uuid="${s.uuid}" data-name="${s.name}">Delete</button>` : ''}
224
+ </div>
193
225
  </div>
194
226
  `).join('');
227
+
228
+ if (isOwner) {
229
+ el.querySelectorAll('.sw-btn-delete').forEach(btn => {
230
+ btn.addEventListener('click', () => deleteShoppe(btn.dataset.uuid, btn.dataset.name, container));
231
+ });
232
+ }
195
233
  } catch (err) {
196
234
  el.innerHTML = '<em class="sw-empty">Could not load directory.</em>';
197
235
  }
@@ -208,8 +246,30 @@
208
246
  if (result.sanoraUrl) {
209
247
  container.querySelector('#sw-url-input').value = result.sanoraUrl;
210
248
  }
249
+ loadDirectory(container, true);
250
+ return true;
211
251
  }
212
252
  } catch (err) { /* not owner, stay hidden */ }
253
+ return false;
254
+ }
255
+
256
+ // ── Delete shoppe (owner) ────────────────────────────────────────────────────
257
+
258
+ async function deleteShoppe(uuid, name, container) {
259
+ if (!confirm(`Delete "${name}"? This will remove the shoppe and all its products from Sanora.`)) return;
260
+
261
+ const row = container.querySelector(`#sw-shoppe-${uuid}`);
262
+ if (row) row.style.opacity = '0.4';
263
+
264
+ try {
265
+ const resp = await fetch(`/plugin/shoppe/${uuid}`, { method: 'DELETE' });
266
+ const result = await resp.json();
267
+ if (!result.success) throw new Error(result.error || 'Delete failed');
268
+ loadDirectory(container, true);
269
+ } catch (err) {
270
+ if (row) row.style.opacity = '1';
271
+ alert(`Could not delete shoppe: ${err.message}`);
272
+ }
213
273
  }
214
274
 
215
275
  // ── Listeners ───────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-plugin-shoppe",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "Multi-tenant digital goods shoppe for federated wiki, powered by Sanora",
5
5
  "keywords": [
6
6
  "wiki",
package/server/server.js CHANGED
@@ -219,7 +219,7 @@ function generateBundleBuffer(tenant, ownerPrivateKey, ownerPubKey, wikiOrigin)
219
219
  private: true,
220
220
  description: 'Shoppe content folder',
221
221
  dependencies: {
222
- 'sessionless-node': '^0.9.12'
222
+ 'sessionless-node': 'latest'
223
223
  }
224
224
  }, null, 2);
225
225
 
@@ -489,6 +489,7 @@ async function sanoraCreateProduct(tenant, title, category, description, price,
489
489
  `${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}`,
490
490
  {
491
491
  method: 'PUT',
492
+ timeout: 15000,
492
493
  headers: { 'Content-Type': 'application/json' },
493
494
  body: JSON.stringify({
494
495
  timestamp,
@@ -522,6 +523,7 @@ async function sanoraUploadArtifact(tenant, title, fileBuffer, filename, artifac
522
523
  `${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}/artifact`,
523
524
  {
524
525
  method: 'PUT',
526
+ timeout: 30000,
525
527
  headers: {
526
528
  'x-pn-artifact-type': artifactType,
527
529
  'x-pn-timestamp': timestamp,
@@ -551,6 +553,7 @@ async function sanoraUploadImage(tenant, title, imageBuffer, filename) {
551
553
  `${getSanoraUrl()}/user/${uuid}/product/${encodeURIComponent(title)}/image`,
552
554
  {
553
555
  method: 'PUT',
556
+ timeout: 30000,
554
557
  headers: {
555
558
  'x-pn-timestamp': timestamp,
556
559
  'x-pn-signature': signature,