Replace Rose::CodeEditor with Hds::CodeEditor (#2679)
* chore: 🤖 replace Rose::CodeEditor with Hds::CodeEditor - Remove CodeEditor 5 dependency * chore: 🤖 Replace CodeMirror5 dep remove CodeMirror5 and replace existing editors with HDS::CodeEditor * chore: 🤖 deleting old codeeditor styles * feat: 🎸 changing code editor changing code editor ✅ Closes: 0 * removed unused export * pr cleanup * updating tests for new code editor * responding to PR feedback * added whitespace * fixing linting violations * chore: 🤖 self-review PR self-review PR in the course of switching to new HDS code editor * fix: 🐛 fix linting bug * fix: 🐛 unneeded dependency remove jslint dependency as this is handled in the new code editor from HDS * fix: 🐛 Fixing import bug Fix import bug caused by importing scss modules that no longer exist * Fix and refactor code editor acceptance test * Fix and refactor code editor acceptance test * chore: 🤖 remove codemirror dependency * refactor: 💡 fix merge conflict issues * test: 💍 update e2e test selectors * test: 💍 fix target spec click event * refactor: 💡 clean up test selectors for code editor * refactor: 💡 clean up storage bucket test selectors * chore: 🤖 remove code mirror modifiers --------- Co-authored-by: calcaide <ccorvo@hashicorp.com> Co-authored-by: cameronperera <cameron.perera@hashicorp.com>
This commit is contained in:
1 parent
fd1eba617b
commit
d469f305de
41 files changed
+354
-744
No files matched your search
@@ -30,7 +30,7 @@ module.exports = function (environment) {
|
||||
},
|
||||
|
||||
mirage: {
|
||||
enabled: true
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ sync-interval:
|
||||
tags:
|
||||
label: Tags
|
||||
secret-editor:
|
||||
label: Secret Editor
|
||||
details: This secret is saved but won't be displayed
|
||||
edit: Click to replace this secret
|
||||
created:
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{{!
|
||||
Copyright IBM Corp. 2021, 2026
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<div
|
||||
data-test-code-editor-field-editor
|
||||
...attributes
|
||||
class='rose-codemirror-container'
|
||||
{{code-mirror value=@value onInput=@onInput options=@options}}
|
||||
></div>
|
||||
@@ -1,13 +0,0 @@
|
||||
{{!
|
||||
Copyright IBM Corp. 2021, 2026
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<div data-test-code-editor>
|
||||
{{yield
|
||||
(hash
|
||||
fieldEditor=(component 'rose/code-editor/field-editor' value=@codeValue)
|
||||
toolbar=(component 'rose/code-editor/toolbar' copyText=@codeValue)
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
{{!
|
||||
Copyright IBM Corp. 2021, 2026
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<div class='rose-code-editor-toolbar' data-test-code-editor-toolbar>
|
||||
<div data-test-code-editor-toolbar-action class='code-editor-toolbar__action'>
|
||||
{{yield to='action'}}
|
||||
</div>
|
||||
|
||||
<div class='rose-code-editor-toolbar__right-aligned'>
|
||||
{{#if (has-block 'menu')}}
|
||||
<div
|
||||
data-test-code-editor-toolbar-menu
|
||||
class='rose-code-editor-toolbar__menu'
|
||||
>
|
||||
{{yield to='menu'}}
|
||||
</div>
|
||||
<div
|
||||
class='rose-code-editor-toolbar__menu-divider'
|
||||
data-test-code-editor-toolbar-menu-divider
|
||||
></div>
|
||||
{{/if}}
|
||||
<Hds::Copy::Button
|
||||
@text='Copy code'
|
||||
@textToCopy={{@copyText}}
|
||||
@onSuccess={{this.copied}}
|
||||
data-test-code-editor-toolbar-copy-button
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class RoseCodeEditorToolbarComponent extends Component {
|
||||
@action
|
||||
copied() {
|
||||
this.args.onCopy?.();
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Modifier from 'ember-modifier';
|
||||
import codemirror from 'codemirror';
|
||||
|
||||
import '../utilities/register-codemirror-hcl';
|
||||
import 'codemirror/mode/go/go';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/shell/shell';
|
||||
import 'codemirror/addon/edit/matchbrackets';
|
||||
import 'codemirror/addon/edit/closebrackets';
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import 'codemirror/addon/display/autorefresh';
|
||||
import 'codemirror/addon/lint/lint';
|
||||
import 'codemirror/addon/lint/json-lint';
|
||||
|
||||
// Here we define default options for the editor.
|
||||
// These should follow the codemirror configuration types
|
||||
//
|
||||
const _PRESET_DEFAULTS = {
|
||||
theme: 'monokai',
|
||||
lineNumbers: true,
|
||||
cursorBlinkRate: 500,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
styleActiveLine: true,
|
||||
mode: 'hcl',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* `CodeMirror` implements a modifier that creates a CodeMirror instance on the provided element.
|
||||
*
|
||||
* The supported modes for the editor are currently: hcl, shell, go, javascript
|
||||
* The supported themes are: monokai
|
||||
*
|
||||
* Sample usage:
|
||||
* ```
|
||||
* <div
|
||||
* {{code-mirror value=@value onInput=@onInput options=@options}}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @class CodeMirror
|
||||
*
|
||||
*/
|
||||
export default class CodeMirrorModifier extends Modifier {
|
||||
_editor;
|
||||
didSetup = false;
|
||||
|
||||
modify(element, _, named) {
|
||||
if (!this.didSetup) {
|
||||
this._setup(element, named);
|
||||
this.didSetup = true;
|
||||
}
|
||||
|
||||
if (this._editor?.getValue() !== named.value) {
|
||||
this._editor?.setValue(named.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when editor contents change.
|
||||
* @method CodeMirror#_onChange
|
||||
* See documentation for the CodeMirror editor events, specifically 'change' event here https://codemirror.net/doc/manual.html#events.
|
||||
*/
|
||||
_onChange(editor, named) {
|
||||
const { onInput } = named;
|
||||
const newVal = editor.getValue();
|
||||
|
||||
onInput && onInput(newVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the editor with default configuration merged with consumer-provided configuration.
|
||||
* Also registers a 'change' event on the editor.
|
||||
* @method CodeMirror#_setup
|
||||
*/
|
||||
_setup(element, named) {
|
||||
if (!element) {
|
||||
throw new Error('CodeMirror modifier has no element');
|
||||
}
|
||||
|
||||
const editor = codemirror(element, {
|
||||
..._PRESET_DEFAULTS,
|
||||
...(named.options || {}),
|
||||
value: named.value,
|
||||
});
|
||||
|
||||
editor.on('change', (editor) => {
|
||||
this._onChange(editor, named);
|
||||
});
|
||||
|
||||
this._editor = editor;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import codemirror from 'codemirror';
|
||||
import 'codemirror/addon/mode/simple';
|
||||
|
||||
// This utility defines 'hcl' mode for a codemirror editor and enables correct syntax highlighting.
|
||||
// Importing this file in the codemirror modifier will allow 'hcl' to be selected as a mode:
|
||||
// import 'cloud-ui-core/utils/register-codemirror-hcl'
|
||||
codemirror.defineSimpleMode('hcl', {
|
||||
start: [
|
||||
{ regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: 'string' }, // strings
|
||||
{ regex: /(\w+)(\s+)(=)/, token: ['keyword', 'null', 'null'] }, // assignment of variables
|
||||
{
|
||||
regex: /(build|deploy|release|hook|registry|type|runner|url)( )({)/,
|
||||
token: ['keyword', 'null', 'null'],
|
||||
}, // top level keywords
|
||||
{ regex: /(variable)\b/, token: 'keyword' }, // input variable keyword
|
||||
{ regex: /true|false|null|undefined/, token: 'atom' }, // bool keywords
|
||||
{
|
||||
regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
|
||||
token: 'number',
|
||||
}, // numbers
|
||||
{ regex: /(#|\/\/)(\s*\S*)/, token: 'comment' }, // single line comments
|
||||
{ regex: /(path)(.)/, token: ['variable-2', 'null'] }, // path variables
|
||||
{ regex: /(workspace)(.)(\S*)/, token: ['string', 'null', 'string'] }, // workspace variables
|
||||
{ regex: /\/\*/, token: 'comment', next: 'comment' }, // multi-line comment
|
||||
{ regex: /[-+/*=<>!]+/, token: 'operator' }, // operators
|
||||
{ regex: /[{[(]/, indent: true }, // for auto indent
|
||||
{ regex: /[}\])]/, dedent: true },
|
||||
],
|
||||
// The multi-line comment state.
|
||||
comment: [
|
||||
{ regex: /.*?\*\//, token: 'comment', next: 'start' },
|
||||
{ regex: /.*/, token: 'comment' },
|
||||
],
|
||||
meta: {
|
||||
dontIndentStates: ['comment'],
|
||||
lineComment: '#',
|
||||
},
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'rose/components/rose/code-editor';
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'rose/components/rose/code-editor/field-editor';
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'rose/components/rose/code-editor/toolbar';
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'rose/modifiers/code-mirror';
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
// This list is sorted in ascending order to improve readability
|
||||
|
||||
@use 'code-editor';
|
||||
@use 'form';
|
||||
@use 'frame';
|
||||
@use 'layout';
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
@use '../../variables/sizing';
|
||||
|
||||
// Codemirror overrides
|
||||
.rose-codemirror-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-family: var(--token-typography-font-stack-code);
|
||||
}
|
||||
|
||||
.rose-autosize {
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror-sizer {
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
.rose-code-editor-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--ui-gray-subtler-3);
|
||||
height: 2.75rem;
|
||||
|
||||
&__action {
|
||||
margin-left: sizing.rems(s);
|
||||
}
|
||||
|
||||
&__right-aligned {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__menu {
|
||||
margin-right: sizing.rems(s);
|
||||
|
||||
.pds-dropdownTrigger {
|
||||
box-shadow: none;
|
||||
|
||||
&__layoutWrapper {
|
||||
padding-left: sizing.rems(s);
|
||||
padding-right: sizing.rems(s);
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
border-right: 1px solid var(--ui-gray-subtler-3);
|
||||
height: sizing.rems(xl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,6 @@ module.exports = {
|
||||
included(app) {
|
||||
this._super.included.apply(this, arguments);
|
||||
|
||||
this.import('node_modules/codemirror/lib/codemirror.css');
|
||||
this.import('node_modules/codemirror/theme/monokai.css');
|
||||
this.import('node_modules/codemirror/addon/lint/lint.css');
|
||||
this.import('node_modules/jsonlint/lib/jsonlint.js');
|
||||
|
||||
this.includeHDSStyles(app);
|
||||
this.includeFlightIcons(app);
|
||||
this.includePublic(app);
|
||||
|
||||
@@ -34,14 +34,12 @@
|
||||
"@hashicorp/design-system-tokens": "^2.3.0",
|
||||
"@hashicorp/flight-icons": "^3.10.0",
|
||||
"@nullvoxpopuli/ember-composable-helpers": "^5.2.10",
|
||||
"codemirror": "5.65.7",
|
||||
"ember-auto-import": "^2.10.0",
|
||||
"ember-cli-babel": "^8.2.0",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-cli-sass": "^11.0.1",
|
||||
"ember-focus-trap": "^1.0.1",
|
||||
"ember-named-blocks-polyfill": "^0.2.5",
|
||||
"jsonlint": "^1.6.3",
|
||||
"sass": "^1.69.5",
|
||||
"uuid": "^14.0.0"
|
||||
},
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | rose/code-editor', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
const codeValue = 'export HCP_CLIENT_ID = {place your client_id here}';
|
||||
this.set('codeValue', codeValue);
|
||||
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor
|
||||
@codeValue={{this.codeValue}}
|
||||
as |c|
|
||||
>
|
||||
<c.toolbar />
|
||||
<c.fieldEditor />
|
||||
</Rose::CodeEditor>
|
||||
`);
|
||||
assert.dom('[data-test-code-editor]').isVisible();
|
||||
assert.dom('[data-test-code-editor-toolbar]').isVisible();
|
||||
assert.dom('[data-test-code-editor-field-editor]').isVisible();
|
||||
assert.dom('[data-test-code-editor-field-editor]').includesText(codeValue);
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { typeIn, render, waitUntil } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module(
|
||||
'Integration | Component | rose/code-editor/field-editor',
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
const editorSelector = '[data-test-code-editor-field-editor]';
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
const codeValue = `export HCP_CLIENT_ID = {place your client_id here}`;
|
||||
this.set('codeValue', codeValue);
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::FieldEditor @value={{this.codeValue}} />
|
||||
`);
|
||||
assert.dom(editorSelector).isVisible();
|
||||
assert.dom(editorSelector).includesText(codeValue);
|
||||
|
||||
const myNewCode = 'NEW CODE';
|
||||
await typeIn(`${editorSelector} textarea`, myNewCode);
|
||||
assert.dom(editorSelector).includesText(myNewCode);
|
||||
});
|
||||
|
||||
test('it renders readonly', async function (assert) {
|
||||
const codeValue = `export HCP_CLIENT_ID = {place your client_id here}`;
|
||||
this.set('codeValue', codeValue);
|
||||
this.set('options', {
|
||||
readOnly: true,
|
||||
});
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::FieldEditor @value={{this.codeValue}} @options={{this.options}} />
|
||||
`);
|
||||
assert.dom(editorSelector).isVisible();
|
||||
assert.dom(editorSelector).includesText(codeValue);
|
||||
|
||||
const myNewCode = 'NEW CODE';
|
||||
waitUntil(async () => {
|
||||
try {
|
||||
// This should fail if the textarea is readonly
|
||||
await typeIn(`${editorSelector} textarea`, myNewCode);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
assert.dom(editorSelector).doesNotIncludeText(myNewCode);
|
||||
});
|
||||
|
||||
test('it calls onInput on change', async function (assert) {
|
||||
const codeValue = `export HCP_CLIENT_ID = {place your client_id here}`;
|
||||
const onInput = () => {
|
||||
this.set('called', true);
|
||||
};
|
||||
this.set('codeValue', codeValue);
|
||||
this.set('onInput', onInput);
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::FieldEditor @value={{this.codeValue}} @onInput={{this.onInput}} />
|
||||
`);
|
||||
assert.dom(editorSelector).isVisible();
|
||||
assert.dom(editorSelector).includesText(codeValue);
|
||||
|
||||
const myNewCode = 'NEW CODE';
|
||||
await typeIn(`${editorSelector} textarea`, myNewCode);
|
||||
assert.dom(editorSelector).includesText(myNewCode);
|
||||
assert.true(this.called);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Integration | Component | rose/code-editor/toolbar', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
const toolbarSelector = '[data-test-code-editor-toolbar]';
|
||||
const copyButtonSelector = '[data-test-code-editor-toolbar-copy-button]';
|
||||
const menuDividerSelector = '[data-test-code-editor-toolbar-menu-divider]';
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
const copyText = 'copyText';
|
||||
this.set('copyText', copyText);
|
||||
});
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::Toolbar @copyText={{this.copyText}} />
|
||||
`);
|
||||
assert.dom(toolbarSelector).isVisible();
|
||||
assert.dom(copyButtonSelector).isVisible();
|
||||
assert.dom(menuDividerSelector).doesNotExist();
|
||||
});
|
||||
|
||||
test('it displays action', async function (assert) {
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::Toolbar @onCopy={{this.copyText}}>
|
||||
<:action>
|
||||
<div data-test-my-action>Hello</div>
|
||||
</:action>
|
||||
</Rose::CodeEditor::Toolbar>
|
||||
`);
|
||||
assert.dom(toolbarSelector).isVisible();
|
||||
assert.dom(copyButtonSelector).isVisible();
|
||||
assert.dom(menuDividerSelector).doesNotExist();
|
||||
assert.dom('[data-test-my-action]').isVisible();
|
||||
});
|
||||
|
||||
test('it calls onCopy callback', async function (assert) {
|
||||
// `writeText` could fail if the document is not in focus which is common during test runs
|
||||
sinon.stub(window.navigator.clipboard, 'writeText').resolves();
|
||||
|
||||
const onCopy = () => {
|
||||
this.set('called', true);
|
||||
};
|
||||
this.set('onCopy', onCopy);
|
||||
await render(hbs`
|
||||
<Rose::CodeEditor::Toolbar @copyText={{this.copyText}} @onCopy={{this.onCopy}} />
|
||||
`);
|
||||
assert.dom(toolbarSelector).isVisible();
|
||||
assert.dom(copyButtonSelector).isVisible();
|
||||
assert.dom(menuDividerSelector).doesNotExist();
|
||||
await click(copyButtonSelector);
|
||||
assert.true(this.called);
|
||||
|
||||
sinon.restore();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user