@qkix/strapi-plugin-rewind 0.1.4 → 0.2.1

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.
@@ -1,6 +1,6 @@
1
- import { createHash as x } from "node:crypto";
2
- import { AsyncLocalStorage as J } from "node:async_hooks";
3
- const Z = ["id", "documentId", "locale"], S = [
1
+ import { createHash as J } from "node:crypto";
2
+ import { AsyncLocalStorage as Z } from "node:async_hooks";
3
+ const Q = ["id", "documentId", "locale"], S = [
4
4
  "createdAt",
5
5
  "updatedAt",
6
6
  "publishedAt",
@@ -10,15 +10,15 @@ const Z = ["id", "documentId", "locale"], S = [
10
10
  "strapi_assignee",
11
11
  "locale",
12
12
  "localizations"
13
- ], I = (e, t) => e.getModel(t), Q = (e) => e?.type === "relation" || e?.type === "media", $ = (e) => e?.type === "relation" && String(e.relation ?? "").toLowerCase().startsWith("morph"), N = (e, t) => Object.entries(I(e, t).attributes).filter(
13
+ ], I = (e, t) => e.getModel(t), x = (e) => e?.type === "relation" || e?.type === "media", $ = (e) => e?.type === "relation" && String(e.relation ?? "").toLowerCase().startsWith("morph"), F = (e, t) => Object.entries(I(e, t).attributes).filter(
14
14
  ([, n]) => !["relation", "media", "component", "dynamiczone"].includes(n.type)
15
- ).map(([n]) => n), q = (e, t, n = !0) => {
16
- const r = I(e, t), o = n ? "select" : "fields";
17
- return Object.entries(r.attributes).reduce(
18
- (a, [s, i]) => {
19
- switch (i.type) {
15
+ ).map(([n]) => n), E = (e, t, n = !0) => {
16
+ const i = I(e, t), o = n ? "select" : "fields";
17
+ return Object.entries(i.attributes).reduce(
18
+ (a, [s, r]) => {
19
+ switch (r.type) {
20
20
  case "relation": {
21
- if ($(i)) break;
21
+ if ($(r)) break;
22
22
  a[s] = { [o]: ["documentId", "locale", "publishedAt"] };
23
23
  break;
24
24
  }
@@ -28,17 +28,17 @@ const Z = ["id", "documentId", "locale"], S = [
28
28
  }
29
29
  case "component": {
30
30
  a[s] = {
31
- populate: q(e, i.component, n),
32
- [o]: N(e, i.component)
31
+ populate: E(e, r.component, n),
32
+ [o]: F(e, r.component)
33
33
  };
34
34
  break;
35
35
  }
36
36
  case "dynamiczone": {
37
37
  a[s] = {
38
- on: (i.components ?? []).reduce(
38
+ on: (r.components ?? []).reduce(
39
39
  (c, l) => (c[l] = {
40
- populate: q(e, l, n),
41
- [o]: N(e, l)
40
+ populate: E(e, l, n),
41
+ [o]: F(e, l)
42
42
  }, c),
43
43
  {}
44
44
  )
@@ -51,17 +51,17 @@ const Z = ["id", "documentId", "locale"], S = [
51
51
  {}
52
52
  );
53
53
  }, ee = (e, t) => {
54
- const n = {}, r = (a) => {
54
+ const n = {}, i = (a) => {
55
55
  if (n[a]) return;
56
56
  const s = I(e, a).attributes;
57
57
  n[a] = R(s, S);
58
- for (const i of Object.values(s))
59
- if (i.type === "component" && r(i.component), i.type === "dynamiczone")
60
- for (const c of i.components ?? []) r(c);
58
+ for (const r of Object.values(s))
59
+ if (r.type === "component" && i(r.component), r.type === "dynamiczone")
60
+ for (const c of r.components ?? []) i(c);
61
61
  }, o = I(e, t).attributes;
62
62
  for (const a of Object.values(o))
63
- if (a.type === "component" && r(a.component), a.type === "dynamiczone")
64
- for (const s of a.components ?? []) r(s);
63
+ if (a.type === "component" && i(a.component), a.type === "dynamiczone")
64
+ for (const s of a.components ?? []) i(s);
65
65
  return {
66
66
  attributes: R(o, S),
67
67
  components: n
@@ -73,77 +73,77 @@ const Z = ["id", "documentId", "locale"], S = [
73
73
  targetUid: t
74
74
  }
75
75
  ), ne = (e, t, n) => {
76
- const r = I(e, t).attributes, o = {}, a = {}, s = R(n, [...S, ...Z]);
77
- for (const [i, c] of Object.entries(s)) {
78
- const l = r[i];
79
- if (l && Q(l)) {
76
+ const i = I(e, t).attributes, o = {}, a = {}, s = R(n, [...S, ...Q]);
77
+ for (const [r, c] of Object.entries(s)) {
78
+ const l = i[r];
79
+ if (l && x(l)) {
80
80
  if ($(l)) continue;
81
81
  const d = l.type === "media" ? "plugin::upload.file" : l.target;
82
- a[i] = te(c, d, l.type === "media");
82
+ a[r] = te(c, d, l.type === "media");
83
83
  continue;
84
84
  }
85
- o[i] = l ? Y(e, l, c) : c;
85
+ o[r] = l ? Y(e, l, c) : c;
86
86
  }
87
87
  return { data: o, relations: a, schemaSnapshot: ee(e, t) };
88
88
  }, Y = (e, t, n) => {
89
89
  if (n == null) return n;
90
90
  if (t.type === "component") {
91
- const r = (o) => _(e, t.component, o);
92
- return Array.isArray(n) ? n.map((o) => r(o)) : r(n);
91
+ const i = (o) => _(e, t.component, o);
92
+ return Array.isArray(n) ? n.map((o) => i(o)) : i(n);
93
93
  }
94
- return t.type === "dynamiczone" && Array.isArray(n) ? n.map((r) => {
95
- const o = r;
94
+ return t.type === "dynamiczone" && Array.isArray(n) ? n.map((i) => {
95
+ const o = i;
96
96
  return _(e, o.__component, o);
97
97
  }) : n;
98
98
  }, _ = (e, t, n) => {
99
99
  if (!n || !t) return n;
100
- const r = I(e, t).attributes, o = {};
100
+ const i = I(e, t).attributes, o = {};
101
101
  for (const [a, s] of Object.entries(n)) {
102
102
  if (a === "id") continue;
103
- const i = r[a];
104
- o[a] = i ? Y(e, i, s) : s;
103
+ const r = i[a];
104
+ o[a] = r ? Y(e, r, s) : s;
105
105
  }
106
106
  return o;
107
107
  }, H = (e) => {
108
- const t = /* @__PURE__ */ new Map(), n = async (r) => {
109
- if (t.has(r)) return t.get(r);
108
+ const t = /* @__PURE__ */ new Map(), n = async (i) => {
109
+ if (t.has(i)) return t.get(i);
110
110
  let o;
111
111
  try {
112
- o = (await e.plugin("content-manager").service("content-types").findConfiguration(e.contentTypes[r]))?.settings?.mainField;
112
+ o = (await e.plugin("content-manager").service("content-types").findConfiguration(e.contentTypes[i]))?.settings?.mainField;
113
113
  } catch {
114
114
  }
115
- return t.set(r, o), o;
115
+ return t.set(i, o), o;
116
116
  };
117
- return async (r, o) => {
117
+ return async (i, o) => {
118
118
  if (!o) return null;
119
- const s = [await n(r), "title", "name", "label"].filter(Boolean);
120
- for (const i of s) {
121
- const c = o[i];
119
+ const s = [await n(i), "title", "name", "label"].filter(Boolean);
120
+ for (const r of s) {
121
+ const c = o[r];
122
122
  if (typeof c == "string" && c.trim())
123
123
  return c.trim().slice(0, 255);
124
124
  }
125
125
  return null;
126
126
  };
127
- }, v = "plugin::rewind.version", oe = /* @__PURE__ */ new Set(["publish", "unpublish", "discardDraft", "restore"]), se = (e) => x("sha256").update(JSON.stringify(e)).digest("hex"), ie = ({ strapi: e }) => {
128
- const t = H(e), n = (i) => !!e.contentTypes[i]?.options?.draftAndPublish, r = (i) => !!(e.getModel(i)?.pluginOptions?.i18n?.localized ?? e.contentTypes[i]?.pluginOptions?.i18n?.localized), o = async (i, c, l) => {
129
- const d = r(i) && l.filter(Boolean).length > 0 ? { locale: { $in: l.filter(Boolean) } } : {};
130
- return e.db.query(i).findMany({
127
+ }, v = "plugin::rewind.version", oe = /* @__PURE__ */ new Set(["publish", "unpublish", "discardDraft", "restore"]), se = (e) => J("sha256").update(JSON.stringify(e)).digest("hex"), ie = ({ strapi: e }) => {
128
+ const t = H(e), n = (r) => !!e.contentTypes[r]?.options?.draftAndPublish, i = (r) => !!(e.getModel(r)?.pluginOptions?.i18n?.localized ?? e.contentTypes[r]?.pluginOptions?.i18n?.localized), o = async (r, c, l) => {
129
+ const d = i(r) && l.filter(Boolean).length > 0 ? { locale: { $in: l.filter(Boolean) } } : {};
130
+ return e.db.query(r).findMany({
131
131
  where: {
132
132
  documentId: c,
133
133
  ...d,
134
- ...n(i) ? { publishedAt: null } : {}
134
+ ...n(r) ? { publishedAt: null } : {}
135
135
  },
136
- populate: q(e, i)
136
+ populate: E(e, r)
137
137
  });
138
- }, a = async (i, c) => {
138
+ }, a = async (r, c) => {
139
139
  try {
140
- const l = e.plugin("content-manager").service("document-metadata"), d = await l.getMetadata(i, c);
140
+ const l = e.plugin("content-manager").service("document-metadata"), d = await l.getMetadata(r, c);
141
141
  return l.getStatus(c, d.availableStatus);
142
142
  } catch {
143
143
  return "draft";
144
144
  }
145
- }, s = async (i, c, l) => e.db.query(v).findOne({
146
- where: { contentType: i, relatedDocumentId: c, locale: l },
145
+ }, s = async (r, c, l) => e.db.query(v).findOne({
146
+ where: { contentType: r, relatedDocumentId: c, locale: l },
147
147
  orderBy: { createdAt: "desc" }
148
148
  });
149
149
  return {
@@ -154,12 +154,12 @@ const Z = ["id", "documentId", "locale"], S = [
154
154
  * Called from the transaction's commit hook and never awaited by the write
155
155
  * path - a failure here must not be able to reach the editor's save.
156
156
  */
157
- async captureAll(i, c) {
158
- for (const l of i)
157
+ async captureAll(r, c) {
158
+ for (const l of r)
159
159
  await this.capture(l, c);
160
160
  },
161
- async capture(i, c) {
162
- const { uid: l, relatedDocumentId: d, locale: p, origin: b } = i, m = i.before ?? await o(l, d, [p]);
161
+ async capture(r, c) {
162
+ const { uid: l, relatedDocumentId: d, locale: p, origin: b } = r, m = r.before ?? await o(l, d, [p]);
163
163
  for (const u of m) {
164
164
  const h = u.locale ?? null, { data: f, relations: w, schemaSnapshot: T } = ne(e, l, u), g = se({ data: f, relations: w });
165
165
  !oe.has(b) && (await s(l, d, h))?.contentHash === g || await e.db.query(v).create({
@@ -195,13 +195,13 @@ const Z = ["id", "documentId", "locale"], S = [
195
195
  }, ae = (e) => {
196
196
  const t = /* @__PURE__ */ new Map();
197
197
  for (const n of e) {
198
- const r = `${n.uid}:${n.relatedDocumentId}:${n.locale ?? ""}`, o = t.get(r);
198
+ const i = `${n.uid}:${n.relatedDocumentId}:${n.locale ?? ""}`, o = t.get(i);
199
199
  if (!o) {
200
- t.set(r, n);
200
+ t.set(i, n);
201
201
  continue;
202
202
  }
203
203
  const a = j.indexOf(n.origin) < j.indexOf(o.origin);
204
- t.set(r, {
204
+ t.set(i, {
205
205
  // A winning `publish` still wants the rows an earlier `discardDraft`
206
206
  // captured - losing them would lose the only copy of that state.
207
207
  ...a ? n : o,
@@ -209,10 +209,10 @@ const Z = ["id", "documentId", "locale"], S = [
209
209
  });
210
210
  }
211
211
  return [...t.values()];
212
- }, W = new J(), ce = (e) => W.run({ active: !0 }, e), le = () => W.getStore()?.active === !0, de = 200, ue = async (e, t) => {
213
- const n = e.db.query(t), r = await n.count({ where: { label: null } });
214
- if (r === 0) return 0;
215
- e.log.info(`[rewind] labelling ${r} older version(s)…`);
212
+ }, W = new Z(), ce = (e) => W.run({ active: !0 }, e), le = () => W.getStore()?.active === !0, de = 200, ue = async (e, t) => {
213
+ const n = e.db.query(t), i = await n.count({ where: { label: null } });
214
+ if (i === 0) return 0;
215
+ e.log.info(`[rewind] labelling ${i} older version(s)…`);
216
216
  const o = H(e);
217
217
  let a = 0;
218
218
  for (; ; ) {
@@ -222,10 +222,10 @@ const Z = ["id", "documentId", "locale"], S = [
222
222
  limit: de
223
223
  });
224
224
  if (s.length === 0) break;
225
- for (const i of s) {
226
- const c = await o(i.contentType, i.data);
225
+ for (const r of s) {
226
+ const c = await o(r.contentType, r.data);
227
227
  await n.update({
228
- where: { id: i.id },
228
+ where: { id: r.id },
229
229
  data: { label: c ?? "" }
230
230
  }), a += 1;
231
231
  }
@@ -251,14 +251,14 @@ const Z = ["id", "documentId", "locale"], S = [
251
251
  `[rewind] could not label older versions: ${o instanceof Error ? o.message : String(o)}`
252
252
  )
253
253
  ), he(e, t);
254
- const n = () => e.plugin("rewind").service("snapshot"), r = (o) => {
254
+ const n = () => e.plugin("rewind").service("snapshot"), i = (o) => {
255
255
  if (!pe.includes(o.action)) return !1;
256
256
  const a = o.contentType?.uid ?? o.uid ?? "";
257
257
  return !(!a.startsWith("api::") || !t.contentTypes.includes(a) || le() || !t.trackApiWrites && !e.requestContext.get()?.request?.url?.startsWith("/content-manager"));
258
258
  };
259
259
  e.documents.use(async (o, a) => {
260
- if (!r(o)) return a();
261
- const s = o.contentType?.uid ?? o.uid, i = o.params?.locale ?? null, c = o.action === "discardDraft" && o.params?.documentId ? await n().readRows(s, o.params.documentId, [i]) : null, l = await a(), d = o.action === "create" || o.action === "clone" ? l?.documentId : o.params?.documentId;
260
+ if (!i(o)) return a();
261
+ const s = o.contentType?.uid ?? o.uid, r = o.params?.locale ?? null, c = o.action === "discardDraft" && o.params?.documentId ? await n().readRows(s, o.params.documentId, [r]) : null, l = await a(), d = o.action === "create" || o.action === "clone" ? l?.documentId : o.params?.documentId;
262
262
  if (!d) return l;
263
263
  const p = ge(e);
264
264
  return await e.db.transaction(async ({ trx: b, onCommit: m }) => {
@@ -266,7 +266,7 @@ const Z = ["id", "documentId", "locale"], S = [
266
266
  u.intents.push({
267
267
  uid: s,
268
268
  relatedDocumentId: d,
269
- locale: i,
269
+ locale: r,
270
270
  origin: o.action,
271
271
  before: c
272
272
  }), !u.flushRegistered && (u.flushRegistered = !0, m(() => {
@@ -296,12 +296,12 @@ const Z = ["id", "documentId", "locale"], S = [
296
296
  const n = e.plugin("rewind").service("lock");
297
297
  if (await n.acquire("prune", 30 * 60 * 1e3))
298
298
  try {
299
- const r = await e.plugin("rewind").service("retention").prune();
300
- r.deleted > 0 && e.log.info(
301
- `[rewind] thinned ${r.deleted} version(s) across ${r.documents} document(s)`
299
+ const i = await e.plugin("rewind").service("retention").prune();
300
+ i.deleted > 0 && e.log.info(
301
+ `[rewind] thinned ${i.deleted} version(s) across ${i.documents} document(s)`
302
302
  );
303
- } catch (r) {
304
- e.log.error("[rewind] thinning failed", r);
303
+ } catch (i) {
304
+ e.log.error("[rewind] thinning failed", i);
305
305
  } finally {
306
306
  await n.release("prune");
307
307
  }
@@ -371,10 +371,10 @@ const Z = ["id", "documentId", "locale"], S = [
371
371
  throw new Error(
372
372
  '[rewind] config.contentTypes must be an array of content type uids, e.g. ["api::article.article"]'
373
373
  );
374
- const r = t.filter((o) => !o.startsWith("api::"));
375
- if (r.length > 0)
374
+ const i = t.filter((o) => !o.startsWith("api::"));
375
+ if (i.length > 0)
376
376
  throw new Error(
377
- `[rewind] config.contentTypes only supports api:: content types, received: ${r.join(", ")}`
377
+ `[rewind] config.contentTypes only supports api:: content types, received: ${i.join(", ")}`
378
378
  );
379
379
  }
380
380
  }, be = "collectionType", Te = "rewind_versions", ve = {
@@ -454,18 +454,18 @@ const Z = ["id", "documentId", "locale"], S = [
454
454
  userId: {
455
455
  type: "integer"
456
456
  }
457
- }, ke = {
457
+ }, Oe = {
458
458
  kind: be,
459
459
  collectionName: Te,
460
460
  info: ve,
461
461
  options: De,
462
462
  pluginOptions: Ae,
463
463
  attributes: Ie
464
- }, Oe = {
465
- version: { schema: ke }
464
+ }, ke = {
465
+ version: { schema: Oe }
466
466
  }, Se = ({ strapi: e }) => {
467
467
  e.cron.remove("rewindPrune");
468
- }, Ee = [
468
+ }, qe = [
469
469
  "id",
470
470
  "contentType",
471
471
  "relatedDocumentId",
@@ -477,24 +477,24 @@ const Z = ["id", "documentId", "locale"], S = [
477
477
  "pinned",
478
478
  "contentHash",
479
479
  "createdAt"
480
- ], qe = ({ strapi: e }) => ({
480
+ ], Ee = ({ strapi: e }) => ({
481
481
  async find(t) {
482
- const { contentType: n, documentId: r, locale: o, page: a = 1, pageSize: s = 10 } = t.query;
483
- if (!n || !r)
482
+ const { contentType: n, documentId: i, locale: o, page: a = 1, pageSize: s = 10 } = t.query;
483
+ if (!n || !i)
484
484
  return t.badRequest("contentType and documentId are required.");
485
- const i = {
485
+ const r = {
486
486
  contentType: n,
487
- relatedDocumentId: r,
487
+ relatedDocumentId: i,
488
488
  ...o ? { locale: o } : {}
489
489
  }, c = Math.min(Number(s) || 10, 100), l = (Math.max(Number(a) || 1, 1) - 1) * c, [d, p] = await Promise.all([
490
490
  e.db.query(v).findMany({
491
- select: Ee,
492
- where: i,
491
+ select: qe,
492
+ where: r,
493
493
  orderBy: { createdAt: "desc" },
494
494
  limit: c,
495
495
  offset: l
496
496
  }),
497
- e.db.query(v).count({ where: i })
497
+ e.db.query(v).count({ where: r })
498
498
  ]);
499
499
  t.body = {
500
500
  data: await U(e, d),
@@ -512,9 +512,9 @@ const Z = ["id", "documentId", "locale"], S = [
512
512
  t.body = {
513
513
  data: await e.plugin("rewind").service("diff").between(Number(t.params.id), n ? Number(n) : void 0)
514
514
  };
515
- } catch (r) {
515
+ } catch (i) {
516
516
  return t.badRequest(
517
- r instanceof Error ? r.message : "Could not compare these versions."
517
+ i instanceof Error ? i.message : "Could not compare these versions."
518
518
  );
519
519
  }
520
520
  },
@@ -527,6 +527,23 @@ const Z = ["id", "documentId", "locale"], S = [
527
527
  return t.badRequest(n instanceof Error ? n.message : "Preview failed.");
528
528
  }
529
529
  },
530
+ /**
531
+ * `pinned` is read from the body rather than inferred as a toggle, so that two
532
+ * editors with the panel open cannot flip the same version past each other:
533
+ * each request states the state it wants, and the last one wins visibly.
534
+ */
535
+ async pin(t) {
536
+ const { pinned: n } = t.request.body ?? {};
537
+ if (typeof n != "boolean")
538
+ return t.badRequest("pinned must be true or false.");
539
+ try {
540
+ t.body = {
541
+ data: await e.plugin("rewind").service("pin").set(Number(t.params.id), n)
542
+ };
543
+ } catch (i) {
544
+ return t.notFound(i instanceof Error ? i.message : "No such version.");
545
+ }
546
+ },
530
547
  async restore(t) {
531
548
  try {
532
549
  t.body = {
@@ -539,7 +556,7 @@ const Z = ["id", "documentId", "locale"], S = [
539
556
  }), U = async (e, t) => {
540
557
  const n = [...new Set(t.map((a) => a.userId).filter(Boolean))];
541
558
  if (n.length === 0) return t.map((a) => ({ ...a, user: null }));
542
- const r = await e.db.query("admin::user").findMany({ select: ["id", "firstname", "lastname", "email"], where: { id: n } }), o = new Map(r.map((a) => [a.id, a]));
559
+ const i = await e.db.query("admin::user").findMany({ select: ["id", "firstname", "lastname", "email"], where: { id: n } }), o = new Map(i.map((a) => [a.id, a]));
543
560
  return t.map((a) => {
544
561
  const s = o.get(a.userId);
545
562
  return {
@@ -551,7 +568,7 @@ const Z = ["id", "documentId", "locale"], S = [
551
568
  };
552
569
  });
553
570
  }, Re = {
554
- version: qe
571
+ version: Ee
555
572
  }, $e = {
556
573
  type: "admin",
557
574
  routes: [
@@ -579,6 +596,12 @@ const Z = ["id", "documentId", "locale"], S = [
579
596
  handler: "version.preview",
580
597
  config: { policies: ["admin::isAuthenticatedAdmin"] }
581
598
  },
599
+ {
600
+ method: "PUT",
601
+ path: "/versions/:id/pin",
602
+ handler: "version.pin",
603
+ config: { policies: ["admin::isAuthenticatedAdmin"] }
604
+ },
582
605
  {
583
606
  method: "POST",
584
607
  path: "/versions/:id/restore",
@@ -586,31 +609,31 @@ const Z = ["id", "documentId", "locale"], S = [
586
609
  config: { policies: ["admin::isAuthenticatedAdmin"] }
587
610
  }
588
611
  ]
589
- }, Fe = {
612
+ }, Ne = {
590
613
  admin: $e
591
- }, B = (e) => e.match(/\S+\s*/g) ?? [], L = 2500, Ne = (e, t) => {
592
- const n = B(e), r = B(t);
593
- if (n.length > L || r.length > L)
614
+ }, B = (e) => e.match(/\S+\s*/g) ?? [], L = 2500, Fe = (e, t) => {
615
+ const n = B(e), i = B(t);
616
+ if (n.length > L || i.length > L)
594
617
  return [
595
618
  { op: "removed", value: e },
596
619
  { op: "added", value: t }
597
620
  ];
598
621
  const o = Array.from(
599
622
  { length: n.length + 1 },
600
- () => new Array(r.length + 1).fill(0)
623
+ () => new Array(i.length + 1).fill(0)
601
624
  );
602
625
  for (let l = n.length - 1; l >= 0; l -= 1)
603
- for (let d = r.length - 1; d >= 0; d -= 1)
604
- o[l][d] = n[l] === r[d] ? o[l + 1][d + 1] + 1 : Math.max(o[l + 1][d], o[l][d + 1]);
626
+ for (let d = i.length - 1; d >= 0; d -= 1)
627
+ o[l][d] = n[l] === i[d] ? o[l + 1][d + 1] + 1 : Math.max(o[l + 1][d], o[l][d + 1]);
605
628
  const a = [], s = (l, d) => {
606
629
  const p = a[a.length - 1];
607
630
  p && p.op === l ? p.value += d : a.push({ op: l, value: d });
608
631
  };
609
- let i = 0, c = 0;
610
- for (; i < n.length && c < r.length; )
611
- n[i] === r[c] ? (s("equal", n[i]), i += 1, c += 1) : o[i + 1][c] >= o[i][c + 1] ? (s("removed", n[i]), i += 1) : (s("added", r[c]), c += 1);
612
- for (; i < n.length; ) s("removed", n[i++]);
613
- for (; c < r.length; ) s("added", r[c++]);
632
+ let r = 0, c = 0;
633
+ for (; r < n.length && c < i.length; )
634
+ n[r] === i[c] ? (s("equal", n[r]), r += 1, c += 1) : o[r + 1][c] >= o[r][c + 1] ? (s("removed", n[r]), r += 1) : (s("added", i[c]), c += 1);
635
+ for (; r < n.length; ) s("removed", n[r++]);
636
+ for (; c < i.length; ) s("added", i[c++]);
614
637
  return a;
615
638
  }, _e = /* @__PURE__ */ new Set([
616
639
  "id",
@@ -620,19 +643,19 @@ const Z = ["id", "documentId", "locale"], S = [
620
643
  "level",
621
644
  "language",
622
645
  "url"
623
- ]), E = (e, t = 0) => t > 12 || e == null ? "" : typeof e == "string" ? e : typeof e == "boolean" ? "" : typeof e == "number" ? String(e) : Array.isArray(e) ? e.map((n) => E(n, t + 1)).filter(Boolean).join(" ") : typeof e == "object" ? Object.entries(e).filter(([n]) => !_e.has(n)).map(([, n]) => E(n, t + 1)).filter(Boolean).join(" ") : "", z = (e) => e.replace(/\s+/g, " ").trim(), je = 6, Ce = (e, t = je) => {
646
+ ]), q = (e, t = 0) => t > 12 || e == null ? "" : typeof e == "string" ? e : typeof e == "boolean" ? "" : typeof e == "number" ? String(e) : Array.isArray(e) ? e.map((n) => q(n, t + 1)).filter(Boolean).join(" ") : typeof e == "object" ? Object.entries(e).filter(([n]) => !_e.has(n)).map(([, n]) => q(n, t + 1)).filter(Boolean).join(" ") : "", z = (e) => e.replace(/\s+/g, " ").trim(), je = 6, Ce = (e, t = je) => {
624
647
  const n = [];
625
- return e.forEach((r, o) => {
626
- if (r.op !== "equal") {
627
- n.push(r);
648
+ return e.forEach((i, o) => {
649
+ if (i.op !== "equal") {
650
+ n.push(i);
628
651
  return;
629
652
  }
630
- const a = r.value.match(/\S+\s*/g) ?? [];
653
+ const a = i.value.match(/\S+\s*/g) ?? [];
631
654
  if (a.length <= t * 2 + 1) {
632
- n.push(r);
655
+ n.push(i);
633
656
  return;
634
657
  }
635
- const s = o === 0, i = o === e.length - 1, c = s ? [] : a.slice(0, t), l = i ? [] : a.slice(-t);
658
+ const s = o === 0, r = o === e.length - 1, c = s ? [] : a.slice(0, t), l = r ? [] : a.slice(-t);
636
659
  c.length && n.push({ op: "equal", value: c.join("") }), n.push({ op: "equal", value: " … " }), l.length && n.push({ op: "equal", value: l.join("") });
637
660
  }), n;
638
661
  }, Me = /* @__PURE__ */ new Set([
@@ -650,7 +673,7 @@ const Z = ["id", "documentId", "locale"], S = [
650
673
  "uid",
651
674
  "email",
652
675
  "password"
653
- ]), Ue = (e, t) => e === "text" || e === "richtext" || e === "blocks" || e === "json" || e === "customField" || e === "component" || e === "dynamiczone" || e === "string" && typeof t == "string" && t.length > 80, O = (e) => `${e.targetUid}:${e.documentId ?? e.id}`, Be = ({ strapi: e }) => {
676
+ ]), Ue = (e, t) => e === "text" || e === "richtext" || e === "blocks" || e === "json" || e === "customField" || e === "component" || e === "dynamiczone" || e === "string" && typeof t == "string" && t.length > 80, k = (e) => `${e.targetUid}:${e.documentId ?? e.id}`, Be = ({ strapi: e }) => {
654
677
  const t = (o) => o && {
655
678
  id: o.id,
656
679
  label: o.label ?? null,
@@ -660,7 +683,7 @@ const Z = ["id", "documentId", "locale"], S = [
660
683
  const a = await e.db.query(v).findOne({ where: { id: o } });
661
684
  if (!a) throw new Error(`No version with id ${o}`);
662
685
  return a;
663
- }, r = async (o) => {
686
+ }, i = async (o) => {
664
687
  const [a] = await e.db.query(v).findMany({
665
688
  where: {
666
689
  contentType: o.contentType,
@@ -683,7 +706,7 @@ const Z = ["id", "documentId", "locale"], S = [
683
706
  * change?", is what `restore.preview()` already answers.
684
707
  */
685
708
  async between(o, a) {
686
- const s = await n(o), i = a ? await n(a) : await r(s), c = e.getModel(s.contentType)?.attributes, l = s.data ?? {}, d = i?.data ?? {}, p = s.relations ?? {}, b = i?.relations ?? {}, m = [];
709
+ const s = await n(o), r = a ? await n(a) : await i(s), c = e.getModel(s.contentType)?.attributes, l = s.data ?? {}, d = r?.data ?? {}, p = s.relations ?? {}, b = r?.relations ?? {}, m = [];
687
710
  for (const u of /* @__PURE__ */ new Set([...Object.keys(d), ...Object.keys(l)])) {
688
711
  const h = d[u], f = l[u];
689
712
  if (JSON.stringify(h) === JSON.stringify(f)) continue;
@@ -692,7 +715,7 @@ const Z = ["id", "documentId", "locale"], S = [
692
715
  m.push({ field: u, type: w, customField: T, kind: g, before: h, after: f });
693
716
  continue;
694
717
  }
695
- const D = z(E(h)), A = z(E(f));
718
+ const D = z(q(h)), A = z(q(f));
696
719
  if (D === A) {
697
720
  m.push({ field: u, type: w, customField: T, kind: g, before: void 0, after: void 0 });
698
721
  continue;
@@ -705,11 +728,11 @@ const Z = ["id", "documentId", "locale"], S = [
705
728
  // Collapsed here rather than in the panel: sending an entire article
706
729
  // to highlight one word wastes the payload as well as the reader's
707
730
  // attention.
708
- spans: Ce(Ne(D, A))
731
+ spans: Ce(Fe(D, A))
709
732
  });
710
733
  }
711
734
  for (const u of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(p)])) {
712
- const h = b[u] ?? [], f = p[u] ?? [], w = new Set(h.map(O)), T = new Set(f.map(O)), g = f.filter((A) => !w.has(O(A))), D = h.filter((A) => !T.has(O(A)));
735
+ const h = b[u] ?? [], f = p[u] ?? [], w = new Set(h.map(k)), T = new Set(f.map(k)), g = f.filter((A) => !w.has(k(A))), D = h.filter((A) => !T.has(k(A)));
713
736
  g.length === 0 && D.length === 0 || m.push({
714
737
  field: u,
715
738
  type: c?.[u]?.type ?? "relation",
@@ -719,7 +742,7 @@ const Z = ["id", "documentId", "locale"], S = [
719
742
  });
720
743
  }
721
744
  return {
722
- from: i ? t(i) : null,
745
+ from: r ? t(r) : null,
723
746
  to: t(s),
724
747
  changes: m,
725
748
  identical: m.length === 0
@@ -733,52 +756,61 @@ const Z = ["id", "documentId", "locale"], S = [
733
756
  key: `lock:${n}`
734
757
  });
735
758
  return {
736
- async acquire(n, r) {
759
+ async acquire(n, i) {
737
760
  const o = await e.store.get(t(n));
738
761
  return o && o.expires > Date.now() ? !1 : (await e.store.set({
739
762
  ...t(n),
740
- value: { expires: Date.now() + r }
763
+ value: { expires: Date.now() + i }
741
764
  }), !0);
742
765
  },
743
766
  async release(n) {
744
767
  await e.store.set({ ...t(n), value: { expires: 0 } });
745
768
  }
746
769
  };
747
- }, K = {
770
+ }, ze = ({ strapi: e }) => ({
771
+ async set(t, n) {
772
+ if (!Number.isInteger(t))
773
+ throw new Error("A version id is required.");
774
+ const i = await e.db.query(v).findOne({ where: { id: t }, select: ["id", "pinned"] });
775
+ if (!i)
776
+ throw new Error(`No version with id ${t}`);
777
+ return !!i.pinned === n ? { id: t, pinned: n } : (await e.db.query(v).update({ where: { id: t }, data: { pinned: n } }), { id: t, pinned: n });
778
+ }
779
+ }), K = {
748
780
  keepAllDays: 7,
749
781
  dailyUntilDays: 30,
750
782
  maxAgeDays: 365,
751
783
  keepAnchors: !0
752
- }, ze = /* @__PURE__ */ new Set(["publish", "unpublish", "discardDraft", "restore"]), G = 24 * 60 * 60 * 1e3, Pe = (e, t) => (t.getTime() - new Date(e).getTime()) / G, Ye = (e) => e.toISOString().slice(0, 10), He = (e) => {
784
+ }, Pe = /* @__PURE__ */ new Set(["publish", "unpublish", "discardDraft", "restore"]), G = 24 * 60 * 60 * 1e3, Ye = (e, t) => (t.getTime() - new Date(e).getTime()) / G, He = (e) => e.toISOString().slice(0, 10), We = (e) => {
753
785
  const t = new Date(Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), e.getUTCDate())), n = t.getUTCDay() || 7;
754
786
  t.setUTCDate(t.getUTCDate() + 4 - n);
755
- const r = new Date(Date.UTC(t.getUTCFullYear(), 0, 1)), o = Math.ceil(((t.getTime() - r.getTime()) / G + 1) / 7);
787
+ const i = new Date(Date.UTC(t.getUTCFullYear(), 0, 1)), o = Math.ceil(((t.getTime() - i.getTime()) / G + 1) / 7);
756
788
  return `${t.getUTCFullYear()}-W${o}`;
757
- }, We = (e, t, n = K) => {
758
- const r = /* @__PURE__ */ new Map(), o = [], a = [...e].sort(
759
- (s, i) => new Date(i.createdAt).getTime() - new Date(s.createdAt).getTime()
789
+ }, Ke = (e, t, n = K) => {
790
+ const i = /* @__PURE__ */ new Map(), o = [], a = [...e].sort(
791
+ (s, r) => new Date(r.createdAt).getTime() - new Date(s.createdAt).getTime()
760
792
  );
761
793
  for (const s of a) {
762
- if (s.pinned || n.keepAnchors && ze.has(s.origin)) continue;
763
- const i = Pe(s.createdAt, t);
764
- if (i <= n.keepAllDays) continue;
765
- if (i > n.maxAgeDays) {
794
+ if (s.pinned || n.keepAnchors && Pe.has(s.origin)) continue;
795
+ const r = Ye(s.createdAt, t);
796
+ if (r <= n.keepAllDays) continue;
797
+ if (r > n.maxAgeDays) {
766
798
  o.push(s.id);
767
799
  continue;
768
800
  }
769
- const c = new Date(s.createdAt), l = i <= n.dailyUntilDays ? `d:${Ye(c)}` : `w:${He(c)}`;
770
- r.has(l) ? o.push(s.id) : r.set(l, s);
801
+ const c = new Date(s.createdAt), l = r <= n.dailyUntilDays ? `d:${He(c)}` : `w:${We(c)}`;
802
+ i.has(l) ? o.push(s.id) : i.set(l, s);
771
803
  }
772
804
  return o;
773
- }, P = 500, Ke = ({ strapi: e }) => {
774
- const t = () => e.db.query(v), n = async (r) => {
805
+ }, P = 500, Ge = ({ strapi: e }) => {
806
+ const t = () => e.db.query(v), n = async (i) => {
775
807
  const o = await t().findMany({
776
808
  select: ["contentType", "relatedDocumentId", "locale"],
777
- where: { createdAt: { $lt: r } }
809
+ where: { createdAt: { $lt: i } }
778
810
  }), a = /* @__PURE__ */ new Map();
779
811
  for (const s of o) {
780
- const i = `${s.contentType}|${s.relatedDocumentId}|${s.locale ?? ""}`;
781
- a.has(i) || a.set(i, {
812
+ const r = `${s.contentType}|${s.relatedDocumentId}|${s.locale ?? ""}`;
813
+ a.has(r) || a.set(r, {
782
814
  contentType: s.contentType,
783
815
  relatedDocumentId: s.relatedDocumentId,
784
816
  locale: s.locale ?? null
@@ -793,13 +825,13 @@ const Z = ["id", "documentId", "locale"], S = [
793
825
  * Returns what it did rather than logging and forgetting, so the cron can
794
826
  * report it and a test can assert on it.
795
827
  */
796
- async prune(r = /* @__PURE__ */ new Date()) {
828
+ async prune(i = /* @__PURE__ */ new Date()) {
797
829
  const o = e.config.get("plugin::rewind"), a = { ...K, ...o?.retention };
798
830
  if (o?.retention?.enabled === !1)
799
831
  return { scanned: 0, deleted: 0, documents: 0 };
800
- const s = new Date(r.getTime() - a.keepAllDays * 24 * 60 * 60 * 1e3), i = await n(s);
832
+ const s = new Date(i.getTime() - a.keepAllDays * 24 * 60 * 60 * 1e3), r = await n(s);
801
833
  let c = 0, l = 0;
802
- for (const d of i) {
834
+ for (const d of r) {
803
835
  const p = await t().findMany({
804
836
  select: ["id", "createdAt", "origin", "pinned"],
805
837
  where: {
@@ -809,22 +841,22 @@ const Z = ["id", "documentId", "locale"], S = [
809
841
  }
810
842
  });
811
843
  c += p.length;
812
- const b = We(p, r, a);
844
+ const b = Ke(p, i, a);
813
845
  for (let m = 0; m < b.length; m += P) {
814
846
  const u = b.slice(m, m + P);
815
847
  await t().deleteMany({ where: { id: { $in: u } } }), l += u.length;
816
848
  }
817
849
  }
818
- return { scanned: c, deleted: l, documents: i.length };
850
+ return { scanned: c, deleted: l, documents: r.length };
819
851
  }
820
852
  };
821
- }, Ge = ({ strapi: e }) => {
853
+ }, Ve = ({ strapi: e }) => {
822
854
  const t = (s) => e.getModel(s), n = async (s) => {
823
- const i = await e.db.query(v).findOne({ where: { id: s } });
824
- if (!i)
855
+ const r = await e.db.query(v).findOne({ where: { id: s } });
856
+ if (!r)
825
857
  throw new Error(`No version with id ${s}`);
826
- return i;
827
- }, r = (s) => s?.pluginOptions?.i18n?.localized === !0, o = (s) => e.contentTypes[s]?.pluginOptions?.i18n?.localized === !0, a = async (s) => s.targetUid === "plugin::upload.file" ? !!await e.db.query("plugin::upload.file").findOne({ where: { id: s.id } }) : !!await e.db.query(s.targetUid).findOne({
858
+ return r;
859
+ }, i = (s) => s?.pluginOptions?.i18n?.localized === !0, o = (s) => e.contentTypes[s]?.pluginOptions?.i18n?.localized === !0, a = async (s) => s.targetUid === "plugin::upload.file" ? !!await e.db.query("plugin::upload.file").findOne({ where: { id: s.id } }) : !!await e.db.query(s.targetUid).findOne({
828
860
  where: {
829
861
  documentId: s.documentId,
830
862
  ...s.locale ? { locale: s.locale } : {}
@@ -840,11 +872,11 @@ const Z = ["id", "documentId", "locale"], S = [
840
872
  * version of a document quietly rewrites a field in every other language.
841
873
  */
842
874
  async preview(s) {
843
- const i = await n(s), c = i.contentType, l = t(c).attributes, d = i.schemaSnapshot?.attributes ?? {}, p = i.data ?? {}, b = i.relations ?? {}, m = Object.keys(l).filter((y) => !S.includes(y)), u = Object.keys(d), h = u.filter((y) => !m.includes(y)), f = m.filter((y) => !u.includes(y)), w = u.filter((y) => m.includes(y)), T = w.filter((y) => $(l[y])), g = w.filter((y) => !T.includes(y)), D = o(c) ? g.filter((y) => !r(l[y])) : [], A = [], F = [];
875
+ const r = await n(s), c = r.contentType, l = t(c).attributes, d = r.schemaSnapshot?.attributes ?? {}, p = r.data ?? {}, b = r.relations ?? {}, m = Object.keys(l).filter((y) => !S.includes(y)), u = Object.keys(d), h = u.filter((y) => !m.includes(y)), f = m.filter((y) => !u.includes(y)), w = u.filter((y) => m.includes(y)), T = w.filter((y) => $(l[y])), g = w.filter((y) => !T.includes(y)), D = o(c) ? g.filter((y) => !i(l[y])) : [], A = [], N = [];
844
876
  for (const [y, X] of Object.entries(b))
845
877
  if (g.includes(y))
846
- for (const k of X)
847
- await a(k) || (k.targetUid === "plugin::upload.file" ? F.push(k) : A.push(k));
878
+ for (const O of X)
879
+ await a(O) || (O.targetUid === "plugin::upload.file" ? N.push(O) : A.push(O));
848
880
  const V = D.length ? await e.plugin("i18n")?.service("locales").find() : [];
849
881
  return {
850
882
  fieldsToRestore: g.filter((y) => y in p),
@@ -853,7 +885,7 @@ const Z = ["id", "documentId", "locale"], S = [
853
885
  crossLocaleFields: D,
854
886
  unsupportedFields: T,
855
887
  brokenRelations: A,
856
- missingMedia: F,
888
+ missingMedia: N,
857
889
  affectedLocales: V.map((y) => y.code)
858
890
  };
859
891
  },
@@ -863,7 +895,7 @@ const Z = ["id", "documentId", "locale"], S = [
863
895
  * The document lands in "modified" and publishing stays a deliberate act -
864
896
  * restoring must never change what the public sees on its own.
865
897
  */
866
- async apply(s, i) {
898
+ async apply(s, r) {
867
899
  const c = await n(s), l = c.contentType, d = await this.preview(s), p = [];
868
900
  await e.plugin("rewind").service("snapshot").capture(
869
901
  {
@@ -873,7 +905,7 @@ const Z = ["id", "documentId", "locale"], S = [
873
905
  origin: "restore",
874
906
  before: null
875
907
  },
876
- i
908
+ r
877
909
  );
878
910
  const b = {}, m = c.data ?? {}, u = c.relations ?? {};
879
911
  for (const f of d.fieldsToRestore)
@@ -910,21 +942,22 @@ const Z = ["id", "documentId", "locale"], S = [
910
942
  }), { documentId: c.relatedDocumentId, warnings: p };
911
943
  }
912
944
  };
913
- }, Ve = {
945
+ }, Xe = {
914
946
  snapshot: ie,
915
- restore: Ge,
947
+ restore: Ve,
916
948
  diff: Be,
917
- retention: Ke,
918
- lock: Le
919
- }, Je = {
949
+ retention: Ge,
950
+ lock: Le,
951
+ pin: ze
952
+ }, Qe = {
920
953
  bootstrap: me,
921
954
  destroy: Se,
922
955
  config: we,
923
- contentTypes: Oe,
956
+ contentTypes: ke,
924
957
  controllers: Re,
925
- routes: Fe,
926
- services: Ve
958
+ routes: Ne,
959
+ services: Xe
927
960
  };
928
961
  export {
929
- Je as default
962
+ Qe as default
930
963
  };