astro 3.5.4 → 3.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/components/ViewTransitions.astro +16 -8
  2. package/dist/@types/astro.d.ts +11 -1
  3. package/dist/cli/add/index.js +9 -1
  4. package/dist/core/app/index.js +24 -6
  5. package/dist/core/build/buildPipeline.js +5 -7
  6. package/dist/core/build/generate.js +83 -73
  7. package/dist/core/build/internal.d.ts +0 -6
  8. package/dist/core/build/internal.js +3 -16
  9. package/dist/core/build/page-data.js +18 -46
  10. package/dist/core/build/static-build.js +7 -9
  11. package/dist/core/build/types.d.ts +1 -1
  12. package/dist/core/constants.js +1 -1
  13. package/dist/core/dev/dev.js +1 -1
  14. package/dist/core/endpoint/index.d.ts +4 -2
  15. package/dist/core/endpoint/index.js +22 -4
  16. package/dist/core/messages.js +2 -2
  17. package/dist/core/middleware/index.js +3 -1
  18. package/dist/core/pipeline.d.ts +6 -0
  19. package/dist/core/pipeline.js +17 -8
  20. package/dist/core/render/context.d.ts +3 -0
  21. package/dist/core/render/context.js +18 -1
  22. package/dist/core/render/core.js +3 -1
  23. package/dist/core/render/result.d.ts +2 -0
  24. package/dist/core/render/result.js +23 -1
  25. package/dist/core/routing/manifest/create.js +25 -20
  26. package/dist/core/routing/manifest/serialization.js +7 -1
  27. package/dist/core/routing/match.js +4 -1
  28. package/dist/core/routing/params.js +1 -1
  29. package/dist/i18n/middleware.d.ts +5 -0
  30. package/dist/i18n/middleware.js +14 -3
  31. package/dist/runtime/client/dev-overlay/entrypoint.js +147 -6
  32. package/dist/runtime/client/dev-overlay/overlay.d.ts +4 -0
  33. package/dist/runtime/client/dev-overlay/overlay.js +20 -8
  34. package/dist/runtime/client/dev-overlay/plugins/settings.js +2 -2
  35. package/dist/runtime/client/dev-overlay/settings.d.ts +2 -2
  36. package/dist/runtime/client/dev-overlay/settings.js +1 -1
  37. package/dist/runtime/client/dev-overlay/ui-library/icons.d.ts +1 -0
  38. package/dist/runtime/client/dev-overlay/ui-library/icons.js +2 -1
  39. package/dist/runtime/client/dev-overlay/ui-library/toggle.d.ts +2 -0
  40. package/dist/runtime/client/dev-overlay/ui-library/toggle.js +6 -0
  41. package/dist/vite-plugin-astro-server/route.js +11 -4
  42. package/package.json +2 -2
@@ -8,16 +8,18 @@ type CreateAPIContext = {
8
8
  props: Record<string, any>;
9
9
  adapterName?: string;
10
10
  locales: string[] | undefined;
11
+ routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined;
12
+ defaultLocale: string | undefined;
11
13
  };
12
14
  /**
13
15
  * Creates a context that holds all the information needed to handle an Astro endpoint.
14
16
  *
15
17
  * @param {CreateAPIContext} payload
16
18
  */
17
- export declare function createAPIContext({ request, params, site, props, adapterName, locales, }: CreateAPIContext): APIContext;
19
+ export declare function createAPIContext({ request, params, site, props, adapterName, locales, routingStrategy, defaultLocale, }: CreateAPIContext): APIContext;
18
20
  type ResponseParameters = ConstructorParameters<typeof Response>;
19
21
  export declare class ResponseWithEncoding extends Response {
20
22
  constructor(body: ResponseParameters[0], init: ResponseParameters[1], encoding?: BufferEncoding);
21
23
  }
22
- export declare function callEndpoint<MiddlewareResult = Response | EndpointOutput>(mod: EndpointHandler, env: Environment, ctx: RenderContext, onRequest: MiddlewareHandler<MiddlewareResult> | undefined, locales: undefined | string[]): Promise<Response>;
24
+ export declare function callEndpoint<MiddlewareResult = Response | EndpointOutput>(mod: EndpointHandler, env: Environment, ctx: RenderContext, onRequest: MiddlewareHandler<MiddlewareResult> | undefined): Promise<Response>;
23
25
  export {};
