mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Merge branch 'codex/feature/trusted-board-url-permissions'
This commit is contained in:
@@ -42,6 +42,10 @@ vi.mock('../subscriptions-setting', () => ({
|
||||
default: () => <div data-testid='subscriptions-settings-panel'>subscriptions-settings</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../trusted-board-links-setting', () => ({
|
||||
default: () => <div data-testid='trusted-board-links-settings-panel'>trusted-board-links-settings</div>,
|
||||
}));
|
||||
|
||||
const LocationProbe = () => {
|
||||
const location = useLocation();
|
||||
return <div data-testid='location'>{location.pathname + location.hash}</div>;
|
||||
@@ -133,6 +137,7 @@ describe('SettingsModal', () => {
|
||||
expect(container.querySelector('[data-testid="media-hosting-settings-panel"]')).not.toBeNull();
|
||||
expect(container.querySelector('[data-testid="account-settings"]')).not.toBeNull();
|
||||
expect(container.querySelector('[data-testid="subscriptions-settings-panel"]')).not.toBeNull();
|
||||
expect(container.querySelector('[data-testid="trusted-board-links-settings-panel"]')).not.toBeNull();
|
||||
expect(container.querySelector('[data-testid="advanced-settings-panel"]')).not.toBeNull();
|
||||
|
||||
const collapseAllControl = Array.from(container.querySelectorAll('[role="button"]')).find((candidate) =>
|
||||
@@ -150,6 +155,7 @@ describe('SettingsModal', () => {
|
||||
expect(container.querySelector('[data-testid="media-hosting-settings-panel"]')).toBeNull();
|
||||
expect(container.querySelector('[data-testid="account-settings"]')).toBeNull();
|
||||
expect(container.querySelector('[data-testid="subscriptions-settings-panel"]')).toBeNull();
|
||||
expect(container.querySelector('[data-testid="trusted-board-links-settings-panel"]')).toBeNull();
|
||||
expect(container.querySelector('[data-testid="advanced-settings-panel"]')).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -9,8 +9,16 @@ import InterfaceSettings from './interface-settings';
|
||||
import MediaHostingSettings from './media-hosting-settings';
|
||||
import AdvancedSettings from './advanced-settings';
|
||||
import SubscriptionsSetting from './subscriptions-setting';
|
||||
import TrustedBoardLinksSetting from './trusted-board-links-setting';
|
||||
|
||||
const allSectionIds = ['interface-settings', 'media-hosting-settings', 'account-settings', 'subscriptions-settings', 'advanced-settings'];
|
||||
const allSectionIds = [
|
||||
'interface-settings',
|
||||
'media-hosting-settings',
|
||||
'account-settings',
|
||||
'subscriptions-settings',
|
||||
'board-link-permissions-settings',
|
||||
'advanced-settings',
|
||||
];
|
||||
|
||||
const hashToSection = (hash: string): string | null => {
|
||||
if (hash === 'crypto-address-settings' || hash === 'crypto-wallet-settings') return 'account-settings';
|
||||
@@ -58,6 +66,7 @@ const SettingsModal = () => {
|
||||
const showMediaHostingSettings = visibleExpandedSections.has('media-hosting-settings');
|
||||
const showAccountSettings = visibleExpandedSections.has('account-settings');
|
||||
const showSubscriptionsSettings = visibleExpandedSections.has('subscriptions-settings');
|
||||
const showBoardLinkPermissionsSettings = visibleExpandedSections.has('board-link-permissions-settings');
|
||||
const showAdvancedSettings = visibleExpandedSections.has('advanced-settings');
|
||||
|
||||
const allExpanded = useMemo(() => allSectionIds.every((id) => visibleExpandedSections.has(id)), [visibleExpandedSections]);
|
||||
@@ -162,6 +171,13 @@ const SettingsModal = () => {
|
||||
</label>
|
||||
</div>
|
||||
{showSubscriptionsSettings && <SubscriptionsSetting />}
|
||||
<div id='board-link-permissions-settings' className={`${styles.setting} ${styles.category}`}>
|
||||
<label onClick={() => handleCategoryClick('board-link-permissions-settings')}>
|
||||
<span className={showBoardLinkPermissionsSettings ? styles.hideButton : styles.showButton} />
|
||||
{t('board_link_permissions')}
|
||||
</label>
|
||||
</div>
|
||||
{showBoardLinkPermissionsSettings && <TrustedBoardLinksSetting />}
|
||||
<div id='advanced-settings' className={`${styles.setting} ${styles.category}`}>
|
||||
<label onClick={() => handleCategoryClick('advanced-settings')}>
|
||||
<span className={showAdvancedSettings ? styles.hideButton : styles.showButton} />
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import * as React from 'react';
|
||||
import { createElement } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import useTrustedBoardUrlPermissionsStore from '../../../../stores/use-trusted-board-url-permissions-store';
|
||||
import TrustedBoardLinksSetting from '../trusted-board-links-setting';
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}));
|
||||
|
||||
let container: HTMLDivElement;
|
||||
let root: Root;
|
||||
|
||||
const render = () => {
|
||||
act(() => {
|
||||
root.render(createElement(TrustedBoardLinksSetting));
|
||||
});
|
||||
};
|
||||
|
||||
describe('TrustedBoardLinksSetting', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
useTrustedBoardUrlPermissionsStore.setState({ trustedOrigins: {} });
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
it('shows an empty state when no board links are trusted', () => {
|
||||
render();
|
||||
|
||||
expect(container.textContent).toContain('trusted_board_links_intro');
|
||||
expect(container.textContent).toContain('trusted_board_links_empty');
|
||||
});
|
||||
|
||||
it('revokes a trusted board link permission', async () => {
|
||||
useTrustedBoardUrlPermissionsStore.getState().trustOrigin('https://spamblocker.bitsocial.net', 'spamblocker.bitsocial.net');
|
||||
|
||||
render();
|
||||
|
||||
expect(container.textContent).toContain('spamblocker.bitsocial.net');
|
||||
|
||||
const revokeButton = Array.from(container.querySelectorAll('button')).find((button) => button.textContent === 'revoke');
|
||||
await act(async () => {
|
||||
revokeButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
});
|
||||
|
||||
expect(useTrustedBoardUrlPermissionsStore.getState().isOriginTrusted('https://spamblocker.bitsocial.net')).toBe(false);
|
||||
expect(container.textContent).toContain('trusted_board_links_empty');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './trusted-board-links-setting';
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
.trustedBoardLinks {
|
||||
margin: 0 10px 10px;
|
||||
}
|
||||
|
||||
.description, .empty {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.site {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.expiry {
|
||||
white-space: nowrap;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useTrustedBoardUrlPermissionsStore from '../../../stores/use-trusted-board-url-permissions-store';
|
||||
import styles from './trusted-board-links-setting.module.css';
|
||||
|
||||
const TrustedBoardLinksSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
const trustedOriginsByOrigin = useTrustedBoardUrlPermissionsStore((state) => state.trustedOrigins);
|
||||
const revokeOrigin = useTrustedBoardUrlPermissionsStore((state) => state.revokeOrigin);
|
||||
const trustedOrigins = useMemo(
|
||||
() => Object.values(trustedOriginsByOrigin).sort((a, b) => a.site.localeCompare(b.site) || a.origin.localeCompare(b.origin)),
|
||||
[trustedOriginsByOrigin],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.trustedBoardLinks}>
|
||||
<div className={styles.description}>{t('trusted_board_links_intro')}</div>
|
||||
{trustedOrigins.length ? (
|
||||
<ul className={styles.list}>
|
||||
{trustedOrigins.map((permission) => (
|
||||
<li key={permission.origin} className={styles.item}>
|
||||
<span className={styles.site}>
|
||||
{permission.site} <span className={styles.expiry}>({t('trusted_board_link_until_revoked')})</span>
|
||||
</span>
|
||||
<button type='button' onClick={() => revokeOrigin(permission.origin)}>
|
||||
{t('revoke')}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className={styles.empty}>{t('trusted_board_links_empty')}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TrustedBoardLinksSetting);
|
||||
Reference in New Issue
Block a user