Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c78beb8
feat: add arborium runtime wrapper
hazre Mar 27, 2026
286b85c
fix: align arborium runtime api with spec
hazre Mar 27, 2026
0e0290f
fix: align arborium runtime api with spec
hazre Mar 27, 2026
bce5b50
fix: treat escaped arborium output as plain
hazre Mar 27, 2026
20c941e
test: preserve code language metadata during sanitization
hazre Mar 27, 2026
9148978
feat: add arborium theme bridge
hazre Mar 27, 2026
dda1be4
fix: use arborium dist theme urls
hazre Mar 27, 2026
c3cf613
fix: restore prism body theme classes
hazre Mar 27, 2026
4e07ec1
feat: add shared arborium code renderer
hazre Mar 27, 2026
e74f167
fix: prevent stale arborium highlight flash
hazre Mar 27, 2026
f804f07
feat: render message code blocks with arborium
hazre Mar 27, 2026
bfdda68
fix: handle whitespace in code block language lookup
hazre Mar 27, 2026
428505c
fix: preserve structured code blocks in parser
hazre Mar 27, 2026
575c4f9
refactor: replace prism text highlighting with arborium
hazre Mar 27, 2026
3c397ac
test: relax text viewer renderer assertion
hazre Mar 27, 2026
240ced5
fix: add arborium language compatibility mapping
hazre Mar 27, 2026
9a15caf
fix: trust arborium availability for unsupported labels
hazre Mar 27, 2026
2675526
feat: add reusable settings selectors and code block themes
hazre Mar 28, 2026
2d26424
chore: add Arborium migration changeset
hazre Mar 28, 2026
a458be5
chore: clarify Arborium changeset summary
hazre Mar 28, 2026
13bf74d
feat: add system and manual code block theme modes
hazre Mar 28, 2026
0f7a28b
refactor: split code block themes into their own section
hazre Mar 28, 2026
153a490
fix: default dark code block theme to dracula
hazre Mar 29, 2026
3bb692f
Merge branch 'dev' of github.com:SableClient/Sable into hazre/feat/ar…
hazre Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/change-code-block-highlighting-to-arborium.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: minor
---

Improve code blocks with faster, more accurate syntax highlighting, broader language support, and separate light and dark theme options.
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"author": "7w1",
"license": "AGPL-3.0-only",
"dependencies": {
"@arborium/arborium": "^2.16.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^1.4.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
Expand Down Expand Up @@ -75,13 +76,11 @@
"matrix-js-sdk": "^38.4.0",
"matrix-widget-api": "^1.16.1",
"pdfjs-dist": "^5.4.624",
"prismjs": "^1.30.0",
"react": "^18.3.1",
"react-aria": "^3.46.0",
"react-blurhash": "^0.3.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.1.2",
"react-google-recaptcha": "^2.1.0",
"react-i18next": "^16.5.4",
"react-range": "^1.10.0",
Expand Down Expand Up @@ -110,7 +109,6 @@
"@types/file-saver": "^2.0.7",
"@types/is-hotkey": "^0.1.10",
"@types/node": "24.10.13",
"@types/prismjs": "^1.26.6",
"@types/react": "^18.3.28",
"@types/react-dom": "^18.3.7",
"@types/react-google-recaptcha": "^2.1.9",
Expand Down
38 changes: 8 additions & 30 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 81 additions & 0 deletions src/app/components/RoomNotificationSwitcher.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { RoomNotificationMode } from '$hooks/useRoomsNotificationPreferences';

import { RoomNotificationModeSwitcher } from './RoomNotificationSwitcher';

const { mockSetMode, modeStateStatus } = vi.hoisted(() => ({
mockSetMode: vi.fn(),
modeStateStatus: { current: 'idle' as 'idle' | 'loading' },
}));

vi.mock('$hooks/useRoomsNotificationPreferences', async () => {
const actual = await vi.importActual<typeof import('$hooks/useRoomsNotificationPreferences')>(
'$hooks/useRoomsNotificationPreferences'
);

return {
...actual,
useSetRoomNotificationPreference: () => ({
modeState: { status: modeStateStatus.current },
setMode: mockSetMode,
}),
};
});

afterEach(() => {
mockSetMode.mockClear();
});

describe('RoomNotificationModeSwitcher', () => {
it('renders the shared selector trigger and real option content', () => {
modeStateStatus.current = 'idle';

render(
<RoomNotificationModeSwitcher roomId="!room:example.org" value={RoomNotificationMode.Unset}>
{(openMenu, opened, changing) => (
<button type="button" onClick={openMenu}>
{opened ? 'open' : 'closed'} {changing ? 'changing' : 'idle'}
</button>
)}
</RoomNotificationModeSwitcher>
);

expect(screen.getByRole('button', { name: 'closed idle' })).toBeInTheDocument();

fireEvent.click(screen.getByRole('button', { name: 'closed idle' }));

expect(screen.getByRole('button', { name: 'open idle' })).toBeInTheDocument();
expect(screen.getByText('Follows your global notification rules')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Mention & Keywords' }));

expect(mockSetMode).toHaveBeenCalledOnce();
expect(mockSetMode).toHaveBeenCalledWith(
RoomNotificationMode.SpecialMessages,
RoomNotificationMode.Unset
);
});

it('disables interaction while the room mode is changing', () => {
modeStateStatus.current = 'loading';

render(
<RoomNotificationModeSwitcher roomId="!room:example.org" value={RoomNotificationMode.Mute}>
{(openMenu, opened, changing) => (
<button type="button" disabled={changing} onClick={openMenu}>
{opened ? 'open' : 'closed'} {changing ? 'changing' : 'idle'}
</button>
)}
</RoomNotificationModeSwitcher>
);

const trigger = screen.getByRole('button', { name: 'closed changing' });

expect(trigger).toBeDisabled();
fireEvent.click(trigger);
expect(screen.queryByRole('button', { name: 'open changing' })).not.toBeInTheDocument();

expect(mockSetMode).not.toHaveBeenCalled();
});
});
Loading
Loading