Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Adding Sammy's Todos to GitHub

  • Loading branch information...
commit e74ac1375af388d8c9c0b41e2a7b507a26db4f86 0 parents
@brandonaaron brandonaaron authored
20 LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright 2010, Brandon Aaron (http://brandonaaron.net/)
+
+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.
38 app.css
@@ -0,0 +1,38 @@
+@font-face {
+ font-family: 'handfont';
+ src: url('assets/handfont.eot');
+}
+
+@font-face {
+ font-family: 'handfont';
+ src: url(//:) format('no404'), url('assets/handfont.woff') format('woff'), url('assets/handfont.ttf') format('truetype'), url('assets/handfont.svg#handfont') format('svg');
+}
+
+body { margin: 0; padding: 0; font-family: handfont; color: #444; }
+ #surface { position: relative; margin: 0 auto; width: 600px; }
+ h1 { position: relative; top: 18px; z-index: 2; margin: 0; padding: 0; width: 100%; height: 1.2em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 25px; font-weight: normal; }
+ #lists { position: absolute; top: 50px; left: 400px; z-index: 2; }
+ dl { margin: 0; padding: 0; color: #555; }
+ dt { margin: 0 0 5px 0; padding: 0; font-size: 20px; font-weight: normal; }
+ dd { margin: 0 0 5px 15px; padding: 0; font-size: 17px; cursor: pointer; }
+ #page { float: left; position: relative; z-index: 1; width: 400px; height: 600px; background: url(assets/paper.jpg) no-repeat; }
+ #page { -webkit-transform: rotate(-5deg); -moz-transform: rotate(-5deg); -o-transform: rotate(-5deg); }
+ h2 { position: absolute; top: 56px; left: 77px; margin: 0; font-size: 30px; width: 275px; height: 1.2em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: normal; }
+ ul { position: absolute; top: 116px; left: 80px; margin: 0; padding: 0; width: 275px; list-style: none; }
+ li { position: relative; margin: 0 0 0 25px; padding: 0; font-size: 20px; line-height: 27px; }
+ li .checkbox { position: absolute; top: 0; left: -25px; display: inline-block; width: 20px; height: 22px; cursor: pointer; background: url(assets/checkbox-unchecked.png) no-repeat; }
+ li.done .checkbox { background-image: url(assets/checkbox-checked.png); /*text-decoration: line-through;*/ }
+ p { position: absolute; top: 116px; left: 80px; margin: 0; padding: 0; width: 275px; font-size: 20px; line-height: 27px; }
+
+
+[contenteditable]:hover { outline: 1px dotted #999; }
+
+
+.new { opacity: .25; cursor: pointer; }
+.new:hover { opacity: .75; }
+
+
+.trashcan { position: absolute; top: 63px; left: 57px; display: none; width: 15px; height: 19px; cursor: pointer; background: url(assets/trashcan.png) no-repeat; opacity: .25; }
+.trashcan:hover { opacity: .75; }
+li .trashcan { top: 1px; left: -48px; }
+#page:hover .trashcan { display: inline-block; }
224 app.js
@@ -0,0 +1,224 @@
+(function($) {
+ var app = $.sammy(function() {
+
+ this.use(Sammy.Template);
+
+ this.notFound = function(verb, path) {
+ this.runRoute('get', '#/404');
+ };
+
+ this.get('#/about', function() {
+ this.partial('templates/about.template', {}, function(html) {
+ $('#page').html(html);
+ });
+ });
+
+ this.get('#/404', function() {
+ this.partial('templates/404.template', {}, function(html) {
+ $('#page').html(html);
+ });
+ });
+
+ this.get('#/list/:id', function() {
+ var list = Lists.get(this.params['id']);
+ if (list) {
+ this.partial('templates/todolist.template', {
+ list: list,
+ todos: Todos.filter('listId', list.id)
+ }, function(html) {
+ $('#page').html(html);
+ });
+ } else {
+ this.notFound();
+ }
+ });
+
+ this.get('#/about', function() {
+ this.log('about');
+ });
+
+
+ // events
+ this.bind('run', function(e, data) {
+ var context = this;
+
+ var title = localStorage.getItem('title') || "Sammy's Todos";
+ $('h1').text(title);
+
+ $('.new')
+ .live('click', function() {
+ var $this = $(this),
+ type = $this.attr('data-type');
+
+ switch (type) {
+ case "list":
+ var list = Lists.create({ name: 'My new list' });
+ Todos.create({ name: 'Something todo', done: false, listId: list.id });
+ context.redirect('#/list/'+list.id);
+ app.trigger('updateLists');
+ break;
+ case "todo":
+ var todo = Todos.create({ name: 'My new todo', done: false, listId: parseInt($('h2').attr('data-id'), 10) });
+ context.partial('templates/_todo.template', todo, function(html) {
+ $(html).insertBefore('#page li:last');
+ });
+ break;
+ }
+ });
+
+ $('#lists')
+ .delegate('dd[data-id]', 'click', function() {
+ context.redirect('#/list/'+$(this).attr('data-id'));
+ app.trigger('updateList');
+ });
+
+ $('.trashcan')
+ .live('click', function() {
+ var $this = $(this);
+ app.trigger('delete', {
+ type: $this.attr('data-type'),
+ id: $this.attr('data-id')
+ });
+ });
+
+ $('.checkbox')
+ .live('click', function() {
+ var $this = $(this),
+ $li = $this.parents('li').toggleClass('done'),
+ isDone = $li.is('.done');
+ app.trigger('mark' + (isDone ? 'Done' : 'Undone'), { id: $li.attr('data-id') });
+ });
+
+ $('[contenteditable]')
+ .live('focus', function() {
+ // store the current value
+ $.data(this, 'prevValue', $(this).text());
+ })
+ .live('blur', function() {
+ var $this = $(this),
+ // grab the, likely, modified value
+ text = $.trim($this.text());
+ if (!text) {
+ // restore the previous value if text is empty
+ $this.text($.data(this, 'prevValue'));
+ } else {
+ if ($this.is('h1')) {
+ // it is the title
+ localStorage.setItem('title', text);
+ } else {
+ // save it
+ app.trigger('save', {
+ type: $this.attr('data-type'),
+ id: $this.attr('data-id'),
+ name: text
+ });
+ }
+ }
+ })
+ .live('keypress', function(event) {
+ // save on enter
+ if (event.which === 13) {
+ this.blur();
+ return false;
+ }
+ });
+
+ if (!localStorage.getItem('initialized')) {
+ // create first list and todo
+ var listId = Lists.create({
+ name: 'My first list'
+ }).id;
+ Todos.create({
+ name: 'My first todo',
+ done: false,
+ listId: listId
+ });
+
+ localStorage.setItem('initialized', 'yup');
+ this.redirect('#/list/'+listId);
+ } else {
+ var lastViewedOrFirstList = localStorage.getItem('lastviewed') || '#/list/' + Lists.first().id;
+ this.redirect(lastViewedOrFirstList);
+ }
+
+ app.trigger('updateLists');
+ });
+
+ this.bind('route-found', function(e, data) {
+ // save the route as the lastviewed
+ localStorage.setItem('lastviewed', document.location.hash);
+ });
+
+ this.bind('save', function(e, data) {
+ var model = data.type == 'todo' ? Todos : Lists;
+ model.update(data.id, { name: data.name });
+ if (data.type == 'list') {
+ app.trigger('updateLists');
+ }
+ });
+
+ this.bind('markDone', function(e, data) {
+ this.log('marking todo with id ' + data.id + ' as done');
+ Todos.update(data.id, { done: true });
+ });
+
+ this.bind('markUndone', function(e, data) {
+ this.log('marking todo with id ' + data.id + ' as not done');
+ Todos.update(data.id, { done: false });
+ });
+
+ this.bind('delete', function(e, data) {
+ if (confirm('Are you sure you want to delete this ' + data.type + '?')) {
+ var model = data.type == 'list' ? Lists : Todos;
+ model.destroy(data.id);
+
+ if (data.type == 'list') {
+ var list = Lists.first();
+ if (list) {
+ this.redirect('#/list/'+list.id);
+ } else {
+ // create first list and todo
+ var listId = Lists.create({
+ name: 'My first list'
+ }).id;
+ Todos.create({
+ name: 'My first todo',
+ done: false,
+ listId: listId
+ });
+
+ this.redirect('#/list/'+listId);
+ }
+ app.trigger('updateLists');
+ } else {
+ // delete the todo from the view
+ $('li[data-id=' + data.id + ']').remove();
+ }
+ }
+ });
+
+ this.bind('updateLists', function(e, data) {
+ var selected = parseInt(location.hash.substr(location.hash.lastIndexOf('/')+1), 10);
+ this.partial('templates/_lists.template', {
+ lists: Lists.getAll(),
+ selected: selected
+ }, function(html) {
+ $('#lists').html(html);
+ });
+ });
+
+ });
+
+ // lists model
+ Lists = Object.create(Model);
+ Lists.name = 'lists';
+ Lists.init();
+
+ // todos model
+ Todos = Object.create(Model);
+ Todos.name = 'todos';
+ Todos.init();
+
+
+ $(function() { app.run(); });
+})(jQuery);
BIN  assets/checkbox-checked.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  assets/checkbox-unchecked.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  assets/handfont.eot
Binary file not shown
BIN  assets/handfont.otf
Binary file not shown
114 assets/handfont.svg
@@ -0,0 +1,114 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<defs>
+<font id="handfont" horiz-adv-x="1046" >
+<font-face font-family="handfont" font-weight="500" font-stretch="normal" units-per-em="2048" panose-1="2 0 6 3 0 0 0 0 0 0" ascent="1638" descent="-410" x-height="686" cap-height="1477" bbox="-49.5 -397 1689.65 1644.25" underline-thickness="102" underline-position="-256" unicode-range="U+0020-U+2022"/>
+<missing-glyph horiz-adv-x="770" />
+<glyph glyph-name=".notdef" horiz-adv-x="770" />
+<glyph glyph-name=".null" horiz-adv-x="0" />
+<glyph glyph-name="nonmarkingreturn" horiz-adv-x="682" />
+<glyph glyph-name="space" unicode=" " horiz-adv-x="770" />
+<glyph glyph-bame="tab" unicode="&#x09;" horiz-adv-x="770" />
+<glyph glyph-bame="uni00A0" unicode="&#xa0;" horiz-adv-x="770" />
+<glyph glyph-name="exclam" unicode="!" horiz-adv-x="684" d="M227 0q-88 25 -61 100q20 61 84 88q12 4 22 9q61 27 105 -33q43 -57 6 -105q-23 -57 -94 -67q-35 -6 -62 8zM285 354q-55 57 -29 201q23 111 23 135q-4 39 12 178q2 23 2 39l20 129q-2 23 9 160q2 25 2 43l22 195q0 6 2 12q-4 63 58 96q68 35 112 -18q14 -70 -16 -226 q-14 -68 -17 -106q-14 -96 -47 -471q-10 -45 -8 -189q2 -94 -18 -147q-14 -47 -86 -41q-25 0 -41 10z" />
+<glyph glyph-name="quotedbl" unicode="&#x22;" horiz-adv-x="757" d="M293 1122q-88 16 -98 138l-11 145q-2 6 -2 12q-37 72 23 131q53 53 108 23q23 -10 39 -35q25 -295 25 -336q-2 -45 -58 -70q-14 -8 -26 -8zM516 1137q-78 6 -82 86v131q-2 20 -10 37q-35 154 0 200q23 33 74 35q68 -25 92 -153q20 -96 22 -107q2 -127 -6 -156 q-18 -81 -90 -73z" />
+<glyph glyph-name="numbersign" unicode="#" horiz-adv-x="1525" d="M676 -98q-86 37 -37 143l68 131q8 18 10 34.5t41 94.5q33 63 -13 66q-14 2 -38 -2q-76 18 -101 -66l-31 -119q-6 -10 -10 -18q27 -66 -39 -113q-57 -41 -100 -16q-29 16 -25 63q-4 37 39 144q33 84 13 114q-31 6 -142 -16q-111 -25 -145 27q-6 10 -12 22q6 82 131 96 l145 15h4q88 -18 123 84l37 137q4 6 6 10q-10 2 -123 0q-115 -4 -121 60q-2 16 2 36q37 51 158 52q100 -2 127 4q47 49 98 194q23 70 41 105q8 72 76 80q63 8 94 -37q29 -45 -20 -101l-80 -153q-23 -53 -6 -80q174 -35 186 113q14 20 47 120q33 96 92 109q12 2 27 2 q80 -33 31 -147l-51 -115q-2 -8 -4 -14q-27 -53 10 -56l125 21q59 0 69 -47q10 -88 -110 -111l-166 -23q-78 -74 -94 -233q31 -10 139 2q109 14 137 -35q8 -16 10 -37q-29 -74 -157 -77q-104 -4 -107 -5q-94 8 -115 -32q-10 -16 -10 -45q-18 -23 -55 -131q-16 -53 -37 -80 l-43 -119q-33 -59 -94 -51zM846 520q4 16 51 125q29 66 21 100q-6 -2 -95 3q-47 2 -65 -15q-14 -27 -58 -162l-14 -45q31 -27 135 -12q15 4 25 6z" />
+<glyph glyph-name="dollar" unicode="$" horiz-adv-x="1021" d="M301 -119q-63 27 -63 129q0 106 -7 127q-4 14 -12 27q-86 111 -78 198q6 55 60 80l59 -36q10 -2 14 14q2 12 -2 37q6 197 17 307q8 86 -31 278l-4 27q33 59 41 197q6 76 14 114q6 61 66 68q61 8 90 -41q12 -20 12 -45q-37 -111 21 -107q18 0 47 13q16 23 22 125 q6 111 56 129q20 8 53 2q76 -29 59 -142l-16 -118v-17q2 -25 57 -108q59 -92 21 -135q-12 -14 -35 -27q-72 -4 -72 -103l2 -135q-2 -23 -8 -37q8 -43 80 -102q76 -63 88 -111l4 -16q39 -129 -59 -250q-47 -57 -109 -88q-2 -160 -92 -164q-25 -2 -53 11q-25 14 -41 73 q-12 41 -43 43q-16 0 -39 -8q-14 -18 -21 -113q-6 -90 -55 -98q-18 -4 -43 2zM494 256q33 86 36 285v37q16 51 -41 100q-39 35 -45 43q-20 -16 -20 -191v-49q2 -8 -8 -129q-8 -111 35 -110q16 0 43 14zM694 354q37 33 13 109q-12 41 -27 37q-12 -6 -8 -62l-2 -96 q4 -21 24 12zM543 819q12 254 14 293q-94 12 -117 -74q-10 -41 0 -86q4 -59 56 -110q27 -27 47 -23z" />
+<glyph glyph-name="percent" unicode="%" horiz-adv-x="1396" d="M375 0q-131 31 0 221l117 180q14 27 22 48q-98 -68 -240 22q-12 6 -22 14q-100 129 -59 312q25 137 126 194q25 14 54 23q129 33 239 -76q31 -31 54 -68q25 -20 28 -112q2 -43 13 -49q47 57 182 266l55 82q51 53 123 37q68 -16 72 -84q10 -70 -68 -148q-76 -74 -94 -106 q-18 -31 -146 -197l-43 -59l-147 -207l-4 -8q-25 -33 -135 -209l-27 -41q-8 -66 -100 -35zM467 616q59 25 43 111q-14 74 -61 90q-31 12 -60 -18q-55 -41 -47 -119q6 -76 66 -82q16 -4 38 2l11 8zM881 -4q-98 -4 -162 133q-29 166 72 309q59 90 170 105q59 6 106 -17 q109 -18 154 -155q6 -20 10 -41l-25 -181q-18 -76 -84 -106q-81 -65 -241 -47zM985 162q117 45 37 160q-6 63 -68 28q-57 -33 -75 -88q-4 -12 -5 -24q-23 -61 33 -84q39 -17 78 8z" />
+<glyph glyph-name="ampersand" unicode="&#x26;" horiz-adv-x="1089" d="M768 -55q-41 0 -94 59q-51 55 -84 53q-25 -2 -55 -28q-164 -39 -308 14q-84 29 -73 174l8 84q18 125 153 289q-10 -12 50 57l-29 223q-4 61 6 113q66 223 168 311q162 117 258 70l20 -12q109 -100 54 -332q-59 -213 -166 -305q-86 -70 -96 -82q-61 -70 -9 -146l70 -153 q25 -39 51 -43q10 10 76 88q72 90 143 65q76 -43 15 -120l-58 -66q-70 -59 -67 -102q0 -18 8 -39q49 -61 27 -123q-14 -39 -60 -51zM410 164q92 -10 116 35q18 37 -28 86q-12 12 -52 106q-29 74 -55 43q-12 -12 -26 -43q-72 -131 -54 -217q8 -20 66 -10q25 4 33 0zM506 770 q111 68 156 207q88 190 30 242q-12 10 -33 14q-100 -31 -151 -189q-4 -16 -23 -73q-18 -61 -2 -152q7 -45 23 -49z" />
+<glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="579" d="M203 1032q-25 66 0 193q12 66 14 100l29 231q8 33 16 58q43 55 103 10q53 -41 47 -100q0 -6 -4 -15q-18 -51 -41 -217q-4 -33 -11 -57q-16 -123 -20 -135q-29 -100 -125 -72z" />
+<glyph glyph-name="parenleft" unicode="(" horiz-adv-x="862" d="M559 -197q-8 -29 -135 23q-113 145 -158 293l-41 178q-4 31 -2 57q-12 37 -16 140q0 27 -4 49l2 190q25 248 33 289q4 27 10 51q57 188 80 236q8 16 14 30q98 166 227 228q74 31 105 -31q29 -59 -10 -111q-18 -25 -50 -32q-133 -96 -186 -252q-39 -106 -68 -365 q-4 -43 -8 -71q2 -231 13 -295q8 -55 22 -101q10 -127 76 -252q8 -25 72 -104q66 -84 32 -137q-2 -7 -8 -13z" />
+<glyph glyph-name="parenright" unicode=")" horiz-adv-x="819" d="M283 -203q-61 59 2 144l88 110q41 57 33 109q61 270 34 741q-2 6 -2 12q-35 248 -71 344q-37 98 -101 164q-94 39 -73 109q16 55 77.5 71.5t110.5 -32.5q82 -80 141 -283q-2 8 13 -43q49 -225 51 -325q16 -201 14 -248q-2 -33 -8 -60q14 -197 -6 -336q-31 -293 -176 -440 q-90 -47 -127 -37z" />
+<glyph glyph-name="asterisk" unicode="*" horiz-adv-x="1095" d="M408 594q-49 41 -23 143q20 74 21 97q16 94 2 100q-10 4 -37 -19q-90 -63 -150 -30q-16 8 -26 26q-27 55 38 103q59 45 70 57l45 45q12 20 -8 37q-72 88 -115 119q-57 45 -28 143q4 16 10 31q102 41 151 -12q12 -6 60 -64q39 -47 43 -12q2 12 2 31q10 125 16 145 q12 41 37 70q39 53 105 26q59 -23 59 -78q-2 -12 -6 -24q-29 -96 -2 -86q10 4 24 20q43 68 127 66q57 -2 92 -43q43 -74 -65 -160q-8 -6 -16 -10q-8 -8 -117 -98q-55 -47 -84 -89q37 -47 182 -196l92 -86q43 -51 13 -101q-49 -72 -127 -38q-41 18 -68 63q-6 4 -61 66 q-49 53 -78 38q-6 -14 -29 -133q-18 -94 -74 -133q-44 -22 -75 -14z" />
+<glyph glyph-name="plus" unicode="+" horiz-adv-x="1236" d="M614 203q-74 4 -92 88q-6 31 0 63l19 209v25q-29 12 -133 18q-55 4 -86 15q-94 2 -140 63q-23 29 -20 64q-33 70 71 71q41 2 80 -8q133 -6 224 6q37 27 32 131q-6 80 4 111q-6 61 54 86q61 25 100 -25q2 -4 6 -8q74 -84 29 -227q4 -47 63 -23q76 31 88 31q10 2 19 -2 q76 -4 115 -64q18 -27 18 -55q-18 -82 -158 -131q-8 -2 -45 -14q-92 -6 -112 -21q-31 -23 -25 -78q-31 -240 -35 -280q-10 -2 -47 -31q-15 -14 -29 -14z" />
+<glyph glyph-name="comma" unicode="," horiz-adv-x="546" d="M156 -231q-92 41 -52 143l64 139q8 27 10 49q-14 96 64 146q72 45 133 0q41 -29 51 -94q-61 -184 -76 -213q-49 -106 -125 -158q-57 -26 -69 -12z" />
+<glyph glyph-name="hyphen" unicode="-" horiz-adv-x="1187" d="M322 537q-66 53 -64 116q2 31 27 56q82 47 272 67q78 8 115 15q186 18 256 -19q63 -33 80 -110q12 -41 -53.5 -84t-125.5 -25l-272 4q-66 -4 -119 -16l-88 -13q-16 1 -28 9z" />
+<glyph glyph-name="period" unicode="." horiz-adv-x="468" d="M195 10q-63 27 -86 101q-27 82 43 118q27 43 104 25q88 -18 104 -92q6 -18 5 -39q-2 -98 -119 -113q-24 -2 -51 0z" />
+<glyph glyph-name="slash" unicode="/" horiz-adv-x="1064" d="M190 25q-115 39 -16 190l72 107q158 264 172 286q29 55 51 105q23 37 131 243l31 56l98 215q35 59 76 90q102 20 129 -62q23 -74 -33 -141q-12 -16 -29 -27q-51 -74 -170 -307q-55 -111 -98 -176q-35 -68 -207 -358q-70 -117 -110 -203q-62 -45 -97 -18z" />
+<glyph glyph-name="zero" unicode="0" horiz-adv-x="1652" d="M633 0q-203 10 -342 184q-92 117 -113 260q-61 469 82 781q96 180 303 243q135 41 271 13q203 -31 327 -181q31 -37 53 -79q100 -80 199 -279q4 -8 27 -57q109 -195 47 -422q-39 -147 -146 -250q-176 -176 -432 -207q-51 -8 -100 -6q-135 -6 -141 -4q-19 0 -35 4z M776 242q174 10 305 131q131 125 115 286q23 207 -235 263q-29 31 -37 125q-8 88 -47 112q-10 6 -27 10q-102 74 -227 37q-100 -31 -142 -119q-57 -123 -73 -385l-2 -47q4 -199 34 -276q39 -96 129 -127q84 -37 207 -10z" />
+<glyph glyph-name="one" unicode="1" horiz-adv-x="765" d="M231 0q-63 51 -30 182q29 123 28 146l74 606l8 43l19 156q25 121 110 145q88 31 149.5 -36.5t26.5 -154.5q-27 -82 -61 -327l-39 -324l-8 -43q-6 -27 -19 -180q-8 -92 -34 -145q-102 -92 -197 -76q-15 2 -27 8z" />
+<glyph glyph-name="two" unicode="2" horiz-adv-x="1228" d="M817 -14q-195 49 -397 30q-154 37 -201 146q-16 37 -16 80q37 109 172 250q121 129 160 184l83 133q53 102 21 166q-45 -8 -139 -70q-92 -61 -158 -49q-82 2 -115 72q-35 74 25 127q14 14 35 22q129 96 272 146q119 51 232 -2q78 -37 108 -115q96 -150 -18 -365 q-6 -8 -11 -16q-37 -111 -184 -260l-84 -84q-57 -70 19 -70q12 2 30 4q264 -29 389 -165q51 -78 7 -142q-45 -61 -132 -39q-41 5 -98 17z" />
+<glyph glyph-name="three" unicode="3" horiz-adv-x="1251" d="M332 20q-94 37 -101 146q-4 63 27 117q59 66 156 36q102 -31 106 -30q27 -4 49 0q74 8 140 67q74 63 24 119l-135 123q-63 68 -86 133q-27 82 49 213q27 47 31 55q-49 14 -168 -6q-80 -12 -125 -2q-98 25 -109 123q-6 74 46 135q76 53 243 47q84 -4 107 -2 q195 -25 272 -63q41 -23 72 -51q82 -98 55 -209q-14 -70 -78 -113q-86 -49 -78 -108q4 -41 60 -68q223 -162 176 -330q-2 -4 -4 -10q-51 -123 -240 -248q-6 -4 -14 -10q-190 -80 -297 -84q-117 0 -139.5 6t-38.5 14z" />
+<glyph glyph-name="four" unicode="4" horiz-adv-x="1228" d="M715 29q-61 68 -51 229l8 88q-2 12 10 133q12 113 -49 129q-27 8 -68 2l-248 8q-45 6 -81 17q-74 51 -50 168l29 127q2 12 2 22q43 221 129 348q70 74 166 13q74 -45 90 -121q23 -53 -31 -123q-57 -74 -63 -108q-2 -10 0 -23q8 -43 86 -57q88 -18 117 30q12 20 2 56 q4 14 12 153q8 141 88 189q96 45 174 -56q45 -57 51 -127q2 -45 -20 -204q-10 -68 -8 -115l-33 -344q-2 -20 -6 -39l-33 -309q-14 -78 -84 -95l-98 -20q-10 -2 -31 20q-6 7 -10 9z" />
+<glyph glyph-name="five" unicode="5" horiz-adv-x="1269" d="M596 0q-203 14 -322 152q-25 27 -41 59q-25 76 31 145q49 59 113 39l139 -82q100 -47 184 -14q76 6 78 66q4 57 -53 92q-29 16 -61 14q-61 25 -189 4q-111 -16 -166 4q-80 25 -106 101q-25 76 28 131q72 133 70 329q-20 158 57 242q35 39 91 51q119 16 421 29 q160 -10 199 -102q47 -53 21 -127q-12 -33 -39 -50q-55 -82 -224 -63q283 -31 -77 8q-106 31 -127 -74l-21 -166l-2 -4q-4 -41 49 -49l111 -12q16 -4 24 -12q164 -43 250 -179q45 -72 51 -151q37 -133 -88 -250q-37 -35 -82 -59q-100 -68 -319 -72z" />
+<glyph glyph-name="six" unicode="6" horiz-adv-x="1118" d="M541 0q-188 16 -262 82q-23 18 -39 41q-53 78 -47 246q2 92 2 118q20 184 94 457q82 213 180 309q104 119 238 195q88 29 163 -41q43 -41 54 -96q16 -55 -84 -137q-12 -10 -15 -13q-170 -115 -233 -266l-43 -100q-12 -49 47 -52q8 -2 16 -2q145 -53 250 -172 q109 -117 64 -311q-4 -12 -6 -22q-31 -102 -189 -177l-74 -32q-16 -8 -94 -21q-12 -4 -22 -6zM516 223q72 23 96 101q27 82 -37 120q-16 10 -38 15q-72 57 -84 -72q-2 -35 0 -74q-12 -102 43 -96q10 2 20 6z" />
+<glyph glyph-name="seven" unicode="7" horiz-adv-x="1181" d="M449 0q-100 -23 -125 94q-18 90 16 168q27 104 137 389q6 16 12 29l144 326q4 16 61.5 122.5t14.5 128.5l-234 -22q-68 -2 -125 10q-80 16 -133 82q-59 76 4 135q41 53 182 47q131 -6 170 2l211 35q102 8 172 -33q125 -53 95 -256q-25 -125 -142 -346q-70 -133 -96 -198 q-23 -39 -86 -215q-23 -66 -43 -107q-70 -262 -82 -346q-102 -70 -153 -45z" />
+<glyph glyph-name="eight" unicode="8" horiz-adv-x="1355" d="M565 -23q-180 8 -315 144q-106 61 -129 178q-16 86 29 158q31 84 151 190l82 74q-18 41 -107 172q-53 78 -73 137q-68 123 -19 285q27 90 84 147q131 125 262 127q150 16 263 -102q109 -111 94 -252q29 -18 96 35q82 66 123 65q35 0 63 -30q76 -70 -10 -191 q-29 -41 -72 -76q-47 -49 -247 -233q-45 -41 -76 -72q16 -55 100 -162q92 -117 101 -196v-17q25 -152 -109 -278q-51 -47 -113 -76q-113 -31 -145 -31q-17 0 -33 4zM623 211q82 35 67 143q-12 94 -80 140q-23 16 -47 18q-256 -209 -76 -289l72 -28q31 -7 64 16zM664 1010 q98 68 116 106q16 31 -63 4q-74 -18 -137 54q-61 70 -27 133q-68 35 -98 -58q-31 -94 4 -180q10 -31 30 -47q18 -96 72 -98q39 -2 74 57l16 16zM1028 1298z" />
+<glyph glyph-name="nine" unicode="9" horiz-adv-x="1230" d="M598 0q-119 10 -121 133q0 84 47 160q45 100 111 348l24 111q6 57 -34 55q-20 0 -52 -14q-168 -23 -294 88q-49 41 -80 94q-82 141 -43 307q6 20 14 41q57 131 180 193q59 27 119 28q178 53 397 -47q174 -104 199 -299l-64 -297q-27 -203 -141 -553l-72 -215 q-27 -68 -63 -108l-98 -27q-17 0 -29 2zM549 1049q111 61 139 102q20 29 29 72q-31 66 -127 77q-104 12 -146 -57q-4 -10 -8 -18q-39 -53 -12 -129q29 -76 90 -62q17 5 35 15z" />
+<glyph glyph-name="colon" unicode=":" horiz-adv-x="468" d="M186 6q-35 -2 -67 45q-12 18 -19 27q-14 68 31 115q18 18 43 22q55 10 100 -41q25 -29 27 -61q18 -70 -47 -105l-16 -8q-17 -2 -52 6zM281 635q-55 -4 -70 43q-45 20 -31 80q6 31 27 49q43 59 100 18q8 -6 15 -14q47 -14 69 -70q20 -57 -20 -84q-65 -30 -90 -22z" />
+<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="555" d="M188 -186q-51 14 -57 59q-6 35 27 59q57 31 37 109q-6 23 -19 43q-29 43 6 94q27 35 64 29q47 -2 98 -70q4 -6 6 -8q59 -92 8 -209q-2 -4 -6 -8q-43 -82 -119 -98q-12 -2 -24 -2q-11 0 -21 2zM317 590q-33 -2 -77 43q-16 18 -27 26q-16 27 12 80q18 31 21 47q47 47 114 5 q20 -10 33 -29q49 -41 33 -107q-8 -29 -33 -47q-31 -20 -76 -18z" />
+<glyph glyph-name="less" unicode="&#x3c;" horiz-adv-x="905" d="M682 0q-16 10 -84 76q-29 27 -53 41q-31 14 -113 82q-51 43 -92 55l-184 92q-31 63 -4 133q18 43 53 53q23 23 51 99q2 10 6 18q66 51 125 189l62 104q16 20 32 31q6 29 54 98q16 25 26 43q33 57 94 2q80 -53 -26 -186q37 45 -23 -27l-108 -155q-6 -10 -11 -21 q-66 -80 -110 -170q0 -16 -33 -72q-31 -53 23 -75l178 -123q111 -74 178 -129q66 -51 22 -133q-4 -10 -10 -19q-28 -22 -53 -6z" />
+<glyph glyph-name="equal" unicode="=" horiz-adv-x="1083" d="M274 420q-53 -6 -59 43q-6 47 31 67q18 12 41 7q33 2 192 26q35 20 162 45l27 4l114 43q55 12 90 -28q47 -37 13 -95q-27 -39 -70 -40q-25 -6 -213 -43q-70 -23 -264 -37h-10q-38 0 -54 8zM387 731l-117 10q-53 14 -59 56q-43 31 -6 69.5t88 16.5l160 2q47 4 84 16 q25 4 112 39q51 20 88 18q55 -20 70 -73q12 -47 -27 -76q-76 -55 -252 -72q-51 -6 -65 -8q-64 0 -76 2z" />
+<glyph glyph-name="greater" unicode="&#x3e;" horiz-adv-x="1077" d="M221 0q-59 4 -65 57q-4 55 47 82l8 4q96 90 252 265l143 155l72 76q37 51 -23 66l-178 135l-215 143q-37 51 14 100q23 25 56 31q43 -4 127 -82q55 -51 84 -63l260 -183l88 -55q70 -53 18 -102q-16 -39 -84 -74q-61 -31 -80 -59l-120 -107l-37 -43l-291 -317 q-49 -39 -76 -29z" />
+<glyph glyph-name="question" unicode="?" horiz-adv-x="813" d="M332 0q-68 14 -74 88q-2 53 31 92q27 68 94 37q27 -12 45 -39q61 -70 29 -115q-6 -8 -19 -16q-57 -49 -86 -49q-10 -2 -20 2zM389 446q-61 14 -72 70q-10 47 35 82q102 156 135 297q2 63 -47 61q-49 -4 -80 -51q-8 -12 -10 -26l-26 -93q-16 -35 -45 -49q-53 -35 -103 13 q-31 29 -24 71q4 59 73 170l21 31q63 96 162 121q119 4 180 -90q80 -68 61 -187q-2 -14 -4 -26l-86 -189q-14 -20 -53 -116q-10 -25 -21 -41q-18 -36 -96 -48z" />
+<glyph glyph-name="at" unicode="@" horiz-adv-x="1236" d="M479 0l-119 23q-16 4 -30 12q-59 23 -72 55q-61 29 -102 150q-16 104 -15 145q-2 156 49 287q12 45 54 141q16 41 63 125q16 29 84 121q68 84 133 121q43 43 131 67q59 18 179 0l88 -51q25 -20 36 -43q27 -33 80 -129q47 -100 41 -223q2 -129 -47 -281l-49 -98 q-14 -23 -33 -37q-94 -100 -168 -104h-12l-61 8q-6 2 -9 6l-79 80q-88 0 -121 16q-43 18 -129 94q-6 4 -11 9q-16 74 7 170q16 70 131 94q72 18 178 -45q86 -66 74 -123q-12 -84 10 -119l4 -8q45 -61 106 69q49 100 62 218q14 137 -47 237q-43 90 -119 113q-31 8 -59 0 q-111 -12 -199 -109q-59 -68 -92 -117q-74 -129 -103 -245q-16 -41 -32 -174q0 -143 14 -189l8 -20q25 -74 107 -109q4 -2 8 -2q66 -18 194 -14q133 12 219 49q12 6 64 45q45 37 82 14q49 -41 24 -102q-14 -41 -53 -53q-23 -16 -114 -45q-14 -4 -25 -9q-94 -20 -317 -22z M559 532q18 29 -14 41q-31 12 -31 -4q0 -8 18 -22l23 -17q2 0 4 2z" />
+<glyph glyph-name="A" unicode="A" horiz-adv-x="1296" d="M205 -203q-88 -2 -72 105q6 33 21 65q8 20 98 307l22 68l41 180q10 33 23 58q43 166 68 237q37 186 139 490q6 10 39 116q23 70 59 101q23 57 88 39q55 -16 66 -68q2 -12 0 -25q49 -53 80 -192q2 -8 2 -12q18 -66 41 -228q4 -10 34 -147q10 -37 21 -64q4 -39 41 -190 q33 -135 43 -211l57 -203l8 -20l39 -113q10 -51 -37 -76q-119 -10 -149 97q-27 41 -53 159l-11 41q-2 61 -82 47q-84 -16 -88 -16q-72 -16 -305 -55q-16 -25 -45 -127q-8 -31 -18 -51q-45 -141 -49 -242q-16 -90 -111 -72q-6 2 -10 2zM580 502q115 14 237 45q41 25 14 112 q-16 59 -18 78q-37 125 -65 275q-4 12 -17 100q-6 47 -26 66q-33 -29 -58 -134q-15 -59 -29 -88l-94 -321q-14 -63 -24 -119q2 -29 55 -18q19 4 25 4z" />
+<glyph glyph-name="B" unicode="B" horiz-adv-x="1327" d="M500 -14q-127 33 -187 75q-23 27 -108 95l-29 24q-20 55 -8 189v16q18 100 25 199q20 125 18 215q-6 35 12 131q6 31 8 51q16 127 21 418q10 37 78 98q20 16 30 29q49 29 168 78q53 37 168 12l17 -4q96 -49 145 -137l74 -132q10 -12 20 -22q68 -78 19 -195 q-6 -14 -13 -26q-8 -25 -63 -94q-31 -39 -35 -66q84 -47 156 -113q37 -31 88 -129q6 -12 12 -22q82 -164 31 -289q-49 -182 -240 -307q-104 -61 -180 -80q-75 -26 -227 -14zM684 168q106 35 236 147q53 70 84 240q-80 180 -193 238q-92 43 -215 4q-147 -41 -170 -54 q-8 -2 -16 -6q-8 -29 -15 -141q-2 -41 -8 -68l-22 -161q0 -31 8 -58q27 -12 45 -69q16 -53 53 -60h18q99 -39 195 -12zM518 987q84 14 170 72q119 94 96 194q-10 37 -73 160q-2 4 -2 8q-29 66 -127 21q-59 -27 -62 -27q-63 -39 -76 -164q0 12 -4 -34l-16 -218q-2 -29 35 -24 q-9 -2 59 12z" />
+<glyph glyph-name="C" unicode="C" horiz-adv-x="1400" d="M520 4q-51 4 -151 66q-25 14 -41 22q-10 12 -74 105q-8 14 -18 24q-29 98 -43 164q-14 70 -3 250q27 129 66 248q10 51 72 153q47 57 168 172q47 35 137 109q80 55 162 70q84 25 182 4q63 -6 184 -56q92 -57 103 -151q-29 -76 -131 -62q-27 4 -54 17q-70 53 -143 65 q-98 16 -244 -82q-8 -6 -20 -14q-121 -88 -207 -260q-45 -96 -55 -234q-41 -182 36 -329q78 -90 156 -97q47 0 170 35l43 10q29 4 148 39q33 10 59 15q94 41 137 6q16 -12 19 -37q-4 -84 -103 -133q-6 -4 -10 -6q-25 -12 -166 -60l-291 -51q-23 -2 -41 -4q-24 0 -47 2z" />
+<glyph glyph-name="D" unicode="D" horiz-adv-x="1359" d="M254 8q-72 35 -96 107q-2 8 -4 14q-12 35 32 70q43 37 41 71l21 201l31 326q18 231 22 260q2 10 0 104q-2 47 14 78q39 61 166 98q41 12 43 13q16 4 107 2q23 0 41 2q199 -14 325 -164l93 -137l75 -135q12 -27 17 -50q25 -43 41 -170q6 -147 -13 -202q-4 -16 -10 -31 q-35 -100 -121 -195q-33 -43 -129 -106q-31 -20 -166 -84q-37 -16 -61 -31q-156 -47 -373 -43q-78 0 -96 2zM606 205q72 16 164 53q111 47 186 117q72 51 86 153q18 76 7 269q-4 35 -52 135l-8 20l-92 136l-27 26q-53 57 -190 62q-55 2 -61 4q-74 4 -111 -56 q-16 -29 -12 -61q-8 -35 -23 -195q-4 -43 -10 -73q-45 -430 -51 -582q20 -35 108 -23q39 6 53 7q17 2 33 8z" />
+<glyph glyph-name="E" unicode="E" horiz-adv-x="1220" d="M365 31q-104 29 -158 61q-45 39 -10 123q25 61 28 78q0 35 25 190q6 43 8 76l43 315q-2 23 8 150q2 10 2 18q8 29 15 146v10l-41 57q-12 27 0 50q41 61 143 125q135 4 199 22q129 35 295 29q119 -6 141 -51q2 -6 4 -15q10 -59 -37 -96q-25 -18 -53 -16l-238 -17 q-76 -10 -135 -37q-90 -4 -106 -69q-4 -18 0 -39q-25 -186 -27 -228q127 10 250 56q106 20 149 -6l13 -9q51 -31 26 -92q-14 -39 -49 -51q-25 -12 -119 -27q-29 -4 -49 -12l-196 -51q-18 -4 -35 -6q-16 -25 -25 -109q-2 -37 -10 -59l-37 -289v-6q14 -39 84 -4q39 18 49 20 q25 6 162 62q72 29 127 32q96 49 168 48q72 -49 78 -82q2 -12 -2 -27q-6 -51 -78 -80q-14 -6 -27 -8q-111 -16 -166 -47q-35 -2 -131 -45q-23 -10 -41 -15q-160 -59 -170 -63q-54 -20 -77 -12zM727 1460z" />
+<glyph glyph-name="F" unicode="F" horiz-adv-x="1222" d="M242 8q-70 18 -43 148q0 2 2 12q16 39 26 168q25 188 19 309q-53 78 -29 139q49 41 41 156v8l-10 230q-2 23 -6 43q-2 29 -33 129q-8 29 -12 53q6 68 102 117l154 -39q10 -2 20 -2q170 -6 356 24q94 41 181 -12q53 -6 65 -59q8 -37 -14 -68q-37 -63 -162 -66 q-137 -16 -346 -18q-82 -4 -94 -8q-35 -14 -27 -60q-10 -31 -4 -125q2 -31 0 -53v-168q27 -39 117 -20q43 10 57 10q80 31 131 -10q47 -4 68 -53q20 -47 -17 -74q-102 -59 -163 -70q-27 -4 -189 -14l-33 -39q-12 -20 11 -35q-20 -18 -9 -72q6 -35 -4 -49q10 -35 2 -143 q-4 -37 0 -61q14 -41 -8 -130q-6 -31 -8 -49q-31 -55 -109 -53q-18 0 -32 4z" />
+<glyph glyph-name="G" unicode="G" horiz-adv-x="1359" d="M582 16q-215 33 -373 238q-4 6 -10 14q-68 102 -68 189q-4 137 41 293l70 243l4 17q6 39 59 135l27 53q72 129 252 252l125 57q27 8 53 13l119 6q51 -2 77 -31q102 -41 201 -141q90 -96 31 -152q-70 -70 -133 -53q-27 4 -45 29l-64 88q-25 25 -53 32q-90 31 -203 -18 q-119 -61 -198 -168q-55 -84 -72 -160q-2 -14 -45 -121q-23 -55 -21 -98l-39 -203q-10 -96 15 -170q12 -45 80 -106q18 -16 28 -27q117 -84 201 -67q123 0 184 82q63 63 76 178q37 84 -113 76l-71 4h-2l-51 71q-16 31 -2 64q4 72 116 76h60q27 8 141 16q23 2 39 4 q78 16 156 -65q57 -20 16 -76q-27 -37 -62 -43q-16 -29 -16 -113q0 -57 -18 -86q12 -27 -25 -88q-16 -27 -20 -41q-96 -154 -252 -188q-133 -29 -215 -15z" />
+<glyph glyph-name="H" unicode="H" horiz-adv-x="1230" d="M268 20q-12 6 -78 7q-51 0 -53 41q-2 6 0 14q-14 31 10 104q12 39 11 64q16 139 12 176q6 23 16 139q4 41 11 68q2 129 18 223q16 223 41 367q12 23 16 102q4 55 29 84q37 55 154 14q2 -2 10 -4q59 -35 18 -137l-14 -39q-23 -51 -43 -168q-16 -45 -23 -186q-2 -23 -4 -39 q-18 -61 62 -70l76 -4q63 -2 196 19l107 4q6 2 10 2q4 53 18 424q0 18 2 35q-4 113 23 147q12 16 29 27q63 25 118 -29q45 -43 33 -94q-8 -39 -26 -248q-6 -63 -15 -107q-8 -207 -12 -245l-14 -140q0 -18 4 -36q-10 -41 -11 -185q-10 -135 7 -254q-16 -33 -82 -33 q-41 0 -58 -8q-59 4 -78 66q-8 31 -2 59q0 -2 33 174q12 209 12 234q-59 6 -190 -8l-166 14l-29 4q-49 37 -73 0q-16 -25 -4 -61q-2 -35 -29 -344l-2 -111q-6 -27 -29 -39l-28 -27q-7 0 -13 4z" />
+<glyph glyph-name="I" unicode="I" horiz-adv-x="1157" d="M510 -41q-16 12 -74 16q-45 6 -59 39l-148 39q-37 14 -57 43q-39 45 -4 92q23 29 57 29q139 -10 146 -10q23 0 41 4q12 29 22 137q2 18 6 35q10 55 21 199q6 18 18 123q2 16 4 28l27 225l8 39q27 182 27 254q-2 0 -178 -26q-6 0 -15 -2q-43 -12 -73 47l-23 43q14 6 8 30 q-4 18 19 25q33 31 129 43q20 2 32 4q29 14 142 33q27 4 45 10l235 17q6 2 13 2q102 -18 135 -78q29 -68 -70 -94q-23 -6 -45 -9q-147 -4 -149 -92q-45 -190 -45 -260l-25 -135q-2 -16 0 -33q-2 -59 -21 -201q-20 -29 -20 -104q0 -39 -6 -63l-12 -187v-4q102 -6 124 -14 q31 -10 52 -33q49 -53 -9 -107q-29 -27 -67 -32q-113 -29 -115 -31q-35 -12 -57 -35q-19 -10 -39 -6z" />
+<glyph glyph-name="J" unicode="J" horiz-adv-x="1437" d="M535 8q-141 6 -248 82q-109 57 -119 172q20 115 72 133q20 6 41 -6q39 -12 67 -76q31 -63 62 -77q8 -4 18 -7q158 -72 244 -57q18 2 35 8q119 39 155 74q25 23 37 53q47 98 23 293q14 -109 -7 47q-8 23 -32 148q-6 27 -13 47q-16 117 -43 231l-26 178q-8 29 -21 49 q-37 14 -139 -2q-45 -8 -70 -6q-61 10 -77 70q-12 45 16 80q25 55 115 53q70 -2 84 0q51 2 206 37q35 2 136 47q72 33 120 29q78 -14 105 -70q12 -25 6 -51q-10 -53 -92 -74q-72 -18 -84 -24q-59 -6 -74 -80q-16 -74 -16 -76q-4 -27 10 -148q4 -29 4 -51q35 -176 39 -184 q2 -6 2 -12l41 -203q-2 -16 16 -129q8 -49 5 -82q10 -66 -39 -195q-74 -115 -140 -143q-8 -4 -16 -6q-141 -74 -272 -74q-96 -2 -131 2z" />
+<glyph glyph-name="K" unicode="K" horiz-adv-x="1284" d="M1016 -96q-25 27 -60 119q-10 31 -20 49q-82 170 -145 237q-106 158 -230 205q-113 61 -192 29q-31 -35 -25 -144q2 -57 -2 -84l-8 -237q0 -14 -2 -29q-35 -39 -135 -37q-33 2 -45 2q-61 29 -43 127l10 49q25 152 37 400q-2 33 10 182v29q14 385 14 409l15 211q2 27 8 49 q20 66 88 80q41 10 78 -10q66 -27 73 -100q6 -51 -30 -88q-10 -41 -33 -222q-4 -20 -6 -38l-11 -199q33 6 72 82q20 39 31 53l102 193q70 123 82 184q59 94 135 82q10 -2 23 -8q80 -16 102 -109q6 -23 6 -47l-106 -104q-23 -25 -37 -52q-20 -27 -80 -137q-12 -25 -24 -43 q-143 -246 -146 -248q-4 -8 -10 -16q252 -104 358 -231q121 -135 224 -340l51 -132q8 -27 6 -51q-6 -47 -78 -65q-34 -8 -57 0z" />
+<glyph glyph-name="L" unicode="L" horiz-adv-x="1222" d="M938 0l-231 49h-9q-102 18 -225 4q-47 -47 -176 -37q-2 0 -14 2q-70 0 -99 45q-16 27 -4 60q45 84 45 190q14 240 15 725l4 168q-2 6 -2 13l-13 133q2 35 19 61q45 63 127 47q57 -10 84 -55q31 -37 8 -123q-8 -31 -10 -45l-13 -201q0 -12 -4 -22l-14 -475l-2 -263 q2 -10 2 -20q27 -8 135 -10q23 0 41 -2q68 -2 281 -43l118 -17q33 -10 41 -34q8 -12 7 -37q-2 -10 20 -19q49 -27 4 -67q-43 -39 -84 -29q-35 0 -51 2z" />
+<glyph glyph-name="M" unicode="M" horiz-adv-x="1589" d="M291 0q-14 6 -76 6q-53 0 -59 49q-8 35 16 119q14 47 12 76l21 237l28 265v8q2 25 25 237q6 49 8 88q6 23 29 160q4 29 10 51q33 72 131 47q27 -8 51 -22q45 -25 72 -117q8 -25 10 -35q29 -55 123 -151q47 -8 90 69q29 51 37 62q20 27 82 151q27 55 53 89q66 68 195 -29 q45 -47 76 -174q4 -12 4 -21l45 -223q2 -8 2 -16q10 -25 22 -127q4 -33 13 -56l32 -213q2 -4 35 -147q2 -10 4 -18l39 -154q12 -31 29 -53q41 -45 -4 -92q-37 -41 -80 -27q-41 -57 -90 12q-23 31 -29 62q-2 14 -49 235q-6 27 -10 49q-12 31 -29 146q-2 25 -6 43l-31 190v6 q-18 51 -41 203q-6 10 -16 82q-8 53 -35 51l-92 -139q-16 -20 -33 -35l-82 -131l-18 -43q-86 -53 -154 -29q-23 10 -39 31q-25 55 -123 174q-45 35 -55 -35l-12 -88v-4l-25 -239q-6 -43 -24 -316v-10l-11 -178q-4 -25 -8 -45q-18 -31 -53 -33q-6 0 -10 2z" />
+<glyph glyph-name="N" unicode="N" horiz-adv-x="1318" d="M233 0q-66 8 -77 70q-8 35 6 67l35 279q31 217 32 241v13q23 150 31 192q20 199 43 295l2 96q8 41 39 62q68 53 141 18q31 -14 52 -45q37 -35 36 -125v-51q6 -27 50 -113q16 -31 22 -53q4 -8 86 -192q37 -98 49 -115q10 -16 23 -23q18 37 33 154l4 27l57 346q4 10 18 98 q8 49 31 76q35 66 146 53q70 -43 73 -106q2 -18 -4 -35q-51 -119 -57 -156q-2 -12 -4 -22q-20 -82 -29 -166l-53 -314q0 -4 -2 -6l-27 -182q-2 -111 -10 -199q16 -86 -33 -121q-16 -12 -39 -12q-88 -25 -147 37l-8 8q-43 55 -90 97q10 0 4 16q-10 29 0 39q0 78 -56 248 l-16 53q-47 127 -62 158q-4 4 -32 65q-18 37 -37 25q-45 -211 -49 -297q-20 -35 -21 -129q0 -45 -8 -72l-20 -227q-4 -27 -11 -49q-12 -20 -98 -21q-15 0 -23 -2z" />
+<glyph glyph-name="O" unicode="O" horiz-adv-x="1425" d="M578 -2q-166 27 -224 51q-16 6 -30 14q-68 29 -113 123q-70 156 -72 244q-14 170 11 275q35 281 139 434q74 121 141 170q119 84 264 43q115 -23 162 -68q84 -82 135 -117q33 -16 84 -80q18 -23 31 -34q47 -101 65 -135q8 -14 43 -107q4 -16 11 -29q41 -160 4 -323 l-49 -135q-6 -14 -17 -27q-12 -39 -76 -119l-24 -31q-88 -78 -129 -96q-10 -4 -19 -8q-98 -45 -184 -43q-114 -4 -153 -2zM680 160q94 6 194 82q80 80 127 200q55 135 35 287l-47 166q-8 53 -71 43q-70 -10 -84 -6l-9 4q-53 4 -67 76q-12 66 -15 71q2 29 -45 52 q-51 23 -61 49q-47 12 -123 -58l-25 -22q-27 -29 -86 -137q-2 -6 -6 -13q-55 -123 -67 -194q-16 -78 -31 -189q-4 -162 10 -217q2 -10 6 -22q49 -111 99 -142q6 -4 10 -6q104 -51 256 -24z" />
+<glyph glyph-name="P" unicode="P" horiz-adv-x="1204" d="M252 0q-102 45 -88 109q0 23 26 102q10 31 11 55q6 27 26 195q4 35 9 59l-33 115q-2 27 6 49q47 98 51 133q2 6 2 12q0 16 14 203q2 29 3 55q6 53 69 136q27 33 115 63q12 4 20 6q143 35 250 -12q125 -35 187 -106q61 -45 96 -134q18 -63 26 -168q-4 -33 -45 -153 q-61 -109 -235 -152q-23 -10 -123 -24q-14 -2 -25 -4q-14 -2 -170 -19q-25 -45 -28 -164q0 -35 -4 -55l-9 -166q-4 -25 -12 -45q6 -59 -82 -84q-16 -4 -39 -8h-8zM573 745q135 23 175 41q47 23 71 64q51 63 27 137q-14 43 -49 64q-88 61 -156 67q-72 12 -102 -65 q-6 -12 -9 -27q-4 -16 -41 -80q-20 -39 -20 -69l-12 -109q2 -25 20 -33z" />
+<glyph glyph-name="Q" unicode="Q" horiz-adv-x="1452" d="M1094 -127q-49 6 -84 72q-37 68 -60 84q-27 4 -104 -21q-45 -14 -76 -8l-223 -2l-39 4l-117 45q-33 16 -57 39q-72 80 -113 262q-20 139 -18 176q2 6 2 15q0 152 22 249q25 137 58 197q29 88 118 211q82 100 218 154q147 68 264 14q94 -33 147 -104l49 -107q16 -45 6 -80 q-6 -20 -59 -49q-45 -23 -27 -47q6 -8 19 -19l98 -110q16 -20 25 -43q102 -190 106 -307v-21q2 -131 -49 -198q-4 -6 -43 -70q-41 -61 12 -94q92 -82 72 -172q-16 -76 -129 -70h-18zM764 141l78 29q41 25 8 68l-57 112q-18 51 -13 94q6 55 66 78q61 23 98 -20q31 -6 53 -68 q18 -49 39 -49q55 94 -14 274l-4 13q-39 90 -172 225l-72 76l-72 84q-25 35 -8 59q33 33 99 17q45 -10 65 -7q-16 66 -102 64q-37 -2 -66 -19q-131 -57 -239 -258q-92 -213 -93 -358q-6 -195 56 -330q57 -82 168 -94q102 -8 182 10z" />
+<glyph glyph-name="R" unicode="R" horiz-adv-x="1241" d="M1008 -4q-90 -4 -113 90q-37 104 -158 195q-25 25 -129 96q-8 4 -14 8l-127 82q-55 43 -64 -47l-2 -51q-2 -20 -41 -226q-6 -33 -10 -59q-14 -59 -78 -76q-51 -14 -90 17q-63 6 -61 86q0 20 6 38q31 57 47 144q18 63 35 250q2 14 2 26q-47 45 -23 158q61 98 60 168l4 154 q-2 23 -4 41q-25 74 8 157q47 68 145 82q35 18 136 23q43 2 71 10q76 4 170 -29q98 -35 131 -98q59 -72 72 -217q0 -98 -51 -170q-33 -72 -127 -150q-16 -16 -96 -59q-37 -20 -54 -39l172 -127q94 -74 117 -106q8 -8 12 -19q-6 -20 0 -18l25 8q6 -2 8 -16q76 -88 90 -109 q10 -18 17 -35q49 -74 16 -135q-6 -10 -12 -18q-39 -47 -80 -33q-4 2 -10 4zM547 799q66 12 155 94q90 98 101 170q-16 72 -84 96q-14 6 -31 6q-115 14 -156 -39q-61 -51 -69 -153l-12 -129q0 -31 18 -49q31 -4 78 4z" />
+<glyph glyph-name="S" unicode="S" horiz-adv-x="1245" d="M594 4q-139 20 -277 82q-74 27 -174 100q-55 49 -24 148q41 70 114 59q10 0 19 -4l92 -80q25 -18 51 -26q25 -12 168 -64q8 -2 19 -6q186 -18 305 20q51 20 -15 84l-30 31q-43 39 -201 146q-61 41 -100 75q-43 27 -125 121q-10 10 -17 19q-59 80 -65 92q-10 23 -15 45 q-43 129 -36 203l24 116q10 33 31 58q35 59 147 96l129 24q16 2 33 0q225 6 318 -102q61 -61 53 -147q-6 -49 -41 -80q-59 -45 -154 -13q-31 14 -41 72q-6 47 -24 62q-88 31 -189 8q-70 -14 -82 -88q-2 -20 0 -41q-2 -45 39 -146q4 -10 9 -18l79 -98q16 -16 31 -27 q18 -18 98 -70q16 -10 29 -20l182 -125q150 -111 179 -274l4 -43q-31 -86 -119 -134q-123 -53 -373 -59h-14q-23 0 -37 4z" />
+<glyph glyph-name="T" unicode="T" horiz-adv-x="1497" d="M635 0q-59 2 -82 66q-12 37 0 71q12 27 16 121q4 39 13 66q14 125 18 258q20 330 21 368v45q20 125 -33 121q-14 0 -33 -8q-39 -2 -182 29q-47 8 -80 12q-131 8 -143 82q4 70 75 106q29 14 62 17q188 4 305 -8q59 -12 246 -13h57q41 -2 192 27q84 14 140 8q80 -55 90 -92 q4 -18 -2 -39q-47 -59 -187 -72q-244 -29 -254 -28q-41 -25 -40 -117q0 -45 -3 -62q-6 -23 -8 -122q-2 -25 -4 -45q-20 -121 -14 -189q-6 -272 -21 -397l-8 -125q-8 -31 -26 -51q-54 -35 -115 -29z" />
+<glyph glyph-name="U" unicode="U" horiz-adv-x="1574" d="M385 2q-84 2 -129 68q-12 16 -18 34q-20 49 -25 185q-2 29 -4 47q-6 246 14 387q8 27 15 137q4 45 12 76q2 12 31 201q0 4 2 8q6 16 28 125q8 35 19 57q4 88 63 119q23 10 49 8q80 20 121 -57q14 -27 19 -56q-4 -29 -52 -122q-25 -47 -28 -84q-63 -217 -86 -633v-21 q-2 -168 -2 -180q4 -47 18 -84q45 -45 131 27q33 27 37 28q115 111 160 187q61 92 92 180q61 147 70 186q12 47 8 93l-2 370q0 20 2 41l35 98q25 49 75 48q74 29 113 -56q8 -16 12 -37q23 -143 2 -358q-33 -158 -18 -262l16 -193q8 -45 23 -82q14 -109 74 -159 q84 -39 106 -125q0 -80 -158 -56q-98 6 -172 132q-6 10 -30 88q-12 39 -31 53q-16 -16 -82 -117q-25 -37 -45 -59q-133 -154 -248 -207q-98 -37 -180 -37q-17 2 -37 2z" />
+<glyph glyph-name="V" unicode="V" horiz-adv-x="1384" d="M537 0q-53 23 -78 117q0 2 -4 20q0 41 -39 158q-14 45 -19 78q-25 68 -41 172l-49 168q-59 174 -94 252l-70 96q-12 20 -14 43q-12 76 45 110q18 10 43 13q76 39 143 -37l11 -10q23 -25 28 -101q4 -53 23 -80q10 -25 45 -163q10 -41 20 -70q2 -20 37 -131q12 -39 15 -68 q10 -74 55 -172q39 14 61 90q16 55 31 74q49 100 78 174l78 141l104 177q74 117 92 166q6 8 33 77q23 61 72 64q133 2 129 -80q-4 -53 -60 -160q-14 -27 -22 -45q-39 -72 -141 -223q-82 -131 -131 -250q-6 -12 -62 -145l-8 -17q-8 -14 -66 -143q-16 -35 -30 -59 q-23 -31 -60 -146q-8 -18 -12 -33q-27 -76 -135 -57h-8z" />
+<glyph glyph-name="W" unicode="W" horiz-adv-x="1812" d="M1161 6q-57 25 -98 123l-16 39q-12 25 -150 283l-18 36q-29 20 -58 -36q-31 -59 -43 -68l-86 -143q-12 -23 -20 -43q-10 -12 -25 -76q-12 -45 -39 -58q-35 -49 -100 -51q-47 0 -70 31q-35 33 -47 129q-2 27 -6 41l-66 211q-49 197 -79 377l-41 198q-4 18 -56 199 q-25 78 58 113q35 14 73 12q66 0 95 -59q14 -35 4 -68q4 -102 30 -223l21 -156l8 -35q25 -143 80 -348q8 -43 33 -45q18 -2 26 33l121 176q41 88 82 205q8 84 72 104q20 6 45 2q117 -31 113 -104q0 -16 -7 -33q-2 -55 45 -176q8 -18 13 -33q14 -18 61 -114q27 -51 53 -76 q25 37 54 155q8 33 14 54l53 198q6 12 37 131q12 39 25 66q10 57 84 178q23 37 34 62q27 115 189 61q57 -55 8 -137l-96 -137l-23 -43q-18 -39 -73 -220l-15 -43q-2 -25 -39 -178q-6 -25 -10 -45q4 14 -88 -340q6 -53 -63 -98q-43 -29 -46 -29q-20 -22 -53 -2z" />
+<glyph glyph-name="X" unicode="X" horiz-adv-x="1372" d="M1022 25q-74 27 -111 135q-53 90 -110 231q-12 31 -84 168q-8 6 -31 74q-18 49 -45 37l-113 -168q-63 -90 -90 -156q4 -102 -65 -182q-16 -82 -123 -76q-20 2 -41 6q-68 14 -66 90q2 41 27 74q66 133 145 258q45 66 103 166l106 174q4 6 8 14q27 39 -20 123 q-12 23 -16 33q-23 49 -84 154q-10 25 -70 96q-37 45 -47 82q-8 63 39 96q29 18 61 12q104 23 144 -84l63 -131l4 -12l70 -139l12 -17l111 136l22 22l166 189q39 39 74 67q57 29 121 12l12 -4q53 -14 51 -73q-2 -39 -26 -62l-76 -90q-16 -14 -33 -27q-209 -225 -217 -235 q-43 -49 -76 -94q-51 -39 -8 -117l22 -43l74 -164q16 -25 62 -129q14 -33 28 -53q49 -109 121 -207q59 -29 45 -96q-6 -35 -35 -60l-59 -35q-25 -9 -45 5z" />
+<glyph glyph-name="Y" unicode="Y" horiz-adv-x="1222" d="M287 0q-61 37 -76 94q-8 33 8 58q63 125 78 182l72 164l67 163q20 33 68 154q4 10 6 16q59 78 -2 146q-12 12 -27 24q-72 72 -166 256q-8 12 -12 21q-6 14 -78 121q-12 17 -20 31q-45 70 -21 137q14 35 47 49q106 35 156 -23l8 -12q8 -12 76 -168l68 -143q8 -14 18 -27 q49 -74 51 -76q41 -47 60 23l98 211q10 20 47 125q10 29 21 49q51 51 133 43q8 0 14 -2q63 16 98 -45q12 -20 13 -45q-10 -12 -5 -58q2 -23 -4 -32l-118 -113q-2 -4 -4 -6q-82 -141 -97 -178q-2 -8 -6 -15q-18 -33 -76 -174l-86 -166q-39 -96 -78 -155q-8 -33 -59 -131 q-25 -45 -33 -78q-43 -80 -63 -191q-4 -10 -17 -108q-8 -68 -43 -98q-55 -33 -106 -27q-6 2 -10 4z" />
+<glyph glyph-name="Z" unicode="Z" horiz-adv-x="1402" d="M197 0q-47 25 -58 84q-4 20 2 33q-29 68 19 96q8 6 20 8q14 39 68 55q4 0 12 3q106 117 146 194l102 156l88 139l139 229l90 146q8 31 -28 31l-90 -9q-123 -31 -158 -51q-6 -4 -14 -8q-100 -39 -150 8q-49 25 -57 82q-4 31 12 53q29 41 133 56q29 4 41 6q160 33 309 51 q78 20 248 20q63 10 76 -73q2 -14 2 -33q33 -63 -64 -139q-41 -43 -129 -176q-29 -45 -51 -74l-194 -297q-18 -25 -101 -158q-14 -18 -94 -158q-6 -10 -10 -18q12 -35 98 -25q47 6 64 5q39 6 180 10q262 8 364 2q59 -35 39 -123q-2 -8 -6 -16q0 -72 -94 -72q-35 0 -66 10 q-78 14 -286 6q-41 -2 -72 -2l-270 -16q-39 -6 -74 -12q-23 -23 -98 -17q-39 2 -52 -6q-9 -12 -36 0z" />
+<glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="1144" d="M139 -135q10 10 25 127q10 74 22 110q0 41 29 222q8 53 10 88l43 276l62 275q6 37 6 69l35 221q4 55 0 101q10 106 104 135q53 16 117 0l295 -55q31 -4 55 -4q88 -12 82 -87q-4 -61 -55 -94q-29 -18 -58 -12q-43 -2 -270 27q-31 4 -53 6q-37 -88 -60 -324 q-16 -158 -36 -229q-8 -98 -35 -348q-2 -37 -51 -240q-35 -150 -19 -190q33 -8 201 10q172 20 194 -31q14 -31 -10 -86q-66 -106 -242 -106q-45 0 -84 8q-63 -12 -174 -15q-63 -47 -108 21q-37 53 -25 125z" />
+<glyph glyph-name="backslash" unicode="\" horiz-adv-x="1101" d="M145 1421q41 92 109 27q14 -14 29 -35q14 -63 94 -209q27 -51 43 -86q78 -166 98 -203q27 -14 31 -90q0 -20 6 -22q4 -2 16 10l117 -219q43 -61 139 -256q8 -31 72 -121q59 -82 41 -129q0 -59 -41 -84l-8 -4q-72 -35 -148 88q14 41 -69 203q-8 14 -12 24 q-113 207 -132 256l-157 291q-178 324 -240 471q-13 57 12 88z" />
+<glyph glyph-name="bracketright" unicode="]" horiz-adv-x="1124" d="M154 -154q37 82 141 72q25 -4 55 -10q162 -8 213 12q72 29 70 111q4 35 4 241q2 82 8 136q-4 51 27 202q10 61 10 99q-4 72 31 258q23 115 18 186q35 121 -74 166q-39 16 -86 14q-45 -6 -98 29q-27 18 -49 -14q-86 -25 -158 45q-61 61 -35 112q18 37 82 45l387 -20 l177 -29q80 -23 75 -112l-28 -377q-8 -61 -19 -115q-20 -68 -28 -264q-6 -100 -21 -162q-14 -319 -12 -365q0 -49 4 -96q27 -82 -12 -143q-31 -51 -37 -66q-43 -20 -135 -22q-51 -2 -93 -12q-164 -63 -307 -19q-49 -23 -88 25q-24 30 -22 73z" />
+<glyph glyph-name="asciicircum" unicode="^" horiz-adv-x="2048" d="M264 1106q25 47 86 127l25 33l121 178q16 23 34 39q59 27 82 4q33 -33 45 -39q72 -94 207 -193q72 -35 88 -67q8 -18 6 -39q39 -80 -6 -115q-31 -23 -80 4q-41 18 -170 129q-78 66 -118 86l-129 -127q-20 -25 -37 -51q-61 -66 -86 -71q-18 -6 -74 -3q-31 10 -10 60 q14 33 16 45z" />
+<glyph glyph-name="underscore" unicode="_" horiz-adv-x="1167" d="M-27 -31l107 25q29 2 82 -6l207 -2q209 6 297 0l356 -2h4q86 12 160 -27q59 -14 37 -49q-27 -37 -105 -37q-176 -16 -317 -2l-228 4h-262l-231 -6q-31 0 -55 4q-68 12 -74 55q-2 12 4 23q12 16 18 20z" />
+<glyph glyph-name="grave" unicode="`" horiz-adv-x="481" d="M102 1468q-4 49 31 76q23 16 49 4q20 12 64 -10q6 -4 10 -4q8 -10 39 -70q8 -14 16 -24q20 -25 62 -117q8 -18 16 -33q-2 -2 4 -20q2 -8 -6 -8q-4 -2 -12 2q-47 -29 -60 -31q-10 0 -20 2q0 12 -21 12q-18 2 -22 19l-72 98q-6 10 -14 18q-6 12 -41 50q-19 18 -23 36z" />
+<glyph glyph-name="a" unicode="a" horiz-adv-x="1140" d="M885 0q-119 37 -162 170q0 6 -2 12q0 47 -35 66q-37 16 -51 -37q-72 -104 -211 -174q-117 -33 -143 -33q-55 -4 -91 27q-70 66 -61 223l4 102q12 248 162 371q16 14 37 27q100 57 278 37q94 -4 131 -91q18 -45 9 -98l75 -199q20 -47 45 -84q2 -55 72 -131q59 -66 68 -104 q-41 -70 -72 -82q-24 -8 -53 -2zM393 201q113 113 168 262q29 82 -35 106q-51 20 -108 -18q-66 -33 -84 -154q-10 -82 -12 -92q2 -104 14 -123q18 -26 57 19z" />
+<glyph glyph-name="b" unicode="b" horiz-adv-x="1058" d="M381 0q-14 4 -86 45q-47 27 -90 23q-57 47 -43 161q8 76 8 74q39 213 43 248q14 238 43 455q10 35 14 168q2 96 33 149q31 41 123 43q29 2 39 2q78 -45 41 -176q-68 -248 -92 -647q27 -10 139 -19q94 -6 139 -47q102 -66 117 -170q4 -31 -2 -61q-39 -143 -207 -209 l-61 -19l-115 -26q-25 0 -43 6zM516 215q51 -2 80 45q29 45 -14 59q-14 6 -39 5q-51 29 -101 -19q-43 -43 -16 -76q10 -12 33 -18q34 -14 57 4z" />
+<glyph glyph-name="c" unicode="c" horiz-adv-x="880" d="M350 0q-100 23 -145 127q-8 20 -12 41q-33 139 36 289q14 72 121 161q41 33 45 35q176 61 262 -49q43 -49 35 -117q-4 -20 -12 -34q-57 -76 -135 -43q-31 12 -56 41q-109 -31 -112 -220q27 -72 108 -45l105 37q39 10 67 -4q51 -72 -59 -156q-43 -31 -84 -47 q-66 -26 -164 -16z" />
+<glyph glyph-name="d" unicode="d" horiz-adv-x="1044" d="M788 -98q-78 16 -104 129l-29 123q-2 2 -2 4q-2 72 -28 104q-20 29 -43 -41q-37 -139 -193 -213q-16 -8 -33 -14q-117 8 -180 149q-16 37 -26 76q-23 254 90 379q59 76 202 72q43 -6 60 57l16 80q23 102 12 342q-6 197 19 303q8 100 123 76q35 -6 67 -23q45 -16 43 -108 q-2 -55 0 -72q8 -223 -22 -508q2 -111 53 -401q12 -70 18 -119q35 -166 41 -180q12 -39 29 -70q72 -98 -39 -133q-33 -10 -74 -12zM389 324q35 39 19 86q-14 37 -35 14q-10 -12 -17 -37q-23 -47 -12 -90q8 -41 45 27z" />
+<glyph glyph-name="e" unicode="e" horiz-adv-x="1058" d="M520 766h21q127 4 200 -80q35 -41 43 -90q2 -119 -116 -186q-72 -43 -152 -37q-70 33 -92 -17q-18 -41 16 -90q29 -119 203 -76q10 2 19 7q49 47 131 36q78 -8 79 -65q0 -20 -8 -43q-94 -104 -323 -125q-221 43 -283 180q-86 158 12 400q117 194 250 186zM559 594 q-4 -2 -67 -14q-27 -47 14 -52q39 -4 63 25q19 23 -10 41z" />
+<glyph glyph-name="f" unicode="f" horiz-adv-x="1060" d="M391 0q-68 -10 -104 53q-25 45 -6 92q0 8 32 119q33 115 -32 133q-12 4 -27 4q-78 -6 -102 72q-23 76 22 129q16 18 39 27q100 -25 121 -23q57 6 41 86l26 336q12 160 70 295q59 96 188 66q61 -14 109 -54q160 -113 160 -245q0 -8 -2 -19q-2 -72 -74 -100 q-76 -29 -123 26q-8 10 -14 23q-10 59 -51 102q-41 45 -48 -30v-19q-29 -193 -47 -434q27 -43 111 -29q94 16 127 4q20 -10 37 -28q76 -66 2 -137q-61 -57 -141 -58q-158 -2 -187 -65q-12 -29 -4 -72l-14 -143q-20 -94 -101 -119z" />
+<glyph glyph-name="g" unicode="g" horiz-adv-x="1085" d="M469 -387q-158 20 -276 147q-59 57 -54 140q6 74 74 92q92 20 129 -94q4 -12 8 -29q90 -94 221 -88q96 -2 140 72q37 61 -2 129q-66 184 -88 229q-4 10 -11 20q-23 6 -123 -4q-98 -8 -135 47q-59 63 -73 207q-25 160 73 289q57 76 140 107q156 27 217 -54q14 -16 20 -39 q31 -45 8 -143q-18 -84 -4 -121q10 -23 6 -102q-2 -66 31 -101l49 -153q12 -29 31 -53q96 -135 45 -332q-47 -80 -209 -137q-6 -2 -14 -4q-121 -43 -203 -25zM545 528q12 55 -35 74q-37 14 -47 -14q-8 -20 8 -53q-33 -39 2 -60q35 -18 59 8q17 16 13 45z" />
+<glyph glyph-name="h" unicode="h" horiz-adv-x="888" d="M594 -111q-47 43 -62 162q-8 68 -16 94q-33 127 -72 127q-20 0 -38 -49q-8 -72 -50 -108q-23 -23 -45 -8q-47 -45 -110 -7q-55 31 -56 88l25 101q6 10 12 22q18 45 37 199q4 45 12 76q29 440 33 592q-29 70 19 125q37 45 92 32q119 -2 110 -133q-2 -39 -16 -82 q-10 -57 -41 -405q-6 -61 -10 -109q-14 -100 33 -108q14 0 34 4q127 -39 185 -162q16 -37 24 -76l49 -160q18 -78 2 -139q-6 -68 -108 -76q-23 -2 -43 0z" />
+<glyph glyph-name="i" unicode="i" horiz-adv-x="770" d="M326 0q-98 23 -70 182l6 31q0 43 31 188q12 53 14 91q2 78 76 104.5t125 -29.5q12 -12 18 -28q25 -49 -8 -140q-20 -59 -22 -88q-12 -33 -19 -151q-4 -66 -20 -97q-6 -41 -74 -61q-34 -10 -57 -2zM414 645q-72 6 -88.5 80t34.5 117q39 35 125 12q63 -16 76 -55 q49 -68 -31 -129q-39 -29 -79 -33q-6 -12 -37 8z" />
+<glyph glyph-name="j" unicode="j" horiz-adv-x="1064" d="M426 -336q-172 35 -236 100q-10 10 -18 23q-51 39 -20 115q29 68 90 69q14 2 30 -4q43 -2 66 -57q20 -49 41 -57q14 -6 39 -3q174 -27 272 29q102 59 17 248q-4 10 -13 29q-31 49 -86 206l-4 13q-53 117 -14 225q8 27 24 49q31 68 113 39q68 -23 88 -78q-31 -176 10 -272 q92 -184 121 -287q35 -133 -43 -233q-25 -31 -57 -54q-147 -115 -264 -104q-105 0 -156 4zM713 909q-72 4 -80 86q-4 66 31 109q61 49 149 6q41 -20 59 -53q8 -84 -55 -131q-47 -35 -104 -17z" />
+<glyph glyph-name="k" unicode="k" d="M199 14q-53 55 -17 164q35 111 37 137v15q6 29 51 317q10 63 21 113q-2 43 24 213q8 61 9 106q12 84 88 117q72 33 131 -16q98 -45 2 -195q-25 -80 -53 -307q-18 -57 6 -51q29 6 55 53q6 12 8 22q63 45 137 0q74 -43 60 -114q-2 -18 -15 -37q-78 -63 -92 -86 q-35 -57 45 -98q8 -12 82 -86q76 -72 72 -138q31 -59 -31 -104q-55 -39 -102 -14l-121 155q-27 29 -55 47q-43 66 -88 80q-39 12 -39 -55q-6 -18 -21 -127q-12 -106 -78 -123q-4 -2 -8 -2q-39 -20 -88 0q-12 6 -20 14z" />
+<glyph glyph-name="l" unicode="l" horiz-adv-x="800" d="M313 0q-66 -16 -90 55q-20 55 6 97q39 158 62 485q27 150 35 367q-10 80 63 120q74 43 135 -6l13 -12q49 -47 24 -160q-14 -63 -16 -80q-16 -113 -47 -360l-35 -393q-2 -10 -2 -23q-4 -78 -80 -88q-19 -2 -68 -2z" />
+<glyph glyph-name="m" unicode="m" horiz-adv-x="1433" d="M1124 -31q-100 -4 -129 109l-24 143l-21 94q-12 27 -37 -32q-10 -16 -65 -133q-53 -113 -129 -140q-61 -37 -123 13q-59 49 -35 118l10 154q-2 49 -18 61q-27 -74 -133 -227l-31 -47q-74 -41 -151 0q-80 43 -54 135q25 156 19 299q-18 104 22 172q37 59 109 29 q41 2 78 -62q27 -43 39 -45q18 -2 45 52q66 66 155 20q80 -39 90 -119q20 -72 56 -31l71 97q41 43 78 30q66 23 127 -45q49 -6 109 -165q20 -59 26 -72q57 -137 11 -318q16 -47 -43 -77q-29 -17 -52 -13z" />
+<glyph glyph-name="n" unicode="n" horiz-adv-x="1107" d="M758 10q-47 57 -53 191q-4 78 -11 108q8 90 -37 105q-45 12 -84 -64q-29 -33 -77 -145q-23 -57 -47 -90q-12 -96 -115 -101q-53 -4 -107 23q-41 47 -20 168q18 111 10 151l14 240l9 43q27 61 98 80q78 16 100 -43q4 -14 6 -31l19 -94q12 -27 49 31q90 90 186 80 q51 -4 88 -48q70 -68 107 -239l12 -64q-6 -31 21 -149q25 -117 -23 -148q-16 -10 -43 -14l-69 -12q-21 2 -33 22z" />
+<glyph glyph-name="o" unicode="o" d="M444 -2q-123 16 -217 195q-102 252 -10 446q4 6 8 14q20 86 158 125q135 45 283 -59q16 -12 32 -27q23 -27 66 -131q33 -84 82 -115q72 -141 18 -253q-10 -23 -26 -41q-63 -121 -224 -148q-43 -6 -86 -4zM543 182q80 8 102 94q16 72 -20 130q-31 16 -84 102 q-51 80 -92 78q-29 0 -64 -33q-47 -182 33 -344q27 -45 125 -27z" />
+<glyph glyph-name="p" unicode="p" horiz-adv-x="1017" d="M242 -362q-113 33 -39 223q4 6 6 14l55 342l4 49q6 23 21 156q10 96 47 147q47 43 131 49q51 4 76 11q137 20 233 -62q18 -14 31 -30q72 -80 49 -183q-10 -43 -39 -75q-80 -137 -205 -191q-16 -8 -104 -25q-78 -12 -84 -63q-2 -16 2 -41q-8 -23 -18 -154 q-10 -113 -62 -151q-57 -30 -104 -16zM543 279q53 23 73 75q25 63 -47 74q-41 -4 -73 -68q-35 -68 0 -86q16 -5 47 5z" />
+<glyph glyph-name="q" unicode="q" horiz-adv-x="1114" d="M680 -387q-160 76 -166 262q-16 35 -6 137q6 88 -37 111q-135 2 -225 115q-39 49 -56 106q-55 164 25 281q53 109 193 123q104 10 186 -43q72 -43 115 -173l18 -59q-2 -31 45 -78q47 -49 37 -86q-27 -35 -29 -131q-4 -94 -53 -129q-6 -29 29 -29q68 0 75 -2q20 -8 29 -26 q121 -55 111 -199q0 -12 -2 -26q-70 -147 -219 -154q-29 -20 -70 0zM754 -156q14 35 -23 82q-18 25 -18 2q0 -12 6 -34q8 -63 12 -70q11 -7 23 20zM451 342q29 72 12 141q-8 45 -41 49q-43 -25 -45 -104.5t45 -89.5q13 -2 29 4z" />
+<glyph glyph-name="r" unicode="r" horiz-adv-x="978" d="M252 0q-80 33 -62 322v22q4 31 -2 188q-2 66 5 109q-12 82 59 119q66 37 121 -8q25 -18 35 -52l18 -125q16 -51 92 7q100 33 184 -7q72 -35 82 -116q12 -102 -63 -103l-105 15q-141 39 -174 -127q-14 -76 -10 -197q0 -45 -88 -55q-53 -4 -92 8z" />
+<glyph glyph-name="s" unicode="s" d="M479 0q-174 25 -256 109q-61 20 -57 96q4 70 55 92q29 12 64 -2q152 -90 282 -76q117 6 -12 98q-41 29 -80 50q-123 100 -158 143q-16 20 -28 39q-53 55 -39 145q8 61 47 82q92 104 264 90q123 -10 189 -110q8 -14 16 -31q43 -55 -12 -121q-49 -61 -109 -47 q-33 -6 -90 43q-47 41 -72 23q37 -70 162 -148q41 -25 45 -29q172 -111 187 -264q2 -12 2 -24q-29 -109 -183 -142q-8 -2 -18 -4q-140 -22 -199 -12z" />
+<glyph glyph-name="t" unicode="t" horiz-adv-x="1144" d="M381 25q-104 51 -49 219l47 397q2 18 2 39q20 111 10 137q-14 41 -72 19q-72 -27 -139 18q-66 43 -41 113q31 70 156 98q98 25 113 31q33 113 47 137q35 61 98 63q76 0 117 -104q2 -6 18 -47q18 -18 100 -19q68 0 95 -22q59 -23 75 -94q16 -80 -47 -105q-12 -6 -28 -8 q-25 -10 -131 -6q-100 4 -119 -45q-2 -8 -4 -19l-31 -254l-33 -288q-6 -18 -12 -129q-6 -106 -68 -125q-8 -2 -18 -4q-57 -16 -74 -11z" />
+<glyph glyph-name="u" unicode="u" horiz-adv-x="1144" d="M815 -4q-63 -2 -141 76q-35 37 -49 47q-68 -86 -203 -115q-39 -8 -74 -8q-100 0 -147 100q-27 55 -19 117q4 213 49 375q41 106 109 98q25 -4 49 -24q59 -47 62 -103q0 -29 -21 -47q-39 -72 -37 -166q4 -70 41 -74q51 -16 96 97q23 57 52 163q23 86 80 91q51 4 71 -54 q10 -29 6 -63q-6 -63 49 -189q25 -59 35 -90q8 -39 74 -67.5t76 -55.5q8 -16 4 -36q18 -55 -70 -72q-51 -8 -92 0z" />
+<glyph glyph-name="v" unicode="v" horiz-adv-x="1193" d="M436 0q-80 59 -135 215q-41 113 -53 139l-55 158q-29 102 24 170q74 63 164 4q76 -47 80 -121q0 -14 -2 -26q-23 -43 6 -131q23 -72 53 -68q23 4 45 57q139 203 197 340q12 86 88 105q72 18 113 -35q29 -35 20 -88q-16 -94 -115 -264q-33 -59 -49 -93l-131 -204 q-92 -131 -207 -158q-29 -8 -43 0z" />
+<glyph glyph-name="w" unicode="w" horiz-adv-x="1519" d="M909 -49q-96 37 -131 178l-33 147l-96 -151q-51 -68 -112 -74q-88 -29 -142 96q-12 29 -20 60q-72 135 -158 360q-68 94 -51 183q14 82 108 92q106 0 152 -185q8 -14 43 -120q25 -82 55 -84q10 0 21 6q20 33 41 145q18 111 82 141q106 37 170 -51q43 -57 39 -131l75 -250 q39 18 101 158q43 96 65 129q25 76 99 96q84 25 126 -49l9 -16q20 -92 -66 -238q-59 -104 -69 -129q-76 -150 -101 -184q-49 -68 -112 -98q-14 2 -54 -23q-23 -14 -41 -8z" />
+<glyph glyph-name="x" unicode="x" horiz-adv-x="1298" d="M260 -16q-84 12 -72 98q10 78 76 127q10 23 80 82q72 66 49 110q-8 16 -26 31l-74 160q-37 102 20 164q109 82 197 -62q41 -66 57 -141q55 -16 152 90q88 96 117 117q49 72 135 39q74 -27 88 -97q6 -27 -2 -53q-33 -74 -150 -162q-113 -86 -149 -133q-12 -41 51 -118 q70 -86 72 -136q33 -45 -9 -88q-43 -45 -92 -12q-61 14 -131 98q-53 68 -84 88q-80 -66 -213 -204q-69 -2 -92 2z" />
+<glyph glyph-name="y" unicode="y" horiz-adv-x="1316" d="M301 -393q-174 10 -70 211q23 41 54 84q33 51 196 338q76 129 133 213q-18 82 -127 231q-84 117 -110 182q-43 59 16 125q55 61 121 43q37 -10 59 -49q10 -12 74 -139q59 -117 123 -133q10 14 74 147q49 100 104 133q88 25 139 -55q49 -80 11 -154q-12 -25 -33 -41 q-145 -240 -238 -378q-16 -31 -161 -242q-82 -121 -123 -209q-35 -45 -111 -207l-33 -63q-4 -35 -82 -37h-16z" />
+<glyph glyph-name="z" unicode="z" horiz-adv-x="1605" d="M326 0q-166 39 -39 254q29 49 67 96q20 39 138 176q86 104 86 146q-53 10 -213 2q-61 -4 -93 4q-94 31 -88 115q6 72 76 106q53 27 115 4q293 2 424 -29q43 -10 80 -24q102 -109 -27 -256q-20 -25 -57 -62q-18 -35 -123 -141q-88 -88 -82 -123q145 25 459 6 q150 -8 231 -6q102 -47 74 -125q-23 -66 -101 -88q-47 -12 -94 0q-37 -2 -293 8q-238 10 -377 -51l-133 -14q-16 0 -30 2z" />
+<glyph glyph-name="braceleft" unicode="{" horiz-adv-x="1126" d="M682 -203q-84 -2 -158 82q-76 90 -84 96q-12 14 -26 23q-41 66 -21 209q20 137 2 192q-6 20 -18 39q-20 20 -125 76q-96 51 -98 96q-2 33 32 72q37 84 144 78q66 -4 96 -39q61 14 35 104l-35 121q-6 37 2 60q-23 252 119 378q12 10 24 19q139 135 301 33q63 -61 21 -140 q-41 -72 -121 -71q-12 2 -27 4q-139 -29 -92 -303q2 -18 6 -37q82 -106 46 -223q-25 -76 -95 -117q-33 -33 -2 -111q33 -90 19 -129q-33 -127 0 -209q33 -84 127 -39q66 43 135 -4q66 -45 45 -108q-8 -29 -33 -51q-90 -95 -219 -101z" />
+<glyph glyph-name="bar" unicode="|" horiz-adv-x="696" d="M199 -242q-100 152 -15 316q45 182 41 291q-2 16 -2 147q31 33 23 145q-6 88 6 123q-16 14 2 47t0 50l22 141q0 23 -4 39q29 141 27 272q2 172 51 184q12 4 31 -2q16 8 22 5q4 -4 43 14l80 -92q51 -74 23 -150q-57 -131 -78 -401q-10 -127 -16 -180q-4 -39 -31 -474 q-6 -119 -16 -208l2 -158q-6 -70 -66 -105q-67 -39 -145 -4z" />
+<glyph glyph-name="braceright" unicode="}" horiz-adv-x="1286" d="M467 -276q-317 37 -285 192q4 16 11 33q45 88 151 57l139 -49q33 -8 58 -4q74 61 26 180l-63 148q-18 55 -10 96q2 25 38 137q35 106 -22 170q-39 113 57 297q78 145 76 141q41 78 -14 80l-137 -10q-84 4 -99 74q-33 113 146 159q20 4 47 11q117 53 207 -27 q53 -45 69 -117q39 -131 -18 -268q-18 -43 -43 -74q-72 -78 -49 -139q23 -70 135 -31l12 4q135 -6 188 -69q29 -35 17 -80q-18 -92 -135 -127l-152 -41q-6 -2 -14 -6q-96 -82 6 -291q4 -10 12 -25q39 -143 -53 -272q-25 -37 -59 -66q-92 -96 -209 -88z" />
+<glyph glyph-name="asciitilde" unicode="~" d="M639 522q-74 -20 -133 62q-25 37 -33 75q-35 29 -88 -26t-76 -64q-8 -2 -16 -2q-57 -4 -90 54q-29 47 -2 88q80 104 186 147q72 43 145 -12q31 -25 48 -60q20 -78 71 -82q29 -4 58 25q156 76 165 -16q0 -4 3 -11q14 -27 -23 -84q-53 -59 -182 -86q-6 0 -33 -8z" />
+<glyph glyph-name="quoteleft" unicode="&#x2018;" horiz-adv-x="419" d="M102 1534q29 61 86 31q4 -2 9 -6q29 -23 67 -107q8 -18 15 -31q51 -78 55 -94q12 -33 4 -61q-6 -4 -35 -49q-16 -29 -51 0q-68 39 -94 143q-68 86 -68 139q2 15 12 35z" />
+<glyph glyph-name="quoteright" unicode="&#x2019;" horiz-adv-x="444" d="M82 1313q59 90 84 166q47 76 84 88q25 6 51 -2q45 23 66 -19q10 -18 4 -41q-16 -61 -101 -180q-37 -61 -141 -108q-63 12 -63 40q0 21 16 56z" />
+<glyph glyph-name="quotedblleft" unicode="&#x201c;" horiz-adv-x="794" d="M240 1075q-86 31 -107 150q-10 63 8 114q14 100 90 174q61 59 123 50q164 -55 -47 -236q-4 -4 -14 -12q-16 -37 31 -111q41 -63 24 -88q-12 -18 -53 -31q-22 -16 -55 -10zM514 1098q-113 78 -53 321q-18 78 51 117q61 35 104 -2q25 -23 23 -66q-68 -201 -2 -276 q16 -18 39 -31q-29 -70 -137 -65q-9 2 -25 2z" />
+<glyph glyph-name="quotedblright" unicode="&#x201d;" horiz-adv-x="856" d="M203 1106q-20 35 -15 135q8 106 -41 156q-4 2 -8 6q-2 96 66 94q61 -2 110 -67q35 -47 31 -95q23 -86 -12 -174q-39 -100 -131 -55zM543 1102q-80 16 -49 76l59 100q31 72 -45 113q-68 35 -53 106q10 55 57 68q41 10 80 -39q121 -92 133 -221q8 -76 -31 -142 q-51 -59 -131 -63q-10 0 -20 2z" />
+<glyph glyph-name="bullet" unicode="&#x2022;" horiz-adv-x="708" d="M578 659q29 -102 -37 -202q-68 -106 -172 -80q-14 4 -27 10q-66 -6 -121 80q-68 109 -26 211q20 51 71 86q123 92 254 -35q31 -31 58 -70z" />
+</font>
+</defs></svg>
BIN  assets/handfont.ttf
Binary file not shown
BIN  assets/handfont.woff
Binary file not shown
BIN  assets/paper.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  assets/trashcan.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 cache.manifest
@@ -0,0 +1,24 @@
+CACHE MANIFEST
+# Rev 2
+
+index.html
+app.css
+app.js
+assets/checkbox-checked.png
+assets/checkbox-unchecked.png
+assets/handfont.eot
+assets/handfont.otf
+assets/handfont.svg
+assets/handfont.ttf
+assets/handfont.woff
+paper.jpg
+trashcan.png
+lib/jquery-1.4.2.js
+lib/model.js
+lib/sammy.js
+lib/sammy.template.js
+templates/404.template
+templates/_lists.template
+templates/_todo.template
+templates/about.template
+templates/todolist.template
25 index.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html manifest="cache.manifest">
+ <head>
+ <meta charset="UTF-8">
+ <title>Sammy's Todos</title>
+ <link rel="stylesheet" href="app.css" type="text/css" media="screen" charset="utf-8">
+ <script src="lib/jquery-1.4.2.js" type="text/javascript" charset="utf-8"></script>
+ <script src="lib/sammy.js" type="text/javascript" charset="utf-8"></script>
+ <script src="lib/sammy.template.js" type="text/javascript" charset="utf-8"></script>
+ <script src="lib/model.js" type="text/javascript" charset="utf-8"></script>
+ <script src="app.js" type="text/javascript" charset="utf-8"></script>
+ <script type="text/javascript" charset="utf-8">
+ if (!'localStorage' in window) {
+ alert("Oh no! Your browser doesn't support localStorage and probably doesn't support the other cool stuff needed to run this app properly. Sad day :(");
+ }
+ </script>
+ </head>
+ <body>
+ <div id="surface">
+ <h1 contenteditable="true"></h1>
+ <div id="lists"></div>
+ <div id="page"></div>
+ </div>
+ </body>
+</html>
6,240 lib/jquery-1.4.2.js
6,240 additions, 0 deletions not shown
155 lib/model.js
@@ -0,0 +1,155 @@
+Model = {
+ name: 'model',
+ init: function() {
+ this._id = 0;
+ this._data = [];
+ this._deserialize();
+ return this;
+ },
+ create: function(attributes, save) {
+ attributes.id = this._newId();
+ var item = this._data[ (this._data.push(attributes)) - 1 ];
+ if (save !== false) {
+ this.save();
+ }
+ return this._clone(item);
+ },
+ first: function() {
+ return this._clone(this._data[0]);
+ },
+ last: function() {
+ return this._clone(_data[ this._data.length-1 ]);
+ },
+ get: function(id) {
+ return this._clone(this._get(id));
+ },
+ getAll: function() {
+ return this._clone(this._data);
+ },
+ filter: function(attribute, value) {
+ return this._clone(this._filter(attribute, value));
+ },
+ multiFilter: function(filters) {
+ return this._clone(this._multiFilter(filter));
+ },
+ update: function(id, attributes, save) {
+ var item = this._get(id) || false;
+ if (item) {
+ this._mixin(item, attributes);
+ if (save !== false) {
+ this.save();
+ }
+ }
+ return item;
+ },
+ destroy: function(id, save) {
+ this._data.splice(this._indexOf(id), 1);
+ if (save !== false) {
+ this.save();
+ }
+ return true;
+ },
+ destroyAll: function(save) {
+ this._data = [];
+ if (save !== false) {
+ this.save();
+ }
+ return true;
+ },
+ save: function() {
+ this._serialize();
+ return true;
+ },
+ _first: function() {
+ return this._data[0];
+ },
+ _last: function() {
+ return _data[ this._data.length-1 ];
+ },
+ _get: function(id) {
+ return this._filter('id', id)[0];
+ },
+ _filter: function(attribute, value) {
+ var items = [], key, item, undefValue = (typeof value == "undefined");
+ for (key in this._data) {
+ if (this._data.hasOwnProperty(key)) {
+ item = this._data[key];
+ if (undefValue || item[attribute] == value) {
+ items.push(item);
+ }
+ }
+ }
+ return items;
+ },
+ _multiFilter: function(filters) {
+ var items = [], key, attribute, item;
+ for (key in this._data) {
+ if (this._data.hasOwnProperty(key)) {
+ item = this._data[key];
+ for (attribute in filters) {
+ if (filters.hasOwnProperty(attribute)) {
+ if (filters[attribute] == item[attribute]) {
+ items.push(item);
+ }
+ }
+ }
+ }
+ }
+ return items;
+ },
+ _indexOf: function(id) {
+ return this._data.indexOf(this._get(id));
+ },
+ _serialize: function() {
+ var data = {
+ prevId: this._id,
+ data: this._data
+ };
+ localStorage[this.name] = JSON.stringify(data);
+ },
+ _deserialize: function() {
+ var data = localStorage[this.name];
+ if (data) {
+ data = JSON.parse(data);
+ this._id = data.prevId;
+ this._data = data.data;
+ }
+ },
+ _newId: function() {
+ return this._id++;
+ },
+ _mixin: function(to, from) {
+ for (var key in from) {
+ if (from.hasOwnProperty(key)) {
+ to[key] = from[key];
+ }
+ }
+ },
+ _clone: function(obj) {
+ var type = Object.prototype.toString.call(obj),
+ cloned = obj;
+
+ if (type == '[object Object]') {
+ cloned = {};
+ for (var key in obj) {
+ obj.hasOwnProperty(key) && (cloned[key] = this._clone(obj[key]));
+ }
+ } else if (type == '[object Array]') {
+ cloned = [];
+ for (var index = 0, length = obj.length; index < length; index++) {
+ cloned[index] = this._clone(obj[index]);
+ }
+ }
+
+ return cloned;
+ }
+};
+
+// http://javascript.crockford.com/prototypal.html
+if (typeof Object.create !== 'function') {
+ Object.create = function (o) {
+ function F() {}
+ F.prototype = o;
+ return new F();
+ };
+}
1,381 lib/sammy.js
@@ -0,0 +1,1381 @@
+// name: sammy
+// version: 0.5.1
+
+(function($) {
+
+ var Sammy,
+ PATH_REPLACER = "([^\/]+)",
+ PATH_NAME_MATCHER = /:([\w\d]+)/g,
+ QUERY_STRING_MATCHER = /\?([^#]*)$/,
+ _decode = decodeURIComponent,
+ _routeWrapper = function(verb) {
+ return function(path, callback) { return this.route.apply(this, [verb, path, callback]); };
+ },
+ loggers = [];
+
+
+ // <tt>Sammy</tt> (also aliased as $.sammy) is not only the namespace for a
+ // number of prototypes, its also a top level method that allows for easy
+ // creation/management of <tt>Sammy.Application</tt> instances. There are a
+ // number of different forms for <tt>Sammy()</tt> but each returns an instance
+ // of <tt>Sammy.Application</tt>. When a new instance is created using
+ // <tt>Sammy</tt> it is added to an Object called <tt>Sammy.apps</tt>. This
+ // provides for an easy way to get at existing Sammy applications. Only one
+ // instance is allowed per <tt>element_selector</tt> so when calling
+ // <tt>Sammy('selector')</tt> multiple times, the first time will create
+ // the application and the following times will extend the application
+ // already added to that selector.
+ //
+ // === Example
+ //
+ // // returns the app at #main or a new app
+ // Sammy('#main')
+ //
+ // // equivilent to "new Sammy.Application", except appends to apps
+ // Sammy();
+ // Sammy(function() { ... });
+ //
+ // // extends the app at '#main' with function.
+ // Sammy('#main', function() { ... });
+ //
+ Sammy = function() {
+ var args = $.makeArray(arguments),
+ app, selector;
+ Sammy.apps = Sammy.apps || {};
+ if (args.length === 0 || args[0] && $.isFunction(args[0])) { // Sammy()
+ return Sammy.apply(Sammy, ['body'].concat(args));
+ } else if (typeof (selector = args.shift()) == 'string') { // Sammy('#main')
+ app = Sammy.apps[selector] || new Sammy.Application();
+ app.element_selector = selector;
+ if (args.length > 0) {
+ $.each(args, function(i, plugin) {
+ app.use(plugin);
+ });
+ }
+ // if the selector changes make sure the refrence in Sammy.apps changes
+ if (app.element_selector != selector) {
+ delete Sammy.apps[selector];
+ }
+ Sammy.apps[app.element_selector] = app;
+ return app;
+ }
+ };
+
+ Sammy.VERSION = '0.5.1';
+
+ // Add to the global logger pool. Takes a function that accepts an
+ // unknown number of arguments and should print them or send them somewhere
+ // The first argument is always a timestamp.
+ Sammy.addLogger = function(logger) {
+ loggers.push(logger);
+ };
+
+ // Sends a log message to each logger listed in the global
+ // loggers pool. Can take any number of arguments.
+ // Also prefixes the arguments with a timestamp.
+ Sammy.log = function() {
+ var args = $.makeArray(arguments);
+ args.unshift("[" + Date() + "]");
+ $.each(loggers, function(i, logger) {
+ logger.apply(Sammy, args);
+ });
+ };
+
+ if (typeof window.console != 'undefined') {
+ if ($.isFunction(console.log.apply)) {
+ Sammy.addLogger(function() {
+ window.console.log.apply(console, arguments);
+ });
+ } else {
+ Sammy.addLogger(function() {
+ window.console.log(arguments);
+ });
+ }
+ } else if (typeof console != 'undefined') {
+ Sammy.addLogger(function() {
+ console.log.apply(console, arguments);
+ });
+ }
+
+ // Sammy.Object is the base for all other Sammy classes. It provides some useful
+ // functionality, including cloning, iterating, etc.
+ Sammy.Object = function(obj) { // constructor
+ return $.extend(this, obj || {});
+ };
+
+ $.extend(Sammy.Object.prototype, {
+
+ // Returns a copy of the object with Functions removed.
+ toHash: function() {
+ var json = {};
+ $.each(this, function(k,v) {
+ if (!$.isFunction(v)) {
+ json[k] = v;
+ }
+ });
+ return json;
+ },
+
+ // Renders a simple HTML version of this Objects attributes.
+ // Does not render functions.
+ // For example. Given this Sammy.Object:
+ //
+ // var s = new Sammy.Object({first_name: 'Sammy', last_name: 'Davis Jr.'});
+ // s.toHTML() //=> '<strong>first_name</strong> Sammy<br /><strong>last_name</strong> Davis Jr.<br />'
+ //
+ toHTML: function() {
+ var display = "";
+ $.each(this, function(k, v) {
+ if (!$.isFunction(v)) {
+ display += "<strong>" + k + "</strong> " + v + "<br />";
+ }
+ });
+ return display;
+ },
+
+ // Generates a unique identifing string. Used for application namespaceing.
+ uuid: function() {
+ if (typeof this._uuid == 'undefined' || !this._uuid) {
+ this._uuid = (new Date()).getTime() + '-' + parseInt(Math.random() * 1000, 10);
+ }
+ return this._uuid;
+ },
+
+ // Returns an array of keys for this object. If <tt>attributes_only</tt>
+ // is true will not return keys that map to a <tt>function()</tt>
+ keys: function(attributes_only) {
+ var keys = [];
+ for (var property in this) {
+ if (!$.isFunction(this[property]) || !attributes_only) {
+ keys.push(property);
+ }
+ }
+ return keys;
+ },
+
+ // Checks if the object has a value at <tt>key</tt> and that the value is not empty
+ has: function(key) {
+ return this[key] && $.trim(this[key].toString()) != '';
+ },
+
+ // convenience method to join as many arguments as you want
+ // by the first argument - useful for making paths
+ join: function() {
+ var args = $.makeArray(arguments);
+ var delimiter = args.shift();
+ return args.join(delimiter);
+ },
+
+ // Shortcut to Sammy.log
+ log: function() {
+ Sammy.log.apply(Sammy, arguments);
+ },
+
+ // Returns a string representation of this object.
+ // if <tt>include_functions</tt> is true, it will also toString() the
+ // methods of this object. By default only prints the attributes.
+ toString: function(include_functions) {
+ var s = [];
+ $.each(this, function(k, v) {
+ if (!$.isFunction(v) || include_functions) {
+ s.push('"' + k + '": ' + v.toString());
+ }
+ });
+ return "Sammy.Object: {" + s.join(',') + "}";
+ }
+ });
+
+ // The HashLocationProxy is the default location proxy for all Sammy applications.
+ // A location proxy is a prototype that conforms to a simple interface. The purpose
+ // of a location proxy is to notify the Sammy.Application its bound to when the location
+ // or 'external state' changes. The HashLocationProxy considers the state to be
+ // changed when the 'hash' (window.location.hash / '#') changes. It does this in two
+ // different ways depending on what browser you are using. The newest browsers
+ // (IE, Safari > 4, FF >= 3.6) support a 'onhashchange' DOM event, thats fired whenever
+ // the location.hash changes. In this situation the HashLocationProxy just binds
+ // to this event and delegates it to the application. In the case of older browsers
+ // a poller is set up to track changes to the hash. Unlike Sammy 0.3 or earlier,
+ // the HashLocationProxy allows the poller to be a global object, eliminating the
+ // need for multiple pollers even when thier are multiple apps on the page.
+ Sammy.HashLocationProxy = function(app, run_interval_every) {
+ this.app = app;
+
+ // check for native hash support
+ if ('onhashchange' in window) {
+ Sammy.log('native hash change exists, using');
+ this.is_native = true;
+ } else {
+ Sammy.log('no native hash change, falling back to polling');
+ this.is_native = false;
+ this._startPolling(run_interval_every);
+ }
+ };
+
+ Sammy.HashLocationProxy.prototype = {
+ // bind the proxy events to the current app.
+ bind: function() {
+ var app = this.app;
+ $(window).bind('hashchange.' + this.app.eventNamespace(), function() {
+ app.trigger('location-changed');
+ });
+ },
+ // unbind the proxy events from the current app
+ unbind: function() {
+ $(window).die('hashchange.' + this.app.eventNamespace());
+ },
+ // get the current location from the hash.
+ getLocation: function() {
+ // Bypass the `window.location.hash` attribute. If a question mark
+ // appears in the hash IE6 will strip it and all of the following
+ // characters from `window.location.hash`.
+ var matches = window.location.toString().match(/^[^#]*(#.+)$/);
+ return matches ? matches[1] : '';
+ },
+ // set the current location to <tt>new_location</tt>
+ setLocation: function(new_location) {
+ return (window.location = new_location);
+ },
+
+ _startPolling: function(every) {
+ // set up interval
+ var proxy = this;
+ if (!Sammy.HashLocationProxy._interval) {
+ if (!every) { every = 10; }
+ var hashCheck = function() {
+ current_location = proxy.getLocation();
+ // Sammy.log('getLocation', current_location);
+ if (!Sammy.HashLocationProxy._last_location ||
+ current_location != Sammy.HashLocationProxy._last_location) {
+ setTimeout(function() {
+ $(window).trigger('hashchange');
+ }, 1);
+ }
+ Sammy.HashLocationProxy._last_location = current_location;
+ };
+ hashCheck();
+ Sammy.HashLocationProxy._interval = setInterval(hashCheck, every);
+ $(window).bind('beforeunload', function() {
+ clearInterval(Sammy.HashLocationProxy._interval);
+ });
+ }
+ }
+ };
+
+ // The DataLocationProxy is an optional location proxy prototype. As opposed to
+ // the <tt>HashLocationProxy</tt> it gets its location from a jQuery.data attribute
+ // tied to the application's element. You can set the name of the attribute by
+ // passing a string as the second argument to the constructor. The default attribute
+ // name is 'sammy-location'. To read more about location proxies, check out the
+ // documentation for <tt>Sammy.HashLocationProxy</tt>
+ Sammy.DataLocationProxy = function(app, data_name) {
+ this.app = app;
+ this.data_name = data_name || 'sammy-location';
+ };
+
+ Sammy.DataLocationProxy.prototype = {
+ bind: function() {
+ var proxy = this;
+ this.app.$element().bind('setData', function(e, key) {
+ if (key == proxy.data_name) {
+ proxy.app.trigger('location-changed');
+ }
+ });
+ },
+
+ unbind: function() {
+ this.app.$element().die('setData');
+ },
+
+ getLocation: function() {
+ return this.app.$element().data(this.data_name);
+ },
+
+ setLocation: function(new_location) {
+ return this.app.$element().data(this.data_name, new_location);
+ }
+ };
+
+ // Sammy.Application is the Base prototype for defining 'applications'.
+ // An 'application' is a collection of 'routes' and bound events that is
+ // attached to an element when <tt>run()</tt> is called.
+ // The only argument an 'app_function' is evaluated within the context of the application.
+ Sammy.Application = function(app_function) {
+ var app = this;
+ this.routes = {};
+ this.listeners = new Sammy.Object({});
+ this.arounds = [];
+ this.befores = [];
+ this.namespace = this.uuid();
+ this.context_prototype = function() { Sammy.EventContext.apply(this, arguments); };
+ this.context_prototype.prototype = new Sammy.EventContext();
+
+ if ($.isFunction(app_function)) {
+ app_function.apply(this, [this]);
+ }
+ // set the location proxy if not defined to the default (HashLocationProxy)
+ if (!this.location_proxy) {
+ this.location_proxy = new Sammy.HashLocationProxy(app, this.run_interval_every);
+ }
+ if (this.debug) {
+ this.bindToAllEvents(function(e, data) {
+ app.log(app.toString(), e.cleaned_type, data || {});
+ });
+ }
+ };
+
+ Sammy.Application.prototype = $.extend({}, Sammy.Object.prototype, {
+
+ // the four route verbs
+ ROUTE_VERBS: ['get','post','put','delete'],
+
+ // An array of the default events triggered by the
+ // application during its lifecycle
+ APP_EVENTS: ['run','unload','lookup-route','run-route','route-found','event-context-before','event-context-after','changed','error','check-form-submission','redirect'],
+
+ _last_route: null,
+ _running: false,
+
+ // Defines what element the application is bound to. Provide a selector
+ // (parseable by <tt>jQuery()</tt>) and this will be used by <tt>$element()</tt>
+ element_selector: 'body',
+
+ // When set to true, logs all of the default events using <tt>log()</tt>
+ debug: false,
+
+ // When set to true, and the error() handler is not overriden, will actually
+ // raise JS errors in routes (500) and when routes can't be found (404)
+ raise_errors: false,
+
+ // The time in milliseconds that the URL is queried for changes
+ run_interval_every: 50,
+
+ // The location proxy for the current app. By default this is set to a new
+ // <tt>Sammy.HashLocationProxy</tt> on initialization. However, you can set
+ // the location_proxy inside you're app function to give youre app a custom
+ // location mechanism
+ location_proxy: null,
+
+ // The default template engine to use when using <tt>partial()</tt> in an
+ // <tt>EventContext</tt>. <tt>template_engine</tt> can either be a string that
+ // corresponds to the name of a method/helper on EventContext or it can be a function
+ // that takes two arguments, the content of the unrendered partial and an optional
+ // JS object that contains interpolation data. Template engine is only called/refered
+ // to if the extension of the partial is null or unknown. See <tt>partial()</tt>
+ // for more information
+ template_engine: null,
+
+ // //=> Sammy.Application: body
+ toString: function() {
+ return 'Sammy.Application:' + this.element_selector;
+ },
+
+ // returns a jQuery object of the Applications bound element.
+ $element: function() {
+ return $(this.element_selector);
+ },
+
+ // <tt>use()</tt> is the entry point for including Sammy plugins.
+ // The first argument to use should be a function() that is evaluated
+ // in the context of the current application, just like the <tt>app_function</tt>
+ // argument to the <tt>Sammy.Application</tt> constructor.
+ //
+ // Any additional arguments are passed to the app function sequentially.
+ //
+ // For much more detail about plugins, check out:
+ // http://code.quirkey.com/sammy/doc/plugins.html
+ //
+ // === Example
+ //
+ // var MyPlugin = function(app, prepend) {
+ //
+ // this.helpers({
+ // myhelper: function(text) {
+ // alert(prepend + " " + text);
+ // }
+ // });
+ //
+ // };
+ //
+ // var app = $.sammy(function() {
+ //
+ // this.use(MyPlugin, 'This is my plugin');
+ //
+ // this.get('#/', function() {
+ // this.myhelper('and dont you forget it!');
+ // //=> Alerts: This is my plugin and dont you forget it!
+ // });
+ //
+ // });
+ //
+ use: function() {
+ // flatten the arguments
+ var args = $.makeArray(arguments);
+ var plugin = args.shift();
+ try {
+ args.unshift(this);
+ plugin.apply(this, args);
+ } catch(e) {
+ if (typeof plugin == 'undefined') {
+ this.error("Plugin Error: called use() but plugin is not defined", e);
+ } else if (!$.isFunction(plugin)) {
+ this.error("Plugin Error: called use() but '" + plugin.toString() + "' is not a function", e);
+ } else {
+ this.error("Plugin Error", e);
+ }
+ }
+ return this;
+ },
+
+ // <tt>route()</tt> is the main method for defining routes within an application.
+ // For great detail on routes, check out: http://code.quirkey.com/sammy/doc/routes.html
+ //
+ // This method also has aliases for each of the different verbs (eg. <tt>get()</tt>, <tt>post()</tt>, etc.)
+ //
+ // === Arguments
+ //
+ // +verb+:: A String in the set of ROUTE_VERBS or 'any'. 'any' will add routes for each
+ // of the ROUTE_VERBS. If only two arguments are passed,
+ // the first argument is the path, the second is the callback and the verb
+ // is assumed to be 'any'.
+ // +path+:: A Regexp or a String representing the path to match to invoke this verb.
+ // +callback+:: A Function that is called/evaluated whent the route is run see: <tt>runRoute()</tt>.
+ // It is also possible to pass a string as the callback, which is looked up as the name
+ // of a method on the application.
+ //
+ route: function(verb, path, callback) {
+ var app = this, param_names = [], add_route;
+
+ // if the method signature is just (path, callback)
+ // assume the verb is 'any'
+ if (!callback && $.isFunction(path)) {
+ path = verb;
+ callback = path;
+ verb = 'any';
+ }
+
+ verb = verb.toLowerCase(); // ensure verb is lower case
+
+ // if path is a string turn it into a regex
+ if (path.constructor == String) {
+
+ // Needs to be explicitly set because IE will maintain the index unless NULL is returned,
+ // which means that with two consecutive routes that contain params, the second set of params will not be found and end up in splat instead of params
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/RegExp/lastIndex
+ PATH_NAME_MATCHER.lastIndex = 0;
+
+ // find the names
+ while ((path_match = PATH_NAME_MATCHER.exec(path)) !== null) {
+ param_names.push(path_match[1]);
+ }
+ // replace with the path replacement
+ path = new RegExp("^" + path.replace(PATH_NAME_MATCHER, PATH_REPLACER) + "$");
+ }
+ // lookup callback
+ if (typeof callback == 'string') {
+ callback = app[callback];
+ }
+
+ add_route = function(with_verb) {
+ var r = {verb: with_verb, path: path, callback: callback, param_names: param_names};
+ // add route to routes array
+ app.routes[with_verb] = app.routes[with_verb] || [];
+ // place routes in order of definition
+ app.routes[with_verb].push(r);
+ };
+
+ if (verb === 'any') {
+ $.each(this.ROUTE_VERBS, function(i, v) { add_route(v); });
+ } else {
+ add_route(verb);
+ }
+
+ // return the app
+ return this;
+ },
+
+ // Alias for route('get', ...)
+ get: _routeWrapper('get'),
+
+ // Alias for route('post', ...)
+ post: _routeWrapper('post'),
+
+ // Alias for route('put', ...)
+ put: _routeWrapper('put'),
+
+ // Alias for route('delete', ...)
+ del: _routeWrapper('delete'),
+
+ // Alias for route('any', ...)
+ any: _routeWrapper('any'),
+
+ // <tt>mapRoutes</tt> takes an array of arrays, each array being passed to route()
+ // as arguments, this allows for mass definition of routes. Another benefit is
+ // this makes it possible/easier to load routes via remote JSON.
+ //
+ // === Example
+ //
+ // var app = $.sammy(function() {
+ //
+ // this.mapRoutes([
+ // ['get', '#/', function() { this.log('index'); }],
+ // // strings in callbacks are looked up as methods on the app
+ // ['post', '#/create', 'addUser'],
+ // // No verb assumes 'any' as the verb
+ // [/dowhatever/, function() { this.log(this.verb, this.path)}];
+ // ]);
+ // })
+ //
+ mapRoutes: function(route_array) {
+ var app = this;
+ $.each(route_array, function(i, route_args) {
+ app.route.apply(app, route_args);
+ });
+ return this;
+ },
+
+ // A unique event namespace defined per application.
+ // All events bound with <tt>bind()</tt> are automatically bound within this space.
+ eventNamespace: function() {
+ return ['sammy-app', this.namespace].join('-');
+ },
+
+ // Works just like <tt>jQuery.fn.bind()</tt> with a couple noteable differences.
+ //
+ // * It binds all events to the application element
+ // * All events are bound within the <tt>eventNamespace()</tt>
+ // * Events are not actually bound until the application is started with <tt>run()</tt>
+ // * callbacks are evaluated within the context of a Sammy.EventContext
+ //
+ // See http://code.quirkey.com/sammy/docs/events.html for more info.
+ //
+ bind: function(name, data, callback) {
+ var app = this;
+ // build the callback
+ // if the arity is 2, callback is the second argument
+ if (typeof callback == 'undefined') { callback = data; }
+ var listener_callback = function() {
+ // pull off the context from the arguments to the callback
+ var e, context, data;
+ e = arguments[0];
+ data = arguments[1];
+ if (data && data.context) {
+ context = data.context;
+ delete data.context;
+ } else {
+ context = new app.context_prototype(app, 'bind', e.type, data);
+ }
+ e.cleaned_type = e.type.replace(app.eventNamespace(), '');
+ callback.apply(context, [e, data]);
+ };
+
+ // it could be that the app element doesnt exist yet
+ // so attach to the listeners array and then run()
+ // will actually bind the event.
+ if (!this.listeners[name]) { this.listeners[name] = []; }
+ this.listeners[name].push(listener_callback);
+ if (this.isRunning()) {
+ // if the app is running
+ // *actually* bind the event to the app element
+ this._listen(name, listener_callback);
+ }
+ return this;
+ },
+
+ // Triggers custom events defined with <tt>bind()</tt>
+ //
+ // === Arguments
+ //
+ // +name+:: The name of the event. Automatically prefixed with the <tt>eventNamespace()</tt>
+ // +data+:: An optional Object that can be passed to the bound callback.
+ // +context+:: An optional context/Object in which to execute the bound callback.
+ // If no context is supplied a the context is a new <tt>Sammy.EventContext</tt>
+ //
+ trigger: function(name, data) {
+ this.$element().trigger([name, this.eventNamespace()].join('.'), [data]);
+ return this;
+ },
+
+ // Reruns the current route
+ refresh: function() {
+ this.last_location = null;
+ this.trigger('location-changed');
+ return this;
+ },
+
+ // Takes a single callback that is pushed on to a stack.
+ // Before any route is run, the callbacks are evaluated in order within
+ // the current <tt>Sammy.EventContext</tt>
+ //
+ // If any of the callbacks explicitly return false, execution of any
+ // further callbacks and the route itself is halted.
+ //
+ // You can also provide a set of options that will define when to run this
+ // before based on the route it proceeds.
+ //
+ // === Example
+ //
+ // var app = $.sammy(function() {
+ //
+ // // will run at #/route but not at #/
+ // this.before('#/route', function() {
+ // //...
+ // });
+ //
+ // // will run at #/ but not at #/route
+ // this.before({except: {path: '#/route'}}, function() {
+ // this.log('not before #/route');
+ // });
+ //
+ // this.get('#/', function() {});
+ //
+ // this.get('#/route', function() {});
+ //
+ // });
+ //
+ // See <tt>contextMatchesOptions()</tt> for a full list of supported options
+ //
+ before: function(options, callback) {
+ if ($.isFunction(options)) {
+ callback = options;
+ options = {};
+ }
+ this.befores.push([options, callback]);
+ return this;
+ },
+
+ // A shortcut for binding a callback to be run after a route is executed.
+ // After callbacks have no guarunteed order.
+ after: function(callback) {
+ return this.bind('event-context-after', callback);
+ },
+
+
+ // Adds an around filter to the application. around filters are functions
+ // that take a single argument <tt>callback</tt> which is the entire route
+ // execution path wrapped up in a closure. This means you can decide whether
+ // or not to proceed with execution by not invoking <tt>callback</tt> or,
+ // more usefuly wrapping callback inside the result of an asynchronous execution.
+ //
+ // === Example
+ //
+ // The most common use case for around() is calling a _possibly_ async function
+ // and executing the route within the functions callback:
+ //
+ // var app = $.sammy(function() {
+ //
+ // var current_user = false;
+ //
+ // function checkLoggedIn(callback) {
+ // // /session returns a JSON representation of the logged in user
+ // // or an empty object
+ // if (!current_user) {
+ // $.getJSON('/session', function(json) {
+ // if (json.login) {
+ // // show the user as logged in
+ // current_user = json;
+ // // execute the route path
+ // callback();
+ // } else {
+ // // show the user as not logged in
+ // current_user = false;
+ // // the context of aroundFilters is an EventContext
+ // this.redirect('#/login');
+ // }
+ // });
+ // } else {
+ // // execute the route path
+ // callback();
+ // }
+ // };
+ //
+ // this.around(checkLoggedIn);
+ //
+ // });
+ //
+ around: function(callback) {
+ this.arounds.push(callback);
+ return this;
+ },
+
+ // Returns a boolean of weather the current application is running.
+ isRunning: function() {
+ return this._running;
+ },
+
+ // Helpers extends the EventContext prototype specific to this app.
+ // This allows you to define app specific helper functions that can be used
+ // whenever you're inside of an event context (templates, routes, bind).
+ //
+ // === Example
+ //
+ // var app = $.sammy(function() {
+ //
+ // helpers({
+ // upcase: function(text) {
+ // return text.toString().toUpperCase();
+ // }
+ // });
+ //
+ // get('#/', function() { with(this) {
+ // // inside of this context I can use the helpers
+ // $('#main').html(upcase($('#main').text());
+ // }});
+ //
+ // });
+ //
+ //
+ // === Arguments
+ //
+ // +extensions+:: An object collection of functions to extend the context.
+ //
+ helpers: function(extensions) {
+ $.extend(this.context_prototype.prototype, extensions);
+ return this;
+ },
+
+ // Helper extends the event context just like <tt>helpers()</tt> but does it
+ // a single method at a time. This is especially useful for dynamically named
+ // helpers
+ //
+ // === Example
+ //
+ // // Trivial example that adds 3 helper methods to the context dynamically
+ // var app = $.sammy(function(app) {
+ //
+ // $.each([1,2,3], function(i, num) {
+ // app.helper('helper' + num, function() {
+ // this.log("I'm helper number " + num);
+ // });
+ // });
+ //
+ // this.get('#/', function() {
+ // this.helper2(); //=> I'm helper number 2
+ // });
+ // });
+ //
+ // === Arguments
+ //
+ // +name+:: The name of the method
+ // +method+:: The function to be added to the prototype at <tt>name</tt>
+ //
+ helper: function(name, method) {
+ this.context_prototype.prototype[name] = method;
+ return this;
+ },
+
+ // Actually starts the application's lifecycle. <tt>run()</tt> should be invoked
+ // within a document.ready block to ensure the DOM exists before binding events, etc.
+ //
+ // === Example
+ //
+ // var app = $.sammy(function() { ... }); // your application
+ // $(function() { // document.ready
+ // app.run();
+ // });
+ //
+ // === Arguments
+ //
+ // +start_url+:: "value", Optionally, a String can be passed which the App will redirect to
+ // after the events/routes have been bound.
+ run: function(start_url) {
+ if (this.isRunning()) { return false; }
+ var app = this;
+
+ // actually bind all the listeners
+ $.each(this.listeners.toHash(), function(name, callbacks) {
+ $.each(callbacks, function(i, listener_callback) {
+ app._listen(name, listener_callback);
+ });
+ });
+
+ this.trigger('run', {start_url: start_url});
+ this._running = true;
+ // set last location
+ this.last_location = null;
+ if (this.getLocation() == '' && typeof start_url != 'undefined') {
+ this.setLocation(start_url);
+ }
+ // check url
+ this._checkLocation();
+ this.location_proxy.bind();
+ this.bind('location-changed', function() {
+ app._checkLocation();
+ });
+
+ // bind to submit to capture post/put/delete routes
+ this.bind('submit', function(e) {
+ var returned = app._checkFormSubmission($(e.target).closest('form'));
+ return (returned === false) ? e.preventDefault() : false;
+ });
+
+ // bind unload to body unload
+ $(window).bind('beforeunload', function() {
+ app.unload();
+ });
+
+ // trigger html changed
+ return this.trigger('changed');
+ },
+
+ // The opposite of <tt>run()</tt>, un-binds all event listeners and intervals
+ // <tt>run()</tt> Automaticaly binds a <tt>onunload</tt> event to run this when
+ // the document is closed.
+ unload: function() {
+ if (!this.isRunning()) { return false; }
+ var app = this;
+ this.trigger('unload');
+ // clear interval
+ this.location_proxy.unbind();
+ // unbind form submits
+ this.$element().unbind('submit').removeClass(app.eventNamespace());
+ // unbind all events
+ $.each(this.listeners.toHash() , function(name, listeners) {
+ $.each(listeners, function(i, listener_callback) {
+ app._unlisten(name, listener_callback);
+ });
+ });
+ this._running = false;
+ return this;
+ },
+
+ // Will bind a single callback function to every event that is already
+ // being listened to in the app. This includes all the <tt>APP_EVENTS</tt>
+ // as well as any custom events defined with <tt>bind()</tt>.
+ //
+ // Used internally for debug logging.
+ bindToAllEvents: function(callback) {
+ var app = this;
+ // bind to the APP_EVENTS first
+ $.each(this.APP_EVENTS, function(i, e) {
+ app.bind(e, callback);
+ });
+ // next, bind to listener names (only if they dont exist in APP_EVENTS)
+ $.each(this.listeners.keys(true), function(i, name) {
+ if (app.APP_EVENTS.indexOf(name) == -1) {
+ app.bind(name, callback);
+ }
+ });
+ return this;
+ },
+
+ // Returns a copy of the given path with any query string after the hash
+ // removed.
+ routablePath: function(path) {
+ return path.replace(QUERY_STRING_MATCHER, '');
+ },
+
+ // Given a verb and a String path, will return either a route object or false
+ // if a matching route can be found within the current defined set.
+ lookupRoute: function(verb, path) {
+ var app = this, routed = false;
+ this.trigger('lookup-route', {verb: verb, path: path});
+ if (typeof this.routes[verb] != 'undefined') {
+ $.each(this.routes[verb], function(i, route) {
+ if (app.routablePath(path).match(route.path)) {
+ routed = route;
+ return false;
+ }
+ });
+ }
+ return routed;
+ },
+
+ // First, invokes <tt>lookupRoute()</tt> and if a route is found, parses the
+ // possible URL params and then invokes the route's callback within a new
+ // <tt>Sammy.EventContext</tt>. If the route can not be found, it calls
+ // <tt>notFound()</tt>. If <tt>raise_errors</tt> is set to <tt>true</tt> and
+ // the <tt>error()</tt> has not been overriden, it will throw an actual JS
+ // error.
+ //
+ // You probably will never have to call this directly.
+ //
+ // === Arguments
+ //
+ // +verb+:: A String for the verb.
+ // +path+:: A String path to lookup.
+ // +params+:: An Object of Params pulled from the URI or passed directly.
+ //
+ // === Returns
+ //
+ // Either returns the value returned by the route callback or raises a 404 Not Found error.
+ //
+ runRoute: function(verb, path, params) {
+ var app = this,
+ route = this.lookupRoute(verb, path),
+ context,
+ wrapped_route,
+ arounds,
+ around,
+ befores,
+ before,
+ callback_args,
+ final_returned;
+
+ this.log('runRoute', [verb, path].join(' '));
+ this.trigger('run-route', {verb: verb, path: path, params: params});
+ if (typeof params == 'undefined') { params = {}; }
+
+ $.extend(params, this._parseQueryString(path));
+
+ if (route) {
+ this.trigger('route-found', {route: route});
+ // pull out the params from the path
+ if ((path_params = route.path.exec(this.routablePath(path))) !== null) {
+ // first match is the full path
+ path_params.shift();
+ // for each of the matches
+ $.each(path_params, function(i, param) {
+ // if theres a matching param name
+ if (route.param_names[i]) {
+ // set the name to the match
+ params[route.param_names[i]] = _decode(param);
+ } else {
+ // initialize 'splat'
+ if (!params.splat) { params.splat = []; }
+ params.splat.push(_decode(param));
+ }
+ });
+ }
+
+ // set event context
+ context = new this.context_prototype(this, verb, path, params);
+ // ensure arrays
+ arounds = this.arounds.slice(0);
+ befores = this.befores.slice(0);
+ // set the callback args to the context + contents of the splat
+ callback_args = [context].concat(params.splat);
+ // wrap the route up with the before filters
+ wrapped_route = function() {
+ var returned;
+ while (befores.length > 0) {
+ before = befores.shift();
+ // check the options
+ if (app.contextMatchesOptions(context, before[0])) {
+ returned = before[1].apply(context, [context]);
+ if (returned === false) { return false; }
+ }
+ }
+ app.last_route = route;
+ context.trigger('event-context-before', {context: context});
+ returned = route.callback.apply(context, callback_args);
+ context.trigger('event-context-after', {context: context});
+ return returned;
+ };
+ $.each(arounds.reverse(), function(i, around) {
+ var last_wrapped_route = wrapped_route;
+ wrapped_route = function() { return around.apply(context, [last_wrapped_route]); };
+ });
+ try {
+ final_returned = wrapped_route();
+ } catch(e) {
+ this.error(['500 Error', verb, path].join(' '), e);
+ }
+ return final_returned;
+ } else {
+ return this.notFound(verb, path);
+ }
+ },
+
+ // Matches an object of options against an <tt>EventContext</tt> like object that
+ // contains <tt>path</tt> and <tt>verb</tt> attributes. Internally Sammy uses this
+ // for matching <tt>before()</tt> filters against specific options. You can set the
+ // object to _only_ match certain paths or verbs, or match all paths or verbs _except_
+ // those that match the options.
+ //
+ // === Example
+ //
+ // var app = $.sammy(),
+ // context = {verb: 'get', path: '#/mypath'};
+ //
+ // // match against a path string
+ // app.contextMatchesOptions(context, '#/mypath'); //=> true
+ // app.contextMatchesOptions(context, '#/otherpath'); //=> false
+ // // equivilent to
+ // app.contextMatchesOptions(context, {only: {path:'#/mypath'}}); //=> true
+ // app.contextMatchesOptions(context, {only: {path:'#/otherpath'}}); //=> false
+ // // match against a path regexp
+ // app.contextMatchesOptions(context, /path/); //=> true
+ // app.contextMatchesOptions(context, /^path/); //=> false
+ // // match only a verb
+ // app.contextMatchesOptions(context, {only: {verb:'get'}}); //=> true
+ // app.contextMatchesOptions(context, {only: {verb:'post'}}); //=> false
+ // // match all except a verb
+ // app.contextMatchesOptions(context, {except: {verb:'post'}}); //=> true
+ // app.contextMatchesOptions(context, {except: {verb:'get'}}); //=> false
+ // // match all except a path
+ // app.contextMatchesOptions(context, {except: {path:'#/otherpath'}}); //=> true
+ // app.contextMatchesOptions(context, {except: {path:'#/mypath'}}); //=> false
+ //
+ contextMatchesOptions: function(context, match_options, positive) {
+ // empty options always match
+ var options = match_options;
+ if (typeof options === 'undefined' || options == {}) {
+ return true;
+ }
+ if (typeof positive === 'undefined') {
+ positive = true;
+ }
+ // normalize options
+ if (typeof options === 'string' || $.isFunction(options.test)) {
+ options = {path: options};
+ }
+ if (options.only) {
+ return this.contextMatchesOptions(context, options.only, true);
+ } else if (options.except) {
+ return this.contextMatchesOptions(context, options.except, false);
+ }
+ var path_matched = true, verb_matched = true;
+ if (options.path) {
+ // wierd regexp test
+ if ($.isFunction(options.path.test)) {
+ path_matched = options.path.test(context.path);
+ } else {
+ path_matched = (options.path.toString() === context.path);
+ }
+ }
+ if (options.verb) {
+ verb_matched = options.verb === context.verb;
+ }
+ return positive ? (verb_matched && path_matched) : !(verb_matched && path_matched);
+ },
+
+
+ // Delegates to the <tt>location_proxy</tt> to get the current location.
+ // See <tt>Sammy.HashLocationProxy</tt> for more info on location proxies.
+ getLocation: function() {
+ return this.location_proxy.getLocation();
+ },
+
+ // Delegates to the <tt>location_proxy</tt> to set the current location.
+ // See <tt>Sammy.HashLocationProxy</tt> for more info on location proxies.
+ //
+ // === Arguments
+ //
+ // +new_location+:: A new location string (e.g. '#/')
+ //
+ setLocation: function(new_location) {
+ return this.location_proxy.setLocation(new_location);
+ },
+
+ // Swaps the content of <tt>$element()</tt> with <tt>content</tt>
+ // You can override this method to provide an alternate swap behavior
+ // for <tt>EventContext.partial()</tt>.
+ //
+ // === Example
+ //
+ // var app = $.sammy(function() {
+ //
+ // // implements a 'fade out'/'fade in'
+ // this.swap = function(content) {
+ // this.$element().hide('slow').html(content).show('slow');
+ // }
+ //
+ // get('#/', function() {
+ // this.partial('index.html.erb') // will fade out and in
+ // });
+ //
+ // });
+ //
+ swap: function(content) {
+ return this.$element().html(content);
+ },
+
+ // This thows a '404 Not Found' error by invoking <tt>error()</tt>.
+ // Override this method or <tt>error()</tt> to provide custom
+ // 404 behavior (i.e redirecting to / or showing a warning)
+ notFound: function(verb, path) {
+ var ret = this.error(['404 Not Found', verb, path].join(' '));
+ return (verb === 'get') ? ret : true;
+ },
+
+ // The base error handler takes a string <tt>message</tt> and an <tt>Error</tt>
+ // object. If <tt>raise_errors</tt> is set to <tt>true</tt> on the app level,
+ // this will re-throw the error to the browser. Otherwise it will send the error
+ // to <tt>log()</tt>. Override this method to provide custom error handling
+ // e.g logging to a server side component or displaying some feedback to the
+ // user.
+ error: function(message, original_error) {
+ if (!original_error) { original_error = new Error(); }
+ original_error.message = [message, original_error.message].join(' ');
+ this.trigger('error', {message: original_error.message, error: original_error});
+ if (this.raise_errors) {
+ throw(original_error);
+ } else {
+ this.log(original_error.message, original_error);
+ }
+ },
+
+ _checkLocation: function() {
+ var location, returned;
+ // get current location
+ location = this.getLocation();
+ // compare to see if hash has changed
+ if (location != this.last_location) {
+ // lookup route for current hash
+ returned = this.runRoute('get', location);
+ }
+ // reset last location
+ this.last_location = location;
+ return returned;
+ },
+
+ _checkFormSubmission: function(form) {
+ var $form, path, verb, params, returned;
+ this.trigger('check-form-submission', {form: form});
+ $form = $(form);
+ path = $form.attr('action');
+ verb = $.trim($form.attr('method').toString().toLowerCase());
+ if (!verb || verb == '') { verb = 'get'; }
+ this.log('_checkFormSubmission', $form, path, verb);
+ params = $.extend({}, this._parseFormParams($form), {'$form': $form});
+ returned = this.runRoute(verb, path, params);
+ return (typeof returned == 'undefined') ? false : returned;
+ },
+
+ _parseFormParams: function($form) {
+ var params = {};
+ $.each($form.serializeArray(), function(i, field) {
+ if (params[field.name]) {
+ if ($.isArray(params[field.name])) {
+ params[field.name].push(field.value);
+ } else {
+ params[field.name] = [params[field.name], field.value];
+ }
+ } else {
+ params[field.name] = field.value;
+ }
+ });
+ return params;
+ },
+
+ _parseQueryString: function(path) {
+ var query = {}, parts, pairs, pair, i;
+
+ parts = path.match(QUERY_STRING_MATCHER);
+ if (parts) {
+ pairs = parts[1].split('&');
+ for (i = 0; i < pairs.length; i += 1) {
+ pair = pairs[i].split('=');
+ query[pair[0]] = _decode(pair[1]);
+ }
+ }
+
+ return query;
+ },
+
+ _listen: function(name, callback) {
+ return this.$element().bind([name, this.eventNamespace()].join('.'), callback);
+ },
+
+ _unlisten: function(name, callback) {
+ return this.$element().unbind([name, this.eventNamespace()].join('.'), callback);
+ }
+
+ });
+
+ // <tt>Sammy.EventContext</tt> objects are created every time a route is run or a
+ // bound event is triggered. The callbacks for these events are evaluated within a <tt>Sammy.EventContext</tt>
+ // This within these callbacks the special methods of <tt>EventContext</tt> are available.
+ //
+ // === Example
+ //
+ // $.sammy(function() { with(this) {
+ // // The context here is this Sammy.Application
+ // get('#/:name', function() { with(this) {
+ // // The context here is a new Sammy.EventContext
+ // if (params['name'] == 'sammy') {
+ // partial('name.html.erb', {name: 'Sammy'});
+ // } else {
+ // redirect('#/somewhere-else')
+ // }
+ // }});
+ // }});
+ //
+ // Initialize a new EventContext
+ //
+ // === Arguments
+ //
+ // +app+:: The <tt>Sammy.Application</tt> this event is called within.
+ // +verb+:: The verb invoked to run this context/route.
+ // +path+:: The string path invoked to run this context/route.
+ // +params+:: An Object of optional params to pass to the context. Is converted
+ // to a <tt>Sammy.Object</tt>.
+ Sammy.EventContext = function(app, verb, path, params) {
+ this.app = app;
+ this.verb = verb;
+ this.path = path;
+ this.params = new Sammy.Object(params);
+ };
+
+ Sammy.EventContext.prototype = $.extend({}, Sammy.Object.prototype, {
+
+ // A shortcut to the app's <tt>$element()</tt>
+ $element: function() {
+ return this.app.$element();
+ },
+
+ // Used for rendering remote templates or documents within the current application/DOM.
+ // By default Sammy and <tt>partial()</tt> know nothing about how your templates
+ // should be interpeted/rendered. This is easy to change, though. <tt>partial()</tt> looks
+ // for a method in <tt>EventContext</tt> that matches the extension of the file you're
+ // fetching (e.g. 'myfile.template' will look for a template() method, 'myfile.haml' => haml(), etc.)
+ // If no matching render method is found it just takes the file contents as is.
+ //
+ // If you're templates have different (or no) extensions, and you want to render them all
+ // through the same engine, you can set the default/fallback template engine on the app level
+ // by setting <tt>app.template_engine</tt> to the name of the engine or a <tt>function() {}</tt>
+ //
+ // === Caching
+ //
+ // If you use the <tt>Sammy.Cache</tt> plugin, remote requests will be automatically cached unless
+ // you explicitly set <tt>cache_partials</tt> to <tt>false</tt>
+ //
+ // === Example
+ //
+ // There are a couple different ways to use <tt>partial()</tt>:
+ //
+ // partial('doc.html');
+ // //=> Replaces $element() with the contents of doc.html
+ //
+ // use(Sammy.Template);
+ // //=> includes the template() method
+ // partial('doc.template', {name: 'Sammy'});
+ // //=> Replaces $element() with the contents of doc.template run through <tt>template()</tt>
+ //
+ // partial('doc.html', function(data) {
+ // // data is the contents of the template.
+ // $('.other-selector').html(data);
+ // });
+ //
+ // === Iteration/Arrays
+ //
+ // If the data object passed to <tt>partial()</tt> is an Array, <tt>partial()</tt>
+ // will itterate over each element in data calling the callback with the
+ // results of interpolation and the index of the element in the array.
+ //
+ // use(Sammy.Template);
+ // // item.template => "<li>I'm an item named <%= name %></li>"
+ // partial('item.template', [{name: "Item 1"}, {name: "Item 2"}])
+ // //=> Replaces $element() with:
+ // // <li>I'm an item named Item 1</li><li>I'm an item named Item 2</li>
+ // partial('item.template', [{name: "Item 1"}, {name: "Item 2"}], function(rendered, i) {
+ // rendered; //=> <li>I'm an item named Item 1</li> // for each element in the Array
+ // i; // the 0 based index of the itteration
+ // });
+ //
+ partial: function(path, data, callback) {
+ var file_data,
+ wrapped_callback,
+ engine,
+ data_array,
+ cache_key = 'partial:' + path,
+ context = this;
+
+ // engine setup
+ if ((engine = path.match(/\.([^\.]+)$/))) { engine = engine[1]; }
+ // set the engine to the default template engine if no match is found
+ if ((!engine || !$.isFunction(context[engine])) && this.app.template_engine) {
+ engine = this.app.template_engine;
+ }
+ if (engine && !$.isFunction(engine) && $.isFunction(context[engine])) {
+ engine = context[engine];
+ }
+ if (!callback && $.isFunction(data)) {
+ // callback is in the data position
+ callback = data;
+ data = {};
+ }
+ data_array = ($.isArray(data) ? data : [data || {}]);
+ wrapped_callback = function(response) {
+ var new_content = response,
+ all_content = "";
+ $.each(data_array, function(i, idata) {
+ // extend the data object with the context
+ $.extend(idata, context);
+ if ($.isFunction(engine)) {
+ new_content = engine.apply(context, [response, idata]);
+ }
+ // collect the content
+ all_content += new_content;
+ // if callback exists call it for each iteration
+ if (callback) {
+ // return the result of the callback
+ // (you can bail the loop by returning false)
+ return callback.apply(context, [new_content, i]);
+ }
+ });
+ if (!callback) { context.swap(all_content); }
+ context.trigger('changed');
+ };
+ if (this.app.cache_partials && this.cache(cache_key)) {
+ // try to load the template from the cache
+ wrapped_callback.apply(context, [this.cache(cache_key)]);
+ } else {
+ // the template wasnt cached, we need to fetch it
+ $.get(path, function(response) {
+ if (context.app.cache_partials) { context.cache(cache_key, response); }
+ wrapped_callback.apply(context, [response]);
+ });
+ }
+ },
+
+ // Changes the location of the current window. If <tt>to</tt> begins with
+ // '#' it only changes the document's hash. If passed more than 1 argument
+ // redirect will join them together with forward slashes.
+ //
+ // === Example
+ //
+ // redirect('#/other/route');
+ // // equivilent to
+ // redirect('#', 'other', 'route');
+ //
+ redirect: function() {
+ var to, args = $.makeArray(arguments),
+ current_location = this.app.getLocation();
+ if (args.length > 1) {
+ args.unshift('/');
+ to = this.join.apply(this, args);
+ } else {
+ to = args[0];
+ }
+ this.trigger('redirect', {to: to});
+ this.app.last_location = this.path;
+ this.app.setLocation(to);
+ if (current_location == to) {
+ this.app.trigger('location-changed');
+ }
+ },
+
+ // Triggers events on <tt>app</tt> within the current context.
+ trigger: function(name, data) {
+ if (typeof data == 'undefined') { data = {}; }
+ if (!data.context) { data.context = this; }
+ return this.app.trigger(name, data);
+ },
+
+ // A shortcut to app's <tt>eventNamespace()</tt>
+ eventNamespace: function() {
+ return this.app.eventNamespace();
+ },
+
+ // A shortcut to app's <tt>swap()</tt>
+ swap: function(contents) {
+ return this.app.swap(contents);
+ },
+
+ // Raises a possible <tt>notFound()</tt> error for the current path.
+ notFound: function() {
+ return this.app.notFound(this.verb, this.path);
+ },
+
+ // //=> Sammy.EventContext: get #/ {}
+ toString: function() {
+ return "Sammy.EventContext: " + [this.verb, this.path, this.params].join(' ');
+ }
+
+ });
+
+ // An alias to Sammy
+ $.sammy = window.Sammy = Sammy;
+
+})(jQuery);
117 lib/sammy.template.js
@@ -0,0 +1,117 @@
+(function($) {
+
+ // Simple JavaScript Templating
+ // John Resig - http://ejohn.org/ - MIT Licensed
+ // adapted from: http://ejohn.org/blog/javascript-micro-templating/
+ // originally $.srender by Greg Borenstein http://ideasfordozens.com in Feb 2009
+ // modified for Sammy by Aaron Quint for caching templates by name
+ var srender_cache = {};
+ var srender = function(name, template, data) {
+ // target is an optional element; if provided, the result will be inserted into it
+ // otherwise the result will simply be returned to the caller
+ if (srender_cache[name]) {
+ fn = srender_cache[name];
+ } else {
+ if (typeof template == 'undefined') {
+ // was a cache check, return false
+ return false;
+ }
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ fn = srender_cache[name] = new Function("obj",
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+ // Introduce the data as local variables using with(){}
+ "with(obj){p.push(\"" +
+
+ // Convert the template into pure JavaScript
+ template
+ .replace(/[\r\t\n]/g, " ")
+ .replace(/\"/g, '\\"')
+ .split("<%").join("\t")
+ .replace(/((^|%>)[^\t]*)/g, "$1\r")
+ .replace(/\t=(.*?)%>/g, "\",$1,\"")
+ .split("\t").join("\");")
+ .split("%>").join("p.push(\"")
+ .split("\r").join("")
+ + "\");}return p.join('');");
+ }
+
+ if (typeof data != 'undefined') {
+ return fn(data);
+ } else {
+ return fn;
+ }
+ };
+
+ Sammy = Sammy || {};
+
+ // <tt>Sammy.Template</tt> is a simple plugin that provides a way to create
+ // and render client side templates. The rendering code is based on John Resig's
+ // quick templates and Greg Borenstien's srender plugin.
+ // This is also a great template/boilerplate for Sammy plugins.
+ //
+ // Templates use <% %> tags to denote embedded javascript.
+ //
+ // === Examples
+ //
+ // Here is an example template (user.template):
+ //
+ // <div class="user">
+ // <div class="user-name"><%= user.name %></div>
+ // <% if (user.photo_url) { %>
+ // <div class="photo"><img src="<%= user.photo_url %>" /></div>
+ // <% } %>
+ // </div>
+ //
+ // Given that is a publicly accesible file, you would render it like:
+ //
+ // $.sammy(function() {
+ // // include the plugin
+ // this.use(Sammy.Template);
+ //
+ // this.get('#/', function() {
+ // // the template is rendered in the current context.
+ // this.user = {name: 'Aaron Quint'};
+ // // partial calls template() because of the file extension
+ // this.partial('user.template');
+ // })
+ // });
+ //
+ // You can also pass a second argument to use() that will alias the template
+ // method and therefore allow you to use a different extension for template files
+ // in <tt>partial()</tt>
+ //
+ // // alias to 'tpl'
+ // this.use(Sammy.Template, 'tpl');
+ //
+ // // now .tpl files will be run through srender
+ // this.get('#/', function() {
+ // this.partial('myfile.tpl');
+ // });
+ //
+ Sammy.Template = function(app, method_alias) {
+
+ // *Helper:* Uses simple templating to parse ERB like templates.
+ //
+ // === Arguments
+ //
+ // +template+:: A String template. '<% %>' tags are evaluated as Javascript and replaced with the elements in data.
+ // +data+:: An Object containing the replacement values for the template.
+ // data is extended with the <tt>EventContext</tt> allowing you to call its methods within the template.
+ // +name+:: An optional String name to cache the template.
+ //
+ var template = function(template, data, name) {
+ // use name for caching
+ if (typeof name == 'undefined') name = template;
+ return srender(name, template, $.extend({}, this, data));
+ };
+
+ // set the default method name/extension
+ if (!method_alias) method_alias = 'template';
+ // create the helper at the method alias
+ app.helper(method_alias, template);
+
+ };
+
+})(jQuery);
11 readme.markdown
@@ -0,0 +1,11 @@
+# Sammy's Todos
+
+This is a demo todo list app built on top of Sammy.js. It uses some advanced browser features and therefore requires an advanced browser. Features like CSS3 and HTML5 storage.
+
+You can see a live demo of the app at: http://sammystodos.brandonaaron.net/
+
+## License
+
+Sammy's Todos is licensed under the MIT License (LICENSE.txt).
+
+Copyright (c) 2010 [Brandon Aaron](http://brandonaaron.net)
2