diff --git a/index.html b/index.html index 8f6d9e5..1cd5e00 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,7 @@ + @@ -38,7 +39,7 @@ - +

logo @@ -71,10 +72,10 @@
- +
- +
@@ -86,19 +87,19 @@
-
+
@@ -122,7 +123,7 @@
To start using glowing bear, please enable the relay plugin in your WeeChat client:
 /set relay.network.password yourpassword
-/relay add weechat {{ port || 9001 }}
+/relay add weechat {{ settings.port || 9001 }}
 
WeeChat version 0.4.2 or higher is required.
The communication goes directly between your browser and your WeeChat relay in plain text. Check the instructions below for help on setting up encrypted communication. @@ -157,18 +158,18 @@

If you check the encryption box, the communication between browser and WeeChat will be encrypted with TLS.

-

Note: If you are using a self-signed certificate, you have to visit https://{{ host || 'weechathost' }}:{{ port || 'relayport' }}/ in your browser first to add a security exception. You can close that tab once you confirmed the certificate, no content will appear. The necessity of this process is a bug in Firefox and other browsers.

+

Note: If you are using a self-signed certificate, you have to visit https://{{ settings.host || 'weechathost' }}:{{ settings.port || 'relayport' }}/ in your browser first to add a security exception. You can close that tab once you confirmed the certificate, no content will appear. The necessity of this process is a bug in Firefox and other browsers.

Setup: If you want to use an encrypted session you first have to set up the relay to use TLS. You basically have two options: a self-signed certificate is easier to set up, but requires manual security exceptions. Using a certificate that is trusted by your browser requires more setup, but offers greater convenience later on and does not require security exceptions. You can find a guide to set up WeeChat with a free trusted certificate from StartSSL here. Should you wish to use a self-signed certificate instead, execute the following commands in a shell on the same host and as the user running WeeChat:

 $ mkdir -p ~/.weechat/ssl
 $ cd ~/.weechat/ssl
-$ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out relay.pem -subj "/CN={{host || 'your weechat host'}}/"
+$ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out relay.pem -subj "/CN={{settings.host || 'your weechat host'}}/"
 
-

If WeeChat is already running, you can reload the certificate and private key and set up an encrypted relay on port {{ port || 9001 }} with these WeeChat commands:

+

If WeeChat is already running, you can reload the certificate and private key and set up an encrypted relay on port {{ settings.port || 9001 }} with these WeeChat commands:

 /set relay.network.password yourpassword
 /relay sslcertkey
-/relay add ssl.weechat {{ port || 9001 }}
+/relay add ssl.weechat {{ settings.port || 9001 }}
 
