@qkix/strapi-plugin-rewind 0.1.3 → 0.1.4
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.
- package/README.md +22 -17
- package/dist/admin/src/diffRegistry.d.ts +1 -1
- package/dist/admin/src/index.d.ts +1 -1
- package/dist/server/index.mjs +7 -7
- package/dist/server/src/config/index.d.ts +1 -1
- package/dist/server/src/content-types/index.d.ts +2 -2
- package/dist/server/src/services/diff.d.ts +6 -6
- package/dist/server/src/services/lock.d.ts +1 -1
- package/dist/server/src/services/restore.d.ts +4 -4
- package/dist/server/src/services/serializer.d.ts +3 -3
- package/dist/server/src/services/snapshot.d.ts +1 -1
- package/dist/server/src/utils/backfillLabels.d.ts +1 -1
- package/dist/server/src/utils/captureContext.d.ts +1 -1
- package/dist/server/src/utils/persistTable.d.ts +2 -2
- package/dist/server/src/utils/textDiff.d.ts +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Strapi sells a feature called **Content History** on its Growth and Enterprise
|
|
|
34
34
|
plans. Rewind is **not that feature**, is not a drop-in replacement for it, and
|
|
35
35
|
does not unlock it.
|
|
36
36
|
|
|
37
|
-
It is the poor cousin. If you pay for Growth, use Strapi's
|
|
37
|
+
It is the poor cousin. If you pay for Growth, use Strapi's - it is deeper, it is
|
|
38
38
|
supported by the people who wrote the CMS, and it will stay in step with Strapi
|
|
39
39
|
in a way a third-party plugin cannot promise. Rewind exists for everyone on
|
|
40
40
|
Community Edition, who today has nothing at all: no way to see what a document
|
|
@@ -56,7 +56,7 @@ cheap to add once the plumbing existed:
|
|
|
56
56
|
- **Writes from outside the Content Manager can be versioned** (opt-in), where
|
|
57
57
|
Strapi only ever records admin-panel edits.
|
|
58
58
|
|
|
59
|
-
And a good deal it does _not_ do
|
|
59
|
+
And a good deal it does _not_ do - see [Limits](#limits).
|
|
60
60
|
|
|
61
61
|
### On not touching the paywall
|
|
62
62
|
|
|
@@ -64,8 +64,8 @@ Rewind never reads or writes `strapi.ee`, never inspects a licence, and never
|
|
|
64
64
|
enables a gated feature. It is an independent implementation writing to its own
|
|
65
65
|
table, and it works the same whether or not you have a Strapi licence. Strapi's
|
|
66
66
|
history code is MIT-licensed and was read while working out what the problem
|
|
67
|
-
actually is
|
|
68
|
-
writing your own
|
|
67
|
+
actually is - the way you would read any open-source implementation before
|
|
68
|
+
writing your own - but the code here is written for this plugin.
|
|
69
69
|
|
|
70
70
|
## Install
|
|
71
71
|
|
|
@@ -94,7 +94,7 @@ the first disk alert.
|
|
|
94
94
|
|
|
95
95
|
Six actions on a tracked content type: `create`, `update`, `clone`, `publish`,
|
|
96
96
|
`unpublish` and `discardDraft`. Each version stores the document's content, its
|
|
97
|
-
relations, and a snapshot of the schema at the time
|
|
97
|
+
relations, and a snapshot of the schema at the time - the last of which is what
|
|
98
98
|
lets a restore tell "this field was empty" apart from "this field did not exist
|
|
99
99
|
yet".
|
|
100
100
|
|
|
@@ -106,7 +106,7 @@ A save that is rolled back records nothing.
|
|
|
106
106
|
<img src="https://raw.githubusercontent.com/qkix/strapi-plugins/main/packages/strapi-plugin-rewind/docs/panel.png" alt="History entries labelled Replaced by restore, Draft discarded and Edited" width="320" />
|
|
107
107
|
|
|
108
108
|
The badge says what happened to the document. **Replaced by restore** marks the
|
|
109
|
-
state a restore was about to overwrite
|
|
109
|
+
state a restore was about to overwrite - the undo point for that restore.
|
|
110
110
|
|
|
111
111
|
## Restoring
|
|
112
112
|
|
|
@@ -125,7 +125,7 @@ part of a restore is what it will _not_ do:
|
|
|
125
125
|
- fields no longer in the model are skipped
|
|
126
126
|
- links to documents or media that have since been deleted are dropped, and
|
|
127
127
|
reported
|
|
128
|
-
- **fields that are not translated per locale change in every locale at once**
|
|
128
|
+
- **fields that are not translated per locale change in every locale at once** -
|
|
129
129
|
restoring the Polish version of a document rewrites a shared field for every
|
|
130
130
|
other language too. This is not a choice the plugin makes; a non-localised
|
|
131
131
|
field physically has one value. The dialog names those fields and counts the
|
|
@@ -138,14 +138,14 @@ with the one saved immediately before it.
|
|
|
138
138
|
|
|
139
139
|
<img src="https://raw.githubusercontent.com/qkix/strapi-plugins/main/packages/strapi-plugin-rewind/docs/changes.png" alt="A word-level diff: 'put back' struck through and replaced by 'restored in one click', and 'ten' by 'twelve'" width="760" />
|
|
140
140
|
|
|
141
|
-
Scalars are shown as a plain before and after. Prose
|
|
142
|
-
stored as JSON
|
|
141
|
+
Scalars are shown as a plain before and after. Prose - including rich text
|
|
142
|
+
stored as JSON - is compared word by word, with the unchanged stretches
|
|
143
143
|
collapsed, so a one-word edit in a long article reads as one word rather than
|
|
144
144
|
as the whole article with something green in it somewhere. Relations are
|
|
145
145
|
reported as linked and unlinked.
|
|
146
146
|
|
|
147
|
-
Where a field's stored value changed but its readable text did not
|
|
148
|
-
applied, blocks reordered
|
|
147
|
+
Where a field's stored value changed but its readable text did not - a mark
|
|
148
|
+
applied, blocks reordered - it says so, rather than showing an empty diff.
|
|
149
149
|
|
|
150
150
|
### Rendering a field type properly
|
|
151
151
|
|
|
@@ -156,7 +156,7 @@ package that owns the format can show that, so the mapping is a registry:
|
|
|
156
156
|
```ts
|
|
157
157
|
import { registerDiffRenderer } from '@qkix/strapi-plugin-rewind/strapi-admin';
|
|
158
158
|
|
|
159
|
-
import { MyBlocksDiff } from './MyBlocksDiff'; // yours
|
|
159
|
+
import { MyBlocksDiff } from './MyBlocksDiff'; // yours - see below
|
|
160
160
|
|
|
161
161
|
registerDiffRenderer('plugin::better-blocks.better-blocks', MyBlocksDiff);
|
|
162
162
|
```
|
|
@@ -165,8 +165,8 @@ Keyed by a custom field's uid, or by an attribute `type`. A uid wins over a
|
|
|
165
165
|
type, since every custom field stores itself as `json`.
|
|
166
166
|
|
|
167
167
|
> **Bring your own renderer.** Rewind ships the registry, not the renderers. No
|
|
168
|
-
> `@qkix` package currently exports a block-aware diff component
|
|
169
|
-
> Better Blocks
|
|
168
|
+
> `@qkix` package currently exports a block-aware diff component - including
|
|
169
|
+
> Better Blocks - so the component above is one you write. A renderer receives
|
|
170
170
|
> the two values and returns the `FieldChange` shape exported alongside
|
|
171
171
|
> `registerDiffRenderer`; without one registered, a field falls back to the
|
|
172
172
|
> generic text diff. A ready-made renderer for Better Blocks documents is
|
|
@@ -185,8 +185,8 @@ type, since every custom field stores itself as `json`.
|
|
|
185
185
|
| `retention.keepAnchors` | `true` | Never thin publish, unpublish, discard or restore versions, whatever their age. |
|
|
186
186
|
| `cron` | `'0 3 * * *'` | When the thinning runs, in the server's timezone. |
|
|
187
187
|
|
|
188
|
-
The three windows must widen in order
|
|
189
|
-
maxAgeDays`
|
|
188
|
+
The three windows must widen in order - `keepAllDays <= dailyUntilDays <=
|
|
189
|
+
maxAgeDays` - and the plugin refuses to boot rather than delete on a guess if
|
|
190
190
|
they do not. Pinned versions are never thinned either, independently of
|
|
191
191
|
`keepAnchors`.
|
|
192
192
|
|
|
@@ -213,7 +213,7 @@ Worth knowing before you install it, not after:
|
|
|
213
213
|
## Keeping your history when the plugin is off
|
|
214
214
|
|
|
215
215
|
Strapi's schema sync drops tables belonging to content types it no longer sees.
|
|
216
|
-
Disable this plugin for a single boot and the history would go with it
|
|
216
|
+
Disable this plugin for a single boot and the history would go with it - so on
|
|
217
217
|
boot Rewind adds its table to Strapi's `persisted_tables` list, which is the
|
|
218
218
|
same mechanism Strapi uses to protect its own. Nothing to configure.
|
|
219
219
|
|
|
@@ -221,6 +221,11 @@ same mechanism Strapi uses to protect its own. Nothing to configure.
|
|
|
221
221
|
|
|
222
222
|
Strapi 5, Node 20 or 22.
|
|
223
223
|
|
|
224
|
+
## Roadmap
|
|
225
|
+
|
|
226
|
+
What is worth building next, what is deliberately not planned, and why:
|
|
227
|
+
[ROADMAP.md](https://github.com/qkix/strapi-plugins/blob/main/packages/strapi-plugin-rewind/ROADMAP.md).
|
|
228
|
+
|
|
224
229
|
## License
|
|
225
230
|
|
|
226
231
|
MIT.
|
|
@@ -42,7 +42,7 @@ export type DiffRenderer = React.ComponentType<{
|
|
|
42
42
|
* Keyed by the attribute's `type`, or by a custom field's uid.
|
|
43
43
|
*
|
|
44
44
|
* `MyBlocksDiff` above is the caller's own component. No @qkix package exports
|
|
45
|
-
* one today
|
|
45
|
+
* one today - this is the hook for writing it, not a wiring instruction for
|
|
46
46
|
* something that already exists. Without a renderer registered, the field falls
|
|
47
47
|
* back to the generic text diff.
|
|
48
48
|
*/
|
|
@@ -11,7 +11,7 @@ declare const _default: {
|
|
|
11
11
|
export default _default;
|
|
12
12
|
export { PLUGIN_ID } from './pluginId';
|
|
13
13
|
/**
|
|
14
|
-
* The hook a package owning a field format uses to render its own diff
|
|
14
|
+
* The hook a package owning a field format uses to render its own diff - see
|
|
15
15
|
* diffRegistry for why this is a registry and not a switch.
|
|
16
16
|
*/
|
|
17
17
|
export { registerDiffRenderer } from './diffRegistry';
|
package/dist/server/index.mjs
CHANGED
|
@@ -152,7 +152,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
152
152
|
* Writes one version per affected locale, for each buffered intent.
|
|
153
153
|
*
|
|
154
154
|
* Called from the transaction's commit hook and never awaited by the write
|
|
155
|
-
* path
|
|
155
|
+
* path - a failure here must not be able to reach the editor's save.
|
|
156
156
|
*/
|
|
157
157
|
async captureAll(i, c) {
|
|
158
158
|
for (const l of i)
|
|
@@ -203,7 +203,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
203
203
|
const a = j.indexOf(n.origin) < j.indexOf(o.origin);
|
|
204
204
|
t.set(r, {
|
|
205
205
|
// A winning `publish` still wants the rows an earlier `discardDraft`
|
|
206
|
-
// captured
|
|
206
|
+
// captured - losing them would lose the only copy of that state.
|
|
207
207
|
...a ? n : o,
|
|
208
208
|
before: o.before ?? n.before
|
|
209
209
|
});
|
|
@@ -342,7 +342,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
342
342
|
* On by default, because a version table that only grows is a problem the
|
|
343
343
|
* plugin would be creating rather than solving. The defaults are gentle:
|
|
344
344
|
* a full week of every save, then a version a day for a month, then one a
|
|
345
|
-
* week
|
|
345
|
+
* week - and anchors (publish, unpublish, discard, restore) and pinned
|
|
346
346
|
* versions are never removed at all, whatever their age.
|
|
347
347
|
*
|
|
348
348
|
* Set `enabled: false` to keep everything forever and manage it yourself.
|
|
@@ -677,8 +677,8 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
677
677
|
/**
|
|
678
678
|
* What changed between a version and the one before it.
|
|
679
679
|
*
|
|
680
|
-
* Comparing against the previous version
|
|
681
|
-
* as it stands
|
|
680
|
+
* Comparing against the previous version - rather than against the document
|
|
681
|
+
* as it stands - is what makes a history readable: each entry answers "what
|
|
682
682
|
* did this edit do?". The other question, "what would restoring this
|
|
683
683
|
* change?", is what `restore.preview()` already answers.
|
|
684
684
|
*/
|
|
@@ -836,7 +836,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
836
836
|
*
|
|
837
837
|
* A separate call, and the UI shows it before asking for confirmation.
|
|
838
838
|
* Restore without a preview of the consequences is a trap rather than a
|
|
839
|
-
* tool
|
|
839
|
+
* tool - `crossLocaleFields` in particular, where restoring the Polish
|
|
840
840
|
* version of a document quietly rewrites a field in every other language.
|
|
841
841
|
*/
|
|
842
842
|
async preview(s) {
|
|
@@ -860,7 +860,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
860
860
|
/**
|
|
861
861
|
* Writes the version back, to the draft only.
|
|
862
862
|
*
|
|
863
|
-
* The document lands in "modified" and publishing stays a deliberate act
|
|
863
|
+
* The document lands in "modified" and publishing stays a deliberate act -
|
|
864
864
|
* restoring must never change what the public sees on its own.
|
|
865
865
|
*/
|
|
866
866
|
async apply(s, i) {
|
|
@@ -31,7 +31,7 @@ declare const _default: {
|
|
|
31
31
|
* On by default, because a version table that only grows is a problem the
|
|
32
32
|
* plugin would be creating rather than solving. The defaults are gentle:
|
|
33
33
|
* a full week of every save, then a version a day for a month, then one a
|
|
34
|
-
* week
|
|
34
|
+
* week - and anchors (publish, unpublish, discard, restore) and pinned
|
|
35
35
|
* versions are never removed at all, whatever their age.
|
|
36
36
|
*
|
|
37
37
|
* Set `enabled: false` to keep everything forever and manage it yourself.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* The versions table.
|
|
3
3
|
*
|
|
4
4
|
* `relatedDocumentId`, not `documentId`: Strapi injects a `documentId` into
|
|
5
|
-
* every content type and rejects the name outright
|
|
6
|
-
* "documentId" is reserved and cannot be used in a model"
|
|
5
|
+
* every content type and rejects the name outright - "The attribute
|
|
6
|
+
* "documentId" is reserved and cannot be used in a model" - at boot, before
|
|
7
7
|
* anything else runs. Strapi's own history model calls it `relatedDocumentId`
|
|
8
8
|
* for the same reason.
|
|
9
9
|
*
|
|
@@ -8,17 +8,17 @@ export interface FieldChange {
|
|
|
8
8
|
type: string;
|
|
9
9
|
/**
|
|
10
10
|
* A custom field's uid, when the attribute is one. Custom fields all report
|
|
11
|
-
* their storage type
|
|
11
|
+
* their storage type - usually `json` - so this is the only thing specific
|
|
12
12
|
* enough for the owning package to key a diff renderer on.
|
|
13
13
|
*/
|
|
14
14
|
customField?: string;
|
|
15
15
|
kind: ChangeKind;
|
|
16
|
-
/** Scalars only
|
|
16
|
+
/** Scalars only - the values themselves, for a plain before/after. */
|
|
17
17
|
before?: unknown;
|
|
18
18
|
after?: unknown;
|
|
19
|
-
/** Prose fields
|
|
19
|
+
/** Prose fields - a word-level diff of the readable text. */
|
|
20
20
|
spans?: DiffSpan[];
|
|
21
|
-
/** Relations and media
|
|
21
|
+
/** Relations and media - what was linked and unlinked. */
|
|
22
22
|
linked?: RelationRef[];
|
|
23
23
|
unlinked?: RelationRef[];
|
|
24
24
|
}
|
|
@@ -45,8 +45,8 @@ declare const diff: ({ strapi }: {
|
|
|
45
45
|
/**
|
|
46
46
|
* What changed between a version and the one before it.
|
|
47
47
|
*
|
|
48
|
-
* Comparing against the previous version
|
|
49
|
-
* as it stands
|
|
48
|
+
* Comparing against the previous version - rather than against the document
|
|
49
|
+
* as it stands - is what makes a history readable: each entry answers "what
|
|
50
50
|
* did this edit do?". The other question, "what would restoring this
|
|
51
51
|
* change?", is what `restore.preview()` already answers.
|
|
52
52
|
*/
|
|
@@ -8,7 +8,7 @@ import type { Core } from '@strapi/strapi';
|
|
|
8
8
|
*
|
|
9
9
|
* Best-effort is the honest description: read-then-write is not atomic, so two
|
|
10
10
|
* instances starting within the same millisecond can both win. That is
|
|
11
|
-
* acceptable for a nightly tidy-up and not for anything else
|
|
11
|
+
* acceptable for a nightly tidy-up and not for anything else - do not reuse
|
|
12
12
|
* this for something that must happen exactly once.
|
|
13
13
|
*/
|
|
14
14
|
declare const lock: ({ strapi }: {
|
|
@@ -3,9 +3,9 @@ import type { RelationRef } from './serializer';
|
|
|
3
3
|
export interface RestorePreview {
|
|
4
4
|
/** Fields that will be written back. */
|
|
5
5
|
fieldsToRestore: string[];
|
|
6
|
-
/** In the model but not in the version
|
|
6
|
+
/** In the model but not in the version - left exactly as they are. */
|
|
7
7
|
fieldsKeptAsIs: string[];
|
|
8
|
-
/** In the version but no longer in the model
|
|
8
|
+
/** In the version but no longer in the model - skipped. */
|
|
9
9
|
fieldsDropped: string[];
|
|
10
10
|
/** Not localised, so restoring them changes every locale at once. */
|
|
11
11
|
crossLocaleFields: string[];
|
|
@@ -26,14 +26,14 @@ declare const restore: ({ strapi }: {
|
|
|
26
26
|
*
|
|
27
27
|
* A separate call, and the UI shows it before asking for confirmation.
|
|
28
28
|
* Restore without a preview of the consequences is a trap rather than a
|
|
29
|
-
* tool
|
|
29
|
+
* tool - `crossLocaleFields` in particular, where restoring the Polish
|
|
30
30
|
* version of a document quietly rewrites a field in every other language.
|
|
31
31
|
*/
|
|
32
32
|
preview(versionId: number): Promise<RestorePreview>;
|
|
33
33
|
/**
|
|
34
34
|
* Writes the version back, to the draft only.
|
|
35
35
|
*
|
|
36
|
-
* The document lands in "modified" and publishing stays a deliberate act
|
|
36
|
+
* The document lands in "modified" and publishing stays a deliberate act -
|
|
37
37
|
* restoring must never change what the public sees on its own.
|
|
38
38
|
*/
|
|
39
39
|
apply(versionId: number, userId: number | null): Promise<{
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Written for this plugin rather than lifted from Strapi's own history module.
|
|
5
5
|
* Where the shapes here resemble theirs it is because Strapi's populate API
|
|
6
|
-
* dictates them
|
|
7
|
-
* dynamic zone only through `on` fragments
|
|
6
|
+
* dictates them - a relation can only be populated by naming its fields, a
|
|
7
|
+
* dynamic zone only through `on` fragments - not because the code was copied.
|
|
8
8
|
* Their implementation is MIT and was read while working out what the problem
|
|
9
9
|
* actually is; two facts learned that way are worth stating outright, since
|
|
10
10
|
* both cost a day if you meet them the hard way:
|
|
@@ -26,7 +26,7 @@ import type { Core } from '@strapi/strapi';
|
|
|
26
26
|
* The timestamps and author columns change on every write, so a version that
|
|
27
27
|
* keeps them can never be compared with another. `locale` and `localizations`
|
|
28
28
|
* are injected by i18n and describe how the document is wired up rather than
|
|
29
|
-
* what it says
|
|
29
|
+
* what it says - restoring them would try to rewrite the translation graph.
|
|
30
30
|
*/
|
|
31
31
|
export declare const FIELDS_TO_IGNORE: string[];
|
|
32
32
|
export interface RelationRef {
|
|
@@ -9,7 +9,7 @@ declare const snapshot: ({ strapi }: {
|
|
|
9
9
|
* Writes one version per affected locale, for each buffered intent.
|
|
10
10
|
*
|
|
11
11
|
* Called from the transaction's commit hook and never awaited by the write
|
|
12
|
-
* path
|
|
12
|
+
* path - a failure here must not be able to reach the editor's save.
|
|
13
13
|
*/
|
|
14
14
|
captureAll(intents: SnapshotIntent[], userId: number | null): Promise<void>;
|
|
15
15
|
capture(intent: SnapshotIntent, userId: number | null): Promise<void>;
|
|
@@ -3,7 +3,7 @@ import type { Core } from '@strapi/strapi';
|
|
|
3
3
|
* Gives versions recorded before labels existed something to show.
|
|
4
4
|
*
|
|
5
5
|
* Without this, upgrading leaves a history where older entries are a badge and
|
|
6
|
-
* a timestamp and newer ones have a title
|
|
6
|
+
* a timestamp and newer ones have a title - the list stays half-unreadable for
|
|
7
7
|
* exactly as long as the old versions are worth keeping.
|
|
8
8
|
*
|
|
9
9
|
* Two details that matter:
|
|
@@ -11,7 +11,7 @@ export interface SnapshotIntent {
|
|
|
11
11
|
* Intents buffered against the transaction they belong to.
|
|
12
12
|
*
|
|
13
13
|
* Keyed by the Knex transaction object because that is the only thing the
|
|
14
|
-
* Content Manager's `update` and `publish` calls have in common
|
|
14
|
+
* Content Manager's `update` and `publish` calls have in common - they are
|
|
15
15
|
* sequential siblings, not nested, so nothing about the call stack relates
|
|
16
16
|
* them. Strapi hands the same `trx` to every nested `db.transaction`, which
|
|
17
17
|
* makes it a reliable key, and a WeakMap means a finished transaction takes its
|
|
@@ -3,8 +3,8 @@ import type { Core } from '@strapi/strapi';
|
|
|
3
3
|
* Keeps the versions table alive when the plugin is not loaded.
|
|
4
4
|
*
|
|
5
5
|
* Strapi's schema sync drops any table it previously tracked that is no longer
|
|
6
|
-
* in the user schema. Disable this plugin for a single boot
|
|
7
|
-
* an upgrade
|
|
6
|
+
* in the user schema. Disable this plugin for a single boot - to debug, to test
|
|
7
|
+
* an upgrade - and the history it exists to protect is gone without a prompt.
|
|
8
8
|
*
|
|
9
9
|
* `persisted_tables` is the core-store key `@strapi/database` consults before
|
|
10
10
|
* dropping (schema/diff.js). The EE `admin::persist-tables` service writes the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A word-level diff, and the text extraction that makes it useful.
|
|
3
3
|
*
|
|
4
|
-
* The alternative
|
|
4
|
+
* The alternative - reporting "content changed" - is true and worthless. The
|
|
5
5
|
* field an editor cares most about is usually rich text stored as JSON, so the
|
|
6
6
|
* readable part is pulled out and compared as prose. That is renderer-agnostic:
|
|
7
7
|
* it walks the structure collecting anything that reads as text, without
|
|
@@ -29,7 +29,7 @@ export declare const diffWords: (before: string, after: string) => DiffSpan[];
|
|
|
29
29
|
* Pulls the human-readable text out of an arbitrary value.
|
|
30
30
|
*
|
|
31
31
|
* Rich text, components and dynamic zones are all nested objects and arrays
|
|
32
|
-
* whose shape this plugin deliberately does not model
|
|
32
|
+
* whose shape this plugin deliberately does not model - a `text` key here, a
|
|
33
33
|
* `children` array there. Collecting every string reached is imprecise but
|
|
34
34
|
* stable across editors, and it is what turns "content changed" into a sentence
|
|
35
35
|
* an editor recognises.
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qkix/strapi-plugin-rewind",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Document version history for Strapi v5
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Document version history for Strapi v5 - snapshots on every save, with restore",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"kind": "plugin",
|
|
7
7
|
"name": "rewind",
|
|
8
8
|
"displayName": "Rewind",
|
|
9
|
-
"description": "Document version history for Strapi v5
|
|
9
|
+
"description": "Document version history for Strapi v5 - snapshots on every save, with restore"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"strapi",
|