Merge branch 'master' of git://github.com/bsag/tracks

This commit is contained in:
Reinier Balt 2009-02-15 22:10:02 +01:00
commit 765d3440d7
22 changed files with 765 additions and 715 deletions

2
README
View file

@ -8,7 +8,7 @@
* Mailing list: http://lists.rousette.org.uk/mailman/listinfo/tracks-discuss * Mailing list: http://lists.rousette.org.uk/mailman/listinfo/tracks-discuss
* Original developer: bsag (http://www.rousette.org.uk/) * Original developer: bsag (http://www.rousette.org.uk/)
* Contributors: http://getontracks.org/wiki/Tracks/Contributing/Contributors * Contributors: http://getontracks.org/wiki/Tracks/Contributing/Contributors
* Version: 1.7 * Version: 1.7RC2
* Copyright: (cc) 2004-2008 rousette.org.uk. * Copyright: (cc) 2004-2008 rousette.org.uk.
* License: GNU GPL * License: GNU GPL

View file

@ -1,268 +1,266 @@
# The filters added to this controller will be run for all controllers in the # The filters added to this controller will be run for all controllers in the
# application. Likewise will all the methods added be available for all # application. Likewise will all the methods added be available for all
# controllers. # controllers.
require_dependency "login_system" require_dependency "login_system"
require_dependency "tracks/source_view" require_dependency "tracks/source_view"
require "redcloth" require "redcloth"
require 'date' require 'date'
require 'time' require 'time'
# Commented the following line because of #744. It prevented rake db:migrate to # Commented the following line because of #744. It prevented rake db:migrate to
# run because this tag went looking for the taggings table that did not exist # run because this tag went looking for the taggings table that did not exist
# when you feshly create a new database Old comment: We need this in development # when you feshly create a new database Old comment: We need this in development
# mode, or you get 'method missing' errors # mode, or you get 'method missing' errors
# #
# Tag # Tag
class CannotAccessContext < RuntimeError; end class CannotAccessContext < RuntimeError; end
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
protect_from_forgery :secret => SITE_CONFIG['salt'] protect_from_forgery :secret => SITE_CONFIG['salt']
helper :application helper :application
include LoginSystem include LoginSystem
helper_method :current_user, :prefs helper_method :current_user, :prefs
layout proc{ |controller| controller.mobile? ? "mobile" : "standard" } layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
before_filter :set_session_expiration before_filter :set_session_expiration
before_filter :set_time_zone before_filter :set_time_zone
prepend_before_filter :login_required prepend_before_filter :login_required
prepend_before_filter :enable_mobile_content_negotiation prepend_before_filter :enable_mobile_content_negotiation
after_filter :set_charset after_filter :set_charset
include ActionView::Helpers::TextHelper
include ActionView::Helpers::SanitizeHelper
include ActionView::Helpers::TextHelper extend ActionView::Helpers::SanitizeHelper::ClassMethods
include ActionView::Helpers::SanitizeHelper helper_method :format_date, :markdown
extend ActionView::Helpers::SanitizeHelper::ClassMethods
helper_method :format_date, :markdown # By default, sets the charset to UTF-8 if it isn't already set
def set_charset
# By default, sets the charset to UTF-8 if it isn't already set headers["Content-Type"] ||= "text/html; charset=UTF-8"
def set_charset end
headers["Content-Type"] ||= "text/html; charset=UTF-8"
end def set_session_expiration
# http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
def set_session_expiration unless session == nil
# http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions return if @controller_name == 'feed' or session['noexpiry'] == "on"
unless session == nil # If the method is called by the feed controller (which we don't have
return if @controller_name == 'feed' or session['noexpiry'] == "on" # under session control) or if we checked the box to keep logged in on
# If the method is called by the feed controller (which we don't have # login don't set the session expiry time.
# under session control) or if we checked the box to keep logged in on if session
# login don't set the session expiry time. # Get expiry time (allow ten seconds window for the case where we have
if session # none)
# Get expiry time (allow ten seconds window for the case where we have expiry_time = session['expiry_time'] || Time.now + 10
# none) if expiry_time < Time.now
expiry_time = session['expiry_time'] || Time.now + 10 # Too late, matey... bang goes your session!
if expiry_time < Time.now reset_session
# Too late, matey... bang goes your session! else
reset_session # Okay, you get another hour
else session['expiry_time'] = Time.now + (60*60)
# Okay, you get another hour end
session['expiry_time'] = Time.now + (60*60) end
end end
end end
end
end def render_failure message, status = 404
render :text => message, :status => status
def render_failure message, status = 404 end
render :text => message, :status => status
end # def rescue_action(exception)
# log_error(exception) if logger
# def rescue_action(exception) # respond_to do |format|
# log_error(exception) if logger # format.html do
# respond_to do |format| # notify :warning, "An error occurred on the server."
# format.html do # render :action => "index"
# notify :warning, "An error occurred on the server." # end
# render :action => "index" # format.js { render :action => 'error' }
# end # format.xml { render :text => 'An error occurred on the server.' + $! }
# format.js { render :action => 'error' } # end
# format.xml { render :text => 'An error occurred on the server.' + $! } # end
# end
# end # 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
# Returns a count of next actions in the given context or project The result # actions or multiple actions
# 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")
# count = count_undone_todos(todos_parent)
def count_undone_todos_phrase(todos_parent, string="actions") if count == 1
count = count_undone_todos(todos_parent) word = string.singularize
if count == 1 else
word = string.singularize word = string.pluralize
else end
word = string.pluralize return count.to_s + "&nbsp;" + word
end end
return count.to_s + "&nbsp;" + word
end def count_undone_todos(todos_parent)
if todos_parent.nil?
def count_undone_todos(todos_parent) count = 0
if todos_parent.nil? elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
count = 0 count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
elsif (todos_parent.is_a?(Project) && todos_parent.hidden?) else
count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]" count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
else end
count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]" count || 0
end end
count || 0
end # Convert a date object to the format specified in the user's preferences in
# config/settings.yml
# Convert a date object to the format specified in the user's preferences in #
# config/settings.yml def format_date(date)
# if date
def format_date(date) date_format = prefs.date_format
if date formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}")
date_format = prefs.date_format else
formatted_date = date.in_time_zone(prefs.time_zone).strftime("#{date_format}") formatted_date = ''
else end
formatted_date = '' formatted_date
end end
formatted_date
end # Uses RedCloth to transform text using either Textile or Markdown Need to
# require redcloth above RedCloth 3.0 or greater is needed to use Markdown,
# Uses RedCloth to transform text using either Textile or Markdown Need to # otherwise it only handles Textile
# require redcloth above RedCloth 3.0 or greater is needed to use Markdown, #
# otherwise it only handles Textile def markdown(text)
# RedCloth.new(text).to_html
def markdown(text) end
RedCloth.new(text).to_html
end def build_default_project_context_name_map(projects)
Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
def build_default_project_context_name_map(projects) end
Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
end # Here's the concept behind this "mobile content negotiation" hack: In
# addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
# Here's the concept behind this "mobile content negotiation" hack: In # 'mobile' version designed to be suitablef or use from a phone or PDA. It
# addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature # makes some sense that tne pages of that mobile version are simply alternate
# 'mobile' version designed to be suitablef or use from a phone or PDA. It # representations of the same Todo resources. The implementation goal was to
# makes some sense that tne pages of that mobile version are simply alternate # treat mobile as another format and be able to use respond_to to render both
# representations of the same Todo resources. The implementation goal was to # versions. Unfortunately, I ran into a lot of trouble simply registering a
# treat mobile as another format and be able to use respond_to to render both # new mime type 'text/html' with format :m because :html already is linked to
# versions. Unfortunately, I ran into a lot of trouble simply registering a # that mime type and the new registration was forcing all html requests to be
# new mime type 'text/html' with format :m because :html already is linked to # rendered in the mobile view. The before_filter and after_filter hackery
# that mime type and the new registration was forcing all html requests to be # below accomplishs that implementation goal by using a 'fake' mime type
# rendered in the mobile view. The before_filter and after_filter hackery # during the processing and then setting it to 'text/html' in an
# below accomplishs that implementation goal by using a 'fake' mime type # 'after_filter' -LKM 2007-04-01
# during the processing and then setting it to 'text/html' in an def mobile?
# 'after_filter' -LKM 2007-04-01 return params[:format] == 'm'
def mobile? end
return params[:format] == 'm'
end def enable_mobile_content_negotiation
if mobile?
def enable_mobile_content_negotiation request.format = :m
if mobile? end
request.format = :m end
end
end def create_todo_from_recurring_todo(rt, date=nil)
# create todo and initialize with data from recurring_todo rt
def create_todo_from_recurring_todo(rt, date=nil) todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id})
# 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}) # set dates
todo.recurring_todo_id = rt.id
# set dates todo.due = rt.get_due_date(date)
todo.recurring_todo_id = rt.id
todo.due = rt.get_due_date(date) show_from_date = rt.get_show_from_date(date)
if show_from_date.nil?
show_from_date = rt.get_show_from_date(date) todo.show_from=nil
if show_from_date.nil? else
todo.show_from=nil # make sure that show_from is not in the past
else todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
# make sure that show_from is not in the past end
todo.show_from = show_from_date < Time.zone.now ? nil : show_from_date
end saved = todo.save
if saved
saved = todo.save todo.tag_with(rt.tag_list)
if saved todo.tags.reload
todo.tag_with(rt.tag_list) end
todo.tags.reload
end # increate number of occurences created from recurring todo
rt.inc_occurences
# increate number of occurences created from recurring todo
rt.inc_occurences # mark recurring todo complete if there are no next actions left
checkdate = todo.due.nil? ? todo.show_from : todo.due
# mark recurring todo complete if there are no next actions left rt.toggle_completion! unless rt.has_next_todo(checkdate)
checkdate = todo.due.nil? ? todo.show_from : todo.due
rt.toggle_completion! unless rt.has_next_todo(checkdate) return saved ? todo : nil
end
return saved ? todo : nil
end protected
protected def admin_login_required
unless User.find_by_id_and_is_admin(session['user_id'], true)
def admin_login_required render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401
unless User.find_by_id_and_is_admin(session['user_id'], true) return false
render :text => "401 Unauthorized: Only admin users are allowed access to this function.", :status => 401 end
return false end
end
end def redirect_back_or_home
respond_to do |format|
def redirect_back_or_home format.html { redirect_back_or_default home_url }
respond_to do |format| format.m { redirect_back_or_default mobile_url }
format.html { redirect_back_or_default home_url } end
format.m { redirect_back_or_default mobile_url } end
end
end def boolean_param(param_name)
return false if param_name.blank?
def boolean_param(param_name) s = params[param_name]
return false if param_name.blank? return false if s.blank? || s == false || s =~ /^false$/i
s = params[param_name] return true if s == true || s =~ /^true$/i
return false if s.blank? || s == false || s =~ /^false$/i raise ArgumentError.new("invalid value for Boolean: \"#{s}\"")
return true if s == true || s =~ /^true$/i end
raise ArgumentError.new("invalid value for Boolean: \"#{s}\"")
end def self.openid_enabled?
Tracks::Config.openid_enabled?
def self.openid_enabled? end
Tracks::Config.openid_enabled?
end def openid_enabled?
self.class.openid_enabled?
def openid_enabled? end
self.class.openid_enabled?
end private
private def parse_date_per_user_prefs( s )
prefs.parse_date(s)
def parse_date_per_user_prefs( s ) end
prefs.parse_date(s)
end def init_data_for_sidebar
@completed_projects = current_user.projects.completed
def init_data_for_sidebar @hidden_projects = current_user.projects.hidden
@completed_projects = current_user.projects.completed @active_projects = current_user.projects.active
@hidden_projects = current_user.projects.hidden
@active_projects = current_user.projects.active @active_contexts = current_user.contexts.active
@hidden_contexts = current_user.contexts.hidden
@active_contexts = current_user.contexts.active
@hidden_contexts = current_user.contexts.hidden init_not_done_counts
if prefs.show_hidden_projects_in_sidebar
init_not_done_counts init_project_hidden_todo_counts(['project'])
if prefs.show_hidden_projects_in_sidebar end
init_project_hidden_todo_counts(['project']) end
end
end def init_not_done_counts(parents = ['project','context'])
parents.each do |parent|
def init_not_done_counts(parents = ['project','context']) eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)")
parents.each do |parent| end
eval("@#{parent}_not_done_counts = @#{parent}_not_done_counts || current_user.todos.active.count(:group => :#{parent}_id)") end
end
end def init_project_hidden_todo_counts(parents = ['project','context'])
parents.each do |parent|
def init_project_hidden_todo_counts(parents = ['project','context']) 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)")
parents.each do |parent| end
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
end # Set the contents of the flash message from a controller Usage: notify
# :warning, "This is the message" Sets the flash of type 'warning' to "This is
# Set the contents of the flash message from a controller Usage: notify # the message"
# :warning, "This is the message" Sets the flash of type 'warning' to "This is def notify(type, message)
# the message" flash[type] = message
def notify(type, message) logger.error("ERROR: #{message}") if type == :error
flash[type] = message end
logger.error("ERROR: #{message}") if type == :error
end def set_time_zone
Time.zone = current_user.prefs.time_zone if logged_in?
def set_time_zone end
Time.zone = current_user.prefs.time_zone if logged_in?
end end
end

View file

@ -21,8 +21,22 @@ class DataController < ApplicationController
all_tables['todos'] = current_user.todos.find(:all) all_tables['todos'] = current_user.todos.find(:all)
all_tables['contexts'] = current_user.contexts.find(:all) all_tables['contexts'] = current_user.contexts.find(:all)
all_tables['projects'] = current_user.projects.find(:all) all_tables['projects'] = current_user.projects.find(:all)
all_tables['tags'] = current_user.tags.find(:all)
all_tables['taggings'] = current_user.taggings.find(:all) tags = Tag.find_by_sql([
"SELECT tags.* "+
"FROM tags, taggings, todos "+
"WHERE todos.user_id=? "+
"AND tags.id = taggings.tag_id " +
"AND taggings.taggable_id = todos.id ", current_user.id])
all_tables['tags'] = tags
taggings = Tagging.find_by_sql([
"SELECT taggings.* "+
"FROM taggings, todos "+
"WHERE todos.user_id=? "+
"AND taggings.taggable_id = todos.id ", current_user.id])
all_tables['taggings'] = taggings
all_tables['notes'] = current_user.notes.find(:all) all_tables['notes'] = current_user.notes.find(:all)
all_tables['recurring_todos'] = current_user.recurring_todos.find(:all) all_tables['recurring_todos'] = current_user.recurring_todos.find(:all)

View file

@ -175,7 +175,7 @@ class ProjectsController < ApplicationController
def order def order
project_ids = params["list-active-projects"] || params["list-hidden-projects"] || params["list-completed-projects"] project_ids = params["list-active-projects"] || params["list-hidden-projects"] || params["list-completed-projects"]
projects = current_user.projects.update_positions( project_ids ) @projects = current_user.projects.update_positions( project_ids )
render :nothing => true render :nothing => true
rescue rescue
notify :error, $! notify :error, $!

View file

@ -1,268 +1,267 @@
class RecurringTodosController < ApplicationController class RecurringTodosController < ApplicationController
helper :todos, :recurring_todos helper :todos, :recurring_todos
append_before_filter :init, :only => [:index, :new, :edit] append_before_filter :init, :only => [:index, :new, :edit]
append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update] append_before_filter :get_recurring_todo_from_param, :only => [:destroy, :toggle_check, :toggle_star, :edit, :update]
def index def index
find_and_inactivate find_and_inactivate
@recurring_todos = current_user.recurring_todos.active @recurring_todos = current_user.recurring_todos.active
@completed_recurring_todos = current_user.recurring_todos.completed @completed_recurring_todos = current_user.recurring_todos.completed
@no_recurring_todos = @recurring_todos.size == 0 @no_recurring_todos = @recurring_todos.size == 0
@no_completed_recurring_todos = @completed_recurring_todos.size == 0 @no_completed_recurring_todos = @completed_recurring_todos.size == 0
@count = @recurring_todos.size @count = @recurring_todos.size
@page_title = "TRACKS::Recurring Actions" @page_title = "TRACKS::Recurring Actions"
end end
def new def new
end end
def show def show
end end
def edit def edit
respond_to do |format| respond_to do |format|
format.js format.js
end end
end end
def update def update
@recurring_todo.tag_with(params[:tag_list]) if params[:tag_list] # TODO: write tests for updating
@original_item_context_id = @recurring_todo.context_id @recurring_todo.tag_with(params[:tag_list]) if params[:tag_list]
@original_item_project_id = @recurring_todo.project_id @original_item_context_id = @recurring_todo.context_id
@original_item_project_id = @recurring_todo.project_id
# we needed to rename the recurring_period selector in the edit form because
# the form for a new recurring todo and the edit form are on the same page. # we needed to rename the recurring_period selector in the edit form because
# Same goes for start_from and end_date # the form for a new recurring todo and the edit form are on the same page.
params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period'] # Same goes for start_from and end_date
params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date']) params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period']
params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from']) params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date'])
params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from'])
# update project
if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil? # update project
if params['project_name'] == 'None' if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil?
project = Project.null_object if params['project_name'] == 'None'
else project = Project.null_object
project = current_user.projects.find_by_name(params['project_name'].strip) else
unless project project = current_user.projects.find_by_name(params['project_name'].strip)
project = current_user.projects.build unless project
project.name = params['project_name'].strip project = current_user.projects.build
project.save project.name = params['project_name'].strip
@new_project_created = true project.save
end @new_project_created = true
end end
params["recurring_todo"]["project_id"] = project.id end
end params["recurring_todo"]["project_id"] = project.id
end
# update context
if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank? # update context
context = current_user.contexts.find_by_name(params['context_name'].strip) if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank?
unless context context = current_user.contexts.find_by_name(params['context_name'].strip)
context = current_user.contexts.build unless context
context.name = params['context_name'].strip context = current_user.contexts.build
context.save context.name = params['context_name'].strip
@new_context_created = true context.save
end @new_context_created = true
params["recurring_todo"]["context_id"] = context.id end
end params["recurring_todo"]["context_id"] = context.id
end
params["recurring_todo"]["weekly_return_monday"]=' ' if params["recurring_todo"]["weekly_return_monday"].nil?
params["recurring_todo"]["weekly_return_tuesday"]=' ' if params["recurring_todo"]["weekly_return_tuesday"].nil? # make sure that we set weekly_return_xxx to empty (space) when they are
params["recurring_todo"]["weekly_return_wednesday"]=' ' if params["recurring_todo"]["weekly_return_wednesday"].nil? # not checked (and thus not present in params["recurring_todo"])
params["recurring_todo"]["weekly_return_thursday"]=' ' if params["recurring_todo"]["weekly_return_thursday"].nil? %w{monday tuesday wednesday thursday friday saturday sunday}.each do |day|
params["recurring_todo"]["weekly_return_friday"]=' ' if params["recurring_todo"]["weekly_return_friday"].nil? params["recurring_todo"]["weekly_return_"+day]=' ' if params["recurring_todo"]["weekly_return_"+day].nil?
params["recurring_todo"]["weekly_return_saturday"]=' ' if params["recurring_todo"]["weekly_return_saturday"].nil? end
params["recurring_todo"]["weekly_return_sunday"]=' ' if params["recurring_todo"]["weekly_return_sunday"].nil?
@saved = @recurring_todo.update_attributes params["recurring_todo"]
@saved = @recurring_todo.update_attributes params["recurring_todo"]
respond_to do |format|
respond_to do |format| format.js
format.js end
end end
end
def create
def create p = RecurringTodoCreateParamsHelper.new(params)
p = RecurringTodoCreateParamsHelper.new(params) p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date'])
p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date']) p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from'])
p.attributes['start_from']=parse_date_per_user_prefs(p.attributes['start_from'])
@recurring_todo = current_user.recurring_todos.build(p.selector_attributes)
@recurring_todo = current_user.recurring_todos.build(p.selector_attributes) @recurring_todo.update_attributes(p.attributes)
@recurring_todo.update_attributes(p.attributes)
if p.project_specified_by_name?
if p.project_specified_by_name? project = current_user.projects.find_or_create_by_name(p.project_name)
project = current_user.projects.find_or_create_by_name(p.project_name) @new_project_created = project.new_record_before_save?
@new_project_created = project.new_record_before_save? @recurring_todo.project_id = project.id
@recurring_todo.project_id = project.id end
end
if p.context_specified_by_name?
if p.context_specified_by_name? context = current_user.contexts.find_or_create_by_name(p.context_name)
context = current_user.contexts.find_or_create_by_name(p.context_name) @new_context_created = context.new_record_before_save?
@new_context_created = context.new_record_before_save? @recurring_todo.context_id = context.id
@recurring_todo.context_id = context.id end
end
@recurring_saved = @recurring_todo.save
@recurring_saved = @recurring_todo.save unless (@recurring_saved == false) || p.tag_list.blank?
unless (@recurring_saved == false) || p.tag_list.blank? @recurring_todo.tag_with(p.tag_list)
@recurring_todo.tag_with(p.tag_list) @recurring_todo.tags.reload
@recurring_todo.tags.reload end
end
if @recurring_saved
if @recurring_saved @message = "The recurring todo was saved"
@message = "The recurring todo was saved" @todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false
@todo_saved = create_todo_from_recurring_todo(@recurring_todo).nil? == false if @todo_saved
if @todo_saved @message += " / created a new todo"
@message += " / created a new todo" else
else @message += " / did not create todo"
@message += " / did not create todo" end
end @count = current_user.recurring_todos.active.count
@count = current_user.recurring_todos.active.count else
else @message = "Error saving recurring todo"
@message = "Error saving recurring todo" end
end
respond_to do |format|
respond_to do |format| format.js
format.js end
end end
end
def destroy
def destroy
# remove all references to this recurring todo
# remove all references to this recurring todo @todos = @recurring_todo.todos
@todos = @recurring_todo.todos @number_of_todos = @todos.size
@number_of_todos = @todos.size @todos.each do |t|
@todos.each do |t| t.recurring_todo_id = nil
t.recurring_todo_id = nil t.save
t.save end
end
# delete the recurring todo
# delete the recurring todo @saved = @recurring_todo.destroy
@saved = @recurring_todo.destroy @remaining = current_user.recurring_todos.count
@remaining = current_user.recurring_todos.count
respond_to do |format|
respond_to do |format|
format.html do
format.html do if @saved
if @saved notify :notice, "Successfully deleted recurring action", 2.0
notify :notice, "Successfully deleted recurring action", 2.0 redirect_to :action => 'index'
redirect_to :action => 'index' else
else notify :error, "Failed to delete the recurring action", 2.0
notify :error, "Failed to delete the recurring action", 2.0 redirect_to :action => 'index'
redirect_to :action => 'index' end
end end
end
format.js do
format.js do render
render end
end end
end end
end
def toggle_check
def toggle_check @saved = @recurring_todo.toggle_completion!
@saved = @recurring_todo.toggle_completion!
@count = current_user.recurring_todos.active.count
@count = current_user.recurring_todos.active.count @remaining = @count
@remaining = @count
if @recurring_todo.active?
if @recurring_todo.active? @remaining = current_user.recurring_todos.completed.count
@remaining = current_user.recurring_todos.completed.count
# from completed back to active -> check if there is an active todo
# from completed back to active -> check if there is an active todo # current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]})
# current_user.todos.count(:all, {:conditions => ["state = ? AND recurring_todo_id = ?", 'active',params[:id]]}) @active_todos = @recurring_todo.todos.active.count
@active_todos = @recurring_todo.todos.active.count # create todo if there is no active todo belonging to the activated
# create todo if there is no active todo belonging to the activated # recurring_todo
# recurring_todo @new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0
@new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0 end
end
respond_to do |format|
respond_to do |format| format.js
format.js end
end end
end
def toggle_star
def toggle_star @recurring_todo.toggle_star!
@recurring_todo.toggle_star! @saved = @recurring_todo.save!
@saved = @recurring_todo.save! respond_to do |format|
respond_to do |format| format.js
format.js end
end end
end
class RecurringTodoCreateParamsHelper
class RecurringTodoCreateParamsHelper
def initialize(params)
def initialize(params) @params = params['request'] || params
@params = params['request'] || params @attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo']
@attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo']
# make sure all selectors (recurring_period, recurrence_selector,
# make sure all selectors (recurring_period, recurrence_selector, # daily_selector, monthly_selector and yearly_selector) are first in hash
# daily_selector, monthly_selector and yearly_selector) are first in hash # so that they are processed first by the model
# so that they are processed first by the model @selector_attributes = {
@selector_attributes = { 'recurring_period' => @attributes['recurring_period'],
'recurring_period' => @attributes['recurring_period'], 'daily_selector' => @attributes['daily_selector'],
'daily_selector' => @attributes['daily_selector'], 'monthly_selector' => @attributes['monthly_selector'],
'monthly_selector' => @attributes['monthly_selector'], 'yearly_selector' => @attributes['yearly_selector']
'yearly_selector' => @attributes['yearly_selector'] }
} end
end
def attributes
def attributes @attributes
@attributes end
end
def selector_attributes
def selector_attributes return @selector_attributes
return @selector_attributes end
end
def project_name
def project_name @params['project_name'].strip unless @params['project_name'].nil?
@params['project_name'].strip unless @params['project_name'].nil? end
end
def context_name
def context_name @params['context_name'].strip unless @params['context_name'].nil?
@params['context_name'].strip unless @params['context_name'].nil? end
end
def tag_list
def tag_list @params['tag_list']
@params['tag_list'] end
end
def project_specified_by_name?
def project_specified_by_name? return false unless @attributes['project_id'].blank?
return false unless @attributes['project_id'].blank? return false if project_name.blank?
return false if project_name.blank? return false if project_name == 'None'
return false if project_name == 'None' true
true end
end
def context_specified_by_name?
def context_specified_by_name? return false unless @attributes['context_id'].blank?
return false unless @attributes['context_id'].blank? return false if context_name.blank?
return false if context_name.blank? true
true end
end
end
end
private
private
def init
def init @days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]]
@days_of_week = [ ['Sunday',0], ['Monday',1], ['Tuesday', 2], ['Wednesday',3], ['Thursday',4], ['Friday',5], ['Saturday',6]] @months_of_year = [
@months_of_year = [ ['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6],
['January',1], ['Februari',2], ['March', 3], ['April',4], ['May',5], ['June',6], ['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]]
['July',7], ['August',8], ['September',9], ['October', 10], ['November', 11], ['December',12]] @xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]]
@xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]] @projects = current_user.projects.find(:all, :include => [:default_context])
@projects = current_user.projects.find(:all, :include => [:default_context]) @contexts = current_user.contexts.find(:all)
@contexts = current_user.contexts.find(:all) @default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json end
end
def get_recurring_todo_from_param
def get_recurring_todo_from_param @recurring_todo = current_user.recurring_todos.find(params[:id])
@recurring_todo = current_user.recurring_todos.find(params[:id]) end
end
def find_and_inactivate
def find_and_inactivate # find active recurring todos without active todos and inactivate them
# find active recurring todos without active todos and inactivate them recurring_todos = current_user.recurring_todos.active
recurring_todos = current_user.recurring_todos.active recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0} end
end
end
end

