add keyboard shortcuts and make todos selectable useing j and k

This commit is contained in:
Reinier Balt 2013-07-01 15:56:54 +02:00
parent b725b7b137
commit db29b84f69
17 changed files with 217 additions and 99 deletions

View file

@ -7,7 +7,7 @@ GIT
GIT
remote: git://github.com/seyhunak/twitter-bootstrap-rails.git
revision: dfc45d21fac4178240bc3f6ec037969b49fa54c2
revision: 9a0a31096ecee572638132d5ed94bc5500f74330
branch: master
specs:
twitter-bootstrap-rails (2.2.7)
@ -205,7 +205,7 @@ GEM
will_paginate (3.0.4)
xpath (2.0.0)
nokogiri (~> 1.3)
yard (0.8.6.1)
yard (0.8.6.2)
PLATFORMS
ruby

View file

@ -41,8 +41,11 @@ $ ->
mouseTrapRails.toggleHints() if mouseTrapRails.showOnLoad
# HELP
Mousetrap.bind '?', -> $('div#tracks-shortcuts-dialog').modal()
Mousetrap.bind 'a', -> $('div#tracks-add-action-dialog').modal()
# ADD: a is bound in navbar
# GO TO
# Mousetrap.bind 'g h', TracksApp.go_home
Mousetrap.bind 'g c', -> alert("go context")
@ -50,6 +53,11 @@ $ ->
Mousetrap.bind 'g t', -> alert("go tag")
Mousetrap.bind 'g p', -> alert("go project")
# Mousetrap.bind 'g P', TracksApp.go_projects
# VIEW
Mousetrap.bind 'v p', -> alert("group by project")
Mousetrap.bind 'v c', -> alert("group by context")
# Item Selection
Mousetrap.bind 'j', -> TracksApp.selectNext()
Mousetrap.bind 'k', -> TracksApp.selectPrev()

View file

@ -3,4 +3,68 @@
# goto_page: (page) -> window.location.href = page
# go_home: this.goto_page "/"
# go_contexts: this.goto_page "/contexts"
# go_projects: this.goto_page "/projects"
# go_projects: this.goto_page "/projects"
TracksApp =
currentPosition: 0
updateCurrentPosition: ->
this.currentPosition = 0
$("div.todo-item").each ->
if $(this).hasClass("selected-item")
return false
else
this.currentPosition++
selectTodo: (new_todo) ->
$("div.todo-item.selected-item").removeClass("selected-item")
new_todo.addClass("selected-item")
TracksApp.updateCurrentPosition()
selectPrevNext: (go_next) ->
current = prev = next = null
stop = false
$("div.todo-item").each ->
if stop
next = $(this)
return false
prev = current
current = $(this)
if $(this).hasClass("selected-item")
stop = true
if go_next
TracksApp.selectTodo(prev) if prev?
return prev
else
TracksApp.selectTodo(next) if next?
return next
selectPrev: ->
unless TracksApp.selectPrevNext(true)?
TracksApp.selectTodo($("div.todo-item").last())
selectNext: ->
unless TracksApp.selectPrevNext(false)?
TracksApp.selectTodo($("div.todo-item").first())
# Make TracksApp globally accessible. From http://stackoverflow.com/questions/4214731/coffeescript-global-variables
root = exports ? this
root.TracksApp = TracksApp
$ ->
$("a#menu-keyboard-shotcuts").click -> $('div#tracks-shortcuts-dialog').modal()
$("a.button-add-todo").click -> $('div#tracks-add-action-dialog').modal()
$("i.icon-book").click ->
notes_id = $( this ).attr("data-note-id")
notes_div = $("div#" + notes_id )
notes_div.toggleClass("hide")
todo_item = $(this).parent().parent().parent().parent()
TracksApp.selectTodo(todo_item)
$("div.todo-item-description-container").click ->
TracksApp.selectTodo( $(this).parent().parent().parent() )

View file

