alinea 2.0.0-preview.4 → 2.0.0-preview.5

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 (164) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/.init/src/cms.ts +1 -4
  3. package/dist/LICENSES.md +107 -107
  4. package/dist/adapter/next/cms.js +5 -1
  5. package/dist/adapter/next/handler.js +35 -6
  6. package/dist/adapter/next/previews.js +7 -1
  7. package/dist/adapter/next/syncCheck.d.ts +12 -0
  8. package/dist/adapter/next/syncCheck.js +59 -0
  9. package/dist/adapter/next/with-alinea.d.ts +0 -8
  10. package/dist/adapter/next/with-alinea.js +43 -19
  11. package/dist/adapter/vite/plugin.js +9 -4
  12. package/dist/backend/Handler.js +106 -0
  13. package/dist/backend/api/ProxyMedia.d.ts +3 -0
  14. package/dist/backend/api/ProxyMedia.js +39 -0
  15. package/dist/backend/api/S3Uploads.d.ts +1 -1
  16. package/dist/backend/router/Router.js +2 -0
  17. package/dist/backend/util/Syncable.js +1 -1
  18. package/dist/bundled.d.ts +164 -70
  19. package/dist/chunks/{chunk-XMOVGDFI.js → chunk-A6M3PNJT.js} +1 -1
  20. package/dist/cli/Generate.js +1 -3
  21. package/dist/cli/Serve.js +3 -2
  22. package/dist/cli/bin.js +1 -1
  23. package/dist/cli/generate/GenerateDashboard.js +1 -0
  24. package/dist/cli/serve/CreateLocalServer.d.ts +1 -1
  25. package/dist/cli/serve/CreateLocalServer.js +42 -34
  26. package/dist/cli/static/init/cms.js +1 -4
  27. package/dist/cli/util/ContentType.d.ts +1 -0
  28. package/dist/cli/util/ContentType.js +36 -0
  29. package/dist/cloud/CloudHandshake.d.ts +6 -0
  30. package/dist/cloud/CloudHandshake.js +67 -10
  31. package/dist/cloud/CloudRemote.js +24 -11
  32. package/dist/components/Calendar.js +4 -4
  33. package/dist/components/Checkbox.js +3 -3
  34. package/dist/components/ColorSwatchPicker.js +2 -2
  35. package/dist/components/ComboBox.js +9 -9
  36. package/dist/components/DateField.js +4 -4
  37. package/dist/components/DatePicker.js +3 -3
  38. package/dist/components/DateRangePicker.js +8 -8
  39. package/dist/components/Disclosure.js +7 -7
  40. package/dist/components/Label.js +4 -4
  41. package/dist/components/List.d.ts +1 -1
  42. package/dist/components/List.js +35 -34
  43. package/dist/components/Menu.js +4 -4
  44. package/dist/components/Modal.js +2 -2
  45. package/dist/components/MultipleSelect.js +23 -23
  46. package/dist/components/NumberField.js +6 -6
  47. package/dist/components/ProgressCircle.js +1 -1
  48. package/dist/components/SearchField.js +7 -7
  49. package/dist/components/Select.js +9 -9
  50. package/dist/components/Surface.js +2 -2
  51. package/dist/components/Table.js +7 -7
  52. package/dist/components/Tabs.js +2 -2
  53. package/dist/components/TagGroup.js +2 -2
  54. package/dist/components/TextField.js +6 -6
  55. package/dist/components/TimeField.js +3 -3
  56. package/dist/components/Toggle.js +2 -2
  57. package/dist/components/Toolbar.js +2 -2
  58. package/dist/components/Tooltip.js +2 -2
  59. package/dist/components/Tree.js +6 -6
  60. package/dist/core/Config.d.ts +10 -3
  61. package/dist/core/Config.js +16 -1
  62. package/dist/core/Graph.d.ts +2 -2
  63. package/dist/core/Role.js +16 -14
  64. package/dist/core/TextDoc.d.ts +2 -0
  65. package/dist/core/TextDoc.js +1 -0
  66. package/dist/core/Workspace.d.ts +6 -5
  67. package/dist/core/db/EntryIndex.d.ts +3 -0
  68. package/dist/core/db/EntryIndex.js +62 -0
  69. package/dist/core/db/EntryTransaction.js +33 -12
  70. package/dist/core/db/EntryUrlConflictError.js +1 -1
  71. package/dist/core/db/LinkResolver.d.ts +8 -2
  72. package/dist/core/db/LinkResolver.js +27 -5
  73. package/dist/core/field/RichTextField.js +16 -10
  74. package/dist/core/media/MediaLocation.d.ts +9 -15
  75. package/dist/core/media/MediaLocation.js +29 -19
  76. package/dist/core/media/MediaTypes.js +1 -2
  77. package/dist/dashboard/App.js +1 -1
  78. package/dist/dashboard/app/AccessDenied.js +2 -2
  79. package/dist/dashboard/app/ActivityStatus.js +14 -13
  80. package/dist/dashboard/app/AppShell.js +1 -1
  81. package/dist/dashboard/app/AuthView.js +9 -9
  82. package/dist/dashboard/app/Badge.js +2 -2
  83. package/dist/dashboard/app/CompactField.js +13 -13
  84. package/dist/dashboard/app/DashboardErrorBoundary.js +4 -4
  85. package/dist/dashboard/app/EntryFields.d.ts +3 -1
  86. package/dist/dashboard/app/EntryFields.js +11 -8
  87. package/dist/dashboard/app/EntryHeader.js +117 -85
  88. package/dist/dashboard/app/EntryHeaderActions.d.ts +21 -4
  89. package/dist/dashboard/app/EntryHeaderActions.js +48 -27
  90. package/dist/dashboard/app/EntryReferences.js +9 -9
  91. package/dist/dashboard/app/EntrySidebar.js +12 -12
  92. package/dist/dashboard/app/EntrySidebarPreview.js +11 -11
  93. package/dist/dashboard/app/EntryTranslationBanner.d.ts +3 -1
  94. package/dist/dashboard/app/EntryTranslationBanner.js +24 -21
  95. package/dist/dashboard/app/Explorer.d.ts +4 -2
  96. package/dist/dashboard/app/Explorer.js +50 -46
  97. package/dist/dashboard/app/ExplorerCards.js +28 -28
  98. package/dist/dashboard/app/ExplorerFileCard.js +19 -19
  99. package/dist/dashboard/app/ExplorerList.js +12 -13
  100. package/dist/dashboard/app/ExplorerModal.js +5 -5
  101. package/dist/dashboard/app/ExplorerTable.js +46 -46
  102. package/dist/dashboard/app/LinkPicker.js +5 -5
  103. package/dist/dashboard/app/LocaleMenu.d.ts +7 -0
  104. package/dist/dashboard/app/LocaleMenu.js +26 -10
  105. package/dist/dashboard/app/ReadOnlyBadge.d.ts +4 -0
  106. package/dist/dashboard/app/ReadOnlyBadge.js +33 -0
  107. package/dist/dashboard/app/SidebarTree.d.ts +3 -6
  108. package/dist/dashboard/app/SidebarTree.js +54 -49
  109. package/dist/dashboard/app/WorkspaceMenu.js +12 -12
  110. package/dist/dashboard/app/WorkspaceRoots.js +24 -24
  111. package/dist/dashboard/app/editor/FileEditor.d.ts +2 -3
  112. package/dist/dashboard/app/editor/FileEditor.js +31 -34
  113. package/dist/dashboard/app/editor/FilePreview.js +6 -6
  114. package/dist/dashboard/app/modals/CreateEntry.js +5 -5
  115. package/dist/dashboard/app/pages/EntryPage.js +73 -30
  116. package/dist/dashboard/app/pages/SplashPage.js +47 -47
  117. package/dist/dashboard/app/pages/UsersPage.js +34 -34
  118. package/dist/dashboard/app/ui/DashboardModal.js +18 -18
  119. package/dist/dashboard/app/ui/Rail.js +3 -3
  120. package/dist/dashboard/app/ui/Sidebar.js +2 -2
  121. package/dist/dashboard/atoms/activity.d.ts +0 -1
  122. package/dist/dashboard/atoms/activity.js +1 -2
  123. package/dist/dashboard/atoms/editor.d.ts +2 -1
  124. package/dist/dashboard/atoms/editor.js +4 -2
  125. package/dist/dashboard/atoms/entry.d.ts +2 -0
  126. package/dist/dashboard/atoms/entry.js +32 -14
  127. package/dist/dashboard/atoms/explorer.d.ts +4 -3
  128. package/dist/dashboard/atoms/explorer.js +61 -22
  129. package/dist/dashboard/atoms/link.d.ts +1 -1
  130. package/dist/dashboard/atoms/link.js +39 -55
  131. package/dist/dashboard/atoms/root.d.ts +7 -2
  132. package/dist/dashboard/atoms/root.js +51 -16
  133. package/dist/dashboard/atoms/upload.d.ts +1 -0
  134. package/dist/dashboard/atoms/upload.js +2 -1
  135. package/dist/dashboard/boot/DashboardWorker.js +1 -0
  136. package/dist/dashboard/fixture/cms.js +1 -4
  137. package/dist/dashboard/hook/UseSaveShortcut.d.ts +1 -0
  138. package/dist/dashboard/hook/UseSaveShortcut.js +24 -0
  139. package/dist/dashboard/hooks.d.ts +1 -1
  140. package/dist/dashboard/hooks.js +12 -3
  141. package/dist/field/code/CodeField.view.js +9 -9
  142. package/dist/field/link/ImageLink.js +1 -3
  143. package/dist/field/link/LinkField.view.js +84 -58
  144. package/dist/field/list/ListField.view.js +13 -13
  145. package/dist/field/metadata/MetadataField.d.ts +3 -3
  146. package/dist/field/metadata/MetadataField.view.js +24 -17
  147. package/dist/field/object/ObjectField.view.js +11 -4
  148. package/dist/field/richtext/PickTextLink.js +5 -2
  149. package/dist/field/richtext/ReferenceLink.d.ts +2 -0
  150. package/dist/field/richtext/ReferenceLink.js +5 -0
  151. package/dist/field/richtext/RichTextBlock.js +2 -2
  152. package/dist/field/richtext/RichTextExtensions.js +22 -22
  153. package/dist/field/richtext/RichTextField.view.js +4 -4
  154. package/dist/field/richtext/extensions/Link.d.ts +1 -0
  155. package/dist/field/richtext/extensions/Link.js +1 -0
  156. package/dist/field/time/TimeField.d.ts +0 -2
  157. package/dist/field/time/TimeField.view.js +1 -1
  158. package/dist/index.css +44 -38
  159. package/dist/next.cjs +96 -19
  160. package/dist/picker/entry/EntryPicker.js +10 -12
  161. package/dist/picker/entry/EntryReference.d.ts +2 -0
  162. package/dist/picker/entry/EntryReference.js +1 -0
  163. package/dist/types.d.ts +0 -1
  164. package/package.json +1 -1
