diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1a41204 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.github +.idea +.vscode +node_modules diff --git a/Caddyfile b/Caddyfile index 8abd4f8..3c0b63d 100644 --- a/Caddyfile +++ b/Caddyfile @@ -2,7 +2,7 @@ @api path /api/* handle @api { uri strip_prefix /api - reverse_proxy http://rss-nest:3600 + reverse_proxy http://feedz-api:3600 } handle { diff --git a/angular.json b/angular.json index c052592..2adfc11 100644 --- a/angular.json +++ b/angular.json @@ -67,7 +67,8 @@ "maximumError": "8kB" } ], - "outputHashing": "all" + "outputHashing": "all", + "serviceWorker": "ngsw-config.json" }, "development": { "optimization": false, diff --git a/ngsw-config.json b/ngsw-config.json new file mode 100644 index 0000000..69edd28 --- /dev/null +++ b/ngsw-config.json @@ -0,0 +1,30 @@ +{ + "$schema": "./node_modules/@angular/service-worker/config/schema.json", + "index": "/index.html", + "assetGroups": [ + { + "name": "app", + "installMode": "prefetch", + "resources": { + "files": [ + "/favicon.ico", + "/index.csr.html", + "/index.html", + "/manifest.webmanifest", + "/*.css", + "/*.js" + ] + } + }, + { + "name": "assets", + "installMode": "lazy", + "updateMode": "prefetch", + "resources": { + "files": [ + "/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)" + ] + } + } + ] +} diff --git a/package-lock.json b/package-lock.json index 9204c1d..d1ee60f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,23 +9,24 @@ "version": "0.0.2", "license": "ISC", "dependencies": { - "@angular/cdk": "^20.2.2", - "@angular/common": "^20.0.0", - "@angular/compiler": "^20.0.0", - "@angular/core": "^20.0.0", - "@angular/forms": "^20.0.0", - "@angular/material": "^20.2.2", - "@angular/platform-browser": "^20.0.0", - "@angular/router": "^20.0.0", + "@angular/cdk": "^20.2.7", + "@angular/common": "^20.3.3", + "@angular/compiler": "^20.3.3", + "@angular/core": "^20.3.3", + "@angular/forms": "^20.3.3", + "@angular/material": "^20.2.7", + "@angular/platform-browser": "^20.3.3", + "@angular/router": "^20.3.3", + "@angular/service-worker": "^20.3.3", "prettier": "^3.5.3", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { - "@angular/build": "^20.0.2", - "@angular/cli": "^20.0.2", - "@angular/compiler-cli": "^20.0.0", + "@angular/build": "^20.3.3", + "@angular/cli": "^20.3.3", + "@angular/compiler-cli": "^20.3.3", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.7.0", "karma": "~6.4.0", @@ -260,13 +261,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2002.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2002.2.tgz", - "integrity": "sha512-amppp/UqKyj+B8hYFU16j4t6SVN+SS0AEnHivDjKy41NNJgXv+5Sm2Q2jaMHviCT3rclyT0wqwNAi0RDjyLx5Q==", + "version": "0.2003.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.3.tgz", + "integrity": "sha512-DOnGyv9g24vaDzf5koLOcVri1kYJIBD9UKiJWOWk4H5cFlcpTXQ+PilPmDq6A+X94Tt4MZHImmKsk6LLRPIwFg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.2.2", + "@angular-devkit/core": "20.3.3", "rxjs": "7.8.2" }, "engines": { @@ -276,9 +277,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "20.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.2.2.tgz", - "integrity": "sha512-SC+f5isSWJBpEgR+R7jP++2Z14WExNWLAdKpIickLWjuL8FlGkj+kaF3dWXhh0KcXo+r6kKb4pWUptSaqer5gA==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.3.tgz", + "integrity": "sha512-2T5mX2duLapZYPYmXUSUe9VW8Dhu10nVBVvEp31jSE6xvjbPM5mlsv6+fks1E4RjhzvaamY9bm3WgwYwNiEV5g==", "dev": true, "license": "MIT", "dependencies": { @@ -304,13 +305,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "20.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.2.2.tgz", - "integrity": "sha512-rtL7slZjzdChQoiADKZv/Ra8D3C3tIw/WcVxd2stiLHdK/Oaf9ejx5m/X9o0QMEbNsy2Fy/RKodNqmz1CjzpCg==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.3.tgz", + "integrity": "sha512-LDn39BjyQLAK/DaVamLElMtI0UoCZIs4jKcMEv8PJ/nnBmrYFHVavWPggeFWMycjeXsdX34Msiml88HZWlXypw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.2.2", + "@angular-devkit/core": "20.3.3", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "8.2.0", @@ -323,14 +324,14 @@ } }, "node_modules/@angular/build": { - "version": "20.2.2", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.2.2.tgz", - "integrity": "sha512-rvlKMt3OmeenHOwejRpI4OLcyERQn6Hl4ODRWlYfNX70Ki1zu6eAD0pWULzcD+HSQd0a26Xzt3gcpEy2vOEAzg==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.3.tgz", + "integrity": "sha512-WhwAbovHAxDbNeR5jB2IS/SVs+yQg9NETFeJ5f7T3n/414ULkGOhXn+29i1rzwJhf1uqM9lsedcv2tKn1N24/A==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2002.2", + "@angular-devkit/architect": "0.2003.3", "@babel/core": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -348,12 +349,12 @@ "parse5-html-rewriting-stream": "8.0.0", "picomatch": "4.0.3", "piscina": "5.1.3", - "rolldown": "1.0.0-beta.32", + "rolldown": "1.0.0-beta.38", "sass": "1.90.0", "semver": "7.7.2", "source-map-support": "0.5.21", "tinyglobby": "0.2.14", - "vite": "7.1.2", + "vite": "7.1.5", "watchpack": "2.4.4" }, "engines": { @@ -372,7 +373,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.2.2", + "@angular/ssr": "^20.3.3", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^20.0.0", @@ -422,9 +423,9 @@ } }, "node_modules/@angular/cdk": { - "version": "20.2.2", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.2.tgz", - "integrity": "sha512-jLvIMmFI8zoi6vAu1Aszua59GmhqBOtsVfkwLUGg5Hi86DI/inJr9BznNX2EKDtaulYMGZCmDgsltXQXeqP5Lg==", + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.7.tgz", + "integrity": "sha512-QTqxPJSMXyjaswtpUrziwdoKRhqT2P9/Ascwzjg8T/SofV1850pc3YmonoOFrurYrmd4plZzWdr7raGcBWIh/Q==", "license": "MIT", "dependencies": { "parse5": "^8.0.0", @@ -437,19 +438,19 @@ } }, "node_modules/@angular/cli": { - "version": "20.2.2", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.2.2.tgz", - "integrity": "sha512-0K8cmuHzRTpPzy/w0+S5o3s0JPV++9/s2JhK4aw/+OnQRpUbodoqjm1ur5k5DUBQfIHi7aM73ZIW3G43lv4F0g==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.3.tgz", + "integrity": "sha512-3c8xCklJ0C0T6ETSncAoXlOYNi3x7vLT3PS56rIaQ0jtlvD4Y+RQakd3+iffVAapvh/JB27WNor8pJRThLZ/jg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2002.2", - "@angular-devkit/core": "20.2.2", - "@angular-devkit/schematics": "20.2.2", + "@angular-devkit/architect": "0.2003.3", + "@angular-devkit/core": "20.3.3", + "@angular-devkit/schematics": "20.3.3", "@inquirer/prompts": "7.8.2", "@listr2/prompt-adapter-inquirer": "3.0.1", "@modelcontextprotocol/sdk": "1.17.3", - "@schematics/angular": "20.2.2", + "@schematics/angular": "20.3.3", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.35.0", "ini": "5.0.0", @@ -472,9 +473,9 @@ } }, "node_modules/@angular/common": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.2.4.tgz", - "integrity": "sha512-mc6Sq1cYjaPJYThnvG6x0f/E27pWksqwaNJxT1RtwhAGc1i2jsc0su6b7e5NnXEgVbdPqu1MZHAEFdXZ5+/MwQ==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.3.tgz", + "integrity": "sha512-iArFCXvgYJCpxLZv8o6rV7Cxuqv1hbndoeUmQgL7ekXwVS6BA49VErXbTPM+pfhAJ+v1fc/DG3rzBwXk3eW2lw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -483,14 +484,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.2.4", + "@angular/core": "20.3.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.2.4.tgz", - "integrity": "sha512-LQzf+Azb/Ms+BavpCFIat+f1C0gUJpby2RW4yebF3JkBFKfJ7M8d49TQpF8rSnGxMRTf49mln7laz4nBYTLDGA==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.3.tgz", + "integrity": "sha512-7AUtF7PO8xo+jOgrhLRPXmt65M/KFuYIsVZGVLB1FTCUAPByFJEUYOSnUuHyvFQQqHesK4aYSP27slDpHH/PSA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -500,9 +501,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.2.4.tgz", - "integrity": "sha512-II2hEpfbo73dL12D42DoIHYGiTYAiO9cpwh29BIo8VD054ei4cm0oK+jCyryDQH5T3+wyCWlj0OFjcZ/GmO7HQ==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.3.tgz", + "integrity": "sha512-kSIE6hkTiZGiJLyisp5Q6NXOHiDNOItp7N2HVNPrK1bqzM8foN6H6BE1a+LYO3Lwy3PkwQFzx03BnzxkM4sWng==", "dev": true, "license": "MIT", "dependencies": { @@ -523,7 +524,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.2.4", + "@angular/compiler": "20.3.3", "typescript": ">=5.8 <6.0" }, "peerDependenciesMeta": { @@ -533,9 +534,9 @@ } }, "node_modules/@angular/core": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.2.4.tgz", - "integrity": "sha512-8yvfvPDWX8M7o82GBl5P1nlvm1ywQ2XZi5HWj3llKpSJE2XjzhATgPrpKwiNVnpgjZWTOwM11fpoAaRKqQjxTA==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.3.tgz", + "integrity": "sha512-AWBCixxw4N9VgKT1uwrRPr1dH3CpT/ffcCsXJQ8TjzsKYjVBkXVht5OjtxJOWOQ2KaHwsGFEmDMv9fc1BHDFhQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -544,7 +545,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.2.4", + "@angular/compiler": "20.3.3", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" }, @@ -558,9 +559,9 @@ } }, "node_modules/@angular/forms": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.2.4.tgz", - "integrity": "sha512-wbgnW+GALVAmK6hgFegkwlHKw35onvh9Z5A236HCyUySEAOiaD/3CoDg5Hw4iHQAiSU6Fn2NwDiv+W0xki6WDw==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.3.tgz", + "integrity": "sha512-Rv3sO1vOAbw03IRK30CB45eucxZ1rI0Jyaa6QVmDlOzQ4bktkanbGxQtaxBdc9bKPBO1SVx27eTbStR7i3BNRg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -569,22 +570,22 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.2.4", - "@angular/core": "20.2.4", - "@angular/platform-browser": "20.2.4", + "@angular/common": "20.3.3", + "@angular/core": "20.3.3", + "@angular/platform-browser": "20.3.3", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "20.2.2", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.2.tgz", - "integrity": "sha512-ovLk6h6XIw3qtSjp2bSqFn7ANYvWOIh2zTrRPdAB78siOpqs11d8YdyD4LUEuUrcZoInNgK7AMJsfldDkHwhnA==", + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.7.tgz", + "integrity": "sha512-VXsP5qkQQ3sCGkSHsgDku/OVlunGsqssOM057foOKJuajECsI3ZpGuLJ13nvLm9Z147UZOZfP463ixZIjd4XuQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/cdk": "20.2.2", + "@angular/cdk": "20.2.7", "@angular/common": "^20.0.0 || ^21.0.0", "@angular/core": "^20.0.0 || ^21.0.0", "@angular/forms": "^20.0.0 || ^21.0.0", @@ -593,9 +594,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.2.4.tgz", - "integrity": "sha512-81vzW8xhnJU7AiYJKXLR2MuvawzhRDgwyNkPEep58wty5zNuIUCXdUERJSsXo7m/U2Dg1FUFfqLm4RC2UkqLzA==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.3.tgz", + "integrity": "sha512-RUWpg49GnXdINjomRFrE/SRioxEehYqUzDVskDWddNeNhV9Z21zeC6Ao2i5q8UKq0y/oq2ShX7XFLprxqLoLnQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -604,9 +605,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "20.2.4", - "@angular/common": "20.2.4", - "@angular/core": "20.2.4" + "@angular/animations": "20.3.3", + "@angular/common": "20.3.3", + "@angular/core": "20.3.3" }, "peerDependenciesMeta": { "@angular/animations": { @@ -615,9 +616,9 @@ } }, "node_modules/@angular/router": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.2.4.tgz", - "integrity": "sha512-KoduI1o+iBfCBGtXMvmy/qncDIwGxd2hNt2hDkkiYZTftmSg/XUJDxJqN84ckm2WLkdJpR9EirrwfHapJBIZOQ==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.3.tgz", + "integrity": "sha512-IrO5GY/vmaWwNdfR51xswNnBSxeEuvQAUqK3H0UNxhZlIE9gUS6pbbSidGGrQOZK+i0nd/rDz7j+RV7h2NK9aA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -626,9 +627,28 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.2.4", - "@angular/core": "20.2.4", - "@angular/platform-browser": "20.2.4", + "@angular/common": "20.3.3", + "@angular/core": "20.3.3", + "@angular/platform-browser": "20.3.3", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/service-worker": { + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-20.3.3.tgz", + "integrity": "sha512-Md0jsR7qIe5w8QmjZ3jPcqT1bVbFNjJik0QC8c+YXbApdt9O658z7eqPjj+sSK0cr1I6+ppCRstIL68DKha8sg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "bin": { + "ngsw-config": "ngsw-config.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.3.3", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -2468,16 +2488,16 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.3.tgz", - "integrity": "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz", + "integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.5", - "@emnapi/runtime": "^1.4.5", - "@tybys/wasm-util": "^0.10.0" + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" } }, "node_modules/@npmcli/agent": { @@ -2776,20 +2796,10 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@oxc-project/runtime": { - "version": "0.81.0", - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.81.0.tgz", - "integrity": "sha512-zm/LDVOq9FEmHiuM8zO4DWirv0VP2Tv2VsgaiHby9nvpq+FVrcqNYgv+TysLKOITQXWZj/roluTxFvpkHP0Iuw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@oxc-project/types": { - "version": "0.81.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.81.0.tgz", - "integrity": "sha512-CnOqkybZK8z6Gx7Wb1qF7AEnSzbol1WwcIzxYOr8e91LytGOjo0wCpgoYWZo8sdbpqX+X+TJayIzo4Pv0R/KjA==", + "version": "0.89.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.89.0.tgz", + "integrity": "sha512-yuo+ECPIW5Q9mSeNmCDC2im33bfKuwW18mwkaHMQh8KakHYDzj4ci/q7wxf2qS3dMlVVCIyrs3kFtH5LmnlYnw==", "dev": true, "license": "MIT", "funding": { @@ -3140,9 +3150,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.32.tgz", - "integrity": "sha512-Gs+313LfR4Ka3hvifdag9r44WrdKQaohya7ZXUXzARF7yx0atzFlVZjsvxtKAw1Vmtr4hB/RjUD1jf73SW7zDw==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.38.tgz", + "integrity": "sha512-AE3HFQrjWCKLFZD1Vpiy+qsqTRwwoil1oM5WsKPSmfQ5fif/A+ZtOZetF32erZdsR7qyvns6qHEteEsF6g6rsQ==", "cpu": [ "arm64" ], @@ -3151,12 +3161,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.32.tgz", - "integrity": "sha512-W8oMqzGcI7wKPXUtS3WJNXzbghHfNiuM1UBAGpVb+XlUCgYRQJd2PRGP7D3WGql3rR3QEhUvSyAuCBAftPQw6Q==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.38.tgz", + "integrity": "sha512-RaoWOKc0rrFsVmKOjQpebMY6c6/I7GR1FBc25v7L/R7NlM0166mUotwGEv7vxu7ruXH4SJcFeVrfADFUUXUmmQ==", "cpu": [ "arm64" ], @@ -3165,12 +3178,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.32.tgz", - "integrity": "sha512-pM4c4sKUk37noJrnnDkJknLhCsfZu7aWyfe67bD0GQHfzAPjV16wPeD9CmQg4/0vv+5IfHYaa4VE536xbA+W0Q==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.38.tgz", + "integrity": "sha512-Ymojqc2U35iUc8NFU2XX1WQPfBRRHN6xHcrxAf9WS8BFFBn8pDrH5QPvH1tYs3lDkw6UGGbanr1RGzARqdUp1g==", "cpu": [ "x64" ], @@ -3179,12 +3195,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.32.tgz", - "integrity": "sha512-M8SUgFlYb5kJJWcFC8gUMRiX4WLFxPKMed3SJ2YrxontgIrEcpizPU8nLNVsRYEStoSfKHKExpQw3OP6fm+5bw==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.38.tgz", + "integrity": "sha512-0ermTQ//WzSI0nOL3z/LUWMNiE9xeM5cLGxjewPFEexqxV/0uM8/lNp9QageQ8jfc/VO1OURsGw34HYO5PaL8w==", "cpu": [ "x64" ], @@ -3193,12 +3212,15 @@ "optional": true, "os": [ "freebsd" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.32.tgz", - "integrity": "sha512-FuQpbNC/hE//bvv29PFnk0AtpJzdPdYl5CMhlWPovd9g3Kc3lw9TrEPIbL7gRPUdhKAiq6rVaaGvOnXxsa0eww==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.38.tgz", + "integrity": "sha512-GADxzVUTCTp6EWI52831A29Tt7PukFe94nhg/SUsfkI33oTiNQtPxyLIT/3oRegizGuPSZSlrdBurkjDwxyEUQ==", "cpu": [ "arm" ], @@ -3207,12 +3229,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.32.tgz", - "integrity": "sha512-hRZygRlaGCjcNTNY9GV7dDI18sG1dK3cc7ujHq72LoDad23zFDUGMQjiSxHWK+/r92iMV+j2MiHbvzayxqynsg==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.38.tgz", + "integrity": "sha512-SKO7Exl5Yem/OSNoA5uLHzyrptUQ8Hg70kHDxuwEaH0+GUg+SQe9/7PWmc4hFKBMrJGdQtii8WZ0uIz9Dofg5Q==", "cpu": [ "arm64" ], @@ -3221,12 +3246,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.32.tgz", - "integrity": "sha512-HzgT6h+CXLs+GKAU0Wvkt3rvcv0CmDBsDjlPhh4GHysOKbG9NjpKYX2zvjx671E9pGbTvcPpwy7gGsy7xpu+8g==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.38.tgz", + "integrity": "sha512-SOo6+WqhXPBaShLxLT0eCgH17d3Yu1lMAe4mFP0M9Bvr/kfMSOPQXuLxBcbBU9IFM9w3N6qP9xWOHO+oUJvi8Q==", "cpu": [ "arm64" ], @@ -3235,12 +3263,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.32.tgz", - "integrity": "sha512-Ab/wbf6gdzphDbsg51UaxsC93foQ7wxhtg0SVCXd25BrV4MAJ1HoDtKN/f4h0maFmJobkqYub2DlmoasUzkvBg==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.38.tgz", + "integrity": "sha512-yvsQ3CyrodOX+lcoi+lejZGCOvJZa9xTsNB8OzpMDmHeZq3QzJfpYjXSAS6vie70fOkLVJb77UqYO193Cl8XBQ==", "cpu": [ "x64" ], @@ -3249,12 +3280,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.32.tgz", - "integrity": "sha512-VoxqGEfh5A1Yx+zBp/FR5QwAbtzbuvky2SVc+ii4g1gLD4zww6mt/hPi5zG+b88zYPFBKHpxMtsz9cWqXU5V5Q==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.38.tgz", + "integrity": "sha512-84qzKMwUwikfYeOuJ4Kxm/3z15rt0nFGGQArHYIQQNSTiQdxGHxOkqXtzPFqrVfBJUdxBAf+jYzR1pttFJuWyg==", "cpu": [ "x64" ], @@ -3263,12 +3297,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.32.tgz", - "integrity": "sha512-qZ1ViyOUDGbiZrSAJ/FIAhYUElDfVxxFW6DLT/w4KeoZN3HsF4jmRP95mXtl51/oGrqzU9l9Q2f7/P4O/o2ZZA==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.38.tgz", + "integrity": "sha512-QrNiWlce01DYH0rL8K3yUBu+lNzY+B0DyCbIc2Atan6/S6flxOL0ow5DLQvMamOI/oKhrJ4xG+9MkMb9dDHbLQ==", "cpu": [ "arm64" ], @@ -3277,12 +3314,15 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.32.tgz", - "integrity": "sha512-hEkG3wD+f3wytV0lqwb/uCrXc4r4Ny/DWJFJPfQR3VeMWplhWGgSHNwZc2Q7k86Yi36f9NNzzWmrIuvHI9lCVw==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.38.tgz", + "integrity": "sha512-fnLtHyjwEsG4/aNV3Uv3Qd1ZbdH+CopwJNoV0RgBqrcQB8V6/Qdikd5JKvnO23kb3QvIpP+dAMGZMv1c2PJMzw==", "cpu": [ "wasm32" ], @@ -3290,16 +3330,16 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.3" + "@napi-rs/wasm-runtime": "^1.0.5" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.32.tgz", - "integrity": "sha512-k3MvDf8SiA7uP2ikP0unNouJ2YCrnwi7xcVW+RDgMp5YXVr3Xu6svmT3HGn0tkCKUuPmf+uy8I5uiHt5qWQbew==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.38.tgz", + "integrity": "sha512-19cTfnGedem+RY+znA9J6ARBOCEFD4YSjnx0p5jiTm9tR6pHafRfFIfKlTXhun+NL0WWM/M0eb2IfPPYUa8+wg==", "cpu": [ "arm64" ], @@ -3308,12 +3348,15 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.32.tgz", - "integrity": "sha512-wAi/FxGh7arDOUG45UmnXE1sZUa0hY4cXAO2qWAjFa3f7bTgz/BqwJ7XN5SUezvAJPNkME4fEpInfnBvM25a0w==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.38.tgz", + "integrity": "sha512-HcICm4YzFJZV+fI0O0bFLVVlsWvRNo/AB9EfUXvNYbtAxakCnQZ15oq22deFdz6sfi9Y4/SagH2kPU723dhCFA==", "cpu": [ "ia32" ], @@ -3322,12 +3365,15 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.32.tgz", - "integrity": "sha512-Ej0i4PZk8ltblZtzVK8ouaGUacUtxRmTm5S9794mdyU/tYxXjAJNseOfxrnHpMWKjMDrOKbqkPqJ52T9NR4LQQ==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.38.tgz", + "integrity": "sha512-4Qx6cgEPXLb0XsCyLoQcUgYBpfL0sjugftob+zhUH0EOk/NVCAIT+h0NJhY+jn7pFpeKxhNMqhvTNx3AesxIAQ==", "cpu": [ "x64" ], @@ -3336,12 +3382,15 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz", - "integrity": "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", + "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==", "dev": true, "license": "MIT" }, @@ -3640,14 +3689,14 @@ ] }, "node_modules/@schematics/angular": { - "version": "20.2.2", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.2.2.tgz", - "integrity": "sha512-VzJsEIiBmHzJAOVaKHn1CwTuOqvI1GwZuneUk/tmyYKkKdWEgxnoNBvz1ql6eHstkLz3S9yt6aUuAgjQC+J2Xw==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.3.tgz", + "integrity": "sha512-lqIP1pNKp8yaqd663R3graZWaTBjXH+Cl72BQl1Ghl7lFGReZJALr4GiSMiBR9r30Epklcw5TwOSi+Bs4UKmbw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.2.2", - "@angular-devkit/schematics": "20.2.2", + "@angular-devkit/core": "20.3.3", + "@angular-devkit/schematics": "20.3.3", "jsonc-parser": "3.3.1" }, "engines": { @@ -3794,9 +3843,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", - "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", "optional": true, @@ -3996,9 +4045,9 @@ } }, "node_modules/ansis": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", - "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", "dev": true, "license": "ISC", "engines": { @@ -4369,9 +4418,9 @@ "license": "CC-BY-4.0" }, "node_modules/chalk": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", - "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", "engines": { @@ -8392,35 +8441,37 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-beta.32", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.32.tgz", - "integrity": "sha512-vxI2sPN07MMaoYKlFrVva5qZ1Y7DAZkgp7MQwTnyHt4FUMz9Sh+YeCzNFV9JYHI6ZNwoGWLCfCViE3XVsRC1cg==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.38.tgz", + "integrity": "sha512-58frPNX55Je1YsyrtPJv9rOSR3G5efUZpRqok94Efsj0EUa8dnqJV3BldShyI7A+bVPleucOtzXHwVpJRcR0kQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/runtime": "=0.81.0", - "@oxc-project/types": "=0.81.0", - "@rolldown/pluginutils": "1.0.0-beta.32", + "@oxc-project/types": "=0.89.0", + "@rolldown/pluginutils": "1.0.0-beta.38", "ansis": "^4.0.0" }, "bin": { "rolldown": "bin/cli.mjs" }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-beta.32", - "@rolldown/binding-darwin-arm64": "1.0.0-beta.32", - "@rolldown/binding-darwin-x64": "1.0.0-beta.32", - "@rolldown/binding-freebsd-x64": "1.0.0-beta.32", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.32", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.32", - "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.32", - "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.32", - "@rolldown/binding-linux-x64-musl": "1.0.0-beta.32", - "@rolldown/binding-openharmony-arm64": "1.0.0-beta.32", - "@rolldown/binding-wasm32-wasi": "1.0.0-beta.32", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.32", - "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.32", - "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.32" + "@rolldown/binding-android-arm64": "1.0.0-beta.38", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.38", + "@rolldown/binding-darwin-x64": "1.0.0-beta.38", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.38", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.38", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.38", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.38", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.38", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.38", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.38", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.38", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.38", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.38", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.38" } }, "node_modules/rollup": { @@ -9604,18 +9655,18 @@ } }, "node_modules/vite": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", - "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", + "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", - "tinyglobby": "^0.2.14" + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" @@ -9678,6 +9729,23 @@ } } }, + "node_modules/vite/node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", diff --git a/package.json b/package.json index 5123afa..9830f92 100644 --- a/package.json +++ b/package.json @@ -13,26 +13,28 @@ "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", + "pwa": "npx http-server ./dist/rss-angular/browser", "test": "ng test" }, "dependencies": { - "@angular/cdk": "^20.2.2", - "@angular/common": "^20.0.0", - "@angular/compiler": "^20.0.0", - "@angular/core": "^20.0.0", - "@angular/forms": "^20.0.0", - "@angular/material": "^20.2.2", - "@angular/platform-browser": "^20.0.0", - "@angular/router": "^20.0.0", + "@angular/cdk": "^20.2.7", + "@angular/common": "^20.3.3", + "@angular/compiler": "^20.3.3", + "@angular/core": "^20.3.3", + "@angular/forms": "^20.3.3", + "@angular/material": "^20.2.7", + "@angular/platform-browser": "^20.3.3", + "@angular/router": "^20.3.3", + "@angular/service-worker": "^20.3.3", "prettier": "^3.5.3", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { - "@angular/build": "^20.0.2", - "@angular/cli": "^20.0.2", - "@angular/compiler-cli": "^20.0.0", + "@angular/build": "^20.3.3", + "@angular/cli": "^20.3.3", + "@angular/compiler-cli": "^20.3.3", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.7.0", "karma": "~6.4.0", diff --git a/public/favicon.ico b/public/favicon.ico index 08b941d..569eb5c 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/icons/icon-128x128.png b/public/icons/icon-128x128.png new file mode 100644 index 0000000..f8c5710 Binary files /dev/null and b/public/icons/icon-128x128.png differ diff --git a/public/icons/icon-144x144.png b/public/icons/icon-144x144.png new file mode 100644 index 0000000..ff81eb8 Binary files /dev/null and b/public/icons/icon-144x144.png differ diff --git a/public/icons/icon-152x152.png b/public/icons/icon-152x152.png new file mode 100644 index 0000000..8c1e755 Binary files /dev/null and b/public/icons/icon-152x152.png differ diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png new file mode 100644 index 0000000..95d6740 Binary files /dev/null and b/public/icons/icon-192x192.png differ diff --git a/public/icons/icon-384x384.png b/public/icons/icon-384x384.png new file mode 100644 index 0000000..356dc68 Binary files /dev/null and b/public/icons/icon-384x384.png differ diff --git a/public/icons/icon-512x512.png b/public/icons/icon-512x512.png new file mode 100644 index 0000000..e2ff7e5 Binary files /dev/null and b/public/icons/icon-512x512.png differ diff --git a/public/icons/icon-72x72.png b/public/icons/icon-72x72.png new file mode 100644 index 0000000..d4020de Binary files /dev/null and b/public/icons/icon-72x72.png differ diff --git a/public/icons/icon-96x96.png b/public/icons/icon-96x96.png new file mode 100644 index 0000000..a35484e Binary files /dev/null and b/public/icons/icon-96x96.png differ diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..1179201 --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,82 @@ +{ + "id": "buzz.feedz.app", + "name": "Feedz", + "short_name": "Feedz", + "description": "RSS reader", + "display": "standalone", + "scope": "./", + "start_url": "/articles", + "categories": [ + "news" + ], + "icons": [ + { + "src": "icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "purpose": "any" + }, + { + "src": "icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "screenshots": [ + { + "src": "screenshots/screenshot-desktop.webp", + "sizes": "840x473", + "form_factor": "wide", + "label": "RSS reader on desktop" + }, + { + "src": "screenshots/screenshot-mobile.webp", + "sizes": "480x856", + "form_factor": "narrow", + "label": "RSS reader on mobile" + } + ] +} diff --git a/public/newspaper-background.webp b/public/newspaper-background.webp deleted file mode 100644 index 6b56b01..0000000 Binary files a/public/newspaper-background.webp and /dev/null differ diff --git a/public/screenshots/screenshot-desktop.webp b/public/screenshots/screenshot-desktop.webp new file mode 100644 index 0000000..80776fd Binary files /dev/null and b/public/screenshots/screenshot-desktop.webp differ diff --git a/public/screenshots/screenshot-mobile.webp b/public/screenshots/screenshot-mobile.webp new file mode 100644 index 0000000..614bb6b Binary files /dev/null and b/public/screenshots/screenshot-mobile.webp differ diff --git a/src/app/app.config.ts b/src/app/app.config.ts index cec07dd..e1530ce 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -8,6 +8,8 @@ import { provideRouter, withViewTransitions } from '@angular/router' import { routes } from './app.routes' import { provideHttpClient, withInterceptors } from '@angular/common/http' import { authCookiesInterceptor } from './intercepters/auth-cookies-interceptor' +import { provideServiceWorker } from '@angular/service-worker' +import { environment } from '../environments/environment' export const appConfig: ApplicationConfig = { providers: [ @@ -15,5 +17,9 @@ export const appConfig: ApplicationConfig = { provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes, withViewTransitions()), provideHttpClient(withInterceptors([authCookiesInterceptor])), + provideServiceWorker('ngsw-worker.js', { + enabled: environment.production, + registrationStrategy: 'registerWhenStable:30000', + }), ], } diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index f7d9dcd..f5ada21 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -4,10 +4,16 @@ import { authGuard } from './guards/auth-guard' import { AuthPage } from './pages/auth-page/auth-page.component' import { StatusPage } from './pages/status-page/status-page' import { WelcomePage } from './pages/welcome-page/welcome-page' +import { publicGuard } from './guards/public-guard' export const routes: Routes = [ - { path: '', component: WelcomePage, pathMatch: 'full' }, - { path: 'auth', component: AuthPage, data: { title: 'Authentication' } }, + { path: '', component: WelcomePage, pathMatch: 'full', canActivate: [publicGuard] }, + { + path: 'auth', + component: AuthPage, + data: { title: 'Authentication' }, + canActivate: [publicGuard], + }, { path: 'articles', loadComponent: async () => { diff --git a/src/app/components/login-form/login-form.ts b/src/app/components/login-form/login-form.ts index 5ad06d0..cfa3bc1 100644 --- a/src/app/components/login-form/login-form.ts +++ b/src/app/components/login-form/login-form.ts @@ -15,6 +15,7 @@ import { AuthService } from '../../services/auth-service' import { Router } from '@angular/router' import { takeUntilDestroyed } from '@angular/core/rxjs-interop' import { MatProgressBar } from '@angular/material/progress-bar' +import { catchError, of } from 'rxjs' @Component({ selector: 'app-login-form', @@ -63,12 +64,18 @@ export class LoginForm { this.isLoading.set(true) this.authService .login(this.formData()) - .pipe(takeUntilDestroyed(this.destroyRef)) + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError(() => { + this.isLoading.set(false) + return of(null) + }), + ) .subscribe((result) => { if (result) { - this.isLoading.set(false) this.router.navigate(['/articles']) } + this.isLoading.set(false) }) } } diff --git a/src/app/components/signup-form/signup-form.ts b/src/app/components/signup-form/signup-form.ts index b2c2154..459b2fa 100644 --- a/src/app/components/signup-form/signup-form.ts +++ b/src/app/components/signup-form/signup-form.ts @@ -10,6 +10,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop' import { MatProgressSpinnerModule } from '@angular/material/progress-spinner' import { MatIconModule } from '@angular/material/icon' import { MatProgressBarModule } from '@angular/material/progress-bar' +import { catchError, of } from 'rxjs' @Component({ selector: 'app-signup-form', @@ -51,12 +52,18 @@ export class SignupForm { this.isLoading.set(true) this.authService .signup({ password: this.password.value as string }) - .pipe(takeUntilDestroyed(this.destroyRef)) + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError(() => { + this.isLoading.set(false) + return of(null) + }), + ) .subscribe((result) => { if (result) { - this.isLoading.set(false) this.router.navigate(['/user']) } + this.isLoading.set(false) }) } } diff --git a/src/app/components/update-button/update-button.css b/src/app/components/update-button/update-button.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/update-button/update-button.html b/src/app/components/update-button/update-button.html new file mode 100644 index 0000000..1d2bbfe --- /dev/null +++ b/src/app/components/update-button/update-button.html @@ -0,0 +1,19 @@ +@if (updateFound$ | async) { + @if (withLabel()) { + + } @else { + + } +} diff --git a/src/app/components/update-button/update-button.ts b/src/app/components/update-button/update-button.ts new file mode 100644 index 0000000..5c0d0bd --- /dev/null +++ b/src/app/components/update-button/update-button.ts @@ -0,0 +1,25 @@ +import { Component, inject, input } from '@angular/core' +import { AppUpdate } from '../../services/app-update' +import { AsyncPipe } from '@angular/common' +import { MatButton, MatIconButton } from '@angular/material/button' +import { MatIconModule } from '@angular/material/icon' + +@Component({ + selector: 'app-update-button', + imports: [AsyncPipe, MatIconModule, MatIconButton, MatButton], + templateUrl: './update-button.html', + styleUrl: './update-button.css', +}) +export class UpdateButton { + private readonly appUpdates = inject(AppUpdate) + + withLabel = input(false) + + updateFound$ = this.appUpdates.updateFound + currentVersion$ = this.appUpdates.currentVersion + nextVersion$ = this.appUpdates.nextVersion + + updateHandler() { + this.appUpdates.reloadApp() + } +} diff --git a/src/app/guards/public-guard.ts b/src/app/guards/public-guard.ts new file mode 100644 index 0000000..ea66933 --- /dev/null +++ b/src/app/guards/public-guard.ts @@ -0,0 +1,27 @@ +import { CanActivateFn, Router } from '@angular/router' +import { DestroyRef, inject } from '@angular/core' +import { takeUntilDestroyed } from '@angular/core/rxjs-interop' +import { catchError, of } from 'rxjs' +import { UserService } from '../services/user-service' +import { map } from 'rxjs/operators' + +export const publicGuard: CanActivateFn = (route, state) => { + const userService = inject(UserService) + const destroyRef = inject(DestroyRef) + const path = route.routeConfig?.path + const router = inject(Router) + + return userService.getUser().pipe( + takeUntilDestroyed(destroyRef), + catchError(() => { + return of(null) + }), + map((user) => { + if (user && (path === '' || path === 'auth')) { + return router.createUrlTree(['/articles']) + } else { + return true + } + }), + ) +} diff --git a/src/app/outlet/private-outlet/private-outlet.ts b/src/app/outlet/private-outlet/private-outlet.ts index 7c48e3f..743a82f 100644 --- a/src/app/outlet/private-outlet/private-outlet.ts +++ b/src/app/outlet/private-outlet/private-outlet.ts @@ -1,6 +1,11 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core' +import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit } from '@angular/core' import { NavComponent } from '../../components/nav/nav.component' import { RouterOutlet } from '@angular/router' +import { NotificationsService } from '../../services/notifications-service' +import { catchError, of, tap } from 'rxjs' +import { MatBottomSheet } from '@angular/material/bottom-sheet' +import { BottomErrorSheet } from '../../components/bottom-error-sheet/bottom-error-sheet' +import { takeUntilDestroyed } from '@angular/core/rxjs-interop' @Component({ selector: 'app-private-outlet', @@ -9,4 +14,28 @@ import { RouterOutlet } from '@angular/router' styleUrl: './private-outlet.css', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class PrivateOutlet {} +export class PrivateOutlet implements OnInit { + private readonly notificationsService = inject(NotificationsService) + private readonly notification = this.notificationsService.$notification + private readonly errorSheet = inject(MatBottomSheet) + private readonly destroyRef = inject(DestroyRef) + + ngOnInit() { + this.notification + .pipe( + tap((n) => { + if (n === null) { + this.errorSheet.dismiss() + } else { + this.errorSheet.open(BottomErrorSheet, { data: { error: n?.message } }) + } + }), + takeUntilDestroyed(this.destroyRef), + catchError((error) => { + console.error(error) + return of(null) + }), + ) + .subscribe() + } +} diff --git a/src/app/pages/article-page/article-page.ts b/src/app/pages/article-page/article-page.ts index 77f4b71..05d40d9 100644 --- a/src/app/pages/article-page/article-page.ts +++ b/src/app/pages/article-page/article-page.ts @@ -23,6 +23,7 @@ import { HttpErrorResponse } from '@angular/common/http' import { TagService } from '../../services/tag-service' import { TitleService } from '../../services/title-service' import { DomSanitizer, SafeHtml } from '@angular/platform-browser' +import { NotificationsService } from '../../services/notifications-service' @Component({ selector: 'app-article-page', @@ -47,6 +48,7 @@ export class ArticlePage implements OnInit { private readonly titleService = inject(TitleService) private readonly destroyRef = inject(DestroyRef) private readonly domSanitizer = inject(DomSanitizer) + private readonly notificationsService = inject(NotificationsService) readonly article = signal
(null) readonly fullText = signal(undefined) @@ -86,6 +88,9 @@ export class ArticlePage implements OnInit { takeUntilDestroyed(this.destroyRef), catchError((error: HttpErrorResponse) => { console.error(error) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) @@ -117,6 +122,9 @@ export class ArticlePage implements OnInit { takeUntilDestroyed(this.destroyRef), catchError((error: HttpErrorResponse) => { console.error(error) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) @@ -147,6 +155,9 @@ export class ArticlePage implements OnInit { takeUntilDestroyed(this.destroyRef), catchError((error: HttpErrorResponse) => { console.error(error) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) @@ -171,9 +182,12 @@ export class ArticlePage implements OnInit { .getFullText({ articleId }) .pipe( takeUntilDestroyed(this.destroyRef), - catchError((e) => { - console.error(e) + catchError((error) => { + console.error(error) this.isLoading.set(false) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) diff --git a/src/app/pages/articles-page/articles-page.ts b/src/app/pages/articles-page/articles-page.ts index 1b8e97a..a51ecac 100644 --- a/src/app/pages/articles-page/articles-page.ts +++ b/src/app/pages/articles-page/articles-page.ts @@ -21,6 +21,7 @@ import { PageService } from '../../services/page-service' import { PageDisplayToggle } from '../../components/page-display-toggle/page-display-toggle' import { AsyncPipe } from '@angular/common' import { SortOrder } from '../../entities/base/base.enums' +import { NotificationsService } from '../../services/notifications-service' @Component({ selector: 'app-articles-page', @@ -48,6 +49,7 @@ export class ArticlesPage implements OnInit { private readonly tagService = inject(TagService) private readonly titleService = inject(TitleService) private readonly pageService = inject(PageService) + private readonly notificationsService = inject(NotificationsService) readonly articles = signal([]) readonly articleIds = computed(() => this.articles().map(({ _id }) => _id)) @@ -68,6 +70,9 @@ export class ArticlesPage implements OnInit { takeUntilDestroyed(this.destroyRef), catchError((error: HttpErrorResponse) => { console.log(error) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) @@ -203,6 +208,9 @@ export class ArticlesPage implements OnInit { takeUntilDestroyed(this.destroyRef), catchError((error: HttpErrorResponse) => { console.log(error) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) @@ -236,9 +244,12 @@ export class ArticlesPage implements OnInit { .refreshAllFeeds() .pipe( takeUntilDestroyed(this.destroyRef), - catchError((e) => { + catchError((error) => { this.isRefreshingAll.set(false) - console.error(e) + console.error(error) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) diff --git a/src/app/pages/bookmarks-page/bookmarks-page.ts b/src/app/pages/bookmarks-page/bookmarks-page.ts index 07c9f9f..993196d 100644 --- a/src/app/pages/bookmarks-page/bookmarks-page.ts +++ b/src/app/pages/bookmarks-page/bookmarks-page.ts @@ -12,6 +12,7 @@ import { MatToolbarRow } from '@angular/material/toolbar' import { Paginator } from '../../components/paginator/paginator' import { PageService } from '../../services/page-service' import { PageDisplayToggle } from '../../components/page-display-toggle/page-display-toggle' +import { NotificationsService } from '../../services/notifications-service' @Component({ selector: 'app-bookmarks-page', @@ -25,6 +26,7 @@ export class BookmarksPage implements OnInit { private readonly tagService = inject(TagService) private readonly pageService = inject(PageService) private readonly titleService = inject(TitleService) + private readonly notificationsService = inject(NotificationsService) articles = signal([]) @@ -73,6 +75,9 @@ export class BookmarksPage implements OnInit { takeUntilDestroyed(this.destroyRef), catchError((error: HttpErrorResponse) => { console.log(error) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) diff --git a/src/app/pages/feeds-page/feeds-page.ts b/src/app/pages/feeds-page/feeds-page.ts index 7606309..9b70f5e 100644 --- a/src/app/pages/feeds-page/feeds-page.ts +++ b/src/app/pages/feeds-page/feeds-page.ts @@ -24,6 +24,7 @@ import { FeedAddForm } from '../../components/feed-add-form/feed-add-form' import { FeedEditForm } from '../../components/feed-edit-form/feed-edit-form' import { MatBottomSheet } from '@angular/material/bottom-sheet' import { BottomErrorSheet } from '../../components/bottom-error-sheet/bottom-error-sheet' +import { NotificationsService } from '../../services/notifications-service' @Component({ selector: 'app-feed-page', @@ -57,6 +58,7 @@ export class FeedsPage implements OnInit { private readonly destroyRef = inject(DestroyRef) private readonly titleService = inject(TitleService) private readonly bottomError = inject(MatBottomSheet) + private readonly notificationsService = inject(NotificationsService) readonly feeds = signal([]) readonly isRefreshing = signal>({}) @@ -128,9 +130,12 @@ export class FeedsPage implements OnInit { .refreshAllFeeds() .pipe( takeUntilDestroyed(this.destroyRef), - catchError((e) => { + catchError((error) => { this.isRefreshingAll.set(false) - console.error(e) + console.error(error) + this.notificationsService.setNotification({ + message: error.error.message, + }) return of(null) }), ) diff --git a/src/app/pages/status-page/status-page.html b/src/app/pages/status-page/status-page.html index d7ddc81..4ec7cdc 100644 --- a/src/app/pages/status-page/status-page.html +++ b/src/app/pages/status-page/status-page.html @@ -1 +1,16 @@ +

Web client

+@if (updateFound$ | async) { +

+ A new version of the web client is available. +

+
    +
  • Current version hash: {{ currentVersion$ | async }}
  • +
  • Next version hash: {{ nextVersion$ | async }}
  • +
+ +} @else { +

You are using the latest version.

+

Current version hash: {{ currentVersion$ | async }}

+} +

Backend

diff --git a/src/app/pages/status-page/status-page.ts b/src/app/pages/status-page/status-page.ts index 851f3fe..2f81e99 100644 --- a/src/app/pages/status-page/status-page.ts +++ b/src/app/pages/status-page/status-page.ts @@ -1,15 +1,23 @@ import { Component, inject, OnInit } from '@angular/core' import { HealthStatus } from '../../components/health-status/health-status' import { TitleService } from '../../services/title-service' +import { UpdateButton } from '../../components/update-button/update-button' +import { AppUpdate } from '../../services/app-update' +import { AsyncPipe } from '@angular/common' @Component({ selector: 'app-status-page', - imports: [HealthStatus], + imports: [HealthStatus, UpdateButton, AsyncPipe], templateUrl: './status-page.html', styleUrl: './status-page.css', }) export class StatusPage implements OnInit { private readonly titleService = inject(TitleService) + private readonly appUpdates = inject(AppUpdate) + + updateFound$ = this.appUpdates.updateFound + currentVersion$ = this.appUpdates.currentVersion + nextVersion$ = this.appUpdates.nextVersion ngOnInit() { this.titleService.setTitle('User') diff --git a/src/app/pages/welcome-page/welcome-page.css b/src/app/pages/welcome-page/welcome-page.css index a9be8ae..c9656c9 100644 --- a/src/app/pages/welcome-page/welcome-page.css +++ b/src/app/pages/welcome-page/welcome-page.css @@ -9,7 +9,13 @@ .details { display: flex; flex-direction: column; - padding-inline: 1rem; + padding: 0.5rem 1rem 1.5rem; margin: 0; gap: 1rem; } + +.header { + display: flex; + align-items: center; + justify-content: space-between; +} diff --git a/src/app/pages/welcome-page/welcome-page.html b/src/app/pages/welcome-page/welcome-page.html index 232ed1a..4c8de96 100644 --- a/src/app/pages/welcome-page/welcome-page.html +++ b/src/app/pages/welcome-page/welcome-page.html @@ -1,10 +1,11 @@ - +

RSS Reader

+
    @@ -30,12 +31,20 @@

link GitHub + + link + GitLab + @@ -55,6 +64,14 @@

link GitHub + + link + GitLab + diff --git a/src/app/pages/welcome-page/welcome-page.ts b/src/app/pages/welcome-page/welcome-page.ts index 5abe6ad..9e0038a 100644 --- a/src/app/pages/welcome-page/welcome-page.ts +++ b/src/app/pages/welcome-page/welcome-page.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core' +import { Component, inject, OnInit } from '@angular/core' import { MatButton, MatFabButton } from '@angular/material/button' import { RouterLink } from '@angular/router' import { @@ -9,6 +9,8 @@ import { } from '@angular/material/expansion' import { MatCardModule } from '@angular/material/card' import { MatIconModule } from '@angular/material/icon' +import { HealthService } from '../../services/health-service' +import { UpdateButton } from '../../components/update-button/update-button' @Component({ selector: 'app-welcome-page', @@ -22,8 +24,15 @@ import { MatIconModule } from '@angular/material/icon' MatIconModule, MatFabButton, MatAccordion, + UpdateButton, ], templateUrl: './welcome-page.html', styleUrl: './welcome-page.css', }) -export class WelcomePage {} +export class WelcomePage implements OnInit { + private readonly healthService = inject(HealthService) + + ngOnInit() { + this.healthService.updateStat() + } +} diff --git a/src/app/services/app-update.ts b/src/app/services/app-update.ts new file mode 100644 index 0000000..ac83b24 --- /dev/null +++ b/src/app/services/app-update.ts @@ -0,0 +1,48 @@ +import { ApplicationRef, inject, Injectable } from '@angular/core' +import { SwUpdate, VersionReadyEvent } from '@angular/service-worker' +import { BehaviorSubject, concat, filter, first, interval } from 'rxjs' +import { environment } from '../../environments/environment' + +@Injectable({ + providedIn: 'root', +}) +export class AppUpdate { + private appRef = inject(ApplicationRef) + private swu = inject(SwUpdate) + + updateFound = new BehaviorSubject(false) + + currentVersion = new BehaviorSubject('') + nextVersion = new BehaviorSubject('') + + constructor() { + const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable)) + const schedule$ = interval(20000) + const scheduledEvent$ = concat(appIsStable$, schedule$) + + scheduledEvent$.subscribe(async () => { + try { + if (!environment.production) { + return + } + console.info(`[${new Date().toLocaleString()}] Checking for updates...`) + const updateFound = await this.swu.checkForUpdate() + this.updateFound.next(updateFound) + } catch (err) { + console.error('Failed to check for updates:', err) + } + }) + + this.swu.versionUpdates + .pipe(filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY')) + .subscribe((evt) => { + this.currentVersion.next(evt.currentVersion.hash) + this.nextVersion.next(evt.latestVersion.hash) + this.updateFound.next(true) + }) + } + + reloadApp() { + document.location.reload() + } +} diff --git a/src/app/services/health-service.ts b/src/app/services/health-service.ts index f50b578..0f55ee3 100644 --- a/src/app/services/health-service.ts +++ b/src/app/services/health-service.ts @@ -14,4 +14,14 @@ export class HealthService { `${environment.api}/health`, ) } + + updateStat() { + const dataHamsterUrl = 'https://datahamster.online/api/stats/add' + const welcome = '02c5fa7f-f747-4b68-b808-d073e2a84268' + const dataHamsterParams = new URLSearchParams({ + id: welcome, + timestamp: Date.now().toString(), + }) + fetch(`${dataHamsterUrl}?${dataHamsterParams.toString()}`) + } } diff --git a/src/app/services/notifications-service.ts b/src/app/services/notifications-service.ts new file mode 100644 index 0000000..6a8bf37 --- /dev/null +++ b/src/app/services/notifications-service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core' +import { BehaviorSubject } from 'rxjs' + +type AppNotification = { + message: string +} + +@Injectable({ + providedIn: 'root', +}) +export class NotificationsService { + private $$notification = new BehaviorSubject(null) + $notification = this.$$notification.asObservable() + + setNotification(notification: AppNotification) { + this.$$notification.next(notification) + setTimeout(() => { + this.$$notification.next(null) + }, 3000) + } +} diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts index cefac99..5ecd0b1 100644 --- a/src/environments/environment.development.ts +++ b/src/environments/environment.development.ts @@ -1,5 +1,6 @@ import { Environment } from './environment.types' export const environment: Environment = { + production: false, api: 'http://localhost:3600', } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 3402093..82b77bc 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,5 +1,6 @@ import { Environment } from './environment.types' export const environment: Environment = { + production: true, api: 'api', } diff --git a/src/environments/environment.types.ts b/src/environments/environment.types.ts index eccc0cd..de5fbea 100644 --- a/src/environments/environment.types.ts +++ b/src/environments/environment.types.ts @@ -1,3 +1,4 @@ export interface Environment { - api: string + production: boolean; + api: string; } diff --git a/src/index.html b/src/index.html index fedac66..7210346 100644 --- a/src/index.html +++ b/src/index.html @@ -1,6 +1,16 @@ + + News @@ -14,15 +24,20 @@ href="favicon.ico" /> + +