├── .npmrc
├── .gitignore
├── deploy.json
├── generate-data.js
├── .travis.yml
├── src
├── render
│ ├── render.js
│ ├── render-header.js
│ ├── render-todos.js
│ ├── render-todo.js
│ └── render-footer.js
├── uuid.js
├── todos.js
├── app.js
└── data.json
├── webpack.config.js
├── hydrate-app.js
├── package.json
├── index.html
├── README.md
└── dist
├── bottle.js
├── bottle-service.js
├── app.css
└── index.html
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=http://registry.npmjs.org/
2 | save-exact=true
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | .grunt/
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/deploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "gh-pages": {
3 | "options": {
4 | "base": "dist"
5 | },
6 | "src": [
7 | "index.html",
8 | "*.js",
9 | "*.css"
10 | ]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/generate-data.js:
--------------------------------------------------------------------------------
1 | const generateFakeTodos = require('fake-todos')
2 |
3 | const fakeTodos = generateFakeTodos(100)
4 | const outputFilename = './src/data.json'
5 | const write = require('fs').writeFileSync
6 | write(outputFilename, JSON.stringify(fakeTodos, null, 2))
7 |
8 | console.log('wrote fake data to', outputFilename)
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | cache:
4 | directories:
5 | - node_modules
6 | notifications:
7 | email: false
8 | node_js:
9 | - '4'
10 | before_script:
11 | - npm prune
12 | script:
13 | - npm run lint
14 | - npm test
15 | - npm run build
16 | branches:
17 | except:
18 | - "/^v\\d+\\.\\d+\\.\\d+$/"
19 |
--------------------------------------------------------------------------------
/src/render/render.js:
--------------------------------------------------------------------------------
1 | const h = require('virtual-dom/h')
2 | const header = require('./render-header')
3 | const renderTodos = require('./render-todos')
4 | const footer = require('./render-footer')
5 |
6 | function render (todos) {
7 | return h('section', {className: 'todoapp'}, [
8 | header(todos),
9 | renderTodos(todos),
10 | footer(todos)
11 | ])
12 | }
13 |
14 | module.exports = render
15 |
--------------------------------------------------------------------------------
/src/uuid.js:
--------------------------------------------------------------------------------
1 | // from http://jsfiddle.net/briguy37/2mvfd/
2 | function uuid () {
3 | var d = new Date().getTime()
4 | var uuidFormat = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
5 | var uuid = uuidFormat.replace(/[xy]/g, function (c) {
6 | var r = (d + Math.random() * 16) % 16 | 0
7 | d = Math.floor(d / 16)
8 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
9 | })
10 | return uuid
11 | }
12 |
13 | module.exports = uuid
14 |
--------------------------------------------------------------------------------
/src/render/render-header.js:
--------------------------------------------------------------------------------
1 | const h = require('virtual-dom/h')
2 |
3 | function render (Todos) {
4 | function isEnter (e) {
5 | return e.keyCode === 13
6 | }
7 | function onKey (e) {
8 | console.log('pressed', e.target.value)
9 | if (isEnter(e)) {
10 | Todos.add(e.target.value)
11 | e.target.value = ''
12 | }
13 | }
14 |
15 | return h('header', {className: 'header'}, [
16 | h('h1', {}, 'todos'),
17 | h('input', {
18 | className: 'new-todo',
19 | placeholder: 'What needs to be done?',
20 | autofocus: true,
21 | onkeyup: onKey
22 | }, [])
23 | ])
24 | }
25 |
26 | module.exports = render
27 |
--------------------------------------------------------------------------------
/src/render/render-todos.js:
--------------------------------------------------------------------------------
1 | const h = require('virtual-dom/h')
2 | const renderTodo = require('./render-todo')
3 |
4 | function render (Todos) {
5 | return h('section', {className: 'main'}, [
6 | h('input', {
7 | className: 'toggle-all',
8 | type: 'checkbox',
9 | onclick: function (e) {
10 | console.log('nothing')
11 | // Todos.mark(e.target.checked);
12 | // renderApp();
13 | }
14 | }),
15 | h('label', {htmlFor: 'toggle-all'}, 'Mark all as complete'),
16 | h('ul', {className: 'todo-list'},
17 | Todos.items.map(renderTodo.bind(null, Todos)))
18 | ])
19 | }
20 |
21 | module.exports = render
22 |
--------------------------------------------------------------------------------
/src/render/render-todo.js:
--------------------------------------------------------------------------------
1 | const h = require('virtual-dom/h')
2 |
3 | function render (Todos, todo) {
4 | return h('li', {className: todo.done ? 'completed' : '', key: todo.id}, [
5 | h('div', {className: 'view'}, [
6 | h('input', {
7 | className: 'toggle',
8 | type: 'checkbox',
9 | checked: todo.done,
10 | onchange: function (e) {
11 | Todos.mark(todo.id, e.target.checked)
12 | }
13 | }),
14 | h('label', todo.what),
15 | h('button', {
16 | className: 'destroy',
17 | onclick: function (e) {
18 | console.log('nothing')
19 | Todos.remove(todo)
20 | }
21 | })
22 | ])
23 | ])
24 | }
25 |
26 | module.exports = render
27 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
2 |
3 | module.exports = {
4 | output: {
5 | path: './dist',
6 | filename: 'app.js'
7 | },
8 | entry: {
9 | app: './src/app.js'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.css$/,
15 | loader: ExtractTextPlugin.extract({
16 | fallbackLoader: 'style-loader',
17 | loader: 'css-loader'
18 | })
19 | },
20 | {
21 | test: /\.json$/,
22 | loader: 'json'
23 | }
24 | ]
25 | }
26 | }
27 |
28 | module.exports.plugins = [
29 | new ExtractTextPlugin({
30 | filename: 'app.css',
31 | disable: false,
32 | allChunks: true
33 | })
34 | ]
35 |
--------------------------------------------------------------------------------
/src/todos.js:
--------------------------------------------------------------------------------
1 | const la = require('lazy-ass')
2 | const is = require('check-more-types')
3 | const uuid = require('./uuid')
4 |
5 | var Todos = {
6 | add: function (what) {
7 | Todos.items.unshift({
8 | what: what,
9 | done: false,
10 | id: uuid()
11 | })
12 | },
13 | mark: function (id, done) {
14 | Todos.items.forEach(function (todo) {
15 | if (todo.id === id) {
16 | todo.done = done
17 | }
18 | })
19 | },
20 | remove: function (todo) {
21 | Todos.items = Todos.items.filter(function (t) {
22 | return t.id !== todo.id
23 | })
24 | },
25 | items: require('./data.json')
26 | }
27 |
28 | la(is.array(Todos.items), 'expected list of todos', Todos.items)
29 |
30 | module.exports = Todos
31 |
--------------------------------------------------------------------------------
/hydrate-app.js:
--------------------------------------------------------------------------------
1 | const render = require('./src/render/render')
2 | const Todos = require('./src/todos')
3 | const rendered = render(Todos)
4 |
5 | const toHTML = require('vdom-to-html')
6 | const beautify = require('js-beautify').html
7 | const appMarkup = beautify(toHTML(rendered), { indent_size: 2 })
8 |
9 | function updateAppMarkup (inputFilename, outputFilename, markup) {
10 | const read = require('fs').readFileSync
11 | const page = read(inputFilename, 'utf-8')
12 |
13 | const write = require('fs').writeFileSync
14 |
15 | // temp hack with ID in the closing tag
16 | const openTag = '
'
17 | const closeTag = '
'
18 | const appDiv = openTag + closeTag
19 | if (!page.indexOf(appDiv)) {
20 | throw new Error('Cannot find markup ' + appDiv + ' in the page ' + inputFilename)
21 | }
22 | const hydratedPage = page.replace(appDiv, openTag + '\n' + markup + '\n' + closeTag)
23 |
24 | write(outputFilename, hydratedPage)
25 | console.log('saved hydrated index', outputFilename)
26 | }
27 |
28 | updateAppMarkup('./index.html', './dist/index.html', appMarkup)
29 |
30 |
--------------------------------------------------------------------------------
/src/render/render-footer.js:
--------------------------------------------------------------------------------
1 | const h = require('virtual-dom/h')
2 |
3 | function hashFragment () {
4 | return typeof window !== 'undefined' && window.location.hash.split('/')[1] || ''
5 | }
6 |
7 | function countRemaining (todos) {
8 | return todos.length - todos.reduce(function (count, todo) {
9 | return count + Number(todo.done)
10 | }, 0)
11 | }
12 |
13 | function hasCompleted (todos) {
14 | return todos && todos.some(function (todo) {
15 | return todo.done
16 | })
17 | }
18 |
19 | function render (Todos) {
20 | const remaining = countRemaining(Todos.items)
21 | const route = hashFragment()
22 |
23 | return h('footer', {className: 'footer'}, [
24 | h('span', {className: 'todo-count'}, [
25 | h('strong', {}, String(remaining)),
26 | ' items left'
27 | ]),
28 | h('ul', {className: 'filters'}, [
29 | h('li', [
30 | h('a', {
31 | className: !route ? 'selected' : '',
32 | href: '#/'
33 | }, 'All')
34 | ]),
35 | h('li', [
36 | h('a', {
37 | className: route === 'active' ? 'selected' : '',
38 | href: '#/active'
39 | }, 'Active')
40 | ]),
41 | h('li', [
42 | h('a', {
43 | className: route === 'completed' ? 'selected' : '',
44 | href: '#/completed'
45 | }, 'Completed')
46 | ])
47 | ]),
48 | h('button', {
49 | className: 'clear-completed',
50 | style: {
51 | display: hasCompleted(Todos.items) ? 'block' : 'none'
52 | },
53 | onclick: function () {
54 | // todos && todos.clearCompleted();
55 | // renderApp();
56 | }
57 | }, 'Clear completed')
58 | ])
59 | }
60 |
61 | module.exports = render
62 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /* global bottleService */
4 |
5 | require('../node_modules/todomvc-common/base.css')
6 | require('../node_modules/todomvc-app-css/index.css')
7 |
8 | const la = require('lazy-ass')
9 | const is = require('check-more-types')
10 | const tinyToast = require('tiny-toast')
11 |
12 | const diff = require('virtual-dom/diff')
13 | const patch = require('virtual-dom/patch')
14 |
15 | const appNode = document.getElementById('app')
16 | var renderedNode = appNode.firstElementChild
17 |
18 | const VNode = require('virtual-dom/vnode/vnode')
19 | const VText = require('virtual-dom/vnode/vtext')
20 | const convertHTML = require('html-to-vdom')({
21 | VNode: VNode,
22 | VText: VText
23 | })
24 | const render = require('./render/render')
25 | var prevView = convertHTML(renderedNode.outerHTML)
26 |
27 | const Todos = require('./todos')
28 |
29 | const appLabel = 'instant-vdom-todo'
30 | const todosStorageLabel = appLabel + '-items'
31 | var updatedTodos = localStorage.getItem(todosStorageLabel)
32 | if (updatedTodos) {
33 | updatedTodos = JSON.parse(updatedTodos)
34 | Todos.items = updatedTodos
35 | console.log('set todos to local storage value with %d items',
36 | updatedTodos.length)
37 | } else {
38 | console.log('No previous todo items found')
39 | }
40 |
41 | function saveApp () {
42 | localStorage.setItem(todosStorageLabel, JSON.stringify(Todos.items))
43 | setTimeout(function () {
44 | // application has renderd itself
45 | bottleService.refill(appLabel, 'app')
46 | }, 0);
47 | }
48 |
49 | // add rendering call after data methods
50 | // also save items
51 | Object.keys(Todos).forEach(function (key) {
52 | const value = Todos[key]
53 | if (is.fn(value)) {
54 | Todos[key] = function () {
55 | const result = value.apply(Todos, arguments)
56 | renderApp()
57 | saveApp()
58 | return result
59 | }
60 | }
61 | })
62 |
63 | function renderApp () {
64 | console.log('rendering %d todos', Todos.items.length)
65 | const view = render(Todos)
66 | const patches = diff(prevView, view)
67 | renderedNode = patch(renderedNode, patches)
68 | prevView = view
69 | }
70 |
71 | console.log('initial render after 500ms delay')
72 | setTimeout(function () {
73 | renderApp()
74 | tinyToast.show('Rendered web app after 500ms delay')
75 | tinyToast.hide(2000)
76 | }, 500)
77 |
78 | window.renderApp = renderApp
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "instant-vdom-todo",
3 | "version": "1.0.0",
4 | "description": "Example of a build time hydration for virtual dom application",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "npm run lint",
8 | "lint": "standard --verbose --fix *.js src/**/*.js",
9 | "build": "webpack && node hydrate-app.js && npm run copy-bottle-service",
10 | "copy-bottle-service": "cp node_modules/bottle-service/dist/*.js dist/",
11 | "webpack": "webpack",
12 | "deploy": "grunty grunt-gh-pages gh-pages deploy.json",
13 | "commit": "commit-wizard",
14 | "start": "http-server dist -c-1",
15 | "dev-start": "http-server dist -c-1 -p 3005"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/bahmutov/instant-vdom-todo.git"
20 | },
21 | "keywords": [
22 | "virtual-dom",
23 | "vdom",
24 | "example",
25 | "hydrate",
26 | "todo"
27 | ],
28 | "private": true,
29 | "author": "Gleb Bahmutov ",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/bahmutov/instant-vdom-todo/issues"
33 | },
34 | "homepage": "https://github.com/bahmutov/instant-vdom-todo#readme",
35 | "dependencies": {
36 | "bottle-service": "1.2.4",
37 | "check-more-types": "2.23.0",
38 | "http-server": "0.9.0",
39 | "lazy-ass": "1.5.0",
40 | "tiny-toast": "1.1.0",
41 | "todomvc-app-css": "2.0.6",
42 | "todomvc-common": "1.0.2",
43 | "vdom-to-html": "2.3.0",
44 | "virtual-dom": "2.1.1"
45 | },
46 | "devDependencies": {
47 | "css-loader": "0.25.0",
48 | "extract-text-webpack-plugin": "2.0.0-beta.4",
49 | "fake-todos": "1.8.0",
50 | "faker": "3.1.0",
51 | "grunt": "1.0.1",
52 | "grunt-gh-pages": "2.0.0",
53 | "grunty": "0.3.0",
54 | "html-to-vdom": "0.7.0",
55 | "html2hscript": "2.0.1",
56 | "js-beautify": "1.6.4",
57 | "json-loader": "0.5.4",
58 | "pre-git": "3.10.0",
59 | "standard": "8.3.0",
60 | "style-loader": "0.13.1",
61 | "webpack": "2.1.0-beta.25"
62 | },
63 | "config": {
64 | "pre-git": {
65 | "commit-msg": [
66 | "simple"
67 | ],
68 | "pre-commit": [
69 | "npm run lint",
70 | "npm run test",
71 | "npm run build"
72 | ],
73 | "pre-push": [],
74 | "post-commit": [],
75 | "post-merge": []
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Instant TodoMVC
7 |
8 |
9 |
10 |
11 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # instant-vdom-todo
2 | > Prehydrated, self-rewriting TodoMVC using Virtual-Dom and bottle-service
3 |
4 | [![Build status][instant-vdom-todo-ci-image] ][instant-vdom-todo-ci-url]
5 |
6 | [live demo](https://instant-todo.herokuapp.com/),
7 | [screen recording of the demo](https://youtu.be/KRVoibtht84) - notice that
8 | I reload the page multiple times, but the app appears instantly.
9 |
10 | Uses [bottle-service](https://github.com/bahmutov/bottle-service)
11 | to enable HTML rewriting on page load.
12 |
13 | Read the [Instant web application](http://glebbahmutov.com/blog/instant-web-application/)
14 | blog post.
15 |
16 | ## Browser support
17 |
18 | ### Chrome, Opera
19 |
20 | * Nothing to do, `ServiceWorker` should be enabled by default
21 |
22 | ### Firefox
23 |
24 | * Open `about:config`
25 | * Set the `dom.serviceWorkers.enabled` setting to **true**
26 | * Set the `dom.serviceWorkers.interception.enabled` setting to **true**
27 |
28 | ### Small print
29 |
30 | Author: Gleb Bahmutov © 2015
31 |
32 | * [@bahmutov](https://twitter.com/bahmutov)
33 | * [glebbahmutov.com](http://glebbahmutov.com)
34 | * [blog](http://glebbahmutov.com/blog/)
35 |
36 | License: MIT - do anything with the code, but don't blame me if it does not work.
37 |
38 | Spread the word: tweet, star on github, etc.
39 |
40 | Support: if you find any problems with this module, email / tweet /
41 | [open issue](https://github.com/bahmutov/instant-vdom-todo/issues) on Github
42 |
43 | ## MIT License
44 |
45 | Copyright (c) 2015 Gleb Bahmutov
46 |
47 | Permission is hereby granted, free of charge, to any person
48 | obtaining a copy of this software and associated documentation
49 | files (the "Software"), to deal in the Software without
50 | restriction, including without limitation the rights to use,
51 | copy, modify, merge, publish, distribute, sublicense, and/or sell
52 | copies of the Software, and to permit persons to whom the
53 | Software is furnished to do so, subject to the following
54 | conditions:
55 |
56 | The above copyright notice and this permission notice shall be
57 | included in all copies or substantial portions of the Software.
58 |
59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
60 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
61 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
62 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
63 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
64 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
65 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
66 | OTHER DEALINGS IN THE SOFTWARE.
67 |
68 | [instant-vdom-todo-ci-image]: https://travis-ci.org/bahmutov/instant-vdom-todo.svg?branch=master
69 | [instant-vdom-todo-ci-url]: https://travis-ci.org/bahmutov/instant-vdom-todo
70 |
--------------------------------------------------------------------------------
/dist/bottle.js:
--------------------------------------------------------------------------------
1 | !(function startBottleService (root) {
2 | 'use strict'
3 |
4 | if (!root.navigator) {
5 | console.error('Missing navigator')
6 | return
7 | }
8 |
9 | if (!root.navigator.serviceWorker) {
10 | console.error('Sorry, not ServiceWorker feature, maybe enable it?')
11 | console.error('http://jakearchibald.com/2014/using-serviceworker-today/')
12 | return
13 | }
14 |
15 | // TODO package lazy-ass and check-more-types using webpack
16 |
17 | function toString (x) {
18 | return typeof x === 'string' ? x : JSON.stringify(x)
19 | }
20 |
21 | function la (condition) {
22 | if (!condition) {
23 | var args = Array.prototype.slice.call(arguments, 1)
24 | .map(toString)
25 | throw new Error(args.join(' '))
26 | }
27 | }
28 |
29 | function isFunction (f) {
30 | return typeof f === 'function'
31 | }
32 |
33 | function getCurrentScriptFolder () {
34 | var scriptEls = document.getElementsByTagName('script')
35 | var thisScriptEl = scriptEls[scriptEls.length - 1]
36 | var scriptPath = thisScriptEl.src
37 | return scriptPath.substr(0, scriptPath.lastIndexOf('/') + 1)
38 | }
39 |
40 | var serviceScriptUrl = getCurrentScriptFolder() + 'bottle-service.js'
41 | // assume we are running at /pathname
42 | var scope = window.location.pathname
43 |
44 | var send = function mockSend () {
45 | console.error('Bottle service not initialized yet')
46 | }
47 |
48 | function registeredWorker (registration) {
49 | la(registration, 'missing service worker registration')
50 | la(registration.active, 'missing active service worker')
51 | la(isFunction(registration.active.postMessage),
52 | 'expected function postMessage to communicate with service worker')
53 |
54 | send = registration.active.postMessage.bind(registration.active)
55 | var info = '\nbottle-service - .\n' +
56 | 'I have a valid service-turtle, use `bottleService` object to update cached page'
57 | console.log(info)
58 |
59 | registration.active.onmessage = function messageFromServiceWorker (e) {
60 | console.log('received message from the service worker', e)
61 | }
62 | }
63 |
64 | function onError (err) {
65 | if (err.message.indexOf('missing active') !== -1) {
66 | // the service worker is installed
67 | window.location.reload()
68 | } else {
69 | console.error('bottle service error', err)
70 | }
71 | }
72 |
73 | root.navigator.serviceWorker.register(serviceScriptUrl, { scope: scope })
74 | .then(registeredWorker)
75 | .catch(onError)
76 |
77 | root.bottleService = {
78 | refill: function refill (applicationName, id) {
79 | console.log('bottle-service: html for app %s element %s', applicationName, id)
80 |
81 | var el = document.getElementById(id)
82 | la(el, 'could not find element with id', id)
83 | var html = el.innerHTML.trim()
84 | send({
85 | cmd: 'refill',
86 | html: html,
87 | name: applicationName,
88 | id: id
89 | })
90 | },
91 | print: function print (applicationName) {
92 | send({
93 | cmd: 'print',
94 | name: applicationName
95 | })
96 | },
97 | clear: function clear (applicationName) {
98 | send({
99 | cmd: 'clear',
100 | name: applicationName
101 | })
102 | }
103 | }
104 | }(window))
105 |
--------------------------------------------------------------------------------
/dist/bottle-service.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 |
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 |
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 |
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 |
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 |
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 |
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 |
29 |
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 |
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 |
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "";
38 |
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ([
44 | /* 0 */
45 | /***/ function(module, exports, __webpack_require__) {
46 |
47 | 'use strict'
48 |
49 | /*
50 | This is ServiceWorker code
51 | */
52 | /* global self, Response, Promise, location, fetch */
53 | var myName = 'bottle-service'
54 | console.log(myName, 'startup')
55 |
56 | function dataStore () {
57 | var cachesStorage = __webpack_require__(1)
58 | return cachesStorage(myName)
59 | }
60 |
61 | self.addEventListener('install', function (event) {
62 | console.log(myName, 'installed')
63 | })
64 |
65 | self.addEventListener('activate', function () {
66 | console.log(myName, 'activated')
67 | })
68 |
69 | var baseHref = location.href.substr(0, location.href.indexOf('bottle-service.js'))
70 | function isIndexPageRequest (event) {
71 | return event &&
72 | event.request &&
73 | event.request.url === baseHref
74 | }
75 |
76 | self.addEventListener('fetch', function (event) {
77 | if (!isIndexPageRequest(event)) {
78 | return fetch(event.request)
79 | }
80 |
81 | console.log(myName, 'fetching index page', event.request.url)
82 |
83 | event.respondWith(
84 | fetch(event.request)
85 | .then(function (response) {
86 | return dataStore()
87 | .then(function (store) {
88 | return store.getItem('contents')
89 | })
90 | .then(function (contents) {
91 | if (contents && contents.html && contents.id) {
92 | console.log('fetched latest', response.url, 'need to update')
93 | console.log('element "%s" with html "%s" ...',
94 | contents.id, contents.html.substr(0, 15))
95 |
96 | var copy = response.clone()
97 | return copy.text().then(function (pageHtml) {
98 | console.log('inserting our html')
99 | // HACK using id in the CLOSING TAG to find fragment
100 | var toReplaceStart = ''
101 | var toReplaceFinish = '
'
102 | var startIndex = pageHtml.indexOf(toReplaceStart)
103 | var finishIndex = pageHtml.indexOf(toReplaceFinish)
104 | if (startIndex !== -1 && finishIndex > startIndex) {
105 | console.log('found fragment')
106 | pageHtml = pageHtml.substr(0, startIndex + toReplaceStart.length) +
107 | '\n' + contents.html + '\n' +
108 | pageHtml.substr(finishIndex)
109 | }
110 |
111 | // console.log('page html')
112 | // console.log(pageHtml)
113 |
114 | var responseOptions = {
115 | status: 200,
116 | headers: {
117 | 'Content-Type': 'text/html charset=UTF-8'
118 | }
119 | }
120 | return new Response(pageHtml, responseOptions)
121 | })
122 | } else {
123 | return response
124 | }
125 | }, function notFound () {
126 | return response
127 | })
128 | })
129 | )
130 | })
131 |
132 | // use window.navigator.serviceWorker.controller.postMessage('hi')
133 | // to communicate with this service worker
134 | self.onmessage = function onMessage (event) {
135 | console.log('message to bottle-service worker cmd', event.data && event.data.cmd)
136 |
137 | // TODO how to use application name?
138 |
139 | dataStore().then(function (store) {
140 | switch (event.data.cmd) {
141 | case 'print': {
142 | return store.getItem('contents')
143 | .then(function (res) {
144 | console.log('bottle service has contents')
145 | console.log(res)
146 | })
147 | }
148 | case 'clear': {
149 | console.log('clearing the bottle')
150 | return store.setItem('contents', {})
151 | }
152 | case 'refill': {
153 | return store.setItem('contents', {
154 | html: event.data.html,
155 | id: event.data.id
156 | }).then(function () {
157 | console.log('saved new html for id', event.data.id)
158 | })
159 | }
160 | default: {
161 | console.error(myName, 'unknown command', event.data)
162 | }
163 | }
164 | })
165 | }
166 |
167 |
168 | /***/ },
169 | /* 1 */
170 | /***/ function(module, exports) {
171 |
172 | // Poor man's async "localStorage" on top of Cache
173 | // https://developer.mozilla.org/en-US/docs/Web/API/Cache
174 | if (typeof caches === 'undefined') {
175 | throw new Error('Cannot find object caches?! Cannot init cache-storage')
176 | }
177 | /* global caches, Response */
178 | function dataStore (name) {
179 | var id = name ? name + '-v1' : 'cache-storage-v1'
180 | return caches.open(id)
181 | .then(function (cache) {
182 | return {
183 | setItem: function (key, data) {
184 | return cache.put(key, new Response(JSON.stringify(data)))
185 | },
186 | getItem: function (key) {
187 | return cache.match(key)
188 | .then(function (res) {
189 | return res &&
190 | res.text().then(JSON.parse)
191 | })
192 | }
193 | }
194 | })
195 | }
196 |
197 | module.exports = dataStore
198 |
199 |
200 | /***/ }
201 | /******/ ]);
--------------------------------------------------------------------------------
/dist/app.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: '\201C';
73 | font-size: 50px;
74 | opacity: .15;
75 | position: absolute;
76 | top: -20px;
77 | left: 3px;
78 | }
79 |
80 | .quote p:after {
81 | content: '\201D';
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 | html,
143 | body {
144 | margin: 0;
145 | padding: 0;
146 | }
147 |
148 | button {
149 | margin: 0;
150 | padding: 0;
151 | border: 0;
152 | background: none;
153 | font-size: 100%;
154 | vertical-align: baseline;
155 | font-family: inherit;
156 | font-weight: inherit;
157 | color: inherit;
158 | -webkit-appearance: none;
159 | appearance: none;
160 | -webkit-font-smoothing: antialiased;
161 | -moz-osx-font-smoothing: grayscale;
162 | }
163 |
164 | body {
165 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
166 | line-height: 1.4em;
167 | background: #f5f5f5;
168 | color: #4d4d4d;
169 | min-width: 230px;
170 | max-width: 550px;
171 | margin: 0 auto;
172 | -webkit-font-smoothing: antialiased;
173 | -moz-osx-font-smoothing: grayscale;
174 | font-weight: 300;
175 | }
176 |
177 | :focus {
178 | outline: 0;
179 | }
180 |
181 | .hidden {
182 | display: none;
183 | }
184 |
185 | .todoapp {
186 | background: #fff;
187 | margin: 130px 0 40px 0;
188 | position: relative;
189 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
190 | 0 25px 50px 0 rgba(0, 0, 0, 0.1);
191 | }
192 |
193 | .todoapp input::-webkit-input-placeholder {
194 | font-style: italic;
195 | font-weight: 300;
196 | color: #e6e6e6;
197 | }
198 |
199 | .todoapp input::-moz-placeholder {
200 | font-style: italic;
201 | font-weight: 300;
202 | color: #e6e6e6;
203 | }
204 |
205 | .todoapp input::input-placeholder {
206 | font-style: italic;
207 | font-weight: 300;
208 | color: #e6e6e6;
209 | }
210 |
211 | .todoapp h1 {
212 | position: absolute;
213 | top: -155px;
214 | width: 100%;
215 | font-size: 100px;
216 | font-weight: 100;
217 | text-align: center;
218 | color: rgba(175, 47, 47, 0.15);
219 | -webkit-text-rendering: optimizeLegibility;
220 | -moz-text-rendering: optimizeLegibility;
221 | text-rendering: optimizeLegibility;
222 | }
223 |
224 | .new-todo,
225 | .edit {
226 | position: relative;
227 | margin: 0;
228 | width: 100%;
229 | font-size: 24px;
230 | font-family: inherit;
231 | font-weight: inherit;
232 | line-height: 1.4em;
233 | border: 0;
234 | color: inherit;
235 | padding: 6px;
236 | border: 1px solid #999;
237 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
238 | box-sizing: border-box;
239 | -webkit-font-smoothing: antialiased;
240 | -moz-osx-font-smoothing: grayscale;
241 | }
242 |
243 | .new-todo {
244 | padding: 16px 16px 16px 60px;
245 | border: none;
246 | background: rgba(0, 0, 0, 0.003);
247 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
248 | }
249 |
250 | .main {
251 | position: relative;
252 | z-index: 2;
253 | border-top: 1px solid #e6e6e6;
254 | }
255 |
256 | label[for='toggle-all'] {
257 | display: none;
258 | }
259 |
260 | .toggle-all {
261 | position: absolute;
262 | top: -55px;
263 | left: -12px;
264 | width: 60px;
265 | height: 34px;
266 | text-align: center;
267 | border: none; /* Mobile Safari */
268 | }
269 |
270 | .toggle-all:before {
271 | content: '\276F';
272 | font-size: 22px;
273 | color: #e6e6e6;
274 | padding: 10px 27px 10px 27px;
275 | }
276 |
277 | .toggle-all:checked:before {
278 | color: #737373;
279 | }
280 |
281 | .todo-list {
282 | margin: 0;
283 | padding: 0;
284 | list-style: none;
285 | }
286 |
287 | .todo-list li {
288 | position: relative;
289 | font-size: 24px;
290 | border-bottom: 1px solid #ededed;
291 | }
292 |
293 | .todo-list li:last-child {
294 | border-bottom: none;
295 | }
296 |
297 | .todo-list li.editing {
298 | border-bottom: none;
299 | padding: 0;
300 | }
301 |
302 | .todo-list li.editing .edit {
303 | display: block;
304 | width: 506px;
305 | padding: 12px 16px;
306 | margin: 0 0 0 43px;
307 | }
308 |
309 | .todo-list li.editing .view {
310 | display: none;
311 | }
312 |
313 | .todo-list li .toggle {
314 | text-align: center;
315 | width: 40px;
316 | /* auto, since non-WebKit browsers doesn't support input styling */
317 | height: auto;
318 | position: absolute;
319 | top: 0;
320 | bottom: 0;
321 | margin: auto 0;
322 | border: none; /* Mobile Safari */
323 | -webkit-appearance: none;
324 | appearance: none;
325 | }
326 |
327 | .todo-list li .toggle:after {
328 | content: url('data:image/svg+xml;utf8,');
329 | }
330 |
331 | .todo-list li .toggle:checked:after {
332 | content: url('data:image/svg+xml;utf8,');
333 | }
334 |
335 | .todo-list li label {
336 | word-break: break-all;
337 | padding: 15px 60px 15px 15px;
338 | margin-left: 45px;
339 | display: block;
340 | line-height: 1.2;
341 | transition: color 0.4s;
342 | }
343 |
344 | .todo-list li.completed label {
345 | color: #d9d9d9;
346 | text-decoration: line-through;
347 | }
348 |
349 | .todo-list li .destroy {
350 | display: none;
351 | position: absolute;
352 | top: 0;
353 | right: 10px;
354 | bottom: 0;
355 | width: 40px;
356 | height: 40px;
357 | margin: auto 0;
358 | font-size: 30px;
359 | color: #cc9a9a;
360 | margin-bottom: 11px;
361 | transition: color 0.2s ease-out;
362 | }
363 |
364 | .todo-list li .destroy:hover {
365 | color: #af5b5e;
366 | }
367 |
368 | .todo-list li .destroy:after {
369 | content: '\D7';
370 | }
371 |
372 | .todo-list li:hover .destroy {
373 | display: block;
374 | }
375 |
376 | .todo-list li .edit {
377 | display: none;
378 | }
379 |
380 | .todo-list li.editing:last-child {
381 | margin-bottom: -1px;
382 | }
383 |
384 | .footer {
385 | color: #777;
386 | padding: 10px 15px;
387 | height: 20px;
388 | text-align: center;
389 | border-top: 1px solid #e6e6e6;
390 | }
391 |
392 | .footer:before {
393 | content: '';
394 | position: absolute;
395 | right: 0;
396 | bottom: 0;
397 | left: 0;
398 | height: 50px;
399 | overflow: hidden;
400 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
401 | 0 8px 0 -3px #f6f6f6,
402 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
403 | 0 16px 0 -6px #f6f6f6,
404 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
405 | }
406 |
407 | .todo-count {
408 | float: left;
409 | text-align: left;
410 | }
411 |
412 | .todo-count strong {
413 | font-weight: 300;
414 | }
415 |
416 | .filters {
417 | margin: 0;
418 | padding: 0;
419 | list-style: none;
420 | position: absolute;
421 | right: 0;
422 | left: 0;
423 | }
424 |
425 | .filters li {
426 | display: inline;
427 | }
428 |
429 | .filters li a {
430 | color: inherit;
431 | margin: 3px;
432 | padding: 3px 7px;
433 | text-decoration: none;
434 | border: 1px solid transparent;
435 | border-radius: 3px;
436 | }
437 |
438 | .filters li a:hover {
439 | border-color: rgba(175, 47, 47, 0.1);
440 | }
441 |
442 | .filters li a.selected {
443 | border-color: rgba(175, 47, 47, 0.2);
444 | }
445 |
446 | .clear-completed,
447 | html .clear-completed:active {
448 | float: right;
449 | position: relative;
450 | line-height: 20px;
451 | text-decoration: none;
452 | cursor: pointer;
453 | }
454 |
455 | .clear-completed:hover {
456 | text-decoration: underline;
457 | }
458 |
459 | .info {
460 | margin: 65px auto 0;
461 | color: #bfbfbf;
462 | font-size: 10px;
463 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
464 | text-align: center;
465 | }
466 |
467 | .info p {
468 | line-height: 1;
469 | }
470 |
471 | .info a {
472 | color: inherit;
473 | text-decoration: none;
474 | font-weight: 400;
475 | }
476 |
477 | .info a:hover {
478 | text-decoration: underline;
479 | }
480 |
481 | /*
482 | Hack to remove background from Mobile Safari.
483 | Can't use it globally since it destroys checkboxes in Firefox
484 | */
485 | @media screen and (-webkit-min-device-pixel-ratio:0) {
486 | .toggle-all,
487 | .todo-list li .toggle {
488 | background: none;
489 | }
490 |
491 | .todo-list li .toggle {
492 | height: 40px;
493 | }
494 |
495 | .toggle-all {
496 | -webkit-transform: rotate(90deg);
497 | transform: rotate(90deg);
498 | -webkit-appearance: none;
499 | appearance: none;
500 | }
501 | }
502 |
503 | @media (max-width: 430px) {
504 | .footer {
505 | height: 50px;
506 | }
507 |
508 | .filters {
509 | bottom: 10px;
510 | }
511 | }
512 |
--------------------------------------------------------------------------------
/src/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "what": "help fix fines",
4 | "due": "tomorrow",
5 | "done": false,
6 | "id": "db2f5b89-7143-4be1-a25d-aafda9b95f31"
7 | },
8 | {
9 | "what": "skip exercising fines",
10 | "due": "tomorrow",
11 | "done": false,
12 | "id": "54cb20a5-56d3-4c82-bb27-e8944981aee9"
13 | },
14 | {
15 | "what": "sell books",
16 | "due": "tomorrow",
17 | "done": false,
18 | "id": "ea373b4d-24cf-457a-9ca3-8ab10f3cbb84"
19 | },
20 | {
21 | "what": "pretend to pick principles",
22 | "due": "tomorrow",
23 | "done": false,
24 | "id": "69e5161e-d038-42aa-b5cb-9a6b3f5fa529"
25 | },
26 | {
27 | "what": "buy bird",
28 | "due": "tomorrow",
29 | "done": false,
30 | "id": "1d1ec006-d05b-4d4a-a82c-9f1f88d332e3"
31 | },
32 | {
33 | "what": "tweet Node.js",
34 | "due": "tomorrow",
35 | "done": false,
36 | "id": "d2db157a-d13b-41ac-9379-12ea5862ef17"
37 | },
38 | {
39 | "what": "help buy castle",
40 | "due": "tomorrow",
41 | "done": false,
42 | "id": "4db10c6f-acb5-4695-9255-5d2f5ecf39ed"
43 | },
44 | {
45 | "what": "help avoid fishing rod",
46 | "due": "tomorrow",
47 | "done": false,
48 | "id": "687c5467-8ec9-45a6-a4b0-88a5caea195c"
49 | },
50 | {
51 | "what": "promote principles",
52 | "due": "tomorrow",
53 | "done": false,
54 | "id": "9119ec04-748c-4a23-95b6-352a6e2379ba"
55 | },
56 | {
57 | "what": "try to tweet distant relatives",
58 | "due": "tomorrow",
59 | "done": false,
60 | "id": "ca5fc106-89e3-4a75-8ef6-2ce67fde5f2f"
61 | },
62 | {
63 | "what": "pick bird",
64 | "due": "tomorrow",
65 | "done": false,
66 | "id": "55149f97-d2e9-4bf6-a3f2-115880c50c76"
67 | },
68 | {
69 | "what": "add bird",
70 | "due": "tomorrow",
71 | "done": false,
72 | "id": "97a31a80-cf42-4970-9cc3-b1f67da623a3"
73 | },
74 | {
75 | "what": "skip exercising needle work",
76 | "due": "tomorrow",
77 | "done": false,
78 | "id": "860423a6-8fc4-4129-8059-97102f9f2132"
79 | },
80 | {
81 | "what": "buy distant relatives",
82 | "due": "tomorrow",
83 | "done": false,
84 | "id": "7e39613a-fd6a-49b2-900b-cc63151be19f"
85 | },
86 | {
87 | "what": "promote Italian",
88 | "due": "tomorrow",
89 | "done": false,
90 | "id": "111b7758-1dfc-414f-bf22-0fc138312089"
91 | },
92 | {
93 | "what": "try to code laptop",
94 | "due": "tomorrow",
95 | "done": false,
96 | "id": "36f8afa1-831b-4361-bd8f-0ca9569c1c94"
97 | },
98 | {
99 | "what": "fix chess",
100 | "due": "tomorrow",
101 | "done": false,
102 | "id": "4b77d626-40ba-43f6-80dc-ddf5aca037b6"
103 | },
104 | {
105 | "what": "try to code bird",
106 | "due": "tomorrow",
107 | "done": false,
108 | "id": "411dae7a-35e4-4a01-8a0f-46a15fec9445"
109 | },
110 | {
111 | "what": "make distant relatives",
112 | "due": "tomorrow",
113 | "done": false,
114 | "id": "30364285-b2ad-4b57-aade-3bfe2e5f45a5"
115 | },
116 | {
117 | "what": "pretend to buy fines",
118 | "due": "tomorrow",
119 | "done": false,
120 | "id": "3d9cf7df-73ca-4fa9-8ca2-b1c992ad9a1e"
121 | },
122 | {
123 | "what": "make Node.js",
124 | "due": "tomorrow",
125 | "done": false,
126 | "id": "f9b60833-ff75-41c4-a38e-1159f37ee687"
127 | },
128 | {
129 | "what": "make castle",
130 | "due": "tomorrow",
131 | "done": false,
132 | "id": "78afe710-2473-4f4d-89c2-1b230d84c364"
133 | },
134 | {
135 | "what": "avoid crashing boots",
136 | "due": "tomorrow",
137 | "done": false,
138 | "id": "12e16af5-498b-41a3-ad99-d27f271ee732"
139 | },
140 | {
141 | "what": "submit books",
142 | "due": "tomorrow",
143 | "done": false,
144 | "id": "b4280a63-5c90-4ddc-83d4-4c11a3e36c5b"
145 | },
146 | {
147 | "what": "skip avoiding milk",
148 | "due": "tomorrow",
149 | "done": false,
150 | "id": "d62cb8f3-a5d4-4851-a578-90672ba7f541"
151 | },
152 | {
153 | "what": "pretend to clean distant relatives",
154 | "due": "tomorrow",
155 | "done": false,
156 | "id": "368f499c-69fc-42a0-97ac-f2920d5b222b"
157 | },
158 | {
159 | "what": "make fines",
160 | "due": "tomorrow",
161 | "done": false,
162 | "id": "7dd2f859-3e98-4b9d-9b75-d204fa111d2e"
163 | },
164 | {
165 | "what": "skip throwing milk",
166 | "due": "tomorrow",
167 | "done": false,
168 | "id": "0f790690-a29d-416e-8a70-bd498f5697c5"
169 | },
170 | {
171 | "what": "promote boots",
172 | "due": "tomorrow",
173 | "done": false,
174 | "id": "bca35e5f-c758-4c0f-8b08-bdf0b4bd2524"
175 | },
176 | {
177 | "what": "play knife",
178 | "due": "tomorrow",
179 | "done": false,
180 | "id": "a02292d9-74a0-4a13-a798-79bc55e2153f"
181 | },
182 | {
183 | "what": "try to avoid adults",
184 | "due": "tomorrow",
185 | "done": false,
186 | "id": "3ca10aa8-3f90-4b9a-bf5b-7959b108a4c6"
187 | },
188 | {
189 | "what": "tweet fines",
190 | "due": "tomorrow",
191 | "done": false,
192 | "id": "cc07b3e2-f734-4f6a-8317-1d314d27d816"
193 | },
194 | {
195 | "what": "avoid chess",
196 | "due": "tomorrow",
197 | "done": false,
198 | "id": "50f66997-e070-45d8-b388-8dc4a4c7d739"
199 | },
200 | {
201 | "what": "forget adults",
202 | "due": "tomorrow",
203 | "done": false,
204 | "id": "2d8dac09-c602-4caa-b13a-21fb4bf5a8bd"
205 | },
206 | {
207 | "what": "pick distant relatives",
208 | "due": "tomorrow",
209 | "done": false,
210 | "id": "3fa0f111-0b93-4f38-821b-1b6bcf7e9e8a"
211 | },
212 | {
213 | "what": "help code knife",
214 | "due": "tomorrow",
215 | "done": false,
216 | "id": "1f93ce72-20c4-454e-bac1-ae44124d321d"
217 | },
218 | {
219 | "what": "crash distant relatives",
220 | "due": "tomorrow",
221 | "done": false,
222 | "id": "d636e89a-a5a1-405d-8b64-0898eff6f489"
223 | },
224 | {
225 | "what": "try to forget principles",
226 | "due": "tomorrow",
227 | "done": false,
228 | "id": "ae761cb2-6e1d-4188-be9e-5a375e54097e"
229 | },
230 | {
231 | "what": "promote Italian",
232 | "due": "tomorrow",
233 | "done": false,
234 | "id": "c9a36858-acd4-4273-9554-057b7cc75c8c"
235 | },
236 | {
237 | "what": "avoid making distant relatives",
238 | "due": "tomorrow",
239 | "done": false,
240 | "id": "bac8d783-5c57-4819-a27c-b823991ee188"
241 | },
242 | {
243 | "what": "pretend to exercise charges",
244 | "due": "tomorrow",
245 | "done": false,
246 | "id": "8408dcc7-a8d8-4a9b-b7a4-c1b85622c50a"
247 | },
248 | {
249 | "what": "help do castle",
250 | "due": "tomorrow",
251 | "done": false,
252 | "id": "0260a1b5-84e4-4ad5-8a1f-fda53eace79c"
253 | },
254 | {
255 | "what": "tweet Node.js",
256 | "due": "tomorrow",
257 | "done": false,
258 | "id": "83872342-eaf0-4c5e-b55a-bd2fcafa927a"
259 | },
260 | {
261 | "what": "pretend to find fines",
262 | "due": "tomorrow",
263 | "done": false,
264 | "id": "76227a1c-ba09-4b7f-8800-0ba652ffeb07"
265 | },
266 | {
267 | "what": "make Node.js",
268 | "due": "tomorrow",
269 | "done": false,
270 | "id": "8424e9f6-675a-459d-b044-74085a43c223"
271 | },
272 | {
273 | "what": "pretend to buy fishing rod",
274 | "due": "tomorrow",
275 | "done": false,
276 | "id": "785f758c-44e3-445a-897d-fd6b865b40a7"
277 | },
278 | {
279 | "what": "avoid milk",
280 | "due": "tomorrow",
281 | "done": false,
282 | "id": "f91d84a6-d555-42a1-b51d-aca1a200e939"
283 | },
284 | {
285 | "what": "fix chess",
286 | "due": "tomorrow",
287 | "done": false,
288 | "id": "830325d9-cd54-4031-806c-c431eecc6fe6"
289 | },
290 | {
291 | "what": "pretend to buy Node.js",
292 | "due": "tomorrow",
293 | "done": false,
294 | "id": "3d74ca36-8b11-471d-9cb0-5c75de529095"
295 | },
296 | {
297 | "what": "try to tweet milk",
298 | "due": "tomorrow",
299 | "done": false,
300 | "id": "05eca89c-9c07-419a-9849-d0bb877448eb"
301 | },
302 | {
303 | "what": "help promote principles",
304 | "due": "tomorrow",
305 | "done": false,
306 | "id": "1c5d10eb-c792-4106-8be2-d89dda1aa75d"
307 | },
308 | {
309 | "what": "try to promote fines",
310 | "due": "tomorrow",
311 | "done": false,
312 | "id": "ae76add3-4ee6-48e1-bdf4-a9cb336b5bfe"
313 | },
314 | {
315 | "what": "try to play adults",
316 | "due": "tomorrow",
317 | "done": false,
318 | "id": "972bc87c-0437-4d27-aa6a-2d808268e717"
319 | },
320 | {
321 | "what": "fix adults",
322 | "due": "tomorrow",
323 | "done": false,
324 | "id": "270bdc2c-781f-4f71-bb91-83fc11426623"
325 | },
326 | {
327 | "what": "promote boots",
328 | "due": "tomorrow",
329 | "done": false,
330 | "id": "be153dff-fa1f-463e-83ec-48daf1391e70"
331 | },
332 | {
333 | "what": "submit knife",
334 | "due": "tomorrow",
335 | "done": false,
336 | "id": "c40a01ba-4bbe-497e-a058-b444dfc1f012"
337 | },
338 | {
339 | "what": "fix needle work",
340 | "due": "tomorrow",
341 | "done": false,
342 | "id": "5b800671-644c-4616-93f1-ab6f75502f9f"
343 | },
344 | {
345 | "what": "make laptop",
346 | "due": "tomorrow",
347 | "done": false,
348 | "id": "6d3360bd-07d1-4db1-b8f8-aa4399ca9c28"
349 | },
350 | {
351 | "what": "play charges",
352 | "due": "tomorrow",
353 | "done": false,
354 | "id": "6fd5180a-f354-4d86-9036-4ae459bffacf"
355 | },
356 | {
357 | "what": "play Italian",
358 | "due": "tomorrow",
359 | "done": false,
360 | "id": "1faa6017-cbd8-4ba9-a5d3-47e5bd40b50d"
361 | },
362 | {
363 | "what": "skip cleaning bird",
364 | "due": "tomorrow",
365 | "done": false,
366 | "id": "50f58690-a02d-4936-b073-a6feb814c950"
367 | },
368 | {
369 | "what": "pick boots",
370 | "due": "tomorrow",
371 | "done": false,
372 | "id": "038c1d0f-59cb-463c-8764-801670f6d556"
373 | },
374 | {
375 | "what": "pretend to skip bird",
376 | "due": "tomorrow",
377 | "done": false,
378 | "id": "8fbf1e0c-a195-40e9-b0ee-0d67d5ed3a12"
379 | },
380 | {
381 | "what": "pretend to throw boots",
382 | "due": "tomorrow",
383 | "done": false,
384 | "id": "e56b4fcc-65e2-497d-af8d-59727d7f525b"
385 | },
386 | {
387 | "what": "clean Node.js",
388 | "due": "tomorrow",
389 | "done": false,
390 | "id": "f8d78d57-10fa-4565-9c1f-7ba435f800b1"
391 | },
392 | {
393 | "what": "code castle",
394 | "due": "tomorrow",
395 | "done": false,
396 | "id": "32296241-9e13-4b5b-858a-52be96778377"
397 | },
398 | {
399 | "what": "crash castle",
400 | "due": "tomorrow",
401 | "done": false,
402 | "id": "e36011dd-100c-4db2-8808-55fe4091ae00"
403 | },
404 | {
405 | "what": "try to buy adults",
406 | "due": "tomorrow",
407 | "done": false,
408 | "id": "1b29b9f0-5f3c-42d6-937d-6f688dba6661"
409 | },
410 | {
411 | "what": "pretend to make laptop",
412 | "due": "tomorrow",
413 | "done": false,
414 | "id": "1da7d7b1-152f-461f-8072-2b84290490db"
415 | },
416 | {
417 | "what": "avoid castle",
418 | "due": "tomorrow",
419 | "done": false,
420 | "id": "20217340-ecc5-40ba-aac3-3b1385ca0681"
421 | },
422 | {
423 | "what": "try to fix Node.js",
424 | "due": "tomorrow",
425 | "done": false,
426 | "id": "373b895d-8662-4d09-9da0-0a6d5d9ff115"
427 | },
428 | {
429 | "what": "avoid throwing charges",
430 | "due": "tomorrow",
431 | "done": false,
432 | "id": "362f3256-295d-4baa-9821-4e5ece50f154"
433 | },
434 | {
435 | "what": "try to tweet fishing rod",
436 | "due": "tomorrow",
437 | "done": false,
438 | "id": "9e721b88-58b0-4055-b216-7be02e6f8470"
439 | },
440 | {
441 | "what": "add castle",
442 | "due": "tomorrow",
443 | "done": false,
444 | "id": "6ecd397f-34bb-4622-8eb3-a4d9058cbdf8"
445 | },
446 | {
447 | "what": "throw laptop",
448 | "due": "tomorrow",
449 | "done": false,
450 | "id": "e4e8ab3f-3ede-4b31-b00b-a61abd730b84"
451 | },
452 | {
453 | "what": "try to learn charges",
454 | "due": "tomorrow",
455 | "done": false,
456 | "id": "49384468-e893-4ada-8b1f-530776038946"
457 | },
458 | {
459 | "what": "try to skip charges",
460 | "due": "tomorrow",
461 | "done": false,
462 | "id": "24a037d1-5136-4f72-9242-0c133bb5d5f3"
463 | },
464 | {
465 | "what": "sell books",
466 | "due": "tomorrow",
467 | "done": false,
468 | "id": "076aba82-a0b5-45a5-959b-a9fca4799763"
469 | },
470 | {
471 | "what": "throw knife",
472 | "due": "tomorrow",
473 | "done": false,
474 | "id": "7c3bcfcf-18b4-41a4-9f4a-9ad74905d282"
475 | },
476 | {
477 | "what": "help throw Node.js",
478 | "due": "tomorrow",
479 | "done": false,
480 | "id": "67f8be37-3555-415c-9f34-0dd4b77e5481"
481 | },
482 | {
483 | "what": "try to find chess",
484 | "due": "tomorrow",
485 | "done": false,
486 | "id": "9d62e22d-ea24-4e4a-a80c-fad106d16d4f"
487 | },
488 | {
489 | "what": "try to add fishing rod",
490 | "due": "tomorrow",
491 | "done": false,
492 | "id": "ead32ebd-a5f7-4d2e-8e7a-5b822838133d"
493 | },
494 | {
495 | "what": "skip finding Italian",
496 | "due": "tomorrow",
497 | "done": false,
498 | "id": "4c4bdaa2-3864-4e40-800b-6059d117dcd8"
499 | },
500 | {
501 | "what": "avoid needle work",
502 | "due": "tomorrow",
503 | "done": false,
504 | "id": "ca87cbb2-fe5f-4a72-bf4f-dcc7016d9c46"
505 | },
506 | {
507 | "what": "pretend to code boots",
508 | "due": "tomorrow",
509 | "done": false,
510 | "id": "21cb6d6c-d9c8-4fb0-b661-20a6dcbfeaf4"
511 | },
512 | {
513 | "what": "forget adults",
514 | "due": "tomorrow",
515 | "done": false,
516 | "id": "4a565cff-f3a2-4956-9e01-b83a6739b9d9"
517 | },
518 | {
519 | "what": "promote boots",
520 | "due": "tomorrow",
521 | "done": false,
522 | "id": "a0a258d8-24e7-4c76-9c2e-d1cca2ceaa1e"
523 | },
524 | {
525 | "what": "avoid playing adults",
526 | "due": "tomorrow",
527 | "done": false,
528 | "id": "d4010122-4800-4e97-a739-f37f1fc38cfa"
529 | },
530 | {
531 | "what": "make milk",
532 | "due": "tomorrow",
533 | "done": false,
534 | "id": "16ecb5c5-14fd-4554-98c2-07f53ae9c827"
535 | },
536 | {
537 | "what": "fix chess",
538 | "due": "tomorrow",
539 | "done": false,
540 | "id": "a6c1ba29-b142-4bea-9e63-02484fc7a3d9"
541 | },
542 | {
543 | "what": "throw knife",
544 | "due": "tomorrow",
545 | "done": false,
546 | "id": "dc77f679-1848-4a89-84cb-53aa377ac0da"
547 | },
548 | {
549 | "what": "add Italian",
550 | "due": "tomorrow",
551 | "done": false,
552 | "id": "190f0191-af71-4fac-a467-58f276c14739"
553 | },
554 | {
555 | "what": "pretend to code laptop",
556 | "due": "tomorrow",
557 | "done": false,
558 | "id": "73ad3df5-530c-4327-a0d2-296d5d1a8388"
559 | },
560 | {
561 | "what": "try to avoid distant relatives",
562 | "due": "tomorrow",
563 | "done": false,
564 | "id": "8bd52ae0-0aac-48ea-b474-19951eb59166"
565 | },
566 | {
567 | "what": "help learn boots",
568 | "due": "tomorrow",
569 | "done": false,
570 | "id": "d2eec216-285f-4e44-8c4c-6248592583f9"
571 | },
572 | {
573 | "what": "add needle work",
574 | "due": "tomorrow",
575 | "done": false,
576 | "id": "42beb520-cacd-4b66-b740-85f2c83ab7c9"
577 | },
578 | {
579 | "what": "help forget laptop",
580 | "due": "tomorrow",
581 | "done": false,
582 | "id": "18d31c9b-f2f9-4538-89ad-d0f3766a9830"
583 | },
584 | {
585 | "what": "code fishing rod",
586 | "due": "tomorrow",
587 | "done": false,
588 | "id": "4a254ad7-ab70-4257-bc59-c0f2b5e61dae"
589 | },
590 | {
591 | "what": "try to promote laptop",
592 | "due": "tomorrow",
593 | "done": false,
594 | "id": "82e54f4b-0507-4127-90a1-ab3a919790d7"
595 | },
596 | {
597 | "what": "exercise laptop",
598 | "due": "tomorrow",
599 | "done": false,
600 | "id": "2cecf0c2-d0b1-4cf6-9ee2-d3ac87e18284"
601 | }
602 | ]
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Instant TodoMVC
7 |
8 |
9 |
10 |
11 |
21 |
22 |
23 |
339 |
340 |
343 |
344 |
345 |
346 |
347 |
348 |
--------------------------------------------------------------------------------