From 8e8bed0efa74c8bfc77e3b794fac8f3d4b824351 Mon Sep 17 00:00:00 2001 From: Isaac Bythewood Date: Wed, 20 Feb 2013 07:59:45 +0000 Subject: [PATCH] Rewrite the display of recent pins without the use of third-party js libs --- pinry/api/api.py | 23 +- pinry/api/urls.py | 10 +- pinry/core/static/core/css/pinry.css | 6 +- pinry/core/static/core/js/pinry.js | 186 +- pinry/core/templates/core/base.html | 31 +- pinry/core/templatetags/verbatim.py | 44 + pinry/pins/templates/pins/recent_pins.html | 2 +- pinry/urls.py | 2 +- .../static/vendor/handlebars/handlebars.js | 2201 +++++++++++++++++ requirements.txt | 2 +- 10 files changed, 2368 insertions(+), 139 deletions(-) create mode 100644 pinry/core/templatetags/verbatim.py create mode 100644 pinry/vendor/static/vendor/handlebars/handlebars.js diff --git a/pinry/api/api.py b/pinry/api/api.py index cd67d36..ba2bab1 100644 --- a/pinry/api/api.py +++ b/pinry/api/api.py @@ -1,15 +1,23 @@ from tastypie.resources import ModelResource from tastypie import fields -from tastypie.authentication import BasicAuthentication -from tastypie.authorization import DjangoAuthorization from django.contrib.auth.models import User from pinry.pins.models import Pin +class UserResource(ModelResource): + class Meta: + queryset = User.objects.all() + resource_name = 'user' + excludes = ['email', 'password', 'is_superuser', 'first_name', + 'last_name', 'is_active', 'is_staff', 'last_login', 'date_joined'] + include_resource_uri = False + + class PinResource(ModelResource): # pylint: disable-msg=R0904 tags = fields.ListField() + submitter = fields.ForeignKey(UserResource, 'submitter', full=True) class Meta: queryset = Pin.objects.all() @@ -17,6 +25,7 @@ class PinResource(ModelResource): # pylint: disable-msg=R0904 include_resource_uri = False filtering = { 'published': ['gt'], + 'submitter': ['exact'] } def build_filters(self, filters=None): @@ -37,13 +46,3 @@ class PinResource(ModelResource): # pylint: disable-msg=R0904 tags = bundle.data.get('tags', []) bundle.obj.tags.set(*tags) return super(PinResource, self).save_m2m(bundle) - - -class UserResource(ModelResource): - class Meta: - queryset = User.objects.all() - resource_name = 'auth/user' - excludes = ['email', 'password', 'is_superuser'] - # Add it here. - authentication = BasicAuthentication() - authorization = DjangoAuthorization() diff --git a/pinry/api/urls.py b/pinry/api/urls.py index 7590b6d..cdfb890 100644 --- a/pinry/api/urls.py +++ b/pinry/api/urls.py @@ -1,14 +1,16 @@ from django.conf.urls import patterns, include, url +from tastypie.api import Api + from .api import PinResource from .api import UserResource -pin_resource = PinResource() -user_resource = UserResource() +v1_api = Api(api_name='v1') +v1_api.register(PinResource()) +v1_api.register(UserResource()) urlpatterns = patterns('', - url(r'', include(pin_resource.urls)), - url(r'', include(user_resource.urls)), + url(r'^api/', include(v1_api.urls)), ) diff --git a/pinry/core/static/core/css/pinry.css b/pinry/core/static/core/css/pinry.css index c673dfc..bd36399 100644 --- a/pinry/core/static/core/css/pinry.css +++ b/pinry/core/static/core/css/pinry.css @@ -70,18 +70,14 @@ body { #pins { top: 70px; - position: absolute; - background: #eee; - z-index: 100; } .pin { background: #fff; width: 200px; - float: left; border: 1px solid #ccc; padding: 20px; - display: none; + position: absolute; } .pin img { diff --git a/pinry/core/static/core/js/pinry.js b/pinry/core/static/core/js/pinry.js index 6f46836..d529497 100644 --- a/pinry/core/static/core/js/pinry.js +++ b/pinry/core/static/core/js/pinry.js @@ -1,121 +1,85 @@ -/** - * Based on Wookmark's endless scroll. - */ -var apiURL = '/api/pin/?format=json&offset=' -var page = 0; -var handler = null; -var globalTag = null; -var isLoading = false; +$(window).load(function() { + function tileLayout() { + // Config + var blockMargin = 20; + var blockWidth = 240; + // End Config -/** - * When scrolled all the way to the bottom, add more tiles. - */ -function onScroll(event) { - if(!isLoading) { - var closeToBottom = ($(window).scrollTop() + $(window).height() > $(document).height() - 100); - if(closeToBottom) loadData(); - } -}; + var blockContainer = $('#pins'); + var blocks = blockContainer.children('.pin'); + var rowSize = Math.floor(blockContainer.width()/blockWidth); + var blockWidth = (blockContainer.width()-blockMargin*(rowSize))/rowSize; + var colHeights = [] -function applyLayout() { - $('#pins').imagesLoaded(function() { - // Clear our previous layout handler. - if(handler) handler.wookmarkClear(); - - // Create a new layout handler. - handler = $('#pins .pin'); - handler.wookmark({ - autoResize: true, - offset: 3, - itemWidth: 242 - }); - }); -}; - -/** - * Loads data from the API. - */ -function loadData(tag) { - isLoading = true; - $('#loader').show(); - - if (tag !== undefined) { - globalTag = tag; - window.history.pushState(tag, 'Pinry - Tag - '+tag, '/pins/tag/'+tag+'/'); - } else if (url(2) == 'tag') { - tag = url(3); - globalTag = tag; - window.history.pushState(tag, 'Pinry - Tag - '+tag, '/pins/tag/'+tag+'/'); - } - - if (tag !== undefined) { - $('#pins').html(''); - page = 0; - if (tag != null) - $('.tags').html('' + tag + ' x'); - else { - $('.tags').html(''); - window.history.pushState(tag, 'Pinry - Recent Pins', '/pins/'); + for (var i=0; i < rowSize; i++) { + colHeights[i] = 0; } + + for (var b=0; b < blocks.length; b++) { + block = blocks.eq(b); + + var col = -1; + var colHeight = 0; + for (var i=0; i < rowSize; i++) { + if (col < 0) { + col = 0; + colHeight = colHeights[col]; + } else { + if (colHeight > colHeights[i]) { + col = i; + colHeight = colHeights[col]; + } + } + } + + block.css({ + 'margin-left': blockWidth*col+col*blockMargin + }); + + blockMarginTop = blockMargin; + block.css({ + 'margin-top': colHeight+blockMarginTop + }); + colHeights[col] += block.height()+blockMarginTop; + + block.css('display', 'block'); + } + + $('.spinner').css('display', 'none'); + blockContainer.css('height', colHeights.sort().slice(-1)[0]); } - var loadURL = apiURL+(page*30); - if (globalTag !== null) loadURL += "&tag=" + tag; - - $.ajax({ - url: loadURL, - success: onLoadData - }); -}; + var offset = 0; -/** - * Receives data from the API, creates HTML for images and updates the layout - */ -function onLoadData(data) { - data = data.objects; - isLoading = false; - $('#loader').hide(); - - page++; - - var html = ''; - var i=0, length=data.length, image; - for(; i'; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - if (image.description) html += '

