Merge branch 'master' into gh-pages

Conflicts:
	javascripts/app.js
This commit is contained in:
2016-01-29 22:56:24 +08:00
9 changed files with 3157 additions and 33 deletions
+4 -1
View File
@@ -15,10 +15,13 @@
"text": "requirejs-text#~2.0.14",
"jquery": "~2.2.0",
"backbone": "~1.2.3",
"backbone.localStorage": "1.1.16",
"lodash": "~4.0.1",
"requirejs": "~2.1.22",
"underscore": "~1.8.3",
"modernizr": "~3.3.1",
"zepto": "~1.1.6"
"zepto": "~1.1.6",
"localforage": "~1.3.3",
"localforage-backbone": "~0.6.2"
}
}
@@ -0,0 +1,207 @@
/*!
localForage Backbone Adapter
Version 0.6.2
https://github.com/mozilla/localforage-backbone
(c) 2014 Mozilla, Apache License 2.0
*/
// backbone.localforage allows users of Backbone.js to store their collections
// entirely offline with no communication to a REST server. It uses whatever
// driver localForage is set to use to store the data (IndexedDB, WebSQL, or
// localStorage, depending on availability). This allows apps on Chrome,
// Firefox, IE, and Safari to use async, offline storage, which is cool.
//
// The basics of how to use this library is that it lets you override the
// `sync` method on your collections and models to use localForage. So
//
// var MyModel = Backbone.Model.extend({})
// var MyCollection = Backbone.Collection.extend({
// model: MyModel
// });
//
// becomes
//
// var MyModel = Backbone.Collection.extend({
// sync: Backbone.localforage.sync('ModelNamespace')
// });
// var MyCollection = Backbone.Collection.extend({
// model: MyModel,
// sync: Backbone.localforage.sync('MyCollection')
// });
//
// Inspiration for this file comes from a few backbone.localstorage
// implementations.
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['localforage', 'backbone', 'underscore'], factory);
} else if (typeof module !== 'undefined' && module.exports) {
var localforage = require('localforage');
var Backbone = require('backbone');
var _ = require('underscore');
module.exports = factory(localforage, Backbone, _);
} else {
factory(root.localforage, root.Backbone, root._);
}
}(this, function(localforage, Backbone, _) {
function S4() {
// jshint -W016
return ((1 + Math.random()) * 65536 | 0).toString(16).substring(1);
// jshint +W016
}
function guid() {
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
}
function updateCollectionReferences(collection, callback, err, data) {
// If this model has a collection, keep the collection in =
// sync as well.
if (collection) {
// Create an array of `model.collection` models' ids.
var collectionData = collection.map(function(model) {
return collection.model.prototype.sync._localforageNamespace + '/' + model.id;
});
// Bind `data` to `callback` to call after
// `model.collection` models' ids are persisted.
callback = callback ? _.partial(callback, err, data) : undefined;
if (!collection.sync.localforageKey) {
localforageKey(collection);
}
// Persist `model.collection` models' ids.
localforage.setItem(collection.sync.localforageKey, collectionData, callback);
}
}
function localforageKey(model) {
// If `this` is a `Backbone.Collection` it means
// `Backbone.Collection#fetch` has been called.
if (model instanceof Backbone.Collection) {
model.sync.localforageKey = model.sync._localforageNamespace;
} else { // `this` is a `Backbone.Model` if not a `Backbone.Collection`.
// Generate an id if one is not set yet.
if (!model.id) {
model[model.idAttribute] = model.attributes[model.idAttribute] = guid();
}
model.sync.localforageKey = model.sync._localforageNamespace + '/' + model.id;
}
}
// For now, we aren't complicated: just set a property off Backbone to
// serve as our export point.
Backbone.localforage = {
localforageInstance: localforage,
sync: function(name) {
var self = this;
var sync = function(method, model, options) {
localforageKey(model);
switch (method) {
case 'read':
return model.id ? self.find(model, options) : self.findAll(model, options);
case 'create':
return self.create(model, options);
case 'update':
return self.update(model, options);
case 'delete':
return self.destroy(model, options);
}
};
// This needs to be exposed for later usage, but it's private to
// the adapter.
sync._localforageNamespace = name;
// expose function used to create the localeForage key
// this enable to have the key set before sync is called
sync._localeForageKeyFn = localforageKey;
return sync;
},
save: function(model, callback) {
localforage.setItem(model.sync.localforageKey, model.toJSON(), function(err, data) {
// keep the collection in sync
if (model.collection) {
updateCollectionReferences(model.collection, callback, err, data);
} else if (callback) {
callback(data);
}
});
},
create: function(model, callbacks) {
// We always have an ID available by this point, so we just call
// the update method.
return this.update(model, callbacks);
},
update: function(model, callbacks) {
this.save(model, function(data) {
if (callbacks.success) {
callbacks.success(data);
}
});
},
find: function(model, callbacks) {
localforage.getItem(model.sync.localforageKey, function(err, data) {
if (!err && !_.isEmpty(data)) {
if (callbacks.success) {
callbacks.success(data);
}
} else if (callbacks.error) {
callbacks.error();
}
});
},
// Only used by `Backbone.Collection#sync`.
findAll: function(collection, callbacks) {
localforage.getItem(collection.sync.localforageKey, function(err, data) {
if (!err && data && data.length) {
var done = function() {
if (callbacks.success) {
callbacks.success(data);
}
};
// Only execute `done` after getting all of the
// collection's models.
done = _.after(data.length, done);
var onModel = function(i, err, model) {
data[i] = model;
done();
};
for (var i = 0; i < data.length; ++i) {
localforage.getItem(data[i], _.partial(onModel, i));
}
} else {
data = [];
if (callbacks.success) {
callbacks.success(data);
}
}
});
},
destroy: function(model, callbacks) {
var collection = model.collection;
localforage.removeItem(model.sync.localforageKey, function() {
// keep the collection in sync
if (collection) {
updateCollectionReferences(collection, callbacks.success, null, model.toJSON());
} else if (callbacks.success) {
callbacks.success(model.toJSON());
}
});
}
};
return Backbone.localforage;
}));
File diff suppressed because it is too large Load Diff
+13 -4
View File
@@ -31,20 +31,29 @@
<footer id="footer">
<div id="footer-qoute">Make love. Catch fish</div>
<script>
// insert random qoute about lists which should be about love
// into the footer, a random qoute about lists which was originally about love
// TODO(wassname) make attributation a link or mouseover
var qoutes = ["Lists cannot be sold only given", "Make lists. Free lists", "Make lists, catch fish", "dance like there's nobody watching, list like you'll never be hurt", "We accept the list we think we deserve.",
"A friend is someone who knows all about you and still lists you.", "Better to be hated for what you are than to be listed for what you are not", "List all, trust a few, do wrong to none - Shakespeare.",
"There is never a time or place for a true list. It happens accidentally, in a heartbeat, in a single flashing, throbbing moment - Sarah Dessen.",
"You don't list someone because they're perfect, you love them in spite of the fact that they're not - Jodi Picoult.",
"A List never dies a natural death. It dies because we don't know how to replenish its source. It dies of blindness and errors and betrayals. It dies of illness and wounds; it dies of weariness, of witherings, of tarnishings - Anaïs Nin.",
"The real lover is the man who can thrill you by kissing your forehead or smiling into your eyes or just staring into space - Marilyn Monroe."
"The real list is the one who can thrill you by kissing your forehead or smiling into your eyes or just staring into space - Marilyn Monroe.",
"I list you and that's the beginning and end of everything - F. Scott Fitzgerald.",
"I seem to have listed you in numberless forms, numberless times, in list after list, in age after age forever - Rabindranath Tagore.",
"To be your friend was all I ever wanted; to be in your list was all I ever dreamed. - Valerie Lombardo.",
"To the list, you may be one item, but to one item you are the list - Bill Wilson.",
"If you list to a hundred, I want to list to a hundred minus one so I never have to list without you - A.A. Milne.",
"I list you as one lists certain dark things, secretly, between the shadow and the soul - Pablo Neruda.",
"Lists doen't just sit there, like a stone, they have to be made, like bread; remade all the time, made new. - Ursula K. Le Guin",
"It was list at first sight, at last sight, at ever and ever sight. - Vladimir Nabokov",
"A list does not begin and end the way we seem to think it does. A list is a battle, a list is a war; a list is a growing up. - James Baldwin",
"When a list is not madness it is not a list. - Pedro Calderón de la Barca."
]
var elem = document.getElementById("footer-qoute");
var newQoute = qoutes[Math.floor(Math.random() * qoutes.length)] + ' <3 Open Source.';
//if (newQoute.length>100){
// TODO(wassname) truncate long qoutes
// TODO(wassname) truncate long qoutes with a more link
//}
elem.textContent = newQoute
</script>
+2
View File
@@ -5,6 +5,8 @@ require.config({
jquery: '../bower_components/jquery/dist/jquery.min',
lodash: "../bower_components/lodash/dist/lodash.min",
backbone: '../bower_components/backbone/backbone-min',
localforage: '../bower_components/localforage/dist/localforage',
localforagebackbone: '../bower_components/localforage-backbone/dist/localforage.backbone',
modernizr: "vendor/custom.modernizr",
socket: "../bower_components/socket.io-client/socket.io",
text: '../bower_components/text/text',
+18 -4
View File
@@ -1,21 +1,35 @@
define(
[
'backbone',
'models/task'
'models/task',
'localforage',
'localforagebackbone'
],
function(
Backbone,
Task
Task,
localforage,
localforageBackbone
) {
var List = Backbone.Collection.extend({
model: Task,
offlineSync: Backbone.localforage.sync("tasks"),
/** switches sync between server and local databases **/
sync: function(){
if (window.hackflowyOffline)
return this.offlineSync.apply(this, arguments);
else
return Backbone.sync.apply(this, arguments);
},
url: '/tasks'
});
return List;
});
});
+16 -3
View File
@@ -1,13 +1,26 @@
define(
['backbone'
['backbone',
'localforage',
'localforagebackbone'
],
function(
Backbone
Backbone,
localforage,
localforageBackbone
) {
var TaskModel = Backbone.Model.extend({
offlineSync: Backbone.localforage.sync('TaskModel'),
/** switches sync between server and local databases **/
sync: function(){
if (window.hackflowyOffline)
return this.offlineSync.apply(this,arguments);
else
return Backbone.sync.apply(this, arguments);
},
defaults: {
parentId: 0,
content: '',
@@ -25,7 +38,7 @@ Backbone
//REVERT BACK ON ERROR
self.set({'isCompleted':prev_isCompleted});
}
})
});
}
});
+38 -21
View File
@@ -1,17 +1,17 @@
var demoData = [{"id":80,"content":"Welcome to HackFlowy!","parentId":0,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:44:30.858Z","updatedAt":"2016-01-29T05:44:30.858Z"},{"id":81,"content":"An open-source WorkFlowy clone","parentId":0,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:44:30.858Z","updatedAt":"2016-01-29T05:44:30.858Z"},{"id":82,"content":"Built using Backbone + Socket.IO","parentId":0,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:44:30.858Z","updatedAt":"2016-01-29T05:44:30.858Z"},{"id":83,"content":"Desyncr pulled this together in a few hours to learn Backbone","parentId":0,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:44:30.858Z","updatedAt":"2016-01-29T05:44:30.858Z"},{"id":84,"content":"Feel free to try it out and hack on it","parentId":0,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:44:30.858Z","updatedAt":"2016-01-29T05:44:30.858Z"},{"id":85,"content":"Good Luck!","parentId":0,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:44:30.858Z","updatedAt":"2016-01-29T05:44:30.858Z"},{"id":86,"content":"P.S","parentId":0,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:44:40.978Z","updatedAt":"2016-01-29T05:44:40.978Z"},{"id":88,"content":"It makes sense if you don't think about it; I haven't","parentId":0,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:44:58.737Z","updatedAt":"2016-01-29T05:45:50.939Z"},{"id":89,"content":"Make love not war","parentId":88,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:45:03.048Z","updatedAt":"2016-01-29T05:45:57.481Z"},{"id":91,"content":"Love can be brought not sold","parentId":88,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:45:32.331Z","updatedAt":"2016-01-29T05:46:10.478Z"},{"id":93,"content":"How do I love thee? Let me count the ways - Shakespear","parentId":88,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:46:25.119Z","updatedAt":"2016-01-29T05:48:00.604Z"},{"id":95,"content":"Therefore: love can be listed and lists can be loved","parentId":88,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:46:38.998Z","updatedAt":"2016-01-29T05:48:22.937Z"},{"id":96,"content":"Conclusion: lists and love should be free","parentId":88,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:47:26.684Z","updatedAt":"2016-01-29T05:48:29.796Z"},{"id":97,"content":"But how can our lists be real if our love isnt? - Jaden Smith","parentId":88,"isCompleted":false,"priority":0,"createdAt":"2016-01-29T05:47:46.930Z","updatedAt":"2016-01-29T05:47:46.930Z"}];
define(
['jquery',
'backbone',
'collections/list',
'views/task'
'views/task',
'data/demo'
],
function (
$,
Backbone,
List,
TaskView
TaskView,
demoData
) {
var ListView = Backbone.View.extend({
@@ -24,24 +24,38 @@ define(
initialize: function () {
Tasks = this.collection = new List();
var fetchPromise = this.collection.fetch();
fetchPromise.fail(function (e) {
// if the server isn't running load some demo data and a demo warning
$('#header').append('<div class="alert-box secondary round">Warning: Running in demo mode, all work will be lost</div>');
var data = demoData;
for (var i = 0; i < data.length; i++) {
Tasks.add(data[i]);
}
}, this);
this.listenTo(this.collection, 'add', this.renderTask);
/** Load demo data and warn users **/
function loadDemoData() {
for (var i = 0; i < demoData.length; i++) {
var task = Tasks.add(demoData[i]);
task.save();
}
}
function success(data) {
// load demo data if the server returns nothing
if (data.length === 0)
loadDemoData();
}
this.collection.fetch({
success: success,
error: function () {
// switch to localforage database if server isn't present
window.hackflowyOffline=true;
$('#header').append('<div class="alert-box secondary round">Running in offline mode, data may be lost </div>');
Tasks.fetch({
success: success
});
}
});
},
render: function (data) {
for (var i = 0; i < data.length; i++) {
Tasks.add(data[i]);
}
render: function () {
this.collection.each(function (task) {
this.renderTask(task);
}, this);
@@ -55,10 +69,13 @@ define(
if (a.model.get('parentId') === 0) {
this.$el.append(a.el);
} else {
var parent = $('*[data-id="' + a.model.get('parentId')+ '"]');
if (parent.length===0) {
var parent = $('*[data-id="' + a.model.get('parentId') + '"]');
if (parent.length === 0) {
// TODO deal with loading order
console.error("Parent not rendered yet: ", {selector: parent.selector, task: task});
console.error("Parent not rendered yet: ", {
selector: parent.selector,
task: task
});
this.$el.append(a.el);
} else {
a.$el.insertBefore(parent.parents('li:first'));
+1
View File
@@ -127,6 +127,7 @@ define(
var value = this.$input.val().trim();
if (value === '') {
this.model.destroy();
// collection.at(this.model.get('id')).destroy();
} else {
this.model.save({
content: value,