15 changed files with 503 additions and 25 deletions
@ -0,0 +1,62 @@ |
|||||||
|
import { Event } from 'nostr-tools' |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import storage from '@/services/local-storage.service' |
||||||
|
|
||||||
|
/** |
||||||
|
* Check if an event has expired based on its expiration tag |
||||||
|
*/ |
||||||
|
export function isEventExpired(event: Event): boolean { |
||||||
|
const expirationTag = event.tags.find(tag => tag[0] === 'expiration') |
||||||
|
if (!expirationTag || !expirationTag[1]) { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
const expirationTime = parseInt(expirationTag[1]) |
||||||
|
if (isNaN(expirationTime)) { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return dayjs().unix() > expirationTime |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Check if an event is in quiet mode based on its quiet tag |
||||||
|
*/ |
||||||
|
export function isEventInQuietMode(event: Event): boolean { |
||||||
|
const quietTag = event.tags.find(tag => tag[0] === 'quiet') |
||||||
|
if (!quietTag || !quietTag[1]) { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
const quietEndTime = parseInt(quietTag[1]) |
||||||
|
if (isNaN(quietEndTime)) { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return dayjs().unix() < quietEndTime |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Check if interactions should be hidden for an event based on quiet settings |
||||||
|
*/ |
||||||
|
export function shouldHideInteractions(event: Event): boolean { |
||||||
|
// Check global quiet mode first
|
||||||
|
if (storage.getGlobalQuietMode()) { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// Check if we should respect quiet tags
|
||||||
|
if (!storage.getRespectQuietTags()) { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Check if the event is in quiet mode
|
||||||
|
return isEventInQuietMode(event) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Check if an event should be filtered out completely (expired) |
||||||
|
*/ |
||||||
|
export function shouldFilterEvent(event: Event): boolean { |
||||||
|
return isEventExpired(event) |
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
import { Label } from '@/components/ui/label' |
||||||
|
import { Switch } from '@/components/ui/switch' |
||||||
|
import { Input } from '@/components/ui/input' |
||||||
|
import storage from '@/services/local-storage.service' |
||||||
|
import { useEffect, useState } from 'react' |
||||||
|
import { useTranslation } from 'react-i18next' |
||||||
|
|
||||||
|
export default function ExpirationSettings() { |
||||||
|
const { t } = useTranslation() |
||||||
|
const [enabled, setEnabled] = useState(false) |
||||||
|
const [months, setMonths] = useState(6) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setEnabled(storage.getDefaultExpirationEnabled()) |
||||||
|
setMonths(storage.getDefaultExpirationMonths()) |
||||||
|
}, []) |
||||||
|
|
||||||
|
const handleEnabledChange = (checked: boolean) => { |
||||||
|
setEnabled(checked) |
||||||
|
storage.setDefaultExpirationEnabled(checked) |
||||||
|
} |
||||||
|
|
||||||
|
const handleMonthsChange = (value: string) => { |
||||||
|
const num = parseInt(value) |
||||||
|
if (!isNaN(num) && num >= 0 && Number.isInteger(num)) { |
||||||
|
setMonths(num) |
||||||
|
storage.setDefaultExpirationMonths(num) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="space-y-4"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<div className="flex items-center space-x-2"> |
||||||
|
<Label htmlFor="expiration-enabled">{t('Add expiration tags by default')}</Label> |
||||||
|
<Switch |
||||||
|
id="expiration-enabled" |
||||||
|
checked={enabled} |
||||||
|
onCheckedChange={handleEnabledChange} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="text-muted-foreground text-xs"> |
||||||
|
{t('Posts will automatically include expiration tags')} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{enabled && ( |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="expiration-months">{t('Default expiration (months)')}</Label> |
||||||
|
<Input |
||||||
|
id="expiration-months" |
||||||
|
type="number" |
||||||
|
min="0" |
||||||
|
step="1" |
||||||
|
value={months} |
||||||
|
onChange={(e) => handleMonthsChange(e.target.value)} |
||||||
|
className="w-24" |
||||||
|
/> |
||||||
|
<div className="text-muted-foreground text-xs"> |
||||||
|
{t('Posts will expire after this many months')} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,108 @@ |
|||||||
|
import { Label } from '@/components/ui/label' |
||||||
|
import { Switch } from '@/components/ui/switch' |
||||||
|
import { Input } from '@/components/ui/input' |
||||||
|
import storage from '@/services/local-storage.service' |
||||||
|
import { useEffect, useState } from 'react' |
||||||
|
import { useTranslation } from 'react-i18next' |
||||||
|
|
||||||
|
export default function QuietSettings() { |
||||||
|
const { t } = useTranslation() |
||||||
|
const [enabled, setEnabled] = useState(false) |
||||||
|
const [days, setDays] = useState(7) |
||||||
|
const [respectQuietTags, setRespectQuietTags] = useState(true) |
||||||
|
const [globalQuietMode, setGlobalQuietMode] = useState(false) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setEnabled(storage.getDefaultQuietEnabled()) |
||||||
|
setDays(storage.getDefaultQuietDays()) |
||||||
|
setRespectQuietTags(storage.getRespectQuietTags()) |
||||||
|
setGlobalQuietMode(storage.getGlobalQuietMode()) |
||||||
|
}, []) |
||||||
|
|
||||||
|
const handleEnabledChange = (checked: boolean) => { |
||||||
|
setEnabled(checked) |
||||||
|
storage.setDefaultQuietEnabled(checked) |
||||||
|
} |
||||||
|
|
||||||
|
const handleDaysChange = (value: string) => { |
||||||
|
const num = parseInt(value) |
||||||
|
if (!isNaN(num) && num >= 0 && Number.isInteger(num)) { |
||||||
|
setDays(num) |
||||||
|
storage.setDefaultQuietDays(num) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const handleRespectQuietTagsChange = (checked: boolean) => { |
||||||
|
setRespectQuietTags(checked) |
||||||
|
storage.setRespectQuietTags(checked) |
||||||
|
} |
||||||
|
|
||||||
|
const handleGlobalQuietModeChange = (checked: boolean) => { |
||||||
|
setGlobalQuietMode(checked) |
||||||
|
storage.setGlobalQuietMode(checked) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="space-y-4"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<div className="flex items-center space-x-2"> |
||||||
|
<Label htmlFor="quiet-enabled">{t('Add quiet tags by default')}</Label> |
||||||
|
<Switch |
||||||
|
id="quiet-enabled" |
||||||
|
checked={enabled} |
||||||
|
onCheckedChange={handleEnabledChange} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="text-muted-foreground text-xs"> |
||||||
|
{t('Posts will automatically include quiet tags')} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{enabled && ( |
||||||
|
<div className="space-y-2"> |
||||||
|
<Label htmlFor="quiet-days">{t('Default quiet period (days)')}</Label> |
||||||
|
<Input |
||||||
|
id="quiet-days" |
||||||
|
type="number" |
||||||
|
min="0" |
||||||
|
step="1" |
||||||
|
value={days} |
||||||
|
onChange={(e) => handleDaysChange(e.target.value)} |
||||||
|
className="w-24" |
||||||
|
/> |
||||||
|
<div className="text-muted-foreground text-xs"> |
||||||
|
{t('Posts will be quiet for this many days')} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<div className="flex items-center space-x-2"> |
||||||
|
<Label htmlFor="respect-quiet-tags">{t('Respect quiet tags')}</Label> |
||||||
|
<Switch |
||||||
|
id="respect-quiet-tags" |
||||||
|
checked={respectQuietTags} |
||||||
|
onCheckedChange={handleRespectQuietTagsChange} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="text-muted-foreground text-xs"> |
||||||
|
{t('Hide interactions on posts with quiet tags')} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="space-y-2"> |
||||||
|
<div className="flex items-center space-x-2"> |
||||||
|
<Label htmlFor="global-quiet-mode">{t('Global quiet mode')}</Label> |
||||||
|
<Switch |
||||||
|
id="global-quiet-mode" |
||||||
|
checked={globalQuietMode} |
||||||
|
onCheckedChange={handleGlobalQuietModeChange} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="text-muted-foreground text-xs"> |
||||||
|
{t('Hide interactions on all posts')} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
Loading…
Reference in new issue