UNPKG

@stackbit/cms-contentful

Version:

Stackbit Contentful CMS Interface

311 lines 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const _ = require('lodash'); const { RICH_TEXT_HINT_MAX_LENGTH } = require('@stackbit/types'); const { IMAGE_MODEL } = require('@stackbit/cms-core'); const ITEM_TYPES = { ENTRY: 'Entry', ASSET: 'Asset', LINK: 'Link' }; class ContentfulEncoderDelegate { constructor({ schema, noEncodeFields = [], omitFields = [], encodedFieldTypes = null, defaultLocale = 'en-US', users = [] }) { this.schema = schema; this.models = schema.models; this.modelsByName = _.keyBy(this.models, 'name'); this.noEncodeFields = noEncodeFields; this.omitFields = omitFields; this.encodedFieldTypes = encodedFieldTypes; this.defaultLocale = defaultLocale; this.users = users; } getModelsByName() { return this.modelsByName; } getEncodedFieldTypes() { return this.encodedFieldTypes; } getNoEncodeFields() { return this.noEncodeFields; } getItemId(item) { return _.get(item, 'sys.id'); } getReferenceId(item) { // in Contentful, reference fields store id of the object they reference // in the same way an object stores its own id return this.getItemId(item); } getItemType(item) { return _.get(item, 'sys.type'); } getItemModelName(item) { return _.get(item, 'sys.contentType.sys.id', null); } itemIsMultiLocale(item) { // https://www.contentful.com/developers/docs/references/content-preview-api/#/reference/localization/retrieve-localized-entries // If the result contains only a single locale, resources will include the property sys.locale indicating the locale of that object. return !_.has(item, 'sys.locale'); } getItemLocale(item) { return _.get(item, 'sys.locale'); } getModelForRootItem(rootItem) { const itemType = this.getItemType(rootItem); if (itemType === ITEM_TYPES.ENTRY) { const modelName = this.getItemModelName(rootItem); const modelsByName = this.getModelsByName(); return _.get(modelsByName, modelName, null); } else if (itemType === ITEM_TYPES.ASSET) { return IMAGE_MODEL; } else if (itemType === ITEM_TYPES.LINK) { // not supporting links return null; } // not supporting anything else return null; } isLinkItem(item) { const itemType = this.getItemType(item); return itemType === ITEM_TYPES.LINK; } getModelForItemOfReferenceType(item) { return this.getModelForRootItem(item); } getUserEmailById(userId) { const user = _.find(this.users, { sys: { id: userId } }); if (!user) { return null; } return user.email; } getItemMetadata(item, model) { const itemType = this.getItemType(item); const itemId = this.getItemId(item); const spaceId = _.get(item, 'sys.space.sys.id'); const environment = _.get(item, 'sys.environment.sys.id'); let envParamPair = ''; if (environment && environment !== 'master') { envParamPair = `/environments/${environment}`; } let type; let srcObjectUrl; let srcModelName; let srcModelLabel; let label; if (itemType === ITEM_TYPES.ENTRY) { type = 'object'; label = this.getItemLabelFieldValue(item, model); srcObjectUrl = `https://app.contentful.com/spaces/${spaceId}${envParamPair}/entries/${itemId}`; srcModelName = this.getItemModelName(item); srcModelLabel = _.get(model, 'label', null); } else if (itemType === ITEM_TYPES.ASSET) { type = 'image'; label = this.getItemLabelFieldValue(item, model); srcObjectUrl = `https://app.contentful.com/spaces/${spaceId}${envParamPair}/assets/${itemId}`; srcModelName = IMAGE_MODEL.name; srcModelLabel = 'Image'; } else { return null; } // currently we only expose isChanged that is treated as isDraft and isChanged because in Sanity there is no way to tell the difference const isDraft = !_.get(item, 'sys.publishedVersion') && !_.get(item, 'sys.archivedVersion'); const isChanged = !!_.get(item, 'sys.publishedVersion') && _.get(item, 'sys.version') >= _.get(item, 'sys.publishedVersion') + 2; const status = isDraft ? 'added' : isChanged ? 'modified' : 'published'; const createdByEmail = this.getUserEmailById(_.get(item, 'sys.createdBy.sys.id')); const updatedByEmail = this.getUserEmailById(_.get(item, 'sys.updatedBy.sys.id')); const updatedByList = updatedByEmail ? [updatedByEmail] : []; return { type: type, isChanged: isDraft || isChanged, status, createdAt: _.get(item, 'sys.createdAt', null), createdBy: createdByEmail, updatedAt: _.get(item, 'sys.updatedAt', null), updatedBy: updatedByList, srcType: 'contentful', srcProjectId: spaceId, srcProjectUrl: `https://app.contentful.com/spaces/${spaceId}/home`, srcEnvironment: environment, srcObjectId: itemId, srcObjectUrl: srcObjectUrl, srcObjectLabel: label, srcModelName: srcModelName, srcModelLabel: srcModelLabel, srcFieldNames: null }; } getItemLabelFieldValue(item, model) { const labelField = _.get(model, 'labelField'); let label = null; if (labelField) { const fields = _.get(item, 'fields'); const field = _.get(fields, labelField, null); const multiLocale = this.itemIsMultiLocale(item); label = multiLocale ? this.getFirstLocalizedFieldValue(field) : field; } if (!label) { label = _.get(model, 'label', null); } if (!label && model.name) { label = _.startCase(model.name); } return label; } getItemFields(item, model) { const itemType = this.getItemType(item); if (!_.includes([ITEM_TYPES.ENTRY, ITEM_TYPES.ASSET], itemType)) { return null; } // get item fields const itemFields = _.get(item, 'fields'); const multiLocale = this.itemIsMultiLocale(item); // get model fields, for code consistency we set field locale to en-US, but it will not be used anywhere // because model fields that do not exist in item fields are not encoded const modelFields = _.transform(model.fields, (accum, fieldModel) => { _.set(accum, fieldModel.name, multiLocale ? { [this.defaultLocale]: null } : null); }, {}); // merge model fields with item fields const mergedFields = _.assign(modelFields, itemFields); // remove omitted fields const fields = _.omit(mergedFields, this.omitFields); const getEncodedDataPathAndKey = (item, model, key, locale) => { const encodedDataPath = ['fields', key]; if (multiLocale) { encodedDataPath.push(locale); } // rewrite encodedDataPath with locale if needed // Contentful's assets store the url inside fields.file.url, and with // locale it is fields.file[locale].url (fields.file['en-US'].url) if (model.type === 'image' && key === 'file') { encodedDataPath.push('url'); } const value = _.get(item, encodedDataPath); return { value, encodedDataPath, locale }; }; return _.map(fields, (fieldValue, key) => { const name = key; let fieldRes; // Our internal image model stores image url in 'fields.url' field, // while Contentful's assets store the url in 'fields.file.url'. // Update the 'key' so the encoded path will be pointing to 'fields.file' if (model.type === 'image' && key === 'url') { key = 'file'; } if (!multiLocale) { const locale = this.getItemLocale(item); fieldRes = getEncodedDataPathAndKey(item, model, key, locale); } else { const fieldLocales = this.getFieldLocales(fieldValue); if (fieldLocales.length > 1) { fieldRes = { locales: _.map(fieldLocales, (locale) => { return getEncodedDataPathAndKey(item, model, key, locale); }) }; } else if (fieldLocales.length === 1) { const locale = _.head(fieldLocales); fieldRes = getEncodedDataPathAndKey(item, model, key, locale); } } return { name: name, unset: !_.has(itemFields, key), ...fieldRes }; }); } getFirstLocalizedFieldValue(field) { const firstLocale = this.getFirstFieldLocale(field); return _.get(field, firstLocale, null); } getFirstFieldLocale(field) { const locales = this.getFieldLocales(field); return _.head(locales); } getFieldLocales(field) { return _.keys(field); } encodeField(fieldValue, fieldModel) { if (!fieldValue) { return { fieldData: { isUnset: true } }; } if (fieldModel.type === 'richText') { return this.encodeRichText(fieldValue); } else if (fieldModel.type === 'image') { if (fieldModel.source === 'cloudinary') { return { fieldData: { fields: { title: { type: 'string', value: fieldValue[0]?.public_id }, url: { type: 'string', value: fieldValue[0]?.derived?.[0]?.secure_url ?? fieldValue[0]?.secure_url } } } }; } return { fieldData: { type: 'unresolved_reference', refId: this.getReferenceId(fieldValue), refType: 'image' } }; } return null; } encodeRichText(fieldValue) { return { encodedData: { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', content: [ { nodeType: 'text', value: '', marks: [], data: {} } ], data: {} } ] }, encodedFieldPath: ['content', 0, 'content', 0, 'value'], fieldData: { hint: this.flattenRichText(fieldValue).substring(0, RICH_TEXT_HINT_MAX_LENGTH), multiElement: true, value: fieldValue } }; } flattenRichText(node) { if (_.get(node, 'nodeType') === 'text') { return _.get(node, 'value', ''); } const content = _.get(node, 'content'); if (content) { return _.reduce(content, (accum, node) => { return accum + this.flattenRichText(node); }, ''); } return ''; } } module.exports = ContentfulEncoderDelegate; //# sourceMappingURL=contentful-encoder-delegate.js.map