diff --git a/jest.config.js b/jest.config.js index 74e19e1458c6..ea4a2343b479 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,6 +9,7 @@ module.exports = { 'decap-cms-widget-object': '/packages/decap-cms-widget-object/src/index.js', '\\.(css|less)$': '/__mocks__/styleMock.js', }, + modulePathIgnorePatterns: ['.nx', 'dist'], snapshotSerializers: ['@emotion/jest/serializer'], transformIgnorePatterns: [ 'node_modules/(?!copy-text-to-clipboard|clean-stack|escape-string-regexp)', diff --git a/packages/decap-cms-core/index.d.ts b/packages/decap-cms-core/index.d.ts index d5efb55dd7dd..e709fdf3f396 100644 --- a/packages/decap-cms-core/index.d.ts +++ b/packages/decap-cms-core/index.d.ts @@ -233,6 +233,7 @@ declare module 'decap-cms-core' { // This is the default widget, so declaring its type is optional. widget?: 'string' | 'text'; default?: string; + visualEditing?: boolean; } export interface CmsFieldMeta { @@ -306,6 +307,7 @@ declare module 'decap-cms-core' { hide?: boolean; editor?: { preview?: boolean; + visualEditing?: boolean; }; publish?: boolean; nested?: { diff --git a/packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js b/packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js index 39424176ac95..3cc025fd705d 100644 --- a/packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js +++ b/packages/decap-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js @@ -6,8 +6,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import Frame, { FrameContextConsumer } from 'react-frame-component'; import { lengths } from 'decap-cms-ui-default'; import { connect } from 'react-redux'; -import { encodeEntry } from 'decap-cms-lib-util/src/stega'; +import { encodeEntry } from '../../../lib/stega'; import { resolveWidget, getPreviewTemplate, diff --git a/packages/decap-cms-lib-util/src/stega.ts b/packages/decap-cms-core/src/lib/stega.ts similarity index 84% rename from packages/decap-cms-lib-util/src/stega.ts rename to packages/decap-cms-core/src/lib/stega.ts index 099b86e6db03..0cbeaaf63de6 100644 --- a/packages/decap-cms-lib-util/src/stega.ts +++ b/packages/decap-cms-core/src/lib/stega.ts @@ -1,6 +1,6 @@ import { vercelStegaEncode } from '@vercel/stega'; -import { isImmutableMap, isImmutableList } from './types'; +import { isImmutableMap, isImmutableList } from '../types/immutable'; import type { Map as ImmutableMap, List } from 'immutable'; import type { CmsField } from 'decap-cms-core'; @@ -38,14 +38,20 @@ function getNestedFields(f?: CmsField): CmsField[] { * For markdown fields, encode each paragraph separately */ function encodeString(value: string, { fields, path }: EncodeContext): string { - const stega = vercelStegaEncode({ decap: path }); - const isMarkdown = fields[0]?.widget === 'markdown'; - - if (isMarkdown && value.includes('\n\n')) { + const [field] = fields; + if (!field) return value; + const { widget } = field; + if (widget === 'string' || widget === 'text') { + if ('visualEditing' in field && field.visualEditing === false) return value; + const stega = vercelStegaEncode({ decap: path }); + return value + stega; + } + if (widget === 'markdown') { + const stega = vercelStegaEncode({ decap: path }); const blocks = value.split(/(\n\n+)/); return blocks.map(block => (block.trim() ? block + stega : block)).join(''); } - return value + stega; + return value; } /** @@ -102,16 +108,21 @@ function encodeMap( return newMap; } +/** + * Cache for encoded values to prevent re-encoding unchanged values + * across keystrokes. The cache is keyed by path. + */ +const encodingCache = new Map(); + /** * Main entry point for encoding steganographic data into entry values * Uses a visitor pattern with caching to handle recursive structures */ export function encodeEntry(value: unknown, fields: List>) { const plainFields = fields.toJS() as CmsField[]; - const cache = new Map(); function visit(value: unknown, fields: CmsField[], path = '') { - const cached = cache.get(path); + const cached = encodingCache.get(path); if (cached === value) return value; const ctx: EncodeContext = { fields, path, visit }; @@ -126,7 +137,7 @@ export function encodeEntry(value: unknown, fields: List { + return ImmutableMap.isMap(value); +} + +export function isImmutableList(value: unknown): value is List { + return List.isList(value); +} + export interface StaticallyTypedRecord { get(key: K, defaultValue?: T[K]): T[K]; set(key: K, value: V): StaticallyTypedRecord & T; diff --git a/packages/decap-cms-core/src/types/redux.ts b/packages/decap-cms-core/src/types/redux.ts index f88f5becdc76..5258ddfc0a6f 100644 --- a/packages/decap-cms-core/src/types/redux.ts +++ b/packages/decap-cms-core/src/types/redux.ts @@ -248,6 +248,7 @@ export interface CmsFieldStringOrText { // This is the default widget, so declaring its type is optional. widget?: 'string' | 'text'; default?: string; + visualEditing?: boolean; } export interface CmsFieldMeta { @@ -322,6 +323,7 @@ export interface CmsCollection { delete?: boolean; editor?: { preview?: boolean; + visualEditing?: boolean; }; publish?: boolean; nested?: {