Commit 87d177de authored by Sam Saccone's avatar Sam Saccone

Merge pull request #1427 from mckamey/master

DUEL: App Update (#1110), cancel editing on escape keypress (#789), and routing support (#812)
parents c70a02b6 18bca51b
......@@ -26,7 +26,9 @@
"excludeFiles": [
"**/node_modules/**",
"**/bower_components/**",
"examples/vanilladart/**/*.js"
"examples/vanilladart/**/*.js",
"examples/duel/www/**",
"examples/duel/src/main/webapp/js/lib/**"
],
"requireSpaceBeforeBlockStatements": true,
"requireParenthesesAroundIIFE": true,
......
target/
node_modules/
www/cdn/debug/
{
"name": "todomvc-duel",
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.3.0"
}
}
{
"private": true,
"scripts": {
"postinstall": "cp node_modules/todomvc-app-css/*.css src/main/webapp/css/ && cp node_modules/todomvc-common/*.css src/main/webapp/css/ && cp node_modules/todomvc-common/base.js src/main/webapp/js/lib/todos.js"
},
"dependencies": {
"todomvc-app-css": "^2.0.0",
"todomvc-common": "^1.0.1"
}
}
......@@ -3,11 +3,12 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.todos</groupId>
<groupId>com.todomvc.duel</groupId>
<artifactId>todomvc</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
<packaging>war</packaging>
<name>TodoMVC</name>
......@@ -17,25 +18,32 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<resourcesDir>${project.basedir}/src/main/resources</resourcesDir>
<staticapps.version>0.8.5</staticapps.version>
<merge.version>0.5.2</merge.version>
<duel.version>0.8.2</duel.version>
<slf4j.version>1.6.4</slf4j.version>
<javac.version>1.6</javac.version>
<staticapps.version>0.9.11</staticapps.version>
<merge.version>0.6.0</merge.version>
<duel.version>0.9.7</duel.version>
<slf4j.version>1.7.12</slf4j.version>
<javac.version>1.7</javac.version>
<duel.clientPrefix>todos.views</duel.clientPrefix>
<duel.serverPrefix>com.example.todos.views</duel.serverPrefix>
<duel.serverPrefix>com.todomvc.duel.views</duel.serverPrefix>
<duel.sourceDir>${resourcesDir}/views/</duel.sourceDir>
<duel.clientPath>/js/</duel.clientPath>
<merge.cdnMapFile>/cdn.properties</merge.cdnMapFile>
<merge.cdnRoot>/cdn/</merge.cdnRoot>
<merge.cdnFiles>.ico .png .jpg .gif .eot .woff .ttf .svg .svgz</merge.cdnFiles>
<merge.cdnFiles>.ico .png .jpg .gif .cur .eot .woff .ttf .svg .svgz</merge.cdnFiles>
<staticapps.config>${project.basedir}/staticapp.json</staticapps.config>
</properties>
<dependencies>
<!-- SLF4J runtime -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- DUEL runtime -->
<dependency>
<groupId>org.duelengine</groupId>
......@@ -49,13 +57,6 @@
<artifactId>duel-staticapps</artifactId>
<version>${staticapps.version}</version>
</dependency>
<!-- SLF4J runtime -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
<build>
......@@ -75,10 +76,11 @@
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.0-beta-1</version>
<version>2.2</version>
<configuration>
<path>/</path>
<port>8080</port>
<uriEncoding>UTF-8</uriEncoding>
<warSourceDirectory>${project.build.directory}/${project.build.finalName}/</warSourceDirectory>
</configuration>
</plugin>
......
# DUEL TodoMVC Example
# DUEL • [TodoMVC](http://todomvc.com)
> DUEL is a dual-side templating engine using HTML for layout and 100% pure JavaScript as the binding language. The same views may be executed both directly in the browser (client-side template) and on the server (server-side template).
> _[DUEL - bitbucket.org/mckamey/duel/wiki/Home](http://bitbucket.org/mckamey/duel/wiki/Home)_
## Resources
## Learning DUEL
- [Website](http://duelengine.org)
- [Documentation](http://bitbucket.org/mckamey/duel/wiki/Home)
- [Syntax](https://bitbucket.org/mckamey/duel/wiki/Syntax)
- [Examples](https://bitbucket.org/mckamey/duel/wiki/Examples)
- [Source](https://bitbucket.org/mckamey/duel/src)
The [DUEL website](http://bitbucket.org/mckamey/duel/wiki/Home) is a great resource for getting started.
Here are some links you may find helpful:
* [Syntax](https://bitbucket.org/mckamey/duel/wiki/Syntax)
* [Examples](https://bitbucket.org/mckamey/duel/wiki/Examples)
* [DUEL on BitBucket](https://bitbucket.org/mckamey/duel/src)
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
*Let us [know](https://github.com/tastejs/todomvc/issues) if you discover anything worth sharing.*
## Implementation
......@@ -44,4 +40,8 @@ To run a debug-able version using Tomcat 7 as the web server, use this Maven com
mvn tomcat7:run
Then navigate your browser to http://localhost:8080/
Then navigate your browser to <http://localhost:8080/>
## Credit
Created by [Stephen McKamey](http://mck.me)
<view name="HomePage"><!doctype html>
<html lang="en" data-framework="duel">
<html lang="en">
<head>
<meta charset="utf-8">
<title>DUEL &bull; TodoMVC</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DUEL • TodoMVC</title>
<link rel="stylesheet" href="/css/styles.merge">
</head>
<body>
<footer id="info">
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Ported to <a href="http://duelengine.org">DUEL</a> by <a href="http://mck.me">Stephen McKamey</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
......
<view name="Stats">
<%-- this footer should hidden by default and shown when there are todos --%>
<footer id="footer" if="<%= data.total %>">
<span id="todo-count"><strong><%= data.active %></strong> <%= (data.active === 1) ? 'item' : 'items' %> left</span>
<%-- This footer should hidden by default and shown when there are todos. --%>
<footer class="footer" if="<%= data.total %>">
<span class="todo-count"><strong><%= data.active %></strong> <%= (data.active === 1) ? 'item' : 'items' %> left</span>
<%-- TODO: implement routing
<ul id="filters">
<ul class="filters">
<li>
<a class="selected" href="#/">All</a>
<a href="#/" class="<%= !data.filter ? 'selected' : null %>">All</a>
</li>
<li>
<a href="#/active">Active</a>
<a href="#/active" class="<%= data.filter === 'active' ? 'selected' : null %>">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
<a href="#/completed" class="<%= data.filter === 'completed' ? 'selected' : null %>">Completed</a>
</li>
</ul>
--%>
<button id="clear-completed"
<%-- Hidden if no completed items are left ↓ --%>
<button class="clear-completed"
if="<%= data.completed %>"
onclick="<%= todos.actions.clear_click %>">Clear completed</button>
onclick="<%= todos.actions.clearOnClick %>">Clear completed</button>
</footer>
<view name="Task">
<%-- could have embedded in 'tasks' for-loop, but this allows us to add single tasks --%>
<%-- List items should get the class `editing` when editing and `completed` when marked as completed. --%>
<li class="<%= data.completed ? 'completed' : '' %>">
<div class="view">
<input class="toggle" type="checkbox" checked="<%= data.completed %>"
onchange="<%= todos.actions.completed_change(data.id) %>">
<label ondblclick="<%= todos.actions.content_dblclick(data.id) %>"><%= data.title %></label>
<button class="destroy" onclick="<%= todos.actions.remove_click(data.id) %>"></button>
onchange="<%= todos.actions.completedOnChange(data.id) %>">
<label ondblclick="<%= todos.actions.editOnDblclick %>"><%= data.title %></label>
<button class="destroy" onclick="<%= todos.actions.removeOnClick(data.id) %>"></button>
</div>
<input class="edit" type="text" value="<%= data.title %>"
onblur="<%= todos.actions.edit_blur(data.id) %>"
onkeypress="<%= todos.actions.edit_keypress(data.id) %>">
onblur="<%= todos.actions.editOnBlur(data.id) %>"
onkeydown="<%= todos.actions.editOnKeydown(data.id) %>">
</li>
<view name="Tasks">
<%-- this section should hidden by default and shown when there are todos --%>
<section id="main" if="<%= data.tasks.length %>">
<input id="toggle-all" type="checkbox" checked="<%= !data.stats.active %>"
onchange="<%= todos.actions.toggle_change %>">
<%-- This section should be hidden by default and shown when there are todos. --%>
<section class="main" if="<%= data.tasks && data.tasks.length %>">
<input class="toggle-all" type="checkbox" checked="<%= !data.stats.active %>"
onchange="<%= todos.actions.toggleOnChange %>">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<ul class="todo-list">
<for each="<%= data.tasks %>">
<call view="Task">
</for>
......
<view name="TodoApp">
<section id="todoapp">
<header id="header">
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input id="new-todo"
placeholder="What needs to be done?"
autofocus
onblur="<%= todos.actions.add_blur %>"
onkeypress="<%= todos.actions.add_keypress %>">
<input class="new-todo" placeholder="What needs to be done?" autofocus
onblur="<%= todos.actions.add_blur %>" onkeydown="<%= todos.actions.addOnKeydown %>">
</header>
<call view="Tasks" data="data" />
<call view="Stats" data="data.stats" />
</section>
......@@ -21,4 +21,17 @@
<servlet-name>routing-servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<mime-mapping>
<extension>html</extension>
<mime-type>text/html;charset=utf-8</mime-type>
</mime-mapping>
<mime-mapping>
<extension>css</extension>
<mime-type>text/css;charset=utf-8</mime-type>
</mime-mapping>
<mime-mapping>
<extension>js</extension>
<mime-type>application/javascript;charset=utf-8</mime-type>
</mime-mapping>
</web-app>
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}
......@@ -12,111 +12,85 @@ button {
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea url('bg.png');
background: #f5f5f5;
color: #4d4d4d;
width: 550px;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
font-weight: 300;
}
button,
input[type="checkbox"] {
outline: none;
outline: none;
}
#todoapp {
.hidden {
display: none;
}
.todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#todoapp input::-webkit-input-placeholder {
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#todoapp input::-moz-placeholder {
.todoapp input::input-placeholder {
font-style: italic;
color: #a9a9a9;
font-weight: 300;
color: #e6e6e6;
}
#todoapp h1 {
.todoapp h1 {
position: absolute;
top: -120px;
top: -155px;
width: 100%;
font-size: 70px;
font-weight: bold;
font-size: 100px;
font-weight: 100;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
outline: none;
......@@ -124,89 +98,83 @@ input[type="checkbox"] {
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
#main {
.main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
border-top: 1px solid #e6e6e6;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
.toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
top: -55px;
left: -12px;
width: 60px;
height: 34px;
text-align: center;
/* Mobile Safari */
border: none;
border: none; /* Mobile Safari */
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
.toggle-all:before {
content: '';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
#toggle-all:checked:before {
.toggle-all:checked:before {
color: #737373;
}
#todo-list {
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
border-bottom: 1px solid #ededed;
}
#todo-list li:last-child {
.todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
.todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
......@@ -215,47 +183,35 @@ label[for='toggle-all'] {
top: 0;
bottom: 0;
margin: auto 0;
/* Mobile Safari */
border: none;
border: none; /* Mobile Safari */
-webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
content: '✔';
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
.todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
#todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
.todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
#todo-list li label {
.todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li.completed label {
color: #a9a9a9;
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
#todo-list li .destroy {
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
......@@ -264,68 +220,65 @@ label[for='toggle-all'] {
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
transition: all 0.2s;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
transform: scale(1.3);
.todo-list li .destroy:hover {
color: #af5b5e;
}
#todo-list li .destroy:after {
content: '';
.todo-list li .destroy:after {
content: '×';
}
#todo-list li:hover .destroy {
.todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
.todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
.footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
padding: 10px 15px;
height: 20px;
z-index: 1;
text-align: center;
border-top: 1px solid #e6e6e6;
}
#footer:before {
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
bottom: 0;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
.todo-count {
float: left;
text-align: left;
}
#filters {
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
......@@ -334,69 +287,79 @@ label[for='toggle-all'] {
left: 0;
}
#filters li {
.filters li {
display: inline;
}
#filters li a {
color: #83756f;
margin: 2px;
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
#filters li a.selected {
font-weight: bold;
.filters li a.selected,
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
#clear-completed {
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
cursor: pointer;
position: relative;
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
.clear-completed:hover {
text-decoration: underline;
}
#info {
.info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
#info a {
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
.toggle-all,
.todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
.todo-list li .toggle {
height: 40px;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
.toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
......@@ -404,151 +367,12 @@ label[for='toggle-all'] {
}
}
.hidden {
display: none;
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
-webkit-transition-property: left;
transition-property: left;
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
.learn-bar > .learn {
left: 8px;
@media (max-width: 430px) {
.footer {
height: 50px;
}
.learn-bar #todoapp {
width: 550px;
margin: 130px auto 40px auto;
.filters {
bottom: 10px;
}
}
# todomvc base file
/bower_components/todomvc-common/base.css
# todomvc base files
/css/base.css
/css/index.css
/*global window */
/**
* @license DUEL v0.8.2 http://duelengine.org
* @license DUEL v0.9.7 http://duelengine.org
* Copyright (c)2006-2012 Stephen M. McKamey.
* Licensed under The MIT License.
*/
/*jshint smarttabs:true */
/**
* @public
......@@ -67,7 +68,7 @@ var duel = (
/**
* Wraps a data value to maintain as raw markup in output
*
*
* @private
* @this {Markup}
* @param {string} value The value
......@@ -84,7 +85,7 @@ var duel = (
/**
* Renders the value
*
*
* @public
* @override
* @this {Markup}
......@@ -96,7 +97,7 @@ var duel = (
/**
* Determines if the value is an Array
*
*
* @private
* @param {*} val the object being tested
* @return {boolean}
......@@ -105,29 +106,9 @@ var duel = (
return (val instanceof Array);
};
/**
* Determines the type of the value
*
* @private
* @param {*} val the object being tested
* @return {number}
*/
function getType(val) {
switch (typeof val) {
case 'object':
return !val ? NUL : (isArray(val) ? ARY : ((val instanceof Markup) ? RAW : ((val instanceof Date) ? VAL : OBJ)));
case 'function':
return FUN;
case 'undefined':
return NUL;
default:
return VAL;
}
}
/**
* Determines if the value is a string
*
*
* @private
* @param {*} val the object being tested
* @return {boolean}
......@@ -138,7 +119,7 @@ var duel = (
/**
* Determines if the value is a function
*
*
* @private
* @param {*} val the object being tested
* @return {boolean}
......@@ -148,140 +129,28 @@ var duel = (
}
/**
* String buffer
*
* @private
* @this {Buffer}
* @constructor
*/
function Buffer() {
/**
* @type {Array|string}
* @private
*/
this.value = Buffer.FAST ? '' : [];
}
/**
* Only IE<9 benefits from Array.join()
*
* @private
* @constant
* @type {boolean}
*/
Buffer.FAST = !(scriptEngine && scriptEngine() < 9);
/**
* Appends to the internal value
*
* @public
* @this {Buffer}
* @param {null|string} v1
* @param {null|string=} v2
* @param {null|string=} v3
*/
Buffer.prototype.append = function(v1, v2, v3) {
if (Buffer.FAST) {
if (v1 !== null) {
this.value += v1;
if (v2 !== null && v2 !== undef) {
this.value += v2;
if (v3 !== null && v3 !== undef) {
this.value += v3;
}
}
}
} else {
this.value.push.apply(
// Closure Compiler type cast
/** @type{Array} */(this.value),
arguments);
}
};
/**
* Clears the internal value
*
* @public
* @this {Buffer}
*/
Buffer.prototype.clear = function() {
this.value = Buffer.FAST ? '' : [];
};
/**
* Renders the value
*
* @public
* @override
* @this {Buffer}
* @return {string} value
*/
Buffer.prototype.toString = function() {
return Buffer.FAST ?
// Closure Compiler type cast
/** @type{string} */(this.value) :
this.value.join('');
};
function digits(n) {
return (n < 10) ? '0'+n : n;
}
/**
* Formats the value as a string
*
* Determines the type of the value
*
* @private
* @param {*} val the object being rendered
* @return {string|null}
* @param {*} val the object being tested
* @return {number}
*/
function asString(val) {
var buffer, needsDelim;
switch (getType(val)) {
case VAL:
return ''+val;
case NUL:
return '';
case ARY:
// flatten into simple list
buffer = new Buffer();
for (var i=0, length=val.length; i<length; i++) {
if (needsDelim) {
buffer.append(', ');
} else {
needsDelim = true;
}
buffer.append(asString(val[i]));
}
return buffer.toString();
case OBJ:
// format JSON-like
buffer = new Buffer();
buffer.append('{');
for (var key in val) {
if (val.hasOwnProperty(key)) {
if (needsDelim) {
buffer.append(', ');
} else {
needsDelim = true;
}
buffer.append(key, '=', asString(val[key]));
}
}
buffer.append('}');
return buffer.toString();
function getType(val) {
switch (typeof val) {
case 'object':
return !val ? NUL : (isArray(val) ? ARY : ((val instanceof Markup) ? RAW : ((val instanceof Date) ? VAL : OBJ)));
case 'function':
return FUN;
case 'undefined':
return NUL;
default:
return VAL;
}
// Closure Compiler type cast
return /** @type{string} */(val);
}
/**
* Wraps a binding result with rendering methods
*
*
* @private
* @this {Result}
* @param {Array|Object|string|number} view The result tree
......@@ -292,7 +161,7 @@ var duel = (
// ensure is rooted element
view = ['', view];
}
/**
* @type {Array}
* @const
......@@ -402,11 +271,20 @@ var duel = (
*/
var NAME = 'name';
/**
* Callback allowed to modify the bound node
*
* @private
* @param {Array} elem bound node
* @return {Array}
*/
var bindFilter;
var bind;
/**
* Appends a node to a parent
*
*
* @private
* @param {Array} parent The parent node
* @param {Array|Object|string|number} child The child node
......@@ -479,7 +357,7 @@ var duel = (
/**
* Binds the child nodes ignoring parent element and attributes
*
*
* @private
* @param {Array} node The template subtree root
* @param {*} data The data item being bound
......@@ -510,7 +388,7 @@ var duel = (
/**
* Binds the content once for each item in data
*
*
* @private
* @param {Array|Object|string|number|function(*,number,number):*} node The template subtree root
* @param {*} data The data item being bound
......@@ -588,7 +466,7 @@ var duel = (
}
}
var type = getType(items);
var type = getType(items);
if (type === ARY) {
// iterate over the items
for (i=0, length=items.length; i<length; i++) {
......@@ -607,7 +485,7 @@ var duel = (
/**
* Binds the node to the first conditional block that evaluates to true
*
*
* @private
* @param {Array|Object|string|number|function(*,number,number):Array|Object|string} node The template subtree root
* @param {*} data The data item being bound
......@@ -644,7 +522,7 @@ var duel = (
/**
* Calls into another view
*
*
* @private
* @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root
* @param {*} data The data item being bound
......@@ -679,12 +557,12 @@ var duel = (
return (v && isFunction(v.getView)) ?
// Closure Compiler type cast
bind(v.getView(), d, /** @type {number} */i, /** @type {number} */c, /** @type {string} */k, p) : null;
bind(v.getView(), d, (/** @type {number} */i), (/** @type {number} */c), (/** @type {string} */k), p) : null;
}
/**
* Replaces a place holder part with the named part from the calling view
*
*
* @private
* @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root
* @param {*} data The data item being bound
......@@ -705,7 +583,7 @@ var duel = (
/**
* Binds the node to data
*
*
* @private
* @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root
* @param {*} data The data item being bound
......@@ -752,7 +630,7 @@ var duel = (
for (var i=1, length=node.length; i<length; i++) {
append(elem, bind(node[i], data, index, count, key, parts));
}
return elem;
return bindFilter ? bindFilter(elem) : elem;
case OBJ:
// attribute map
......@@ -770,22 +648,79 @@ var duel = (
return node;
};
/**
* Boolean attribute map (used by both render.js & dom.js)
*
* @private
* @constant
* @type {Object.<number>}
*/
var ATTR_BOOL = {
'allowfullscreen': 1,
'async': 1,
'autofocus': 1,
'autoplay': 1,
'checked': 1,
'compact': 1,
'controls': 1,
'declare': 1,
'default': 1,
'defaultchecked': 1,
'defaultmuted': 1,
'defaultselected': 1,
'defer': 1,
'disabled': 1,
'draggable': 1,
'enabled': 1,
'formnovalidate': 1,
'hidden': 1,
'indeterminate': 1,
'inert': 1,
'ismap': 1,
'itemscope': 1,
'loop': 1,
'multiple': 1,
'muted': 1,
'nohref': 1,
'noresize': 1,
'noshade': 1,
'novalidate': 1,
'nowrap': 1,
'open': 1,
'pauseonexit': 1,
'readonly': 1,
'required': 1,
'reversed': 1,
'scoped': 1,
'seamless': 1,
'selected': 1,
'sortable': 1,
'spellcheck': 1,
'translate': 1,
'truespeed': 1,
'typemustmatch': 1,
'visible': 1
// update non-readonly attributes as spec changes
// curl -s "http://www.w3.org/TR/html51/single-page.html" | grep "attribute boolean" > boolean.txt
};
/* factory.js --------------------*/
/**
* Renders an error as text
*
* Renders an error directly as text
*
* @private
* @param {Error} ex The exception
* @return {string}
* @return {string|Result}
*/
function onError(ex) {
return '['+ex+']';
}
var onError = function(ex) {
return '[ '+ex+' ]';
};
/**
* Wraps a view definition with binding method
*
*
* @private
* @param {Array|Object|string|number} view The template definition
* @return {function(*)}
......@@ -798,7 +733,7 @@ var duel = (
/**
* Binds and wraps the result
*
*
* @public
* @param {*} data The data item being bound
* @param {number} index The index of the current data item
......@@ -816,15 +751,24 @@ var duel = (
isFinite(count) ? count : 1,
isString(key) ? key : null);
return new Result(result);
} catch (ex) {
// handle error with context
return new Result(onError(ex));
var errValue = onError(ex);
if (errValue instanceof Result) {
return errValue;
} else {
// render the error as a text node
return new Result(''+errValue);
}
}
};
/**
* Gets the internal view definition
*
*
* @private
* @return {Array}
*/
......@@ -845,6 +789,26 @@ var duel = (
return (isFunction(view) && isFunction(view.getView)) ? view : factory(view);
};
/**
* @public
* @param {string} value error callback
*/
duel.onerror = function(value) {
if (isFunction(value)) {
onError = value;
}
};
/**
* @public
* @param {string} value onbind filter callback
*/
duel.onbind = function(value) {
if (isFunction(value)) {
bindFilter = value;
}
};
/**
* @public
* @param {string} value Markup text
......@@ -858,115 +822,174 @@ var duel = (
/**
* Void tag lookup
*
*
* @private
* @constant
* @type {Object.<boolean>}
* @type {Object.<number>}
*/
var VOID_TAGS = {
'area' : true,
'base' : true,
'basefont' : true,
'br' : true,
'col' : true,
'frame' : true,
'hr' : true,
'img' : true,
'input' : true,
'isindex' : true,
'keygen' : true,
'link' : true,
'meta' : true,
'param' : true,
'source' : true,
'wbr' : true
'area': 1,
'base': 1,
'basefont': 1,
'br': 1,
'col': 1,
'frame': 1,
'embed': 1,
'hr': 1,
'img': 1,
'input': 1,
'isindex': 1,
'keygen': 1,
'link': 1,
'menuitem': 1,
'meta': 1,
'param': 1,
'source': 1,
'track': 1,
'wbr': 1
// update elements as spec changes
// http://www.w3.org/TR/html51/single-page.html#void-elements
};
/**
* Boolean attribute map
*
* String buffer
*
* @private
* @this {Buffer}
* @constructor
*/
function Buffer() {
/**
* @type {Array|string}
* @private
*/
this.value = Buffer.FAST ? '' : [];
}
/**
* IE<9 benefits from Array.join() for large strings
*
* @private
* @constant
* @type {Object.<number>}
* @type {boolean}
*/
var ATTR_BOOL = {
'async': 1,
'checked': 1,
'defer': 1,
'disabled': 1,
'hidden': 1,
'novalidate': 1,
'formnovalidate': 1
// can add more attributes here as needed
Buffer.FAST = !(scriptEngine && scriptEngine() < 9);
/**
* Appends to the internal value
*
* @public
* @this {Buffer}
* @param {string} a
* @param {string=} b
* @param {string=} c
*/
Buffer.prototype.append = function(a, b, c) {
var args = arguments;
if (Buffer.FAST) {
var len = args.length;
if (len > 1) {
if (len > 2) {
b += c;
}
a += b;
}
this.value += a;
} else {
this.value.push.apply(
// Closure Compiler type cast
/** @type{Array} */(this.value),
args);
}
};
/**
* Renders the value
*
* @public
* @override
* @this {Buffer}
* @return {string} value
*/
Buffer.prototype.toString = function() {
return Buffer.FAST ?
// Closure Compiler type cast
/** @type{string} */(this.value) :
this.value.join('');
};
// /**
// * Resets the internal value
// *
// * @public
// * @this {Buffer}
// */
// Buffer.prototype.clear = function() {
// this.value = Buffer.FAST ? '' : [];
// };
/**
* Encodes invalid literal characters in strings
*
*
* @private
* @param {Array|Object|string|number} val The value
* @return {Array|Object|string|number}
* @return {string}
*/
function htmlEncode(val) {
if (!isString(val)) {
return val;
return (val !== null && val !== undef) ? ''+val : '';
}
return val.replace(/[&<>]/g,
function(ch) {
switch(ch) {
case '&':
return '&amp;';
case '<':
return '&lt;';
case '>':
return '&gt;';
default:
return ch;
}
});
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;'
};
return val.replace(/[&<>]/g, function(ch) {
return map[ch] || ch;
});
}
/**
* Encodes invalid attribute characters in strings
*
*
* @private
* @param {Array|Object|string|number} val The value
* @return {Array|Object|string|number}
* @return {string}
*/
function attrEncode(val) {
if (!isString(val)) {
return val;
return (val !== null && val !== undef) ? ''+val : '';
}
return val.replace(/[&<>"]/g,
function(ch) {
switch(ch) {
case '&':
return '&amp;';
case '<':
return '&lt;';
case '>':
return '&gt;';
case '"':
return '&quot;';
default:
return ch;
}
});
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;'
};
return val.replace(/[&<>"]/g, function(ch) {
return map[ch] || ch;
});
}
/**
* Renders the comment as a string
*
*
* @private
* @param {Buffer} buffer The output buffer
* @param {Array} node The result tree
*/
function renderComment(buffer, node) {
if (node[0] === '!DOCTYPE') {
if (node[0].toLowerCase() === '!doctype') {
// emit doctype
buffer.append('<!DOCTYPE ', node[1], '>');
buffer.append('<!doctype ', node[1], '>');
} else {
// emit HTML comment
buffer.append('<!--', node[1], '-->');
......@@ -975,7 +998,7 @@ var duel = (
/**
* Renders the element as a string
*
*
* @private
* @param {Buffer} buffer The output buffer
* @param {Array} node The result tree
......@@ -1002,20 +1025,23 @@ var duel = (
for (var name in child) {
if (child.hasOwnProperty(name)) {
var val = child[name];
if (ATTR_BOOL[name]) {
if (ATTR_BOOL[name.toLowerCase()]) {
if (val) {
val = name;
} else {
// falsey boolean attributes must not be present
continue;
}
}
if (getType(val) === NUL) {
// null/undefined removes attributes
continue;
}
buffer.append(' ', name);
if (getType(val) !== NUL) {
// Closure Compiler type cast
buffer.append('="', /** @type{string} */(attrEncode(val)), '"');
}
// Closure Compiler type cast
buffer.append('="', attrEncode(val), '"');
}
}
i++;
......@@ -1031,10 +1057,10 @@ var duel = (
child = node[i];
if (isArray(child)) {
renderElem(buffer, child);
} else {
// encode string literals
// Closure Compiler type cast
buffer.append(/** @type{string} */(htmlEncode(child)));
buffer.append(htmlEncode(child));
}
}
......@@ -1042,29 +1068,38 @@ var duel = (
// emit close tag
buffer.append('</', tag, '>');
}
return buffer;
}
/**
* Renders the result as a string
*
*
* @private
* @param {Array} view The compiled view
* @return {string}
*/
function render(view) {
try {
var buffer = new Buffer();
renderElem(buffer, view);
return buffer.toString();
return renderElem(new Buffer(), view).toString();
} catch (ex) {
// handle error with context
return onError(ex);
var errValue = onError(ex);
if (errValue instanceof Result) {
return render(errValue.value);
} else {
// render the error as a string
return (''+errValue);
}
}
}
/**
* Returns result as HTML text
*
*
* @public
* @override
* @this {Result}
......@@ -1076,7 +1111,7 @@ var duel = (
/**
* Immediately writes the resulting value to the document
*
*
* @public
* @this {Result}
* @param {Document} doc optional Document reference
......@@ -1105,43 +1140,80 @@ var duel = (
/**
* Attribute name map
*
*
* @private
* @constant
* @type {Object.<string>}
*/
var ATTR_MAP = {
'rowspan': 'rowSpan',
'colspan': 'colSpan',
'allowfullscreen': 'allowFullscreen',
'accesskey': 'accessKey',
'bgcolor': 'bgColor',
'cellpadding': 'cellPadding',
'cellspacing': 'cellSpacing',
'tabindex': 'tabIndex',
'accesskey': 'accessKey',
'checked': 'defaultChecked',
'class': 'className',
'colspan': 'colSpan',
'contenteditable': 'contentEditable',
'defaultchecked': 'defaultChecked',
'defaultselected': 'defaultSelected',
'defaultmuted': 'defaultMuted',
'for': 'htmlFor',
'formnovalidate': 'formNoValidate',
'hidefocus': 'hideFocus',
'usemap': 'useMap',
'ismap': 'isMap',
'itemscope': 'itemScope',
'maxlength': 'maxLength',
'muted': 'defaultMuted',
'nohref': 'noHref',
'noresize': 'noResize',
'noshade': 'noShade',
'novalidate': 'noValidate',
'nowrap': 'noWrap',
'pauseonexit': 'pauseOnExit',
'readonly': 'readOnly',
'contenteditable': 'contentEditable'
'rowspan': 'rowSpan',
'selected': 'defaultSelected',
'spellcheck': 'spellCheck',
'tabindex': 'tabIndex',
'truespeed': 'trueSpeed',
'typemustmatch': 'typeMustMatch',
'usemap': 'useMap',
'willvalidate': 'willValidate'
// can add more attributes here as needed
};
/**
* Attribute duplicates map
*
*
* @private
* @constant
* @type {Object.<string>}
*/
var ATTR_DUP = {
'enctype': 'encoding',
'onscroll': 'DOMMouseScroll',
'checked': 'defaultChecked'
'onscroll': 'DOMMouseScroll'
// can add more attributes here as needed
};
/**
* Attributes to be set via DOM
*
* @private
* @constant
* @type {Object.<number>}
*/
var ATTR_DOM = {
'autocapitalize': 1,
'autocomplete': 1,
'autocorrect': 1,
'type': 1
// can add more attributes here as needed
};
/**
* Leading SGML line ending pattern
*
*
* @private
* @constant
* @type {RegExp}
......@@ -1150,7 +1222,7 @@ var duel = (
/**
* Trailing SGML line ending pattern
*
*
* @private
* @constant
* @type {RegExp}
......@@ -1158,8 +1230,8 @@ var duel = (
var TRAILING = /[\r\n]+$/;
/**
* Creates a DOM element
*
* Creates a DOM element
*
* @private
* @param {string} tag The element's tag name
* @return {Node}
......@@ -1187,7 +1259,7 @@ var duel = (
/**
* Appends a child to an element
*
*
* @private
* @param {Node} elem The parent element
* @param {Node} child The child
......@@ -1248,36 +1320,58 @@ var duel = (
/**
* Adds an event handler to an element
*
*
* @private
* @param {Node} elem The element
* @param {string} name The event name
* @param {function(Event)} handler The event handler
*/
function addHandler(elem, name, handler) {
if (name.substr(0,2) === 'on') {
name = name.substr(2);
}
switch (typeof handler) {
case 'function':
if (elem.addEventListener) {
// DOM Level 2
elem.addEventListener((name.substr(0,2) === 'on') ? name.substr(2) : name, handler, false);
elem.addEventListener(name, handler, false);
} else if (isFunction(window.jQuery) && getType(elem[name]) !== NUL) {
// cop out and patch IE6-8 with jQuery
var $elem = window.jQuery(elem);
if (isFunction($elem.on)) {
$elem.on(name, handler); // v1.7+
} else {
$elem.bind(name, handler); // pre-1.7
}
} else if (elem.attachEvent && getType(elem[name]) !== NUL) {
// IE legacy events
elem.attachEvent('on'+name, handler);
} else {
// DOM Level 0
elem[name] = handler;
var old = elem['on'+name] || elem[name];
elem['on'+name] = elem[name] = !isFunction(old) ? handler :
function(e) {
return (old.call(this, e) !== false) && (handler.call(this, e) !== false);
};
}
break;
case 'string':
// inline functions are DOM Level 0
/*jslint evil:true */
elem[name] = new Function('event', handler);
elem['on'+name] = new Function('event', handler);
/*jslint evil:false */
break;
}
}
/**
* Appends a child to an element
*
* Appends an attribute to an element
*
* @private
* @param {Node} elem The element
* @param {Object} attr Attributes object
......@@ -1304,52 +1398,70 @@ var duel = (
if (name) {
if (type === NUL) {
value = '';
type = VAL;
// null/undefined removes attributes
continue;
}
name = ATTR_MAP[name.toLowerCase()] || name;
if (ATTR_BOOL[name]) {
elem[name] = !!value;
// also set duplicated attributes
if (ATTR_DUP[name]) {
elem[ATTR_DUP[name]] = !!value;
}
} else if (name === 'style') {
if (typeof elem.style.cssText !== 'undefined') {
if (name === 'style') {
if (getType(elem.style.cssText) !== NUL) {
elem.style.cssText = value;
} else {
elem.style = value;
}
} else if (name === 'class') {
elem.className = value;
} else if (name.substr(0,2) === 'on') {
addHandler(elem, name, value);
// also set duplicated events
if (ATTR_DUP[name]) {
addHandler(elem, ATTR_DUP[name], value);
name = ATTR_DUP[name];
if (name) {
addHandler(elem, name, value);
}
} else if (type === VAL && name.charAt(0) !== '$') {
elem.setAttribute(name, value);
} else if (!ATTR_DOM[name.toLowerCase()] && (type !== VAL || name.charAt(0) === '$' || getType(elem[name]) !== NUL || getType(elem[ATTR_DUP[name]]) !== NUL)) {
// direct setting of existing properties
try {
elem[name] = value;
// also set duplicated attributes
if (ATTR_DUP[name]) {
elem.setAttribute(ATTR_DUP[name], value);
// also set duplicated properties
name = ATTR_DUP[name];
if (name) {
elem[name] = value;
}
} catch(ex2) {
if (name.toLowerCase() === 'type' && elem.tagName.toLowerCase() === 'input') {
// IE9 doesn't like HTML5 input types
continue;
}
throw new Error('DOM property '+elem.tagName+'.'+name+': '+ex2);
}
} else if (ATTR_BOOL[name.toLowerCase()]) {
if (value) {
// boolean attributes
elem.setAttribute(name, name);
// also set duplicated attributes
name = ATTR_DUP[name];
if (name) {
elem.setAttribute(name, name);
}
}
} else {
// allow direct setting of complex properties
elem[name] = value;
// http://www.quirksmode.org/dom/w3c_core.html#attributes
// custom and 'data-*' attributes
elem.setAttribute(name, value);
// also set duplicated attributes
if (ATTR_DUP[name]) {
elem[ATTR_DUP[name]] = value;
name = ATTR_DUP[name];
if (name) {
elem.setAttribute(name, value);
}
}
}
......@@ -1360,7 +1472,7 @@ var duel = (
/**
* Tests a node for whitespace
*
*
* @private
* @param {Node} node The node
* @return {boolean}
......@@ -1371,7 +1483,7 @@ var duel = (
/**
* Trims whitespace pattern from the text node
*
*
* @private
* @param {Node} node The node
*/
......@@ -1383,7 +1495,7 @@ var duel = (
/**
* Removes leading and trailing whitespace nodes
*
*
* @private
* @param {Node} elem The node
*/
......@@ -1406,7 +1518,7 @@ var duel = (
/**
* Converts the markup to DOM nodes
*
*
* @private
* @param {string|Markup} value The node
* @return {Node}
......@@ -1414,7 +1526,7 @@ var duel = (
function toDOM(value) {
var wrapper = createElement('div');
wrapper.innerHTML = ''+value;
// trim extraneous whitespace
trimWhitespace(wrapper);
......@@ -1433,7 +1545,7 @@ var duel = (
/**
* Retrieve and remove method
*
*
* @private
* @param {Node} elem The element
* @param {string} key The callback name
......@@ -1445,8 +1557,11 @@ var duel = (
try {
delete elem[key];
} catch (ex) {
// sometimes IE doesn't like deleting from DOM
elem[key] = undef;
try {
// IE7 doesn't like deleting from DOM
elem[key] = '';
elem.removeAttribute(key);
} catch (ex2) {}
}
if (!isFunction(method)) {
......@@ -1454,7 +1569,7 @@ var duel = (
/*jslint evil:true */
method = new Function(''+method);
/*jslint evil:false */
} catch (ex2) {
} catch (ex3) {
// filter
method = null;
}
......@@ -1466,7 +1581,7 @@ var duel = (
/**
* Executes oninit/onload callbacks
*
*
* @private
* @param {Node} elem The element
*/
......@@ -1498,7 +1613,7 @@ var duel = (
/**
* Applies node to DOM
*
*
* @private
* @param {Node} elem The element to append
* @param {Array} node The node to populate
......@@ -1559,20 +1674,9 @@ var duel = (
return elem;
}
/**
* Renders an error as a text node
*
* @private
* @param {Error} ex The exception
* @return {Node}
*/
function onErrorDOM(ex) {
return document.createTextNode(onError(ex));
}
/**
* Returns result as DOM objects
*
*
* @public
* @this {Result}
* @param {Node|string=} elem An optional element or element ID to be replaced or merged
......@@ -1582,9 +1686,14 @@ var duel = (
Result.prototype.toDOM = function(elem, merge) {
// resolve the element ID
if (getType(elem) === VAL) {
// try as id, then as query selector
elem = document.getElementById(
// Closure Compiler type cast
/** @type{string} */(elem));
/** @type{string} */(elem)) || (('querySelector' in document) ?
document.querySelector(
// Closure Compiler type cast
/** @type{string} */(elem)) :
null);
}
var view;
......@@ -1598,7 +1707,15 @@ var duel = (
} catch (ex) {
// handle error with context
view = onErrorDOM(ex);
var errValue = onError(ex);
if (errValue instanceof Result) {
return errValue.toDOM(elem || view);
} else {
// render the error as a text node
view = document.createTextNode(''+errValue);
}
}
if (elem && elem.parentNode) {
......@@ -1612,7 +1729,7 @@ var duel = (
/**
* Replaces entire document with this Result
*
*
* @public
* @this {Result}
*/
......@@ -1643,7 +1760,7 @@ var duel = (
} catch (ex) {
/*jslint evil:true*/
doc = doc.open('text/html');
doc.write(this.toString());
doc.write(''+this);
doc.close();
/*jslint evil:false*/
}
......
/* global _ */
(function () {
'use strict';
/* jshint ignore:start */
// Underscore's Template Module
// Courtesy of underscorejs.org
var _ = (function (_) {
......@@ -112,8 +114,14 @@
})({});
if (location.hostname === 'todomvc.com') {
window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script'));
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-31081062-1', 'auto');
ga('send', 'pageview');
}
/* jshint ignore:end */
function redirect() {
if (location.hostname === 'tastejs.github.io') {
......@@ -175,13 +183,17 @@
if (learnJSON.backend) {
this.frameworkJSON = learnJSON.backend;
this.frameworkJSON.issueLabel = framework;
this.append({
backend: true
});
} else if (learnJSON[framework]) {
this.frameworkJSON = learnJSON[framework];
this.frameworkJSON.issueLabel = framework;
this.append();
}
this.fetchIssueCount();
}
Learn.prototype.append = function (opts) {
......@@ -212,6 +224,26 @@
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
};
Learn.prototype.fetchIssueCount = function () {
var issueLink = document.getElementById('issue-count-link');
if (issueLink) {
var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) {
var count = parsedResponse.length;
if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline';
}
}
};
xhr.send();
}
};
redirect();
getFile('learn.json', Learn);
})();
# todomvc base file
/bower_components/todomvc-common/base.js
#/js/lib/todos.js
# libraries
/js/lib/duel.js
# model
/js/todos/model.js
# views
# compiled views
/js/todos/views/Stats.js
/js/todos/views/Task.js
/js/todos/views/Tasks.js
/js/todos/views/TodoApp.js
# model
/js/todos/model.js
# controller
/js/todos/controller.js
......@@ -8,52 +8,36 @@ var todos = todos || {};
/*-- private members -------------------------------*/
var ESC_KEY = 27;
var ENTER_KEY = 13;
var STATS_ID = 'footer';
var TODOAPP_ID = 'todoapp';
var TASKS_ID = 'main';
var LIST_ID = 'todo-list';
var EDITING_CSS = 'editing';
function getById(id) {
return document.getElementById(id);
}
function refreshStats(stats) {
// get the data
var data = stats || todos.model.stats();
// Poor man's routing.
addEventListener('hashchange', refreshView, false);
// build the view
var view = todos.views.Stats(data).toDOM();
function curFilter() {
return document.location.hash.substr(2);
}
// replace old stats
var old = getById(STATS_ID);
if (old) {
old.parentNode.replaceChild(view, old);
} else {
getById(TODOAPP_ID).appendChild(view);
}
function find(selector, scope) {
return (scope || document).querySelector(selector);
}
function refreshAll() {
function refreshView() {
// get the data
var data = {
tasks: todos.model.tasks(),
stats: todos.model.stats()
};
var data = todos.model.viewData(curFilter());
// build the view
var view = todos.views.Tasks(data).toDOM();
var view = todos.views.TodoApp(data).toDOM();
// replace old task list
var old = getById(TASKS_ID);
var old = find('.todoapp');
if (old) {
// replace old task list
old.parentNode.replaceChild(view, old);
} else {
getById(TODOAPP_ID).appendChild(view);
// insert at top
document.body.insertBefore(view, document.body.firstChild);
}
refreshStats(data.stats);
find('.new-todo').focus();
}
function add(input) {
......@@ -64,16 +48,8 @@ var todos = todos || {};
return;
}
var task = todos.model.add(title);
var list = getById(LIST_ID);
if (list) {
// add new at the top
list.appendChild(todos.views.Task(task).toDOM());
refreshStats();
} else {
refreshAll();
}
todos.model.add(title);
refreshView();
}
function edit(input, id) {
......@@ -85,99 +61,91 @@ var todos = todos || {};
} else {
todos.model.remove(id);
}
refreshAll();
refreshView();
}
function reset(input, id) {
var task = todos.model.find(id);
if (task) {
input.value = task.title;
}
}
/*-- export public interface -------------------------------*/
// event handlers
todos.actions = {
addBlur: function () {
add(this);
},
add_keypress: function (e) {
addOnKeydown: function (e) {
if (e.keyCode === ENTER_KEY) {
add(this);
} else if (e.keyCode === ESC_KEY) {
refreshView();
}
},
edit_blur: function (id) {
editOnBlur: function (id) {
// create a closure around the ID
return function () {
edit(this, id);
};
},
edit_keypress: function () {
editOnKeydown: function (id) {
// create a closure around the ID
return function (e) {
if (e.keyCode === ENTER_KEY) {
// just blur so doesn't get triggered twice
this.blur();
} else if (e.keyCode === ESC_KEY) {
reset(this, id);
this.blur();
}
};
},
remove_click: function (id) {
removeOnClick: function (id) {
// create a closure around the ID
return function () {
todos.model.remove(id);
refreshAll();
refreshView();
};
},
clear_click: function () {
clearOnClick: function () {
todos.model.expunge();
refreshAll();
refreshView();
},
content_dblclick: function () {
// create a closure around the ID
var toggleEditingMode = function (li) {
if (li.tagName !== 'LI') {
return toggleEditingMode(li.parentNode);
}
editOnDblclick: function () {
var self = this;
while (self.tagName !== 'LI') {
self = self.parentNode;
}
li.className = EDITING_CSS;
self.className = 'editing';
var input = li.getElementsByTagName('input')[1];
var input = find('input[type=text]', self);
if (input) {
input.focus();
input.value = input.value;
};
return function () {
toggleEditingMode(this);
};
}
},
completed_change: function (id) {
completedOnChange: function (id) {
// create a closure around the ID
return function () {
var checkbox = this;
todos.model.toggle(id, checkbox.checked);
refreshAll();
todos.model.toggle(id, this.checked);
refreshView();
};
},
toggle_change: function () {
var checkbox = this;
todos.model.toggleAll(checkbox.checked);
refreshAll();
toggleOnChange: function () {
todos.model.toggleAll(this.checked);
refreshView();
}
};
/*-- init task list -------------------------------*/
(function (body) {
// build out task list
var view = todos.views.TodoApp({
tasks: todos.model.tasks(),
stats: todos.model.stats()
}).toDOM();
// insert at top
body.insertBefore(view, body.firstChild);
})(document.body);
refreshView();
})(todos, window.document);
......@@ -32,6 +32,12 @@ var todos = todos || {};
};
}
function find(id) {
return tasks.filter(function (task) {
return task.id === id;
})[0];
}
function save() {
// if doesn't support JSON then will be directly stored in polyfill
var value = typeof JSON !== 'undefined' ? JSON.stringify(tasks) : tasks;
......@@ -47,25 +53,40 @@ var todos = todos || {};
tasks = [];
}
/*-- export public interface -------------------------------*/
function filterTasks(filter) {
if (filter === 'completed' || filter === 'active') {
var completed = filter === 'completed';
return tasks.filter(function (task) {
return task.completed === completed;
});
}
return tasks;
}
todos.model = {
tasks: function () {
return tasks;
},
function calcStats(filter) {
var stats = {
total: tasks.length,
completed: tasks.filter(function (task) {
return task.completed;
}).length,
stats: function () {
var stats = {};
filter: filter || ''
};
stats.total = tasks.length;
stats.active = stats.total - stats.completed;
stats.completed = tasks.filter(function (task) {
return task.completed;
}).length;
return stats;
}
stats.active = stats.total - stats.completed;
/*-- export public interface -------------------------------*/
return stats;
todos.model = {
viewData: function (filter) {
return {
tasks: filterTasks(filter),
stats: calcStats(filter)
};
},
add: function (title) {
......@@ -77,19 +98,17 @@ var todos = todos || {};
return task;
},
find: find,
edit: function (id, title) {
tasks.filter(function (task) {
return task.id === id;
})[0].title = title;
(find(id) || {}).title = title;
save();
},
// toggle completion of task
toggle: function (id, completed) {
tasks.filter(function (task) {
return task.id === id;
})[0].completed = completed;
(find(id) || {}).completed = completed;
save();
},
......@@ -104,21 +123,23 @@ var todos = todos || {};
},
remove: function (id) {
tasks.forEach(function (task, index) {
if (task.id === id) {
tasks.splice(index, 1);
// traverse in reverse for removals
for (var i = tasks.length - 1; i >= 0; i--) {
if (tasks[i].id === id) {
tasks.splice(i, 1);
}
});
}
save();
},
expunge: function () {
tasks.forEach(function (task, index) {
if (task.completed) {
tasks.splice(index, 1);
// traverse in reverse for removals
for (var i = tasks.length - 1; i >= 0; i--) {
if (tasks[i].completed) {
tasks.splice(i, 1);
}
});
}
save();
}
......
{
"targetDir": "www/",
"sourceDir": "target/todomvc/",
"serverPrefix" : "com.example.todos.views",
"serverPrefix" : "com.todomvc.duel.views",
"cdnMap": "cdn",
"cdnLinksMap": "cdnLinks",
"cdnHost": "./",
......
/*
DUEL v0.9.7 http://duelengine.org
Copyright (c)2006-2012 Stephen M. McKamey.
Licensed under The MIT License.
*/
var duel=function(m,C,D){function y(a){this.value=a}function l(a){return"function"===typeof a}function k(a){switch(typeof a){case "object":return!a?0:z(a)?2:a instanceof y?5:a instanceof Date?4:3;case "function":return 1;case "undefined":return 0;default:return 4}}function p(a){z(a)||(a=["",a]);this.value=a}function r(a,b){switch(k(b)){case 2:if(""===b[0])for(var d=1,f=b.length;d<f;d++)r(a,b[d]);else a.push(b);break;case 3:if(1===a.length)a.push(b);else if(d=a[1],3===k(d))for(f in b)b.hasOwnProperty(f)&&
(d[f]=b[f]);else a.splice(1,0,b);break;case 4:""!==b&&(b=""+b,d=a.length-1,0<d&&4===k(a[d])?a[d]+=b:a.push(b));break;case 0:break;default:a.push(b)}}function s(a,b,d,f,c,e){var g=3===k(a[1]);if(a.length===(g?3:2))return n(a[a.length-1],b,d,f,c,e);for(var h=[""],g=g?2:1,j=a.length;g<j;g++)r(h,n(a[g],b,d,f,c,e));return h}function E(a,b,d,f,c,e){for(var g=1,h=a.length;g<h;g++){var j=a[g],i=j[1].test;if(3===k(j[1])&&i&&(l(i)&&(i=i(b,d,f,c)),!i))continue;return s(j,b,d,f,c,e)}return null}function q(){this.value=
q.FAST?"":[]}function O(a){if("string"!==typeof a)return null!==a&&a!==D?""+a:"";var b={"&":"&amp;","<":"&lt;",">":"&gt;"};return a.replace(/[&<>]/g,function(a){return b[a]||a})}function P(a){if("string"!==typeof a)return null!==a&&a!==D?""+a:"";var b={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"};return a.replace(/[&<>"]/g,function(a){return b[a]||a})}function F(a,b){var d=b[0]||"",f=b.length,c=1,e,g=Q[d];if("!"===d.charAt(0))"!doctype"===b[0].toLowerCase()?a.append("<!doctype ",b[1],">"):a.append("<\!--",
b[1],"--\>");else{if(d){a.append("<",d);e=b[c];if(3===k(e)){for(var h in e)if(e.hasOwnProperty(h)){var j=e[h];if(G[h.toLowerCase()])if(j)j=h;else continue;0!==k(j)&&(a.append(" ",h),a.append('="',P(j),'"'))}c++}g&&a.append(" /");a.append(">")}for(;c<f;c++)e=b[c],z(e)?F(a,e):a.append(O(e));d&&!g&&a.append("</",d,">");return a}}function H(a){try{return F(new q,a).toString()}catch(b){return a=v(b),a instanceof p?H(a.value):""+a}}function t(a){if(a){if("!"===a.charAt(0))return m.createComment("!"===a?
"":a.substr(1)+" ")}else{if(m.createDocumentFragment)return m.createDocumentFragment();a=""}return"style"===a.toLowerCase()&&m.createStyleSheet?m.createStyleSheet():m.createElement(a)}function w(a,b){if(b){var d=(a.tagName||"").toLowerCase();if(8===a.nodeType)3===b.nodeType&&(a.nodeValue+=b.nodeValue);else if("table"===d&&a.tBodies)if(b.tagName)if((d=b.tagName.toLowerCase())&&"tbody"!==d&&"thead"!==d){var f=0<a.tBodies.length?a.tBodies[a.tBodies.length-1]:null;f||(f=t("th"===d?"thead":"tbody"),a.appendChild(f));
f.appendChild(b)}else!1!==a.canHaveChildren&&a.appendChild(b);else{if(11===b.nodeType)for(;b.firstChild;)w(a,b.removeChild(b.firstChild))}else if("style"===d&&m.createStyleSheet)a.cssText=b;else if(!1!==a.canHaveChildren)a.appendChild(b);else if("object"===d&&b.tagName&&"param"===b.tagName.toLowerCase()){try{a.appendChild(b)}catch(c){}try{a.object&&(a.object[b.name]=b.value)}catch(e){}}}}function I(a,b,d){"on"===b.substr(0,2)&&(b=b.substr(2));switch(typeof d){case "function":if(a.addEventListener)a.addEventListener(b,
d,!1);else if(l(window.jQuery)&&0!==k(a[b]))if(a=window.jQuery(a),l(a.on))a.on(b,d);else a.bind(b,d);else if(a.attachEvent&&0!==k(a[b]))a.attachEvent("on"+b,d);else{var f=a["on"+b]||a[b];a["on"+b]=a[b]=!l(f)?d:function(a){return!1!==f.call(this,a)&&!1!==d.call(this,a)}}break;case "string":a["on"+b]=new Function("event",d)}}function J(a){return!!a&&3===a.nodeType&&(!a.nodeValue||!/\S/.exec(a.nodeValue))}function K(a,b){a&&(3===a.nodeType&&b.exec(a.nodeValue))&&(a.nodeValue=a.nodeValue.replace(b,""))}
function A(a){if(a){for(;J(a.firstChild);)a.removeChild(a.firstChild);for(K(a.firstChild,R);J(a.lastChild);)a.removeChild(a.lastChild);K(a.lastChild,S)}}function L(a,b){var d=a[b];if(d){try{delete a[b]}catch(f){try{a[b]="",a.removeAttribute(b)}catch(c){}}if(!l(d))try{d=new Function(""+d)}catch(e){d=null}}return d}function M(a){if(a){var b=L(a,"$init");b&&b.call(a);(b=L(a,"$load"))?setTimeout(function(){b.call(a);b=a=null},0):b=a=null}}function N(a,b){for(var d=1,f=b.length;d<f;d++){var c=b[d];switch(k(c)){case 2:var e=
c[0],c=N(t(e),c);if("html"===e)return A(c),M(c),c;w(a,c);break;case 4:""!==c&&w(a,m.createTextNode(""+c));break;case 3:if(1===a.nodeType){var e=a,g=c;if(g.name&&m.attachEvent&&!e.parentNode)try{var h=t("<"+e.tagName+' name="'+g.name+'">');e.tagName===h.tagName&&(e=h)}catch(j){}c=void 0;for(c in g)if(g.hasOwnProperty(c)){var i=g[c],l=k(i);if(c&&0!==l)if(c=T[c.toLowerCase()]||c,"style"===c)0!==k(e.style.cssText)?e.style.cssText=i:e.style=i;else if("on"===c.substr(0,2))I(e,c,i),(c=u[c])&&I(e,c,i);else if(!U[c.toLowerCase()]&&
(4!==l||"$"===c.charAt(0)||0!==k(e[c])||0!==k(e[u[c]])))try{e[c]=i,(c=u[c])&&(e[c]=i)}catch(n){if(!("type"===c.toLowerCase()&&"input"===e.tagName.toLowerCase()))throw Error("DOM property "+e.tagName+"."+c+": "+n);}else G[c.toLowerCase()]?i&&(e.setAttribute(c,c),(c=u[c])&&e.setAttribute(c,c)):(e.setAttribute(c,i),(c=u[c])&&e.setAttribute(c,i))}a=e}break;case 5:e=w;g=a;i=c;c=t("div");c.innerHTML=""+i;A(c);if(1===c.childNodes.length)c=c.firstChild;else{for(i=t("");c.firstChild;)i.appendChild(c.firstChild);
c=i}e(g,c)}}A(a);M(a);11===a.nodeType&&1===a.childNodes.length&&(a=a.firstChild);return a}y.prototype.toString=function(){return this.value};var z=Array.isArray||function(a){return a instanceof Array},B,n;n=function(a,b,d,f,c,e){switch(k(a)){case 1:return a(b,d,f,c);case 2:var g=a[0]||"";switch(g){case "$for":a:{var h=a[1]||{},g=[""],j;if(h.hasOwnProperty("count")){j=h.count;l(j)&&(j=j(b,d,f,c));h.hasOwnProperty("data")?(h=h.data,l(h)&&(h=h(b,d,f,c))):h=b;for(b=0;b<j;b++)r(g,s(a,h,b,j,null,e))}else{if(h.hasOwnProperty("in")){var i=
h["in"];l(i)&&(i=i(b,d,f,c));if(3===k(i)){h=[];for(j in i)i.hasOwnProperty(j)&&h.push(j);b=0;for(j=h.length;b<j;b++)r(g,s(a,i[h[b]],b,j,h[b],e));a=g;break a}h=i}else h=h.each,l(h)&&(h=h(b,d,f,c));b=k(h);if(2===b){b=0;for(j=h.length;b<j;b++)r(g,s(a,h[b],b,j,null,e))}else 0!==b&&(g=s(a,h,0,1,null,e))}a=g}return a;case "$xor":return E(a,b,d,f,c,e);case "$if":return E(["$xor",a],b,d,f,c,e);case "$call":e=a[1]||{};if(e.view){g=n(e.view,b,d,f,c);h=e.hasOwnProperty("data")?n(e.data,b,d,f,c):b;j=e.hasOwnProperty("index")?
n(e.index,b,d,f,c):d;i=e.hasOwnProperty("count")?n(e.count,b,d,f,c):f;b=e.hasOwnProperty("key")?n(e.key,b,d,f,c):c;d={};for(f=a.length-1;2<=f;f--)c=a[f],e=c[1]||{},e.hasOwnProperty("name")&&(d[e.name]=c);a=g&&l(g.getView)?n(g.getView(),h,j,i,b,d):null}else a=null;return a;case "$part":return g=(a[1]||{}).name||"",g=e&&e.hasOwnProperty(g)?e[g]:a,s(g,b,d,f,c)}g=[g];h=1;for(j=a.length;h<j;h++)r(g,n(a[h],b,d,f,c,e));return B?B(g):g;case 3:e={};for(g in a)a.hasOwnProperty(g)&&(e[g]=n(a[g],b,d,f,c));return e}return a};
var G={allowfullscreen:1,async:1,autofocus:1,autoplay:1,checked:1,compact:1,controls:1,declare:1,"default":1,defaultchecked:1,defaultmuted:1,defaultselected:1,defer:1,disabled:1,draggable:1,enabled:1,formnovalidate:1,hidden:1,indeterminate:1,inert:1,ismap:1,itemscope:1,loop:1,multiple:1,muted:1,nohref:1,noresize:1,noshade:1,novalidate:1,nowrap:1,open:1,pauseonexit:1,readonly:1,required:1,reversed:1,scoped:1,seamless:1,selected:1,sortable:1,spellcheck:1,translate:1,truespeed:1,typemustmatch:1,visible:1},
v=function(a){return"[ "+a+" ]"},x=function(a){if(!l(a)||!l(a.getView)){var b=a;2!==k(b)&&(b=["",b]);a=function(a,f,c,e){try{var g=n(b,a,isFinite(f)?f:0,isFinite(c)?c:1,"string"===typeof e?e:null);return new p(g)}catch(h){return a=v(h),a instanceof p?a:new p(""+a)}};a.getView=function(){return b}}return a};x.onerror=function(a){l(a)&&(v=a)};x.onbind=function(a){l(a)&&(B=a)};x.raw=function(a){return new y(a)};var Q={area:1,base:1,basefont:1,br:1,col:1,frame:1,embed:1,hr:1,img:1,input:1,isindex:1,keygen:1,
link:1,menuitem:1,meta:1,param:1,source:1,track:1,wbr:1};q.FAST=!(C&&9>C());q.prototype.append=function(a,b,d){var f=arguments;q.FAST?(f=f.length,1<f&&(2<f&&(b+=d),a+=b),this.value+=a):this.value.push.apply(this.value,f)};q.prototype.toString=function(){return q.FAST?this.value:this.value.join("")};p.prototype.toString=function(){return H(this.value)};p.prototype.write=function(a){(a||m).write(""+this)};var T={allowfullscreen:"allowFullscreen",accesskey:"accessKey",bgcolor:"bgColor",cellpadding:"cellPadding",
cellspacing:"cellSpacing",checked:"defaultChecked","class":"className",colspan:"colSpan",contenteditable:"contentEditable",defaultchecked:"defaultChecked",defaultselected:"defaultSelected",defaultmuted:"defaultMuted","for":"htmlFor",formnovalidate:"formNoValidate",hidefocus:"hideFocus",ismap:"isMap",itemscope:"itemScope",maxlength:"maxLength",muted:"defaultMuted",nohref:"noHref",noresize:"noResize",noshade:"noShade",novalidate:"noValidate",nowrap:"noWrap",pauseonexit:"pauseOnExit",readonly:"readOnly",
rowspan:"rowSpan",selected:"defaultSelected",spellcheck:"spellCheck",tabindex:"tabIndex",truespeed:"trueSpeed",typemustmatch:"typeMustMatch",usemap:"useMap",willvalidate:"willValidate"},u={enctype:"encoding",onscroll:"DOMMouseScroll"},U={autocapitalize:1,autocomplete:1,autocorrect:1,type:1},R=/^[\r\n]+/,S=/[\r\n]+$/;p.prototype.toDOM=function(a,b){4===k(a)&&(a=m.getElementById(a)||("querySelector"in m?m.querySelector(a):null));var d;try{b&&(d=a,a=null),d=N(d||t(this.value[0]),this.value)}catch(f){var c=
v(f);if(c instanceof p)return c.toDOM(a||d);d=m.createTextNode(""+c)}a&&a.parentNode&&a.parentNode.replaceChild(d,a);return d};p.prototype.reload=function(){var a=m;try{var b=this.toDOM();a.replaceChild(b,a.documentElement);if(a.createStyleSheet){for(var d=b.firstChild;d&&"HEAD"!==(d.tagName||"");)d=d.nextSibling;for(var f=d&&d.firstChild;f;){if("LINK"===(f.tagName||""))f.href=f.href;f=f.nextSibling}}}catch(c){a=a.open("text/html"),a.write(""+this),a.close()}};return x}(document,window.ScriptEngineMajorVersion);var todos=todos||{};todos.views=todos.views||{};
todos.views.Stats=duel([""," ",["$if",{test:function(a){return a.total}},["footer",{"class":"footer"}," ",["span",{"class":"todo-count"},["strong",function(a){return a.active}]," ",function(a){return 1===a.active?"item":"items"}," left"]," ",["ul",{"class":"filters"}," ",["li"," ",["a",{href:"#/","class":function(a){return!a.filter?"selected":null}},"All"]," "]," ",["li"," ",["a",{href:"#/active","class":function(a){return"active"===a.filter?"selected":null}},"Active"]," "]," ",["li"," ",["a",{href:"#/completed",
"class":function(a){return"completed"===a.filter?"selected":null}},"Completed"]," "]," "]," "," ",["$if",{test:function(a){return a.completed}},["button",{"class":"clear-completed",onclick:function(){return todos.actions.clearOnClick}},"Clear completed"]]]]]);var todos=todos||{};todos.views=todos.views||{};
todos.views.Task=duel([""," ",["li",{"class":function(a){return a.completed?"completed":""}}," ",["div",{"class":"view"}," ",["input",{"class":"toggle",type:"checkbox",checked:function(a){return a.completed},onchange:function(a){return todos.actions.completedOnChange(a.id)}}]," ",["label",{ondblclick:function(){return todos.actions.editOnDblclick}},function(a){return a.title}]," ",["button",{"class":"destroy",onclick:function(a){return todos.actions.removeOnClick(a.id)}}]," "]," ",["input",{"class":"edit",
type:"text",value:function(a){return a.title},onblur:function(a){return todos.actions.editOnBlur(a.id)},onkeydown:function(a){return todos.actions.editOnKeydown(a.id)}}]]]);var todos=todos||{};todos.views=todos.views||{};
todos.views.Tasks=duel([""," ",["$if",{test:function(a){return a.tasks&&a.tasks.length}},["section",{"class":"main"}," ",["input",{"class":"toggle-all",type:"checkbox",checked:function(a){return!a.stats.active},onchange:function(){return todos.actions.toggleOnChange}}]," ",["label",{"for":"toggle-all"},"Mark all as complete"]," ",["ul",{"class":"todo-list"}," ",["$for",{each:function(a){return a.tasks}}," ",["$call",{view:function(){return todos.views.Task}}]]," "]]]]);var todos=todos||{};todos.views=todos.views||{};todos.views.TodoApp=duel(["section",{"class":"todoapp"}," ",["header",{"class":"header"}," ",["h1","todos"]," ",["input",{"class":"new-todo",placeholder:"What needs to be done?",autofocus:!0,onblur:function(){return todos.actions.add_blur},onkeydown:function(){return todos.actions.addOnKeydown}}]," "]," ",["$call",{view:function(){return todos.views.Tasks},data:function(a){return a}}]," ",["$call",{view:function(){return todos.views.Stats},data:function(a){return a.stats}}]]);var todos=todos||{};
(function(j,f,h){function g(a){return c.filter(function(b){return b.id===a})[0]}function d(){var a="undefined"!==typeof JSON?JSON.stringify(c):c;f.setItem(h,a)}var c,e;if(!(e=f)){var i={};e={getItem:function(a){return i[a]},setItem:function(a,b){i[a]=b}}}f=e;c=(e=f.getItem(h))?"undefined"!==typeof JSON?JSON.parse(e):e:[];j.model={viewData:function(a){var b;if("completed"===a||"active"===a){var d="completed"===a;b=c.filter(function(a){return a.completed===d})}else b=c;a={total:c.length,completed:c.filter(function(a){return a.completed}).length,
filter:a||""};a.active=a.total-a.completed;return{tasks:b,stats:a}},add:function(a){a={id:((new Date).getTime()+Math.random()).toString(36),title:a,completed:!1};c.push(a);d();return a},find:g,edit:function(a,b){(g(a)||{}).title=b;d()},toggle:function(a,b){(g(a)||{}).completed=b;d()},toggleAll:function(a){c.forEach(function(b){b.completed=a});d()},remove:function(a){for(var b=c.length-1;0<=b;b--)c[b].id===a&&c.splice(b,1);d()},expunge:function(){for(var a=c.length-1;0<=a;a--)c[a].completed&&c.splice(a,
1);d()}}})(todos,window.localStorage,"todos-duel");var todos=todos||{};
(function(b,e){function d(){var a=b.model.viewData(e.location.hash.substr(2)),a=b.views.TodoApp(a).toDOM(),c=e.querySelector(".todoapp");c?c.parentNode.replaceChild(a,c):e.body.insertBefore(a,e.body.firstChild);e.querySelector(".new-todo").focus()}addEventListener("hashchange",d,!1);b.actions={addOnKeydown:function(a){13===a.keyCode?(a=(this.value||"").trim(),this.value="",a&&(b.model.add(a),d())):27===a.keyCode&&d()},editOnBlur:function(a){return function(){var c=(this.value||"").trim();(this.value=
c)?b.model.edit(a,c):b.model.remove(a);d()}},editOnKeydown:function(a){return function(c){if(13===c.keyCode)this.blur();else if(27===c.keyCode){if(c=b.model.find(a))this.value=c.title;this.blur()}}},removeOnClick:function(a){return function(){b.model.remove(a);d()}},clearOnClick:function(){b.model.expunge();d()},editOnDblclick:function(){for(var a=this;"LI"!==a.tagName;)a=a.parentNode;a.className="editing";(a=(a||e).querySelector("input[type=text]"))&&a.focus()},completedOnChange:function(a){return function(){b.model.toggle(a,
this.checked);d()}},toggleOnChange:function(){b.model.toggleAll(this.checked);d()}};d()})(todos,window.document);
\ No newline at end of file
hr{margin:20px 0;border:0;border-top:1px dashed #c5c5c5;border-bottom:1px dashed #f7f7f7;}.learn a{font-weight:normal;text-decoration:none;color:#b83f45;}.learn a:hover{text-decoration:underline;color:#787e7e;}.learn h3,.learn h4,.learn h5{margin:10px 0;font-weight:500;line-height:1.2;color:#000;}.learn h3{font-size:24px;}.learn h4{font-size:18px;}.learn h5{margin-bottom:0;font-size:14px;}.learn ul{padding:0;margin:0 0 30px 25px;}.learn li{line-height:20px;}.learn p{font-size:15px;font-weight:300;line-height:1.3;margin-top:0;margin-bottom:0;}#issue-count{display:none;}.quote{border:none;margin:20px 0 60px 0;}.quote p{font-style:italic;}.quote p:before{content:'“';font-size:50px;opacity:0.15;position:absolute;top:-20px;left:3px;}.quote p:after{content:'”';font-size:50px;opacity:0.15;position:absolute;bottom:-42px;right:3px;}.quote footer{position:absolute;bottom:-40px;right:0;}.quote footer img{border-radius:3px;}.quote footer a{margin-left:5px;vertical-align:middle;}.speech-bubble{position:relative;padding:10px;background:rgba(0,0,0,0.04);border-radius:5px;}.speech-bubble:after{content:'';position:absolute;top:100%;right:30px;border:13px solid transparent;border-top-color:rgba(0,0,0,0.04);}.learn-bar>.learn{position:absolute;width:272px;top:8px;left:-300px;padding:10px;border-radius:5px;background-color:rgba(255,255,255,0.6);transition-property:left;transition-duration:500ms;}@media (min-width:899px){.learn-bar{width:auto;padding-left:300px;}.learn-bar>.learn{left:8px;}}html,body{margin:0;padding:0;}button{margin:0;padding:0;border:0;background:none;font-size:100%;vertical-align:baseline;font-family:inherit;font-weight:inherit;color:inherit;-webkit-appearance:none;appearance:none;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;font-smoothing:antialiased;}body{font:14px 'Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.4em;background:#f5f5f5;color:#4d4d4d;min-width:230px;max-width:550px;margin:0 auto;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;font-smoothing:antialiased;font-weight:300;}button,input[type="checkbox"]{outline:none;}.hidden{display:none;}.todoapp{background:#fff;margin:130px 0 40px 0;position:relative;box-shadow:0 2px 4px 0 rgba(0,0,0,0.2),0 25px 50px 0 rgba(0,0,0,0.1);}.todoapp input::-webkit-input-placeholder{font-style:italic;font-weight:300;color:#e6e6e6;}.todoapp input::-moz-placeholder{font-style:italic;font-weight:300;color:#e6e6e6;}.todoapp input::input-placeholder{font-style:italic;font-weight:300;color:#e6e6e6;}.todoapp h1{position:absolute;top:-155px;width:100%;font-size:100px;font-weight:100;text-align:center;color:rgba(175,47,47,0.15);-webkit-text-rendering:optimizeLegibility;-moz-text-rendering:optimizeLegibility;text-rendering:optimizeLegibility;}.new-todo,.edit{position:relative;margin:0;width:100%;font-size:24px;font-family:inherit;font-weight:inherit;line-height:1.4em;border:0;outline:none;color:inherit;padding:6px;border:1px solid #999;box-shadow:inset 0 -1px 5px 0 rgba(0,0,0,0.2);box-sizing:border-box;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;font-smoothing:antialiased;}.new-todo{padding:16px 16px 16px 60px;border:none;background:rgba(0,0,0,0.003);box-shadow:inset 0 -2px 1px rgba(0,0,0,0.03);}.main{position:relative;z-index:2;border-top:1px solid #e6e6e6;}label[for='toggle-all']{display:none;}.toggle-all{position:absolute;top:-55px;left:-12px;width:60px;height:34px;text-align:center;border:none;}.toggle-all:before{content:'❯';font-size:22px;color:#e6e6e6;padding:10px 27px 10px 27px;}.toggle-all:checked:before{color:#737373;}.todo-list{margin:0;padding:0;list-style:none;}.todo-list li{position:relative;font-size:24px;border-bottom:1px solid #ededed;}.todo-list li:last-child{border-bottom:none;}.todo-list li.editing{border-bottom:none;padding:0;}.todo-list li.editing .edit{display:block;width:506px;padding:13px 17px 12px 17px;margin:0 0 0 43px;}.todo-list li.editing .view{display:none;}.todo-list li .toggle{text-align:center;width:40px;height:auto;position:absolute;top:0;bottom:0;margin:auto 0;border:none;-webkit-appearance:none;appearance:none;}.todo-list li .toggle:after{content:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');}.todo-list li .toggle:checked:after{content:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');}.todo-list li label{white-space:pre;word-break:break-word;padding:15px 60px 15px 15px;margin-left:45px;display:block;line-height:1.2;transition:color 0.4s;}.todo-list li.completed label{color:#d9d9d9;text-decoration:line-through;}.todo-list li .destroy{display:none;position:absolute;top:0;right:10px;bottom:0;width:40px;height:40px;margin:auto 0;font-size:30px;color:#cc9a9a;margin-bottom:11px;transition:color 0.2s ease-out;}.todo-list li .destroy:hover{color:#af5b5e;}.todo-list li .destroy:after{content:'×';}.todo-list li:hover .destroy{display:block;}.todo-list li .edit{display:none;}.todo-list li.editing:last-child{margin-bottom:-1px;}.footer{color:#777;padding:10px 15px;height:20px;text-align:center;border-top:1px solid #e6e6e6;}.footer:before{content:'';position:absolute;right:0;bottom:0;left:0;height:50px;overflow:hidden;box-shadow:0 1px 1px rgba(0,0,0,0.2),0 8px 0 -3px #f6f6f6,0 9px 1px -3px rgba(0,0,0,0.2),0 16px 0 -6px #f6f6f6,0 17px 2px -6px rgba(0,0,0,0.2);}.todo-count{float:left;text-align:left;}.todo-count strong{font-weight:300;}.filters{margin:0;padding:0;list-style:none;position:absolute;right:0;left:0;}.filters li{display:inline;}.filters li a{color:inherit;margin:3px;padding:3px 7px;text-decoration:none;border:1px solid transparent;border-radius:3px;}.filters li a.selected,.filters li a:hover{border-color:rgba(175,47,47,0.1);}.filters li a.selected{border-color:rgba(175,47,47,0.2);}.clear-completed,html .clear-completed:active{float:right;position:relative;line-height:20px;text-decoration:none;cursor:pointer;position:relative;}.clear-completed:hover{text-decoration:underline;}.info{margin:65px auto 0;color:#bfbfbf;font-size:10px;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-align:center;}.info p{line-height:1;}.info a{color:inherit;text-decoration:none;font-weight:400;}.info a:hover{text-decoration:underline;}@media screen and (-webkit-min-device-pixel-ratio:0){.toggle-all,.todo-list li .toggle{background:none;}.todo-list li .toggle{height:40px;}.toggle-all{-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-appearance:none;appearance:none;}}@media (max-width:430px){.footer{height:50px;}.filters{bottom:10px;}}
\ No newline at end of file
html,body{margin:0;padding:0;}button{margin:0;padding:0;border:0;background:none;font-size:100%;vertical-align:baseline;font-family:inherit;color:inherit;-webkit-appearance:none;-ms-appearance:none;-o-appearance:none;appearance:none;}body{font:14px 'Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.4em;background:#eaeaea url("0c975f0bb536597038b63a8cb0d85cff2222b0c9.png");color:#4d4d4d;width:550px;margin:0 auto;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;-ms-font-smoothing:antialiased;-o-font-smoothing:antialiased;font-smoothing:antialiased;}button,input[type="checkbox"]{outline:none;}#todoapp{background:#fff;background:rgba(255,255,255,0.9);margin:130px 0 40px 0;border:1px solid #ccc;position:relative;border-top-left-radius:2px;border-top-right-radius:2px;box-shadow:0 2px 6px 0 rgba(0,0,0,0.2),0 25px 50px 0 rgba(0,0,0,0.15);}#todoapp:before{content:'';border-left:1px solid #f5d6d6;border-right:1px solid #f5d6d6;width:2px;position:absolute;top:0;left:40px;height:100%;}#todoapp input::-webkit-input-placeholder{font-style:italic;}#todoapp input::-moz-placeholder{font-style:italic;color:#a9a9a9;}#todoapp h1{position:absolute;top:-120px;width:100%;font-size:70px;font-weight:bold;text-align:center;color:#b3b3b3;color:rgba(255,255,255,0.3);text-shadow:-1px -1px rgba(0,0,0,0.2);-webkit-text-rendering:optimizeLegibility;-moz-text-rendering:optimizeLegibility;-ms-text-rendering:optimizeLegibility;-o-text-rendering:optimizeLegibility;text-rendering:optimizeLegibility;}#header{padding-top:15px;border-radius:inherit;}#header:before{content:'';position:absolute;top:0;right:0;left:0;height:15px;z-index:2;border-bottom:1px solid #6c615c;background:#8d7d77;background:-webkit-gradient(linear,left top,left bottom,from(rgba(132,110,100,0.8)),to(rgba(101,84,76,0.8)));background:-webkit-linear-gradient(top,rgba(132,110,100,0.8),rgba(101,84,76,0.8));background:linear-gradient(top,rgba(132,110,100,0.8),rgba(101,84,76,0.8));filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83',EndColorStr='#847670');border-top-left-radius:1px;border-top-right-radius:1px;}#new-todo,.edit{position:relative;margin:0;width:100%;font-size:24px;font-family:inherit;line-height:1.4em;border:0;outline:none;color:inherit;padding:6px;border:1px solid #999;box-shadow:inset 0 -1px 5px 0 rgba(0,0,0,0.2);-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;-ms-font-smoothing:antialiased;-o-font-smoothing:antialiased;font-smoothing:antialiased;}#new-todo{padding:16px 16px 16px 60px;border:none;background:rgba(0,0,0,0.02);z-index:2;box-shadow:none;}#main{position:relative;z-index:2;border-top:1px dotted #adadad;}label[for='toggle-all']{display:none;}#toggle-all{position:absolute;top:-42px;left:-4px;width:40px;text-align:center;border:none;}#toggle-all:before{content:'»';font-size:28px;color:#d9d9d9;padding:0 25px 7px;}#toggle-all:checked:before{color:#737373;}#todo-list{margin:0;padding:0;list-style:none;}#todo-list li{position:relative;font-size:24px;border-bottom:1px dotted #ccc;}#todo-list li:last-child{border-bottom:none;}#todo-list li.editing{border-bottom:none;padding:0;}#todo-list li.editing .edit{display:block;width:506px;padding:13px 17px 12px 17px;margin:0 0 0 43px;}#todo-list li.editing .view{display:none;}#todo-list li .toggle{text-align:center;width:40px;height:auto;position:absolute;top:0;bottom:0;margin:auto 0;border:none;-webkit-appearance:none;-ms-appearance:none;-o-appearance:none;appearance:none;}#todo-list li .toggle:after{content:'✔';line-height:43px;font-size:20px;color:#d9d9d9;text-shadow:0 -1px 0 #bfbfbf;}#todo-list li .toggle:checked:after{color:#85ada7;text-shadow:0 1px 0 #669991;bottom:1px;position:relative;}#todo-list li label{white-space:pre;word-break:break-word;padding:15px 60px 15px 15px;margin-left:45px;display:block;line-height:1.2;-webkit-transition:color 0.4s;transition:color 0.4s;}#todo-list li.completed label{color:#a9a9a9;text-decoration:line-through;}#todo-list li .destroy{display:none;position:absolute;top:0;right:10px;bottom:0;width:40px;height:40px;margin:auto 0;font-size:22px;color:#a88a8a;-webkit-transition:all 0.2s;transition:all 0.2s;}#todo-list li .destroy:hover{text-shadow:0 0 1px #000,0 0 10px rgba(199,107,107,0.8);-webkit-transform:scale(1.3);transform:scale(1.3);}#todo-list li .destroy:after{content:'✖';}#todo-list li:hover .destroy{display:block;}#todo-list li .edit{display:none;}#todo-list li.editing:last-child{margin-bottom:-1px;}#footer{color:#777;padding:0 15px;position:absolute;right:0;bottom:-31px;left:0;height:20px;z-index:1;text-align:center;}#footer:before{content:'';position:absolute;right:0;bottom:31px;left:0;height:50px;z-index:-1;box-shadow:0 1px 1px rgba(0,0,0,0.3),0 6px 0 -3px rgba(255,255,255,0.8),0 7px 1px -3px rgba(0,0,0,0.3),0 43px 0 -6px rgba(255,255,255,0.8),0 44px 2px -6px rgba(0,0,0,0.2);}#todo-count{float:left;text-align:left;}#filters{margin:0;padding:0;list-style:none;position:absolute;right:0;left:0;}#filters li{display:inline;}#filters li a{color:#83756f;margin:2px;text-decoration:none;}#filters li a.selected{font-weight:bold;}#clear-completed{float:right;position:relative;line-height:20px;text-decoration:none;background:rgba(0,0,0,0.1);font-size:11px;padding:0 10px;border-radius:3px;box-shadow:0 -1px 0 0 rgba(0,0,0,0.2);}#clear-completed:hover{background:rgba(0,0,0,0.15);box-shadow:0 -1px 0 0 rgba(0,0,0,0.3);}#info{margin:65px auto 0;color:#a6a6a6;font-size:12px;text-shadow:0 1px 0 rgba(255,255,255,0.7);text-align:center;}#info a{color:inherit;}@media screen and (-webkit-min-device-pixel-ratio:0){#toggle-all,#todo-list li .toggle{background:none;}#todo-list li .toggle{height:40px;}#toggle-all{top:-56px;left:-15px;width:65px;height:41px;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-appearance:none;appearance:none;}}.hidden{display:none;}hr{margin:20px 0;border:0;border-top:1px dashed #C5C5C5;border-bottom:1px dashed #F7F7F7;}.learn a{font-weight:normal;text-decoration:none;color:#b83f45;}.learn a:hover{text-decoration:underline;color:#787e7e;}.learn h3,.learn h4,.learn h5{margin:10px 0;font-weight:500;line-height:1.2;color:#000;}.learn h3{font-size:24px;}.learn h4{font-size:18px;}.learn h5{margin-bottom:0;font-size:14px;}.learn ul{padding:0;margin:0 0 30px 25px;}.learn li{line-height:20px;}.learn p{font-size:15px;font-weight:300;line-height:1.3;margin-top:0;margin-bottom:0;}.quote{border:none;margin:20px 0 60px 0;}.quote p{font-style:italic;}.quote p:before{content:'“';font-size:50px;opacity:0.15;position:absolute;top:-20px;left:3px;}.quote p:after{content:'”';font-size:50px;opacity:0.15;position:absolute;bottom:-42px;right:3px;}.quote footer{position:absolute;bottom:-40px;right:0;}.quote footer img{border-radius:3px;}.quote footer a{margin-left:5px;vertical-align:middle;}.speech-bubble{position:relative;padding:10px;background:rgba(0,0,0,0.04);border-radius:5px;}.speech-bubble:after{content:'';position:absolute;top:100%;right:30px;border:13px solid transparent;border-top-color:rgba(0,0,0,0.04);}.learn-bar>.learn{position:absolute;width:272px;top:8px;left:-300px;padding:10px;border-radius:5px;background-color:rgba(255,255,255,0.6);-webkit-transition-property:left;transition-property:left;-webkit-transition-duration:500ms;transition-duration:500ms;}@media (min-width:899px){.learn-bar{width:auto;margin:0 0 0 300px;}.learn-bar>.learn{left:8px;}.learn-bar #todoapp{width:550px;margin:130px auto 40px auto;}}
\ No newline at end of file
(function(){function i(){var a=location.href.indexOf("examples/");return location.href.substr(0,a)}function f(a,b){if(!(this instanceof f))return new f(a,b);var c,d;if("object"!==typeof a)try{a=JSON.parse(a)}catch(k){return}if(b)c=b.template,d=b.framework;if(!c&&a.templates)c=a.templates.todomvc;if(!d&&document.querySelector("[data-framework]"))d=document.querySelector("[data-framework]").dataset.framework;this.template=c;if(a.backend)this.frameworkJSON=a.backend,this.append({backend:!0});else if(a[d])this.frameworkJSON=
a[d],this.append()}var m=function(a){a.defaults=function(a){if(!a)return a;for(var b=1,c=arguments.length;b<c;b++){var d=arguments[b];if(d)for(var j in d)null==a[j]&&(a[j]=d[j])}return a};a.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var b=/(.)^/,c={"'":"'","\\":"\\","\r":"r","\n":"n","\t":"t","\u2028":"u2028","\u2029":"u2029"},d=/\\|'|\r|\n|\t|\u2028|\u2029/g;a.template=function(k,h,g){var f,g=a.defaults({},g,a.templateSettings),j=RegExp([(g.escape||
b).source,(g.interpolate||b).source,(g.evaluate||b).source].join("|")+"|$","g"),i=0,e="__p+='";k.replace(j,function(a,b,h,f,g){e+=k.slice(i,g).replace(d,function(a){return"\\"+c[a]});b&&(e+="'+\n((__t=("+b+"))==null?'':_.escape(__t))+\n'");h&&(e+="'+\n((__t=("+h+"))==null?'':__t)+\n'");f&&(e+="';\n"+f+"\n__p+='");i=g+a.length;return a});e+="';\n";g.variable||(e="with(obj||{}){\n"+e+"}\n");e="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+e+"return __p;\n";
try{f=new Function(g.variable||"obj","_",e)}catch(l){throw l.source=e,l;}if(h)return f(h,a);h=function(b){return f.call(this,b,a)};h.source="function("+(g.variable||"obj")+"){\n"+e+"}";return h};return a}({});if("todomvc.com"===location.hostname)window._gaq=[["_setAccount","UA-31081062-1"],["_trackPageview"]],function(a,b){var c=a.createElement(b),d=a.getElementsByTagName(b)[0];c.src="//www.google-analytics.com/ga.js";d.parentNode.insertBefore(c,d)}(document,"script");f.prototype.append=function(a){var b=
document.createElement("aside");b.innerHTML=m.template(this.template,this.frameworkJSON);b.className="learn";if(a&&a.backend){var a=b.querySelector(".source-links"),c=a.firstElementChild,d=a.lastElementChild,f=d.getAttribute("href");d.setAttribute("href",f.substr(f.lastIndexOf("http")));a.innerHTML=c.outerHTML+d.outerHTML}else a=b.querySelectorAll(".demo-link"),Array.prototype.forEach.call(a,function(a){"http"!==a.getAttribute("href").substr(0,4)&&a.setAttribute("href",i()+a.getAttribute("href"))});
document.body.className=(document.body.className+" learn-bar").trim();document.body.insertAdjacentHTML("afterBegin",b.outerHTML)};if("tastejs.github.io"===location.hostname)location.href=location.href.replace("tastejs.github.io/todomvc","todomvc.com");(function(a,b){if(!location.host)return console.info("Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.");var c=new XMLHttpRequest;c.open("GET",i()+a,!0);c.send();c.onload=function(){200===c.status&&b&&b(c.responseText)}})("learn.json",
f)})();/*
DUEL v0.8.2 http://duelengine.org
Copyright (c)2006-2012 Stephen M. McKamey.
Licensed under The MIT License.
*/
var duel=function(m,u,w){function x(a){this.value=a}function l(a){switch(typeof a){case "object":return!a?0:y(a)?2:a instanceof x?5:a instanceof Date?4:3;case "function":return 1;case "undefined":return 0;default:return 4}}function n(a){return"function"===typeof a}function o(){this.value=o.FAST?"":[]}function q(a){y(a)||(a=["",a]);this.value=a}function r(a,b){switch(l(b)){case 2:if(""===b[0])for(var c=1,f=b.length;c<f;c++)r(a,b[c]);else a.push(b);break;case 3:if(1===a.length)a.push(b);else if(c=a[1],
3===l(c))for(f in b)b.hasOwnProperty(f)&&(c[f]=b[f]);else a.splice(1,0,b);break;case 4:""!==b&&(b=""+b,c=a.length-1,0<c&&4===l(a[c])?a[c]+=b:a.push(b));break;case 0:break;default:a.push(b)}}function s(a,b,c,f,d,e){var g=3===l(a[1]);if(a.length===(g?3:2))return k(a[a.length-1],b,c,f,d,e);for(var h=[""],g=g?2:1,j=a.length;g<j;g++)r(h,k(a[g],b,c,f,d,e));return h}function A(a,b,c,f,d,e){for(var g=1,h=a.length;g<h;g++){var j=a[g],i=j[1].test;if(3===l(j[1])&&i&&(n(i)&&(i=i(b,c,f,d)),!i))continue;return s(j,
b,c,f,d,e)}return null}function J(a){2!==l(a)&&(a=["",a]);var b=function(b,f,d,e){try{var g=k(a,b,isFinite(f)?f:0,isFinite(d)?d:1,"string"===typeof e?e:null);return new q(g)}catch(h){return new q("["+h+"]")}};b.getView=function(){return a};return b}function K(a){return"string"!==typeof a?a:a.replace(/[&<>]/g,function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";default:return a}})}function L(a){return"string"!==typeof a?a:a.replace(/[&<>"]/g,function(a){switch(a){case "&":return"&amp;";
case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";default:return a}})}function B(a,b){var c=b[0]||"",f=b.length,d=1,e,g=M[c];if("!"===c.charAt(0))"!DOCTYPE"===b[0]?a.append("<!DOCTYPE ",b[1],">"):a.append("<\!--",b[1],"--\>");else{if(c){a.append("<",c);e=b[d];if(3===l(e)){for(var h in e)if(e.hasOwnProperty(h)){var j=e[h];if(C[h])if(j)j=h;else continue;a.append(" ",h);0!==l(j)&&a.append('="',L(j),'"')}d++}g&&a.append(" /");a.append(">")}for(;d<f;d++)e=b[d],y(e)?B(a,e):a.append(K(e));
c&&!g&&a.append("</",c,">")}}function t(a){if(a){if("!"===a.charAt(0))return m.createComment("!"===a?"":a.substr(1)+" ")}else{if(m.createDocumentFragment)return m.createDocumentFragment();a=""}return"style"===a.toLowerCase()&&m.createStyleSheet?m.createStyleSheet():m.createElement(a)}function v(a,b){if(b){var c=(a.tagName||"").toLowerCase();if(8===a.nodeType)3===b.nodeType&&(a.nodeValue+=b.nodeValue);else if("table"===c&&a.tBodies)if(b.tagName)if((c=b.tagName.toLowerCase())&&"tbody"!==c&&"thead"!==
c){var f=0<a.tBodies.length?a.tBodies[a.tBodies.length-1]:null;f||(f=t("th"===c?"thead":"tbody"),a.appendChild(f));f.appendChild(b)}else!1!==a.canHaveChildren&&a.appendChild(b);else{if(11===b.nodeType)for(;b.firstChild;)v(a,b.removeChild(b.firstChild))}else if("style"===c&&m.createStyleSheet)a.cssText=b;else if(!1!==a.canHaveChildren)a.appendChild(b);else if("object"===c&&b.tagName&&"param"===b.tagName.toLowerCase()){try{a.appendChild(b)}catch(d){}try{if(a.object)a.object[b.name]=b.value}catch(e){}}}}
function D(a,b,c){switch(typeof c){case "function":a.addEventListener?a.addEventListener("on"===b.substr(0,2)?b.substr(2):b,c,!1):a[b]=c;break;case "string":a[b]=new Function("event",c)}}function E(a){return!!a&&3===a.nodeType&&(!a.nodeValue||!/\S/.exec(a.nodeValue))}function F(a,b){if(a&&3===a.nodeType&&b.exec(a.nodeValue))a.nodeValue=a.nodeValue.replace(b,"")}function z(a){if(a){for(;E(a.firstChild);)a.removeChild(a.firstChild);for(F(a.firstChild,N);E(a.lastChild);)a.removeChild(a.lastChild);F(a.lastChild,
O)}}function G(a,b){var c=a[b];if(c){try{delete a[b]}catch(f){a[b]=w}if(!n(c))try{c=new Function(""+c)}catch(d){c=null}}return c}function H(a){if(a){var b=G(a,"$init");b&&b.call(a);(b=G(a,"$load"))?setTimeout(function(){b.call(a);b=a=null},0):b=a=null}}function I(a,b){for(var c=1,f=b.length;c<f;c++){var d=b[c];switch(l(d)){case 2:var e=d[0],d=I(t(e),d);if("html"===e)return z(d),H(d),d;v(a,d);break;case 4:""!==d&&v(a,m.createTextNode(""+d));break;case 3:if(1===a.nodeType){var e=a,g=d;if(g.name&&m.attachEvent&&
!e.parentNode)try{var h=t("<"+e.tagName+' name="'+g.name+'">');e.tagName===h.tagName&&(e=h)}catch(j){}d=void 0;for(d in g)if(g.hasOwnProperty(d)){var i=g[d],k=l(i);if(d)0===k&&(i="",k=4),d=P[d.toLowerCase()]||d,C[d]?(e[d]=!!i,p[d]&&(e[p[d]]=!!i)):"style"===d?"undefined"!==typeof e.style.cssText?e.style.cssText=i:e.style=i:"class"===d?e.className=i:"on"===d.substr(0,2)?(D(e,d,i),p[d]&&D(e,p[d],i)):4===k&&"$"!==d.charAt(0)?(e.setAttribute(d,i),p[d]&&e.setAttribute(p[d],i)):(e[d]=i,p[d]&&(e[p[d]]=i))}a=
e}break;case 5:e=v;g=a;i=d;d=t("div");d.innerHTML=""+i;z(d);if(1===d.childNodes.length)d=d.firstChild;else{for(i=t("");d.firstChild;)i.appendChild(d.firstChild);d=i}e(g,d)}}z(a);H(a);if(11===a.nodeType&&1===a.childNodes.length)a=a.firstChild;return a}x.prototype.toString=function(){return this.value};var y=Array.isArray||function(a){return a instanceof Array};o.FAST=!(u&&9>u());o.prototype.append=function(a,b,c){o.FAST?null!==a&&(this.value+=a,null!==b&&b!==w&&(this.value+=b,null!==c&&c!==w&&(this.value+=
c))):this.value.push.apply(this.value,arguments)};o.prototype.clear=function(){this.value=o.FAST?"":[]};o.prototype.toString=function(){return o.FAST?this.value:this.value.join("")};var k;k=function(a,b,c,f,d,e){switch(l(a)){case 1:return a(b,c,f,d);case 2:var g=a[0]||"";switch(g){case "$for":a:{var h=a[1]||{},g=[""],j;if(h.hasOwnProperty("count")){j=h.count;n(j)&&(j=j(b,c,f,d));h.hasOwnProperty("data")?(h=h.data,n(h)&&(h=h(b,c,f,d))):h=b;for(b=0;b<j;b++)r(g,s(a,h,b,j,null,e))}else{if(h.hasOwnProperty("in")){var i=
h["in"];n(i)&&(i=i(b,c,f,d));if(3===l(i)){h=[];for(j in i)i.hasOwnProperty(j)&&h.push(j);for(b=0,j=h.length;b<j;b++)r(g,s(a,i[h[b]],b,j,h[b],e));a=g;break a}h=i}else h=h.each,n(h)&&(h=h(b,c,f,d));b=l(h);if(2===b)for(b=0,j=h.length;b<j;b++)r(g,s(a,h[b],b,j,null,e));else 0!==b&&(g=s(a,h,0,1,null,e))}a=g}return a;case "$xor":return A(a,b,c,f,d,e);case "$if":return A(["$xor",a],b,c,f,d,e);case "$call":e=a[1]||{};if(e.view){g=k(e.view,b,c,f,d);h=e.hasOwnProperty("data")?k(e.data,b,c,f,d):b;j=e.hasOwnProperty("index")?
k(e.index,b,c,f,d):c;i=e.hasOwnProperty("count")?k(e.count,b,c,f,d):f;b=e.hasOwnProperty("key")?k(e.key,b,c,f,d):d;c={};for(f=a.length-1;2<=f;f--)d=a[f],e=d[1]||{},e.hasOwnProperty("name")&&(c[e.name]=d);a=g&&n(g.getView)?k(g.getView(),h,j,i,b,c):null}else a=null;return a;case "$part":return g=(a[1]||{}).name||"",g=e&&e.hasOwnProperty(g)?e[g]:a,s(g,b,c,f,d)}g=[g];h=1;for(j=a.length;h<j;h++)r(g,k(a[h],b,c,f,d,e));return g;case 3:e={};for(g in a)a.hasOwnProperty(g)&&(e[g]=k(a[g],b,c,f,d));return e}return a};
u=function(a){return n(a)&&n(a.getView)?a:J(a)};u.raw=function(a){return new x(a)};var M={area:!0,base:!0,basefont:!0,br:!0,col:!0,frame:!0,hr:!0,img:!0,input:!0,isindex:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,wbr:!0},C={async:1,checked:1,defer:1,disabled:1,hidden:1,novalidate:1,formnovalidate:1};q.prototype.toString=function(){var a;var b=this.value;try{var c=new o;B(c,b);a=c.toString()}catch(f){a="["+f+"]"}return a};q.prototype.write=function(a){(a||m).write(""+this)};var P={rowspan:"rowSpan",
colspan:"colSpan",cellpadding:"cellPadding",cellspacing:"cellSpacing",tabindex:"tabIndex",accesskey:"accessKey",hidefocus:"hideFocus",usemap:"useMap",maxlength:"maxLength",readonly:"readOnly",contenteditable:"contentEditable"},p={enctype:"encoding",onscroll:"DOMMouseScroll",checked:"defaultChecked"},N=/^[\r\n]+/,O=/[\r\n]+$/;q.prototype.toDOM=function(a,b){4===l(a)&&(a=m.getElementById(a));var c;try{b&&(c=a,a=null),c=I(c||t(this.value[0]),this.value)}catch(f){c=m.createTextNode("["+f+"]")}a&&a.parentNode&&
a.parentNode.replaceChild(c,a);return c};q.prototype.reload=function(){var a=m;try{var b=this.toDOM();a.replaceChild(b,a.documentElement);if(a.createStyleSheet){for(var c=b.firstChild;c&&"HEAD"!==(c.tagName||"");)c=c.nextSibling;for(var f=c&&c.firstChild;f;){if("LINK"===(f.tagName||""))f.href=f.href;f=f.nextSibling}}}catch(d){a=a.open("text/html"),a.write(this.toString()),a.close()}};return u}(document,window.ScriptEngineMajorVersion);var todos=todos||{};
(function(h,e,g){function d(){var a="undefined"!==typeof JSON?JSON.stringify(b):b;e.setItem(g,a)}var b,e=e||function(){var a={};return{getItem:function(c){return a[c]},setItem:function(c,b){a[c]=b}}}(),f=e.getItem(g);b=f?"undefined"!==typeof JSON?JSON.parse(f):f:[];h.model={tasks:function(){return b},stats:function(){var a={};a.total=b.length;a.completed=b.filter(function(a){return a.completed}).length;a.active=a.total-a.completed;return a},add:function(a){a={id:((new Date).getTime()+Math.random()).toString(36),
title:a,completed:!1};b.push(a);d();return a},edit:function(a,c){b.filter(function(b){return b.id===a})[0].title=c;d()},toggle:function(a,c){b.filter(function(b){return b.id===a})[0].completed=c;d()},toggleAll:function(a){b.forEach(function(b){b.completed=a});d()},remove:function(a){b.forEach(function(c,d){c.id===a&&b.splice(d,1)});d()},expunge:function(){b.forEach(function(a,c){a.completed&&b.splice(c,1)});d()}}})(todos,window.localStorage,"todos-duel");var todos=todos||{};todos.views=todos.views||{};todos.views.Stats=duel([""," ",["$if",{test:function(a){return a.total}},["footer",{id:"footer"}," ",["span",{id:"todo-count"},["strong",function(a){return a.active}]," ",function(a){return 1===a.active?"item":"items"}," left"]," "," ",["$if",{test:function(a){return a.completed}},["button",{id:"clear-completed",onclick:function(){return todos.actions.clear_click}},"Clear completed"]]]]]);var todos=todos||{};todos.views=todos.views||{};
todos.views.Task=duel([""," ",["li",{"class":function(a){return a.completed?"completed":""}}," ",["div",{"class":"view"}," ",["input",{"class":"toggle",type:"checkbox",checked:function(a){return a.completed},onchange:function(a){return todos.actions.completed_change(a.id)}}]," ",["label",{ondblclick:function(a){return todos.actions.content_dblclick(a.id)}},function(a){return a.title}]," ",["button",{"class":"destroy",onclick:function(a){return todos.actions.remove_click(a.id)}}]," "]," ",["input",
{"class":"edit",type:"text",value:function(a){return a.title},onblur:function(a){return todos.actions.edit_blur(a.id)},onkeypress:function(a){return todos.actions.edit_keypress(a.id)}}]]]);var todos=todos||{};todos.views=todos.views||{};todos.views.Tasks=duel([""," ",["$if",{test:function(a){return a.tasks.length}},["section",{id:"main"}," ",["input",{id:"toggle-all",type:"checkbox",checked:function(a){return!a.stats.active},onchange:function(){return todos.actions.toggle_change}}]," ",["label",{"for":"toggle-all"},"Mark all as complete"]," ",["ul",{id:"todo-list"}," ",["$for",{each:function(a){return a.tasks}}," ",["$call",{view:function(){return todos.views.Task}}]]," "]]]]);var todos=todos||{};todos.views=todos.views||{};todos.views.TodoApp=duel(["section",{id:"todoapp"}," ",["header",{id:"header"}," ",["h1","todos"]," ",["input",{id:"new-todo",placeholder:"What needs to be done?",autofocus:null,onblur:function(){return todos.actions.add_blur},onkeypress:function(){return todos.actions.add_keypress}}]," "]," ",["$call",{view:function(){return todos.views.Tasks},data:function(a){return a}}]," ",["$call",{view:function(){return todos.views.Stats},data:function(a){return a.stats}}]]);var todos=todos||{};
(function(c,e){function f(a){var a=a||c.model.stats(),a=c.views.Stats(a).toDOM(),b=e.getElementById(i);b?b.parentNode.replaceChild(a,b):e.getElementById(g).appendChild(a)}function d(){var a={tasks:c.model.tasks(),stats:c.model.stats()},b=c.views.Tasks(a).toDOM(),d=e.getElementById(j);d?d.parentNode.replaceChild(b,d):e.getElementById(g).appendChild(b);f(a.stats)}function h(a){var b=(a.value||"").trim();a.value="";b&&(a=c.model.add(b),(b=e.getElementById(k))?(b.appendChild(c.views.Task(a).toDOM()),f()):
d())}var i="footer",g="todoapp",j="main",k="todo-list";c.actions={addBlur:function(){h(this)},add_keypress:function(a){13===a.keyCode&&h(this)},edit_blur:function(a){return function(){var b=(this.value||"").trim();(this.value=b)?c.model.edit(a,b):c.model.remove(a);d()}},edit_keypress:function(){return function(a){13===a.keyCode&&this.blur()}},remove_click:function(a){return function(){c.model.remove(a);d()}},clear_click:function(){c.model.expunge();d()},content_dblclick:function(){var a=function(b){if("LI"!==
b.tagName)return a(b.parentNode);b.className="editing";b=b.getElementsByTagName("input")[1];b.focus();b.value=b.value};return function(){a(this)}},completed_change:function(a){return function(){c.model.toggle(a,this.checked);d()}},toggle_change:function(){c.model.toggleAll(this.checked);d()}};(function(a){var b=c.views.TodoApp({tasks:c.model.tasks(),stats:c.model.stats()}).toDOM();a.insertBefore(b,a.firstChild)})(e.body)})(todos,window.document);
\ No newline at end of file
<!DOCTYPE html>
<html lang="en" data-framework="duel">
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DUEL &#x2022; TodoMVC</title>
<link rel="stylesheet" href="./cdn/bada2673af7edc043a573c3e20ac2d8ce9d10037.css" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DUEL &bull; TodoMVC</title>
<link rel="stylesheet" href="./cdn/b12c1274056c76efb21a375280fdd622eb22b845.css">
</head>
<body>
<footer id="info">
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Ported to <a href="http://duelengine.org">DUEL</a> by <a href="http://mck.me">Stephen McKamey</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="./cdn/d10574a720ccdf2d2c810f0c45afb5f159264a1f.js"></script>
<script src="./cdn/3aba0c24fc2dfb2ef530691bf611672891b75c6d.js"></script>
</body>
</html>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment