Skip to content

Commit f23133a

Browse files
Merge pull request #51 from contentful/editor-interface-feature
Add Editor Interface Support
2 parents 0e5bf04 + 5802c10 commit f23133a

File tree

11 files changed

+213
-10
lines changed

11 files changed

+213
-10
lines changed

lib/create-space-api.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export default function createSpaceApi ({
6767
const {wrapRole, wrapRoleCollection} = entities.role
6868
const {wrapSpaceMembership, wrapSpaceMembershipCollection} = entities.spaceMembership
6969
const {wrapApiKey, wrapApiKeyCollection} = entities.apiKey
70+
const {wrapEditorInterface} = entities.editorInterface
7071

7172
/**
7273
* Space instances.
@@ -122,6 +123,20 @@ export default function createSpaceApi ({
122123
.then((response) => wrapContentType(http, response.data), errorHandler)
123124
}
124125

126+
/**
127+
* Gets an EditorInterface for a ContentType
128+
* @memberof ContentfulSpaceAPI
129+
* @param {string} contentTypeId
130+
* @return {Promise<EditorInterface.EditorInterface>} Promise for an EditorInterface
131+
* @example
132+
* space.getEditorInterfaceForContentType('contentTypeId')
133+
* .then(editorInterface => console.log(editorInterface))
134+
*/
135+
function getEditorInterfaceForContentType (contentTypeId) {
136+
return http.get('content_types/' + contentTypeId + '/editor_interface')
137+
.then((response) => wrapEditorInterface(http, response.data), errorHandler)
138+
}
139+
125140
/**
126141
* Gets a collection of Content Types
127142
* @memberof ContentfulSpaceAPI
@@ -622,6 +637,7 @@ export default function createSpaceApi ({
622637
getContentTypes: getContentTypes,
623638
createContentType: createContentType,
624639
createContentTypeWithId: createContentTypeWithId,
640+
getEditorInterfaceForContentType: getEditorInterfaceForContentType,
625641
getEntry: getEntry,
626642
getEntries: getEntries,
627643
createEntry: createEntry,

lib/entities/content-type.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
createUpdatedChecker,
1616
createDraftChecker
1717
} from '../instance-actions'
18+
import {wrapEditorInterface} from './editor-interface'
19+
import errorHandler from '../error-handler'
1820

1921
/**
2022
* @memberof ContentType
@@ -90,6 +92,19 @@ function createContentTypeApi (http) {
9092
wrapperMethod: wrapContentType
9193
}),
9294

95+
/**
96+
* get the editor interface for the object
97+
* @memberof ContentType
98+
* @func getEditorInterface
99+
* @return {Promise<EditorInterface.EditorInterface>} Object returned from the server with the current editor interface.
100+
* @example
101+
* contentType.getEditorInterface()
102+
* .then(editorInterface => console.log(editorInterface.controls))
103+
*/
104+
getEditorInterface: function () {
105+
return http.get('content_types/' + this.sys.id + '/editor_interface')
106+
.then((response) => wrapEditorInterface(http, response.data), errorHandler)
107+
},
93108
/**
94109
* Checks if the contentType is published. A published contentType might have unpublished changes (@see {ContentType.isUpdated})
95110
* @memberof ContentType

lib/entities/editor-interface.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Editor Interface instances
3+
* @namespace EditorInterface
4+
*/
5+
6+
import cloneDeep from 'lodash/cloneDeep'
7+
import freezeSys from 'contentful-sdk-core/freeze-sys'
8+
import enhanceWithMethods from '../enhance-with-methods'
9+
import mixinToPlainObject from 'contentful-sdk-core/mixins/to-plain-object'
10+
import omit from 'lodash/omit'
11+
import errorHandler from '../error-handler'
12+
13+
function createEditorInterfaceApi (http) {
14+
return {
15+
/**
16+
* Sends an update to the server with any changes made to the object's properties
17+
* @memberof EditorInterface
18+
* @func update
19+
* @return {Promise<EditorInterface>} Object returned from the server with updated changes.
20+
* @example
21+
* editorInterface.controls[0] = { "fieldId": "title", "widgetId": "singleLine"}
22+
* editorInterface.update()
23+
* .then(editorInterface => console.log(editorInterface.controls))
24+
*/
25+
update: function () {
26+
const raw = this.toPlainObject()
27+
const data = omit(raw, ['sys'])
28+
return http.put(`content_types/${this.sys.contentType.sys.id}/editor_interface`,
29+
data,
30+
{
31+
headers: {'X-Contentful-Version': this.sys.version}
32+
}
33+
)
34+
.then((response) => wrapEditorInterface(http, response.data), errorHandler)
35+
},
36+
/**
37+
* gets a control for a specific field
38+
* @memberof EditorInterface
39+
* @func getControlForField
40+
* @return {?Object} control object for specific field.
41+
* @example
42+
* const control = editorInterface.getControlForField('fieldId')
43+
* console.log(control)
44+
*/
45+
getControlForField: function (fieldId) {
46+
const result = this.controls.filter((control) => {
47+
return control.fieldId === fieldId
48+
})
49+
return (result && result.length > 0) ? result[0] : null
50+
}
51+
}
52+
}
53+
54+
/**
55+
* @private
56+
* @param {Object} http - HTTP client instance
57+
* @param {Object} data - Raw editor-interface data
58+
* @return {EditorInterface} Wrapped editor-interface data
59+
*/
60+
export function wrapEditorInterface (http, data) {
61+
const editorInterface = mixinToPlainObject(cloneDeep(data))
62+
enhanceWithMethods(editorInterface, createEditorInterfaceApi(http))
63+
return freezeSys(editorInterface)
64+
}

lib/entities/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as space from './space'
22
import * as entry from './entry'
33
import * as asset from './asset'
44
import * as contentType from './content-type'
5+
import * as editorInterface from './editor-interface'
56
import * as locale from './locale'
67
import * as webhook from './webhook'
78
import * as spaceMembership from './space-membership'
@@ -13,6 +14,7 @@ export default {
1314
entry,
1415
asset,
1516
contentType,
17+
editorInterface,
1618
locale,
1719
webhook,
1820
spaceMembership,

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"tonicExampleFilename": "tonic-example.js",
1414
"scripts": {
1515
"clean": "rimraf dist && rimraf browser-dist && rimraf coverage && rimraf out",
16-
"build": "npm run clean && npm run vendor:version && npm run build:dist && npm run build:standalone",
16+
"build:ci": "npm run vendor:version && npm run build:dist && npm run build:standalone",
17+
"build": "npm run clean && npm run build:ci",
1718
"build:dist": "babel lib --out-dir dist",
1819
"build:standalone": "webpack && webpack -p --output-filename contentful-management.min.js",
1920
"link-dev-deps": "mkdirp node_modules && ln -s ../../contentful-sdk-core/src node_modules/contentful-sdk-core",
@@ -31,7 +32,7 @@
3132
"test:browser-remote": "BABEL_ENV=test ./node_modules/.bin/karma start karma.conf.saucelabs.js",
3233
"vendor:version": "echo \"module.exports = '`cat package.json|json version`'\" > version.js",
3334
"browser-coverage": "npm run test:cover && opener coverage/lcov-report/index.html",
34-
"prepublish": "in-publish && npm run build || not-in-publish",
35+
"prepublish": "in-publish && npm run build:ci || not-in-publish",
3536
"postpublish": "npm run docs:publish && npm run clean",
3637
"pretest": "standard lib/*.js && standard lib/**/*.js && standard test/**/*.js",
3738
"test": "npm run test:cover && npm run test:integration && npm run test:browser-local",

test/integration/content-type-integration.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export function contentTypeReadOnlyTests (t, space) {
2121
}
2222

2323
export function contentTypeWriteTests (t, space) {
24-
t.test('Create, update, publish, unpublish and delete content type', (t) => {
25-
t.plan(7)
24+
t.test('Create, update, publish, getEditorInterface, unpublish and delete content type', (t) => {
25+
t.plan(10)
2626
return space.createContentType({name: 'testentity'})
2727
.then((contentType) => {
2828
t.ok(contentType.isDraft(), 'contentType is in draft')
@@ -38,10 +38,22 @@ export function contentTypeWriteTests (t, space) {
3838
.then((updatedContentType) => {
3939
t.ok(updatedContentType.isUpdated(), 'contentType is updated')
4040
t.equals(updatedContentType.fields[0].id, 'field', 'field id')
41-
return updatedContentType.unpublish()
42-
.then((unpublishedContentType) => {
43-
t.ok(unpublishedContentType.isDraft(), 'contentType is back in draft')
44-
return unpublishedContentType.delete()
41+
t.ok(updatedContentType.getEditorInterface, 'updatedContentType.getEditorInterface')
42+
return updatedContentType.publish()
43+
.then((publishedContentType) => {
44+
return publishedContentType.getEditorInterface()
45+
.then((editorInterface) => {
46+
t.ok(editorInterface.controls, 'editor interface controls')
47+
t.ok(editorInterface.sys, 'editor interface sys')
48+
return editorInterface.update()
49+
.then((editorInterface) => {
50+
return updatedContentType.unpublish()
51+
.then((unpublishedContentType) => {
52+
t.ok(unpublishedContentType.isDraft(), 'contentType is back in draft')
53+
return unpublishedContentType.delete()
54+
})
55+
})
56+
})
4557
})
4658
})
4759
})

test/integration/integration-tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ test('Create space for tests which create, change and delete data', (t) => {
8585
// Also comment the test.onFinish line below to avoid removing the space.
8686
// The below line also uses double quotes on purpose so it breaks the linter
8787
// in case someone forgets to comment this line again.
88-
// client.getSpace("")
88+
// client.getSpace('a3f19zbn5ldg')
8989
.then((space) => {
9090
return space.createLocale({
9191
name: 'German (Germany)',

test/unit/create-space-api-test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import mixinToPlainObject from 'contentful-sdk-core/mixins/to-plain-object'
44
import createSpaceApi, {__RewireAPI__ as createSpaceApiRewireApi} from '../../lib/create-space-api'
55
import {
66
contentTypeMock,
7+
editorInterfaceMock,
78
assetMock,
89
entryMock,
910
localeMock,
@@ -157,6 +158,20 @@ test('API call createContentTypeWithId fails', (t) => {
157158
})
158159
})
159160

161+
test('API call getEditorInterfaceForContentType', (t) => {
162+
makeGetEntityTest(t, setup, teardown, {
163+
entityType: 'editorInterface',
164+
mockToReturn: editorInterfaceMock,
165+
methodToTest: 'getEditorInterfaceForContentType'
166+
})
167+
})
168+
169+
test('API call getEditorInterfaceForContentType fails', (t) => {
170+
makeEntityMethodFailingTest(t, setup, teardown, {
171+
methodToTest: 'getEditorInterfaceForContentType'
172+
})
173+
})
174+
160175
test('API call getEntry', (t) => {
161176
makeGetEntityTest(t, setup, teardown, {
162177
entityType: 'entry',

test/unit/entities/content-type-test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ test('ContentType unpublish fails', (t) => {
8888
})
8989
})
9090

91+
test('ContentType getEditorInterface', (t) => {
92+
return entityActionTest(t, setup, {
93+
wrapperMethod: wrapContentType,
94+
actionMethod: 'getEditorInterface'
95+
})
96+
})
97+
98+
test('ContentType getEditorInterface fails', (t) => {
99+
return failingActionTest(t, setup, {
100+
wrapperMethod: wrapContentType,
101+
actionMethod: 'getEditorInterface'
102+
})
103+
})
104+
91105
test('ContentType isPublished', (t) => {
92106
isPublishedTest(t, setup, {wrapperMethod: wrapContentType})
93107
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import test from 'tape'
2+
import {cloneMock} from '../mocks/entities'
3+
import setupHttpMock from '../mocks/http'
4+
import {wrapEditorInterface} from '../../../lib/entities/editor-interface'
5+
import {
6+
entityWrappedTest,
7+
entityUpdateTest
8+
} from '../test-creators/instance-entity-methods'
9+
10+
function setup (promise) {
11+
return {
12+
httpMock: setupHttpMock(promise),
13+
entityMock: cloneMock('editorInterface')
14+
}
15+
}
16+
17+
test('Editor Interface is wrapped', (t) => {
18+
return entityWrappedTest(t, setup, {
19+
wrapperMethod: wrapEditorInterface
20+
})
21+
})
22+
23+
test('EditorInterface update', (t) => {
24+
return entityUpdateTest(t, setup, {
25+
wrapperMethod: wrapEditorInterface
26+
})
27+
})
28+
29+
test('EditorInterface getControlForField with an existing fieldId', (t) => {
30+
t.plan(5)
31+
const {httpMock, entityMock} = setup()
32+
const editorInterface = wrapEditorInterface(httpMock, entityMock)
33+
const control = editorInterface.getControlForField('fieldId')
34+
t.ok(control, 'control object sould be there')
35+
t.ok(control.fieldId, 'should have a fieldId')
36+
t.ok(control.widgetId, 'should have a widgetId')
37+
t.equals(control.fieldId, 'fieldId')
38+
t.equals(control.widgetId, 'singleLine')
39+
})
40+
41+
test('EditorInterface getControlForField without an existing fieldId', (t) => {
42+
t.plan(1)
43+
const {httpMock, entityMock} = setup()
44+
const editorInterface = wrapEditorInterface(httpMock, entityMock)
45+
const control = editorInterface.getControlForField('notThere')
46+
t.equals(control, null)
47+
})

0 commit comments

Comments
 (0)