@yuneta/gobj-ui 7.23.194 → 7.23.196

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.
@@ -7305,6 +7305,39 @@ function json_diff_rows(doc_a, doc_b) {
7305
7305
  return rows;
7306
7306
  }
7307
7307
  //#endregion
7308
+ //#region src/form_time_value.js
7309
+ /****************************************************************************
7310
+ * form_time_value.js
7311
+ *
7312
+ * A treedb `time` / `now` column is an epoch in SECONDS, and a form
7313
+ * shows it in a `datetime-local` input. These two convert between
7314
+ * them, and they must be each other's inverse: a writable time
7315
+ * column travels back on EVERY save of the record.
7316
+ *
7317
+ * Copyright (c) 2026, ArtGins.
7318
+ * All Rights Reserved.
7319
+ ****************************************************************************/
7320
+ /************************************************************
7321
+ * A Date -> the value of a `datetime-local` input, in local time,
7322
+ * WITH seconds: the input is built with `step: 1`, which is what
7323
+ * lets it hold them. Written as "YYYY-MM-DDTHH:mm" it lost them on
7324
+ * every save of any field (M26 of the 2026-09-21 review).
7325
+ ************************************************************/
7326
+ function date_to_datetime_local(value) {
7327
+ if (!(value instanceof Date) || isNaN(value.getTime())) return "";
7328
+ let pad = (n) => String(n).padStart(2, "0");
7329
+ return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}T${pad(value.getHours())}:${pad(value.getMinutes())}:${pad(value.getSeconds())}`;
7330
+ }
7331
+ /************************************************************
7332
+ * The value of a `datetime-local` input -> epoch in seconds
7333
+ ************************************************************/
7334
+ function datetime_local_to_epoch(value) {
7335
+ if (!value) return null;
7336
+ const date = new Date(value);
7337
+ if (isNaN(date.getTime())) return null;
7338
+ return Math.floor(date.getTime() / 1e3);
7339
+ }
7340
+ //#endregion
7308
7341
  //#region src/c_yui_form.js
7309
7342
  /***********************************************************************
7310
7343
  * c_yui_form.js
@@ -8317,6 +8350,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
8317
8350
  type: inputType,
8318
8351
  placeholder
8319
8352
  };
8353
+ if (inputType === "datetime-local") attrs.step = 1;
8320
8354
  Object.assign(attrs, extras);
8321
8355
  if (readonly) attrs.readonly = "";
8322
8356
  else if (required) attrs.required = "";
@@ -9017,8 +9051,7 @@ function get_form_values(gobj, $form) {
9017
9051
  case "input":
9018
9052
  switch (input_type) {
9019
9053
  case "datetime-local":
9020
- const date = new Date($input.value);
9021
- value = Math.floor(date.getTime() / 1e3);
9054
+ value = datetime_local_to_epoch($input.value);
9022
9055
  break;
9023
9056
  default: value = getInputValue($input);
9024
9057
  }
@@ -9181,10 +9214,7 @@ function set_form_values(gobj, template, $form, record) {
9181
9214
  case "input":
9182
9215
  switch (input_type) {
9183
9216
  case "datetime-local":
9184
- if (value instanceof Date && !isNaN(value.getTime())) {
9185
- let pad = (n) => String(n).padStart(2, "0");
9186
- $input.value = `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}T${pad(value.getHours())}:${pad(value.getMinutes())}`;
9187
- } else $input.value = "";
9217
+ $input.value = date_to_datetime_local(value);
9188
9218
  break;
9189
9219
  case "color":
9190
9220
  $input.value = convertColor(value);
@@ -26476,6 +26506,58 @@ function delete_impact(desc, records) {
26476
26506
  return out;
26477
26507
  }
26478
26508
  //#endregion
26509
+ //#region src/table_cell_text.js
26510
+ /****************************************************************************
26511
+ * table_cell_text.js
26512
+ *
26513
+ * What a treedb table cell hands Tabulator.
26514
+ *
26515
+ * Tabulator assigns a formatter's STRING result with `innerHTML`
26516
+ * (only its own `plaintext` formatter escapes), so a record's text
26517
+ * returned as a string is parsed as markup: `a<b and c>d` showed
26518
+ * as `ad`, and a field holding `<img src=x onerror=...>` ran it in
26519
+ * the operator's browser (M31 of the 2026-09-21 review). Record
26520
+ * data goes in as a TEXT NODE, and a cell built around it as DOM,
26521
+ * never as an HTML string with the data interpolated.
26522
+ *
26523
+ * Pure but for the `document` it is handed, so it is tested
26524
+ * without one.
26525
+ *
26526
+ * Copyright (c) 2026, ArtGins.
26527
+ * All Rights Reserved.
26528
+ ****************************************************************************/
26529
+ /************************************************************
26530
+ * A value shown as text, whatever characters it holds
26531
+ ************************************************************/
26532
+ function cell_text(value, doc = globalThis.document) {
26533
+ if (value === null || value === void 0) return "";
26534
+ return doc.createTextNode(String(value));
26535
+ }
26536
+ /************************************************************
26537
+ * The hook cell, as a createElement2 spec: the row id is an
26538
+ * attribute VALUE, never text spliced into markup (an id holding
26539
+ * a `"` cut the attribute and lost the rest of the id)
26540
+ ************************************************************/
26541
+ function hook_cell_spec(row_id, col_id, items, title, title_key) {
26542
+ return [
26543
+ "a",
26544
+ {
26545
+ class: "HOOK_CELL hook_cell",
26546
+ title,
26547
+ "data-i18n-title": title_key,
26548
+ "aria-label": title,
26549
+ "data-i18n-aria-label": title_key,
26550
+ "data-row_id": String(row_id),
26551
+ "data-col_id": String(col_id)
26552
+ },
26553
+ [["span", { class: "icon yi-eye" }], [
26554
+ "u",
26555
+ { class: "HOOK_CELL_COUNT" },
26556
+ `[ ${items} ]`
26557
+ ]]
26558
+ ];
26559
+ }
26560
+ //#endregion
26479
26561
  //#region src/c_yui_treedb_topic_with_form.js
26480
26562
  /***********************************************************************
26481
26563
  * c_yui_treedb_topic_with_form.js
@@ -27902,18 +27984,8 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
27902
27984
  break;
27903
27985
  case "hook":
27904
27986
  let items = treedb_hook_data_size(value);
27905
- if (items > 0) {
27906
- let hook_title = String(t("show linked records")).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
27907
- value = [
27908
- "<a class=\"hook_cell\" ",
27909
- `title="${hook_title}" aria-label="${hook_title}" `,
27910
- `data-row_id="${row.id}" `,
27911
- `data-col_id="${col.id}" > `,
27912
- "<span style=\"\" class=\"icon yi-eye\"></span>",
27913
- `<span>[&nbsp;<u>${items}</u>&nbsp;]</span>`,
27914
- "</a>"
27915
- ].join("");
27916
- } else value = "";
27987
+ if (items > 0) value = createElement2(hook_cell_spec(row.id, col.id, items, t("show linked records"), "show linked records"));
27988
+ else value = "";
27917
27989
  break;
27918
27990
  case "file":
27919
27991
  case "fkey":
@@ -27939,7 +28011,7 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
27939
28011
  value = /* @__PURE__ */ new Date(value * 1e3);
27940
28012
  const rawLocale = navigator.language;
27941
28013
  const userLocale = typeof rawLocale === "string" && rawLocale && rawLocale !== "undefined" ? rawLocale : void 0;
