Skip to content
Snippets Groups Projects
markables.directive.ts 4.25 KiB
Newer Older
  • Learn to ignore specific revisions
  • jojohoch's avatar
    jojohoch committed
    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;
    
    jojohoch's avatar
    jojohoch committed
      @Input() savedMarks!: string[];
    
    jojohoch's avatar
    jojohoch committed
    
      constructor(
        private markableService: MarkableService,
        private renderer: Renderer2,
        private applicationRef: ApplicationRef) { }
    
      ngAfterViewInit(): void {
        const nodes = MarkablesDirective.findNodes(this.elementComponent.textContainerRef.nativeElement.childNodes);
    
    jojohoch's avatar
    jojohoch committed
        const markablesContainers = this.getMarkablesContainers(nodes);
    
    jojohoch's avatar
    jojohoch committed
        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;
    
    jojohoch's avatar
    jojohoch committed
          componentRef.instance.markablesChange.subscribe(() => {
            this.elementComponent.elementValueChanged.emit(
              {
                id: this.elementComponent.elementModel.id,
                value: this.markableService.markables
              }
            );
          });
    
    jojohoch's avatar
    jojohoch committed
          this.applicationRef.attachView(componentRef.hostView);
        });
      }
    
    
    jojohoch's avatar
    jojohoch committed
      private getMarkablesContainers(nodes: Node[]): MarkablesContainer[] {
    
    jojohoch's avatar
    jojohoch committed
        const markablesContainers: MarkablesContainer[] = [];
        let wordsCount = 0;
        nodes.forEach((node: Node) => {
    
    jojohoch's avatar
    jojohoch committed
          const currentNodes = this.getMarkablesContainer(node, wordsCount);
    
    jojohoch's avatar
    jojohoch committed
          wordsCount += currentNodes.markables.length;
          markablesContainers.push(currentNodes);
        });
        return markablesContainers;
      }
    
    
    jojohoch's avatar
    jojohoch committed
      private getMarkablesContainer(node: Node, wordsCount: number): MarkablesContainer {
    
    jojohoch's avatar
    jojohoch committed
        return {
          node: node,
    
    jojohoch's avatar
    jojohoch committed
          markables: this.getMarkables(node.textContent || '', wordsCount)
    
    jojohoch's avatar
    jojohoch committed
      private getMarkables(text: string, startIndex: number): Markable[] {
    
    jojohoch's avatar
    jojohoch committed
        const markables: Markable[] = [];
    
    jojohoch's avatar
    jojohoch committed
        const wordsWithWhitespace = text?.match(/(\s*\S+\s*)|(s+\S*\s*)|(s*\S*\s+)/g);
    
    jojohoch's avatar
    jojohoch committed
        wordsWithWhitespace?.forEach((wordWithWhitespace: string, index: number) => {
    
    jojohoch's avatar
    jojohoch committed
          const prefix = wordWithWhitespace.match(/\s+(?=[^,]*\S*)/);
          const word = wordWithWhitespace.match(/\S+/);
          const suffix = wordWithWhitespace.match(/[^\S]\s*$/);
    
    jojohoch's avatar
    jojohoch committed
          const id = startIndex + index;
    
    jojohoch's avatar
    jojohoch committed
          const markedWord = this.getMarkedValueById(id);
    
    jojohoch's avatar
    jojohoch committed
          markables.push(
            {
    
    jojohoch's avatar
    jojohoch committed
              id: id,
    
    jojohoch's avatar
    jojohoch committed
              prefix: prefix ? prefix[0] : '',
              word: word ? word[0] : '',
    
    jojohoch's avatar
    jojohoch committed
              suffix: suffix ? suffix[0] : '',
    
              isActive: !!(word && word[0].length),
    
    jojohoch's avatar
    jojohoch committed
              marked: markedWord
    
    jojohoch's avatar
    jojohoch committed
            }
          );
        });
        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;
      }
    
    jojohoch's avatar
    jojohoch committed
    
      private getMarkedValueById(id: number): boolean {
        return this.savedMarks.map((mark: string) => mark.split('-')[0]).includes(id.toString());
      }