diff --git a/_custombuild b/_custombuild
index f79b4a2..88dbafc 100755
--- a/_custombuild
+++ b/_custombuild
@@ -64,7 +64,8 @@ var components = gulp.src([
APP_ROOT + 'components/gaia-list/gaia-list.js',
APP_ROOT + 'components/gaia-button/gaia-button.js',
APP_ROOT + 'components/gaia-switch/gaia-switch.js',
- APP_ROOT + 'components/jszip/dist/jszip.js'
+ APP_ROOT + 'components/jszip/dist/jszip.js',
+ APP_ROOT + 'components/esprima/esprima.js'
])
.pipe(concat('components-concat.js'))
diff --git a/app/js/elements/fxos-code-editor.js b/app/js/elements/fxos-code-editor.js
new file mode 100644
index 0000000..35b453d
--- /dev/null
+++ b/app/js/elements/fxos-code-editor.js
@@ -0,0 +1,107 @@
+(function(window) {
+'use strict';
+
+var proto = Object.create(HTMLElement.prototype);
+
+var template =
+`
+
`;
+
+proto.createdCallback = function() {
+ var value = this.innerHTML;
+
+ this.shadow = this.createShadowRoot();
+ this.shadow.innerHTML = template;
+
+ this.lineNumbers = this.shadow.querySelector('.line-numbers');
+
+ this.textarea = this.shadow.querySelector('textarea');
+ this.textarea.value = value;
+
+ this.textarea.addEventListener('keyup', () => {
+ this.dispatchEvent(new CustomEvent('change'));
+ updateLineNumbers(this);
+ });
+
+ this.textarea.addEventListener('scroll', () => {
+ this.lineNumbers.scrollTop = this.textarea.scrollTop;
+ });
+};
+
+Object.defineProperty(proto, 'value', {
+ get: function() {
+ return this.textarea.value;
+ },
+
+ set: function(value) {
+ this.textarea.value = value;
+ this.dispatchEvent(new CustomEvent('change'));
+ updateLineNumbers(this);
+ }
+});
+
+function updateLineNumbers(element) {
+ var html = '';
+
+ var lines = element.value.split('\n').length;
+ for (var i = 1; i <= lines; i++) {
+ html += '' + i + '
';
+ }
+
+ element.lineNumbers.innerHTML = html;
+ element.lineNumbers.scrollTop = element.textarea.scrollTop;
+}
+
+try {
+ document.registerElement('fxos-code-editor', { prototype: proto });
+} catch (e) {
+ if (e.name !== 'NotSupportedError') {
+ throw e;
+ }
+}
+
+})(window);
diff --git a/app/js/views/edit.js b/app/js/views/edit.js
index 22bb2f4..29142e0 100644
--- a/app/js/views/edit.js
+++ b/app/js/views/edit.js
@@ -1,5 +1,6 @@
/* global View */
+/* global esprima */
/* global html_beautify */
var editViewTemplate =
@@ -30,12 +31,6 @@ var editViewTemplate =
.tab-pane.active {
display: block;
}
- textarea {
- border: none;
- font-family: Consolas,Monaco,"Andale Mono",monospace;
- width: 100%;
- height: 100%;
- }
textarea,
input {
-moz-user-select: text !important;
@@ -50,6 +45,30 @@ var editViewTemplate =
background: #000;
color: #fff;
}
+ fxos-code-editor {
+ width: 100%;
+ height: 100%;
+ }
+ .errors {
+ background: #820000;
+ color: #fff;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 20px;
+ overflow: hidden;
+ z-index: 2;
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ pointer-events: none;
+ }
+ .errors.active {
+ opacity: 1;
+ }
+ .errors.active ~ fxos-code-editor {
+ height: calc(100% - 20px);
+ }
@@ -63,10 +82,11 @@ var editViewTemplate =
Properties
@@ -92,11 +112,13 @@ export default class EditView extends View {
this.header = this.$('gaia-header');
this.tabs = this.$('gaia-tabs');
- this.htmlTextarea = this.$('section[data-id="html"] > textarea');
- this.scriptTextarea = this.$('section[data-id="script"] > textarea');
+ this.htmlCodeEditor = this.$('section[data-id="html"] > fxos-code-editor');
+ this.scriptCodeEditor = this.$('section[data-id="script"] > fxos-code-editor');
this.attributeInspector = this.$('section[data-id="attributes"] > gaia-property-inspector');
this.propertyInspector = this.$('section[data-id="properties"] > gaia-property-inspector');
+ this.scriptErrors = this.$('section[data-id="script"] > .errors');
+
this.tabPanes = [].slice.apply(this.$$('.tab-pane'));
this.on('click', 'button[data-action="cancel"]', (evt) => {
@@ -117,12 +139,15 @@ export default class EditView extends View {
});
});
- this.htmlTextarea.addEventListener('keyup', (evt) => {
- this.controller.changes.innerHTML = this.htmlTextarea.value;
+ this.htmlCodeEditor.addEventListener('change', (evt) => {
+ this.controller.changes.innerHTML = this.htmlCodeEditor.value;
});
- this.scriptTextarea.addEventListener('keyup', (evt) => {
- this.controller.changes.script = this.scriptTextarea.value;
+ this.scriptCodeEditor.addEventListener('change', (evt) => {
+ this.controller.changes.script = this.scriptCodeEditor.value;
+
+ clearTimeout(this.validateScriptTimeout);
+ this.validateScriptTimeout = setTimeout(this.validateScript.bind(this), 1000);
});
this.on('save', 'gaia-property-inspector', (evt) => {
@@ -158,8 +183,8 @@ export default class EditView extends View {
indent_size: 2
});
- this.htmlTextarea.value = html;
- this.scriptTextarea.value =
+ this.htmlCodeEditor.value = html;
+ this.scriptCodeEditor.value =
`/**
* You can edit a script to be inserted
* in the generated add-on here.
@@ -178,4 +203,29 @@ export default class EditView extends View {
this.attributeInspector.set(clonedTarget);
this.propertyInspector.set(clonedTarget);
}
+
+ validateScript() {
+ var error;
+
+ try {
+ var syntax = esprima.parse(this.controller.changes.script);
+ if (syntax.errors && syntax.errors.length > 0) {
+ error = syntax.errors[0];
+ }
+ }
+
+ catch (e) {
+ error = e;
+ }
+
+ if (error) {
+ this.scriptErrors.textContent = error.message;
+ this.scriptErrors.classList.add('active');
+ }
+
+ else {
+ this.scriptErrors.textContent = '';
+ this.scriptErrors.classList.remove('active');
+ }
+ }
}
diff --git a/bower.json b/bower.json
index 51f1b11..c2591e1 100644
--- a/bower.json
+++ b/bower.json
@@ -25,7 +25,8 @@
"js-beautify": "~1.5.5",
"jszip": "~2.5.0",
"observe-shim": "~0.4.2",
- "observe-utils": "~0.3.2"
+ "observe-utils": "~0.3.2",
+ "esprima": "~2.0.0"
},
"resolutions": {
"font-fit": "~0.3.0"