diff --git a/package-lock.json b/package-lock.json index ad412d62eb39f3ea745dadfa77f559f3aba1d3b6..950dc82500fd2bdd9a826a9318c7dbda3bf0e55b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,10 @@ "bootstrap": "^5.2.3", "bootstrap-icons": "^1.10.3", "dayjs": "^1.11.7", + "dompurify": "^3.0.1", "filesize": "^10.0.6", "pinia": "^2.0.32", + "showdown": "^2.1.0", "vue": "^3.2.47", "vue-router": "^4.1.6", "vue3-cookies": "^1.0.6" @@ -26,7 +28,9 @@ "@esbuild-plugins/node-modules-polyfill": "^0.1.4", "@rushstack/eslint-patch": "^1.2.0", "@types/bootstrap": "^5.2.6", + "@types/dompurify": "^2.4.0", "@types/node": "^16.11.45", + "@types/showdown": "^2.0.0", "@vitejs/plugin-vue": "^3.2.0", "@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.2", @@ -2329,6 +2333,15 @@ "@popperjs/core": "^2.9.2" } }, + "node_modules/@types/dompurify": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz", + "integrity": "sha512-IDBwO5IZhrKvHFUl+clZxgf3hn2b/lU6H1KaBShPkQyGJUQ0xwebezIPSuiyGwfz1UzJWQl4M7BDxtHtCCPlTg==", + "dev": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -2341,6 +2354,18 @@ "integrity": "sha512-3rKg/L5x0rofKuuUt5zlXzOnKyIHXmIu5R8A0TuNDMF2062/AOIDBciFIjToLEJ/9F9DzkHNot+BpNsMI1OLdQ==", "dev": true }, + "node_modules/@types/showdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.0.tgz", + "integrity": "sha512-70xBJoLv+oXjB5PhtA8vo7erjLDp9/qqI63SRHm4REKrwuPOLs8HhXwlZJBJaB4kC18cCZ1UUZ6Fb/PLFW4TCA==", + "dev": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.30.7", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.7.tgz", @@ -2868,6 +2893,18 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", @@ -3107,7 +3144,6 @@ "version": "9.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", - "dev": true, "engines": { "node": "^12.20.0 || >=14" } @@ -3232,6 +3268,11 @@ "node": ">=6.0.0" } }, + "node_modules/dompurify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz", + "integrity": "sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw==" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3242,34 +3283,44 @@ } }, "node_modules/es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", "dev": true, "dependencies": { + "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", + "get-intrinsic": "^1.1.3", "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", "has": "^1.0.3", "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", + "object-inspect": "^1.12.2", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", + "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" }, "engines": { "node": ">= 0.4" @@ -3278,6 +3329,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -4099,6 +4164,15 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -4187,9 +4261,9 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "dev": true, "dependencies": { "function-bind": "^1.1.1", @@ -4263,6 +4337,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -4283,6 +4372,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -4358,6 +4459,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -4475,12 +4588,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.0", + "get-intrinsic": "^1.2.0", "has": "^1.0.3", "side-channel": "^1.0.4" }, @@ -4488,6 +4601,20 @@ "node": ">= 0.4" } }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4535,9 +4662,9 @@ } }, "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "engines": { "node": ">= 0.4" @@ -4697,6 +4824,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -5171,9 +5317,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5189,14 +5335,14 @@ } }, "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, "engines": { @@ -5755,6 +5901,20 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sass": { "version": "1.56.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.1.tgz", @@ -5814,6 +5974,21 @@ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "dev": true }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -5925,28 +6100,28 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6076,6 +6251,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "4.7.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", @@ -6428,6 +6617,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -8455,6 +8664,15 @@ "@popperjs/core": "^2.9.2" } }, + "@types/dompurify": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz", + "integrity": "sha512-IDBwO5IZhrKvHFUl+clZxgf3hn2b/lU6H1KaBShPkQyGJUQ0xwebezIPSuiyGwfz1UzJWQl4M7BDxtHtCCPlTg==", + "dev": true, + "requires": { + "@types/trusted-types": "*" + } + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -8467,6 +8685,18 @@ "integrity": "sha512-3rKg/L5x0rofKuuUt5zlXzOnKyIHXmIu5R8A0TuNDMF2062/AOIDBciFIjToLEJ/9F9DzkHNot+BpNsMI1OLdQ==", "dev": true }, + "@types/showdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.0.tgz", + "integrity": "sha512-70xBJoLv+oXjB5PhtA8vo7erjLDp9/qqI63SRHm4REKrwuPOLs8HhXwlZJBJaB4kC18cCZ1UUZ6Fb/PLFW4TCA==", + "dev": true + }, + "@types/trusted-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "5.30.7", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.7.tgz", @@ -8844,6 +9074,12 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, "axios": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", @@ -9014,8 +9250,7 @@ "commander": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", - "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", - "dev": true + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==" }, "concat-map": { "version": "0.0.1", @@ -9105,6 +9340,11 @@ "esutils": "^2.0.2" } }, + "dompurify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz", + "integrity": "sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw==" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9115,34 +9355,55 @@ } }, "es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", "dev": true, "requires": { + "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", + "get-intrinsic": "^1.1.3", "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", "has": "^1.0.3", "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", + "object-inspect": "^1.12.2", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", + "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" } }, "es-to-primitive": { @@ -9649,6 +9910,15 @@ "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", "dev": true }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -9715,9 +9985,9 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -9767,6 +10037,15 @@ "type-fest": "^0.20.2" } }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -9781,6 +10060,15 @@ "slash": "^3.0.0" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -9836,6 +10124,12 @@ "get-intrinsic": "^1.1.1" } }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -9912,16 +10206,27 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", "dev": true, "requires": { - "get-intrinsic": "^1.1.0", + "get-intrinsic": "^1.2.0", "has": "^1.0.3", "side-channel": "^1.0.4" } }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -9957,9 +10262,9 @@ } }, "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true }, "is-core-module": { @@ -10059,6 +10364,19 @@ "has-symbols": "^1.0.2" } }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -10433,9 +10751,9 @@ } }, "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "dev": true }, "object-keys": { @@ -10445,14 +10763,14 @@ "dev": true }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, @@ -10807,6 +11125,17 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, "sass": { "version": "1.56.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.1.tgz", @@ -10848,6 +11177,14 @@ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "dev": true }, + "showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "requires": { + "commander": "^9.0.0" + } + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -10941,25 +11278,25 @@ } }, "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" } }, "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" } }, "strip-ansi": { @@ -11047,6 +11384,17 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, "typescript": { "version": "4.7.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", @@ -11278,6 +11626,20 @@ "is-symbol": "^1.0.3" } }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index 9add30fba47ae5eba210323ce126771520bbb04d..17eaf9f907a3942b6ed46992506b4bab6bdf820d 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,16 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.281.0", - "@aws-sdk/s3-request-presigner": "^3.281.0", "@aws-sdk/lib-storage": "^3.281.0", + "@aws-sdk/s3-request-presigner": "^3.281.0", "@popperjs/core": "^2.11.6", "bootstrap": "^5.2.3", "bootstrap-icons": "^1.10.3", "dayjs": "^1.11.7", + "dompurify": "^3.0.1", "filesize": "^10.0.6", "pinia": "^2.0.32", + "showdown": "^2.1.0", "vue": "^3.2.47", "vue-router": "^4.1.6", "vue3-cookies": "^1.0.6" @@ -32,6 +34,8 @@ "@rushstack/eslint-patch": "^1.2.0", "@types/bootstrap": "^5.2.6", "@types/node": "^16.11.45", + "@types/showdown": "^2.0.0", + "@types/dompurify": "^2.4.0", "@vitejs/plugin-vue": "^3.2.0", "@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.2", diff --git a/src/client/workflow/services/WorkflowService.ts b/src/client/workflow/services/WorkflowService.ts index fc952b1701c545bc22bb1ec7397909c8cae9abd9..ba3fe73c2e18c797938e8ca019fcb25df0259f5f 100644 --- a/src/client/workflow/services/WorkflowService.ts +++ b/src/client/workflow/services/WorkflowService.ts @@ -20,20 +20,20 @@ export class WorkflowService { * * Permission "workflow:list" required. * @param nameSubstring Filter workflows by a substring in their name. - * @param filterUnpublished Filter Workflows with unpublished versions. Permission 'Workflow:list_filter' required + * @param versionStatus Which versions of the workflow to include in the response. Permission 'workflow:list_filter required'. Default PUBLISHED and DEPRECATED. * @returns WorkflowOut Successful Response * @throws ApiError */ public static workflowListWorkflows( nameSubstring?: string, - filterUnpublished: boolean = false, + versionStatus?: Array<Status>, ): CancelablePromise<Array<WorkflowOut>> { return __request(OpenAPI, { method: 'GET', url: '/workflows', query: { 'name_substring': nameSubstring, - 'filter_unpublished': filterUnpublished, + 'version_status': versionStatus, }, errors: { 400: `Error decoding JWT Token`, diff --git a/src/components/MarkdownRenderer.vue b/src/components/MarkdownRenderer.vue new file mode 100644 index 0000000000000000000000000000000000000000..05bea01436d8a74b1dfe03ed8b73e2ffc84df890 --- /dev/null +++ b/src/components/MarkdownRenderer.vue @@ -0,0 +1,21 @@ +<script setup lang="ts"> +import showdown from "showdown"; +import DOMPurify from "dompurify"; +import { computed } from "vue"; + +const props = defineProps<{ + markdown: string; +}>(); + +const converter = new showdown.Converter(); +const outputHtml = computed(() => { + const dirtyHTML = converter.makeHtml(props.markdown); + return DOMPurify.sanitize(dirtyHTML); +}); +</script> + +<template> + <div v-html="outputHtml"></div> +</template> + +<style scoped></style> diff --git a/src/components/NavbarTop.vue b/src/components/NavbarTop.vue index d8fbb2f5c5818102c073affce8c418c42ddbae96..94a91a0dd0b0b6bec8dff29a07fe1d5fa65d7773 100644 --- a/src/components/NavbarTop.vue +++ b/src/components/NavbarTop.vue @@ -31,6 +31,8 @@ watch( if (typeof to === "string") { if (to.startsWith("bucket")) { activeRoute.value = "buckets"; + } else if (to.startsWith("workflow")) { + activeRoute.value = "workflows"; } else { activeRoute.value = to; } diff --git a/src/components/transitions/CardTransitionGroup.vue b/src/components/transitions/CardTransitionGroup.vue new file mode 100644 index 0000000000000000000000000000000000000000..677b5fac67f0363950f3774b8162db4e42f261d1 --- /dev/null +++ b/src/components/transitions/CardTransitionGroup.vue @@ -0,0 +1,31 @@ +<script setup lang="ts"> +defineProps({ + tag: { type: String, required: false, default: "div" }, +}); +</script> + +<template> + <transition-group name="card" :tag="tag"> + <slot></slot> + </transition-group> +</template> + +<style> +.card-move, /* apply transition to moving elements */ +.card-enter-active, +.card-leave-active { + transition: all 0.5s ease; +} + +.card-enter-from, +.card-leave-to { + opacity: 0; + transform: scale(0); +} + +/* ensure leaving items are taken out of layout flow so that moving + animations can be calculated correctly. */ +.card-leave-active { + position: absolute; +} +</style> diff --git a/src/components/transitions/ListTransitionGroup.vue b/src/components/transitions/ListTransitionGroup.vue new file mode 100644 index 0000000000000000000000000000000000000000..59f342d62396e5ad6f15e1adeb0cca58299d1f56 --- /dev/null +++ b/src/components/transitions/ListTransitionGroup.vue @@ -0,0 +1,31 @@ +<script setup lang="ts"> +defineProps({ + tag: { type: String, required: false, default: "div" }, +}); +</script> + +<template> + <transition-group name="list" :tag="tag"> + <slot></slot> + </transition-group> +</template> + +<style> +.list-move, /* apply transition to moving elements */ +.list-enter-active, +.list-leave-active { + transition: all 0.3s ease; +} + +.list-enter-from, +.list-leave-to { + transform: rotateX(90deg); + transform-origin: center top; +} + +/* ensure leaving items are taken out of layout flow so that moving + animations can be calculated correctly. */ +.list-leave-active { + position: absolute; +} +</style> diff --git a/src/components/workflows/WorkflowCard.vue b/src/components/workflows/WorkflowCard.vue index 42e31f131eb0547f04c642b08bfac8f229778c5d..8c16d992beebf8fa39968ba1425e4bccb35481c2 100644 --- a/src/components/workflows/WorkflowCard.vue +++ b/src/components/workflows/WorkflowCard.vue @@ -32,12 +32,32 @@ onMounted(() => { <template> <div class="card-hover border border-secondary card text-bg-dark m-2"> <div class="card-body"> - <h3 class="card-title"> + <div + class="card-title fs-3 d-flex justify-content-between align-items-center" + > <div v-if="props.loading" class="placeholder-glow"> <span class="placeholder col-6"></span> </div> - <a v-else href="#">{{ props.workflow.name }}</a> - </h3> + <router-link + v-else + class="text-truncate" + :to="{ + name: 'workflow-version', + params: { + workflowId: workflow.workflow_id, + versionId: latestVersion?.git_commit_hash, + }, + }" + >{{ props.workflow.name }} + </router-link> + + <img + v-if="latestVersion?.icon_url != null" + :src="latestVersion.icon_url" + class="img-fluid float-end icon" + alt="Workflow icon" + /> + </div> <p class="card-text" :class="{ 'text-truncate': truncateDescription }"> <span v-if="props.loading" class="placeholder-glow" ><span class="placeholder col-12"></span @@ -95,4 +115,9 @@ onMounted(() => { .card-hover:hover { transform: translate(0, -5px); } + +.icon { + max-height: 30px; + max-width: 30px; +} </style> diff --git a/src/router/index.ts b/src/router/index.ts index 39acd6ecddd838a0a4346340f3dba5d803fe3db6..f98facbab14997df5b85814d29a785e2fe939fee 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -33,6 +33,25 @@ const router = createRouter({ name: "workflows", component: () => import("../views/workflows/ListWorkflowsView.vue"), }, + { + path: "workflows/:workflowId/", + name: "workflow", + component: () => import("../views/workflows/WorkflowView.vue"), + props: true, + children: [ + { + path: "version/:versionId", + name: "workflow-version", + component: () => + import("../views/workflows/WorkflowVersionView.vue"), + props: (route) => ({ + versionId: route.params.versionId, + workflowId: route.params.workflowId, + activeTab: route.query.tab ?? "description", + }), + }, + ], + }, ], }, { diff --git a/src/views/object-storage/S3KeysView.vue b/src/views/object-storage/S3KeysView.vue index 42766a2e6c7c74142d5cfd5776a41aee760233e6..4c09afb658bea79934aa7484be0fcb0f99ccedb4 100644 --- a/src/views/object-storage/S3KeysView.vue +++ b/src/views/object-storage/S3KeysView.vue @@ -139,7 +139,7 @@ onMounted(() => { <button v-for="(s3key, index) in keyState.keys" :key="s3key.access_key" - class="btn w-100 fs-5 mb-3" + class="btn w-100 fs-5 mb-3 text-truncate" type="button" @click="keyState.activeKey = index" :class="{ diff --git a/src/views/workflows/ListWorkflowsView.vue b/src/views/workflows/ListWorkflowsView.vue index 211b92019f525f93620bb5137f834fb579b2cf3b..8ac24cd539791e94c49f65f109df337db257f51f 100644 --- a/src/views/workflows/ListWorkflowsView.vue +++ b/src/views/workflows/ListWorkflowsView.vue @@ -4,6 +4,7 @@ import type { ComputedRef } from "vue"; import { useWorkflowStore } from "@/stores/workflows"; import type { WorkflowOut } from "@/client/workflow"; import WorkflowCard from "@/components/workflows/WorkflowCard.vue"; +import CardTransitionGroup from "@/components/transitions/CardTransitionGroup.vue"; import dayjs from "dayjs"; import BootstrapIcon from "@/components/BootstrapIcon.vue"; @@ -44,13 +45,13 @@ function filterWorkflowWithoutVersion(workflow: WorkflowOut): boolean { } const processedWorkflows: ComputedRef<WorkflowOut[]> = computed(() => { - return workflowRepository.workflows - .filter( + return [ + ...workflowRepository.workflows.filter( (workflow) => filterWorkflowByString(workflow) && filterWorkflowWithoutVersion(workflow) - ) - .sort((a, b) => (bla[workflowsState.sortByAttribute](a, b) ? 1 : -1)); + ), + ].sort((a, b) => (bla[workflowsState.sortByAttribute](a, b) ? 1 : -1)); }); onMounted(() => { @@ -131,13 +132,12 @@ onMounted(() => { > <bootstrap-icon icon="x-lg" - class="mb-5" + class="my-5" width="75" height="75" style="color: var(--bs-secondary)" /> - <br /> - There are no workflows in the system. Please come again later. + <p>There are no workflows in the system. Please come again later.</p> </div> <div v-else-if="processedWorkflows.length === 0" @@ -145,17 +145,18 @@ onMounted(() => { > <bootstrap-icon icon="search" - class="mb-5" + class="my-5" width="75" height="75" style="color: var(--bs-secondary)" /> - <br /> - Could not find any Workflows containing<br />'{{ - workflowsState.filterString - }}' + <p> + Could not find any Workflows containing<br />'{{ + workflowsState.filterString + }}' + </p> </div> - <div + <CardTransitionGroup v-else class="d-flex flex-wrap align-items-center justify-content-between" > @@ -165,7 +166,7 @@ onMounted(() => { :workflow="workflow" :loading="false" /> - </div> + </CardTransitionGroup> </div> <div v-else diff --git a/src/views/workflows/WorkflowVersionView.vue b/src/views/workflows/WorkflowVersionView.vue new file mode 100644 index 0000000000000000000000000000000000000000..63e70256d39e7e6277389851ad95e11de252c5d7 --- /dev/null +++ b/src/views/workflows/WorkflowVersionView.vue @@ -0,0 +1,142 @@ +<script setup lang="ts"> +import { onMounted, reactive, watch } from "vue"; +import { WorkflowVersionService } from "@/client/workflow"; +import type { WorkflowVersionFull } from "@/client/workflow"; +import axios from "axios"; +import MarkdownRenderer from "@/components/MarkdownRenderer.vue"; + +const props = defineProps<{ + versionId: string; + workflowId: string; + activeTab: string; +}>(); + +const versionState = reactive({ + loading: true, + fileLoading: true, + version: undefined, + errorLoading: false, + descriptionMarkdown: "", + changelogMarkdown: "", + parameterSchema: {}, +} as { + loading: boolean; + fileLoading: boolean; + version: undefined | WorkflowVersionFull; + descriptionMarkdown: string; + changelogMarkdown: string; + parameterSchema: Record<string, never>; +}); + +watch( + () => props.versionId, + (newVersionId, oldVersionId) => { + if (newVersionId !== oldVersionId) { + // If bucket is changed, update the objects + updateVersion(newVersionId, props.workflowId); + } + } +); + +function updateVersion(versionId: string, workflowId: string) { + versionState.loading = true; + versionState.fileLoading = true; + WorkflowVersionService.workflowVersionGetWorkflowVersion( + versionId, + workflowId + ) + .then((version) => { + versionState.version = version; + downloadVersionFiles(version); + }) + .catch(() => { + versionState.version = undefined; + }) + .finally(() => { + versionState.loading = false; + }); +} + +onMounted(() => { + updateVersion(props.versionId, props.workflowId); +}); + +function downloadVersionFiles(version: WorkflowVersionFull) { + versionState.fileLoading = true; + const descriptionPromise = axios.get(version.readme_url).then((response) => { + versionState.descriptionMarkdown = response.data; + }); + const changelogPromise = axios.get(version.changelog_url).then((response) => { + versionState.changelogMarkdown = response.data; + }); + const parameterPromise = axios + .get(version.parameter_schema_url) + .then((response) => { + versionState.parameterSchema = response.data; + }); + Promise.all([descriptionPromise, changelogPromise, parameterPromise]).finally( + () => { + versionState.fileLoading = false; + } + ); +} +</script> + +<template> + <ul class="nav justify-content-evenly nav-tabs bg-dark fs-5 mb-3"> + <li class="nav-item"> + <router-link + class="nav-link" + aria-current="page" + :to="{ query: { tab: 'description' } }" + :class="{ active: props.activeTab === 'description' }" + >Description + </router-link> + </li> + <li class="nav-item"> + <router-link + class="nav-link" + :to="{ query: { tab: 'parameters' } }" + :class="{ active: props.activeTab === 'parameters' }" + >Parameters + </router-link> + </li> + <li class="nav-item"> + <router-link + class="nav-link" + :to="{ query: { tab: 'changes' } }" + :class="{ active: props.activeTab === 'changes' }" + >Releases + </router-link> + </li> + </ul> + <div v-if="versionState.fileLoading"> + <p class="placeholder-glow mt-2 mb-4"> + <span class="placeholder col-7 fs-1"></span> + </p> + <p + v-for="n in 8" + :key="n" + class="placeholder-glow row ms-1" + :class="'my-' + Math.floor(Math.random() * 6)" + > + <span + class="placeholder" + :class="'col-' + Math.floor(Math.random() * 9 + 2)" + ></span> + </p> + </div> + <div v-else> + <p v-if="props.activeTab === 'description'"> + <markdown-renderer :markdown="versionState.descriptionMarkdown" /> + </p> + <pre v-else-if="props.activeTab === 'parameters'" + >{{ JSON.stringify(versionState.parameterSchema, null, 2) }} + </pre> + <p v-else-if="props.activeTab === 'changes'"> + <markdown-renderer :markdown="versionState.changelogMarkdown" /> + </p> + </div> +</template> + +<style scoped></style> diff --git a/src/views/workflows/WorkflowView.vue b/src/views/workflows/WorkflowView.vue new file mode 100644 index 0000000000000000000000000000000000000000..a13beb39e0f04fe391d177bc198000ff4d9aace5 --- /dev/null +++ b/src/views/workflows/WorkflowView.vue @@ -0,0 +1,182 @@ +<script setup lang="ts"> +import { computed, onMounted, reactive, watch } from "vue"; +import type { ComputedRef } from "vue"; +import type { WorkflowOut } from "@/client/workflow"; +import { WorkflowService } from "@/client/workflow"; +import { useRoute, useRouter } from "vue-router"; +import BootstrapIcon from "@/components/BootstrapIcon.vue"; + +const props = defineProps<{ + workflowId: string; +}>(); +const router = useRouter(); +const route = useRoute(); + +const workflowState = reactive({ + loading: true, + workflow: undefined, + activeVersionId: "", +} as { + loading: boolean; + workflow?: WorkflowOut; + activeVersionId: string; +}); + +watch( + () => props.workflowId, + (newWorkflowId, oldWorkflowId) => { + if (newWorkflowId !== oldWorkflowId) { + updateWorkflow(newWorkflowId); + } + } +); + +watch( + () => workflowState.activeVersionId, + (newVersionId, oldVersionId) => { + if (newVersionId !== oldVersionId) { + router.push({ + name: "workflow-version", + params: { versionId: newVersionId }, + query: { tab: route.query.tab }, + }); + } + } +); + +function updateWorkflow(workflowId: string) { + workflowState.loading = true; + WorkflowService.workflowGetWorkflow(workflowId) + .then((workflow) => { + workflowState.workflow = workflow; + workflowState.activeVersionId = + workflow.versions[workflow.versions.length - 1].git_commit_hash; + }) + .catch(() => { + workflowState.workflow = undefined; + }) + .finally(() => { + workflowState.loading = false; + }); +} + +const activeVersionString: ComputedRef<string> = computed(() => { + return ( + workflowState.workflow?.versions.find( + (w) => w.git_commit_hash === workflowState.activeVersionId + )?.version ?? "" + ); +}); + +const activeVersionIcon: ComputedRef<string | undefined> = computed(() => { + return workflowState.workflow?.versions.find( + (w) => w.git_commit_hash === workflowState.activeVersionId + )?.icon_url; +}); + +onMounted(() => { + updateWorkflow(props.workflowId); +}); +</script> + +<template> + <div v-if="workflowState.loading"> + <div + class="d-flex mt-5 justify-content-between align-items-center placeholder-glow" + > + <span class="fs-0 placeholder col-6"></span> + <span class="fs-0 placeholder col-1"></span> + </div> + <div class="fs-4 mb-5 mt-4 placeholder-glow"> + <span class="placeholder col-10"></span> + </div> + <div class="row align-items-center placeholder-glow my-1"> + <span class="mx-auto col-2 placeholder bg-success fs-0"></span> + <span class="position-absolute end-0 col-1 placeholder fs-2"></span> + </div> + <div class="row w-100 mb-4 mt-3 mx-0 placeholder-glow"> + <span class="placeholder col-3 mx-auto"></span> + </div> + </div> + <div v-else-if="workflowState.workflow != null"> + <div class="d-flex justify-content-between align-items-center"> + <span class="fs-0 w-fit">{{ workflowState.workflow.name }}</span> + <a :href="workflowState.workflow.repository_url" target="_blank"> + <img + v-if="activeVersionIcon != null" + :src="activeVersionIcon" + class="img-fluid icon" + alt="Workflow icon" + /></a> + </div> + <p class="fs-4 mb-5 mt-3">{{ workflowState.workflow.short_description }}</p> + <div class="row align-items-center"> + <a role="button" class="btn btn-success btn-lg w-fit mx-auto" href="#"> + <bootstrap-icon icon="rocket-takeoff-fill" class="me-2" /> + <span class="align-middle">Launch {{ activeVersionString }}</span> + </a> + <div class="input-group w-fit position-absolute end-0"> + <span class="input-group-text px-2" id="workflow-version-wrapping" + ><bootstrap-icon icon="tags-fill" class="text-secondary" + /></span> + <select + class="form-select form-select-sm" + aria-label="Workflow version selection" + aria-describedby="workflow-version-wrapping" + v-model="workflowState.activeVersionId" + > + <option + v-for="version in [...workflowState.workflow.versions].reverse()" + :key="version.git_commit_hash" + :value="version.git_commit_hash" + > + {{ version.version }} + </option> + </select> + </div> + </div> + <div class="row w-100 mb-4 mt-2 mx-0"> + <a + :href="workflowState.workflow.repository_url" + target="_blank" + class="text-secondary text-decoration-none mx-auto w-fit p-0" + > + <bootstrap-icon icon="git" class="me-1" /> + <span class="align-middle"> + {{ workflowState.workflow.repository_url }}</span + ></a + > + </div> + </div> + <router-view v-if="workflowState.loading || workflowState.workflow != null" /> + <div v-else class="text-center fs-1 mt-5"> + <bootstrap-icon + icon="search" + class="my-5" + width="85" + height="85" + style="color: var(--bs-secondary)" + /> + <p class="my-5"> + Could not find any Workflow with ID <br />'{{ workflowId }}' + </p> + <router-link :to="{ name: 'workflows' }" class="mt-5">Back</router-link> + </div> +</template> + +<style scoped> +.fs-0 { + font-size: 4em; +} + +.icon:hover { + opacity: 0.8; +} + +.icon { + max-width: 60px; + max-height: 60px; + min-width: 50px; + min-height: 50px; +} +</style>