27942
- value = "<span class=\"is-size-7\">" + new Intl.DateTimeFormat(userLocale, {
28014
+ const formatter = new Intl.DateTimeFormat(userLocale, {
27943
28015
  day: "numeric",
27944
28016
  month: "short",
27945
28017
  year: "numeric",
@@ -27947,7 +28019,12 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
27947
28019
  minute: "2-digit",
27948
28020
  second: "2-digit",
27949
28021
  hour12: true
27950
- }).format(value) + "</span>";
28022
+ });
28023
+ value = createElement2([
28024
+ "span",
28025
+ { class: "is-size-7" },
28026
+ formatter.format(value)
28027
+ ]);
27951
28028
  }
27952
28029
  break;
27953
28030
  case "color": break;
@@ -27959,6 +28036,7 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
27959
28036
  log_error(`transform__treedb_value_2_table_value() unexpected object value for field '${field}' (type='${field_desc.type}', real_type='${field_desc.real_type}'): ${JSON.stringify(value)}, topic ${priv.topic_name}`);
27960
28037
  value = JSON.stringify(value);
27961
28038
  }
28039
+ if (typeof value === "string" && value !== "") value = cell_text(value);
27962
28040
  return value;
27963
28041
  }
27964
28042
  /************************************************************
@@ -70080,10 +70158,11 @@ function register_c_yui_treedb_schema() {
70080
70158
  * operator drew does nothing at all. Restarting on
70081
70159
  * this is an outage with no gain.
70082
70160
  * `warning` the schema opens and something is not what it looks
70083
- * like — an unbumped `topic_version` masking the whole
70084
- * edit is the one that costs the most time, because
70085
- * the restart SUCCEEDS and the change is simply not
70086
- * there.
70161
+ * like.
70162
+ *
70163
+ * A version is not checked here: an edit is a DRAFT and moves no
70164
+ * version, and C_TREEDB's save-schema raises the versions of what
70165
+ * changed when it publishes it.
70087
70166
  *
70088
70167
  * Every `code` is an i18n key: the caller renders, this decides.
70089
70168
  *
@@ -70130,9 +70209,6 @@ var FKEY_TYPES = [
70130
70209
  ***************************************************************/
