@yuneta/gobj-ui 7.19.3 → 7.20.0

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.
package/README.md CHANGED
@@ -426,19 +426,42 @@ with limits, or any equivalent), and hands the subtree back via
426
426
  the DOM, so the tree stays bounded regardless of document size. With no
427
427
  sentinels present it degrades to a plain client-side collapsible tree.
428
428
 
429
+ **Two views, one document.** The toolbar switch (and the `view_mode` attr,
430
+ `"tree"` | `"text"`) turns the tree into the **raw text** of the same working
431
+ document — `JSON.stringify(…, 4)`, four characters per level like every other
432
+ indentation in the house. It is the view for reading a document as it is
433
+ written, selecting a slab of it, or searching it with the browser's own Ctrl+F.
434
+ Two consequences worth knowing:
435
+
436
+ - Nothing there is lazy. The text prints what the client currently holds,
437
+ `__collapsed__` sentinels included, because that is honestly what it has.
438
+ Expand a branch in the tree and the text grows with it.
439
+ - The tree-only controls (search, expand-loaded, collapse-all) hide with the
440
+ tree; copy stays. A control that can answer nothing is worse than an absent
441
+ one.
442
+
443
+ Long lines scroll sideways **inside** the viewer (`white-space: pre` on a
444
+ `max-content`-wide `<pre>`), never on the page body: in a raw dump the
445
+ indentation *is* the structure, and a wrapped line restarts at column 0 and
446
+ lies about the depth of everything under it.
447
+
429
448
  **Contract:**
430
449
 
431
450
  - Attributes: `subscriber`, `title` (i18n key, optional), `json_data` (initial
432
- JSON, optional), `$container` (mounted by the parent).
451
+ JSON, optional), `view_mode` (`"tree"` default | `"text"`), `$container`
452
+ (mounted by the parent).
433
453
  - Input events: `EV_SET_JSON {json}` (replace the whole document; `ST_EMPTY` →
434
454
  `ST_READY`), `EV_SUBTREE_LOADED {path, json}` (splice a fetched subtree),
435
- `EV_SUBTREE_ERROR {path, error}`, plus `EV_REFRESH` / `EV_SHOW` / `EV_HIDE` /
436
- `EV_LANGUAGE_CHANGED`.
455
+ `EV_SUBTREE_ERROR {path, error}`, `EV_SET_VIEW_MODE {mode}` (`"tree"` /
456
+ `"text"`; **no mode = toggle**, which is what the toolbar button sends), plus
457
+ `EV_REFRESH` / `EV_SHOW` / `EV_HIDE` / `EV_LANGUAGE_CHANGED`.
437
458
  - Output event: `EV_EXPAND_PATH {path, size}` (`EVF_OUTPUT_EVENT`) — the parent
438
459
  must declare it in its own FSM (CHILD subscription model).
439
460
  - Internal (DOM → FSM): `EV_TOGGLE_NODE`, `EV_EXPAND_COLLAPSED`, `EV_SEARCH`,
440
461
  `EV_EXPAND_ALL`, `EV_COLLAPSE_ALL`, `EV_COPY_ALL`. Every kw carries only a
441
462
  `path` string — never a DOM node or gobj.
463
+ - i18n keys added by the text view: `text view`, `tree view`,
464
+ `text truncated; collapse some branches`.
442
465
  - Paths use the kernel delimiter (backtick) and index arrays numerically, so a
443
466
  path emitted by the viewer round-trips through `kw_find_path` on the backend.
444
467
 
@@ -1467,7 +1467,7 @@ function navigate_to(gobj, route, depth, no_drain) {
1467
1467
  if (!(0, _yuneta_gobj_js.empty_string)(t.event)) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, t.event, t.kw || {});
1468
1468
  };
1469
1469
  let config = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "config") || {};
