@takagaki/cortex-decisions-viewer 0.12.116 → 0.12.117

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.
Files changed (2) hide show
  1. package/dist/render.js +67 -8
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -6067,6 +6067,9 @@ button.ops-card-btn:disabled { opacity: .6; cursor: default; }
6067
6067
  .confirm-body-label { font-size: 13px; color: var(--muted); }
6068
6068
  .modal-ok { color: #047857; font-weight: 600; }
6069
6069
  .modal-status-error { color: #dc2626; font-weight: 600; }
6070
+ .modal-reopen { margin-top: 6px; font-weight: 400; }
6071
+ .modal-reopen-link { font-weight: 600; }
6072
+ .modal-reopen-note { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
6070
6073
  .modal-paths { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; }
6071
6074
  .modal-paths-label { font-size: 12px; color: var(--muted); }
6072
6075
  .modal-path { font-size: 12.5px; word-break: break-all; color: var(--accent); }
@@ -6180,20 +6183,48 @@ const CLIENT_JS = `
6180
6183
  // 券は document.cookie から**1回だけ**読んでメモリに持つ。site-data にも localStorage にも書かない
6181
6184
  // (ビルド時の値ではなく、開いた人ごとに違う値なので)。
6182
6185
  //
6183
- // **1回だけ読むのは仕様。** ポータルからログアウトしても、開いたままのタブは券の期限まで
6186
+ // **期限内の券は1回だけ読むのが仕様。** ポータルからログアウトしても、開いたままのタブは券の期限まで
6184
6187
  // 名乗り続ける——署名付き Cookie と同じ性質で、券の寿命がそのまま名乗りの寿命になる。
6188
+ //
6189
+ // 🔴 **持っている券の期限(exp)が過ぎたら、Cookie を読み直す**(券が無かったときも読み直す・engine #1037)。
6190
+ // 切れた券は intake が必ず ACTOR_INVALID で断るので、持ち続けて得るものは無い。持ち続けると、
6191
+ // 案内どおり別のタブでポータルから開き直して Cookie が新しくなっても、このタブは古い券を送り続け、
6192
+ // 入力したフォームの[送信]が何度押しても通らない(10/1 に 3 分で 10 回断られた形)。
6193
+ // exp を読めない券は「切れていない」として扱う(読み直さない=今までどおり)
6185
6194
  var ACTOR_COOKIE = "cortex-actor";
6186
6195
  var actorTicketCache;
6196
+ /**
6197
+ * intake が名乗りの理由で断った券は、期限内に見えても次の呼び出しで Cookie を読み直す
6198
+ * (鍵の入れ替えで kid が外れた・端末の時計が遅れている、のとき・#1037 の実装レビュー🟡)。
6199
+ * Cookie が同じ値のままなら同じ券を送り続ける(開き直すまでは何も変わらない)
6200
+ */
6201
+ var actorTicketRejected = false;
6202
+ function forgetActorTicket() { actorTicketRejected = true; }
6203
+ function actorTicketExpired(ticket) {
6204
+ try {
6205
+ var p = String(ticket).split(".")[1];
6206
+ if (!p) return false;
6207
+ p = p.replace(/-/g, "+").replace(/_/g, "/");
6208
+ while (p.length % 4) p += "=";
6209
+ var claims = JSON.parse(atob(p));
6210
+ return !!claims && typeof claims.exp === "number" && isFinite(claims.exp) && !(claims.exp * 1000 > Date.now());
6211
+ } catch (e) { return false; }
6212
+ }
6187
6213
  function actorTicket() {
6188
- if (actorTicketCache !== undefined) return actorTicketCache;
6189
- actorTicketCache = null;
6214
+ if (actorTicketCache && !actorTicketRejected && !actorTicketExpired(actorTicketCache)) return actorTicketCache;
6215
+ var found = null;
6190
6216
  try {
6191
6217
  var parts = String(document.cookie || "").split(";");
6192
6218
  for (var i = 0; i < parts.length; i++) {
6193
6219
  var kv = parts[i].trim();
6194
- if (kv.indexOf(ACTOR_COOKIE + "=") === 0) { actorTicketCache = kv.slice(ACTOR_COOKIE.length + 1) || null; break; }
6220
+ if (kv.indexOf(ACTOR_COOKIE + "=") === 0) { found = kv.slice(ACTOR_COOKIE.length + 1) || null; break; }
6195
6221
  }
6196
- } catch (e) { actorTicketCache = null; }
6222
+ } catch (e) { found = null; }
6223
+ // **別の券**を読めたら乗り換える(その券はまだ断られていない)。
6224
+ // 読み直して何も無ければ、持っている券を送り続ける(intake が断り、画面が開き直しを案内する)。
6225
+ // 券なしに変えると ACTOR_REQUIRED になるだけで、案内は同じ——名無しに変える理由が無い
6226
+ if (found && found !== actorTicketCache) { actorTicketCache = found; actorTicketRejected = false; }
6227
+ else if (!actorTicketCache) actorTicketCache = found;
6197
6228
  return actorTicketCache;
6198
6229
  }
6199
6230
  /**
@@ -19337,6 +19368,28 @@ ${calendarAddUrl.toString()}
19337
19368
  submitIntake(kind, selectedFiles, nameInput.value, status, submitBtn, isT ? meetingValue() : null, allDup);
19338
19369
  });
19339
19370
  }
19371
+ /**
19372
+ * 名乗りの理由で断られたときに、送信の結果の下へ足す一手(engine #1037)。
19373
+ *
19374
+ * intake の文言は「ポータルに戻って、この案件を開き直してください」だが、それだけでは
19375
+ * **同じタブで開き直して入力を失う**か、**別のタブで開き直してこのタブで押し直し、また断られる**
19376
+ * (券を持ち続けていた頃)のどちらかになる。券は期限が切れると読み直す(actorTicket)ので、
19377
+ * 別のタブで開き直せば、このタブの入力のまま押し直して通る——それを 1 行で言う。
19378
+ * ポータル配下でないとき(Amplify・手元の確認)は開き直す先が無いので出さない
19379
+ */
19380
+ var ACTOR_REOPEN_CODES = ["ACTOR_INVALID", "ACTOR_REQUIRED", "ACTOR_PROJECT_MISMATCH"];
19381
+ function actorReopenNote(code) {
19382
+ // ACCESS_REVOKED(参加を確認できない)は入れない。参加は人で引くので、券を取り直しても
19383
+ // 同じ答えになる——「押し直せば通る」と言えない(intake の文言がポータルからの開き直しを案内する)
19384
+ if (ACTOR_REOPEN_CODES.indexOf(code) === -1) return null;
19385
+ var m = /^\\/p\\/[^/]+\\/[^/]+\\//.exec(location.pathname);
19386
+ if (!m) return null;
19387
+ var href = "/open?to=" + encodeURIComponent(m[0]);
19388
+ return el("div", { class: "modal-reopen" }, [
19389
+ el("a", { class: "modal-reopen-link", href: href, target: "_blank", rel: "noopener", text: "ポータルでこの案件を開き直す(別タブ)" }),
19390
+ el("div", { class: "modal-reopen-note", text: "開き直したら、このタブに戻ってもう一度[送信]を押してください。入力はそのまま使えます。" }),
19391
+ ]);
19392
+ }
19340
19393
  async function submitIntake(kind, entries, submitter, status, submitBtn, meeting, allDup) {
19341
19394
  var intake = SITE.intake;
19342
19395
  var isT = kind === "transcript";
@@ -19345,7 +19398,13 @@ ${calendarAddUrl.toString()}
19345
19398
  if (!entries || !entries.length) { setStatus("⚠ ファイルを選択してください。", true); return; }
19346
19399
  if (isT && !meeting) { setStatus("⚠ 会議名を選んでください。", true); return; }
19347
19400
  submitBtn.disabled = true;
19348
- function fail(msg) { setStatus("✗ " + msg, true); submitBtn.disabled = false; }
19401
+ function fail(msg, code) {
19402
+ if (ACTOR_REOPEN_CODES.indexOf(code) !== -1) forgetActorTicket();
19403
+ setStatus("✗ " + msg, true);
19404
+ var reopen = actorReopenNote(code);
19405
+ if (reopen) status.appendChild(reopen);
19406
+ submitBtn.disabled = false;
19407
+ }
19349
19408
  try {
19350
19409
  var uploaded = [];
19351
19410
  for (var i = 0; i < entries.length; i++) {
@@ -19356,7 +19415,7 @@ ${calendarAddUrl.toString()}
19356
19415
  pres = await callIntake("presign", { projectKey: intake.projectKey, filename: f.name, contentType: f.type || "application/octet-stream" })
19357
19416
  .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
19358
19417
  } catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
19359
- if (!pres.ok || !pres.j.url) return fail((pres.j && pres.j.message) || "アップロードURLの取得に失敗しました。対応していないファイル形式かもしれません。");
19418
+ if (!pres.ok || !pres.j.url) return fail((pres.j && pres.j.message) || "アップロードURLの取得に失敗しました。対応していないファイル形式かもしれません。", pres.j && pres.j.code);
19360
19419
  var put;
19361
19420
  try {
19362
19421
  put = await fetch(pres.j.url, { method: "PUT", headers: { "content-type": f.type || "application/octet-stream" }, body: f });
@@ -19375,7 +19434,7 @@ ${calendarAddUrl.toString()}
19375
19434
  sub = await callIntake("submit", payload)
19376
19435
  .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
19377
19436
  } catch (net3) { return fail("サーバーに接続できませんでした。もう一度お試しください。"); }
19378
- if (!sub.ok) return fail((sub.j && sub.j.message) || "取り込みに失敗しました。時間をおいて再度お試しください。");
19437
+ if (!sub.ok) return fail((sub.j && sub.j.message) || "取り込みに失敗しました。時間をおいて再度お試しください。", sub.j && sub.j.code);
19379
19438
  status.className = "modal-status";
19380
19439
  status.textContent = "";
19381
19440
  status.appendChild(el("div", { class: "modal-ok", text: allDup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.116",
3
+ "version": "0.12.117",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,