Browse Source

bug-fixes

imwald
Silberengel 2 months ago
parent
commit
01be297154
  1. 4
      docker-compose.prod.yml
  2. 1
      nip66-cron/index.mjs
  3. 4
      package-lock.json
  4. 2
      package.json
  5. 60
      scripts/README-deploy.md
  6. 25
      scripts/build-and-push-prod.sh
  7. 24
      src/PageManager.tsx
  8. 12
      src/components/Explore/index.tsx
  9. 2
      src/components/FollowingFavoriteRelayList/index.tsx
  10. 6
      src/components/ImageGallery/index.tsx
  11. 6
      src/components/ImageWithLightbox/index.tsx
  12. 34
      src/components/NormalFeed/index.tsx
  13. 6
      src/components/Note/AsciidocArticle/AsciidocArticle.tsx
  14. 6
      src/components/Note/MarkdownArticle/MarkdownArticle.tsx
  15. 2
      src/components/RelaySimpleInfo/index.tsx
  16. 2
      src/components/ui/scroll-area.tsx
  17. 31
      src/layouts/PrimaryPageLayout/index.tsx
  18. 3
      src/lib/draft-event.ts
  19. 33
      src/pages/primary/ExplorePage/index.tsx
  20. 13
      src/pages/primary/NoteListPage/FeedButton.tsx

4
docker-compose.prod.yml

@ -28,9 +28,7 @@ services: @@ -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:

1
nip66-cron/index.mjs

