Enhance Template and Object Management with Resource Support
- Updated fcTemplateLang to include resourceNames for improved autocompletion in template languages. - Added resource management capabilities to DocumentTemplate and EmailTemplate models, allowing for dynamic handling of resources. - Enhanced DocumentTemplateInfo and EmailTemplateInfo components to display and manage resources, improving user interaction and data visibility. - Refactored related components, including CodeBlockEditor and ObjectProperty, to support resource integration, ensuring consistent functionality across the application.
This commit is contained in:
parent
db23e685ff
commit
e610290465
@ -4,7 +4,8 @@ import {
|
|||||||
indentNodeProp,
|
indentNodeProp,
|
||||||
delimitedIndent,
|
delimitedIndent,
|
||||||
foldNodeProp,
|
foldNodeProp,
|
||||||
foldInside
|
foldInside,
|
||||||
|
syntaxTree
|
||||||
} from '@codemirror/language'
|
} from '@codemirror/language'
|
||||||
import { styleTags, tags as t } from '@lezer/highlight'
|
import { styleTags, tags as t } from '@lezer/highlight'
|
||||||
import { parseMixed } from '@lezer/common'
|
import { parseMixed } from '@lezer/common'
|
||||||
@ -81,13 +82,64 @@ function buildJsScope(autoCompleteObject, templateType) {
|
|||||||
return typed
|
return typed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isResourceValueContext(state, position) {
|
||||||
|
let node = syntaxTree(state).resolveInner(position, -1)
|
||||||
|
let insideTemplateInterpolation = false
|
||||||
|
|
||||||
|
while (node) {
|
||||||
|
if (node.name === 'Interpolation') {
|
||||||
|
insideTemplateInterpolation = true
|
||||||
|
}
|
||||||
|
if (node.name === 'AttributeValue' || node.name === 'String') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (node.name === 'TemplateString') {
|
||||||
|
return !insideTemplateInterpolation
|
||||||
|
}
|
||||||
|
node = node.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function resourceCompletionSource(resourceNames) {
|
||||||
|
const options = [...new Set(resourceNames)]
|
||||||
|
.filter((name) => typeof name === 'string' && name.length > 0)
|
||||||
|
.map((name) => ({
|
||||||
|
label: `@[${name}]`,
|
||||||
|
apply: `@[${name}]`,
|
||||||
|
type: 'variable',
|
||||||
|
detail: 'Template resource'
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (context) => {
|
||||||
|
if (options.length === 0) return null
|
||||||
|
|
||||||
|
const before = context.state.sliceDoc(0, context.pos)
|
||||||
|
const match = before.match(/@(?:\[[^\]\r\n]*)?$/)
|
||||||
|
if (!match || !isResourceValueContext(context.state, context.pos)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
from: context.pos - match[0].length,
|
||||||
|
options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Language support for FarmControl EJS+XML document templates.
|
* Language support for FarmControl EJS+XML document templates.
|
||||||
* @param {{ autoCompleteObject?: object|string, templateType?: string }} [options]
|
* @param {{ autoCompleteObject?: object|string, templateType?: string, resourceNames?: string[] }} [options]
|
||||||
*/
|
*/
|
||||||
export function fcTemplateLang(options = {}) {
|
export function fcTemplateLang(options = {}) {
|
||||||
const { autoCompleteObject, templateType = 'documentTemplate' } = options
|
const {
|
||||||
|
autoCompleteObject,
|
||||||
|
templateType = 'documentTemplate',
|
||||||
|
resourceNames = []
|
||||||
|
} = options
|
||||||
const jsScope = buildJsScope(autoCompleteObject, templateType)
|
const jsScope = buildJsScope(autoCompleteObject, templateType)
|
||||||
|
const completeResources = resourceCompletionSource(resourceNames)
|
||||||
|
|
||||||
return new LanguageSupport(fcTemplateLanguage, [
|
return new LanguageSupport(fcTemplateLanguage, [
|
||||||
xmlLanguage.data.of({
|
xmlLanguage.data.of({
|
||||||
@ -96,6 +148,8 @@ export function fcTemplateLang(options = {}) {
|
|||||||
fcTemplateAttributes
|
fcTemplateAttributes
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
xmlLanguage.data.of({ autocomplete: completeResources }),
|
||||||
|
javascriptLanguage.data.of({ autocomplete: completeResources }),
|
||||||
autoCloseTags,
|
autoCloseTags,
|
||||||
...javascriptCompletionSupport(jsScope)
|
...javascriptCompletionSupport(jsScope)
|
||||||
])
|
])
|
||||||
|
|||||||
@ -8,10 +8,12 @@ import useCollapseState from '../../hooks/useCollapseState.jsx'
|
|||||||
import NotesPanel from '../../common/NotesPanel.jsx'
|
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||||
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
||||||
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||||
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
import ViewButton from '../../common/ViewButton.jsx'
|
import ViewButton from '../../common/ViewButton.jsx'
|
||||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
|
import FileIcon from '../../../Icons/FileIcon.jsx'
|
||||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||||
import EditButtons from '../../common/EditButtons.jsx'
|
import EditButtons from '../../common/EditButtons.jsx'
|
||||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||||
@ -23,7 +25,10 @@ import ObjectTable from '../../common/ObjectTable.jsx'
|
|||||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
|
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
|
||||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import { getModelByName } from '../../../../database/ObjectModels.js'
|
import {
|
||||||
|
getModelByName,
|
||||||
|
getModelProperty
|
||||||
|
} from '../../../../database/ObjectModels.js'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('DocumentTemplateInfo')
|
const log = loglevel.getLogger('DocumentTemplateInfo')
|
||||||
@ -40,6 +45,7 @@ const DocumentTemplateInfo = () => {
|
|||||||
'DocumentTemplateInfo',
|
'DocumentTemplateInfo',
|
||||||
{
|
{
|
||||||
info: true,
|
info: true,
|
||||||
|
resources: true,
|
||||||
stocks: true,
|
stocks: true,
|
||||||
notes: true,
|
notes: true,
|
||||||
auditLogs: false
|
auditLogs: false
|
||||||
@ -108,6 +114,7 @@ const DocumentTemplateInfo = () => {
|
|||||||
disabled={objectFormState.loading}
|
disabled={objectFormState.loading}
|
||||||
items={[
|
items={[
|
||||||
{ key: 'info', label: 'DocumentTemplate Information' },
|
{ key: 'info', label: 'DocumentTemplate Information' },
|
||||||
|
{ key: 'resources', label: 'Resources' },
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
]}
|
]}
|
||||||
@ -161,25 +168,27 @@ const DocumentTemplateInfo = () => {
|
|||||||
loading={objectFormState.loading}
|
loading={objectFormState.loading}
|
||||||
ref={actionHandlerRef}
|
ref={actionHandlerRef}
|
||||||
>
|
>
|
||||||
<InfoCollapse
|
<ObjectForm
|
||||||
title='Document Template Information'
|
id={documentTemplateId}
|
||||||
icon={<InfoCircleIcon />}
|
type='documentTemplate'
|
||||||
active={collapseState.info}
|
style={{ height: '100%' }}
|
||||||
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
ref={objectFormRef}
|
||||||
collapseKey='info'
|
setCurrentObject={true}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ObjectForm
|
{({ loading, isEditing, objectData }) => (
|
||||||
id={documentTemplateId}
|
<Flex vertical gap={'large'}>
|
||||||
type='documentTemplate'
|
<InfoCollapse
|
||||||
style={{ height: '100%' }}
|
title='Document Template Information'
|
||||||
ref={objectFormRef}
|
icon={<InfoCircleIcon />}
|
||||||
setCurrentObject={true}
|
active={collapseState.info}
|
||||||
onStateChange={(state) => {
|
onToggle={(expanded) =>
|
||||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
updateCollapseState('info', expanded)
|
||||||
}}
|
}
|
||||||
>
|
collapseKey='info'
|
||||||
{({ loading, isEditing, objectData }) => {
|
>
|
||||||
return (
|
|
||||||
<ObjectInfo
|
<ObjectInfo
|
||||||
loading={loading}
|
loading={loading}
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
@ -188,14 +197,35 @@ const DocumentTemplateInfo = () => {
|
|||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
content: false,
|
content: false,
|
||||||
testObject: false
|
testObject: false,
|
||||||
|
resources: false
|
||||||
}}
|
}}
|
||||||
labelWidth='190px'
|
labelWidth='190px'
|
||||||
/>
|
/>
|
||||||
)
|
</InfoCollapse>
|
||||||
}}
|
<InfoCollapse
|
||||||
</ObjectForm>
|
title='Resources'
|
||||||
</InfoCollapse>
|
icon={<FileIcon />}
|
||||||
|
active={collapseState.resources}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('resources', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='resources'
|
||||||
|
>
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty(
|
||||||
|
'documentTemplate',
|
||||||
|
'resources'
|
||||||
|
)}
|
||||||
|
isEditing={isEditing}
|
||||||
|
objectData={objectData}
|
||||||
|
loading={loading}
|
||||||
|
size='medium'
|
||||||
|
/>
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</ObjectForm>
|
||||||
</ActionHandler>
|
</ActionHandler>
|
||||||
|
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
|
|||||||
@ -42,6 +42,7 @@ const EmailTemplateInfo = () => {
|
|||||||
'EmailTemplateInfo',
|
'EmailTemplateInfo',
|
||||||
{
|
{
|
||||||
info: true,
|
info: true,
|
||||||
|
resources: true,
|
||||||
attachments: true,
|
attachments: true,
|
||||||
notes: true,
|
notes: true,
|
||||||
auditLogs: false
|
auditLogs: false
|
||||||
@ -107,6 +108,7 @@ const EmailTemplateInfo = () => {
|
|||||||
disabled={objectFormState.loading}
|
disabled={objectFormState.loading}
|
||||||
items={[
|
items={[
|
||||||
{ key: 'info', label: 'Email Template Information' },
|
{ key: 'info', label: 'Email Template Information' },
|
||||||
|
{ key: 'resources', label: 'Resources' },
|
||||||
{ key: 'attachments', label: 'Attachments' },
|
{ key: 'attachments', label: 'Attachments' },
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
@ -191,11 +193,29 @@ const EmailTemplateInfo = () => {
|
|||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
content: false,
|
content: false,
|
||||||
testObject: false,
|
testObject: false,
|
||||||
|
resources: false,
|
||||||
attachments: false
|
attachments: false
|
||||||
}}
|
}}
|
||||||
labelWidth='210px'
|
labelWidth='210px'
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Resources'
|
||||||
|
icon={<FileIcon />}
|
||||||
|
active={collapseState.resources}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('resources', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='resources'
|
||||||
|
>
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty('emailTemplate', 'resources')}
|
||||||
|
isEditing={isEditing}
|
||||||
|
objectData={objectData}
|
||||||
|
loading={loading}
|
||||||
|
size='medium'
|
||||||
|
/>
|
||||||
|
</InfoCollapse>
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
title='Attachments'
|
title='Attachments'
|
||||||
icon={<FileIcon />}
|
icon={<FileIcon />}
|
||||||
|
|||||||
@ -166,6 +166,7 @@ export default function CodeBlockEditor({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
minimal = false,
|
minimal = false,
|
||||||
autoCompleteObject = null,
|
autoCompleteObject = null,
|
||||||
|
resourceNames = [],
|
||||||
errorLines = [],
|
errorLines = [],
|
||||||
templateType,
|
templateType,
|
||||||
onCursorChange = null
|
onCursorChange = null
|
||||||
@ -181,8 +182,14 @@ export default function CodeBlockEditor({
|
|||||||
}, [onCursorChange])
|
}, [onCursorChange])
|
||||||
|
|
||||||
const languageExtension = useMemo(
|
const languageExtension = useMemo(
|
||||||
() => getCodeLanguageExtension(language, autoCompleteObject, templateType),
|
() =>
|
||||||
[language, autoCompleteObject, templateType]
|
getCodeLanguageExtension(
|
||||||
|
language,
|
||||||
|
autoCompleteObject,
|
||||||
|
templateType,
|
||||||
|
resourceNames
|
||||||
|
),
|
||||||
|
[language, autoCompleteObject, templateType, resourceNames]
|
||||||
)
|
)
|
||||||
|
|
||||||
const errorLineExtension = useMemo(() => {
|
const errorLineExtension = useMemo(() => {
|
||||||
@ -326,6 +333,7 @@ CodeBlockEditor.propTypes = {
|
|||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
minimal: PropTypes.bool,
|
minimal: PropTypes.bool,
|
||||||
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||||
|
resourceNames: PropTypes.arrayOf(PropTypes.string),
|
||||||
errorLines: PropTypes.arrayOf(PropTypes.number),
|
errorLines: PropTypes.arrayOf(PropTypes.number),
|
||||||
templateType: PropTypes.string,
|
templateType: PropTypes.string,
|
||||||
onCursorChange: PropTypes.func
|
onCursorChange: PropTypes.func
|
||||||
|
|||||||
@ -116,6 +116,7 @@ const ObjectProperty = ({
|
|||||||
height = 'auto',
|
height = 'auto',
|
||||||
minimal = false,
|
minimal = false,
|
||||||
autoCompleteObject = null,
|
autoCompleteObject = null,
|
||||||
|
resourceNames = [],
|
||||||
errorLines = [],
|
errorLines = [],
|
||||||
templateType,
|
templateType,
|
||||||
onCursorChange,
|
onCursorChange,
|
||||||
@ -437,6 +438,7 @@ const ObjectProperty = ({
|
|||||||
readOnly={true}
|
readOnly={true}
|
||||||
minimal={minimal}
|
minimal={minimal}
|
||||||
autoCompleteObject={autoCompleteObject}
|
autoCompleteObject={autoCompleteObject}
|
||||||
|
resourceNames={resourceNames}
|
||||||
errorLines={errorLines}
|
errorLines={errorLines}
|
||||||
templateType={templateType}
|
templateType={templateType}
|
||||||
onCursorChange={onCursorChange}
|
onCursorChange={onCursorChange}
|
||||||
@ -990,6 +992,7 @@ const ObjectProperty = ({
|
|||||||
height={height}
|
height={height}
|
||||||
minimal={minimal}
|
minimal={minimal}
|
||||||
autoCompleteObject={autoCompleteObject}
|
autoCompleteObject={autoCompleteObject}
|
||||||
|
resourceNames={resourceNames}
|
||||||
errorLines={errorLines}
|
errorLines={errorLines}
|
||||||
templateType={templateType}
|
templateType={templateType}
|
||||||
onCursorChange={onCursorChange}
|
onCursorChange={onCursorChange}
|
||||||
@ -1145,6 +1148,7 @@ ObjectProperty.propTypes = {
|
|||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
language: PropTypes.string,
|
language: PropTypes.string,
|
||||||
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||||
|
resourceNames: PropTypes.arrayOf(PropTypes.string),
|
||||||
errorLines: PropTypes.arrayOf(PropTypes.number),
|
errorLines: PropTypes.arrayOf(PropTypes.number),
|
||||||
templateType: PropTypes.string,
|
templateType: PropTypes.string,
|
||||||
onCursorChange: PropTypes.func,
|
onCursorChange: PropTypes.func,
|
||||||
|
|||||||
@ -94,6 +94,16 @@ const parseEjsErrorLines = (message) => {
|
|||||||
return [...lines]
|
return [...lines]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeResourceNames = (resources) => {
|
||||||
|
if (!Array.isArray(resources)) return []
|
||||||
|
|
||||||
|
return resources
|
||||||
|
.map((resource) =>
|
||||||
|
typeof resource === 'string' ? resource : resource?.name
|
||||||
|
)
|
||||||
|
.filter((name) => typeof name === 'string' && name.length > 0)
|
||||||
|
}
|
||||||
|
|
||||||
const TemplateEditor = ({
|
const TemplateEditor = ({
|
||||||
objectData,
|
objectData,
|
||||||
template,
|
template,
|
||||||
@ -118,11 +128,15 @@ const TemplateEditor = ({
|
|||||||
const [errorLines, setErrorLines] = useState([])
|
const [errorLines, setErrorLines] = useState([])
|
||||||
const [formatLoading, setFormatLoading] = useState(false)
|
const [formatLoading, setFormatLoading] = useState(false)
|
||||||
const [intellisense, setIntellisense] = useState(null)
|
const [intellisense, setIntellisense] = useState(null)
|
||||||
|
const [responseResources, setResponseResources] = useState(null)
|
||||||
const intellisenseRequestIdRef = useRef(0)
|
const intellisenseRequestIdRef = useRef(0)
|
||||||
const intellisenseTimerRef = useRef(null)
|
const intellisenseTimerRef = useRef(null)
|
||||||
|
const resourceNames =
|
||||||
|
responseResources ?? normalizeResourceNames(currentTemplate?.resources)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIntellisense(null)
|
setIntellisense(null)
|
||||||
|
setResponseResources(null)
|
||||||
}, [currentTemplate?._id])
|
}, [currentTemplate?._id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -168,6 +182,9 @@ const TemplateEditor = ({
|
|||||||
setIntellisense(result.intellisense)
|
setIntellisense(result.intellisense)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(result?.resources)) {
|
||||||
|
setResponseResources(normalizeResourceNames(result.resources))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}, 200)
|
}, 200)
|
||||||
@ -369,6 +386,7 @@ const TemplateEditor = ({
|
|||||||
autoCompleteObject={
|
autoCompleteObject={
|
||||||
intellisense ?? currentTemplate?.testObject
|
intellisense ?? currentTemplate?.testObject
|
||||||
}
|
}
|
||||||
|
resourceNames={resourceNames}
|
||||||
errorLines={errorLines}
|
errorLines={errorLines}
|
||||||
templateType={templateType}
|
templateType={templateType}
|
||||||
onCursorChange={handleCursorChange}
|
onCursorChange={handleCursorChange}
|
||||||
|
|||||||
@ -26,7 +26,8 @@ export function toEditorCode(code, language = 'javascript') {
|
|||||||
export function getCodeLanguageExtension(
|
export function getCodeLanguageExtension(
|
||||||
language = 'javascript',
|
language = 'javascript',
|
||||||
autoCompleteObject = null,
|
autoCompleteObject = null,
|
||||||
templateType
|
templateType,
|
||||||
|
resourceNames = []
|
||||||
) {
|
) {
|
||||||
const lang = Array.isArray(language) ? language[0] : language
|
const lang = Array.isArray(language) ? language[0] : language
|
||||||
|
|
||||||
@ -43,7 +44,11 @@ export function getCodeLanguageExtension(
|
|||||||
return xml()
|
return xml()
|
||||||
case 'fctemplatelang':
|
case 'fctemplatelang':
|
||||||
case 'ejs':
|
case 'ejs':
|
||||||
return fcTemplateLang({ autoCompleteObject, templateType })
|
return fcTemplateLang({
|
||||||
|
autoCompleteObject,
|
||||||
|
templateType,
|
||||||
|
resourceNames
|
||||||
|
})
|
||||||
case 'html':
|
case 'html':
|
||||||
return html()
|
return html()
|
||||||
case 'css':
|
case 'css':
|
||||||
|
|||||||
@ -2240,12 +2240,14 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = getTemplateErrorFromResponse(err)
|
const error = getTemplateErrorFromResponse(err)
|
||||||
const intellisense = err?.response?.data?.intellisense
|
const intellisense = err?.response?.data?.intellisense
|
||||||
|
const resources = err?.response?.data?.resources
|
||||||
logger.error('Error fetching template intellisense:', error)
|
logger.error('Error fetching template intellisense:', error)
|
||||||
const payload = {
|
const payload = {
|
||||||
error,
|
error,
|
||||||
...(intellisense != null && typeof intellisense === 'object'
|
...(intellisense != null && typeof intellisense === 'object'
|
||||||
? { intellisense }
|
? { intellisense }
|
||||||
: {})
|
: {}),
|
||||||
|
...(Array.isArray(resources) ? { resources } : {})
|
||||||
}
|
}
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
callback(payload)
|
callback(payload)
|
||||||
|
|||||||
@ -22,6 +22,34 @@ import ListIcon from '../../components/Icons/ListIcon'
|
|||||||
import DuplicateIcon from '../../components/Icons/DuplicateIcon'
|
import DuplicateIcon from '../../components/Icons/DuplicateIcon'
|
||||||
import BinIcon from '../../components/Icons/BinIcon'
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
|
|
||||||
|
const computeTemplateResources = (resources) => {
|
||||||
|
if (!Array.isArray(resources)) return []
|
||||||
|
|
||||||
|
const usedNames = new Set()
|
||||||
|
const nextSuffix = new Map()
|
||||||
|
|
||||||
|
return resources.map((resource) => {
|
||||||
|
const filename = resource?.file?.filename || resource?.file?.name || ''
|
||||||
|
const baseName =
|
||||||
|
filename
|
||||||
|
.replace(/\.[^.]*$/, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '') || 'resource'
|
||||||
|
let name = baseName
|
||||||
|
let suffix = nextSuffix.get(baseName) || 1
|
||||||
|
|
||||||
|
while (usedNames.has(name)) {
|
||||||
|
name = `${baseName}-${suffix}`
|
||||||
|
suffix += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
usedNames.add(name)
|
||||||
|
nextSuffix.set(baseName, suffix)
|
||||||
|
return { ...resource, name }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const DocumentTemplate = {
|
export const DocumentTemplate = {
|
||||||
name: 'documentTemplate',
|
name: 'documentTemplate',
|
||||||
label: 'Document Template',
|
label: 'Document Template',
|
||||||
@ -288,6 +316,36 @@ export const DocumentTemplate = {
|
|||||||
type: 'codeBlock',
|
type: 'codeBlock',
|
||||||
language: 'fcTemplateLang'
|
language: 'fcTemplateLang'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'resources',
|
||||||
|
label: 'Resources',
|
||||||
|
type: 'objectChildren',
|
||||||
|
required: false,
|
||||||
|
canAddRemove: true,
|
||||||
|
size: 'medium',
|
||||||
|
columns: ['name', 'file'],
|
||||||
|
value: (documentTemplate) =>
|
||||||
|
computeTemplateResources(documentTemplate?.resources),
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'file',
|
||||||
|
label: 'File',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'file',
|
||||||
|
required: true,
|
||||||
|
showHyperlink: true,
|
||||||
|
columnWidth: 230
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'testObject',
|
name: 'testObject',
|
||||||
label: 'Test Object',
|
label: 'Test Object',
|
||||||
|
|||||||
@ -22,6 +22,34 @@ import EmailTemplateIcon from '../../components/Icons/EmailTemplateIcon'
|
|||||||
import DuplicateIcon from '../../components/Icons/DuplicateIcon'
|
import DuplicateIcon from '../../components/Icons/DuplicateIcon'
|
||||||
import BinIcon from '../../components/Icons/BinIcon'
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
|
|
||||||
|
const computeTemplateResources = (resources) => {
|
||||||
|
if (!Array.isArray(resources)) return []
|
||||||
|
|
||||||
|
const usedNames = new Set()
|
||||||
|
const nextSuffix = new Map()
|
||||||
|
|
||||||
|
return resources.map((resource) => {
|
||||||
|
const filename = resource?.file?.filename || resource?.file?.name || ''
|
||||||
|
const baseName =
|
||||||
|
filename
|
||||||
|
.replace(/\.[^.]*$/, '')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '') || 'resource'
|
||||||
|
let name = baseName
|
||||||
|
let suffix = nextSuffix.get(baseName) || 1
|
||||||
|
|
||||||
|
while (usedNames.has(name)) {
|
||||||
|
name = `${baseName}-${suffix}`
|
||||||
|
suffix += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
usedNames.add(name)
|
||||||
|
nextSuffix.set(baseName, suffix)
|
||||||
|
return { ...resource, name }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const EmailTemplate = {
|
export const EmailTemplate = {
|
||||||
name: 'emailTemplate',
|
name: 'emailTemplate',
|
||||||
label: 'Email Template',
|
label: 'Email Template',
|
||||||
@ -270,6 +298,36 @@ export const EmailTemplate = {
|
|||||||
objectType: (data) => data?.objectType,
|
objectType: (data) => data?.objectType,
|
||||||
showHyperlink: true
|
showHyperlink: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'resources',
|
||||||
|
label: 'Resources',
|
||||||
|
type: 'objectChildren',
|
||||||
|
required: false,
|
||||||
|
canAddRemove: true,
|
||||||
|
size: 'medium',
|
||||||
|
columns: ['name', 'file'],
|
||||||
|
value: (emailTemplate) =>
|
||||||
|
computeTemplateResources(emailTemplate?.resources),
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'file',
|
||||||
|
label: 'File',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'file',
|
||||||
|
required: true,
|
||||||
|
showHyperlink: true,
|
||||||
|
columnWidth: 230
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'attachments',
|
name: 'attachments',
|
||||||
label: 'Attachments',
|
label: 'Attachments',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user