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

Conflicts:
	app/views/todos/_todo.html.erb
This commit is contained in:
Hans de Graaff 2010-04-03 14:22:39 +02:00
commit deacdbda29
17 changed files with 138 additions and 35 deletions

View file

@ -123,6 +123,12 @@ class ApplicationController < ActionController::Base
formatted_date
end
def for_autocomplete(coll, substr)
filtered = coll.find_all{|item| item.name.downcase.include? substr.downcase}
return filtered.map {|item| "#{item.name}|#{item.id}"}.join("\n")
end
# Uses RedCloth to transform text using either Textile or Markdown Need to
# require redcloth above RedCloth 3.0 or greater is needed to use Markdown,
# otherwise it only handles Textile

View file

@ -22,6 +22,7 @@ class ContextsController < ApplicationController
format.rss &render_contexts_rss_feed
format.atom &render_contexts_atom_feed
format.text { render :action => 'index', :layout => false, :content_type => Mime::TEXT }
format.autocomplete { render :text => for_autocomplete(@active_contexts + @hidden_contexts, params[:q])}
end
end

View file

@ -26,6 +26,7 @@ class ProjectsController < ApplicationController
format.rss &render_rss_feed
format.atom &render_atom_feed
format.text &render_text_feed
format.autocomplete { render :text => for_autocomplete(@projects, params[:q]) }
end
end
end

View file

@ -526,6 +526,13 @@ class TodosController < ApplicationController
}
end
end
def tags
@tags = Tag.all
respond_to do |format|
format.autocomplete { render :text => for_autocomplete(@tags, params[:q]) }
end
end
def defer
@source_view = params['_source_view'] || 'todo'

View file

@ -202,9 +202,11 @@ module ApplicationHelper
end
def format_note(note)
note.gsub!(/</, '&lt;') # eliminate tags
note.gsub!(/>/, '&gt;')
note = markdown(note)
note = auto_link_message(note)
note = auto_link(note)
note = markdown(note)
note = sanitize(note)
return note
end
end

View file

@ -2,4 +2,8 @@ module NotesHelper
def truncated_note(note, characters = 50)
sanitize(textilize_without_paragraph(truncate(note.body, :length => characters, :omission => "...")))
end
def rendered_note(note)
sanitize(textilize_without_paragraph(note.body))
end
end

View file

@ -276,20 +276,6 @@ module TodosHelper
return "c#{todo.context_id}empty-nd"
end
def project_names_for_autocomplete
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( current_user.contexts.collect{|c| escape_javascript(c.name) } )
end
def tag_names_for_autocomplete
array_or_string_for_javascript( Tag.all.collect{|c| escape_javascript(c.name) } )
end
def default_contexts_for_autocomplete
projects = current_user.projects.find(:all, :conditions => ['default_context_id is not null'])
Hash[*projects.map{ |p| [p.name, p.default_context.name] }.flatten].to_json

View file

@ -6,18 +6,16 @@
'jquery.autocomplete', :cache => true %>
<%= stylesheet_link_tag "print", :media => "print" %>
<%= javascript_include_tag 'jquery','jquery-ui','jquery.cookie',
'jquery.blockUI','jquery.jeditable','jquery.autocomplete', :cache => 'jquery-all' %>
'jquery.blockUI','jquery.jeditable','jquery.autocomplete',
'jquery.truncator', :cache => 'jquery-all' %>
<%= javascript_include_tag 'hoverIntent','superfish','application',
'accesskey-hints','niftycube','flashobject', :cache => 'tracks' %>
<%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %>
<%= javascript_tag "var SOURCE_VIEW = '#{@source_view}';" %>
<%= javascript_tag "var TAG_NAME = '#{@tag_name}';" if @tag_name %>
<script type="text/javascript">
var contextNames = <%= context_names_for_autocomplete rescue '[]' %>;
var projectNames = <%= project_names_for_autocomplete rescue '[]' %>;
var defaultContexts = <%= default_contexts_for_autocomplete rescue '{}' %>;
var defaultTags = <%= default_tags_for_autocomplete rescue '{}' %>;
var tagNames = <%= tag_names_for_autocomplete rescue '[]' %>;
var dateFormat = '<%= date_format_for_date_picker %>';
var weekStart = '<%= current_user.prefs.week_starts %>';
function relative_to_root(path) { return '<%= root_url %>'+path; };

