mirror of
https://github.com/withastro/astro.git
synced 2025-03-03 22:57:08 -05:00
Merge branch 'main' into test/e2e-hydration
This commit is contained in:
commit
13805208bf
19 changed files with 661 additions and 523 deletions
5
.changeset/dull-monkeys-grab.md
Normal file
5
.changeset/dull-monkeys-grab.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix component usage in imported markdown files
|
|
@ -11,7 +11,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/preact": "^0.1.2",
|
"@astrojs/preact": "^0.1.2",
|
||||||
"astro": "^1.0.0-beta.31",
|
"astro": "^1.0.0-beta.31",
|
||||||
"sass": "^1.51.0"
|
"sass": "^1.52.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"preact": "^10.7.2"
|
"preact": "^10.7.2"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/preact": "^0.1.2",
|
"@astrojs/preact": "^0.1.2",
|
||||||
"astro": "^1.0.0-beta.31",
|
"astro": "^1.0.0-beta.31",
|
||||||
"sass": "^1.51.0"
|
"sass": "^1.52.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"preact": "^10.7.2"
|
"preact": "^10.7.2"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/react": "^0.1.2",
|
"@astrojs/react": "^0.1.2",
|
||||||
"astro": "^1.0.0-beta.31",
|
"astro": "^1.0.0-beta.31",
|
||||||
"sass": "^1.51.0"
|
"sass": "^1.52.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
|
|
|
@ -77,16 +77,16 @@
|
||||||
"test:e2e:match": "playwright test e2e -g"
|
"test:e2e:match": "playwright test e2e -g"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^0.14.2",
|
"@astrojs/compiler": "^0.14.3",
|
||||||
"@astrojs/language-server": "^0.13.4",
|
"@astrojs/language-server": "^0.13.4",
|
||||||
"@astrojs/markdown-remark": "^0.9.4",
|
"@astrojs/markdown-remark": "^0.9.4",
|
||||||
"@astrojs/prism": "0.4.1",
|
"@astrojs/prism": "0.4.1",
|
||||||
"@astrojs/telemetry": "^0.1.2",
|
"@astrojs/telemetry": "^0.1.2",
|
||||||
"@astrojs/webapi": "^0.11.1",
|
"@astrojs/webapi": "^0.11.1",
|
||||||
"@babel/core": "^7.17.12",
|
"@babel/core": "^7.18.0",
|
||||||
"@babel/generator": "^7.17.12",
|
"@babel/generator": "^7.18.0",
|
||||||
"@babel/parser": "^7.17.12",
|
"@babel/parser": "^7.18.0",
|
||||||
"@babel/traverse": "^7.17.12",
|
"@babel/traverse": "^7.18.0",
|
||||||
"@proload/core": "^0.3.2",
|
"@proload/core": "^0.3.2",
|
||||||
"@proload/plugin-tsm": "^0.2.1",
|
"@proload/plugin-tsm": "^0.2.1",
|
||||||
"ast-types": "^0.14.2",
|
"ast-types": "^0.14.2",
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
"recast": "^0.20.5",
|
"recast": "^0.20.5",
|
||||||
"resolve": "^1.22.0",
|
"resolve": "^1.22.0",
|
||||||
"rollup": "^2.74.0",
|
"rollup": "^2.74.1",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.3.7",
|
||||||
"serialize-javascript": "^6.0.0",
|
"serialize-javascript": "^6.0.0",
|
||||||
"shiki": "^0.10.1",
|
"shiki": "^0.10.1",
|
||||||
|
@ -136,7 +136,7 @@
|
||||||
"zod": "^3.16.0"
|
"zod": "^3.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.17.12",
|
"@babel/types": "^7.18.0",
|
||||||
"@playwright/test": "^1.22.1",
|
"@playwright/test": "^1.22.1",
|
||||||
"@types/babel__core": "^7.1.19",
|
"@types/babel__core": "^7.1.19",
|
||||||
"@types/babel__generator": "^7.6.4",
|
"@types/babel__generator": "^7.6.4",
|
||||||
|
@ -162,7 +162,7 @@
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
"cheerio": "^1.0.0-rc.10",
|
"cheerio": "^1.0.0-rc.10",
|
||||||
"mocha": "^9.2.2",
|
"mocha": "^9.2.2",
|
||||||
"sass": "^1.51.0",
|
"sass": "^1.52.0",
|
||||||
"srcset-parse": "^1.1.0"
|
"srcset-parse": "^1.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import type * as vite from 'vite';
|
import type { HtmlTagDescriptor, ViteDevServer } from 'vite';
|
||||||
import type {
|
import type {
|
||||||
AstroConfig,
|
AstroConfig,
|
||||||
AstroRenderer,
|
AstroRenderer,
|
||||||
|
@ -15,9 +15,9 @@ import { prependForwardSlash } from '../../../core/path.js';
|
||||||
import { RouteCache } from '../route-cache.js';
|
import { RouteCache } from '../route-cache.js';
|
||||||
import { createModuleScriptElementWithSrcSet } from '../ssr-element.js';
|
import { createModuleScriptElementWithSrcSet } from '../ssr-element.js';
|
||||||
import { getStylesForURL } from './css.js';
|
import { getStylesForURL } from './css.js';
|
||||||
import { getHmrScript } from './hmr.js';
|
|
||||||
import { injectTags } from './html.js';
|
import { injectTags } from './html.js';
|
||||||
import { isBuildingToSSR } from '../../util.js';
|
import { isBuildingToSSR } from '../../util.js';
|
||||||
|
import { collectMdMetadata } from '../util.js';
|
||||||
|
|
||||||
export interface SSROptions {
|
export interface SSROptions {
|
||||||
/** an instance of the AstroConfig */
|
/** an instance of the AstroConfig */
|
||||||
|
@ -37,7 +37,7 @@ export interface SSROptions {
|
||||||
/** pass in route cache because SSR can’t manage cache-busting */
|
/** pass in route cache because SSR can’t manage cache-busting */
|
||||||
routeCache: RouteCache;
|
routeCache: RouteCache;
|
||||||
/** Vite instance */
|
/** Vite instance */
|
||||||
viteServer: vite.ViteDevServer;
|
viteServer: ViteDevServer;
|
||||||
/** Request */
|
/** Request */
|
||||||
request: Request;
|
request: Request;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ export type RenderResponse =
|
||||||
const svelteStylesRE = /svelte\?svelte&type=style/;
|
const svelteStylesRE = /svelte\?svelte&type=style/;
|
||||||
|
|
||||||
async function loadRenderer(
|
async function loadRenderer(
|
||||||
viteServer: vite.ViteDevServer,
|
viteServer: ViteDevServer,
|
||||||
renderer: AstroRenderer
|
renderer: AstroRenderer
|
||||||
): Promise<SSRLoadedRenderer> {
|
): Promise<SSRLoadedRenderer> {
|
||||||
// Vite modules can be out-of-date when using an un-resolved url
|
// Vite modules can be out-of-date when using an un-resolved url
|
||||||
|
@ -65,7 +65,7 @@ async function loadRenderer(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadRenderers(
|
export async function loadRenderers(
|
||||||
viteServer: vite.ViteDevServer,
|
viteServer: ViteDevServer,
|
||||||
astroConfig: AstroConfig
|
astroConfig: AstroConfig
|
||||||
): Promise<SSRLoadedRenderer[]> {
|
): Promise<SSRLoadedRenderer[]> {
|
||||||
return Promise.all(astroConfig._ctx.renderers.map((r) => loadRenderer(viteServer, r)));
|
return Promise.all(astroConfig._ctx.renderers.map((r) => loadRenderer(viteServer, r)));
|
||||||
|
@ -80,6 +80,15 @@ export async function preload({
|
||||||
const renderers = await loadRenderers(viteServer, astroConfig);
|
const renderers = await loadRenderers(viteServer, astroConfig);
|
||||||
// Load the module from the Vite SSR Runtime.
|
// Load the module from the Vite SSR Runtime.
|
||||||
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
|
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
|
||||||
|
if (viteServer.config.mode === 'development' || !mod?.$$metadata) {
|
||||||
|
return [renderers, mod];
|
||||||
|
}
|
||||||
|
|
||||||
|
// append all nested markdown metadata to mod.$$metadata
|
||||||
|
const modGraph = await viteServer.moduleGraph.getModuleByUrl(fileURLToPath(filePath));
|
||||||
|
if (modGraph) {
|
||||||
|
await collectMdMetadata(mod.$$metadata, modGraph, viteServer);
|
||||||
|
}
|
||||||
|
|
||||||
return [renderers, mod];
|
return [renderers, mod];
|
||||||
}
|
}
|
||||||
|
@ -179,7 +188,7 @@ export async function render(
|
||||||
}
|
}
|
||||||
|
|
||||||
// inject tags
|
// inject tags
|
||||||
const tags: vite.HtmlTagDescriptor[] = [];
|
const tags: HtmlTagDescriptor[] = [];
|
||||||
|
|
||||||
// add injected tags
|
// add injected tags
|
||||||
let html = injectTags(content.html, tags);
|
let html = injectTags(content.html, tags);
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import npath from 'path-browserify';
|
import npath from 'path-browserify';
|
||||||
|
import type { ModuleNode, ViteDevServer } from 'vite';
|
||||||
|
import type { Metadata } from '../../runtime/server/metadata.js';
|
||||||
|
|
||||||
/** Normalize URL to its canonical form */
|
/** Normalize URL to its canonical form */
|
||||||
export function createCanonicalURL(url: string, base?: string): URL {
|
export function createCanonicalURL(url: string, base?: string): URL {
|
||||||
|
@ -30,9 +32,59 @@ export const STYLE_EXTENSIONS = new Set([
|
||||||
'.less',
|
'.less',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// duplicate const from vite-plugin-markdown
|
||||||
|
// can't import directly due to Deno bundling issue
|
||||||
|
// (node fs import failing during prod builds)
|
||||||
|
const MARKDOWN_IMPORT_FLAG = '?mdImport';
|
||||||
|
|
||||||
const cssRe = new RegExp(
|
const cssRe = new RegExp(
|
||||||
`\\.(${Array.from(STYLE_EXTENSIONS)
|
`\\.(${Array.from(STYLE_EXTENSIONS)
|
||||||
.map((s) => s.slice(1))
|
.map((s) => s.slice(1))
|
||||||
.join('|')})($|\\?)`
|
.join('|')})($|\\?)`
|
||||||
);
|
);
|
||||||
export const isCSSRequest = (request: string): boolean => cssRe.test(request);
|
export const isCSSRequest = (request: string): boolean => cssRe.test(request);
|
||||||
|
|
||||||
|
// During prod builds, some modules have dependencies we should preload by hand
|
||||||
|
// Ex. markdown files imported asynchronously or via Astro.glob(...)
|
||||||
|
// This calls each md file's $$loadMetadata to discover those dependencies
|
||||||
|
// and writes all results to the input `metadata` object
|
||||||
|
const seenMdMetadata = new Set<string>();
|
||||||
|
export async function collectMdMetadata(
|
||||||
|
metadata: Metadata,
|
||||||
|
modGraph: ModuleNode,
|
||||||
|
viteServer: ViteDevServer
|
||||||
|
) {
|
||||||
|
const importedModules = [...(modGraph?.importedModules ?? [])];
|
||||||
|
await Promise.all(
|
||||||
|
importedModules.map(async (importedModule) => {
|
||||||
|
// recursively check for importedModules
|
||||||
|
if (!importedModule.id || seenMdMetadata.has(importedModule.id)) return;
|
||||||
|
|
||||||
|
seenMdMetadata.add(importedModule.id);
|
||||||
|
await collectMdMetadata(metadata, importedModule, viteServer);
|
||||||
|
|
||||||
|
if (!importedModule?.id?.endsWith(MARKDOWN_IMPORT_FLAG)) return;
|
||||||
|
|
||||||
|
const mdSSRMod = await viteServer.ssrLoadModule(importedModule.id);
|
||||||
|
const mdMetadata = (await mdSSRMod.$$loadMetadata?.()) as Metadata;
|
||||||
|
if (!mdMetadata) return;
|
||||||
|
|
||||||
|
for (let mdMod of mdMetadata.modules) {
|
||||||
|
mdMod.specifier = mdMetadata.resolvePath(mdMod.specifier);
|
||||||
|
metadata.modules.push(mdMod);
|
||||||
|
}
|
||||||
|
for (let mdHoisted of mdMetadata.hoisted) {
|
||||||
|
metadata.hoisted.push(mdHoisted);
|
||||||
|
}
|
||||||
|
for (let mdHydrated of mdMetadata.hydratedComponents) {
|
||||||
|
metadata.hydratedComponents.push(mdHydrated);
|
||||||
|
}
|
||||||
|
for (let mdClientOnly of mdMetadata.clientOnlyComponents) {
|
||||||
|
metadata.clientOnlyComponents.push(mdClientOnly);
|
||||||
|
}
|
||||||
|
for (let mdHydrationDirective of mdMetadata.hydrationDirectives) {
|
||||||
|
metadata.hydrationDirectives.add(mdHydrationDirective);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -89,6 +89,10 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
export const frontmatter = ${JSON.stringify(frontmatter)};
|
export const frontmatter = ${JSON.stringify(frontmatter)};
|
||||||
export const file = ${JSON.stringify(fileId)};
|
export const file = ${JSON.stringify(fileId)};
|
||||||
export const url = ${JSON.stringify(fileUrl)};
|
export const url = ${JSON.stringify(fileUrl)};
|
||||||
|
|
||||||
|
export function $$loadMetadata() {
|
||||||
|
return load().then((m) => m.$$metadata)
|
||||||
|
}
|
||||||
|
|
||||||
// Deferred
|
// Deferred
|
||||||
export default async function load() {
|
export default async function load() {
|
||||||
|
@ -109,10 +113,10 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
|
||||||
// directly as a page in Vite, or it was a deferred render from a JS module.
|
// directly as a page in Vite, or it was a deferred render from a JS module.
|
||||||
// This returns the compiled markdown -> astro component that renders to HTML.
|
// This returns the compiled markdown -> astro component that renders to HTML.
|
||||||
if (id.endsWith('.md')) {
|
if (id.endsWith('.md')) {
|
||||||
const source = await fs.promises.readFile(id, 'utf8');
|
const filename = normalizeFilename(id);
|
||||||
|
const source = await fs.promises.readFile(filename, 'utf8');
|
||||||
const renderOpts = config.markdown;
|
const renderOpts = config.markdown;
|
||||||
|
|
||||||
const filename = normalizeFilename(id);
|
|
||||||
const fileUrl = new URL(`file://${filename}`);
|
const fileUrl = new URL(`file://${filename}`);
|
||||||
const isPage = fileUrl.pathname.startsWith(resolvePages(config).pathname);
|
const isPage = fileUrl.pathname.startsWith(resolvePages(config).pathname);
|
||||||
const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr');
|
const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr');
|
||||||
|
@ -142,7 +146,7 @@ ${setup}`.trim();
|
||||||
|
|
||||||
// Transform from `.astro` to valid `.ts`
|
// Transform from `.astro` to valid `.ts`
|
||||||
let { code: tsResult } = await transform(astroResult, {
|
let { code: tsResult } = await transform(astroResult, {
|
||||||
pathname: fileUrl.pathname.slice(config.root.pathname.length - 1),
|
pathname: '/@fs' + prependForwardSlash(fileUrl.pathname),
|
||||||
projectRoot: config.root.toString(),
|
projectRoot: config.root.toString(),
|
||||||
site: config.site ? new URL(config.base, config.site).toString() : undefined,
|
site: config.site ? new URL(config.base, config.site).toString() : undefined,
|
||||||
sourcefile: id,
|
sourcefile: id,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
import preact from '@astrojs/preact';
|
import preact from '@astrojs/preact';
|
||||||
|
import svelte from "@astrojs/svelte";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [preact()],
|
integrations: [preact(), svelte()]
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/preact": "workspace:*",
|
"@astrojs/preact": "workspace:*",
|
||||||
|
"@astrojs/svelte": "workspace:*",
|
||||||
"astro": "workspace:*"
|
"astro": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
packages/astro/test/fixtures/astro-markdown/src/components/SvelteButton.svelte
vendored
Normal file
11
packages/astro/test/fixtures/astro-markdown/src/components/SvelteButton.svelte
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<script>
|
||||||
|
let cool = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button on:click={() => cool = true}>This is cool right? {cool}</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
</style>
|
6
packages/astro/test/fixtures/astro-markdown/src/imported-md/plain.md
vendored
Normal file
6
packages/astro/test/fixtures/astro-markdown/src/imported-md/plain.md
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plain jane
|
||||||
|
|
||||||
|
I am plain markdown!
|
17
packages/astro/test/fixtures/astro-markdown/src/imported-md/with-components.md
vendored
Normal file
17
packages/astro/test/fixtures/astro-markdown/src/imported-md/with-components.md
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
setup: |
|
||||||
|
import Counter from '../components/Counter.jsx'
|
||||||
|
import Hello from '../components/Hello.jsx'
|
||||||
|
import SvelteButton from '../components/SvelteButton.svelte'
|
||||||
|
---
|
||||||
|
|
||||||
|
## With components
|
||||||
|
|
||||||
|
### Non-hydrated
|
||||||
|
|
||||||
|
<Hello name="Astro Naut" />
|
||||||
|
|
||||||
|
### Hydrated
|
||||||
|
|
||||||
|
<Counter client:load />
|
||||||
|
<SvelteButton client:load />
|
9
packages/astro/test/fixtures/astro-markdown/src/pages/imported-md/with-components.astro
vendored
Normal file
9
packages/astro/test/fixtures/astro-markdown/src/pages/imported-md/with-components.astro
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
import Layout from '../../layouts/content.astro'
|
||||||
|
|
||||||
|
const posts = await Astro.glob('../../imported-md/*.md')
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
{posts.map(({ Content }) => <Content />)}
|
||||||
|
</Layout>
|
|
@ -3,13 +3,10 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "workspace:*",
|
|
||||||
"@astrojs/react": "workspace:*",
|
|
||||||
"@adobe/react-spectrum": "^3.17.0",
|
"@adobe/react-spectrum": "^3.17.0",
|
||||||
"react": "^17.0.0",
|
"@astrojs/react": "workspace:*",
|
||||||
"react-dom": "^17.0.0"
|
"astro": "workspace:*",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "workspace:*",
|
"astro": "workspace:*",
|
||||||
"sass": "^1.51.0"
|
"sass": "^1.52.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ export async function loadFixture(inlineConfig) {
|
||||||
let devServer;
|
let devServer;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }),
|
build: (opts = {}) => build(config, { logging, telemetry, ...opts }),
|
||||||
startDevServer: async (opts = {}) => {
|
startDevServer: async (opts = {}) => {
|
||||||
devServer = await dev(config, { logging, telemetry, ...opts });
|
devServer = await dev(config, { logging, telemetry, ...opts });
|
||||||
config.server.port = devServer.address.port; // update port
|
config.server.port = devServer.address.port; // update port
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
"magic-string": "^0.25.9",
|
"magic-string": "^0.25.9",
|
||||||
"mocha": "^9.2.2",
|
"mocha": "^9.2.2",
|
||||||
"node-fetch": "^3.2.4",
|
"node-fetch": "^3.2.4",
|
||||||
"rollup": "^2.74.0",
|
"rollup": "^2.74.1",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.6.4",
|
||||||
|
|
1010
pnpm-lock.yaml
generated
1010
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue