From 0e6fd8409a3107c6cfe870e0023ce53416eb0a14 Mon Sep 17 00:00:00 2001 From: hzhang Date: Sun, 8 Dec 2024 17:11:14 +0000 Subject: [PATCH] improve: use react-query for caching --- BuildConfig.sh | 2 +- package-lock.json | 220 ++++++++++++++++++- package.json | 1 + src/AuthProvider.js | 55 +++-- src/components/Markdowns/MarkdownContent.js | 45 ++-- src/components/Markdowns/MarkdownEditor.js | 70 +++--- src/components/Navigations/PathNode.js | 94 +++----- src/components/Navigations/SideNavigation.js | 65 ++---- src/components/PathManager.js | 119 +++++----- src/config.js | 2 - src/index.js | 51 ++++- src/utils/markdown-queries.js | 56 +++++ src/utils/path-queries.js | 90 ++++++++ src/utils/request-utils.js | 28 +++ src/utils/requestUtils.js | 95 -------- 15 files changed, 640 insertions(+), 353 deletions(-) create mode 100644 src/utils/markdown-queries.js create mode 100644 src/utils/path-queries.js create mode 100644 src/utils/request-utils.js delete mode 100644 src/utils/requestUtils.js diff --git a/BuildConfig.sh b/BuildConfig.sh index 5565e21..fc9dfae 100644 --- a/BuildConfig.sh +++ b/BuildConfig.sh @@ -1,5 +1,5 @@ #!/bin/bash -rm -f /app/config.js; +rm -f /app/src/config.js; if [ -z "$BACKEND_HOST" ]; then BACKEND_HOST="http://localhost:5000" fi diff --git a/package-lock.json b/package-lock.json index 4d74af9..53dc5c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", + "react-query": "^3.39.3", "react-router-dom": "^7.0.1", "react-syntax-highlighter": "^15.6.1", "rehype-katex": "^7.0.1", @@ -2824,12 +2825,25 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2882,6 +2896,15 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -2894,6 +2917,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/browserslist": { "version": "4.24.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", @@ -3171,6 +3209,11 @@ "node": ">= 0.8.0" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", @@ -3468,8 +3511,7 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "node_modules/devlop": { "version": "1.1.0", @@ -3752,9 +3794,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -3776,7 +3818,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -3791,6 +3833,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/cookie": { @@ -4057,6 +4103,11 @@ "node": ">= 0.6" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4108,6 +4159,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -4821,11 +4892,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/inline-style-parser": { "version": "0.2.4", @@ -5066,6 +5146,11 @@ "node": ">= 10.13.0" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5241,6 +5326,15 @@ "yallist": "^3.0.2" } }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/mdast-util-from-markdown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", @@ -5929,6 +6023,11 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -5968,6 +6067,17 @@ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5987,6 +6097,14 @@ "multicast-dns": "cli.js" } }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -6085,6 +6203,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -6123,6 +6246,14 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/open": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", @@ -6278,6 +6409,14 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6294,9 +6433,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, "node_modules/picocolors": { @@ -6595,6 +6734,31 @@ "react": ">=18" } }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.1.tgz", @@ -6947,6 +7111,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -7022,6 +7191,21 @@ "node": ">= 4" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -7959,6 +8143,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8475,6 +8668,11 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/package.json b/package.json index 30383ef..2137cb9 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", + "react-query": "^3.39.3", "react-router-dom": "^7.0.1", "react-syntax-highlighter": "^15.6.1", "rehype-katex": "^7.0.1", diff --git a/src/AuthProvider.js b/src/AuthProvider.js index abb1467..8844b90 100644 --- a/src/AuthProvider.js +++ b/src/AuthProvider.js @@ -1,8 +1,7 @@ -import React, {createContext, useEffect, useMemo, useState} from "react"; +import React, { createContext, useEffect, useMemo, useState } from "react"; import { UserManager } from "oidc-client-ts"; import config from "./config"; - export const AuthContext = createContext({ user: null, login: () => {}, @@ -13,8 +12,7 @@ export const AuthContext = createContext({ const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [roles, setRoles] = useState([]); - const userManager = - useMemo(() => new UserManager(config.OIDC_CONFIG), []); + const userManager = useMemo(() => new UserManager(config.OIDC_CONFIG), []); useEffect(() => { userManager.getUser() @@ -30,27 +28,54 @@ const AuthProvider = ({ children }) => { .then((newUser) => { setUser(newUser); localStorage.setItem("accessToken", newUser.access_token); - const clientRoles = - newUser?.profile?.resource_access?.[config.KC_CLIENT_ID]?.roles || []; + const clientRoles = newUser?.profile?.resource_access?.[config.KC_CLIENT_ID]?.roles || []; setRoles(clientRoles); }) .catch((err) => { console.error(err); - }) + logout(); + }); } + }) + .catch((err) => { + console.error(err); + logout(); }); - }, [userManager]); + + const onUserLoaded = (loadedUser) => { + setUser(loadedUser); + localStorage.setItem("accessToken", loadedUser.access_token); + const clientRoles = loadedUser?.profile?.resource_access?.[config.KC_CLIENT_ID]?.roles || []; + setRoles(clientRoles); + }; + + const onUserUnloaded = () => { + setUser(null); + setRoles([]); + localStorage.removeItem("accessToken"); + }; + + userManager.events.addUserLoaded(onUserLoaded); + userManager.events.addUserUnloaded(onUserUnloaded); + + return () => { + userManager.events.removeUserLoaded(onUserLoaded); + userManager.events.removeUserUnloaded(onUserUnloaded); + }; + }, [userManager]); const login = () => { userManager .signinRedirect() - .catch( - (err) => { - console.log(config); - console.log(err); - }); - } - const logout = () => userManager.signoutRedirect(); + .catch((err) => { + console.log(config); + console.log(err); + }); + }; + + const logout = () => { + userManager.signoutRedirect(); + }; return ( diff --git a/src/components/Markdowns/MarkdownContent.js b/src/components/Markdowns/MarkdownContent.js index c637825..0c39321 100644 --- a/src/components/Markdowns/MarkdownContent.js +++ b/src/components/Markdowns/MarkdownContent.js @@ -2,50 +2,37 @@ import React, { useEffect, useState } from "react"; import {Link, useParams} from "react-router-dom"; import "katex/dist/katex.min.css"; import "./MarkdownContent.css"; -import { fetch_ } from "../../utils/requestUtils"; -import config from "../../config"; import MarkdownView from "./MarkdownView"; import PermissionGuard from "../PermissionGuard"; +import {useMarkdown} from "../../utils/markdown-queries"; +import {usePath} from "../../utils/path-queries"; const MarkdownContent = () => { const { id } = useParams(); - const [content, setContent] = useState(null); - const [title, setTitle] = useState(null); - const [error, setError] = useState(null); const [indexTitle, setIndexTitle] = useState(null); + const {data: markdown, isLoading, error} = useMarkdown(id); + const {data: path, isFetching: isPathFetching} = usePath(markdown?.path_id); + useEffect(() => { - fetch_(`${config.BACKEND_HOST}/api/markdown/${id}`, {}, { - use_cache: true, - use_token: false - }) - .then((data) => { - setTitle(data.title); - setContent(data.content); - if(data.title === "index"){ - fetch_(`${config.BACKEND_HOST}/api/path/${data.path_id}`, {}, { - use_cache: true, - use_token: false - }).then((path_data) => { - setIndexTitle(path_data.id === 1 ? "Home" : path_data.name); - }).catch((err) => setError(error)); - } - }) - .catch((error) => setError(error)); - }, [id]); + if(markdown && markdown.title === "index" && path){ + setIndexTitle(path.id === 1 ? "Home" : path.name); + } + }, [markdown, path]); + + + if (isLoading || isPathFetching) { + return
Loading...
; + } if (error) { return
Error: {error.message || "Failed to load content"}
; } - if (!content) { - return
Loading...
; - } - return (
-

{title === "index" ? indexTitle : title}

+

{markdown.title === "index" ? indexTitle : markdown.title}

Edit @@ -53,7 +40,7 @@ const MarkdownContent = () => {
- +
); }; diff --git a/src/components/Markdowns/MarkdownEditor.js b/src/components/Markdowns/MarkdownEditor.js index b86f708..1385e66 100644 --- a/src/components/Markdowns/MarkdownEditor.js +++ b/src/components/Markdowns/MarkdownEditor.js @@ -3,10 +3,9 @@ import { AuthContext } from "../../AuthProvider"; import { useNavigate, useParams } from "react-router-dom"; import "katex/dist/katex.min.css"; import "./MarkdownEditor.css"; -import config from "../../config"; -import { fetch_ } from "../../utils/requestUtils"; import PathManager from "../PathManager"; import MarkdownView from "./MarkdownView"; +import { useMarkdown, useSaveMarkdown } from "../../utils/markdown-queries"; const MarkdownEditor = () => { const { roles } = useContext(AuthContext); @@ -15,55 +14,45 @@ const MarkdownEditor = () => { const [title, setTitle] = useState(""); const [content, setContent] = useState(""); const [pathId, setPathId] = useState(1); - const [loading, setLoading] = useState(false); + const {data: markdown, isLoading, error} = useMarkdown(id); + const saveMarkdown = useSaveMarkdown(); + + useEffect(() => { - if (id) { - setLoading(true); - fetch_(`${config.BACKEND_HOST}/api/markdown/${id}`, {}, { - use_cache: true, - use_token: false - }) - .then((data) => { - setTitle(data.title); - setContent(data.content); - setPathId(data.path_id); - }) - .catch((err) => { - console.error("Failed to load markdown", err); - }) - .finally(() => { - setLoading(false); - }); + if(markdown){ + setTitle(markdown.title); + setContent(markdown.content); + setPathId(markdown.path_id); } - }, [id]); + }, [markdown]); const handleSave = () => { - const url = id ? `${config.BACKEND_HOST}/api/markdown/${id}` : `${config.BACKEND_HOST}/api/markdown/`; - const method = id ? "PUT" : "POST"; - fetch_(url, { - method, - body: JSON.stringify({ title, content, path_id: pathId }), - }, { - use_cache: false, - use_token: true, - }).then((data) => { - if(data.error) - throw new Error(data.error.message); - navigate("/"); - }).catch((err) => { - console.error("Failed to load markdown", err); - }); + saveMarkdown.mutate( + {id, data: {title, content, path_id: pathId}}, + { + onSuccess: () => { + navigate("/"); + }, + onError: () => { + alert("Error saving markdown file"); + } + }); }; + const hasPermission = roles.includes("admin") || roles.includes("creator"); if (!hasPermission) { return
Permission Denied
; } - return loading ? ( -

loading

- ) : ( + if(isLoading) + return

Loading...

; + + if(error) + return

{error.message || "Failed to load markdown"}

; + + return (

{id ? "Edit Markdown" : "Create Markdown"}

@@ -114,8 +103,9 @@ const MarkdownEditor = () => { className="button is-primary" type="button" onClick={handleSave} + disabled={saveMarkdown.isLoading} > - Save + {saveMarkdown.isLoading ? "Saving..." : "Save"}
diff --git a/src/components/Navigations/PathNode.js b/src/components/Navigations/PathNode.js index 7bd0596..bcb5fb7 100644 --- a/src/components/Navigations/PathNode.js +++ b/src/components/Navigations/PathNode.js @@ -1,71 +1,51 @@ -import React, {useEffect, useState} from "react"; +import React, {useState} from "react"; import { Link } from "react-router-dom"; import PermissionGuard from "../PermissionGuard"; -import config from "../../config"; import "./PathNode.css"; -import { fetch_ } from "../../utils/requestUtils"; +import {useDeletePath, usePaths, useUpdatePath} from "../../utils/path-queries"; +import {useIndexMarkdown, useMarkdownsByPath} from "../../utils/markdown-queries"; -const PathNode = ({ path, isRoot = false, onDelete, onSave }) => { - const [children, setChildren] = useState([]); - const [markdowns, setMarkdowns] = useState([]); +const PathNode = ({ path, isRoot = false }) => { const [isExpanded, setIsExpanded] = useState(isRoot); - const [loading, setLoading] = useState(false); const [isEditing, setIsEditing] = useState(false); const [newName, setNewName] = useState(path.name); - const [indexMarkdownId, setIndexMarkdownId] = useState(null); - useEffect(() => { - fetch_(`${config.BACKEND_HOST}/api/markdown/get_index/${path.id}`, {}, { - use_cache: true, - use_token: false - }) - .then((data) => setIndexMarkdownId(data.id)) - .catch((error) => setIndexMarkdownId(null) ); - }, [path]); + const { data: childPaths, isLoading: isChildLoading, error: childError } = usePaths(path.id); + const { data: markdowns, isLoading: isMarkdownLoading, error: markdownError } = useMarkdownsByPath(path.id); + const deletePath = useDeletePath(); + const updatePath = useUpdatePath(); - const handleSave = () => { - if (onSave) { - onSave(path.id, newName); - setIsEditing(false); - } else { - console.error("onSave is not defined"); - } + const {data: indexMarkdown, isLoading: isIndexLoading, error: indexMarkdownError} = useIndexMarkdown(path.id); + + + const toggleExpand = () => { + setIsExpanded(!isExpanded); }; + const handleSave = () => { + console.log(`handleSave ${path.id}`); + updatePath.mutate({id: path.id, data: {name: newName}}, { + onsuccess: () => setIsEditing(false), + onError: err => alert("failed to update this path"), + }) + }; + const handleDelete = () => { + if(window.confirm("Are you sure?")) { + deletePath.mutate(path.id, { + onError: err => alert("failed to delete this path"), + }) + } + }; const handleEdit = () => { setIsEditing(true); }; - const toggleExpand = () => { - if (isRoot || isExpanded) { - setIsExpanded(false); - return; - } - setIsExpanded(true); - if (children.length === 0 && markdowns.length === 0) { - setLoading(true); - fetch_(`${config.BACKEND_HOST}/api/path/parent/${path.id}`, {}, { - use_cache: true, - use_token: false, - }) - .then((childPaths) => { - setChildren(childPaths); - return fetch_( - `${config.BACKEND_HOST}/api/markdown/by_path/${path.id}` - ); - }) - .then((markdownData) => { - const filteredMarkdowns = markdownData - .filter(markdown => markdown.title !== "index"); - setMarkdowns(filteredMarkdowns); - }) - .catch((error) => console.error(error)) - .finally(() => setLoading(false)); - } + if(childError || markdownError){ + return
  • Error...
  • ; + } - }; return (
  • @@ -83,8 +63,8 @@ const PathNode = ({ path, isRoot = false, onDelete, onSave }) => { ) : ( { - indexMarkdownId ? ( - + indexMarkdown ? ( + {path.name} ) : ( @@ -125,7 +105,7 @@ const PathNode = ({ path, isRoot = false, onDelete, onSave }) => {