diff --git a/core-web/libs/edit-content/src/lib/custom-languages/html-monaco-language-base.ts b/core-web/libs/edit-content/src/lib/custom-languages/html-monaco-language-base.ts new file mode 100644 index 000000000000..caa7baa3ab89 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/custom-languages/html-monaco-language-base.ts @@ -0,0 +1,150 @@ +export const HTML_BASE_TOKENIZER_ROOT = [ + [/)/, ['delimiter', 'tag', '', 'delimiter']], + [/(<)(script)/, ['delimiter', { token: 'tag', next: '@script' }]], + [/(<)(style)/, ['delimiter', { token: 'tag', next: '@style' }]], + [/(<)((?:[\w-]+:)?[\w-]+)/, ['delimiter', { token: 'tag', next: '@otherTag' }]], + [/(<\/)((?:[\w-]+:)?[\w-]+)/, ['delimiter', { token: 'tag', next: '@otherTag' }]], + [/]+/, 'metatag.content'], + [/>/, 'metatag', '@pop'] + ], + + comment: [ + [/-->/, 'comment', '@pop'], + [/[^-]+/, 'comment.content'], + [/./, 'comment.content'] + ], + + otherTag: [ + [/\/?>/, 'delimiter', '@pop'], + [/"([^"]*)"/, 'attribute.value'], + [/'([^']*)'/, 'attribute.value'], + [/[\w-]+/, 'attribute.name'], + [/=/, 'delimiter'], + [/[ \t\r\n]+/, ''] // whitespace + ], + + script: [ + [/type/, 'attribute.name', '@scriptAfterType'], + [/"([^"]*)"/, 'attribute.value'], + [/'([^']*)'/, 'attribute.value'], + [/[\w-]+/, 'attribute.name'], + [/=/, 'delimiter'], + [ + />/, + { + token: 'delimiter', + next: '@scriptEmbedded', + nextEmbedded: 'text/javascript' + } + ], + [/[ \t\r\n]+/], // whitespace + [/(<\/)(script\s*)(>)/, ['delimiter', 'tag', { token: 'delimiter', next: '@pop' }]] + ], + + scriptAfterType: [ + [/=/, 'delimiter', '@scriptAfterTypeEquals'], + [ + />/, + { + token: 'delimiter', + next: '@scriptEmbedded', + nextEmbedded: 'text/javascript' + } + ], + [/[ \t\r\n]+/], // whitespace + [/<\/script\s*>/, { token: '@rematch', next: '@pop' }] + ], + + scriptAfterTypeEquals: [ + [/"([^"]*)"/, { token: 'attribute.value', switchTo: '@scriptWithCustomType.$1' }], + [/'([^']*)'/, { token: 'attribute.value', switchTo: '@scriptWithCustomType.$1' }], + [ + />/, + { + token: 'delimiter', + next: '@scriptEmbedded', + nextEmbedded: 'text/javascript' + } + ], + [/[ \t\r\n]+/], // whitespace + [/<\/script\s*>/, { token: '@rematch', next: '@pop' }] + ], + + scriptWithCustomType: [ + [/>/, { token: 'delimiter', next: '@scriptEmbedded.$S2', nextEmbedded: '$S2' }], + [/"([^"]*)"/, 'attribute.value'], + [/'([^']*)'/, 'attribute.value'], + [/[\w-]+/, 'attribute.name'], + [/=/, 'delimiter'], + [/[ \t\r\n]+/], // whitespace + [/<\/script\s*>/, { token: '@rematch', next: '@pop' }] + ], + + scriptEmbedded: [ + [/<\/script/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop' }], + [/[^<]+/, ''] + ], + + style: [ + [/type/, 'attribute.name', '@styleAfterType'], + [/"([^"]*)"/, 'attribute.value'], + [/'([^']*)'/, 'attribute.value'], + [/[\w-]+/, 'attribute.name'], + [/=/, 'delimiter'], + [/>/, { token: 'delimiter', next: '@styleEmbedded', nextEmbedded: 'text/css' }], + [/[ \t\r\n]+/], // whitespace + [/(<\/)(style\s*)(>)/, ['delimiter', 'tag', { token: 'delimiter', next: '@pop' }]] + ], + + styleAfterType: [ + [/=/, 'delimiter', '@styleAfterTypeEquals'], + [/>/, { token: 'delimiter', next: '@styleEmbedded', nextEmbedded: 'text/css' }], + [/[ \t\r\n]+/], // whitespace + [/<\/style\s*>/, { token: '@rematch', next: '@pop' }] + ], + + styleAfterTypeEquals: [ + [/"([^"]*)"/, { token: 'attribute.value', switchTo: '@styleWithCustomType.$1' }], + [/'([^']*)'/, { token: 'attribute.value', switchTo: '@styleWithCustomType.$1' }], + [ + />/, + { + token: 'delimiter', + next: '@styleEmbedded', + nextEmbedded: 'text/css' + } + ], + [/[ \t\r\n]+/], // whitespace + [/<\/style\s*>/, { token: '@rematch', next: '@pop' }] + ], + + styleWithCustomType: [ + [ + />/, + { + token: 'delimiter', + next: '@styleEmbedded.$S2', + nextEmbedded: '$S2' + } + ], + [/"([^"]*)"/, 'attribute.value'], + [/'([^']*)'/, 'attribute.value'], + [/[\w-]+/, 'attribute.name'], + [/=/, 'delimiter'], + [/[ \t\r\n]+/], // whitespace + [/<\/style\s*>/, { token: '@rematch', next: '@pop' }] + ], + + styleEmbedded: [ + [/<\/style/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop' }], + [/[^<]+/, ''] + ] +}; diff --git a/core-web/libs/edit-content/src/lib/custom-languages/velocity-monaco-language.ts b/core-web/libs/edit-content/src/lib/custom-languages/velocity-monaco-language.ts new file mode 100644 index 000000000000..29a69863f813 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/custom-languages/velocity-monaco-language.ts @@ -0,0 +1,65 @@ +import { HTML_BASE_TOKENIZER_ROOT, HTML_BASE_TOKENIZER_STATES } from './html-monaco-language-base'; + +const VELOCITY_TOKENS = [ + // Velocity directives with markers + [/^\*#.*?#$\*$/, 'code.velocity'], // Marked directives *#...#* + + // Velocity comments + [/#\*[\s\S]*?\*#/, 'comment.velocity'], // Block comments + [/^\s*##.*/, 'comment.velocity'], // Line comments with optional leading whitespace + + // Velocity directives + [/#(foreach|if|else|elseif|end|set|parse|include|macro|stop)\b/, 'keyword.velocity'], + [/#(dotParse)\b/, 'keyword.dotparse.velocity'], + + // Velocity Variables + [/\$!?\{[^}]+}/, 'variable.velocity'], + [/\$!?[a-zA-Z_][a-zA-Z0-9_]*/, 'variable.velocity'] +] as monaco.languages.IMonarchLanguageRule[]; + +const VELOCITY_STATES = { + velocityVariable: [ + [/\}/, 'variable.velocity.delimiter', '@pop'], + [/\(/, 'delimiter.parenthesis', '@velocityMethod'], + [/[^}()]/, 'variable.velocity'] + ] as monaco.languages.IMonarchLanguageRule[], + + velocityMethod: [ + [/\)/, 'delimiter.parenthesis', '@pop'], + [/\(/, 'delimiter.parenthesis', '@push'], + [/[^()]/, 'variable.velocity'] + ] as monaco.languages.IMonarchLanguageRule[] +}; + +export const dotVelocityLanguageDefinition: monaco.languages.IMonarchLanguage = { + defaultToken: '', + tokenPostfix: '.vtl', + ignoreCase: true, + + brackets: [ + { open: '{', close: '}', token: 'delimiter.curly' }, + { open: '[', close: ']', token: 'delimiter.square' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, + { open: '<', close: '>', token: 'delimiter.angle' } + ], + + keywords: [ + 'foreach', + 'if', + 'else', + 'elseif', + 'end', + 'set', + 'parse', + 'include', + 'macro', + 'stop', + 'dotParse' + ], + + tokenizer: { + root: [...VELOCITY_TOKENS, ...HTML_BASE_TOKENIZER_ROOT], + ...HTML_BASE_TOKENIZER_STATES, + ...VELOCITY_STATES + } as monaco.languages.IMonarchLanguage['tokenizer'] +}; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/dot-binary-field-editor.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/dot-binary-field-editor.component.spec.ts index e32dfde93779..52ecb023ab22 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/dot-binary-field-editor.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/dot-binary-field-editor.component.spec.ts @@ -167,7 +167,7 @@ describe('DotBinaryFieldEditorComponent', () => { it('should force html language on vtl files', fakeAsync(() => { const expectedMonacoOptions = { ...DEFAULT_BINARY_FIELD_MONACO_CONFIG, - language: 'html' + language: 'velocity' }; spectator.detectChanges(); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/dot-binary-field-editor.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/dot-binary-field-editor.component.ts index ad83b373a4c7..24ac2bda5b5d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/dot-binary-field-editor.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/dot-binary-field-editor.component.ts @@ -37,6 +37,7 @@ import { debounceTime } from 'rxjs/operators'; import { DotMessageService, DotUploadService } from '@dotcms/data-access'; import { DotCMSTempFile } from '@dotcms/dotcms-models'; import { DEFAULT_BINARY_FIELD_MONACO_CONFIG } from '@dotcms/edit-content'; +import { dotVelocityLanguageDefinition } from '@dotcms/edit-content/custom-languages/velocity-monaco-language'; import { DotFieldValidationMessageComponent, DotMessagePipe } from '@dotcms/ui'; import { DotBinaryFieldValidatorService } from '../../service/dot-binary-field-validator/dot-binary-field-validator.service'; @@ -142,6 +143,14 @@ export class DotBinaryFieldEditorComponent implements OnInit, OnChanges { if (this.fileName) { this.setEditorLanguage(this.fileName); } + + window.monaco.languages.register({ + id: 'velocity', + extensions: ['.vtl'], + mimetypes: ['text/x-velocity'] + }); + + window.monaco.languages.setMonarchTokensProvider('velocity', dotVelocityLanguageDefinition); } onSubmit(): void { @@ -200,7 +209,7 @@ export class DotBinaryFieldEditorComponent implements OnInit, OnChanges { private setVelocityLanguage() { this.mimeType = 'text/x-velocity'; this.extension = 'vtl'; - this.updateEditorLanguage('html'); //Force html highlighting for .vtl files + this.updateEditorLanguage('velocity'); } private updateLanguageForFileExtension(fileExtension: string) { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/components/dot-wysiwyg-monaco/custom-languages/velocity-monaco-language.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/components/dot-wysiwyg-monaco/custom-languages/velocity-monaco-language.ts deleted file mode 100644 index 98471448fcba..000000000000 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/components/dot-wysiwyg-monaco/custom-languages/velocity-monaco-language.ts +++ /dev/null @@ -1,136 +0,0 @@ -// Velocity language definition for Monaco Editor -export const dotVelocityLanguageDefinition: monaco.languages.IMonarchLanguage = { - defaultToken: '', - tokenPostfix: '.vtl', - ignoreCase: true, - - brackets: [ - { open: '{', close: '}', token: 'delimiter.curly' }, - { open: '[', close: ']', token: 'delimiter.square' }, - { open: '(', close: ')', token: 'delimiter.parenthesis' }, - { open: '<', close: '>', token: 'delimiter.angle' } - ], - - keywords: [ - 'foreach', - 'if', - 'else', - 'elseif', - 'end', - 'set', - 'parse', - 'include', - 'macro', - 'stop', - 'dotParse' - ], - - tokenizer: { - root: [ - // HTML Comments - [//, 'comment.html', '@pop'], - [/-/, 'comment.html'] - ], - - htmlAttributeValue: [ - [/[^"]+/, 'string.html'], - [ - /(\$!?\{?)([a-zA-Z][\w-]*(?:\.[a-zA-Z][\w-]*)*(?:\([^)]*\))?)(\})?/, - ['variable.velocity', 'variable.velocity', 'variable.velocity'] - ], - [/\$[a-zA-Z][\w-]*/, 'variable.velocity'], - [/"/, { token: 'string.html', next: '@pop' }] - ], - - string_double: [ - [/[^\\"$]+/, 'string.velocity'], - [/\\./, 'string.escape.velocity'], - [ - /(\$!?\{?)([a-zA-Z][\w-]*(?:\.[a-zA-Z][\w-]*)*(?:\([^)]*\))?)(\})?/, - ['variable.velocity', 'variable.velocity', 'variable.velocity'] - ], - [/\$[a-zA-Z][\w-]*/, 'variable.velocity'], - [/"/, 'string.velocity', '@pop'] - ], - - string_single: [ - [/[^\\'$]+/, 'string.velocity'], - [/\\./, 'string.escape.velocity'], - [ - /(\$!?\{?)([a-zA-Z][\w-]*(?:\.[a-zA-Z][\w-]*)*(?:\([^)]*\))?)(\})?/, - ['variable.velocity', 'variable.velocity', 'variable.velocity'] - ], - [/\$[a-zA-Z][\w-]*/, 'variable.velocity'], - [/'/, 'string.velocity', '@pop'] - ], - - velocityComment: [ - [/[^*#]+/, 'comment.velocity'], - [/#\*/, 'comment.velocity', '@push'], - [/\*#/, 'comment.velocity', '@pop'], - [/[*#]/, 'comment.velocity'] - ], - - velocityVariable: [ - [/\}/, 'variable.velocity.delimiter', '@pop'], - [/\(/, 'delimiter.parenthesis', '@velocityMethod'], - [/[^}()]/, 'variable.velocity'] - ], - - velocityMethod: [ - [/\)/, 'delimiter.parenthesis', '@pop'], - [/\(/, 'delimiter.parenthesis', '@push'], - [/[^()]/, 'variable.velocity'] - ] - } -}; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/components/dot-wysiwyg-monaco/dot-wysiwyg-monaco.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/components/dot-wysiwyg-monaco/dot-wysiwyg-monaco.component.ts index 89b2a9d02d54..ac4124076141 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/components/dot-wysiwyg-monaco/dot-wysiwyg-monaco.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/components/dot-wysiwyg-monaco/dot-wysiwyg-monaco.component.ts @@ -23,8 +23,7 @@ import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; import { PaginatorModule } from 'primeng/paginator'; import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; - -import { dotVelocityLanguageDefinition } from './custom-languages/velocity-monaco-language'; +import { dotVelocityLanguageDefinition } from '@dotcms/edit-content/custom-languages/velocity-monaco-language'; import { getFieldVariablesParsed, stringToJson } from '../../../../utils/functions.util'; import { diff --git a/core-web/libs/edit-content/tsconfig.json b/core-web/libs/edit-content/tsconfig.json index 2c3af383b4f6..2f4cd8046ab2 100644 --- a/core-web/libs/edit-content/tsconfig.json +++ b/core-web/libs/edit-content/tsconfig.json @@ -24,5 +24,8 @@ "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true + }, + "paths": { + "@dotcms/edit-content/custom-languages/*": ["./src/lib/custom-languages/*"] } }