Updated the documents, which are now in doc (CHANGELOG and README_FOR_APP).

Also removed the tabs from all the files, and replaced with spaces (2 per tab).


git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@102 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
bsag 2005-06-11 12:24:19 +00:00
parent 977ca82306
commit b4d72cf17a
29 changed files with 2694 additions and 2713 deletions

View file

@ -1,29 +0,0 @@
== Installation
Done generating the login system. but there are still a few things you have to do
manually. First open your application.rb and add
require_dependency "login_system"
to the top of the file and include the login system with
include LoginSystem
The beginning of your ApplicationController.
It should look something like this :
require_dependency "login_system"
class ApplicationController < ActionController::Base
include LoginSystem
After you have done the modifications the the AbstractController you can
import the user model into the database. This model is meant as an example
and you should extend it. If you just want to get things up and running you
can find some create table syntax in db/user_model.sql.
== Useage
Now you can go around and happily add "before_filter :login_required" to the controllers which you would like to protect.
If the user hits a controller with the login_required filter he will be redirected to the login page and redirected back after a successful login. You can find the login_system.rb in the lib/ directory. It comes with some comments which should help explain the general useage.

View file

@ -62,9 +62,10 @@ task :test_functional => [ :clone_structure_to_test ]
desc "Generate documentation for the application"
Rake::RDocTask.new("appdoc") { |rdoc|
rdoc.rdoc_dir = 'doc/app'
rdoc.title = "Rails Application Documentation"
rdoc.title = "Tracks Documentation"
rdoc.options << '--line-numbers --inline-source'
rdoc.rdoc_files.include('doc/README_FOR_APP')
rdoc.rdoc_files.include('doc/CHANGELOG')
rdoc.rdoc_files.include('app/**/*.rb')
}

View file

@ -9,13 +9,16 @@ class ApplicationController < ActionController::Base
helper :application
include LoginSystem
# Contstants from settings.yml
#
DATE_FORMAT = app_configurations["formats"]["date"]
WEEK_STARTS_ON = app_configurations["formats"]["week_starts"]
NO_OF_ACTIONS = app_configurations["formats"]["hp_completed"]
STALENESS_STARTS = app_configurations["formats"]["staleness_starts"]
# Count the number of uncompleted actions, excluding those in hidden contexts
#
def count_shown_items(hidden)
count = 0
sub = 0
@ -26,10 +29,13 @@ class ApplicationController < ActionController::Base
end
# Returns all the errors on the page for an object...
#
def errors_for( obj )
error_messages_for( obj ) unless instance_eval("@#{obj}").nil?
end
# Reverses the urlize() method by substituting underscores for spaces
#
def deurlize(name)
name.to_s.gsub(/_/, " ")
end

View file

@ -2,8 +2,8 @@ class ContextController < ApplicationController
helper :context
model :project
model :todo
model :todo
before_filter :login_required
layout "standard"
@ -16,160 +16,160 @@ class ContextController < ApplicationController
# Set page title, and collect existing contexts in @contexts
#
def list
@page_title = "TRACKS::List Contexts"
@contexts = Context.find(:all, :conditions => nil, :order => "position ASC", :limit => nil )
end
@page_title = "TRACKS::List Contexts"
@contexts = Context.find(:all, :conditions => nil, :order => "position ASC", :limit => nil )
end
# Filter the projects to show just the one passed in the URL
# e.g. <home>/project/show/<project_name> shows just <project_name>.
#
def show
@context = Context.find_by_name(deurlize(@params["name"]))
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
@page_title = "TRACKS::Context: #{@context.name}"
@not_done = Todo.find(:all, :conditions => "done=0 AND context_id=#{@context.id}",
:order => "due IS NULL, due ASC, created ASC")
@done = Todo.find(:all, :conditions => "done=1 AND context_id=#{@context.id}",
:order => "completed DESC")
@count = Todo.count( "context_id=#{@context.id} AND done=0" )
end
# Creates a new context via Ajax helpers
#
def new_context
@context = Context.new(@params['context'])
if @context.save
render_partial( 'context_listing', @context )
else
flash["warning"] = "Couldn't add new context"
render_text "#{flash["warning"]}"
end
end
# Edit the details of the context
#
def update
context = Context.find(params[:id])
context.attributes = @params["context"]
if context.save
render_partial 'context_listing', context
else
flash["warning"] = "Couldn't update new context"
render_text ""
end
end
# Edit the details of the action in this context
#
def update_action
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
action = Todo.find(params[:id])
action.attributes = @params["item"]
if action.due?
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
else
action.due = ""
end
if action.save
render_partial 'show_items', action
else
flash["warning"] = "Couldn't update the action"
render_text ""
end
end
# Called by a form button
# Parameters from form fields are passed to create new action
#
def add_item
@projects = Project.find( :all, :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
# Filter the projects to show just the one passed in the URL
# e.g. <home>/project/show/<project_name> shows just <project_name>.
#
def show
@context = Context.find_by_name(deurlize(@params["name"]))
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
@page_title = "TRACKS::Context: #{@context.name}"
@not_done = Todo.find(:all, :conditions => "done=0 AND context_id=#{@context.id}",
:order => "due IS NULL, due ASC, created ASC")
@done = Todo.find(:all, :conditions => "done=1 AND context_id=#{@context.id}",
:order => "completed DESC")
@count = Todo.count( "context_id=#{@context.id} AND done=0" )
end
# Creates a new context via Ajax helpers
#
def new_context
@context = Context.new(@params['context'])
if @context.save
render_partial( 'context_listing', @context )
else
flash["warning"] = "Couldn't add new context"
render_text "#{flash["warning"]}"
end
end
# Edit the details of the context
#
def update
context = Context.find(params[:id])
context.attributes = @params["context"]
if context.save
render_partial 'context_listing', context
else
flash["warning"] = "Couldn't update new context"
render_text ""
end
end
# Edit the details of the action in this context
#
def update_action
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
action = Todo.find(params[:id])
action.attributes = @params["item"]
if action.due?
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
else
action.due = ""
end
if action.save
render_partial 'show_items', action
else
flash["warning"] = "Couldn't update the action"
render_text ""
end
end
# Called by a form button
# Parameters from form fields are passed to create new action
#
def add_item
@projects = Project.find( :all, :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
item = Todo.new
item.attributes = @params["new_item"]
item = Todo.new
item.attributes = @params["new_item"]
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
if item.save
render_partial 'show_items', item
else
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
end
end
# Fairly self-explanatory; deletes the context
# If the context contains actions, you'll get a warning dialogue.
# If you choose to go ahead, any actions in the context will also be deleted.
def destroy
this_context = Context.find(params[:id])
if this_context.destroy
render_text ""
else
flash["warning"] = "Couldn't delete context \"#{context.name}\""
if item.save
render_partial 'show_items', item
else
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
end
end
# Fairly self-explanatory; deletes the context
# If the context contains actions, you'll get a warning dialogue.
# If you choose to go ahead, any actions in the context will also be deleted.
def destroy
this_context = Context.find(params[:id])
if this_context.destroy
render_text ""
else
flash["warning"] = "Couldn't delete context \"#{context.name}\""
redirect_to( :controller => "context", :action => "list" )
end
end
end
end
# Delete a next action in a context
#
def destroy_action
item = Todo.find(params[:id])
if item.destroy
render_text ""
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
redirect_to :action => "list"
end
end
# Delete a next action in a context
#
def destroy_action
item = Todo.find(params[:id])
if item.destroy
render_text ""
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
redirect_to :action => "list"
end
end
# Toggles the 'done' status of the action
#
def toggle_check
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
item = Todo.find(params[:id])
# Toggles the 'done' status of the action
#
def toggle_check
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
item = Todo.find(params[:id])
item.toggle!('done')
render_partial 'show_items', item
end
# Methods for changing the sort order of the contexts in the list
#
def move_up
line = Context.find(params[:id])
line.move_higher
line.save
redirect_to(:controller => "context", :action => "list")
end
def move_down
line = Context.find(params[:id])
line.move_lower
line.save
redirect_to(:controller => "context", :action => "list")
end
def move_top
line = Context.find(params[:id])
line.move_to_top
line.save
redirect_to(:controller => "context", :action => "list")
end
def move_bottom
line = Context.find(params[:id])
line.move_to_bottom
line.save
redirect_to(:controller => "context", :action => "list" )
end
item.toggle!('done')
render_partial 'show_items', item
end
# Methods for changing the sort order of the contexts in the list
#
def move_up
line = Context.find(params[:id])
line.move_higher
line.save
redirect_to(:controller => "context", :action => "list")
end
def move_down
line = Context.find(params[:id])
line.move_lower
line.save
redirect_to(:controller => "context", :action => "list")
end
def move_top
line = Context.find(params[:id])
line.move_to_top
line.save
redirect_to(:controller => "context", :action => "list")
end
def move_bottom
line = Context.find(params[:id])
line.move_to_bottom
line.save
redirect_to(:controller => "context", :action => "list" )
end
end

View file

@ -19,7 +19,7 @@ class FeedController < ApplicationController
@user_name = @params['name']
@current_user = User.find_by_login(@user_name)
if (@token == @current_user.word && @user_name == @current_user.login)
@not_done = Todo.find_all( "done=0", "created DESC" )
@not_done = Todo.find_all( "done=0", "created DESC" )
@headers["Content-Type"] = "text/xml; charset=utf-8"
else
render_text "Sorry, you don't have permission to view this page."

View file

@ -13,17 +13,17 @@ class ProjectController < ApplicationController
end
# Main method for listing projects
# Set page title, and collect existing projects in @projects
#
# Set page title, and collect existing projects in @projects
#
def list
@page_title = "TRACKS::List Projects"
@projects = Project.find(:all, :conditions => nil, :order => "position ASC")
end
# Filter the projects to show just the one passed in the URL
@page_title = "TRACKS::List Projects"
@projects = Project.find(:all, :conditions => nil, :order => "position ASC")
end
# Filter the projects to show just the one passed in the URL
# e.g. <home>/project/show/<project_name> shows just <project_name>.
#
def show
def show
@project = Project.find_by_name(deurlize(@params["name"]))
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
@ -33,143 +33,143 @@ class ProjectController < ApplicationController
@done = Todo.find(:all, :conditions => "done=1 AND project_id=#{@project.id}",
:order => "completed DESC")
@count = @not_done.length
end
def new_project
@project = Project.new(@params['project'])
if @project.save
render_partial( 'project_listing', @project )
else
flash["warning"] = "Couldn't update new project"
render_text ""
end
end
# Edit the details of the project
#
def update
project = Project.find(params[:id])
project.attributes = @params["project"]
if project.save
render_partial 'project_listing', project
else
flash["warning"] = "Couldn't update new project"
render_text ""
end
end
# Edit the details of the action in this project
#
def update_action
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
action = Todo.find(params[:id])
action.attributes = @params["item"]
if action.due?
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
else
action.due = ""
end
if action.save
render_partial 'show_items', action
else
flash["warning"] = "Couldn't update the action"
render_text ""
end
end
# Called by a form button
# Parameters from form fields are passed to create new action
#
def add_item
@projects = Project.find( :all, :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
item = Todo.new
item.attributes = @params["new_item"]
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
if item.save
render_partial 'show_items', item
else
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
end
end
# Delete a project
#
def destroy
this_project = Project.find( @params['id'] )
if this_project.destroy
render_text ""
else
flash["warning"] = "Couldn't delete project \"#{project.name}\""
redirect_to( :controller => "project", :action => "list" )
end
end
# Delete a next action in a project
#
def destroy_action
item = Todo.find(@params['id'])
if item.destroy
#flash["confirmation"] = "Next action \"#{item.description}\" was successfully deleted"
render_text ""
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
redirect_to :action => "list"
end
end
# Toggles the 'done' status of the action
#
def toggle_check
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
item = Todo.find(@params['id'])
item.toggle!('done')
render_partial 'show_items', item
end
end
# Methods for changing the sort order of the projects in the list
#
def move_up
line = Project.find(params[:id])
line.move_higher
line.save
redirect_to(:controller => "project", :action => "list")
end
def new_project
@project = Project.new(@params['project'])
if @project.save
render_partial( 'project_listing', @project )
else
flash["warning"] = "Couldn't update new project"
render_text ""
end
end
# Edit the details of the project
#
def update
project = Project.find(params[:id])
project.attributes = @params["project"]
if project.save
render_partial 'project_listing', project
else
flash["warning"] = "Couldn't update new project"
render_text ""
end
end
# Edit the details of the action in this project
#
def update_action
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
action = Todo.find(params[:id])
action.attributes = @params["item"]
if action.due?
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
else
action.due = ""
end
if action.save
render_partial 'show_items', action
else
flash["warning"] = "Couldn't update the action"
render_text ""
end
end
# Called by a form button
# Parameters from form fields are passed to create new action
#
def add_item
@projects = Project.find( :all, :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
def move_down
line = Project.find(params[:id])
line.move_lower
line.save
redirect_to(:controller => "project", :action => "list")
end
item = Todo.new
item.attributes = @params["new_item"]
def move_top
line = Project.find(params[:id])
line.move_to_top
line.save
redirect_to(:controller => "project", :action => "list")
end
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
def move_bottom
line = Project.find(params[:id])
line.move_to_bottom
line.save
redirect_to(:controller => "project", :action => "list" )
end
if item.save
render_partial 'show_items', item
else
flash["warning"] = "Couldn't add next action \"#{item.description}\""
render_text ""
end
end
# Delete a project
#
def destroy
this_project = Project.find( @params['id'] )
if this_project.destroy
render_text ""
else
flash["warning"] = "Couldn't delete project \"#{project.name}\""
redirect_to( :controller => "project", :action => "list" )
end
end
# Delete a next action in a project
#
def destroy_action
item = Todo.find(@params['id'])
if item.destroy
#flash["confirmation"] = "Next action \"#{item.description}\" was successfully deleted"
render_text ""
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
redirect_to :action => "list"
end
end
# Toggles the 'done' status of the action
#
def toggle_check
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
item = Todo.find(@params['id'])
item.toggle!('done')
render_partial 'show_items', item
end
# Methods for changing the sort order of the projects in the list
#
def move_up
line = Project.find(params[:id])
line.move_higher
line.save
redirect_to(:controller => "project", :action => "list")
end
def move_down
line = Project.find(params[:id])
line.move_lower
line.save
redirect_to(:controller => "project", :action => "list")
end
def move_top
line = Project.find(params[:id])
line.move_to_top
line.save
redirect_to(:controller => "project", :action => "list")
end
def move_bottom
line = Project.find(params[:id])
line.move_to_bottom
line.save
redirect_to(:controller => "project", :action => "list" )
end
end

View file

@ -3,7 +3,7 @@ class TodoController < ApplicationController
helper :todo
model :context, :project
before_filter :login_required
before_filter :login_required
layout "standard"
def index
@ -15,36 +15,36 @@ class TodoController < ApplicationController
# Set page title, and fill variables with contexts and done and not-done tasks
# Number of completed actions to show is determined by a setting in settings.yml
#
def list
@page_title = "TRACKS::List tasks"
@projects = Project.find( :all, :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
@shown_places = Context.find( :all, :conditions => "hide=0", :order => "position ASC" )
def list
@page_title = "TRACKS::List tasks"
@projects = Project.find( :all, :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
@shown_places = Context.find( :all, :conditions => "hide=0", :order => "position ASC" )
@hidden_places = Context.find( :all, :conditions => "hide=1", :order => "position ASC" )
@done = Todo.find( :all, :conditions => "done=1", :order => "completed DESC",
:limit => NO_OF_ACTIONS )
# Set count badge to number of not-done, not hidden context items
@count = count_shown_items( @hidden_places )
end
@done = Todo.find( :all, :conditions => "done=1", :order => "completed DESC",
:limit => NO_OF_ACTIONS )
# Set count badge to number of not-done, not hidden context items
@count = count_shown_items( @hidden_places )
end
# List the completed tasks, sorted by completion date
#
# Use days declaration? 1.day.ago?
# List the completed tasks, sorted by completion date
#
# Use days declaration? 1.day.ago?
def completed
@page_title = "TRACKS::Completed tasks"
today_date = Date::today() - 1
today_date = Date::today() - 1
today_query = today_date.strftime("'%Y-%m-%d'") + " <= completed"
week_begin = Date::today() - 2
week_end = Date::today() - 7
week_begin = Date::today() - 2
week_end = Date::today() - 7
week_query = week_begin.strftime("'%Y-%m-%d'") + " >= completed
AND " + week_end.strftime("'%Y-%m-%d'") + " <= completed"
month_begin = Date::today() - 8
month_end = Date::today() - 31
month_begin = Date::today() - 8
month_end = Date::today() - 31
month_query = month_begin.strftime("'%Y-%m-%d'") + " >= completed
AND " + month_end.strftime("'%Y-%m-%d'") + " <= completed"
@ -58,30 +58,30 @@ class TodoController < ApplicationController
end
# Archived completed items, older than 31 days
#
#
def completed_archive
@page_title = "TRACKS::Archived completed tasks"
archive_date = Date::today() - 32
archive_date = Date::today() - 32
archive_query = archive_date.strftime("'%Y-%m-%d'") + " >= completed"
@done_archive = Todo.find_by_sql( "SELECT * FROM todos WHERE done = 1 AND #{archive_query}
ORDER BY completed DESC;" )
end
# Called by a form button
# Parameters from form fields are passed to create new action
# in the selected context.
def add_item
@projects = Project.find( :all, :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
@places = Context.find( :all, :order => "position ASC" )
item = Todo.new
item.attributes = @params["new_item"]
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
item = Todo.new
item.attributes = @params["new_item"]
if item.due?
item.due = Date.strptime(@params["new_item"]["due"], DATE_FORMAT)
else
item.due = ""
end
if item.save
render_partial 'show_items', item
@ -90,20 +90,20 @@ class TodoController < ApplicationController
render_text ""
end
end
# Edit the details of an action
#
#
def update_action
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
action = Todo.find(params[:id])
@places = Context.find(:all, :order => "position ASC")
@projects = Project.find(:all, :order => "position ASC")
action = Todo.find(params[:id])
action.attributes = @params["item"]
if action.due?
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
action.due = Date.strptime(@params["item"]["due"], DATE_FORMAT)
else
action.due = ""
action.due = ""
end
if action.save
render_partial 'show_items', action
else
@ -111,29 +111,29 @@ class TodoController < ApplicationController
render_text ""
end
end
# Delete a next action in a context
#
def destroy_action
item = Todo.find(@params['id'])
if item.destroy
render_text ""
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
redirect_to :action => "list"
end
if item.destroy
render_text ""
else
flash["warning"] = "Couldn't delete next action \"#{item.description}\""
render_text ""
end
end
# Toggles the 'done' status of the action
#
def toggle_check
@projects = Project.find(:all, :order => "position ASC")
@places = Context.find(:all, :order => "position ASC")
# Toggles the 'done' status of the action
#
def toggle_check
@projects = Project.find(:all, :order => "position ASC")
@places = Context.find(:all, :order => "position ASC")
item = Todo.find(@params['id'])
item = Todo.find(@params['id'])
item.toggle!('done')
render_partial 'show_items', item
end
item.toggle!('done')
render_partial 'show_items', item
end
end

View file

@ -2,77 +2,78 @@
module ApplicationHelper
# Convert a date object to the format specified
# in config/settings.yml
#
# in config/settings.yml
#
def format_date(date)
if date
formatted_date = date.strftime("#{ApplicationController::DATE_FORMAT}")
else
formatted_date = ''
end
end
if date
formatted_date = date.strftime("#{ApplicationController::DATE_FORMAT}")
else
formatted_date = ''
end
end
# Uses RedCloth to transform text using either Textile or Markdown
# Need to require redcloth above
# RedCloth 3.0 or greater is needed to use Markdown, otherwise it only handles Textile
#
def markdown(text)
RedCloth.new(text).to_html
end
# Wraps object in HTML tags, tag
#
def tag_object(object, tag)
tagged = "<#{tag}>#{object}</#{tag}>"
end
# Uses RedCloth to transform text using either Textile or Markdown
# Need to require redcloth above
# RedCloth 3.0 or greater is needed to use Markdown, otherwise it only handles Textile
#
def markdown(text)
RedCloth.new(text).to_html
end
# Wraps object in HTML tags, tag
#
def tag_object(object, tag)
tagged = "<#{tag}>#{object}</#{tag}>"
end
# Converts names to URL-friendly format by substituting underscores for spaces
#
def urlize(name)
name.to_s.gsub(/ /, "_")
end
# Check due date in comparison to today's date
# Flag up date appropriately with a 'traffic light' colour code
#
# Use 2.days.until?
def due_date(due)
if due == nil
return ""
end
@now = Date.today
@days = due-@now
case @days
# overdue or due very soon! sound the alarm!
when -365..1
"<span class=\"red\">" + format_date(due) + "</span> "
# due 2-7 days away
when 2..7
"<span class=\"amber\">" + format_date(due) + "</span> "
# more than a week away - relax
else
"<span class=\"green\">" + format_date(due) + "</span> "
end
end
# Uses the 'staleness_starts' value from settings.yml (in days) to colour
# the background of the action appropriately according to the age
# of the creation date:
# * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts
# * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts
# * l3: created more than 3 x staleness_starts
#
def staleness(created)
if created < (ApplicationController::STALENESS_STARTS*3).days.ago
return "<div class=\"stale_l3\">"
elsif created < (ApplicationController::STALENESS_STARTS*2).days.ago
return "<div class=\"stale_l2\">"
elsif created < (ApplicationController::STALENESS_STARTS).days.ago
return "<div class=\"stale_l1\">"
else
return "<div class=\"description\">"
end
end
# Check due date in comparison to today's date
# Flag up date appropriately with a 'traffic light' colour code
#
def due_date(due)
if due == nil
return ""
end
@now = Date.today
@days = due-@now
case @days
# overdue or due very soon! sound the alarm!
when -365..1
"<span class=\"red\">" + format_date(due) + "</span> "
# due 2-7 days away
when 2..7
"<span class=\"amber\">" + format_date(due) + "</span> "
# more than a week away - relax
else
"<span class=\"green\">" + format_date(due) + "</span> "
end
end
# Uses the 'staleness_starts' value from settings.yml (in days) to colour
# the background of the action appropriately according to the age
# of the creation date:
# * l1: created more than 1 x staleness_starts, but < 2 x staleness_starts
# * l2: created more than 2 x staleness_starts, but < 3 x staleness_starts
# * l3: created more than 3 x staleness_starts
#
def staleness(created)
if created < (ApplicationController::STALENESS_STARTS*3).days.ago
return "<div class=\"stale_l3\">"
elsif created < (ApplicationController::STALENESS_STARTS*2).days.ago
return "<div class=\"stale_l2\">"
elsif created < (ApplicationController::STALENESS_STARTS).days.ago
return "<div class=\"stale_l1\">"
else
return "<div class=\"description\">"
end
end
end

View file

@ -10,25 +10,25 @@ module FeedHelper
for @place in @places
result_string << "\n" + @place.name.upcase + ":\n"
list.each do |@item|
if @item.context_id == @place.id
if @item.due
result_string << " [" + format_date(@item.due) + "] "
result_string << @item.description + " "
else
result_string << " " + @item.description + " "
end
if @item.project_id
result_string << "(" + @item.project['name'] + ")"
end
result_string << "\n"
end
end
end
return result_string
list.each do |@item|
if @item.context_id == @place.id
if @item.due
result_string << " [" + format_date(@item.due) + "] "
result_string << @item.description + " "
else
result_string << " " + @item.description + " "
end
if @item.project_id
result_string << "(" + @item.project['name'] + ")"
end
result_string << "\n"
end
end
end
return result_string
end
end

View file

@ -2,17 +2,17 @@ module LoginHelper
def render_errors(obj)
return "" unless obj
return "" unless @request.post?
tag = String.new
return "" unless obj
return "" unless @request.post?
tag = String.new
unless obj.valid?
tag << %{<ul class="objerrors">}
obj.errors.each_full { |message| tag << %{<li>#{message}</li>} }
tag << %{</ul>}
end
unless obj.valid?
tag << %{<ul class="objerrors">}
obj.errors.each_full { |message| tag << %{<li>#{message}</li>} }
tag << %{</ul>}
end
tag
end
end
end

View file

@ -1,9 +1,9 @@
module TodoHelper
# Counts the number of uncompleted items in the selected context
#
def count_items(context)
count = Todo.find_all("done=0 AND context_id=#{context.id}").length
end
# Counts the number of uncompleted items in the selected context
#
def count_items(context)
count = Todo.find_all("done=0 AND context_id=#{context.id}").length
end
end

View file

@ -1,26 +1,26 @@
class Todo < ActiveRecord::Base
belongs_to :context, :order => 'name'
belongs_to :project
# Description field can't be empty, and must be < 100 bytes
# Notes must be < 60,000 bytes (65,000 actually, but I'm being cautious)
validates_presence_of :description
validates_length_of :description, :maximum => 100
validates_length_of :notes, :maximum => 60000
# Add a creation date (Ruby object format) to item before it's saved
# if there is no existing creation date (this prevents creation date
# being reset to completion date when item is completed)
#
def before_save
if self.created == nil
self.created = Time.now()
end
if self.done == 1
self.completed = Time.now()
end
end
belongs_to :project
# Description field can't be empty, and must be < 100 bytes
# Notes must be < 60,000 bytes (65,000 actually, but I'm being cautious)
validates_presence_of :description
validates_length_of :description, :maximum => 100
validates_length_of :notes, :maximum => 60000
# Add a creation date (Ruby object format) to item before it's saved
# if there is no existing creation date (this prevents creation date
# being reset to completion date when item is completed)
#
def before_save
if self.created == nil
self.created = Time.now()
end
if self.done == 1
self.completed = Time.now()
end
end
end

View file

@ -1,4 +1,4 @@
<% item = show_items %>
<% item = show_items %>
<% if !item.done? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "context", :action => "toggle_check", :id => item.id ),
@ -16,7 +16,7 @@
:update => "item-#{item.id}-container",
:loading => "new Effect.Squish('item-#{item.id}-container')",
:url => { :controller => "context", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " " +
link_to_function(image_tag( "blank", :title => "Edit item", :class=>"edit_item"), "Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}');" ) + " "
link_to_function(image_tag( "blank", :title => "Edit item", :class=>"edit_item"), "Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}');" ) + " "
%>
<!-- begin div.checkbox -->
<div class="checkbox">
@ -30,17 +30,17 @@
<% else %>
<%= staleness(item.created) %>
<% end %>
<%= due_date( item.due ) %>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div>
<%= due_date( item.due ) %>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div>
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
@ -65,7 +65,7 @@
) %>
<div id="done-item-<%= item.id %>">
<div class="big-box">
<div class="big-box">
<%=
link_to_remote( image_tag("blank", :title =>"Delete this action", :class=>"delete_item"),
:update => "done-item-#{item.id}-container",
@ -79,18 +79,18 @@
<input type="checkbox" name="item_id" value="<%= item.id %>" checked="checked" onclick="document.forms['checkbox-done-<%= item.id %>'].onsubmit();" />
</div>
<!-- end div.checkbox -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div><!-- [end:description] -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div><!-- [end:description] -->
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
</div><!-- [end:item-item.id-container] -->

View file

@ -42,27 +42,27 @@
:loading => "context.reset()",
:complete => "Form.focusFirstElement('context-form-new-action');",
:html=> { :id=>'context-form-new-action', :name=>'context', :class => 'inline-form' } %>
<%= hidden_field( "new_item", "context_id", "value" => "#{@context.id}") %>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1 ) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_project_id">Project</label><br />
<select name="item[project_id]" id="item_project_id" tabindex="3">
<option selected="selected"></option>
<%= options_from_collection_for_select(@projects, "id", "name") %>
</select><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 4) %>
<br /><br />
<input type="submit" value="Add item" tabindex="5">
<%= end_form_tag %><!--[eoform:context]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
</div><!-- [end:context-new-action] -->
<%= hidden_field( "new_item", "context_id", "value" => "#{@context.id}") %>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1 ) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_project_id">Project</label><br />
<select name="item[project_id]" id="item_project_id" tabindex="3">
<option selected="selected"></option>
<%= options_from_collection_for_select(@projects, "id", "name") %>
</select><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 4) %>
<br /><br />
<input type="submit" value="Add item" tabindex="5">
<%= end_form_tag %><!--[eoform:context]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
</div><!-- [end:context-new-action] -->
<h3>Active Projects:</h3>
<ul>
<% for project in Project.list_of -%>

View file

@ -1,37 +1,37 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<%= stylesheet_link_tag "standard" %>
<%= javascript_include_tag "toggle_notes" %>
<%= javascript_include_tag "prototype" %>
<%= javascript_include_tag "prototype-ex" %>
<%= stylesheet_link_tag 'calendar-system.css' %>
<%= javascript_include_tag 'calendar', 'calendar-en', 'calendar-setup' %>
<link rel="shortcut icon" href="<%= url_for(:controller => 'favicon.ico') %>" />
<%= stylesheet_link_tag "standard" %>
<%= javascript_include_tag "toggle_notes" %>
<%= javascript_include_tag "prototype" %>
<%= javascript_include_tag "prototype-ex" %>
<%= stylesheet_link_tag 'calendar-system.css' %>
<%= javascript_include_tag 'calendar', 'calendar-en', 'calendar-setup' %>
<link rel="shortcut icon" href="<%= url_for(:controller => 'favicon.ico') %>" />
<title><%= @page_title %></title>
</head>
<body>
<div>
<h1>
<% if @count %>
<span class="badge"><%= @count %></span>
<% end %>
<%= Time.now.strftime("%A, %d %B %Y") %></h1>
<h1>
<% if @count %>
<span class="badge"><%= @count %></span>
<% end %>
<%= Time.now.strftime("%A, %d %B %Y") %></h1>
</div>
<div id="navcontainer">
<ul id="navlist">
<li><%= link_to( "Home", {:controller => "todo", :action => "list"}, {:accesskey=>"t", :title=>"Home AccessKey: Alt+T"} ) %></li>
<li><%= link_to( "Contexts", {:controller => "context", :action => "list"}, {:accesskey=>"c", :title=>"Contexts AccessKey: Alt+C"} ) %></li>
<li><%= link_to( "Projects", {:controller => "project", :action => "list"}, {:accesskey=>"p", :title=>"Projects AccessKey: Alt+P"} ) %></li>
<li><%= link_to( "Completed", {:controller => "todo", :action => "completed"}, {:accesskey=>"d", :title=>"Completed AccessKey: Alt+D"} ) %></li>
<li><a href="javascript:toggleAll('notes','block')" accesskey="S" title="Show all notes AccessKey: Alt+S">Show</a></li>
<li><a href="javascript:toggleAll('notes','none')" accesskey="H" title="Show all notes AccessKey: Alt+H">Hide</a></li>
<li><%= link_to("<span class=\"feed\">RSS</span>", {:controller => "feed", :action => "na_feed", :name => "#{@session['user']['login']}", :token => "#{@session['user']['word']}"}, :title => "Subscribe to an RSS feed of your next actions" ) %></li>
<li><%= link_to( "Contexts", {:controller => "context", :action => "list"}, {:accesskey=>"c", :title=>"Contexts AccessKey: Alt+C"} ) %></li>
<li><%= link_to( "Projects", {:controller => "project", :action => "list"}, {:accesskey=>"p", :title=>"Projects AccessKey: Alt+P"} ) %></li>
<li><%= link_to( "Completed", {:controller => "todo", :action => "completed"}, {:accesskey=>"d", :title=>"Completed AccessKey: Alt+D"} ) %></li>
<li><a href="javascript:toggleAll('notes','block')" accesskey="S" title="Show all notes AccessKey: Alt+S">Show</a></li>
<li><a href="javascript:toggleAll('notes','none')" accesskey="H" title="Show all notes AccessKey: Alt+H">Hide</a></li>
<li><%= link_to("<span class=\"feed\">RSS</span>", {:controller => "feed", :action => "na_feed", :name => "#{@session['user']['login']}", :token => "#{@session['user']['word']}"}, :title => "Subscribe to an RSS feed of your next actions" ) %></li>
<li><%= link_to("<span class=\"feed\">TXT</span>", {:controller => "feed", :action => "na_text", :name => "#{@session['user']['login']}", :token => "#{@session['user']['word']}"}, :title => "View a plain text feed of your next actions" ) %></li>
<li><%= link_to "Logout (#{@session['user']['login']}) &#187;", :controller => "login", :action=>"logout"%></li>
<li><%= link_to "Logout (#{@session['user']['login']}) &#187;", :controller => "login", :action=>"logout"%></li>
</ul>
</div>
<%= @content_for_layout %>

View file

@ -1,4 +1,4 @@
<% item = show_items %>
<% item = show_items %>
<% if !item.done? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "project", :action => "toggle_check", :id => item.id ),
@ -16,7 +16,7 @@
:update => "item-#{item.id}-container",
:loading => "new Effect.Squish('item-#{item.id}-container')",
:url => { :controller => "project", :action => "destroy_action", :id => item.id }, :confirm => "Are you sure that you want to delete the action \'#{item.description}\'?" ) + " " +
link_to_function(image_tag( "blank", :title => "Edit action", :class=>"edit_item"), "Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}');" ) + " "
link_to_function(image_tag( "blank", :title => "Edit action", :class=>"edit_item"), "Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}');" ) + " "
%>
</div>
<!-- begin div.checkbox -->
@ -31,17 +31,17 @@
<%= staleness(item.created) %>
<% end %>
<%= due_date( item.due ) %>
<%= item.description %>
<% if item.context_id %>
<%= link_to( "[C]", { :controller => "context", :action => "show", :name => urlize(item.context.name) }, :title => "View context: #{item.context.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div>
<%= due_date( item.due ) %>
<%= item.description %>
<% if item.context_id %>
<%= link_to( "[C]", { :controller => "context", :action => "show", :name => urlize(item.context.name) }, :title => "View context: #{item.context.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div>
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
@ -66,7 +66,7 @@
) %>
<div id="done-item-<%= item.id %>">
<div class="big-box">
<div class="big-box">
<%=
link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
:update => "done-item-#{item.id}-container",
@ -79,18 +79,18 @@
<input type="checkbox" name="item_id" value="<%= item.id %>" checked="checked" onclick="document.forms['checkbox-done-<%= item.id %>'].onsubmit();" />
</div>
<!-- end div.checkbox -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[C]", { :controller => "context", :action => "show", :name => urlize(item.context.name) }, :title => "View context: #{item.context.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div><!-- [end:description] -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[C]", { :controller => "context", :action => "show", :name => urlize(item.context.name) }, :title => "View context: #{item.context.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div><!-- [end:description] -->
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
</div><!-- [end:item-item.id-container] -->

View file

@ -44,25 +44,25 @@
:loading => "project.reset()",
:complete => "Form.focusFirstElement('project-form-new-action');",
:html=> { :id=>'project-form-new-action', :name=>'project', :class => 'inline-form' } %>
<%= hidden_field( "new_item", "project_id", "value" => "#{@project.id}") %>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1 ) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_context_id">Context</label><br />
<select name="new_item[context_id]" id="new_item_context_id" tabindex="3">
<%= options_from_collection_for_select(@places, "id", "name" ) %>
</select><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 4) %>
<br /><br />
<input type="submit" value="Add item" tabindex="5">
<%= end_form_tag %><!--[eoform:project]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
</div><!-- [end:project-new-action] -->
<%= hidden_field( "new_item", "project_id", "value" => "#{@project.id}") %>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1 ) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_context_id">Context</label><br />
<select name="new_item[context_id]" id="new_item_context_id" tabindex="3">
<%= options_from_collection_for_select(@places, "id", "name" ) %>
</select><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 4) %>
<br /><br />
<input type="submit" value="Add item" tabindex="5">
<%= end_form_tag %><!--[eoform:project]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
</div><!-- [end:project-new-action] -->
<h3>Active Projects:</h3>
<ul>

View file

@ -5,52 +5,52 @@
<%= hidden_field( "item", "id" ) %>
<table>
<tr>
<td class="label"><label for="item_description">Next action</label></td>
<td><%= text_field( "item", "description", "tabindex" => 1 ) %></td>
</tr>
<tr>
<td class="label"><label for="item_notes">Notes</label></td>
<td><%= text_area( "item", "notes", "cols" => 20, "rows" => 5, "tabindex" => 2 ) %></td>
</tr>
<tr>
<td class="label"><label for="item_context_id">Context</label></td>
<td><select name="item[context_id]" id="item_context_id" tabindex="3">
<% for @place in @places %>
<% if @item %>
<% if @place.id == @item.context_id %>
<option value="<%= @place.id %>" selected="selected"><%= @place.name %></option>
<% else %>
<option value="<%= @place.id %>"><%= @place.name %></option>
<% end %>
<% else %>
<option value="<%= @place.id %>"><%= @place.name %></option>
<% end %>
<% end %>
</select></td>
</tr>
<tr>
<td class="label"><label for="item_project_id">Project</label></td>
<tr>
<td class="label"><label for="item_description">Next action</label></td>
<td><%= text_field( "item", "description", "tabindex" => 1 ) %></td>
</tr>
<tr>
<td class="label"><label for="item_notes">Notes</label></td>
<td><%= text_area( "item", "notes", "cols" => 20, "rows" => 5, "tabindex" => 2 ) %></td>
</tr>
<tr>
<td class="label"><label for="item_context_id">Context</label></td>
<td><select name="item[context_id]" id="item_context_id" tabindex="3">
<% for @place in @places %>
<% if @item %>
<% if @place.id == @item.context_id %>
<option value="<%= @place.id %>" selected="selected"><%= @place.name %></option>
<% else %>
<option value="<%= @place.id %>"><%= @place.name %></option>
<% end %>
<% else %>
<option value="<%= @place.id %>"><%= @place.name %></option>
<% end %>
<% end %>
</select></td>
</tr>
<tr>
<td class="label"><label for="item_project_id">Project</label></td>
<td>
<select name="item[project_id]" id="item_project_id" tabindex="4">
<% if !@item.project_id? %>
<option selected="selected"></option>
<%= options_from_collection_for_select(@projects, "id", "name") %>
<% else %>
<option></option>
<%= options_from_collection_for_select(@projects, "id", "name", @item.project_id) %>
<% end %>
</select>
</td>
</tr>
<tr>
<td class="label"><label for="item_due">Due</td>
<td><input name="item[due]" id="due_<%= @item.id %>" type="text" value="<%= format_date(@item.due) %>" tabindex="5" size="10" onFocus="Calendar.setup" /></td>
</tr>
<tr>
<td><input type="submit" value="Update" tabindex="6" />
<a href="javascript:void(0);" onclick="Element.toggle('item-<%= @item.id %>','action-<%= @item.id %>-edit-form');Form.reset('form-action-<%= @item.id %>');">Cancel</a></td>
</tr>
<select name="item[project_id]" id="item_project_id" tabindex="4">
<% if !@item.project_id? %>
<option selected="selected"></option>
<%= options_from_collection_for_select(@projects, "id", "name") %>
<% else %>
<option></option>
<%= options_from_collection_for_select(@projects, "id", "name", @item.project_id) %>
<% end %>
</select>
</td>
</tr>
<tr>
<td class="label"><label for="item_due">Due</td>
<td><input name="item[due]" id="due_<%= @item.id %>" type="text" value="<%= format_date(@item.due) %>" tabindex="5" size="10" onFocus="Calendar.setup" /></td>
</tr>
<tr>
<td><input type="submit" value="Update" tabindex="6" />
<a href="javascript:void(0);" onclick="Element.toggle('item-<%= @item.id %>','action-<%= @item.id %>-edit-form');Form.reset('form-action-<%= @item.id %>');">Cancel</a></td>
</tr>
</table>
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"due_<%= @item.id %>",cache:true,align:"TR" })

View file

@ -1,4 +1,4 @@
<% item = show_items %>
<% item = show_items %>
<% if !item.done? %>
<div id="item-<%= item.id %>-container">
<%= form_remote_tag( :url => url_for( :controller => "todo", :action => "toggle_check", :id => item.id ),
@ -17,8 +17,8 @@
:loading => "new Effect.Squish('item-#{item.id}-container')",
:url => { :controller => "todo", :action => "destroy_action", :id => item.id },
:confirm => "Are you sure that you want to delete the action, \'#{item.description}\'?") + " " +
link_to_function(image_tag( "blank", :title => "Edit action", :class => "edit_item"),
"Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}')" ) + " "
link_to_function(image_tag( "blank", :title => "Edit action", :class => "edit_item"),
"Element.toggle('item-#{item.id}','action-#{item.id}-edit-form'); new Effect.Appear('action-#{item.id}-edit-form'); Form.focusFirstElement('form-action-#{item.id}')" ) + " "
%>
</div>
<div class="checkbox">
@ -30,17 +30,17 @@
<% else %>
<%= staleness(item.created) %>
<% end %>
<%= due_date( item.due ) %>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div>
<%= due_date( item.due ) %>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div>
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
@ -65,7 +65,7 @@
) %>
<div id="done-item-<%= item.id %>">
<div class="big-box">
<div class="big-box">
<%=
link_to_remote( image_tag("blank", :title =>"Delete action", :class=>"delete_item"),
:update => "done-item-#{item.id}-container",
@ -79,18 +79,18 @@
<input type="checkbox" name="item_id" value="<%= item.id %>" checked="checked" onclick="document.forms['checkbox-done-<%= item.id %>'].onsubmit();" />
</div>
<!-- end div.checkbox -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div><!-- [end:description] -->
<div class="description">
<span class="grey"><%= format_date( item.completed ) %></span>
<%= item.description %>
<% if item.project_id %>
<%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %>
<% end %>
<% if item.notes? %>
<%= "<a href=\"javascript:Element.toggle('" + item.id.to_s + "')\" class=\"show_notes\" title=\"Show notes\">" + image_tag( "blank", :width=>"16", :height=>"16", :border=>"0" ) + "</a>" %>
<% m_notes = markdown( item.notes ) %>
<%= "<div class=\"notes\" id=\"" + item.id.to_s + "\" style=\"display:none\">" + m_notes + "</div>" %>
<% end %>
</div><!-- [end:description] -->
</div><!-- [end:item-item.id] -->
<%= end_form_tag %>
</div><!-- [end:item-item.id-container] -->

View file

@ -1,79 +1,85 @@
<div id="display_box">
<!-- begin div.new_actions -->
<div id="new_actions" class="new_actions" style="display:none">
<h2>Fresh actions (hit <a href="javascript:window.location.reload()">refresh</a> to sort)</h2>
</div>
<!-- end div.new_actions -->
<% for @shown_place in @shown_places -%>
<% @not_done = Todo.find_all("done=0 AND context_id=#{@shown_place.id}", "due IS NULL, due ASC, created ASC") -%>
<% if !@not_done.empty? -%>
<div class="contexts">
<h2><a href="javascript:toggleSingle('c<%=@shown_place.id%>');javascript:toggleImage('toggle_context_<%=@shown_place.id%>')" class="refresh"><%= image_tag("collapse.png", :name=>"toggle_context_#{@shown_place.id}", :border=>"0") %></a>
<%= link_to( "#{@shown_place.name}", :controller => "context", :action => "show", :name => urlize(@shown_place.name) ) %></h2>
<!-- begin div.new_actions -->
<div id="new_actions" class="new_actions" style="display:none">
<h2>Fresh actions (hit <a href="javascript:window.location.reload()">refresh</a> to sort)</h2>
</div>
<!-- end div.new_actions -->
<% for @shown_place in @shown_places -%>
<% @not_done = Todo.find_all("done=0 AND context_id=#{@shown_place.id}", "due IS NULL, due ASC, created ASC") -%>
<% if !@not_done.empty? -%>
<div class="contexts">
<h2><a href="javascript:toggleSingle('c<%=@shown_place.id%>');javascript:toggleImage('toggle_context_<%=@shown_place.id%>')" class="refresh"><%= image_tag("collapse.png", :name=>"toggle_context_#{@shown_place.id}", :border=>"0") %></a>
<%= link_to( "#{@shown_place.name}", :controller => "context", :action => "show", :name => urlize(@shown_place.name) ) %></h2>
<div id="c<%= @shown_place.id %>" class="next_actions">
<% if @not_done.empty? -%>
<p>There are no next actions yet in this context</p>
<% else -%>
<% for item in @not_done -%>
<%= render_partial "show_items", item %>
<% end -%>
<% end -%>
</div><!-- [end:next_actions] -->
</div><!-- [end:contexts] -->
<% end -%>
<% end -%>
<div id="c<%= @shown_place.id %>" class="next_actions">
<% if @not_done.empty? -%>
<p>There are no next actions yet in this context</p>
<% else -%>
<% for item in @not_done -%>
<%= render_partial "show_items", item %>
<% end -%>
<% end -%>
</div><!-- [end:next_actions] -->
</div><!-- [end:contexts] -->
<% end -%>
<% end -%>
<div class="contexts">
<h2>Completed actions in this context</h2>
<div class="contexts">
<h2>Completed actions in this context</h2>
<div id="completed">
<% if @done.empty? -%>
<p>There are no completed next actions yet in this context</p>
<% else -%>
<% for done_item in @done -%>
<%= render_partial "show_items", done_item %>
<% end -%>
<% end -%>
</div>
</div><!-- [end:contexts] -->
<div id="completed">
<% if @done.empty? -%>
<p>There are no completed next actions yet in this context</p>
<% else -%>
<% for done_item in @done -%>
<%= render_partial "show_items", done_item %>
<% end -%>
<% end -%>
</div>
</div><!-- [end:contexts] -->
</div><!-- End of display_box -->
<div id="input_box">
<% if @flash["confirmation"] -%>
<div class="confirmation"><%= @flash["confirmation"] %></div>
<% end -%>
<% if @flash["warning"] -%>
<div class="warning"><%= @flash["warning"] %></div>
<% end -%>
<%= link_to_function( "Add the next action in this context &#187;", "Element.toggle('todo_new_action');Element.toggle('new_actions');Form.focusFirstElement('todo-form-new-action');", {:title => "Add the next action [Alt+n]", :accesskey => "n"}) %>
<div id="todo_new_action" class="context_new" style="display:none">
<!--[form:todo]-->
<%= form_remote_tag :url => { :action => "add_item" },
:update => "new_actions",
:update => "new_actions",
:position=> "bottom",
:loading => "context.reset()",
:complete => "Form.focusFirstElement('todo-form-new-action');",
:complete => "Form.focusFirstElement('todo-form-new-action');",
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' } %>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1 ) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_context_id">Context</label><br />
<%= collection_select( "new_item", "context_id", @places, "id", "name", {}, {"tabindex" => 3}) %><br />
<label for="new_item_project_id">Project</label><br />
<select name="new_item[project_id]" id="new_item_project_id" tabindex="4">
<option selected="selected"></option>
<%= options_from_collection_for_select(@projects, "id", "name") %>
</select><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5) %>
<br /><br />
<input type="submit" value="Add item" tabindex="6">
<%= end_form_tag %><!--[eoform:todo]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
<label for="new_item_description">Description</label><br />
<%= text_field( "new_item", "description", "size" => 25, "tabindex" => 1 ) %><br />
<label for="new_item_notes">Notes</label><br />
<%= text_area( "new_item", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2 ) %><br />
<label for="new_item_context_id">Context</label><br />
<%= collection_select( "new_item", "context_id", @places, "id", "name", {}, {"tabindex" => 3}) %><br />
<label for="new_item_project_id">Project</label><br />
<select name="new_item[project_id]" id="new_item_project_id" tabindex="4">
<option selected="selected"></option>
<%= options_from_collection_for_select(@projects, "id", "name") %>
</select><br />
<label for="item_due">Due</label><br />
<%= text_field("new_item", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5) %>
<br /><br />
<input type="submit" value="Add item" tabindex="6">
<%= end_form_tag %><!--[eoform:todo]-->
<script type="text/javascript">
Calendar.setup({ ifFormat:"<%= ApplicationController::DATE_FORMAT %>",firstDay:<%= ApplicationController::WEEK_STARTS_ON %>,showOthers:true,range:[2004, 2010],step:1,inputField:"new_item_due",cache:true,align:"TR" })
</script>
</div><!-- [end:todo-new-action] -->
</div><!-- [end:todo-new-action] -->
<h3>Active Projects:</h3>
<ul>
@ -111,7 +117,3 @@
<% end -%>
</ul>
</div><!-- End of input box -->
<% if @flash["confirmation"] %><div class="confirmation"><%= @flash["confirmation"] %></div><% end %>
<% if @flash["warning"] %><div class="warning"><%= @flash["warning"] %></div><% end %>

77
tracks/doc/CHANGELOG Normal file
View file

@ -0,0 +1,77 @@
= Tracks: a GTD web application, built with Ruby on Rails
* Homepage: http://www.rousette.org.uk/projects/
* Author: bsag (http://www.rousette.org.uk/)
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg
* Version: 1.03
* Copyright: (cc) 2004-2005 rousette.org.uk
* License: GNU GPL
Main project site: http://www.rousette.org.uk/projects/
Trac (for bug reports and feature requests): http://dev.rousette.org.uk/report/6
Wiki (deprecated - please use Trac): http://www.rousette.org.uk/projects/wiki/
== Version 1.03
13. All the adding, updating, deleting and marking actions done is performed using Ajax, so happens without needing to refresh the page.
14. There's a new setting in settings.yml ('staleness_starts') which defines the number of days before which actions get marked as stale. Let's say you set it to 7 days. Actions created between 7 and 14 days ago get marked pale yellow, those created between 14 and 28 days ago (staleness_starts x 2) get marked darker yellow, and those created more than 28 days ago (staleness_starts x 3) are fluorescent yellow! This is only applied to items without a due date, so you can add items to be done some time in the future without getting yellow splashed all over the place (thanks to Nicholas for the patch to restrict to items with no due date).
15. Contexts and projects can now be sorted in any order you like. Arrow buttons on the <tt>/contexts</tt> and <tt>/projects</tt> pages let you move an item to the top, up, down or to the bottom. For contexts, this affects the order in which they sort on the home page. Position is also used to sort the listings of active/completed/hidden contexts and projects in the 'sidebar', and in the dropdown lists on forms.
16. You can mark projects as completed (by editing the project on <tt>/projects</tt>). In the 'sidebar' active and completed projects are shown separately, but you can still view the completed project.
17. New images (from eclipse.org) for the edit, delete, notes and up, down, top and bottom buttons. I've made a greyscale version for the default, then the coloured version gets loaded when the mouse is hovering over the button.
1. Added back border="0" to images which I had mistakenly taken out. This should fix the ugly red border around images that appears in Firefox (thanks, Adam Hughes).
2. Removed the section in <tt>config/environment.rb</tt> which requires Redcloth. This was causing errors because Rails now requires Redcloth itself. This means that you now need to have Redcloth installed as a gem (gem install redcloth) (thanks, Jim).
3. SQL dumps are now available for each of the available database formats (MySQL, PostgreSQL and SQLite), as well as a separate file containing some example contents, which should work for all of the formats. These are in the <tt>tracks/db</tt> directory (thanks, Jim)
4. The new item forms on all pages now use a mini calendar which pops up when you click in the due date field. The calendar is the GPL one from dynarch.com http://www.dynarch.com/projects/calendar/
5. <b>Contributed by Lolindrath</b>: Toggling of contexts in on the home page to collapse or expand their display via a small '+' or '-' graphic. This is independent of the shown/hidden setting for contexts, and is ideal for just hiding things on the fly to focus your view.
6. <b>Contributed by Jim Ray</b>: Jim added a host of fixes and bits of cleaning up, including a position column for contexts and projects to allow custom sorting, and changes to the links for pages to make them more human-readable.
7. <b>Contributed by Nicholas Lee</b>: URLs are now generated using the <tt>url_for</tt> or <tt>link_to</tt> methods, which should mean that people using Tracks in a subdirectory won't have to manually tinker with the URLs to get them to work.
8. <b>Contributed by Arnaud Limbourg, ticket 18</b>: A new entry in settings.yml allows you to choose the number of completed actions you want to see on the home page. Also sorts by due date (ascending) first, then creation date (descending) on the home page, <tt>/context/show/[name]</tt>, and <tt>/project/show/[name]</tt>.
9. Counts of next actions are now shown on the <tt>/context/show/[name]</tt> and <tt>/project/show/[name]</tt> pages. These just show how many uncompleted actions you have in the selected context or project.
10. <b>Patch by lolindrath</b>: Sorting by date is now much smarter on the home page. Actions are sorted by ascending due date then ascending creation date, but non-due dated items sort to the bottom. This means that the most urgent items float to the top of each context list.
11. You can now uncheck actions from the completed actions list, so that they dynamically appear back in the uncompleted actions area.
12. A tiny improvement: the toggling of the individual notes now uses Element.toggle from prototype.js, so it doesn't have to be an onLoad property of the body tag. This means that you don't get the notes flashing before they are hidden when you load or reload a page.
== Version 1.02
1. Uses Rails 0.10.0
2. Added validation for the entry fields. If you enter a bit of text that's too long or you omit the description (not much point in a blank next action!) you'll get an error message and the action won't be saved.
3. Added action caching.
4. Did a bit of refactoring to try to make page loading a bit more efficient.
5. Added a new row to the context table: 'hide'. This determines whether a particular context gets hidden on the main page. If the checkbox on the add new context form is checked, the context is hidden, and isn't listed on the front (todo/list) page. This is useful for contexts like 'wish list' or 'someday/maybe' that you don't want taking up your attention all the time.
6. Added a list of links to the hidden contexts at the bottom of the page.
7. Changed the method of adding due dates to drop-down option lists. It's more obvious and less error prone than typing in the date. Your preferences for date formatting are still honoured when displaying due dates.
8. Added a favicon, kindly produced by Jim Ray.
9. Added border="0" to the button images to stop Firefox putting a red border around them.
10. Added a new path:, base: setting in settings.yml. This is used in constructing URLs in standard.rhtml and other places for the javascripts and stylesheets. You need to specify it in the format <tt>http://my.domain.tld/subdir/tracks</tt>, or whatever the full URL is. This should help people who put Tracks in a subdirectory.
11. Added some rudimentary sorting of completed items. They are now sorted in to done today, done in the last 7 days and done in the last 31 days. At the bottom of completed.rhtml, there's a link to completed_archive.rhtml, which shows archived items older than 31 days.
12. Changed the method of generating links to stylesheet and javascripts. Together with the new Routes, this should sort out any problems with putting Tracks in a sub-directory.
13. config/environment.rb now checks to see if a gem version of Redcloth is installed that is greater than version 3.0.3. If that fails, it falls back on the packaged lib version. Note that there's an odd bug I can't seem to pin down that means that if you are using the packaged lib version, you'll get some errors like: <tt>./script/../config/..//lib/redcloth.rb:169: warning: already initialized constant VERSION</tt> or <tt>./script/../config/..//lib/redcloth.rb:170: warning: already initialized constant DEFAULT_RULES</tt> but ONLY if you're using the development environment; with production it's fine, and with the gem version of Redcloth it's fine in both environments.
13. Modified the 'count' badge on todo/list: now shows the number of uncompleted items in contexts that <b>aren't</b> hidden (i.e. the actions actually listed on todo/list). Number of items in hidden contexts are shown in parentheses after the link to that context. So you don't forget about that stuff ;-)
14. Protected RSS and text feeds at last! The appropriate URLs can be copied from the RSS and TXT links in the navigation bar. The URL includes the login name of the current user, and an MD5 encoded string of the 'word' field of the users table. This is checked against users to make sure it's valid; if it is, the feed is displayed, if not, you get an error message.
15. Better signup system implemented. The users table has another new column, 'is_admin'. If no users have been created, the first user to sign in is made the admin user. If the admin user (while logged in), visits the signup page, the form indicates that this user can create a new user (who won't have admin rights). If anyone who is not not logged in and not an admin user visits signup, they are greeted with a message that they don't have permission to create an account, and should contact the admin. I've made a new field in settings.yml to hold your admin email address for this purpose. This should mean that you can safely leave signup.rhtml intact on a public server.
16. Added a list of other contexts and projects to the context/show/[id] and project/show/[id] pages, so that you can easily navigate between the filtered views of contexts and projects, without having to go back to context/list or project/list.
== Version 1.01
A small increment to fix a few bugs and typographical errors in the README:
1. README.txt now lists the correct location for the dump file
2. Fixed the redirection from the login page. Once you have signed up or logged in, you should be taken automatically to the main todo/list page. This also works once you have logged out and clicked the "<< login" page to get back to <tt>login/login</tt>. (Thanks, Max Prophet!)
3. Properly updated for Rails 0.9.3: this probably means that it won't work for a lower version of Rails. If you have the gems version of Rails installed, you can update it with: <tt>sudo gem update rails</tt>
4. Made some minor changes to the appearance of the listed items, so that the description wraps properly, and the tool buttons (edit, delete and notes) are always in the same place relative to the checkbox.
== Version 1.0
1. Updated to run on Rails 0.9.1. This should work with any 0.9.x branch.
2. Added a proper edit page for todos. This also allows you to add a todo to a project, or re-assign it to a new project.
3. Improved the efficiency of the Projects page: it now only finds the Project you've selected, rather than all of them (thanks, Johan http://theexciter.com/!)
4. You now get a warning if you try to delete a project or context with undone items. If you choose to continue, the dependent items will be deleted, so that the list page should no longer break.
5. Using Redcloth for markup now that it allows you to use either Textile or Markdown format transparently. This is now included in the distribution, so that you don't need to install it.
6. Added links to context/list page so that you can go directly to <tt>todo/show/blah</tt> if you click the link for context 'blah'.
7. You can now set the date format you want to use in the file config/settings.yml. Use the strftime formats, with whatever separator you like. e.g. If you want 2004-12-31, the correct format is <tt>%Y-%m-%d</tt>. 31/12/2004 would be <tt>%d/%m/%Y</tt>. The same format is used for entry of the date in the due date box, and for display of the date in the list.
8. You can now add a new item to a particular project when you are viewing it at <tt>/project/show/[id]</tt>. This makes it easy to add a block of next actions to a project. Edit and delete buttons work on this page, but redirect you to <tt>/todo/list</tt>. I need to fix this.
9. Added a login system by using the login_generator: http://wiki.rubyonrails.com/rails/show/LoginGenerator. The first time you access the system, you need to visit <tt>[tracks_url]/login/signup</tt> to enter a username and password. Then when you visit, you will be greeted with a login page. This stores a temporary cookie for your session. The whole app is protected by the login.
10. You can now add a new item to a particular context when you are viewing it at <tt>/context/show/[id]</tt>. This makes it easy to add a block of next actions to a project. Edit and delete buttons work on this page, but redirect you to <tt>/todo/list</tt>. I need to fix this.
11. Feeds! There's an RSS feed available at <tt>[tracks_url]/feed/na_feed</tt> which shows the last 15 next actions, and a text feed at <tt>[tracks_url]/feed/na_text</tt> which gives you a plain text list of all your next actions, sorted by context. You can use the latter to display your next actions on the desktop with GeekTool. Simply set up a shell command containing the following: <tt>curl http://[tracks_url]/feed/na_text</tt>. Obviously, you need to replace [tracks_url] with the actual URL to the Tracks application.

View file

@ -1,89 +0,0 @@
## Tracks :: GTD web application
## $LastChangedDate$
## $HeadURL$
# Homepage:: http://www.rousette.org.uk/projects/
# Author:: bsag (http://www.rousette.org.uk/)
# Contributors:: Lolindrath, Jim Ray, Nicholas Lee, Arnaud Limbourg
# Version:: 1.02
# Copyright:: (cc) 2004-2005 rousette.org.uk
# License:: GNU GPL
Main project site: <http://www.rousette.org.uk/projects/>
Project wiki: <http://www.rousette.org.uk/projects/wiki/>
## Version 1.021
1. Added back border="0" to images which I had mistakenly taken out (thanks, Adam Hughes)
2. Removed the section in config/environment.rb which requires Redcloth. This was causing errors because Rails now requires Redcloth itself. This means that you now need to have Redcloth installed as a gem (gem install redcloth) (thanks, Jim).
3. Fixed SQLite dump format in db/tracks_1.0.2_sqlite.sql (thanks, Jim)
4. The new item forms on all pages now use a mini calendar which pops up when you click the due date field. Calendar is the GPL one from dynarch.com <http://www.dynarch.com/projects/calendar/>
6. [Contributed by Lolindrath] Toggling of contexts in /todo/list to collapse or expand their display via a small '+' or '-' graphic. This is independent of the shown/hidden setting for contexts, and is ideal for just hiding things on the fly to focus your view.
7. [Contributed by Jim Ray] Jim added a host of fixes and bits of cleaning up, including a position column for contexts and projects to allow custom sorting, and changes to the links for pages to make them more human-readable.
8. I added a pop-up calendar to set the due date. This is entirely lifted from Michele's excellent tutorial on pxl8.com <http://www.pxl8.com/calendar_date_picker.html>. It works well, but I need to make sure it doesn't break in postgresql or sqlite.
9. [Contributed by Nicholas Lee] Changes to the way that URLs are specified which should improve the situation for people using Tracks in a subdirectory.
10. [Contributed by Arnaud Limbourg, ticket:18] A new entry in settings.yml allows you to choose the number of completed actions you want to see on the /todo/list home page. Also sorts by due date (ascending) first, then creation date (descending) on /todo/list, /context/show/[name], and /project/show/[name]
11. Added a count of next actions to the /projects page, showing how many uncompleted next actions remain for each project.
12. [Patch by lolindrath] Sorting by date is now much smarter on /todo/list: Actions are sorted by ascending due date then ascending creation date, but non-due dated items sort to the bottom. This means that the most urgent items float to the top of each context list.
13. Can now uncheck actions from the completed actions list, so that they dynamically appear back in the uncompleted actions area.
14. A tiny improvement: the toggling of the individual notes now uses Element.toggle from prototype.js, so it doesn't have to be an onLoad property of the body tag. This means that you don't get the notes flashing before they are hidden when you load or reload a page.
15. All the adding, updating, deleting and marking actions done is performed using Ajax, so happens without needing to refresh the page.
16. There's a new setting in settings.yml ('staleness_starts') which defines the number of days before which actions get marked as stale. Let's say you set it to 7 days. Actions created between 7 and 14 days ago get marked pale yellow, those created between 14 and 28 days ago (staleness_starts * 2) get marked darker yellow, and those created more than 28 days ago (staleness_starts * 3) are fluorescent yellow! This is only applied to items without a due date, so you can add items to be done some time in the future without getting yellow splashed all over the place (thanks to nic for the patch to restrict to items with no due date).
17. Contexts and projects can now be sorted in any order you like. Arrow buttons on the /contexts and /projects pages let you move an item to the top, up, down or to the bottom. For contexts, this affects the order in which they sort on the home page.
18. You can mark projects as completed (by editing the project on /projects). In the 'sidebar' active and completed projects are shown separately, but you can still view the completed project.
19. New images (from eclipse.org) for the edit, delete, notes and up, down, top and bottom buttons. I've made a greyscale version for the default, then the coloured version gets loaded when the mouse is hovering over the button.
## Version 1.02
1. Uses Rails 0.10.0
2. Added validation for the entry fields. If you enter a bit of text that's too long or you omit the description (not much point in a blank next action!) you'll get an error message and the action won't be saved.
3. Added action caching.
4. Did a bit of refactoring to try to make page loading a bit more efficient.
5. Added a new row to the context table: 'hide'. This determines whether a particular context gets hidden on the main page. If the checkbox on the add new context form is checked, the context is hidden, and isn't listed on the front (todo/list) page. This is useful for contexts like 'wish list' or 'someday/maybe' that you don't want taking up your attention all the time.
6. Added a list of links to the hidden contexts at the bottom of the page.
7. Changed the method of adding due dates to drop-down option lists. It's more obvious and less error prone than typing in the date. Your preferences for date formatting are still honoured when displaying due dates.
8. Added a favicon, kindly produced by Jim Ray.
9. Added border="0" to the button images to stop Firefox putting a red border around them.
10. Added a new path:, base: setting in settings.yml. This is used in constructing URLs in standard.rhtml and other places for the javascripts and stylesheets. You need to specify it in the format,
http://my.domain.tld/subdir/tracks
or whatever the full URL is. This should help people who put Tracks in a subdirectory.
11. Added some rudimentary sorting of completed items. They are now sorted in to done today, done in the last 7 days and done in the last 31 days. At the bottom of completed.rhtml, there's a link to completed_archive.rhtml, which shows archived items older than 31 days.
12. Changed the method of generating links to stylesheet and javascripts. Together with the new Routes, this should sort out any problems with putting Tracks in a sub-directory.
13. config/environment.rb now checks to see if a gem version of Redcloth is installed that is greater than version 3.0.3. If that fails, it falls back on the packaged lib version. Note that there's an odd bug I can't seem to pin down that means that if you are using the packaged lib version, you'll get some errors like:
`./script/../config/..//lib/redcloth.rb:169: warning: already initialized constant VERSION`
`./script/../config/..//lib/redcloth.rb:170: warning: already initialized constant DEFAULT_RULES`
but ONLY if you're using the development environment; with production it's fine, and with the gem version of Redcloth it's fine in both environments.
13. Modified the 'count' badge on todo/list: now shows the number of uncompleted items in contexts that *aren't* hidden (i.e. the actions actually listed on todo/list). Number of items in hidden contexts are shown in parentheses after the link to that context. So you don't forget about that stuff ;-)
14. Protected RSS and text feeds at last! The appropriate URLs can be copied from the RSS and TXT links in the navigation bar. The URL includes the login name of the current user, and an MD5 encoded string of the 'word' field of the users table. This is checked against users to make sure it's valid; if it is, the feed is displayed, if not, you get an error message.
15. Better signup system implemented. The users table has another new column, 'is_admin'. If no users have been created, the first user to sign in is made the admin user. If the admin user (while logged in), visits the signup page, the form indicates that this user can create a new user (who won't have admin rights). If anyone who is not not logged in and not an admin user visits signup, they are greeted with a message that they don't have permission to create an account, and should contact the admin. I've made a new field in settings.yml to hold your admin email address for this purpose. This should mean that you can safely leave signup.rhtml intact on a public server.
16. Added a list of other contexts and projects to the context/show/[id] and project/show/[id] pages, so that you can easily navigate between the filtered views of contexts and projects, without having to go back to context/list or project/list.
## Version 1.01
A small increment to fix a few bugs and typographical errors in the README:
1. README.txt now lists the correct location for the dump file
2. Fixed the redirection from the login page. Once you have signed up or logged in, you should be taken automatically to the main todo/list page. This also works once you have logged out and clicked the "<< login" page to get back to login/login. (Thanks, Max Prophet!)
3. Properly updated for Rails 0.9.3: this probably means that it won't work for a lower version of Rails. If you have the gems version of Rails installed, you can update it with:
sudo gem update rails
4. Made some minor changes to the appearance of the listed items, so that the description wraps properly, and the tool buttons (edit, delete and notes) are always in the same place relative to the checkbox.
## Version 1.0
1. Updated to run on Rails 0.9.1. This should work with any 0.9.x branch.
2. Added a proper edit page for todos. This also allows you to add a todo to a project, or re-assign it to a new project.
3. Improved the efficiency of the Projects page: it now only finds the Project you've selected, rather than all of them (thanks, Johan <http://theexciter.com/>!)
4. You now get a warning if you try to delete a project or context with undone items. If you choose to continue, the dependent items will be deleted, so that the list page should no longer break.
5. Using Redcloth for markup now that it allows you to use either Textile or Markdown format transparently. This is now included in the distribution, so that you don't need to install it.
6. Added links to context/list page so that you can go directly to todo/show/blah if you click the link for context 'blah'.
7. You can now set the date format you want to use in the file config/settings.yml. Use the strftime formats, with whatever separator you like. e.g. If you want 2004-12-31, the correct format is %Y-%m-%d. 31/12/2004 would be %d/%m/%Y. The same format is used for entry of the date in the due date box, and for display of the date in the list.
8. You can now add a new item to a particular project when you are viewing it at /project/show/[id]. This makes it easy to add a block of next actions to a project. Edit and delete buttons work on this page, but redirect you to /todo/list. I need to fix this.
9. Added a login system by using the login_generator:
<http://wiki.rubyonrails.com/rails/show/LoginGenerator>
The first time you access the system, you need to visit <http://[tracks_url]/login/signup> to enter a username and password. Then when you visit, you will be greeted with a login page. This stores a temporary cookie for your session. The whole app is protected by the login.
10. You can now add a new item to a particular context when you are viewing it at /context/show/[id]. This makes it easy to add a block of next actions to a project. Edit and delete buttons work on this page, but redirect you to /todo/list. I need to fix this.
11. Feeds! There's an RSS feed available at <http://[tracks_url]/feed/na_feed> which shows the last 15 next actions, and a text feed at <http://[tracks_url]/feed/na_text> which gives you a plain text list of all your next actions, sorted by context. You can use the latter to display your next actions on the desktop with GeekTool. Simply set up a shell command containing the following:
curl http://[tracks_url]/feed/na_text
Obviously, you need to replace [tracks_url] with the actual URL to the Tracks application.

View file

@ -1,80 +0,0 @@
Tracks: a GTD web application, built with Ruby on Rails
-------------------------------------------------------------
# Homepage:: http://www.rousette.org.uk/projects/
# Author:: bsag (http://www.rousette.org.uk/)
# Version:: 1.02
# Copyright:: (cc) 2004-2005 rousette.org.uk
# License:: GNU GPL
This is a still a work in progress; use caution when you run it, and make sure that you back up any important data. Full changenotes can be found in tracks/doc/CHANGENOTES.txt. Full API documentation can be found at tracks/doc/app/index.html.
**IF THIS CRASHES YOUR MACHINE AND LOSES YOUR DATA, IT'S NOT MY FAULT!**
## Installation
Before you start, you need to make sure that you have Ruby 1.8.2, mySQL and Rails 0.10.0. Note particularly the last requirement: this version *requires* Rails 0.10.0, or the re-writing of URLs will not work. In fact, the whole app will not work ;-). I have now included RedCloth 3.0.3 (which allows both Textile and Markdown format in the notes field) in the distribution. Find out more about RedCloth here:
<http://www.whytheluckystiff.net/ruby/redcloth/>
It's licensed under a BSD license. There's an odd error which pops up when using Redcloth from the lib directory in the application, but only when you are running under the development environment. To get around this, I've set environment.rb to look for a gem version of Redcloth first and use that, and only then fall back on the included version (see CHANGENOTES.txt for details).
### New users
1. Unzip tracks_1_02.zip somewhere in your home folder ( e.g. /Users/yourusername/Sites).
2. Make a mySQL database called tracks for which you have full access rights.
3. Import the tables and contents using the tracks_1.0.2_tables_mysql.sql and tracks_1.0.2_content_mysql.sql files (in tracks/db). If you don't want to start with dummy next actions in your database, simply delete everything from the line:
# Dump of table todos
to the end of the tracks_1.0.2_content_mysql.sql file.
4. Open the tracks/config/database.yml file, and enter your username and password details.
5. Open the tracks/config/setting.yml file, and enter your desired format for dates and your email address for the login/signup page (see CHANGENOTES.txt for details).
6. Open Terminal and navigate inside the tracks folder (e.g. cd /Users/yourusername/Sites/tracks).
7. Run the command: ruby script/server --environment=production
*IMPORTANT* If you already have an application running on WEBrick (Tracks or anything else), make sure that you stop the server, or run Tracks on a different port with ruby script/server --environment=production --port=3030
8. In a browser, go to http://127.0.0.1:3000/login/signup. This will allow you to choose a username and password for the admin user. Thereafter, anyone else trying to access login/signup will get a message that they are not allowed to sign up, and are given your email address to contact for permission. When you are logged in as the admin user, you can visit login/signup to sign up additional users. Note that Tracks isn't truly multi-user: all your users will be able to view (and edit) your data. The login system is just to restrict access to your sensitive data.
9. Have fun!
### Upgrading
1. Before you do anything else, BACK UP YOUR DATABASE (tables and content). Then make a separate export of the contents only (assuming that you want to move your data to the new version.)
2. In your contents export, delete the contents of the Users table. There are new fields in the users table, and it would be best to create your users again from scratch from the login/signup page.
3. For safety, rename your current Tracks directory to 'tracks-old' or something similar, and if you are able, create a new database for the new version. If you can't create a new database, delete the contents and tables in your old one MAKING SURE THAT YOU HAVE BACKED UP YOUR DATABASE AS IN STEP 1 FIRST.
4. Import first tracks_1.0.2_tables_mysql.sql from tracks/db, then your own contents dump file into the new database.
5. Fill in the correct details in database.yml and settings.yml, referring to the old copies in tracks-old if necessary.
6. From here, follow steps 6-9 for new users above. Don't forget that as you've deleted your users table, you'll need to re-create your users via login/signup.
## Using databases other than MySQL
Rick Bradley kindly converted the MySQL schema for Tracks to Postgresql format, so I was able to use that as a model for the new version - see tracks/db/tracks_1.0.2_postgres.sql. Remember that you'll also need to change the adapter line in database.yml:
adapter: postgresql
If you use SQLite, you could try the schema tracks/db/tracks_1.0.2_sqlite.sql, but note that it is untested. You also need to alter database.yml a little:
adapter: sqlite
dbfile: ../db/tracks.db
Substitute your real SQLite database name for tracks.db above.
## Other servers
WEBrick is the easiest server to get working to test out Tracks, and will be fine if you have Tracks installed on your own machine. One nice feature in Rails 0.10.0 is that WEBrick runs by default on the IP address 0.0.0.0, which means that you can access it via 127.0.0.1 when you are on the same machine, or via the external IP address of the machine running Tracks, so long as you can access the network of that machine from your current location. However, it is possible to use other servers, and the new re-writing rules of Rails 0.10.0 ('Routes' in environment/routes.rb) mean that very little configuration is needed.
### Apache
See the file tracks/README_RAILS for an example of an Apache conf. The file tracks/public/.htaccess contains the necessary re-write rules to call dispatch.cgi or dispatch.fcgi. All other rules are handled by routes.rb
### Lighttpd
Again, see tracks/README_RAILS for a working example of a lighttpd.conf file. Note that you'll want to change the line:
"bin-environment" => ( "RAILS_ENV" => "development"
to
"bin-environment" => ( "RAILS_ENV" => "production"
## Contacting me
I'd love any suggestions you have for improvements, bug-fixes etc. Email me on:
butshesagirl@rousette.org.uk
You can also leave bug reports, feature requests, and comments at:
<http://www.rousette.org.uk/projects/wiki/>

92
tracks/doc/README_FOR_APP Normal file
View file

@ -0,0 +1,92 @@
= Tracks: a GTD web application, built with Ruby on Rails
* Homepage: http://www.rousette.org.uk/projects/
* Author: bsag (http://www.rousette.org.uk/)
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg
* Version: 1.03
* Copyright: (cc) 2004-2005 rousette.org.uk
* License: GNU GPL
Trac (for bug reports and feature requests): http://dev.rousette.org.uk/report/6
Wiki (deprecated - please use Trac): http://www.rousette.org.uk/projects/wiki/
While fully usable for everyday use, Tracks is still a work in progress. Make sure that you take sensible precautions and back up all your data frequently. Full changenotes can be found in tracks/doc/CHANGENOTES.txt. Full API documentation can be found at tracks/doc/app/index.html.
<b>IF THIS CRASHES YOUR MACHINE AND LOSES YOUR DATA, IT'S NOT MY FAULT!</b>
== Installation
Before you start, you need to make sure that you have Ruby 1.8.2, Rails 0.12.1 (it <i>may</i> work with versions down to 0.11.0, but it's much better to get the latest version), and Redcloth 3.0.3. By far the easiest way to get these installed is using gems (see instructions on getting gems here http://wiki.rubyonrails.com/rails/show/GemRails). You also need some kind of database. MySQL is probably the most popular, but it's also easy to use PostgreSQL or SQLite. If you have Mac OS X Tiger, you already have Ruby 1.8.2 and SQLite3 installed, so all you need to do after installing Rails and Redcloth is to install the sqlite3-ruby gem (1.1.0).
=== New users
In the following, I'm assuming that you're using MySQL and the built-in WEBrick server. See the sections below for addtional instructions on using other databases and servers.
* Unzip tracks_1_03.zip somewhere in your home folder ( e.g. /Users/yourusername/Sites).
* Make a mySQL database called tracks for which you have full access rights. e.g. at the command line:
<tt>mysql -uroot -p</tt>
<tt>mysql> CREATE DATABASE tracks;</tt>
<tt>mysql> GRANT ALL PRIVILEGES ON tracks.* TO yourmysqluser@localhost IDENTIFIED BY 'password-goes-here' WITH GRANT OPTION;</tt>
* Import the tables using the <tt>tracks_1.0.3_mysql.sql</tt> files (in <tt>tracks/db</tt>). If you'd also like some example data to play with, you can import it from tracks_1.0.3_content.sql (also in <tt>tracks/db</tt>). You don't have to use the example data, but if you don't, you'll need to visit <tt>[tracks_url]/contexts</tt> first to add a few contexts before you add any next actions.
* Copy the files <tt>tracks/config/database.yml.tmpl</tt> and <tt>tracks/config/settings.yml.tmpl</tt> to the same file names without the <tt>*.tmpl</tt> extension (i.e. the extension should just be <tt>.yml</tt>).
* Copy the <tt>log.tmpl</tt> directory to <tt>log</tt>
* Open the <tt>tracks/config/database.yml</tt> file, and enter your username and password details for the database you set up before. If you're running in production mode, you only really need to set up the entry under 'production'.
* Open the <tt>tracks/config/setting.yml</tt> file, and enter your desired format for dates and your email address for the signup page, along with the other configuration settings. The comments in settings.yml should make it self explanatory. <b>NB:</b> It's very important that you don't use TABS in any of the .yml files. Just use spaces to indent.
* Check that the path to Ruby is correct in the <tt>tracks/public/dispatch.*</tt> files, and also <tt>/script/server</tt>. The default (<tt>#!/usr/bin/ruby</tt>) is fine for Mac OS X Tiger, but if you've installed Ruby yourself in <tt>/usr/local/bin</tt>, you'll need to change it.
* Open a terminal and navigate inside the tracks folder (e.g. cd /Users/yourusername/Sites/tracks).
* Run the following command (<b>Important:</b> If you already have an application running on WEBrick (Tracks or anything else), make sure that you stop the server, or run Tracks on a different port using the <tt>--port</tt> option):
ruby script/server --environment=production
* In a browser, go to <tt>http://0.0.0.0:3000/signup</tt>. This will allow you to choose a username and password for the admin user. Thereafter, anyone else trying to access <tt>/signup</tt> will get a message that they are not allowed to sign up, and are given your email address to contact for permission. When you are logged in as the admin user, you can visit <tt>/signup</tt> to sign up additional users, and visit <tt>/login</tt> to login yourself. Note that Tracks isn't truly multi-user: all your users will be able to view (and edit) your data. The login system is just to restrict access to your sensitive data.
* Have fun!
=== Upgrading from a previous version of Tracks
* Before you do anything else, <b>BACK UP YOUR DATABASE</b> (tables and content). Then make a separate export of the contents only (assuming that you want to move your data to the new version.)
* There are new fields in the projects and contexts table, so you might need to edit your contents dump accordingly. Use <tt>db/tracks_1.0.3_content.sql</tt> as a guide. If you were using Tracks 1.02a, you shouldn't need to edit either the users or todos table, but check <tt>db/tracks_1.0.3_content.sql</tt> if you are unsure. If you are upgrading from an earlier version of Tracks, it would be safer not to include the contents of your previous users table, but to create your users again through <tt>/signup</tt>.
* For safety, rename your current Tracks directory to 'tracks-old' or something similar, and if you are able, create a new database for the new version. If you can't create a new database, delete the contents and tables in your old one MAKING SURE THAT YOU HAVE BACKED UP YOUR DATABASE FIRST.
* Import <tt>tracks_1.0.3_mysql.sql</tt> from <tt>tracks/db</tt> (or the appropriate schema for your database), then your own contents dump file into the new database.
* Copy the files <tt>tracks/config/database.yml.tmpl</tt> and <tt>tracks/config/settings.yml.tmpl</tt> to the same file names without the <tt>*.tmpl</tt> extension (i.e. the extension should just be <tt>.yml</tt>).
* Copy the <tt>log.tmpl</tt> directory to <tt>log</tt>
* Fill in the correct details in database.yml and settings.yml, referring to the old copies in tracks-old if necessary. Note that there are some new fields to be added in settings.yml, so make sure that you get a fresh copy, or you'll get errors.
* Check that the path to Ruby is correct in the <tt>tracks/public/dispatch.*</tt> files, and also <tt>/script/server</tt>. The default (<tt>#!/usr/bin/ruby</tt>) is fine for Mac OS X Tiger, but if you've installed Ruby yourself in <tt>/usr/local/bin</tt>, you'll need to change it.
* From here, follow the remaining steps for new users above to start the server. Don't forget that if you've deleted your users table, you'll need to re-create your users via <tt>/signup</tt>. Signup is now at <tt>http://0.0.0.0:3000/signup</tt>, and login at <tt>http://0.0.0.0:3000/login</tt>.
== Using databases other than MySQL
Rick Bradley kindly converted the MySQL schema for Tracks to Postgresql format, so I was able to use that as a model for the new version - see <tt>tracks/db/tracks_1.0.3_postgres.sql</tt>. There's also a schema for SQLite/SQLite3 (<tt>tracks/db/tracks_1.0.3_sqlite.sql</tt>). Remember that you'll also need to change the adapter line in <tt>database.yml</tt>:
adapter: postgresql
or for SQLite3:
adapter: sqlite3
dbfile: /fullpathto/db/yoursqlite.db
If you're using SQLite 2.x, substitute 'sqlite' for 'sqlite3' above. Also note that those are the <b>only</b> lines you need for SQLite; the username, password, database and host lines are not necessary.
== Other servers
WEBrick is the easiest server to get working to test out Tracks, and will be fine if you have Tracks installed on your own machine. One nice feature in Rails 0.12.1 is that WEBrick runs by default on the IP address 0.0.0.0, which means that you can access it via 127.0.0.1 when you are on the same machine, or via the external IP address of the machine running Tracks, so long as you can access the network of that machine from your current location. However, it is possible to use other servers, and the new re-writing rules of Rails 0.12.1 ('Routes' in <tt>environment/routes.rb</tt>) mean that very little configuration is needed.
=== Apache
See the file tracks/README_RAILS for an example of an Apache conf. The file tracks/public/.htaccess contains the necessary re-write rules to call dispatch.cgi or dispatch.fcgi. All other rules are handled by routes.rb
=== Lighttpd
Again, see tracks/README_RAILS for a working example of a lighttpd.conf file. Note that you'll want to change the line:
"bin-environment" => ( "RAILS_ENV" => "development"
to
"bin-environment" => ( "RAILS_ENV" => "production"
== Contacting me
I'd love any suggestions you have for improvements, bug-fixes etc. Email me at: butshesagirl@rousette.org.uk
You can also leave bug reports, feature requests, and comments at: http://dev.rousette.org.uk/report/6

View file

@ -60,141 +60,141 @@
* saying "nothing to setup".
*/
Calendar.setup = function (params) {
function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
param_default("inputField", null);
param_default("displayArea", null);
param_default("button", null);
param_default("eventName", "click");
param_default("ifFormat", "%Y/%m/%d");
param_default("daFormat", "%Y/%m/%d");
param_default("singleClick", true);
param_default("disableFunc", null);
param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined
param_default("dateText", null);
param_default("firstDay", null);
param_default("align", "Br");
param_default("range", [1900, 2999]);
param_default("weekNumbers", true);
param_default("flat", null);
param_default("flatCallback", null);
param_default("onSelect", null);
param_default("onClose", null);
param_default("onUpdate", null);
param_default("date", null);
param_default("showsTime", false);
param_default("timeFormat", "24");
param_default("electric", true);
param_default("step", 2);
param_default("position", null);
param_default("cache", false);
param_default("showOthers", false);
param_default("multiple", null);
param_default("inputField", null);
param_default("displayArea", null);
param_default("button", null);
param_default("eventName", "click");
param_default("ifFormat", "%Y/%m/%d");
param_default("daFormat", "%Y/%m/%d");
param_default("singleClick", true);
param_default("disableFunc", null);
param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined
param_default("dateText", null);
param_default("firstDay", null);
param_default("align", "Br");
param_default("range", [1900, 2999]);
param_default("weekNumbers", true);
param_default("flat", null);
param_default("flatCallback", null);
param_default("onSelect", null);
param_default("onClose", null);
param_default("onUpdate", null);
param_default("date", null);
param_default("showsTime", false);
param_default("timeFormat", "24");
param_default("electric", true);
param_default("step", 2);
param_default("position", null);
param_default("cache", false);
param_default("showOthers", false);
param_default("multiple", null);
var tmp = ["inputField", "displayArea", "button"];
for (var i in tmp) {
if (typeof params[tmp[i]] == "string") {
params[tmp[i]] = document.getElementById(params[tmp[i]]);
}
}
if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) {
alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");
return false;
}
var tmp = ["inputField", "displayArea", "button"];
for (var i in tmp) {
if (typeof params[tmp[i]] == "string") {
params[tmp[i]] = document.getElementById(params[tmp[i]]);
}
}
if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) {
alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");
return false;
}
function onSelect(cal) {
var p = cal.params;
var update = (cal.dateClicked || p.electric);
if (update && p.inputField) {
p.inputField.value = cal.date.print(p.ifFormat);
if (typeof p.inputField.onchange == "function")
p.inputField.onchange();
}
if (update && p.displayArea)
p.displayArea.innerHTML = cal.date.print(p.daFormat);
if (update && typeof p.onUpdate == "function")
p.onUpdate(cal);
if (update && p.flat) {
if (typeof p.flatCallback == "function")
p.flatCallback(cal);
}
if (update && p.singleClick && cal.dateClicked)
cal.callCloseHandler();
};
function onSelect(cal) {
var p = cal.params;
var update = (cal.dateClicked || p.electric);
if (update && p.inputField) {
p.inputField.value = cal.date.print(p.ifFormat);
if (typeof p.inputField.onchange == "function")
p.inputField.onchange();
}
if (update && p.displayArea)
p.displayArea.innerHTML = cal.date.print(p.daFormat);
if (update && typeof p.onUpdate == "function")
p.onUpdate(cal);
if (update && p.flat) {
if (typeof p.flatCallback == "function")
p.flatCallback(cal);
}
if (update && p.singleClick && cal.dateClicked)
cal.callCloseHandler();
};
if (params.flat != null) {
if (typeof params.flat == "string")
params.flat = document.getElementById(params.flat);
if (!params.flat) {
alert("Calendar.setup:\n Flat specified but can't find parent.");
return false;
}
var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect);
cal.showsOtherMonths = params.showOthers;
cal.showsTime = params.showsTime;
cal.time24 = (params.timeFormat == "24");
cal.params = params;
cal.weekNumbers = params.weekNumbers;
cal.setRange(params.range[0], params.range[1]);
cal.setDateStatusHandler(params.dateStatusFunc);
cal.getDateText = params.dateText;
if (params.ifFormat) {
cal.setDateFormat(params.ifFormat);
}
if (params.inputField && typeof params.inputField.value == "string") {
cal.parseDate(params.inputField.value);
}
cal.create(params.flat);
cal.show();
return false;
}
if (params.flat != null) {
if (typeof params.flat == "string")
params.flat = document.getElementById(params.flat);
if (!params.flat) {
alert("Calendar.setup:\n Flat specified but can't find parent.");
return false;
}
var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect);
cal.showsOtherMonths = params.showOthers;
cal.showsTime = params.showsTime;
cal.time24 = (params.timeFormat == "24");
cal.params = params;
cal.weekNumbers = params.weekNumbers;
cal.setRange(params.range[0], params.range[1]);
cal.setDateStatusHandler(params.dateStatusFunc);
cal.getDateText = params.dateText;
if (params.ifFormat) {
cal.setDateFormat(params.ifFormat);
}
if (params.inputField && typeof params.inputField.value == "string") {
cal.parseDate(params.inputField.value);
}
cal.create(params.flat);
cal.show();
return false;
}
var triggerEl = params.button || params.displayArea || params.inputField;
triggerEl["on" + params.eventName] = function() {
var dateEl = params.inputField || params.displayArea;
var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
var mustCreate = false;
var cal = window.calendar;
if (dateEl)
params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
if (!(cal && params.cache)) {
window.calendar = cal = new Calendar(params.firstDay,
params.date,
params.onSelect || onSelect,
params.onClose || function(cal) { cal.hide(); });
cal.showsTime = params.showsTime;
cal.time24 = (params.timeFormat == "24");
cal.weekNumbers = params.weekNumbers;
mustCreate = true;
} else {
if (params.date)
cal.setDate(params.date);
cal.hide();
}
if (params.multiple) {
cal.multiple = {};
for (var i = params.multiple.length; --i >= 0;) {
var d = params.multiple[i];
var ds = d.print("%Y%m%d");
cal.multiple[ds] = d;
}
}
cal.showsOtherMonths = params.showOthers;
cal.yearStep = params.step;
cal.setRange(params.range[0], params.range[1]);
cal.params = params;
cal.setDateStatusHandler(params.dateStatusFunc);
cal.getDateText = params.dateText;
cal.setDateFormat(dateFmt);
if (mustCreate)
cal.create();
cal.refresh();
if (!params.position)
cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
else
cal.showAt(params.position[0], params.position[1]);
return false;
};
var triggerEl = params.button || params.displayArea || params.inputField;
triggerEl["on" + params.eventName] = function() {
var dateEl = params.inputField || params.displayArea;
var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
var mustCreate = false;
var cal = window.calendar;
if (dateEl)
params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
if (!(cal && params.cache)) {
window.calendar = cal = new Calendar(params.firstDay,
params.date,
params.onSelect || onSelect,
params.onClose || function(cal) { cal.hide(); });
cal.showsTime = params.showsTime;
cal.time24 = (params.timeFormat == "24");
cal.weekNumbers = params.weekNumbers;
mustCreate = true;
} else {
if (params.date)
cal.setDate(params.date);
cal.hide();
}
if (params.multiple) {
cal.multiple = {};
for (var i = params.multiple.length; --i >= 0;) {
var d = params.multiple[i];
var ds = d.print("%Y%m%d");
cal.multiple[ds] = d;
}
}
cal.showsOtherMonths = params.showOthers;
cal.yearStep = params.step;
cal.setRange(params.range[0], params.range[1]);
cal.params = params;
cal.setDateStatusHandler(params.dateStatusFunc);
cal.getDateText = params.dateText;
cal.setDateFormat(dateFmt);
if (mustCreate)
cal.create();
cal.refresh();
if (!params.position)
cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
else
cal.showAt(params.position[0], params.position[1]);
return false;
};
return cal;
return cal;
};

File diff suppressed because it is too large Load diff

View file

@ -120,10 +120,10 @@ function $() {
if (!Array.prototype.push) {
Array.prototype.push = function() {
var startLength = this.length;
for (var i = 0; i < arguments.length; i++)
var startLength = this.length;
for (var i = 0; i < arguments.length; i++)
this[startLength + i] = arguments[i];
return this.length;
return this.length;
}
}

View file

@ -13,54 +13,54 @@ document.getElementById(idname).style.display = (document.getElementById(idname)
function toggleAllImages()
{
var cookies = document.cookie.split(';');
var cookies = document.cookie.split(';');
for(var i = 0; i < cookies.length; i++)
{
var str = cookies[i].split('=')[0];
for(var i = 0; i < cookies.length; i++)
{
var str = cookies[i].split('=')[0];
if(str.indexOf('toggle_context_') != -1)
{
var id = str.split('_')[2];
if(getCookie(str) == 'collapsed')
{
toggleSingle('c'+id);
toggleImage('toggle_context_'+id);
}
}
}
if(str.indexOf('toggle_context_') != -1)
{
var id = str.split('_')[2];
if(getCookie(str) == 'collapsed')
{
toggleSingle('c'+id);
toggleImage('toggle_context_'+id);
}
}
}
}
function toggleImage(idname)
{
if(document.images)
{
if(document[idname].src.indexOf('collapse.png') != -1)
{
document[idname].src = '/images/expand.png';
SetCookie(idname, "collapsed");
}
else
{
document[idname].src = '/images/collapse.png';
SetCookie(idname, "expanded");
}
}
if(document.images)
{
if(document[idname].src.indexOf('collapse.png') != -1)
{
document[idname].src = '/images/expand.png';
SetCookie(idname, "collapsed");
}
else
{
document[idname].src = '/images/collapse.png';
SetCookie(idname, "expanded");
}
}
}
function SetCookie (name, value) {
var argv = SetCookie.arguments;
var argc = SetCookie.arguments.length;
var expires = (argc > 2) ? argv[2] : null;
var path = (argc > 3) ? argv[3] : null;
var domain = (argc > 4) ? argv[4] : null;
var secure = (argc > 5) ? argv[5] : false;
document.cookie = name + "=" + escape (value) +
((expires == null) ? "" : ("; expires=" +
expires.toGMTString())) +
((path == null) ? "" : ("; path=" + path)) +
((domain == null) ? "" : ("; domain=" + domain)) +
((secure == true) ? "; secure" : "");
var argv = SetCookie.arguments;
var argc = SetCookie.arguments.length;
var expires = (argc > 2) ? argv[2] : null;
var path = (argc > 3) ? argv[3] : null;
var domain = (argc > 4) ? argv[4] : null;
var secure = (argc > 5) ? argv[5] : false;
document.cookie = name + "=" + escape (value) +
((expires == null) ? "" : ("; expires=" +
expires.toGMTString())) +
((path == null) ? "" : ("; path=" + path)) +
((domain == null) ? "" : ("; domain=" + domain)) +
((secure == true) ? "; secure" : "");
}
var bikky = document.cookie;

View file

@ -1,31 +1,31 @@
/* Stylesheet for the main listing page */
body {
font-family: "Lucida Grande", Verdana, Geneva, Arial, sans-serif;
font-family: "Lucida Grande", Verdana, Geneva, Arial, sans-serif;
font-size: 12px;
line-height: 19px;
padding: 0px 10px;
margin: 0px;
background: #eee;
}
padding: 0px 10px;
margin: 0px;
background: #eee;
}
p {
padding: 2px;
}
padding: 2px;
}
a, a:link, a:active, a:visited {
color: #cc3334;
text-decoration: none;
padding-left: 1px;
padding-right: 1px;
}
color: #cc3334;
text-decoration: none;
padding-left: 1px;
padding-right: 1px;
}
a:hover {
color: #fff;
background-color: #cc3334;
}
color: #fff;
background-color: #cc3334;
}
/* Rules for the icon links */
img.edit_item {background-image: url(../images/edit_off.png); background-repeat: no-repeat; border: none;}
@ -53,22 +53,22 @@ a.show_notes:hover {background-image: url(../images/notes_on.png); background-re
/* Structural divs */
#display_box {
float: left;
width: 450px;
margin: 0px 15px 50px 15px;
}
float: left;
width: 450px;
margin: 0px 15px 50px 15px;
}
#full_width_display {
float: left;
width: 800px;
margin: 0px 15px 50px 15px;
}
float: left;
width: 800px;
margin: 0px 15px 50px 15px;
}
#display_box_projects {
float: left;
width: 820px;
margin: 0px 15px 50px 15px;
}
float: left;
width: 820px;
margin: 0px 15px 50px 15px;
}
/* Navigation links at the top */
@ -76,73 +76,73 @@ a.show_notes:hover {background-image: url(../images/notes_on.png); background-re
margin: 15px;
width: 822px;
}
#navlist {
margin: 0;
padding: 0 0 20px 10px;
border-bottom: 1px solid #000;
}
margin: 0;
padding: 0 0 20px 10px;
border-bottom: 1px solid #000;
}
#navlist ul, #navlist li {
margin: 0;
padding: 0;
display: inline;
list-style-type: none;
}
margin: 0;
padding: 0;
display: inline;
list-style-type: none;
}
#navlist a:link, #navlist a:visited {
float: left;
line-height: 14px;
font-weight: bold;
margin: 0 10px 4px 10px;
text-decoration: none;
color: #999;
}
float: left;
line-height: 14px;
font-weight: bold;
margin: 0 10px 4px 10px;
text-decoration: none;
color: #999;
}
#navlist a:link#current, #navlist a:visited#current, #navlist a:hover {
border-bottom: 4px solid #000;
padding-bottom: 2px;
background: transparent;
color: #000;
}
border-bottom: 4px solid #000;
padding-bottom: 2px;
background: transparent;
color: #000;
}
#navlist a:hover { color: #000; }
.contexts {
padding: 0px 5px 2px 5px;
border: 1px solid #999;
margin: 0px 0px 15px 0px;
background: #fff;
}
padding: 0px 5px 2px 5px;
border: 1px solid #999;
margin: 0px 0px 15px 0px;
background: #fff;
}
.contexts h2 {
background: #CCC;
padding: 5px;
margin-top: 0px;
margin-left: -5px;
margin-right: -5px;
color: #fff;
text-shadow: rgba(0,0,0,.4) 0px 2px 5px;
}
background: #CCC;
padding: 5px;
margin-top: 0px;
margin-left: -5px;
margin-right: -5px;
color: #fff;
text-shadow: rgba(0,0,0,.4) 0px 2px 5px;
}
.new_actions {
padding: 0px 5px 2px 5px;
background: #E7FDDE;
border: 1px solid #57A620;
padding: 5px;
margin-bottom: 15px;
}
padding: 0px 5px 2px 5px;
background: #E7FDDE;
border: 1px solid #57A620;
padding: 5px;
margin-bottom: 15px;
}
.new_actions h2 {
padding: 0 px 5px 0px 5px;
color: #57A620;
}
padding: 0 px 5px 0px 5px;
color: #57A620;
}
h2 a, h2 a:link, h2 a:active, h2 a:visited {
color: #fff;
text-decoration: none;
}
color: #fff;
text-decoration: none;
}
h2 a:hover {
color: #cc3334;
background-color: transparent;
@ -150,19 +150,19 @@ h2 a:hover {
}
a.refresh, a.refresh:link, a.refresh:active {color: #57A620;}
#input_box {
margin-left: 490px;
margin-top: 20px;
width: 313px;
padding: 0px 15px 0px 15px;
/* border: 1px solid #999; */
}
padding: 0px 15px 0px 15px;
/* border: 1px solid #999; */
}
#input_box h2 {
color: #999;
}
color: #999;
}
#input_box ul {list-style-type: circle; font-size: 0.9em;}
@ -173,16 +173,16 @@ a.refresh, a.refresh:link, a.refresh:active {color: #57A620;}
div.big-box, div.big-box a, div.big-box a:hover {
float: left;
vertical-align: middle;
background-color: transparent;
}
vertical-align: middle;
background-color: transparent;
}
.checkbox {
float: left;
margin-left: 10px;
vertical-align: middle;
}
.description {
margin-left: 70px;
margin-right: 10px;
@ -196,87 +196,87 @@ div.big-box, div.big-box a, div.big-box a:hover {
#footer {
clear: both;
font-size: 0.8em;
text-align: center;
color: #999;
margin: 20px 20px 5px 20px;
padding: 0px;
}
/* The notes which may be attached to an item */
font-size: 0.8em;
text-align: center;
color: #999;
margin: 20px 20px 5px 20px;
padding: 0px;
}
/* The notes which may be attached to an item */
.notes {
margin: 5px 20px;
padding: 3px;
border: 1px solid #F5ED59;
background: #FAF6AE;
color: #666666;
}
padding: 3px;
border: 1px solid #F5ED59;
background: #FAF6AE;
color: #666666;
}
.notes p, .notes li {
padding: 1px 5px;
margin: 0px;
font-size: 12px;
}
padding: 1px 5px;
margin: 0px;
font-size: 12px;
}
.notes ul {
list-style-type: disc;
margin-left: 5px;
list-style-type: disc;
margin-left: 5px;
}
.notes ol {
list-style-type: decimal;
margin-left: 5px;
list-style-type: decimal;
margin-left: 5px;
}
/* The alert box notifications */
.confirmation {
margin: 20px 50px 20px 490px;
border: 1px solid #007E00;
background-color: #c2ffc2;
padding: 5px;
color: #007E00;
text-align: center;
}
border: 1px solid #007E00;
background-color: #c2ffc2;
padding: 5px;
color: #007E00;
text-align: center;
}
.warning {
margin: 20px 50px 20px 490px;
border: 1px solid #ED2E38;
background-color: #F6979C;
padding: 5px;
color: #ED2E38;
text-align: center;
}
/* Draw attention to some text
Same format as traffic lights */
border: 1px solid #ED2E38;
background-color: #F6979C;
padding: 5px;
color: #ED2E38;
text-align: center;
}
/* Draw attention to some text
Same format as traffic lights */
.red {
color: #fff;
background: #f00;
padding: 1px;
font-size: 10px;
}
color: #fff;
background: #f00;
padding: 1px;
font-size: 10px;
}
.amber {
color: #fff;
background: #ff6600;
padding: 1px;
font-size: 10px;
}
color: #fff;
background: #ff6600;
padding: 1px;
font-size: 10px;
}
.green {
color: #fff;
background: #33cc00;
padding: 1px;
font-size: 10px;
}
color: #fff;
background: #33cc00;
padding: 1px;
font-size: 10px;
}
.grey {
color: #fff;
background: #999;
padding: 1px;
font-size: 10px;
}
color: #fff;
background: #999;
padding: 1px;
font-size: 10px;
}
.info {
color: #fff;
background: #CCC;
@ -310,22 +310,22 @@ div.big-box, div.big-box a, div.big-box a:hover {
/* Shows the number of undone next action */
.badge {
color: #fff;
background: #f00;
padding: 5px;
font-size: 16px;
margin: 10px 10px 0px 0px;
background: #f00;
padding: 5px;
font-size: 16px;
margin: 10px 10px 0px 0px;
}
ul {
list-style-type: none;
margin-left: -25px;
}
list-style-type: none;
margin-left: -25px;
}
li {
font-size: 1.1em;
padding: 3px 0px;
}
font-size: 1.1em;
padding: 3px 0px;
}
.even_row {
background: #fff;
padding: 5px 5px 5px 10px;
@ -346,8 +346,8 @@ li {
/* Right align labels in forms */
.label {
text-align: right;
}
text-align: right;
}
input {
vertical-align: middle;