Added url to sub-lists

This commit is contained in:
2016-02-01 16:37:05 +08:00
parent 855fbb1a3f
commit 4cde8f6fe2
10 changed files with 329 additions and 173 deletions
+3
View File
@@ -23,5 +23,8 @@
"normalize-css": "normalize.css#~3.0.3",
"backbone.marionette": "~2.4.4",
"underscore": "~1.8.3"
},
"resolutions": {
"jquery": "^1.8.0 || ^2.0.0"
}
}
+31 -25
View File
@@ -10,6 +10,8 @@
<title>HackFlowy</title>
<link rel="icon" type="image/x-icon" href="images/favicon.png" />
<link rel="stylesheet" href="bower_components/normalize-css/normalize.css" />
<link rel="stylesheet" href="bower_components/foundation-sites/dist/foundation.css" />
<link rel="stylesheet" href="bower_components/foundation-icon-fonts/foundation-icons.css" />
<link rel="stylesheet" href="stylesheets/app.css" />
</head>
@@ -17,7 +19,11 @@
<div class="row">
<div class="large-12 columns">
<section id="hackflowy">
<header id="header"></header>
<header id="header">
<div id="header-alerts"></div>
<div id="task-breadcrumbs"></div>
</header>
<section id="main">
@@ -28,37 +34,37 @@
</ul>
</section>
<footer id="footer">
<span id="footer-qoute" onclick="$('#footer-qoute-more').toggle(); $('#footer-qoute-less').toggle()">Make love. Catch fish</span><span id="footer-qoute-more" style="display: none;"></span><span id="footer-qoute-less" style="display: inline;"></span>
<div class="large-10 columns" >
<span id="footer-qoute">Make love. Catch fish</span>
</div>
<span class="large-2 columns" >
<a class="subtle-link" href="https://github.com/abhshkdz/HackFlowy">
<i class="fi-heart"></i> open source
</a>
</span>
<script>
// into the footer, a random qoute about lists which was originally about love
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 Llist 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 list is the one who can thrill you by kissing your forehead or smiling into your eyes or just staring into space - Marilyn Monroe.",
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.",
"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 be your friend was all I ever wanted; to be in your list was all I ever dreamed. - V. 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",
"I list you as one lists certain dark things, secretly, between the shadow and the soul - P. Neruda.",
"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."
"When a list is not madness it is not a list. - P. Calderón de la Barca."
]
var newQoute = qoutes[Math.floor(Math.random() * qoutes.length)] + ' <3 Open Source';
if (newQoute.length>100){
document.getElementById("footer-qoute").textContent = newQoute.slice(0,120);
document.getElementById("footer-qoute").title=newQoute;
document.getElementById("footer-qoute-more").textContent = newQoute.slice(120);
document.getElementById("footer-qoute-less").textContent = '...';
} else {
document.getElementById("footer-qoute").textContent = newQoute;
}
var newQoute = qoutes[Math.floor(Math.random() * qoutes.length)];
document.getElementById("footer-qoute").textContent = newQoute;
</script>
</footer>
</section>
+1 -1
View File
@@ -28,5 +28,5 @@ require([
'views/page'
],
function (App) {
new App();
pageView = new App();
});
+14 -4
View File
@@ -24,20 +24,30 @@ localforageBackbone
this.on('add remove', this.updateModelPriority);
},
/**
* Move a model in the list up. Only a sort event is emitted
* @param {Backbone.Model} model - Model to be moved
*/
moveUp: function(model) { // I see move up as the -1
var index = this.indexOf(model);
if (index > 0) {
this.remove(model, {silent: true}); // silence this to stop excess event triggers
this.add(model, {at: index-1});
this.remove(model, {silent: true});
this.add(model, {at: index-1, silent: true});
}
this.trigger('sort', this, {});
},
/**
* Move a model in the list up. Only a sort event is emitted
* @param {Backbone.Model} model - Model to be moved
*/
moveDown: function(model) { // I see move up as the -1
var index = this.indexOf(model);
if (index < this.models.length) {
this.remove(model, {silent: true}); // silence this to stop excess event triggers
this.add(model, {at: index+1});
this.remove(model, {silent: true});
this.add(model, {at: index+1, silent: true});
}
this.trigger('sort', this, {});
},
/** Updated priority of each member of list **/
+44 -51
View File
@@ -3,7 +3,6 @@ define(
'backbone',
'collections/list',
'views/task',
'data/demo',
'text!../../templates/task.html',
'marionette'
],
@@ -13,7 +12,6 @@ define(
Backbone,
List,
TaskView,
demoData,
listTemplate,
Marionette
) {
@@ -27,59 +25,53 @@ define(
template: _.template(listTemplate),
events: {
'click #add': 'addTask'
'click #add': 'addTask',
},
initialize: function () {
var self = this;
// this wholeCollection holds all items
this.wholeCollection = Tasks = new List();
//this.collection = new List();
// custom events
this.listenTo(this, 'childview:rerender', this.render);
// this.listenTo(this.collection, 'add remove', this.render);
/** Load demo data **/
function loadDemoData() {
for (var i = 0; i < demoData.length; i++) {
var task = Tasks.add(demoData[i]);
task.save();
}
}
function success(children, data, promise) {
// load demo data if the server returns nothing
var directChildren = children.filter(this.filterDirectChildren);
if (directChildren.length === 0)
loadDemoData();
this.collection = new List(Tasks.filter(this.filterDirectChildren));
this.render();
}
Tasks.fetch({
success: success,
error: function () {
// switch to localforage database if server isn't present and fetch again
// from there
window.hackflowyOffline = true;
$('#header').append('<div class="alert-box secondary round">Running in offline mode, data may be lost </div>');
Tasks.fetch({
success: success,
context: this
});
},
context: this
});
},
/** Get root id for the displayed list from the url hash or 0 **/
// getRootId: function(){
// var hash = window.location.hash.slice(1);
// return hash ? hash: 0;
//
// },
// /** Called when window location hash changes **/
// changeRootId: function(){
// this.collection.remove(this.collection.models);
// this.collection.add(pageView.collection.filter(this.filterDirectChildren,this));
// this.updateBreadCrumbs();
// },
// updateBreadCrumbs: function(){
// var rootId = this.getRootId();
// if (rootId){
// var current = Tasks.get(rootId);
// var breadCrumbs = _.template('<a href="#<%= id %>"><%= content %></a>')(current.attributes);
// var depth=0;
// while (current.get('parentId') && depth<100){
// depth++;
// current = Tasks.get(current.get('parentId'));
// breadCrumbs=_.template('<a href="#<%= id %>"><%= content %></a> > ')(current.attributes)+breadCrumbs;
// }
// if (depth>=100) console.error('Max depth exceeded while making breadCrumbs');
// breadCrumbs='<a href="#">Home</a> > '+breadCrumbs;
// $('#task-breadcrumbs').append(breadCrumbs);
//
// }
// },
// Only show direct children
filterDirectChildren: function (child, index, collection) {
return child.get('parentId') === 0;
},
// filterDirectChildren: function (child, index, collection) {
// var rootId = this.getRootId();
// if (rootId)
// return child.get('id') == rootId;
// else
// return child.get('parentId') == rootId;
// },
/** This is the root view in the tree **/
getParentView: function () {
@@ -88,8 +80,9 @@ define(
/** Update parentId when added to collection **/
onAddChild: function(childView){
if (childView.model.get('parentId')!==0)
childView.model.save({parentId: 0});
var rootId = pageView.getRootId();
if (childView.model.get('parentId')!=rootId && childView.model==rootId)
childView.model.save({parentId: rootId});
},
});
+104 -15
View File
@@ -1,41 +1,130 @@
define(
['jquery',
'backbone',
'marionette',
'views/list',
'models/task'
'data/demo',
'models/task',
'collections/list'
],
function(
$,
Backbone,
Marionette,
ListView,
Task
demoData,
Task,
List
) {
var PageView = Backbone.View.extend({
var PageView = Marionette.View.extend({
el: $("#main"),
el: $("#hackflowy"),
events: {
'keypress #newTask': 'createNewTask',
'blur #newTask': 'createNewTask'
},
initialize: function() {
listView = this.listView = new ListView();
this.input = $('#newTask');
// this wholeCollection holds all items
this.collection = new List();
function success(children, data, promise) {
// load demo data if the server returns nothing
var directChildren = children.filter(this.filterDirectChildren,this);
if (directChildren.length === 0){
for (var i = 0; i < demoData.length; i++) {
var task = this.collection.add(demoData[i]);
task.save();
}
}
this.listView.collection.add(children.filter(this.filterDirectChildren,this));
this.listView.render();
this.updateBreadCrumbs();
}
this.collection.fetch({
success: success,
error: function () {
// switch to localforage database if server isn't present and fetch again
// from there
window.hackflowyOffline = true;
this.$('#header-alerts').append('<div class="alert-box secondary round">Running in offline mode, data may be lost </div>');
this.collection.fetch({
success: success,
context: this
});
},
context: this
});
var rootItems = new List(this.collection.filter(this.filterDirectChildren,this));
this.listView = new ListView({collection: rootItems});
// change root item when the url hash changes
// HACK should ideally use http://stackoverflow.com/a/19114496/221742
$(window).on("hashchange", this.changeRootId.bind(this));
// stop browser going back a page when jamming backspace
window.addEventListener('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}},true);
$(window).on('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}});
},
createNewTask: function(e) {
if (e.keyCode != 13) return;
if (!this.input.val().trim()) return;
this.listView.collection.add(new Task({content: this.input.val().trim() }));
this.input.val('');
}
/** remove non backbone listener on delete **/
onDestroy: function() {
$(window).off("haschange",this.changeRootId);
$(window).off('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}});
},
/** Get root id for the displayed list from the url hash or 0 **/
getRootId: function(){
var hash = window.location.hash.slice(1);
if (hash && pageView.collection.get(hash))
return hash;
else if (hash)
window.location.hash='';
return 0;
},
/** Called when window location hash changes **/
changeRootId: function(){
// change the listview children
this.listView.collection.remove(this.collection.models);
this.listView.collection.add(this.collection.filter(this.filterDirectChildren,this));
this.updateBreadCrumbs();
},
// Only show direct children
filterDirectChildren: function (child, index, collection) {
var rootId = this.getRootId();
if (rootId)
return child.get('id') == rootId;
else
return child.get('parentId') == rootId;
},
updateBreadCrumbs: function(){
var rootId = this.getRootId();
if (rootId){
this.$('#task-breadcrumbs').empty();
var current = this.collection.get(rootId);
var breadCrumbs = '';//_.template('<a href="#<%= id %>"><%= content %></a>')(current.attributes);
var depth=0;
while (current.get('parentId') && depth<100){
depth++;
current = this.collection.get(current.get('parentId'));
breadCrumbs=_.template('<a href="#<%= id %>"><%= content %></a> > ')(current.attributes)+breadCrumbs;
}
if (depth>=100) console.error('Max depth exceeded while making breadCrumbs');
breadCrumbs='<a href="#">Home</a> > '+breadCrumbs;
this.$('#task-breadcrumbs').append(breadCrumbs);
}
},
});
+20 -14
View File
@@ -22,7 +22,8 @@ define(
template: _.template(taskTemplate), //'#task-view-template',
tagName: 'ul',
className: "shift",
className: "task-view",
viewComparator: List.prototype.comporator,
childView: TaskView,
childViewContainer: '.children',
childViewOptions: {
@@ -50,11 +51,14 @@ define(
'click .complete:first': 'markComplete',
'click .uncomplete:first': 'unmarkComlete',
'click .note:first': 'addNote',
'click .mouse-tip:first': 'foldChildren',
'click .fold-button:first': 'foldChildren',
// custom events
'focus': 'this.model.focusOnView',
},
collectionEvents: {},
collectionEvents: {
},
childEvents: {},
@@ -69,19 +73,23 @@ define(
// backlink
this.model.view = this;
var children = Tasks.filter(
var children = pageView.collection.filter(
function (child, index, collection) {
return child.get('parentId') === this.model.id;
}, this);
this.collection = new List(children);
// there is probobly a better way to do this
if (this.isEmpty()){
this.$el.addClass('empty');
}
// events
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
// refresh ui hashes after children are rendered
this.listenTo(this, 'render:collection', this.bindUIElements);
// custom event
this.listenTo(this, 'childview:rerender', this.render);
this.listenTo(this, 'focus', this.model.focusOnView);
// updates from server
@@ -113,9 +121,9 @@ define(
/** Get the parent view or root view **/
getParentView: function () {
var parent = Tasks.get(this.model.get('parentId'));
var parent = pageView.collection.get(this.model.get('parentId'));
if (parent) return parent.view;
else return listView;
else return pageView.listView;
},
/** Update parentId when added to collection **/
@@ -126,7 +134,7 @@ define(
/** Focus on the next visible element down despite list level **/
focusOnPrev: function () {
var all = listView.$el.find('ul:visible');
var all = pageView.listView.$el.find('ul:visible');
var prev = $(all[all.index(this.$el) - 1]);
if (prev.length){
prev.find('input:first').focus();
@@ -135,7 +143,7 @@ define(
},
focusOnNext: function () {
var all = listView.$el.find('ul:visible');
var all = pageView.listView.$el.find('ul:visible');
var next = $(all[all.index(this.$el) + 1]);
if (next)
next.find('input:first').focus();
@@ -169,13 +177,11 @@ define(
// move down list by swapping priority with next sibling
e.preventDefault();
this.getParentView().collection.moveDown(this.model);
this.trigger('rerender');
this.model.focusOnView();
} else if (e.ctrlKey && e.keyCode == constants.UP_ARROW) {
// move up the list
e.preventDefault();
this.getParentView().collection.moveUp(this.model);
this.trigger('rerender');
this.model.focusOnView();
} else if (e.keyCode == constants.DOWN_ARROW) {
this.focusOnNext();
@@ -188,7 +194,7 @@ define(
if (parentId===0){
} else if (parentId){
parentModel = Tasks.get(parentId);
parentModel = pageView.collection.get(parentId);
this.getParentView().collection.remove(this.model);
var index = parentModel.view.getParentView().collection.indexOf(parentModel);
if (index<0) index=this.getParentView().collection.length-1;
@@ -203,7 +209,7 @@ define(
var prevSibling = this.$el.prev('ul');
if (prevSibling.length) {
var prevSibId = prevSibling.find('input:first').data('id');
var prevSibView =Tasks.get(prevSibId).view;
var prevSibView =pageView.collection.get(prevSibId).view;
this.getParentView().collection.remove(this.model);
prevSibView.collection.add(this.model);
this.model.focusOnView();
@@ -269,7 +275,7 @@ define(
var index= this.getParentView().collection.indexOf(this.model);
var task = Tasks.add({parentId: this.model.get('parentId')});
var task = pageView.collection.add({parentId: this.model.get('parentId')});
task.save();
if (index>=0)
this.getParentView().collection.add(task, {at:index+1});
+3
View File
@@ -162,6 +162,9 @@ body {
li.shift9 {
margin-left: 270px;
}
ul.shift {
margin-bottom: 0px;
}
}
}
#main > .children {
+107 -62
View File
@@ -5681,9 +5681,6 @@ body #hackflowy #main .children li.shift8 {
body #hackflowy #main .children li.shift9 {
margin-left: 270px;
}
body #hackflowy #main .children ul.shift {
margin-bottom: 0px;
}
/* line 167, ../sass/app.scss */
body #hackflowy #main > .children {
margin-left: 0;
@@ -5695,54 +5692,58 @@ body #hackflowy #footer {
font-size: 12px;
}
body #hackflowy #main .children ul.task-view {
margin-bottom: 0px;
margin-left: 0px;
}
.options-div{
display: none;
}
.options{
position: absolute;
z-index: 1000;
color: rgb(51, 51, 51);
cursor: auto;
display: none;
font-family: 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
font-weight: normal;
height: 0px;
line-height: 16px;
width: 675px;
position: absolute;
z-index: 1000;
color: rgb(51, 51, 51);
cursor: auto;
display: none;
font-family: 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
font-weight: normal;
height: 0px;
line-height: 16px;
width: 675px;
}
.options a {
display: inline-block;
width: 20px;
height: 20px;
cursor: pointer;
display: inline-block;
width: 20px;
height: 20px;
cursor: pointer;
}
.options-left {
background: #ddd;
border: 1px solid #bbb;
width: 80px;
min-height: 58px;
padding: 10px 0 5px 10px;
margin-bottom: 10px;
border-radius: 4px;
font-family: 'Helvetica Neue', Arial, Sans-serif;
z-index: 8;
margin-top: 8px;
margin-left: -29px;
background: #ddd;
border: 1px solid #bbb;
width: 80px;
min-height: 58px;
padding: 10px 0 5px 10px;
margin-bottom: 10px;
border-radius: 4px;
font-family: 'Helvetica Neue', Arial, Sans-serif;
z-index: 8;
margin-top: 8px;
margin-left: -29px;
}
.options-left a {
color: #555;
margin-bottom: 5px;
font-size: 12px;
display: block;
margin-right: 10px;
width: auto;
color: #555;
margin-bottom: 5px;
font-size: 12px;
display: block;
margin-right: 10px;
width: auto;
}
.options-left a:hover {
@@ -5750,42 +5751,86 @@ width: auto;
}
.options-left > .mouse-tip {
content: '';
position: absolute;
left: 0;
height: 25px;
width: 90px;
margin-top: -36px;
margin-left: -1%;
content: '';
position: absolute;
left: 0;
height: 25px;
width: 90px;
margin-top: -36px;
margin-left: -1%;
}
.options-left > .mouse-tip:before {
content: '';
position: absolute;
top: 10px;
left: 19%;
margin-left: -15px;
height: 0;
width: 0;
border-bottom: 15px solid #ddd;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
content: '';
position: absolute;
top: 10px;
left: 19%;
margin-left: -15px;
height: 0;
width: 0;
border-bottom: 15px solid #ddd;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
}
.mouse-tip:before {
content: '';
position: absolute;
top: 10px;
left: 50%;
margin-left: -15px;
height: 0;
width: 0;
border-bottom: 15px solid #ddd;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
content: '';
position: absolute;
top: 10px;
left: 50%;
margin-left: -15px;
height: 0;
width: 0;
border-bottom: 15px solid #ddd;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
}
.options-left > .fold-button {
position: absolute;
left: 0;
/*height: 25px;*/
/*width: 90px;*/
margin-top: -36px;
margin-left: -3%;
font-size: 1.5em;
}
.options-left > .fold-button:hover {
text-decoration: none;
}
/* By default show fold button */
.options-left>.fold-button:before {
content: "-";
}
/* no fold button on empty nodes */
.empty .options-left:first-child > .fold-button:before {
content: '';
}
/* expand button on folded nodes */
.folded .options-left:first-child>.fold-button:before {
content: "+";
}
.task-completed{
text-decoration: line-through;
}
.subtle-link {
color: rgb(51, 51, 51);
}
.subtle-link:hover {
color: rgb(51, 51, 51);
text-decoration: underline;
}
#task-breadcrumbs {
font-size: smaller;
}
#task-breadcrumbs a {
color: rgb(51, 51, 51);
}
#task-breadcrumbs a:hover {
color: rgb(51, 51, 51);
}
+2 -1
View File
@@ -4,7 +4,8 @@
<div class="link">
<div class="options">
<div class="options-left">
<div class="mouse-tip"></div>
<a class="fold-button" title="Expand/collapse children"></a>
<a class="mouse-tip" href="#<%= obj.id %>"></a>
<% if(isCompleted){ %>
<a class="uncomplete">Uncomplete<hr></a>
<% }else { %>