Browse Source

refine calendar and maps

imwald
Silberengel 1 month ago
parent
commit
19c2275086
  1. 54
      src/components/CalendarEventNip52StructuredMeta.tsx
  2. 2
      src/components/ConnectedRelays/ActiveRelaysTitlebarButton.tsx
  3. 3
      src/i18n/locales/cs.ts
  4. 3
      src/i18n/locales/de.ts
  5. 3
      src/i18n/locales/en.ts
  6. 3
      src/i18n/locales/es.ts
  7. 3
      src/i18n/locales/fr.ts
  8. 3
      src/i18n/locales/nl.ts
  9. 3
      src/i18n/locales/pl.ts
  10. 3
      src/i18n/locales/ru.ts
  11. 3
      src/i18n/locales/tr.ts
  12. 3
      src/i18n/locales/zh.ts
  13. 202
      src/pages/primary/NoteListPage/index.tsx

54
src/components/CalendarEventNip52StructuredMeta.tsx

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import { getNip52CalendarEventTagExtras, type CalendarEventMeta } from '@/lib/calendar-event'
import { useSmartNoteNavigation } from '@/PageManager'
import { cn } from '@/lib/utils'
import { Event } from 'nostr-tools'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@ -9,6 +8,19 @@ import { Calendar, ExternalLink, Link2, MapPin } from 'lucide-react' @@ -9,6 +8,19 @@ import { Calendar, ExternalLink, Link2, MapPin } from 'lucide-react'
type Placement = 'beforeDescription' | 'afterDescription'
/** [Geohash Explorer](https://geohash.softeng.co/) style URL for a geohash string. */
function nip52GeohashSoftengUrl(geohash: string): string {
const h = geohash.trim()
return `https://geohash.softeng.co/${encodeURIComponent(h)}`
}
/** Google Maps “place” style URL from a free-text address or place name. */
function googleMapsPlaceUrl(placeQuery: string): string {
const q = placeQuery.trim()
if (!q) return '#'
return `https://www.google.com/maps/place/${encodeURIComponent(q).replace(/%20/g, '+')}`
}
export function CalendarEventNip52StructuredMeta({
placement,
event,
@ -38,16 +50,34 @@ export function CalendarEventNip52StructuredMeta({ @@ -38,16 +50,34 @@ export function CalendarEventNip52StructuredMeta({
{meta.locations.length > 1 ? t('calendarNip52Locations') : t('calendarNip52Location')}
</div>
{meta.locations.length === 1 ? (
<p className="flex gap-2 text-sm leading-snug text-foreground">
<MapPin className="mt-0.5 size-4 shrink-0 text-muted-foreground" aria-hidden />
<span className="min-w-0">{meta.locations[0]}</span>
</p>
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<p className="flex min-w-0 flex-1 gap-2 text-sm leading-snug text-foreground">
<MapPin className="mt-0.5 size-4 shrink-0 text-muted-foreground" aria-hidden />
<span className="min-w-0">{meta.locations[0]}</span>
</p>
<Button variant="outline" size="sm" className="h-8 w-full shrink-0 sm:w-auto" asChild>
<a href={googleMapsPlaceUrl(meta.locations[0])} target="_blank" rel="noopener noreferrer">
<ExternalLink className="size-3.5" />
{t('calendarNip52GoogleMaps')}
</a>
</Button>
</div>
) : (
<ul className="min-w-0 space-y-2">
<ul className="min-w-0 space-y-3">
{meta.locations.map((loc, i) => (
<li key={`${i}-${loc.slice(0, 24)}`} className="flex gap-2 text-sm leading-snug text-foreground">
<MapPin className="mt-0.5 size-4 shrink-0 text-muted-foreground" aria-hidden />
<span className="min-w-0">{loc}</span>
<li key={`${i}-${loc.slice(0, 24)}`}>
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<p className="flex min-w-0 flex-1 gap-2 text-sm leading-snug text-foreground">
<MapPin className="mt-0.5 size-4 shrink-0 text-muted-foreground" aria-hidden />
<span className="min-w-0">{loc}</span>
</p>
<Button variant="outline" size="sm" className="h-8 w-full shrink-0 sm:w-auto" asChild>
<a href={googleMapsPlaceUrl(loc)} target="_blank" rel="noopener noreferrer">
<ExternalLink className="size-3.5" />
{t('calendarNip52GoogleMaps')}
</a>
</Button>
</div>
</li>
))}
</ul>
@ -72,11 +102,7 @@ export function CalendarEventNip52StructuredMeta({ @@ -72,11 +102,7 @@ export function CalendarEventNip52StructuredMeta({
<p className="flex flex-wrap items-center gap-2 text-sm font-mono text-foreground">
<span className="break-all">{meta.geo.trim()}</span>
<Button variant="outline" size="sm" className="h-8 shrink-0" asChild>
<a
href={`https://geohash.org/${encodeURIComponent(meta.geo.trim())}`}
target="_blank"
rel="noopener noreferrer"
>
<a href={nip52GeohashSoftengUrl(meta.geo)} target="_blank" rel="noopener noreferrer">
<ExternalLink className="size-3.5" />
{t('calendarNip52ViewGeohash')}
</a>

2
src/components/ConnectedRelays/ActiveRelaysTitlebarButton.tsx

@ -50,7 +50,7 @@ export function ActiveRelaysTitlebarButton() { @@ -50,7 +50,7 @@ export function ActiveRelaysTitlebarButton() {
<Button
variant="ghost"
size="titlebar-icon"
className="shrink-0 gap-0.5 text-muted-foreground hover:text-primary disabled:opacity-40 max-sm:mr-3 max-sm:pr-1"
className="shrink-0 gap-0.5 text-muted-foreground hover:text-primary disabled:opacity-40"
title={t('Active relays')}
aria-label={t('Active relays')}
disabled={rows.length === 0}

3
src/i18n/locales/cs.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Mapa geohash",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/de.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Orte",
calendarNip52Summary: "Kurzfassung",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "Auf Karte anzeigen",
calendarNip52ViewGeohash: "Geohash-Karte",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Tages-Indizes (NIP-52)",
calendarNip52CalendarInclusion: "Kalender-Einbindung",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/en.ts

@ -240,7 +240,8 @@ export default { @@ -240,7 +240,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Geohash map",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/es.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Mapa Geohash",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/fr.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Carte Geohash",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/nl.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Geohash-kaart",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/pl.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Mapa geohash",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/ru.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Карта геохэша",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/tr.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Geohash haritası",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

3
src/i18n/locales/zh.ts

@ -236,7 +236,8 @@ export default { @@ -236,7 +236,8 @@ export default {
calendarNip52Locations: "Locations",
calendarNip52Summary: "Summary",
calendarNip52Geohash: "Geohash",
calendarNip52ViewGeohash: "View on map",
calendarNip52ViewGeohash: "Geohash 地图",
calendarNip52GoogleMaps: "Google Maps",
calendarNip52DayIndices: "Day indices (NIP-52)",
calendarNip52CalendarInclusion: "Calendar inclusion",
calendarNip52CalendarInclusionHint:

202
src/pages/primary/NoteListPage/index.tsx

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import RelayInfo from '@/components/RelayInfo'
import { RefreshButton } from '@/components/RefreshButton'
import { Button } from '@/components/ui/button'
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
@ -9,11 +8,9 @@ import { useScreenSize } from '@/providers/ScreenSizeProvider' @@ -9,11 +8,9 @@ import { useScreenSize } from '@/providers/ScreenSizeProvider'
import type { TNoteListRef } from '@/components/NoteList'
import { NoteCardLoadingSkeleton } from '@/components/NoteCard'
import { TPageRef } from '@/types'
import { Compass, Info, Star, UsersRound } from 'lucide-react'
import { Calendar, Compass, Star, UsersRound } from 'lucide-react'
import React, {
Dispatch,
forwardRef,
SetStateAction,
useCallback,
useEffect,
useImperativeHandle,
@ -37,7 +34,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => { @@ -37,7 +34,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => {
const feedRef = useRef<TNoteListRef>(null)
const { feedInfo, relayUrls, isReady } = useFeed()
const { isSmallScreen } = useScreenSize()
const [showRelayDetails, setShowRelayDetails] = useState(false)
const [homeSubHeader, setHomeSubHeader] = useState<React.ReactNode>(null)
const usesSubHeader =
@ -100,9 +96,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => { @@ -100,9 +96,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => {
} else {
content = (
<>
{showRelayDetails && feedInfo.feedType === 'relay' && !!feedInfo.id && (
<RelayInfo url={feedInfo.id!} className="mb-2 pt-3" />
)}
<RelaysFeed
ref={feedRef}
setSubHeader={setHomeSubHeaderStable}
@ -150,15 +143,7 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => { @@ -150,15 +143,7 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => {
suppressMobileDefaultActiveRelaysButton
titlebar={
showNoteListTitlebar ? (
<NoteListPageTitlebar
layoutRef={layoutRef}
onFeedRefresh={runFeedRefresh}
showTitlebarRefresh={!usesSubHeader}
showRelayDetails={showRelayDetails}
setShowRelayDetails={
feedInfo.feedType === 'relay' && !!feedInfo.id ? setShowRelayDetails : undefined
}
/>
<NoteListPageTitlebar onFeedRefresh={runFeedRefresh} showTitlebarRefresh={!usesSubHeader} />
) : null
}
subHeader={subHeader}
@ -174,17 +159,11 @@ NoteListPage.displayName = 'NoteListPage' @@ -174,17 +159,11 @@ NoteListPage.displayName = 'NoteListPage'
export default NoteListPage
function NoteListPageTitlebar({
layoutRef,
onFeedRefresh,
showTitlebarRefresh,
showRelayDetails,
setShowRelayDetails
showTitlebarRefresh
}: {
layoutRef?: React.RefObject<TPageRef>
onFeedRefresh: () => void
showTitlebarRefresh: boolean
showRelayDetails?: boolean
setShowRelayDetails?: Dispatch<SetStateAction<boolean>>
}) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
@ -196,109 +175,112 @@ function NoteListPageTitlebar({ @@ -196,109 +175,112 @@ function NoteListPageTitlebar({
const followsLatestActive = display && current === 'follows-latest' && primaryViewType === null
const favoritesActive =
display && current === 'spells' && spell === 'favorites' && primaryViewType === null
const calendarActive = display && current === 'calendar' && primaryViewType === null
if (!isSmallScreen) {
return (
<div className="flex h-full w-full min-w-0 items-center justify-end gap-1 pr-1">
{showTitlebarRefresh ? <RefreshButton onClick={onFeedRefresh} /> : null}
</div>
)
}
/**
* Mobile: avoid absolutely centered logo (overlaps side controls on narrow widths). Three columns
* left/right hug content; center flexes so the banner shrinks. Overflow columns scroll if needed.
*/
return (
<div className="relative flex gap-1 items-center h-full justify-between">
<div className="flex min-w-0 flex-1 items-center gap-1 h-full pl-1 sm:pl-3">
{isSmallScreen && (
<div className="grid h-full w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-0.5 sm:gap-x-1">
<div className="flex min-h-0 min-w-0 items-center justify-start gap-0.5 overflow-x-auto overflow-y-hidden scrollbar-hide sm:gap-1">
<Button
variant="ghost"
size="titlebar-icon"
title={t('Explore')}
aria-label={t('Explore')}
className={`shrink-0 ${exploreActive ? 'bg-accent/50' : ''}`}
onClick={(e) => {
e.stopPropagation()
if (primaryViewType !== null) {
setPrimaryNoteView(null)
} else {
navigate('explore')
}
}}
>
<Compass />
</Button>
{pubkey ? (
<>
<Button
variant="ghost"
size="titlebar-icon"
title={t('Explore')}
aria-label={t('Explore')}
className={exploreActive ? 'bg-accent/50' : ''}
title={t('Follows latest nav label')}
aria-label={t('Follows latest nav label')}
className={`shrink-0 ${followsLatestActive ? 'bg-accent/50' : ''}`}
onClick={(e) => {
e.stopPropagation()
if (primaryViewType !== null) {
setPrimaryNoteView(null)
} else {
navigate('explore')
}
navigate('follows-latest')
}}
>
<Compass />
<UsersRound />
</Button>
<Button
variant="ghost"
size="titlebar-icon"
title={t('Favorites')}
aria-label={t('Favorites')}
className={`shrink-0 ${favoritesActive ? 'bg-accent/50' : ''}`}
onClick={(e) => {
e.stopPropagation()
if (primaryViewType !== null) {
setPrimaryNoteView(null)
}
navigate('spells', { spell: 'favorites' })
}}
>
<Star />
</Button>
{pubkey ? (
<>
<Button
variant="ghost"
size="titlebar-icon"
title={t('Follows latest nav label')}
aria-label={t('Follows latest nav label')}
className={followsLatestActive ? 'bg-accent/50' : ''}
onClick={(e) => {
e.stopPropagation()
if (primaryViewType !== null) {
setPrimaryNoteView(null)
}
navigate('follows-latest')
}}
>
<UsersRound />
</Button>
<Button
variant="ghost"
size="titlebar-icon"
title={t('Favorites')}
aria-label={t('Favorites')}
className={favoritesActive ? 'bg-accent/50' : ''}
onClick={(e) => {
e.stopPropagation()
if (primaryViewType !== null) {
setPrimaryNoteView(null)
}
navigate('spells', { spell: 'favorites' })
}}
>
<Star />
</Button>
</>
) : null}
</>
)}
) : null}
</div>
{isSmallScreen && (
<div className="absolute left-1/2 z-10 -translate-x-1/2 transform">
<button
type="button"
className="flex max-h-10 max-w-[min(72vw,14rem)] cursor-pointer items-center justify-center overflow-hidden rounded-xl bg-card px-1.5 ring-1 ring-border/50"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
<div className="flex min-h-0 min-w-0 items-center justify-center gap-0.5 px-0.5">
<button
type="button"
className="flex min-h-8 min-w-0 max-w-full flex-1 cursor-pointer items-center justify-center overflow-hidden rounded-xl bg-card px-1 py-0.5 ring-1 ring-border/50 sm:px-1.5"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setPrimaryNoteView(null)
}}
aria-label="Imwald"
>
<Logo className="max-h-7 w-full min-w-0 object-contain object-center sm:max-h-8" />
</button>
<Button
variant="ghost"
size="titlebar-icon"
className={`shrink-0 ${calendarActive ? 'bg-accent/50' : ''}`}
title={t('Calendar')}
aria-label={t('Calendar')}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
if (primaryViewType !== null) {
setPrimaryNoteView(null)
}}
aria-label="Imwald"
>
<Logo className="max-h-8 w-full object-contain object-center" />
</button>
</div>
)}
<div className="shrink-0 flex gap-1 items-center">
{showTitlebarRefresh && <RefreshButton onClick={onFeedRefresh} />}
{setShowRelayDetails && (
<Button
variant="ghost"
size="titlebar-icon"
onClick={(e) => {
e.stopPropagation()
setShowRelayDetails((show) => !show)
if (!showRelayDetails) {
layoutRef?.current?.scrollToTop('smooth')
}
}}
className={showRelayDetails ? 'bg-accent/50' : ''}
>
<Info />
</Button>
)}
{isSmallScreen ? (
<>
<ActiveRelaysTitlebarButton />
<HelpAndAccountMenu variant="titlebar" />
</>
) : null}
}
navigate('calendar')
}}
>
<Calendar />
</Button>
</div>
<div className="flex min-h-0 min-w-0 items-center justify-end gap-0.5 overflow-x-auto overflow-y-hidden scrollbar-hide sm:gap-1">
{showTitlebarRefresh ? <RefreshButton onClick={onFeedRefresh} /> : null}
<ActiveRelaysTitlebarButton />
<HelpAndAccountMenu variant="titlebar" />
</div>
</div>
)

Loading…
Cancel
Save