From 47611d9f8b527f82d60efa426fa4ca552b2f4d1b Mon Sep 17 00:00:00 2001 From: NessArt Date: Fri, 30 Jun 2023 11:23:56 +0200 Subject: [PATCH 01/39] zwischenstand mit div und innerHtml --- .../app/base-editor/base-editor.component.css | 8 +++++- .../base-editor/base-editor.component.html | 13 ++++++++-- .../app/base-editor/base-editor.component.ts | 25 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.css b/frontend/src/app/base-editor/base-editor.component.css index 0d69e7051..7583d6540 100644 --- a/frontend/src/app/base-editor/base-editor.component.css +++ b/frontend/src/app/base-editor/base-editor.component.css @@ -418,4 +418,10 @@ input.scenario, input.background { width: 11px !important; height: 11px !important; - } \ No newline at end of file + } + + .regex-expression { + color: var(--ocean-blue); + font-weight: bold; + /* Add any other desired styles */ +} diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index f2d1e90ad..21da95998 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -95,8 +95,17 @@
- + +
+
diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index cde43bc40..d44284d1b 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -141,6 +141,9 @@ export class BaseEditorComponent { exampleChild: ExampleTableComponent; + @ViewChild('textField1') textField1: ElementRef; + regexDetected: boolean = false; + /** * Subscribtions for all EventEmitter */ @@ -269,6 +272,7 @@ export class BaseEditorComponent { if (this.exampleChildren.last != undefined) { this.exampleChild = this.exampleChildren.last; } + this.textField1.nativeElement.addEventListener('input', this.highlightRegex.bind(this)); } /** @@ -1677,4 +1681,25 @@ export class BaseEditorComponent { this.markUnsaved(); } + highlightRegex() { // /[a-z]+\d+/gi + console.log('in hightlight') + const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions + const textContent = this.textField1.nativeElement.textContent; + const regexMatchedText = textContent.match(regexPattern); + console.log(textContent) + console.log(regexMatchedText) + + if (regexMatchedText) { + this.regexDetected = true; + + const highlightedText = textContent.replace(regexPattern, (match) => { + return `${match}`; + }); + + this.textField1.nativeElement.innerHTML = highlightedText; + } else { + this.regexDetected = false; + } + } + } From 7e5f77ddbfdc05c4a2383762d99d0233f72de6e4 Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 6 Jul 2023 13:27:25 +0200 Subject: [PATCH 02/39] zwischenstand mit div und ohne innerHtml aber position fehler --- .../app/base-editor/base-editor.component.css | 11 +- .../base-editor/base-editor.component.html | 7 +- .../app/base-editor/base-editor.component.ts | 152 +++++++++++++++--- 3 files changed, 138 insertions(+), 32 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.css b/frontend/src/app/base-editor/base-editor.component.css index 7583d6540..de9074103 100644 --- a/frontend/src/app/base-editor/base-editor.component.css +++ b/frontend/src/app/base-editor/base-editor.component.css @@ -420,8 +420,11 @@ input.scenario, input.background { } - .regex-expression { - color: var(--ocean-blue); - font-weight: bold; - /* Add any other desired styles */ +.content_editable_element { /*not needed !?*/ + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ } + diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index 21da95998..72371664d 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -98,13 +98,14 @@
+ +
diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index d44284d1b..7adbb085e 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1,7 +1,7 @@ import { ApiService } from 'src/app/Services/api.service'; import { CopyExampleToast } from '../copyExample-toast'; import { CdkDragDrop, CdkDragStart, DragRef, moveItemInArray } from '@angular/cdk/drag-drop'; -import { Component, ElementRef, Input, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { Component, ElementRef, Input, QueryList, Renderer2, ViewChild, ViewChildren } from '@angular/core'; import { ToastrService } from 'ngx-toastr'; import { AddBlockFormComponent } from '../modals/add-block-form/add-block-form.component'; import { NewStepRequestComponent } from '../modals/new-step-request/new-step-request.component'; @@ -141,7 +141,7 @@ export class BaseEditorComponent { exampleChild: ExampleTableComponent; - @ViewChild('textField1') textField1: ElementRef; + //@ViewChild('textField1', { static: true }) textField1: ElementRef; regexDetected: boolean = false; /** @@ -160,7 +160,8 @@ export class BaseEditorComponent { public exampleService: ExampleService, public scenarioService: ScenarioService, public backgroundService: BackgroundService, - public apiService: ApiService) {} + public apiService: ApiService, + private renderer: Renderer2) {} ngOnInit(): void { this.addBlocktoScenarioObservable = this.blockService.addBlockToScenarioEvent.subscribe(block => { @@ -252,6 +253,9 @@ export class BaseEditorComponent { } ngAfterViewInit(): void { + const textField = document.getElementById('textField1'); + textField.addEventListener('input', this.highlightRegex.bind(this)); + this.step_type_input.changes.subscribe(_ => { this.step_type_input.forEach(in_field => { if ( in_field.nativeElement.id === this.lastToFocus) { @@ -272,7 +276,6 @@ export class BaseEditorComponent { if (this.exampleChildren.last != undefined) { this.exampleChild = this.exampleChildren.last; } - this.textField1.nativeElement.addEventListener('input', this.highlightRegex.bind(this)); } /** @@ -1681,25 +1684,124 @@ export class BaseEditorComponent { this.markUnsaved(); } - highlightRegex() { // /[a-z]+\d+/gi - console.log('in hightlight') - const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions - const textContent = this.textField1.nativeElement.textContent; - const regexMatchedText = textContent.match(regexPattern); - console.log(textContent) - console.log(regexMatchedText) - - if (regexMatchedText) { - this.regexDetected = true; - - const highlightedText = textContent.replace(regexPattern, (match) => { - return `${match}`; - }); - - this.textField1.nativeElement.innerHTML = highlightedText; - } else { - this.regexDetected = false; + /*highlightRegex() { // with input field + hi + const regexPattern = // Regex pattern to recognize and highlight regex expressions + + const divElement = document.getElementById('textField1'); + const selection = window.getSelection(); + + // Store the current selection position + const selectionStart = selection ? selection.anchorOffset : 0; + + const textContent = divElement.innerText || ''; + + const regexMatchedText = textContent.match(regexPattern); + const regexDetected = regexMatchedText !== null; + + // Clear previous styling + divElement.innerHTML = ''; + + if (regexDetected) { + const fragment = document.createDocumentFragment(); + let currentIndex = 0; + + for (const match of regexMatchedText) { + const startIndex = textContent.indexOf(match, currentIndex); + const nonRegexPart = textContent.substring(currentIndex, startIndex); + + if (nonRegexPart) { + const nonRegexNode = document.createTextNode(nonRegexPart); + fragment.appendChild(nonRegexNode); + } + + const span = document.createElement('span'); + span.style.color = 'var(--ocean-blue)'; + span.style.fontWeight = 'bold'; + span.textContent = match; + fragment.appendChild(span); + + currentIndex = startIndex + match.length; + } + + const remainingText = textContent.substring(currentIndex); + if (remainingText) { + const remainingTextNode = document.createTextNode(remainingText); + fragment.appendChild(remainingTextNode); + } + + divElement.appendChild(fragment); + } else { + // No regex matches, simply set the text content + divElement.innerText = textContent; + } + + // Restore the cursor position + if (selection) { + const updatedLength = divElement.innerText.length; + const updatedStart = Math.min(selectionStart, updatedLength); + const updatedEnd = Math.min(selectionStart, updatedLength); + + selection.removeAllRanges(); + const range = document.createRange(); + range.setStart(divElement.firstChild, updatedStart); + range.setEnd(divElement.firstChild, updatedEnd); + selection.addRange(range); + } + }*/ + highlightRegex() { // div but cursor position bad + const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions + + const textField = document.getElementById('textField1'); + const textContent = textField.textContent; + + const regexMatchedText = textContent.match(regexPattern); + this.regexDetected = regexMatchedText !== null; + + // Clear previous styling + this.renderer.setStyle(textField, 'color', ''); + this.renderer.setStyle(textField, 'fontWeight', ''); + + if (this.regexDetected) { + const matches: RegExpExecArray[] = []; + let match: RegExpExecArray | null; + + while ((match = regexPattern.exec(textContent)) !== null) { + matches.push(match); + } + + const fragment = document.createDocumentFragment(); + let currentIndex = 0; + + for (const match of matches) { + const nonRegexPart = textContent.substring(currentIndex, match.index); + const matchText = match[0]; + + if (nonRegexPart) { + const nonRegexNode = document.createTextNode(nonRegexPart); + fragment.appendChild(nonRegexNode); + } + + const span = document.createElement('span'); + span.style.color = 'var(--ocean-blue)'; + span.style.fontWeight = 'bold'; + span.appendChild(document.createTextNode(matchText)); + fragment.appendChild(span); + + currentIndex = match.index + matchText.length; + } + + const remainingText = textContent.substring(currentIndex); + if (remainingText) { + const remainingTextNode = document.createTextNode(remainingText); + fragment.appendChild(remainingTextNode); + } + + while (textField.firstChild) { + textField.removeChild(textField.firstChild); + } + + textField.appendChild(fragment); + } } - } - -} +} // /[a-z]+\d+/gi \ No newline at end of file From c8982b4199b1e9770935d147670e2a4aa5dab5a0 Mon Sep 17 00:00:00 2001 From: NessArt Date: Wed, 9 Aug 2023 15:12:23 +0200 Subject: [PATCH 03/39] WIP cursor pos working --- .../app/base-editor/base-editor.component.ts | 136 +++++++++--------- 1 file changed, 66 insertions(+), 70 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 7adbb085e..b0edb2ae5 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -253,8 +253,8 @@ export class BaseEditorComponent { } ngAfterViewInit(): void { - const textField = document.getElementById('textField1'); - textField.addEventListener('input', this.highlightRegex.bind(this)); + //const textField = document.getElementById('textField1'); + //textField.addEventListener('input', this.highlightRegex.bind(this)); this.step_type_input.changes.subscribe(_ => { this.step_type_input.forEach(in_field => { @@ -1684,77 +1684,15 @@ export class BaseEditorComponent { this.markUnsaved(); } - /*highlightRegex() { // with input field - hi - const regexPattern = // Regex pattern to recognize and highlight regex expressions - - const divElement = document.getElementById('textField1'); - const selection = window.getSelection(); - - // Store the current selection position - const selectionStart = selection ? selection.anchorOffset : 0; - - const textContent = divElement.innerText || ''; - - const regexMatchedText = textContent.match(regexPattern); - const regexDetected = regexMatchedText !== null; - - // Clear previous styling - divElement.innerHTML = ''; - - if (regexDetected) { - const fragment = document.createDocumentFragment(); - let currentIndex = 0; - - for (const match of regexMatchedText) { - const startIndex = textContent.indexOf(match, currentIndex); - const nonRegexPart = textContent.substring(currentIndex, startIndex); - - if (nonRegexPart) { - const nonRegexNode = document.createTextNode(nonRegexPart); - fragment.appendChild(nonRegexNode); - } - - const span = document.createElement('span'); - span.style.color = 'var(--ocean-blue)'; - span.style.fontWeight = 'bold'; - span.textContent = match; - fragment.appendChild(span); - - currentIndex = startIndex + match.length; - } - - const remainingText = textContent.substring(currentIndex); - if (remainingText) { - const remainingTextNode = document.createTextNode(remainingText); - fragment.appendChild(remainingTextNode); - } - - divElement.appendChild(fragment); - } else { - // No regex matches, simply set the text content - divElement.innerText = textContent; - } - - // Restore the cursor position - if (selection) { - const updatedLength = divElement.innerText.length; - const updatedStart = Math.min(selectionStart, updatedLength); - const updatedEnd = Math.min(selectionStart, updatedLength); - - selection.removeAllRanges(); - const range = document.createRange(); - range.setStart(divElement.firstChild, updatedStart); - range.setEnd(divElement.firstChild, updatedEnd); - selection.addRange(range); - } - }*/ - highlightRegex() { // div but cursor position bad + + highlightRegex() { const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions const textField = document.getElementById('textField1'); const textContent = textField.textContent; - + //Get current cursor position + const offset = this.getCaretCharacterOffsetWithin(textField) + const regexMatchedText = textContent.match(regexPattern); this.regexDetected = regexMatchedText !== null; @@ -1803,5 +1741,63 @@ export class BaseEditorComponent { textField.appendChild(fragment); } + + // Set cursor to correct position + if (this.regexDetected){ + const selection = window.getSelection(); + selection.removeAllRanges() + + let length = 0; + let preLength = 0; + let node=0; + let offsetIndex=0; + + for(let i = 0; i<= textField.childNodes.length; i++) { + length = textField.childNodes[i].textContent.length + if (preLength+length>=offset){ + offsetIndex = offset-preLength + node=i + break; + } + else { + preLength = preLength+length + } + } + if (textField.childNodes[node].nodeType == 3){ + selection.setBaseAndExtent(textField.childNodes[node], offsetIndex, textField.childNodes[node], offsetIndex) + } else { + selection.setBaseAndExtent(textField.childNodes[node].childNodes[0], offsetIndex, textField.childNodes[node].childNodes[0], offsetIndex) + } + } } -} // /[a-z]+\d+/gi \ No newline at end of file + + /** + * Helper for Regex Highlighter, extract current cursor position + * @param element HTMLElement + * @returns num, offset of cursor position + */ + getCaretCharacterOffsetWithin(element) { + var caretOffset = 0; + var doc = element.ownerDocument || element.document; + var win = doc.defaultView || doc.parentWindow; + var sel; + if (typeof win.getSelection != "undefined") { + sel = win.getSelection(); + if (sel.rangeCount > 0) { + var range = win.getSelection().getRangeAt(0); + var preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(element); + preCaretRange.setEnd(range.endContainer, range.endOffset); + caretOffset = preCaretRange.toString().length; + } + } else if ( (sel = doc.selection) && sel.type != "Control") { + var textRange = sel.createRange(); + var preCaretTextRange = doc.body.createTextRange(); + preCaretTextRange.moveToElementText(element); + preCaretTextRange.setEndPoint("EndToEnd", textRange); + caretOffset = preCaretTextRange.text.length; + } + return caretOffset; + } + +} \ No newline at end of file From cd430ce34b36e48739679827fc3040f80110bf1a Mon Sep 17 00:00:00 2001 From: NessArt Date: Wed, 9 Aug 2023 16:17:55 +0200 Subject: [PATCH 04/39] WIP div as input --- frontend/src/app/base-editor/base-editor.component.html | 8 +------- frontend/src/app/base-editor/base-editor.component.ts | 7 ++++--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index 72371664d..44980930b 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -97,13 +97,7 @@
-
+
{{currentStep.values[0]}}
diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index b0edb2ae5..444a180d8 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1685,10 +1685,10 @@ export class BaseEditorComponent { } - highlightRegex() { + highlightRegex(element, stepIndex: number, valueIndex: number, stepType: string, step?:StepType) { const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions - - const textField = document.getElementById('textField1'); + + const textField = document.getElementById(element.id); const textContent = textField.textContent; //Get current cursor position const offset = this.getCaretCharacterOffsetWithin(textField) @@ -1769,6 +1769,7 @@ export class BaseEditorComponent { selection.setBaseAndExtent(textField.childNodes[node].childNodes[0], offsetIndex, textField.childNodes[node].childNodes[0], offsetIndex) } } + this.addToValues(textContent, stepIndex, valueIndex, stepType, step) } /** From 3de82fc9ddd009412400e7090765250f483de6f8 Mon Sep 17 00:00:00 2001 From: NessArt Date: Wed, 9 Aug 2023 16:31:39 +0200 Subject: [PATCH 05/39] doku --- .../app/base-editor/base-editor.component.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 444a180d8..1751419f1 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1685,6 +1685,15 @@ export class BaseEditorComponent { } + /** + * Add value and highlight regex, Style regex in value and give value to addToValue() function + * Value is in textContent and style is in innerHTML + * @param element HTMLElement of div, id needed for hightlightRegex, textContend needed for addToValue + * @param stepIndex for addToValue + * @param valueIndex for addToValue + * @param stepType for addToValue + * @param step for addToValue + */ highlightRegex(element, stepIndex: number, valueIndex: number, stepType: string, step?:StepType) { const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions @@ -1711,6 +1720,7 @@ export class BaseEditorComponent { const fragment = document.createDocumentFragment(); let currentIndex = 0; + // Create span with style for every regex match for (const match of matches) { const nonRegexPart = textContent.substring(currentIndex, match.index); const matchText = match[0]; @@ -1747,11 +1757,11 @@ export class BaseEditorComponent { const selection = window.getSelection(); selection.removeAllRanges() + // Check in which node the cursor is and set new offsetIndex to position in that node let length = 0; let preLength = 0; let node=0; let offsetIndex=0; - for(let i = 0; i<= textField.childNodes.length; i++) { length = textField.childNodes[i].textContent.length if (preLength+length>=offset){ @@ -1763,12 +1773,13 @@ export class BaseEditorComponent { preLength = preLength+length } } - if (textField.childNodes[node].nodeType == 3){ + if (textField.childNodes[node].nodeType == 3){ // in case childNode is text selection.setBaseAndExtent(textField.childNodes[node], offsetIndex, textField.childNodes[node], offsetIndex) - } else { + } else { // in case childNode is span, childNode of span is text selection.setBaseAndExtent(textField.childNodes[node].childNodes[0], offsetIndex, textField.childNodes[node].childNodes[0], offsetIndex) } } + this.addToValues(textContent, stepIndex, valueIndex, stepType, step) } From d1fa822a29ffc3650f640f2b2ebce4a8c0b94a16 Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 10 Aug 2023 18:19:25 +0200 Subject: [PATCH 06/39] toast, cursor improvement, second input --- .../base-editor/base-editor.component.html | 10 ++-- .../app/base-editor/base-editor.component.ts | 50 ++++++++++++++----- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index 44980930b..ac93aedec 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -97,10 +97,7 @@
-
{{currentStep.values[0]}}
- - - +
{{currentStep.values[0]}}
@@ -131,8 +128,9 @@
- + +
{{currentStep.values[1]}}
diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 1751419f1..7a7f6c238 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -31,6 +31,8 @@ export class BaseEditorComponent { @ViewChildren('step_type_input1') step_type_input1: QueryList; + @ViewChildren('step_type_input2') step_type_input2: QueryList; + /** * View child of the example table */ @@ -141,8 +143,7 @@ export class BaseEditorComponent { exampleChild: ExampleTableComponent; - //@ViewChild('textField1', { static: true }) textField1: ElementRef; - regexDetected: boolean = false; + regexInStory: boolean = false; /** * Subscribtions for all EventEmitter @@ -253,9 +254,6 @@ export class BaseEditorComponent { } ngAfterViewInit(): void { - //const textField = document.getElementById('textField1'); - //textField.addEventListener('input', this.highlightRegex.bind(this)); - this.step_type_input.changes.subscribe(_ => { this.step_type_input.forEach(in_field => { if ( in_field.nativeElement.id === this.lastToFocus) { @@ -276,6 +274,14 @@ export class BaseEditorComponent { if (this.exampleChildren.last != undefined) { this.exampleChild = this.exampleChildren.last; } + + // Regex Highlight on init + this.step_type_input1.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); + this.step_type_input2.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); } /** @@ -1688,28 +1694,33 @@ export class BaseEditorComponent { /** * Add value and highlight regex, Style regex in value and give value to addToValue() function * Value is in textContent and style is in innerHTML - * @param element HTMLElement of div, id needed for hightlightRegex, textContend needed for addToValue + * @param element id of HTML div * @param stepIndex for addToValue * @param valueIndex for addToValue * @param stepType for addToValue * @param step for addToValue + * @param initialCall if call is from ngAfterViewInit */ - highlightRegex(element, stepIndex: number, valueIndex: number, stepType: string, step?:StepType) { + highlightRegex(element:string, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, initialCall?:boolean) { const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions - const textField = document.getElementById(element.id); + const textField = document.getElementById(element); const textContent = textField.textContent; //Get current cursor position const offset = this.getCaretCharacterOffsetWithin(textField) + if(!initialCall){ + this.addToValues(textContent, stepIndex, valueIndex, stepType, step) + } + const regexMatchedText = textContent.match(regexPattern); - this.regexDetected = regexMatchedText !== null; + const regexDetected = regexMatchedText !== null; // Clear previous styling this.renderer.setStyle(textField, 'color', ''); this.renderer.setStyle(textField, 'fontWeight', ''); - if (this.regexDetected) { + if (regexDetected) { const matches: RegExpExecArray[] = []; let match: RegExpExecArray | null; @@ -1753,7 +1764,9 @@ export class BaseEditorComponent { } // Set cursor to correct position - if (this.regexDetected){ + if(!initialCall){ + requestAnimationFrame(() => { + if (regexDetected){ //maybe not needed const selection = window.getSelection(); selection.removeAllRanges() @@ -1778,9 +1791,20 @@ export class BaseEditorComponent { } else { // in case childNode is span, childNode of span is text selection.setBaseAndExtent(textField.childNodes[node].childNodes[0], offsetIndex, textField.childNodes[node].childNodes[0], offsetIndex) } + } else { + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.setBaseAndExtent(textField.firstChild, offset, textField.firstChild, offset) + } + })} + if(initialCall && regexDetected) { + this.regexInStory = true + } + + if(regexDetected && !this.regexInStory){ + this.regexInStory = true + this.toastr.info('Regex Highlight'); } - - this.addToValues(textContent, stepIndex, valueIndex, stepType, step) } /** From 601ebfe01fbfe001d583d88af9fa92efea81c2bb Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 10 Aug 2023 18:34:01 +0200 Subject: [PATCH 07/39] drittes Inputfeld --- frontend/src/app/base-editor/base-editor.component.html | 5 +++-- frontend/src/app/base-editor/base-editor.component.ts | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index ac93aedec..f8ad8515d 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -163,8 +163,9 @@
- + +
{{currentStep.values[2]}}
diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 7a7f6c238..dbd627d4a 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -33,6 +33,8 @@ export class BaseEditorComponent { @ViewChildren('step_type_input2') step_type_input2: QueryList; + @ViewChildren('step_type_input3') step_type_input3: QueryList; + /** * View child of the example table */ @@ -282,6 +284,9 @@ export class BaseEditorComponent { this.step_type_input2.forEach(in_field => { this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) }); + this.step_type_input3.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); } /** From bf82edccb9441b308327d43d3ac8e477a8adc998 Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 10 Aug 2023 18:49:22 +0200 Subject: [PATCH 08/39] moved initial highlight call --- .../app/base-editor/base-editor.component.ts | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index dbd627d4a..870f0f390 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -205,7 +205,7 @@ export class BaseEditorComponent { } } }); - + } ngOnDestroy(): void { @@ -253,6 +253,18 @@ export class BaseEditorComponent { if(this.allChecked) { this.checkAllSteps(this.allChecked); } + + // Regex Highlight on init + if(this.step_type_input1){ + this.step_type_input1.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); + this.step_type_input2.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); + this.step_type_input3.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + });} } ngAfterViewInit(): void { @@ -276,17 +288,6 @@ export class BaseEditorComponent { if (this.exampleChildren.last != undefined) { this.exampleChild = this.exampleChildren.last; } - - // Regex Highlight on init - this.step_type_input1.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) - }); - this.step_type_input2.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) - }); - this.step_type_input3.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) - }); } /** @@ -1805,7 +1806,7 @@ export class BaseEditorComponent { if(initialCall && regexDetected) { this.regexInStory = true } - + console.log(this.regexInStory, regexDetected) if(regexDetected && !this.regexInStory){ this.regexInStory = true this.toastr.info('Regex Highlight'); From 6587d840dd34a86ba26cb9cdd57476c94ca31c71 Mon Sep 17 00:00:00 2001 From: NessArt Date: Fri, 11 Aug 2023 12:02:32 +0200 Subject: [PATCH 09/39] toast only when regex new in story --- frontend/src/app/base-editor/base-editor.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 870f0f390..242085927 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -255,6 +255,7 @@ export class BaseEditorComponent { } // Regex Highlight on init + this.regexInStory = false if(this.step_type_input1){ this.step_type_input1.forEach(in_field => { this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) From e555474083a40dfcac88ebbef89e2ea20af02bb9 Mon Sep 17 00:00:00 2001 From: NessArt Date: Fri, 11 Aug 2023 12:32:17 +0200 Subject: [PATCH 10/39] WIP --- .../app/base-editor/base-editor.component.ts | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 242085927..6def79324 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1770,12 +1770,19 @@ export class BaseEditorComponent { textField.appendChild(fragment); } + // Toastr logic + if(initialCall && regexDetected) { + this.regexInStory = true + } + if(regexDetected && !this.regexInStory){ + this.regexInStory = true + this.toastr.info('Regex Highlight'); + } + // Set cursor to correct position if(!initialCall){ - requestAnimationFrame(() => { - if (regexDetected){ //maybe not needed - const selection = window.getSelection(); - selection.removeAllRanges() + //if (regexDetected){ //maybe not needed + console.log(offset) // Check in which node the cursor is and set new offsetIndex to position in that node let length = 0; @@ -1793,24 +1800,29 @@ export class BaseEditorComponent { preLength = preLength+length } } + console.log(node, offset, offsetIndex) + requestAnimationFrame(() => { if (textField.childNodes[node].nodeType == 3){ // in case childNode is text + const selection = window.getSelection(); + selection.removeAllRanges() selection.setBaseAndExtent(textField.childNodes[node], offsetIndex, textField.childNodes[node], offsetIndex) + console.log('in if') } else { // in case childNode is span, childNode of span is text + const selection = window.getSelection(); + selection.removeAllRanges() selection.setBaseAndExtent(textField.childNodes[node].childNodes[0], offsetIndex, textField.childNodes[node].childNodes[0], offsetIndex) + console.log('in else 1') } - } else { + console.log(window.getSelection()) + }) + /*} else { + //requestAnimationFrame(() => { const selection = window.getSelection(); selection.removeAllRanges(); selection.setBaseAndExtent(textField.firstChild, offset, textField.firstChild, offset) - } - })} - if(initialCall && regexDetected) { - this.regexInStory = true - } - console.log(this.regexInStory, regexDetected) - if(regexDetected && !this.regexInStory){ - this.regexInStory = true - this.toastr.info('Regex Highlight'); + console.log('in else 2') + //}) + }*/ } } From 534dead7dd274b5485a79c3cd0dc3a122f0f8172 Mon Sep 17 00:00:00 2001 From: NessArt Date: Fri, 11 Aug 2023 14:06:06 +0200 Subject: [PATCH 11/39] WIP lifecycle call and keystroke call interfere with each other so that cursor position is broken --- .../app/base-editor/base-editor.component.ts | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 6def79324..2f8d4afc0 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1781,8 +1781,9 @@ export class BaseEditorComponent { // Set cursor to correct position if(!initialCall){ - //if (regexDetected){ //maybe not needed - console.log(offset) + if (regexDetected){ //maybe not needed + const selection = window.getSelection(); + selection.removeAllRanges() // Check in which node the cursor is and set new offsetIndex to position in that node let length = 0; @@ -1800,29 +1801,21 @@ export class BaseEditorComponent { preLength = preLength+length } } - console.log(node, offset, offsetIndex) + requestAnimationFrame(() => { if (textField.childNodes[node].nodeType == 3){ // in case childNode is text - const selection = window.getSelection(); - selection.removeAllRanges() selection.setBaseAndExtent(textField.childNodes[node], offsetIndex, textField.childNodes[node], offsetIndex) - console.log('in if') } else { // in case childNode is span, childNode of span is text - const selection = window.getSelection(); - selection.removeAllRanges() selection.setBaseAndExtent(textField.childNodes[node].childNodes[0], offsetIndex, textField.childNodes[node].childNodes[0], offsetIndex) - console.log('in else 1') } - console.log(window.getSelection()) }) - /*} else { - //requestAnimationFrame(() => { + } else { + requestAnimationFrame(() => { const selection = window.getSelection(); selection.removeAllRanges(); selection.setBaseAndExtent(textField.firstChild, offset, textField.firstChild, offset) - console.log('in else 2') - //}) - }*/ + }) + } } } From 866a8873de9ad3b0987c8d10bbc6b94ab11f6b60 Mon Sep 17 00:00:00 2001 From: NessArt Date: Fri, 11 Aug 2023 14:10:45 +0200 Subject: [PATCH 12/39] lifecycle and keystroke dont interfere --- .../app/base-editor/base-editor.component.ts | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 2f8d4afc0..332a0cd57 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -146,6 +146,7 @@ export class BaseEditorComponent { exampleChild: ExampleTableComponent; regexInStory: boolean = false; + initialRegex: boolean = true; /** * Subscribtions for all EventEmitter @@ -230,6 +231,10 @@ export class BaseEditorComponent { } + ngOnChanges(){ + this.initialRegex = true; + } + /** * retrieves the saved block from the session storage */ @@ -255,17 +260,20 @@ export class BaseEditorComponent { } // Regex Highlight on init - this.regexInStory = false - if(this.step_type_input1){ - this.step_type_input1.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) - }); - this.step_type_input2.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) - }); - this.step_type_input3.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) - });} + if(this.initialRegex){ + this.regexInStory = false + if(this.step_type_input1){ + this.step_type_input1.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); + this.step_type_input2.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); + this.step_type_input3.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); + } + } } ngAfterViewInit(): void { @@ -1720,6 +1728,10 @@ export class BaseEditorComponent { this.addToValues(textContent, stepIndex, valueIndex, stepType, step) } + if(!initialCall){ + this.initialRegex = false; + } + const regexMatchedText = textContent.match(regexPattern); const regexDetected = regexMatchedText !== null; From e7a375aa2c4b8e162654c1a6bd28d82ca3c0cd99 Mon Sep 17 00:00:00 2001 From: NessArt Date: Fri, 22 Sep 2023 10:02:58 +0200 Subject: [PATCH 13/39] style of new divs like old text input --- .../app/base-editor/base-editor.component.css | 18 +++++++++++++++++- .../app/base-editor/base-editor.component.html | 6 +++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.css b/frontend/src/app/base-editor/base-editor.component.css index de9074103..757ddf19c 100644 --- a/frontend/src/app/base-editor/base-editor.component.css +++ b/frontend/src/app/base-editor/base-editor.component.css @@ -420,7 +420,7 @@ input.scenario, input.background { } -.content_editable_element { /*not needed !?*/ +.contentEditableElement { /*not needed !?*/ white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ @@ -428,3 +428,19 @@ input.scenario, input.background { word-wrap: break-word; /* Internet Explorer 5.5+ */ } +.inputEditableDiv{ + margin: 1px; + padding: 1px; + border: none; + border-bottom: 1px solid #333; + position: relative; + top: -2px; + width: auto; + min-width: 5px; + + margin-left: 10px; + margin-right: 10px; + padding-left: 5px; + padding-right: 5px; +} + diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index bd2046df9..fc0bbe5dd 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -97,7 +97,7 @@
-
{{currentStep.values[0]}}
+
{{currentStep.values[0]}}
@@ -130,7 +130,7 @@
-
{{currentStep.values[1]}}
+
{{currentStep.values[1]}}
@@ -165,7 +165,7 @@
-
{{currentStep.values[2]}}
+
{{currentStep.values[2]}}
From 0c13cea0228bffe7813c2ddb5c2f57773bfbb589 Mon Sep 17 00:00:00 2001 From: NessArt Date: Wed, 4 Oct 2023 12:55:47 +0200 Subject: [PATCH 14/39] regex styling in background --- .../app/base-editor/base-editor.component.html | 15 +++++++++------ .../src/app/base-editor/base-editor.component.ts | 7 ++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index fc0bbe5dd..9f4e4574f 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -279,18 +279,21 @@ {{j+1}}. {{currentStep.pre}}
- + +
{{currentStep.values[0]}}
{{currentStep.mid}}
- + +
{{currentStep.values[1]}}
{{currentStep.post}}
- + +
{{currentStep.values[2]}}
diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 06b073c5e..8710e29bb 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -272,7 +272,12 @@ export class BaseEditorComponent { // Regex Highlight on init if(this.initialRegex){ this.regexInStory = false - if(this.step_type_input1){ + if(this.step_type_input){ //background + this.step_type_input.forEach(in_field => { + this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + }); + } + if(this.step_type_input1){ //scenario this.step_type_input1.forEach(in_field => { this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) }); From 87706ef36995b2b1585500018869e2222612bbd6 Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 5 Oct 2023 15:31:56 +0200 Subject: [PATCH 15/39] regex highlight only for selected steps --- .../base-editor/base-editor.component.html | 14 ++-- .../app/base-editor/base-editor.component.ts | 67 +++++++++++-------- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index 9f4e4574f..44484e71c 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -91,13 +91,13 @@ {{ selectedCount(selectedScenario.stepDefinitions,i) || 1 }}
- {{i+1}}.{{j+1}} {{currentStep.pre}} + {{i+1}}.{{j+1}} 
{{currentStep.pre}}
-
{{currentStep.values[0]}}
+
{{currentStep.values[0]}}
@@ -130,7 +130,7 @@
-
{{currentStep.values[1]}}
+
{{currentStep.values[1]}}
@@ -165,7 +165,7 @@
-
{{currentStep.values[2]}}
+
{{currentStep.values[2]}}
@@ -281,19 +281,19 @@
-
{{currentStep.values[0]}}
+
{{currentStep.values[0]}}
{{currentStep.mid}}
-
{{currentStep.values[1]}}
+
{{currentStep.values[1]}}
{{currentStep.post}}
-
{{currentStep.values[2]}}
+
{{currentStep.values[2]}}
diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 8710e29bb..be5e1e355 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -29,6 +29,8 @@ export class BaseEditorComponent { @ViewChildren('step_type_input') step_type_input: QueryList; + @ViewChildren('step_type_pre') step_type_pre: QueryList; + @ViewChildren('step_type_input1') step_type_input1: QueryList; @ViewChildren('step_type_input2') step_type_input2: QueryList; @@ -272,21 +274,25 @@ export class BaseEditorComponent { // Regex Highlight on init if(this.initialRegex){ this.regexInStory = false - if(this.step_type_input){ //background + //Logic currently not needed since regex only in then step + /*if(this.step_type_input){ //background this.step_type_input.forEach(in_field => { this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) }); - } + }*/ if(this.step_type_input1){ //scenario - this.step_type_input1.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + const stepTypePre = this.step_type_pre.toArray() + this.step_type_input1.forEach((in_field, index) => { + this.highlightRegex(in_field.nativeElement.id,undefined,0,undefined,undefined,stepTypePre[index].nativeElement.innerText, true) }); - this.step_type_input2.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) - }); - this.step_type_input3.forEach(in_field => { - this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) + + //Logic currently not needed since regex only in first input field + /*this.step_type_input2.forEach((in_field, index) => { + this.highlightRegex(in_field.nativeElement.id,undefined,1,undefined,undefined,stepTypePre1[index].nativeElement.innerText, true) }); + this.step_type_input3.forEach((in_field, index) => { + this.highlightRegex(in_field.nativeElement.id,undefined,2,undefined,undefined,stepTypePre1[index].nativeElement.innerText, true) + });*/ } } } @@ -374,7 +380,6 @@ export class BaseEditorComponent { * @param stepType */ updateScenarioValues(input: string, stepIndex: number, valueIndex: number, stepType: string) { - console.log(this.selectedScenario.stepDefinitions); switch (stepType) { case 'given': @@ -1734,15 +1739,19 @@ export class BaseEditorComponent { * @param valueIndex for addToValue * @param stepType for addToValue * @param step for addToValue + * @param stepPre pre text of step * @param initialCall if call is from ngAfterViewInit */ - highlightRegex(element:string, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, initialCall?:boolean) { + highlightRegex(element:string, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, stepPre?: string, initialCall?:boolean) { const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions const textField = document.getElementById(element); const textContent = textField.textContent; //Get current cursor position const offset = this.getCaretCharacterOffsetWithin(textField) + const regexSteps = ['So I can see the text', 'So I can see the text:', 'So I can\'t see the text:', 'So I can\'t see text in the textbox:'] + + let textIsRegex = false; if(!initialCall){ this.addToValues(textContent, stepIndex, valueIndex, stepType, step) @@ -1752,14 +1761,17 @@ export class BaseEditorComponent { this.initialRegex = false; } - const regexMatchedText = textContent.match(regexPattern); - const regexDetected = regexMatchedText !== null; - - // Clear previous styling - this.renderer.setStyle(textField, 'color', ''); - this.renderer.setStyle(textField, 'fontWeight', ''); - + if(0==valueIndex && regexSteps.includes(stepPre)){ + + const regexMatchedText = textContent.match(regexPattern); + const regexDetected = regexMatchedText !== null; + + // Clear previous styling + this.renderer.setStyle(textField, 'color', ''); + this.renderer.setStyle(textField, 'fontWeight', ''); + if (regexDetected) { + textIsRegex = true; const matches: RegExpExecArray[] = []; let match: RegExpExecArray | null; @@ -1801,19 +1813,20 @@ export class BaseEditorComponent { textField.appendChild(fragment); } - - // Toastr logic - if(initialCall && regexDetected) { - this.regexInStory = true - } - if(regexDetected && !this.regexInStory){ - this.regexInStory = true - this.toastr.info('Regex Highlight'); + // Toastr logic + if(initialCall && regexDetected) { + this.regexInStory = true + } + if(regexDetected && !this.regexInStory){ + this.regexInStory = true + this.toastr.info('Regex Highlight'); + } } + // Set cursor to correct position if(!initialCall){ - if (regexDetected){ //maybe not needed + if (textIsRegex){ //maybe not needed const selection = window.getSelection(); selection.removeAllRanges() From 5a6db2836f8efe0851d6de38881aa410bb1aad8d Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 12 Oct 2023 16:19:26 +0200 Subject: [PATCH 16/39] WIP highlighting in then steps and /^m/ --- .../app/base-editor/base-editor.component.ts | 100 +++++++++--------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index be5e1e355..cbf9883fd 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1731,6 +1731,11 @@ export class BaseEditorComponent { } + //TODO + //darkmode, handhabung felder vergleichen (str v), multiple scenario + //beim hinzufügen von blöcken oder kopierten wird nicht gehighlighted + //zentrale Bloeke + /** * Add value and highlight regex, Style regex in value and give value to addToValue() function * Value is in textContent and style is in innerHTML @@ -1743,14 +1748,12 @@ export class BaseEditorComponent { * @param initialCall if call is from ngAfterViewInit */ highlightRegex(element:string, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, stepPre?: string, initialCall?:boolean) { - const regexPattern = /\/[^\n\/]+\/[a-z]*/gi; // Regex pattern to recognize and highlight regex expressions - + const regexPattern = /\/\^[^/]*\//g///\/\^[^/]*\//g;// Regex pattern to recognize and highlight regex expressions -> start with /^ and end with / const textField = document.getElementById(element); const textContent = textField.textContent; //Get current cursor position const offset = this.getCaretCharacterOffsetWithin(textField) const regexSteps = ['So I can see the text', 'So I can see the text:', 'So I can\'t see the text:', 'So I can\'t see text in the textbox:'] - let textIsRegex = false; if(!initialCall){ @@ -1761,67 +1764,66 @@ export class BaseEditorComponent { this.initialRegex = false; } - if(0==valueIndex && regexSteps.includes(stepPre)){ + // Clear previous styling + this.renderer.setStyle(textField, 'color', ''); + this.renderer.setStyle(textField, 'fontWeight', ''); - const regexMatchedText = textContent.match(regexPattern); - const regexDetected = regexMatchedText !== null; + var regexDetected = false; - // Clear previous styling - this.renderer.setStyle(textField, 'color', ''); - this.renderer.setStyle(textField, 'fontWeight', ''); - - if (regexDetected) { - textIsRegex = true; - const matches: RegExpExecArray[] = []; - let match: RegExpExecArray | null; + const matches: RegExpExecArray[] = []; + let match: RegExpExecArray | null; - while ((match = regexPattern.exec(textContent)) !== null) { - matches.push(match); - } + while ((match = regexPattern.exec(textContent)) !== null) { + matches.push(match); + textIsRegex = true; + } - const fragment = document.createDocumentFragment(); - let currentIndex = 0; + const fragment = document.createDocumentFragment(); + let currentIndex = 0; - // Create span with style for every regex match - for (const match of matches) { - const nonRegexPart = textContent.substring(currentIndex, match.index); - const matchText = match[0]; + // Create span with style for every regex match + for (const match of matches) { + const nonRegexPart = textContent.substring(currentIndex, match.index); + const matchText = match[0]; - if (nonRegexPart) { - const nonRegexNode = document.createTextNode(nonRegexPart); - fragment.appendChild(nonRegexNode); - } + if (nonRegexPart) { + const nonRegexNode = document.createTextNode(nonRegexPart); + fragment.appendChild(nonRegexNode); + } - const span = document.createElement('span'); + const span = document.createElement('span'); + if(0==valueIndex && regexSteps.includes(stepPre)){ + regexDetected = true; span.style.color = 'var(--ocean-blue)'; span.style.fontWeight = 'bold'; - span.appendChild(document.createTextNode(matchText)); - fragment.appendChild(span); - - currentIndex = match.index + matchText.length; } + span.appendChild(document.createTextNode(matchText)); + fragment.appendChild(span); - const remainingText = textContent.substring(currentIndex); - if (remainingText) { - const remainingTextNode = document.createTextNode(remainingText); - fragment.appendChild(remainingTextNode); - } + currentIndex = match.index + matchText.length; + } - while (textField.firstChild) { - textField.removeChild(textField.firstChild); - } + const remainingText = textContent.substring(currentIndex); + if (remainingText) { + const remainingTextNode = document.createTextNode(remainingText); + fragment.appendChild(remainingTextNode); + } - textField.appendChild(fragment); + while (textField.firstChild) { + textField.removeChild(textField.firstChild); } - // Toastr logic - if(initialCall && regexDetected) { - this.regexInStory = true - } - if(regexDetected && !this.regexInStory){ - this.regexInStory = true - this.toastr.info('Regex Highlight'); - } + + textField.appendChild(fragment); + + // Toastr logic + if(initialCall && regexDetected) { + this.regexInStory = true } + if(regexDetected && !this.regexInStory){ + this.regexInStory = true + this.toastr.info('Regex Highlight'); + } + // Set cursor to correct position From 043bc913b518b85b3570d62cb51dacf9b647f68b Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 12 Oct 2023 16:48:07 +0200 Subject: [PATCH 17/39] @@ highlight for regex --- frontend/src/app/base-editor/base-editor.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index cbf9883fd..7ffd6954c 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1748,7 +1748,7 @@ export class BaseEditorComponent { * @param initialCall if call is from ngAfterViewInit */ highlightRegex(element:string, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, stepPre?: string, initialCall?:boolean) { - const regexPattern = /\/\^[^/]*\//g///\/\^[^/]*\//g;// Regex pattern to recognize and highlight regex expressions -> start with /^ and end with / + const regexPattern = /@@[^ ]+/g;// Regex pattern to recognize and highlight regex expressions -> start with @@ const textField = document.getElementById(element); const textContent = textField.textContent; //Get current cursor position From 6baef6423e12b4d2e15c61a1bf96ba00ac8b6685 Mon Sep 17 00:00:00 2001 From: NessArt Date: Fri, 13 Oct 2023 10:54:41 +0200 Subject: [PATCH 18/39] merge fix ts file --- frontend/src/app/base-editor/base-editor.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index d6503003f..7773d444b 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1,7 +1,6 @@ import { ApiService } from 'src/app/Services/api.service'; import { CdkDragDrop, CdkDragStart, DragRef, moveItemInArray } from '@angular/cdk/drag-drop'; -import { Component, ElementRef, Input, QueryList, Renderer2, ViewChild, ViewChildren } from '@angular/core'; -import { Component, ElementRef, EventEmitter, Input, Output, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, Output, Renderer2, QueryList, ViewChild, ViewChildren } from '@angular/core'; import { ToastrService } from 'ngx-toastr'; import { AddBlockFormComponent } from '../modals/add-block-form/add-block-form.component'; import { NewStepRequestComponent } from '../modals/new-step-request/new-step-request.component'; From 8c59fc61aa79ecd61d5d85e00b58a806b4b6dadf Mon Sep 17 00:00:00 2001 From: NessArt Date: Tue, 24 Oct 2023 11:00:19 +0200 Subject: [PATCH 19/39] css merge fix and html merge --- .../app/base-editor/base-editor.component.css | 3 -- .../base-editor/base-editor.component.html | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.css b/frontend/src/app/base-editor/base-editor.component.css index 50b400478..4d4a7569c 100644 --- a/frontend/src/app/base-editor/base-editor.component.css +++ b/frontend/src/app/base-editor/base-editor.component.css @@ -507,9 +507,6 @@ input.background { padding-top: 0; } - - } - .contentEditableElement { /*not needed !?*/ white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index fdbee7e87..a42800953 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -144,18 +144,19 @@ context: {$implicit: currentStep}">
- {{currentStep.pre}} +  
{{currentStep.pre}}
- + style="padding-left: 5px; padding-right: 5px;min-width: 100px; padding-bottom: 1px;" /--> +
{{currentStep.values[0]}}
- + style="padding-left: 5px; padding-right: 5px;min-width: 100px" /--> +
{{currentStep.values[1]}}
- + style="padding-left: 5px; padding-right: 5px;min-width: 100px" /--> +
{{currentStep.values[2]}}
{{j+1}}. {{currentStep.pre}}
- + style="padding-left: 5px; padding-right: 5px;min-width: 100px" /--> +
{{currentStep.values[0]}}
{{currentStep.mid}}
- + style="padding-left: 5px; padding-right: 5px;min-width: 100px" /--> +
{{currentStep.values[1]}}
{{currentStep.post}}
- + style="padding-left: 5px; padding-right: 5px;min-width: 100px" /--> +
{{currentStep.values[2]}}
From e6a96af4ef293b1dff1b71b56acb8c5a1903b65c Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 9 Nov 2023 15:57:37 +0100 Subject: [PATCH 20/39] wip --- .../app/base-editor/base-editor.component.css | 2 +- .../app/base-editor/base-editor.component.ts | 21 ++- .../example-table/example-table.component.css | 12 ++ .../example-table.component.html | 18 +-- .../example-table/example-table.component.ts | 144 ++++++++++++++++++ 5 files changed, 181 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.css b/frontend/src/app/base-editor/base-editor.component.css index 4d4a7569c..aeedb0d1c 100644 --- a/frontend/src/app/base-editor/base-editor.component.css +++ b/frontend/src/app/base-editor/base-editor.component.css @@ -507,7 +507,7 @@ input.background { padding-top: 0; } -.contentEditableElement { /*not needed !?*/ +.contentEditableElement { /*needed for trailing whitespaces*/ white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 7773d444b..0aeef1c59 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -2013,9 +2013,22 @@ export class BaseEditorComponent { //TODO - //darkmode, handhabung felder vergleichen (str v), multiple scenario + //multiple scenario //beim hinzufügen von blöcken oder kopierten wird nicht gehighlighted - //zentrale Bloeke + //zentrale Bloeke -> geht aber in ansicht ohne feld schwarz + //higlighting start @@ und ende whitespace oder zeile + + //span.setAttribute('class', 'regexStyling'); -> wie funktioniert darkmode + /*.regexStyling { + color: var(--ocean-blue); + font-weight: bold; +} + +.darkTheme .regexStyling em{ + color: var(--light-blue) !important; + font-weight: bold; +}*/ + /** * Add value and highlight regex, Style regex in value and give value to addToValue() function @@ -2045,10 +2058,6 @@ export class BaseEditorComponent { this.initialRegex = false; } - // Clear previous styling - this.renderer.setStyle(textField, 'color', ''); - this.renderer.setStyle(textField, 'fontWeight', ''); - var regexDetected = false; const matches: RegExpExecArray[] = []; diff --git a/frontend/src/app/example-table/example-table.component.css b/frontend/src/app/example-table/example-table.component.css index 694c734a3..338f62e28 100644 --- a/frontend/src/app/example-table/example-table.component.css +++ b/frontend/src/app/example-table/example-table.component.css @@ -134,4 +134,16 @@ th.mat-header-cell:first-of-type, td.mat-cell:first-of-type, td.mat-footer-cell: font-size: 17px; margin: 2px; margin-right: 5px; +} + +.blue-text { + color: blue; +} + +.contentEditableElement { /*needed for trailing whitespaces*/ + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ } \ No newline at end of file diff --git a/frontend/src/app/example-table/example-table.component.html b/frontend/src/app/example-table/example-table.component.html index faeae00bb..45143c011 100644 --- a/frontend/src/app/example-table/example-table.component.html +++ b/frontend/src/app/example-table/example-table.component.html @@ -36,15 +36,15 @@     

{{rowIndex+1}}.

- - - {{element[column]}} - - - - - - +
+ +
{{element[column]}}
+ + + +
diff --git a/frontend/src/app/example-table/example-table.component.ts b/frontend/src/app/example-table/example-table.component.ts index 568181084..c25892853 100644 --- a/frontend/src/app/example-table/example-table.component.ts +++ b/frontend/src/app/example-table/example-table.component.ts @@ -391,4 +391,148 @@ export class ExampleTableComponent implements OnInit { this.selectedScenario.stepDefinitions.example[i].values = newData[i-1] } } + + private highlightMatches(el, columnIndex, rowIndex, initialCall) { + const regex = /@@[^ ]+/g; + const inputValue: string = el.textContent; + const offset = this.getCaretCharacterOffsetWithin(el) + + if(!initialCall){ + this.selectedScenario.stepDefinitions.example[rowIndex + 1].values[columnIndex-1] = inputValue + } + const trailingWhitepace = inputValue.slice(-1) == ' ' + console.log('inputValue', el.innerHTML.slice(-1)) + console.log('textContent',el.textContent.slice(-1)) + console.log(trailingWhitepace) + + const highlightedText = inputValue.replace(regex, (match) => `${match}`); + + const textIsRegex = regex.test(inputValue); + console.log(inputValue) + //if (trailingWhitepace){ + //highlightedText.concat(' ') + //} + console.log(highlightedText) + el.innerHTML = highlightedText + console.log(el) + + /*var regexDetected = false; + let textIsRegex = false; + + const matches: RegExpExecArray[] = []; + let match: RegExpExecArray | null; + + while ((match = regex.exec(inputValue)) !== null) { + matches.push(match); + textIsRegex = true; + } + + const fragment = document.createDocumentFragment(); + let currentIndex = 0; + + // Create span with style for every regex match + for (const match of matches) { + const nonRegexPart = inputValue.substring(currentIndex, match.index); + const matchText = match[0]; + + if (nonRegexPart) { + const nonRegexNode = document.createTextNode(nonRegexPart); + fragment.appendChild(nonRegexNode); + } + + const span = document.createElement('span'); + regexDetected = true; + span.style.color = 'var(--ocean-blue)'; + span.style.fontWeight = 'bold'; + + span.appendChild(document.createTextNode(matchText)); + fragment.appendChild(span); + + currentIndex = match.index + matchText.length; + } + + const remainingText = inputValue.substring(currentIndex); + if (remainingText) { + const remainingTextNode = document.createTextNode(remainingText); + fragment.appendChild(remainingTextNode); + } + + while (el.firstChild) { + el.removeChild(el.firstChild); + } + + el.appendChild(fragment); + + console.log(el)*/ + + // Set cursor to correct position + if(!initialCall){ + if (textIsRegex){ //maybe not needed + const selection = window.getSelection(); + selection.removeAllRanges() + + // Check in which node the cursor is and set new offsetIndex to position in that node + let length = 0; + let preLength = 0; + let node=0; + let offsetIndex=0; + for(let i = 0; i<= el.childNodes.length; i++) { + length = el.childNodes[i].textContent.length + if (preLength+length>=offset){ + offsetIndex = offset-preLength + node=i + break; + } + else { + preLength = preLength+length + } + } + + requestAnimationFrame(() => { + if (el.childNodes[node].nodeType == 3){ // in case childNode is text + selection.setBaseAndExtent(el.childNodes[node], offsetIndex, el.childNodes[node], offsetIndex) + } else { // in case childNode is span, childNode of span is text + selection.setBaseAndExtent(el.childNodes[node].childNodes[0], offsetIndex, el.childNodes[node].childNodes[0], offsetIndex) + } + }) + } else { + requestAnimationFrame(() => { + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.setBaseAndExtent(el.firstChild, offset, el.firstChild, offset) + }) + } + } + + + } + + /** + * Helper for Regex Highlighter, extract current cursor position + * @param element HTMLElement + * @returns num, offset of cursor position + */ + getCaretCharacterOffsetWithin(element) { + var caretOffset = 0; + var doc = element.ownerDocument || element.document; + var win = doc.defaultView || doc.parentWindow; + var sel; + if (typeof win.getSelection != "undefined") { + sel = win.getSelection(); + if (sel.rangeCount > 0) { + var range = win.getSelection().getRangeAt(0); + var preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(element); + preCaretRange.setEnd(range.endContainer, range.endOffset); + caretOffset = preCaretRange.toString().length; + } + } else if ( (sel = doc.selection) && sel.type != "Control") { + var textRange = sel.createRange(); + var preCaretTextRange = doc.body.createTextRange(); + preCaretTextRange.moveToElementText(element); + preCaretTextRange.setEndPoint("EndToEnd", textRange); + caretOffset = preCaretTextRange.text.length; + } + return caretOffset; +} } From 8685bb0c2643f8e2c6245247531f4dc23940defa Mon Sep 17 00:00:00 2001 From: NessArt Date: Thu, 16 Nov 2023 13:47:42 +0100 Subject: [PATCH 21/39] working multiple scenario highlight --- .../app/base-editor/base-editor.component.ts | 16 +---- .../example-table.component.html | 2 +- .../example-table/example-table.component.ts | 68 ++++++++++++------- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 0aeef1c59..8c40cd607 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -2013,21 +2013,7 @@ export class BaseEditorComponent { //TODO - //multiple scenario //beim hinzufügen von blöcken oder kopierten wird nicht gehighlighted - //zentrale Bloeke -> geht aber in ansicht ohne feld schwarz - //higlighting start @@ und ende whitespace oder zeile - - //span.setAttribute('class', 'regexStyling'); -> wie funktioniert darkmode - /*.regexStyling { - color: var(--ocean-blue); - font-weight: bold; -} - -.darkTheme .regexStyling em{ - color: var(--light-blue) !important; - font-weight: bold; -}*/ /** @@ -2039,7 +2025,7 @@ export class BaseEditorComponent { * @param stepType for addToValue * @param step for addToValue * @param stepPre pre text of step - * @param initialCall if call is from ngAfterViewInit + * @param initialCall if call is from ngDoCheck */ highlightRegex(element:string, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, stepPre?: string, initialCall?:boolean) { const regexPattern = /@@[^ ]+/g;// Regex pattern to recognize and highlight regex expressions -> start with @@ diff --git a/frontend/src/app/example-table/example-table.component.html b/frontend/src/app/example-table/example-table.component.html index 45143c011..3c0779cef 100644 --- a/frontend/src/app/example-table/example-table.component.html +++ b/frontend/src/app/example-table/example-table.component.html @@ -38,7 +38,7 @@
-
{{element[column]}}
+
{{element[column]}}
-
{{currentStep.values[0]}}
+
{{currentStep.values[0]}}
-
{{currentStep.values[1]}}
+
{{currentStep.values[1]}}
-
{{currentStep.values[2]}}
+
{{currentStep.values[2]}}
-
{{currentStep.values[0]}}
+
{{currentStep.values[0]}}
{{currentStep.mid}}
@@ -420,7 +420,7 @@ *ngIf="currentStep.values[1] != null" type="text" value="{{currentStep.values[1]}}" on-input="addToValues(step_type_input.value,j,1,currentStep.stepType)" style="padding-left: 5px; padding-right: 5px;min-width: 100px" /--> -
{{currentStep.values[1]}}
+
{{currentStep.values[1]}}
{{currentStep.post}}
@@ -429,7 +429,7 @@ *ngFor="let value of currentStep.values | slice:2; let n = index" type="text" value="{{value}}" on-input="addToValues(step_type_input.value,j , n + 2, currentStep.stepType)" style="padding-left: 5px; padding-right: 5px;min-width: 100px" /--> -
{{currentStep.values[2]}}
+
{{currentStep.values[2]}}
diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 2131a6485..f8e52e9c2 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -78,6 +78,7 @@ export class BaseEditorComponent { this.checkAllSteps(false); } this.selectedScenario = scenario; + this.initialRegex = true; } @Input() @@ -93,6 +94,7 @@ export class BaseEditorComponent { @Input() set newlySelectedStory(story: Story) { this.selectedStory = story; + this.initialRegex = true; } /** @@ -230,6 +232,7 @@ export class BaseEditorComponent { this.renameExampleObservable = this.exampleService.renameExampleEvent.subscribe(value => { this.renameExample(value.name, value.column); }); this.scenarioChangedObservable = this.scenarioService.scenarioChangedEvent.subscribe(() => { this.checkAllSteps(false); + this.initialRegex = true; }); this.backgroundChangedObservable = this.backgroundService.backgroundChangedEvent.subscribe(() => { this.checkAllSteps(false); @@ -276,10 +279,6 @@ export class BaseEditorComponent { } - ngOnChanges(){ - this.initialRegex = true; - } - /** * retrieves the saved block from the session storage */ @@ -306,29 +305,20 @@ export class BaseEditorComponent { if (this.allChecked) { this.checkAllSteps(this.allChecked); } + } + ngAfterViewChecked(){ + this.regexDOMChangesHelper() if(this.initialRegex){ this.regexHighlightOnInit() } } ngAfterViewInit(): void { - this.step_type_input.changes.subscribe(_ => { - this.step_type_input.forEach(in_field => { - if (in_field.nativeElement.id === this.lastToFocus) { - in_field.nativeElement.focus(); - } - }); - this.lastToFocus = ''; - }); - this.step_type_input1.changes.subscribe(_ => { - this.step_type_input1.forEach(in_field => { - if (in_field.nativeElement.id === this.lastToFocus) { - in_field.nativeElement.focus(); - } - }); - this.lastToFocus = ''; - }); + this.regexDOMChangesHelper() + if(this.initialRegex){ + this.regexHighlightOnInit() + } if (this.exampleChildren.last != undefined) { this.exampleChild = this.exampleChildren.last; @@ -2015,28 +2005,24 @@ export class BaseEditorComponent { this.markUnsaved(); } - //TODO code redundanz, tooltip mouseposition, story/scenario highlight wenn wechsel (directives?), example - /** * Add value and highlight regex, Style regex in value and give value to addToValue() function * Value is in textContent and style is in innerHTML * If initialCall only check if a regex is already there and hightlight it * Only hightlights regex in first field of regexSteps, only then steps for now. Gets checked with stepPre * Get cursor position with getCaretCharacterOffsetWithin Helper and set position again, else it is lost because of overwriting and changing the HTML Element in case of hightlighting - * @param element id of HTML div + * @param element HTML element of contentedible div * @param stepIndex for addToValue * @param valueIndex for addToValue * @param stepType for addToValue * @param step for addToValue * @param stepPre pre text of step - * @param initialCall if call is from ngDoCheck + * @param initialCall if call is from ngAfterView */ - highlightRegex(element:string, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, stepPre?: string, initialCall?:boolean) { + highlightRegex(element, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, stepPre?: string, initialCall?:boolean) { const regexPattern =/(\{Regex:)(.*?)(\})(?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with {Regex: and end with } - //const regexPattern2 =/\/\^.*?\$\/(?:\s*\/\^.*?\$\/)*(?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with /^ and end with $/ - //const regexPattern = new RegExp(`${regexPattern1.source}|${regexPattern2.source}`, 'g'); - const textField = document.getElementById(element); + const textField = element//document.getElementById(element); const textContent = textField.textContent; //Get current cursor position const offset = this.getCaretCharacterOffsetWithin(textField) @@ -2071,7 +2057,6 @@ export class BaseEditorComponent { }); } } - textField.innerHTML = highlightedText // Toastr logic @@ -2176,27 +2161,81 @@ export class BaseEditorComponent { */ regexHighlightOnInit(){ // Regex Highlight on init - this.regexInStory = false + this.regexInStory = false; + this.initialRegex = false; //Logic currently not needed since regex only in then step /*if(this.step_type_input){ //background this.step_type_input.forEach(in_field => { this.highlightRegex(in_field.nativeElement.id,undefined,undefined,undefined,undefined,true) }); }*/ - if(this.step_type_input1){ //scenario + if(this.step_type_input1){ //scenario first input value const stepTypePre = this.step_type_pre.toArray() this.step_type_input1.forEach((in_field, index) => { - this.highlightRegex(in_field.nativeElement.id,undefined,0,undefined,undefined,stepTypePre[index].nativeElement.innerText, true) + this.highlightRegex(in_field.nativeElement,undefined,0,undefined,undefined,stepTypePre[index].nativeElement.innerText, true) }); //Logic currently not needed since regex only in first input field - /*this.step_type_input2.forEach((in_field, index) => { - this.highlightRegex(in_field.nativeElement.id,undefined,1,undefined,undefined,stepTypePre1[index].nativeElement.innerText, true) + /*this.step_type_input2.forEach((in_field, index) => { //scenario second input value + this.highlightRegex(in_field.nativeElement.id,undefined,1,undefined,undefined,stepTypePre1[index].nativeElement.innerText, true) }); - this.step_type_input3.forEach((in_field, index) => { - this.highlightRegex(in_field.nativeElement.id,undefined,2,undefined,undefined,stepTypePre1[index].nativeElement.innerText, true) + this.step_type_input3.forEach((in_field, index) => { //scenario third input value + this.highlightRegex(in_field.nativeElement.id,undefined,2,undefined,undefined,stepTypePre1[index].nativeElement.innerText, true) });*/ } } + /** + * Helper for DOM change subscription + */ + regexDOMChangesHelper(){ + + //Logic currently not needed + /*this.step_type_input.changes.subscribe(_ => { //background + this.step_type_input.forEach(in_field => { + if (in_field.nativeElement.id === this.lastToFocus) { + in_field.nativeElement.focus(); + } + }); + this.lastToFocus = ''; + });*/ + + this.step_type_pre.changes.subscribe(_ => { //scenario text before first input value + this.step_type_pre.forEach(in_field => { + if (in_field.nativeElement.id === this.lastToFocus) { + in_field.nativeElement.focus(); + } + }); + this.lastToFocus = ''; + }); + + this.step_type_input1.changes.subscribe(_ => { //scenario first input value + this.step_type_input1.forEach(in_field => { + if (in_field.nativeElement.id === this.lastToFocus) { + in_field.nativeElement.focus(); + } + }); + this.lastToFocus = ''; + }); + + //Logic currently not needed + /*this.step_type_input2.changes.subscribe(_ => { //scenario second input value + this.step_type_input2.forEach(in_field => { + if (in_field.nativeElement.id === this.lastToFocus) { + in_field.nativeElement.focus(); + } + }); + this.lastToFocus = ''; + }); + this.step_type_input3.changes.subscribe(_ => { //scenario third input value + this.step_type_input3.forEach(in_field => { + if (in_field.nativeElement.id === this.lastToFocus) { + in_field.nativeElement.focus(); + } + }); + this.lastToFocus = ''; + });*/ + + } + } \ No newline at end of file diff --git a/frontend/src/app/example-table/example-table.component.ts b/frontend/src/app/example-table/example-table.component.ts index 05b627584..0dcf3d3db 100644 --- a/frontend/src/app/example-table/example-table.component.ts +++ b/frontend/src/app/example-table/example-table.component.ts @@ -132,6 +132,7 @@ export class ExampleTableComponent implements OnInit { set newSelectedScenario(scenario: Scenario) { this.selectedScenario = scenario; this.updateTable(); + this.initialRegex = true; } @Input() isDark: boolean; @@ -175,7 +176,7 @@ export class ExampleTableComponent implements OnInit { this.isDark = this.themeService.isDarkMode(); this.themeObservable = this.themeService.themeChanged.subscribe((changedTheme) => { this.isDark = this.themeService.isDarkMode(); - this.regexHightlightOnInit(); + this.regexHighlightOnInit(); }); } @@ -192,9 +193,17 @@ export class ExampleTableComponent implements OnInit { } } - ngDoCheck(){ + ngAfterViewInit(){ + this.regexDOMChangesHelper(); if(this.initialRegex){ - this.regexHightlightOnInit(); + this.regexHighlightOnInit() + } + } + + ngAfterViewChecked(){ + this.regexDOMChangesHelper(); + if(this.initialRegex){ + this.regexHighlightOnInit() } } @@ -247,7 +256,7 @@ export class ExampleTableComponent implements OnInit { } this.data.push(js); } - this.regexHightlightOnInit(); + this.regexHighlightOnInit(); } /** @@ -418,9 +427,7 @@ export class ExampleTableComponent implements OnInit { * @param initialCall if call is from ngDoCheck */ private highlightRegex(el, columnIndex, rowIndex, initialCall) { - const regexPattern1 =/\[Regex:(.*?)](?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with [Regex: and end with ] - const regexPattern2 =/\/\^.*?\$\/(?:\s*\/\^.*?\$\/)*(?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with /^ and end with $/ - const regex = /(\{Regex:)(.*?)(\})(?=\s|$)/g;//new RegExp(`${regexPattern1.source}|${regexPattern2.source}`, 'g'); + const regex = /(\{Regex:)(.*?)(\})(?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with {Regex: and end with } const inputValue: string = el.textContent; const offset = this.getCaretCharacterOffsetWithin(el) @@ -553,12 +560,20 @@ export class ExampleTableComponent implements OnInit { /** * Helper for inital hightlighting */ - regexHightlightOnInit(){ + regexHighlightOnInit(){ this.regexInStory = false - if(this.example_input){ - this.example_input.forEach(in_field => { - this.highlightRegex(in_field.nativeElement, undefined, undefined, true) - }); - } + this.initialRegex = false + if(this.example_input){ + this.example_input.forEach(in_field => { + this.highlightRegex(in_field.nativeElement, undefined, undefined, true) + }); + } + } + + /** + * Helper for DOM change subscription + */ + regexDOMChangesHelper(){ + this.example_input.changes.subscribe(_ => {}); } } From 44a2baab48c84c3ae69953c19ff665696bfe3259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20K=C3=B6hler?= Date: Wed, 6 Dec 2023 01:29:22 +0100 Subject: [PATCH 35/39] allow inline regex execution for then text --- backend/features/step_definitions/stepdefs.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/features/step_definitions/stepdefs.js b/backend/features/step_definitions/stepdefs.js index 1237f07ca..c692cb0aa 100644 --- a/backend/features/step_definitions/stepdefs.js +++ b/backend/features/step_definitions/stepdefs.js @@ -665,6 +665,9 @@ Then('So I will be navigated to the website: {string}', async function checkUrl( // Search a textfield in the html code and assert it with a Text Then('So I can see the text {string} in the textbox: {string}', async function checkForTextInField(expectedText, label) { + const regex = /\{Regex:([^}]*)\}/g; + const resultString = expectedText.replace(regex, '($1)'); + const world = this; const identifiers = [`//*[@id='${label}']`, `//*[@*='${label}']`, `//*[contains(@*, '${label}')]`, @@ -677,7 +680,7 @@ Then('So I can see the text {string} in the textbox: {string}', async function c let resp = await elem.getText(); resp = resp == '' ? await elem.getAttribute('value') : resp; resp = resp == '' ? await elem.getAttribute('outerHTML') : resp; - match(resp, RegExp(expectedText.toString()), `Textfield does not contain the string/regex: ${expectedText} , actual: ${resp}`); + match(resp, RegExp(resultString), `Textfield does not contain the string/regex: ${resultString} , actual: ${resp}`); }) .catch(async (e) => { await driver.takeScreenshot().then(async (buffer) => { @@ -690,6 +693,8 @@ Then('So I can see the text {string} in the textbox: {string}', async function c // Search if a is text in html code Then('So I can see the text: {string}', async function (expectedText) { // text is present + const regex = /\{Regex:([^}]*)\}/g; + const resultString = expectedText.replace(regex, '($1)'); const world = this; try { await driver.wait(async () => driver.executeScript('return document.readyState').then(async (readyState) => readyState === 'complete')); @@ -699,7 +704,7 @@ Then('So I can see the text: {string}', async function (expectedText) { // text const innerHtmlBody = await driver.executeScript('return document.documentElement.innerHTML'); const outerHtmlBody = await driver.executeScript('return document.documentElement.outerHTML'); const bodyAll = cssBody + innerHtmlBody + outerHtmlBody; - match(bodyAll, RegExp(expectedText.toString()), `Page HTML does not contain the string/regex: ${expectedText}`); + match(bodyAll, RegExp(resultString), `Page HTML does not contain the string/regex: ${resultString}`); }); } catch (e) { await driver.takeScreenshot().then(async (buffer) => { From 296b1d1b45cd191c116d413bf4ff97e242b0d09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20K=C3=B6hler?= Date: Thu, 7 Dec 2023 11:27:49 +0100 Subject: [PATCH 36/39] include nested Brackets: Regex --- backend/features/step_definitions/stepdefs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/features/step_definitions/stepdefs.js b/backend/features/step_definitions/stepdefs.js index c692cb0aa..461a5b74f 100644 --- a/backend/features/step_definitions/stepdefs.js +++ b/backend/features/step_definitions/stepdefs.js @@ -693,7 +693,7 @@ Then('So I can see the text {string} in the textbox: {string}', async function c // Search if a is text in html code Then('So I can see the text: {string}', async function (expectedText) { // text is present - const regex = /\{Regex:([^}]*)\}/g; + const regex = /\{Regex:([^}]*(?:\{[^}]*\}[^}]*)*)(\})(?=\s|$)/g; const resultString = expectedText.replace(regex, '($1)'); const world = this; try { From 76f708418ee9e3a2f6cbc45c2e2e8f951423f37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20K=C3=B6hler?= Date: Tue, 12 Dec 2023 15:11:17 +0100 Subject: [PATCH 37/39] expand regex to all then text --- backend/features/step_definitions/stepdefs.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/features/step_definitions/stepdefs.js b/backend/features/step_definitions/stepdefs.js index 461a5b74f..a0588b900 100644 --- a/backend/features/step_definitions/stepdefs.js +++ b/backend/features/step_definitions/stepdefs.js @@ -663,10 +663,16 @@ Then('So I will be navigated to the website: {string}', async function checkUrl( await driver.sleep(100 + currentParameters.waitTime); }); +const resolveRegex = (rawString) => { + // undefined to empty string + rawString = !rawString ? '' : rawString; + const regex = /\{Regex:([^}]*(?:\{[^}]*\}[^}]*)*)(\})(?=\s|$)/g; + return rawString.replace(regex, '($1)'); +}; + // Search a textfield in the html code and assert it with a Text Then('So I can see the text {string} in the textbox: {string}', async function checkForTextInField(expectedText, label) { - const regex = /\{Regex:([^}]*)\}/g; - const resultString = expectedText.replace(regex, '($1)'); + const resultString = resolveRegex(expectedText); const world = this; @@ -692,9 +698,8 @@ Then('So I can see the text {string} in the textbox: {string}', async function c }); // Search if a is text in html code -Then('So I can see the text: {string}', async function (expectedText) { // text is present - const regex = /\{Regex:([^}]*(?:\{[^}]*\}[^}]*)*)(\})(?=\s|$)/g; - const resultString = expectedText.replace(regex, '($1)'); +Then('So I can see the text: {string}', async function textPresent(expectedText) { // text is present + const resultString = resolveRegex(expectedText); const world = this; try { await driver.wait(async () => driver.executeScript('return document.readyState').then(async (readyState) => readyState === 'complete')); @@ -716,7 +721,7 @@ Then('So I can see the text: {string}', async function (expectedText) { // text }); // Search a textfield in the html code and assert if it's empty -Then('So I can\'t see text in the textbox: {string}', async function (label) { +Then('So I can\'t see text in the textbox: {string}', async function textAbsent(label) { const world = this; const identifiers = [`//*[@id='${label}']`, `//*[@*='${label}']`, `//*[contains(@id, '${label}')]`, `${label}`]; const promises = []; @@ -805,6 +810,7 @@ Then('So the picture {string} has the name {string}', async function checkPictur // Search if a text isn't in html code Then('So I can\'t see the text: {string}', async function checkIfTextIsMissing(expectedText) { + const resultString = resolveRegex(expectedText.toString()); const world = this; try { await driver.wait(async () => driver.executeScript('return document.readyState').then(async (readyState) => readyState === 'complete')); @@ -813,7 +819,7 @@ Then('So I can\'t see the text: {string}', async function checkIfTextIsMissing(e const innerHtmlBody = await driver.executeScript('return document.documentElement.innerHTML'); const outerHtmlBody = await driver.executeScript('return document.documentElement.outerHTML'); const bodyAll = cssBody + innerHtmlBody + outerHtmlBody; - doesNotMatch(bodyAll, RegExp(expectedText.toString()), `Page HTML does contain the string/regex: ${expectedText}`); + doesNotMatch(bodyAll, RegExp(resultString), `Page HTML does contain the string/regex: ${resultString}`); }); } catch (e) { await driver.takeScreenshot().then(async (buffer) => { From b2ce4bf0141639f3b3916d3b5e918aa8d269cc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20K=C3=B6hler?= Date: Thu, 14 Dec 2023 14:38:48 +0100 Subject: [PATCH 38/39] Fix: Resolve bug causing undefined in non regex steps --- frontend/src/app/base-editor/base-editor.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 14f80752f..3194034a6 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -2071,7 +2071,7 @@ export class BaseEditorComponent { }); } } - textField.innerHTML = highlightedText + textField.innerHTML = highlightedText ? highlightedText : textContent; // Toastr logic if(initialCall && regexDetected) { From 2af55391e06cddef3d36ccfa721b692a58c60d84 Mon Sep 17 00:00:00 2001 From: NessArt Date: Mon, 18 Dec 2023 16:24:19 +0100 Subject: [PATCH 39/39] remove duplicated code with new service --- .../Services/highlight-input.service.spec.ts | 16 ++ .../app/Services/highlight-input.service.ts | 154 ++++++++++++++++++ .../app/base-editor/base-editor.component.ts | 128 +-------------- .../example-table/example-table.component.ts | 122 +------------- 4 files changed, 182 insertions(+), 238 deletions(-) create mode 100644 frontend/src/app/Services/highlight-input.service.spec.ts create mode 100644 frontend/src/app/Services/highlight-input.service.ts diff --git a/frontend/src/app/Services/highlight-input.service.spec.ts b/frontend/src/app/Services/highlight-input.service.spec.ts new file mode 100644 index 000000000..ff787fe87 --- /dev/null +++ b/frontend/src/app/Services/highlight-input.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { HighlightInputService } from './highlight-input.service'; + +describe('HighlightInputService', () => { + let service: HighlightInputService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(HighlightInputService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/Services/highlight-input.service.ts b/frontend/src/app/Services/highlight-input.service.ts new file mode 100644 index 000000000..9e36bce16 --- /dev/null +++ b/frontend/src/app/Services/highlight-input.service.ts @@ -0,0 +1,154 @@ +import { Injectable } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; + +@Injectable({ + providedIn: 'root' +}) +export class HighlightInputService { + + constructor(public toastr: ToastrService) { } + targetOffset: number = 0; + + /** + * Add value and highlight regex, Style regex in value and give value to addToValue() function + * Value is in textContent and style is in innerHTML + * If initialCall only check if a regex is already there and hightlight it + * If valueIndex only hightlights regex in first field of regexSteps, only then steps for now. Gets checked with stepPre + * Get cursor position with getCaretCharacterOffsetWithin Helper and set position again, else it is lost because of overwriting and changing the HTML Element in case of hightlighting + * @param element HTML element of contentedible div + * @param initialCall if call is from ngAfterView + * @param isDark theming Service + * @param regexInStory if first regex in Story + * @param valueIndex index of input field + * @param stepPre pre text of step + * @returns if a regex was detected + */ + highlightRegex(element, initialCall?:boolean, isDark?:boolean, regexInStory?:boolean, valueIndex?: number, stepPre?: string) { + const regexPattern =/(\{Regex:)(.*?)(\})(?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with {Regex: and end with } + + const textField = element + const textContent = textField.textContent; + //Get current cursor position + const offset = this.getCaretCharacterOffsetWithin(textField) + const regexSteps = ['So I can see the text', 'So I can see the text:', 'So I can\'t see the text:', 'So I can\'t see text in the textbox:'] + var regexDetected = false; + + let highlightedText; + if(!valueIndex || (0==valueIndex && regexSteps.includes(stepPre))){ + if(isDark){ + highlightedText = textContent.replace(regexPattern, (match, match1, match2, match3) => { + regexDetected = true; + return ``+ + `${match1}`+ + `${match2}`+ + `${match3}`; + }); + } else{ + highlightedText = textContent.replace(regexPattern, (match, match1, match2, match3) => { + regexDetected = true; + return ``+ + `${match1}`+ + `${match2}`+ + `${match3}`; + }); + } + } + textField.innerHTML = highlightedText ? highlightedText : textContent; + + // Toastr logic + if(initialCall && regexDetected) { + regexInStory = true + } + if(regexDetected && !regexInStory){ + this.toastr.info('View our Documentation for more Info','Regular Expression detected!'); + } + + // Set cursor to correct position + if(!initialCall){ + if (regexDetected) { //maybe not needed + const selection = window.getSelection(); + selection.removeAllRanges() + + // Call the function to find the correct node and offset + this.targetOffset = offset + const result = this.findNodeAndOffset(textField); + + if (result !== null) { + const [node, offsetIndex] = result; + requestAnimationFrame(() => { + if (node.nodeType === 3) { + // Text node + selection.setBaseAndExtent(node, offsetIndex, node, offsetIndex); + } else if (node.nodeType === 1 && node.childNodes.length > 0) { + // Element node with child nodes (e.g., ) + selection.setBaseAndExtent(node.childNodes[0], offsetIndex, node.childNodes[0], offsetIndex); + } + }); + } + } else { + requestAnimationFrame(() => { + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.setBaseAndExtent(textField.firstChild, offset, textField.firstChild, offset) + }) + } + } + return regexDetected; + } + +/** + * Helper for Regex Highlighter, find right node and index for current cursor position + * @param element HTMLElement + * @returns node: node with cursor, number: offest of cursor in node + */ + findNodeAndOffset(element: Node): [Node, number] | null { + if (element.nodeType === 3) { + // Text node + const textLength = (element.nodeValue || "").length; + if (this.targetOffset <= textLength) { + return [element, this.targetOffset]; + } else { + this.targetOffset -= textLength; + } + } else if (element.nodeType === 1){ + // Element node + for (let i = 0; i < element.childNodes.length; i++) { + const child = element.childNodes[i]; + const result = this.findNodeAndOffset(child); + if (result !== null) { + return result; + } + } + } + return null; + } + + /** + * Helper for Regex Highlighter, extract current cursor position + * @param element HTMLElement + * @returns num, offset of cursor position + */ + getCaretCharacterOffsetWithin(element) { + var caretOffset = 0; + var doc = element.ownerDocument || element.document; + var win = doc.defaultView || doc.parentWindow; + var sel; + if (typeof win.getSelection != "undefined") { + sel = win.getSelection(); + if (sel.rangeCount > 0) { + var range = win.getSelection().getRangeAt(0); + var preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(element); + preCaretRange.setEnd(range.endContainer, range.endOffset); + caretOffset = preCaretRange.toString().length; + } + } else if ( (sel = doc.selection) && sel.type != "Control") { + var textRange = sel.createRange(); + var preCaretTextRange = doc.body.createTextRange(); + preCaretTextRange.moveToElementText(element); + preCaretTextRange.setEndPoint("EndToEnd", textRange); + caretOffset = preCaretTextRange.text.length; + } + return caretOffset; + } +} diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 3194034a6..f5d96321e 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -22,6 +22,7 @@ import { InfoWarningToast } from '../info-warning-toast'; import { EditBlockComponent } from '../modals/edit-block/edit-block.component'; import { DeleteToast } from '../delete-toast'; import { ThemingService } from '../Services/theming.service'; +import { HighlightInputService } from '../Services/highlight-input.service'; @Component({ selector: 'app-base-editor', @@ -174,7 +175,6 @@ export class BaseEditorComponent { regexInStory: boolean = false; initialRegex: boolean = true; - targetOffset: number = 0; @Input() isDark: boolean; @@ -196,7 +196,8 @@ export class BaseEditorComponent { public scenarioService: ScenarioService, public backgroundService: BackgroundService, public apiService: ApiService, - public themeService: ThemingService) {} + public themeService: ThemingService, + public highlightInputService: HighlightInputService) {} ngOnInit(): void { this.addBlocktoScenarioObservable = this.blockService.addBlockToScenarioEvent.subscribe(block => { @@ -2024,7 +2025,6 @@ export class BaseEditorComponent { * Value is in textContent and style is in innerHTML * If initialCall only check if a regex is already there and hightlight it * Only hightlights regex in first field of regexSteps, only then steps for now. Gets checked with stepPre - * Get cursor position with getCaretCharacterOffsetWithin Helper and set position again, else it is lost because of overwriting and changing the HTML Element in case of hightlighting * @param element HTML element of contentedible div * @param stepIndex for addToValue * @param valueIndex for addToValue @@ -2034,13 +2034,8 @@ export class BaseEditorComponent { * @param initialCall if call is from ngAfterView */ highlightRegex(element, stepIndex?: number, valueIndex?: number, stepType?: string, step?:StepType, stepPre?: string, initialCall?:boolean) { - const regexPattern =/(\{Regex:)(.*?)(\})(?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with {Regex: and end with } - - const textField = element//document.getElementById(element); + const textField = element; const textContent = textField.textContent; - //Get current cursor position - const offset = this.getCaretCharacterOffsetWithin(textField) - const regexSteps = ['So I can see the text', 'So I can see the text:', 'So I can\'t see the text:', 'So I can\'t see text in the textbox:'] var regexDetected = false; if(!initialCall){ @@ -2051,123 +2046,12 @@ export class BaseEditorComponent { this.initialRegex = false; } - let highlightedText; - if(0==valueIndex && regexSteps.includes(stepPre)){ - if(this.isDark){ - highlightedText = textContent.replace(regexPattern, (match, match1, match2, match3) => { - regexDetected = true; - return ``+ - `${match1}`+ - `${match2}`+ - `${match3}`; - }); - } else{ - highlightedText = textContent.replace(regexPattern, (match, match1, match2, match3) => { - regexDetected = true; - return ``+ - `${match1}`+ - `${match2}`+ - `${match3}`; - }); - } - } - textField.innerHTML = highlightedText ? highlightedText : textContent; + regexDetected = this.highlightInputService.highlightRegex(element, initialCall, this.isDark, this.regexInStory, valueIndex, stepPre) - // Toastr logic if(initialCall && regexDetected) { this.regexInStory = true } - if(regexDetected && !this.regexInStory){ - this.regexInStory = true - this.toastr.info('View our Documentation for more Info','Regular Expression detected!'); - } - - // Set cursor to correct position - if(!initialCall){ - if (regexDetected) { //maybe not needed - const selection = window.getSelection(); - selection.removeAllRanges() - - // Call the function to find the correct node and offset - this.targetOffset = offset - const result = this.findNodeAndOffset(textField); - - if (result !== null) { - const [node, offsetIndex] = result; - requestAnimationFrame(() => { - if (node.nodeType === 3) { - // Text node - selection.setBaseAndExtent(node, offsetIndex, node, offsetIndex); - } else if (node.nodeType === 1 && node.childNodes.length > 0) { - // Element node with child nodes (e.g., ) - selection.setBaseAndExtent(node.childNodes[0], offsetIndex, node.childNodes[0], offsetIndex); - } - }); - } - } else { - requestAnimationFrame(() => { - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.setBaseAndExtent(textField.firstChild, offset, textField.firstChild, offset) - }) - } - } - } - - /** - * Helper for Regex Highlighter, find right node and index for current cursor position - * @param element HTMLElement - * @returns node: node with cursor, number: offest of cursor in node - */ - findNodeAndOffset(element: Node): [Node, number] | null { - if (element.nodeType === 3) { - // Text node - const textLength = (element.nodeValue || "").length; - if (this.targetOffset <= textLength) { - return [element, this.targetOffset]; - } else { - this.targetOffset -= textLength; - } - } else if (element.nodeType === 1){ - // Element node - for (let i = 0; i < element.childNodes.length; i++) { - const child = element.childNodes[i]; - const result = this.findNodeAndOffset(child); - if (result !== null) { - return result; - } - } - } - return null; - } - - /** - * Helper for Regex Highlighter, extract current cursor position - * @param element HTMLElement - * @returns num, offset of cursor position - */ - getCaretCharacterOffsetWithin(element) { - var caretOffset = 0; - var doc = element.ownerDocument || element.document; - var win = doc.defaultView || doc.parentWindow; - var sel; - if (typeof win.getSelection != "undefined") { - sel = win.getSelection(); - if (sel.rangeCount > 0) { - var range = win.getSelection().getRangeAt(0); - var preCaretRange = range.cloneRange(); - preCaretRange.selectNodeContents(element); - preCaretRange.setEnd(range.endContainer, range.endOffset); - caretOffset = preCaretRange.toString().length; - } - } else if ( (sel = doc.selection) && sel.type != "Control") { - var textRange = sel.createRange(); - var preCaretTextRange = doc.body.createTextRange(); - preCaretTextRange.moveToElementText(element); - preCaretTextRange.setEndPoint("EndToEnd", textRange); - caretOffset = preCaretTextRange.text.length; - } - return caretOffset; + } /** diff --git a/frontend/src/app/example-table/example-table.component.ts b/frontend/src/app/example-table/example-table.component.ts index 0dcf3d3db..30a3028e4 100644 --- a/frontend/src/app/example-table/example-table.component.ts +++ b/frontend/src/app/example-table/example-table.component.ts @@ -15,6 +15,7 @@ import { MatTable } from '@angular/material/table'; import { StepDefinition } from '../model/StepDefinition'; import { ThemePalette } from '@angular/material/core'; import { ThemingService } from '../Services/theming.service'; +import { HighlightInputService } from '../Services/highlight-input.service'; @Component({ @@ -158,8 +159,8 @@ export class ExampleTableComponent implements OnInit { private toastr: ToastrService, public exampleService: ExampleService, public apiService: ApiService, - public themeService: ThemingService - + public themeService: ThemingService, + public highlightInputService: HighlightInputService ) {} /** @@ -427,9 +428,7 @@ export class ExampleTableComponent implements OnInit { * @param initialCall if call is from ngDoCheck */ private highlightRegex(el, columnIndex, rowIndex, initialCall) { - const regex = /(\{Regex:)(.*?)(\})(?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with {Regex: and end with } const inputValue: string = el.textContent; - const offset = this.getCaretCharacterOffsetWithin(el) if(!initialCall){ this.selectedScenario.stepDefinitions.example[rowIndex + 1].values[columnIndex-1] = inputValue; @@ -438,123 +437,14 @@ export class ExampleTableComponent implements OnInit { if(!initialCall){ this.initialRegex = false; } - - let highlightedText: string; - var regexDetected = false; - if(this.isDark){ - highlightedText = inputValue.replace(regex, (match, match1, match2, match3) => { - regexDetected = true; - return ``+ - `${match1}`+ - `${match2}`+ - `${match3}`; - }); - } else{ - highlightedText = inputValue.replace(regex, (match, match1, match2, match3) => { - regexDetected = true; - return ``+ - `${match1}`+ - `${match2}`+ - `${match3}`; - }); - } - el.innerHTML = highlightedText + var regexDetected = false; + + regexDetected = this.highlightInputService.highlightRegex(el,initialCall, this.isDark, this.regexInStory) if(initialCall && regexDetected) { this.regexInStory = true } - if(regexDetected && !this.regexInStory){ - this.regexInStory = true - this.toastr.info('View our Documentation for more Info','Regular Expression detected!'); - } - - // Set cursor to correct position - if(!initialCall){ - if (regexDetected){ //maybe not needed - const selection = window.getSelection(); - selection.removeAllRanges() - - // Call the function to find the correct node and offset - this.targetOffset = offset - const result = this.findNodeAndOffset(el); - - if (result !== null) { - const [node, offsetIndex] = result; - requestAnimationFrame(() => { - if (node.nodeType === 3) { - // Text node - selection.setBaseAndExtent(node, offsetIndex, node, offsetIndex); - } else if (node.nodeType === 1 && node.childNodes.length > 0) { - // Element node with child nodes (e.g., ) - selection.setBaseAndExtent(node.childNodes[0], offsetIndex, node.childNodes[0], offsetIndex); - } - }); - } - } else { - requestAnimationFrame(() => { - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.setBaseAndExtent(el.firstChild, offset, el.firstChild, offset) - }) - } - } - } - - /** - * Helper for Regex Highlighter, find right node and index for current cursor position - * @param element HTMLElement - * @returns node: node with cursor, number: offest of cursor in node - */ - findNodeAndOffset(element: Node): [Node, number] | null { - if (element.nodeType === 3) { - // Text node - const textLength = (element.nodeValue || "").length; - if (this.targetOffset <= textLength) { - return [element, this.targetOffset]; - } else { - this.targetOffset -= textLength; - } - } else if (element.nodeType === 1){ - // Element node - for (let i = 0; i < element.childNodes.length; i++) { - const child = element.childNodes[i]; - const result = this.findNodeAndOffset(child); - if (result !== null) { - return result; - } - } - } - return null; - } - - /** - * Helper for Regex Highlighter, extract current cursor position - * @param element HTMLElement - * @returns num, offset of cursor position - */ - getCaretCharacterOffsetWithin(element) { - var caretOffset = 0; - var doc = element.ownerDocument || element.document; - var win = doc.defaultView || doc.parentWindow; - var sel; - if (typeof win.getSelection != "undefined") { - sel = win.getSelection(); - if (sel.rangeCount > 0) { - var range = win.getSelection().getRangeAt(0); - var preCaretRange = range.cloneRange(); - preCaretRange.selectNodeContents(element); - preCaretRange.setEnd(range.endContainer, range.endOffset); - caretOffset = preCaretRange.toString().length; - } - } else if ( (sel = doc.selection) && sel.type != "Control") { - var textRange = sel.createRange(); - var preCaretTextRange = doc.body.createTextRange(); - preCaretTextRange.moveToElementText(element); - preCaretTextRange.setEndPoint("EndToEnd", textRange); - caretOffset = preCaretTextRange.text.length; - } - return caretOffset; } /**