View file

@ -14,8 +14,8 @@ class SearchController < ApplicationController
@found_tags = Tagging.find_by_sql([ @found_tags = Tagging.find_by_sql([
"SELECT DISTINCT tags.name as name "+ "SELECT DISTINCT tags.name as name "+
"FROM tags "+ "FROM tags "+
"LEFT JOIN taggings ON tags.id = taggings.tag_id "+ "LEFT JOIN taggings ON tags.id = taggings.tag_id "+
"LEFT JOIN todos ON taggings.taggable_id = todos.id "+ "LEFT JOIN todos ON taggings.taggable_id = todos.id "+
"WHERE todos.user_id=? "+ "WHERE todos.user_id=? "+
"AND tags.name LIKE ? ", current_user.id, terms]) "AND tags.name LIKE ? ", current_user.id, terms])

View file

@ -6,8 +6,8 @@ class Todo < ActiveRecord::Base
belongs_to :recurring_todo belongs_to :recurring_todo
named_scope :active, :conditions => { :state => 'active' } named_scope :active, :conditions => { :state => 'active' }
named_scope :not_completed, :conditions => ['NOT state = ? ', 'completed'] named_scope :not_completed, :conditions => ['NOT (state = ? )', 'completed']
named_scope :are_due, :conditions => ['NOT todos.due IS NULL'] named_scope :are_due, :conditions => ['NOT (todos.due IS NULL)']
STARRED_TAG_NAME = "starred" STARRED_TAG_NAME = "starred"

