Enhance fcTemplateLang with autocompletion support and XML schema integration
- Updated fcTemplateLang to accept an autoCompleteObject parameter, improving JavaScript autocompletion capabilities. - Introduced a new schema.js file defining XML attributes and elements for FarmControl templates, enhancing template editing. - Modified CodeBlockEditor and ObjectProperty components to pass autoCompleteObject, ensuring consistent autocompletion behavior. - Updated TemplateEditor to utilize objectData for autocompletion, improving user experience in template editing.
This commit is contained in:
parent
24959ce9d8
commit
eeccfe077f
@ -8,9 +8,22 @@ import {
|
||||
} from '@codemirror/language'
|
||||
import { styleTags, tags as t } from '@lezer/highlight'
|
||||
import { parseMixed } from '@lezer/common'
|
||||
import { javascriptLanguage } from '@codemirror/lang-javascript'
|
||||
import { xmlLanguage } from '@codemirror/lang-xml'
|
||||
import {
|
||||
javascriptLanguage,
|
||||
scopeCompletionSource,
|
||||
localCompletionSource
|
||||
} from '@codemirror/lang-javascript'
|
||||
import {
|
||||
xmlLanguage,
|
||||
completeFromSchema,
|
||||
autoCloseTags
|
||||
} from '@codemirror/lang-xml'
|
||||
import { parser as ejsParser } from './ejs.parser.js'
|
||||
import {
|
||||
fcTemplateElements,
|
||||
fcTemplateAttributes,
|
||||
fcTemplateHelpers
|
||||
} from './schema.js'
|
||||
|
||||
/**
|
||||
* FarmControl document template language: custom XML tags with EJS
|
||||
@ -18,6 +31,7 @@ import { parser as ejsParser } from './ejs.parser.js'
|
||||
* XML and JavaScript each get their own highlighting/support.
|
||||
*
|
||||
* @see https://codemirror.net/examples/mixed-language/
|
||||
* @see https://codemirror.net/examples/autocompletion/
|
||||
*/
|
||||
const fcTemplateParser = ejsParser.configure({
|
||||
props: [
|
||||
@ -60,9 +74,42 @@ export const fcTemplateLanguage = LRLanguage.define({
|
||||
}
|
||||
})
|
||||
|
||||
function buildJsScope(autoCompleteObject) {
|
||||
let data = autoCompleteObject
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
data = JSON.parse(data)
|
||||
} catch {
|
||||
data = {}
|
||||
}
|
||||
}
|
||||
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
||||
data = {}
|
||||
}
|
||||
return {
|
||||
...data,
|
||||
fc: fcTemplateHelpers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Language support for FarmControl EJS+XML document templates.
|
||||
* @param {{ autoCompleteObject?: object|string }} [options]
|
||||
*/
|
||||
export function fcTemplateLang() {
|
||||
return new LanguageSupport(fcTemplateLanguage)
|
||||
export function fcTemplateLang(options = {}) {
|
||||
const { autoCompleteObject } = options
|
||||
const jsScope = buildJsScope(autoCompleteObject)
|
||||
|
||||
return new LanguageSupport(fcTemplateLanguage, [
|
||||
xmlLanguage.data.of({
|
||||
autocomplete: completeFromSchema(
|
||||
fcTemplateElements,
|
||||
fcTemplateAttributes
|
||||
)
|
||||
}),
|
||||
autoCloseTags,
|
||||
javascriptLanguage.data.of({
|
||||
autocomplete: [localCompletionSource, scopeCompletionSource(jsScope)]
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
106
src/codemirror/fcTemplateLang/schema.js
Normal file
106
src/codemirror/fcTemplateLang/schema.js
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* XML schema for FarmControl document template tags.
|
||||
* Mirrors custom elements / style attrs handled in farmcontrol-ws TemplateManager.
|
||||
*/
|
||||
|
||||
const styleAttrs = [
|
||||
'padding',
|
||||
'width',
|
||||
'height',
|
||||
'maxWidth',
|
||||
'maxHeight',
|
||||
'gap',
|
||||
'justify',
|
||||
'align',
|
||||
'border',
|
||||
'borderRadius',
|
||||
'vertical',
|
||||
'grow',
|
||||
'shrink',
|
||||
'color',
|
||||
'background',
|
||||
'scale',
|
||||
'textAlign',
|
||||
'textSize',
|
||||
'wordWrap'
|
||||
]
|
||||
|
||||
const justifyValues = [
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly'
|
||||
]
|
||||
|
||||
const alignValues = [
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
'stretch',
|
||||
'baseline'
|
||||
]
|
||||
|
||||
/** @type {import('@codemirror/lang-xml').AttrSpec[]} */
|
||||
export const fcTemplateAttributes = [
|
||||
...styleAttrs.map((name) => {
|
||||
const attr = { name, global: true }
|
||||
if (name === 'vertical') attr.values = ['true', 'false']
|
||||
if (name === 'justify') attr.values = justifyValues
|
||||
if (name === 'align') attr.values = alignValues
|
||||
if (name === 'textAlign') {
|
||||
attr.values = ['left', 'center', 'right', 'justify']
|
||||
}
|
||||
return attr
|
||||
}),
|
||||
{
|
||||
name: 'format',
|
||||
values: ['code128', 'code39', 'ean13', 'ean8', 'upc', 'qrcode']
|
||||
},
|
||||
{ name: 'barcodeWidth' },
|
||||
{
|
||||
name: 'type',
|
||||
values: ['header', 'footer', 'body']
|
||||
}
|
||||
]
|
||||
|
||||
const allTags = [
|
||||
'Flex',
|
||||
'Container',
|
||||
'Title1',
|
||||
'Title2',
|
||||
'Title3',
|
||||
'Title4',
|
||||
'Text',
|
||||
'Bold',
|
||||
'Divider',
|
||||
'ProgressBar',
|
||||
'Barcode',
|
||||
'DateTime',
|
||||
'Table',
|
||||
'Row',
|
||||
'Col'
|
||||
]
|
||||
|
||||
/** @type {import('@codemirror/lang-xml').ElementSpec[]} */
|
||||
export const fcTemplateElements = allTags.map((name) => {
|
||||
const el = { name, top: true, children: allTags }
|
||||
|
||||
if (name === 'Barcode') {
|
||||
el.attributes = ['format', 'barcodeWidth', ...styleAttrs]
|
||||
} else if (name === 'Row') {
|
||||
el.attributes = ['type', ...styleAttrs]
|
||||
} else if (name === 'Divider') {
|
||||
el.children = []
|
||||
}
|
||||
|
||||
return el
|
||||
})
|
||||
|
||||
/** Runtime helpers available as `fc` inside templates. */
|
||||
export const fcTemplateHelpers = {
|
||||
listObjects: async () => [],
|
||||
getObject: async () => ({}),
|
||||
formatDate: () => ''
|
||||
}
|
||||
@ -32,7 +32,8 @@ export default function CodeBlockEditor({
|
||||
height = 'auto',
|
||||
showLineNumbers = true,
|
||||
disabled = false,
|
||||
minimal = false
|
||||
minimal = false,
|
||||
autoCompleteObject = null
|
||||
}) {
|
||||
const { isDarkMode } = useThemeContext()
|
||||
const [codeMirrorOpen, setCodeMirrorOpen] = useState(false)
|
||||
@ -57,7 +58,7 @@ export default function CodeBlockEditor({
|
||||
return xml()
|
||||
case 'fctemplatelang':
|
||||
case 'ejs':
|
||||
return fcTemplateLang()
|
||||
return fcTemplateLang({ autoCompleteObject })
|
||||
case 'html':
|
||||
return html()
|
||||
case 'css':
|
||||
@ -86,7 +87,7 @@ export default function CodeBlockEditor({
|
||||
default:
|
||||
return javascript() // Default fallback
|
||||
}
|
||||
}, [language])
|
||||
}, [language, autoCompleteObject])
|
||||
|
||||
const handleOnChange = (value) => {
|
||||
if (typeof code == 'object' && language == 'json') {
|
||||
@ -178,7 +179,7 @@ export default function CodeBlockEditor({
|
||||
}
|
||||
|
||||
CodeBlockEditor.propTypes = {
|
||||
code: PropTypes.string.isRequired,
|
||||
code: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||
language: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.arrayOf(PropTypes.string)
|
||||
@ -190,5 +191,6 @@ CodeBlockEditor.propTypes = {
|
||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
showLineNumbers: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
minimal: PropTypes.bool
|
||||
minimal: PropTypes.bool,
|
||||
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string])
|
||||
}
|
||||
|
||||
@ -111,6 +111,7 @@ const ObjectProperty = ({
|
||||
initial = false,
|
||||
height = 'auto',
|
||||
minimal = false,
|
||||
autoCompleteObject = null,
|
||||
previewOpen = false,
|
||||
showPreview = true,
|
||||
useFormItem = true,
|
||||
@ -399,6 +400,7 @@ const ObjectProperty = ({
|
||||
height={height}
|
||||
readOnly={true}
|
||||
minimal={minimal}
|
||||
autoCompleteObject={autoCompleteObject}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@ -906,6 +908,7 @@ const ObjectProperty = ({
|
||||
language={language}
|
||||
height={height}
|
||||
minimal={minimal}
|
||||
autoCompleteObject={autoCompleteObject}
|
||||
{...inputProps}
|
||||
/>
|
||||
)
|
||||
@ -1037,6 +1040,7 @@ ObjectProperty.propTypes = {
|
||||
required: PropTypes.bool,
|
||||
name: PropTypes.string,
|
||||
language: PropTypes.string,
|
||||
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
min: PropTypes.number,
|
||||
|
||||
@ -92,6 +92,7 @@ const TemplateEditor = ({
|
||||
language={'fcTemplateLang'}
|
||||
objectData={objectData}
|
||||
isEditing={isEditing}
|
||||
autoCompleteObject={objectData?.testObject}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user