mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Add SmartyPants flag (#5769)
* feat: add smartypants flag * test: smartypants in markdown and mdx * docs: Smartypants -> SmartyPants * chore: changeset * chore: update changeset with 1.0 -> 2.0 in mind * chore: bump to minor change
This commit is contained in:
parent
04bf679a5d
commit
93e633922c
14 changed files with 155 additions and 16 deletions
34
.changeset/angry-pots-boil.md
Normal file
34
.changeset/angry-pots-boil.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
'astro': minor
|
||||
'@astrojs/mdx': minor
|
||||
'@astrojs/markdown-remark': minor
|
||||
---
|
||||
|
||||
Introduce a `smartypants` flag to opt-out of Astro's default SmartyPants plugin.
|
||||
|
||||
```js
|
||||
{
|
||||
markdown: {
|
||||
smartypants: false,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration
|
||||
|
||||
You may have disabled Astro's built-in plugins (GitHub-Flavored Markdown and Smartypants) with the `extendDefaultPlugins` option. This has now been split into 2 flags to disable each plugin individually:
|
||||
- `markdown.gfm` to disable GitHub-Flavored Markdown
|
||||
- `markdown.smartypants` to disable SmartyPants
|
||||
|
||||
```diff
|
||||
// astro.config.mjs
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
export default defineConfig({
|
||||
markdown: {
|
||||
- extendDefaultPlugins: false,
|
||||
+ smartypants: false,
|
||||
+ gfm: false,
|
||||
}
|
||||
});
|
||||
```
|
|
@ -785,6 +785,23 @@ export interface AstroUserConfig {
|
|||
* ```
|
||||
*/
|
||||
gfm?: boolean;
|
||||
/**
|
||||
* @docs
|
||||
* @name markdown.smartypants
|
||||
* @type {boolean}
|
||||
* @default `true`
|
||||
* @description
|
||||
* Astro uses the [SmartyPants formatter](https://daringfireball.net/projects/smartypants/) by default. To disable this, set the `smartypants` flag to `false`:
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* markdown: {
|
||||
* smartypants: false,
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
smartypants?: boolean;
|
||||
/**
|
||||
* @docs
|
||||
* @name markdown.remarkRehype
|
||||
|
|
|
@ -163,6 +163,7 @@ export const AstroConfigSchema = z.object({
|
|||
.optional()
|
||||
.default(ASTRO_CONFIG_DEFAULTS.markdown.remarkRehype),
|
||||
gfm: z.boolean().default(ASTRO_CONFIG_DEFAULTS.markdown.gfm),
|
||||
smartypants: z.boolean().default(ASTRO_CONFIG_DEFAULTS.markdown.smartypants),
|
||||
})
|
||||
.default({}),
|
||||
vite: z
|
||||
|
|
|
@ -47,25 +47,23 @@ describe('Astro Markdown plugins', () => {
|
|||
});
|
||||
|
||||
// Asserts Astro 1.0 behavior is removed. Test can be removed in Astro 3.0.
|
||||
it('Still applies GFM when user plugins are provided', async () => {
|
||||
it('Still applies default plugins when user plugins are provided', async () => {
|
||||
const fixture = await buildFixture({
|
||||
markdown: {
|
||||
remarkPlugins: [remarkExamplePlugin],
|
||||
rehypePlugins: [[addClasses, { 'h1,h2,h3': 'title' }]],
|
||||
},
|
||||
});
|
||||
const html = await fixture.readFile('/with-gfm/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
const gfmHtml = await fixture.readFile('/with-gfm/index.html');
|
||||
const $1 = cheerio.load(gfmHtml);
|
||||
expect($1('a[href="https://example.com"]')).to.have.lengthOf(1);
|
||||
|
||||
// test 1: GFM autolink applied correctly
|
||||
expect($('a[href="https://example.com"]')).to.have.lengthOf(1);
|
||||
const smartypantsHtml = await fixture.readFile('/with-smartypants/index.html');
|
||||
const $2 = cheerio.load(smartypantsHtml);
|
||||
expect($2('p').html()).to.equal('“Smartypants” is — awesome');
|
||||
|
||||
// test 2: remark plugins still applied
|
||||
expect(html).to.include('Remark plugin applied!');
|
||||
|
||||
// test 3: rehype plugins still applied
|
||||
expect($('#github-flavored-markdown-test')).to.have.lengthOf(1);
|
||||
expect($('#github-flavored-markdown-test').hasClass('title')).to.equal(true);
|
||||
testRemark(gfmHtml);
|
||||
testRehype(gfmHtml, '#github-flavored-markdown-test');
|
||||
});
|
||||
|
||||
for (const gfm of [true, false]) {
|
||||
|
@ -87,12 +85,42 @@ describe('Astro Markdown plugins', () => {
|
|||
expect($('a[href="https://example.com"]')).to.have.lengthOf(0);
|
||||
}
|
||||
|
||||
// test 2: remark plugins still applied
|
||||
expect(html).to.include('Remark plugin applied!');
|
||||
testRemark(html);
|
||||
testRehype(html, '#github-flavored-markdown-test');
|
||||
});
|
||||
}
|
||||
|
||||
// test 3: rehype plugins still applied
|
||||
expect($('#github-flavored-markdown-test')).to.have.lengthOf(1);
|
||||
expect($('#github-flavored-markdown-test').hasClass('title')).to.equal(true);
|
||||
for (const smartypants of [true, false]) {
|
||||
it(`Handles SmartyPants when smartypants = ${smartypants}`, async () => {
|
||||
const fixture = await buildFixture({
|
||||
markdown: {
|
||||
remarkPlugins: [remarkExamplePlugin],
|
||||
rehypePlugins: [[addClasses, { 'h1,h2,h3': 'title' }]],
|
||||
smartypants,
|
||||
},
|
||||
});
|
||||
const html = await fixture.readFile('/with-smartypants/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
// test 1: GFM autolink applied correctly
|
||||
if (smartypants === true) {
|
||||
expect($('p').html()).to.equal('“Smartypants” is — awesome');
|
||||
} else {
|
||||
expect($('p').html()).to.equal('"Smartypants" is -- awesome');
|
||||
}
|
||||
|
||||
testRemark(html);
|
||||
testRehype(html, '#smartypants-test');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function testRehype(html, headingId) {
|
||||
const $ = cheerio.load(html);
|
||||
expect($(headingId)).to.have.lengthOf(1);
|
||||
expect($(headingId).hasClass('title')).to.equal(true);
|
||||
}
|
||||
|
||||
function testRemark(html) {
|
||||
expect(html).to.include('Remark plugin applied!');
|
||||
}
|
||||
|
|
3
packages/astro/test/fixtures/astro-markdown-plugins/src/pages/with-smartypants.md
vendored
Normal file
3
packages/astro/test/fixtures/astro-markdown-plugins/src/pages/with-smartypants.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Smartypants test
|
||||
|
||||
"Smartypants" is -- awesome
|
|
@ -43,6 +43,7 @@
|
|||
"rehype-raw": "^6.1.1",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-smartypants": "^2.0.0",
|
||||
"shiki": "^0.11.1",
|
||||
"unist-util-visit": "^4.1.0",
|
||||
"vfile": "^5.3.2"
|
||||
|
|
|
@ -186,6 +186,7 @@ function applyDefaultOptions({
|
|||
recmaPlugins: options.recmaPlugins ?? defaults.recmaPlugins,
|
||||
remarkRehype: options.remarkRehype ?? defaults.remarkRehype,
|
||||
gfm: options.gfm ?? defaults.gfm,
|
||||
smartypants: options.smartypants ?? defaults.smartypants,
|
||||
remarkPlugins: options.remarkPlugins ?? defaults.remarkPlugins,
|
||||
rehypePlugins: options.rehypePlugins ?? defaults.rehypePlugins,
|
||||
shikiConfig: options.shikiConfig ?? defaults.shikiConfig,
|
||||
|
|
|
@ -14,6 +14,7 @@ import type { Image } from 'mdast';
|
|||
import { pathToFileURL } from 'node:url';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkSmartypants from 'remark-smartypants';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import type { VFile } from 'vfile';
|
||||
import { MdxOptions } from './index.js';
|
||||
|
@ -153,6 +154,9 @@ export async function getRemarkPlugins(
|
|||
if (mdxOptions.gfm) {
|
||||
remarkPlugins.push(remarkGfm);
|
||||
}
|
||||
if (mdxOptions.smartypants) {
|
||||
remarkPlugins.push(remarkSmartypants);
|
||||
}
|
||||
|
||||
remarkPlugins = [...remarkPlugins, ...ignoreStringPlugins(mdxOptions.remarkPlugins)];
|
||||
|
||||
|
|
|
@ -21,3 +21,5 @@ Oh cool, more text!
|
|||
And section 2, with a hyperlink to check GFM is preserved: https://handle-me-gfm.com
|
||||
|
||||
<div data-recma-plugin-works={recmaPluginWorking}></div>
|
||||
|
||||
> "Smartypants" is -- awesome
|
||||
|
|
|
@ -36,6 +36,19 @@ describe('MDX plugins', () => {
|
|||
expect(selectGfmLink(document)).to.not.be.null;
|
||||
});
|
||||
|
||||
it('Applies SmartyPants by default', async () => {
|
||||
const fixture = await buildFixture({
|
||||
integrations: [mdx()],
|
||||
});
|
||||
|
||||
const html = await fixture.readFile(FILE);
|
||||
const { document } = parseHTML(html);
|
||||
|
||||
const quote = selectSmartypantsQuote(document);
|
||||
expect(quote).to.not.be.null;
|
||||
expect(quote.textContent).to.contain('“Smartypants” is — awesome');
|
||||
});
|
||||
|
||||
it('supports custom rehype plugins', async () => {
|
||||
const fixture = await buildFixture({
|
||||
integrations: [
|
||||
|
@ -88,6 +101,7 @@ describe('MDX plugins', () => {
|
|||
markdown: {
|
||||
remarkPlugins: [remarkToc],
|
||||
gfm: false,
|
||||
smartypants: false,
|
||||
},
|
||||
integrations: [
|
||||
mdx({
|
||||
|
@ -129,6 +143,23 @@ describe('MDX plugins', () => {
|
|||
expect(selectGfmLink(document), 'Respects `markdown.gfm` unexpectedly.').to.not.be.null;
|
||||
}
|
||||
});
|
||||
|
||||
it('Handles smartypants', async () => {
|
||||
const html = await fixture.readFile(FILE);
|
||||
const { document } = parseHTML(html);
|
||||
|
||||
const quote = selectSmartypantsQuote(document);
|
||||
|
||||
if (extendMarkdownConfig === true) {
|
||||
expect(quote.textContent, 'Does not respect `markdown.smartypants` option.').to.contain(
|
||||
'"Smartypants" is -- awesome'
|
||||
);
|
||||
} else {
|
||||
expect(quote.textContent, 'Respects `markdown.smartypants` unexpectedly.').to.contain(
|
||||
'“Smartypants” is — awesome'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -202,6 +233,10 @@ function selectGfmLink(document) {
|
|||
return document.querySelector('a[href="https://handle-me-gfm.com"]');
|
||||
}
|
||||
|
||||
function selectSmartypantsQuote(document) {
|
||||
return document.querySelector('blockquote');
|
||||
}
|
||||
|
||||
function selectRemarkExample(document) {
|
||||
return document.querySelector('div[data-remark-plugin-works]');
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
"remark-gfm": "^3.0.1",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"remark-smartypants": "^2.0.0",
|
||||
"shiki": "^0.11.1",
|
||||
"unified": "^10.1.2",
|
||||
"unist-util-map": "^3.1.1",
|
||||
|
|
|
@ -24,6 +24,7 @@ import remarkUnwrap from './remark-unwrap.js';
|
|||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeStringify from 'rehype-stringify';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkSmartypants from 'remark-smartypants';
|
||||
import markdown from 'remark-parse';
|
||||
import markdownToHtml from 'remark-rehype';
|
||||
import { unified } from 'unified';
|
||||
|
@ -43,6 +44,7 @@ export const markdownConfigDefaults: Omit<Required<AstroMarkdownOptions>, 'draft
|
|||
rehypePlugins: [],
|
||||
remarkRehype: {},
|
||||
gfm: true,
|
||||
smartypants: true,
|
||||
};
|
||||
|
||||
/** Shared utility for rendering markdown */
|
||||
|
@ -58,6 +60,7 @@ export async function renderMarkdown(
|
|||
rehypePlugins = markdownConfigDefaults.rehypePlugins,
|
||||
remarkRehype = markdownConfigDefaults.remarkRehype,
|
||||
gfm = markdownConfigDefaults.gfm,
|
||||
smartypants = markdownConfigDefaults.smartypants,
|
||||
isAstroFlavoredMd = false,
|
||||
isExperimentalContentCollections = false,
|
||||
contentDir,
|
||||
|
@ -75,6 +78,10 @@ export async function renderMarkdown(
|
|||
parser.use(remarkGfm);
|
||||
}
|
||||
|
||||
if (smartypants) {
|
||||
parser.use(remarkSmartypants);
|
||||
}
|
||||
|
||||
const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
|
||||
const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ export interface AstroMarkdownOptions {
|
|||
rehypePlugins?: RehypePlugins;
|
||||
remarkRehype?: RemarkRehype;
|
||||
gfm?: boolean;
|
||||
smartypants?: boolean;
|
||||
}
|
||||
|
||||
export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
||||
|
|
|
@ -2917,6 +2917,7 @@ importers:
|
|||
remark-gfm: ^3.0.1
|
||||
remark-rehype: ^10.1.0
|
||||
remark-shiki-twoslash: ^3.1.0
|
||||
remark-smartypants: ^2.0.0
|
||||
remark-toc: ^8.0.1
|
||||
shiki: ^0.11.1
|
||||
unist-util-visit: ^4.1.0
|
||||
|
@ -2936,6 +2937,7 @@ importers:
|
|||
rehype-raw: 6.1.1
|
||||
remark-frontmatter: 4.0.1
|
||||
remark-gfm: 3.0.1
|
||||
remark-smartypants: 2.0.0
|
||||
shiki: 0.11.1
|
||||
unist-util-visit: 4.1.1
|
||||
vfile: 5.3.6
|
||||
|
@ -3520,6 +3522,7 @@ importers:
|
|||
remark-gfm: ^3.0.1
|
||||
remark-parse: ^10.0.1
|
||||
remark-rehype: ^10.1.0
|
||||
remark-smartypants: ^2.0.0
|
||||
shiki: ^0.11.1
|
||||
unified: ^10.1.2
|
||||
unist-util-map: ^3.1.1
|
||||
|
@ -3544,6 +3547,7 @@ importers:
|
|||
remark-gfm: 3.0.1
|
||||
remark-parse: 10.0.1
|
||||
remark-rehype: 10.1.0
|
||||
remark-smartypants: 2.0.0
|
||||
shiki: 0.11.1
|
||||
unified: 10.1.2
|
||||
unist-util-map: 3.1.2
|
||||
|
|
Loading…
Reference in a new issue