@takagaki/cortex-decisions-viewer 0.12.173 → 0.12.174

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 +63 -10
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -4923,6 +4923,10 @@ a:hover { text-decoration: underline; }
4923
4923
  .mface { position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; overflow: hidden; background: var(--border, #e5e7eb); color: var(--muted, #6b7280); font-size: 13px; font-weight: 700; line-height: 1; text-decoration: none; box-sizing: border-box; }
4924
4924
  a.mface:hover { box-shadow: 0 0 0 2px var(--accent, #4f46e5); }
4925
4925
  .mface svg { width: 18px; height: 18px; display: block; }
4926
+ .mface-loading { animation: mface-pulse 1.4s ease-in-out infinite; }
4927
+ .mface-loading img { opacity: 0; }
4928
+ @keyframes mface-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
4929
+ @media (prefers-reduced-motion: reduce) { .mface-loading { animation: none; } }
4926
4930
  .mface img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
4927
4931
  .detail-namerow .mface { width: 36px; height: 36px; font-size: 16px; }
4928
4932
  .detail-namerow .mface svg { width: 22px; height: 22px; }
@@ -7741,36 +7745,82 @@ ${story_1.splitByNameLinks.toString()}
7741
7745
  var mfAsked = {};
7742
7746
  var mfWaiting = [];
7743
7747
  var mfTimer = null;
7748
+ /** 頼んでいて答え待ちの人 -> その人の丸(待っている間はスケルトン) */
7749
+ var mfInflight = {};
7750
+ var MF_ASK_TIMEOUT_MS = 8000;
7751
+ var MF_IMG_TIMEOUT_MS = 8000;
7752
+ /** 待ちの形から最後の形へ(loading を外す)。state: photo=写真が読めた/initial=頭文字 */
7753
+ function mfSettle(box, state) {
7754
+ if (box.getAttribute("data-state") !== "loading") return;
7755
+ box.classList.remove("mface-loading");
7756
+ box.setAttribute("data-state", state);
7757
+ if (state === "initial") {
7758
+ var img = box.querySelector("img");
7759
+ if (img && img.parentNode) img.parentNode.removeChild(img);
7760
+ box.appendChild(el("span", { class: "mface-initial", text: box.getAttribute("data-initial") || "" }));
7761
+ }
7762
+ }
7763
+ /** 取れた写真を読み込む。読めるまではスケルトンのまま・失敗か時間切れで頭文字(止まり続けない) */
7744
7764
  function mfAttach(box, path) {
7745
- if (!box.isConnected && !box.parentNode) return;
7765
+ if (box.getAttribute("data-state") !== "loading") return;
7746
7766
  var img = document.createElement("img");
7767
+ var timer = setTimeout(function () { mfSettle(box, "initial"); }, MF_IMG_TIMEOUT_MS);
7747
7768
  img.setAttribute("alt", "");
7748
7769
  img.setAttribute("referrerpolicy", "no-referrer");
7749
- img.setAttribute("loading", "lazy");
7750
- img.addEventListener("error", function () { if (img.parentNode) img.parentNode.removeChild(img); });
7770
+ img.addEventListener("load", function () { clearTimeout(timer); mfSettle(box, "photo"); });
7771
+ img.addEventListener("error", function () { clearTimeout(timer); mfSettle(box, "initial"); });
7751
7772
  img.setAttribute("src", PORTAL_ORIGIN + path);
7752
7773
  box.appendChild(img);
7753
7774
  }
7775
+ /** もう答えが分かっている人の丸を決める(写真のパスがあれば読みに行き、無ければ頭文字) */
7776
+ function mfResolve(box, id) {
7777
+ if (mfFaces[id]) mfAttach(box, mfFaces[id]);
7778
+ else mfSettle(box, "initial");
7779
+ }
7754
7780
  function mfFlush() {
7755
7781
  mfTimer = null;
7756
7782
  var waiting = mfWaiting;
7757
7783
  mfWaiting = [];
7758
- if (!waiting.length || !SITE.intake || !actorTicket()) return;
7784
+ if (!waiting.length) return;
7785
+ if (!SITE.intake || !actorTicket()) { waiting.forEach(function (w) { mfSettle(w.box, "initial"); }); return; }
7759
7786
  var ids = [];
7760
7787
  waiting.forEach(function (w) {
7761
- if (Object.prototype.hasOwnProperty.call(mfFaces, w.id)) { if (mfFaces[w.id]) mfAttach(w.box, mfFaces[w.id]); return; }
7762
- if (!Object.prototype.hasOwnProperty.call(mfAsked, w.id) && ids.indexOf(w.id) < 0 && ids.length < 200) ids.push(w.id);
7788
+ if (Object.prototype.hasOwnProperty.call(mfFaces, w.id)) { mfResolve(w.box, w.id); return; }
7789
+ var asked = Object.prototype.hasOwnProperty.call(mfAsked, w.id);
7790
+ if (!asked && ids.indexOf(w.id) < 0) {
7791
+ // 1 回に頼むのは 200 人まで。あふれた人は待たせず頭文字(スケルトンのまま止まらない)
7792
+ if (ids.length >= 200) { mfSettle(w.box, "initial"); return; }
7793
+ ids.push(w.id);
7794
+ }
7795
+ (mfInflight[w.id] = mfInflight[w.id] || []).push(w.box);
7763
7796
  });
7764
7797
  if (!ids.length) return;
7765
7798
  ids.forEach(function (id) { mfAsked[id] = 1; });
7799
+ var finished = false;
7800
+ // 答えが出た(または諦めた)人の待ち行列を空にして、全部の丸を最後の形へ
7801
+ var drain = function () {
7802
+ ids.forEach(function (id) {
7803
+ var boxes = mfInflight[id] || [];
7804
+ delete mfInflight[id];
7805
+ boxes.forEach(function (b) { mfResolve(b, id); });
7806
+ });
7807
+ };
7766
7808
  var give = function (j) {
7767
7809
  (j.people || []).forEach(function (one) {
7768
7810
  if (one && typeof one.userId === "string" && typeof one.avatarUrl === "string" && MF_AVATAR.test(one.avatarUrl)) mfFaces[one.userId] = one.avatarUrl;
7769
7811
  });
7770
7812
  ids.forEach(function (id) { if (!Object.prototype.hasOwnProperty.call(mfFaces, id)) mfFaces[id] = null; });
7771
- waiting.forEach(function (w) { if (mfFaces[w.id]) mfAttach(w.box, mfFaces[w.id]); });
7813
+ if (!finished) { finished = true; clearTimeout(giveUp); drain(); }
7814
+ };
7815
+ // 失敗・断り・時間切れは覚えない(あとで画面を開き直せば取り直す)。待っていた丸は頭文字に落とす
7816
+ var forget = function () {
7817
+ if (finished) return;
7818
+ finished = true;
7819
+ clearTimeout(giveUp);
7820
+ ids.forEach(function (id) { delete mfAsked[id]; });
7821
+ drain();
7772
7822
  };
7773
- var forget = function () { ids.forEach(function (id) { delete mfAsked[id]; }); };
7823
+ var giveUp = setTimeout(forget, MF_ASK_TIMEOUT_MS);
7774
7824
  intakeJson(callIntake("member-faces", { op: "member-faces", userIds: ids })).then(function (res) {
7775
7825
  var j = (res && res.j) || {};
7776
7826
  if (res && res.ok && j.ok === true && Array.isArray(j.people)) give(j);
@@ -7805,15 +7855,18 @@ ${story_1.splitByNameLinks.toString()}
7805
7855
  return plain;
7806
7856
  }
7807
7857
  var name = Array.from(humanTitle(rec.title))[0] || "";
7858
+ // 🔴 画像を取りに行っている間は頭文字を出さず、丸の形のスケルトン(確定した表示がすり替わって見えないように)。
7859
+ // 頭文字は「取れなかった・読めなかった・時間切れ」の最後の形にだけ出す。読み上げには「読み込み中」を足さない
7808
7860
  var a = el("a", {
7809
- class: "mface mface-portal",
7861
+ class: "mface mface-portal mface-loading",
7810
7862
  "data-face": "portal",
7863
+ "data-state": "loading",
7864
+ "data-initial": name,
7811
7865
  href: PORTAL_ORIGIN + "/people/" + encodeURIComponent(id),
7812
7866
  target: "_blank",
7813
7867
  rel: "noopener",
7814
7868
  title: "ポータルでこの人のページを開く",
7815
7869
  });
7816
- a.appendChild(el("span", { class: "mface-initial", text: name }));
7817
7870
  // 行ごと(カード・詳細)の click に伝えない(カードを押した扱いで詳細へ移らない)
7818
7871
  a.addEventListener("click", function (ev) { ev.stopPropagation(); });
7819
7872
  mfWant(id, a);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.173",
3
+ "version": "0.12.174",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,