@yuneta/gobj-js 7.26.2 → 7.26.4

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.
@@ -4004,6 +4004,12 @@ function set_own_key(obj, key, value) {
4004
4004
  * called gobj_short_name() on a null gobj ("gobj bad type"), and a
4005
4005
  * null middle segment threw a TypeError.
4006
4006
  ************************************************************/
4007
+ function list_segment_index(list, seg) {
4008
+ if (!/^[0-9]+$/.test(seg)) return -1;
4009
+ const idx = Number(seg);
4010
+ if (idx >= list.length) return -1;
4011
+ return idx;
4012
+ }
4007
4013
  function kw_find_path(gobj, kw, path, verbose) {
4008
4014
  if (gobj && !is_gobj(gobj)) log_error(`GObj bad instanceof`);
4009
4015
  const prefix = gobj ? `${gobj_short_name(gobj)}: ` : "";
@@ -4019,11 +4025,12 @@ function kw_find_path(gobj, kw, path, verbose) {
4019
4025
  let len = ss.length;
4020
4026
  for (let i = 0; i < len; i++) {
4021
4027
  if (is_array(kw)) {
4022
- if (!/^[0-9]+$/.test(ss[i])) {
4028
+ const idx = list_segment_index(kw, ss[i]);
4029
+ if (idx < 0) {
4023
4030
  if (verbose) log_error(`${prefix}path not found: '${path}'`);
4024
4031
  return;
4025
4032
  }
4026
- ss[i] = String(Number(ss[i]));
4033
+ ss[i] = String(idx);
4027
4034
  }
4028
4035
  if (is_prototype_segment(kw, ss[i])) {
4029
4036
  if (verbose) log_error(`${prefix}path not found: '${path}'`);
@@ -4058,8 +4065,14 @@ function strrchr_split(str, char) {
4058
4065
  function kw_delete(gobj, kw, path) {
4059
4066
  if (gobj && !is_gobj(gobj)) log_error(`GObj bad instanceof`);
4060
4067
  let [path_, k] = strrchr_split(path, "`");
4061
- if (!empty_string(k)) json_object_del(kw_find_path(gobj, kw, path_, true), k);
4062
- else json_object_del(kw, path);
4068
+ if (!empty_string(k)) {
4069
+ let v = kw_find_path(gobj, kw, path_, true);
4070
+ if (is_array(v)) {
4071
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}kw_delete(): cannot delete an item of a list: '${path}'`);
4072
+ return -1;
4073
+ }
4074
+ json_object_del(v, k);
4075
+ } else json_object_del(kw, path);
4063
4076
  return 0;
4064
4077
  }
4065
4078
  /************************************************************
@@ -4415,6 +4428,14 @@ function kw_set_dict_value(gobj, kw, path, value) {
4415
4428
  let len = ss.length;
4416
4429
  for (let i = 0; i < len; i++) {
4417
4430
  let key = ss[i];
4431
+ if (is_array(kw)) {
4432
+ const idx = list_segment_index(kw, key);
4433
+ if (idx < 0) {
4434
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}kw_set_dict_value(): segment '${key}' is not an index of its list: '${path}'`);
4435
+ return -1;
4436
+ }
4437
+ key = String(idx);
4438
+ }
4418
4439
  if (i === len - 1) set_own_key(kw, key, value);
4419
4440
  else {
4420
4441
  let seg = is_prototype_segment(kw, key) ? void 0 : kw[key];
@@ -5401,6 +5422,68 @@ function safeSrc(url) {
5401
5422
  /***************************************************************************
5402
5423
  *
5403
5424
  ***************************************************************************/
5425
+ /***************************************************************************
5426
+ * Markup that createElement2() inserts AS HTML, said out loud.
5427
+ *
5428
+ * createElement2() reads a string that starts with '<' as markup. That
5429
+ * decision depends on the CONTENT of the string, not on who wrote it, so
5430
+ * a value from a backend or typed by a user that starts with '<' runs as
5431
+ * html. html_markup() is the explicit way to ask for markup: the string
5432
+ * is the caller's own (an icon, an svg), never data.
5433
+ *
5434
+ * ['span', {class: 'icon'}, html_markup('<i class="yi-copy"></i>')]
5435
+ *
5436
+ * With set_strings_are_text(true) a plain string is ALWAYS text and
5437
+ * html_markup() is the only way to insert markup.
5438
+ ***************************************************************************/
5439
+ var HtmlMarkup = class {
5440
+ constructor(html) {
5441
+ this.html = html;
5442
+ Object.freeze(this);
5443
+ }
5444
+ toString() {
5445
+ return this.html;
5446
+ }
5447
+ };
5448
+ function html_markup(html) {
5449
+ if (typeof html !== "string") {
5450
+ log_error(`html_markup: not a string, type ${typeof html}`);
5451
+ return new HtmlMarkup("");
5452
+ }
5453
+ return new HtmlMarkup(html);
5454
+ }
5455
+ function is_html_markup(v) {
5456
+ return v instanceof HtmlMarkup;
5457
+ }
5458
+ function markup_fragment(markup) {
5459
+ const template = document.createElement("template");
5460
+ template.innerHTML = markup.html.trim();
5461
+ return template.content;
5462
+ }
5463
+ var __strings_are_text__ = false;
5464
+ var __markup_strings_warned__ = /* @__PURE__ */ new Set();
5465
+ /***************************************************************************
5466
+ * The mode of createElement2() for a plain string that starts with '<':
5467
+ * false (default): it is markup, as it always was.
5468
+ * true: it is TEXT; markup is asked for with html_markup().
5469
+ * An app turns it on once its own code and the libraries it mounts pass
5470
+ * every deliberate markup through html_markup(). A string that starts
5471
+ * with '<' is then logged once (a deliberate one not migrated yet, or a
5472
+ * datum that used to run as html).
5473
+ ***************************************************************************/
5474
+ function set_strings_are_text(on) {
5475
+ __strings_are_text__ = !!on;
5476
+ }
5477
+ function strings_are_text() {
5478
+ return __strings_are_text__;
5479
+ }
5480
+ function warn_markup_string(content) {
5481
+ if (__markup_strings_warned__.size >= 100) return;
5482
+ const key = content.slice(0, 80);
5483
+ if (__markup_strings_warned__.has(key)) return;
5484
+ __markup_strings_warned__.add(key);
5485
+ log_warning(`createElement2: a string that starts with '<' is TEXT (set_strings_are_text): use html_markup() if it is markup: ${JSON.stringify(key)}`);
5486
+ }
5404
5487
  function createOneHtml(htmlString) {
5405
5488
  const template = document.createElement("template");
5406
5489
  template.innerHTML = htmlString.trim();
@@ -5562,6 +5645,7 @@ parent: document.body
5562
5645
  * if string begins with '<' then
5563
5646
  * it will be converted to HTMLElement
5564
5647
  * content = createOneHtml(string)
5648
+ * | html_markup(string): markup, said out loud (any mode)
5565
5649
  * ⚠️ So a string is MARKUP when it starts with '<': never
5566
5650
  * pass DATA (a value from a backend, a name a user typed)
5567
5651
  * as a bare string, or "<img src=x onerror=...>" runs.
@@ -5571,6 +5655,8 @@ parent: document.body
5571
5655
  * appended as it is and a string is always text -- but a
5572
5656
  * list whose first item is a string is read as ONE
5573
5657
  * description (the string is its tag).
5658
+ * With set_strings_are_text(true) a plain string is
5659
+ * always text, and markup only comes in html_markup().
5574
5660
  * if attrs['i18n'] is not empty then (or 'data-i18n' or 'data_i18next')
5575
5661
  * it will be overridden with the translation
5576
5662
  * content = translate(attrs['i18n']));
@@ -5613,19 +5699,24 @@ function createElement2(description, translate_fn) {
5613
5699
  el.setAttribute("data-i18n", data_i18next);
5614
5700
  }
5615
5701
  } else el.setAttribute(attr, attrs[attr]);
5616
- if (is_string(content)) {
5702
+ if (is_html_markup(content)) el.appendChild(markup_fragment(content));
5703
+ else if (is_string(content)) {
5617
5704
  content = content.trim();
5618
- if (content.length > 0 && content[0] === "<") el.appendChild(createOneHtml(content));
5619
- else if (translate_fn && data_i18next) {
5620
- let translated = translate_fn(data_i18next);
5621
- el.textContent = translated ? translated : content;
5622
- } else el.textContent = content;
5705
+ if (content.length > 0 && content[0] === "<" && !__strings_are_text__) el.appendChild(createOneHtml(content));
5706
+ else {
5707
+ if (content.length > 0 && content[0] === "<") warn_markup_string(content);
5708
+ if (translate_fn && data_i18next) {
5709
+ let translated = translate_fn(data_i18next);
5710
+ el.textContent = translated ? translated : content;
5711
+ } else el.textContent = content;
5712
+ }
5623
5713
  } else if (is_array(content)) {
5624
5714
  if (content.length > 0) {
5625
5715
  if (is_string(content[0])) el.appendChild(createElement2(content, translate_fn));
5626
5716
  else for (let child of content) if (Array.isArray(child)) {
5627
5717
  if (child.length > 0) el.appendChild(createElement2(child, translate_fn));
5628
5718
  } else if (child instanceof Node) el.appendChild(child);
5719
+ else if (is_html_markup(child)) el.appendChild(markup_fragment(child));
5629
5720
  else if (is_string(child)) el.appendChild(document.createTextNode(child));
5630
5721
  else if (child !== null && child !== void 0 && child !== false) log_error(`createElement2: child ignored, type ${typeof child}`);
5631
5722
  }
@@ -7231,13 +7322,13 @@ function setup_websocket(gobj) {
7231
7322
  websocket = new WebSocket(url);
7232
7323
  } catch (e) {
7233
7324
  log_error(`${gobj_short_name(gobj)}: Cannot open WebSocket to '${url}', Error: ${e.message}`);
7234
- gobj_post_event(gobj, "EV_ON_CLOSE", {
7325
+ if (gobj_post_event(gobj, "EV_ON_CLOSE", {
7235
7326
  url,
7236
7327
  ws_seq,
7237
7328
  code: 1006,
7238
7329
  reason: e.message,
7239
7330
  was_clean: false
7240
- }, gobj);
7331
+ }, gobj) < 0) set_timeout(gobj.priv.gobj_timer, next_retry(gobj));
7241
7332
  return null;
7242
7333
  }
7243
7334
  if (!websocket) {
@@ -7976,6 +8067,7 @@ exports.gobj_yuno = gobj_yuno;
7976
8067
  exports.gobj_yuno_id = gobj_yuno_id;
7977
8068
  exports.gobj_yuno_name = gobj_yuno_name;
7978
8069
  exports.gobj_yuno_role = gobj_yuno_role;
8070
+ exports.html_markup = html_markup;
7979
8071
  exports.id_index_in_obj_list = id_index_in_obj_list;
7980
8072
  exports.index_in_list = index_in_list;
7981
8073
  exports.is_array = is_array;
@@ -7983,6 +8075,7 @@ exports.is_boolean = is_boolean;
7983
8075
  exports.is_date = is_date;
7984
8076
  exports.is_function = is_function;
7985
8077
  exports.is_gobj = is_gobj;
8078
+ exports.is_html_markup = is_html_markup;
7986
8079
  exports.is_metadata_key = is_metadata_key;
7987
8080
  exports.is_null = is_null;
7988
8081
  exports.is_number = is_number;
@@ -8074,6 +8167,7 @@ exports.set_console_log_enabled = set_console_log_enabled;
8074
8167
  exports.set_console_log_filter = set_console_log_filter;
8075
8168
  exports.set_log_callback = set_log_callback;
8076
8169
  exports.set_remote_log_functions = set_remote_log_functions;
8170
+ exports.set_strings_are_text = set_strings_are_text;
8077
8171
  exports.set_timeout = set_timeout;
8078
8172
  exports.set_timeout_periodic = set_timeout_periodic;
8079
8173
  exports.sprintf = sprintf;
@@ -8081,6 +8175,7 @@ exports.stats_parser = stats_parser;
8081
8175
  exports.str_in_list = str_in_list;
8082
8176
  exports.strcasecmp = strcasecmp;
8083
8177
  exports.strcmp = strcmp;
8178
+ exports.strings_are_text = strings_are_text;
8084
8179
  exports.strncmp = strncmp;
8085
8180
  exports.strs_in_list = strs_in_list;
8086
8181
  exports.strstr = strstr;