@takagaki/cortex-decisions-viewer 0.12.107 → 0.12.109

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 +183 -16
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -4913,14 +4913,23 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
4913
4913
  }
4914
4914
  .card:hover { border-color: var(--accent); }
4915
4915
  .card-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
4916
- .card-title { font-size: 16px; font-weight: 650; margin: 2px 0 6px; color: var(--text); }
4916
+ /* card-title はカードの見出し(#1 新しいタブでも開けるよう <a> にした)。見た目は元の <div> と揃える */
4917
+ .card-title { display: block; font-size: 16px; font-weight: 650; margin: 2px 0 6px; color: var(--text); }
4917
4918
  .card-summary { color: #3b424c; font-size: 14px; margin: 0; }
4918
4919
  .empty { color: var(--muted); text-align: center; padding: 48px 0; }
4919
4920
  .pager { display: flex; align-items: center; justify-content: center; gap: 14px; padding: 20px 0 4px; }
4920
- .pager-btn { padding: 6px 14px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); color: inherit; cursor: pointer; font: inherit; }
4921
+ .pager-btn { display: inline-block; padding: 6px 14px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); color: inherit; cursor: pointer; font: inherit; }
4921
4922
  .pager-btn:hover:not(:disabled) { border-color: var(--muted); }
4922
4923
  .pager-btn:disabled { opacity: .4; cursor: default; }
4924
+ /* ボタンの見た目の <a> に下線は要らない(.trace-btn 等、押しボタン型のリンクと同じ扱い) */
4925
+ a.pager-btn:hover { text-decoration: none; }
4926
+ /* 前へ/次へ(#2)の端は <a> ではなく <span>(キーボードでも踏めないように)。このクラスで見た目をそろえる */
4927
+ .pager-btn.is-disabled { opacity: .4; cursor: default; pointer-events: none; }
4923
4928
  .pager-ind { color: var(--muted); font-size: 13px; min-width: 72px; text-align: center; }
4929
+ /* 詳細の「前へ/次へ」は本文の上(戻るリンクのすぐ下)に置くので、一覧の下の余白は要らない。
4930
+ 「前へ」は左端・「次へ」は右端に置く(オーナー指摘)。常に両方の <a>/<span> が居るので、
4931
+ 端で押せない側があっても space-between でもう片方の位置は動かない */
4932
+ .detail-pager { justify-content: space-between; padding: 0 0 16px; }
4924
4933
 
4925
4934
  /* ---- badges / chips ---- */
4926
4935
  .badge {
@@ -8577,11 +8586,37 @@ ${calendarAddUrl.toString()}
8577
8586
  });
8578
8587
  return a;
8579
8588
  }
8589
+ // 一覧 ⇄ 詳細のスクロール位置(#1 一覧→詳細→戻るで毎回スクロールが消える問題)。
8590
+ //
8591
+ // ⚠ ブラウザ既定の自動復元(scrollRestoration: "auto")に任せない。** この画面は
8592
+ // 一覧も詳細も同じ #app を毎回 innerHTML="" で作り直す静的サイトなので、戻ったときの
8593
+ // 高さが自動復元の計算と噛み合わず当てにできない。代わりに「一覧を離れた瞬間のY」を
8594
+ // 自分で1つだけ覚え、一覧へ戻るときに明示的に scrollTo する(分類B 2026-10-01)。
8595
+ //
8596
+ // ⚠ 詳細→詳細(後述の前へ/次へ)では更新しない。** 更新すると、何件か読み進めてから
8597
+ // 一覧へ戻ったときに「最後に見ていた詳細でのスクロール位置」に飛んでしまう。
8598
+ // 一覧に戻る行き先は常に「一覧を離れたときの位置」でなければならない
8599
+ var savedListScrollY = 0;
8600
+ // どのタブの一覧で覚えた位置か(実装レビュー②)。** route() は詳細を描くとき、その記録の
8601
+ // 種類で listState.tab を書き換える(例: 用語集の一覧からウィキリンクで決定の詳細へ移ると
8602
+ // listState.tab は "decisions" になる)。そこで「一覧へ戻る」を押すと、戻る先はその
8603
+ // 書き換わった後のタブになるので、離れたときのタブと戻る先のタブが違うことがある。
8604
+ // 違うタブに離れたときのYを当てはめない(合わなければ先頭へ)
8605
+ var savedListTab = null;
8606
+ // 直前に描いたのが詳細(not foundも含む)か一覧か。popstate(ブラウザの戻る/進む)は
8607
+ // go() を通らないので、この印で「詳細→一覧」の遷移だけを見分けて復元する
8608
+ var lastWasDetail = false;
8609
+ if ("scrollRestoration" in history) history.scrollRestoration = "manual";
8610
+
8580
8611
  function go(id) {
8612
+ // 一覧から詳細へ移るときだけ、今の一覧のスクロール位置とタブを覚える
8613
+ if (id && !lastWasDetail) { savedListScrollY = window.scrollY; savedListTab = listState.tab; }
8581
8614
  var url = id ? "?id=" + encodeURIComponent(id) : tabUrl(listState.tab);
8582
8615
  history.pushState({}, "", url);
8583
8616
  route();
8584
- window.scrollTo(0, 0);
8617
+ // 詳細は常に先頭から。一覧は、離れたときと同じタブに戻るときだけ元の位置へ
8618
+ if (id) window.scrollTo(0, 0);
8619
+ else window.scrollTo(0, listState.tab === savedListTab ? savedListScrollY : 0);
8585
8620
  }
