aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Trevor Elliott <trevor@galois.com>2013-06-12 16:41:00 -0700
committerGravatar Trevor Elliott <trevor@galois.com>2013-06-12 16:41:00 -0700
commitc7b493dcc46ac800ace472a580d5430a1b0c2e41 (patch)
tree1ae2ea9ad7b84b07387d41c2b6ca6c4eccc4366a
parent0decef2093c676d91b0b590a9b94f201c4995716 (diff)
Group url patterns with rule set definitions
-rw-r--r--exampleData/ruleSets/colorRules/backgroundCheck.js8
-rw-r--r--exampleData/ruleSets/colorRules/foregroundCheck.js3
-rw-r--r--src/js/chrome/js/platform-ajax.js2
-rw-r--r--src/js/chrome/js/platform-background.js4
-rw-r--r--src/js/firefox/lib/main.js1
-rw-r--r--src/js/fiveui/build.mk3
-rw-r--r--src/js/fiveui/css/entry.css59
-rw-r--r--src/js/fiveui/css/options.css66
-rw-r--r--src/js/fiveui/injected/compute.js17
-rw-r--r--src/js/fiveui/js/background.js13
-rw-r--r--src/js/fiveui/js/entry.js300
-rw-r--r--src/js/fiveui/js/messenger.js28
-rw-r--r--src/js/fiveui/js/options.js310
-rw-r--r--src/js/fiveui/js/rules.js130
-rw-r--r--src/js/fiveui/js/settings.js283
-rw-r--r--src/js/fiveui/options.html17
16 files changed, 564 insertions, 680 deletions
diff --git a/exampleData/ruleSets/colorRules/backgroundCheck.js b/exampleData/ruleSets/colorRules/backgroundCheck.js
index 98df08c..d860410 100644
--- a/exampleData/ruleSets/colorRules/backgroundCheck.js
+++ b/exampleData/ruleSets/colorRules/backgroundCheck.js
@@ -7,12 +7,16 @@ exports.description =
].join('\n');
exports.rule = function() {
+ var rule = this;
+
+
var allow = '#00 #FF #3D #F7 #C2 #B4 #4E'.split(' ');
- this.report("broken");
+ rule.report("broken");
$5(':visible')
.cssIsNot('background-color', allow, fiveui.color.colorToHex)
.each(function(i, elt) {
var color = fiveui.color.colorToHex($(elt).css('background-color'));
- this.report('non-standard background color: ' + color, $(elt));
+ eonsole.log(rule);
+ rule.report('non-standard background color: ' + color, $(elt));
});
};
diff --git a/exampleData/ruleSets/colorRules/foregroundCheck.js b/exampleData/ruleSets/colorRules/foregroundCheck.js
index 2cf2b5f..9102bbe 100644
--- a/exampleData/ruleSets/colorRules/foregroundCheck.js
+++ b/exampleData/ruleSets/colorRules/foregroundCheck.js
@@ -6,11 +6,12 @@ exports.description =
].join('\n');
exports.rule = function() {
+ var rule = this;
var allow = '#00 #FF #3D #F7 #C2 #B4 #4E #FFCB05 #7B8738'.split(' ');
$5(':visible')
.cssIsNot('color', allow, fiveui.color.colorToHex)
.each(function(i, elt) {
var color = fiveui.color.colorToHex($(elt).css('color'));
- this.report('foreground color: ' + color, elt);
+ rule.report('foreground color: ' + color, elt);
});
};
diff --git a/src/js/chrome/js/platform-ajax.js b/src/js/chrome/js/platform-ajax.js
index 68ced39..32aac99 100644
--- a/src/js/chrome/js/platform-ajax.js
+++ b/src/js/chrome/js/platform-ajax.js
@@ -20,6 +20,8 @@ fiveui.ajax.get = function(url, options) {
jQuery.ajax(url, {
+ cache: false,
+
dataType: 'text',
success:function(text) {
diff --git a/src/js/chrome/js/platform-background.js b/src/js/chrome/js/platform-background.js
index 6ffecf8..0f92fb5 100644
--- a/src/js/chrome/js/platform-background.js
+++ b/src/js/chrome/js/platform-background.js
@@ -135,13 +135,15 @@ fiveui.chrome.background = function() {
chrome.tabs.onCreated.addListener(function(tab) {
- // console.log('in oncreated');
+ console.log('in oncreated');
if (tab.url) {
background.pageLoad(tab.id, tab.url);
}
});
+
// check page load events against the generic background
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+ console.log('in onupdated');
if (changeInfo.status == 'complete') {
background.pageLoad(tabId, tab.url);
}
diff --git a/src/js/firefox/lib/main.js b/src/js/firefox/lib/main.js
index 7679fd2..dfcacb1 100644
--- a/src/js/firefox/lib/main.js
+++ b/src/js/firefox/lib/main.js
@@ -197,7 +197,6 @@ fiveui.firefox.main = function() {
, data.url('js/options.js')
, data.url('js/update-manager.js')
, data.url('js/utils.js')
- , data.url('js/entry.js')
, data.url('js/rules.js')
, data.url('js/url-pat.js')
, data.url('js/platform-ajax.js')
diff --git a/src/js/fiveui/build.mk b/src/js/fiveui/build.mk
index 3cb1851..c9bbf7d 100644
--- a/src/js/fiveui/build.mk
+++ b/src/js/fiveui/build.mk
@@ -45,8 +45,7 @@ $2: $(patsubst $(fiveui-dir)/%,$1/data/%,$(wildcard $(fiveui-dir)/js/*))
$(call fiveui-files,$1/data,css)
-$2: $1/data/css/entry.css \
- $1/data/css/options.css
+$2: $1/data/css/options.css
$(call fiveui-files,$1/data,images)
diff --git a/src/js/fiveui/css/entry.css b/src/js/fiveui/css/entry.css
deleted file mode 100644
index 5d91b3b..0000000
--- a/src/js/fiveui/css/entry.css
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Module : entry.css
- * Copyright : (c) 2011-2012, Galois, Inc.
- *
- * Maintainer :
- * Stability : Provisional
- * Portability: Portable
- *
- * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-ul.entries {
- list-style: none;
- padding-left: 5px;
-}
-
-ul.entries li.entry {
- border-top: 1px solid #CDCDCD;
- margin-top: 5px;
- padding-top: 10px;
- padding-bottom: 10px;
-}
-
-li.entry .error {
- margin-top: 10px;
- padding: 10px;
- background-color: #FF4D4D;
- color: #ffffff;
-}
-
-ul.entries li.entry:first-child {
- border-top: 0px;
-}
-
-li.entry button {
- padding: 0px;
- font-size: 0.75em;
-}
-
-li.entry .editable {
- padding: 5px;
- border: 1px solid #CDCDCD;
-}
-
-li.entry .placeholder {
- font-style: italic;
- color: #CDCDCD;
-}
diff --git a/src/js/fiveui/css/options.css b/src/js/fiveui/css/options.css
index efa4986..65540e2 100644
--- a/src/js/fiveui/css/options.css
+++ b/src/js/fiveui/css/options.css
@@ -135,6 +135,68 @@ div.title {
float: left;
}
-.buttons {
- display: inline;
+.control .label {
+ color: #b2b2b2;
+ font-weight: italic;
+}
+
+.control button {
+ font-weight: normal;
+ font-size: 0.75em;
+}
+
+
+ul.entries {
+ list-style: none;
+ padding-left: 2px;
+}
+
+ul.entries li.entry {
+ border: 1px solid #CDCDCD;
+ margin-top: 5px;
+ padding: 10px;
+ background-color: #f0f0f0;
+}
+
+li.entry .error {
+ margin-top: 10px;
+ padding: 10px;
+ background-color: #FF4D4D;
+ color: #ffffff;
+}
+
+li.entry button {
+ padding: 0px;
+ font-size: 0.6em;
+}
+
+li.entry .editable {
+ padding: 5px;
+ border: 1px solid #CDCDCD;
+}
+
+li.entry .placeholder {
+ font-style: italic;
+ color: #CDCDCD;
+}
+
+
+
+
+li.entry div.pattern-control {
+ padding-top: 10px;
+}
+
+ul.patterns {
+ list-style: none;
+ padding: 0.5em;
+ padding-left: 0.5em;
+}
+
+ul.patterns li {
+ padding-top: 5px;
+}
+
+li.entry div.pattern-input {
+ margin: 2px;
}
diff --git a/src/js/fiveui/injected/compute.js b/src/js/fiveui/injected/compute.js
index 225c794..8efc8f7 100644
--- a/src/js/fiveui/injected/compute.js
+++ b/src/js/fiveui/injected/compute.js
@@ -60,7 +60,7 @@
var delta = new Date() - core.lastEvent;
if(delta > core.timeout && !core.maskRules) {
core.scheduled = false;
- core.evaluate(core.rules.rules);
+ core.evaluate(core.rules);
} else {
setTimeout(check, core.timeout);
}
@@ -291,25 +291,18 @@
var registerBackendListeners = function(port) {
port.on('SetRules', function(payload) {
- var rules = payload.rules;
+ core.rules = [];
- core.rules = payload;
- core.rules.rules = [];
-
- for(var i=0; i<rules.length; ++i) {
+ for(var i=0; i<payload.length; ++i) {
var moduleStr =
[ '(function(){'
, 'var exports = {};'
- , rules[i].module
+ , payload[i]
, 'return exports;'
, '})()'
].join('\n');
- core.rules.rules.push(eval(moduleStr));
- }
-
- if (null == core.rules) {
- debugger;
+ core.rules.push(eval(moduleStr));
}
core.scheduleRules();
diff --git a/src/js/fiveui/js/background.js b/src/js/fiveui/js/background.js
index c0d4e07..e662e95 100644
--- a/src/js/fiveui/js/background.js
+++ b/src/js/fiveui/js/background.js
@@ -113,13 +113,11 @@ fiveui.Background.prototype.connect = function(tabId, port, url, isUiPort) {
this._registerComputeListeners(port, tabState);
// get the rule set and send it down to the injected page:
- var pat = this.settings.checkUrl(url);
- if (!pat) {
+ var ruleSet = this.settings.checkUrl(url);
+ if (ruleSet == null) {
console.err('could not find url pattern for tab.url, but one was strongly expected');
} else {
- var ruleSet = this.settings.getRuleSet(pat.rule_id);
-
- port.emit('SetRules', ruleSet);
+ port.emit('SetRules', ruleSet.rules);
}
}
};
@@ -130,9 +128,9 @@ fiveui.Background.prototype.connect = function(tabId, port, url, isUiPort) {
* @param {*} data
*/
fiveui.Background.prototype.pageLoad = function(tabId, url, data) {
- var pat = this.settings.checkUrl(url);
+ var ruleSet = this.settings.checkUrl(url);
- if (null == pat) {
+ if (ruleSet == null) {
this.updateWidget(null);
} else {
var tabState = this.state.acquireTabState(tabId);
@@ -141,7 +139,6 @@ fiveui.Background.prototype.pageLoad = function(tabId, url, data) {
this.updateWidget(tabState);
var dependencies = [];
- var ruleSet = this.settings.getRuleSet(pat.rule_id);
if (ruleSet && ruleSet.dependencies ) {
dependencies = ruleSet.dependencies;
diff --git a/src/js/fiveui/js/entry.js b/src/js/fiveui/js/entry.js
deleted file mode 100644
index 5d460b4..0000000
--- a/src/js/fiveui/js/entry.js
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Module : entry.js
- * Copyright : (c) 2011-2012, Galois, Inc.
- *
- * Maintainer :
- * Stability : Provisional
- * Portability: Portable
- *
- * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-var fiveui = fiveui || {};
-
-(function() {
-
-var editable = function(el, model, placeholder) {
-
- el.prop('contenteditable', true).addClass('editable');
-
- // prevent newlines
- el.on('keypress', function(e) {
- return e.which != 13;
- });
-
- var addPlaceholder = function() {
- el.addClass('placeholder')
- .text(placeholder)
- .one('click keypress paste', remPlaceholder);
- };
-
- var remPlaceholder = function() {
- el.removeClass('placeholder').text('');
- };
-
- // if the model is new, set the placeholder, and a listener to clear it
- if(model.isNew()) {
- addPlaceholder();
- }
-
- el.on('blur', function() {
- if(_.isEmpty(el.text())) {
- addPlaceholder();
- }
- });
-
- el.focus();
-
-};
-
-var button = function(el, icon) {
- el.button({ icons: icon, text: false });
-};
-
-
-/** UrlPat Entry Elements ****************************************************/
-
-fiveui.UrlPatEntry = Backbone.View.extend({
-
- tagName: 'li',
-
- className: 'entry',
-
- events: {
- 'click .save' : 'save',
- 'click .remove' : 'remove',
- 'click .edit' : 'edit',
- },
-
- initialize:function() {
- this.listenTo(this.model, 'remove', function() {
- this.$el.remove();
- this.stopListening();
- });
- },
-
- viewTemplate: _.template(
- [ '<div>'
- , ' <button class="remove">x</button>'
- , ' <button class="edit">edit</button>'
- , ' <span><%= regex %></span>'
- , ' <span><%= rule_name %></span>'
- , '</div>'
- ].join('')),
-
- render:function() {
-
- var attrs = _.clone(this.model.attributes);
- var ruleSet = this.options.rules.model.findWhere({ id: attrs.rule_id })
- attrs.rule_name = ruleSet.get('name');
- this.$el.html(this.viewTemplate(attrs));
-
- // setup buttons
- button(this.$el.find('.remove'), { primary: 'ui-icon-close' });
- button(this.$el.find('.edit'), { primary: 'ui-icon-pencil' });
-
- return this;
- },
-
- editTemplate: _.template(
- [ '<div>'
- , ' <button class="remove">x</button>'
- , ' <button class="save">save</button>'
- , ' <span class="regex"></span>'
- , ' <span class="rules"></span>'
- , '</div>'
- ].join('')),
-
- edit:function() {
- var attrs = this.model.attributes;
- this.$el.html(this.editTemplate(attrs));
-
- this.$el.find('.rules').append(this.options.rules.render().$el);
- editable(this.$el.find('.regex'), this.model, 'url pattern');
-
- button(this.$el.find('.remove'), { primary: 'ui-icon-close' });
- button(this.$el.find('.save'), { primary: 'ui-icon-disk' });
-
- return this;
- },
-
- remove: function() {
- this.model.destroy();
- },
-
- save:function() {
- var regex = this.$el.find('.regex').text();
- var rule_id = parseInt(this.options.rules.$el.val());
- this.model.save({ regex : regex, rule_id : rule_id }, {
- success: _.bind(this.render, this),
- error: _.bind(this.edit, this)
- });
- },
-
-});
-
-
-/** Rule Set View ************************************************************/
-
-fiveui.RulesView = Backbone.View.extend({
-
- tagName: 'select',
-
- initialize:function() {
- this.listenTo(this.model, 'sync', this.update);
- this.listenTo(this.model, 'remove', this.update);
- },
-
- optionTemplate:_.template(
- '<option value="<%= id %>"><%= name %></option>'
- ),
-
- update:function() {
- if(this.model.length == 0) {
- return this.remove();
- } else {
- return this.render();
- }
- },
-
- remove:function() {
- this.stopListening();
- this.$el.remove();
-
- this.trigger('remove');
-
- return this;
- },
-
- render:function() {
-
- var scope = this;
-
- this.$el.children().remove();
-
- var text = this.model.foldl(function(body,ruleSet) {
- return body + scope.optionTemplate(ruleSet.attributes);
- }, '');
-
- this.$el.html(text);
-
- return this;
- },
-
-});
-
-
-/** Rule Entry Elements ******************************************************/
-
-fiveui.RuleSetEntry = Backbone.View.extend({
-
- tagName: 'li',
-
- className: 'entry',
-
- events: {
- 'click .save' : 'save',
- 'click .remove' : 'remove',
- 'click .edit' : 'edit',
- 'click .reload' : 'reload',
- },
-
- viewTemplate: _.template(
- [ '<div class="content">'
- , ' <button class="button remove">remove</button>'
- , ' <button class="edit">edit</button>'
- , ' <button class="reload">reload</button>'
- , ' <span class="title"><%= name %></span>'
- , '</div>'
- ].join('')),
-
- render:function() {
-
- var attrs = this.model.attributes;
- this.$el.html(this.viewTemplate(attrs));
-
- button(this.$el.find('.remove'), { primary: 'ui-icon-close' });
- button(this.$el.find('.edit'), { primary: 'ui-icon-pencil' });
- button(this.$el.find('.reload'), { primary: 'ui-icon-refresh' });
-
- return this;
- },
-
- editTemplate: _.template(
- [ '<div class="content">'
- , ' <button class="remove">x</button>'
- , ' <button class="save">save</button>'
- , ' <span class="source"><%= source %></span>'
- , '</div>'
- ].join('')),
-
- edit:function() {
- var attrs = this.model.attributes;
- this.$el.html(this.editTemplate(attrs));
-
- button(this.$el.find('.remove'), { primary: 'ui-icon-close' });
- button(this.$el.find('.save'), { primary: 'ui-icon-disk' });
-
- editable(this.$el.find('.source'), this.model,
- 'http://example.com/manifest.json')
-
- return this;
- },
-
- errorTemplate: _.template('<div class="error"><%= message %></div>'),
-
- editError:function(target, message) {
- this.edit();
-
- this.$el.append(this.errorTemplate({ message: message }));
-
- return this;
- },
-
- save: function() {
- var source = this.$el.find('.source').text();
- this.model.set('source', source);
- this.model.save({}, {
- success: _.bind(this.render, this),
- error: _.bind(this.editError, this)
- });
- },
-
- reload:function() {
- this.model.save({}, {
- success: _.bind(this.render, this),
- error: _.bind(this.edit, this)
- });
- },
-
- remove:function() {
- var self = this;
-
- this.model.destroy({
- wait:true,
-
- success:function() {
- self.$el.remove();
- self.stopListening();
- },
-
- error:function() {
- console.log('communicate failure somehow...');
- }
- });
- },
-
-});
-
-})();
diff --git a/src/js/fiveui/js/messenger.js b/src/js/fiveui/js/messenger.js
index fc1962c..fd1fb5b 100644
--- a/src/js/fiveui/js/messenger.js
+++ b/src/js/fiveui/js/messenger.js
@@ -127,14 +127,28 @@ _.extend(fiveui.Messenger.prototype, {
*/
fiveui.Messenger.Payload = function(isCallback, type, data, id) {
this.isCallback = isCallback;
- this.type = type;
- this.id = id;
+ this.type = type;
+ this.id = id;
+ this.rawData = null;
+
this.__defineGetter__('data', function() {
- return JSON.parse(this.rawData);
- });
- this.__defineSetter__('data', function(obj){
- this.rawData = JSON.stringify(obj);
- });
+ if(_.isNull(this.rawData)) {
+ return null;
+ } else {
+ return JSON.parse(this.rawData);
+ }
+ });
+
+ this.__defineSetter__('data', function(obj) {
+ if(_.isUndefined(obj) || _.isNull(obj)) {
+ this.rawData = null;
+ } else {
+ this.rawData = JSON.stringify(obj);
+ }
+ });
+
+
+ // use the setter defined above
this.data = data;
};
diff --git a/src/js/fiveui/js/options.js b/src/js/fiveui/js/options.js
index 2b29f8d..598649f 100644
--- a/src/js/fiveui/js/options.js
+++ b/src/js/fiveui/js/options.js
@@ -113,9 +113,13 @@ fiveui.options.init = function(port) {
var ruleSetEntries = jQuery('#ruleSetEntries');
// handle clicks to the 'add' button on the rule sets page
- jQuery('#addRsButton').on('click', function() {
- ruleSets.add(new fiveui.RuleSetModel({}, { url : msg }));
- });
+ jQuery('#addRsButton')
+ .button({
+ icons: { primary: 'ui-icon-plus' },
+ })
+ .on('click', function() {
+ ruleSets.add(new fiveui.RuleSetModel({}, { url : msg }));
+ });
// render a ruleset added to the collection
ruleSets.on('add', function(model) {
@@ -191,13 +195,12 @@ fiveui.options.init = function(port) {
};
// listen to click events on navigation elements
- setClickHandler(jQuery('#url-defaults'), select('#tab-url-defaults'));
setClickHandler(jQuery('#rule-sets'), select('#tab-rule-sets'));
setClickHandler(jQuery('#basics'), select('#tab-basics'));
// select the url patterns tab by default
- selectNav(jQuery('#url-defaults'));
- selectSection(jQuery('#tab-url-defaults'));
+ selectNav(jQuery('#rule-sets'));
+ selectSection(jQuery('#tab-rule-sets'));
/** Pre-populate UI elements ***********************************************/
@@ -214,4 +217,299 @@ fiveui.options.init = function(port) {
});
};
+
+ /** Rule-Set Views *********************************************************/
+
+var editable = function(el, placeholder, onEnter) {
+
+ el.prop('contenteditable', true).addClass('editable');
+
+ // prevent newlines
+ if(onEnter) {
+ el.on('keypress', function(e) {
+ if(e.which == 13) {
+ onEnter();
+ return false;
+ } else {
+ return true;
+ }
+ });
+ } else {
+ el.on('keypress', function(e) {
+ return e.which != 13;
+ });
+ }
+
+ var addPlaceholder = function() {
+ el.addClass('placeholder')
+ .text(placeholder)
+ .one('click keypress paste', remPlaceholder);
+ };
+
+ var remPlaceholder = function() {
+ el.removeClass('placeholder').text('');
+ };
+
+ // if the model is new, set the placeholder, and a listener to clear it
+ if(el.text() == '') {
+ addPlaceholder();
+ }
+
+ el.on('blur', function() {
+ if(el.text() == '') {
+ addPlaceholder();
+ }
+ });
+
+ el.focus();
+
+};
+
+var button = function(el, icon) {
+ el.button({ icons: icon, text: false });
+};
+
+
+/** Rule Set View ************************************************************/
+
+fiveui.RulesView = Backbone.View.extend({
+
+ tagName: 'select',
+
+ initialize:function() {
+ this.listenTo(this.model, 'sync', this.update);
+ this.listenTo(this.model, 'remove', this.update);
+ },
+
+ optionTemplate:_.template(
+ '<option value="<%= id %>"><%= name %></option>'
+ ),
+
+ update:function() {
+ if(this.model.length == 0) {
+ return this.remove();
+ } else {
+ return this.render();
+ }
+ },
+
+ remove:function() {
+ this.stopListening();
+ this.$el.remove();
+
+ this.trigger('remove');
+
+ return this;
+ },
+
+ render:function() {
+
+ var scope = this;
+
+ this.$el.children().remove();
+
+ var text = this.model.foldl(function(body,ruleSet) {
+ return body + scope.optionTemplate(ruleSet.attributes);
+ }, '');
+
+ this.$el.html(text);
+
+ return this;
+ },
+
+});
+
+
+/** Rule Entry Elements ******************************************************/
+
+fiveui.RuleSetEntry = Backbone.View.extend({
+
+ tagName: 'li',
+
+ className: 'entry',
+
+ // setup the skeleton for the rule set editor.
+ initialize:function() {
+ this.$el.html(
+ [ '<div class="rule-set">'
+ , '</div>'
+ , '<ul class="patterns"></ul>'
+ , '<div class="pattern-control">'
+ , ' <div class="pattern-input"></div>'
+ , ' <button class="add-pattern">add url pattern</button'
+ , '</div>'
+ ].join(''));
+
+ this.$rs = this.$el.find('.rule-set');
+ this.$pat = this.$el.find('.patterns');
+ this.$urlpat = this.$el.find('.pattern-input');
+ this.$addpat = this.$el.find('.add-pattern');
+
+ // setup the url pattern editor
+ this.$addpat.button({ icons: { primary: 'ui-icon-plus' } });
+ editable(this.$urlpat, 'http://example.com/*',
+ _.bind(this.$addpat.click, this.$addpat))
+ },
+
+ events: {
+ 'click .save' : 'save',
+ 'click .remove' : 'remove',
+ 'click .edit' : 'edit',
+ 'click .reload' : 'reload',
+ 'click .add-pattern' : 'addPattern',
+ },
+
+ viewRsTemplate: _.template(
+ [ '<button class="remove">remove</button>'
+ , '<button class="edit">edit</button>'
+ , '<button class="reload">reload</button>'
+ , '<span class="title"><%= name %></span>'
+ ].join('')),
+
+ // render the rule set as its title, with some buttons to edit/remove/reload
+ // it.
+ render:function() {
+
+ // render the rule set
+ var attrs = _.clone(this.model.attributes);
+ this.$rs.html(this.viewRsTemplate(attrs));
+
+ button(this.$rs.find('.edit'), { primary: 'ui-icon-pencil' });
+ button(this.$rs.find('.reload'), { primary: 'ui-icon-refresh' });
+ button(this.$rs.find('.remove'), { primary: 'ui-icon-close' });
+
+ this.renderPats(this.model.get('patterns'));
+
+ this.$addpat.prop('disabled', false);
+
+ return this;
+ },
+
+ editTemplate: _.template(
+ [ '<button class="remove">x</button>'
+ , '<button class="save">save</button>'
+ , '<span class="source"><%= source %></span>'
+ ].join('')),
+
+ // rework the rule set display area to a single input field for the url, and a
+ // remove and save button.
+ edit:function() {
+ var attrs = this.model.attributes;
+ this.$rs.html(this.editTemplate(attrs));
+
+ button(this.$rs.find('.remove'), { primary: 'ui-icon-close' });
+
+ var save = this.$rs.find('.save');
+ button(save, { primary: 'ui-icon-disk' });
+
+ this.$addpat.prop('disabled', true);
+
+ editable(this.$rs.find('.source'), 'http://example.com/manifest.json',
+ _.bind(save.click, save));
+
+ return this;
+ },
+
+ errorTemplate: _.template('<div class="error"><%= message %></div>'),
+
+ // render an error message below the edit ui for the result sets, but before
+ // any url patterns.
+ editError:function(target, message) {
+ this.edit();
+ this.$rs.append(this.errorTemplate({ message: message }));
+
+ return this;
+ },
+
+ // save the current model, falling back on the editor dialog when errors show
+ // up. it's assumed that this is only called from the editor dialog.
+ save: function() {
+ var source = this.$el.find('.source').text();
+ this.model.set('source', source);
+ this.model.save({}, {
+ success: _.bind(this.render, this),
+ error: _.bind(this.editError, this)
+ });
+ },
+
+ // reaload the model, and render. on failure, display the edit dialog with a
+ // message.
+ reload:function() {
+ this.model.save({}, {
+ success: _.bind(this.render, this),
+ error: _.bind(this.editError, this)
+ });
+ },
+
+ // remove the model, and remove the element from the list.
+ remove:function() {
+ this.model.destroy();
+ this.$el.remove();
+ this.stopListening();
+ },
+
+
+
+ viewPatTemplate: _.template(
+ [ '<li>'
+ , ' <button class="remove-pat">remove</button>'
+ , ' <span class="pattern"><%= pattern %></span>'
+ , '</li>'
+ ].join('')),
+
+ addUrlPat:function(pat) {
+ var el = jQuery(this.viewPatTemplate({ pattern: pat }));
+
+ var remove = el.find('.remove-pat');
+
+ button(remove, { primary: 'ui-icon-close' });
+ remove.on('click', _.bind(this.removePattern, this, pat));
+
+ this.$pat.append(el);
+ },
+
+ // render the url patterns of the rule set into this.$pat.
+ renderPats:function(pats) {
+ this.$pat.children().remove();
+
+ _.each(pats, _.bind(this.addUrlPat, this))
+ return this;
+ },
+
+ // add a pattern to the underlying collection of patterns
+ addPattern:function() {
+ var pat = this.$urlpat.text();
+ var pats = this.model.get('patterns');
+
+ pats.push(pat);
+
+ this.model.save({ patterns : pats }, {
+ wait: true,
+ patch: true,
+ success: _.bind(function() {
+ this.$urlpat.text('').blur();
+ this.render();
+ }, this),
+ error: function(msg) {
+ debugger;
+ },
+ });
+ },
+
+ removePattern:function(pat) {
+ var pats = _.filter(this.model.get('patterns'), function(p) {
+ return p != pat;
+ });
+
+ this.model.save({ patterns: pats }, {
+ wait: true,
+ patch: true,
+ success:_.bind(this.render, this),
+ // XXX make this report an actual error
+ error: _.bind(this.render, this)
+ });
+ },
+
+});
+
})();
diff --git a/src/js/fiveui/js/rules.js b/src/js/fiveui/js/rules.js
index 314d50b..7bd2a10 100644
--- a/src/js/fiveui/js/rules.js
+++ b/src/js/fiveui/js/rules.js
@@ -25,45 +25,29 @@ var fiveui = fiveui || {};
/**
* @constructor
- * @param {!string} module A Javascript module that defines the rule.
+ * @param {!number} config Initializers for the rule set structure.
*/
-fiveui.Rule = function(module) {
- this.module = module;
+fiveui.RuleSet = function(config) {
+ // fill in fields
+ _.defaults(this, fiveui.RuleSet.sanitize(config));
};
-fiveui.Rule.defaults = function(obj) {
- return _.defaults(obj, {
- module: '',
- });
-};
+fiveui.RuleSet.sanitize = function(obj) {
+ var defs = {
+ id: null,
+ name: '',
+ description: '',
+ source: '',
+ rules: [],
+ patterns: [],
+ dependencies: [],
+ };
-/**
- * Create a Rule from a JSON object.
- *
- * @param {!Object} obj The object to take settings from.
- * @return {!fiveui.Rule} A populated Rule object.
- */
-fiveui.Rule.fromJSON = function(obj) {
- return new fiveui.Rule(obj.module);
+ // scrub out any values that aren't in the defaults list, fill in any that are
+ // missing.
+ return _.defaults(_.pick(obj, _.keys(defs)), defs);
};
-/**
- * @constructor
- * @param {!number} id The unique RuleSet identifier.
- * @param {!string} name A human-readable name for this RuleSet.
- * @param {!string} desc A human-readable description of the Rule Set.
- * @param {!string} source The url where the manifest can be retrieved
- * @param {!Array.<fiveui.Rule>} rules An Array of Rules.
- * @param {?Array.<string>} deps Dependencies that this RuleSet requires.
- */
-fiveui.RuleSet = function(id, name, desc, source, rules, deps) {
- this.id = id;
- this.name = name;
- this.description = desc;
- this.source = source;
- this.rules = rules || [];
- this.dependencies = deps || [];
-};
/**
* Create a Rule Setfrom a JSON object.
@@ -73,21 +57,9 @@ fiveui.RuleSet = function(id, name, desc, source, rules, deps) {
* @return {!fiveui.RuleSet} A populated RuleSet object.
*/
fiveui.RuleSet.fromJSON = function(id, obj) {
- var rules = (/** @type {!Array.<!fiveui.Rule>} */
- _.map(obj.rules, fiveui.Rule.fromJSON));
-
- return new fiveui.RuleSet(id, obj.name, obj.description, obj.source,
- rules, obj.dependencies);
-};
-
-
-fiveui.RuleSet.defaults = function(obj) {
- return _.defaults(obj, {
- name: '',
- description: '',
- rules: [],
- dependencies: []
- });
+ // make sure to override any id value passed in.
+ obj.id = id;
+ return new fiveui.RuleSet(obj);
};
@@ -115,13 +87,13 @@ fiveui.RuleSet.load = function(manifest_url, options) {
// XXX there's likely problems here, how should we make sure that the
// url is what we expect?
- var rule_file = fiveui.Rule.defaults(rules.pop());
+ var rule_file = rules.pop();
var rule_url = base_url + '/' + rule_file;
fiveui.ajax.get(rule_url, {
success: function(text) {
- manifest.rules.push(new fiveui.Rule(text));
+ manifest.rules.push(text);
loadRules(manifest, rules);
},
@@ -135,21 +107,27 @@ fiveui.RuleSet.load = function(manifest_url, options) {
fiveui.ajax.get(manifest_url, {
success: function(text) {
- try {
- var sanitized = fiveui.utils.filterJSON(text,'json');
- var manifest = JSON.parse(sanitized);
+ // cleanup the parsed JSON object
+ var sanitized = fiveui.utils.filterJSON(text,'json');
+ var obj = null;
+ try {
+ obj = JSON.parse(sanitized);
} catch(e) {
- // XXX incoming error continuation is empty
- // (and we may have syntax error details in e)
options.error('failed to parse manifest');
return;
}
- fiveui.RuleSet.defaults(manifest);
+ var manifest = fiveui.RuleSet.sanitize(obj);
+
- var rules = manifest.rules;
- manifest.rules = [];
+ // explicitly zero out the patterns, they shouldn't be part of the
+ // manifest.
+ manifest.patterns = [];
+
+ var rules = manifest.rules;
+ manifest.rules = [];
+ manifest.source = manifest_url;
loadRules(manifest, rules);
},
@@ -182,6 +160,7 @@ fiveui.RuleSetModel = Backbone.Model.extend({
source: '',
rules: [],
dependencies: [],
+ patterns: [],
},
sync: function(method, model, options) {
@@ -191,20 +170,34 @@ fiveui.RuleSetModel = Backbone.Model.extend({
error: function() {}
});
+ var attrs = _.clone(model.attributes);
var msg = this.url;
- var id = model.get('id');
- var source = model.get('source');
switch(method) {
+ // the patched fields are in options.attrs
+ case 'patch':
+
+ var patch = options.attrs;
+
+ // at the moment, we only support patching the patterns
+ if(!_.isEmpty(_.difference(_.keys(patch),['patterns']))) {
+ options.error('unable to patch more than the patterns field');
+ } else {
+ attrs.patterns = patch.patterns;
+ msg.send('updateRuleSet', attrs, options.success);
+ }
+
+ break;
+
case 'update':
case 'create':
var rsMethod = method == 'update' ? 'updateRuleSet' : 'addRuleSet';
- msg.send('loadRuleSet', source, function(obj) {
+ msg.send('loadRuleSet', attrs.source, function(obj) {
if(!obj.error) {
- obj.id = id;
- obj.source = source;
+ obj.id = attrs.id;
+ obj.patterns = attrs.patterns;
msg.send(rsMethod, obj, options.success);
} else {
@@ -214,17 +207,11 @@ fiveui.RuleSetModel = Backbone.Model.extend({
break;
case 'delete':
- msg.send('remRuleSet', id, function(obj) {
- if(obj.removed) {
- options.success();
- } else {
- options.error();
- }
- });
+ msg.send('remRuleSet', attrs.id, options.success);
break;
case 'read':
- msg.send('getRuleSet', id, function(rs) {
+ msg.send('getRuleSet', attrs.id, function(rs) {
model.set({
title: rs.name,
descr: rs.description,
@@ -251,6 +238,7 @@ fiveui.RuleSetModel = Backbone.Model.extend({
rules: ruleSet.rules,
dependencies:ruleSet.dependencies,
source: ruleSet.source,
+ patterns: ruleSet.patterns,
}, { url : msg });
},
diff --git a/src/js/fiveui/js/settings.js b/src/js/fiveui/js/settings.js
index a17a9e0..96ec864 100644
--- a/src/js/fiveui/js/settings.js
+++ b/src/js/fiveui/js/settings.js
@@ -70,6 +70,43 @@ _.extend(fiveui.Settings.prototype, {
},
/**
+ * @param {!number} id The nuber of the element to retrieve.
+ * @param {!string} listName The name of the portion of the localstorage hierarchy to search for id.
+ * @param {!function(number, !Object): *} fromJSON A deserialization function.
+ *
+ * @return {*} Either null, or the result of fromJSON.
+ */
+ getById: function(id, listName, fromJSON) {
+ var obj = this.get(listName + '.' + id);
+ if (!obj) {
+ return null;
+ }
+
+ return fromJSON(id, obj);
+ },
+
+ /**
+ * @param {!number} id The nuber of the element to retrieve.
+ * @param {!string} listName The name of the portion of the
+ * localstorage hierarchy to search for id.
+ */
+ remById: function(id, listName) {
+ // remove it from the list of ids:
+ var ids = this.get(listName) || [];
+
+ for (var i = 0; i < ids.length; ++i) {
+ if (ids[i] == id) {
+ ids.splice(i, 1);
+ this.set(listName, ids);
+ this.remove(listName + '.' + id);
+ break;
+ }
+ }
+ },
+
+ /** General Config **********************************************************/
+
+ /**
* Set the display default.
*
* @param {!boolean} def Whether or not to display the FiveUI Window
@@ -92,106 +129,39 @@ _.extend(fiveui.Settings.prototype, {
return !!def;
},
- /**
- * Add a mapping from url patterns to RuleSets (via rule set ids)
- *
- * @param {!string} url_pat A regular expression (actually, a glob) to match URLs against.
- * @param {!number} rule_id The id of the RuleSet to use with any matching URL.
- * @return {!number} The id of the new url pattern.
- */
- addUrl: function(url_pat, rule_id) {
- var pats = this.getUrls();
-
- var new_id = fiveui.utils.getNewId(pats);
-
- this.updateUrl(new_id, url_pat, rule_id);
-
- // add it to the patterns list
- pats.push(new_id);
- this.set('urls', pats);
-
- return new_id;
- },
-
- updateUrl: function(id, url_pat, rule_id) {
- this.set('urls.' + id, new fiveui.UrlPat(id, url_pat, rule_id));
- return id;
- },
-
- /**
- * Retrieve the list of url patterns.
- *
- * @return {Array.<number>} An ordered list of the currently active
- * url patterns.
- */
- getUrls: function() {
- return (/** @type {Array.<number>} */ this.get('urls')) || [];
- },
- /**
- * Retrieve a specific url pattern.
- *
- * @param {!number} url_id The id of the url pattern to retrieve.
- * @return {?fiveui.UrlPat} The matching UrlPat or null,
- * if no pattern exists for url_id.
- */
- getUrlPat: function(url_id) {
- return /** @type {?fiveui.UrlPat} */ this.getById(url_id, 'urls', fiveui.UrlPat.fromJSON);
- },
+ /** Rule Sets ***************************************************************/
/**
- * Remove a UrlPat from the persistent storage.
+ * Retrieve the list of rule set ids.
*
- * @param {!number} pat_id The id of the UrlPat to remove.
- * @return {void}
- */
- remUrlPat: function(pat_id) {
- this.remById(pat_id, 'urls');
- },
-
- /**
- * @param {!string} url A url to compare against the list of ordered
- * url patterns in local storage.
- * @return {?fiveui.UrlPat} The matching pattern, or null, if no
- * mapping was found.
+ * @return {!Array.<number>} An ordered list of the configured rule sets.
*/
- checkUrl: function(url) {
- var pats = this.getUrls();
-
- // check for a possible match
- for (var i = 0; i < pats.length; ++i) {
- var pat = this.getUrlPat(pats[i]);
- if (pat.match(url)) {
- return pat;
- }
- }
-
- return null;
+ getRuleSetIds: function() {
+ return (/** @type {!Array.<number>} */ this.get('ruleSet')) || [];
},
/**
- * Retrieve the list of rule sets.
- *
- * @return {!Array.<number>} An ordered list of the configured rule sets.
+ * Retrieve all rule set ids.
*/
- getRuleSets: function() {
- return (/** @type {!Array.<number>} */ this.get('ruleSet')) || [];
+ getRuleSets:function() {
+ return _.map(this.getRuleSetIds(), _.bind(this.getRuleSet, this));
},
-
/**
* @param {!Object} ruleSet The new rule set, as an anonymous JSON object.
- * @return {!fiveui.RuleSet} The new RuleSet object.
+ * @return {!fiveui.RuleSet} The id of the new rule set.
*/
addRuleSet: function(ruleSet) {
- var ids = this.getRuleSets();
- var id = fiveui.utils.getNewId(ids);
+ var ids = this.getRuleSetIds();
+ var id = fiveui.utils.getNewId(ids);
+
+ this.updateRuleSet(id, ruleSet);
- var newRS = this.updateRuleSet(id, ruleSet);
ids.push(id);
this.set('ruleSet', ids);
- return newRS;
+ return id;
},
/**
@@ -199,12 +169,10 @@ _.extend(fiveui.Settings.prototype, {
*
* @param {!number} ruleSetId The id of the ruleset that is being modified.
* @param {!Object} ruleSet The rule set, as an anonymous JSON object.
- * @return {!fiveui.RuleSet} The new RuleSet object.
*/
updateRuleSet: function(ruleSetId, ruleSet) {
var newRS = fiveui.RuleSet.fromJSON(ruleSetId, ruleSet);
this.set('ruleSet.'+ruleSetId, newRS);
- return newRS;
},
/**
@@ -212,79 +180,35 @@ _.extend(fiveui.Settings.prototype, {
* @return {?fiveui.RuleSet} The RuleSet, or null, if no RuleSet was found.
*/
getRuleSet: function(id) {
- return /** @type {?fiveui.RuleSet} */ this.getById(id, 'ruleSet', fiveui.RuleSet.fromJSON);
+ return this.getById(id, 'ruleSet', fiveui.RuleSet.fromJSON);
},
/**
* @param {!number} id The id of the rule set to remove.
- * @return {!Array.<fiveui.UrlPat>} null if the remove succeeded, otherwise,
- * returns the list of UrlPats that use this
- * rule set, if any.
*/
remRuleSet: function(id) {
- var matches = _.map(this.getRuleSetUrlPats(id), function(id) {
- return this.getUrlPat(id);
- }, this);
-
- if (0 == matches.length) {
- this.remById(id, 'ruleSet');
- }
-
- return matches;
+ this.remById(id, 'ruleSet');
},
+ /** URL Pattern Management **************************************************/
+
/**
- * @param {!number} ruleSetId The rule set to retrieve url patterns for.
- * @return {Array.<number>} Url pattern ids associated with this rule set.
+ * Test a url agains the rule set database. Return the first rule set that
+ * matches, or null if none do.
*/
- getRuleSetUrlPats: function(ruleSetId) {
- var urls = this.getUrls();
- var patIds = [];
-
- _.each(urls, function(patId) {
- var pat = this.getUrlPat(patId);
- if(pat.rule_id == ruleSetId) {
- patIds.push(patId);
- }
- }, this);
+ checkUrl: function(url) {
+ return _.find(this.getRuleSets(), function(rs) {
- return patIds;
- },
+ var pat = _.find(rs.patterns, function(pat) {
+ var regex = fiveui.UrlPat.compile(pat);
+ return regex.test(url);
+ });
- /**
- * @param {!number} id The nuber of the element to retrieve.
- * @param {!string} listName The name of the portion of the localstorage hierarchy to search for id.
- * @param {!function(number, !Object): *} fromJSON A deserialization function.
- *
- * @return {*} Either null, or the result of fromJSON.
- */
- getById: function(id, listName, fromJSON) {
- var obj = this.get(listName + '.' + id);
- if (!obj) {
- return null;
- }
+ return pat != null;
- return fromJSON(id, obj);
+ });
},
- /**
- * @param {!number} id The nuber of the element to retrieve.
- * @param {!string} listName The name of the portion of the
- * localstorage hierarchy to search for id.
- */
- remById: function(id, listName) {
- // remove it from the list of ids:
- var ids = this.get(listName) || [];
-
- for (var i = 0; i < ids.length; ++i) {
- if (ids[i] == id) {
- ids.splice(i, 1);
- this.set(listName, ids);
- this.remove(listName + '.' + id);
- break;
- }
- }
- }
});
@@ -297,41 +221,29 @@ fiveui.Settings.manager = function(chan, settings) {
var msg = new fiveui.Messenger(chan);
+ // create a new rule set, and call the response continuation with the created
+ // object.
msg.register('addRuleSet', function(ruleSet,respond){
- var newRS = settings.addRuleSet(ruleSet);
- respond(newRS);
+ var id = settings.addRuleSet(ruleSet)
+ respond(settings.getRuleSet(id));
});
+ // update a rule set, and call the response continuation with the updated
+ // object.
msg.register('updateRuleSet', function(updatedRS,respond){
- var newRS = settings.updateRuleSet(updatedRS.id, updatedRS);
- respond(newRS);
+ settings.updateRuleSet(updatedRS.id, updatedRS);
+ respond(settings.getRuleSet(updatedRS.id));
});
+ // remove a rule set by id. the response continuation is called with no
+ // argument.
msg.register('remRuleSet', function(ruleSetId, respond) {
- var pats = settings.remRuleSet(ruleSetId);
- respond({
- id: ruleSetId,
- pats: pats,
- removed: pats.length == 0
- });
- });
-
- msg.register('getRuleSetUrlPats', function(ruleSetId, respond) {
- var pats = settings.getUrls();
- var patIds = [];
-
- _.each(pats, function(patId) {
- var pat = settings.getUrlPat(patId);
- if(pat.rule_id == ruleSetId) {
- patIds.push(patId);
- }
- });
-
- respond(patIds);
+ settings.remRuleSet(ruleSetId);
+ respond();
});
// Retrieve the manifest, and return the object to the caller. Invokes the
- // caller with `null` when the manifest fails to load.
+ // response continuation with an error object when rule set fails to load.
msg.register('loadRuleSet', function(url, respond) {
fiveui.RuleSet.load(url, {
success:respond,
@@ -342,44 +254,27 @@ fiveui.Settings.manager = function(chan, settings) {
});
});
+ // get a rule set structure by id. invoke the respond continuation with the
+ // rule set, if it exists, and null if it does not.
msg.register('getRuleSet', function(ruleSetId, respond){
respond(settings.getRuleSet(ruleSetId));
});
+ // Retrieve the list of all rule sets. invoke the respond continuation with
+ // the list of rule sets.
msg.register('getRuleSets', function(unused, respond) {
- var ruleSets = _.map(settings.getRuleSets(),
- _.bind(settings.getRuleSet, settings));
- respond(ruleSets);
- });
-
- msg.register('getUrlPats', function(unused, respond){
- respond(_.map(settings.getUrls(), _.bind(settings.getUrlPat, settings)));
- });
-
- msg.register('addUrlPat', function(url, respond){
- var urlId = settings.addUrl(url.regex, url.rule_id);
- respond(settings.getUrlPat(urlId));
- });
-
- msg.register('updateUrlPat', function(pat, respond) {
- var obj = settings.getUrlPat(pat.id);
- settings.updateUrl(pat.id, pat.regex, pat.rule_id);
- respond(pat);
- });
-
- msg.register('getUrlPat', function(urlPatId, respond){
- respond(settings.getUrlPat(urlPatId));
- });
-
- msg.register('remUrlPat', function(urlPatId, respond){
- settings.remUrlPat(urlPatId);
- respond(true);
+ respond(settings.getRuleSets());
});
- msg.register('setDisplayDefault', function(def) {
+ // sets the value of the 'display default' config option. invokes the respond
+ // callback with no argument.
+ msg.register('setDisplayDefault', function(def, respond) {
settings.setDisplayDefault(def);
+ respond();
});
+ // get the value of the 'display default' config option. invokes the respond
+ // callback with the value.
msg.register('getDisplayDefault', function(ignored, respond) {
respond(settings.getDisplayDefault());
});
diff --git a/src/js/fiveui/options.html b/src/js/fiveui/options.html
index 3864a36..0115368 100644
--- a/src/js/fiveui/options.html
+++ b/src/js/fiveui/options.html
@@ -27,7 +27,6 @@
<link id="favicon" rel="icon" href="images/fiveui-icon-16.png" />
<link rel="stylesheet" href="css/options.css" />
- <link rel="stylesheet" href="css/entry.css" />
<link rel="stylesheet" href="jquery/bundled.css" />
<script src="jquery/jquery-1.8.3.js"></script>
<script src="jquery/jquery-ui-1.9.2.custom.js"></script>
@@ -39,7 +38,6 @@
<script src="js/options.js"></script>
<script src="js/update-manager.js"></script>
<script src="js/utils.js"></script>
- <script src="js/entry.js"></script>
<script src="js/rules.js"></script>
<script src="js/url-pat.js"></script>
<script src="js/platform-ajax.js"></script>
@@ -52,29 +50,19 @@
<div id="navbar-container">
<div id="navbar-content-title">Settings</div>
<nav>
- <div id="url-defaults">URL Patterns</div>
<div id="rule-sets">Rule Sets</div>
<div id="basics">Basics</div>
</nav>
</div>
<div id="content">
- <section id="tab-url-defaults">
- <div class="title">URL Patterns</div>
- <section>
- <ul id="urlPatEntries" class="entries"></ul>
- <div>
- <button id="addUrlPat">Add</button>
- </div>
- </section>
- </section>
<section id="tab-rule-sets">
<div class="title">Rule Sets</div>
<section>
<ul id="ruleSetEntries" class="entries"></ul>
- <div>
- <button id="addRsButton">Add</button>
+ <div class="control">
+ <button id="addRsButton">add a rule set</button>
</div>
</section>
</section>
@@ -93,6 +81,7 @@
</div>
</section>
</section>
+
</div>
</body>