Porównaj commity

...

22 Commity

Autor SHA1 Wiadomość Data
marcin mikołajczak 29f4322d01 Merge branch 'missing-description-confirmation' into 'main'
Move missing description confirmation setting back to Settings

See merge request soapbox-pub/soapbox!2979
2024-05-03 04:43:50 +00:00
Alex Gleason 1f665defb6 Merge branch 'block-feature' into 'main'
Feature-gate blocks

See merge request soapbox-pub/soapbox!3015
2024-05-02 20:56:33 +00:00
Alex Gleason 80b500e710
Feature-gate blocks 2024-05-02 15:38:01 -05:00
Alex Gleason 2869b2e9f3 Merge branch 'admin-relays' into 'main'
Nostr Admin Relays

See merge request soapbox-pub/soapbox!3014
2024-05-02 00:19:23 +00:00
Alex Gleason 235476b7b2
yarn i18n 2024-05-01 19:01:29 -05:00
Alex Gleason ac46a89068
Add link to Nostr Relays in dashboard 2024-05-01 18:34:20 -05:00
Alex Gleason 61ead81ed9
Allow updating admin relays 2024-05-01 18:31:53 -05:00
Alex Gleason 060014ff92
Scaffold admin relays page 2024-05-01 17:15:35 -05:00
Alex Gleason 4e7a259dd4 Merge branch 'nostr-keys-parse' into 'main'
NKeyStorage: fix dataSchema so it correctly parses the result as an array

See merge request soapbox-pub/soapbox!3013
2024-05-01 18:35:10 +00:00
Alex Gleason 8d20a14251
NKeyStorage: fix dataSchema so it correctly parses the result as an array 2024-05-01 13:17:49 -05:00
Alex Gleason c41b2c794e Merge branch 'global-feed' into 'main'
Rename "Fediverse" tab to "Global"

See merge request soapbox-pub/soapbox!3012
2024-04-30 23:49:36 +00:00
Alex Gleason 1a0970c3bd
Rename "Fediverse" tab to "Global" 2024-04-30 18:30:30 -05:00
Alex Gleason 35354d16b5 Merge branch 'nostrify' into 'main'
Switch from NSpec to Nostrify

See merge request soapbox-pub/soapbox!3011
2024-04-30 19:48:26 +00:00
Alex Gleason 49a25f2eb0
useSignerStream: simplify with nostrify 2024-04-30 14:30:34 -05:00
Alex Gleason df407381b2
Upgrade zod to v3.23.5 2024-04-30 14:26:30 -05:00
Alex Gleason 6bfc0f95a4
@soapbox/nspec -> @nostrify/nostrify 2024-04-30 13:55:10 -05:00
Alex Gleason 1cd41450ee Merge branch 'nip46-full' into 'main'
Support the full NIP-46 protocol

See merge request soapbox-pub/soapbox!3009
2024-04-28 16:32:38 +00:00
Alex Gleason d2a28ea3c9
Support the full NIP-46 protocol 2024-04-28 11:13:54 -05:00
marcin mikołajczak 39a9e174d4 Merge branch 'announcements' into 'main'
Announcement reactions fix

See merge request soapbox-pub/soapbox!3008
2024-04-28 13:39:50 +00:00
marcin mikołajczak 43cc692c37 Announcement reactions fix
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-04-28 15:23:57 +02:00
marcin mikołajczak 8ef1de5f7a Merge remote-tracking branch 'origin/main' into missing-description-confirmation
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-04-04 11:05:04 +02:00
marcin mikołajczak 75be34ce45 Move missing description confirmation setting back to Settings, add optional indicator for attachments without description
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-04-04 00:12:36 +02:00
23 zmienionych plików z 276 dodań i 138 usunięć

Wyświetl plik

