@tolgee/core 4.7.0 → 4.7.2
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.
- package/dist/tolgee.cjs.js +2 -4
- package/dist/tolgee.cjs.js.map +1 -1
- package/dist/tolgee.cjs.min.js +1 -1
- package/dist/tolgee.cjs.min.js.map +1 -1
- package/dist/{tolgee.esm.js → tolgee.esm.min.mjs} +2 -2
- package/dist/tolgee.esm.min.mjs.map +1 -0
- package/dist/tolgee.esm.mjs +5690 -0
- package/dist/tolgee.esm.mjs.map +1 -0
- package/dist/tolgee.umd.js +2 -4
- package/dist/tolgee.umd.js.map +1 -1
- package/dist/tolgee.umd.min.js +1 -1
- package/dist/tolgee.umd.min.js.map +1 -1
- package/package.json +10 -9
- package/src/Constants/Global.ts +9 -0
- package/src/Constants/ModifierKey.ts +6 -0
- package/src/Errors/ApiHttpError.ts +8 -0
- package/src/Observer.test.ts +119 -0
- package/src/Observer.ts +68 -0
- package/src/Properties.test.ts +150 -0
- package/src/Properties.ts +112 -0
- package/src/Tolgee.test.ts +473 -0
- package/src/Tolgee.ts +335 -0
- package/src/TolgeeConfig.test.ts +21 -0
- package/src/TolgeeConfig.ts +134 -0
- package/src/__integration/FormatterIcu.test.ts +80 -0
- package/src/__integration/FormatterMissing.ts +54 -0
- package/src/__integration/Tolgee.test.ts +90 -0
- package/src/__integration/TolgeeInvisible.test.ts +145 -0
- package/src/__integration/mockTranslations.ts +6 -0
- package/src/__integration/testConfig.ts +16 -0
- package/src/__testFixtures/classMock.ts +11 -0
- package/src/__testFixtures/createElement.ts +43 -0
- package/src/__testFixtures/createTestDom.ts +25 -0
- package/src/__testFixtures/mocked.ts +25 -0
- package/src/__testFixtures/setupAfterEnv.ts +34 -0
- package/src/helpers/NodeHelper.ts +90 -0
- package/src/helpers/TextHelper.test.ts +62 -0
- package/src/helpers/TextHelper.ts +58 -0
- package/src/helpers/commonTypes.ts +8 -0
- package/src/helpers/encoderPolyfill.ts +96 -0
- package/src/helpers/secret.test.ts +61 -0
- package/src/helpers/secret.ts +68 -0
- package/src/helpers/sleep.ts +2 -0
- package/src/highlighter/HighlightFunctionsInitializer.test.ts +40 -0
- package/src/highlighter/HighlightFunctionsInitializer.ts +61 -0
- package/src/highlighter/MouseEventHandler.test.ts +151 -0
- package/src/highlighter/MouseEventHandler.ts +191 -0
- package/src/highlighter/TranslationHighlighter.test.ts +177 -0
- package/src/highlighter/TranslationHighlighter.ts +113 -0
- package/src/index.ts +10 -0
- package/src/internal.ts +2 -0
- package/src/modules/IcuFormatter.ts +17 -0
- package/src/modules/index.ts +1 -0
- package/src/services/ApiHttpService.ts +85 -0
- package/src/services/CoreService.test.ts +142 -0
- package/src/services/CoreService.ts +76 -0
- package/src/services/DependencyService.test.ts +51 -0
- package/src/services/DependencyService.ts +116 -0
- package/src/services/ElementRegistrar.test.ts +131 -0
- package/src/services/ElementRegistrar.ts +108 -0
- package/src/services/EventEmitter.ts +52 -0
- package/src/services/EventService.ts +14 -0
- package/src/services/ModuleService.ts +14 -0
- package/src/services/ScreenshotService.ts +31 -0
- package/src/services/Subscription.ts +7 -0
- package/src/services/TextService.test.ts +88 -0
- package/src/services/TextService.ts +82 -0
- package/src/services/TranslationService.test.ts +358 -0
- package/src/services/TranslationService.ts +417 -0
- package/src/services/__mocks__/CoreService.ts +17 -0
- package/src/toolsManager/Messages.test.ts +79 -0
- package/src/toolsManager/Messages.ts +60 -0
- package/src/toolsManager/PluginManager.test.ts +108 -0
- package/src/toolsManager/PluginManager.ts +129 -0
- package/src/types/DTOs.ts +25 -0
- package/src/types/apiSchema.generated.ts +6208 -0
- package/src/types.ts +146 -0
- package/src/wrappers/AbstractWrapper.ts +14 -0
- package/src/wrappers/NodeHandler.ts +143 -0
- package/src/wrappers/WrappedHandler.ts +28 -0
- package/src/wrappers/invisible/AttributeHandler.ts +23 -0
- package/src/wrappers/invisible/Coder.ts +65 -0
- package/src/wrappers/invisible/ContentHandler.ts +15 -0
- package/src/wrappers/invisible/CoreHandler.ts +17 -0
- package/src/wrappers/invisible/InvisibleWrapper.ts +59 -0
- package/src/wrappers/invisible/ValueMemory.test.ts +25 -0
- package/src/wrappers/invisible/ValueMemory.ts +16 -0
- package/src/wrappers/text/AttributeHandler.test.ts +117 -0
- package/src/wrappers/text/AttributeHandler.ts +25 -0
- package/src/wrappers/text/Coder.test.ts +298 -0
- package/src/wrappers/text/Coder.ts +202 -0
- package/src/wrappers/text/ContentHandler.test.ts +185 -0
- package/src/wrappers/text/ContentHandler.ts +21 -0
- package/src/wrappers/text/CoreHandler.test.ts +106 -0
- package/src/wrappers/text/CoreHandler.ts +45 -0
- package/src/wrappers/text/TextWrapper.ts +69 -0
- package/dist/tolgee.esm.js.map +0 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Properties } from '../Properties';
|
|
2
|
+
import { EventService } from './EventService';
|
|
3
|
+
import { ApiHttpService } from './ApiHttpService';
|
|
4
|
+
import { TranslationService } from './TranslationService';
|
|
5
|
+
import { TextService } from './TextService';
|
|
6
|
+
import { MouseEventHandler } from '../highlighter/MouseEventHandler';
|
|
7
|
+
import { TranslationHighlighter } from '../highlighter/TranslationHighlighter';
|
|
8
|
+
import { ElementRegistrar } from './ElementRegistrar';
|
|
9
|
+
import { Observer } from '../Observer';
|
|
10
|
+
import { CoreService } from './CoreService';
|
|
11
|
+
import { TolgeeConfig } from '../TolgeeConfig';
|
|
12
|
+
import { PluginManager } from '../toolsManager/PluginManager';
|
|
13
|
+
import { Messages } from '../toolsManager/Messages';
|
|
14
|
+
import { HighlightFunctionsInitializer } from '../highlighter/HighlightFunctionsInitializer';
|
|
15
|
+
import { ScreenshotService } from './ScreenshotService';
|
|
16
|
+
import { ModuleService } from './ModuleService';
|
|
17
|
+
import { TextWrapper } from '../wrappers/text/TextWrapper';
|
|
18
|
+
import { NodeHelper } from '../helpers/NodeHelper';
|
|
19
|
+
import { AbstractWrapper } from '../wrappers/AbstractWrapper';
|
|
20
|
+
import { InvisibleWrapper } from '../wrappers/invisible/InvisibleWrapper';
|
|
21
|
+
|
|
22
|
+
export class DependencyService {
|
|
23
|
+
public properties: Properties = new Properties();
|
|
24
|
+
public eventService: EventService = new EventService();
|
|
25
|
+
public apiHttpService: ApiHttpService = new ApiHttpService(this.properties);
|
|
26
|
+
public mouseEventHandler = new MouseEventHandler(this);
|
|
27
|
+
public moduleService = new ModuleService();
|
|
28
|
+
public coreService: CoreService = new CoreService(
|
|
29
|
+
this.properties,
|
|
30
|
+
this.apiHttpService
|
|
31
|
+
);
|
|
32
|
+
public screenshotService = new ScreenshotService(
|
|
33
|
+
this.coreService,
|
|
34
|
+
this.apiHttpService
|
|
35
|
+
);
|
|
36
|
+
public translationService: TranslationService = new TranslationService(
|
|
37
|
+
this.properties,
|
|
38
|
+
this.coreService,
|
|
39
|
+
this.apiHttpService,
|
|
40
|
+
this.eventService
|
|
41
|
+
);
|
|
42
|
+
public textService: TextService = new TextService(
|
|
43
|
+
this.properties,
|
|
44
|
+
this.translationService,
|
|
45
|
+
this.moduleService
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
public highlightFunctionInitializer = new HighlightFunctionsInitializer(
|
|
49
|
+
this.properties
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
public translationHighlighter = new TranslationHighlighter(this);
|
|
53
|
+
|
|
54
|
+
public elementRegistrar: ElementRegistrar = new ElementRegistrar(
|
|
55
|
+
this.properties,
|
|
56
|
+
this.translationHighlighter,
|
|
57
|
+
this.eventService
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
public messages: Messages = new Messages();
|
|
61
|
+
|
|
62
|
+
public pluginManager: PluginManager = new PluginManager(
|
|
63
|
+
this.messages,
|
|
64
|
+
this.properties,
|
|
65
|
+
this.eventService,
|
|
66
|
+
this.elementRegistrar,
|
|
67
|
+
this.translationService
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
constructor() {
|
|
71
|
+
this.translationHighlighter.pluginManager = this.pluginManager;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public wrapper: AbstractWrapper;
|
|
75
|
+
public observer: Observer;
|
|
76
|
+
|
|
77
|
+
init(config: TolgeeConfig) {
|
|
78
|
+
if (this.properties.config) {
|
|
79
|
+
throw new Error('Duplicate initialization of config');
|
|
80
|
+
}
|
|
81
|
+
this.properties.config = new TolgeeConfig(config);
|
|
82
|
+
if (this.properties.config.wrapperMode === 'invisible') {
|
|
83
|
+
this.wrapper = new InvisibleWrapper(
|
|
84
|
+
this.properties,
|
|
85
|
+
this.elementRegistrar
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
this.wrapper = new TextWrapper(
|
|
89
|
+
this.eventService,
|
|
90
|
+
this.properties,
|
|
91
|
+
this.textService,
|
|
92
|
+
this.elementRegistrar
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.observer = new Observer(
|
|
97
|
+
this.properties,
|
|
98
|
+
this.wrapper,
|
|
99
|
+
this.elementRegistrar
|
|
100
|
+
);
|
|
101
|
+
this.translationService.initStatic();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
run = () => {
|
|
105
|
+
this.mouseEventHandler.run();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
stop = () => {
|
|
109
|
+
this.observer.stopObserving();
|
|
110
|
+
this.elementRegistrar.cleanAll();
|
|
111
|
+
this.mouseEventHandler.stop();
|
|
112
|
+
NodeHelper.unmarkElementAsTargetElement(
|
|
113
|
+
this.properties.config.targetElement
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
jest.dontMock('./ElementRegistrar');
|
|
2
|
+
jest.dontMock('./DependencyService');
|
|
3
|
+
|
|
4
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
5
|
+
import { ElementRegistrar } from './ElementRegistrar';
|
|
6
|
+
import { ElementWithMeta } from '../types';
|
|
7
|
+
import { getMockedInstance } from '@testFixtures/mocked';
|
|
8
|
+
import { TranslationHighlighter } from '../highlighter/TranslationHighlighter';
|
|
9
|
+
import { createElement } from '@testFixtures/createElement';
|
|
10
|
+
import { Properties } from '../Properties';
|
|
11
|
+
import { TOLGEE_ATTRIBUTE_NAME } from '../Constants/Global';
|
|
12
|
+
import { DependencyService } from './DependencyService';
|
|
13
|
+
import { EventEmitterImpl } from './EventEmitter';
|
|
14
|
+
import { EventService } from './EventService';
|
|
15
|
+
|
|
16
|
+
describe('ElementRegistrar', () => {
|
|
17
|
+
let elementRegistrar: ElementRegistrar;
|
|
18
|
+
const mockElementRegisteredEmit = jest.fn();
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
const dependencyStore = new DependencyService();
|
|
22
|
+
dependencyStore.init({});
|
|
23
|
+
elementRegistrar = dependencyStore.elementRegistrar;
|
|
24
|
+
(getMockedInstance(EventService).ELEMENT_REGISTERED as any) = {
|
|
25
|
+
emit: mockElementRegisteredEmit,
|
|
26
|
+
} as any as EventEmitterImpl<ElementWithMeta>;
|
|
27
|
+
getMockedInstance(Properties).config.targetElement = document.body;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('In development mode', () => {
|
|
35
|
+
const element = createElement(1, 1);
|
|
36
|
+
|
|
37
|
+
beforeEach(async () => {
|
|
38
|
+
getMockedInstance(Properties).mode = 'development';
|
|
39
|
+
document.body.append(element);
|
|
40
|
+
await elementRegistrar.register(element);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('will be registered for highlighting in development mode', () => {
|
|
44
|
+
expect(getMockedInstance(TranslationHighlighter).listen).toBeCalledWith(
|
|
45
|
+
element
|
|
46
|
+
);
|
|
47
|
+
expect(getMockedInstance(TranslationHighlighter).listen).toBeCalledTimes(
|
|
48
|
+
1
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('will emit element registered event', () => {
|
|
53
|
+
expect(mockElementRegisteredEmit).toBeCalledTimes(1);
|
|
54
|
+
expect(mockElementRegisteredEmit).toBeCalledWith(element);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('throws error on register element without any node', async () => {
|
|
59
|
+
const element = createElement(0, 0);
|
|
60
|
+
getMockedInstance(Properties).mode = 'development';
|
|
61
|
+
document.body.append(element);
|
|
62
|
+
elementRegistrar.register(element);
|
|
63
|
+
expect((elementRegistrar as any).registeredElements).toBeInstanceOf(Set);
|
|
64
|
+
expect((elementRegistrar as any).registeredElements).not.toContain(element);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('register, clean & refresh methods', () => {
|
|
68
|
+
let mockedElements: ElementWithMeta[];
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
mockedElements = [
|
|
71
|
+
createElement(5, 0),
|
|
72
|
+
createElement(1, 0),
|
|
73
|
+
createElement(3, 0),
|
|
74
|
+
];
|
|
75
|
+
document.body.append(...mockedElements);
|
|
76
|
+
mockedElements.forEach((e) => elementRegistrar.register(e));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('refresh all will remove inactive elements', () => {
|
|
80
|
+
const node = mockedElements[1]._tolgee.nodes.values().next().value;
|
|
81
|
+
node.parentElement.removeChild(node);
|
|
82
|
+
elementRegistrar.refreshAll();
|
|
83
|
+
expect(mockedElements[1]._tolgee).not.toBeDefined();
|
|
84
|
+
expect(mockedElements[1]).not.toHaveAttribute(TOLGEE_ATTRIBUTE_NAME);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('refresh all will remove inactive nodes', () => {
|
|
88
|
+
const node = mockedElements[2]._tolgee.nodes.values().next().value;
|
|
89
|
+
node.parentElement.removeChild(node);
|
|
90
|
+
elementRegistrar.refreshAll();
|
|
91
|
+
expect(mockedElements[2]._tolgee.nodes.size).toEqual(2);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('clean all will clean all elements', () => {
|
|
95
|
+
elementRegistrar.cleanAll();
|
|
96
|
+
for (const mockedElement of mockedElements) {
|
|
97
|
+
expect(mockedElement._tolgee).not.toBeDefined();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("clean all doesn't clean elements with preventClean", () => {
|
|
102
|
+
mockedElements[1]._tolgee.preventClean = true;
|
|
103
|
+
const node = mockedElements[1]._tolgee.nodes.values().next().value;
|
|
104
|
+
node.parentElement.removeChild(node);
|
|
105
|
+
elementRegistrar.refreshAll();
|
|
106
|
+
expect(mockedElements[1]._tolgee).toBeDefined();
|
|
107
|
+
expect(mockedElements[1]).toHaveAttribute(TOLGEE_ATTRIBUTE_NAME);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("refresh all doesn't delete nodes on elements wih preventClean", () => {
|
|
111
|
+
mockedElements[1]._tolgee.preventClean = true;
|
|
112
|
+
const node = mockedElements[1]._tolgee.nodes.values().next().value;
|
|
113
|
+
node.parentElement.removeChild(node);
|
|
114
|
+
elementRegistrar.refreshAll();
|
|
115
|
+
expect(mockedElements[1]._tolgee).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('will register attribute node', () => {
|
|
120
|
+
const inputElement = document.createElement(
|
|
121
|
+
'input'
|
|
122
|
+
) as any as ElementWithMeta;
|
|
123
|
+
document.body.append(inputElement);
|
|
124
|
+
inputElement.setAttribute('_tolgee', '');
|
|
125
|
+
inputElement.setAttribute('placeholder', 'Text');
|
|
126
|
+
inputElement._tolgee = {
|
|
127
|
+
nodes: new Set([inputElement.attributes['placeholder']]),
|
|
128
|
+
};
|
|
129
|
+
elementRegistrar.register(inputElement);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ElementWithMeta } from '../types';
|
|
2
|
+
import { Properties } from '../Properties';
|
|
3
|
+
import { TOLGEE_ATTRIBUTE_NAME } from '../Constants/Global';
|
|
4
|
+
import { TranslationHighlighter } from '../highlighter/TranslationHighlighter';
|
|
5
|
+
import { NodeHelper } from '../helpers/NodeHelper';
|
|
6
|
+
import { EventService } from './EventService';
|
|
7
|
+
import { EventEmitterImpl } from './EventEmitter';
|
|
8
|
+
|
|
9
|
+
export class ElementRegistrar {
|
|
10
|
+
private registeredElements: Set<ElementWithMeta> = new Set();
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
private properties: Properties,
|
|
14
|
+
private translationHighlighter: TranslationHighlighter,
|
|
15
|
+
private eventService: EventService
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
register(element: ElementWithMeta) {
|
|
19
|
+
//ignore element with no active nodes
|
|
20
|
+
if (
|
|
21
|
+
this.getActiveNodes(element).next().value === undefined &&
|
|
22
|
+
!element._tolgee.wrappedWithElementOnlyKey
|
|
23
|
+
) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (
|
|
27
|
+
this.properties.mode === 'development' &&
|
|
28
|
+
!this.registeredElements.has(element)
|
|
29
|
+
) {
|
|
30
|
+
this.translationHighlighter.listen(element);
|
|
31
|
+
}
|
|
32
|
+
this.registeredElements.add(element);
|
|
33
|
+
(
|
|
34
|
+
this.eventService.ELEMENT_REGISTERED as EventEmitterImpl<ElementWithMeta>
|
|
35
|
+
).emit(element);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
refreshAll() {
|
|
39
|
+
for (const element of this.registeredElements) {
|
|
40
|
+
if (!element._tolgee.preventClean) {
|
|
41
|
+
this.cleanElementInactiveNodes(element);
|
|
42
|
+
if (
|
|
43
|
+
element._tolgee.nodes.size === 0 &&
|
|
44
|
+
!element._tolgee.wrappedWithElementOnlyKey
|
|
45
|
+
) {
|
|
46
|
+
this.cleanElement(element);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
cleanAll() {
|
|
53
|
+
for (const registeredElement of this.registeredElements) {
|
|
54
|
+
this.cleanElement(registeredElement);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
findAllByKey(key: string) {
|
|
59
|
+
const result: ElementWithMeta[] = [];
|
|
60
|
+
for (const registeredElement of this.registeredElements) {
|
|
61
|
+
if (registeredElement._tolgee.wrappedWithElementOnlyKey === key) {
|
|
62
|
+
result.push(registeredElement);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
for (const node of registeredElement._tolgee.nodes) {
|
|
66
|
+
if (
|
|
67
|
+
node._tolgee.keys.findIndex(
|
|
68
|
+
(keyWithParams) => keyWithParams.key === key
|
|
69
|
+
) > -1
|
|
70
|
+
) {
|
|
71
|
+
result.push(registeredElement);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private cleanElementInactiveNodes(element: ElementWithMeta) {
|
|
80
|
+
if (this.isElementActive(element)) {
|
|
81
|
+
element._tolgee.nodes = new Set(this.getActiveNodes(element));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private cleanElement(element: ElementWithMeta) {
|
|
87
|
+
if (!element._tolgee.preventClean) {
|
|
88
|
+
if (element._tolgee.highlightEl) {
|
|
89
|
+
element._tolgee.unhighlight();
|
|
90
|
+
}
|
|
91
|
+
element.removeAttribute(TOLGEE_ATTRIBUTE_NAME);
|
|
92
|
+
delete element._tolgee;
|
|
93
|
+
this.registeredElements.delete(element);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private *getActiveNodes(element: ElementWithMeta) {
|
|
98
|
+
for (const node of element._tolgee.nodes) {
|
|
99
|
+
if (NodeHelper.nodeContains(this.properties.config.targetElement, node)) {
|
|
100
|
+
yield node;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private isElementActive(element: ElementWithMeta) {
|
|
106
|
+
return this.properties.config.targetElement.contains(element);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Subscription } from './Subscription';
|
|
2
|
+
|
|
3
|
+
export type CallbackType<T> = (data: T) => Promise<void> | void;
|
|
4
|
+
|
|
5
|
+
export interface EventEmitter<T> {
|
|
6
|
+
subscribe(callback: CallbackType<T>): Subscription;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class EventEmitterImpl<T> {
|
|
10
|
+
private idCounter = 0;
|
|
11
|
+
private _subscriptions: Map<number, CallbackType<T>> = new Map<
|
|
12
|
+
number,
|
|
13
|
+
CallbackType<T>
|
|
14
|
+
>();
|
|
15
|
+
|
|
16
|
+
private get subscriptions() {
|
|
17
|
+
return this._subscriptions;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public emit(data?: T): Promise<void> | void {
|
|
21
|
+
const promiseReturns = [];
|
|
22
|
+
for (const callback of this.subscriptions.values()) {
|
|
23
|
+
const returned = callback(data);
|
|
24
|
+
if (typeof returned?.['then'] === 'function') {
|
|
25
|
+
promiseReturns.push(returned);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (promiseReturns.length === 0) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve) =>
|
|
34
|
+
Promise.all(promiseReturns).then(() => resolve())
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public subscribe(callback: CallbackType<T>) {
|
|
39
|
+
const newId = this.idCounter++;
|
|
40
|
+
const subscription = new Subscription(() => this.unsubscribe(newId));
|
|
41
|
+
this.subscriptions.set(newId, callback);
|
|
42
|
+
return subscription;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private unsubscribe(id: number) {
|
|
46
|
+
const wasPresent = this._subscriptions.delete(id);
|
|
47
|
+
if (!wasPresent) {
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.warn('Event to unsubscribe was not found');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EventEmitter, EventEmitterImpl } from './EventEmitter';
|
|
2
|
+
import { TranslationData } from '../types/DTOs';
|
|
3
|
+
import { ElementWithMeta } from '../types';
|
|
4
|
+
|
|
5
|
+
export class EventService {
|
|
6
|
+
public readonly TRANSLATION_CHANGED: EventEmitter<TranslationData> =
|
|
7
|
+
new EventEmitterImpl<TranslationData>();
|
|
8
|
+
public readonly LANGUAGE_CHANGED: EventEmitter<string> =
|
|
9
|
+
new EventEmitterImpl<string>();
|
|
10
|
+
public readonly LANGUAGE_LOADED: EventEmitter<string> =
|
|
11
|
+
new EventEmitterImpl<string>();
|
|
12
|
+
public readonly ELEMENT_REGISTERED: EventEmitter<ElementWithMeta> =
|
|
13
|
+
new EventEmitterImpl<ElementWithMeta>();
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Formatter, TolgeeModule } from '../types';
|
|
2
|
+
|
|
3
|
+
export class ModuleService {
|
|
4
|
+
formatter: Formatter | null = null;
|
|
5
|
+
|
|
6
|
+
addModule = (module: TolgeeModule) => {
|
|
7
|
+
if (module.type === 'formatter') {
|
|
8
|
+
const instance = new module();
|
|
9
|
+
this.formatter = instance;
|
|
10
|
+
} else {
|
|
11
|
+
throw new Error('Module with unknown type');
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { UploadedImageModel } from '..';
|
|
2
|
+
import { ApiHttpService } from './ApiHttpService';
|
|
3
|
+
import { CoreService } from './CoreService';
|
|
4
|
+
|
|
5
|
+
export class ScreenshotService {
|
|
6
|
+
constructor(
|
|
7
|
+
private coreService: CoreService,
|
|
8
|
+
private apiHttpService: ApiHttpService
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
async uploadImage(blob: Blob) {
|
|
12
|
+
const formData = new FormData();
|
|
13
|
+
|
|
14
|
+
formData.append('image', blob);
|
|
15
|
+
|
|
16
|
+
return this.apiHttpService.postJson('v2/image-upload', undefined, {
|
|
17
|
+
headers: {},
|
|
18
|
+
body: formData,
|
|
19
|
+
}) as Promise<UploadedImageModel>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async deleteImages(ids: number[]) {
|
|
23
|
+
return this.apiHttpService.post(
|
|
24
|
+
`v2/image-upload/${ids.join(',')}`,
|
|
25
|
+
undefined,
|
|
26
|
+
{
|
|
27
|
+
method: 'delete',
|
|
28
|
+
}
|
|
29
|
+
) as Promise<Response>;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
jest.dontMock('./TextService');
|
|
2
|
+
jest.dontMock('../helpers/TextHelper');
|
|
3
|
+
jest.dontMock('./DependencyService');
|
|
4
|
+
jest.dontMock('./ModuleService');
|
|
5
|
+
jest.dontMock('../modules/IcuFormatter');
|
|
6
|
+
|
|
7
|
+
import { Properties } from '../Properties';
|
|
8
|
+
import { TextService } from './TextService';
|
|
9
|
+
import { getMockedInstance } from '@testFixtures/mocked';
|
|
10
|
+
import { TranslationService } from './TranslationService';
|
|
11
|
+
import { DependencyService } from './DependencyService';
|
|
12
|
+
import { IcuFormatter } from '../modules/IcuFormatter';
|
|
13
|
+
|
|
14
|
+
describe('TextService', () => {
|
|
15
|
+
let mockedTranslationReturn = '';
|
|
16
|
+
const params = { param1: 'Dummy param 1', param2: 'Dummy param 2' };
|
|
17
|
+
let expectedTranslated = '';
|
|
18
|
+
let textService: TextService;
|
|
19
|
+
|
|
20
|
+
const getTranslationMock = jest.fn(async () => {
|
|
21
|
+
return mockedTranslationReturn;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const getFromCacheOrCallbackMock = jest.fn(() => {
|
|
25
|
+
return mockedTranslationReturn;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
const depStore = new DependencyService();
|
|
30
|
+
depStore.moduleService.addModule(IcuFormatter);
|
|
31
|
+
textService = depStore.textService;
|
|
32
|
+
mockedTranslationReturn = 'Dummy translated text {param1} {param2}';
|
|
33
|
+
expectedTranslated = mockedTranslationReturn
|
|
34
|
+
.replace('{param1}', params.param1)
|
|
35
|
+
.replace('{param2}', params.param2);
|
|
36
|
+
|
|
37
|
+
getMockedInstance(Properties).config = {
|
|
38
|
+
inputPrefix: '{{',
|
|
39
|
+
inputSuffix: '}}',
|
|
40
|
+
restrictedElements: [],
|
|
41
|
+
tagAttributes: {
|
|
42
|
+
'*': ['aria-label'],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
getMockedInstance(TranslationService).getTranslation = getTranslationMock;
|
|
46
|
+
|
|
47
|
+
getMockedInstance(TranslationService).getFromCacheOrFallback =
|
|
48
|
+
getFromCacheOrCallbackMock;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(async () => {
|
|
52
|
+
jest.clearAllMocks();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('translation functions', () => {
|
|
56
|
+
test('it will translate asynchronously correctly', async () => {
|
|
57
|
+
const translated = await textService.translate(
|
|
58
|
+
mockedTranslationReturn,
|
|
59
|
+
params,
|
|
60
|
+
`en`,
|
|
61
|
+
true,
|
|
62
|
+
'Default'
|
|
63
|
+
);
|
|
64
|
+
expect(translated).toEqual(expectedTranslated);
|
|
65
|
+
expect(getTranslationMock).toBeCalledWith(
|
|
66
|
+
'Dummy translated text {param1} {param2}',
|
|
67
|
+
'en',
|
|
68
|
+
'Default'
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('it will translate synchronously correctly', () => {
|
|
73
|
+
const translated = textService.instant(
|
|
74
|
+
mockedTranslationReturn,
|
|
75
|
+
params,
|
|
76
|
+
'en',
|
|
77
|
+
true,
|
|
78
|
+
'Default'
|
|
79
|
+
);
|
|
80
|
+
expect(translated).toEqual(expectedTranslated);
|
|
81
|
+
expect(getFromCacheOrCallbackMock).toBeCalledWith(
|
|
82
|
+
'Dummy translated text {param1} {param2}',
|
|
83
|
+
'en',
|
|
84
|
+
'Default'
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { TranslationTags, TranslationParamsTags } from '../types';
|
|
2
|
+
import { TranslationService } from './TranslationService';
|
|
3
|
+
import { Properties } from '../Properties';
|
|
4
|
+
import { ModuleService } from './ModuleService';
|
|
5
|
+
|
|
6
|
+
export class TextService {
|
|
7
|
+
constructor(
|
|
8
|
+
private properties: Properties,
|
|
9
|
+
private translationService: TranslationService,
|
|
10
|
+
private moduleService: ModuleService
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
async translate(
|
|
14
|
+
key: string,
|
|
15
|
+
params: TranslationParamsTags<any>,
|
|
16
|
+
lang = this.properties.currentLanguage,
|
|
17
|
+
orEmpty?: boolean,
|
|
18
|
+
defaultValue?: string
|
|
19
|
+
) {
|
|
20
|
+
const translation = await this.translationService.getTranslation(
|
|
21
|
+
key,
|
|
22
|
+
lang,
|
|
23
|
+
defaultValue
|
|
24
|
+
);
|
|
25
|
+
return this.formatTranslation(key, translation, params, lang, orEmpty);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
instant(
|
|
29
|
+
key: string,
|
|
30
|
+
params: TranslationParamsTags<any>,
|
|
31
|
+
lang = this.properties.currentLanguage,
|
|
32
|
+
orEmpty?: boolean,
|
|
33
|
+
defaultValue?: string
|
|
34
|
+
) {
|
|
35
|
+
const translation = this.translationService.getFromCacheOrFallback(
|
|
36
|
+
key,
|
|
37
|
+
lang,
|
|
38
|
+
defaultValue
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return this.formatTranslation(key, translation, params, lang, orEmpty);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private formatTranslation(
|
|
45
|
+
key: string,
|
|
46
|
+
translation: string | undefined,
|
|
47
|
+
params: TranslationParamsTags<any> | undefined,
|
|
48
|
+
lang: string | undefined,
|
|
49
|
+
orEmpty: boolean | undefined
|
|
50
|
+
) {
|
|
51
|
+
if (translation !== undefined) {
|
|
52
|
+
return this.format(translation, params, lang);
|
|
53
|
+
}
|
|
54
|
+
if (!orEmpty) {
|
|
55
|
+
return key;
|
|
56
|
+
}
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private readonly format = (
|
|
61
|
+
translation: string,
|
|
62
|
+
params: TranslationParamsTags<any>,
|
|
63
|
+
lang: string | undefined
|
|
64
|
+
): TranslationTags<any> => {
|
|
65
|
+
try {
|
|
66
|
+
// try to format the text
|
|
67
|
+
let result: TranslationTags<any> = translation;
|
|
68
|
+
if (this.moduleService.formatter) {
|
|
69
|
+
result = this.moduleService.formatter.format({
|
|
70
|
+
translation: result,
|
|
71
|
+
params,
|
|
72
|
+
language: lang || this.properties.currentLanguage,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return result as TranslationTags<any>;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
// if string cannot be formatted, throw error
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.error(e);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|