alinea 1.1.1 → 1.2.0

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.
@@ -150,7 +150,11 @@ ${JSON.stringify(mutation)}`
150
150
  }
151
151
  async updateChildren(tx, previous, next) {
152
152
  const { childrenDir: dir } = previous;
153
- if (next.status !== EntryStatus.Published || dir === next.childrenDir)
153
+ const publishing = next.status === EntryStatus.Published;
154
+ const unarchive = previous.status === EntryStatus.Archived;
155
+ const pathChanged = dir !== next.childrenDir;
156
+ const needsUpdate = publishing && (unarchive || pathChanged);
157
+ if (!needsUpdate)
154
158
  return [];
155
159
  const children = await tx.select().from(EntryRow).where(
156
160
  or(eq(EntryRow.parentDir, dir), like(EntryRow.childrenDir, `${dir}/%`))
@@ -166,11 +170,15 @@ ${JSON.stringify(mutation)}`
166
170
  ...child,
167
171
  parentPaths
168
172
  });
173
+ const extension = paths.extname(child.filePath);
174
+ const fileName = paths.basename(child.filePath, extension);
175
+ const [, status] = entryInfo(fileName);
169
176
  await tx.update(EntryRow).set({
170
177
  filePath,
171
178
  childrenDir,
172
179
  parentDir,
173
- url
180
+ url,
181
+ status
174
182
  }).where(
175
183
  eq(EntryRow.id, child.id),
176
184
  is(EntryRow.locale, child.locale),
@@ -180,20 +188,29 @@ ${JSON.stringify(mutation)}`
180
188
  return children;
181
189
  }
182
190
  async logEntries() {
183
- const entries2 = await this.store.select().from(EntryRow).orderBy(asc(EntryRow.url), asc(EntryRow.index));
184
- for (const entry of entries2) {
185
- console.info(
186
- entry.url.padEnd(35),
187
- entry.id.padEnd(12),
188
- (entry.locale ?? "").padEnd(5),
189
- entry.status.padEnd(12),
190
- entry.title
191
- );
192
- }
191
+ const entries2 = await this.store.select({
192
+ id: EntryRow.id,
193
+ url: EntryRow.url,
194
+ locale: EntryRow.locale,
195
+ status: EntryRow.status,
196
+ title: EntryRow.title,
197
+ filePath: EntryRow.filePath
198
+ }).from(EntryRow).orderBy(asc(EntryRow.url), asc(EntryRow.index));
199
+ console.table(entries2);
193
200
  }
194
201
  async applyMutation(tx, mutation) {
195
202
  switch (mutation.type) {
196
203
  case MutationType.Create: {
204
+ const { entry } = mutation;
205
+ let status = entry.status;
206
+ if (entry.parentId) {
207
+ const parent = await tx.select().from(EntryRow).where(
208
+ eq(EntryRow.id, entry.parentId),
209
+ is(EntryRow.locale, mutation.locale),
210
+ is(EntryRow.main, true)
211
+ ).get();
212
+ status = parent?.status ?? status;
213
+ }
197
214
  const condition = and(
198
215
  eq(EntryRow.id, mutation.entryId),
199
216
  eq(EntryRow.status, mutation.entry.status),
@@ -202,7 +219,7 @@ ${JSON.stringify(mutation)}`
202
219
  const current = await tx.select().from(EntryRow).where(condition).get();
203
220
  if (current)
204
221
  return;
205
- await tx.insert(EntryRow).values(mutation.entry);
222
+ await tx.insert(EntryRow).values({ ...mutation.entry, status });
206
223
  return () => this.updateHash(tx, condition);
207
224
  }