@ -16,6 +16,9 @@ div.tracks-middle {
.navbar-inner {
border-radius: none;
div.btn-toolbar {
margin: 0px 15px 0px 0px;
}
}
div#tracks-login-navbar {
@ -42,16 +45,15 @@ span.badge_count {
footer {
margin-top: 50px;
text-align: center;
background-color: #000;
background-image: linear-gradient(to bottom, #FFFFFF, #F2F2F2);
background-color: #DDD;
}
/* Todo */
div.todo-item {
margin-top: 7px;
margin-left: 0px;
border: 3px solid #EEE;
border-width: 0px 0px 1px;
border-width: 0px 0px 1px 0px;
padding: 0px 3px 0px 3px;
min-height: none;
line-height: none;
@ -59,6 +61,9 @@ div.todo-item {
min-height: 0px;
}
div.row {
margin-left: 0px;
}
i.icon-check-empty {
margin-right: 10px;
}
@ -84,6 +89,23 @@ div.todo-item {
display: inline-block;
float:left;
}
div.todo-notes {
background-color: #EEE;
border-radius: 3px;
padding: 10px;
margin: 0px -3px 0px -3px;
}
}
div.selected-item {
border: 3px solid #AAA;
border-radius: 3px;
.row {
font-weight: bold;
}
.todo-notes {
font-weight: normal;
}
}
span.tags {
@ -112,4 +134,8 @@ div.todos-container {
color: #444;
}
}
}
div.hide_me {
display: none;
}

View file

@ -2,26 +2,13 @@ module TodosHelper
# === helpers for rendering container
def empty_message_holder(container_name, show, title_param=nil)
content_tag(:div, :id => "no_todos_in_view", :class => "container #{container_name}", :style => "display:" + (show ? "block" : "none") ) do
content_tag(:h4) { t("todos.no_actions.title", :param=>title_param) } +
content_tag(:div, :class => "message") do
content_tag(:p) { t("todos.no_actions.#{container_name}", :param=>title_param) }
end
end
end
def todos_container_empty_message(container_name, container_id, show_message)
render partial: "todos/container_empty_message", locals:
{container_id: container_id, container_name: container_name, show_message: show_message}
end
def show_grouped_todos(settings = {})
collection = (@group_view_by == 'context') ? @contexts_to_show : @projects_to_show
render(:partial => collection, :locals => { :settings => settings.reverse_merge!({
:collapsible => true,
:show_empty_containers => @show_empty_containers,
:parent_container_type => @group_view_by
:parent_container_type => @group_view_by,
:show_container => !collection.empty? || settings[:show_empty_containers],
})})
end
@ -86,32 +73,24 @@ module TodosHelper
}
end
def todos_container(settings={})
def todos_container_settings(settings={})
settings.reverse_merge!({
:id => "#{settings[:container_name]}-container",
:class => "todos-container #{settings[:container_name]}",
:title => t("todos.actions.#{settings[:parent_container_type]}_#{settings[:container_name]}", :param => settings[:title_param])
})
if settings[:collapsible]
settings[:class] += " collapsible"
end
content_tag(:div,
:class=>settings[:class],
:id=>settings[:id],
:style => "display:" + (settings[:show_container] ? "block" : "none")) do
yield
end
return settings
end
def todos_container_header(settings={})
settings.reverse_merge!({
:title => t("todos.actions.#{settings[:parent_container_type]}_#{settings[:container_name]}", :param => settings[:title_param])
})
header = settings[:link_in_header].nil? ? "" : content_tag(:div, :class=>"add_note_link"){settings[:link_in_header]}
header += content_tag(:h4) do
toggle = ""
# toggle = settings[:collapsible] ? container_toggle("toggle_#{settings[:id]}") : ""
# TODO: toggle = settings[:collapsible] ? container_toggle("toggle_#{settings[:id]}") : ""
"#{toggle} #{settings[:title]} #{settings[:append_descriptor]}".html_safe
end
header.html_safe
@ -126,7 +105,7 @@ module TodosHelper
div_id: settings[:id]+"_items",
container_name: settings[:container_name],
todo_id: settings[:id],
hide_empty_message: collection.empty?,
show_empty_message: !collection.empty?,
collection: collection,
settings: settings
}