@ -27,7 +27,6 @@ const DEFAULT_RELAYS_TO_MONITOR = [ @@ -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'

4
package-lock.json generated

@ -1,12 +1,12 @@ @@ -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",

2
package.json

@ -1,6 +1,6 @@ @@ -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",

60
scripts/README-deploy.md

@ -0,0 +1,60 @@ @@ -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 <your-repo-url> 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
```

25
scripts/build-and-push-prod.sh

@ -0,0 +1,25 @@ @@ -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 :<version from package.json>.
# 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"

24
src/PageManager.tsx

@ -473,10 +473,10 @@ function MainContentArea({ @@ -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 (
<div className="grid grid-cols-1 gap-2 w-full pr-2 py-2">
<div className="rounded-lg shadow-lg bg-background overflow-hidden">
<div className="flex-1 flex flex-col min-h-0 w-full pr-2 py-2">
<div className="flex-1 flex flex-col min-h-0 rounded-lg shadow-lg bg-background overflow-hidden">
{primaryNoteView ? (
// Show note view with back button
<div className="flex flex-col h-full w-full">
@ -519,7 +519,7 @@ function MainContentArea({ @@ -519,7 +519,7 @@ function MainContentArea({
return (
<div
key={name}
className="flex flex-col h-full w-full"
className="flex flex-col h-full min-h-0 w-full"
style={{
display: isCurrentPage ? 'block' : 'none'
}}
@ -1579,13 +1579,15 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -1579,13 +1579,15 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
} else {
// Single-pane mode: show feed only, drawer overlay for notes
return (
<MainContentArea
primaryPages={primaryPages}
currentPrimaryPage={currentPrimaryPage}
primaryNoteView={primaryNoteView}
primaryViewType={primaryViewType}
goBack={goBack}
/>
<div className="flex-1 flex flex-col min-h-0 min-w-0">
<MainContentArea
primaryPages={primaryPages}
currentPrimaryPage={currentPrimaryPage}
primaryNoteView={primaryNoteView}
primaryViewType={primaryViewType}
goBack={goBack}
/>
</div>
)
}
})()}

12
src/components/Explore/index.tsx

@ -6,8 +6,6 @@ import relayInfoService from '@/services/relay-info.service' @@ -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<TAwesomeRelayCollection[] | null>(null)
@ -30,7 +28,7 @@ export default function Explore() { @@ -30,7 +28,7 @@ export default function Explore() {
}
return (
<div className="min-w-0 w-full overflow-x-hidden space-y-6">
<div className="min-w-0 w-full overflow-x-hidden space-y-6 pb-8">
{collections.map((collection) => (
<RelayCollection key={collection.id} collection={collection} />
))}
@ -39,15 +37,9 @@ export default function Explore() { @@ -39,15 +37,9 @@ export default function Explore() {
}
function RelayCollection({ collection }: { collection: TAwesomeRelayCollection }) {
const { deepBrowsing } = useDeepBrowsing()
return (
<div className="min-w-0">
<div
className={cn(
'sticky bg-background z-20 px-4 py-3 text-2xl font-semibold max-md:border-b min-w-0 break-words',
deepBrowsing ? 'top-12' : 'top-24'
)}
>
<div className="px-4 pt-3 pb-3.5 text-2xl font-semibold max-md:border-b min-w-0 break-words">
{collection.name}
</div>
<div className="grid min-w-0 md:px-4 md:grid-cols-2 md:gap-3">

2
src/components/FollowingFavoriteRelayList/index.tsx

@ -57,7 +57,7 @@ export default function FollowingFavoriteRelayList() { @@ -57,7 +57,7 @@ export default function FollowingFavoriteRelayList() {
}, [showCount, relays])
return (
<div>
<div className="pb-8">
{relays.slice(0, showCount).map(([url, users]) => (
<RelayItem key={url} url={url} users={users} />
))}

6
src/components/ImageGallery/index.tsx

@ -124,10 +124,14 @@ export default function ImageGallery({ @@ -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' }
}}

6
src/components/ImageWithLightbox/index.tsx

@ -84,10 +84,14 @@ export default function ImageWithLightbox({ @@ -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' }
}}

34
src/components/NormalFeed/index.tsx

@ -261,22 +261,24 @@ const NormalFeed = forwardRef<TNoteListRef, { @@ -261,22 +261,24 @@ const NormalFeed = forwardRef<TNoteListRef, {
</>
}
/>
{activeTab === 'rss' ? (
<RssFeedList key={rssRefreshKey} />
) : (
<NoteList
ref={noteListRef}
showKinds={temporaryShowKinds}
showKind1OPs={showKind1OPs}
showKind1Replies={showKind1Replies}
showKind1111={showKind1111}
subRequests={subRequests}
hideReplies={listMode === 'posts'}
hideUntrustedNotes={hideUntrustedNotes}
areAlgoRelays={areAlgoRelays}
showRelayCloseReason={showRelayCloseReason}
/>
)}
<div className="pt-2 min-w-0">
{activeTab === 'rss' ? (
<RssFeedList key={rssRefreshKey} />
) : (
<NoteList
ref={noteListRef}
showKinds={temporaryShowKinds}
showKind1OPs={showKind1OPs}
showKind1Replies={showKind1Replies}
showKind1111={showKind1111}
subRequests={subRequests}
hideReplies={listMode === 'posts'}
hideUntrustedNotes={hideUntrustedNotes}
areAlgoRelays={areAlgoRelays}
showRelayCloseReason={showRelayCloseReason}
/>
)}
</div>
</>
)
})

6
src/components/Note/AsciidocArticle/AsciidocArticle.tsx

@ -2090,10 +2090,14 @@ export default function AsciidocArticle({ @@ -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' }
}}

6
src/components/Note/MarkdownArticle/MarkdownArticle.tsx

@ -3737,10 +3737,14 @@ export default function MarkdownArticle({ @@ -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' }
}}

2
src/components/RelaySimpleInfo/index.tsx

@ -19,7 +19,7 @@ export default function RelaySimpleInfo({ @@ -19,7 +19,7 @@ export default function RelaySimpleInfo({
const { t } = useTranslation()
return (
<div className={cn('min-w-0 space-y-1', className)} {...props}>
<div className={cn('min-w-0 min-h-[4.5rem] space-y-1', className)} {...props}>
<div className="flex items-start justify-between gap-2 w-full min-w-0">
<div className="flex flex-1 w-0 items-center gap-2">
<RelayIcon url={relayInfo?.url} className="h-9 w-9" />

2
src/components/ui/scroll-area.tsx

@ -8,7 +8,7 @@ const ScrollArea = React.forwardRef< @@ -8,7 +8,7 @@ const ScrollArea = React.forwardRef<
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & { scrollBarClassName?: string }
>(({ className, scrollBarClassName, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root className={cn('relative overflow-hidden', className)} {...props}>
<ScrollAreaPrimitive.Viewport ref={ref} className="h-full w-full rounded-[inherit]">
<ScrollAreaPrimitive.Viewport ref={ref} className="h-full min-h-0 w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar className={scrollBarClassName} />

31
src/layouts/PrimaryPageLayout/index.tsx

@ -1,6 +1,5 @@ @@ -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( @@ -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( @@ -72,7 +74,7 @@ const PrimaryPageLayout = forwardRef(
<DeepBrowsingProvider active={current === pageName && display}>
<div
ref={smallScreenScrollAreaRef}
className="min-w-0 overflow-x-hidden"
className="min-w-0 w-full overflow-x-hidden"
style={{
paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)'
}}
@ -80,7 +82,10 @@ const PrimaryPageLayout = forwardRef( @@ -80,7 +82,10 @@ const PrimaryPageLayout = forwardRef(
<PrimaryPageTitlebar hideBottomBorder={hideTitlebarBottomBorder}>
{titlebar}
</PrimaryPageTitlebar>
{children}
{subHeader && <div className="shrink-0 w-full min-w-0 bg-background">{subHeader}</div>}
<div className="min-w-0 w-full">
{children}
</div>
</div>
{displayScrollToTopButton && <ScrollToTopButton />}
</DeepBrowsingProvider>
@ -89,17 +94,19 @@ const PrimaryPageLayout = forwardRef( @@ -89,17 +94,19 @@ const PrimaryPageLayout = forwardRef(
return (
<DeepBrowsingProvider active={current === pageName && display} scrollAreaRef={scrollAreaRef}>
<ScrollArea
className="h-full overflow-auto"
scrollBarClassName="z-50 pt-12"
ref={scrollAreaRef}
>
<div className="relative h-full min-h-0 flex flex-col">
<PrimaryPageTitlebar hideBottomBorder={hideTitlebarBottomBorder}>
{titlebar}
</PrimaryPageTitlebar>
{children}
<div className="h-4" />
</ScrollArea>
{subHeader && <div className="shrink-0 bg-background">{subHeader}</div>}
<div
ref={scrollAreaRef}
className={subHeader ? 'flex-1 min-h-0 overflow-y-auto overflow-x-hidden' : 'absolute top-12 left-0 right-0 bottom-0 overflow-y-auto overflow-x-hidden'}
>
{children}
<div className="h-4" />
</div>
</div>
{displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
</DeepBrowsingProvider>
)

3
src/lib/draft-event.ts

@ -571,14 +571,13 @@ export async function createPollDraftEvent( @@ -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

33
src/pages/primary/ExplorePage/index.tsx

@ -28,23 +28,26 @@ const ExplorePage = forwardRef((_, ref) => { @@ -28,23 +28,26 @@ const ExplorePage = forwardRef((_, ref) => {
ref={ref}
pageName="explore"
titlebar={<ExplorePageTitlebar />}
subHeader={
<Tabs
value={tab}
tabs={[
{ value: 'explore', label: 'Explore' },
{ value: 'following', label: "Following's Favorites" }
]}
onTabChange={(tab) => {
setTab(tab as TExploreTabs)
window.dispatchEvent(new CustomEvent('pageTabChanged', {
detail: { page: 'explore', tab: tab }
}))
}}
/>
}
displayScrollToTopButton
>
<Tabs
value={tab}
tabs={[
{ value: 'explore', label: 'Explore' },
{ value: 'following', label: "Following's Favorites" }
]}
onTabChange={(tab) => {
setTab(tab as TExploreTabs)
// Dispatch tab change event for PageManager
window.dispatchEvent(new CustomEvent('pageTabChanged', {
detail: { page: 'explore', tab: tab }
}))
}}
/>
{tab === 'following' ? <FollowingFavoriteRelayList /> : <Explore />}
<div className="min-w-0 pt-2">
{tab === 'following' ? <FollowingFavoriteRelayList /> : <Explore />}
</div>
</PrimaryPageLayout>
)
})

13
src/pages/primary/NoteListPage/FeedButton.tsx

@ -7,7 +7,7 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' @@ -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 }) { @@ -52,7 +52,7 @@ export default function FeedButton({ className }: { className?: string }) {
)
}
const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
const FeedSwitcherTrigger = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement>>(
({ className, ...props }, ref) => {
const { t } = useTranslation()
const { feedInfo, relayUrls } = useFeed()
@ -84,8 +84,9 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle @@ -84,8 +84,9 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
}, [feedInfo, activeRelaySet])
return (
<div
className={cn('flex items-center gap-2 clickable px-3 h-full rounded-lg', className)}
<button
type="button"
className={cn('flex items-center gap-2 clickable px-3 h-full rounded-lg bg-transparent border-0 text-left', className)}
ref={ref}
{...props}
>
@ -98,9 +99,9 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle @@ -98,9 +99,9 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
) : (
<Server />
)}
<div className="text-lg font-semibold truncate">{title}</div>
<span className="text-lg font-semibold truncate">{title}</span>
<ChevronDown />
</div>
</button>
)
}
)

Loading…
Cancel
Save