alinea 2.0.0-preview.0 → 2.0.0-preview.2

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 (237) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/LICENSES.md +134 -80
  3. package/dist/adapter/next/DevRemote.d.ts +3 -0
  4. package/dist/adapter/next/DevRemote.js +37 -0
  5. package/dist/adapter/next/ForwardCredentials.d.ts +1 -0
  6. package/dist/adapter/next/ForwardCredentials.js +17 -0
  7. package/dist/adapter/next/cms.js +86 -38
  8. package/dist/adapter/next/context.d.ts +1 -1
  9. package/dist/adapter/next/context.js +11 -3
  10. package/dist/adapter/next/handler.d.ts +2 -0
  11. package/dist/adapter/next/handler.js +23 -16
  12. package/dist/adapter/next/with-alinea.js +18 -16
  13. package/dist/backend/Handler.d.ts +14 -13
  14. package/dist/backend/Handler.js +70 -36
  15. package/dist/backend/resolver/ParsePreview.d.ts +8 -4
  16. package/dist/backend/resolver/ParsePreview.js +40 -43
  17. package/dist/backend/router/Router.d.ts +1 -0
  18. package/dist/backend/router/Router.js +21 -15
  19. package/dist/bundled.d.ts +369 -99
  20. package/dist/chunks/chunk-6UT4N4CO.js +919 -0
  21. package/dist/chunks/{chunk-LUBOGJCG.js → chunk-6V4TAD2N.js} +1 -1
  22. package/dist/chunks/{chunk-TL7ZW6BX.js → chunk-A3XXZBOQ.js} +2535 -2999
  23. package/dist/chunks/chunk-AC5ATYI3.js +1434 -0
  24. package/dist/chunks/{chunk-7ORQ7BYK.js → chunk-KMLLLZ6X.js} +202 -269
  25. package/dist/chunks/{chunk-IWN3T3FL.js → chunk-O7XWTMCB.js} +1 -1
  26. package/dist/chunks/{chunk-53DZYORR.js → chunk-PAYSVIYI.js} +76 -176
  27. package/dist/chunks/{chunk-YK3OSMDM.js → chunk-S4AU6SRS.js} +188 -158
  28. package/dist/chunks/{chunk-EQ3TDHYD.js → chunk-UVOEM4AH.js} +6 -3
  29. package/dist/chunks/{chunk-I7X64JTZ.js → chunk-VXZM6V4P.js} +4 -4
  30. package/dist/cli/Generate.js +3 -13
  31. package/dist/cli/Serve.js +7 -3
  32. package/dist/cli/bin.js +8 -5
  33. package/dist/cli/generate/CompileConfig.js +3 -2
  34. package/dist/cli/generate/CopyStaticFiles.js +2 -3
  35. package/dist/cli/generate/DevDB.d.ts +1 -1
  36. package/dist/cli/generate/DevDB.js +3 -5
  37. package/dist/cli/serve/CreateLocalServer.d.ts +1 -1
  38. package/dist/cli/serve/CreateLocalServer.js +3 -2
  39. package/dist/cli/serve/DevServer.d.ts +3 -2
  40. package/dist/cli/serve/DevServer.js +60 -3
  41. package/dist/cli/serve/ServeContext.d.ts +1 -0
  42. package/dist/cli/serve/StartServer.d.ts +1 -0
  43. package/dist/cli/serve/StartServer.js +6 -1
  44. package/dist/cli/util/EnsureLibs.js +2 -2
  45. package/dist/cli/util/EnsureNode.js +2 -2
  46. package/dist/cloud/CloudRemote.d.ts +2 -1
  47. package/dist/cloud/CloudRemote.js +3 -2
  48. package/dist/components/Breadcrumbs.js +8 -8
  49. package/dist/components/Button.js +6 -6
  50. package/dist/components/Calendar.js +11 -11
  51. package/dist/components/Checkbox.js +7 -7
  52. package/dist/components/CheckboxGroup.js +6 -6
  53. package/dist/components/ColorSwatch.js +6 -6
  54. package/dist/components/ColorSwatchPicker.js +6 -6
  55. package/dist/components/ComboBox.js +17 -17
  56. package/dist/components/DateField.js +9 -9
  57. package/dist/components/DatePicker.js +10 -10
  58. package/dist/components/DateRangePicker.js +14 -14
  59. package/dist/components/Dialog.js +6 -6
  60. package/dist/components/Disclosure.js +13 -13
  61. package/dist/components/DropZone.js +6 -6
  62. package/dist/components/Label.js +16 -16
  63. package/dist/components/Link.js +6 -6
  64. package/dist/components/List.js +34 -34
  65. package/dist/components/Menu.js +10 -10
  66. package/dist/components/Modal.js +8 -8
  67. package/dist/components/MultipleSelect.js +33 -33
  68. package/dist/components/NumberField.d.ts +2 -1
  69. package/dist/components/NumberField.js +21 -11
  70. package/dist/components/Popover.js +6 -6
  71. package/dist/components/ProgressCircle.js +10 -10
  72. package/dist/components/RadioGroup.js +6 -6
  73. package/dist/components/SearchField.js +11 -11
  74. package/dist/components/Select.js +13 -13
  75. package/dist/components/Surface.js +3 -3
  76. package/dist/components/Switch.js +6 -6
  77. package/dist/components/Table.js +13 -13
  78. package/dist/components/Tabs.js +8 -8
  79. package/dist/components/TagGroup.js +7 -7
  80. package/dist/components/TextField.js +10 -10
  81. package/dist/components/TimeField.js +6 -6
  82. package/dist/components/Toggle.js +6 -6
  83. package/dist/components/Toolbar.js +7 -7
  84. package/dist/components/Tooltip.js +10 -10
  85. package/dist/components/Tree.js +9 -9
  86. package/dist/core/BlobTransport.d.ts +5 -0
  87. package/dist/core/BlobTransport.js +79 -0
  88. package/dist/core/Client.d.ts +2 -1
  89. package/dist/core/Client.js +17 -12
  90. package/dist/core/Config.d.ts +4 -0
  91. package/dist/core/Config.js +6 -0
  92. package/dist/core/Connection.d.ts +4 -3
  93. package/dist/core/Connection.js +7 -0
  94. package/dist/core/Graph.d.ts +3 -4
  95. package/dist/core/ListRow.d.ts +2 -0
  96. package/dist/core/OrderBy.d.ts +7 -3
  97. package/dist/core/TextDoc.d.ts +11 -0
  98. package/dist/core/Trace.d.ts +8 -0
  99. package/dist/core/Trace.js +32 -0
  100. package/dist/core/Type.d.ts +7 -1
  101. package/dist/core/db/CommitRequest.d.ts +0 -3
  102. package/dist/core/db/CommitRequest.js +0 -9
  103. package/dist/core/db/EntryDB.js +1 -1
  104. package/dist/core/db/EntryIndex.d.ts +12 -2
  105. package/dist/core/db/EntryIndex.js +131 -22
  106. package/dist/core/db/EntryResolver.js +104 -29
  107. package/dist/core/db/EntryTransaction.d.ts +1 -2
  108. package/dist/core/db/EntryTransaction.js +29 -22
  109. package/dist/core/db/LocalDB.d.ts +5 -5
  110. package/dist/core/db/LocalDB.js +27 -12
  111. package/dist/core/field/RichTextField.d.ts +30 -3
  112. package/dist/core/field/RichTextField.js +124 -56
  113. package/dist/core/media/MediaAltField.d.ts +1 -0
  114. package/dist/core/media/MediaAltField.js +9 -1
  115. package/dist/core/media/MediaLocation.d.ts +5 -2
  116. package/dist/core/media/MediaLocation.js +6 -7
  117. package/dist/core/media/MediaTypes.js +11 -0
  118. package/dist/core/source/CombinedSource.d.ts +2 -2
  119. package/dist/core/source/CombinedSource.js +6 -3
  120. package/dist/core/source/FSSource.d.ts +4 -3
  121. package/dist/core/source/FSSource.js +59 -27
  122. package/dist/core/source/GithubSource.d.ts +2 -2
  123. package/dist/core/source/GithubSource.js +21 -9
  124. package/dist/core/source/IndexedDBSource.d.ts +2 -2
  125. package/dist/core/source/IndexedDBSource.js +3 -1
  126. package/dist/core/source/MemorySource.d.ts +2 -2
  127. package/dist/core/source/MemorySource.js +3 -1
  128. package/dist/core/source/OverlaySource.d.ts +12 -0
  129. package/dist/core/source/OverlaySource.js +50 -0
  130. package/dist/core/source/Source.d.ts +5 -2
  131. package/dist/core/source/Source.js +3 -3
  132. package/dist/core/util/Async.d.ts +8 -2
  133. package/dist/core/util/Async.js +67 -0
  134. package/dist/core/util/EntryFilenames.d.ts +2 -2
  135. package/dist/core/util/EntryFilenames.js +3 -3
  136. package/dist/dashboard/App.css +158 -0
  137. package/dist/dashboard/App.js +2 -2
  138. package/dist/dashboard/app/AccessDenied.js +3 -3
  139. package/dist/dashboard/app/ActivityStatus.js +15 -15
  140. package/dist/dashboard/app/AppShell.js +1 -1
  141. package/dist/dashboard/app/AuthView.js +11 -11
  142. package/dist/dashboard/app/CompactField.js +10 -10
  143. package/dist/dashboard/app/DashboardErrorBoundary.js +5 -5
  144. package/dist/dashboard/app/DashboardLayout.js +22 -19
  145. package/dist/dashboard/app/EntryHeader.js +12 -12
  146. package/dist/dashboard/app/EntryReferences.js +9 -9
  147. package/dist/dashboard/app/EntrySidebar.d.ts +1 -1
  148. package/dist/dashboard/app/EntrySidebar.js +17 -16
  149. package/dist/dashboard/app/EntrySidebarPreview.d.ts +1 -1
  150. package/dist/dashboard/app/EntrySidebarPreview.js +19 -14
  151. package/dist/dashboard/app/EntryTranslationBanner.js +7 -7
  152. package/dist/dashboard/app/Explorer.js +51 -43
  153. package/dist/dashboard/app/ExplorerCards.js +34 -34
  154. package/dist/dashboard/app/ExplorerFileCard.js +18 -18
  155. package/dist/dashboard/app/ExplorerList.js +13 -13
  156. package/dist/dashboard/app/ExplorerModal.js +3 -3
  157. package/dist/dashboard/app/ExplorerTable.js +76 -61
  158. package/dist/dashboard/app/ExternalLinkPicker.js +15 -4
  159. package/dist/dashboard/app/LinkPicker.js +8 -8
  160. package/dist/dashboard/app/LocaleMenu.js +12 -12
  161. package/dist/dashboard/app/SidebarLayout.d.ts +8 -0
  162. package/dist/dashboard/app/SidebarLayout.js +1169 -0
  163. package/dist/dashboard/app/SidebarTree.js +46 -46
  164. package/dist/dashboard/app/WorkspaceMenu.js +17 -17
  165. package/dist/dashboard/app/WorkspaceRoots.js +23 -23
  166. package/dist/dashboard/app/editor/FileEditor.d.ts +2 -1
  167. package/dist/dashboard/app/editor/FileEditor.js +41 -32
  168. package/dist/dashboard/app/editor/FilePreview.js +4 -4
  169. package/dist/dashboard/app/editor/MediaAltField.view.d.ts +1 -1
  170. package/dist/dashboard/app/editor/MediaAltField.view.js +6 -9
  171. package/dist/dashboard/app/modals/CreateEntry.js +4 -4
  172. package/dist/dashboard/app/pages/EntryPage.js +57 -31
  173. package/dist/dashboard/app/pages/UsersPage.js +33 -33
  174. package/dist/dashboard/app/ui/DashboardModal.js +23 -23
  175. package/dist/dashboard/app/ui/Rail.js +3 -3
  176. package/dist/dashboard/app/ui/Sidebar.js +2 -2
  177. package/dist/dashboard/atoms/auth.js +2 -0
  178. package/dist/dashboard/atoms/dashboard.d.ts +9 -0
  179. package/dist/dashboard/atoms/dashboard.js +17 -0
  180. package/dist/dashboard/atoms/editor.d.ts +7 -1
  181. package/dist/dashboard/atoms/editor.js +4 -1
  182. package/dist/dashboard/atoms/entry.d.ts +48 -2
  183. package/dist/dashboard/atoms/entry.js +222 -17
  184. package/dist/dashboard/atoms/explorer.js +4 -9
  185. package/dist/dashboard/atoms/preview.d.ts +3 -0
  186. package/dist/dashboard/atoms/preview.js +18 -1
  187. package/dist/dashboard/atoms/root.d.ts +4 -1
  188. package/dist/dashboard/atoms/root.js +96 -157
  189. package/dist/dashboard/hooks.d.ts +4 -2
  190. package/dist/dashboard/hooks.js +9 -5
  191. package/dist/dashboard/icons.d.ts +1 -0
  192. package/dist/dashboard/icons.js +10 -0
  193. package/dist/field/check/CheckField.view.js +2 -2
  194. package/dist/field/code/CodeField.view.js +12 -12
  195. package/dist/field/date/DateField.view.js +12 -2
  196. package/dist/field/link/FileLink.d.ts +2 -2
  197. package/dist/field/link/FileLink.js +5 -3
  198. package/dist/field/link/ImageLink.d.ts +1 -1
  199. package/dist/field/link/ImageLink.js +1 -1
  200. package/dist/field/link/Link.js +2 -2
  201. package/dist/field/link/LinkField.view.js +155 -128
  202. package/dist/field/list/ListField.view.js +22 -33
  203. package/dist/field/metadata/MetadataField.view.js +19 -19
  204. package/dist/field/number/NumberField.d.ts +2 -0
  205. package/dist/field/number/NumberField.view.js +4 -2
  206. package/dist/field/richtext/PickTextLink.d.ts +7 -0
  207. package/dist/field/richtext/PickTextLink.js +104 -2
  208. package/dist/field/richtext/ReferenceLink.d.ts +4 -1
  209. package/dist/field/richtext/ReferenceLink.js +18 -4
  210. package/dist/field/richtext/RichTextDocument.d.ts +7 -2
  211. package/dist/field/richtext/RichTextDocument.js +34 -5
  212. package/dist/field/richtext/RichTextExtensions.d.ts +3 -1
  213. package/dist/field/richtext/RichTextExtensions.js +2704 -7467
  214. package/dist/field/richtext/RichTextField.d.ts +4 -0
  215. package/dist/field/richtext/RichTextField.story.d.ts +4 -0
  216. package/dist/field/richtext/RichTextField.view.js +43 -20
  217. package/dist/field/richtext/RichTextInsertMenu.js +281 -495
  218. package/dist/field/richtext/RichTextToolbar.d.ts +5 -2
  219. package/dist/field/richtext/RichTextToolbar.js +38 -3
  220. package/dist/field/richtext/Toolbar.d.ts +19 -1
  221. package/dist/field/richtext/Toolbar.js +18 -13
  222. package/dist/field/richtext/extensions/Anchor.js +1 -1
  223. package/dist/field/richtext/extensions/Link.js +1 -1
  224. package/dist/field/richtext/extensions/Small.js +1 -1
  225. package/dist/field/select/SelectField.view.js +8 -6
  226. package/dist/index.css +227 -16
  227. package/dist/next.cjs +18 -16
  228. package/dist/next.d.ts +2 -1
  229. package/dist/next.edge.d.ts +2 -1
  230. package/dist/picker/entry/EntryPicker.js +9 -35
  231. package/dist/preview/PreviewPayload.js +35 -250
  232. package/dist/ui/RichText.d.ts +1 -0
  233. package/dist/ui/RichText.js +29 -2
  234. package/package.json +6 -3
  235. package/dist/cli/util/ExternalPlugin.d.ts +0 -2
  236. package/dist/cli/util/ExternalPlugin.js +0 -919
  237. package/dist/chunks/{chunk-JFROOUG6.js → chunk-NIVGVRDS.js} +17 -17
