diff --git a/angular.json b/angular.json index d9535f656cd2..815c38a3f705 100644 --- a/angular.json +++ b/angular.json @@ -20,37 +20,40 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "allowedCommonJsDependencies": [ - "clone-deep", - "crypto-js", - "crypto", - "dagre", - "dayjs/locale/de", - "dompurify", - "export-to-csv", - "hoist-non-react-statics", - "interactjs", - "is-mobile", - "js-video-url-parser", - "jszip", - "localforage", - "mobile-drag-drop", - "papaparse", - "pepjs", - "prop-types", - "react", - "react-dom", - "react-dom/client", - "react-is", - "rfdc", - "shallowequal", - "markdown-it-class", - "smoothscroll-polyfill", - "sockjs-client", - "use-sync-external-store/shim", - "use-sync-external-store/shim/with-selector", - "webcola", - "webstomp-client" + "allowedCommonJsDependencies": [ + "@vscode/markdown-it-katex", + "clone-deep", + "crypto-js", + "crypto", + "dagre", + "dayjs/locale/de", + "dompurify", + "emoji-js", + "export-to-csv", + "hoist-non-react-statics", + "interactjs", + "is-mobile", + "js-video-url-parser", + "jszip", + "localforage", + "markdown-it-highlightjs", + "mobile-drag-drop", + "papaparse", + "pepjs", + "prop-types", + "react", + "react-dom", + "react-dom/client", + "react-is", + "rfdc", + "shallowequal", + "markdown-it-class", + "smoothscroll-polyfill", + "sockjs-client", + "use-sync-external-store/shim", + "use-sync-external-store/shim/with-selector", + "webcola", + "webstomp-client" ], "outputPath": { "base": "build/resources/main/static/", diff --git a/jest.config.js b/jest.config.js index f19a6c392563..79e40bdb3162 100644 --- a/jest.config.js +++ b/jest.config.js @@ -102,10 +102,10 @@ module.exports = { coverageThreshold: { global: { // TODO: in the future, the following values should increase to at least 90% - statements: 87.53, + statements: 87.52, branches: 73.62, - functions: 82.13, - lines: 87.58, + functions: 82.12, + lines: 87.57, }, }, coverageReporters: ['clover', 'json', 'lcov', 'text-summary'], diff --git a/package-lock.json b/package-lock.json index 74e3625253d0..5f2c0c279049 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "dayjs": "1.11.13", "diff-match-patch-typescript": "1.1.0", "dompurify": "3.1.7", + "emoji-js": "3.8.0", "export-to-csv": "1.4.0", "fast-json-patch": "3.1.1", "franc-min": "6.2.0", @@ -93,6 +94,7 @@ "@types/crypto-js": "4.2.2", "@types/d3-shape": "3.1.6", "@types/dompurify": "3.0.5", + "@types/emoji-js": "3.5.2", "@types/jest": "29.5.14", "@types/lodash-es": "4.17.12", "@types/markdown-it": "14.1.2", @@ -7194,6 +7196,12 @@ "@types/trusted-types": "*" } }, + "node_modules/@types/emoji-js": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@types/emoji-js/-/emoji-js-3.5.2.tgz", + "integrity": "sha512-qPR85yjSPk2UEbdjYYNHfcOjVod7DCARSrJlPcL+cwaDFwdnmOFhPyYUvP5GaW0YZEy8mU93ZjTNgsVWz1zzlg==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -10935,6 +10943,19 @@ "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, + "node_modules/emoji-datasource": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/emoji-datasource/-/emoji-datasource-15.0.1.tgz", + "integrity": "sha512-aF5Q6LCKXzJzpG4K0ETiItuzz0xLYxNexR9qWw45/shuuEDWZkOIbeGHA23uopOSYA/LmeZIXIFsySCx+YKg2g==" + }, + "node_modules/emoji-js": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/emoji-js/-/emoji-js-3.8.0.tgz", + "integrity": "sha512-A5FNHKlRPRo6RJWrrdGWnoolIBMkVXHy4qkO0V5ahekQPjfVECxvOOWADeAF/SbzRVA9Sxdj24FCoRYGt06skA==", + "dependencies": { + "emoji-datasource": "15.0.1" + } + }, "node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", diff --git a/package.json b/package.json index 04ad311c7f9a..ffeb9a9418be 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "dayjs": "1.11.13", "diff-match-patch-typescript": "1.1.0", "dompurify": "3.1.7", + "emoji-js": "3.8.0", "export-to-csv": "1.4.0", "fast-json-patch": "3.1.1", "franc-min": "6.2.0", @@ -130,6 +131,7 @@ "@types/crypto-js": "4.2.2", "@types/d3-shape": "3.1.6", "@types/dompurify": "3.0.5", + "@types/emoji-js": "3.5.2", "@types/jest": "29.5.14", "@types/lodash-es": "4.17.12", "@types/markdown-it": "14.1.2", diff --git a/src/main/webapp/app/admin/admin.route.ts b/src/main/webapp/app/admin/admin.route.ts index 0c2099494d4a..81c3a096f66f 100644 --- a/src/main/webapp/app/admin/admin.route.ts +++ b/src/main/webapp/app/admin/admin.route.ts @@ -20,6 +20,7 @@ import { BuildAgentSummaryComponent } from 'app/localci/build-agents/build-agent import { StandardizedCompetencyManagementComponent } from 'app/admin/standardized-competencies/standardized-competency-management.component'; import { BuildAgentDetailsComponent } from 'app/localci/build-agents/build-agent-details/build-agent-details/build-agent-details.component'; import { AdminImportStandardizedCompetenciesComponent } from 'app/admin/standardized-competencies/import/admin-import-standardized-competencies.component'; +import { PendingChangesGuard } from 'app/shared/guard/pending-changes.guard'; export const adminState: Routes = [ { @@ -116,6 +117,7 @@ export const adminState: Routes = [ data: { pageTitle: 'artemisApp.standardizedCompetency.title', }, + canDeactivate: [PendingChangesGuard], }, { // Create a new path without a component defined to prevent the StandardizedCompetencyManagementComponent from being always rendered diff --git a/src/main/webapp/app/admin/standardized-competencies/standardized-competency-management.component.ts b/src/main/webapp/app/admin/standardized-competencies/standardized-competency-management.component.ts index fb73da984d1b..d23fe7213f6d 100644 --- a/src/main/webapp/app/admin/standardized-competencies/standardized-competency-management.component.ts +++ b/src/main/webapp/app/admin/standardized-competencies/standardized-competency-management.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { faChevronRight, faDownLeftAndUpRightToCenter, faEye, faFileExport, faFileImport, faPlus, faUpRightAndDownLeftFromCenter } from '@fortawesome/free-solid-svg-icons'; import { KnowledgeAreaDTO, @@ -576,14 +576,4 @@ export class StandardizedCompetencyManagementComponent extends StandardizedCompe get canDeactivateWarning(): string { return this.translateService.instant('pendingChanges'); } - - /** - * Displays the alert for confirming refreshing or closing the page if there are unsaved changes - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } } diff --git a/src/main/webapp/app/course/competencies/generate-competencies/generate-competencies.component.ts b/src/main/webapp/app/course/competencies/generate-competencies/generate-competencies.component.ts index 9501484fe9b5..19d3d33b6124 100644 --- a/src/main/webapp/app/course/competencies/generate-competencies/generate-competencies.component.ts +++ b/src/main/webapp/app/course/competencies/generate-competencies/generate-competencies.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { CompetencyService } from 'app/course/competencies/competency.service'; import { AlertService } from 'app/core/util/alert.service'; import { onError } from 'app/shared/util/global.utils'; @@ -249,14 +249,4 @@ export class GenerateCompetenciesComponent implements OnInit, ComponentCanDeacti get canDeactivateWarning(): string { return this.translateService.instant('pendingChanges'); } - - /** - * Only allow to refresh the page if no pending changes exist - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } } diff --git a/src/main/webapp/app/course/competencies/import-standardized-competencies/course-import-standardized-course-competencies.component.ts b/src/main/webapp/app/course/competencies/import-standardized-competencies/course-import-standardized-course-competencies.component.ts index 1c369bea4657..63b74465efe7 100644 --- a/src/main/webapp/app/course/competencies/import-standardized-competencies/course-import-standardized-course-competencies.component.ts +++ b/src/main/webapp/app/course/competencies/import-standardized-competencies/course-import-standardized-course-competencies.component.ts @@ -10,7 +10,7 @@ import { } from 'app/entities/competency/standardized-competency.model'; import { faBan, faDownLeftAndUpRightToCenter, faFileImport, faSort, faTrash, faUpRightAndDownLeftFromCenter } from '@fortawesome/free-solid-svg-icons'; import { ActivatedRoute, Router } from '@angular/router'; -import { Component, HostListener, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { onError } from 'app/shared/util/global.utils'; import { forkJoin, map } from 'rxjs'; import { HttpErrorResponse } from '@angular/common/http'; @@ -168,16 +168,6 @@ export abstract class CourseImportStandardizedCourseCompetenciesComponent extend return this.translateService.instant('pendingChanges'); } - /** - * Displays the alert for confirming refreshing or closing the page if there are unsaved changes - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } - private convertToKnowledgeAreaForImport(knowledgeAreaDTO: KnowledgeAreaDTO, isVisible = true, level = 0, selected = false): KnowledgeAreaForImport { const children = knowledgeAreaDTO.children?.map((child) => this.convertToKnowledgeAreaForImport(child, isVisible, level + 1)); const competencies = knowledgeAreaDTO.competencies?.map((competency) => diff --git a/src/main/webapp/app/course/competencies/import/import-course-competencies.component.ts b/src/main/webapp/app/course/competencies/import/import-course-competencies.component.ts index 7cb8bbb30cf1..0190d71b8b5e 100644 --- a/src/main/webapp/app/course/competencies/import/import-course-competencies.component.ts +++ b/src/main/webapp/app/course/competencies/import/import-course-competencies.component.ts @@ -4,7 +4,7 @@ import { CourseCompetency, CourseCompetencyType } from 'app/entities/competency. import { AlertService } from 'app/core/util/alert.service'; import { SortService } from 'app/shared/service/sort.service'; import { onError } from 'app/shared/util/global.utils'; -import { Component, HostListener, OnInit, inject } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { faBan, faFileImport, faSave, faTrash } from '@fortawesome/free-solid-svg-icons'; import { ButtonType } from 'app/shared/components/button.component'; import { ActivatedRoute, Router } from '@angular/router'; @@ -204,14 +204,4 @@ export abstract class ImportCourseCompetenciesComponent implements OnInit, Compo get canDeactivateWarning(): string { return this.translateService.instant('pendingChanges'); } - - /** - * Displays the alert for confirming refreshing or closing the page if there are unsaved changes - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } } diff --git a/src/main/webapp/app/exam/participate/exam-participation.component.ts b/src/main/webapp/app/exam/participate/exam-participation.component.ts index a26170db25ca..7871ce88d681 100644 --- a/src/main/webapp/app/exam/participate/exam-participation.component.ts +++ b/src/main/webapp/app/exam/participate/exam-participation.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { JhiWebsocketService } from 'app/core/websocket/websocket.service'; import { ExamParticipationService } from 'app/exam/participate/exam-participation.service'; @@ -246,14 +246,6 @@ export class ExamParticipationComponent implements OnInit, OnDestroy, ComponentC return this.translateService.instant('artemisApp.examParticipation.pendingChanges'); } - // displays the alert for confirming leaving the page if there are unsaved changes - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any): void { - if (!this.canDeactivate()) { - event.returnValue = this.canDeactivateWarning; - } - } - get activePageIndex(): number { if (!this.activeExamPage || this.activeExamPage.isOverviewPage) { return -1; diff --git a/src/main/webapp/app/exercises/modeling/participate/modeling-submission.component.ts b/src/main/webapp/app/exercises/modeling/participate/modeling-submission.component.ts index e4684d85321b..e9c54a294c31 100644 --- a/src/main/webapp/app/exercises/modeling/participate/modeling-submission.component.ts +++ b/src/main/webapp/app/exercises/modeling/participate/modeling-submission.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, HostListener, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Patch, Selection, UMLDiagramType, UMLElementType, UMLModel, UMLRelationshipType } from '@ls1intum/apollon'; import { TranslateService } from '@ngx-translate/core'; @@ -642,14 +642,6 @@ export class ModelingSubmissionComponent implements OnInit, OnDestroy, Component return false; } - // displays the alert for confirming leaving the page if there are unsaved changes - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any): void { - if (!this.canDeactivate()) { - event.returnValue = this.translateService.instant('pendingChanges'); - } - } - /** * counts the number of model elements * is used in the submit() function diff --git a/src/main/webapp/app/exercises/programming/shared/code-editor/container/code-editor-container.component.ts b/src/main/webapp/app/exercises/programming/shared/code-editor/container/code-editor-container.component.ts index f8e9607545db..b2b81406b23a 100644 --- a/src/main/webapp/app/exercises/programming/shared/code-editor/container/code-editor-container.component.ts +++ b/src/main/webapp/app/exercises/programming/shared/code-editor/container/code-editor-container.component.ts @@ -1,8 +1,7 @@ -import { Component, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { isEmpty as _isEmpty, fromPairs, toPairs, uniq } from 'lodash-es'; import { CodeEditorFileService } from 'app/exercises/programming/shared/code-editor/service/code-editor-file.service'; -import { ComponentCanDeactivate } from 'app/shared/guard/can-deactivate.model'; import { CodeEditorGridComponent } from 'app/exercises/programming/shared/code-editor/layout/code-editor-grid.component'; import { CommitState, @@ -37,7 +36,7 @@ export enum CollapsableCodeEditorElement { templateUrl: './code-editor-container.component.html', styleUrls: ['./code-editor-container.component.scss'], }) -export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeactivate { +export class CodeEditorContainerComponent implements OnChanges { readonly CommitState = CommitState; readonly EditorState = EditorState; readonly CollapsableCodeEditorElement = CollapsableCodeEditorElement; @@ -262,13 +261,6 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac this.alertService.error(`artemisApp.editor.errors.${errorTranslationKey}`, translationParams); } - /** - * The user will be warned if there are unsaved changes when trying to leave the code-editor. - */ - canDeactivate() { - return _isEmpty(this.unsavedFiles); - } - getText(): string { return this.monacoEditor.getText() ?? ''; } @@ -286,14 +278,6 @@ export class CodeEditorContainerComponent implements OnChanges, ComponentCanDeac this.monacoEditor.highlightLines(startLine, endLine); } - // displays the alert for confirming refreshing or closing the page if there are unsaved changes - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.translateService.instant('pendingChanges'); - } - } - onToggleCollapse(event: InteractableEvent, collapsableElement: CollapsableCodeEditorElement) { this.grid.toggleCollapse(event, collapsableElement); } diff --git a/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts b/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts index b6fda2b6e1ee..f212b3469e6c 100644 --- a/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnChanges, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnChanges, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; import { QuizExerciseService } from './quiz-exercise.service'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; @@ -355,16 +355,6 @@ export class QuizExerciseUpdateComponent extends QuizExerciseValidationDirective return !this.pendingChangesCache; } - /** - * Displays the alert for confirming refreshing or closing the page if there are unsaved changes - */ - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.translateService.instant('pendingChanges'); - } - } - /** * @desc Callback for datepicker to decide whether given date should be disabled * All dates which are in the past (< today) are disabled diff --git a/src/main/webapp/app/exercises/text/participate/text-editor.component.ts b/src/main/webapp/app/exercises/text/participate/text-editor.component.ts index ca6401b6f59a..e270ad1d191a 100644 --- a/src/main/webapp/app/exercises/text/participate/text-editor.component.ts +++ b/src/main/webapp/app/exercises/text/participate/text-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { ActivatedRoute } from '@angular/router'; import { HttpErrorResponse } from '@angular/common/http'; @@ -248,14 +248,6 @@ export class TextEditorComponent implements OnInit, OnDestroy, ComponentCanDeact return this.stringCountService.countCharacters(this.answer); } - // Displays the alert for confirming refreshing or closing the page if there are unsaved changes - @HostListener('window:beforeunload', ['$event']) - unloadNotification(event: any) { - if (!this.canDeactivate()) { - event.returnValue = this.translateService.instant('pendingChanges'); - } - } - canDeactivate(): boolean { if (!this.submission) { return true; diff --git a/src/main/webapp/app/exercises/text/participate/text-editor.route.ts b/src/main/webapp/app/exercises/text/participate/text-editor.route.ts index 1fa9cff2a527..8ed5f35e65d6 100644 --- a/src/main/webapp/app/exercises/text/participate/text-editor.route.ts +++ b/src/main/webapp/app/exercises/text/participate/text-editor.route.ts @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; import { TextEditorComponent } from './text-editor.component'; import { Authority } from 'app/shared/constants/authority.constants'; +import { PendingChangesGuard } from 'app/shared/guard/pending-changes.guard'; export const textEditorRoute: Routes = [ { @@ -12,5 +13,6 @@ export const textEditorRoute: Routes = [ pageTitle: 'artemisApp.textExercise.home.title', }, canActivate: [UserRouteAccessService], + canDeactivate: [PendingChangesGuard], }, ]; diff --git a/src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.html b/src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.html index 9b9c54d262fb..d1d011e3e1f8 100644 --- a/src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.html +++ b/src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.html @@ -229,7 +229,7 @@ -