View file

@ -1,6 +1,6 @@
<% note = notes_summary -%>
<div class="note_wrapper" id="<%= dom_id(note) %>">
<%= link_to( image_tag("blank.png", :border => 0), note_path(note), :title => "Show note", :class => "link_to_notes icon") %>&nbsp;
<%= truncated_note(note) %>
<%= rendered_note(note) %>
</div>
<% note = nil -%>

View file

@ -29,7 +29,7 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
<div class="description<%= staleness_class( todo ) %>">
<%= grip_span %>
<%= date_span -%>
<span class="todo.descr"><%= sanitize(todo.description) %></span>
<span class="todo.descr"><%= h todo.description %></span>
<% #= successors_span %>
<%= image_tag_for_recurring_todo(todo) if @todo.from_recurring_todo? %>
<%= tag_list %>

View file

@ -1,4 +1,5 @@
# 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
Mime::Type.register_alias "text/html", :m
Mime::Type.register_alias "text/plain", :autocomplete

View file

@ -44,6 +44,9 @@ ActionController::Routing::Routes.draw do |map|
# routed to mobile view of tags.
todos.tag 'todos/tag/:name.m', :action => "tag", :format => 'm'
todos.tag 'todos/tag/:name', :action => "tag", :name => /.*/
todos.tags 'tags.autocomplete', :action => "tags", :format => 'autocomplete'
todos.auto_complete_for_predecessor 'auto_complete_for_predecessor', :action => 'auto_complete_for_predecessor'
todos.calendar 'calendar.ics', :action => "calendar", :format => 'ics'
todos.calendar 'calendar', :action => "calendar"

View file