@ -63,6 +63,7 @@
"@lexical/utils": "^0.14.2",
"@mkljczk/react-hotkeys": "^1.2.2",
"@noble/hashes": "^1.3.3",
"@nostrify/nostrify": "npm:@jsr/nostrify__nostrify",
"@popperjs/core": "^2.11.5",
"@reach/combobox": "^0.18.0",
"@reach/menu-button": "^0.18.0",
@ -73,7 +74,6 @@
"@sentry/browser": "^7.74.1",
"@sentry/react": "^7.74.1",
"@soapbox.pub/wasmboy": "^0.8.0",
"@soapbox/nspec": "npm:@jsr/soapbox__nspec",
"@soapbox/weblock": "npm:@jsr/soapbox__weblock",
"@tabler/icons": "^3.1.0",
"@tailwindcss/aspect-ratio": "^0.4.2",
@ -187,7 +187,7 @@
"vite-plugin-require": "^1.1.10",
"vite-plugin-static-copy": "^1.0.0",
"wicg-inert": "^3.1.1",
"zod": "^3.22.4"
"zod": "^3.23.5"
},
"devDependencies": {
"@formatjs/cli": "^6.2.0",

Wyświetl plik

@ -1,37 +1,91 @@
import { type NostrEvent } from '@soapbox/nspec';
import { NostrEvent, NostrConnectResponse, NSchema as n } from '@nostrify/nostrify';
import { useEffect } from 'react';
import { useNostr } from 'soapbox/contexts/nostr-context';
import { connectRequestSchema, nwcRequestSchema } from 'soapbox/schemas/nostr';
import { jsonSchema } from 'soapbox/schemas/utils';
import { nwcRequestSchema } from 'soapbox/schemas/nostr';
function useSignerStream() {
const { relay, pubkey, signer } = useNostr();
async function sendConnect(response: NostrConnectResponse) {
if (!relay || !pubkey || !signer) return;
const event = await signer.signEvent({
kind: 24133,
content: await signer.nip04!.encrypt(pubkey, JSON.stringify(response)),
tags: [['p', pubkey]],
created_at: Math.floor(Date.now() / 1000),
});
relay.event(event);
}
async function handleConnectEvent(event: NostrEvent) {
if (!relay || !pubkey || !signer) return;
const decrypted = await signer.nip04!.decrypt(pubkey, event.content);
const reqMsg = jsonSchema.pipe(connectRequestSchema).safeParse(decrypted);
const reqMsg = n.json().pipe(n.connectRequest()).safeParse(decrypted);
if (!reqMsg.success) {
console.warn(decrypted);
console.warn(reqMsg.error);
return;
}
const respMsg = {
id: reqMsg.data.id,
result: JSON.stringify(await signer.signEvent(JSON.parse(reqMsg.data.params[0]))),
};
const request = reqMsg.data;
const respEvent = await signer.signEvent({
kind: 24133,
content: await signer.nip04!.encrypt(pubkey, JSON.stringify(respMsg)),
tags: [['p', pubkey]],
created_at: Math.floor(Date.now() / 1000),
});
relay.event(respEvent);
switch (request.method) {
case 'connect':
return sendConnect({
id: request.id,
result: 'ack',
});
case 'sign_event':
return sendConnect({
id: request.id,
result: JSON.stringify(await signer.signEvent(JSON.parse(request.params[0]))),
});
case 'ping':
return sendConnect({
id: request.id,
result: 'pong',
});
case 'get_relays':
return sendConnect({
id: request.id,
result: JSON.stringify(await signer.getRelays?.() ?? []),
});
case 'get_public_key':
return sendConnect({
id: request.id,
result: await signer.getPublicKey(),
});
case 'nip04_encrypt':
return sendConnect({
id: request.id,
result: await signer.nip04!.encrypt(request.params[0], request.params[1]),
});
case 'nip04_decrypt':
return sendConnect({
id: request.id,
result: await signer.nip04!.decrypt(request.params[0], request.params[1]),
});
case 'nip44_encrypt':
return sendConnect({
id: request.id,
result: await signer.nip44!.encrypt(request.params[0], request.params[1]),
});
case 'nip44_decrypt':
return sendConnect({
id: request.id,
result: await signer.nip44!.decrypt(request.params[0], request.params[1]),
});
default:
return sendConnect({
id: request.id,
result: '',
error: `Unrecognized method: ${request.method}`,
});
}
}
async function handleWalletEvent(event: NostrEvent) {
@ -39,7 +93,7 @@ function useSignerStream() {
const decrypted = await signer.nip04!.decrypt(pubkey, event.content);
const reqMsg = jsonSchema.pipe(nwcRequestSchema).safeParse(decrypted);
const reqMsg = n.json().pipe(nwcRequestSchema).safeParse(decrypted);
if (!reqMsg.success) {
console.warn(decrypted);
console.warn(reqMsg.error);

Wyświetl plik

@ -11,7 +11,7 @@ import { useAccount } from 'soapbox/api/hooks';
import Account from 'soapbox/components/account';
import { Stack, Divider, HStack, Icon, IconButton, Text } from 'soapbox/components/ui';
import ProfileStats from 'soapbox/features/ui/components/profile-stats';
import { useAppDispatch, useAppSelector, useGroupsPath, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useGroupsPath, useFeatures, useInstance } from 'soapbox/hooks';
import { makeGetOtherAccounts } from 'soapbox/selectors';
import type { List as ImmutableList } from 'immutable';
@ -89,6 +89,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
const settings = useAppSelector((state) => getSettings(state));
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
const groupsPath = useGroupsPath();
const instance = useInstance();
const closeButtonRef = React.useRef(null);
@ -248,16 +249,16 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<SidebarLink
to='/timeline/local'
icon={features.federating ? require('@tabler/icons/outline/affiliate.svg') : require('@tabler/icons/outline/world.svg')}
text={features.federating ? <FormattedMessage id='tabs_bar.local' defaultMessage='Local' /> : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />}
icon={features.federating ? require('@tabler/icons/outline/users-group.svg') : require('@tabler/icons/outline/world.svg')}
text={features.federating ? instance.title : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />}
onClick={onClose}
/>
{features.federating && (
<SidebarLink
to='/timeline/fediverse'
icon={require('@tabler/icons/outline/topology-star-ring-3.svg')}
text={<FormattedMessage id='tabs_bar.fediverse' defaultMessage='Fediverse' />}
to='/timeline/global'
icon={require('@tabler/icons/outline/world.svg')}
text={<FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
onClick={onClose}
/>
)}
@ -265,12 +266,14 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<Divider />
<SidebarLink
to='/blocks'
icon={require('@tabler/icons/outline/ban.svg')}
text={intl.formatMessage(messages.blocks)}
onClick={onClose}
/>
{features.blocks && (
<SidebarLink
to='/blocks'
icon={require('@tabler/icons/outline/ban.svg')}
text={intl.formatMessage(messages.blocks)}
onClick={onClose}
/>
)}
<SidebarLink
to='/mutes'

Wyświetl plik

@ -179,17 +179,16 @@ const SidebarNavigation = () => {
{(account || !restrictUnauth.timelines.local) && (
<SidebarNavigationLink
to='/timeline/local'
icon={features.federating ? require('@tabler/icons/outline/affiliate.svg') : require('@tabler/icons/outline/world.svg')}
activeIcon={features.federating ? require('@tabler/icons/filled/affiliate.svg') : undefined}
text={features.federating ? <FormattedMessage id='tabs_bar.local' defaultMessage='Local' /> : <FormattedMessage id='tabs_bar.all' defaultMessage='All' />}
icon={features.federating ? require('@tabler/icons/outline/users-group.svg') : require('@tabler/icons/outline/world.svg')}
text={features.federating ? instance.title : <FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
/>
)}
{(features.federating && (account || !restrictUnauth.timelines.federated)) && (
<SidebarNavigationLink
to='/timeline/fediverse'
icon={require('@tabler/icons/outline/topology-star-ring-3.svg')}
text={<FormattedMessage id='tabs_bar.fediverse' defaultMessage='Fediverse' />}
to='/timeline/global'
icon={require('@tabler/icons/outline/world.svg')}
text={<FormattedMessage id='tabs_bar.global' defaultMessage='Global' />}
/>
)}
</>

Wyświetl plik

@ -1,4 +1,4 @@
import { NRelay, NRelay1, NostrSigner } from '@soapbox/nspec';
import { NRelay, NRelay1, NostrSigner } from '@nostrify/nostrify';
import React, { createContext, useContext, useState, useEffect, useMemo } from 'react';
import { NKeys } from 'soapbox/features/nostr/keys';

Wyświetl plik

@ -0,0 +1,21 @@
import { useQuery } from '@tanstack/react-query';
import { z } from 'zod';
import { useApi } from 'soapbox/hooks';
const relayEntitySchema = z.object({
url: z.string().url(),
marker: z.enum(['read', 'write']).optional(),
});
export function useAdminNostrRelays() {
const api = useApi();
return useQuery({
queryKey: ['NostrRelay'],
queryFn: async () => {
const { data } = await api.get('/api/v1/admin/ditto/relays');
return relayEntitySchema.array().parse(data);
},
});
}

Wyświetl plik

@ -0,0 +1,55 @@
import { useMutation } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { Button, Column, Form, FormActions, Stack } from 'soapbox/components/ui';
import RelayEditor, { RelayData } from 'soapbox/features/nostr-relays/components/relay-editor';
import { useApi } from 'soapbox/hooks';
import { useAdminNostrRelays } from './hooks/useAdminNostrRelays';
const messages = defineMessages({
title: { id: 'column.admin.nostr_relays', defaultMessage: 'Relays' },
});
const AdminNostrRelays: React.FC = () => {
const api = useApi();
const intl = useIntl();
const result = useAdminNostrRelays();
const [relays, setRelays] = useState<RelayData[]>(result.data ?? []);
const mutation = useMutation({
mutationFn: async () => api.put('/api/v1/admin/ditto/relays', relays),
});
const handleSubmit = () => {
mutation.mutate();
};
useEffect(() => {
setRelays(result.data ?? []);
}, [result.data]);
return (
<Column label={intl.formatMessage(messages.title)}>
<Form onSubmit={handleSubmit}>
<Stack space={4}>
<RelayEditor relays={relays} setRelays={setRelays} />
<FormActions>
<Button to='/soapbox/admin' theme='tertiary'>
<FormattedMessage id='common.cancel' defaultMessage='Cancel' />
</Button>
<Button theme='primary' type='submit' disabled={mutation.isPending}>
<FormattedMessage id='edit_profile.save' defaultMessage='Save' />
</Button>
</FormActions>
</Stack>
</Form>
</Column>
);
};
export default AdminNostrRelays;

Wyświetl plik

@ -113,6 +113,13 @@ const Dashboard: React.FC = () => {
label={<FormattedMessage id='column.admin.domains' defaultMessage='Domains' />}
/>
)}
{features.nostr && (
<ListItem
to='/soapbox/admin/nostr/relays'
label={<FormattedMessage id='column.admin.nostr_relays' defaultMessage='Relays' />}
/>
)}
</List>
{account.admin && (

Wyświetl plik

@ -118,10 +118,6 @@ const SettingsStore: React.FC = () => {
<SettingToggle settings={settings} settingPath={['unfollowModal']} onChange={onToggleChange} />
</ListItem>
<ListItem label={<FormattedMessage id='preferences.fields.missing_description_modal_label' defaultMessage='Show confirmation dialog before sending a post without media descriptions' />}>
<SettingToggle settings={settings} settingPath={['missingDescriptionModal']} onChange={onToggleChange} />
</ListItem>
<ListItem label={<FormattedMessage id='preferences.fields.reduce_motion_label' defaultMessage='Reduce motion in animations' />}>
<SettingToggle settings={settings} settingPath={['reduceMotion']} onChange={onToggleChange} />
</ListItem>

Wyświetl plik

@ -18,8 +18,8 @@ const NostrRelays = () => {
const { relay, signer } = useNostr();
const { events } = useNostrReq(
account?.nostr
? [{ kinds: [10002], authors: [account?.nostr.pubkey], limit: 1 }]
account?.nostr?.pubkey
? [{ kinds: [10002], authors: [account.nostr.pubkey], limit: 1 }]
: [],
);

Wyświetl plik

@ -1,4 +1,4 @@
import { NSchema as n, NostrSigner, NSecSigner } from '@soapbox/nspec';
import { NSchema as n, NostrSigner, NSecSigner } from '@nostrify/nostrify';
import { WebLock } from '@soapbox/weblock';
import { getPublicKey, nip19 } from 'nostr-tools';
import { z } from 'zod';
@ -22,7 +22,7 @@ export class NKeyStorage implements ReadonlyMap<string, NostrSigner> {
WebLock.storages.lockKey(storageKey);
try {
const nsecs = new Set(this.#dataSchema().parse(data));
const nsecs = new Set(this.dataSchema().parse(data));
for (const nsec of nsecs) {
const { data: secretKey } = nip19.decode(nsec);
@ -34,8 +34,8 @@ export class NKeyStorage implements ReadonlyMap<string, NostrSigner> {
}
}
#dataSchema(): z.ZodType<`nsec1${string}`[]> {
return n.json().pipe(n.bech32('nsec').array());
private dataSchema(): z.ZodType<`nsec1${string}`[]> {
return n.json().pipe(n.bech32('nsec').array()) as z.ZodType<`nsec1${string}`[]>;
}
#syncStorage() {

Wyświetl plik

@ -1,4 +1,4 @@
import { NSet, NostrEvent, NostrFilter } from '@soapbox/nspec';
import { NSet, NostrEvent, NostrFilter } from '@nostrify/nostrify';
import isEqual from 'lodash/isEqual';
import { useEffect, useRef, useState } from 'react';

Wyświetl plik

@ -190,6 +190,10 @@ const Preferences = () => {
<ListItem label={<FormattedMessage id='preferences.fields.delete_modal_label' defaultMessage='Show confirmation dialog before deleting a post' />}>
<SettingToggle settings={settings} settingPath={['deleteModal']} onChange={onToggleChange} />
</ListItem>
<ListItem label={<FormattedMessage id='preferences.fields.missing_description_modal_label' defaultMessage='Show confirmation dialog before sending a post without media descriptions' />}>
<SettingToggle settings={settings} settingPath={['missingDescriptionModal']} onChange={onToggleChange} />
</ListItem>
</List>
<List>

Wyświetl plik

@ -13,7 +13,7 @@ import PinnedHostsPicker from '../remote-timeline/components/pinned-hosts-picker
import Timeline from '../ui/components/timeline';
const messages = defineMessages({
title: { id: 'column.public', defaultMessage: 'Fediverse timeline' },
title: { id: 'column.public', defaultMessage: 'Global timeline' },
dismiss: { id: 'fediverse_tab.explanation_box.dismiss', defaultMessage: 'Don\'t show again' },
});

Wyświetl plik

@ -43,7 +43,9 @@ const LinkFooter: React.FC = (): JSX.Element => {
{features.profileDirectory && (
<FooterLink to='/directory'><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></FooterLink>
)}
<FooterLink to='/blocks'><FormattedMessage id='navigation_bar.blocks' defaultMessage='Blocks' /></FooterLink>
{features.blocks && (
<FooterLink to='/blocks'><FormattedMessage id='navigation_bar.blocks' defaultMessage='Blocks' /></FooterLink>
)}
<FooterLink to='/mutes'><FormattedMessage id='navigation_bar.mutes' defaultMessage='Mutes' /></FooterLink>
{(features.filters || features.filtersV2) && (
<FooterLink to='/filters'><FormattedMessage id='navigation_bar.filters' defaultMessage='Filters' /></FooterLink>

Wyświetl plik

@ -142,6 +142,7 @@ import {
Bech32Redirect,
Relays,
Rules,
AdminNostrRelays,
} from './util/async-components';
import GlobalHotkeys from './util/global-hotkeys';
import { WrappedRoute } from './util/react-router-helpers';
@ -187,7 +188,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
https://stackoverflow.com/a/68637108
*/}
{features.federating && <WrappedRoute path='/timeline/local' exact page={HomePage} component={CommunityTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/fediverse' exact page={HomePage} component={PublicTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/global' exact page={HomePage} component={PublicTimeline} content={children} publicRoute />}
{features.federating && <WrappedRoute path='/timeline/:instance' exact page={RemoteInstancePage} component={RemoteTimeline} content={children} />}
{features.conversations && <WrappedRoute path='/conversations' page={DefaultPage} component={Conversations} content={children} />}
@ -202,11 +203,11 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
<Redirect from='/web/:path' to='/:path' />
<Redirect from='/timelines/home' to='/' />
<Redirect from='/timelines/public/local' to='/timeline/local' />
<Redirect from='/timelines/public' to='/timeline/fediverse' />
<Redirect from='/timelines/public' to='/timeline/global' />
<Redirect from='/timelines/direct' to='/messages' />
{/* Pleroma FE web routes */}
<Redirect from='/main/all' to='/timeline/fediverse' />
<Redirect from='/main/all' to='/timeline/global' />
<Redirect from='/main/public' to='/timeline/local' />
<Redirect from='/main/friends' to='/' />
<Redirect from='/tag/:id' to='/tags/:id' />
@ -244,6 +245,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
<Redirect from='/auth/mfa' to='/settings/mfa' />
<Redirect from='/auth/password/new' to='/reset-password' />
<Redirect from='/auth/password/edit' to={`/edit-password${search}`} />
<Redirect from='/timeline/fediverse' to='/timeline/global' />
<WrappedRoute path='/tags/:id' publicRoute page={DefaultPage} component={HashtagTimeline} content={children} />
@ -266,7 +268,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
{features.chats && <WrappedRoute path='/chats/:chatId' page={ChatsPage} component={ChatIndex} content={children} />}
<WrappedRoute path='/follow_requests' page={DefaultPage} component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' page={DefaultPage} component={Blocks} content={children} />
{features.blocks && <WrappedRoute path='/blocks' page={DefaultPage} component={Blocks} content={children} />}
{features.federating && <WrappedRoute path='/domain_blocks' page={DefaultPage} component={DomainBlocks} content={children} />}
<WrappedRoute path='/mutes' page={DefaultPage} component={Mutes} content={children} />
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters/new' page={DefaultPage} component={EditFilter} content={children} />}
@ -333,6 +335,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
<WrappedRoute path='/soapbox/admin/users' staffOnly page={AdminPage} component={UserIndex} content={children} exact />
<WrappedRoute path='/soapbox/admin/theme' staffOnly page={AdminPage} component={ThemeEditor} content={children} exact />
<WrappedRoute path='/soapbox/admin/relays' staffOnly page={AdminPage} component={Relays} content={children} exact />
{features.nostr && <WrappedRoute path='/soapbox/admin/nostr/relays' staffOnly page={AdminPage} component={AdminNostrRelays} content={children} exact />}
{features.adminAnnouncements && <WrappedRoute path='/soapbox/admin/announcements' staffOnly page={AdminPage} component={Announcements} content={children} exact />}
{features.domains && <WrappedRoute path='/soapbox/admin/domains' staffOnly page={AdminPage} component={Domains} content={children} exact />}
{features.adminRules && <WrappedRoute path='/soapbox/admin/rules' staffOnly page={AdminPage} component={Rules} content={children} exact />}

Wyświetl plik

@ -175,3 +175,4 @@ export const Bech32Redirect = lazy(() => import('soapbox/features/nostr/Bech32Re
export const Relays = lazy(() => import('soapbox/features/admin/relays'));
export const Rules = lazy(() => import('soapbox/features/admin/rules'));
export const EditRuleModal = lazy(() => import('soapbox/features/ui/components/modals/edit-rule-modal'));
export const AdminNostrRelays = lazy(() => import('soapbox/features/admin/nostr-relays'));

Wyświetl plik

@ -338,6 +338,7 @@
"column.admin.edit_domain": "Edit domain",
"column.admin.edit_rule": "Edit rule",
"column.admin.moderation_log": "Moderation Log",
"column.admin.nostr_relays": "Relays",
"column.admin.relays": "Instance relays",
"column.admin.reports": "Reports",
"column.admin.reports.menu.moderation_log": "Moderation Log",
@ -423,7 +424,7 @@
"column.notifications": "Notifications",
"column.pins": "Pinned posts",
"column.preferences": "Preferences",
"column.public": "Fediverse timeline",
"column.public": "Global timeline",
"column.quotes": "Post quotes",
"column.reactions": "Reactions",
"column.reblogs": "Reposts",
@ -1569,10 +1570,9 @@
"sw.url": "Script URL",
"tabs_bar.all": "All",
"tabs_bar.dashboard": "Dashboard",
"tabs_bar.fediverse": "Fediverse",
"tabs_bar.global": "Global",
"tabs_bar.groups": "Groups",
"tabs_bar.home": "Home",
"tabs_bar.local": "Local",
"tabs_bar.more": "More",
"tabs_bar.notifications": "Notifications",
"tabs_bar.profile": "Profile",

Wyświetl plik

@ -1,4 +1,4 @@
import { NSchema as n } from '@soapbox/nspec';
import { NSchema as n } from '@nostrify/nostrify';
import escapeTextContentForBrowser from 'escape-html';
import DOMPurify from 'isomorphic-dompurify';
import z from 'zod';

Wyświetl plik

@ -1,35 +1,9 @@
import { NSchema as n } from '@nostrify/nostrify';
import { verifyEvent } from 'nostr-tools';
import { z } from 'zod';
/** Schema to validate Nostr hex IDs such as event IDs and pubkeys. */
const nostrIdSchema = z.string().regex(/^[0-9a-f]{64}$/);
/** Nostr kinds are positive integers. */
const kindSchema = z.number().int().nonnegative();
/** Nostr event template schema. */
const eventTemplateSchema = z.object({
kind: kindSchema,
tags: z.array(z.array(z.string())),
content: z.string(),
created_at: z.number(),
});
/** Nostr event schema. */
const eventSchema = eventTemplateSchema.extend({
id: nostrIdSchema,
pubkey: nostrIdSchema,
sig: z.string(),
});
/** Nostr event schema that also verifies the event's signature. */
const signedEventSchema = eventSchema.refine(verifyEvent);
/** NIP-46 signer request. */
const connectRequestSchema = z.object({
id: z.string(),
method: z.literal('sign_event'),
params: z.tuple([z.string()]),
});
const signedEventSchema = n.event().refine(verifyEvent);
/** NIP-47 signer response. */
const nwcRequestSchema = z.object({
@ -39,4 +13,4 @@ const nwcRequestSchema = z.object({
}),
});
export { nostrIdSchema, kindSchema, eventSchema, signedEventSchema, connectRequestSchema, nwcRequestSchema };
export { signedEventSchema, nwcRequestSchema };

Wyświetl plik

@ -1,4 +1,4 @@
import type { NostrSigner } from '@soapbox/nspec';
import type { NostrSigner } from '@nostrify/nostrify';
declare global {
interface Window {

Wyświetl plik

@ -231,7 +231,7 @@ const getInstanceFeatures = (instance: Instance) => {
* @see DELETE /api/v1/announcements/:id/reactions/:name
* @see {@link https://docs.joinmastodon.org/methods/announcements/}
*/
announcementsReactions: true, // v.software === MASTODON && gte(v.compatVersion, '3.1.0'),
announcementsReactions: v.software === MASTODON && gte(v.compatVersion, '3.1.0'),
/**
* Pleroma backups.
@ -251,6 +251,14 @@ const getInstanceFeatures = (instance: Instance) => {
/** Whether people who blocked you are visible through the API. */
blockersVisible: features.includes('blockers_visible'),
/**
* Ability to block users.
* @see POST /api/v1/accounts/:id/block
* @see POST /api/v1/accounts/:id/unblock
* @see GET /api/v1/blocks
*/
blocks: v.software !== DITTO,
/**
* Can group bookmarks in folders.
* @see GET /api/v1/pleroma/bookmark_folders

107
yarn.lock
Wyświetl plik

@ -1877,12 +1877,12 @@
dependencies:
"@noble/hashes" "1.3.2"
"@noble/curves@~1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e"
integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==
"@noble/curves@~1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6"
integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==
dependencies:
"@noble/hashes" "1.3.3"
"@noble/hashes" "1.4.0"
"@noble/hashes@1.3.1":
version "1.3.1"
@ -1894,7 +1894,12 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==
"@noble/hashes@1.3.3", "@noble/hashes@^1.3.3", "@noble/hashes@~1.3.2":
"@noble/hashes@1.4.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==
"@noble/hashes@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699"
integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==
@ -1920,6 +1925,21 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@nostrify/nostrify@npm:@jsr/nostrify__nostrify":
version "0.17.1"
resolved "https://npm.jsr.io/~/10/@jsr/nostrify__nostrify/0.17.1.tgz#130487F4CF4715F1036A687E7EA819D3ADEC9B6F"
integrity sha512-+mUwudIJFSsnVD3+7PdFG+s27tOf9F1OROyRv7+cWoPWK9UnWlGFAQ50GIeDGYv9nd+gp8N7nu31r/cmqHmORw==
dependencies:
"@noble/hashes" "^1.4.0"
"@scure/base" "^1.1.6"
"@scure/bip32" "^1.4.0"
"@scure/bip39" "^1.3.0"
kysely "^0.27.3"
lru-cache "^10.2.0"
nostr-tools "^2.5.0"
websocket-ts "^2.1.5"
zod "^3.23.4"
"@popperjs/core@^2.11.5", "@popperjs/core@^2.9.2":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
@ -2153,10 +2173,10 @@
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
"@scure/base@^1.1.5", "@scure/base@~1.1.4":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157"
integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==
"@scure/base@^1.1.6", "@scure/base@~1.1.6":
version "1.1.6"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d"
integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==
"@scure/bip32@1.3.1":
version "1.3.1"
@ -2167,14 +2187,14 @@
"@noble/hashes" "~1.3.1"
"@scure/base" "~1.1.0"
"@scure/bip32@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8"
integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==
"@scure/bip32@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67"
integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==
dependencies:
"@noble/curves" "~1.3.0"
"@noble/hashes" "~1.3.2"
"@scure/base" "~1.1.4"
"@noble/curves" "~1.4.0"
"@noble/hashes" "~1.4.0"
"@scure/base" "~1.1.6"
"@scure/bip39@1.2.1":
version "1.2.1"
@ -2184,13 +2204,13 @@
"@noble/hashes" "~1.3.0"
"@scure/base" "~1.1.0"
"@scure/bip39@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527"
integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==
"@scure/bip39@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3"
integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==
dependencies:
"@noble/hashes" "~1.3.2"
"@scure/base" "~1.1.4"
"@noble/hashes" "~1.4.0"
"@scure/base" "~1.1.6"
"@sentry-internal/tracing@7.74.1":
version "7.74.1"
@ -2276,20 +2296,6 @@
raf "^3.4.0"
responsive-gamepad "1.1.0"
"@soapbox/nspec@npm:@jsr/soapbox__nspec":
version "0.6.0"
resolved "https://npm.jsr.io/~/6/@jsr/soapbox__nspec/0.6.0.tgz#60A75BCDBEC1B76DFA91BEFDF5505CCB8ADDAD3B"
integrity sha512-HY+MssBjm532J9SAqLek8YGxBlEaXdT1Eek3bOWkq4uLJxipJhYkdQrW+NzXhfVvGZUt6YXBobeSqRQx1JFgkQ==
dependencies:
"@noble/hashes" "^1.3.3"
"@scure/base" "^1.1.5"
"@scure/bip32" "^1.3.3"
"@scure/bip39" "^1.2.2"
lru-cache "^10.2.0"
nostr-tools "^2.3.1"
websocket-ts "^2.1.5"
zod "^3.22.4"
"@soapbox/weblock@npm:@jsr/soapbox__weblock":
version "0.1.0"
resolved "https://npm.jsr.io/~/7/@jsr/soapbox__weblock/0.1.0.tgz#749AEE0872D23CC4E37366D5F0D092B87986C5E1"
@ -6153,6 +6159,11 @@ known-css-properties@^0.29.0:
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.29.0.tgz#e8ba024fb03886f23cb882e806929f32d814158f"
integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==
kysely@^0.27.3:
version "0.27.3"
resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.3.tgz#6cc6c757040500b43c4ac596cdbb12be400ee276"
integrity sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==
language-subtag-registry@~0.3.2:
version "0.3.21"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
@ -6396,9 +6407,9 @@ lowercase-keys@^2.0.0:
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
lru-cache@^10.2.0:
version "10.2.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
version "10.2.2"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878"
integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==
lru-cache@^4.1.2:
version "4.1.5"
@ -6724,10 +6735,10 @@ nostr-tools@^2.3.0:
optionalDependencies:
nostr-wasm v0.1.0
nostr-tools@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.3.1.tgz#348d3c4aab0ab00716f93d6e2a72333d8c7da982"
integrity sha512-qjKx2C3EzwiQOe2LPSPyCnp07pGz1pWaWjDXcm+L2y2c8iTECbvlzujDANm3nJUjWL5+LVRUVDovTZ1a/DC4Bg==
nostr-tools@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.5.1.tgz#614d6aaf5c21df6b239d7ed42fdf77616a4621e7"
integrity sha512-bpkhGGAhdiCN0irfV+xoH3YP5CQeOXyXzUq7SYeM6D56xwTXZCPEmBlUGqFVfQidvRsoVeVxeAiOXW2c2HxoRQ==
dependencies:
"@noble/ciphers" "^0.5.1"
"@noble/curves" "1.2.0"
@ -9805,7 +9816,7 @@ zod@^3.21.0:
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.3.tgz#2fbc96118b174290d94e8896371c95629e87a060"
integrity sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==
zod@^3.22.4:
version "3.22.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
zod@^3.23.4, zod@^3.23.5:
version "3.23.5"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.5.tgz#c7b7617d017d4a2f21852f533258d26a9a5ae09f"
integrity sha512-fkwiq0VIQTksNNA131rDOsVJcns0pfVUjHzLrNBiF/O/Xxb5lQyEXkhZWcJ7npWsYlvs+h0jFWXXy4X46Em1JA==