mirror of
https://github.com/wassname/HackFlowy.git
synced 2026-07-07 00:06:38 +08:00
Merge branch 'master' into gh-pages
Conflicts: javascripts/app.js
This commit is contained in:
+4
-1
@@ -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;
|
||||
}));
|
||||
+2858
File diff suppressed because it is too large
Load Diff
+13
-4
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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'));
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user