@yuneta/gobj-ui 7.26.24 → 7.26.26

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.
@@ -5010,9 +5010,16 @@ function create_form_button_row(parent, buttons)
5010
5010
  {
5011
5011
  let btnRow = document.createElement('div');
5012
5012
  btnRow.style.cssText = 'display:flex;gap:8px;';
5013
- for(let {text, style, onClick} of buttons) {
5013
+ for(let {i18n, style, onClick} of buttons) {
5014
+ /* `i18n` is the key (named so that validate-locales sees a literal
5015
+ * one): the button carries it, and its name */
5014
5016
  let btn = document.createElement('button');
5015
- btn.textContent = t(text);
5017
+ btn.textContent = t(i18n);
5018
+ btn.setAttribute('data-i18n', i18n);
5019
+ btn.setAttribute('title', t(i18n));
5020
+ btn.setAttribute('data-i18n-title', i18n);
5021
+ btn.setAttribute('aria-label', t(i18n));
5022
+ btn.setAttribute('data-i18n-aria-label', i18n);
5016
5023
  btn.style.cssText = style + touch_control_css();
5017
5024
  btn.addEventListener('click', (e) => {
5018
5025
  e.stopPropagation();
@@ -5059,12 +5066,12 @@ function show_confirm_popover(gobj, target_el, message, confirm_text, confirm_co
5059
5066
 
5060
5067
  create_form_button_row(popover, [
5061
5068
  {
5062
- text: 'cancel',
5069
+ i18n: 'cancel',
5063
5070
  style: BTN_STYLE_CANCEL,
5064
5071
  onClick: () => hide_overlay(gobj, priv_key),
5065
5072
  },
5066
5073
  {
5067
- text: confirm_text,
5074
+ i18n: confirm_text,
5068
5075
  style: 'flex:1;padding:6px;background:' + confirm_color + ';color:#fff;border:none;' +
5069
5076
  'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
5070
5077
  onClick: onConfirm,
@@ -6148,14 +6155,14 @@ function show_port_popover(gobj)
6148
6155
 
6149
6156
  create_form_button_row(popover, [
6150
6157
  {
6151
- text: 'cancel',
6158
+ i18n: 'cancel',
6152
6159
  style: BTN_STYLE_CANCEL,
6153
6160
  onClick: () => {
6154
6161
  hide_port_popover(gobj); /* which undoes the preview */
6155
6162
  },
6156
6163
  },
6157
6164
  {
6158
- text: 'apply',
6165
+ i18n: 'apply',
6159
6166
  style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
6160
6167
  'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
6161
6168
  onClick: () => {
@@ -7100,14 +7107,14 @@ function show_edge_popover(gobj)
7100
7107
 
7101
7108
  create_form_button_row(popover, [
7102
7109
  {
7103
- text: 'cancel',
7110
+ i18n: 'cancel',
7104
7111
  style: BTN_STYLE_CANCEL,
7105
7112
  onClick: () => {
7106
7113
  deselect_edge(gobj); /* which undoes the preview */
7107
7114
  },
7108
7115
  },
7109
7116
  {
7110
- text: 'apply',
7117
+ i18n: 'apply',
7111
7118
  style: 'flex:1;padding:6px;background:#52c41a;color:#fff;border:none;' +
7112
7119
  'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
7113
7120
  onClick: () => {
@@ -7349,14 +7356,14 @@ function show_node_popover(gobj)
7349
7356
 
7350
7357
  create_form_button_row(popover, [
7351
7358
  {
7352
- text: 'cancel',
7359
+ i18n: 'cancel',
7353
7360
  style: BTN_STYLE_CANCEL,
7354
7361
  onClick: () => {
7355
7362
  hide_node_popover(gobj); /* which undoes the preview */
7356
7363
  },
7357
7364
  },
7358
7365
  {
7359
- text: 'apply',
7366
+ i18n: 'apply',
7360
7367
  style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
7361
7368
  'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
7362
7369
  onClick: () => {
@@ -8431,15 +8438,24 @@ function show_create_popover(gobj)
8431
8438
  let titleEl = document.createElement('div');
8432
8439
  titleEl.style.cssText = 'margin-bottom:10px;font-weight:600;font-size:14px;';
8433
8440
  titleEl.textContent = t('create node');
8441
+ titleEl.setAttribute('data-i18n', 'create node');
8434
8442
  popover.appendChild(titleEl);
8435
8443
 
8436
- // Topic selector
8444
+ // Topic selector: the word carries its key, the colon is not translated
8437
8445
  let topicLabel = document.createElement('div');
8438
8446
  topicLabel.style.cssText = 'margin-bottom:4px;font-weight:500;';
8439
- topicLabel.textContent = t('topic') + ':';
8447
+ let topicWord = document.createElement('span');
8448
+ topicWord.textContent = t('topic');
8449
+ topicWord.setAttribute('data-i18n', 'topic');
8450
+ topicLabel.appendChild(topicWord);
8451
+ topicLabel.appendChild(document.createTextNode(':'));
8440
8452
  popover.appendChild(topicLabel);
8441
8453
 
8442
8454
  let topicSelect = document.createElement('select');
8455
+ topicSelect.setAttribute('title', t('topic'));
8456
+ topicSelect.setAttribute('data-i18n-title', 'topic');
8457
+ topicSelect.setAttribute('aria-label', t('topic'));
8458
+ topicSelect.setAttribute('data-i18n-aria-label', 'topic');
8443
8459
  topicSelect.style.cssText =
8444
8460
  'width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;' +
8445
8461
  'font-size:13px;margin-bottom:8px;' + touch_control_css();
@@ -8483,6 +8499,11 @@ function show_create_popover(gobj)
8483
8499
  let idInput = document.createElement('input');
8484
8500
  idInput.type = 'text';
8485
8501
  idInput.placeholder = t('node id');
8502
+ idInput.setAttribute('data-i18n-placeholder', 'node id');
8503
+ idInput.setAttribute('title', t('node id'));
8504
+ idInput.setAttribute('data-i18n-title', 'node id');
8505
+ idInput.setAttribute('aria-label', t('node id'));
8506
+ idInput.setAttribute('data-i18n-aria-label', 'node id');
8486
8507
  idInput.style.cssText =
8487
8508
  'width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;' +
8488
8509
  'font-size:13px;margin-bottom:10px;box-sizing:border-box;' +
@@ -8499,18 +8520,19 @@ function show_create_popover(gobj)
8499
8520
  let createBtn; // need reference for Enter key handler
8500
8521
  create_form_button_row(popover, [
8501
8522
  {
8502
- text: 'cancel',
8523
+ i18n: 'cancel',
8503
8524
  style: BTN_STYLE_CANCEL,
8504
8525
  onClick: () => hide_create_popover(gobj),
8505
8526
  },
8506
8527
  {
8507
- text: 'create',
8528
+ i18n: 'create',
8508
8529
  style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
8509
8530
  'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
8510
8531
  onClick: () => {
8511
8532
  let node_id = idInput.value.trim();
8512
8533
  if(!node_id) {
8513
- errorEl.textContent = t('node id') + ' required';
8534
+ errorEl.textContent = t('node id required');
8535
+ errorEl.setAttribute('data-i18n', 'node id required');
8514
8536
  errorEl.style.display = 'block';
8515
8537
  idInput.focus();
8516
8538
  return;
@@ -2689,9 +2689,15 @@ function render_import_plan(gobj, plan)
2689
2689
  let where = conflict.col
2690
2690
  ? `${conflict.topic}.${conflict.col}`
2691
2691
  : conflict.topic;
2692
+ /* ...and, for a name of a hook map, the hook column it
2693
+ * is written in: where to fix it. */
2692
2694
  return ["p", {class: "SCHEMA_IMPORT_CONFLICT is-size-7"}, [
2693
2695
  ["span", {i18n: conflict.code}, t(conflict.code)],
2694
- where ? ["code", {class: "SCHEMA_IMPORT_CONFLICT_NAME ml-2"}, text_node(where)] : null
2696
+ where ? ["code", {class: "SCHEMA_IMPORT_CONFLICT_NAME ml-2"}, text_node(where)] : null,
2697
+ conflict.hook ? ["span", {class: "SCHEMA_IMPORT_CONFLICT_HOOK ml-2",
2698
+ i18n: "in the hook map of"}, t("in the hook map of")] : null,
2699
+ conflict.hook ? ["code", {class: "SCHEMA_IMPORT_CONFLICT_HOOK_NAME ml-1"},
2700
+ text_node(conflict.hook)] : null
2695
2701
  ].filter(Boolean)];
2696
2702
  })]
2697
2703
  ));
@@ -1056,6 +1056,39 @@ describe("the import plan", () => {
1056
1056
  });
1057
1057
  });
1058
1058
 
1059
+ describe("the import panel says what each conflict is about (REV-CLOUD V17.3.5)", () => {
1060
+
1061
+ test("the bad name of a hook map is printed, and the hook column it is written in", () => {
1062
+ const {editor, host} = build("i3", "db");
1063
+ gobj_send_event(editor, "EV_IMPORT", {}, host);
1064
+ const dialog = modals[modals.length - 1];
1065
+ gobj_send_event(editor, "EV_PREVIEW_IMPORT", {prune: false, model_gen: editor.priv.model_gen,
1066
+ text: JSON.stringify({id: "db", topics: [{id: "p", pkey: "id", cols: {
1067
+ "id": {id: "id", type: "string"},
1068
+ "kids": {id: "kids", type: "dict", flag: ["hook"], hook: {"a/b": "x`y"}},
1069
+ "good": {id: "go`od", type: "string"}
1070
+ }}]})}, editor);
1071
+
1072
+ const lines = [...dialog.$content.querySelectorAll(".SCHEMA_IMPORT_CONFLICT")];
1073
+ const said = lines.map((p) => {
1074
+ const name = p.querySelector(".SCHEMA_IMPORT_CONFLICT_NAME");
1075
+ const hook = p.querySelector(".SCHEMA_IMPORT_CONFLICT_HOOK_NAME");
1076
+ return `${p.querySelector("span").textContent}|${name ? name.textContent : ""}|${hook ? hook.textContent : ""}`;
1077
+ });
1078
+ expect(said).toContain("topic name breaks the name rule|a/b|p.kids");
1079
+ expect(said).toContain("column name breaks the name rule|a/b.x`y|p.kids");
1080
+ /* Not from a hook map: no hook column said */
1081
+ expect(said).toContain("column name breaks the name rule|p.go`od|");
1082
+ /* The names are DATA, text and not translated: no key on them */
1083
+ for(const code of dialog.$content.querySelectorAll(".SCHEMA_IMPORT_CONFLICT_NAME, .SCHEMA_IMPORT_CONFLICT_HOOK_NAME")) {
1084
+ expect(code.getAttribute("i18n")).toBe(null);
1085
+ expect(code.getAttribute("data-i18n")).toBe(null);
1086
+ }
1087
+ expect(dialog.$content.querySelector(".SCHEMA_IMPORT_RUN").disabled).toBe(true);
1088
+ expect(errors()).toEqual([]);
1089
+ });
1090
+ });
1091
+
1059
1092
  describe("a confirmation answered where no treedb is open", () => {
1060
1093
 
1061
1094
  test("after a reload that landed on zero treedbs: refused as stale, not NOT DEFINED", () => {
@@ -245,16 +245,20 @@ function plan_import(treedb, incoming, options)
245
245
  * the treedb refuses too, even when no column declares them. */
246
246
  let hook = (in_col.col || {}).hook;
247
247
  if(hook && typeof hook === "object" && !Array.isArray(hook)) {
248
+ /* The parent's hook column the bad name comes from: a
249
+ * conflict that said only `child.fkey` did not say where
250
+ * to fix it (REV-CLOUD V17.3.5). */
251
+ let from_hook = `${name || ""}.${in_col.name}`;
248
252
  for(let [child_topic, fkey_col] of Object.entries(hook)) {
249
253
  /* The conflict names the BAD name (the child topic, its
250
- * fkey), not the hook column that maps to it. */
254
+ * fkey), and `hook` the column that maps to it. */
251
255
  if(topic_name_breaks_the_rule(child_topic)) {
252
256
  conflicts.push({code: "topic name breaks the name rule",
253
- topic: child_topic, col: ""});
257
+ topic: child_topic, col: "", hook: from_hook});
254
258
  }
255
259
  if(col_name_breaks_the_rule(fkey_col)) {
256
260
  conflicts.push({code: "column name breaks the name rule",
257
- topic: child_topic, col: String(fkey_col)});
261
+ topic: child_topic, col: String(fkey_col), hook: from_hook});
258
262
  }
259
263
  }
260
264
  }
@@ -323,9 +323,9 @@ describe("a conflict names the bad name (REV-CLOUD V16.5)", () => {
323
323
  }}]
324
324
  });
325
325
  expect(plan.writes).toEqual([]);
326
- let named = plan.conflicts.map((c) => `${c.code}|${c.topic}|${c.col}`);
327
- expect(named).toContain("topic name breaks the name rule|a/b|");
328
- expect(named).toContain("column name breaks the name rule|a/b|x`y");
329
- expect(named).toContain("column name breaks the name rule|p|go`od");
326
+ let named = plan.conflicts.map((c) => `${c.code}|${c.topic}|${c.col}|${c.hook || ""}`);
327
+ expect(named).toContain("topic name breaks the name rule|a/b||p.kids");
328
+ expect(named).toContain("column name breaks the name rule|a/b|x`y|p.kids");
329
+ expect(named).toContain("column name breaks the name rule|p|go`od|");
330
330
  });
331
331
  });
@@ -328,11 +328,11 @@ function yui_file_control(gobj, {name, value, readonly, accept, on_pick})
328
328
  if(picked) {
329
329
  $state.classList.add("is-picked");
330
330
  $state.setAttribute("data-i18n", "not saved yet");
331
- $state.textContent = "not saved yet";
331
+ $state.textContent = t("not saved yet");
332
332
  } else if(!kept) {
333
333
  $state.classList.add("is-empty");
334
334
  $state.setAttribute("data-i18n", "no file");
335
- $state.textContent = "no file";
335
+ $state.textContent = t("no file");
336
336
  }
337
337
 
338
338
  $clear.hidden = readonly || (!picked && !kept);