diff --git a/angular.json b/angular.json index 008ac75d13bf..815c38a3f705 100644 --- a/angular.json +++ b/angular.json @@ -20,39 +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", - "showdown-highlight", - "showdown-katex", - "showdown", - "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/docs/user/markdown-support.rst b/docs/user/markdown-support.rst index c28d50fa2786..9ac5b6f5a56f 100644 --- a/docs/user/markdown-support.rst +++ b/docs/user/markdown-support.rst @@ -9,7 +9,7 @@ Markdown Support `Markdown `__ is an easy-to-read, easy-to-write syntax for formatting plain text. -A markdown playground can be found `here `__. +A markdown playground can be found `here `__. Artemis extends the basic `Markdown `__ syntax to support Artemis-specific features. This Artemis flavored Markdown is used to format text content across the platform using an integrated markdown editor. @@ -52,9 +52,9 @@ Markdown is also supported in the context of :ref:`communicating` Supported Syntax ^^^^^^^^^^^^^^^^ -The integrated markdown editor uses `Showdown `__. A quick description of the supported syntax can be found `here `__. +The integrated markdown editor uses `MarkdownIt `__. A quick description of the supported syntax can be found `here `__. -The following Showdown extensions are activated: +The following Plugins are activated: -- `Showdown Katex `__ to render LaTeX math and AsciiMath using KaTeX. -- `Showdown Highlight `__ for syntax highlighting in code blocks. +- `MarkdownIt Katex `__ to render LaTeX math and AsciiMath using KaTeX. +- `MarkdownIt HighlightJS `__ for syntax highlighting in code blocks. 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 9ab71995dbff..5f2c0c279049 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "@swimlane/ngx-charts": "20.5.0", "@swimlane/ngx-graph": "8.4.0", "@vscode/codicons": "0.0.36", + "@vscode/markdown-it-katex": "1.1.0", "bootstrap": "5.3.3", "compare-versions": "6.1.1", "core-js": "3.38.1", @@ -45,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", @@ -54,6 +56,9 @@ "js-video-url-parser": "0.5.1", "jszip": "3.10.1", "lodash-es": "4.17.21", + "markdown-it": "14.1.0", + "markdown-it-class": "1.0.0", + "markdown-it-highlightjs": "4.2.0", "mobile-drag-drop": "3.0.0-rc.0", "monaco-editor": "0.52.0", "ngx-infinite-scroll": "18.0.0", @@ -62,15 +67,13 @@ "pdfjs-dist": "4.7.76", "posthog-js": "1.176.0", "rxjs": "7.8.1", - "showdown": "2.1.0", - "showdown-highlight": "3.1.0", - "showdown-katex": "0.6.0", "simple-statistics": "7.8.7", "smoothscroll-polyfill": "0.4.4", "sockjs-client": "1.6.1", "split.js": "1.6.5", "ts-cacheable": "1.0.10", "tslib": "2.8.0", + "turndown": "7.2.0", "uuid": "10.0.0", "webstomp-client": "1.2.6", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", @@ -91,13 +94,15 @@ "@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", "@types/node": "22.7.9", "@types/papaparse": "5.3.15", - "@types/showdown": "2.0.6", "@types/smoothscroll-polyfill": "0.3.4", "@types/sockjs-client": "1.5.4", + "@types/turndown": "5.0.5", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "8.11.0", "@typescript-eslint/parser": "8.11.0", @@ -5262,6 +5267,12 @@ "node": ">=6" } }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", + "license": "BSD-2-Clause" + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -7185,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", @@ -7325,6 +7342,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/lodash": { "version": "4.17.12", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.12.tgz", @@ -7342,6 +7366,24 @@ "@types/lodash": "*" } }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -7466,13 +7508,6 @@ "@types/send": "*" } }, - "node_modules/@types/showdown": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz", - "integrity": "sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/smoothscroll-polyfill": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@types/smoothscroll-polyfill/-/smoothscroll-polyfill-0.3.4.tgz", @@ -7518,6 +7553,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/turndown": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", + "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -7774,6 +7816,15 @@ "integrity": "sha512-wsNOvNMMJ2BY8rC2N2MNBG7yOowV3ov8KlvUE/AiVUlHKTfWsw3OgAOQduX7h0Un6GssKD3aoTVH+TF3DSQwKQ==", "license": "CC-BY-4.0" }, + "node_modules/@vscode/markdown-it-katex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vscode/markdown-it-katex/-/markdown-it-katex-1.1.0.tgz", + "integrity": "sha512-9cF2eJpsJOEs2V1cCAoJW/boKz9GQQLvZhNvI030K90z6ZE9lRGc9hDVvKut8zdFO2ObjwylPXXXVYvTdP2O2Q==", + "license": "MIT", + "dependencies": { + "katex": "^0.16.4" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -8333,7 +8384,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -10893,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", @@ -10986,7 +11049,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -12966,15 +13028,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/highlight.js": { "version": "11.10.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz", @@ -13038,17 +13091,6 @@ "integrity": "sha512-9SQg9oLQSAOZb8rO17mRNPkVB95QRh6iLY5J0Dbc/cgeoBT+XJBK/6XrQqfd+vxUVRjdctW+sfgYqgYzi0vg9g==", "license": "ISC" }, - "node_modules/html-encoder-decoder": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/html-encoder-decoder/-/html-encoder-decoder-1.3.10.tgz", - "integrity": "sha512-18SjgzQZ9U1mxb96rjcWgWMnTlEzNj2lU2wAU7OeUobdIWXTS6lOGc6419eLhMlX24sNQYDyQfgkSXWjyq/Ilg==", - "license": "MIT", - "dependencies": { - "he": "^1.1.0", - "iterate-object": "^1.3.2", - "regex-escape": "^3.4.2" - } - }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -13912,12 +13954,6 @@ "node": ">=8" } }, - "node_modules/iterate-object": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.4.tgz", - "integrity": "sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==", - "license": "MIT" - }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -16321,6 +16357,15 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/lint-staged": { "version": "15.2.10", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz", @@ -16983,12 +17028,50 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-class": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-class/-/markdown-it-class-1.0.0.tgz", + "integrity": "sha512-CVDYqSgmErLAqInwWu8WmAR2nX6MMIBIt8LB6qg8DNldca9+aoC6ZyuY0lvBMsaTSHNFJRkcHVR1XjLw9nr9qQ==", + "license": "MIT" + }, + "node_modules/markdown-it-highlightjs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/markdown-it-highlightjs/-/markdown-it-highlightjs-4.2.0.tgz", + "integrity": "sha512-NC7pXE8KkOl6xWJVRNt8p6wgJVznXKsE0HgYGdk6DD2tn1l4L9f0ALf3VIoGVkotNU1uGQatSxfBF1zZPUMmuQ==", + "license": "Unlicense", + "dependencies": { + "highlight.js": "^11.9.0" + } + }, "node_modules/material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", "license": "ISC" }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -19547,6 +19630,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -19898,12 +19990,6 @@ "@babel/runtime": "^7.8.4" } }, - "node_modules/regex-escape": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/regex-escape/-/regex-escape-3.4.10.tgz", - "integrity": "sha512-qEqf7uzW+iYcKNLMDFnMkghhQBnGdivT6KqVQyKsyjSWnoFyooXVnxrw9dtv3AFLnD6VBGXxtZGAQNFGFTnCqA==", - "license": "MIT" - }, "node_modules/regex-parser": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", @@ -20800,57 +20886,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/showdown": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", - "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", - "license": "MIT", - "dependencies": { - "commander": "^9.0.0" - }, - "bin": { - "showdown": "bin/showdown.js" - }, - "funding": { - "type": "individual", - "url": "https://www.paypal.me/tiviesantos" - } - }, - "node_modules/showdown-highlight": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/showdown-highlight/-/showdown-highlight-3.1.0.tgz", - "integrity": "sha512-wrTxtE63L/bpW5A2Uy/AO1gblXnNHK/cDL6LszECOoCdMJKWTj0/4n4I/pmqub+3H3KCPVDDvtXpCArnT/heFA==", - "license": "MIT", - "dependencies": { - "highlight.js": "^11.5.0", - "html-encoder-decoder": "^1.3.9", - "showdown": "^2.0.3" - } - }, - "node_modules/showdown-katex": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/showdown-katex/-/showdown-katex-0.6.0.tgz", - "integrity": "sha512-eEOipJjqMxRJ+e69WlA7XENhFZzKhNl12csey0iLd4QbLzGF61+FBxNPhEZFz9wICYTJNfyqNgLSqmm8Uj0fGA==", - "license": "MIT", - "dependencies": { - "katex": "^0.10.0" - }, - "engines": { - "node": "*" - }, - "peerDependencies": { - "showdown": "^1.4.3" - } - }, - "node_modules/showdown/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -22244,6 +22279,15 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/turndown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", + "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", + "license": "MIT", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -22362,6 +22406,12 @@ "typescript-compare": "^0.0.2" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/package.json b/package.json index c195b3503caa..ffeb9a9418be 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@swimlane/ngx-charts": "20.5.0", "@swimlane/ngx-graph": "8.4.0", "@vscode/codicons": "0.0.36", + "@vscode/markdown-it-katex": "1.1.0", "bootstrap": "5.3.3", "compare-versions": "6.1.1", "core-js": "3.38.1", @@ -48,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", @@ -57,6 +59,9 @@ "js-video-url-parser": "0.5.1", "jszip": "3.10.1", "lodash-es": "4.17.21", + "markdown-it": "14.1.0", + "markdown-it-class": "1.0.0", + "markdown-it-highlightjs": "4.2.0", "mobile-drag-drop": "3.0.0-rc.0", "monaco-editor": "0.52.0", "ngx-infinite-scroll": "18.0.0", @@ -65,15 +70,13 @@ "pdfjs-dist": "4.7.76", "posthog-js": "1.176.0", "rxjs": "7.8.1", - "showdown": "2.1.0", - "showdown-highlight": "3.1.0", - "showdown-katex": "0.6.0", "simple-statistics": "7.8.7", "smoothscroll-polyfill": "0.4.4", "sockjs-client": "1.6.1", "split.js": "1.6.5", "ts-cacheable": "1.0.10", "tslib": "2.8.0", + "turndown": "7.2.0", "uuid": "10.0.0", "webstomp-client": "1.2.6", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", @@ -102,13 +105,9 @@ }, "express": "5.0.1", "jsdom": "25.0.1", - "katex": "0.16.11", "postcss": "8.4.47", "rimraf": "6.0.1", "semver": "7.6.3", - "showdown-katex": { - "showdown": "2.1.0" - }, "tough-cookie": "5.0.0", "vite": "5.4.10", "webpack-dev-middleware": "7.4.2", @@ -132,13 +131,15 @@ "@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", "@types/node": "22.7.9", "@types/papaparse": "5.3.15", - "@types/showdown": "2.0.6", "@types/smoothscroll-polyfill": "0.3.4", "@types/sockjs-client": "1.5.4", + "@types/turndown": "5.0.5", "@types/uuid": "10.0.0", "@typescript-eslint/eslint-plugin": "8.11.0", "@typescript-eslint/parser": "8.11.0", diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/LongFeedbackTextRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/LongFeedbackTextRepository.java index 6ad61c4ef7ff..87df115afc0d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/LongFeedbackTextRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/LongFeedbackTextRepository.java @@ -1,14 +1,20 @@ package de.tum.cit.aet.artemis.assessment.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + import java.util.List; import java.util.Optional; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.assessment.domain.LongFeedbackText; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; +@Profile(PROFILE_CORE) +@Repository public interface LongFeedbackTextRepository extends ArtemisJpaRepository { @Query(""" diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseCompetencyRepository.java b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseCompetencyRepository.java index f3f393a41657..dc999216bb88 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseCompetencyRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/CourseCompetencyRepository.java @@ -1,14 +1,18 @@ package de.tum.cit.aet.artemis.atlas.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + import java.util.List; import java.util.Optional; import java.util.Set; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Profile; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.atlas.domain.LearningObject; import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency; @@ -21,6 +25,8 @@ /** * Spring Data JPA repository for the {@link CourseCompetency} entity. */ +@Profile(PROFILE_CORE) +@Repository public interface CourseCompetencyRepository extends ArtemisJpaRepository { @Query(""" diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/PrerequisiteRepository.java b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/PrerequisiteRepository.java index a6640f2ca10b..e86b66612a76 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/repository/PrerequisiteRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/repository/PrerequisiteRepository.java @@ -1,11 +1,15 @@ package de.tum.cit.aet.artemis.atlas.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + import java.util.List; import java.util.Optional; import java.util.Set; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.atlas.domain.competency.Prerequisite; import de.tum.cit.aet.artemis.core.domain.Course; @@ -14,10 +18,10 @@ /** * Spring Data JPA repository for the {@link Prerequisite} entity. */ +@Profile(PROFILE_CORE) +@Repository public interface PrerequisiteRepository extends ArtemisJpaRepository { - List findAllByCourseIdOrderById(long courseId); - @Query(""" SELECT p FROM Prerequisite p diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/CustomPostRepository.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/CustomPostRepository.java index d40778fbaae6..db460a6b27c6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/CustomPostRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/CustomPostRepository.java @@ -1,11 +1,17 @@ package de.tum.cit.aet.artemis.communication.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import org.springframework.context.annotation.Profile; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.communication.domain.Post; +@Profile(PROFILE_CORE) +@Repository public interface CustomPostRepository { Page findPostIdsWithSpecification(Specification specification, Pageable pageable); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/AuthorityRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/AuthorityRepository.java index 4e3f3f0466af..70a1078fbf7b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/AuthorityRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/AuthorityRepository.java @@ -1,13 +1,20 @@ package de.tum.cit.aet.artemis.core.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + import java.util.List; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + import de.tum.cit.aet.artemis.core.domain.Authority; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; /** * Spring Data JPA repository for the Authority entity. */ +@Profile(PROFILE_CORE) +@Repository public interface AuthorityRepository extends ArtemisJpaRepository { /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/MigrationChangeRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/MigrationChangeRepository.java index 71b6b9c1a8c4..12ff470bed9f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/MigrationChangeRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/MigrationChangeRepository.java @@ -1,7 +1,14 @@ package de.tum.cit.aet.artemis.core.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + import de.tum.cit.aet.artemis.core.domain.MigrationChangelog; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; +@Profile(PROFILE_CORE) +@Repository public interface MigrationChangeRepository extends ArtemisJpaRepository { } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/PersistenceAuditEventRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/PersistenceAuditEventRepository.java index 9c4c133fe6da..c2afe2117540 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/PersistenceAuditEventRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/PersistenceAuditEventRepository.java @@ -1,5 +1,6 @@ package de.tum.cit.aet.artemis.core.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD; import java.time.Instant; @@ -9,12 +10,14 @@ import jakarta.validation.constraints.NotNull; +import org.springframework.context.annotation.Profile; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.domain.PersistentAuditEvent; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; @@ -22,6 +25,8 @@ /** * Spring Data JPA repository for the PersistentAuditEvent entity. */ +@Profile(PROFILE_CORE) +@Repository public interface PersistenceAuditEventRepository extends ArtemisJpaRepository { @EntityGraph(type = LOAD, attributePaths = { "data" }) diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/feature/FeatureToggleService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/feature/FeatureToggleService.java index 83438c369cfd..3e2a906bee8b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/feature/FeatureToggleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/feature/FeatureToggleService.java @@ -4,15 +4,19 @@ import java.util.List; import java.util.Map; +import java.util.Optional; -import jakarta.annotation.PostConstruct; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Profile; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.HazelcastInstanceNotActiveException; import de.tum.cit.aet.artemis.communication.service.WebsocketMessagingService; @@ -20,6 +24,8 @@ @Service public class FeatureToggleService { + private static final Logger log = LoggerFactory.getLogger(FeatureToggleService.class); + private static final String TOPIC_FEATURE_TOGGLES = "/topic/management/feature-toggles"; @Value("${artemis.science.event-logging.enable:false}") @@ -36,10 +42,22 @@ public FeatureToggleService(WebsocketMessagingService websocketMessagingService, this.hazelcastInstance = hazelcastInstance; } + private Optional> getFeatures() { + try { + if (isHazelcastRunning()) { + return Optional.ofNullable(features); + } + } + catch (HazelcastInstanceNotActiveException e) { + log.error("Failed to get features in {} as Hazelcast instance is not active anymore.", FeatureToggleService.class.getSimpleName()); + } + return Optional.empty(); + } + /** * Initialize relevant data from hazelcast */ - @PostConstruct + @EventListener(ApplicationReadyEvent.class) public void init() { // The map will automatically be distributed between all instances by Hazelcast. features = hazelcastInstance.getMap("features"); @@ -63,8 +81,10 @@ public void init() { * @param feature The feature that should be enabled */ public void enableFeature(Feature feature) { - features.put(feature, true); - sendUpdate(); + getFeatures().ifPresent(features -> { + features.put(feature, true); + sendUpdate(); + }); } /** @@ -73,23 +93,34 @@ public void enableFeature(Feature feature) { * @param feature The feature that should be disabled */ public void disableFeature(Feature feature) { - features.put(feature, false); - sendUpdate(); + getFeatures().ifPresent(features -> { + features.put(feature, false); + sendUpdate(); + }); } /** * Updates the given feature toggles and enables/disables the features based on the given map. Also notifies all clients * by sending a message via the websocket. * - * @param features A map of features (feature -> shouldBeActivated) + * @param updatedFeatures A map of features (feature -> shouldBeActivated) */ - public void updateFeatureToggles(final Map features) { - this.features.putAll(features); - sendUpdate(); + public void updateFeatureToggles(final Map updatedFeatures) { + getFeatures().ifPresent(features -> { + features.putAll(updatedFeatures); + sendUpdate(); + }); } private void sendUpdate() { - websocketMessagingService.sendMessage(TOPIC_FEATURE_TOGGLES, enabledFeatures()); + try { + if (isHazelcastRunning()) { + websocketMessagingService.sendMessage(TOPIC_FEATURE_TOGGLES, enabledFeatures()); + } + } + catch (HazelcastInstanceNotActiveException e) { + log.error("Failed to send features update in {} as Hazelcast instance is not active anymore.", FeatureToggleService.class.getSimpleName()); + } } /** @@ -99,8 +130,16 @@ private void sendUpdate() { * @return if the feature is enabled */ public boolean isFeatureEnabled(Feature feature) { - Boolean isEnabled = features.get(feature); - return Boolean.TRUE.equals(isEnabled); + try { + if (isHazelcastRunning()) { + Boolean isEnabled = features.get(feature); + return Boolean.TRUE.equals(isEnabled); + } + } + catch (HazelcastInstanceNotActiveException e) { + log.error("Failed to check if feature is enabled in FeatureToggleService as Hazelcast instance is not active any more."); + } + return false; } /** @@ -109,7 +148,15 @@ public boolean isFeatureEnabled(Feature feature) { * @return A list of enabled features */ public List enabledFeatures() { - return features.entrySet().stream().filter(feature -> Boolean.TRUE.equals(feature.getValue())).map(Map.Entry::getKey).toList(); + try { + if (isHazelcastRunning()) { + return features.entrySet().stream().filter(feature -> Boolean.TRUE.equals(feature.getValue())).map(Map.Entry::getKey).toList(); + } + } + catch (HazelcastInstanceNotActiveException e) { + log.error("Failed to retrieve enabled features update in FeatureToggleService as Hazelcast instance is not active any more."); + } + return List.of(); } /** @@ -118,6 +165,18 @@ public List enabledFeatures() { * @return A list of disabled features */ public List disabledFeatures() { - return features.entrySet().stream().filter(feature -> Boolean.FALSE.equals(feature.getValue())).map(Map.Entry::getKey).toList(); + try { + if (isHazelcastRunning()) { + return features.entrySet().stream().filter(feature -> Boolean.FALSE.equals(feature.getValue())).map(Map.Entry::getKey).toList(); + } + } + catch (HazelcastInstanceNotActiveException e) { + log.error("Failed to retrieve disabled features update in FeatureToggleService as Hazelcast instance is not active any more."); + } + return List.of(); + } + + private boolean isHazelcastRunning() { + return hazelcastInstance != null && hazelcastInstance.getLifecycleService().isRunning(); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisTextExerciseChatSessionRepository.java b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisTextExerciseChatSessionRepository.java index a8f76c5ff679..be8d6c3b4331 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisTextExerciseChatSessionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisTextExerciseChatSessionRepository.java @@ -1,5 +1,6 @@ package de.tum.cit.aet.artemis.iris.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD; import java.util.Collections; @@ -7,10 +8,12 @@ import jakarta.validation.constraints.NotNull; +import org.springframework.context.annotation.Profile; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.domain.DomainObject; import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; @@ -21,6 +24,8 @@ * Repository interface for managing {@link IrisTextExerciseChatSession} entities. * Provides custom queries for finding text exercise chat sessions based on different criteria. */ +@Profile(PROFILE_IRIS) +@Repository public interface IrisTextExerciseChatSessionRepository extends ArtemisJpaRepository { /** diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/BuildPlanRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/BuildPlanRepository.java index 1a1cd6167bea..9e9c996a8e37 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/BuildPlanRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/BuildPlanRepository.java @@ -1,17 +1,22 @@ package de.tum.cit.aet.artemis.programming.repository; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD; import java.util.Optional; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.build.BuildPlan; +@Profile(PROFILE_CORE) +@Repository public interface BuildPlanRepository extends ArtemisJpaRepository { @Query(""" diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CodeHintRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CodeHintRepository.java index 7c78408aedb6..9c4a03766832 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CodeHintRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CodeHintRepository.java @@ -1,12 +1,16 @@ package de.tum.cit.aet.artemis.programming.repository.hestia; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + import java.util.Optional; import java.util.Set; import jakarta.validation.constraints.NotNull; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; @@ -15,6 +19,8 @@ /** * Spring Data repository for the CodeHint entity. */ +@Profile(PROFILE_CORE) +@Repository public interface CodeHintRepository extends ArtemisJpaRepository { Set findByExerciseId(Long exerciseId); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintActivationRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintActivationRepository.java index 5a24463cdc7f..c827a9b3052b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintActivationRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintActivationRepository.java @@ -1,14 +1,20 @@ package de.tum.cit.aet.artemis.programming.repository.hestia; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + import java.util.Optional; import java.util.Set; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHintActivation; +@Profile(PROFILE_CORE) +@Repository public interface ExerciseHintActivationRepository extends ArtemisJpaRepository { @Query(""" diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseSolutionEntryRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseSolutionEntryRepository.java index 839a7d67dc49..14a03ed49c0a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseSolutionEntryRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseSolutionEntryRepository.java @@ -1,12 +1,16 @@ package de.tum.cit.aet.artemis.programming.repository.hestia; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + import java.util.Optional; import java.util.Set; import jakarta.validation.constraints.NotNull; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; @@ -15,6 +19,8 @@ /** * Spring Data repository for the ProgrammingExerciseSolutionEntry entity. */ +@Profile(PROFILE_CORE) +@Repository public interface ProgrammingExerciseSolutionEntryRepository extends ArtemisJpaRepository { /** diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseTaskRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseTaskRepository.java index 432727c61a3e..778c0c811374 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseTaskRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseTaskRepository.java @@ -1,13 +1,17 @@ package de.tum.cit.aet.artemis.programming.repository.hestia; +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; + import java.util.List; import java.util.Optional; import java.util.Set; import jakarta.validation.constraints.NotNull; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; @@ -16,37 +20,10 @@ /** * Spring Data repository for the ProgrammingExerciseTask entity. */ +@Profile(PROFILE_CORE) +@Repository public interface ProgrammingExerciseTaskRepository extends ArtemisJpaRepository { - Set findByExerciseId(Long exerciseId); - - /** - * Gets a task with its programming exercise, test cases and solution entries of the test cases - * - * @param entryId The id of the task - * @return The task with the given ID if found - * @throws EntityNotFoundException If no task with the given ID was found - */ - @NotNull - default ProgrammingExerciseTask findByIdWithTestCaseAndSolutionEntriesElseThrow(long entryId) throws EntityNotFoundException { - return getValueElseThrow(findByIdWithTestCaseAndSolutionEntries(entryId), entryId); - } - - /** - * Gets a task with its programming exercise, test cases and solution entries of the test cases - * - * @param entryId The id of the task - * @return The task with the given ID - */ - @Query(""" - SELECT t - FROM ProgrammingExerciseTask t - LEFT JOIN FETCH t.testCases tc - LEFT JOIN FETCH tc.solutionEntries - WHERE t.id = :entryId - """) - Optional findByIdWithTestCaseAndSolutionEntries(@Param("entryId") long entryId); - /** * Gets all tasks with its test cases and solution entries of the test case for a programming exercise * 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/assessment/assessment-header/assessment-header.component.html b/src/main/webapp/app/assessment/assessment-header/assessment-header.component.html index fbf4de107932..47c600031279 100644 --- a/src/main/webapp/app/assessment/assessment-header/assessment-header.component.html +++ b/src/main/webapp/app/assessment/assessment-header/assessment-header.component.html @@ -115,7 +115,7 @@

