@yuneta/gobj-ui 7.26.19 → 7.26.21

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.
@@ -13590,7 +13590,7 @@ function install_long_press_contextmenu(graph, options) {
13590
13590
  pending = {
13591
13591
  x,
13592
13592
  y,
13593
- t0: Date.now(),
13593
+ t0: performance.now(),
13594
13594
  event: Object.assign({}, event),
13595
13595
  tick: setTimeout(announce, 500)
13596
13596
  };
@@ -13616,7 +13616,7 @@ function install_long_press_contextmenu(graph, options) {
13616
13616
  let y = event.client?.y;
13617
13617
  if (x === void 0) x = press.x;
13618
13618
  if (y === void 0) y = press.y;
13619
- if (classify_press(press, x, y, Date.now()) !== "long") return;
13619
+ if (classify_press(press, x, y, performance.now()) !== "long") return;
13620
13620
  fire(press.event);
13621
13621
  };
13622
13622
  let on_touch_cancel = () => {
@@ -74386,6 +74386,16 @@ function mt_destroy$1(gobj) {
74386
74386
  /***************************
74387
74387
  * Local Methods
74388
74388
  ***************************/
74389
+ /************************************************************
74390
+ * THE name rule of timeranger2 and treedb
74391
+ * (tranger2_name_is_identifier(), SDK 7.26.9): a topic or a column
74392
+ * name is a directory and a key, so it cannot be "." or "..", nor hold
74393
+ * '/' or the path delimiter '`'. Said here, before the backend answers
74394
+ * a generic "cannot create topic".
74395
+ ************************************************************/
74396
+ function name_breaks_the_rule(name) {
74397
+ return name === "." || name === ".." || name.includes("/") || name.includes("`");
74398
+ }
74389
74399
  /***************************************************************
74390
74400
  * The frame: a toolbar that never changes place, a notice line
74391
74401
  * for what the whole view has to say, and one body the screens
@@ -77898,6 +77908,10 @@ function ac_save_column(gobj, event, kw, src) {
77898
77908
  yui_shell_show_error(yui_shell_of(gobj), "a column needs a name", { t: i18next.t });
77899
77909
  return -1;
77900
77910
  }
77911
+ if (kw.creating && name_breaks_the_rule(String(values.value).trim())) {
77912
+ yui_shell_show_error(yui_shell_of(gobj), "a name cannot be . or .., nor hold / or `", { t: i18next.t });
77913
+ return -1;
77914
+ }
77901
77915
  if (kw.creating && find_col(priv.model, priv.treedb_id, kw.topic, String(values.value).trim())) {
77902
77916
  yui_shell_show_error(yui_shell_of(gobj), "that column is already there", { t: i18next.t });
77903
77917
  return -1;
@@ -78152,6 +78166,10 @@ function ac_save_topic(gobj, event, kw, src) {
78152
78166
  yui_shell_show_error(yui_shell_of(gobj), "a topic needs a name", { t: i18next.t });
78153
78167
  return -1;
78154
78168
  }
78169
+ if (name_breaks_the_rule(name)) {
78170
+ yui_shell_show_error(yui_shell_of(gobj), "a name cannot be . or .., nor hold / or `", { t: i18next.t });
78171
+ return -1;
78172
+ }
78155
78173
  if (find_topic(priv.model, priv.treedb_id, name)) {
78156
78174
  yui_shell_show_error(yui_shell_of(gobj), "that topic is already there", { t: i18next.t });
78157
78175
  return -1;
@@ -13585,7 +13585,7 @@ function install_long_press_contextmenu(graph, options) {
13585
13585
  pending = {
13586
13586
  x,
13587
13587
  y,
13588
- t0: Date.now(),
13588
+ t0: performance.now(),
13589
13589
  event: Object.assign({}, event),
13590
13590
  tick: setTimeout(announce, 500)
13591
13591
  };
@@ -13611,7 +13611,7 @@ function install_long_press_contextmenu(graph, options) {
13611
13611
  let y = event.client?.y;
13612
13612
  if (x === void 0) x = press.x;
13613
13613
  if (y === void 0) y = press.y;
13614
- if (classify_press(press, x, y, Date.now()) !== "long") return;
13614
+ if (classify_press(press, x, y, performance.now()) !== "long") return;
13615
13615
  fire(press.event);
13616
13616
  };
13617
13617
  let on_touch_cancel = () => {
@@ -74381,6 +74381,16 @@ function mt_destroy$1(gobj) {
74381
74381
  /***************************
74382
74382
  * Local Methods
74383
74383
  ***************************/
74384
+ /************************************************************
74385
+ * THE name rule of timeranger2 and treedb
74386
+ * (tranger2_name_is_identifier(), SDK 7.26.9): a topic or a column
74387
+ * name is a directory and a key, so it cannot be "." or "..", nor hold
74388
+ * '/' or the path delimiter '`'. Said here, before the backend answers
74389
+ * a generic "cannot create topic".
74390
+ ************************************************************/
74391
+ function name_breaks_the_rule(name) {
74392
+ return name === "." || name === ".." || name.includes("/") || name.includes("`");
74393
+ }
74384
74394
  /***************************************************************
74385
74395
  * The frame: a toolbar that never changes place, a notice line
74386
74396
  * for what the whole view has to say, and one body the screens
@@ -77893,6 +77903,10 @@ function ac_save_column(gobj, event, kw, src) {
77893
77903
  yui_shell_show_error(yui_shell_of(gobj), "a column needs a name", { t });
77894
77904
  return -1;
77895
77905
  }
77906
+ if (kw.creating && name_breaks_the_rule(String(values.value).trim())) {
77907
+ yui_shell_show_error(yui_shell_of(gobj), "a name cannot be . or .., nor hold / or `", { t });
77908
+ return -1;
77909
+ }
77896
77910
  if (kw.creating && find_col(priv.model, priv.treedb_id, kw.topic, String(values.value).trim())) {
77897
77911
  yui_shell_show_error(yui_shell_of(gobj), "that column is already there", { t });
77898
77912
  return -1;
@@ -78147,6 +78161,10 @@ function ac_save_topic(gobj, event, kw, src) {
78147
78161
  yui_shell_show_error(yui_shell_of(gobj), "a topic needs a name", { t });
78148
78162
  return -1;
78149
78163
  }
78164
+ if (name_breaks_the_rule(name)) {
78165
+ yui_shell_show_error(yui_shell_of(gobj), "a name cannot be . or .., nor hold / or `", { t });
78166
+ return -1;
78167
+ }
78150
78168
  if (find_topic(priv.model, priv.treedb_id, name)) {
78151
78169
  yui_shell_show_error(yui_shell_of(gobj), "that topic is already there", { t });
78152
78170
  return -1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.26.19",
3
+ "version": "7.26.21",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -361,6 +361,18 @@ function mt_destroy(gobj)
361
361
 
362
362
 
363
363
 
364
+ /************************************************************
365
+ * THE name rule of timeranger2 and treedb
366
+ * (tranger2_name_is_identifier(), SDK 7.26.9): a topic or a column
367
+ * name is a directory and a key, so it cannot be "." or "..", nor hold
368
+ * '/' or the path delimiter '`'. Said here, before the backend answers
369
+ * a generic "cannot create topic".
370
+ ************************************************************/
371
+ function name_breaks_the_rule(name)
372
+ {
373
+ return name === "." || name === ".." || name.includes("/") || name.includes("`");
374
+ }
375
+
364
376
  /***************************************************************
365
377
  * The frame: a toolbar that never changes place, a notice line
366
378
  * for what the whole view has to say, and one body the screens
@@ -4026,6 +4038,10 @@ function ac_save_column(gobj, event, kw, src)
4026
4038
  yui_shell_show_error(yui_shell_of(gobj), "a column needs a name", {t: t});
4027
4039
  return -1;
4028
4040
  }
4041
+ if(kw.creating && name_breaks_the_rule(String(values.value).trim())) {
4042
+ yui_shell_show_error(yui_shell_of(gobj), "a name cannot be . or .., nor hold / or `", {t: t});
4043
+ return -1;
4044
+ }
4029
4045
  if(kw.creating && find_col(priv.model, priv.treedb_id, kw.topic, String(values.value).trim())) {
4030
4046
  yui_shell_show_error(yui_shell_of(gobj), "that column is already there", {t: t});
4031
4047
  return -1;
@@ -4333,6 +4349,10 @@ function ac_save_topic(gobj, event, kw, src)
4333
4349
  yui_shell_show_error(yui_shell_of(gobj), "a topic needs a name", {t: t});
4334
4350
  return -1;
4335
4351
  }
4352
+ if(name_breaks_the_rule(name)) {
4353
+ yui_shell_show_error(yui_shell_of(gobj), "a name cannot be . or .., nor hold / or `", {t: t});
4354
+ return -1;
4355
+ }
4336
4356
  if(find_topic(priv.model, priv.treedb_id, name)) {
4337
4357
  yui_shell_show_error(yui_shell_of(gobj), "that topic is already there", {t: t});
4338
4358
  return -1;
@@ -0,0 +1,51 @@
1
+ /***********************************************************************
2
+ * g6_long_press_clock.test.js
3
+ *
4
+ * The long press of the G6 graphs is measured on a MONOTONIC clock
5
+ * (yunovatios REV-CLOUD.md V11.4 / V12.4). press_arbiter.test.js
6
+ * injects the time into classify_press(), so it passes whatever
7
+ * clock the handler reads; this drives the handler itself, with the
8
+ * wall clock set back an hour in the middle of the press -- an NTP
9
+ * step -- while the monotonic one moves on as a finger does.
10
+ *
11
+ * Copyright (c) 2026, ArtGins.
12
+ * All Rights Reserved.
13
+ ***********************************************************************/
14
+ import {describe, test, expect, vi, afterEach} from "vitest";
15
+ import {install_dom_double} from "../test/dom_double.js";
16
+
17
+ install_dom_double();
18
+
19
+ const {CommonEvent} = await import("@antv/g6");
20
+ const {install_long_press_contextmenu} = await import("./g6_touch_gestures.js");
21
+ const {LONG_PRESS_MS} = await import("./press_arbiter.js");
22
+
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ });
26
+
27
+ describe("the long press of a G6 graph and the clocks", () => {
28
+ test("a wall clock set back mid-press does not turn a long press into a tap", () => {
29
+ const handlers = {};
30
+ const emitted = [];
31
+ const graph = {
32
+ on(name, fn) { handlers[name] = fn; },
33
+ off() {},
34
+ emit(name) { emitted.push(name); },
35
+ };
36
+ let mono = 1000;
37
+ let wall = 1_800_000_000_000;
38
+ vi.spyOn(performance, "now").mockImplementation(() => mono);
39
+ vi.spyOn(Date, "now").mockImplementation(() => wall);
40
+
41
+ install_long_press_contextmenu(graph, {});
42
+ handlers[CommonEvent.POINTER_DOWN]({
43
+ pointerType: "touch", client: {x: 10, y: 10}, targetType: "node"
44
+ });
45
+ mono += LONG_PRESS_MS + 100; /* the finger stays long enough */
46
+ wall -= 3600 * 1000; /* and the wall clock steps back */
47
+ handlers[CommonEvent.POINTER_UP]({client: {x: 10, y: 10}});
48
+
49
+ expect(emitted).toContain(CommonEvent.CONTEXT_MENU);
50
+ });
51
+ });
@@ -461,7 +461,8 @@ export function install_long_press_contextmenu(graph, options)
461
461
  pending = {
462
462
  x: x,
463
463
  y: y,
464
- t0: Date.now(),
464
+ /* A monotonic clock: the wall clock may step mid-press. */
465
+ t0: performance.now(),
465
466
  event: Object.assign({}, event),
466
467
  tick: setTimeout(announce, LONG_PRESS_MS),
467
468
  };
@@ -505,7 +506,7 @@ export function install_long_press_contextmenu(graph, options)
505
506
  if(y === undefined) {
506
507
  y = press.y;
507
508
  }
508
- if(classify_press(press, x, y, Date.now()) !== "long") {
509
+ if(classify_press(press, x, y, performance.now()) !== "long") {
509
510
  return; /* a drag, or a tap: both are already served */
510
511
  }
511
512
  fire(press.event);