208
225
  case MutationType.Edit: {
@@ -216,10 +233,8 @@ ${JSON.stringify(mutation)}`
216
233
  await tx.delete(EntryRow).where(condition);
217
234
  await tx.insert(EntryRow).values(entry);
218
235
  let children = [];
219
- if (entry.status === EntryStatus.Published) {
220
- if (current)
221
- children = await this.updateChildren(tx, current, entry);
222
- }
236
+ if (entry.status === EntryStatus.Published && current)
237
+ children = await this.updateChildren(tx, current, entry);
223
238
  return () => {
224
239
  return this.updateHash(tx, condition).then(
225
240
  (self) => this.updateHash(
@@ -263,11 +278,15 @@ ${JSON.stringify(mutation)}`
263
278
  return;
264
279
  const filePath = published.filePath.slice(0, -5) + `.${EntryStatus.Archived}.json`;
265
280
  await tx.delete(EntryRow).where(archived);
266
- await tx.update(EntryRow).set({
267
- status: EntryStatus.Archived,
268
- filePath
269
- }).where(condition);
270
- return () => this.updateHash(tx, archived);
281
+ await tx.update(EntryRow).set({ status: EntryStatus.Archived, filePath }).where(condition);
282
+ const children = await tx.update(EntryRow).set({ status: EntryStatus.Archived }).where(
283
+ eq(EntryRow.status, EntryStatus.Published),
284
+ or(
285
+ eq(EntryRow.parentDir, published.childrenDir),
286
+ like(EntryRow.childrenDir, published.childrenDir + "/%")
287
+ )
288
+ ).returning(EntryRow.id);
289
+ return () => this.updateHash(tx, or(archived, inArray(EntryRow.id, children)));
271
290
  }
