@servicetitan/dte-unlayer 0.153.0 → 0.154.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/merge-tags.d.ts +6 -10
- package/dist/merge-tags.d.ts.map +1 -1
- package/dist/merge-tags.js +35 -44
- package/dist/merge-tags.js.map +1 -1
- package/dist/shared/instance-id.d.ts +13 -0
- package/dist/shared/instance-id.d.ts.map +1 -0
- package/dist/shared/instance-id.js +22 -0
- package/dist/shared/instance-id.js.map +1 -0
- package/dist/store.d.ts +14 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +51 -3
- package/dist/store.js.map +1 -1
- package/dist/tools.d.ts +12 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +47 -0
- package/dist/tools.js.map +1 -1
- package/dist/unlayer-interface.d.ts +11 -1
- package/dist/unlayer-interface.d.ts.map +1 -1
- package/dist/unlayer-interface.js.map +1 -1
- package/dist/unlayer.d.ts.map +1 -1
- package/dist/unlayer.js +5 -3
- package/dist/unlayer.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/merge-tags.test.ts +98 -76
- package/src/__tests__/store.merge-tags.test.ts +186 -0
- package/src/index.ts +1 -2
- package/src/merge-tags.ts +41 -60
- package/src/shared/instance-id.ts +23 -0
- package/src/store.ts +62 -2
- package/src/tools.ts +51 -0
- package/src/unlayer-interface.tsx +11 -1
- package/src/unlayer.tsx +5 -3
package/dist/unlayer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/unlayer.tsx"],"sourcesContent":["import { emitDisplayCondition } from './display-conditions/displayConditionController';\nimport { editorCoreScript, editorCoreStyles, editorCoreTools } from './editor-core';\nimport { logUnlayerError } from './log';\nimport { scheduleUnlayerMergeTags, toUnlayerMergeTags } from './merge-tags';\nimport { constGenericsEditor } from './shared/const';\nimport { unlayerSupportedFonts } from './shared/fonts';\nimport {\n unlayerCustomEmptyToolState,\n unlayerCustomToolMetaData,\n unlayerCustomToolsGetRegisterUrls,\n} from './tools';\nimport { CreateUnlayerEditorProps } from './unlayer-interface';\n\nexport interface UnlayerExport {\n html: string;\n chunks: any;\n design: any;\n demoData?: Record<string, unknown>;\n}\n\nexport interface Unlayer {\n addEventListener(event: string, cb: (arg: any) => void): void;\n removeEventListener(event: string, cb: (arg: any) => void): void;\n registerCallback(type: string, cb: (...args: any[]) => void): void;\n unregisterCallback(type: string, cb: (...args: any[]) => void): void;\n registerProvider(type: string, cb: (...args: any[]) => void): void;\n unregisterProvider(type: string, cb: (...args: any[]) => void): void;\n loadDesign(design: any): void;\n saveDesign(callback: (design: any) => void): void;\n exportHtml(callback: (data: UnlayerExport) => void): void;\n setMergeTags(tags: any): void;\n createViewer(opts: { render: (values: any) => string }): any;\n registerTool(tool: any): void;\n setBodyValues(values: any): void;\n}\n\nexport enum DesignUpdatedEventType {\n ContentAdded = 'content:added',\n ContentMoved = 'content:moved',\n ContentRemoved = 'content:removed',\n ContentModified = 'content:modified',\n RowAdded = 'row:added',\n RowMoved = 'row:moved',\n RowRemoved = 'row:removed',\n RowModified = 'row:modified',\n ColumnAdded = 'column:added',\n ColumnRemoved = 'column:removed',\n ColumnModified = 'column:modified',\n BodyModified = 'body:modified',\n}\n\nexport interface EventDesignUpdated {\n type: DesignUpdatedEventType;\n item: {\n type: string;\n slug?: string;\n values: any;\n };\n}\n\n/**\n * !!! IMPORTANT !!!\n * do not forget to update schema version when updating unlayer version\n * it is important for templates export and should be in sync\n * to know correct schema version, open unlayer editor and check output design ('schema' property)\n */\nexport const versions = {\n unlayer: '1.421.0',\n schema: 24,\n};\n\nexport const handlePreselectTool = (preselectToolSlug: string) => {\n return `\n const element = document.querySelector('.u_content_custom_${preselectToolSlug}');\n if (element) {\n element.click();\n }\n `;\n};\n\nexport const hideUnlayerContentsControls = () => {\n return `\n .blockbuilder-layer-controls-contents, .blockbuilder-layer-controls-rows {\n display: none !important;\n }\n\n .blockbuilder-options-header .text-right .icon-delete, .blockbuilder-options-header .text-right .icon-duplicate {\n display: none !important;\n }\n `;\n};\n\nexport const hideUnlayerBodyMenuItem = () => {\n return `\n .nav-item.tab-body {\n display: none !important;\n }\n `;\n};\n\nconst injectEditorCoreStyles = () => {\n const cssText = editorCoreStyles?.trim();\n\n if (!cssText) {\n return '';\n }\n\n return `\n (function () {\n var css = ${JSON.stringify(cssText)};\n var id = 'dte-unlayer-core-styles';\n if (document.getElementById(id)) {\n return;\n }\n var style = document.createElement('style');\n style.id = id;\n style.type = 'text/css';\n style.appendChild(document.createTextNode(css));\n document.head.appendChild(style);\n })();\n `;\n};\n\nexport const hideMobileTabletPreview = () => `\n [data-key=\"preview-tablet\"],\n [data-key=\"resolution\"],\n [data-key=\"dark-mode\"],\n [data-key=\"preview-desktop\"],\n [data-key=\"preview-mobile\"] {\n display: none !important;\n }\n`;\nexport const hideUnlayerTools = () => {\n return `\n .blockbuilder-content-tools {\n display: none !important;\n }\n \n .unlayer-custom-empty-tool-state {\n display: flex;\n margin-top: 150px;\n flex-direction: column;\n justify-content: center;\n }\n \n .unlayer-custom-empty-tool-state-icon {\n text-align: center;\n margin-bottom: 12px;\n }\n \n .unlayer-custom-empty-tool-state-icon svg{\n color: #606162;\n }\n \n .unlayer-custom-empty-tool-state-text {\n text-align: center;\n font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;\n font-size: 16px;\n font-style: normal;\n font-weight: 500;\n line-height: normal;\n color: #606162;\n line-height: 13px;\n }\n `;\n};\n\nexport const createUnlayerEditor = (\n container: HTMLDivElement,\n {\n customCSS,\n customJS,\n displayConditions,\n eSignFieldTypes,\n eSignRecipients,\n enableHTMLEditing,\n enableHTMLEditingReadonly,\n hideAllTools,\n hideBodyMenuItem,\n hideContentControls,\n latest,\n mergeTags,\n noCoreTools,\n tools,\n units,\n }: CreateUnlayerEditorProps,\n): Unlayer => {\n const customScripts = [\n editorCoreScript,\n editorCoreTools,\n injectEditorCoreStyles(),\n 'window.dteStore.sendData(\"--core-loaded\")',\n ...unlayerCustomToolsGetRegisterUrls(tools),\n unlayerCustomToolMetaData(tools),\n ...(customJS ? (Array.isArray(customJS) ? customJS : [customJS]) : []),\n hideAllTools ? unlayerCustomEmptyToolState() : '',\n 'window.dteStore.sendData(\"--data-loaded\")',\n ].filter(js => !!js?.trim());\n const customStyles = [\n editorCoreStyles,\n ...(customCSS ? (Array.isArray(customCSS) ? customCSS : [customCSS]) : []),\n /*\n * @TODO this is workaround for hiding all tools from unlayer sidebar.\n * Will be better to use unlayer functionality for this but it is not implemented yet\n */\n hideMobileTabletPreview(),\n hideAllTools ? hideUnlayerTools() : '',\n hideContentControls ? hideUnlayerContentsControls() : '',\n hideBodyMenuItem ? hideUnlayerBodyMenuItem() : '',\n ].filter(css => !!css?.trim());\n\n /**\n * Unlayer supports only passing id of container, but when we use shadowDOM (MFE),\n * getElementById can't find element in it\n * so making this dirty hack to let unlayer find container in DOM\n */\n const id = container.id;\n const currentFind = document.getElementById;\n document.getElementById = function (elementId: string) {\n if (id === elementId) {\n return container;\n }\n\n return currentFind.call(document, id);\n };\n\n const acv = constGenericsEditor.adminConfigProperty;\n\n const eSignTypeMapping = (eSignFieldTypes ?? []).map(key => ({\n label: key,\n value: key.charAt(0).toLowerCase() + key.replace(/ /g, '').slice(1),\n }));\n\n const unitsConfig = (units ?? []).reduce(\n (out, unit) => {\n out[`custom#${unit.generic}:unit:${unit.id}`] = {\n data: {\n [acv]: unit.values[acv],\n },\n properties: {\n [constGenericsEditor.userConfigProperty]: {\n editor: {\n data: { [acv]: unit.values[acv] },\n },\n },\n [constGenericsEditor.infoDataProperty]: {\n editor: {\n data: { title: unit.title },\n },\n },\n },\n };\n return out;\n },\n {} as Record<string, any>,\n );\n\n const eSignComponentRecipients = {\n properties: {\n eSignRecipient: {\n editor: {\n data: {\n options: eSignRecipients?.map(item => ({\n label: item.displayName,\n value: item.name,\n })),\n },\n },\n },\n eSignFieldType: {\n editor: {\n data: {\n options: eSignTypeMapping,\n },\n },\n },\n },\n };\n\n let result: Unlayer;\n\n try {\n const unlayerApi = (window as any).unlayer;\n\n if (!unlayerApi?.createEditor) {\n throw new Error(\n 'Unlayer embed is not available. window.unlayer.createEditor is missing.',\n );\n }\n\n result = unlayerApi.createEditor({\n displayMode: 'web',\n devices: ['desktop'],\n features: {\n preview: true,\n userUploads: false,\n stockImages: false,\n textEditor: {\n tables: true,\n },\n },\n mergeTags: toUnlayerMergeTags(mergeTags, { groups: false }),\n projectId: 5713,\n version: latest ? undefined : versions.unlayer,\n appearance: {\n theme: 'classic_light',\n },\n tools: {\n 'carousel': { enabled: false },\n 'button': { enabled: false },\n 'html': {\n enabled: enableHTMLEditing,\n properties: {\n html: {\n editor: {\n widgetParams: {\n codeMirrorOptions: {\n readOnly: enableHTMLEditingReadonly,\n },\n },\n },\n },\n },\n },\n 'table': {\n enabled: true,\n },\n 'menu': { enabled: false },\n 'form': { enabled: false },\n ...(noCoreTools\n ? {\n columns: { enabled: false },\n text: { enabled: false },\n heading: { enabled: false },\n divider: { enabled: false },\n image: { enabled: false },\n }\n : {}),\n ...unitsConfig,\n 'custom#e-sign': eSignComponentRecipients,\n },\n editor: {\n autoSelectOnDrop: true,\n },\n customJS: customScripts,\n customCSS: customStyles,\n id,\n fonts: {\n showDefaultFonts: false,\n customFonts: unlayerSupportedFonts,\n },\n tabs: {\n content: {},\n body: {},\n blocks: { enabled: false },\n },\n });\n } catch (error: unknown) {\n logUnlayerError('unlayer.createEditor failed', error);\n throw error;\n } finally {\n document.getElementById = currentFind;\n }\n\n scheduleUnlayerMergeTags(result, mergeTags);\n\n if (displayConditions) {\n result.registerCallback(\n 'displayCondition',\n function (data: any, done: (condition: any) => void) {\n emitDisplayCondition({ data, done });\n },\n );\n }\n\n return result;\n};\n"],"names":["emitDisplayCondition","editorCoreScript","editorCoreStyles","editorCoreTools","logUnlayerError","scheduleUnlayerMergeTags","toUnlayerMergeTags","constGenericsEditor","unlayerSupportedFonts","unlayerCustomEmptyToolState","unlayerCustomToolMetaData","unlayerCustomToolsGetRegisterUrls","DesignUpdatedEventType","versions","unlayer","schema","handlePreselectTool","preselectToolSlug","hideUnlayerContentsControls","hideUnlayerBodyMenuItem","injectEditorCoreStyles","cssText","trim","JSON","stringify","hideMobileTabletPreview","hideUnlayerTools","createUnlayerEditor","container","customCSS","customJS","displayConditions","eSignFieldTypes","eSignRecipients","enableHTMLEditing","enableHTMLEditingReadonly","hideAllTools","hideBodyMenuItem","hideContentControls","latest","mergeTags","noCoreTools","tools","units","customScripts","Array","isArray","filter","js","customStyles","css","id","currentFind","document","getElementById","elementId","call","acv","adminConfigProperty","eSignTypeMapping","map","key","label","value","charAt","toLowerCase","replace","slice","unitsConfig","reduce","out","unit","generic","data","values","properties","userConfigProperty","editor","infoDataProperty","title","eSignComponentRecipients","eSignRecipient","options","item","displayName","name","eSignFieldType","result","unlayerApi","window","createEditor","Error","displayMode","devices","features","preview","userUploads","stockImages","textEditor","tables","groups","projectId","version","undefined","appearance","theme","enabled","html","widgetParams","codeMirrorOptions","readOnly","columns","text","heading","divider","image","autoSelectOnDrop","fonts","showDefaultFonts","customFonts","tabs","content","body","blocks","error","registerCallback","done"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/unlayer.tsx"],"sourcesContent":["import { emitDisplayCondition } from './display-conditions/displayConditionController';\nimport { editorCoreScript, editorCoreStyles, editorCoreTools } from './editor-core';\nimport { logUnlayerError } from './log';\nimport { toUnlayerMergeTags } from './merge-tags';\nimport { constGenericsEditor } from './shared/const';\nimport { unlayerSupportedFonts } from './shared/fonts';\nimport {\n unlayerCustomEmptyToolState,\n unlayerCustomToolMetaData,\n unlayerCustomToolsGetRegisterUrls,\n} from './tools';\nimport { CreateUnlayerEditorProps } from './unlayer-interface';\n\nexport interface UnlayerExport {\n html: string;\n chunks: any;\n design: any;\n demoData?: Record<string, unknown>;\n}\n\nexport interface Unlayer {\n addEventListener(event: string, cb: (arg: any) => void): void;\n removeEventListener(event: string, cb: (arg: any) => void): void;\n registerCallback(type: string, cb: (...args: any[]) => void): void;\n unregisterCallback(type: string, cb: (...args: any[]) => void): void;\n registerProvider(type: string, cb: (...args: any[]) => void): void;\n unregisterProvider(type: string, cb: (...args: any[]) => void): void;\n loadDesign(design: any): void;\n saveDesign(callback: (design: any) => void): void;\n exportHtml(callback: (data: UnlayerExport) => void): void;\n setMergeTags(tags: any): void;\n createViewer(opts: { render: (values: any) => string }): any;\n registerTool(tool: any): void;\n setBodyValues(values: any): void;\n}\n\nexport enum DesignUpdatedEventType {\n ContentAdded = 'content:added',\n ContentMoved = 'content:moved',\n ContentRemoved = 'content:removed',\n ContentModified = 'content:modified',\n RowAdded = 'row:added',\n RowMoved = 'row:moved',\n RowRemoved = 'row:removed',\n RowModified = 'row:modified',\n ColumnAdded = 'column:added',\n ColumnRemoved = 'column:removed',\n ColumnModified = 'column:modified',\n BodyModified = 'body:modified',\n}\n\nexport interface EventDesignUpdated {\n type: DesignUpdatedEventType;\n item: {\n type: string;\n slug?: string;\n values: any;\n };\n}\n\n/**\n * !!! IMPORTANT !!!\n * do not forget to update schema version when updating unlayer version\n * it is important for templates export and should be in sync\n * to know correct schema version, open unlayer editor and check output design ('schema' property)\n */\nexport const versions = {\n unlayer: '1.421.0',\n schema: 24,\n};\n\nexport const handlePreselectTool = (preselectToolSlug: string) => {\n return `\n const element = document.querySelector('.u_content_custom_${preselectToolSlug}');\n if (element) {\n element.click();\n }\n `;\n};\n\nexport const hideUnlayerContentsControls = () => {\n return `\n .blockbuilder-layer-controls-contents, .blockbuilder-layer-controls-rows {\n display: none !important;\n }\n\n .blockbuilder-options-header .text-right .icon-delete, .blockbuilder-options-header .text-right .icon-duplicate {\n display: none !important;\n }\n `;\n};\n\nexport const hideUnlayerBodyMenuItem = () => {\n return `\n .nav-item.tab-body {\n display: none !important;\n }\n `;\n};\n\nconst injectEditorCoreStyles = () => {\n const cssText = editorCoreStyles?.trim();\n\n if (!cssText) {\n return '';\n }\n\n return `\n (function () {\n var css = ${JSON.stringify(cssText)};\n var id = 'dte-unlayer-core-styles';\n if (document.getElementById(id)) {\n return;\n }\n var style = document.createElement('style');\n style.id = id;\n style.type = 'text/css';\n style.appendChild(document.createTextNode(css));\n document.head.appendChild(style);\n })();\n `;\n};\n\nexport const hideMobileTabletPreview = () => `\n [data-key=\"preview-tablet\"],\n [data-key=\"resolution\"],\n [data-key=\"dark-mode\"],\n [data-key=\"preview-desktop\"],\n [data-key=\"preview-mobile\"] {\n display: none !important;\n }\n`;\nexport const hideUnlayerTools = () => {\n return `\n .blockbuilder-content-tools {\n display: none !important;\n }\n \n .unlayer-custom-empty-tool-state {\n display: flex;\n margin-top: 150px;\n flex-direction: column;\n justify-content: center;\n }\n \n .unlayer-custom-empty-tool-state-icon {\n text-align: center;\n margin-bottom: 12px;\n }\n \n .unlayer-custom-empty-tool-state-icon svg{\n color: #606162;\n }\n \n .unlayer-custom-empty-tool-state-text {\n text-align: center;\n font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;\n font-size: 16px;\n font-style: normal;\n font-weight: 500;\n line-height: normal;\n color: #606162;\n line-height: 13px;\n }\n `;\n};\n\nexport const createUnlayerEditor = (\n container: HTMLDivElement,\n {\n customCSS,\n customJS,\n displayConditions,\n eSignFieldTypes,\n eSignRecipients,\n enableHTMLEditing,\n enableHTMLEditingReadonly,\n hideAllTools,\n hideBodyMenuItem,\n hideContentControls,\n latest,\n mergeTags,\n noCoreTools,\n tools,\n units,\n }: CreateUnlayerEditorProps,\n): Unlayer => {\n const customScripts = [\n editorCoreScript,\n editorCoreTools,\n injectEditorCoreStyles(),\n 'window.dteStore.sendData(\"--core-loaded\")',\n ...unlayerCustomToolsGetRegisterUrls(tools),\n unlayerCustomToolMetaData(tools),\n ...(customJS ? (Array.isArray(customJS) ? customJS : [customJS]) : []),\n hideAllTools ? unlayerCustomEmptyToolState() : '',\n 'window.dteStore.sendData(\"--data-loaded\")',\n ].filter(js => !!js?.trim());\n const customStyles = [\n editorCoreStyles,\n ...(customCSS ? (Array.isArray(customCSS) ? customCSS : [customCSS]) : []),\n /*\n * @TODO this is workaround for hiding all tools from unlayer sidebar.\n * Will be better to use unlayer functionality for this but it is not implemented yet\n */\n hideMobileTabletPreview(),\n hideAllTools ? hideUnlayerTools() : '',\n hideContentControls ? hideUnlayerContentsControls() : '',\n hideBodyMenuItem ? hideUnlayerBodyMenuItem() : '',\n ].filter(css => !!css?.trim());\n\n /**\n * Unlayer supports only passing id of container, but when we use shadowDOM (MFE),\n * getElementById can't find element in it\n * so making this dirty hack to let unlayer find container in DOM\n */\n const id = container.id;\n const currentFind = document.getElementById;\n document.getElementById = function (elementId: string) {\n if (id === elementId) {\n return container;\n }\n\n return currentFind.call(document, id);\n };\n\n const acv = constGenericsEditor.adminConfigProperty;\n\n const eSignTypeMapping = (eSignFieldTypes ?? []).map(key => ({\n label: key,\n value: key.charAt(0).toLowerCase() + key.replace(/ /g, '').slice(1),\n }));\n\n const unitsConfig = (units ?? []).reduce(\n (out, unit) => {\n out[`custom#${unit.generic}:unit:${unit.id}`] = {\n data: {\n [acv]: unit.values[acv],\n },\n properties: {\n [constGenericsEditor.userConfigProperty]: {\n editor: {\n data: { [acv]: unit.values[acv] },\n },\n },\n [constGenericsEditor.infoDataProperty]: {\n editor: {\n data: { title: unit.title },\n },\n },\n },\n };\n return out;\n },\n {} as Record<string, any>,\n );\n\n const eSignComponentRecipients = {\n properties: {\n eSignRecipient: {\n editor: {\n data: {\n options: eSignRecipients?.map(item => ({\n label: item.displayName,\n value: item.name,\n })),\n },\n },\n },\n eSignFieldType: {\n editor: {\n data: {\n options: eSignTypeMapping,\n },\n },\n },\n },\n };\n\n let result: Unlayer;\n\n try {\n const unlayerApi = (window as any).unlayer;\n\n if (!unlayerApi?.createEditor) {\n throw new Error(\n 'Unlayer embed is not available. window.unlayer.createEditor is missing.',\n );\n }\n\n result = unlayerApi.createEditor({\n displayMode: 'web',\n devices: ['desktop'],\n features: {\n preview: true,\n userUploads: false,\n stockImages: false,\n textEditor: {\n tables: true,\n },\n },\n /*\n * Flat only: nested groups here break custom tool registration. UnlayerStore applies\n * the grouped config via setMergeTags once the editor is ready.\n */\n mergeTags: toUnlayerMergeTags(mergeTags, { groups: false }),\n projectId: 5713,\n version: latest ? undefined : versions.unlayer,\n appearance: {\n theme: 'classic_light',\n },\n tools: {\n 'carousel': { enabled: false },\n 'button': { enabled: false },\n 'html': {\n enabled: enableHTMLEditing,\n properties: {\n html: {\n editor: {\n widgetParams: {\n codeMirrorOptions: {\n readOnly: enableHTMLEditingReadonly,\n },\n },\n },\n },\n },\n },\n 'table': {\n enabled: true,\n },\n 'menu': { enabled: false },\n 'form': { enabled: false },\n ...(noCoreTools\n ? {\n columns: { enabled: false },\n text: { enabled: false },\n heading: { enabled: false },\n divider: { enabled: false },\n image: { enabled: false },\n }\n : {}),\n ...unitsConfig,\n 'custom#e-sign': eSignComponentRecipients,\n },\n editor: {\n autoSelectOnDrop: true,\n },\n customJS: customScripts,\n customCSS: customStyles,\n id,\n fonts: {\n showDefaultFonts: false,\n customFonts: unlayerSupportedFonts,\n },\n tabs: {\n content: {},\n body: {},\n blocks: { enabled: false },\n },\n });\n } catch (error: unknown) {\n logUnlayerError('unlayer.createEditor failed', error);\n throw error;\n } finally {\n document.getElementById = currentFind;\n }\n\n if (displayConditions) {\n result.registerCallback(\n 'displayCondition',\n function (data: any, done: (condition: any) => void) {\n emitDisplayCondition({ data, done });\n },\n );\n }\n\n return result;\n};\n"],"names":["emitDisplayCondition","editorCoreScript","editorCoreStyles","editorCoreTools","logUnlayerError","toUnlayerMergeTags","constGenericsEditor","unlayerSupportedFonts","unlayerCustomEmptyToolState","unlayerCustomToolMetaData","unlayerCustomToolsGetRegisterUrls","DesignUpdatedEventType","versions","unlayer","schema","handlePreselectTool","preselectToolSlug","hideUnlayerContentsControls","hideUnlayerBodyMenuItem","injectEditorCoreStyles","cssText","trim","JSON","stringify","hideMobileTabletPreview","hideUnlayerTools","createUnlayerEditor","container","customCSS","customJS","displayConditions","eSignFieldTypes","eSignRecipients","enableHTMLEditing","enableHTMLEditingReadonly","hideAllTools","hideBodyMenuItem","hideContentControls","latest","mergeTags","noCoreTools","tools","units","customScripts","Array","isArray","filter","js","customStyles","css","id","currentFind","document","getElementById","elementId","call","acv","adminConfigProperty","eSignTypeMapping","map","key","label","value","charAt","toLowerCase","replace","slice","unitsConfig","reduce","out","unit","generic","data","values","properties","userConfigProperty","editor","infoDataProperty","title","eSignComponentRecipients","eSignRecipient","options","item","displayName","name","eSignFieldType","result","unlayerApi","window","createEditor","Error","displayMode","devices","features","preview","userUploads","stockImages","textEditor","tables","groups","projectId","version","undefined","appearance","theme","enabled","html","widgetParams","codeMirrorOptions","readOnly","columns","text","heading","divider","image","autoSelectOnDrop","fonts","showDefaultFonts","customFonts","tabs","content","body","blocks","error","registerCallback","done"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toUnlayerMergeTags } from '../merge-tags';
|
|
2
2
|
|
|
3
3
|
const customerNameTag = {
|
|
4
4
|
id: '10',
|
|
@@ -75,7 +75,7 @@ describe('toUnlayerMergeTags', () => {
|
|
|
75
75
|
name: 'Customer Name',
|
|
76
76
|
value: '{{Customer.Name | safe | nl2br }}',
|
|
77
77
|
},
|
|
78
|
-
custom_fields: {
|
|
78
|
+
'custom_fields': {
|
|
79
79
|
name: 'Custom Fields',
|
|
80
80
|
mergeTags: {
|
|
81
81
|
custom_field_12345: {
|
|
@@ -124,6 +124,101 @@ describe('toUnlayerMergeTags', () => {
|
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
describe('when group is a path', () => {
|
|
128
|
+
const intakeScoreTag = {
|
|
129
|
+
id: 'form-7-abc123',
|
|
130
|
+
name: 'Score',
|
|
131
|
+
propertyPath: '{{ __submission_fields["7"]["abc123"] | safe | nl2br }}',
|
|
132
|
+
group: ['Forms', 'Customer Intake'],
|
|
133
|
+
};
|
|
134
|
+
const intakeNotesTag = {
|
|
135
|
+
id: 'form-7-def456',
|
|
136
|
+
name: 'Notes',
|
|
137
|
+
propertyPath: '{{ __submission_fields["7"]["def456"] | safe | nl2br }}',
|
|
138
|
+
group: ['Forms', 'Customer Intake'],
|
|
139
|
+
};
|
|
140
|
+
const inspectionDateTag = {
|
|
141
|
+
id: 'form-9-fed987',
|
|
142
|
+
name: 'Inspection Date',
|
|
143
|
+
propertyPath: '{{ __submission_fields["9"]["fed987"] | safe | nl2br }}',
|
|
144
|
+
group: ['Forms', 'Inspection'],
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const subject = () =>
|
|
148
|
+
toUnlayerMergeTags([
|
|
149
|
+
contractTermTag,
|
|
150
|
+
renewalDateTag,
|
|
151
|
+
intakeScoreTag,
|
|
152
|
+
intakeNotesTag,
|
|
153
|
+
inspectionDateTag,
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
test('nests one submenu per path segment', () => {
|
|
157
|
+
expect(subject()).toEqual({
|
|
158
|
+
...groupedCustomFields,
|
|
159
|
+
forms: {
|
|
160
|
+
name: 'Forms',
|
|
161
|
+
mergeTags: {
|
|
162
|
+
customer_intake: {
|
|
163
|
+
name: 'Customer Intake',
|
|
164
|
+
mergeTags: {
|
|
165
|
+
form_7_abc123: {
|
|
166
|
+
name: 'Score',
|
|
167
|
+
value: '{{ __submission_fields["7"]["abc123"] | safe | nl2br }}',
|
|
168
|
+
},
|
|
169
|
+
form_7_def456: {
|
|
170
|
+
name: 'Notes',
|
|
171
|
+
value: '{{ __submission_fields["7"]["def456"] | safe | nl2br }}',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
inspection: {
|
|
176
|
+
name: 'Inspection',
|
|
177
|
+
mergeTags: {
|
|
178
|
+
form_9_fed987: {
|
|
179
|
+
name: 'Inspection Date',
|
|
180
|
+
value: '{{ __submission_fields["9"]["fed987"] | safe | nl2br }}',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('drops blank segments and keeps the remaining path', () => {
|
|
190
|
+
expect(toUnlayerMergeTags([{ ...intakeScoreTag, group: [' ', 'Forms', ''] }])).toEqual({
|
|
191
|
+
forms: {
|
|
192
|
+
name: 'Forms',
|
|
193
|
+
mergeTags: {
|
|
194
|
+
form_7_abc123: {
|
|
195
|
+
name: 'Score',
|
|
196
|
+
value: '{{ __submission_fields["7"]["abc123"] | safe | nl2br }}',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('treats an all-blank path as ungrouped', () => {
|
|
204
|
+
expect(toUnlayerMergeTags([{ ...intakeScoreTag, group: [] }])).toEqual({
|
|
205
|
+
form_7_abc123: {
|
|
206
|
+
name: 'Score',
|
|
207
|
+
value: '{{ __submission_fields["7"]["abc123"] | safe | nl2br }}',
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('flattens the path when groups are disabled', () => {
|
|
213
|
+
expect(toUnlayerMergeTags([intakeScoreTag], { groups: false })).toEqual({
|
|
214
|
+
form_7_abc123: {
|
|
215
|
+
name: 'Score',
|
|
216
|
+
value: '{{ __submission_fields["7"]["abc123"] | safe | nl2br }}',
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
127
222
|
describe('when group is blank', () => {
|
|
128
223
|
const subject = () =>
|
|
129
224
|
toUnlayerMergeTags([
|
|
@@ -153,7 +248,7 @@ describe('toUnlayerMergeTags', () => {
|
|
|
153
248
|
name: 'Customer Name',
|
|
154
249
|
value: '{{Customer.Name | safe | nl2br }}',
|
|
155
250
|
},
|
|
156
|
-
custom_field_12345: {
|
|
251
|
+
'custom_field_12345': {
|
|
157
252
|
name: 'Contract Term',
|
|
158
253
|
value: '{{ __custom_fields["Contract Term"] | safe | nl2br }}',
|
|
159
254
|
},
|
|
@@ -161,76 +256,3 @@ describe('toUnlayerMergeTags', () => {
|
|
|
161
256
|
});
|
|
162
257
|
});
|
|
163
258
|
});
|
|
164
|
-
|
|
165
|
-
describe('scheduleUnlayerMergeTags', () => {
|
|
166
|
-
describe('when grouped tags exist', () => {
|
|
167
|
-
const setMergeTags = jest.fn();
|
|
168
|
-
const addEventListener = jest.fn();
|
|
169
|
-
|
|
170
|
-
beforeEach(() => {
|
|
171
|
-
setMergeTags.mockReset();
|
|
172
|
-
addEventListener.mockReset();
|
|
173
|
-
scheduleUnlayerMergeTags(
|
|
174
|
-
{ addEventListener, setMergeTags },
|
|
175
|
-
[customerNameTag, contractTermTag, renewalDateTag],
|
|
176
|
-
);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('does not apply grouped merge tags until the editor is ready', () => {
|
|
180
|
-
expect(setMergeTags).not.toHaveBeenCalled();
|
|
181
|
-
expect(addEventListener).toHaveBeenCalledWith('editor:ready', expect.any(Function));
|
|
182
|
-
expect(addEventListener).toHaveBeenCalledWith('design:loaded', expect.any(Function));
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('applies grouped merge tags when the editor is ready', () => {
|
|
186
|
-
addEventListener.mock.calls[0][1]();
|
|
187
|
-
|
|
188
|
-
expect(setMergeTags).toHaveBeenCalledWith({
|
|
189
|
-
'10': {
|
|
190
|
-
name: 'Customer Name',
|
|
191
|
-
value: '{{Customer.Name | safe | nl2br }}',
|
|
192
|
-
},
|
|
193
|
-
...groupedCustomFields,
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test('applies grouped merge tags only once', () => {
|
|
198
|
-
addEventListener.mock.calls[0][1]();
|
|
199
|
-
addEventListener.mock.calls[1][1]();
|
|
200
|
-
|
|
201
|
-
expect(setMergeTags).toHaveBeenCalledTimes(1);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test('retries on design:loaded when setMergeTags throws', () => {
|
|
205
|
-
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
206
|
-
setMergeTags.mockImplementationOnce(() => {
|
|
207
|
-
throw new Error('setMergeTags failed');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
addEventListener.mock.calls[0][1]();
|
|
211
|
-
addEventListener.mock.calls[1][1]();
|
|
212
|
-
|
|
213
|
-
expect(setMergeTags).toHaveBeenCalledTimes(2);
|
|
214
|
-
expect(consoleError).toHaveBeenCalledWith(
|
|
215
|
-
'[dte-unlayer]',
|
|
216
|
-
'Failed to apply grouped merge tags',
|
|
217
|
-
expect.any(Error),
|
|
218
|
-
);
|
|
219
|
-
consoleError.mockRestore();
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe('when tags are missing', () => {
|
|
224
|
-
const setMergeTags = jest.fn();
|
|
225
|
-
const addEventListener = jest.fn();
|
|
226
|
-
|
|
227
|
-
beforeEach(() => {
|
|
228
|
-
scheduleUnlayerMergeTags({ addEventListener, setMergeTags }, undefined);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test('does not touch the editor', () => {
|
|
232
|
-
expect(setMergeTags).not.toHaveBeenCalled();
|
|
233
|
-
expect(addEventListener).not.toHaveBeenCalled();
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { UnlayerStore } from '../store';
|
|
2
|
+
import type { CreateUnlayerEditorProps } from '../unlayer-interface';
|
|
3
|
+
|
|
4
|
+
const editorMock = {
|
|
5
|
+
addEventListener: jest.fn(),
|
|
6
|
+
removeEventListener: jest.fn(),
|
|
7
|
+
registerCallback: jest.fn(),
|
|
8
|
+
unregisterCallback: jest.fn(),
|
|
9
|
+
setMergeTags: jest.fn(),
|
|
10
|
+
loadDesign: jest.fn(),
|
|
11
|
+
setBodyValues: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
jest.mock('../loadScript', () => ({ loadScript: jest.fn().mockResolvedValue(undefined) }));
|
|
15
|
+
jest.mock('../unlayer', () => ({
|
|
16
|
+
...jest.requireActual('../unlayer'),
|
|
17
|
+
createUnlayerEditor: jest.fn(() => editorMock),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
const customerNameTag = {
|
|
21
|
+
id: '10',
|
|
22
|
+
name: 'Customer Name',
|
|
23
|
+
propertyPath: '{{Customer.Name | safe | nl2br }}',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const scoreTag = {
|
|
27
|
+
id: 'form-7-abc',
|
|
28
|
+
name: 'Score',
|
|
29
|
+
propertyPath: '{{ __submission_fields["7"]["abc"] | safe | nl2br }}',
|
|
30
|
+
group: ['Forms', 'Customer Intake'],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const flatCustomer = {
|
|
34
|
+
'10': { name: 'Customer Name', value: '{{Customer.Name | safe | nl2br }}' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const groupedScore = {
|
|
38
|
+
forms: {
|
|
39
|
+
name: 'Forms',
|
|
40
|
+
mergeTags: {
|
|
41
|
+
customer_intake: {
|
|
42
|
+
name: 'Customer Intake',
|
|
43
|
+
mergeTags: {
|
|
44
|
+
form_7_abc: {
|
|
45
|
+
name: 'Score',
|
|
46
|
+
value: '{{ __submission_fields["7"]["abc"] | safe | nl2br }}',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const fireEditorEvent = (event: string) => {
|
|
55
|
+
editorMock.addEventListener.mock.calls
|
|
56
|
+
.filter(([name]) => name === event)
|
|
57
|
+
.forEach(([, handler]) => handler());
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
describe('UnlayerStore merge tags', () => {
|
|
61
|
+
let store: UnlayerStore;
|
|
62
|
+
let consoleError: jest.SpyInstance;
|
|
63
|
+
|
|
64
|
+
const createStore = (props: Partial<CreateUnlayerEditorProps> = {}) => {
|
|
65
|
+
store = new UnlayerStore({ tools: [], ...props });
|
|
66
|
+
return store;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const initStore = async () => {
|
|
70
|
+
(window as any).unlayer = {};
|
|
71
|
+
const container = document.createElement('div');
|
|
72
|
+
container.appendChild(document.createElement('iframe'));
|
|
73
|
+
await store.init(container);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
jest.clearAllMocks();
|
|
78
|
+
consoleError = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterEach(() => {
|
|
82
|
+
store.destroy();
|
|
83
|
+
consoleError.mockRestore();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('when the editor initializes with merge tags', () => {
|
|
87
|
+
beforeEach(async () => {
|
|
88
|
+
createStore({ mergeTags: [customerNameTag, scoreTag] });
|
|
89
|
+
await initStore();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('does not touch the editor before it is ready', () => {
|
|
93
|
+
expect(editorMock.setMergeTags).not.toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('applies the grouped shape once on editor:ready', () => {
|
|
97
|
+
fireEditorEvent('editor:ready');
|
|
98
|
+
fireEditorEvent('design:loaded');
|
|
99
|
+
|
|
100
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledWith({
|
|
102
|
+
...flatCustomer,
|
|
103
|
+
...groupedScore,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('applies on design:loaded when editor:ready never fires', () => {
|
|
108
|
+
fireEditorEvent('design:loaded');
|
|
109
|
+
|
|
110
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledWith({
|
|
112
|
+
...flatCustomer,
|
|
113
|
+
...groupedScore,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('retries on design:loaded when the first apply throws', () => {
|
|
118
|
+
editorMock.setMergeTags.mockImplementationOnce(() => {
|
|
119
|
+
throw new Error('not ready');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
fireEditorEvent('editor:ready');
|
|
123
|
+
fireEditorEvent('design:loaded');
|
|
124
|
+
fireEditorEvent('design:loaded');
|
|
125
|
+
|
|
126
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledTimes(2);
|
|
127
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
128
|
+
'[dte-unlayer]',
|
|
129
|
+
'Failed to apply merge tags',
|
|
130
|
+
expect.any(Error),
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('with setMergeTags', () => {
|
|
136
|
+
describe('when called before the editor is ready', () => {
|
|
137
|
+
beforeEach(async () => {
|
|
138
|
+
createStore({ mergeTags: [customerNameTag] });
|
|
139
|
+
store.unlayerRef.setMergeTags([customerNameTag, scoreTag]);
|
|
140
|
+
await initStore();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('replaces the initial tags and applies once on ready', () => {
|
|
144
|
+
fireEditorEvent('editor:ready');
|
|
145
|
+
|
|
146
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledTimes(1);
|
|
147
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledWith({
|
|
148
|
+
...flatCustomer,
|
|
149
|
+
...groupedScore,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('when called after the editor is ready', () => {
|
|
155
|
+
beforeEach(async () => {
|
|
156
|
+
createStore({ mergeTags: [customerNameTag] });
|
|
157
|
+
await initStore();
|
|
158
|
+
fireEditorEvent('editor:ready');
|
|
159
|
+
editorMock.setMergeTags.mockClear();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('applies immediately', () => {
|
|
163
|
+
store.unlayerRef.setMergeTags([customerNameTag, scoreTag]);
|
|
164
|
+
|
|
165
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledTimes(1);
|
|
166
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledWith({
|
|
167
|
+
...flatCustomer,
|
|
168
|
+
...groupedScore,
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('does not re-apply on later design:loaded events', () => {
|
|
173
|
+
store.unlayerRef.setMergeTags([customerNameTag, scoreTag]);
|
|
174
|
+
fireEditorEvent('design:loaded');
|
|
175
|
+
|
|
176
|
+
expect(editorMock.setMergeTags).toHaveBeenCalledTimes(1);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('ignores an empty set', () => {
|
|
180
|
+
store.unlayerRef.setMergeTags([]);
|
|
181
|
+
|
|
182
|
+
expect(editorMock.setMergeTags).not.toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -3,13 +3,12 @@ export * from './tools';
|
|
|
3
3
|
export * from './api-core';
|
|
4
4
|
export * from './api-custom-tools';
|
|
5
5
|
export * from './unlayer-interface';
|
|
6
|
-
export { isUnlayerMergeTagGroup,
|
|
6
|
+
export { isUnlayerMergeTagGroup, toUnlayerMergeTags } from './merge-tags';
|
|
7
7
|
export type {
|
|
8
8
|
ToUnlayerMergeTagsOptions,
|
|
9
9
|
UnlayerMergeTagGroup,
|
|
10
10
|
UnlayerMergeTagLeaf,
|
|
11
11
|
UnlayerMergeTagsConfig,
|
|
12
|
-
UnlayerMergeTagsEditor,
|
|
13
12
|
} from './merge-tags';
|
|
14
13
|
export * from './loadScript';
|
|
15
14
|
export * from './shared/const';
|
package/src/merge-tags.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { logUnlayerError } from './log';
|
|
2
1
|
import { UnlayerEditorMergeTagInfo } from './unlayer-interface';
|
|
3
2
|
|
|
4
3
|
export interface UnlayerMergeTagLeaf {
|
|
@@ -8,7 +7,8 @@ export interface UnlayerMergeTagLeaf {
|
|
|
8
7
|
|
|
9
8
|
export interface UnlayerMergeTagGroup {
|
|
10
9
|
name: string;
|
|
11
|
-
|
|
10
|
+
/** Nested submenu: leaves and/or further groups (Unlayer supports arbitrary depth). */
|
|
11
|
+
mergeTags: UnlayerMergeTagsConfig;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export type UnlayerMergeTagsConfig = Record<string, UnlayerMergeTagLeaf | UnlayerMergeTagGroup>;
|
|
@@ -21,11 +21,6 @@ export interface ToUnlayerMergeTagsOptions {
|
|
|
21
21
|
groups?: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export interface UnlayerMergeTagsEditor {
|
|
25
|
-
addEventListener(event: string, cb: (arg: any) => void): void;
|
|
26
|
-
setMergeTags(tags: UnlayerMergeTagsConfig): void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
24
|
export function isUnlayerMergeTagGroup(
|
|
30
25
|
entry: UnlayerMergeTagLeaf | UnlayerMergeTagGroup,
|
|
31
26
|
): entry is UnlayerMergeTagGroup {
|
|
@@ -42,6 +37,43 @@ const toMergeTagKey = (value: string) => {
|
|
|
42
37
|
return key || value;
|
|
43
38
|
};
|
|
44
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes the host `group` into the submenu path the tag belongs to:
|
|
42
|
+
* `'Custom Fields'` → `['Custom Fields']`, `['Forms', 'Intake']` → `['Forms', 'Intake']`.
|
|
43
|
+
* Blank segments are dropped; an empty result means the tag stays at the top level.
|
|
44
|
+
*/
|
|
45
|
+
const toGroupPath = (group: UnlayerEditorMergeTagInfo['group']): string[] => {
|
|
46
|
+
const segments = Array.isArray(group) ? group : group === undefined ? [] : [group];
|
|
47
|
+
|
|
48
|
+
return segments.map(segment => segment.trim()).filter(Boolean);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Returns the `mergeTags` record of the (possibly nested) group at `path`, creating groups on the way. */
|
|
52
|
+
const ensureGroup = (root: UnlayerMergeTagsConfig, path: string[]) => {
|
|
53
|
+
let current = root;
|
|
54
|
+
|
|
55
|
+
for (const groupName of path) {
|
|
56
|
+
const groupKey = toMergeTagKey(groupName);
|
|
57
|
+
const existing = current[groupKey];
|
|
58
|
+
|
|
59
|
+
if (existing && isUnlayerMergeTagGroup(existing)) {
|
|
60
|
+
current = existing.mergeTags;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const created: UnlayerMergeTagGroup = { name: groupName, mergeTags: {} };
|
|
65
|
+
current[groupKey] = created;
|
|
66
|
+
current = created.mergeTags;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return current;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Builds Unlayer's `mergeTags` config. With `groups` enabled (default) tags nest under the
|
|
74
|
+
* submenu path in `group`; `UnlayerStore` applies that shape once the editor is ready, since
|
|
75
|
+
* nested groups at `createEditor` time prevent DTE custom tools from loading.
|
|
76
|
+
*/
|
|
45
77
|
export const toUnlayerMergeTags = (
|
|
46
78
|
tags: UnlayerEditorMergeTagInfo[] | undefined,
|
|
47
79
|
options?: ToUnlayerMergeTagsOptions,
|
|
@@ -58,61 +90,10 @@ export const toUnlayerMergeTags = (
|
|
|
58
90
|
value: tag.propertyPath,
|
|
59
91
|
};
|
|
60
92
|
const tagKey = toMergeTagKey(tag.id);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
if (!groupName) {
|
|
64
|
-
out[tagKey] = mergeTag;
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const groupKey = toMergeTagKey(groupName);
|
|
69
|
-
const existing = out[groupKey];
|
|
70
|
-
|
|
71
|
-
if (existing && isUnlayerMergeTagGroup(existing)) {
|
|
72
|
-
existing.mergeTags[tagKey] = mergeTag;
|
|
73
|
-
return out;
|
|
74
|
-
}
|
|
93
|
+
const groupPath = groupsEnabled ? toGroupPath(tag.group) : [];
|
|
75
94
|
|
|
76
|
-
out[
|
|
77
|
-
name: groupName,
|
|
78
|
-
mergeTags: {
|
|
79
|
-
[tagKey]: mergeTag,
|
|
80
|
-
},
|
|
81
|
-
};
|
|
95
|
+
ensureGroup(out, groupPath)[tagKey] = mergeTag;
|
|
82
96
|
|
|
83
97
|
return out;
|
|
84
98
|
}, {});
|
|
85
99
|
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Nested Unlayer groups passed to createEditor prevent DTE custom tools from
|
|
89
|
-
* loading in the host app. Apply the 0.151 flat shape at init, then upgrade to
|
|
90
|
-
* grouped submenus once the editor (and customJS tools) are ready.
|
|
91
|
-
*/
|
|
92
|
-
export const scheduleUnlayerMergeTags = (
|
|
93
|
-
editor: UnlayerMergeTagsEditor,
|
|
94
|
-
tags: UnlayerEditorMergeTagInfo[] | undefined,
|
|
95
|
-
) => {
|
|
96
|
-
const grouped = toUnlayerMergeTags(tags);
|
|
97
|
-
|
|
98
|
-
if (!grouped) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let isApplied = false;
|
|
103
|
-
const apply = () => {
|
|
104
|
-
if (isApplied) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
editor.setMergeTags(grouped);
|
|
110
|
-
isApplied = true;
|
|
111
|
-
} catch (error: unknown) {
|
|
112
|
-
logUnlayerError('Failed to apply grouped merge tags', error);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
editor.addEventListener('editor:ready', apply);
|
|
117
|
-
editor.addEventListener('design:loaded', apply);
|
|
118
|
-
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom tools whose `values.id` is a per-instance GUID that must stay unique
|
|
3
|
+
* within a design. When such a tool is duplicated (content or row duplicate),
|
|
4
|
+
* Unlayer copies the values as-is, so the host regenerates the id of the copy.
|
|
5
|
+
*/
|
|
6
|
+
export const INSTANCE_ID_TOOL_SLUGS = ['embedded-image'];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generates an RFC 4122 v4 GUID (`3fa85f64-5717-4562-b3fc-2c963f66afa6`) with the
|
|
10
|
+
* platform `crypto.randomUUID()`. It is only exposed in secure contexts (https /
|
|
11
|
+
* localhost); when missing the error is logged and no id is produced.
|
|
12
|
+
*/
|
|
13
|
+
export const generateInstanceId = (): string | undefined => {
|
|
14
|
+
if (typeof globalThis.crypto?.randomUUID !== 'function') {
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.error(
|
|
17
|
+
'crypto.randomUUID is not available (insecure context?), cannot generate an instance id',
|
|
18
|
+
);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return globalThis.crypto.randomUUID();
|
|
23
|
+
};
|