@tolgee/web 4.9.3-rc.128edeb.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 (206) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/dist/tolgee-backend-fetch.cjs.min.js +2 -0
  4. package/dist/tolgee-backend-fetch.cjs.min.js.map +1 -0
  5. package/dist/tolgee-backend-fetch.esm.min.mjs +2 -0
  6. package/dist/tolgee-backend-fetch.esm.min.mjs.map +1 -0
  7. package/dist/tolgee-backend-fetch.umd.min.js +2 -0
  8. package/dist/tolgee-backend-fetch.umd.min.js.map +1 -0
  9. package/dist/tolgee-context-ui.cjs.min.js +143 -0
  10. package/dist/tolgee-context-ui.cjs.min.js.map +1 -0
  11. package/dist/tolgee-context-ui.esm.min.mjs +143 -0
  12. package/dist/tolgee-context-ui.esm.min.mjs.map +1 -0
  13. package/dist/tolgee-context-ui.umd.min.js +143 -0
  14. package/dist/tolgee-context-ui.umd.min.js.map +1 -0
  15. package/dist/tolgee-in-context-tools.cjs.min.js +143 -0
  16. package/dist/tolgee-in-context-tools.cjs.min.js.map +1 -0
  17. package/dist/tolgee-in-context-tools.esm.min.mjs +143 -0
  18. package/dist/tolgee-in-context-tools.esm.min.mjs.map +1 -0
  19. package/dist/tolgee-in-context-tools.umd.min.js +143 -0
  20. package/dist/tolgee-in-context-tools.umd.min.js.map +1 -0
  21. package/dist/tolgee-invisible-observer.cjs.min.js +2 -0
  22. package/dist/tolgee-invisible-observer.cjs.min.js.map +1 -0
  23. package/dist/tolgee-invisible-observer.esm.min.mjs +2 -0
  24. package/dist/tolgee-invisible-observer.esm.min.mjs.map +1 -0
  25. package/dist/tolgee-invisible-observer.umd.min.js +2 -0
  26. package/dist/tolgee-invisible-observer.umd.min.js.map +1 -0
  27. package/dist/tolgee-language-detector.cjs.min.js +2 -0
  28. package/dist/tolgee-language-detector.cjs.min.js.map +1 -0
  29. package/dist/tolgee-language-detector.esm.min.mjs +2 -0
  30. package/dist/tolgee-language-detector.esm.min.mjs.map +1 -0
  31. package/dist/tolgee-language-detector.umd.min.js +2 -0
  32. package/dist/tolgee-language-detector.umd.min.js.map +1 -0
  33. package/dist/tolgee-language-storage.cjs.min.js +2 -0
  34. package/dist/tolgee-language-storage.cjs.min.js.map +1 -0
  35. package/dist/tolgee-language-storage.esm.min.mjs +2 -0
  36. package/dist/tolgee-language-storage.esm.min.mjs.map +1 -0
  37. package/dist/tolgee-language-storage.umd.min.js +2 -0
  38. package/dist/tolgee-language-storage.umd.min.js.map +1 -0
  39. package/dist/tolgee-text-observer.cjs.min.js +2 -0
  40. package/dist/tolgee-text-observer.cjs.min.js.map +1 -0
  41. package/dist/tolgee-text-observer.esm.min.mjs +2 -0
  42. package/dist/tolgee-text-observer.esm.min.mjs.map +1 -0
  43. package/dist/tolgee-text-observer.umd.min.js +2 -0
  44. package/dist/tolgee-text-observer.umd.min.js.map +1 -0
  45. package/dist/tolgee-web-tolgee.cjs.min.js +2 -0
  46. package/dist/tolgee-web-tolgee.cjs.min.js.map +1 -0
  47. package/dist/tolgee-web-tolgee.esm.min.mjs +2 -0
  48. package/dist/tolgee-web-tolgee.esm.min.mjs.map +1 -0
  49. package/dist/tolgee-web-tolgee.umd.min.js +2 -0
  50. package/dist/tolgee-web-tolgee.umd.min.js.map +1 -0
  51. package/dist/tolgee-web.cjs.js +27076 -0
  52. package/dist/tolgee-web.cjs.js.map +1 -0
  53. package/dist/tolgee-web.cjs.min.js +143 -0
  54. package/dist/tolgee-web.cjs.min.js.map +1 -0
  55. package/dist/tolgee-web.esm.js +27052 -0
  56. package/dist/tolgee-web.esm.js.map +1 -0
  57. package/dist/tolgee-web.esm.min.mjs +143 -0
  58. package/dist/tolgee-web.esm.min.mjs.map +1 -0
  59. package/dist/tolgee-web.umd.js +27082 -0
  60. package/dist/tolgee-web.umd.js.map +1 -0
  61. package/dist/tolgee-web.umd.min.js +143 -0
  62. package/dist/tolgee-web.umd.min.js.map +1 -0
  63. package/lib/BackendFetch.d.ts +4 -0
  64. package/lib/BrowserExtensionPlugin/BrowserExtensionPlugin.d.ts +8 -0
  65. package/lib/BrowserExtensionPlugin/constants.d.ts +3 -0
  66. package/lib/BrowserExtensionPlugin/loadInContextLib.d.ts +1 -0
  67. package/lib/ContextUi.d.ts +2 -0
  68. package/lib/DevBackend.d.ts +2 -0
  69. package/lib/DevTools.d.ts +3 -0
  70. package/lib/InContextTools.d.ts +2 -0
  71. package/lib/InvisibleObserver.d.ts +2 -0
  72. package/lib/LanguageDetector.d.ts +3 -0
  73. package/lib/LanguageStorage.d.ts +3 -0
  74. package/lib/TextObserver.d.ts +2 -0
  75. package/lib/WebTolgee.d.ts +3 -0
  76. package/lib/constants.d.ts +5 -0
  77. package/lib/index.d.ts +5 -0
  78. package/lib/observers/general/DomHelper.d.ts +4 -0
  79. package/lib/observers/general/ElementHighlighter.d.ts +10 -0
  80. package/lib/observers/general/ElementMeta.d.ts +3 -0
  81. package/lib/observers/general/ElementRegistry.d.ts +11 -0
  82. package/lib/observers/general/ElementStore.d.ts +10 -0
  83. package/lib/observers/general/GeneralObserver.d.ts +12 -0
  84. package/lib/observers/general/MouseEventHandler.d.ts +13 -0
  85. package/lib/observers/general/NodeHandler.d.ts +6 -0
  86. package/lib/observers/general/helpers.d.ts +7 -0
  87. package/lib/observers/invisible/InvisibleWrapper.d.ts +2 -0
  88. package/lib/observers/invisible/ValueMemory.d.ts +5 -0
  89. package/lib/observers/invisible/encoderPolyfill.d.ts +8 -0
  90. package/lib/observers/invisible/secret.d.ts +6 -0
  91. package/lib/observers/text/TextWrapper.d.ts +8 -0
  92. package/lib/observers/text/helpers.d.ts +3 -0
  93. package/lib/tools/decodeApiKey.d.ts +1 -0
  94. package/lib/tools/extension.d.ts +28 -0
  95. package/lib/typedIndex.d.ts +11 -0
  96. package/lib/types.d.ts +28 -0
  97. package/lib/ui/KeyContextMenu/KeyContextMenu.d.ts +19 -0
  98. package/lib/ui/KeyDialog/KeyDialog.d.ts +23 -0
  99. package/lib/ui/KeyDialog/KeyForm.d.ts +2 -0
  100. package/lib/ui/KeyDialog/LanguageSelect.d.ts +2 -0
  101. package/lib/ui/KeyDialog/NewWindow.d.ts +2 -0
  102. package/lib/ui/KeyDialog/NsSelect.d.ts +9 -0
  103. package/lib/ui/KeyDialog/ScreenshotGallery/ExtensionPrompt.d.ts +6 -0
  104. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotDetail.d.ts +8 -0
  105. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotDropzone.d.ts +6 -0
  106. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotGallery.d.ts +2 -0
  107. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotThumbnail.d.ts +8 -0
  108. package/lib/ui/KeyDialog/ScreenshotGallery/utils.d.ts +3 -0
  109. package/lib/ui/KeyDialog/TranslationDialog.d.ts +2 -0
  110. package/lib/ui/KeyDialog/TranslationDialogContextProvider.d.ts +128 -0
  111. package/lib/ui/KeyDialog/TranslationDialogWrapper.d.ts +2 -0
  112. package/lib/ui/KeyDialog/TranslationFields.d.ts +2 -0
  113. package/lib/ui/KeyDialog/languageHelpers.d.ts +12 -0
  114. package/lib/ui/KeyDialog/tools.d.ts +3 -0
  115. package/lib/ui/ThemeProvider.d.ts +2 -0
  116. package/lib/ui/client/QueryProvider.d.ts +12 -0
  117. package/lib/ui/client/apiSchema.generated.d.ts +3283 -0
  118. package/lib/ui/client/client.d.ts +5 -0
  119. package/lib/ui/client/types.d.ts +58 -0
  120. package/lib/ui/client/useQueryApi.d.ts +84 -0
  121. package/lib/ui/common/BodyEnd.d.ts +13 -0
  122. package/lib/ui/common/FieldTitle.d.ts +2 -0
  123. package/lib/ui/common/LoadingButton.d.ts +7 -0
  124. package/lib/ui/index.d.ts +13 -0
  125. package/lib/ui/tools/createProvider.d.ts +5 -0
  126. package/lib/ui/tools/isLanguagePermitted.d.ts +1 -0
  127. package/lib/ui/tools/sleep.d.ts +1 -0
  128. package/package.json +91 -0
  129. package/src/BackendFetch.ts +64 -0
  130. package/src/BrowserExtensionPlugin/BrowserExtensionPlugin.ts +98 -0
  131. package/src/BrowserExtensionPlugin/constants.ts +3 -0
  132. package/src/BrowserExtensionPlugin/loadInContextLib.ts +32 -0
  133. package/src/ContextUi.ts +7 -0
  134. package/src/DevBackend.ts +30 -0
  135. package/src/DevTools.ts +9 -0
  136. package/src/InContextTools.ts +20 -0
  137. package/src/InvisibleObserver.ts +16 -0
  138. package/src/LanguageDetector.test.ts +19 -0
  139. package/src/LanguageDetector.ts +32 -0
  140. package/src/LanguageStorage.ts +23 -0
  141. package/src/TextObserver.ts +45 -0
  142. package/src/WebTolgee.ts +8 -0
  143. package/src/__test__/browser.extension.test.ts +70 -0
  144. package/src/__test__/observer.test.ts +13 -0
  145. package/src/__test__/testObserver.ts +106 -0
  146. package/src/__test__/testRetranslate.ts +47 -0
  147. package/src/constants.ts +12 -0
  148. package/src/index.ts +8 -0
  149. package/src/observers/general/DomHelper.ts +46 -0
  150. package/src/observers/general/ElementHighlighter.ts +72 -0
  151. package/src/observers/general/ElementMeta.ts +17 -0
  152. package/src/observers/general/ElementRegistry.ts +159 -0
  153. package/src/observers/general/ElementStore.ts +34 -0
  154. package/src/observers/general/GeneralObserver.ts +133 -0
  155. package/src/observers/general/MouseEventHandler.ts +199 -0
  156. package/src/observers/general/NodeHandler.ts +39 -0
  157. package/src/observers/general/helpers.ts +65 -0
  158. package/src/observers/invisible/InvisibleWrapper.test.ts +17 -0
  159. package/src/observers/invisible/InvisibleWrapper.ts +96 -0
  160. package/src/observers/invisible/ValueMemory.test.ts +25 -0
  161. package/src/observers/invisible/ValueMemory.ts +20 -0
  162. package/src/observers/invisible/encoderPolyfill.ts +96 -0
  163. package/src/observers/invisible/secret.test.ts +61 -0
  164. package/src/observers/invisible/secret.ts +68 -0
  165. package/src/observers/text/TextWrapper.ts +258 -0
  166. package/src/observers/text/helpers.ts +56 -0
  167. package/src/tools/decodeApiKey.test.ts +14 -0
  168. package/src/tools/decodeApiKey.ts +74 -0
  169. package/src/tools/extension.test.ts +159 -0
  170. package/src/tools/extension.ts +119 -0
  171. package/src/typedIndex.ts +13 -0
  172. package/src/types.ts +33 -0
  173. package/src/ui/KeyContextMenu/KeyContextMenu.test.ts +51 -0
  174. package/src/ui/KeyContextMenu/KeyContextMenu.tsx +108 -0
  175. package/src/ui/KeyDialog/KeyDialog.tsx +67 -0
  176. package/src/ui/KeyDialog/KeyForm.tsx +208 -0
  177. package/src/ui/KeyDialog/LanguageSelect.tsx +78 -0
  178. package/src/ui/KeyDialog/NewWindow.tsx +106 -0
  179. package/src/ui/KeyDialog/NsSelect.tsx +67 -0
  180. package/src/ui/KeyDialog/ScreenshotGallery/ExtensionPrompt.tsx +97 -0
  181. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotDetail.tsx +33 -0
  182. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotDropzone.tsx +138 -0
  183. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotGallery.tsx +240 -0
  184. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotThumbnail.tsx +113 -0
  185. package/src/ui/KeyDialog/ScreenshotGallery/utils.ts +17 -0
  186. package/src/ui/KeyDialog/TranslationDialog.tsx +14 -0
  187. package/src/ui/KeyDialog/TranslationDialogContextProvider.tsx +464 -0
  188. package/src/ui/KeyDialog/TranslationDialogWrapper.tsx +44 -0
  189. package/src/ui/KeyDialog/TranslationFields.tsx +113 -0
  190. package/src/ui/KeyDialog/languageHelpers.ts +18 -0
  191. package/src/ui/KeyDialog/tools.ts +30 -0
  192. package/src/ui/ThemeProvider.tsx +71 -0
  193. package/src/ui/client/QueryProvider.tsx +38 -0
  194. package/src/ui/client/apiSchema.generated.ts +3281 -0
  195. package/src/ui/client/client.ts +155 -0
  196. package/src/ui/client/types.ts +113 -0
  197. package/src/ui/client/useQueryApi.ts +121 -0
  198. package/src/ui/common/BodyEnd.tsx +44 -0
  199. package/src/ui/common/FieldTitle.tsx +9 -0
  200. package/src/ui/common/LoadingButton.tsx +45 -0
  201. package/src/ui/index.ts +88 -0
  202. package/src/ui/screenshots/ScreenshotPreview.tsx +18 -0
  203. package/src/ui/tools/createProvider.tsx +54 -0
  204. package/src/ui/tools/isLanguagePermitted.ts +14 -0
  205. package/src/ui/tools/sleep.ts +2 -0
  206. package/types/index.d.ts +9 -0
