diff --git a/docs/release-notes-editor.md b/docs/release-notes-editor.md index b1d8ff014fb1dc316d6361c2374b203f210d8a9c..68da2dee1970d98eaf6823e53a6648bf3ff9dc37 100644 --- a/docs/release-notes-editor.md +++ b/docs/release-notes-editor.md @@ -1,6 +1,15 @@ Editor ====== -##1.35.2 +## 1.35.3 +### Verbesserungen +- Ä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 +- Überarbeitet die Variablenliste zur Codierung für Geometrie-, Bildbereiche- und Textelemente + +## 1.35.2 ### Verbesserungen - Wendet die Eigenschaft "Schreibgeschützt" auf Formel Elemente an - Auswahllisten, die nur ein Element zulassen, werden von diesem Element komplett ausgefüllt diff --git a/docs/release-notes-player.md b/docs/release-notes-player.md index af5bb6e21bf18e35bff6a4be6c6bdb2208b75190..c8f772b6315b352488ebef08361adfa783d5b5fa 100644 --- a/docs/release-notes-player.md +++ b/docs/release-notes-player.md @@ -1,5 +1,14 @@ 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 +- Korrigiert Darstellungsfehler von Eingabefeldern in Lückentexten in FireFox ## 1.28.2 ### Verbesserungen diff --git a/package.json b/package.json index be30b71f1f7789a99aadb6cb1f44d43c40b99cb1..19fc2982779a19e32012cccc865c30cf1dc9c854 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "verona-modules-aspect", "config": { - "player_version": "1.28.2", - "editor_version": "1.35.2", + "player_version": "1.28.3", + "editor_version": "1.35.3", "unit_definition_version": "3.8.0" }, "scripts": { diff --git a/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts b/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts index e9210a0f62ccb564842f85d08fb1ed0ac3673402..14dafbbcd101c3e631267aca3550a6af1a1b4668 100644 --- a/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts +++ b/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts @@ -10,7 +10,7 @@ import { TextInputComponent } from 'common/directives/text-input-component.direc selector: 'aspect-text-field-simple', template: ` <input #input - class="clozeChild" + class="cloze-child" autocomplete="off" autocapitalize="none" autocorrect="off" @@ -37,10 +37,10 @@ import { TextInputComponent } from 'common/directives/text-input-component.direc (blur)="focusChanged.emit({ inputElement: input, focused: false })"> `, styles: [ - '.clozeChild {border: 1px solid rgba(0,0,0,.12); border-radius: 4px}', - 'input {width: calc(100% - 2px); height: calc(100% - 2px); padding: 0 0 1px 0;}', + '.cloze-child {border: 1px solid rgba(0,0,0,.12); border-radius: 4px;}', + 'input {width: 100%; height: 100%; padding: 0 2px; box-sizing: border-box}', 'input:hover {border: 1px solid currentColor;}', - 'input:focus {outline: 1px solid #3f51b5;}', + 'input:focus {border: 1px solid #3f51b5; outline: 0}', '.errors {border: 2px solid #f44336 !important;}' ] }) diff --git a/projects/common/models/elements/geometry/geometry.ts b/projects/common/models/elements/geometry/geometry.ts index 0f8c0f287162b10926c42810d818a9df170633bc..834e78395bf28e4eb5ce8fa788bcc2e104f9a0f4 100644 --- a/projects/common/models/elements/geometry/geometry.ts +++ b/projects/common/models/elements/geometry/geometry.ts @@ -1,5 +1,6 @@ import { Type } from '@angular/core'; import { + AnswerScheme, PositionedUIElement, PositionProperties, UIElement } from 'common/models/elements/element'; import { ElementComponent } from 'common/directives/element-component.directive'; @@ -34,6 +35,22 @@ export class GeometryElement extends UIElement implements PositionedUIElement { this.position = UIElement.initPositionProps({ ...element.position }); } + hasAnswerScheme(): boolean { + return Boolean(this.getAnswerScheme); + } + + getAnswerScheme(): AnswerScheme { + return { + id: this.id, + type: 'string', + format: 'ggb-file', + multiple: false, + nullable: false, + values: [], + valuesComplete: false + }; + } + getElementComponent(): Type<ElementComponent> { return GeometryComponent; } diff --git a/projects/common/models/elements/input-elements/hotspot-image.ts b/projects/common/models/elements/input-elements/hotspot-image.ts index 57cb50b53585746c5e05a61cad229edd583a2a7c..606fd0e4a6f144091e0d3f9973dd845a0d0a9c13 100644 --- a/projects/common/models/elements/input-elements/hotspot-image.ts +++ b/projects/common/models/elements/input-elements/hotspot-image.ts @@ -35,15 +35,17 @@ export class HotspotImageElement extends InputElement implements PositionedUIEle private getAnswerSchemeValues(): AnswerSchemeValue[] { return this.value - .map((hotspot, index) => ({ - value: (index + 1).toString(), - label: `top: ${hotspot.top}, - left: ${hotspot.left}, - height: ${hotspot.height}, - width: ${hotspot.width}, - shape: ${hotspot.shape}, - value: ${hotspot.value}` - })); + .map(hotspot => ( + [{ + value: 'true', + // eslint-disable-next-line max-len + label: `Gefüllt top: ${hotspot.top}, left: ${hotspot.left}, height: ${hotspot.height}, width: ${hotspot.width}, shape: ${hotspot.shape}, value: ${hotspot.value}` + }, { + value: 'false', + // eslint-disable-next-line max-len + label: `Nicht gefüllt: top: ${hotspot.top}, left: ${hotspot.left}, height: ${hotspot.height}, width: ${hotspot.width}, shape: ${hotspot.shape}, value: ${hotspot.value}` + } + ])).flat(); } getElementComponent(): Type<ElementComponent> { diff --git a/projects/common/models/elements/text/text.ts b/projects/common/models/elements/text/text.ts index 9a1bccf11b7bc94d33adc589ccea60536977c10a..199e09da0cb5d7060a065f9f638a20c8e84fc174 100644 --- a/projects/common/models/elements/text/text.ts +++ b/projects/common/models/elements/text/text.ts @@ -50,7 +50,7 @@ export class TextElement extends UIElement implements PositionedUIElement { return { id: this.id, type: 'string', - format: 'coloredSelectionRange', + format: 'text-selection', multiple: true, nullable: false, values: [], 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 c577b6983d5cabe9300e082f220f0049e845bddf..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" > @@ -301,9 +309,9 @@ (click)="insertToggleButton()"> <mat-icon>radio_button_checked</mat-icon> </button> - <button mat-icon-button matTooltip="Navigationsknopf" [matTooltipShowDelay]="300" + <button mat-icon-button matTooltip="Knopf" [matTooltipShowDelay]="300" (click)="insertButton()"> - <mat-icon>navigation</mat-icon> + <mat-icon>smart_button</mat-icon> </button> </div> </div> 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/directives/section-visibility-handling.directive.ts b/projects/player/src/app/directives/section-visibility-handling.directive.ts index cfcd928e80920db866f30f2c6446353fdf8e19b8..5255465a624373ba4a2cc12aac6296a84e4bcb1e 100644 --- a/projects/player/src/app/directives/section-visibility-handling.directive.ts +++ b/projects/player/src/app/directives/section-visibility-handling.directive.ts @@ -22,7 +22,8 @@ export class SectionVisibilityHandlingDirective { this.isScrollSection = this.isVisible ? false : this.pageSections - .filter(pageSection => pageSection.activeAfterID === this.section.activeAfterID) + .filter(pageSection => pageSection.activeAfterID === this.section.activeAfterID && + pageSection.activeAfterIdDelay === this.section.activeAfterIdDelay) .findIndex(section => section === this.section) === 0; if (this.mediaStatusChanged) { @@ -38,8 +39,10 @@ export class SectionVisibilityHandlingDirective { if (!this.isVisible) { this.setVisibility(id === this.section.activeAfterID); if (this.isScrollSection) { - this.elementRef.nativeElement.style.scrollMargin = `${window.innerHeight / 3}px`; - this.elementRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + this.elementRef.nativeElement.style.scrollMarginTop = 100; + setTimeout(() => { + this.elementRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }); } } } 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; +}