70131
70210
  function validate_schema(treedb, options) {
70132
70211
  let findings = [];
70133
- let opts = options || {};
70134
- let written = Array.isArray(opts.written_topics) ? opts.written_topics : [];
70135
- let baseline = opts.baseline || {};
70136
70212
  if (!treedb || !Array.isArray(treedb.topics)) return findings;
70137
70213
  let add = (severity, code, topic, col, detail) => {
70138
70214
  findings.push({
@@ -70153,10 +70229,6 @@ function validate_schema(treedb, options) {
70153
70229
  let pkey = topic.pkey || "id";
70154
70230
  if (col_names.indexOf(pkey) < 0) add("error", "pkey names no column", topic.name, "", pkey);
70155
70231
  for (let pkey2 of topic_pkey2s(topic.record)) if (col_names.indexOf(pkey2) < 0) add("error", "pkey2 names no column", topic.name, "", pkey2);
70156
- if (written.indexOf(topic.id) >= 0) {
70157
- let before = baseline[topic.id];
70158
- if (before !== void 0 && String(before) === String(topic.topic_version)) add("warning", "topic version not bumped", topic.name, "", String(topic.topic_version));
70159
- }
70160
70232
  for (let col of topic.cols) {
70161
70233
  let flags = col_flags(col.record);
70162
70234
  let type = col.record ? col.record.type : "";
@@ -71425,13 +71497,15 @@ function write_options(op) {
71425
71497
  * fkey, the order of a new sibling, the version that publishes
71426
71498
  * the change.
71427
71499
  *
71428
- * THE VERSION IS THE POINT, not a detail. A topic whose columns
71429
- * changed and whose `topic_version` did not is a topic whose
71430
- * persisted `topic_cols.json` masks the whole edit: the restart
71431
- * succeeds and nothing moved. Nothing in the store says so, and
71432
- * the log that would is on the node. So every write here raises
71433
- * the version of the topic it touched, and the toolbar says which
71434
- * topics are waiting for a restart.
71500
+ * AN EDIT IS A DRAFT. A write here moves no version and reaches no
71501
+ * treedb: the host SAVES it (C_TREEDB's save-schema raises the
71502
+ * topic_version of what changed and the schema_version, once) and
71503
+ * APPLIES it (apply-schema + a restart of the owning yuno). Until
71504
+ * the owner's design of M36 (2026-09-21 review) every write here
71505
+ * raised both versions, so an edit half made was already the
71506
+ * schema of the next start. The topic list marks what this session
71507
+ * wrote and has not saved; the host's EV_REFRESH after a save is
71508
+ * what forgets it.
71435
71509
  *
71436
71510
  * WHAT ELSE IS HERE, and why each is here and not somewhere else:
71437
71511
  *
@@ -71776,16 +71850,20 @@ function start_measuring(gobj) {
71776
71850
  for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
71777
71851
  }
71778
71852
  /***************************************************************
71779
- * "This topic was written and its version did not move."
71853
+ * "This topic was written in this session and is not saved."
71854
+ *
71855
+ * An edit is a DRAFT: it moves no version and reaches no treedb
71856
+ * until the host saves it (C_TREEDB's save-schema, which raises the
71857
+ * versions of what changed) -- and the save makes the host send
71858
+ * EV_REFRESH, whose reload is what forgets the drafts.
71780
71859
  *
71781
- * ONE predicate, because it is asked in two places -- the topic
71782
- * list draws a chip with it and the column view draws a banner --
71783
- * and two copies of a rule is how they drift.
71860
+ * ONE predicate, because it is asked in three places -- the topic
71861
+ * list draws a chip with it, the column view a banner, the export
71862
+ * a warning -- and copies of a rule are how they drift.
71784
71863
  ***************************************************************/
71785
- function needs_version_bump(written, baseline, topic) {
71786
- if (!topic || !written || !baseline) return false;
71787
- if (!written[topic.id]) return false;
71788
- return String(baseline[topic.id]) === String(topic.topic_version);
71864
+ function topic_is_draft(written, topic) {
71865
+ if (!topic || !written) return false;
71866
+ return written[topic.id] ? true : false;
71789
71867
  }
71790
71868
  /***************************************************************
71791
71869
  * Say what the whole store would be refused for.
@@ -72227,7 +72305,7 @@ function render_topics(gobj, $body) {
72227
72305
  let twice = repeated_names(treedb.topics);
72228
72306
  let $rows = [];
72229
72307
  for (let topic of treedb.topics) {
72230
- let pending = needs_version_bump(priv.written, priv.baseline, topic);
72308
+ let pending = topic_is_draft(priv.written, topic);
72231
72309
  let ambiguous = !!twice[topic.name];
72232
72310
  let address = ambiguous ? topic.id : topic.name;
72233
72311
  let $actions = [];
@@ -72462,7 +72540,7 @@ function render_columns(gobj, $body) {
72462
72540
  { class: "SCHEMA_COLUMNS p-3" },
72463
72541
  []
72464
72542
  ]);
72465
- let $banner = version_banner(gobj, topic);
72543
+ let $banner = draft_banner(gobj, topic);
72466
72544
  if ($banner) $wrap.appendChild($banner);
72467
72545
  let twice = repeated_names(topic.cols);
72468
72546
  let $rows = [];
@@ -72540,66 +72618,37 @@ function render_columns(gobj, $body) {
72540
72618
  refresh_language($wrap, t);
72541
72619
  }
72542
72620
  /***************************************************************
72543
- * "This topic changed and its version did not."
72621
+ * "This topic has changes that are not saved."
72544
72622
  *
72545
- * Only shown when it is TRUE — the topic was written in this
72546
- * session and its `topic_version` still reads what it read when
72547
- * the model loaded. Every write this view makes raises it, so
72548
- * the banner is for the case where something else did not: an
72549
- * edit made from the raw tables, or a bump undone by hand.
72623
+ * A draft, not an error: the edit is in __system__ and moves no
72624
+ * version, so the treedb does not see it until the host saves it
72625
+ * (and applies it). The banner says so and offers nothing -- the
72626
+ * save is the host's, one level up, because it publishes the whole
72627
+ * treedb and not one topic.
72550
72628
  ***************************************************************/
72551
- function version_banner(gobj, topic) {
72629
+ function draft_banner(gobj, topic) {
72552
72630
  let priv = gobj.priv;
72553
- if (!needs_version_bump(priv.written, priv.baseline, topic)) return null;
72631
+ if (!topic_is_draft(priv.written, topic)) return null;
72554
72632
  if (is_readonly(gobj)) return null;
72555
- let $banner = createElement2([
72633
+ return createElement2([
72556
72634
  "div",
72557
72635
  {
72558
- class: "SCHEMA_VERSION_BANNER notification is-warning is-light p-3 mb-3 is-flex is-align-items-center",
72636
+ class: "SCHEMA_DRAFT_BANNER notification is-warning is-light p-3 mb-3 is-flex is-align-items-center",
72559
72637
  style: "gap:.5rem;"
72560
72638
  },
72561
- [
72562
- [
72563
- "span",
72564
- { class: "icon" },
72565
- [["i", { class: "yi-triangle-exclamation" }]]
72566
- ],
72567
- [
72568
- "span",
72569
- {
72570
- class: "SCHEMA_VERSION_TEXT is-size-7",
72571
- i18n: "version not raised"
72572
- },
72573
- t("version not raised")
72574
- ],
72575
- [
72576
- "button",
72577
- {
72578
- class: "SCHEMA_VERSION_BUMP button is-warning",
72579
- type: "button",
72580
- style: "margin-left:auto;",
72581
- title: t("raise the version"),
72582
- "aria-label": t("raise the version"),
72583
- "data-i18n-title": "raise the version",
72584
- "data-i18n-aria-label": "raise the version"
72585
- },
72586
- [[
72587
- "span",
72588
- { class: "icon" },
72589
- [["i", { class: "yi-circle-up" }]]
72590
- ], [
72591
- "span",
72592
- { i18n: "raise" },
72593
- t("raise")
72594
- ]]
72595
- ]
72596
- ]
72639
+ [[
72640
+ "span",
72641
+ { class: "icon" },
72642
+ [["i", { class: "yi-triangle-exclamation" }]]
72643
+ ], [
72644
+ "span",
72645
+ {
72646
+ class: "SCHEMA_DRAFT_TEXT is-size-7",
72647
+ i18n: "unsaved schema changes: save to publish them"
72648
+ },
72649
+ t("unsaved schema changes: save to publish them")
72650
+ ]]
72597
72651
  ]);
72598
- $banner.querySelector(".SCHEMA_VERSION_BUMP").addEventListener("click", (evt) => {
72599
- evt.stopPropagation();
72600
- gobj_send_event(gobj, "EV_BUMP_VERSION", { topic: topic.name }, gobj);
72601
- });
72602
- return $banner;
72603
72652
  }
72604
72653
  /***************************************************************
72605
72654
  * One column, as a row. The drag events carry INDEXES in the
@@ -73468,6 +73517,8 @@ function open_report(gobj, findings, treedb_id) {
73468
73517
  * as a download, which a published page cannot start anyway.
73469
73518
  ***************************************************************/
73470
73519
  function open_export(gobj, treedb) {
73520
+ let priv = gobj.priv;
73521
+ let drafts = treedb.topics.some((topic) => topic_is_draft(priv.written, topic));
73471
73522
  let c_text = schema_to_c(treedb);
73472
73523
  let json_text = JSON.stringify(schema_to_json(treedb), null, 4);
73473
73524
  let $content = createElement2([
@@ -73521,13 +73572,29 @@ function open_export(gobj, treedb) {
73521
73572
  },
73522
73573
  t("an edit made here works and lives in no source: this is what to paste back")
73523
73574
  ],
73575
+ drafts ? [
73576
+ "p",
73577
+ {
73578
+ class: "SCHEMA_EXPORT_DRAFTS notification is-warning is-light p-2 mb-2 is-size-7",
73579
+ i18n: "unsaved changes: save first, so the literal carries the versions that publish it"
73580
+ },
73581
+ t("unsaved changes: save first, so the literal carries the versions that publish it")
73582
+ ] : [
73583
+ "span",
73584
+ {},
73585
+ ""
73586
+ ],
73524
73587
  [
73525
73588
  "textarea",
73526
73589
  {
73527
73590
  class: "SCHEMA_EXPORT_TEXT textarea is-small is-family-monospace",
73528
73591
  rows: "18",
73529
73592
  readonly: "readonly",
73530
- spellcheck: "false"
73593
+ spellcheck: "false",
73594
+ title: t("schema"),
73595
+ "data-i18n-title": "schema",
73596
+ "aria-label": t("schema"),
73597
+ "data-i18n-aria-label": "schema"
73531
73598
  },
73532
73599
  c_text
73533
73600
  ],
@@ -73541,7 +73608,11 @@ function open_export(gobj, treedb) {
73541
73608
  "button",
73542
73609
  {
73543
73610
  class: "SCHEMA_EXPORT_COPY button",
73544
- type: "button"
73611
+ type: "button",
73612
+ title: t("copy"),
73613
+ "data-i18n-title": "copy",
73614
+ "aria-label": t("copy"),
73615
+ "data-i18n-aria-label": "copy"
73545
73616
  },
73546
73617
  [[
73547
73618
  "span",
@@ -73876,52 +73947,6 @@ function open_orphans(gobj) {
73876
73947
  * Writing
73877
73948
  ***************************/
73878
73949
  /***************************************************************
73879
- * THE TWO VERSIONS A SCHEMA EDIT HAS TO RAISE, and why both.
73880
- *
73881
- * `topic_version` is what publishes a change of a topic's
73882
- * columns: leave it and the persisted `topic_cols.json` masks
73883
- * the edit, the restart succeeds and nothing moved.
73884
- *
73885
- * `schema_version` is what publishes the schema as a whole —
73886
- * "the stored one wins on ties, and the incoming one has to be
73887
- * strictly newer to take over" (c_treedb.c). It is safe to
73888
- * raise: re-projection from C compares `c_schema_version`, the
73889
- * version of the LITERAL, precisely so that an edit made here
73890
- * survives every start until a newer literal arrives.
73891
- *
73892
- * So every write that changes a schema carries both, and the
73893
- * operator is never asked to remember either.
73894
- ***************************************************************/
73895
- function version_writes(gobj, topic) {
73896
- let treedb = current_treedb(gobj);
73897
- let writes = [];
73898
- if (topic) {
73899
- let version = Number(topic.topic_version);
73900
- if (!isFinite(version)) version = 0;
73901
- writes.push({
73902
- op: "update",
73903
- topic_name: T_TOPICS,
73904
- record: {
73905
- id: topic.id,
73906
- topic_version: version + 1
73907
- }
73908
- });
73909
- }
73910
- if (treedb) {
73911
- let version = Number(treedb.schema_version);
73912
- if (!isFinite(version)) version = 0;
73913
- writes.push({
73914
- op: "update",
73915
- topic_name: T_TREEDBS,
73916
- record: {
73917
- id: treedb.id,
73918
- schema_version: version + 1
73919
- }
73920
- });
73921
- }
73922
- return writes;
73923
- }
73924
- /***************************************************************
73925
73950
  * WHERE THE COLUMNS WERE before this session started moving them.
73926
73951
  *
73927
73952
  * A drag is a WRITE: the order is a field of the column, so the drop
@@ -74380,7 +74405,7 @@ function ac_save_column(gobj, event, kw, src) {
74380
74405
  op: kw.creating ? "create" : "update",
74381
74406
  topic_name: T_COLS,
74382
74407
  record: column_record(gobj, topic, col, values)
74383
- }].concat(version_writes(gobj, topic)));
74408
+ }]);
74384
74409
  }
74385
74410
  function ac_delete_column(gobj, event, kw, src) {
74386
74411
  let priv = gobj.priv;
@@ -74431,7 +74456,7 @@ function ac_move_column(gobj, event, kw, src) {
74431
74456
  };
74432
74457
  });
74433
74458
  if (writes.length === 0) return 0;
74434
- return queue_writes(gobj, writes.concat(version_writes(gobj, topic)));
74459
+ return queue_writes(gobj, writes);
74435
74460
  }
74436
74461
  /***************************************************************
74437
74462
  * Put the columns back where they were before the dragging
@@ -74451,7 +74476,7 @@ function ac_undo_order(gobj, event, kw, src) {
74451
74476
  return 0;
74452
74477
  }
74453
74478
  delete priv.order_undo[topic.id];
74454
- return queue_writes(gobj, writes.concat(version_writes(gobj, topic)));
74479
+ return queue_writes(gobj, writes);
74455
74480
  }
74456
74481
  /***************************************************************
74457
74482
  * Editing a topic.
@@ -74504,7 +74529,7 @@ function ac_save_topic(gobj, event, kw, src) {
74504
74529
  op: kw.creating ? "create" : "update",
74505
74530
  topic_name: T_TOPICS,
74506
74531
  record: topic_record(gobj, treedb, topic, values)
74507
- }].concat(version_writes(gobj, null)));
74532
+ }]);
74508
74533
  }
74509
74534
  function ac_delete_topic(gobj, event, kw, src) {
74510
74535
  let priv = gobj.priv;
@@ -74521,20 +74546,6 @@ function ac_delete_topic(gobj, event, kw, src) {
74521
74546
  return 0;
74522
74547
  }
74523
74548
  /***************************************************************
74524
- * Raising a version by hand: what the banner offers when
74525
- * something else wrote the topic and left its version alone.
74526
- ***************************************************************/
74527
- function ac_bump_version(gobj, event, kw, src) {
74528
- let priv = gobj.priv;
74529
- let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
74530
- if (refuse_if_readonly(gobj, event)) return -1;
74531
- if (!topic) {
74532
- log_error(`${gobj_short_name(gobj)}: ${event} names no topic: ${kw.topic}`);
74533
- return -1;
74534
- }
74535
- return queue_writes(gobj, version_writes(gobj, topic));
74536
- }
74537
- /***************************************************************
74538
74549
  * Deleting a leftover.
74539
74550
  ***************************************************************/
74540
74551
  function ac_delete_orphan(gobj, event, kw, src) {
@@ -74683,11 +74694,10 @@ function ac_confirmed(gobj, event, kw, src) {
74683
74694
  topic_name: T_TOPICS,
74684
74695
  record: { id: topic.id }
74685
74696
  });
74686
- return queue_writes(gobj, writes.concat(version_writes(gobj, null)));
74697
+ return queue_writes(gobj, writes);
74687
74698
  }
74688
74699
  if (kw.what === "column") {
74689
- let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
74690
- let col = topic ? find_col(priv.model, priv.treedb_id, kw.topic, kw.col) : null;
74700
+ let col = find_topic(priv.model, priv.treedb_id, kw.topic) ? find_col(priv.model, priv.treedb_id, kw.topic, kw.col) : null;
74691
74701
  if (!col) {
74692
74702
  log_error(`${gobj_short_name(gobj)}: ${event} names no column: ${kw.col}`);
74693
74703
  return -1;
@@ -74696,7 +74706,7 @@ function ac_confirmed(gobj, event, kw, src) {
74696
74706
  op: "delete",
74697
74707
  topic_name: T_COLS,
74698
74708
  record: { id: col.id }
74699
- }].concat(version_writes(gobj, topic)));
74709
+ }]);
74700
74710
  }
74701
74711
  log_error(`${gobj_short_name(gobj)}: ${event} of nothing this view asked about: ${kw.what}`);
74702
74712
  return -1;
@@ -74885,11 +74895,6 @@ function create_gclass$1(gclass_name) {
74885
74895
  "EV_UNDO_ORDER",
74886
74896
  ac_undo_order,
74887
74897
  null
74888
- ],
74889
- [
74890
- "EV_BUMP_VERSION",
74891
- ac_bump_version,
74892
- null
74893
74898
  ]
74894
74899
  ])],
74895
74900
  ["ST_SAVING", COMMON.slice()]
@@ -74918,7 +74923,6 @@ function create_gclass$1(gclass_name) {
74918
74923
  ["EV_SAVE_COLUMN", 0],
74919
74924
  ["EV_MOVE_COLUMN", 0],
74920
74925
  ["EV_UNDO_ORDER", 0],
74921
- ["EV_BUMP_VERSION", 0],
74922
74926
  ["EV_VALIDATE", 0],
74923
74927
  ["EV_EXPORT", 0],
74924
74928
  ["EV_IMPORT", 0],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.194",
3
+ "version": "7.23.196",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
package/src/c_yui_form.js CHANGED
@@ -83,6 +83,7 @@ import "tabulator-tables/dist/css/tabulator_bulma.css";
83
83
 
84
84
  import "./tabulator.css";
85
85
  import {is_time_field} from "./json_view_helpers.js";
86
+ import {date_to_datetime_local, datetime_local_to_epoch} from "./form_time_value.js";
86
87
  import { TabulatorFull as Tabulator } from "tabulator-tables"; // Import Full Tabulator JS
87
88
  // import { Tabulator } from "tabulator-tables"; // Import light Tabulator JS
88
89
 
@@ -1247,6 +1248,12 @@ function create_form_field(
1247
1248
  type: inputType,
1248
1249
  placeholder: placeholder
1249
1250
  };
1251
+ if(inputType === 'datetime-local') {
1252
+ /* A time column is an epoch in SECONDS: without `step: 1`
1253
+ * the input holds minutes, and a save wrote the seconds
1254
+ * away (see form_time_value.js). */
1255
+ attrs.step = 1;
1256
+ }
1250
1257
  Object.assign(attrs, extras);
1251
1258
 
1252
1259
  if(readonly) {
@@ -2321,10 +2328,7 @@ function get_form_values(gobj, $form)
2321
2328
  case "input":
2322
2329
  switch(input_type) {
2323
2330
  case "datetime-local":
2324
- // Create a new Date object from the datetime-local $input value
2325
- const date = new Date($input.value);
2326
- // Convert the Date object to epoch time (in seconds)
2327
- value = Math.floor(date.getTime() / 1000);
2331
+ value = datetime_local_to_epoch($input.value);
2328
2332
  break;
2329
2333
  default:
2330
2334
  value = getInputValue($input);
@@ -2588,18 +2592,8 @@ function set_form_values(gobj, template, $form, record)
2588
2592
  case "input":
2589
2593
  switch(input_type) {
2590
2594
  case "datetime-local":
2591
- /* value is a Date (see treedb_value_2_form_value);
2592
- * a datetime-local input wants local
2593
- * "YYYY-MM-DDTHH:mm" (no seconds). */
2594
- if(value instanceof Date && !isNaN(value.getTime())) {
2595
- let pad = (n) => String(n).padStart(2, "0");
2596
- $input.value =
2597
- `${value.getFullYear()}-${pad(value.getMonth() + 1)}` +
2598
- `-${pad(value.getDate())}T` +
2599
- `${pad(value.getHours())}:${pad(value.getMinutes())}`;
2600
- } else {
2601
- $input.value = "";
2602
- }
2595
+ /* value is a Date (see treedb_value_2_form_value) */
2596
+ $input.value = date_to_datetime_local(value);
2603
2597
  break;
2604
2598
  case "color":
2605
2599
  $input.value = convertColor(value);