View file

@ -59,7 +59,7 @@ class User < ActiveRecord::Base
"SELECT project.id, count(todo.id) as p_count " + "SELECT project.id, count(todo.id) as p_count " +
"FROM projects as project " + "FROM projects as project " +
"LEFT OUTER JOIN todos as todo ON todo.project_id = project.id "+ "LEFT OUTER JOIN todos as todo ON todo.project_id = project.id "+
"WHERE project.user_id = ? AND NOT todo.state='completed' " + "WHERE project.user_id = ? AND NOT (todo.state='completed') " +
query_state + query_state +
" GROUP BY project.id ORDER by p_count DESC",user_id]) " GROUP BY project.id ORDER by p_count DESC",user_id])
self.update_positions(projects.map{ |p| p.id }) self.update_positions(projects.map{ |p| p.id })
@ -91,7 +91,7 @@ class User < ActiveRecord::Base
end end
has_many :completed_todos, has_many :completed_todos,
:class_name => 'Todo', :class_name => 'Todo',
:conditions => ['todos.state = ? and todos.completed_at is not null', 'completed'], :conditions => ['todos.state = ? AND NOT(todos.completed_at IS NULL)', 'completed'],
:order => 'todos.completed_at DESC', :order => 'todos.completed_at DESC',
:include => [ :project, :context ] do :include => [ :project, :context ] do
def completed_within( date ) def completed_within( date )

