diff --git a/projects/player/src/app/components/element-text-group/element-text-group.component.ts b/projects/player/src/app/components/element-text-group/element-text-group.component.ts index 83251aedde7c749a9a8aa73aa33ad06c9a4cf38d..e5185c4d3eba544d304c8ce96e5c73c02f276d1f 100644 --- a/projects/player/src/app/components/element-text-group/element-text-group.component.ts +++ b/projects/player/src/app/components/element-text-group/element-text-group.component.ts @@ -5,7 +5,7 @@ import { first, takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; import { TextElement } from 'common/interfaces/elements'; import { TextComponent } from 'common/components/ui-elements/text.component'; -import { TextMarker } from '../../classes/text-marker'; +import { TextMarkingService } from '../../services/text-marking.service'; import { NativeEventService } from '../../services/native-event.service'; import { UnitStateService } from '../../services/unit-state.service'; import { ElementGroupDirective } from '../../directives/element-group.directive'; @@ -83,7 +83,7 @@ export class ElementTextGroupComponent extends ElementGroupDirective implements } applySelectionToText(mode: 'mark' | 'delete', color: string, elementComponent: TextComponent): void { - TextMarker + TextMarkingService .applySelection( mode, color, @@ -99,8 +99,8 @@ export class ElementTextGroupComponent extends ElementGroupDirective implements elementComponent: TextComponent ) { const selection = window.getSelection(); - if (selection && TextMarker.isSelectionValid(selection) && selection.rangeCount > 0) { - if (!TextMarker.isRangeInside(selection.getRangeAt(0), elementComponent.textContainerRef.nativeElement) || + if (selection && TextMarkingService.isSelectionValid(selection) && selection.rangeCount > 0) { + if (!TextMarkingService.isRangeInside(selection.getRangeAt(0), elementComponent.textContainerRef.nativeElement) || (ctrlKey)) { selection.removeAllRanges(); } else if (this.selectedMode && this.selectedColor) { diff --git a/projects/player/src/app/services/element-model-element-code-mapping.service.ts b/projects/player/src/app/services/element-model-element-code-mapping.service.ts index 2936f1812197ae5c47432f3c7cfe1609155329d2..dfca85f158520092ba3e2a274b99385c3880dac3 100644 --- a/projects/player/src/app/services/element-model-element-code-mapping.service.ts +++ b/projects/player/src/app/services/element-model-element-code-mapping.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { TextMarker } from '../classes/text-marker'; +import { TextMarkingService } from './text-marking.service'; import { AudioElement, DragNDropValueObject, ImageElement, InputElement, InputElementValue, TextElement, UIElement, UIElementType, VideoElement @@ -23,7 +23,7 @@ export class ElementModelElementCodeMappingService { (elementModel as InputElement).value; case 'text': return (elementCodeValue !== undefined) ? - TextMarker.restoreMarkings(elementCodeValue as string[], (elementModel as TextElement).text) : + TextMarkingService.restoreMarkings(elementCodeValue as string[], (elementModel as TextElement).text) : (elementModel as TextElement).text; case 'audio': return elementCodeValue !== undefined ? @@ -54,7 +54,7 @@ export class ElementModelElementCodeMappingService { case 'drop-list-simple': return (elementModelValue as DragNDropValueObject[]).map(object => object.id); case 'text': - return TextMarker.getMarkingData(elementModelValue as string); + return TextMarkingService.getMarkingData(elementModelValue as string); case 'radio': case 'radio-group-images': case 'dropdown': diff --git a/projects/player/src/app/classes/text-marker.ts b/projects/player/src/app/services/text-marking.service.ts similarity index 76% rename from projects/player/src/app/classes/text-marker.ts rename to projects/player/src/app/services/text-marking.service.ts index 3aafc0ade35690a18d774b2f6247546c230116ce..a907c256221153812eb9be22b303379c3b46edb6 100644 --- a/projects/player/src/app/classes/text-marker.ts +++ b/projects/player/src/app/services/text-marking.service.ts @@ -1,6 +1,12 @@ import { TextComponent } from 'common/components/ui-elements/text.component'; +import { Injectable } from '@angular/core'; + + +@Injectable({ + providedIn: 'root' +}) +export class TextMarkingService { -export class TextMarker { private static readonly MARKING_TAG = 'ASPECT-MARKED'; static applySelection( @@ -9,14 +15,14 @@ export class TextMarker { textComponent: TextComponent ): void { const selection = window.getSelection(); - if (selection && TextMarker.isSelectionValid(selection) && selection.rangeCount > 0) { + if (selection && TextMarkingService.isSelectionValid(selection) && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const element = textComponent.textContainerRef.nativeElement; - if (TextMarker.isRangeInside(range, element)) { - TextMarker.applyRange(range, selection, mode === 'delete', color); + if (TextMarkingService.isRangeInside(range, element)) { + TextMarkingService.applyRange(range, selection, mode === 'delete', color); textComponent.elementValueChanged.emit({ id: textComponent.elementModel.id, - value: TextMarker.getMarkingData(element.innerHTML) + value: TextMarkingService.getMarkingData(element.innerHTML) }); textComponent.savedText = element.innerHTML; } else { @@ -30,14 +36,14 @@ export class TextMarker { static isSelectionValid = (selection: Selection): boolean => selection.toString().length > 0; static isRangeInside(range: Range, element: HTMLElement): boolean { - return (TextMarker.isDescendantOf(range.startContainer, element) && - TextMarker.isDescendantOf(range.endContainer, element)); + return (TextMarkingService.isDescendantOf(range.startContainer, element) && + TextMarkingService.isDescendantOf(range.endContainer, element)); } static getMarkingData = (htmlText: string): string[] => { const markingStartPattern = - new RegExp(`<${TextMarker.MARKING_TAG.toLowerCase()} [a-z]+="[\\w\\d()-;:, #]+">`); - const markingClosingTag = `</${TextMarker.MARKING_TAG.toLowerCase()}>`; + new RegExp(`<${TextMarkingService.MARKING_TAG.toLowerCase()} [a-z]+="[\\w\\d()-;:, #]+">`); + const markingClosingTag = `</${TextMarkingService.MARKING_TAG.toLowerCase()}>`; let newHtmlText = htmlText; const markCollection = []; let matchesArray; @@ -53,7 +59,7 @@ export class TextMarker { newHtmlText = newHtmlText.replace(startMatch, ''); const endIndex = newHtmlText.search(endMatch); newHtmlText = newHtmlText.replace(endMatch, ''); - markCollection.push(`${startIndex}-${endIndex}-${TextMarker.getMarkingColor(startMatch)}`); + markCollection.push(`${startIndex}-${endIndex}-${TextMarkingService.getMarkingColor(startMatch)}`); } } } while (matchesArray); @@ -65,13 +71,13 @@ export class TextMarker { if (markings.length) { const markCollectionReversed = [...markings].reverse(); const markingDataPattern = /^(\d+)-(\d+)-(.+)$/; - const markingClosingTag = `</${TextMarker.MARKING_TAG.toLowerCase()}>`; + const markingClosingTag = `</${TextMarkingService.MARKING_TAG.toLowerCase()}>`; markCollectionReversed.forEach(markingData => { const matchesArray = markingData.match(markingDataPattern); if (matchesArray) { const startIndex = Number(matchesArray[1]); const endIndex = Number(matchesArray[2]); - const startMatch = TextMarker.createMarkingStartTag(matchesArray[3]); + const startMatch = TextMarkingService.createMarkingStartTag(matchesArray[3]); newHtmlText = newHtmlText.substring(0, endIndex) + markingClosingTag + newHtmlText.substr(endIndex); newHtmlText = newHtmlText.substring(0, startIndex) + startMatch + newHtmlText.substr(startIndex); } @@ -87,18 +93,18 @@ export class TextMarker { if (node.parentElement === element) { return true; } - return TextMarker.isDescendantOf(node.parentNode, element); + return TextMarkingService.isDescendantOf(node.parentNode, element); } private static getMarkingColor = (tag: string): string => { const colors = tag.match(/\d{1,3}, \d{1,3}, \d{1,3}/); - return (colors) ? TextMarker.rgbToHex(colors[0].split(',').map(value => Number(value))) : 'none'; + return (colors) ? TextMarkingService.rgbToHex(colors[0].split(',').map(value => Number(value))) : 'none'; }; private static createMarkingStartTag(color: string): string { - const rgb = TextMarker.hexToRgb(color); + const rgb = TextMarkingService.hexToRgb(color); return `<${ - TextMarker.MARKING_TAG.toLowerCase() + TextMarkingService.MARKING_TAG.toLowerCase() } style="background-color: rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]});">`; } @@ -107,22 +113,22 @@ export class TextMarker { ): void { if (range.startContainer === range.endContainer) { if (clear) { - TextMarker.clearMarkingFromNode(range); + TextMarkingService.clearMarkingFromNode(range); } else { - TextMarker.markNode(range, color); + TextMarkingService.markNode(range, color); } } else { - const nodes: Node[] = TextMarker.getSelectedNodes(range, selection); + const nodes: Node[] = TextMarkingService.getSelectedNodes(range, selection); if (clear) { - TextMarker.clearNodes(nodes, range); + TextMarkingService.clearNodes(nodes, range); } else { - TextMarker.markNodes(nodes, range, color); + TextMarkingService.markNodes(nodes, range, color); } } } private static getSelectedNodes = (range: Range, selection: Selection): Node[] => { - const nodes: Node[] = TextMarker.findNodes(range.commonAncestorContainer.childNodes, selection); + const nodes: Node[] = TextMarkingService.findNodes(range.commonAncestorContainer.childNodes, selection); // When the user finishes selecting between paragraphs and the selection happens from // back to front, Firefox does not consider the start container as a selected child node. // Therefore, it is added to the list of selected nodes at the beginning. @@ -132,7 +138,7 @@ export class TextMarker { // When the user finishes selecting between paragraphs the browser does not consider the end container // as a selected child node. Therefore, it is added to the list of selected nodes at the end. if (range.endOffset === 0) { - const endContainer = TextMarker.getEndContainer(range.endContainer); + const endContainer = TextMarkingService.getEndContainer(range.endContainer); if (endContainer && !nodes.includes(endContainer)) { nodes.push(endContainer); } @@ -154,12 +160,12 @@ export class TextMarker { }; private static clearMarkingFromNode(range: Range): void { - if (range.startContainer.parentElement?.tagName?.toUpperCase() === TextMarker.MARKING_TAG) { + if (range.startContainer.parentElement?.tagName?.toUpperCase() === TextMarkingService.MARKING_TAG) { const previousText = range.startContainer.nodeValue?.substring(0, range.startOffset) || ''; const text = range.startContainer.nodeValue?.substring(range.startOffset, range.endOffset) || ''; const nextText = range.startContainer.nodeValue?.substring(range.endOffset) || ''; if (text) { - TextMarker.clearMarking(range.startContainer, text, previousText, nextText); + TextMarkingService.clearMarking(range.startContainer, text, previousText, nextText); } } } @@ -171,12 +177,12 @@ export class TextMarker { const color = (node.parentNode as HTMLElement).style.backgroundColor || 'none'; parentNode?.replaceChild(textElement, node.parentNode); if (previousText) { - const prev = TextMarker.createMarkedElement(color); + const prev = TextMarkingService.createMarkedElement(color); prev.append(document.createTextNode(previousText)); parentNode?.insertBefore(prev, textElement); } if (nextText) { - const end = TextMarker.createMarkedElement(color); + const end = TextMarkingService.createMarkedElement(color); end.append(document.createTextNode(nextText)); parentNode?.insertBefore(end, textElement.nextSibling); } @@ -186,10 +192,10 @@ export class TextMarker { private static clearNodes(nodes: Node[], range: Range): void { nodes.forEach((node: Node) => { const index = nodes.findIndex(rangeNode => rangeNode === node); - if (node.parentElement?.tagName.toUpperCase() === TextMarker.MARKING_TAG) { - const nodeValues = TextMarker.getNodeValues(node, nodes, index, range); + if (node.parentElement?.tagName.toUpperCase() === TextMarkingService.MARKING_TAG) { + const nodeValues = TextMarkingService.getNodeValues(node, nodes, index, range); if (nodeValues.text) { - TextMarker.clearMarking(node, nodeValues.text, nodeValues.previousText, nodeValues.nextText); + TextMarkingService.clearMarking(node, nodeValues.text, nodeValues.previousText, nodeValues.nextText); } else { // eslint-disable-next-line no-console console.warn('Cannot recreate node for text', nodeValues); @@ -201,7 +207,7 @@ export class TextMarker { private static mark( node: Node, text: string, previousText: string, nextText: string, color: string ): void { - const markedElement: HTMLElement = TextMarker.createMarkedElement(color); + const markedElement: HTMLElement = TextMarkingService.createMarkedElement(color); markedElement.append(document.createTextNode(text)); // important! const { parentNode } = node; @@ -237,23 +243,23 @@ export class TextMarker { }; private static markNode(range: Range, color: string): void { - if (range.startContainer.parentElement?.tagName.toUpperCase() !== TextMarker.MARKING_TAG) { - const markedElement: HTMLElement = TextMarker.createMarkedElement(color); + if (range.startContainer.parentElement?.tagName.toUpperCase() !== TextMarkingService.MARKING_TAG) { + const markedElement: HTMLElement = TextMarkingService.createMarkedElement(color); range.surroundContents(markedElement); } } private static markNodes(nodes: Node[], range: Range, color: string): void { nodes.forEach((node, index) => { - const nodeValues = TextMarker.getNodeValues(node, nodes, index, range); - if (nodeValues.text && node.parentElement?.tagName.toUpperCase() !== TextMarker.MARKING_TAG) { - TextMarker.mark(node, nodeValues.text, nodeValues.previousText, nodeValues.nextText, color); + const nodeValues = TextMarkingService.getNodeValues(node, nodes, index, range); + if (nodeValues.text && node.parentElement?.tagName.toUpperCase() !== TextMarkingService.MARKING_TAG) { + TextMarkingService.mark(node, nodeValues.text, nodeValues.previousText, nodeValues.nextText, color); } }); } private static createMarkedElement = (color: string): HTMLElement => { - const markedElement = document.createElement(TextMarker.MARKING_TAG); + const markedElement = document.createElement(TextMarkingService.MARKING_TAG); markedElement.style.backgroundColor = color; return markedElement; }; @@ -267,7 +273,7 @@ export class TextMarker { } if (node.nodeType === Node.ELEMENT_NODE) { if (node.childNodes.length) { - nodes.push(...TextMarker.findNodes(node.childNodes, selection)); + nodes.push(...TextMarkingService.findNodes(node.childNodes, selection)); } else if (!nodes.includes(node)) { nodes.push(node); }