@@ -0,0 +1,133 @@
1
+ import {
2
+ FallbackNSTranslation,
3
+ TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE,
4
+ TranslationOnClick,
5
+ WrapperInterface,
6
+ ObserverRunProps,
7
+ ObserverOptions,
8
+ } from '@tolgee/core';
9
+
10
+ import { DomHelper } from './DomHelper';
11
+ import { initNodeMeta } from './ElementMeta';
12
+ import { ElementRegistry } from './ElementRegistry';
13
+ import { getNodeText, setNodeText, xPathEvaluate } from './helpers';
14
+ import { NodeHandler } from './NodeHandler';
15
+
16
+ export const GeneralObserver = (
17
+ wrapper: WrapperInterface,
18
+ options: ObserverOptions,
19
+ onClick: TranslationOnClick
20
+ ) => {
21
+ let isObserving = false;
22
+
23
+ const domHelper = DomHelper(options);
24
+ const nodeHandler = NodeHandler(options, wrapper);
25
+ const elementRegistry = ElementRegistry(options, onClick);
26
+
27
+ function handleNodes(nodes: Array<Text | Attr>) {
28
+ for (const textNode of nodes) {
29
+ const oldTextContent = getNodeText(textNode);
30
+ const result = oldTextContent ? wrapper.unwrap(oldTextContent) : null;
31
+ if (result) {
32
+ const { text, keys } = result;
33
+ setNodeText(textNode, text);
34
+ const nodeMeta = initNodeMeta(oldTextContent!, keys);
35
+ const parentElement = domHelper.getSuitableParent(textNode);
36
+ elementRegistry.register(parentElement, textNode, nodeMeta);
37
+ }
38
+ }
39
+ }
40
+
41
+ const handleKeyAttribute = (node: Node) => {
42
+ const xPath = `./descendant-or-self::*[@${TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE}]`;
43
+ const elements = xPathEvaluate(xPath, node) as Element[];
44
+ elements.forEach((element) => {
45
+ const node = element.getAttributeNode(TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE);
46
+ const parentElement = domHelper.getSuitableParent(node as Node);
47
+ elementRegistry.register(parentElement, node as Node, {
48
+ oldTextContent: '',
49
+ keys: [{ key: getNodeText(node as Node)! }],
50
+ keyAttributeOnly: true,
51
+ });
52
+ });
53
+ };
54
+
55
+ const createMutationObserver = () => {
56
+ return new MutationObserver((mutationsList: MutationRecord[]) => {
57
+ if (!isObserving) {
58
+ return;
59
+ }
60
+ for (const mutation of mutationsList) {
61
+ let result: (Attr | Text)[] = [];
62
+ switch (mutation.type) {
63
+ case 'characterData':
64
+ result = nodeHandler.handleText(mutation.target);
65
+ break;
66
+
67
+ case 'childList':
68
+ handleKeyAttribute(mutation.target);
69
+ result = nodeHandler.handleChildList(mutation.target);
70
+ break;
71
+
72
+ case 'attributes':
73
+ handleKeyAttribute(mutation.target);
74
+ result = nodeHandler.handleAttributes(mutation.target);
75
+ break;
76
+ }
77
+ handleNodes(result);
78
+ elementRegistry.refreshAll();
79
+ }
80
+ });
81
+ };
82
+
83
+ let observer: MutationObserver;
84
+
85
+ const run = ({ mouseHighlight }: ObserverRunProps) => {
86
+ if (!observer) {
87
+ observer = createMutationObserver();
88
+ }
89
+ const targetElement = options.targetElement || document.body;
90
+ isObserving = true;
91
+ elementRegistry.run(mouseHighlight);
92
+
93
+ // initially go through all elements
94
+ handleKeyAttribute(targetElement);
95
+ handleNodes(nodeHandler.handleChildList(targetElement));
96
+
97
+ // then observe for changes
98
+ observer.observe(options.targetElement || document.body, {
99
+ attributes: true,
100
+ childList: true,
101
+ subtree: true,
102
+ characterData: true,
103
+ });
104
+ };
105
+
106
+ const stop = () => {
107
+ isObserving = false;
108
+ elementRegistry.stop();
109
+ observer.disconnect();
110
+ };
111
+
112
+ const highlight = (key?: string, ns?: FallbackNSTranslation) => {
113
+ const elements = elementRegistry.findAll(key, ns);
114
+ elements.forEach((el) => el.highlight?.());
115
+
116
+ return {
117
+ unhighlight() {
118
+ elements.forEach((el) => el.unhighlight?.());
119
+ },
120
+ };
121
+ };
122
+
123
+ return Object.freeze({
124
+ run,
125
+ stop,
126
+ wrap: wrapper.wrap,
127
+ unwrap: wrapper.unwrap,
128
+ forEachElement: elementRegistry.forEachElement,
129
+ highlight,
130
+ });
131
+ };
132
+
133
+ export type GeneralObserverType = ReturnType<typeof GeneralObserver>;
@@ -0,0 +1,199 @@
1
+ import { TolgeeElement } from '../../types';
2
+ import { ModifierKey } from '@tolgee/core';
3
+ import { DEVTOOLS_ID } from '../../constants';
4
+ import { ElementStoreType } from './ElementStore';
5
+
6
+ const eCapture = {
7
+ capture: true,
8
+ };
9
+
10
+ const ePassive = {
11
+ capture: true,
12
+ passive: true,
13
+ };
14
+
15
+ type Coordinates = {
16
+ x: number;
17
+ y: number;
18
+ };
19
+
20
+ type Props = {
21
+ highlightKeys: ModifierKey[];
22
+ elementStore: ElementStoreType;
23
+ onClick: (event: MouseEvent, el: TolgeeElement) => void;
24
+ };
25
+
26
+ export const MouseEventHandler = ({
27
+ highlightKeys,
28
+ elementStore,
29
+ onClick,
30
+ }: Props) => {
31
+ let keysDown = new Set<ModifierKey>();
32
+ let highlighted: TolgeeElement | undefined;
33
+ // let mouseOnChanged = new EventEmitterImpl<Element>();
34
+ // let keysChanged: EventEmitterImpl<boolean> =
35
+ // new EventEmitterImpl<boolean>();
36
+ let cursorPosition: Coordinates | undefined;
37
+
38
+ const highlight = (el: TolgeeElement | undefined) => {
39
+ if (highlighted !== el) {
40
+ unhighlight();
41
+ const meta = elementStore.get(el);
42
+ if (meta) {
43
+ meta.preventClean = true;
44
+ meta.highlight?.();
45
+ highlighted = el;
46
+ // mouseOnChanged.emit(el);
47
+ }
48
+ }
49
+ };
50
+
51
+ const unhighlight = () => {
52
+ const meta = elementStore.get(highlighted);
53
+ if (meta) {
54
+ meta.preventClean = false;
55
+ meta.unhighlight?.();
56
+ highlighted = undefined;
57
+ // mouseOnChanged.emit(highlighted);
58
+ }
59
+ };
60
+
61
+ function updateHighlight() {
62
+ const position = cursorPosition;
63
+
64
+ let newHighlighted: TolgeeElement | undefined;
65
+ if (position && areKeysDown()) {
66
+ const element = document.elementFromPoint(position.x, position.y);
67
+ if (element) {
68
+ newHighlighted = getClosestTolgeeElement(element);
69
+ }
70
+ }
71
+ highlight(newHighlighted);
72
+ }
73
+
74
+ function updateCursorPosition(position: Coordinates) {
75
+ cursorPosition = position;
76
+ updateHighlight();
77
+ }
78
+
79
+ const blockEvents = (e: MouseEvent) => {
80
+ if (areKeysDown() && !isInUiDialog(e.target as Element)) {
81
+ e.stopPropagation();
82
+ e.preventDefault();
83
+ }
84
+ };
85
+ const onMouseMove = (e: MouseEvent) => {
86
+ updateCursorPosition({ x: e.clientX, y: e.clientY });
87
+ };
88
+ const onBlur = () => {
89
+ keysDown = new Set();
90
+ // keysChanged.emit(areKeysDown());
91
+ updateHighlight();
92
+ };
93
+ const onKeyDown = (e: KeyboardEvent) => {
94
+ const modifierKey = e.key as unknown as ModifierKey;
95
+ if (modifierKey !== undefined) {
96
+ keysDown.add(modifierKey);
97
+ // keysChanged.emit(areKeysDown());
98
+ }
99
+ updateHighlight();
100
+ };
101
+ const onKeyUp = (e: KeyboardEvent) => {
102
+ keysDown.delete(e.key as unknown as ModifierKey);
103
+ // keysChanged.emit(areKeysDown());
104
+ updateHighlight();
105
+ };
106
+ const onScroll = () => {
107
+ const meta = elementStore.get(highlighted);
108
+ meta?.highlight?.();
109
+ };
110
+ const handleClick = (e: MouseEvent) => {
111
+ blockEvents(e);
112
+ if (areKeysDown()) {
113
+ const element = getClosestTolgeeElement(e.target as TolgeeElement);
114
+ if (element && element === highlighted) {
115
+ onClick(e, element);
116
+ unhighlight();
117
+ }
118
+ }
119
+ };
120
+
121
+ function initEventListeners() {
122
+ window.addEventListener('blur', onBlur, eCapture);
123
+ window.addEventListener('keydown', onKeyDown, eCapture);
124
+ window.addEventListener('keyup', onKeyUp, eCapture);
125
+ window.addEventListener('mousemove', onMouseMove, ePassive);
126
+ window.addEventListener('scroll', onScroll, ePassive);
127
+ window.addEventListener('click', handleClick, eCapture);
128
+
129
+ window.addEventListener('mouseenter', blockEvents, eCapture);
130
+ window.addEventListener('mouseover', blockEvents, eCapture);
131
+ window.addEventListener('mouseout', blockEvents, eCapture);
132
+ window.addEventListener('mouseleave', blockEvents, eCapture);
133
+ window.addEventListener('mousedown', blockEvents, eCapture);
134
+ window.addEventListener('mouseup', blockEvents, eCapture);
135
+ }
136
+
137
+ function removeEventListeners() {
138
+ window.removeEventListener('blur', onBlur, eCapture);
139
+ window.removeEventListener('keydown', onKeyDown, eCapture);
140
+ window.removeEventListener('keyup', onKeyUp, eCapture);
141
+ window.removeEventListener('mousemove', onMouseMove, ePassive);
142
+ window.removeEventListener('scroll', onScroll, ePassive);
143
+ window.removeEventListener('click', handleClick, eCapture);
144
+
145
+ window.removeEventListener('mouseenter', blockEvents, eCapture);
146
+ window.removeEventListener('mouseover', blockEvents, eCapture);
147
+ window.removeEventListener('mouseout', blockEvents, eCapture);
148
+ window.removeEventListener('mouseleave', blockEvents, eCapture);
149
+ window.removeEventListener('mousedown', blockEvents, eCapture);
150
+ window.removeEventListener('mouseup', blockEvents, eCapture);
151
+ }
152
+
153
+ function isInUiDialog(element: Element) {
154
+ return Boolean(findAncestor(element, (el) => el.id === DEVTOOLS_ID));
155
+ }
156
+
157
+ function getClosestTolgeeElement(
158
+ element: Element
159
+ ): TolgeeElement | undefined {
160
+ return findAncestor(element, (el) =>
161
+ elementStore.get(el as TolgeeElement)
162
+ ) as TolgeeElement;
163
+ }
164
+
165
+ function findAncestor(
166
+ element: Element,
167
+ func: (el: Element) => any
168
+ ): Element | undefined {
169
+ if (func(element)) {
170
+ return element;
171
+ }
172
+ if (element?.parentElement) {
173
+ return findAncestor(element.parentElement, func);
174
+ }
175
+ return undefined;
176
+ }
177
+
178
+ function areKeysDown() {
179
+ for (const key of highlightKeys) {
180
+ if (!keysDown.has(key)) {
181
+ return false;
182
+ }
183
+ }
184
+ return true;
185
+ }
186
+
187
+ function stop() {
188
+ removeEventListeners();
189
+ }
190
+
191
+ function run() {
192
+ initEventListeners();
193
+ }
194
+
195
+ return Object.freeze({
196
+ run,
197
+ stop,
198
+ });
199
+ };
@@ -0,0 +1,39 @@
1
+ import { WrapperInterface, ObserverOptions } from '@tolgee/core';
2
+ import { xPathEvaluate } from './helpers';
3
+
4
+ export const NodeHandler = (
5
+ options: ObserverOptions,
6
+ wrapper: WrapperInterface
7
+ ) => {
8
+ const handleText = (node: Node) => {
9
+ const xPath = wrapper.getTextXPath();
10
+ const nodes = xPathEvaluate(xPath, node);
11
+ return nodes as Text[];
12
+ };
13
+
14
+ const handleAttributes = (node: Node) => {
15
+ let result: Attr[] = [];
16
+ for (const [tag, attributes] of Object.entries(options.tagAttributes)) {
17
+ for (const attribute of attributes) {
18
+ const expression = wrapper.getAttributeXPath({ tag, attribute });
19
+ const nodes = xPathEvaluate(expression, node) as Attr[];
20
+ result = [...result, ...nodes];
21
+ }
22
+ }
23
+ return result;
24
+ };
25
+
26
+ const handleChildList = (node: Node) => {
27
+ let result: (Attr | Text)[] = [];
28
+ result = result.concat(handleAttributes(node));
29
+ result = result.concat(handleText(node));
30
+ // wrappedHandler(node);
31
+ return result;
32
+ };
33
+
34
+ return Object.freeze({
35
+ handleAttributes,
36
+ handleChildList,
37
+ handleText,
38
+ });
39
+ };
@@ -0,0 +1,65 @@
1
+ import { KeyDescriptorInternal } from '@tolgee/core';
2
+
3
+ export function xPathEvaluate<T extends Node>(
4
+ expression: string,
5
+ targetNode: Node
6
+ ): Node[] {
7
+ let node: Node | null;
8
+ const evaluated = document?.evaluate(
9
+ expression,
10
+ targetNode,
11
+ undefined,
12
+ XPathResult.ANY_TYPE
13
+ );
14
+ const result: Node[] = [];
15
+ while ((node = evaluated?.iterateNext?.())) {
16
+ result.push(node as T);
17
+ }
18
+ return result;
19
+ }
20
+
21
+ export function closestElement(node: Element | Text) {
22
+ if (node instanceof Text) {
23
+ return node.parentElement;
24
+ }
25
+ return node;
26
+ }
27
+
28
+ export function getNodeText(node: Node) {
29
+ return node.textContent;
30
+ }
31
+
32
+ export function setNodeText(node: Node, text: string) {
33
+ node.textContent = text;
34
+ }
35
+
36
+ export function nodeContains(descendant: Node, node: Node) {
37
+ if (descendant.contains(node)) {
38
+ return true;
39
+ }
40
+ if (node instanceof Attr) {
41
+ const ownerContainsAttr =
42
+ node.ownerElement &&
43
+ Object.values(node.ownerElement.attributes).indexOf(node) > -1;
44
+ if (descendant.contains(node.ownerElement) && ownerContainsAttr) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+
51
+ export const compareDescriptors = (
52
+ descriptor: KeyDescriptorInternal,
53
+ criteria: KeyDescriptorInternal
54
+ ) => {
55
+ const keyMatches =
56
+ descriptor.key === undefined ||
57
+ criteria.key === undefined ||
58
+ criteria.key === descriptor.key;
59
+ const nsMatches =
60
+ descriptor.ns === undefined ||
61
+ criteria.ns === undefined ||
62
+ descriptor.ns?.findIndex((ns) => criteria.ns?.includes(ns)) !== -1;
63
+
64
+ return keyMatches && nsMatches;
65
+ };
@@ -0,0 +1,17 @@
1
+ jest.autoMockOff();
2
+ import { InvisibleWrapper } from './InvisibleWrapper';
3
+
4
+ describe('invisible wrapper', () => {
5
+ it('wraps and unwraps', () => {
6
+ const wrapper = InvisibleWrapper();
7
+ const key = 'hello';
8
+ const translation = 'world';
9
+ const defaultValue = '!';
10
+
11
+ const wrapped = wrapper.wrap({ key, translation, defaultValue });
12
+ const unwraped = wrapper.unwrap(wrapped);
13
+ expect(unwraped.text).toEqual(translation);
14
+ expect(unwraped.keys[0].key).toEqual(key);
15
+ expect(unwraped.keys[0].defaultValue).toEqual(defaultValue);
16
+ });
17
+ });
@@ -0,0 +1,96 @@
1
+ import type {
2
+ WrapperAttributeXPathGetter,
3
+ KeyAndParams,
4
+ Unwrapped,
5
+ WrapperInterface,
6
+ WrapperWrapFunction,
7
+ TranslateProps,
8
+ } from '@tolgee/core';
9
+ import {
10
+ decodeFromText,
11
+ encodeMessage,
12
+ INVISIBLE_CHARACTERS,
13
+ removeSecrets,
14
+ stringToCodePoints,
15
+ } from './secret';
16
+
17
+ import { ValueMemory } from './ValueMemory';
18
+
19
+ type EncodeValue = {
20
+ // key
21
+ k: string;
22
+ // namespaces
23
+ n: string[] | string | undefined;
24
+ // default value
25
+ d: string | undefined;
26
+ };
27
+
28
+ export const InvisibleWrapper = (): WrapperInterface => {
29
+ const keyMemory = ValueMemory();
30
+
31
+ const unwrap = (text: string): Unwrapped => {
32
+ const keysAndParams = [] as KeyAndParams[];
33
+ const messages = decodeFromText(text);
34
+
35
+ messages.forEach((msg: string) => {
36
+ const [valueCode] = stringToCodePoints(msg);
37
+ const encodedValue = keyMemory.numberToValue(valueCode);
38
+ const { k: key, d: defaultValue, n: ns } = decodeValue(encodedValue);
39
+ keysAndParams.push({
40
+ key,
41
+ defaultValue,
42
+ ns,
43
+ });
44
+ });
45
+
46
+ const result = removeSecrets(text);
47
+
48
+ return { text: result, keys: keysAndParams };
49
+ };
50
+
51
+ const encodeValue = (data: TranslateProps) => {
52
+ const value: EncodeValue = {
53
+ k: data.key,
54
+ n: data.ns,
55
+ d: data.defaultValue,
56
+ };
57
+ return JSON.stringify(value);
58
+ };
59
+
60
+ const decodeValue = (value: string) => {
61
+ return JSON.parse(value) as EncodeValue;
62
+ };
63
+
64
+ const wrap: WrapperWrapFunction = ({
65
+ key,
66
+ defaultValue,
67
+ translation,
68
+ ns,
69
+ }) => {
70
+ const encodedValue = encodeValue({ key, ns, defaultValue });
71
+ const code = keyMemory.valueToNumber(encodedValue);
72
+
73
+ const value = translation || '';
74
+ const invisibleMark = encodeMessage(String.fromCodePoint(code));
75
+
76
+ return typeof value === 'string' ? value + invisibleMark : value;
77
+ };
78
+
79
+ const getTextXPath = () => {
80
+ return `./descendant-or-self::text()[contains(., '${INVISIBLE_CHARACTERS[0]}${INVISIBLE_CHARACTERS[0]}') or contains(., '${INVISIBLE_CHARACTERS[1]}${INVISIBLE_CHARACTERS[0]}')]`;
81
+ };
82
+
83
+ const getAttributeXPath: WrapperAttributeXPathGetter = ({
84
+ tag,
85
+ attribute,
86
+ }) => {
87
+ return `descendant-or-self::${tag}/@${attribute}[contains(., '${INVISIBLE_CHARACTERS[0]}${INVISIBLE_CHARACTERS[0]}') or contains(., '${INVISIBLE_CHARACTERS[1]}${INVISIBLE_CHARACTERS[0]}')]`;
88
+ };
89
+
90
+ return Object.freeze({
91
+ unwrap,
92
+ wrap,
93
+ getTextXPath,
94
+ getAttributeXPath,
95
+ });
96
+ };
@@ -0,0 +1,25 @@
1
+ jest.dontMock('./ValueMemory');
2
+ import { ValueMemory, ValueMemoryInstance } from './ValueMemory';
3
+
4
+ describe('Value memory', () => {
5
+ let valueMemory: ValueMemoryInstance;
6
+
7
+ beforeEach(() => {
8
+ valueMemory = ValueMemory();
9
+ });
10
+
11
+ it('stores key correctly', () => {
12
+ const num = valueMemory.valueToNumber('abcd');
13
+ expect(num).toEqual(0);
14
+ expect(valueMemory.valueToNumber('abcd')).toEqual(0);
15
+ });
16
+
17
+ it('retrieves key correctly', () => {
18
+ const num = valueMemory.valueToNumber('abcd');
19
+ expect(valueMemory.numberToValue(num)).toEqual('abcd');
20
+ });
21
+
22
+ it('retrieves missing key correctly', () => {
23
+ expect(valueMemory.numberToValue(100)).toEqual(undefined);
24
+ });
25
+ });
@@ -0,0 +1,20 @@
1
+ export const ValueMemory = () => {
2
+ const values: string[] = [];
3
+
4
+ const valueToNumber = (key: string) => {
5
+ let index = values.indexOf(key);
6
+ if (index === -1) {
7
+ index = values.length;
8
+ values.push(key);
9
+ }
10
+ return index;
11
+ };
12
+
13
+ const numberToValue = (num: number) => {
14
+ return values[num];
15
+ };
16
+
17
+ return Object.freeze({ valueToNumber, numberToValue });
18
+ };
19
+
20
+ export type ValueMemoryInstance = ReturnType<typeof ValueMemory>;
@@ -0,0 +1,96 @@
1
+ // TextEncoder/TextDecoder polyfills for utf-8 - an implementation of TextEncoder/TextDecoder APIs
2
+ // Written in 2013 by Viktor Mukhachev <vic99999@yandex.ru>
3
+ // To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
4
+ // You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
5
+
6
+ // Some important notes about the polyfill below:
7
+ // Native TextEncoder/TextDecoder implementation is overwritten
8
+ // String.prototype.codePointAt polyfill not included, as well as String.fromCodePoint
9
+ // TextEncoder.prototype.encode returns a regular array instead of Uint8Array
10
+ // No options (fatal of the TextDecoder constructor and stream of the TextDecoder.prototype.decode method) are supported.
11
+ // TextDecoder.prototype.decode does not valid byte sequences
12
+ // This is a demonstrative implementation not intended to have the best performance
13
+
14
+ // http://encoding.spec.whatwg.org/#textencoder
15
+
16
+ // http://encoding.spec.whatwg.org/#textencoder
17
+
18
+ function PTextEncoder() {}
19
+
20
+ PTextEncoder.prototype.encode = function (string: string) {
21
+ const octets = [];
22
+ const length = string.length;
23
+ let i = 0;
24
+ while (i < length) {
25
+ const codePoint = string.codePointAt(i)!;
26
+ let c = 0;
27
+ let bits = 0;
28
+ if (codePoint <= 0x0000007f) {
29
+ c = 0;
30
+ bits = 0x00;
31
+ } else if (codePoint <= 0x000007ff) {
32
+ c = 6;
33
+ bits = 0xc0;
34
+ } else if (codePoint <= 0x0000ffff) {
35
+ c = 12;
36
+ bits = 0xe0;
37
+ } else if (codePoint <= 0x001fffff) {
38
+ c = 18;
39
+ bits = 0xf0;
40
+ }
41
+ octets.push(bits | (codePoint >> c));
42
+ c -= 6;
43
+ while (c >= 0) {
44
+ octets.push(0x80 | ((codePoint >> c) & 0x3f));
45
+ c -= 6;
46
+ }
47
+ i += codePoint >= 0x10000 ? 2 : 1;
48
+ }
49
+ return octets;
50
+ };
51
+
52
+ function PTextDecoder() {}
53
+
54
+ PTextDecoder.prototype.decode = function (octets: any[]) {
55
+ let string = '';
56
+ let i = 0;
57
+ while (i < octets.length) {
58
+ let octet = octets[i];
59
+ let bytesNeeded = 0;
60
+ let codePoint = 0;
61
+ if (octet <= 0x7f) {
62
+ bytesNeeded = 0;
63
+ codePoint = octet & 0xff;
64
+ } else if (octet <= 0xdf) {
65
+ bytesNeeded = 1;
66
+ codePoint = octet & 0x1f;
67
+ } else if (octet <= 0xef) {
68
+ bytesNeeded = 2;
69
+ codePoint = octet & 0x0f;
70
+ } else if (octet <= 0xf4) {
71
+ bytesNeeded = 3;
72
+ codePoint = octet & 0x07;
73
+ }
74
+ if (octets.length - i - bytesNeeded > 0) {
75
+ let k = 0;
76
+ while (k < bytesNeeded) {
77
+ octet = octets[i + k + 1];
78
+ codePoint = (codePoint << 6) | (octet & 0x3f);
79
+ k += 1;
80
+ }
81
+ } else {
82
+ codePoint = 0xfffd;
83
+ bytesNeeded = octets.length - i;
84
+ }
85
+ string += String.fromCodePoint(codePoint);
86
+ i += bytesNeeded + 1;
87
+ }
88
+ return string;
89
+ };
90
+
91
+ export const Encoder = (typeof TextEncoder === 'undefined'
92
+ ? PTextEncoder
93
+ : TextEncoder) as unknown as typeof TextEncoder;
94
+ export const Decoder = (typeof TextDecoder === 'undefined'
95
+ ? PTextDecoder
96
+ : TextDecoder) as unknown as typeof TextDecoder;