@takagaki/cortex-decisions-viewer 0.12.107 → 0.12.108

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