Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import {
AfterViewInit, ApplicationRef, createComponent, Directive, Input, Renderer2
} from '@angular/core';
import {
MarkablesContainerComponent
} from 'player/src/app/components/elements/markables-container/markables-container.component';
import { TextComponent } from 'common/components/text/text.component';
import { Markable, MarkablesContainer } from 'player/src/app/models/markable.interface';
import { MarkableService } from 'player/src/app/services/markable.service';
@Directive({
selector: '[markables]',
standalone: true
})
export class MarkablesDirective implements AfterViewInit {
@Input() elementComponent!: TextComponent;
constructor(
private markableService: MarkableService,
private renderer: Renderer2,
private applicationRef: ApplicationRef) { }
ngAfterViewInit(): void {
const nodes = MarkablesDirective.findNodes(this.elementComponent.textContainerRef.nativeElement.childNodes);
const markablesContainers = MarkablesDirective.getMarkablesContainers(nodes);
this.markableService.markables = markablesContainers
.flatMap((markablesContainer: MarkablesContainer) => markablesContainer.markables);
this.createComponents(markablesContainers);
}
createComponents(markablesContainers: MarkablesContainer[]): void {
markablesContainers.forEach((markablesContainer: MarkablesContainer) => {
const node = markablesContainer.node;
const markableContainerElement = this.renderer.createElement('markable-container');
node.parentNode?.replaceChild(markableContainerElement, node);
const environmentInjector = this.applicationRef.injector;
const componentRef = createComponent(MarkablesContainerComponent, {
environmentInjector,
hostElement: markableContainerElement
});
componentRef.instance.markables = markablesContainer.markables;
this.applicationRef.attachView(componentRef.hostView);
});
}
private static getMarkablesContainers(nodes: Node[]): MarkablesContainer[] {
const markablesContainers: MarkablesContainer[] = [];
let wordsCount = 0;
nodes.forEach((node: Node) => {
const currentNodes = MarkablesDirective.getMarkablesContainer(node, wordsCount);
wordsCount += currentNodes.markables.length;
markablesContainers.push(currentNodes);
});
return markablesContainers;
}
private static getMarkablesContainer(node: Node, wordsCount: number): MarkablesContainer {
return {
node: node,
markables: MarkablesDirective.getMarkables(node.textContent || '', wordsCount)
};
}
private static getMarkables(text: string, startIndex: number): Markable[] {
const markables: Markable[] = [];
const wordsWithWhitespace = text?.match(/\s*\S+\s*/g);
wordsWithWhitespace?.forEach((wordWithWhitespace: string, index: number) => {
const prefix = wordWithWhitespace.match(/\s(?=[^,]*\S*)/g);
const word = wordWithWhitespace.match(/\S+/g);
const after = wordWithWhitespace.match(/[^\S]\s*$/g);
markables.push(
{
id: startIndex + index,
prefix: prefix ? prefix[0] : '',
word: word ? word[0] : '',
suffix: after ? after[0] : '',
marked: false
}
);
});
return markables;
}
static findNodes(childList: Node[] | NodeListOf<ChildNode>): Node[] {
const nodes: Node[] = [];
childList.forEach((node: Node) => {
if (node.nodeType === Node.TEXT_NODE && !nodes.includes(node)) {
nodes.push(node);
}
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.childNodes.length) {
nodes.push(...MarkablesDirective.findNodes(node.childNodes));
} else if (!nodes.includes(node)) {
nodes.push(node);
}
}
});
return nodes;
}
}