diff --git a/.eslintignore b/.eslintignore index 4c9f00119b..dd4f092fa0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ /node_modules /dist **/build/** +/test/cypress diff --git a/lib/api.ts b/lib/api.ts index 194748200f..58dac56ab6 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -8,7 +8,7 @@ import createEditor from './editor' import createNavigator from './navigator' import createApp from './app' import locations from './locations' -import { BaseExtensionSDK, EntryFieldInfo, FieldInfo, NavigatorAPI, KnownSDK } from './types' +import { BaseExtensionSDK, EntryFieldInfo, NavigatorAPI, KnownSDK, ConnectMessage } from './types' import { Channel } from './channel' const DEFAULT_API_PRODUCERS = [ @@ -19,8 +19,8 @@ const DEFAULT_API_PRODUCERS = [ makeWindowAPI ] -type producerFunc = (channel: Channel, data: any, currentWindow: Window) => any -const LOCATION_TO_API_PRODUCERS: { [location: string]: producerFunc[] } = { +type ProducerFunc = (channel: Channel, data: ConnectMessage, currentWindow: Window) => any +const LOCATION_TO_API_PRODUCERS: { [location: string]: ProducerFunc[] } = { [locations.LOCATION_ENTRY_FIELD]: DEFAULT_API_PRODUCERS, [locations.LOCATION_ENTRY_FIELD_SIDEBAR]: DEFAULT_API_PRODUCERS, [locations.LOCATION_ENTRY_SIDEBAR]: [makeSharedAPI, makeEntryAPI, makeEditorAPI, makeWindowAPI], @@ -30,9 +30,11 @@ const LOCATION_TO_API_PRODUCERS: { [location: string]: producerFunc[] } = { [locations.LOCATION_APP_CONFIG]: [makeSharedAPI, makeAppAPI] } -// TODO Remove any once we know payload -// TODO data is directly received through event passthrough -export default function createAPI(channel: Channel, data: any, currentWindow: Window): KnownSDK { +export default function createAPI( + channel: Channel, + data: ConnectMessage, + currentWindow: Window +): KnownSDK { const producers = LOCATION_TO_API_PRODUCERS[data.location as string] || DEFAULT_API_PRODUCERS return producers.reduce((api, produce) => { @@ -40,7 +42,7 @@ export default function createAPI(channel: Channel, data: any, currentWindow: Wi }, {}) as any } -function makeSharedAPI(channel: Channel, data: any): BaseExtensionSDK { +function makeSharedAPI(channel: Channel, data: ConnectMessage): BaseExtensionSDK { const { user, parameters, locales, ids, initialContentTypes } = data const currentLocation = data.location || locations.LOCATION_ENTRY_FIELD @@ -89,12 +91,7 @@ function makeEditorAPI(channel: Channel, data: any) { function makeEntryAPI( channel: Channel, - { - locales, - contentType, - entry, - fieldInfo - }: { locales: any; contentType: string; entry: any; fieldInfo: any } + { locales, contentType, entry, fieldInfo }: ConnectMessage ) { const createEntryField = (info: EntryFieldInfo) => new Field(channel, info, locales.default) @@ -104,7 +101,10 @@ function makeEntryAPI( } } -function makeFieldAPI(channel: Channel, { field }: { field: FieldInfo }) { +function makeFieldAPI(channel: Channel, { field }: ConnectMessage) { + if (!field) { + throw new Error('FieldAPI called for location without "field" property defined.') + } return { field: new FieldLocale(channel, field) } diff --git a/lib/channel.ts b/lib/channel.ts index 783b9ec348..a7477098a9 100644 --- a/lib/channel.ts +++ b/lib/channel.ts @@ -1,12 +1,11 @@ import { Signal } from './signal' +import { ConnectMessage } from './types' -// TODO derive arg1 and arg2 shape of onConnect callback -// TODO Both come through initial event.data over Channel export default function connect( currentWindow: Window, - onConnect: (arg1: Channel, args2: any, arg3: any) => void + onConnect: (channel: Channel, message: ConnectMessage, messageQueue: unknown[]) => void ) { - waitForConnect(currentWindow, (params: any, messageQueue: any) => { + waitForConnect(currentWindow, (params: ConnectMessage, messageQueue: unknown[]) => { const channel = new Channel(params.id, currentWindow) onConnect(channel, params, messageQueue) }) diff --git a/lib/editor.ts b/lib/editor.ts index 2e95e666d9..8c7ec79e49 100644 --- a/lib/editor.ts +++ b/lib/editor.ts @@ -1,4 +1,4 @@ -import { EditorInterface } from '../typings' +import { EditorInterface, EditorLocaleSettings } from './types' import { Channel } from './channel' import { MemoizedSignal } from './signal' @@ -6,8 +6,7 @@ export default function createEditor(channel: Channel, editorInterface: EditorIn const _localeSettingsSygnal = new MemoizedSignal(undefined) const _showDisabledFieldsSygnal = new MemoizedSignal(undefined) - // TODO any - channel.addHandler('localeSettingsChanged', (settings: any) => { + channel.addHandler('localeSettingsChanged', (settings: EditorLocaleSettings) => { _localeSettingsSygnal.dispatch(settings) }) diff --git a/lib/field.ts b/lib/field.ts index de6059abb0..caa52e02a2 100644 --- a/lib/field.ts +++ b/lib/field.ts @@ -54,8 +54,7 @@ export default class Field implements EntryFieldAPI { return this.setValue(undefined, locale) } - // TODO maybe swap argument positions to get rid of swap logic - onValueChanged(locale: string | Function, handler?: Function) { + onValueChanged(locale: string | ((value: any) => void), handler?: (value: any) => void) { const h = handler || locale if (!handler) { locale = '' @@ -63,8 +62,10 @@ export default class Field implements EntryFieldAPI { return this._getFieldLocale(locale as string).onValueChanged(h as any) } - // TODO maybe swap argument positions to get rid of swap logic - onIsDisabledChanged(locale: string | Function, handler?: Function) { + onIsDisabledChanged( + locale: string | ((isDisabled: boolean) => void), + handler?: (isDisabled: boolean) => void + ) { const h = handler || locale if (!handler) { locale = '' diff --git a/lib/initialize.ts b/lib/initialize.ts index fdf8a3c784..e4f54dbff6 100644 --- a/lib/initialize.ts +++ b/lib/initialize.ts @@ -1,10 +1,10 @@ -import { KnownSDK } from './types' +import { ConnectMessage, KnownSDK } from './types' import connect, { Channel } from './channel' export default function createInitializer( currentWindow: Window, - apiCreator: (channel: Channel, data: any, window: Window) => KnownSDK -): (initCallback: (sdk: T) => any) => void { + apiCreator: (channel: Channel, data: ConnectMessage, window: Window) => KnownSDK +) { const connectDeferred = createDeferred() connectDeferred.promise.then(([channel]: [Channel]) => { @@ -20,29 +20,32 @@ export default function createInitializer( (...args) => connectDeferred.resolve(args) ) - // TODO Replace sdk/customSdk [any] with api and customApi types - return function init(initCb: (sdk: any, customSdk: any) => any, { makeCustomApi = null } = {}) { - connectDeferred.promise.then(([channel, params, messageQueue]: [Channel, any, any]) => { - const api = apiCreator(channel, params, currentWindow) + return function init( + initCb: (sdk: KnownSDK, customSdk: any) => any, + { makeCustomApi = null } = {} + ) { + connectDeferred.promise.then( + ([channel, params, messageQueue]: [Channel, ConnectMessage, unknown[]]) => { + const api = apiCreator(channel, params, currentWindow) - let customApi - if (typeof makeCustomApi === 'function') { - // Reason for the typecast: https://github.com/microsoft/TypeScript/issues/14889 - customApi = (makeCustomApi as any)(channel, params) - } + let customApi + if (typeof makeCustomApi === 'function') { + // Reason for the typecast: https://github.com/microsoft/TypeScript/issues/14889 + customApi = (makeCustomApi as any)(channel, params) + } + + // Handle pending incoming messages. + // APIs are created before so handlers are already + // registered on the channel. + messageQueue.forEach(m => { + // TODO Expose private handleMessage method + ;(channel as any)._handleMessage(m) + }) - // Handle pending incoming messages. - // APIs are created before so handlers are already - // registered on the channel. - // TODO message has "id" or "method" attribute defined - messageQueue.forEach((m: any) => { - // TODO Expose private handleMessage method - ;(channel as any)._handleMessage(m) - }) - - // Hand over control to the developer. - initCb(api, customApi) - }) + // Hand over control to the developer. + initCb(api, customApi) + } + ) } } diff --git a/lib/space.ts b/lib/space.ts index 9b379157b5..186709ec10 100644 --- a/lib/space.ts +++ b/lib/space.ts @@ -1,6 +1,5 @@ -import { ContentType } from '../typings' +import { ContentType, SpaceAPI } from './types' import { Channel } from './channel' -import { SpaceAPI } from './types' const spaceMethods: Array = [ 'getContentType', diff --git a/lib/types.ts b/lib/types.ts index 7661bda008..5ffe57cbb2 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -75,6 +75,24 @@ export interface EntryFieldInfo { values: { [locale: string]: any } } +export interface ConnectMessage { + id: string + location: Location[keyof Location] + parameters: ParametersAPI + locales: LocalesAPI + user: User + initialContentTypes: ContentType[] + ids: IdsAPI + contentType: ContentType + editorInterface?: EditorInterface + entry: { + sys: EntrySys + metadata?: any + } + fieldInfo: EntryFieldInfo[] + field?: FieldInfo +} + /* Field API */ export interface FieldAPI { /** The ID of a field is defined in an entry's content type. */ @@ -569,16 +587,16 @@ export interface IdsAPI { contentType: string } +export interface EditorLocaleSettings { + mode: 'multi' | 'single' + focused?: string + active?: Array +} + export interface SharedEditorSDK { editor: { editorInterface: EditorInterface - onLocaleSettingsChanged: ( - callback: (value: { - mode: 'multi' | 'single' - focused?: string - active?: Array - }) => any - ) => Function + onLocaleSettingsChanged: (callback: (value: EditorLocaleSettings) => any) => Function onShowDisabledFieldsChanged: (callback: (value: boolean) => any) => Function } /** Allows to read and update the value of any field of the current entry and to get the entry's metadata */ diff --git a/package.json b/package.json index f2ebc039b9..da10407a95 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ ], "scripts": { "test": "ts-mocha 'test/unit/*.[jt]s'", - "lint": "tsc -b && eslint '{lib,test}/**/*.{t,j}s'", - "lint:fix": "eslint --fix lib/ test/", + "lint": "eslint '{lib,test}/**/*.{t,j}s'", + "lint:fix": "npm run lint -- --fix", "build": "npm run typings && rollup -c --compact", "build:debug": "npm run build -- --sourcemap", "typings": "tsc -b typings.tsconfig.json && mv lib/types.d.ts typings.d.ts && cat module-declaration.d.ts >> typings.d.ts", diff --git a/test/unit/api.spec.ts b/test/unit/api.spec.ts index 3888e31817..bc6d93d21b 100644 --- a/test/unit/api.spec.ts +++ b/test/unit/api.spec.ts @@ -2,6 +2,7 @@ import { makeDOM, mockMutationObserver, expect } from '../helpers' import createAPI from '../../lib/api' import locations from '../../lib/locations' +import { AppExtensionSDK, ConnectMessage } from '../../lib/types' const sharedExpected = [ 'location', @@ -19,7 +20,7 @@ const sharedExpected = [ function test(expected: string[], location: string | undefined, expectedLocation = location) { const channel = { addHandler: () => {} } as any - const data = { + const data = ({ location, user: 'USER', parameters: 'PARAMS', @@ -45,7 +46,7 @@ function test(expected: string[], location: string | undefined, expectedLocation ids: { extension: 'my-test-id' } - } + } as unknown) as ConnectMessage const dom = makeDOM() mockMutationObserver(dom, () => {}) @@ -110,10 +111,9 @@ describe('createAPI()', () => { it('returns correct shape of the app API (app)', () => { const expected = ['app'] - const api = test(expected, locations.LOCATION_APP_CONFIG) + const api = (test(expected, locations.LOCATION_APP_CONFIG) as unknown) as AppExtensionSDK - // TODO app not on this object? - expect((api as any).app).to.have.all.keys([ + expect(api.app).to.have.all.keys([ 'setReady', 'isInstalled', 'getParameters',