{capitalize(t('enable_infinite_scroll_tip'))}
diff --git a/src/components/settings-modal/media-hosting-settings/media-hosting-settings.tsx b/src/components/settings-modal/media-hosting-settings/media-hosting-settings.tsx
index 259ccb9f..2d08b057 100644
--- a/src/components/settings-modal/media-hosting-settings/media-hosting-settings.tsx
+++ b/src/components/settings-modal/media-hosting-settings/media-hosting-settings.tsx
@@ -32,13 +32,29 @@ const MediaHostingSettings = () => {
)}
{uploadMode === 'preferred' && (
@@ -51,6 +67,7 @@ const MediaHostingSettings = () => {
{t('media_hosting_none_tip')}
diff --git a/src/components/settings-modal/p2p-stats-settings/__tests__/p2p-stats-settings.test.tsx b/src/components/settings-modal/p2p-stats-settings/__tests__/p2p-stats-settings.test.tsx
index f0c978be..7a2cab72 100644
--- a/src/components/settings-modal/p2p-stats-settings/__tests__/p2p-stats-settings.test.tsx
+++ b/src/components/settings-modal/p2p-stats-settings/__tests__/p2p-stats-settings.test.tsx
@@ -155,7 +155,7 @@ describe('P2PStatsSettings', () => {
// from libp2p observed/WebRTC addresses.
expect(fetch).toHaveBeenCalledWith('https://api.ipify.org?format=json', expect.objectContaining({ signal: expect.any(AbortSignal) }));
const yourIpRow = Array.from(container.querySelectorAll('tr')).find((row) => row.textContent?.includes('Your IP'));
- expect(yourIpRow?.querySelector('[role="img"]')).not.toBeNull();
+ expect(yourIpRow?.querySelector('img[aria-label]')).not.toBeNull();
expect(container.textContent).not.toContain('browser Helia');
expect(container.textContent).not.toContain('seed mode');
expect(container.textContent).not.toContain('status');
@@ -325,7 +325,7 @@ describe('P2PStatsSettings', () => {
const marker = getMarkerByTitle('Your node - Da Nang, Da Nang City, VN');
expect(container.textContent).toContain('Leeching');
expect(rows.get('Your IP')).toContain('117.2.120.113');
- expect(yourIpRow?.querySelector('[role="img"]')?.getAttribute('aria-label')).toBe('Vietnam');
+ expect(yourIpRow?.querySelector('img[aria-label]')?.getAttribute('aria-label')).toBe('Vietnam');
expect(marker?.getAttribute('data-peer-role')).toBe('leecher');
expect(Number(marker?.getAttribute('x'))).toBeCloseTo(286.72, 1);
expect(Number(marker?.getAttribute('y'))).toBeCloseTo(72.43, 1);
@@ -417,7 +417,7 @@ describe('P2PStatsSettings', () => {
const marker = getMarkerByTitle('Your node - VN');
expect(rows.get('Your IP')).toContain('104.28.68.171');
expect(rows.get('Your IP')).not.toContain('146.75.187.55');
- expect(yourIpRow?.querySelector('[role="img"]')?.getAttribute('aria-label')).toBe('Vietnam');
+ expect(yourIpRow?.querySelector('img[aria-label]')?.getAttribute('aria-label')).toBe('Vietnam');
expect(marker).not.toBeNull();
expect(container.querySelector('svg rect title')?.textContent).not.toContain('Toronto');
expect(fetchMock).not.toHaveBeenCalledWith('https://free.freeipapi.com/api/json/146.75.187.55', expect.anything());
@@ -562,7 +562,7 @@ describe('P2PStatsSettings', () => {
expect(rows.get('Your IP')).toContain('104.28.68.171');
expect(rows.get('Your IP')).not.toContain('unknown');
const yourIpRow = Array.from(container.querySelectorAll('tr')).find((row) => row.textContent?.includes('Your IP'));
- expect(yourIpRow?.querySelector('[role="img"]')).not.toBeNull();
+ expect(yourIpRow?.querySelector('img[aria-label]')).not.toBeNull();
expect(fetch).toHaveBeenCalledWith('https://api.ipify.org?format=json', expect.objectContaining({ signal: expect.any(AbortSignal) }));
});
diff --git a/src/components/settings-modal/p2p-stats-settings/p2p-stats-settings.tsx b/src/components/settings-modal/p2p-stats-settings/p2p-stats-settings.tsx
index 3658e088..d2d4f234 100644
--- a/src/components/settings-modal/p2p-stats-settings/p2p-stats-settings.tsx
+++ b/src/components/settings-modal/p2p-stats-settings/p2p-stats-settings.tsx
@@ -140,6 +140,7 @@ type ObservedTransferStats = {
const KUBO_API_URL = 'http://localhost:50019/api/v0';
const SEEDER_REPO_URL = 'https://github.com/bitsocialnet/bitsocial-seeder';
+const transparentPixelSrc = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="1"%3E%3C/svg%3E';
const STATS_REFRESH_MS = 5000;
const MAX_TRANSFER_COUNTER_DEPTH = 10;
const MAX_TRANSFER_COUNTER_OBJECTS = 400;
@@ -1028,10 +1029,11 @@ const NodeEndpointValue = ({ row }: { row: NodeEndpointStatRow }) => {
return (
{flagPosition && (
-
@@ -1087,10 +1089,11 @@ const ConnectedPeersValue = ({ row }: { row: ConnectedPeersStatRow }) => (
{flagPosition && (
-
diff --git a/src/components/settings-modal/settings-modal.module.css b/src/components/settings-modal/settings-modal.module.css
index 991ea309..1be5d64a 100644
--- a/src/components/settings-modal/settings-modal.module.css
+++ b/src/components/settings-modal/settings-modal.module.css
@@ -1,4 +1,5 @@
.overlay {
+ all: unset;
position: fixed;
top: 0;
left: 0;
@@ -48,6 +49,8 @@
border-right: var(--settings-modal-border-right);
border-bottom: var(--settings-modal-border-bottom);
border-left: var(--settings-modal-border-left);
+ color: inherit;
+ margin: 0;
}
.settingsModal textarea {
@@ -74,6 +77,9 @@
}
.closeButton {
+ appearance: none;
+ background-color: transparent;
+ border: 0;
right: 5px;
width: 18px;
height: 18px;
@@ -84,6 +90,8 @@
background-image: var(--settings-modal-close-button-background-image);
background-position: center;
background-repeat: no-repeat;
+ margin: 0;
+ padding: 0;
}
.expandAllSettings {
@@ -91,11 +99,17 @@
text-transform: capitalize;
}
-.expandAllSettings span {
+.expandAllSettings span, .expandAllSettings button {
+ appearance: none;
+ background: transparent;
+ border: 0;
color: var(--button-desktop-text-color);
+ font: inherit;
+ margin: 0;
+ padding: 0;
}
-.expandAllSettings span:hover {
+.expandAllSettings span:hover, .expandAllSettings button:hover {
color: var(--button-desktop-text-color-hover);
cursor: pointer;
}
@@ -128,10 +142,23 @@
margin-bottom: 10px;
}
-.category label {
+.category label, .categoryButton {
font-weight: 700;
}
+.categoryButton {
+ appearance: none;
+ background: transparent;
+ border: 0;
+ color: inherit;
+ cursor: pointer;
+ display: inline;
+ font: inherit;
+ font-weight: 700;
+ margin: 0;
+ padding: 0;
+}
+
.subSectionHeader {
text-align: left;
font-weight: 700;
diff --git a/src/components/settings-modal/settings-modal.tsx b/src/components/settings-modal/settings-modal.tsx
index 50badf9a..f8437ede 100644
--- a/src/components/settings-modal/settings-modal.tsx
+++ b/src/components/settings-modal/settings-modal.tsx
@@ -118,15 +118,15 @@ const SettingsModal = () => {
return (
<>
-
-
+
+
+
>
);
};
diff --git a/src/components/settings-modal/subscriptions-setting/__tests__/subscriptions-setting.test.tsx b/src/components/settings-modal/subscriptions-setting/__tests__/subscriptions-setting.test.tsx
index f6bbcd02..335126e6 100644
--- a/src/components/settings-modal/subscriptions-setting/__tests__/subscriptions-setting.test.tsx
+++ b/src/components/settings-modal/subscriptions-setting/__tests__/subscriptions-setting.test.tsx
@@ -47,7 +47,7 @@ const render = () => {
};
const getButtonByText = (text: string) => {
- const button = Array.from(container.querySelectorAll('[role="button"]')).find((candidate) => (candidate.textContent ?? '').includes(text));
+ const button = Array.from(container.querySelectorAll('button')).find((candidate) => (candidate.textContent ?? '').includes(text));
if (!button) {
throw new Error(`Button containing "${text}" not found`);
}
diff --git a/src/components/settings-modal/subscriptions-setting/subscriptions-setting.module.css b/src/components/settings-modal/subscriptions-setting/subscriptions-setting.module.css
index 32a3bede..1b1f929a 100644
--- a/src/components/settings-modal/subscriptions-setting/subscriptions-setting.module.css
+++ b/src/components/settings-modal/subscriptions-setting/subscriptions-setting.module.css
@@ -12,6 +12,12 @@
}
.button {
+ appearance: none;
+ background: transparent;
+ border: 0;
+ font: inherit;
+ margin: 0;
+ padding: 0;
text-transform: capitalize;
color: var(--button-desktop-text-color);
text-decoration: var(--button-text-decoration);
@@ -26,6 +32,11 @@
cursor: pointer;
}
+.button:focus-visible {
+ outline: 1px dotted currentcolor;
+ outline-offset: 1px;
+}
+
.notSubscribed {
padding-bottom: 10px;
}
diff --git a/src/components/settings-modal/subscriptions-setting/subscriptions-setting.tsx b/src/components/settings-modal/subscriptions-setting/subscriptions-setting.tsx
index 50e09bc6..aa9efe00 100644
--- a/src/components/settings-modal/subscriptions-setting/subscriptions-setting.tsx
+++ b/src/components/settings-modal/subscriptions-setting/subscriptions-setting.tsx
@@ -22,9 +22,9 @@ const SubscriptionButton = ({ address }: { address: string }) => {
return (
[
- {
if (e.key === 'Enter' || e.key === ' ') {
@@ -35,7 +35,7 @@ const SubscriptionButton = ({ address }: { address: string }) => {
onClick={toggleSubscription}
>
{recentlyUnsubscribed || !subscribed ? t('subscribe') : t('unsubscribe')}
-
+
]
);
@@ -59,9 +59,9 @@ const SubscriptionsSetting = () => {
{subscriptions?.length > 1 && (
[
- {
if (e.key === 'Enter' || e.key === ' ') {
@@ -72,7 +72,7 @@ const SubscriptionsSetting = () => {
onClick={unsubscribeAll}
>
{t('unsubscribe_all')}
-
+
]
)}
diff --git a/src/views/account-data-editor/account-data-editor.tsx b/src/views/account-data-editor/account-data-editor.tsx
index c09c0d37..c579015c 100644
--- a/src/views/account-data-editor/account-data-editor.tsx
+++ b/src/views/account-data-editor/account-data-editor.tsx
@@ -23,14 +23,13 @@ type EditorState = {
};
const loadAce = async () => {
- const aceModule = await import('react-ace');
- const [workerJsonModule] = await Promise.all([
- import('ace-builds/src-noconflict/worker-json?url'),
- import('ace-builds/esm-resolver'),
- import('ace-builds/src-noconflict/mode-json'),
- import('ace-builds/src-noconflict/theme-monokai'),
- ]);
- // Load react-ace first so esm-resolver sees the global ace instance.
+ const aceModulePromise = import('react-ace');
+ const workerJsonModulePromise = import('ace-builds/src-noconflict/worker-json?url');
+ const modeJsonPromise = import('ace-builds/src-noconflict/mode-json');
+ const themeMonokaiPromise = import('ace-builds/src-noconflict/theme-monokai');
+ const resolverPromise = aceModulePromise.then(() => import('ace-builds/esm-resolver'));
+ const [aceModule, workerJsonModule] = await Promise.all([aceModulePromise, workerJsonModulePromise, resolverPromise, modeJsonPromise, themeMonokaiPromise]);
+ // esm-resolver waits for react-ace so it can see the global ace instance.
const mod = aceModule.default;
const Editor = typeof mod === 'function' ? mod : (mod as unknown as { default: typeof mod }).default;
@@ -143,6 +142,7 @@ const AccountDataEditor = () => {
/>
) : (