From ff3a08b628bcd4ebbfd363bfd4b6dd9632644bdc Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Tue, 29 Oct 2024 00:36:22 +0100 Subject: [PATCH 1/7] Added delay with undo button on delete --- .../app/shared/metis/post/post.component.html | 48 ++++++++++------- .../app/shared/metis/post/post.component.scss | 28 ++++++++++ .../app/shared/metis/post/post.component.ts | 53 ++++++++++++++++++- .../post-header/post-header.component.html | 45 ++++++++-------- .../post-header/post-header.component.ts | 7 ++- .../profile-picture.component.html | 4 +- .../profile-picture.component.ts | 1 + src/main/webapp/i18n/de/metis.json | 4 +- src/main/webapp/i18n/en/metis.json | 4 +- .../shared/metis/post/post.component.spec.ts | 49 ++++++++++++++++- .../post-header/post-header.component.spec.ts | 19 ++++--- 11 files changed, 204 insertions(+), 58 deletions(-) diff --git a/src/main/webapp/app/shared/metis/post/post.component.html b/src/main/webapp/app/shared/metis/post/post.component.html index 53d03e33673b..54062aa64c4f 100644 --- a/src/main/webapp/app/shared/metis/post/post.component.html +++ b/src/main/webapp/app/shared/metis/post/post.component.html @@ -4,10 +4,12 @@ [previewMode]="previewMode" [readOnlyMode]="readOnlyMode" [posting]="posting" + [isDeleted]="isDeleted" [isCommunicationPage]="isCommunicationPage" [hasChannelModerationRights]="hasChannelModerationRights" (isModalOpen)="displayInlineInput = true" [lastReadDate]="lastReadDate" + (isDeleteEvent)="onDeleteEvent(true)" />
@@ -49,7 +51,7 @@ }
- @if (!displayInlineInput) { + @if (!isDeleted && !displayInlineInput) { + } @else if (isDeleted) { + + + + } - @if (displayInlineInput && !readOnlyMode) { + @if (!isDeleted && displayInlineInput && !readOnlyMode) {
} -
- - @if (!previewMode) { - - } -
+ @if (!isDeleted) { +
+ + @if (!previewMode) { + + } +
+ } implements OnInit, OnChanges, AfterContentChecked { +export class PostComponent extends PostingDirective implements OnInit, OnChanges, AfterContentChecked, OnDestroy { @Input() lastReadDate?: dayjs.Dayjs; @Input() readOnlyMode: boolean; @Input() previewMode: boolean; @@ -53,6 +61,11 @@ export class PostComponent extends PostingDirective implements OnInit, OnC sortedAnswerPosts: AnswerPost[]; createdAnswerPost: AnswerPost; isAtLeastTutorInCourse: boolean; + isDeleted = false; + readonly timeToDeleteInSeconds = 6; + deleteTimerInSeconds = 6; + deleteTimer: NodeJS.Timeout | undefined; + deleteInterval: NodeJS.Timeout | undefined; pageType: PageType; contextInformation: ContextInformation; @@ -95,6 +108,16 @@ export class PostComponent extends PostingDirective implements OnInit, OnC this.sortAnswerPosts(); } + ngOnDestroy(): void { + if (this.deleteTimer !== undefined) { + clearTimeout(this.deleteTimer); + } + + if (this.deleteInterval !== undefined) { + clearInterval(this.deleteInterval); + } + } + /** * this lifecycle hook is required to avoid causing "Expression has changed after it was checked"-error when * dismissing the edit-create-modal -> we do not want to store changes in the create-edit-modal that are not saved @@ -178,4 +201,32 @@ export class PostComponent extends PostingDirective implements OnInit, OnC } } } + + onDeleteEvent(isDelete: boolean) { + this.isDeleted = isDelete; + + if (this.deleteTimer !== undefined) { + clearTimeout(this.deleteTimer); + } + + if (this.deleteInterval !== undefined) { + clearInterval(this.deleteInterval); + } + + if (isDelete) { + this.deleteTimerInSeconds = this.timeToDeleteInSeconds; + + this.deleteTimer = setTimeout( + () => { + this.metisService.deletePost(this.posting); + }, + this.deleteTimerInSeconds * 1000 + 1000, + ); + + this.deleteInterval = setInterval(() => { + this.deleteTimerInSeconds = Math.max(0, this.deleteTimerInSeconds - 1); + this.changeDetector.detectChanges(); + }, 1000); + } + } } diff --git a/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.html b/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.html index a8d59ea4cb9a..190a7c27af0a 100644 --- a/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.html +++ b/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.html @@ -7,6 +7,7 @@ fontSizeInRem="0.9" imageId="post-profile-picture" defaultPictureId="post-default-profile-picture" + [isGray]="isDeleted()" [authorId]="posting.author?.id" [authorName]="posting.author?.name" [imageUrl]="posting.author?.imageUrl" @@ -45,25 +46,27 @@ } -
- @if (mayEditOrDelete) { - - } - - @if (mayEditOrDelete) { - - } -
+ @if (!isDeleted()) { +
+ @if (mayEditOrDelete) { + + } + + @if (mayEditOrDelete) { + + } +
+ } diff --git a/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.ts b/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.ts index b0a52e3b88bc..5f4814a9bd1d 100644 --- a/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.ts +++ b/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnChanges, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnChanges, OnDestroy, OnInit, ViewChild, input, output } from '@angular/core'; import { Post } from 'app/entities/metis/post.model'; import { PostingHeaderDirective } from 'app/shared/metis/posting-header/posting-header.directive'; import { MetisService } from 'app/shared/metis/metis.service'; @@ -23,6 +23,9 @@ export class PostHeaderComponent extends PostingHeaderDirective implements isAtLeastInstructorInCourse: boolean; mayEditOrDelete = false; + isDeleted = input(false); + isDeleteEvent = output(); + // Icons faPencilAlt = faPencilAlt; faCheckSquare = faCheckSquare; @@ -60,7 +63,7 @@ export class PostHeaderComponent extends PostingHeaderDirective implements * invokes the metis service to delete a post */ deletePosting(): void { - this.metisService.deletePost(this.posting); + this.isDeleteEvent.emit(true); } setMayEditOrDelete(): void { diff --git a/src/main/webapp/app/shared/profile-picture/profile-picture.component.html b/src/main/webapp/app/shared/profile-picture/profile-picture.component.html index 2d08f4f7737c..64acbdb0ce98 100644 --- a/src/main/webapp/app/shared/profile-picture/profile-picture.component.html +++ b/src/main/webapp/app/shared/profile-picture/profile-picture.component.html @@ -3,7 +3,7 @@ {{ userProfilePictureInitials }} @@ -13,7 +13,7 @@ [alt]="authorName()" class="profile-picture rounded-3" [src]="imageUrl()" - [ngStyle]="{ 'background-color': profilePictureBackgroundColor }" + [ngStyle]="{ 'background-color': isGray() ? '#888' : profilePictureBackgroundColor }" [ngClass]="imageClass()" /> } diff --git a/src/main/webapp/app/shared/profile-picture/profile-picture.component.ts b/src/main/webapp/app/shared/profile-picture/profile-picture.component.ts index 5dd16a992369..f8600f3a0ccf 100644 --- a/src/main/webapp/app/shared/profile-picture/profile-picture.component.ts +++ b/src/main/webapp/app/shared/profile-picture/profile-picture.component.ts @@ -24,6 +24,7 @@ export class ProfilePictureComponent implements OnInit, OnChanges { readonly imageId = input(''); readonly defaultPictureId = input(''); readonly isEditable = input(false); + readonly isGray = input(false); profilePictureBackgroundColor: string; userProfilePictureInitials: string; diff --git a/src/main/webapp/i18n/de/metis.json b/src/main/webapp/i18n/de/metis.json index 441dcb362e63..8578c5f050e2 100644 --- a/src/main/webapp/i18n/de/metis.json +++ b/src/main/webapp/i18n/de/metis.json @@ -125,7 +125,9 @@ "exerciseOrLecture": "Übung / Vorlesung", "showAllPosts": "Zeige alle Nachrichten", "showContent": "Inhalt ausklappen", - "collapseContent": "Inhalt einklappen" + "collapseContent": "Inhalt einklappen", + "deletedContent": "Beitrag wird in {{ progress }} Sekunde(n) gelöscht.", + "undoDelete": "Löschen rückgängig machen" }, "answerPost": { "created": "Antwort erfolgreich erstellt", diff --git a/src/main/webapp/i18n/en/metis.json b/src/main/webapp/i18n/en/metis.json index 4778ad6c3532..92a75b2da28d 100644 --- a/src/main/webapp/i18n/en/metis.json +++ b/src/main/webapp/i18n/en/metis.json @@ -125,7 +125,9 @@ "exerciseOrLecture": "Exercise / Lecture", "showAllPosts": "Show all messages", "showContent": "Show content", - "collapseContent": "Collapse content" + "collapseContent": "Collapse content", + "deletedContent": "Post is being deleted in {{ progress }} second(s).", + "undoDelete": "Undo delete" }, "answerPost": { "created": "New reply successfully created", diff --git a/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts b/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts index dac357f94bec..f2baf5576b7b 100644 --- a/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; +import { MockComponent, MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; import { DebugElement } from '@angular/core'; import { HtmlForMarkdownPipe } from 'app/shared/pipes/html-for-markdown.pipe'; import { PostComponent } from 'app/shared/metis/post/post.component'; @@ -33,6 +33,7 @@ import { HttpResponse } from '@angular/common/http'; import { MockRouter } from '../../../../helpers/mocks/mock-router'; import { AnswerPostCreateEditModalComponent } from 'app/shared/metis/posting-create-edit-modal/answer-post-create-edit-modal/answer-post-create-edit-modal.component'; import { PostReactionsBarComponent } from 'app/shared/metis/posting-reactions-bar/post-reactions-bar/post-reactions-bar.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; describe('PostComponent', () => { let component: PostComponent; @@ -46,7 +47,7 @@ describe('PostComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [MockDirective(NgbTooltip)], + imports: [MockDirective(NgbTooltip), MockModule(BrowserAnimationsModule)], providers: [ provideRouter([]), { provide: MetisService, useClass: MockMetisService }, @@ -214,4 +215,48 @@ describe('PostComponent', () => { expect(setActiveConversationSpy).toHaveBeenCalledWith(metisChannel.id!); }); + + it('should set isDeleted to true', () => { + component.onDeleteEvent(true); + expect(component.isDeleted).toBeTrue(); + }); + + it('should clear existing timers and intervals', () => { + const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); + const clearIntervalSpy = jest.spyOn(global, 'clearInterval'); + + component.deleteTimer = setTimeout(() => {}, 1000); + component.deleteInterval = setInterval(() => {}, 1000); + component.onDeleteEvent(true); + + expect(clearIntervalSpy).toHaveBeenCalledOnce(); + expect(clearTimeoutSpy).toHaveBeenCalledOnce(); + }); + + it('should clear existing timers and intervals on destroy', () => { + const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); + const clearIntervalSpy = jest.spyOn(global, 'clearInterval'); + + component.deleteTimer = setTimeout(() => {}, 1000); + component.deleteInterval = setInterval(() => {}, 1000); + component.ngOnDestroy(); + + expect(clearIntervalSpy).toHaveBeenCalledOnce(); + expect(clearTimeoutSpy).toHaveBeenCalledOnce(); + }); + + it('should set deleteTimer and deleteInterval when isDelete is true', () => { + component.onDeleteEvent(true); + + expect(component.deleteTimer).toBeDefined(); + expect(component.deleteInterval).toBeDefined(); + expect(component.deleteTimerInSeconds).toBe(component.timeToDeleteInSeconds); + }); + + it('should not set timers when isDelete is false', () => { + component.onDeleteEvent(false); + + expect(component.deleteTimer).toBeUndefined(); + expect(component.deleteInterval).toBeUndefined(); + }); }); diff --git a/src/test/javascript/spec/component/shared/metis/postings-header/post-header/post-header.component.spec.ts b/src/test/javascript/spec/component/shared/metis/postings-header/post-header/post-header.component.spec.ts index 8a244697b4fe..f599c236354d 100644 --- a/src/test/javascript/spec/component/shared/metis/postings-header/post-header/post-header.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/postings-header/post-header/post-header.component.spec.ts @@ -29,7 +29,6 @@ describe('PostHeaderComponent', () => { let metisServiceUserIsAtLeastTutorStub: jest.SpyInstance; let metisServiceUserIsAtLeastInstructorStub: jest.SpyInstance; let metisServiceUserIsAuthorOfPostingStub: jest.SpyInstance; - let metisServiceDeletePostMock: jest.SpyInstance; beforeEach(() => { return TestBed.configureTestingModule({ imports: [MockModule(FormsModule), MockModule(ReactiveFormsModule), MockDirective(NgbTooltip), MockModule(MetisModule)], @@ -54,7 +53,6 @@ describe('PostHeaderComponent', () => { metisServiceUserIsAtLeastTutorStub = jest.spyOn(metisService, 'metisUserIsAtLeastTutorInCourse'); metisServiceUserIsAtLeastInstructorStub = jest.spyOn(metisService, 'metisUserIsAtLeastInstructorInCourse'); metisServiceUserIsAuthorOfPostingStub = jest.spyOn(metisService, 'metisUserIsAuthorOfPosting'); - metisServiceDeletePostMock = jest.spyOn(metisService, 'deletePost'); debugElement = fixture.debugElement; component.posting = metisPostLectureUser1; component.ngOnInit(); @@ -111,14 +109,6 @@ describe('PostHeaderComponent', () => { expect(getElement(debugElement, '.deleteIcon')).toBeNull(); }); - it('should invoke metis service when delete icon is clicked', () => { - metisServiceUserIsAtLeastTutorStub.mockReturnValue(true); - fixture.detectChanges(); - expect(getElement(debugElement, '.deleteIcon')).not.toBeNull(); - component.deletePosting(); - expect(metisServiceDeletePostMock).toHaveBeenCalledOnce(); - }); - it('should not display edit and delete options to tutor if posting is announcement', () => { metisServiceUserIsAtLeastInstructorStub.mockReturnValue(false); component.posting = metisAnnouncement; @@ -137,6 +127,15 @@ describe('PostHeaderComponent', () => { expect(getElement(debugElement, '.deleteIcon')).not.toBeNull(); }); + it('should not display edit and delete options when post is deleted', () => { + fixture.componentRef.setInput('isDeleted', true); + component.ngOnInit(); + fixture.detectChanges(); + expect(getElement(debugElement, '.editIcon')).toBeNull(); + expect(getElement(debugElement, '.deleteIcon')).toBeNull(); + fixture.componentRef.setInput('isDeleted', false); + }); + it.each` input | expect ${UserRole.INSTRUCTOR} | ${'post-authority-icon-instructor'} From 2804c140f1a0270bb5481c55c0c68f85f484551e Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Tue, 29 Oct 2024 01:39:46 +0100 Subject: [PATCH 2/7] Added functionality to answer posts --- .../answer-post/answer-post.component.html | 32 ++++--- .../answer-post/answer-post.component.ts | 8 ++ .../app/shared/metis/post/post.component.ts | 51 +---------- .../answer-post-header.component.html | 84 ++++++++++--------- .../answer-post-header.component.ts | 2 +- .../post-header/post-header.component.ts | 5 +- .../posting-header.directive.ts | 5 +- .../app/shared/metis/posting.directive.ts | 45 +++++++++- .../answer-post/answer-post.component.spec.ts | 12 +-- .../answer-post-header.component.spec.ts | 21 ++--- 10 files changed, 136 insertions(+), 129 deletions(-) diff --git a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html index 52b4aebd1e29..2e24426d6f93 100644 --- a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html +++ b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html @@ -6,8 +6,10 @@ [isCommunicationPage]="isCommunicationPage" [lastReadDate]="lastReadDate" [hasChannelModerationRights]="hasChannelModerationRights" + [isDeleted]="isDeleted" + (isDeleteEvent)="onDeleteEvent(true)" /> - @if (!createAnswerPostModal.isInputOpen) { + @if (!isDeleted && !createAnswerPostModal.isInputOpen) {
+ } @else if (isDeleted) { + + + + }
-
- -
+ @if (!isDeleted) { +
+ +
+ } diff --git a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.ts b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.ts index 442ddc748082..177791de4758 100644 --- a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.ts +++ b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.ts @@ -2,12 +2,19 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewCh import { AnswerPost } from 'app/entities/metis/answer-post.model'; import { PostingDirective } from 'app/shared/metis/posting.directive'; import dayjs from 'dayjs/esm'; +import { animate, style, transition, trigger } from '@angular/animations'; @Component({ selector: 'jhi-answer-post', templateUrl: './answer-post.component.html', styleUrls: ['./answer-post.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('fade', [ + transition(':enter', [style({ opacity: 0 }), animate('300ms ease-in', style({ opacity: 1 }))]), + transition(':leave', [animate('300ms ease-out', style({ opacity: 0 }))]), + ]), + ], }) export class AnswerPostComponent extends PostingDirective { @Input() lastReadDate?: dayjs.Dayjs; @@ -15,6 +22,7 @@ export class AnswerPostComponent extends PostingDirective { @Output() openPostingCreateEditModal = new EventEmitter(); @Output() userReferenceClicked = new EventEmitter(); @Output() channelReferenceClicked = new EventEmitter(); + isAnswerPost = true; @Input() isReadOnlyMode = false; diff --git a/src/main/webapp/app/shared/metis/post/post.component.ts b/src/main/webapp/app/shared/metis/post/post.component.ts index f8858c98ccaa..f49132143432 100644 --- a/src/main/webapp/app/shared/metis/post/post.component.ts +++ b/src/main/webapp/app/shared/metis/post/post.component.ts @@ -1,20 +1,6 @@ -import { - AfterContentChecked, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnChanges, - OnDestroy, - OnInit, - Output, - ViewChild, - ViewContainerRef, -} from '@angular/core'; +import { AfterContentChecked, ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; import { Post } from 'app/entities/metis/post.model'; import { PostingDirective } from 'app/shared/metis/posting.directive'; -import { MetisService } from 'app/shared/metis/metis.service'; import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { ContextInformation, DisplayPriority, PageType, RouteComponents } from '../metis.util'; import { faBullhorn, faCheckSquare } from '@fortawesome/free-solid-svg-icons'; @@ -61,11 +47,6 @@ export class PostComponent extends PostingDirective implements OnInit, OnC sortedAnswerPosts: AnswerPost[]; createdAnswerPost: AnswerPost; isAtLeastTutorInCourse: boolean; - isDeleted = false; - readonly timeToDeleteInSeconds = 6; - deleteTimerInSeconds = 6; - deleteTimer: NodeJS.Timeout | undefined; - deleteInterval: NodeJS.Timeout | undefined; pageType: PageType; contextInformation: ContextInformation; @@ -77,8 +58,6 @@ export class PostComponent extends PostingDirective implements OnInit, OnC faCheckSquare = faCheckSquare; constructor( - private metisService: MetisService, - protected changeDetector: ChangeDetectorRef, private oneToOneChatService: OneToOneChatService, private metisConversationService: MetisConversationService, private router: Router, @@ -201,32 +180,4 @@ export class PostComponent extends PostingDirective implements OnInit, OnC } } } - - onDeleteEvent(isDelete: boolean) { - this.isDeleted = isDelete; - - if (this.deleteTimer !== undefined) { - clearTimeout(this.deleteTimer); - } - - if (this.deleteInterval !== undefined) { - clearInterval(this.deleteInterval); - } - - if (isDelete) { - this.deleteTimerInSeconds = this.timeToDeleteInSeconds; - - this.deleteTimer = setTimeout( - () => { - this.metisService.deletePost(this.posting); - }, - this.deleteTimerInSeconds * 1000 + 1000, - ); - - this.deleteInterval = setInterval(() => { - this.deleteTimerInSeconds = Math.max(0, this.deleteTimerInSeconds - 1); - this.changeDetector.detectChanges(); - }, 1000); - } - } } diff --git a/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html b/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html index ca36ef227e51..cbe2ad73de8d 100644 --- a/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html +++ b/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html @@ -11,6 +11,7 @@ [authorName]="posting.author?.name" [imageUrl]="posting.author?.imageUrl" [isEditable]="currentUser !== undefined && posting.author.id === currentUser.id" + [isGray]="isDeleted()" > @@ -37,48 +38,49 @@ } - -
- @if (mayEditOrDelete) { - - } - @if (mayEditOrDelete) { - - } - @if (!isAnswerOfAnnouncement) { -
- @if (posting.resolvesPost) { -
- -
- } @else { - @if (isAtLeastTutorInCourse || isAuthorOfOriginalPost) { + @if (!isDeleted()) { +
+ @if (mayEditOrDelete) { + + } + @if (mayEditOrDelete) { + + } + @if (!isAnswerOfAnnouncement) { +
+ @if (posting.resolvesPost) {
- +
+ } @else { + @if (isAtLeastTutorInCourse || isAuthorOfOriginalPost) { +
+ +
+ } } - } -
- } -
+
+ } +
+ } diff --git a/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.ts b/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.ts index a48dc5137678..1d70cdd34bc6 100644 --- a/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.ts +++ b/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.ts @@ -50,7 +50,7 @@ export class AnswerPostHeaderComponent extends PostingHeaderDirective implements isAtLeastInstructorInCourse: boolean; mayEditOrDelete = false; - isDeleted = input(false); - isDeleteEvent = output(); - // Icons faPencilAlt = faPencilAlt; faCheckSquare = faCheckSquare; diff --git a/src/main/webapp/app/shared/metis/posting-header/posting-header.directive.ts b/src/main/webapp/app/shared/metis/posting-header/posting-header.directive.ts index b20c051bd433..7892a6df0f09 100644 --- a/src/main/webapp/app/shared/metis/posting-header/posting-header.directive.ts +++ b/src/main/webapp/app/shared/metis/posting-header/posting-header.directive.ts @@ -1,5 +1,5 @@ import { Posting } from 'app/entities/metis/posting.model'; -import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Directive, EventEmitter, Input, OnInit, Output, input, output } from '@angular/core'; import dayjs from 'dayjs/esm'; import { MetisService } from 'app/shared/metis/metis.service'; import { UserRole } from 'app/shared/metis/metis.util'; @@ -16,6 +16,9 @@ export abstract class PostingHeaderDirective implements OnIni @Input() hasChannelModerationRights = false; @Output() isModalOpen = new EventEmitter(); + + isDeleted = input(false); + isDeleteEvent = output(); isAtLeastTutorInCourse: boolean; isAuthorOfPosting: boolean; postingIsOfToday: boolean; diff --git a/src/main/webapp/app/shared/metis/posting.directive.ts b/src/main/webapp/app/shared/metis/posting.directive.ts index 8e6636ee4c7a..3bee7f149d75 100644 --- a/src/main/webapp/app/shared/metis/posting.directive.ts +++ b/src/main/webapp/app/shared/metis/posting.directive.ts @@ -1,5 +1,6 @@ import { Posting } from 'app/entities/metis/posting.model'; -import { Directive, Input, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Directive, Input, OnInit, inject } from '@angular/core'; +import { MetisService } from 'app/shared/metis/metis.service'; @Directive() export abstract class PostingDirective implements OnInit { @@ -10,9 +11,51 @@ export abstract class PostingDirective implements OnInit { @Input() hasChannelModerationRights = false; @Input() isThreadSidebar: boolean; + isAnswerPost = false; + isDeleted = false; + readonly timeToDeleteInSeconds = 6; + deleteTimerInSeconds = 6; + deleteTimer: NodeJS.Timeout | undefined; + deleteInterval: NodeJS.Timeout | undefined; + content?: string; + protected metisService = inject(MetisService); + protected changeDetector = inject(ChangeDetectorRef); + ngOnInit(): void { this.content = this.posting.content; } + + onDeleteEvent(isDelete: boolean) { + this.isDeleted = isDelete; + + if (this.deleteTimer !== undefined) { + clearTimeout(this.deleteTimer); + } + + if (this.deleteInterval !== undefined) { + clearInterval(this.deleteInterval); + } + + if (isDelete) { + this.deleteTimerInSeconds = this.timeToDeleteInSeconds; + + this.deleteTimer = setTimeout( + () => { + if (this.isAnswerPost) { + this.metisService.deleteAnswerPost(this.posting); + } else { + this.metisService.deletePost(this.posting); + } + }, + this.deleteTimerInSeconds * 1000 + 1000, + ); + + this.deleteInterval = setInterval(() => { + this.deleteTimerInSeconds = Math.max(0, this.deleteTimerInSeconds - 1); + this.changeDetector.detectChanges(); + }, 1000); + } + } } diff --git a/src/test/javascript/spec/component/shared/metis/answer-post/answer-post.component.spec.ts b/src/test/javascript/spec/component/shared/metis/answer-post/answer-post.component.spec.ts index a3be0a1de0b2..b4b9eea75fd7 100644 --- a/src/test/javascript/spec/component/shared/metis/answer-post/answer-post.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/answer-post/answer-post.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AnswerPostComponent } from 'app/shared/metis/answer-post/answer-post.component'; -import { MockComponent, MockPipe } from 'ng-mocks'; +import { MockComponent, MockModule, MockPipe } from 'ng-mocks'; import { DebugElement } from '@angular/core'; import { HtmlForMarkdownPipe } from 'app/shared/pipes/html-for-markdown.pipe'; import { getElement } from '../../../../helpers/utils/general.utils'; @@ -9,6 +9,9 @@ import { AnswerPostFooterComponent } from 'app/shared/metis/posting-footer/answe import { PostingContentComponent } from 'app/shared/metis/posting-content/posting-content.components'; import { metisResolvingAnswerPostUser1 } from '../../../../helpers/sample/metis-sample-data'; import { AnswerPostCreateEditModalComponent } from 'app/shared/metis/posting-create-edit-modal/answer-post-create-edit-modal/answer-post-create-edit-modal.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MetisService } from 'app/shared/metis/metis.service'; +import { MockMetisService } from '../../../../helpers/mocks/service/mock-metis-service.service'; describe('AnswerPostComponent', () => { let component: AnswerPostComponent; @@ -17,6 +20,7 @@ describe('AnswerPostComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ + imports: [MockModule(BrowserAnimationsModule)], declarations: [ AnswerPostComponent, MockPipe(HtmlForMarkdownPipe), @@ -25,6 +29,7 @@ describe('AnswerPostComponent', () => { MockComponent(AnswerPostCreateEditModalComponent), MockComponent(AnswerPostFooterComponent), ], + providers: [{ provide: MetisService, useClass: MockMetisService }], }) .compileComponents() .then(() => { @@ -48,11 +53,6 @@ describe('AnswerPostComponent', () => { expect(answerPostCreateEditModal).not.toBeNull(); }); - it('should contain an answer post footer', () => { - const footer = getElement(debugElement, 'jhi-answer-post-footer'); - expect(footer).not.toBeNull(); - }); - it('should have correct content', () => { component.posting = metisResolvingAnswerPostUser1; component.ngOnInit(); diff --git a/src/test/javascript/spec/component/shared/metis/postings-header/answer-post-header/answer-post-header.component.spec.ts b/src/test/javascript/spec/component/shared/metis/postings-header/answer-post-header/answer-post-header.component.spec.ts index 750274c5b685..dc4d6d491b06 100644 --- a/src/test/javascript/spec/component/shared/metis/postings-header/answer-post-header/answer-post-header.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/postings-header/answer-post-header/answer-post-header.component.spec.ts @@ -32,7 +32,6 @@ describe('AnswerPostHeaderComponent', () => { let metisServiceUserIsAtLeastTutorMock: jest.SpyInstance; let metisServiceUserIsAtLeastInstructorMock: jest.SpyInstance; let metisServiceUserPostingAuthorMock: jest.SpyInstance; - let metisServiceDeleteAnswerPostMock: jest.SpyInstance; let metisServiceUpdateAnswerPostMock: jest.SpyInstance; const yesterday: dayjs.Dayjs = dayjs().subtract(1, 'day'); @@ -70,7 +69,6 @@ describe('AnswerPostHeaderComponent', () => { metisServiceUserIsAtLeastTutorMock = jest.spyOn(metisService, 'metisUserIsAtLeastTutorInCourse'); metisServiceUserIsAtLeastInstructorMock = jest.spyOn(metisService, 'metisUserIsAtLeastInstructorInCourse'); metisServiceUserPostingAuthorMock = jest.spyOn(metisService, 'metisUserIsAuthorOfPosting'); - metisServiceDeleteAnswerPostMock = jest.spyOn(metisService, 'deleteAnswerPost'); metisServiceUpdateAnswerPostMock = jest.spyOn(metisService, 'updateAnswerPost'); debugElement = fixture.debugElement; component.posting = metisResolvingAnswerPostUser1; @@ -190,12 +188,13 @@ describe('AnswerPostHeaderComponent', () => { expect(openPostingCreateEditModalEmitSpy).toHaveBeenCalledOnce(); }); - it('should invoke metis service when delete icon is clicked', () => { - metisServiceUserIsAtLeastTutorMock.mockReturnValue(true); + it('should not display edit and delete options when post is deleted', () => { + fixture.componentRef.setInput('isDeleted', true); + component.ngOnInit(); fixture.detectChanges(); - expect(getElement(debugElement, '.deleteIcon')).not.toBeNull(); - component.deletePosting(); - expect(metisServiceDeleteAnswerPostMock).toHaveBeenCalledOnce(); + expect(getElement(debugElement, '.editIcon')).toBeNull(); + expect(getElement(debugElement, '.deleteIcon')).toBeNull(); + fixture.componentRef.setInput('isDeleted', false); }); it('should invoke metis service when toggle resolve is clicked as tutor', () => { @@ -208,12 +207,4 @@ describe('AnswerPostHeaderComponent', () => { expect(component.posting.resolvesPost).toEqual(!previousState); expect(metisServiceUpdateAnswerPostMock).toHaveBeenCalledOnce(); }); - - it('should invoke metis service when toggle resolve is clicked as post author', () => { - metisServiceUserIsAtLeastTutorMock.mockReturnValue(true); - fixture.detectChanges(); - expect(getElement(debugElement, '.deleteIcon')).not.toBeNull(); - component.deletePosting(); - expect(metisServiceDeleteAnswerPostMock).toHaveBeenCalledOnce(); - }); }); From 2edf026814269e26e1ed9dcba28763304daf5f72 Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Tue, 29 Oct 2024 01:43:50 +0100 Subject: [PATCH 3/7] Moved on destroy --- .../webapp/app/shared/metis/post/post.component.ts | 14 ++------------ .../webapp/app/shared/metis/posting.directive.ts | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/webapp/app/shared/metis/post/post.component.ts b/src/main/webapp/app/shared/metis/post/post.component.ts index f49132143432..49b9dc7a8d3e 100644 --- a/src/main/webapp/app/shared/metis/post/post.component.ts +++ b/src/main/webapp/app/shared/metis/post/post.component.ts @@ -1,4 +1,4 @@ -import { AfterContentChecked, ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; +import { AfterContentChecked, ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; import { Post } from 'app/entities/metis/post.model'; import { PostingDirective } from 'app/shared/metis/posting.directive'; import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; @@ -27,7 +27,7 @@ import { animate, style, transition, trigger } from '@angular/animations'; ]), ], }) -export class PostComponent extends PostingDirective implements OnInit, OnChanges, AfterContentChecked, OnDestroy { +export class PostComponent extends PostingDirective implements OnInit, OnChanges, AfterContentChecked { @Input() lastReadDate?: dayjs.Dayjs; @Input() readOnlyMode: boolean; @Input() previewMode: boolean; @@ -87,16 +87,6 @@ export class PostComponent extends PostingDirective implements OnInit, OnC this.sortAnswerPosts(); } - ngOnDestroy(): void { - if (this.deleteTimer !== undefined) { - clearTimeout(this.deleteTimer); - } - - if (this.deleteInterval !== undefined) { - clearInterval(this.deleteInterval); - } - } - /** * this lifecycle hook is required to avoid causing "Expression has changed after it was checked"-error when * dismissing the edit-create-modal -> we do not want to store changes in the create-edit-modal that are not saved diff --git a/src/main/webapp/app/shared/metis/posting.directive.ts b/src/main/webapp/app/shared/metis/posting.directive.ts index 3bee7f149d75..805290303cc3 100644 --- a/src/main/webapp/app/shared/metis/posting.directive.ts +++ b/src/main/webapp/app/shared/metis/posting.directive.ts @@ -1,9 +1,9 @@ import { Posting } from 'app/entities/metis/posting.model'; -import { ChangeDetectorRef, Directive, Input, OnInit, inject } from '@angular/core'; +import { ChangeDetectorRef, Directive, Input, OnDestroy, OnInit, inject } from '@angular/core'; import { MetisService } from 'app/shared/metis/metis.service'; @Directive() -export abstract class PostingDirective implements OnInit { +export abstract class PostingDirective implements OnInit, OnDestroy { @Input() posting: T; @Input() isCommunicationPage: boolean; @Input() showChannelReference?: boolean; @@ -27,6 +27,16 @@ export abstract class PostingDirective implements OnInit { this.content = this.posting.content; } + ngOnDestroy(): void { + if (this.deleteTimer !== undefined) { + clearTimeout(this.deleteTimer); + } + + if (this.deleteInterval !== undefined) { + clearInterval(this.deleteInterval); + } + } + onDeleteEvent(isDelete: boolean) { this.isDeleted = isDelete; From 37f8fcef51b5d73ab471893baed55f98f674b69b Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Tue, 29 Oct 2024 01:59:04 +0100 Subject: [PATCH 4/7] Fix failing test --- .../discussion-section/discussion-section.component.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/javascript/spec/component/overview/discussion-section/discussion-section.component.spec.ts b/src/test/javascript/spec/component/overview/discussion-section/discussion-section.component.spec.ts index 262cae99dadd..3227b7de1259 100644 --- a/src/test/javascript/spec/component/overview/discussion-section/discussion-section.component.spec.ts +++ b/src/test/javascript/spec/component/overview/discussion-section/discussion-section.component.spec.ts @@ -45,6 +45,7 @@ import { NotificationService } from 'app/shared/notification/notification.servic import { MockNotificationService } from '../../../helpers/mocks/service/mock-notification.service'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ProfilePictureComponent } from 'app/shared/profile-picture/profile-picture.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector @@ -66,7 +67,7 @@ describe('DiscussionSectionComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [MockModule(FormsModule), MockModule(ReactiveFormsModule), MockModule(NgbTooltipModule), DiscussionSectionComponent], + imports: [MockModule(FormsModule), MockModule(ReactiveFormsModule), MockModule(NgbTooltipModule), DiscussionSectionComponent, MockModule(BrowserAnimationsModule)], providers: [ provideHttpClient(), provideHttpClientTesting(), From 82a35634c5635a96949b32342c0bee087f8da25a Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Thu, 31 Oct 2024 11:51:02 +0100 Subject: [PATCH 5/7] Feedback answer post indexes and code cleanliness --- .../answer-post/answer-post.component.html | 13 +++----- .../answer-post/answer-post.component.scss | 2 ++ .../app/shared/metis/metis.component.scss | 30 +++++++++++++++++++ .../app/shared/metis/post/post.component.html | 13 +++----- .../app/shared/metis/post/post.component.scss | 28 ----------------- .../posting-content.component.html | 10 ++++++- .../posting-content.components.ts | 5 +++- .../post-footer/post-footer.component.html | 2 +- .../post-footer/post-footer.component.ts | 4 +++ .../answer-post-header.component.html | 10 +++---- .../app/shared/metis/posting.directive.ts | 1 + .../profile-picture.component.html | 8 ++--- .../profile-picture.component.scss | 6 +++- .../posting-content.component.spec.ts | 18 +++++++++-- 14 files changed, 88 insertions(+), 62 deletions(-) diff --git a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html index 2e24426d6f93..33641bc8ad2b 100644 --- a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html +++ b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html @@ -9,7 +9,7 @@ [isDeleted]="isDeleted" (isDeleteEvent)="onDeleteEvent(true)" /> - @if (!isDeleted && !createAnswerPostModal.isInputOpen) { + @if (!createAnswerPostModal.isInputOpen) {
- } @else if (isDeleted) { - - - - }
diff --git a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.scss b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.scss index 4b340b2ecc95..2289b1aa893c 100644 --- a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.scss +++ b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.scss @@ -2,6 +2,8 @@ @import 'bootstrap/scss/variables'; @import 'bootstrap/scss/mixins'; +@import 'src/main/webapp/app/shared/metis/metis.component'; + .answer-post { background-color: var(--metis-answer-post-background-color); border-radius: 7px; diff --git a/src/main/webapp/app/shared/metis/metis.component.scss b/src/main/webapp/app/shared/metis/metis.component.scss index 019ecf67506d..9c00d5dff217 100644 --- a/src/main/webapp/app/shared/metis/metis.component.scss +++ b/src/main/webapp/app/shared/metis/metis.component.scss @@ -2,6 +2,8 @@ @import 'bootstrap/scss/variables'; @import 'bootstrap/scss/mixins'; +$delete-delay-duration: 6s; + .post-result-information { font-size: small; font-style: italic; @@ -98,3 +100,31 @@ font-size: 0.75rem !important; } } + +.post-delete-button-background { + position: absolute; + top: 0; + left: 0; + bottom: 0; + z-index: 0; + background-color: rgba($primary, 0.3); + animation: increaseWidth $delete-delay-duration forwards linear; + + @media (prefers-reduced-motion) { + animation: none; + } +} + +.post-delete-button-label { + position: relative; + z-index: 1; +} + +@keyframes increaseWidth { + from { + width: 0; + } + to { + width: 100%; + } +} diff --git a/src/main/webapp/app/shared/metis/post/post.component.html b/src/main/webapp/app/shared/metis/post/post.component.html index 54062aa64c4f..ca84c6b6580d 100644 --- a/src/main/webapp/app/shared/metis/post/post.component.html +++ b/src/main/webapp/app/shared/metis/post/post.component.html @@ -51,7 +51,7 @@ }
- @if (!isDeleted && !displayInlineInput) { + @if (!displayInlineInput) { - } @else if (isDeleted) { - - - - } diff --git a/src/main/webapp/app/shared/metis/post/post.component.scss b/src/main/webapp/app/shared/metis/post/post.component.scss index 8d3d7c629e75..d88762caf808 100644 --- a/src/main/webapp/app/shared/metis/post/post.component.scss +++ b/src/main/webapp/app/shared/metis/post/post.component.scss @@ -1,7 +1,5 @@ @import 'src/main/webapp/app/shared/metis/metis.component'; -$delete-delay-duration: 6s; - .linked-context-information, .context-information, .post-title { @@ -16,29 +14,3 @@ $delete-delay-duration: 6s; .reference-hash { color: inherit; } - -@import 'src/main/webapp/content/scss/artemis-variables'; - -.post-delete-button-background { - position: absolute; - top: 0; - left: 0; - bottom: 0; - z-index: 0; - background-color: rgba($primary, 0.3); - animation: increaseWidth $delete-delay-duration forwards linear; -} - -.post-delete-button-label { - position: relative; - z-index: 1; -} - -@keyframes increaseWidth { - from { - width: 0; - } - to { - width: 100%; - } -} diff --git a/src/main/webapp/app/shared/metis/posting-content/posting-content.component.html b/src/main/webapp/app/shared/metis/posting-content/posting-content.component.html index 6500e1b28bbf..86d8860b100d 100644 --- a/src/main/webapp/app/shared/metis/posting-content/posting-content.component.html +++ b/src/main/webapp/app/shared/metis/posting-content/posting-content.component.html @@ -1,4 +1,12 @@ -@if (currentlyLoadedPosts) { +@if (isDeleted()) { + + + + +} @else if (currentlyLoadedPosts) { @if (previewMode) {
diff --git a/src/main/webapp/app/shared/metis/posting-content/posting-content.components.ts b/src/main/webapp/app/shared/metis/posting-content/posting-content.components.ts index d7f4b4a42f19..45b92cb0bdde 100644 --- a/src/main/webapp/app/shared/metis/posting-content/posting-content.components.ts +++ b/src/main/webapp/app/shared/metis/posting-content/posting-content.components.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, input, output, signal } from '@angular/core'; import { Params } from '@angular/router'; import { faAngleDown, faAngleUp } from '@fortawesome/free-solid-svg-icons'; import { Post } from 'app/entities/metis/post.model'; @@ -24,6 +24,9 @@ export class PostingContentComponent implements OnInit, OnChanges, OnDestroy { @Input() isReply?: boolean; @Output() userReferenceClicked = new EventEmitter(); @Output() channelReferenceClicked = new EventEmitter(); + isDeleted = input(false); + deleteTimerInSeconds = input(0); + onUndoDeleteEvent = output(); showContent = false; currentlyLoadedPosts: Post[]; diff --git a/src/main/webapp/app/shared/metis/posting-footer/post-footer/post-footer.component.html b/src/main/webapp/app/shared/metis/posting-footer/post-footer/post-footer.component.html index dcd03b7d6be1..c04bbd8392e7 100644 --- a/src/main/webapp/app/shared/metis/posting-footer/post-footer/post-footer.component.html +++ b/src/main/webapp/app/shared/metis/posting-footer/post-footer/post-footer.component.html @@ -12,7 +12,7 @@
- @for (answerPost of sortedAnswerPosts; track $index; let isLastAnswer = $last) { + @for (answerPost of sortedAnswerPosts; track postsTrackByFn($index, answerPost); let isLastAnswer = $last) { implements openCreateAnswerPostModal() { this.createAnswerPostModalComponent.open(); } + + protected postsTrackByFn(index: number, post: Post): number { + return post.id!; + } } diff --git a/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html b/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html index cbe2ad73de8d..7ff859bcdbd7 100644 --- a/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html +++ b/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html @@ -72,12 +72,10 @@ " /> - } @else { - @if (isAtLeastTutorInCourse || isAuthorOfOriginalPost) { -
- -
- } + } @else if (isAtLeastTutorInCourse || isAuthorOfOriginalPost) { +
+ +
} } diff --git a/src/main/webapp/app/shared/metis/posting.directive.ts b/src/main/webapp/app/shared/metis/posting.directive.ts index 805290303cc3..a6bfc936e855 100644 --- a/src/main/webapp/app/shared/metis/posting.directive.ts +++ b/src/main/webapp/app/shared/metis/posting.directive.ts @@ -59,6 +59,7 @@ export abstract class PostingDirective implements OnInit, OnD this.metisService.deletePost(this.posting); } }, + // We add a tiny buffer to make it possible for the user to react a bit longer than the ui displays (+1000) this.deleteTimerInSeconds * 1000 + 1000, ); diff --git a/src/main/webapp/app/shared/profile-picture/profile-picture.component.html b/src/main/webapp/app/shared/profile-picture/profile-picture.component.html index 64acbdb0ce98..ceebd3f4adfb 100644 --- a/src/main/webapp/app/shared/profile-picture/profile-picture.component.html +++ b/src/main/webapp/app/shared/profile-picture/profile-picture.component.html @@ -3,8 +3,8 @@ {{ userProfilePictureInitials }} } @else { @@ -13,8 +13,8 @@ [alt]="authorName()" class="profile-picture rounded-3" [src]="imageUrl()" - [ngStyle]="{ 'background-color': isGray() ? '#888' : profilePictureBackgroundColor }" - [ngClass]="imageClass()" + [ngStyle]="{ 'background-color': profilePictureBackgroundColor }" + [ngClass]="imageClass() + (isGray() ? ' is-grayscale' : '')" /> } @if (isEditable()) { diff --git a/src/main/webapp/app/shared/profile-picture/profile-picture.component.scss b/src/main/webapp/app/shared/profile-picture/profile-picture.component.scss index 2cb3f625f5aa..7082b154b5fc 100644 --- a/src/main/webapp/app/shared/profile-picture/profile-picture.component.scss +++ b/src/main/webapp/app/shared/profile-picture/profile-picture.component.scss @@ -4,8 +4,12 @@ display: inline-flex; align-items: center; justify-content: center; - background-color: var(--gray-400); + background-color: var(--gray-600); color: var(--white); + + &.is-grayscale { + filter: grayscale(100%); + } } .profile-picture-wrap { diff --git a/src/test/javascript/spec/component/shared/metis/posting-content/posting-content.component.spec.ts b/src/test/javascript/spec/component/shared/metis/posting-content/posting-content.component.spec.ts index c54fa917d128..00be6399589b 100644 --- a/src/test/javascript/spec/component/shared/metis/posting-content/posting-content.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/posting-content/posting-content.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { PostingContentPartComponent } from 'app/shared/metis/posting-content/posting-content-part/posting-content-part.components'; -import { MockComponent, MockPipe } from 'ng-mocks'; +import { MockComponent, MockDirective, MockPipe } from 'ng-mocks'; import { PostingContentComponent } from 'app/shared/metis/posting-content/posting-content.components'; import { MetisService } from 'app/shared/metis/metis.service'; import { provideHttpClientTesting } from '@angular/common/http/testing'; @@ -13,6 +13,7 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { metisCourse, metisCoursePosts, metisExercisePosts, metisGeneralCourseWidePosts, metisLecturePosts } from '../../../../helpers/sample/metis-sample-data'; import { Params } from '@angular/router'; import { provideHttpClient } from '@angular/common/http'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; describe('PostingContentComponent', () => { let component: PostingContentComponent; @@ -23,7 +24,13 @@ describe('PostingContentComponent', () => { return TestBed.configureTestingModule({ imports: [], providers: [provideHttpClient(), provideHttpClientTesting(), { provide: MetisService, useClass: MockMetisService }], - declarations: [PostingContentComponent, MockComponent(PostingContentPartComponent), MockComponent(FaIconComponent), MockPipe(ArtemisTranslatePipe)], + declarations: [ + PostingContentComponent, + MockComponent(PostingContentPartComponent), + MockComponent(FaIconComponent), + MockPipe(ArtemisTranslatePipe), + MockDirective(TranslateDirective), + ], }) .compileComponents() .then(() => { @@ -117,6 +124,13 @@ describe('PostingContentComponent', () => { expect(component.getPatternMatches()).toEqual([firstMatch]); }); + it('should display undo delete prompt when isDeleted is set to true', () => { + fixture.componentRef.setInput('isDeleted', true); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('.posting-content-undo-delete')).not.toBeNull(); + fixture.componentRef.setInput('isDeleted', false); + }); + it('should calculate correct pattern matches for content with post, multiple exercise, lecture and attachment references', () => { component.content = 'I do want to reference #4, #10, ' + From 2e02f0fd5e737d86500bbf1d88a34899df008c53 Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Thu, 31 Oct 2024 12:14:17 +0100 Subject: [PATCH 6/7] Wording for more condensed button --- src/main/webapp/i18n/de/metis.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/i18n/de/metis.json b/src/main/webapp/i18n/de/metis.json index 8578c5f050e2..7b278eeee22e 100644 --- a/src/main/webapp/i18n/de/metis.json +++ b/src/main/webapp/i18n/de/metis.json @@ -127,7 +127,7 @@ "showContent": "Inhalt ausklappen", "collapseContent": "Inhalt einklappen", "deletedContent": "Beitrag wird in {{ progress }} Sekunde(n) gelöscht.", - "undoDelete": "Löschen rückgängig machen" + "undoDelete": "Löschen umkehren" }, "answerPost": { "created": "Antwort erfolgreich erstellt", From c97e80f9f3bf1b4c79bd3578affbda48c21b5d92 Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Thu, 31 Oct 2024 12:19:57 +0100 Subject: [PATCH 7/7] Added Empty line --- src/main/webapp/app/shared/metis/metis.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/webapp/app/shared/metis/metis.component.scss b/src/main/webapp/app/shared/metis/metis.component.scss index 9c00d5dff217..f17e5cb1389c 100644 --- a/src/main/webapp/app/shared/metis/metis.component.scss +++ b/src/main/webapp/app/shared/metis/metis.component.scss @@ -124,6 +124,7 @@ $delete-delay-duration: 6s; from { width: 0; } + to { width: 100%; }