Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve inline code and code block support in RTL languages #525

Merged
merged 6 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/long-parrots-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Improve inline code and code block support in RTL languages
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { rehype } from 'rehype';
import { expect, test } from 'vitest';
import { rehypeRtlCodeSupport } from '../../integrations/code-rtl-support';

const processor = rehype().data('settings', { fragment: true }).use(rehypeRtlCodeSupport());

test('applies `dir="auto"` to inline code', async () => {
const input = `<p>Some text with <code>inline code</code>.</p>`;
const output = String(await processor.process(input));
expect(output).not.toEqual(input);
expect(output).includes('dir="auto"');
expect(output).toMatchInlineSnapshot(
'"<p>Some text with <code dir=\\"auto\\">inline code</code>.</p>"'
);
});

test('applies `dir="ltr"` to code blocks', async () => {
const input = `<p>Some text in a paragraph:</p><pre><code>console.log('test')</code></pre>`;
const output = String(await processor.process(input));
expect(output).not.toEqual(input);
expect(output).includes('dir="ltr"');
expect(output).toMatchInlineSnapshot(
'"<p>Some text in a paragraph:</p><pre dir=\\"ltr\\"><code>console.log(\'test\')</code></pre>"'
);
});
3 changes: 3 additions & 0 deletions packages/starlight/__tests__/remark-rehype/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineVitestConfig } from '../test-config';

export default defineVitestConfig({ title: 'Remark-Rehype' });
2 changes: 2 additions & 0 deletions packages/starlight/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { starlightSitemap } from './integrations/sitemap';
import { vitePluginStarlightUserConfig } from './integrations/virtual-user-config';
import { errorMap } from './utils/error-map';
import { StarlightConfigSchema, StarlightUserConfig } from './utils/user-config';
import { rehypeRtlCodeSupport } from './integrations/code-rtl-support';

export default function StarlightIntegration(opts: StarlightUserConfig): AstroIntegration[] {
const parsedConfig = StarlightConfigSchema.safeParse(opts, { errorMap });
Expand Down Expand Up @@ -39,6 +40,7 @@ export default function StarlightIntegration(opts: StarlightUserConfig): AstroIn
},
markdown: {
remarkPlugins: [...starlightAsides()],
rehypePlugins: [rehypeRtlCodeSupport()],
shikiConfig:
// Configure Shiki theme if the user is using the default github-dark theme.
config.markdown.shikiConfig.theme !== 'github-dark' ? {} : { theme: 'css-variables' },
Expand Down
31 changes: 31 additions & 0 deletions packages/starlight/integrations/code-rtl-support.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Root } from 'hastscript/lib/core';
import { CONTINUE, SKIP, visit } from 'unist-util-visit';

/**
* rehype plugin that adds `dir` attributes to `<code>` and `<pre>`
* elements that don’t already have them.
*
* `<code>` will become `<code dir="auto">`
* `<pre>` will become `<pre dir="ltr">`
*
* `<code>` _inside_ `<pre>` is skipped, so respects the `ltr` on its parent.
*
* Reasoning:
* - `<pre>` is usually a code block and code should be LTR even in an RTL document
* - `<code>` is often LTR, but could also be RTL. `dir="auto"` ensures the bidirectional
* algorithm treats the contents of `<code>` in isolation and gives its best guess.
*/
export function rehypeRtlCodeSupport() {
return () => (root: Root) => {
visit(root, 'element', (el) => {
if (el.tagName === 'pre' || el.tagName === 'code') {
el.properties ||= {};
if (!('dir' in el.properties)) {
el.properties.dir = { pre: 'ltr', code: 'auto' }[el.tagName];
}
return SKIP;
}
return CONTINUE;
});
};
}
Loading