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'
|
} 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'
|
||||||
import { javascriptLanguage } from '@codemirror/lang-javascript'
|
import {
|
||||||
import { xmlLanguage } from '@codemirror/lang-xml'
|
javascriptLanguage,
|
||||||
|
scopeCompletionSource,
|
||||||
|
localCompletionSource
|
||||||
|
} from '@codemirror/lang-javascript'
|
||||||
|
import {
|
||||||
|
xmlLanguage,
|
||||||
|
completeFromSchema,
|
||||||
|
autoCloseTags
|
||||||
|
} from '@codemirror/lang-xml'
|
||||||
import { parser as ejsParser } from './ejs.parser.js'
|
import { parser as ejsParser } from './ejs.parser.js'
|
||||||
|
import {
|
||||||
|
fcTemplateElements,
|
||||||
|
fcTemplateAttributes,
|
||||||
|
fcTemplateHelpers
|
||||||
|
} from './schema.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FarmControl document template language: custom XML tags with EJS
|
* 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.
|
* XML and JavaScript each get their own highlighting/support.
|
||||||
*
|
*
|
||||||
* @see https://codemirror.net/examples/mixed-language/
|
* @see https://codemirror.net/examples/mixed-language/
|
||||||
|
* @see https://codemirror.net/examples/autocompletion/
|
||||||
*/
|
*/
|
||||||
const fcTemplateParser = ejsParser.configure({
|
const fcTemplateParser = ejsParser.configure({
|
||||||
props: [
|
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.
|
* Language support for FarmControl EJS+XML document templates.
|
||||||
|
* @param {{ autoCompleteObject?: object|string }} [options]
|
||||||
*/
|
*/
|
||||||
export function fcTemplateLang() {
|
export function fcTemplateLang(options = {}) {
|
||||||
return new LanguageSupport(fcTemplateLanguage)
|
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',
|
height = 'auto',
|
||||||
showLineNumbers = true,
|
showLineNumbers = true,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
minimal = false
|
minimal = false,
|
||||||
|
autoCompleteObject = null
|
||||||
}) {
|
}) {
|
||||||
const { isDarkMode } = useThemeContext()
|
const { isDarkMode } = useThemeContext()
|
||||||
const [codeMirrorOpen, setCodeMirrorOpen] = useState(false)
|
const [codeMirrorOpen, setCodeMirrorOpen] = useState(false)
|
||||||
@ -57,7 +58,7 @@ export default function CodeBlockEditor({
|
|||||||
return xml()
|
return xml()
|
||||||
case 'fctemplatelang':
|
case 'fctemplatelang':
|
||||||
case 'ejs':
|
case 'ejs':
|
||||||
return fcTemplateLang()
|
return fcTemplateLang({ autoCompleteObject })
|
||||||
case 'html':
|
case 'html':
|
||||||
return html()
|
return html()
|
||||||
case 'css':
|
case 'css':
|
||||||
@ -86,7 +87,7 @@ export default function CodeBlockEditor({
|
|||||||
default:
|
default:
|
||||||
return javascript() // Default fallback
|
return javascript() // Default fallback
|
||||||
}
|
}
|
||||||
}, [language])
|
}, [language, autoCompleteObject])
|
||||||
|
|
||||||
const handleOnChange = (value) => {
|
const handleOnChange = (value) => {
|
||||||
if (typeof code == 'object' && language == 'json') {
|
if (typeof code == 'object' && language == 'json') {
|
||||||
@ -178,7 +179,7 @@ export default function CodeBlockEditor({
|
|||||||
}
|
}
|
||||||
|
|
||||||
CodeBlockEditor.propTypes = {
|
CodeBlockEditor.propTypes = {
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||||
language: PropTypes.oneOfType([
|
language: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.arrayOf(PropTypes.string)
|
PropTypes.arrayOf(PropTypes.string)
|
||||||
@ -190,5 +191,6 @@ CodeBlockEditor.propTypes = {
|
|||||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
showLineNumbers: PropTypes.bool,
|
showLineNumbers: PropTypes.bool,
|
||||||
disabled: 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,
|
initial = false,
|
||||||
height = 'auto',
|
height = 'auto',
|
||||||
minimal = false,
|
minimal = false,
|
||||||
|
autoCompleteObject = null,
|
||||||
previewOpen = false,
|
previewOpen = false,
|
||||||
showPreview = true,
|
showPreview = true,
|
||||||
useFormItem = true,
|
useFormItem = true,
|
||||||
@ -399,6 +400,7 @@ const ObjectProperty = ({
|
|||||||
height={height}
|
height={height}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
minimal={minimal}
|
minimal={minimal}
|
||||||
|
autoCompleteObject={autoCompleteObject}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -906,6 +908,7 @@ const ObjectProperty = ({
|
|||||||
language={language}
|
language={language}
|
||||||
height={height}
|
height={height}
|
||||||
minimal={minimal}
|
minimal={minimal}
|
||||||
|
autoCompleteObject={autoCompleteObject}
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -1037,6 +1040,7 @@ ObjectProperty.propTypes = {
|
|||||||
required: PropTypes.bool,
|
required: PropTypes.bool,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
language: PropTypes.string,
|
language: PropTypes.string,
|
||||||
|
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||||
prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
min: PropTypes.number,
|
min: PropTypes.number,
|
||||||
|
|||||||
@ -92,6 +92,7 @@ const TemplateEditor = ({
|
|||||||
language={'fcTemplateLang'}
|
language={'fcTemplateLang'}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
|
autoCompleteObject={objectData?.testObject}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user