From c4a5cfec49c54f00f13599dd082b1b7140b0ce87 Mon Sep 17 00:00:00 2001 From: "plebeius.eth" Date: Tue, 27 Feb 2024 17:58:08 +0100 Subject: [PATCH] perf: rewrite plebchan completely --- .gitignore | 145 +- capacitor.config.json | 6 - capacitor.config.ts | 10 + package.json | 92 +- public/index.html | 13 +- src/App.js | 193 - src/app.module.css | 7 + src/app.tsx | 15 + src/components/BoardAvatar.jsx | 17 - src/components/BoardSettings.jsx | 315 -- src/components/BoardStats.jsx | 74 - src/components/CatalogLoader.jsx | 49 - src/components/DisplayName.jsx | 9 - src/components/EditLabel.jsx | 67 - src/components/Embed.jsx | 284 - src/components/ForwardRefLink.jsx | 29 - src/components/ImageBanner.jsx | 41 - src/components/OfflineIndicator.jsx | 28 - src/components/PendingLabel.jsx | 18 - src/components/Post.jsx | 136 - src/components/PostLoader.jsx | 39 - src/components/PostOnHover.jsx | 303 -- src/components/StateLabel.jsx | 28 - src/components/VerifiedAuthor.jsx | 11 - src/components/modals/AdminListModal.jsx | 84 - src/components/modals/CaptchaModal.jsx | 207 - src/components/modals/CreateBoardModal.jsx | 231 - src/components/modals/EditModal.jsx | 71 - src/components/modals/ModerationModal.jsx | 221 - .../modals/OriginalCommentModal.jsx | 62 - src/components/modals/ReplyModal.jsx | 317 -- src/components/modals/SettingsModal.jsx | 763 --- src/components/styled/BoardStats.styled.jsx | 101 - src/components/styled/GlobalStyle.styled.jsx | 203 - src/components/styled/Toast.styled.jsx | 12 - .../styled/modals/AlertModal.styled.jsx | 74 - .../styled/modals/CaptchaModal.styled.jsx | 244 - .../styled/modals/CreateBoardModal.styled.jsx | 335 -- .../styled/modals/ModerationModal.styled.jsx | 388 -- .../styled/modals/ReplyModal.styled.jsx | 259 - .../styled/modals/SettingsModal.styled.jsx | 447 -- src/components/styled/views/Board.styled.jsx | 4281 --------------- .../styled/views/Catalog.styled.jsx | 420 -- src/components/styled/views/Home.styled.jsx | 458 -- src/components/styled/views/Thread.styled.jsx | 3463 ------------ src/components/views/All.jsx | 3559 ------------- src/components/views/AllCatalog.jsx | 1092 ---- src/components/views/Board.jsx | 4629 ----------------- src/components/views/Catalog.jsx | 1876 ------- src/components/views/Description.jsx | 770 --- src/components/views/Home.jsx | 397 -- src/components/views/NotFound.jsx | 78 - src/components/views/Pending.jsx | 750 --- src/components/views/Rules.jsx | 706 --- src/components/views/Subscriptions.jsx | 3572 ------------- src/components/views/SubscriptionsCatalog.jsx | 1095 ---- src/components/views/Thread.jsx | 3309 ------------ src/globals.d.ts | 8 + src/hooks/stores/useAnonModeStore.js | 17 - src/hooks/stores/useGeneralStore.js | 123 - src/hooks/useAnonMode.js | 29 - src/hooks/useAnonModeRef.js | 28 - src/hooks/useClickForm.js | 14 - src/hooks/useError.js | 52 - src/hooks/useFeedRows.js | 19 - src/hooks/useFeedStateString.js | 98 - src/hooks/useInfo.js | 43 - src/hooks/useStateString.js | 61 - src/hooks/useSuccess.js | 43 - src/hooks/useWindowWidth.js | 16 - src/index.css | 13 + src/index.js | 37 - src/index.tsx | 28 + src/modules.d.ts | 8 + ...tion.js => service-worker-registration.js} | 0 src/utils/countLinks.js | 19 - src/utils/findShortParentCid.js | 18 - src/utils/getCommentMediaInfo.js | 78 - src/utils/getDate.js | 35 - src/utils/getFormattedTime.js | 44 - src/utils/handleAddressClick.js | 38 - src/utils/handleImageClick.js | 12 - src/utils/handleQuoteClick.js | 53 - src/utils/handleQuoteHover.js | 42 - src/utils/handleShareClick.js | 26 - src/utils/handleStyleChange.js | 92 - src/utils/isValidUrl.js | 10 - src/utils/preloadImages.js | 8 - src/utils/removeHighlight.js | 9 - src/utils/showNewVersionToast.js | 29 - src/views/home/home.module.css | 3 + src/views/home/home.tsx | 7 + src/views/home/index.ts | 1 + tsconfig.json | 26 + yarn.lock | 4595 ++++++++-------- 95 files changed, 2658 insertions(+), 39527 deletions(-) delete mode 100644 capacitor.config.json create mode 100644 capacitor.config.ts delete mode 100755 src/App.js create mode 100644 src/app.module.css create mode 100644 src/app.tsx delete mode 100644 src/components/BoardAvatar.jsx delete mode 100644 src/components/BoardSettings.jsx delete mode 100644 src/components/BoardStats.jsx delete mode 100644 src/components/CatalogLoader.jsx delete mode 100644 src/components/DisplayName.jsx delete mode 100644 src/components/EditLabel.jsx delete mode 100644 src/components/Embed.jsx delete mode 100644 src/components/ForwardRefLink.jsx delete mode 100644 src/components/ImageBanner.jsx delete mode 100644 src/components/OfflineIndicator.jsx delete mode 100644 src/components/PendingLabel.jsx delete mode 100644 src/components/Post.jsx delete mode 100644 src/components/PostLoader.jsx delete mode 100644 src/components/PostOnHover.jsx delete mode 100644 src/components/StateLabel.jsx delete mode 100644 src/components/VerifiedAuthor.jsx delete mode 100644 src/components/modals/AdminListModal.jsx delete mode 100644 src/components/modals/CaptchaModal.jsx delete mode 100644 src/components/modals/CreateBoardModal.jsx delete mode 100644 src/components/modals/EditModal.jsx delete mode 100755 src/components/modals/ModerationModal.jsx delete mode 100644 src/components/modals/OriginalCommentModal.jsx delete mode 100755 src/components/modals/ReplyModal.jsx delete mode 100755 src/components/modals/SettingsModal.jsx delete mode 100644 src/components/styled/BoardStats.styled.jsx delete mode 100644 src/components/styled/GlobalStyle.styled.jsx delete mode 100644 src/components/styled/Toast.styled.jsx delete mode 100644 src/components/styled/modals/AlertModal.styled.jsx delete mode 100644 src/components/styled/modals/CaptchaModal.styled.jsx delete mode 100644 src/components/styled/modals/CreateBoardModal.styled.jsx delete mode 100644 src/components/styled/modals/ModerationModal.styled.jsx delete mode 100644 src/components/styled/modals/ReplyModal.styled.jsx delete mode 100644 src/components/styled/modals/SettingsModal.styled.jsx delete mode 100644 src/components/styled/views/Board.styled.jsx delete mode 100644 src/components/styled/views/Catalog.styled.jsx delete mode 100644 src/components/styled/views/Home.styled.jsx delete mode 100644 src/components/styled/views/Thread.styled.jsx delete mode 100755 src/components/views/All.jsx delete mode 100755 src/components/views/AllCatalog.jsx delete mode 100755 src/components/views/Board.jsx delete mode 100755 src/components/views/Catalog.jsx delete mode 100644 src/components/views/Description.jsx delete mode 100644 src/components/views/Home.jsx delete mode 100644 src/components/views/NotFound.jsx delete mode 100755 src/components/views/Pending.jsx delete mode 100644 src/components/views/Rules.jsx delete mode 100755 src/components/views/Subscriptions.jsx delete mode 100755 src/components/views/SubscriptionsCatalog.jsx delete mode 100755 src/components/views/Thread.jsx create mode 100644 src/globals.d.ts delete mode 100644 src/hooks/stores/useAnonModeStore.js delete mode 100644 src/hooks/stores/useGeneralStore.js delete mode 100644 src/hooks/useAnonMode.js delete mode 100644 src/hooks/useAnonModeRef.js delete mode 100644 src/hooks/useClickForm.js delete mode 100755 src/hooks/useError.js delete mode 100644 src/hooks/useFeedRows.js delete mode 100644 src/hooks/useFeedStateString.js delete mode 100644 src/hooks/useInfo.js delete mode 100644 src/hooks/useStateString.js delete mode 100755 src/hooks/useSuccess.js delete mode 100644 src/hooks/useWindowWidth.js create mode 100644 src/index.css delete mode 100644 src/index.js create mode 100644 src/index.tsx create mode 100644 src/modules.d.ts rename src/{serviceWorkerRegistration.js => service-worker-registration.js} (100%) delete mode 100644 src/utils/countLinks.js delete mode 100644 src/utils/findShortParentCid.js delete mode 100644 src/utils/getCommentMediaInfo.js delete mode 100644 src/utils/getDate.js delete mode 100644 src/utils/getFormattedTime.js delete mode 100644 src/utils/handleAddressClick.js delete mode 100644 src/utils/handleImageClick.js delete mode 100644 src/utils/handleQuoteClick.js delete mode 100644 src/utils/handleQuoteHover.js delete mode 100644 src/utils/handleShareClick.js delete mode 100644 src/utils/handleStyleChange.js delete mode 100644 src/utils/isValidUrl.js delete mode 100644 src/utils/preloadImages.js delete mode 100644 src/utils/removeHighlight.js delete mode 100644 src/utils/showNewVersionToast.js create mode 100644 src/views/home/home.module.css create mode 100644 src/views/home/home.tsx create mode 100644 src/views/home/index.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index d32530b1..bf2abdd0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,138 @@ -# scripts/deploy.sh env -.deploy-env - -# electron builder binaries -/bin - -# data created by plebbit-js +# plebbit temp files .plebbit +bin + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules @@ -17,11 +144,9 @@ # production /build -/dist # misc .DS_Store -.env .env.local .env.development.local .env.test.local @@ -30,3 +155,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +.vscode \ No newline at end of file diff --git a/capacitor.config.json b/capacitor.config.json deleted file mode 100644 index 33060a4e..00000000 --- a/capacitor.config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "appId": "plebchan.android", - "appName": "plebchan", - "webDir": "build", - "bundledWebRuntime": false -} diff --git a/capacitor.config.ts b/capacitor.config.ts new file mode 100644 index 00000000..5ea7c858 --- /dev/null +++ b/capacitor.config.ts @@ -0,0 +1,10 @@ +import { CapacitorConfig } from '@capacitor/cli'; + +const config: CapacitorConfig = { + appId: 'plebchan.android', + appName: 'plebchan', + webDir: 'build', + bundledWebRuntime: false +}; + +export default config; diff --git a/package.json b/package.json index a71d6a70..e99fb534 100644 --- a/package.json +++ b/package.json @@ -5,50 +5,40 @@ "author": "plebeius.eth", "private": true, "dependencies": { - "@babel/core": "7.21.3", - "@babel/plugin-syntax-flow": "7.18.6", - "@babel/plugin-transform-react-jsx": "7.21.0", - "@capacitor/android": "3.6.0", "@capacitor/app": "1.1.1", - "@capacitor/core": "3.6.0", - "@plebbit/plebbit-react-hooks": "https://github.com/plebbit/plebbit-react-hooks.git#4d667f7ad7a77cfd416184acffd6f561761ebc9b", - "@testing-library/dom": "9.0.1", + "@floating-ui/react": "0.26.1", + "@plebbit/plebbit-react-hooks": "https://github.com/plebbit/plebbit-react-hooks.git#a2d2e9d4dd66f71bfd55768fb30e6799a77ee197", "@testing-library/jest-dom": "5.14.1", - "@testing-library/react": "14.0.0", - "@testing-library/user-event": "14.4.3", - "@types/node": "18.15.5", - "@types/react": "18.0.30", - "babel-plugin-styled-components": "2.0.7", + "@testing-library/react": "13.0.0", + "@testing-library/user-event": "13.2.1", + "@types/jest": "29.5.5", + "@types/node": "20.8.2", + "@types/react": "18.2.25", + "@types/react-dom": "18.2.10", + "cross-env": "7.0.3", "electron-context-menu": "3.3.0", "electron-is-dev": "2.0.0", - "eslint": "8.36.0", - "eslint-config-react-app": "7.0.1", "ext-name": "5.0.0", + "i18next": "23.5.1", + "i18next-browser-languagedetector": "7.1.0", + "i18next-http-backend": "2.2.2", "json-stringify-pretty-compact": "4.0.0", "lodash": "4.17.21", - "mock-require": "3.0.3", - "postcss": "8.4.21", + "memoizee": "0.4.15", + "prettier": "3.0.3", "react": "18.2.0", - "react-confirm-alert": "3.0.6", - "react-content-loader": "6.2.1", "react-dom": "18.2.0", - "react-draggable": "4.4.5", - "react-helmet-async": "1.3.0", - "react-infinite-scroller": "1.2.6", - "react-is": "18.2.0", + "react-i18next": "13.2.2", "react-markdown": "8.0.6", - "react-modal": "3.16.1", - "react-router-dom": "6.9.0", + "react-router-dom": "6.16.0", "react-scripts": "5.0.1", - "react-toastify": "9.1.1", - "react-tooltip": "5.10.0", - "react-virtuoso": "4.5.0", + "react-virtuoso": "4.6.0", "rehype-sanitize": "5.0.1", - "remark-breaks": "3.0.2", - "styled-components": "5.3.9", + "remark-gfm": "3.0.1", + "remark-supersub": "1.0.0", "tcp-port-used": "1.0.2", - "web-vitals": "3.3.0", - "zustand": "4.3.6" + "typescript": "5.2.2", + "zustand": "4.4.3" }, "scripts": { "start": "react-scripts start", @@ -56,6 +46,7 @@ "build-netlify": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=true REACT_APP_COMMIT_REF=$COMMIT_REF react-scripts build", "test": "react-scripts test", "analyze-bundle": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=true react-scripts build && source-map-explorer 'build/static/js/*.js'", + "eject": "react-scripts eject", "electron": "yarn electron:before && electron .", "electron:no-delete-data": "yarn electron:before:download-ipfs && electron .", "electron:start": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron\"", @@ -67,7 +58,7 @@ "electron:before:download-ipfs": "node electron/download-ipfs", "electron:before:delete-data": "rimraf .plebbit", "android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'", - "prettier": "prettier {src,electron}/**/*.{js,jsx} --write", + "prettier": "prettier {src,electron}/**/*.{js,ts,tsx} --write", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" }, "eslintConfig": { @@ -90,6 +81,23 @@ "last 1 safari version" ] }, + "devDependencies": { + "@capacitor/android": "3.6.0", + "@capacitor/cli": "3.6.0", + "@capacitor/core": "3.6.0", + "@types/memoizee": "0.4.9", + "concurrently": "8.0.1", + "conventional-changelog-cli": "4.1.0", + "cordova-res": "0.15.4", + "cross-env": "7.0.3", + "cz-conventional-changelog": "3.3.0", + "decompress": "4.2.1", + "electron": "19.1.8", + "electron-builder": "23.0.9", + "husky": "4.3.8", + "lint-staged": "12.3.8", + "wait-on": "7.0.1" + }, "main": "electron/main.js", "build": { "appId": "plebchan.desktop", @@ -127,29 +135,11 @@ "category": "Network" } }, - "devDependencies": { - "@babel/plugin-proposal-private-property-in-object": "7.21.11", - "@capacitor/cli": "3.6.0", - "concurrently": "8.0.1", - "conventional-changelog-cli": "4.1.0", - "cordova-res": "0.15.4", - "cross-env": "7.0.3", - "cz-conventional-changelog": "3.3.0", - "decompress": "4.2.1", - "electron": "19.1.8", - "electron-builder": "23.0.9", - "husky": "4.3.8", - "lint-staged": "12.3.8", - "prettier": "2.3.2", - "source-map-explorer": "2.5.3", - "typescript": "5.0.2", - "wait-on": "7.0.1" - }, "resolutions": { "styled-components": "^5" }, "lint-staged": { - "{src,electron}/**/*.{js,jsx}": [ + "{src,electron}/**/*.{js,ts,tsx}": [ "prettier --write" ] }, diff --git a/public/index.html b/public/index.html index bedd85cc..a15a76fb 100644 --- a/public/index.html +++ b/public/index.html @@ -2,9 +2,9 @@ - + plebchan + diff --git a/src/App.js b/src/App.js deleted file mode 100755 index 02ffd8be..00000000 --- a/src/App.js +++ /dev/null @@ -1,193 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Route, Routes, useLocation } from 'react-router-dom'; -import { Helmet } from 'react-helmet-async'; -import 'react-tooltip/dist/react-tooltip.css'; -import 'react-toastify/dist/ReactToastify.css'; -import useGeneralStore from './hooks/stores/useGeneralStore'; -import { GlobalStyle } from './components/styled/GlobalStyle.styled'; -import { Toast } from './components/styled/Toast.styled'; -import All from './components/views/All'; -import AllCatalog from './components/views/AllCatalog'; -import Board from './components/views/Board'; -import Catalog from './components/views/Catalog'; -import Description from './components/views/Description'; -import Home from './components/views/Home'; -import NotFound from './components/views/NotFound'; -import Pending from './components/views/Pending'; -import Rules from './components/views/Rules'; -import Subscriptions from './components/views/Subscriptions'; -import SubscriptionsCatalog from './components/views/SubscriptionsCatalog'; -import Thread from './components/views/Thread'; -import CaptchaModal from './components/modals/CaptchaModal'; -import { importAll } from './components/ImageBanner'; -import preloadImages from './utils/preloadImages'; -import showNewVersionToast from './utils/showNewVersionToast'; -import useError from './hooks/useError'; -import useInfo from './hooks/useInfo'; -import useSuccess from './hooks/useSuccess'; -import packageJson from '../package.json'; -const commitRef = process?.env?.REACT_APP_COMMIT_REF || ''; - -export default function App() { - const { - bodyStyle, - setBodyStyle, - setDefaultSubplebbits, - setDefaultNsfwSubplebbits, - isCaptchaOpen, - setIsCaptchaOpen, - setIsSettingsOpen, - selectedStyle, - setShowPostForm, - setShowPostFormLink, - } = useGeneralStore((state) => state); - - const location = useLocation(); - const isElectron = window.electron && window.electron.isElectron; - - useEffect(() => { - if (location.state?.scrollToTop) { - window.scrollTo(0, 0); - } - }, [location]); - - const [, setNewErrorMessage] = useError(); - const [, setNewSuccessMessage] = useSuccess(); - const [, setNewInfoMessage] = useInfo(); - - useEffect(() => { - const successToast = localStorage.getItem('successToast'); - const errorToast = localStorage.getItem('errorToast'); - if (successToast) { - setNewSuccessMessage(successToast); - localStorage.removeItem('successToast'); - } else if (errorToast) { - setNewErrorMessage(errorToast); - localStorage.removeItem('errorToast'); - } else { - return; - } - }, [setNewErrorMessage, setNewSuccessMessage]); - - // TODO: delete this code and toast when v1.0.0 is released (minor updates expected) - let hasRun = useRef(false); - - useEffect(() => { - if (!hasRun.current) { - showNewVersionToast(setNewInfoMessage, isElectron, packageJson, commitRef); - hasRun.current = true; - } - }, [setNewInfoMessage, isElectron]); - - // preload banners - useEffect(() => { - const loadImages = async () => { - const images = await importAll(require.context('../public/assets', true, /\.(png|jpe?g|svg)$/)); - const imageUrls = images.map((image) => image.default); - preloadImages(imageUrls); - }; - - loadImages(); - }, []); - - // fetch default subplebbits - useEffect(() => { - let didCancel = false; - fetch('https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/multisub.json', { cache: 'no-cache' }) - .then((res) => res.json()) - .then((res) => { - if (!didCancel) { - setDefaultSubplebbits(res.subplebbits); - } - }); - return () => { - didCancel = true; - }; - }, [setDefaultSubplebbits]); - - // fetch default NSFW subplebbits - useEffect(() => { - let didCancel = false; - fetch('https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits-nsfw.json', { cache: 'no-cache' }) - .then((res) => res.json()) - .then((res) => { - if (!didCancel) { - setDefaultNsfwSubplebbits(res); - } - }); - return () => { - didCancel = true; - }; - }, [setDefaultNsfwSubplebbits]); - - // handle nested routes - useEffect(() => { - const path = location.pathname; - if (path.endsWith('/post')) { - setShowPostFormLink(false); - setShowPostForm(true); - } else { - setShowPostFormLink(true); - setShowPostForm(false); - } - - if (path.endsWith('/settings')) { - setIsSettingsOpen(true); - } else { - setIsSettingsOpen(false); - } - }, [location.pathname, setIsSettingsOpen, setShowPostForm, setShowPostFormLink]); - - return ( -
- - - - - - - - - - - } /> - }> - } /> - } /> - - }> - } /> - } /> - - }> - } /> - } /> - - }> - } /> - - }> - } /> - - }> - } /> - - }> - } /> - - }> - } /> - - }> - } /> - - }> - } /> - - } /> - - - setIsCaptchaOpen(false)} /> -
- ); -} diff --git a/src/app.module.css b/src/app.module.css new file mode 100644 index 00000000..fcf990b9 --- /dev/null +++ b/src/app.module.css @@ -0,0 +1,7 @@ +.app { + z-index: 1; + min-height: 100%; + overflow-y: hidden; + overflow-x: hidden; + min-height: 100vh; +} diff --git a/src/app.tsx b/src/app.tsx new file mode 100644 index 00000000..5609356f --- /dev/null +++ b/src/app.tsx @@ -0,0 +1,15 @@ +import { Route, Routes } from 'react-router-dom'; +import Home from './views/home'; +import styles from './app.module.css'; + +const App = () => { + return ( +
+ + } /> + +
+ ); +}; + +export default App; diff --git a/src/components/BoardAvatar.jsx b/src/components/BoardAvatar.jsx deleted file mode 100644 index bc1f5010..00000000 --- a/src/components/BoardAvatar.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useSubplebbit } from '@plebbit/plebbit-react-hooks'; - -const BoardAvatar = ({ address }) => { - const [avatarUrl, setAvatarUrl] = useState('assets/plebchan.png'); - const subplebbit = useSubplebbit({ subplebbitAddress: address }); - - useEffect(() => { - if (subplebbit.suggested?.avatarUrl) { - setAvatarUrl(subplebbit.suggested?.avatarUrl); - } - }, [subplebbit.suggested?.avatarUrl, subplebbit]); - - return board avatar; -}; - -export default BoardAvatar; diff --git a/src/components/BoardSettings.jsx b/src/components/BoardSettings.jsx deleted file mode 100644 index 49055b29..00000000 --- a/src/components/BoardSettings.jsx +++ /dev/null @@ -1,315 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Link } from 'react-router-dom'; -import { usePublishSubplebbitEdit } from '@plebbit/plebbit-react-hooks'; -import { StyledModal } from './styled/modals/ModerationModal.styled'; -import useError from '../hooks/useError'; -import useSuccess from '../hooks/useSuccess'; -import useGeneralStore from '../hooks/stores/useGeneralStore'; - -const BoardSettings = ({ subplebbit }) => { - const { setCaptchaResponse, setChallengesArray, setIsCaptchaOpen, setResolveCaptchaPromise, selectedAddress, selectedStyle } = useGeneralStore((state) => state); - - const allowedSettings = { - address: subplebbit.address, - apiUrl: subplebbit.apiUrl, - description: subplebbit.description, - pubsubTopic: subplebbit.pubsubTopic, - settings: { - fetchThumbnailUrls: subplebbit.settings?.fetchThumbnailUrls, - fetchThumbnailUrlsProxyUrl: subplebbit.settings?.fetchThumbnailUrlsProxyUrl, - }, - roles: subplebbit.roles, - rules: subplebbit.rules, - suggested: { - avatarUrl: subplebbit.suggested?.avatarUrl, - backgroundUrl: subplebbit.suggested?.backgroundUrl, - bannerUrl: subplebbit.suggested?.bannerUrl, - language: subplebbit.suggested?.language, - primaryColor: subplebbit.suggested?.primaryColor, - secondaryColor: subplebbit.suggested?.secondaryColor, - }, - title: subplebbit.title, - }; - - const generateSettingsFromSubplebbit = (subplebbitData) => ({ - address: subplebbitData.address, - apiUrl: subplebbitData.apiUrl, - description: subplebbitData.description, - pubsubTopic: subplebbitData.pubsubTopic, - settings: { - fetchThumbnailUrls: subplebbitData.settings?.fetchThumbnailUrls, - fetchThumbnailUrlsProxyUrl: subplebbitData.settings?.fetchThumbnailUrlsProxyUrl, - }, - roles: subplebbitData.roles, - rules: subplebbitData.rules, - suggested: { - avatarUrl: subplebbitData.suggested?.avatarUrl, - backgroundUrl: subplebbitData.suggested?.backgroundUrl, - bannerUrl: subplebbitData.suggested?.bannerUrl, - language: subplebbitData.suggested?.language, - primaryColor: subplebbitData.suggested?.primaryColor, - secondaryColor: subplebbitData.suggested?.secondaryColor, - }, - title: subplebbitData.title, - }); - - const initialSettings = generateSettingsFromSubplebbit(subplebbit); - const [isModalOpen, setIsModalOpen] = useState(false); - const [boardSettingsJson, setBoardSettingsJson] = useState(JSON.stringify(initialSettings, null, 2)); - const [triggerPublilshSubplebbitEdit, setTriggerPublishCommentEdit] = useState(false); - - const [, setNewErrorMessage] = useError(); - const [, setNewSuccessMessage] = useSuccess(); - - const getDifferences = (oldObj, newObj) => { - let differences = {}; - - for (let key in oldObj) { - if (typeof oldObj[key] === 'object' && oldObj[key] !== null) { - const nestedDifferences = getDifferences(oldObj[key], newObj[key] || {}); - if (Object.keys(nestedDifferences).length > 0) { - differences[key] = nestedDifferences; - } - } else if (oldObj[key] !== newObj[key]) { - differences[key] = newObj[key]; - } - } - - for (let key in newObj) { - if (!oldObj.hasOwnProperty(key)) { - differences[key] = newObj[key]; - } - } - - return differences; - }; - - const isInitialMount = useRef(true); - - useEffect(() => { - if (isInitialMount.current) { - setBoardSettingsJson(JSON.stringify(generateSettingsFromSubplebbit(subplebbit), null, 2)); - isInitialMount.current = false; - } - }, [subplebbit]); - - function validateSettings(updatedSettings, allowedSettings) { - for (let key in updatedSettings) { - if (!allowedSettings.hasOwnProperty(key) && !initialSettings.hasOwnProperty(key)) { - throw new Error(`Unexpected setting: ${key}`); - } - - if (typeof updatedSettings[key] === 'object' && updatedSettings[key] !== null && !Array.isArray(updatedSettings[key])) { - if (typeof allowedSettings[key] !== 'object' || allowedSettings[key] === null || Array.isArray(allowedSettings[key])) { - throw new Error(`Expected ${key} to be an object in allowedSettings`); - } - validateSettings(updatedSettings[key], allowedSettings[key]); - } - } - } - - const onChallenge = async (challenges, subplebbitEdit) => { - let challengeAnswers = []; - - try { - challengeAnswers = await getChallengeAnswersFromUser(challenges); - } catch (error) { - setNewErrorMessage(error.message); - console.log(error); - } - if (challengeAnswers) { - await subplebbitEdit.publishChallengeAnswers(challengeAnswers); - } - }; - - const onChallengeVerification = (challengeVerification) => { - if (challengeVerification.challengeSuccess === true) { - setNewSuccessMessage('Challenge Success'); - console.log('challenge success', challengeVerification); - } else if (challengeVerification.challengeSuccess === false) { - setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`); - console.log('challenge failed', challengeVerification); - } - }; - - const getChallengeAnswersFromUser = async (challenges) => { - setChallengesArray(challenges); - - return new Promise((resolve, reject) => { - const imageString = challenges?.challenges[0].challenge; - const imageSource = `data:image/png;base64,${imageString}`; - const challengeImg = new Image(); - challengeImg.src = imageSource; - - challengeImg.onload = () => { - setIsCaptchaOpen(true); - - const handleKeyDown = async (event) => { - if (event.key === 'Enter') { - const currentCaptchaResponse = useGeneralStore.getState().captchaResponse; - resolve(currentCaptchaResponse); - setIsCaptchaOpen(false); - document.removeEventListener('keydown', handleKeyDown); - event.preventDefault(); - } - }; - - setCaptchaResponse(''); - document.addEventListener('keydown', handleKeyDown); - - setResolveCaptchaPromise(resolve); - }; - - challengeImg.onerror = () => { - reject(setNewErrorMessage('Could not load challenges')); - }; - }); - }; - - const [editSubplebbitOptions, setEditSubplebbitOptions] = useState({ - subplebbitAddress: selectedAddress, - onChallenge, - onChallengeVerification, - onError: (error) => { - setNewErrorMessage(error.message); - console.log(error); - }, - }); - - const { publishSubplebbitEdit } = usePublishSubplebbitEdit(editSubplebbitOptions); - - useEffect(() => { - let isActive = true; - if (editSubplebbitOptions && triggerPublilshSubplebbitEdit) { - (async () => { - await publishSubplebbitEdit(editSubplebbitOptions); - if (isActive) { - setTriggerPublishCommentEdit(false); - } - })(); - } - - return () => { - isActive = false; - }; - }, [editSubplebbitOptions, publishSubplebbitEdit, triggerPublilshSubplebbitEdit]); - - const handleSaveChanges = async () => { - try { - const updatedSettings = JSON.parse(boardSettingsJson); - validateSettings(updatedSettings, allowedSettings); - const changes = getDifferences(initialSettings, updatedSettings); - if (Object.keys(changes).length > 0) { - setEditSubplebbitOptions((prevOptions) => ({ - ...prevOptions, - ...changes, - })); - setTriggerPublishCommentEdit(true); - } else { - setNewErrorMessage('No changes detected'); - } - } catch (error) { - setNewErrorMessage(`Error saving changes: ${error}`); - console.log(error); - } - }; - - const handleResetChanges = () => { - setBoardSettingsJson(JSON.stringify(initialSettings, null, 2)); - }; - - function generateSettingsList(settingsObj, parentKey = '') { - let result = []; - - for (let key in settingsObj) { - if (typeof settingsObj[key] === 'object' && settingsObj[key] !== null) { - const nestedItems = generateSettingsList(settingsObj[key], `${parentKey}${key}.`); - if (nestedItems.length > 1) { - result.push(`${parentKey}${key}: { ${nestedItems.join(', ')} }`); - } else { - result.push(...nestedItems); - } - } else { - result.push(`${parentKey}${key}`); - } - } - - return result; - } - - const possibleSettingsList = generateSettingsList(initialSettings); - - const handleCloseModal = () => { - setIsModalOpen(false); - setBoardSettingsJson(JSON.stringify(initialSettings, null, 2)); - }; - - const openModal = () => { - setIsModalOpen(true); - setBoardSettingsJson(JSON.stringify(generateSettingsFromSubplebbit(subplebbit), null, 2)); - }; - - return ( - <> - -
-
- Board Settings - - - -
-
-
- Allowed settings: - {`{ ${possibleSettingsList.join(', ')} }`} -
- API docs:  - - https://github.com/plebbit/plebbit-js#readme - -
-