Fix markdown issues (#208)
* Init fix/markdown * Astro Markdown (#207) * Add Astro Markdown to VSCode Extension * Add Astro Markdown to Astro * refactor: update astro-markdown example * feat: remove embedded components from `.md` files * fix: resolve `.md.astro` files at runtime * chore: update markdown tests * feat: add <Markdown> component * chore: bump examples * chore: update example * fix: improve Markdown child handling * feat: harden markdown support, add code fence support, add automatic dedenting * chore: add weird markdown edge cases * chore: update remote-markdown examples * chore: add comment to Markdown.astro * feat: improve markdown support (codefences, nested inside HTML) * refactor: extract import specifier types to set * refactor: conditionally import markdown renderer * refactor: revert special-cased "astro/components" * refactor: revert special-cased "astro/components" * refactor: use astro/components/Markdown.astro * refactor: remove `.md.astro` support in favor of Markdown component * refactor: use regular .astro files * refactor: remove unused code * refactor: move Markdown inside Layout * wip: markdown scoped styles * feat: improve scoped styles in Markdown * feat: micromark => remark ecosystem * fix: markdown build * fix: markdown build * chore: add todo * fix: collect headers text * docs: add Markdown doc * chore: add changeset * docs: improve Markdown highlighting * refactor: prefer Set * refactor: exclude large unified deps * docs: update markdown docs Co-authored-by: Jonathan Neal <jonathantneal@hotmail.com> * chore: remove extra markdown deps * perf: optimize markdown * fix: unified/rehype deps * temp: fix markdown test * test: add TODO comment * fix: do not namespace frontmatter, just astro metadata * test: fix astro-markdown test * test: add realworld markdown example * fix: prism language bug * docs: update markdown docs * chore: bump dependencies * fix: escape codespan * fix: unterminated string literal * fix(vscode): inline dependencies * fix(vscode): dependencies * feat(vscode): embedded markdown * feat: add Markdown syntax highlighting * chore: improve markdown example * fix: markdown example * feat: highlighting improvements * chore: add changeset * fix: CodeBlock => CodeSpan * chore: get astro-markdown example running Co-authored-by: Jonathan Neal <jonathantneal@hotmail.com>
This commit is contained in:
parent
fe5cf78e8e
commit
b3886c206f
69 changed files with 4731 additions and 286 deletions
5
.changeset/four-avocados-pretend.md
Normal file
5
.changeset/four-avocados-pretend.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro-vscode': patch
|
||||
---
|
||||
|
||||
Added support for new <Markdown> component
|
8
.changeset/loud-actors-develop.md
Normal file
8
.changeset/loud-actors-develop.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
'astro': minor
|
||||
'astro-parser': minor
|
||||
---
|
||||
|
||||
Enhanced **Markdown** support! Markdown processing has been moved from `micromark` to `remark` to prepare Astro for user-provided `remark` plugins _in the future_.
|
||||
|
||||
This change also introduces a built-in `<Markdown>` component for embedding Markdown and any Astro-supported component format inside of `.astro` files. [Read more about Astro's Markdown support.](https://github.com/snowpackjs/astro/blob/main/docs/markdown.md)
|
20
README.md
20
README.md
|
@ -63,12 +63,12 @@ Even though nearly-everything [is configurable][docs-config], we recommend start
|
|||
|
||||
#### 🚦 Routing
|
||||
|
||||
Routing happens in `src/pages/*`. Every `.astro` or `.md.astro` file in this folder corresponds with a public URL. For example:
|
||||
Routing happens in `src/pages/*`. Every `.astro` or `.md` file in this folder corresponds with a public URL. For example:
|
||||
|
||||
| Local file | Public URL |
|
||||
| :------------------------------------- | :------------------------------ |
|
||||
| `src/pages/index.astro` | `/index.html` |
|
||||
| `src/pages/post/my-blog-post.md.astro` | `/post/my-blog-post/index.html` |
|
||||
| `src/pages/post/my-blog-post.md` | `/post/my-blog-post/index.html` |
|
||||
|
||||
#### 🗂 Static Assets
|
||||
|
||||
|
@ -76,7 +76,17 @@ Static assets should be placed in a `public/` folder in your project. You can pl
|
|||
|
||||
#### 🪨 Generating HTML with Astro
|
||||
|
||||
TODO: Astro syntax guide
|
||||
Astro introduces a special `.astro` format, which combines the best of HTML with the best of JavaScript.
|
||||
|
||||
To learn more about `.astro` files, read our complete [Syntax Guide][docs-syntax].
|
||||
|
||||
#### ✍️ Markdown
|
||||
|
||||
Spend less time configuring your tooling and more time writing content. Astro has phenomenal Markdown support (powered by [`remark`][remark]) baked in!
|
||||
|
||||
Not only can you use local `.md` files as pages, but Astro also comes with a `<Markdown>` component to turn every page into a Markdown file. Using the `<Markdown>` component in an `.astro` file should feel very similar to [MDX][mdx], but with the ability to use components from any framework (with [partial hydration](#partial-hydration), too)!
|
||||
|
||||
To learn more about use Markdown in Astro, read our [Markdown Guide][docs-markdown].
|
||||
|
||||
#### ⚡ Dynamic Components
|
||||
|
||||
|
@ -180,13 +190,17 @@ Astro will automatically create a `/sitemap.xml` for you for SEO! Be sure to set
|
|||
👉 [**Dev Server Docs**][docs-dev]
|
||||
|
||||
[docs-config]: ./docs/config.md
|
||||
[docs-syntax]: ./docs/syntax.md
|
||||
[docs-api]: ./docs/api.md
|
||||
[docs-collections]: ./docs/collections.md
|
||||
[docs-markdown]: ./docs/markdown.md
|
||||
[docs-dev]: ./docs/dev.md
|
||||
[docs-styling]: ./docs/styling.md
|
||||
[example-blog]: ./examples/blog
|
||||
[fetch-content]: ./docs/api.md#fetchcontent
|
||||
[fetch-js]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
|
||||
[remark]: https://github.com/remarkjs/remark
|
||||
[mdx]: https://mdxjs.com/
|
||||
[mdn-io]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
|
||||
[mdn-ric]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
|
||||
[routing]: #-routing
|
||||
|
|
120
docs/markdown.md
Normal file
120
docs/markdown.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
## ✍️ Markdown
|
||||
|
||||
Astro comes with out-of-the-box Markdown support powered by the expansive [**remark**](https://github.com/remarkjs/remark) ecosystem.
|
||||
|
||||
## Remark Plugins
|
||||
|
||||
**This is the first draft of Markdown support!** While we plan to support user-provided `remark` plugins soon, our hope is that you won't need `remark` plugins at all!
|
||||
|
||||
In addition to [custom components inside the `<Markdown>` component](#markdown-component), Astro comes with [GitHub-flavored Markdown](https://github.github.com/gfm/) support, [Footnotes](https://github.com/remarkjs/remark-footnotes) syntax, [Smartypants](https://github.com/silvenon/remark-smartypants), and syntax highlighting via [Prism](https://prismjs.com/) pre-enabled. These features are likely to be configurable in the future.
|
||||
|
||||
### Markdown Pages
|
||||
|
||||
Astro treats any `.md` files inside of the `/src/pages` directory as pages. These pages are processed as plain Markdown files and do not support components. If you're looking to embed rich components in your Markdown, take a look at the [Markdown Component](#markdown-component) section.
|
||||
|
||||
#### `layout`
|
||||
|
||||
The only special Frontmatter key is `layout`, which defines the relative path to a `.astro` component which should wrap your Markdown content.
|
||||
|
||||
`src/pages/index.md`
|
||||
```md
|
||||
---
|
||||
layout: ../layouts/main.astro
|
||||
---
|
||||
|
||||
# Hello world!
|
||||
```
|
||||
|
||||
Layout files are normal `.astro` components. Any Frontmatter defined in your `.md` page will be exposed to the Layout component as the `content` prop. `content` also has an `astro` key which holds special metadata about your file, like the complete Markdown `source` and a `headings` object.
|
||||
|
||||
The rendered Markdown content is placed into the default `<slot />` element.
|
||||
|
||||
`src/layouts/main.astro`
|
||||
```jsx
|
||||
---
|
||||
export let content;
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{content.title}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<slot/>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Markdown Component
|
||||
|
||||
Similar to tools like [MDX](https://mdxjs.com/) or [MDsveX](https://github.com/pngwn/MDsveX), Astro makes it straightforward to embed rich, interactive components inside of your Markdown content. The `<Markdown>` component is statically rendered, so it does not add any runtime overhead.
|
||||
|
||||
Astro exposes a special `Markdown` component for `.astro` files which enables markdown syntax for its children **recursively**. Within the `Markdown` component you may also use plain HTML or any other type of component that is supported by Astro.
|
||||
|
||||
```jsx
|
||||
---
|
||||
// For now, this import _must_ be named "Markdown" and _must not_ be wrapped with a custom component
|
||||
// We're working on easing these restrictions!
|
||||
import Markdown from 'astro/components/Markdown.astro';
|
||||
import Layout from '../layouts/main.astro';
|
||||
import MyFancyCodePreview from '../components/MyFancyCodePreview.tsx';
|
||||
|
||||
const expressions = 'Lorem ipsum';
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Markdown>
|
||||
# Hello world!
|
||||
|
||||
**Everything** supported in a `.md` file is also supported here!
|
||||
|
||||
There is _zero_ runtime overhead.
|
||||
|
||||
In addition, Astro supports:
|
||||
- Astro {expressions}
|
||||
- Automatic indentation normalization
|
||||
- Automatic escaping of expressions inside code blocks
|
||||
|
||||
```jsx
|
||||
// This content is not transformed!
|
||||
const object = { someOtherValue };
|
||||
```
|
||||
|
||||
- Rich component support like any `.astro` file!
|
||||
- Recursive Markdown support (Component children are also processed as Markdown)
|
||||
|
||||
<MyFancyCodePreview:visible>
|
||||
```jsx
|
||||
const object = { someOtherValue };
|
||||
```
|
||||
</MyFancyCodePreview:visible>
|
||||
</Markdown>
|
||||
</Layout>
|
||||
```
|
||||
|
||||
### Remote Markdown
|
||||
|
||||
If you have Markdown in a remote source, you may pass it directly to the Markdown component. For example, the example below fetches the README from Snowpack's GitHub repository and renders it as HTML.
|
||||
|
||||
```jsx
|
||||
---
|
||||
import Markdown from 'astro/components/Markdown.astro';
|
||||
|
||||
const content = await fetch('https://raw.githubusercontent.com/snowpackjs/snowpack/main/README.md').then(res => res.text());
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Markdown>{content}</Markdown>
|
||||
</Layout>
|
||||
```
|
||||
|
||||
### Security FAQs
|
||||
|
||||
**Aren't there security concerns to rendering remote markdown directly to HTML?**
|
||||
|
||||
Yes! Just like with regular HTML, improper use the `<Markdown>` component can open you up to a [cross-site scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) attack. If you are rendering untrusted content, be sure to _santize your content **before** rendering it_.
|
||||
|
||||
**Why not use a prop like React's `dangerouslySetInnerHTML={{ __html: content }}`?**
|
||||
|
||||
Rendering a string of HTML (or Markdown) is an extremely common use case when rendering a static site and you probably don't need the extra hoops to jump through. Rendering untrusted content is always dangerous! Be sure to _santize your content **before** rendering it_.
|
6
examples/astro-markdown/astro.config.mjs
Normal file
6
examples/astro-markdown/astro.config.mjs
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
extensions: {
|
||||
'.jsx': 'react',
|
||||
'.tsx': 'preact',
|
||||
}
|
||||
};
|
17
examples/astro-markdown/package.json
Normal file
17
examples/astro-markdown/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "@example/astro-markdown",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"astro-dev": "nodemon --delay 0.5 -w ../../packages/astro/dist -x '../../packages/astro/astro.mjs dev'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "0.0.13",
|
||||
"nodemon": "^2.0.7"
|
||||
},
|
||||
"snowpack": {
|
||||
"workspaceRoot": "../.."
|
||||
}
|
||||
}
|
20
examples/astro-markdown/src/components/PreactCounter.tsx
Normal file
20
examples/astro-markdown/src/components/PreactCounter.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { h, Fragment } from 'preact';
|
||||
import { useState } from 'preact/hooks';
|
||||
|
||||
/** a counter written in Preact */
|
||||
export default function PreactCounter({ children }) {
|
||||
const [count, setCount] = useState(0);
|
||||
const add = () => setCount((i) => i + 1);
|
||||
const subtract = () => setCount((i) => i - 1);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="counter">
|
||||
<button onClick={subtract}>-</button>
|
||||
<pre>{count}</pre>
|
||||
<button onClick={add}>+</button>
|
||||
</div>
|
||||
<div className="children">{children}</div>
|
||||
</>
|
||||
);
|
||||
}
|
19
examples/astro-markdown/src/components/ReactCounter.jsx
Normal file
19
examples/astro-markdown/src/components/ReactCounter.jsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
/** a counter written in React */
|
||||
export default function ReactCounter({ children }) {
|
||||
const [count, setCount] = useState(0);
|
||||
const add = () => setCount((i) => i + 1);
|
||||
const subtract = () => setCount((i) => i - 1);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="counter">
|
||||
<button onClick={subtract}>-</button>
|
||||
<pre>{count}</pre>
|
||||
<button onClick={add}>+</button>
|
||||
</div>
|
||||
<div className="children">{children}</div>
|
||||
</>
|
||||
);
|
||||
}
|
22
examples/astro-markdown/src/components/SvelteCounter.svelte
Normal file
22
examples/astro-markdown/src/components/SvelteCounter.svelte
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
<script>
|
||||
let children;
|
||||
let count = 0;
|
||||
|
||||
function add() {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
function subtract() {
|
||||
count -= 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="counter">
|
||||
<button on:click={subtract}>-</button>
|
||||
<pre>{ count }</pre>
|
||||
<button on:click={add}>+</button>
|
||||
</div>
|
||||
<div class="children">
|
||||
<slot />
|
||||
</div>
|
27
examples/astro-markdown/src/components/VueCounter.vue
Normal file
27
examples/astro-markdown/src/components/VueCounter.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="counter">
|
||||
<button @click="subtract()">-</button>
|
||||
<pre>{{ count }}</pre>
|
||||
<button @click="add()">+</button>
|
||||
</div>
|
||||
<div class="children">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
export default {
|
||||
setup() {
|
||||
const count = ref(0)
|
||||
const add = () => count.value = count.value + 1;
|
||||
const subtract = () => count.value = count.value - 1;
|
||||
|
||||
return {
|
||||
count,
|
||||
add,
|
||||
subtract
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
13
examples/astro-markdown/src/layouts/main.astro
Normal file
13
examples/astro-markdown/src/layouts/main.astro
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
export let content;
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{content.title}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<slot/>
|
||||
</body>
|
||||
</html>
|
211
examples/astro-markdown/src/pages/collections.astro
Normal file
211
examples/astro-markdown/src/pages/collections.astro
Normal file
|
@ -0,0 +1,211 @@
|
|||
---
|
||||
import Markdown from 'astro/components/Markdown.astro';
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Collections</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<Markdown>
|
||||
# 🍱 Collections
|
||||
|
||||
## ❓ What are Collections?
|
||||
|
||||
[Fetching data is easy in Astro][docs-data]. But what if you wanted to make a paginated blog? What if you wanted an easy way to sort data, or filter data based on part of the URL? Or generate an RSS 2.0 feed? When you need something a little more powerful than simple data fetching, Astro’s Collections API may be what you need.
|
||||
|
||||
An Astro Collection is similar to the general concept of Collections in static site generators like Jekyll, Hugo, Eleventy, etc. It’s a general way to load an entire data set. But one big difference between Astro Collections and traditional static site generators is: **Astro lets you seamlessly blend remote API data and local files in a JAMstack-friendly way.** To see how, this guide will walk through a few examples. If you’d like, you can reference the [blog example project][example-blog] to see the finished code in context.
|
||||
|
||||
## 🧑🎨 How to Use
|
||||
|
||||
By default, any Astro component can fetch data from any API or local `*.md` files. But what if you had a blog you wanted to paginate? What if you wanted to generate dynamic URLs based on metadata (e.g. `/tag/:tag/`)? Or do both together? Astro Collections are a way to do all of that. It’s perfect for generating blog-like content, or scaffolding out dynamic URLs from your data.
|
||||
|
||||
Let’s pretend we have some blog posts written already. This is our starting project structure:
|
||||
|
||||
```
|
||||
└── src/
|
||||
└── pages/
|
||||
└── post/
|
||||
└── (blog content)
|
||||
```
|
||||
|
||||
The first step in adding some dynamic collections is deciding on a URL schema. For our example website, we’re aiming for the following URLs:
|
||||
|
||||
- `/post/:post`: A single blog post page
|
||||
- `/posts/:page`: A list page of all blog posts, paginated, and sorted most recent first
|
||||
- `/tag/:tag`: All blog posts, filtered by a specific tag
|
||||
|
||||
Because `/post/:post` references the static files we have already, that doesn’t need to be a collection. But we will need collections for `/posts/:page` and `/tag/:tag` because those will be dynamically generated. For both collections we’ll create a `/src/pages/$[collection].astro` file. This is our new structure:
|
||||
|
||||
```diff
|
||||
└── src/
|
||||
└── pages/
|
||||
├── post/
|
||||
│ └── (blog content)
|
||||
+ ├── $posts.astro -> /posts/1, /posts/2, …
|
||||
+ └── $tag.astro -> /tag/:tag/1, /tag/:tag/2, …
|
||||
```
|
||||
|
||||
💁 **Tip**: Any `.astro` filename beginning with a `$` is how it’s marked as a collection.
|
||||
|
||||
In each `$[collection].astro` file, we’ll need 2 things:
|
||||
|
||||
```js
|
||||
// 1. We need to mark “collection” as a prop (this is a special reserved name)
|
||||
export let collection: any;
|
||||
|
||||
// 2. We need to export an async createCollection() function that will retrieve our data.
|
||||
export async function createCollection() {
|
||||
return {
|
||||
async data() {
|
||||
// return data here to load (we’ll cover how later)
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
These are important so your data is exposed to the page as a prop, and also Astro has everything it needs to gather your data and generate the proper routes. How it does this is more clear if we walk through a practical example.
|
||||
|
||||
#### Example 1: Simple pagination
|
||||
|
||||
Our blog posts all contain `title`, `tags`, and `published_at` in their frontmatter:
|
||||
|
||||
```md
|
||||
---
|
||||
title: My Blog Post
|
||||
tags:
|
||||
- javascript
|
||||
published_at: 2021-03-01 09:34:00
|
||||
---
|
||||
|
||||
# My Blog post
|
||||
|
||||
…
|
||||
```
|
||||
|
||||
There’s nothing special or reserved about any of these names; you’re free to name everything whatever you’d like, or have as much or little frontmatter as you need.
|
||||
|
||||
```jsx
|
||||
// /src/pages/$posts.astro
|
||||
---
|
||||
export let collection: any;
|
||||
|
||||
export async function createCollection() {
|
||||
const allPosts = Astro.fetchContent('./post/*.md'); // load data that already lives at `/post/:slug`
|
||||
allPosts.sort((a, b) => new Date(b.published_at) - new Date(a.published_at)); // sort newest -> oldest (we got "published_at" from frontmatter!)
|
||||
|
||||
// (load more data here, if needed)
|
||||
|
||||
return {
|
||||
async data() {
|
||||
return allPosts;
|
||||
},
|
||||
pageSize: 10, // how many we want to show per-page (default: 25)
|
||||
};
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
return new Date(date).toUTCString();
|
||||
}
|
||||
---
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Blog Posts: page {collection.page.current}</title>
|
||||
<link rel="canonical" href={collection.url.current} />
|
||||
<link rel="prev" href={collection.url.prev} />
|
||||
<link rel="next" href={collection.url.next} />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h5>Results {collection.start + 1}–{collection.end + 1} of {collection.total}</h6>
|
||||
{collection.data.map((post) => (
|
||||
<h1>{post.title}</h1>
|
||||
<time>{formatDate(post.published_at)}</time>
|
||||
<a href={post.url}>Read</a>
|
||||
)}
|
||||
</main>
|
||||
<footer>
|
||||
<h4>Page {collection.page.current} / {collection.page.last}</h4>
|
||||
<nav class="nav">
|
||||
<a class="prev" href={collection.url.prev || '#'}>Prev</a>
|
||||
<a class="next" href={collection.url.next || '#'}>Next</a>
|
||||
</nav>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Let’s walk through some of the key parts:
|
||||
|
||||
- `export let collection`: this is important because it exposes a prop to the page for Astro to return with all your data loaded. ⚠️ **It must be named `collection`**.
|
||||
- `export async function createCollection()`: this is also required, **and must be named this exactly.** This is an async function that lets you load data from anywhere (even a remote API!). At the end, you must return an object with `{ data: yourData }`. There are other options such as `pageSize` we’ll cover later.
|
||||
- `{collection.data.map((post) => (…`: this lets us iterate over all the markdown posts. This will take the shape of whatever you loaded in `createCollection()`. It will always be an array.
|
||||
- `{collection.page.current}`: this, and other properties, simply return more info such as what page a user is on, what the URL is, etc. etc.
|
||||
- Curious about everything on `collection`? See the [reference][collection-api].
|
||||
|
||||
#### Example 2: Advanced filtering & pagination
|
||||
|
||||
In our earlier example, we covered simple pagination for `/posts/1`, but we’d still like to make `/tag/:tag/1` and `/year/:year/1`. To do that, we’ll create 2 more collections: `/src/pages/$tag.astro` and `src/pages/$year.astro`. Assume that the markup is the same, but we’ve expanded the `createCollection()` function with more data.
|
||||
|
||||
```diff
|
||||
// /src/pages/$tag.astro
|
||||
---
|
||||
import Pagination from '../components/Pagination.astro';
|
||||
import PostPreview from '../components/PostPreview.astro';
|
||||
|
||||
export let collection: any;
|
||||
|
||||
export async function createCollection() {
|
||||
const allPosts = Astro.fetchContent('./post/*.md');
|
||||
allPosts.sort((a, b) => new Date(b.published_at) - new Date(a.published_at));
|
||||
+ const allTags = [...new Set(allPosts.map((post) => post.tags).flat())]; // gather all unique tags (we got "tags" from frontmatter!)
|
||||
+ allTags.sort((a, b) => a.localeCompare(b)); // sort tags A -> Z
|
||||
+ const routes = allTags.map((tag) => ({ tag })); // this is where we set { params: { tag } }
|
||||
|
||||
return {
|
||||
- async data() {
|
||||
- return allPosts;
|
||||
+ async data({ params }) {
|
||||
+ return allPosts.filter((post) => post.tags.includes(params.tag)); // filter posts that match the :tag from the URL ("params")
|
||||
},
|
||||
pageSize: 10,
|
||||
+ routes,
|
||||
+ permalink: ({ params }) => `/tag/${params.tag}/` // this is where we generate our URL structure
|
||||
};
|
||||
}
|
||||
---
|
||||
```
|
||||
|
||||
Some important concepts here:
|
||||
|
||||
- `routes = allTags.map((tag) => ({ tag }))`: Astro handles pagination for you automatically. But when it needs to generate multiple routes, this is where you tell Astro about all the possible routes. This way, when you run `astro build`, your static build isn’t missing any pages.
|
||||
- `` permalink: ({ params }) => `/tag/${params.tag}/` ``: this is where you tell Astro what the generated URL should be. Note that while you have control over this, the root of this must match the filename (it's best **NOT** to use `/pages/$tag.astro` to generate `/year/$year.astro`; that should live at `/pages/$year.astro` as a separate file).
|
||||
- `allPosts.filter((post) => post.tag === params.tag)`: we aren’t returning all posts here; we’re only returning posts with a matching tag. _What tag,_ you ask? The `routes` array has `[{ tag: 'javascript' }, { tag: '…`, and all the routes we need to gather. So we first need to query everything, but only return the `.filter()`ed posts at the very end.
|
||||
|
||||
Other things of note is that we are sorting like before, but we filter by the frontmatter `tag` property, and return those at URLs.
|
||||
|
||||
These are still paginated, too! But since there are other conditions applied, they live at a different URL.
|
||||
|
||||
#### Tips
|
||||
|
||||
- Having to load different collections in different `$[collection].astro` files might seem like a pain at first, until you remember **you can create reusable components!** Treat `/pages/*.astro` files as your one-off routing & data fetching logic, and treat `/components/*.astro` as your reusable markup. If you find yourself duplicating things too much, you can probably use a component instead!
|
||||
- Stay true to `/pages/$[collection].astro` naming. If you have an `/all-posts/*` route, then use `/pages/$all-posts.astro` to manage that. Don’t try and trick `permalink` to generate too many URL trees; it’ll only result in pages being missed when it comes time to build.
|
||||
|
||||
### 📚 Further Reading
|
||||
|
||||
- [Fetching data in Astro][docs-data]
|
||||
- API Reference: [collection][collection-api]
|
||||
- API Reference: [createCollection()][create-collection-api]
|
||||
- API Reference: [Creating an RSS feed][create-collection-api]
|
||||
|
||||
[docs-data]: ../README.md#-fetching-data
|
||||
[collection-api]: ./api.md#collection
|
||||
[create-collection-api]: ./api.md#createcollection
|
||||
[example-blog]: ../examples/blog
|
||||
[fetch-content]: ./api.md#fetchcontent
|
||||
</Markdown>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
44
examples/astro-markdown/src/pages/index.astro
Normal file
44
examples/astro-markdown/src/pages/index.astro
Normal file
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
import Markdown from 'astro/components/Markdown.astro';
|
||||
import Layout from '../layouts/main.astro';
|
||||
import ReactCounter from '../components/ReactCounter.jsx';
|
||||
import PreactCounter from '../components/PreactCounter.tsx';
|
||||
import VueCounter from '../components/VueCounter.vue';
|
||||
import SvelteCounter from '../components/SvelteCounter.svelte';
|
||||
|
||||
const title = 'Astro Markdown';
|
||||
const variable = 'content';
|
||||
const items = ['A', 'B', 'C'];
|
||||
---
|
||||
|
||||
<Layout content={{ title }}>
|
||||
<Markdown>
|
||||
# Introducing {title}
|
||||
|
||||
**Astro Markdown** brings native Markdown support to HTML!
|
||||
|
||||
> It's inspired by [`mdx`](https://mdxjs.com/) and powered by [`remark`](https://github.com/remarkjs/remark)).
|
||||
|
||||
The best part? It comes with all the Astro features you expect.
|
||||
|
||||
## Embed framework components
|
||||
|
||||
<ReactCounter:visible />
|
||||
<PreactCounter:visible />
|
||||
<VueCounter:visible />
|
||||
<SvelteCounter:visible />
|
||||
|
||||
## Use Expressions
|
||||
|
||||
You can use any {variable} in scope and use JavaScript for templating ({items.join(', ')})
|
||||
|
||||
## Oh yeah...
|
||||
|
||||
<ReactCounter:visible>
|
||||
🤯 It's also _recursive_!
|
||||
|
||||
### Markdown can be embedded in any child component
|
||||
</ReactCounter:visible>
|
||||
|
||||
</Markdown>
|
||||
</Layout>
|
5
examples/remote-markdown/astro.config.mjs
Normal file
5
examples/remote-markdown/astro.config.mjs
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
extensions: {
|
||||
'.jsx': 'preact'
|
||||
}
|
||||
}
|
48
examples/remote-markdown/docs/dev.md
Normal file
48
examples/remote-markdown/docs/dev.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Development Server
|
||||
|
||||
The development server comes as part of the Astro CLI. Start the server with:
|
||||
|
||||
```shell
|
||||
astro dev
|
||||
```
|
||||
|
||||
In your project root. You can specify an alternative
|
||||
|
||||
## Special routes
|
||||
|
||||
The dev server will serve the following special routes:
|
||||
|
||||
### /400
|
||||
|
||||
This is a custom **400** status code page. You can add this route by adding a page component to your `src/pages` folder:
|
||||
|
||||
```
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ └── pages/
|
||||
│ └── 400.astro
|
||||
```
|
||||
|
||||
For any URL you visit that doesn't have a corresponding page, the `400.astro` file will be used.
|
||||
|
||||
### /500
|
||||
|
||||
This is a custom **500** status code page. You can add this route by adding a page component to your `src/pages` folder:
|
||||
|
||||
```astro
|
||||
├── src/ │ ├── components/ │ └── pages/ │ └── 500.astro
|
||||
```
|
||||
|
||||
This page is used any time an error occurs in the dev server.
|
||||
|
||||
The 500 page will receive an `error` query parameter which you can access with:
|
||||
|
||||
```
|
||||
---
|
||||
const error = Astro.request.url.searchParams.get('error');
|
||||
---
|
||||
|
||||
<strong>{error}</strong>
|
||||
```
|
||||
|
||||
A default error page is included with Astro so you will get pretty error messages even without adding a custom 500 page.
|
17
examples/remote-markdown/package.json
Normal file
17
examples/remote-markdown/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "@example/remote-markdown",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"astro-dev": "nodemon --delay 0.5 -w ../../packages/astro/dist -x '../../packages/astro/astro.mjs dev'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "0.0.13",
|
||||
"nodemon": "^2.0.7"
|
||||
},
|
||||
"snowpack": {
|
||||
"workspaceRoot": "../.."
|
||||
}
|
||||
}
|
5
examples/remote-markdown/src/components/Yell.jsx
Normal file
5
examples/remote-markdown/src/components/Yell.jsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { h, Fragment } from 'preact';
|
||||
|
||||
export default function Yell({ children }) {
|
||||
return children.filter(v => typeof v === 'string').join('').toUpperCase() + '!'
|
||||
}
|
14
examples/remote-markdown/src/layouts/main.astro
Normal file
14
examples/remote-markdown/src/layouts/main.astro
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
export let content;
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{content.title}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<slot />
|
||||
<pre>{JSON.stringify(content)}</pre>
|
||||
</body>
|
||||
</html>
|
72
examples/remote-markdown/src/pages/index.astro
Normal file
72
examples/remote-markdown/src/pages/index.astro
Normal file
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
import Markdown from 'astro/components/Markdown.astro';
|
||||
import Yell from '../components/Yell.jsx';
|
||||
const title = 'INTERPOLATED';
|
||||
const quietTest = 'interpolated';
|
||||
const content = await fetch('https://raw.githubusercontent.com/snowpackjs/snowpack/main/README.md').then(res => res.text());
|
||||
---
|
||||
|
||||
<!-- Basic -->
|
||||
<Markdown>
|
||||
# Hello world!
|
||||
</Markdown>
|
||||
|
||||
<!-- Indented -->
|
||||
<Markdown>
|
||||
# Hello indent!
|
||||
</Markdown>
|
||||
|
||||
<!-- Interpolation -->
|
||||
<Markdown>
|
||||
# Hello {title}!
|
||||
</Markdown>
|
||||
|
||||
|
||||
<!-- Can I break this? -->
|
||||
<Markdown>
|
||||
# I cannot!
|
||||
|
||||
<div>
|
||||
# ahhhh
|
||||
</div>
|
||||
|
||||
<Yell>{quietTest}</Yell>
|
||||
|
||||
<strong>Dope</strong>
|
||||
|
||||
`nice`
|
||||
|
||||
```
|
||||
plain fence
|
||||
```
|
||||
|
||||
```html
|
||||
don't <div>me</div> bro
|
||||
```
|
||||
|
||||
```js
|
||||
Astro.fetchContent()
|
||||
```
|
||||
|
||||
### cool stuff?
|
||||
```astro
|
||||
{'can\'t interpolate'}
|
||||
{}
|
||||
{title}
|
||||
|
||||
Do I break? <Markdown> </Markdown>
|
||||
```
|
||||
</Markdown>
|
||||
|
||||
<!-- external content -->
|
||||
<Markdown>{content}</Markdown>
|
||||
|
||||
<!-- external with newlines -->
|
||||
<Markdown>
|
||||
{content}
|
||||
</Markdown>
|
||||
|
||||
<!-- external with indentation -->
|
||||
<Markdown>
|
||||
{content}
|
||||
</Markdown>
|
|
@ -6,8 +6,8 @@
|
|||
"release": "yarn build && yarn changeset publish",
|
||||
"build": "yarn build:core",
|
||||
"build:core": "lerna run build --scope astro --scope astro-parser --scope create-astro",
|
||||
"build:vscode": "lerna run build --scope astro-languageserver --scope astro-vscode",
|
||||
"dev:vscode": "lerna run dev --scope astro-languageserver --scope astro-vscode --parallel --stream",
|
||||
"build:vscode": "lerna run build --scope astro-languageserver --scope astro-vscode --scope astro-parser",
|
||||
"dev:vscode": "lerna run dev --scope astro-languageserver --scope astro-vscode --scope astro-parser --parallel --stream",
|
||||
"format": "prettier -w '**/*.{js,jsx,ts,tsx,md,json}'",
|
||||
"lint": "eslint 'packages/**/*.ts'",
|
||||
"test": "yarn test:core && yarn test:prettier",
|
||||
|
|
|
@ -20,6 +20,20 @@ export interface Text extends BaseNode {
|
|||
raw: string;
|
||||
}
|
||||
|
||||
export interface CodeFence extends BaseNode {
|
||||
type: 'CodeFence';
|
||||
metadata: string;
|
||||
data: string;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export interface CodeSpan extends BaseNode {
|
||||
type: 'CodeFence';
|
||||
metadata: string;
|
||||
data: string;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export interface Attribute extends BaseNode {
|
||||
type: 'Attribute';
|
||||
name: string;
|
||||
|
@ -48,7 +62,7 @@ export interface Transition extends BaseDirective {
|
|||
|
||||
export type Directive = BaseDirective | Transition;
|
||||
|
||||
export type TemplateNode = Text | MustacheTag | BaseNode | Directive | Transition;
|
||||
export type TemplateNode = Text | CodeSpan | CodeFence | MustacheTag | BaseNode | Directive | Transition;
|
||||
|
||||
export interface Expression {
|
||||
type: 'Expression';
|
||||
|
|
38
packages/astro-parser/src/parse/state/codefence.ts
Normal file
38
packages/astro-parser/src/parse/state/codefence.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
// @ts-nocheck
|
||||
import { Parser } from '../index.js';
|
||||
|
||||
export default function codefence(parser: Parser) {
|
||||
const start = parser.index;
|
||||
const open = parser.match_regex(/[`~]{3,}/);
|
||||
parser.index += open!.length;
|
||||
|
||||
let raw = open + '';
|
||||
|
||||
while (parser.index < parser.template.length && !parser.match(open)) {
|
||||
raw += parser.template[parser.index++];
|
||||
}
|
||||
|
||||
parser.eat(open, true);
|
||||
raw += open;
|
||||
const trailingWhitespace = parser.read_until(/\S/);
|
||||
const { metadata, data } = extractCodeFence(raw);
|
||||
|
||||
const node = {
|
||||
start,
|
||||
end: parser.index,
|
||||
type: 'CodeFence',
|
||||
raw: `${raw}` + trailingWhitespace,
|
||||
metadata,
|
||||
data
|
||||
};
|
||||
|
||||
parser.current().children.push(node);
|
||||
}
|
||||
|
||||
/** Extract attributes on first line */
|
||||
function extractCodeFence(str: string) {
|
||||
const [_, leadingLine] = str.match(/(^[^\n]*\r?\n)/m) ?? ['', ''];
|
||||
const metadata = leadingLine.trim();
|
||||
const data = str.slice(leadingLine.length);
|
||||
return { metadata, data };
|
||||
}
|
25
packages/astro-parser/src/parse/state/codespan.ts
Normal file
25
packages/astro-parser/src/parse/state/codespan.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
// @ts-nocheck
|
||||
import { Parser } from '../index.js';
|
||||
|
||||
export default function codespan(parser: Parser) {
|
||||
const start = parser.index;
|
||||
const open = parser.match_regex(/(?<!\\)`{1,2}/);
|
||||
parser.index += open!.length;
|
||||
|
||||
let raw = open;
|
||||
while (parser.index < parser.template.length && !parser.match(open)) {
|
||||
raw += parser.template[parser.index++];
|
||||
}
|
||||
parser.eat(open, true);
|
||||
raw += open;
|
||||
|
||||
const node = {
|
||||
start,
|
||||
end: parser.index,
|
||||
type: 'CodeSpan',
|
||||
raw,
|
||||
data: raw?.slice(open?.length, open?.length * -1).replace(/^ /, '').replace(/ $/, '')
|
||||
};
|
||||
|
||||
parser.current().children.push(node);
|
||||
}
|
|
@ -2,6 +2,8 @@ import tag from './tag.js';
|
|||
import setup from './setup.js';
|
||||
import mustache from './mustache.js';
|
||||
import text from './text.js';
|
||||
import codefence from './codefence.js';
|
||||
import codespan from './codespan.js';
|
||||
import { Parser } from '../index.js';
|
||||
|
||||
export default function fragment(parser: Parser) {
|
||||
|
@ -9,6 +11,15 @@ export default function fragment(parser: Parser) {
|
|||
return setup;
|
||||
}
|
||||
|
||||
// Fenced code blocks are pretty complex in the GFM spec
|
||||
// https://github.github.com/gfm/#fenced-code-blocks
|
||||
if (parser.match_regex(/[`~]{3,}/)) {
|
||||
return codefence;
|
||||
}
|
||||
if (parser.match_regex(/(?<!\\)`{1,2}/)) {
|
||||
return codespan;
|
||||
}
|
||||
|
||||
if (parser.match('<')) {
|
||||
return tag;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @ts-nocheck
|
||||
|
||||
import read_expression from '../read/expression.js';
|
||||
import read_script from '../read/script.js';
|
||||
import read_style from '../read/style.js';
|
||||
import { decode_character_references, closing_tag_omitted } from '../utils/html.js';
|
||||
import { is_void } from '../../utils/names.js';
|
||||
|
@ -518,7 +517,7 @@ function read_attribute_value(parser: Parser) {
|
|||
return value;
|
||||
}
|
||||
|
||||
function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
|
||||
export function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
|
||||
let current_chunk: Text = {
|
||||
start: parser.index,
|
||||
end: null,
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function text(parser: Parser) {
|
|||
|
||||
let data = '';
|
||||
|
||||
while (parser.index < parser.template.length && !parser.match('---') && !parser.match('<') && !parser.match('{')) {
|
||||
while (parser.index < parser.template.length && !parser.match('---') && !parser.match('<') && !parser.match('{') && !parser.match('`')) {
|
||||
data += parser.template[parser.index++];
|
||||
}
|
||||
|
||||
|
|
3
packages/astro/components/Markdown.astro
Normal file
3
packages/astro/components/Markdown.astro
Normal file
|
@ -0,0 +1,3 @@
|
|||
<!-- Probably not what you're looking for! -->
|
||||
<!-- Check `astro-parser` or /frontend/markdown.ts -->
|
||||
<slot />
|
|
@ -26,6 +26,7 @@ if(languageMap.has(lang)) {
|
|||
ensureLoaded('typescript');
|
||||
addAstro(Prism);
|
||||
} else {
|
||||
ensureLoaded('markup-templating'); // Prism expects this to exist for a number of other langs
|
||||
ensureLoaded(lang);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"@babel/generator": "^7.13.9",
|
||||
"@babel/parser": "^7.13.15",
|
||||
"@babel/traverse": "^7.13.15",
|
||||
"@silvenon/remark-smartypants": "^1.0.0",
|
||||
"@snowpack/plugin-sass": "^1.4.0",
|
||||
"@snowpack/plugin-svelte": "^3.6.1",
|
||||
"@snowpack/plugin-vue": "^2.4.0",
|
||||
|
@ -58,10 +59,7 @@
|
|||
"kleur": "^4.1.4",
|
||||
"locate-character": "^2.0.5",
|
||||
"magic-string": "^0.25.3",
|
||||
"micromark": "^2.11.4",
|
||||
"micromark-extension-gfm": "^0.3.3",
|
||||
"micromark-extension-mdx-expression": "^0.3.2",
|
||||
"micromark-extension-mdx-jsx": "^0.3.3",
|
||||
"mdast-util-mdx": "^0.1.1",
|
||||
"mime": "^2.5.2",
|
||||
"moize": "^6.0.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
@ -74,6 +72,12 @@
|
|||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"rehype-parse": "^7.0.1",
|
||||
"rehype-raw": "^5.1.0",
|
||||
"rehype-stringify": "^8.0.0",
|
||||
"remark-footnotes": "^3.0.0",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"remark-parse": "^9.0.0",
|
||||
"remark-rehype": "^8.1.0",
|
||||
"rollup": "^2.43.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"sass": "^1.32.13",
|
||||
|
@ -102,7 +106,8 @@
|
|||
"@types/react-dom": "^17.0.2",
|
||||
"@types/sass": "^1.16.0",
|
||||
"@types/yargs-parser": "^20.2.0",
|
||||
"astro-scripts": "0.0.1"
|
||||
"astro-scripts": "0.0.1",
|
||||
"unist-util-visit": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
export interface MicromarkExtensionContext {
|
||||
sliceSerialize(node: any): string;
|
||||
raw(value: string): void;
|
||||
tag(value: string): void;
|
||||
data(value: string): void;
|
||||
resume(): any;
|
||||
}
|
||||
|
||||
export type MicromarkExtensionCallback = (this: MicromarkExtensionContext, node: any) => void;
|
||||
|
|
|
@ -181,7 +181,7 @@ async function gatherRuntimes({ astroConfig, buildState, filepath, logging, reso
|
|||
|
||||
let source = await fs.promises.readFile(filepath, 'utf8');
|
||||
if (filepath.pathname.endsWith('.md')) {
|
||||
source = await convertMdToAstroSource(source);
|
||||
source = await convertMdToAstroSource(source, { filename: fileURLToPath(filepath) });
|
||||
}
|
||||
|
||||
const ast = parse(source, { filepath });
|
||||
|
|
|
@ -305,6 +305,9 @@ interface CodegenState {
|
|||
filename: string;
|
||||
components: Components;
|
||||
css: string[];
|
||||
markers: {
|
||||
insideMarkdown: boolean|string;
|
||||
};
|
||||
importExportStatements: Set<string>;
|
||||
dynamicImports: DynamicImportMap;
|
||||
}
|
||||
|
@ -318,6 +321,7 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
|||
const componentExports: ExportNamedDeclaration[] = [];
|
||||
|
||||
const contentImports = new Map<string, { spec: string; declarator: string }>();
|
||||
const importSpecifierTypes = new Set(['ImportDefaultSpecifier', 'ImportSpecifier']);
|
||||
|
||||
let script = '';
|
||||
let propsStatement = '';
|
||||
|
@ -418,7 +422,7 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
|
|||
const specifier = componentImport.specifiers[0];
|
||||
if (!specifier) continue; // this is unused
|
||||
// set componentName to default import if used (user), or use filename if no default import (mostly internal use)
|
||||
const componentName = specifier.type === 'ImportDefaultSpecifier' ? specifier.local.name : path.posix.basename(importUrl, componentType);
|
||||
const componentName = importSpecifierTypes.has(specifier.type) ? specifier.local.name : path.posix.basename(importUrl, componentType);
|
||||
const plugin = extensions[componentType] || defaultExtensions[componentType];
|
||||
state.components[componentName] = {
|
||||
type: componentType,
|
||||
|
@ -541,7 +545,7 @@ function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOption
|
|||
|
||||
let outSource = '';
|
||||
walk(enterNode, {
|
||||
enter(node: TemplateNode) {
|
||||
enter(node: TemplateNode, parent: TemplateNode) {
|
||||
switch (node.type) {
|
||||
case 'Expression': {
|
||||
let children: string[] = [];
|
||||
|
@ -579,27 +583,42 @@ function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOption
|
|||
try {
|
||||
const attributes = getAttributes(node.attributes);
|
||||
|
||||
outSource += outSource === '' ? '' : ',';
|
||||
if (node.type === 'Slot') {
|
||||
outSource += `(children`;
|
||||
outSource += outSource === '' ? '' : ',';
|
||||
if (node.type === 'Slot') {
|
||||
outSource += `(children`;
|
||||
return;
|
||||
}
|
||||
const COMPONENT_NAME_SCANNER = /^[A-Z]/;
|
||||
if (!COMPONENT_NAME_SCANNER.test(name)) {
|
||||
outSource += `h("${name}", ${attributes ? generateAttributes(attributes) : 'null'}`;
|
||||
if (state.markers.insideMarkdown) {
|
||||
outSource += `,h(__astroMarkdownRender, null`
|
||||
}
|
||||
return;
|
||||
}
|
||||
const [componentName, componentKind] = name.split(':');
|
||||
const componentImportData = components[componentName];
|
||||
if (!componentImportData) {
|
||||
throw new Error(`Unknown Component: ${componentName}`);
|
||||
}
|
||||
if (componentImportData.type === '.astro') {
|
||||
if (componentName === 'Markdown') {
|
||||
const attributeStr = attributes ? generateAttributes(attributes) : 'null';
|
||||
state.markers.insideMarkdown = attributeStr;
|
||||
outSource += `h(__astroMarkdownRender, ${attributeStr}`
|
||||
return;
|
||||
}
|
||||
const COMPONENT_NAME_SCANNER = /^[A-Z]/;
|
||||
if (!COMPONENT_NAME_SCANNER.test(name)) {
|
||||
outSource += `h("${name}", ${attributes ? generateAttributes(attributes) : 'null'}`;
|
||||
return;
|
||||
}
|
||||
const [componentName, componentKind] = name.split(':');
|
||||
const componentImportData = components[componentName];
|
||||
if (!componentImportData) {
|
||||
throw new Error(`Unknown Component: ${componentName}`);
|
||||
}
|
||||
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], { astroConfig, dynamicImports, filename });
|
||||
if (wrapperImport) {
|
||||
importExportStatements.add(wrapperImport);
|
||||
}
|
||||
}
|
||||
const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], { astroConfig, dynamicImports, filename });
|
||||
if (wrapperImport) {
|
||||
importExportStatements.add(wrapperImport);
|
||||
}
|
||||
|
||||
outSource += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`;
|
||||
if (state.markers.insideMarkdown) {
|
||||
const attributeStr = state.markers.insideMarkdown;
|
||||
outSource += `,h(__astroMarkdownRender, ${attributeStr}`
|
||||
}
|
||||
} catch (err) {
|
||||
// handle errors in scope with filename
|
||||
const rel = filename.replace(astroConfig.projectRoot.pathname, '');
|
||||
|
@ -617,9 +636,16 @@ function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOption
|
|||
this.skip();
|
||||
return;
|
||||
}
|
||||
case 'CodeSpan':
|
||||
case 'CodeFence': {
|
||||
outSource += ',' + JSON.stringify(node.raw);
|
||||
return;
|
||||
}
|
||||
case 'Text': {
|
||||
const text = getTextFromAttribute(node);
|
||||
if (!text.trim()) {
|
||||
// Whitespace is significant if we are immediately inside of <Markdown>,
|
||||
// but not if we're inside of another component in <Markdown>
|
||||
if (parent.name !== 'Markdown' && !text.trim()) {
|
||||
return;
|
||||
}
|
||||
outSource += ',' + JSON.stringify(text);
|
||||
|
@ -632,6 +658,8 @@ function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOption
|
|||
leave(node, parent, prop, index) {
|
||||
switch (node.type) {
|
||||
case 'Text':
|
||||
case 'CodeSpan':
|
||||
case 'CodeFence':
|
||||
case 'Attribute':
|
||||
case 'Comment':
|
||||
case 'Fragment':
|
||||
|
@ -643,9 +671,16 @@ function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOption
|
|||
case 'Body':
|
||||
case 'Title':
|
||||
case 'Element':
|
||||
case 'InlineComponent':
|
||||
case 'InlineComponent': {
|
||||
if (node.type === 'InlineComponent' && node.name === 'Markdown') {
|
||||
state.markers.insideMarkdown = false;
|
||||
}
|
||||
if (state.markers.insideMarkdown) {
|
||||
outSource += ')';
|
||||
}
|
||||
outSource += ')';
|
||||
return;
|
||||
}
|
||||
case 'Style': {
|
||||
this.remove(); // this will be optimized in a global CSS file; remove so it‘s not accidentally inlined
|
||||
return;
|
||||
|
@ -674,8 +709,11 @@ export async function codegen(ast: Ast, { compileOptions, filename }: CodeGenOpt
|
|||
filename,
|
||||
components: {},
|
||||
css: [],
|
||||
markers: {
|
||||
insideMarkdown: false
|
||||
},
|
||||
importExportStatements: new Set(),
|
||||
dynamicImports: new Map(),
|
||||
dynamicImports: new Map()
|
||||
};
|
||||
|
||||
const { script, componentPlugins, createCollection } = compileModule(ast.module, state, compileOptions);
|
||||
|
|
|
@ -3,15 +3,9 @@ import type { CompileResult, TransformResult } from '../@types/astro';
|
|||
import type { CompileOptions } from '../@types/compiler.js';
|
||||
|
||||
import path from 'path';
|
||||
import micromark from 'micromark';
|
||||
import gfmSyntax from 'micromark-extension-gfm';
|
||||
import matter from 'gray-matter';
|
||||
import gfmHtml from 'micromark-extension-gfm/html.js';
|
||||
import { renderMarkdownWithFrontmatter } from './utils.js';
|
||||
|
||||
import { parse } from 'astro-parser';
|
||||
import { createMarkdownHeadersCollector } from './markdown/micromark-collect-headers.js';
|
||||
import { encodeMarkdown } from './markdown/micromark-encode.js';
|
||||
import { encodeAstroMdx } from './markdown/micromark-mdx-astro.js';
|
||||
import { transform } from './transform/index.js';
|
||||
import { codegen } from './codegen/index.js';
|
||||
|
||||
|
@ -53,38 +47,24 @@ async function convertAstroToJsx(template: string, opts: ConvertAstroOptions): P
|
|||
/**
|
||||
* .md -> .astro source
|
||||
*/
|
||||
export async function convertMdToAstroSource(contents: string): Promise<string> {
|
||||
const { data: frontmatterData, content } = matter(contents);
|
||||
const { headers, headersExtension } = createMarkdownHeadersCollector();
|
||||
const { htmlAstro, mdAstro } = encodeAstroMdx();
|
||||
const mdHtml = micromark(content, {
|
||||
allowDangerousHtml: true,
|
||||
extensions: [gfmSyntax(), ...htmlAstro],
|
||||
htmlExtensions: [gfmHtml, encodeMarkdown, headersExtension, mdAstro],
|
||||
});
|
||||
|
||||
// TODO: Warn if reserved word is used in "frontmatterData"
|
||||
const contentData: any = {
|
||||
...frontmatterData,
|
||||
headers,
|
||||
source: content,
|
||||
};
|
||||
|
||||
let imports = '';
|
||||
for (let [ComponentName, specifier] of Object.entries(frontmatterData.import || {})) {
|
||||
imports += `import ${ComponentName} from '${specifier}';\n`;
|
||||
export async function convertMdToAstroSource(contents: string, { filename }: { filename: string }): Promise<string> {
|
||||
const { content, frontmatter: { layout, ...frontmatter }, ...data } = await renderMarkdownWithFrontmatter(contents);
|
||||
if (frontmatter['astro'] !== undefined) {
|
||||
throw new Error(`"astro" is a reserved word but was used as a frontmatter value!\n\tat ${filename}`);
|
||||
}
|
||||
|
||||
const contentData: any = {
|
||||
...frontmatter,
|
||||
...data
|
||||
};
|
||||
// </script> can't be anywhere inside of a JS string, otherwise the HTML parser fails.
|
||||
// Break it up here so that the HTML parser won't detect it.
|
||||
const stringifiedSetupContext = JSON.stringify(contentData).replace(/\<\/script\>/g, `</scrip" + "t>`);
|
||||
|
||||
return `---
|
||||
${imports}
|
||||
${frontmatterData.layout ? `import {__renderPage as __layout} from '${frontmatterData.layout}';` : 'const __layout = undefined;'}
|
||||
export const __content = ${stringifiedSetupContext};
|
||||
${layout ? `import {__renderPage as __layout} from '${layout}';` : 'const __layout = undefined;'}
|
||||
export const __content = ${stringifiedSetupContext};
|
||||
---
|
||||
<section>${mdHtml}</section>`;
|
||||
${content}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,24 +75,24 @@ async function convertMdToJsx(
|
|||
contents: string,
|
||||
{ compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string }
|
||||
): Promise<TransformResult> {
|
||||
const raw = await convertMdToAstroSource(contents);
|
||||
const raw = await convertMdToAstroSource(contents, { filename });
|
||||
const convertOptions = { compileOptions, filename, fileID };
|
||||
return await convertAstroToJsx(raw, convertOptions);
|
||||
}
|
||||
|
||||
type SupportedExtensions = '.astro' | '.md';
|
||||
|
||||
/** Given a file, process it either as .astro or .md. */
|
||||
/** Given a file, process it either as .astro, .md */
|
||||
async function transformFromSource(
|
||||
contents: string,
|
||||
{ compileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
|
||||
): Promise<TransformResult> {
|
||||
const fileID = path.relative(projectRoot, filename);
|
||||
switch (path.extname(filename) as SupportedExtensions) {
|
||||
case '.astro':
|
||||
switch (true) {
|
||||
case filename.slice(-6) === '.astro':
|
||||
return await convertAstroToJsx(contents, { compileOptions, filename, fileID });
|
||||
case '.md':
|
||||
|
||||
case filename.slice(-3) === '.md':
|
||||
return await convertMdToJsx(contents, { compileOptions, filename, fileID });
|
||||
|
||||
default:
|
||||
throw new Error('Not Supported!');
|
||||
}
|
||||
|
@ -125,6 +105,7 @@ export async function compileComponent(
|
|||
): Promise<CompileResult> {
|
||||
const result = await transformFromSource(source, { compileOptions, filename, projectRoot });
|
||||
const site = compileOptions.astroConfig.buildOptions.site || `http://localhost:${compileOptions.astroConfig.devOptions.port}`;
|
||||
const usesMarkdown = !!result.imports.find(spec => spec.indexOf('Markdown') > -1);
|
||||
|
||||
// return template
|
||||
let modJsx = `
|
||||
|
@ -135,6 +116,7 @@ ${result.imports.join('\n')}
|
|||
|
||||
// \`__render()\`: Render the contents of the Astro module.
|
||||
import { h, Fragment } from '${internalImport('h.js')}';
|
||||
${usesMarkdown ? `import __astroMarkdownRender from '${internalImport('markdown.js')}';` : ''};
|
||||
const __astroRequestSymbol = Symbol('astro.request');
|
||||
async function __render(props, ...children) {
|
||||
const Astro = {
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import slugger from 'github-slugger';
|
||||
|
||||
/**
|
||||
* Create Markdown Headers Collector
|
||||
* NOTE: micromark has terrible TS types. Instead of fighting with the
|
||||
* limited/broken TS types that they ship, we just reach for our good friend, "any".
|
||||
*/
|
||||
export function createMarkdownHeadersCollector() {
|
||||
const headers: any[] = [];
|
||||
let currentHeader: any;
|
||||
return {
|
||||
headers,
|
||||
headersExtension: {
|
||||
enter: {
|
||||
atxHeading(node: any) {
|
||||
currentHeader = {};
|
||||
headers.push(currentHeader);
|
||||
this.buffer();
|
||||
},
|
||||
atxHeadingSequence(node: any) {
|
||||
currentHeader.depth = this.sliceSerialize(node).length;
|
||||
},
|
||||
atxHeadingText(node: any) {
|
||||
currentHeader.text = this.sliceSerialize(node);
|
||||
},
|
||||
} as any,
|
||||
exit: {
|
||||
atxHeading(node: any) {
|
||||
currentHeader.slug = slugger.slug(currentHeader.text);
|
||||
this.resume();
|
||||
this.tag(`<h${currentHeader.depth} id="${currentHeader.slug}">`);
|
||||
this.raw(currentHeader.text);
|
||||
this.tag(`</h${currentHeader.depth}>`);
|
||||
},
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
import type { Token } from 'micromark/dist/shared-types';
|
||||
import type { MicromarkExtension, MicromarkExtensionContext } from '../../@types/micromark';
|
||||
|
||||
const characterReferences = {
|
||||
'"': 'quot',
|
||||
'&': 'amp',
|
||||
'<': 'lt',
|
||||
'>': 'gt',
|
||||
'{': 'lbrace',
|
||||
'}': 'rbrace',
|
||||
};
|
||||
|
||||
type EncodedChars = '"' | '&' | '<' | '>' | '{' | '}';
|
||||
|
||||
/** Encode HTML entity */
|
||||
function encode(value: string): string {
|
||||
return value.replace(/["&<>{}]/g, (raw: string) => {
|
||||
return '&' + characterReferences[raw as EncodedChars] + ';';
|
||||
});
|
||||
}
|
||||
|
||||
/** Encode Markdown node */
|
||||
function encodeToken(this: MicromarkExtensionContext) {
|
||||
const token: Token = arguments[0];
|
||||
const value = this.sliceSerialize(token);
|
||||
this.raw(encode(value));
|
||||
}
|
||||
|
||||
const plugin: MicromarkExtension = {
|
||||
exit: {
|
||||
codeTextData: encodeToken,
|
||||
codeFlowValue: encodeToken,
|
||||
},
|
||||
};
|
||||
|
||||
export { plugin as encodeMarkdown };
|
|
@ -1,22 +0,0 @@
|
|||
import type { MicromarkExtension } from '../../@types/micromark';
|
||||
import mdxExpression from 'micromark-extension-mdx-expression';
|
||||
import mdxJsx from 'micromark-extension-mdx-jsx';
|
||||
|
||||
/**
|
||||
* Keep MDX.
|
||||
*/
|
||||
export function encodeAstroMdx() {
|
||||
const extension: MicromarkExtension = {
|
||||
enter: {
|
||||
mdxJsxFlowTag(node: any) {
|
||||
const mdx = this.sliceSerialize(node);
|
||||
this.raw(mdx);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
htmlAstro: [mdxExpression(), mdxJsx()],
|
||||
mdAstro: extension,
|
||||
};
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
declare module 'micromark-extension-mdx-expression' {
|
||||
import type { HtmlExtension } from 'micromark/dist/shared-types';
|
||||
|
||||
export default function (): HtmlExtension;
|
||||
declare module '@silvenon/remark-smartypants' {
|
||||
export default function (): any;
|
||||
}
|
||||
|
||||
declare module 'micromark-extension-mdx-jsx' {
|
||||
import type { HtmlExtension } from 'micromark/dist/shared-types';
|
||||
|
||||
export default function (): HtmlExtension;
|
||||
declare module 'mdast-util-mdx/from-markdown.js' {
|
||||
export default function (): any;
|
||||
}
|
||||
|
||||
declare module 'mdast-util-mdx/to-markdown.js' {
|
||||
export default function (): any;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { visit } from 'unist-util-visit';
|
||||
import slugger from 'github-slugger';
|
||||
|
||||
/** */
|
||||
export default function createCollectHeaders() {
|
||||
const headers: any[] = [];
|
||||
|
||||
const visitor = (node: any) => {
|
||||
if (node.type !== 'element') return;
|
||||
const { tagName, children } = node
|
||||
if (tagName[0] !== 'h') return;
|
||||
let [_, depth] = tagName.match(/h([0-6])/) ?? [];
|
||||
if (!depth) return;
|
||||
depth = Number.parseInt(depth);
|
||||
|
||||
let text = '';
|
||||
visit(node, 'text', (child) => {
|
||||
text += child.value;
|
||||
})
|
||||
|
||||
let slug = slugger.slug(text);
|
||||
node.properties = node.properties || {};
|
||||
node.properties.id = slug;
|
||||
headers.push({ depth, slug, text });
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return { headers, rehypeCollectHeaders: () => (tree: any) => visit(tree, visitor) }
|
||||
}
|
26
packages/astro/src/compiler/markdown/remark-mdx-lite.ts
Normal file
26
packages/astro/src/compiler/markdown/remark-mdx-lite.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import fromMarkdown from 'mdast-util-mdx/from-markdown.js';
|
||||
import toMarkdown from 'mdast-util-mdx/to-markdown.js';
|
||||
|
||||
/** See https://github.com/micromark/micromark-extension-mdx-md */
|
||||
const syntax = { disable: {null: ['autolink', 'codeIndented']} };
|
||||
|
||||
/**
|
||||
* Lite version of https://github.com/mdx-js/mdx/tree/main/packages/remark-mdx
|
||||
* We don't need all the features MDX does because all components are precompiled
|
||||
* to HTML. We just want to disable a few MD features that cause issues.
|
||||
*/
|
||||
function mdxLite (this: any) {
|
||||
let data = this.data()
|
||||
|
||||
add('micromarkExtensions', syntax);
|
||||
add('fromMarkdownExtensions', fromMarkdown)
|
||||
add('toMarkdownExtensions', toMarkdown)
|
||||
|
||||
/** Adds remark plugin */
|
||||
function add(field: string, value: any) {
|
||||
if (data[field]) data[field].push(value)
|
||||
else data[field] = [value]
|
||||
}
|
||||
}
|
||||
|
||||
export default mdxLite;
|
18
packages/astro/src/compiler/markdown/remark-scoped-styles.ts
Normal file
18
packages/astro/src/compiler/markdown/remark-scoped-styles.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { visit } from 'unist-util-visit';
|
||||
const noVisit = new Set(['root', 'html', 'text']);
|
||||
|
||||
/** */
|
||||
export default function scopedStyles(className: string) {
|
||||
const visitor = (node: any) => {
|
||||
if (noVisit.has(node.type)) return;
|
||||
|
||||
const {data} = node
|
||||
const currentClassName = data?.hProperties?.class ?? '';
|
||||
node.data = node.data || {};
|
||||
node.data.hProperties = node.data.hProperties || {};
|
||||
node.data.hProperties.className = `${className} ${currentClassName}`.trim();
|
||||
|
||||
return node;
|
||||
}
|
||||
return () => (tree: any) => visit(tree, visitor);
|
||||
}
|
|
@ -156,6 +156,36 @@ async function transformStyle(code: string, { logging, type, filename, scopedCla
|
|||
return { css, type: styleType };
|
||||
}
|
||||
|
||||
/** For a given node, inject or append a `scopedClass` to its `class` attribute */
|
||||
function injectScopedClassAttribute(node: TemplateNode, scopedClass: string, attribute = 'class') {
|
||||
if (!node.attributes) node.attributes = [];
|
||||
const classIndex = node.attributes.findIndex(({ name }: any) => name === attribute);
|
||||
if (classIndex === -1) {
|
||||
// 3a. element has no class="" attribute; add one and append scopedClass
|
||||
node.attributes.push({ start: -1, end: -1, type: 'Attribute', name: attribute, value: [{ type: 'Text', raw: scopedClass, data: scopedClass }] });
|
||||
} else {
|
||||
// 3b. element has class=""; append scopedClass
|
||||
const attr = node.attributes[classIndex];
|
||||
for (let k = 0; k < attr.value.length; k++) {
|
||||
if (attr.value[k].type === 'Text') {
|
||||
// don‘t add same scopedClass twice
|
||||
if (!hasClass(attr.value[k].data, scopedClass)) {
|
||||
// string literal
|
||||
attr.value[k].raw += ' ' + scopedClass;
|
||||
attr.value[k].data += ' ' + scopedClass;
|
||||
}
|
||||
} else if (attr.value[k].type === 'MustacheTag' && attr.value[k]) {
|
||||
// don‘t add same scopedClass twice (this check is a little more basic, but should suffice)
|
||||
if (!attr.value[k].expression.codeChunks[0].includes(`' ${scopedClass}'`)) {
|
||||
// MustacheTag
|
||||
// FIXME: this won't work when JSX element can appear in attributes (rare but possible).
|
||||
attr.value[k].expression.codeChunks[0] = `(${attr.value[k].expression.codeChunks[0]}) + ' ${scopedClass}'`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Transform <style> tags */
|
||||
export default function transformStyles({ compileOptions, filename, fileID }: TransformOptions): Transformer {
|
||||
const styleNodes: TemplateNode[] = []; // <style> tags to be updated
|
||||
|
@ -180,6 +210,12 @@ export default function transformStyles({ compileOptions, filename, fileID }: Tr
|
|||
return {
|
||||
visitors: {
|
||||
html: {
|
||||
InlineComponent: {
|
||||
enter(node) {
|
||||
if (node.name !== 'Markdown') return;
|
||||
injectScopedClassAttribute(node, scopedClass, '$scope');
|
||||
}
|
||||
},
|
||||
Element: {
|
||||
enter(node) {
|
||||
// 1. if <style> tag, transform it and continue to next node
|
||||
|
@ -204,32 +240,7 @@ export default function transformStyles({ compileOptions, filename, fileID }: Tr
|
|||
if (NEVER_SCOPED_TAGS.has(node.name)) return; // only continue if this is NOT a <script> tag, etc.
|
||||
// Note: currently we _do_ scope web components/custom elements. This seems correct?
|
||||
|
||||
if (!node.attributes) node.attributes = [];
|
||||
const classIndex = node.attributes.findIndex(({ name }: any) => name === 'class');
|
||||
if (classIndex === -1) {
|
||||
// 3a. element has no class="" attribute; add one and append scopedClass
|
||||
node.attributes.push({ start: -1, end: -1, type: 'Attribute', name: 'class', value: [{ type: 'Text', raw: scopedClass, data: scopedClass }] });
|
||||
} else {
|
||||
// 3b. element has class=""; append scopedClass
|
||||
const attr = node.attributes[classIndex];
|
||||
for (let k = 0; k < attr.value.length; k++) {
|
||||
if (attr.value[k].type === 'Text') {
|
||||
// don‘t add same scopedClass twice
|
||||
if (!hasClass(attr.value[k].data, scopedClass)) {
|
||||
// string literal
|
||||
attr.value[k].raw += ' ' + scopedClass;
|
||||
attr.value[k].data += ' ' + scopedClass;
|
||||
}
|
||||
} else if (attr.value[k].type === 'MustacheTag' && attr.value[k]) {
|
||||
// don‘t add same scopedClass twice (this check is a little more basic, but should suffice)
|
||||
if (!attr.value[k].expression.codeChunks[0].includes(`' ${scopedClass}'`)) {
|
||||
// MustacheTag
|
||||
// FIXME: this won't work when JSX element can appear in attributes (rare but possible).
|
||||
attr.value[k].expression.codeChunks[0] = `(${attr.value[k].expression.codeChunks[0]}) + ' ${scopedClass}'`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
injectScopedClassAttribute(node, scopedClass);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
70
packages/astro/src/compiler/utils.ts
Normal file
70
packages/astro/src/compiler/utils.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import mdxLite from './markdown/remark-mdx-lite.js';
|
||||
import createCollectHeaders from './markdown/rehype-collect-headers.js';
|
||||
import scopedStyles from './markdown/remark-scoped-styles.js';
|
||||
import raw from 'rehype-raw';
|
||||
import unified from 'unified';
|
||||
import markdown from 'remark-parse';
|
||||
import markdownToHtml from 'remark-rehype';
|
||||
import smartypants from '@silvenon/remark-smartypants';
|
||||
import stringify from 'rehype-stringify';
|
||||
|
||||
export interface MarkdownRenderingOptions {
|
||||
$?: {
|
||||
scopedClassName: string | null;
|
||||
};
|
||||
footnotes?: boolean;
|
||||
gfm?: boolean;
|
||||
plugins?: any[];
|
||||
}
|
||||
|
||||
/** Internal utility for rendering a full markdown file and extracting Frontmatter data */
|
||||
export async function renderMarkdownWithFrontmatter(contents: string, opts?: MarkdownRenderingOptions|null) {
|
||||
// Dynamic import to ensure that "gray-matter" isn't built by Snowpack
|
||||
const { default: matter } = await import('gray-matter');
|
||||
const {
|
||||
data: frontmatter,
|
||||
content,
|
||||
} = matter(contents);
|
||||
const value = await renderMarkdown(content, opts);
|
||||
return { ...value, frontmatter };
|
||||
}
|
||||
|
||||
/** Shared utility for rendering markdown */
|
||||
export async function renderMarkdown(content: string, opts?: MarkdownRenderingOptions | null) {
|
||||
const { $: { scopedClassName = null } = {}, footnotes: useFootnotes = true, gfm: useGfm = true, plugins = [] } = opts ?? {};
|
||||
const { headers, rehypeCollectHeaders } = createCollectHeaders();
|
||||
|
||||
let parser = unified().use(markdown).use(mdxLite).use(smartypants);
|
||||
|
||||
if (scopedClassName) {
|
||||
parser = parser.use(scopedStyles(scopedClassName));
|
||||
}
|
||||
|
||||
if (useGfm) {
|
||||
const {default:gfm} = await import('remark-gfm');
|
||||
parser = parser.use(gfm);
|
||||
}
|
||||
|
||||
if (useFootnotes) {
|
||||
const {default:footnotes} = await import('remark-footnotes');
|
||||
parser = parser.use(footnotes);
|
||||
}
|
||||
|
||||
let result: string;
|
||||
try {
|
||||
const vfile = await parser
|
||||
.use(markdownToHtml, { allowDangerousHtml: true, passThrough: ['raw'] })
|
||||
.use(raw)
|
||||
.use(rehypeCollectHeaders)
|
||||
.use(stringify)
|
||||
.process(content);
|
||||
result = vfile.contents.toString();
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return {
|
||||
astro: { headers, source: content },
|
||||
content: result.toString(),
|
||||
};
|
||||
}
|
26
packages/astro/src/frontend/markdown.ts
Normal file
26
packages/astro/src/frontend/markdown.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { renderMarkdown } from '../compiler/utils.js';
|
||||
|
||||
/**
|
||||
* Functional component which uses Astro's built-in Markdown rendering
|
||||
* to render out its children.
|
||||
*
|
||||
* Note: the children have already been properly escaped/rendered
|
||||
* by the parser and Astro, so at this point we're just rendering
|
||||
* out plain markdown, no need for JSX support
|
||||
*/
|
||||
export default async function Markdown(props: { $scope: string|null }, ...children: string[]): Promise<string> {
|
||||
const { $scope = null } = props ?? {};
|
||||
const text = dedent(children.join('').trimEnd());
|
||||
let { content } = await renderMarkdown(text, { $: { scopedClassName: $scope } });
|
||||
if (content.split('<p>').length === 2) {
|
||||
content = content.replace(/^\<p\>/i, '').replace(/\<\/p\>$/i, '');
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/** Remove leading indentation based on first line */
|
||||
function dedent(str: string) {
|
||||
let arr = str.match(/^[ \t]*(?=\S)/gm);
|
||||
let first = !!arr && arr.find(x => x.length > 0)?.length;
|
||||
return (!arr || !first) ? str : str.replace(new RegExp(`^[ \\t]{0,${first}}`, 'gm'), '');
|
||||
}
|
|
@ -36,17 +36,15 @@ export function createRenderer(renderer: SupportedComponentRenderer) {
|
|||
}
|
||||
value = `<div data-astro-id="${innerContext['data-astro-id']}" style="display:contents">${value}</div>`;
|
||||
|
||||
const script = `
|
||||
${typeof wrapperStart === 'function' ? wrapperStart(innerContext) : wrapperStart}
|
||||
${_imports(renderContext)}
|
||||
${renderer.render({
|
||||
const script = `${typeof wrapperStart === 'function' ? wrapperStart(innerContext) : wrapperStart}
|
||||
${_imports(renderContext)}
|
||||
${renderer.render({
|
||||
...innerContext,
|
||||
props: serializeProps(props),
|
||||
children: `[${childrenToH(renderer, children) ?? ''}]`,
|
||||
childrenAsString: `\`${children}\``,
|
||||
})}
|
||||
${typeof wrapperEnd === 'function' ? wrapperEnd(innerContext) : wrapperEnd}
|
||||
`;
|
||||
${typeof wrapperEnd === 'function' ? wrapperEnd(innerContext) : wrapperEnd}`;
|
||||
|
||||
return [value, `<script type="module">${script.trim()}</script>`].join('\n');
|
||||
};
|
||||
|
|
|
@ -3,12 +3,10 @@ import parse from 'rehype-parse';
|
|||
import toH from 'hast-to-hyperscript';
|
||||
import { ComponentRenderer } from '../../@types/renderer';
|
||||
import moize from 'moize';
|
||||
// This prevents tree-shaking of render.
|
||||
Function.prototype(toH);
|
||||
|
||||
/** @internal */
|
||||
function childrenToTree(children: string[]) {
|
||||
return children.map((child) => (unified().use(parse, { fragment: true }).parse(child) as any).children.pop());
|
||||
function childrenToTree(children: string[]): any[] {
|
||||
return [].concat(...children.map((child) => (unified().use(parse, { fragment: true }).parse(child) as any).children));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,17 +30,20 @@ export const childrenToVnodes = moize.deep(function childrenToVnodes(h: any, chi
|
|||
*/
|
||||
export const childrenToH = moize.deep(function childrenToH(renderer: ComponentRenderer<any>, children: string[]): any {
|
||||
if (!renderer.jsxPragma) return;
|
||||
|
||||
const tree = childrenToTree(children);
|
||||
const innerH = (name: any, attrs: Record<string, any> | null = null, _children: string[] | null = null) => {
|
||||
const vnode = renderer.jsxPragma?.(name, attrs, _children);
|
||||
const childStr = _children ? `, [${_children.map((child) => serializeChild(child)).join(',')}]` : '';
|
||||
/* fix(react): avoid hard-coding keys into the serialized tree */
|
||||
if (attrs && attrs.key) attrs.key = undefined;
|
||||
if (attrs && attrs.key) attrs.key = Math.random();
|
||||
const __SERIALIZED = `${renderer.jsxPragmaName}("${name}", ${attrs ? JSON.stringify(attrs) : 'null'}${childStr})` as string;
|
||||
return { ...vnode, __SERIALIZED };
|
||||
};
|
||||
|
||||
const simpleTypes = new Set(['number', 'boolean']);
|
||||
const serializeChild = (child: unknown) => {
|
||||
if (['string', 'number', 'boolean'].includes(typeof child)) return JSON.stringify(child);
|
||||
if (typeof child === 'string') return JSON.stringify(child).replace(/<\/script>/gmi, '</script" + ">');
|
||||
if (simpleTypes.has(typeof child)) return JSON.stringify(child);
|
||||
if (child === null) return `null`;
|
||||
if ((child as any).__SERIALIZED) return (child as any).__SERIALIZED;
|
||||
return innerH(child).__SERIALIZED;
|
||||
|
|
|
@ -314,7 +314,11 @@ async function createSnowpack(astroConfig: AstroConfig, options: CreateSnowpackO
|
|||
},
|
||||
packageOptions: {
|
||||
knownEntrypoints: ['preact-render-to-string'],
|
||||
external: ['@vue/server-renderer', 'node-fetch', 'prismjs/components/index.js'],
|
||||
external: [
|
||||
'@vue/server-renderer',
|
||||
'node-fetch',
|
||||
'prismjs/components/index.js'
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ export function searchForPage(url: URL, astroRoot: URL): SearchResult {
|
|||
|
||||
// Try to find index.astro/md paths
|
||||
if (reqPath.endsWith('/')) {
|
||||
const candidates = [`${base}index.astro`, `${base}index.md`];
|
||||
const candidates = [`${base}index.astro`, `${base}index.md`,];
|
||||
const location = findAnyPage(candidates, astroRoot);
|
||||
if (location) {
|
||||
return {
|
||||
|
|
|
@ -3,12 +3,13 @@ import * as assert from 'uvu/assert';
|
|||
import { doc } from './test-utils.js';
|
||||
import { setup, setupBuild } from './helpers.js';
|
||||
|
||||
const Markdown = suite('Astro Markdown');
|
||||
const Markdown = suite('Astro Markdown tests');
|
||||
|
||||
setup(Markdown, './fixtures/astro-markdown');
|
||||
setupBuild(Markdown, './fixtures/astro-markdown');
|
||||
|
||||
Markdown('Can load markdown pages with hmx', async ({ runtime }) => {
|
||||
|
||||
Markdown('Can load markdown pages with Astro', async ({ runtime }) => {
|
||||
const result = await runtime.load('/post');
|
||||
if (result.error) throw new Error(result.error);
|
||||
|
||||
|
|
3
packages/astro/test/fixtures/astro-markdown/snowpack.config.json
vendored
Normal file
3
packages/astro/test/fixtures/astro-markdown/snowpack.config.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"workspaceRoot": "../../../../../"
|
||||
}
|
20
packages/astro/test/fixtures/astro-markdown/src/pages/complex.astro
vendored
Normal file
20
packages/astro/test/fixtures/astro-markdown/src/pages/complex.astro
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
import Markdown from 'astro/components/Markdown.astro';
|
||||
import Layout from '../layouts/content.astro';
|
||||
import Hello from '../components/Hello.jsx';
|
||||
import Counter from '../components/Counter.jsx';
|
||||
|
||||
export const title = 'My Blog Post';
|
||||
export const description = 'This is a post about some stuff.';
|
||||
---
|
||||
|
||||
<Markdown>
|
||||
<Layout>
|
||||
|
||||
## Interesting Topic
|
||||
|
||||
<Hello name={`world`} />
|
||||
<Counter:load />
|
||||
|
||||
</Layout>
|
||||
</Markdown>
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
layout: ../layouts/content.astro
|
||||
title: My Blog Post
|
||||
description: This is a post about some stuff.
|
||||
import:
|
||||
Hello: '../components/Hello.jsx'
|
||||
Counter: '../components/Counter.jsx'
|
||||
---
|
||||
|
||||
## Interesting Topic
|
||||
|
||||
<Hello name={`world`} />
|
||||
<Counter:load />
|
16
packages/astro/test/fixtures/astro-markdown/src/pages/post.astro
vendored
Normal file
16
packages/astro/test/fixtures/astro-markdown/src/pages/post.astro
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
import Markdown from 'astro/components/Markdown.astro';
|
||||
import Layout from '../layouts/content.astro';
|
||||
import Example from '../components/Example.jsx';
|
||||
|
||||
export const title = 'My Blog Post';
|
||||
export const description = 'This is a post about some stuff.';
|
||||
---
|
||||
|
||||
<Markdown>
|
||||
## Interesting Topic
|
||||
|
||||
<div id="first">Some content</div>
|
||||
|
||||
<Example></Example>
|
||||
</Markdown>
|
8
packages/astro/test/fixtures/plain-markdown/astro.config.mjs
vendored
Normal file
8
packages/astro/test/fixtures/plain-markdown/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
extensions: {
|
||||
'.jsx': 'preact',
|
||||
},
|
||||
buildOptions: {
|
||||
sitemap: false,
|
||||
},
|
||||
};
|
3
packages/astro/test/fixtures/plain-markdown/snowpack.config.json
vendored
Normal file
3
packages/astro/test/fixtures/plain-markdown/snowpack.config.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"workspaceRoot": "../../../../../"
|
||||
}
|
10
packages/astro/test/fixtures/plain-markdown/src/layouts/content.astro
vendored
Normal file
10
packages/astro/test/fixtures/plain-markdown/src/layouts/content.astro
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<!-- Head Stuff -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -2,12 +2,10 @@
|
|||
layout: ../layouts/content.astro
|
||||
title: My Blog Post
|
||||
description: This is a post about some stuff.
|
||||
import:
|
||||
Example: '../components/Example.jsx'
|
||||
---
|
||||
|
||||
## Interesting Topic
|
||||
|
||||
<div id="first">Some content</div>
|
||||
Hello world!
|
||||
|
||||
<Example />
|
||||
<div id="first">Some content</div>
|
117
packages/astro/test/fixtures/plain-markdown/src/pages/realworld.md
vendored
Normal file
117
packages/astro/test/fixtures/plain-markdown/src/pages/realworld.md
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
---
|
||||
# Taken from https://github.com/endymion1818/deliciousreverie/blob/master/src/pages/post/advanced-custom-fields-bootstrap-tabs.md
|
||||
categories:
|
||||
- development
|
||||
date: "2015-06-02T15:21:21+01:00"
|
||||
description: I'm not a huge fan of Advanced Custom Fields, but there was a requirement
|
||||
to use it in a recent project that had Bootstrap as a basis for the UI. The challenge
|
||||
for me was to get Bootstrap `nav-tabs` to play nice with an ACF repeater field.
|
||||
draft: false
|
||||
tags:
|
||||
- wordpress
|
||||
- advanced custom fields
|
||||
title: Advanced Custom Fields and Bootstrap Tabs
|
||||
---
|
||||
|
||||
**I'm not a huge fan of Advanced Custom Fields, but there was a requirement to use it in a recent project that had Bootstrap as a basis for the UI. The challenge for me was to get Bootstrap [nav-tabs](http://getbootstrap.com/components/#nav-tabs "Bootstrap nav-tabs component") to play nice with an [ACF repeater field](http://www.advancedcustomfields.com/resources/querying-the-database-for-repeater-sub-field-values/ "Repeater sub-field on Advanced Custom Fields website").**
|
||||
|
||||
I started with the basic HTML markup for Bootstrap's Nav Tabs:
|
||||
|
||||
```html
|
||||
<ul class="nav nav-tabs">
|
||||
<li role="presentation" class="active"><a href="tabone">TabOne</a></li>
|
||||
<li role="presentation"><a href="tabtwo">TabTwo</a></li>
|
||||
<li role="presentation"><a href="tabthree">TabThree</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tabone">
|
||||
Some content in tab one
|
||||
</div>
|
||||
<div class="tab-pane active" id="tabtwo">
|
||||
Some content in tab two
|
||||
</div>
|
||||
<div class="tab-pane active" id="tabthree">
|
||||
Some content in tab three
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
In the Field Groups settings, I created a Repeater (this is a paid-for add on to the standard Advanced Custom Fields) called "tab Panes", with 2 sub-fields, "Tab Title" and "Tab Contents".
|
||||
|
||||
```php
|
||||
<?php
|
||||
<!-- Check for parent repeater row -->
|
||||
<?php if( have_rows('tab_panes') ): ?>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<?php // Step 1: Loop through rows, first displaying tab titles in a list
|
||||
while( have_rows('tab_panes') ): the_row();
|
||||
?>
|
||||
<li role="presentation" class="active">
|
||||
<a
|
||||
href="#tabone"
|
||||
role="tab"
|
||||
data-toggle="tab"
|
||||
>
|
||||
<?php the_sub_field('tab_title'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endwhile; // end of (have_rows('tab_panes') ):?>
|
||||
</ul>
|
||||
<?php endif; // end of (have_rows('tab_panes') ): ?>
|
||||
```
|
||||
|
||||
The PHP above displays the tabs. The code below, very similarly, displays the tab panes:
|
||||
|
||||
```php
|
||||
<?php if( have_rows('tab_panes') ): ?>
|
||||
<div class="tab-content">
|
||||
<?php// number rows ?>
|
||||
<?php // Step 2: Loop through rows, now displaying tab contents
|
||||
while( have_rows('tab_panes') ): the_row();
|
||||
// Display each item as a list ?>
|
||||
<div class="tab-pane active" id="tabone">
|
||||
<?php the_sub_field('tab_contents'); ?>
|
||||
</div>
|
||||
<?php endwhile; // (have_rows('tab_panes') ):?>
|
||||
</div>
|
||||
<?php endif; // (have_rows('tab_panes') ): ?>
|
||||
```
|
||||
|
||||
By looping through the same repeater, we can get all the tabs out of the database, no problem. But we still have two problems: 1) linking the tab to the pane 2) Assigning the class of "active" so the Javascript is able to add and remove the CSS to reveal / hide the appropriate pane.
|
||||
|
||||
### 1) Linking to the Pane
|
||||
|
||||
There are a number of ways to do this. I could ask the user to input a number to uniquely identify the tab pane. But that would add extra work to the users flow, and they might easily find themselves out of their depth. I want to make this as easy as possible for the user.
|
||||
|
||||
On the other hand, Wordpress has a very useful function called Sanitize HTML, which we input the value of the title, take out spaces and capitals, and use this as the link:
|
||||
|
||||
```php
|
||||
<a href="#<?php echo sanitize_html_class( the_sub_field( 'tab_title' ) ); ?>"
|
||||
```
|
||||
|
||||
### 2) Assigning the 'Active' Class
|
||||
|
||||
So now we need to get a class of 'active' _only on_ the first tab. The Bootstrap Javascript will do the rest for us. How do we do that?
|
||||
|
||||
I added this code just inside the `while` loop, inside the `ul` tag:
|
||||
|
||||
```php
|
||||
<?php $row = 1; // number rows ?>
|
||||
```
|
||||
|
||||
This php is a counter. So we can identify the first instance and assign an `if` statement to it.
|
||||
|
||||
```php
|
||||
<a class="<?php if($row == 1) {echo 'active';}?>">
|
||||
```
|
||||
|
||||
The final thing to do, is to keep the counter running, but adding this jsut before the `endwhile`.
|
||||
|
||||
```php
|
||||
<?php $row++; endwhile; // (have_rows('tab_panes') ):?>
|
||||
```
|
||||
|
||||
Once you've added these to the tab panes in a similar way, you'll be up and running with Boostrap Tabs.
|
||||
|
||||
Below is a Github Gist, with the complete code for reference. [Link to this (if you can't see the iFrame)](https://gist.github.com/endymion1818/478d86025f41c8060888 "Github GIST for Advanced Custom Fields bootstrap tabs").
|
||||
|
||||
<script src="https://gist.github.com/endymion1818/478d86025f41c8060888.js"></script>
|
38
packages/astro/test/plain-markdown.test.js
Normal file
38
packages/astro/test/plain-markdown.test.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { suite } from 'uvu';
|
||||
import * as assert from 'uvu/assert';
|
||||
import { doc } from './test-utils.js';
|
||||
import { setup, setupBuild } from './helpers.js';
|
||||
|
||||
const Markdown = suite('Plain Markdown tests');
|
||||
|
||||
setup(Markdown, './fixtures/plain-markdown');
|
||||
setupBuild(Markdown, './fixtures/plain-markdown');
|
||||
|
||||
Markdown('Can load a simple markdown page with Astro', async ({ runtime }) => {
|
||||
const result = await runtime.load('/post');
|
||||
|
||||
assert.equal(result.statusCode, 200);
|
||||
|
||||
const $ = doc(result.contents);
|
||||
|
||||
assert.equal($('p').first().text(), 'Hello world!');
|
||||
assert.equal($('#first').text(), 'Some content');
|
||||
assert.equal($('#interesting-topic').text(), 'Interesting Topic');
|
||||
});
|
||||
|
||||
Markdown('Can load a realworld markdown page with Astro', async ({ runtime }) => {
|
||||
const result = await runtime.load('/realworld');
|
||||
if (result.error) throw new Error(result.error);
|
||||
|
||||
assert.equal(result.statusCode, 200);
|
||||
const $ = doc(result.contents);
|
||||
|
||||
assert.equal($('pre').length, 7);
|
||||
});
|
||||
|
||||
Markdown('Builds markdown pages for prod', async (context) => {
|
||||
await context.build();
|
||||
});
|
||||
|
||||
|
||||
Markdown.run();
|
|
@ -15,7 +15,7 @@ Inside of your Astro project, you'll see the following folders and files:
|
|||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md.astro` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
"build": "astro build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^0.0.12"
|
||||
"astro": "^0.0.13"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ Inside of your Astro project, you'll see the following folders and files:
|
|||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md.astro` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
"build": "astro build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^0.0.12"
|
||||
"astro": "^0.0.13"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</pre>
|
||||
|
||||
<p>
|
||||
Astro looks for <code>.astro</code> or <code>.md.astro</code> files in the <code>src/pages/</code> directory.
|
||||
Astro looks for <code>.astro</code> or <code>.md</code> files in the <code>src/pages/</code> directory.
|
||||
Each page is exposed as a route based on its file name.
|
||||
</p>
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"comments": {
|
||||
"blockComment": ["<!--", "-->"]
|
||||
},
|
||||
"brackets": [
|
||||
["---", "---"],
|
||||
["<!--", "-->"],
|
||||
["<", ">"],
|
||||
["{", "}"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
{ "open": "{", "close": "}" },
|
||||
{ "open": "[", "close": "]" },
|
||||
{ "open": "(", "close": ")" },
|
||||
{ "open": "'", "close": "'" },
|
||||
{ "open": "\"", "close": "\"" },
|
||||
{
|
||||
"open": "<",
|
||||
"close": ">",
|
||||
"notIn": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{ "open": "<!--", "close": "-->", "notIn": ["comment", "string"] },
|
||||
],
|
||||
"autoCloseBefore": ";:.,=}])>` \n\t",
|
||||
"surroundingPairs": [
|
||||
{ "open": "'", "close": "'" },
|
||||
{ "open": "\"", "close": "\"" },
|
||||
{ "open": "{", "close": "}" },
|
||||
{ "open": "[", "close": "]" },
|
||||
{ "open": "(", "close": ")" },
|
||||
{ "open": "<", "close": ">" },
|
||||
{ "open": "`", "close": "`" },
|
||||
{ "open": "_", "close": "_" },
|
||||
{ "open": "*", "close": "*" }
|
||||
],
|
||||
"folding": {
|
||||
"markers": {
|
||||
"start": "^\\s*<!--\\s*#region\\b.*-->",
|
||||
"end": "^\\s*<!--\\s*#endregion\\b.*-->"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,12 +24,12 @@
|
|||
"onLanguage:astro"
|
||||
],
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "~7.0.0",
|
||||
"vscode-html-languageservice": "^3.0.3",
|
||||
"vscode-emmet-helper": "2.1.2",
|
||||
"astro-languageserver": "0.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vscode-html-languageservice": "^3.0.3",
|
||||
"vscode-emmet-helper": "2.1.2",
|
||||
"vscode-languageclient": "~7.0.0",
|
||||
"astro-scripts": "0.0.1",
|
||||
"@types/vscode": "^1.52.0"
|
||||
},
|
||||
|
@ -72,6 +72,13 @@
|
|||
"Astro"
|
||||
],
|
||||
"configuration": "./languages/astro-language-configuration.json"
|
||||
},
|
||||
{
|
||||
"id": "astro-markdown",
|
||||
"aliases": [
|
||||
"Astro Markdown"
|
||||
],
|
||||
"configuration": "./languages/astro-markdown-language-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
|
@ -79,11 +86,9 @@
|
|||
"language": "astro",
|
||||
"scopeName": "text.html.astro",
|
||||
"path": "./syntaxes/astro.tmLanguage.json",
|
||||
"injectTo": [
|
||||
"text.html.markdown"
|
||||
],
|
||||
"embeddedLanguages": {
|
||||
"text.html.astro": "astro",
|
||||
"text.html.markdown.astro": "astro-markdown",
|
||||
"text.html": "html",
|
||||
"source.css": "css",
|
||||
"source.scss": "scss",
|
||||
|
@ -91,6 +96,63 @@
|
|||
"source.tsx": "typescriptreact",
|
||||
"meta.embedded.block.frontmatter": "typescriptreact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"language": "astro-markdown",
|
||||
"scopeName": "text.html.markdown.astro",
|
||||
"path": "./syntaxes/astro-markdown.tmLanguage.json",
|
||||
"injectTo": [
|
||||
"text.html.astro"
|
||||
],
|
||||
"embeddedLanguages": {
|
||||
"text.html.astro": "astro",
|
||||
"text.html.markdown.astro": "astro-markdown",
|
||||
"text.html.markdown": "markdown",
|
||||
"source.tsx": "typescriptreact",
|
||||
"source.js": "javascript",
|
||||
"source.css": "css",
|
||||
"meta.embedded.block.frontmatter": "yaml",
|
||||
"meta.embedded.block.css": "css",
|
||||
"meta.embedded.block.astro": "astro",
|
||||
"meta.embedded.block.ini": "ini",
|
||||
"meta.embedded.block.java": "java",
|
||||
"meta.embedded.block.lua": "lua",
|
||||
"meta.embedded.block.makefile": "makefile",
|
||||
"meta.embedded.block.perl": "perl",
|
||||
"meta.embedded.block.r": "r",
|
||||
"meta.embedded.block.ruby": "ruby",
|
||||
"meta.embedded.block.php": "php",
|
||||
"meta.embedded.block.sql": "sql",
|
||||
"meta.embedded.block.vs_net": "vs_net",
|
||||
"meta.embedded.block.xml": "xml",
|
||||
"meta.embedded.block.xsl": "xsl",
|
||||
"meta.embedded.block.yaml": "yaml",
|
||||
"meta.embedded.block.dosbatch": "dosbatch",
|
||||
"meta.embedded.block.clojure": "clojure",
|
||||
"meta.embedded.block.coffee": "coffee",
|
||||
"meta.embedded.block.c": "c",
|
||||
"meta.embedded.block.cpp": "cpp",
|
||||
"meta.embedded.block.diff": "diff",
|
||||
"meta.embedded.block.dockerfile": "dockerfile",
|
||||
"meta.embedded.block.go": "go",
|
||||
"meta.embedded.block.groovy": "groovy",
|
||||
"meta.embedded.block.pug": "jade",
|
||||
"meta.embedded.block.javascript": "javascript",
|
||||
"meta.embedded.block.json": "json",
|
||||
"meta.embedded.block.less": "less",
|
||||
"meta.embedded.block.objc": "objc",
|
||||
"meta.embedded.block.scss": "scss",
|
||||
"meta.embedded.block.perl6": "perl6",
|
||||
"meta.embedded.block.powershell": "powershell",
|
||||
"meta.embedded.block.python": "python",
|
||||
"meta.embedded.block.rust": "rust",
|
||||
"meta.embedded.block.scala": "scala",
|
||||
"meta.embedded.block.shellscript": "shellscript",
|
||||
"meta.embedded.block.typescript": "typescript",
|
||||
"meta.embedded.block.typescriptreact": "typescriptreact",
|
||||
"meta.embedded.block.csharp": "csharp",
|
||||
"meta.embedded.block.fsharp": "fsharp"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as vscode from 'vscode';
|
||||
import * as lsp from 'vscode-languageclient/node';
|
||||
import { activateTagClosing } from './html/autoClose';
|
||||
import * as lsp from 'vscode-languageclient/node.js';
|
||||
import { activateTagClosing } from './html/autoClose.js';
|
||||
|
||||
let docClient: lsp.LanguageClient;
|
||||
|
||||
|
|
2829
tools/astro-vscode/syntaxes/astro-markdown.tmLanguage.json
Normal file
2829
tools/astro-vscode/syntaxes/astro-markdown.tmLanguage.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -4,7 +4,11 @@
|
|||
"foldingStopMarker": "(?x)\n(</(?i:head|body|table|thead|tbody|tfoot|tr|div|select|fieldset|style|script|ul|ol|li|form|dl)>\n|^(?!.*?<!--).*?--\\s*>\n|^<!--\\ end\\ tminclude\\ -->$\n|<\\?(?:php)?.*\\bend(if|for(each)?|while)\\b\n|\\{\\{?/(if|foreach|capture|literal|foreach|php|section|strip)\n|^[^{]*\\}\n)",
|
||||
"keyEquivalent": "^~H",
|
||||
"name": "Astro",
|
||||
"scopeName": "text.html.astro",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#astro-markdown"
|
||||
},
|
||||
{
|
||||
"include": "#astro-expressions"
|
||||
},
|
||||
|
@ -472,6 +476,32 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"begin": "(</?)([A-Z][a-zA-Z0-9-]*)(\\:(load|idle|visible))?",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.tag.begin.html"
|
||||
},
|
||||
"2": {
|
||||
"name": "entity.name.tag.component.astro"
|
||||
},
|
||||
"3": {
|
||||
"name": "keyword.control.loading.astro"
|
||||
}
|
||||
},
|
||||
"end": "(/?>)",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.tag.end.html"
|
||||
}
|
||||
},
|
||||
"name": "meta.tag.component.astro",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#tag-stuff"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"begin": "(</?)([a-zA-Z0-9:-]+)",
|
||||
"beginCaptures": {
|
||||
|
@ -672,8 +702,51 @@
|
|||
{
|
||||
"include": "#string-single-quoted"
|
||||
},
|
||||
{
|
||||
"include": "#astro-load-directive"
|
||||
},
|
||||
{
|
||||
"include": "#astro-expressions"
|
||||
},
|
||||
{
|
||||
"include": "#astro-markdown"
|
||||
}
|
||||
]
|
||||
},
|
||||
"astro-markdown": {
|
||||
"begin": "(?:^\\s+)?(<)(Markdown)\\b(?=[^>]*)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.tag.begin.html"
|
||||
},
|
||||
"2": {
|
||||
"name": "entity.name.tag.markdown.astro"
|
||||
}
|
||||
},
|
||||
"end": "(</)(Markdown)(>)",
|
||||
"endCaptures": {
|
||||
"2": {
|
||||
"name": "punctuation.definition.tag.html"
|
||||
}
|
||||
},
|
||||
"name": "text.html.markdown.astro",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#tag-stuff"
|
||||
},
|
||||
{
|
||||
"begin": "(>)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.tag.end.html"
|
||||
}
|
||||
},
|
||||
"end": "(?=</Markdown)",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "text.html.markdown.astro"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -701,6 +774,5 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scopeName": "text.html.astro"
|
||||
}
|
||||
}
|
||||
|
|
404
yarn.lock
404
yarn.lock
|
@ -1395,6 +1395,15 @@
|
|||
dependencies:
|
||||
"@octokit/openapi-types" "^6.2.1"
|
||||
|
||||
"@silvenon/remark-smartypants@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@silvenon/remark-smartypants/-/remark-smartypants-1.0.0.tgz#0692263f4b22af92caea9382b77bc287db69c5d1"
|
||||
integrity sha512-+Icx9z8zKBdO9mMcsUkfRbzGkHDXmv+Q4TyoPTiuhTrWK2UtLUglfTB5iRacuYHzNYKC4hJIJmTlC5c7fNxOiw==
|
||||
dependencies:
|
||||
retext "^7.0.1"
|
||||
retext-smartypants "^4.0.0"
|
||||
unist-util-visit "^2.0.1"
|
||||
|
||||
"@sindresorhus/is@^0.14.0":
|
||||
version "0.14.0"
|
||||
resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz"
|
||||
|
@ -2133,6 +2142,11 @@ array-ify@^1.0.0:
|
|||
resolved "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz"
|
||||
integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=
|
||||
|
||||
array-iterate@^1.0.0:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-1.1.4.tgz#add1522e9dd9749bb41152d08b845bd08d6af8b7"
|
||||
integrity sha512-sNRaPGh9nnmdC8Zf+pT3UqP8rnWj5Hf9wiFGsX3wUQ2yVSIhO2ShFwCoceIPpB41QF6i2OEmrHmCo36xronCVA==
|
||||
|
||||
array-union@^1.0.1, array-union@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz"
|
||||
|
@ -4280,6 +4294,11 @@ escape-string-regexp@^2.0.0:
|
|||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz"
|
||||
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
|
||||
|
||||
escape-string-regexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
eslint-config-prettier@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a"
|
||||
|
@ -4398,11 +4417,6 @@ estraverse@^5.1.0, estraverse@^5.2.0:
|
|||
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz"
|
||||
integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
|
||||
|
||||
estree-util-is-identifier-name@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-1.1.0.tgz"
|
||||
integrity sha512-OVJZ3fGGt9By77Ix9NhaRbzfbDV/2rx9EP7YIDJTmsZSEc5kYn2vWcNccYyahJL2uAQZK2a5Or2i0wtIKTPoRQ==
|
||||
|
||||
estree-walker@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz"
|
||||
|
@ -5404,7 +5418,7 @@ hash-sum@^2.0.0:
|
|||
resolved "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz"
|
||||
integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==
|
||||
|
||||
hast-to-hyperscript@~9.0.0:
|
||||
hast-to-hyperscript@^9.0.0, hast-to-hyperscript@~9.0.0:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz"
|
||||
integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==
|
||||
|
@ -5429,11 +5443,65 @@ hast-util-from-parse5@^6.0.0:
|
|||
vfile-location "^3.2.0"
|
||||
web-namespaces "^1.0.0"
|
||||
|
||||
hast-util-is-element@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425"
|
||||
integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==
|
||||
|
||||
hast-util-parse-selector@^2.0.0:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz"
|
||||
integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
|
||||
|
||||
hast-util-raw@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.1.0.tgz#e16a3c2642f65cc7c480c165400a40d604ab75d0"
|
||||
integrity sha512-5FoZLDHBpka20OlZZ4I/+RBw5piVQ8iI1doEvffQhx5CbCyTtP8UCq8Tw6NmTAMtXgsQxmhW7Ly8OdFre5/YMQ==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
hast-util-from-parse5 "^6.0.0"
|
||||
hast-util-to-parse5 "^6.0.0"
|
||||
html-void-elements "^1.0.0"
|
||||
parse5 "^6.0.0"
|
||||
unist-util-position "^3.0.0"
|
||||
unist-util-visit "^2.0.0"
|
||||
vfile "^4.0.0"
|
||||
web-namespaces "^1.0.0"
|
||||
xtend "^4.0.0"
|
||||
zwitch "^1.0.0"
|
||||
|
||||
hast-util-to-html@^7.1.1:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-7.1.3.tgz#9f339ca9bea71246e565fc79ff7dbfe98bb50f5e"
|
||||
integrity sha512-yk2+1p3EJTEE9ZEUkgHsUSVhIpCsL/bvT8E5GzmWc+N1Po5gBw+0F8bo7dpxXR0nu0bQVxVZGX2lBGF21CmeDw==
|
||||
dependencies:
|
||||
ccount "^1.0.0"
|
||||
comma-separated-tokens "^1.0.0"
|
||||
hast-util-is-element "^1.0.0"
|
||||
hast-util-whitespace "^1.0.0"
|
||||
html-void-elements "^1.0.0"
|
||||
property-information "^5.0.0"
|
||||
space-separated-tokens "^1.0.0"
|
||||
stringify-entities "^3.0.1"
|
||||
unist-util-is "^4.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
hast-util-to-parse5@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479"
|
||||
integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==
|
||||
dependencies:
|
||||
hast-to-hyperscript "^9.0.0"
|
||||
property-information "^5.0.0"
|
||||
web-namespaces "^1.0.0"
|
||||
xtend "^4.0.0"
|
||||
zwitch "^1.0.0"
|
||||
|
||||
hast-util-whitespace@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41"
|
||||
integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==
|
||||
|
||||
hastscript@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz"
|
||||
|
@ -5485,6 +5553,11 @@ html-tags@^3.1.0:
|
|||
resolved "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz"
|
||||
integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==
|
||||
|
||||
html-void-elements@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483"
|
||||
integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==
|
||||
|
||||
htmlparser2@^3.10.0:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz"
|
||||
|
@ -7058,6 +7131,13 @@ markdown-table@^1.1.0:
|
|||
resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz"
|
||||
integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==
|
||||
|
||||
markdown-table@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b"
|
||||
integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==
|
||||
dependencies:
|
||||
repeat-string "^1.0.0"
|
||||
|
||||
mathml-tag-names@^2.0.1, mathml-tag-names@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz"
|
||||
|
@ -7080,6 +7160,30 @@ mdast-util-compact@^1.0.0:
|
|||
dependencies:
|
||||
unist-util-visit "^1.1.0"
|
||||
|
||||
mdast-util-definitions@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2"
|
||||
integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==
|
||||
dependencies:
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
mdast-util-find-and-replace@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz#b7db1e873f96f66588c321f1363069abf607d1b5"
|
||||
integrity sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==
|
||||
dependencies:
|
||||
escape-string-regexp "^4.0.0"
|
||||
unist-util-is "^4.0.0"
|
||||
unist-util-visit-parents "^3.0.0"
|
||||
|
||||
mdast-util-footnote@^0.1.0:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-footnote/-/mdast-util-footnote-0.1.7.tgz#4b226caeab4613a3362c144c94af0fdd6f7e0ef0"
|
||||
integrity sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==
|
||||
dependencies:
|
||||
mdast-util-to-markdown "^0.6.0"
|
||||
micromark "~2.11.0"
|
||||
|
||||
mdast-util-from-markdown@^0.8.0, mdast-util-from-markdown@^0.8.5:
|
||||
version "0.8.5"
|
||||
resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz"
|
||||
|
@ -7091,7 +7195,97 @@ mdast-util-from-markdown@^0.8.0, mdast-util-from-markdown@^0.8.5:
|
|||
parse-entities "^2.0.0"
|
||||
unist-util-stringify-position "^2.0.0"
|
||||
|
||||
mdast-util-to-markdown@^0.6.0:
|
||||
mdast-util-gfm-autolink-literal@^0.1.0:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz#9c4ff399c5ddd2ece40bd3b13e5447d84e385fb7"
|
||||
integrity sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==
|
||||
dependencies:
|
||||
ccount "^1.0.0"
|
||||
mdast-util-find-and-replace "^1.1.0"
|
||||
micromark "^2.11.3"
|
||||
|
||||
mdast-util-gfm-strikethrough@^0.2.0:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz#45eea337b7fff0755a291844fbea79996c322890"
|
||||
integrity sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==
|
||||
dependencies:
|
||||
mdast-util-to-markdown "^0.6.0"
|
||||
|
||||
mdast-util-gfm-table@^0.1.0:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz#af05aeadc8e5ee004eeddfb324b2ad8c029b6ecf"
|
||||
integrity sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==
|
||||
dependencies:
|
||||
markdown-table "^2.0.0"
|
||||
mdast-util-to-markdown "~0.6.0"
|
||||
|
||||
mdast-util-gfm-task-list-item@^0.1.0:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz#70c885e6b9f543ddd7e6b41f9703ee55b084af10"
|
||||
integrity sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==
|
||||
dependencies:
|
||||
mdast-util-to-markdown "~0.6.0"
|
||||
|
||||
mdast-util-gfm@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz#8ecddafe57d266540f6881f5c57ff19725bd351c"
|
||||
integrity sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==
|
||||
dependencies:
|
||||
mdast-util-gfm-autolink-literal "^0.1.0"
|
||||
mdast-util-gfm-strikethrough "^0.2.0"
|
||||
mdast-util-gfm-table "^0.1.0"
|
||||
mdast-util-gfm-task-list-item "^0.1.0"
|
||||
mdast-util-to-markdown "^0.6.1"
|
||||
|
||||
mdast-util-mdx-expression@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-0.1.1.tgz#fa1a04a5ea6777b0e8db6c120adf03088595df95"
|
||||
integrity sha512-SoO8y1B9NjMOYlNdwXMchuTVvqSTlUmXm1P5QvZNPv7OH7aa8qJV+3aA+vl1DHK9Vk1uZAlgwokjvDQhS6bINA==
|
||||
dependencies:
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
mdast-util-mdx-jsx@~0.1.0:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-0.1.4.tgz#868371b90b17337b4f072a07021f7ce19612cf34"
|
||||
integrity sha512-67KOAvCmypBSpr+AJEAVQg1Obig5Wnguo4ETTxASe5WVP4TLt57bZjDX/9EW5sWYQsO4gPqLxkUOlypVn5rkhg==
|
||||
dependencies:
|
||||
mdast-util-to-markdown "^0.6.0"
|
||||
parse-entities "^2.0.0"
|
||||
stringify-entities "^3.1.0"
|
||||
unist-util-remove-position "^3.0.0"
|
||||
unist-util-stringify-position "^2.0.0"
|
||||
vfile-message "^2.0.0"
|
||||
|
||||
mdast-util-mdx@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-0.1.1.tgz#16acbc6cabe33f4cebeb63fa9cf8be5da1d56fbf"
|
||||
integrity sha512-9nncdnHNYSb4HNxY3AwE6gU632jhbXsDGXe9PkkJoEawYWJ8tTwmEOHGlGa2TCRidtkd6FF5I8ogDU9pTDlQyA==
|
||||
dependencies:
|
||||
mdast-util-mdx-expression "~0.1.0"
|
||||
mdast-util-mdx-jsx "~0.1.0"
|
||||
mdast-util-mdxjs-esm "~0.1.0"
|
||||
mdast-util-to-markdown "^0.6.1"
|
||||
|
||||
mdast-util-mdxjs-esm@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-0.1.1.tgz#69134a0dad71a59a9e0e9cfdc0633dde31dff69a"
|
||||
integrity sha512-kBiYeashz+nuhfv+712nc4THQhzXIH2gBFUDbuLxuDCqU/fZeg+9FAcdRBx9E13dkpk1p2Xwufzs3wsGJ+mISQ==
|
||||
|
||||
mdast-util-to-hast@^10.2.0:
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz#61875526a017d8857b71abc9333942700b2d3604"
|
||||
integrity sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ==
|
||||
dependencies:
|
||||
"@types/mdast" "^3.0.0"
|
||||
"@types/unist" "^2.0.0"
|
||||
mdast-util-definitions "^4.0.0"
|
||||
mdurl "^1.0.0"
|
||||
unist-builder "^2.0.0"
|
||||
unist-util-generated "^1.0.0"
|
||||
unist-util-position "^3.0.0"
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
mdast-util-to-markdown@^0.6.0, mdast-util-to-markdown@^0.6.1, mdast-util-to-markdown@~0.6.0:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz"
|
||||
integrity sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==
|
||||
|
@ -7121,7 +7315,7 @@ mdast-util-toc@^5.1.0:
|
|||
unist-util-is "^4.0.0"
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
mdurl@^1.0.1:
|
||||
mdurl@^1.0.0, mdurl@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz"
|
||||
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
|
||||
|
@ -7231,6 +7425,13 @@ micro-memoize@^4.0.9:
|
|||
resolved "https://registry.npmjs.org/micro-memoize/-/micro-memoize-4.0.9.tgz"
|
||||
integrity sha512-Z2uZi/IUMGQDCXASdujXRqrXXEwSY0XffUrAOllhqzQI3wpUyZbiZTiE2JuYC0HSG2G7DbCS5jZmsEKEGZuemg==
|
||||
|
||||
micromark-extension-footnote@^0.3.0:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/micromark-extension-footnote/-/micromark-extension-footnote-0.3.2.tgz#129b74ef4920ce96719b2c06102ee7abb2b88a20"
|
||||
integrity sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==
|
||||
dependencies:
|
||||
micromark "~2.11.0"
|
||||
|
||||
micromark-extension-gfm-autolink-literal@~0.5.0:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz"
|
||||
|
@ -7264,7 +7465,7 @@ micromark-extension-gfm-task-list-item@~0.3.0:
|
|||
dependencies:
|
||||
micromark "~2.11.0"
|
||||
|
||||
micromark-extension-gfm@^0.3.3:
|
||||
micromark-extension-gfm@^0.3.0:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz"
|
||||
integrity sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==
|
||||
|
@ -7276,25 +7477,7 @@ micromark-extension-gfm@^0.3.3:
|
|||
micromark-extension-gfm-tagfilter "~0.3.0"
|
||||
micromark-extension-gfm-task-list-item "~0.3.0"
|
||||
|
||||
micromark-extension-mdx-expression@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-0.3.2.tgz"
|
||||
integrity sha512-Sh8YHLSAlbm/7TZkVKEC4wDcJE8XhVpZ9hUXBue1TcAicrrzs/oXu7PHH3NcyMemjGyMkiVS34Y0AHC5KG3y4A==
|
||||
dependencies:
|
||||
micromark "~2.11.0"
|
||||
vfile-message "^2.0.0"
|
||||
|
||||
micromark-extension-mdx-jsx@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-0.3.3.tgz"
|
||||
integrity sha512-kG3VwaJlzAPdtIVDznfDfBfNGMTIzsHqKpTmMlew/iPnUCDRNkX+48ElpaOzXAtK5axtpFKE3Hu3VBriZDnRTQ==
|
||||
dependencies:
|
||||
estree-util-is-identifier-name "^1.0.0"
|
||||
micromark "~2.11.0"
|
||||
micromark-extension-mdx-expression "^0.3.2"
|
||||
vfile-message "^2.0.0"
|
||||
|
||||
micromark@^2.11.4, micromark@~2.11.0, micromark@~2.11.3:
|
||||
micromark@^2.11.3, micromark@~2.11.0, micromark@~2.11.3:
|
||||
version "2.11.4"
|
||||
resolved "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz"
|
||||
integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==
|
||||
|
@ -7635,6 +7818,11 @@ neo-async@^2.6.0:
|
|||
resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
||||
nlcst-to-string@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-2.0.4.tgz#9315dfab80882bbfd86ddf1b706f53622dc400cc"
|
||||
integrity sha512-3x3jwTd6UPG7vi5k4GEzvxJ5rDA7hVUIRNHPblKuMVP9Z3xmlsd9cgLcpAMkc5uPOBna82EeshROFhsPkbnTZg==
|
||||
|
||||
node-emoji@^1.8.1:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz"
|
||||
|
@ -8283,6 +8471,15 @@ parse-json@^5.0.0:
|
|||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
parse-latin@^4.0.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-4.3.0.tgz#1a70fc5601743baa06c5f12253c334fc94b4a917"
|
||||
integrity sha512-TYKL+K98dcAWoCw/Ac1yrPviU8Trk+/gmjQVaoWEFDZmVD4KRg6c/80xKqNNFQObo2mTONgF8trzAf2UTwKafw==
|
||||
dependencies:
|
||||
nlcst-to-string "^2.0.0"
|
||||
unist-util-modify-children "^2.0.0"
|
||||
unist-util-visit-children "^1.0.0"
|
||||
|
||||
parse-ms@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmjs.org/parse-ms/-/parse-ms-0.1.2.tgz"
|
||||
|
@ -9455,6 +9652,36 @@ rehype-parse@^7.0.1:
|
|||
hast-util-from-parse5 "^6.0.0"
|
||||
parse5 "^6.0.0"
|
||||
|
||||
rehype-raw@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-5.1.0.tgz#66d5e8d7188ada2d31bc137bc19a1000cf2c6b7e"
|
||||
integrity sha512-MDvHAb/5mUnif2R+0IPCYJU8WjHa9UzGtM/F4AVy5GixPlDZ1z3HacYy4xojDU+uBa+0X/3PIfyQI26/2ljJNA==
|
||||
dependencies:
|
||||
hast-util-raw "^6.1.0"
|
||||
|
||||
rehype-stringify@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-8.0.0.tgz#9b6afb599bcf3165f10f93fc8548f9a03d2ec2ba"
|
||||
integrity sha512-VkIs18G0pj2xklyllrPSvdShAV36Ff3yE5PUO9u36f6+2qJFnn22Z5gKwBOwgXviux4UC7K+/j13AnZfPICi/g==
|
||||
dependencies:
|
||||
hast-util-to-html "^7.1.1"
|
||||
|
||||
remark-footnotes@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-3.0.0.tgz#5756b56f8464fa7ed80dbba0c966136305d8cb8d"
|
||||
integrity sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==
|
||||
dependencies:
|
||||
mdast-util-footnote "^0.1.0"
|
||||
micromark-extension-footnote "^0.3.0"
|
||||
|
||||
remark-gfm@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-1.0.0.tgz#9213643001be3f277da6256464d56fd28c3b3c0d"
|
||||
integrity sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==
|
||||
dependencies:
|
||||
mdast-util-gfm "^0.1.0"
|
||||
micromark-extension-gfm "^0.3.0"
|
||||
|
||||
remark-parse@^6.0.0:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz"
|
||||
|
@ -9478,11 +9705,18 @@ remark-parse@^6.0.0:
|
|||
|
||||
remark-parse@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640"
|
||||
integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==
|
||||
dependencies:
|
||||
mdast-util-from-markdown "^0.8.0"
|
||||
|
||||
remark-rehype@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-8.1.0.tgz#610509a043484c1e697437fa5eb3fd992617c945"
|
||||
integrity sha512-EbCu9kHgAxKmW1yEYjx3QafMyGY3q8noUbNUI5xyKbaFP89wbhDrKxyIQNukNYthzjNHZu6J7hwFg7hRm1svYA==
|
||||
dependencies:
|
||||
mdast-util-to-hast "^10.2.0"
|
||||
|
||||
remark-stringify@^6.0.0:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz"
|
||||
|
@ -9664,6 +9898,38 @@ ret@~0.1.10:
|
|||
resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz"
|
||||
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
|
||||
|
||||
retext-latin@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-2.0.4.tgz#ef5d34ae7641ae56b0675ea391095e8ee762b251"
|
||||
integrity sha512-fOoSSoQgDZ+l/uS81oxI3alBghDUPja0JEl0TpQxI6MN+dhM6fLFumPJwMZ4PJTyL5FFAgjlsdv8IX+6IRuwMw==
|
||||
dependencies:
|
||||
parse-latin "^4.0.0"
|
||||
unherit "^1.0.4"
|
||||
|
||||
retext-smartypants@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-4.0.0.tgz#77478bd9775b4d7505122b0799594339e08d4fda"
|
||||
integrity sha512-Mknd05zuIycr4Z/hNDxA8ktqv7pG7wYdTZc68a2MJF+Ibg/WloR5bbyrEjijwNwHRR+xWsovkLH4OQIz/mghdw==
|
||||
dependencies:
|
||||
nlcst-to-string "^2.0.0"
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
retext-stringify@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-2.0.4.tgz#496d6c532f7dc6d15e4b262de0266e828f72efa9"
|
||||
integrity sha512-xOtx5mFJBoT3j7PBtiY2I+mEGERNniofWktI1cKXvjMEJPOuqve0dghLHO1+gz/gScLn4zqspDGv4kk2wS5kSA==
|
||||
dependencies:
|
||||
nlcst-to-string "^2.0.0"
|
||||
|
||||
retext@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/retext/-/retext-7.0.1.tgz#04b7965ab78fe6e5e3a489304545b460d41bf5aa"
|
||||
integrity sha512-N0IaEDkvUjqyfn3/gwxVfI51IxfGzOiVXqPLWnKeCDbiQdxSg0zebzHPxXWnL7TeplAJ+RE4uqrXyYN//s9HjQ==
|
||||
dependencies:
|
||||
retext-latin "^2.0.0"
|
||||
retext-stringify "^2.0.0"
|
||||
unified "^8.0.0"
|
||||
|
||||
retry@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz"
|
||||
|
@ -10538,6 +10804,15 @@ stringify-entities@^1.0.1:
|
|||
is-alphanumerical "^1.0.0"
|
||||
is-hexadecimal "^1.0.0"
|
||||
|
||||
stringify-entities@^3.0.1, stringify-entities@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.1.0.tgz#b8d3feac256d9ffcc9fa1fefdcf3ca70576ee903"
|
||||
integrity sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==
|
||||
dependencies:
|
||||
character-entities-html4 "^1.0.0"
|
||||
character-entities-legacy "^1.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
|
||||
|
@ -11411,9 +11686,20 @@ unified@^7.0.0:
|
|||
vfile "^3.0.0"
|
||||
x-is-string "^0.1.0"
|
||||
|
||||
unified@^8.0.0:
|
||||
version "8.4.2"
|
||||
resolved "https://registry.yarnpkg.com/unified/-/unified-8.4.2.tgz#13ad58b4a437faa2751a4a4c6a16f680c500fff1"
|
||||
integrity sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==
|
||||
dependencies:
|
||||
bail "^1.0.0"
|
||||
extend "^3.0.0"
|
||||
is-plain-obj "^2.0.0"
|
||||
trough "^1.0.0"
|
||||
vfile "^4.0.0"
|
||||
|
||||
unified@^9.1.0, unified@^9.2.1:
|
||||
version "9.2.1"
|
||||
resolved "https://registry.npmjs.org/unified/-/unified-9.2.1.tgz"
|
||||
resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.1.tgz#ae18d5674c114021bfdbdf73865ca60f410215a3"
|
||||
integrity sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA==
|
||||
dependencies:
|
||||
bail "^1.0.0"
|
||||
|
@ -11459,6 +11745,11 @@ unique-string@^2.0.0:
|
|||
dependencies:
|
||||
crypto-random-string "^2.0.0"
|
||||
|
||||
unist-builder@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436"
|
||||
integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==
|
||||
|
||||
unist-util-find-all-after@^1.0.2:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.5.tgz"
|
||||
|
@ -11473,6 +11764,11 @@ unist-util-find-all-after@^3.0.2:
|
|||
dependencies:
|
||||
unist-util-is "^4.0.0"
|
||||
|
||||
unist-util-generated@^1.0.0:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b"
|
||||
integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==
|
||||
|
||||
unist-util-is@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz"
|
||||
|
@ -11483,6 +11779,23 @@ unist-util-is@^4.0.0:
|
|||
resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz"
|
||||
integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==
|
||||
|
||||
unist-util-is@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.0.tgz#93cab236c0d98e7c02265f6cfa3efe8b117a628c"
|
||||
integrity sha512-pWspZ+AvTqYbC+xWeRmzGqbcY8Na08Eowlfs2xchWTYot8vBBAq+syrE/LWS0bw1D/JOu4lwzDbEb6Mz13tK+g==
|
||||
|
||||
unist-util-modify-children@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-2.0.0.tgz#9c9c30d4e32502aabb3fde10d7872a17c86801e2"
|
||||
integrity sha512-HGrj7JQo9DwZt8XFsX8UD4gGqOsIlCih9opG6Y+N11XqkBGKzHo8cvDi+MfQQgiZ7zXRUiQREYHhjOBHERTMdg==
|
||||
dependencies:
|
||||
array-iterate "^1.0.0"
|
||||
|
||||
unist-util-position@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47"
|
||||
integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==
|
||||
|
||||
unist-util-remove-position@^1.0.0:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz"
|
||||
|
@ -11490,6 +11803,13 @@ unist-util-remove-position@^1.0.0:
|
|||
dependencies:
|
||||
unist-util-visit "^1.1.0"
|
||||
|
||||
unist-util-remove-position@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-3.0.0.tgz#4cd19e82c8e665f462b6acfcfd0a8353235a88e9"
|
||||
integrity sha512-17kIOuolVuK16LMb9KyMJlqdfCtlfQY5FjY3Sdo9iC7F5wqdXhNjMq0PBvMpkVNNnAmHxXssUW+rZ9T2zbP0Rg==
|
||||
dependencies:
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz"
|
||||
|
@ -11502,6 +11822,11 @@ unist-util-stringify-position@^2.0.0:
|
|||
dependencies:
|
||||
"@types/unist" "^2.0.2"
|
||||
|
||||
unist-util-visit-children@^1.0.0:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-1.1.4.tgz#e8a087e58a33a2815f76ea1901c15dec2cb4b432"
|
||||
integrity sha512-sA/nXwYRCQVRwZU2/tQWUqJ9JSFM1X3x7JIOsIgSzrFHcfVt6NkzDtKzyxg2cZWkCwGF9CO8x4QNZRJRMK8FeQ==
|
||||
|
||||
unist-util-visit-parents@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz"
|
||||
|
@ -11517,6 +11842,14 @@ unist-util-visit-parents@^3.0.0:
|
|||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^4.0.0"
|
||||
|
||||
unist-util-visit-parents@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz#e83559a4ad7e6048a46b1bdb22614f2f3f4724f2"
|
||||
integrity sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^5.0.0"
|
||||
|
||||
unist-util-visit@^1.1.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz"
|
||||
|
@ -11524,7 +11857,7 @@ unist-util-visit@^1.1.0:
|
|||
dependencies:
|
||||
unist-util-visit-parents "^2.0.0"
|
||||
|
||||
unist-util-visit@^2.0.0:
|
||||
unist-util-visit@^2.0.0, unist-util-visit@^2.0.1:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz"
|
||||
integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==
|
||||
|
@ -11533,6 +11866,15 @@ unist-util-visit@^2.0.0:
|
|||
unist-util-is "^4.0.0"
|
||||
unist-util-visit-parents "^3.0.0"
|
||||
|
||||
unist-util-visit@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-3.1.0.tgz#9420d285e1aee938c7d9acbafc8e160186dbaf7b"
|
||||
integrity sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==
|
||||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^5.0.0"
|
||||
unist-util-visit-parents "^4.0.0"
|
||||
|
||||
universal-user-agent@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz"
|
||||
|
|
Loading…
Reference in a new issue