@yuneta/gobj-ui 7.23.138 → 7.23.139

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.
@@ -21798,7 +21798,7 @@ var attrs_table$6 = [
21798
21798
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
21799
21799
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_cards_landing", 0, false, "Land on a grid of topic cards (list->detail): a card opens its table, with the tabs bar + a back-to-grid button. Off = tabs only (legacy)."),
21800
21800
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: every topic opens without its write affordances and the write events are refused. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
21801
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "card_action_routes", 0, null, "Per-card hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show 3 icon actions; absent ⇒ a single card that opens the table."),
21801
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "card_action_routes", 0, null, "Hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show the info and table icons, and `graph` without its `/{topic}` is the toolbar's graph button (the card has no graph icon: its topic segment is a focus); absent ⇒ a single card that opens the table."),
21802
21802
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "landing_routes", 0, null, "Host-supplied hashes for the two landing sub-views {cards, schema}; the toggle navigates to them so the landing is URL-addressable (ROUTING.md). Absent ⇒ toggle flips in-view only (legacy)."),
21803
21803
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its sub-routes (topics / info / schema) to the site map (ROUTING.md contributor)."),
21804
21804
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "source_url", 0, "", "The backend this view browses (host-supplied, typically the wss url of the connection), shown in the toolbar. A tab label names the TREEDB, and the same treedb name lives on more than one node, so the node has to be readable somewhere -- and a tab is too narrow to carry a url. Empty ⇒ nothing is shown."),
@@ -22221,13 +22221,103 @@ function card_action_anchor(logical_class, icon, i18n_key, href) {
22221
22221
  ];
22222
22222
  }
22223
22223
  /************************************************************
22224
+ * What a topic card says about its topic, from its desc alone
22225
+ * (no request): its version and how many columns it has, the
22226
+ * topics it hangs FROM (its fkeys) and the ones that hang from
22227
+ * it (its hooks). The names are DATA and are not translated;
22228
+ * each label carries its key. Lists are cut with an ellipsis,
22229
+ * whole in the title.
22230
+ ************************************************************/
22231
+ function topic_card_meta(desc) {
22232
+ if (!(0, _yuneta_gobj_js.is_object)(desc)) return [];
22233
+ let cols = (0, _yuneta_gobj_js.is_array)(desc.cols) ? desc.cols : [];
22234
+ let n_cols = 0;
22235
+ let parents = [];
22236
+ let children = [];
22237
+ for (let col of cols) {
22238
+ if (!col || !col.id || col.id.charAt(0) === "_") continue;
22239
+ n_cols++;
22240
+ if ((0, _yuneta_gobj_js.is_object)(col.fkey)) {
22241
+ for (let t_name of Object.keys(col.fkey)) if (!parents.includes(t_name)) parents.push(t_name);
22242
+ }
22243
+ if ((0, _yuneta_gobj_js.is_object)(col.hook)) {
22244
+ for (let t_name of Object.keys(col.hook)) if (!children.includes(t_name)) children.push(t_name);
22245
+ }
22246
+ }
22247
+ let meta = [];
22248
+ let head = [];
22249
+ if (desc.topic_version !== void 0 && desc.topic_version !== null && desc.topic_version !== "") head.push([
22250
+ "span",
22251
+ {
22252
+ class: "TREEDB_TOPIC_CARD_VERSION",
22253
+ title: (0, i18next.t)("version"),
22254
+ "data-i18n-title": "version"
22255
+ },
22256
+ `v${desc.topic_version}`
22257
+ ]);
22258
+ head.push([
22259
+ "span",
22260
+ { class: "TREEDB_TOPIC_CARD_COLS" },
22261
+ [[
22262
+ "span",
22263
+ {},
22264
+ `${n_cols}`
22265
+ ], [
22266
+ "span",
22267
+ { i18n: "columns" },
22268
+ "columns"
22269
+ ]]
22270
+ ]);
22271
+ meta.push([
22272
+ "div",
22273
+ { class: "TREEDB_TOPIC_CARD_META" },
22274
+ head
22275
+ ]);
22276
+ let relation = (cls, key, arrow, names) => {
22277
+ let list = names.join(", ");
22278
+ return [
22279
+ "div",
22280
+ {
22281
+ class: `TREEDB_TOPIC_CARD_REL ${cls}`,
22282
+ title: list
22283
+ },
22284
+ [
22285
+ [
22286
+ "span",
22287
+ {
22288
+ class: "TREEDB_TOPIC_CARD_REL_ARROW",
22289
+ "aria-hidden": "true"
22290
+ },
22291
+ arrow
22292
+ ],
22293
+ [
22294
+ "span",
22295
+ {
22296
+ class: "TREEDB_TOPIC_CARD_REL_LABEL",
22297
+ i18n: key
22298
+ },
22299
+ key
22300
+ ],
22301
+ [
22302
+ "span",
22303
+ { class: "TREEDB_TOPIC_CARD_REL_LIST" },
22304
+ list
22305
+ ]
22306
+ ]
22307
+ ];
22308
+ };
22309
+ if (parents.length) meta.push(relation("TREEDB_TOPIC_CARD_PARENTS", "parents", "↑", parents));
22310
+ if (children.length) meta.push(relation("TREEDB_TOPIC_CARD_CHILDREN", "children", "↓", children));
22311
+ return meta;
22312
+ }
22313
+ /************************************************************
22224
22314
  * Add one topic card to the cards-landing grid (list->detail).
22225
22315
  * With `card_action_routes` (host-supplied templates) the card
22226
22316
  * shows three hash-routed icons — info / table / graph. Without,
22227
22317
  * it falls back to a single card that opens the table (same id
22228
22318
  * contract as the tab, `<gobj>?<topic>`).
22229
22319
  ************************************************************/