1470
- let prev = priv.stages && priv.stages.main && priv.stages.main.active_route || (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "default_route") || config.shell && config.shell.stages && config.shell.stages.main && config.shell.stages.main.default_route || "/";
1470
+ let prev = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "current_route") || priv.stages && priv.stages.main && priv.stages.main.active_route || (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "default_route") || config.shell && config.shell.stages && config.shell.stages.main && config.shell.stages.main.default_route || "/";
1471
1471
  let redirect = t.redirect;
1472
1472
  if (redirect === "stay") {
1473
1473
  if ((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "use_hash")) {
@@ -10416,6 +10416,39 @@ function format_epoch(value) {
10416
10416
  if (isNaN(d.getTime())) return null;
10417
10417
  return d.toLocaleString();
10418
10418
  }
10419
+ /************************************************************
10420
+ * The text view's payload: the document as text, indented
10421
+ * four characters, cut at `max_chars`.
10422
+ *
10423
+ * Returns {text, capped}. `capped` is what the caller
10424
+ * announces under the dump — a cut that is not announced
10425
+ * reads as a document that ends there.
10426
+ ************************************************************/
10427
+ function json_text_dump(root, max_chars) {
10428
+ let text;
10429
+ try {
10430
+ text = JSON.stringify(root, null, 4);
10431
+ } catch (e) {
10432
+ return {
10433
+ text: "",
10434
+ capped: false,
10435
+ error: String(e)
10436
+ };
10437
+ }
10438
+ if (text === void 0) return {
10439
+ text: "",
10440
+ capped: false,
10441
+ error: "not representable as JSON"
10442
+ };
10443
+ if (typeof max_chars === "number" && max_chars > 0 && text.length > max_chars) return {
10444
+ text: text.slice(0, max_chars),
10445
+ capped: true
10446
+ };
10447
+ return {
10448
+ text,
10449
+ capped: false
10450
+ };
10451
+ }
10419
10452
  //#endregion
10420
10453
  //#region src/c_yui_json.js
10421
10454
  /***********************************************************************
@@ -10444,9 +10477,19 @@ function format_epoch(value) {
10444
10477
  * Only expanded containers are materialised in the DOM, so the tree
10445
10478
  * stays bounded no matter how large the source document is.
10446
10479
  *
10480
+ * TWO VIEWS over the same working document, chosen by the `view_mode`
10481
+ * attr ("tree" | "text") and switched from the toolbar: the lazy tree
10482
+ * above, and the raw `JSON.stringify(…, 4)` dump of what is currently
10483
+ * loaded. The text view is what you reach for to read a document as
10484
+ * it is written, to select a slab of it, or to search it with the
10485
+ * browser's own Ctrl+F; it shows the `__collapsed__` sentinels
10486
+ * verbatim, because that is honestly what the client holds. The
10487
+ * tree-only controls (search, expand/collapse) hide with the tree.
10488
+ *
10447
10489
  * DOM is self-describing (UPPER_SNAKE logical classes): JSON_VIEWER /
10448
- * JSON_TOOLBAR / JSON_SEARCH / JSON_TREE / JSON_ROW / JSON_KEY /
10449
- * JSON_VALUE / JSON_SUMMARY / JSON_COLLAPSED / JSON_TIME.
10490
+ * JSON_TOOLBAR / JSON_SEARCH / JSON_VIEW_MODE / JSON_TREE / JSON_TEXT /
10491
+ * JSON_TEXT_BODY / JSON_ROW / JSON_KEY / JSON_VALUE / JSON_SUMMARY /
10492
+ * JSON_COLLAPSED / JSON_TIME.
10450
10493
  *
10451
10494
  * Copyright (c) 2026, ArtGins.
10452
10495
  * All Rights Reserved.
@@ -10456,6 +10499,7 @@ function format_epoch(value) {
10456
10499
  ***************************************************************/
10457
10500
  var GCLASS_NAME$10 = "C_YUI_JSON";
10458
10501
  var MAX_RENDER_ROWS = 5e3;
10502
+ var MAX_TEXT_CHARS = 2e6;
10459
10503
  /***************************************************************
10460
10504
  * Data
10461
10505
  ***************************************************************/
@@ -10463,6 +10507,7 @@ var attrs_table$10 = [
10463
10507
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
10464
10508
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "title", 0, "", "Optional header title (i18n key)"),
10465
10509
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "json_data", 0, null, "Initial JSON to render (usually already collapsed)"),
10510
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "view_mode", 0, "tree", "View: 'tree' (lazy tree) or 'text' (raw JSON dump)"),
10466
10511
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$container", 0, null, "HTMLElement root, mounted by the parent"),
10467
10512
  (0, _yuneta_gobj_js.SDATA_END)()
