./static/js/editors/textarea.js annotated source
Back to indexThis is a reference implementation of the EditorCore
interface that allows any third-party editor to be plugged into Codeframe as the code editor of choice. TextAreaEditor
is backed by a simple <textarea>
element.
4class TextAreaEditor {
5
6 constructor(callback) {
7 this.mode = 'html';
8
When the file contents are not being edited, they are cached in this.frames
.
11 this.frames = {
12 html: '',
13 javascript: '',
14 }
15
16 this.container = document.createElement('textarea');
17 this.container.classList.add('editorContainer');
18
Attach some basic event handlers to allow correct indentation with tabs (4 spaces, for consistency).
21 this.container.addEventListener('keydown', evt => {
22 if (evt.key === 'Tab') {
By default, the tab key will lead the user to go the next element in the tab-index order.
25 evt.preventDefault();
26
27 const tgt = evt.target;
28 if (evt.shiftKey) {
29 const idx = tgt.selectionStart;
30 if (idx !== null) {
31 let front = tgt.value.substr(0, idx);
32 const back = tgt.value.substr(idx);
33 let diff = 0;
If dedenting, remove any whitespace up to 4 spaces in front of the cursor.
36 while (front.endsWith(' ') && diff < 4) {
37 front = front.substr(0, front.length - 1);
38 tgt.value = front + back;
39 diff ++;
40 }
Rendering the new input value will make us lose focus on the textarea, so we put the focus back by selecting the area the user was just editing.
44 tgt.setSelectionRange(idx - diff, idx - diff);
45 }
46 } else {
47 const idx = tgt.selectionStart;
48 if (idx !== null) {
49 const front = tgt.value.substr(0, idx);
50 const back = tgt.value.substr(idx);
If indenting, just add 4 spaces at the position of the cursor.
52 tgt.value = front + ' ' + back;
Rendering the new input value will make us lose focus on the textarea, so we put the focus back by selecting the area the user was just editing.
56 tgt.setSelectionRange(idx + 4, idx + 4);
57 }
58 }
59 }
60 });
61
Set the first file editing mode ('html')
63 this.setMode(this.mode);
64
This is a trick to get around the asynchrony requirement for the callback. (Promise callbacks are executed in the microtask queue, not immediately.) This is required because Codeframe's editor component assumes that this callback is called after the editor is instantiated, so a synchronous callback in the constructor doesn't work.
70 Promise.resolve().then(() => {
71 callback(this);
72 });
73 }
74
75 getValue(mode = this.mode) {
76 if (mode === this.mode) {
77 return this.container.value;
78 } else {
79 return this.frames[mode];
80 }
81 }
82
83 setValue(value, mode = this.mode) {
84 if (mode === this.mode) {
85 this.container.value = value;
86 } else {
87 this.frames[mode] = value;
88 }
89 }
90
91 getMode() {
92 return this.mode;
93 }
94
95 setMode(mode) {
Persist the current editor value to memory first
97 this.frames[this.mode] = this.container.value;
98
99 this.mode = mode;
100 this.container.value = this.frames[this.mode];
101 }
102
103 addChangeHandler(handler) {
104 this.container.addEventListener('input', handler);
105 }
106
107 getContainer() {
108 return this.container;
109 }
110
111 resize() {
112 // no-op
113 }
114
115 ready() {
Since the text area editor is synchronously initialized, it is always "ready" to be used.
118 return true;
119 }
120
121}
122