diff --git a/docs/release-notes-editor.md b/docs/release-notes-editor.md index 88d6c91146b4a318705faa8f053effe83e8631b2..70885efce3e10dcf61e4268b8e70a611a4c00ca6 100644 --- a/docs/release-notes-editor.md +++ b/docs/release-notes-editor.md @@ -2,8 +2,11 @@ Editor ====== ## 1.35.3 ### Verbesserungen -- Ändert Symbol und Tooltip-Namen für Knopf als Lückentextelement - +- Ändert Symbol und Tooltip-Namen für Knopf als Lückentextelement +- Erweiterung der Möglichkeiten für hervorzuhebende Textbereiche im TextEditor + - Die hervorzuhebenden TextBereichen können mit einer Farbe belegt werden + - Innerhalb eines Textbereichs können weitere Textbereiche mit anderen Farben definiert werden + - Eine noch tiefere Verschachtelung ist nicht möglich ## 1.35.2 ### Verbesserungen diff --git a/docs/release-notes-player.md b/docs/release-notes-player.md index f0b78eb06de98612fddef0878ed63118928f15db..dd7a819a3dc9bcd0cf2e27d8ab03e13e561aa61b 100644 --- a/docs/release-notes-player.md +++ b/docs/release-notes-player.md @@ -1,6 +1,11 @@ Player ====== ## 1.28.3 +### Verbesserungen +- Änderungen an hervorzuhebenden Textbereiche + - Die Dauer der Anzeige beträgt 15 Sekunden + - Es ist immer nur eine Hervorhebung aktiv + ### Fehlerbehebungen - Korrigiert das Scroll-Verhalten von versteckten Abschnitten mit unterschiedlichen Verzögerungen diff --git a/projects/editor/src/app/text-editor/extensions/anchorId.ts b/projects/editor/src/app/text-editor/extensions/anchorId.ts index 6826ccd107e6b9d64af7138c3953375331b410e3..432797832a616cced4fc53beccdbf53b428016c0 100644 --- a/projects/editor/src/app/text-editor/extensions/anchorId.ts +++ b/projects/editor/src/app/text-editor/extensions/anchorId.ts @@ -1,9 +1,20 @@ -import { Mark } from '@tiptap/core'; +import { Mark, mergeAttributes } from '@tiptap/core'; declare module '@tiptap/core' { interface Commands<ReturnType> { anchorId: { - toggleAnchorId: (attributes?: { anchorId: string }) => ReturnType, + toggleAnchorId: (attributes?: { + anchorId: string, + parentAnchorId: string, + anchorColor: string, + parentAnchorColor: string + }) => ReturnType, + setAnchorId: (attributes?: { + anchorId: string, + parentAnchorId: string, + anchorColor: string, + parentAnchorColor: string + }) => ReturnType } } } @@ -13,7 +24,7 @@ export const AnchorId = Mark.create({ addAttributes() { return { anchorId: { - default: null, + default: '', parseHTML: element => element.getAttribute('data-anchor-id'), renderHTML: attributes => { if (!attributes.anchorId) { @@ -23,6 +34,43 @@ export const AnchorId = Mark.create({ 'data-anchor-id': attributes.anchorId }; } + }, + parentAnchorId: { + default: '', + parseHTML: element => element.getAttribute('data-parent-anchor-id'), + renderHTML: attributes => { + if (!attributes.parentAnchorId) { + return {}; + } + return { + 'data-parent-anchor-id': attributes.parentAnchorId + }; + } + }, + anchorColor: { + default: '', + parseHTML: element => element.getAttribute('data-anchor-color') || element.style.backgroundColor, + renderHTML: attributes => { + if (!attributes.anchorColor) { + return {}; + } + return { + 'data-anchor-color': attributes.anchorColor, + style: `background-color: ${attributes.anchorColor};` + }; + } + }, + parentAnchorColor: { + default: '', + parseHTML: element => element.getAttribute('data-parent-anchor-color'), + renderHTML: attributes => { + if (!attributes.parentAnchorColor) { + return {}; + } + return { + 'data-parent-anchor-color': attributes.parentAnchorColor + }; + } } }; }, @@ -34,11 +82,12 @@ export const AnchorId = Mark.create({ ]; }, renderHTML({ HTMLAttributes }) { - return ['aspect-anchor', HTMLAttributes, 0]; + return ['aspect-anchor', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]; }, addCommands() { return { - toggleAnchorId: attributes => ({ commands }) => commands.toggleMark(this.name, attributes) + toggleAnchorId: attributes => ({ commands }) => commands.toggleMark(this.name, attributes), + setAnchorId: attributes => ({ commands }) => commands.setMark(this.name, attributes) }; } }); diff --git a/projects/editor/src/app/text-editor/rich-text-editor.component.html b/projects/editor/src/app/text-editor/rich-text-editor.component.html index d10ac2a8a156692de49687426d1f8d7a1f6b9f82..e197a182c994eb9a3380078d115ecf9f61ef6237 100644 --- a/projects/editor/src/app/text-editor/rich-text-editor.component.html +++ b/projects/editor/src/app/text-editor/rich-text-editor.component.html @@ -281,11 +281,19 @@ (click)="toggleBlockquote()"> <mat-icon>format_quote</mat-icon> </button> - <button mat-icon-button matTooltip="Bereich markieren" - [class.active]="editor.isActive('anchorId')" - (click)="applyAnchorId()"> - <mat-icon>read_more</mat-icon> - </button> + <div *ngIf="!clozeMode" class="combo-button" fxLayout="row" + [style.background-color]="selectedAnchorColor"> + <button mat-icon-button matTooltip="Bereich markieren" + (click)="applyAnchorId()"> + <mat-icon>read_more</mat-icon> + </button> + <mat-select matTooltip="Bereichsfarbe wählen" + (click)="anchorIdColorInput.click()" + (focus)="setSelectedAnchorIdText()"> + </mat-select> + <input matInput type="color" #anchorIdColorInput hidden + (input)="selectedAnchorColor = $any($event.target).value; applyAnchorId()"> + </div> </fieldset> </div> <div *ngIf="clozeMode" fxLayout="row" fxLayoutAlign="space-around center" > diff --git a/projects/editor/src/app/text-editor/rich-text-editor.component.ts b/projects/editor/src/app/text-editor/rich-text-editor.component.ts index 7061960c3414c5fa9411eb349a9d6f261922784d..4f7424f1c4ed4da33369642c957b19b4b2b9b61d 100644 --- a/projects/editor/src/app/text-editor/rich-text-editor.component.ts +++ b/projects/editor/src/app/text-editor/rich-text-editor.component.ts @@ -47,6 +47,8 @@ export class RichTextEditorComponent implements OnInit, AfterViewInit { selectedFontColor = 'lightgrey'; selectedHighlightColor = 'lightgrey'; + selectedAnchorColor = '#adff2f'; + selectedAnchorIdText = ''; selectedFontSize = '20px'; selectedIndentSize = 20; bulletListStyle: string = 'disc'; @@ -143,15 +145,55 @@ export class RichTextEditorComponent implements OnInit, AfterViewInit { } applyAnchorId(): void { - const text = window?.getSelection()?.toString(); - if (text) { - const id = text.replace(/[^0-9a-zA-Z]/g, '_').substring(0, 20); - this.editor.chain().focus().toggleAnchorId({ anchorId: id }).run(); + const id = this.getAnchorIdFromSelection(); + if (id) { + const activeAnchorId = this.editor.getAttributes('anchorId').anchorId; + const activeAnchorColor = this.editor.getAttributes('anchorId').anchorColor; + const activeParentAnchorId = this.editor.getAttributes('anchorId').parentAnchorId; + const activeParentAnchorColor = this.editor.getAttributes('anchorId').parentAnchorColor; + if (activeParentAnchorId) { // reset nested child + if (this.selectedAnchorColor === activeParentAnchorColor || this.selectedAnchorColor === activeAnchorColor) { + this.editor.chain().focus().setAnchorId({ + anchorId: activeParentAnchorId, + parentAnchorId: '', + anchorColor: activeParentAnchorColor, + parentAnchorColor: '' + }).run(); + } else { // set new color for nested Child + this.editor.chain().focus().setAnchorId({ + anchorId: activeAnchorId, + parentAnchorId: activeParentAnchorId, + anchorColor: this.selectedAnchorColor, + parentAnchorColor: activeParentAnchorColor + }).run(); + } + } else { // standard toggle + this.editor.chain().focus().toggleAnchorId({ + anchorId: id, + parentAnchorId: (activeAnchorId !== id) ? activeAnchorId : '', + anchorColor: this.selectedAnchorColor, + parentAnchorColor: (activeAnchorId !== id) ? activeAnchorColor : '' + }).run(); + } + this.resetSelectedAnchorIdText(); } else { console.warn('No text selected for anchor!'); } } + setSelectedAnchorIdText() { + this.selectedAnchorIdText = window.getSelection()?.toString() as string; + } + + private getAnchorIdFromSelection(): string { + const selection = window?.getSelection()?.toString() || this.selectedAnchorIdText; + return selection.replace(/[^0-9a-zA-Z]/g, '_').substring(0, 20); + } + + private resetSelectedAnchorIdText(): void { + this.selectedAnchorIdText = ''; + } + alignText(direction: string): void { this.editor.chain().focus().setTextAlign(direction).run(); } diff --git a/projects/editor/src/styles.css b/projects/editor/src/styles.css index 41703b3e0c9dd8590e920065467e5bdf8dbb0644..1aea6cd7dc25771caf07a7e9fb9ca5e3f7f472ed 100644 --- a/projects/editor/src/styles.css +++ b/projects/editor/src/styles.css @@ -17,6 +17,3 @@ input[type=color] { cursor: pointer; } -aspect-anchor { - background-color: #adff2f; -} diff --git a/projects/player/src/app/services/anchor.service.ts b/projects/player/src/app/services/anchor.service.ts index cf6ff034c921c0b64719a366bce1bb911b4779b1..a63c73ea4612d0eefa74a781cb208756c7f84d8a 100644 --- a/projects/player/src/app/services/anchor.service.ts +++ b/projects/player/src/app/services/anchor.service.ts @@ -11,6 +11,7 @@ export class AnchorService { if (this.activeAnchors[anchorId]) { this.removeAnchor(anchorId); } else { + this.reset(); this.addAnchor(anchorId); } } @@ -18,7 +19,7 @@ export class AnchorService { private addAnchor(anchorId: string): void { this.activeAnchors[anchorId] = of(true) .pipe( - delay(30000)) + delay(15000)) .subscribe(() => { this.removeAnchor(anchorId); }); @@ -31,10 +32,32 @@ export class AnchorService { AnchorService.toggleAnchorRendering(anchorId, false); } - private static toggleAnchorRendering(anchorId: string, shouldScroll: boolean): void { - const anchors = document.querySelectorAll(`aspect-anchor[data-anchor-id="${anchorId}"]`); - anchors.forEach(anchor => anchor.classList.toggle('active-anchor')); - if (shouldScroll) anchors.item(0).scrollIntoView({ behavior: 'smooth', block: 'start' }); + private static toggleAnchorRendering(anchorId: string, showAnchor: boolean): void { + const anchors = Array.from( + document.querySelectorAll(`aspect-anchor[data-anchor-id="${anchorId}"]`) + ) as HTMLElement[]; + const nestedAnchors = Array.from( + document.querySelectorAll(`aspect-anchor[data-parent-anchor-id="${anchorId}"]`) + ) as HTMLElement[]; + anchors.forEach(anchor => { + if (!showAnchor && anchor.dataset.parentAnchorColor) { + anchor.style.backgroundColor = anchor.dataset.parentAnchorColor as string; + } else { + anchor.style.backgroundColor = anchor.dataset.anchorColor as string; + } + anchor.classList.toggle('active-anchor'); + }); + nestedAnchors.forEach(anchor => { + if (showAnchor) { + anchor.style.backgroundColor = (anchor.dataset.parentAnchorColor) as string; + } else { + anchor.style.backgroundColor = anchor.dataset.anchorColor as string; + } + anchor.classList.toggle('active-nested-anchor'); + }); + if (anchors.length && showAnchor) { + anchors[0].scrollIntoView({ behavior: 'smooth', block: 'start' }); + } } reset(): void { diff --git a/projects/player/src/styles.css b/projects/player/src/styles.css index e4ed25d59f9e59338e164e36dfc306d7a5929eca..3097b5772fd4ccc42b6de9eb6bcdbb072063a840 100644 --- a/projects/player/src/styles.css +++ b/projects/player/src/styles.css @@ -16,10 +16,17 @@ body { } .active-anchor { - background-color: #adff2f; scroll-margin: 100px 0; } .active-anchor > mark, .active-anchor > aspect-marked { background-color: unset !important; } + +.active-nested-anchor > mark, .active-anchor > aspect-marked { + background-color: unset !important; +} + +aspect-anchor:not(.active-anchor):not(.active-nested-anchor) { + background-color: unset !important; +}