@ -170,10 +170,16 @@ function update_order(event, ui){
/* Unobtrusive jQuery behavior */
function project_defaults(){
if(defaultContexts[$(this).val()] !== undefined) {
context_name = $(this).parents('form').find('input[name=context_name]');
if(context_name.attr('edited') === undefined){
context_name.val(defaultContexts[$(this).val()]);
if($('body').hasClass('contexts')){
// don't change the context
// see ticket #934
}
else {
if(defaultContexts[$(this).val()] !== undefined) {
context_name = $(this).parents('form').find('input[name=context_name]');
if(context_name.attr('edited') === undefined){
context_name.val(defaultContexts[$(this).val()]);
}
}
}
if(defaultTags[$(this).val()] !== undefined) {
@ -187,11 +193,16 @@ function project_defaults(){
function enable_rich_interaction(){
$('input.Date').datepicker({'dateFormat': dateFormat, 'firstDay': weekStart});
/* Autocomplete */
$('input[name=context_name]').autocomplete(contextNames, {matchContains: true});
$('input[name=project[default_context_name]]').autocomplete(contextNames, {matchContains: true});
$('input[name=project_name]').autocomplete(projectNames, {matchContains: true});
$('input[name=tag_list]:not(.ac_input)').autocomplete(tagNames, {multiple: true,multipleSeparator:',',matchContains:true});
$('input[name=predecessor_list]:not(.ac_input)').autocomplete('/todos/auto_complete_for_predecessor',
$('input[name=context_name]').autocomplete(
relative_to_root('contexts.autocomplete'), {matchContains: true});
$('input[name=project[default_context_name]]').autocomplete(
relative_to_root('contexts.autocomplete'), {matchContains: true});
$('input[name=project_name]').autocomplete(
relative_to_root('projects.autocomplete'), {matchContains: true});
$('input[name=tag_list]:not(.ac_input)').autocomplete(
relative_to_root('tags.autocomplete'), {multiple: true,multipleSeparator:',',matchContains:true});
$('input[name=predecessor_list]:not(.ac_input)').autocomplete(
relative_to_root('auto_complete_for_predecessor'),
{multiple: true,multipleSeparator:','});
/* have to bind on keypress because of limitataions of live() */
@ -289,6 +300,8 @@ $(document).ready(function() {
$(this).next().toggle("fast"); return false;
});
$('.note_wrapper').truncate({max_length: 90, more: '', less: ''});
$(".show_successors").live('click', function () {
$(this).next().toggle("fast"); return false;
});

View file

@ -5,7 +5,7 @@
var cfg = {
sensitivity: 7,
interval: 100,
timeout: 0
timeout: 400
};
// override configuration options with user supplied object
cfg = $.extend(cfg, g ? { over: f, out: g } : f );
@ -81,4 +81,4 @@
return this.mouseover(handleHover).mouseout(handleHover);
};
})(jQuery);
})(jQuery);

View file

@ -364,7 +364,7 @@ $.Autocompleter = function(input, options) {
// limit abortion to this input
port: "autocomplete" + input.name,
dataType: options.dataType,
type: 'POST',
type: 'GET',
url: options.url,
data: $.extend({
q: lastWord(term),

View file

@ -0,0 +1,81 @@
// HTML Truncator for jQuery
// by Henrik Nyh <http://henrik.nyh.se> 2008-02-28.
// modified by Eric Allen <http://hackerengineer.net> 2010-04-02
// Free to modify and redistribute with credit.
(function($) {
var trailing_whitespace = true;
$.fn.truncate = function(options) {
var opts = $.extend({}, $.fn.truncate.defaults, options);
$(this).each(function() {
var content_length = $.trim(squeeze($(this).text())).length;
if (content_length <= opts.max_length)
return; // bail early if not overlong
var actual_max_length = opts.max_length - opts.more.length - 3; // 3 for " ()"
var truncated_node = recursivelyTruncate(this, actual_max_length);
var full_node = $(this).hide();
truncated_node.insertAfter(full_node);
findNodeForMore(truncated_node).append('...');
});
}
// Note that the " (…more)" bit counts towards the max length so a max
// length of 10 would truncate "1234567890" to "12 (…more)".
$.fn.truncate.defaults = {
max_length: 100,
more: '…more',
less: 'less'
};
function recursivelyTruncate(node, max_length) {
return (node.nodeType == 3) ? truncateText(node, max_length) : truncateNode(node, max_length);
}
function truncateNode(node, max_length) {
var node = $(node);
var new_node = node.clone().empty();
var truncatedChild;
node.contents().each(function() {
var remaining_length = max_length - new_node.text().length;
if (remaining_length == 0) return; // breaks the loop
truncatedChild = recursivelyTruncate(this, remaining_length);
if (truncatedChild) new_node.append(truncatedChild);
});
return new_node;
}
function truncateText(node, max_length) {
var text = squeeze(node.data);
if (trailing_whitespace) // remove initial whitespace if last text
text = text.replace(/^ /, ''); // node had trailing whitespace.
trailing_whitespace = !!text.match(/ $/);
var text = text.slice(0, max_length);
// Ensure HTML entities are encoded
// http://debuggable.com/posts/encode-html-entities-with-jquery:480f4dd6-13cc-4ce9-8071-4710cbdd56cb
text = $('<div/>').text(text).html();
return text;
}
// Collapses a sequence of whitespace into a single space.
function squeeze(string) {
return string.replace(/\s+/g, ' ');
}
// Finds the last, innermost block-level element
function findNodeForMore(node) {
var $node = $(node);
var last_child = $node.children(":last");
if (!last_child) return node;
var display = last_child.css('display');
if (!display || display=='inline') return $node;
return findNodeForMore(last_child);
};
})(jQuery);