22230
- function add_topic_card(gobj, id, text, icon) {
22320
+ function add_topic_card(gobj, id, text, icon, desc) {
22231
22321
  let $grid = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container").querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
22232
22322
  if (!$grid) return;
22233
22323
  let routes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "card_action_routes");
@@ -22270,25 +22360,28 @@ function add_topic_card(gobj, id, text, icon) {
22270
22360
  let actions = [];
22271
22361
  if (routes.info) actions.push(card_action_anchor("TREEDB_CARD_INFO", "yi-circle-info", "info", fill(routes.info)));
22272
22362
  if (routes.table) actions.push(card_action_anchor("TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
22273
- if (routes.graph) actions.push(card_action_anchor("TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
22274
22363
  let $card = (0, _yuneta_gobj_js.createElement2)([
22275
22364
  "div",
22276
22365
  {
22277
22366
  class: "TREEDB_TOPIC_CARD",
22278
22367
  "data-topic": topic
22279
22368
  },
22280
- [[
22281
- "span",
22282
- {
22283
- class: "TREEDB_TOPIC_CARD_NAME",
22284
- i18n: text
22285
- },
22286
- text
22287
- ], [
22288
- "div",
22289
- { class: "TREEDB_TOPIC_CARD_ACTIONS" },
22290
- actions
22291
- ]]
22369
+ [
22370
+ [
22371
+ "span",
22372
+ {
22373
+ class: "TREEDB_TOPIC_CARD_NAME",
22374
+ i18n: text
22375
+ },
22376
+ text
22377
+ ],
22378
+ ...topic_card_meta(desc),
22379
+ [
22380
+ "div",
22381
+ { class: "TREEDB_TOPIC_CARD_ACTIONS" },
22382
+ actions
22383
+ ]
22384
+ ]
22292
22385
  ]);
22293
22386
  if (gobj.priv._selected_card_topic === topic) $card.classList.add("is-selected");
22294
22387
  $card.addEventListener("click", function() {
@@ -22641,7 +22734,7 @@ function process_treedb_descs$1(gobj) {
22641
22734
  let id = `${(0, _yuneta_gobj_js.gobj_name)(gobj)}?${desc.topic_name}`;
22642
22735
  let gobj_topic_form = (0, _yuneta_gobj_js.gobj_create_service)(id, "C_YUI_TREEDB_TOPIC_WITH_FORM", kw_topic_form, gobj);
22643
22736
  add_tab(gobj, gobj_topic_form, id, key, "yi-table");
22644
- add_topic_card(gobj, id, key, "yi-table");
22737
+ add_topic_card(gobj, id, key, "yi-table", desc);
22645
22738
  (0, _yuneta_gobj_js.gobj_start)(gobj_topic_form);
22646
22739
  }
22647
22740
  let href = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "last_selection");
@@ -21793,7 +21793,7 @@ var attrs_table$6 = [
21793
21793
  SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
21794
21794
  SDATA(data_type_t.DTP_BOOLEAN, "with_cards_landing", 0, false, "Land on a grid of topic cards (list->detail): a card opens its table, with the tabs bar + a back-to-grid button. Off = tabs only (legacy)."),
21795
21795
  SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: every topic opens without its write affordances and the write events are refused. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
21796
- SDATA(data_type_t.DTP_JSON, "card_action_routes", 0, null, "Per-card hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show 3 icon actions; absent ⇒ a single card that opens the table."),
21796
+ SDATA(data_type_t.DTP_JSON, "card_action_routes", 0, null, "Hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show the info and table icons, and `graph` without its `/{topic}` is the toolbar's graph button (the card has no graph icon: its topic segment is a focus); absent ⇒ a single card that opens the table."),
21797
21797
  SDATA(data_type_t.DTP_JSON, "landing_routes", 0, null, "Host-supplied hashes for the two landing sub-views {cards, schema}; the toggle navigates to them so the landing is URL-addressable (ROUTING.md). Absent ⇒ toggle flips in-view only (legacy)."),
21798
21798
  SDATA(data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its sub-routes (topics / info / schema) to the site map (ROUTING.md contributor)."),
21799
21799
  SDATA(data_type_t.DTP_STRING, "source_url", 0, "", "The backend this view browses (host-supplied, typically the wss url of the connection), shown in the toolbar. A tab label names the TREEDB, and the same treedb name lives on more than one node, so the node has to be readable somewhere -- and a tab is too narrow to carry a url. Empty ⇒ nothing is shown."),
@@ -22216,13 +22216,103 @@ function card_action_anchor(logical_class, icon, i18n_key, href) {
22216
22216
  ];
22217
22217
  }
22218
22218
  /************************************************************
22219
+ * What a topic card says about its topic, from its desc alone
22220
+ * (no request): its version and how many columns it has, the
22221
+ * topics it hangs FROM (its fkeys) and the ones that hang from
22222
+ * it (its hooks). The names are DATA and are not translated;
22223
+ * each label carries its key. Lists are cut with an ellipsis,
22224
+ * whole in the title.
22225
+ ************************************************************/
22226
+ function topic_card_meta(desc) {
22227
+ if (!is_object(desc)) return [];
22228
+ let cols = is_array(desc.cols) ? desc.cols : [];
22229
+ let n_cols = 0;
22230
+ let parents = [];
22231
+ let children = [];
22232
+ for (let col of cols) {
22233
+ if (!col || !col.id || col.id.charAt(0) === "_") continue;
22234
+ n_cols++;
22235
+ if (is_object(col.fkey)) {
22236
+ for (let t_name of Object.keys(col.fkey)) if (!parents.includes(t_name)) parents.push(t_name);
22237
+ }
22238
+ if (is_object(col.hook)) {
22239
+ for (let t_name of Object.keys(col.hook)) if (!children.includes(t_name)) children.push(t_name);
22240
+ }
22241
+ }
22242
+ let meta = [];
22243
+ let head = [];
22244
+ if (desc.topic_version !== void 0 && desc.topic_version !== null && desc.topic_version !== "") head.push([
22245
+ "span",
22246
+ {
22247
+ class: "TREEDB_TOPIC_CARD_VERSION",
22248
+ title: t("version"),
22249
+ "data-i18n-title": "version"
22250
+ },
22251
+ `v${desc.topic_version}`
22252
+ ]);
22253
+ head.push([
22254
+ "span",
22255
+ { class: "TREEDB_TOPIC_CARD_COLS" },
22256
+ [[
22257
+ "span",
22258
+ {},
22259
+ `${n_cols}`
22260
+ ], [
22261
+ "span",
22262
+ { i18n: "columns" },
22263
+ "columns"
22264
+ ]]
22265
+ ]);
22266
+ meta.push([
22267
+ "div",
22268
+ { class: "TREEDB_TOPIC_CARD_META" },
22269
+ head
22270
+ ]);
22271
+ let relation = (cls, key, arrow, names) => {
22272
+ let list = names.join(", ");
22273
+ return [
22274
+ "div",
22275
+ {
22276
+ class: `TREEDB_TOPIC_CARD_REL ${cls}`,
22277
+ title: list
22278
+ },
22279
+ [
22280
+ [
22281
+ "span",
22282
+ {
22283
+ class: "TREEDB_TOPIC_CARD_REL_ARROW",
22284
+ "aria-hidden": "true"
22285
+ },
22286
+ arrow
22287
+ ],
22288
+ [
22289
+ "span",
22290
+ {
22291
+ class: "TREEDB_TOPIC_CARD_REL_LABEL",
22292
+ i18n: key
22293
+ },
22294
+ key
22295
+ ],
22296
+ [
22297
+ "span",
22298
+ { class: "TREEDB_TOPIC_CARD_REL_LIST" },
22299
+ list
22300
+ ]
22301
+ ]
22302
+ ];
22303
+ };
22304
+ if (parents.length) meta.push(relation("TREEDB_TOPIC_CARD_PARENTS", "parents", "↑", parents));
22305
+ if (children.length) meta.push(relation("TREEDB_TOPIC_CARD_CHILDREN", "children", "↓", children));
22306
+ return meta;
22307
+ }
22308
+ /************************************************************
22219
22309
  * Add one topic card to the cards-landing grid (list->detail).
22220
22310
  * With `card_action_routes` (host-supplied templates) the card
22221
22311
  * shows three hash-routed icons — info / table / graph. Without,
22222
22312
  * it falls back to a single card that opens the table (same id
22223
22313
  * contract as the tab, `<gobj>?<topic>`).
22224
22314
  ************************************************************/
22225
- function add_topic_card(gobj, id, text, icon) {
22315
+ function add_topic_card(gobj, id, text, icon, desc) {
22226
22316
  let $grid = gobj_read_attr(gobj, "$container").querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
22227
22317
  if (!$grid) return;
22228
22318
  let routes = gobj_read_attr(gobj, "card_action_routes");
@@ -22265,25 +22355,28 @@ function add_topic_card(gobj, id, text, icon) {
22265
22355
  let actions = [];
22266
22356
  if (routes.info) actions.push(card_action_anchor("TREEDB_CARD_INFO", "yi-circle-info", "info", fill(routes.info)));
22267
22357
  if (routes.table) actions.push(card_action_anchor("TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
22268
- if (routes.graph) actions.push(card_action_anchor("TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
22269
22358
  let $card = createElement2([
22270
22359
  "div",
22271
22360
  {
22272
22361
  class: "TREEDB_TOPIC_CARD",
22273
22362
  "data-topic": topic
22274
22363
  },
22275
- [[
22276
- "span",
22277
- {
22278
- class: "TREEDB_TOPIC_CARD_NAME",
22279
- i18n: text
22280
- },
22281
- text
22282
- ], [
22283
- "div",
22284
- { class: "TREEDB_TOPIC_CARD_ACTIONS" },
22285
- actions
22286
- ]]
22364
+ [
22365
+ [
22366
+ "span",
22367
+ {
22368
+ class: "TREEDB_TOPIC_CARD_NAME",
22369
+ i18n: text
22370
+ },
22371
+ text
22372
+ ],
22373
+ ...topic_card_meta(desc),
22374
+ [
22375
+ "div",
22376
+ { class: "TREEDB_TOPIC_CARD_ACTIONS" },
22377
+ actions
22378
+ ]
22379
+ ]
22287
22380
  ]);
22288
22381
  if (gobj.priv._selected_card_topic === topic) $card.classList.add("is-selected");
22289
22382
  $card.addEventListener("click", function() {
@@ -22636,7 +22729,7 @@ function process_treedb_descs$1(gobj) {
22636
22729
  let id = `${gobj_name(gobj)}?${desc.topic_name}`;
22637
22730
  let gobj_topic_form = gobj_create_service(id, "C_YUI_TREEDB_TOPIC_WITH_FORM", kw_topic_form, gobj);
22638
22731
  add_tab(gobj, gobj_topic_form, id, key, "yi-table");
22639
- add_topic_card(gobj, id, key, "yi-table");
22732
+ add_topic_card(gobj, id, key, "yi-table", desc);
22640
22733
  gobj_start(gobj_topic_form);
22641
22734
  }
22642
22735
  let href = gobj_read_str_attr(gobj, "last_selection");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.138",
3
+ "version": "7.23.139",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -36,6 +36,31 @@
36
36
  line-height: 1.2;
37
37
  word-break: break-word;
38
38
  }
39
+ /* What the card says about its topic: version + columns, then the
40
+ * topics it hangs from and the ones hanging from it. Muted and small,
41
+ * under the name; a long list is cut, whole in the title. The spans
42
+ * are spaced by `gap` because createElement2 trims text nodes. */
43
+ .TREEDB_TOPIC_CARD_META,
44
+ .TREEDB_TOPIC_CARD_REL {
45
+ display: flex;
46
+ align-items: baseline;
47
+ justify-content: center;
48
+ gap: 0.3rem;
49
+ max-width: 100%;
50
+ font-size: 0.75rem;
51
+ color: var(--bulma-text-weak, #7a7a7a);
52
+ white-space: nowrap;
53
+ }
54
+ .TREEDB_TOPIC_CARD_COLS {
55
+ display: inline-flex;
56
+ gap: 0.25rem;
57
+ }
58
+ .TREEDB_TOPIC_CARD_REL_LIST {
59
+ min-width: 0;
60
+ overflow: hidden;
61
+ text-overflow: ellipsis;
62
+ color: var(--bulma-text, inherit);
63
+ }
39
64
  .TREEDB_TOPIC_CARD_ACTIONS {
40
65
  display: flex;
41
66
  justify-content: center;
@@ -84,7 +84,7 @@ SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system t
84
84
  SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
85
85
  SDATA(data_type_t.DTP_BOOLEAN, "with_cards_landing",0, false, "Land on a grid of topic cards (list->detail): a card opens its table, with the tabs bar + a back-to-grid button. Off = tabs only (legacy)."),
86
86
  SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: every topic opens without its write affordances and the write events are refused. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
87
- SDATA(data_type_t.DTP_JSON, "card_action_routes",0, null, "Per-card hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show 3 icon actions; absent ⇒ a single card that opens the table."),
87
+ SDATA(data_type_t.DTP_JSON, "card_action_routes",0, null, "Hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show the info and table icons, and `graph` without its `/{topic}` is the toolbar's graph button (the card has no graph icon: its topic segment is a focus); absent ⇒ a single card that opens the table."),
88
88
  SDATA(data_type_t.DTP_JSON, "landing_routes", 0, null, "Host-supplied hashes for the two landing sub-views {cards, schema}; the toggle navigates to them so the landing is URL-addressable (ROUTING.md). Absent ⇒ toggle flips in-view only (legacy)."),
89
89
  SDATA(data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its sub-routes (topics / info / schema) to the site map (ROUTING.md contributor)."),
90
90
  SDATA(data_type_t.DTP_STRING, "source_url", 0, "", "The backend this view browses (host-supplied, typically the wss url of the connection), shown in the toolbar. A tab label names the TREEDB, and the same treedb name lives on more than one node, so the node has to be readable somewhere -- and a tab is too narrow to carry a url. Empty ⇒ nothing is shown."),
@@ -599,6 +599,74 @@ function card_action_anchor(logical_class, icon, i18n_key, href)
599
599
  ]];
600
600
  }
601
601
 
602
+ /************************************************************
603
+ * What a topic card says about its topic, from its desc alone
604
+ * (no request): its version and how many columns it has, the
605
+ * topics it hangs FROM (its fkeys) and the ones that hang from
606
+ * it (its hooks). The names are DATA and are not translated;
607
+ * each label carries its key. Lists are cut with an ellipsis,
608
+ * whole in the title.
609
+ ************************************************************/
610
+ function topic_card_meta(desc)
611
+ {
612
+ if(!is_object(desc)) {
613
+ return [];
614
+ }
615
+ let cols = is_array(desc.cols)? desc.cols : [];
616
+ let n_cols = 0;
617
+ let parents = [];
618
+ let children = [];
619
+ for(let col of cols) {
620
+ if(!col || !col.id || col.id.charAt(0) === "_") {
621
+ continue;
622
+ }
623
+ n_cols++;
624
+ if(is_object(col.fkey)) {
625
+ for(let t_name of Object.keys(col.fkey)) {
626
+ if(!parents.includes(t_name)) {
627
+ parents.push(t_name);
628
+ }
629
+ }
630
+ }
631
+ if(is_object(col.hook)) {
632
+ for(let t_name of Object.keys(col.hook)) {
633
+ if(!children.includes(t_name)) {
634
+ children.push(t_name);
635
+ }
636
+ }
637
+ }
638
+ }
639
+
640
+ let meta = [];
641
+ let head = [];
642
+ if(desc.topic_version !== undefined && desc.topic_version !== null && desc.topic_version !== "") {
643
+ head.push(['span', {class: 'TREEDB_TOPIC_CARD_VERSION',
644
+ title: t('version'), 'data-i18n-title': 'version'},
645
+ `v${desc.topic_version}`]);
646
+ }
647
+ head.push(['span', {class: 'TREEDB_TOPIC_CARD_COLS'}, [
648
+ ['span', {}, `${n_cols}`],
649
+ ['span', {i18n: 'columns'}, 'columns']
650
+ ]]);
651
+ meta.push(['div', {class: 'TREEDB_TOPIC_CARD_META'}, head]);
652
+
653
+ let relation = (cls, key, arrow, names) => {
654
+ let list = names.join(", ");
655
+ return ['div', {class: `TREEDB_TOPIC_CARD_REL ${cls}`, title: list}, [
656
+ ['span', {class: 'TREEDB_TOPIC_CARD_REL_ARROW', 'aria-hidden': 'true'}, arrow],
657
+ ['span', {class: 'TREEDB_TOPIC_CARD_REL_LABEL', i18n: key}, key],
658
+ ['span', {class: 'TREEDB_TOPIC_CARD_REL_LIST'}, list]
659
+ ]];
660
+ };
661
+ if(parents.length) {
662
+ meta.push(relation('TREEDB_TOPIC_CARD_PARENTS', 'parents', '↑', parents));
663
+ }
664
+ if(children.length) {
665
+ meta.push(relation('TREEDB_TOPIC_CARD_CHILDREN', 'children', '↓', children));
666
+ }
667
+ return meta;
668
+ }
669
+
602
670
  /************************************************************
603
671
  * Add one topic card to the cards-landing grid (list->detail).
604
672
  * With `card_action_routes` (host-supplied templates) the card
@@ -606,7 +674,7 @@ function card_action_anchor(logical_class, icon, i18n_key, href)
606
674
  * it falls back to a single card that opens the table (same id
607
675
  * contract as the tab, `<gobj>?<topic>`).
608
676
  ************************************************************/
609
- function add_topic_card(gobj, id, text, icon)
677
+ function add_topic_card(gobj, id, text, icon, desc)
610
678
  {
611
679
  let $container = gobj_read_attr(gobj, "$container");
612
680
  let $grid = $container.querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
@@ -635,9 +703,16 @@ function add_topic_card(gobj, id, text, icon)
635
703
  return;
636
704
  }
637
705
 
638
- /* Three-icon card: info / table / graph, all real hash anchors.
639
- * {topic} in each template is the raw topic name (identifier-safe,
640
- * matching the tab/host `<base_route>/<topic>` convention). */
706
+ /* Two-icon card: info / table, real hash anchors. {topic} in each
707
+ * template is the raw topic name (identifier-safe, matching the
708
+ * tab/host `<base_route>/<topic>` convention).
709
+ *
710
+ * NO graph icon any more. Its route was `<graph>/<topic>`, and that
711
+ * segment is a FOCUS: every way into the graph from a card arrived
712
+ * with that topic highlighted, over whatever the reader had left
713
+ * there. The graph is one button in the toolbar now, with no topic
714
+ * (routes.graph still feeds it), and the room goes to what the card
715
+ * can say about its topic. */
641
716
  let topic = id.indexOf("?") >= 0 ? id.split("?")[1] : id;
642
717
  let fill = (tpl) => String(tpl || "").replace("{topic}", topic);
643
718
 
@@ -650,14 +725,11 @@ function add_topic_card(gobj, id, text, icon)
650
725
  actions.push(card_action_anchor(
651
726
  "TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
652
727
  }
653
- if(routes.graph) {
654
- actions.push(card_action_anchor(
655
- "TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
656
- }
657
728
 
658
729
  let $card = createElement2(
659
730
  ['div', {class: 'TREEDB_TOPIC_CARD', 'data-topic': topic}, [
660
731
  ['span', {class: 'TREEDB_TOPIC_CARD_NAME', i18n: text}, text],
732
+ ...topic_card_meta(desc),
661
733
  ['div', {class: 'TREEDB_TOPIC_CARD_ACTIONS'}, actions]
662
734
  ]]
663
735
  );
@@ -1087,7 +1159,7 @@ function process_treedb_descs(gobj)
1087
1159
 
1088
1160
  // TODO get icon from remote config
1089
1161
  add_tab(gobj, gobj_topic_form, id, key, "yi-table");
1090
- add_topic_card(gobj, id, key, "yi-table");
1162
+ add_topic_card(gobj, id, key, "yi-table", desc);
1091
1163
 
1092
1164
  gobj_start(gobj_topic_form);
1093
1165
  }