├── .gitignore ├── README.md ├── dist ├── base.css ├── base.js ├── bundle.js └── index.css ├── index.html ├── js ├── app.js ├── controllers │ └── todo.js ├── models │ ├── storage.js │ └── todo.js └── views │ ├── footer.js │ ├── header.js │ └── main.js ├── learn.json ├── node_modules ├── hyperhtml │ └── min.js ├── todomvc-app-css │ └── index.css └── todomvc-common │ ├── base.css │ └── base.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | 3 | node_modules/todomvc-app-css/* 4 | !node_modules/todomvc-app-css/index.css 5 | 6 | node_modules/todomvc-common/* 7 | !node_modules/todomvc-common/base.css 8 | !node_modules/todomvc-common/base.js 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hypermvc 2 | hyperHTML for TodoMVC 3 | 4 | Live: https://webreflection.github.io/hypermvc/index.html 5 | -------------------------------------------------------------------------------- /dist/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /dist/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /dist/bundle.js: -------------------------------------------------------------------------------- 1 | // jscs:disable 2 | !function t(e,n,o){function l(i,a){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!a&&c)return c(i,!0);if(r)return r(i,!0);var s=new Error("Cannot find module '"+i+"'");throw s.code="MODULE_NOT_FOUND",s}var u=n[i]={exports:{}};e[i][0].call(u.exports,function(t){var n=e[i][1][t];return l(n?n:t)},u,u.exports,t,e,n,o)}return n[i].exports}for(var r="function"==typeof require&&require,i=0;i"+e+"":e,o&&(n={childNodes:n.querySelectorAll(RegExp.$1)}),c(t,T.call((n.content||n).childNodes))}function d(t){for(var e=[],n=t.length;n--;e[n]={name:t[n].name,value:t[n].value});return e}function f(t,e){switch(e.nodeType){case 1:var n=t.childNodes;if(0",E=function(t){return t.innerHTML='',/class/i.test(t.firstChild.attributes[0].name)}(document.createElement("p")),M=E&&new RegExp("([^\\S][a-z]+[a-z0-9_-]*=)(['\"])"+C+"\\2","g"),j=E&&function(t,e,n){return _.push(e.slice(1,-1)),e+n+N+n},S=x.trim||function(){return this.replace(/^\s+|\s+$/g,"")},T=[].slice,A=("undefined"==typeof WeakMap?"undefined":o(WeakMap))===(void 0===A?"undefined":o(A))?{get:function(t){return t[x]},set:function(t,e){Object.defineProperty(t,x,{configurable:!0,value:e})}}:new WeakMap;return t}();n.default=l},{}],4:[function(t,e,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var o="todos-hyperHTML";n.default={get:function(){return JSON.parse(localStorage.getItem(o)||"[]")},set:function(t){return localStorage.setItem(o,JSON.stringify(t))}}},{}],5:[function(t,e,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var o=0;n.default=function(t){return{title:t,id:o++,completed:!1}}},{}],6:[function(t,e,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var o=function(t,e){return Object.freeze(Object.defineProperties(t,{raw:{value:Object.freeze(e)}}))}(['\n\t
\n\t\t\n\t\t\t '," item",' left\n\t\t\n\t\t\n\t\tClear completed\n\t
'],['\n\t
\n\t\t\n\t\t\t '," item",' left\n\t\t\n\t\t\n\t\tClear completed\n\t
']),l=t("../controllers/todo"),r=function(t){return t&&t.__esModule?t:{default:t}}(l),i=function(t,e){return t===e?"selected":""};n.default=function(t,e){var n=r.default.todosSize(),l=r.default.todosLeft(),a=r.default.hash();return t(o,n?"":"display:none",l,~-l?"s":"",i(a,"all"),i(a,"active"),i(a,"completed"),r.default.clear,l\n\t\t

todos

\n\t\t\n\t'],['\n\t
\n\t\t

todos

\n\t\t\n\t
']),l=t("../controllers/todo"),r=function(t){return t&&t.__esModule?t:{default:t}}(l);n.default=function(t,e){return t(o,r.default.create)}},{"../controllers/todo":2}],8:[function(t,e,n){"use strict";function o(t){return t&&t.__esModule?t:{default:t}}function l(t,e){return Object.freeze(Object.defineProperties(t,{raw:{value:Object.freeze(e)}}))}function r(){var t=this.closest("li");t.classList.add("editing"),t.querySelector(".edit").focus()}function i(t){this.closest("li").classList.remove("edit"),f.default.edit(t)}Object.defineProperty(n,"__esModule",{value:!0});var a=l(['\n\t
\n\t\t\n\t\t\n\t\t
    ',"
\n\t
"],['\n\t
\n\t\t\n\t\t\n\t\t
    ',"
\n\t
"]),c=l(['\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\n\t'],['\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\n\t']),s=t("../lib/hyperhtml"),u=o(s),d=t("../controllers/todo"),f=o(d);n.default=function(t,e){return t(a,e.length?"":"display:none",f.default.toggleAll,e.every(function(t){return t.completed}),e.map(function(t){return u.default.wire(t)(c,f.default.items.indexOf(t),t.completed?"completed":"",t.completed,f.default.complete,r,t.title,f.default.destroy,t.title,i,f.default.edit,function(e){e.keyCode===f.default.ESC_KEY&&(this.value=t.title,this.blur())})}))}},{"../controllers/todo":2,"../lib/hyperhtml":3}]},{},[1]); 3 | -------------------------------------------------------------------------------- /dist/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | min-width: 230px; 29 | max-width: 550px; 30 | margin: 0 auto; 31 | -webkit-font-smoothing: antialiased; 32 | -moz-osx-font-smoothing: grayscale; 33 | font-weight: 300; 34 | } 35 | 36 | :focus { 37 | outline: 0; 38 | } 39 | 40 | .hidden { 41 | display: none; 42 | } 43 | 44 | .todoapp { 45 | background: #fff; 46 | margin: 130px 0 40px 0; 47 | position: relative; 48 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 49 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 50 | } 51 | 52 | .todoapp input::-webkit-input-placeholder { 53 | font-style: italic; 54 | font-weight: 300; 55 | color: #e6e6e6; 56 | } 57 | 58 | .todoapp input::-moz-placeholder { 59 | font-style: italic; 60 | font-weight: 300; 61 | color: #e6e6e6; 62 | } 63 | 64 | .todoapp input::input-placeholder { 65 | font-style: italic; 66 | font-weight: 300; 67 | color: #e6e6e6; 68 | } 69 | 70 | .todoapp h1 { 71 | position: absolute; 72 | top: -155px; 73 | width: 100%; 74 | font-size: 100px; 75 | font-weight: 100; 76 | text-align: center; 77 | color: rgba(175, 47, 47, 0.15); 78 | -webkit-text-rendering: optimizeLegibility; 79 | -moz-text-rendering: optimizeLegibility; 80 | text-rendering: optimizeLegibility; 81 | } 82 | 83 | .new-todo, 84 | .edit { 85 | position: relative; 86 | margin: 0; 87 | width: 100%; 88 | font-size: 24px; 89 | font-family: inherit; 90 | font-weight: inherit; 91 | line-height: 1.4em; 92 | border: 0; 93 | color: inherit; 94 | padding: 6px; 95 | border: 1px solid #999; 96 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 97 | box-sizing: border-box; 98 | -webkit-font-smoothing: antialiased; 99 | -moz-osx-font-smoothing: grayscale; 100 | } 101 | 102 | .new-todo { 103 | padding: 16px 16px 16px 60px; 104 | border: none; 105 | background: rgba(0, 0, 0, 0.003); 106 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 107 | } 108 | 109 | .main { 110 | position: relative; 111 | z-index: 2; 112 | border-top: 1px solid #e6e6e6; 113 | } 114 | 115 | label[for='toggle-all'] { 116 | display: none; 117 | } 118 | 119 | .toggle-all { 120 | position: absolute; 121 | top: -55px; 122 | left: -12px; 123 | width: 60px; 124 | height: 34px; 125 | text-align: center; 126 | border: none; /* Mobile Safari */ 127 | } 128 | 129 | .toggle-all:before { 130 | content: '❯'; 131 | font-size: 22px; 132 | color: #e6e6e6; 133 | padding: 10px 27px 10px 27px; 134 | } 135 | 136 | .toggle-all:checked:before { 137 | color: #737373; 138 | } 139 | 140 | .todo-list { 141 | margin: 0; 142 | padding: 0; 143 | list-style: none; 144 | } 145 | 146 | .todo-list li { 147 | position: relative; 148 | font-size: 24px; 149 | border-bottom: 1px solid #ededed; 150 | } 151 | 152 | .todo-list li:last-child { 153 | border-bottom: none; 154 | } 155 | 156 | .todo-list li.editing { 157 | border-bottom: none; 158 | padding: 0; 159 | } 160 | 161 | .todo-list li.editing .edit { 162 | display: block; 163 | width: 506px; 164 | padding: 12px 16px; 165 | margin: 0 0 0 43px; 166 | } 167 | 168 | .todo-list li.editing .view { 169 | display: none; 170 | } 171 | 172 | .todo-list li .toggle { 173 | text-align: center; 174 | width: 40px; 175 | /* auto, since non-WebKit browsers doesn't support input styling */ 176 | height: auto; 177 | position: absolute; 178 | top: 0; 179 | bottom: 0; 180 | margin: auto 0; 181 | border: none; /* Mobile Safari */ 182 | -webkit-appearance: none; 183 | appearance: none; 184 | } 185 | 186 | .todo-list li .toggle:after { 187 | content: url('data:image/svg+xml;utf8,'); 188 | } 189 | 190 | .todo-list li .toggle:checked:after { 191 | content: url('data:image/svg+xml;utf8,'); 192 | } 193 | 194 | .todo-list li label { 195 | word-break: break-all; 196 | padding: 15px 60px 15px 15px; 197 | margin-left: 45px; 198 | display: block; 199 | line-height: 1.2; 200 | transition: color 0.4s; 201 | } 202 | 203 | .todo-list li.completed label { 204 | color: #d9d9d9; 205 | text-decoration: line-through; 206 | } 207 | 208 | .todo-list li .destroy { 209 | display: none; 210 | position: absolute; 211 | top: 0; 212 | right: 10px; 213 | bottom: 0; 214 | width: 40px; 215 | height: 40px; 216 | margin: auto 0; 217 | font-size: 30px; 218 | color: #cc9a9a; 219 | margin-bottom: 11px; 220 | transition: color 0.2s ease-out; 221 | } 222 | 223 | .todo-list li .destroy:hover { 224 | color: #af5b5e; 225 | } 226 | 227 | .todo-list li .destroy:after { 228 | content: '×'; 229 | } 230 | 231 | .todo-list li:hover .destroy { 232 | display: block; 233 | } 234 | 235 | .todo-list li .edit { 236 | display: none; 237 | } 238 | 239 | .todo-list li.editing:last-child { 240 | margin-bottom: -1px; 241 | } 242 | 243 | .footer { 244 | color: #777; 245 | padding: 10px 15px; 246 | height: 20px; 247 | text-align: center; 248 | border-top: 1px solid #e6e6e6; 249 | } 250 | 251 | .footer:before { 252 | content: ''; 253 | position: absolute; 254 | right: 0; 255 | bottom: 0; 256 | left: 0; 257 | height: 50px; 258 | overflow: hidden; 259 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 260 | 0 8px 0 -3px #f6f6f6, 261 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 262 | 0 16px 0 -6px #f6f6f6, 263 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 264 | } 265 | 266 | .todo-count { 267 | float: left; 268 | text-align: left; 269 | } 270 | 271 | .todo-count strong { 272 | font-weight: 300; 273 | } 274 | 275 | .filters { 276 | margin: 0; 277 | padding: 0; 278 | list-style: none; 279 | position: absolute; 280 | right: 0; 281 | left: 0; 282 | } 283 | 284 | .filters li { 285 | display: inline; 286 | } 287 | 288 | .filters li a { 289 | color: inherit; 290 | margin: 3px; 291 | padding: 3px 7px; 292 | text-decoration: none; 293 | border: 1px solid transparent; 294 | border-radius: 3px; 295 | } 296 | 297 | .filters li a:hover { 298 | border-color: rgba(175, 47, 47, 0.1); 299 | } 300 | 301 | .filters li a.selected { 302 | border-color: rgba(175, 47, 47, 0.2); 303 | } 304 | 305 | .clear-completed, 306 | html .clear-completed:active { 307 | float: right; 308 | position: relative; 309 | line-height: 20px; 310 | text-decoration: none; 311 | cursor: pointer; 312 | } 313 | 314 | .clear-completed:hover { 315 | text-decoration: underline; 316 | } 317 | 318 | .info { 319 | margin: 65px auto 0; 320 | color: #bfbfbf; 321 | font-size: 10px; 322 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 323 | text-align: center; 324 | } 325 | 326 | .info p { 327 | line-height: 1; 328 | } 329 | 330 | .info a { 331 | color: inherit; 332 | text-decoration: none; 333 | font-weight: 400; 334 | } 335 | 336 | .info a:hover { 337 | text-decoration: underline; 338 | } 339 | 340 | /* 341 | Hack to remove background from Mobile Safari. 342 | Can't use it globally since it destroys checkboxes in Firefox 343 | */ 344 | @media screen and (-webkit-min-device-pixel-ratio:0) { 345 | .toggle-all, 346 | .todo-list li .toggle { 347 | background: none; 348 | } 349 | 350 | .todo-list li .toggle { 351 | height: 40px; 352 | } 353 | 354 | .toggle-all { 355 | -webkit-transform: rotate(90deg); 356 | transform: rotate(90deg); 357 | -webkit-appearance: none; 358 | appearance: none; 359 | } 360 | } 361 | 362 | @media (max-width: 430px) { 363 | .footer { 364 | height: 50px; 365 | } 366 | 367 | .filters { 368 | bottom: 10px; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Template • TodoMVC 7 | 11 | 12 | 13 | 14 | 15 |
16 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | import hyperHTML from './lib/hyperhtml'; 2 | import controller from './controllers/todo'; 3 | import storage from './models/storage'; 4 | import updateHeader from './views/header'; 5 | import updateMain from './views/main'; 6 | import updateFooter from './views/footer'; 7 | 8 | const appRender = hyperHTML.bind(document.querySelector('.todoapp')); 9 | 10 | const header = hyperHTML.wire(); 11 | const main = hyperHTML.wire(); 12 | const footer = hyperHTML.wire(); 13 | 14 | controller.init(storage, todos => { 15 | appRender`${[ 16 | updateHeader(header, todos), 17 | updateMain(main, todos), 18 | updateFooter(footer, todos) 19 | ]}`; 20 | }); 21 | 22 | controller.update(); 23 | -------------------------------------------------------------------------------- /js/controllers/todo.js: -------------------------------------------------------------------------------- 1 | 2 | import Todo from '../models/todo'; 3 | 4 | const controller = { 5 | 6 | ENTER_KEY: 13, 7 | ESC_KEY: 27, 8 | 9 | init(storage, update) { 10 | 11 | this.items = storage.get(); 12 | 13 | this.update = () => { 14 | const hash = controller.hash(); 15 | let todos = this.items; 16 | if (hash !== 'all') { 17 | todos = todos.filter( 18 | hash === 'active' ? 19 | todo => !todo.completed : 20 | todo => todo.completed 21 | ); 22 | } 23 | update(todos); 24 | storage.set(this.items); 25 | }; 26 | 27 | window.onhashchange = this.update; 28 | }, 29 | 30 | // controller actions invoked through the DOM 31 | 32 | clear: () => { 33 | controller.items = controller.items.filter(todo => !todo.completed); 34 | controller.update(); 35 | }, 36 | 37 | complete: event => { 38 | const index = event.target.closest('li').dataset.index; 39 | const todo = controller.items[index]; 40 | todo.completed = !todo.completed; 41 | controller.update(); 42 | }, 43 | 44 | create: event => { 45 | const target = event.target; 46 | const value = target.value.trim(); 47 | if (event.keyCode === controller.ENTER_KEY && value.length) { 48 | controller.items.push(Todo(value)); 49 | target.value = ''; 50 | controller.update(); 51 | } 52 | }, 53 | 54 | destroy: event => { 55 | const index = event.target.closest('li').dataset.index; 56 | controller.items.splice(index, 1); 57 | controller.update(); 58 | }, 59 | 60 | edit: event => { 61 | if (event.type === 'blur' || event.keyCode === controller.ENTER_KEY) { 62 | const value = event.target.value.trim(); 63 | if (value.length) { 64 | const index = event.target.closest('li').dataset.index; 65 | controller.items[index].title = value; 66 | controller.update(); 67 | } else { 68 | if (event.type === 'blur') { 69 | controller.destroy(event); 70 | } else { 71 | event.target.blur(); 72 | } 73 | } 74 | } 75 | }, 76 | 77 | hash: () => { 78 | const str = location.hash.slice(2); 79 | return str !== 'completed' && str !== 'active' ? 'all' : str; 80 | }, 81 | 82 | todosLeft: () => controller.items.filter(todo => !todo.completed).length, 83 | 84 | todosSize: () => controller.items.length, 85 | 86 | toggleAll: (event) => { 87 | controller.items.forEach(todo => { 88 | todo.completed = event.target.checked; 89 | }); 90 | controller.update(); 91 | } 92 | }; 93 | 94 | export default controller; 95 | -------------------------------------------------------------------------------- /js/models/storage.js: -------------------------------------------------------------------------------- 1 | 2 | const name = 'todos-hyperHTML'; 3 | 4 | export default { 5 | get: () => JSON.parse(localStorage.getItem(name) || '[]'), 6 | set: value => localStorage.setItem(name, JSON.stringify(value)) 7 | }; 8 | -------------------------------------------------------------------------------- /js/models/todo.js: -------------------------------------------------------------------------------- 1 | 2 | let counter = 0; 3 | 4 | export default title => ({ 5 | title, 6 | id: counter++, 7 | completed: false 8 | }); 9 | -------------------------------------------------------------------------------- /js/views/footer.js: -------------------------------------------------------------------------------- 1 | 2 | import controller from '../controllers/todo'; 3 | 4 | const selected = (hash, curr) => hash === curr ? 'selected' : ''; 5 | 6 | export default (render, todos) => { 7 | 8 | const all = controller.todosSize(); 9 | const left = controller.todosLeft(); 10 | const hash = controller.hash(); 11 | 12 | return render` 13 |
14 | 15 | ${left} item${~-left ? 's' : ''} left 16 | 17 | 22 | 27 |
`; 28 | }; 29 | -------------------------------------------------------------------------------- /js/views/header.js: -------------------------------------------------------------------------------- 1 | 2 | import controller from '../controllers/todo'; 3 | 4 | export default (render, todos) => render` 5 |
6 |

todos

7 | 12 |
`; 13 | -------------------------------------------------------------------------------- /js/views/main.js: -------------------------------------------------------------------------------- 1 | import hyperHTML from '../lib/hyperhtml'; 2 | import controller from '../controllers/todo'; 3 | 4 | export default (render, todos) => render` 5 |
6 | 11 | 12 |
    ${todos.map(todo => hyperHTML.wire(todo)` 13 |
  • 17 |
    18 | 23 | 26 | 27 |
    28 | 39 |
  • 40 | `)}
41 |
`; 42 | 43 | function dblclick2Edit() { 44 | const li = this.closest('li'); 45 | li.classList.add('editing'); 46 | li.querySelector('.edit').focus(); 47 | } 48 | 49 | function blur2Save(event) { 50 | this.closest('li').classList.remove('edit'); 51 | controller.edit(event); 52 | } 53 | -------------------------------------------------------------------------------- /learn.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /node_modules/hyperhtml/min.js: -------------------------------------------------------------------------------- 1 | var hyperHTML=function(){"use strict";/*! (C) 2017 Andrea Giammarchi @WebReflection (MIT) */ 2 | function e(e){return T in this&&this[T].s===e?b.apply(this,arguments):N.apply(this,arguments)}function t(e,t){for(var n,r=M?x:k,o=M?s(e.attributes):L.call(e.attributes),a=0,c=o.length;a"+t+"":t,r&&(n={childNodes:n.querySelectorAll(RegExp.$1)}),c(e,L.call((n.content||n).childNodes))}function s(e){for(var t=[],n=e.length;n--;t[n]={name:e[n].name,value:e[n].value});return t}function h(e,t){switch(t.nodeType){case 1:var n=e.childNodes;if(0",M=function(e){return e.innerHTML='',/class/i.test(e.firstChild.attributes[0].name)}(document.createElement("p")),E=M&&new RegExp("([^\\S][a-z]+[a-z0-9_-]*=)(['\"])"+k+"\\2","g"),A=M&&function(e,t,n){return w.push(t.slice(1,-1)),t+n+x+n},D=T.trim||function(){return this.replace(/^\s+|\s+$/g,"")},L=[].slice,H=typeof WeakMap==typeof H?{get:function(e){return e[T]},set:function(e,t){Object.defineProperty(e,T,{configurable:!0,value:t})}}:new WeakMap;return e}();try{module.exports=hyperHTML}catch(e){} -------------------------------------------------------------------------------- /node_modules/todomvc-app-css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #f5f5f5; 27 | color: #4d4d4d; 28 | min-width: 230px; 29 | max-width: 550px; 30 | margin: 0 auto; 31 | -webkit-font-smoothing: antialiased; 32 | -moz-osx-font-smoothing: grayscale; 33 | font-weight: 300; 34 | } 35 | 36 | :focus { 37 | outline: 0; 38 | } 39 | 40 | .hidden { 41 | display: none; 42 | } 43 | 44 | .todoapp { 45 | background: #fff; 46 | margin: 130px 0 40px 0; 47 | position: relative; 48 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 49 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 50 | } 51 | 52 | .todoapp input::-webkit-input-placeholder { 53 | font-style: italic; 54 | font-weight: 300; 55 | color: #e6e6e6; 56 | } 57 | 58 | .todoapp input::-moz-placeholder { 59 | font-style: italic; 60 | font-weight: 300; 61 | color: #e6e6e6; 62 | } 63 | 64 | .todoapp input::input-placeholder { 65 | font-style: italic; 66 | font-weight: 300; 67 | color: #e6e6e6; 68 | } 69 | 70 | .todoapp h1 { 71 | position: absolute; 72 | top: -155px; 73 | width: 100%; 74 | font-size: 100px; 75 | font-weight: 100; 76 | text-align: center; 77 | color: rgba(175, 47, 47, 0.15); 78 | -webkit-text-rendering: optimizeLegibility; 79 | -moz-text-rendering: optimizeLegibility; 80 | text-rendering: optimizeLegibility; 81 | } 82 | 83 | .new-todo, 84 | .edit { 85 | position: relative; 86 | margin: 0; 87 | width: 100%; 88 | font-size: 24px; 89 | font-family: inherit; 90 | font-weight: inherit; 91 | line-height: 1.4em; 92 | border: 0; 93 | color: inherit; 94 | padding: 6px; 95 | border: 1px solid #999; 96 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 97 | box-sizing: border-box; 98 | -webkit-font-smoothing: antialiased; 99 | -moz-osx-font-smoothing: grayscale; 100 | } 101 | 102 | .new-todo { 103 | padding: 16px 16px 16px 60px; 104 | border: none; 105 | background: rgba(0, 0, 0, 0.003); 106 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 107 | } 108 | 109 | .main { 110 | position: relative; 111 | z-index: 2; 112 | border-top: 1px solid #e6e6e6; 113 | } 114 | 115 | label[for='toggle-all'] { 116 | display: none; 117 | } 118 | 119 | .toggle-all { 120 | position: absolute; 121 | top: -55px; 122 | left: -12px; 123 | width: 60px; 124 | height: 34px; 125 | text-align: center; 126 | border: none; /* Mobile Safari */ 127 | } 128 | 129 | .toggle-all:before { 130 | content: '❯'; 131 | font-size: 22px; 132 | color: #e6e6e6; 133 | padding: 10px 27px 10px 27px; 134 | } 135 | 136 | .toggle-all:checked:before { 137 | color: #737373; 138 | } 139 | 140 | .todo-list { 141 | margin: 0; 142 | padding: 0; 143 | list-style: none; 144 | } 145 | 146 | .todo-list li { 147 | position: relative; 148 | font-size: 24px; 149 | border-bottom: 1px solid #ededed; 150 | } 151 | 152 | .todo-list li:last-child { 153 | border-bottom: none; 154 | } 155 | 156 | .todo-list li.editing { 157 | border-bottom: none; 158 | padding: 0; 159 | } 160 | 161 | .todo-list li.editing .edit { 162 | display: block; 163 | width: 506px; 164 | padding: 12px 16px; 165 | margin: 0 0 0 43px; 166 | } 167 | 168 | .todo-list li.editing .view { 169 | display: none; 170 | } 171 | 172 | .todo-list li .toggle { 173 | text-align: center; 174 | width: 40px; 175 | /* auto, since non-WebKit browsers doesn't support input styling */ 176 | height: auto; 177 | position: absolute; 178 | top: 0; 179 | bottom: 0; 180 | margin: auto 0; 181 | border: none; /* Mobile Safari */ 182 | -webkit-appearance: none; 183 | appearance: none; 184 | } 185 | 186 | .todo-list li .toggle:after { 187 | content: url('data:image/svg+xml;utf8,'); 188 | } 189 | 190 | .todo-list li .toggle:checked:after { 191 | content: url('data:image/svg+xml;utf8,'); 192 | } 193 | 194 | .todo-list li label { 195 | word-break: break-all; 196 | padding: 15px 60px 15px 15px; 197 | margin-left: 45px; 198 | display: block; 199 | line-height: 1.2; 200 | transition: color 0.4s; 201 | } 202 | 203 | .todo-list li.completed label { 204 | color: #d9d9d9; 205 | text-decoration: line-through; 206 | } 207 | 208 | .todo-list li .destroy { 209 | display: none; 210 | position: absolute; 211 | top: 0; 212 | right: 10px; 213 | bottom: 0; 214 | width: 40px; 215 | height: 40px; 216 | margin: auto 0; 217 | font-size: 30px; 218 | color: #cc9a9a; 219 | margin-bottom: 11px; 220 | transition: color 0.2s ease-out; 221 | } 222 | 223 | .todo-list li .destroy:hover { 224 | color: #af5b5e; 225 | } 226 | 227 | .todo-list li .destroy:after { 228 | content: '×'; 229 | } 230 | 231 | .todo-list li:hover .destroy { 232 | display: block; 233 | } 234 | 235 | .todo-list li .edit { 236 | display: none; 237 | } 238 | 239 | .todo-list li.editing:last-child { 240 | margin-bottom: -1px; 241 | } 242 | 243 | .footer { 244 | color: #777; 245 | padding: 10px 15px; 246 | height: 20px; 247 | text-align: center; 248 | border-top: 1px solid #e6e6e6; 249 | } 250 | 251 | .footer:before { 252 | content: ''; 253 | position: absolute; 254 | right: 0; 255 | bottom: 0; 256 | left: 0; 257 | height: 50px; 258 | overflow: hidden; 259 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 260 | 0 8px 0 -3px #f6f6f6, 261 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 262 | 0 16px 0 -6px #f6f6f6, 263 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 264 | } 265 | 266 | .todo-count { 267 | float: left; 268 | text-align: left; 269 | } 270 | 271 | .todo-count strong { 272 | font-weight: 300; 273 | } 274 | 275 | .filters { 276 | margin: 0; 277 | padding: 0; 278 | list-style: none; 279 | position: absolute; 280 | right: 0; 281 | left: 0; 282 | } 283 | 284 | .filters li { 285 | display: inline; 286 | } 287 | 288 | .filters li a { 289 | color: inherit; 290 | margin: 3px; 291 | padding: 3px 7px; 292 | text-decoration: none; 293 | border: 1px solid transparent; 294 | border-radius: 3px; 295 | } 296 | 297 | .filters li a:hover { 298 | border-color: rgba(175, 47, 47, 0.1); 299 | } 300 | 301 | .filters li a.selected { 302 | border-color: rgba(175, 47, 47, 0.2); 303 | } 304 | 305 | .clear-completed, 306 | html .clear-completed:active { 307 | float: right; 308 | position: relative; 309 | line-height: 20px; 310 | text-decoration: none; 311 | cursor: pointer; 312 | } 313 | 314 | .clear-completed:hover { 315 | text-decoration: underline; 316 | } 317 | 318 | .info { 319 | margin: 65px auto 0; 320 | color: #bfbfbf; 321 | font-size: 10px; 322 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 323 | text-align: center; 324 | } 325 | 326 | .info p { 327 | line-height: 1; 328 | } 329 | 330 | .info a { 331 | color: inherit; 332 | text-decoration: none; 333 | font-weight: 400; 334 | } 335 | 336 | .info a:hover { 337 | text-decoration: underline; 338 | } 339 | 340 | /* 341 | Hack to remove background from Mobile Safari. 342 | Can't use it globally since it destroys checkboxes in Firefox 343 | */ 344 | @media screen and (-webkit-min-device-pixel-ratio:0) { 345 | .toggle-all, 346 | .todo-list li .toggle { 347 | background: none; 348 | } 349 | 350 | .todo-list li .toggle { 351 | height: 40px; 352 | } 353 | 354 | .toggle-all { 355 | -webkit-transform: rotate(90deg); 356 | transform: rotate(90deg); 357 | -webkit-appearance: none; 358 | appearance: none; 359 | } 360 | } 361 | 362 | @media (max-width: 430px) { 363 | .footer { 364 | height: 50px; 365 | } 366 | 367 | .filters { 368 | bottom: 10px; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "base": "cp node_modules/todomvc-app-css/index.css dist; cp node_modules/todomvc-common/base.{js,css} dist;", 5 | "build": "mkdir -p dist; npm run hyperhtml-add; npm run babelify; npm run bundle; rm -rf es2015; npm run hyperhtml-remove; npm run base", 6 | "hyperhtml-add": "mkdir -p js/lib; cp node_modules/hyperhtml/hyperhtml.js js/lib; sed -i.bak 's/try { module.exports = hyperHTML; } catch(o_O) {}/export default hyperHTML;/' js/lib/hyperhtml.js", 7 | "hyperhtml-remove": "rm -rf js/lib", 8 | "bundle": "echo '// jscs:disable' > dist/bundle.js; browserify ./es2015/**.js | uglifyjs -mc warnings=false >> dist/bundle.js", 9 | "babelify": "babel ./js --out-dir ./es2015 --presets=es2015", 10 | "prepublish": "npm run build" 11 | }, 12 | "dependencies": { 13 | "hyperhtml": "^0.11.7", 14 | "todomvc-app-css": "^2.0.0", 15 | "todomvc-common": "^1.0.0" 16 | }, 17 | "devDependencies": { 18 | "babel-cli": "^6.23.0", 19 | "babel-preset-es2015": "^6.22.0", 20 | "browserify": "^14.1.0", 21 | "uglify-js": "^2.8.11" 22 | } 23 | } 24 | --------------------------------------------------------------------------------