@@ -4,7 +4,11 @@ import { ASTRO_VERSION } from "../constants.js";
4
4
  import { AstroCookies, attachCookiesToResponse } from "../cookies/index.js";
5
5
  import { AstroError, AstroErrorData } from "../errors/index.js";
6
6
  import { callMiddleware } from "../middleware/callMiddleware.js";
7
- import { computePreferredLocale, computePreferredLocaleList } from "../render/context.js";
7
+ import {
8
+ computeCurrentLocale,
9
+ computePreferredLocale,
10
+ computePreferredLocaleList
11
+ } from "../render/context.js";
8
12
  import {} from "../render/index.js";
9
13
  const encoder = new TextEncoder();
10
14
  const clientAddressSymbol = Symbol.for("astro.clientAddress");
@@ -15,10 +19,13 @@ function createAPIContext({
15
19
  site,
16
20
  props,
17
21
  adapterName,
18
- locales
22
+ locales,
23
+ routingStrategy,
24
+ defaultLocale
19
25
  }) {
20
26
  let preferredLocale = void 0;
21
27
  let preferredLocaleList = void 0;
28
+ let currentLocale = void 0;
22
29
  const context = {
23
30
  cookies: new AstroCookies(request),
24
31
  request,
@@ -55,6 +62,15 @@ function createAPIContext({
55
62
  }
56
63
  return void 0;
57
64
  },
65
+ get currentLocale() {
66
+ if (currentLocale) {
67
+ return currentLocale;
68
+ }
69
+ if (locales) {
70
+ currentLocale = computeCurrentLocale(request, locales, routingStrategy, defaultLocale);
71
+ }
72
+ return currentLocale;
73
+ },
58
74
  url: new URL(request.url),
59
75
  get clientAddress() {
60
76
  if (clientAddressSymbol in request) {
@@ -106,14 +122,16 @@ class ResponseWithEncoding extends Response {
106
122
  }
107
123
  }
108
124
  }
109
- async function callEndpoint(mod, env, ctx, onRequest, locales) {
125
+ async function callEndpoint(mod, env, ctx, onRequest) {
110
126
  const context = createAPIContext({
111
127
  request: ctx.request,
112
128
  params: ctx.params,
113
129
  props: ctx.props,
114
130
  site: env.site,
115
131
  adapterName: env.adapterName,
116
- locales
132
+ routingStrategy: ctx.routingStrategy,
133
+ defaultLocale: ctx.defaultLocale,
134
+ locales: ctx.locales
117
135
  });
118
136
  let response;
119
137
  if (onRequest) {
@@ -50,7 +50,7 @@ function serverStart({
50
50
  base,
51
51
  isRestart = false
52
52
  }) {
53
- const version = "3.5.4";
53
+ const version = "3.5.6";
54
54
  const localPrefix = `${dim("\u2503")} Local `;
55
55
  const networkPrefix = `${dim("\u2503")} Network `;
56
56
  const emptyPrefix = " ".repeat(11);
@@ -235,7 +235,7 @@ function printHelp({
235
235
  message.push(
236
236
  linebreak(),
237
237
  ` ${bgGreen(black(` ${commandName} `))} ${green(
238
- `v${"3.5.4"}`
238
+ `v${"3.5.6"}`
239
239
  )} ${headline}`
240
240
  );
241
241
  }
@@ -9,7 +9,9 @@ function createContext({ request, params, userDefinedLocales = [] }) {
9
9
  params: params ?? {},
10
10
  props: {},
11
11
  site: void 0,
12
- locales: userDefinedLocales
12
+ locales: userDefinedLocales,
13
+ defaultLocale: void 0,
14
+ routingStrategy: void 0
13
15
  });
14
16
  }
15
17
  function isLocalsSerializable(value) {
@@ -1,6 +1,7 @@
1
1
  import type { ComponentInstance, MiddlewareEndpointHandler } from '../@types/astro.js';
2
2
  import { type Environment, type RenderContext } from './render/index.js';
3
3
  type EndpointResultHandler = (originalRequest: Request, result: Response) => Promise<Response> | Response;
4
+ export type PipelineHookFunction = (ctx: RenderContext, mod: ComponentInstance | undefined) => void;
4
5
  /**
5
6
  * This is the basic class of a pipeline.
6
7
  *
@@ -37,5 +38,10 @@ export declare class Pipeline {
37
38
  * The main function of the pipeline. Use this function to render any route known to Astro;
38
39
  */
39
40
  renderRoute(renderContext: RenderContext, componentInstance: ComponentInstance | undefined): Promise<Response>;
41
+ /**
42
+ * Store a function that will be called before starting the rendering phase.
43
+ * @param fn
44
+ */
45
+ onBeforeRenderRoute(fn: PipelineHookFunction): void;
40
46
  }
41
47
  export {};
@@ -5,6 +5,9 @@ import {} from "./render/index.js";
5
5
  class Pipeline {
6
6
  env;
7
7
  #onRequest;
8
+ #hooks = {
9
+ before: []
10
+ };
8
11
  /**
9
12
  * The handler accepts the *original* `Request` and result returned by the endpoint.
10
13
  * It must return a `Response`.
@@ -49,6 +52,9 @@ class Pipeline {
49
52
  * The main function of the pipeline. Use this function to render any route known to Astro;
50
53
  */
51
54
  async renderRoute(renderContext, componentInstance) {
55
+ for (const hook of this.#hooks.before) {
56
+ hook(renderContext, componentInstance);
57
+ }
52
58
  const result = await this.#tryRenderRoute(
53
59
  renderContext,
54
60
  this.env,
@@ -83,7 +89,9 @@ class Pipeline {
83
89
  props: renderContext.props,
84
90
  site: env.site,
85
91
  adapterName: env.adapterName,
86
- locales: renderContext.locales
92
+ locales: renderContext.locales,
93
+ routingStrategy: renderContext.routingStrategy,
94
+ defaultLocale: renderContext.defaultLocale
87
95
  });
88
96
  switch (renderContext.route.type) {
89
97
  case "page":
@@ -113,18 +121,19 @@ class Pipeline {
113
121
  }
114
122
  }
115
123
  case "endpoint": {
116
- return await callEndpoint(
117
- mod,
118
- env,
119
- renderContext,
120
- onRequest,
121
- renderContext.locales
122
- );
124
+ return await callEndpoint(mod, env, renderContext, onRequest);
123
125
  }
124
126
  default:
125
127
  throw new Error(`Couldn't find route of type [${renderContext.route.type}]`);
126
128
  }
127
129
  }
130
+ /**
131
+ * Store a function that will be called before starting the rendering phase.
132
+ * @param fn
133
+ */
134
+ onBeforeRenderRoute(fn) {
135
+ this.#hooks.before.push(fn);
136
+ }
128
137
  }
129
138
  export {
130
139
  Pipeline
@@ -16,6 +16,8 @@ export interface RenderContext {
16
16
  props: Props;
17
17
  locals?: object;
18
18
  locales: string[] | undefined;
19
+ defaultLocale: string | undefined;
20
+ routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined;
19
21
  }
20
22
  export type CreateRenderContextArgs = Partial<Omit<RenderContext, 'params' | 'props' | 'locals'>> & {
21
23
  route: RouteData;
@@ -45,4 +47,5 @@ export declare function parseLocale(header: string): BrowserLocale[];
45
47
  */
46
48
  export declare function computePreferredLocale(request: Request, locales: string[]): string | undefined;
47
49
  export declare function computePreferredLocaleList(request: Request, locales: string[]): string[];
50
+ export declare function computeCurrentLocale(request: Request, locales: string[], routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined, defaultLocale: string | undefined): undefined | string;
48
51
  export {};
@@ -18,7 +18,9 @@ async function createRenderContext(options) {
18
18
  pathname,
19
19
  params,
20
20
  props,
21
- locales: options.locales
21
+ locales: options.locales,
22
+ routingStrategy: options.routingStrategy,
23
+ defaultLocale: options.defaultLocale
22
24
  };
23
25
  Object.defineProperty(context, "locals", {
24
26
  enumerable: true,
@@ -124,7 +126,22 @@ function computePreferredLocaleList(request, locales) {
124
126
  }
125
127
  return result;
126
128
  }
129
+ function computeCurrentLocale(request, locales, routingStrategy, defaultLocale) {
130
+ const requestUrl = new URL(request.url);
131
+ for (const segment of requestUrl.pathname.split("/")) {
132
+ for (const locale of locales) {
133
+ if (normalizeTheLocale(locale) === normalizeTheLocale(segment)) {
134
+ return locale;
135
+ }
136
+ }
137
+ }
138
+ if (routingStrategy === "prefix-other-locales") {
139
+ return defaultLocale;
140
+ }
141
+ return void 0;
142
+ }
127
143
  export {
144
+ computeCurrentLocale,
128
145
  computePreferredLocale,
129
146
  computePreferredLocaleList,
130
147
  createRenderContext,
@@ -43,7 +43,9 @@ async function renderPage({ mod, renderContext, env, cookies }) {
43
43
  status: renderContext.status ?? 200,
44
44
  cookies,
45
45
  locals: renderContext.locals ?? {},
46
- locales: renderContext.locales
46
+ locales: renderContext.locales,
47
+ defaultLocale: renderContext.defaultLocale,
48
+ routingStrategy: renderContext.routingStrategy
47
49
  });
48
50
  if (mod.frontmatter && typeof mod.frontmatter === "object" && "draft" in mod.frontmatter) {
49
51
  env.logger.warn(
@@ -31,5 +31,7 @@ export interface CreateResultArgs {
31
31
  locals: App.Locals;
32
32
  cookies?: AstroCookies;
33
33
  locales: string[] | undefined;
34
+ defaultLocale: string | undefined;
35
+ routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined;
34
36
  }
35
37
  export declare function createResult(args: CreateResultArgs): SSRResult;
@@ -3,7 +3,11 @@ import { renderJSX } from "../../runtime/server/jsx.js";
3
3
  import { chunkToString } from "../../runtime/server/render/index.js";
4
4
  import { AstroCookies } from "../cookies/index.js";
5
5
  import { AstroError, AstroErrorData } from "../errors/index.js";
6
- import { computePreferredLocale, computePreferredLocaleList } from "./context.js";
6
+ import {
7
+ computeCurrentLocale,
8
+ computePreferredLocale,
9
+ computePreferredLocaleList
10
+ } from "./context.js";
7
11
  const clientAddressSymbol = Symbol.for("astro.clientAddress");
8
12
  const responseSentSymbol = Symbol.for("astro.responseSent");
9
13
  function getFunctionExpression(slot) {
@@ -91,6 +95,7 @@ function createResult(args) {
91
95
  let cookies = args.cookies;
92
96
  let preferredLocale = void 0;
93
97
  let preferredLocaleList = void 0;
98
+ let currentLocale = void 0;
94
99
  const result = {
95
100
  styles: args.styles ?? /* @__PURE__ */ new Set(),
96
101
  scripts: args.scripts ?? /* @__PURE__ */ new Set(),
@@ -149,6 +154,23 @@ function createResult(args) {
149
154
  }
150
155
  return void 0;
151
156
  },
157
+ get currentLocale() {
158
+ if (currentLocale) {
159
+ return currentLocale;
160
+ }
161
+ if (args.locales) {
162
+ currentLocale = computeCurrentLocale(
163
+ request,
164
+ args.locales,
165
+ args.routingStrategy,
166
+ args.defaultLocale
167
+ );
168
+ if (currentLocale) {
169
+ return currentLocale;
170
+ }
171
+ }
172
+ return void 0;
173
+ },
152
174
  params,
153
175
  props,
154
176
  locals,
@@ -236,7 +236,8 @@ function createRouteManifest({ settings, cwd, fsMod }, logger) {
236
236
  component,
237
237
  generate,
238
238
  pathname: pathname || void 0,
239
- prerender
239
+ prerender,
240
+ fallbackRoutes: []
240
241
  });
241
242
  }
242
243
  });
@@ -290,7 +291,8 @@ This route collides with: "${collision.component}".`
290
291
  component,
291
292
  generate,
292
293
  pathname: pathname || void 0,
293
- prerender: prerenderInjected ?? prerender
294
+ prerender: prerenderInjected ?? prerender,
295
+ fallbackRoutes: []
294
296
  });
295
297
  });
296
298
  Object.entries(settings.config.redirects).forEach(([from, to]) => {
@@ -315,7 +317,8 @@ This route collides with: "${collision.component}".`
315
317
  pathname: pathname || void 0,
316
318
  prerender: false,
317
319
  redirect: to,
318
- redirectRoute: routes.find((r) => r.route === to)
320
+ redirectRoute: routes.find((r) => r.route === to),
321
+ fallbackRoutes: []
319
322
  };
320
323
  const lastSegmentIsDynamic = (r) => !!r.segments.at(-1)?.at(-1)?.dynamic;
321
324
  const redirBase = path.posix.dirname(route);
@@ -414,33 +417,35 @@ This route collides with: "${collision.component}".`
414
417
  if (!hasRoute) {
415
418
  let pathname;
416
419
  let route;
417
- if (fallbackToLocale === i18n.defaultLocale) {
420
+ if (fallbackToLocale === i18n.defaultLocale && i18n.routingStrategy === "prefix-other-locales") {
418
421
  if (fallbackToRoute.pathname) {
419
422
  pathname = `/${fallbackFromLocale}${fallbackToRoute.pathname}`;
420
423
  }
421
424
  route = `/${fallbackFromLocale}${fallbackToRoute.route}`;
422
425
  } else {
423
- pathname = fallbackToRoute.pathname?.replace(
424
- `/${fallbackToLocale}`,
425
- `/${fallbackFromLocale}`
426
- );
427
- route = fallbackToRoute.route.replace(
428
- `/${fallbackToLocale}`,
429
- `/${fallbackFromLocale}`
430
- );
426
+ pathname = fallbackToRoute.pathname?.replace(`/${fallbackToLocale}/`, `/${fallbackFromLocale}/`).replace(`/${fallbackToLocale}`, `/${fallbackFromLocale}`);
427
+ route = fallbackToRoute.route.replace(`/${fallbackToLocale}`, `/${fallbackFromLocale}`).replace(`/${fallbackToLocale}/`, `/${fallbackFromLocale}/`);
431
428
  }
432
429
  const segments = removeLeadingForwardSlash(route).split(path.posix.sep).filter(Boolean).map((s) => {
433
430
  validateSegment(s);
434
431
  return getParts(s, route);
435
432
  });
436
- routes.push({
437
- ...fallbackToRoute,
438
- pathname,
439
- route,
440
- segments,
441
- pattern: getPattern(segments, config, config.trailingSlash),
442
- type: "fallback"
443
- });
433
+ const generate = getRouteGenerator(segments, config.trailingSlash);
434
+ const index = routes.findIndex((r) => r === fallbackToRoute);
435
+ if (index) {
436
+ const fallbackRoute = {
437
+ ...fallbackToRoute,
438
+ pathname,
439
+ route,
440
+ segments,
441
+ generate,
442
+ pattern: getPattern(segments, config, config.trailingSlash),
443
+ type: "fallback",
444
+ fallbackRoutes: []
445
+ };
446
+ const routeData = routes[index];
447
+ routeData.fallbackRoutes.push(fallbackRoute);
448
+ }
444
449
  }
445
450
  }
446
451
  }
@@ -5,6 +5,9 @@ function serializeRouteData(routeData, trailingSlash) {
5
5
  generate: void 0,
6
6
  pattern: routeData.pattern.source,
7
7
  redirectRoute: routeData.redirectRoute ? serializeRouteData(routeData.redirectRoute, trailingSlash) : void 0,
8
+ fallbackRoutes: routeData.fallbackRoutes.map((fallbackRoute) => {
9
+ return serializeRouteData(fallbackRoute, trailingSlash);
10
+ }),
8
11
  _meta: { trailingSlash }
9
12
  };
10
13
  }
@@ -20,7 +23,10 @@ function deserializeRouteData(rawRouteData) {
20
23
  segments: rawRouteData.segments,
21
24
  prerender: rawRouteData.prerender,
22
25
  redirect: rawRouteData.redirect,
23
- redirectRoute: rawRouteData.redirectRoute ? deserializeRouteData(rawRouteData.redirectRoute) : void 0
26
+ redirectRoute: rawRouteData.redirectRoute ? deserializeRouteData(rawRouteData.redirectRoute) : void 0,
27
+ fallbackRoutes: rawRouteData.fallbackRoutes.map((fallback) => {
28
+ return deserializeRouteData(fallback);
29
+ })
24
30
  };
25
31
  }
26
32
  export {
@@ -1,5 +1,8 @@
1
1
  function matchRoute(pathname, manifest) {
2
- return manifest.routes.find((route) => route.pattern.test(decodeURI(pathname)));
2
+ const decodedPathname = decodeURI(pathname);
3
+ return manifest.routes.find((route) => {
4
+ return route.pattern.test(decodedPathname) || route.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(decodedPathname));
5
+ });
3
6
  }
4
7
  function matchAllRoutes(pathname, manifest) {
5
8
  return manifest.routes.filter((route) => route.pattern.test(pathname));
@@ -16,7 +16,7 @@ function getParams(array) {
16
16
  }
17
17
  function stringifyParams(params, route) {
18
18
  const validatedParams = Object.entries(params).reduce((acc, next) => {
19
- validateGetStaticPathsParameter(next, route.component);
19
+ validateGetStaticPathsParameter(next, route.route);
20
20
  const [key, value] = next;
21
21
  if (value !== void 0) {
22
22
  acc[key] = typeof value === "string" ? trimSlashes(value) : value.toString();
@@ -1,2 +1,7 @@
1
1
  import type { MiddlewareEndpointHandler, SSRManifest } from '../@types/astro.js';
2
+ import type { PipelineHookFunction } from '../core/pipeline.js';
2
3
  export declare function createI18nMiddleware(i18n: SSRManifest['i18n'], base: SSRManifest['base'], trailingSlash: SSRManifest['trailingSlash']): MiddlewareEndpointHandler | undefined;
4
+ /**
5
+ * This pipeline hook attaches a `RouteData` object to the `Request`
6
+ */
7
+ export declare const i18nPipelineHook: PipelineHookFunction;
@@ -1,4 +1,5 @@
1
1
  import { appendForwardSlash, joinPaths } from "@astrojs/internal-helpers/path";
2
+ const routeDataSymbol = Symbol.for("astro.routeData");
2
3
  function checkIsLocaleFree(pathname, locales) {
3
4
  for (const locale of locales) {
4
5
  if (pathname.includes(`/${locale}`)) {
@@ -15,8 +16,14 @@ function createI18nMiddleware(i18n, base, trailingSlash) {
15
16
  if (!i18n) {
16
17
  return await next();
17
18
  }
18
- const { locales, defaultLocale, fallback } = i18n;
19
+ const routeData = Reflect.get(context.request, routeDataSymbol);
20
+ if (routeData) {
21
+ if (routeData.type !== "page" && routeData.type !== "fallback") {
22
+ return await next();
23
+ }
24
+ }
19
25
  const url = context.url;
26
+ const { locales, defaultLocale, fallback, routingStrategy } = i18n;
20
27
  const response = await next();
21
28
  if (response instanceof Response) {
22
29
  const separators = url.pathname.split("/");
@@ -49,7 +56,7 @@ function createI18nMiddleware(i18n, base, trailingSlash) {
49
56
  if (urlLocale && fallbackKeys.includes(urlLocale)) {
50
57
  const fallbackLocale = fallback[urlLocale];
51
58
  let newPathname;
52
- if (fallbackLocale === defaultLocale) {
59
+ if (fallbackLocale === defaultLocale && routingStrategy === "prefix-other-locales") {
53
60
  newPathname = url.pathname.replace(`/${urlLocale}`, ``);
54
61
  } else {
55
62
  newPathname = url.pathname.replace(`/${urlLocale}`, `/${fallbackLocale}`);
@@ -61,6 +68,10 @@ function createI18nMiddleware(i18n, base, trailingSlash) {
61
68
  return response;
62
69
  };
63
70
  }
71
+ const i18nPipelineHook = (ctx) => {
72
+ Reflect.set(ctx.request, routeDataSymbol, ctx.route);
73
+ };
64
74
  export {
65
- createI18nMiddleware
75
+ createI18nMiddleware,
76
+ i18nPipelineHook
66
77
  };
@@ -13,7 +13,8 @@ document.addEventListener("DOMContentLoaded", async () => {
13
13
  { DevOverlayHighlight },
14
14
  { DevOverlayTooltip },
15
15
  { DevOverlayWindow },
16
- { DevOverlayToggle }
16
+ { DevOverlayToggle },
17
+ { getIconElement, isDefinedIcon }
17
18
  ] = await Promise.all([
18
19
  // @ts-expect-error
19
20
  import("astro:dev-overlay"),
@@ -26,7 +27,8 @@ document.addEventListener("DOMContentLoaded", async () => {
26
27
  import("./ui-library/highlight.js"),
27
28
  import("./ui-library/tooltip.js"),
28
29
  import("./ui-library/window.js"),
29
- import("./ui-library/toggle.js")
30
+ import("./ui-library/toggle.js"),
31
+ import("./ui-library/icons.js")
30
32
  ]);
31
33
  customElements.define("astro-dev-overlay", AstroDevOverlay);
32
34
  customElements.define("astro-dev-overlay-window", DevOverlayWindow);
@@ -43,6 +45,7 @@ document.addEventListener("DOMContentLoaded", async () => {
43
45
  builtIn,
44
46
  active: false,
45
47
  status: "loading",
48
+ notification: { state: false },
46
49
  eventTarget
47
50
  };
48
51
  eventTarget.addEventListener("toggle-notification", (evt) => {
@@ -53,7 +56,8 @@ document.addEventListener("DOMContentLoaded", async () => {
53
56
  if (evt instanceof CustomEvent) {
54
57
  newState = evt.detail.state ?? true;
55
58
  }
56
- if (settings.config.showPluginNotifications === false) {
59
+ plugin.notification.state = newState;
60
+ if (settings.config.disablePluginNotification === false) {
57
61
  target.querySelector(".notification")?.toggleAttribute("data-active", newState);
58
62
  }
59
63
  });
@@ -66,11 +70,148 @@ document.addEventListener("DOMContentLoaded", async () => {
66
70
  });
67
71
  return plugin;
68
72
  };
73
+ const astromorePlugin = {
74
+ id: "astro:more",
75
+ name: "More",
76
+ icon: "dots-three",
77
+ init(canvas, eventTarget) {
78
+ const hiddenPlugins = plugins.filter((p) => !p.builtIn).slice(overlay.customPluginsToShow);
79
+ createDropdown();
80
+ document.addEventListener("astro:after-swap", createDropdown);
81
+ function createDropdown() {
82
+ const style = document.createElement("style");
83
+ style.innerHTML = `
84
+ #dropdown {
85
+ background: rgba(19, 21, 26, 1);
86
+ border: 1px solid rgba(52, 56, 65, 1);
87
+ border-radius: 12px;
88
+ box-shadow: 0px 0px 0px 0px rgba(19, 21, 26, 0.30), 0px 1px 2px 0px rgba(19, 21, 26, 0.29), 0px 4px 4px 0px rgba(19, 21, 26, 0.26), 0px 10px 6px 0px rgba(19, 21, 26, 0.15), 0px 17px 7px 0px rgba(19, 21, 26, 0.04), 0px 26px 7px 0px rgba(19, 21, 26, 0.01);
89
+ width: 180px;
90
+ padding: 8px;
91
+ z-index: 9999999999;
92
+ }
93
+
94
+ .notification {
95
+ display: none;
96
+ position: absolute;
97
+ top: -4px;
98
+ right: -6px;
99
+ width: 8px;
100
+ height: 8px;
101
+ border-radius: 9999px;
102
+ border: 1px solid rgba(19, 21, 26, 1);
103
+ background: #B33E66;
104
+ }
105
+
106
+ .notification[data-active] {
107
+ display: block;
108
+ }
109
+
110
+ #dropdown button {
111
+ border: 0;
112
+ background: transparent;
113
+ color: white;
114
+ font-family: system-ui, sans-serif;
115
+ font-size: 16px;
116
+ line-height: 1.2;
117
+ white-space: nowrap;
118
+ text-decoration: none;
119
+ margin: 0;
120
+ display: flex;
121
+ align-items: center;
122
+ width: 100%;
123
+ padding: 8px;
124
+ border-radius: 8px;
125
+ }
126
+
127
+ #dropdown button:hover, #dropdown button:focus-visible {
128
+ background: rgba(27, 30, 36, 1);
129
+ cursor: pointer;
130
+ }
131
+
132
+ #dropdown button.active {
133
+ background: rgba(71, 78, 94, 1);
134
+ }
135
+
136
+ #dropdown .icon {
137
+ position: relative;
138
+ height: 24px;
139
+ width: 24px;
140
+ margin-right: 0.5em;
141
+ }
142
+
143
+ #dropdown .icon svg {
144
+ max-height: 100%;
145
+ max-width: 100%;
146
+ }
147
+ `;
148
+ canvas.append(style);
149
+ const dropdown = document.createElement("div");
150
+ dropdown.id = "dropdown";
151
+ for (const plugin of hiddenPlugins) {
152
+ const buttonContainer = document.createElement("div");
153
+ buttonContainer.classList.add("item");
154
+ const button = document.createElement("button");
155
+ button.setAttribute("data-plugin-id", plugin.id);
156
+ const iconContainer = document.createElement("div");
157
+ const iconElement = getPluginIcon(plugin.icon);
158
+ iconContainer.append(iconElement);
159
+ const notification = document.createElement("div");
160
+ notification.classList.add("notification");
161
+ iconContainer.append(notification);
162
+ iconContainer.classList.add("icon");
163
+ button.append(iconContainer);
164
+ button.append(document.createTextNode(plugin.name));
165
+ button.addEventListener("click", () => {
166
+ overlay.togglePluginStatus(plugin);
167
+ });
168
+ buttonContainer.append(button);
169
+ dropdown.append(buttonContainer);
170
+ eventTarget.addEventListener("plugin-toggled", positionDropdown);
171
+ window.addEventListener("resize", positionDropdown);
172
+ plugin.eventTarget.addEventListener("toggle-notification", (evt) => {
173
+ if (!(evt instanceof CustomEvent))
174
+ return;
175
+ if (settings.config.disablePluginNotification === false) {
176
+ notification.toggleAttribute("data-active", evt.detail.state ?? true);
177
+ }
178
+ eventTarget.dispatchEvent(
179
+ new CustomEvent("toggle-notification", {
180
+ detail: {
181
+ state: hiddenPlugins.some((p) => p.notification.state === true)
182
+ }
183
+ })
184
+ );
185
+ });
186
+ }
187
+ canvas.append(dropdown);
188
+ function getPluginIcon(icon) {
189
+ if (isDefinedIcon(icon)) {
190
+ return getIconElement(icon);
191
+ }
192
+ return icon;
193
+ }
194
+ function positionDropdown() {
195
+ const moreButtonRect = overlay.shadowRoot.querySelector('[data-plugin-id="astro:more"]')?.getBoundingClientRect();
196
+ const dropdownRect = dropdown.getBoundingClientRect();
197
+ if (moreButtonRect && dropdownRect) {
198
+ dropdown.style.position = "absolute";
199
+ dropdown.style.top = `${moreButtonRect.top - dropdownRect.height - 12}px`;
200
+ dropdown.style.left = `${moreButtonRect.left + moreButtonRect.width - dropdownRect.width}px`;
201
+ }
202
+ }
203
+ }
204
+ }
205
+ };
69
206
  const customPluginsDefinitions = await loadDevOverlayPlugins();
70
207
  const plugins = [
71
- ...[astroDevToolPlugin, astroXrayPlugin, astroAuditPlugin, astroSettingsPlugin].map(
72
- (pluginDef) => preparePlugin(pluginDef, true)
73
- ),
208
+ ...[
209
+ astroDevToolPlugin,
210
+ astroXrayPlugin,
211
+ astroAuditPlugin,
212
+ astroSettingsPlugin,
213
+ astromorePlugin
214
+ ].map((pluginDef) => preparePlugin(pluginDef, true)),
74
215
  ...customPluginsDefinitions.map((pluginDef) => preparePlugin(pluginDef, false))
75
216
  ];
76
217
  overlay.plugins = plugins;