Commit 194eee15 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '241023-editor-cleanup' into 'master'

Moved model down to instance level

See merge request gitlab-org/gitlab!41179
parents e3471000 c5d65a6d
...@@ -9,11 +9,7 @@ import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from './constants'; ...@@ -9,11 +9,7 @@ import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from './constants';
export default class Editor { export default class Editor {
constructor(options = {}) { constructor(options = {}) {
this.editorEl = null;
this.blobContent = '';
this.blobPath = '';
this.instances = []; this.instances = [];
this.model = null;
this.options = { this.options = {
extraEditorClassName: 'gl-editor-lite', extraEditorClassName: 'gl-editor-lite',
...defaultEditorOptions, ...defaultEditorOptions,
...@@ -32,6 +28,17 @@ export default class Editor { ...@@ -32,6 +28,17 @@ export default class Editor {
monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME); monacoEditor.setTheme(theme ? themeName : DEFAULT_THEME);
} }
static updateModelLanguage(path, instance) {
if (!instance) return;
const model = instance.getModel();
const ext = `.${path.split('.').pop()}`;
const language = monacoLanguages
.getLanguages()
.find(lang => lang.extensions.indexOf(ext) !== -1);
const id = language ? language.id : 'plaintext';
monacoEditor.setModelLanguage(model, id);
}
/** /**
* Creates a monaco instance with the given options. * Creates a monaco instance with the given options.
* *
...@@ -51,19 +58,18 @@ export default class Editor { ...@@ -51,19 +58,18 @@ export default class Editor {
if (!el) { if (!el) {
throw new Error(EDITOR_LITE_INSTANCE_ERROR_NO_EL); throw new Error(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
} }
this.editorEl = el;
this.blobContent = blobContent;
this.blobPath = blobPath;
clearDomElement(this.editorEl); clearDomElement(el);
const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath); const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
const model = monacoEditor.createModel(this.blobContent, undefined, Uri.file(uriFilePath)); const model = monacoEditor.createModel(blobContent, undefined, Uri.file(uriFilePath));
monacoEditor.onDidCreateEditor(this.renderEditor.bind(this)); monacoEditor.onDidCreateEditor(() => {
delete el.dataset.editorLoading;
});
const instance = monacoEditor.create(this.editorEl, { const instance = monacoEditor.create(el, {
...this.options, ...this.options,
...instanceOptions, ...instanceOptions,
}); });
...@@ -73,13 +79,7 @@ export default class Editor { ...@@ -73,13 +79,7 @@ export default class Editor {
this.instances.splice(index, 1); this.instances.splice(index, 1);
model.dispose(); model.dispose();
}); });
instance.updateModelLanguage = path => this.updateModelLanguage(path); instance.updateModelLanguage = path => Editor.updateModelLanguage(path, instance);
// Reference to the model on the editor level will go away in
// https://gitlab.com/gitlab-org/gitlab/-/issues/241023
// After that, the references to the model will be routed through
// instance exclusively
this.model = model;
this.instances.push(instance); this.instances.push(instance);
return instance; return instance;
...@@ -89,21 +89,6 @@ export default class Editor { ...@@ -89,21 +89,6 @@ export default class Editor {
this.instances.forEach(instance => instance.dispose()); this.instances.forEach(instance => instance.dispose());
} }
renderEditor() {
delete this.editorEl.dataset.editorLoading;
}
updateModelLanguage(path) {
if (path === this.blobPath) return;
this.blobPath = path;
const ext = `.${path.split('.').pop()}`;
const language = monacoLanguages
.getLanguages()
.find(lang => lang.extensions.indexOf(ext) !== -1);
const id = language ? language.id : 'plaintext';
monacoEditor.setModelLanguage(this.model, id);
}
use(exts = [], instance = null) { use(exts = [], instance = null) {
const extensions = Array.isArray(exts) ? exts : [exts]; const extensions = Array.isArray(exts) ? exts : [exts];
if (instance) { if (instance) {
......
...@@ -26,9 +26,7 @@ describe('Base editor', () => { ...@@ -26,9 +26,7 @@ describe('Base editor', () => {
it('initializes Editor with basic properties', () => { it('initializes Editor with basic properties', () => {
expect(editor).toBeDefined(); expect(editor).toBeDefined();
expect(editor.editorEl).toBe(null); expect(editor.instances).toEqual([]);
expect(editor.blobContent).toBe('');
expect(editor.blobPath).toBe('');
}); });
it('removes `editor-loading` data attribute from the target DOM element', () => { it('removes `editor-loading` data attribute from the target DOM element', () => {
...@@ -59,10 +57,6 @@ describe('Base editor', () => { ...@@ -59,10 +57,6 @@ describe('Base editor', () => {
editor.createInstance(); editor.createInstance();
}).toThrow(EDITOR_LITE_INSTANCE_ERROR_NO_EL); }).toThrow(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
expect(editor.editorEl).toBe(null);
expect(editor.blobContent).toBe('');
expect(editor.blobPath).toBe('');
expect(modelSpy).not.toHaveBeenCalled(); expect(modelSpy).not.toHaveBeenCalled();
expect(instanceSpy).not.toHaveBeenCalled(); expect(instanceSpy).not.toHaveBeenCalled();
expect(setModel).not.toHaveBeenCalled(); expect(setModel).not.toHaveBeenCalled();
...@@ -93,14 +87,14 @@ describe('Base editor', () => { ...@@ -93,14 +87,14 @@ describe('Base editor', () => {
}); });
it('initializes instance with passed properties', () => { it('initializes instance with passed properties', () => {
const instanceOptions = {
foo: 'bar',
};
editor.createInstance({ editor.createInstance({
el: editorEl, el: editorEl,
blobContent, ...instanceOptions,
blobPath,
}); });
expect(editor.editorEl).toBe(editorEl); expect(instanceSpy).toHaveBeenCalledWith(editorEl, expect.objectContaining(instanceOptions));
expect(editor.blobContent).toBe(blobContent);
expect(editor.blobPath).toBe(blobPath);
}); });
it('disposes instance when the editor is disposed', () => { it('disposes instance when the editor is disposed', () => {
...@@ -149,16 +143,26 @@ describe('Base editor', () => { ...@@ -149,16 +143,26 @@ describe('Base editor', () => {
it('can initialize several instances of the same editor', () => { it('can initialize several instances of the same editor', () => {
editor.createInstance(inst1Args); editor.createInstance(inst1Args);
expect(editor.editorEl).toBe(editorEl1);
expect(editor.instances).toHaveLength(1); expect(editor.instances).toHaveLength(1);
editor.createInstance(inst2Args); editor.createInstance(inst2Args);
expect(editor.editorEl).toBe(editorEl2);
expect(instanceSpy).toHaveBeenCalledTimes(2); expect(instanceSpy).toHaveBeenCalledTimes(2);
expect(editor.instances).toHaveLength(2); expect(editor.instances).toHaveLength(2);
}); });
it('sets independent models on independent instances', () => {
inst1 = editor.createInstance(inst1Args);
inst2 = editor.createInstance(inst2Args);
const model1 = inst1.getModel();
const model2 = inst2.getModel();
expect(model1).toBeDefined();
expect(model2).toBeDefined();
expect(model1).not.toEqual(model2);
});
it('shares global editor options among all instances', () => { it('shares global editor options among all instances', () => {
editor = new Editor({ editor = new Editor({
readOnly: true, readOnly: true,
...@@ -218,20 +222,20 @@ describe('Base editor', () => { ...@@ -218,20 +222,20 @@ describe('Base editor', () => {
const blobRenamedPath = 'test.js'; const blobRenamedPath = 'test.js';
expect(editor.model.getLanguageIdentifier().language).toBe('markdown'); expect(instance.getModel().getLanguageIdentifier().language).toBe('markdown');
editor.updateModelLanguage(blobRenamedPath); instance.updateModelLanguage(blobRenamedPath);
expect(editor.model.getLanguageIdentifier().language).toBe('javascript'); expect(instance.getModel().getLanguageIdentifier().language).toBe('javascript');
}); });
it('falls back to plaintext if there is no language associated with an extension', () => { it('falls back to plaintext if there is no language associated with an extension', () => {
const blobRenamedPath = 'test.myext'; const blobRenamedPath = 'test.myext';
const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
editor.updateModelLanguage(blobRenamedPath); instance.updateModelLanguage(blobRenamedPath);
expect(spy).not.toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled();
expect(editor.model.getLanguageIdentifier().language).toBe('plaintext'); expect(instance.getModel().getLanguageIdentifier().language).toBe('plaintext');
}); });
}); });
...@@ -298,7 +302,6 @@ describe('Base editor', () => { ...@@ -298,7 +302,6 @@ describe('Base editor', () => {
}; };
editor.use(FunctionExt); editor.use(FunctionExt);
expect(instance.inst()).toEqual(editor.instances[0]); expect(instance.inst()).toEqual(editor.instances[0]);
expect(instance.mod()).toEqual(editor.model);
}); });
describe('multiple instances', () => { describe('multiple instances', () => {
......
...@@ -35,7 +35,7 @@ describe('Markdown Extension for Editor Lite', () => { ...@@ -35,7 +35,7 @@ describe('Markdown Extension for Editor Lite', () => {
}); });
afterEach(() => { afterEach(() => {
editor.model.dispose(); instance.dispose();
editorEl.remove(); editorEl.remove();
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment