@nascentdigital/funnel-editor-contentful 4.6.7 → 5.0.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.
Files changed (85) hide show
  1. package/dist/Editor.d.ts +6 -1
  2. package/dist/Editor.js +17 -1
  3. package/dist/EditorApp.js +1 -1
  4. package/dist/EntryEditor.js +42 -1
  5. package/dist/EntrySidebar.js +18 -1
  6. package/dist/collection/controller/LocalCollectionController.d.ts +35 -0
  7. package/dist/collection/controller/LocalCollectionController.js +200 -0
  8. package/dist/collection/controller/LocalCollectionControllerContainer.d.ts +8 -0
  9. package/dist/collection/controller/LocalCollectionControllerContainer.js +36 -0
  10. package/dist/collection/controller/LocalCollectionControllerContext.d.ts +7 -0
  11. package/dist/collection/controller/LocalCollectionControllerContext.js +6 -0
  12. package/dist/collection/controller/index.d.ts +4 -0
  13. package/dist/collection/controller/index.js +20 -0
  14. package/dist/collection/controller/useLocalCollectionController.d.ts +2 -0
  15. package/dist/collection/controller/useLocalCollectionController.js +10 -0
  16. package/dist/collection/index.d.ts +2 -0
  17. package/dist/collection/index.js +18 -0
  18. package/dist/collection/screens/CollectionEditor.d.ts +1 -0
  19. package/dist/collection/screens/CollectionEditor.js +42 -0
  20. package/dist/collection/screens/index.d.ts +1 -0
  21. package/dist/{page/hooks → collection/screens}/index.js +1 -1
  22. package/dist/core/bridge/AppBridgeContainer.d.ts +9 -0
  23. package/dist/core/bridge/AppBridgeContainer.js +55 -0
  24. package/dist/core/bridge/AppBridgeContext.d.ts +8 -0
  25. package/dist/core/bridge/AppBridgeContext.js +11 -0
  26. package/dist/core/bridge/AppMessage.d.ts +19 -0
  27. package/dist/core/bridge/AppMessage.js +19 -0
  28. package/dist/core/bridge/index.d.ts +4 -0
  29. package/dist/core/bridge/index.js +20 -0
  30. package/dist/core/bridge/useAppBridge.d.ts +8 -0
  31. package/dist/core/bridge/useAppBridge.js +24 -0
  32. package/dist/core/contentful/Contentful.d.ts +33 -0
  33. package/dist/core/contentful/Contentful.js +67 -0
  34. package/dist/core/contentful/ContentfulAsset.d.ts +23 -0
  35. package/dist/core/contentful/ContentfulAsset.js +88 -0
  36. package/dist/core/contentful/ContentfulCollection.d.ts +29 -0
  37. package/dist/core/contentful/ContentfulCollection.js +97 -0
  38. package/dist/core/contentful/ContentfulPage.d.ts +31 -0
  39. package/dist/core/contentful/ContentfulPage.js +84 -0
  40. package/dist/core/contentful/ContentfulRefs.d.ts +16 -0
  41. package/dist/core/contentful/ContentfulRefs.js +152 -0
  42. package/dist/core/contentful/index.d.ts +5 -0
  43. package/dist/core/contentful/index.js +21 -0
  44. package/dist/core/domain/Collection.d.ts +99 -0
  45. package/dist/core/domain/Collection.js +311 -0
  46. package/dist/core/domain/Page.d.ts +58 -8
  47. package/dist/core/domain/Page.js +329 -16
  48. package/dist/core/domain/index.d.ts +1 -0
  49. package/dist/core/domain/index.js +1 -0
  50. package/dist/core/index.d.ts +2 -0
  51. package/dist/core/index.js +2 -0
  52. package/dist/core/repository/AssetRepository.d.ts +10 -0
  53. package/dist/core/repository/AssetRepository.js +33 -0
  54. package/dist/core/repository/CollectionSerializer.d.ts +9 -0
  55. package/dist/core/repository/CollectionSerializer.js +41 -0
  56. package/dist/core/repository/LocalCollectionRepository.d.ts +27 -0
  57. package/dist/core/repository/LocalCollectionRepository.js +143 -0
  58. package/dist/core/repository/LocalPageRepository.d.ts +16 -0
  59. package/dist/core/repository/LocalPageRepository.js +153 -0
  60. package/dist/core/repository/PageSerializer.d.ts +2 -2
  61. package/dist/core/repository/PageSerializer.js +16 -13
  62. package/dist/core/repository/RemoteCollectionRepository.d.ts +19 -0
  63. package/dist/core/repository/RemoteCollectionRepository.js +236 -0
  64. package/dist/core/repository/RemotePageRepository.d.ts +14 -0
  65. package/dist/core/repository/RemotePageRepository.js +113 -0
  66. package/dist/core/repository/index.d.ts +5 -1
  67. package/dist/core/repository/index.js +5 -1
  68. package/dist/page/{hooks/PageController.d.ts → controller/LocalPageController.d.ts} +11 -6
  69. package/dist/page/controller/LocalPageController.js +190 -0
  70. package/dist/page/controller/LocalPageControllerContainer.d.ts +8 -0
  71. package/dist/page/controller/LocalPageControllerContainer.js +36 -0
  72. package/dist/page/controller/LocalPageControllerContext.d.ts +7 -0
  73. package/dist/page/controller/LocalPageControllerContext.js +6 -0
  74. package/dist/page/controller/index.d.ts +3 -0
  75. package/dist/page/controller/index.js +19 -0
  76. package/dist/page/controller/useLocalPageController.d.ts +2 -0
  77. package/dist/page/controller/useLocalPageController.js +10 -0
  78. package/dist/page/index.d.ts +1 -0
  79. package/dist/page/index.js +1 -0
  80. package/dist/page/screens/PageEditor.js +9 -11
  81. package/package.json +5 -3
  82. package/dist/core/repository/PageRepository.d.ts +0 -12
  83. package/dist/core/repository/PageRepository.js +0 -23
  84. package/dist/page/hooks/PageController.js +0 -107
  85. package/dist/page/hooks/index.d.ts +0 -1
