From 0aac63d96889b81ff92569d3e643eeebde2bc38e Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 11 Nov 2024 22:37:49 +0500 Subject: [PATCH 01/27] feat: implemented WOT --- .env.example | 3 + .gitea/workflows/release-production.yaml | 1 + .gitea/workflows/release-staging.yaml | 1 + .../workflows/release-pages-production.yaml | 1 + src/components/ModsFilter.tsx | 70 +++- src/contexts/NDKContext.tsx | 2 +- src/hooks/useFilteredMods.ts | 30 +- src/layout/header.tsx | 2 + src/layout/index.tsx | 112 ++++++- src/pages/game.tsx | 6 +- src/pages/home.tsx | 7 +- src/pages/mods.tsx | 6 +- src/pages/profile.tsx | 4 +- src/pages/search.tsx | 6 +- src/pages/settings/preference.tsx | 317 +++++++++++------- src/store/index.ts | 4 +- src/store/reducers/wot.ts | 84 +++++ src/types/modsFilter.ts | 8 + src/utils/wot.ts | 147 ++++++++ src/vite-env.d.ts | 1 + 20 files changed, 686 insertions(+), 126 deletions(-) create mode 100644 src/store/reducers/wot.ts create mode 100644 src/utils/wot.ts diff --git a/.env.example b/.env.example index e6b55e6..4fade8b 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,9 @@ VITE_ADMIN_NPUBS= # A dedicated npub used for reporting mods, blogs, profile and etc. VITE_REPORTING_NPUB= +# A dedicated npub used for site WOT. +VITE_SITE_WOT_NPUB= + # if there's no featured image, or if the image breaks somewhere down the line, then it should default to this image VITE_FALLBACK_MOD_IMAGE=https://image.nostr.build/1fa7afd3489302c2da8957993ac0fd6c4308eedd4b1b95b24ecfabe3651b2183.png diff --git a/.gitea/workflows/release-production.yaml b/.gitea/workflows/release-production.yaml index 981e1be..25ac9ab 100644 --- a/.gitea/workflows/release-production.yaml +++ b/.gitea/workflows/release-production.yaml @@ -25,6 +25,7 @@ jobs: echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env + echo "VITE_SITE_WOT_NPUB"=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env echo "VITE_FALLBACK_MOD_IMAGE=${{ vars.VITE_FALLBACK_MOD_IMAGE }}" >> .env echo "VITE_FALLBACK_GAME_IMAGE=${{ vars.VITE_FALLBACK_GAME_IMAGE }}" >> .env cat .env diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 10e4bc4..01bc5ff 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -25,6 +25,7 @@ jobs: echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env + echo "VITE_SITE_WOT_NPUB"=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env echo "VITE_FALLBACK_MOD_IMAGE=${{ vars.VITE_FALLBACK_MOD_IMAGE }}" >> .env echo "VITE_FALLBACK_GAME_IMAGE=${{ vars.VITE_FALLBACK_GAME_IMAGE }}" >> .env cat .env diff --git a/.github/workflows/release-pages-production.yaml b/.github/workflows/release-pages-production.yaml index a80541b..10d4fde 100644 --- a/.github/workflows/release-pages-production.yaml +++ b/.github/workflows/release-pages-production.yaml @@ -32,6 +32,7 @@ jobs: run: | echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env + echo "VITE_SITE_WOT_NPUB"=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env echo "VITE_FALLBACK_GAME_IMAGE=${{ vars.VITE_FALLBACK_GAME_IMAGE }}" >> .env echo "VITE_FALLBACK_MOD_IMAGE=${{ vars.VITE_FALLBACK_MOD_IMAGE }}" >> .env echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env diff --git a/src/components/ModsFilter.tsx b/src/components/ModsFilter.tsx index 755ff95..ab83fa7 100644 --- a/src/components/ModsFilter.tsx +++ b/src/components/ModsFilter.tsx @@ -1,7 +1,13 @@ import { useAppSelector } from 'hooks' import React from 'react' import { Dispatch, SetStateAction } from 'react' -import { FilterOptions, ModeratedFilter, NSFWFilter, SortBy } from 'types' +import { + FilterOptions, + ModeratedFilter, + NSFWFilter, + SortBy, + WOTFilterOptions +} from 'types' type Props = { filterOptions: FilterOptions @@ -15,6 +21,7 @@ export const ModFilter = React.memo( return (
+ {/* sort filter options */}
+ + {/* moderation filter options */}
+ + {/* wot filter options */} +
+
+ +
+ {Object.values(WOTFilterOptions).map((item, index) => { + // when user is not logged in + if ( + (item === WOTFilterOptions.Site_And_Mine || + item === WOTFilterOptions.Mine_Only) && + !userState.auth + ) { + return null + } + + // when logged in user not admin + if (item === WOTFilterOptions.None) { + const isAdmin = + userState.user?.npub === + import.meta.env.VITE_REPORTING_NPUB + + const isOwnProfile = + filterOptions.author && + userState.auth && + userState.user?.pubkey === filterOptions.author + + if (!(isAdmin || isOwnProfile)) return null + } + + return ( +
+ setFilterOptions((prev) => ({ + ...prev, + wot: item + })) + } + > + {item} +
+ ) + })} +
+
+
+ + {/* nsfw filter options */}
+ + {/* source filter options */}
-
- - -
-
- - -
-
-
-
-

Not Safe For Work (NSFW)

-
-
- - -
-
-
-
-

Web of Trust (WoT) level

-
-

- This affects what posts you see, reactions, DMs, and - notifications. Learn more: Link -

-
- -

10

