UNPKG

@atlaskit/renderer

Version:
239 lines (227 loc) 8.94 kB
/* eslint-disable @atlaskit/editor/no-re-export */ // Entry file in package.json import { defaultSchema } from '@atlaskit/adf-schema/schema-default'; import { nativeEmbedsFallbackTransform, transformContainerNodes, transformMediaLinkMarks, transformNestedTablesIncomingDocument } from '@atlaskit/adf-utils/transforms'; import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics'; import { findAndTrackUnsupportedContentNodes, validateADFEntity } from '@atlaskit/editor-common/utils'; import { getValidDocument } from '@atlaskit/editor-common/validator'; import { fg } from '@atlaskit/platform-feature-flags'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import memoizeOne from 'memoize-one'; import { PLATFORM } from './analytics/events'; import { trackUnsupportedContentLevels } from './analytics/unsupported-content'; import { countNodes } from './ui/Renderer/count-nodes'; const SUPPORTS_HIRES_TIMER_API = !!(typeof window !== 'undefined' && window.performance && performance.now); const withStopwatch = cb => { const startTime = SUPPORTS_HIRES_TIMER_API ? performance.now() : Date.now(); const output = cb(); const endTime = SUPPORTS_HIRES_TIMER_API ? performance.now() : Date.now(); const time = endTime - startTime; return { output, time }; }; const _validation = (doc, schema, adfStage, useSpecBasedValidator, dispatchAnalyticsEvent, skipValidation, validationOverrides) => { let result; if (useSpecBasedValidator) { // link mark on mediaSingle is deprecated, need to move link mark to child media node // https://product-fabric.atlassian.net/browse/ED-14043 const { transformedAdf, isTransformed } = transformMediaLinkMarks(doc); if (isTransformed && dispatchAnalyticsEvent) { dispatchAnalyticsEvent({ action: ACTION.MEDIA_LINK_TRANSFORMED, actionSubject: ACTION_SUBJECT.RENDERER, eventType: EVENT_TYPE.OPERATIONAL }); } result = skipValidation ? transformedAdf || doc : validateADFEntity(schema, transformedAdf || doc, dispatchAnalyticsEvent, validationOverrides); } else { result = getValidDocument(doc, schema, adfStage); } if (!result) { return result; } // ProseMirror always require a child under doc if (result.type === 'doc' && useSpecBasedValidator) { if (Array.isArray(result.content) && result.content.length === 0) { result.content.push({ type: 'paragraph', content: [] }); } // Just making sure doc is always valid if (!result.version) { result.version = 1; } } // Convert nested-table extensions into nested tables try { const { transformedAdf, isTransformed } = transformNestedTablesIncomingDocument(result); if (isTransformed) { dispatchAnalyticsEvent === null || dispatchAnalyticsEvent === void 0 ? void 0 : dispatchAnalyticsEvent({ action: ACTION.NESTED_TABLE_TRANSFORMED, actionSubject: ACTION_SUBJECT.RENDERER, eventType: EVENT_TYPE.OPERATIONAL }); result = transformedAdf; } } catch (e) { dispatchAnalyticsEvent === null || dispatchAnalyticsEvent === void 0 ? void 0 : dispatchAnalyticsEvent({ action: ACTION.INVALID_PROSEMIRROR_DOCUMENT, actionSubject: ACTION_SUBJECT.RENDERER, eventType: EVENT_TYPE.OPERATIONAL, attributes: { platform: PLATFORM.WEB, errorStack: `${e instanceof Error && e.name === 'NodeNestingTransformError' ? 'NodeNestingTransformError - Failed to encode one or more nested tables' : undefined}` } }); } // Upgrade panel → panel_c1 where the schema allows it if (result && expValEquals('platform_editor_nest_table_in_panel', 'isEnabled', true)) { try { const { transformedAdf, isTransformed } = transformContainerNodes(result, schema); if (isTransformed && transformedAdf) { // TODO: EDITOR-7175 - Add analytics result = transformedAdf; } } catch (e) { // TODO: EDITOR-7175 - Add analytics } } if (result && !expValEquals('cc-maui-experiment', 'isEnabled', true) && !fg('platform_native_embeds_rollout_non_maui_experience') && fg('platform_editor_native_embeds_fallback_transform')) { const { transformedAdf, hasValidTransform } = nativeEmbedsFallbackTransform(result, schema); if (hasValidTransform && transformedAdf) { dispatchAnalyticsEvent === null || dispatchAnalyticsEvent === void 0 ? void 0 : dispatchAnalyticsEvent({ action: ACTION.NATIVE_EMBEDS_TRANSFORMED, actionSubject: ACTION_SUBJECT.RENDERER, eventType: EVENT_TYPE.OPERATIONAL }); result = transformedAdf; } } return result; }; const memoValidation = memoizeOne(_validation, (newArgs, lastArgs) => { const [newDoc, newSchema, newADFStage, newUseSpecValidator,, /* ignoring dispatchAnalyticsEvent */newSkipValidation, newValidationOverrides] = newArgs; const [oldDoc, oldSchema, oldADFStage, oldUseSpecValidator,, /* ignoring dispatchAnalyticsEvent */oldSkipValidation, oldValidationOverrides] = lastArgs; return areDocsEqual(newDoc, oldDoc) && newSchema === oldSchema && newADFStage === oldADFStage && newUseSpecValidator === oldUseSpecValidator && newSkipValidation === oldSkipValidation && newValidationOverrides === oldValidationOverrides; }); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any const areDocsEqual = (docA, docB) => { if (docA === docB) { return true; } if (typeof docA === 'string' && typeof docB === 'string') { return docA === docB; } // PMNode if (docA.type && docA.toJSON && docB.type && docB.toJSON) { return JSON.stringify(docA.toJSON()) === JSON.stringify(docB.toJSON()); } // Object return JSON.stringify(docA) === JSON.stringify(docB); }; const _serializeFragment = (serializer, doc) => { return serializer.serializeFragment(doc.content); }; const memoSerializeFragment = memoizeOne(_serializeFragment, (newArgs, lastArgs) => { const [newSerializer, newDoc] = newArgs; const [oldSerializer, oldDoc] = lastArgs; return newSerializer === oldSerializer && areDocsEqual(newDoc, oldDoc); }); const _createNodeAndCheck = (schema, doc, dispatchAnalyticsEvent) => { const pmNode = schema.nodeFromJSON(doc); try { pmNode.check(); } catch (err) { if (dispatchAnalyticsEvent) { dispatchAnalyticsEvent({ action: ACTION.INVALID_PROSEMIRROR_DOCUMENT, actionSubject: ACTION_SUBJECT.RENDERER, attributes: { platform: PLATFORM.WEB, errorStack: err instanceof Error ? err.message : String(err) }, eventType: EVENT_TYPE.OPERATIONAL }); } } return pmNode; }; const memoCreateNodeAndCheck = memoizeOne(_createNodeAndCheck, (newArgs, lastArgs) => { // ignore dispatchAnalyticsEvent const [newSchema, newDoc] = newArgs; const [oldSchema, oldDoc] = lastArgs; return newSchema === oldSchema && areDocsEqual(newDoc, oldDoc); }); export const renderDocument = (doc, serializer, schema = defaultSchema, adfStage = 'final', useSpecBasedValidator = false, rendererId = 'noid', dispatchAnalyticsEvent, unsupportedContentLevelsTracking, appearance, includeNodesCountInStats, skipValidation, validationOverrides) => { const stat = { sanitizeTime: 0 }; if (fg('platform_editor_renderer_rm_usespecbasedvalidator')) { useSpecBasedValidator = true; } const { output: validDoc, time: sanitizeTime } = withStopwatch(() => { return memoValidation(doc, schema, adfStage, useSpecBasedValidator, dispatchAnalyticsEvent, skipValidation, validationOverrides); }); // save sanitize time to stats stat.sanitizeTime = sanitizeTime; if (!validDoc) { return { stat, result: null }; } const { output: node, time: buildTreeTime } = withStopwatch(() => { return memoCreateNodeAndCheck(schema, validDoc, dispatchAnalyticsEvent); }); // save build tree time to stats stat.buildTreeTime = buildTreeTime; const { output: result, time: serializeTime } = withStopwatch(() => { return memoSerializeFragment(serializer, node); }); // save serialize tree time to stats stat.serializeTime = serializeTime; if (dispatchAnalyticsEvent && useSpecBasedValidator) { findAndTrackUnsupportedContentNodes(node, schema, dispatchAnalyticsEvent); if (unsupportedContentLevelsTracking !== null && unsupportedContentLevelsTracking !== void 0 && unsupportedContentLevelsTracking.enabled) { const documentData = { doc: validDoc, appearance, rendererId }; trackUnsupportedContentLevels(documentData, unsupportedContentLevelsTracking, dispatchAnalyticsEvent); } } if (includeNodesCountInStats) { stat.nodesCount = countNodes(doc); } return { result, stat, pmDoc: node }; };