@@ -213,7 +214,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
- brand + brand
@@ -255,7 +256,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
- +
@@ -306,12 +307,12 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
- +
- +
@@ -321,7 +322,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
- +
@@ -331,7 +332,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
@@ -341,17 +342,17 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
-
    +
    • @@ -363,7 +364,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
      @@ -373,7 +374,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
      @@ -383,7 +384,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
      @@ -393,7 +394,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
      @@ -403,7 +404,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
      @@ -413,7 +414,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
      @@ -423,7 +424,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
      diff --git a/js/glowingbear.js b/js/glowingbear.js index 688833e..cc72e6b 100644 --- a/js/glowingbear.js +++ b/js/glowingbear.js @@ -1,7 +1,10 @@ (function() { 'use strict'; -var weechat = angular.module('weechat', ['ngRoute', 'localStorage', 'weechatModels', 'plugins', 'IrcUtils', 'ngSanitize', 'ngWebsockets', 'ngTouch']); +var weechat = angular.module('weechat', ['ngRoute', 'localStorage', 'weechatModels', 'plugins', 'IrcUtils', 'ngSanitize', 'ngWebsockets', 'ngTouch'], function($compileProvider) { + // hacky way to be able to find out if we're in debug mode + weechat.compileProvider = $compileProvider; +}); weechat.config(['$compileProvider', function ($compileProvider) { // hack to determine whether we're executing the tests if (typeof(it) === "undefined" && typeof(describe) === "undefined") { @@ -9,11 +12,33 @@ weechat.config(['$compileProvider', function ($compileProvider) { } }]); -weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', '$log', 'models', 'connection', 'notifications', 'utils', function ($rootScope, $scope, $store, $timeout, $log, models, connection, notifications, utils) { +weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', '$log', 'models', 'connection', 'notifications', 'utils', 'settings', + function ($rootScope, $scope, $store, $timeout, $log, models, connection, notifications, utils, settings) { $scope.command = ''; $scope.themes = ['dark', 'light']; + settings.setDefaults({ + 'theme': 'dark', + 'host': 'localhost', + 'port': 9001, + 'ssl': (window.location.protocol === "https:"), + 'savepassword': false, + 'autoconnect': false, + 'nonicklist': utils.isMobileUi(), + 'noembed': utils.isMobileUi(), + 'onlyUnread': false, + 'hotlistsync': true, + 'orderbyserver': true, + 'useFavico': true, + 'showtimestamp': true, + 'showtimestampSeconds': false, + 'fontsize': '14px', + 'fontfamily': (utils.isMobileUi() ? 'sans-serif' : 'Inconsolata, Consolas, Monaco, Ubuntu Mono, monospace'), + 'readlineBindings': false + }); + $scope.settings = settings; + // From: http://stackoverflow.com/a/18539624 by StackOverflow user "plantian" $rootScope.countWatchers = function () { var q = [$rootScope], watchers = 0, scope; @@ -69,22 +94,16 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', // Enable debug mode if "?debug=1" or "?debug=true" is set (function() { - var hasReloaded = false; window.location.search.substring(1).split('&').forEach(function(f) { var segs = f.split('='); if (segs[0] === "debug" && ["true", "1"].indexOf(segs[1]) != -1) { $rootScope.debugMode = true; - } else if (segs[0] === "debugReload" && segs[1] === "1") { - hasReloaded = true; } }); // If we haven't reloaded yet, do an angular reload with debug infos // store whether this has happened yet in a GET parameter - if ($rootScope.debugMode && !hasReloaded) { - document.location.search += "&debugReload=1"; - setTimeout(function() { - angular.reloadWithDebugInfo(); - }, 0); + if ($rootScope.debugMode && !weechat.compileProvider.debugInfoEnabled()) { + angular.reloadWithDebugInfo(); } })(); @@ -196,7 +215,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', // we will send a /buffer bufferName command every time // the user switches a buffer. This will ensure that notifications // are cleared in the buffer the user switches to - if ($scope.hotlistsync && ab.fullName) { + if (settings.hotlistsync && ab.fullName) { connection.sendCoreCommand('/buffer ' + ab.fullName); } @@ -218,7 +237,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', $rootScope.$on('notificationChanged', function() { notifications.updateTitle(); - if ($scope.useFavico && $rootScope.favico) { + if (settings.useFavico && $rootScope.favico) { notifications.updateFavico(); } }); @@ -249,72 +268,31 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', $rootScope.iterCandidate = null; - $store.bind($scope, "host", "localhost"); - $store.bind($scope, "port", "9001"); - $store.bind($scope, "proto", "weechat"); - $store.bind($scope, "ssl", (window.location.protocol === "https:")); - $store.bind($scope, "savepassword", false); - if ($scope.savepassword) { - $store.bind($scope, "password", ""); + if (settings.savepassword) { + $scope.$watch('password', function() { + settings.password = $scope.password; + }); + settings.addCallback('password', function(password) { + $scope.password = password; + }); + $scope.password = settings.password; + } else { + settings.password = ''; } - $store.bind($scope, "autoconnect", false); - - // If we are on mobile change some defaults - // We use 968 px as the cutoff, which should match the value in glowingbear.css - var nonicklist = false; - var noembed = false; - var showtimestamp = true; $rootScope.wasMobileUi = false; - if (utils.isMobileUi()) { - nonicklist = true; - noembed = true; $rootScope.wasMobileUi = true; } - - // Save setting for displaying only buffers with unread messages - $store.bind($scope, "onlyUnread", false); - - // Save setting for syncing hotlist - $store.bind($scope, "hotlistsync", true); - // Save setting for displaying nicklist - $store.bind($scope, "nonicklist", nonicklist); - // Save setting for displaying embeds - $store.bind($scope, "noembed", noembed); - // Save setting for channel ordering - $store.bind($scope, "orderbyserver", true); - // Save setting for updating favicon - $store.bind($scope, "useFavico", true); - // Save setting for showtimestamp - $store.bind($scope, "showtimestamp", showtimestamp); - // Save setting for showing seconds on timestamps - $store.bind($scope, "showtimestampSeconds", false); - // Save setting for playing sound on notification - $store.bind($scope, "soundnotification", false); - // Save setting for font family - $store.bind($scope, "fontfamily"); - // Save setting for theme - $store.bind($scope, "theme", 'dark'); - // Save setting for font size - $store.bind($scope, "fontsize", "14px"); - // Save setting for readline keybindings - $store.bind($scope, "readlineBindings", false); - // Save settings for non-native Emoji support - $store.bind($scope, "enableJSEmoji", false); - - if (!$scope.fontfamily) { + if (!settings.fontfamily) { if (utils.isMobileUi()) { - $scope.fontfamily = 'sans-serif'; + settings.fontfamily = 'sans-serif'; } else { - $scope.fontfamily = "Inconsolata, Consolas, Monaco, Ubuntu Mono, monospace"; + settings.fontfamily = "Inconsolata, Consolas, Monaco, Ubuntu Mono, monospace"; } } - // Save setting for displaying embeds in rootScope so it can be used from service - $rootScope.auto_display_embedded_content = $scope.noembed === false; - $scope.isSidebarVisible = function() { return document.getElementById('content').getAttribute('sidebar-state') === 'visible'; }; @@ -336,9 +314,8 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', document.getElementById('content').setAttribute('sidebar-state', 'hidden'); } }; - // This also fires on page load - $scope.$watch('autoconnect', function() { - if ($scope.autoconnect && !$rootScope.connected && !$rootScope.sslError && !$rootScope.securityError && !$rootScope.errorMessage) { + settings.addCallback('autoconnect', function(autoconnect) { + if (autoconnect && !$rootScope.connected && !$rootScope.sslError && !$rootScope.securityError && !$rootScope.errorMessage) { $scope.connect(); } }); @@ -357,35 +334,31 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', // Open and close panels while on mobile devices through swiping $scope.openNick = function() { if (utils.isMobileUi()) { - if ($scope.nonicklist) { - $scope.nonicklist = false; + if (settings.nonicklist) { + settings.nonicklist = false; } } }; $scope.closeNick = function() { if (utils.isMobileUi()) { - if (!$scope.nonicklist) { - $scope.nonicklist = true; + if (!settings.nonicklist) { + settings.nonicklist = true; } } }; - // Watch model and update show setting when it changes - $scope.$watch('noembed', function() { - $rootScope.auto_display_embedded_content = $scope.noembed === false; - }); // Watch model and update channel sorting when it changes - $scope.$watch('orderbyserver', function() { - $rootScope.predicate = $scope.orderbyserver ? 'serverSortKey' : 'number'; + settings.addCallback('orderbyserver', function(orderbyserver) { + $rootScope.predicate = orderbyserver ? 'serverSortKey' : 'number'; }); - $scope.$watch('useFavico', function() { + settings.addCallback('useFavico', function(useFavico) { // this check is necessary as this is called on page load, too if (!$rootScope.connected) { return; } - if ($scope.useFavico) { + if (useFavico) { notifications.updateFavico(); } else { $rootScope.favico.reset(); @@ -393,17 +366,12 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', }); // Update font family when changed - $scope.$watch('fontfamily', function() { - utils.changeClassStyle('favorite-font', 'fontFamily', $scope.fontfamily); + settings.addCallback('fontfamily', function(fontfamily) { + utils.changeClassStyle('favorite-font', 'fontFamily', fontfamily); }); // Update font size when changed - $scope.$watch('fontsize', function() { - utils.changeClassStyle('favorite-font', 'fontSize', $scope.fontsize); - }); - // Crude scoping hack. The keypress listener does not live in the same scope as - // the checkbox, so we need to transfer this between scopes here. - $scope.$watch('readlineBindings', function() { - $rootScope.readlineBindings = $scope.readlineBindings; + settings.addCallback('fontsize', function(fontsize) { + utils.changeClassStyle('favorite-font', 'fontSize', fontsize); }); $scope.setActiveBuffer = function(bufferId, key) { @@ -529,7 +497,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', $rootScope.errorMessage = false; $rootScope.bufferBottom = true; $scope.connectbutton = 'Connecting ...'; - connection.connect($scope.host, $scope.port, $scope.password, $scope.ssl); + connection.connect(settings.host, settings.port, $scope.password, settings.ssl); }; $scope.disconnect = function() { $scope.connectbutton = 'Connect'; @@ -597,7 +565,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', if ($scope.search && $scope.search !== "") { return true; } - if ($scope.onlyUnread) { + if (settings.onlyUnread) { // Always show current buffer in list if (models.getActiveBuffer() === buffer) { return true; @@ -612,7 +580,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', }; // Watch model and update show setting when it changes - $scope.$watch('nonicklist', function() { + settings.addCallback('nonicklist', function() { $scope.showNicklist = $scope.updateShowNicklist(); // restore bottom view if ($rootScope.connected && $rootScope.bufferBottom) { @@ -631,7 +599,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', return false; } // Check if option no nicklist is set - if ($scope.nonicklist) { + if (settings.nonicklist) { return false; } // Check if nicklist is empty @@ -665,7 +633,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', }; // Helper function since the keypress handler is in a different scope $rootScope.toggleNicklist = function() { - $scope.nonicklist = !$scope.nonicklist; + settings.nonicklist = !settings.nonicklist; }; diff --git a/js/inputbar.js b/js/inputbar.js index e65a327..ab4394a 100644 --- a/js/inputbar.js +++ b/js/inputbar.js @@ -14,13 +14,14 @@ weechat.directive('inputBar', function() { command: '=command' }, - controller: ['$rootScope', '$scope', '$element', '$log', 'connection', 'models', 'IrcUtils', function($rootScope, + controller: ['$rootScope', '$scope', '$element', '$log', 'connection', 'models', 'IrcUtils', 'settings', function($rootScope, $scope, $element, //XXX do we need this? don't seem to be using it $log, connection, //XXX we should eliminate this dependency and use signals instead models, - IrcUtils) { + IrcUtils, + settings) { /* * Returns the input element @@ -340,7 +341,7 @@ weechat.directive('inputBar', function() { } // Some readline keybindings - if ($rootScope.readlineBindings && $event.ctrlKey && !$event.altKey && !$event.shiftKey && document.activeElement === inputNode) { + if (settings.readlineBindings && $event.ctrlKey && !$event.altKey && !$event.shiftKey && document.activeElement === inputNode) { // get current caret position caretPos = inputNode.selectionStart; // Ctrl-a diff --git a/js/localstorage.js b/js/localstorage.js index 5909bf8..0c1218e 100644 --- a/js/localstorage.js +++ b/js/localstorage.js @@ -10,6 +10,10 @@ ls.factory("$store", ["$parse", function($parse){ var storage = (typeof window.localStorage === 'undefined') ? undefined : window.localStorage, supported = !(typeof storage == 'undefined' || typeof window.JSON == 'undefined'); + if (!supported) { + console.log('Warning: localStorage is not supported'); + } + var privateMethods = { /** * Pass any type of a string from the localStorage to be parsed so it returns a usable version (like an Object) @@ -29,7 +33,7 @@ ls.factory("$store", ["$parse", function($parse){ if (val === 'false'){ val = false; } - if (parseFloat(val) === val && !angular.isObject(val)) { + if (parseFloat(val) == val && !angular.isObject(val)) { val = parseFloat(val); } } catch(e){ @@ -40,77 +44,73 @@ ls.factory("$store", ["$parse", function($parse){ }; var publicMethods = { /** - * Set - let's you set a new localStorage key pair set + * Set - lets you set a new localStorage key pair set * @param key - a string that will be used as the accessor for the pair * @param value - the value of the localStorage item * @returns {*} - will return whatever it is you've stored in the local storage */ set: function(key,value){ if (!supported){ - try { - $.cookie(key, value); - return value; - } catch(e){ - console.log('Local Storage not supported, make sure you have the $.cookie supported.'); - } + console.log('Local Storage not supported'); } var saver = JSON.stringify(value); - storage.setItem(key, saver); + storage.setItem(key, saver); return privateMethods.parseValue(saver); }, /** - * Get - let's you get the value of any pair you've stored + * Get - lets you get the value of any pair you've stored * @param key - the string that you set as accessor for the pair * @returns {*} - Object,String,Float,Boolean depending on what you stored */ get: function(key){ if (!supported){ - try { - return privateMethods.parseValue($.cookie(key)); - } catch(e){ - return null; - } + return null; } var item = storage.getItem(key); return privateMethods.parseValue(item); }, /** - * Remove - let's you nuke a value from localStorage + * Remove - lets you nuke a value from localStorage * @param key - the accessor value * @returns {boolean} - if everything went as planned */ remove: function(key) { if (!supported){ - try { - $.cookie(key, null); - return true; - } catch(e){ - return false; - } + return false; } storage.removeItem(key); return true; }, /** - * Bind - let's you directly bind a localStorage value to a $scope variable - * @param $scope - the current scope you want the variable available in - * @param key - the name of the variable you are binding - * @param def - the default value (OPTIONAL) - * @returns {*} - returns whatever the stored value is - */ - bind: function ($scope, key, def) { - if (def === undefined) { - def = ''; - } - if (publicMethods.get(key) === undefined || publicMethods.get(key) === null) { - publicMethods.set(key, def); - } - $parse(key).assign($scope, publicMethods.get(key)); - $scope.$watch(key, function (val) { - publicMethods.set(key, val); - }, true); - return publicMethods.get(key); + * Enumerate all keys + */ + enumerateKeys: function() { + var keys = []; + for (var i = 0, len = storage.length; i < len; ++i) { + keys.push(storage.key(i)); } + return keys; + }, + /** + * Bind - lets you directly bind a localStorage value to a $scope variable + * @param $scope - the current scope you want the variable available in + * @param key - the name of the variable you are binding + * @param def - the default value (OPTIONAL) + * @returns {*} - returns whatever the stored value is + */ + bind: function ($scope, key, def) { + if (def === undefined) { + def = ''; + } + if (publicMethods.get(key) === undefined || publicMethods.get(key) === null) { + publicMethods.set(key, def); + } + $parse(key).assign($scope, publicMethods.get(key)); + $scope.$watch(key, function (val) { + publicMethods.set(key, val); + }, true); + return publicMethods.get(key); + } }; return publicMethods; }]); diff --git a/js/notifications.js b/js/notifications.js index 00cfb32..739a651 100644 --- a/js/notifications.js +++ b/js/notifications.js @@ -1,9 +1,8 @@ var weechat = angular.module('weechat'); -weechat.factory('notifications', ['$rootScope', '$log', 'models', function($rootScope, $log, models) { - var notifications = []; - +weechat.factory('notifications', ['$rootScope', '$log', 'models', 'settings', function($rootScope, $log, models, settings) { // Ask for permission to display desktop notifications + var notifications = []; var requestNotificationPermission = function() { // Firefox if (window.Notification) { @@ -135,7 +134,7 @@ weechat.factory('notifications', ['$rootScope', '$log', 'models', function($root delete notifications[this.id]; }; - if ($rootScope.soundnotification) { + if (settings.soundnotification) { // TODO fill in a sound file var audioFile = "assets/audio/sonar"; var soundHTML = ''; @@ -153,10 +152,10 @@ weechat.factory('notifications', ['$rootScope', '$log', 'models', function($root }; return { - requestNotificationPermission: requestNotificationPermission, - updateTitle: updateTitle, - updateFavico: updateFavico, - createHighlight: createHighlight, + requestNotificationPermission: requestNotificationPermission, + updateTitle: updateTitle, + updateFavico: updateFavico, + createHighlight: createHighlight, cancelAll: cancelAll, }; }]); diff --git a/js/plugin-directive.js b/js/plugin-directive.js index f61cba4..94d19f5 100644 --- a/js/plugin-directive.js +++ b/js/plugin-directive.js @@ -3,7 +3,7 @@ var weechat = angular.module('weechat'); -weechat.directive('plugin', ['$rootScope', function($rootScope) { +weechat.directive('plugin', ['$rootScope', 'settings', function($rootScope, settings) { /* * Plugin directive * Shows additional plugin content @@ -20,7 +20,7 @@ weechat.directive('plugin', ['$rootScope', function($rootScope) { $scope.displayedContent = ""; // Auto-display embedded content only if it isn't NSFW - $scope.plugin.visible = $rootScope.auto_display_embedded_content && !$scope.plugin.nsfw; + $scope.plugin.visible = !settings.noembed && !$scope.plugin.nsfw; // user-accessible hash key that is a valid CSS class name $scope.plugin.className = "embed_" + $scope.plugin.$$hashKey.replace(':','_'); diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 0000000..8c312fc --- /dev/null +++ b/js/settings.js @@ -0,0 +1,68 @@ +(function() { +'use strict'; + +var weechat = angular.module('weechat'); + +weechat.factory('settings', ['$store', '$rootScope', function($store, $rootScope) { + var that = this; + this.callbacks = {}; + + // Define a property for a setting, retrieving it on read + // and writing it to localStorage on write + var defineProperty = function(key) { + Object.defineProperty(that, key, { + enumerable: true, + key: key, + get: function() { + return $store.get(key); + }, + set: function(newVal) { + $store.set(key, newVal); + // Call any callbacks + var callbacks = that.callbacks[key]; + for (var i = 0; callbacks !== undefined && i < callbacks.length; i++) { + callbacks[i](newVal); + } + // Update the page (might be needed) + setTimeout(function() { + $rootScope.$apply(); + }, 0); + } + }); + }; + + // Define properties for all settings + var keys = $store.enumerateKeys(); + for (var keyIdx in keys) { + var key = keys[keyIdx]; + defineProperty(key); + } + + // Add a callback to be called whenever the value is changed + // It's like a free $watch and used to be called the observer + // pattern, but I guess that's too old-school for JS kids :> + this.addCallback = function(key, callback, callNow) { + if (this.callbacks[key] === undefined) { + this.callbacks[key] = [callback]; + } else { + this.callbacks[key].push(callback); + } + // call now to emulate $watch behaviour + setTimeout(function() { + callback($store.get(key)); + }, 0); + }; + + this.setDefaults = function(defaults) { + for (var key in defaults) { + // null means the key isn't set + if ($store.get(key) === null) { + this[key] = defaults[key]; + } + } + }; + + return this; +}]); + +})();