From 9101bc694a2d9dd63b6972e42d915e32939343da Mon Sep 17 00:00:00 2001 From: Bojan Mojsilovic Date: Mon, 7 Aug 2023 19:05:58 +0200 Subject: [PATCH] New settings page layout with section sub-pages --- src/Router.tsx | 32 +- src/assets/icons/add.svg | 3 + src/assets/icons/chevron_right.svg | 3 + src/assets/icons/connect.svg | 11 + .../PageCaption/PageCaption.module.scss | 16 +- .../SettingsNotifications.module.scss | 5 +- .../SettingsNotifications.tsx | 4 +- .../SettingsSidebar.module.scss | 102 +++++ .../SettingsSidebar/SettingsSidebar.tsx | 79 ++++ .../SettingsZap/SettingsZap.module.scss | 22 + src/components/SettingsZap/SettingsZap.tsx | 29 +- src/constants.ts | 11 + src/contexts/AccountContext.tsx | 136 +++++- src/contexts/SettingsContext.tsx | 21 +- src/lib/profile.ts | 4 - src/pages/Profile.tsx | 2 +- src/pages/Settings.module.scss | 69 --- src/pages/Settings.tsx | 97 ----- src/pages/Settings/Appearance.tsx | 30 ++ src/pages/Settings/HomeFeeds.tsx | 58 +++ src/pages/Settings/Menu.tsx | 47 ++ src/pages/Settings/Muted.tsx | 89 ++++ src/pages/Settings/Network.tsx | 250 +++++++++++ src/pages/Settings/Notifications.tsx | 28 ++ src/pages/Settings/Settings.module.scss | 411 ++++++++++++++++++ src/pages/Settings/Settings.tsx | 40 ++ src/pages/Settings/Zaps.tsx | 28 ++ src/sockets.tsx | 4 +- src/translations.ts | 103 ++++- 29 files changed, 1531 insertions(+), 203 deletions(-) create mode 100644 src/assets/icons/add.svg create mode 100644 src/assets/icons/chevron_right.svg create mode 100644 src/assets/icons/connect.svg create mode 100644 src/components/SettingsSidebar/SettingsSidebar.module.scss create mode 100644 src/components/SettingsSidebar/SettingsSidebar.tsx delete mode 100644 src/pages/Settings.module.scss delete mode 100644 src/pages/Settings.tsx create mode 100644 src/pages/Settings/Appearance.tsx create mode 100644 src/pages/Settings/HomeFeeds.tsx create mode 100644 src/pages/Settings/Menu.tsx create mode 100644 src/pages/Settings/Muted.tsx create mode 100644 src/pages/Settings/Network.tsx create mode 100644 src/pages/Settings/Notifications.tsx create mode 100644 src/pages/Settings/Settings.module.scss create mode 100644 src/pages/Settings/Settings.tsx create mode 100644 src/pages/Settings/Zaps.tsx diff --git a/src/Router.tsx b/src/Router.tsx index 2c019de3..0a264f02 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -1,5 +1,6 @@ -import { Component, createReaction, createResource, lazy, Resource } from 'solid-js'; +import { Component, createResource, lazy } from 'solid-js'; import { Routes, Route, Navigate, RouteDataFuncArgs } from "@solidjs/router" + import Home from './pages/Home'; import Layout from './components/Layout/Layout'; import Explore from './pages/Explore'; @@ -7,9 +8,20 @@ import Thread from './pages/Thread'; import Messages from './pages/Messages'; import Notifications from './pages/Notifications'; import Downloads from './pages/Downloads'; -import Settings from './pages/Settings'; +import Settings from './pages/Settings/Settings'; import Help from './pages/Help'; -// import Profile from './pages/Profile'; +import Search from './pages/Search'; +import NotFound from './pages/NotFound'; +import EditProfile from './pages/EditProfile'; + +import NotifSettings from './pages/Settings/Notifications'; +import Appearance from './pages/Settings/Appearance'; +import HomeFeeds from './pages/Settings/HomeFeeds'; +import ZapSettings from './pages/Settings/Zaps'; +import Muted from './pages/Settings/Muted'; +import Network from './pages/Settings/Network'; +import Menu from './pages/Settings/Menu'; + import { PrimalWindow } from './types/primal'; import { useHomeContext } from './contexts/HomeContext'; import { useExploreContext } from './contexts/ExploreContext'; @@ -17,14 +29,12 @@ import { useThreadContext } from './contexts/ThreadContext'; import { useAccountContext } from './contexts/AccountContext'; import { useProfileContext } from './contexts/ProfileContext'; import { useSettingsContext } from './contexts/SettingsContext'; -import NotFound from './pages/NotFound'; import { fetchKnownProfiles } from './lib/profile'; -import Search from './pages/Search'; import { useMessagesContext } from './contexts/MessagesContext'; import { useMediaContext } from './contexts/MediaContext'; import { useNotificationsContext } from './contexts/NotificationsContext'; import { useSearchContext } from './contexts/SearchContext'; -import EditProfile from './pages/EditProfile'; + const primalWindow = window as PrimalWindow; @@ -78,7 +88,15 @@ const Router: Component = () => { } />; - + + + + + + + + + diff --git a/src/assets/icons/add.svg b/src/assets/icons/add.svg new file mode 100644 index 00000000..6a1dc99f --- /dev/null +++ b/src/assets/icons/add.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/chevron_right.svg b/src/assets/icons/chevron_right.svg new file mode 100644 index 00000000..ee463065 --- /dev/null +++ b/src/assets/icons/chevron_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/connect.svg b/src/assets/icons/connect.svg new file mode 100644 index 00000000..b26103b3 --- /dev/null +++ b/src/assets/icons/connect.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/PageCaption/PageCaption.module.scss b/src/components/PageCaption/PageCaption.module.scss index 63209e81..3a9cbab2 100644 --- a/src/components/PageCaption/PageCaption.module.scss +++ b/src/components/PageCaption/PageCaption.module.scss @@ -8,8 +8,22 @@ font-weight: 300; font-size: 32px; line-height: 34px; - color: var(--brand-text); + color: var(--text-secondary); text-transform: lowercase; + + a { + font-weight: 300; + font-size: 32px; + line-height: 34px; + color: var(--text-secondary); + text-transform: lowercase; + text-decoration: none; + + &:hover { + text-decoration: underline; + // text-decoration-thickness: 1px; + } + } } .logo { diff --git a/src/components/SettingsNotifications/SettingsNotifications.module.scss b/src/components/SettingsNotifications/SettingsNotifications.module.scss index c652c99b..2cfd8504 100644 --- a/src/components/SettingsNotifications/SettingsNotifications.module.scss +++ b/src/components/SettingsNotifications/SettingsNotifications.module.scss @@ -14,7 +14,8 @@ .caption { font-weight: 400; - font-size: 14px; - line-height: 32px; + font-size: 18px; + line-height: 20px; color: var(--text-secondary); + margin-bottom: 16px; } diff --git a/src/components/SettingsNotifications/SettingsNotifications.tsx b/src/components/SettingsNotifications/SettingsNotifications.tsx index 380c6919..69caf6cd 100644 --- a/src/components/SettingsNotifications/SettingsNotifications.tsx +++ b/src/components/SettingsNotifications/SettingsNotifications.tsx @@ -27,7 +27,7 @@ import { useSettingsContext } from '../../contexts/SettingsContext'; import { useIntl } from '@cookbook/solid-intl'; import Checkbox from '../Checkbox/Checkbox'; -const SettingsZap: Component = () => { +const SettingsNotifications: Component = () => { const settings = useSettingsContext(); const intl = useIntl(); @@ -173,4 +173,4 @@ const SettingsZap: Component = () => { ); } -export default SettingsZap; +export default SettingsNotifications; diff --git a/src/components/SettingsSidebar/SettingsSidebar.module.scss b/src/components/SettingsSidebar/SettingsSidebar.module.scss new file mode 100644 index 00000000..7c1ad197 --- /dev/null +++ b/src/components/SettingsSidebar/SettingsSidebar.module.scss @@ -0,0 +1,102 @@ +@mixin heading { + position: -webkit-sticky; + position: sticky; + top: 0px; + width: 100%; + height: 44px; + // background-color: var(--background-site); + background: var(--fade-gradient-vertical); + z-index: 5; + padding-bottom: 22px; + display:flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + font-size: 18px; + font-weight: 800; + line-height: 22px; + color: var(--text-secondary-2); + text-transform: uppercase; + >div{ + display: flex; + height: 22px; + >span { + color: var(--text-tertiary-2); + text-transform: lowercase; + margin-left: 6px; + } + } +} + +.headingConnectedRelays { + @include heading; +} + +.headingCachingService { + @include heading(); + margin-top: 34px; + z-index: 10px; +} + +.relayEntry { + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 14px; + font-weight: 400; + line-height: 28px; + color: var(--text-secondary-2); + text-align: left; + + .connected { + background-color: #66E205; + width: 6px; + height: 6px; + border-radius: 2px; + margin-right: 8px; + } + + .disconnected { + background-color: #E20505; + width: 6px; + height: 6px; + border-radius: 2px; + margin-right: 8px; + } + + .relayActions { + display: flex; + font-size: 12px; + line-height: 12px; + font-weight: 400; + min-width: 100px; + margin-left: 20px; + + >span { + display: inline; + } + + >button { + border: none; + background: none; + font-size: 12px; + line-height: 12px; + font-weight: 400; + margin: 2px; + padding: 0px; + color: var(--warning-color); + display: none; + flex-direction: column; + align-items: center; + } + + &:hover { + >span { + display: inline; + } + >button { + display: flex; + } + } + } +} diff --git a/src/components/SettingsSidebar/SettingsSidebar.tsx b/src/components/SettingsSidebar/SettingsSidebar.tsx new file mode 100644 index 00000000..12b0428a --- /dev/null +++ b/src/components/SettingsSidebar/SettingsSidebar.tsx @@ -0,0 +1,79 @@ +import { useIntl } from '@cookbook/solid-intl'; +import { Component, For, Show } from 'solid-js'; +import { useAccountContext } from '../../contexts/AccountContext'; +import { settings as t } from '../../translations'; + +// @ts-ignore Bad types in nostr-tools +import { Relay, relayInit } from "nostr-tools"; + +import styles from './SettingsSidebar.module.scss'; +import { cacheServer, isConnected, socket } from '../../sockets'; + +const SettingsSidebar: Component = () => { + + const intl = useIntl(); + const account = useAccountContext(); + + const connectedRelays = () => account?.relays || []; + + const disconnectedRelays = () => { + const allRelayUrls = Object.keys(account?.relaySettings || {}); + const connectedUrls = connectedRelays().map(r => r.url); + + return allRelayUrls.reduce( + (acc: Relay[], url) => connectedUrls.includes(url) ? acc : [...acc, relayInit(url)], + [], + ); + }; + + return ( + <> +
+
+ {intl.formatMessage(t.relays)} +
+
+ + + {relay => ( +
+
+ + {relay.url} + +
+ )} +
+ + {relay => ( +
+
+ + {relay.url} + +
+ )} +
+ +
+
+ {intl.formatMessage(t.cashingService)} +
+
+ +
+
} + > +
+ + + {socket()?.url || cacheServer} + + + + ) +} + +export default SettingsSidebar; diff --git a/src/components/SettingsZap/SettingsZap.module.scss b/src/components/SettingsZap/SettingsZap.module.scss index 4246a364..17e77b44 100644 --- a/src/components/SettingsZap/SettingsZap.module.scss +++ b/src/components/SettingsZap/SettingsZap.module.scss @@ -35,3 +35,25 @@ input.zapInput { border: 1px solid var(--subtile-devider); border-radius: 6px; } + +.restoreZaps { + display: flex; + justify-content: flex-start; + margin-top: 36px; + + button { + background: none; + color: var(--accent-1); + width: auto; + font-size: 16px; + font-weight: 400; + line-height: 20px; + border: none; + margin: 0; + padding: 0; + + &:hover { + text-decoration: underline; + } + } +} diff --git a/src/components/SettingsZap/SettingsZap.tsx b/src/components/SettingsZap/SettingsZap.tsx index 3aa5e1fe..6ea5807b 100644 --- a/src/components/SettingsZap/SettingsZap.tsx +++ b/src/components/SettingsZap/SettingsZap.tsx @@ -1,13 +1,25 @@ -import { Component, For } from 'solid-js'; +import { Component, createSignal, For } from 'solid-js'; import styles from './SettingsZap.module.scss'; import { useSettingsContext } from '../../contexts/SettingsContext'; import { debounce } from '../../utils'; +import { useIntl } from '@cookbook/solid-intl'; +import ConfirmModal from '../ConfirmModal/ConfirmModal'; +import { settings as t } from '../../translations'; const SettingsZap: Component = () => { + const intl = useIntl(); const settings = useSettingsContext(); + + const [isRestoringZaps, setIsRestoringZaps] = createSignal(false); + + const onRestoreZaps = () => { + settings?.actions.resetZapOptionsToDefault(); + setIsRestoringZaps(false); + }; + const changeDefaultZap = (e: InputEvent) => { debounce(() => { const target = e.target as HTMLInputElement; @@ -65,6 +77,21 @@ const SettingsZap: Component = () => { + +
+ +
+ + setIsRestoringZaps(false)} + /> ); } diff --git a/src/constants.ts b/src/constants.ts index 432927bc..70731e96 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -261,3 +261,14 @@ export const iosCheck = '8/01/23'; export const andCheck = '8/07/23'; export const refreshFeedDelay = 2_500; + +export const defaultZapAmount = 10; + +export const defaultZapOptions = [ + 21, + 420, + 10_000, + 69_420, + 100_000, + 1_000_000, +]; diff --git a/src/contexts/AccountContext.tsx b/src/contexts/AccountContext.tsx index 9372902c..08340289 100644 --- a/src/contexts/AccountContext.tsx +++ b/src/contexts/AccountContext.tsx @@ -12,6 +12,7 @@ import { NostrEOSE, NostrEvent, NostrMutedContent, + NostrRelay, NostrRelays, NostrWindow, PrimalNote, @@ -21,7 +22,7 @@ import { Kind, relayConnectingTimeout } from "../constants"; import { isConnected, refreshSocketListeners, removeSocketListeners, socket, subscribeTo } from "../sockets"; import { sendContacts, sendLike, sendMuteList } from "../lib/notes"; // @ts-ignore Bad types in nostr-tools -import { Relay } from "nostr-tools"; +import { Relay, relayInit } from "nostr-tools"; import { APP_ID } from "../App"; import { getLikes, getProfileContactList, getProfileMuteList, getUserProfiles } from "../lib/profile"; import { getStorage, saveFollowing, saveLikes, saveMuted, saveMuteList, saveRelaySettings } from "../lib/localStore"; @@ -43,6 +44,7 @@ export type AccountContextStore = { hasPublicKey: () => boolean, isKeyLookupDone: boolean, quotedNote: string | undefined, + connectToPrimaryRelays: boolean, actions: { showNewNoteForm: () => void, hideNewNoteForm: () => void, @@ -54,6 +56,9 @@ export type AccountContextStore = { quoteNote: (noteId: string | undefined) => void, addToMuteList: (pubkey: string) => void, removeFromMuteList: (pubkey: string) => void, + addRelay: (url: string) => void, + removeRelay: (url: string) => void, + setConnectToPrimaryRelays: (flag: boolean) => void, }, } @@ -71,6 +76,7 @@ const initialData = { mutedSince: 0, isKeyLookupDone: false, quotedNote: undefined, + connectToPrimaryRelays: true, }; export const AccountContext = createContext(); @@ -97,14 +103,16 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX } return { ...acc, [url]: settings[url] }; - }, {}); + }, rs); + + console.log('TO SAVE: ', toSave) if (Object.keys(toSave).length === 0) { return; } updateStore('relaySettings', () => ({ ...toSave })); - saveRelaySettings(store.publicKey, settings) + saveRelaySettings(store.publicKey, toSave) } const attachDefaultRelays = (relaySettings: NostrRelays) => { @@ -116,9 +124,14 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX let relayAtempts: Record = {}; const relayAtemptLimit = 10; + let relaysExplicitlyClosed: string[] = []; let relayReliability: Record = {}; + const setConnectToPrimaryRelays = (flag: boolean) => { + updateStore('connectToPrimaryRelays', () => flag); + } + const connectToRelays = (relaySettings: NostrRelays) => { if (Object.keys(relaySettings).length === 0) { @@ -126,7 +139,9 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX return; } - const relaysToConnect = attachDefaultRelays(relaySettings); + const relaysToConnect = store.connectToPrimaryRelays ? + attachDefaultRelays(relaySettings) : + relaySettings; const onConnect = (connectedRelay: Relay) => { if (store.relays.find(r => r.url === connectedRelay.url)) { @@ -149,6 +164,11 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX updateStore('relays', (rs) => rs.filter(r => r.url !== failedRelay.url)); + if (relaysExplicitlyClosed.includes(failedRelay.url)) { + relaysExplicitlyClosed = relaysExplicitlyClosed.filter(u => u !== failedRelay.url); + return; + } + if ((relayAtempts[failedRelay.url] || 0) < relayAtemptLimit) { relayAtempts[failedRelay.url] = (relayAtempts[failedRelay.url] || 0) + 1; @@ -229,6 +249,90 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX return success; }; + const addRelay = (url: string) => { + const relay: NostrRelays = { [url]: { write: true, read: true }}; + + setRelaySettings(relay); + + relaysExplicitlyClosed = relaysExplicitlyClosed.filter(u => u !== url); + + const unsub = subscribeTo(`before_add_relay_${APP_ID}`, async (type, subId, content) => { + if (type === 'EOSE') { + + const relayInfo = JSON.stringify(store.relaySettings); + const date = Math.floor((new Date()).getTime() / 1000); + const following = [...store.following]; + + const { success } = await sendContacts(following, date, relayInfo, store.relays, store.relaySettings); + + if (success) { + updateStore('following', () => following); + updateStore('followingSince', () => date); + saveFollowing(store.publicKey, following, date); + } + + unsub(); + return; + } + + if (content && + content.kind === Kind.Contacts && + content.created_at && + content.created_at > store.followingSince + ) { + updateContacts(content); + } + }); + + getProfileContactList(store.publicKey, `before_add_relay_${APP_ID}`); + }; + + const removeRelay = (url: string) => { + const relay: Relay = store.relays.find(r => r.url === url); + + if (relay) { + relay.close(); + updateStore('relays', () => [...store.relays.filter(r => r.url !== url)]); + } + + relaysExplicitlyClosed.push(url); + relayAtempts[url] = 0; + + updateStore('relaySettings', () => ({ [url]: undefined })); + + setRelaySettings(store.relaySettings); + + const unsub = subscribeTo(`before_remove_relay_${APP_ID}`, async (type, subId, content) => { + if (type === 'EOSE') { + + const relayInfo = JSON.stringify(store.relaySettings); + const date = Math.floor((new Date()).getTime() / 1000); + const following = [...store.following]; + + const { success } = await sendContacts(following, date, relayInfo, store.relays, store.relaySettings); + + if (success) { + updateStore('following', () => following); + updateStore('followingSince', () => date); + saveFollowing(store.publicKey, following, date); + } + + unsub(); + return; + } + + if (content && + content.kind === Kind.Contacts && + content.created_at && + content.created_at > store.followingSince + ) { + updateContacts(content); + } + }); + + getProfileContactList(store.publicKey, `before_remove_relay_${APP_ID}`); + }; + const updateContacts = (content: NostrContactsContent) => { const followingSince = content.created_at; @@ -516,6 +620,27 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX } }); + createEffect(() => { + const rels: string[] = import.meta.env.PRIMAL_PRIORITY_RELAYS?.split(',') || []; + + if (store.connectToPrimaryRelays) { + const relaySettings = rels.reduce((acc, r) => ({ ...acc, [r]: { read: true, write: true } }), {}); + + connectToRelays(relaySettings) + } + else { + for (let i = 0; i < rels.length; i++) { + const url = rels[i]; + const relay = store.relays.find(r => r.url === url); + + if (relay) { + relay.close(); + updateStore('relays', () => [...store.relays.filter(r => r.url !== url)]); + } + } + } + }); + onCleanup(() => { removeSocketListeners( socket(), @@ -601,6 +726,9 @@ const [store, updateStore] = createStore({ quoteNote, addToMuteList, removeFromMuteList, + addRelay, + removeRelay, + setConnectToPrimaryRelays, }, }); diff --git a/src/contexts/SettingsContext.tsx b/src/contexts/SettingsContext.tsx index 54d17296..2cf84e9e 100644 --- a/src/contexts/SettingsContext.tsx +++ b/src/contexts/SettingsContext.tsx @@ -1,6 +1,6 @@ import { createStore } from "solid-js/store"; import { useToastContext } from "../components/Toaster/Toaster"; -import { defaultFeeds, defaultNotificationSettings, themes, trendingFeed } from "../constants"; +import { defaultFeeds, defaultNotificationSettings, defaultZapAmount, defaultZapOptions, themes, trendingFeed } from "../constants"; import { createContext, createEffect, @@ -55,6 +55,7 @@ export type SettingsContextStore = { loadSettings: (pubkey: string) => void, setDefaultZapAmount: (amount: number) => void, setZapOptions: (amount:number, index: number) => void, + resetZapOptionsToDefault: (temp?: boolean) => void, updateNotificationSettings: (key: string, value: boolean, temp?: boolean) => void, restoreDefaultFeeds: () => void, } @@ -66,15 +67,8 @@ export const initialData = { themes, availableFeeds: [], defaultFeed: defaultFeeds[0], - defaultZapAmount: 10, - availableZapOptions: [ - 21, - 420, - 10_000, - 69_420, - 100_000, - 1_000_000, - ], + defaultZapAmount: defaultZapAmount, + availableZapOptions: defaultZapOptions, notificationSettings: { ...defaultNotificationSettings }, }; @@ -99,6 +93,12 @@ export const SettingsProvider = (props: { children: ContextChildren }) => { !temp && saveSettings(); }; + const resetZapOptionsToDefault = (temp?: boolean) => { + updateStore('availableZapOptions', () => defaultZapOptions); + updateStore('defaultZapAmount', () => defaultZapAmount); + !temp && saveSettings(); + } + const setTheme = (theme: PrimalTheme | null, temp?: boolean) => { if (!theme) { return; @@ -466,6 +466,7 @@ export const SettingsProvider = (props: { children: ContextChildren }) => { restoreDefaultFeeds, setDefaultZapAmount, setZapOptions, + resetZapOptionsToDefault, updateNotificationSettings, }, }); diff --git a/src/lib/profile.ts b/src/lib/profile.ts index 0f8c5a2f..0356982f 100644 --- a/src/lib/profile.ts +++ b/src/lib/profile.ts @@ -165,10 +165,6 @@ export const sendProfile = async (metaData: any, relays: Relay[], relaySettings? return await sendEvent(event, relays, relaySettings); }; -export const muteUser = async (pubkey: string, relays: Relay[], relaySettings?: NostrRelays) => { - -}; - export const reportUser = async (pubkey: string, subid: string, user?: PrimalUser) => { if (!pubkey) { return; diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index a8dd10a0..0bc3932d 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -563,7 +563,7 @@ const Profile: Component = () => { } diff --git a/src/pages/Settings.module.scss b/src/pages/Settings.module.scss deleted file mode 100644 index 22737624..00000000 --- a/src/pages/Settings.module.scss +++ /dev/null @@ -1,69 +0,0 @@ -.settingsContainer { - background-color: var(--background-card); - padding-inline: 28px; - min-height: 100vh; - padding-bottom: 20px; -} - -.fullHeader { - display: grid; - height: 128px; - align-items: center; - justify-content: left; - - >div { - font-weight: 300; - font-size: 32px; - line-height: 34px; - color: var(--brand-text); - text-transform: lowercase; - } -} - -.comingSoon { - font-weight: 300; - font-size: 18px; - line-height: 34px; - color: var(--text-secondary); -} - -.settingsCaption { - font-size: 18px; - font-weight: 800; - line-height: 20px; - color: var(--text-secondary); - margin-bottom: 20px; -} - -.feedCaption { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: -10px; - .settingsCaption { - margin-bottom: 0px; - } - - .restoreFeedsButton { - background-color: var(--background-site); - color: var(--text-tertiary); - width: auto; - font-size: 14px; - font-weight: 400; - line-height: 20px; - border: none; - margin: 0; - padding-block: 10px; - padding-inline: 10px; - - &:hover { - color: var(--text-secondary); - } - } -} - -.devider { - width: 100%; - border-bottom: solid 1px var(--subtile-devider); - margin-block: 32px; -} diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx deleted file mode 100644 index b65d4835..00000000 --- a/src/pages/Settings.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { Component, createSignal } from 'solid-js'; -import Branding from '../components/Branding/Branding'; -import styles from './Settings.module.scss'; - -import FeedSorter from '../components/FeedSorter/FeedSorter'; -import ThemeChooser from '../components/ThemeChooser/ThemeChooser'; -import Wormhole from '../components/Wormhole/Wormhole'; -import { useIntl } from '@cookbook/solid-intl'; -import SettingsZap from '../components/SettingsZap/SettingsZap'; -import Search from '../components/Search/Search'; -import SettingsNotifications from '../components/SettingsNotifications/SettingsNotifications'; -import { settings as t } from '../translations'; -import { useSettingsContext } from '../contexts/SettingsContext'; -import ConfirmModal from '../components/ConfirmModal/ConfirmModal'; -import PageCaption from '../components/PageCaption/PageCaption'; - -const Settings: Component = () => { - - const intl = useIntl(); - const settings = useSettingsContext(); - - const [isRestoringFeeds, setIsRestoringFeeds] = createSignal(false); - - const onRestoreFeeds = () => { - settings?.actions.restoreDefaultFeeds(); - setIsRestoringFeeds(false); - }; - - return ( -
- - - - - - - - - - -
- {intl.formatMessage(t.theme)} -
- - - -
- -
-
- {intl.formatMessage(t.feeds)} -
- - - - setIsRestoringFeeds(false)} - > -
- - -
- -
- -
- -
- {intl.formatMessage(t.feeds)} -
- -
- -
- -
- -
- {intl.formatMessage(t.notifications.title)} -
- -
- -
-
- ) -} - -export default Settings; diff --git a/src/pages/Settings/Appearance.tsx b/src/pages/Settings/Appearance.tsx new file mode 100644 index 00000000..1743d92d --- /dev/null +++ b/src/pages/Settings/Appearance.tsx @@ -0,0 +1,30 @@ +import { Component } from 'solid-js'; +import styles from './Settings.module.scss'; + +import ThemeChooser from '../../components/ThemeChooser/ThemeChooser'; +import { useIntl } from '@cookbook/solid-intl'; +import { settings as t } from '../../translations'; +import PageCaption from '../../components/PageCaption/PageCaption'; +import { Link } from '@solidjs/router'; + +const Appearance: Component = () => { + + const intl = useIntl(); + + return ( +
+ + {intl.formatMessage(t.index.title)}:  +
{intl.formatMessage(t.appearance.title)}
+
+ +
+ {intl.formatMessage(t.appearance.caption)} +
+ + +
+ ) +} + +export default Appearance; diff --git a/src/pages/Settings/HomeFeeds.tsx b/src/pages/Settings/HomeFeeds.tsx new file mode 100644 index 00000000..ee84b46d --- /dev/null +++ b/src/pages/Settings/HomeFeeds.tsx @@ -0,0 +1,58 @@ +import { Component, createSignal } from 'solid-js'; +import styles from './Settings.module.scss'; + +import { useIntl } from '@cookbook/solid-intl'; +import { settings as t } from '../../translations'; +import PageCaption from '../../components/PageCaption/PageCaption'; +import { Link } from '@solidjs/router'; +import ConfirmModal from '../../components/ConfirmModal/ConfirmModal'; +import { useSettingsContext } from '../../contexts/SettingsContext'; +import FeedSorter from '../../components/FeedSorter/FeedSorter'; + +const HomeFeeds: Component = () => { + + const intl = useIntl(); + const settings = useSettingsContext(); + + const [isRestoringFeeds, setIsRestoringFeeds] = createSignal(false); + + const onRestoreFeeds = () => { + settings?.actions.restoreDefaultFeeds(); + setIsRestoringFeeds(false); + }; + + return ( +
+ + {intl.formatMessage(t.index.title)}:  +
{intl.formatMessage(t.homeFeeds.title)}
+
+ +
+
+ {intl.formatMessage(t.homeFeeds.caption)} +
+ + + + setIsRestoringFeeds(false)} + > +
+ +
+ +
+
+ ) +} + +export default HomeFeeds; diff --git a/src/pages/Settings/Menu.tsx b/src/pages/Settings/Menu.tsx new file mode 100644 index 00000000..ee227113 --- /dev/null +++ b/src/pages/Settings/Menu.tsx @@ -0,0 +1,47 @@ +import { Component } from 'solid-js'; +import styles from './Settings.module.scss'; + +import { useIntl } from '@cookbook/solid-intl'; +import { settings as t } from '../../translations'; +import PageCaption from '../../components/PageCaption/PageCaption'; +import { Link } from '@solidjs/router'; + +const Menu: Component = () => { + + const intl = useIntl(); + + return ( +
+ + +
+ + {intl.formatMessage(t.appearance.title)} +
+ + + {intl.formatMessage(t.homeFeeds.title)} +
+ + + {intl.formatMessage(t.muted.title)} +
+ + + {intl.formatMessage(t.notifications.title)} +
+ + + {intl.formatMessage(t.network.title)} +
+ + + {intl.formatMessage(t.zaps)} +
+ +
+
+ ) +} + +export default Menu; diff --git a/src/pages/Settings/Muted.tsx b/src/pages/Settings/Muted.tsx new file mode 100644 index 00000000..190b1243 --- /dev/null +++ b/src/pages/Settings/Muted.tsx @@ -0,0 +1,89 @@ +import { Component, createEffect, For } from 'solid-js'; +import styles from './Settings.module.scss'; + +import { useIntl } from '@cookbook/solid-intl'; +import { settings as t, actions as tActions } from '../../translations'; +import PageCaption from '../../components/PageCaption/PageCaption'; +import { Link } from '@solidjs/router'; +import { useAccountContext } from '../../contexts/AccountContext'; +import { getUserProfiles } from '../../lib/profile'; +import { APP_ID } from '../../App'; +import { subscribeTo } from '../../sockets'; +import { convertToUser, nip05Verification, userName } from '../../stores/profile'; +import { Kind } from '../../constants'; +import { createStore } from 'solid-js/store'; +import { PrimalUser } from '../../types/primal'; +import Avatar from '../../components/Avatar/Avatar'; + +const Muted: Component = () => { + + const intl = useIntl(); + const account = useAccountContext(); + + const [mutedUsers, setMutedUsers] = createStore([]); + + const mutedMetadataSubId = `muted_metadata_${APP_ID}`; + + createEffect(() => { + if (account && account.isKeyLookupDone) { + + let users: PrimalUser[] = []; + + const unsub = subscribeTo(mutedMetadataSubId, (type, subId, content) => { + if (type === 'EVENT') { + if (content?.kind === Kind.Metadata) { + users.push(convertToUser(content)); + } + } + + if (type === 'EOSE') { + setMutedUsers(() => [ ...users ]); + unsub(); + } + }); + + getUserProfiles(account.muted, mutedMetadataSubId); + } + }); + + const unMuteUser = (user: PrimalUser) => { + account?.actions.removeFromMuteList(user.pubkey); + }; + + return ( +
+ + {intl.formatMessage(t.index.title)}:  +
{intl.formatMessage(t.muted.title)}
+
+ +
+ + {intl.formatMessage(t.muted.empty)} +
+ } + > + {user => ( +
+
+ +
+
{userName(user)}
+
{nip05Verification(user)}
+
+
+ +
+ )} + +
+ + ) +} + +export default Muted; diff --git a/src/pages/Settings/Network.tsx b/src/pages/Settings/Network.tsx new file mode 100644 index 00000000..1b7a0050 --- /dev/null +++ b/src/pages/Settings/Network.tsx @@ -0,0 +1,250 @@ +import { Component, createEffect, createSignal, For, onMount, Show } from 'solid-js'; +// @ts-ignore Bad types in nostr-tools +import { Relay, relayInit } from "nostr-tools"; +import styles from './Settings.module.scss'; + +import { useIntl } from '@cookbook/solid-intl'; +import { settings as t } from '../../translations'; +import PageCaption from '../../components/PageCaption/PageCaption'; +import { Link } from '@solidjs/router'; +import { useAccountContext } from '../../contexts/AccountContext'; +import { getDefaultRelays } from '../../lib/relays'; +import { APP_ID } from '../../App'; +import { isConnected as isSocketConnected, socket, subscribeTo } from '../../sockets'; +import { createStore } from 'solid-js/store'; +import Checkbox from '../../components/Checkbox/Checkbox'; +import { store } from '../../services/StoreService'; +import ConfirmModal from '../../components/ConfirmModal/ConfirmModal'; + +const Network: Component = () => { + + const intl = useIntl(); + const account = useAccountContext(); + + const [recomendedRelays, setRecomendedRelays] = createStore([]); + const [confirmRemoveRelay, setConfirmRemoveRelay] = createSignal(''); + const [invalidCustomRelay, setInvalidCustomRelay] = createSignal(false); + + let customRelayInput: HTMLInputElement | undefined; + + const relays = () => { + let settingsRelays = []; + + for (let url in (account?.relaySettings || {})) { + settingsRelays.push(relayInit(url)) + } + + return settingsRelays; + }; + + const otherRelays = () => { + const myRelays: string[] = relays().map(r => r.url); + + let unusedRelays: string[] = []; + + for (let i = 0; i < recomendedRelays.length; i++) { + const relay = recomendedRelays[i]; + + const exists = myRelays.find(r => { + + const a = new URL(r); + const b = new URL(relay.url); + + return a.origin === b.origin; + }); + + if (!exists) { + unusedRelays.push(relay.url); + } + } + + return unusedRelays; + } + + const isConnected = (url: string) => { + const relay: Relay | undefined = account?.relays.find(r => r.url === url); + return relay && relay.status === WebSocket.OPEN; + }; + + const isPrimalRelayInUserSettings = () => { + const rels: string[] = import.meta.env.PRIMAL_PRIORITY_RELAYS?.split(',') || []; + + return Object.keys(account?.relaySettings || {}).includes(rels[0]); + } + + const onCheckPrimalRelay = () => { + account?.actions.setConnectToPrimaryRelays(!account.connectToPrimaryRelays) + }; + + createEffect(() => { + const unsub = subscribeTo(`settings_drs_${APP_ID}`, (type, subId, content) => { + if (type === 'EVENT' && content) { + const urls = JSON.parse(content.content || '[]'); + setRecomendedRelays(() => urls.map(relayInit)); + } + + if (type === 'EOSE') { + unsub(); + } + }); + + getDefaultRelays(`settings_drs_${APP_ID}`); + }); + + const onAddRelay = (url: string) => { + const rels: string[] = import.meta.env.PRIMAL_PRIORITY_RELAYS?.split(',') || []; + if (rels.includes(url)) { + account?.actions.setConnectToPrimaryRelays(true); + } + account?.actions.addRelay(url); + }; + + const onRemoveRelay = (url: string) => { + account?.actions.removeRelay(url); + }; + + const onCustomRelayInput = () => { + if (!customRelayInput) { + return; + } + + try { + const url = new URL(customRelayInput.value); + if (!url.origin.startsWith('wss://')) { + throw(new Error('must be a wss')) + } + + customRelayInput.value = ''; + account?.actions.addRelay(url.origin); + setInvalidCustomRelay(false); + } catch (e) { + console.log('invalid url'); + setInvalidCustomRelay(true); + } + } + + return ( +
+ + {intl.formatMessage(t.index.title)}:  +
{intl.formatMessage(t.network.title)}
+
+ +
+ {intl.formatMessage(t.network.relays)} +
+ +
+ {intl.formatMessage(t.network.myRelays)} +
+ + + {relay => ( +
+
remove
+ + )} + + + + onCheckPrimalRelay()} + label={`Post a copy of all content to the Primal relay (${import.meta.env.PRIMAL_PRIORITY_RELAYS})`} + /> + + +
+ {intl.formatMessage(t.network.recomended)} +
+ + + {url => ( + + )} + + +
+ {intl.formatMessage(t.network.customRelay)} +
+ +
+
+ onCustomRelayInput()} + /> + +
+ + +
+ Invalid relay url +
+
+ +
+ {intl.formatMessage(t.network.cachingService)} +
+ +
+ {intl.formatMessage(t.network.connectedCachingService)} +
+ +
+
+
} + > +
+ +
+ + {socket()?.url} + +
+ + + 0} + description="Remove Relay?" + onConfirm={() => { + onRemoveRelay(confirmRemoveRelay()) + setConfirmRemoveRelay(''); + }} + onAbort={() => setConfirmRemoveRelay('')} + /> + + ) +} + +export default Network; diff --git a/src/pages/Settings/Notifications.tsx b/src/pages/Settings/Notifications.tsx new file mode 100644 index 00000000..b2f90114 --- /dev/null +++ b/src/pages/Settings/Notifications.tsx @@ -0,0 +1,28 @@ +import { Component } from 'solid-js'; +import styles from './Settings.module.scss'; + +import { useIntl } from '@cookbook/solid-intl'; +import { settings as t } from '../../translations'; +import PageCaption from '../../components/PageCaption/PageCaption'; +import { Link } from '@solidjs/router'; +import SettingsNotifications from '../../components/SettingsNotifications/SettingsNotifications'; + +const Notifications: Component = () => { + + const intl = useIntl(); + + return ( +
+ + {intl.formatMessage(t.index.title)}:  +
{intl.formatMessage(t.notifications.title)}
+
+ +
+ +
+
+ ) +} + +export default Notifications; diff --git a/src/pages/Settings/Settings.module.scss b/src/pages/Settings/Settings.module.scss new file mode 100644 index 00000000..41f3319e --- /dev/null +++ b/src/pages/Settings/Settings.module.scss @@ -0,0 +1,411 @@ +.settingsContainer { + background-color: var(--background-card); + padding-inline: 28px; + min-height: 100vh; + padding-bottom: 20px; +} + +.fullHeader { + display: grid; + height: 128px; + align-items: center; + justify-content: left; + + >div { + font-weight: 300; + font-size: 32px; + line-height: 34px; + color: var(--brand-text); + text-transform: lowercase; + } +} + +.comingSoon { + font-weight: 300; + font-size: 18px; + line-height: 34px; + color: var(--text-secondary); +} + +.bigCaption { + color: var(--text-primary); + font-size: 18px; + font-weight: 600; + line-height: 20px; + text-transform: uppercase; + margin-bottom: 24px; +} + +.secondBigCaption { + margin-top: 48px; +} + +.settingsCaption { + font-size: 18px; + font-weight: 400; + line-height: 20px; + color: var(--text-secondary); + margin-bottom: 20px; +} + +.secondCaption { + margin-top: 24px; +} + +.feedCaption { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + .settingsCaption { + margin-bottom: 0px; + } + + .restoreFeedsButton { + background: none; + color: var(--accent-1); + width: auto; + font-size: 16px; + font-weight: 400; + line-height: 20px; + border: none; + margin: 0; + padding: 0; + + &:hover { + text-decoration: underline; + } + } +} + +.devider { + width: 100%; + border-bottom: solid 1px var(--subtile-devider); + margin-block: 32px; +} + +.subpageLinks { + display: flex; + flex-direction: column; + a { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + padding-block: 16px; + padding-right: 20px; + border-bottom: 1px solid var(--background-input); + width: 100%; + color: var(--text-primary); + font-size: 20px; + font-weight: 500; + line-height: 28px; + text-decoration: none; + + >div { + width: 12px; + height: 20px; + background-color: var(--text-tertiary); + -webkit-mask: url(../../assets/icons/chevron_right.svg) no-repeat 0 / 100%; + mask: url(../../assets/icons/chevron_right.svg) no-repeat 0 / 100%; + } + + &:hover { + >div { + background-color: var(--text-primary); + } + } + } +} + +.emptyListBanner { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + color: var(--text-secondary); + font-size: 18px; + font-weight: 700; + line-height: 20px; + text-transform: uppercase; +} + +.mutedUser { + display: flex; + justify-content: space-between; + align-items: center; + padding-block: 12px; + border-bottom: 1px solid var(--background-input); + .userInfo { + display: flex; + align-items: center; + .userName { + height: 36px; + display: flex; + flex-direction: column; + justify-content: space-between; + margin-left: 8px; + .title { + color: var(--text-primary); + font-size: 16px; + font-weight: 700; + line-height: 16px; + } + .verification { + color: var(--text-tertiary-2); + font-size: 14px; + font-weight: 400; + line-height: 14px; + } + } + } + + button { + width: auto; + border-radius: 6px; + padding: 0px; + font-size: 14px; + font-weight: 500; + line-height: 18px; + padding-block: 7px; + padding-inline: 12px; + margin: 0px; + color: var(--text-primary); + background: linear-gradient(var(--background-card), var(--background-card)) padding-box, + var(--brand-gradient) border-box; + border: 1px solid transparent; + + &:hover { + background: linear-gradient(var(--background-site), var(--background-site)) padding-box, + var(--brand-gradient) border-box; + } + } +} + +.closeIcon { + display: inline-block; + width: 8px; + height: 8px; + background-color: var(--text-secondary); + -webkit-mask: url(../../assets/icons/close.svg) no-repeat 0 / 100%; + mask: url(../../assets/icons/close.svg) no-repeat 0 / 100%; +} + +.addIcon { + display: inline-block; + width: 8px; + height: 8px; + background-color: var(--text-secondary); + -webkit-mask: url(../../assets/icons/add.svg) no-repeat 0 / 100%; + mask: url(../../assets/icons/add.svg) no-repeat 0 / 100%; +} + +.webIcon { + width: 18px; + height: 18px; + background-color: var(--text-secondary); + margin-inline: 8px; + -webkit-mask: url(../../assets/icons/global.svg) no-repeat 0 / 100%; + mask: url(../../assets/icons/global.svg) no-repeat 0 / 100%; +} + +.connectIcon { + width: 18px; + height: 18px; + background-color: var(--text-secondary); + margin-inline: 8px; + -webkit-mask: url(../../assets/icons/connect.svg) no-repeat 0 / 100%; + mask: url(../../assets/icons/connect.svg) no-repeat 0 / 100%; +} + +.relayItem { + display: flex; + justify-content: space-between; + padding-block: 11px; + padding-inline: 0; + border-bottom: 1px solid var(--background-input); + border: none; + outline: none; + margin: 0; + background: none; + + .relayEntry { + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 14px; + font-weight: 400; + line-height: 18px; + color: var(--text-secondary-2); + text-align: left; + + .connected { + background-color: #66E205; + width: 6px; + height: 6px; + border-radius: 2px; + margin-right: 8px; + } + + .disconnected { + background-color: #E20505; + width: 6px; + height: 6px; + border-radius: 2px; + margin-right: 8px; + } + + .relayActions { + display: flex; + font-size: 12px; + line-height: 12px; + font-weight: 400; + min-width: 100px; + margin-left: 20px; + + >span { + display: inline; + } + + >button { + border: none; + background: none; + font-size: 12px; + line-height: 12px; + font-weight: 400; + margin: 2px; + padding: 0px; + color: var(--warning-color); + display: none; + flex-direction: column; + align-items: center; + } + + &:hover { + >span { + display: inline; + } + >button { + display: flex; + } + } + } + } + + .remove, .add { + width: auto; + border: none; + outline: none; + margin: 0; + padding: 0; + color: var(--text-tertiary-2); + background: none; + font-size: 14px; + font-weight: 700; + line-height: 12px; + transition: color 0s linear; + + .removeIcon, .addIcon { + background-color: var(--text-tertiary-2); + } + } + + &:hover { + + .relayEntry { + color: var(--text-primary); + + .webIcon, .addIcon { + background-color: var(--text-primary); + } + } + + .remove { + color: #E20505; + transition: color 0s linear; + + .closeIcon { + background-color: #E20505; + } + } + + .add { + color: #66E205; + transition: color 0s linear; + + .addIcon { + background-color: #66E205; + } + } + } + +} + +.relayInput { + position: relative; + + .webIcon { + position: absolute; + top: 11px; + left: 4px; + } + + input { + font-size: 16px; + font-weight: 400; + line-height: 20px; + padding-inline: 40px; + height: 40px; + border: 1px solid var(--subtile-devider); + border-radius: 8px; + color: var(--text-secondary); + margin-bottom: 0; + + &::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ + color: var(--text-tertiary-2); + opacity: 1; /* Firefox */ + } + + &:-ms-input-placeholder { /* Internet Explorer 10-11 */ + color: var(--text-tertiary-2); + } + + &::-ms-input-placeholder { /* Microsoft Edge */ + color: var(--text-tertiary-2); + } + + &:focus { + border-color: var(--text-tertiary); + } + } + + button { + position: absolute; + top: 0px; + right: 0px; + width: 40px; + height: 40px; + background: none; + margin: 0; + padding: 2px; + border-radius: 8px; + border: none; + outline: none; + box-shadow: none; + + &:hover { + .connectIcon { + background-color: var(--text-primary); + } + } + } +} + +.invalidInput { + color:#E20505; + font-size: 16px; + font-weight: 400; + line-height: 20px; + text-align: left; +} diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx new file mode 100644 index 00000000..7a25b682 --- /dev/null +++ b/src/pages/Settings/Settings.tsx @@ -0,0 +1,40 @@ +import { Component } from 'solid-js'; +import Branding from '../../components/Branding/Branding'; +import styles from './Settings.module.scss'; + +import Wormhole from '../../components/Wormhole/Wormhole'; +import { useIntl } from '@cookbook/solid-intl'; +import Search from '../../components/Search/Search'; +import { settings as t } from '../../translations'; +import { useSettingsContext } from '../../contexts/SettingsContext'; +import PageCaption from '../../components/PageCaption/PageCaption'; +import { Link, Outlet } from '@solidjs/router'; +import HomeSidebar from '../../components/HomeSidebar/HomeSidebar'; +import StickySidebar from '../../components/StickySidebar/StickySidebar'; +import SettingsSidebar from '../../components/SettingsSidebar/SettingsSidebar'; + +const Settings: Component = () => { + + const intl = useIntl(); + const settings = useSettingsContext(); + + return ( +
+ + + + + + + + + + + + + +
+ ) +} + +export default Settings; diff --git a/src/pages/Settings/Zaps.tsx b/src/pages/Settings/Zaps.tsx new file mode 100644 index 00000000..d5a928a8 --- /dev/null +++ b/src/pages/Settings/Zaps.tsx @@ -0,0 +1,28 @@ +import { Component } from 'solid-js'; +import styles from './Settings.module.scss'; + +import { useIntl } from '@cookbook/solid-intl'; +import { settings as t } from '../../translations'; +import PageCaption from '../../components/PageCaption/PageCaption'; +import { Link } from '@solidjs/router'; +import SettingsZap from '../../components/SettingsZap/SettingsZap'; + +const Zaps: Component = () => { + + const intl = useIntl(); + + return ( +
+ + {intl.formatMessage(t.index.title)}:  +
{intl.formatMessage(t.zaps)}
+
+ +
+ +
+
+ ) +} + +export default Zaps; diff --git a/src/sockets.tsx b/src/sockets.tsx index 78a65d88..4df7f9ba 100644 --- a/src/sockets.tsx +++ b/src/sockets.tsx @@ -27,9 +27,11 @@ const onError = (error: Event) => { console.log("ws error: ", error); }; +export let cacheServer = ''; + export const connect = () => { if (isNotConnected()) { - const cacheServer = + cacheServer = localStorage.getItem('cacheServer') || import.meta.env.PRIMAL_CACHE_URL || 'wss://cache3.primal.net/cache17'; diff --git a/src/translations.ts b/src/translations.ts index d0c838f7..4d67f244 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -91,9 +91,9 @@ export const actions = { defaultMessage: 'Add {name} to your mute list?', description: 'Label for mute user confirmation', }, - mute: { - id: 'actions.mute', - defaultMessage: 'click to unmute', + unmute: { + id: 'actions.unmute', + defaultMessage: 'unmute', description: 'Label un-mute button', }, profileContext: { @@ -812,6 +812,96 @@ export const search = { }; export const settings = { + index: { + title: { + id: 'settings.index.title', + defaultMessage: 'Settings', + description: 'Title of the settings page', + }, + }, + appearance: { + title: { + id: 'settings.appearance.title', + defaultMessage: 'Appearance', + description: 'Title of the appearance settings sub-page', + }, + caption: { + id: 'settings.appearance.caption', + defaultMessage: 'Select a theme', + description: 'Caption for theme selection', + }, + }, + homeFeeds: { + title: { + id: 'settings.homeFeeds.title', + defaultMessage: 'Home Feeds', + description: 'Title of the home feeds settings sub-page', + }, + caption: { + id: 'settings.homeFeeds.caption', + defaultMessage: 'Edit and order your home page feeds', + description: 'Caption for home feed ordering', + }, + }, + muted: { + title: { + id: 'settings.muted.title', + defaultMessage: 'Muted Accounts', + description: 'Title of the muted accounts settings sub-page', + }, + empty: { + id: 'settings.muted.empty', + defaultMessage: 'No muted users', + description: 'Caption indicating that there are no muted users', + }, + }, + network: { + title: { + id: 'settings.network.title', + defaultMessage: 'Network', + description: 'Title of the network settings sub-page', + }, + relays: { + id: 'settings.network.relays', + defaultMessage: 'Relays', + description: 'Title of the Relays section of the Network settings sub-page', + }, + myRelays: { + id: 'settings.network.myRelays', + defaultMessage: 'My Relays', + description: 'Title of the My Relays section of the Network settings sub-page', + }, + recomended: { + id: 'settings.network.recomended', + defaultMessage: 'Recomended Relays', + description: 'Title of the Recomended Relays section of the Network settings sub-page', + }, + customRelay: { + id: 'settings.network.customRelay', + defaultMessage: 'Connect to relay', + description: 'Title of the Customd Relays section of the Network settings sub-page', + }, + cachingService: { + id: 'settings.network.cachingService', + defaultMessage: 'Caching Service', + description: 'Title of the Caching Service section of the Network settings sub-page', + }, + connectedCachingService: { + id: 'settings.network.connectedCachingService', + defaultMessage: 'Connected caching service', + description: 'Title of the Caching Service section of the Network settings sub-page', + }, + }, + relays: { + id: 'settings.relays', + defaultMessage: 'Relays', + description: 'Title of the relays sections of the settings sidebar', + }, + cashingService: { + id: 'settings.cashingService', + defaultMessage: 'Caching Service', + description: 'Title of the caching service sections of the settings sidebar', + }, title: { id: 'settings.title', defaultMessage: 'Settings', @@ -829,7 +919,7 @@ export const settings = { }, feedsRestore: { id: 'settings.feedsRestore', - defaultMessage: 'Reset to default feeds', + defaultMessage: 'restore defaults', description: 'Label for the button for restoring default feeds to the feeds list', }, feedsRestoreConfirm: { @@ -837,6 +927,11 @@ export const settings = { defaultMessage: 'Restoring default feeds will erase all your custom feed settings', description: 'Label explaining the impact of restoring default feeds', }, + zapsRestoreConfirm: { + id: 'settings.zapsRestoreConfirm', + defaultMessage: 'This action will restore all your zap settings to their default values', + description: 'Label explaining the impact of restoring default zaps', + }, feedLatest: { id: 'feeds.latestFollowing', defaultMessage: 'Latest',