From d98c1242dc46c15fd6f129333f38a7ad35985df7 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Fri, 19 Oct 2018 17:36:38 +0100 Subject: [PATCH] ui: Adds multi syntax linting to the code editor (#4814) --- ui-v2/app/components/code-editor.js | 55 +++++- ui-v2/app/index.html | 15 +- ui-v2/app/initializers/ivy-codemirror.js | 35 ++++ ui-v2/app/styles/components/code-editor.scss | 5 +- .../styles/components/code-editor/index.scss | 186 +----------------- .../styles/components/code-editor/layout.scss | 31 +++ .../styles/components/code-editor/skin.scss | 184 +++++++++++++++++ .../app/templates/components/code-editor.hbs | 13 +- .../templates/dc/acls/policies/-fieldsets.hbs | 2 +- ui-v2/app/utils/editor/lint.js | 38 ++++ ui-v2/ember-cli-build.js | 27 ++- ui-v2/package.json | 4 +- ui-v2/yarn.lock | 42 ++++ 13 files changed, 439 insertions(+), 198 deletions(-) create mode 100644 ui-v2/app/styles/components/code-editor/layout.scss create mode 100644 ui-v2/app/styles/components/code-editor/skin.scss create mode 100644 ui-v2/app/utils/editor/lint.js diff --git a/ui-v2/app/components/code-editor.js b/ui-v2/app/components/code-editor.js index a8f55602329a..524fd597bd38 100644 --- a/ui-v2/app/components/code-editor.js +++ b/ui-v2/app/components/code-editor.js @@ -1,12 +1,57 @@ import Component from '@ember/component'; -import qsaFactory from 'consul-ui/utils/dom/qsa-factory'; -const $$ = qsaFactory(); +import { get, set } from '@ember/object'; +import { inject as service } from '@ember/service'; +const DEFAULTS = { + tabSize: 2, + lineNumbers: true, + theme: 'hashi', + showCursorWhenSelecting: true, + gutters: ['CodeMirror-lint-markers'], + lint: true, +}; export default Component.extend({ - mode: 'application/json', + settings: service('settings'), + helper: service('code-mirror'), classNames: ['code-editor'], + syntax: '', + onchange: function(value) { + get(this, 'settings').persist({ + 'code-editor': value, + }); + this.setMode(value); + }, onkeyup: function() {}, + init: function() { + this._super(...arguments); + set(this, 'modes', get(this, 'helper').modes()); + }, + setMode: function(mode) { + set(this, 'options', { + ...DEFAULTS, + mode: mode.mime, + }); + const editor = get(this, 'editor'); + editor.setOption('mode', mode.mime); + get(this, 'helper').lint(editor, mode.mode); + set(this, 'mode', mode); + }, + didInsertElement: function() { + set(this, 'editor', get(this, 'helper').getEditor(this.element)); + get(this, 'settings') + .findBySlug('code-editor') + .then(mode => { + const modes = get(this, 'modes'); + const syntax = get(this, 'syntax'); + if (syntax) { + mode = modes.find(function(item) { + return item.name.toLowerCase() == syntax.toLowerCase(); + }); + } + mode = !mode ? modes[0] : mode; + this.setMode(mode); + }); + }, didAppear: function() { - const $editor = [...$$('textarea + div', this.element)][0]; - $editor.CodeMirror.refresh(); + get(this, 'editor').refresh(); }, }); diff --git a/ui-v2/app/index.html b/ui-v2/app/index.html index f88cc23e4c28..eb3753a26645 100644 --- a/ui-v2/app/index.html +++ b/ui-v2/app/index.html @@ -37,7 +37,20 @@

JavaScript Required

} - + {{content-for "body-footer"}} diff --git a/ui-v2/app/initializers/ivy-codemirror.js b/ui-v2/app/initializers/ivy-codemirror.js index c4f343605736..16f12963ec2d 100644 --- a/ui-v2/app/initializers/ivy-codemirror.js +++ b/ui-v2/app/initializers/ivy-codemirror.js @@ -1,9 +1,44 @@ +import { inject as service } from '@ember/service'; +import { get } from '@ember/object'; +import lint from 'consul-ui/utils/editor/lint'; +const MODES = [ + { + name: 'JSON', + mime: 'application/json', + mode: 'javascript', + ext: ['json', 'map'], + alias: ['json5'], + }, + { + name: 'HCL', + mime: 'text/x-ruby', + mode: 'ruby', + ext: ['rb'], + alias: ['jruby', 'macruby', 'rake', 'rb', 'rbx'], + }, + { name: 'YAML', mime: 'text/x-yaml', mode: 'yaml', ext: ['yaml', 'yml'], alias: ['yml'] }, +]; export function initialize(application) { const IvyCodeMirrorComponent = application.resolveRegistration('component:ivy-codemirror'); + const IvyCodeMirrorService = application.resolveRegistration('service:code-mirror'); // Make sure ivy-codemirror respects/maintains a `name=""` attribute IvyCodeMirrorComponent.reopen({ attributeBindings: ['name'], }); + // Add some method to the code-mirror service so I don't have to have 2 services + // for dealing with codemirror + IvyCodeMirrorService.reopen({ + dom: service('dom'), + modes: function() { + return MODES; + }, + lint: function() { + return lint(...arguments); + }, + getEditor: function(element) { + return get(this, 'dom').element('textarea + div', element).CodeMirror; + }, + }); } export default { diff --git a/ui-v2/app/styles/components/code-editor.scss b/ui-v2/app/styles/components/code-editor.scss index 966b044a5eee..3ad7d8fdcca8 100644 --- a/ui-v2/app/styles/components/code-editor.scss +++ b/ui-v2/app/styles/components/code-editor.scss @@ -1,2 +1,5 @@ @import './code-editor/index'; -// yet to make into placeholder +// yet to pull the CodeMirror skin into the placeholder +.code-editor { + @extend %code-editor; +} diff --git a/ui-v2/app/styles/components/code-editor/index.scss b/ui-v2/app/styles/components/code-editor/index.scss index e5d1c6395a00..bc182521964a 100644 --- a/ui-v2/app/styles/components/code-editor/index.scss +++ b/ui-v2/app/styles/components/code-editor/index.scss @@ -1,184 +1,2 @@ -$syntax-light-grey: #dde3e7; -$syntax-light-gray: #a4a4a4; -$syntax-light-grey-blue: #6c7b81; -$syntax-dark-grey: #788290; -$syntax-faded-gray: #eaeaea; -// Product colors -$syntax-atlas: #127eff; -$syntax-vagrant: #2f88f7; -$syntax-consul: #69499a; -$syntax-terraform: #822ff7; -$syntax-serf: #dd4e58; -$syntax-packer: #1ddba3; - -// Our colors -$syntax-gray: lighten($ui-black, 89%); -$syntax-red: #ff3d3d; -$syntax-green: #39b54a; -$syntax-dark-gray: #535f73; - -$syntax-gutter-grey: #2a2f36; -$syntax-yellow: $ui-yellow-500; -.CodeMirror { - max-width: 1150px; - min-height: 300px; - height: auto; - /* adds some space at the bottom of the editor for when a horizonal-scroll has appeared */ - padding-bottom: 20px; -} -.CodeMirror-scroll { - overflow-x: hidden !important; -} -.CodeMirror-lint-tooltip { - background-color: #f9f9fa; - border: 1px solid $syntax-light-gray; - border-radius: 0; - color: lighten($ui-black, 13%); - font-family: $typo-family-mono; - font-size: 13px; - padding: 7px 8px 9px; -} - -.cm-s-hashi { - &.CodeMirror { - width: 100%; - background-color: $ui-black !important; - color: #cfd2d1 !important; - border: none; - font-family: $typo-family-mono; - -webkit-font-smoothing: auto; - line-height: 1.4; - } - - .CodeMirror-gutters { - color: $syntax-dark-grey; - background-color: $syntax-gutter-grey; - border: none; - } - - .CodeMirror-cursor { - border-left: solid thin #f8f8f0; - } - - .CodeMirror-linenumber { - color: #6d8a88; - } - - &.CodeMirror-focused div.CodeMirror-selected { - background: rgb(33, 66, 131); - } - - .CodeMirror-line::selection, - .CodeMirror-line > span::selection, - .CodeMirror-line > span > span::selection { - background: rgb(33, 66, 131); - } - - .CodeMirror-line::-moz-selection, - .CodeMirror-line > span::-moz-selection, - .CodeMirror-line > span > span::-moz-selection { - background: rgba(255, 255, 255, 0.1); - } - - span.cm-comment { - color: $syntax-light-grey; - } - - span.cm-string, - span.cm-string-2 { - color: $syntax-packer; - } - - span.cm-number { - color: $syntax-serf; - } - - span.cm-variable { - color: lighten($syntax-consul, 20%); - } - - span.cm-variable-2 { - color: lighten($syntax-consul, 20%); - } - - span.cm-def { - color: $syntax-packer; - } - - span.cm-operator { - color: $syntax-gray; - } - span.cm-keyword { - color: $syntax-yellow; - } - - span.cm-atom { - color: $syntax-serf; - } - - span.cm-meta { - color: $syntax-packer; - } - - span.cm-tag { - color: $syntax-packer; - } - - span.cm-attribute { - color: #9fca56; - } - - span.cm-qualifier { - color: #9fca56; - } - - span.cm-property { - color: lighten($syntax-consul, 20%); - } - - span.cm-variable-3 { - color: #9fca56; - } - - span.cm-builtin { - color: #9fca56; - } - - .CodeMirror-activeline-background { - background: #101213; - } - - .CodeMirror-matchingbracket { - text-decoration: underline; - color: $ui-white !important; - } -} - -.readonly-codemirror { - .CodeMirror-cursors { - display: none; - } - - .cm-s-hashi { - span { - color: $syntax-light-grey; - } - - span.cm-string, - span.cm-string-2 { - color: $syntax-faded-gray; - } - - span.cm-number { - color: lighten($syntax-dark-gray, 30%); - } - - span.cm-property { - color: $ui-white; - } - - span.cm-variable-2 { - color: $syntax-light-grey-blue; - } - } -} +@import './skin'; +@import './layout'; diff --git a/ui-v2/app/styles/components/code-editor/layout.scss b/ui-v2/app/styles/components/code-editor/layout.scss new file mode 100644 index 000000000000..1410a6534466 --- /dev/null +++ b/ui-v2/app/styles/components/code-editor/layout.scss @@ -0,0 +1,31 @@ +%code-editor { + display: block; + border: 10px; + overflow: hidden; + position: relative; +} +%code-editor .ember-power-select-trigger { + @extend %code-editor-syntax-select; +} +%code-editor-syntax-select { + width: 200px; + float: right; + z-index: 1; +} +%code-editor-syntax-select { + margin-top: 1px; + border: 0; + background-color: $ui-black; + color: $ui-white; + border-left: 1px solid; + border-radius: 0; +} +%code-editor::after { + position: absolute; + bottom: 0px; + width: 100%; + height: 25px; + background-color: black; + content: ''; + display: block; +} diff --git a/ui-v2/app/styles/components/code-editor/skin.scss b/ui-v2/app/styles/components/code-editor/skin.scss new file mode 100644 index 000000000000..e5d1c6395a00 --- /dev/null +++ b/ui-v2/app/styles/components/code-editor/skin.scss @@ -0,0 +1,184 @@ +$syntax-light-grey: #dde3e7; +$syntax-light-gray: #a4a4a4; +$syntax-light-grey-blue: #6c7b81; +$syntax-dark-grey: #788290; +$syntax-faded-gray: #eaeaea; +// Product colors +$syntax-atlas: #127eff; +$syntax-vagrant: #2f88f7; +$syntax-consul: #69499a; +$syntax-terraform: #822ff7; +$syntax-serf: #dd4e58; +$syntax-packer: #1ddba3; + +// Our colors +$syntax-gray: lighten($ui-black, 89%); +$syntax-red: #ff3d3d; +$syntax-green: #39b54a; +$syntax-dark-gray: #535f73; + +$syntax-gutter-grey: #2a2f36; +$syntax-yellow: $ui-yellow-500; +.CodeMirror { + max-width: 1150px; + min-height: 300px; + height: auto; + /* adds some space at the bottom of the editor for when a horizonal-scroll has appeared */ + padding-bottom: 20px; +} +.CodeMirror-scroll { + overflow-x: hidden !important; +} +.CodeMirror-lint-tooltip { + background-color: #f9f9fa; + border: 1px solid $syntax-light-gray; + border-radius: 0; + color: lighten($ui-black, 13%); + font-family: $typo-family-mono; + font-size: 13px; + padding: 7px 8px 9px; +} + +.cm-s-hashi { + &.CodeMirror { + width: 100%; + background-color: $ui-black !important; + color: #cfd2d1 !important; + border: none; + font-family: $typo-family-mono; + -webkit-font-smoothing: auto; + line-height: 1.4; + } + + .CodeMirror-gutters { + color: $syntax-dark-grey; + background-color: $syntax-gutter-grey; + border: none; + } + + .CodeMirror-cursor { + border-left: solid thin #f8f8f0; + } + + .CodeMirror-linenumber { + color: #6d8a88; + } + + &.CodeMirror-focused div.CodeMirror-selected { + background: rgb(33, 66, 131); + } + + .CodeMirror-line::selection, + .CodeMirror-line > span::selection, + .CodeMirror-line > span > span::selection { + background: rgb(33, 66, 131); + } + + .CodeMirror-line::-moz-selection, + .CodeMirror-line > span::-moz-selection, + .CodeMirror-line > span > span::-moz-selection { + background: rgba(255, 255, 255, 0.1); + } + + span.cm-comment { + color: $syntax-light-grey; + } + + span.cm-string, + span.cm-string-2 { + color: $syntax-packer; + } + + span.cm-number { + color: $syntax-serf; + } + + span.cm-variable { + color: lighten($syntax-consul, 20%); + } + + span.cm-variable-2 { + color: lighten($syntax-consul, 20%); + } + + span.cm-def { + color: $syntax-packer; + } + + span.cm-operator { + color: $syntax-gray; + } + span.cm-keyword { + color: $syntax-yellow; + } + + span.cm-atom { + color: $syntax-serf; + } + + span.cm-meta { + color: $syntax-packer; + } + + span.cm-tag { + color: $syntax-packer; + } + + span.cm-attribute { + color: #9fca56; + } + + span.cm-qualifier { + color: #9fca56; + } + + span.cm-property { + color: lighten($syntax-consul, 20%); + } + + span.cm-variable-3 { + color: #9fca56; + } + + span.cm-builtin { + color: #9fca56; + } + + .CodeMirror-activeline-background { + background: #101213; + } + + .CodeMirror-matchingbracket { + text-decoration: underline; + color: $ui-white !important; + } +} + +.readonly-codemirror { + .CodeMirror-cursors { + display: none; + } + + .cm-s-hashi { + span { + color: $syntax-light-grey; + } + + span.cm-string, + span.cm-string-2 { + color: $syntax-faded-gray; + } + + span.cm-number { + color: lighten($syntax-dark-gray, 30%); + } + + span.cm-property { + color: $ui-white; + } + + span.cm-variable-2 { + color: $syntax-light-grey-blue; + } + } +} diff --git a/ui-v2/app/templates/components/code-editor.hbs b/ui-v2/app/templates/components/code-editor.hbs index 029c902cda91..4ebd81644b36 100644 --- a/ui-v2/app/templates/components/code-editor.hbs +++ b/ui-v2/app/templates/components/code-editor.hbs @@ -1,7 +1,18 @@ {{ivy-codemirror value=value + readonly=readonly name=name class=class - options=(hash readOnly=readonly lineNumbers=true mode=mode theme='hashi' showCursorWhenSelecting=true) + options=options valueUpdated=(action onkeyup) }} +{{#if (not syntax)}} +{{#power-select + onchange=(action onchange) + selected=mode + searchEnabled=false + options=modes as |mode| +}} + {{mode.name}} +{{/power-select}} +{{/if}} diff --git a/ui-v2/app/templates/dc/acls/policies/-fieldsets.hbs b/ui-v2/app/templates/dc/acls/policies/-fieldsets.hbs index be9932ecbb98..a2755edbfbee 100644 --- a/ui-v2/app/templates/dc/acls/policies/-fieldsets.hbs +++ b/ui-v2/app/templates/dc/acls/policies/-fieldsets.hbs @@ -11,7 +11,7 @@