@@ -3,6 +3,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
3
3
  // src/adapter/vite/plugin.ts
4
4
  import { nodeHandler } from "alinea/backend/router/NodeHandler";
5
5
  import { createDevServer } from "alinea/cli/serve/DevServer";
6
+ import { Config } from "alinea/core/Config";
6
7
  function createFallbackUrl(server) {
7
8
  const address = server.httpServer?.address();
8
9
  const protocol = server.config.server.https ? "https" : "http";
@@ -28,19 +29,23 @@ function waitForServerUrl(server) {
28
29
  server.httpServer?.once("listening", resolveUrl);
29
30
  });
30
31
  }
31
- function isAlineaRequest(request) {
32
+ function isAlineaRequest(request, adminPath) {
32
33
  const url = new URL(request.url);
33
- return url.pathname === "/admin" || url.pathname.startsWith("/admin/") || url.pathname === "/api" || url.pathname.startsWith("/api/") || url.pathname === "/~dev" || url.pathname.startsWith("/~dev/") || url.search.startsWith("?/upload");
34
+ return url.pathname === adminPath || url.pathname.startsWith(`${adminPath}/`) || url.pathname === "/api" || url.pathname.startsWith("/api/") || url.pathname === "/~dev" || url.pathname.startsWith("/~dev/") || url.search.startsWith("?/upload");
34
35
  }
35
36
  function alineaPlugin(dir) {
36
37
  let devServer;
38
+ let adminPath = "/admin";
37
39
  return {
38
40
  name: "alinea-plugin",
39
41
  configureServer(server) {
40
42
  const serverUrl = waitForServerUrl(server);
41
43
  const devServerPromise = createDevServer(dir, {
42
44
  cmd: "dev",
43
- dashboardUrl: serverUrl
45
+ dashboardUrl: serverUrl,
46
+ onAfterGenerate(_, config) {
47
+ adminPath = Config.adminPath(config);
48
+ }
44
49
  }).then((result) => {
45
50
  devServer = result;
46
51
  return result;
@@ -50,7 +55,7 @@ function alineaPlugin(dir) {
50
55
  });
51
56
  server.middlewares.use(
52
57
  nodeHandler(async (request) => {
53
- if (!isAlineaRequest(request)) return void 0;
58
+ if (!isAlineaRequest(request, adminPath)) return void 0;
54
59
  const localServer = await devServerPromise;
55
60
  return localServer.handle(request);
56
61
  })
@@ -17,16 +17,20 @@ import {
17
17
  BLOB_SEQUENCE_CONTENT_TYPE,
18
18
  encodeBlobSequence
19
19
  } from "alinea/core/BlobTransport";
20
+ import { Config } from "alinea/core/Config";
20
21
  import { developmentKeyHeader } from "alinea/core/Connection";
21
22
  import { ErrorCode, HttpError } from "alinea/core/HttpError";
23
+ import { MediaLocation } from "alinea/core/media/MediaLocation";
22
24
  import { assertUploadSize } from "alinea/core/media/UploadLimits";
23
25
  import { Permission, Policy } from "alinea/core/Role";
24
26
  import { getScope } from "alinea/core/Scope";
25
27
  import { ShaMismatchError } from "alinea/core/source/ShaMismatchError";
26
28
  import { base64 } from "alinea/core/util/Encoding";
27
29
  import { isRecord } from "alinea/core/util/Objects";
30
+ import { isAbsolute, normalize } from "alinea/core/util/Paths";
28
31
  import { InvalidCredentialsError, MissingCredentialsError } from "./Auth.js";
29
32
  import { HandleAction } from "./HandleAction.js";
33
+ import { isRedirectStatus, proxyMediaUrl } from "./api/ProxyMedia.js";
30
34
  import { applyPreview, decodePreviewRequest } from "./resolver/ParsePreview.js";
31
35
  import { compressResponse } from "./router/Router.js";
32
36
  import { createThrottledSync } from "./util/Syncable.js";
@@ -75,6 +79,73 @@ function createHandler({
75
79
  }
76
80
  });
77
81
  }
82
+ if (!params.has("action") && params.has("file") && (request.method === "GET" || request.method === "HEAD")) {
83
+ const file = string(params.get("file"));
84
+ const proxy = params.get("delivery") === "proxy";
85
+ const normalized = normalize(file);
86
+ if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../"))
87
+ throw new HttpError(400, "Invalid file path");
88
+ await periodicSync(cnx);
89
+ const requestedUrl = Config.filePathname(cms.config, normalized);
90
+ const entry = local.index.findByUrl(
91
+ requestedUrl,
92
+ (entry2) => entry2.type === "MediaFile" && entry2.status === "published" && entry2.main
93
+ );
94
+ if (!entry) return new Response("Not found", { status: 404 });
95
+ if (entry.url !== requestedUrl && !proxy)
96
+ return new Response(null, {
97
+ status: 308,
98
+ headers: { location: entry.url }
99
+ });
100
+ const wasBuilt = local.index.initialSync?.has(entry.filePath) ?? false;
101
+ const previewUrl = entry.data.previewUrl;
102
+ const location = entry.data.location;
103
+ if (typeof location !== "string")
104
+ return new Response("Not found", { status: 404 });
105
+ const previewSource = typeof previewUrl === "string" && previewUrl ? new URL(previewUrl, request.url).href : void 0;
106
+ async function proxyMedia(source2, cacheControl, isAllowed) {
107
+ const previewEntryId = uploadPreviewEntryId(
108
+ source2,
109
+ context.handlerUrl
110
+ );
111
+ let response;
112
+ if (previewEntryId && cnx.previewUpload) {
113
+ response = await cnx.previewUpload(previewEntryId);
114
+ } else {
115
+ response = await proxyMediaUrl(
116
+ request,
117
+ new URL(source2, request.url),
118
+ isAllowed
119
+ );
120
+ }
121
+ return mediaResponse(request, response, cacheControl);
122
+ }
123
+ if (!wasBuilt && previewSource) {
124
+ if (proxy) return proxyMedia(previewSource, "private, no-store");
125
+ return redirectFile(previewSource, "private, no-store");
126
+ }
127
+ const source = MediaLocation.sourceUrl(
128
+ cms.config,
129
+ entry.workspace,
130
+ location
131
+ );
132
+ if (!source || source === requestedUrl)
133
+ return new Response("Not found", { status: 404 });
134
+ if (!proxy) return redirectFile(source, "public, max-age=60");
135
+ const configuredBase = Config.baseUrl(
136
+ cms.config,
137
+ context.isDev ? "development" : "production"
138
+ );
139
+ const deliveryBase = configuredBase ?? context.handlerUrl;
140
+ const sourceUrl = new URL(source, deliveryBase);
141
+ const deliveryOrigin = new URL(deliveryBase).origin;
142
+ const handlerPath = context.handlerUrl.pathname;
143
+ return proxyMedia(
144
+ sourceUrl.href,
145
+ "public, max-age=60",
146
+ (url2) => url2.origin === deliveryOrigin && url2.pathname !== handlerPath && !url2.pathname.startsWith(`${handlerPath}/`)
147
+ );
148
+ }
78
149
  const action = params.get("action");
79
150
  const expectJson = () => {
80
151
  const acceptsJson = request.headers.get("accept")?.includes("application/json");
@@ -347,6 +418,41 @@ function createHandler({
347
418
  }
348
419
  };
349
420
  }
421
+ function redirectFile(location, cacheControl) {
422
+ return new Response(null, {
423
+ status: 307,
424
+ headers: { "cache-control": cacheControl, location }
425
+ });
426
+ }
427
+ function uploadPreviewEntryId(previewSource, handlerUrl) {
428
+ const previewUrl = new URL(previewSource);
429
+ if (previewUrl.origin !== handlerUrl.origin || previewUrl.pathname !== handlerUrl.pathname || previewUrl.searchParams.get("action") !== HandleAction.Upload)
430
+ return;
431
+ return previewUrl.searchParams.get("entryId") ?? void 0;
432
+ }
433
+ function mediaResponse(request, upstream, cacheControl) {
434
+ if (isRedirectStatus(upstream.status))
435
+ return new Response("Media backend returned a redirect", { status: 502 });
436
+ const responseHeaders = new Headers({
437
+ "cache-control": upstream.ok || upstream.status === 304 ? cacheControl : "no-store"
438
+ });
439
+ for (const name of [
440
+ "accept-ranges",
441
+ "content-disposition",
442
+ "content-length",
443
+ "content-range",
444
+ "content-type",
445
+ "etag",
446
+ "last-modified"
447
+ ]) {
448
+ const value = upstream.headers.get(name);
449
+ if (value) responseHeaders.set(name, value);
450
+ }
451
+ return new Response(request.method === "HEAD" ? null : upstream.body, {
452
+ status: upstream.status,
453
+ headers: responseHeaders
454
+ });
455
+ }
350
456
  function parseUser(input) {
351
457
  if (!isRecord(input)) throw new HttpError(400, "Expected user object");
352
458
  const { sub, name, email, roles } = input;
@@ -0,0 +1,3 @@
1
+ import { type Request, Response } from '@alinea/iso';
2
+ export declare function proxyMediaUrl(request: Request, source: URL, isAllowed?: (url: URL) => boolean): Promise<Response>;
3
+ export declare function isRedirectStatus(status: number): boolean;
@@ -0,0 +1,39 @@
1
+ import "../../chunks/chunk-CQ52GEHY.js";
2
+
3
+ // src/backend/api/ProxyMedia.ts
4
+ import { Headers, Response } from "@alinea/iso";
5
+ async function proxyMediaUrl(request, source, isAllowed = (url) => url.origin === source.origin) {
6
+ const headers = new Headers();
7
+ for (const name of [
8
+ "range",
9
+ "if-range",
10
+ "if-none-match",
11
+ "if-modified-since"
12
+ ]) {
13
+ const value = request.headers.get(name);
14
+ if (value) headers.set(name, value);
15
+ }
16
+ let current = source;
17
+ for (let redirects = 0; ; redirects++) {
18
+ if (!isAllowed(current))
19
+ return new Response("Invalid media source", { status: 502 });
20
+ const response = await fetch(current, {
21
+ method: request.method,
22
+ headers,
23
+ redirect: "manual",
24
+ signal: request.signal
25
+ });
26
+ const location = response.headers.get("location");
27
+ if (!isRedirectStatus(response.status) || !location) return response;
28
+ if (redirects === 3)
29
+ return new Response("Too many media redirects", { status: 502 });
30
+ current = new URL(location, current);
31
+ }
32
+ }
33
+ function isRedirectStatus(status) {
34
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
35
+ }
36
+ export {
37
+ isRedirectStatus,
38
+ proxyMediaUrl
39
+ };
@@ -13,7 +13,7 @@ export interface S3UploadsOptions {
13
13
  previewExpiresIn?: number;
14
14
  }
15
15
  interface S3PresignOptions {
16
- method: 'GET' | 'PUT';
16
+ method: 'GET' | 'HEAD' | 'PUT';
17
17
  url: URL;
18
18
  region: string;
19
19
  accessKeyId: string;
@@ -195,6 +195,8 @@ function callHandler(handler, input) {
195
195
  function compressResponse(request, response) {
196
196
  const body = response.body;
197
197
  if (!body) return response;
198
+ if (response.status === 206 || response.headers.has("content-range"))
199
+ return response;
198
200
  const isCompressed = response.headers.get("content-encoding");
199
201
  if (isCompressed) return response;
200
202
  const accept = request.headers.get("accept-encoding");
@@ -5,7 +5,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
5
5
 
6
6
  // src/backend/util/Syncable.ts
7
7
  function createThrottledSync() {
8
- let lastSync = Date.now();
8
+ let lastSync = 0;
9
9
  const limit = pLimit(1);
10
10
  return async function throttle(sync, intervalSeconds = 60) {
11
11
  return limit(async () => {