@@ -1,42 +1,75 @@
1
1
  import { AssetClient } from '@nascentdigital/funnel-cms';
2
- import { EventBus, Language, Schema } from '@nascentdigital/funnel-core';
2
+ import { EventBus, FieldModel, Language, Schema } from '@nascentdigital/funnel-core';
3
3
  import { Asset, Block, IPage, PageSettings, Section, SectionBlock } from '@nascentdigital/funnel-editor/components';
4
4
  import { Observable } from 'rxjs';
5
5
  export declare class Page implements IPage {
6
6
  readonly id: Schema.Page.Id;
7
7
  readonly template: Schema.Page;
8
8
  readonly eventBus: EventBus;
9
+ readonly settings: PageSettings;
9
10
  readonly groupId: Schema.Page.GroupId;
10
11
  readonly brand: string;
11
12
  readonly language: Language;
12
13
  readonly region: string;
13
- readonly url: string;
14
14
  readonly publishedOn?: number;
15
15
  readonly createdOn: number;
16
- readonly updatedOn: number;
16
+ private _state;
17
+ private _initialErrors;
18
+ private readonly _discover;
17
19
  private readonly _content;
18
20
  private readonly _assets;
19
21
  private readonly _pages;
20
22
  private readonly _collections;
21
- private readonly _tags;
22
23
  private _initialTags;
24
+ private readonly _tags;
25
+ private _initialName;
26
+ private _name;
23
27
  private _initialSlug;
28
+ private _url;
24
29
  private _slug;
25
- private _syncing;
30
+ private _initialPublication?;
31
+ private _publication?;
32
+ private _updatedOn;
26
33
  private _modifiedOn?;
34
+ private _deletedBlocks;
35
+ private _syncing;
36
+ private readonly _subscription;
37
+ private _savingOn?;
27
38
  constructor(args: Page.ConstructorArgs);
28
- settings: PageSettings;
29
- publication: number | undefined;
30
39
  get events$(): Observable<IPage.Events>;
31
40
  get stable$(): Observable<boolean>;
41
+ get state(): Schema.Page.State;
42
+ private set state(value);
32
43
  get modified(): boolean;
44
+ get updatedOn(): number;
45
+ get changes(): Page.Changes;
33
46
  get published(): boolean;
47
+ get type(): Schema.Page.Type;
34
48
  get syncing(): boolean;
49
+ set syncing(syncing: boolean);
50
+ repair(): void;
51
+ get url(): string;
52
+ get initialSlug(): string | undefined;
35
53
  get hasSlug(): boolean;
54
+ set slug(slug: string | undefined);
36
55
  get slug(): string | undefined;
56
+ get initialName(): string;
57
+ get name(): string;
58
+ set name(name: string);
59
+ get initialPublication(): number | undefined;
60
+ get publication(): number | undefined;
61
+ set publication(publication: number | undefined);
62
+ get initialErrors(): boolean;
63
+ get errors(): Page.Errors;
64
+ get valid(): boolean;
65
+ get hasErrors(): boolean;
66
+ get asRef(): IPage.Ref;
37
67
  get discover(): ReadonlyArray<Block>;
68
+ get seo(): Block<Schema.Block<string, any, any>>;
69
+ getPreviewBlock(): Block | undefined;
38
70
  get content(): ReadonlyArray<Section | Block>;
39
71
  get blocks(): (Block<Schema.Block<string, any, any>> | SectionBlock<Schema.Block<string, any, any>>)[];
72
+ get deleteBlocks(): ReadonlyArray<Block>;
40
73
  get firstBlock(): Block<Schema.Block<string, any, any>> | SectionBlock<Schema.Block<string, any, any>> | undefined;
41
74
  get firstItem(): Block<Schema.Block<string, any, any>> | Section;
42
75
  get items(): Array<Block | Section>;
@@ -45,9 +78,10 @@ export declare class Page implements IPage {
45
78
  get initialTags(): ReadonlySet<Schema.Tag<string, string>>;
46
79
  get tagGroups(): ReadonlyArray<Schema.TagGroup>;
47
80
  get tags(): ReadonlySet<Schema.Tag>;
81
+ hydrateStores(references: IPage.References): void;
48
82
  getItem(identifier: string | undefined): Block | SectionBlock | Section | PageSettings | undefined;
49
83
  coerceItem(identifier: string | undefined): Block | SectionBlock | Section | PageSettings;
50
- deleteBlock(area: IPage.Area, key: string): void;
84
+ deleteBlock(area: IPage.Area, key: string): boolean;
51
85
  getAsset(assetId: Asset.Id): Asset | undefined;
52
86
  getAssetScope(range: Schema.AssetScope.Range): Schema.AssetScope;
53
87
  getSearchScope(): AssetClient.GetAssets.Scope;
@@ -61,6 +95,10 @@ export declare class Page implements IPage {
61
95
  removePageRef(pageId: Schema.Page.Id): boolean;
62
96
  addTag(tag: Schema.Tag): void;
63
97
  removeTag(tag: Schema.Tag): void;
98
+ onSaveStart(): void;
99
+ onSaveComplete(updatedOn: number): void;
100
+ private getBlock;
101
+ private raiseEvent;
64
102
  }
65
103
  export declare namespace Page {
66
104
  type ConstructorArgs = {
@@ -75,15 +113,27 @@ export declare namespace Page {
75
113
  slug?: string;
76
114
  name: string;
77
115
  tags: Schema.Tag[];
116
+ discover: Blocks;
78
117
  content: Content;
79
118
  files?: Asset[];
80
119
  pages?: IPage.Ref[];
81
120
  collections?: Schema.Collection.Data.AsLink[];
82
121
  state: Schema.Page.State;
122
+ errors: boolean;
83
123
  createdOn: number;
84
124
  updatedOn: number;
85
125
  publishedOn?: number;
86
126
  publication?: number;
87
127
  };
128
+ type Blocks = Block[];
88
129
  type Content = Array<Block | Section>;
130
+ type Errors = {
131
+ page: Page;
132
+ items: Array<Block | Section>;
133
+ };
134
+ type Changes = {
135
+ page: Page;
136
+ items: Array<Block | Section>;
137
+ };
138
+ type Target = Block | SectionBlock | Section | FieldModel;
89
139
  }
@@ -16,21 +16,35 @@ class Page {
16
16
  this.brand = args.brand;
17
17
  this.language = args.language;
18
18
  this.region = args.region;
19
- this.url = args.url;
20
19
  this.publishedOn = args.publishedOn;
21
20
  this.createdOn = args.createdOn;
22
- this.updatedOn = args.updatedOn;
23
21
  this.settings = new components_1.PageSettings(this);
22
+ this._state = args.state;
23
+ this._initialErrors = args.errors;
24
+ this._initialName = this._name = args.name;
25
+ this._updatedOn = args.updatedOn;
26
+ this._discover = args.discover;
24
27
  this._content = args.content;
28
+ this._url = args.url;
25
29
  this._initialSlug = this._slug = args.slug;
30
+ this._initialPublication = this._publication = args.publication;
26
31
  this._assets = (_a = args.files) !== null && _a !== void 0 ? _a : [];
27
32
  this._pages = (_b = args.pages) !== null && _b !== void 0 ? _b : [];
28
33
  this._collections = (_c = args.collections) !== null && _c !== void 0 ? _c : [];
29
34
  this._initialTags = new Set(args.tags);
30
35
  this._tags = new Set(args.tags);
36
+ this._deletedBlocks = [];
31
37
  this._syncing = false;
32
38
  // bind content
39
+ this._discover.forEach(item => item.bind({ parent: this, area: 'discover' }));
33
40
  this._content.forEach((item) => item.bind({ parent: this, area: 'content' }));
41
+ // listen to block/field changes
42
+ this._subscription = this.eventBus.events$
43
+ .pipe((0, rxjs_1.filter)(e => (funnel_core_1.Field.Event.filter(e) && e.type === 'changed' && !e.field.sandboxed)
44
+ || (funnel_core_1.FieldList.Event.filter(e) && e.type === 'changed')
45
+ || (funnel_core_1.ObjectField.Event.filter(e) && e.type === 'changed')
46
+ || (components_1.Item.Event.filter(e) && e.type === 'changed')))
47
+ .subscribe(() => this.raiseEvent('changed'));
34
48
  }
35
49
  get events$() {
36
50
  return this.eventBus.events$;
@@ -38,23 +52,166 @@ class Page {
38
52
  get stable$() {
39
53
  return this.eventBus.events$.pipe((0, rxjs_1.map)(() => !this._syncing && !this.modified), (0, rxjs_1.distinctUntilChanged)());
40
54
  }
55
+ get state() {
56
+ return this._state;
57
+ }
58
+ set state(state) {
59
+ // skip if unchanged
60
+ if (this._state === state) {
61
+ return;
62
+ }
63
+ // update state
64
+ this._state = state;
65
+ this._modifiedOn = Date.now();
66
+ // raise change
67
+ this.raiseEvent('changed');
68
+ }
41
69
  get modified() {
42
- return false;
70
+ const { _deletedBlocks, initialSlug, _slug, initialPublication, _publication, initialTags, _tags, items } = this;
71
+ return _deletedBlocks.length > 0
72
+ || initialSlug !== _slug
73
+ || initialPublication !== _publication
74
+ || initialTags.symmetricDifference(_tags).size > 0
75
+ || items.some(item => item.modified);
76
+ }
77
+ get updatedOn() {
78
+ return this._updatedOn;
79
+ }
80
+ get changes() {
81
+ return {
82
+ page: this,
83
+ items: this.items.filter(item => item instanceof components_1.Section
84
+ ? item.changes.blocks.length > 0
85
+ : item.changes.fields.length > 0)
86
+ };
43
87
  }
44
88
  get published() {
45
89
  return this.publishedOn !== undefined;
46
90
  }
91
+ get type() {
92
+ return this.template.type;
93
+ }
47
94
  get syncing() {
48
95
  return this._syncing;
49
96
  }
97
+ set syncing(syncing) {
98
+ this._syncing = syncing;
99
+ this.raiseEvent('changed');
100
+ }
101
+ repair() {
102
+ // repair all content blocks
103
+ this.blocks.forEach(block => block.repair());
104
+ }
105
+ get url() {
106
+ return this._url;
107
+ }
108
+ get initialSlug() {
109
+ return this._initialSlug;
110
+ }
50
111
  get hasSlug() {
51
112
  return this.template.variant === 'dynamic';
52
113
  }
114
+ set slug(slug) {
115
+ // stop static pages from setting slugs
116
+ if (!this.hasSlug) {
117
+ throw new funnel_core_1.InvalidOperationError('Cannot set slug on static page');
118
+ }
119
+ // or from being unset
120
+ else if (!slug) {
121
+ throw new funnel_core_1.ArgumentError('Slug cannot be unset');
122
+ }
123
+ // ignore if unchanged
124
+ else if (this._slug === slug) {
125
+ return;
126
+ }
127
+ // update slug
128
+ this._slug = slug;
129
+ this._modifiedOn = Date.now();
130
+ // raise change
131
+ this.raiseEvent('modified');
132
+ }
53
133
  get slug() {
54
134
  return this._slug;
55
135
  }
136
+ get initialName() {
137
+ return this._initialName;
138
+ }
139
+ get name() {
140
+ return this._name;
141
+ }
142
+ set name(name) {
143
+ // ignore if unchanged
144
+ if (this._name === name) {
145
+ return;
146
+ }
147
+ // update name
148
+ this._name = name;
149
+ this._modifiedOn = Date.now();
150
+ // raise change
151
+ this.raiseEvent('modified');
152
+ }
153
+ get initialPublication() {
154
+ return this._initialPublication;
155
+ }
156
+ get publication() {
157
+ return this._publication;
158
+ }
159
+ set publication(publication) {
160
+ // ignore if unchanged
161
+ if (this._publication === publication) {
162
+ return;
163
+ }
164
+ // update name
165
+ this._publication = publication;
166
+ this._modifiedOn = Date.now();
167
+ // raise change
168
+ this.raiseEvent('modified');
169
+ }
170
+ get initialErrors() {
171
+ return this._initialErrors;
172
+ }
173
+ get errors() {
174
+ return {
175
+ page: this,
176
+ items: this.items.filter(item => (!item.activated && item.required)
177
+ || (item.activated && !item.valid))
178
+ };
179
+ }
180
+ get valid() {
181
+ return this.errors.items.length === 0;
182
+ }
183
+ get hasErrors() {
184
+ return this.errors.items.length > 0;
185
+ }
186
+ get asRef() {
187
+ const { id, groupId, template, brand, region, language, slug, url, name, tags, state, hasErrors, createdOn, updatedOn, publishedOn } = this;
188
+ return {
189
+ id, groupId, template,
190
+ brand, region, language, slug, url, name, tags: Array.from(tags),
191
+ state, errors: hasErrors, createdOn, updatedOn, publishedOn
192
+ };
193
+ }
56
194
  get discover() {
57
- return [];
195
+ return this._discover;
196
+ }
197
+ get seo() {
198
+ // get seo (or blow up)
199
+ const seo = this.discover.find(block => block.key === 'seo');
200
+ if (!seo) {
201
+ throw new funnel_core_1.IllegalStateError('Failed to resolve SEO block - should always exist');
202
+ }
203
+ // return seo
204
+ return seo;
205
+ }
206
+ getPreviewBlock() {
207
+ // get preview block identifier
208
+ const previewBlockIdentifier = this.template.preview;
209
+ // bail if no identifier specified
210
+ if (!previewBlockIdentifier) {
211
+ return undefined;
212
+ }
213
+ // otherwise try to get the preview block
214
+ return this.getBlock(previewBlockIdentifier);
58
215
  }
59
216
  get content() {
60
217
  return this._content;
@@ -63,6 +220,9 @@ class Page {
63
220
  return this.content
64
221
  .flatMap((item) => item instanceof components_1.Section ? item.blocks : item);
65
222
  }
223
+ get deleteBlocks() {
224
+ return this._deletedBlocks;
225
+ }
66
226
  get firstBlock() {
67
227
  // find first block
68
228
  for (const item of this.content) {
@@ -85,7 +245,7 @@ class Page {
85
245
  return this.content[0];
86
246
  }
87
247
  get items() {
88
- return [...this._content];
248
+ return [...this._discover, ...this._content];
89
249
  }
90
250
  get assets() {
91
251
  return this._assets;
@@ -102,6 +262,11 @@ class Page {
102
262
  get tags() {
103
263
  return this._tags;
104
264
  }
265
+ hydrateStores(references) {
266
+ this._assets.push(...references.assets);
267
+ this._pages.push(...references.pages);
268
+ this._collections.push(...references.collections);
269
+ }
105
270
  getItem(identifier) {
106
271
  // skip if there's no identifier
107
272
  if (!identifier) {
@@ -135,40 +300,152 @@ class Page {
135
300
  return (_b = (_a = this.getItem(identifier)) !== null && _a !== void 0 ? _a : this.firstBlock) !== null && _b !== void 0 ? _b : this.firstItem;
136
301
  }
137
302
  deleteBlock(area, key) {
138
- throw new Error('Method not implemented.');
303
+ // get parent
304
+ let parent;
305
+ switch (area) {
306
+ case 'discover':
307
+ parent = this._discover;
308
+ break;
309
+ case 'content':
310
+ parent = this._content;
311
+ break;
312
+ }
313
+ // skip if item isn't in parent
314
+ const blockIndex = parent.findIndex(item => item.key === key);
315
+ if (!blockIndex) {
316
+ return false;
317
+ }
318
+ // remove item
319
+ const [deletedBlock] = parent.splice(blockIndex, 1);
320
+ this._modifiedOn = Date.now();
321
+ // track deletion of top-level blocks
322
+ if (deletedBlock instanceof components_1.Block) {
323
+ console.debug('Removing deleted block', deletedBlock);
324
+ this._deletedBlocks.push(deletedBlock);
325
+ }
326
+ // raise change
327
+ this.raiseEvent('modified');
328
+ // indicate success
329
+ return true;
139
330
  }
140
331
  getAsset(assetId) {
141
332
  return this._assets.find((asset) => asset.value.id === assetId);
142
333
  }
143
334
  getAssetScope(range) {
144
- throw new Error('Method not implemented.');
335
+ switch (range) {
336
+ case 'global':
337
+ return funnel_core_1.Schema.AssetScope.global;
338
+ case 'type':
339
+ return new funnel_core_1.Schema.PageTypeAssetScope(this.type);
340
+ case 'instance':
341
+ return new funnel_core_1.Schema.PageInstanceAssetScope(this.groupId);
342
+ }
145
343
  }
146
344
  getSearchScope() {
147
- throw new Error('Method not implemented.');
345
+ return {
346
+ target: 'page',
347
+ type: this.type,
348
+ groupId: this.groupId
349
+ };
148
350
  }
149
351
  addAsset(asset) {
150
- throw new Error('Method not implemented.');
352
+ // replace asset if it exists
353
+ const { _assets } = this;
354
+ const { id } = asset.value;
355
+ const assetIndex = _assets.findIndex(asset => asset.value.id === id);
356
+ if (assetIndex >= 0) {
357
+ _assets[assetIndex] = asset;
358
+ }
359
+ // or add asset
360
+ else {
361
+ _assets.push(asset);
362
+ }
363
+ // raise event
364
+ this.raiseEvent('changed');
151
365
  }
152
366
  removeAsset(assetId) {
153
- throw new Error('Method not implemented.');
367
+ // skip if asset isn't there
368
+ const { _assets } = this;
369
+ const assetIndex = _assets.findIndex(asset => asset.value.id === assetId);
370
+ if (assetIndex < 0) {
371
+ return false;
372
+ }
373
+ // remove asset
374
+ _assets.splice(assetIndex, 1);
375
+ // raise event
376
+ this.raiseEvent('changed');
377
+ // indicate success
378
+ return true;
154
379
  }
155
380
  getCollection(collectionId) {
156
- throw new Error('Method not implemented.');
381
+ return this._collections.find(collection => collection.id === collectionId);
157
382
  }
158
383
  addCollection(collection) {
159
- throw new Error('Method not implemented.');
384
+ // replace collection if it exists
385
+ const { _collections } = this;
386
+ const collectionId = collection.id;
387
+ const collectionIndex = _collections.findIndex(collection => collection.id === collectionId);
388
+ if (collectionIndex >= 0) {
389
+ _collections[collectionIndex] = collection;
390
+ }
391
+ // or add collection
392
+ else {
393
+ _collections.push(collection);
394
+ }
395
+ // raise event
396
+ this.raiseEvent('changed');
160
397
  }
161
398
  removeCollection(collectionId) {
162
- throw new Error('Method not implemented.');
399
+ // skip if collection isn't there
400
+ const { _collections } = this;
401
+ const collectionIndex = _collections.findIndex(collection => collection.id === collectionId);
402
+ if (collectionIndex < 0) {
403
+ return false;
404
+ }
405
+ // remove collection
406
+ const [collection] = _collections.splice(collectionIndex, 1);
407
+ // raise event
408
+ this.raiseEvent('changed');
409
+ // indicate success
410
+ return true;
163
411
  }
164
412
  getPageRef(pageId) {
165
- return pageId && pageId !== funnel_core_1.Schema.Page.Id.draft ? this._pages.find((ref) => ref.id === pageId) : undefined;
413
+ return pageId && pageId !== funnel_core_1.Schema.Page.Id.draft
414
+ ? this._pages.find((ref) => ref.id === pageId)
415
+ : undefined;
166
416
  }
167
417
  addPageRef(ref) {
168
- throw new Error('Method not implemented.');
418
+ // throw if page is a draft
419
+ if (ref.id === funnel_core_1.Schema.Page.Id.draft) {
420
+ throw new funnel_core_1.InvalidOperationError('Cannot add draft Page as reference');
421
+ }
422
+ // replace ref if it exists
423
+ const { _pages } = this;
424
+ const pageId = ref.id;
425
+ const refIndex = _pages.findIndex(ref => ref.id === pageId);
426
+ if (refIndex >= 0) {
427
+ _pages[refIndex] = ref;
428
+ }
429
+ // or add ref
430
+ else {
431
+ _pages.push(ref);
432
+ }
433
+ // raise event
434
+ this.raiseEvent('changed');
169
435
  }
170
436
  removePageRef(pageId) {
171
- throw new Error('Method not implemented.');
437
+ // skip if file isn't there
438
+ const { _pages } = this;
439
+ const refIndex = _pages.findIndex(ref => ref.id === pageId);
440
+ if (refIndex < 0) {
441
+ return false;
442
+ }
443
+ // remove file
444
+ _pages.splice(refIndex, 1);
445
+ // raise event
446
+ this.raiseEvent('changed');
447
+ // indicate success
448
+ return true;
172
449
  }
173
450
  addTag(tag) {
174
451
  throw new Error('Method not implemented.');
@@ -176,5 +453,41 @@ class Page {
176
453
  removeTag(tag) {
177
454
  throw new Error('Method not implemented.');
178
455
  }
456
+ onSaveStart() {
457
+ this._savingOn = Date.now();
458
+ }
459
+ onSaveComplete(updatedOn) {
460
+ // update fundamentals
461
+ this._updatedOn = updatedOn;
462
+ this._state = 'draft';
463
+ // reset to values to match save
464
+ const { _modifiedOn, _savingOn } = this;
465
+ if (_savingOn && (!_modifiedOn || _savingOn >= _modifiedOn)) {
466
+ // sync data
467
+ this._initialErrors = !this.valid;
468
+ this._initialSlug = this._slug;
469
+ this._initialName = this._name;
470
+ this._initialTags = new Set(this._tags);
471
+ this._initialPublication = this._publication;
472
+ this._deletedBlocks = [];
473
+ }
474
+ // clear saving flag
475
+ delete this._savingOn;
476
+ }
477
+ getBlock(identifier) {
478
+ // try to find item
479
+ const item = this.getItem(identifier);
480
+ // make sure it is a block
481
+ if (item instanceof components_1.Block) {
482
+ return item;
483
+ }
484
+ // otherwise undefined
485
+ else {
486
+ return undefined;
487
+ }
488
+ }
489
+ raiseEvent(type) {
490
+ this.eventBus.publish(components_1.IPage.Event.create({ type, page: this }));
491
+ }
179
492
  }
180
493
  exports.Page = Page;
@@ -1 +1,2 @@
1
+ export * from './Collection';
1
2
  export * from './Page';
@@ -14,4 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./Collection"), exports);
17
18
  __exportStar(require("./Page"), exports);
@@ -1,3 +1,5 @@
1
1
  export * from './api';
2
+ export * from './bridge';
3
+ export * from './contentful';
2
4
  export * from './domain';
3
5
  export * from './repository';
@@ -15,5 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./api"), exports);
18
+ __exportStar(require("./bridge"), exports);
19
+ __exportStar(require("./contentful"), exports);
18
20
  __exportStar(require("./domain"), exports);
19
21
  __exportStar(require("./repository"), exports);
@@ -0,0 +1,10 @@
1
+ import { EditorAppSDK } from '@contentful/app-sdk';
2
+ import { IAssetRepository } from '@nascentdigital/funnel-editor/components';
3
+ export declare class AssetRepository implements IAssetRepository {
4
+ readonly sdk: EditorAppSDK;
5
+ constructor(sdk: EditorAppSDK);
6
+ search(args: IAssetRepository.Search.Args): Promise<IAssetRepository.Search.Result>;
7
+ get(args: IAssetRepository.Get.Args): Promise<IAssetRepository.Get.Result>;
8
+ update(args: IAssetRepository.Update.Args): Promise<IAssetRepository.Update.Result>;
9
+ uploadFile(args: IAssetRepository.UploadFile.Args): Promise<IAssetRepository.UploadFile.Result>;
10
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AssetRepository = void 0;
4
+ const funnel_core_1 = require("@nascentdigital/funnel-core");
5
+ const contentful_1 = require("../contentful");
6
+ // class
7
+ class AssetRepository {
8
+ constructor(sdk) {
9
+ this.sdk = sdk;
10
+ }
11
+ async search(args) {
12
+ // TODO: implement fetching assets
13
+ return {
14
+ items: [],
15
+ page: 1,
16
+ pageCount: 0,
17
+ totalItemCount: 0
18
+ };
19
+ }
20
+ async get(args) {
21
+ throw new funnel_core_1.NotImplementedError();
22
+ }
23
+ async update(args) {
24
+ throw new funnel_core_1.NotImplementedError();
25
+ }
26
+ async uploadFile(args) {
27
+ // upload asset
28
+ const asset = await contentful_1.ContentfulAsset.upload(args, this.sdk);
29
+ // return result
30
+ return contentful_1.ContentfulAsset.serialize(asset, this.sdk.locales.default);
31
+ }
32
+ }
33
+ exports.AssetRepository = AssetRepository;
@@ -0,0 +1,9 @@
1
+ import { EventBus, Schema } from '@nascentdigital/funnel-core';
2
+ import { Collection } from '../domain';
3
+ export declare namespace CollectionSerializer {
4
+ type DeserializeCollectionArgs = {
5
+ flattenedCollection: Schema.Collection.FlattenedData;
6
+ eventBus: EventBus;
7
+ };
8
+ function deserializeCollection({ flattenedCollection, eventBus }: DeserializeCollectionArgs): Collection;
9
+ }