272
291
  case MutationType.Publish: {
273
292
  const promoting = await tx.select().from(EntryRow).where(
@@ -582,9 +601,8 @@ ${JSON.stringify(mutation)}`
582
601
  try {
583
602
  const raw = JsonLoader.parse(this.config.schema, file.contents);
584
603
  const { meta, data, v0Id } = parseRecord(raw);
585
- if (v0Id) {
604
+ if (v0Id)
586
605
  v0Ids.set(v0Id, meta.id);
587
- }
588
606
  const seeded = meta.seeded;
589
607
  const key = seedKey(
590
608
  file.workspace,
@@ -718,12 +736,18 @@ ${JSON.stringify(mutation)}`
718
736
  EntryRow.locale,
719
737
  sql`'null'`
720
738
  )}, ${EntryRow.status}) in ${values(...inserted)}`;
739
+ const archivedPaths = await tx.select(EntryRow.childrenDir).from(EntryRow).where(eq(EntryRow.status, EntryStatus.Archived));
740
+ for (const archivedPath of archivedPaths) {
741
+ const isChildOf = or(
742
+ eq(EntryRow.parentDir, archivedPath),
743
+ like(EntryRow.childrenDir, archivedPath + "/%")
744
+ );
745
+ await tx.update(EntryRow).set({ status: EntryStatus.Archived }).where(isChildOf, eq(EntryRow.status, EntryStatus.Published));
746
+ }
721
747
  const entries2 = await tx.select().from(EntryRow).where(isInserted);
722
748
  for (const entry of entries2) {
723
749
  const rowHash = await createRowHash(entry);
724
- await tx.update(EntryRow).set({
725
- rowHash
726
- }).where(
750
+ await tx.update(EntryRow).set({ rowHash }).where(
727
751
  eq(EntryRow.id, entry.id),
728
752
  is(EntryRow.locale, entry.locale),
729
753
  eq(EntryRow.status, entry.status)
@@ -1,2 +1,2 @@
1
- import { Database } from 'rado';
2
- export type Store = Database;
1
+ import { SyncDatabase } from 'rado';
2
+ export type Store = SyncDatabase<'sqlite'>;
@@ -1,2 +1,2 @@
1
1
  import { Store } from '../Store.js';
2
- export declare function createEntrySearch(db: Store): import("rado").QueryBatch<unknown, import("rado/core/MetaData.js").Either>;
2
+ export declare function createEntrySearch(db: Store): import("rado").QueryBatch<unknown, import("rado/core/MetaData.js").Sync<"sqlite">>;
@@ -152,7 +152,7 @@ var EntryResolver = class {
152
152
  const name = this.scope.nameOf(field);
153
153
  if (!name)
154
154
  throw new Error(`Expression has no name ${field}`);
155
- const isEntryField = name === "path" || name === "type";
155
+ const isEntryField = name === "path" || name === "title";
156
156
  if (isEntryField)
157
157
  return table[name];
158
158
  return table.data[name];
@@ -2,7 +2,7 @@
2
2
  var package_default = {
3
3
  bin: "./dist/cli.js",
4
4
  name: "alinea",
5
- version: "1.1.1",
5
+ version: "1.2.0",
6
6
  description: "Headless git-based CMS",
7
7
  repository: {
8
8
  type: "git",
package/dist/cli/Serve.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../chunks/chunk-LRCCBBNL.js";
3
+ } from "../chunks/chunk-76ZJ7RWJ.js";
4
4
  import "../chunks/chunk-U5RRZUYZ.js";
5
5
 
6
6
  // src/cli/Serve.ts
package/dist/cli/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../chunks/chunk-LRCCBBNL.js";
3
+ } from "../chunks/chunk-76ZJ7RWJ.js";
4
4
  import "../chunks/chunk-U5RRZUYZ.js";
5
5
 
6
6
  // node_modules/mri/lib/index.mjs
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../chunks/chunk-LRCCBBNL.js";
3
+ } from "../chunks/chunk-76ZJ7RWJ.js";
4
4
  import {
5
5
  PLazy
6
6
  } from "../chunks/chunk-IKINPSS5.js";
package/dist/core/CMS.js CHANGED
@@ -49,7 +49,8 @@ var CMS = class extends Graph {
49
49
  return this.get({
50
50
  type: query.type,
51
51
  id: op.id,
52
- locale: query.locale
52
+ locale: query.locale,
53
+ status: "preferPublished"
53
54
  });
54
55
  }
55
56
  async remove(...entryIds) {
@@ -61,7 +61,7 @@ export interface Order {
61
61
  type InferSelection<Selection> = Selection extends GraphQuery & Edge ? Expand<AnyQueryResult<Selection>> : Selection extends Expr<infer V> ? V : {
62
62
  [K in keyof Selection]: Selection[K] extends Type<infer V> ? Type.Infer<V> : InferSelection<Selection[K]>;
63
63
  };
64
- type InferResult<Selection, Types, Include> = Selection extends Expr<infer Value> ? Value : Selection extends undefined ? Types extends undefined ? EntryFields & (Include extends undefined ? {} : InferSelection<Include>) : EntryFields & Infer<Types> & (Include extends undefined ? {} : InferSelection<Include>) : InferSelection<Selection>;
64
+ type InferResult<Selection, Types, Include> = Selection extends undefined ? Types extends undefined ? EntryFields & (Include extends undefined ? {} : InferSelection<Include>) : EntryFields & Infer<Types> & (Include extends undefined ? {} : InferSelection<Include>) : Selection extends Expr<infer Value> ? Value : InferSelection<Selection>;
65
65
  type QueryResult<Selection, Types, Include> = Expand<InferResult<Selection, Types, Include>>;
66
66
  interface CountQuery<Selection, Types, Include> extends GraphQuery<Selection, Types, Include> {
67
67
  count: true;
@@ -3,6 +3,7 @@ import { StoredRow } from 'alinea/core/Infer';
3
3
  import { ImagePreviewDetails } from 'alinea/core/media/CreatePreview';
4
4
  import type { CMS } from './CMS.js';
5
5
  import { Config } from './Config.js';
6
+ import { EntryStatus } from './EntryRow.js';
6
7
  import { Status } from './Graph.js';
7
8
  import { Mutation } from './Mutation.js';
8
9
  import { Type } from './Type.js';
@@ -48,6 +49,7 @@ export interface CreateQuery<Fields> {
48
49
  root?: string;
49
50
  parentId?: string | null;
50
51
  locale?: string;
52
+ status?: EntryStatus;
51
53
  set?: Partial<StoredRow<Fields>>;
52
54
  }
53
55
  export declare class CreateOperation<Definition> extends Operation {
@@ -238,7 +238,7 @@ var CreateOperation = class extends Operation {
238
238
  }
239
239
  ];
240
240
  async function entryRow() {
241
- const { workspace, root, parentId, locale, set } = query;
241
+ const { workspace, root, parentId, locale, set, status } = query;
242
242
  const typeName = getScope(cms.config).nameOf(query.type);
243
243
  if (!typeName)
244
244
  throw new Error(
@@ -246,6 +246,7 @@ var CreateOperation = class extends Operation {
246
246
  );
247
247
  const partial = {
248
248
  id: entryId,
249
+ status,
249
250
  type: typeName,
250
251
  workspace,
251
252
  root,
@@ -25,6 +25,7 @@ export declare namespace Type {
25
25
  type Infer<Definition> = TypeRow<Definition>;
26
26
  function label(type: Type): Label;
27
27
  function contains(type: Type): Array<string | Type>;
28
+ function insertOrder(type: Type): 'first' | 'last' | 'free';
28
29
  function isHidden(type: Type): boolean;
29
30
  function shape(type: Type): RecordShape;
30
31
  function searchableText(type: Type, value: any): string;
@@ -58,6 +59,8 @@ export interface TypeConfig<Definition> {
58
59
  summaryRow?: View<SummaryProps>;
59
60
  /** A React component used to view a thumbnail of this type in the dashboard */
60
61
  summaryThumb?: View<SummaryProps>;
62
+ /** The position where new children will be inserted */
63
+ insertOrder?: 'first' | 'last' | 'free';
61
64
  entryUrl?: (meta: EntryUrlMeta) => string;
62
65
  }
63
66
  export interface TypeInternal extends TypeConfig<FieldsDefinition> {
package/dist/core/Type.js CHANGED
@@ -21,6 +21,10 @@ var Type;
21
21
  return getType(type2).contains ?? [];
22
22
  }
23
23
  Type2.contains = contains;
24
+ function insertOrder(type2) {
25
+ return getType(type2).insertOrder ?? "free";
26
+ }
27
+ Type2.insertOrder = insertOrder;
24
28
  function isHidden(type2) {
25
29
  return Boolean(getType(type2).hidden);
26
30
  }
@@ -83,6 +83,7 @@ export declare const entryRoute: Route<{
83
83
  parents: Array<{
84
84
  id: string;
85
85
  path: string;
86
+ status: import("alinea/core/EntryRow").EntryStatus;
86
87
  }>;
87
88
  client: import("alinea/core/Connection").Connection;
88
89
  config: import("alinea/core/Config").Config;
@@ -94,6 +95,8 @@ export declare const entryRoute: Route<{
94
95
  locale: string;
95
96
  entryId: string;
96
97
  }>;
98
+ untranslated: boolean;
99
+ canPublish: boolean;
97
100
  parentNeedsTranslation: boolean;
98
101
  edits: import("./atoms/Edits.js").Edits;
99
102
  } | undefined;
@@ -111,6 +111,7 @@ export declare const entryEditorAtoms: import("jotai/vanilla/utils/atomFamily.js
111
111
  parents: Array<{
112
112
  id: string;
113
113
  path: string;
114
+ status: EntryStatus;
114
115
  }>;
115
116
  client: Connection;
116
117
  config: Config;
@@ -122,6 +123,8 @@ export declare const entryEditorAtoms: import("jotai/vanilla/utils/atomFamily.js
122
123
  locale: string;
123
124
  entryId: string;
124
125
  }>;
126
+ untranslated: boolean;
127
+ canPublish: boolean;
125
128
  parentNeedsTranslation: boolean;
126
129
  edits: Edits;
127
130
  } | undefined>>>;
@@ -129,6 +132,7 @@ export interface EntryData {
129
132
  parents: Array<{
130
133
  id: string;
131
134
  path: string;
135
+ status: EntryStatus;
132
136
  }>;
133
137
  client: Connection;
134
138
  config: Config;
@@ -140,6 +144,8 @@ export interface EntryData {
140
144
  locale: string;
141
145
  entryId: string;
142
146
  }>;
147
+ untranslated: boolean;
148
+ canPublish: boolean;
143
149
  parentNeedsTranslation: boolean;
144
150
  edits: Edits;
145
151
  }
@@ -227,6 +233,7 @@ export declare function createEntryEditor(entryData: EntryData): {
227
233
  parents: Array<{
228
234
  id: string;
229
235
  path: string;
236
+ status: EntryStatus;
230
237
  }>;
231
238
  client: Connection;
232
239
  config: Config;
@@ -238,6 +245,8 @@ export declare function createEntryEditor(entryData: EntryData): {
238
245
  locale: string;
239
246
  entryId: string;
240
247
  }>;
248
+ untranslated: boolean;
249
+ canPublish: boolean;
241
250
  parentNeedsTranslation: boolean;
242
251
  edits: Edits;
243
252
  };
@@ -139,7 +139,8 @@ var entryEditorAtoms = atomFamily(
139
139
  edge: "parents",
140
140
  select: {
141
141
  id: Entry.id,
142
- path: Entry.path
142
+ path: Entry.path,
143
+ status: Entry.status
143
144
  }
144
145
  }
145
146
  },
@@ -164,7 +165,14 @@ var entryEditorAtoms = atomFamily(
164
165
  locale: searchLocale,
165
166
  status: "preferDraft"
166
167
  });
168
+ const untranslated = Boolean(
169
+ entry.locale && searchLocale !== entry.locale
170
+ );
167
171
  const parentNeedsTranslation = entry.parentId ? !parentLink : false;
172
+ const parents = withParents?.parents ?? [];
173
+ const canPublish = parents.every(
174
+ (parent) => parent.status === EntryStatus.Published
175
+ );
168
176
  if (versions.length === 0)
169
177
  return void 0;
170
178
  const statuses = fromEntries(
@@ -174,8 +182,10 @@ var entryEditorAtoms = atomFamily(
174
182
  (status) => statuses[status] !== void 0
175
183
  );
176
184
  return createEntryEditor({
177
- parents: withParents?.parents ?? [],
185
+ parents,
186
+ canPublish,
178
187
  translations,
188
+ untranslated,
179
189
  parentNeedsTranslation,
180
190
  client,
181
191
  config,
@@ -640,7 +650,7 @@ function createEntryEditor(entryData) {
640
650
  });
641
651
  const form = atom((get) => {
642
652
  const doc = get(currentDoc);
643
- const readOnly = doc !== edits.doc ? true : void 0;
653
+ const readOnly = doc !== edits.doc ? true : !entryData.canPublish;
644
654
  return new FormAtoms(type, doc.getMap(DOC_KEY), "", { readOnly });
645
655
  });
646
656
  const yUpdate = debounceAtom(edits.yUpdate, 250);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "../../chunks/chunk-LRCCBBNL.js";
3
+ } from "../../chunks/chunk-76ZJ7RWJ.js";
4
4
  import {
5
5
  create as create2
6
6
  } from "../../chunks/chunk-IOTY7UTU.js";
@@ -74,10 +74,9 @@ function EntryEdit({ editor }) {
74
74
  useEffect(() => {
75
75
  ref.current?.scrollTo({ top: 0 });
76
76
  }, [editor.entryId, mode, selectedStatus]);
77
- const untranslated = locale && locale !== editor.activeVersion.locale;
78
77
  const { isBlocking, nextRoute, confirm, cancel } = useRouteBlocker(
79
78
  "Are you sure you want to discard changes?",
80
- !untranslated && hasChanges
79
+ !editor.untranslated && hasChanges
81
80
  );
82
81
  const isNavigationChange = nextRoute?.data.editor?.entryId !== editor.entryId;
83
82
  const form = useAtomValue(editor.form);
@@ -99,7 +98,7 @@ function EntryEdit({ editor }) {
99
98
  alert("todo");
100
99
  return;
101
100
  }
102
- if (untranslated && hasChanges) {
101
+ if (editor.untranslated && hasChanges) {
103
102
  translate();
104
103
  } else if (config.enableDrafts) {
105
104
  if (hasChanges)
@@ -214,7 +213,7 @@ function EntryEdit({ editor }) {
214
213
  }
215
214
  ),
216
215
  /* @__PURE__ */ jsxs(Main.Container, { children: [
217
- untranslated && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
216
+ editor.untranslated && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
218
217
  EntryNotice,
219
218
  {
220
219
  icon: IcRoundTranslate,
@@ -120,6 +120,7 @@ var variantIcon = {
120
120
  function EntryHeader({ editor, editable = true }) {
121
121
  const config = useConfig();
122
122
  const locale = useLocale();
123
+ const { canPublish, untranslated, parentNeedsTranslation } = editor;
123
124
  const statusInUrl = useAtomValue(editor.statusInUrl);
124
125
  const selectedStatus = useAtomValue(editor.selectedStatus);
125
126
  const previewRevision = useAtomValue(editor.previewRevision);
@@ -128,7 +129,6 @@ function EntryHeader({ editor, editable = true }) {
128
129
  const isMediaLibrary = editor.activeVersion.type === "MediaLibrary";
129
130
  const hasChanges = useAtomValue(editor.hasChanges);
130
131
  const currentTransition = useAtomValue(editor.transition)?.transition;
131
- const untranslated = locale && locale !== editor.activeVersion.locale;
132
132
  const variant = currentTransition ? "transition" : previewRevision ? "revision" : untranslated ? "untranslated" : hasChanges && !statusInUrl ? "editing" : selectedStatus;
133
133
  const saveDraft = useSetAtom(editor.saveDraft);
134
134
  const publishEdits = useSetAtom(editor.publishEdits);
@@ -224,7 +224,7 @@ function EntryHeader({ editor, editable = true }) {
224
224
  children: "Archive"
225
225
  }
226
226
  ) : variant === EntryStatus.Archived ? /* @__PURE__ */ jsxs(Fragment, { children: [
227
- /* @__PURE__ */ jsx(
227
+ canPublish && /* @__PURE__ */ jsx(
228
228
  DropdownMenu.Item,
229
229
  {
230
230
  className: styles.root.action(),
@@ -283,7 +283,7 @@ function EntryHeader({ editor, editable = true }) {
283
283
  })
284
284
  ] })
285
285
  ] }),
286
- editable && !currentTransition && !hasChanges && isActiveStatus && !untranslated && !previewRevision && /* @__PURE__ */ jsxs(Fragment, { children: [
286
+ editable && !currentTransition && !hasChanges && isActiveStatus && !untranslated && !previewRevision && canPublish && /* @__PURE__ */ jsxs(Fragment, { children: [
287
287
  /* @__PURE__ */ jsx("span", { className: styles.root.description.separator() }),
288
288
  /* @__PURE__ */ jsx("div", { className: styles.root.description.action(), children: "Edit to create a new draft" })
289
289
  ] }),
@@ -291,7 +291,7 @@ function EntryHeader({ editor, editable = true }) {
291
291
  /* @__PURE__ */ jsx("span", { className: styles.root.description.separator() }),
292
292
  /* @__PURE__ */ jsx("div", { className: styles.root.description.action(), children: "A newer draft version is available" })
293
293
  ] }),
294
- !currentTransition && untranslated && !editor.parentNeedsTranslation && !hasChanges && /* @__PURE__ */ jsxs(Fragment, { children: [
294
+ !currentTransition && untranslated && !parentNeedsTranslation && !hasChanges && /* @__PURE__ */ jsxs(Fragment, { children: [
295
295
  /* @__PURE__ */ jsx("span", { className: styles.root.description.separator() }),
296
296
  /* @__PURE__ */ jsx("div", { className: styles.root.description.action(), children: /* @__PURE__ */ jsxs(HStack, { center: true, children: [
297
297
  /* @__PURE__ */ jsx("span", { style: { marginRight: px(8) }, children: "Translate from" }),
@@ -40,6 +40,7 @@ import { select } from "alinea/field/select";
40
40
  import { text } from "alinea/field/text";
41
41
  import { entryPicker } from "alinea/picker/entry/EntryPicker";
42
42
  import { EntryReference } from "alinea/picker/entry/EntryReference";
43
+ import { children, parents } from "alinea/query";
43
44
  import { Button, Loader } from "alinea/ui";
44
45
  import { Link } from "alinea/ui/Link";
45
46
  import { Suspense, useEffect, useMemo, useState } from "react";
@@ -74,16 +75,19 @@ var parentData = {
74
75
  url: Entry.url,
75
76
  level: Entry.level,
76
77
  parent: Entry.parentId,
77
- parentPaths: {
78
- edge: "parents",
78
+ parentPaths: parents({
79
79
  select: Entry.path
80
- },
81
- childrenIndex: {
82
- first: true,
83
- edge: "children",
80
+ }),
81
+ firstChildIndex: children({
82
+ take: 1,
84
83
  select: Entry.index,
85
84
  orderBy: { asc: Entry.index, caseSensitive: true }
86
- }
85
+ }),
86
+ lastChildIndex: children({
87
+ take: 1,
88
+ select: Entry.index,
89
+ orderBy: { desc: Entry.index, caseSensitive: true }
90
+ })
87
91
  };
88
92
  var titleField = text("Title", { autoFocus: true });
89
93
  function NewEntryForm({ parentId }) {
@@ -158,6 +162,34 @@ function NewEntryForm({ parentId }) {
158
162
  };
159
163
  });
160
164
  }, []);
165
+ const insertOrderField = useMemo(() => {
166
+ const insertOrderField2 = select(
167
+ "Insert order",
168
+ {
169
+ initialValue: "last",
170
+ options: {
171
+ first: "At the top of the list",
172
+ last: "At the bottom of the list"
173
+ }
174
+ }
175
+ );
176
+ return track.options(insertOrderField2, async (get) => {
177
+ const selectedParent2 = get(parentField);
178
+ const parentId2 = selectedParent2?.[EntryReference.entry];
179
+ const parent = await graph.get({
180
+ select: {
181
+ type: Entry.type
182
+ },
183
+ id: parentId2,
184
+ status: "preferDraft"
185
+ });
186
+ const parentType = parent && config.schema[parent.type];
187
+ const parentInsertOrder = Type.insertOrder(parentType);
188
+ return {
189
+ hidden: parentInsertOrder !== "free"
190
+ };
191
+ });
192
+ }, []);
161
193
  const copyFromField = useMemo(() => {
162
194
  const copyFromField2 = entry("Copy content from");
163
195
  return track.options(copyFromField2, (get) => {
@@ -183,6 +215,7 @@ function NewEntryForm({ parentId }) {
183
215
  parent: parentField,
184
216
  title: titleField,
185
217
  type: typeField,
218
+ order: insertOrderField,
186
219
  copyFrom: copyFromField
187
220
  }
188
221
  }),
@@ -225,6 +258,7 @@ function NewEntryForm({ parentId }) {
225
258
  locale,
226
259
  status: "preferPublished"
227
260
  }) : null;
261
+ const parentType = parent && config.schema[parent.type];
228
262
  const parentPaths = parent ? parent.parentPaths.concat(parent.path) : [];
229
263
  const filePath = entryFilepath(config, data, parentPaths);
230
264
  const childrenDir = entryChildrenDir(config, data, parentPaths);
@@ -237,6 +271,11 @@ function NewEntryForm({ parentId }) {
237
271
  id: copyFrom,
238
272
  status: "preferPublished"
239
273
  }) : Type.initialValue(entryType);
274
+ const parentInsertOrder = parentType ? Type.insertOrder(parentType) : "free";
275
+ let index = generateKeyBetween(null, parent?.firstChildIndex[0] || null);
276
+ if (parentInsertOrder === "last" || parentInsertOrder === "free" && form.data().order === "last") {
277
+ index = generateKeyBetween(parent?.lastChildIndex[0] || null, null);
278
+ }
240
279
  const entry2 = await createEntryRow(config, {
241
280
  id,
242
281
  ...data,
@@ -245,7 +284,7 @@ function NewEntryForm({ parentId }) {
245
284
  path,
246
285
  title,
247
286
  url,
248
- index: generateKeyBetween(null, parent?.childrenIndex || null),
287
+ index,
249
288
  parentId: parent?.id ?? null,
250
289
  seeded: null,
251
290
  level: parent ? parent.level + 1 : 0,
@@ -1675,6 +1675,8 @@ function typeExtension(field, name, type) {
1675
1675
  const { [BlockNode.id]: id } = node.attrs;
1676
1676
  const meta = getType(type);
1677
1677
  const { readOnly } = useFieldOptions(field);
1678
+ if (!id)
1679
+ return null;
1678
1680
  return /* @__PURE__ */ jsx(FormRow, { field, type, rowId: id, readOnly, children: /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsxs(Sink.Root, { style: { margin: `${px(18)} 0` }, children: [
1679
1681
  /* @__PURE__ */ jsxs(Sink.Header, { children: [
1680
1682
  /* @__PURE__ */ jsx(Sink.Options, { children: /* @__PURE__ */ jsx(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bin": "./dist/cli.js",
3
3
  "name": "alinea",
4
- "version": "1.1.1",
4
+ "version": "1.2.0",
5
5
  "description": "Headless git-based CMS",
6
6
  "repository": {
7
7
  "type": "git",