10468
10513
  ];
@@ -10473,7 +10518,13 @@ var PRIVATE_DATA$10 = {
10473
10518
  errors: null,
10474
10519
  search: "",
10475
10520
  $tree: null,
10476
- $search: null
10521
+ $text: null,
10522
+ $text_body: null,
10523
+ $search: null,
10524
+ $search_ctl: null,
10525
+ $expand_btn: null,
10526
+ $collapse_btn: null,
10527
+ $mode_btn: null
10477
10528
  };
10478
10529
  var __gclass__$10 = null;
10479
10530
  /******************************
@@ -10491,6 +10542,7 @@ function mt_create$10(gobj) {
10491
10542
  let json_data = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "json_data");
10492
10543
  if (json_data !== null && json_data !== void 0) priv.root = (0, _yuneta_gobj_js.json_deep_copy)(json_data);
10493
10544
  build_ui$9(gobj);
10545
+ apply_view_mode(gobj);
10494
10546
  let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
10495
10547
  if (!subscriber) subscriber = (0, _yuneta_gobj_js.gobj_parent)(gobj);
10496
10548
  (0, _yuneta_gobj_js.gobj_subscribe_event)(gobj, null, {}, subscriber);
@@ -10499,7 +10551,7 @@ function mt_create$10(gobj) {
10499
10551
  * Framework Method: Start
10500
10552
  ***************************************************************/
10501
10553
  function mt_start$10(gobj) {
10502
- render_tree(gobj);
10554
+ render_view(gobj);
10503
10555
  }
10504
10556
  /***************************************************************
10505
10557
  * Framework Method: Stop
@@ -10526,22 +10578,38 @@ function build_ui$9(gobj) {
10526
10578
  class: "C_YUI_JSON JSON_VIEWER view-card",
10527
10579
  style: "height:100%; display:flex; flex-direction:column;"
10528
10580
  },
10529
- [[
10530
- "div",
10531
- { class: "JSON_TOOLBAR is-flex-grow-0" },
10532
- [$toolbar]
10533
- ], [
10534
- "div",
10535
- {
10536
- class: "JSON_TREE is-flex-grow-1",
10537
- style: "flex:1 1 auto; min-height:0; overflow:auto;"
10538
- },
10539
- []
10540
- ]]
10581
+ [
10582
+ [
10583
+ "div",
10584
+ { class: "JSON_TOOLBAR is-flex-grow-0" },
10585
+ [$toolbar]
10586
+ ],
10587
+ [
10588
+ "div",
10589
+ {
10590
+ class: "JSON_TREE is-flex-grow-1",
10591
+ style: "flex:1 1 auto; min-height:0; overflow:auto;"
10592
+ },
10593
+ []
10594
+ ],
10595
+ [
10596
+ "div",
10597
+ {
10598
+ class: "JSON_TEXT is-flex-grow-1 is-hidden",
10599
+ style: "flex:1 1 auto; min-height:0; overflow:auto;"
10600
+ },
10601
+ []
10602
+ ]
10603
+ ]
10541
10604
  ]);
10542
10605
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", $container);
10543
10606
  priv.$tree = $container.querySelector(".JSON_TREE");
10607
+ priv.$text = $container.querySelector(".JSON_TEXT");
10544
10608
  priv.$search = $container.querySelector(".JSON_SEARCH");
10609
+ priv.$search_ctl = $container.querySelector(".JSON_SEARCH_CONTROL");
10610
+ priv.$expand_btn = $container.querySelector(".EV_EXPAND_ALL");
10611
+ priv.$collapse_btn = $container.querySelector(".EV_COLLAPSE_ALL");
10612
+ priv.$mode_btn = $container.querySelector(".JSON_VIEW_MODE");
10545
10613
  (0, _yuneta_gobj_js.refresh_language)($container, i18next.t);
10546
10614
  }
10547
10615
  /************************************************************
@@ -10555,10 +10623,17 @@ function destroy_ui$5(gobj) {
10555
10623
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", null);
10556
10624
  }
10557
10625
  priv.$tree = null;
10626
+ priv.$text = null;
10627
+ priv.$text_body = null;
10558
10628
  priv.$search = null;
10629
+ priv.$search_ctl = null;
10630
+ priv.$expand_btn = null;
10631
+ priv.$collapse_btn = null;
10632
+ priv.$mode_btn = null;
10559
10633
  }
10560
10634
  /************************************************************
10561
- * Toolbar: search + expand-all / collapse-all / copy
10635
+ * Toolbar: search + tree/text switch + expand-all /
10636
+ * collapse-all / copy
10562
10637
  ************************************************************/