diff --git a/src/main/webapp/app/forms/form-footer/form-footer.component.ts b/src/main/webapp/app/forms/form-footer/form-footer.component.ts index 378422878b70..bb2192728381 100644 --- a/src/main/webapp/app/forms/form-footer/form-footer.component.ts +++ b/src/main/webapp/app/forms/form-footer/form-footer.component.ts @@ -10,8 +10,8 @@ import { ButtonSize } from 'app/shared/components/button.component'; }) export class FormFooterComponent { @Output() save = new EventEmitter(); - // eslint-disable-next-line @angular-eslint/no-output-native - @Output() cancel = new EventEmitter(); + + @Output() onCancel = new EventEmitter(); @Input() isSaving: boolean = false; @Input() isDisabled: boolean = false; diff --git a/src/main/webapp/app/index.d.ts b/src/main/webapp/app/index.d.ts index 44ce332f5e6b..41fc280ef9ff 100644 --- a/src/main/webapp/app/index.d.ts +++ b/src/main/webapp/app/index.d.ts @@ -1,9 +1,4 @@ -declare module 'showdown-katex' { - const main: () => ShowDownExtension; - export = main; -} - -declare module 'showdown-highlight' { - const main: ({ pre: boolean }) => ShowDownExtension; +declare module 'markdown-it-class' { + const main: (md: MarkdownIt) => void; export = main; } diff --git a/src/main/webapp/app/overview/course-conversations/course-conversations.component.html b/src/main/webapp/app/overview/course-conversations/course-conversations.component.html index 113ab1ca1cd8..1e7b44fa6fb7 100644 --- a/src/main/webapp/app/overview/course-conversations/course-conversations.component.html +++ b/src/main/webapp/app/overview/course-conversations/course-conversations.component.html @@ -62,10 +62,7 @@ } -
+
@if (!!postInThread) { { + md.core.ruler.before('normalize', 'artemis_text_replacement', (state) => { + // Perform the replacement on the raw markdown text + state.src = this.replaceText(state.src); + }); + }; + } + + /** + * Performs text replacement on the raw markdown before parsing. + * @param text The raw markdown text. + * @returns The modified markdown text after replacements. + */ + abstract replaceText(text: string): string; +} diff --git a/src/main/webapp/app/shared/markdown-editor/extensions/artemis-showdown-extension-wrapper.ts b/src/main/webapp/app/shared/markdown-editor/extensions/artemis-showdown-extension-wrapper.ts deleted file mode 100644 index ce716b886c4e..000000000000 --- a/src/main/webapp/app/shared/markdown-editor/extensions/artemis-showdown-extension-wrapper.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ShowdownExtension } from 'showdown'; -import { Observable } from 'rxjs'; - -/** - * The idea of this interface is to provide more information for an extension. - * By implementing the interface, the extension can use data that is in the closure of the class (e.g. this.latestResult). - * 1) The component that uses the extension can request it from the wrapper class by using getExtension. - * 2) In some cases it might also be necessary to inject content after the html is loaded, as async data fetching is necessary. - * Therefore, the component can subscribe for injectable elements. - * - */ -export interface ArtemisShowdownExtensionWrapper { - getExtension: () => ShowdownExtension; - subscribeForInjectableElementsFound: () => Observable<() => void>; -} 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 f34057d6ca0a..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 @@ -40,7 +40,7 @@ > @@ -229,7 +229,7 @@
-