Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Refactor snippet editor and save snippet icon url. #5

Merged
merged 1 commit into from

2 participants

@Osmose
Owner

Switches to using ICanHaz for JS templating and reorganizes the
snippet editor to be a bit more manageable.

In addition, the snippet editor saves the icon URL in a comment
in the snippet so the url box keeps the icon URL between saves.

@tofumatt tofumatt commented on the diff
apps/homesnippets/templates/snippetBodyWidget.html
((2 lines not shown))
<div id="snippet-editor">
+ {# ICanHaz Templates #}
+ {% icanhaz %}
+ <script type="text/html" id="snippet_template">
+ <!--basic-->
+ <div class="snippet">
+ [[#icon]]
+ <!--icon:[[ url ]]-->
+ <img class="icon" src="[[ data ]]" />

If this is HTML5 you don't need the trailing slash for img tags. Also: no alt attribute?

@Osmose Owner
Osmose added a note

Snippets unfortunately aren't HTML5, but have to be valid XML: http://dxr.mozilla.org/mozilla/mozilla-central/browser/base/content/aboutHome.js.html#l239

As for the alt attribute, would an empty alt do? If not, could we use an empty alt and add a bug for letting the user specify the alt text? (This also brings up issues of localizing the alt text when the snippet is submitted for translation)

Empty alt is bad; it's a legit thing for screenreaders and such to use. If this is on the about:home page I'd argue we put our best foot forward and make it sexy, accessible HTML.

How hard would it be to have an alt attribute in here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tofumatt tofumatt commented on the diff
apps/homesnippets/templatetags/icanhaz.py
@@ -0,0 +1,34 @@
+from django import template
+
+register = template.Library()
+
+
+SYMBOLS = [

Hahaha this is awesomely brain-hurty to read at first. I was searching for ASCII art.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tofumatt tofumatt commented on the diff
apps/homesnippets/templatetags/icanhaz.py
((1 lines not shown))
+from django import template
+
+register = template.Library()
+
+
+SYMBOLS = [
+ ('[[[', '{{{'),
+ (']]]', '}}}'),
+ ('[[', '{{'),
+ (']]', '}}'),
+]
+
+
+@register.tag
+def icanhaz(parser, token):
+ """

Docstrings first sentence should be one line and on the first line; anything else comes after an empty line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/homesnippets/templatetags/icanhaz.py
((11 lines not shown))
+]
+
+
+@register.tag
+def icanhaz(parser, token):
+ """
+ Replaces double and triple square brackets with curly brackets to allow for
+ embedding ICanHaz/Mustache templates in django templates.
+ """
+ nodelist = parser.parse(('endicanhaz',))
+ parser.delete_first_token()
+ return ICanHazNode(nodelist)
+
+
+class ICanHazNode(template.Node):
+ """Performs a find and replace on the contents of the node."""

Can you "perform a find (and replace)"? Isn't this a basic parser? Why not just call it that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/homesnippets/templatetags/icanhaz.py
((15 lines not shown))
+def icanhaz(parser, token):
+ """
+ Replaces double and triple square brackets with curly brackets to allow for
+ embedding ICanHaz/Mustache templates in django templates.
+ """
+ nodelist = parser.parse(('endicanhaz',))
+ parser.delete_first_token()
+ return ICanHazNode(nodelist)
+
+
+class ICanHazNode(template.Node):
+ """Performs a find and replace on the contents of the node."""
+ def __init__(self, nodelist):
+ self.nodelist = nodelist
+
+ def render(self, context, ):

Extra comma/space?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/homesnippets/tests/test_templatetags.py
@@ -0,0 +1,25 @@
+from django.template import Context
+from django.template.loader import get_template_from_string
+from django.test import TestCase
+
+from nose.tools import eq_
+
+class TestICanHaz(TestCase):
+
+ def setUp(self):
+ self.context = Context()
+
+ def test_basic(self):
+ """
+ icanhaz replaces double and triple square brackets with curly brackets.

Should be on the line above. (See earlier note about docstrings.)

Also, docstrings should be a complete sentence. I don't feel like this informs the developer of the test's how/why.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
site_media/snippetBodyWidget.js
((33 lines not shown))
- function previewSnippet(snippet) {
- form_textarea.val(snippet);
- preview.html(snippet);
- }
+ function updatePreview() {
+ var code = ich.snippet_template(snippet, true);
+ elems['code'].val(code);

Dot syntax is shorter for what it's worth, and also nicer and more natural, as you're really accessing an object property. elems.code not elems['code'].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
site_media/snippetBodyWidget.js
((44 lines not shown))
- // Generate snippet code
- function generateSnippet(icon_uri, text) {
- // Add comment to easily identify "basic" snippets
- var snippet = '<!--basic--><div class="snippet">';
- if (icon_uri !== '') {
- snippet += '<img class="icon" src="' + icon_uri + '" />';
- }
- snippet += '<p>' + wiki2link(text) + '</p></div>';
+ // Sends icon URL to server to encode in base64
+ function encodeIcon(icon_url, successCallback) {
+ // TODO: Support non-png images
+ $.ajax({
+ url: '/base64encode?url=' + encodeURIComponent(icon_url),
+ dataType: 'json',
+ error: function() {
+ alert('Error encoding icon. Please check that the icon URL points to a valid PNG image.');

alert() for user notifications? Kinda annoying. Maybe at least TODO: make pretty?

@Osmose Owner
Osmose added a note

FWIW, this is only used in the django admin interface, so it's not user-facing. But yeah, it's ugly.

Admins are users too, but if you aren't expecting this to happen a lot I suppose it's alright.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
site_media/snippetBodyWidget.js
((68 lines not shown))
- return snippet;
- }
+ // Bind events and do UI
+ $('#snippet-text').bind('change keyup', function() {
+ snippet['text'] = wiki2link(elems['text'].val());

You can use dot notation in here too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
site_media/snippetBodyWidget.js
((109 lines not shown))
- // Bind events and do UI
- $('#snippet-text').bind('change keyup', function() {
- generateAndPreviewSnippet(preview_icon_data, snippet_text_input.val());
- });
- $('#snippet-embed-button').click(function() {
- encodeIcon(icon_url_input.val(), function(icon_data) {
- preview_icon_data = icon_data;
- generateAndPreviewSnippet(icon_data, snippet_text_input.val());
- });
- });
- $('#snippet-editor').easytabs();
+ // Parse snippet code and fill in basic form with pulled info
+ (function() {
+ var snippet_code = elems['code'].val();
+ if (snippet_code.match(/<!--basic-->/) === null) {
+ if (snippet_code != '') {

!== would be preferred in JS, yo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
site_media/snippetBodyWidget.js
((129 lines not shown))
+ // Be dumb and throw a regex at it
+ var img_matches = snippet_code.match(/<img class="icon" src="([\s\S]+)" \/>/),
+ icon_matches = snippet_code.match(/<!--icon:([\s\S]+)-->/);
+ if (img_matches || icon_matches) {
+ snippet['icon'] = {
+ url: (icon_matches ? icon_matches[1] : ''),
+ data: (img_matches ? img_matches[1] : '')
+ };
+ elems['icon_url'].val(snippet['icon']['url']);

More dot syntax opportunities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
site_media/snippetBodyWidget.js
((129 lines not shown))
+ // Be dumb and throw a regex at it

Kind of a weird comment o_O

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tofumatt

I'm pretty OK with this; lots of nits, but it's overall good. Could you fix them up and I'll have another quick run-through?

apps/homesnippets/tests/test_templatetags.py
@@ -0,0 +1,25 @@
+from django.template import Context
+from django.template.loader import get_template_from_string
+from django.test import TestCase
+
+from nose.tools import eq_
+
+class TestICanHaz(TestCase):
+
+ def setUp(self):

I'm probably a bit overzealous about docs/docstrings, but I really feel like every Class/method ought have one. Even if it's simply: "why this method exists".

I feel like it makes our code look really well-maintained and it means there's less groking needed when someone new checks out your code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tofumatt tofumatt commented on the diff
apps/homesnippets/tests/test_templatetags.py
@@ -0,0 +1,25 @@
+from django.template import Context
+from django.template.loader import get_template_from_string
+from django.test import TestCase
+
+from nose.tools import eq_
+
+class TestICanHaz(TestCase):
+

What I wrote below applies to the class as well. What is this testing? Even one line (even if it's a bit repetitive based on the Class's name) will do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
apps/homesnippets/tests/test_templatetags.py
((8 lines not shown))
+
+ def setUp(self):
+ self.context = Context()
+
+ def test_basic(self):
+ """Tests icanhaz bracket replacement."""
+ template_str = """
+ {% load icanhaz %}
+ {% icanhaz %}
+ Nyan [Nyan] [[Nyan]] [[[Nyan]]]
+ {% endicanhaz %}
+ """
+ template = get_template_from_string(template_str)
+ result = template.render(self.context).strip()
+
+ # Single brackets should be preserved, but double and triple

Make this the third argument to your eq_() call and it will show up if the test fails. That's more useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tofumatt tofumatt commented on the diff
site_media/ICanHaz.js
((3 lines not shown))
+More info at: http://icanhazjs.com
+*/
+(function ($) {
+/*!
+ mustache.js -- Logic-less templates in JavaScript
+
+ by @janl (MIT Licensed, https://github.com/janl/mustache.js/blob/master/LICENSE).
+
+ See http://mustache.github.com/ for more info.
+*/
+
+var Mustache = function() {
+ var Renderer = function() {};
+
+ Renderer.prototype = {
+ otag: "{{",

I like that your quotes are consistent, but make them consistently single-quotes. Good rule of thumb is "construct your string literals the same as you would in Python" (re: when to use single/double quotes).

Wait a second -- I'm a moron. This is a library. It's not your fault.

lololololololololol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tofumatt

Let me know what you think about the alt attribute. Otherwise I think this is good r+wc.

@Osmose
Owner

Fixing the alt attribute is going to be a bit more work and is out of the scope of this pull request, I think.

I'll leave out the alt attribute for now; I've filed a bug for using a semantic alternative to an image tag: https://bugzilla.mozilla.org/show_bug.cgi?id=698596

@Osmose Osmose Refactor snippet editor and save snippet icon url.
Switches to using ICanHaz for JS templating and reorganizes the
snippet editor to be a bit more manageable.

In addition, the snippet editor saves the icon URL in a comment
in the snippet so the url box keeps the icon URL between saves.
83c230e
@tofumatt

That works for me. ARRRRRRRRR PLUS!

@Osmose Osmose merged commit 83c230e into mozilla:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 31, 2011
  1. @Osmose

    Refactor snippet editor and save snippet icon url.

    Osmose authored
    Switches to using ICanHaz for JS templating and reorganizes the
    snippet editor to be a bit more manageable.
    
    In addition, the snippet editor saves the icon URL in a comment
    in the snippet so the url box keeps the icon URL between saves.
This page is out of date. Refresh to see the latest.
View
4 apps/homesnippets/admin.py
@@ -120,7 +120,9 @@ class Media:
css = {
'all': ('snippetBodyWidget.css',)
}
- js = ('jquery-1.6.1.min.js', 'jquery.easytabs.min.js',
+ js = ('jquery-1.6.1.min.js',
+ 'jquery.easytabs.min.js',
+ 'ICanHaz.js',
'snippetBodyWidget.js')
def render(self, name, value, attrs=None):
View
15 apps/homesnippets/templates/snippetBodyWidget.html
@@ -1,4 +1,19 @@
+{% load icanhaz %}
<div id="snippet-editor">
+ {# ICanHaz Templates #}
+ {% icanhaz %}
+ <script type="text/html" id="snippet_template">
+ <!--basic-->
+ <div class="snippet">
+ [[#icon]]
+ <!--icon:[[ url ]]-->
+ <img class="icon" src="[[ data ]]" />

If this is HTML5 you don't need the trailing slash for img tags. Also: no alt attribute?

@Osmose Owner
Osmose added a note

Snippets unfortunately aren't HTML5, but have to be valid XML: http://dxr.mozilla.org/mozilla/mozilla-central/browser/base/content/aboutHome.js.html#l239

As for the alt attribute, would an empty alt do? If not, could we use an empty alt and add a bug for letting the user specify the alt text? (This also brings up issues of localizing the alt text when the snippet is submitted for translation)

Empty alt is bad; it's a legit thing for screenreaders and such to use. If this is on the about:home page I'd argue we put our best foot forward and make it sexy, accessible HTML.

How hard would it be to have an alt attribute in here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ [[/icon]]
+ <p>[[[ text ]]]</p>
+ </div>
+ </script>
+ {% endicanhaz %}
+
<ul id="snippet-tabs">
<li><a href="#snippet-basic">{{ _('Basic') }}</a></li>
<li><a href="#snippet-advanced">{{ _('Advanced') }}</a></li>
View
0  apps/homesnippets/templatetags/__init__.py
No changes.
View
34 apps/homesnippets/templatetags/icanhaz.py
@@ -0,0 +1,34 @@
+from django import template
+
+register = template.Library()
+
+
+SYMBOLS = [

Hahaha this is awesomely brain-hurty to read at first. I was searching for ASCII art.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ ('[[[', '{{{'),
+ (']]]', '}}}'),
+ ('[[', '{{'),
+ (']]', '}}'),
+]
+
+
+@register.tag
+def icanhaz(parser, token):
+ """Replaces double and triple square brackets with curly brackets.

Docstrings first sentence should be one line and on the first line; anything else comes after an empty line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ Used for embedding ICanHaz/Mustache templates in django templates.
+ """
+ nodelist = parser.parse(('endicanhaz',))
+ parser.delete_first_token()
+ return ICanHazNode(nodelist)
+
+
+class ICanHazNode(template.Node):
+ """Parses template code and replaces certain symbols."""
+ def __init__(self, nodelist):
+ self.nodelist = nodelist
+
+ def render(self, context):
+ output = self.nodelist.render(context)
+ for find, replace in SYMBOLS:
+ output = output.replace(find, replace)
+ return output
View
27 apps/homesnippets/tests/test_templatetags.py
@@ -0,0 +1,27 @@
+from django.template import Context
+from django.template.loader import get_template_from_string
+from django.test import TestCase
+
+from nose.tools import eq_
+
+class TestICanHaz(TestCase):
+ """Test the icanhaz template tag."""

What I wrote below applies to the class as well. What is this testing? Even one line (even if it's a bit repetitive based on the Class's name) will do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ def setUp(self):
+ """Create empty context to render test templates with."""
+ self.context = Context()
+
+ def test_basic(self):
+ """Tests icanhaz bracket replacement."""
+ template_str = """
+ {% load icanhaz %}
+ {% icanhaz %}
+ Nyan [Nyan] [[Nyan]] [[[Nyan]]]
+ {% endicanhaz %}
+ """
+ template = get_template_from_string(template_str)
+ result = template.render(self.context).strip()
+
+ eq_(result, 'Nyan [Nyan] {{Nyan}} {{{Nyan}}}',
+ "Single brackets should' be preserved, but double and triple "
+ "brackets should be replaced.")
View
401 site_media/ICanHaz.js
@@ -0,0 +1,401 @@
+/*!
+ICanHaz.js version 0.9 -- by @HenrikJoreteg
+More info at: http://icanhazjs.com
+*/
+(function ($) {
+/*!
+ mustache.js -- Logic-less templates in JavaScript
+
+ by @janl (MIT Licensed, https://github.com/janl/mustache.js/blob/master/LICENSE).
+
+ See http://mustache.github.com/ for more info.
+*/
+
+var Mustache = function() {
+ var Renderer = function() {};
+
+ Renderer.prototype = {
+ otag: "{{",

I like that your quotes are consistent, but make them consistently single-quotes. Good rule of thumb is "construct your string literals the same as you would in Python" (re: when to use single/double quotes).

Wait a second -- I'm a moron. This is a library. It's not your fault.

lololololololololol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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 "&amp;";
+ case "\\": return "\\\\";
+ case '"': return '\"';
+ case "<": return "&lt;";
+ case ">": return "&gt;";
+ 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.0",
+
+ /*
+ 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");
+ }
+ }
+ });
+}();/*!
+ ICanHaz.js -- by @HenrikJoreteg
+*/
+/*global jQuery */
+function ICanHaz() {
+ var self = this;
+ self.VERSION = "0.9";
+ self.templates = {};
+ self.partials = {};
+
+ // public function for adding templates
+ // We're enforcing uniqueness to avoid accidental template overwrites.
+ // If you want a different template, it should have a different name.
+ self.addTemplate = function (name, templateString) {
+ if (self[name]) throw "Invalid name: " + name + ".";
+ if (self.templates[name]) throw "Template \" + name + \" exists";
+
+ self.templates[name] = templateString;
+ self[name] = function (data, raw) {
+ data = data || {};
+ var result = Mustache.to_html(self.templates[name], data, self.partials);
+ return raw ? result : $(result);
+ };
+ };
+
+ // public function for adding partials
+ self.addPartial = function (name, templateString) {
+ if (self.partials[name]) {
+ throw "Partial \" + name + \" exists";
+ } else {
+ self.partials[name] = templateString;
+ }
+ };
+
+ // grabs templates from the DOM and caches them.
+ // Loop through and add templates.
+ // Whitespace at beginning and end of all templates inside <script> tags will
+ // be trimmed. If you want whitespace around a partial, add it in the parent,
+ // not the partial. Or do it explicitly using <br/> or &nbsp;
+ self.grabTemplates = function () {
+ $('script[type="text/html"]').each(function (a, b) {
+ var script = $((typeof a === 'number') ? b : a), // Zepto doesn't bind this
+ text = (''.trim) ? script.html().trim() : $.trim(script.html());
+
+ self[script.hasClass('partial') ? 'addPartial' : 'addTemplate'](script.attr('id'), text);
+ script.remove();
+ });
+ };
+
+ // clears all retrieval functions and empties caches
+ self.clearAll = function () {
+ for (var key in self.templates) {
+ delete self[key];
+ }
+ self.templates = {};
+ self.partials = {};
+ };
+
+ self.refresh = function () {
+ self.clearAll();
+ self.grabTemplates();
+ };
+}
+
+window.ich = new ICanHaz();
+
+// init itself on document ready
+$(function () {
+ ich.grabTemplates();
+});
+})(window.jQuery || window.Zepto);
View
162 site_media/snippetBodyWidget.js
@@ -1,92 +1,96 @@
-jQuery(function($) {
- var icon_url_input = $('#snippet-icon-url');
- var snippet_text_input = $('#snippet-text');
- var preview = $('#snippet-preview');
- var form_textarea = $('#id_body');
+SnippetBodyWidget = function($) {
+ var elems = {
+ icon_url: $('#snippet-icon-url'),
+ text: $('#snippet-text'),
+ preview: $('#snippet-preview'),
+ code: $('#id_body')
+ };
- var preview_icon_data = '';
+ var snippet = {
+ text: '',
+ icon: null
+ };
- function wiki2link(str) {
- return str.replace(/\[(.+)\|(.+)\]/, '<a href="$1">$2</a>');
- }
+ function wiki2link(str) {
+ return str.replace(/\[(.+)\|(.+)\]/, '<a href="$1">$2</a>');
+ }
- function link2wiki(str) {
- return str.replace(/<a href="(.+)">(.+)<\/a>/, '[$1|$2]');
- }
+ function link2wiki(str) {
+ return str.replace(/<a href="(.+)">(.+)<\/a>/, '[$1|$2]');
+ }
- function previewSnippet(snippet) {
- form_textarea.val(snippet);
- preview.html(snippet);
- }
+ function updatePreview() {
+ var code = ich.snippet_template(snippet, true);
+ elems.code.val(code);
+ elems.preview.html(code);
+ }
- // Generate snippet code
- function generateSnippet(icon_uri, text) {
- // Add comment to easily identify "basic" snippets
- var snippet = '<!--basic--><div class="snippet">';
- if (icon_uri !== '') {
- snippet += '<img class="icon" src="' + icon_uri + '" />';
- }
- snippet += '<p>' + wiki2link(text) + '</p></div>';
+ // Sends icon URL to server to encode in base64
+ function encodeIcon(icon_url, successCallback) {
+ // TODO: Support non-png images
+ // TODO: Show animation while waiting for image encoding
+ $.ajax({
+ url: '/base64encode?url=' + encodeURIComponent(icon_url),
+ dataType: 'json',
+ error: function() {
+ // TODO: Make pretty
+ alert('Error encoding icon. Please check that the icon URL points to a valid PNG image.');
+ },
+ success: function(data) {
+ if (typeof successCallback === 'function') {
+ successCallback('data:image/png;base64,' + data['img']);
+ }
+ }
+ });
+ }
- return snippet;
- }
+ // Bind events and do UI
+ $('#snippet-text').bind('change keyup', function() {
+ snippet.text = wiki2link(elems.text.val());
+ updatePreview();
+ });
- function generateAndPreviewSnippet(icon_uri, text) {
- var snippet_code = generateSnippet(icon_uri, text);
- previewSnippet(snippet_code);
- }
+ $('#snippet-embed-button').click(function() {
+ encodeIcon(elems.icon_url.val(), function(icon_data) {
+ snippet.icon = {
+ url: elems.icon_url.val(),
+ data: icon_data
+ };
+ updatePreview();
+ });
+ });
+ $('#snippet-editor').easytabs();
- // Sends icon URL to server to encode in base64
- function encodeIcon(icon_url, successCallback) {
- // TODO: Support non-png images
- $.ajax({
- url: '/base64encode?url=' + encodeURIComponent(icon_url),
- dataType: 'json',
- error: function() {
- alert('Error encoding icon. Please check that the icon URL points to a valid PNG image.');
- },
- success: function(data) {
- if (typeof successCallback === 'function') {
- successCallback('data:image/png;base64,' + data['img']);
- }
- }
- });
- }
- // Bind events and do UI
- $('#snippet-text').bind('change keyup', function() {
- generateAndPreviewSnippet(preview_icon_data, snippet_text_input.val());
- });
- $('#snippet-embed-button').click(function() {
- encodeIcon(icon_url_input.val(), function(icon_data) {
- preview_icon_data = icon_data;
- generateAndPreviewSnippet(icon_data, snippet_text_input.val());
- });
- });
- $('#snippet-editor').easytabs();
+ // Parse snippet code and fill in basic form with pulled info
+ (function() {
+ var snippet_code = elems.code.val();
+ if (snippet_code.match(/<!--basic-->/) === null) {
+ if (snippet_code !== '') {
+ $('#snippet-editor').easytabs('select', '#snippet-advanced');
+ }
+ return;
+ }
+ // Simple parsing using regex
+ var img_matches = snippet_code.match(/<img class="icon" src="([\s\S]+)" \/>/),
+ icon_matches = snippet_code.match(/<!--icon:([\s\S]+)-->/);
+ if (img_matches || icon_matches) {
+ snippet.icon = {
+ url: (icon_matches ? icon_matches[1] : ''),
+ data: (img_matches ? img_matches[1] : '')
+ };
+ elems.icon_url.val(snippet.icon.url);
+ }
- // Parse snippet code and fill in basic form with pulled info
- (function() {
- var snippet_code = form_textarea.val();
- if (snippet_code.match(/<!--basic-->/) === null) {
- if (snippet_code != '') {
- $('#snippet-editor').easytabs('select', '#snippet-advanced');
- }
- return;
- }
+ var text_matches = snippet_code.match(/<p>(.+)<\/p>/);
+ if (text_matches !== null) {
+ snippet.text = text_matches[1];
+ elems.text.val(link2wiki(text_matches[1]));
+ }
- // Be dumb and throw a regex at it
- var img_matches = snippet_code.match(/<img class="icon" src="([\s\S]+)" \/>/);
- if (img_matches !== null) {
- preview_icon_data = img_matches[1];
- }
+ updatePreview();
+ })();
+};
- var text_matches = snippet_code.match(/<p>(.+)<\/p>/);
- if (text_matches === null) return;
-
- var snippet_text = link2wiki(text_matches[1]);
- snippet_text_input.val(snippet_text);
- generateAndPreviewSnippet(preview_icon_data, snippet_text);
- })();
-});
+jQuery(SnippetBodyWidget);
Something went wrong with that request. Please try again.