View file

@ -2,7 +2,7 @@ class AddUpdatedAtToTodos < ActiveRecord::Migration
def self.up def self.up
add_column :todos, :updated_at, :timestamp add_column :todos, :updated_at, :timestamp
execute 'update todos set updated_at = created_at where completed_at IS NULL' execute 'update todos set updated_at = created_at where completed_at IS NULL'
execute 'update todos set updated_at = completed_at where NOT completed_at IS NULL' execute 'update todos set updated_at = completed_at where NOT (completed_at IS NULL)'
end end
def self.down def self.down
remove_column :todos, :updated_at remove_column :todos, :updated_at

Binary file not shown.

Binary file not shown.

View file

@ -6,8 +6,6 @@
body { body {
background: #fff; background: #fff;
color: #000; color: #000;
/* width: 2.5in;
height: 4.7in;*/
font-size: 8.2pt; font-size: 8.2pt;
font-family: "Lucida Grande", "Bitstream Vera Sans", Helvetica, Verdana, Arial, sans-serif; font-family: "Lucida Grande", "Bitstream Vera Sans", Helvetica, Verdana, Arial, sans-serif;
} }
@ -18,7 +16,7 @@ img {
border:0; border:0;
} }
#navcontainer, #input_box, #footer, .big-box, .refresh, .badge, h1, .icon, #minilinks { #navcontainer, #input_box, #footer, .big-box, .refresh, .badge, h1, .icon, #minilinks, .defer-container {
display:none; display:none;
} }

View file

