diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 47684676..da7ac24f 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -28,9 +28,7 @@ services: # NIP-66 relay monitor cron: publishes 30166 (relay discovery) and 10166 (announcement). # Starts and stops with the app. Requires NIP66_MONITOR_NSEC to do anything. jumble-nip66-monitor: - build: - context: ./nip66-cron - dockerfile: Dockerfile + image: silberengel/imwald-jumble-nip66-monitor:latest container_name: imwald-jumble-nip66-monitor restart: unless-stopped environment: diff --git a/nip66-cron/index.mjs b/nip66-cron/index.mjs index 3ffd52f1..6b93feac 100644 --- a/nip66-cron/index.mjs +++ b/nip66-cron/index.mjs @@ -27,7 +27,6 @@ const DEFAULT_RELAYS_TO_MONITOR = [ ] const DEFAULT_PUBLISH_RELAYS = [ - 'wss://theforest.nostr1.com', 'wss://thecitadel.nostr1.com', 'wss://relay.damus.io', 'wss://relay.nostr.watch' diff --git a/package-lock.json b/package-lock.json index e4113187..564bd97d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jumble-imwald", - "version": "16.1.3", + "version": "17.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jumble-imwald", - "version": "16.1.3", + "version": "17.0.0", "license": "MIT", "dependencies": { "@asciidoctor/core": "^3.0.4", diff --git a/package.json b/package.json index d6dd2346..e9bd2661 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jumble-imwald", - "version": "16.1.3", + "version": "17.0.0", "description": "A user-friendly Nostr client focused on relay feed browsing and relay discovery, forked from Jumble", "private": true, "type": "module", diff --git a/scripts/README-deploy.md b/scripts/README-deploy.md new file mode 100644 index 00000000..f745b778 --- /dev/null +++ b/scripts/README-deploy.md @@ -0,0 +1,60 @@ +# Deploy Jumble with docker-compose.prod.yml (remote server) + +Workflow: **build and push locally** → **pull and run on the server**. + +## Local: build and push + +From the **repo root** on your machine: + +```bash +docker login # once, if needed +./scripts/build-and-push-prod.sh +``` + +This builds both images and pushes two tags each (`latest` and the version from `package.json`, e.g. `17.0.0`): + +- **Main app:** `silberengel/imwald-jumble` +- **NIP-66 monitor:** `silberengel/imwald-jumble-nip66-monitor` + +## Remote server: one-time setup + +1. **Docker** + Install Docker and Docker Compose (v2). + +2. **Clone the repo** (so you have `docker-compose.prod.yml`): + ```bash + git clone jumble + cd jumble + ``` + +3. **Optional env file** (e.g. for NIP-66 monitor): + ```bash + # .env next to docker-compose.prod.yml + NIP66_MONITOR_NSEC=nsec1... + NIP66_MONITOR_NPUB=npub1... + ``` + +## Remote server: pull and run + +After you’ve pushed from local: + +```bash +cd jumble +docker compose -f docker-compose.prod.yml pull +docker compose -f docker-compose.prod.yml up -d +``` + +The app is on **port 8089**. Both services use `:latest`; to pin a version, set the image in `docker-compose.prod.yml` to e.g. `silberengel/imwald-jumble:17.0.0` and `silberengel/imwald-jumble-nip66-monitor:17.0.0`. + +## Useful commands (server) + +```bash +# Status +docker compose -f docker-compose.prod.yml ps + +# Logs +docker compose -f docker-compose.prod.yml logs -f + +# Stop +docker compose -f docker-compose.prod.yml down +``` diff --git a/scripts/build-and-push-prod.sh b/scripts/build-and-push-prod.sh new file mode 100755 index 00000000..85c242d0 --- /dev/null +++ b/scripts/build-and-push-prod.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Build main app and NIP-66 monitor images locally; push to silberengel/imwald-jumble and silberengel/imwald-jumble-nip66-monitor as :latest and :. +# Run from repo root. Requires: docker, docker login. On the server you then pull and run docker-compose.prod.yml. +set -e + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +VERSION="$(node -p "require('./package.json').version")" +IMAGE_APP="silberengel/imwald-jumble" +IMAGE_MONITOR="silberengel/imwald-jumble-nip66-monitor" + +echo "Building main app (version: $VERSION)" +docker build -t "$IMAGE_APP:latest" -t "$IMAGE_APP:$VERSION" . + +echo "Building NIP-66 monitor (version: $VERSION)" +docker build -t "$IMAGE_MONITOR:latest" -t "$IMAGE_MONITOR:$VERSION" ./nip66-cron + +echo "Pushing $IMAGE_APP and $IMAGE_MONITOR" +docker push "$IMAGE_APP:latest" +docker push "$IMAGE_APP:$VERSION" +docker push "$IMAGE_MONITOR:latest" +docker push "$IMAGE_MONITOR:$VERSION" + +echo "Done. On the server: docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d" diff --git a/src/PageManager.tsx b/src/PageManager.tsx index 27ca9ada..978298d9 100644 --- a/src/PageManager.tsx +++ b/src/PageManager.tsx @@ -473,10 +473,10 @@ function MainContentArea({ primaryNoteView: !!primaryNoteView }) - // Always use single column layout since double-panel is disabled + // Always use single column layout since double-panel is disabled. flex + min-h-0 so primary page ScrollArea gets a height and can scroll. return ( -
-
+
+
{primaryNoteView ? ( // Show note view with back button
@@ -519,7 +519,7 @@ function MainContentArea({ return (
+
+ +
) } })()} diff --git a/src/components/Explore/index.tsx b/src/components/Explore/index.tsx index dae3f51c..0fe218c9 100644 --- a/src/components/Explore/index.tsx +++ b/src/components/Explore/index.tsx @@ -6,8 +6,6 @@ import relayInfoService from '@/services/relay-info.service' import { TAwesomeRelayCollection } from '@/types' import { useEffect, useState } from 'react' import RelaySimpleInfo, { RelaySimpleInfoSkeleton } from '../RelaySimpleInfo' -import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider' -import { cn } from '@/lib/utils' export default function Explore() { const [collections, setCollections] = useState(null) @@ -30,7 +28,7 @@ export default function Explore() { } return ( -
+
{collections.map((collection) => ( ))} @@ -39,15 +37,9 @@ export default function Explore() { } function RelayCollection({ collection }: { collection: TAwesomeRelayCollection }) { - const { deepBrowsing } = useDeepBrowsing() return (
-
+
{collection.name}
diff --git a/src/components/FollowingFavoriteRelayList/index.tsx b/src/components/FollowingFavoriteRelayList/index.tsx index c9e6b828..9b53f1ac 100644 --- a/src/components/FollowingFavoriteRelayList/index.tsx +++ b/src/components/FollowingFavoriteRelayList/index.tsx @@ -57,7 +57,7 @@ export default function FollowingFavoriteRelayList() { }, [showCount, relays]) return ( -
+
{relays.slice(0, showCount).map(([url, users]) => ( ))} diff --git a/src/components/ImageGallery/index.tsx b/src/components/ImageGallery/index.tsx index fae861fd..ef894a3c 100644 --- a/src/components/ImageGallery/index.tsx +++ b/src/components/ImageGallery/index.tsx @@ -124,10 +124,14 @@ export default function ImageGallery({ open={index >= 0} close={() => setIndex(-1)} controller={{ - closeOnBackdropClick: true, + closeOnBackdropClick: false, closeOnPullUp: true, closeOnPullDown: true }} + render={{ + buttonPrev: images.length <= 1 ? () => null : undefined, + buttonNext: images.length <= 1 ? () => null : undefined + }} styles={{ toolbar: { paddingTop: '2.25rem' } }} diff --git a/src/components/ImageWithLightbox/index.tsx b/src/components/ImageWithLightbox/index.tsx index 7d512a8d..2deb3b13 100644 --- a/src/components/ImageWithLightbox/index.tsx +++ b/src/components/ImageWithLightbox/index.tsx @@ -84,10 +84,14 @@ export default function ImageWithLightbox({ open={index >= 0} close={() => setIndex(-1)} controller={{ - closeOnBackdropClick: true, + closeOnBackdropClick: false, closeOnPullUp: true, closeOnPullDown: true }} + render={{ + buttonPrev: () => null, + buttonNext: () => null + }} styles={{ toolbar: { paddingTop: '2.25rem' } }} diff --git a/src/components/NormalFeed/index.tsx b/src/components/NormalFeed/index.tsx index 9fc24ba8..fbbe4c91 100644 --- a/src/components/NormalFeed/index.tsx +++ b/src/components/NormalFeed/index.tsx @@ -261,22 +261,24 @@ const NormalFeed = forwardRef } /> - {activeTab === 'rss' ? ( - - ) : ( - - )} +
+ {activeTab === 'rss' ? ( + + ) : ( + + )} +
) }) diff --git a/src/components/Note/AsciidocArticle/AsciidocArticle.tsx b/src/components/Note/AsciidocArticle/AsciidocArticle.tsx index 630a379d..8a73e5c6 100644 --- a/src/components/Note/AsciidocArticle/AsciidocArticle.tsx +++ b/src/components/Note/AsciidocArticle/AsciidocArticle.tsx @@ -2090,10 +2090,14 @@ export default function AsciidocArticle({ open={lightboxIndex >= 0} close={() => setLightboxIndex(-1)} controller={{ - closeOnBackdropClick: true, + closeOnBackdropClick: false, closeOnPullUp: true, closeOnPullDown: true }} + render={{ + buttonPrev: allImages.length <= 1 ? () => null : undefined, + buttonNext: allImages.length <= 1 ? () => null : undefined + }} styles={{ toolbar: { paddingTop: '2.25rem' } }} diff --git a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx index 33a27087..f0db77e4 100644 --- a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx +++ b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx @@ -3737,10 +3737,14 @@ export default function MarkdownArticle({ open={lightboxIndex >= 0} close={() => setLightboxIndex(-1)} controller={{ - closeOnBackdropClick: true, + closeOnBackdropClick: false, closeOnPullUp: true, closeOnPullDown: true }} + render={{ + buttonPrev: allImages.length <= 1 ? () => null : undefined, + buttonNext: allImages.length <= 1 ? () => null : undefined + }} styles={{ toolbar: { paddingTop: '2.25rem' } }} diff --git a/src/components/RelaySimpleInfo/index.tsx b/src/components/RelaySimpleInfo/index.tsx index 8dadca30..a6b7f7b3 100644 --- a/src/components/RelaySimpleInfo/index.tsx +++ b/src/components/RelaySimpleInfo/index.tsx @@ -19,7 +19,7 @@ export default function RelaySimpleInfo({ const { t } = useTranslation() return ( -
+
diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx index dae70118..b1904cb3 100644 --- a/src/components/ui/scroll-area.tsx +++ b/src/components/ui/scroll-area.tsx @@ -8,7 +8,7 @@ const ScrollArea = React.forwardRef< React.ComponentPropsWithoutRef & { scrollBarClassName?: string } >(({ className, scrollBarClassName, children, ...props }, ref) => ( - + {children} diff --git a/src/layouts/PrimaryPageLayout/index.tsx b/src/layouts/PrimaryPageLayout/index.tsx index 55c5e9cc..ec71045b 100644 --- a/src/layouts/PrimaryPageLayout/index.tsx +++ b/src/layouts/PrimaryPageLayout/index.tsx @@ -1,6 +1,5 @@ import ScrollToTopButton from '@/components/ScrollToTopButton' import { Titlebar } from '@/components/Titlebar' -import { ScrollArea } from '@/components/ui/scroll-area' import { TPrimaryPageName, usePrimaryPage } from '@/PageManager' import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' @@ -13,13 +12,16 @@ const PrimaryPageLayout = forwardRef( titlebar, pageName, displayScrollToTopButton = false, - hideTitlebarBottomBorder = false + hideTitlebarBottomBorder = false, + subHeader }: { children?: React.ReactNode titlebar: React.ReactNode pageName: TPrimaryPageName displayScrollToTopButton?: boolean hideTitlebarBottomBorder?: boolean + /** Rendered between titlebar and scroll area; not in scroll flow so it never overlaps content */ + subHeader?: React.ReactNode }, ref ) => { @@ -72,7 +74,7 @@ const PrimaryPageLayout = forwardRef(
{titlebar} - {children} + {subHeader &&
{subHeader}
} +
+ {children} +
{displayScrollToTopButton && }
@@ -89,17 +94,19 @@ const PrimaryPageLayout = forwardRef( return ( - +
{titlebar} - {children} -
- + {subHeader &&
{subHeader}
} +
+ {children} +
+
+
{displayScrollToTopButton && } ) diff --git a/src/lib/draft-event.ts b/src/lib/draft-event.ts index b20c23ac..dcfc2818 100644 --- a/src/lib/draft-event.ts +++ b/src/lib/draft-event.ts @@ -571,14 +571,13 @@ export async function createPollDraftEvent( mentions: string[], { isMultipleChoice, relays, options, endsAt }: TPollCreateData, { - addClientTag, isNsfw, addExpirationTag, expirationMonths, addQuietTag, quietDays }: { - addClientTag?: boolean + addClientTag?: boolean // accepted for API compat; client tag is added in publish() isNsfw?: boolean addExpirationTag?: boolean expirationMonths?: number diff --git a/src/pages/primary/ExplorePage/index.tsx b/src/pages/primary/ExplorePage/index.tsx index d19b8462..8b695030 100644 --- a/src/pages/primary/ExplorePage/index.tsx +++ b/src/pages/primary/ExplorePage/index.tsx @@ -28,23 +28,26 @@ const ExplorePage = forwardRef((_, ref) => { ref={ref} pageName="explore" titlebar={} + subHeader={ + { + setTab(tab as TExploreTabs) + window.dispatchEvent(new CustomEvent('pageTabChanged', { + detail: { page: 'explore', tab: tab } + })) + }} + /> + } displayScrollToTopButton > - { - setTab(tab as TExploreTabs) - // Dispatch tab change event for PageManager - window.dispatchEvent(new CustomEvent('pageTabChanged', { - detail: { page: 'explore', tab: tab } - })) - }} - /> - {tab === 'following' ? : } +
+ {tab === 'following' ? : } +
) }) diff --git a/src/pages/primary/NoteListPage/FeedButton.tsx b/src/pages/primary/NoteListPage/FeedButton.tsx index c997696d..e8a623fb 100644 --- a/src/pages/primary/NoteListPage/FeedButton.tsx +++ b/src/pages/primary/NoteListPage/FeedButton.tsx @@ -7,7 +7,7 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import { useFeed } from '@/providers/FeedProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { BookmarkIcon, ChevronDown, Server, UsersRound } from 'lucide-react' -import { forwardRef, HTMLAttributes, useMemo, useState } from 'react' +import { forwardRef, ButtonHTMLAttributes, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' export default function FeedButton({ className }: { className?: string }) { @@ -52,7 +52,7 @@ export default function FeedButton({ className }: { className?: string }) { ) } -const FeedSwitcherTrigger = forwardRef>( +const FeedSwitcherTrigger = forwardRef>( ({ className, ...props }, ref) => { const { t } = useTranslation() const { feedInfo, relayUrls } = useFeed() @@ -84,8 +84,9 @@ const FeedSwitcherTrigger = forwardRef @@ -98,9 +99,9 @@ const FeedSwitcherTrigger = forwardRef )} -
{title}
+ {title} -
+ ) } )