10563
10638
  function make_toolbar$2(gobj) {
10564
10639
  let title = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "title");
@@ -10587,7 +10662,7 @@ function make_toolbar$2(gobj) {
10587
10662
  let $search_control = (0, _yuneta_gobj_js.createElement2)([
10588
10663
  "div",
10589
10664
  {
10590
- class: "control has-icons-left",
10665
+ class: "control has-icons-left JSON_SEARCH_CONTROL",
10591
10666
  style: "max-width:22em;"
10592
10667
  },
10593
10668
  [$search_input, [
@@ -10599,6 +10674,7 @@ function make_toolbar$2(gobj) {
10599
10674
  attach_clear($search_control, $search_input);
10600
10675
  left_items.push($search_control);
10601
10676
  let right_items = [
10677
+ view_mode_button(gobj),
10602
10678
  icon_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand loaded"),
10603
10679
  icon_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all"),
10604
10680
  icon_button(gobj, "yi-copy", "EV_COPY_ALL", "copy json")
@@ -10655,6 +10731,122 @@ function icon_button(gobj, icon, event_name, label_key) {
10655
10731
  ];
10656
10732
  }
10657
10733
  /************************************************************
10734
+ * The tree/text switch.
10735
+ *
10736
+ * It shows the view it takes you TO — the `code` glyph while the
10737
+ * tree is on screen, the `sitemap` glyph while the text is — and
10738
+ * sends EV_SET_VIEW_MODE with no mode, which the action reads as
10739
+ * "toggle". apply_view_mode() keeps the icon and its i18n keys in
10740
+ * step with the attr.
10741
+ ************************************************************/
10742
+ function view_mode_button(gobj) {
10743
+ return [
10744
+ "button",
10745
+ {
10746
+ class: "button JSON_VIEW_MODE",
10747
+ style: "width:2.5em;",
10748
+ title: (0, i18next.t)("text view"),
10749
+ "data-i18n-title": "text view",
10750
+ "aria-label": (0, i18next.t)("text view"),
10751
+ "data-i18n-aria-label": "text view"
10752
+ },
10753
+ [[
10754
+ "span",
10755
+ { class: "icon" },
10756
+ [["i", { class: "yi-code" }]]
10757
+ ]],
10758
+ { click: function(evt) {
10759
+ evt.stopPropagation();
10760
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SET_VIEW_MODE", {}, gobj);
10761
+ } }
10762
+ ];
10763
+ }
10764
+ /************************************************************
10765
+ * The current view, normalised. Anything but "text" is the tree.
10766
+ ************************************************************/
10767
+ function current_view_mode(gobj) {
10768
+ return (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "view_mode") === "text" ? "text" : "tree";
10769
+ }
10770
+ /************************************************************
10771
+ * Show the chrome of the current view and hide the other's.
10772
+ ************************************************************/
10773
+ function apply_view_mode(gobj) {
10774
+ let priv = gobj.priv;
10775
+ let text_mode = current_view_mode(gobj) === "text";
10776
+ if (priv.$tree) priv.$tree.classList.toggle("is-hidden", text_mode);
10777
+ if (priv.$text) priv.$text.classList.toggle("is-hidden", !text_mode);
10778
+ [
10779
+ priv.$search_ctl,
10780
+ priv.$expand_btn,
10781
+ priv.$collapse_btn
10782
+ ].forEach(function($el) {
10783
+ if ($el) $el.classList.toggle("is-hidden", text_mode);
10784
+ });
10785
+ if (priv.$mode_btn) {
10786
+ let icon = text_mode ? "yi-sitemap" : "yi-code";
10787
+ let key = text_mode ? "tree view" : "text view";
10788
+ let $i = priv.$mode_btn.querySelector("i");
10789
+ if ($i) $i.className = icon;
10790
+ priv.$mode_btn.setAttribute("data-i18n-title", key);
10791
+ priv.$mode_btn.setAttribute("data-i18n-aria-label", key);
10792
+ priv.$mode_btn.setAttribute("title", (0, i18next.t)(key));
10793
+ priv.$mode_btn.setAttribute("aria-label", (0, i18next.t)(key));
10794
+ }
10795
+ }
10796
+ /************************************************************
10797
+ * Render whichever view is current.
10798
+ ************************************************************/
10799
+ function render_view(gobj) {
10800
+ if (current_view_mode(gobj) === "text") render_text(gobj);
10801
+ else render_tree(gobj);
10802
+ }
10803
+ /************************************************************
10804
+ * Render the text view: the whole loaded document, verbatim.
10805
+ *
10806
+ * Unlike the tree, nothing here is lazy — what the client holds is
10807
+ * what it prints, `__collapsed__` sentinels included. Over
10808
+ * MAX_TEXT_CHARS the dump is cut and the cut is announced.
10809
+ ************************************************************/
10810
+ function render_text(gobj) {
10811
+ let priv = gobj.priv;
10812
+ let $text = priv.$text;
10813
+ if (!$text) return;
10814
+ let scroll_top = $text.scrollTop;
10815
+ $text.textContent = "";
10816
+ priv.$text_body = null;
10817
+ if (priv.root === null || priv.root === void 0) {
10818
+ $text.appendChild((0, _yuneta_gobj_js.createElement2)([
10819
+ "div",
10820
+ {
10821
+ class: "JSON_EMPTY has-text-grey p-3",
10822
+ "data-i18n": "no data"
10823
+ },
10824
+ "no data"
10825
+ ]));
10826
+ return;
10827
+ }
10828
+ let dump = json_text_dump(priv.root, MAX_TEXT_CHARS);
10829
+ if (dump.error) (0, _yuneta_gobj_js.log_error)(`${GCLASS_NAME$10}: cannot dump the document as text: ${dump.error}`);
10830
+ let $pre = (0, _yuneta_gobj_js.createElement2)([
10831
+ "pre",
10832
+ { class: "JSON_TEXT_BODY" },
10833
+ []
10834
+ ]);
10835
+ $pre.textContent = dump.text;
10836
+ $text.appendChild($pre);
10837
+ priv.$text_body = $pre;
10838
+ if (dump.capped) $text.appendChild((0, _yuneta_gobj_js.createElement2)([
10839
+ "div",
10840
+ {
10841
+ class: "JSON_CAPPED has-text-warning p-2",
10842
+ "data-i18n": "text truncated; collapse some branches"
10843
+ },
10844
+ "text truncated; collapse some branches"
10845
+ ]));
10846
+ (0, _yuneta_gobj_js.refresh_language)($text, i18next.t);
10847
+ $text.scrollTop = scroll_top;
10848
+ }
10849
+ /************************************************************
10658
10850
  * Re-render the whole tree from priv.root + priv.expanded.
10659
10851
  *
10660
10852
  * Only expanded containers are walked, so the DOM size is
@@ -10985,7 +11177,7 @@ function ac_set_json(gobj, event, kw, src) {
10985
11177
  priv.expanded.clear();
10986
11178
  priv.pending.clear();
10987
11179
  priv.errors.clear();
10988
- render_tree(gobj);
11180
+ render_view(gobj);
10989
11181
  return 0;
10990
11182
  }
10991
11183
  /************************************************************
@@ -11000,7 +11192,7 @@ function ac_subtree_loaded(gobj, event, kw, src) {
11000
11192
  priv.pending.delete(path);
11001
11193
  priv.errors.delete(path);
11002
11194
  if (path) priv.expanded.add(path);
11003
- render_tree(gobj);
11195
+ render_view(gobj);
11004
11196
  return 0;
11005
11197
  }
11006
11198
  /************************************************************
@@ -11012,7 +11204,7 @@ function ac_subtree_error(gobj, event, kw, src) {
11012
11204
  priv.pending.delete(path);
11013
11205
  priv.errors.set(path, kw.error || "error");
11014
11206
  (0, _yuneta_gobj_js.log_error)(`${GCLASS_NAME$10}: subtree load failed at '${path}': ${kw.error || ""}`);
11015
- render_tree(gobj);
11207
+ render_view(gobj);
11016
11208
  return 0;
11017
11209
  }
11018
11210
  /************************************************************
@@ -11023,7 +11215,7 @@ function ac_toggle_node(gobj, event, kw, src) {
11023
11215
  let path = kw.path || "";
11024
11216
  if (priv.expanded.has(path)) priv.expanded.delete(path);
11025
11217
  else priv.expanded.add(path);
11026
- render_tree(gobj);
11218
+ render_view(gobj);
11027
11219
  return 0;
11028
11220
  }
11029
11221
  /************************************************************
@@ -11036,7 +11228,7 @@ function ac_expand_collapsed(gobj, event, kw, src) {
11036
11228
  if (priv.pending.has(path)) return 0;
11037
11229
  priv.pending.add(path);
11038
11230
  priv.errors.delete(path);
11039
- render_tree(gobj);
11231
+ render_view(gobj);
11040
11232
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_EXPAND_PATH", {
11041
11233
  path,
11042
11234
  size: kw.size
@@ -11049,7 +11241,7 @@ function ac_expand_collapsed(gobj, event, kw, src) {
11049
11241
  function ac_search$1(gobj, event, kw, src) {
11050
11242
  let priv = gobj.priv;
11051
11243
  priv.search = (kw.text || "").trim().toLowerCase();
11052
- render_tree(gobj);
11244
+ render_view(gobj);
11053
11245
  return 0;
11054
11246
  }
11055
11247
  /************************************************************
@@ -11067,7 +11259,7 @@ function ac_expand_all$1(gobj, event, kw, src) {
11067
11259
  paths.forEach(function(p) {
11068
11260
  priv.expanded.add(p);
11069
11261
  });
11070
- render_tree(gobj);
11262
+ render_view(gobj);
11071
11263
  return 0;
11072
11264
  }
11073
11265
  /************************************************************
@@ -11075,7 +11267,7 @@ function ac_expand_all$1(gobj, event, kw, src) {
11075
11267
  ************************************************************/
11076
11268
  function ac_collapse_all$1(gobj, event, kw, src) {
11077
11269
  gobj.priv.expanded.clear();
11078
- render_tree(gobj);
11270
+ render_view(gobj);
11079
11271
  return 0;
11080
11272
  }
11081
11273
  /************************************************************
@@ -11084,7 +11276,12 @@ function ac_collapse_all$1(gobj, event, kw, src) {
11084
11276
  function ac_copy_all(gobj, event, kw, src) {
11085
11277
  let priv = gobj.priv;
11086
11278
  if (priv.root === null || priv.root === void 0) return 0;
11087
- let text = JSON.stringify(priv.root, null, 4);
11279
+ let dump = json_text_dump(priv.root, 0);
11280
+ if (dump.error) {
11281
+ (0, _yuneta_gobj_js.log_error)(`${GCLASS_NAME$10}: cannot copy the document: ${dump.error}`);
11282
+ return -1;
11283
+ }
11284
+ let text = dump.text;
11088
11285
  if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).catch(function(e) {
11089
11286
  (0, _yuneta_gobj_js.log_error)(`${GCLASS_NAME$10}: clipboard write failed: ${e}`);
11090
11287
  });
@@ -11092,6 +11289,23 @@ function ac_copy_all(gobj, event, kw, src) {
11092
11289
  return 0;
11093
11290
  }
11094
11291
  /************************************************************
11292
+ * EV_SET_VIEW_MODE { mode } — "tree" | "text".
11293
+ *
11294
+ * With no mode it toggles, which is what the toolbar button sends.
11295
+ ************************************************************/
11296
+ function ac_set_view_mode(gobj, event, kw, src) {
11297
+ let mode = kw.mode;
11298
+ if (mode === void 0 || mode === null || mode === "") mode = current_view_mode(gobj) === "text" ? "tree" : "text";
11299
+ else if (mode !== "tree" && mode !== "text") {
11300
+ (0, _yuneta_gobj_js.log_error)(`${GCLASS_NAME$10}: unknown view_mode '${mode}'`);
11301
+ return -1;
11302
+ }
11303
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "view_mode", mode);
11304
+ apply_view_mode(gobj);
11305
+ render_view(gobj);
11306
+ return 0;
11307
+ }
11308
+ /************************************************************
11095
11309
  * EV_LANGUAGE_CHANGED — re-translate chrome + re-render
11096
11310
  ************************************************************/
11097
11311
  function ac_language_changed$3(gobj, event, kw, src) {
@@ -11100,8 +11314,9 @@ function ac_language_changed$3(gobj, event, kw, src) {
11100
11314
  if ($container) {
11101
11315
  (0, _yuneta_gobj_js.refresh_language)($container, i18next.t);
11102
11316
  if (priv.$search) priv.$search.setAttribute("placeholder", (0, i18next.t)("search"));
11317
+ apply_view_mode(gobj);
11103
11318
  }
11104
- render_tree(gobj);
11319
+ render_view(gobj);
11105
11320
  return 0;
11106
11321
  }
11107
11322
  /************************************************************
@@ -11121,7 +11336,7 @@ function ac_hide$6(gobj, event, kw, src) {
11121
11336
  * EV_REFRESH
11122
11337
  ************************************************************/
11123
11338
  function ac_refresh$4(gobj, event, kw, src) {
11124
- render_tree(gobj);
11339
+ render_view(gobj);
11125
11340
  return 0;
11126
11341
  }
11127
11342
  /***************************
@@ -11147,6 +11362,11 @@ function create_gclass$10(gclass_name) {
11147
11362
  ac_set_json,
11148
11363
  "ST_READY"
11149
11364
  ],
11365
+ [
11366
+ "EV_SET_VIEW_MODE",
11367
+ ac_set_view_mode,
11368
+ null
11369
+ ],
11150
11370
  [
11151
11371
  "EV_LANGUAGE_CHANGED",
11152
11372
  ac_language_changed$3,
@@ -11213,6 +11433,11 @@ function create_gclass$10(gclass_name) {
11213
11433
  ac_copy_all,
11214
11434
  null
11215
11435
  ],
11436
+ [
11437
+ "EV_SET_VIEW_MODE",
11438
+ ac_set_view_mode,
11439
+ null
11440
+ ],
11216
11441
  [
11217
11442
  "EV_LANGUAGE_CHANGED",
11218
11443
  ac_language_changed$3,
@@ -11244,6 +11469,7 @@ function create_gclass$10(gclass_name) {
11244
11469
  ["EV_EXPAND_ALL", 0],
11245
11470
  ["EV_COLLAPSE_ALL", 0],
11246
11471
  ["EV_COPY_ALL", 0],
11472
+ ["EV_SET_VIEW_MODE", 0],
11247
11473
  ["EV_LANGUAGE_CHANGED", 0],
11248
11474
  ["EV_REFRESH", 0],
11249
11475
  ["EV_SHOW", 0],