View file

@ -1,3 +1,6 @@
<%# Template Dependency: todos/collection -%>
<%# Template Dependency: contexts/context -%>
<%# Template Dependency: projects/project -%>
<%
suffix_completed = t('contexts.last_completed_in_context', :number=>prefs.show_number_completed)
deferred_pending_options = {:append_descriptor => nil, :parent_container_type => 'context'}
@ -5,7 +8,9 @@
show_empty_containers = (@group_view_by == 'context')
-%>
<%= empty_message_holder("not_done_context", @not_done_todos.empty?) %>
<% cache("not_done_context", @not_done_todos.empty?) do -%>
<%= render partial: "todos/empty_message_container", locals: {:show => @not_done_todos.empty?, :container_name => "not_done"} %>
<% end -%>
<%= show_grouped_todos({:collapsible => false, :show_empty_containers => show_empty_containers, :parent_container_type => 'context'}) %>

View file

@ -6,37 +6,32 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= content_for?(:title) ? yield(:title) : @page_title %></title>
<%= csrf_meta_tags %>
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.6.1/html5shiv.js" type="text/javascript"></script>
<![endif]-->
<%= stylesheet_link_tag "application", :media => "all" %>
<!-- For third-generation iPad with high-resolution Retina display: -->
<!-- Size should be 144 x 144 pixels -->
<%= favicon_link_tag 'apple-touch-icon-144x144-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '144x144' %>
<!-- For iPhone with high-resolution Retina display: -->
<!-- Size should be 114 x 114 pixels -->
<%= favicon_link_tag 'apple-touch-icon-114x114-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '114x114' %>
<!-- For first- and second-generation iPad: -->
<!-- Size should be 72 x 72 pixels -->
<%= favicon_link_tag 'apple-touch-icon-72x72-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '72x72' %>
<!-- For non-Retina iPhone, iPod Touch, and Android 2.1+ devices: -->
<!-- Size should be 57 x 57 pixels -->
<%= favicon_link_tag 'apple-touch-icon-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png' %>
<!-- For all other devices -->
<!-- Size should be 32 x 32 pixels -->
<%= favicon_link_tag 'favicon.ico', :rel => 'shortcut icon' %>
</head>
<body class="<%= controller.controller_name %>">
<%= render partial: "shared/navbar" %>
<% cache do -%>
<%= render partial: "shared/navbar" %>
<% end -%>
<div class="container-fluid">
<div class="row-fluid">
@ -47,10 +42,12 @@
</div>
</div>
<%= render partial: "shared/footer" %>
<% # dialogs -%>
<%= render partial: "shared/keyboard_shortcuts" %>
<%= render partial: "shared/add_new_action" %>
<% cache do -%>
<%= render partial: "shared/footer" %>
<% # dialogs -%>
<%= render partial: "shared/keyboard_shortcuts" %>
<%= render partial: "shared/add_new_action" %>
<% end -%>
<%
# Javascripts
# ==================================================

View file

@ -11,4 +11,4 @@
</small></p>
</div>
</div>
</footer>
</footer>

View file