@@ -1774,8 +1774,8 @@ import {
1774
1774
  Field
1775
1775
  } from "../Field.js";
1776
1776
  import { createId } from "../Id.js";
1777
+ import { mediaAltText } from "../media/MediaAltField.js";
1777
1778
  import { MediaFile } from "../media/MediaTypes.js";
1778
- import { MediaLocation } from "../media/MediaLocation.js";
1779
1779
  import { Schema } from "../Schema.js";
1780
1780
  import {
1781
1781
  LinkMark,
@@ -1787,14 +1787,9 @@ import { applyUrlSuffix, createUniqueAnchor } from "../util/Anchors.js";
1787
1787
  import { entries } from "../util/Objects.js";
1788
1788
  import { slugify } from "../util/Slugs.js";
1789
1789
  var linkInfoFields = {
1790
- entryUrl: Entry.url,
1791
- extension: MediaFile.extension,
1792
1790
  id: Entry.id,
1793
- location: MediaFile.location,
1794
- path: Entry.path,
1795
- root: Entry.root,
1796
1791
  url: Entry.url,
1797
- workspace: Entry.workspace
1792
+ alt: MediaFile.alt
1798
1793
  };
1799
1794
  var RichTextField = class extends Field {
1800
1795
  constructor(schema, meta) {
@@ -2014,6 +2009,18 @@ function richTextReferences(schema, doc, path, label) {
2014
2009
  linkType
2015
2010
  });
2016
2011
  });
2012
+ iterImageNodes(doc, (node, index) => {
2013
+ if (typeof node._entry !== "string") return;
2014
+ result.push({
2015
+ targetId: node._entry,
2016
+ fieldPath: referenceFieldPath(
2017
+ typeof node._id === "string" ? [...path, node._id] : [...path, String(index)]
2018
+ ),
2019
+ fieldLabel: label,
2020
+ linkId: node._id,
2021
+ linkType: "image"
2022
+ });
2023
+ });
2017
2024
  doc.forEach((row, index) => {
2018
2025
  if (!schema || !Node.isBlock(row)) return;
2019
2026
  const type = schema[row[Node.type]];
@@ -2039,27 +2046,33 @@ async function applyLinkMarks(doc, loader) {
2039
2046
  const entryId = mark[LinkMark.entry];
2040
2047
  if (typeof entryId === "string") links.set(mark, entryId);
2041
2048
  });
2042
- const linkIds = Array.from(new Set(links.values()));
2049
+ const images = /* @__PURE__ */ new Map();
2050
+ iterImageNodes(doc, (node) => {
2051
+ if (node._link === "image" && typeof node._entry === "string") {
2052
+ delete node.src;
2053
+ delete node.alt;
2054
+ images.set(node, node._entry);
2055
+ }
2056
+ });
2057
+ const linkIds = Array.from(/* @__PURE__ */ new Set([...links.values(), ...images.values()]));
2043
2058
  const entries2 = await loader.resolveLinks(linkInfoFields, linkIds);
2044
2059
  const info = new Map(entries2.map((entry) => [entry.id, entry]));
2045
2060
  for (const [mark, entryId] of links) {
2046
- const type = mark[LinkMark.link];
2047
2061
  const data = info.get(entryId);
2048
2062
  if (!data) continue;
2049
- const href = type === "file" ? MediaLocation.publicUrl(loader.resolver.config, {
2050
- entryUrl: data.entryUrl,
2051
- extension: data.extension,
2052
- location: data.location,
2053
- path: data.path,
2054
- root: data.root,
2055
- workspace: data.workspace
2056
- }) : data.url;
2063
+ const href = data.url;
2057
2064
  mark.href = applyUrlSuffix(
2058
2065
  href,
2059
2066
  mark[LinkMark.suffix],
2060
2067
  mark[LinkMark.anchor]
2061
2068
  );
2062
2069
  }
2070
+ for (const [node, entryId] of images) {
2071
+ const data = info.get(entryId);
2072
+ if (!data) continue;
2073
+ node.src = data.url;
2074
+ node.alt = mediaAltText(data.alt, loader.locale ?? void 0);
2075
+ }
2063
2076
  }
