estela 0.2.2 → 0.2.4

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/web/app.js CHANGED
@@ -1213,6 +1213,8 @@ function wireProjects() {
1213
1213
  });
1214
1214
  });
1215
1215
 
1216
+ // Publicar va en dos pasos, como en la terminal: primero lo que va a quedar
1217
+ // público (autores, ramas, ficheros, correos), y solo al confirmar se sube.
1216
1218
  document.querySelectorAll(".share-btn").forEach((btn) => {
1217
1219
  btn.addEventListener("click", async () => {
1218
1220
  const card = btn.closest(".pcard");
@@ -1220,54 +1222,91 @@ function wireProjects() {
1220
1222
  const out = card.querySelector(".share-out");
1221
1223
  btn.disabled = true;
1222
1224
  try {
1223
- // El token ya vive en la base de datos local (una fila por proyecto,
1224
- // se lee de state.projects.publications): guardarlo también en
1225
- // localStorage sería una segunda copia que puede desincronizarse de
1226
- // la real, y lo hizo — un token de una prueba vieja en el navegador
1227
- // pisó el que de verdad estaba publicado.
1228
1225
  const known = state.projects?.publications?.find((p) => p.projectId === projectId);
1229
- const r = await api("/api/publish", {
1226
+ const { summary: s } = await api("/api/publish", {
1230
1227
  method: "POST",
1231
- body: JSON.stringify({ projectId, token: known?.token || "" }),
1228
+ body: JSON.stringify({ projectId, token: known?.token || "", preview: true }),
1232
1229
  });
1233
-
1230
+ const list = (items) => items.length ? items.map(esc).join(", ") : "—";
1231
+ const row = (label, value) => '<p class="share-line">' + label + ' ' + value + '</p>';
1234
1232
  out.hidden = false;
1235
1233
  out.innerHTML =
1236
- '<p class="share-line"><b>' + (r.adopted ? tr("publish.linked") : tr("publish.published")) +
1237
- '</b></p>' +
1238
- '<p class="share-line">' + tr("publish.link") + ' <code>' + esc(r.url) + '</code></p>' +
1239
- '<p class="share-warn">' + tr("publish.noPassword") + '</p>';
1240
- toast(r.adopted ? tr("toast.linkedToAccount") : tr("publish.published"));
1241
-
1242
- // Publicar pone a cero los "bloques nuevos desde que lo publicaste",
1243
- // pero ese número vive en `state`, que se quedaba con el valor de
1244
- // antes: el aviso seguía ahí después de resolverlo, y solo se iba
1245
- // recargando la página a mano. Se vuelve a pedir el estado y se
1246
- // repinta, conservando el mensaje del enlace recién generado.
1247
- const recien = out.innerHTML;
1248
- await loadProjects();
1249
- const card2 = document.querySelector(`.pcard[data-project="${CSS.escape(projectId)}"]`);
1250
- const out3 = card2?.querySelector(".share-out");
1251
- if (out3) { out3.hidden = false; out3.innerHTML = recien; }
1234
+ '<p class="share-line"><b>' + tr("publish.preview.title") + '</b></p>' +
1235
+ row(tr("publish.preview.project"), esc(s.project) + " · " + esc(s.client)) +
1236
+ row(tr("publish.preview.authors"), list(s.authors)) +
1237
+ row(tr("publish.preview.branches"), list(s.branches)) +
1238
+ row(tr("publish.preview.files"), list(s.files)) +
1239
+ row(tr("publish.preview.clients"), list(s.clients)) +
1240
+ '<p class="share-warn">' + tr("publish.noPassword") + '</p>' +
1241
+ '<p><button type="button" class="chip share-confirm">' + tr("publish.preview.confirm") + '</button> ' +
1242
+ '<button type="button" class="chip share-cancel">' + tr("publish.preview.cancel") + '</button></p>';
1243
+ out.querySelector(".share-cancel").addEventListener("click", () => { out.hidden = true; out.innerHTML = ""; });
1244
+ out.querySelector(".share-confirm").addEventListener("click", (e) => {
1245
+ e.currentTarget.disabled = true;
1246
+ publishNow(card, btn);
1247
+ });
1252
1248
  } catch (error) {
1253
- const out2 = card.querySelector(".share-out");
1254
- // "Sin cuenta" no es un error cualquiera: se enseña con el comando
1255
- // exacto para resolverlo, no como un aviso genérico que hay que
1256
- // interpretar. `needsLogin` viaja en el cuerpo desde /api/publish.
1257
- if (error.body?.needsLogin) {
1258
- out2.hidden = false;
1259
- out2.innerHTML =
1260
- '<p class="share-line"><b>' + tr("publish.needsLogin.title") + '</b></p>' +
1261
- '<p class="share-line">' + tr("publish.needsLogin.why") + '</p>' +
1262
- '<code class="cmd">' + tr("publish.needsLogin.cmd") + '</code>' +
1263
- '<p class="share-warn">' + tr("publish.needsLogin.once") + '</p>';
1264
- } else {
1265
- toast(error.message);
1266
- }
1249
+ showPublishError(card, error);
1267
1250
  } finally { btn.disabled = false; }
1268
1251
  });
1269
1252
  });
1270
1253
 
1254
+ async function publishNow(card, btn) {
1255
+ const projectId = card.dataset.project;
1256
+ const out = card.querySelector(".share-out");
1257
+ btn.disabled = true;
1258
+ try {
1259
+ // El token ya vive en la base de datos local (una fila por proyecto,
1260
+ // se lee de state.projects.publications): guardarlo también en
1261
+ // localStorage sería una segunda copia que puede desincronizarse de
1262
+ // la real, y lo hizo — un token de una prueba vieja en el navegador
1263
+ // pisó el que de verdad estaba publicado.
1264
+ const known = state.projects?.publications?.find((p) => p.projectId === projectId);
1265
+ const r = await api("/api/publish", {
1266
+ method: "POST",
1267
+ body: JSON.stringify({ projectId, token: known?.token || "" }),
1268
+ });
1269
+
1270
+ out.hidden = false;
1271
+ out.innerHTML =
1272
+ '<p class="share-line"><b>' + (r.adopted ? tr("publish.linked") : tr("publish.published")) +
1273
+ '</b></p>' +
1274
+ '<p class="share-line">' + tr("publish.link") + ' <code>' + esc(r.url) + '</code></p>' +
1275
+ '<p class="share-warn">' + tr("publish.noPassword") + '</p>';
1276
+ toast(r.adopted ? tr("toast.linkedToAccount") : tr("publish.published"));
1277
+
1278
+ // Publicar pone a cero los "bloques nuevos desde que lo publicaste",
1279
+ // pero ese número vive en `state`, que se quedaba con el valor de
1280
+ // antes: el aviso seguía ahí después de resolverlo, y solo se iba
1281
+ // recargando la página a mano. Se vuelve a pedir el estado y se
1282
+ // repinta, conservando el mensaje del enlace recién generado.
1283
+ const recien = out.innerHTML;
1284
+ await loadProjects();
1285
+ const card2 = document.querySelector(`.pcard[data-project="${CSS.escape(projectId)}"]`);
1286
+ const out3 = card2?.querySelector(".share-out");
1287
+ if (out3) { out3.hidden = false; out3.innerHTML = recien; }
1288
+ } catch (error) {
1289
+ showPublishError(card, error);
1290
+ } finally { btn.disabled = false; }
1291
+ }
1292
+
1293
+ function showPublishError(card, error) {
1294
+ const out2 = card.querySelector(".share-out");
1295
+ // "Sin cuenta" no es un error cualquiera: se enseña con el comando
1296
+ // exacto para resolverlo, no como un aviso genérico que hay que
1297
+ // interpretar. `needsLogin` viaja en el cuerpo desde /api/publish.
1298
+ if (error.body?.needsLogin) {
1299
+ out2.hidden = false;
1300
+ out2.innerHTML =
1301
+ '<p class="share-line"><b>' + tr("publish.needsLogin.title") + '</b></p>' +
1302
+ '<p class="share-line">' + tr("publish.needsLogin.why") + '</p>' +
1303
+ '<code class="cmd">' + tr("publish.needsLogin.cmd") + '</code>' +
1304
+ '<p class="share-warn">' + tr("publish.needsLogin.once") + '</p>';
1305
+ } else {
1306
+ toast(error.message);
1307
+ }
1308
+ }
1309
+
1271
1310
  document.querySelectorAll(".rateform").forEach((form) => {
1272
1311
  form.addEventListener("submit", async (e) => {
1273
1312
  e.preventDefault();
package/web/i18n.js CHANGED
@@ -280,6 +280,14 @@
280
280
  "publish.published": "Panel publicado",
281
281
  "publish.link": "Enlace:",
282
282
  "publish.noPassword": "Quien tenga el enlace, entra: no hay contraseña.",
283
+ "publish.preview.title": "Esto va a quedar público. Quien tenga el enlace lo ve todo:",
284
+ "publish.preview.project": "Proyecto:",
285
+ "publish.preview.authors": "Autores:",
286
+ "publish.preview.branches": "Ramas:",
287
+ "publish.preview.files": "Ficheros:",
288
+ "publish.preview.clients": "Clientes:",
289
+ "publish.preview.confirm": "Publicar",
290
+ "publish.preview.cancel": "Cancelar",
283
291
  "publish.needsLogin.title": "Hace falta vincular una cuenta",
284
292
  "publish.needsLogin.why": "Publicar aloja el panel en getestela.dev, y eso exige sesión:",
285
293
  "publish.needsLogin.cmd": "estela login --email tu@correo.com",
@@ -577,6 +585,14 @@
577
585
  "publish.published": "Panel published",
578
586
  "publish.link": "Link:",
579
587
  "publish.noPassword": "Anyone with the link can get in: there's no password.",
588
+ "publish.preview.title": "This will become public. Anyone with the link sees all of it:",
589
+ "publish.preview.project": "Project:",
590
+ "publish.preview.authors": "Authors:",
591
+ "publish.preview.branches": "Branches:",
592
+ "publish.preview.files": "Files:",
593
+ "publish.preview.clients": "Clients:",
594
+ "publish.preview.confirm": "Publish",
595
+ "publish.preview.cancel": "Cancel",
580
596
  "publish.needsLogin.title": "You need to link an account",
581
597
  "publish.needsLogin.why": "Publishing hosts the panel on getestela.dev, which requires signing in:",
582
598
  "publish.needsLogin.cmd": "estela login --email you@example.com",