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:
Nate Moore 2021-05-17 09:29:16 -05:00 committed by GitHub
parent fe5cf78e8e
commit b3886c206f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 4731 additions and 286 deletions

View file

@ -0,0 +1,5 @@
---
'astro-vscode': patch
---
Added support for new <Markdown> component

View 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)

View file

@ -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
View 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_.

View file

@ -0,0 +1,6 @@
export default {
extensions: {
'.jsx': 'react',
'.tsx': 'preact',
}
};

View 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": "../.."
}
}

View 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>
</>
);
}

View 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>
</>
);
}

View 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>

View 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>

View file

@ -0,0 +1,13 @@
---
export let content;
---
<html>
<head>
<title>{content.title}</title>
</head>
<body>
<slot/>
</body>
</html>

View 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, Astros 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. Its 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 youd 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. Its perfect for generating blog-like content, or scaffolding out dynamic URLs from your data.
Lets 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, were 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 doesnt 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 well 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 its marked as a collection.
In each `$[collection].astro` file, well 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 (well 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
```
Theres nothing special or reserved about any of these names; youre free to name everything whatever youd 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>
```
Lets 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` well 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 wed still like to make `/tag/:tag/1` and `/year/:year/1`. To do that, well create 2 more collections: `/src/pages/$tag.astro` and `src/pages/$year.astro`. Assume that the markup is the same, but weve 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 isnt 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 arent returning all posts here; were 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. Dont try and trick `permalink` to generate too many URL trees; itll 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>

View 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>

View file

@ -0,0 +1,5 @@
export default {
extensions: {
'.jsx': 'preact'
}
}

View 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.

View 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": "../.."
}
}

View file

@ -0,0 +1,5 @@
import { h, Fragment } from 'preact';
export default function Yell({ children }) {
return children.filter(v => typeof v === 'string').join('').toUpperCase() + '!'
}

View file

@ -0,0 +1,14 @@
---
export let content;
---
<html>
<head>
<title>{content.title}</title>
</head>
<body>
<slot />
<pre>{JSON.stringify(content)}</pre>
</body>
</html>

View 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>

View file

@ -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",

View file

@ -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';

View 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 };
}

View 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);
}

View file

@ -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;
}

View file

@ -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,

View file

@ -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++];
}

View file

@ -0,0 +1,3 @@
<!-- Probably not what you're looking for! -->
<!-- Check `astro-parser` or /frontend/markdown.ts -->
<slot />

View file

@ -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);
}

View file

@ -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",

View file

@ -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;

View file

@ -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 });

View file

@ -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 its 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);

View file

@ -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 = {

View file

@ -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,
};
}

View file

@ -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 };

View file

@ -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,
};
}

View file

@ -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;
}

View file

@ -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) }
}

View 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;

View 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);
}

View file

@ -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') {
// dont 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]) {
// dont 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') {
// dont 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]) {
// dont 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);
},
},
},

View 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(),
};
}

View 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'), '');
}

View file

@ -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');
};

View file

@ -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;

View file

@ -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'
],
},
});

View file

@ -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 {

View file

@ -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);

View file

@ -0,0 +1,3 @@
{
"workspaceRoot": "../../../../../"
}

View 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>

View file

@ -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 />

View 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>

View file

@ -0,0 +1,8 @@
export default {
extensions: {
'.jsx': 'preact',
},
buildOptions: {
sitemap: false,
},
};

View file

@ -0,0 +1,3 @@
{
"workspaceRoot": "../../../../../"
}

View file

@ -0,0 +1,10 @@
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<div class="container">
<slot></slot>
</div>
</body>
</html>

View file

@ -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>

View 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>

View 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();

View file

@ -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.

View file

@ -6,6 +6,6 @@
"build": "astro build"
},
"devDependencies": {
"astro": "^0.0.12"
"astro": "^0.0.13"
}
}

View file

@ -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.

View file

@ -6,6 +6,6 @@
"build": "astro build"
},
"devDependencies": {
"astro": "^0.0.12"
"astro": "^0.0.13"
}
}

View file

@ -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>

View file

@ -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.*-->"
}
}
}

View file

@ -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"
}
}
]
}

View file

@ -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;

File diff suppressed because it is too large Load diff

View file

@ -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
View file

@ -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"