-
-
- - -
-
-
-
- + ) } diff --git a/src/store/index.ts b/src/store/index.ts index 13ced19..d9f509f 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,9 +1,11 @@ import { configureStore } from '@reduxjs/toolkit' import userReducer from './reducers/user' +import wotReducer from './reducers/wot' export const store = configureStore({ reducer: { - user: userReducer + user: userReducer, + wot: wotReducer } }) diff --git a/src/store/reducers/wot.ts b/src/store/reducers/wot.ts new file mode 100644 index 0000000..87a3881 --- /dev/null +++ b/src/store/reducers/wot.ts @@ -0,0 +1,84 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' + +export enum WOTStatus { + IDLE, // Not started + LOADING, // Currently loading + LOADED, // Successfully loaded + FAILED // Failed to load +} + +export interface IWOT { + siteWot: string[] + siteWotStatus: WOTStatus + siteWotLevel: number + userWot: string[] + userWotStatus: WOTStatus + userWotLevel: number +} + +const initialState: IWOT = { + siteWot: [], + siteWotStatus: WOTStatus.IDLE, + siteWotLevel: 3, + userWot: [], + userWotStatus: WOTStatus.IDLE, + userWotLevel: 3 +} + +export const wotSlice = createSlice({ + name: 'wot', + initialState, + reducers: { + setSiteWot(state, action: PayloadAction) { + state = { + ...state, + siteWot: action.payload, + siteWotStatus: WOTStatus.LOADED + } + return state + }, + setUserWot(state, action: PayloadAction) { + state = { + ...state, + userWot: action.payload, + userWotStatus: WOTStatus.LOADED + } + return state + }, + setSiteWotStatus(state, action: PayloadAction) { + return { + ...state, + siteWotStatus: action.payload + } + }, + setUserWotStatus(state, action: PayloadAction) { + return { + ...state, + userWotStatus: action.payload + } + }, + setSiteWotLevel(state, action: PayloadAction) { + return { + ...state, + siteWotLevel: action.payload + } + }, + setUserWotLevel(state, action: PayloadAction) { + return { + ...state, + userWotLevel: action.payload + } + } + } +}) + +export const { + setSiteWot, + setUserWot, + setSiteWotStatus, + setUserWotStatus, + setSiteWotLevel, + setUserWotLevel +} = wotSlice.actions + +export default wotSlice.reducer diff --git a/src/types/modsFilter.ts b/src/types/modsFilter.ts index a10542d..9334bfa 100644 --- a/src/types/modsFilter.ts +++ b/src/types/modsFilter.ts @@ -17,10 +17,18 @@ export enum ModeratedFilter { Unmoderated_Fully = 'Unmoderated Fully' } +export enum WOTFilterOptions { + Site_And_Mine = 'Site & Mine', + Site_Only = 'Site Only', + Mine_Only = 'Mine Only', + None = 'None' +} + export interface FilterOptions { sort: SortBy nsfw: NSFWFilter source: string moderated: ModeratedFilter + wot: WOTFilterOptions author?: string } diff --git a/src/utils/wot.ts b/src/utils/wot.ts new file mode 100644 index 0000000..f894f96 --- /dev/null +++ b/src/utils/wot.ts @@ -0,0 +1,147 @@ +import NDK, { + Hexpubkey, + NDKFilter, + NDKKind, + NDKSubscriptionCacheUsage, + NDKTag +} from '@nostr-dev-kit/ndk' +import { nip19 } from 'nostr-tools' + +interface UserRelations { + follows: Set + muted: Set +} + +type Network = Map + +export const calculateWot = async ( + pubkey: Hexpubkey, + ndk: NDK, + targetWOTScore: number +) => { + const network: Network = new Map() + const WOT = new Set() + + const userRelations = await findFollowsAndMuteUsers(pubkey, ndk) + network.set(pubkey, userRelations) + + // find the userRelations of every user in follow list + const follows = Array.from(userRelations.follows) + const promises = follows.map((user) => + findFollowsAndMuteUsers(user, ndk).then((userRelations) => { + network.set(user, userRelations) + }) + ) + await Promise.all(promises) + + // add all the following users to WOT + follows.forEach((f) => { + WOT.add(f) + }) + + // construct a list of users not being followed directly + const indirectFollows = new Set() + follows.forEach((hexpubkey) => { + const relations = network.get(hexpubkey) + if (relations) { + relations.follows.forEach((f) => { + indirectFollows.add(f) + }) + } + }) + + indirectFollows.forEach((targetHexPubkey) => { + // if any of the indirect followed user is in direct mute list + // we'll not include it in WOT + if (userRelations.muted.has(targetHexPubkey)) return + + const wotScore = calculateWoTScore(pubkey, targetHexPubkey, network) + if (wotScore >= targetWOTScore) { + WOT.add(targetHexPubkey) + } + }) + + return WOT +} + +export const calculateWoTScore = ( + user: Hexpubkey, + targetUser: Hexpubkey, + network: Network +): number => { + const userRelations = network.get(user) + if (!userRelations) return 0 + + let wotScore = 0 + + // Check each user followed + for (const followedUser of userRelations.follows) { + const followedUserRelations = network.get(followedUser) + if (!followedUserRelations) continue + + // Positive Score: +1 if followedUser also follows targetId + if (followedUserRelations.follows.has(targetUser)) { + wotScore += 1 + } + + // Negative Score: -1 if followedUser has muted targetId + if (followedUserRelations.muted.has(targetUser)) { + wotScore -= 1 + } + } + + return wotScore +} + +export const findFollowsAndMuteUsers = async ( + pubkey: string, + ndk: NDK +): Promise => { + const follows = new Set() + const muted = new Set() + + const filter: NDKFilter = { + kinds: [NDKKind.Contacts, NDKKind.MuteList], + authors: [pubkey] + } + + const events = await ndk.fetchEvents(filter, { + groupable: false, + closeOnEose: true, + cacheUsage: NDKSubscriptionCacheUsage.PARALLEL + }) + + events.forEach((event) => { + if (event.kind === NDKKind.Contacts) { + filterValidPTags(event.tags).forEach((f) => { + follows.add(f) + }) + } + }) + + events.forEach((event) => { + if (event.kind === NDKKind.MuteList) { + filterValidPTags(event.tags).forEach((f) => { + muted.add(f) + }) + } + }) + + return { + follows, + muted + } +} + +export const filterValidPTags = (tags: NDKTag[]) => + tags + .filter((t: NDKTag) => t[0] === 'p') + .map((t: NDKTag) => t[1]) + .filter((f: Hexpubkey) => { + try { + nip19.npubEncode(f) + return true + } catch { + return false + } + }) diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 1f3c47c..c80ac45 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -4,6 +4,7 @@ interface ImportMetaEnv { readonly VITE_APP_RELAY: string readonly VITE_ADMIN_NPUBS: string readonly VITE_REPORTING_NPUB: string + readonly VITE_SITE_WOT_NPUB: string readonly VITE_FALLBACK_MOD_IMAGE: string readonly VITE_FALLBACK_GAME_IMAGE: string // more env variables... From ad197fdd621d539834d9aaf9c6961f976a607a88 Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 11 Nov 2024 23:10:00 +0500 Subject: [PATCH 02/27] chore: necessary fixes after merging stagging branch --- src/components/ModsFilter.tsx | 4 ++-- src/utils/consts.ts | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/ModsFilter.tsx b/src/components/ModsFilter.tsx index 781518c..2a5384c 100644 --- a/src/components/ModsFilter.tsx +++ b/src/components/ModsFilter.tsx @@ -130,9 +130,9 @@ export const ModFilter = React.memo( import.meta.env.VITE_REPORTING_NPUB const isOwnProfile = - filterOptions.author && + author && userState.auth && - userState.user?.pubkey === filterOptions.author + userState.user?.pubkey === author if (!(isAdmin || isOwnProfile)) return null } diff --git a/src/utils/consts.ts b/src/utils/consts.ts index ff8e47b..611a3a8 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -1,8 +1,15 @@ -import { FilterOptions, SortBy, NSFWFilter, ModeratedFilter } from 'types' +import { + FilterOptions, + SortBy, + NSFWFilter, + ModeratedFilter, + WOTFilterOptions +} from 'types' export const DEFAULT_FILTER_OPTIONS: FilterOptions = { sort: SortBy.Latest, nsfw: NSFWFilter.Hide_NSFW, source: window.location.host, - moderated: ModeratedFilter.Moderated + moderated: ModeratedFilter.Moderated, + wot: WOTFilterOptions.Site_Only } From 33635194fca4157f7d822c02d8a41fab03490b76 Mon Sep 17 00:00:00 2001 From: enes Date: Fri, 15 Nov 2024 10:25:50 +0100 Subject: [PATCH 03/27] fix(workflow): remove extra quote mark and duplicate env var --- .gitea/workflows/release-production.yaml | 3 +-- .gitea/workflows/release-staging.yaml | 3 +-- .github/workflows/release-pages-production.yaml | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/release-production.yaml b/.gitea/workflows/release-production.yaml index 3f4b38b..4a19f05 100644 --- a/.gitea/workflows/release-production.yaml +++ b/.gitea/workflows/release-production.yaml @@ -25,11 +25,10 @@ jobs: echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env - echo "VITE_SITE_WOT_NPUB"=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env + echo "VITE_SITE_WOT_NPUB=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env echo "VITE_FALLBACK_MOD_IMAGE=${{ vars.VITE_FALLBACK_MOD_IMAGE }}" >> .env echo "VITE_FALLBACK_GAME_IMAGE=${{ vars.VITE_FALLBACK_GAME_IMAGE }}" >> .env echo "VITE_BLOG_NPUBS=${{ vars.VITE_BLOG_NPUBS }}" >> .env - echo "VITE_SITE_WOT_NPUB=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env cat .env - name: Create Build diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 41c5d3e..aa2c833 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -25,11 +25,10 @@ jobs: echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env - echo "VITE_SITE_WOT_NPUB"=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env + echo "VITE_SITE_WOT_NPUB=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env echo "VITE_FALLBACK_MOD_IMAGE=${{ vars.VITE_FALLBACK_MOD_IMAGE }}" >> .env echo "VITE_FALLBACK_GAME_IMAGE=${{ vars.VITE_FALLBACK_GAME_IMAGE }}" >> .env echo "VITE_BLOG_NPUBS=${{ vars.VITE_BLOG_NPUBS }}" >> .env - echo "VITE_SITE_WOT_NPUB=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env cat .env - name: Create Build diff --git a/.github/workflows/release-pages-production.yaml b/.github/workflows/release-pages-production.yaml index 84fed7e..0536394 100644 --- a/.github/workflows/release-pages-production.yaml +++ b/.github/workflows/release-pages-production.yaml @@ -32,12 +32,11 @@ jobs: run: | echo "VITE_APP_RELAY=${{ vars.VITE_APP_RELAY }}" >> .env echo "VITE_ADMIN_NPUBS=${{ vars.VITE_ADMIN_NPUBS }}" >> .env - echo "VITE_SITE_WOT_NPUB"=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env + echo "VITE_SITE_WOT_NPUB=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env echo "VITE_FALLBACK_GAME_IMAGE=${{ vars.VITE_FALLBACK_GAME_IMAGE }}" >> .env echo "VITE_FALLBACK_MOD_IMAGE=${{ vars.VITE_FALLBACK_MOD_IMAGE }}" >> .env echo "VITE_REPORTING_NPUB=${{ vars.VITE_REPORTING_NPUB }}" >> .env echo "VITE_BLOG_NPUBS=${{ vars.VITE_BLOG_NPUBS }}" >> .env - echo "VITE_SITE_WOT_NPUB=${{ vars.VITE_SITE_WOT_NPUB }}" >> .env cat .env - name: Build run: npm run build From 2de5cd52b618447a039c1e71a40d90181baa8898 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Sun, 17 Nov 2024 20:55:04 +0000 Subject: [PATCH 04/27] Update src/styles/post.css --- src/styles/post.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/styles/post.css b/src/styles/post.css index 746f823..03dc076 100644 --- a/src/styles/post.css +++ b/src/styles/post.css @@ -19,6 +19,7 @@ justify-content: center; align-items: center; padding-top: 56.25%; + border-bottom: solid 1px rgb(255 255 255 / 5%); } .IBMSMSMBSSPostTitle { @@ -28,6 +29,7 @@ display: flex; flex-direction: column; align-items: center; + color: rgb(255 255 255 / 85%); } .IBMSMSMBSSPostBody { @@ -38,6 +40,7 @@ align-items: center; position: relative; overflow: hidden; + color: rgb(255 255 255 / 85%); } .IBMSMSMBSSPostBody > div { @@ -237,4 +240,15 @@ background: #0000002e; border-radius: 6px; padding: 2px; +} + +.IBMSMSMBSSPostBody > div > div > p { + margin-bottom: 10px; +} + +.IBMSMSMBSSPostBody > * > h1, h2, h3, h4, h5, h6 { + margin: 15px 0 15px 0 !important; + border-bottom: solid 1px rgb(255 255 255 / 10%); + padding: 0px 0 10px 0; + line-height: 1.5 !important; } \ No newline at end of file From 56696129d67e5048677527f24f1d8ba22b906c11 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Sun, 17 Nov 2024 21:32:33 +0000 Subject: [PATCH 05/27] Update src/assets/games/Games_Steam3.csv --- src/assets/games/Games_Steam3.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/games/Games_Steam3.csv b/src/assets/games/Games_Steam3.csv index 9592d26..23043ab 100644 --- a/src/assets/games/Games_Steam3.csv +++ b/src/assets/games/Games_Steam3.csv @@ -24966,7 +24966,7 @@ Mystery of Island,, SEARCH ALL - POTIONS,, Sanctuary Saga Playtest,, Fantasy Grounds - Beastheart and Monstrous Companions,, -Dragon Ageā„¢: The Veilguard,, +Dragon Age: The Veilguard,,https://image.nostr.build/0ec48cc2ef08b3f09647c8403e29c5fd814dc3e4aeb2bb76add93928f3a867b0.jpg Transport INC - Map Pack,, Fantasy Grounds - Dungeon Crawl Classics #97: The Queen of Elfland's Son,, cyberpunkdreams: outside edges,, From 8f7a85cf0a73b1d32c5a71de6b8584768440f0bd Mon Sep 17 00:00:00 2001 From: freakoverse Date: Sun, 17 Nov 2024 21:44:47 +0000 Subject: [PATCH 06/27] Update src/styles/post.css --- src/styles/post.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/post.css b/src/styles/post.css index 03dc076..2109b53 100644 --- a/src/styles/post.css +++ b/src/styles/post.css @@ -246,7 +246,7 @@ margin-bottom: 10px; } -.IBMSMSMBSSPostBody > * > h1, h2, h3, h4, h5, h6 { +.IBMSMSMBSSPostBody > * > :is(h1, h2, h3, h4, h5, h6) { margin: 15px 0 15px 0 !important; border-bottom: solid 1px rgb(255 255 255 / 10%); padding: 0px 0 10px 0; From e0a3b3b286abf715bf78d63a9e6ab6a52058b850 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Sun, 17 Nov 2024 21:46:20 +0000 Subject: [PATCH 07/27] Update src/layout/header.tsx --- src/layout/header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/header.tsx b/src/layout/header.tsx index e21ba45..acf7b65 100644 --- a/src/layout/header.tsx +++ b/src/layout/header.tsx @@ -225,7 +225,7 @@ export const Header = () => { >
Date: Sun, 17 Nov 2024 21:47:27 +0000 Subject: [PATCH 08/27] Update src/layout/footer.tsx --- src/layout/footer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/footer.tsx b/src/layout/footer.tsx index a73aa3e..5ddedb3 100644 --- a/src/layout/footer.tsx +++ b/src/layout/footer.tsx @@ -16,7 +16,7 @@ export const Footer = () => { by  Freakoverse From 7a5128c8026fde9b7fb52b1fc864c9e9a7ef78b8 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Sun, 17 Nov 2024 21:51:57 +0000 Subject: [PATCH 09/27] Update src/layout/header.tsx --- src/layout/header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/header.tsx b/src/layout/header.tsx index acf7b65..0d0c571 100644 --- a/src/layout/header.tsx +++ b/src/layout/header.tsx @@ -225,7 +225,7 @@ export const Header = () => { > Date: Sun, 17 Nov 2024 21:55:52 +0000 Subject: [PATCH 10/27] Update src/styles/post.css --- src/styles/post.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/post.css b/src/styles/post.css index 2109b53..c2f3474 100644 --- a/src/styles/post.css +++ b/src/styles/post.css @@ -246,7 +246,7 @@ margin-bottom: 10px; } -.IBMSMSMBSSPostBody > * > :is(h1, h2, h3, h4, h5, h6) { +.IBMSMSMBSSPostBody > div > div > *:is(h1, h2, h3, h4, h5, h6) { margin: 15px 0 15px 0 !important; border-bottom: solid 1px rgb(255 255 255 / 10%); padding: 0px 0 10px 0; From 77c2e880f33e83bb4da0cdbe170812723b081395 Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 18 Nov 2024 12:27:43 +0500 Subject: [PATCH 11/27] fix: change default value for wotLevel --- src/pages/settings/preference.tsx | 25 ++++++++++++++++++++----- src/store/reducers/wot.ts | 4 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/pages/settings/preference.tsx b/src/pages/settings/preference.tsx index b3c9c79..82c84ee 100644 --- a/src/pages/settings/preference.tsx +++ b/src/pages/settings/preference.tsx @@ -10,12 +10,14 @@ import { log, LogType, now } from 'utils' // todo: use components from Input.tsx export const PreferencesSetting = () => { + const { ndk, fetchEventFromUserRelays, publish } = useNDKContext() const dispatch = useAppDispatch() - const [wotLevel, setWotLevel] = useState(3) - const [isSaving, setIsSaving] = useState(false) - const { ndk, fetchEventFromUserRelays, publish } = useNDKContext() const user = useAppSelector((state) => state.user.user) + const { userWotLevel } = useAppSelector((state) => state.wot) + + const [wotLevel, setWotLevel] = useState(userWotLevel) + const [isSaving, setIsSaving] = useState(false) useEffect(() => { if (user?.pubkey) { @@ -80,9 +82,22 @@ export const PreferencesSetting = () => { const ndkEvent = new NDKEvent(ndk, signedEvent) await publish(ndkEvent) - dispatch(setUserWotLevel(wotLevel)) + .then((publishedOnRelays) => { + toast.success( + `Preferences published to following relays: \n\n${publishedOnRelays.join( + '\n' + )}` + ) - setIsSaving(false) + dispatch(setUserWotLevel(wotLevel)) + }) + .catch((err) => { + console.error(err) + toast.error('Error: Failed to publish preferences!') + }) + .finally(() => { + setIsSaving(false) + }) } return ( diff --git a/src/store/reducers/wot.ts b/src/store/reducers/wot.ts index 87a3881..4288c65 100644 --- a/src/store/reducers/wot.ts +++ b/src/store/reducers/wot.ts @@ -19,10 +19,10 @@ export interface IWOT { const initialState: IWOT = { siteWot: [], siteWotStatus: WOTStatus.IDLE, - siteWotLevel: 3, + siteWotLevel: 0, userWot: [], userWotStatus: WOTStatus.IDLE, - userWotLevel: 3 + userWotLevel: 0 } export const wotSlice = createSlice({ From 79ef25cb3bcefd23fd078e6324b5087073fa6d69 Mon Sep 17 00:00:00 2001 From: freakoverse Date: Mon, 18 Nov 2024 09:15:36 +0000 Subject: [PATCH 12/27] Update src/assets/games/Games_Other.csv --- src/assets/games/Games_Other.csv | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/assets/games/Games_Other.csv b/src/assets/games/Games_Other.csv index d32146e..403b61f 100644 --- a/src/assets/games/Games_Other.csv +++ b/src/assets/games/Games_Other.csv @@ -1,5 +1,7 @@ -Game Name,16 by 9 image,Boxart image -(Unlisted Game),, -Minecraft,,https://image.nostr.build/b75b2d3a7855370230f2976567e2d5f913a567c57ac61adfb60c7e1102f05117.jpg -Vintage Story,,https://image.nostr.build/9efe683d339cc864032a99047ce26b2b5c19fab1ec4dcc6d4db96e2785c44eda.png -Yandere Simulator,,https://image.nostr.build/54ba56b752bb9d411cbdc1d249fa0cb74c6062a305bcd0a70ecacb61b8d50030.png \ No newline at end of file +Game Name,16 by 9 image,Boxart image +(Unlisted Game),, +Minecraft,,https://image.nostr.build/b75b2d3a7855370230f2976567e2d5f913a567c57ac61adfb60c7e1102f05117.jpg +Vintage Story,,https://image.nostr.build/9efe683d339cc864032a99047ce26b2b5c19fab1ec4dcc6d4db96e2785c44eda.png +Yandere Simulator,,https://image.nostr.build/54ba56b752bb9d411cbdc1d249fa0cb74c6062a305bcd0a70ecacb61b8d50030.png +Genshin Impact,,https://image.nostr.build/999fccf93cf16a2e0dd8e6f00595b0ab3b5cc6beff9fe4a52f64f427cce9aedd.jpg +Zenless Zone Zero,,https://image.nostr.build/4a9b9c2cbef619552d0c123f8794286f35710dc7ca1ca0010380a630883eb2ca.jpg \ No newline at end of file From 9a30eae749f88795ac5f841474a68e9e3f169a1f Mon Sep 17 00:00:00 2001 From: freakoverse Date: Mon, 18 Nov 2024 09:18:04 +0000 Subject: [PATCH 13/27] Update src/constants.ts --- src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.ts b/src/constants.ts index a654aab..56f1bdd 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -25,7 +25,7 @@ export const LANDING_PAGE_DATA = { 'naddr1qvzqqqr4gupzpa9lr76m4zlg88mscue3wvlrp8mcpq3txy0k8cqlnhy2hw6z37x4qqjrzcfc8qurjefn943xyen9956rywp595unjc3h94nxvwfexymxxcfnvdjxxlyq37c', 'naddr1qvzqqqr4gupzpa9lr76m4zlg88mscue3wvlrp8mcpq3txy0k8cqlnhy2hw6z37x4qqjryv3k8qenydpj94nrscmp956xgwtp94snydtz95ekgvphvfnxvvrzvyexzsvsz9y', 'naddr1qvzqqqr4gupzpa9lr76m4zlg88mscue3wvlrp8mcpq3txy0k8cqlnhy2hw6z37x4qq2kwjtwvahns3n0tf8j6kjxggkkz4mff499ge7xzsz', - 'naddr1qvzqqqr4gupzpa9lr76m4zlg88mscue3wvlrp8mcpq3txy0k8cqlnhy2hw6z37x4qq2573jhg9trsu6vgav9gnn4dffkzk2ww3yrjejnc2s' + 'naddr1qvzqqqr4gupzpa9lr76m4zlg88mscue3wvlrp8mcpq3txy0k8cqlnhy2hw6z37x4qqjrycf5vyunyd34943kydn9956rycmp943xydpc95cxge3cvguxgcmyxsmkyzpyj60' ] } // we use this object to check if a user has reacted positively or negatively to a post From 6e4e580402710d051c981e6a08735c019cf1b0cc Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 18 Nov 2024 15:33:55 +0500 Subject: [PATCH 14/27] fix: update wot filter to remove mine_only for non admin users --- src/components/ModsFilter.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/ModsFilter.tsx b/src/components/ModsFilter.tsx index 2a5384c..806fabe 100644 --- a/src/components/ModsFilter.tsx +++ b/src/components/ModsFilter.tsx @@ -116,15 +116,17 @@ export const ModFilter = React.memo( {Object.values(WOTFilterOptions).map((item, index) => { // when user is not logged in if ( - (item === WOTFilterOptions.Site_And_Mine || - item === WOTFilterOptions.Mine_Only) && + item === WOTFilterOptions.Site_And_Mine && !userState.auth ) { return null } // when logged in user not admin - if (item === WOTFilterOptions.None) { + if ( + item === WOTFilterOptions.None || + item === WOTFilterOptions.Mine_Only + ) { const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB From 870262fcdc2726846841d25e9d256f140a1ab314 Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 18 Nov 2024 11:52:42 +0100 Subject: [PATCH 15/27] fix(filters): merge defaults and stored value --- src/hooks/useLocalStorage.tsx | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/hooks/useLocalStorage.tsx b/src/hooks/useLocalStorage.tsx index 8dc9893..4d1eac2 100644 --- a/src/hooks/useLocalStorage.tsx +++ b/src/hooks/useLocalStorage.tsx @@ -10,11 +10,29 @@ const useLocalStorageSubscribe = (callback: () => void) => { return () => window.removeEventListener('storage', callback) } +function mergeWithInitialValue(storedValue: T, initialValue: T): T { + if (typeof storedValue === 'object' && storedValue !== null) { + return { ...initialValue, ...storedValue } + } + return storedValue +} + export function useLocalStorage( key: string, initialValue: T ): [T, React.Dispatch>] { - const getSnapshot = () => getLocalStorageItem(key, initialValue) + const getSnapshot = () => { + // Get the stored value + const storedValue = getLocalStorageItem(key, initialValue) + + // Parse the value + const parsedStoredValue = JSON.parse(storedValue) + + // Merge the default and the stored in case some of the required fields are missing + return JSON.stringify( + mergeWithInitialValue(parsedStoredValue, initialValue) + ) + } const data = React.useSyncExternalStore(useLocalStorageSubscribe, getSnapshot) @@ -35,7 +53,7 @@ export function useLocalStorage( console.warn(e) } }, - [key, data] + [data, key] ) React.useEffect(() => { From 4f8cac6eeee56cc0ba0d34621f700648a9e4fa77 Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 18 Nov 2024 23:21:05 +0500 Subject: [PATCH 16/27] fix: improve wot logic Improve data structure for storing WoT Also improve WoT calculation logic --- src/hooks/useFilteredMods.ts | 20 ++++++-- src/layout/header.tsx | 34 +++++++++---- src/layout/index.tsx | 9 ++-- src/pages/home.tsx | 9 +++- src/store/reducers/wot.ts | 56 ++++++++------------- src/utils/wot.ts | 98 ++++++++++++++++-------------------- 6 files changed, 116 insertions(+), 110 deletions(-) diff --git a/src/hooks/useFilteredMods.ts b/src/hooks/useFilteredMods.ts index a7654d9..7c46c76 100644 --- a/src/hooks/useFilteredMods.ts +++ b/src/hooks/useFilteredMods.ts @@ -11,6 +11,7 @@ import { } from 'types' import { npubToHex } from 'utils' import { useAppSelector } from './redux' +import { isInWoT } from 'utils/wot' export const useFilteredMods = ( mods: ModDetails[], @@ -23,7 +24,9 @@ export const useFilteredMods = ( }, author?: string | undefined ) => { - const { siteWot, userWot } = useAppSelector((state) => state.wot) + const { siteWot, siteWotLevel, userWot, userWotLevel } = useAppSelector( + (state) => state.wot + ) return useMemo(() => { const nsfwFilter = (mods: ModDetails[]) => { @@ -56,13 +59,18 @@ export const useFilteredMods = ( case WOTFilterOptions.None: return mods case WOTFilterOptions.Site_Only: - return mods.filter((mod) => siteWot.includes(mod.author)) + return mods.filter((mod) => + isInWoT(siteWot, siteWotLevel, mod.author) + ) case WOTFilterOptions.Mine_Only: - return mods.filter((mod) => userWot.includes(mod.author)) + return mods.filter((mod) => + isInWoT(userWot, userWotLevel, mod.author) + ) case WOTFilterOptions.Site_And_Mine: return mods.filter( (mod) => - siteWot.includes(mod.author) || userWot.includes(mod.author) + isInWoT(siteWot, siteWotLevel, mod.author) || + isInWoT(userWot, userWotLevel, mod.author) ) } } @@ -114,6 +122,8 @@ export const useFilteredMods = ( muteLists, nsfwList, siteWot, - userWot + siteWotLevel, + userWot, + userWotLevel ]) } diff --git a/src/layout/header.tsx b/src/layout/header.tsx index 0d0c571..f131dc6 100644 --- a/src/layout/header.tsx +++ b/src/layout/header.tsx @@ -21,7 +21,7 @@ import '../styles/popup.css' import { npubToHex } from '../utils' import logo from '../assets/img/DEG Mods Logo With Text.svg' import placeholder from '../assets/img/DEG Mods Default PP.png' -import { setUserWot } from 'store/reducers/wot' +import { resetUserWot } from 'store/reducers/wot' export const Header = () => { const dispatch = useAppDispatch() @@ -49,7 +49,7 @@ export const Header = () => { if (opts.type === 'logout') { dispatch(setAuth(null)) dispatch(setUser(null)) - dispatch(setUserWot([])) + dispatch(resetUserWot()) } else { dispatch( setAuth({ @@ -381,13 +381,29 @@ const RegisterButtonWithDialog = () => { Browser Extensions (Windows)

- Once you create your "account" on any of these, come back and click login, then sign-in with - extension. Here's a quick video guide, and here's a guide post to help with this process.

-
- + Once you create your "account" on any of these, come + back and click login, then sign-in with extension. + Here's a quick video guide, and here's a{' '} + + guide post + {' '} + to help with this process. +

+
+
{ const hexPubkey = npubToHex(SITE_WOT_NPUB) if (hexPubkey) { dispatch(setSiteWotStatus(WOTStatus.LOADING)) - calculateWot(hexPubkey, ndk, siteWotLevel) + calculateWot(hexPubkey, ndk) .then((wot) => { - dispatch(setSiteWot(Array.from(wot))) - dispatch(setSiteWotStatus(WOTStatus.LOADED)) + dispatch(setSiteWot(wot)) }) .catch((err) => { console.trace('An error occurred in calculating site WOT', err) @@ -54,9 +53,9 @@ export const Layout = () => { if (ndk && userState.user?.pubkey) { const hexPubkey = npubToHex(userState.user.pubkey as string) if (hexPubkey) - calculateWot(hexPubkey, ndk, userWotLevel) + calculateWot(hexPubkey, ndk) .then((wot) => { - dispatch(setUserWot(Array.from(wot))) + dispatch(setUserWot(wot)) }) .catch((err) => { console.trace('An error occurred in calculating user WOT', err) diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 72168a1..78f722c 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -38,6 +38,7 @@ import 'swiper/css/navigation' import 'swiper/css/pagination' import { LoadingSpinner } from 'components/LoadingSpinner' import { Spinner } from 'components/Spinner' +import { isInWoT } from 'utils/wot' export const HomePage = () => { const navigate = useNavigate() @@ -248,7 +249,9 @@ const DisplayMod = ({ naddr }: DisplayModProps) => { const DisplayLatestMods = () => { const navigate = useNavigate() const { fetchMods } = useNDKContext() - const { siteWot, userWot } = useAppSelector((state) => state.wot) + const { siteWot, siteWotLevel, userWot, userWotLevel } = useAppSelector( + (state) => state.wot + ) const [isFetchingLatestMods, setIsFetchingLatestMods] = useState(true) const [latestMods, setLatestMods] = useState([]) @@ -261,7 +264,9 @@ const DisplayLatestMods = () => { // Sort by the latest (published_at descending) mods.sort((a, b) => b.published_at - a.published_at) const wotFilteredMods = mods.filter( - (mod) => siteWot.includes(mod.author) || userWot.includes(mod.author) + (mod) => + isInWoT(siteWot, siteWotLevel, mod.author) || + isInWoT(userWot, userWotLevel, mod.author) ) setLatestMods(wotFilteredMods) }) diff --git a/src/store/reducers/wot.ts b/src/store/reducers/wot.ts index 4288c65..3cc5e02 100644 --- a/src/store/reducers/wot.ts +++ b/src/store/reducers/wot.ts @@ -8,19 +8,19 @@ export enum WOTStatus { } export interface IWOT { - siteWot: string[] + siteWot: Record siteWotStatus: WOTStatus siteWotLevel: number - userWot: string[] + userWot: Record userWotStatus: WOTStatus userWotLevel: number } const initialState: IWOT = { - siteWot: [], + siteWot: {}, siteWotStatus: WOTStatus.IDLE, siteWotLevel: 0, - userWot: [], + userWot: {}, userWotStatus: WOTStatus.IDLE, userWotLevel: 0 } @@ -29,45 +29,30 @@ export const wotSlice = createSlice({ name: 'wot', initialState, reducers: { - setSiteWot(state, action: PayloadAction) { - state = { - ...state, - siteWot: action.payload, - siteWotStatus: WOTStatus.LOADED - } - return state + setSiteWot(state, action: PayloadAction>) { + state.siteWot = action.payload + state.siteWotStatus = WOTStatus.LOADED }, - setUserWot(state, action: PayloadAction) { - state = { - ...state, - userWot: action.payload, - userWotStatus: WOTStatus.LOADED - } - return state + setUserWot(state, action: PayloadAction>) { + state.userWot = action.payload + state.userWotStatus = WOTStatus.LOADED + }, + resetUserWot(state) { + state.userWot = {} + state.userWotStatus = WOTStatus.IDLE + state.userWotLevel = 0 }, setSiteWotStatus(state, action: PayloadAction) { - return { - ...state, - siteWotStatus: action.payload - } + state.siteWotStatus = action.payload }, setUserWotStatus(state, action: PayloadAction) { - return { - ...state, - userWotStatus: action.payload - } + state.userWotStatus = action.payload }, setSiteWotLevel(state, action: PayloadAction) { - return { - ...state, - siteWotLevel: action.payload - } + state.siteWotLevel = action.payload }, setUserWotLevel(state, action: PayloadAction) { - return { - ...state, - userWotLevel: action.payload - } + state.userWotLevel = action.payload } } }) @@ -78,7 +63,8 @@ export const { setSiteWotStatus, setUserWotStatus, setSiteWotLevel, - setUserWotLevel + setUserWotLevel, + resetUserWot } = wotSlice.actions export default wotSlice.reducer diff --git a/src/utils/wot.ts b/src/utils/wot.ts index f894f96..be3b481 100644 --- a/src/utils/wot.ts +++ b/src/utils/wot.ts @@ -14,19 +14,26 @@ interface UserRelations { type Network = Map -export const calculateWot = async ( - pubkey: Hexpubkey, - ndk: NDK, - targetWOTScore: number -) => { - const network: Network = new Map() - const WOT = new Set() +export const calculateWot = async (pubkey: Hexpubkey, ndk: NDK) => { + const WoT: Record = {} const userRelations = await findFollowsAndMuteUsers(pubkey, ndk) - network.set(pubkey, userRelations) + const follows = Array.from(userRelations.follows) + const muted = Array.from(userRelations.muted) + + // Add all the following users to WoT with score set to Positive_Infinity + follows.forEach((f) => { + WoT[f] = Number.POSITIVE_INFINITY + }) + + // Add all the muted users to WoT with score set to Negative_Infinity + muted.forEach((m) => { + WoT[m] = Number.NEGATIVE_INFINITY + }) + + const network: Network = new Map() // find the userRelations of every user in follow list - const follows = Array.from(userRelations.follows) const promises = follows.map((user) => findFollowsAndMuteUsers(user, ndk).then((userRelations) => { network.set(user, userRelations) @@ -34,61 +41,35 @@ export const calculateWot = async ( ) await Promise.all(promises) - // add all the following users to WOT - follows.forEach((f) => { - WOT.add(f) + // make a list of all the users in the network either mutes or followed + const users = new Set() + const userRelationsArray = Array.from(network.values()) + userRelationsArray.forEach(({ follows, muted }) => { + follows.forEach((f) => users.add(f)) + muted.forEach((m) => users.add(m)) }) - // construct a list of users not being followed directly - const indirectFollows = new Set() - follows.forEach((hexpubkey) => { - const relations = network.get(hexpubkey) - if (relations) { - relations.follows.forEach((f) => { - indirectFollows.add(f) - }) + users.forEach((user) => { + // Only calculate if it's not already added to WoT + if (!(user in WoT)) { + const wotScore = calculateWoTScore(user, network) + WoT[user] = wotScore } }) - indirectFollows.forEach((targetHexPubkey) => { - // if any of the indirect followed user is in direct mute list - // we'll not include it in WOT - if (userRelations.muted.has(targetHexPubkey)) return - - const wotScore = calculateWoTScore(pubkey, targetHexPubkey, network) - if (wotScore >= targetWOTScore) { - WOT.add(targetHexPubkey) - } - }) - - return WOT + return WoT } -export const calculateWoTScore = ( - user: Hexpubkey, - targetUser: Hexpubkey, - network: Network -): number => { - const userRelations = network.get(user) - if (!userRelations) return 0 - +export const calculateWoTScore = (user: Hexpubkey, network: Network) => { let wotScore = 0 - // Check each user followed - for (const followedUser of userRelations.follows) { - const followedUserRelations = network.get(followedUser) - if (!followedUserRelations) continue + // iterate over all the entries in the network and increment/decrement + // wotScore based on the list user in which exists (followed/mutes) + network.forEach(({ follows, muted }) => { + if (follows.has(user)) wotScore += 1 - // Positive Score: +1 if followedUser also follows targetId - if (followedUserRelations.follows.has(targetUser)) { - wotScore += 1 - } - - // Negative Score: -1 if followedUser has muted targetId - if (followedUserRelations.muted.has(targetUser)) { - wotScore -= 1 - } - } + if (muted.has(user)) wotScore -= 1 + }) return wotScore } @@ -145,3 +126,12 @@ export const filterValidPTags = (tags: NDKTag[]) => return false } }) + +export const isInWoT = ( + WoT: Record, + targetScore: number, + targetUser: string +): boolean => { + const wotScore = WoT[targetUser] ?? 0 // Default to 0 if the user is not in the record + return wotScore >= targetScore +} From 4b6db36646aa02b70c62bc4a474105e3977cfd8b Mon Sep 17 00:00:00 2001 From: daniyal Date: Tue, 19 Nov 2024 00:57:32 +0500 Subject: [PATCH 17/27] fix: include authors in wotLevel filter --- src/layout/index.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/layout/index.tsx b/src/layout/index.tsx index 53a44e6..f936f04 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -24,9 +24,7 @@ export const Layout = () => { const dispatch = useAppDispatch() const { ndk, fetchEventFromUserRelays } = useNDKContext() const userState = useAppSelector((state) => state.user) - const { siteWotStatus, siteWotLevel, userWotLevel } = useAppSelector( - (state) => state.wot - ) + const { siteWotStatus } = useAppSelector((state) => state.wot) // calculate site's wot useEffect(() => { @@ -46,7 +44,7 @@ export const Layout = () => { }) } } - }, [ndk, siteWotLevel, dispatch]) + }, [ndk, dispatch]) // calculate user's wot useEffect(() => { @@ -62,7 +60,7 @@ export const Layout = () => { toast.error('An error occurred in calculating user web-of-trust!') }) } - }, [ndk, userState.user, userWotLevel, dispatch]) + }, [ndk, userState.user, dispatch]) // get site's wot level useEffect(() => { @@ -72,7 +70,8 @@ export const Layout = () => { fetchEventFromUserRelays( { kinds: [NDKKind.AppSpecificData], - '#d': ['degmods'] + '#d': ['degmods'], + authors: [hexPubkey] }, hexPubkey, UserRelaysType.Both @@ -94,7 +93,8 @@ export const Layout = () => { fetchEventFromUserRelays( { kinds: [NDKKind.AppSpecificData], - '#d': ['degmods'] + '#d': ['degmods'], + authors: [hexPubkey] }, hexPubkey, UserRelaysType.Both From 1b960e5f020fb5d90cf11ed9b9853641602e3e6d Mon Sep 17 00:00:00 2001 From: freakoverse Date: Tue, 19 Nov 2024 00:17:39 +0000 Subject: [PATCH 18/27] Update src/styles/post.css --- src/styles/post.css | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/styles/post.css b/src/styles/post.css index c2f3474..1792296 100644 --- a/src/styles/post.css +++ b/src/styles/post.css @@ -251,4 +251,25 @@ border-bottom: solid 1px rgb(255 255 255 / 10%); padding: 0px 0 10px 0; line-height: 1.5 !important; -} \ No newline at end of file +} + +.dropdown.dropdownMain.dropdownMainBlogpost { + flex-grow: unset; + position: absolute; + top: 10px; + right: 10px; + background: rgba(0,0,0,0.1); + border-radius: 6px; + padding: 2px; +} + +.IBMSMSMBSSWarning { + width: 100%; + border-radius: 8px; + padding: 10px 15px; + border: solid 2px tomato; + background: rgba(255,80,80,0.15); + color: rgba(255,255,255,0.95); + text-align: center; +} + From 81d012b0cbcd10b9105ec27b0d8c4884afa541d8 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 19 Nov 2024 10:01:02 +0100 Subject: [PATCH 19/27] refactor(settings): add readOnly to remove warnings for wip checkboxes --- src/pages/settings/preference.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/preference.tsx b/src/pages/settings/preference.tsx index 82c84ee..cb41186 100644 --- a/src/pages/settings/preference.tsx +++ b/src/pages/settings/preference.tsx @@ -21,12 +21,14 @@ export const PreferencesSetting = () => { useEffect(() => { if (user?.pubkey) { + const hexPubkey = user.pubkey as string fetchEventFromUserRelays( { kinds: [NDKKind.AppSpecificData], - '#d': ['degmods'] + '#d': ['degmods'], + authors: [hexPubkey] }, - user.pubkey as string, + hexPubkey, UserRelaysType.Both ).then((event) => { if (event) { @@ -119,6 +121,7 @@ export const PreferencesSetting = () => { className='CheckboxMain' name='notificationsSettings' checked + readOnly />
@@ -130,6 +133,7 @@ export const PreferencesSetting = () => { className='CheckboxMain' name='notificationsSettings' checked + readOnly />
@@ -141,6 +145,7 @@ export const PreferencesSetting = () => { className='CheckboxMain' name='notificationsSettings' checked + readOnly />
@@ -152,6 +157,7 @@ export const PreferencesSetting = () => { className='CheckboxMain' name='notificationsSettings' checked + readOnly />
@@ -163,6 +169,7 @@ export const PreferencesSetting = () => { className='CheckboxMain' name='notificationsSettings' checked + readOnly />
@@ -212,6 +219,7 @@ export const PreferencesSetting = () => { className='CheckboxMain' name='WoTZap' checked + readOnly /> From 4b6926b0b90422699c113611a77bf1488b06e0a6 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 19 Nov 2024 13:08:27 +0100 Subject: [PATCH 20/27] refactor(wot): single loop only --- src/utils/wot.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/wot.ts b/src/utils/wot.ts index be3b481..fa8bcd0 100644 --- a/src/utils/wot.ts +++ b/src/utils/wot.ts @@ -98,9 +98,7 @@ export const findFollowsAndMuteUsers = async ( follows.add(f) }) } - }) - events.forEach((event) => { if (event.kind === NDKKind.MuteList) { filterValidPTags(event.tags).forEach((f) => { muted.add(f) From 2936d6d53b8fa5a275e2cb6876c132d5dbc84f27 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 19 Nov 2024 13:09:12 +0100 Subject: [PATCH 21/27] refactor(wot): add Trust label to wot filter --- src/components/ModsFilter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ModsFilter.tsx b/src/components/ModsFilter.tsx index 806fabe..6e0342d 100644 --- a/src/components/ModsFilter.tsx +++ b/src/components/ModsFilter.tsx @@ -110,7 +110,7 @@ export const ModFilter = React.memo( data-bs-toggle='dropdown' type='button' > - {filterOptions.wot} + Trust: {filterOptions.wot}
{Object.values(WOTFilterOptions).map((item, index) => { From 8b5b9a6e3059fefeda34bfd1a7d44b4d154c30c0 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 19 Nov 2024 13:11:50 +0100 Subject: [PATCH 22/27] refactor(wot): ignore filter selection based on ruleset --- src/hooks/useFilteredMods.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/hooks/useFilteredMods.ts b/src/hooks/useFilteredMods.ts index 7c46c76..fd64adb 100644 --- a/src/hooks/useFilteredMods.ts +++ b/src/hooks/useFilteredMods.ts @@ -54,18 +54,29 @@ export const useFilteredMods = ( } const wotFilter = (mods: ModDetails[]) => { - // Determine the filtering logic based on the WOT filter option + // Determine the filtering logic based on the WOT filter option and user state + // when user is not logged in use Site_Only + if (!userState.auth) { + return mods.filter((mod) => isInWoT(siteWot, siteWotLevel, mod.author)) + } + // when user is logged, allow other filter selections + const isWoTNpub = + userState.user?.npub === import.meta.env.VITE_SITE_WOT_NPUB switch (filterOptions.wot) { case WOTFilterOptions.None: - return mods + // Only admins can choose None, use siteWoT for others + return isWoTNpub + ? mods + : mods.filter((mod) => isInWoT(siteWot, siteWotLevel, mod.author)) case WOTFilterOptions.Site_Only: return mods.filter((mod) => isInWoT(siteWot, siteWotLevel, mod.author) ) case WOTFilterOptions.Mine_Only: - return mods.filter((mod) => - isInWoT(userWot, userWotLevel, mod.author) - ) + // Only admins can choose Mine_Only, use siteWoT for others + return isWoTNpub + ? mods.filter((mod) => isInWoT(userWot, userWotLevel, mod.author)) + : mods.filter((mod) => isInWoT(siteWot, siteWotLevel, mod.author)) case WOTFilterOptions.Site_And_Mine: return mods.filter( (mod) => From 02a81213a21a1d7ee378458f5f3ff13eb6fb627c Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 19 Nov 2024 16:23:42 +0100 Subject: [PATCH 23/27] refactor(wot): update redux wot level if admin wot npub changes level --- src/hooks/useFilteredMods.ts | 1 + src/pages/settings/preference.tsx | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hooks/useFilteredMods.ts b/src/hooks/useFilteredMods.ts index fd64adb..5530b0d 100644 --- a/src/hooks/useFilteredMods.ts +++ b/src/hooks/useFilteredMods.ts @@ -123,6 +123,7 @@ export const useFilteredMods = ( return filtered }, [ + userState.auth, userState.user?.npub, filterOptions.sort, filterOptions.moderated, diff --git a/src/pages/settings/preference.tsx b/src/pages/settings/preference.tsx index cb41186..69e3d12 100644 --- a/src/pages/settings/preference.tsx +++ b/src/pages/settings/preference.tsx @@ -4,9 +4,9 @@ import { useAppDispatch, useAppSelector, useNDKContext } from 'hooks' import { kinds, UnsignedEvent, Event } from 'nostr-tools' import { useEffect, useState } from 'react' import { toast } from 'react-toastify' -import { setUserWotLevel } from 'store/reducers/wot' +import { setSiteWotLevel, setUserWotLevel } from 'store/reducers/wot' import { UserRelaysType } from 'types' -import { log, LogType, now } from 'utils' +import { log, LogType, now, npubToHex } from 'utils' // todo: use components from Input.tsx export const PreferencesSetting = () => { @@ -92,6 +92,13 @@ export const PreferencesSetting = () => { ) dispatch(setUserWotLevel(wotLevel)) + + // If wot admin, update site wot level too + const SITE_WOT_NPUB = import.meta.env.VITE_SITE_WOT_NPUB + const siteWotPubkey = npubToHex(SITE_WOT_NPUB) + if (siteWotPubkey === hexPubkey) { + dispatch(setSiteWotLevel(wotLevel)) + } }) .catch((err) => { console.error(err) From 994382f39cbbfd7bc1758549acaa8d8a72c2bd68 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 20 Nov 2024 12:27:41 +0100 Subject: [PATCH 24/27] fix(wot): add exclude and fix wot dropdown --- src/components/ModsFilter.tsx | 9 +++++---- src/hooks/useFilteredMods.ts | 6 ++++++ src/types/modsFilter.ts | 3 ++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/ModsFilter.tsx b/src/components/ModsFilter.tsx index 6e0342d..8d733c7 100644 --- a/src/components/ModsFilter.tsx +++ b/src/components/ModsFilter.tsx @@ -125,18 +125,19 @@ export const ModFilter = React.memo( // when logged in user not admin if ( item === WOTFilterOptions.None || - item === WOTFilterOptions.Mine_Only + item === WOTFilterOptions.Mine_Only || + item === WOTFilterOptions.Exclude ) { - const isAdmin = + const isWoTNpub = userState.user?.npub === - import.meta.env.VITE_REPORTING_NPUB + import.meta.env.VITE_SITE_WOT_NPUB const isOwnProfile = author && userState.auth && userState.user?.pubkey === author - if (!(isAdmin || isOwnProfile)) return null + if (!(isWoTNpub || isOwnProfile)) return null } return ( diff --git a/src/hooks/useFilteredMods.ts b/src/hooks/useFilteredMods.ts index 5530b0d..7c16b18 100644 --- a/src/hooks/useFilteredMods.ts +++ b/src/hooks/useFilteredMods.ts @@ -68,6 +68,12 @@ export const useFilteredMods = ( return isWoTNpub ? mods : mods.filter((mod) => isInWoT(siteWot, siteWotLevel, mod.author)) + case WOTFilterOptions.Exclude: + // Only admins can choose Exlude, use siteWot for others + // Exlude returns the mods not in the site's WoT + return isWoTNpub + ? mods.filter((mod) => !isInWoT(siteWot, siteWotLevel, mod.author)) + : mods.filter((mod) => isInWoT(siteWot, siteWotLevel, mod.author)) case WOTFilterOptions.Site_Only: return mods.filter((mod) => isInWoT(siteWot, siteWotLevel, mod.author) diff --git a/src/types/modsFilter.ts b/src/types/modsFilter.ts index 3752caf..b0ae63b 100644 --- a/src/types/modsFilter.ts +++ b/src/types/modsFilter.ts @@ -21,7 +21,8 @@ export enum WOTFilterOptions { Site_And_Mine = 'Site & Mine', Site_Only = 'Site Only', Mine_Only = 'Mine Only', - None = 'None' + None = 'None', + Exclude = 'Exclude' } export interface FilterOptions { From 8d20678c754f0ea368fd8fe8c7921dd98c45cb55 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 20 Nov 2024 13:50:49 +0100 Subject: [PATCH 25/27] fix(ndk): dont create NDKRelaySet from empty arrays --- src/contexts/NDKContext.tsx | 6 ++++-- src/hooks/useComments.ts | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/contexts/NDKContext.tsx b/src/contexts/NDKContext.tsx index e7cd380..75d0d41 100644 --- a/src/contexts/NDKContext.tsx +++ b/src/contexts/NDKContext.tsx @@ -245,7 +245,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => { // Find the user's relays (10s timeout). const relayUrls = await Promise.race([ getRelayListForUser(hexKey, ndk), - timeout(10000) + timeout(3000) ]) .then((ndkRelayList) => { if (ndkRelayList) return ndkRelayList[userRelaysType] @@ -265,7 +265,9 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => { .fetchEvents( filter, { closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL }, - NDKRelaySet.fromRelayUrls(relayUrls, ndk, true) + relayUrls.length + ? NDKRelaySet.fromRelayUrls(relayUrls, ndk, true) + : undefined ) .then((ndkEventSet) => { const ndkEvents = Array.from(ndkEventSet) diff --git a/src/hooks/useComments.ts b/src/hooks/useComments.ts index 8b107bc..e62f918 100644 --- a/src/hooks/useComments.ts +++ b/src/hooks/useComments.ts @@ -66,7 +66,9 @@ export const useComments = ( closeOnEose: false, cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST }, - NDKRelaySet.fromRelayUrls(Array.from(relayUrls), ndk) + relayUrls.size + ? NDKRelaySet.fromRelayUrls(Array.from(relayUrls), ndk) + : undefined ) subscription.on('event', (ndkEvent) => { From a486e5a3834fce132af3f02de11cd9fa9e962543 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 20 Nov 2024 13:54:35 +0100 Subject: [PATCH 26/27] refactor: remove a few console logs --- src/contexts/NDKContext.tsx | 1 - src/pages/blog/report.tsx | 1 - src/pages/settings/preference.tsx | 1 - 3 files changed, 3 deletions(-) diff --git a/src/contexts/NDKContext.tsx b/src/contexts/NDKContext.tsx index 75d0d41..7ff19af 100644 --- a/src/contexts/NDKContext.tsx +++ b/src/contexts/NDKContext.tsx @@ -74,7 +74,6 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { window.onunhandledrejection = async (event: PromiseRejectionEvent) => { event.preventDefault() - console.log(event.reason) if (event.reason?.name === Dexie.errnames.DatabaseClosed) { console.log( 'Could not open Dexie DB, probably version change. Deleting old DB and reloading...' diff --git a/src/pages/blog/report.tsx b/src/pages/blog/report.tsx index b3f6f14..bbbd880 100644 --- a/src/pages/blog/report.tsx +++ b/src/pages/blog/report.tsx @@ -22,7 +22,6 @@ export const ReportPopup = ({ handleClose }: ReportPopupProps) => { useEffect(() => { if (fetcher.data) { const { isSent } = fetcher.data - console.log(fetcher.data) if (isSent) { handleClose() } diff --git a/src/pages/settings/preference.tsx b/src/pages/settings/preference.tsx index 69e3d12..985a509 100644 --- a/src/pages/settings/preference.tsx +++ b/src/pages/settings/preference.tsx @@ -32,7 +32,6 @@ export const PreferencesSetting = () => { UserRelaysType.Both ).then((event) => { if (event) { - console.log('event :>> ', event) const wot = event.tagValue('wot') if (wot) setWotLevel(parseInt(wot)) } From a241f902699621dc00012ff8604e6dc92fe8b368 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 20 Nov 2024 16:14:39 +0100 Subject: [PATCH 27/27] fix(settings): load relays for new npubs Closes #159 --- src/pages/settings/relay.tsx | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/relay.tsx b/src/pages/settings/relay.tsx index 1a32d28..89f0957 100644 --- a/src/pages/settings/relay.tsx +++ b/src/pages/settings/relay.tsx @@ -11,7 +11,7 @@ import { Event, kinds, UnsignedEvent } from 'nostr-tools' import { useEffect, useState } from 'react' import { toast } from 'react-toastify' import { UserRelaysType } from 'types' -import { log, LogType, normalizeWebSocketURL, now } from 'utils' +import { log, LogType, normalizeWebSocketURL, now, timeout } from 'utils' const READ_MARKER = 'read' const WRITE_MARKER = 'write' @@ -21,12 +21,16 @@ export const RelaySettings = () => { const userState = useAppSelector((state) => state.user) const [ndkRelayList, setNDKRelayList] = useState(null) const [isPublishing, setIsPublishing] = useState(false) - + const [isLoading, setIsLoading] = useState(true) const [inputValue, setInputValue] = useState('') useEffect(() => { if (userState.auth && userState.user?.pubkey) { - getRelayListForUser(userState.user.pubkey as string, ndk) + setIsLoading(true) + Promise.race([ + getRelayListForUser(userState.user?.pubkey as string, ndk), + timeout(10000) + ]) .then((res) => { setNDKRelayList(res) }) @@ -36,9 +40,13 @@ export const RelaySettings = () => { err.message || err }` ) - setNDKRelayList(null) + setNDKRelayList(new NDKRelayList(ndk)) + }) + .finally(() => { + setIsLoading(false) }) } else { + setIsLoading(false) setNDKRelayList(null) } }, [userState, ndk]) @@ -224,6 +232,14 @@ export const RelaySettings = () => { setIsPublishing(false) } + if (isLoading) + return ( + <> +
+ + + ) + if (!ndkRelayList) return
Could not fetch user relay list or user is not logged in
@@ -258,6 +274,12 @@ export const RelaySettings = () => {
+ {relayEntries.length === 0 && ( + <> + We recommend adding one of our relays if you're planning to + frequently use DEG Mods, for a better experience. + + )} {relayEntries.map(([relayUrl, relayType]) => (