diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index b713d34..d9a9394 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -39,7 +39,7 @@ Compile SCSS to CSS: Install JS components: ~> cd isso/js - ~> bower install almond q requirejs requirejs-domready requirejs-text + ~> bower install almond requirejs requirejs-domready requirejs-text Integration diff --git a/docs/index.html b/docs/index.html index 18437b8..1664e0a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -30,7 +30,7 @@
  • client-side JavaScript

    -

    Embed a single JS file, 54kb (18kb gzipped) and you are +

    Embed a single JS file, 52kb (16kb gzipped) and you are done.

    Supports Firefox, Safari, Chrome and IE10.

  • diff --git a/isso/js/app/api.js b/isso/js/app/api.js index d13cbca..e4f0118 100644 --- a/isso/js/app/api.js +++ b/isso/js/app/api.js @@ -1,41 +1,14 @@ -define(["q"], function(Q) { +define(["app/lib/promise"], function(Q) { "use strict"; - Q.stopUnhandledRejectionTracking(); - Q.longStackSupport = true; - var salt = "Eech7co8Ohloopo9Ol6baimi", location = window.location.pathname; - var rules = { - "/": [200, 404], - "/new": [201, 202], - "/id/\\d+": [200, 403, 404], - "/id/\\d+/(like/dislike)": [200], - "/count": [200] - }; - - /* - * Detect Isso API endpoint. There are typically two use cases: - * - * 1. use minified, single-file JavaScript. The browser interprets - * scripts sequentially, thus we can safely use the last script - * tag. Then, we chop off some characters -- /js/embed.min.s -- - * and we're done. - * - * If the script is not served by Isso directly, a custom data - * attribute can be used to override the default detection - * mechanism: - * - * .. code-block:: html - * - * - */ - var script, endpoint, js = document.getElementsByTagName("script"); + // prefer `data-isso="//host/api/endpoint"` if provided for (var i = 0; i < js.length; i++) { if (js[i].hasAttribute("data-isso")) { endpoint = js[i].getAttribute("data-isso"); @@ -43,6 +16,7 @@ define(["q"], function(Q) { } } + // if no async-script is embedded, use the last script tag of `js` if (! endpoint) { for (i = 0; i < js.length; i++) { if (js[i].getAttribute("async") || js[i].getAttribute("defer")) { @@ -61,25 +35,19 @@ define(["q"], function(Q) { endpoint = endpoint.substring(0, endpoint.length - 1); } - var curl = function(method, url, data) { + var curl = function(method, url, data, resolve, reject) { var xhr = new XMLHttpRequest(); - var response = Q.defer(); function onload() { - var rule = url.replace(endpoint, "").split("?", 1)[0]; var cookie = xhr.getResponseHeader("X-Set-Cookie"); if (cookie && cookie.match(/^isso-/)) { document.cookie = cookie; } - if (rule in rules && rules[rule].indexOf(xhr.status) === -1) { - response.reject(xhr.responseText); - } else { - response.resolve({status: xhr.status, body: xhr.responseText}); - } + resolve({status: xhr.status, body: xhr.responseText}); } try { @@ -93,11 +61,10 @@ define(["q"], function(Q) { } }; } catch (exception) { - response.reject(exception.message); + (reject || console.log)(exception.message); } xhr.send(data); - return response.promise; }; var qs = function(params) { @@ -112,70 +79,98 @@ define(["q"], function(Q) { }; var create = function(tid, data) { - return curl("POST", endpoint + "/new?" + qs({uri: tid || location}), JSON.stringify(data)).then( - function (rv) { return JSON.parse(rv.body); }); + var deferred = Q.defer(); + curl("POST", endpoint + "/new?" + qs({uri: tid || location}), JSON.stringify(data), + function (rv) { deferred.resolve(JSON.parse(rv.body)); }); + return deferred.promise; }; var modify = function(id, data) { - return curl("PUT", endpoint + "/id/" + id, JSON.stringify(data)).then( - function (rv) { return JSON.parse(rv.body); }); + var deferred = Q.defer(); + curl("PUT", endpoint + "/id/" + id, JSON.stringify(data), function (rv) { + deferred.resolve(JSON.parse(rv.body)); + }); + return deferred.promise; }; var remove = function(id) { - return curl("DELETE", endpoint + "/id/" + id, null).then(function(rv) { + var deferred = Q.defer(); + curl("DELETE", endpoint + "/id/" + id, null, function(rv) { if (rv.status === 403) { - throw "Not authorized to remove this comment!"; + deferred.reject("Not authorized to remove this comment!"); + } else if (rv.status === 200) { + deferred.resolve(JSON.parse(rv.body) === null); + } else { + deferred.reject(rv.body); } - - return JSON.parse(rv.body) === null; }); + return deferred.promise; }; var view = function(id, plain) { - return curl("GET", endpoint + "/id/" + id + "?" + qs({plain: plain}), null).then(function (rv) { - return JSON.parse(rv.body); - }); + var deferred = Q.defer(); + curl("GET", endpoint + "/id/" + id + "?" + qs({plain: plain}), null, + function(rv) { deferred.resolve(JSON.parse(rv.body)); }); + return deferred.promise; }; var fetch = function(tid) { - - return curl("GET", endpoint + "/?" + qs({uri: tid || location}), null).then(function (rv) { + var deferred = Q.defer(); + curl("GET", endpoint + "/?" + qs({uri: tid || location}), null, function(rv) { if (rv.status === 200) { - return JSON.parse(rv.body); + deferred.resolve(JSON.parse(rv.body)); + } else if (rv.status === 404) { + deferred.resolve([]); } else { - return []; + deferred.reject(rv.body); } }); + return deferred.promise; }; var count = function(tid) { - return curl("GET", endpoint + "/count?" + qs({uri: tid || location}), null).then(function(rv) { - return JSON.parse(rv.body); + var deferred = Q.defer(); + curl("GET", endpoint + "/count?" + qs({uri: tid || location}), null, function(rv) { + if (rv.status === 200) { + deferred.resolve(JSON.parse(rv.body)); + } else if (rv.status === 404) { + deferred.resolve(0); + } else { + deferred.reject(rv.body); + } }); + return deferred.promise; }; var like = function(id) { - return curl("POST", endpoint + "/id/" + id + "/like", null).then(function(rv) { - return JSON.parse(rv.body); - }); + var deferred = Q.defer(); + curl("POST", endpoint + "/id/" + id + "/like", null, + function(rv) { deferred.resolve(JSON.parse(rv.body)); }); + return deferred.promise; }; var dislike = function(id) { - return curl("POST", endpoint + "/id/" + id + "/dislike", null).then(function(rv) { - return JSON.parse(rv.body); - }); + var deferred = Q.defer(); + curl("POST", endpoint + "/id/" + id + "/dislike", null, + function(rv) { deferred.resolve(JSON.parse(rv.body)); }); + return deferred.promise; }; var remote_addr = function() { - return curl("GET", endpoint + "/check-ip", null).then(function(rv) { - return rv.body; + var deferred = Q.defer(); + curl("GET", endpoint + "/check-ip", null, function(rv) { + if (rv.status === 200) { + deferred.resolve(rv.body); + } else { + deferred.reject(rv.body); + } }); + return deferred.promise; }; return { endpoint: endpoint, salt: salt, - remote_addr: remote_addr, create: create, diff --git a/isso/js/app/lib/identicons.js b/isso/js/app/lib/identicons.js index 6e462db..2ce41aa 100644 --- a/isso/js/app/lib/identicons.js +++ b/isso/js/app/lib/identicons.js @@ -4,7 +4,7 @@ Inspired by http://codepen.io/gschier/pen/GLvAy */ -define(["q"], function(Q) { +define(["app/lib/promise"], function(Q) { "use strict"; diff --git a/isso/js/app/lib/pbkdf2.js b/isso/js/app/lib/pbkdf2.js index 1f8f165..db15c1f 100644 --- a/isso/js/app/lib/pbkdf2.js +++ b/isso/js/app/lib/pbkdf2.js @@ -1,4 +1,4 @@ -define(["q", "app/lib/sha1"], function(Q, sha1) { +define(["app/lib/promise", "app/lib/sha1"], function(Q, sha1) { /* * JavaScript implementation of Password-Based Key Derivation Function 2 * (PBKDF2) as defined in RFC 2898. @@ -191,11 +191,11 @@ define(["q", "app/lib/sha1"], function(Q, sha1) { Q.when(text, function(text) { var pbkdf2 = new PBKDF2(text, salt, iterations, size); - pbkdf2.deriveKey(function(bla) {}, function(rv) { + pbkdf2.deriveKey(function() {}, function(rv) { deferred.resolve(rv); }); - }) + }); return deferred.promise; } -}) \ No newline at end of file +}) diff --git a/isso/js/app/lib/promise.js b/isso/js/app/lib/promise.js new file mode 100644 index 0000000..a540c56 --- /dev/null +++ b/isso/js/app/lib/promise.js @@ -0,0 +1,55 @@ +define(function() { + + "use strict"; + + var Promise = function() { + this.success = []; + this.errors = []; + }; + + Promise.prototype.then = function(onSuccess, onError) { + this.success.push(onSuccess); + if (onError) { + this.errors.push(onError); + } else { + this.errors.push(console.log); + } + }; + + var defer = function() { + this.promise = new Promise(); + }; + + defer.prototype = { + promise: Promise, + resolve: function(rv) { + this.promise.success.forEach(function(callback) { + window.setTimeout(function() { + callback(rv); + }, 0); + }); + }, + + reject: function(error) { + this.promise.errors.forEach(function(callback) { + window.setTimeout(function() { + callback(error); + }, 0); + }); + } + }; + + var when = function(obj, func) { + if (obj instanceof Promise) { + return obj.then(func); + } else { + return func(obj); + } + }; + + return { + defer: function() { return new defer(); }, + when: when + }; + +}); diff --git a/isso/js/config.js b/isso/js/config.js index d6e9efc..7abcb01 100644 --- a/isso/js/config.js +++ b/isso/js/config.js @@ -1,6 +1,5 @@ var requirejs = { paths: { - q: "components/q/q", text : "components/requirejs-text/text", ready: "components/requirejs-domready/domReady" }, diff --git a/isso/js/embed.js b/isso/js/embed.js index 0ad860d..59f825f 100644 --- a/isso/js/embed.js +++ b/isso/js/embed.js @@ -26,23 +26,25 @@ require(["ready", "app/config", "app/api", "app/isso", "app/count", "app/dom", " $("#isso-thread").append(new isso.Postbox(null)); $("#isso-thread").append('
    '); - api.fetch($("#isso-thread").getAttribute("data-isso-id")).then(function(rv) { - - if (! rv.length) { - $("#isso-thread > h4").textContent = Mark.up("{{ i18n-no-comments }}"); - return; - } - - $("#isso-thread > h4").textContent = Mark.up("{{ i18n-num-comments | pluralize : `n` }}", {n: rv.length}); - for (var i=0; i < rv.length; i++) { - isso.insert(rv[i], false); - } - }).fail(function(err) { - console.log(err); - }).done(function() { - if (window.location.hash.length > 0) { - $(window.location.hash).scrollIntoView(); + api.fetch($("#isso-thread").getAttribute("data-isso-id")).then( + function(rv) { + if (! rv.length) { + $("#isso-thread > h4").textContent = Mark.up("{{ i18n-no-comments }}"); + return; + } + + $("#isso-thread > h4").textContent = Mark.up("{{ i18n-num-comments | pluralize : `n` }}", {n: rv.length}); + for (var i=0; i < rv.length; i++) { + isso.insert(rv[i], false); + } + + if (window.location.hash.length > 0) { + $(window.location.hash).scrollIntoView(); + } + }, + function(err) { + console.log(err); } - }); + ); }); });