8586
8621
 
8587
8622
  var listState = { tab: DEFAULT_TAB, q: "", category: "", month: "", status: "", page: 1 };
@@ -8609,6 +8644,38 @@ ${calendarAddUrl.toString()}
8609
8644
  return el("div", { class: "pager" }, [prev, ind, next]);
8610
8645
  }
8611
8646
 
8647
+ /** 新しいタブ・新しいウィンドウ等を求めるクリックではない(左クリック・修飾キー無し)か */
8648
+ function isPlainClick(ev) {
8649
+ return !ev.defaultPrevented && ev.button === 0 && !ev.metaKey && !ev.ctrlKey && !ev.shiftKey && !ev.altKey;
8650
+ }
8651
+
8652
+ /**
8653
+ * 一覧カードの見出し(#1)。普通の a href="?id=..." にして、中クリック・
8654
+ * Ctrl/Cmd クリックで新しいタブでも開けるようにする(opsTabLink と同じ型)。
8655
+ * 素のクリックは今までどおり SPA 内遷移(go)のまま——1 件ずつ確かめるときに
8656
+ * ページ全体の再読み込みを挟まない。
8657
+ */
8658
+ function cardTitleLink(rec, extraClass) {
8659
+ var a = el("a", {
8660
+ class: "card-title" + (extraClass || ""),
8661
+ href: "?id=" + encodeURIComponent(rec.id),
8662
+ text: humanTitle(rec.title),
8663
+ });
8664
+ a.addEventListener("click", function (ev) {
8665
+ if (ev.defaultPrevented) return;
8666
+ if (!isPlainClick(ev)) {
8667
+ // 新しいタブ・新しいウィンドウ等はブラウザの既定の動作に任せる。
8668
+ // ここで止めないと、外側のカードの click にも伝わって SPA 内遷移も重ねて走ってしまう
8669
+ ev.stopPropagation();
8670
+ return;
8671
+ }
8672
+ ev.preventDefault();
8673
+ ev.stopPropagation();
8674
+ go(rec.id);
8675
+ });
8676
+ return a;
8677
+ }
8678
+
8612
8679
  function monthLabel(m) {
8613
8680
  var p = m.split("-");
8614
8681
  return p[0] + "年" + parseInt(p[1], 10) + "月";
@@ -8677,6 +8744,7 @@ ${calendarAddUrl.toString()}
8677
8744
 
8678
8745
  // ---------- 一覧 ----------
8679
8746
  function renderList() {
8747
+ lastWasDetail = false;
8680
8748
  app.innerHTML = "";
8681
8749
  renderTabs();
8682
8750
  renderInfoBar();
@@ -19177,11 +19245,16 @@ ${calendarAddUrl.toString()}
19177
19245
  return sel;
19178
19246
  }
19179
19247
 
19180
- function drawCards() {
19248
+ /**
19249
+ * 今の絞り込み(検索語・期間・カテゴリー・状態)を当てた決定の一覧(並びは
19250
+ * SITE.decisions の順=新しい順のまま)。drawCards と、詳細の「前へ/次へ」
19251
+ * (#2)の両方がここを通る——一覧の並びと前後移動の並びがずれないように、口を1つにする。
19252
+ */
19253
+ function filteredDecisions() {
19181
19254
  var q = listState.q.trim().toLowerCase();
19182
19255
  // 削除済みは絞り込みの手前で落とす(分母も一緒に減るので「12/13件」が残らない)
19183
19256
  var all = visibleRecords(SITE.decisions);
19184
- var items = all.filter(function (d) {
19257
+ return all.filter(function (d) {
19185
19258
  if (listState.month && d.date.slice(0, 7) !== listState.month) return false;
19186
19259
  if (listState.category && d.category !== listState.category) return false;
19187
19260
  if (!matchesStatusFilter(d, !!SITE.decisionFollowup)) return false;
@@ -19191,6 +19264,11 @@ ${calendarAddUrl.toString()}
19191
19264
  }
19192
19265
  return true;
19193
19266
  });
19267
+ }
19268
+
19269
+ function drawCards() {
19270
+ var all = visibleRecords(SITE.decisions);
19271
+ var items = filteredDecisions();
19194
19272
 
19195
19273
  var wrap = document.getElementById("card-list");
19196
19274
  wrap.innerHTML = "";
@@ -19224,7 +19302,7 @@ ${calendarAddUrl.toString()}
19224
19302
  var card = el("div", { class: "card", title: d.id, "data-record-id": d.id }, [
19225
19303
  meta,
19226
19304
  // 消したレコードは一覧でも取り消し線(次のビルドまで一覧には残る)
19227
- el("div", { class: "card-title" + (d.deleted ? " is-deleted" : ""), text: humanTitle(d.title) }),
19305
+ cardTitleLink(d, d.deleted ? " is-deleted" : ""),
19228
19306
  d.summary ? el("p", { class: "card-summary", text: d.summary }) : null,
19229
19307
  ]);
19230
19308
  card.addEventListener("click", function () { go(d.id); });
@@ -19237,8 +19315,7 @@ ${calendarAddUrl.toString()}
19237
19315
 
19238
19316
  // ---------- 汎用一覧(records/ を持つディレクトリ) ----------
19239
19317
  function renderGenericList() {
19240
- var tab = null;
19241
- TABS.forEach(function (t) { if (t.key === listState.tab) tab = t; });
19318
+ var tab = findTab(listState.tab);
19242
19319
  // 削除済みは描画の手前で落とす(件数の分母・絞り込みの選択肢もこれ1本で揃う)
19243
19320
  var items = visibleRecords(tab && tab.items ? tab.items : []);
19244
19321
 
@@ -19259,12 +19336,7 @@ ${calendarAddUrl.toString()}
19259
19336
  app.appendChild(listWrap);
19260
19337
 
19261
19338
  function draw() {
19262
- var q = listState.q.trim().toLowerCase();
19263
- var shown = items.filter(function (r) {
19264
- if (!matchesStatusFilter(r)) return false;
19265
- if (!q) return true;
19266
- return (r.title + " " + r.summary + " " + r.searchText).toLowerCase().indexOf(q) !== -1;
19267
- });
19339
+ var shown = filteredGenericItems(tab);
19268
19340
  listWrap.innerHTML = "";
19269
19341
  if (!shown.length) {
19270
19342
  countEl.textContent = "0 / " + items.length + " 件";
@@ -19293,7 +19365,7 @@ ${calendarAddUrl.toString()}
19293
19365
  meta.appendChild(traceR);
19294
19366
  var card = el("div", { class: "card", title: r.id, "data-record-id": r.id }, [
19295
19367
  meta,
19296
- el("div", { class: "card-title" + (r.deleted ? " is-deleted" : ""), text: humanTitle(r.title) }),
19368
+ cardTitleLink(r, r.deleted ? " is-deleted" : ""),
19297
19369
  r.summary ? el("p", { class: "card-summary", text: r.summary }) : null,
19298
19370
  ]);
19299
19371
  card.addEventListener("click", function () { go(r.id); });
@@ -20063,6 +20135,70 @@ ${calendarAddUrl.toString()}
20063
20135
  function visibleRecords(items) {
20064
20136
  return (items || []).filter(function (r) { return !r.deleted; });
20065
20137
  }
20138
+
20139
+ /** タブkeyからTABSの該当行を探す(見つからなければ null) */
20140
+ function findTab(key) {
20141
+ var found = null;
20142
+ TABS.forEach(function (t) { if (t.key === key) found = t; });
20143
+ return found;
20144
+ }
20145
+
20146
+ /**
20147
+ * 今の絞り込み(検索語・状態)を当てた汎用一覧(用語集・関係者・ルール等)。
20148
+ * renderGenericList の draw と、詳細の「前へ/次へ」(#2)の両方がここを通る
20149
+ */
20150
+ function filteredGenericItems(tab) {
20151
+ var q = listState.q.trim().toLowerCase();
20152
+ var items = visibleRecords(tab && tab.items ? tab.items : []);
20153
+ return items.filter(function (r) {
20154
+ if (!matchesStatusFilter(r)) return false;
20155
+ if (!q) return true;
20156
+ return (r.title + " " + r.summary + " " + r.searchText).toLowerCase().indexOf(q) !== -1;
20157
+ });
20158
+ }
20159
+
20160
+ /**
20161
+ * 絞り込んだ並び(filtered)に今の記録が無ければ、絞り込み前の既定の並び(fallback。
20162
+ * 削除済みを除いた新しい順)を使う(#2 一覧を経ずに開いたとき・絞り込みに
20163
+ * 合わない記録を開いたときは既定の順、の受け皿)。
20164
+ */
20165
+ function pagerOrder(filtered, fallback, id) {
20166
+ for (var i = 0; i < filtered.length; i++) { if (filtered[i].id === id) return filtered; }
20167
+ return fallback;
20168
+ }
20169
+
20170
+ /**
20171
+ * 詳細画面の「前へ/次へ」(#2)。ordered の中で id の前後の記録へ移る。
20172
+ * 端(先頭・末尾)では押せない。ordered に id が無ければ何も出さない
20173
+ * (削除済みを直リンクで開いた等、並びそのものが無いとき)
20174
+ */
20175
+ function buildDetailPager(ordered, id) {
20176
+ var idx = -1;
20177
+ for (var i = 0; i < ordered.length; i++) { if (ordered[i].id === id) { idx = i; break; } }
20178
+ if (idx === -1) return null;
20179
+ var prevRec = idx > 0 ? ordered[idx - 1] : null;
20180
+ var nextRec = idx < ordered.length - 1 ? ordered[idx + 1] : null;
20181
+ function navBtn(rec, cls, text) {
20182
+ // 見た目は新設せず、同じ詳細画面の「つながり」ボタン(.trace-btn)のリキッドグラス
20183
+ // (角丸・やわらかい影・半透明の地)をそのまま使い回す(オーナー指摘)
20184
+ // 端では押せない。<a href="#"> のままだとポインタは止められても、Tab→Enter では
20185
+ // 踏めてしまう(#へ飛んで popstate が走り、詳細を描き直してしまう)ので、
20186
+ // 端では href を持たない <span> にする(実装レビュー④)
20187
+ if (!rec) return el("span", { class: "pager-btn trace-btn is-disabled " + cls, "aria-disabled": "true", text: text });
20188
+ var a = el("a", { class: "pager-btn trace-btn " + cls, href: "?id=" + encodeURIComponent(rec.id), text: text });
20189
+ a.addEventListener("click", function (ev) {
20190
+ if (!isPlainClick(ev)) { ev.stopPropagation(); return; }
20191
+ ev.preventDefault();
20192
+ ev.stopPropagation();
20193
+ go(rec.id);
20194
+ });
20195
+ return a;
20196
+ }
20197
+ return el("div", { class: "pager detail-pager" }, [
20198
+ navBtn(prevRec, "detail-pager-prev", "← 前へ"),
20199
+ navBtn(nextRec, "detail-pager-next", "次へ →"),
20200
+ ]);
20201
+ }
20066
20202
  /**
20067
20203
  * 削除済みを除いたグラフ。**消費側で1行ずつ摘まむのではなく、ここ1箇所で作る**
20068
20204
  * ——グラフ・エクスプローラー・件数表示が別々に間引くと、片方だけ消え残る。
@@ -20087,6 +20223,7 @@ ${calendarAddUrl.toString()}
20087
20223
 
20088
20224
  // ---------- 汎用詳細 ----------
20089
20225
  function renderGenericDetail(r) {
20226
+ lastWasDetail = true;
20090
20227
  app.innerHTML = "";
20091
20228
  countEl.textContent = "";
20092
20229
 
@@ -20094,6 +20231,12 @@ ${calendarAddUrl.toString()}
20094
20231
  back.addEventListener("click", function (ev) { ev.preventDefault(); go(null); });
20095
20232
  app.appendChild(back);
20096
20233
 
20234
+ // 前へ/次へ(#2)。一覧の今の絞り込み・並びの順。一覧を経ずに開いたときは既定の順
20235
+ var pagerTabG = findTab("dir:" + r._section);
20236
+ var orderedG = pagerOrder(filteredGenericItems(pagerTabG), visibleRecords(pagerTabG ? pagerTabG.items : []), r.id);
20237
+ var detailPagerG = buildDetailPager(orderedG, r.id);
20238
+ if (detailPagerG) app.appendChild(detailPagerG);
20239
+
20097
20240
  var head = el("div", { class: "detail-head" });
20098
20241
  head.appendChild(el("div", {}, [ el("h1", { class: "detail-title", text: humanTitle(r.title) }) ]));
20099
20242
  var traceBtn2 = el("a", { class: "trace-btn", href: "?tab=graph", text: "🕸 つながり" });
@@ -20779,6 +20922,7 @@ ${calendarAddUrl.toString()}
20779
20922
 
20780
20923
  // ---------- Decision詳細 ----------
20781
20924
  function renderDetail(d) {
20925
+ lastWasDetail = true;
20782
20926
  app.innerHTML = "";
20783
20927
  countEl.textContent = "";
20784
20928
 
@@ -20786,6 +20930,11 @@ ${calendarAddUrl.toString()}
20786
20930
  back.addEventListener("click", function (ev) { ev.preventDefault(); go(null); });
20787
20931
  app.appendChild(back);
20788
20932
 
20933
+ // 前へ/次へ(#2)。一覧の今の絞り込み・並びの順。一覧を経ずに開いたときは既定の順(新しい順)
20934
+ var orderedD = pagerOrder(filteredDecisions(), visibleRecords(SITE.decisions), d.id);
20935
+ var detailPagerD = buildDetailPager(orderedD, d.id);
20936
+ if (detailPagerD) app.appendChild(detailPagerD);
20937
+
20789
20938
  var head = el("div", { class: "detail-head" });
20790
20939
  var headLeft = el("div", {}, [ el("h1", { class: "detail-title", text: humanTitle(d.title) }) ]);
20791
20940
  head.appendChild(headLeft);
@@ -20864,6 +21013,7 @@ ${calendarAddUrl.toString()}
20864
21013
  }
20865
21014
 
20866
21015
  function renderNotFound(id) {
21016
+ lastWasDetail = true;
20867
21017
  app.innerHTML = "";
20868
21018
  var back = el("a", { class: "detail-back", href: "?", text: "← 一覧へ戻る" });
20869
21019
  back.addEventListener("click", function (ev) { ev.preventDefault(); go(null); });
@@ -21348,7 +21498,24 @@ ${calendarAddUrl.toString()}
21348
21498
  }
21349
21499
  })();
21350
21500
 
21351
- window.addEventListener("popstate", route);
21501
+ // ブラウザの「戻る」「進む」。go() を通らないので、ここで一覧⇄詳細のスクロールだけ別に見る
21502
+ // (#1 ブラウザの戻るでも一覧のスクロール位置を保つ)
21503
+ window.addEventListener("popstate", function () {
21504
+ var goingToId = getParam("id");
21505
+ var cameFromDetail = lastWasDetail;
21506
+ // 「進む」で一覧→詳細に戻ることもある(戻る→スクロール→進む、で位置が古くなる実装レビュー③)。
21507
+ // go() と同じ条件で、そのときの一覧の位置とタブを覚え直す
21508
+ if (goingToId && !cameFromDetail) { savedListScrollY = window.scrollY; savedListTab = listState.tab; }
21509
+ route();
21510
+ if (!goingToId && cameFromDetail) {
21511
+ // 一覧へ戻る。離れたときと同じタブのときだけ元の位置へ(違えば先頭)
21512
+ window.scrollTo(0, listState.tab === savedListTab ? savedListScrollY : 0);
21513
+ } else if (goingToId) {
21514
+ // 詳細はブラウザの戻る/進むでも常に先頭から
21515
+ // (scrollRestoration を manual にしたので、ここで明示しないとブラウザがどこへも戻さない)
21516
+ window.scrollTo(0, 0);
21517
+ }
21518
+ });
21352
21519
  /**
21353
21520
  * ヘッダーの案件名(#379)。**ポータル配下(/p/<key>/ の下)で開いているときだけ**、
21354
21521
  * ポータルの案件一覧(同一オリジンの /)へ戻るリンクにする。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.107",
3
+ "version": "0.12.109",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,