From c8393db30fc66ca1c8ad7e493cc3744b156abddb Mon Sep 17 00:00:00 2001 From: Jalinson Diaz Date: Wed, 12 Feb 2025 15:16:37 -0300 Subject: [PATCH 1/3] add support for velocity files --- .../dot-binary-field-editor.component.spec.ts | 2 +- .../dot-binary-field-editor.component.ts | 19 +- .../dot-binary-field-editor/languages.ts | 178 ++++++++++++++++++ 3 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/languages.ts 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..0a5e2d699724 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 @@ -39,6 +39,8 @@ import { DotCMSTempFile } from '@dotcms/dotcms-models'; import { DEFAULT_BINARY_FIELD_MONACO_CONFIG } from '@dotcms/edit-content'; import { DotFieldValidationMessageComponent, DotMessagePipe } from '@dotcms/ui'; +import { VELOCITY_TOKENIZER } from './languages'; + import { DotBinaryFieldValidatorService } from '../../service/dot-binary-field-validator/dot-binary-field-validator.service'; const DEFAULT_FILE_TYPE = 'text'; @@ -142,6 +144,21 @@ 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', { + defaultToken: '', + tokenPostfix: '.velocity', + ignoreCase: true, + tokenizer: { + ...VELOCITY_TOKENIZER + } + } as monaco.languages.IMonarchLanguage); } onSubmit(): void { @@ -200,7 +217,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-binary-field/components/dot-binary-field-editor/languages.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/languages.ts new file mode 100644 index 000000000000..6270470caaa9 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/languages.ts @@ -0,0 +1,178 @@ +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' }], + [/[^<]+/, ''] + ] +}; + +const VELOCITY_TOKENIZER_ROOT = [ + // ========== Velocity-specific tokens ========== + // Velocity directives + [ + /#(set|if|else|elseif|end|foreach|include|parse|stop|break|evaluate|macro|define)\b/, + 'keyword' + ], + + // Velocity variables + [/\$!?\{[^}]+}/, 'variable'], + [/\$!?[a-zA-Z_][a-zA-Z0-9_]*/, 'variable'], + + // ========== HTML tokenizer (from Monaco) ========== + // The rules below should match Monaco's HTML tokenizer + ...HTML_BASE_TOKENIZER_ROOT +]; + +export const VELOCITY_TOKENIZER = { + root: [ + // ========== Velocity-specific tokens ========== + ...VELOCITY_TOKENIZER_ROOT, + // ========== HTML tokenizer (from Monaco) ========== + ...HTML_BASE_TOKENIZER_ROOT + ], + // ========== HTML states (from Monaco) ========== + ...HTML_BASE_TOKENIZER_STATES +}; From 9829c02b915b3dd57bf6adf3463f763ec303458d Mon Sep 17 00:00:00 2001 From: Jalinson Diaz Date: Wed, 12 Feb 2025 16:13:26 -0300 Subject: [PATCH 2/3] merge old and new language to centralize functionality within wysiwyg and binary field --- .../html-monaco-language-base.ts} | 34 +--------- .../velocity-monaco-language.ts | 65 +++++++++++++++++++ .../dot-binary-field-editor.component.ts | 12 +--- .../dot-wysiwyg-monaco.component.ts | 3 +- core-web/libs/edit-content/tsconfig.json | 3 + 5 files changed, 74 insertions(+), 43 deletions(-) rename core-web/libs/edit-content/src/lib/{fields/dot-edit-content-binary-field/components/dot-binary-field-editor/languages.ts => custom-languages/html-monaco-language-base.ts} (83%) create mode 100644 core-web/libs/edit-content/src/lib/custom-languages/velocity-monaco-language.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/languages.ts b/core-web/libs/edit-content/src/lib/custom-languages/html-monaco-language-base.ts similarity index 83% rename from core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/languages.ts rename to core-web/libs/edit-content/src/lib/custom-languages/html-monaco-language-base.ts index 6270470caaa9..caa7baa3ab89 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/components/dot-binary-field-editor/languages.ts +++ b/core-web/libs/edit-content/src/lib/custom-languages/html-monaco-language-base.ts @@ -1,4 +1,4 @@ -const HTML_BASE_TOKENIZER_ROOT = [ +export const HTML_BASE_TOKENIZER_ROOT = [ [/)/, ['delimiter', 'tag', '', 'delimiter']], @@ -8,9 +8,9 @@ const HTML_BASE_TOKENIZER_ROOT = [ [/(<\/)((?:[\w-]+:)?[\w-]+)/, ['delimiter', { token: 'tag', next: '@otherTag' }]], [/]+/, 'metatag.content'], [/>/, 'metatag', '@pop'] @@ -148,31 +148,3 @@ const HTML_BASE_TOKENIZER_STATES = { [/[^<]+/, ''] ] }; - -const VELOCITY_TOKENIZER_ROOT = [ - // ========== Velocity-specific tokens ========== - // Velocity directives - [ - /#(set|if|else|elseif|end|foreach|include|parse|stop|break|evaluate|macro|define)\b/, - 'keyword' - ], - - // Velocity variables - [/\$!?\{[^}]+}/, 'variable'], - [/\$!?[a-zA-Z_][a-zA-Z0-9_]*/, 'variable'], - - // ========== HTML tokenizer (from Monaco) ========== - // The rules below should match Monaco's HTML tokenizer - ...HTML_BASE_TOKENIZER_ROOT -]; - -export const VELOCITY_TOKENIZER = { - root: [ - // ========== Velocity-specific tokens ========== - ...VELOCITY_TOKENIZER_ROOT, - // ========== HTML tokenizer (from Monaco) ========== - ...HTML_BASE_TOKENIZER_ROOT - ], - // ========== HTML states (from Monaco) ========== - ...HTML_BASE_TOKENIZER_STATES -}; 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.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 0a5e2d699724..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,10 +37,9 @@ 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 { VELOCITY_TOKENIZER } from './languages'; - import { DotBinaryFieldValidatorService } from '../../service/dot-binary-field-validator/dot-binary-field-validator.service'; const DEFAULT_FILE_TYPE = 'text'; @@ -151,14 +150,7 @@ export class DotBinaryFieldEditorComponent implements OnInit, OnChanges { mimetypes: ['text/x-velocity'] }); - window.monaco.languages.setMonarchTokensProvider('velocity', { - defaultToken: '', - tokenPostfix: '.velocity', - ignoreCase: true, - tokenizer: { - ...VELOCITY_TOKENIZER - } - } as monaco.languages.IMonarchLanguage); + window.monaco.languages.setMonarchTokensProvider('velocity', dotVelocityLanguageDefinition); } onSubmit(): void { 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/*"] } } From b707d5756a6648146be7937e83f5e3c51fbf1739 Mon Sep 17 00:00:00 2001 From: Jalinson Diaz Date: Wed, 12 Feb 2025 16:15:13 -0300 Subject: [PATCH 3/3] delete old language file --- .../velocity-monaco-language.ts | 136 ------------------ 1 file changed, 136 deletions(-) delete mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/components/dot-wysiwyg-monaco/custom-languages/velocity-monaco-language.ts 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'] - ] - } -};