@ -76,31 +76,31 @@ h3 {
/* Rules for the icon links */ /* Rules for the icon links */
img.edit_item {background-image: url(../images/edit_off.png); background-repeat: no-repeat; border: none;} 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;} 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;} 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;} 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;} 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;} 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;} 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: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 {background: transparent url(/images/top_off.png) no-repeat;}
a.to_top:hover {background: transparent url(../images/top_on.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 {background: transparent url(/images/up_off.png) no-repeat;}
a.up:hover {background: transparent url(../images/up_on.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 {background: transparent url(/images/down_off.png) no-repeat;}
a.down:hover {background: transparent url(../images/down_on.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 {background: transparent url(/images/bottom_off.png) no-repeat;}
a.to_bottom:hover {background: transparent url(../images/bottom_on.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, 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;} 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;}
/* Structural divs */ /* Structural divs */
@ -180,7 +180,7 @@ a.show_notes:hover, a.link_to_notes:hover {background-image: url(../images/notes
height: 100%; height: 100%;
z-index: 102; z-index: 102;
text-align: center; text-align: center;
background-image:url("../images/trans70.png"); background-image:url("/images/trans70.png");
} }
#overlay #new-recurring-todo, #overlay #edit-recurring-todo { #overlay #new-recurring-todo, #overlay #edit-recurring-todo {
@ -997,7 +997,7 @@ ul#prefs {list-style-type: disc; margin-left: 15px;}
font-style:oblique; font-style:oblique;
} }
input.open_id { input.open_id {
background: url(../images/open-id-login-bg.gif) no-repeat; background: url(/images/open-id-login-bg.gif) no-repeat;
background-color: #fff; background-color: #fff;
background-position: 0 50%; background-position: 0 50%;
color: #000; color: #000;
@ -1146,28 +1146,28 @@ button.positive, .widgets a.positive{
color:#fff; color:#fff;
} }
.tracks__waiting { .tracks__waiting {
background-image:url('../images/waiting.gif'); background-image:url('/images/waiting.gif');
background-repeat:no-repeat; background-repeat:no-repeat;
background-position:center center; background-position:center center;
background-color:white; background-color:white;
} }
.bigWaiting { .bigWaiting {
background-image:url('../images/bigWaiting.gif'); background-image:url('/images/bigWaiting.gif');
background-repeat:no-repeat; background-repeat:no-repeat;
background-position:center 20%; background-position:center 20%;
background-color:white; background-color:white;
} }
.blackWaiting { .blackWaiting {
background-image:url('../images/blackWaiting.gif'); background-image:url('/images/blackWaiting.gif');
background-repeat:no-repeat; background-repeat:no-repeat;
background-position:center center; background-position:center center;
background-color:black; background-color:black;
} }
.bigBlackWaiting { .bigBlackWaiting {
background-image:url('../images/bigBlackWaiting.gif'); background-image:url('/images/bigBlackWaiting.gif');
background-repeat:no-repeat; background-repeat:no-repeat;
background-position:center center; background-position:center center;
background-color:black; background-color:black;

View file

@ -1,3 +1,27 @@
<%
def today
Time.zone.now.beginning_of_day.to_s(:db)
end
def next_week
1.week.from_now.beginning_of_day.to_s(:db)
end
def last_week
1.week.ago.beginning_of_day.to_s(:db)
end
def two_weeks_ago
2.weeks.ago.beginning_of_day.to_s(:db)
end
def two_weeks_hence
2.weeks.from_now.beginning_of_day.to_s(:db)
end
%>
1: 1:
id: 1 id: 1
user_id: 1 user_id: 1

View file

@ -18,4 +18,9 @@ class DataControllerTest < Test::Rails::TestCase
login_as :admin_user login_as :admin_user
get :csv_notes get :csv_notes
end end
def test_yml_export_comleted_without_error
login_as :admin_user
get :yaml_export
end
end end

View file

@ -5,4 +5,5 @@ assert_context_count_incremented do
type "todo_description", "a new action" type "todo_description", "a new action"
type "todo_context_name", "Brand new context" type "todo_context_name", "Brand new context"
click "css=#todo-form-new-action .submit_box button" click "css=#todo-form-new-action .submit_box button"
end assert_confirmation "New context \"Brand new context\" will be also created. Are you sure?"
end

View file

@ -6,4 +6,5 @@ assert_context_count_incremented do
type "todo_project_name", "pppp" type "todo_project_name", "pppp"
type "todo_context_name", "cccc" type "todo_context_name", "cccc"
click "css=#todo-form-new-action .submit_box button" click "css=#todo-form-new-action .submit_box button"
end assert_confirmation "New context \"cccc\" will be also created. Are you sure?"
end

View file

@ -5,4 +5,5 @@ assert_context_count_incremented do
type "todo_description", "a new action" type "todo_description", "a new action"
type "todo_context_name", "Brand new context" type "todo_context_name", "Brand new context"
click "css=#todo-form-new-action .submit_box button" click "css=#todo-form-new-action .submit_box button"
end assert_confirmation "New context \"Brand new context\" will be also created. Are you sure?"
end

View file

@ -7,6 +7,7 @@ assert_context_count_incremented do
type "todo_project_name", "None" type "todo_project_name", "None"
type "todo_show_from", "1/1/2030" type "todo_show_from", "1/1/2030"
click "css=#todo-form-new-action .submit_box button" click "css=#todo-form-new-action .submit_box button"
assert_confirmation "New context \"errands\" will be also created. Are you sure?"
end end
wait_for_not_visible "tickler-empty-nd" wait_for_not_visible "tickler-empty-nd"
wait_for_element_present "xpath=//div[@class='item-container'] //a[@title='01/01/2030']" wait_for_element_present "xpath=//div[@class='item-container'] //a[@title='01/01/2030']"

View file

@ -6,4 +6,5 @@ assert_context_count_incremented do
type "todo_context_name", "Brand new context" type "todo_context_name", "Brand new context"
type "todo_show_from", "1/1/2030" type "todo_show_from", "1/1/2030"
click "css=#todo-form-new-action .submit_box button" click "css=#todo-form-new-action .submit_box button"
assert_confirmation "New context \"Brand new context\" will be also created. Are you sure?"
end end

View file

@ -1,146 +1,147 @@
class BundleFu class BundleFu
class << self class << self
attr_accessor :content_store attr_accessor :content_store
def init def init
@content_store = {} @content_store = {}
end end
def bundle_files(filenames=[]) def bundle_files(filenames=[])
output = "" output = ""
filenames.each{ |filename| filenames.each{ |filename|
output << "/* --------- #{filename} --------- */ " filename_no_root = filename.sub(/^#{ActionController::Base.relative_url_root}/, '')
output << "\n" output << "/* --------- #{filename} - #{filename_no_root} --- ------ */ "
begin output << "\n"
content = (File.read(File.join(RAILS_ROOT, "public", filename))) begin
rescue content = (File.read(File.join(RAILS_ROOT, "public", filename_no_root)))
output << "/* FILE READ ERROR! */" rescue
next output << "/* FILE READ ERROR! */"
end next
end
output << (yield(filename, content)||"")
} output << (yield(filename, content)||"")
output }
end output
end
def bundle_js_files(filenames=[], options={})
output = def bundle_js_files(filenames=[], options={})
bundle_files(filenames) { |filename, content| output =
if options[:compress] bundle_files(filenames) { |filename, content|
if Object.const_defined?("Packr") if options[:compress]
content if Object.const_defined?("Packr")
else content
JSMinimizer.minimize_content(content) else
end JSMinimizer.minimize_content(content)
else end
content else
end content
} end
}
if Object.const_defined?("Packr")
# use Packr plugin (http://blog.jcoglan.com/packr/) if Object.const_defined?("Packr")
Packr.new.pack(output, options[:packr_options] || {:shrink_vars => false, :base62 => false}) # use Packr plugin (http://blog.jcoglan.com/packr/)
else Packr.new.pack(output, options[:packr_options] || {:shrink_vars => false, :base62 => false})
output else
end output
end
end
end
def bundle_css_files(filenames=[], options = {})
bundle_files(filenames) { |filename, content| def bundle_css_files(filenames=[], options = {})
BundleFu::CSSUrlRewriter.rewrite_urls(filename, content) bundle_files(filenames) { |filename, content|
} BundleFu::CSSUrlRewriter.rewrite_urls(filename, content)
end }
end end
end
self.init
self.init
module InstanceMethods
# valid options: module InstanceMethods
# :name - The name of the css and js files you wish to output # valid options:
# returns true if a regen occured. False if not. # :name - The name of the css and js files you wish to output
def bundle(options={}, &block) # returns true if a regen occured. False if not.
# allow bypassing via the querystring def bundle(options={}, &block)
session[:bundle_fu] = (params[:bundle_fu]=="true") if params.has_key?(:bundle_fu) # allow bypassing via the querystring
session[:bundle_fu] = (params[:bundle_fu]=="true") if params.has_key?(:bundle_fu)
options = {
:css_path => ($bundle_css_path || "/stylesheets/cache"), options = {
:js_path => ($bundle_js_path || "/javascripts/cache"), :css_path => ($bundle_css_path || "/stylesheets/cache"),
:name => ($bundle_default_name || "bundle"), :js_path => ($bundle_js_path || "/javascripts/cache"),
:compress => true, :name => ($bundle_default_name || "bundle"),
:bundle_fu => ( session[:bundle_fu].nil? ? ($bundle_fu.nil? ? true : $bundle_fu) : session[:bundle_fu] ) :compress => true,
}.merge(options) :bundle_fu => ( session[:bundle_fu].nil? ? ($bundle_fu.nil? ? true : $bundle_fu) : session[:bundle_fu] )
}.merge(options)
# allow them to bypass via parameter
options[:bundle_fu] = false if options[:bypass] # allow them to bypass via parameter
options[:bundle_fu] = false if options[:bypass]
paths = { :css => options[:css_path], :js => options[:js_path] }
paths = { :css => options[:css_path], :js => options[:js_path] }
content = capture(&block)
content_changed = false content = capture(&block)
content_changed = false
new_files = nil
abs_filelist_paths = [:css, :js].inject({}) { | hash, filetype | hash[filetype] = File.join(RAILS_ROOT, "public", paths[filetype], "#{options[:name]}.#{filetype}.filelist"); hash } new_files = nil
abs_filelist_paths = [:css, :js].inject({}) { | hash, filetype | hash[filetype] = File.join(RAILS_ROOT, "public", paths[filetype], "#{options[:name]}.#{filetype}.filelist"); hash }
# only rescan file list if content_changed, or if a filelist cache file is missing
unless content == BundleFu.content_store[options[:name]] && File.exists?(abs_filelist_paths[:css]) && File.exists?(abs_filelist_paths[:js]) # only rescan file list if content_changed, or if a filelist cache file is missing
BundleFu.content_store[options[:name]] = content unless content == BundleFu.content_store[options[:name]] && File.exists?(abs_filelist_paths[:css]) && File.exists?(abs_filelist_paths[:js])
new_files = {:js => [], :css => []} BundleFu.content_store[options[:name]] = content
new_files = {:js => [], :css => []}
content.scan(/(href|src) *= *["']([^"^'^\?]+)/i).each{ |property, value|
case property content.scan(/(href|src) *= *["']([^"^'^\?]+)/i).each{ |property, value|
when "src" case property
new_files[:js] << value when "src"
when "href" new_files[:js] << value
new_files[:css] << value when "href"
end new_files[:css] << value
} end
end }
end
[:css, :js].each { |filetype|
output_filename = File.join(paths[filetype], "#{options[:name]}.#{filetype}") [:css, :js].each { |filetype|
abs_path = File.join(RAILS_ROOT, "public", output_filename) output_filename = File.join(paths[filetype], "#{options[:name]}.#{filetype}")
abs_filelist_path = abs_filelist_paths[filetype] abs_path = File.join(RAILS_ROOT, "public", output_filename)
abs_filelist_path = abs_filelist_paths[filetype]
filelist = FileList.open( abs_filelist_path )
filelist = FileList.open( abs_filelist_path )
# check against newly parsed filelist. If we didn't parse the filelist from the output, then check against the updated mtimes.
new_filelist = new_files ? BundleFu::FileList.new(new_files[filetype]) : filelist.clone.update_mtimes # check against newly parsed filelist. If we didn't parse the filelist from the output, then check against the updated mtimes.
new_filelist = new_files ? BundleFu::FileList.new(new_files[filetype]) : filelist.clone.update_mtimes
unless new_filelist == filelist
FileUtils.mkdir_p(File.join(RAILS_ROOT, "public", paths[filetype])) unless new_filelist == filelist
# regenerate everything FileUtils.mkdir_p(File.join(RAILS_ROOT, "public", paths[filetype]))
if new_filelist.filenames.empty? # regenerate everything
# delete the javascript/css bundle file if it's empty, but keep the filelist cache if new_filelist.filenames.empty?
FileUtils.rm_f(abs_path) # delete the javascript/css bundle file if it's empty, but keep the filelist cache
else FileUtils.rm_f(abs_path)
# call bundle_css_files or bundle_js_files to bundle all files listed. output it's contents to a file else
output = BundleFu.send("bundle_#{filetype}_files", new_filelist.filenames, options) # call bundle_css_files or bundle_js_files to bundle all files listed. output it's contents to a file
File.open( abs_path, "w") {|f| f.puts output } if output output = BundleFu.send("bundle_#{filetype}_files", new_filelist.filenames, options)
end File.open( abs_path, "w") {|f| f.puts output } if output
new_filelist.save_as(abs_filelist_path) end
end new_filelist.save_as(abs_filelist_path)
end
if File.exists?(abs_path) && options[:bundle_fu] if File.exists?(abs_path) && options[:bundle_fu]
tag = filetype==:css ? stylesheet_link_tag(output_filename) : javascript_include_tag(output_filename) tag = filetype==:css ? stylesheet_link_tag(output_filename) : javascript_include_tag(output_filename)
if Rails::version < "2.2.0" if Rails::version < "2.2.0"
concat( tag , block.binding) concat( tag , block.binding)
else else
#concat doesn't need block.binding in Rails >= 2.2.0 #concat doesn't need block.binding in Rails >= 2.2.0
concat( tag ) concat( tag )
end end
end end
} }
unless options[:bundle_fu] unless options[:bundle_fu]
if Rails::version < "2.2.0" if Rails::version < "2.2.0"
concat( content, block.binding ) concat( content, block.binding )
else else
#concat doesn't need block.binding in Rails >= 2.2.0 #concat doesn't need block.binding in Rails >= 2.2.0
concat( content ) concat( content )
end end
end end
end end
end end
end end

View file

@ -1,12 +1,16 @@
class BundleFu::CSSUrlRewriter class BundleFu::CSSUrlRewriter
class << self class << self
# rewrites a relative path to an absolute path, removing excess "../" and "./" # rewrites a relative path to an absolute path, removing excess "../" and "./"
# rewrite_relative_path("stylesheets/default/global.css", "../image.gif") => "/stylesheets/image.gif" # rewrite_relative_path("stylesheets/default/global.css", "../image.gif") => "#{rails_relative_root}/stylesheets/image.gif"
def rewrite_relative_path(source_filename, relative_url) def rewrite_relative_path(source_filename, relative_url)
relative_url = relative_url.to_s.strip.gsub(/["']/, "") relative_url = relative_url.to_s.strip.gsub(/["']/, "")
return relative_url if relative_url.first == "/" || relative_url.include?("://") return relative_url if relative_url.include?("://")
if ( relative_url.first == "/" )
return relative_url unless ActionController::Base.relative_url_root
return "#{ActionController::Base.relative_url_root}#{relative_url}"
end
elements = File.join("/", File.dirname(source_filename)).gsub(/\/+/, '/').split("/") elements = File.join("/", File.dirname(source_filename)).gsub(/\/+/, '/').split("/")
elements += relative_url.gsub(/\/+/, '/').split("/") elements += relative_url.gsub(/\/+/, '/').split("/")
@ -24,7 +28,9 @@ class BundleFu::CSSUrlRewriter
end end
end end
elements * "/" path = elements * "/"
return path unless ActionController::Base.relative_url_root
"#{ActionController::Base.relative_url_root}#{path}"
end end
# rewrite the URL reference paths # rewrite the URL reference paths
@ -39,4 +45,4 @@ class BundleFu::CSSUrlRewriter
end end
end end
end end