2064
2077
  function richTextSearchableText(schema, value) {
2065
2078
  if (!Array.isArray(value)) return "";
@@ -2089,6 +2102,13 @@ function iterMarks(doc, fn) {
2089
2102
  else if (Node.isElement(row) && row.content) iterMarks(row.content, fn);
2090
2103
  }
2091
2104
  }
2105
+ function iterImageNodes(doc, fn) {
2106
+ doc.forEach((node, index) => {
2107
+ if (!Node.isElement(node)) return;
2108
+ if (node._type === "image") fn(node, index);
2109
+ if (node.content) iterImageNodes(node.content, fn);
2110
+ });
2111
+ }
2092
2112
  function iterNodes(doc, fn, path = []) {
2093
2113
  doc.forEach((row, index) => {
2094
2114
  const rowPath = [...path, String(index)];
@@ -2107,8 +2127,12 @@ var RichTextEditor = class {
2107
2127
  this.doc.push({ _id: createId(), _type: type, ...block });
2108
2128
  return this;
2109
2129
  }
2110
- addHtml(html) {
2111
- this.doc.push(...parseHTML(html.trim()));
2130
+ addHtml(html, options) {
2131
+ this.doc.push(...parseHTML(html.trim(), options));
2132
+ return this;
2133
+ }
2134
+ async addHtmlAsync(html, options) {
2135
+ this.doc.push(...await parseHTMLAsync(html.trim(), options));
2112
2136
  return this;
2113
2137
  }
2114
2138
  value() {
@@ -2170,51 +2194,94 @@ function mapMark(name, attributes) {
2170
2194
  return { _type: "link", ...attributes };
2171
2195
  }
2172
2196
  }
2173
- function parseHTML(html) {
2174
- const doc = [];
2175
- if (typeof html !== "string") return doc;
2176
- const parents = [
2177
- { tag: void 0, doc }
2178
- ];
2179
- const markStack = [];
2197
+ async function parseHTMLAsync(html, options = {}) {
2198
+ const state = createParseHTMLState();
2199
+ if (typeof html !== "string") return state.doc;
2200
+ for (const token of tokenizeHTML(html)) {
2201
+ if (token.type === "text") {
2202
+ processText(state, token.text);
2203
+ } else if (token.type === "closeTag") {
2204
+ processCloseTag(state, token.name);
2205
+ } else {
2206
+ const tagHandler = options.tags?.[token.name];
2207
+ const markHandler = options.marks?.[token.name];
2208
+ const node = tagHandler ? await tagHandler(token.attributes) : mapNode(token.name, token.attributes);
2209
+ const mark = markHandler ? await markHandler(token.attributes) : mapMark(token.name, token.attributes);
2210
+ processOpenTag(state, token.name, node, mark);
2211
+ }
2212
+ }
2213
+ concatTextNodes(state.doc);
2214
+ return state.doc;
2215
+ }
2216
+ function parseHTML(html, options = {}) {
2217
+ const state = createParseHTMLState();
2218
+ if (typeof html !== "string") return state.doc;
2219
+ for (const token of tokenizeHTML(html)) {
2220
+ if (token.type === "text") {
2221
+ processText(state, token.text);
2222
+ } else if (token.type === "closeTag") {
2223
+ processCloseTag(state, token.name);
2224
+ } else {
2225
+ const tagHandler = options.tags?.[token.name];
2226
+ const markHandler = options.marks?.[token.name];
2227
+ const node = tagHandler ? tagHandler(token.attributes) : mapNode(token.name, token.attributes);
2228
+ const mark = markHandler ? markHandler(token.attributes) : mapMark(token.name, token.attributes);
2229
+ processOpenTag(state, token.name, node, mark);
2230
+ }
2231
+ }
2232
+ concatTextNodes(state.doc);
2233
+ return state.doc;
2234
+ }
2235
+ function tokenizeHTML(html) {
2236
+ const tokens = [];
2180
2237
  const parser = new Parser({
2181
2238
  onopentag(name, attributes) {
2182
- const node = mapNode(name, attributes);
2183
- const mark = mapMark(name, attributes);
2184
- const parent = parents.at(-1);
2185
- if (node) {
2186
- parent?.doc?.push(node);
2187
- parents.push({ tag: name, doc: node?.content });
2188
- } else if (mark) {
2189
- const target = parent?.doc;
2190
- if (!target) return;
2191
- markStack.push({
2192
- tag: name,
2193
- mark,
2194
- doc: target,
2195
- start: target.length
2196
- });
2197
- }
2239
+ tokens.push({ type: "openTag", name, attributes });
2198
2240
  },
2199
2241
  ontext(text) {
2200
- const parent = parents.at(-1);
2201
- if (parent?.doc === doc && text.trim().length === 0) return;
2202
- parent?.doc?.push({ _type: "text", text });
2242
+ tokens.push({ type: "text", text });
2203
2243
  },
2204
2244
  onclosetag(name) {
2205
- const parent = parents.at(-1);
2206
- if (parent?.tag === name) parents.pop();
2207
- const match = findMark(name, markStack);
2208
- if (match < 0) return;
2209
- const { mark, doc: doc2, start } = markStack[match];
2210
- for (let i = start; i < doc2.length; i++) applyMark(doc2[i], mark);
2211
- markStack.splice(match);
2245
+ tokens.push({ type: "closeTag", name });
2212
2246
  }
2213
2247
  });
2214
2248
  parser.write(html);
2215
2249
  parser.end();
2216
- concatTextNodes(doc);
2217
- return doc;
2250
+ return tokens;
2251
+ }
2252
+ function createParseHTMLState() {
2253
+ const doc = [];
2254
+ return { doc, parents: [{ doc }], markStack: [] };
2255
+ }
2256
+ function processOpenTag(state, name, node, mark) {
2257
+ const parent = state.parents.at(-1);
2258
+ if (node) {
2259
+ parent?.doc?.push(node);
2260
+ state.parents.push({ tag: name, doc: node.content });
2261
+ } else if (mark) {
2262
+ const target = parent?.doc;
2263
+ if (!target) return;
2264
+ state.markStack.push({
2265
+ tag: name,
2266
+ mark,
2267
+ doc: target,
2268
+ start: target.length
2269
+ });
2270
+ }
2271
+ }
2272
+ function processText(state, text) {
2273
+ const parent = state.parents.at(-1);
2274
+ if (parent?.doc === state.doc && text.trim().length === 0) return;
2275
+ parent?.doc?.push({ _type: "text", text });
2276
+ }
2277
+ function processCloseTag(state, name) {
2278
+ const parent = state.parents.at(-1);
2279
+ if (parent?.tag === name) state.parents.pop();
2280
+ const match = findMark(name, state.markStack);
2281
+ if (match < 0) return;
2282
+ const { mark, doc, start } = state.markStack[match];
2283
+ for (let i = start; i < doc.length; i++) applyMark(doc[i], mark);
2284
+ state.markStack.splice(match);
2218
2285
  }
2219
2286
  function findMark(tag, marks) {
2220
2287
  for (let i = marks.length - 1; i >= 0; i--) {
@@ -2223,7 +2290,7 @@ function findMark(tag, marks) {
2223
2290
  return -1;
2224
2291
  }
2225
2292
  function applyMark(node, mark) {
2226
- if (node._type === "text") {
2293
+ if (Node.isText(node)) {
2227
2294
  const marks = node.marks || (node.marks = []);
2228
2295
  if (!marks.some((current) => sameMark(current, mark))) marks.unshift(mark);
2229
2296
  return;
@@ -2269,5 +2336,6 @@ function concatTextNodes(doc) {
2269
2336
  export {
2270
2337
  RichTextEditor,
2271
2338
  RichTextField,
2272
- parseHTML
2339
+ parseHTML,
2340
+ parseHTMLAsync
2273
2341
  };
@@ -7,3 +7,4 @@ export interface MediaAltOptions extends Omit<TextOptions, 'initialValue' | 'val
7
7
  export declare class MediaAltField extends ScalarField<MediaAltValue, MediaAltOptions> {
8
8
  }
9
9
  export declare function mediaAlt(label: string, options?: WithoutLabel<MediaAltOptions>): MediaAltField;
10
+ export declare function mediaAltText(value: MediaAltValue, locale?: string): string;
@@ -3,6 +3,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
3
3
  // src/core/media/MediaAltField.ts
4
4
  import { ScalarField } from "alinea/core/field/ScalarField";
5
5
  import { viewKeys } from "alinea/dashboard/ViewKeys";
6
+ import { isRecord } from "../util/Objects.js";
6
7
  var MediaAltField = class extends ScalarField {
7
8
  };
8
9
  function mediaAlt(label, options = {}) {
@@ -11,7 +12,14 @@ function mediaAlt(label, options = {}) {
11
12
  view: viewKeys.MediaAltInput
12
13
  });
13
14
  }
15
+ function mediaAltText(value, locale) {
16
+ if (typeof value === "string") return value;
17
+ if (!isRecord(value)) return "";
18
+ if (locale && typeof value[locale] === "string") return value[locale];
19
+ return Object.values(value).find((value2) => typeof value2 === "string") ?? "";
20
+ }
14
21
  export {
15
22
  MediaAltField,
16
- mediaAlt
23
+ mediaAlt,
24
+ mediaAltText
17
25
  };
@@ -16,10 +16,13 @@ export interface MediaUrlResolver {
16
16
  (meta: MediaUrlMeta): string;
17
17
  }
18
18
  export interface MediaPublicUrlMeta extends Omit<MediaUrlMeta, 'parentPaths'> {
19
- entryUrl: string;
19
+ /** @deprecated Pass parentPaths directly when available. */
20
+ entryUrl?: string;
21
+ parentPaths?: Array<string>;
20
22
  }
21
23
  export interface MediaEntryUrlMeta {
22
- entryUrl: string;
24
+ defaultUrl: string;
25
+ parentPaths: Array<string>;
23
26
  path: string;
24
27
  workspace: string;
25
28
  root: string;
@@ -24,29 +24,28 @@ var MediaLocation;
24
24
  }
25
25
  MediaLocation2.entryLocation = entryLocation;
26
26
  function publicUrl(config, meta) {
27
- const { entryUrl: entryUrl2, ...media } = meta;
28
- const { location, workspace } = media;
27
+ const { entryUrl: entryUrl2, parentPaths, ...media } = meta;
28
+ const { location, workspace } = meta;
29
29
  const { mediaUrl } = Workspace.data(config.workspaces[workspace]);
30
30
  if (!mediaUrl) return location;
31
31
  if (typeof mediaUrl === "function") {
32
- const segments = entryUrl2.split("/").filter(Boolean);
33
32
  return mediaUrl({
34
33
  ...media,
35
- parentPaths: segments.slice(0, -1)
34
+ parentPaths: parentPaths ?? entryUrl2?.split("/").filter(Boolean).slice(0, -1) ?? []
36
35
  });
37
36
  }
38
37
  return joinPaths(mediaUrl, location);
39
38
  }
40
39
  MediaLocation2.publicUrl = publicUrl;
41
40
  function entryUrl(config, meta) {
42
- const { data, entryUrl: entryUrl2, path, root, workspace } = meta;
41
+ const { data, defaultUrl, parentPaths, path, root, workspace } = meta;
43
42
  const { extension, location } = data;
44
43
  if (typeof extension !== "string" || typeof location !== "string")
45
- return entryUrl2;
44
+ return defaultUrl;
46
45
  return publicUrl(config, {
47
- entryUrl: entryUrl2,
48
46
  extension,
49
47
  location,
48
+ parentPaths,
50
49
  path,
51
50
  root,
52
51
  workspace
@@ -9,6 +9,7 @@ import { path } from "alinea/field/path/PathField";
9
9
  import { text } from "alinea/field/text/TextField";
10
10
  import { type } from "../Type.js";
11
11
  import { mediaAlt } from "./MediaAltField.js";
12
+ import { MediaLocation } from "./MediaLocation.js";
12
13
  var MediaLibrary = type("Media directory", {
13
14
  icon: IcRoundPermMedia,
14
15
  contains: ["MediaLibrary", "MediaFile"],
@@ -20,6 +21,16 @@ var MediaLibrary = type("Media directory", {
20
21
  });
21
22
  var MediaFile = type("Media file", {
22
23
  hidden: true,
24
+ entryUrl({ config, data, defaultUrl, parentPaths, path: path2, root, workspace }) {
25
+ return MediaLocation.entryUrl(config, {
26
+ data,
27
+ defaultUrl,
28
+ parentPaths,
29
+ path: path2,
30
+ root,
31
+ workspace
32
+ });
33
+ },
23
34
  fields: {
24
35
  title: text("Title"),
25
36
  path: path("Path"),
@@ -1,11 +1,11 @@
1
1
  import type { ChangesBatch } from './Change.js';
2
- import type { Source } from './Source.js';
2
+ import type { GetBlobsOptions, Source } from './Source.js';
3
3
  import { ReadonlyTree } from './Tree.js';
4
4
  export declare class CombinedSource implements Source {
5
5
  #private;
6
6
  constructor(sources: Record<string, Source>);
7
7
  getTree(): Promise<ReadonlyTree>;
8
8
  getTreeIfDifferent(sha: string): Promise<ReadonlyTree | undefined>;
9
- getBlobs(shas: Array<string>): AsyncGenerator<[sha: string, blob: Uint8Array]>;
9
+ getBlobs(shas: ReadonlyArray<string>, options?: GetBlobsOptions): AsyncGenerator<[sha: string, blob: Uint8Array]>;
10
10
  applyChanges(batch: ChangesBatch): Promise<void>;
11
11
  }
@@ -32,8 +32,11 @@ var CombinedSource = class {
32
32
  if (tree.sha === sha) return void 0;
33
33
  return tree;
34
34
  }
35
- async *getBlobs(shas) {
36
- if (this.#only) return this.#only.getBlobs(shas);
35
+ async *getBlobs(shas, options = {}) {
36
+ if (this.#only) {
37
+ yield* this.#only.getBlobs(shas, options);
38
+ return;
39
+ }
37
40
  const tree = await this.getTree();
38
41
  const perSource = /* @__PURE__ */ new Map();
39
42
  for (const [name, source] of entries(this.#sources)) {
@@ -47,7 +50,7 @@ var CombinedSource = class {
47
50
  }
48
51
  }
49
52
  for (const [source, shas2] of perSource) {
50
- yield* source.getBlobs(shas2);
53
+ yield* source.getBlobs(shas2, options);
51
54
  }
52
55
  }
53
56
  async applyChanges(batch) {
@@ -1,5 +1,5 @@
1
1
  import type { ChangesBatch } from './Change.js';
2
- import type { Source } from './Source.js';
2
+ import type { GetBlobsOptions, Source } from './Source.js';
3
3
  import { ReadonlyTree, WriteableTree } from './Tree.js';
4
4
  export declare class FSSource implements Source {
5
5
  #private;
@@ -7,7 +7,8 @@ export declare class FSSource implements Source {
7
7
  getTree(): Promise<ReadonlyTree>;
8
8
  getFile(current: ReadonlyTree, builder: WriteableTree, file: string): Promise<readonly [string, Uint8Array<ArrayBufferLike>] | undefined>;
9
9
  getTreeIfDifferent(sha: string): Promise<ReadonlyTree | undefined>;
10
- getBlobs(shas: Array<string>): AsyncGenerator<[sha: string, blob: Uint8Array]>;
10
+ getBlobs(shas: ReadonlyArray<string>, options?: GetBlobsOptions): AsyncGenerator<[sha: string, blob: Uint8Array]>;
11
+ readBlob(sha: string, file: string, signal?: AbortSignal): Promise<[sha: string, blob: Uint8Array]>;
11
12
  applyChanges(batch: ChangesBatch): Promise<void>;
12
13
  }
13
14
  export declare class CachedFSSource extends FSSource {
@@ -17,5 +18,5 @@ export declare class CachedFSSource extends FSSource {
17
18
  getTree(): Promise<ReadonlyTree>;
18
19
  applyChanges(batch: ChangesBatch): Promise<void>;
19
20
  getFile(current: ReadonlyTree, builder: WriteableTree, file: string): Promise<readonly [string, Uint8Array<ArrayBufferLike>] | undefined>;
20
- getBlobs(shas: Array<string>): AsyncGenerator<[sha: string, blob: Uint8Array]>;
21
+ getBlobs(shas: ReadonlyArray<string>, options?: GetBlobsOptions): AsyncGenerator<[sha: string, blob: Uint8Array]>;
21
22
  }
@@ -4,6 +4,7 @@ import {
4
4
  import "../../chunks/chunk-CQ52GEHY.js";
5
5
 
6
6
  // src/core/source/FSSource.ts
7
+ import { createHash } from "node:crypto";
7
8
  import fs from "node:fs/promises";
8
9
  import path from "node:path/posix";
9
10
 
@@ -54,12 +55,17 @@ var p_debounce_default = pDebounce;
54
55
 
55
56
  // src/core/source/FSSource.ts
56
57
  import { assert } from "../util/Assert.js";
57
- import { accumulate } from "../util/Async.js";
58
+ import { mapConcurrent } from "../util/Async.js";
58
59
  import { isRecord } from "../util/Objects.js";
59
- import { hashBlob } from "./GitUtils.js";
60
60
  import { ReadonlyTree, WriteableTree } from "./Tree.js";
61
61
  var limit = pLimit(1);
62
62
  var fileConcurrency = 64;
63
+ var blobReadConcurrency = 32;
64
+ var encoder = new TextEncoder();
65
+ function hashFileBlob(contents) {
66
+ const header = encoder.encode(`blob ${contents.byteLength}\0`);
67
+ return createHash("sha1").update(header).update(contents).digest("hex");
68
+ }
63
69
  var FSSource = class {
64
70
  #current = ReadonlyTree.EMPTY;
65
71
  #cwd;
@@ -75,11 +81,12 @@ var FSSource = class {
75
81
  const files = await fs.readdir(this.#cwd, {
76
82
  recursive: true
77
83
  });
78
- const fileLimit = pLimit(fileConcurrency);
79
- const tasks = files.map(
80
- (file) => fileLimit(() => this.getFile(current, builder, file))
81
- );
82
- await Promise.all(tasks);
84
+ for await (const result of mapConcurrent(
85
+ files,
86
+ (file) => this.getFile(current, builder, file),
87
+ { concurrency: fileConcurrency }
88
+ ))
89
+ void result;
83
90
  const tree = await builder.compile(current);
84
91
  this.#current = tree;
85
92
  return tree;
@@ -111,7 +118,7 @@ var FSSource = class {
111
118
  if (isRecord(error) && error.code === "ENOENT") return;
112
119
  throw error;
113
120
  }
114
- const sha = await hashBlob(contents);
121
+ const sha = hashFileBlob(contents);
115
122
  this.#locations.set(sha, filePath);
116
123
  this.#lastModified.set(filePath, stat.mtimeMs);
117
124
  builder.add(filePath, sha);
@@ -121,12 +128,20 @@ var FSSource = class {
121
128
  const current = await this.getTree();
122
129
  return current.sha === sha ? void 0 : current;
123
130
  }
124
- async *getBlobs(shas) {
125
- for (const sha of shas) {
126
- const path2 = this.#locations.get(sha);
127
- assert(path2, `Missing path for blob ${sha}`);
128
- yield [sha, await fs.readFile(`${this.#cwd}/${path2}`)];
129
- }
131
+ getBlobs(shas, options = {}) {
132
+ return mapConcurrent(
133
+ shas,
134
+ (sha, signal) => {
135
+ const file = this.#locations.get(sha);
136
+ assert(file, `Missing path for blob ${sha}`);
137
+ return this.readBlob(sha, file, signal);
138
+ },
139
+ { concurrency: blobReadConcurrency, signal: options.signal }
140
+ );
141
+ }
142
+ async readBlob(sha, file, signal) {
143
+ const blob = await fs.readFile(`${this.#cwd}/${file}`, { signal });
144
+ return [sha, blob];
130
145
  }
131
146
  async applyChanges(batch) {
132
147
  return limit(async () => {
@@ -157,9 +172,23 @@ var CachedFSSource = class extends FSSource {
157
172
  constructor(cwd) {
158
173
  super(cwd);
159
174
  }
160
- refresh = p_debounce_default(async () => {
161
- this.#blobs = /* @__PURE__ */ new Map();
162
- return this.#tree = super.getTree();
175
+ refresh = p_debounce_default(() => {
176
+ let refresh;
177
+ refresh = super.getTree().then(
178
+ (tree) => {
179
+ const currentShas = new Set(tree.index().values());
180
+ for (const sha of this.#blobs.keys()) {
181
+ if (!currentShas.has(sha)) this.#blobs.delete(sha);
182
+ }
183
+ return tree;
184
+ },
185
+ (error) => {
186
+ if (this.#tree === refresh) this.#tree = void 0;
187
+ throw error;
188
+ }
189
+ );
190
+ this.#tree = refresh;
191
+ return refresh;
163
192
  }, 50);
164
193
  getTree() {
165
194
  if (!this.#tree) return this.refresh();
@@ -174,16 +203,19 @@ var CachedFSSource = class extends FSSource {
174
203
  if (result) this.#blobs?.set(result[0], result[1]);
175
204
  return result;
176
205
  }
177
- async *getBlobs(shas) {
178
- const fromLocal = shas.filter((sha) => this.#blobs.has(sha));
179
- const localEntries = fromLocal.map(
180
- (sha) => [sha, this.#blobs.get(sha)]
181
- );
182
- const fromRemote = shas.filter((sha) => !this.#blobs.has(sha));
183
- const remoteEntries = fromRemote.length > 0 ? await accumulate(super.getBlobs(fromRemote)) : [];
184
- const entries = [...localEntries, ...remoteEntries];
185
- this.#blobs = new Map(entries);
186
- yield* entries;
206
+ async *getBlobs(shas, options = {}) {
207
+ const fromRemote = [];
208
+ for (const sha of shas) {
209
+ if (options.signal?.aborted)
210
+ throw options.signal.reason ?? new Error("Blob transfer aborted");
211
+ const blob = this.#blobs.get(sha);
212
+ if (blob) yield [sha, blob];
213
+ else fromRemote.push(sha);
214
+ }
215
+ for await (const [sha, blob] of super.getBlobs(fromRemote, options)) {
216
+ this.#blobs.set(sha, blob);
217
+ yield [sha, blob];
218
+ }
187
219
  }
188
220
  };
189
221
  export {
@@ -1,5 +1,5 @@
1
1
  import type { ChangesBatch } from './Change.js';
2
- import type { Source } from './Source.js';
2
+ import type { GetBlobsOptions, Source } from './Source.js';
3
3
  import { ReadonlyTree } from './Tree.js';
4
4
  export interface GithubSourceOptions {
5
5
  authToken: string;
@@ -17,6 +17,6 @@ export declare class GithubSource implements Source {
17
17
  getTree(): Promise<ReadonlyTree>;
18
18
  shaAt(ref: string): Promise<string>;
19
19
  getTreeIfDifferent(sha: string): Promise<ReadonlyTree | undefined>;
20
- getBlobs(shas: Array<string>): AsyncGenerator<[sha: string, blob: Uint8Array]>;
20
+ getBlobs(shas: ReadonlyArray<string>, options?: GetBlobsOptions): AsyncGenerator<[sha: string, blob: Uint8Array]>;
21
21
  applyChanges(batch: ChangesBatch): Promise<void>;
22
22
  }
@@ -19,6 +19,7 @@ var GithubSource = class {
19
19
  #current = ReadonlyTree.EMPTY;
20
20
  #options;
21
21
  #limit = pLimit(8);
22
+ #shaCache = /* @__PURE__ */ new Map();
22
23
  constructor(options) {
23
24
  this.#options = normalizeGithubSourceOptions(options);
24
25
  }
@@ -35,17 +36,25 @@ var GithubSource = class {
35
36
  async shaAt(ref) {
36
37
  const { owner, repo, authToken } = this.#options;
37
38
  const parentDir = this.contentLocation.split("/").slice(0, -1).join("/");
38
- const parentInfo = await fetch(
39
- `https://api.github.com/repos/${owner}/${repo}/contents/${parentDir}?ref=${ref}`,
40
- { headers: { Authorization: `Bearer ${authToken}` } }
41
- );
39
+ const url = `https://api.github.com/repos/${owner}/${repo}/contents/${parentDir}?ref=${ref}`;
40
+ const cached = this.#shaCache.get(url);
41
+ const headers = new Headers({ Authorization: `Bearer ${authToken}` });
42
+ if (cached) headers.set("If-None-Match", cached.etag);
43
+ const parentInfo = await fetch(url, { headers });
44
+ if (parentInfo.status === 304) {
45
+ assert(cached, "Received 304 without a cached GitHub response");
46
+ return cached.sha;
47
+ }
42
48
  assert(parentInfo.ok, `Failed to get parent: ${parentInfo.statusText}`);
43
49
  const parents = await parentInfo.json();
44
50
  assert(Array.isArray(parents));
45
51
  const parent = parents.find((entry) => entry.path === this.contentLocation);
46
- if (!parent) return ReadonlyTree.EMPTY.sha;
47
- assert(typeof parent.sha === "string");
48
- return parent.sha;
52
+ const sha = parent ? parent.sha : ReadonlyTree.EMPTY.sha;
53
+ assert(typeof sha === "string");
54
+ const etag = parentInfo.headers.get("etag");
55
+ if (etag) this.#shaCache.set(url, { etag, sha });
56
+ else this.#shaCache.delete(url);
57
+ return sha;
49
58
  }
50
59
  async getTreeIfDifferent(sha) {
51
60
  const { branch, owner, repo, authToken } = this.#options;
@@ -61,13 +70,16 @@ var GithubSource = class {
61
70
  const tree = ReadonlyTree.fromFlat(treeData);
62
71
  return tree;
63
72
  }
64
- async *getBlobs(shas) {
73
+ async *getBlobs(shas, options = {}) {
65
74
  const { owner, repo, authToken } = this.#options;
66
75
  const responses = shas.map((sha) => {
67
76
  const promise = this.#limit(
68
77
  () => fetch(
69
78
  `https://api.github.com/repos/${owner}/${repo}/git/blobs/${sha}`,
70
- { headers: { Authorization: `Bearer ${authToken}` } }
79
+ {
80
+ headers: { Authorization: `Bearer ${authToken}` },
81
+ signal: options.signal
82
+ }
71
83
  )
72
84
  );
73
85
  return [sha, promise];
@@ -1,11 +1,11 @@
1
1
  import type { ChangesBatch } from './Change.js';
2
- import type { Source } from './Source.js';
2
+ import type { GetBlobsOptions, Source } from './Source.js';
3
3
  import { ReadonlyTree } from './Tree.js';
4
4
  export declare class IndexedDBSource implements Source {
5
5
  #private;
6
6
  constructor(indexedDB: IDBFactory, name: string);
7
7
  getTree(): Promise<ReadonlyTree>;
8
8
  getTreeIfDifferent(sha: string): Promise<ReadonlyTree | undefined>;
9
- getBlobs(shas: Array<string>): AsyncGenerator<[sha: string, blob: Uint8Array]>;
9
+ getBlobs(shas: ReadonlyArray<string>, options?: GetBlobsOptions): AsyncGenerator<[sha: string, blob: Uint8Array]>;
10
10
  applyChanges(batch: ChangesBatch): Promise<void>;
11
11
  }