@ -51,20 +51,25 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><%= t('layouts.navigation.help') %><b class="caret"></b></a>
<ul class="dropdown-menu">
<li><%= menu_item("keyboard_shortcuts", "#") %></li>
<li><%= menu_item("integrations", integrations_path) %></li>
<li><%= menu_item("api_docs", rest_api_docs_path) %></li>
<li><%= menu_item("keyboard_shortcuts", "#",{:id => "menu-keyboard-shotcuts"}) %></li>
<li><%= menu_item("integrations", integrations_path) %></li>
<li><%= menu_item("api_docs", rest_api_docs_path) %></li>
</ul>
</li>
</ul>
<ul class="nav pull-right">
<li>
<div class="btn-toolbar">
<a class="btn btn-primary btn-small button-add-todo" title="Add a new action" data-keybinding="a"><i class="icon-plus icon-large"></i></a>
</div>
</li>
<li>
<form class="navbar-search pull-right">
<input type="text" class="search-query" placeholder="Search">
</form>
</li>
<li><%= link_to("#{t('common.logout')} (#{current_user.display_name})".html_safe, logout_path) %>
<li><%= link_to("<i class='icon-signout'></i>".html_safe, logout_path, title: "#{t('common.logout')} (#{current_user.display_name})") %></li>
</ul>
</div>

View file

@ -1,8 +1,9 @@
<%=
settings[:show_container] = !collection.empty? || settings[:show_empty_containers]
todos_container(settings) do
todos_container_header(settings) +
todos_container_items(collection, settings)
end
%>
<%
# Template Dependency: todos/container_items
settings = todos_container_settings(settings)
settings[:class] += " hide" if collection.empty? || settings[:show_container]
-%>
<div id="<%=settings[:id]%>" class="<%=settings[:class]%>">
<%= todos_container_header(settings) %>
<%= todos_container_items(collection, settings) %>
</div>

View file

@ -1,4 +1,4 @@
<div id="<%=container_id%>-empty-d" style="display:<%= show_message ? 'block' : 'none'%>">
<div id="<%=container_id%>-empty-d" class="<%= show_empty_message ? '' : 'hide'%>">
<div class="message">
<p><%=t("todos.no_actions.#{container_name}")%></p>
</div>

View file

@ -1,4 +1,11 @@
<div id="<%=div_id%>" class="todo_items toggle_target">
<%= todos_container_empty_message(container_name, todo_id, hide_empty_message) %>
<% cache container_name, show_empty_message do %>
<%= render partial: "todos/container_empty_message", locals: {
container_id: settings[:container_id],
container_name: settings[:container_name],
show_empty_message: settings[:show_empty_message]} -%>
<% end -%>
<%= render(:partial => "todos/todo", :collection => collection, :locals => settings) %>
</div>

View file

@ -0,0 +1,10 @@
<%
title_param ||= ""
hidden_class = show ? "" : " hide"
%>
<div id="no_todos_in_view" class="container <%=container_name + hidden_class%>">
<h4><%= t("todos.no_actions.title", :param=>title_param) %></h4>
<div class="message">
<p><%= t("todos.no_actions.#{container_name}", :param=>title_param)%></p>
</div>
</div>

View file

@ -1,23 +1,32 @@
<%
suppress_context ||= false
suppress_project ||= false
cache [todo, current_user.date.strftime("%Y%m%d"), @source_view, current_user.prefs.verbose_action_descriptors] do
-%>
<div id="<%= dom_id(todo) %>" class="row todo-item">
<div class="span8">
<div class="todo-item-icons">
<i class="<%= todo.completed? ? "icon-check-sign" : "icon-check-empty"%>"></i>
<i class="<%= todo.starred? ? "icon-star" : "icon-star-empty"%>"></i>
<div id="<%= dom_id(todo) %>" class="todo-item">
<div class="row">
<div class="span8">
<div class="todo-item-icons">
<i class="<%= todo.completed? ? "icon-check-sign" : "icon-check-empty"%>"></i>
<i class="<%= todo.starred? ? "icon-star" : "icon-star-empty"%>"></i>
</div>
<div class="todo-item-description-container">
<span class="todo-description"><%= todo.description %></span>
<%= content_tag(:i, {class: "icon-refresh"}){} if todo.from_recurring_todo? -%>
<%= deferred_due_date(todo) -%>
<%= content_tag(:i, {class: "icon-sitemap"}){} if todo.has_pending_successors -%>
<%= content_tag(:i, {class: "icon-book", "data-note-id" => dom_id(todo, 'notes')}){} unless todo.notes.blank? %>
</div>
</div>
<div class="span4 pull-right">
<%= date_span(todo) -%>
<%= tag_list(todo) %>
</div>
</div>
<div class="todo-item-description-container">
<span class="todo-description"><%= todo.description %></span>
<%= content_tag(:i, {class: "icon-refresh"}){} if todo.from_recurring_todo? %>
<%= deferred_due_date(todo) %>
<%= content_tag(:i, {class: "icon-play-sign"}){} if todo.has_pending_successors %>
<%= content_tag(:i, {class: "icon-book"}){} unless todo.notes.blank? %>
<div class="row">
<div id="<%=dom_id(todo, 'notes')%>" class="todo-notes hide">
<%=todo.rendered_notes.html_safe%>
</div>
</div>
</div>
<div class="span4 pull-right">
<%= date_span(todo) -%>
<%= tag_list(todo) %>
</div>
</div>
<% end -%>

