@macrostrat/feedback-components 1.1.10 → 2.0.0

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.
Files changed (136) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/extractions/index.cjs +198 -0
  3. package/dist/extractions/index.cjs.map +1 -0
  4. package/dist/extractions/index.d.ts +30 -0
  5. package/dist/extractions/index.js +198 -0
  6. package/dist/extractions/index.js.map +1 -0
  7. package/dist/extractions/main.module.sass.cjs +12 -0
  8. package/dist/extractions/main.module.sass.cjs.map +1 -0
  9. package/dist/extractions/main.module.sass.js +12 -0
  10. package/dist/extractions/main.module.sass.js.map +1 -0
  11. package/dist/extractions/types.d.ts +32 -0
  12. package/dist/feedback/edit-state.cjs +382 -0
  13. package/dist/feedback/edit-state.cjs.map +1 -0
  14. package/dist/feedback/edit-state.d.ts +127 -0
  15. package/dist/feedback/edit-state.js +382 -0
  16. package/dist/feedback/edit-state.js.map +1 -0
  17. package/dist/feedback/feedback.module.sass.cjs +47 -0
  18. package/dist/feedback/feedback.module.sass.cjs.map +1 -0
  19. package/dist/feedback/feedback.module.sass.js +47 -0
  20. package/dist/feedback/feedback.module.sass.js.map +1 -0
  21. package/dist/feedback/graph.cjs +124 -0
  22. package/dist/feedback/graph.cjs.map +1 -0
  23. package/dist/feedback/graph.d.ts +9 -0
  24. package/dist/feedback/graph.js +124 -0
  25. package/dist/feedback/graph.js.map +1 -0
  26. package/dist/feedback/index.cjs +347 -0
  27. package/dist/feedback/index.cjs.map +1 -0
  28. package/dist/feedback/index.d.ts +15 -0
  29. package/dist/feedback/index.js +348 -0
  30. package/dist/feedback/index.js.map +1 -0
  31. package/dist/feedback/matches.cjs +244 -0
  32. package/dist/feedback/matches.cjs.map +1 -0
  33. package/dist/feedback/matches.d.ts +17 -0
  34. package/dist/feedback/matches.js +244 -0
  35. package/dist/feedback/matches.js.map +1 -0
  36. package/dist/feedback/node.cjs +63 -0
  37. package/dist/feedback/node.cjs.map +1 -0
  38. package/dist/feedback/node.d.ts +5 -0
  39. package/dist/feedback/node.js +63 -0
  40. package/dist/feedback/node.js.map +1 -0
  41. package/dist/feedback/text-visualizer.cjs +288 -0
  42. package/dist/feedback/text-visualizer.cjs.map +1 -0
  43. package/dist/feedback/text-visualizer.d.ts +36 -0
  44. package/dist/feedback/text-visualizer.js +288 -0
  45. package/dist/feedback/text-visualizer.js.map +1 -0
  46. package/dist/feedback/type-selector/index.cjs +47 -0
  47. package/dist/feedback/type-selector/index.cjs.map +1 -0
  48. package/dist/feedback/type-selector/index.d.ts +20 -0
  49. package/dist/feedback/type-selector/index.js +47 -0
  50. package/dist/feedback/type-selector/index.js.map +1 -0
  51. package/dist/feedback/type-selector/main.module.sass.cjs +13 -0
  52. package/dist/feedback/type-selector/main.module.sass.cjs.map +1 -0
  53. package/dist/feedback/type-selector/main.module.sass.js +13 -0
  54. package/dist/feedback/type-selector/main.module.sass.js.map +1 -0
  55. package/dist/feedback/typelist.cjs +293 -0
  56. package/dist/feedback/typelist.cjs.map +1 -0
  57. package/dist/feedback/typelist.d.ts +8 -0
  58. package/dist/feedback/typelist.js +293 -0
  59. package/dist/feedback/typelist.js.map +1 -0
  60. package/dist/feedback/types.d.ts +64 -0
  61. package/dist/feedback-components.css +343 -0
  62. package/dist/index.cjs +14 -0
  63. package/dist/index.cjs.map +1 -0
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +14 -0
  66. package/dist/index.js.map +1 -0
  67. package/package.json +43 -41
  68. package/src/extractions/index.ts +2 -2
  69. package/src/feedback/text-visualizer.ts +0 -1
  70. package/src/feedback/types.ts +2 -2
  71. package/dist/esm/feedback-components.2364179b.js +0 -287
  72. package/dist/esm/feedback-components.2364179b.js.map +0 -1
  73. package/dist/esm/feedback-components.6d32ee91.js +0 -16
  74. package/dist/esm/feedback-components.6d32ee91.js.map +0 -1
  75. package/dist/esm/feedback-components.7dd42a9f.js +0 -248
  76. package/dist/esm/feedback-components.7dd42a9f.js.map +0 -1
  77. package/dist/esm/feedback-components.7f72e5e9.js +0 -64
  78. package/dist/esm/feedback-components.7f72e5e9.js.map +0 -1
  79. package/dist/esm/feedback-components.ad9f284e.js +0 -63
  80. package/dist/esm/feedback-components.ad9f284e.js.map +0 -1
  81. package/dist/esm/feedback-components.ba79c0ef.js +0 -204
  82. package/dist/esm/feedback-components.ba79c0ef.js.map +0 -1
  83. package/dist/esm/feedback-components.bf93773c.css +0 -17
  84. package/dist/esm/feedback-components.bf93773c.css.map +0 -1
  85. package/dist/esm/feedback-components.c6e2c296.js +0 -148
  86. package/dist/esm/feedback-components.c6e2c296.js.map +0 -1
  87. package/dist/esm/feedback-components.ca78c2d4.js +0 -287
  88. package/dist/esm/feedback-components.ca78c2d4.js.map +0 -1
  89. package/dist/esm/feedback-components.d769ffa5.css +0 -205
  90. package/dist/esm/feedback-components.d769ffa5.css.map +0 -1
  91. package/dist/esm/feedback-components.e273ed5b.css +0 -14
  92. package/dist/esm/feedback-components.e273ed5b.css.map +0 -1
  93. package/dist/esm/feedback-components.e702eece.js +0 -97
  94. package/dist/esm/feedback-components.e702eece.js.map +0 -1
  95. package/dist/esm/feedback-components.edc606bb.js +0 -360
  96. package/dist/esm/feedback-components.edc606bb.js.map +0 -1
  97. package/dist/esm/feedback-components.f9850d85.js +0 -19
  98. package/dist/esm/feedback-components.f9850d85.js.map +0 -1
  99. package/dist/esm/feedback-components.fa847634.js +0 -453
  100. package/dist/esm/feedback-components.fa847634.js.map +0 -1
  101. package/dist/esm/index.d.ts +0 -96
  102. package/dist/esm/index.d.ts.map +0 -1
  103. package/dist/esm/index.js +0 -9
  104. package/dist/esm/index.js.map +0 -1
  105. package/dist/node/feedback-components.1bd49bf0.js +0 -2
  106. package/dist/node/feedback-components.1bd49bf0.js.map +0 -1
  107. package/dist/node/feedback-components.2f391fa4.js +0 -2
  108. package/dist/node/feedback-components.2f391fa4.js.map +0 -1
  109. package/dist/node/feedback-components.6a4a1290.js +0 -2
  110. package/dist/node/feedback-components.6a4a1290.js.map +0 -1
  111. package/dist/node/feedback-components.70780da4.js +0 -2
  112. package/dist/node/feedback-components.70780da4.js.map +0 -1
  113. package/dist/node/feedback-components.794f429b.js +0 -2
  114. package/dist/node/feedback-components.794f429b.js.map +0 -1
  115. package/dist/node/feedback-components.83c21466.css +0 -2
  116. package/dist/node/feedback-components.83c21466.css.map +0 -1
  117. package/dist/node/feedback-components.97518e90.css +0 -2
  118. package/dist/node/feedback-components.97518e90.css.map +0 -1
  119. package/dist/node/feedback-components.9eb1d41a.css +0 -2
  120. package/dist/node/feedback-components.9eb1d41a.css.map +0 -1
  121. package/dist/node/feedback-components.acac789b.js +0 -2
  122. package/dist/node/feedback-components.acac789b.js.map +0 -1
  123. package/dist/node/feedback-components.c84ff10e.js +0 -2
  124. package/dist/node/feedback-components.c84ff10e.js.map +0 -1
  125. package/dist/node/feedback-components.db72d0e1.js +0 -2
  126. package/dist/node/feedback-components.db72d0e1.js.map +0 -1
  127. package/dist/node/feedback-components.dc76072c.js +0 -2
  128. package/dist/node/feedback-components.dc76072c.js.map +0 -1
  129. package/dist/node/feedback-components.ddd11fe8.js +0 -2
  130. package/dist/node/feedback-components.ddd11fe8.js.map +0 -1
  131. package/dist/node/feedback-components.e59a061f.js +0 -2
  132. package/dist/node/feedback-components.e59a061f.js.map +0 -1
  133. package/dist/node/feedback-components.feab0bd0.js +0 -2
  134. package/dist/node/feedback-components.feab0bd0.js.map +0 -1
  135. package/dist/node/index.js +0 -2
  136. package/dist/node/index.js.map +0 -1
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const feedback_module = require("./feedback.module.sass.cjs");
4
+ const hyper = require("@macrostrat/hyper");
5
+ const index = require("../extractions/index.cjs");
6
+ const react = require("react");
7
+ const core = require("@blueprintjs/core");
8
+ const matches = require("./matches.cjs");
9
+ const h = hyper.styled(feedback_module.default);
10
+ function buildTags(highlights, selectedNodes) {
11
+ let tags = [];
12
+ const entities = /* @__PURE__ */ new Set();
13
+ for (const highlight of highlights) {
14
+ if (entities.has(highlight.id)) continue;
15
+ const highlighted = isHighlighted(highlight, selectedNodes);
16
+ const active = isActive(highlight, selectedNodes);
17
+ const tagStyle = index.getTagStyle(highlight.backgroundColor, {
18
+ highlighted,
19
+ active
20
+ });
21
+ const tag = {
22
+ color: tagStyle.color,
23
+ tagStyle: {
24
+ display: "none"
25
+ },
26
+ markStyle: {
27
+ backgroundColor: tagStyle.backgroundColor
28
+ },
29
+ ...highlight,
30
+ backgroundColor: tagStyle.backgroundColor
31
+ };
32
+ tags.push(tag);
33
+ entities.add(highlight.id);
34
+ }
35
+ return tags;
36
+ }
37
+ function isActive(tag, selectedNodes) {
38
+ return selectedNodes.includes(tag.id);
39
+ }
40
+ function isHighlighted(tag, selectedNodes) {
41
+ if (selectedNodes.length === 0) return true;
42
+ return (selectedNodes.includes(tag.id) || tag.parents?.some((d) => selectedNodes.includes(d))) ?? false;
43
+ }
44
+ function FeedbackText(props) {
45
+ const {
46
+ text,
47
+ selectedNodes,
48
+ nodes,
49
+ dispatch,
50
+ allowOverlap,
51
+ matchLinks,
52
+ viewOnly
53
+ } = props;
54
+ const allTags = buildTags(
55
+ index.buildHighlights(nodes, null),
56
+ selectedNodes
57
+ );
58
+ return h(
59
+ "div.feedback-text-wrapper",
60
+ {
61
+ tabIndex: 0,
62
+ onKeyDown: (e) => {
63
+ if (e.key === "Backspace") {
64
+ dispatch({
65
+ type: "delete-node",
66
+ payload: { ids: selectedNodes }
67
+ });
68
+ }
69
+ }
70
+ },
71
+ h(HighlightedText, {
72
+ text,
73
+ allTags,
74
+ allowOverlap,
75
+ dispatch,
76
+ selectedNodes,
77
+ viewOnly,
78
+ matchLinks
79
+ })
80
+ );
81
+ }
82
+ function createTagFromSelection({
83
+ container
84
+ }) {
85
+ const selection = window.getSelection();
86
+ if (!selection || selection.isCollapsed || selection.rangeCount === 0 || !container)
87
+ return null;
88
+ const range = selection.getRangeAt(0);
89
+ if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) {
90
+ return null;
91
+ }
92
+ const preRange = document.createRange();
93
+ preRange.setStart(container, 0);
94
+ preRange.setEnd(range.startContainer, range.startOffset);
95
+ const start = preRange.toString().length;
96
+ const selectedText = range.toString();
97
+ const end = start + selectedText.length;
98
+ return {
99
+ start,
100
+ end,
101
+ text: selectedText
102
+ };
103
+ }
104
+ function addTag({ tag, dispatch, text, allTags, allowOverlap }) {
105
+ let { start, end } = tag;
106
+ if (text[end - 1] != " ") {
107
+ while (start > 0 && /\w/.test(text[start - 1])) {
108
+ start--;
109
+ }
110
+ while (end < text.length && /\w/.test(text[end])) {
111
+ end++;
112
+ }
113
+ }
114
+ let payload = { start, end, text: text.slice(start, end) };
115
+ if (payload.text.trim() === "") {
116
+ console.log("Blank tag found, ignoring");
117
+ return;
118
+ }
119
+ const duplicate = allTags.find(
120
+ (t) => t.start === payload.start && (t.end === payload.end || t.end === payload.end - 1)
121
+ );
122
+ if (duplicate) {
123
+ console.log("Duplicate tag found, ignoring");
124
+ return;
125
+ }
126
+ if (payload.text.endsWith(" ")) {
127
+ payload.text = payload.text.slice(0, -1);
128
+ payload.end -= 1;
129
+ }
130
+ const inside = allTags.some(
131
+ (t) => t.start <= payload.start && t.end >= payload.end
132
+ );
133
+ const overlap = allTags.some(
134
+ (t) => t.start < payload.end && t.end > payload.start
135
+ );
136
+ if ((inside || overlap) && !allowOverlap) {
137
+ console.log("Tag is inside another tag, ignoring");
138
+ return;
139
+ }
140
+ dispatch({ type: "create-node", payload });
141
+ }
142
+ function nestHighlights(text, tags) {
143
+ const events = [];
144
+ for (const tag of tags) {
145
+ events.push({ pos: tag.start, type: "start", tag });
146
+ events.push({ pos: tag.end, type: "end", tag });
147
+ }
148
+ events.sort((a, b) => {
149
+ if (a.pos !== b.pos) return a.pos - b.pos;
150
+ if (a.type === "end" && b.type === "start") return -1;
151
+ if (a.type === "start" && b.type === "end") return 1;
152
+ return 0;
153
+ });
154
+ const root = { children: [], textStart: 0 };
155
+ const stack = [root];
156
+ let lastPos = 0;
157
+ for (const { pos, type, tag } of events) {
158
+ const parent = stack[stack.length - 1];
159
+ if (pos > lastPos) {
160
+ const slice = text.slice(lastPos, pos);
161
+ parent.children.push(slice);
162
+ }
163
+ if (type === "start") {
164
+ const newNode = { tag, children: [], textStart: pos };
165
+ parent.children.push(newNode);
166
+ stack.push(newNode);
167
+ } else {
168
+ stack.pop();
169
+ }
170
+ lastPos = pos;
171
+ }
172
+ if (lastPos < text.length) {
173
+ stack[stack.length - 1].children.push(text.slice(lastPos));
174
+ }
175
+ return root;
176
+ }
177
+ function renderNode(node, dispatch, selectedNodes, parentSelected, matchLinks, viewOnly) {
178
+ if (typeof node === "string") return node;
179
+ const { tag, children } = node;
180
+ const isSelected = selectedNodes?.includes(tag.id);
181
+ const showBorder = selectedNodes.length === 0 || isSelected;
182
+ const match = tag.match;
183
+ const style = {
184
+ ...tag,
185
+ zIndex: parentSelected ? -1 : 1,
186
+ border: "1px solid " + (match != void 0 && matchLinks ? "orange" : showBorder ? tag.color : "transparent"),
187
+ margin: "-1px"
188
+ };
189
+ let moveText = [];
190
+ if (isSelected) {
191
+ for (const key in children) {
192
+ if (Object.prototype.hasOwnProperty.call(children, key)) {
193
+ const child = children[key];
194
+ if (child?.tag) {
195
+ moveText.push(child.children[0]);
196
+ } else {
197
+ moveText.push(child);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ const tagComponent = h(
203
+ "span",
204
+ {
205
+ onMouseEnter: (e) => {
206
+ e.stopPropagation();
207
+ },
208
+ className: "highlight" + (!viewOnly || match ? " clickable" : ""),
209
+ style,
210
+ onClick: (e) => {
211
+ e.stopPropagation();
212
+ if (e.ctrlKey || e.metaKey || selectedNodes[0] === tag.id && selectedNodes.length === 1) {
213
+ e.stopPropagation();
214
+ dispatch({
215
+ type: "toggle-node-selected",
216
+ payload: { ids: [tag.id] }
217
+ });
218
+ } else if (e.shiftKey && selectedNodes.length > 0) {
219
+ const lastSelected = selectedNodes[selectedNodes.length - 1];
220
+ dispatch({
221
+ type: "select-range",
222
+ payload: { ids: [lastSelected, tag.id] }
223
+ });
224
+ } else {
225
+ dispatch({
226
+ type: "select-node",
227
+ payload: { ids: [tag.id] }
228
+ });
229
+ }
230
+ }
231
+ },
232
+ isSelected ? moveText.flat() : children.map(
233
+ (child, i) => renderNode(
234
+ child,
235
+ dispatch,
236
+ selectedNodes,
237
+ isSelected,
238
+ matchLinks,
239
+ viewOnly
240
+ )
241
+ )
242
+ );
243
+ if (viewOnly && match) {
244
+ return h(
245
+ core.Popover,
246
+ {
247
+ content: h("div.match-link", h(matches.MatchTag, { data: match, matchLinks })),
248
+ interactionKind: "hover"
249
+ },
250
+ tagComponent
251
+ );
252
+ }
253
+ return tagComponent;
254
+ }
255
+ function HighlightedText(props) {
256
+ const {
257
+ text,
258
+ allTags = [],
259
+ dispatch,
260
+ selectedNodes,
261
+ allowOverlap,
262
+ matchLinks,
263
+ viewOnly
264
+ } = props;
265
+ const tree = nestHighlights(text, allTags);
266
+ const spanRef = react.useRef(null);
267
+ react.useEffect(() => {
268
+ const handleMouseUp = () => {
269
+ const tag = createTagFromSelection({ container: spanRef.current });
270
+ if (!tag) return;
271
+ addTag({ tag, dispatch, text, allTags, allowOverlap });
272
+ };
273
+ document.addEventListener("mouseup", handleMouseUp);
274
+ return () => {
275
+ document.removeEventListener("mouseup", handleMouseUp);
276
+ };
277
+ }, [text, allTags, dispatch, allowOverlap]);
278
+ return h(
279
+ "span",
280
+ { ref: spanRef },
281
+ tree.children.map(
282
+ (child, i) => renderNode(child, dispatch, selectedNodes, false, matchLinks, viewOnly)
283
+ )
284
+ );
285
+ }
286
+ exports.FeedbackText = FeedbackText;
287
+ exports.HighlightedText = HighlightedText;
288
+ //# sourceMappingURL=text-visualizer.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-visualizer.cjs","sources":["../../src/feedback/text-visualizer.ts"],"sourcesContent":["import { AnnotateBlendTag } from \"react-text-annotate-blend\";\nimport { InternalEntity } from \"./types\";\nimport { TreeDispatch } from \"./edit-state\";\nimport styles from \"./feedback.module.sass\";\nimport hyper from \"@macrostrat/hyper\";\nimport { buildHighlights, getTagStyle } from \"../extractions\";\nimport { Highlight } from \"../extractions/types\";\nimport { useEffect, useRef } from \"react\";\nimport { Popover } from \"@blueprintjs/core\";\nimport { MatchTag } from \"./matches\";\n\nconst h = hyper.styled(styles);\n\nexport interface FeedbackTextProps {\n text: string;\n selectedNodes: number[];\n nodes: InternalEntity[];\n updateNodes: (nodes: string[]) => void;\n dispatch: TreeDispatch;\n lineHeight: string;\n allowOverlap?: boolean;\n matchLinks?: {\n lithology: string;\n strat_name: string;\n lith_att: string;\n };\n viewOnly?: boolean;\n}\n\nfunction buildTags(\n highlights: Highlight[],\n selectedNodes: number[],\n): AnnotateBlendTag[] {\n let tags: AnnotateBlendTag[] = [];\n // If entity ID has already been seen, don't add it again\n const entities = new Set<number>();\n\n for (const highlight of highlights) {\n // Don't add multiply-linked entities multiple times\n if (entities.has(highlight.id)) continue;\n\n const highlighted = isHighlighted(highlight, selectedNodes);\n const active = isActive(highlight, selectedNodes);\n const tagStyle = getTagStyle(highlight.backgroundColor, {\n highlighted,\n active,\n });\n\n const tag = {\n color: tagStyle.color,\n tagStyle: {\n display: \"none\",\n },\n markStyle: {\n backgroundColor: tagStyle.backgroundColor,\n },\n ...highlight,\n backgroundColor: tagStyle.backgroundColor,\n };\n\n tags.push(tag);\n\n entities.add(highlight.id);\n }\n\n return tags;\n}\n\nfunction isActive(tag: Highlight, selectedNodes: number[]) {\n return selectedNodes.includes(tag.id);\n}\n\nfunction isHighlighted(tag: Highlight, selectedNodes: number[]) {\n if (selectedNodes.length === 0) return true;\n return (\n (selectedNodes.includes(tag.id) ||\n tag.parents?.some((d) => selectedNodes.includes(d))) ??\n false\n );\n}\n\nexport function FeedbackText(props: FeedbackTextProps) {\n // Convert input to tags\n const {\n text,\n selectedNodes,\n nodes,\n dispatch,\n allowOverlap,\n matchLinks,\n viewOnly,\n } = props;\n const allTags: AnnotateBlendTag[] = buildTags(\n buildHighlights(nodes, null),\n selectedNodes,\n );\n\n return h(\n \"div.feedback-text-wrapper\",\n {\n tabIndex: 0,\n onKeyDown: (e) => {\n if (e.key === \"Backspace\") {\n dispatch({\n type: \"delete-node\",\n payload: { ids: selectedNodes },\n });\n }\n },\n },\n h(HighlightedText, {\n text,\n allTags,\n allowOverlap,\n dispatch,\n selectedNodes,\n viewOnly,\n matchLinks,\n }),\n );\n}\n\nfunction createTagFromSelection({\n container,\n}: {\n container: HTMLElement | null;\n}) {\n const selection = window.getSelection();\n if (\n !selection ||\n selection.isCollapsed ||\n selection.rangeCount === 0 ||\n !container\n )\n return null;\n\n const range = selection.getRangeAt(0);\n\n if (\n !container.contains(range.startContainer) ||\n !container.contains(range.endContainer)\n ) {\n return null;\n }\n\n const preRange = document.createRange();\n preRange.setStart(container, 0);\n preRange.setEnd(range.startContainer, range.startOffset);\n const start = preRange.toString().length;\n\n const selectedText = range.toString();\n const end = start + selectedText.length;\n\n return {\n start,\n end,\n text: selectedText,\n };\n}\n\nfunction addTag({ tag, dispatch, text, allTags, allowOverlap }) {\n let { start, end } = tag;\n // snap to text\n if (text[end - 1] != \" \") {\n // double clicking word overselects by one, shouldn't increase to next word\n while (start > 0 && /\\w/.test(text[start - 1])) {\n start--;\n }\n while (end < text.length && /\\w/.test(text[end])) {\n end++;\n }\n }\n\n let payload = { start, end, text: text.slice(start, end) };\n\n if (payload.text.trim() === \"\") {\n console.log(\"Blank tag found, ignoring\");\n return;\n }\n\n const duplicate = allTags.find(\n (t) =>\n t.start === payload.start &&\n (t.end === payload.end || t.end === payload.end - 1),\n );\n\n if (duplicate) {\n console.log(\"Duplicate tag found, ignoring\");\n return;\n }\n\n if (payload.text.endsWith(\" \")) {\n payload.text = payload.text.slice(0, -1);\n payload.end -= 1;\n }\n\n const inside = allTags.some(\n (t) => t.start <= payload.start && t.end >= payload.end,\n );\n\n const overlap = allTags.some(\n (t) => t.start < payload.end && t.end > payload.start,\n );\n\n if ((inside || overlap) && !allowOverlap) {\n console.log(\"Tag is inside another tag, ignoring\");\n return;\n }\n\n dispatch({ type: \"create-node\", payload });\n}\n\nfunction nestHighlights(text: string, tags: AnnotateBlendTag[]) {\n const events: Array<{\n pos: number;\n type: \"start\" | \"end\";\n tag: AnnotateBlendTag;\n }> = [];\n\n for (const tag of tags) {\n events.push({ pos: tag.start, type: \"start\", tag });\n events.push({ pos: tag.end, type: \"end\", tag });\n }\n\n events.sort((a, b) => {\n if (a.pos !== b.pos) return a.pos - b.pos;\n if (a.type === \"end\" && b.type === \"start\") return -1;\n if (a.type === \"start\" && b.type === \"end\") return 1;\n return 0;\n });\n\n const root = { children: [], textStart: 0 };\n const stack = [root];\n let lastPos = 0;\n\n for (const { pos, type, tag } of events) {\n const parent = stack[stack.length - 1];\n\n if (pos > lastPos) {\n const slice = text.slice(lastPos, pos);\n parent.children.push(slice);\n }\n\n if (type === \"start\") {\n const newNode = { tag, children: [], textStart: pos };\n parent.children.push(newNode);\n stack.push(newNode);\n } else {\n stack.pop();\n }\n\n lastPos = pos;\n }\n\n if (lastPos < text.length) {\n stack[stack.length - 1].children.push(text.slice(lastPos));\n }\n\n return root;\n}\n\nfunction renderNode(\n node: any,\n dispatch: TreeDispatch,\n selectedNodes: number[],\n parentSelected: boolean,\n matchLinks?: {\n lithology: string;\n strat_name: string;\n lith_att: string;\n },\n viewOnly?: boolean,\n): any {\n if (typeof node === \"string\") return node;\n\n const { tag, children } = node;\n const isSelected = selectedNodes?.includes(tag.id);\n const showBorder = selectedNodes.length === 0 || isSelected;\n const match = tag.match;\n\n const style = {\n ...tag,\n zIndex: parentSelected ? -1 : 1,\n border:\n \"1px solid \" +\n (match != undefined && matchLinks\n ? \"orange\"\n : showBorder\n ? tag.color\n : \"transparent\"),\n margin: \"-1px\",\n };\n\n let moveText = [];\n if (isSelected) {\n for (const key in children) {\n if (Object.prototype.hasOwnProperty.call(children, key)) {\n const child = children[key];\n if (child?.tag) {\n moveText.push(child.children[0]);\n } else {\n moveText.push(child);\n }\n }\n }\n }\n\n const tagComponent = h(\n \"span\",\n {\n onMouseEnter: (e: MouseEvent) => {\n e.stopPropagation();\n },\n className: \"highlight\" + (!viewOnly || match ? \" clickable\" : \"\"),\n style,\n onClick: (e: MouseEvent) => {\n e.stopPropagation();\n if (\n e.ctrlKey ||\n e.metaKey ||\n (selectedNodes[0] === tag.id && selectedNodes.length === 1)\n ) {\n // Toggle selection on ctrl/cmd click or when node is only selected node\n e.stopPropagation();\n dispatch({\n type: \"toggle-node-selected\",\n payload: { ids: [tag.id] },\n });\n } else if (e.shiftKey && selectedNodes.length > 0) {\n // Select range from last selected node to this one\n const lastSelected = selectedNodes[selectedNodes.length - 1];\n\n dispatch({\n type: \"select-range\",\n payload: { ids: [lastSelected, tag.id] },\n });\n } else {\n dispatch({\n type: \"select-node\",\n payload: { ids: [tag.id] },\n });\n }\n },\n },\n isSelected\n ? moveText.flat()\n : children.map((child: any, i: number) =>\n renderNode(\n child,\n dispatch,\n selectedNodes,\n isSelected,\n matchLinks,\n viewOnly,\n ),\n ),\n );\n\n if (viewOnly && match) {\n return h(\n Popover,\n {\n content: h(\"div.match-link\", h(MatchTag, { data: match, matchLinks })),\n interactionKind: \"hover\",\n },\n tagComponent,\n );\n }\n\n return tagComponent;\n}\n\nexport function HighlightedText(props: {\n text: string;\n allTags: AnnotateBlendTag[];\n lineHeight: string;\n allowOverlap?: boolean;\n dispatch: TreeDispatch;\n selectedNodes: number[];\n matchLinks?: {\n lithology: string;\n strat_name: string;\n lith_att: string;\n };\n viewOnly?: boolean;\n}) {\n const {\n text,\n allTags = [],\n dispatch,\n selectedNodes,\n allowOverlap,\n matchLinks,\n viewOnly,\n } = props;\n\n const tree = nestHighlights(text, allTags);\n\n const spanRef = useRef<HTMLSpanElement>(null);\n\n useEffect(() => {\n const handleMouseUp = () => {\n const tag = createTagFromSelection({ container: spanRef.current });\n if (!tag) return;\n addTag({ tag, dispatch, text, allTags, allowOverlap });\n };\n\n document.addEventListener(\"mouseup\", handleMouseUp);\n return () => {\n document.removeEventListener(\"mouseup\", handleMouseUp);\n };\n }, [text, allTags, dispatch, allowOverlap]);\n\n return h(\n \"span\",\n { ref: spanRef },\n tree.children.map((child: any, i: number) =>\n renderNode(child, dispatch, selectedNodes, false, matchLinks, viewOnly),\n ),\n );\n}\n"],"names":["styles","getTagStyle","buildHighlights","Popover","MatchTag","useRef","useEffect"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { AnnotateBlendTag } from 'react-text-annotate-blend';
2
+ import { InternalEntity } from './types';
3
+ import { TreeDispatch } from './edit-state';
4
+ export interface FeedbackTextProps {
5
+ text: string;
6
+ selectedNodes: number[];
7
+ nodes: InternalEntity[];
8
+ updateNodes: (nodes: string[]) => void;
9
+ dispatch: TreeDispatch;
10
+ lineHeight: string;
11
+ allowOverlap?: boolean;
12
+ matchLinks?: {
13
+ lithology: string;
14
+ strat_name: string;
15
+ lith_att: string;
16
+ };
17
+ viewOnly?: boolean;
18
+ }
19
+ export declare function FeedbackText(props: FeedbackTextProps): import('react').DOMElement<{
20
+ tabIndex: number;
21
+ onKeyDown: (e: import('react').KeyboardEvent<Element>) => void;
22
+ }, Element>;
23
+ export declare function HighlightedText(props: {
24
+ text: string;
25
+ allTags: AnnotateBlendTag[];
26
+ lineHeight: string;
27
+ allowOverlap?: boolean;
28
+ dispatch: TreeDispatch;
29
+ selectedNodes: number[];
30
+ matchLinks?: {
31
+ lithology: string;
32
+ strat_name: string;
33
+ lith_att: string;
34
+ };
35
+ viewOnly?: boolean;
36
+ }): import('react').DetailedReactHTMLElement<import('react').HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;
@@ -0,0 +1,288 @@
1
+ import styles from "./feedback.module.sass.js";
2
+ import hyper from "@macrostrat/hyper";
3
+ import { buildHighlights, getTagStyle } from "../extractions/index.js";
4
+ import { useRef, useEffect } from "react";
5
+ import { Popover } from "@blueprintjs/core";
6
+ import { MatchTag } from "./matches.js";
7
+ const h = hyper.styled(styles);
8
+ function buildTags(highlights, selectedNodes) {
9
+ let tags = [];
10
+ const entities = /* @__PURE__ */ new Set();
11
+ for (const highlight of highlights) {
12
+ if (entities.has(highlight.id)) continue;
13
+ const highlighted = isHighlighted(highlight, selectedNodes);
14
+ const active = isActive(highlight, selectedNodes);
15
+ const tagStyle = getTagStyle(highlight.backgroundColor, {
16
+ highlighted,
17
+ active
18
+ });
19
+ const tag = {
20
+ color: tagStyle.color,
21
+ tagStyle: {
22
+ display: "none"
23
+ },
24
+ markStyle: {
25
+ backgroundColor: tagStyle.backgroundColor
26
+ },
27
+ ...highlight,
28
+ backgroundColor: tagStyle.backgroundColor
29
+ };
30
+ tags.push(tag);
31
+ entities.add(highlight.id);
32
+ }
33
+ return tags;
34
+ }
35
+ function isActive(tag, selectedNodes) {
36
+ return selectedNodes.includes(tag.id);
37
+ }
38
+ function isHighlighted(tag, selectedNodes) {
39
+ if (selectedNodes.length === 0) return true;
40
+ return (selectedNodes.includes(tag.id) || tag.parents?.some((d) => selectedNodes.includes(d))) ?? false;
41
+ }
42
+ function FeedbackText(props) {
43
+ const {
44
+ text,
45
+ selectedNodes,
46
+ nodes,
47
+ dispatch,
48
+ allowOverlap,
49
+ matchLinks,
50
+ viewOnly
51
+ } = props;
52
+ const allTags = buildTags(
53
+ buildHighlights(nodes, null),
54
+ selectedNodes
55
+ );
56
+ return h(
57
+ "div.feedback-text-wrapper",
58
+ {
59
+ tabIndex: 0,
60
+ onKeyDown: (e) => {
61
+ if (e.key === "Backspace") {
62
+ dispatch({
63
+ type: "delete-node",
64
+ payload: { ids: selectedNodes }
65
+ });
66
+ }
67
+ }
68
+ },
69
+ h(HighlightedText, {
70
+ text,
71
+ allTags,
72
+ allowOverlap,
73
+ dispatch,
74
+ selectedNodes,
75
+ viewOnly,
76
+ matchLinks
77
+ })
78
+ );
79
+ }
80
+ function createTagFromSelection({
81
+ container
82
+ }) {
83
+ const selection = window.getSelection();
84
+ if (!selection || selection.isCollapsed || selection.rangeCount === 0 || !container)
85
+ return null;
86
+ const range = selection.getRangeAt(0);
87
+ if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) {
88
+ return null;
89
+ }
90
+ const preRange = document.createRange();
91
+ preRange.setStart(container, 0);
92
+ preRange.setEnd(range.startContainer, range.startOffset);
93
+ const start = preRange.toString().length;
94
+ const selectedText = range.toString();
95
+ const end = start + selectedText.length;
96
+ return {
97
+ start,
98
+ end,
99
+ text: selectedText
100
+ };
101
+ }
102
+ function addTag({ tag, dispatch, text, allTags, allowOverlap }) {
103
+ let { start, end } = tag;
104
+ if (text[end - 1] != " ") {
105
+ while (start > 0 && /\w/.test(text[start - 1])) {
106
+ start--;
107
+ }
108
+ while (end < text.length && /\w/.test(text[end])) {
109
+ end++;
110
+ }
111
+ }
112
+ let payload = { start, end, text: text.slice(start, end) };
113
+ if (payload.text.trim() === "") {
114
+ console.log("Blank tag found, ignoring");
115
+ return;
116
+ }
117
+ const duplicate = allTags.find(
118
+ (t) => t.start === payload.start && (t.end === payload.end || t.end === payload.end - 1)
119
+ );
120
+ if (duplicate) {
121
+ console.log("Duplicate tag found, ignoring");
122
+ return;
123
+ }
124
+ if (payload.text.endsWith(" ")) {
125
+ payload.text = payload.text.slice(0, -1);
126
+ payload.end -= 1;
127
+ }
128
+ const inside = allTags.some(
129
+ (t) => t.start <= payload.start && t.end >= payload.end
130
+ );
131
+ const overlap = allTags.some(
132
+ (t) => t.start < payload.end && t.end > payload.start
133
+ );
134
+ if ((inside || overlap) && !allowOverlap) {
135
+ console.log("Tag is inside another tag, ignoring");
136
+ return;
137
+ }
138
+ dispatch({ type: "create-node", payload });
139
+ }
140
+ function nestHighlights(text, tags) {
141
+ const events = [];
142
+ for (const tag of tags) {
143
+ events.push({ pos: tag.start, type: "start", tag });
144
+ events.push({ pos: tag.end, type: "end", tag });
145
+ }
146
+ events.sort((a, b) => {
147
+ if (a.pos !== b.pos) return a.pos - b.pos;
148
+ if (a.type === "end" && b.type === "start") return -1;
149
+ if (a.type === "start" && b.type === "end") return 1;
150
+ return 0;
151
+ });
152
+ const root = { children: [], textStart: 0 };
153
+ const stack = [root];
154
+ let lastPos = 0;
155
+ for (const { pos, type, tag } of events) {
156
+ const parent = stack[stack.length - 1];
157
+ if (pos > lastPos) {
158
+ const slice = text.slice(lastPos, pos);
159
+ parent.children.push(slice);
160
+ }
161
+ if (type === "start") {
162
+ const newNode = { tag, children: [], textStart: pos };
163
+ parent.children.push(newNode);
164
+ stack.push(newNode);
165
+ } else {
166
+ stack.pop();
167
+ }
168
+ lastPos = pos;
169
+ }
170
+ if (lastPos < text.length) {
171
+ stack[stack.length - 1].children.push(text.slice(lastPos));
172
+ }
173
+ return root;
174
+ }
175
+ function renderNode(node, dispatch, selectedNodes, parentSelected, matchLinks, viewOnly) {
176
+ if (typeof node === "string") return node;
177
+ const { tag, children } = node;
178
+ const isSelected = selectedNodes?.includes(tag.id);
179
+ const showBorder = selectedNodes.length === 0 || isSelected;
180
+ const match = tag.match;
181
+ const style = {
182
+ ...tag,
183
+ zIndex: parentSelected ? -1 : 1,
184
+ border: "1px solid " + (match != void 0 && matchLinks ? "orange" : showBorder ? tag.color : "transparent"),
185
+ margin: "-1px"
186
+ };
187
+ let moveText = [];
188
+ if (isSelected) {
189
+ for (const key in children) {
190
+ if (Object.prototype.hasOwnProperty.call(children, key)) {
191
+ const child = children[key];
192
+ if (child?.tag) {
193
+ moveText.push(child.children[0]);
194
+ } else {
195
+ moveText.push(child);
196
+ }
197
+ }
198
+ }
199
+ }
200
+ const tagComponent = h(
201
+ "span",
202
+ {
203
+ onMouseEnter: (e) => {
204
+ e.stopPropagation();
205
+ },
206
+ className: "highlight" + (!viewOnly || match ? " clickable" : ""),
207
+ style,
208
+ onClick: (e) => {
209
+ e.stopPropagation();
210
+ if (e.ctrlKey || e.metaKey || selectedNodes[0] === tag.id && selectedNodes.length === 1) {
211
+ e.stopPropagation();
212
+ dispatch({
213
+ type: "toggle-node-selected",
214
+ payload: { ids: [tag.id] }
215
+ });
216
+ } else if (e.shiftKey && selectedNodes.length > 0) {
217
+ const lastSelected = selectedNodes[selectedNodes.length - 1];
218
+ dispatch({
219
+ type: "select-range",
220
+ payload: { ids: [lastSelected, tag.id] }
221
+ });
222
+ } else {
223
+ dispatch({
224
+ type: "select-node",
225
+ payload: { ids: [tag.id] }
226
+ });
227
+ }
228
+ }
229
+ },
230
+ isSelected ? moveText.flat() : children.map(
231
+ (child, i) => renderNode(
232
+ child,
233
+ dispatch,
234
+ selectedNodes,
235
+ isSelected,
236
+ matchLinks,
237
+ viewOnly
238
+ )
239
+ )
240
+ );
241
+ if (viewOnly && match) {
242
+ return h(
243
+ Popover,
244
+ {
245
+ content: h("div.match-link", h(MatchTag, { data: match, matchLinks })),
246
+ interactionKind: "hover"
247
+ },
248
+ tagComponent
249
+ );
250
+ }
251
+ return tagComponent;
252
+ }
253
+ function HighlightedText(props) {
254
+ const {
255
+ text,
256
+ allTags = [],
257
+ dispatch,
258
+ selectedNodes,
259
+ allowOverlap,
260
+ matchLinks,
261
+ viewOnly
262
+ } = props;
263
+ const tree = nestHighlights(text, allTags);
264
+ const spanRef = useRef(null);
265
+ useEffect(() => {
266
+ const handleMouseUp = () => {
267
+ const tag = createTagFromSelection({ container: spanRef.current });
268
+ if (!tag) return;
269
+ addTag({ tag, dispatch, text, allTags, allowOverlap });
270
+ };
271
+ document.addEventListener("mouseup", handleMouseUp);
272
+ return () => {
273
+ document.removeEventListener("mouseup", handleMouseUp);
274
+ };
275
+ }, [text, allTags, dispatch, allowOverlap]);
276
+ return h(
277
+ "span",
278
+ { ref: spanRef },
279
+ tree.children.map(
280
+ (child, i) => renderNode(child, dispatch, selectedNodes, false, matchLinks, viewOnly)
281
+ )
282
+ );
283
+ }
284
+ export {
285
+ FeedbackText,
286
+ HighlightedText
287
+ };
288
+ //# sourceMappingURL=text-visualizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-visualizer.js","sources":["../../src/feedback/text-visualizer.ts"],"sourcesContent":["import { AnnotateBlendTag } from \"react-text-annotate-blend\";\nimport { InternalEntity } from \"./types\";\nimport { TreeDispatch } from \"./edit-state\";\nimport styles from \"./feedback.module.sass\";\nimport hyper from \"@macrostrat/hyper\";\nimport { buildHighlights, getTagStyle } from \"../extractions\";\nimport { Highlight } from \"../extractions/types\";\nimport { useEffect, useRef } from \"react\";\nimport { Popover } from \"@blueprintjs/core\";\nimport { MatchTag } from \"./matches\";\n\nconst h = hyper.styled(styles);\n\nexport interface FeedbackTextProps {\n text: string;\n selectedNodes: number[];\n nodes: InternalEntity[];\n updateNodes: (nodes: string[]) => void;\n dispatch: TreeDispatch;\n lineHeight: string;\n allowOverlap?: boolean;\n matchLinks?: {\n lithology: string;\n strat_name: string;\n lith_att: string;\n };\n viewOnly?: boolean;\n}\n\nfunction buildTags(\n highlights: Highlight[],\n selectedNodes: number[],\n): AnnotateBlendTag[] {\n let tags: AnnotateBlendTag[] = [];\n // If entity ID has already been seen, don't add it again\n const entities = new Set<number>();\n\n for (const highlight of highlights) {\n // Don't add multiply-linked entities multiple times\n if (entities.has(highlight.id)) continue;\n\n const highlighted = isHighlighted(highlight, selectedNodes);\n const active = isActive(highlight, selectedNodes);\n const tagStyle = getTagStyle(highlight.backgroundColor, {\n highlighted,\n active,\n });\n\n const tag = {\n color: tagStyle.color,\n tagStyle: {\n display: \"none\",\n },\n markStyle: {\n backgroundColor: tagStyle.backgroundColor,\n },\n ...highlight,\n backgroundColor: tagStyle.backgroundColor,\n };\n\n tags.push(tag);\n\n entities.add(highlight.id);\n }\n\n return tags;\n}\n\nfunction isActive(tag: Highlight, selectedNodes: number[]) {\n return selectedNodes.includes(tag.id);\n}\n\nfunction isHighlighted(tag: Highlight, selectedNodes: number[]) {\n if (selectedNodes.length === 0) return true;\n return (\n (selectedNodes.includes(tag.id) ||\n tag.parents?.some((d) => selectedNodes.includes(d))) ??\n false\n );\n}\n\nexport function FeedbackText(props: FeedbackTextProps) {\n // Convert input to tags\n const {\n text,\n selectedNodes,\n nodes,\n dispatch,\n allowOverlap,\n matchLinks,\n viewOnly,\n } = props;\n const allTags: AnnotateBlendTag[] = buildTags(\n buildHighlights(nodes, null),\n selectedNodes,\n );\n\n return h(\n \"div.feedback-text-wrapper\",\n {\n tabIndex: 0,\n onKeyDown: (e) => {\n if (e.key === \"Backspace\") {\n dispatch({\n type: \"delete-node\",\n payload: { ids: selectedNodes },\n });\n }\n },\n },\n h(HighlightedText, {\n text,\n allTags,\n allowOverlap,\n dispatch,\n selectedNodes,\n viewOnly,\n matchLinks,\n }),\n );\n}\n\nfunction createTagFromSelection({\n container,\n}: {\n container: HTMLElement | null;\n}) {\n const selection = window.getSelection();\n if (\n !selection ||\n selection.isCollapsed ||\n selection.rangeCount === 0 ||\n !container\n )\n return null;\n\n const range = selection.getRangeAt(0);\n\n if (\n !container.contains(range.startContainer) ||\n !container.contains(range.endContainer)\n ) {\n return null;\n }\n\n const preRange = document.createRange();\n preRange.setStart(container, 0);\n preRange.setEnd(range.startContainer, range.startOffset);\n const start = preRange.toString().length;\n\n const selectedText = range.toString();\n const end = start + selectedText.length;\n\n return {\n start,\n end,\n text: selectedText,\n };\n}\n\nfunction addTag({ tag, dispatch, text, allTags, allowOverlap }) {\n let { start, end } = tag;\n // snap to text\n if (text[end - 1] != \" \") {\n // double clicking word overselects by one, shouldn't increase to next word\n while (start > 0 && /\\w/.test(text[start - 1])) {\n start--;\n }\n while (end < text.length && /\\w/.test(text[end])) {\n end++;\n }\n }\n\n let payload = { start, end, text: text.slice(start, end) };\n\n if (payload.text.trim() === \"\") {\n console.log(\"Blank tag found, ignoring\");\n return;\n }\n\n const duplicate = allTags.find(\n (t) =>\n t.start === payload.start &&\n (t.end === payload.end || t.end === payload.end - 1),\n );\n\n if (duplicate) {\n console.log(\"Duplicate tag found, ignoring\");\n return;\n }\n\n if (payload.text.endsWith(\" \")) {\n payload.text = payload.text.slice(0, -1);\n payload.end -= 1;\n }\n\n const inside = allTags.some(\n (t) => t.start <= payload.start && t.end >= payload.end,\n );\n\n const overlap = allTags.some(\n (t) => t.start < payload.end && t.end > payload.start,\n );\n\n if ((inside || overlap) && !allowOverlap) {\n console.log(\"Tag is inside another tag, ignoring\");\n return;\n }\n\n dispatch({ type: \"create-node\", payload });\n}\n\nfunction nestHighlights(text: string, tags: AnnotateBlendTag[]) {\n const events: Array<{\n pos: number;\n type: \"start\" | \"end\";\n tag: AnnotateBlendTag;\n }> = [];\n\n for (const tag of tags) {\n events.push({ pos: tag.start, type: \"start\", tag });\n events.push({ pos: tag.end, type: \"end\", tag });\n }\n\n events.sort((a, b) => {\n if (a.pos !== b.pos) return a.pos - b.pos;\n if (a.type === \"end\" && b.type === \"start\") return -1;\n if (a.type === \"start\" && b.type === \"end\") return 1;\n return 0;\n });\n\n const root = { children: [], textStart: 0 };\n const stack = [root];\n let lastPos = 0;\n\n for (const { pos, type, tag } of events) {\n const parent = stack[stack.length - 1];\n\n if (pos > lastPos) {\n const slice = text.slice(lastPos, pos);\n parent.children.push(slice);\n }\n\n if (type === \"start\") {\n const newNode = { tag, children: [], textStart: pos };\n parent.children.push(newNode);\n stack.push(newNode);\n } else {\n stack.pop();\n }\n\n lastPos = pos;\n }\n\n if (lastPos < text.length) {\n stack[stack.length - 1].children.push(text.slice(lastPos));\n }\n\n return root;\n}\n\nfunction renderNode(\n node: any,\n dispatch: TreeDispatch,\n selectedNodes: number[],\n parentSelected: boolean,\n matchLinks?: {\n lithology: string;\n strat_name: string;\n lith_att: string;\n },\n viewOnly?: boolean,\n): any {\n if (typeof node === \"string\") return node;\n\n const { tag, children } = node;\n const isSelected = selectedNodes?.includes(tag.id);\n const showBorder = selectedNodes.length === 0 || isSelected;\n const match = tag.match;\n\n const style = {\n ...tag,\n zIndex: parentSelected ? -1 : 1,\n border:\n \"1px solid \" +\n (match != undefined && matchLinks\n ? \"orange\"\n : showBorder\n ? tag.color\n : \"transparent\"),\n margin: \"-1px\",\n };\n\n let moveText = [];\n if (isSelected) {\n for (const key in children) {\n if (Object.prototype.hasOwnProperty.call(children, key)) {\n const child = children[key];\n if (child?.tag) {\n moveText.push(child.children[0]);\n } else {\n moveText.push(child);\n }\n }\n }\n }\n\n const tagComponent = h(\n \"span\",\n {\n onMouseEnter: (e: MouseEvent) => {\n e.stopPropagation();\n },\n className: \"highlight\" + (!viewOnly || match ? \" clickable\" : \"\"),\n style,\n onClick: (e: MouseEvent) => {\n e.stopPropagation();\n if (\n e.ctrlKey ||\n e.metaKey ||\n (selectedNodes[0] === tag.id && selectedNodes.length === 1)\n ) {\n // Toggle selection on ctrl/cmd click or when node is only selected node\n e.stopPropagation();\n dispatch({\n type: \"toggle-node-selected\",\n payload: { ids: [tag.id] },\n });\n } else if (e.shiftKey && selectedNodes.length > 0) {\n // Select range from last selected node to this one\n const lastSelected = selectedNodes[selectedNodes.length - 1];\n\n dispatch({\n type: \"select-range\",\n payload: { ids: [lastSelected, tag.id] },\n });\n } else {\n dispatch({\n type: \"select-node\",\n payload: { ids: [tag.id] },\n });\n }\n },\n },\n isSelected\n ? moveText.flat()\n : children.map((child: any, i: number) =>\n renderNode(\n child,\n dispatch,\n selectedNodes,\n isSelected,\n matchLinks,\n viewOnly,\n ),\n ),\n );\n\n if (viewOnly && match) {\n return h(\n Popover,\n {\n content: h(\"div.match-link\", h(MatchTag, { data: match, matchLinks })),\n interactionKind: \"hover\",\n },\n tagComponent,\n );\n }\n\n return tagComponent;\n}\n\nexport function HighlightedText(props: {\n text: string;\n allTags: AnnotateBlendTag[];\n lineHeight: string;\n allowOverlap?: boolean;\n dispatch: TreeDispatch;\n selectedNodes: number[];\n matchLinks?: {\n lithology: string;\n strat_name: string;\n lith_att: string;\n };\n viewOnly?: boolean;\n}) {\n const {\n text,\n allTags = [],\n dispatch,\n selectedNodes,\n allowOverlap,\n matchLinks,\n viewOnly,\n } = props;\n\n const tree = nestHighlights(text, allTags);\n\n const spanRef = useRef<HTMLSpanElement>(null);\n\n useEffect(() => {\n const handleMouseUp = () => {\n const tag = createTagFromSelection({ container: spanRef.current });\n if (!tag) return;\n addTag({ tag, dispatch, text, allTags, allowOverlap });\n };\n\n document.addEventListener(\"mouseup\", handleMouseUp);\n return () => {\n document.removeEventListener(\"mouseup\", handleMouseUp);\n };\n }, [text, allTags, dispatch, allowOverlap]);\n\n return h(\n \"span\",\n { ref: spanRef },\n tree.children.map((child: any, i: number) =>\n renderNode(child, dispatch, selectedNodes, false, matchLinks, viewOnly),\n ),\n );\n}\n"],"names":[],"mappings":"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"}