wunderbaum 0.0.1-0 → 0.0.3

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.
@@ -0,0 +1,169 @@
1
+ /*!
2
+ * Wunderbaum - drag_observer
3
+ * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4
+ * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
+ */
6
+ import * as util from "./util";
7
+
8
+ export type DragCallbackArgType = {
9
+ /** "dragstart", "drag", or "dragstop". */
10
+ type: string;
11
+ /** Original mouse or touch event that triggered the drag event. */
12
+ event: MouseEvent | TouchEvent;
13
+ /** Element which is currently dragged. */
14
+ dragElem: HTMLElement | null;
15
+ /** Relative horizontal drag distance since start. */
16
+ dx: number;
17
+ /** Relative vertical drag distance since start. */
18
+ dy: number;
19
+ /** False if drag was canceled. */
20
+ apply?: boolean;
21
+ };
22
+ export type DragCallbackType = (e: DragCallbackArgType) => boolean | void;
23
+ type DragObserverOptionsType = {
24
+ /**Event target (typically `window.document`). */
25
+ root: EventTarget;
26
+ /**Event delegation selector.*/
27
+ selector?: string;
28
+ /**Minimum drag distance in px. */
29
+ thresh?: number;
30
+ /**Return `false` to cancel drag. */
31
+ dragstart: DragCallbackType;
32
+ drag?: DragCallbackType;
33
+ dragstop?: DragCallbackType;
34
+ };
35
+
36
+ /**
37
+ * Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
38
+ */
39
+ export class DragObserver {
40
+ protected _handler;
41
+ protected root: EventTarget;
42
+ protected start = {
43
+ x: 0,
44
+ y: 0,
45
+ altKey: false,
46
+ ctrlKey: false,
47
+ metaKey: false,
48
+ shiftKey: false,
49
+ };
50
+ protected dragElem: HTMLElement | null = null;
51
+ protected dragging: boolean = false;
52
+ // TODO: touch events
53
+ protected events = ["mousedown", "mouseup", "mousemove", "keydown"];
54
+ protected opts: DragObserverOptionsType;
55
+
56
+ constructor(opts: DragObserverOptionsType) {
57
+ util.assert(opts.root);
58
+ this.opts = util.extend({ thresh: 5 }, opts);
59
+ this.root = opts.root;
60
+ this._handler = this.handleEvent.bind(this) as EventListener;
61
+ this.events.forEach((type) => {
62
+ this.root.addEventListener(type, this._handler);
63
+ });
64
+ }
65
+ /** Unregister all event listeners. */
66
+ disconnect() {
67
+ this.events.forEach((type) => {
68
+ this.root.removeEventListener(type, this._handler);
69
+ });
70
+ }
71
+
72
+ public getDragElem(): HTMLElement | null {
73
+ return this.dragElem;
74
+ }
75
+
76
+ public isDragging(): boolean {
77
+ return this.dragging;
78
+ }
79
+
80
+ public stopDrag(cb_event?: DragCallbackArgType): void {
81
+ if (this.dragging && this.opts.dragstop && cb_event) {
82
+ cb_event.type = "dragstop";
83
+ this.opts.dragstop(cb_event);
84
+ }
85
+ this.dragElem = null;
86
+ this.dragging = false;
87
+ }
88
+
89
+ protected handleEvent(e: MouseEvent): boolean | void {
90
+ const type = e.type;
91
+ const opts = this.opts;
92
+ const cb_event: DragCallbackArgType = {
93
+ type: e.type,
94
+ event: e,
95
+ dragElem: this.dragElem,
96
+ dx: e.pageX - this.start.x,
97
+ dy: e.pageY - this.start.y,
98
+ apply: undefined,
99
+ };
100
+ switch (type) {
101
+ case "keydown":
102
+ this.stopDrag(cb_event);
103
+ break;
104
+ case "mousedown":
105
+ if (this.dragElem) {
106
+ this.stopDrag(cb_event);
107
+ break;
108
+ }
109
+ if (opts.selector) {
110
+ let elem = e.target as HTMLElement;
111
+ if (elem.matches(opts.selector)) {
112
+ this.dragElem = elem;
113
+ } else {
114
+ elem = elem.closest(opts.selector) as HTMLElement;
115
+ if (elem) {
116
+ this.dragElem = elem;
117
+ } else {
118
+ break; // no event delegation selector matched
119
+ }
120
+ }
121
+ }
122
+ this.start.x = e.pageX;
123
+ this.start.y = e.pageY;
124
+ this.start.altKey = e.altKey;
125
+ this.start.ctrlKey = e.ctrlKey;
126
+ this.start.metaKey = e.metaKey;
127
+ this.start.shiftKey = e.shiftKey;
128
+ break;
129
+
130
+ case "mousemove":
131
+ // TODO: debounce/throttle?
132
+ // TODO: horizontal mode: ignore if dx unchanged
133
+ if (!this.dragElem) {
134
+ break;
135
+ }
136
+ if (!this.dragging) {
137
+ if (opts.thresh) {
138
+ const dist2 = cb_event.dx * cb_event.dx + cb_event.dy * cb_event.dy;
139
+ if (dist2 < opts.thresh * opts.thresh) {
140
+ break;
141
+ }
142
+ }
143
+ cb_event.type = "dragstart";
144
+ if (opts.dragstart(cb_event) === false) {
145
+ this.stopDrag(cb_event);
146
+ break;
147
+ }
148
+ this.dragging = true;
149
+ }
150
+ if (this.dragging && this.opts.drag) {
151
+ cb_event.type = "drag";
152
+ this.opts.drag(cb_event);
153
+ }
154
+ break;
155
+ case "mouseup":
156
+ if (!this.dragging) {
157
+ this.stopDrag(cb_event);
158
+ break;
159
+ }
160
+ if (e.button === 0) {
161
+ cb_event.apply = true;
162
+ } else {
163
+ cb_event.apply = false;
164
+ }
165
+ this.stopDrag(cb_event);
166
+ break;
167
+ }
168
+ }
169
+ }
package/src/util.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
4
4
  * @VERSION, @DATE (https://github.com/mar10/wunderbaum)
5
5
  */
6
+ /** @module util */
6
7
 
7
8
  /** Readable names for `MouseEvent.button` */
8
9
  export const MOUSE_BUTTONS: { [key: number]: string } = {
@@ -16,7 +17,7 @@ export const MOUSE_BUTTONS: { [key: number]: string } = {
16
17
 
17
18
  export const MAX_INT = 9007199254740991;
18
19
  const userInfo = _getUserInfo();
19
- /**True if the user is using a macOS platform. */
20
+ /**True if the client is using a macOS platform. */
20
21
  export const isMac = userInfo.isMac;
21
22
 
22
23
  const REX_HTML = /[&<>"'/]/g; // Escape those characters
@@ -186,7 +187,7 @@ export function escapeTooltip(s: string): string {
186
187
  /** TODO */
187
188
  export function extractHtmlText(s: string) {
188
189
  if (s.indexOf(">") >= 0) {
189
- error("not implemented");
190
+ error("Not implemented");
190
191
  // return $("<div/>").html(s).text();
191
192
  }
192
193
  return s;
@@ -315,7 +316,7 @@ export function setValueToElem(elem: HTMLElement, value: any): void {
315
316
  input.valueAsNumber = value;
316
317
  break;
317
318
  case "radio":
318
- assert(false, "not implemented");
319
+ error("Not implemented");
319
320
  // const name = input.name;
320
321
  // const checked = input.parentElement!.querySelector(
321
322
  // `input[name="${name}"]:checked`
@@ -338,7 +339,7 @@ export function setValueToElem(elem: HTMLElement, value: any): void {
338
339
  // return value;
339
340
  }
340
341
 
341
- /** Return an unconnected `HTMLElement` from a HTML string. */
342
+ /** Create and return an unconnected `HTMLElement` from a HTML string. */
342
343
  export function elemFromHtml(html: string): HTMLElement {
343
344
  const t = document.createElement("template");
344
345
  t.innerHTML = html.trim();
@@ -358,11 +359,26 @@ export function elemFromSelector(obj: string | Element): HTMLElement | null {
358
359
  return obj as HTMLElement;
359
360
  }
360
361
 
362
+ /** Return a EventTarget from selector or cast an existing element. */
363
+ export function eventTargetFromSelector(
364
+ obj: string | EventTarget
365
+ ): EventTarget | null {
366
+ if (!obj) {
367
+ return null;
368
+ }
369
+ if (typeof obj === "string") {
370
+ return document.querySelector(obj) as EventTarget;
371
+ }
372
+ return obj as EventTarget;
373
+ }
374
+
361
375
  /**
362
- * Return a descriptive string for a keyboard or mouse event.
376
+ * Return a canonical descriptive string for a keyboard or mouse event.
363
377
  *
364
378
  * The result also contains a prefix for modifiers if any, for example
365
379
  * `"x"`, `"F2"`, `"Control+Home"`, or `"Shift+clickright"`.
380
+ * This is especially useful in `switch` statements, to make sure that modifier
381
+ * keys are considered and handled correctly.
366
382
  */
367
383
  export function eventToString(event: Event): string {
368
384
  let key = (<KeyboardEvent>event).key,
@@ -397,6 +413,13 @@ export function eventToString(event: Event): string {
397
413
  return s.join("+");
398
414
  }
399
415
 
416
+ /**
417
+ * Copy allproperties from one or more source objects to a target object.
418
+ *
419
+ * @returns the modified target object.
420
+ */
421
+ // TODO: use Object.assign()? --> https://stackoverflow.com/a/42740894
422
+ // TODO: support deep merge --> https://stackoverflow.com/a/42740894
400
423
  export function extend(...args: any[]) {
401
424
  for (let i = 1; i < args.length; i++) {
402
425
  let arg = args[i];
@@ -412,18 +435,22 @@ export function extend(...args: any[]) {
412
435
  return args[0];
413
436
  }
414
437
 
438
+ /** Return true if `obj` is of type `array`. */
415
439
  export function isArray(obj: any) {
416
440
  return Array.isArray(obj);
417
441
  }
418
442
 
443
+ /** Return true if `obj` is of type `Object` and has no propertied. */
419
444
  export function isEmptyObject(obj: any) {
420
445
  return Object.keys(obj).length === 0 && obj.constructor === Object;
421
446
  }
422
447
 
448
+ /** Return true if `obj` is of type `function`. */
423
449
  export function isFunction(obj: any) {
424
450
  return typeof obj === "function";
425
451
  }
426
452
 
453
+ /** Return true if `obj` is of type `Object`. */
427
454
  export function isPlainObject(obj: any) {
428
455
  return Object.prototype.toString.call(obj) === "[object Object]";
429
456
  }
@@ -432,14 +459,14 @@ export function isPlainObject(obj: any) {
432
459
  export function noop(...args: any[]): any {}
433
460
 
434
461
  /**
435
- * Bind one or more event handlers directly to an [[HtmlElement]].
462
+ * Bind one or more event handlers directly to an [[EventTarget]].
436
463
  *
437
- * @param element HTMLElement or selector
464
+ * @param element EventTarget or selector
438
465
  * @param eventNames
439
466
  * @param handler
440
467
  */
441
468
  export function onEvent(
442
- rootElem: HTMLElement | string,
469
+ rootTarget: EventTarget | string,
443
470
  eventNames: string,
444
471
  handler: EventCallbackType
445
472
  ): void;
@@ -455,26 +482,26 @@ export function onEvent(
455
482
  * });
456
483
  * ```
457
484
  *
458
- * @param element HTMLElement or selector
485
+ * @param element EventTarget or selector
459
486
  * @param eventNames
460
487
  * @param selector
461
488
  * @param handler
462
489
  */
463
490
  export function onEvent(
464
- rootElem: HTMLElement | string,
491
+ rootTarget: EventTarget | string,
465
492
  eventNames: string,
466
493
  selector: string,
467
494
  handler: EventCallbackType
468
495
  ): void;
469
496
 
470
497
  export function onEvent(
471
- rootElem: HTMLElement | string,
498
+ rootTarget: EventTarget | string,
472
499
  eventNames: string,
473
500
  selectorOrHandler: string | EventCallbackType,
474
501
  handlerOrNone?: EventCallbackType
475
502
  ): void {
476
503
  let selector: string | null, handler: EventCallbackType;
477
- rootElem = elemFromSelector(rootElem)!;
504
+ rootTarget = eventTargetFromSelector(rootTarget)!;
478
505
 
479
506
  if (handlerOrNone) {
480
507
  selector = selectorOrHandler as string;
@@ -485,7 +512,7 @@ export function onEvent(
485
512
  }
486
513
 
487
514
  eventNames.split(" ").forEach((evn) => {
488
- (<HTMLElement>rootElem).addEventListener(evn, function (e) {
515
+ (<EventTarget>rootTarget).addEventListener(evn, function (e) {
489
516
  if (!selector) {
490
517
  return handler!(e); // no event delegation
491
518
  } else if (e.target) {
@@ -578,6 +605,13 @@ export async function sleep(ms: number) {
578
605
 
579
606
  /**
580
607
  * Set or rotate checkbox status with support for tri-state.
608
+ *
609
+ * An initial 'indeterminate' state becomes 'checked' on the first call.
610
+ *
611
+ * If the input element has the class 'wb-tristate' assigned, the sequence is:<br>
612
+ * 'indeterminate' -> 'checked' -> 'unchecked' -> 'indeterminate' -> ...<br>
613
+ * Otherwise we toggle like <br>
614
+ * 'checked' -> 'unchecked' -> 'checked' -> ...
581
615
  */
582
616
  export function toggleCheckbox(
583
617
  element: HTMLElement | string,
@@ -648,6 +682,7 @@ export function toSet(val: any): Set<string> {
648
682
  throw new Error("Cannot convert to Set<string>: " + val);
649
683
  }
650
684
 
685
+ /**Return a canonical string representation for an object's type (e.g. 'array', 'number', ...) */
651
686
  export function type(obj: any): string {
652
687
  return Object.prototype.toString
653
688
  .call(obj)
package/src/wb_ext_dnd.ts CHANGED
@@ -8,9 +8,9 @@ import { EventCallbackType, onEvent } from "./util";
8
8
  import { Wunderbaum } from "./wunderbaum";
9
9
  import { WunderbaumExtension } from "./wb_extension_base";
10
10
  import { WunderbaumNode } from "./wb_node";
11
- import { ROW_HEIGHT } from "./common";
11
+ import { ROW_HEIGHT, WbNodeEventType } from "./common";
12
12
 
13
- const nodeMimeType = "application/x-fancytree-node";
13
+ const nodeMimeType = "application/x-wunderbaum-node";
14
14
  export type DropRegionType = "over" | "before" | "after";
15
15
  type DropRegionTypeSet = Set<DropRegionType>;
16
16
  // type AllowedDropRegionType =
@@ -24,6 +24,147 @@ type DropRegionTypeSet = Set<DropRegionType>;
24
24
  // | "none" // == false == "" == null
25
25
  // | "over"; // == "child"
26
26
 
27
+ export type DndOptionsType = {
28
+ /**
29
+ * Expand nodes after n milliseconds of hovering
30
+ * Default: 1500
31
+ */
32
+ autoExpandMS: 1500;
33
+ // /**
34
+ // * Additional offset for drop-marker with hitMode = "before"/"after"
35
+ // * Default:
36
+ // */
37
+ // dropMarkerInsertOffsetX: -16;
38
+ // /**
39
+ // * Absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
40
+ // * Default:
41
+ // */
42
+ // dropMarkerOffsetX: -24;
43
+ // /**
44
+ // * Root Container used for drop marker (could be a shadow root)
45
+ // * (#1021 `document.body` is not available yet)
46
+ // * Default:
47
+ // */
48
+ // dropMarkerParent: "body";
49
+ /**
50
+ * true: Drag multiple (i.e. selected) nodes. Also a callback() is allowed
51
+ * Default: false
52
+ */
53
+ multiSource: false;
54
+ /**
55
+ * Restrict the possible cursor shapes and modifier operations (can also be set in the dragStart event)
56
+ * Default: "all"
57
+ */
58
+ effectAllowed: "all";
59
+ // /**
60
+ // * 'copy'|'link'|'move'|'auto'(calculate from `effectAllowed`+modifier keys) or callback(node, data) that returns such string.
61
+ // * Default:
62
+ // */
63
+ // dropEffect: "auto";
64
+ /**
65
+ * Default dropEffect ('copy', 'link', or 'move') when no modifier is pressed (overide in dragDrag, dragOver).
66
+ * Default: "move"
67
+ */
68
+ dropEffectDefault: string;
69
+ /**
70
+ * Prevent dropping nodes from different Wunderbaum trees
71
+ * Default: false
72
+ */
73
+ preventForeignNodes: boolean;
74
+ /**
75
+ * Prevent dropping items on unloaded lazy Wunderbaum tree nodes
76
+ * Default: true
77
+ */
78
+ preventLazyParents: boolean;
79
+ /**
80
+ * Prevent dropping items other than Wunderbaum tree nodes
81
+ * Default: false
82
+ */
83
+ preventNonNodes: boolean;
84
+ /**
85
+ * Prevent dropping nodes on own descendants
86
+ * Default: true
87
+ */
88
+ preventRecursion: boolean;
89
+ /**
90
+ * Prevent dropping nodes under same direct parent
91
+ * Default: false
92
+ */
93
+ preventSameParent: false;
94
+ /**
95
+ * Prevent dropping nodes 'before self', etc. (move only)
96
+ * Default: true
97
+ */
98
+ preventVoidMoves: boolean;
99
+ /**
100
+ * Enable auto-scrolling while dragging
101
+ * Default: true
102
+ */
103
+ scroll: boolean;
104
+ /**
105
+ * Active top/bottom margin in pixel
106
+ * Default: 20
107
+ */
108
+ scrollSensitivity: 20;
109
+ /**
110
+ * Pixel per event
111
+ * Default: 5
112
+ */
113
+ scrollSpeed: 5;
114
+ // /**
115
+ // * Allow dragging of nodes to different IE windows
116
+ // * Default: false
117
+ // */
118
+ // setTextTypeJson: boolean;
119
+ /**
120
+ * Optional callback passed to `toDict` on dragStart @since 2.38
121
+ * Default: null
122
+ */
123
+ sourceCopyHook: null;
124
+ // Events (drag support)
125
+ /**
126
+ * Callback(sourceNode, data), return true, to enable dnd drag
127
+ * Default: null
128
+ */
129
+ dragStart?: WbNodeEventType;
130
+ /**
131
+ * Callback(sourceNode, data)
132
+ * Default: null
133
+ */
134
+ dragDrag: null;
135
+ /**
136
+ * Callback(sourceNode, data)
137
+ * Default: null
138
+ */
139
+ dragEnd: null;
140
+ // Events (drop support)
141
+ /**
142
+ * Callback(targetNode, data), return true, to enable dnd drop
143
+ * Default: null
144
+ */
145
+ dragEnter: null;
146
+ /**
147
+ * Callback(targetNode, data)
148
+ * Default: null
149
+ */
150
+ dragOver: null;
151
+ /**
152
+ * Callback(targetNode, data), return false to prevent autoExpand
153
+ * Default: null
154
+ */
155
+ dragExpand: null;
156
+ /**
157
+ * Callback(targetNode, data)
158
+ * Default: null
159
+ */
160
+ dragDrop: null;
161
+ /**
162
+ * Callback(targetNode, data)
163
+ * Default: null
164
+ */
165
+ dragLeave: null;
166
+ };
167
+
27
168
  export class DndExtension extends WunderbaumExtension {
28
169
  // public dropMarkerElem?: HTMLElement;
29
170
  protected srcNode: WunderbaumNode | null = null;
@@ -78,7 +219,7 @@ export class DndExtension extends WunderbaumExtension {
78
219
  // this.$scrollParent = $temp.scrollParent();
79
220
  // $temp.remove();
80
221
  const tree = this.tree;
81
- const dndOpts = tree.options.dnd;
222
+ const dndOpts = tree.options.dnd!;
82
223
 
83
224
  // Enable drag support if dragStart() is specified:
84
225
  if (dndOpts.dragStart) {
@@ -153,7 +294,7 @@ export class DndExtension extends WunderbaumExtension {
153
294
  /* Implement auto scrolling when drag cursor is in top/bottom area of scroll parent. */
154
295
  protected autoScroll(event: DragEvent): number {
155
296
  let tree = this.tree,
156
- dndOpts = tree.options.dnd,
297
+ dndOpts = tree.options.dnd!,
157
298
  sp = tree.scrollContainer,
158
299
  sensitivity = dndOpts.scrollSensitivity,
159
300
  speed = dndOpts.scrollSpeed,
@@ -162,7 +162,7 @@ export class EditExtension extends WunderbaumExtension {
162
162
  break;
163
163
  case "F2":
164
164
  if (trigger.indexOf("F2") >= 0) {
165
- // tree.setCellMode(NavigationMode.cellEdit);
165
+ // tree.setNavigationMode(NavigationMode.cellEdit);
166
166
  this.startEditTitle();
167
167
  return false;
168
168
  }
@@ -207,6 +207,7 @@ export class EditExtension extends WunderbaumExtension {
207
207
  if (validity) {
208
208
  // Permanently apply input validations (CSS and tooltip)
209
209
  inputElem.addEventListener("keydown", (e) => {
210
+ inputElem.setCustomValidity("");
210
211
  if (!inputElem.reportValidity()) {
211
212
  // node?.logInfo(`Invalid input: '${inputElem.value}'`);
212
213
  }
@@ -252,6 +253,14 @@ export class EditExtension extends WunderbaumExtension {
252
253
  node.logDebug(`stopEditTitle(${apply})`, opts, focusElem, newValue);
253
254
 
254
255
  if (apply && newValue !== null && newValue !== node.title) {
256
+ const errMsg = focusElem.validationMessage;
257
+ if (errMsg) {
258
+ // input element's native validation failed
259
+ throw new Error(
260
+ `Input validation failed for "${newValue}": ${errMsg}.`
261
+ );
262
+ }
263
+
255
264
  const colElem = node.getColElem(0)!;
256
265
 
257
266
  this._applyChange("edit.apply", node, colElem, {
@@ -9,7 +9,6 @@ import {
9
9
  escapeHtml,
10
10
  escapeRegex,
11
11
  extend,
12
- extractHtmlText,
13
12
  onEvent,
14
13
  } from "./util";
15
14
  import { NodeFilterCallback, NodeStatusType } from "./common";
@@ -79,7 +78,7 @@ export class FilterExtension extends WunderbaumExtension {
79
78
  count = 0,
80
79
  tree = this.tree,
81
80
  treeOpts = tree.options,
82
- escapeTitles = treeOpts.escapeTitles,
81
+ // escapeTitles = treeOpts.escapeTitles,
83
82
  prevAutoCollapse = treeOpts.autoCollapse,
84
83
  opts = extend({}, treeOpts.filter, _opts),
85
84
  hideMode = opts.mode === "hide",
@@ -118,35 +117,36 @@ export class FilterExtension extends WunderbaumExtension {
118
117
  if (!node.title) {
119
118
  return false;
120
119
  }
121
- let text = escapeTitles ? node.title : extractHtmlText(node.title);
120
+ // let text = escapeTitles ? node.title : extractHtmlText(node.title);
121
+ let text = node.title;
122
122
  // `.match` instead of `.test` to get the capture groups
123
123
  let res = text.match(re);
124
124
 
125
125
  if (res && opts.highlight) {
126
- if (escapeTitles) {
127
- if (opts.fuzzy) {
128
- temp = _markFuzzyMatchedChars(text, res, escapeTitles);
129
- } else {
130
- // #740: we must not apply the marks to escaped entity names, e.g. `&quot;`
131
- // Use some exotic characters to mark matches:
132
- temp = text.replace(reHighlight, function (s) {
133
- return START_MARKER + s + END_MARKER;
134
- });
135
- }
136
- // now we can escape the title...
137
- node.titleWithHighlight = escapeHtml(temp)
138
- // ... and finally insert the desired `<mark>` tags
139
- .replace(RE_START_MARKER, "<mark>")
140
- .replace(RE_END_MARTKER, "</mark>");
126
+ // if (escapeTitles) {
127
+ if (opts.fuzzy) {
128
+ temp = _markFuzzyMatchedChars(text, res, true);
141
129
  } else {
142
- if (opts.fuzzy) {
143
- node.titleWithHighlight = _markFuzzyMatchedChars(text, res);
144
- } else {
145
- node.titleWithHighlight = text.replace(reHighlight, function (s) {
146
- return "<mark>" + s + "</mark>";
147
- });
148
- }
130
+ // #740: we must not apply the marks to escaped entity names, e.g. `&quot;`
131
+ // Use some exotic characters to mark matches:
132
+ temp = text.replace(reHighlight, function (s) {
133
+ return START_MARKER + s + END_MARKER;
134
+ });
149
135
  }
136
+ // now we can escape the title...
137
+ node.titleWithHighlight = escapeHtml(temp)
138
+ // ... and finally insert the desired `<mark>` tags
139
+ .replace(RE_START_MARKER, "<mark>")
140
+ .replace(RE_END_MARTKER, "</mark>");
141
+ // } else {
142
+ // if (opts.fuzzy) {
143
+ // node.titleWithHighlight = _markFuzzyMatchedChars(text, res);
144
+ // } else {
145
+ // node.titleWithHighlight = text.replace(reHighlight, function (s) {
146
+ // return "<mark>" + s + "</mark>";
147
+ // });
148
+ // }
149
+ // }
150
150
  // node.debug("filter", escapeTitles, text, node.titleWithHighlight);
151
151
  }
152
152
  return !!res;
@@ -248,8 +248,6 @@ export class FilterExtension extends WunderbaumExtension {
248
248
 
249
249
  /**
250
250
  * [ext-filter] Re-apply current filter.
251
- *
252
- * @requires jquery.fancytree.filter.js
253
251
  */
254
252
  updateFilter() {
255
253
  let tree = this.tree;
@@ -266,14 +264,11 @@ export class FilterExtension extends WunderbaumExtension {
266
264
 
267
265
  /**
268
266
  * [ext-filter] Reset the filter.
269
- *
270
- * @alias Fancytree#clearFilter
271
- * @requires jquery.fancytree.filter.js
272
267
  */
273
268
  clearFilter() {
274
- let tree = this.tree,
275
- // statusNode = tree.root.findDirectChild(KEY_NODATA),
276
- escapeTitles = tree.options.escapeTitles;
269
+ let tree = this.tree;
270
+ // statusNode = tree.root.findDirectChild(KEY_NODATA),
271
+ // escapeTitles = tree.options.escapeTitles;
277
272
  // enhanceTitle = tree.options.enhanceTitle,
278
273
  tree.enableUpdate(false);
279
274
 
@@ -289,11 +284,11 @@ export class FilterExtension extends WunderbaumExtension {
289
284
  if (node.match && node._rowElem) {
290
285
  // #491, #601
291
286
  let titleElem = node._rowElem.querySelector("span.wb-title")!;
292
- if (escapeTitles) {
293
- titleElem.textContent = node.title;
294
- } else {
295
- titleElem.innerHTML = node.title;
296
- }
287
+ // if (escapeTitles) {
288
+ titleElem.textContent = node.title;
289
+ // } else {
290
+ // titleElem.innerHTML = node.title;
291
+ // }
297
292
  node._callEvent("enhanceTitle", { titleElem: titleElem });
298
293
  }
299
294
  delete node.match;
@@ -335,7 +330,7 @@ export class FilterExtension extends WunderbaumExtension {
335
330
  function _markFuzzyMatchedChars(
336
331
  text: string,
337
332
  matches: RegExpMatchArray,
338
- escapeTitles = false
333
+ escapeTitles = true
339
334
  ) {
340
335
  let matchingIndices = [];
341
336
  // get the indices of matched characters (Iterate through `RegExpMatchArray`)
@@ -355,7 +350,7 @@ function _markFuzzyMatchedChars(
355
350
  // Map each `text` char to its position and store in `textPoses`.
356
351
  let textPoses = text.split("");
357
352
  if (escapeTitles) {
358
- // If escaping the title, then wrap the matchng char within exotic chars
353
+ // If escaping the title, then wrap the matching char within exotic chars
359
354
  matchingIndices.forEach(function (v) {
360
355
  textPoses[v] = START_MARKER + textPoses[v] + END_MARKER;
361
356
  });