'+image.description+'

'; - if (image.tags) { - html += '

'; - for (tag in image.tags) { - html += '' + image.tags[tag] + ' '; - } - html += '

'; - } - html += ''; + function loadPins() { + $('.spinner').css('display', 'block'); + $.get('/api/v1/pin/?format=json&offset='+String(offset), function(pins) { + console.log(pins.objects[0]) + var source = $('#pins-template').html(); + var template = Handlebars.compile(source); + var context = { + pins: pins.objects + } + var html = template(context); + $('#pins').append(html); + + $('#pins').ajaxStop(function() { + tileLayout(); + }); + + offset += 30; + }); } - - $('#pins').append(html); - - applyLayout(); -}; -$(document).ready(new function() { - $(document).bind('scroll', onScroll); - loadData(); -}); + loadPins(); -/** - * On clicking an image show fancybox original. - */ -$('.fancybox').fancybox({ - openEffect: 'none', - closeEffect: 'none' + $(window).resize(function() { + tileLayout(); + }) + + $(window).scroll(function() { + if($(window).scrollTop() + $(window).height() > $(document).height() - 100) { + loadPins(); + } + }); }); diff --git a/pinry/core/templates/core/base.html b/pinry/core/templates/core/base.html index bf1dca9..a087896 100644 --- a/pinry/core/templates/core/base.html +++ b/pinry/core/templates/core/base.html @@ -1,5 +1,6 @@ {% load new_pin %} {% load compress %} +{% load verbatim %} @@ -46,13 +47,35 @@ {% new_pin request %} + {% verbatim %} + + {% endverbatim %} + {% compress js %} - - - - + + + diff --git a/pinry/core/templatetags/verbatim.py b/pinry/core/templatetags/verbatim.py new file mode 100644 index 0000000..e4bf429 --- /dev/null +++ b/pinry/core/templatetags/verbatim.py @@ -0,0 +1,44 @@ +""" +jQuery templates use constructs like: + + {{if condition}} print something{{/if}} + +This, of course, completely screws up Django templates, +because Django thinks {{ and }} mean something. + +Wrap {% verbatim %} and {% endverbatim %} around those +blocks of jQuery templates and this will try its best +to output the contents with no changes. +""" + +from django import template + +register = template.Library() + + +class VerbatimNode(template.Node): + + def __init__(self, text): + self.text = text + + def render(self, context): + return self.text + + +@register.tag +def verbatim(parser, token): + text = [] + while 1: + token = parser.tokens.pop(0) + if token.contents == 'endverbatim': + break + if token.token_type == template.TOKEN_VAR: + text.append('{{') + elif token.token_type == template.TOKEN_BLOCK: + text.append('{%') + text.append(token.contents) + if token.token_type == template.TOKEN_VAR: + text.append('}}') + elif token.token_type == template.TOKEN_BLOCK: + text.append('%}') + return VerbatimNode(''.join(text)) diff --git a/pinry/pins/templates/pins/recent_pins.html b/pinry/pins/templates/pins/recent_pins.html index f79ca90..7315482 100644 --- a/pinry/pins/templates/pins/recent_pins.html +++ b/pinry/pins/templates/pins/recent_pins.html @@ -4,7 +4,7 @@ {% block yield %}
-
+
Loader diff --git a/pinry/urls.py b/pinry/urls.py index 7639a1b..9597811 100644 --- a/pinry/urls.py +++ b/pinry/urls.py @@ -4,7 +4,7 @@ from django.conf import settings urlpatterns = patterns('', - url(r'^api/', include('pinry.api.urls', namespace='api')), url(r'^pins/', include('pinry.pins.urls', namespace='pins')), + url(r'', include('pinry.api.urls', namespace='api')), url(r'', include('pinry.core.urls', namespace='core')), ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/pinry/vendor/static/vendor/handlebars/handlebars.js b/pinry/vendor/static/vendor/handlebars/handlebars.js new file mode 100644 index 0000000..9c653ee --- /dev/null +++ b/pinry/vendor/static/vendor/handlebars/handlebars.js @@ -0,0 +1,2201 @@ +/* + +Copyright (C) 2011 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// lib/handlebars/base.js + +/*jshint eqnull:true*/ +this.Handlebars = {}; + +(function(Handlebars) { + +Handlebars.VERSION = "1.0.0-rc.3"; +Handlebars.COMPILER_REVISION = 2; + +Handlebars.REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '>= 1.0.0-rc.3' +}; + +Handlebars.helpers = {}; +Handlebars.partials = {}; + +Handlebars.registerHelper = function(name, fn, inverse) { + if(inverse) { fn.not = inverse; } + this.helpers[name] = fn; +}; + +Handlebars.registerPartial = function(name, str) { + this.partials[name] = str; +}; + +Handlebars.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Error("Could not find property '" + arg + "'"); + } +}); + +var toString = Object.prototype.toString, functionType = "[object Function]"; + +Handlebars.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + + var ret = ""; + var type = toString.call(context); + + if(type === functionType) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if(type === "[object Array]") { + if(context.length > 0) { + return Handlebars.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + return fn(context); + } +}); + +Handlebars.K = function() {}; + +Handlebars.createFrame = Object.create || function(object) { + Handlebars.K.prototype = object; + var obj = new Handlebars.K(); + Handlebars.K.prototype = null; + return obj; +}; + +Handlebars.logger = { + DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, + + methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, + + // can be overridden in the host environment + log: function(level, obj) { + if (Handlebars.logger.level <= level) { + var method = Handlebars.logger.methodMap[level]; + if (typeof console !== 'undefined' && console[method]) { + console[method].call(console, obj); + } + } + } +}; + +Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; + +Handlebars.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; + + if (options.data) { + data = Handlebars.createFrame(options.data); + } + + if(context && typeof context === 'object') { + if(context instanceof Array){ + for(var j = context.length; i 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; +} +}; +/* Jison generated lexer */ +var lexer = (function(){ +var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, +setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + if (this.options.ranges) this.yylloc.range = [0,0]; + this.offset = 0; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length-len-1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length-1); + this.matched = this.matched.substr(0, this.matched.length-1); + + if (lines.length-1) this.yylineno -= lines.length-1; + var r = this.yylloc.range; + + this.yylloc = {first_line: this.yylloc.first_line, + last_line: this.yylineno+1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, +more:function () { + this._more = true; + return this; + }, +less:function (n) { + this.unput(this.match.slice(n)); + }, +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, +lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +popState:function popState() { + return this.conditionStack.pop(); + }, +_currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, +topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, +pushState:function begin(condition) { + this.begin(condition); + }}); +lexer.options = {}; +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START +switch($avoiding_name_collisions) { +case 0: + if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); + if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); + if(yy_.yytext) return 14; + +break; +case 1: return 14; +break; +case 2: + if(yy_.yytext.slice(-1) !== "\\") this.popState(); + if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); + return 14; + +break; +case 3: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; +break; +case 4: this.begin("par"); return 24; +break; +case 5: return 16; +break; +case 6: return 20; +break; +case 7: return 19; +break; +case 8: return 19; +break; +case 9: return 23; +break; +case 10: return 23; +break; +case 11: this.popState(); this.begin('com'); +break; +case 12: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; +break; +case 13: return 22; +break; +case 14: return 36; +break; +case 15: return 35; +break; +case 16: return 35; +break; +case 17: return 39; +break; +case 18: /*ignore whitespace*/ +break; +case 19: this.popState(); return 18; +break; +case 20: this.popState(); return 18; +break; +case 21: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30; +break; +case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30; +break; +case 23: yy_.yytext = yy_.yytext.substr(1); return 28; +break; +case 24: return 32; +break; +case 25: return 32; +break; +case 26: return 31; +break; +case 27: return 35; +break; +case 28: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35; +break; +case 29: return 'INVALID'; +break; +case 30: /*ignore whitespace*/ +break; +case 31: this.popState(); return 37; +break; +case 32: return 5; +break; +} +}; +lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/]; +lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}}; +return lexer;})() +parser.lexer = lexer; +function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})();; +// lib/handlebars/compiler/base.js +Handlebars.Parser = handlebars; + +Handlebars.parse = function(input) { + + // Just return if an already-compile AST was passed in. + if(input.constructor === Handlebars.AST.ProgramNode) { return input; } + + Handlebars.Parser.yy = Handlebars.AST; + return Handlebars.Parser.parse(input); +}; + +Handlebars.print = function(ast) { + return new Handlebars.PrintVisitor().accept(ast); +};; +// lib/handlebars/compiler/ast.js +(function() { + + Handlebars.AST = {}; + + Handlebars.AST.ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } + }; + + Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { + this.type = "mustache"; + this.escaped = !unescaped; + this.hash = hash; + + var id = this.id = rawParams[0]; + var params = this.params = rawParams.slice(1); + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var eligibleHelper = this.eligibleHelper = id.isSimple; + + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + this.isHelper = eligibleHelper && (params.length || hash); + + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. + }; + + Handlebars.AST.PartialNode = function(partialName, context) { + this.type = "partial"; + this.partialName = partialName; + this.context = context; + }; + + var verifyMatch = function(open, close) { + if(open.original !== close.original) { + throw new Handlebars.Exception(open.original + " doesn't match " + close.original); + } + }; + + Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { + verifyMatch(mustache.id, close); + this.type = "block"; + this.mustache = mustache; + this.program = program; + this.inverse = inverse; + + if (this.inverse && !this.program) { + this.isInverse = true; + } + }; + + Handlebars.AST.ContentNode = function(string) { + this.type = "content"; + this.string = string; + }; + + Handlebars.AST.HashNode = function(pairs) { + this.type = "hash"; + this.pairs = pairs; + }; + + Handlebars.AST.IdNode = function(parts) { + this.type = "ID"; + this.original = parts.join("."); + + var dig = [], depth = 0; + + for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } + } + else { dig.push(part); } + } + + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + + this.stringModeValue = this.string; + }; + + Handlebars.AST.PartialNameNode = function(name) { + this.type = "PARTIAL_NAME"; + this.name = name; + }; + + Handlebars.AST.DataNode = function(id) { + this.type = "DATA"; + this.id = id; + }; + + Handlebars.AST.StringNode = function(string) { + this.type = "STRING"; + this.string = string; + this.stringModeValue = string; + }; + + Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.integer = integer; + this.stringModeValue = Number(integer); + }; + + Handlebars.AST.BooleanNode = function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; + }; + + Handlebars.AST.CommentNode = function(comment) { + this.type = "comment"; + this.comment = comment; + }; + +})();; +// lib/handlebars/utils.js + +var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + +Handlebars.Exception = function(message) { + var tmp = Error.prototype.constructor.apply(this, arguments); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } +}; +Handlebars.Exception.prototype = new Error(); + +// Build out our basic SafeString type +Handlebars.SafeString = function(string) { + this.string = string; +}; +Handlebars.SafeString.prototype.toString = function() { + return this.string.toString(); +}; + +(function() { + var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var badChars = /[&<>"'`]/g; + var possible = /[&<>"'`]/; + + var escapeChar = function(chr) { + return escape[chr] || "&"; + }; + + Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, + + isEmpty: function(value) { + if (!value && value !== 0) { + return true; + } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; + } + } + }; +})();; +// lib/handlebars/compiler/compiler.js + +/*jshint eqnull:true*/ +Handlebars.Compiler = function() {}; +Handlebars.JavaScriptCompiler = function() {}; + +(function(Compiler, JavaScriptCompiler) { + // the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; + + for (var i=0, l=opcodes.length; i 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; + } + } + + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } + + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } + + if (!this.environment.isSimple) { + this.source.push("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; + } + return item; + } + }, + + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; + } + + if (!inverse) { + this.context.aliases.self = "this"; + inverse = "self.noop"; + } + + options.push("inverse:" + inverse); + options.push("fn:" + program); + } + + for(var i=0; i