diff --git a/plugins/cockpit/lib/cli.js b/plugins/cockpit/lib/cli.js index cfdbcc00..01d81a34 100644 --- a/plugins/cockpit/lib/cli.js +++ b/plugins/cockpit/lib/cli.js @@ -446,7 +446,8 @@ Requisition.prototype = { * Helper to call canon.exec */ exec: function() { - canon.exec(this.command, this.getArgs()); + // TODO: have a toString rather than hack this.input + canon.exec(this.command, this.getArgs(), this.input.typed); } }; exports.Requisition = Requisition; @@ -492,8 +493,8 @@ oop.inherits(CliRequisition, Requisition); * */ CliRequisition.prototype.update = function(input) { - // TODO: We only store this so getHints can work. Find a better way. this.input = input; + // TODO: We only store this so getHints can work. Find a better way. this.localHints = []; if (util.none(input.typed)) { diff --git a/plugins/cockpit/lib/ui/images/closer.png b/plugins/cockpit/lib/ui/images/closer.png new file mode 100644 index 00000000..d49bf204 Binary files /dev/null and b/plugins/cockpit/lib/ui/images/closer.png differ diff --git a/plugins/cockpit/lib/ui/images/dot_clear.gif b/plugins/cockpit/lib/ui/images/dot_clear.gif new file mode 100644 index 00000000..1d9a4f52 Binary files /dev/null and b/plugins/cockpit/lib/ui/images/dot_clear.gif differ diff --git a/plugins/cockpit/lib/ui/images/minus.png b/plugins/cockpit/lib/ui/images/minus.png new file mode 100644 index 00000000..4c029f11 Binary files /dev/null and b/plugins/cockpit/lib/ui/images/minus.png differ diff --git a/plugins/cockpit/lib/ui/images/pinaction.png b/plugins/cockpit/lib/ui/images/pinaction.png new file mode 100644 index 00000000..0e5b2ea1 Binary files /dev/null and b/plugins/cockpit/lib/ui/images/pinaction.png differ diff --git a/plugins/cockpit/lib/ui/images/pinin.png b/plugins/cockpit/lib/ui/images/pinin.png new file mode 100644 index 00000000..e761de0e Binary files /dev/null and b/plugins/cockpit/lib/ui/images/pinin.png differ diff --git a/plugins/cockpit/lib/ui/images/pinout.png b/plugins/cockpit/lib/ui/images/pinout.png new file mode 100644 index 00000000..a5353624 Binary files /dev/null and b/plugins/cockpit/lib/ui/images/pinout.png differ diff --git a/plugins/cockpit/lib/ui/images/pins.png b/plugins/cockpit/lib/ui/images/pins.png new file mode 100644 index 00000000..fa6ec820 Binary files /dev/null and b/plugins/cockpit/lib/ui/images/pins.png differ diff --git a/plugins/cockpit/lib/ui/images/plus.png b/plugins/cockpit/lib/ui/images/plus.png new file mode 100644 index 00000000..fc017ad3 Binary files /dev/null and b/plugins/cockpit/lib/ui/images/plus.png differ diff --git a/plugins/cockpit/lib/ui/images/throbber.gif b/plugins/cockpit/lib/ui/images/throbber.gif new file mode 100644 index 00000000..d84f6537 Binary files /dev/null and b/plugins/cockpit/lib/ui/images/throbber.gif differ diff --git a/plugins/cockpit/lib/ui/plain.css b/plugins/cockpit/lib/ui/plain.css index 2d72c6f0..bd1c000f 100644 --- a/plugins/cockpit/lib/ui/plain.css +++ b/plugins/cockpit/lib/ui/plain.css @@ -38,6 +38,29 @@ border: none; outline: none; } +.cptOutput { + border: 1px solid #AAA; + -moz-border-radius-topleft: 10px; + -moz-border-radius-topright: 10px; + border-top-left-radius: 4px; border-top-right-radius: 4px; + padding: 10px; + margin: 0 15px; + background: #DDD; color: #000; + font-family: consolas, courier, monospace; + font-size: 90%; + overflow: hidden; + position: absolute; + z-index: 999; +} + + + + + + + + + .cptHints article { margin-left: 25px; @@ -96,14 +119,6 @@ -/* Layout */ -.cptOutput { - background: @bg; color: @text; - font-family: @fonts; font-size: 90%; - overflow: hidden; - position: absolute; -} - /* The input area */ .cptInput { border-top: 1px solid @border_bg; @@ -140,42 +155,3 @@ } - - -.cptRowIn { - display: box; display: -moz-box; display: -webkit-box; - box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal; - box-align: center; -moz-box-align: center; -webkit-box-align: center; - color: @text; - background-color: @input_bg; - width: 100%; -} -.cptRowIn > * { padding-left: 2px; padding-right: 2px; } -.cptRowIn > img { cursor: pointer; } -.cptHover { display: none; } -.cptRowIn:hover > .cptHover { display: block; } -.cptRowIn:hover > .cptHover.cptHidden { display: none; } -.cptOutTyped { - box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1; - font-weight: bold; color: @hi_text; font-size: 120%; -} -.cptRowOutput { padding-left: 10px; line-height: 1.2em; } -.cptRowOutput strong, -.cptRowOutput b, -.cptRowOutput th, -.cptRowOutput h1, -.cptRowOutput h2, -.cptRowOutput h3 { color: @hi_text; } -.cptRowOutput a { font-weight: bold; color: @link_text; text-decoration: none; } -.cptRowOutput a: hover { text-decoration: underline; cursor: pointer; } -.cptRowOutput input[type=password], -.cptRowOutput input[type=text], -.cptRowOutput textarea { - color: @hi_text; font-size: 120%; - background: transparent; padding: 3px; - border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; -} -.cptRowOutput table, -.cptRowOutput td, -.cptRowOutput th { border: 0; padding: 0 2px; } -.cptRowOutput .right { text-align: right; } diff --git a/plugins/cockpit/lib/ui/plain.js b/plugins/cockpit/lib/ui/plain.js index f1d2f81d..cb0ea8ac 100644 --- a/plugins/cockpit/lib/ui/plain.js +++ b/plugins/cockpit/lib/ui/plain.js @@ -37,18 +37,18 @@ define(function(require, exports, module) { + var editorCss = require("text!cockpit/ui/plain.css"); var dom = require("pilot/dom").dom; dom.importCssString(editorCss); -var CliRequisition = require('cockpit/cli').CliRequisition; -var Hint = require('cockpit/cli').Hint; -var keyutil = require('pilot/keyboard/keyutil'); - -var plainRow = require("text!cockpit/ui/plainRow.html"); -var Templater = require("pilot/domtemplate").Templater; var canon = require("pilot/canon"); var Status = require('pilot/types').Status; +var keyutil = require('pilot/keyboard/keyutil'); + +var CliRequisition = require('cockpit/cli').CliRequisition; +var Hint = require('cockpit/cli').Hint; +var RequestView = require('cockpit/ui/requestView').RequestView; var NO_HINT = new Hint(Status.VALID, '', 0, 0); @@ -61,19 +61,19 @@ var NO_HINT = new Hint(Status.VALID, '', 0, 0); * 2. Attach a set of events so the command line works */ exports.startup = function(data, reason) { - var plainUi = new PlainUi(data); + var plainUi = new CliView(data); }; /** * A class to handle the simplest UI implementation */ -function PlainUi(data) { +function CliView(data) { this.doc = document; this.win = this.doc.defaultView; // TODO: we should have a better way to specify command lines??? - this.input = this.doc.getElementById('cockpit'); - if (!this.input) { + this.element = this.doc.getElementById('cockpit'); + if (!this.element) { console.log('No element with an id of cockpit. Bailing on plain cli'); return; } @@ -90,26 +90,22 @@ function PlainUi(data) { this.createElements(); } -PlainUi.prototype = { +CliView.prototype = { /** - * Create divs for templates, completion, hints and output + * Create divs for completion, hints and output */ createElements: function() { - this.templates = this.doc.createElement('div'); - this.templates.innerHTML = plainRow; - this.row = this.templates.firstChild; - this.completer = this.doc.createElement('div'); this.completer.className = 'cptCompletion VALID'; - this.input.parentNode.insertBefore(this.completer, this.input); + this.element.parentNode.insertBefore(this.completer, this.element); this.hinter = this.doc.createElement('div'); this.hinter.className = 'cptHints'; - this.input.parentNode.insertBefore(this.hinter, this.input); + this.element.parentNode.insertBefore(this.hinter, this.element); this.output = this.doc.createElement('div'); this.output.className = 'cptOutput'; - this.input.parentNode.insertBefore(this.output, this.input); + this.element.parentNode.insertBefore(this.output, this.element); this.win.addEventListener('resize', this.resizer.bind(this), false); this.resizer(); @@ -119,9 +115,9 @@ PlainUi.prototype = { this.showHint.addEventListener('change', this.hintShower.bind(this)); this.hintShower(); - keyutil.addKeyDownListener(this.input, this.onKeyDown.bind(this)); - this.input.addEventListener('mouseup', this.onMouseUp.bind(this), false); - this.input.addEventListener('keyup', this.onKeyUp.bind(this), true); + keyutil.addKeyDownListener(this.element, this.onKeyDown.bind(this)); + this.element.addEventListener('mouseup', this.onMouseUp.bind(this), false); + this.element.addEventListener('keyup', this.onKeyUp.bind(this), true); }, /** @@ -131,15 +127,15 @@ PlainUi.prototype = { resizer: function() { var top, height, left, width; - if (this.input.getClientRects) { - var rect = this.input.getClientRects()[0]; + if (this.element.getClientRects) { + var rect = this.element.getClientRects()[0]; top = rect.top; height = rect.height; left = rect.left; width = rect.width; } else { - var style = this.win.getComputedStyle(this.input, null); + var style = this.win.getComputedStyle(this.element, null); top = parseInt(style.getPropertyValue('top'), 10); height = parseInt(style.getPropertyValue('height'), 10); left = parseInt(style.getPropertyValue('left'), 10); @@ -156,25 +152,42 @@ PlainUi.prototype = { this.output.style.bottom = (this.win.innerHeight - top) + 'px'; this.output.style.left = left + 'px'; - this.output.style.width = width + 'px'; + this.output.style.width = (width - 60) + 'px'; }, /** * Update the display of executed commands */ showOutput: function(ev) { + var requestOutput = new RequestView(ev.request, this); + + /* // TODO: be less brutal in how we update this this.output.innerHTML = ''; + new RequestView(ev.request, this); ev.requests.forEach(function(request) { - request.outputs.forEach(function(out) { - if (typeof out === 'string') { - this.output.appendChild(this.doc.createTextNode(out)); + // this.duration.innerHTML = this.request.duration ? + // 'completed in ' + (this.request.duration / 1000) + ' sec ' : + // ''; + // this.typed.innerHTML = this.request.typed; + + request.outputs.forEach(function(output) { + var node; + if (typeof output == 'string') { + node = document.createElement('p'); + node.innerHTML = output; } else { - this.output.appendChild(out); + node = output; } + this.output.appendChild(node); }, this); + + // this.cliView.scrollToBottom(); + // util.setClass(this.output, 'cmd_error', this.request.error); + // this.throb.style.display = this.request.completed ? 'none' : 'block'; }, this); + */ }, /** @@ -219,27 +232,26 @@ PlainUi.prototype = { if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) { if (this.worstHint && this.worstHint.status !== Status.VALID) { - this.input.selectionStart = this.worstHint.start; - this.input.selectionEnd = this.worstHint.end; + this.element.selectionStart = this.worstHint.start; + this.element.selectionEnd = this.worstHint.end; } else { this.cli.exec(); - this.input.value = ''; + this.element.value = ''; } } - if (ev.keyCode === keyutil.KeyHelper.KEY.TAB) { - if (this.shownHint && this.shownHint.predictions && this.shownHint.predictions.length > 0) { - var prefix = this.input.value.substring(0, this.shownHint.start); - var suffix = this.input.value.substring(this.shownHint.end); - var insert = this.shownHint.predictions[0]; - insert = typeof insert === 'string' ? insert : insert.name; - this.input.value = prefix + insert + suffix; - // Fix the cursor. - var insertEnd = (prefix + insert).length; - this.input.selectionStart = insertEnd; - this.input.selectionEnd = insertEnd; - } + if (ev.keyCode === keyutil.KeyHelper.KEY.TAB && this.shownHint && + this.shownHint.predictions && this.shownHint.predictions.length > 0) { + var prefix = this.element.value.substring(0, this.shownHint.start); + var suffix = this.element.value.substring(this.shownHint.end); + var insert = this.shownHint.predictions[0]; + insert = typeof insert === 'string' ? insert : insert.name; + this.element.value = prefix + insert + suffix; + // Fix the cursor. + var insertEnd = (prefix + insert).length; + this.element.selectionStart = insertEnd; + this.element.selectionEnd = insertEnd; } this.update(); @@ -260,10 +272,10 @@ PlainUi.prototype = { */ update: function() { this.cli.update({ - typed: this.input.value, + typed: this.element.value, cursor: { - start: this.input.selectionStart, - end: this.input.selectionEnd + start: this.element.selectionStart, + end: this.element.selectionEnd } }); @@ -288,10 +300,10 @@ PlainUi.prototype = { // Create a marked up version of the input var highlightedInput = ''; - if (this.input.value.length > 0) { + if (this.element.value.length > 0) { // 'scores' is an array which tells us what chars are errors // Initialize with everything VALID - var scores = this.input.value.split('').map(function(char) { + var scores = this.element.value.split('').map(function(char) { return Status.VALID; }); // For all chars in all hints, check and upgrade the score @@ -310,9 +322,9 @@ PlainUi.prototype = { highlightedInput += ''; lastStatus = scores[i]; } - highlightedInput += this.input.value[i]; + highlightedInput += this.element.value[i]; i++; - if (i === this.input.value.length) { + if (i === this.element.value.length) { highlightedInput += ''; break; } @@ -351,7 +363,7 @@ PlainUi.prototype = { // dom.addCssClass(input, status.toString()); } }; -exports.PlainUi = PlainUi; +exports.CliView = CliView; }); diff --git a/plugins/cockpit/lib/ui/plainRow.html b/plugins/cockpit/lib/ui/plainRow.html deleted file mode 100644 index ca40e36c..00000000 --- a/plugins/cockpit/lib/ui/plainRow.html +++ /dev/null @@ -1,47 +0,0 @@ - - - -
- -
{ } >
- -
-
- -
- -
-
-
- - - - - -
- -
- - -
>
-
- - -
- Hide command output - Show command output - Remove this command from the history - -
- - -
-
- -
-
diff --git a/plugins/cockpit/lib/ui/requestView.css b/plugins/cockpit/lib/ui/requestView.css new file mode 100644 index 00000000..7d014eb7 --- /dev/null +++ b/plugins/cockpit/lib/ui/requestView.css @@ -0,0 +1,38 @@ + +.cptRowIn { + display: box; display: -moz-box; display: -webkit-box; + box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal; + box-align: center; -moz-box-align: center; -webkit-box-align: center; + color: #333; + background-color: @input_bg; + width: 100%; +} +.cptRowIn > * { padding-left: 2px; padding-right: 2px; } +.cptRowIn > img { cursor: pointer; } +.cptHover { display: none; } +.cptRowIn:hover > .cptHover { display: block; } +.cptRowIn:hover > .cptHover.cptHidden { display: none; } +.cptOutTyped { + box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1; + font-weight: bold; color: #000; font-size: 120%; +} +.cptRowOutput { padding-left: 10px; line-height: 1.2em; } +.cptRowOutput strong, +.cptRowOutput b, +.cptRowOutput th, +.cptRowOutput h1, +.cptRowOutput h2, +.cptRowOutput h3 { color: #000; } +.cptRowOutput a { font-weight: bold; color: #666; text-decoration: none; } +.cptRowOutput a: hover { text-decoration: underline; cursor: pointer; } +.cptRowOutput input[type=password], +.cptRowOutput input[type=text], +.cptRowOutput textarea { + color: #000; font-size: 120%; + background: transparent; padding: 3px; + border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; +} +.cptRowOutput table, +.cptRowOutput td, +.cptRowOutput th { border: 0; padding: 0 2px; } +.cptRowOutput .right { text-align: right; } diff --git a/plugins/cockpit/lib/ui/requestView.html b/plugins/cockpit/lib/ui/requestView.html new file mode 100644 index 00000000..5ecbfc1b --- /dev/null +++ b/plugins/cockpit/lib/ui/requestView.html @@ -0,0 +1,28 @@ + +
+ +
+ + +
>
+
${request.typed}
+ + +
+ Hide command output + Show command output + Remove this command from the history + +
+ + +
+
+ +
+
diff --git a/plugins/cockpit/lib/ui/requestView.js b/plugins/cockpit/lib/ui/requestView.js new file mode 100644 index 00000000..b9cb3708 --- /dev/null +++ b/plugins/cockpit/lib/ui/requestView.js @@ -0,0 +1,152 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Skywriter. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Joe Walker (jwalker@mozilla.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + + +var util = require('pilot/util'); + +var editorCss = require("text!cockpit/ui/requestView.css"); +var dom = require("pilot/dom").dom; +dom.importCssString(editorCss); + +var plainRow = require("text!cockpit/ui/requestView.html"); +var Templater = require("pilot/domtemplate").Templater; + +/** + * Pull the HTML into the DOM, but don't add it to the document + */ +var templates = document.createElement('div'); +templates.innerHTML = plainRow; +var row = templates.querySelector('.cptRow'); + +/** + * Adds a row to the CLI output display + */ +function RequestView(request, cliView) { + this.request = request; + this.cliView = cliView; + + this.imagePath = '/plugins/ace/pluging/cockpit/ui'; + + // Elements attached to this by the templater. For info only + this.rowin = null; + this.rowout = null; + this.output = null; + this.hide = null; + this.show = null; + this.duration = null; + this.throb = null; + + new Templater().processNode(row.cloneNode(true), this); + + this.cliView.output.appendChild(this.rowin); + this.cliView.output.appendChild(this.rowout); + + this.request.addEventListener('output', this.onRequestChange.bind(this)); +}; + +RequestView.prototype = { + /** + * A single click on an invocation line in the console copies the command to + * the command line + */ + copyToInput: function() { + this.cliView.input.value = this.request.typed; + }, + + /** + * A double click on an invocation line in the console executes the command + */ + executeRequest: function(ev) { + this.cliView.cli.update({ + typed: this.request.typed, + cursor: { start:0, end:0 } + }); + this.cliView.cli.exec(); + }, + + hideOutput: function(ev) { + this.output.style.display = 'none'; + util.addClass(this.hide, 'cmd_hidden'); + util.removeClass(this.show, 'cmd_hidden'); + + ev.stopPropagation(); + }, + + showOutput: function(ev) { + this.output.style.display = 'block'; + util.removeClass(this.hide, 'cmd_hidden'); + util.addClass(this.show, 'cmd_hidden'); + + ev.stopPropagation(); + }, + + remove: function(ev) { + this.cliView.output.removeChild(this.rowin); + this.cliView.output.removeChild(this.rowout); + ev.stopPropagation(); + }, + + onRequestChange: function(ev) { + this.duration.innerHTML = this.request.duration ? + 'completed in ' + (this.request.duration / 1000) + ' sec ' : + ''; + + this.output.innerHTML = ''; + this.request.outputs.forEach(function(output) { + var node; + if (typeof output == 'string') { + node = document.createElement('p'); + node.innerHTML = output; + } else { + node = output; + } + this.output.appendChild(node); + }, this); + // TODO: Fix this + // this.cliView.scrollToBottom(); + + util.setClass(this.output, 'cmd_error', this.request.error); + + this.throb.style.display = this.request.completed ? 'none' : 'block'; + } +}; +exports.RequestView = RequestView; + + +}); diff --git a/plugins/pilot/lib/canon.js b/plugins/pilot/lib/canon.js index dc303de4..0e3644d3 100644 --- a/plugins/pilot/lib/canon.js +++ b/plugins/pilot/lib/canon.js @@ -160,7 +160,8 @@ function getCommandNames() { * everything it needs to about the command params * @param command Either a command, or the name of one */ -function exec(command, args) { +function exec(command, args, typed) { + // TODO: Use requisition.toString rather than 'typed' if (typeof command === 'string') { command = commands[command]; } @@ -171,7 +172,12 @@ function exec(command, args) { // TODO: Ugg. really? env.selection = env.editor.getSelection(); - var request = new Request(); + + var request = new Request({ + command: command, + args: args, + typed: typed + }); command.exec(env, args || {}, request); return true; }; @@ -216,21 +222,6 @@ var requests = []; */ var maxRequestLength = 100; -/** - * Called by Request instances when some output (or a cell to async() happens) - */ -function addRequestOutput(request) { - requests.push(request); - // This could probably be optimized with some maths, but 99.99% of the - // time we will only be off by one, and I'm feeling lazy. - while (requests.length > maxRequestLength) { - requests.shiftObject(); - } - - exports._dispatchEvent('output', { requests: requests }); -}; -exports.addRequestOutput = addRequestOutput; - /** * To create an invocation, you need to do something like this (all the ctor * args are optional): @@ -249,7 +240,6 @@ function Request(options) { // Will be used in the keyboard case and the cli case this.command = options.command; - this.commandExt = options.commandExt; // Will be used only in the cli case this.args = options.args; @@ -274,7 +264,14 @@ Request.prototype._beginOutput = function() { this._begunOutput = true; this.outputs = []; - exports.addRequestOutput(this); + requests.push(this); + // This could probably be optimized with some maths, but 99.99% of the + // time we will only be off by one, and I'm feeling lazy. + while (requests.length > maxRequestLength) { + requests.shiftObject(); + } + + exports._dispatchEvent('output', { requests: requests, request: this }); }; /** @@ -311,7 +308,7 @@ Request.prototype.output = function(content) { } this.outputs.push(content); - this._dispatchEvent('changed', {}); + this._dispatchEvent('output', {}); return this; }; @@ -329,7 +326,7 @@ Request.prototype.done = function(content) { this.output(content); } - this._dispatchEvent('changed', {}); + this._dispatchEvent('output', {}); }; exports.Request = Request; diff --git a/plugins/pilot/lib/commands/settings.js b/plugins/pilot/lib/commands/settings.js index c6679bc9..1cfb9ae6 100644 --- a/plugins/pilot/lib/commands/settings.js +++ b/plugins/pilot/lib/commands/settings.js @@ -59,19 +59,15 @@ var setCommandSpec = { var html; if (!args.setting) { // 'set' by itself lists all the settings - var settingsList = env.settings._list(); + var names = env.settings.getSettingNames(); html = ''; // first sort the settingsList based on the name - settingsList.sort(function(a, b) { - if (a.name < b.name) { - return -1; - } else if (a.name == b.name) { - return 0; - } else { - return 1; - } + names.sort(function(name1, name2) { + return name1.localeCompare(name2); }); - settingsList.forEach(function(setting) { + + names.forEach(function(name) { + var setting = env.settings.getSetting(name); var url = 'https://wiki.mozilla.org/Labs/Skywriter/Settings#' + setting.name; html += '