@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.
- package/README.md +11 -10
- package/dist/gobj-ui.cjs.js +179 -175
- package/dist/gobj-ui.es.js +179 -175
- package/package.json +1 -1
- package/src/c_yui_form.js +10 -16
- package/src/c_yui_schema_editor.js +57 -122
- package/src/c_yui_treedb_topic_with_form.js +17 -15
- package/src/form_time_value.js +46 -0
- package/src/form_time_value.test.js +23 -0
- package/src/schema_validate.js +5 -17
- package/src/schema_validate.test.js +4 -24
- package/src/schema_version_bump.test.js +14 -71
- package/src/table_cell_text.js +53 -0
- package/src/table_cell_text.test.js +39 -0
package/dist/gobj-ui.es.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>[ <u>${items}</u> ]</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
|
-
|
|
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
|
-
})
|
|
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
|
|
70084
|
-
*
|
|
70085
|
-
*
|
|
70086
|
-
*
|
|
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
|
-
*
|
|
71429
|
-
*
|
|
71430
|
-
*
|
|
71431
|
-
*
|
|
71432
|
-
* the
|
|
71433
|
-
*
|
|
71434
|
-
*
|
|
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
|
|
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
|
|
71782
|
-
* list draws a chip with it
|
|
71783
|
-
* and
|
|
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
|
|
71786
|
-
if (!topic || !written
|
|
71787
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
72621
|
+
* "This topic has changes that are not saved."
|
|
72544
72622
|
*
|
|
72545
|
-
*
|
|
72546
|
-
*
|
|
72547
|
-
*
|
|
72548
|
-
*
|
|
72549
|
-
*
|
|
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
|
|
72629
|
+
function draft_banner(gobj, topic) {
|
|
72552
72630
|
let priv = gobj.priv;
|
|
72553
|
-
if (!
|
|
72631
|
+
if (!topic_is_draft(priv.written, topic)) return null;
|
|
72554
72632
|
if (is_readonly(gobj)) return null;
|
|
72555
|
-
|
|
72633
|
+
return createElement2([
|
|
72556
72634
|
"div",
|
|
72557
72635
|
{
|
|
72558
|
-
class: "
|
|
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
|
-
|
|
72564
|
-
|
|
72565
|
-
|
|
72566
|
-
|
|
72567
|
-
|
|
72568
|
-
"
|
|
72569
|
-
|
|
72570
|
-
|
|
72571
|
-
|
|
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
|
-
}]
|
|
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
|
|
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
|
|
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
|
-
}]
|
|
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
|
|
74697
|
+
return queue_writes(gobj, writes);
|
|
74687
74698
|
}
|
|
74688
74699
|
if (kw.what === "column") {
|
|
74689
|
-
let
|
|
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
|
-
}]
|
|
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
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
|
-
|
|
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
|
-
|
|
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);
|