diff --git a/config/assets.yml b/config/assets.yml index 1442e4c54..86ea18512 100644 --- a/config/assets.yml +++ b/config/assets.yml @@ -14,9 +14,12 @@ javascripts: - public/javascripts/vendor/jquery.infinitescroll.min.js - public/javascripts/vendor/timeago.js - public/javascripts/vendor/fileuploader.js + - public/javascripts/vendor/Mustache.js - public/javascripts/view.js - public/javascripts/stream.js - public/javascripts/application.js + - public/javascripts/diaspora.js + - public/javascripts/widgets/alert.js mobile: - public/javascripts/vendor/jquery144.min.js diff --git a/public/javascripts/aspect-edit.js b/public/javascripts/aspect-edit.js index 7ffa6ad64..5a57c0334 100644 --- a/public/javascripts/aspect-edit.js +++ b/public/javascripts/aspect-edit.js @@ -93,7 +93,7 @@ var AspectEdit = { var person_id = person.attr('data-guid'); if( $(".person[data-guid='"+ person_id +"']").length == 1) { - AspectEdit.alertUser("You cannot remove the person from the last aspect"); + Diaspora.widgets.alert.alert("Error removing aspect", "You cannot remove the person from the last aspect"); } else { if (!person.hasClass('request')) { @@ -118,27 +118,27 @@ var AspectEdit = { link = "/aspects/" + id; - $this.keypress(function(e) { - if (e.which == 13) { - e.preventDefault(); - $this.blur(); - - //save changes - $.ajax({ - type: "PUT", - url: link, - data: { - "aspect": { - "name" : $this.text() - } - } - }); - } - + $this.keyup(function(e) { + e.preventDefault(); //update all other aspect links - $this.keyup(function(e) { - $("#aspect_nav li[data-guid='" + id + "'] a").text($this.text()); + $("#aspect_nav li[data-guid='" + id + "'] a").text($this.text()); + + if(e.keyCode === 13) { + $this.trigger("blur"); + } + }); + + $this.blur(function() { + //save changes + $.ajax({ + type: "PUT", + url: link, + data: { + "aspect": { + "name" : $this.text() + } + } }); }); }, @@ -165,10 +165,6 @@ var AspectEdit = { AspectEdit.deletePersonFromAspect(person); } } - }, - - alertUser: function(message) { - alert(message); } }; diff --git a/public/javascripts/contact-list.js b/public/javascripts/contact-list.js index c8a7e20fd..315c4cd31 100644 --- a/public/javascripts/contact-list.js +++ b/public/javascripts/contact-list.js @@ -46,7 +46,7 @@ $(document).ready(function() { }); $('.added').live('ajax:failure', function(data, html, xhr) { - alert("#{t('.cannot_remove')}"); + Diaspora.widgets.alert.alert("#{t('.cannot_remove')}"); $(this).fadeTo(200,1); }); diff --git a/public/javascripts/diaspora.js b/public/javascripts/diaspora.js index dc454466f..698791b40 100644 --- a/public/javascripts/diaspora.js +++ b/public/javascripts/diaspora.js @@ -4,20 +4,30 @@ */ var Diaspora = Diaspora || {}; -Diaspora.widgets = Diaspora.widgets || { - pageWidgets: {}, - - add: function(widgetId, widget) { - this.pageWidgets[widgetId] = widget; - }, - remove: function(widgetId) { - delete this.pageWidgets[widgetId]; - }, +Diaspora.widgetCollection = function() { + this.ready = false; + this.collection = {}; +}; - init: function() { - for (var widgetId in this.pageWidgets) { - this.pageWidgets[widgetId].start(); +Diaspora.widgetCollection.prototype.add = function(widgetId, widget) { + this[widgetId] = this.collection[widgetId] = new widget(); + if(this.ready) { + this.collection[widgetId].start(); + } + }; + +Diaspora.widgetCollection.prototype.remove = function(widgetId) { + delete this.collection[widgetId]; +}; + +Diaspora.widgetCollection.prototype.init = function() { + this.ready = true; + for(var widgetId in this.collection) { + this.collection[widgetId].start(); } } -}; \ No newline at end of file + +Diaspora.widgets = Diaspora.widgets || new Diaspora.widgetCollection(); + +$(Diaspora.widgets.init); \ No newline at end of file diff --git a/public/javascripts/photo-show.js b/public/javascripts/photo-show.js index 1c2541278..765598f03 100644 --- a/public/javascripts/photo-show.js +++ b/public/javascripts/photo-show.js @@ -19,7 +19,7 @@ $(document).ready(function() { }); $('.edit_photo').bind('ajax:failure', function(data, json, xhr) { - alert('Failed to delete photo. Are you sure you own this?'); + Diaspora.widgets.alert.alert("Failed to delete photo.", "Are you sure you own this?"); $("#show_photo").find("img").fadeTo(200,1); $("#photo_spinner").hide(); }); @@ -47,7 +47,7 @@ $(document).ready(function() { $('.make_profile_photo').bind('ajax:failure', function(data, json, xhr) { var person_id = $(this).closest(".photo_options").attr('data-actor_person'); - alert("Failed to update profile photo!"); + Diaspora.widgets.alert.alert("Failed to update profile photo!"); $("img[data-person_id='" + person_id + "']").fadeTo(200, 1); }); diff --git a/public/javascripts/stream.js b/public/javascripts/stream.js index 53499d2cc..ea9a754ed 100644 --- a/public/javascripts/stream.js +++ b/public/javascripts/stream.js @@ -142,7 +142,7 @@ var Stream = { }); $(".new_status_message").bind('ajax:failure', function(data, html, xhr) { - alert('failed to post message!'); + Diaspora.widgets.alert.alert('Failed to post message!'); }); $(".new_comment").live('ajax:success', function(data, json, xhr) { @@ -150,7 +150,7 @@ var Stream = { WebSocketReceiver.processComment(json.post_id, json.comment_id, json.html, false); }); $(".new_comment").live('ajax:failure', function(data, html, xhr) { - alert('failed to post message!'); + Diaspora.widgets.alert.alert('Failed to post message!'); }); $(".stream").find(".delete").live('ajax:success', function(data, html, xhr) { diff --git a/public/javascripts/vendor/Mustache.js b/public/javascripts/vendor/Mustache.js new file mode 100644 index 000000000..1b6b85c81 --- /dev/null +++ b/public/javascripts/vendor/Mustache.js @@ -0,0 +1,332 @@ +/* + mustache.js — Logic-less templates in JavaScript + + See http://mustache.github.com/ for more info. + */ +(function() { + var Mustache = function() { + var Renderer = function() { + }; + + Renderer.prototype = { + otag: "{{", + ctag: "}}", + pragmas: {}, + buffer: [], + pragmas_implemented: { + "IMPLICIT-ITERATOR": true + }, + context: {}, + + render: function(template, context, partials, in_recursion) { + // reset buffer & set context + if (!in_recursion) { + this.context = context; + this.buffer = []; // TODO: make this non-lazy + } + + // fail fast + if (!this.includes("", template)) { + if (in_recursion) { + return template; + } else { + this.send(template); + return; + } + } + + template = this.render_pragmas(template); + var html = this.render_section(template, context, partials); + if (in_recursion) { + return this.render_tags(html, context, partials, in_recursion); + } + + this.render_tags(html, context, partials, in_recursion); + }, + + /* + Sends parsed lines + */ + send: function(line) { + if (line != "") { + this.buffer.push(line); + } + }, + + /* + Looks for %PRAGMAS + */ + render_pragmas: function(template) { + // no pragmas + if (!this.includes("%", template)) { + return template; + } + + var that = this; + var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + + this.ctag); + return template.replace(regex, function(match, pragma, options) { + if (!that.pragmas_implemented[pragma]) { + throw({message: + "This implementation of mustache doesn't understand the '" + + pragma + "' pragma"}); + } + that.pragmas[pragma] = {}; + if (options) { + var opts = options.split("="); + that.pragmas[pragma][opts[0]] = opts[1]; + } + return ""; + // ignore unknown pragmas silently + }); + }, + + /* + Tries to find a partial in the curent scope and render it + */ + render_partial: function(name, context, partials) { + name = this.trim(name); + if (!partials || partials[name] === undefined) { + throw({message: "unknown_partial '" + name + "'"}); + } + if (typeof(context[name]) != "object") { + return this.render(partials[name], context, partials, true); + } + return this.render(partials[name], context[name], partials, true); + }, + + /* + Renders inverted (^) and normal (#) sections + */ + render_section: function(template, context, partials) { + if (!this.includes("#", template) && !this.includes("^", template)) { + return template; + } + + var that = this; + // CSW - Added "+?" so it finds the tighest bound, not the widest + var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + + "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + + "\\s*", "mg"); + + // for each {{#foo}}{{/foo}} section do... + return template.replace(regex, function(match, type, name, content) { + var value = that.find(name, context); + if (type == "^") { // inverted section + if (!value || that.is_array(value) && value.length === 0) { + // false or empty list, render it + return that.render(content, context, partials, true); + } else { + return ""; + } + } else if (type == "#") { // normal section + if (that.is_array(value)) { // Enumerable, Let's loop! + return that.map(value, + function(row) { + return that.render(content, that.create_context(row), + partials, true); + }).join(""); + } else if (that.is_object(value)) { // Object, Use it as subcontext! + return that.render(content, that.create_context(value), + partials, true); + } else if (typeof value === "function") { + // higher order section + return value.call(context, content, function(text) { + return that.render(text, context, partials, true); + }); + } else if (value) { // boolean section + return that.render(content, context, partials, true); + } else { + return ""; + } + } + }); + }, + + /* + Replace {{foo}} and friends with values from our view + */ + render_tags: function(template, context, partials, in_recursion) { + // tit for tat + var that = this; + + var new_regex = function() { + return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + + that.ctag + "+", "g"); + }; + + var regex = new_regex(); + var tag_replace_callback = function(match, operator, name) { + switch (operator) { + case "!": // ignore comments + return ""; + case "=": // set new delimiters, rebuild the replace regexp + that.set_delimiters(name); + regex = new_regex(); + return ""; + case ">": // render partial + return that.render_partial(name, context, partials); + case "{": // the triple mustache is unescaped + return that.find(name, context); + default: // escape the value + return that.escape(that.find(name, context)); + } + }; + var lines = template.split("\n"); + for (var i = 0; i < lines.length; i++) { + lines[i] = lines[i].replace(regex, tag_replace_callback, this); + if (!in_recursion) { + this.send(lines[i]); + } + } + + if (in_recursion) { + return lines.join("\n"); + } + }, + + set_delimiters: function(delimiters) { + var dels = delimiters.split(" "); + this.otag = this.escape_regex(dels[0]); + this.ctag = this.escape_regex(dels[1]); + }, + + escape_regex: function(text) { + // thank you Simon Willison + if (!arguments.callee.sRE) { + var specials = [ + '/', '.', '*', '+', '?', '|', + '(', ')', '[', ']', '{', '}', '\\' + ]; + arguments.callee.sRE = new RegExp( + '(\\' + specials.join('|\\') + ')', 'g' + ); + } + return text.replace(arguments.callee.sRE, '\\$1'); + }, + + /* + find `name` in current `context`. That is find me a value + from the view object + */ + find: function(name, context) { + name = this.trim(name); + + // Checks whether a value is thruthy or false or 0 + function is_kinda_truthy(bool) { + return bool === false || bool === 0 || bool; + } + + var value; + if (is_kinda_truthy(context[name])) { + value = context[name]; + } else if (is_kinda_truthy(this.context[name])) { + value = this.context[name]; + } + + if (typeof value === "function") { + return value.apply(context); + } + if (value !== undefined) { + return value; + } + // silently ignore unkown variables + return ""; + }, + + // Utility methods + + /* includes tag */ + includes: function(needle, haystack) { + return haystack.indexOf(this.otag + needle) != -1; + }, + + /* + Does away with nasty characters + */ + escape: function(s) { + s = String(s === null ? "" : s); + return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) { + switch (s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '"'; + case "'": return '''; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); + }, + + // by @langalex, support for arrays of strings + create_context: function(_context) { + if (this.is_object(_context)) { + return _context; + } else { + var iterator = "."; + if (this.pragmas["IMPLICIT-ITERATOR"]) { + iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; + } + var ctx = {}; + ctx[iterator] = _context; + return ctx; + } + }, + + is_object: function(a) { + return a && typeof a == "object"; + }, + + is_array: function(a) { + return Object.prototype.toString.call(a) === '[object Array]'; + }, + + /* + Gets rid of leading and trailing whitespace + */ + trim: function(s) { + return s.replace(/^\s*|\s*$/g, ""); + }, + + /* + Why, why, why? Because IE. Cry, cry cry. + */ + map: function(array, fn) { + if (typeof array.map == "function") { + return array.map(fn); + } else { + var r = []; + var l = array.length; + for (var i = 0; i < l; i++) { + r.push(fn(array[i])); + } + return r; + } + } + }; + + return({ + name: "mustache.js", + version: "0.3.1-dev", + + /* + Turns a template and view into HTML + */ + to_html: function(template, view, partials, send_fun) { + var renderer = new Renderer(); + if (send_fun) { + renderer.send = send_fun; + } + renderer.render(template, view, partials); + if (!send_fun) { + return renderer.buffer.join("\n"); + } + } + }); + }(); + + $.mustache = function(template, view, partials, send_fun) { + return Mustache.to_html(template, view, partials, send_fun); + }; +})(); \ No newline at end of file diff --git a/public/javascripts/widgets/alert.js b/public/javascripts/widgets/alert.js new file mode 100644 index 000000000..cafb71366 --- /dev/null +++ b/public/javascripts/widgets/alert.js @@ -0,0 +1,34 @@ +Diaspora.widgets.add("alert", function() { + this.start = function() { + $(document).bind("close.facebox", function() { + if ($("#diaspora_alert").length) { + $("#diaspora_alert").detach(); + } + }); + }; + + this.faceboxTemplate = '