View file

@ -1,11 +1,14 @@
<div id="display_box">
<%= empty_message_holder("not_done", @not_done_todos.empty?) %>
<%# Template Dependency: todos/collection -%>
<%# Template Dependency: contexts/context -%>
<%# Template Dependency: projects/project -%>
<% cache("not_done", @not_done_todos.empty?) do -%>
<%= render partial: "empty_message_container", locals: {:show => @not_done_todos.empty?, :container_name => "not_done"} %>
<% end -%>
<%= show_grouped_todos %>
<%= show_grouped_todos %>
<% if @group_view_by == 'project' -%>
<%= show_todos_without_project(@todos_without_project) -%>
<% end -%>
<% if @group_view_by == 'project' -%>
<%= show_todos_without_project(@todos_without_project) -%>
<% end -%>
<%= show_done_todos(@done, {:parent_container_type => @group_view_by, :collapsible => true}) unless @done.nil? %>
</div>
<%= show_done_todos(@done, {:parent_container_type => @group_view_by, :collapsible => true}) unless @done.nil? %>

View file

@ -1,4 +1,7 @@
<%
# Template Dependency: todos/collection
# Template Dependency: contexts/context
# Template Dependency: projects/project
options = {
:collapsible => false,
:parent_container_type => 'tag',
@ -8,19 +11,18 @@
hidden_options = options.clone
done_options = options.clone
-%>
<div id="display_box">
<%= empty_message_holder("not_done_with_tag", @not_done_todos.empty?, @tag_name) %>
<% cache("not_done_with_tag_#{@tag_name}", @not_done_todos.empty?) do -%>
<%= render partial: "empty_message_container", locals: {:show => @not_done_todos.empty?, :container_name => "not_done_with_tag"} %>
<% end -%>
<%= show_grouped_todos %>
<%= show_grouped_todos %>
<% if @group_view_by == 'project' -%>
<%= show_todos_without_project(@todos_without_project) -%>
<% end -%>
<% if @group_view_by == 'project' -%>
<%= show_todos_without_project(@todos_without_project) -%>
<% end -%>
<%= show_deferred_pending_todos(@deferred_todos, @pending_todos, deferred_pending_options) %>
<%= show_deferred_pending_todos(@deferred_todos, @pending_todos, deferred_pending_options) %>
<%= show_hidden_todos(@hidden_todos, hidden_options) unless @hidden_todos.nil? %>
<%= show_hidden_todos(@hidden_todos, hidden_options) unless @hidden_todos.nil? %>
<%= show_done_todos(@done, done_options) unless @done.nil? %>
</div>
<%= show_done_todos(@done, done_options) unless @done.nil? %>

View file

@ -421,6 +421,8 @@ en:
show_empty_containers_project: "Show empty projects"
show_empty_containers_context: "Show empty contexts"
show_empty_containers_title: "Show or hide the empty projects or contexts"
keyboard_shortcuts: Keyboard shortcuts
keyboard_shortcuts_title: Show the keyboard shortcuts you can use
footer:
send_feedback: Send feedback on %{version}
sidebar: