mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-24 03:00:12 +01:00
Merge branch 'master' into clinton/master
* master: (40 commits)
Updated the manual in HTML, PDF and .tex format.
Minor updates to the example db.
set version to 1.7RC
update version in template
update changelog
OpenSearch support.
update the sample sqlite databases and update sample content sql. Solves #802
forgot ot change one version to 1.7
update documentation for 1.7rc release
No point in changing the name of the OpenID identity column in users table. Use the existing one.
show recurrence pattern in :title of a recurring todo. Needed slight refactoring to make it happen
Re-write OpenID code to use new authentication plugin. Tested to work!
Upgraded to open_id_authentication plugin at 00d8bc7f97 and unpacked ruby-openid gem version 2.1.2.
remove old compressed js and css from the asset_packager plugin that we don't use anymore
applied the patches from Jakub to improve the mobile html
adds test for changing context on a todo in tag view. Resolves #762
Add testcase where changing the name of a project should be reflected in the default project name. resolves #756
add testcase for deleting a user. Resolves #734
implement suggestions for search in #787
fix failing tests
...
This commit is contained in:
commit
4fb4a98bc8
1905 changed files with 119478 additions and 58883 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -9,3 +9,5 @@ nbproject
|
|||
vendor/plugins/query_trace/
|
||||
db/schema.rb
|
||||
.dotest
|
||||
public/javascripts/cache
|
||||
public/stylesheets/cache
|
||||
5
README
5
README
|
|
@ -8,12 +8,10 @@
|
|||
* Mailing list: http://lists.rousette.org.uk/mailman/listinfo/tracks-discuss
|
||||
* Original developer: bsag (http://www.rousette.org.uk/)
|
||||
* Contributors: http://dev.rousette.org.uk/wiki/Tracks/Contributing/Contributors
|
||||
* Version: 1.6
|
||||
* Version: 1.7RC
|
||||
* Copyright: (cc) 2004-2008 rousette.org.uk.
|
||||
* License: GNU GPL
|
||||
|
||||
**An important note for version 1.6: OpenID support is broken in this release. The fix isn't trivial because of changes to the `ruby-openid` gem, so we wanted to get this version out now and fix OpenID for the next release. If you depend on OpenID integration, we recommend waiting until the next release.**
|
||||
|
||||
All the documentation for Tracks can be found within the /doc directory. It contains a manual in HTML (manual.html) or PDF format (manual.pdf), and this includes full instructions for both new installations and upgrades from older installations of Tracks. The instructions might appear long and intimidatingly complex, but that is mostly because of the number of different platforms supported, and the different configurations which can be used (e.g. running Tracks on your local computer or on a remote server). If you choose the appropriate section for your situation (installation vs. upgrade), and use the easiest (recommended) method, you should find the instructions easy to follow. If you encounter problems, try searching the wiki, forum or mailing list (URLs above), and ask a question if you cannot find a solution to your problem.
|
||||
|
||||
For those upgrading, change notes are available in /doc/CHANGELOG. If you are thinking about contributing towards the development of Tracks, please read /doc/README_DEVELOPERS for general information, or /doc/tracks_api_wrapper.rb for information on Tracks' API.
|
||||
|
|
@ -21,3 +19,4 @@ For those upgrading, change notes are available in /doc/CHANGELOG. If you are th
|
|||
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, taking particular care when you are upgrading.
|
||||
|
||||
Enjoy being productive!
|
||||
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ class ApplicationController < ActionController::Base
|
|||
before_filter :set_time_zone
|
||||
prepend_before_filter :login_required
|
||||
prepend_before_filter :enable_mobile_content_negotiation
|
||||
after_filter :restore_content_type_for_mobile
|
||||
after_filter :set_charset
|
||||
|
||||
|
||||
|
||||
include ActionView::Helpers::TextHelper
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
||||
helper_method :format_date, :markdown
|
||||
|
||||
# By default, sets the charset to UTF-8 if it isn't already set
|
||||
|
|
@ -148,21 +148,15 @@ class ApplicationController < ActionController::Base
|
|||
# during the processing and then setting it to 'text/html' in an
|
||||
# 'after_filter' -LKM 2007-04-01
|
||||
def mobile?
|
||||
return params[:format] == 'm' || response.content_type == MOBILE_CONTENT_TYPE
|
||||
return params[:format] == 'm'
|
||||
end
|
||||
|
||||
def enable_mobile_content_negotiation
|
||||
if mobile?
|
||||
request.accepts.unshift(Mime::Type::lookup(MOBILE_CONTENT_TYPE))
|
||||
request.format = :m
|
||||
end
|
||||
end
|
||||
|
||||
def restore_content_type_for_mobile
|
||||
if mobile?
|
||||
response.content_type = 'text/html'
|
||||
end
|
||||
end
|
||||
|
||||
def create_todo_from_recurring_todo(rt, date=nil)
|
||||
# create todo and initialize with data from recurring_todo rt
|
||||
todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
|
||||
|
|
@ -234,8 +228,13 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def init_data_for_sidebar
|
||||
@projects = @projects || current_user.projects.find(:all, :include => [:default_context ])
|
||||
@contexts = @contexts || current_user.contexts
|
||||
@completed_projects = current_user.projects.completed
|
||||
@hidden_projects = current_user.projects.hidden
|
||||
@active_projects = current_user.projects.active
|
||||
|
||||
@active_contexts = current_user.contexts.active
|
||||
@hidden_contexts = current_user.contexts.hidden
|
||||
|
||||
init_not_done_counts
|
||||
if prefs.show_hidden_projects_in_sidebar
|
||||
init_project_hidden_todo_counts(['project'])
|
||||
|
|
@ -244,13 +243,13 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
def init_not_done_counts(parents = ['project','context'])
|
||||
parents.each do |parent|
|
||||
eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || Todo.count(:conditions => ['user_id = ? and state = ?', current_user.id, 'active'], :group => :#{parent}_id)")
|
||||
eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)")
|
||||
end
|
||||
end
|
||||
|
||||
def init_project_hidden_todo_counts(parents = ['project','context'])
|
||||
parents.each do |parent|
|
||||
eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || Todo.count(:conditions => ['user_id = ? and (state = ? or state = ?)', current_user.id, 'project_hidden', 'active'], :group => :#{parent}_id)")
|
||||
eval("@#{parent}_project_hidden_todo_counts = @#{parent}_project_hidden_todo_counts || current_user.todos.count(:conditions => ['state = ? or state = ?', 'project_hidden', 'active'], :group => :#{parent}_id)")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@ class ContextsController < ApplicationController
|
|||
def render_contexts_mobile
|
||||
lambda do
|
||||
@page_title = "TRACKS::List Contexts"
|
||||
@active_contexts = @contexts.find(:all, { :conditions => ["hide = ?", false]})
|
||||
@hidden_contexts = @contexts.find(:all, { :conditions => ["hide = ?", true]})
|
||||
@active_contexts = @contexts.active
|
||||
@hidden_contexts = @contexts.hidden
|
||||
@down_count = @active_contexts.size + @hidden_contexts.size
|
||||
cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
|
||||
render :action => 'index_mobile'
|
||||
|
|
@ -201,6 +201,9 @@ class ContextsController < ApplicationController
|
|||
:conditions => ['todos.state = ? AND (todos.project_id IS ? OR projects.state = ?)', 'active', nil, 'active'],
|
||||
:order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC",
|
||||
:include => [:project, :tags])
|
||||
|
||||
@projects = current_user.projects
|
||||
|
||||
@count = @not_done_todos.size
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ class FeedlistController < ApplicationController
|
|||
@contexts = current_user.contexts
|
||||
end
|
||||
|
||||
@active_projects = @projects.select{ |p| p.active? }
|
||||
@hidden_projects = @projects.select{ |p| p.hidden? }
|
||||
@completed_projects = @projects.select{ |p| p.completed? }
|
||||
@active_projects = current_user.projects.active
|
||||
@hidden_projects = current_user.projects.hidden
|
||||
@completed_projects = current_user.projects.completed
|
||||
|
||||
@active_contexts = @contexts.select{ |c| !c.hidden? }
|
||||
@hidden_contexts = @contexts.select{ |c| c.hidden? }
|
||||
@active_contexts = current_user.contexts.active
|
||||
@hidden_contexts = current_user.contexts.hidden
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :layout => 'standard' }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
class IntegrationsController < ApplicationController
|
||||
|
||||
skip_before_filter :login_required, :only => :search_plugin
|
||||
|
||||
def index
|
||||
@page_title = 'TRACKS::Integrations'
|
||||
end
|
||||
|
|
@ -22,4 +24,12 @@ class IntegrationsController < ApplicationController
|
|||
context = current_user.contexts.find params[:context_id]
|
||||
render :partial => 'applescript2', :locals => { :context => context }
|
||||
end
|
||||
|
||||
def search_plugin
|
||||
@icon_data = [File.open(RAILS_ROOT + '/public/images/done.png').read].
|
||||
pack('m').gsub(/\n/, '')
|
||||
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,114 +6,45 @@ class LoginController < ApplicationController
|
|||
skip_before_filter :login_required
|
||||
before_filter :login_optional
|
||||
before_filter :get_current_user
|
||||
open_id_consumer if openid_enabled?
|
||||
|
||||
def login
|
||||
@page_title = "TRACKS::Login"
|
||||
@openid_url = cookies[:openid_url] if openid_enabled?
|
||||
case request.method
|
||||
when :post
|
||||
if @user = User.authenticate(params['user_login'], params['user_password'])
|
||||
session['user_id'] = @user.id
|
||||
# If checkbox on login page checked, we don't expire the session after 1 hour
|
||||
# of inactivity and we remember this user for future browser sessions
|
||||
session['noexpiry'] = params['user_noexpiry']
|
||||
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||
notify :notice, "Login successful: session #{msg}"
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year, :secure => TRACKS_COOKIES_SECURE }
|
||||
unless should_expire_sessions?
|
||||
@user.remember_me
|
||||
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => TRACKS_COOKIES_SECURE }
|
||||
if openid_enabled? && using_open_id?
|
||||
login_openid
|
||||
else
|
||||
@page_title = "TRACKS::Login"
|
||||
case request.method
|
||||
when :post
|
||||
if @user = User.authenticate(params['user_login'], params['user_password'])
|
||||
session['user_id'] = @user.id
|
||||
# If checkbox on login page checked, we don't expire the session after 1 hour
|
||||
# of inactivity and we remember this user for future browser sessions
|
||||
session['noexpiry'] = params['user_noexpiry']
|
||||
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||
notify :notice, "Login successful: session #{msg}"
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year, :secure => TRACKS_COOKIES_SECURE }
|
||||
unless should_expire_sessions?
|
||||
@user.remember_me
|
||||
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => TRACKS_COOKIES_SECURE }
|
||||
end
|
||||
redirect_back_or_home
|
||||
return
|
||||
else
|
||||
@login = params['user_login']
|
||||
notify :warning, "Login unsuccessful"
|
||||
end
|
||||
redirect_back_or_home
|
||||
return
|
||||
else
|
||||
@login = params['user_login']
|
||||
notify :warning, "Login unsuccessful"
|
||||
end
|
||||
when :get
|
||||
if User.no_users_yet?
|
||||
redirect_to :controller => 'users', :action => 'new'
|
||||
return
|
||||
end
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.m { render :action => 'login_mobile.html.erb', :layout => 'mobile' }
|
||||
when :get
|
||||
if User.no_users_yet?
|
||||
redirect_to :controller => 'users', :action => 'new'
|
||||
return
|
||||
end
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.m { render :action => 'login_mobile.html.erb', :layout => 'mobile' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def begin
|
||||
# If the URL was unusable (either because of network conditions,
|
||||
# a server error, or that the response returned was not an OpenID
|
||||
# identity page), the library will return HTTP_FAILURE or PARSE_ERROR.
|
||||
# Let the user know that the URL is unusable.
|
||||
case open_id_response.status
|
||||
when OpenID::SUCCESS
|
||||
session['openid_url'] = params[:openid_url]
|
||||
session['user_noexpiry'] = params[:user_noexpiry]
|
||||
# The URL was a valid identity URL. Now we just need to send a redirect
|
||||
# to the server using the redirect_url the library created for us.
|
||||
|
||||
# redirect to the server
|
||||
respond_to do |format|
|
||||
format.html { redirect_to open_id_response.redirect_url((request.protocol + request.host_with_port + "/"), open_id_complete_url) }
|
||||
format.m { redirect_to open_id_response.redirect_url((request.protocol + request.host_with_port + "/"), formatted_open_id_complete_url(:format => 'm')) }
|
||||
end
|
||||
else
|
||||
notify :warning, "Unable to find openid server for <q>#{openid_url}</q>"
|
||||
redirect_to_login
|
||||
end
|
||||
end
|
||||
|
||||
def complete
|
||||
openid_url = session['openid_url']
|
||||
if openid_url.blank?
|
||||
notify :error, "expected an openid_url"
|
||||
end
|
||||
|
||||
case open_id_response.status
|
||||
when OpenID::FAILURE
|
||||
# In the case of failure, if info is non-nil, it is the
|
||||
# URL that we were verifying. We include it in the error
|
||||
# message to help the user figure out what happened.
|
||||
if open_id_response.identity_url
|
||||
msg = "Verification of #{openid_url}(#{open_id_response.identity_url}) failed. "
|
||||
else
|
||||
msg = "Verification failed. "
|
||||
end
|
||||
notify :error, open_id_response.msg.to_s + msg
|
||||
|
||||
when OpenID::SUCCESS
|
||||
# Success means that the transaction completed without
|
||||
# error. If info is nil, it means that the user cancelled
|
||||
# the verification.
|
||||
@user = User.find_by_open_id_url(openid_url)
|
||||
unless (@user.nil?)
|
||||
session['user_id'] = @user.id
|
||||
session['noexpiry'] = session['user_noexpiry']
|
||||
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||
notify :notice, "You have successfully verified #{openid_url} as your identity. Login successful: session #{msg}"
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year, :secure => TRACKS_COOKIES_SECURE }
|
||||
unless should_expire_sessions?
|
||||
@user.remember_me
|
||||
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => TRACKS_COOKIES_SECURE }
|
||||
end
|
||||
cookies[:openid_url] = { :value => openid_url, :expires => Time.now + 1.year, :secure => TRACKS_COOKIES_SECURE }
|
||||
redirect_back_or_home
|
||||
else
|
||||
notify :warning, "You have successfully verified #{openid_url} as your identity, but you do not have a Tracks account. Please ask your administrator to sign you up."
|
||||
end
|
||||
|
||||
when OpenID::CANCEL
|
||||
notify :warning, "Verification cancelled."
|
||||
|
||||
else
|
||||
notify :warning, "Unknown response status: #{open_id_response.status}"
|
||||
end
|
||||
redirect_to_login unless performed?
|
||||
end
|
||||
|
||||
def logout
|
||||
@user.forget_me if logged_in?
|
||||
cookies.delete :auth_token
|
||||
|
|
@ -156,5 +87,31 @@ class LoginController < ApplicationController
|
|||
def should_expire_sessions?
|
||||
session['noexpiry'] != "on"
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
def login_openid
|
||||
# If checkbox on login page checked, we don't expire the session after 1 hour
|
||||
# of inactivity and we remember this user for future browser sessions
|
||||
session['noexpiry'] ||= params['user_noexpiry']
|
||||
authenticate_with_open_id do |result, identity_url|
|
||||
if result.successful?
|
||||
if @user = User.find_by_open_id_url(identity_url)
|
||||
session['user_id'] = @user.id
|
||||
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||
notify :notice, "Login successful: session #{msg}"
|
||||
cookies[:tracks_login] = { :value => @user.login, :expires => Time.now + 1.year, :secure => TRACKS_COOKIES_SECURE }
|
||||
unless should_expire_sessions?
|
||||
@user.remember_me
|
||||
cookies[:auth_token] = { :value => @user.remember_token , :expires => @user.remember_token_expires_at, :secure => TRACKS_COOKIES_SECURE }
|
||||
end
|
||||
redirect_back_or_home
|
||||
else
|
||||
notify :warning, "Sorry, no user by that identity URL exists (#{identity_url})"
|
||||
end
|
||||
else
|
||||
notify :warning, result.message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def projects_and_actions
|
||||
@projects = @projects.select { |p| p.active? }
|
||||
@projects = @projects.active
|
||||
respond_to do |format|
|
||||
format.text {
|
||||
render :action => 'index_text_projects_and_actions', :layout => false, :content_type => Mime::TEXT
|
||||
|
|
@ -83,7 +83,7 @@ class ProjectsController < ApplicationController
|
|||
@go_to_project = params['go_to_project']
|
||||
@saved = @project.save
|
||||
@project_not_done_counts = { @project.id => 0 }
|
||||
@active_projects_count = current_user.projects.count(:conditions => "state = 'active'")
|
||||
@active_projects_count = current_user.projects.active.count
|
||||
@contexts = current_user.contexts
|
||||
respond_to do |format|
|
||||
format.js { @down_count = current_user.projects.size }
|
||||
|
|
@ -124,9 +124,9 @@ class ProjectsController < ApplicationController
|
|||
@project_not_done_counts[@project.id] = @project.reload().not_done_todo_count(:include_project_hidden_todos => true)
|
||||
end
|
||||
@contexts = current_user.contexts
|
||||
@active_projects_count = current_user.projects.count(:conditions => "state = 'active'")
|
||||
@hidden_projects_count = current_user.projects.count(:conditions => "state = 'hidden'")
|
||||
@completed_projects_count = current_user.projects.count(:conditions => "state = 'completed'")
|
||||
@active_projects_count = current_user.projects.active.count
|
||||
@hidden_projects_count = current_user.projects.hidden.count
|
||||
@completed_projects_count = current_user.projects.completed.count
|
||||
render :template => 'projects/update.js.rjs'
|
||||
return
|
||||
elsif boolean_param('update_status')
|
||||
|
|
@ -161,9 +161,9 @@ class ProjectsController < ApplicationController
|
|||
|
||||
def destroy
|
||||
@project.destroy
|
||||
@active_projects_count = current_user.projects.count(:conditions => "state = 'active'")
|
||||
@hidden_projects_count = current_user.projects.count(:conditions => "state = 'hidden'")
|
||||
@completed_projects_count = current_user.projects.count(:conditions => "state = 'completed'")
|
||||
@active_projects_count = current_user.projects.active.count
|
||||
@hidden_projects_count = current_user.projects.hidden.count
|
||||
@completed_projects_count = current_user.projects.completed.count
|
||||
respond_to do |format|
|
||||
format.js { @down_count = current_user.projects.size }
|
||||
format.xml { render :text => "Deleted project #{@project.name}" }
|
||||
|
|
@ -199,9 +199,9 @@ class ProjectsController < ApplicationController
|
|||
lambda do
|
||||
@page_title = "TRACKS::List Projects"
|
||||
@count = current_user.projects.size
|
||||
@active_projects = @projects.select{ |p| p.active? }
|
||||
@hidden_projects = @projects.select{ |p| p.hidden? }
|
||||
@completed_projects = @projects.select{ |p| p.completed? }
|
||||
@active_projects = @projects.active
|
||||
@hidden_projects = @projects.hidden
|
||||
@completed_projects = @projects.completed
|
||||
@no_projects = @projects.empty?
|
||||
@projects.cache_note_counts
|
||||
@new_project = current_user.projects.build
|
||||
|
|
@ -211,9 +211,9 @@ class ProjectsController < ApplicationController
|
|||
|
||||
def render_projects_mobile
|
||||
lambda do
|
||||
@active_projects = @projects.select{ |p| p.active? }
|
||||
@hidden_projects = @projects.select{ |p| p.hidden? }
|
||||
@completed_projects = @projects.select{ |p| p.completed? }
|
||||
@active_projects = @projects.active
|
||||
@hidden_projects = @projects.hidden
|
||||
@completed_projects = @projects.completed
|
||||
@down_count = @active_projects.size + @hidden_projects.size + @completed_projects.size
|
||||
cookies[:mobile_url]= {:value => request.request_uri, :secure => TRACKS_COOKIES_SECURE}
|
||||
render :action => 'index_mobile'
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class RecurringTodosController < ApplicationController
|
|||
else
|
||||
@message += " / did not create todo"
|
||||
end
|
||||
@count = current_user.recurring_todos.count(:all, :conditions => ["state = ?", "active"])
|
||||
@count = current_user.recurring_todos.active.count
|
||||
else
|
||||
@message = "Error saving recurring todo"
|
||||
end
|
||||
|
|
@ -140,7 +140,7 @@ class RecurringTodosController < ApplicationController
|
|||
|
||||
# delete the recurring todo
|
||||
@saved = @recurring_todo.destroy
|
||||
@remaining = current_user.recurring_todos.count(:all)
|
||||
@remaining = current_user.recurring_todos.count
|
||||
|
||||
respond_to do |format|
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,30 @@
|
|||
class SearchController < ApplicationController
|
||||
|
||||
helper :todos, :application, :notes, :projects
|
||||
|
||||
def results
|
||||
@source_view = params['_source_view'] || 'search'
|
||||
@page_title = "TRACKS::Search Results for #{params[:search]}"
|
||||
terms = '%' + params[:search] + '%'
|
||||
@found_todos = current_user.todos.find(:all, :conditions => ["todos.description LIKE ? OR todos.notes LIKE ?", terms, terms], :include => [:tags, :project, :context])
|
||||
@found_projects = current_user.projects.find(:all, :conditions => ["name LIKE ? or description LIKE ?", terms, terms])
|
||||
@found_notes = current_user.notes.find(:all, :conditions => ["body LIKE ?", terms])
|
||||
|
||||
@count = @found_todos.size + @found_projects.size + @found_notes.size
|
||||
|
||||
init_not_done_counts(['project'])
|
||||
init_project_hidden_todo_counts(['project'])
|
||||
end
|
||||
|
||||
def index
|
||||
@page_title = "TRACKS::Search"
|
||||
end
|
||||
|
||||
def init
|
||||
@source_view = params['_source_view'] || 'search'
|
||||
end
|
||||
|
||||
end
|
||||
class SearchController < ApplicationController
|
||||
|
||||
helper :todos, :application, :notes, :projects
|
||||
|
||||
def results
|
||||
@source_view = params['_source_view'] || 'search'
|
||||
@page_title = "TRACKS::Search Results for #{params[:search]}"
|
||||
terms = '%' + params[:search] + '%'
|
||||
@found_todos = current_user.todos.find(:all, :conditions => ["todos.description LIKE ? OR todos.notes LIKE ?", terms, terms], :include => [:tags, :project, :context])
|
||||
@found_projects = current_user.projects.find(:all, :conditions => ["name LIKE ? OR description LIKE ?", terms, terms])
|
||||
@found_notes = current_user.notes.find(:all, :conditions => ["body LIKE ?", terms])
|
||||
@found_contexts = current_user.contexts.find(:all, :conditions => ["name LIKE ?", terms])
|
||||
# TODO: limit search to tags on todos
|
||||
@found_tags = current_user.tags.find(:all, :conditions => ["name LIKE ?", terms])
|
||||
|
||||
@count = @found_todos.size + @found_projects.size + @found_notes.size + @found_contexts.size + @found_tags.size
|
||||
|
||||
init_not_done_counts
|
||||
init_project_hidden_todo_counts
|
||||
end
|
||||
|
||||
def index
|
||||
@page_title = "TRACKS::Search"
|
||||
end
|
||||
|
||||
def init
|
||||
@source_view = params['_source_view'] || 'search'
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class StatsController < ApplicationController
|
|||
@page_title = 'TRACKS::Statistics'
|
||||
|
||||
@unique_tags = @tags.count(:all, {:group=>"tag_id"})
|
||||
@hidden_contexts = @contexts.find(:all, {:conditions => ["hide = ? ", true]})
|
||||
@hidden_contexts = @contexts.hidden
|
||||
@first_action = @actions.find(:first, :order => "created_at ASC")
|
||||
|
||||
get_stats_actions
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class TodosController < ApplicationController
|
|||
@projects = current_user.projects.find(:all, :include => [:default_context])
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
|
||||
@contexts_to_show = @contexts.reject {|x| x.hide? }
|
||||
@contexts_to_show = current_user.contexts.active
|
||||
|
||||
respond_to do |format|
|
||||
format.html &render_todos_html
|
||||
|
|
@ -28,7 +28,7 @@ class TodosController < ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@projects = current_user.projects.select { |p| p.active? }
|
||||
@projects = current_user.projects.active
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
respond_to do |format|
|
||||
format.m {
|
||||
|
|
@ -76,7 +76,7 @@ class TodosController < ApplicationController
|
|||
format.m do
|
||||
@return_path=cookies[:mobile_url]
|
||||
# todo: use function for this fixed path
|
||||
@return_path='/mobile' if @return_path.nil?
|
||||
@return_path='/m' if @return_path.nil?
|
||||
if @saved
|
||||
redirect_to @return_path
|
||||
else
|
||||
|
|
@ -116,7 +116,7 @@ class TodosController < ApplicationController
|
|||
def show
|
||||
respond_to do |format|
|
||||
format.m do
|
||||
@projects = current_user.projects.select { |p| p.active? }
|
||||
@projects = current_user.projects.active
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
@edit_mobile = true
|
||||
@return_path=cookies[:mobile_url]
|
||||
|
|
@ -399,8 +399,9 @@ class TodosController < ApplicationController
|
|||
:limit => max_completed,
|
||||
:conditions => ['taggings.user_id = ? and state = ?', current_user.id, 'completed'],
|
||||
:order => 'todos.completed_at DESC')
|
||||
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
|
||||
@projects = current_user.projects
|
||||
@contexts = current_user.contexts
|
||||
@contexts_to_show = @contexts.reject {|x| x.hide? }
|
||||
|
||||
# Set count badge to number of items with this tag
|
||||
|
|
@ -442,33 +443,33 @@ class TodosController < ApplicationController
|
|||
due_next_week_date = due_this_week_date + 7.days
|
||||
due_this_month_date = Time.zone.now.end_of_month
|
||||
|
||||
@due_today = current_user.todos.find(:all,
|
||||
@due_today = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due <= ?', 'active', 'deferred', due_today_date],
|
||||
:conditions => ['todos.due <= ?', due_today_date],
|
||||
:order => "due")
|
||||
@due_this_week = current_user.todos.find(:all,
|
||||
@due_this_week = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_today_date, due_this_week_date],
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date],
|
||||
:order => "due")
|
||||
@due_next_week = current_user.todos.find(:all,
|
||||
@due_next_week = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_this_week_date, due_next_week_date],
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date],
|
||||
:order => "due")
|
||||
@due_this_month = current_user.todos.find(:all,
|
||||
@due_this_month = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_next_week_date, due_this_month_date],
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date],
|
||||
:order => "due")
|
||||
@due_after_this_month = current_user.todos.find(:all,
|
||||
@due_after_this_month = current_user.todos.not_completed.find(:all,
|
||||
:include => [:taggings, :tags],
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ?', 'active', 'deferred', due_this_month_date],
|
||||
:conditions => ['todos.due > ?', due_this_month_date],
|
||||
:order => "due")
|
||||
|
||||
|
||||
@count = current_user.todos.not_completed.are_due.count
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.ics {
|
||||
@due_all = current_user.todos.find(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND NOT todos.due IS NULL', 'active', 'deferred'],
|
||||
:order => "due")
|
||||
@due_all = current_user.todos.not_completed.are_due.find(:all, :order => "due")
|
||||
render :action => 'calendar', :layout => false, :content_type => Mime::ICS
|
||||
}
|
||||
end
|
||||
|
|
@ -536,13 +537,17 @@ class TodosController < ApplicationController
|
|||
end
|
||||
|
||||
def with_parent_resource_scope(&block)
|
||||
@feed_title = "Actions "
|
||||
if (params[:context_id])
|
||||
@context = current_user.contexts.find_by_params(params)
|
||||
@feed_title = @feed_title + "in context '#{@context.name}'"
|
||||
Todo.send :with_scope, :find => {:conditions => ['todos.context_id = ?', @context.id]} do
|
||||
yield
|
||||
end
|
||||
elsif (params[:project_id])
|
||||
@project = current_user.projects.find_by_params(params)
|
||||
@feed_title = @feed_title + "in project '#{@project.name}'"
|
||||
@project_feed = true
|
||||
Todo.send :with_scope, :find => {:conditions => ['todos.project_id = ?', @project.id]} do
|
||||
yield
|
||||
end
|
||||
|
|
@ -718,7 +723,7 @@ class TodosController < ApplicationController
|
|||
render_rss_feed_for @todos, :feed => todo_feed_options,
|
||||
:item => {
|
||||
:title => :description,
|
||||
:link => lambda { |t| context_url(t.context) },
|
||||
:link => lambda { |t| @project_feed.nil? ? context_url(t.context) : project_url(t.project) },
|
||||
:guid => lambda { |t| todo_url(t) },
|
||||
:description => todo_feed_content
|
||||
}
|
||||
|
|
@ -726,7 +731,9 @@ class TodosController < ApplicationController
|
|||
end
|
||||
|
||||
def todo_feed_options
|
||||
Todo.feed_options(current_user)
|
||||
options = Todo.feed_options(current_user)
|
||||
options[:title] = @feed_title
|
||||
return options
|
||||
end
|
||||
|
||||
def todo_feed_content
|
||||
|
|
@ -779,8 +786,8 @@ class TodosController < ApplicationController
|
|||
if todo.from_recurring_todo?
|
||||
recurring_todo = todo.recurring_todo
|
||||
|
||||
# check if there are active todos belonging to this recurring todo.
|
||||
# only add new one if all active todos are completed
|
||||
# check if there are active todos belonging to this recurring todo. only
|
||||
# add new one if all active todos are completed
|
||||
if recurring_todo.todos.active.count == 0
|
||||
|
||||
# check for next todo either from the due date or the show_from date
|
||||
|
|
@ -788,7 +795,7 @@ class TodosController < ApplicationController
|
|||
|
||||
# if both due and show_from are nil, check for a next todo from now
|
||||
date_to_check = Time.zone.now if date_to_check.nil?
|
||||
|
||||
|
||||
if recurring_todo.active? && recurring_todo.has_next_todo(date_to_check)
|
||||
|
||||
# shift the reference date to yesterday if date_to_check is furher in
|
||||
|
|
@ -799,7 +806,7 @@ class TodosController < ApplicationController
|
|||
# that new todos due for today will be created instead of new todos
|
||||
# for tomorrow.
|
||||
date = date_to_check.at_midnight >= Time.zone.now.at_midnight ? date_to_check : Time.zone.now-1.day
|
||||
|
||||
|
||||
new_recurring_todo = create_todo_from_recurring_todo(recurring_todo, date)
|
||||
end
|
||||
end
|
||||
|
|
@ -834,20 +841,20 @@ class TodosController < ApplicationController
|
|||
due_this_month_date = Time.zone.now.end_of_month
|
||||
case id
|
||||
when "due_today"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due <= ?', 'active', 'deferred', due_today_date])
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due <= ?', due_today_date])
|
||||
when "due_this_week"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_today_date, due_this_week_date])
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_today_date, due_this_week_date])
|
||||
when "due_next_week"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_this_week_date, due_next_week_date])
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_this_week_date, due_next_week_date])
|
||||
when "due_this_month"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ? AND todos.due <= ?', 'active', 'deferred', due_next_week_date, due_this_month_date])
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due > ? AND todos.due <= ?', due_next_week_date, due_this_month_date])
|
||||
when "due_after_this_month"
|
||||
return 0 == current_user.todos.count(:all,
|
||||
:conditions => ['(todos.state = ? OR todos.state = ?) AND todos.due > ?', 'active', 'deferred', due_this_month_date])
|
||||
return 0 == current_user.todos.not_completed.count(:all,
|
||||
:conditions => ['todos.due > ?', due_this_month_date])
|
||||
else
|
||||
raise Exception.new, "unknown due id for calendar: '#{id}'"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
class UsersController < ApplicationController
|
||||
|
||||
if openid_enabled?
|
||||
open_id_consumer
|
||||
before_filter :begin_open_id_auth, :only => :update_auth_type
|
||||
end
|
||||
|
||||
before_filter :admin_login_required, :only => [ :index, :show, :destroy ]
|
||||
skip_before_filter :login_required, :only => [ :new, :create ]
|
||||
prepend_before_filter :login_optional, :only => [ :new, :create ]
|
||||
|
|
@ -153,18 +147,25 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
def update_auth_type
|
||||
if (params[:user][:auth_type] == 'open_id') && openid_enabled?
|
||||
case open_id_response.status
|
||||
when OpenID::SUCCESS
|
||||
# The URL was a valid identity URL. Now we just need to send a redirect
|
||||
# to the server using the redirect_url the library created for us.
|
||||
session['openid_url'] = params[:openid_url]
|
||||
|
||||
# redirect to the server
|
||||
redirect_to open_id_response.redirect_url((request.protocol + request.host_with_port + "/"), url_for(:action => 'complete'))
|
||||
if (params[:open_id_complete] || (params[:user][:auth_type] == 'open_id')) && openid_enabled?
|
||||
authenticate_with_open_id do |result, identity_url|
|
||||
if result.successful?
|
||||
# Success means that the transaction completed without
|
||||
# error. If info is nil, it means that the user cancelled
|
||||
# the verification.
|
||||
@user.auth_type = 'open_id'
|
||||
@user.open_id_url = identity_url
|
||||
if @user.save
|
||||
notify :notice, "You have successfully verified #{identity_url} as your identity and set your authentication type to Open ID."
|
||||
else
|
||||
debugger
|
||||
notify :warning, "You have successfully verified #{identity_url} as your identity but there was a problem saving your authentication preferences."
|
||||
end
|
||||
redirect_to preferences_path
|
||||
else
|
||||
notify :warning, "Unable to find openid server for <q>#{openid_url}</q>"
|
||||
notify :warning, result.message
|
||||
redirect_to :action => 'change_auth_type'
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
|
@ -178,47 +179,6 @@ class UsersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def complete
|
||||
return unless openid_enabled?
|
||||
openid_url = session['openid_url']
|
||||
if openid_url.blank?
|
||||
notify :error, "expected an openid_url"
|
||||
end
|
||||
case open_id_response.status
|
||||
when OpenID::FAILURE
|
||||
# In the case of failure, if info is non-nil, it is the
|
||||
# URL that we were verifying. We include it in the error
|
||||
# message to help the user figure out what happened.
|
||||
if open_id_response.identity_url
|
||||
msg = "Verification of #{openid_url}(#{open_id_response.identity_url}) failed. "
|
||||
else
|
||||
msg = "Verification failed. "
|
||||
end
|
||||
notify :error, open_id_response.msg.to_s + msg
|
||||
|
||||
when OpenID::SUCCESS
|
||||
# Success means that the transaction completed without
|
||||
# error. If info is nil, it means that the user cancelled
|
||||
# the verification.
|
||||
@user.auth_type = 'open_id'
|
||||
@user.open_id_url = openid_url
|
||||
if @user.save
|
||||
notify :notice, "You have successfully verified #{openid_url} as your identity and set your authentication type to Open ID."
|
||||
else
|
||||
notify :warning, "You have successfully verified #{openid_url} as your identity but there was a problem saving your authentication preferences."
|
||||
end
|
||||
redirect_to preferences_path
|
||||
|
||||
when OpenID::CANCEL
|
||||
notify :warning, "Verification cancelled."
|
||||
|
||||
else
|
||||
notify :warning, "Unknown response status: #{open_id_response.status}"
|
||||
end
|
||||
redirect_to :action => 'change_auth_type' unless performed?
|
||||
end
|
||||
|
||||
|
||||
def refresh_token
|
||||
@user.generate_token
|
||||
@user.save!
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module ApplicationHelper
|
|||
|
||||
# Replicates the link_to method but also checks request.request_uri to find
|
||||
# current page. If that matches the url, the link is marked id = "current"
|
||||
#
|
||||
#
|
||||
def navigation_link(name, options = {}, html_options = nil, *parameters_for_method_reference)
|
||||
if html_options
|
||||
html_options = html_options.stringify_keys
|
||||
|
|
@ -29,7 +29,7 @@ module ApplicationHelper
|
|||
|
||||
# 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 ""
|
||||
|
|
@ -62,7 +62,7 @@ module ApplicationHelper
|
|||
|
||||
# Check due date in comparison to today's date Flag up date appropriately with
|
||||
# a 'traffic light' colour code Modified method for mobile screen
|
||||
#
|
||||
#
|
||||
def due_date_mobile(due)
|
||||
if due == nil
|
||||
return ""
|
||||
|
|
@ -92,7 +92,7 @@ module ApplicationHelper
|
|||
# Returns a count of next actions in the given context or project. The result
|
||||
# is count and a string descriptor, correctly pluralised if there are no
|
||||
# actions or multiple actions
|
||||
#
|
||||
#
|
||||
def count_undone_todos_phrase(todos_parent, string="actions")
|
||||
@controller.count_undone_todos_phrase(todos_parent, string)
|
||||
end
|
||||
|
|
@ -143,5 +143,31 @@ module ApplicationHelper
|
|||
page.replace 'flash', "<h4 id='flash' class='alert #{type}'>#{message}</h4>"
|
||||
page.visual_effect :fade, 'flash', :duration => fade_duration
|
||||
end
|
||||
|
||||
|
||||
def recurrence_time_span(rt)
|
||||
case rt.ends_on
|
||||
when "no_end_date"
|
||||
return rt.start_from.nil? ? "" : "from " + format_date(rt.start_from)
|
||||
when "ends_on_number_of_times"
|
||||
return "for "+rt.number_of_occurences.to_s + " times"
|
||||
when "ends_on_end_date"
|
||||
starts = rt.start_from.nil? ? "" : "from " + format_date(rt.start_from)
|
||||
ends = rt.end_date.nil? ? "" : " until " + format_date(rt.end_date)
|
||||
return starts+ends
|
||||
else
|
||||
raise Exception.new, "unknown recurrence time span selection (#{rt.ends_on})"
|
||||
end
|
||||
end
|
||||
|
||||
def recurrence_pattern_as_text(recurring_todo)
|
||||
rt = recurring_todo.recurring_target_as_text
|
||||
rp = recurring_todo.recurrence_pattern
|
||||
# only add space if recurrence_pattern has content
|
||||
rp = " " + rp if !rp.nil?
|
||||
rts = recurrence_time_span(recurring_todo)
|
||||
# only add space if recurrence_time_span has content
|
||||
rts = " " + rts if !(rts == "")
|
||||
return rt+rp+rts
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
module NotesHelper
|
||||
def truncated_note(note, characters = 50)
|
||||
sanitize(textilize_without_paragraph(truncate(note.body, characters, "...")))
|
||||
sanitize(textilize_without_paragraph(truncate(note.body, :length => characters, :omission => "...")))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ module ProjectsHelper
|
|||
def project_next_prev
|
||||
html = ''
|
||||
unless @previous_project.nil?
|
||||
project_name = truncate(@previous_project.name, 40, "...")
|
||||
project_name = truncate(@previous_project.name, :length => 40, :omission => "...")
|
||||
html << link_to_project(@previous_project, "« #{project_name}")
|
||||
end
|
||||
html << ' | ' if @previous_project && @next_project
|
||||
unless @next_project.nil?
|
||||
project_name = truncate(@next_project.name, 40, "...")
|
||||
project_name = truncate(@next_project.name, :length => 40, :omission => "...")
|
||||
html << link_to_project(@next_project, "#{project_name} »")
|
||||
end
|
||||
html
|
||||
|
|
@ -34,12 +34,12 @@ module ProjectsHelper
|
|||
def project_next_prev_mobile
|
||||
html = ''
|
||||
unless @previous_project.nil?
|
||||
project_name = truncate(@previous_project.name, 40, "...")
|
||||
project_name = truncate(@previous_project.name, :length => 40, :omission => "...")
|
||||
html << link_to_project_mobile(@previous_project, "5", "« 5-#{project_name}")
|
||||
end
|
||||
html << ' | ' if @previous_project && @next_project
|
||||
unless @next_project.nil?
|
||||
project_name = truncate(@next_project.name, 40, "...")
|
||||
project_name = truncate(@next_project.name, :length => 40, :omission => "...")
|
||||
html << link_to_project_mobile(@next_project, "6", "6-#{project_name} »")
|
||||
end
|
||||
html
|
||||
|
|
|
|||
|
|
@ -1,37 +1,11 @@
|
|||
module RecurringTodosHelper
|
||||
|
||||
def recurrence_time_span(rt)
|
||||
case rt.ends_on
|
||||
when "no_end_date"
|
||||
return ""
|
||||
when "ends_on_number_of_times"
|
||||
return "for "+rt.number_of_occurences.to_s + " times"
|
||||
when "ends_on_end_date"
|
||||
starts = rt.start_from.nil? ? "" : "from " + format_date(rt.start_from)
|
||||
ends = rt.end_date.nil? ? "" : " until " + format_date(rt.end_date)
|
||||
return starts+ends
|
||||
else
|
||||
raise Exception.new, "unknown recurrence time span selection (#{self.ends_on})"
|
||||
end
|
||||
end
|
||||
|
||||
def recurrence_target(rt)
|
||||
case rt.target
|
||||
when 'due_date'
|
||||
return "due"
|
||||
when 'show_from_date'
|
||||
return "show"
|
||||
else
|
||||
return "ERROR"
|
||||
end
|
||||
end
|
||||
|
||||
def recurring_todo_tag_list
|
||||
tags_except_starred = @recurring_todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
|
||||
tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" +
|
||||
tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" +
|
||||
# link_to(t.name, :controller => "todos", :action => "tag", :id =>
|
||||
# t.name) + TODO: tag view for recurring_todos (yet?)
|
||||
t.name +
|
||||
t.name +
|
||||
"</span>"}.join('')
|
||||
"<span class='tags'>#{tag_list}</span>"
|
||||
end
|
||||
|
|
@ -44,7 +18,7 @@ module RecurringTodosHelper
|
|||
str
|
||||
end
|
||||
|
||||
def recurring_todo_remote_star_icon
|
||||
def recurring_todo_remote_star_icon
|
||||
str = link_to( image_tag_for_star(@recurring_todo),
|
||||
toggle_star_recurring_todo_path(@recurring_todo),
|
||||
:class => "icon star_item", :title => "star the action '#{@recurring_todo.description}'")
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ module TodosHelper
|
|||
"<span class=\"tag\">" +
|
||||
link_to(t.name, {:action => "tag", :controller => "todos", :id => t.name+".m"}) +
|
||||
"</span>"}.join('')
|
||||
"<span class=\"tags\">#{tag_list}</span>"
|
||||
if tag_list.empty? then "" else "<span class=\"tags\">#{tag_list}</span>" end
|
||||
end
|
||||
|
||||
def deferred_due_date
|
||||
|
|
@ -242,13 +242,13 @@ module TodosHelper
|
|||
end
|
||||
|
||||
def project_names_for_autocomplete
|
||||
array_or_string_for_javascript( ['None'] + @projects.select{ |p| p.active? }.collect{|p| escape_javascript(p.name) } )
|
||||
array_or_string_for_javascript( ['None'] + current_user.projects.active.collect{|p| escape_javascript(p.name) } )
|
||||
end
|
||||
|
||||
def context_names_for_autocomplete
|
||||
# #return array_or_string_for_javascript(['Create a new context']) if
|
||||
# @contexts.empty?
|
||||
array_or_string_for_javascript( @contexts.collect{|c| escape_javascript(c.name) } )
|
||||
array_or_string_for_javascript( current_user.contexts.collect{|c| escape_javascript(c.name) } )
|
||||
end
|
||||
|
||||
def format_ical_notes(notes)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ class Context < ActiveRecord::Base
|
|||
|
||||
has_many :todos, :dependent => :delete_all, :include => :project, :order => "todos.completed_at DESC"
|
||||
belongs_to :user
|
||||
|
||||
|
||||
named_scope :active, :conditions => { :hide => false }
|
||||
named_scope :hidden, :conditions => { :hide => true }
|
||||
|
||||
acts_as_list :scope => :user
|
||||
extend NamePartFinder
|
||||
include Tracks::TodoList
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
class MessageGateway < ActionMailer::Base
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
||||
|
||||
def receive(email)
|
||||
user = User.find(:first, :include => [:preference], :conditions => ["preferences.sms_email = ?", email.from[0].strip])
|
||||
if user.nil?
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ class Project < ActiveRecord::Base
|
|||
has_many :notes, :dependent => :delete_all, :order => "created_at DESC"
|
||||
belongs_to :default_context, :class_name => "Context", :foreign_key => "default_context_id"
|
||||
belongs_to :user
|
||||
|
||||
named_scope :active, :conditions => { :state => 'active' }
|
||||
named_scope :hidden, :conditions => { :state => 'hidden' }
|
||||
named_scope :completed, :conditions => { :state => 'completed'}
|
||||
|
||||
validates_presence_of :name, :message => "project must have a name"
|
||||
validates_length_of :name, :maximum => 255, :message => "project name must be less than 256 characters"
|
||||
|
|
|
|||
|
|
@ -295,6 +295,17 @@ class RecurringTodo < ActiveRecord::Base
|
|||
def recurring_target=(t)
|
||||
self.target = t
|
||||
end
|
||||
|
||||
def recurring_target_as_text
|
||||
case self.target
|
||||
when 'due_date'
|
||||
return "due"
|
||||
when 'show_from_date'
|
||||
return "show"
|
||||
else
|
||||
raise Exception.new, "unexpected value of recurrence target '#{self.target}'"
|
||||
end
|
||||
end
|
||||
|
||||
def recurring_show_days_before=(days)
|
||||
self.show_from_delta=days
|
||||
|
|
@ -361,6 +372,8 @@ class RecurringTodo < ActiveRecord::Base
|
|||
when 'show_from'
|
||||
# so leave due date empty
|
||||
return nil
|
||||
else
|
||||
raise Exception.new, "unexpected value of recurrence target '#{self.target}'"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -471,12 +484,23 @@ class RecurringTodo < ActiveRecord::Base
|
|||
start = determine_start(previous)
|
||||
day = self.every_other1
|
||||
n = self.every_other2
|
||||
|
||||
|
||||
case self.recurrence_selector
|
||||
when 0 # specific day of the month
|
||||
if start.mday >= day
|
||||
if start.mday >= day
|
||||
# there is no next day n in this month, search in next month
|
||||
start += n.months
|
||||
#
|
||||
# start += n.months
|
||||
#
|
||||
# The above seems to not work. Fiddle with timezone. Looks like we hit a
|
||||
# bug in rails here where 2008-12-01 +0100 plus 1.month becomes
|
||||
# 2008-12-31 +0100. For now, just calculate in UTC and convert back to
|
||||
# local timezone.
|
||||
#
|
||||
# TODO: recheck if future rails versions have this problem too
|
||||
start = Time.utc(start.year, start.month, start.day)+n.months
|
||||
start = Time.zone.local(start.year, start.month, start.day)
|
||||
|
||||
# go back to day
|
||||
end
|
||||
return Time.zone.local(start.year, start.month, day)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ class Todo < ActiveRecord::Base
|
|||
|
||||
named_scope :active, :conditions => { :state => 'active' }
|
||||
named_scope :not_completed, :conditions => ['NOT state = ? ', 'completed']
|
||||
named_scope :are_due, :conditions => ['NOT todos.due IS NULL']
|
||||
|
||||
STARRED_TAG_NAME = "starred"
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class User < ActiveRecord::Base
|
|||
|
||||
#for will_paginate plugin
|
||||
cattr_accessor :per_page
|
||||
@@per_page = 1
|
||||
@@per_page = 5
|
||||
|
||||
def validate
|
||||
unless Tracks::Config.auth_schemes.include?(auth_type)
|
||||
|
|
@ -145,8 +145,8 @@ class User < ActiveRecord::Base
|
|||
return nil
|
||||
end
|
||||
|
||||
def self.find_by_open_id_url(raw_open_id_url)
|
||||
normalized_open_id_url = normalize_open_id_url(raw_open_id_url)
|
||||
def self.find_by_open_id_url(raw_identity_url)
|
||||
normalized_open_id_url = OpenIdAuthentication.normalize_url(raw_identity_url)
|
||||
find(:first, :conditions => ['open_id_url = ?', normalized_open_id_url])
|
||||
end
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def at_midnight(date)
|
||||
return TimeZone[prefs.time_zone].local(date.year, date.month, date.day, 0, 0, 0)
|
||||
return ActiveSupport::TimeZone[prefs.time_zone].local(date.year, date.month, date.day, 0, 0, 0)
|
||||
end
|
||||
|
||||
def generate_token
|
||||
|
|
@ -237,13 +237,6 @@ protected
|
|||
|
||||
def normalize_open_id_url
|
||||
return if open_id_url.nil?
|
||||
self.open_id_url = self.class.normalize_open_id_url(open_id_url)
|
||||
self.open_id_url = OpenIdAuthentication.normalize_url(open_id_url)
|
||||
end
|
||||
|
||||
def self.normalize_open_id_url(raw_open_id_url)
|
||||
normalized = raw_open_id_url
|
||||
normalized = "http://#{raw_open_id_url}" unless raw_open_id_url =~ /\:\/\//
|
||||
normalized.downcase.chomp('/')
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
<% context = context_listing %>
|
||||
<% context = context_listing
|
||||
suppress_drag_handle ||= false
|
||||
suppress_edit_button ||= false
|
||||
%>
|
||||
<div id="<%= dom_id(context, "container") %>" class="list">
|
||||
<div id="<%= dom_id(context) %>" class="context sortable_row" style="display:'';">
|
||||
<div class="position">
|
||||
<span class="handle">DRAG</span>
|
||||
</div>
|
||||
<% unless suppress_drag_handle -%>
|
||||
<div class="position">
|
||||
<span class="handle">DRAG</span>
|
||||
</div>
|
||||
<% end -%>
|
||||
<div class="data">
|
||||
<%= link_to_context( context ) %> <%= " (" + count_undone_todos_phrase(context,"actions") + ")" %>
|
||||
</div>
|
||||
|
|
@ -32,5 +37,5 @@
|
|||
-%>
|
||||
</div>
|
||||
</div>
|
||||
<%= render :partial => 'context_form', :object => context %>
|
||||
<%= render :partial => 'contexts/context_form', :object => context %>
|
||||
</div>
|
||||
|
|
@ -6,9 +6,9 @@ if not @not_done.empty?
|
|||
# only show a context when there are actions in it
|
||||
-%>
|
||||
<h2><%=mobile_context.name%></h2>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<ul class="c">
|
||||
<%= render :partial => "todos/mobile_todo",
|
||||
:collection => @not_done,
|
||||
:locals => { :parent_container_type => "context" }-%>
|
||||
</table>
|
||||
<% end -%>
|
||||
</ul>
|
||||
<% end -%>
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@
|
|||
|
||||
<div id="input_box">
|
||||
<%= render :partial => "shared/add_new_item_form" %>
|
||||
<%= render "sidebar/sidebar" %>
|
||||
<%= render :template => "sidebar/sidebar" %>
|
||||
</div><!-- End of input box -->
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
</div><!-- End of display_box -->
|
||||
|
||||
<div id="input_box">
|
||||
<%= render "sidebar/sidebar" %>
|
||||
<%= render :template => "sidebar/sidebar" %>
|
||||
</div><!-- End of input box -->
|
||||
|
||||
<script type="text/javascript">
|
||||
|
|
|
|||
14
app/views/integrations/search_plugin.rxml
Normal file
14
app/views/integrations/search_plugin.rxml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
xml.instruct!
|
||||
|
||||
xml.OpenSearchDescription 'xmlns' => "http://a9.com/-/spec/opensearch/1.1/" do
|
||||
|
||||
xml.ShortName Tracks
|
||||
xml.Description 'Search in Tracks'
|
||||
xml.InputEncoding 'UTF-8'
|
||||
xml.Image("data:image/x-icon;base64," + @icon_data,
|
||||
'width' => '16', 'height' => '16')
|
||||
xml.Url 'type' => 'text/html', 'method' => 'GET',
|
||||
'template' => url_for(:controller => 'search', :action => 'results',
|
||||
:only_path => false) + '?search={searchTerms}'
|
||||
end
|
||||
|
||||
|
|
@ -2,35 +2,40 @@
|
|||
new_todo_params = {}
|
||||
new_todo_params[:from_project] = @mobile_from_project if @mobile_from_project
|
||||
new_todo_params[:from_context] = @mobile_from_context if @mobile_from_context
|
||||
-%><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
-%><?xml version="1.0"?>
|
||||
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="initial-scale = 1.0" />
|
||||
<%= stylesheet_link_tag "mobile"%>
|
||||
<%= stylesheet_link_tag "mobile", :media => 'handheld,all' %>
|
||||
<title><%= @page_title %></title>
|
||||
</head><body>
|
||||
<% if !(@new_mobile || @edit_mobile)
|
||||
if !@prefs.nil? -%>
|
||||
<h1><span class="count"><%= @down_count %></span> <%=
|
||||
user_time.strftime(@prefs.title_date_format) -%></h1>
|
||||
<%= (link_to("0-Add new action", formatted_new_todo_path(:m, new_todo_params))+" | ") unless @new_mobile -%>
|
||||
<div class="nav">
|
||||
<%= (link_to("0-New action", formatted_new_todo_path(:m, new_todo_params))+" | ") unless @new_mobile -%>
|
||||
<%= (link_to("1-Home", formatted_todos_path(:m))+" | ") unless @home -%>
|
||||
<%= (link_to("2-Contexts", formatted_contexts_path(:m))+" | ") -%>
|
||||
<%= (link_to("3-Projects", formatted_projects_path(:m))+" | ") -%>
|
||||
<%= (link_to("4-Starred", {:action => "tag", :controller => "todos", :id => "starred.m"})) -%>
|
||||
<% end
|
||||
end -%><%= render_flash -%>
|
||||
<hr/><%= yield -%>
|
||||
</div>
|
||||
<%= yield -%>
|
||||
<hr/><% if !@prefs.nil? -%>
|
||||
<div class="nav">
|
||||
<%= (link_to("Logout", formatted_logout_path(:format => 'm')) +" | ") -%>
|
||||
<%= (link_to("0-Add new action", formatted_new_todo_path(:m), {:accesskey => "0"})+" | ") unless @new_mobile -%>
|
||||
<%= (link_to("0-New action", formatted_new_todo_path(:m), {:accesskey => "0"})+" | ") unless @new_mobile -%>
|
||||
<%= (link_to("1-Home", formatted_todos_path(:m), {:accesskey => "1"})+" | ") unless @home -%>
|
||||
<%= (link_to("2-Contexts", formatted_contexts_path(:m), {:accesskey => "2"})+" | ") -%>
|
||||
<%= (link_to("3-Projects", formatted_projects_path(:m), {:accesskey => "3"})+" | ") -%>
|
||||
<%= (link_to("4-Starred", {:action => "tag", :controller => "todos", :id => "starred.m"}, {:accesskey => "4"})+" | ") -%>
|
||||
<%= (link_to("Tickler", {:action => "index", :controller => "tickler.m"})+" | ") -%>
|
||||
<%= (link_to("Feeds", {:action => "index", :controller => "feeds.m"})) -%>
|
||||
</div>
|
||||
<% end -%>
|
||||
<%= render :partial => "shared/mobile_footer" -%>
|
||||
</body></html>
|
||||
|
|
@ -5,13 +5,23 @@
|
|||
<% if @prefs.refresh != 0 -%>
|
||||
<meta http-equiv="Refresh" content="<%= @prefs["refresh"].to_i*60 %>;url=<%= request.request_uri %>">
|
||||
<% end -%>
|
||||
<%= javascript_include_merged :tracks %>
|
||||
<% bundle do %>
|
||||
<%= javascript_include_tag *%w[
|
||||
prototype effects dragdrop controls application
|
||||
calendar calendar-en calendar-setup
|
||||
accesskey-hints todo-items niftycube
|
||||
protoload flashobject lowpro
|
||||
] %>
|
||||
<%= stylesheet_link_tag *%w[ standard calendar-system niftyCorners] %>
|
||||
<% end %>
|
||||
<%= javascript_include_tag :unobtrusive %>
|
||||
<%= stylesheet_link_merged :tracks %>
|
||||
<%= stylesheet_link_tag "print", :media => "print" %>
|
||||
|
||||
<link rel="shortcut icon" href="<%= url_for(:controller => 'favicon.ico') %>" />
|
||||
<%= auto_discovery_link_tag(:rss, {:controller => "todos", :action => "index", :format => 'rss', :token => "#{current_user.token}"}, {:title => "RSS feed of next actions"}) %>
|
||||
<link rel="search" type="application/opensearchdescription+xml"
|
||||
title="Tracks"
|
||||
href="<%= search_plugin_path %>" />
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
|
|
|
|||
|
|
@ -14,19 +14,19 @@
|
|||
<% form_tag :action=> 'login' do %>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_login">Login:</label></td>
|
||||
<td width="100px"><input type="text" name="user_login" id="user_login" value="" class="login_text" /></td>
|
||||
<td><label for="user_login">Login:</label></td>
|
||||
<td><input type="text" name="user_login" id="user_login" value="" class="login_text" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_password">Password:</label></td>
|
||||
<td width="100px"><input type="password" name="user_password" id="user_password" class="login_text" /></td>
|
||||
<td><label for="user_password">Password:</label></td>
|
||||
<td><input type="password" name="user_password" id="user_password" class="login_text" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_noexpiry">Stay logged in:</label></td>
|
||||
<td width="100px"><input type="checkbox" name="user_noexpiry" id="user_noexpiry" checked /></td>
|
||||
<td><label for="user_noexpiry">Stay logged in:</label></td>
|
||||
<td><input type="checkbox" name="user_noexpiry" id="user_noexpiry" checked /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"></td>
|
||||
<td></td>
|
||||
<td><input type="submit" name="login" value="Sign In »" class="primary" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -36,18 +36,18 @@
|
|||
|
||||
<% if show_openid_form %>
|
||||
<div id="openid_auth_form" style="display:none">
|
||||
<% form_tag :action=> 'login', :action => 'begin' do %>
|
||||
<% form_tag :action=> 'login' do %>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="100px"><label for="openid_url">Identity URL:</label></td>
|
||||
<td width="100px"><input type="text" name="openid_url" id="openid_url" value="<%= @openid_url %>" class="login_text open_id" /></td>
|
||||
<td><label for="openid_url">Identity URL:</label></td>
|
||||
<td><input type="text" name="openid_url" id="openid_url" value="<%= @openid_url %>" class="login_text open_id" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_noexpiry">Stay logged in:</label></td>
|
||||
<td width="100px"><input type="checkbox" name="user_noexpiry" id="user_noexpiry" checked /></td>
|
||||
<td><label for="user_noexpiry">Stay logged in:</label></td>
|
||||
<td><input type="checkbox" name="user_noexpiry" id="user_noexpiry" checked /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"></td>
|
||||
<td></td>
|
||||
<td><input type="submit" name="login" value="Sign In »" class="primary" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -14,19 +14,19 @@
|
|||
<% form_tag formatted_login_path(:format => 'm') do %>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_login">Login:</label></td>
|
||||
<td width="100px"><input type="text" name="user_login" id="user_login" value="" class="login_text" /></td>
|
||||
<td><label for="user_login">Login:</label></td>
|
||||
<td><input type="text" name="user_login" id="user_login" value="" class="login_text" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_password">Password:</label></td>
|
||||
<td width="100px"><input type="password" name="user_password" id="user_password" class="login_text" /></td>
|
||||
<td><label for="user_password">Password:</label></td>
|
||||
<td><input type="password" name="user_password" id="user_password" class="login_text" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"><label for="user_noexpiry">Stay logged in:</label></td>
|
||||
<td width="100px"><input type="checkbox" name="user_noexpiry" id="user_noexpiry" checked /></td>
|
||||
<td><label for="user_noexpiry">Stay logged in:</label></td>
|
||||
<td><input type="checkbox" name="user_noexpiry" id="user_noexpiry" checked="checked" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100px"></td>
|
||||
<td> </td>
|
||||
<td><input type="submit" name="login" value="Sign In »" class="primary" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="page_name_auto_complete" id="default_context_list" style="display:none;z-index:9999"></div>
|
||||
<script type="text/javascript">
|
||||
defaultContextAutoCompleter = new Autocompleter.Local('project[default_context_name]', 'default_context_list', <%= context_names_for_autocomplete %>, {choices:100,autoSelect:false});
|
||||
Event.observe($('project[default_context_name]'), "focus", defaultContextAutoCompleter.activate.bind(defaultContextAutoCompleter));
|
||||
Event.observe($('project[default_context_name]'), "click", defaultContextAutoCompleter.activate.bind(defaultContextAutoCompleter));
|
||||
defaultContextAutoCompleter = new Autocompleter.Local('project_default_context_name', 'default_context_list', <%= context_names_for_autocomplete %>, {choices:100,autoSelect:false});
|
||||
Event.observe($('project_default_context_name'), "focus", defaultContextAutoCompleter.activate.bind(defaultContextAutoCompleter));
|
||||
Event.observe($('project_default_context_name'), "click", defaultContextAutoCompleter.activate.bind(defaultContextAutoCompleter));
|
||||
</script>
|
||||
|
|
@ -74,5 +74,5 @@
|
|||
|
||||
<div id="input_box">
|
||||
<%= render :partial => "shared/add_new_item_form" %>
|
||||
<%= render "sidebar/sidebar" %>
|
||||
<%= render :template => "sidebar/sidebar" %>
|
||||
</div><!-- End of input box -->
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="description">
|
||||
<span class="todo.descr"><%= sanitize(recurring_todo.description) %></span> <%= recurring_todo_tag_list %>
|
||||
<span class='recurrence_pattern'>
|
||||
[<%=recurrence_target(recurring_todo)%> <%= recurring_todo.recurrence_pattern %> <%= recurrence_time_span(recurring_todo) %>]
|
||||
[<%= recurrence_pattern_as_text(@recurring_todo) %>]
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ if @recurring_saved
|
|||
page.hide 'recurring-todos-empty-nd'
|
||||
page.insert_html :bottom,
|
||||
'recurring_todos_container',
|
||||
:partial => 'recurring_todos/recurring_todo'
|
||||
:partial => 'recurring_todos/recurring_todo', :locals => { :recurring_todo => @recurring_todo}
|
||||
page.visual_effect :highlight, dom_id(@recurring_todo), :duration => 3
|
||||
# update badge count
|
||||
page['badge_count'].replace_html @count
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ if @saved
|
|||
|
||||
if @recurring_todo.completed?
|
||||
# show completed recurring todo
|
||||
page.insert_html :top, "completed_recurring_todos_container", :partial => 'recurring_todos/recurring_todo'
|
||||
page.insert_html :top, "completed_recurring_todos_container", :partial => 'recurring_todos/recurring_todo', :locals => { :recurring_todo => @recurring_todo }
|
||||
page.visual_effect :highlight, dom_id(@recurring_todo), :duration => 3
|
||||
|
||||
# set empty messages
|
||||
|
|
@ -14,7 +14,7 @@ if @saved
|
|||
# recurring_todo is activated
|
||||
|
||||
# show completed recurring todo
|
||||
page.insert_html :top, "recurring_todos_container", :partial => 'recurring_todos/recurring_todo'
|
||||
page.insert_html :top, "recurring_todos_container", :partial => 'recurring_todos/recurring_todo', :locals => { :recurring_todo => @recurring_todo }
|
||||
page.visual_effect :highlight, dom_id(@recurring_todo), :duration => 3
|
||||
|
||||
# inform user if a new todo has been created because of the activation
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ if @saved
|
|||
page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}; projectAutoCompleter.changed = true" if @new_project_created
|
||||
|
||||
# replace old recurring todo with updated todo
|
||||
page.replace dom_id(@recurring_todo), :partial => 'recurring_todos/recurring_todo'
|
||||
page.replace dom_id(@recurring_todo), :partial => 'recurring_todos/recurring_todo', :locals => { :recurring_todo => @recurring_todo }
|
||||
page.visual_effect :highlight, dom_id(@recurring_todo), :duration => 3
|
||||
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<div id="display_box_search">
|
||||
<% form_tag(:action => :results) do %>
|
||||
<%= text_field_tag(:search, params[:search]) %>
|
||||
<%= submit_tag "Search" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div id="display_box_search">
|
||||
<% form_tag({:action => :results}, :id => 'search-form') do %>
|
||||
<%= text_field_tag(:search, params[:search]) %>
|
||||
<%= submit_tag "Search" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
Form.focusFirstElement('search-form')
|
||||
</script>
|
||||
|
|
@ -1,32 +1,48 @@
|
|||
<div id="display_box_results">
|
||||
<% if @count == 0 -%>
|
||||
<div class="message"><p>Your search yielded no results.</p></div>
|
||||
<% else -%>
|
||||
<% source_view_is = :search %>
|
||||
<% parent_container_type = 'search' %>
|
||||
<% if not @found_todos.empty? -%>
|
||||
<div id="found-todos-container" class="container">
|
||||
<h2><span id="found-todos-count" class="badge"><%= @found_todos.size %></span>Todos matching query</h2>
|
||||
<%= render :partial => "todos/todo", :collection => @found_todos, :locals => { :parent_container_type => 'search', :suppress_context => false, :suppress_project => false, :suppress_edit_button => true } %>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<% if not @found_projects.empty? -%>
|
||||
<div id="found-projects-container" class="container">
|
||||
<h2><span id="found-projects-count" class="badge"><%= @found_projects.size %></span>Projects matching query</h2>
|
||||
<%= render :partial => "projects/project_listing", :collection => @found_projects, :locals => { :suppress_drag_handle => true, :suppress_edit_button => true } %>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<% if not @found_notes.empty? -%>
|
||||
<div id="found-notes-container" class="container">
|
||||
<h2><span id="found-notes-count" class="badge"><%= @found_notes.size %></span>Notes matching query</h2>
|
||||
<% for notes in @found_notes -%>
|
||||
<div class="container" id="note-<%= notes.id %>-wrapper">
|
||||
<%= render :partial => "notes/notes_summary", :object => notes %>
|
||||
</div>
|
||||
<% end -%>
|
||||
</div>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
</div>
|
||||
<div id="display_box_results">
|
||||
<% if @count == 0 -%>
|
||||
<div class="message"><p>Your search yielded no results.</p></div>
|
||||
<% else -%>
|
||||
<% unless @found_todos.empty? -%>
|
||||
<div id="found-todos-container" class="container">
|
||||
<h2><span id="found-todos-count" class="badge"><%= @found_todos.size %></span>Todos matching query</h2>
|
||||
<%= render :partial => "todos/todo", :collection => @found_todos, :locals => { :parent_container_type => 'search', :suppress_context => false, :suppress_project => false, :suppress_edit_button => true } %>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<% unless @found_projects.empty? -%>
|
||||
<div id="found-projects-container" class="container">
|
||||
<h2><span id="found-projects-count" class="badge"><%= @found_projects.size %></span>Projects matching query</h2>
|
||||
<%= render :partial => "projects/project_listing", :collection => @found_projects, :locals => { :suppress_drag_handle => true, :suppress_edit_button => true } %>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<% unless @found_notes.empty? -%>
|
||||
<div id="found-notes-container" class="container">
|
||||
<h2><span id="found-notes-count" class="badge"><%= @found_notes.size %></span>Notes matching query</h2>
|
||||
<% for notes in @found_notes -%>
|
||||
<div class="container" id="note-<%= notes.id %>-wrapper">
|
||||
<%= render :partial => "notes/notes_summary", :object => notes %>
|
||||
</div>
|
||||
<% end -%>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<% unless @found_contexts.empty? -%>
|
||||
<div id="found-contexts-container" class="container">
|
||||
<h2><span id="found-contexts-count" class="badge"><%= @found_contexts.size %></span>Contexts matching query</h2>
|
||||
<%= render :partial => "contexts/context_listing", :collection => @found_contexts, :locals => { :suppress_drag_handle => true, :suppress_edit_button => true } %>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<% unless @found_tags.empty? -%>
|
||||
<div id="found-tags-container" class="container">
|
||||
<h2><span id="found-tags-count" class="badge"><%= @found_tags.size %></span>Tags matching query</h2>
|
||||
<span class="tags"><% @found_tags.each do |tag| -%>
|
||||
<span class="tag"><%= link_to tag.name, {:controller => "todos", :action => "tag", :id => tag.name} -%></span>
|
||||
<% end %>
|
||||
</span>
|
||||
<br/>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end -%>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
@todo = nil
|
||||
@initial_context_name = @context.name unless @context.nil?
|
||||
@initial_context_name ||= @project.default_context.name unless @project.nil? || @project.default_context.nil?
|
||||
@initial_context_name ||= @contexts[0].name unless @contexts[0].nil?
|
||||
@initial_context_name ||= current_user.contexts.first.name unless current_user.contexts.first.nil?
|
||||
@initial_project_name = @project.name unless @project.nil?
|
||||
%>
|
||||
<div id="todo_new_action_container">
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
<div id="sidebar">
|
||||
<% # show active items before hidden / completed items -%>
|
||||
<% # show active items before hidden / completed items -%>
|
||||
|
||||
<%= render :partial => "sidebar/project_list",
|
||||
:locals => { :list_name => 'Active Projects',
|
||||
:projects => @projects.select{|p| p.active? } } -%>
|
||||
:projects => @active_projects } -%>
|
||||
|
||||
<%= render :partial => "sidebar/context_list",
|
||||
:locals => { :list_name => 'Active Contexts',
|
||||
:contexts => @contexts.reject{|c| c.hide? } } -%>
|
||||
:contexts => @active_contexts } -%>
|
||||
|
||||
<% if prefs.show_hidden_projects_in_sidebar -%>
|
||||
<%= render :partial => "sidebar/project_list",
|
||||
:locals => { :list_name => 'Hidden Projects',
|
||||
:projects => @projects.select{|p| p.hidden? } } -%>
|
||||
:projects => @hidden_projects } -%>
|
||||
<% end -%>
|
||||
|
||||
<% if prefs.show_completed_projects_in_sidebar -%>
|
||||
<%= render :partial => "sidebar/project_list",
|
||||
:locals => { :list_name => 'Completed Projects',
|
||||
:projects => @projects.select{|p| p.completed? } } -%>
|
||||
:projects => @completed_projects } -%>
|
||||
<% end -%>
|
||||
|
||||
<% if prefs.show_hidden_contexts_in_sidebar -%>
|
||||
<%= render :partial => "sidebar/context_list",
|
||||
:locals => { :list_name => 'Hidden Contexts',
|
||||
:contexts => @contexts.select{|c| c.hide? } } -%>
|
||||
:contexts => @hidden_contexts } -%>
|
||||
<% end -%>
|
||||
|
||||
<div class="integrations-link"><ul>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ end
|
|||
-%><%=@actions_per_context[@actions_per_context.size()-1]['total'].to_i*100/@sum%>&
|
||||
&pie_labels=<%
|
||||
0.upto @actions_per_context.size()-2 do | i |
|
||||
%><%=truncate(@actions_per_context[i]['name'],@truncate_chars, '...')%>,<%
|
||||
%><%=truncate(@actions_per_context[i]['name'], :length => @truncate_chars, :omission => '...')%>,<%
|
||||
end
|
||||
-%><%=truncate(@actions_per_context[@actions_per_context.size()-1]['name'],@truncate_chars,'...') %>&
|
||||
-%><%=truncate(@actions_per_context[@actions_per_context.size()-1]['name'], :legnth => @truncate_chars, :omission => '...') %>&
|
||||
&links=<%
|
||||
0.upto @actions_per_context.size()-2 do | i |
|
||||
%><%=url_for :controller => "contexts", :action => "show", :id=>@actions_per_context[i]['id']%>,<%
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ end
|
|||
-%><%=@actions_per_context[@actions_per_context.size()-1]['total'].to_i*100/@sum%>&
|
||||
&pie_labels=<%
|
||||
0.upto @actions_per_context.size()-2 do | i |
|
||||
%><%=truncate(@actions_per_context[i]['name'], @truncate_chars, '...') %>,<%
|
||||
%><%=truncate(@actions_per_context[i]['name'], :length => @truncate_chars, :omission => '...') %>,<%
|
||||
end
|
||||
-%><%=truncate(@actions_per_context[@actions_per_context.size()-1]['name'], @truncate_chars, '...') %>&
|
||||
-%><%=truncate(@actions_per_context[@actions_per_context.size()-1]['name'], :length => @truncate_chars, :omission => '...') %>&
|
||||
&links=<%
|
||||
0.upto @actions_per_context.size()-2 do | i |
|
||||
%><%=url_for :controller => "contexts", :action => "show", :id=>@actions_per_context[i]['id']%>,<%
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ if parent_container_type == 'show_mobile' -%>
|
|||
<h2><label for="todo_description">Description</label></h2>
|
||||
<%= text_field( "todo", "description", "tabindex" => 2) %>
|
||||
<h2><label for="todo_notes">Notes</label></h2>
|
||||
<%= text_area( "todo", "notes", "cols" => 30, "rows" => 5, "tabindex" => 3) %>
|
||||
<%= text_area( "todo", "notes", "cols" => 30, "rows" => 2, "tabindex" => 3) %>
|
||||
<h2><label for="todo_context_id">Context</label></h2>
|
||||
<%= unless @mobile_from_context
|
||||
collection_select( "todo", "context_id", @contexts, "id", "name", {}, {"tabindex" => 4} )
|
||||
|
|
@ -22,7 +22,7 @@ end %>
|
|||
<h2><label for="todo_project_id">Project</label></h2>
|
||||
<%= unless @mobile_from_project
|
||||
collection_select( "todo", "project_id", @projects, "id", "name",
|
||||
{:include_blank => true}, {"tabindex" => 5} )
|
||||
{:include_blank => '--No project--'}, {"tabindex" => 5} )
|
||||
else
|
||||
# manually add blank option since :include_blank does not work
|
||||
# with options_from_collection_for_select
|
||||
|
|
@ -34,7 +34,7 @@ end %>
|
|||
<%= text_field_tag "tag_list", @tag_list_text, :size => 30, :tabindex => 6 %>
|
||||
<h2><label for="todo_due">Due</label></h2>
|
||||
<%= date_select("todo", "due", {:order => [:day, :month, :year],
|
||||
:start_year => this_year, :include_blank => true}, :tabindex => 7) %>
|
||||
:start_year => this_year, :include_blank => '--'}, :tabindex => 7) %>
|
||||
<h2><label for="todo_show_from">Show from</label></h2>
|
||||
<%= date_select("todo", "show_from", {:order => [:day, :month, :year],
|
||||
:start_year => this_year, :include_blank => true}, :tabindex => 8) %>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
<% @todo = mobile_todo
|
||||
if mobile_todo.starred?
|
||||
bullet = "<span class=star>"+image_tag("menustar_small.gif")+"</span>"
|
||||
li_class = " class=\"star\""
|
||||
else
|
||||
bullet = "<span class=r>» </span>"
|
||||
li_class = ""
|
||||
end -%>
|
||||
<div class="t" id="<%= dom_id(mobile_todo) %>">
|
||||
<tr valign="top"><td><%= bullet %></td><td><%
|
||||
<li id="<%= dom_id(mobile_todo) %>" <%= li_class %>><%
|
||||
if mobile_todo.completed?
|
||||
-%><span class="m_t_d">
|
||||
<% else
|
||||
|
|
@ -23,4 +22,4 @@ end -%>
|
|||
")</span>" -%>
|
||||
<% end -%>
|
||||
<%= tag_list_mobile -%>
|
||||
</span></td></tr></div>
|
||||
</span></li>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@
|
|||
<% unless @todo.completed? %><span class="defer-container"><%= defer_link(1) %> <%= defer_link(7) %></span><% end %>
|
||||
<%= date_span -%>
|
||||
<span class="todo.descr"><%= h sanitize(todo.description) %></span>
|
||||
<%= link_to(image_tag("recurring16x16.png"), {:controller => "recurring_todos", :action => "index"}, :class => "recurring_icon") if @todo.from_recurring_todo? %>
|
||||
<%= link_to(
|
||||
image_tag("recurring16x16.png"),
|
||||
{:controller => "recurring_todos", :action => "index"},
|
||||
:class => "recurring_icon", :title => recurrence_pattern_as_text(@todo.recurring_todo)) if @todo.from_recurring_todo? %>
|
||||
<%= tag_list %>
|
||||
<%= deferred_due_date %>
|
||||
<%= project_and_context_links( parent_container_type, :suppress_context => suppress_context, :suppress_project => suppress_project ) %>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ CLASS:PUBLIC
|
|||
CATEGORIES:Tracks
|
||||
CREATED:<%= todo.created_at.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
DESCRIPTION:<%= format_ical_notes(todo.notes) %>
|
||||
LAST-MODIFIED:<%= due_date.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
LAST-MODIFIED:<%= todo.updated_at.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
LOCATION:
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ if @saved
|
|||
page.insert_html :top, 'display_box', :partial => 'contexts/context', :locals => { :context => @todo.context, :collapsible => true }
|
||||
else
|
||||
page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@todo.context_id}" if source_view_is_one_of(:todo, :deferred)
|
||||
page.insert_html :bottom, item_container_id(@todo) + 'items', :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type, :source_view => @source_view }
|
||||
page.insert_html :bottom, item_container_id(@todo) + 'items', :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type, :source_view => @source_view }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,5 +10,7 @@
|
|||
|
||||
<div id="input_box">
|
||||
<%= render :partial => "shared/add_new_item_form" %>
|
||||
<%= render "sidebar/sidebar" %>
|
||||
<%- # TODO: this used to be render :template, but somehow it was not
|
||||
#rendered after the rails2.2.2 upgrade -%>
|
||||
<%= render :file => "sidebar/sidebar.html.erb" %>
|
||||
</div><!-- End of input box -->
|
||||
|
|
@ -11,5 +11,5 @@
|
|||
|
||||
<div id="input_box">
|
||||
<%= render :partial => "shared/add_new_item_form" %>
|
||||
<%= render "sidebar/sidebar" %>
|
||||
<%= render :template => "sidebar/sidebar" %>
|
||||
</div><!-- End of input box -->
|
||||
|
|
@ -23,5 +23,5 @@
|
|||
|
||||
<div id="input_box">
|
||||
<%= render :partial => "shared/add_new_item_form" %>
|
||||
<%= render "sidebar/sidebar" %>
|
||||
<%= render :template => "sidebar/sidebar" %>
|
||||
</div><!-- End of input box -->
|
||||
|
|
@ -5,7 +5,7 @@ if @saved
|
|||
|
||||
# completed todos move from their context to the completed container
|
||||
unless @prefs.hide_completed_actions?
|
||||
page.insert_html :top, "completed_containeritems", :partial => 'todos/todo', :locals => { :parent_container_type => "completed" }
|
||||
page.insert_html :top, "completed_containeritems", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => "completed" }
|
||||
page.visual_effect :highlight, dom_id(@todo, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
page[empty_container_msg_div_id].show if @down_count == 0 && !empty_container_msg_div_id.nil?
|
||||
page.show 'tickler-empty-nd' if source_view_is(:project) && @deferred_count == 0
|
||||
|
|
@ -33,7 +33,7 @@ if @saved
|
|||
else
|
||||
# todo is activated from completed container
|
||||
page.call "todoItems.ensureVisibleWithEffectAppear", item_container_id(@todo)
|
||||
page.insert_html :bottom, item_container_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.insert_html :bottom, item_container_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
page.show "empty-d" if @completed_count == 0
|
||||
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? # If we've checked something as undone, incomplete items can't be empty
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ if @saved
|
|||
page.call "todoItems.expandNextActionListingByContext", "c#{@todo.context_id}items", true
|
||||
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil?
|
||||
# show all todos in context
|
||||
page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
end
|
||||
|
||||
# update badge count
|
||||
|
|
@ -52,7 +52,7 @@ if @saved
|
|||
end
|
||||
end
|
||||
else
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
elsif source_view_is :project
|
||||
|
|
@ -63,17 +63,17 @@ if @saved
|
|||
elsif @todo.deferred?
|
||||
page[@todo].remove
|
||||
page.show("p#{@original_item_project_id}empty-nd") if (@remaining_undone_in_project == 0)
|
||||
page.insert_html :bottom, "tickler", :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.insert_html :bottom, "tickler", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page['tickler-empty-nd'].hide
|
||||
page.replace_html "badge_count", @down_count
|
||||
elsif @todo_was_activated_from_deferred_state
|
||||
page[@todo].remove
|
||||
page['tickler-empty-nd'].show if (@deferred_count == 0)
|
||||
page.insert_html :bottom, "p#{@todo.project_id}", :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.insert_html :bottom, "p#{@todo.project_id}", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page["p#{@todo.project_id}empty-nd"].hide
|
||||
page.replace_html "badge_count", @down_count
|
||||
else
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
elsif source_view_is :deferred
|
||||
|
|
@ -87,7 +87,7 @@ if @saved
|
|||
page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@todo.context_id}"
|
||||
page.call "todoItems.expandNextActionListingByContext", "c#{@todo.context_id}items", true
|
||||
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil?
|
||||
page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.replace_html("badge_count", @down_count)
|
||||
page.delay(0.5) do
|
||||
page.call "todoItems.ensureContainerHeight", "c#{@original_item_context_id}items"
|
||||
|
|
@ -95,18 +95,18 @@ if @saved
|
|||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
else
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
elsif source_view_is :stats
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
elsif source_view_is :calendar
|
||||
if @due_date_changed
|
||||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
page.hide "empty_"+@new_due_id
|
||||
page.insert_html :bottom, @new_due_id, :partial => 'todos/todo'
|
||||
page.insert_html :bottom, @new_due_id, :partial => 'todos/todo', :locals => {:todo => @todo}
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
else
|
||||
if @todo.due.nil?
|
||||
|
|
@ -114,7 +114,7 @@ if @saved
|
|||
page[@todo].remove
|
||||
page.show "empty_"+@original_item_due_id if @old_due_empty
|
||||
else
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
javascripts:
|
||||
- tracks:
|
||||
- prototype
|
||||
- effects
|
||||
- dragdrop
|
||||
- controls
|
||||
- application
|
||||
- calendar
|
||||
- calendar-en
|
||||
- calendar-setup
|
||||
- accesskey-hints
|
||||
- todo-items
|
||||
- niftycube
|
||||
- protoload
|
||||
- flashobject
|
||||
- lowpro
|
||||
stylesheets:
|
||||
- tracks:
|
||||
- standard
|
||||
- calendar-system
|
||||
- niftyCorners
|
||||
|
|
@ -67,7 +67,7 @@ module Rails
|
|||
|
||||
class << self
|
||||
def rubygems_version
|
||||
Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
|
||||
Gem::RubyGemsVersion rescue nil
|
||||
end
|
||||
|
||||
def gem_version
|
||||
|
|
@ -82,14 +82,14 @@ module Rails
|
|||
|
||||
def load_rubygems
|
||||
require 'rubygems'
|
||||
|
||||
unless rubygems_version >= '0.9.4'
|
||||
$stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
|
||||
min_version = '1.3.1'
|
||||
unless rubygems_version >= min_version
|
||||
$stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
|
||||
exit 1
|
||||
end
|
||||
|
||||
rescue LoadError
|
||||
$stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
|
||||
$stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
|
||||
exit 1
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ class Rails::Configuration
|
|||
attr_accessor :action_web_service
|
||||
end
|
||||
|
||||
# Leave this alone or set it to one or more of ['database', 'ldap', 'open_id'].
|
||||
# If you choose ldap, see the additional configuration options further down.
|
||||
AUTHENTICATION_SCHEMES = ['database']
|
||||
|
||||
Rails::Initializer.run do |config|
|
||||
# Skip frameworks you're not going to use
|
||||
# config.frameworks -= [ :action_web_service, :action_mailer ]
|
||||
|
|
@ -22,6 +26,8 @@ Rails::Initializer.run do |config|
|
|||
config.action_web_service = Rails::OrderedOptions.new
|
||||
config.load_paths += %W( #{RAILS_ROOT}/app/apis )
|
||||
|
||||
config.action_controller.use_accept_header = true
|
||||
|
||||
# Add additional load paths for your own custom dirs
|
||||
# config.load_paths += %W( #{RAILS_ROOT}/app/services )
|
||||
|
||||
|
|
@ -33,10 +39,10 @@ Rails::Initializer.run do |config|
|
|||
# (create the session table with 'rake create_sessions_table')
|
||||
config.action_controller.session_store = :active_record_store
|
||||
|
||||
# config.action_controller.session = {
|
||||
# :session_key => '_tracks_session_id',
|
||||
# :secret => SALT * (30.0 / SALT.length).ceil #must be at least 30 characters
|
||||
# }
|
||||
config.action_controller.session = {
|
||||
:session_key => '_tracks_session_id',
|
||||
:secret => SALT * (30.0 / SALT.length).ceil #must be at least 30 characters
|
||||
}
|
||||
|
||||
# Enable page/fragment caching by setting a file-based store
|
||||
# (remember to create the caching directory and make it readable to the application)
|
||||
|
|
@ -70,9 +76,6 @@ end
|
|||
|
||||
# Include your application configuration below
|
||||
|
||||
# Leave this alone or set it to one or more of ['database', 'ldap', 'open_id'].
|
||||
# If you choose ldap, see the additional configuration options further down.
|
||||
AUTHENTICATION_SCHEMES = ['database']
|
||||
|
||||
require 'name_part_finder'
|
||||
require 'tracks/todo_list'
|
||||
|
|
@ -96,14 +99,11 @@ end
|
|||
# setting this to true will make the cookies only available over HTTPS
|
||||
TRACKS_COOKIES_SECURE = false
|
||||
|
||||
MOBILE_CONTENT_TYPE = 'tracks/mobile'
|
||||
Mime::Type.register(MOBILE_CONTENT_TYPE, :m)
|
||||
|
||||
tracks_version='1.7-devel'
|
||||
tracks_version='1.7RC'
|
||||
|
||||
# comment out next two lines if you do not want (or can not) the date of the
|
||||
# last git commit in the footer
|
||||
info=`git log --pretty=format:"%ai" -1`
|
||||
tracks_version=tracks_version + ' ('+info+')'
|
||||
# info=`git log --pretty=format:"%ai" -1`
|
||||
# tracks_version=tracks_version + ' ('+info+')'
|
||||
|
||||
TRACKS_VERSION=tracks_version
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# Add new mime types for use in respond_to blocks:
|
||||
# Mime::Type.register "text/richtext", :rtf
|
||||
# Mime::Type.register "application/x-mobile", :mobile
|
||||
Mime::Type.register_alias "text/html", :m
|
||||
|
|
@ -6,10 +6,6 @@ ActionController::Routing::Routes.draw do |map|
|
|||
login.formatted_login 'login.:format', :action => 'login'
|
||||
login.logout 'logout', :action => 'logout'
|
||||
login.formatted_logout 'logout.:format', :action => 'logout'
|
||||
login.open_id_begin 'begin', :action => 'begin'
|
||||
login.formatted_open_id_begin 'begin.:format', :action => 'begin'
|
||||
login.open_id_complete 'complete', :action => 'complete'
|
||||
login.formatted_open_id_complete 'complete.:format', :action => 'complete'
|
||||
end
|
||||
|
||||
map.resources :users,
|
||||
|
|
@ -57,6 +53,7 @@ ActionController::Routing::Routes.draw do |map|
|
|||
todos.mobile_abbrev 'm', :action => "index", :format => 'm'
|
||||
todos.mobile_abbrev_new 'm/new', :action => "new", :format => 'm'
|
||||
end
|
||||
map.root :controller => 'todos' # Make OpenID happy because it needs #root_url defined
|
||||
|
||||
map.resources :notes
|
||||
map.feeds 'feeds', :controller => 'feedlist', :action => 'index'
|
||||
|
|
@ -68,6 +65,7 @@ ActionController::Routing::Routes.draw do |map|
|
|||
|
||||
map.preferences 'preferences', :controller => 'preferences', :action => 'index'
|
||||
map.integrations 'integrations', :controller => 'integrations', :action => 'index'
|
||||
map.search_plugin '/integrations/search_plugin.xml', :controller => 'integrations', :action => 'search_plugin', :format => 'xml'
|
||||
|
||||
map.resources :recurring_todos,
|
||||
:member => {:toggle_check => :put, :toggle_star => :put}
|
||||
|
|
|
|||
43
db/migrate/044_upgrade_open_id.rb
Normal file
43
db/migrate/044_upgrade_open_id.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
class UpgradeOpenId < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :open_id_authentication_associations, :force => true do |t|
|
||||
t.integer :issued, :lifetime
|
||||
t.string :handle, :assoc_type
|
||||
t.binary :server_url, :secret
|
||||
end
|
||||
|
||||
create_table :open_id_authentication_nonces, :force => true do |t|
|
||||
t.integer :timestamp, :null => false
|
||||
t.string :server_url, :null => true
|
||||
t.string :salt, :null => false
|
||||
end
|
||||
|
||||
drop_table :open_id_associations
|
||||
drop_table :open_id_nonces
|
||||
drop_table :open_id_settings
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :open_id_authentication_associations
|
||||
drop_table :open_id_authentication_nonces
|
||||
|
||||
create_table "open_id_associations", :force => true do |t|
|
||||
t.binary "server_url"
|
||||
t.string "handle"
|
||||
t.binary "secret"
|
||||
t.integer "issued"
|
||||
t.integer "lifetime"
|
||||
t.string "assoc_type"
|
||||
end
|
||||
|
||||
create_table "open_id_nonces", :force => true do |t|
|
||||
t.string "nonce"
|
||||
t.integer "created"
|
||||
end
|
||||
|
||||
create_table "open_id_settings", :force => true do |t|
|
||||
t.string "setting"
|
||||
t.binary "value"
|
||||
end
|
||||
end
|
||||
end
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -5,15 +5,15 @@
|
|||
-- Dump of table contexts
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
INSERT INTO "contexts" VALUES(1,'agenda',1,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(2,'call',2,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(3,'email',3,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(4,'errand',4,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(5,'lab',5,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(6,'library',6,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(7,'freetime',7,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(8,'office',8,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(9,'waiting for',9,'f',1,'2008-02-25 20:21:09','2008-02-25 20:21:09');
|
||||
INSERT INTO "contexts" VALUES(1,'agenda',1,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
INSERT INTO "contexts" VALUES(2,'call',2,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
INSERT INTO "contexts" VALUES(3,'email',3,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
INSERT INTO "contexts" VALUES(4,'errand',4,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
INSERT INTO "contexts" VALUES(5,'lab',5,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
INSERT INTO "contexts" VALUES(6,'library',6,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
INSERT INTO "contexts" VALUES(7,'freetime',7,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
INSERT INTO "contexts" VALUES(8,'office',8,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
INSERT INTO "contexts" VALUES(9,'waiting for',9,'f',1,'2008-02-25 20:21:09','2008-03-24 19:23:53');
|
||||
|
||||
-- Dump of table notes
|
||||
-- ------------------------------------------------------------
|
||||
|
|
@ -25,33 +25,33 @@ INSERT INTO "notes" VALUES(2,1,1,'Should I go for a swirly effect or a whooshy o
|
|||
-- Dump of table projects
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
INSERT INTO "projects" VALUES(1,'Build a working time machine',1,1,'','active','2008-02-25 20:21:09','2008-02-25 20:21:09',NULL,NULL);
|
||||
INSERT INTO "projects" VALUES(2,'Make more money than Billy Gates',2,1,'','active','2008-02-25 20:21:09','2008-02-25 20:21:09',NULL,NULL);
|
||||
INSERT INTO "projects" VALUES(3,'Evict dinosaurs from the garden',3,1,'','active','2008-02-25 20:21:09','2008-02-25 20:21:09',NULL,NULL);
|
||||
INSERT INTO "projects" VALUES(1,'Build a working time machine',1,1,'','active','2008-02-25 20:21:09','2008-03-24 19:23:53',NULL,NULL);
|
||||
INSERT INTO "projects" VALUES(2,'Make more money than Billy Gates',2,1,'','active','2008-02-25 20:21:09','2008-03-24 19:23:53',NULL,NULL);
|
||||
INSERT INTO "projects" VALUES(3,'Evict dinosaurs from the garden',3,1,'','active','2008-02-25 20:21:09','2008-03-24 19:23:53',NULL,NULL);
|
||||
|
||||
|
||||
-- Dump of table schema_info
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
INSERT INTO "schema_info" VALUES(37);
|
||||
INSERT INTO "schema_migrations" VALUES('44');
|
||||
|
||||
|
||||
-- Dump of table todos
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
INSERT INTO "todos" VALUES(1,1,2,'Call Bill Gates to find out how much he makes per day',NULL,'2006-06-03 14:36:02','2006-06-24',NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(2,2,3,'Call dinosaur exterminator','Ask him if I need to hire a skip for the corpses.','2006-06-10 14:36:02','2006-06-24',NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(3,4,NULL,'Buy milk',NULL,'2006-06-10 14:36:02',NULL,NULL,1,NULL,'completed');
|
||||
INSERT INTO "todos" VALUES(4,4,NULL,'Buy bread',NULL,'2006-06-10 14:36:02',NULL,NULL,1,NULL,'completed');
|
||||
INSERT INTO "todos" VALUES(5,5,1,'Construct time dilation device',NULL,'2006-06-10 14:36:02',NULL,NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(6,2,1,'Phone Grandfather to ask about the paradox','Added some _notes_.','2006-06-10 14:36:02','2006-06-03',NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(7,6,3,'Get a book out of the library','Dinosaurs''R','2006-06-10 14:36:02',NULL,NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(8,4,NULL,'Upgrade to Rails 0.9.1',NULL,'2006-06-10 14:36:02','2006-06-10',NULL,1,NULL,'completed');
|
||||
INSERT INTO "todos" VALUES(9,1,NULL,'This should be due today',NULL,'2006-06-10 14:36:02','2006-06-10',NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(10,1,NULL,'foo',NULL,'2006-06-10 14:36:02','2005-01-05',NULL,1,NULL,'completed');
|
||||
INSERT INTO "todos" VALUES(11,1,2,'Buy shares',NULL,'2006-06-10 14:36:02','2005-02-01',NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(12,1,3,'Buy stegosaurus bait',NULL,'2006-06-10 14:36:02','2006-06-17',NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(13,1,3,'New action in context','Some notes','2006-06-10 14:36:02','2006-06-17',NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(14,2,2,'Call stock broker','tel: 12345','2006-06-03 14:36:02',NULL,NULL,1,NULL,'active');
|
||||
INSERT INTO "todos" VALUES(1,1,2,'Call Bill Gates to find out how much he makes per day',NULL,'2006-06-03 14:36:02','2006-06-23 23:00:00',NULL,1,NULL,'active',NULL,'2006-06-03 14:36:02');
|
||||
INSERT INTO "todos" VALUES(2,2,3,'Call dinosaur exterminator','Ask him if I need to hire a skip for the corpses.','2006-06-10 14:36:02','2006-06-23 23:00:00',NULL,1,NULL,'active',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(3,4,NULL,'Buy milk',NULL,'2006-06-10 14:36:02',NULL,NULL,1,NULL,'completed',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(4,4,NULL,'Buy bread',NULL,'2006-06-10 14:36:02',NULL,NULL,1,NULL,'completed',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(5,5,1,'Construct time dilation device',NULL,'2006-06-10 14:36:02',NULL,NULL,1,NULL,'active',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(6,2,1,'Phone Grandfather to ask about the paradox','Added some _notes_.','2006-06-10 14:36:02','2006-06-02 23:00:00',NULL,1,NULL,'active',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(7,6,3,'Get a book out of the library','Dinosaurs''R','2006-06-10 14:36:02',NULL,NULL,1,NULL,'active',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(8,4,NULL,'Upgrade to Rails 0.9.1',NULL,'2006-06-10 14:36:02','2006-06-09 23:00:00',NULL,1,NULL,'completed',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(9,1,NULL,'This should be due today',NULL,'2006-06-10 14:36:02','2006-06-09 23:00:00',NULL,1,NULL,'active',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(10,1,NULL,'foo',NULL,'2006-06-10 14:36:02','2005-01-05 00:00:00',NULL,1,NULL,'completed',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(11,1,2,'Buy shares',NULL,'2006-06-10 14:36:02','2005-02-01 00:00:00',NULL,1,NULL,'active',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(12,1,3,'Buy stegosaurus bait',NULL,'2006-06-10 14:36:02','2006-06-16 23:00:00',NULL,1,NULL,'active',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(13,1,3,'New action in context','Some notes','2006-06-10 14:36:02','2006-06-16 23:00:00',NULL,1,NULL,'active',NULL,'2006-06-10 14:36:02');
|
||||
INSERT INTO "todos" VALUES(14,2,2,'Call stock broker','tel: 12345','2006-06-03 14:36:02',NULL,NULL,1,NULL,'active',NULL,'2006-06-03 14:36:02');
|
||||
|
||||
|
||||
|
|
@ -1,19 +1,18 @@
|
|||
= 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, Timothy Martens, Luke Melia, John Leonard, Jim Strupp, Eric Lesh, Damien Cirotteau, Janet Riley, Reinier Balt, Jacqui Maher, James Kebinger, Jeffrey Gipson, Eric Allen
|
||||
* Version: 1.6
|
||||
* Copyright: (cc) 2004-2008 rousette.org.uk
|
||||
* License: GNU GPL
|
||||
* Project homepage: http://www.rousette.org.uk/projects/
|
||||
* GitHub: http://github.com/bsag/tracks/
|
||||
* Trac (for bug reports and feature requests): http://dev.rousette.org.uk/report/6
|
||||
* Wiki (community contributed information): http://www.rousette.org.uk/projects/wiki/
|
||||
* Forum: http://www.rousette.org.uk/projects/forums/
|
||||
* Mailing list: http://lists.rousette.org.uk/mailman/listinfo/tracks-discuss
|
||||
* Original developer: bsag (http://www.rousette.org.uk/)
|
||||
* Contributors: http://dev.rousette.org.uk/wiki/Tracks/Contributing/Contributors
|
||||
* Version: 1.7
|
||||
* Copyright: (cc) 2004-2008 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.7dev
|
||||
== Version 1.7RC
|
||||
|
||||
New features:
|
||||
1. Recurring todos
|
||||
|
|
@ -23,10 +22,12 @@ New features:
|
|||
5. New buttons to quickly defer an action 1 or 7 days
|
||||
6. Calendar view to review due actions, includes iCal feed to use in your calendar app (tested with Google Calendar, Evolution, Outlook 2007)
|
||||
7. You can now sort projects on number of active todos
|
||||
8. Support for OpenSearch. This means you can add a Tracks as a search provider in your webbrowser (tested on FF3 and IE7)
|
||||
|
||||
Under the hood:
|
||||
1. Move selenium tests to RSpec
|
||||
2. Bugfixes
|
||||
1. Started to move selenium tests to RSpec
|
||||
2. Upgrade to Rails 2.2.2
|
||||
2. Bugfixes, including fixing OpenID
|
||||
|
||||
== Version 1.6
|
||||
1. upgrade to rails 2.0.2
|
||||
|
|
|
|||
|
|
@ -11,27 +11,25 @@
|
|||
<meta name="Copyright" content="2008 rousette.org.uk
|
||||
This work is licensed under a Creative Commons License.
|
||||
http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
||||
<meta name="Date" content="2008-04-07" />
|
||||
<meta name="Date" content="2008-12-14" />
|
||||
<meta name="Format" content="complete" />
|
||||
<meta name="LaTeXXSLT" content="memoir-twosided-manual.xslt" />
|
||||
<meta name="Revision" content="$Id: manual.markdown 864 2008-06-03 17:01:00Z bsag $" />
|
||||
<title>Tracks 1.6 Manual</title>
|
||||
<meta name="Version" content="1.6" />
|
||||
<meta name="Revision" content="$Id: manual.markdown 2008-12-14 11:50:00Z bsag $" />
|
||||
<title>Tracks 1.7 Manual</title>
|
||||
<meta name="Version" content="1.7" />
|
||||
<meta name="XMP" content="CCAttributionShareAlike" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- The HTML file manual.html is generated from manual.markdown, so make edits to the *.markdown file -->
|
||||
|
||||
<h2 id="installingtracks1.6">Installing Tracks 1.6</h2>
|
||||
<h2 id="installingtracks1.7">Installing Tracks 1.7</h2>
|
||||
|
||||
<h3 id="introduction">Introduction</h3>
|
||||
|
||||
<p><strong>An important note for version 1.6: OpenID support is broken in this release. The fix isn’t trivial because of changes to the <code>ruby-openid</code> gem, so we wanted to get this version out now and fix OpenID for the next release. If you depend on OpenID integration, we recommend waiting until the next release.</strong></p>
|
||||
<p>Tracks 1.7 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it’s only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in <code>doc/CHANGELOG</code>. Full API documentation can be found at <code>doc/app/index.html</code>, once you have run <code>rake appdoc</code></p>
|
||||
|
||||
<p>Tracks 1.6 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it’s only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in <code>doc/CHANGELOG</code>. Full API documentation can be found at <code>doc/app/index.html</code>, once you have run <code>rake appdoc</code></p>
|
||||
|
||||
<p>There are two methods of downloading Tracks 1.6:</p>
|
||||
<p>There are two methods of downloading Tracks 1.7:</p>
|
||||
|
||||
<ol>
|
||||
<li>(Recommended for most people) Download the <a href="http://www.rousette.org.uk/projects/files/tracks-current.zip">zipped package</a>, and unzip in your preferred location (e.g. <code>~/Sites</code> for Mac OS X users).</li>
|
||||
|
|
@ -62,7 +60,7 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<ol>
|
||||
<li>Tracks itself</li>
|
||||
<li>Rails 2.0.2 (installed in the <code>/vendor/rails</code> directory, so you do not need to install Rails yourself)</li>
|
||||
<li>Rails 2.2.2 (installed in the <code>/vendor/rails</code> directory, so you do not need to install Rails yourself)</li>
|
||||
<li>An empty SQLite3 database, set up with the correct database schema</li>
|
||||
</ol>
|
||||
|
||||
|
|
@ -76,19 +74,19 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
<li><strong>Database</strong>. The easiest option is to use SQLite3, as the database is included in the package. All you need then is the <code>sqlite3-ruby</code> gem, as described in step 2, and the SQLite3 libraries and binary (see <a href="http://sqlite.org/download.html">sqlite.org</a> for downloads and installation instructions). If you want to use MySQL, download and install a package for your platform from <a href="http://dev.mysql.com/downloads/mysql/5.0.html">MySQL.com</a>. The basic steps for Postgresql should be similar to those for MySQL, but they will not be discussed further here.</li>
|
||||
</ol>
|
||||
|
||||
<p>If you are using Unix, you might find <a href="http://www.cooldown.com.ar/2006/12/16/install-tracks-on-ubuntu-or-debian/">this guide</a> by c00i90wn helpful. It was written for Tracks 1.043, but it should work for Tracks 1.6.</p>
|
||||
<p>You can find several installation howtos for specific setups <a href="http://dev.rousette.org.uk/wiki/Tracks/Install">here</a>. They were contributed by various Tracks users.</p>
|
||||
|
||||
<h3 id="installation">Installation</h3>
|
||||
|
||||
<p>This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see <a href="#upgrading" title="Upgrading to Tracks 1.6">Upgrading to Tracks 1.6</a>.</p>
|
||||
<p>This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see <a href="#upgrading" title="Upgrading to Tracks 1.7">Upgrading to Tracks 1.7</a>.</p>
|
||||
|
||||
<ol>
|
||||
<li><a href="#unzip_install" title="Unzip Tracks and install">Unzip tracks</a> and install in a directory</li>
|
||||
<li>Decide on a <a href="#database_install" title="Decide on a database">database</a> to use
|
||||
<ol><li>SQLite3 - change database.yml to point to SQLite3 database</li>
|
||||
<ol><li>SQLite3 - change database.yml to point to SQLite3 database. Make sure you add the complete path to the database</li>
|
||||
<li>MySQL - create new MySQL db and grant all privileges </li></ol></li>
|
||||
<li><a href="#config_install" title="Configure variables">Configure some variables</a></li>
|
||||
<li>Populate the database with the <a href="#rake_install" title="Populate your database with the Tracks 1.6 schema">Tracks 1.6 schema</a></li>
|
||||
<li>Populate the database with the <a href="#rake_install" title="Populate your database with the Tracks 1.7 schema">Tracks 1.7 schema</a></li>
|
||||
<li><a href="#startserver_install" title="Start the server">Start the server</a></li>
|
||||
<li><a href="#signup_install" title="Visit Tracks in a browser">Visit Tracks in a browser</a></li>
|
||||
<li><a href="#customise_install" title="Customise Tracks">Customise Tracks</a></li>
|
||||
|
|
@ -104,14 +102,14 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<ol>
|
||||
<li><strong>SQLite3</strong>. All you need to do is make sure that you point Tracks to the included SQLite3 database in <code>/db</code> in the next step, <a href="#config_install" title="Configure variables">Configure variables</a>.</li>
|
||||
<li><strong>MySQL</strong>. Once you have MySQL installed, you need to create a database to use with Tracks 1.6. Go into a terminal and issue the following commands:</li>
|
||||
<li><strong>MySQL</strong>. Once you have MySQL installed, you need to create a database and database-user to use with Tracks 1.7. For this, you can use MySQL Administrator or go into a terminal and issue the following commands:</li>
|
||||
</ol>
|
||||
|
||||
<pre>
|
||||
<code>
|
||||
mysql -uroot -p
|
||||
mysql> CREATE DATABASE tracks15;
|
||||
mysql> GRANT ALL PRIVILEGES ON tracks15.* TO yourmysqluser@localhost \
|
||||
mysql> CREATE DATABASE tracks16;
|
||||
mysql> GRANT ALL PRIVILEGES ON tracks16.* TO yourmysqluser@localhost \
|
||||
IDENTIFIED BY 'password-goes-here' WITH GRANT OPTION;
|
||||
</code>
|
||||
</pre>
|
||||
|
|
@ -119,23 +117,23 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
<h4 id="config_install">Configure variables</h4>
|
||||
|
||||
<ol>
|
||||
<li>If you downloaded Tracks 1.6 via Subversion, you need to duplicate the files <code>database.yml.tmpl</code> and <code>environment.yml.tmpl</code> and remove the <code>*.tmpl</code> extension from the duplicates. Similarly, duplicate <code>/log.tmpl</code> and remove the <code>*.tmpl</code> extension, then edit the files as described in steps 2 and 3.</li>
|
||||
<li>If you downloaded Tracks 1.7 via Subversion, you need to duplicate the files <code>database.yml.tmpl</code> and <code>environment.yml.tmpl</code> and remove the <code>*.tmpl</code> extension from the duplicates. Similarly, duplicate <code>/log.tmpl</code> and remove the <code>*.tmpl</code> extension, then edit the files as described in steps 2 and 3.</li>
|
||||
<li>Open the file <code>/config/database.yml</code> and edit the <code>production:</code> section with the details of your database. If you are using MySQL the <code>adapter:</code> line should read <code>adapter: mysql</code>, <code>host: localhost</code> (in the majority of cases), and your username and password should match those you assigned when you created the database. If you are using SQLite3, you should have only two lines under the production section: <code>adapter: sqlite3</code> and <code>database: db/tracks-15-blank.db</code>. If you downloaded the zipped file, the database.yml file is already configured to use the provided SQLite3 file.</li>
|
||||
<li>Open the file <code>/config/environment.rb</code>, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the <code>SALT = "change-me"</code> line (change the string “change-me” to some other string of your choice), and the time zone setting.</li>
|
||||
<li>Open the file <code>/config/environment.rb</code>, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the <code>SALT = "change-me"</code> line (change the string “change-me” to some other string of your choice), and the time zone setting. For the time zone setting, most people will only want to change the config.time_zone option and leave the timezone to use in your database to :utc</li>
|
||||
<li>If you are using Windows, you may need to check the ‘shebang’ lines (<code>#!/usr/bin/env ruby</code>) of the <code>/public/dispatch.*</code> files and all the files in the <code>/script</code> directory. They are set to <code>#!/usr/bin/env ruby</code> by default. This should work for all *nix based setups (Linux or Mac OS X), but Windows users will probably have to change it to something like <code>#c:/ruby/bin/ruby</code> to point to the Ruby binary on your system.</li>
|
||||
</ol>
|
||||
|
||||
<h4 id="rake_install">Populate your database with the Tracks 1.6 schema</h4>
|
||||
<h4 id="rake_install">Populate your database with the Tracks 1.7 schema</h4>
|
||||
|
||||
<p>Open a terminal and change into the root of your Tracks 1.6 directory. Enter the following command:</p>
|
||||
<p>Open a terminal and change into the root of your Tracks 1.7 directory. Enter the following command:</p>
|
||||
|
||||
<p><code>rake db:migrate RAILS_ENV=production</code></p>
|
||||
|
||||
<p>This will update your database with the required schema for Tracks 1.6. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).</p>
|
||||
<p>This will update your database with the required schema for Tracks 1.7. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).</p>
|
||||
|
||||
<h4 id="startserver_install">Start the server</h4>
|
||||
|
||||
<p>While still in the Terminal inside the Tracks 1.6 root directory, issue the following command:</p>
|
||||
<p>While still in the Terminal inside the Tracks 1.7 root directory, issue the following command:</p>
|
||||
|
||||
<p><code>script/server -e production</code></p>
|
||||
|
||||
|
|
@ -149,7 +147,21 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<p>Once logged in, add some Contexts and Projects, and then go ahead and add your actions. You might also want to visit the Preferences page to edit various settings to your liking. Have fun!</p>
|
||||
|
||||
<h2 id="upgrading">Upgrading to Tracks 1.6</h2>
|
||||
<h2 id="upgrading">Upgrading to Tracks 1.7</h2>
|
||||
|
||||
<h3 id="upgrading_1.6">Upgrading from Tracks 1.6</h3>
|
||||
|
||||
<p>You will need to upgrade your <code>config/environment.rb</code> with the new content from <code>config/environment.rb.tmpl</code> included in 1.7, as the format of this file has changed a bit between 1.6 and 1.7. Also, there were some database changes in Tracks 1.7, so you need to migrate to them.</p>
|
||||
|
||||
<ol>
|
||||
<li><a href="#backup_upgrade" title="Backing up">Back up</a> your existing database and installation of Tracks</li>
|
||||
<li><a href="#install_upgrade" title="Install Tracks 1.7">Install Tracks 1.7</a> in a new directory</li>
|
||||
<li><a href="#config_upgrade" title="Copy over old configuration files">Copy over</a> a few configuration files from your Tracks 1.6 directory. If using SQLite3, copy the old database into the new Tracks 1.7 directory.</li>
|
||||
<li>Rebuild your environment.rb from the updated environment.rb.tmpl</li>
|
||||
<li>Run <code>rake db:migrate RAILS_ENV=production</code> to <a href="#rake_upgrade" title="Update your old database to the new format">update your old database</a> to the new schema – you did back up your database didn’t you?</li>
|
||||
<li>Run <code>script/server</code> inside your Tracks 1.7 directory to <a href="#startserver_upgrade" title="Start the server">start up Tracks 1.7</a>.</li>
|
||||
<li>Once you are happy that everything is working well, <a href="#cleanup_upgrade" title="Clean up your old installation">delete your old Tracks directory</a>.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="upgrading_1.5">Upgrading from Tracks 1.5</h3>
|
||||
|
||||
|
|
@ -157,8 +169,9 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<ol>
|
||||
<li><a href="#backup_upgrade" title="Backing up">Back up</a> your existing database and installation of Tracks</li>
|
||||
<li><a href="#install_upgrade" title="Install Tracks 1.6">Install Tracks 1.6</a> in a new directory</li>
|
||||
<li><a href="#install_upgrade" title="Install Tracks 1.7">Install Tracks 1.6</a> in a new directory</li>
|
||||
<li><a href="#config_upgrade" title="Copy over old configuration files">Copy over</a> a few configuration files from your Tracks 1.043 directory. If using SQLite3, copy the old database into the new Tracks 1.6 directory</li>
|
||||
<li>Rebuild your environment.rb from the updated environment.rb.tmpl</li>
|
||||
<li>Run <code>script/server</code> inside your Tracks 1.6 directory to <a href="#startserver_upgrade" title="Start the server">start up Tracks 1.6</a>.</li>
|
||||
<li>Once you are happy that everything is working well, <a href="#cleanup_upgrade" title="Clean up your old installation">delete your old Tracks directory</a>.</li>
|
||||
</ol>
|
||||
|
|
@ -169,7 +182,7 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<ol>
|
||||
<li><a href="#backup_upgrade" title="Backing up">Back up</a> your existing database and installation of Tracks</li>
|
||||
<li><a href="#install_upgrade" title="Install Tracks 1.6">Install Tracks 1.6</a> in a new directory</li>
|
||||
<li><a href="#install_upgrade" title="Install Tracks 1.7">Install Tracks 1.6</a> in a new directory</li>
|
||||
<li><a href="#config_upgrade" title="Copy over old configuration files">Copy over</a> a few configuration files from your Tracks 1.043 directory. If using SQLite3, copy the old database into the new Tracks 1.6 directory</li>
|
||||
<li>Run <code>rake db:migrate RAILS_ENV=production</code> to <a href="#rake_upgrade" title="Update your old database to the new format">update your old database</a> to the new schema – you did back up your database didn’t you?</li>
|
||||
<li>Run <code>script/server</code> inside your Tracks 1.6 directory to <a href="#startserver_upgrade" title="Start the server">start up Tracks 1.6</a>.</li>
|
||||
|
|
@ -182,11 +195,11 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<p><code>mysqldump –-user [user name] –-password=[password] [database name] > [dump file]</code></p>
|
||||
|
||||
<p>Rename your old Tracks installation (e.g. to ‘tracks-old’) so that you can install Tracks 1.6 along side it.</p>
|
||||
<p>Rename your old Tracks installation (e.g. to ‘tracks-old’) so that you can install Tracks 1.7 along side it.</p>
|
||||
|
||||
<h4 id="install_upgrade">Install Tracks 1.6</h4>
|
||||
<h4 id="install_upgrade">Install Tracks 1.7</h4>
|
||||
|
||||
<p>There are two methods of downloading Tracks 1.6:</p>
|
||||
<p>There are two methods of downloading Tracks 1.7:</p>
|
||||
|
||||
<ol>
|
||||
<li>(Recommended for most people) Download the <a href="http://www.rousette.org.uk/projects/files/tracks-current.zip">zipped package</a>, and unzip in your preferred location (e.g. <code>~/Sites</code> for Mac OS X users).</li>
|
||||
|
|
@ -208,7 +221,7 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<ol>
|
||||
<li>Copy <code>/config/database.yml</code> from your old Tracks directory to the same location in the new one. Double check that the information there is still correct.</li>
|
||||
<li>Duplicate <code>/config/environment.rb.tmpl</code> in the Tracks 1.6 directory, and rename the file to <code>environment.rb</code>. Open the file and alter the line <code>SALT = "change-me"</code> so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location (<code>ENV['TZ'] = 'US/Eastern'</code>). If you have made any other customisations to <code>environment.rb</code> in the past, copy those over, but the contents of the file have changed quite a lot since 1.043, so check it carefully.</li>
|
||||
<li>Duplicate <code>/config/environment.rb.tmpl</code> in the Tracks 1.7 directory, and rename the file to <code>environment.rb</code>. Open the file and alter the line <code>SALT = "change-me"</code> so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location. If you have made any other customisations to <code>environment.rb</code> in the past, copy those over, but the contents of the file have slightly changed since 1.5, so check it carefully.</li>
|
||||
<li>Copy your <code>/log</code> directory over from your old installation to the root of the new one, or just rename <code>/log.tmpl</code> to <code>log</code> to start afresh.</li>
|
||||
<li>If you are using SQLite3, copy your database from <code>/db</code> in your old Tracks directory to the same location in the new one.</li>
|
||||
<li>If you are using Windows, you may need to check the ‘shebang’ lines (<code>#!/usr/bin/env ruby</code>)<a href="#fn:env" id="fnref:env" class="footnote">1</a> of the <code>/public/dispatch.*</code> files and all the files in the <code>/script</code> directory. They are set to <code>#!/usr/bin/env ruby</code> by default. Check the format of those lines in your old installation, and change the new ones as necessary.</li>
|
||||
|
|
@ -216,7 +229,7 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<h4 id="rake_upgrade">Update your old database to the new format</h4>
|
||||
|
||||
<p>In a terminal, change directories so that you are inside the Tracks 1.6 directory. Then issue the command to update your Tracks 1.043 database to the format required for Tracks 1.6:</p>
|
||||
<p>In a terminal, change directories so that you are inside the Tracks 1.7 directory. Then issue the command to update your Tracks 1.6 database to the format required for Tracks 1.7:</p>
|
||||
|
||||
<p><code>rake db:migrate RAILS_ENV=production</code></p>
|
||||
|
||||
|
|
@ -224,7 +237,7 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<h4 id="startserver_upgrade">Start the server</h4>
|
||||
|
||||
<p>If you’re still in the Tracks 1.6 root directory in a terminal, enter the following command to start up Tracks in production mode:</p>
|
||||
<p>If you’re still in the Tracks 1.7 root directory in a terminal, enter the following command to start up Tracks in production mode:</p>
|
||||
|
||||
<p><code>script/server -e production</code></p>
|
||||
|
||||
|
|
@ -233,7 +246,7 @@ http://creativecommons.org/licenses/by-nc-sa/3.0/" />
|
|||
|
||||
<h4 id="cleanup_upgrade">Clean up your old installation</h4>
|
||||
|
||||
<p>Once you’re certain that your new Tracks 1.6 installation is working perfectly, you can delete your old Tracks directory.</p>
|
||||
<p>Once you’re certain that your new Tracks 1.7 installation is working perfectly, you can delete your old Tracks directory.</p>
|
||||
|
||||
<h3 id="upgradingfromversionspriorto1.043">Upgrading from versions prior to 1.043</h3>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
Title: Tracks 1.6 Manual
|
||||
Title: Tracks 1.7 Manual
|
||||
Author: Tracks Development Team
|
||||
Date: 2008-04-07
|
||||
Revision: $Id: manual.markdown 864 2008-06-03 17:01:00Z bsag $
|
||||
Version: 1.6
|
||||
Date: 2008-12-14
|
||||
Revision: $Id: manual.markdown 2008-12-14 11:50:00Z bsag $
|
||||
Version: 1.7
|
||||
Copyright: 2008 rousette.org.uk
|
||||
This work is licensed under a Creative Commons License.
|
||||
http://creativecommons.org/licenses/by-nc-sa/3.0/
|
||||
|
|
@ -14,15 +14,13 @@ CSS: manual.css
|
|||
|
||||
<!-- The HTML file manual.html is generated from manual.markdown, so make edits to the *.markdown file -->
|
||||
|
||||
# Installing Tracks 1.6 #
|
||||
# Installing Tracks 1.7 #
|
||||
|
||||
## Introduction ##
|
||||
|
||||
**An important note for version 1.6: OpenID support is broken in this release. The fix isn't trivial because of changes to the `ruby-openid` gem, so we wanted to get this version out now and fix OpenID for the next release. If you depend on OpenID integration, we recommend waiting until the next release.**
|
||||
Tracks 1.7 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it's only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in `doc/CHANGELOG`. Full API documentation can be found at `doc/app/index.html`, once you have run `rake appdoc`
|
||||
|
||||
Tracks 1.6 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it's only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in `doc/CHANGELOG`. Full API documentation can be found at `doc/app/index.html`, once you have run `rake appdoc`
|
||||
|
||||
There are two methods of downloading Tracks 1.6:
|
||||
There are two methods of downloading Tracks 1.7:
|
||||
|
||||
1. (Recommended for most people) Download the [zipped package](http://www.rousette.org.uk/projects/files/tracks-current.zip), and unzip in your preferred location (e.g. `~/Sites` for Mac OS X users).
|
||||
2. If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions):
|
||||
|
|
@ -50,7 +48,7 @@ If you'd like an easy way to access Tracks from any internet-connected computer,
|
|||
### What is included with the Tracks package ###
|
||||
|
||||
1. Tracks itself
|
||||
2. Rails 2.0.2 (installed in the `/vendor/rails` directory, so you do not need to install Rails yourself)
|
||||
2. Rails 2.2.2 (installed in the `/vendor/rails` directory, so you do not need to install Rails yourself)
|
||||
3. An empty SQLite3 database, set up with the correct database schema
|
||||
|
||||
### What you need to install [whatyouneed] ###
|
||||
|
|
@ -61,18 +59,18 @@ If you don't want to (or can't) use one of the all in one installations, you'll
|
|||
2. **RubyGems**. The gems needed by Rails to interact with the database have to be compiled on the platform on which they will be run, so we cannot include them with the Tracks package, unlike some other gems. So you will need to [download](http://rubyforge.org/frs/?group_id=126) and install RubyGems (run `ruby setup.rb` after extracting the package). Note that once again, Mac OS X Leopard users get an easy life, because RubyGems and the SQLite3 gem is already installed. Once installed you can use RubyGems to install the gems you need for your database. If you are using SQLite3, run `sudo gem install sqlite3-ruby`, then select the appropriate package for your platform (version 1.2.1 recommended). You can use MySQL without installing a gem, but installing the gem can speed things up a bit: `sudo install gem mysql`. If you're using Leopard, there are a few work-arounds necessary, which are explained on [Mac OS Forge](http://trac.macosforge.org/projects/ruby/wiki/Troubleshooting#IcannotbuildrubymysqlonLeopardwithmysql.combinaries). The ruby-mysql bindings can sometimes be a bit troublesome to install, so to be honest, it's probably not worth the bother unless you are trying to wring maximum speed out of your system. If you are using PostgreSQL, then you can install a postgres gem: `gem install postgres`.
|
||||
3. **Database**. The easiest option is to use SQLite3, as the database is included in the package. All you need then is the `sqlite3-ruby` gem, as described in step 2, and the SQLite3 libraries and binary (see [sqlite.org](http://sqlite.org/download.html) for downloads and installation instructions). If you want to use MySQL, download and install a package for your platform from [MySQL.com](http://dev.mysql.com/downloads/mysql/5.0.html). The basic steps for Postgresql should be similar to those for MySQL, but they will not be discussed further here.
|
||||
|
||||
If you are using Unix, you might find [this guide](http://www.cooldown.com.ar/2006/12/16/install-tracks-on-ubuntu-or-debian/) by c00i90wn helpful. It was written for Tracks 1.043, but it should work for Tracks 1.6.
|
||||
You can find several installation howtos for specific setups [here](http://dev.rousette.org.uk/wiki/Tracks/Install). They were contributed by various Tracks users.
|
||||
|
||||
## Installation ##
|
||||
|
||||
This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see [Upgrading to Tracks 1.6][upgrading].
|
||||
This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see [Upgrading to Tracks 1.7][upgrading].
|
||||
|
||||
1. [Unzip tracks][unzip_install] and install in a directory
|
||||
2. Decide on a [database][database_install] to use
|
||||
1. SQLite3 - change database.yml to point to SQLite3 database
|
||||
1. SQLite3 - change database.yml to point to SQLite3 database. Make sure you add the complete path to the database
|
||||
2. MySQL - create new MySQL db and grant all privileges
|
||||
3. [Configure some variables][config_install]
|
||||
4. Populate the database with the [Tracks 1.6 schema][rake_install]
|
||||
4. Populate the database with the [Tracks 1.7 schema][rake_install]
|
||||
5. [Start the server][startserver_install]
|
||||
6. [Visit Tracks in a browser][signup_install]
|
||||
7. [Customise Tracks][customise_install]
|
||||
|
|
@ -86,34 +84,34 @@ Unzip the package and move Tracks into the directory you want to run it from. Fo
|
|||
Before you go any further, you need to decide which database you will use. See the [What you need to install][whatyouneed] section for details on installing the required components for you choice of database.
|
||||
|
||||
1. **SQLite3**. All you need to do is make sure that you point Tracks to the included SQLite3 database in `/db` in the next step, [Configure variables][config_install].
|
||||
2. **MySQL**. Once you have MySQL installed, you need to create a database to use with Tracks 1.6. Go into a terminal and issue the following commands:
|
||||
2. **MySQL**. Once you have MySQL installed, you need to create a database and database-user to use with Tracks 1.7. For this, you can use MySQL Administrator or go into a terminal and issue the following commands:
|
||||
<pre>
|
||||
<code>
|
||||
mysql -uroot -p
|
||||
mysql> CREATE DATABASE tracks15;
|
||||
mysql> GRANT ALL PRIVILEGES ON tracks15.* TO yourmysqluser@localhost \
|
||||
mysql> CREATE DATABASE tracks16;
|
||||
mysql> GRANT ALL PRIVILEGES ON tracks16.* TO yourmysqluser@localhost \
|
||||
IDENTIFIED BY 'password-goes-here' WITH GRANT OPTION;
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
### Configure variables [config_install] ###
|
||||
|
||||
1. If you downloaded Tracks 1.6 via Subversion, you need to duplicate the files `database.yml.tmpl` and `environment.yml.tmpl` and remove the `*.tmpl` extension from the duplicates. Similarly, duplicate `/log.tmpl` and remove the `*.tmpl` extension, then edit the files as described in steps 2 and 3.
|
||||
1. If you downloaded Tracks 1.7 via Subversion, you need to duplicate the files `database.yml.tmpl` and `environment.yml.tmpl` and remove the `*.tmpl` extension from the duplicates. Similarly, duplicate `/log.tmpl` and remove the `*.tmpl` extension, then edit the files as described in steps 2 and 3.
|
||||
2. Open the file `/config/database.yml` and edit the `production:` section with the details of your database. If you are using MySQL the `adapter:` line should read `adapter: mysql`, `host: localhost` (in the majority of cases), and your username and password should match those you assigned when you created the database. If you are using SQLite3, you should have only two lines under the production section: `adapter: sqlite3` and `database: db/tracks-15-blank.db`. If you downloaded the zipped file, the database.yml file is already configured to use the provided SQLite3 file.
|
||||
3. Open the file `/config/environment.rb`, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the `SALT = "change-me"` line (change the string "change-me" to some other string of your choice), and the time zone setting.
|
||||
3. Open the file `/config/environment.rb`, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the `SALT = "change-me"` line (change the string "change-me" to some other string of your choice), and the time zone setting. For the time zone setting, most people will only want to change the config.time_zone option and leave the timezone to use in your database to :utc
|
||||
4. If you are using Windows, you may need to check the 'shebang' lines (`#!/usr/bin/env ruby`) of the `/public/dispatch.*` files and all the files in the `/script` directory. They are set to `#!/usr/bin/env ruby` by default. This should work for all *nix based setups (Linux or Mac OS X), but Windows users will probably have to change it to something like `#c:/ruby/bin/ruby` to point to the Ruby binary on your system.
|
||||
|
||||
### Populate your database with the Tracks 1.6 schema [rake_install] ###
|
||||
### Populate your database with the Tracks 1.7 schema [rake_install] ###
|
||||
|
||||
Open a terminal and change into the root of your Tracks 1.6 directory. Enter the following command:
|
||||
Open a terminal and change into the root of your Tracks 1.7 directory. Enter the following command:
|
||||
|
||||
`rake db:migrate RAILS_ENV=production`
|
||||
|
||||
This will update your database with the required schema for Tracks 1.6. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).
|
||||
This will update your database with the required schema for Tracks 1.7. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).
|
||||
|
||||
### Start the server [startserver_install] ###
|
||||
|
||||
While still in the Terminal inside the Tracks 1.6 root directory, issue the following command:
|
||||
While still in the Terminal inside the Tracks 1.7 root directory, issue the following command:
|
||||
|
||||
`script/server -e production`
|
||||
|
||||
|
|
@ -127,7 +125,19 @@ Visit `http://0.0.0.0:3000/signup` in a browser (or whatever URL and port was re
|
|||
|
||||
Once logged in, add some Contexts and Projects, and then go ahead and add your actions. You might also want to visit the Preferences page to edit various settings to your liking. Have fun!
|
||||
|
||||
# Upgrading to Tracks 1.6 [upgrading] #
|
||||
# Upgrading to Tracks 1.7 [upgrading] #
|
||||
|
||||
## Upgrading from Tracks 1.6 [upgrading_1.6] ##
|
||||
|
||||
You will need to upgrade your `config/environment.rb` with the new content from `config/environment.rb.tmpl` included in 1.7, as the format of this file has changed a bit between 1.6 and 1.7. Also, there were some database changes in Tracks 1.7, so you need to migrate to them.
|
||||
|
||||
1. [Back up][backup_upgrade] your existing database and installation of Tracks
|
||||
2. [Install Tracks 1.7][install_upgrade] in a new directory
|
||||
3. [Copy over][config_upgrade] a few configuration files from your Tracks 1.6 directory. If using SQLite3, copy the old database into the new Tracks 1.7 directory.
|
||||
4. Rebuild your environment.rb from the updated environment.rb.tmpl
|
||||
5. Run `rake db:migrate RAILS_ENV=production` to [update your old database][rake_upgrade] to the new schema -- you did back up your database didn't you?
|
||||
6. Run `script/server` inside your Tracks 1.7 directory to [start up Tracks 1.7][startserver_upgrade].
|
||||
7. Once you are happy that everything is working well, [delete your old Tracks directory][cleanup_upgrade].
|
||||
|
||||
## Upgrading from Tracks 1.5 [upgrading_1.5] ##
|
||||
|
||||
|
|
@ -136,9 +146,9 @@ There are no changes to the database between 1.5 and 1.6, but you will need to u
|
|||
1. [Back up][backup_upgrade] your existing database and installation of Tracks
|
||||
2. [Install Tracks 1.6][install_upgrade] in a new directory
|
||||
3. [Copy over][config_upgrade] a few configuration files from your Tracks 1.043 directory. If using SQLite3, copy the old database into the new Tracks 1.6 directory
|
||||
4. Run `script/server` inside your Tracks 1.6 directory to [start up Tracks 1.6][startserver_upgrade].
|
||||
5. Once you are happy that everything is working well, [delete your old Tracks directory][cleanup_upgrade].
|
||||
|
||||
4. Rebuild your environment.rb from the updated environment.rb.tmpl
|
||||
5. Run `script/server` inside your Tracks 1.6 directory to [start up Tracks 1.6][startserver_upgrade].
|
||||
6. Once you are happy that everything is working well, [delete your old Tracks directory][cleanup_upgrade].
|
||||
|
||||
## Upgrading from Tracks 1.043 [upgrading_1043] ##
|
||||
|
||||
|
|
@ -157,11 +167,11 @@ It's very important that you **back up your database** before you start the upgr
|
|||
|
||||
`mysqldump –-user [user name] –-password=[password] [database name] > [dump file]`
|
||||
|
||||
Rename your old Tracks installation (e.g. to 'tracks-old') so that you can install Tracks 1.6 along side it.
|
||||
Rename your old Tracks installation (e.g. to 'tracks-old') so that you can install Tracks 1.7 along side it.
|
||||
|
||||
### Install Tracks 1.6 [install_upgrade] ###
|
||||
### Install Tracks 1.7 [install_upgrade] ###
|
||||
|
||||
There are two methods of downloading Tracks 1.6:
|
||||
There are two methods of downloading Tracks 1.7:
|
||||
|
||||
1. (Recommended for most people) Download the [zipped package](http://www.rousette.org.uk/projects/files/tracks-current.zip), and unzip in your preferred location (e.g. `~/Sites` for Mac OS X users).
|
||||
2. If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions):
|
||||
|
|
@ -179,14 +189,14 @@ There are two methods of downloading Tracks 1.6:
|
|||
There are a few files you need to copy over from your old installation. If you copy them over rather than moving them, you can still run your old version of Tracks if anything goes awry with the installation process.
|
||||
|
||||
1. Copy `/config/database.yml` from your old Tracks directory to the same location in the new one. Double check that the information there is still correct.
|
||||
2. Duplicate `/config/environment.rb.tmpl` in the Tracks 1.6 directory, and rename the file to `environment.rb`. Open the file and alter the line `SALT = "change-me"` so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location (`ENV['TZ'] = 'US/Eastern'`). If you have made any other customisations to `environment.rb` in the past, copy those over, but the contents of the file have changed quite a lot since 1.043, so check it carefully.
|
||||
2. Duplicate `/config/environment.rb.tmpl` in the Tracks 1.7 directory, and rename the file to `environment.rb`. Open the file and alter the line `SALT = "change-me"` so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location. If you have made any other customisations to `environment.rb` in the past, copy those over, but the contents of the file have slightly changed since 1.5, so check it carefully.
|
||||
3. Copy your `/log` directory over from your old installation to the root of the new one, or just rename `/log.tmpl` to `log` to start afresh.
|
||||
4. If you are using SQLite3, copy your database from `/db` in your old Tracks directory to the same location in the new one.
|
||||
5. If you are using Windows, you may need to check the 'shebang' lines (`#!/usr/bin/env ruby`)[^env] of the `/public/dispatch.*` files and all the files in the `/script` directory. They are set to `#!/usr/bin/env ruby` by default. Check the format of those lines in your old installation, and change the new ones as necessary.
|
||||
|
||||
### Update your old database to the new format [rake_upgrade] ###
|
||||
|
||||
In a terminal, change directories so that you are inside the Tracks 1.6 directory. Then issue the command to update your Tracks 1.043 database to the format required for Tracks 1.6:
|
||||
In a terminal, change directories so that you are inside the Tracks 1.7 directory. Then issue the command to update your Tracks 1.6 database to the format required for Tracks 1.7:
|
||||
|
||||
`rake db:migrate RAILS_ENV=production`
|
||||
|
||||
|
|
@ -194,7 +204,7 @@ Watch the output carefully for errors, but it should report at the end of the pr
|
|||
|
||||
### Start the server [startserver_upgrade] ###
|
||||
|
||||
If you're still in the Tracks 1.6 root directory in a terminal, enter the following command to start up Tracks in production mode:
|
||||
If you're still in the Tracks 1.7 root directory in a terminal, enter the following command to start up Tracks in production mode:
|
||||
|
||||
`script/server -e production`
|
||||
|
||||
|
|
@ -203,7 +213,7 @@ Visit the URL indicated by the output (e.g. `** Mongrel available at 0.0.0.0:300
|
|||
|
||||
### Clean up your old installation [cleanup_upgrade] ###
|
||||
|
||||
Once you're certain that your new Tracks 1.6 installation is working perfectly, you can delete your old Tracks directory.
|
||||
Once you're certain that your new Tracks 1.7 installation is working perfectly, you can delete your old Tracks directory.
|
||||
|
||||
[^env]: The `env` binary helps to locate other binaries, regardless of their location. If you don't have `env` installed, you'll need to change this line to point to the location of your Ruby binary.
|
||||
|
||||
|
|
|
|||
BIN
doc/manual.pdf
BIN
doc/manual.pdf
Binary file not shown.
105
doc/manual.tex
105
doc/manual.tex
|
|
@ -112,12 +112,12 @@
|
|||
\usepackage{booktabs} % Better tables
|
||||
\usepackage{tabulary} % Support longer table cells
|
||||
\usepackage[utf8]{inputenc} % For UTF-8 support
|
||||
% \usepackage{xcolor} % Allow for color (annotations)
|
||||
%
|
||||
% %\geometry{landscape} % Activate for rotated page geometry
|
||||
%
|
||||
% \usepackage[parfill]{parskip} % Activate to begin paragraphs with an empty
|
||||
% % line rather than an indent
|
||||
%\usepackage{xcolor} % Allow for color (annotations)
|
||||
|
||||
%\geometry{landscape} % Activate for rotated page geometry
|
||||
|
||||
%\usepackage[parfill]{parskip} % Activate to begin paragraphs with an empty
|
||||
% line rather than an indent
|
||||
|
||||
|
||||
\def\myauthor{Author} % In case these were not included in metadata
|
||||
|
|
@ -130,14 +130,14 @@
|
|||
\def\myauthor{Tracks Development Team}
|
||||
\def\baseheaderlevel{2}
|
||||
\def\mycopyright{2008 rousette.org.uk \\ This work is licensed under a Creative Commons License. \\ http://creativecommons.org/licenses/by-nc-sa/3.0/}
|
||||
\date{2008-04-07}
|
||||
\date{2008-12-14}
|
||||
\def\format{complete}
|
||||
\def\latexxslt{memoir-twosided-manual.xslt}
|
||||
\def\revision{Revision: \$Id: manual.markdown 864 2008-06-03 17:01:00Z bsag \$}
|
||||
\def\mytitle{Tracks 1.6 Manual}
|
||||
\def\version{1.6}
|
||||
% \usepackage{xmpincl}
|
||||
% \includexmp{CCAttributionShareAlike}
|
||||
\def\revision{Revision: \$Id: manual.markdown 2008-12-14 11:50:00Z bsag \$}
|
||||
\def\mytitle{Tracks 1.7 Manual}
|
||||
\def\version{1.7}
|
||||
\usepackage{xmpincl}
|
||||
\includexmp{CCAttributionShareAlike}
|
||||
|
||||
|
||||
%
|
||||
|
|
@ -254,19 +254,16 @@
|
|||
\setlength{\parskip}{\baselineskip/2}
|
||||
|
||||
\mainmatter
|
||||
\chapter{Installing Tracks 1.6}
|
||||
\label{installingtracks1.6}
|
||||
\chapter{Installing Tracks 1.7}
|
||||
\label{installingtracks1.7}
|
||||
|
||||
\section{Introduction}
|
||||
\label{introduction}
|
||||
|
||||
\textbf{An important note for version 1.6: OpenID support is broken in this release. The fix isn't trivial because of changes to the \texttt{ruby-openid} gem, so we wanted to get this version out now and fix OpenID for the next release. If you depend on OpenID integration, we recommend waiting until the next release.}
|
||||
Tracks 1.7 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it's only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in \texttt{doc/CHANGELOG}. Full API documentation can be found at \texttt{doc/app/index.html}, once you have run \texttt{rake appdoc}
|
||||
|
||||
|
||||
Tracks 1.6 has been thoroughly beta tested by a large number of people, and should be fully stable for everyday use. However, once set up, Tracks will contain the majority of your plans for your work and personal life, so it's only sensible to make sure that you have frequent, reliable backups of your data. Full changenotes on the release can be found in \texttt{doc/CHANGELOG}. Full API documentation can be found at \texttt{doc/app/index.html}, once you have run \texttt{rake appdoc}
|
||||
|
||||
|
||||
There are two methods of downloading Tracks 1.6:
|
||||
There are two methods of downloading Tracks 1.7:
|
||||
|
||||
|
||||
\begin{enumerate}
|
||||
|
|
@ -315,7 +312,7 @@ If you'd like an easy way to access Tracks from any internet-connected computer,
|
|||
|
||||
\item Tracks itself
|
||||
|
||||
\item Rails 2.0.2 (installed in the \texttt{/vendor/rails} directory, so you do not need to install Rails yourself)
|
||||
\item Rails 2.2.2 (installed in the \texttt{/vendor/rails} directory, so you do not need to install Rails yourself)
|
||||
|
||||
\item An empty SQLite3 database, set up with the correct database schema
|
||||
\end{enumerate}
|
||||
|
|
@ -336,13 +333,13 @@ If you don't want to (or can't) use one of the all in one installations, you'll
|
|||
\item \textbf{Database}. The easiest option is to use SQLite3, as the database is included in the package. All you need then is the \texttt{sqlite3-ruby} gem, as described in step 2, and the SQLite3 libraries and binary (see \href{http://sqlite.org/download.html}{sqlite.org} for downloads and installation instructions). If you want to use MySQL, download and install a package for your platform from \href{http://dev.mysql.com/downloads/mysql/5.0.html}{MySQL.com}. The basic steps for Postgresql should be similar to those for MySQL, but they will not be discussed further here.
|
||||
\end{enumerate}
|
||||
|
||||
If you are using Unix, you might find \href{http://www.cooldown.com.ar/2006/12/16/install-tracks-on-ubuntu-or-debian/}{this guide} by c00i90wn helpful. It was written for Tracks 1.043, but it should work for Tracks 1.6.
|
||||
You can find several installation howtos for specific setups \href{http://dev.rousette.org.uk/wiki/Tracks/Install}{here}. They were contributed by various Tracks users.
|
||||
|
||||
|
||||
\section{Installation}
|
||||
\label{installation}
|
||||
|
||||
This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see Upgrading to Tracks 1.6 (\autoref{upgrading}).
|
||||
This description is intended for people installing Tracks from scratch. If you would like to upgrade an existing installation, please see Upgrading to Tracks 1.7 (\autoref{upgrading}).
|
||||
|
||||
|
||||
\begin{enumerate}
|
||||
|
|
@ -354,7 +351,7 @@ This description is intended for people installing Tracks from scratch. If you w
|
|||
\begin{enumerate}
|
||||
|
||||
|
||||
\item SQLite3 - change database.yml to point to SQLite3 database
|
||||
\item SQLite3 - change database.yml to point to SQLite3 database. Make sure you add the complete path to the database
|
||||
|
||||
\item MySQL - create new MySQL db and grant all privileges
|
||||
\end{enumerate}
|
||||
|
|
@ -363,7 +360,7 @@ This description is intended for people installing Tracks from scratch. If you w
|
|||
|
||||
\item Configure some variables (\autoref{config_install})
|
||||
|
||||
\item Populate the database with the Tracks 1.6 schema (\autoref{rake_install})
|
||||
\item Populate the database with the Tracks 1.7 schema (\autoref{rake_install})
|
||||
|
||||
\item Start the server (\autoref{startserver_install})
|
||||
|
||||
|
|
@ -389,7 +386,7 @@ Before you go any further, you need to decide which database you will use. See t
|
|||
|
||||
\item \textbf{SQLite3}. All you need to do is make sure that you point Tracks to the included SQLite3 database in \texttt{/db} in the next step, Configure variables (\autoref{config_install}).
|
||||
|
||||
\item \textbf{MySQL}. Once you have MySQL installed, you need to create a database to use with Tracks 1.6. Go into a terminal and issue the following commands:
|
||||
\item \textbf{MySQL}. Once you have MySQL installed, you need to create a database and database-user to use with Tracks 1.7. For this, you can use MySQL Administrator or go into a terminal and issue the following commands:
|
||||
\end{enumerate}
|
||||
|
||||
\begin{adjustwidth}{2.5em}{2.5em}
|
||||
|
|
@ -397,8 +394,8 @@ Before you go any further, you need to decide which database you will use. See t
|
|||
|
||||
|
||||
mysql -uroot -p
|
||||
mysql> CREATE DATABASE tracks15;
|
||||
mysql> GRANT ALL PRIVILEGES ON tracks15.* TO yourmysqluser@localhost \
|
||||
mysql> CREATE DATABASE tracks16;
|
||||
mysql> GRANT ALL PRIVILEGES ON tracks16.* TO yourmysqluser@localhost \
|
||||
IDENTIFIED BY 'password-goes-here' WITH GRANT OPTION;
|
||||
|
||||
|
||||
|
|
@ -411,31 +408,31 @@ Before you go any further, you need to decide which database you will use. See t
|
|||
\begin{enumerate}
|
||||
|
||||
|
||||
\item If you downloaded Tracks 1.6 via Subversion, you need to duplicate the files \texttt{database.yml.tmpl} and \texttt{environment.yml.tmpl} and remove the \texttt{*.tmpl} extension from the duplicates. Similarly, duplicate \texttt{/log.tmpl} and remove the \texttt{*.tmpl} extension, then edit the files as described in steps 2 and 3.
|
||||
\item If you downloaded Tracks 1.7 via Subversion, you need to duplicate the files \texttt{database.yml.tmpl} and \texttt{environment.yml.tmpl} and remove the \texttt{*.tmpl} extension from the duplicates. Similarly, duplicate \texttt{/log.tmpl} and remove the \texttt{*.tmpl} extension, then edit the files as described in steps 2 and 3.
|
||||
|
||||
\item Open the file \texttt{/config/database.yml} and edit the \texttt{production:} section with the details of your database. If you are using MySQL the \texttt{adapter:} line should read \texttt{adapter: mysql}, \texttt{host: localhost} (in the majority of cases), and your username and password should match those you assigned when you created the database. If you are using SQLite3, you should have only two lines under the production section: \texttt{adapter: sqlite3} and \texttt{database: db/tracks-15-blank.db}. If you downloaded the zipped file, the database.yml file is already configured to use the provided SQLite3 file.
|
||||
|
||||
\item Open the file \texttt{/config/environment.rb}, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the \texttt{SALT = "change-me"} line (change the string ``change-me" to some other string of your choice), and the time zone setting.
|
||||
\item Open the file \texttt{/config/environment.rb}, and read through the settings to make sure that they suit your setup. In most cases, all you need to change is the \texttt{SALT = "change-me"} line (change the string ``change-me" to some other string of your choice), and the time zone setting. For the time zone setting, most people will only want to change the config.time\_zone option and leave the timezone to use in your database to :utc
|
||||
|
||||
\item If you are using Windows, you may need to check the `shebang' lines (\texttt{\#!/usr/bin/env ruby}) of the \texttt{/public/dispatch.*} files and all the files in the \texttt{/script} directory. They are set to \texttt{\#!/usr/bin/env ruby} by default. This should work for all *nix based setups (Linux or Mac OS X), but Windows users will probably have to change it to something like \texttt{\#c:/ruby/bin/ruby} to point to the Ruby binary on your system.
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Populate your database with the Tracks 1.6 schema}
|
||||
\subsection{Populate your database with the Tracks 1.7 schema}
|
||||
\label{rake_install}
|
||||
|
||||
Open a terminal and change into the root of your Tracks 1.6 directory. Enter the following command:
|
||||
Open a terminal and change into the root of your Tracks 1.7 directory. Enter the following command:
|
||||
|
||||
|
||||
\texttt{rake db:migrate RAILS\_ENV=production}
|
||||
|
||||
|
||||
This will update your database with the required schema for Tracks 1.6. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).
|
||||
This will update your database with the required schema for Tracks 1.7. If you are using SQLite3, it is not strictly necessary, because the SQLite3 database included with Tracks already has the schema included in it, but it should not do any harm to run the command (nothing will happen if it is up to date).
|
||||
|
||||
|
||||
\subsection{Start the server}
|
||||
\label{startserver_install}
|
||||
|
||||
While still in the Terminal inside the Tracks 1.6 root directory, issue the following command:
|
||||
While still in the Terminal inside the Tracks 1.7 root directory, issue the following command:
|
||||
|
||||
|
||||
\texttt{script/server -e production}
|
||||
|
|
@ -456,9 +453,33 @@ Visit \texttt{http://0.0.0.0:3000/signup} in a browser (or whatever URL and port
|
|||
Once logged in, add some Contexts and Projects, and then go ahead and add your actions. You might also want to visit the Preferences page to edit various settings to your liking. Have fun!
|
||||
|
||||
|
||||
\chapter{Upgrading to Tracks 1.6}
|
||||
\chapter{Upgrading to Tracks 1.7}
|
||||
\label{upgrading}
|
||||
|
||||
\section{Upgrading from Tracks 1.6}
|
||||
\label{upgrading_1.6}
|
||||
|
||||
You will need to upgrade your \texttt{config/environment.rb} with the new content from \texttt{config/environment.rb.tmpl} included in 1.7, as the format of this file has changed a bit between 1.6 and 1.7. Also, there were some database changes in Tracks 1.7, so you need to migrate to them.
|
||||
|
||||
|
||||
\begin{enumerate}
|
||||
|
||||
|
||||
\item Back up (\autoref{backup_upgrade}) your existing database and installation of Tracks
|
||||
|
||||
\item Install Tracks 1.7 (\autoref{install_upgrade}) in a new directory
|
||||
|
||||
\item Copy over (\autoref{config_upgrade}) a few configuration files from your Tracks 1.6 directory. If using SQLite3, copy the old database into the new Tracks 1.7 directory.
|
||||
|
||||
\item Rebuild your environment.rb from the updated environment.rb.tmpl
|
||||
|
||||
\item Run \texttt{rake db:migrate RAILS\_ENV=production} to update your old database (\autoref{rake_upgrade}) to the new schema -- you did back up your database didn't you?
|
||||
|
||||
\item Run \texttt{script/server} inside your Tracks 1.7 directory to start up Tracks 1.7 (\autoref{startserver_upgrade}).
|
||||
|
||||
\item Once you are happy that everything is working well, delete your old Tracks directory (\autoref{cleanup_upgrade}).
|
||||
\end{enumerate}
|
||||
|
||||
\section{Upgrading from Tracks 1.5}
|
||||
\label{upgrading_1.5}
|
||||
|
||||
|
|
@ -474,6 +495,8 @@ There are no changes to the database between 1.5 and 1.6, but you will need to u
|
|||
|
||||
\item Copy over (\autoref{config_upgrade}) a few configuration files from your Tracks 1.043 directory. If using SQLite3, copy the old database into the new Tracks 1.6 directory
|
||||
|
||||
\item Rebuild your environment.rb from the updated environment.rb.tmpl
|
||||
|
||||
\item Run \texttt{script/server} inside your Tracks 1.6 directory to start up Tracks 1.6 (\autoref{startserver_upgrade}).
|
||||
|
||||
\item Once you are happy that everything is working well, delete your old Tracks directory (\autoref{cleanup_upgrade}).
|
||||
|
|
@ -510,13 +533,13 @@ It's very important that you \textbf{back up your database} before you start the
|
|||
\texttt{mysqldump ---user [user name] ---password=[password] [database name] $>$ [dump file]}
|
||||
|
||||
|
||||
Rename your old Tracks installation (e.g.\ to `tracks-old') so that you can install Tracks 1.6 along side it.
|
||||
Rename your old Tracks installation (e.g.\ to `tracks-old') so that you can install Tracks 1.7 along side it.
|
||||
|
||||
|
||||
\subsection{Install Tracks 1.6}
|
||||
\subsection{Install Tracks 1.7}
|
||||
\label{install_upgrade}
|
||||
|
||||
There are two methods of downloading Tracks 1.6:
|
||||
There are two methods of downloading Tracks 1.7:
|
||||
|
||||
|
||||
\begin{enumerate}
|
||||
|
|
@ -550,7 +573,7 @@ There are a few files you need to copy over from your old installation. If you c
|
|||
|
||||
\item Copy \texttt{/config/database.yml} from your old Tracks directory to the same location in the new one. Double check that the information there is still correct.
|
||||
|
||||
\item Duplicate \texttt{/config/environment.rb.tmpl} in the Tracks 1.6 directory, and rename the file to \texttt{environment.rb}. Open the file and alter the line \texttt{SALT = "change-me"} so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location (\texttt{ENV['TZ'] = 'US/Eastern'}). If you have made any other customisations to \texttt{environment.rb} in the past, copy those over, but the contents of the file have changed quite a lot since 1.043, so check it carefully.
|
||||
\item Duplicate \texttt{/config/environment.rb.tmpl} in the Tracks 1.7 directory, and rename the file to \texttt{environment.rb}. Open the file and alter the line \texttt{SALT = "change-me"} so that it matches what you had in this file in your old installation. You may also want to change the time zone setting as appropriate for your location. If you have made any other customisations to \texttt{environment.rb} in the past, copy those over, but the contents of the file have slightly changed since 1.5, so check it carefully.
|
||||
|
||||
\item Copy your \texttt{/log} directory over from your old installation to the root of the new one, or just rename \texttt{/log.tmpl} to \texttt{log} to start afresh.
|
||||
|
||||
|
|
@ -562,7 +585,7 @@ There are a few files you need to copy over from your old installation. If you c
|
|||
\subsection{Update your old database to the new format}
|
||||
\label{rake_upgrade}
|
||||
|
||||
In a terminal, change directories so that you are inside the Tracks 1.6 directory. Then issue the command to update your Tracks 1.043 database to the format required for Tracks 1.6:
|
||||
In a terminal, change directories so that you are inside the Tracks 1.7 directory. Then issue the command to update your Tracks 1.6 database to the format required for Tracks 1.7:
|
||||
|
||||
|
||||
\texttt{rake db:migrate RAILS\_ENV=production}
|
||||
|
|
@ -574,7 +597,7 @@ Watch the output carefully for errors, but it should report at the end of the pr
|
|||
\subsection{Start the server}
|
||||
\label{startserver_upgrade}
|
||||
|
||||
If you're still in the Tracks 1.6 root directory in a terminal, enter the following command to start up Tracks in production mode:
|
||||
If you're still in the Tracks 1.7 root directory in a terminal, enter the following command to start up Tracks in production mode:
|
||||
|
||||
|
||||
\texttt{script/server -e production}
|
||||
|
|
@ -587,7 +610,7 @@ Visit the URL indicated by the output (e.g.\ \texttt{** Mongrel available at 0.0
|
|||
\subsection{Clean up your old installation}
|
||||
\label{cleanup_upgrade}
|
||||
|
||||
Once you're certain that your new Tracks 1.6 installation is working perfectly, you can delete your old Tracks directory.
|
||||
Once you're certain that your new Tracks 1.7 installation is working perfectly, you can delete your old Tracks directory.
|
||||
|
||||
|
||||
\section{Upgrading from versions prior to 1.043}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ namespace :tracks do
|
|||
desc 'Replace the password of USER with a new one.'
|
||||
task :password => :environment do
|
||||
|
||||
Dependencies.load_paths.unshift(File.dirname(__FILE__) + "/..../vendor/gems/highline-1.4.0/lib")
|
||||
Dependencies.load_paths.unshift(File.dirname(__FILE__) + "/../../vendor/gems/highline-1.5.0/lib")
|
||||
require "highline/import"
|
||||
|
||||
user = User.find_by_login(ENV['USER'])
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
raise "To avoid rake task loading problems: run 'rake clobber' in vendor/plugins/rspec" if File.directory?(File.join(File.dirname(__FILE__), *%w[.. .. vendor plugins rspec pkg]))
|
||||
raise "To avoid rake task loading problems: run 'rake clobber' in vendor/plugins/rspec-rails" if File.directory?(File.join(File.dirname(__FILE__), *%w[.. .. vendor plugins rspec-rails pkg]))
|
||||
|
||||
# In rails 1.2, plugins aren't available in the path until they're loaded.
|
||||
# Check to see if the rspec plugin is installed first and require
|
||||
# it if it is. If not, use the gem version.
|
||||
rspec_base = File.expand_path(File.dirname(__FILE__) + '/../../rspec/lib')
|
||||
rspec_base = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec/lib')
|
||||
$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
|
||||
require 'spec/rake/spectask'
|
||||
require 'spec/translator'
|
||||
|
||||
spec_prereq = File.exist?(File.join(RAILS_ROOT, 'config', 'database.yml')) ? "db:test:prepare" : :noop
|
||||
task :noop do
|
||||
|
|
@ -64,13 +66,6 @@ namespace :spec do
|
|||
end
|
||||
end
|
||||
|
||||
desc "Translate/upgrade specs using the built-in translator"
|
||||
task :translate do
|
||||
translator = ::Spec::Translator.new
|
||||
dir = RAILS_ROOT + '/spec'
|
||||
translator.translate(dir, dir)
|
||||
end
|
||||
|
||||
# Setup specs for stats
|
||||
task :statsetup do
|
||||
require 'code_statistics'
|
||||
|
|
@ -39,4 +39,4 @@ RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
|
|||
# Example:
|
||||
# ErrorDocument 500 /500.html
|
||||
|
||||
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
|
||||
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
|
||||
|
|
|
|||
146
public/javascripts/controls.js
vendored
146
public/javascripts/controls.js
vendored
|
|
@ -1,22 +1,22 @@
|
|||
// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
|
||||
// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// that's independent of the data source for autocompletion. This
|
||||
// includes drawing the autocompletion menu, observing keyboard
|
||||
// and mouse events, and similar.
|
||||
//
|
||||
// Specific autocompleters need to provide, at the very least,
|
||||
// Specific autocompleters need to provide, at the very least,
|
||||
// a getUpdatedChoices function that will be invoked every time
|
||||
// the text inside the monitored textbox changes. This method
|
||||
// the text inside the monitored textbox changes. This method
|
||||
// should get the text for which to provide autocompletion by
|
||||
// invoking this.getToken(), NOT by directly accessing
|
||||
// this.element.value. This is to allow incremental tokenized
|
||||
|
|
@ -30,23 +30,23 @@
|
|||
// will incrementally autocomplete with a comma as the token.
|
||||
// Additionally, ',' in the above example can be replaced with
|
||||
// a token array, e.g. { tokens: [',', '\n'] } which
|
||||
// enables autocompletion on multiple tokens. This is most
|
||||
// useful when one of the tokens is \n (a newline), as it
|
||||
// enables autocompletion on multiple tokens. This is most
|
||||
// useful when one of the tokens is \n (a newline), as it
|
||||
// allows smart autocompletion after linebreaks.
|
||||
|
||||
if(typeof Effect == 'undefined')
|
||||
throw("controls.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Autocompleter = { }
|
||||
var Autocompleter = { };
|
||||
Autocompleter.Base = Class.create({
|
||||
baseInitialize: function(element, update, options) {
|
||||
element = $(element)
|
||||
this.element = element;
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
element = $(element);
|
||||
this.element = element;
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
this.oldElementValue = this.element.value;
|
||||
|
||||
|
|
@ -59,28 +59,28 @@ Autocompleter.Base = Class.create({
|
|||
this.options.tokens = this.options.tokens || [];
|
||||
this.options.frequency = this.options.frequency || 0.4;
|
||||
this.options.minChars = this.options.minChars || 1;
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {
|
||||
setHeight: false,
|
||||
setHeight: false,
|
||||
offsetTop: element.offsetHeight
|
||||
});
|
||||
}
|
||||
Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
this.options.onHide = this.options.onHide ||
|
||||
this.options.onHide = this.options.onHide ||
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
|
||||
if(typeof(this.options.tokens) == 'string')
|
||||
if(typeof(this.options.tokens) == 'string')
|
||||
this.options.tokens = new Array(this.options.tokens);
|
||||
// Force carriage returns as token delimiters anyway
|
||||
if (!this.options.tokens.include('\n'))
|
||||
this.options.tokens.push('\n');
|
||||
|
||||
this.observer = null;
|
||||
|
||||
|
||||
this.element.setAttribute('autocomplete','off');
|
||||
|
||||
Element.hide(this.update);
|
||||
|
|
@ -91,10 +91,10 @@ Autocompleter.Base = Class.create({
|
|||
|
||||
show: function() {
|
||||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
||||
if(!this.iefix &&
|
||||
if(!this.iefix &&
|
||||
(Prototype.Browser.IE) &&
|
||||
(Element.getStyle(this.update, 'position')=='absolute')) {
|
||||
new Insertion.After(this.update,
|
||||
new Insertion.After(this.update,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
|
|
@ -102,7 +102,7 @@ Autocompleter.Base = Class.create({
|
|||
}
|
||||
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
|
||||
},
|
||||
|
||||
|
||||
fixIEOverlapping: function() {
|
||||
Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
|
||||
this.iefix.style.zIndex = 1;
|
||||
|
|
@ -150,15 +150,15 @@ Autocompleter.Base = Class.create({
|
|||
Event.stop(event);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
|
||||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
|
||||
|
||||
this.changed = true;
|
||||
this.hasFocus = true;
|
||||
|
||||
if(this.observer) clearTimeout(this.observer);
|
||||
this.observer =
|
||||
this.observer =
|
||||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
||||
},
|
||||
|
||||
|
|
@ -170,35 +170,35 @@ Autocompleter.Base = Class.create({
|
|||
|
||||
onHover: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
if(this.index != element.autocompleteIndex)
|
||||
if(this.index != element.autocompleteIndex)
|
||||
{
|
||||
this.index = element.autocompleteIndex;
|
||||
this.render();
|
||||
}
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
|
||||
onClick: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
this.index = element.autocompleteIndex;
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
|
||||
onBlur: function(event) {
|
||||
// needed to make click events working
|
||||
setTimeout(this.hide.bind(this), 250);
|
||||
this.hasFocus = false;
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if(this.entryCount > 0) {
|
||||
for (var i = 0; i < this.entryCount; i++)
|
||||
this.index==i ?
|
||||
Element.addClassName(this.getEntry(i),"selected") :
|
||||
this.index==i ?
|
||||
Element.addClassName(this.getEntry(i),"selected") :
|
||||
Element.removeClassName(this.getEntry(i),"selected");
|
||||
if(this.hasFocus) {
|
||||
if(this.hasFocus) {
|
||||
this.show();
|
||||
this.active = true;
|
||||
}
|
||||
|
|
@ -207,27 +207,27 @@ Autocompleter.Base = Class.create({
|
|||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--
|
||||
if(this.index > 0) this.index--;
|
||||
else this.index = this.entryCount-1;
|
||||
this.getEntry(this.index).scrollIntoView(false);
|
||||
this.getEntry(this.index).scrollIntoView(true);
|
||||
},
|
||||
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++
|
||||
if(this.index < this.entryCount-1) this.index++;
|
||||
else this.index = 0;
|
||||
this.getEntry(this.index).scrollIntoView(false);
|
||||
},
|
||||
|
||||
|
||||
getEntry: function(index) {
|
||||
return this.update.firstChild.childNodes[index];
|
||||
},
|
||||
|
||||
|
||||
getCurrentEntry: function() {
|
||||
return this.getEntry(this.index);
|
||||
},
|
||||
|
||||
|
||||
selectEntry: function() {
|
||||
this.active = false;
|
||||
this.updateElement(this.getCurrentEntry());
|
||||
|
|
@ -244,7 +244,7 @@ Autocompleter.Base = Class.create({
|
|||
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
|
||||
} else
|
||||
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||
|
||||
|
||||
var bounds = this.getTokenBounds();
|
||||
if (bounds[0] != -1) {
|
||||
var newValue = this.element.value.substr(0, bounds[0]);
|
||||
|
|
@ -257,7 +257,7 @@ Autocompleter.Base = Class.create({
|
|||
}
|
||||
this.oldElementValue = this.element.value;
|
||||
this.element.focus();
|
||||
|
||||
|
||||
if (this.options.afterUpdateElement)
|
||||
this.options.afterUpdateElement(this.element, selectedElement);
|
||||
},
|
||||
|
|
@ -269,20 +269,20 @@ Autocompleter.Base = Class.create({
|
|||
Element.cleanWhitespace(this.update.down());
|
||||
|
||||
if(this.update.firstChild && this.update.down().childNodes) {
|
||||
this.entryCount =
|
||||
this.entryCount =
|
||||
this.update.down().childNodes.length;
|
||||
for (var i = 0; i < this.entryCount; i++) {
|
||||
var entry = this.getEntry(i);
|
||||
entry.autocompleteIndex = i;
|
||||
this.addObservers(entry);
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
this.entryCount = 0;
|
||||
}
|
||||
|
||||
this.stopIndicator();
|
||||
this.index = 0;
|
||||
|
||||
|
||||
if(this.entryCount==1 && this.options.autoSelect) {
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
|
|
@ -298,7 +298,7 @@ Autocompleter.Base = Class.create({
|
|||
},
|
||||
|
||||
onObserverEvent: function() {
|
||||
this.changed = false;
|
||||
this.changed = false;
|
||||
this.tokenBounds = null;
|
||||
if(this.getToken().length>=this.options.minChars) {
|
||||
this.getUpdatedChoices();
|
||||
|
|
@ -351,16 +351,16 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
|
|||
|
||||
getUpdatedChoices: function() {
|
||||
this.startIndicator();
|
||||
|
||||
var entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
|
||||
var entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
encodeURIComponent(this.getToken());
|
||||
|
||||
this.options.parameters = this.options.callback ?
|
||||
this.options.callback(this.element, entry) : entry;
|
||||
|
||||
if(this.options.defaultParams)
|
||||
if(this.options.defaultParams)
|
||||
this.options.parameters += '&' + this.options.defaultParams;
|
||||
|
||||
|
||||
new Ajax.Request(this.url, this.options);
|
||||
},
|
||||
|
||||
|
|
@ -382,7 +382,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
|
|||
// - choices - How many autocompletion choices to offer
|
||||
//
|
||||
// - partialSearch - If false, the autocompleter will match entered
|
||||
// text only at the beginning of strings in the
|
||||
// text only at the beginning of strings in the
|
||||
// autocomplete array. Defaults to true, which will
|
||||
// match text at the beginning of any *word* in the
|
||||
// strings in the autocomplete array. If you want to
|
||||
|
|
@ -399,7 +399,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
|
|||
// - ignoreCase - Whether to ignore case when autocompleting.
|
||||
// Defaults to true.
|
||||
//
|
||||
// It's possible to pass in a custom function as the 'selector'
|
||||
// It's possible to pass in a custom function as the 'selector'
|
||||
// option, if you prefer to write your own autocompletion logic.
|
||||
// In that case, the other options above will not apply unless
|
||||
// you support them.
|
||||
|
|
@ -427,20 +427,20 @@ Autocompleter.Local = Class.create(Autocompleter.Base, {
|
|||
var entry = instance.getToken();
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < instance.options.array.length &&
|
||||
ret.length < instance.options.choices ; i++) {
|
||||
for (var i = 0; i < instance.options.array.length &&
|
||||
ret.length < instance.options.choices ; i++) {
|
||||
|
||||
var elem = instance.options.array[i];
|
||||
var foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
||||
var foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
||||
elem.indexOf(entry);
|
||||
|
||||
while (foundPos != -1) {
|
||||
if (foundPos == 0 && elem.length != entry.length) {
|
||||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
if (foundPos == 0 && elem.length != entry.length) {
|
||||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
break;
|
||||
} else if (entry.length >= instance.options.partialChars &&
|
||||
} else if (entry.length >= instance.options.partialChars &&
|
||||
instance.options.partialSearch && foundPos != -1) {
|
||||
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
|
||||
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
|
|
@ -450,14 +450,14 @@ Autocompleter.Local = Class.create(Autocompleter.Base, {
|
|||
}
|
||||
}
|
||||
|
||||
foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
|
||||
foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
|
||||
elem.indexOf(entry, foundPos + 1);
|
||||
|
||||
}
|
||||
}
|
||||
if (partial.length)
|
||||
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
|
||||
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
|
||||
return "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, options || { });
|
||||
|
|
@ -474,7 +474,7 @@ Field.scrollFreeActivate = function(field) {
|
|||
setTimeout(function() {
|
||||
Field.activate(field);
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
|
||||
Ajax.InPlaceEditor = Class.create({
|
||||
initialize: function(element, url, options) {
|
||||
|
|
@ -604,7 +604,7 @@ Ajax.InPlaceEditor = Class.create({
|
|||
this.triggerCallback('onEnterHover');
|
||||
},
|
||||
getText: function() {
|
||||
return this.element.innerHTML;
|
||||
return this.element.innerHTML.unescapeHTML();
|
||||
},
|
||||
handleAJAXFailure: function(transport) {
|
||||
this.triggerCallback('onFailure', transport);
|
||||
|
|
@ -780,7 +780,7 @@ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
|
|||
onSuccess: function(transport) {
|
||||
var js = transport.responseText.strip();
|
||||
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
|
||||
throw 'Server returned an invalid collection representation.';
|
||||
throw('Server returned an invalid collection representation.');
|
||||
this._collection = eval(js);
|
||||
this.checkForExternalText();
|
||||
}.bind(this),
|
||||
|
|
@ -937,7 +937,7 @@ Ajax.InPlaceCollectionEditor.DefaultOptions = {
|
|||
loadingCollectionText: 'Loading options...'
|
||||
};
|
||||
|
||||
// Delayed observer, like Form.Element.Observer,
|
||||
// Delayed observer, like Form.Element.Observer,
|
||||
// but waits for delay after last key input
|
||||
// Ideal for live-search fields
|
||||
|
||||
|
|
@ -947,7 +947,7 @@ Form.Element.DelayedObserver = Class.create({
|
|||
this.element = $(element);
|
||||
this.callback = callback;
|
||||
this.timer = null;
|
||||
this.lastValue = $F(this.element);
|
||||
this.lastValue = $F(this.element);
|
||||
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
|
||||
},
|
||||
delayedListener: function(event) {
|
||||
|
|
@ -960,4 +960,4 @@ Form.Element.DelayedObserver = Class.create({
|
|||
this.timer = null;
|
||||
this.callback(this.element, $F(this.element));
|
||||
}
|
||||
});
|
||||
});
|
||||
329
public/javascripts/dragdrop.js
vendored
329
public/javascripts/dragdrop.js
vendored
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ var Droppables = {
|
|||
options._containers.push($(containment));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(options.accept) options.accept = [options.accept].flatten();
|
||||
|
||||
Element.makePositioned(element); // fix IE
|
||||
|
|
@ -40,34 +40,34 @@ var Droppables = {
|
|||
|
||||
this.drops.push(options);
|
||||
},
|
||||
|
||||
|
||||
findDeepestChild: function(drops) {
|
||||
deepest = drops[0];
|
||||
|
||||
|
||||
for (i = 1; i < drops.length; ++i)
|
||||
if (Element.isParent(drops[i].element, deepest.element))
|
||||
deepest = drops[i];
|
||||
|
||||
|
||||
return deepest;
|
||||
},
|
||||
|
||||
isContained: function(element, drop) {
|
||||
var containmentNode;
|
||||
if(drop.tree) {
|
||||
containmentNode = element.treeNode;
|
||||
containmentNode = element.treeNode;
|
||||
} else {
|
||||
containmentNode = element.parentNode;
|
||||
}
|
||||
return drop._containers.detect(function(c) { return containmentNode == c });
|
||||
},
|
||||
|
||||
|
||||
isAffected: function(point, element, drop) {
|
||||
return (
|
||||
(drop.element!=element) &&
|
||||
((!drop._containers) ||
|
||||
this.isContained(element, drop)) &&
|
||||
((!drop.accept) ||
|
||||
(Element.classNames(element).detect(
|
||||
(Element.classNames(element).detect(
|
||||
function(v) { return drop.accept.include(v) } ) )) &&
|
||||
Position.within(drop.element, point[0], point[1]) );
|
||||
},
|
||||
|
|
@ -87,12 +87,12 @@ var Droppables = {
|
|||
show: function(point, element) {
|
||||
if(!this.drops.length) return;
|
||||
var drop, affected = [];
|
||||
|
||||
|
||||
this.drops.each( function(drop) {
|
||||
if(Droppables.isAffected(point, element, drop))
|
||||
affected.push(drop);
|
||||
});
|
||||
|
||||
|
||||
if(affected.length>0)
|
||||
drop = Droppables.findDeepestChild(affected);
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ var Droppables = {
|
|||
Position.within(drop.element, point[0], point[1]);
|
||||
if(drop.onHover)
|
||||
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||
|
||||
|
||||
if (drop != this.last_active) Droppables.activate(drop);
|
||||
}
|
||||
},
|
||||
|
|
@ -112,8 +112,8 @@ var Droppables = {
|
|||
|
||||
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
|
||||
if (this.last_active.onDrop) {
|
||||
this.last_active.onDrop(element, this.last_active.element, event);
|
||||
return true;
|
||||
this.last_active.onDrop(element, this.last_active.element, event);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -121,25 +121,25 @@ var Droppables = {
|
|||
if(this.last_active)
|
||||
this.deactivate(this.last_active);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var Draggables = {
|
||||
drags: [],
|
||||
observers: [],
|
||||
|
||||
|
||||
register: function(draggable) {
|
||||
if(this.drags.length == 0) {
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
|
||||
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
||||
|
||||
|
||||
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||
Event.observe(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
this.drags.push(draggable);
|
||||
},
|
||||
|
||||
|
||||
unregister: function(draggable) {
|
||||
this.drags = this.drags.reject(function(d) { return d==draggable });
|
||||
if(this.drags.length == 0) {
|
||||
|
|
@ -148,24 +148,24 @@ var Draggables = {
|
|||
Event.stopObserving(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
activate: function(draggable) {
|
||||
if(draggable.options.delay) {
|
||||
this._timeout = setTimeout(function() {
|
||||
Draggables._timeout = null;
|
||||
window.focus();
|
||||
Draggables.activeDraggable = draggable;
|
||||
}.bind(this), draggable.options.delay);
|
||||
if(draggable.options.delay) {
|
||||
this._timeout = setTimeout(function() {
|
||||
Draggables._timeout = null;
|
||||
window.focus();
|
||||
Draggables.activeDraggable = draggable;
|
||||
}.bind(this), draggable.options.delay);
|
||||
} else {
|
||||
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
|
||||
this.activeDraggable = draggable;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
deactivate: function() {
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
|
||||
updateDrag: function(event) {
|
||||
if(!this.activeDraggable) return;
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
|
|
@ -173,36 +173,36 @@ var Draggables = {
|
|||
// the same coordinates, prevent needless redrawing (moz bug?)
|
||||
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
|
||||
this._lastPointer = pointer;
|
||||
|
||||
|
||||
this.activeDraggable.updateDrag(event, pointer);
|
||||
},
|
||||
|
||||
|
||||
endDrag: function(event) {
|
||||
if(this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
if(this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
if(!this.activeDraggable) return;
|
||||
this._lastPointer = null;
|
||||
this.activeDraggable.endDrag(event);
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
|
||||
keyPress: function(event) {
|
||||
if(this.activeDraggable)
|
||||
this.activeDraggable.keyPress(event);
|
||||
},
|
||||
|
||||
|
||||
addObserver: function(observer) {
|
||||
this.observers.push(observer);
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
|
||||
removeObserver: function(element) { // element instead of observer fixes mem leaks
|
||||
this.observers = this.observers.reject( function(o) { return o.element==element });
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
|
||||
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
|
||||
if(this[eventName+'Count'] > 0)
|
||||
this.observers.each( function(o) {
|
||||
|
|
@ -210,7 +210,7 @@ var Draggables = {
|
|||
});
|
||||
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
|
||||
},
|
||||
|
||||
|
||||
_cacheObserverCallbacks: function() {
|
||||
['onStart','onEnd','onDrag'].each( function(eventName) {
|
||||
Draggables[eventName+'Count'] = Draggables.observers.select(
|
||||
|
|
@ -218,7 +218,7 @@ var Draggables = {
|
|||
).length;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
|
|
@ -234,12 +234,12 @@ var Draggable = Class.create({
|
|||
},
|
||||
endeffect: function(element) {
|
||||
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
|
||||
queue: {scope:'_draggable', position:'end'},
|
||||
afterFinish: function(){
|
||||
Draggable._dragging[element] = false
|
||||
afterFinish: function(){
|
||||
Draggable._dragging[element] = false
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false,
|
||||
|
|
@ -250,57 +250,57 @@ var Draggable = Class.create({
|
|||
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
delay: 0
|
||||
};
|
||||
|
||||
|
||||
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
|
||||
Object.extend(defaults, {
|
||||
starteffect: function(element) {
|
||||
element._opacity = Element.getOpacity(element);
|
||||
Draggable._dragging[element] = true;
|
||||
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
|
||||
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var options = Object.extend(defaults, arguments[1] || { });
|
||||
|
||||
this.element = $(element);
|
||||
|
||||
|
||||
if(options.handle && Object.isString(options.handle))
|
||||
this.handle = this.element.down('.'+options.handle, 0);
|
||||
|
||||
|
||||
if(!this.handle) this.handle = $(options.handle);
|
||||
if(!this.handle) this.handle = this.element;
|
||||
|
||||
|
||||
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
|
||||
options.scroll = $(options.scroll);
|
||||
this._isScrollChild = Element.childOf(this.element, options.scroll);
|
||||
}
|
||||
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
this.options = options;
|
||||
this.dragging = false;
|
||||
this.dragging = false;
|
||||
|
||||
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
|
||||
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
||||
|
||||
|
||||
Draggables.register(this);
|
||||
},
|
||||
|
||||
|
||||
destroy: function() {
|
||||
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
||||
Draggables.unregister(this);
|
||||
},
|
||||
|
||||
|
||||
currentDelta: function() {
|
||||
return([
|
||||
parseInt(Element.getStyle(this.element,'left') || '0'),
|
||||
parseInt(Element.getStyle(this.element,'top') || '0')]);
|
||||
},
|
||||
|
||||
|
||||
initDrag: function(event) {
|
||||
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
|
||||
Draggable._dragging[this.element]) return;
|
||||
if(Event.isLeftClick(event)) {
|
||||
if(Event.isLeftClick(event)) {
|
||||
// abort on form elements, fixes a Firefox issue
|
||||
var src = Event.element(event);
|
||||
if((tag_name = src.tagName.toUpperCase()) && (
|
||||
|
|
@ -309,34 +309,34 @@ var Draggable = Class.create({
|
|||
tag_name=='OPTION' ||
|
||||
tag_name=='BUTTON' ||
|
||||
tag_name=='TEXTAREA')) return;
|
||||
|
||||
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
|
||||
|
||||
|
||||
Draggables.activate(this);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
startDrag: function(event) {
|
||||
this.dragging = true;
|
||||
if(!this.delta)
|
||||
this.delta = this.currentDelta();
|
||||
|
||||
|
||||
if(this.options.zindex) {
|
||||
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
|
||||
this.element.style.zIndex = this.options.zindex;
|
||||
}
|
||||
|
||||
|
||||
if(this.options.ghosting) {
|
||||
this._clone = this.element.cloneNode(true);
|
||||
this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
|
||||
if (!this.element._originallyAbsolute)
|
||||
this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
|
||||
if (!this._originallyAbsolute)
|
||||
Position.absolutize(this.element);
|
||||
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||
}
|
||||
|
||||
|
||||
if(this.options.scroll) {
|
||||
if (this.options.scroll == window) {
|
||||
var where = this._getWindowScroll(this.options.scroll);
|
||||
|
|
@ -347,28 +347,28 @@ var Draggable = Class.create({
|
|||
this.originalScrollTop = this.options.scroll.scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Draggables.notify('onStart', this, event);
|
||||
|
||||
|
||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||
},
|
||||
|
||||
|
||||
updateDrag: function(event, pointer) {
|
||||
if(!this.dragging) this.startDrag(event);
|
||||
|
||||
|
||||
if(!this.options.quiet){
|
||||
Position.prepare();
|
||||
Droppables.show(pointer, this.element);
|
||||
}
|
||||
|
||||
|
||||
Draggables.notify('onDrag', this, event);
|
||||
|
||||
|
||||
this.draw(pointer);
|
||||
if(this.options.change) this.options.change(this);
|
||||
|
||||
|
||||
if(this.options.scroll) {
|
||||
this.stopScrolling();
|
||||
|
||||
|
||||
var p;
|
||||
if (this.options.scroll == window) {
|
||||
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
|
||||
|
|
@ -386,16 +386,16 @@ var Draggable = Class.create({
|
|||
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
|
||||
this.startScrolling(speed);
|
||||
}
|
||||
|
||||
|
||||
// fix AppleWebKit rendering
|
||||
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
|
||||
|
||||
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
|
||||
finishDrag: function(event, success) {
|
||||
this.dragging = false;
|
||||
|
||||
|
||||
if(this.options.quiet){
|
||||
Position.prepare();
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
|
|
@ -403,24 +403,24 @@ var Draggable = Class.create({
|
|||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
if (!this.element._originallyAbsolute)
|
||||
if (!this._originallyAbsolute)
|
||||
Position.relativize(this.element);
|
||||
delete this.element._originallyAbsolute;
|
||||
delete this._originallyAbsolute;
|
||||
Element.remove(this._clone);
|
||||
this._clone = null;
|
||||
}
|
||||
|
||||
var dropped = false;
|
||||
if(success) {
|
||||
dropped = Droppables.fire(event, this.element);
|
||||
if (!dropped) dropped = false;
|
||||
var dropped = false;
|
||||
if(success) {
|
||||
dropped = Droppables.fire(event, this.element);
|
||||
if (!dropped) dropped = false;
|
||||
}
|
||||
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
|
||||
Draggables.notify('onEnd', this, event);
|
||||
|
||||
var revert = this.options.revert;
|
||||
if(revert && Object.isFunction(revert)) revert = revert(this.element);
|
||||
|
||||
|
||||
var d = this.currentDelta();
|
||||
if(revert && this.options.reverteffect) {
|
||||
if (dropped == 0 || revert != 'failure')
|
||||
|
|
@ -433,67 +433,67 @@ var Draggable = Class.create({
|
|||
if(this.options.zindex)
|
||||
this.element.style.zIndex = this.originalZ;
|
||||
|
||||
if(this.options.endeffect)
|
||||
if(this.options.endeffect)
|
||||
this.options.endeffect(this.element);
|
||||
|
||||
|
||||
Draggables.deactivate(this);
|
||||
Droppables.reset();
|
||||
},
|
||||
|
||||
|
||||
keyPress: function(event) {
|
||||
if(event.keyCode!=Event.KEY_ESC) return;
|
||||
this.finishDrag(event, false);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
|
||||
endDrag: function(event) {
|
||||
if(!this.dragging) return;
|
||||
this.stopScrolling();
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
|
||||
draw: function(point) {
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
if(this.options.ghosting) {
|
||||
var r = Position.realOffset(this.element);
|
||||
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
|
||||
}
|
||||
|
||||
|
||||
var d = this.currentDelta();
|
||||
pos[0] -= d[0]; pos[1] -= d[1];
|
||||
|
||||
|
||||
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
|
||||
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||
}
|
||||
|
||||
var p = [0,1].map(function(i){
|
||||
return (point[i]-pos[i]-this.offset[i])
|
||||
|
||||
var p = [0,1].map(function(i){
|
||||
return (point[i]-pos[i]-this.offset[i])
|
||||
}.bind(this));
|
||||
|
||||
|
||||
if(this.options.snap) {
|
||||
if(Object.isFunction(this.options.snap)) {
|
||||
p = this.options.snap(p[0],p[1],this);
|
||||
} else {
|
||||
if(Object.isArray(this.options.snap)) {
|
||||
p = p.map( function(v, i) {
|
||||
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
|
||||
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
|
||||
} else {
|
||||
p = p.map( function(v) {
|
||||
return (v/this.options.snap).round()*this.options.snap }.bind(this))
|
||||
return (v/this.options.snap).round()*this.options.snap }.bind(this));
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
var style = this.element.style;
|
||||
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
|
||||
style.left = p[0] + "px";
|
||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||
style.top = p[1] + "px";
|
||||
|
||||
|
||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||
},
|
||||
|
||||
|
||||
stopScrolling: function() {
|
||||
if(this.scrollInterval) {
|
||||
clearInterval(this.scrollInterval);
|
||||
|
|
@ -501,14 +501,14 @@ var Draggable = Class.create({
|
|||
Draggables._lastScrollPointer = null;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
startScrolling: function(speed) {
|
||||
if(!(speed[0] || speed[1])) return;
|
||||
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
|
||||
this.lastScrolled = new Date();
|
||||
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
|
||||
},
|
||||
|
||||
|
||||
scroll: function() {
|
||||
var current = new Date();
|
||||
var delta = current - this.lastScrolled;
|
||||
|
|
@ -524,7 +524,7 @@ var Draggable = Class.create({
|
|||
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
|
||||
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
|
||||
}
|
||||
|
||||
|
||||
Position.prepare();
|
||||
Droppables.show(Draggables._lastPointer, this.element);
|
||||
Draggables.notify('onDrag', this);
|
||||
|
|
@ -538,10 +538,10 @@ var Draggable = Class.create({
|
|||
Draggables._lastScrollPointer[1] = 0;
|
||||
this.draw(Draggables._lastScrollPointer);
|
||||
}
|
||||
|
||||
|
||||
if(this.options.change) this.options.change(this);
|
||||
},
|
||||
|
||||
|
||||
_getWindowScroll: function(w) {
|
||||
var T, L, W, H;
|
||||
with (w.document) {
|
||||
|
|
@ -560,7 +560,7 @@ var Draggable = Class.create({
|
|||
H = documentElement.clientHeight;
|
||||
} else {
|
||||
W = body.offsetWidth;
|
||||
H = body.offsetHeight
|
||||
H = body.offsetHeight;
|
||||
}
|
||||
}
|
||||
return { top: T, left: L, width: W, height: H };
|
||||
|
|
@ -577,11 +577,11 @@ var SortableObserver = Class.create({
|
|||
this.observer = observer;
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
|
||||
onStart: function() {
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
|
||||
onEnd: function() {
|
||||
Sortable.unmark();
|
||||
if(this.lastValue != Sortable.serialize(this.element))
|
||||
|
|
@ -591,11 +591,11 @@ var SortableObserver = Class.create({
|
|||
|
||||
var Sortable = {
|
||||
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
|
||||
|
||||
|
||||
sortables: { },
|
||||
|
||||
|
||||
_findRootElement: function(element) {
|
||||
while (element.tagName.toUpperCase() != "BODY") {
|
||||
while (element.tagName.toUpperCase() != "BODY") {
|
||||
if(element.id && Sortable.sortables[element.id]) return element;
|
||||
element = element.parentNode;
|
||||
}
|
||||
|
|
@ -606,22 +606,23 @@ var Sortable = {
|
|||
if(!element) return;
|
||||
return Sortable.sortables[element.id];
|
||||
},
|
||||
|
||||
|
||||
destroy: function(element){
|
||||
var s = Sortable.options(element);
|
||||
|
||||
element = $(element);
|
||||
var s = Sortable.sortables[element.id];
|
||||
|
||||
if(s) {
|
||||
Draggables.removeObserver(s.element);
|
||||
s.droppables.each(function(d){ Droppables.remove(d) });
|
||||
s.draggables.invoke('destroy');
|
||||
|
||||
|
||||
delete Sortable.sortables[s.element.id];
|
||||
}
|
||||
},
|
||||
|
||||
create: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
var options = Object.extend({
|
||||
element: element,
|
||||
tag: 'li', // assumes li children, override with tag: 'tagname'
|
||||
dropOnEmpty: false,
|
||||
|
|
@ -635,17 +636,17 @@ var Sortable = {
|
|||
delay: 0,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
quiet: false,
|
||||
quiet: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
format: this.SERIALIZE_RULE,
|
||||
|
||||
// these take arrays of elements or ids and can be
|
||||
|
||||
// these take arrays of elements or ids and can be
|
||||
// used for better initialization performance
|
||||
elements: false,
|
||||
handles: false,
|
||||
|
||||
|
||||
onChange: Prototype.emptyFunction,
|
||||
onUpdate: Prototype.emptyFunction
|
||||
}, arguments[1] || { });
|
||||
|
|
@ -682,24 +683,24 @@ var Sortable = {
|
|||
if(options.zindex)
|
||||
options_for_draggable.zindex = options.zindex;
|
||||
|
||||
// build options for the droppables
|
||||
// build options for the droppables
|
||||
var options_for_droppable = {
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
tree: options.tree,
|
||||
hoverclass: options.hoverclass,
|
||||
onHover: Sortable.onHover
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var options_for_tree = {
|
||||
onHover: Sortable.onEmptyHover,
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
hoverclass: options.hoverclass
|
||||
}
|
||||
};
|
||||
|
||||
// fix for gecko engine
|
||||
Element.cleanWhitespace(element);
|
||||
Element.cleanWhitespace(element);
|
||||
|
||||
options.draggables = [];
|
||||
options.droppables = [];
|
||||
|
|
@ -712,14 +713,14 @@ var Sortable = {
|
|||
|
||||
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
|
||||
var handle = options.handles ? $(options.handles[i]) :
|
||||
(options.handle ? $(e).select('.' + options.handle)[0] : e);
|
||||
(options.handle ? $(e).select('.' + options.handle)[0] : e);
|
||||
options.draggables.push(
|
||||
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
|
||||
Droppables.add(e, options_for_droppable);
|
||||
if(options.tree) e.treeNode = element;
|
||||
options.droppables.push(e);
|
||||
options.droppables.push(e);
|
||||
});
|
||||
|
||||
|
||||
if(options.tree) {
|
||||
(Sortable.findTreeElements(element, options) || []).each( function(e) {
|
||||
Droppables.add(e, options_for_tree);
|
||||
|
|
@ -741,7 +742,7 @@ var Sortable = {
|
|||
return Element.findChildren(
|
||||
element, options.only, options.tree ? true : false, options.tag);
|
||||
},
|
||||
|
||||
|
||||
findTreeElements: function(element, options) {
|
||||
return Element.findChildren(
|
||||
element, options.only, options.tree ? true : false, options.treeTag);
|
||||
|
|
@ -758,7 +759,7 @@ var Sortable = {
|
|||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, dropon);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
|
|
@ -769,26 +770,26 @@ var Sortable = {
|
|||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, nextElement);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onEmptyHover: function(element, dropon, overlap) {
|
||||
var oldParentNode = element.parentNode;
|
||||
var droponOptions = Sortable.options(dropon);
|
||||
|
||||
|
||||
if(!Element.isParent(dropon, element)) {
|
||||
var index;
|
||||
|
||||
|
||||
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
|
||||
var child = null;
|
||||
|
||||
|
||||
if(children) {
|
||||
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
|
||||
|
||||
|
||||
for (index = 0; index < children.length; index += 1) {
|
||||
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
|
||||
offset -= Element.offsetSize (children[index], droponOptions.overlap);
|
||||
|
|
@ -801,9 +802,9 @@ var Sortable = {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dropon.insertBefore(element, child);
|
||||
|
||||
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
droponOptions.onChange(element);
|
||||
}
|
||||
|
|
@ -816,34 +817,34 @@ var Sortable = {
|
|||
mark: function(dropon, position) {
|
||||
// mark on ghosting only
|
||||
var sortable = Sortable.options(dropon.parentNode);
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker =
|
||||
Sortable._marker =
|
||||
($('dropmarker') || Element.extend(document.createElement('DIV'))).
|
||||
hide().addClassName('dropmarker').setStyle({position:'absolute'});
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
|
||||
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
|
||||
else
|
||||
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
|
||||
|
||||
|
||||
Sortable._marker.show();
|
||||
},
|
||||
|
||||
|
||||
_tree: function(element, options, parent) {
|
||||
var children = Sortable.findElements(element, options) || [];
|
||||
|
||||
|
||||
for (var i = 0; i < children.length; ++i) {
|
||||
var match = children[i].id.match(options.format);
|
||||
|
||||
if (!match) continue;
|
||||
|
||||
|
||||
var child = {
|
||||
id: encodeURIComponent(match ? match[1] : null),
|
||||
element: element,
|
||||
|
|
@ -851,16 +852,16 @@ var Sortable = {
|
|||
children: [],
|
||||
position: parent.children.length,
|
||||
container: $(children[i]).down(options.treeTag)
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* Get the element containing the children and recurse over it */
|
||||
if (child.container)
|
||||
this._tree(child.container, options, child)
|
||||
|
||||
this._tree(child.container, options, child);
|
||||
|
||||
parent.children.push (child);
|
||||
}
|
||||
|
||||
return parent;
|
||||
return parent;
|
||||
},
|
||||
|
||||
tree: function(element) {
|
||||
|
|
@ -873,15 +874,15 @@ var Sortable = {
|
|||
name: element.id,
|
||||
format: sortableOptions.format
|
||||
}, arguments[1] || { });
|
||||
|
||||
|
||||
var root = {
|
||||
id: null,
|
||||
parent: null,
|
||||
children: [],
|
||||
container: element,
|
||||
position: 0
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return Sortable._tree(element, options, root);
|
||||
},
|
||||
|
||||
|
|
@ -897,7 +898,7 @@ var Sortable = {
|
|||
sequence: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[1] || { });
|
||||
|
||||
|
||||
return $(this.findElements(element, options) || []).map( function(item) {
|
||||
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
|
||||
});
|
||||
|
|
@ -906,14 +907,14 @@ var Sortable = {
|
|||
setSequence: function(element, new_sequence) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[2] || { });
|
||||
|
||||
|
||||
var nodeMap = { };
|
||||
this.findElements(element, options).each( function(n) {
|
||||
if (n.id.match(options.format))
|
||||
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
|
||||
n.parentNode.removeChild(n);
|
||||
});
|
||||
|
||||
|
||||
new_sequence.each(function(ident) {
|
||||
var n = nodeMap[ident];
|
||||
if (n) {
|
||||
|
|
@ -922,16 +923,16 @@ var Sortable = {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
serialize: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend(Sortable.options(element), arguments[1] || { });
|
||||
var name = encodeURIComponent(
|
||||
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
|
||||
|
||||
|
||||
if (options.tree) {
|
||||
return Sortable.tree(element, arguments[1]).children.map( function (item) {
|
||||
return [name + Sortable._constructIndex(item) + "[id]=" +
|
||||
return [name + Sortable._constructIndex(item) + "[id]=" +
|
||||
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
|
||||
}).flatten().join('&');
|
||||
} else {
|
||||
|
|
@ -940,16 +941,16 @@ var Sortable = {
|
|||
}).join('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true if child is contained within element
|
||||
Element.isParent = function(child, element) {
|
||||
if (!child.parentNode || child == element) return false;
|
||||
if (child.parentNode == element) return true;
|
||||
return Element.isParent(child.parentNode, element);
|
||||
}
|
||||
};
|
||||
|
||||
Element.findChildren = function(element, only, recursive, tagName) {
|
||||
Element.findChildren = function(element, only, recursive, tagName) {
|
||||
if(!element.hasChildNodes()) return null;
|
||||
tagName = tagName.toUpperCase();
|
||||
if(only) only = [only].flatten();
|
||||
|
|
@ -965,8 +966,8 @@ Element.findChildren = function(element, only, recursive, tagName) {
|
|||
});
|
||||
|
||||
return (elements.length>0 ? elements.flatten() : []);
|
||||
}
|
||||
};
|
||||
|
||||
Element.offsetSize = function (element, type) {
|
||||
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
|
||||
}
|
||||
};
|
||||
338
public/javascripts/effects.js
vendored
338
public/javascripts/effects.js
vendored
|
|
@ -3,46 +3,46 @@
|
|||
// Justin Palmer (http://encytemedia.com/)
|
||||
// Mark Pilgrim (http://diveintomark.org/)
|
||||
// Martin Bialasinki
|
||||
//
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// converts rgb() and #xxx to #xxxxxx format,
|
||||
// returns self (or first argument) if not convertable
|
||||
String.prototype.parseColor = function() {
|
||||
// converts rgb() and #xxx to #xxxxxx format,
|
||||
// returns self (or first argument) if not convertable
|
||||
String.prototype.parseColor = function() {
|
||||
var color = '#';
|
||||
if (this.slice(0,4) == 'rgb(') {
|
||||
var cols = this.slice(4,this.length-1).split(',');
|
||||
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
|
||||
} else {
|
||||
if (this.slice(0,1) == '#') {
|
||||
if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
|
||||
if (this.length==7) color = this.toLowerCase();
|
||||
}
|
||||
}
|
||||
return (color.length==7 ? color : (arguments[0] || this));
|
||||
if (this.slice(0,4) == 'rgb(') {
|
||||
var cols = this.slice(4,this.length-1).split(',');
|
||||
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
|
||||
} else {
|
||||
if (this.slice(0,1) == '#') {
|
||||
if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
|
||||
if (this.length==7) color = this.toLowerCase();
|
||||
}
|
||||
}
|
||||
return (color.length==7 ? color : (arguments[0] || this));
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
Element.collectTextNodes = function(element) {
|
||||
Element.collectTextNodes = function(element) {
|
||||
return $A($(element).childNodes).collect( function(node) {
|
||||
return (node.nodeType==3 ? node.nodeValue :
|
||||
return (node.nodeType==3 ? node.nodeValue :
|
||||
(node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
|
||||
}).flatten().join('');
|
||||
};
|
||||
|
||||
Element.collectTextNodesIgnoreClass = function(element, className) {
|
||||
Element.collectTextNodesIgnoreClass = function(element, className) {
|
||||
return $A($(element).childNodes).collect( function(node) {
|
||||
return (node.nodeType==3 ? node.nodeValue :
|
||||
((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
|
||||
return (node.nodeType==3 ? node.nodeValue :
|
||||
((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
|
||||
Element.collectTextNodesIgnoreClass(node, className) : ''));
|
||||
}).flatten().join('');
|
||||
};
|
||||
|
||||
Element.setContentZoom = function(element, percent) {
|
||||
element = $(element);
|
||||
element.setStyle({fontSize: (percent/100) + 'em'});
|
||||
element = $(element);
|
||||
element.setStyle({fontSize: (percent/100) + 'em'});
|
||||
if (Prototype.Browser.WebKit) window.scrollBy(0,0);
|
||||
return element;
|
||||
};
|
||||
|
|
@ -70,28 +70,23 @@ var Effect = {
|
|||
Transitions: {
|
||||
linear: Prototype.K,
|
||||
sinoidal: function(pos) {
|
||||
return (-Math.cos(pos*Math.PI)/2) + 0.5;
|
||||
return (-Math.cos(pos*Math.PI)/2) + .5;
|
||||
},
|
||||
reverse: function(pos) {
|
||||
return 1-pos;
|
||||
},
|
||||
flicker: function(pos) {
|
||||
var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
|
||||
var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
|
||||
return pos > 1 ? 1 : pos;
|
||||
},
|
||||
wobble: function(pos) {
|
||||
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
|
||||
return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
|
||||
},
|
||||
pulse: function(pos, pulses) {
|
||||
pulses = pulses || 5;
|
||||
return (
|
||||
((pos % (1/pulses)) * pulses).round() == 0 ?
|
||||
((pos * pulses * 2) - (pos * pulses * 2).floor()) :
|
||||
1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
|
||||
);
|
||||
pulse: function(pos, pulses) {
|
||||
return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
|
||||
},
|
||||
spring: function(pos) {
|
||||
return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
|
||||
spring: function(pos) {
|
||||
return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
|
||||
},
|
||||
none: function(pos) {
|
||||
return 0;
|
||||
|
|
@ -112,14 +107,14 @@ var Effect = {
|
|||
tagifyText: function(element) {
|
||||
var tagifyStyle = 'position:relative';
|
||||
if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
|
||||
|
||||
|
||||
element = $(element);
|
||||
$A(element.childNodes).each( function(child) {
|
||||
if (child.nodeType==3) {
|
||||
child.nodeValue.toArray().each( function(character) {
|
||||
element.insertBefore(
|
||||
new Element('span', {style: tagifyStyle}).update(
|
||||
character == ' ' ? String.fromCharCode(160) : character),
|
||||
character == ' ' ? String.fromCharCode(160) : character),
|
||||
child);
|
||||
});
|
||||
Element.remove(child);
|
||||
|
|
@ -128,13 +123,13 @@ var Effect = {
|
|||
},
|
||||
multiple: function(element, effect) {
|
||||
var elements;
|
||||
if (((typeof element == 'object') ||
|
||||
Object.isFunction(element)) &&
|
||||
if (((typeof element == 'object') ||
|
||||
Object.isFunction(element)) &&
|
||||
(element.length))
|
||||
elements = element;
|
||||
else
|
||||
elements = $(element).childNodes;
|
||||
|
||||
|
||||
var options = Object.extend({
|
||||
speed: 0.1,
|
||||
delay: 0.0
|
||||
|
|
@ -156,7 +151,7 @@ var Effect = {
|
|||
var options = Object.extend({
|
||||
queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
|
||||
}, arguments[2] || { });
|
||||
Effect[element.visible() ?
|
||||
Effect[element.visible() ?
|
||||
Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
|
||||
}
|
||||
};
|
||||
|
|
@ -168,20 +163,20 @@ Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
|
|||
Effect.ScopedQueue = Class.create(Enumerable, {
|
||||
initialize: function() {
|
||||
this.effects = [];
|
||||
this.interval = null;
|
||||
this.interval = null;
|
||||
},
|
||||
_each: function(iterator) {
|
||||
this.effects._each(iterator);
|
||||
},
|
||||
add: function(effect) {
|
||||
var timestamp = new Date().getTime();
|
||||
|
||||
var position = Object.isString(effect.options.queue) ?
|
||||
|
||||
var position = Object.isString(effect.options.queue) ?
|
||||
effect.options.queue : effect.options.queue.position;
|
||||
|
||||
|
||||
switch(position) {
|
||||
case 'front':
|
||||
// move unstarted effects after this effect
|
||||
// move unstarted effects after this effect
|
||||
this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
|
||||
e.startOn += effect.finishOn;
|
||||
e.finishOn += effect.finishOn;
|
||||
|
|
@ -195,13 +190,13 @@ Effect.ScopedQueue = Class.create(Enumerable, {
|
|||
timestamp = this.effects.pluck('finishOn').max() || timestamp;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
effect.startOn += timestamp;
|
||||
effect.finishOn += timestamp;
|
||||
|
||||
if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
|
||||
this.effects.push(effect);
|
||||
|
||||
|
||||
if (!this.interval)
|
||||
this.interval = setInterval(this.loop.bind(this), 15);
|
||||
},
|
||||
|
|
@ -214,7 +209,7 @@ Effect.ScopedQueue = Class.create(Enumerable, {
|
|||
},
|
||||
loop: function() {
|
||||
var timePos = new Date().getTime();
|
||||
for(var i=0, len=this.effects.length;i<len;i++)
|
||||
for(var i=0, len=this.effects.length;i<len;i++)
|
||||
this.effects[i] && this.effects[i].loop(timePos);
|
||||
}
|
||||
});
|
||||
|
|
@ -223,7 +218,7 @@ Effect.Queues = {
|
|||
instances: $H(),
|
||||
get: function(queueName) {
|
||||
if (!Object.isString(queueName)) return queueName;
|
||||
|
||||
|
||||
return this.instances.get(queueName) ||
|
||||
this.instances.set(queueName, new Effect.ScopedQueue());
|
||||
}
|
||||
|
|
@ -248,23 +243,35 @@ Effect.Base = Class.create({
|
|||
this.fromToDelta = this.options.to-this.options.from;
|
||||
this.totalTime = this.finishOn-this.startOn;
|
||||
this.totalFrames = this.options.fps*this.options.duration;
|
||||
|
||||
eval('this.render = function(pos){ '+
|
||||
'if (this.state=="idle"){this.state="running";'+
|
||||
codeForEvent(this.options,'beforeSetup')+
|
||||
(this.setup ? 'this.setup();':'')+
|
||||
codeForEvent(this.options,'afterSetup')+
|
||||
'};if (this.state=="running"){'+
|
||||
'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
|
||||
'this.position=pos;'+
|
||||
codeForEvent(this.options,'beforeUpdate')+
|
||||
(this.update ? 'this.update(pos);':'')+
|
||||
codeForEvent(this.options,'afterUpdate')+
|
||||
'}}');
|
||||
|
||||
|
||||
this.render = (function() {
|
||||
function dispatch(effect, eventName) {
|
||||
if (effect.options[eventName + 'Internal'])
|
||||
effect.options[eventName + 'Internal'](effect);
|
||||
if (effect.options[eventName])
|
||||
effect.options[eventName](effect);
|
||||
}
|
||||
|
||||
return function(pos) {
|
||||
if (this.state === "idle") {
|
||||
this.state = "running";
|
||||
dispatch(this, 'beforeSetup');
|
||||
if (this.setup) this.setup();
|
||||
dispatch(this, 'afterSetup');
|
||||
}
|
||||
if (this.state === "running") {
|
||||
pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
|
||||
this.position = pos;
|
||||
dispatch(this, 'beforeUpdate');
|
||||
if (this.update) this.update(pos);
|
||||
dispatch(this, 'afterUpdate');
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
this.event('beforeStart');
|
||||
if (!this.options.sync)
|
||||
Effect.Queues.get(Object.isString(this.options.queue) ?
|
||||
Effect.Queues.get(Object.isString(this.options.queue) ?
|
||||
'global' : this.options.queue.scope).add(this);
|
||||
},
|
||||
loop: function(timePos) {
|
||||
|
|
@ -273,9 +280,9 @@ Effect.Base = Class.create({
|
|||
this.render(1.0);
|
||||
this.cancel();
|
||||
this.event('beforeFinish');
|
||||
if (this.finish) this.finish();
|
||||
if (this.finish) this.finish();
|
||||
this.event('afterFinish');
|
||||
return;
|
||||
return;
|
||||
}
|
||||
var pos = (timePos - this.startOn) / this.totalTime,
|
||||
frame = (pos * this.totalFrames).round();
|
||||
|
|
@ -287,7 +294,7 @@ Effect.Base = Class.create({
|
|||
},
|
||||
cancel: function() {
|
||||
if (!this.options.sync)
|
||||
Effect.Queues.get(Object.isString(this.options.queue) ?
|
||||
Effect.Queues.get(Object.isString(this.options.queue) ?
|
||||
'global' : this.options.queue.scope).remove(this);
|
||||
this.state = 'finished';
|
||||
},
|
||||
|
|
@ -325,10 +332,10 @@ Effect.Parallel = Class.create(Effect.Base, {
|
|||
Effect.Tween = Class.create(Effect.Base, {
|
||||
initialize: function(object, from, to) {
|
||||
object = Object.isString(object) ? $(object) : object;
|
||||
var args = $A(arguments), method = args.last(),
|
||||
var args = $A(arguments), method = args.last(),
|
||||
options = args.length == 5 ? args[3] : null;
|
||||
this.method = Object.isFunction(method) ? method.bind(object) :
|
||||
Object.isFunction(object[method]) ? object[method].bind(object) :
|
||||
Object.isFunction(object[method]) ? object[method].bind(object) :
|
||||
function(value) { object[method] = value };
|
||||
this.start(Object.extend({ from: from, to: to }, options || { }));
|
||||
},
|
||||
|
|
@ -392,7 +399,7 @@ Effect.Move = Class.create(Effect.Base, {
|
|||
|
||||
// for backwards compatibility
|
||||
Effect.MoveBy = function(element, toTop, toLeft) {
|
||||
return new Effect.Move(element,
|
||||
return new Effect.Move(element,
|
||||
Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
|
||||
};
|
||||
|
||||
|
|
@ -414,15 +421,15 @@ Effect.Scale = Class.create(Effect.Base, {
|
|||
setup: function() {
|
||||
this.restoreAfterFinish = this.options.restoreAfterFinish || false;
|
||||
this.elementPositioning = this.element.getStyle('position');
|
||||
|
||||
|
||||
this.originalStyle = { };
|
||||
['top','left','width','height','fontSize'].each( function(k) {
|
||||
this.originalStyle[k] = this.element.style[k];
|
||||
}.bind(this));
|
||||
|
||||
|
||||
this.originalTop = this.element.offsetTop;
|
||||
this.originalLeft = this.element.offsetLeft;
|
||||
|
||||
|
||||
var fontSize = this.element.getStyle('font-size') || '100%';
|
||||
['em','px','%','pt'].each( function(fontSizeType) {
|
||||
if (fontSize.indexOf(fontSizeType)>0) {
|
||||
|
|
@ -430,9 +437,9 @@ Effect.Scale = Class.create(Effect.Base, {
|
|||
this.fontSizeType = fontSizeType;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
|
||||
this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
|
||||
|
||||
|
||||
this.dims = null;
|
||||
if (this.options.scaleMode=='box')
|
||||
this.dims = [this.element.offsetHeight, this.element.offsetWidth];
|
||||
|
|
@ -507,17 +514,16 @@ Effect.Highlight = Class.create(Effect.Base, {
|
|||
|
||||
Effect.ScrollTo = function(element) {
|
||||
var options = arguments[1] || { },
|
||||
scrollOffsets = document.viewport.getScrollOffsets(),
|
||||
elementOffsets = $(element).cumulativeOffset(),
|
||||
max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
|
||||
scrollOffsets = document.viewport.getScrollOffsets(),
|
||||
elementOffsets = $(element).cumulativeOffset();
|
||||
|
||||
if (options.offset) elementOffsets[1] += options.offset;
|
||||
|
||||
return new Effect.Tween(null,
|
||||
scrollOffsets.top,
|
||||
elementOffsets[1] > max ? max : elementOffsets[1],
|
||||
elementOffsets[1],
|
||||
options,
|
||||
function(p){ scrollTo(scrollOffsets.left, p.round()) }
|
||||
function(p){ scrollTo(scrollOffsets.left, p.round()); }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -529,9 +535,9 @@ Effect.Fade = function(element) {
|
|||
var options = Object.extend({
|
||||
from: element.getOpacity() || 1.0,
|
||||
to: 0.0,
|
||||
afterFinishInternal: function(effect) {
|
||||
afterFinishInternal: function(effect) {
|
||||
if (effect.options.to!=0) return;
|
||||
effect.element.hide().setStyle({opacity: oldOpacity});
|
||||
effect.element.hide().setStyle({opacity: oldOpacity});
|
||||
}
|
||||
}, arguments[1] || { });
|
||||
return new Effect.Opacity(element,options);
|
||||
|
|
@ -547,15 +553,15 @@ Effect.Appear = function(element) {
|
|||
effect.element.forceRerendering();
|
||||
},
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.setOpacity(effect.options.from).show();
|
||||
effect.element.setOpacity(effect.options.from).show();
|
||||
}}, arguments[1] || { });
|
||||
return new Effect.Opacity(element,options);
|
||||
};
|
||||
|
||||
Effect.Puff = function(element) {
|
||||
element = $(element);
|
||||
var oldStyle = {
|
||||
opacity: element.getInlineOpacity(),
|
||||
var oldStyle = {
|
||||
opacity: element.getInlineOpacity(),
|
||||
position: element.getStyle('position'),
|
||||
top: element.style.top,
|
||||
left: element.style.left,
|
||||
|
|
@ -563,12 +569,12 @@ Effect.Puff = function(element) {
|
|||
height: element.style.height
|
||||
};
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Scale(element, 200,
|
||||
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
|
||||
Object.extend({ duration: 1.0,
|
||||
[ new Effect.Scale(element, 200,
|
||||
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
|
||||
Object.extend({ duration: 1.0,
|
||||
beforeSetupInternal: function(effect) {
|
||||
Position.absolutize(effect.effects[0].element)
|
||||
Position.absolutize(effect.effects[0].element);
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide().setStyle(oldStyle); }
|
||||
|
|
@ -580,12 +586,12 @@ Effect.BlindUp = function(element) {
|
|||
element = $(element);
|
||||
element.makeClipping();
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
restoreAfterFinish: true,
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide().undoClipping();
|
||||
}
|
||||
}
|
||||
}, arguments[1] || { })
|
||||
);
|
||||
};
|
||||
|
|
@ -593,15 +599,15 @@ Effect.BlindUp = function(element) {
|
|||
Effect.BlindDown = function(element) {
|
||||
element = $(element);
|
||||
var elementDimensions = element.getDimensions();
|
||||
return new Effect.Scale(element, 100, Object.extend({
|
||||
scaleContent: false,
|
||||
return new Effect.Scale(element, 100, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
effect.element.makeClipping().setStyle({height: '0px'}).show();
|
||||
},
|
||||
effect.element.makeClipping().setStyle({height: '0px'}).show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping();
|
||||
}
|
||||
|
|
@ -616,16 +622,16 @@ Effect.SwitchOff = function(element) {
|
|||
from: 0,
|
||||
transition: Effect.Transitions.flicker,
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Scale(effect.element, 1, {
|
||||
new Effect.Scale(effect.element, 1, {
|
||||
duration: 0.3, scaleFromCenter: true,
|
||||
scaleX: false, scaleContent: false, restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.makePositioned().makeClipping();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}, arguments[1] || { }));
|
||||
};
|
||||
|
|
@ -637,16 +643,16 @@ Effect.DropOut = function(element) {
|
|||
left: element.getStyle('left'),
|
||||
opacity: element.getInlineOpacity() };
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Move(element, {x: 0, y: 100, sync: true }),
|
||||
[ new Effect.Move(element, {x: 0, y: 100, sync: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
|
||||
Object.extend(
|
||||
{ duration: 0.5,
|
||||
beforeSetup: function(effect) {
|
||||
effect.effects[0].element.makePositioned();
|
||||
effect.effects[0].element.makePositioned();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
|
||||
}
|
||||
}
|
||||
}, arguments[1] || { }));
|
||||
};
|
||||
|
||||
|
|
@ -674,7 +680,7 @@ Effect.Shake = function(element) {
|
|||
new Effect.Move(effect.element,
|
||||
{ x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
|
||||
effect.element.undoPositioned().setStyle(oldStyle);
|
||||
}}) }}) }}) }}) }}) }});
|
||||
}}); }}); }}); }}); }}); }});
|
||||
};
|
||||
|
||||
Effect.SlideDown = function(element) {
|
||||
|
|
@ -682,9 +688,9 @@ Effect.SlideDown = function(element) {
|
|||
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
|
||||
var oldInnerBottom = element.down().getStyle('bottom');
|
||||
var elementDimensions = element.getDimensions();
|
||||
return new Effect.Scale(element, 100, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
return new Effect.Scale(element, 100, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: window.opera ? 0 : 1,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
|
|
@ -692,11 +698,11 @@ Effect.SlideDown = function(element) {
|
|||
effect.element.makePositioned();
|
||||
effect.element.down().makePositioned();
|
||||
if (window.opera) effect.element.setStyle({top: ''});
|
||||
effect.element.makeClipping().setStyle({height: '0px'}).show();
|
||||
effect.element.makeClipping().setStyle({height: '0px'}).show();
|
||||
},
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.down().setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' });
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' });
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping().undoPositioned();
|
||||
|
|
@ -710,8 +716,8 @@ Effect.SlideUp = function(element) {
|
|||
var oldInnerBottom = element.down().getStyle('bottom');
|
||||
var elementDimensions = element.getDimensions();
|
||||
return new Effect.Scale(element, window.opera ? 0 : 1,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleMode: 'box',
|
||||
scaleFrom: 100,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
|
|
@ -721,7 +727,7 @@ Effect.SlideUp = function(element) {
|
|||
effect.element.down().makePositioned();
|
||||
if (window.opera) effect.element.setStyle({top: ''});
|
||||
effect.element.makeClipping().show();
|
||||
},
|
||||
},
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.down().setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' });
|
||||
|
|
@ -734,15 +740,15 @@ Effect.SlideUp = function(element) {
|
|||
);
|
||||
};
|
||||
|
||||
// Bug in opera makes the TD containing this element expand for a instance after finish
|
||||
// Bug in opera makes the TD containing this element expand for a instance after finish
|
||||
Effect.Squish = function(element) {
|
||||
return new Effect.Scale(element, window.opera ? 1 : 0, {
|
||||
return new Effect.Scale(element, window.opera ? 1 : 0, {
|
||||
restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.makeClipping();
|
||||
},
|
||||
effect.element.makeClipping();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide().undoClipping();
|
||||
effect.element.hide().undoClipping();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -762,13 +768,13 @@ Effect.Grow = function(element) {
|
|||
width: element.style.width,
|
||||
opacity: element.getInlineOpacity() };
|
||||
|
||||
var dims = element.getDimensions();
|
||||
var dims = element.getDimensions();
|
||||
var initialMoveX, initialMoveY;
|
||||
var moveX, moveY;
|
||||
|
||||
|
||||
switch (options.direction) {
|
||||
case 'top-left':
|
||||
initialMoveX = initialMoveY = moveX = moveY = 0;
|
||||
initialMoveX = initialMoveY = moveX = moveY = 0;
|
||||
break;
|
||||
case 'top-right':
|
||||
initialMoveX = dims.width;
|
||||
|
|
@ -793,11 +799,11 @@ Effect.Grow = function(element) {
|
|||
moveY = -dims.height / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return new Effect.Move(element, {
|
||||
x: initialMoveX,
|
||||
y: initialMoveY,
|
||||
duration: 0.01,
|
||||
duration: 0.01,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.hide().makeClipping().makePositioned();
|
||||
},
|
||||
|
|
@ -806,17 +812,17 @@ Effect.Grow = function(element) {
|
|||
[ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
|
||||
new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
|
||||
new Effect.Scale(effect.element, 100, {
|
||||
scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
|
||||
scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
|
||||
sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
|
||||
], Object.extend({
|
||||
beforeSetup: function(effect) {
|
||||
effect.effects[0].element.setStyle({height: '0px'}).show();
|
||||
effect.effects[0].element.setStyle({height: '0px'}).show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
|
||||
effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
|
||||
}
|
||||
}, options)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -838,7 +844,7 @@ Effect.Shrink = function(element) {
|
|||
|
||||
var dims = element.getDimensions();
|
||||
var moveX, moveY;
|
||||
|
||||
|
||||
switch (options.direction) {
|
||||
case 'top-left':
|
||||
moveX = moveY = 0;
|
||||
|
|
@ -855,19 +861,19 @@ Effect.Shrink = function(element) {
|
|||
moveX = dims.width;
|
||||
moveY = dims.height;
|
||||
break;
|
||||
case 'center':
|
||||
case 'center':
|
||||
moveX = dims.width / 2;
|
||||
moveY = dims.height / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
|
||||
new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
|
||||
new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
|
||||
], Object.extend({
|
||||
], Object.extend({
|
||||
beforeStartInternal: function(effect) {
|
||||
effect.effects[0].element.makePositioned().makeClipping();
|
||||
effect.effects[0].element.makePositioned().makeClipping();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
|
||||
|
|
@ -877,12 +883,14 @@ Effect.Shrink = function(element) {
|
|||
|
||||
Effect.Pulsate = function(element) {
|
||||
element = $(element);
|
||||
var options = arguments[1] || { };
|
||||
var oldOpacity = element.getInlineOpacity();
|
||||
var transition = options.transition || Effect.Transitions.sinoidal;
|
||||
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
|
||||
reverser.bind(transition);
|
||||
return new Effect.Opacity(element,
|
||||
var options = arguments[1] || { },
|
||||
oldOpacity = element.getInlineOpacity(),
|
||||
transition = options.transition || Effect.Transitions.linear,
|
||||
reverser = function(pos){
|
||||
return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
|
||||
};
|
||||
|
||||
return new Effect.Opacity(element,
|
||||
Object.extend(Object.extend({ duration: 2.0, from: 0,
|
||||
afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
|
||||
}, options), {transition: reverser}));
|
||||
|
|
@ -896,12 +904,12 @@ Effect.Fold = function(element) {
|
|||
width: element.style.width,
|
||||
height: element.style.height };
|
||||
element.makeClipping();
|
||||
return new Effect.Scale(element, 5, Object.extend({
|
||||
return new Effect.Scale(element, 5, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Scale(element, 1, {
|
||||
scaleContent: false,
|
||||
new Effect.Scale(element, 1, {
|
||||
scaleContent: false,
|
||||
scaleY: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide().undoClipping().setStyle(oldStyle);
|
||||
|
|
@ -916,7 +924,7 @@ Effect.Morph = Class.create(Effect.Base, {
|
|||
var options = Object.extend({
|
||||
style: { }
|
||||
}, arguments[1] || { });
|
||||
|
||||
|
||||
if (!Object.isString(options.style)) this.style = $H(options.style);
|
||||
else {
|
||||
if (options.style.include(':'))
|
||||
|
|
@ -934,18 +942,18 @@ Effect.Morph = Class.create(Effect.Base, {
|
|||
effect.transforms.each(function(transform) {
|
||||
effect.element.style[transform.style] = '';
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
this.start(options);
|
||||
},
|
||||
|
||||
|
||||
setup: function(){
|
||||
function parseColor(color){
|
||||
if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
|
||||
color = color.parseColor();
|
||||
return $R(0,2).map(function(i){
|
||||
return parseInt( color.slice(i*2+1,i*2+3), 16 )
|
||||
return parseInt( color.slice(i*2+1,i*2+3), 16 );
|
||||
});
|
||||
}
|
||||
this.transforms = this.style.map(function(pair){
|
||||
|
|
@ -965,9 +973,9 @@ Effect.Morph = Class.create(Effect.Base, {
|
|||
}
|
||||
|
||||
var originalValue = this.element.getStyle(property);
|
||||
return {
|
||||
style: property.camelize(),
|
||||
originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
|
||||
return {
|
||||
style: property.camelize(),
|
||||
originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
|
||||
targetValue: unit=='color' ? parseColor(value) : value,
|
||||
unit: unit
|
||||
};
|
||||
|
|
@ -978,13 +986,13 @@ Effect.Morph = Class.create(Effect.Base, {
|
|||
transform.unit != 'color' &&
|
||||
(isNaN(transform.originalValue) || isNaN(transform.targetValue))
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
},
|
||||
update: function(position) {
|
||||
var style = { }, transform, i = this.transforms.length;
|
||||
while(i--)
|
||||
style[(transform = this.transforms[i]).style] =
|
||||
style[(transform = this.transforms[i]).style] =
|
||||
transform.unit=='color' ? '#'+
|
||||
(Math.round(transform.originalValue[0]+
|
||||
(transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
|
||||
|
|
@ -993,7 +1001,7 @@ Effect.Morph = Class.create(Effect.Base, {
|
|||
(Math.round(transform.originalValue[2]+
|
||||
(transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
|
||||
(transform.originalValue +
|
||||
(transform.targetValue - transform.originalValue) * position).toFixed(3) +
|
||||
(transform.targetValue - transform.originalValue) * position).toFixed(3) +
|
||||
(transform.unit === null ? '' : transform.unit);
|
||||
this.element.setStyle(style, true);
|
||||
}
|
||||
|
|
@ -1030,7 +1038,7 @@ Effect.Transform = Class.create({
|
|||
});
|
||||
|
||||
Element.CSS_PROPERTIES = $w(
|
||||
'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
|
||||
'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
|
||||
'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
|
||||
'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
|
||||
'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
|
||||
|
|
@ -1039,7 +1047,7 @@ Element.CSS_PROPERTIES = $w(
|
|||
'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
|
||||
'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
|
||||
'right textIndent top width wordSpacing zIndex');
|
||||
|
||||
|
||||
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
|
||||
|
||||
String.__parseStyleElement = document.createElement('div');
|
||||
|
|
@ -1051,11 +1059,11 @@ String.prototype.parseStyle = function(){
|
|||
String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
|
||||
style = String.__parseStyleElement.childNodes[0].style;
|
||||
}
|
||||
|
||||
|
||||
Element.CSS_PROPERTIES.each(function(property){
|
||||
if (style[property]) styleRules.set(property, style[property]);
|
||||
if (style[property]) styleRules.set(property, style[property]);
|
||||
});
|
||||
|
||||
|
||||
if (Prototype.Browser.IE && this.include('opacity'))
|
||||
styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
|
||||
|
||||
|
|
@ -1074,14 +1082,14 @@ if (document.defaultView && document.defaultView.getComputedStyle) {
|
|||
Element.getStyles = function(element) {
|
||||
element = $(element);
|
||||
var css = element.currentStyle, styles;
|
||||
styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) {
|
||||
hash.set(property, css[property]);
|
||||
return hash;
|
||||
styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
|
||||
results[property] = css[property];
|
||||
return results;
|
||||
});
|
||||
if (!styles.opacity) styles.set('opacity', element.getOpacity());
|
||||
if (!styles.opacity) styles.opacity = element.getOpacity();
|
||||
return styles;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Effect.Methods = {
|
||||
morph: function(element, style) {
|
||||
|
|
@ -1090,7 +1098,7 @@ Effect.Methods = {
|
|||
return element;
|
||||
},
|
||||
visualEffect: function(element, effect, options) {
|
||||
element = $(element)
|
||||
element = $(element);
|
||||
var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
|
||||
new Effect[klass](element, options);
|
||||
return element;
|
||||
|
|
@ -1104,17 +1112,17 @@ Effect.Methods = {
|
|||
|
||||
$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
|
||||
'pulsate shake puff squish switchOff dropOut').each(
|
||||
function(effect) {
|
||||
function(effect) {
|
||||
Effect.Methods[effect] = function(element, options){
|
||||
element = $(element);
|
||||
Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
|
||||
return element;
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
|
||||
$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
|
||||
function(f) { Effect.Methods[f] = Element[f]; }
|
||||
);
|
||||
|
||||
Element.addMethods(Effect.Methods);
|
||||
Element.addMethods(Effect.Methods);
|
||||
629
public/javascripts/prototype.js
vendored
629
public/javascripts/prototype.js
vendored
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -29,15 +29,18 @@ div.footer a {
|
|||
}
|
||||
|
||||
h1 {
|
||||
color: #f00;
|
||||
font-size: small;
|
||||
color: #f00;
|
||||
font-size: small;
|
||||
margin-top:.3em;
|
||||
margin-bottom:.3em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
background-color: #cccccc;
|
||||
font-size : small;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0;
|
||||
background-color: #aaaaaa;
|
||||
font-size : small;
|
||||
margin: .3em 0;
|
||||
padding: .3em 0 .1em .3em;
|
||||
border-top: 1px solid #777777;
|
||||
}
|
||||
|
||||
h4.alert {
|
||||
|
|
@ -60,12 +63,8 @@ h4.notice {
|
|||
color: #007E00;
|
||||
}
|
||||
|
||||
div.t {
|
||||
padding-left:5px;
|
||||
}
|
||||
|
||||
span.tag {
|
||||
font-size: XX-small;
|
||||
font-size: x-small;
|
||||
background-color: #CCE7FF;
|
||||
color: #000;
|
||||
padding: 1px;
|
||||
|
|
@ -86,43 +85,78 @@ span.prj, span.ctx{
|
|||
color: #fff;
|
||||
background: #f00;
|
||||
padding: 1px;
|
||||
font-size: 10px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.amber {
|
||||
color: #fff;
|
||||
background: #ff6600;
|
||||
padding: 1px;
|
||||
font-size: 10px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.orange {
|
||||
color: #fff;
|
||||
background: #FFA500;
|
||||
padding: 1px;
|
||||
font-size: 10px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: #fff;
|
||||
background: #33cc00;
|
||||
padding: 1px;
|
||||
font-size: 10px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.grey {
|
||||
color: #fff;
|
||||
background: #999;
|
||||
padding: 1px;
|
||||
font-size: 10px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.count {
|
||||
color: #fff;
|
||||
background: #000;
|
||||
font-size: medium;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
.errors {
|
||||
background: #FFC2C2;
|
||||
}
|
||||
}
|
||||
|
||||
ul.c li.star {
|
||||
list-style-image:url(../images/menustar_small.gif)
|
||||
}
|
||||
|
||||
ul.c {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-left: 1.1em;
|
||||
}
|
||||
|
||||
ul.c li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul.c li span.r {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.r {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.nav {
|
||||
font-size: x-small;
|
||||
}
|
||||
|
||||
#database_auth_form table td {
|
||||
width:7em;
|
||||
}
|
||||
|
||||
table.c {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,293 +0,0 @@
|
|||
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td {margin:0; padding:0}
|
||||
table {border-collapse:collapse; border-spacing:0}
|
||||
fieldset,img {border:0}
|
||||
address,caption,cite,code,dfn,em,strong,th,var {font-style:normal; font-weight:normal}
|
||||
ol,ul {list-style:none}
|
||||
caption,th {text-align:left}
|
||||
h1,h2,h3,h4,h5,h6 {font-size:100%; font-weight:normal}
|
||||
q:before,q:after {content:''}
|
||||
abbr,acronym {border:0}
|
||||
body {font-family: "Lucida Grande", Verdana, Geneva, Arial, sans-serif; font-size: 80%; padding: 0px 10px; margin: 0px; background: #eee}
|
||||
p {padding: 2px; font-size: 92%; line-height: 140%}
|
||||
a, a:link, a:active, a:visited {color: #cc3334; text-decoration: none; padding-left: 1px; padding-right: 1px}
|
||||
a:hover {color: #fff; background-color: #cc3334}
|
||||
h1 {font-size: 304%; font-weight: bold}
|
||||
h2 {font-size: 148%; font-weight: bold}
|
||||
h3 {font-size: 129%; font-weight: bold}
|
||||
img.edit_item {background-image: url(../images/edit_off.png); background-repeat: no-repeat; border: none;}
|
||||
a:hover img.edit_item {background-image: url(../images/edit_on.png); background-color: transparent; background-repeat: no-repeat; border: none;}
|
||||
img.delete_item {background-image: url(../images/delete_off.png); background-repeat: no-repeat; border: none;}
|
||||
a:hover img.delete_item {background-image: url(../images/delete_on.png);background-color: transparent;background-repeat: no-repeat; border: none;}
|
||||
img.starred_todo {background-image: url(../images/staricons.png); background-repeat: no-repeat; border:none; background-position: 0px 0px;}
|
||||
a:hover img.starred_todo {background-image: url(../images/staricons.png); background-repeat: no-repeat; border:none; background-position: -16px 0px;}
|
||||
img.unstarred_todo {background-image: url(../images/staricons.png); background-repeat: no-repeat; border:none; background-position: -32px 0px;}
|
||||
a:hover img.unstarred_todo {background-image: url(../images/staricons.png); background-repeat: no-repeat; border:none; background-position: -48px 0px;}
|
||||
a.to_top {background: transparent url(../images/top_off.png) no-repeat;}
|
||||
a.to_top:hover {background: transparent url(../images/top_on.png) no-repeat;}
|
||||
a.up {background: transparent url(../images/up_off.png) no-repeat;}
|
||||
a.up:hover {background: transparent url(../images/up_on.png) no-repeat;}
|
||||
a.down {background: transparent url(../images/down_off.png) no-repeat;}
|
||||
a.down:hover {background: transparent url(../images/down_on.png) no-repeat;}
|
||||
a.to_bottom {background: transparent url(../images/bottom_off.png) no-repeat;}
|
||||
a.to_bottom:hover {background: transparent url(../images/bottom_on.png) no-repeat;}
|
||||
a.show_notes, a.link_to_notes {background-image: url(../images/notes_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
|
||||
a.show_notes:hover, a.link_to_notes:hover {background-image: url(../images/notes_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
|
||||
#content {margin-top: 90px}
|
||||
#display_box {float: left; width: 55%; margin: 0px 10px 50px 15px}
|
||||
#single_box {width: 60%; margin: 80px auto}
|
||||
#full_width_display {float: left; width: 95%; margin: 0px 15px 90px 15px}
|
||||
#display_box_projects {float: left; width: 95%; margin: 0px 15px 90px 15px}
|
||||
#recurring_timespan, #recurring_target {border: none; clear: both}
|
||||
#recurring_target {border-top-style: dotted; padding: 15px 0px 0px 0px}
|
||||
#recurring_daily, #recurring_weekly, #recurring_monthly, #recurring_yearly, #recurring_edit_daily, #recurring_edit_weekly, #recurring_edit_monthly, #recurring_edit_yearly {border: none; border-top-style: dotted; clear: both; padding: 15px 0px 15px 0px}
|
||||
#recurring_period_id, #recurring_edit_period_id {border: none; float: left; margin: 0px 50px 0px 0px; padding: 0px 25px 15px 50px; border-right-style: none}
|
||||
#recurring_todo {width: 270px}
|
||||
#recurring_todo_form_container {border-right-style: dotted; padding: 0px 50px 15px 0px; float: left}
|
||||
#recurring_todo input, #recurring_todo textarea {width: 100%}
|
||||
#overlay {visibility: hidden; position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; z-index: 102; text-align: center; background-image:url("../images/trans70.png")}
|
||||
#overlay #new-recurring-todo, #overlay #edit-recurring-todo {width:750px; background-color: #fff; border:1px solid #000; padding: 15px; margin: 70px auto}
|
||||
.recurring_container {padding: 0px 5px 0px 5px; border: 1px solid #999; margin: 0px 0px 0px 0px; background: #fff; text-align: left}
|
||||
.recurring_submit_box {height: 25px; padding: 5px 0; text-align: center; clear: both; border: none}
|
||||
#navcontainer {position: fixed; top: 48px; left: 0px}
|
||||
#navlist {margin: 0; padding: 0 0 20px 5px}
|
||||
#navlist ul, #navlist li {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: #eee}
|
||||
#navlist a:link#current, #navlist a:visited#current, #navlist a:hover {border-bottom: 4px solid #CCC; padding-bottom: 2px; background: transparent; color: #CCC}
|
||||
#navlist a:hover {color: #CCC}
|
||||
#topbar {position: fixed; top: 0px; left: 0px; height: 68px; margin-bottom: 20px; clear: both; background-color: #000; filter: alpha(opacity=75); -moz-opacity: .75; opacity: .75; color: #eee; width: 100%; z-index:101}
|
||||
body.stats #topbar {filter: alpha(opacity=100); -moz-opacity: 1; opacity: 1}
|
||||
#date {float: left; width: 45%; padding-left: 15px; margin-top: 15px; margin-bottom: 5px; white-space: nowrap}
|
||||
#date h1 {font-size: 152%}
|
||||
#minilinks {text-align: right; position: fixed; right: 15px; top: 10px; font-size: 0.9em}
|
||||
.container {padding: 0px 5px 0px 5px; border: 1px solid #999; margin: 0px 0px 15px 0px; background: #fff}
|
||||
.completed {background: #eee}
|
||||
.container h2 {background: #ccc; padding: 5px; margin-top: 0px; margin-left: -5px; margin-right: -5px; margin-bottom: 0px; color: #666; position:static}
|
||||
.container_toggle img {height:20px; width:20px; border:0px}
|
||||
h2 a, h2 a:link, h2 a:active, h2 a:visited {color: #666; text-decoration: none}
|
||||
h2 a:hover {color: #cc3334; background-color: transparent; text-decoration: none}
|
||||
div#input_box {margin: 0px 15px 0px 58%; padding: 0px 15px 0px 0px}
|
||||
#input_box h2 {color: #999}
|
||||
#input_box ul {list-style-type: circle; font-size: 0.9em;}
|
||||
.show_from_input, .due_input, .project_input, .context_input {float:left}
|
||||
.box {float: left; width: 20px}
|
||||
div.item-container {padding: 2px 0px; line-height:20px; clear: both}
|
||||
a.recurring_icon {vertical-align: middle; background-color: transparent}
|
||||
a.icon {float: left; vertical-align: middle; background-color: transparent}
|
||||
input.item-checkbox {float: left; margin-left: 10px; vertical-align: middle}
|
||||
.description {margin-left: 85px; position:relative }
|
||||
.stale_l1, .stale_l2, .stale_l3 {margin-left: 82px; padding-left: 3px}
|
||||
.stale_l1 {background: #ffC}
|
||||
.tools {margin-left: 25px; width: 40px; border-top: 1px solid #999}
|
||||
#footer {clear: both; font-size: 85%; text-align: center; color: #999; margin: 20px 20px 5px 20px; padding: 0px}
|
||||
.todo_notes {margin: 5px; padding: 3px; border: 1px solid #F5ED59; background: #FAF6AE; color: #666666}
|
||||
.todo_notes p, .todo_notes li {padding: 1px; margin: 0px; font-size: 12px}
|
||||
.todo_notes ul, .note_wrapper ul {list-style-type: disc; margin-left:20px}
|
||||
.todo_notes ol {list-style-type: decimal; margin-left:20px}
|
||||
div.note_wrapper {margin: 3px; padding: 2px}
|
||||
div.note_wrapper p {display: inline}
|
||||
div.note_footer {border-top: 1px solid #999; padding-top: 3px; font-style: italic; font-size: 0.9em; color: #666}
|
||||
div.note_footer a, div.note_footer a:hover {border-top: none; padding-top: 0px; vertical-align: middle; background-color: transparent}
|
||||
div.add_note_link {margin-top:12px; float: right}
|
||||
div#project_status > div {padding: 10px}
|
||||
#project_status span {margin-right:5px; background-color:white}
|
||||
#project_status .active_state {font-weight:bold}
|
||||
div#default_context > div{ padding:10px}
|
||||
a.footer_link {color: #cc3334; font-style: normal;}
|
||||
a.footer_link:hover {color: #fff; background-color: #cc3334 !important;}
|
||||
span.tag {font-size: 0.8em; background-color: #CCE7FF; color: #000; padding: 1px; margin-right: 2px}
|
||||
span.tag a, span.tag a:link, span.tag a:active, span.tag a:visited {color: #000}
|
||||
span.tag a:hover {background-color: #99CCFF; color: #333}
|
||||
div#message_holder {position: absolute; z-index: 100; left: 60%; top: 30px; right: 0px; margin: 0px}
|
||||
h4.alert {font-size: 1em; margin: 0px; padding: 5px; text-align: center}
|
||||
h4.warning {border: 1px solid #ED2E38; background-color: #F6979C; color: #000}
|
||||
h4.error {color:#fff; background:#c00}
|
||||
h4.notice {border: 1px solid #007E00; background-color: #c2ffc2; color: #007E00}
|
||||
.project_completed {border: 1px solid #007E00; background-color: #c2ffc2; padding: 5px; color: #007E00; text-align: center}
|
||||
.red {color: #fff; background: #f00; padding: 1px; font-size: 85%}
|
||||
.amber {color: #fff; background: #ff6600; padding: 1px; font-size: 85%}
|
||||
.orange {color: #fff; background: #FFA500; padding: 1px; font-size: 85%}
|
||||
.green {color: #fff; background: #33cc00; padding: 1px; font-size: 85%}
|
||||
.grey {color: #fff; background: #999; padding: 2px; font-size: 85%}
|
||||
.info {color: #fff; background: #CCC; border: 1px solid #999; padding: 5px; text-align: center}
|
||||
.highlight {background: #ffC; padding: 2px}
|
||||
.stale_l1 {background: #ffC}
|
||||
.stale_l2 {background: #ff6}
|
||||
.stale_l3 {background: #ff0}
|
||||
.badge {color: #fff; background: #f00; padding: 3px 5px; font-size: 12pt; margin: 10px 10px 0px 0px; height:26px}
|
||||
ul {list-style-type: none}
|
||||
#sidebar h3 {margin-top:15px; margin-bottom:5px}
|
||||
#sidebar ul {margin-left: auto; list-style-position: inside}
|
||||
li {font-size: 1.1em; padding: 3px 0px}
|
||||
#sidebar .integrations-link {margin-top:10px; padding-top:10px; font-size: 0.8em}
|
||||
.sortable_row {background: #fff; _background: transparent; padding: 4px 4px 4px 8px; margin: 2px 2px; border: 1px solid #ccc}
|
||||
.edit-form {background: #ccc; padding: 0 10px; border-top: 1px solid #999; border-bottom: 1px solid #999; position:relative}
|
||||
.label {text-align: right}
|
||||
input {vertical-align: middle}
|
||||
img.position, a:hover img.position {text-align: left; vertical-align: middle; background-color: transparent}
|
||||
.data {text-align: left; margin-left: 20px; float: left}
|
||||
div.buttons, div.buttons a, div.buttons a:hover {text-align: right; margin-right: 0px; vertical-align: middle; background-color: transparent}
|
||||
div#list-active-projects, div#list-hidden-projects, div#list-completed-projects, div#list-contexts, div#projects-empty-nd {clear:right; border: 1px solid #999}
|
||||
.project-state-group h2 {margin:20px 0px 8px 13px}
|
||||
.search-result-group h2 {margin:20px 0px 8px 13px }
|
||||
div.menu_sort {margin-top:-20px; float:right}
|
||||
div.alpha_sort, div.tasks_sort,span.sort_separator {float:left}
|
||||
.container td {border: none; padding-bottom: 5px}
|
||||
.container form {border: none}
|
||||
div.project_description {background: #eee; padding: 5px; margin-top: 0px; margin-left: -5px; margin-right: -5px; color: #666; font-style: italic; font-size: 12px; font-weight: normal}
|
||||
#project-next-prev {text-align:right}
|
||||
form {border: 1px solid #CCC; padding: 10px; margin: 0px}
|
||||
.inline-form {border: none; padding: 3px}
|
||||
.inline-form table {padding-right: 3px}
|
||||
.inline-form table, .inline-form textarea#item_notes, .inline-form input#item_description {width: 100%}
|
||||
.inline-form table td.label {width: 13ex}
|
||||
#todo_new_action_container, #project_new_project_container, #context_new_container, #recurring_new_container {background: #ddd; width: 270px; padding: 5px 10px; background-color: #000; filter: alpha(opacity=75); -moz-opacity: .75; opacity: .75; color: #eee}
|
||||
#recurring_new_container img {vertical-align: middle}
|
||||
#project_new_project_filler {padding-top: 50px}
|
||||
#todo_new_action_container input, #todo_new_action_container textarea, #project_new_project_container input, #project_new_project_container textarea, #context_new_container input {width: 100%}
|
||||
input#go_to_project, input#context_hide {width: 5%}
|
||||
#todo_new_action_container .show_from_input, #todo_new_action_container .due_input {width: 45%}
|
||||
#todo_new_action_container .show_from_input {float: right}
|
||||
#todo-form-new-action .submit_box, #project_form .submit_box, #context_form .submit_box {height: 25px; padding: 5px 0; text-align: center; clear: right}
|
||||
.edit_todo_form .submit_box {height: 25px; padding: 5px 0; text-align: center; clear: right}
|
||||
.edit_todo_form input, .edit_todo_form textarea {width:100%}
|
||||
.edit_todo_form .Date {width:89%}
|
||||
.edit_todo_form a.date_clear:hover {background: #CCCCCC}
|
||||
.edit_todo_form .tag_list_label {clear:both}
|
||||
.edit_todo_form .due_input, .edit_todo_form .show_from_input, .edit_todo_form .project_input, .edit_todo_form .context_input {width:48%}
|
||||
.edit_todo_form .show_from_input, .edit_todo_form .context_input {float: right}
|
||||
.edit_todo_form .submit_box input {width:120px}
|
||||
.hide_form {text-align:right}
|
||||
#todo-form-new-action label, .edit_todo_form label {display: block; padding-bottom: 3px}
|
||||
form.button-to {border: none; padding: 0px; margin: 0px}
|
||||
label {font-weight: bold; padding: 0px 0px}
|
||||
input, select, textarea {margin: 0px 0px 5px 0px}
|
||||
#feedicons-project, #feedicons-context {background-color: #D2D3D6; margin: 0px 0px 0px 60px; padding: 0px 0px 0px 5px; width: 70%}
|
||||
.feed {font-family: verdana, sans-serif; font-size: 10px; font-weight:bold; text-decoration:none; color: white; background-color: #F60; border:1px solid; border-color: #FC9 #630 #330 #F96; padding:0px 3px 0px 3px; margin:0px}
|
||||
.position {float: left; margin-top:2px}
|
||||
.handle {color: #fff; background: #000; padding: 2px; font-size: 92%; cursor: move}
|
||||
div.message {margin: 5px 0px; background: #FAF4B5; padding: 2px}
|
||||
.message p {margin: 0px; padding: 0px; text-align: center; font-size: 1em; color: #666}
|
||||
.fieldWithErrors {padding: 2px; background-color: red; display: table}
|
||||
#errorExplanation {border: 2px solid #ff0000; padding: 7px; padding-bottom: 12px; margin: 10px auto 20px auto; background-color: #f0f0f0}
|
||||
#errorExplanation h2 {text-align: left; font-weight: bold; padding: 5px 5px 5px 15px; font-size: 12px; margin: -7px; background-color: #c00; color: #fff}
|
||||
#errorExplanation > p {color: #333; margin-top: 5px; margin-bottom: 0; padding: 5px}
|
||||
#errorExplanation ul li {font-size: 1em; list-style-type: disc; list-style-position: inside; margin-left:7px; color: #333}
|
||||
ul#prefs {list-style-type: disc; margin-left: 15px;}
|
||||
#token_area, #authentication_area {text-align:center; margin-top:20px; margin-bottom:10px}
|
||||
#token_area .description{ font-weight:bold}
|
||||
#token_area form {width:100%; text-align:center}
|
||||
.prefscontainer .actions {text-align:center; margin-bottom:20px}
|
||||
.authtype_container .actions {margin-top:20px; margin-bottom:20px}
|
||||
#feedlegend {padding: 2px; background-color: #D2D3D6; color: #666; padding: 5px 20px; text-align: left}
|
||||
#feedlegend h3, #feedlegend dl, #feedlegend dt, #feedlegend dd {display: inline}
|
||||
#feedlegend dt {margin-left: 15px}
|
||||
#feedlegend dd {margin-left: 3px}
|
||||
#feedlegend p {margin-bottom: 0px}
|
||||
#feeds img.rss-icon {margin-bottom: -4px}
|
||||
#feeds li {font-size:13px; font-family: "Lucida Grande", Verdana, Geneva, Arial, sans-serif}
|
||||
#feeds li h4 {margin-top: 12px; margin-bottom: 4px; font-weight: normal; font-style:oblique}
|
||||
input.open_id {background: url(../images/open-id-login-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; color: #000; padding-left: 18px; width:182px}
|
||||
div.page_name_auto_complete {width: 100%; background: #fff; display: inline; z-index: 100}
|
||||
div.page_name_auto_complete ul {border: 1px solid #888; margin: 0; padding: 0; width: 100%; list-style-type: none}
|
||||
div.page_name_auto_complete ul li {margin: 0; padding: 2px; font-weight:bold; list-style-type: none; color: #000}
|
||||
div.page_name_auto_complete ul li.selected {background-color: #ffb}
|
||||
div.page_name_auto_complete ul strong.highlight {color: #800; margin: 0; padding: 0}
|
||||
table.next_actions td {padding:5px 3px 2px 0px}
|
||||
table.users_table {width: 100%; text-align: center; border: 1px solid #666; background-color: #fff; border-spacing: 0px}
|
||||
.users_table th {color: #fff; background-color: #000;}
|
||||
.users_table td {border: none;}
|
||||
table.export_table {border: 1px solid #666; background-color: #fff; border-spacing: 0px}
|
||||
.export_table th {color: #fff; background-color: #000;}
|
||||
.export_table td {border: 1px; padding: 5px 0px 5px 5px;}
|
||||
.widgets a, .widgets button{ display:block; float: left; margin:0px 7px 0 0; background-color:#f5f5f5; border:1px solid #dedede; border-top:1px solid #eee; border-left:1px solid #eee; font-family:"Lucida Grande", Verdana, Geneva, Arial, sans-serif; font-size:80%; line-height:100%; text-decoration:none; font-weight:bold; color:#565656; cursor:pointer; padding:3px 5px 7px 5px}
|
||||
.widgets button{ width:auto; overflow:visible; padding:3px 5px 5px 5px}
|
||||
.widgets button[type]{ padding:3px 5px 4px 5px; line-height:15px}
|
||||
.widgets button img, .widgets a img{ margin:0 3px -3px 0 !important; padding:0; border:none; width:16px; height:16px}
|
||||
.widgets a:hover{ background-color:#dff4ff; border:1px solid #c2e1ef; color:#336699}
|
||||
.widgets a:active{ background-color:#6299c5; border:1px solid #6299c5; color:#fff}
|
||||
button.positive, .widgets a.positive{ color: #498111}
|
||||
.widgets a.positive:hover, button.positive:hover{ background-color:#E6EFC2; border:1px solid #C6D880; color:#529214}
|
||||
.widgets a.positive:active{ background-color:#529214; border:1px solid #529214; color:#fff}
|
||||
.widgets a.negative, button.negative{ color:#d12f19}
|
||||
.widgets a.negative:hover, button.negative:hover{ background:#fbe3e4; border:1px solid #fbc2c4; color:#d12f19}
|
||||
.widgets a.negative:active{ background-color:#d12f19; border:1px solid #d12f19; color:#fff}
|
||||
.tracks__waiting {background-image:url('../images/waiting.gif'); background-repeat:no-repeat; background-position:center center; background-color:white}
|
||||
.bigWaiting {background-image:url('../images/bigWaiting.gif'); background-repeat:no-repeat; background-position:center 20%; background-color:white}
|
||||
.blackWaiting {background-image:url('../images/blackWaiting.gif'); background-repeat:no-repeat; background-position:center center; background-color:black}
|
||||
.bigBlackWaiting {background-image:url('../images/bigBlackWaiting.gif'); background-repeat:no-repeat; background-position:center center; background-color:black}
|
||||
.stats_content .open-flash-chart, .stats_content .stats_module {float: left; width: 450px; margin-right:20px; padding-bottom:20px}
|
||||
.stats_content h2 {clear:both; margin-top:15px; margin-bottom:15px}
|
||||
body.integrations h2 {margin-top:40px; padding-top:20px; margin-bottom:10px; border-top:1px solid #ccc}
|
||||
body.integrations p, body.integrations li {font-size:1.0em}
|
||||
body.integrations li {list-style-type: disc; list-style-position: inside; margin-left:30px}
|
||||
body.integrations textarea {margin:10px; padding:3px; width:80%; background-color:#ddd}
|
||||
.defer-container {float:right}
|
||||
.defer-container a:hover {background-color: inherit}
|
||||
div.calendar {position: relative}
|
||||
.calendar, .calendar table {border: 1px solid #556; font-size: 11px; color: #000; cursor: default; background: #eef; z-index: 110; font-family: tahoma,verdana,sans-serif}
|
||||
.calendar .button {text-align: center; padding: 2px}
|
||||
.calendar .nav {background: #778 url(../images/menuarrow.gif) no-repeat 100% 100%}
|
||||
.calendar thead .title {font-weight: bold; text-align: center; background: #fff; color: #000; padding: 2px}
|
||||
.calendar thead .headrow {background: #778; color: #fff}
|
||||
.calendar thead .daynames {background: #bdf}
|
||||
.calendar thead .name {border-bottom: 1px solid #556; padding: 2px; text-align: center; color: #000}
|
||||
.calendar thead .weekend {color: #a66}
|
||||
.calendar thead .hilite {background-color: #aaf; color: #000; border: 1px solid #04f; padding: 1px}
|
||||
.calendar thead .active {background-color: #77c; padding: 2px 0px 0px 2px}
|
||||
.calendar tbody .day {width: 2em; color: #456; text-align: right; padding: 2px 4px 2px 2px}
|
||||
.calendar tbody .day.othermonth {font-size: 80%; color: #bbb}
|
||||
.calendar tbody .day.othermonth.oweekend {color: #fbb}
|
||||
.calendar table .wn {padding: 2px 3px 2px 2px; border-right: 1px solid #000; background: #bdf}
|
||||
.calendar tbody .rowhilite td {background: #def}
|
||||
.calendar tbody .rowhilite td.wn {background: #eef}
|
||||
.calendar tbody td.hilite {background: #def; padding: 1px 3px 1px 1px; border: 1px solid #bbb}
|
||||
.calendar tbody td.active {background: #cde; padding: 2px 2px 0px 2px}
|
||||
.calendar tbody td.selected {font-weight: bold; border: 1px solid #000; padding: 1px 3px 1px 1px; background: #fff; color: #000}
|
||||
.calendar tbody td.weekend {color: #a66}
|
||||
.calendar tbody td.today {font-weight: bold; color: #00f}
|
||||
.calendar tbody .disabled {color: #999}
|
||||
.calendar tbody .emptycell {visibility: hidden}
|
||||
.calendar tbody .emptyrow {display: none}
|
||||
.calendar tfoot .footrow {text-align: center; background: #556; color: #fff}
|
||||
.calendar tfoot .ttip {background: #fff; color: #445; border-top: 1px solid #556; padding: 1px}
|
||||
.calendar tfoot .hilite {background: #aaf; border: 1px solid #04f; color: #000; padding: 1px}
|
||||
.calendar tfoot .active {background: #77c; padding: 2px 0px 0px 2px}
|
||||
.calendar .combo {position: absolute; display: none; top: 0px; left: 0px; width: 4em; cursor: default; border: 1px solid #655; background: #def; color: #000; font-size: 90%; z-index: 100}
|
||||
.calendar .combo .label, .calendar .combo .label-IEfix {text-align: center; padding: 1px}
|
||||
.calendar .combo .label-IEfix {width: 4em}
|
||||
.calendar .combo .hilite {background: #acf}
|
||||
.calendar .combo .active {border-top: 1px solid #46a; border-bottom: 1px solid #46a; background: #eef; font-weight: bold}
|
||||
.calendar td.time {border-top: 1px solid #000; padding: 1px 0px; text-align: center; background-color: #f4f0e8}
|
||||
.calendar td.time .hour, .calendar td.time .minute, .calendar td.time .ampm {padding: 0px 3px 0px 4px; border: 1px solid #889; font-weight: bold; background-color: #fff}
|
||||
.calendar td.time .ampm {text-align: center}
|
||||
.calendar td.time .colon {padding: 0px 2px 0px 3px; font-weight: bold}
|
||||
.calendar td.time span.hilite {border-color: #000; background-color: #667; color: #fff}
|
||||
.calendar td.time span.active {border-color: #f00; background-color: #000; color: #0f0}
|
||||
b.niftycorners,b.niftyfill{display:block}
|
||||
b.niftycorners *{display:block;height: 1px;line-height:1px;font-size: 1px; overflow:hidden;border-style:solid;border-width: 0 1px}
|
||||
b.r1{margin: 0 3px;border-width: 0 2px}
|
||||
b.r2{margin: 0 2px}
|
||||
b.r3{margin: 0 1px}
|
||||
b.r4{height: 2px}
|
||||
b.rb1{margin: 0 8px;border-width:0 2px}
|
||||
b.rb2{margin: 0 6px;border-width:0 2px}
|
||||
b.rb3{margin: 0 5px}
|
||||
b.rb4{margin: 0 4px}
|
||||
b.rb5{margin: 0 3px}
|
||||
b.rb6{margin: 0 2px}
|
||||
b.rb7{margin: 0 1px;height:2px}
|
||||
b.rb8{margin: 0;height:2px}
|
||||
b.rs1{margin: 0 1px}
|
||||
b.t1{border-width: 0 5px}
|
||||
b.t2{border-width: 0 3px}
|
||||
b.t3{border-width: 0 2px}
|
||||
b.t4{height: 2px}
|
||||
b.tb1{border-width: 0 10px}
|
||||
b.tb2{border-width: 0 8px}
|
||||
b.tb3{border-width: 0 6px}
|
||||
b.tb4{border-width: 0 5px}
|
||||
b.tb5{border-width: 0 4px}
|
||||
b.tb6{border-width: 0 3px}
|
||||
b.tb7{border-width: 0 2px;height:2px}
|
||||
b.tb8{border-width: 0 1px;height:2px}
|
||||
b.ts1{border-width: 0 2px}
|
||||
4
script/autospec
Executable file
4
script/autospec
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
ENV['RSPEC'] = 'true' # allows autotest to discover rspec
|
||||
ENV['AUTOTEST'] = 'true' # allows autotest to run w/ color on linux
|
||||
system (RUBY_PLATFORM =~ /mswin|mingw/ ? 'autotest.bat' : 'autotest'), *ARGV
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../vendor/plugins/rspec/lib"))
|
||||
require 'rubygems'
|
||||
require 'spec'
|
||||
exit ::Spec::Runner::CommandLine.run(::Spec::Runner::OptionParser.parse(ARGV, STDERR, STDOUT))
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@ module LuckySneaks
|
|||
it "should not be valid if #{attribute} length is more than #{maximum}" do
|
||||
instance.send "#{attribute}=", 'x'*(maximum+1)
|
||||
instance.errors_on(attribute).should include(
|
||||
options[:message_too_long] || ActiveRecord::Errors.default_error_messages[:too_long] % maximum
|
||||
options[:message_too_long] || I18n.t('activerecord.errors.messages.too_long', :count => maximum)
|
||||
)
|
||||
end if maximum
|
||||
|
||||
it "should not be valid if #{attribute} length is less than #{minimum}" do
|
||||
instance.send "#{attribute}=", 'x'*(minimum-1)
|
||||
instance.errors_on(attribute).should include(
|
||||
options[:message_to_short] || ActiveRecord::Errors.default_error_messages[:too_short] % minimum
|
||||
options[:message_to_short] || I18n.t('activerecord.errors.messages.too_short', :count => minimum)
|
||||
)
|
||||
end if minimum
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,32 +3,36 @@ require File.dirname(__FILE__) + '/../../spec_helper'
|
|||
describe "/notes/_notes.rhtml" do
|
||||
before :each do
|
||||
@project = mock_model(Project, :name => "a project")
|
||||
@note = mock_model(Note, :body => "this is a note", :project => @project,
|
||||
@note = mock_model(Note, :body => "this is a note", :project => @project, :project_id => @project.id,
|
||||
:created_at => Time.now, :updated_at? => false)
|
||||
@controller.template.stub!(:apply_behavior)
|
||||
# @controller.template.stub!(:apply_behavior)
|
||||
@controller.template.stub!(:format_date)
|
||||
@controller.template.stub!(:render)
|
||||
@controller.template.stub!(:form_remote_tag)
|
||||
# @controller.template.stub!(:render)
|
||||
# @controller.template.stub!(:form_remote_tag)
|
||||
end
|
||||
|
||||
it "should render" do
|
||||
pending "figure out how to mock or work with with UJS"
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("div.note_footer")
|
||||
end
|
||||
|
||||
it "should auto-link URLs" do
|
||||
pending "figure out how to mock or work with with UJS"
|
||||
@note.stub!(:body).and_return("http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should auto-link embedded URLs" do
|
||||
pending "figure out how to mock or work with with UJS"
|
||||
@note.stub!(:body).and_return("this is cool: http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
end
|
||||
|
||||
it "should parse Textile links correctly" do
|
||||
pending "figure out how to mock or work with with UJS"
|
||||
@note.stub!(:body).and_return("\"link\":http://www.google.com/")
|
||||
render :partial => "/notes/notes", :locals => {:notes => @note}
|
||||
response.should have_tag("a[href=\"http://www.google.com/\"]")
|
||||
|
|
|
|||
4
stories/all.rb
Normal file
4
stories/all.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
dir = File.dirname(__FILE__)
|
||||
Dir[File.expand_path("#{dir}/**/*.rb")].uniq.each do |file|
|
||||
require file
|
||||
end
|
||||
|
|
@ -1,136 +1,3 @@
|
|||
ENV["RAILS_ENV"] = "test"
|
||||
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
||||
$:.unshift File.join(File.dirname(__FILE__), *%w[.. vendor plugings rspec lib])
|
||||
require 'test_help'
|
||||
require 'test/unit/testresult'
|
||||
require 'spec'
|
||||
require 'spec/rails'
|
||||
require 'spec/story'
|
||||
require 'webrat/selenium'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
module Spec
|
||||
module Story
|
||||
class StepGroup
|
||||
def include_steps_for(name)
|
||||
require File.expand_path(File.dirname(__FILE__) + "/steps/#{name}")
|
||||
step_matchers = rspec_story_steps[name.to_sym]
|
||||
warn "WARNING: 0 step matchers found for include_steps_for(:#{name}). Are you missing an include?" if step_matchers.empty?
|
||||
self << step_matchers
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Test::Unit.run = true
|
||||
|
||||
class SeleniumRailsStory < Test::Unit::TestCase
|
||||
include Spec::Matchers
|
||||
include Spec::Rails::Matchers
|
||||
|
||||
def initialize #:nodoc:
|
||||
# TODO - eliminate this hack, which is here to stop
|
||||
# Rails Stories from dumping the example summary.
|
||||
Spec::Runner::Options.class_eval do
|
||||
def examples_should_be_run?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@_result = Test::Unit::TestResult.new
|
||||
end
|
||||
|
||||
def should_see(text_or_regexp)
|
||||
if text_or_regexp.is_a?(Regexp)
|
||||
response.should have_tag("*", text_or_regexp)
|
||||
else
|
||||
response.should have_tag("*", /#{Regexp.escape(text_or_regexp)}/i)
|
||||
end
|
||||
end
|
||||
|
||||
def should_not_see(text_or_regexp)
|
||||
if text_or_regexp.is_a?(Regexp)
|
||||
response.should_not have_tag("*", text_or_regexp)
|
||||
else
|
||||
response.should_not have_tag("*", /#{Regexp.escape(text_or_regexp)}/i)
|
||||
end
|
||||
end
|
||||
|
||||
def response
|
||||
webrat_session.response_body
|
||||
end
|
||||
|
||||
def logged_in_as(user)
|
||||
visits("/selenium_helper/login?as=#{user.login}")
|
||||
end
|
||||
|
||||
def selenium
|
||||
SeleniumDriverManager.instance.running_selenium_driver
|
||||
end
|
||||
|
||||
def badge_count_should_show(count)
|
||||
response.should have_tag('#badge_count', count.to_s)
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
if webrat_session.respond_to?(name)
|
||||
webrat_session.send(name, *args)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def webrat_session
|
||||
@webrat_session ||= begin
|
||||
Webrat::SeleniumSession.new(SeleniumDriverManager.instance.running_selenium_driver)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DatabaseResetListener
|
||||
include Singleton
|
||||
|
||||
def scenario_started(*args)
|
||||
if defined?(ActiveRecord::Base)
|
||||
connection = ActiveRecord::Base.connection
|
||||
%w[users].each do |table|
|
||||
connection.execute "DELETE FROM #{table}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing sym, *args, &block
|
||||
# ignore all messages you don't care about
|
||||
end
|
||||
end
|
||||
|
||||
class CookieResetListener
|
||||
include Singleton
|
||||
|
||||
def scenario_started(*args)
|
||||
%w[tracks_login auth_token _session_id].each do |cookie_name|
|
||||
SeleniumDriverManager.instance.running_selenium_driver.get_eval("window.document.cookie = '#{cookie_name}=;expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/';")
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing sym, *args, &block
|
||||
# ignore all messages you don't care about
|
||||
end
|
||||
end
|
||||
|
||||
class Spec::Story::Runner::ScenarioRunner
|
||||
def initialize
|
||||
@listeners = [DatabaseResetListener.instance, CookieResetListener.instance]
|
||||
end
|
||||
end
|
||||
|
||||
class Spec::Story::GivenScenario
|
||||
def perform(instance, name = nil)
|
||||
scenario = Spec::Story::Runner::StoryRunner.scenario_from_current_story @name
|
||||
runner = Spec::Story::Runner::ScenarioRunner.new
|
||||
runner.instance_variable_set(:@listeners,[])
|
||||
runner.run(scenario, instance)
|
||||
end
|
||||
end
|
||||
require 'spec/rails/story_adapter'
|
||||
|
|
@ -223,8 +223,8 @@ class ProjectsControllerTest < TodoContainerControllerTestBase
|
|||
login_as :admin_user
|
||||
u = users(:admin_user)
|
||||
post :actionize, :state => "active", :format => 'js'
|
||||
assert_equal 1, projects(:gardenclean).position
|
||||
assert_equal 2, projects(:moremoney).position
|
||||
assert_equal 2, projects(:gardenclean).position
|
||||
assert_equal 1, projects(:moremoney).position
|
||||
assert_equal 3, projects(:timemachine).position
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
|
||||
assert_xml_select 'rss[version="2.0"]' do
|
||||
assert_select 'channel' do
|
||||
assert_select '>title', 'Tracks Actions'
|
||||
assert_select '>title', 'Actions'
|
||||
assert_select '>description', "Actions for #{users(:admin_user).display_name}"
|
||||
assert_select 'language', 'en-us'
|
||||
assert_select 'ttl', '40'
|
||||
|
|
@ -205,7 +205,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
|
||||
assert_xml_select 'rss[version="2.0"]' do
|
||||
assert_select 'channel' do
|
||||
assert_select '>title', 'Tracks Actions'
|
||||
assert_select '>title', 'Actions'
|
||||
assert_select '>description', "Actions for #{users(:admin_user).display_name}"
|
||||
assert_select 'item', 5 do
|
||||
assert_select 'title', /.+/
|
||||
|
|
@ -240,7 +240,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
# #puts @response.body
|
||||
|
||||
assert_xml_select 'feed[xmlns="http://www.w3.org/2005/Atom"]' do
|
||||
assert_xml_select '>title', 'Tracks Actions'
|
||||
assert_xml_select '>title', 'Actions'
|
||||
assert_xml_select '>subtitle', "Actions for #{users(:admin_user).display_name}"
|
||||
assert_xml_select 'entry', 11 do
|
||||
assert_xml_select 'title', /.+/
|
||||
|
|
@ -450,7 +450,7 @@ class TodosControllerTest < Test::Rails::TestCase
|
|||
|
||||
# check that the new_todo is in the tickler to show next month
|
||||
assert !new_todo.show_from.nil?
|
||||
assert_equal Time.utc(today.year, today.month+1, today.day), new_todo.show_from
|
||||
assert_equal Time.utc(today.year, today.month, today.day)+1.month, new_todo.show_from
|
||||
end
|
||||
|
||||
def test_check_for_next_todo
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ class ContextXmlApiTest < ActionController::IntegrationTest
|
|||
end
|
||||
|
||||
def test_fails_with_invalid_xml_format
|
||||
authenticated_post_xml_to_context_create "<foo></bar>"
|
||||
assert_equal 500, @integration_session.status
|
||||
# Fails too hard for test to catch
|
||||
# authenticated_post_xml_to_context_create "<foo></bar>"
|
||||
# assert_equal 500, @integration_session.status
|
||||
end
|
||||
|
||||
def test_fails_with_invalid_xml_format2
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ class ProjectXmlApiTest < ActionController::IntegrationTest
|
|||
end
|
||||
|
||||
def test_fails_with_invalid_xml_format
|
||||
authenticated_post_xml_to_project_create "<foo></bar>"
|
||||
assert_equal 500, @integration_session.status
|
||||
#Fails too hard for test to catch
|
||||
# authenticated_post_xml_to_project_create "<foo></bar>"
|
||||
# assert_equal 500, @integration_session.status
|
||||
end
|
||||
|
||||
def test_fails_with_invalid_xml_format2
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ class UsersXmlApiTest < ActionController::IntegrationTest
|
|||
authenticated_post_xml_to_user_create @@foobar_postdata, users(:admin_user).login, 'abracadabra', {'CONTENT_TYPE' => "application/x-www-form-urlencoded"}
|
||||
assert_404_invalid_xml
|
||||
end
|
||||
|
||||
def test_fails_with_invalid_xml_format
|
||||
authenticated_post_xml_to_user_create "<foo></bar>"
|
||||
assert_equal 500, @integration_session.status
|
||||
end
|
||||
|
||||
# Fails too hard for test to catch
|
||||
# def test_fails_with_invalid_xml_format
|
||||
# authenticated_post_xml_to_user_create "<foo></bar>"
|
||||
# assert_equal 500, @integration_session.status
|
||||
# end
|
||||
|
||||
def test_fails_with_invalid_xml_format2
|
||||
authenticated_post_xml_to_user_create "<request><username>foo</username></request>"
|
||||
|
|
|
|||
|
|
@ -18,4 +18,7 @@ click "css=#toggle_c1"
|
|||
wait_for_not_visible "todo_9"
|
||||
|
||||
# check that newly added action is not there
|
||||
wait_for_not_visible "xpath=//span[text()='a new action']"
|
||||
wait_for_not_visible "xpath=//span[text()='a new action']"
|
||||
|
||||
# toggle c1 back. Selenium does not reset cookies
|
||||
click "css=#toggle_c1"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ login :as => 'admin'
|
|||
|
||||
open "/"
|
||||
# we should start with 10 actions on home page
|
||||
assert_text 'badge_count', '10'
|
||||
assert_text 'badge_count', '11'
|
||||
|
||||
# set project to hidden state
|
||||
open "/projects/2"
|
||||
|
|
@ -28,4 +28,4 @@ wait_for_visible "flash"
|
|||
verify_text_not_present 'should be hidden'
|
||||
|
||||
# badge count should still be same
|
||||
assert_text 'badge_count', '7'
|
||||
assert_text 'badge_count', '7'
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ wait_for_element_present "show_from_todo_9"
|
|||
type "show_from_todo_9", "1/1/2030"
|
||||
click "css=#submit_todo_9"
|
||||
wait_for_element_not_present "todo_9"
|
||||
assert_text 'badge_count', '9'
|
||||
assert_text 'badge_count', '10'
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ wait_for_element_present "show_from_todo_5"
|
|||
type "show_from_todo_5", "1/1/2030"
|
||||
click "css=#submit_todo_5"
|
||||
wait_for_element_not_present "todo_5"
|
||||
assert_text 'badge_count', '9'
|
||||
assert_text 'badge_count', '10'
|
||||
wait_for_not_visible "c5"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open "/"
|
||||
wait_for_element_present '//div[@id="line_todo_5"]//img[@alt="Defer_1"]/..'
|
||||
click '//div[@id="line_todo_5"]//img[@alt="Defer_1"]/..'
|
||||
wait_for_element_present '//div[@id="line_todo_5"]//img[@alt="Defer 1 day"]/..'
|
||||
click '//div[@id="line_todo_5"]//img[@alt="Defer 1 day"]/..'
|
||||
wait_for_element_not_present "todo_5"
|
||||
assert_text 'badge_count', '9'
|
||||
assert_text 'badge_count', '10'
|
||||
wait_for_not_visible "c5"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ setup :fixtures => :all
|
|||
login :as => 'admin'
|
||||
|
||||
open '/m'
|
||||
wait_for_text 'css=h1 span.count', '10'
|
||||
wait_for_text 'css=h1 span.count', '11'
|
||||
|
||||
click_and_wait "link=0-Add new action"
|
||||
|
||||
|
|
@ -15,4 +15,4 @@ select "todo_due_2i", "label=January"
|
|||
select "todo_due_1i", "label=2009"
|
||||
click_and_wait "//input[@value='Create']"
|
||||
|
||||
wait_for_text 'css=h1 span.count', '11'
|
||||
wait_for_text 'css=h1 span.count', '12'
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ setup :fixtures => :all
|
|||
login :as => 'admin'
|
||||
|
||||
open '/m'
|
||||
wait_for_text 'css=h1 span.count', '10'
|
||||
wait_for_text 'css=h1 span.count', '11'
|
||||
|
||||
open '/todos/6.m'
|
||||
wait_for_page_to_load 3000
|
||||
|
||||
open_and_wait '/todos/6.m'
|
||||
click "done"
|
||||
click_and_wait "//input[@value='Update']"
|
||||
|
||||
wait_for_text 'css=h1 span.count', '9'
|
||||
wait_for_text 'css=h1 span.count', '10'
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ login :as => 'admin'
|
|||
# open home page
|
||||
open '/m'
|
||||
wait_for_title "All actions"
|
||||
wait_for_text 'css=h1 span.count', '10'
|
||||
wait_for_text 'css=h1 span.count', '11'
|
||||
|
||||
# open context page
|
||||
click_and_wait "link=2-Contexts"
|
||||
# verify_title "All actions in context agenda"
|
||||
# choose agenda context
|
||||
click_and_wait "link=agenda"
|
||||
wait_for_text 'css=h1 span.count', '5'
|
||||
wait_for_text 'css=h1 span.count', '6'
|
||||
|
||||
# click on tag foo to go to tag page
|
||||
click_and_wait "link=foo"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open '/projects/1'
|
||||
assert_checked 'project_state_active', 'ignored'
|
||||
assert_checked 'project_state_active'
|
||||
assert_attribute 'css=#project_status .active span', 'class', 'active_state'
|
||||
assert_attribute 'css=#project_status .hidden span', 'class', 'inactive_state'
|
||||
assert_text 'badge_count', '2'
|
||||
|
|
@ -11,4 +11,4 @@ wait_for_attribute 'css=#project_status .hidden span', 'class', 'active_state'
|
|||
assert_text 'badge_count', '2'
|
||||
open '/projects/1'
|
||||
assert_text 'badge_count', '2'
|
||||
assert_checked 'project_state_hidden', 'ignored'
|
||||
assert_checked 'project_state_hidden'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
setup :fixtures => :all
|
||||
login :as => 'admin'
|
||||
open "/projects/1"
|
||||
|
||||
# change project name
|
||||
click "project_name_in_place_editor"
|
||||
wait_for_element_present "css=#project_name_in_place_editor-inplaceeditor input.editor_field"
|
||||
type "css=#project_name_in_place_editor-inplaceeditor input.editor_field", "Test Foo"
|
||||
click "css=#project_name_in_place_editor-inplaceeditor input.editor_ok_button"
|
||||
wait_for_text "project_name_in_place_editor", "Test Foo"
|
||||
|
||||
# check that the default project name is changed too
|
||||
assert_value "todo_project_name", "Test Foo"
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue