From c1d2580892033d28ff05f3d47cee78aaef963c29 Mon Sep 17 00:00:00 2001 From: FOI archive email account Date: Sun, 29 Jul 2012 12:57:59 +0100 Subject: Fix loading of specific file abspath is not a standard command. Replace with a case statement. --- script/load-exim-logs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/script/load-exim-logs b/script/load-exim-logs index 5ca0c66f8..00b6b9825 100755 --- a/script/load-exim-logs +++ b/script/load-exim-logs @@ -5,7 +5,10 @@ LOC=`dirname "$0"` # Specific file if specified if [ x$1 != x ] then - f=`abspath "$1"` + case "$1" in + /*) f=$1 ;; + *) f=$(pwd)/$1 ;; + esac cd "$LOC" bundle exec ./runner 'EximLog.load_file("'$f'")' exit -- cgit v1.2.3 From 4505c80c14dd0b34222aec3d398e7b16d076feb8 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 16 Aug 2012 15:24:43 +0100 Subject: Fix for external requests. --- app/views/request/show.rhtml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/app/views/request/show.rhtml b/app/views/request/show.rhtml index 27ad0700e..21e8ea83f 100644 --- a/app/views/request/show.rhtml +++ b/app/views/request/show.rhtml @@ -27,7 +27,7 @@

<%=h(@info_request.title)%>

- <% if @info_request.user.profile_photo %> + <% if @info_request.user && @info_request.user.profile_photo %>

@@ -44,48 +44,48 @@ :public_body_admin_url => public_body_admin_url(@info_request.public_body)) %> <% else %> <%= _('{{user}} made this {{law_used_full}} request',:user=>user_link(@info_request.user), :law_used_full=>h(@info_request.law_used_full)) %> - <%= _('to {{public_body}}',:public_body=>public_body_link(@info_request.public_body)) %> + <%= _('to {{public_body}}',:public_body=>public_body_link(@info_request.public_body)) %> <% end %>

<% if @info_request.awaiting_description %> <% if @is_owning_user %> - <%= _('Please answer the question above so we know whether the ')%> + <%= _('Please answer the question above so we know whether the ')%> <%= MySociety::Format.fancy_pluralize(@new_responses_count, 'recent response contains', 'recent responses contain') %> <%= _('useful information.') %> <% else %> <%= _('This request has an unknown status.') %> <% if @old_unclassified %> - <%= _('We\'re waiting for someone to read') %> + <%= _('We\'re waiting for someone to read') %> <%= MySociety::Format.fancy_pluralize(@new_responses_count, 'a recent response', 'recent responses') %> <%= _('and update the status accordingly. Perhaps you might like to help out by doing that?') %> <% else %> <%= _('We\'re waiting for') %> - <%= user_link(@info_request.user) %> <%= _('to read') %> - <%= MySociety::Format.fancy_pluralize(@new_responses_count, 'a recent response', 'recent responses') %> + <%= user_link(@info_request.user) %> <%= _('to read') %> + <%= MySociety::Format.fancy_pluralize(@new_responses_count, 'a recent response', 'recent responses') %> <%= _('and update the status.') %> <% end %> <% end %> <% elsif @status == 'waiting_response' %> - <%= _('Currently waiting for a response from {{public_body_link}}, they must respond promptly and', :public_body_link=> public_body_link(@info_request.public_body)) %> + <%= _('Currently waiting for a response from {{public_body_link}}, they must respond promptly and', :public_body_link=> public_body_link(@info_request.public_body)) %> <% if @info_request.public_body.is_school? %> <%= _('in term time') %> <% else %> - <%= _('normally') %> + <%= _('normally') %> <% end %> <%= _('no later than') %> <%= simple_date(@info_request.date_response_required_by) %> (<%= link_to _("details"), "/help/requesting#quickly_response" %>). <% elsif @status == 'waiting_response_overdue' %> <%= _('Response to this request is delayed.') %> - <%= _('By law, {{public_body_link}} should normally have responded promptly and',:public_body_link=>public_body_link(@info_request.public_body)) %> + <%= _('By law, {{public_body_link}} should normally have responded promptly and',:public_body_link=>public_body_link(@info_request.public_body)) %> <% if @info_request.public_body.is_school? %> - <%= _('in term time') %> + <%= _('in term time') %> <% end %> <%= _('by') %> <%= simple_date(@info_request.date_response_required_by) %> - (<%= _('details') % [help_requesting_path + '#quickly_response'] %>) + (<%= _('details') % [help_requesting_path + '#quickly_response'] %>) <% elsif @status == 'waiting_response_very_overdue' %> - <%= _('Response to this request is long overdue.') %> - <%= _('By law, under all circumstances, {{public_body_link}} should have responded by now',:public_body_link => public_body_link(@info_request.public_body)) %> + <%= _('Response to this request is long overdue.') %> + <%= _('By law, under all circumstances, {{public_body_link}} should have responded by now',:public_body_link => public_body_link(@info_request.public_body)) %> (<%= _('details') % [help_requesting_path + '#quickly_response'] %>). <%= _('You can complain by') %> <%= link_to _("requesting an internal review"), show_response_no_followup_url(:id => @info_request.id, :incoming_message_id => nil) + "?internal_review=1#followup" %>. @@ -99,25 +99,25 @@ <%= _('The request was partially successful.') %> <% elsif @status == 'waiting_clarification' %> <% if @is_owning_user %> - <%=h @info_request.public_body.name %> <%= _('is waiting for your clarification.') %> + <%=h @info_request.public_body.name %> <%= _('is waiting for your clarification.') %> <%= _('Please') %> <%= link_to _("send a follow up message"), respond_to_last_url(@info_request) + '#followup' %>. <% else %> - <%= _('The request is waiting for clarification.') %> - <%= _('If you are {{user_link}}, please',:user_link=>user_link(@info_request.user)) %> + <%= _('The request is waiting for clarification.') %> + <%= _('If you are {{user_link}}, please',:user_link=>user_link(@info_request.user)) %> <%= link_to _("sign in"), signin_url(:r => request.request_uri) %> <%= _('to send a follow up message.') %> <% end %> <% elsif @status == 'gone_postal' %> <%= _('The authority would like to / has responded by post to this request.') %> <% elsif @status == 'internal_review' %> - <%= _('Waiting for an internal review by {{public_body_link}} of their handling of this request.',:public_body_link=>public_body_link(@info_request.public_body)) %> + <%= _('Waiting for an internal review by {{public_body_link}} of their handling of this request.',:public_body_link=>public_body_link(@info_request.public_body)) %> <% elsif @status == 'error_message' %> <%= _('There was a delivery error or similar, which needs fixing by the {{site_name}} team.', :site_name=>site_name) %> <% elsif @status == 'requires_admin' %> <%= _('This request has had an unusual response, and requires attention from the {{site_name}} team.', :site_name=>site_name) %> <% elsif @status == 'user_withdrawn' %> - <%= _('This request has been withdrawn by the person who made it. - There may be an explanation in the correspondence below.') %> + <%= _('This request has been withdrawn by the person who made it. + There may be an explanation in the correspondence below.') %> <% elsif @status == 'attention_requested' %> <%= _('This request has been reported as needing administrator attention (perhaps because it is vexatious, or a request for personal information)') %> <% elsif @status == 'vexatious' %> -- cgit v1.2.3 From 32f44e53c879481198bdc02044d4fd3c892ce801 Mon Sep 17 00:00:00 2001 From: Seb Bacon Date: Mon, 9 Jul 2012 12:43:55 +0100 Subject: Calls to API that are expected to return JSON are always expected to return something, even when there are no errors. --- app/controllers/api_controller.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 4db07b4c9..718c31e6f 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -151,8 +151,9 @@ class ApiController < ApplicationController mail = RequestMailer.create_external_response(request, body, sent_at, attachment_hashes) request.receive(mail, mail.encoded, true) end - - head :no_content + render :json => { + 'url' => make_url("request", request.url_title), + } end def body_request_events -- cgit v1.2.3 From 9289239593e4a64eb51b4183ab70357642810a25 Mon Sep 17 00:00:00 2001 From: Seb Bacon Date: Mon, 9 Jul 2012 12:42:47 +0100 Subject: Ensure (at least in the most basic cases) that views support recent "external request" changes (mainly that InfoRequests no longer necessarily have a User). Conflicts: app/controllers/admin_request_controller.rb app/views/request/show.rhtml --- app/controllers/admin_request_controller.rb | 3 +- app/helpers/link_to_helper.rb | 8 ++++ app/views/comment/new.rhtml | 3 ++ app/views/request/_after_actions.rhtml | 2 +- app/views/request/_correspondence.rhtml | 2 +- app/views/request/_describe_state.rhtml | 4 +- app/views/request/show.rhtml | 12 ++++-- app/views/request/upload_response.rhtml | 67 ++++++++++++++++------------- app/views/track_mailer/event_digest.rhtml | 8 ++-- 9 files changed, 67 insertions(+), 42 deletions(-) diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index dba4955dc..b6ea4fe60 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -28,7 +28,8 @@ class AdminRequestController < AdminController @info_request = InfoRequest.find(params[:id]) # XXX is this *really* the only way to render a template to a # variable, rather than to the response? - vars = OpenStruct.new(:name_to => @info_request.user.name, + + vars = OpenStruct.new(:name_to => @info_request.user_name, :name_from => MySociety::Config.get("CONTACT_NAME", 'Alaveteli'), :info_request => @info_request, :reason => params[:reason], :info_request_url => 'http://' + MySociety::Config.get('DOMAIN') + request_url(@info_request), diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb index 1a86333b6..01332c5ab 100755 --- a/app/helpers/link_to_helper.rb +++ b/app/helpers/link_to_helper.rb @@ -93,6 +93,14 @@ module LinkToHelper def user_link(user, cls=nil) link_to h(user.name), user_url(user), :class => cls end + def user_link_for_request(request, cls=nil) + if request.is_external? + request.external_user_name || _("Anonymous user") + else + link_to h(request.user.name), user_url(request.user), :class => cls + end + end + def user_link_absolute(user) link_to h(user.name), main_url(user_url(user)) end diff --git a/app/views/comment/new.rhtml b/app/views/comment/new.rhtml index cf9367b88..55155c8a2 100644 --- a/app/views/comment/new.rhtml +++ b/app/views/comment/new.rhtml @@ -69,6 +69,9 @@

<%= _('Annotations will be posted publicly here, and are not sent to {{public_body_name}}.',:public_body_name=>h(@info_request.public_body.name)) %> + <% if @info_request.is_external? %> + <%= _('Note that the requester will not be notified about your annotation, because the request was published by {{public_body_name}} on their behalf.', :public_body_name => @info_request.public_body.name) %> + <% end %>

<%= render :partial => 'comment/comment_form', :locals => { :track_thing => @track_thing } %> diff --git a/app/views/request/_after_actions.rhtml b/app/views/request/_after_actions.rhtml index 205b738de..02ed7c849 100644 --- a/app/views/request/_after_actions.rhtml +++ b/app/views/request/_after_actions.rhtml @@ -20,7 +20,7 @@
- <%= _('{{info_request_user_name}} only:',:info_request_user_name=>h(@info_request.user.name)) %> + <%= _('{{info_request_user_name}} only:',:info_request_user_name=>h(@info_request.user_name)) %>
  • diff --git a/app/views/request/_correspondence.rhtml b/app/views/request/_correspondence.rhtml index 9a198ad7d..36257991b 100644 --- a/app/views/request/_correspondence.rhtml +++ b/app/views/request/_correspondence.rhtml @@ -33,7 +33,7 @@ elsif [ 'sent', 'followup_sent' ].include?(info_request_event.event_type)

    - <%= _("From:") %> <%=h @info_request.user.name %>
    + <%= _("From:") %> <%=h @info_request.user_name %>

    <%= simple_date(info_request_event.created_at) %>

    diff --git a/app/views/request/_describe_state.rhtml b/app/views/request/_describe_state.rhtml index 71699835c..5b6004e81 100644 --- a/app/views/request/_describe_state.rhtml +++ b/app/views/request/_describe_state.rhtml @@ -104,9 +104,11 @@ <% elsif @old_unclassified %> <%= render :partial => 'other_describe_state', :locals => {:id_suffix => id_suffix } %> <% else %> -<%= _('We don\'t know whether the most recent response to this request contains + <% if !@info_request.is_external? %> + <%= _('We don\'t know whether the most recent response to this request contains information or not – if you are {{user_link}} please sign in and let everyone know.',:user_link=>user_link(@info_request.user), :url=>signin_url(:r => request.request_uri)) %> + <% end %> <% end %> diff --git a/app/views/request/show.rhtml b/app/views/request/show.rhtml index 21e8ea83f..be09ef080 100644 --- a/app/views/request/show.rhtml +++ b/app/views/request/show.rhtml @@ -27,7 +27,8 @@

    <%=h(@info_request.title)%>

    - <% if @info_request.user && @info_request.user.profile_photo %> + + <% if !@info_request.is_external? && @info_request.user.profile_photo %>

    @@ -43,8 +44,10 @@ :public_body_link => public_body_link(@info_request.public_body), :public_body_admin_url => public_body_admin_url(@info_request.public_body)) %> <% else %> - <%= _('{{user}} made this {{law_used_full}} request',:user=>user_link(@info_request.user), :law_used_full=>h(@info_request.law_used_full)) %> + + <%= _('{{user}} made this {{law_used_full}} request',:user=>@info_request.user.nil? ? @info_request.user_name : user_link(@info_request.user), :law_used_full=>h(@info_request.law_used_full)) %> <%= _('to {{public_body}}',:public_body=>public_body_link(@info_request.public_body)) %> + <% end %>

    @@ -61,7 +64,8 @@ <%= _('and update the status accordingly. Perhaps you might like to help out by doing that?') %> <% else %> <%= _('We\'re waiting for') %> - <%= user_link(@info_request.user) %> <%= _('to read') %> + + <%= user_link_for_request(@info_request) %> <%= _('to read') %> <%= MySociety::Format.fancy_pluralize(@new_responses_count, 'a recent response', 'recent responses') %> <%= _('and update the status.') %> <% end %> @@ -104,7 +108,7 @@ <%= link_to _("send a follow up message"), respond_to_last_url(@info_request) + '#followup' %>. <% else %> <%= _('The request is waiting for clarification.') %> - <%= _('If you are {{user_link}}, please',:user_link=>user_link(@info_request.user)) %> + <%= _('If you are {{user_link}}, please',:user_link=>user_link_for_request(@info_request)) %> <%= link_to _("sign in"), signin_url(:r => request.request_uri) %> <%= _('to send a follow up message.') %> <% end %> <% elsif @status == 'gone_postal' %> diff --git a/app/views/request/upload_response.rhtml b/app/views/request/upload_response.rhtml index 38c3db268..0de96c5f3 100644 --- a/app/views/request/upload_response.rhtml +++ b/app/views/request/upload_response.rhtml @@ -1,45 +1,52 @@ -<% @title = "Respond to the FOI request '" + h(@info_request.title) + "' made by " + h(@info_request.user.name) %> +<% @title = "Respond to the FOI request '" + h(@info_request.title) + "' made by " + h(@info_request.user_name) %> -<%= foi_error_messages_for :comment %> +<% if @info_request.is_external? %> -

    <%= _('Respond to the FOI request')%> '<%=request_link(@info_request)%>'<% _(' made by ')%><%=user_link(@info_request.user) %>

    +

    <%= _('This request was not made via {{site_name}}', :site_name => site_name) %>

    +

    <%= _('Sorry - you cannot respond to this request via {{site_name}}, because this is a copy of the request originally at {{link_to_original_request}}.', :site_name => site_name, :link_to_original_request => link_to(@info_request.external_url, @info_request.external_url)) %>

    -

    - <%= _('Your response will appear on the Internet, read why and answers to other questions.')% [help_officers_path] %> -

    +<% else %> -

    <%= _('Respond by email')%>

    + <%= foi_error_messages_for :comment %> -

    <%= _('You should have received a copy of the request by email, and you can respond -by simply replying to that email. For your convenience, here is the address:')%> -<%=h @info_request.incoming_email%>. -<%= _('You may include attachments. If you would like to attach a -file too large for email, use the form below.')%> -

    +

    <%= _('Respond to the FOI request')%> '<%=request_link(@info_request)%>'<% _(' made by ')%><%=user_link(@info_request.user) %>

    +

    + <%= _('Your response will appear on the Internet, read why and answers to other questions.')% [help_officers_path] %> +

    -

    <%= _('Respond using the web')%>

    +

    <%= _('Respond by email')%>

    -

    <%= _('Enter your response below. You may attach one file (use email, or -contact us if you need more).')% [help_contact_path] %>

    +

    <%= _('You should have received a copy of the request by email, and you can respond + by simply replying to that email. For your convenience, here is the address:')%> + <%=h @info_request.incoming_email%>. + <%= _('You may include attachments. If you would like to attach a + file too large for email, use the form below.')%> +

    -<% form_tag '', :id => 'upload_response_form', :multipart => true do %> -

    - - <%= text_area_tag :body, "", :rows => 10, :cols => 55 %> -

    -

    - - <%= file_field_tag :file_1, :size => 35 %> -

    +

    <%= _('Respond using the web')%>

    -

    - <%= hidden_field_tag 'submitted_upload_response', 1 %> - <%= submit_tag "Upload FOI response" %> - <%= _(' (patience, especially for large files, it may take a while!)')%> -

    +

    <%= _('Enter your response below. You may attach one file (use email, or + contact us if you need more).')% [help_contact_path] %>

    + <% form_tag '', :id => 'upload_response_form', :multipart => true do %> +

    + + <%= text_area_tag :body, "", :rows => 10, :cols => 55 %> +

    + +

    + + <%= file_field_tag :file_1, :size => 35 %> +

    + +

    + <%= hidden_field_tag 'submitted_upload_response', 1 %> + <%= submit_tag "Upload FOI response" %> + <%= _(' (patience, especially for large files, it may take a while!)')%> +

    + <% end %> <% end %> diff --git a/app/views/track_mailer/event_digest.rhtml b/app/views/track_mailer/event_digest.rhtml index 089b778f8..469910007 100644 --- a/app/views/track_mailer/event_digest.rhtml +++ b/app/views/track_mailer/event_digest.rhtml @@ -18,17 +18,17 @@ # e.g. Julian Burgess sent a request to Royal Mail Group (15 May 2008) if event.event_type == 'response' url = main_url(incoming_message_url(event.incoming_message)) - main_text += _("{{public_body}} sent a response to {{user_name}}", :public_body => event.info_request.public_body.name, :user_name => event.info_request.user.name) + main_text += _("{{public_body}} sent a response to {{user_name}}", :public_body => event.info_request.public_body.name, :user_name => event.info_request.user_name) elsif event.event_type == 'followup_sent' url = main_url(outgoing_message_url(event.outgoing_message)) - main_text += _("{{user_name}} sent a follow up message to {{public_body}}", :user_name => event.info_request.user.name, :public_body => event.info_request.public_body.name) + main_text += _("{{user_name}} sent a follow up message to {{public_body}}", :user_name => event.info_request.user_name, :public_body => event.info_request.public_body.name) elsif event.event_type == 'sent' # this is unlikely to happen in real life, but happens in the test code url = main_url(outgoing_message_url(event.outgoing_message)) - main_text += _("{{user_name}} sent a request to {{public_body}}", :user_name => event.info_request.user.name, :public_body => event.info_request.public_body.name) + main_text += _("{{user_name}} sent a request to {{public_body}}", :user_name => event.info_request.user_name, :public_body => event.info_request.public_body.name) elsif event.event_type == 'comment' url = main_url(comment_url(event.comment)) - main_text += _("{{user_name}} added an annotation", :user_name => event.comment.user.name) + main_text += _("{{user_name}} added an annotation", :user_name => event.comment.user_name) else raise "unknown type in event_digest " + event.event_type end -- cgit v1.2.3 From 4612f4c4b721157c6d14f277f97f7b262de0e550 Mon Sep 17 00:00:00 2001 From: Seb Bacon Date: Mon, 9 Jul 2012 13:45:27 +0100 Subject: Fix bug introduced in commit 049198b --- app/views/track_mailer/event_digest.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/track_mailer/event_digest.rhtml b/app/views/track_mailer/event_digest.rhtml index 469910007..2c2e3c957 100644 --- a/app/views/track_mailer/event_digest.rhtml +++ b/app/views/track_mailer/event_digest.rhtml @@ -28,7 +28,7 @@ main_text += _("{{user_name}} sent a request to {{public_body}}", :user_name => event.info_request.user_name, :public_body => event.info_request.public_body.name) elsif event.event_type == 'comment' url = main_url(comment_url(event.comment)) - main_text += _("{{user_name}} added an annotation", :user_name => event.comment.user_name) + main_text += _("{{user_name}} added an annotation", :user_name => event.comment.user.name) else raise "unknown type in event_digest " + event.event_type end -- cgit v1.2.3 From 0703efa26d3de27f7d3a8ac26652068a632d13ca Mon Sep 17 00:00:00 2001 From: Robin Houston Date: Mon, 16 Jul 2012 18:58:10 +0100 Subject: Beef up mocks Add the user_name and is_external? properties to the InfoRequest mock objects used for view testing, where necessary. --- spec/views/request/_after_actions.rhtml_spec.rb | 2 ++ spec/views/request/_describe_state.rhtml_spec.rb | 7 ++++++- spec/views/request/show.rhtml_spec.rb | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/views/request/_after_actions.rhtml_spec.rb b/spec/views/request/_after_actions.rhtml_spec.rb index 6a56e7a71..d04db3fc2 100644 --- a/spec/views/request/_after_actions.rhtml_spec.rb +++ b/spec/views/request/_after_actions.rhtml_spec.rb @@ -9,6 +9,8 @@ describe 'when displaying actions that can be taken with regard to a request' do :url_name => 'test_user') @mock_request = mock_model(InfoRequest, :title => 'test request', :user => @mock_user, + :user_name => @mock_user.name, + :is_external? => false, :public_body => @mock_body, :url_title => 'test_request') assigns[:info_request] = @mock_request diff --git a/spec/views/request/_describe_state.rhtml_spec.rb b/spec/views/request/_describe_state.rhtml_spec.rb index ccf653b1b..18778d5d2 100644 --- a/spec/views/request/_describe_state.rhtml_spec.rb +++ b/spec/views/request/_describe_state.rhtml_spec.rb @@ -18,7 +18,12 @@ describe 'when showing the form for describing the state of a request' do before do @mock_user = mock_model(User, :name => 'test user', :url_name => 'test_user') - @mock_request = mock_model(InfoRequest, :described_state => '', :user => @mock_user) + @mock_request = mock_model(InfoRequest, + :described_state => '', + :user => @mock_user, + :user_name => @mock_user.name, + :is_external? => false + ) assigns[:info_request] = @mock_request end diff --git a/spec/views/request/show.rhtml_spec.rb b/spec/views/request/show.rhtml_spec.rb index ef7d1a47c..4429e9e58 100644 --- a/spec/views/request/show.rhtml_spec.rb +++ b/spec/views/request/show.rhtml_spec.rb @@ -15,6 +15,8 @@ describe 'when viewing an information request' do :law_used_full => 'Freedom of Information', :public_body => @mock_body, :user => @mock_user, + :user_name => @mock_user.name, + :is_external? => false, :calculate_status => 'waiting_response', :date_response_required_by => Date.today, :prominence => 'normal') -- cgit v1.2.3 From 6c8af1b86a8d0585118017ac44074ff33ca1a11f Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 16 Aug 2012 17:35:58 +0100 Subject: Also handle moved and deleted incoming messages. --- lib/tasks/temp.rake | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/tasks/temp.rake b/lib/tasks/temp.rake index 9decc13db..669cdf989 100644 --- a/lib/tasks/temp.rake +++ b/lib/tasks/temp.rake @@ -31,6 +31,26 @@ namespace :temp do if ! dryrun FileUtils.rm_rf(request_subdir) end + else + Dir.glob(File.join(request_subdir, 'response', '*')) do |response_subdir| + incoming_message_id = File.basename(response_subdir) + puts "Looking for IncomingMessage with id #{incoming_message_id}" if verbose + begin + incoming_message = IncomingMessage.find(incoming_message_id) + puts "Got IncomingMessage #{incoming_message_id}" if verbose + if incoming_message.info_request != info_request + puts "Deleting cache at #{response_subdir}: IncomingMessage #{incoming_message_id} has been moved from InfoRequest #{info_request_id}" + if ! dryrun + FileUtils.rm_rf(response_subdir) + end + end + rescue ActiveRecord::RecordNotFound + puts "Deleting cache at #{response_subdir} for deleted IncomingMessage #{incoming_message_id}" + if ! dryrun + FileUtils.rm_rf(response_subdir) + end + end + end end rescue ActiveRecord::RecordNotFound puts "Deleting cache at #{request_subdir} for deleted InfoRequest #{info_request_id}" -- cgit v1.2.3 From e3887f042ba1309985e58ce553de0de05227dc56 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 16 Aug 2012 18:21:25 +0100 Subject: Re-adding the serving of cached attachments direct from Apache. --- config/httpd.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/httpd.conf b/config/httpd.conf index b4d389d4b..6428a2006 100644 --- a/config/httpd.conf +++ b/config/httpd.conf @@ -41,9 +41,9 @@ RewriteRule /files/(.+) http://files.whatdotheyknow.com/$1 # # The condition means that the rule will fire only if the cached # file exists. -# RewriteMap escape int:escape -# RewriteCond %{DOCUMENT_ROOT}/views_cache/request/$2/$1/${escape:$3} -f -# RewriteRule ^/request/((\d{1,3})\d*)/(response/\d+/attach/\d+/.+) /views_cache/request/$2/$1/${escape:$3} [L] +RewriteMap escape int:escape +RewriteCond %{DOCUMENT_ROOT}/views_cache/request/$2/$1/${escape:$3} -f +RewriteRule ^/request/((\d{1,3})\d*)/(response/\d+/attach/\d+/.+) /views_cache/request/$2/$1/${escape:$3} [L] # Set this to something like 100 if you have memory leak issues -- cgit v1.2.3 From 30c2eeb997d0e676f66015445fd9fc85dc9df5eb Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 23 Aug 2012 17:49:47 +0100 Subject: Restore survey variables. --- config/general.yml-example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/general.yml-example b/config/general.yml-example index 33e3ad5bf..419e9f4e5 100644 --- a/config/general.yml-example +++ b/config/general.yml-example @@ -157,3 +157,8 @@ VARNISH_HOST: localhost # Adding a value here will enable Google Analytics on all non-admin pages. GA_CODE: '' + +# We need to add the WDTK survey variables here, or else the deployment +# system will cry. +SURVEY_SECRET: '' +SURVEY_URL: '' -- cgit v1.2.3 From 52c3e28d09fd75fdbf467c96909e20a86aeaa7fa Mon Sep 17 00:00:00 2001 From: Robin Houston Date: Mon, 10 Sep 2012 17:10:44 +0100 Subject: Add a since_date parameter to the API feed --- app/controllers/api_controller.rb | 46 +++++++++++++++++++++++++-------- spec/controllers/api_controller_spec.rb | 15 +++++++++++ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 6c98ebeba..409a432eb 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -167,17 +167,41 @@ class ApiController < ApplicationController feed_type = params[:feed_type] raise PermissionDenied.new("#{@public_body.id} != #{params[:id]}") if @public_body.id != params[:id].to_i - @events = InfoRequestEvent.find_by_sql([ - %(select info_request_events.* - from info_requests - join info_request_events on info_requests.id = info_request_events.info_request_id - where info_requests.public_body_id = ? - and info_request_events.event_type in ( - 'sent', 'followup_sent', 'resent', 'followup_resent' - ) - order by info_request_events.created_at desc - ), @public_body.id - ]) + since_date_str = params[:since_date] + if since_date_str.nil? + @events = InfoRequestEvent.find_by_sql([ + %(select info_request_events.* + from info_requests + join info_request_events on info_requests.id = info_request_events.info_request_id + where info_requests.public_body_id = ? + and info_request_events.event_type in ( + 'sent', 'followup_sent', 'resent', 'followup_resent' + ) + order by info_request_events.created_at desc + ), @public_body.id + ]) + else + begin + since_date = Date.strptime(since_date_str, "%Y-%m-%d") + rescue ArgumentError + render :json => {"errors" => [ + "Parameter since_date must be in format yyyy-mm-dd (not '#{since_date_str}')" ] }, + :status => 500 + return + end + @events = InfoRequestEvent.find_by_sql([ + %(select info_request_events.* + from info_requests + join info_request_events on info_requests.id = info_request_events.info_request_id + where info_requests.public_body_id = ? + and info_request_events.event_type in ( + 'sent', 'followup_sent', 'resent', 'followup_resent' + ) + and info_request_events.created_at >= ? + order by info_request_events.created_at desc + ), @public_body.id, since_date + ]) + end if feed_type == "atom" render :template => "api/request_events.atom", :layout => false elsif feed_type == "json" diff --git a/spec/controllers/api_controller_spec.rb b/spec/controllers/api_controller_spec.rb index 925b7adb4..ded9a040a 100644 --- a/spec/controllers/api_controller_spec.rb +++ b/spec/controllers/api_controller_spec.rb @@ -320,6 +320,21 @@ describe ApiController, "when using the API" do assigns[:event_data].should == [first_event] end + it "should honour the since_date parameter for the Atom feed" do + get :body_request_events, + :id => public_bodies(:humpadink_public_body).id, + :k => public_bodies(:humpadink_public_body).api_key, + :since_date => "2010-01-01", + :feed_type => "atom" + + response.should be_success + response.should render_template("api/request_events.atom") + assigns[:events].size.should > 0 + assigns[:events].each do |event| + event.created_at.should >= Date.new(2010, 1, 1) + end + end + it "should return a JSON 404 error for non-existent requests" do request_id = 123459876 # Let's hope this doesn't exist! sent_at = "2012-05-28T12:35:39+01:00" -- cgit v1.2.3 From 6d3d9d9846fa905e606f0a2aa6f1ec32122f4850 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 12 Sep 2012 12:48:50 +0100 Subject: Add some indexes based on queries appearing in the SQL slow queries log. --- ...120912111713_add_raw_email_index_to_incoming_messages.rb | 9 +++++++++ ...20120912112036_add_info_request_id_index_to_exim_logs.rb | 9 +++++++++ ...fo_request_id_index_to_incoming_and_outgoing_messages.rb | 11 +++++++++++ ...2655_add_incoming_message_id_index_to_foi_attachments.rb | 9 +++++++++ .../20120912113004_add_indexes_to_info_request_events.rb | 13 +++++++++++++ ...20120912113720_add_public_body_index_to_info_requests.rb | 9 +++++++++ .../20120912114022_add_user_index_to_info_requests.rb | 9 +++++++++ 7 files changed, 69 insertions(+) create mode 100644 db/migrate/20120912111713_add_raw_email_index_to_incoming_messages.rb create mode 100644 db/migrate/20120912112036_add_info_request_id_index_to_exim_logs.rb create mode 100644 db/migrate/20120912112312_add_info_request_id_index_to_incoming_and_outgoing_messages.rb create mode 100644 db/migrate/20120912112655_add_incoming_message_id_index_to_foi_attachments.rb create mode 100644 db/migrate/20120912113004_add_indexes_to_info_request_events.rb create mode 100644 db/migrate/20120912113720_add_public_body_index_to_info_requests.rb create mode 100644 db/migrate/20120912114022_add_user_index_to_info_requests.rb diff --git a/db/migrate/20120912111713_add_raw_email_index_to_incoming_messages.rb b/db/migrate/20120912111713_add_raw_email_index_to_incoming_messages.rb new file mode 100644 index 000000000..14174935e --- /dev/null +++ b/db/migrate/20120912111713_add_raw_email_index_to_incoming_messages.rb @@ -0,0 +1,9 @@ +class AddRawEmailIndexToIncomingMessages < ActiveRecord::Migration + def self.up + add_index :incoming_messages, :raw_email_id + end + + def self.down + remove_index :incoming_messages, :raw_email_id + end +end diff --git a/db/migrate/20120912112036_add_info_request_id_index_to_exim_logs.rb b/db/migrate/20120912112036_add_info_request_id_index_to_exim_logs.rb new file mode 100644 index 000000000..81e2a7946 --- /dev/null +++ b/db/migrate/20120912112036_add_info_request_id_index_to_exim_logs.rb @@ -0,0 +1,9 @@ +class AddInfoRequestIdIndexToEximLogs < ActiveRecord::Migration + def self.up + add_index :exim_logs, :info_request_id + end + + def self.down + remove_index :exim_logs, :info_request_id + end +end diff --git a/db/migrate/20120912112312_add_info_request_id_index_to_incoming_and_outgoing_messages.rb b/db/migrate/20120912112312_add_info_request_id_index_to_incoming_and_outgoing_messages.rb new file mode 100644 index 000000000..814fa7540 --- /dev/null +++ b/db/migrate/20120912112312_add_info_request_id_index_to_incoming_and_outgoing_messages.rb @@ -0,0 +1,11 @@ +class AddInfoRequestIdIndexToIncomingAndOutgoingMessages < ActiveRecord::Migration + def self.up + add_index :incoming_messages, :info_request_id + add_index :outgoing_messages, :info_request_id + end + + def self.down + remove_index :incoming_messages, :info_request_id + remove_index :outgoing_messages, :info_request_id + end +end diff --git a/db/migrate/20120912112655_add_incoming_message_id_index_to_foi_attachments.rb b/db/migrate/20120912112655_add_incoming_message_id_index_to_foi_attachments.rb new file mode 100644 index 000000000..be0bf76c3 --- /dev/null +++ b/db/migrate/20120912112655_add_incoming_message_id_index_to_foi_attachments.rb @@ -0,0 +1,9 @@ +class AddIncomingMessageIdIndexToFoiAttachments < ActiveRecord::Migration + def self.up + add_index :foi_attachments, :incoming_message_id + end + + def self.down + remove_index :foi_attachments, :incoming_message_id + end +end diff --git a/db/migrate/20120912113004_add_indexes_to_info_request_events.rb b/db/migrate/20120912113004_add_indexes_to_info_request_events.rb new file mode 100644 index 000000000..b3780322f --- /dev/null +++ b/db/migrate/20120912113004_add_indexes_to_info_request_events.rb @@ -0,0 +1,13 @@ +class AddIndexesToInfoRequestEvents < ActiveRecord::Migration + def self.up + add_index :info_request_events, :incoming_message_id + add_index :info_request_events, :outgoing_message_id + add_index :info_request_events, :comment_id + end + + def self.down + remove_index :info_request_events, :incoming_message_id + remove_index :info_request_events, :outgoing_message_id + remove_index :info_request_events, :comment_id + end +end diff --git a/db/migrate/20120912113720_add_public_body_index_to_info_requests.rb b/db/migrate/20120912113720_add_public_body_index_to_info_requests.rb new file mode 100644 index 000000000..a56cad1f9 --- /dev/null +++ b/db/migrate/20120912113720_add_public_body_index_to_info_requests.rb @@ -0,0 +1,9 @@ +class AddPublicBodyIndexToInfoRequests < ActiveRecord::Migration + def self.up + add_index :info_requests, :public_body_id + end + + def self.down + remove_index :info_requests, :public_body_id + end +end diff --git a/db/migrate/20120912114022_add_user_index_to_info_requests.rb b/db/migrate/20120912114022_add_user_index_to_info_requests.rb new file mode 100644 index 000000000..8be51c0c8 --- /dev/null +++ b/db/migrate/20120912114022_add_user_index_to_info_requests.rb @@ -0,0 +1,9 @@ +class AddUserIndexToInfoRequests < ActiveRecord::Migration + def self.up + add_index :info_requests, :user_id + end + + def self.down + remove_index :info_requests, :user_id + end +end -- cgit v1.2.3 From bd94b0390fcfba53bee7d546227e94e860f37c17 Mon Sep 17 00:00:00 2001 From: Robin Houston Date: Thu, 13 Sep 2012 14:49:00 +0100 Subject: Fix Atom version of api_body_request_events Remove duplicate element, and add an element to each entry. --- app/views/api/request_events.atom.builder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/api/request_events.atom.builder b/app/views/api/request_events.atom.builder index 4f0133051..44759ae7e 100644 --- a/app/views/api/request_events.atom.builder +++ b/app/views/api/request_events.atom.builder @@ -6,7 +6,7 @@ atom_feed("xmlns:alaveteli" => "http://www.alaveteli.org/API/v2/RequestEvents/At feed.entry(event) do |entry| request = event.info_request - entry.published(event.created_at) + entry.updated(event.created_at.utc.iso8601) entry.tag!("alaveteli:event_type", event.event_type) entry.tag!("alaveteli:request_url", main_url(request_url(request))) entry.title(request.title) -- cgit v1.2.3 From f952be7693595642e0e5d66853528406af92b1b6 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 3 Oct 2012 14:23:36 +0100 Subject: Set the PassengerMinInstances variable - this spawns a certain number of instances on the first request. I suspect that some of the slowness we see in WDTK is time taken to spawn new instances. --- config/httpd.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/httpd.conf b/config/httpd.conf index 129b1577b..9e6b39c12 100644 --- a/config/httpd.conf +++ b/config/httpd.conf @@ -52,6 +52,8 @@ RewriteRule ^/request/((\d{1,3})\d*)/(response/\d+/attach/(html/)?\d+/.+) /views PassengerResolveSymlinksInDocumentRoot on # Recommend setting this to 3 or less on servers with 512MB RAM PassengerMaxPoolSize 6 + # Minimum number of instances that will be maintained + PassengerMinInstances 6 RailsEnv production -- cgit v1.2.3 From 991e6f00f62fa06842ca8c51743d80dbee03ec09 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 3 Oct 2012 14:36:31 +0100 Subject: Removing PassengerMinInstances - need to have Passenger 3.0.0 --- config/httpd.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/httpd.conf b/config/httpd.conf index 9e6b39c12..129b1577b 100644 --- a/config/httpd.conf +++ b/config/httpd.conf @@ -52,8 +52,6 @@ RewriteRule ^/request/((\d{1,3})\d*)/(response/\d+/attach/(html/)?\d+/.+) /views PassengerResolveSymlinksInDocumentRoot on # Recommend setting this to 3 or less on servers with 512MB RAM PassengerMaxPoolSize 6 - # Minimum number of instances that will be maintained - PassengerMinInstances 6 RailsEnv production -- cgit v1.2.3 From d3f848a36e710515eee0943f99c83d0bfec79ea1 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 4 Oct 2012 13:31:44 +0100 Subject: Task for generating monthly volume statistics filtered by tag set. --- lib/tasks/stats.rake | 96 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 25 deletions(-) diff --git a/lib/tasks/stats.rake b/lib/tasks/stats.rake index e1b58905d..9d7d70540 100644 --- a/lib/tasks/stats.rake +++ b/lib/tasks/stats.rake @@ -1,48 +1,94 @@ -namespace :stats do - - desc 'Produce transaction stats' - task :show => :environment do +namespace :stats do + + desc 'Produce transaction stats' + task :show => :environment do month_starts = (Date.new(2009, 1)..Date.new(2011, 8)).select { |d| d.day == 1 } headers = ['Period', - 'Requests sent', - 'Annotations added', - 'Track this request email signups', - 'Comments on own requests', + 'Requests sent', + 'Annotations added', + 'Track this request email signups', + 'Comments on own requests', 'Follow up messages sent'] puts headers.join("\t") month_starts.each do |month_start| month_end = month_start.end_of_month period = "#{month_start}-#{month_end}" - date_conditions = ['created_at >= ? - AND created_at < ?', + date_conditions = ['created_at >= ? + AND created_at < ?', month_start, month_end+1] request_count = InfoRequest.count(:conditions => date_conditions) comment_count = Comment.count(:conditions => date_conditions) - track_conditions = ['track_type = ? - AND track_medium = ? - AND created_at >= ? - AND created_at < ?', + track_conditions = ['track_type = ? + AND track_medium = ? + AND created_at >= ? + AND created_at < ?', 'request_updates', 'email_daily', month_start, month_end+1] email_request_track_count = TrackThing.count(:conditions => track_conditions) - comment_on_own_request_conditions = ['comments.user_id = info_requests.user_id - AND comments.created_at >= ? + comment_on_own_request_conditions = ['comments.user_id = info_requests.user_id + AND comments.created_at >= ? AND comments.created_at < ?', month_start, month_end+1] comment_on_own_request_count = Comment.count(:conditions => comment_on_own_request_conditions, :include => :info_request) - - followup_conditions = ['message_type = ? - AND created_at >= ? + + followup_conditions = ['message_type = ? + AND created_at >= ? AND created_at < ?', 'followup', month_start, month_end+1] follow_up_count = OutgoingMessage.count(:conditions => followup_conditions) - puts [period, - request_count, - comment_count, - email_request_track_count, - comment_on_own_request_count, + puts [period, + request_count, + comment_count, + email_request_track_count, + comment_on_own_request_count, follow_up_count].join("\t") end end - + + desc 'Produce stats on volume of requests to authorities matching a set of tags. Specify tags as TAGS=tagone,tagtwo' + task :volumes_by_authority_tag => :environment do + tags = ENV['TAGS'].split(',') + first_request_datetime = InfoRequest.minimum(:created_at) + start_year = first_request_datetime.strftime("%Y").to_i + start_month = first_request_datetime.strftime("%m").to_i + end_year = Time.now.strftime("%Y").to_i + end_month = Time.now.strftime("%m").to_i + puts "Start year: #{start_year}" + puts "Start month: #{start_month}" + puts "End year: #{end_year}" + puts "End month: #{end_month}" + public_bodies = [] + tags.each do |tag| + tag_bodies = PublicBody.find_by_tag(tag) + puts "Bodies with tag '#{tag}': #{tag_bodies.size}" + public_bodies += tag_bodies + end + public_body_ids = public_bodies.map{ |body| body.id }.uniq + public_body_condition_string = 'AND public_bodies.id in (?)' + month_starts = (Date.new(start_year, start_month)..Date.new(end_year, end_month)).select { |d| d.day == 1 } + headers = ['Period', + 'Requests sent', + 'Requests sent as % of total sent in period'] + puts headers.join("\t") + month_starts.each do |month_start| + month_end = month_start.end_of_month + period = "#{month_start}-#{month_end}" + date_condition_string = 'info_requests.created_at >= ? AND info_requests.created_at < ?' + conditions = [date_condition_string + " " + public_body_condition_string, + month_start, + month_end+1, + public_body_ids] + request_count = InfoRequest.count(:conditions => conditions, + :include => :public_body) + + total_count = InfoRequest.count(:conditions => [date_condition_string, month_start, month_end+1]) + if total_count > 0 + percent = ((request_count.to_f / total_count.to_f ) * 100).round(2) + else + percent = 0.0 + end + puts [period, request_count, percent].join("\t") + end + end + end -- cgit v1.2.3 From cc27fe66a13fe1504711c64ce5530a1c178e907d Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 9 Oct 2012 18:26:22 +0100 Subject: Handle the case where an incoming message is badly encoded and has no charset on the part we're using as a main part. --- app/models/incoming_message.rb | 1 + spec/fixtures/files/no-part-charset-bad-utf8.email | 38 ++++++++++++++++++++++ spec/models/incoming_message_spec.rb | 10 ++++++ 3 files changed, 49 insertions(+) create mode 100644 spec/fixtures/files/no-part-charset-bad-utf8.email diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index 13fc316cd..1a7bb1bfd 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -647,6 +647,7 @@ class IncomingMessage < ActiveRecord::Base # Text looks like unlabelled nonsense, # strip out anything that isn't UTF-8 begin + source_charset = 'utf-8' if source_charset.nil? text = Iconv.conv('utf-8//IGNORE', source_charset, text) + _("\n\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]", :site_name => MySociety::Config.get('SITE_NAME', 'Alaveteli')) diff --git a/spec/fixtures/files/no-part-charset-bad-utf8.email b/spec/fixtures/files/no-part-charset-bad-utf8.email new file mode 100644 index 000000000..7e67d0063 --- /dev/null +++ b/spec/fixtures/files/no-part-charset-bad-utf8.email @@ -0,0 +1,38 @@ +From xxxx@yahoo.cn Mon Oct 08 14:01:34 2012 +Return-path: +Envelope-to: foi@atlas.ukcod.org.uk +Delivery-date: Mon, 08 Oct 2012 14:01:34 +0100 +Received: (qmail 63864 invoked from network); 8 Oct 2012 13:01:12 -0000 +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.cn; s=s1024; t=1349701272; bh=T/mtlIYvhB/L5RO+CvTazeAdGf1n1zsGXBoA8EKGT9M=; h=Message-ID:X-Yahoo-Newman-Property:X-YMail-OSG:X-Yahoo-SMTP:Received:X-mailer:From:Subject:To:Content-Transfer-Encoding:Content-Type:Date; b=LYI/PXvA7DA746bmyprChUg7N8YDvN9XE/bhfTt5MW7siOmxHHzn1w+s5X33PvLI0x0UfJLo+MCkTnGPKnG5BYY38US8PkocJYyphrvF/eaUl3ALf8UvxHBOJX1iIi89Xp2NnfbS8lz9kZAWifb9GOnOA5/kLDcL5/WJXliit2k= +Message-ID: +X-Yahoo-Newman-Property: ymail-5 +X-YMail-OSG: nPs5jgsVM1myUoKjeEPTxxalz4BM6BZMEUYu.E8NPMPQyo_ + Yej8T2WCTurn767NOwhuDIqNxC2QGZINqfjmKcdyW7a1P_Zxqr9GsjgxODci + ihwr7qYAGDDbcsrB.PX4epnJZHl3yAwoGW.1ReEZnXQANFcNep7.zNEbZ_2k + RU1IhI9aHYvxPxt5RWugwOoFRh9P8Ym35A88IMazNtVaBiBEXF6Vk8Aqr9XP + 3Vh9xOT9Pn6X8qOUjNXkdb3xB4S5AAIRSE9mqhL1KzHBwdVQs25IoM_2FV2b + gPsQGgL4_mwBH0WcEMhdj7Kn6Nfb44L.50E_V3DH.8P7KzDK8zNVXSbAqohX + Qi6MzUK2frr8IyZyYzHb.ekff7kAcJgUoHvhnyPar8tRYxhQT3_xsUTzsx8N + oWckVPh_i3OT7U4ObgekqgtteMoYqPH2eF1SZXamGBAs- +X-Yahoo-SMTP: YUQHwRWswBDjbw_M.D6EP4KpT9khlJErDRBQi4ySZQ-- +X-mailer: MIME::Lite 3.027 (F2.74; T1.31; A2.07; B3.13; Q3.13) +From: =?GB2312?B?zsJKaWFu?= Bing +Subject: =?GB2312?B?yM7A1svJ?= +To: FOI Person +Content-Transfer-Encoding: base64 +Content-Type: text/plain +Date: Tue, 9 Oct 2012 20:53:06 +0800 + +DQogICAgICAgICAgufO5q8u+uLrU8MjLKL6twO0vssbO8SnE+rrDo7oNCiAgICAgICAgICAgILG+ +uavLvtTaMTk5N8Tqs8nBorn6vNK5pMnM16Ky4S7KtcGm0Nu68aGj09C2wMGiy7DO8Q0KICAgICAg +ICAgINeo0rXIy9SxO9TayKu5+rj3s8fK0MnowaK31rmry76jqNXjva2hosnPuqOhornj1t2hor2t +y9W1yA0KICAgICAgICAgILXYt72jqdLyvfjP7r3PtuDP1s3qs8myu8HLw7/Uws/6ytu27rbIoaPD +v9TC09DSu7K/t9YNCiAgICAgICAgICDU9ta1tpCjqDYtNyXX89PSKbrNxtXGsaOoMC41JS0yJSDX +89PSo6nTxbvdtPq/qrvyus/X96OsDQogICAgICAgICAgtePK/b3Ptc2ho7T6wO23ts6nyOfPwqO6 +DQogICAgICAgICAg1PbWtcuwOjEuMTcl16jTw9T21rWjuzI6xtXNqNT21rWjuzM6uqO52MBVv+6V ++CANCiAgICAgICAgICAgNC65+suwzajTw7v6tPI7IDUutdjLsM2o08O7+rTyDQogICAgICAgICAg +ICAgPT09Pdaj1tiz0MW1PT09PSANCiAgICAgICAgICDGsb7dvvnOqraQhNW+1sHss/a78tPJxvPS +tdaxvdO/qrP2o6zR6dakuvO4tr/uoaMNCiAgICAgICAgICAgICAgIMGqIMLnIDq7xr6twO0gICAg +ICAgICAgICAgyMjP3zoxMzgtMjQzNi0wNTE1DQogICAgICAgICAgICDStc7xUVE6OTc4My05Njg5 +OCAgIEUtbWFpbDp3d3dheDg4QDEyNi5jb20NCg== + diff --git a/spec/models/incoming_message_spec.rb b/spec/models/incoming_message_spec.rb index bc73ef071..a00e85fbc 100644 --- a/spec/models/incoming_message_spec.rb +++ b/spec/models/incoming_message_spec.rb @@ -56,6 +56,15 @@ describe IncomingMessage, " when dealing with incoming mail" do message.subject.should == "Câmara Responde: Banco de ideias" end + it 'should not error on display of a message which has no charset set on the body part and + is not good utf-8' do + ir = info_requests(:fancy_dog_request) + receive_incoming_mail('no-part-charset-bad-utf8.email', ir.incoming_email) + puts ir.incoming_messages.inspect + message = ir.incoming_messages[1] + message.parse_raw_email! + message.get_main_body_text_internal.should include("The above text was badly encoded") + end it "should fold multiline sections" do { @@ -77,6 +86,7 @@ describe IncomingMessage, "when parsing HTML mail" do plain_text = IncomingMessage._get_attachment_text_internal_one_file('text/html', html) plain_text.should match(/është/) end + end describe IncomingMessage, "when getting the attachment text" do -- cgit v1.2.3 From ea550321b7fc1d728c82de1aaf62a0aa0209f118 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 17 Oct 2012 15:37:14 +0100 Subject: Require rather than load to avoid already defined constant error when loading rails environment. --- script/handle-mail-replies.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/handle-mail-replies.rb b/script/handle-mail-replies.rb index d354a9266..f4ffb61f8 100755 --- a/script/handle-mail-replies.rb +++ b/script/handle-mail-replies.rb @@ -16,7 +16,7 @@ $alaveteli_dir = File.expand_path(File.join(File.dirname(__FILE__), '..')) $:.push(File.join($alaveteli_dir, "commonlib", "rblib")) load "config.rb" $:.push(File.join($alaveteli_dir, "lib")) -load "configuration.rb" +require "configuration" MySociety::Config.set_file(File.join($alaveteli_dir, 'config', 'general'), true) MySociety::Config.load_default -- cgit v1.2.3 From 4d38fc99ca515fb61ee00bdd217b09da9f24b0bb Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 22 Oct 2012 16:33:32 +0100 Subject: Trying a higher PassengerMaxRequests value - we now have some headroom in terms of memory, but CPU limited. --- config/httpd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/httpd.conf b/config/httpd.conf index 440da0d87..acf37d97c 100644 --- a/config/httpd.conf +++ b/config/httpd.conf @@ -46,7 +46,7 @@ RewriteRule ^/request/((\d{1,3})\d*)/(response/\d+/attach/(html/)?\d+/.+) /views # Set this to something like 100 if you have memory leak issues - PassengerMaxRequests 20 + PassengerMaxRequests 500 PassengerResolveSymlinksInDocumentRoot on # Recommend setting this to 3 or less on servers with 512MB RAM PassengerMaxPoolSize 6 -- cgit v1.2.3 From c498277ae686b6a0328ff6169cadd1f2fb2462c6 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 22 Oct 2012 17:44:46 +0100 Subject: Reformat line for length. --- app/models/request_mailer.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index 413e93e25..6f521484a 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -273,7 +273,14 @@ class RequestMailer < ApplicationMailer end # For now, just to the user who created the request - sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = ? and user_id = ? and info_request_id = ? and info_request_event_id = ?", alert_type, info_request.user_id, info_request.id, alert_event_id]) + sent_already = UserInfoRequestSentAlert.find(:first, :conditions => [ "alert_type = ? + AND user_id = ? + AND info_request_id = ? + AND info_request_event_id = ?", + alert_type, + info_request.user_id, + info_request.id, + alert_event_id]) if sent_already.nil? # Alert not yet sent for this user, so send it store_sent = UserInfoRequestSentAlert.new -- cgit v1.2.3 From f89807c795fc219cb308825dd6dcae3335c90cec Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 22 Oct 2012 17:54:41 +0100 Subject: Adding new spec from https://github.com/mysociety/alaveteli/commit/cdb1c9420169ed6eafdd4e57ca21e1809c95e5cb --- spec/controllers/request_controller_spec.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb index 77f43b618..5867c994f 100644 --- a/spec/controllers/request_controller_spec.rb +++ b/spec/controllers/request_controller_spec.rb @@ -245,7 +245,7 @@ describe RequestController, "when showing one request" do response.should have_tag('#anyone_actions', /Add an annotation/) end end - + describe 'when the request does not allow comments' do it 'should not have a comment link' do get :show, { :url_title => 'spam_1' }, @@ -253,7 +253,7 @@ describe RequestController, "when showing one request" do response.should_not have_tag('#anyone_actions', /Add an annotation/) end end - + describe 'when the request is being viewed by an admin' do describe 'if the request is awaiting description' do @@ -1746,6 +1746,17 @@ describe RequestController, "sending overdue request alerts" do assigns[:info_request].should == info_requests(:naughty_chicken_request) end + it "should not resend alerts to people who've already received them" do + chicken_request = info_requests(:naughty_chicken_request) + chicken_request.outgoing_messages[0].last_sent_at = Time.now() - 60.days + chicken_request.outgoing_messages[0].save! + RequestMailer.alert_overdue_requests + chicken_mails = ActionMailer::Base.deliveries.select{|x| x.body =~ /chickens/} + chicken_mails.size.should == 1 + RequestMailer.alert_overdue_requests + chicken_mails = ActionMailer::Base.deliveries.select{|x| x.body =~ /chickens/} + chicken_mails.size.should == 1 + end end describe RequestController, "sending unclassified new response reminder alerts" do -- cgit v1.2.3 From 61abdeeed3d18a2a23d4659df558894aab6b11bc Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 22 Oct 2012 18:38:33 +0100 Subject: Don't keep recalculating the request status --- app/controllers/request_controller.rb | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index 396e6593a..3296db6b7 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -446,18 +446,19 @@ class RequestController < ApplicationController return end + calculated_status = @info_request.calculate_status # Display advice for requester on what to do next, as appropriate - if @info_request.calculate_status == 'waiting_response' + if calculated_status == 'waiting_response' flash[:notice] = _("

    Thank you! Hopefully your wait isn't too long.

    By law, you should get a response promptly, and normally before the end of {{date_response_required_by}}.

    ",:date_response_required_by=>simple_date(@info_request.date_response_required_by)) redirect_to request_url(@info_request) - elsif @info_request.calculate_status == 'waiting_response_overdue' + elsif calculated_status == 'waiting_response_overdue' flash[:notice] = _("

    Thank you! Hope you don't have to wait much longer.

    By law, you should have got a response promptly, and normally before the end of {{date_response_required_by}}.

    ",:date_response_required_by=>simple_date(@info_request.date_response_required_by)) redirect_to request_url(@info_request) - elsif @info_request.calculate_status == 'waiting_response_very_overdue' + elsif calculated_status == 'waiting_response_very_overdue' flash[:notice] = _("

    Thank you! Your request is long overdue, by more than {{very_late_number_of_days}} working days. Most requests should be answered within {{late_number_of_days}} working days. You might like to complain about this, see below.

    ", :very_late_number_of_days => Configuration::reply_very_late_after_days, :late_number_of_days => Configuration::reply_late_after_days) redirect_to unhappy_url(@info_request) - elsif @info_request.calculate_status == 'not_held' + elsif calculated_status == 'not_held' flash[:notice] = _("

    Thank you! Here are some ideas on what to do next:

    • To send your request to another authority, first copy the text of your request below, then find the other authority.
    • @@ -472,37 +473,37 @@ class RequestController < ApplicationController :complain_url => CGI.escapeHTML(unhappy_url(@info_request)), :other_means_url => CGI.escapeHTML(unhappy_url(@info_request)) + "#other_means") redirect_to request_url(@info_request) - elsif @info_request.calculate_status == 'rejected' + elsif calculated_status == 'rejected' flash[:notice] = _("Oh no! Sorry to hear that your request was refused. Here is what to do now.") redirect_to unhappy_url(@info_request) - elsif @info_request.calculate_status == 'successful' + elsif calculated_status == 'successful' flash[:notice] = _("

      We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

      If you found {{site_name}} useful, make a donation to the charity which runs it.

      ", :site_name=>site_name, :donation_url => "http://www.mysociety.org/donate/") redirect_to request_url(@info_request) - elsif @info_request.calculate_status == 'partially_successful' + elsif calculated_status == 'partially_successful' flash[:notice] = _("

      We're glad you got some of the information that you wanted. If you found {{site_name}} useful, make a donation to the charity which runs it.

      If you want to try and get the rest of the information, here's what to do now.

      ", :site_name=>site_name, :donation_url=>"http://www.mysociety.org/donate/") redirect_to unhappy_url(@info_request) - elsif @info_request.calculate_status == 'waiting_clarification' + elsif calculated_status == 'waiting_clarification' flash[:notice] = _("Please write your follow up message containing the necessary clarifications below.") redirect_to respond_to_last_url(@info_request) - elsif @info_request.calculate_status == 'gone_postal' + elsif calculated_status == 'gone_postal' redirect_to respond_to_last_url(@info_request) + "?gone_postal=1" - elsif @info_request.calculate_status == 'internal_review' + elsif calculated_status == 'internal_review' flash[:notice] = _("

      Thank you! Hopefully your wait isn't too long.

      You should get a response within {{late_number_of_days}} days, or be told if it will take longer (details).

      ",:late_number_of_days => Configuration.reply_late_after_days, :review_url => unhappy_url(@info_request) + "#internal_review") redirect_to request_url(@info_request) - elsif @info_request.calculate_status == 'error_message' + elsif calculated_status == 'error_message' flash[:notice] = _("

      Thank you! We'll look into what happened and try and fix it up.

      If the error was a delivery failure, and you can find an up to date FOI email address for the authority, please tell us using the form below.

      ") redirect_to help_general_url(:action => 'contact') - elsif @info_request.calculate_status == 'requires_admin' + elsif calculated_status == 'requires_admin' flash[:notice] = _("Please use the form below to tell us more.") redirect_to help_general_url(:action => 'contact') - elsif @info_request.calculate_status == 'user_withdrawn' + elsif calculated_status == 'user_withdrawn' flash[:notice] = _("If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn.") redirect_to respond_to_last_url(@info_request) else if @@custom_states_loaded return self.theme_describe_state(@info_request) else - raise "unknown calculate_status " + @info_request.calculate_status + raise "unknown calculate_status " + calculated_status end end end -- cgit v1.2.3 From 9d10df84c9595a600fca3fa9dfd5e2db6fe22322 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 23 Oct 2012 09:05:18 +0100 Subject: Add extra checks from https://github.com/mysociety/alaveteli/commit/cdb1c9420169ed6eafdd4e57ca21e1809c95e5cb and some extra description. --- spec/controllers/request_controller_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb index 5867c994f..1f79b0ef1 100644 --- a/spec/controllers/request_controller_spec.rb +++ b/spec/controllers/request_controller_spec.rb @@ -1709,15 +1709,17 @@ describe RequestController, "sending overdue request alerts" do mail.to_addrs.first.to_s.should == info_requests(:naughty_chicken_request).user.name_and_email end - it "should send not actually send the overdue alert if the user is banned" do + it "should send not actually send the overdue alert if the user is banned but should + record it as sent" do user = info_requests(:naughty_chicken_request).user user.ban_text = 'Banned' user.save! - + UserInfoRequestSentAlert.find_all_by_user_id(user.id).count.should == 0 RequestMailer.alert_overdue_requests deliveries = ActionMailer::Base.deliveries deliveries.size.should == 0 + UserInfoRequestSentAlert.find_all_by_user_id(user.id).count.should > 0 end it "should send a very overdue alert mail to creators of very overdue requests" do -- cgit v1.2.3 From f71e933e7a535d1734b2e62d5a034316bf17d408 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 23 Oct 2012 09:07:04 +0100 Subject: Line length reformatting. --- app/models/request_mailer.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index 6f521484a..f4f69bbc6 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -256,7 +256,9 @@ class RequestMailer < ApplicationMailer def self.alert_overdue_requests() info_requests = InfoRequest.find(:all, :conditions => [ - "described_state = 'waiting_response' and awaiting_description = ? and user_id is not null", false + "described_state = 'waiting_response' + AND awaiting_description = ? + AND user_id is not null", false ], :include => [ :user ] ) -- cgit v1.2.3 From 8d9e273deb8a1365ec40fc0a395a07f69745725a Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 23 Oct 2012 09:07:28 +0100 Subject: Don't keep re-calling info_request.calculate_status --- app/models/request_mailer.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index f4f69bbc6..ec45ac42a 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -265,10 +265,11 @@ class RequestMailer < ApplicationMailer for info_request in info_requests alert_event_id = info_request.last_event_forming_initial_request.id # Only overdue requests - if ['waiting_response_overdue', 'waiting_response_very_overdue'].include?(info_request.calculate_status) - if info_request.calculate_status == 'waiting_response_overdue' + calculated_status = info_request.calculate_status + if ['waiting_response_overdue', 'waiting_response_very_overdue'].include?(calculated_status) + if calculated_status == 'waiting_response_overdue' alert_type = 'overdue_1' - elsif info_request.calculate_status == 'waiting_response_very_overdue' + elsif calculated_status == 'waiting_response_very_overdue' alert_type = 'very_overdue_1' else raise "unknown request status" @@ -293,9 +294,9 @@ class RequestMailer < ApplicationMailer # Only send the alert if the user can act on it by making a followup # (otherwise they are banned, and there is no point sending it) if info_request.user.can_make_followup? - if info_request.calculate_status == 'waiting_response_overdue' + if calculated_status == 'waiting_response_overdue' RequestMailer.deliver_overdue_alert(info_request, info_request.user) - elsif info_request.calculate_status == 'waiting_response_very_overdue' + elsif calculated_status == 'waiting_response_very_overdue' RequestMailer.deliver_very_overdue_alert(info_request, info_request.user) else raise "unknown request status" -- cgit v1.2.3 From 68c4c99c2f947d0b1cc275d2003d1b799ca411e2 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 23 Oct 2012 10:17:16 +0100 Subject: Add a clause to exclude from the original set of info requests to be processed any for which the last event of a kind which could form the initial request has already been alerted on. --- app/models/request_mailer.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index ec45ac42a..698d29995 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -258,7 +258,20 @@ class RequestMailer < ApplicationMailer :conditions => [ "described_state = 'waiting_response' AND awaiting_description = ? - AND user_id is not null", false + AND user_id is not null + AND (SELECT id + FROM user_info_request_sent_alerts + WHERE alert_type = 'very_overdue_1' + AND info_request_id = info_requests.id + AND user_id = info_requests.user_id + AND info_request_event_id = (SELECT max(id) + FROM info_request_events + WHERE event_type in ('sent', + 'followup-sent', + 'resent', + 'followup-resent') + AND info_request_id = info_requests.id) + ) IS NULL", false ], :include => [ :user ] ) -- cgit v1.2.3 From 8a7de1217cc1aedd240670bd24c8d51f3525e397 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 23 Oct 2012 14:28:38 +0100 Subject: Add spec - currently failing due to typo. --- spec/controllers/request_controller_spec.rb | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb index 1f79b0ef1..b0223588e 100644 --- a/spec/controllers/request_controller_spec.rb +++ b/spec/controllers/request_controller_spec.rb @@ -1759,6 +1759,48 @@ describe RequestController, "sending overdue request alerts" do chicken_mails = ActionMailer::Base.deliveries.select{|x| x.body =~ /chickens/} chicken_mails.size.should == 1 end + + it 'should send alerts for requests where the last event forming the initial request is a followup + being sent following a request for clarification' do + chicken_request = info_requests(:naughty_chicken_request) + chicken_request.outgoing_messages[0].last_sent_at = Time.now() - 60.days + chicken_request.outgoing_messages[0].save! + RequestMailer.alert_overdue_requests + chicken_mails = ActionMailer::Base.deliveries.select{|x| x.body =~ /chickens/} + chicken_mails.size.should == 1 + + # Request is waiting clarification + chicken_request.set_described_state('waiting_clarification') + + # Followup message is sent + outgoing_message = OutgoingMessage.new(:status => 'ready', + :message_type => 'followup', + :info_request_id => chicken_request.id, + :body => 'Some text', + :what_doing => 'normal_sort') + outgoing_message.send_message + outgoing_message.save! + + chicken_request = InfoRequest.find(chicken_request.id) + + # Last event forming the request is now the followup + chicken_request.last_event_forming_initial_request.event_type.should == 'followup_sent' + + # This isn't overdue, so no email + RequestMailer.alert_overdue_requests + chicken_mails = ActionMailer::Base.deliveries.select{|x| x.body =~ /chickens/} + chicken_mails.size.should == 1 + + # Make the followup older + outgoing_message.last_sent_at = Time.now() - 60.days + outgoing_message.save! + + # Now it should be alerted on + RequestMailer.alert_overdue_requests + chicken_mails = ActionMailer::Base.deliveries.select{|x| x.body =~ /chickens/} + chicken_mails.size.should == 2 + end + end describe RequestController, "sending unclassified new response reminder alerts" do -- cgit v1.2.3 From 34b07ca1937cc9b471b20db8a0e38eaac09bfde0 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 23 Oct 2012 14:28:53 +0100 Subject: Fix typo. --- app/models/request_mailer.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index 698d29995..9159a0219 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -267,15 +267,16 @@ class RequestMailer < ApplicationMailer AND info_request_event_id = (SELECT max(id) FROM info_request_events WHERE event_type in ('sent', - 'followup-sent', + 'followup_sent', 'resent', - 'followup-resent') + 'followup_resent') AND info_request_id = info_requests.id) ) IS NULL", false ], :include => [ :user ] ) for info_request in info_requests + puts "looking to send #{info_request.id}" alert_event_id = info_request.last_event_forming_initial_request.id # Only overdue requests calculated_status = info_request.calculate_status -- cgit v1.2.3 From 7cad6d60e25e5d1343b1955fd37785589da6b820 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 23 Oct 2012 14:46:13 +0100 Subject: Remove debug line. --- app/models/request_mailer.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index 9159a0219..90c4c6b53 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -276,7 +276,6 @@ class RequestMailer < ApplicationMailer :include => [ :user ] ) for info_request in info_requests - puts "looking to send #{info_request.id}" alert_event_id = info_request.last_event_forming_initial_request.id # Only overdue requests calculated_status = info_request.calculate_status -- cgit v1.2.3 From 646fffde374e575ab53cfae78e7a0c521cd90d6f Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 29 Oct 2012 19:44:16 +0000 Subject: Only get the holidays once per request. --- app/models/holiday.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/models/holiday.rb b/app/models/holiday.rb index d2437f438..13258396a 100644 --- a/app/models/holiday.rb +++ b/app/models/holiday.rb @@ -23,11 +23,12 @@ class Holiday < ActiveRecord::Base - def Holiday.weekend_or_holiday?(date) - # TODO only fetch holidays after the start_date - holidays = self.all.collect { |h| h.day }.to_set + def Holiday.holidays + @@holidays ||= self.all.collect { |h| h.day }.to_set + end - date.wday == 0 || date.wday == 6 || holidays.include?(date) + def Holiday.weekend_or_holiday?(date) + date.wday == 0 || date.wday == 6 || Holiday.holidays.include?(date) end def Holiday.due_date_from(start_date, days, type_of_days) -- cgit v1.2.3 From 05fc4076cd643e1cfeb9430296d394e8d456dcfd Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 13 Dec 2012 12:16:46 +0000 Subject: Don't offer or allow viewing of an HTML version of a response attachment if the request is hidden, or requester_only. Google docs viewer won't be able to access it, and our own conversion process currently can produce image files that will then be publicly viewable directly from the webserver (see config/httpd.conf). If necessary we can revisit this code to enable admins and requesters to view the HTML version created by our own conversion without adding these files to a path that is served directly by the web server. --- app/controllers/request_controller.rb | 6 ++++++ app/views/request/_bubble.rhtml | 16 ++++++++-------- spec/controllers/request_controller_spec.rb | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index c732a4b32..2c95114e6 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -743,6 +743,12 @@ class RequestController < ApplicationController end def get_attachment_as_html + + # The conversion process can generate files in the cache directory that can be served up + # directly by the webserver according to httpd.conf, so don't allow it unless that's OK. + if @files_can_be_cached != true + raise ActiveRecord::RecordNotFound.new("Attachment HTML not found.") + end get_attachment_internal(true) # images made during conversion (e.g. images in PDF files) are put in the cache directory, so diff --git a/app/views/request/_bubble.rhtml b/app/views/request/_bubble.rhtml index 331c2163e..747e2aa1f 100644 --- a/app/views/request/_bubble.rhtml +++ b/app/views/request/_bubble.rhtml @@ -1,16 +1,16 @@
      <% if not attachments.nil? and attachments.size > 0 %> -
      +

      <% attachments.each do |a| %>

      - <% + <% attachment_url = get_attachment_url(:id => incoming_message.info_request_id, - :incoming_message_id => incoming_message.id, :part => a.url_part_number, - :file_name => a.display_filename) + :incoming_message_id => incoming_message.id, :part => a.url_part_number, + :file_name => a.display_filename) attachment_as_html_url = get_attachment_as_html_url(:id => incoming_message.info_request_id, - :incoming_message_id => incoming_message.id, :part => a.url_part_number, - :file_name => a.display_filename + '.html') + :incoming_message_id => incoming_message.id, :part => a.url_part_number, + :file_name => a.display_filename + '.html') %> <% img_filename = "icon_" + a.content_type.sub('/', '_') + "_large.png" full_filename = File.expand_path(File.join(File.dirname(__FILE__), "../../../public/images", img_filename)) @@ -23,9 +23,9 @@
      <%= a.display_size %> <%= link_to "Download", attachment_url %> - <% if a.has_body_as_html? %> + <% if a.has_body_as_html? && incoming_message.info_request.all_can_view? %> <%= link_to "View as HTML", attachment_as_html_url %> - <% end %> + <% end %> <%= a.extra_note %>

      diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb index e898fb91b..63200fe09 100644 --- a/spec/controllers/request_controller_spec.rb +++ b/spec/controllers/request_controller_spec.rb @@ -859,6 +859,21 @@ describe RequestController, "when changing prominence of a request" do response.should render_template('request/hidden') end + it 'should not generate an HTML version of an attachment whose prominence is hidden/requester + only even for the requester or an admin but should return a 404' do + ir = info_requests(:fancy_dog_request) + ir.prominence = 'hidden' + ir.save! + receive_incoming_mail('incoming-request-two-same-name.email', ir.incoming_email) + session[:user_id] = users(:admin_user).id + lambda do + get :get_attachment_as_html, :incoming_message_id => ir.incoming_messages[1].id, + :id => ir.id, + :part => 2, + :file_name => ['hello.txt'] + end.should raise_error(ActiveRecord::RecordNotFound) + end + end # XXX do this for invalid ids -- cgit v1.2.3 From de20a797f247df93177e2754420d8478866a64b3 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 17 Dec 2012 15:32:39 +0000 Subject: Only serve up 'similar' pages up to the offset we use for list. --- app/controllers/request_controller.rb | 5 +++++ spec/controllers/request_controller_spec.rb | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index d8c34c2dd..5d950ceb2 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -139,6 +139,11 @@ class RequestController < ApplicationController short_cache @per_page = 25 @page = (params[:page] || "1").to_i + + # Later pages are very expensive to load + if @page > MAX_RESULTS / PER_PAGE + raise ActiveRecord::RecordNotFound.new("Sorry. No pages after #{MAX_RESULTS / PER_PAGE}.") + end @info_request = InfoRequest.find_by_url_title!(params[:url_title]) raise ActiveRecord::RecordNotFound.new("Request not found") if @info_request.nil? diff --git a/spec/controllers/request_controller_spec.rb b/spec/controllers/request_controller_spec.rb index 61415661c..a93c48d2f 100644 --- a/spec/controllers/request_controller_spec.rb +++ b/spec/controllers/request_controller_spec.rb @@ -2204,6 +2204,14 @@ describe RequestController, "when showing similar requests" do }.should raise_error(ActiveRecord::RecordNotFound) end + + it "should return 404 for pages we don't want to serve up" do + badger_request = info_requests(:badger_request) + lambda { + get :similar, :url_title => badger_request.url_title, :page => 100 + }.should raise_error(ActiveRecord::RecordNotFound) + end + end -- cgit v1.2.3 From 06887f4ebad15b8feee8864208a03bb85f328fac Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 17 Dec 2012 15:50:36 +0000 Subject: Limit pagination on similar pages in line with new upper limit on page offset. --- app/controllers/request_controller.rb | 2 ++ app/views/request/similar.rhtml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index 5d950ceb2..970dfca45 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -153,6 +153,8 @@ class RequestController < ApplicationController end @xapian_object = ::ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events, :offset => (@page - 1) * @per_page, :limit => @per_page, :collapse_by_prefix => 'request_collapse') + @matches_estimated = @xapian_object.matches_estimated + @show_no_more_than = (@matches_estimated > MAX_RESULTS) ? MAX_RESULTS : @matches_estimated if (@page > 1) @page_desc = " (page " + @page.to_s + ")" diff --git a/app/views/request/similar.rhtml b/app/views/request/similar.rhtml index d9806aeb1..0d53f6919 100644 --- a/app/views/request/similar.rhtml +++ b/app/views/request/similar.rhtml @@ -20,4 +20,4 @@ <% end %> <% end %> -<%= will_paginate WillPaginate::Collection.new(@page, @per_page, @xapian_object.matches_estimated) %> +<%= will_paginate WillPaginate::Collection.new(@page, @per_page, @show_no_more_than) %> -- cgit v1.2.3 From 33b0b98f260b5666cff90db7a6bc8ef45696ad30 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Fri, 21 Dec 2012 07:31:59 -0800 Subject: Handle the case of a comment on an external request. --- app/views/request/_request_listing_via_event.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/request/_request_listing_via_event.rhtml b/app/views/request/_request_listing_via_event.rhtml index ee1cc079a..2ba9613e5 100644 --- a/app/views/request/_request_listing_via_event.rhtml +++ b/app/views/request/_request_listing_via_event.rhtml @@ -25,7 +25,7 @@ end %> <%=event.display_status %> <%= _('by {{public_body_name}} to {{info_request_user}} on {{date}}.',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>request_user_link_absolute(info_request),:date=>simple_date(event.created_at )) %> <% elsif event.event_type == 'comment' %> - <%= _('Request to {{public_body_name}} by {{info_request_user}}. Annotated by {{event_comment_user}} on {{date}}.',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>user_link_absolute(info_request.user),:event_comment_user=>user_link_absolute(event.comment.user),:date=>simple_date(event.created_at)) %> + <%= _('Request to {{public_body_name}} by {{info_request_user}}. Annotated by {{event_comment_user}} on {{date}}.',:public_body_name=>public_body_link_absolute(info_request.public_body),:info_request_user=>request_user_link_absolute(info_request),:event_comment_user=>user_link_absolute(event.comment.user),:date=>simple_date(event.created_at)) %> <% else %> <%# Events of other types will not be indexed: see InfoRequestEvent#indexed_by_search? However, it can happen that we see other types of event transiently here in the period -- cgit v1.2.3 From 7506dd30d20a8c3d8f7535d2346292fb36337a8e Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 7 Jan 2013 12:00:30 +0000 Subject: Handle case where info request doesn't have a user_name --- app/models/incoming_message.rb | 1 + spec/models/incoming_message_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index 123319125..06fd94063 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -347,6 +347,7 @@ class IncomingMessage < ActiveRecord::Base # Lotus notes quoting yeuch! def remove_lotus_quoting(text, replacement = "FOLDED_QUOTED_SECTION") text = text.dup + return text if self.info_request.user_name.nil? name = Regexp.escape(self.info_request.user_name) # To end of message sections diff --git a/spec/models/incoming_message_spec.rb b/spec/models/incoming_message_spec.rb index fdbcd1e23..42e85633a 100644 --- a/spec/models/incoming_message_spec.rb +++ b/spec/models/incoming_message_spec.rb @@ -166,6 +166,13 @@ describe IncomingMessage, " folding quoted parts of emails" do @incoming_message.remove_lotus_quoting(text).should match(/FOLDED_QUOTED_SECTION/) end + it 'should not error when trying to fold lotus notes quoted parts on a request with no user_name' do + text = "hello" + @incoming_message = IncomingMessage.new() + @incoming_message.stub_chain(:info_request, :user_name).and_return(nil) + @incoming_message.remove_lotus_quoting(text).should == 'hello' + end + it "cope with [ in user names properly" do @incoming_message = IncomingMessage.new() @incoming_message.stub_chain(:info_request, :user_name).and_return("Sir [ Bobble") -- cgit v1.2.3 From 07121bf22b0929e03215f02b8740c3979ae6c872 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 14 Jan 2013 14:45:41 +0000 Subject: As we're validating filename with validates_presence_of, which doesn't allow blanks, ensure_filename! should populate a default filename on a blank filename, as well as on nil. --- app/models/foi_attachment.rb | 2 +- spec/models/foi_attachment_spec.rb | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/models/foi_attachment.rb b/app/models/foi_attachment.rb index a40898aef..2d3cce93a 100644 --- a/app/models/foi_attachment.rb +++ b/app/models/foi_attachment.rb @@ -205,7 +205,7 @@ class FoiAttachment < ActiveRecord::Base def ensure_filename! - if self.filename.nil? + if self.filename.blank? calc_ext = AlaveteliFileTypes.mimetype_to_extension(self.content_type) if !calc_ext calc_ext = "bin" diff --git a/spec/models/foi_attachment_spec.rb b/spec/models/foi_attachment_spec.rb index 9d44957e4..537a3363c 100644 --- a/spec/models/foi_attachment_spec.rb +++ b/spec/models/foi_attachment_spec.rb @@ -30,6 +30,17 @@ describe FoiAttachment, " when calculating due date" do main.delete_cached_file! main = im.get_main_body_text_part main.body.should == orig_body - + end end + +describe FoiAttachment, "when ensuring a filename is present" do + + it 'should create a filename for an instance with a blank filename' do + attachment = FoiAttachment.new + attachment.filename = '' + attachment.ensure_filename! + attachment.filename.should == 'attachment.bin' + end + +end -- cgit v1.2.3 From c4b2c65c0349b440303c67322ce370bcd8895630 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 15 Jan 2013 16:28:42 +0000 Subject: Update lock to latest copy of rails - 2.3.15 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 903562017..2b9ef5951 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/mysociety/rails.git - revision: 9e452eaf296d3732f8058ad31cf18e0b659f27a6 + revision: 541c061c13337b8235a1dcc2357b962f88868ff9 branch: 2-3-stable specs: actionmailer (2.3.15) @@ -89,7 +89,7 @@ GEM newrelic_rpm (3.5.4.34) pg (0.13.2) polyglot (0.3.3) - rack (1.1.4) + rack (1.1.5) rake (0.9.2.2) rbx-require-relative (0.0.9) rdoc (2.4.3) -- cgit v1.2.3 From f8cf582f8c7461bfd680aac9c87cb5377c39c30c Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 11 Feb 2013 21:39:19 +0000 Subject: Upgrade JSON gem to get fix for CVE-2013-0269. Update to latest Rails 2-3 series - has fixes for CVE-2013-0277, CVE-2013-0276, although alaveteli does not use attr_protected or serialize. --- Gemfile | 4 ++-- Gemfile.lock | 38 +++++++++++++++++++------------------- config/environment.rb | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Gemfile b/Gemfile index e103d5ba6..13dd66022 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ end source :rubygems # A fork of rails that is kept up to date with security patches -git "git://github.com/mysociety/rails.git", :tag => "v2.3.16.1" do +git "git://github.com/mysociety/rails.git", :tag => "v2.3.17.1" do gem 'rails' end gem 'pg' @@ -17,7 +17,7 @@ gem 'fast_gettext', '>= 0.6.0' gem 'fastercsv', '>=1.5.5' gem 'gettext_i18n_rails', '>= 0.7.1' gem 'gettext', '~> 2.3.3' -gem 'json', '~> 1.5.1' +gem 'json', '~> 1.5.5' gem 'mahoro' gem 'mail', :platforms => :ruby_19 gem 'memcache-client', :require => 'memcache' diff --git a/Gemfile.lock b/Gemfile.lock index 35a2f40be..4526c5786 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,24 +1,24 @@ GIT remote: git://github.com/mysociety/rails.git - revision: be86a2fd6264637a22e1a1aeb8a8ec979f53ec1e - tag: v2.3.16.1 + revision: 893560c501f2c3eda044938840108431dcf6ab91 + tag: v2.3.17.1 specs: - actionmailer (2.3.16) - actionpack (= 2.3.16) - actionpack (2.3.16) - activesupport (= 2.3.16) + actionmailer (2.3.17) + actionpack (= 2.3.17) + actionpack (2.3.17) + activesupport (= 2.3.17) rack (~> 1.1.0) - activerecord (2.3.16) - activesupport (= 2.3.16) - activeresource (2.3.16) - activesupport (= 2.3.16) - activesupport (2.3.16) - rails (2.3.16) - actionmailer (= 2.3.16) - actionpack (= 2.3.16) - activerecord (= 2.3.16) - activeresource (= 2.3.16) - activesupport (= 2.3.16) + activerecord (2.3.17) + activesupport (= 2.3.17) + activeresource (2.3.17) + activesupport (= 2.3.17) + activesupport (2.3.17) + rails (2.3.17) + actionmailer (= 2.3.17) + actionpack (= 2.3.17) + activerecord (= 2.3.17) + activeresource (= 2.3.17) + activesupport (= 2.3.17) rake (>= 0.8.3) GEM @@ -54,7 +54,7 @@ GEM hoe (3.0.8) rake (~> 0.8) i18n (0.6.1) - json (1.5.4) + json (1.5.5) linecache (0.46) rbx-require-relative (> 0.0.4) linecache19 (0.5.12) @@ -160,7 +160,7 @@ DEPENDENCIES fastercsv (>= 1.5.5) gettext (~> 2.3.3) gettext_i18n_rails (>= 0.7.1) - json (~> 1.5.1) + json (~> 1.5.5) locale (>= 2.0.5) mahoro mail diff --git a/config/environment.rb b/config/environment.rb index 8933f9ade..bdeb4c983 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -18,7 +18,7 @@ end # ENV['RAILS_ENV'] ||= 'production' # Specifies gem version of Rails to use when vendor/rails is not present -RAILS_GEM_VERSION = '2.3.16' unless defined? RAILS_GEM_VERSION +RAILS_GEM_VERSION = '2.3.17' unless defined? RAILS_GEM_VERSION # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') -- cgit v1.2.3 From e7b20617f3127a14df41a2b7dd732271fe5fbbea Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 14 Feb 2013 17:54:24 +0000 Subject: Silence printing of Erubis version number to stdout - can result in bounces to incoming mail depending on your mail config. --- vendor/plugins/rails_xss/lib/rails_xss/erubis.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vendor/plugins/rails_xss/lib/rails_xss/erubis.rb b/vendor/plugins/rails_xss/lib/rails_xss/erubis.rb index c8171c669..b8a239483 100644 --- a/vendor/plugins/rails_xss/lib/rails_xss/erubis.rb +++ b/vendor/plugins/rails_xss/lib/rails_xss/erubis.rb @@ -1,4 +1,10 @@ -require 'erubis/helpers/rails_helper' +# stop erubis from printing it's version number all the time +old_stdout = $stdout +File.open("/dev/null", "w") do |f| + $stdout = f + require 'erubis/helpers/rails_helper' + $stdout = old_stdout +end module RailsXss class Erubis < ::Erubis::Eruby -- cgit v1.2.3 From a8f4222974a36154234b32157e6b9556f2d43ed5 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 14 Feb 2013 18:14:57 +0000 Subject: Rename spec file so that it's picked up by rake spec. --- spec/script/mailin-spec.rb | 21 --------------------- spec/script/mailin_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 spec/script/mailin-spec.rb create mode 100644 spec/script/mailin_spec.rb diff --git a/spec/script/mailin-spec.rb b/spec/script/mailin-spec.rb deleted file mode 100644 index d80789635..000000000 --- a/spec/script/mailin-spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') -require "external_command" - -def mailin_test(email_filename) - Dir.chdir Rails.root do - xc = ExternalCommand.new("script/mailin") - xc.run(load_file_fixture(email_filename)) - xc.err.should == "" - return xc - end -end - -describe "When importing mail into the application" do - - it "should not produce any output and should return a 0 code on importing a plain email" do - r = mailin_test("incoming-request-plain.email") - r.status.should == 0 - r.out.should == "" - end - -end \ No newline at end of file diff --git a/spec/script/mailin_spec.rb b/spec/script/mailin_spec.rb new file mode 100644 index 000000000..d80789635 --- /dev/null +++ b/spec/script/mailin_spec.rb @@ -0,0 +1,21 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require "external_command" + +def mailin_test(email_filename) + Dir.chdir Rails.root do + xc = ExternalCommand.new("script/mailin") + xc.run(load_file_fixture(email_filename)) + xc.err.should == "" + return xc + end +end + +describe "When importing mail into the application" do + + it "should not produce any output and should return a 0 code on importing a plain email" do + r = mailin_test("incoming-request-plain.email") + r.status.should == 0 + r.out.should == "" + end + +end \ No newline at end of file -- cgit v1.2.3 From d310d96f50d8b339d0d3278d48c9412867f2a310 Mon Sep 17 00:00:00 2001 From: Matthew Landauer Date: Tue, 26 Feb 2013 14:21:38 +1100 Subject: Make flash message in admin interface html safe --- app/controllers/admin_request_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index e39d55c7c..f66c6c8d0 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -304,7 +304,7 @@ class AdminRequestController < AdminController post_redirect.save! url = main_url(confirm_url(:email_token => post_redirect.email_token, :only_path => true)) - flash[:notice] = 'Send "' + name + '" <' + email + '> this URL: ' + url + " - it will log them in and let them upload a response to this request.".html_safe + flash[:notice] = ('Send "' + name + '" <' + email + '> this URL: ' + url + " - it will log them in and let them upload a response to this request.").html_safe redirect_to request_admin_url(info_request) end -- cgit v1.2.3 From 7db25f53b981d7355b58b2b3cc9d85dee188bc06 Mon Sep 17 00:00:00 2001 From: Matthew Landauer Date: Tue, 26 Feb 2013 14:26:20 +1100 Subject: Use string interpolation to make the flash message more readable in the code --- app/controllers/admin_request_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index f66c6c8d0..c3bb597be 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -304,7 +304,7 @@ class AdminRequestController < AdminController post_redirect.save! url = main_url(confirm_url(:email_token => post_redirect.email_token, :only_path => true)) - flash[:notice] = ('Send "' + name + '" <' + email + '> this URL: ' + url + " - it will log them in and let them upload a response to this request.").html_safe + flash[:notice] = ("Send \"#{name}\" <#{email}> this URL: #{url} - it will log them in and let them upload a response to this request.").html_safe redirect_to request_admin_url(info_request) end -- cgit v1.2.3 From 8dc1a2d282f175c283f42cebdbb3797783a480af Mon Sep 17 00:00:00 2001 From: Matthew Landauer Date: Tue, 26 Feb 2013 14:40:16 +1100 Subject: Don't escape uploaded responses --- app/views/request_mailer/fake_response.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/request_mailer/fake_response.rhtml b/app/views/request_mailer/fake_response.rhtml index e9858f03f..896054a43 100644 --- a/app/views/request_mailer/fake_response.rhtml +++ b/app/views/request_mailer/fake_response.rhtml @@ -1 +1 @@ -<%=@body%> +<%= raw @body %> -- cgit v1.2.3 From e396265fb5ba6852d38cc5c5d1205b83d95de7d2 Mon Sep 17 00:00:00 2001 From: Matthew Landauer Date: Tue, 26 Feb 2013 14:46:38 +1100 Subject: Don't escape subject and body of new response emails --- app/models/request_mailer.rb | 2 +- app/views/request_mailer/new_response.rhtml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/request_mailer.rb b/app/models/request_mailer.rb index 493d6961c..73230a3a2 100644 --- a/app/models/request_mailer.rb +++ b/app/models/request_mailer.rb @@ -83,7 +83,7 @@ class RequestMailer < ApplicationMailer 'Auto-Submitted' => 'auto-generated', # http://tools.ietf.org/html/rfc3834 'X-Auto-Response-Suppress' => 'OOF' @recipients = info_request.user.name_and_email - @subject = _("New response to your FOI request - ") + info_request.title + @subject = (_("New response to your FOI request - ") + info_request.title).html_safe @body = { :incoming_message => incoming_message, :info_request => info_request, :url => url } end diff --git a/app/views/request_mailer/new_response.rhtml b/app/views/request_mailer/new_response.rhtml index 083f873b4..672212f20 100644 --- a/app/views/request_mailer/new_response.rhtml +++ b/app/views/request_mailer/new_response.rhtml @@ -1,6 +1,6 @@ <%= _('You have a new response to the {{law_used_full}} request ',:law_used_full=>@info_request.law_used_full)%> -'<%= @info_request.title %>' <%=_('that you made to')%> -<%= @info_request.public_body.name %>. +'<%= raw @info_request.title %>' <%=_('that you made to')%> +<%= raw @info_request.public_body.name %>. <%= _('To view the response, click on the link below.')%> -- cgit v1.2.3 From 6930cd39d8d6656b687f5e09fffa8018aa5786d5 Mon Sep 17 00:00:00 2001 From: Matthew Landauer Date: Tue, 26 Feb 2013 15:09:09 +1100 Subject: Don't escape content in the rest of the text mailer views Conflicts: app/views/contact_mailer/from_admin_message.rhtml --- app/views/contact_mailer/from_admin_message.rhtml | 3 +-- app/views/contact_mailer/to_admin_message.rhtml | 2 +- app/views/contact_mailer/user_message.rhtml | 2 +- app/views/request_mailer/external_response.rhtml | 2 +- app/views/request_mailer/new_response_reminder_alert.rhtml | 2 +- app/views/request_mailer/overdue_alert.rhtml | 2 +- app/views/request_mailer/requires_admin.rhtml | 4 ++-- app/views/request_mailer/very_overdue_alert.rhtml | 2 +- app/views/user_mailer/already_registered.rhtml | 4 ++-- app/views/user_mailer/changeemail_confirm.rhtml | 2 +- app/views/user_mailer/confirm_login.rhtml | 4 ++-- 11 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/views/contact_mailer/from_admin_message.rhtml b/app/views/contact_mailer/from_admin_message.rhtml index bdb48d580..b2acc5fb3 100644 --- a/app/views/contact_mailer/from_admin_message.rhtml +++ b/app/views/contact_mailer/from_admin_message.rhtml @@ -1,2 +1 @@ -<%= @message.strip %> - +<%= raw @message.strip %> diff --git a/app/views/contact_mailer/to_admin_message.rhtml b/app/views/contact_mailer/to_admin_message.rhtml index 9c0a74c02..8c56779fd 100644 --- a/app/views/contact_mailer/to_admin_message.rhtml +++ b/app/views/contact_mailer/to_admin_message.rhtml @@ -1,4 +1,4 @@ -<%= @message.strip %> +<%= raw @message.strip %> --------------------------------------------------------------------- <%= _('Message sent using {{site_name}} contact form, ', :site_name=>site_name)%> diff --git a/app/views/contact_mailer/user_message.rhtml b/app/views/contact_mailer/user_message.rhtml index b1d6e81ae..afa1494db 100644 --- a/app/views/contact_mailer/user_message.rhtml +++ b/app/views/contact_mailer/user_message.rhtml @@ -5,7 +5,7 @@ learn your email address. Only reply if that is okay.', :user_name => @from_user.name) %> --------------------------------------------------------------------- -<%= @message.strip %> +<%= raw @message.strip %> --------------------------------------------------------------------- <%= _('View Freedom of Information requests made by {{user_name}}:', :user_name=>@from_user.name)%> diff --git a/app/views/request_mailer/external_response.rhtml b/app/views/request_mailer/external_response.rhtml index e9858f03f..896054a43 100644 --- a/app/views/request_mailer/external_response.rhtml +++ b/app/views/request_mailer/external_response.rhtml @@ -1 +1 @@ -<%=@body%> +<%= raw @body %> diff --git a/app/views/request_mailer/new_response_reminder_alert.rhtml b/app/views/request_mailer/new_response_reminder_alert.rhtml index 86fc71de7..c196dafe6 100644 --- a/app/views/request_mailer/new_response_reminder_alert.rhtml +++ b/app/views/request_mailer/new_response_reminder_alert.rhtml @@ -3,7 +3,7 @@ <%=@url%> <%= _('Your request was called {{info_request}}. Letting everyone know whether you got the information will help us keep tabs on',:info_request=>@info_request.title)%> -<%= @info_request.public_body.name %>. +<%= raw @info_request.public_body.name %>. -- <%= _('the {{site_name}} team', :site_name=>site_name) %> diff --git a/app/views/request_mailer/overdue_alert.rhtml b/app/views/request_mailer/overdue_alert.rhtml index b8a9ba525..249bf6bb8 100644 --- a/app/views/request_mailer/overdue_alert.rhtml +++ b/app/views/request_mailer/overdue_alert.rhtml @@ -1,4 +1,4 @@ -<%= @info_request.public_body.name %> <%= _('have delayed.')%> +<%= raw @info_request.public_body.name %> <%= _('have delayed.')%> <%= _('They have not replied to your {{law_used_short}} request {{title}} promptly, as normally required by law',:law_used_short=>@info_request.law_used_short,:title=>@info_request.title)%><% if @info_request.public_body.is_school? %> <%=_('during term time')%> <% end %>. diff --git a/app/views/request_mailer/requires_admin.rhtml b/app/views/request_mailer/requires_admin.rhtml index 06a798792..e7ab53c59 100644 --- a/app/views/request_mailer/requires_admin.rhtml +++ b/app/views/request_mailer/requires_admin.rhtml @@ -1,9 +1,9 @@ --------------------------------------------------------------------- -<%=@reported_by.name%> <%= _('has reported an')%> <%=@info_request.law_used_short%> +<%= raw @reported_by.name %> <%= _('has reported an')%> <%= raw @info_request.law_used_short %> <%= _('response as needing administrator attention. Take a look, and reply to this email to let them know what you are going to do about it.')%> -Request '<%=@info_request.title%>': +Request '<%= raw @info_request.title %>': <%= @url %> <%= _('Administration URL:') %> diff --git a/app/views/request_mailer/very_overdue_alert.rhtml b/app/views/request_mailer/very_overdue_alert.rhtml index 6abd198a0..80597473c 100644 --- a/app/views/request_mailer/very_overdue_alert.rhtml +++ b/app/views/request_mailer/very_overdue_alert.rhtml @@ -1,4 +1,4 @@ -<%= @info_request.public_body.name %> <%= _('are long overdue.')%> +<%= raw @info_request.public_body.name %> <%= _('are long overdue.')%> <%= _('They have not replied to your {{law_used_short}} request {{title}}, as required by law',:law_used_short=>@info_request.law_used_short,:title=>@info_request.title)%><% if @info_request.public_body.is_school? %> <%= _('even during holidays')%><% end %>. diff --git a/app/views/user_mailer/already_registered.rhtml b/app/views/user_mailer/already_registered.rhtml index 59ffcbf94..32c2c7e63 100644 --- a/app/views/user_mailer/already_registered.rhtml +++ b/app/views/user_mailer/already_registered.rhtml @@ -1,10 +1,10 @@ -<%= @name %>, +<%= raw @name %>, <%= _('You just tried to sign up to {{site_name}}, when you already have an account. Your name and password have been left as they previously were. -Please click on the link below.', :site_name=>site_name)%> <%=@reasons[:email]%> +Please click on the link below.', :site_name=>site_name)%> <%=raw @reasons[:email] %> <%=@url%> diff --git a/app/views/user_mailer/changeemail_confirm.rhtml b/app/views/user_mailer/changeemail_confirm.rhtml index ffb9737f7..c73e9486b 100644 --- a/app/views/user_mailer/changeemail_confirm.rhtml +++ b/app/views/user_mailer/changeemail_confirm.rhtml @@ -1,4 +1,4 @@ -<%= @name %>, +<%= raw @name %>, <%= _('Please click on the link below to confirm that you want to change the email address that you use for {{site_name}} diff --git a/app/views/user_mailer/confirm_login.rhtml b/app/views/user_mailer/confirm_login.rhtml index 6f4feff00..fa86dc2b1 100644 --- a/app/views/user_mailer/confirm_login.rhtml +++ b/app/views/user_mailer/confirm_login.rhtml @@ -1,7 +1,7 @@ -<%= @name %>, +<%= raw @name %>, <%= _('Please click on the link below to confirm your email address.')%> -<%=@reasons[:email]%> +<%= raw @reasons[:email] %> <%=@url%> -- cgit v1.2.3 From a0ded883ac3e6f623ccb66316ed9c3e7a398bdca Mon Sep 17 00:00:00 2001 From: Matthew Landauer Date: Tue, 26 Feb 2013 14:17:58 +1100 Subject: Don't escape text of outgoing emails --- app/views/outgoing_mailer/followup.rhtml | 4 ++-- app/views/outgoing_mailer/initial_request.rhtml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/outgoing_mailer/followup.rhtml b/app/views/outgoing_mailer/followup.rhtml index 7050a295b..049ebc881 100644 --- a/app/views/outgoing_mailer/followup.rhtml +++ b/app/views/outgoing_mailer/followup.rhtml @@ -1,6 +1,6 @@ -<%= @outgoing_message.body.strip %> +<%= raw @outgoing_message.body.strip %> -<%= @outgoing_message.quoted_part_to_append_to_email.strip %> +<%= raw @outgoing_message.quoted_part_to_append_to_email.strip %> ------------------------------------------------------------------- <%= _('Please use this email address for all replies to this request:')%> diff --git a/app/views/outgoing_mailer/initial_request.rhtml b/app/views/outgoing_mailer/initial_request.rhtml index d537a20bc..5c418ecc7 100644 --- a/app/views/outgoing_mailer/initial_request.rhtml +++ b/app/views/outgoing_mailer/initial_request.rhtml @@ -1,4 +1,4 @@ -<%= @outgoing_message.body.strip %> +<%= raw @outgoing_message.body.strip %> ------------------------------------------------------------------- -- cgit v1.2.3 From ff822b780a1d5bc2438d9f865867b0065662a306 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 25 Apr 2013 09:34:38 +0100 Subject: Mark ban text as html safe --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 612ac7fa2..6b66e1a8d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -306,7 +306,7 @@ class User < ActiveRecord::Base text = CGI.escapeHTML(text) text = MySociety::Format.make_clickable(text, :contract => 1) text = text.gsub(/\n/, '
      ') - return text + return text.html_safe end # Returns domain part of user's email address -- cgit v1.2.3 From e3bf8f35ad14f833ccc9d664b2c16683d5a22c28 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 6 Jun 2013 11:18:55 +0100 Subject: Cope with replying to a message with a missing or empty Subject --- app/models/info_request.rb | 2 +- spec/models/info_request_spec.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/info_request.rb b/app/models/info_request.rb index cf1af0e87..c549d6f5d 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -294,7 +294,7 @@ public end end def email_subject_followup(incoming_message = nil) - if incoming_message.nil? || !incoming_message.valid_to_reply_to? + if incoming_message.nil? || !incoming_message.valid_to_reply_to? || !incoming_message.subject 'Re: ' + self.email_subject_request else if incoming_message.subject.match(/^Re:/i) diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb index b2f0a20fc..b32d8a04e 100644 --- a/spec/models/info_request_spec.rb +++ b/spec/models/info_request_spec.rb @@ -564,5 +564,17 @@ describe InfoRequest do end + describe 'when working out a subject for a followup emails' do + + it "should not be confused by an nil subject in the incoming message" do + ir = info_requests(:fancy_dog_request) + im = mock_model(IncomingMessage, + :subject => nil, + :valid_to_reply_to? => true) + subject = ir.email_subject_followup im + subject.should match(/^Re: Freedom of Information request.*fancy dog/) + end + + end end -- cgit v1.2.3 From d6dc9d2036654ab81cc32dc7a4e260f6f5d8c54c Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 10 Jun 2013 13:42:10 +0100 Subject: Add a rake task to re-extract any missing attachments You can see the number of emails that would be reparsed by doing: bundle exec rake temp:reextract_missing_attachments To actually reparse the incoming emails and rextract attachments for any that were missing, you would do: bundle exec rake temp:reextract_missing_attachments[commit] (In fact, the 'commit' can be any non-empty string.) --- lib/tasks/temp.rake | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/tasks/temp.rake b/lib/tasks/temp.rake index 35ae442c7..fcabb23de 100644 --- a/lib/tasks/temp.rake +++ b/lib/tasks/temp.rake @@ -7,6 +7,28 @@ namespace :temp do user.save! unless dryrun end + desc "Re-extract any missing cached attachments" + task :reextract_missing_attachments, [:commit] => :environment do |t, args| + dry_run = args.commit.nil? || args.commit.empty? + total_messages = 0 + messages_to_reparse = 0 + IncomingMessage.find_each :include => :foi_attachments do |im| + reparse = im.foi_attachments.any? { |fa| ! File.exists? fa.filepath } + total_messages += 1 + messages_to_reparse += 1 if reparse + if total_messages % 1000 == 0 + puts "Considered #{total_messages} received emails." + end + unless dry_run + im.parse_raw_email! true if reparse + sleep 2 + end + end + message = dry_run ? "Would reparse" : "Reparsed" + message += " #{messages_to_reparse} out of #{total_messages} received emails." + puts message + end + desc 'Cleanup accounts with a space in the email address' task :clean_up_emails_with_spaces => :environment do dryrun = ENV['DRYRUN'] == '0' ? false : true -- cgit v1.2.3 From 97f8d8340e867bcca0fd349e66a83a2860aaea9f Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 20 Jun 2013 14:12:43 -0700 Subject: Add pagination to the list of requests on the admin page for a public body --- app/controllers/admin_public_body_controller.rb | 3 +++ app/views/admin_public_body/show.html.erb | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb index 52b56eda2..078af12f4 100644 --- a/app/controllers/admin_public_body_controller.rb +++ b/app/controllers/admin_public_body_controller.rb @@ -75,6 +75,9 @@ class AdminPublicBodyController < AdminController @locale = self.locale_from_params() I18n.with_locale(@locale) do @public_body = PublicBody.find(params[:id]) + @info_requests = @public_body.info_requests.paginate :order => "created_at desc", + :page => params[:page], + :per_page => 100 render end end diff --git a/app/views/admin_public_body/show.html.erb b/app/views/admin_public_body/show.html.erb index cfb10b24e..8262287d5 100644 --- a/app/views/admin_public_body/show.html.erb +++ b/app/views/admin_public_body/show.html.erb @@ -83,7 +83,8 @@ <% end %>

      Requests

      -<%= render :partial => 'admin_request/some_requests', :locals => { :info_requests => @public_body.info_requests } %> +<%= render :partial => 'admin_request/some_requests', :locals => { :info_requests => @info_requests } %> +<%= will_paginate(@info_requests, :class => "paginator") %>

      Track things

      <%= render :partial => 'admin_track/some_tracks', :locals => { :track_things => @public_body.track_things, :include_destroy => true } %> -- cgit v1.2.3 From 884eb6b5e51b9e0e8aa5ae5ed0eeb25fcbda685e Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 20 Jun 2013 14:47:56 -0700 Subject: Respond to a (currently unsupported) json request for a public body list with a 406, not a 500 caused by a missing template. --- app/controllers/public_body_controller.rb | 4 +++- spec/controllers/public_body_controller_spec.rb | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 74ea043bb..374866eda 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -131,7 +131,9 @@ class PublicBodyController < ApplicationController @public_bodies = PublicBody.where(conditions).joins(:translations).order("public_body_translations.name").paginate( :page => params[:page], :per_page => 100 ) - render :template => "public_body/list" + respond_to do |format| + format.html { render :template => "public_body/list" } + end end end diff --git a/spec/controllers/public_body_controller_spec.rb b/spec/controllers/public_body_controller_spec.rb index 22d8418c9..e01bcb0a6 100644 --- a/spec/controllers/public_body_controller_spec.rb +++ b/spec/controllers/public_body_controller_spec.rb @@ -183,8 +183,11 @@ describe PublicBodyController, "when listing bodies" do response.should render_template('list') assigns[:public_bodies].should == [ public_bodies(:humpadink_public_body) ] assigns[:tag].should == "eats_cheese:stilton" + end - + it 'should return a "406 Not Acceptable" code if asked for a json version of a list' do + get :list, :format => 'json' + response.code.should == '406' end end -- cgit v1.2.3 From b000f049c8447cd96dcd9b1faad2ba9650910ee5 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Fri, 21 Jun 2013 14:03:47 -0700 Subject: Handle the case where an encoding is extracted that iconv doesn't handle. --- lib/mail_handler/backends/mail_extensions.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index 322c49bb5..6d6a20f8c 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -78,7 +78,10 @@ module Mail # invalid character at the end of the string, even # with UTF-8//IGNORE: # http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/ - str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str + " ")[0...-4] + begin + str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str + " ")[0...-4] + rescue Iconv::InvalidEncoding + end end str end -- cgit v1.2.3 From 66a66e00561afba01cd7fcf99b4b38a8c4dc675c Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 15 Jul 2013 10:35:22 +0100 Subject: Ignore exceptions when re-extracting attachments This can be a very long-running script, and there is an exception thrown on re-extraction once every 50,000 emails or so. So, just catch any StandardError, output details of the exception and the IncomingMessage ID and then carry on. --- lib/tasks/temp.rake | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/tasks/temp.rake b/lib/tasks/temp.rake index fcabb23de..ddc3aabd7 100644 --- a/lib/tasks/temp.rake +++ b/lib/tasks/temp.rake @@ -13,15 +13,21 @@ namespace :temp do total_messages = 0 messages_to_reparse = 0 IncomingMessage.find_each :include => :foi_attachments do |im| - reparse = im.foi_attachments.any? { |fa| ! File.exists? fa.filepath } - total_messages += 1 - messages_to_reparse += 1 if reparse - if total_messages % 1000 == 0 - puts "Considered #{total_messages} received emails." - end - unless dry_run - im.parse_raw_email! true if reparse - sleep 2 + begin + reparse = im.foi_attachments.any? { |fa| ! File.exists? fa.filepath } + total_messages += 1 + messages_to_reparse += 1 if reparse + if total_messages % 1000 == 0 + puts "Considered #{total_messages} received emails." + end + unless dry_run + im.parse_raw_email! true if reparse + sleep 2 + end + rescue StandardError => e + puts "There was a #{e.class} exception reparsing IncomingMessage with ID #{im.id}" + puts e.backtrace + puts e.message end end message = dry_run ? "Would reparse" : "Reparsed" -- cgit v1.2.3 From 535915b50a45dcac4719b0b506ed6f67c996252a Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 24 Jul 2013 14:25:55 +0100 Subject: Update commonlib. --- commonlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonlib b/commonlib index 403047368..770fa9e55 160000 --- a/commonlib +++ b/commonlib @@ -1 +1 @@ -Subproject commit 4030473685e388acc75c428ed36267acc62b571a +Subproject commit 770fa9e556fb8138e7715e4b828f8c2e46fafa5d -- cgit v1.2.3 From c449240a1b1cddebb53e9748b15be43c0f65c463 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 25 Jul 2013 14:40:45 +0100 Subject: New version of commonlib --- commonlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonlib b/commonlib index 770fa9e55..b5e4bada6 160000 --- a/commonlib +++ b/commonlib @@ -1 +1 @@ -Subproject commit 770fa9e556fb8138e7715e4b828f8c2e46fafa5d +Subproject commit b5e4bada6633bc39b708714d1948706c83d2367a -- cgit v1.2.3 From bce48a138d6033b7e927ecd8b7b4cec6d15fbe62 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 20 Aug 2013 13:03:55 +0100 Subject: Handle case of nil user_id When status was updated by a script. --- app/views/admin_general/timeline.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin_general/timeline.html.erb b/app/views/admin_general/timeline.html.erb index 8fd8875b6..439ae1e68 100644 --- a/app/views/admin_general/timeline.html.erb +++ b/app/views/admin_general/timeline.html.erb @@ -88,7 +88,7 @@ <% elsif event.event_type == 'comment' %> had an annotation posted by <%=h event.comment.user.name %>. <% elsif event.event_type == 'status_update' %> - had its status updated by <%=h User.find(event.params[:user_id]).name %> from '<%= h event.params[:old_described_state] %>' to '<%= h event.params[:described_state] %>'. + had its status updated by <%= event.params[:user_id] ? User.find(event.params[:user_id]).name : event.params[:script] %> from '<%= h event.params[:old_described_state] %>' to '<%= h event.params[:described_state] %>'. <% else %> had '<%=event.event_type%>' done to it, parameters <%=h event.params_yaml%>. <% end %> -- cgit v1.2.3 From fea5f1580608abf81f14c960f590b9395c9edaf8 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 24 Sep 2013 10:30:46 +0100 Subject: Fix typo --- app/views/request/_correspondence.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/request/_correspondence.html.erb b/app/views/request/_correspondence.html.erb index 872761749..c39625c3c 100644 --- a/app/views/request/_correspondence.html.erb +++ b/app/views/request/_correspondence.html.erb @@ -5,7 +5,7 @@ <% when 'sent', 'followup_sent' %> <%= render :partial => 'request/outgoing_correspondence', :locals => { :outgoing_message => info_request_event.outgoing_message, :info_request_event => info_request_event }%> <% when 'resent', 'followup_resent' %> - <%= render :partial => 'request/resent_outgoing_correspondence', :locals => { outgoing_message => info_request_event.outgoing_message, :info_request_event => info_request_event }%> + <%= render :partial => 'request/resent_outgoing_correspondence', :locals => { :outgoing_message => info_request_event.outgoing_message, :info_request_event => info_request_event }%> <% when 'comment' %> <%= render :partial => 'comment/single_comment', :locals => { :comment => info_request_event.comment } %> <% end %> -- cgit v1.2.3 From bd81b21cad716899312e73c0129a09d54154a5a7 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 30 Sep 2013 09:01:50 +0100 Subject: Beginning of switch to using the asset pipeline --- Gemfile | 5 +++++ Gemfile.lock | 10 ++++++++++ app/assets/stylesheets/application.css | 3 +++ config/application.rb | 12 +++++++++++- config/environments/development.rb | 7 +++++++ config/environments/production.rb | 17 +++++++++++++++++ 6 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 app/assets/stylesheets/application.css diff --git a/Gemfile b/Gemfile index 04fa16eba..0d870c149 100644 --- a/Gemfile +++ b/Gemfile @@ -51,6 +51,11 @@ gem 'routing-filter' gem 'unicode' gem 'unidecode' +group :assets do + gem 'sass-rails', "~> 3.1" + gem 'uglifier' +end + group :test do gem 'fakeweb' gem 'coveralls', :require => false diff --git a/Gemfile.lock b/Gemfile.lock index 4494c2342..f60bcd1a4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -85,6 +85,7 @@ GEM eventmachine (1.0.3) exception_notification (3.0.1) actionmailer (>= 3.0.4) + execjs (2.0.1) factory_girl (2.6.4) activesupport (>= 2.3.9) factory_girl_rails (1.7.0) @@ -202,6 +203,10 @@ GEM vpim (>= 0.360) ruby-ole (1.2.11.6) sass (3.2.9) + sass-rails (3.1.0) + actionpack (~> 3.1.0) + railties (~> 3.1.0) + sass (>= 3.1.4) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) @@ -235,6 +240,9 @@ GEM polyglot polyglot (>= 0.3.1) tzinfo (0.3.37) + uglifier (2.2.1) + execjs (>= 0.3.0) + multi_json (~> 1.0, >= 1.0.2) unicode (0.4.4) unidecode (1.0.0) vpim (0.695) @@ -289,9 +297,11 @@ DEPENDENCIES rspec-rails ruby-debug ruby-msg (~> 1.5.0) + sass-rails (~> 3.1) spork-rails statistics2 (~> 0.54) syslog_protocol + uglifier unicode unidecode vpim diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 000000000..9f66182a9 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,3 @@ +/* ... +*= require_self +*/ diff --git a/config/application.rb b/config/application.rb index 92fd30685..5ce58b588 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,7 +6,7 @@ require File.dirname(__FILE__) + '/../lib/configuration' # If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) if defined?(Bundler) +Bundler.require(:default, :assets, Rails.env) if defined?(Bundler) module Alaveteli class Application < Rails::Application @@ -71,5 +71,15 @@ module Alaveteli # Insert a bit of middleware code to prevent uneeded cookie setting. require "#{Rails.root}/lib/whatdotheyknow/strip_empty_sessions" config.middleware.insert_before ActionDispatch::Session::CookieStore, WhatDoTheyKnow::StripEmptySessions, :key => '_wdtk_cookie_session', :path => "/", :httponly => true + + # Enable the asset pipeline + config.assets.enabled = true + + # Version of your assets, change this if you want to expire all your assets + config.assets.version = '1.0' + + # Change the path that assets are served from + # config.assets.prefix = "/assets" + end end diff --git a/config/environments/development.rb b/config/environments/development.rb index 54ab2977f..629685c50 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -27,4 +27,11 @@ Alaveteli::Application.configure do # Print deprecation notices to the Rails logger config.active_support.deprecation = :log + + # Do not compress assets + config.assets.compress = false + + # Expands the lines which load the assets + config.assets.debug = true + end diff --git a/config/environments/production.rb b/config/environments/production.rb index 0c1929366..a3e3cebd2 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -31,4 +31,21 @@ Alaveteli::Application.configure do if AlaveteliConfiguration::force_ssl config.middleware.insert_before ActionDispatch::Cookies, ::Rack::SSL end + + # Compress JavaScripts and CSS + config.assets.compress = true + + # Choose the compressors to use + # config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :yui + + # Don't fallback to assets pipeline if a precompiled asset is missed + config.assets.compile = false + + # Generate digests for assets URLs. + config.assets.digest = true + + # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) + # config.assets.precompile += %w( search.js ) + end -- cgit v1.2.3 From 7b8d0fc0e9fd3883b4a961d733115b031bd30d51 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 25 Sep 2013 16:02:32 +0100 Subject: Adding rubyracer to supply a js runtime --- Gemfile | 1 + Gemfile.lock | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/Gemfile b/Gemfile index 0d870c149..0d1197c58 100644 --- a/Gemfile +++ b/Gemfile @@ -54,6 +54,7 @@ gem 'unidecode' group :assets do gem 'sass-rails', "~> 3.1" gem 'uglifier' + gem 'therubyracer' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index f60bcd1a4..a304f1791 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,6 +109,7 @@ GEM railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.0) + libv8 (3.16.14.3) linecache (0.46) rbx-require-relative (> 0.0.4) locale (2.0.8) @@ -177,6 +178,7 @@ GEM rdoc (3.12.2) json (~> 1.4) recaptcha (0.3.5) + ref (1.0.5) rest-client (1.6.7) mime-types (>= 1.16) rmagick (2.13.2) @@ -230,6 +232,9 @@ GEM statistics2 (0.54) syslog_protocol (0.9.2) text (1.2.1) + therubyracer (0.12.0) + libv8 (~> 3.16.14.0) + ref thin (1.5.1) daemons (>= 1.0.9) eventmachine (>= 0.12.6) @@ -301,6 +306,7 @@ DEPENDENCIES spork-rails statistics2 (~> 0.54) syslog_protocol + therubyracer uglifier unicode unidecode -- cgit v1.2.3 From 5b3f408bea1262d451b69b85186ec100f0737b36 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 25 Sep 2013 16:52:45 +0100 Subject: Move header to partial to make it easier to customize. --- app/views/general/_header.html.erb | 37 +++++++++++++++++++++++++++++++++++++ app/views/layouts/default.html.erb | 38 +------------------------------------- 2 files changed, 38 insertions(+), 37 deletions(-) create mode 100644 app/views/general/_header.html.erb diff --git a/app/views/general/_header.html.erb b/app/views/general/_header.html.erb new file mode 100644 index 000000000..55bf719e2 --- /dev/null +++ b/app/views/general/_header.html.erb @@ -0,0 +1,37 @@ + diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 5895becf7..1648e6af3 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -77,43 +77,7 @@ <% end %>
      - + <%= render :partial => 'general/header' %>
      <% if flash[:notice] %> -- cgit v1.2.3 From 156fe47124d2d0001c1f5050695742b7022bda3c Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 26 Sep 2013 10:54:32 +0100 Subject: Upgrade sass-rails An attempt to get image-path to work in sass files. --- Gemfile | 2 +- Gemfile.lock | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 0d1197c58..9250a374b 100644 --- a/Gemfile +++ b/Gemfile @@ -52,7 +52,7 @@ gem 'unicode' gem 'unidecode' group :assets do - gem 'sass-rails', "~> 3.1" + gem 'sass-rails', "= 3.1.4" gem 'uglifier' gem 'therubyracer' end diff --git a/Gemfile.lock b/Gemfile.lock index a304f1791..2f1853a3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,8 +103,8 @@ GEM haml (4.0.3) tilt highline (1.6.19) - hike (1.2.2) - i18n (0.6.4) + hike (1.2.3) + i18n (0.6.5) jquery-rails (2.3.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) @@ -128,7 +128,7 @@ GEM sqlite3 (~> 1.3) thin (~> 1.5.0) mime-types (1.23) - multi_json (1.7.4) + multi_json (1.8.0) net-http-local (0.1.2) net-purge (0.1.0) net-scp (1.1.1) @@ -204,11 +204,13 @@ GEM ruby-ole (>= 1.2.8) vpim (>= 0.360) ruby-ole (1.2.11.6) - sass (3.2.9) - sass-rails (3.1.0) + sass (3.2.10) + sass-rails (3.1.4) actionpack (~> 3.1.0) railties (~> 3.1.0) sass (>= 3.1.4) + sprockets (~> 2.0.0) + tilt (~> 1.3.2) simplecov (0.7.1) multi_json (~> 1.0) simplecov-html (~> 0.7.1) @@ -240,7 +242,7 @@ GEM eventmachine (>= 0.12.6) rack (>= 1.0.0) thor (0.14.6) - tilt (1.4.1) + tilt (1.3.7) treetop (1.4.12) polyglot polyglot (>= 0.3.1) @@ -302,7 +304,7 @@ DEPENDENCIES rspec-rails ruby-debug ruby-msg (~> 1.5.0) - sass-rails (~> 3.1) + sass-rails (= 3.1.4) spork-rails statistics2 (~> 0.54) syslog_protocol -- cgit v1.2.3 From 36a22217703ab9d55989b2a52f49b6b045a605b4 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 26 Sep 2013 10:50:12 +0100 Subject: Switch Javascript (bar admin) to be served with the asset pipline --- app/assets/javascripts/application.js | 6 + app/assets/javascripts/ba-throttle-debounce.js | 9 + app/assets/javascripts/bootstrap-collapse.js | 138 +++++++ app/assets/javascripts/bootstrap-tab.js | 130 ++++++ app/assets/javascripts/excanvas.min.js | 1 + app/assets/javascripts/general.js | 53 +++ app/assets/javascripts/jquery-ui.min.js | 168 ++++++++ app/assets/javascripts/jquery.Jcrop.js | 163 ++++++++ app/assets/javascripts/jquery.cookie.js | 41 ++ .../javascripts/jquery.fancybox-1.3.4.pack.js | 46 +++ app/assets/javascripts/jquery.flot.axislabels.js | 451 +++++++++++++++++++++ .../javascripts/jquery.flot.errorbars.min.js | 63 +++ app/assets/javascripts/jquery.flot.min.js | 29 ++ app/assets/javascripts/jquery.form.js | 11 + app/assets/javascripts/jquery.js | 4 + app/assets/javascripts/jquery_ujs.js | 393 ++++++++++++++++++ app/assets/javascripts/profile-photos.js | 3 + app/assets/javascripts/profile_photo.js | 49 +++ app/assets/javascripts/stats-graphs.js | 87 ++++ app/assets/javascripts/stats.js | 5 + app/views/layouts/default.html.erb | 5 +- app/views/layouts/no_chrome.html.erb | 2 +- app/views/public_body/statistics.html.erb | 4 +- app/views/request/new.html.erb | 1 - app/views/request/select_authority.html.erb | 1 - config/application.rb | 10 + public/javascripts/application.js | 2 - public/javascripts/ba-throttle-debounce.js | 9 - public/javascripts/bootstrap-collapse.js | 138 ------- public/javascripts/bootstrap-tab.js | 130 ------ public/javascripts/excanvas.min.js | 1 - public/javascripts/general.js | 53 --- public/javascripts/jquery-ui.min.js | 168 -------- public/javascripts/jquery.Jcrop.js | 163 -------- public/javascripts/jquery.cookie.js | 41 -- public/javascripts/jquery.fancybox-1.3.4.pack.js | 46 --- public/javascripts/jquery.flot.axislabels.js | 451 --------------------- public/javascripts/jquery.flot.errorbars.min.js | 63 --- public/javascripts/jquery.flot.min.js | 29 -- public/javascripts/jquery.form.js | 11 - public/javascripts/jquery.js | 4 - public/javascripts/jquery_ujs.js | 393 ------------------ public/javascripts/profile_photo.js | 49 --- public/javascripts/stats-graphs.js | 87 ---- 44 files changed, 1865 insertions(+), 1846 deletions(-) create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/javascripts/ba-throttle-debounce.js create mode 100644 app/assets/javascripts/bootstrap-collapse.js create mode 100644 app/assets/javascripts/bootstrap-tab.js create mode 100644 app/assets/javascripts/excanvas.min.js create mode 100644 app/assets/javascripts/general.js create mode 100644 app/assets/javascripts/jquery-ui.min.js create mode 100644 app/assets/javascripts/jquery.Jcrop.js create mode 100644 app/assets/javascripts/jquery.cookie.js create mode 100755 app/assets/javascripts/jquery.fancybox-1.3.4.pack.js create mode 100644 app/assets/javascripts/jquery.flot.axislabels.js create mode 100644 app/assets/javascripts/jquery.flot.errorbars.min.js create mode 100644 app/assets/javascripts/jquery.flot.min.js create mode 100644 app/assets/javascripts/jquery.form.js create mode 100644 app/assets/javascripts/jquery.js create mode 100644 app/assets/javascripts/jquery_ujs.js create mode 100644 app/assets/javascripts/profile-photos.js create mode 100644 app/assets/javascripts/profile_photo.js create mode 100644 app/assets/javascripts/stats-graphs.js create mode 100644 app/assets/javascripts/stats.js delete mode 100644 public/javascripts/application.js delete mode 100644 public/javascripts/ba-throttle-debounce.js delete mode 100644 public/javascripts/bootstrap-collapse.js delete mode 100644 public/javascripts/bootstrap-tab.js delete mode 100644 public/javascripts/excanvas.min.js delete mode 100644 public/javascripts/general.js delete mode 100644 public/javascripts/jquery-ui.min.js delete mode 100644 public/javascripts/jquery.Jcrop.js delete mode 100644 public/javascripts/jquery.cookie.js delete mode 100755 public/javascripts/jquery.fancybox-1.3.4.pack.js delete mode 100644 public/javascripts/jquery.flot.axislabels.js delete mode 100644 public/javascripts/jquery.flot.errorbars.min.js delete mode 100644 public/javascripts/jquery.flot.min.js delete mode 100644 public/javascripts/jquery.form.js delete mode 100644 public/javascripts/jquery.js delete mode 100644 public/javascripts/jquery_ujs.js delete mode 100644 public/javascripts/profile_photo.js delete mode 100644 public/javascripts/stats-graphs.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 000000000..f92f0562a --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,6 @@ +// ... +//= require jquery +//= require jquery-ui.min +//= require jquery.cookie +//= require general +//= require ba-throttle-debounce diff --git a/app/assets/javascripts/ba-throttle-debounce.js b/app/assets/javascripts/ba-throttle-debounce.js new file mode 100644 index 000000000..07205508e --- /dev/null +++ b/app/assets/javascripts/ba-throttle-debounce.js @@ -0,0 +1,9 @@ +/* + * jQuery throttle / debounce - v1.1 - 3/7/2010 + * http://benalman.com/projects/jquery-throttle-debounce-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); \ No newline at end of file diff --git a/app/assets/javascripts/bootstrap-collapse.js b/app/assets/javascripts/bootstrap-collapse.js new file mode 100644 index 000000000..9a364468b --- /dev/null +++ b/app/assets/javascripts/bootstrap-collapse.js @@ -0,0 +1,138 @@ +/* ============================================================= + * bootstrap-collapse.js v2.0.2 + * http://twitter.github.com/bootstrap/javascript.html#collapse + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + +!function( $ ){ + + "use strict" + + var Collapse = function ( element, options ) { + this.$element = $(element) + this.options = $.extend({}, $.fn.collapse.defaults, options) + + if (this.options["parent"]) { + this.$parent = $(this.options["parent"]) + } + + this.options.toggle && this.toggle() + } + + Collapse.prototype = { + + constructor: Collapse + + , dimension: function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + , show: function () { + var dimension = this.dimension() + , scroll = $.camelCase(['scroll', dimension].join('-')) + , actives = this.$parent && this.$parent.find('.in') + , hasData + + if (actives && actives.length) { + hasData = actives.data('collapse') + actives.collapse('hide') + hasData || actives.data('collapse', null) + } + + this.$element[dimension](0) + this.transition('addClass', 'show', 'shown') + this.$element[dimension](this.$element[0][scroll]) + + } + + , hide: function () { + var dimension = this.dimension() + this.reset(this.$element[dimension]()) + this.transition('removeClass', 'hide', 'hidden') + this.$element[dimension](0) + } + + , reset: function ( size ) { + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + [dimension](size || 'auto') + [0].offsetWidth + + this.$element[size ? 'addClass' : 'removeClass']('collapse') + + return this + } + + , transition: function ( method, startEvent, completeEvent ) { + var that = this + , complete = function () { + if (startEvent == 'show') that.reset() + that.$element.trigger(completeEvent) + } + + this.$element + .trigger(startEvent) + [method]('in') + + $.support.transition && this.$element.hasClass('collapse') ? + this.$element.one($.support.transition.end, complete) : + complete() + } + + , toggle: function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + } + + /* COLLAPSIBLE PLUGIN DEFINITION + * ============================== */ + + $.fn.collapse = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('collapse') + , options = typeof option == 'object' && option + if (!data) $this.data('collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.defaults = { + toggle: true + } + + $.fn.collapse.Constructor = Collapse + + + /* COLLAPSIBLE DATA-API + * ==================== */ + + $(function () { + $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { + var $this = $(this), href + , target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + , option = $(target).data('collapse') ? 'toggle' : $this.data() + $(target).collapse(option) + }) + }) + +}( window.jQuery ); \ No newline at end of file diff --git a/app/assets/javascripts/bootstrap-tab.js b/app/assets/javascripts/bootstrap-tab.js new file mode 100644 index 000000000..26c9ece75 --- /dev/null +++ b/app/assets/javascripts/bootstrap-tab.js @@ -0,0 +1,130 @@ +/* ======================================================== + * bootstrap-tab.js v2.0.1 + * http://twitter.github.com/bootstrap/javascript.html#tabs + * ======================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================== */ + + +!function( $ ){ + + "use strict" + + /* TAB CLASS DEFINITION + * ==================== */ + + var Tab = function ( element ) { + this.element = $(element) + } + + Tab.prototype = { + + constructor: Tab + + , show: function () { + var $this = this.element + , $ul = $this.closest('ul:not(.dropdown-menu)') + , selector = $this.attr('data-target') + , previous + , $target + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + if ( $this.parent('li').hasClass('active') ) return + + previous = $ul.find('.active a').last()[0] + + $this.trigger({ + type: 'show' + , relatedTarget: previous + }) + + $target = $(selector) + + this.activate($this.parent('li'), $ul) + this.activate($target, $target.parent(), function () { + $this.trigger({ + type: 'shown' + , relatedTarget: previous + }) + }) + } + + , activate: function ( element, container, callback) { + var $active = container.find('> .active') + , transition = callback + && $.support.transition + && $active.hasClass('fade') + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + + element.addClass('active') + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if ( element.parent('.dropdown-menu') ) { + element.closest('li.dropdown').addClass('active') + } + + callback && callback() + } + + transition ? + $active.one($.support.transition.end, next) : + next() + + $active.removeClass('in') + } + } + + + /* TAB PLUGIN DEFINITION + * ===================== */ + + $.fn.tab = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('tab') + if (!data) $this.data('tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.tab.Constructor = Tab + + + /* TAB DATA-API + * ============ */ + + $(function () { + $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { + e.preventDefault() + $(this).tab('show') + }) + }) + +}( window.jQuery ); \ No newline at end of file diff --git a/app/assets/javascripts/excanvas.min.js b/app/assets/javascripts/excanvas.min.js new file mode 100644 index 000000000..fcf876c74 --- /dev/null +++ b/app/assets/javascripts/excanvas.min.js @@ -0,0 +1 @@ +if(!document.createElement("canvas").getContext){(function(){var ab=Math;var n=ab.round;var l=ab.sin;var A=ab.cos;var H=ab.abs;var N=ab.sqrt;var d=10;var f=d/2;var z=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];function y(){return this.context_||(this.context_=new D(this))}var t=Array.prototype.slice;function g(j,m,p){var i=t.call(arguments,2);return function(){return j.apply(m,i.concat(t.call(arguments)))}}function af(i){return String(i).replace(/&/g,"&").replace(/"/g,""")}function Y(m,j,i){if(!m.namespaces[j]){m.namespaces.add(j,i,"#default#VML")}}function R(j){Y(j,"g_vml_","urn:schemas-microsoft-com:vml");Y(j,"g_o_","urn:schemas-microsoft-com:office:office");if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}var C={};function F(j){if(j in C){return C[j]}var ag,Z=1;j=String(j);if(j.charAt(0)=="#"){ag=j}else{if(/^rgb/.test(j)){var p=M(j);var ag="#",ah;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){ah=Math.floor(c(p[m])*255)}else{ah=+p[m]}ag+=k[r(ah,0,255)]}Z=+p[3]}else{if(/^hsl/.test(j)){var p=M(j);ag=I(p);Z=p[3]}else{ag=b[j]||j}}}return C[j]={color:ag,alpha:Z}}var o={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||o.style,variant:m.fontVariant||o.variant,weight:m.fontWeight||o.weight,size:m.fontSize||o.size,family:m.fontFamily||o.family}}function u(m,j){var i={};for(var ah in m){i[ah]=m[ah]}var ag=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ag*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ag/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=Z/0.75}else{i.size=ag}}}}}i.size*=0.981;return i}function ac(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}var s={butt:"flat",round:"round"};function S(i){return s[i]||"square"}function D(i){this.m_=B();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=d*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var m="width:"+i.clientWidth+"px;height:"+i.clientHeight+"px;overflow:hidden;position:absolute";var j=i.ownerDocument.createElement("div");j.style.cssText=m;i.appendChild(j);var p=j.cloneNode(false);p.style.backgroundColor="red";p.style.filter="alpha(opacity=0)";i.appendChild(p);this.element_=j;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var q=D.prototype;q.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.lineTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.bezierCurveTo=function(m,j,ak,aj,ai,ag){var i=V(this,ai,ag);var ah=V(this,m,j);var Z=V(this,ak,aj);K(this,ah,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}q.quadraticCurveTo=function(ai,m,j,i){var ah=V(this,ai,m);var ag=V(this,j,i);var aj={x:this.currentX_+2/3*(ah.x-this.currentX_),y:this.currentY_+2/3*(ah.y-this.currentY_)};var Z={x:aj.x+(ag.x-this.currentX_)/3,y:aj.y+(ag.y-this.currentY_)/3};K(this,aj,Z,ag)};q.arc=function(al,aj,ak,ag,j,m){ak*=d;var ap=m?"at":"wa";var am=al+A(ag)*ak-f;var ao=aj+l(ag)*ak-f;var i=al+A(j)*ak-f;var an=aj+l(j)*ak-f;if(am==i&&!m){am+=0.125}var Z=V(this,al,aj);var ai=V(this,am,ao);var ah=V(this,i,an);this.currentPath_.push({type:ap,x:Z.x,y:Z.y,radius:ak,xStart:ai.x,yStart:ai.y,xEnd:ah.x,yEnd:ah.y})};q.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};q.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};q.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};q.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};q.createRadialGradient=function(p,ag,m,j,Z,i){var ah=new U("gradientradial");ah.x0_=p;ah.y0_=ag;ah.r0_=m;ah.x1_=j;ah.y1_=Z;ah.r1_=i;return ah};q.drawImage=function(aq,m){var aj,ah,al,ay,ao,am,at,aA;var ak=aq.runtimeStyle.width;var ap=aq.runtimeStyle.height;aq.runtimeStyle.width="auto";aq.runtimeStyle.height="auto";var ai=aq.width;var aw=aq.height;aq.runtimeStyle.width=ak;aq.runtimeStyle.height=ap;if(arguments.length==3){aj=arguments[1];ah=arguments[2];ao=am=0;at=al=ai;aA=ay=aw}else{if(arguments.length==5){aj=arguments[1];ah=arguments[2];al=arguments[3];ay=arguments[4];ao=am=0;at=ai;aA=aw}else{if(arguments.length==9){ao=arguments[1];am=arguments[2];at=arguments[3];aA=arguments[4];aj=arguments[5];ah=arguments[6];al=arguments[7];ay=arguments[8]}else{throw Error("Invalid number of arguments")}}}var az=V(this,aj,ah);var p=at/2;var j=aA/2;var ax=[];var i=10;var ag=10;ax.push(" ','","");this.element_.insertAdjacentHTML("BeforeEnd",ax.join(""))};q.stroke=function(ao){var Z=10;var ap=10;var ag=5000;var ai={x:null,y:null};var an={x:null,y:null};for(var aj=0;ajan.x){an.x=m.x}if(ai.y==null||m.yan.y){an.y=m.y}}}am.push(' ">');if(!ao){w(this,am)}else{G(this,am,ai,an)}am.push("");this.element_.insertAdjacentHTML("beforeEnd",am.join(""))}};function w(m,ag){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ag.push("')}function G(aq,ai,aK,ar){var aj=aq.fillStyle;var aB=aq.arcScaleX_;var aA=aq.arcScaleY_;var j=ar.x-aK.x;var p=ar.y-aK.y;if(aj instanceof U){var an=0;var aF={x:0,y:0};var ax=0;var am=1;if(aj.type_=="gradient"){var al=aj.x0_/aB;var m=aj.y0_/aA;var ak=aj.x1_/aB;var aM=aj.y1_/aA;var aJ=V(aq,al,m);var aI=V(aq,ak,aM);var ag=aI.x-aJ.x;var Z=aI.y-aJ.y;an=Math.atan2(ag,Z)*180/Math.PI;if(an<0){an+=360}if(an<0.000001){an=0}}else{var aJ=V(aq,aj.x0_,aj.y0_);aF={x:(aJ.x-aK.x)/j,y:(aJ.y-aK.y)/p};j/=aB*d;p/=aA*d;var aD=ab.max(j,p);ax=2*aj.r0_/aD;am=2*aj.r1_/aD-ax}var av=aj.colors_;av.sort(function(aN,i){return aN.offset-i.offset});var ap=av.length;var au=av[0].color;var at=av[ap-1].color;var az=av[0].alpha*aq.globalAlpha;var ay=av[ap-1].alpha*aq.globalAlpha;var aE=[];for(var aH=0;aH')}else{if(aj instanceof T){if(j&&p){var ah=-aK.x;var aC=-aK.y;ai.push("')}}else{var aL=F(aq.fillStyle);var aw=aL.color;var aG=aL.alpha*aq.globalAlpha;ai.push('')}}}q.fill=function(){this.stroke(true)};q.closePath=function(){this.currentPath_.push({type:"close"})};function V(j,Z,p){var i=j.m_;return{x:d*(Z*i[0][0]+p*i[1][0]+i[2][0])-f,y:d*(Z*i[0][1]+p*i[1][1]+i[2][1])-f}}q.save=function(){var i={};v(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(B(),this.m_)};q.restore=function(){if(this.aStack_.length){v(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function h(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function aa(j,i,p){if(!h(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}q.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];aa(this,J(i,this.m_),false)};q.rotate=function(j){var p=A(j);var m=l(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];aa(this,J(i,this.m_),false)};q.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];aa(this,J(i,this.m_),true)};q.transform=function(Z,p,ah,ag,j,i){var m=[[Z,p,0],[ah,ag,0],[j,i,1]];aa(this,J(m,this.m_),true)};q.setTransform=function(ag,Z,ai,ah,p,j){var i=[[ag,Z,0],[ai,ah,0],[p,j,1]];aa(this,i,true)};q.drawText_=function(am,ak,aj,ap,ai){var ao=this.m_,at=1000,j=0,ar=at,ah={x:0,y:0},ag=[];var i=u(E(this.font),this.element_);var p=ac(i);var au=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=au.direction=="ltr"?"right":"left";break;case"start":Z=au.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":ah.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":ah.y=-i.size/2.25;break}switch(Z){case"right":j=at;ar=0.05;break;case"center":j=ar=at/2;break}var aq=V(this,ak+ah.x,aj+ah.y);ag.push('');if(ai){w(this,ag)}else{G(this,ag,{x:-j,y:0},{x:ar,y:i.size})}var an=ao[0][0].toFixed(3)+","+ao[1][0].toFixed(3)+","+ao[0][1].toFixed(3)+","+ao[1][1].toFixed(3)+",0,0";var al=n(aq.x/d)+","+n(aq.y/d);ag.push('','','');this.element_.insertAdjacentHTML("beforeEnd",ag.join(""))};q.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};q.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};q.measureText=function(m){if(!this.textMeasureEl_){var i='';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};q.clip=function(){};q.arcTo=function(){};q.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var X=P.prototype=new Error;X.INDEX_SIZE_ERR=1;X.DOMSTRING_SIZE_ERR=2;X.HIERARCHY_REQUEST_ERR=3;X.WRONG_DOCUMENT_ERR=4;X.INVALID_CHARACTER_ERR=5;X.NO_DATA_ALLOWED_ERR=6;X.NO_MODIFICATION_ALLOWED_ERR=7;X.NOT_FOUND_ERR=8;X.NOT_SUPPORTED_ERR=9;X.INUSE_ATTRIBUTE_ERR=10;X.INVALID_STATE_ERR=11;X.SYNTAX_ERR=12;X.INVALID_MODIFICATION_ERR=13;X.NAMESPACE_ERR=14;X.INVALID_ACCESS_ERR=15;X.VALIDATION_ERR=16;X.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=e;CanvasRenderingContext2D=D;CanvasGradient=U;CanvasPattern=T;DOMException=P})()}; \ No newline at end of file diff --git a/app/assets/javascripts/general.js b/app/assets/javascripts/general.js new file mode 100644 index 000000000..b52131b83 --- /dev/null +++ b/app/assets/javascripts/general.js @@ -0,0 +1,53 @@ +$(document).ready(function() { + // flash message for people coming from other countries + if(window.location.search.substring(1).search("country_name") == -1) { + if (!$.cookie('has_seen_country_message')) { + $.ajax({ + url: "/country_message", + dataType: 'html', + success: function(country_message){ + if (country_message != ''){ + $('#other-country-notice').html(country_message); + $('body:not(.front) #other-country-notice').show() + } + } + }) + + } + } + + $('#other-country-notice').click(function() { + $('#other-country-notice').hide(); + $.cookie('has_seen_country_message', 1, {expires: 365, path: '/'}); + }); + // "link to this" widget + $('a.link_to_this').click(function() { + var box = $('div#link_box'); + var location = window.location.protocol + "//" + window.location.hostname + $(this).attr('href'); + box.width(location.length + " em"); + box.find('input').val(location).attr('size', location.length + " em"); + box.show(); + box.find('input').select(); + box.position({ + my: "left top", + at: "left bottom", + of: this, + collision: "fit" }); + return false; + }); + + $('.close-button').click(function() { $(this).parent().hide() }); + $('div#variety-filter a').each(function() { + $(this).click(function() { + var form = $('form#search_form'); + form.attr('action', $(this).attr('href')); + form.submit(); + return false; + }) + }) + + if($.cookie('seen_foi2') == 1) { + $('#everypage').hide(); + } + +}) diff --git a/app/assets/javascripts/jquery-ui.min.js b/app/assets/javascripts/jquery-ui.min.js new file mode 100644 index 000000000..fb641f675 --- /dev/null +++ b/app/assets/javascripts/jquery-ui.min.js @@ -0,0 +1,168 @@ +/*! + * jQuery UI 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.16", +keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({propAttr:c.fn.prop||c.fn.attr,_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d= +this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this, +"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart": +"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight, +outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a, +"tabindex"),d=isNaN(b);return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&& +a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= +a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), +g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); +;/* + * jQuery UI Tabs 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function(d,p){function u(){return++v}function w(){return++x}var v=0,x=0;d.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
      ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
    • #{label}
    • "},_create:function(){this._tabify(true)},_setOption:function(b,e){if(b=="selected")this.options.collapsible&& +e==this.options.selected||this.select(e);else{this.options[b]=e;this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[b].concat(d.makeArray(arguments)))},_ui:function(b,e){return{tab:b,panel:e,index:this.anchors.index(b)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b= +d(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(b){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var a=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var i=d(f).attr("href"),l=i.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]|| +(q=d("base")[0])&&l===q.href)){i=f.hash;f.href=i}if(h.test(i))a.panels=a.panels.add(a.element.find(a._sanitizeSelector(i)));else if(i&&i!=="#"){d.data(f,"href.tabs",i);d.data(f,"load.tabs",i.replace(/#.*$/,""));i=a._tabId(f);f.href="#"+i;f=a.element.find("#"+i);if(!f.length){f=d(c.panelTemplate).attr("id",i).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(a.panels[g-1]||a.list);f.data("destroy.tabs",true)}a.panels=a.panels.add(f)}else c.disabled.push(g)});if(b){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); +this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(a._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected= +this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return a.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); +if(c.selected>=0&&this.anchors.length){a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");a.element.queue("tabs",function(){a._trigger("show",null,a._ui(a.anchors[c.selected],a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash))[0]))});this.load(c.selected)}d(window).bind("unload",function(){a.lis.add(a.anchors).unbind(".tabs");a.lis=a.anchors=a.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")); +this.element[c.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);b=0;for(var j;j=this.lis[b];b++)d(j)[d.inArray(b,c.disabled)!=-1&&!d(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+ +g)};this.lis.bind("mouseover.tabs",function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal", +function(){e(f,o);a._trigger("show",null,a._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");a._trigger("show",null,a._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);a.element.dequeue("tabs")})}:function(g,f){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");a.element.dequeue("tabs")}; +this.anchors.bind(c.event+".tabs",function(){var g=this,f=d(g).closest("li"),i=a.panels.filter(":not(.ui-tabs-hide)"),l=a.element.find(a._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||a.panels.filter(":animated").length||a._trigger("select",null,a._ui(this,l[0]))===false){this.blur();return false}c.selected=a.anchors.index(this);a.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected= +-1;c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){s(g,i)}).dequeue("tabs");this.blur();return false}else if(!i.length){c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this));this.blur();return false}c.cookie&&a._cookie(c.selected,c.cookie);if(l.length){i.length&&a.element.queue("tabs",function(){s(g,i)});a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier."; +d.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(b){if(typeof b=="string")b=this.anchors.index(this.anchors.filter("[href$="+b+"]"));return b},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e= +d.data(this,"href.tabs");if(e)this.href=e;var a=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){a.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});b.cookie&&this._cookie(null,b.cookie);return this},add:function(b, +e,a){if(a===p)a=this.anchors.length;var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,b).replace(/#\{label\}/g,e));b=!b.indexOf("#")?b.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var j=c.element.find("#"+b);j.length||(j=d(h.panelTemplate).attr("id",b).data("destroy.tabs",true));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(a>=this.lis.length){e.appendTo(this.list);j.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[a]); +j.insertBefore(this.panels[a])}h.disabled=d.map(h.disabled,function(k){return k>=a?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");j.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[a],this.panels[a]));return this},remove:function(b){b=this._getIndex(b);var e=this.options,a=this.lis.eq(b).remove(),c=this.panels.eq(b).remove(); +if(a.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(b+(b+1=b?--h:h});this._tabify();this._trigger("remove",null,this._ui(a.find("a")[0],c[0]));return this},enable:function(b){b=this._getIndex(b);var e=this.options;if(d.inArray(b,e.disabled)!=-1){this.lis.eq(b).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(a){return a!=b});this._trigger("enable",null, +this._ui(this.anchors[b],this.panels[b]));return this}},disable:function(b){b=this._getIndex(b);var e=this.options;if(b!=e.selected){this.lis.eq(b).addClass("ui-state-disabled");e.disabled.push(b);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[b],this.panels[b]))}return this},select:function(b){b=this._getIndex(b);if(b==-1)if(this.options.collapsible&&this.options.selected!=-1)b=this.options.selected;else return this;this.anchors.eq(b).trigger(this.options.event+".tabs");return this}, +load:function(b){b=this._getIndex(b);var e=this,a=this.options,c=this.anchors.eq(b)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(b).addClass("ui-state-processing");if(a.spinner){var j=d("span",c);j.data("label.tabs",j.html()).html(a.spinner)}this.xhr=d.ajax(d.extend({},a.ajaxOptions,{url:h,success:function(k,n){e.element.find(e._sanitizeSelector(c.hash)).html(k);e._cleanup();a.cache&&d.data(c, +"cache.tabs",true);e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.error(k,n,b,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this}, +url:function(b,e){this.anchors.eq(b).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.16"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(b,e){var a=this,c=this.options,h=a._rotate||(a._rotate=function(j){clearTimeout(a.rotation);a.rotation=setTimeout(function(){var k=c.selected;a.select(++k
      '))}function N(a){return a.bind("mouseout", +function(b){b=d(b.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");b.length&&b.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(b){b=d(b.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");if(!(d.datepicker._isDisabledDatepicker(J.inline?a.parent()[0]:J.input[0])||!b.length)){b.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); +b.addClass("ui-state-hover");b.hasClass("ui-datepicker-prev")&&b.addClass("ui-datepicker-prev-hover");b.hasClass("ui-datepicker-next")&&b.addClass("ui-datepicker-next-hover")}})}function H(a,b){d.extend(a,b);for(var c in b)if(b[c]==null||b[c]==C)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.16"}});var B=(new Date).getTime(),J;d.extend(M.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv}, +setDefaults:function(a){H(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]=f}}}e=a.nodeName.toLowerCase();f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_-])/g, +"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:N(d('
      '))}},_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker", +function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b);b.settings.disabled&&this._disableDatepicker(a)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&&b.append.remove();if(c){b.append=d(''+c+"");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c== +"focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('').addClass(this._triggerClass).html(f==""?c:d("").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker(): +d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;gh){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a, +b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b),true);this._updateDatepicker(b);this._updateAlternate(b);b.settings.disabled&&this._disableDatepicker(a);b.dpDiv.css("display","block")}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+= +1;this._dialogInput=d('');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}H(a.settings,e||{});b=b&&b.constructor==Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/ +2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b= +d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e= +a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span"){b=b.children("."+this._inlineClass);b.children().removeClass("ui-state-disabled");b.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b=d(a),c=d.data(a, +"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span"){b=b.children("."+this._inlineClass);b.children().addClass("ui-state-disabled");b.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=d.map(this._disabledInputs,function(f){return f== +a?null:f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;for(var b=0;b-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true},_showDatepicker:function(a){a=a.target||a;if(a.nodeName.toLowerCase()!="input")a=d("input", +a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);if(d.datepicker._curInst&&d.datepicker._curInst!=b){d.datepicker._datepickerShowing&&d.datepicker._triggerOnClose(d.datepicker._curInst);d.datepicker._curInst.dpDiv.stop(true,true)}var c=d.datepicker._get(b,"beforeShow");c=c?c.apply(a,[a,b]):{};if(c!==false){H(b.settings,c);b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value= +"";if(!d.datepicker._pos){d.datepicker._pos=d.datepicker._findPos(a);d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.empty();b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b); +c=d.datepicker._checkOffset(b,c,e);b.dpDiv.css({position:d.datepicker._inDialog&&d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){var i=b.dpDiv.find("iframe.ui-datepicker-cover");if(i.length){var g=d.datepicker._getBorders(b.dpDiv);i.css({left:-g[0],top:-g[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex(d(a).zIndex()+1);d.datepicker._datepickerShowing= +true;d.effects&&d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f,h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}}},_updateDatepicker:function(a){this.maxRows=4;var b=d.datepicker._getBorders(a.dpDiv);J=a;a.dpDiv.empty().append(this._generateHTML(a));var c=a.dpDiv.find("iframe.ui-datepicker-cover");c.length&&c.css({left:-b[0],top:-b[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}); +a.dpDiv.find("."+this._dayOverClass+" a").mouseover();b=this._getNumberOfMonths(a);c=b[1];a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");c>1&&a.dpDiv.addClass("ui-datepicker-multi-"+c).css("width",17*c+"em");a.dpDiv[(b[0]!=1||b[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&& +!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var e=a.yearshtml;setTimeout(function(){e===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml);e=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(), +h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(),j=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e-g):0);b.top-=Math.min(b.top,b.top+f>j&&j>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b= +this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1||d.expr.filters.hidden(a));)a=a[b?"previousSibling":"nextSibling"];a=d(a).offset();return[a.left,a.top]},_triggerOnClose:function(a){var b=this._get(a,"onClose");if(b)b.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a])},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b); +this._curInst=null};d.effects&&d.effects[a]?b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();d.datepicker._triggerOnClose(b);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")}, +_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&&!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"): +0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth;b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e["selected"+(c=="M"? +"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay=d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=d(a); +this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,"altField"); +if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b=a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"? +b.toString():b+"";if(b=="")return null;var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;e=typeof e!="string"?e:(new Date).getFullYear()%100+parseInt(e,10);for(var f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,j=c=-1,l=-1,u=-1,k=false,o=function(p){(p=A+1-1){j=1;l=u;do{e=this._getDaysInMonth(c,j-1);if(l<=e)break;j++;l-=e}while(1)}v=this._daylightSavingAdjust(new Date(c,j-1,l));if(v.getFullYear()!=c||v.getMonth()+1!=j||v.getDate()!=l)throw"Invalid date";return v},ATOM:"yy-mm-dd", +COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames: +null)||this._defaults.monthNames;var i=function(o){(o=k+1 +12?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&& +a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),j=this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay? +new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),k=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n=this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=k&&nn;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-j,1)),this._getFormatConfig(a)); +n=this._canAdjustMonth(a,-1,m,g)?''+n+"":f?"":''+n+"";var s=this._get(a,"nextText");s=!h?s:this.formatDate(s,this._daylightSavingAdjust(new Date(m, +g+j,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?''+s+"":f?"":''+s+"";j=this._get(a,"currentText");s=this._get(a,"gotoCurrent")&& +a.currentDay?u:b;j=!h?j:this.formatDate(j,s,this._getFormatConfig(a));h=!a.inline?'":"";e=e?'
      '+(c?h:"")+(this._isInRange(a,s)?'":"")+(c?"":h)+"
      ":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;j=this._get(a,"showWeek");s=this._get(a,"dayNames");this._get(a,"dayNamesShort");var q=this._get(a,"dayNamesMin"),A=this._get(a,"monthNames"),v=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),D=this._get(a,"showOtherMonths"),K=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var E=this._getDefaultDate(a),w="",x=0;x1)switch(G){case 0:y+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]-1:y+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:y+=" ui-datepicker-group-middle";t="";break}y+='">'}y+='
      '+(/all|left/.test(t)&& +x==0?c?f:n:"")+(/all|right/.test(t)&&x==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,k,o,x>0||G>0,A,v)+'
      ';var z=j?'":"";for(t=0;t<7;t++){var r=(t+h)%7;z+="=5?' class="ui-datepicker-week-end"':"")+'>'+q[r]+""}y+=z+"";z=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay, +z);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;z=Math.ceil((t+z)/7);this.maxRows=z=l?this.maxRows>z?this.maxRows:z:z;r=this._daylightSavingAdjust(new Date(m,g,1-t));for(var Q=0;Q";var R=!j?"":'";for(t=0;t<7;t++){var I=p?p.apply(a.input?a.input[0]:null,[r]):[true,""],F=r.getMonth()!=g,L=F&&!K||!I[0]||k&&ro;R+='";r.setDate(r.getDate()+1);r=this._daylightSavingAdjust(r)}y+=R+""}g++;if(g>11){g=0;m++}y+="
      '+this._get(a,"weekHeader")+"
      '+this._get(a,"calculateWeek")(r)+""+(F&&!D?" ":L?''+ +r.getDate()+"":''+r.getDate()+"")+"
      "+(l?"
      "+(i[0]>0&&G==i[1]-1?'
      ':""):"");O+=y}w+=O}w+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'': +"");a._keyEvent=false;return w},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var j=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),k='
      ',o="";if(h||!j)o+=''+i[b]+"";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='"}u||(k+=o+(h||!(j&&l)?" ":""));if(!a.yearshtml){a.yearshtml="";if(h||!l)k+=''+c+"";else{g=this._get(a,"yearRange").split(":");var s=(new Date).getFullYear();i=function(q){q=q.match(/c[+-].*/)?c+parseInt(q.substring(1),10):q.match(/[+-].*/)?s+parseInt(q,10):parseInt(q,10);return isNaN(q)?s:q};b=i(g[0]);g=Math.max(b,i(g[1]||""));b=e?Math.max(b, +e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(a.yearshtml+='";k+=a.yearshtml;a.yearshtml=null}}k+=this._get(a,"yearSuffix");if(u)k+=(h||!(j&&l)?" ":"")+o;k+="
      ";return k},_adjustInstDate:function(a,b,c){var e=a.drawYear+(c=="Y"?b:0),f=a.drawMonth+ +(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&ba?a:b},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");if(b)b.apply(a.input? +a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a);c=this._daylightSavingAdjust(new Date(c, +e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a, +"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker=function(a){if(!this.length)return this; +if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));return this.each(function(){typeof a== +"string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new M;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.16";window["DP_jQuery_"+B]=d})(jQuery); +; \ No newline at end of file diff --git a/app/assets/javascripts/jquery.Jcrop.js b/app/assets/javascripts/jquery.Jcrop.js new file mode 100644 index 000000000..9002b9787 --- /dev/null +++ b/app/assets/javascripts/jquery.Jcrop.js @@ -0,0 +1,163 @@ +/** + * Jcrop v.0.9.8 (minimized) + * (c) 2008 Kelly Hallman and DeepLiquid.com + * More information: http://deepliquid.com/content/Jcrop.html + * Released under MIT License - this header must remain with code + */ + + +(function($){$.Jcrop=function(obj,opt) +{var obj=obj,opt=opt;if(typeof(obj)!=='object')obj=$(obj)[0];if(typeof(opt)!=='object')opt={};if(!('trackDocument'in opt)) +{opt.trackDocument=$.browser.msie?false:true;if($.browser.msie&&$.browser.version.split('.')[0]=='8') +opt.trackDocument=true;} +if(!('keySupport'in opt)) +opt.keySupport=$.browser.msie?false:true;var defaults={trackDocument:false,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:.6,borderOpacity:.4,handleOpacity:.5,handlePad:5,handleSize:9,handleOffset:5,edgeMargin:14,aspectRatio:0,keySupport:true,cornerHandles:true,sideHandles:true,drawBorders:true,dragEdges:true,boxWidth:0,boxHeight:0,boundary:8,animationDelay:20,swingSpeed:3,allowSelect:true,allowMove:true,allowResize:true,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){}};var options=defaults;setOptions(opt);var $origimg=$(obj);var $img=$origimg.clone().removeAttr('id').css({position:'absolute'});$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('
      ').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);;if(options.addClass)$div.addClass(options.addClass);var $img2=$('').attr('src',$img.attr('src')).css('position','absolute').width(boundx).height(boundy);var $img_holder=$('
      ').width(pct(100)).height(pct(100)).css({zIndex:310,position:'absolute',overflow:'hidden'}).append($img2);var $hdl_holder=$('
      ').width(pct(100)).height(pct(100)).css('zIndex',320);var $sel=$('
      ').css({position:'absolute',zIndex:300}).insertBefore($img).append($img_holder,$hdl_holder);var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var xlimit,ylimit,xmin,ymin;var xscale,yscale,enabled=true;var docOffset=getPos($img),btndown,lastcurs,dimmed,animating,shift_down;var Coords=function() +{var x1=0,y1=0,x2=0,y2=0,ox,oy;function setPressed(pos) +{var pos=rebound(pos);x2=x1=pos[0];y2=y1=pos[1];};function setCurrent(pos) +{var pos=rebound(pos);ox=pos[0]-x2;oy=pos[1]-y2;x2=pos[0];y2=pos[1];};function getOffset() +{return[ox,oy];};function moveOffset(offset) +{var ox=offset[0],oy=offset[1];if(0>x1+ox)ox-=ox+x1;if(0>y1+oy)oy-=oy+y1;if(boundyboundx) +{xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}} +else +{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0) +{yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;} +else if(yy>boundy) +{yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}} +if(xx>x1){if(xx-x1max_x){xx=x1+max_x;} +if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xxmax_x){xx=x1-max_x;} +if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}} +if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;} +if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;} +return last=makeObj(flipCoords(x1,y1,xx,yy));};function rebound(p) +{if(p[0]<0)p[0]=0;if(p[1]<0)p[1]=0;if(p[0]>boundx)p[0]=boundx;if(p[1]>boundy)p[1]=boundy;return[p[0],p[1]];};function flipCoords(x1,y1,x2,y2) +{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2xlimit)) +x2=(xsize>0)?(x1+xlimit):(x1-xlimit);if(ylimit&&(Math.abs(ysize)>ylimit)) +y2=(ysize>0)?(y1+ylimit):(y1-ylimit);if(ymin&&(Math.abs(ysize)0)?(y1+ymin):(y1-ymin);if(xmin&&(Math.abs(xsize)0)?(x1+xmin):(x1-xmin);if(x1<0){x2-=x1;x1-=x1;} +if(y1<0){y2-=y1;y1-=y1;} +if(x2<0){x1-=x2;x2-=x2;} +if(y2<0){y1-=y2;y2-=y2;} +if(x2>boundx){var delta=x2-boundx;x1-=delta;x2-=delta;} +if(y2>boundy){var delta=y2-boundy;y1-=delta;y2-=delta;} +if(x1>boundx){var delta=x1-boundy;y2-=delta;y1-=delta;} +if(y1>boundy){var delta=y1-boundy;y2-=delta;y1-=delta;} +return makeObj(flipCoords(x1,y1,x2,y2));};function makeObj(a) +{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};};return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}();var Selection=function() +{var start,end,dragmode,awake,hdep=370;var borders={};var handle={};var seehandles=false;var hhs=options.handleOffset;if(options.drawBorders){borders={top:insertBorder('hline').css('top',$.browser.msie?px(-1):px(0)),bottom:insertBorder('hline'),left:insertBorder('vline'),right:insertBorder('vline')};} +if(options.dragEdges){handle.t=insertDragbar('n');handle.b=insertDragbar('s');handle.r=insertDragbar('e');handle.l=insertDragbar('w');} +options.sideHandles&&createHandles(['n','s','e','w']);options.cornerHandles&&createHandles(['sw','nw','ne','se']);function insertBorder(type) +{var jq=$('
      ').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;};function dragDiv(ord,zi) +{var jq=$('
      ').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi});$hdl_holder.append(jq);return jq;};function insertHandle(ord) +{return dragDiv(ord,hdep++).css({top:px(-hhs+1),left:px(-hhs+1),opacity:options.handleOpacity}).addClass(cssClass('handle'));};function insertDragbar(ord) +{var s=options.handleSize,o=hhs,h=s,w=s,t=o,l=o;switch(ord) +{case'n':case's':w=pct(100);break;case'e':case'w':h=pct(100);break;} +return dragDiv(ord,hdep++).width(w).height(h).css({top:px(-t+1),left:px(-l+1)});};function createHandles(li) +{for(i in li)handle[li[i]]=insertHandle(li[i]);};function moveHandles(c) +{var midvert=Math.round((c.h/2)-hhs),midhoriz=Math.round((c.w/2)-hhs),north=west=-hhs+1,east=c.w-hhs,south=c.h-hhs,x,y;'e'in handle&&handle.e.css({top:px(midvert),left:px(east)})&&handle.w.css({top:px(midvert)})&&handle.s.css({top:px(south),left:px(midhoriz)})&&handle.n.css({left:px(midhoriz)});'ne'in handle&&handle.ne.css({left:px(east)})&&handle.se.css({top:px(south),left:px(east)})&&handle.sw.css({top:px(south)});'b'in handle&&handle.b.css({top:px(south)})&&handle.r.css({left:px(east)});};function moveto(x,y) +{$img2.css({top:px(-y),left:px(-x)});$sel.css({top:px(y),left:px(x)});};function resize(w,h) +{$sel.width(w).height(h);};function refresh() +{var c=Coords.getFixed();Coords.setPressed([c.x,c.y]);Coords.setCurrent([c.x2,c.y2]);updateVisible();};function updateVisible() +{if(awake)return update();};function update() +{var c=Coords.getFixed();resize(c.w,c.h);moveto(c.x,c.y);options.drawBorders&&borders['right'].css({left:px(c.w-1)})&&borders['bottom'].css({top:px(c.h-1)});seehandles&&moveHandles(c);awake||show();options.onChange(unscale(c));};function show() +{$sel.show();$img.css('opacity',options.bgOpacity);awake=true;};function release() +{disableHandles();$sel.hide();$img.css('opacity',1);awake=false;};function showHandles() +{if(seehandles) +{moveHandles(Coords.getFixed());$hdl_holder.show();}};function enableHandles() +{seehandles=true;if(options.allowResize) +{moveHandles(Coords.getFixed());$hdl_holder.show();return true;}};function disableHandles() +{seehandles=false;$hdl_holder.hide();};function animMode(v) +{(animating=v)?disableHandles():enableHandles();};function done() +{animMode(false);refresh();};var $track=newTracker().mousedown(createDragger('move')).css({cursor:'move',position:'absolute',zIndex:360}) +$img_holder.append($track);disableHandles();return{updateVisible:updateVisible,update:update,release:release,refresh:refresh,setCursor:function(cursor){$track.css('cursor',cursor);},enableHandles:enableHandles,enableOnly:function(){seehandles=true;},showHandles:showHandles,disableHandles:disableHandles,animMode:animMode,done:done};}();var Tracker=function() +{var onMove=function(){},onDone=function(){},trackDoc=options.trackDocument;if(!trackDoc) +{$trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);} +function toFront() +{$trk.css({zIndex:450});if(trackDoc) +{$(document).mousemove(trackMove).mouseup(trackUp);}} +function toBack() +{$trk.css({zIndex:290});if(trackDoc) +{$(document).unbind('mousemove',trackMove).unbind('mouseup',trackUp);}} +function trackMove(e) +{onMove(mouseAbs(e));};function trackUp(e) +{e.preventDefault();e.stopPropagation();if(btndown) +{btndown=false;onDone(mouseAbs(e));options.onSelect(unscale(Coords.getFixed()));toBack();onMove=function(){};onDone=function(){};} +return false;};function activateHandlers(move,done) +{btndown=true;onMove=move;onDone=done;toFront();return false;};function setCursor(t){$trk.css('cursor',t);};$img.before($trk);return{activateHandlers:activateHandlers,setCursor:setCursor};}();var KeyManager=function() +{var $keymgr=$('').css({position:'absolute',left:'-30px'}).keypress(parseKey).blur(onBlur),$keywrap=$('
      ').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys() +{if(options.keySupport) +{$keymgr.show();$keymgr.focus();}};function onBlur(e) +{$keymgr.hide();};function doNudge(e,x,y) +{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible();};e.preventDefault();e.stopPropagation();};function parseKey(e) +{if(e.ctrlKey)return true;shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode) +{case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:Selection.release();break;case 9:return true;} +return nothing(e);};if(options.keySupport)$keywrap.insertBefore($img);return{watchKeys:watchKeys};}();function px(n){return''+parseInt(n)+'px';};function pct(n){return''+parseInt(n)+'%';};function cssClass(cl){return options.baseClass+'-'+cl;};function getPos(obj) +{var pos=$(obj).offset();return[pos.left,pos.top];};function mouseAbs(e) +{return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];};function myCursor(type) +{if(type!=lastcurs) +{Tracker.setCursor(type);lastcurs=type;}};function startDragMode(mode,pos) +{docOffset=getPos($img);Tracker.setCursor(mode=='move'?mode:mode+'-resize');if(mode=='move') +return Tracker.activateHandlers(createMover(pos),doneSelect);var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);};function dragmodeHandler(mode,f) +{return function(pos){if(!options.aspectRatio)switch(mode) +{case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;} +else switch(mode) +{case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;} +Coords.setCurrent(pos);Selection.update();};};function createMover(pos) +{var lloc=pos;KeyManager.watchKeys();return function(pos) +{Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};};function oppLockCorner(ord) +{switch(ord) +{case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';};};function createDragger(ord) +{return function(e){if(options.disabled)return false;if((ord=='move')&&!options.allowMove)return false;btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};};function presize($obj,w,h) +{var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0) +{nw=w;nh=(w/$obj.width())*$obj.height();} +if((nh>h)&&h>0) +{nh=h;nw=(h/$obj.height())*$obj.width();} +xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);};function unscale(c) +{return{x:parseInt(c.x*xscale),y:parseInt(c.y*yscale),x2:parseInt(c.x2*xscale),y2:parseInt(c.y2*yscale),w:parseInt(c.w*xscale),h:parseInt(c.h*yscale)};};function doneSelect(pos) +{var c=Coords.getFixed();if(c.w>options.minSelect[0]&&c.h>options.minSelect[1]) +{Selection.enableHandles();Selection.done();} +else +{Selection.release();} +Tracker.setCursor(options.allowSelect?'crosshair':'default');};function newSelection(e) +{if(options.disabled)return false;if(!options.allowSelect)return false;btndown=true;docOffset=getPos($img);Selection.disableHandles();myCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Tracker.activateHandlers(selectDrag,doneSelect);KeyManager.watchKeys();Selection.update();e.stopPropagation();e.preventDefault();return false;};function selectDrag(pos) +{Coords.setCurrent(pos);Selection.update();};function newTracker() +{var trk=$('
      ').addClass(cssClass('tracker'));$.browser.msie&&trk.css({opacity:0,backgroundColor:'white'});return trk;};function animateTo(a) +{var x1=a[0]/xscale,y1=a[1]/yscale,x2=a[2]/xscale,y2=a[3]/yscale;if(animating)return;var animto=Coords.flipCoords(x1,y1,x2,y2);var c=Coords.getFixed();var animat=initcr=[c.x,c.y,c.x2,c.y2];var interv=options.animationDelay;var x=animat[0];var y=animat[1];var x2=animat[2];var y2=animat[3];var ix1=animto[0]-initcr[0];var iy1=animto[1]-initcr[1];var ix2=animto[2]-initcr[2];var iy2=animto[3]-initcr[3];var pcent=0;var velocity=options.swingSpeed;Selection.animMode(true);var animator=function() +{return function() +{pcent+=(100-pcent)/velocity;animat[0]=x+((pcent/100)*ix1);animat[1]=y+((pcent/100)*iy1);animat[2]=x2+((pcent/100)*ix2);animat[3]=y2+((pcent/100)*iy2);if(pcent<100)animateStart();else Selection.done();if(pcent>=99.8)pcent=100;setSelectRaw(animat);};}();function animateStart() +{window.setTimeout(animator,interv);};animateStart();};function setSelect(rect) +{setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);};function setSelectRaw(l) +{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();};function setOptions(opt) +{if(typeof(opt)!='object')opt={};options=$.extend(options,opt);if(typeof(options.onChange)!=='function') +options.onChange=function(){};if(typeof(options.onSelect)!=='function') +options.onSelect=function(){};};function tellSelect() +{return unscale(Coords.getFixed());};function tellScaled() +{return Coords.getFixed();};function setOptionsNew(opt) +{setOptions(opt);interfaceUpdate();};function disableCrop() +{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');};function enableCrop() +{options.disabled=false;interfaceUpdate();};function cancelCrop() +{Selection.done();Tracker.activateHandlers(null,null);};function destroy() +{$div.remove();$origimg.show();};function interfaceUpdate(alt) +{options.allowResize?alt?Selection.enableOnly():Selection.enableHandles():Selection.disableHandles();Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');$div.css('backgroundColor',options.bgColor);if('setSelect'in options){setSelect(opt.setSelect);Selection.done();delete(options.setSelect);} +if('trueSize'in options){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;} +xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if('outerImage'in options) +{$img.attr('src',options.outerImage);delete(options.outerImage);} +Selection.refresh();};$hdl_holder.hide();interfaceUpdate(true);var api={animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},release:Selection.release,destroy:destroy};$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options) +{function attachWhenDone(from) +{var loadsrc=options.useImg||from.src;var img=new Image();img.onload=function(){$.Jcrop(from,options);};img.src=loadsrc;};if(typeof(options)!=='object')options={};this.each(function() +{if($(this).data('Jcrop')) +{if(options=='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);} +else attachWhenDone(this);});return this;};})(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.cookie.js b/app/assets/javascripts/jquery.cookie.js new file mode 100644 index 000000000..6a3e394b4 --- /dev/null +++ b/app/assets/javascripts/jquery.cookie.js @@ -0,0 +1,41 @@ +/** + * jQuery Cookie plugin + * + * Copyright (c) 2010 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ +jQuery.cookie = function (key, value, options) { + + // key and at least value given, set cookie... + if (arguments.length > 1 && String(value) !== "[object Object]") { + options = jQuery.extend({}, options); + + if (value === null || value === undefined) { + options.expires = -1; + } + + if (typeof options.expires === 'number') { + var days = options.expires, t = options.expires = new Date(); + t.setDate(t.getDate() + days); + } + + value = String(value); + + return (document.cookie = [ + encodeURIComponent(key), '=', + options.raw ? value : encodeURIComponent(value), + options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE + options.path ? '; path=' + options.path : '', + options.domain ? '; domain=' + options.domain : '', + options.secure ? '; secure' : '' + ].join('')); + } + + // key and possibly options given, get cookie... + options = value || {}; + var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent; + return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null; +}; diff --git a/app/assets/javascripts/jquery.fancybox-1.3.4.pack.js b/app/assets/javascripts/jquery.fancybox-1.3.4.pack.js new file mode 100755 index 000000000..1373ed083 --- /dev/null +++ b/app/assets/javascripts/jquery.fancybox-1.3.4.pack.js @@ -0,0 +1,46 @@ +/* + * FancyBox - jQuery Plugin + * Simple and fancy lightbox alternative + * + * Examples and documentation at: http://fancybox.net + * + * Copyright (c) 2008 - 2010 Janis Skarnelis + * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated. + * + * Version: 1.3.4 (11/11/2010) + * Requires: jQuery v1.3+ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +;(function(b){var m,t,u,f,D,j,E,n,z,A,q=0,e={},o=[],p=0,d={},l=[],G=null,v=new Image,J=/\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,W=/[^\.]\.(swf)\s*$/i,K,L=1,y=0,s="",r,i,h=false,B=b.extend(b("
      ")[0],{prop:0}),M=b.browser.msie&&b.browser.version<7&&!window.XMLHttpRequest,N=function(){t.hide();v.onerror=v.onload=null;G&&G.abort();m.empty()},O=function(){if(false===e.onError(o,q,e)){t.hide();h=false}else{e.titleShow=false;e.width="auto";e.height="auto";m.html('

      The requested content cannot be loaded.
      Please try again later.

      '); +F()}},I=function(){var a=o[q],c,g,k,C,P,w;N();e=b.extend({},b.fn.fancybox.defaults,typeof b(a).data("fancybox")=="undefined"?e:b(a).data("fancybox"));w=e.onStart(o,q,e);if(w===false)h=false;else{if(typeof w=="object")e=b.extend(e,w);k=e.title||(a.nodeName?b(a).attr("title"):a.title)||"";if(a.nodeName&&!e.orig)e.orig=b(a).children("img:first").length?b(a).children("img:first"):b(a);if(k===""&&e.orig&&e.titleFromAlt)k=e.orig.attr("alt");c=e.href||(a.nodeName?b(a).attr("href"):a.href)||null;if(/^(?:javascript)/i.test(c)|| +c=="#")c=null;if(e.type){g=e.type;if(!c)c=e.content}else if(e.content)g="html";else if(c)g=c.match(J)?"image":c.match(W)?"swf":b(a).hasClass("iframe")?"iframe":c.indexOf("#")===0?"inline":"ajax";if(g){if(g=="inline"){a=c.substr(c.indexOf("#"));g=b(a).length>0?"inline":"ajax"}e.type=g;e.href=c;e.title=k;if(e.autoDimensions)if(e.type=="html"||e.type=="inline"||e.type=="ajax"){e.width="auto";e.height="auto"}else e.autoDimensions=false;if(e.modal){e.overlayShow=true;e.hideOnOverlayClick=false;e.hideOnContentClick= +false;e.enableEscapeButton=false;e.showCloseButton=false}e.padding=parseInt(e.padding,10);e.margin=parseInt(e.margin,10);m.css("padding",e.padding+e.margin);b(".fancybox-inline-tmp").unbind("fancybox-cancel").bind("fancybox-change",function(){b(this).replaceWith(j.children())});switch(g){case "html":m.html(e.content);F();break;case "inline":if(b(a).parent().is("#fancybox-content")===true){h=false;break}b('
      ').hide().insertBefore(b(a)).bind("fancybox-cleanup",function(){b(this).replaceWith(j.children())}).bind("fancybox-cancel", +function(){b(this).replaceWith(m.children())});b(a).appendTo(m);F();break;case "image":h=false;b.fancybox.showActivity();v=new Image;v.onerror=function(){O()};v.onload=function(){h=true;v.onerror=v.onload=null;e.width=v.width;e.height=v.height;b("").attr({id:"fancybox-img",src:v.src,alt:e.title}).appendTo(m);Q()};v.src=c;break;case "swf":e.scrolling="no";C='';P="";b.each(e.swf,function(x,H){C+='';P+=" "+x+'="'+H+'"'});C+='";m.html(C);F();break;case "ajax":h=false;b.fancybox.showActivity();e.ajax.win=e.ajax.success;G=b.ajax(b.extend({},e.ajax,{url:c,data:e.ajax.data||{},error:function(x){x.status>0&&O()},success:function(x,H,R){if((typeof R=="object"?R:G).status==200){if(typeof e.ajax.win== +"function"){w=e.ajax.win(c,x,H,R);if(w===false){t.hide();return}else if(typeof w=="string"||typeof w=="object")x=w}m.html(x);F()}}}));break;case "iframe":Q()}}else O()}},F=function(){var a=e.width,c=e.height;a=a.toString().indexOf("%")>-1?parseInt((b(window).width()-e.margin*2)*parseFloat(a)/100,10)+"px":a=="auto"?"auto":a+"px";c=c.toString().indexOf("%")>-1?parseInt((b(window).height()-e.margin*2)*parseFloat(c)/100,10)+"px":c=="auto"?"auto":c+"px";m.wrapInner('
      ');e.width=m.width();e.height=m.height();Q()},Q=function(){var a,c;t.hide();if(f.is(":visible")&&false===d.onCleanup(l,p,d)){b.event.trigger("fancybox-cancel");h=false}else{h=true;b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");f.is(":visible")&&d.titlePosition!=="outside"&&f.css("height",f.height());l=o;p=q;d=e;if(d.overlayShow){u.css({"background-color":d.overlayColor, +opacity:d.overlayOpacity,cursor:d.hideOnOverlayClick?"pointer":"auto",height:b(document).height()});if(!u.is(":visible")){M&&b("select:not(#fancybox-tmp select)").filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one("fancybox-cleanup",function(){this.style.visibility="inherit"});u.show()}}else u.hide();i=X();s=d.title||"";y=0;n.empty().removeAttr("style").removeClass();if(d.titleShow!==false){if(b.isFunction(d.titleFormat))a=d.titleFormat(s,l,p,d);else a=s&&s.length? +d.titlePosition=="float"?'
      '+s+'
      ':'
      '+s+"
      ":false;s=a;if(!(!s||s==="")){n.addClass("fancybox-title-"+d.titlePosition).html(s).appendTo("body").show();switch(d.titlePosition){case "inside":n.css({width:i.width-d.padding*2,marginLeft:d.padding,marginRight:d.padding}); +y=n.outerHeight(true);n.appendTo(D);i.height+=y;break;case "over":n.css({marginLeft:d.padding,width:i.width-d.padding*2,bottom:d.padding}).appendTo(D);break;case "float":n.css("left",parseInt((n.width()-i.width-40)/2,10)*-1).appendTo(f);break;default:n.css({width:i.width-d.padding*2,paddingLeft:d.padding,paddingRight:d.padding}).appendTo(f)}}}n.hide();if(f.is(":visible")){b(E.add(z).add(A)).hide();a=f.position();r={top:a.top,left:a.left,width:f.width(),height:f.height()};c=r.width==i.width&&r.height== +i.height;j.fadeTo(d.changeFade,0.3,function(){var g=function(){j.html(m.contents()).fadeTo(d.changeFade,1,S)};b.event.trigger("fancybox-change");j.empty().removeAttr("filter").css({"border-width":d.padding,width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2});if(c)g();else{B.prop=0;b(B).animate({prop:1},{duration:d.changeSpeed,easing:d.easingChange,step:T,complete:g})}})}else{f.removeAttr("style");j.css("border-width",d.padding);if(d.transitionIn=="elastic"){r=V();j.html(m.contents()); +f.show();if(d.opacity)i.opacity=0;B.prop=0;b(B).animate({prop:1},{duration:d.speedIn,easing:d.easingIn,step:T,complete:S})}else{d.titlePosition=="inside"&&y>0&&n.show();j.css({width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2}).html(m.contents());f.css(i).fadeIn(d.transitionIn=="none"?0:d.speedIn,S)}}}},Y=function(){if(d.enableEscapeButton||d.enableKeyboardNav)b(document).bind("keydown.fb",function(a){if(a.keyCode==27&&d.enableEscapeButton){a.preventDefault();b.fancybox.close()}else if((a.keyCode== +37||a.keyCode==39)&&d.enableKeyboardNav&&a.target.tagName!=="INPUT"&&a.target.tagName!=="TEXTAREA"&&a.target.tagName!=="SELECT"){a.preventDefault();b.fancybox[a.keyCode==37?"prev":"next"]()}});if(d.showNavArrows){if(d.cyclic&&l.length>1||p!==0)z.show();if(d.cyclic&&l.length>1||p!=l.length-1)A.show()}else{z.hide();A.hide()}},S=function(){if(!b.support.opacity){j.get(0).style.removeAttribute("filter");f.get(0).style.removeAttribute("filter")}e.autoDimensions&&j.css("height","auto");f.css("height","auto"); +s&&s.length&&n.show();d.showCloseButton&&E.show();Y();d.hideOnContentClick&&j.bind("click",b.fancybox.close);d.hideOnOverlayClick&&u.bind("click",b.fancybox.close);b(window).bind("resize.fb",b.fancybox.resize);d.centerOnScroll&&b(window).bind("scroll.fb",b.fancybox.center);if(d.type=="iframe")b('').appendTo(j); +f.show();h=false;b.fancybox.center();d.onComplete(l,p,d);var a,c;if(l.length-1>p){a=l[p+1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}if(p>0){a=l[p-1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}},T=function(a){var c={width:parseInt(r.width+(i.width-r.width)*a,10),height:parseInt(r.height+(i.height-r.height)*a,10),top:parseInt(r.top+(i.top-r.top)*a,10),left:parseInt(r.left+(i.left-r.left)*a,10)};if(typeof i.opacity!=="undefined")c.opacity=a<0.5?0.5:a;f.css(c); +j.css({width:c.width-d.padding*2,height:c.height-y*a-d.padding*2})},U=function(){return[b(window).width()-d.margin*2,b(window).height()-d.margin*2,b(document).scrollLeft()+d.margin,b(document).scrollTop()+d.margin]},X=function(){var a=U(),c={},g=d.autoScale,k=d.padding*2;c.width=d.width.toString().indexOf("%")>-1?parseInt(a[0]*parseFloat(d.width)/100,10):d.width+k;c.height=d.height.toString().indexOf("%")>-1?parseInt(a[1]*parseFloat(d.height)/100,10):d.height+k;if(g&&(c.width>a[0]||c.height>a[1]))if(e.type== +"image"||e.type=="swf"){g=d.width/d.height;if(c.width>a[0]){c.width=a[0];c.height=parseInt((c.width-k)/g+k,10)}if(c.height>a[1]){c.height=a[1];c.width=parseInt((c.height-k)*g+k,10)}}else{c.width=Math.min(c.width,a[0]);c.height=Math.min(c.height,a[1])}c.top=parseInt(Math.max(a[3]-20,a[3]+(a[1]-c.height-40)*0.5),10);c.left=parseInt(Math.max(a[2]-20,a[2]+(a[0]-c.width-40)*0.5),10);return c},V=function(){var a=e.orig?b(e.orig):false,c={};if(a&&a.length){c=a.offset();c.top+=parseInt(a.css("paddingTop"), +10)||0;c.left+=parseInt(a.css("paddingLeft"),10)||0;c.top+=parseInt(a.css("border-top-width"),10)||0;c.left+=parseInt(a.css("border-left-width"),10)||0;c.width=a.width();c.height=a.height();c={width:c.width+d.padding*2,height:c.height+d.padding*2,top:c.top-d.padding-20,left:c.left-d.padding-20}}else{a=U();c={width:d.padding*2,height:d.padding*2,top:parseInt(a[3]+a[1]*0.5,10),left:parseInt(a[2]+a[0]*0.5,10)}}return c},Z=function(){if(t.is(":visible")){b("div",t).css("top",L*-40+"px");L=(L+1)%12}else clearInterval(K)}; +b.fn.fancybox=function(a){if(!b(this).length)return this;b(this).data("fancybox",b.extend({},a,b.metadata?b(this).metadata():{})).unbind("click.fb").bind("click.fb",function(c){c.preventDefault();if(!h){h=true;b(this).blur();o=[];q=0;c=b(this).attr("rel")||"";if(!c||c==""||c==="nofollow")o.push(this);else{o=b("a[rel="+c+"], area[rel="+c+"]");q=o.index(this)}I()}});return this};b.fancybox=function(a,c){var g;if(!h){h=true;g=typeof c!=="undefined"?c:{};o=[];q=parseInt(g.index,10)||0;if(b.isArray(a)){for(var k= +0,C=a.length;ko.length||q<0)q=0;I()}};b.fancybox.showActivity=function(){clearInterval(K);t.show();K=setInterval(Z,66)};b.fancybox.hideActivity=function(){t.hide()};b.fancybox.next=function(){return b.fancybox.pos(p+ +1)};b.fancybox.prev=function(){return b.fancybox.pos(p-1)};b.fancybox.pos=function(a){if(!h){a=parseInt(a);o=l;if(a>-1&&a1){q=a>=l.length?0:l.length-1;I()}}};b.fancybox.cancel=function(){if(!h){h=true;b.event.trigger("fancybox-cancel");N();e.onCancel(o,q,e);h=false}};b.fancybox.close=function(){function a(){u.fadeOut("fast");n.empty().hide();f.hide();b.event.trigger("fancybox-cleanup");j.empty();d.onClosed(l,p,d);l=e=[];p=q=0;d=e={};h=false}if(!(h||f.is(":hidden"))){h= +true;if(d&&false===d.onCleanup(l,p,d))h=false;else{N();b(E.add(z).add(A)).hide();b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");j.find("iframe").attr("src",M&&/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank");d.titlePosition!=="inside"&&n.empty();f.stop();if(d.transitionOut=="elastic"){r=V();var c=f.position();i={top:c.top,left:c.left,width:f.width(),height:f.height()};if(d.opacity)i.opacity=1;n.empty().hide();B.prop=1; +b(B).animate({prop:0},{duration:d.speedOut,easing:d.easingOut,step:T,complete:a})}else f.fadeOut(d.transitionOut=="none"?0:d.speedOut,a)}}};b.fancybox.resize=function(){u.is(":visible")&&u.css("height",b(document).height());b.fancybox.center(true)};b.fancybox.center=function(a){var c,g;if(!h){g=a===true?1:0;c=U();!g&&(f.width()>c[0]||f.height()>c[1])||f.stop().animate({top:parseInt(Math.max(c[3]-20,c[3]+(c[1]-j.height()-40)*0.5-d.padding)),left:parseInt(Math.max(c[2]-20,c[2]+(c[0]-j.width()-40)*0.5- +d.padding))},typeof a=="number"?a:200)}};b.fancybox.init=function(){if(!b("#fancybox-wrap").length){b("body").append(m=b('
      '),t=b('
      '),u=b('
      '),f=b('
      '));D=b('
      ').append('
      ').appendTo(f); +D.append(j=b('
      '),E=b(''),n=b('
      '),z=b(''),A=b(''));E.click(b.fancybox.close);t.click(b.fancybox.cancel);z.click(function(a){a.preventDefault();b.fancybox.prev()});A.click(function(a){a.preventDefault();b.fancybox.next()}); +b.fn.mousewheel&&f.bind("mousewheel.fb",function(a,c){if(h)a.preventDefault();else if(b(a.target).get(0).clientHeight==0||b(a.target).get(0).scrollHeight===b(a.target).get(0).clientHeight){a.preventDefault();b.fancybox[c>0?"prev":"next"]()}});b.support.opacity||f.addClass("fancybox-ie");if(M){t.addClass("fancybox-ie6");f.addClass("fancybox-ie6");b('').prependTo(D)}}}; +b.fn.fancybox.defaults={padding:10,margin:40,opacity:false,modal:false,cyclic:false,scrolling:"auto",width:560,height:340,autoScale:true,autoDimensions:true,centerOnScroll:false,ajax:{},swf:{wmode:"transparent"},hideOnOverlayClick:true,hideOnContentClick:false,overlayShow:true,overlayOpacity:0.7,overlayColor:"#777",titleShow:true,titlePosition:"float",titleFormat:null,titleFromAlt:false,transitionIn:"fade",transitionOut:"fade",speedIn:300,speedOut:300,changeSpeed:300,changeFade:"fast",easingIn:"swing", +easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,enableKeyboardNav:true,onStart:function(){},onCancel:function(){},onComplete:function(){},onCleanup:function(){},onClosed:function(){},onError:function(){}};b(document).ready(function(){b.fancybox.init()})})(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.flot.axislabels.js b/app/assets/javascripts/jquery.flot.axislabels.js new file mode 100644 index 000000000..d75b03ba9 --- /dev/null +++ b/app/assets/javascripts/jquery.flot.axislabels.js @@ -0,0 +1,451 @@ +/* +Axis Labels Plugin for flot. +http://github.com/markrcote/flot-axislabels + +Original code is Copyright (c) 2010 Xuan Luo. +Original code was released under the GPLv3 license by Xuan Luo, September 2010. +Original code was rereleased under the MIT license by Xuan Luo, April 2012. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +(function ($) { + var options = { }; + + function canvasSupported() { + return !!document.createElement('canvas').getContext; + } + + function canvasTextSupported() { + if (!canvasSupported()) { + return false; + } + var dummy_canvas = document.createElement('canvas'); + var context = dummy_canvas.getContext('2d'); + return typeof context.fillText == 'function'; + } + + function css3TransitionSupported() { + var div = document.createElement('div'); + return typeof div.style.MozTransition != 'undefined' // Gecko + || typeof div.style.OTransition != 'undefined' // Opera + || typeof div.style.webkitTransition != 'undefined' // WebKit + || typeof div.style.transition != 'undefined'; + } + + + function AxisLabel(axisName, position, padding, plot, opts) { + this.axisName = axisName; + this.position = position; + this.padding = padding; + this.plot = plot; + this.opts = opts; + this.width = 0; + this.height = 0; + } + + AxisLabel.prototype.delete = function() { + }; + + + CanvasAxisLabel.prototype = new AxisLabel(); + CanvasAxisLabel.prototype.constructor = CanvasAxisLabel; + function CanvasAxisLabel(axisName, position, padding, plot, opts) { + AxisLabel.prototype.constructor.call(this, axisName, position, padding, + plot, opts); + } + + CanvasAxisLabel.prototype.calculateSize = function() { + if (!this.opts.axisLabelFontSizePixels) + this.opts.axisLabelFontSizePixels = 14; + if (!this.opts.axisLabelFontFamily) + this.opts.axisLabelFontFamily = 'sans-serif'; + + var textWidth = this.opts.axisLabelFontSizePixels + this.padding; + var textHeight = this.opts.axisLabelFontSizePixels + this.padding; + if (this.position == 'left' || this.position == 'right') { + this.width = this.opts.axisLabelFontSizePixels + this.padding; + this.height = 0; + } else { + this.width = 0; + this.height = this.opts.axisLabelFontSizePixels + this.padding; + } + }; + + CanvasAxisLabel.prototype.draw = function(box) { + var ctx = this.plot.getCanvas().getContext('2d'); + ctx.save(); + ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + + this.opts.axisLabelFontFamily; + var width = ctx.measureText(this.opts.axisLabel).width; + var height = this.opts.axisLabelFontSizePixels; + var x, y, angle = 0; + if (this.position == 'top') { + x = box.left + box.width/2 - width/2; + y = box.top + height*0.72; + } else if (this.position == 'bottom') { + x = box.left + box.width/2 - width/2; + y = box.top + box.height - height*0.72; + } else if (this.position == 'left') { + x = box.left + height*0.72; + y = box.height/2 + box.top + width/2; + angle = -Math.PI/2; + } else if (this.position == 'right') { + x = box.left + box.width - height*0.72; + y = box.height/2 + box.top - width/2; + angle = Math.PI/2; + } + ctx.translate(x, y); + ctx.rotate(angle); + ctx.fillText(this.opts.axisLabel, 0, 0); + ctx.restore(); + }; + + + HtmlAxisLabel.prototype = new AxisLabel(); + HtmlAxisLabel.prototype.constructor = HtmlAxisLabel; + function HtmlAxisLabel(axisName, position, padding, plot, opts) { + AxisLabel.prototype.constructor.call(this, axisName, position, + padding, plot, opts); + this.elem = null; + } + + HtmlAxisLabel.prototype.calculateSize = function() { + var elem = $('
      ' + + this.opts.axisLabel + '
      '); + this.plot.getPlaceholder().append(elem); + // store height and width of label itself, for use in draw() + this.labelWidth = elem.outerWidth(true); + this.labelHeight = elem.outerHeight(true); + elem.remove(); + + this.width = this.height = 0; + if (this.position == 'left' || this.position == 'right') { + this.width = this.labelWidth + this.padding; + } else { + this.height = this.labelHeight + this.padding; + } + }; + + HtmlAxisLabel.prototype.delete = function() { + if (this.elem) { + this.elem.remove(); + } + }; + + HtmlAxisLabel.prototype.draw = function(box) { + this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove(); + this.elem = $('
      ' + + this.opts.axisLabel + '
      '); + this.plot.getPlaceholder().append(this.elem); + if (this.position == 'top') { + this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + + 'px'); + this.elem.css('top', box.top + 'px'); + } else if (this.position == 'bottom') { + this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + + 'px'); + this.elem.css('top', box.top + box.height - this.labelHeight + + 'px'); + } else if (this.position == 'left') { + this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + + 'px'); + this.elem.css('left', box.left + 'px'); + } else if (this.position == 'right') { + this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + + 'px'); + this.elem.css('left', box.left + box.width - this.labelWidth + + 'px'); + } + }; + + + CssTransformAxisLabel.prototype = new HtmlAxisLabel(); + CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel; + function CssTransformAxisLabel(axisName, position, padding, plot, opts) { + HtmlAxisLabel.prototype.constructor.call(this, axisName, position, + padding, plot, opts); + } + + CssTransformAxisLabel.prototype.calculateSize = function() { + HtmlAxisLabel.prototype.calculateSize.call(this); + this.width = this.height = 0; + if (this.position == 'left' || this.position == 'right') { + this.width = this.labelHeight + this.padding; + } else { + this.height = this.labelHeight + this.padding; + } + }; + + CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) { + var stransforms = { + '-moz-transform': '', + '-webkit-transform': '', + '-o-transform': '', + '-ms-transform': '' + }; + if (x != 0 || y != 0) { + var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)'; + stransforms['-moz-transform'] += stdTranslate; + stransforms['-webkit-transform'] += stdTranslate; + stransforms['-o-transform'] += stdTranslate; + stransforms['-ms-transform'] += stdTranslate; + } + if (degrees != 0) { + var rotation = degrees / 90; + var stdRotate = ' rotate(' + degrees + 'deg)'; + stransforms['-moz-transform'] += stdRotate; + stransforms['-webkit-transform'] += stdRotate; + stransforms['-o-transform'] += stdRotate; + stransforms['-ms-transform'] += stdRotate; + } + var s = 'top: 0; left: 0; '; + for (var prop in stransforms) { + if (stransforms[prop]) { + s += prop + ':' + stransforms[prop] + ';'; + } + } + s += ';'; + return s; + }; + + CssTransformAxisLabel.prototype.calculateOffsets = function(box) { + var offsets = { x: 0, y: 0, degrees: 0 }; + if (this.position == 'bottom') { + offsets.x = box.left + box.width/2 - this.labelWidth/2; + offsets.y = box.top + box.height - this.labelHeight; + } else if (this.position == 'top') { + offsets.x = box.left + box.width/2 - this.labelWidth/2; + offsets.y = box.top; + } else if (this.position == 'left') { + offsets.degrees = -90; + offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2; + offsets.y = box.height/2 + box.top; + } else if (this.position == 'right') { + offsets.degrees = 90; + offsets.x = box.left + box.width - this.labelWidth/2 + - this.labelHeight/2; + offsets.y = box.height/2 + box.top; + } + return offsets; + }; + + CssTransformAxisLabel.prototype.draw = function(box) { + this.plot.getPlaceholder().find("." + this.axisName + "Label").remove(); + var offsets = this.calculateOffsets(box); + this.elem = $('
      ' + this.opts.axisLabel + '
      '); + this.plot.getPlaceholder().append(this.elem); + }; + + + IeTransformAxisLabel.prototype = new CssTransformAxisLabel(); + IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel; + function IeTransformAxisLabel(axisName, position, padding, plot, opts) { + CssTransformAxisLabel.prototype.constructor.call(this, axisName, + position, padding, + plot, opts); + this.requiresResize = false; + } + + IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) { + // I didn't feel like learning the crazy Matrix stuff, so this uses + // a combination of the rotation transform and CSS positioning. + var s = ''; + if (degrees != 0) { + var rotation = degrees/90; + while (rotation < 0) { + rotation += 4; + } + s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); '; + // see below + this.requiresResize = (this.position == 'right'); + } + if (x != 0) { + s += 'left: ' + x + 'px; '; + } + if (y != 0) { + s += 'top: ' + y + 'px; '; + } + return s; + }; + + IeTransformAxisLabel.prototype.calculateOffsets = function(box) { + var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call( + this, box); + // adjust some values to take into account differences between + // CSS and IE rotations. + if (this.position == 'top') { + // FIXME: not sure why, but placing this exactly at the top causes + // the top axis label to flip to the bottom... + offsets.y = box.top + 1; + } else if (this.position == 'left') { + offsets.x = box.left; + offsets.y = box.height/2 + box.top - this.labelWidth/2; + } else if (this.position == 'right') { + offsets.x = box.left + box.width - this.labelHeight; + offsets.y = box.height/2 + box.top - this.labelWidth/2; + } + return offsets; + }; + + IeTransformAxisLabel.prototype.draw = function(box) { + CssTransformAxisLabel.prototype.draw.call(this, box); + if (this.requiresResize) { + this.elem = this.plot.getPlaceholder().find("." + this.axisName + + "Label"); + // Since we used CSS positioning instead of transforms for + // translating the element, and since the positioning is done + // before any rotations, we have to reset the width and height + // in case the browser wrapped the text (specifically for the + // y2axis). + this.elem.css('width', this.labelWidth); + this.elem.css('height', this.labelHeight); + } + }; + + + function init(plot) { + // This is kind of a hack. There are no hooks in Flot between + // the creation and measuring of the ticks (setTicks, measureTickLabels + // in setupGrid() ) and the drawing of the ticks and plot box + // (insertAxisLabels in setupGrid() ). + // + // Therefore, we use a trick where we run the draw routine twice: + // the first time to get the tick measurements, so that we can change + // them, and then have it draw it again. + var secondPass = false; + + var axisLabels = {}; + var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 }; + + var defaultPadding = 2; // padding between axis and tick labels + plot.hooks.draw.push(function (plot, ctx) { + var hasAxisLabels = false; + if (!secondPass) { + // MEASURE AND SET OPTIONS + $.each(plot.getAxes(), function(axisName, axis) { + var opts = axis.options // Flot 0.7 + || plot.getOptions()[axisName]; // Flot 0.6 + + // Handle redraws initiated outside of this plug-in. + if (axisName in axisLabels) { + axis.labelHeight = axis.labelHeight - + axisLabels[axisName].height; + axis.labelWidth = axis.labelWidth - + axisLabels[axisName].width; + opts.labelHeight = axis.labelHeight; + opts.labelWidth = axis.labelWidth; + axisLabels[axisName].delete(); + delete axisLabels[axisName]; + } + + if (!opts || !opts.axisLabel || !axis.show) + return; + + hasAxisLabels = true; + var renderer = null; + + if (!opts.axisLabelUseHtml && + navigator.appName == 'Microsoft Internet Explorer') { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) { + rv = parseFloat(RegExp.$1); + } + if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { + renderer = CssTransformAxisLabel; + } else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { + renderer = IeTransformAxisLabel; + } else if (opts.axisLabelUseCanvas) { + renderer = CanvasAxisLabel; + } else { + renderer = HtmlAxisLabel; + } + } else { + if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) { + renderer = HtmlAxisLabel; + } else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) { + renderer = CanvasAxisLabel; + } else { + renderer = CssTransformAxisLabel; + } + } + + var padding = opts.axisLabelPadding === undefined ? + defaultPadding : opts.axisLabelPadding; + + axisLabels[axisName] = new renderer(axisName, + axis.position, padding, + plot, opts); + + // flot interprets axis.labelHeight and .labelWidth as + // the height and width of the tick labels. We increase + // these values to make room for the axis label and + // padding. + + axisLabels[axisName].calculateSize(); + + // AxisLabel.height and .width are the size of the + // axis label and padding. + // Just set opts here because axis will be sorted out on + // the redraw. + + opts.labelHeight = axis.labelHeight + + axisLabels[axisName].height; + opts.labelWidth = axis.labelWidth + + axisLabels[axisName].width; + }); + + // If there are axis labels, re-draw with new label widths and + // heights. + + if (hasAxisLabels) { + secondPass = true; + plot.setupGrid(); + plot.draw(); + } + } else { + secondPass = false; + // DRAW + $.each(plot.getAxes(), function(axisName, axis) { + var opts = axis.options // Flot 0.7 + || plot.getOptions()[axisName]; // Flot 0.6 + if (!opts || !opts.axisLabel || !axis.show) + return; + + axisLabels[axisName].draw(axis.box); + }); + } + }); + } + + + $.plot.plugins.push({ + init: init, + options: options, + name: 'axisLabels', + version: '2.0b0' + }); +})(jQuery); diff --git a/app/assets/javascripts/jquery.flot.errorbars.min.js b/app/assets/javascripts/jquery.flot.errorbars.min.js new file mode 100644 index 000000000..72d7e3dc7 --- /dev/null +++ b/app/assets/javascripts/jquery.flot.errorbars.min.js @@ -0,0 +1,63 @@ +/* Flot plugin for plotting error bars. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +Error bars are used to show standard deviation and other statistical +properties in a plot. + +* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com + +This plugin allows you to plot error-bars over points. Set "errorbars" inside +the points series to the axis name over which there will be error values in +your data array (*even* if you do not intend to plot them later, by setting +"show: null" on xerr/yerr). + +The plugin supports these options: + + series: { + points: { + errorbars: "x" or "y" or "xy", + xerr: { + show: null/false or true, + asymmetric: null/false or true, + upperCap: null or "-" or function, + lowerCap: null or "-" or function, + color: null or color, + radius: null or number + }, + yerr: { same options as xerr } + } + } + +Each data point array is expected to be of the type: + + "x" [ x, y, xerr ] + "y" [ x, y, yerr ] + "xy" [ x, y, xerr, yerr ] + +Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and +equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric +error-bars on X and asymmetric on Y would be: + + [ x, y, xerr, yerr_lower, yerr_upper ] + +By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will +draw a small cap perpendicular to the error bar. They can also be set to a +user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg. + + function drawSemiCircle( ctx, x, y, radius ) { + ctx.beginPath(); + ctx.arc( x, y, radius, 0, Math.PI, false ); + ctx.moveTo( x - radius, y ); + ctx.lineTo( x + radius, y ); + ctx.stroke(); + } + +Color and radius both default to the same ones of the points series if not +set. The independent radius parameter on xerr/yerr is useful for the case when +we may want to add error-bars to a line, without showing the interconnecting +points (with radius: 0), and still showing end caps on the error-bars. +shadowSize and lineWidth are derived as well from the points series. + +*/(function(e){function n(e,t,n,r){if(!t.points.errorbars)return;var i=[{x:!0,number:!0,required:!0},{y:!0,number:!0,required:!0}],s=t.points.errorbars;if(s=="x"||s=="xy")t.points.xerr.asymmetric?(i.push({x:!0,number:!0,required:!0}),i.push({x:!0,number:!0,required:!0})):i.push({x:!0,number:!0,required:!0});if(s=="y"||s=="xy")t.points.yerr.asymmetric?(i.push({y:!0,number:!0,required:!0}),i.push({y:!0,number:!0,required:!0})):i.push({y:!0,number:!0,required:!0});r.format=i}function r(e,t){var n=e.datapoints.points,r=null,i=null,s=null,o=null,u=e.points.xerr,a=e.points.yerr,f=e.points.errorbars;f=="x"||f=="xy"?u.asymmetric?(r=n[t+2],i=n[t+3],f=="xy"&&(a.asymmetric?(s=n[t+4],o=n[t+5]):s=n[t+4])):(r=n[t+2],f=="xy"&&(a.asymmetric?(s=n[t+3],o=n[t+4]):s=n[t+3])):f=="y"&&(a.asymmetric?(s=n[t+2],o=n[t+3]):s=n[t+2]),i==null&&(i=r),o==null&&(o=s);var l=[r,i,s,o];return u.show||(l[0]=null,l[1]=null),a.show||(l[2]=null,l[3]=null),l}function i(e,t,n){var i=n.datapoints.points,o=n.datapoints.pointsize,u=[n.xaxis,n.yaxis],a=n.points.radius,f=[n.points.xerr,n.points.yerr],l=!1;if(u[0].p2c(u[0].max)u[1].max||yu[0].max)continue;if(f[v].err=="y")if(g>u[0].max||gu[1].max)continue;var E=!0,S=!0;b>m[1]&&(E=!1,b=m[1]),w0&&T>0){var N=T/2;t.lineWidth=N,t.strokeStyle="rgba(0,0,0,0.1)",s(t,f[v],g,y,b,w,E,S,a,N+N/2,m),t.strokeStyle="rgba(0,0,0,0.2)",s(t,f[v],g,y,b,w,E,S,a,N/2,m)}t.strokeStyle=f[v].color?f[v].color:n.color,t.lineWidth=x,s(t,f[v],g,y,b,w,E,S,a,0,m)}}}}function s(t,n,r,i,s,u,a,f,l,c,h){i+=c,s+=c,u+=c,n.err=="x"?(s>r+l?o(t,[[s,i],[Math.max(r+l,h[0]),i]]):a=!1,ui+l?o(t,[[r,Math.max(i+l,h[1])],[r,u]]):f=!1),l=n.radius!=null?n.radius:l,a&&(n.upperCap=="-"?n.err=="x"?o(t,[[s,i-l],[s,i+l]]):o(t,[[r-l,s],[r+l,s]]):e.isFunction(n.upperCap)&&(n.err=="x"?n.upperCap(t,s,i,l):n.upperCap(t,r,s,l))),f&&(n.lowerCap=="-"?n.err=="x"?o(t,[[u,i-l],[u,i+l]]):o(t,[[r-l,u],[r+l,u]]):e.isFunction(n.lowerCap)&&(n.err=="x"?n.lowerCap(t,u,i,l):n.lowerCap(t,r,u,l)))}function o(e,t){e.beginPath(),e.moveTo(t[0][0],t[0][1]);for(var n=1;n=1?"rgb("+[s.r,s.g,s.b].join(",")+")":"rgba("+[s.r,s.g,s.b,s.a].join(",")+")"},s.normalize=function(){function e(e,t,n){return tn?n:t}return s.r=e(0,parseInt(s.r),255),s.g=e(0,parseInt(s.g),255),s.b=e(0,parseInt(s.b),255),s.a=e(0,s.a,1),s},s.clone=function(){return e.color.make(s.r,s.b,s.g,s.a)},s.normalize()},e.color.extract=function(t,n){var r;do{r=t.css(n).toLowerCase();if(r!=""&&r!="transparent")break;t=t.parent()}while(!e.nodeName(t.get(0),"body"));return r=="rgba(0, 0, 0, 0)"&&(r="transparent"),e.color.parse(r)},e.color.parse=function(n){var r,i=e.color.make;if(r=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(n))return i(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10));if(r=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(n))return i(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10),parseFloat(r[4]));if(r=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(n))return i(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55);if(r=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(n))return i(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55,parseFloat(r[4]));if(r=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(n))return i(parseInt(r[1],16),parseInt(r[2],16),parseInt(r[3],16));if(r=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(n))return i(parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16),parseInt(r[3]+r[3],16));var s=e.trim(n).toLowerCase();return s=="transparent"?i(255,255,255,0):(r=t[s]||[0,0,0],i(r[0],r[1],r[2]))};var t={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery),function(e){function n(t,n){var r=n.children("."+t)[0];if(r==null){r=document.createElement("canvas"),r.className=t,e(r).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(n);if(!r.getContext){if(!window.G_vmlCanvasManager)throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");r=window.G_vmlCanvasManager.initElement(r)}}this.element=r;var i=this.context=r.getContext("2d"),s=window.devicePixelRatio||1,o=i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1;this.pixelRatio=s/o,this.resize(n.width(),n.height()),this.textContainer=null,this.text={},this._textCache={}}function r(t,r,s,o){function E(e,t){t=[w].concat(t);for(var n=0;nn&&(n=i))}t<=n&&(t=n+1);var s,o=[],f=a.colors,l=f.length,c=0;for(r=0;r=0?c<.5?c=-c-.2:c=0:c=-c),o[r]=s.scale("rgb",1+c);var h=0,p;for(r=0;re.datamax&&n!=r&&(e.datamax=n)}var t=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY,r=Number.MAX_VALUE,i,s,o,a,f,l,c,h,p,d,v,m,g,y,w,S;e.each(k(),function(e,r){r.datamin=t,r.datamax=n,r.used=!1});for(i=0;i0&&c[o-h]!=null&&c[o-h]!=c[o]&&c[o-h+1]!=c[o+1]){for(a=0;aO&&(O=m)),g.y&&(mM&&(M=m))}}if(l.bars.show){var _;switch(l.bars.align){case"left":_=0;break;case"right":_=-l.bars.barWidth;break;case"center":_=-l.bars.barWidth/2;break;default:throw new Error("Invalid bar alignment: "+l.bars.align)}l.bars.horizontal?(A+=_,M+=_+l.bars.barWidth):(L+=_,O+=_+l.bars.barWidth)}x(l.xaxis,L,O),x(l.yaxis,A,M)}e.each(k(),function(e,r){r.datamin==t&&(r.datamin=null),r.datamax==n&&(r.datamax=null)})}function D(){t.css("padding",0).children(":not(.flot-base,.flot-overlay)").remove(),t.css("position")=="static"&&t.css("position","relative"),f=new n("flot-base",t),l=new n("flot-overlay",t),h=f.context,p=l.context,c=e(l.element).unbind();var r=t.data("plot");r&&(r.shutdown(),l.clear()),t.data("plot",w)}function P(){a.grid.hoverable&&(c.mousemove(at),c.bind("mouseleave",ft)),a.grid.clickable&&c.click(lt),E(b.bindEvents,[c])}function H(){ot&&clearTimeout(ot),c.unbind("mousemove",at),c.unbind("mouseleave",ft),c.unbind("click",lt),E(b.shutdown,[c])}function B(e){function t(e){return e}var n,r,i=e.options.transform||t,s=e.options.inverseTransform;e.direction=="x"?(n=e.scale=g/Math.abs(i(e.max)-i(e.min)),r=Math.min(i(e.max),i(e.min))):(n=e.scale=y/Math.abs(i(e.max)-i(e.min)),n=-n,r=Math.max(i(e.max),i(e.min))),i==t?e.p2c=function(e){return(e-r)*n}:e.p2c=function(e){return(i(e)-r)*n},s?e.c2p=function(e){return s(r+e/n)}:e.c2p=function(e){return r+e/n}}function j(e){var t=e.options,n=e.ticks||[],r=t.labelWidth||0,i=t.labelHeight||0,s=r||e.direction=="x"?Math.floor(f.width/(n.length||1)):null;legacyStyles=e.direction+"Axis "+e.direction+e.n+"Axis",layer="flot-"+e.direction+"-axis flot-"+e.direction+e.n+"-axis "+legacyStyles,font=t.font||"flot-tick-label tickLabel";for(var o=0;o=0;--t)F(o[t]);q(),e.each(o,function(e,t){I(t)})}g=f.width-m.left-m.right,y=f.height-m.bottom-m.top,e.each(n,function(e,t){B(t)}),r&&G(),it()}function U(e){var t=e.options,n=+(t.min!=null?t.min:e.datamin),r=+(t.max!=null?t.max:e.datamax),i=r-n;if(i==0){var s=r==0?1:.01;t.min==null&&(n-=s);if(t.max==null||t.min!=null)r+=s}else{var o=t.autoscaleMargin;o!=null&&(t.min==null&&(n-=i*o,n<0&&e.datamin!=null&&e.datamin>=0&&(n=0)),t.max==null&&(r+=i*o,r>0&&e.datamax!=null&&e.datamax<=0&&(r=0)))}e.min=n,e.max=r}function z(t){var n=t.options,r;typeof n.ticks=="number"&&n.ticks>0?r=n.ticks:r=.3*Math.sqrt(t.direction=="x"?f.width:f.height);var s=(t.max-t.min)/r,o=-Math.floor(Math.log(s)/Math.LN10),u=n.tickDecimals;u!=null&&o>u&&(o=u);var a=Math.pow(10,-o),l=s/a,c;l<1.5?c=1:l<3?(c=2,l>2.25&&(u==null||o+1<=u)&&(c=2.5,++o)):l<7.5?c=5:c=10,c*=a,n.minTickSize!=null&&c0&&(n.min==null&&(t.min=Math.min(t.min,p[0])),n.max==null&&p.length>1&&(t.max=Math.max(t.max,p[p.length-1]))),t.tickGenerator=function(e){var t=[],n,r;for(r=0;r1&&/\..*0$/.test((g[1]-g[0]).toFixed(m))||(t.tickDecimals=m)}}}}function W(t){var n=t.options.ticks,r=[];n==null||typeof n=="number"&&n>0?r=t.tickGenerator(t):n&&(e.isFunction(n)?r=n(t):r=n);var i,s;t.ticks=[];for(i=0;i1&&(o=u[1])):s=+u,o==null&&(o=t.tickFormatter(s,t)),isNaN(s)||t.ticks.push({v:s,label:o})}}function X(e,t){e.options.autoscaleMargin&&t.length>0&&(e.options.min==null&&(e.min=Math.min(e.min,t[0].v)),e.options.max==null&&t.length>1&&(e.max=Math.max(e.max,t[t.length-1].v)))}function V(){f.clear(),E(b.drawBackground,[h]);var e=a.grid;e.show&&e.backgroundColor&&K(),e.show&&!e.aboveData&&Q();for(var t=0;ti){var a=r;r=i,i=a}return{from:r,to:i,axis:n}}function K(){h.save(),h.translate(m.left,m.top),h.fillStyle=bt(a.grid.backgroundColor,y,0,"rgba(255, 255, 255, 0)"),h.fillRect(0,0,g,y),h.restore()}function Q(){var t,n,r,i;h.save(),h.translate(m.left,m.top);var s=a.grid.markings;if(s){e.isFunction(s)&&(n=w.getAxes(),n.xmin=n.xaxis.min,n.xmax=n.xaxis.max,n.ymin=n.yaxis.min,n.ymax=n.yaxis.max,s=s(n));for(t=0;tu.axis.max||f.tof.axis.max)continue;u.from=Math.max(u.from,u.axis.min),u.to=Math.min(u.to,u.axis.max),f.from=Math.max(f.from,f.axis.min),f.to=Math.min(f.to,f.axis.max);if(u.from==u.to&&f.from==f.to)continue;u.from=u.axis.p2c(u.from),u.to=u.axis.p2c(u.to),f.from=f.axis.p2c(f.from),f.to=f.axis.p2c(f.to),u.from==u.to||f.from==f.to?(h.beginPath(),h.strokeStyle=o.color||a.grid.markingsColor,h.lineWidth=o.lineWidth||a.grid.markingsLineWidth,h.moveTo(u.from,f.from),h.lineTo(u.to,f.to),h.stroke()):(h.fillStyle=o.color||a.grid.markingsColor,h.fillRect(u.from,f.to,u.to-u.from,f.from-f.to))}}n=k(),r=a.grid.borderWidth;for(var l=0;lc.max||d=="full"&&(typeof r=="object"&&r[c.position]>0||r>0)&&(x==c.min||x==c.max))continue;c.direction=="x"?(v=c.p2c(x),S=d=="full"?-y:d,c.position=="top"&&(S=-S)):(b=c.p2c(x),E=d=="full"?-g:d,c.position=="left"&&(E=-E)),h.lineWidth==1&&(c.direction=="x"?v=Math.floor(v)+.5:b=Math.floor(b)+.5),h.moveTo(v,b),h.lineTo(v+E,b+S)}h.stroke()}r&&(i=a.grid.borderColor,typeof r=="object"||typeof i=="object"?(typeof r!="object"&&(r={top:r,right:r,bottom:r,left:r}),typeof i!="object"&&(i={top:i,right:i,bottom:i,left:i}),r.top>0&&(h.strokeStyle=i.top,h.lineWidth=r.top,h.beginPath(),h.moveTo(0-r.left,0-r.top/2),h.lineTo(g,0-r.top/2),h.stroke()),r.right>0&&(h.strokeStyle=i.right,h.lineWidth=r.right,h.beginPath(),h.moveTo(g+r.right/2,0-r.top),h.lineTo(g+r.right/2,y),h.stroke()),r.bottom>0&&(h.strokeStyle=i.bottom,h.lineWidth=r.bottom,h.beginPath(),h.moveTo(g+r.right,y+r.bottom/2),h.lineTo(0,y+r.bottom/2),h.stroke()),r.left>0&&(h.strokeStyle=i.left,h.lineWidth=r.left,h.beginPath(),h.moveTo(0-r.left/2,y+r.bottom),h.lineTo(0-r.left/2,0),h.stroke())):(h.lineWidth=r,h.strokeStyle=a.grid.borderColor,h.strokeRect(-r/2,-r/2,g+r,y+r))),h.restore()}function G(){e.each(k(),function(e,t){if(!t.show||t.ticks.length==0)return;var n=t.box,r=t.direction+"Axis "+t.direction+t.n+"Axis",i="flot-"+t.direction+"-axis flot-"+t.direction+t.n+"-axis "+r,s=t.options.font||"flot-tick-label tickLabel",o,u,a,l,c;f.removeText(i);for(var h=0;ht.max)continue;t.direction=="x"?(l="center",u=m.left+t.p2c(o.v),t.position=="bottom"?a=n.top+n.padding:(a=n.top+n.height-n.padding,c="bottom")):(c="middle",a=m.top+t.p2c(o.v),t.position=="left"?(u=n.left+n.width-n.padding,l="right"):u=n.left+n.padding),f.addText(i,u,a,o.label,s,null,null,l,c)}})}function Y(e){e.lines.show&&Z(e),e.bars.show&&nt(e),e.points.show&&et(e)}function Z(e){function t(e,t,n,r,i){var s=e.points,o=e.pointsize,u=null,a=null;h.beginPath();for(var f=o;f=d&&c>i.max){if(d>i.max)continue;l=(i.max-c)/(d-c)*(p-l)+l,c=i.max}else if(d>=c&&d>i.max){if(c>i.max)continue;p=(i.max-c)/(d-c)*(p-l)+l,d=i.max}if(l<=p&&l=p&&l>r.max){if(p>r.max)continue;c=(r.max-l)/(p-l)*(d-c)+c,l=r.max}else if(p>=l&&p>r.max){if(l>r.max)continue;d=(r.max-l)/(p-l)*(d-c)+c,p=r.max}(l!=u||c!=a)&&h.moveTo(r.p2c(l)+t,i.p2c(c)+n),u=p,a=d,h.lineTo(r.p2c(p)+t,i.p2c(d)+n)}h.stroke()}function n(e,t,n){var r=e.points,i=e.pointsize,s=Math.min(Math.max(0,n.min),n.max),o=0,u,a=!1,f=1,l=0,c=0;for(;;){if(i>0&&o>r.length+i)break;o+=i;var p=r[o-i],d=r[o-i+f],v=r[o],m=r[o+f];if(a){if(i>0&&p!=null&&v==null){c=o,i=-i,f=2;continue}if(i<0&&o==l+i){h.fill(),a=!1,i=-i,f=1,o=l=c+i;continue}}if(p==null||v==null)continue;if(p<=v&&p=v&&p>t.max){if(v>t.max)continue;d=(t.max-p)/(v-p)*(m-d)+d,p=t.max}else if(v>=p&&v>t.max){if(p>t.max)continue;m=(t.max-p)/(v-p)*(m-d)+d,v=t.max}a||(h.beginPath(),h.moveTo(t.p2c(p),n.p2c(s)),a=!0);if(d>=n.max&&m>=n.max){h.lineTo(t.p2c(p),n.p2c(n.max)),h.lineTo(t.p2c(v),n.p2c(n.max));continue}if(d<=n.min&&m<=n.min){h.lineTo(t.p2c(p),n.p2c(n.min)),h.lineTo(t.p2c(v),n.p2c(n.min));continue}var g=p,y=v;d<=m&&d=n.min?(p=(n.min-d)/(m-d)*(v-p)+p,d=n.min):m<=d&&m=n.min&&(v=(n.min-d)/(m-d)*(v-p)+p,m=n.min),d>=m&&d>n.max&&m<=n.max?(p=(n.max-d)/(m-d)*(v-p)+p,d=n.max):m>=d&&m>n.max&&d<=n.max&&(v=(n.max-d)/(m-d)*(v-p)+p,m=n.max),p!=g&&h.lineTo(t.p2c(g),n.p2c(d)),h.lineTo(t.p2c(p),n.p2c(d)),h.lineTo(t.p2c(v),n.p2c(m)),v!=y&&(h.lineTo(t.p2c(v),n.p2c(m)),h.lineTo(t.p2c(y),n.p2c(m)))}}h.save(),h.translate(m.left,m.top),h.lineJoin="round";var r=e.lines.lineWidth,i=e.shadowSize;if(r>0&&i>0){h.lineWidth=i,h.strokeStyle="rgba(0,0,0,0.1)";var s=Math.PI/18;t(e.datapoints,Math.sin(s)*(r/2+i/2),Math.cos(s)*(r/2+i/2),e.xaxis,e.yaxis),h.lineWidth=i/2,t(e.datapoints,Math.sin(s)*(r/2+i/4),Math.cos(s)*(r/2+i/4),e.xaxis,e.yaxis)}h.lineWidth=r,h.strokeStyle=e.color;var o=rt(e.lines,e.color,0,y);o&&(h.fillStyle=o,n(e.datapoints,e.xaxis,e.yaxis)),r>0&&t(e.datapoints,0,0,e.xaxis,e.yaxis),h.restore()}function et(e){function t(e,t,n,r,i,s,o,u){var a=e.points,f=e.pointsize;for(var l=0;ls.max||po.max)continue;h.beginPath(),c=s.p2c(c),p=o.p2c(p)+r,u=="circle"?h.arc(c,p,t,0,i?Math.PI:Math.PI*2,!1):u(h,c,p,t,i),h.closePath(),n&&(h.fillStyle=n,h.fill()),h.stroke()}}h.save(),h.translate(m.left,m.top);var n=e.points.lineWidth,r=e.shadowSize,i=e.points.radius,s=e.points.symbol;n==0&&(n=1e-4);if(n>0&&r>0){var o=r/2;h.lineWidth=o,h.strokeStyle="rgba(0,0,0,0.1)",t(e.datapoints,i,null,o+o/2,!0,e.xaxis,e.yaxis,s),h.strokeStyle="rgba(0,0,0,0.2)",t(e.datapoints,i,null,o/2,!0,e.xaxis,e.yaxis,s)}h.lineWidth=n,h.strokeStyle=e.color,t(e.datapoints,i,rt(e.points,e.color),0,!1,e.xaxis,e.yaxis,s),h.restore()}function tt(e,t,n,r,i,s,o,u,a,f,l,c){var h,p,d,v,m,g,y,b,w;l?(b=g=y=!0,m=!1,h=n,p=e,v=t+r,d=t+i,pu.max||va.max)return;hu.max&&(p=u.max,g=!1),da.max&&(v=a.max,y=!1),h=u.p2c(h),d=a.p2c(d),p=u.p2c(p),v=a.p2c(v),o&&(f.beginPath(),f.moveTo(h,d),f.lineTo(h,v),f.lineTo(p,v),f.lineTo(p,d),f.fillStyle=o(d,v),f.fill()),c>0&&(m||g||y||b)&&(f.beginPath(),f.moveTo(h,d+s),m?f.lineTo(h,v+s):f.moveTo(h,v+s),y?f.lineTo(p,v+s):f.moveTo(p,v+s),g?f.lineTo(p,d+s):f.moveTo(p,d+s),b?f.lineTo(h,d+s):f.moveTo(h,d+s),f.stroke())}function nt(e){function t(t,n,r,i,s,o,u){var a=t.points,f=t.pointsize;for(var l=0;l"),n.push(""),i=!0),n.push('
      '+''+h.label+"")}i&&n.push("");if(n.length==0)return;var p=''+n.join("")+"
      ";if(a.legend.container!=null)e(a.legend.container).html(p);else{var d="",v=a.legend.position,g=a.legend.margin;g[0]==null&&(g=[g,g]),v.charAt(0)=="n"?d+="top:"+(g[1]+m.top)+"px;":v.charAt(0)=="s"&&(d+="bottom:"+(g[1]+m.bottom)+"px;"),v.charAt(1)=="e"?d+="right:"+(g[0]+m.right)+"px;":v.charAt(1)=="w"&&(d+="left:"+(g[0]+m.left)+"px;");var y=e('
      '+p.replace('style="','style="position:absolute;'+d+";")+"
      ").appendTo(t);if(a.legend.backgroundOpacity!=0){var b=a.legend.backgroundColor;b==null&&(b=a.grid.backgroundColor,b&&typeof b=="string"?b=e.color.parse(b):b=e.color.extract(y,"background-color"),b.a=1,b=b.toString());var w=y.children();e('
      ').prependTo(y).css("opacity",a.legend.backgroundOpacity)}}}function ut(e,t,n){var r=a.grid.mouseActiveRadius,i=r*r+1,s=null,o=!1,f,l,c;for(f=u.length-1;f>=0;--f){if(!n(u[f]))continue;var h=u[f],p=h.xaxis,d=h.yaxis,v=h.datapoints.points,m=p.c2p(e),g=d.c2p(t),y=r/p.scale,b=r/d.scale;c=h.datapoints.pointsize,p.options.inverseTransform&&(y=Number.MAX_VALUE),d.options.inverseTransform&&(b=Number.MAX_VALUE);if(h.lines.show||h.points.show)for(l=0;ly||w-m<-y||E-g>b||E-g<-b)continue;var S=Math.abs(p.p2c(w)-e),x=Math.abs(d.p2c(E)-t),T=S*S+x*x;T=Math.min(k,w)&&g>=E+N&&g<=E+C:m>=w+N&&m<=w+C&&g>=Math.min(k,E)&&g<=Math.max(k,E))s=[f,l/c]}}}return s?(f=s[0],l=s[1],c=u[f].datapoints.pointsize,{datapoint:u[f].datapoints.points.slice(l*c,(l+1)*c),dataIndex:l,series:u[f],seriesIndex:f}):null}function at(e){a.grid.hoverable&&ct("plothover",e,function(e){return e["hoverable"]!=0})}function ft(e){a.grid.hoverable&&ct("plothover",e,function(e){return!1})}function lt(e){ct("plotclick",e,function(e){return e["clickable"]!=0})}function ct(e,n,r){var i=c.offset(),s=n.pageX-i.left-m.left,o=n.pageY-i.top-m.top,u=L({left:s,top:o});u.pageX=n.pageX,u.pageY=n.pageY;var f=ut(s,o,r);f&&(f.pageX=parseInt(f.series.xaxis.p2c(f.datapoint[0])+i.left+m.left,10),f.pageY=parseInt(f.series.yaxis.p2c(f.datapoint[1])+i.top+m.top,10));if(a.grid.autoHighlight){for(var l=0;ls.max||io.max)return;var a=t.points.radius+t.points.lineWidth/2;p.lineWidth=a,p.strokeStyle=u;var f=1.5*a;r=s.p2c(r),i=o.p2c(i),p.beginPath(),t.points.symbol=="circle"?p.arc(r,i,f,0,2*Math.PI,!1):t.points.symbol(p,r,i,f,!1),p.closePath(),p.stroke()}function yt(t,n){var r=typeof t.highlightColor=="string"?t.highlightColor:e.color.parse(t.color).scale("a",.5).toString(),i=r,s=t.bars.align=="left"?0:-t.bars.barWidth/2;p.lineWidth=t.bars.lineWidth,p.strokeStyle=r,tt(n[0],n[1],n[2]||0,s,s+t.bars.barWidth,0,function(){return i},t.xaxis,t.yaxis,p,t.bars.horizontal,t.bars.lineWidth)}function bt(t,n,r,i){if(typeof t=="string")return t;var s=h.createLinearGradient(0,r,0,n);for(var o=0,u=t.colors.length;o
      ").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)),n=this.text[t]=e("
      ").addClass(t).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)),n},n.prototype.getTextInfo=function(t,n,r,i,s){var o,u,a,f;n=""+n,typeof r=="object"?o=r.style+" "+r.variant+" "+r.weight+" "+r.size+"px/"+r.lineHeight+"px "+r.family:o=r,u=this._textCache[t],u==null&&(u=this._textCache[t]={}),a=u[o],a==null&&(a=u[o]={}),f=a[n];if(f==null){var l=e("
      ").html(n).css({position:"absolute","max-width":s,top:-9999}).appendTo(this.getTextLayer(t));typeof r=="object"?l.css({font:o,color:r.color}):typeof r=="string"&&l.addClass(r),f=a[n]={width:l.outerWidth(!0),height:l.outerHeight(!0),element:l,positions:[]},l.detach()}return f},n.prototype.addText=function(e,t,n,r,i,s,o,u,a){var f=this.getTextInfo(e,r,i,s,o),l=f.positions;u=="center"?t-=f.width/2:u=="right"&&(t-=f.width),a=="middle"?n-=f.height/2:a=="bottom"&&(n-=f.height);for(var c=0,h;h=l[c];c++)if(h.x==t&&h.y==n){h.active=!0;return}h={active:!0,rendered:!1,element:l.length?f.element.clone():f.element,x:t,y:n},l.push(h),h.element.css({top:Math.round(n),left:Math.round(t),"text-align":u})},n.prototype.removeText=function(e,n,r,i,s,o){if(i==null){var u=this._textCache[e];if(u!=null)for(var a in u)if(t.call(u,a)){var f=u[a];for(var l in f)if(t.call(f,l)){var c=f[l].positions;for(var h=0,p;p=c[h];h++)p.active=!1}}}else{var c=this.getTextInfo(e,i,s,o).positions;for(var h=0,p;p=c[h];h++)p.x==n&&p.y==r&&(p.active=!1)}},e.plot=function(t,n,i){var s=new r(e(t),n,i,e.plot.plugins);return s},e.plot.version="0.8.1",e.plot.plugins=[],e.fn.plot=function(t,n){return this.each(function(){e.plot(this,t,n)})}}(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.form.js b/app/assets/javascripts/jquery.form.js new file mode 100644 index 000000000..bc0061418 --- /dev/null +++ b/app/assets/javascripts/jquery.form.js @@ -0,0 +1,11 @@ +/*! + * jQuery Form Plugin + * version: 2.83 (11-JUL-2011) + * @requires jQuery v1.3.2 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +(function(a){function b(){var a="[jquery.form] "+Array.prototype.join.call(arguments,"");if(window.console&&window.console.log){window.console.log(a)}else if(window.opera&&window.opera.postError){window.opera.postError(a)}}a.fn.ajaxSubmit=function(c){function t(e){function C(c){if(o.aborted||B){return}try{z=w(n)}catch(d){b("cannot access response document: ",d);c=v}if(c===u&&o){o.abort("timeout");return}else if(c==v&&o){o.abort("server abort");return}if(!z||z.location.href==j.iframeSrc){if(!r)return}n.detachEvent?n.detachEvent("onload",C):n.removeEventListener("load",C,false);var e="success",f;try{if(r){throw"timeout"}var g=j.dataType=="xml"||z.XMLDocument||a.isXMLDoc(z);b("isXml="+g);if(!g&&window.opera&&(z.body==null||z.body.innerHTML=="")){if(--A){b("requeing onLoad callback, DOM not available");setTimeout(C,250);return}}var h=z.body?z.body:z.documentElement;o.responseText=h?h.innerHTML:null;o.responseXML=z.XMLDocument?z.XMLDocument:z;if(g)j.dataType="xml";o.getResponseHeader=function(a){var b={"content-type":j.dataType};return b[a]};if(h){o.status=Number(h.getAttribute("status"))||o.status;o.statusText=h.getAttribute("statusText")||o.statusText}var i=j.dataType||"";var l=/(json|script|text)/.test(i.toLowerCase());if(l||j.textarea){var p=z.getElementsByTagName("textarea")[0];if(p){o.responseText=p.value;o.status=Number(p.getAttribute("status"))||o.status;o.statusText=p.getAttribute("statusText")||o.statusText}else if(l){var q=z.getElementsByTagName("pre")[0];var t=z.getElementsByTagName("body")[0];if(q){o.responseText=q.textContent?q.textContent:q.innerHTML}else if(t){o.responseText=t.innerHTML}}}else if(j.dataType=="xml"&&!o.responseXML&&o.responseText!=null){o.responseXML=D(o.responseText)}try{y=F(o,j.dataType,j)}catch(c){e="parsererror";o.error=f=c||e}}catch(c){b("error caught: ",c);e="error";o.error=f=c||e}if(o.aborted){b("upload aborted");e=null}if(o.status){e=o.status>=200&&o.status<300||o.status===304?"success":"error"}if(e==="success"){j.success&&j.success.call(j.context,y,"success",o);k&&a.event.trigger("ajaxSuccess",[o,j])}else if(e){if(f==undefined)f=o.statusText;j.error&&j.error.call(j.context,o,e,f);k&&a.event.trigger("ajaxError",[o,j,f])}k&&a.event.trigger("ajaxComplete",[o,j]);if(k&&!--a.active){a.event.trigger("ajaxStop")}j.complete&&j.complete.call(j.context,o,e);B=true;if(j.timeout)clearTimeout(s);setTimeout(function(){if(!j.iframeTarget)m.remove();o.responseXML=null},100)}function x(){function h(){try{var a=w(n).readyState;b("state = "+a);if(a.toLowerCase()=="uninitialized")setTimeout(h,50)}catch(c){b("Server abort: ",c," (",c.name,")");C(v);s&&clearTimeout(s);s=undefined}}var c=g.attr("target"),e=g.attr("action");f.setAttribute("target",l);if(!d){f.setAttribute("method","POST")}if(e!=j.url){f.setAttribute("action",j.url)}if(!j.skipEncodingOverride&&(!d||/post/i.test(d))){g.attr({encoding:"multipart/form-data",enctype:"multipart/form-data"})}if(j.timeout){s=setTimeout(function(){r=true;C(u)},j.timeout)}var i=[];try{if(j.extraData){for(var k in j.extraData){i.push(a('').attr("value",j.extraData[k]).appendTo(f)[0])}}if(!j.iframeTarget){m.appendTo("body");n.attachEvent?n.attachEvent("onload",C):n.addEventListener("load",C,false)}setTimeout(h,15);f.submit()}finally{f.setAttribute("action",e);if(c){f.setAttribute("target",c)}else{g.removeAttr("target")}a(i).remove()}}function w(a){var b=a.contentWindow?a.contentWindow.document:a.contentDocument?a.contentDocument:a.document;return b}var f=g[0],h,i,j,k,l,m,n,o,p,q,r,s;var t=!!a.fn.prop;if(e){for(i=0;i');m.css({position:"absolute",top:"-1000px",left:"-1000px"})}n=m[0];o={aborted:0,responseText:null,responseXML:null,status:0,statusText:"n/a",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(c){var d=c==="timeout"?"timeout":"aborted";b("aborting upload... "+d);this.aborted=1;m.attr("src",j.iframeSrc);o.error=d;j.error&&j.error.call(j.context,o,d,c);k&&a.event.trigger("ajaxError",[o,j,d]);j.complete&&j.complete.call(j.context,o,d)}};k=j.global;if(k&&!(a.active++)){a.event.trigger("ajaxStart")}if(k){a.event.trigger("ajaxSend",[o,j])}if(j.beforeSend&&j.beforeSend.call(j.context,o,j)===false){if(j.global){a.active--}return}if(o.aborted){return}p=f.clk;if(p){q=p.name;if(q&&!p.disabled){j.extraData=j.extraData||{};j.extraData[q]=p.value;if(p.type=="image"){j.extraData[q+".x"]=f.clk_x;j.extraData[q+".y"]=f.clk_y}}}var u=1;var v=2;if(j.forceSync){x()}else{setTimeout(x,10)}var y,z,A=50,B;var D=a.parseXML||function(a,b){if(window.ActiveXObject){b=new ActiveXObject("Microsoft.XMLDOM");b.async="false";b.loadXML(a)}else{b=(new DOMParser).parseFromString(a,"text/xml")}return b&&b.documentElement&&b.documentElement.nodeName!="parsererror"?b:null};var E=a.parseJSON||function(a){return window["eval"]("("+a+")")};var F=function(b,c,d){var e=b.getResponseHeader("content-type")||"",f=c==="xml"||!c&&e.indexOf("xml")>=0,g=f?b.responseXML:b.responseText;if(f&&g.documentElement.nodeName==="parsererror"){a.error&&a.error("parsererror")}if(d&&d.dataFilter){g=d.dataFilter(g,c)}if(typeof g==="string"){if(c==="json"||!c&&e.indexOf("json")>=0){g=E(g)}else if(c==="script"||!c&&e.indexOf("javascript")>=0){a.globalEval(g)}}return g}}if(!this.length){b("ajaxSubmit: skipping submit process - no element selected");return this}var d,e,f,g=this;if(typeof c=="function"){c={success:c}}d=this.attr("method");e=this.attr("action");f=typeof e==="string"?a.trim(e):"";f=f||window.location.href||"";if(f){f=(f.match(/^([^#]+)/)||[])[1]}c=a.extend(true,{url:f,success:a.ajaxSettings.success,type:d||"GET",iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank"},c);var h={};this.trigger("form-pre-serialize",[this,c,h]);if(h.veto){b("ajaxSubmit: submit vetoed via form-pre-serialize trigger");return this}if(c.beforeSerialize&&c.beforeSerialize(this,c)===false){b("ajaxSubmit: submit aborted via beforeSerialize callback");return this}var i,j,k=this.formToArray(c.semantic);if(c.data){c.extraData=c.data;for(i in c.data){if(c.data[i]instanceof Array){for(var l in c.data[i]){k.push({name:i,value:c.data[i][l]})}}else{j=c.data[i];j=a.isFunction(j)?j():j;k.push({name:i,value:j})}}}if(c.beforeSubmit&&c.beforeSubmit(k,this,c)===false){b("ajaxSubmit: submit aborted via beforeSubmit callback");return this}this.trigger("form-submit-validate",[k,this,c,h]);if(h.veto){b("ajaxSubmit: submit vetoed via form-submit-validate trigger");return this}var m=a.param(k);if(c.type.toUpperCase()=="GET"){c.url+=(c.url.indexOf("?")>=0?"&":"?")+m;c.data=null}else{c.data=m}var n=[];if(c.resetForm){n.push(function(){g.resetForm()})}if(c.clearForm){n.push(function(){g.clearForm()})}if(!c.dataType&&c.target){var o=c.success||function(){};n.push(function(b){var d=c.replaceTarget?"replaceWith":"html";a(c.target)[d](b).each(o,arguments)})}else if(c.success){n.push(c.success)}c.success=function(a,b,d){var e=c.context||c;for(var f=0,h=n.length;f0;var q="multipart/form-data";var r=g.attr("enctype")==q||g.attr("encoding")==q;if(c.iframe!==false&&(p||c.iframe||r)){if(c.closeKeepAlive){a.get(c.closeKeepAlive,function(){t(k)})}else{t(k)}}else{if(a.browser.msie&&d=="get"){var s=g[0].getAttribute("method");if(typeof s==="string")c.type=s}a.ajax(c)}this.trigger("form-submit-notify",[this,c]);return this};a.fn.ajaxForm=function(c){if(this.length===0){var d={s:this.selector,c:this.context};if(!a.isReady&&d.s){b("DOM not ready, queuing ajaxForm");a(function(){a(d.s,d.c).ajaxForm(c)});return this}b("terminating; zero elements found by selector"+(a.isReady?"":" (DOM not ready)"));return this}return this.ajaxFormUnbind().bind("submit.form-plugin",function(b){if(!b.isDefaultPrevented()){b.preventDefault();a(this).ajaxSubmit(c)}}).bind("click.form-plugin",function(b){var c=b.target;var d=a(c);if(!d.is(":submit,input:image")){var e=d.closest(":submit");if(e.length==0){return}c=e[0]}var f=this;f.clk=c;if(c.type=="image"){if(b.offsetX!=undefined){f.clk_x=b.offsetX;f.clk_y=b.offsetY}else if(typeof a.fn.offset=="function"){var g=d.offset();f.clk_x=b.pageX-g.left;f.clk_y=b.pageY-g.top}else{f.clk_x=b.pageX-c.offsetLeft;f.clk_y=b.pageY-c.offsetTop}}setTimeout(function(){f.clk=f.clk_x=f.clk_y=null},100)})};a.fn.ajaxFormUnbind=function(){return this.unbind("submit.form-plugin click.form-plugin")};a.fn.formToArray=function(b){var c=[];if(this.length===0){return c}var d=this[0];var e=b?d.getElementsByTagName("*"):d.elements;if(!e){return c}var f,g,h,i,j,k,l;for(f=0,k=e.length;f").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
      a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
      "+""+"
      ",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
      t
      ",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
      ",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

      ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
      ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
      ","
      "]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
      ").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/app/assets/javascripts/jquery_ujs.js b/app/assets/javascripts/jquery_ujs.js new file mode 100644 index 000000000..50121d6ed --- /dev/null +++ b/app/assets/javascripts/jquery_ujs.js @@ -0,0 +1,393 @@ +(function($, undefined) { + +/** + * Unobtrusive scripting adapter for jQuery + * https://github.com/rails/jquery-ujs + * + * Requires jQuery 1.7.0 or later. + * + * Released under the MIT license + * + */ + + // Cut down on the number of issues from people inadvertently including jquery_ujs twice + // by detecting and raising an error when it happens. + if ( $.rails !== undefined ) { + $.error('jquery-ujs has already been loaded!'); + } + + // Shorthand to make it a little easier to call public rails functions from within rails.js + var rails; + + $.rails = rails = { + // Link elements bound by jquery-ujs + linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]', + + // Button elements boud jquery-ujs + buttonClickSelector: 'button[data-remote]', + + // Select elements bound by jquery-ujs + inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]', + + // Form elements bound by jquery-ujs + formSubmitSelector: 'form', + + // Form input elements bound by jquery-ujs + formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])', + + // Form input elements disabled during form submission + disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]', + + // Form input elements re-enabled after form submission + enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled', + + // Form required input elements + requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])', + + // Form file input elements + fileInputSelector: 'input[type=file]', + + // Link onClick disable selector with possible reenable after remote submission + linkDisableSelector: 'a[data-disable-with]', + + // Make sure that every Ajax request sends the CSRF token + CSRFProtection: function(xhr) { + var token = $('meta[name="csrf-token"]').attr('content'); + if (token) xhr.setRequestHeader('X-CSRF-Token', token); + }, + + // Triggers an event on an element and returns false if the event result is false + fire: function(obj, name, data) { + var event = $.Event(name); + obj.trigger(event, data); + return event.result !== false; + }, + + // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm + confirm: function(message) { + return confirm(message); + }, + + // Default ajax function, may be overridden with custom function in $.rails.ajax + ajax: function(options) { + return $.ajax(options); + }, + + // Default way to get an element's href. May be overridden at $.rails.href. + href: function(element) { + return element.attr('href'); + }, + + // Submits "remote" forms and links with ajax + handleRemote: function(element) { + var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options; + + if (rails.fire(element, 'ajax:before')) { + elCrossDomain = element.data('cross-domain'); + crossDomain = elCrossDomain === undefined ? null : elCrossDomain; + withCredentials = element.data('with-credentials') || null; + dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); + + if (element.is('form')) { + method = element.attr('method'); + url = element.attr('action'); + data = element.serializeArray(); + // memoized value from clicked submit button + var button = element.data('ujs:submit-button'); + if (button) { + data.push(button); + element.data('ujs:submit-button', null); + } + } else if (element.is(rails.inputChangeSelector)) { + method = element.data('method'); + url = element.data('url'); + data = element.serialize(); + if (element.data('params')) data = data + "&" + element.data('params'); + } else if (element.is(rails.buttonClickSelector)) { + method = element.data('method') || 'get'; + url = element.data('url'); + data = element.serialize(); + if (element.data('params')) data = data + "&" + element.data('params'); + } else { + method = element.data('method'); + url = rails.href(element); + data = element.data('params') || null; + } + + options = { + type: method || 'GET', data: data, dataType: dataType, + // stopping the "ajax:beforeSend" event will cancel the ajax request + beforeSend: function(xhr, settings) { + if (settings.dataType === undefined) { + xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); + } + return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); + }, + success: function(data, status, xhr) { + element.trigger('ajax:success', [data, status, xhr]); + }, + complete: function(xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + }, + error: function(xhr, status, error) { + element.trigger('ajax:error', [xhr, status, error]); + }, + crossDomain: crossDomain + }; + + // There is no withCredentials for IE6-8 when + // "Enable native XMLHTTP support" is disabled + if (withCredentials) { + options.xhrFields = { + withCredentials: withCredentials + }; + } + + // Only pass url to `ajax` options if not blank + if (url) { options.url = url; } + + var jqxhr = rails.ajax(options); + element.trigger('ajax:send', jqxhr); + return jqxhr; + } else { + return false; + } + }, + + // Handles "data-method" on links such as: + // Delete + handleMethod: function(link) { + var href = rails.href(link), + method = link.data('method'), + target = link.attr('target'), + csrf_token = $('meta[name=csrf-token]').attr('content'), + csrf_param = $('meta[name=csrf-param]').attr('content'), + form = $('
      '), + metadata_input = ''; + + if (csrf_param !== undefined && csrf_token !== undefined) { + metadata_input += ''; + } + + if (target) { form.attr('target', target); } + + form.hide().append(metadata_input).appendTo('body'); + form.submit(); + }, + + /* Disables form elements: + - Caches element value in 'ujs:enable-with' data store + - Replaces element text with value of 'data-disable-with' attribute + - Sets disabled property to true + */ + disableFormElements: function(form) { + form.find(rails.disableSelector).each(function() { + var element = $(this), method = element.is('button') ? 'html' : 'val'; + element.data('ujs:enable-with', element[method]()); + element[method](element.data('disable-with')); + element.prop('disabled', true); + }); + }, + + /* Re-enables disabled form elements: + - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) + - Sets disabled property to false + */ + enableFormElements: function(form) { + form.find(rails.enableSelector).each(function() { + var element = $(this), method = element.is('button') ? 'html' : 'val'; + if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); + element.prop('disabled', false); + }); + }, + + /* For 'data-confirm' attribute: + - Fires `confirm` event + - Shows the confirmation dialog + - Fires the `confirm:complete` event + + Returns `true` if no function stops the chain and user chose yes; `false` otherwise. + Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. + Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function + return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. + */ + allowAction: function(element) { + var message = element.data('confirm'), + answer = false, callback; + if (!message) { return true; } + + if (rails.fire(element, 'confirm')) { + answer = rails.confirm(message); + callback = rails.fire(element, 'confirm:complete', [answer]); + } + return answer && callback; + }, + + // Helper function which checks for blank inputs in a form that match the specified CSS selector + blankInputs: function(form, specifiedSelector, nonBlank) { + var inputs = $(), input, valueToCheck, + selector = specifiedSelector || 'input,textarea', + allInputs = form.find(selector); + + allInputs.each(function() { + input = $(this); + valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : input.val(); + // If nonBlank and valueToCheck are both truthy, or nonBlank and valueToCheck are both falsey + if (!valueToCheck === !nonBlank) { + + // Don't count unchecked required radio if other radio with same name is checked + if (input.is('input[type=radio]') && allInputs.filter('input[type=radio]:checked[name="' + input.attr('name') + '"]').length) { + return true; // Skip to next input + } + + inputs = inputs.add(input); + } + }); + return inputs.length ? inputs : false; + }, + + // Helper function which checks for non-blank inputs in a form that match the specified CSS selector + nonBlankInputs: function(form, specifiedSelector) { + return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank + }, + + // Helper function, needed to provide consistent behavior in IE + stopEverything: function(e) { + $(e.target).trigger('ujs:everythingStopped'); + e.stopImmediatePropagation(); + return false; + }, + + // replace element's html with the 'data-disable-with' after storing original html + // and prevent clicking on it + disableElement: function(element) { + element.data('ujs:enable-with', element.html()); // store enabled state + element.html(element.data('disable-with')); // set to disabled state + element.bind('click.railsDisable', function(e) { // prevent further clicking + return rails.stopEverything(e); + }); + }, + + // restore element to its original state which was disabled by 'disableElement' above + enableElement: function(element) { + if (element.data('ujs:enable-with') !== undefined) { + element.html(element.data('ujs:enable-with')); // set to old enabled state + element.removeData('ujs:enable-with'); // clean up cache + } + element.unbind('click.railsDisable'); // enable element + } + + }; + + if (rails.fire($(document), 'rails:attachBindings')) { + + $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }}); + + $(document).delegate(rails.linkDisableSelector, 'ajax:complete', function() { + rails.enableElement($(this)); + }); + + $(document).delegate(rails.linkClickSelector, 'click.rails', function(e) { + var link = $(this), method = link.data('method'), data = link.data('params'); + if (!rails.allowAction(link)) return rails.stopEverything(e); + + if (link.is(rails.linkDisableSelector)) rails.disableElement(link); + + if (link.data('remote') !== undefined) { + if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; } + + var handleRemote = rails.handleRemote(link); + // response from rails.handleRemote() will either be false or a deferred object promise. + if (handleRemote === false) { + rails.enableElement(link); + } else { + handleRemote.error( function() { rails.enableElement(link); } ); + } + return false; + + } else if (link.data('method')) { + rails.handleMethod(link); + return false; + } + }); + + $(document).delegate(rails.buttonClickSelector, 'click.rails', function(e) { + var button = $(this); + if (!rails.allowAction(button)) return rails.stopEverything(e); + + rails.handleRemote(button); + return false; + }); + + $(document).delegate(rails.inputChangeSelector, 'change.rails', function(e) { + var link = $(this); + if (!rails.allowAction(link)) return rails.stopEverything(e); + + rails.handleRemote(link); + return false; + }); + + $(document).delegate(rails.formSubmitSelector, 'submit.rails', function(e) { + var form = $(this), + remote = form.data('remote') !== undefined, + blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector), + nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); + + if (!rails.allowAction(form)) return rails.stopEverything(e); + + // skip other logic when required values are missing or file upload is present + if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { + return rails.stopEverything(e); + } + + if (remote) { + if (nonBlankFileInputs) { + // slight timeout so that the submit button gets properly serialized + // (make it easy for event handler to serialize form without disabled values) + setTimeout(function(){ rails.disableFormElements(form); }, 13); + var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); + + // re-enable form elements if event bindings return false (canceling normal form submission) + if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); } + + return aborted; + } + + rails.handleRemote(form); + return false; + + } else { + // slight timeout so that the submit button gets properly serialized + setTimeout(function(){ rails.disableFormElements(form); }, 13); + } + }); + + $(document).delegate(rails.formInputClickSelector, 'click.rails', function(event) { + var button = $(this); + + if (!rails.allowAction(button)) return rails.stopEverything(event); + + // register the pressed submit button + var name = button.attr('name'), + data = name ? {name:name, value:button.val()} : null; + + button.closest('form').data('ujs:submit-button', data); + }); + + $(document).delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) { + if (this == event.target) rails.disableFormElements($(this)); + }); + + $(document).delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) { + if (this == event.target) rails.enableFormElements($(this)); + }); + + $(function(){ + // making sure that all forms have actual up-to-date token(cached forms contain old one) + var csrf_token = $('meta[name=csrf-token]').attr('content'); + var csrf_param = $('meta[name=csrf-param]').attr('content'); + $('form input[name="' + csrf_param + '"]').val(csrf_token); + }); + } + +})( jQuery ); diff --git a/app/assets/javascripts/profile-photos.js b/app/assets/javascripts/profile-photos.js new file mode 100644 index 000000000..c7ebe894d --- /dev/null +++ b/app/assets/javascripts/profile-photos.js @@ -0,0 +1,3 @@ +// ... +//= require jquery.Jcrop +//= require profile_photo diff --git a/app/assets/javascripts/profile_photo.js b/app/assets/javascripts/profile_photo.js new file mode 100644 index 000000000..6d637b439 --- /dev/null +++ b/app/assets/javascripts/profile_photo.js @@ -0,0 +1,49 @@ +// Remember to invoke within jQuery(window).load(...) +// If you don't, Jcrop may not initialize properly +jQuery(window).load(function(){ + // The size of the initial selection (largest, centreted rectangle) + var w = jQuery('#profile_photo_cropbox').width(); + var h = jQuery('#profile_photo_cropbox').height(); + var t = 0; + var l = 0; + var initial; + if (h < w) { + initial = h; + l = (w - initial) / 2; + } else { + initial = w; + t = (h - initial) / 2; + } + + jQuery('#profile_photo_cropbox').Jcrop({ + onChange: showPreview, + onSelect: showPreview, + aspectRatio: 1, + setSelect: [ l, t, initial, initial ] + }); + +}); + +// Our simple event handler, called from onChange and onSelect +// event handlers, as per the Jcrop invocation above +function showPreview(coords) +{ + if (parseInt(coords.w) > 0) + { + var rx = 100 / coords.w; + var ry = 100 / coords.h; + + jQuery('#profile_photo_preview').css({ + width: Math.round(rx * jQuery('#profile_photo_cropbox').width()) + 'px', + height: Math.round(ry * jQuery('#profile_photo_cropbox').height()) + 'px', + marginLeft: '-' + Math.round(rx * coords.x) + 'px', + marginTop: '-' + Math.round(ry * coords.y) + 'px' + }); + + $('#x').val(coords.x); + $('#y').val(coords.y); + $('#w').val(coords.w); + $('#h').val(coords.h); + } +} + diff --git a/app/assets/javascripts/stats-graphs.js b/app/assets/javascripts/stats-graphs.js new file mode 100644 index 000000000..73e19a6fc --- /dev/null +++ b/app/assets/javascripts/stats-graphs.js @@ -0,0 +1,87 @@ +/* From http://stackoverflow.com/a/10284006/223092 */ +function zip(arrays) { + return arrays[0].map(function(_,i){ + return arrays.map(function(array){return array[i]}) + }); +} + +$(document).ready(function() { + $.each(graphs_data, function(index, graph_data) { + var graph_id = graph_data.id, + dataset, + plot, + graph_data, + graph_div = $('#' + graph_id); + + if (!graph_data.x_values) { + /* Then there's no data for this graph */ + return true; + } + + graph_div.css('width', '700px'); + graph_div.css('height', '400px'); + + dataset = [ + {'color': 'orange', + 'bars': { + 'show': true, + 'barWidth': 0.5, + 'align': 'center' + }, + 'data': zip([graph_data.x_values, + graph_data.y_values]) + } + ] + + if (graph_data.errorbars) { + dataset.push({ + 'color': 'orange', + 'points': { + // Don't show these, just draw error bars: + 'radius': 0, + 'errorbars': 'y', + 'yerr': { + 'asymmetric': true, + 'show': true, + 'upperCap': "-", + 'lowerCap': "-", + 'radius': 5 + } + }, + 'data': zip([graph_data.x_values, + graph_data.y_values, + graph_data.cis_below, + graph_data.cis_above]) + }); + } + + options = { + 'xaxis': { + 'ticks': graph_data.x_ticks, + }, + 'yaxis': { + 'min': 0, + 'max': graph_data.y_max + }, + 'xaxes': [{ + 'axisLabel': graph_data.x_axis, + 'axisLabelPadding': 20, + 'axisLabelColour': 'black' + }], + 'yaxes': [{ + 'axisLabel': graph_data.y_axis, + 'axisLabelPadding': 20, + 'axisLabelColour': 'black' + }], + 'series': { + 'lines': { + 'show': false + } + }, + } + + plot = $.plot(graph_div, + dataset, + options); + }); +}); diff --git a/app/assets/javascripts/stats.js b/app/assets/javascripts/stats.js new file mode 100644 index 000000000..cafea336a --- /dev/null +++ b/app/assets/javascripts/stats.js @@ -0,0 +1,5 @@ +// ... +//= require jquery.flot.min +//= require jquery.flot.errorbars.min +//= require jquery.flot.axislabels +//= require stats-graphs diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 1648e6af3..857d9f65e 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -17,10 +17,9 @@ <%= stylesheet_link_tag "/admin/stylesheets/admin", :title => "Main", :rel => "stylesheet" %> <% end %> - <%= javascript_include_tag 'jquery.js', 'jquery-ui.min','jquery.cookie.js', 'general.js' %> + <%= javascript_include_tag "application" %> <% if @profile_photo_javascript %> - - + <%= javascript_include_tag "profile-photos" %> <% end %> diff --git a/app/views/layouts/no_chrome.html.erb b/app/views/layouts/no_chrome.html.erb index d7918cffc..090ee409b 100644 --- a/app/views/layouts/no_chrome.html.erb +++ b/app/views/layouts/no_chrome.html.erb @@ -10,7 +10,7 @@ <% end %> - + <%= javascript_include_tag "application" %> <%= stylesheet_link_tag 'main', :title => "Main", :rel => "stylesheet" %> <%= stylesheet_link_tag 'fonts', :rel => "stylesheet" %> diff --git a/app/views/public_body/statistics.html.erb b/app/views/public_body/statistics.html.erb index 840af0c10..8e887bc1b 100644 --- a/app/views/public_body/statistics.html.erb +++ b/app/views/public_body/statistics.html.erb @@ -69,7 +69,7 @@ are due to him.") %>

      - -<%= javascript_include_tag 'jquery.flot.min.js', 'jquery.flot.errorbars.min.js', 'jquery.flot.axislabels.js', 'stats-graphs.js' %> + +<%= javascript_include_tag "stats" %>
      diff --git a/app/views/request/new.html.erb b/app/views/request/new.html.erb index f8b97ffe3..849a94216 100644 --- a/app/views/request/new.html.erb +++ b/app/views/request/new.html.erb @@ -1,4 +1,3 @@ - - + <%= javascript_include_tag "profile-photos" %> <% end %> diff --git a/app/views/layouts/no_chrome.html.erb b/app/views/layouts/no_chrome.html.erb index d7918cffc..090ee409b 100644 --- a/app/views/layouts/no_chrome.html.erb +++ b/app/views/layouts/no_chrome.html.erb @@ -10,7 +10,7 @@ <% end %> - + <%= javascript_include_tag "application" %> <%= stylesheet_link_tag 'main', :title => "Main", :rel => "stylesheet" %> <%= stylesheet_link_tag 'fonts', :rel => "stylesheet" %> diff --git a/app/views/public_body/statistics.html.erb b/app/views/public_body/statistics.html.erb index 6ea253260..223511065 100644 --- a/app/views/public_body/statistics.html.erb +++ b/app/views/public_body/statistics.html.erb @@ -69,7 +69,7 @@ are due to him.") %>

      - -<%= javascript_include_tag 'jquery.flot.min.js', 'jquery.flot.errorbars.min.js', 'jquery.flot.tickrotor.min.js', 'jquery.flot.axislabels.min.js', 'stats-graphs.js' %> + +<%= javascript_include_tag "stats" %> diff --git a/app/views/request/new.html.erb b/app/views/request/new.html.erb index f8b97ffe3..849a94216 100644 --- a/app/views/request/new.html.erb +++ b/app/views/request/new.html.erb @@ -1,4 +1,3 @@ - - + <%= javascript_include_tag "stats" %> diff --git a/config/application.rb b/config/application.rb index dd920c3c0..426db2318 100644 --- a/config/application.rb +++ b/config/application.rb @@ -90,6 +90,7 @@ module Alaveteli # grouped: config.assets.precompile += ['jquery.fancybox-1.3.4.pack.js', 'jquery-ui-1.8.15.custom.css', + 'jquery.Jcrop.css', 'excanvas.min.js', 'fonts.css', 'main.css', -- cgit v1.2.3 From 3b86cb6129140fc123dc3aeffcccdb5652f19085 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 22 Nov 2013 11:32:48 +0000 Subject: Add byebug, for interactive debugging under Ruby 2 --- Gemfile | 1 + Gemfile.lock | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Gemfile b/Gemfile index 5467eaa4d..6f5043710 100644 --- a/Gemfile +++ b/Gemfile @@ -66,6 +66,7 @@ end group :develop do gem 'ruby-debug', :platforms => :ruby_18 gem 'debugger', :platforms => :ruby_19 + gem 'byebug', :platforms => :ruby_20 gem 'bootstrap-sass' gem 'compass' gem 'annotate' diff --git a/Gemfile.lock b/Gemfile.lock index 36f7a1a3e..d38ac52b6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,6 +52,9 @@ GEM bootstrap-sass (2.3.1.2) sass (~> 3.2) builder (3.0.4) + byebug (2.3.1) + columnize (~> 0.3.6) + debugger-linecache (~> 1.2.0) capistrano (2.15.4) highline net-scp (>= 1.0.0) @@ -254,6 +257,7 @@ DEPENDENCIES acts_as_versioned! annotate bootstrap-sass + byebug capistrano charlock_holmes compass -- cgit v1.2.3 From ea8b8e7cdae9d2a2596df0a643ea4921e17b628f Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 22 Nov 2013 13:27:15 +0000 Subject: Fix quietly_try_to_purge on Ruby 2.0 The tests of quietly_try_to_purge were failing on Ruby 2.0 due to the net-http-local gem not working with that Ruby version. When Net::HTTP::bind is called, it temporarily replaces the open method of TCPSocket with a version of open that only takes two parameters. The Ruby 1.9 version of net/http.rb calls TCPSocket.open with two parameters, but the Ruby 2.0 version calls it with 4, and so fails with a mismatched number of arguments error. In fact, net-http-local doesn't seem to be necessary with Ruby 2.0, where one can supply a :local_port argument to Net::HTTP.start, so this commit patches lib/quiet_opener.rb to use that approach with Ruby >= 2.0, and net-http-local on earlier versions. --- Gemfile | 2 +- lib/quiet_opener.rb | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 6f5043710..9545f1ae1 100644 --- a/Gemfile +++ b/Gemfile @@ -20,7 +20,7 @@ gem 'fastercsv', '>=1.5.5' gem 'jquery-rails', '~> 2.1' gem 'json' gem 'mahoro' -gem 'net-http-local' +gem 'net-http-local', :platforms => [:ruby_18, :ruby_19] gem 'net-purge' gem 'newrelic_rpm' gem 'rack' diff --git a/lib/quiet_opener.rb b/lib/quiet_opener.rb index ae6605c43..16ea27b8e 100644 --- a/lib/quiet_opener.rb +++ b/lib/quiet_opener.rb @@ -1,6 +1,8 @@ require 'open-uri' require 'net-purge' -require 'net/http/local' +if RUBY_VERSION.to_f < 2.0 + require 'net/http/local' +end def quietly_try_to_open(url) begin @@ -12,17 +14,36 @@ def quietly_try_to_open(url) return result end +# On Ruby versions before 2.0, we need to use the net-http-local gem +# to force the use of 127.0.0.1 as the local interface for the +# connection. However, at the time of writing this gem doesn't work +# on Ruby 2.0 and it's not necessary with that Ruby version - one can +# supply a :local_host option to Net::HTTP:start. So, this helper +# function is to abstract away that difference, and can be used as you +# would Net::HTTP.start(host) when passed a block. +def http_from_localhost(host) + if RUBY_VERSION.to_f >= 2.0 + Net::HTTP.start(host, :local_host => '127.0.0.1') do |http| + yield http + end + else + Net::HTTP.bind '127.0.0.1' do + Net::HTTP.start(host) do |http| + yield http + end + end + end +end + def quietly_try_to_purge(host, url) begin result = "" result_body = "" - Net::HTTP.bind '127.0.0.1' do - Net::HTTP.start(host) {|http| - request = Net::HTTP::Purge.new(url) - response = http.request(request) - result = response.code - result_body = response.body - } + http_from_localhost(host) do |http| + request = Net::HTTP::Purge.new(url) + response = http.request(request) + result = response.code + result_body = response.body end rescue OpenURI::HTTPError, SocketError, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET, Errno::ENETUNREACH Rails.logger.warn("PURGE: Unable to reach host #{host}") -- cgit v1.2.3 From 2ab06caa9a713322728e9e08d5472ea084baae85 Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Fri, 22 Nov 2013 22:10:14 -0200 Subject: Don't HTML escape subject lines --- app/mailers/request_mailer.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/mailers/request_mailer.rb b/app/mailers/request_mailer.rb index 13b3bc4a1..c8a19afa8 100644 --- a/app/mailers/request_mailer.rb +++ b/app/mailers/request_mailer.rb @@ -63,7 +63,7 @@ class RequestMailer < ApplicationMailer mail(:from => user.name_and_email, :to => contact_from_name_and_email, - :subject => _("FOI response requires admin ({{reason}}) - {{title}}", :reason => info_request.described_state, :title => info_request.title)) + :subject => _("FOI response requires admin ({{reason}}) - {{title}}", :reason => info_request.described_state, :title => info_request.title).html_safe) end # Tell the requester that a new response has arrived @@ -79,7 +79,7 @@ class RequestMailer < ApplicationMailer mail(:from => contact_from_name_and_email, :to => info_request.user.name_and_email, - :subject => _("New response to your FOI request - ") + info_request.title, + :subject => (_("New response to your FOI request - ") + info_request.title).html_safe, :charset => "UTF-8", # not much we can do if the user's email is broken :reply_to => contact_from_name_and_email) @@ -182,7 +182,7 @@ class RequestMailer < ApplicationMailer mail(:from => contact_from_name_and_email, :to => info_request.user.name_and_email, - :subject => _("Clarify your FOI request - ") + info_request.title) + :subject => (_("Clarify your FOI request - ") + info_request.title).html_safe) end # Tell requester that somebody add an annotation to their request -- cgit v1.2.3 From ffa22a2d2586c09ac502a4993abf8d74fcb5aa0a Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 21 Nov 2013 12:38:07 +0000 Subject: Move getting similar requests to the InfoRequest model. --- app/controllers/request_controller.rb | 9 --------- app/models/info_request.rb | 15 +++++++++++++++ app/views/request/_sidebar.html.erb | 7 ++++--- spec/models/info_request_spec.rb | 21 +++++++++++++++++++-- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index 388473b51..341ecdcd5 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -92,15 +92,6 @@ class RequestController < ApplicationController # Sidebar stuff @sidebar = true - # ... requests that have similar imporant terms - begin - limit = 10 - @xapian_similar = ActsAsXapian::Similar.new([InfoRequestEvent], @info_request.info_request_events, - :limit => limit, :collapse_by_prefix => 'request_collapse') - @xapian_similar_more = (@xapian_similar.matches_estimated > limit) - rescue - @xapian_similar = nil - end # Track corresponding to this page @track_thing = TrackThing.create_track_for_request(@info_request) @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ] diff --git a/app/models/info_request.rb b/app/models/info_request.rb index 9463a236e..4b76269e3 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -1212,6 +1212,21 @@ public end end + + # Get requests that have similar important terms + def similar_requests(limit=10) + xapian_similar = nil + xapian_similar_more = false + begin + xapian_similar = ActsAsXapian::Similar.new([InfoRequestEvent], + info_request_events, + :limit => limit, + :collapse_by_prefix => 'request_collapse') + xapian_similar_more = (xapian_similar.matches_estimated > limit) + rescue + end + return [xapian_similar, xapian_similar_more] + end private def set_defaults diff --git a/app/views/request/_sidebar.html.erb b/app/views/request/_sidebar.html.erb index 8d4a4a2d8..2659a7989 100644 --- a/app/views/request/_sidebar.html.erb +++ b/app/views/request/_sidebar.html.erb @@ -52,12 +52,13 @@ <%= render :partial => 'request/next_actions' %> <% # TODO: Cache for 1 day %> - <% if !@xapian_similar.nil? && @xapian_similar.results.size > 0 %> + <% xapian_similar, xapian_similar_more = @info_request.similar_requests %> + <% if !xapian_similar.nil? && xapian_similar.results.size > 0 %>

      <%= _('Similar requests')%>

      - <% for result in @xapian_similar.results %> + <% for result in xapian_similar.results %> <%= render :partial => 'request/request_listing_short_via_event', :locals => { :event => result[:model], :info_request => result[:model].info_request } %> <% end %> - <% if @xapian_similar_more %> + <% if xapian_similar_more %>

      <%= link_to _("More similar requests"), similar_request_path(@info_request.url_title) %>

      <% end %> <% end %> diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb index 64ad1972e..ed7c55bb8 100644 --- a/spec/models/info_request_spec.rb +++ b/spec/models/info_request_spec.rb @@ -27,7 +27,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe InfoRequest do - describe 'when validating', :focus => true do + describe 'when validating' do it 'should accept a summary with ascii characters' do info_request = InfoRequest.new(:title => 'abcde') @@ -1030,7 +1030,7 @@ describe InfoRequest do end end - context "another series of events on a request", :focus => true do + context "another series of events on a request" do it "should have sensible event states" do # An initial request is sent request.log_event('sent', {}) @@ -1122,5 +1122,22 @@ describe InfoRequest do end + describe InfoRequest, 'when getting similar requests' do + + before(:each) do + get_fixtures_xapian_index + end + + it 'should return similar requests' do + similar, more = info_requests(:spam_1_request).similar_requests(1) + similar.results.first[:model].info_request.should == info_requests(:spam_2_request) + end + + it 'should return a flag set to true' do + similar, more = info_requests(:spam_1_request).similar_requests(1) + more.should be_true + end + + end end -- cgit v1.2.3 From b54aef5dda20fc9ff15a0ef3dd72327e52c8604c Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 21 Nov 2013 14:53:11 +0000 Subject: Cache the similar requests for 1 day --- app/controllers/request_controller.rb | 7 +++++++ app/views/request/_sidebar.html.erb | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index 341ecdcd5..841950cd5 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -92,6 +92,8 @@ class RequestController < ApplicationController # Sidebar stuff @sidebar = true + @similar_cache_key = cache_key_for_similar_requests(@info_request, @locale) + # Track corresponding to this page @track_thing = TrackThing.create_track_for_request(@info_request) @feed_autodetect = [ { :url => do_track_url(@track_thing, 'feed'), :title => @track_thing.params[:title_in_rss], :has_json => true } ] @@ -962,5 +964,10 @@ class RequestController < ApplicationController file_info end + def cache_key_for_similar_requests(info_request, locale) + "request/similar/#{info_request.id}/#{locale}" + end + + end diff --git a/app/views/request/_sidebar.html.erb b/app/views/request/_sidebar.html.erb index 2659a7989..71405ae06 100644 --- a/app/views/request/_sidebar.html.erb +++ b/app/views/request/_sidebar.html.erb @@ -51,7 +51,7 @@ <%= render :partial => 'request/next_actions' %> - <% # TODO: Cache for 1 day %> + <% cache(@similar_cache_key, :expires_in => 1.day) do %> <% xapian_similar, xapian_similar_more = @info_request.similar_requests %> <% if !xapian_similar.nil? && xapian_similar.results.size > 0 %>

      <%= _('Similar requests')%>

      @@ -62,6 +62,7 @@

      <%= link_to _("More similar requests"), similar_request_path(@info_request.url_title) %>

      <% end %> <% end %> + <% end %>

      <%= link_to _('Event history details'), request_details_path(@info_request) %>

      -- cgit v1.2.3 From 70c5bb9845dc2438eef2b76fead0e76d21744a3b Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 21 Nov 2013 15:13:14 +0000 Subject: Move getting popular bodies into a model method. --- app/controllers/general_controller.rb | 21 --------------------- app/models/public_body.rb | 23 +++++++++++++++++++++++ app/views/general/_frontpage_bodies_list.html.erb | 5 +++-- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb index beefef4e6..e138ec120 100644 --- a/app/controllers/general_controller.rb +++ b/app/controllers/general_controller.rb @@ -12,28 +12,7 @@ class GeneralController < ApplicationController # New, improved front page! def frontpage medium_cache - # get some example searches and public bodies to display - # either from config, or based on a (slow!) query if not set - body_short_names = AlaveteliConfiguration::frontpage_publicbody_examples.split(/\s*;\s*/).map{|s| "'%s'" % s.gsub(/'/, "''") }.join(", ") @locale = self.locale_from_params() - locale_condition = 'public_body_translations.locale = ?' - conditions = [locale_condition, @locale] - I18n.with_locale(@locale) do - if body_short_names.empty? - # This is too slow - @popular_bodies = PublicBody.visible.find(:all, - :order => "info_requests_count desc", - :limit => 32, - :conditions => conditions, - :joins => :translations - ) - else - conditions[0] += " and public_bodies.url_name in (" + body_short_names + ")" - @popular_bodies = PublicBody.find(:all, - :conditions => conditions, - :joins => :translations) - end - end # Get some successful requests begin query = 'variety:response (status:successful OR status:partially_successful)' diff --git a/app/models/public_body.rb b/app/models/public_body.rb index db6359f6b..4b19ec95e 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -723,6 +723,29 @@ class PublicBody < ActiveRecord::Base 'y_max' => 100, 'totals' => original_totals} end + def self.popular_bodies(locale) + # get some example searches and public bodies to display + # either from config, or based on a (slow!) query if not set + body_short_names = AlaveteliConfiguration::frontpage_publicbody_examples.split(/\s*;\s*/).map{|s| "'%s'" % s.gsub(/'/, "''") }.join(", ") + locale_condition = 'public_body_translations.locale = ?' + conditions = [locale_condition, locale] + bodies = [] + I18n.with_locale(locale) do + if body_short_names.empty? + # This is too slow + bodies = visible.find(:all, + :order => "info_requests_count desc", + :limit => 32, + :conditions => conditions, + :joins => :translations + ) + else + conditions[0] += " and public_bodies.url_name in (" + body_short_names + ")" + bodies = find(:all, :conditions => conditions, :joins => :translations) + end + end + return bodies + end private diff --git a/app/views/general/_frontpage_bodies_list.html.erb b/app/views/general/_frontpage_bodies_list.html.erb index 75daea41d..44321f14a 100644 --- a/app/views/general/_frontpage_bodies_list.html.erb +++ b/app/views/general/_frontpage_bodies_list.html.erb @@ -1,10 +1,11 @@ -<% if @popular_bodies.size > 0 %> +<%- popular_bodies = PublicBody.popular_bodies(@locale) %> +<% if popular_bodies.size > 0 %>

      <%= _("Who can I request information from?") %>

      <%= _("{{site_name}} covers requests to {{number_of_authorities}} authorities, including:", :site_name => site_name, :number_of_authorities => PublicBody.visible.count) %>
        - <% for popular_body in @popular_bodies %> + <% for popular_body in popular_bodies %>
      • <%=public_body_link(popular_body)%> <%= n_('{{count}} request', '{{count}} requests', popular_body.info_requests_count, :count => popular_body.info_requests_count) %>
      • -- cgit v1.2.3 From 040793f9e29810d35bb27759b5946d594daa83fa Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 25 Nov 2013 16:30:58 +0000 Subject: Use built-in SQL quoting. --- app/models/public_body.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 4b19ec95e..d2cfc4b8b 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -726,7 +726,7 @@ class PublicBody < ActiveRecord::Base def self.popular_bodies(locale) # get some example searches and public bodies to display # either from config, or based on a (slow!) query if not set - body_short_names = AlaveteliConfiguration::frontpage_publicbody_examples.split(/\s*;\s*/).map{|s| "'%s'" % s.gsub(/'/, "''") }.join(", ") + body_short_names = AlaveteliConfiguration::frontpage_publicbody_examples.split(/\s*;\s*/) locale_condition = 'public_body_translations.locale = ?' conditions = [locale_condition, locale] bodies = [] @@ -740,7 +740,8 @@ class PublicBody < ActiveRecord::Base :joins => :translations ) else - conditions[0] += " and public_bodies.url_name in (" + body_short_names + ")" + conditions[0] += " and public_bodies.url_name in (?)" + conditions << body_short_names bodies = find(:all, :conditions => conditions, :joins => :translations) end end -- cgit v1.2.3 From 9750dd3f14ea2be22b3e4a8c8c15d90625a9e5fb Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 21 Nov 2013 15:16:07 +0000 Subject: Move getting recent requests into a helper method. --- app/controllers/general_controller.rb | 24 ----------- app/models/info_request.rb | 48 ++++++++++++++++++++++ .../general/_frontpage_requests_list.html.erb | 1 + spec/controllers/general_controller_spec.rb | 42 ------------------- spec/models/info_request_spec.rb | 37 +++++++++++++++++ 5 files changed, 86 insertions(+), 66 deletions(-) diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb index e138ec120..aac078829 100644 --- a/app/controllers/general_controller.rb +++ b/app/controllers/general_controller.rb @@ -13,30 +13,6 @@ class GeneralController < ApplicationController def frontpage medium_cache @locale = self.locale_from_params() - # Get some successful requests - begin - query = 'variety:response (status:successful OR status:partially_successful)' - sortby = "newest" - max_count = 5 - xapian_object = perform_search([InfoRequestEvent], query, sortby, 'request_title_collapse', max_count) - @request_events = xapian_object.results.map { |r| r[:model] } - - # If there are not yet enough successful requests, fill out the list with - # other requests - if @request_events.count < max_count - @request_events_all_successful = false - query = 'variety:sent' - xapian_object = perform_search([InfoRequestEvent], query, sortby, 'request_title_collapse', max_count-@request_events.count) - more_events = xapian_object.results.map { |r| r[:model] } - @request_events += more_events - # Overall we still want the list sorted with the newest first - @request_events.sort!{|e1,e2| e2.created_at <=> e1.created_at} - else - @request_events_all_successful = true - end - rescue - @request_events = [] - end end # Display blog entries diff --git a/app/models/info_request.rb b/app/models/info_request.rb index 4b76269e3..0a073dc79 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -1227,6 +1227,54 @@ public end return [xapian_similar, xapian_similar_more] end + + def InfoRequest.recent_requests + request_events = [] + request_events_all_successful = false + # Get some successful requests + begin + query = 'variety:response (status:successful OR status:partially_successful)' + sortby = "newest" + max_count = 5 + + xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], + query, + :offset => 0, + :limit => 5, + :sort_by_prefix => 'created_at', + :sort_by_ascending => true, + :collapse_by_prefix => 'request_title_collapse' + ) + xapian_object.results + request_events = xapian_object.results.map { |r| r[:model] } + + # If there are not yet enough successful requests, fill out the list with + # other requests + if request_events.count < max_count + query = 'variety:sent' + xapian_object = ActsAsXapian::Search.new([InfoRequestEvent], + query, + :offset => 0, + :limit => max_count-request_events.count, + :sort_by_prefix => 'created_at', + :sort_by_ascending => true, + :collapse_by_prefix => 'request_title_collapse' + ) + xapian_object.results + more_events = xapian_object.results.map { |r| r[:model] } + request_events += more_events + # Overall we still want the list sorted with the newest first + request_events.sort!{|e1,e2| e2.created_at <=> e1.created_at} + else + request_events_all_successful = true + end + rescue + request_events = [] + end + + return [request_events, request_events_all_successful] + end + private def set_defaults diff --git a/app/views/general/_frontpage_requests_list.html.erb b/app/views/general/_frontpage_requests_list.html.erb index fa498dfa7..41a875cab 100644 --- a/app/views/general/_frontpage_requests_list.html.erb +++ b/app/views/general/_frontpage_requests_list.html.erb @@ -1,3 +1,4 @@ +<%- @request_events, @request_events_all_successful = InfoRequest.recent_requests %>

        <% if @request_events_all_successful %> diff --git a/spec/controllers/general_controller_spec.rb b/spec/controllers/general_controller_spec.rb index 116dbe07a..e67cc9492 100644 --- a/spec/controllers/general_controller_spec.rb +++ b/spec/controllers/general_controller_spec.rb @@ -116,49 +116,7 @@ describe GeneralController, "when showing the frontpage" do end end -describe GeneralController, "when showing the front page with fixture data" do - describe 'when constructing the list of recent requests' do - - before(:each) do - get_fixtures_xapian_index - end - - describe 'when there are fewer than five successful requests' do - - it 'should list the most recently sent and successful requests by the creation date of the - request event' do - # Make sure the newest response is listed first even if a request - # with an older response has a newer comment or was reclassified more recently: - # https://github.com/mysociety/alaveteli/issues/370 - # - # This is a deliberate behaviour change, in that the - # previous behaviour (showing more-recently-reclassified - # requests first) was intentional. - get :frontpage - - request_events = assigns[:request_events] - previous = nil - request_events.each do |event| - if previous - previous.created_at.should be >= event.created_at - end - ['sent', 'response'].include?(event.event_type).should be_true - if event.event_type == 'response' - ['successful', 'partially_successful'].include?(event.calculated_state).should be_true - end - previous = event - end - end - end - - it 'should coalesce duplicate requests' do - get :frontpage - assigns[:request_events].map(&:info_request).select{|x|x.url_title =~ /^spam/}.length.should == 1 - end - end - -end describe GeneralController, 'when using xapian search' do diff --git a/spec/models/info_request_spec.rb b/spec/models/info_request_spec.rb index ed7c55bb8..dcc94e967 100644 --- a/spec/models/info_request_spec.rb +++ b/spec/models/info_request_spec.rb @@ -1140,4 +1140,41 @@ describe InfoRequest do end + describe InfoRequest, 'when constructing the list of recent requests' do + + before(:each) do + get_fixtures_xapian_index + end + + describe 'when there are fewer than five successful requests' do + + it 'should list the most recently sent and successful requests by the creation date of the + request event' do + # Make sure the newest response is listed first even if a request + # with an older response has a newer comment or was reclassified more recently: + # https://github.com/mysociety/alaveteli/issues/370 + # + # This is a deliberate behaviour change, in that the + # previous behaviour (showing more-recently-reclassified + # requests first) was intentional. + request_events, request_events_all_successful = InfoRequest.recent_requests + previous = nil + request_events.each do |event| + if previous + previous.created_at.should be >= event.created_at + end + ['sent', 'response'].include?(event.event_type).should be_true + if event.event_type == 'response' + ['successful', 'partially_successful'].include?(event.calculated_state).should be_true + end + previous = event + end + end + end + + it 'should coalesce duplicate requests' do + request_events, request_events_all_successful = InfoRequest.recent_requests + request_events.map(&:info_request).select{|x|x.url_title =~ /^spam/}.length.should == 1 + end + end end -- cgit v1.2.3 From fe436d8bb70125b9ad3d9d194fe9b569b9a42bd4 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 19 Nov 2013 18:13:01 +0000 Subject: Restore caching for five minutes to frontpage. --- app/views/general/frontpage.html.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/general/frontpage.html.erb b/app/views/general/frontpage.html.erb index bf5261d15..2900782ef 100644 --- a/app/views/general/frontpage.html.erb +++ b/app/views/general/frontpage.html.erb @@ -1,4 +1,4 @@ -<% # TODO: Cache for 5 minutes %> +<% cache("frontpage-#{@locale}", :expires_in => 5.minutes) do %>
        <%= render :partial => "frontpage_new_request" %> @@ -17,3 +17,4 @@ <%= render :partial => "frontpage_bodies_list" %> <%= render :partial => "frontpage_requests_list" %>
        +<% end %> -- cgit v1.2.3 From cdc0c7d342d99d2c5dc52747b1bf1111028be9d4 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 21 Nov 2013 15:16:56 +0000 Subject: Add a rake task to analyse the distribution of hits to requests. Also removes old temp tasks. --- lib/tasks/temp.rake | 316 ++++++---------------------------------------------- 1 file changed, 32 insertions(+), 284 deletions(-) diff --git a/lib/tasks/temp.rake b/lib/tasks/temp.rake index d371ad0dc..bdee3027b 100644 --- a/lib/tasks/temp.rake +++ b/lib/tasks/temp.rake @@ -1,292 +1,40 @@ namespace :temp do - desc "Fix the history of requests where the described state doesn't match the latest status value - used by search, by adding an edit event that will correct the latest status" - task :fix_bad_request_states => :environment do - dryrun = ENV['DRYRUN'] != '0' - if dryrun - puts "This is a dryrun" - end - - InfoRequest.find_each() do |info_request| - next if info_request.url_title == 'holding_pen' - last_info_request_event = info_request.info_request_events[-1] - if last_info_request_event.latest_status != info_request.described_state - puts "#{info_request.id} #{info_request.url_title} #{last_info_request_event.latest_status} #{info_request.described_state}" - params = { :script => 'rake temp:fix_bad_request_states', - :user_id => nil, - :old_described_state => info_request.described_state, - :described_state => info_request.described_state - } - if ! dryrun - info_request.info_request_events.create!(:last_described_at => last_info_request_event.described_at + 1.second, - :event_type => 'status_update', - :described_state => info_request.described_state, - :calculated_state => info_request.described_state, - :params => params) - info_request.info_request_events.each{ |event| event.xapian_mark_needs_index } - end - end - - end - end - - def disable_duplicate_account(user, count, dryrun) - dupe_email = "duplicateemail#{count}@example.com" - puts "Updating #{user.email} to #{dupe_email} for user #{user.id}" - user.email = dupe_email - user.save! unless dryrun - end - - desc "Re-extract any missing cached attachments" - task :reextract_missing_attachments, [:commit] => :environment do |t, args| - dry_run = args.commit.nil? || args.commit.empty? - total_messages = 0 - messages_to_reparse = 0 - IncomingMessage.find_each :include => :foi_attachments do |im| - begin - reparse = im.foi_attachments.any? { |fa| ! File.exists? fa.filepath } - total_messages += 1 - messages_to_reparse += 1 if reparse - if total_messages % 1000 == 0 - puts "Considered #{total_messages} received emails." - end - unless dry_run - im.parse_raw_email! true if reparse - sleep 2 - end - rescue StandardError => e - puts "There was a #{e.class} exception reparsing IncomingMessage with ID #{im.id}" - puts e.backtrace - puts e.message - end - end - message = dry_run ? "Would reparse" : "Reparsed" - message += " #{messages_to_reparse} out of #{total_messages} received emails." - puts message - end - - desc 'Cleanup accounts with a space in the email address' - task :clean_up_emails_with_spaces => :environment do - dryrun = ENV['DRYRUN'] == '0' ? false : true - if dryrun - puts "This is a dryrun" - end - count = 0 - User.find_each do |user| - if / /.match(user.email) - - email_without_spaces = user.email.gsub(' ', '') - existing = User.find_user_by_email(email_without_spaces) - # Another account exists with the canonical address - if existing - if user.info_requests.count == 0 and user.comments.count == 0 and user.track_things.count == 0 - count += 1 - disable_duplicate_account(user, count, dryrun) - elsif existing.info_requests.count == 0 and existing.comments.count == 0 and existing.track_things.count == 0 - count += 1 - disable_duplicate_account(existing, count, dryrun) - user.email = email_without_spaces - puts "Updating #{user.email} to #{email_without_spaces} for user #{user.id}" - user.save! unless dryrun - else - user.info_requests.each do |info_request| - info_request.user = existing - info_request.save! unless dryrun - puts "Moved request #{info_request.id} from user #{user.id} to #{existing.id}" - end - - user.comments.each do |comment| - comment.user = existing - comment.save! unless dryrun - puts "Moved comment #{comment.id} from user #{user.id} to #{existing.id}" - end - - user.track_things.each do |track_thing| - track_thing.tracking_user = existing - track_thing.save! unless dryrun - puts "Moved track thing #{track_thing.id} from user #{user.id} to #{existing.id}" - end - - TrackThingsSentEmail.find_each(:conditions => ['user_id = ?', user]) do |sent_email| - sent_email.user = existing - sent_email.save! unless dryrun - puts "Moved track thing sent email #{sent_email.id} from user #{user.id} to #{existing.id}" - - end - - user.censor_rules.each do |censor_rule| - censor_rule.user = existing - censor_rule.save! unless dryrun - puts "Moved censor rule #{censor_rule.id} from user #{user.id} to #{existing.id}" - end - - user.user_info_request_sent_alerts.each do |sent_alert| - sent_alert.user = existing - sent_alert.save! unless dryrun - puts "Moved sent alert #{sent_alert.id} from user #{user.id} to #{existing.id}" - end - - count += 1 - disable_duplicate_account(user, count, dryrun) - end - else - puts "Updating #{user.email} to #{email_without_spaces} for user #{user.id}" - user.email = email_without_spaces - user.save! unless dryrun - end - end - end - end - - desc 'Create a CSV file of a random selection of raw emails, for comparing hexdigests' - task :random_attachments_hexdigests => :environment do - # The idea is to run this under the Rail 2 codebase, where - # Tmail was used to extract the attachements, and the task - # will output all of those file paths in a CSV file, and a - # list of the raw email files in another. The latter file is - # useful so that one can easily tar up the emails with: - # - # tar cvz -T raw-email-files -f raw_emails.tar.gz - # - # Then you can switch to the Rails 3 codebase, where - # attachment parsing is done via - # recompute_attachments_hexdigests - - require 'csv' - - File.open('raw-email-files', 'w') do |f| - CSV.open('attachment-hexdigests.csv', 'w') do |csv| - csv << ['filepath', 'i', 'url_part_number', 'hexdigest'] - IncomingMessage.all(:order => 'RANDOM()', :limit => 1000).each do |incoming_message| - # raw_email.filepath fails unless the - # incoming_message has an associated request - next unless incoming_message.info_request - raw_email = incoming_message.raw_email - f.puts raw_email.filepath - incoming_message.foi_attachments.each_with_index do |attachment, i| - csv << [raw_email.filepath, i, attachment.url_part_number, attachment.hexdigest] - end - end - end - end - - end - - - desc 'Check the hexdigests of attachments in emails on disk' - task :recompute_attachments_hexdigests => :environment do - - require 'csv' - require 'digest/md5' - - OldAttachment = Struct.new :filename, :attachment_index, :url_part_number, :hexdigest - - filename_to_attachments = Hash.new {|h,k| h[k] = []} - - header_line = true - CSV.foreach('attachment-hexdigests.csv') do |filename, attachment_index, url_part_number, hexdigest| - if header_line - header_line = false - else - filename_to_attachments[filename].push OldAttachment.new filename, attachment_index, url_part_number, hexdigest - end + desc 'Analyse rails log specified by LOG_FILE to produce a list of request volume' + task :request_volume => :environment do + example = 'rake log_analysis:request_volume LOG_FILE=log/access_log OUTPUT_FILE=/tmp/log_analysis.csv' + check_for_env_vars(['LOG_FILE', 'OUTPUT_FILE'],example) + log_file_path = ENV['LOG_FILE'] + output_file_path = ENV['OUTPUT_FILE'] + is_gz = log_file_path.include?(".gz") + urls = Hash.new(0) + f = is_gz ? Zlib::GzipReader.open(log_file_path) : File.open(log_file_path, 'r') + processed = 0 + f.each_line do |line| + line.force_encoding('ASCII-8BIT') + if request_match = line.match(/^Started (?GET|OPTIONS|POST) "(?\/request\/.*?)"/) + next if line.match(/request\/\d+\/response/) + urls[request_match[:path]] += 1 + processed += 1 + end + end + url_counts = urls.to_a + num_requests_visited_n_times = Hash.new(0) + CSV.open(output_file_path, "wb") do |csv| + csv << ['URL', 'Number of visits'] + url_counts.sort_by(&:last).each do |url, count| + num_requests_visited_n_times[count] +=1 + csv << [url,"#{count}"] + end + csv << ['Number of visits', 'Number of URLs'] + num_requests_visited_n_times.to_a.sort.each do |number_of_times, number_of_requests| + csv << [number_of_times, number_of_requests] + end + csv << ['Total number of visits'] + csv << [processed] end - total_attachments = 0 - attachments_with_different_hexdigest = 0 - files_with_different_numbers_of_attachments = 0 - no_tnef_attachments = 0 - no_parts_in_multipart = 0 - - multipart_error = "no parts on multipart mail" - tnef_error = "tnef produced no attachments" - - # Now check each file: - filename_to_attachments.each do |filename, old_attachments| - - # Currently it doesn't seem to be possible to reuse the - # attachment parsing code in Alaveteli without saving - # objects to the database, so reproduce what it does: - - raw_email = nil - File.open(filename) do |f| - raw_email = f.read - end - mail = MailHandler.mail_from_raw_email(raw_email) - - begin - attachment_attributes = MailHandler.get_attachment_attributes(mail) - rescue IOError => e - if e.message == tnef_error - puts "#{filename} #{tnef_error}" - no_tnef_attachments += 1 - next - else - raise - end - rescue Exception => e - if e.message == multipart_error - puts "#{filename} #{multipart_error}" - no_parts_in_multipart += 1 - next - else - raise - end - end - - if attachment_attributes.length != old_attachments.length - puts "#{filename} the number of old attachments #{old_attachments.length} didn't match the number of new attachments #{attachment_attributes.length}" - files_with_different_numbers_of_attachments += 1 - else - old_attachments.each_with_index do |old_attachment, i| - total_attachments += 1 - attrs = attachment_attributes[i] - old_hexdigest = old_attachment.hexdigest - new_hexdigest = attrs[:hexdigest] - new_content_type = attrs[:content_type] - old_url_part_number = old_attachment.url_part_number.to_i - new_url_part_number = attrs[:url_part_number] - if old_url_part_number != new_url_part_number - puts "#{i} #{filename} old_url_part_number #{old_url_part_number}, new_url_part_number #{new_url_part_number}" - end - if old_hexdigest != new_hexdigest - body = attrs[:body] - # First, if the content type is one of - # text/plain, text/html or application/rtf try - # changing CRLF to LF and calculating a new - # digest - we generally don't worry about - # these changes: - new_converted_hexdigest = nil - if ["text/plain", "text/html", "application/rtf"].include? new_content_type - converted_body = body.gsub /\r\n/, "\n" - new_converted_hexdigest = Digest::MD5.hexdigest converted_body - puts "new_converted_hexdigest is #{new_converted_hexdigest}" - end - if (! new_converted_hexdigest) || (old_hexdigest != new_converted_hexdigest) - puts "#{i} #{filename} old_hexdigest #{old_hexdigest} wasn't the same as new_hexdigest #{new_hexdigest}" - puts " body was of length #{body.length}" - puts " content type was: #{new_content_type}" - path = "/tmp/#{new_hexdigest}" - f = File.new path, "w" - f.write body - f.close - puts " wrote body to #{path}" - attachments_with_different_hexdigest += 1 - end - end - end - end - - end - - puts "total_attachments: #{total_attachments}" - puts "attachments_with_different_hexdigest: #{attachments_with_different_hexdigest}" - puts "files_with_different_numbers_of_attachments: #{files_with_different_numbers_of_attachments}" - puts "no_tnef_attachments: #{no_tnef_attachments}" - puts "no_parts_in_multipart: #{no_parts_in_multipart}" - end end -- cgit v1.2.3 From 7b8978560a193e958fe601af1a32ab7a7f40fde9 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 19 Nov 2013 16:24:23 +0000 Subject: Set cache store to memcached, turn off rack-cache. The rack-cache gem version used by Rails (and turned on by default in 3.1) has a bug using memcached backend and the backend is unavailable. Note that we could just add the git version with the relevant pull request merged to the Gemfile if we wanted to keep rake-cache on, but it seems like Varnish should really be doing the job of whole page caching better for us. --- Gemfile | 1 + Gemfile.lock | 2 ++ config/application.rb | 4 ++++ config/packages | 1 + 4 files changed, 8 insertions(+) diff --git a/Gemfile b/Gemfile index 186251de4..5cb705a07 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,7 @@ gem 'fastercsv', '>=1.5.5' gem 'jquery-rails', '~> 2.1' gem 'json' gem 'mahoro' +gem 'memcache-client' gem 'net-http-local' gem 'net-purge' gem 'newrelic_rpm' diff --git a/Gemfile.lock b/Gemfile.lock index 989920a72..53f77e1e3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -125,6 +125,7 @@ GEM skinny (~> 0.2.3) sqlite3 (~> 1.3) thin (~> 1.5.0) + memcache-client (1.8.5) mime-types (1.23) multi_json (1.7.4) net-http-local (0.1.2) @@ -273,6 +274,7 @@ DEPENDENCIES locale mahoro mailcatcher + memcache-client net-http-local net-purge newrelic_rpm diff --git a/config/application.rb b/config/application.rb index ad5d4b03f..245a60782 100644 --- a/config/application.rb +++ b/config/application.rb @@ -55,6 +55,10 @@ module Alaveteli # will be in this time zone config.time_zone = ::AlaveteliConfiguration::time_zone + # Set the cache to use a memcached backend + config.cache_store = :mem_cache_store, { :namespace => AlaveteliConfiguration::domain } + config.action_dispatch.rack_cache = nil + config.after_initialize do |app| require 'routing_filters.rb' # Add a catch-all route to force routing errors to be handled by the application, diff --git a/config/packages b/config/packages index 8bb00a849..9a07c5f20 100644 --- a/config/packages +++ b/config/packages @@ -38,3 +38,4 @@ bundler sqlite3 libsqlite3-dev libicu-dev +memcached -- cgit v1.2.3 From d29ab6b1ef7b005b3dbd2e0f1c295e2022794267 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 21 Nov 2013 15:52:10 +0000 Subject: Make fragment caching dependent on a config variable. --- app/helpers/application_helper.rb | 7 +++++++ app/views/general/frontpage.html.erb | 2 +- app/views/request/_sidebar.html.erb | 2 +- config/general.yml-example | 5 +++++ lib/configuration.rb | 1 + 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e3b1e57ac..0c346ab4e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -116,5 +116,12 @@ module ApplicationHelper return !session[:using_admin].nil? || (!@user.nil? && @user.super?) end + def cache_if_caching_fragments(*args, &block) + if AlaveteliConfiguration::cache_fragments + cache(*args) { yield } + else + yield + end + end end diff --git a/app/views/general/frontpage.html.erb b/app/views/general/frontpage.html.erb index 2900782ef..8bb32bdf2 100644 --- a/app/views/general/frontpage.html.erb +++ b/app/views/general/frontpage.html.erb @@ -1,4 +1,4 @@ -<% cache("frontpage-#{@locale}", :expires_in => 5.minutes) do %> +<% cache_if_caching_fragments("frontpage-#{@locale}", :expires_in => 5.minutes) do %>
        <%= render :partial => "frontpage_new_request" %> diff --git a/app/views/request/_sidebar.html.erb b/app/views/request/_sidebar.html.erb index 71405ae06..8400cd6ac 100644 --- a/app/views/request/_sidebar.html.erb +++ b/app/views/request/_sidebar.html.erb @@ -51,7 +51,7 @@ <%= render :partial => 'request/next_actions' %> - <% cache(@similar_cache_key, :expires_in => 1.day) do %> + <% cache_if_caching_fragments(@similar_cache_key, :expires_in => 1.day) do %> <% xapian_similar, xapian_similar_more = @info_request.similar_requests %> <% if !xapian_similar.nil? && xapian_similar.results.size > 0 %>

        <%= _('Similar requests')%>

        diff --git a/config/general.yml-example b/config/general.yml-example index 60eb5ae1c..b8d9fc854 100644 --- a/config/general.yml-example +++ b/config/general.yml-example @@ -209,3 +209,8 @@ PUBLIC_BODY_LIST_FALLBACK_TO_DEFAULT_LOCALE: false # If true, while in development mode, try to send mail by SMTP to port # 1025 (the port the mailcatcher listens on by default): USE_MAILCATCHER_IN_DEVELOPMENT: true + +# Use memcached to cache HTML fragments for better performance. Will +# only have an effect in environments where +# config.action_controller.perform_caching is set to true +CACHE_FRAGMENTS: true diff --git a/lib/configuration.rb b/lib/configuration.rb index fba70f27c..2192433f7 100644 --- a/lib/configuration.rb +++ b/lib/configuration.rb @@ -21,6 +21,7 @@ module AlaveteliConfiguration :AVAILABLE_LOCALES => '', :BLACKHOLE_PREFIX => 'do-not-reply-to-this-address', :BLOG_FEED => '', + :CACHE_FRAGMENTS => true, :CONTACT_EMAIL => 'contact@localhost', :CONTACT_NAME => 'Alaveteli', :COOKIE_STORE_SESSION_SECRET => 'this default is insecure as code is open source, please override for live sites in config/general; this will do for local development', -- cgit v1.2.3 From 5ddd0620f8023c4bbf7d82e61f841dadd73bb7ba Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 25 Nov 2013 17:31:55 +0000 Subject: Ruby 1.8 compatibility fixes. --- lib/tasks/temp.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tasks/temp.rake b/lib/tasks/temp.rake index bdee3027b..67fa10174 100644 --- a/lib/tasks/temp.rake +++ b/lib/tasks/temp.rake @@ -12,10 +12,10 @@ namespace :temp do f = is_gz ? Zlib::GzipReader.open(log_file_path) : File.open(log_file_path, 'r') processed = 0 f.each_line do |line| - line.force_encoding('ASCII-8BIT') - if request_match = line.match(/^Started (?GET|OPTIONS|POST) "(?\/request\/.*?)"/) + line.force_encoding('ASCII-8BIT') if RUBY_VERSION.to_f >= 1.9 + if request_match = line.match(/^Started (GET|OPTIONS|POST) "(\/request\/.*?)"/) next if line.match(/request\/\d+\/response/) - urls[request_match[:path]] += 1 + urls[request_match[2]] += 1 processed += 1 end end -- cgit v1.2.3 From 2bc00ec896b955ba87faf2c051517d26635f80a3 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 25 Nov 2013 17:53:30 +0000 Subject: Update the switch-theme.rb script allow switching to no theme --- script/switch-theme.rb | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/script/switch-theme.rb b/script/switch-theme.rb index 47f81c7a8..f6ecc80b1 100755 --- a/script/switch-theme.rb +++ b/script/switch-theme.rb @@ -31,6 +31,7 @@ require 'tempfile' +$no_theme_name = 'none' theme_directory = ENV['ALAVETELI_THEMES_DIR'] alaveteli_directory = File.expand_path(File.join(File.dirname(__FILE__), "..")) @@ -53,7 +54,9 @@ $available_themes = Dir.entries(theme_directory).find_all do |local_theme_name| next unless File.directory? full_path next unless File.directory? File.join(full_path, '.git') local_theme_name -end +end.sort + +$available_themes.unshift $no_theme_name if $available_themes.empty? STDERR.puts "There were no theme directories found in '#{theme_directory}'" @@ -62,7 +65,7 @@ end def usage_and_exit STDERR.puts "Usage: #{$0} " - $available_themes.sort.each do |theme_name| + $available_themes.each do |theme_name| STDERR.puts " #{theme_name}" end exit 1 @@ -108,13 +111,19 @@ symlink(File.basename(theme_filename), config_directory, "general.yml") -symlink(File.join(full_theme_path, 'public'), - File.join(alaveteli_directory, 'public'), - 'alavetelitheme') +public_directory = File.join(alaveteli_directory, 'public') -symlink(full_theme_path, - File.join(alaveteli_directory, 'vendor', 'plugins'), - requested_theme) +if requested_theme == $no_theme_name + File.unlink File.join(public_directory, 'alavetelitheme') +else + symlink(File.join(full_theme_path, 'public'), + public_directory, + 'alavetelitheme') + + symlink(full_theme_path, + File.join(alaveteli_directory, 'vendor', 'plugins'), + requested_theme) +end STDERR.puts """Switched to #{requested_theme}! You will need to restart any development server you have running.""" -- cgit v1.2.3 From 7b0ef273f5fff50c8d58fbfd27094a1b68020e3c Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 19 Nov 2013 16:37:25 +0000 Subject: Add a guide for upgrading themes to use the asset pipeline --- doc/THEME-ASSETS-UPGRADE.md | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 doc/THEME-ASSETS-UPGRADE.md diff --git a/doc/THEME-ASSETS-UPGRADE.md b/doc/THEME-ASSETS-UPGRADE.md new file mode 100644 index 000000000..66a1e95f4 --- /dev/null +++ b/doc/THEME-ASSETS-UPGRADE.md @@ -0,0 +1,69 @@ +This document has notes on switching your Alaveteli theme to use +the Rails asset pipeline. + +Firstly, add the following to your `lib/alavetelitheme.rb`, in +order to add the subdirectories of your theme's `assets` +directory to `config.assets.path`: + + # Prepend the asset directories in this theme to the asset path: + ['stylesheets', 'images', 'javascripts'].each do |asset_type| + theme_asset_path = File.join(File.dirname(__FILE__), + '..', + 'assets', + asset_type) + Rails.application.config.assets.paths.unshift theme_asset_path + end + +In the root of your theme, create these directories: + + assets + \ images + \ stylesheets + \ javascripts + +i.e. `assets` is at the same level as `lib` and `locale-theme`. + +Move any image files from `public/images` to `assets/images`. +Now change any references to those images with a literal `` +tag to use `image_tag` instead. For example, instead of: + + + +... you should have: + + image_tag('helpmeinvestigate.png', :alt => "", :class => "rss") + +You should similarly move your stylesheets into +`assets/stylesheets`. If a stylesheet refers to images, you +should rename the `.css` file to `.css.scss`, and change `url` +to the sass-rails `image-url` helper. e.g. instead of: + + background-image: url(../images/mysociety.png); + +... you should have: + + background-image: image-url('mysociety.png'); + +If your only stylesheet is called `custom.css`, as in the +example theme, you shouldn't need to make any other changes to +the CSS. If you have added additional stylesheets +(e.g. `extra.css`), then you'll need to both: + +1. add them to +`lib/views/general/_stylesheet_includes.html.erb`, for example +with: + + <%= stylesheet_link_tag "extra" %> + +2. add the following in `lib/alavetelitheme.rb`: + + config.assets.precompile.push 'extra.css' + +Any custom Javascript should be moved to `assets/javascripts` in +your theme directory, and, simlarly to the additional CSS, it +should be mentioned in `lib/alavetelitheme.rb` with: + + config.assets.precompile.push 'fancy-effects.js' + +You should be left with nothing in the `public` directory after +making these changes, except possibly custom error pages. -- cgit v1.2.3 From 2934c425ab120b2205fe4de4f8e80505eba293a0 Mon Sep 17 00:00:00 2001 From: Jedidiah Broadbent Date: Tue, 19 Nov 2013 17:55:05 +0000 Subject: Add Jedidiah's placeholder logo --- app/assets/images/logo.png | Bin 0 -> 1356 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/assets/images/logo.png diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png new file mode 100644 index 000000000..1e44ced7d Binary files /dev/null and b/app/assets/images/logo.png differ -- cgit v1.2.3 From 1ab142a8c456af25959ec3479bec8d5d12fa0ceb Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 19 Nov 2013 18:24:17 +0000 Subject: Fix asset inclusion where THEME_URLS is empty --- app/views/general/_stylesheet_includes.html.erb | 2 +- config/application.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/general/_stylesheet_includes.html.erb b/app/views/general/_stylesheet_includes.html.erb index 8f375d777..390b5b10d 100644 --- a/app/views/general/_stylesheet_includes.html.erb +++ b/app/views/general/_stylesheet_includes.html.erb @@ -18,6 +18,6 @@ <%= stylesheet_link_tag 'ie7.css' %> <% if AlaveteliConfiguration::force_registration_on_new_request %> - <%= stylesheet_link_tag 'jquery.fancybox-1.3.4', :rel => "stylesheet" %> + <%= stylesheet_link_tag 'jquery.fancybox-1.3.4.pack.js', :rel => "stylesheet" %> <% end %> <% end %> diff --git a/config/application.rb b/config/application.rb index 426db2318..10b1fc5c5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -93,6 +93,7 @@ module Alaveteli 'jquery.Jcrop.css', 'excanvas.min.js', 'fonts.css', + 'print.css', 'main.css', 'admin.css', 'ie6.css', -- cgit v1.2.3 From 35c4623a76bf1c515b3c11df17f260731f9e222b Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 19 Nov 2013 18:28:46 +0000 Subject: Add an empty custom.css to cope with THEME_URLS being empty --- app/assets/stylesheets/custom.css | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 app/assets/stylesheets/custom.css diff --git a/app/assets/stylesheets/custom.css b/app/assets/stylesheets/custom.css new file mode 100644 index 000000000..f1df5cca5 --- /dev/null +++ b/app/assets/stylesheets/custom.css @@ -0,0 +1,5 @@ +/* Themes will typically add a custom.css file with their own CSS. + This file is present to stop errors where THEME_URLS is empty, + since it is mentioned in the application.css manifest. Themes + should prepend their directories to the asset path so this will + be overriden by any custom.css in the theme. */ -- cgit v1.2.3 From 4df10dea45b2c8dafa39925fcedfe296cc69431c Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Wed, 20 Nov 2013 11:58:52 +0000 Subject: Precompile the assets in rails-post-deploy --- script/rails-post-deploy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/rails-post-deploy b/script/rails-post-deploy index c09868347..bd5165a72 100755 --- a/script/rails-post-deploy +++ b/script/rails-post-deploy @@ -96,3 +96,5 @@ bundle exec rake submodules:check bundle exec rake db:migrate #--trace bundle exec rake themes:install + +bundle exec rake assets:precompile -- cgit v1.2.3 From e1930f6c5ac9542e20830c50ed5fdb7e3603b14c Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 21 Nov 2013 08:41:07 +0000 Subject: Move last JS files from public/javascripts/ to app/assets/javascripts/ --- .../javascripts/jquery.flot.axislabels.min.js | 1 + app/assets/javascripts/jquery.flot.errorbars.js | 353 +++ app/assets/javascripts/jquery.flot.js | 3078 ++++++++++++++++++++ app/assets/javascripts/jquery.flot.tickrotor.js | 205 ++ .../javascripts/jquery.flot.tickrotor.min.js | 1 + public/javascripts/jquery.flot.axislabels.min.js | 1 - public/javascripts/jquery.flot.errorbars.js | 353 --- public/javascripts/jquery.flot.js | 3078 -------------------- public/javascripts/jquery.flot.tickrotor.js | 205 -- public/javascripts/jquery.flot.tickrotor.min.js | 1 - 10 files changed, 3638 insertions(+), 3638 deletions(-) create mode 100644 app/assets/javascripts/jquery.flot.axislabels.min.js create mode 100644 app/assets/javascripts/jquery.flot.errorbars.js create mode 100644 app/assets/javascripts/jquery.flot.js create mode 100644 app/assets/javascripts/jquery.flot.tickrotor.js create mode 100644 app/assets/javascripts/jquery.flot.tickrotor.min.js delete mode 100644 public/javascripts/jquery.flot.axislabels.min.js delete mode 100644 public/javascripts/jquery.flot.errorbars.js delete mode 100644 public/javascripts/jquery.flot.js delete mode 100644 public/javascripts/jquery.flot.tickrotor.js delete mode 100644 public/javascripts/jquery.flot.tickrotor.min.js diff --git a/app/assets/javascripts/jquery.flot.axislabels.min.js b/app/assets/javascripts/jquery.flot.axislabels.min.js new file mode 100644 index 000000000..684d6173a --- /dev/null +++ b/app/assets/javascripts/jquery.flot.axislabels.min.js @@ -0,0 +1 @@ +(function($){var options={};function canvasSupported(){return !!document.createElement("canvas").getContext}function canvasTextSupported(){if(!canvasSupported()){return false}var dummy_canvas=document.createElement("canvas");var context=dummy_canvas.getContext("2d");return typeof context.fillText=="function"}function css3TransitionSupported(){var div=document.createElement("div");return typeof div.style.MozTransition!="undefined"||typeof div.style.OTransition!="undefined"||typeof div.style.webkitTransition!="undefined"||typeof div.style.transition!="undefined"}function AxisLabel(axisName,position,padding,plot,opts){this.axisName=axisName;this.position=position;this.padding=padding;this.plot=plot;this.opts=opts;this.width=0;this.height=0}AxisLabel.prototype["delete"]=function(){};CanvasAxisLabel.prototype=new AxisLabel();CanvasAxisLabel.prototype.constructor=CanvasAxisLabel;function CanvasAxisLabel(axisName,position,padding,plot,opts){AxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts)}CanvasAxisLabel.prototype.calculateSize=function(){if(!this.opts.axisLabelFontSizePixels){this.opts.axisLabelFontSizePixels=14}if(!this.opts.axisLabelFontFamily){this.opts.axisLabelFontFamily="sans-serif"}var textWidth=this.opts.axisLabelFontSizePixels+this.padding;var textHeight=this.opts.axisLabelFontSizePixels+this.padding;if(this.position=="left"||this.position=="right"){this.width=this.opts.axisLabelFontSizePixels+this.padding;this.height=0}else{this.width=0;this.height=this.opts.axisLabelFontSizePixels+this.padding}};CanvasAxisLabel.prototype.draw=function(box){var ctx=this.plot.getCanvas().getContext("2d");ctx.save();ctx.font=this.opts.axisLabelFontSizePixels+"px "+this.opts.axisLabelFontFamily;var width=ctx.measureText(this.opts.axisLabel).width;var height=this.opts.axisLabelFontSizePixels;var x,y,angle=0;if(this.position=="top"){x=box.left+box.width/2-width/2;y=box.top+height*0.72}else{if(this.position=="bottom"){x=box.left+box.width/2-width/2;y=box.top+box.height-height*0.72}else{if(this.position=="left"){x=box.left+height*0.72;y=box.height/2+box.top+width/2;angle=-Math.PI/2}else{if(this.position=="right"){x=box.left+box.width-height*0.72;y=box.height/2+box.top-width/2;angle=Math.PI/2}}}}ctx.translate(x,y);ctx.rotate(angle);ctx.fillText(this.opts.axisLabel,0,0);ctx.restore()};HtmlAxisLabel.prototype=new AxisLabel();HtmlAxisLabel.prototype.constructor=HtmlAxisLabel;function HtmlAxisLabel(axisName,position,padding,plot,opts){AxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts);this.elem=null}HtmlAxisLabel.prototype.calculateSize=function(){var elem=$('
        '+this.opts.axisLabel+"
        ");this.plot.getPlaceholder().append(elem);this.labelWidth=elem.outerWidth(true);this.labelHeight=elem.outerHeight(true);elem.remove();this.width=this.height=0;if(this.position=="left"||this.position=="right"){this.width=this.labelWidth+this.padding}else{this.height=this.labelHeight+this.padding}};HtmlAxisLabel.prototype["delete"]=function(){if(this.elem){this.elem.remove()}};HtmlAxisLabel.prototype.draw=function(box){this.plot.getPlaceholder().find("#"+this.axisName+"Label").remove();this.elem=$('
        '+this.opts.axisLabel+"
        ");this.plot.getPlaceholder().append(this.elem);if(this.position=="top"){this.elem.css("left",box.left+box.width/2-this.labelWidth/2+"px");this.elem.css("top",box.top+"px")}else{if(this.position=="bottom"){this.elem.css("left",box.left+box.width/2-this.labelWidth/2+"px");this.elem.css("top",box.top+box.height-this.labelHeight+"px")}else{if(this.position=="left"){this.elem.css("top",box.top+box.height/2-this.labelHeight/2+"px");this.elem.css("left",box.left+"px")}else{if(this.position=="right"){this.elem.css("top",box.top+box.height/2-this.labelHeight/2+"px");this.elem.css("left",box.left+box.width-this.labelWidth+"px")}}}}};CssTransformAxisLabel.prototype=new HtmlAxisLabel();CssTransformAxisLabel.prototype.constructor=CssTransformAxisLabel;function CssTransformAxisLabel(axisName,position,padding,plot,opts){HtmlAxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts)}CssTransformAxisLabel.prototype.calculateSize=function(){HtmlAxisLabel.prototype.calculateSize.call(this);this.width=this.height=0;if(this.position=="left"||this.position=="right"){this.width=this.labelHeight+this.padding}else{this.height=this.labelHeight+this.padding}};CssTransformAxisLabel.prototype.transforms=function(degrees,x,y){var stransforms={"-moz-transform":"","-webkit-transform":"","-o-transform":"","-ms-transform":""};if(x!=0||y!=0){var stdTranslate=" translate("+x+"px, "+y+"px)";stransforms["-moz-transform"]+=stdTranslate;stransforms["-webkit-transform"]+=stdTranslate;stransforms["-o-transform"]+=stdTranslate;stransforms["-ms-transform"]+=stdTranslate}if(degrees!=0){var rotation=degrees/90;var stdRotate=" rotate("+degrees+"deg)";stransforms["-moz-transform"]+=stdRotate;stransforms["-webkit-transform"]+=stdRotate;stransforms["-o-transform"]+=stdRotate;stransforms["-ms-transform"]+=stdRotate}var s="top: 0; left: 0; ";for(var prop in stransforms){if(stransforms[prop]){s+=prop+":"+stransforms[prop]+";"}}s+=";";return s};CssTransformAxisLabel.prototype.calculateOffsets=function(box){var offsets={x:0,y:0,degrees:0};if(this.position=="bottom"){offsets.x=box.left+box.width/2-this.labelWidth/2;offsets.y=box.top+box.height-this.labelHeight}else{if(this.position=="top"){offsets.x=box.left+box.width/2-this.labelWidth/2;offsets.y=box.top}else{if(this.position=="left"){offsets.degrees=-90;offsets.x=box.left-this.labelWidth/2+this.labelHeight/2;offsets.y=box.height/2+box.top}else{if(this.position=="right"){offsets.degrees=90;offsets.x=box.left+box.width-this.labelWidth/2-this.labelHeight/2;offsets.y=box.height/2+box.top}}}}return offsets};CssTransformAxisLabel.prototype.draw=function(box){this.plot.getPlaceholder().find("."+this.axisName+"Label").remove();var offsets=this.calculateOffsets(box);this.elem=$('
        '+this.opts.axisLabel+"
        ");this.plot.getPlaceholder().append(this.elem)};IeTransformAxisLabel.prototype=new CssTransformAxisLabel();IeTransformAxisLabel.prototype.constructor=IeTransformAxisLabel;function IeTransformAxisLabel(axisName,position,padding,plot,opts){CssTransformAxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts);this.requiresResize=false}IeTransformAxisLabel.prototype.transforms=function(degrees,x,y){var s="";if(degrees!=0){var rotation=degrees/90;while(rotation<0){rotation+=4}s+=" filter: progid:DXImageTransform.Microsoft.BasicImage(rotation="+rotation+"); ";this.requiresResize=(this.position=="right")}if(x!=0){s+="left: "+x+"px; "}if(y!=0){s+="top: "+y+"px; "}return s};IeTransformAxisLabel.prototype.calculateOffsets=function(box){var offsets=CssTransformAxisLabel.prototype.calculateOffsets.call(this,box);if(this.position=="top"){offsets.y=box.top+1}else{if(this.position=="left"){offsets.x=box.left;offsets.y=box.height/2+box.top-this.labelWidth/2}else{if(this.position=="right"){offsets.x=box.left+box.width-this.labelHeight;offsets.y=box.height/2+box.top-this.labelWidth/2}}}return offsets};IeTransformAxisLabel.prototype.draw=function(box){CssTransformAxisLabel.prototype.draw.call(this,box);if(this.requiresResize){this.elem=this.plot.getPlaceholder().find("."+this.axisName+"Label");this.elem.css("width",this.labelWidth);this.elem.css("height",this.labelHeight)}};function init(plot){var secondPass=false;var axisLabels={};var axisOffsetCounts={left:0,right:0,top:0,bottom:0};var defaultPadding=2;plot.hooks.draw.push(function(plot,ctx){var hasAxisLabels=false;if(!secondPass){$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options||plot.getOptions()[axisName];if(axisName in axisLabels){axis.labelHeight=axis.labelHeight-axisLabels[axisName].height;axis.labelWidth=axis.labelWidth-axisLabels[axisName].width;opts.labelHeight=axis.labelHeight;opts.labelWidth=axis.labelWidth;axisLabels[axisName]["delete"]();delete axisLabels[axisName]}if(!opts||!opts.axisLabel||!axis.show){return}hasAxisLabels=true;var renderer=null;if(!opts.axisLabelUseHtml&&navigator.appName=="Microsoft Internet Explorer"){var ua=navigator.userAgent;var re=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");if(re.exec(ua)!=null){rv=parseFloat(RegExp.$1)}if(rv>=9&&!opts.axisLabelUseCanvas&&!opts.axisLabelUseHtml){renderer=CssTransformAxisLabel}else{if(!opts.axisLabelUseCanvas&&!opts.axisLabelUseHtml){renderer=IeTransformAxisLabel}else{if(opts.axisLabelUseCanvas){renderer=CanvasAxisLabel}else{renderer=HtmlAxisLabel}}}}else{if(opts.axisLabelUseHtml||(!css3TransitionSupported()&&!canvasTextSupported())&&!opts.axisLabelUseCanvas){renderer=HtmlAxisLabel}else{if(opts.axisLabelUseCanvas||!css3TransitionSupported()){renderer=CanvasAxisLabel}else{renderer=CssTransformAxisLabel}}}var padding=opts.axisLabelPadding===undefined?defaultPadding:opts.axisLabelPadding;axisLabels[axisName]=new renderer(axisName,axis.position,padding,plot,opts);axisLabels[axisName].calculateSize();opts.labelHeight=axis.labelHeight+axisLabels[axisName].height;opts.labelWidth=axis.labelWidth+axisLabels[axisName].width});if(hasAxisLabels){secondPass=true;plot.setupGrid();plot.draw()}}else{secondPass=false;$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options||plot.getOptions()[axisName];if(!opts||!opts.axisLabel||!axis.show){return}axisLabels[axisName].draw(axis.box)})}})}$.plot.plugins.push({init:init,options:options,name:"axisLabels",version:"2.0b0"})})(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.flot.errorbars.js b/app/assets/javascripts/jquery.flot.errorbars.js new file mode 100644 index 000000000..729843678 --- /dev/null +++ b/app/assets/javascripts/jquery.flot.errorbars.js @@ -0,0 +1,353 @@ +/* Flot plugin for plotting error bars. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +Error bars are used to show standard deviation and other statistical +properties in a plot. + +* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com + +This plugin allows you to plot error-bars over points. Set "errorbars" inside +the points series to the axis name over which there will be error values in +your data array (*even* if you do not intend to plot them later, by setting +"show: null" on xerr/yerr). + +The plugin supports these options: + + series: { + points: { + errorbars: "x" or "y" or "xy", + xerr: { + show: null/false or true, + asymmetric: null/false or true, + upperCap: null or "-" or function, + lowerCap: null or "-" or function, + color: null or color, + radius: null or number + }, + yerr: { same options as xerr } + } + } + +Each data point array is expected to be of the type: + + "x" [ x, y, xerr ] + "y" [ x, y, yerr ] + "xy" [ x, y, xerr, yerr ] + +Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and +equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric +error-bars on X and asymmetric on Y would be: + + [ x, y, xerr, yerr_lower, yerr_upper ] + +By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will +draw a small cap perpendicular to the error bar. They can also be set to a +user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg. + + function drawSemiCircle( ctx, x, y, radius ) { + ctx.beginPath(); + ctx.arc( x, y, radius, 0, Math.PI, false ); + ctx.moveTo( x - radius, y ); + ctx.lineTo( x + radius, y ); + ctx.stroke(); + } + +Color and radius both default to the same ones of the points series if not +set. The independent radius parameter on xerr/yerr is useful for the case when +we may want to add error-bars to a line, without showing the interconnecting +points (with radius: 0), and still showing end caps on the error-bars. +shadowSize and lineWidth are derived as well from the points series. + +*/ + +(function ($) { + var options = { + series: { + points: { + errorbars: null, //should be 'x', 'y' or 'xy' + xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}, + yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null} + } + } + }; + + function processRawData(plot, series, data, datapoints){ + if (!series.points.errorbars) + return; + + // x,y values + var format = [ + { x: true, number: true, required: true }, + { y: true, number: true, required: true } + ]; + + var errors = series.points.errorbars; + // error bars - first X then Y + if (errors == 'x' || errors == 'xy') { + // lower / upper error + if (series.points.xerr.asymmetric) { + format.push({ x: true, number: true, required: true }); + format.push({ x: true, number: true, required: true }); + } else + format.push({ x: true, number: true, required: true }); + } + if (errors == 'y' || errors == 'xy') { + // lower / upper error + if (series.points.yerr.asymmetric) { + format.push({ y: true, number: true, required: true }); + format.push({ y: true, number: true, required: true }); + } else + format.push({ y: true, number: true, required: true }); + } + datapoints.format = format; + } + + function parseErrors(series, i){ + + var points = series.datapoints.points; + + // read errors from points array + var exl = null, + exu = null, + eyl = null, + eyu = null; + var xerr = series.points.xerr, + yerr = series.points.yerr; + + var eb = series.points.errorbars; + // error bars - first X + if (eb == 'x' || eb == 'xy') { + if (xerr.asymmetric) { + exl = points[i + 2]; + exu = points[i + 3]; + if (eb == 'xy') + if (yerr.asymmetric){ + eyl = points[i + 4]; + eyu = points[i + 5]; + } else eyl = points[i + 4]; + } else { + exl = points[i + 2]; + if (eb == 'xy') + if (yerr.asymmetric) { + eyl = points[i + 3]; + eyu = points[i + 4]; + } else eyl = points[i + 3]; + } + // only Y + } else if (eb == 'y') + if (yerr.asymmetric) { + eyl = points[i + 2]; + eyu = points[i + 3]; + } else eyl = points[i + 2]; + + // symmetric errors? + if (exu == null) exu = exl; + if (eyu == null) eyu = eyl; + + var errRanges = [exl, exu, eyl, eyu]; + // nullify if not showing + if (!xerr.show){ + errRanges[0] = null; + errRanges[1] = null; + } + if (!yerr.show){ + errRanges[2] = null; + errRanges[3] = null; + } + return errRanges; + } + + function drawSeriesErrors(plot, ctx, s){ + + var points = s.datapoints.points, + ps = s.datapoints.pointsize, + ax = [s.xaxis, s.yaxis], + radius = s.points.radius, + err = [s.points.xerr, s.points.yerr]; + + //sanity check, in case some inverted axis hack is applied to flot + var invertX = false; + if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) { + invertX = true; + var tmp = err[0].lowerCap; + err[0].lowerCap = err[0].upperCap; + err[0].upperCap = tmp; + } + + var invertY = false; + if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) { + invertY = true; + var tmp = err[1].lowerCap; + err[1].lowerCap = err[1].upperCap; + err[1].upperCap = tmp; + } + + for (var i = 0; i < s.datapoints.points.length; i += ps) { + + //parse + var errRanges = parseErrors(s, i); + + //cycle xerr & yerr + for (var e = 0; e < err.length; e++){ + + var minmax = [ax[e].min, ax[e].max]; + + //draw this error? + if (errRanges[e * err.length]){ + + //data coordinates + var x = points[i], + y = points[i + 1]; + + //errorbar ranges + var upper = [x, y][e] + errRanges[e * err.length + 1], + lower = [x, y][e] - errRanges[e * err.length]; + + //points outside of the canvas + if (err[e].err == 'x') + if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) + continue; + if (err[e].err == 'y') + if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) + continue; + + // prevent errorbars getting out of the canvas + var drawUpper = true, + drawLower = true; + + if (upper > minmax[1]) { + drawUpper = false; + upper = minmax[1]; + } + if (lower < minmax[0]) { + drawLower = false; + lower = minmax[0]; + } + + //sanity check, in case some inverted axis hack is applied to flot + if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) { + //swap coordinates + var tmp = lower; + lower = upper; + upper = tmp; + tmp = drawLower; + drawLower = drawUpper; + drawUpper = tmp; + tmp = minmax[0]; + minmax[0] = minmax[1]; + minmax[1] = tmp; + } + + // convert to pixels + x = ax[0].p2c(x), + y = ax[1].p2c(y), + upper = ax[e].p2c(upper); + lower = ax[e].p2c(lower); + minmax[0] = ax[e].p2c(minmax[0]); + minmax[1] = ax[e].p2c(minmax[1]); + + //same style as points by default + var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth, + sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize; + + //shadow as for points + if (lw > 0 && sw > 0) { + var w = sw / 2; + ctx.lineWidth = w; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax); + + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax); + } + + ctx.strokeStyle = err[e].color? err[e].color: s.color; + ctx.lineWidth = lw; + //draw it + drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax); + } + } + } + } + + function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){ + + //shadow offset + y += offset; + upper += offset; + lower += offset; + + // error bar - avoid plotting over circles + if (err.err == 'x'){ + if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]); + else drawUpper = false; + if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] ); + else drawLower = false; + } + else { + if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] ); + else drawUpper = false; + if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] ); + else drawLower = false; + } + + //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps + //this is a way to get errorbars on lines without visible connecting dots + radius = err.radius != null? err.radius: radius; + + // upper cap + if (drawUpper) { + if (err.upperCap == '-'){ + if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] ); + else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] ); + } else if ($.isFunction(err.upperCap)){ + if (err.err=='x') err.upperCap(ctx, upper, y, radius); + else err.upperCap(ctx, x, upper, radius); + } + } + // lower cap + if (drawLower) { + if (err.lowerCap == '-'){ + if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] ); + else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] ); + } else if ($.isFunction(err.lowerCap)){ + if (err.err=='x') err.lowerCap(ctx, lower, y, radius); + else err.lowerCap(ctx, x, lower, radius); + } + } + } + + function drawPath(ctx, pts){ + ctx.beginPath(); + ctx.moveTo(pts[0][0], pts[0][1]); + for (var p=1; p < pts.length; p++) + ctx.lineTo(pts[p][0], pts[p][1]); + ctx.stroke(); + } + + function draw(plot, ctx){ + var plotOffset = plot.getPlotOffset(); + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + $.each(plot.getData(), function (i, s) { + if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) + drawSeriesErrors(plot, ctx, s); + }); + ctx.restore(); + } + + function init(plot) { + plot.hooks.processRawData.push(processRawData); + plot.hooks.draw.push(draw); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'errorbars', + version: '1.0' + }); +})(jQuery); diff --git a/app/assets/javascripts/jquery.flot.js b/app/assets/javascripts/jquery.flot.js new file mode 100644 index 000000000..2855d2eb3 --- /dev/null +++ b/app/assets/javascripts/jquery.flot.js @@ -0,0 +1,3078 @@ +/* Javascript plotting library for jQuery, version 0.8.2-alpha. + +Copyright (c) 2007-2013 IOLA and Ole Laursen. +Licensed under the MIT license. + +*/ + +// first an inline dependency, jquery.colorhelpers.js, we inline it here +// for convenience + +/* Plugin for jQuery for working with colors. + * + * Version 1.1. + * + * Inspiration from jQuery color animation plugin by John Resig. + * + * Released under the MIT license by Ole Laursen, October 2009. + * + * Examples: + * + * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() + * var c = $.color.extract($("#mydiv"), 'background-color'); + * console.log(c.r, c.g, c.b, c.a); + * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" + * + * Note that .scale() and .add() return the same modified object + * instead of making a new one. + * + * V. 1.1: Fix error handling so e.g. parsing an empty string does + * produce a color rather than just crashing. + */ +(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); + +// the actual Flot code +(function($) { + + // Cache the prototype hasOwnProperty for faster access + + var hasOwnProperty = Object.prototype.hasOwnProperty; + + /////////////////////////////////////////////////////////////////////////// + // The Canvas object is a wrapper around an HTML5 tag. + // + // @constructor + // @param {string} cls List of classes to apply to the canvas. + // @param {element} container Element onto which to append the canvas. + // + // Requiring a container is a little iffy, but unfortunately canvas + // operations don't work unless the canvas is attached to the DOM. + + function Canvas(cls, container) { + + var element = container.children("." + cls)[0]; + + if (element == null) { + + element = document.createElement("canvas"); + element.className = cls; + + $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) + .appendTo(container); + + // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas + + if (!element.getContext) { + if (window.G_vmlCanvasManager) { + element = window.G_vmlCanvasManager.initElement(element); + } else { + throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); + } + } + } + + this.element = element; + + var context = this.context = element.getContext("2d"); + + // Determine the screen's ratio of physical to device-independent + // pixels. This is the ratio between the canvas width that the browser + // advertises and the number of pixels actually present in that space. + + // The iPhone 4, for example, has a device-independent width of 320px, + // but its screen is actually 640px wide. It therefore has a pixel + // ratio of 2, while most normal devices have a ratio of 1. + + var devicePixelRatio = window.devicePixelRatio || 1, + backingStoreRatio = + context.webkitBackingStorePixelRatio || + context.mozBackingStorePixelRatio || + context.msBackingStorePixelRatio || + context.oBackingStorePixelRatio || + context.backingStorePixelRatio || 1; + + this.pixelRatio = devicePixelRatio / backingStoreRatio; + + // Size the canvas to match the internal dimensions of its container + + this.resize(container.width(), container.height()); + + // Collection of HTML div layers for text overlaid onto the canvas + + this.textContainer = null; + this.text = {}; + + // Cache of text fragments and metrics, so we can avoid expensively + // re-calculating them when the plot is re-rendered in a loop. + + this._textCache = {}; + } + + // Resizes the canvas to the given dimensions. + // + // @param {number} width New width of the canvas, in pixels. + // @param {number} width New height of the canvas, in pixels. + + Canvas.prototype.resize = function(width, height) { + + if (width <= 0 || height <= 0) { + throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); + } + + var element = this.element, + context = this.context, + pixelRatio = this.pixelRatio; + + // Resize the canvas, increasing its density based on the display's + // pixel ratio; basically giving it more pixels without increasing the + // size of its element, to take advantage of the fact that retina + // displays have that many more pixels in the same advertised space. + + // Resizing should reset the state (excanvas seems to be buggy though) + + if (this.width != width) { + element.width = width * pixelRatio; + element.style.width = width + "px"; + this.width = width; + } + + if (this.height != height) { + element.height = height * pixelRatio; + element.style.height = height + "px"; + this.height = height; + } + + // Save the context, so we can reset in case we get replotted. The + // restore ensure that we're really back at the initial state, and + // should be safe even if we haven't saved the initial state yet. + + context.restore(); + context.save(); + + // Scale the coordinate space to match the display density; so even though we + // may have twice as many pixels, we still want lines and other drawing to + // appear at the same size; the extra pixels will just make them crisper. + + context.scale(pixelRatio, pixelRatio); + }; + + // Clears the entire canvas area, not including any overlaid HTML text + + Canvas.prototype.clear = function() { + this.context.clearRect(0, 0, this.width, this.height); + }; + + // Finishes rendering the canvas, including managing the text overlay. + + Canvas.prototype.render = function() { + + var cache = this._textCache; + + // For each text layer, add elements marked as active that haven't + // already been rendered, and remove those that are no longer active. + + for (var layerKey in cache) { + if (hasOwnProperty.call(cache, layerKey)) { + + var layer = this.getTextLayer(layerKey), + layerCache = cache[layerKey]; + + layer.hide(); + + for (var styleKey in layerCache) { + if (hasOwnProperty.call(layerCache, styleKey)) { + var styleCache = layerCache[styleKey]; + for (var key in styleCache) { + if (hasOwnProperty.call(styleCache, key)) { + + var positions = styleCache[key].positions; + + for (var i = 0, position; position = positions[i]; i++) { + if (position.active) { + if (!position.rendered) { + layer.append(position.element); + position.rendered = true; + } + } else { + positions.splice(i--, 1); + if (position.rendered) { + position.element.detach(); + } + } + } + + if (positions.length == 0) { + delete styleCache[key]; + } + } + } + } + } + + layer.show(); + } + } + }; + + // Creates (if necessary) and returns the text overlay container. + // + // @param {string} classes String of space-separated CSS classes used to + // uniquely identify the text layer. + // @return {object} The jQuery-wrapped text-layer div. + + Canvas.prototype.getTextLayer = function(classes) { + + var layer = this.text[classes]; + + // Create the text layer if it doesn't exist + + if (layer == null) { + + // Create the text layer container, if it doesn't exist + + if (this.textContainer == null) { + this.textContainer = $("
        ") + .css({ + position: "absolute", + top: 0, + left: 0, + bottom: 0, + right: 0, + 'font-size': "smaller", + color: "#545454" + }) + .insertAfter(this.element); + } + + layer = this.text[classes] = $("
        ") + .addClass(classes) + .css({ + position: "absolute", + top: 0, + left: 0, + bottom: 0, + right: 0 + }) + .appendTo(this.textContainer); + } + + return layer; + }; + + // Creates (if necessary) and returns a text info object. + // + // The object looks like this: + // + // { + // width: Width of the text's wrapper div. + // height: Height of the text's wrapper div. + // element: The jQuery-wrapped HTML div containing the text. + // positions: Array of positions at which this text is drawn. + // } + // + // The positions array contains objects that look like this: + // + // { + // active: Flag indicating whether the text should be visible. + // rendered: Flag indicating whether the text is currently visible. + // element: The jQuery-wrapped HTML div containing the text. + // x: X coordinate at which to draw the text. + // y: Y coordinate at which to draw the text. + // } + // + // Each position after the first receives a clone of the original element. + // + // The idea is that that the width, height, and general 'identity' of the + // text is constant no matter where it is placed; the placements are a + // secondary property. + // + // Canvas maintains a cache of recently-used text info objects; getTextInfo + // either returns the cached element or creates a new entry. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {string} text Text string to retrieve info for. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which to rotate the text, in degrees. + // Angle is currently unused, it will be implemented in the future. + // @param {number=} width Maximum width of the text before it wraps. + // @return {object} a text info object. + + Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { + + var textStyle, layerCache, styleCache, info; + + // Cast the value to a string, in case we were given a number or such + + text = "" + text; + + // If the font is a font-spec object, generate a CSS font definition + + if (typeof font === "object") { + textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; + } else { + textStyle = font; + } + + // Retrieve (or create) the cache for the text's layer and styles + + layerCache = this._textCache[layer]; + + if (layerCache == null) { + layerCache = this._textCache[layer] = {}; + } + + styleCache = layerCache[textStyle]; + + if (styleCache == null) { + styleCache = layerCache[textStyle] = {}; + } + + info = styleCache[text]; + + // If we can't find a matching element in our cache, create a new one + + if (info == null) { + + var element = $("
        ").html(text) + .css({ + position: "absolute", + 'max-width': width, + top: -9999 + }) + .appendTo(this.getTextLayer(layer)); + + if (typeof font === "object") { + element.css({ + font: textStyle, + color: font.color + }); + } else if (typeof font === "string") { + element.addClass(font); + } + + info = styleCache[text] = { + width: element.outerWidth(true), + height: element.outerHeight(true), + element: element, + positions: [] + }; + + element.detach(); + } + + return info; + }; + + // Adds a text string to the canvas text overlay. + // + // The text isn't drawn immediately; it is marked as rendering, which will + // result in its addition to the canvas on the next render pass. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {number} x X coordinate at which to draw the text. + // @param {number} y Y coordinate at which to draw the text. + // @param {string} text Text string to draw. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which to rotate the text, in degrees. + // Angle is currently unused, it will be implemented in the future. + // @param {number=} width Maximum width of the text before it wraps. + // @param {string=} halign Horizontal alignment of the text; either "left", + // "center" or "right". + // @param {string=} valign Vertical alignment of the text; either "top", + // "middle" or "bottom". + + Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { + + var info = this.getTextInfo(layer, text, font, angle, width), + positions = info.positions; + + // Tweak the div's position to match the text's alignment + + if (halign == "center") { + x -= info.width / 2; + } else if (halign == "right") { + x -= info.width; + } + + if (valign == "middle") { + y -= info.height / 2; + } else if (valign == "bottom") { + y -= info.height; + } + + // Determine whether this text already exists at this position. + // If so, mark it for inclusion in the next render pass. + + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = true; + return; + } + } + + // If the text doesn't exist at this position, create a new entry + + // For the very first position we'll re-use the original element, + // while for subsequent ones we'll clone it. + + position = { + active: true, + rendered: false, + element: positions.length ? info.element.clone() : info.element, + x: x, + y: y + }; + + positions.push(position); + + // Move the element to its final position within the container + + position.element.css({ + top: Math.round(y), + left: Math.round(x), + 'text-align': halign // In case the text wraps + }); + }; + + // Removes one or more text strings from the canvas text overlay. + // + // If no parameters are given, all text within the layer is removed. + // + // Note that the text is not immediately removed; it is simply marked as + // inactive, which will result in its removal on the next render pass. + // This avoids the performance penalty for 'clear and redraw' behavior, + // where we potentially get rid of all text on a layer, but will likely + // add back most or all of it later, as when redrawing axes, for example. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {number=} x X coordinate of the text. + // @param {number=} y Y coordinate of the text. + // @param {string=} text Text string to remove. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which the text is rotated, in degrees. + // Angle is currently unused, it will be implemented in the future. + + Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { + if (text == null) { + var layerCache = this._textCache[layer]; + if (layerCache != null) { + for (var styleKey in layerCache) { + if (hasOwnProperty.call(layerCache, styleKey)) { + var styleCache = layerCache[styleKey]; + for (var key in styleCache) { + if (hasOwnProperty.call(styleCache, key)) { + var positions = styleCache[key].positions; + for (var i = 0, position; position = positions[i]; i++) { + position.active = false; + } + } + } + } + } + } + } else { + var positions = this.getTextInfo(layer, text, font, angle).positions; + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = false; + } + } + } + }; + + /////////////////////////////////////////////////////////////////////////// + // The top-level container for the entire plot. + + function Plot(placeholder, data_, options_, plugins) { + // data is on the form: + // [ series1, series2 ... ] + // where series is either just the data as [ [x1, y1], [x2, y2], ... ] + // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } + + var series = [], + options = { + // the color theme used for graphs + colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], + legend: { + show: true, + noColumns: 1, // number of colums in legend table + labelFormatter: null, // fn: string -> string + labelBoxBorderColor: "#ccc", // border color for the little label boxes + container: null, // container (as jQuery object) to put legend in, null means default on top of graph + position: "ne", // position of default legend container within plot + margin: 5, // distance from grid edge to default legend container within plot + backgroundColor: null, // null means auto-detect + backgroundOpacity: 0.85, // set to 0 to avoid background + sorted: null // default to no legend sorting + }, + xaxis: { + show: null, // null = auto-detect, true = always, false = never + position: "bottom", // or "top" + mode: null, // null or "time" + font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } + color: null, // base color, labels, ticks + tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" + transform: null, // null or f: number -> number to transform axis + inverseTransform: null, // if transform is set, this should be the inverse function + min: null, // min. value to show, null means set automatically + max: null, // max. value to show, null means set automatically + autoscaleMargin: null, // margin in % to add if auto-setting min/max + ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks + tickFormatter: null, // fn: number -> string + labelWidth: null, // size of tick labels in pixels + labelHeight: null, + reserveSpace: null, // whether to reserve space even if axis isn't shown + tickLength: null, // size in pixels of ticks, or "full" for whole line + alignTicksWithAxis: null, // axis number or null for no sync + tickDecimals: null, // no. of decimals, null means auto + tickSize: null, // number or [number, "unit"] + minTickSize: null // number or [number, "unit"] + }, + yaxis: { + autoscaleMargin: 0.02, + position: "left" // or "right" + }, + xaxes: [], + yaxes: [], + series: { + points: { + show: false, + radius: 3, + lineWidth: 2, // in pixels + fill: true, + fillColor: "#ffffff", + symbol: "circle" // or callback + }, + lines: { + // we don't put in show: false so we can see + // whether lines were actively disabled + lineWidth: 2, // in pixels + fill: false, + fillColor: null, + steps: false + // Omit 'zero', so we can later default its value to + // match that of the 'fill' option. + }, + bars: { + show: false, + lineWidth: 2, // in pixels + barWidth: 1, // in units of the x axis + fill: true, + fillColor: null, + align: "left", // "left", "right", or "center" + horizontal: false, + zero: true + }, + shadowSize: 3, + highlightColor: null + }, + grid: { + show: true, + aboveData: false, + color: "#545454", // primary color used for outline and labels + backgroundColor: null, // null for transparent, else color + borderColor: null, // set if different from the grid color + tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" + margin: 0, // distance from the canvas edge to the grid + labelMargin: 5, // in pixels + axisMargin: 8, // in pixels + borderWidth: 2, // in pixels + minBorderMargin: null, // in pixels, null means taken from points radius + markings: null, // array of ranges or fn: axes -> array of ranges + markingsColor: "#f4f4f4", + markingsLineWidth: 2, + // interactive stuff + clickable: false, + hoverable: false, + autoHighlight: true, // highlight in case mouse is near + mouseActiveRadius: 10 // how far the mouse can be away to activate an item + }, + interaction: { + redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow + }, + hooks: {} + }, + surface = null, // the canvas for the plot itself + overlay = null, // canvas for interactive stuff on top of plot + eventHolder = null, // jQuery object that events should be bound to + ctx = null, octx = null, + xaxes = [], yaxes = [], + plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, + plotWidth = 0, plotHeight = 0, + hooks = { + processOptions: [], + processRawData: [], + processDatapoints: [], + processOffset: [], + drawBackground: [], + drawSeries: [], + draw: [], + bindEvents: [], + drawOverlay: [], + shutdown: [] + }, + plot = this; + + // public functions + plot.setData = setData; + plot.setupGrid = setupGrid; + plot.draw = draw; + plot.getPlaceholder = function() { return placeholder; }; + plot.getCanvas = function() { return surface.element; }; + plot.getPlotOffset = function() { return plotOffset; }; + plot.width = function () { return plotWidth; }; + plot.height = function () { return plotHeight; }; + plot.offset = function () { + var o = eventHolder.offset(); + o.left += plotOffset.left; + o.top += plotOffset.top; + return o; + }; + plot.getData = function () { return series; }; + plot.getAxes = function () { + var res = {}, i; + $.each(xaxes.concat(yaxes), function (_, axis) { + if (axis) + res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; + }); + return res; + }; + plot.getXAxes = function () { return xaxes; }; + plot.getYAxes = function () { return yaxes; }; + plot.c2p = canvasToAxisCoords; + plot.p2c = axisToCanvasCoords; + plot.getOptions = function () { return options; }; + plot.highlight = highlight; + plot.unhighlight = unhighlight; + plot.triggerRedrawOverlay = triggerRedrawOverlay; + plot.pointOffset = function(point) { + return { + left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), + top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) + }; + }; + plot.shutdown = shutdown; + plot.resize = function () { + var width = placeholder.width(), + height = placeholder.height(); + surface.resize(width, height); + overlay.resize(width, height); + }; + + // public attributes + plot.hooks = hooks; + + // initialize + initPlugins(plot); + parseOptions(options_); + setupCanvases(); + setData(data_); + setupGrid(); + draw(); + bindEvents(); + + + function executeHooks(hook, args) { + args = [plot].concat(args); + for (var i = 0; i < hook.length; ++i) + hook[i].apply(this, args); + } + + function initPlugins() { + + // References to key classes, allowing plugins to modify them + + var classes = { + Canvas: Canvas + }; + + for (var i = 0; i < plugins.length; ++i) { + var p = plugins[i]; + p.init(plot, classes); + if (p.options) + $.extend(true, options, p.options); + } + } + + function parseOptions(opts) { + + $.extend(true, options, opts); + + // $.extend merges arrays, rather than replacing them. When less + // colors are provided than the size of the default palette, we + // end up with those colors plus the remaining defaults, which is + // not expected behavior; avoid it by replacing them here. + + if (opts && opts.colors) { + options.colors = opts.colors; + } + + if (options.xaxis.color == null) + options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + if (options.yaxis.color == null) + options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + + if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility + options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; + if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility + options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; + + if (options.grid.borderColor == null) + options.grid.borderColor = options.grid.color; + if (options.grid.tickColor == null) + options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + + // Fill in defaults for axis options, including any unspecified + // font-spec fields, if a font-spec was provided. + + // If no x/y axis options were provided, create one of each anyway, + // since the rest of the code assumes that they exist. + + var i, axisOptions, axisCount, + fontDefaults = { + style: placeholder.css("font-style"), + size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)), + variant: placeholder.css("font-variant"), + weight: placeholder.css("font-weight"), + family: placeholder.css("font-family") + }; + + fontDefaults.lineHeight = fontDefaults.size * 1.15; + + axisCount = options.xaxes.length || 1; + for (i = 0; i < axisCount; ++i) { + + axisOptions = options.xaxes[i]; + if (axisOptions && !axisOptions.tickColor) { + axisOptions.tickColor = axisOptions.color; + } + + axisOptions = $.extend(true, {}, options.xaxis, axisOptions); + options.xaxes[i] = axisOptions; + + if (axisOptions.font) { + axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); + if (!axisOptions.font.color) { + axisOptions.font.color = axisOptions.color; + } + } + } + + axisCount = options.yaxes.length || 1; + for (i = 0; i < axisCount; ++i) { + + axisOptions = options.yaxes[i]; + if (axisOptions && !axisOptions.tickColor) { + axisOptions.tickColor = axisOptions.color; + } + + axisOptions = $.extend(true, {}, options.yaxis, axisOptions); + options.yaxes[i] = axisOptions; + + if (axisOptions.font) { + axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); + if (!axisOptions.font.color) { + axisOptions.font.color = axisOptions.color; + } + } + } + + // backwards compatibility, to be removed in future + if (options.xaxis.noTicks && options.xaxis.ticks == null) + options.xaxis.ticks = options.xaxis.noTicks; + if (options.yaxis.noTicks && options.yaxis.ticks == null) + options.yaxis.ticks = options.yaxis.noTicks; + if (options.x2axis) { + options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); + options.xaxes[1].position = "top"; + } + if (options.y2axis) { + options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); + options.yaxes[1].position = "right"; + } + if (options.grid.coloredAreas) + options.grid.markings = options.grid.coloredAreas; + if (options.grid.coloredAreasColor) + options.grid.markingsColor = options.grid.coloredAreasColor; + if (options.lines) + $.extend(true, options.series.lines, options.lines); + if (options.points) + $.extend(true, options.series.points, options.points); + if (options.bars) + $.extend(true, options.series.bars, options.bars); + if (options.shadowSize != null) + options.series.shadowSize = options.shadowSize; + if (options.highlightColor != null) + options.series.highlightColor = options.highlightColor; + + // save options on axes for future reference + for (i = 0; i < options.xaxes.length; ++i) + getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; + for (i = 0; i < options.yaxes.length; ++i) + getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; + + // add hooks from options + for (var n in hooks) + if (options.hooks[n] && options.hooks[n].length) + hooks[n] = hooks[n].concat(options.hooks[n]); + + executeHooks(hooks.processOptions, [options]); + } + + function setData(d) { + series = parseData(d); + fillInSeriesOptions(); + processData(); + } + + function parseData(d) { + var res = []; + for (var i = 0; i < d.length; ++i) { + var s = $.extend(true, {}, options.series); + + if (d[i].data != null) { + s.data = d[i].data; // move the data instead of deep-copy + delete d[i].data; + + $.extend(true, s, d[i]); + + d[i].data = s.data; + } + else + s.data = d[i]; + res.push(s); + } + + return res; + } + + function axisNumber(obj, coord) { + var a = obj[coord + "axis"]; + if (typeof a == "object") // if we got a real axis, extract number + a = a.n; + if (typeof a != "number") + a = 1; // default to first axis + return a; + } + + function allAxes() { + // return flat array without annoying null entries + return $.grep(xaxes.concat(yaxes), function (a) { return a; }); + } + + function canvasToAxisCoords(pos) { + // return an object with x/y corresponding to all used axes + var res = {}, i, axis; + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) + res["x" + axis.n] = axis.c2p(pos.left); + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) + res["y" + axis.n] = axis.c2p(pos.top); + } + + if (res.x1 !== undefined) + res.x = res.x1; + if (res.y1 !== undefined) + res.y = res.y1; + + return res; + } + + function axisToCanvasCoords(pos) { + // get canvas coords from the first pair of x/y found in pos + var res = {}, i, axis, key; + + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) { + key = "x" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "x"; + + if (pos[key] != null) { + res.left = axis.p2c(pos[key]); + break; + } + } + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) { + key = "y" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "y"; + + if (pos[key] != null) { + res.top = axis.p2c(pos[key]); + break; + } + } + } + + return res; + } + + function getOrCreateAxis(axes, number) { + if (!axes[number - 1]) + axes[number - 1] = { + n: number, // save the number for future reference + direction: axes == xaxes ? "x" : "y", + options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) + }; + + return axes[number - 1]; + } + + function fillInSeriesOptions() { + + var neededColors = series.length, maxIndex = -1, i; + + // Subtract the number of series that already have fixed colors or + // color indexes from the number that we still need to generate. + + for (i = 0; i < series.length; ++i) { + var sc = series[i].color; + if (sc != null) { + neededColors--; + if (typeof sc == "number" && sc > maxIndex) { + maxIndex = sc; + } + } + } + + // If any of the series have fixed color indexes, then we need to + // generate at least as many colors as the highest index. + + if (neededColors <= maxIndex) { + neededColors = maxIndex + 1; + } + + // Generate all the colors, using first the option colors and then + // variations on those colors once they're exhausted. + + var c, colors = [], colorPool = options.colors, + colorPoolSize = colorPool.length, variation = 0; + + for (i = 0; i < neededColors; i++) { + + c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); + + // Each time we exhaust the colors in the pool we adjust + // a scaling factor used to produce more variations on + // those colors. The factor alternates negative/positive + // to produce lighter/darker colors. + + // Reset the variation after every few cycles, or else + // it will end up producing only white or black colors. + + if (i % colorPoolSize == 0 && i) { + if (variation >= 0) { + if (variation < 0.5) { + variation = -variation - 0.2; + } else variation = 0; + } else variation = -variation; + } + + colors[i] = c.scale('rgb', 1 + variation); + } + + // Finalize the series options, filling in their colors + + var colori = 0, s; + for (i = 0; i < series.length; ++i) { + s = series[i]; + + // assign colors + if (s.color == null) { + s.color = colors[colori].toString(); + ++colori; + } + else if (typeof s.color == "number") + s.color = colors[s.color].toString(); + + // turn on lines automatically in case nothing is set + if (s.lines.show == null) { + var v, show = true; + for (v in s) + if (s[v] && s[v].show) { + show = false; + break; + } + if (show) + s.lines.show = true; + } + + // If nothing was provided for lines.zero, default it to match + // lines.fill, since areas by default should extend to zero. + + if (s.lines.zero == null) { + s.lines.zero = !!s.lines.fill; + } + + // setup axes + s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); + s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); + } + } + + function processData() { + var topSentry = Number.POSITIVE_INFINITY, + bottomSentry = Number.NEGATIVE_INFINITY, + fakeInfinity = Number.MAX_VALUE, + i, j, k, m, length, + s, points, ps, x, y, axis, val, f, p, + data, format; + + function updateAxis(axis, min, max) { + if (min < axis.datamin && min != -fakeInfinity) + axis.datamin = min; + if (max > axis.datamax && max != fakeInfinity) + axis.datamax = max; + } + + $.each(allAxes(), function (_, axis) { + // init axis + axis.datamin = topSentry; + axis.datamax = bottomSentry; + axis.used = false; + }); + + for (i = 0; i < series.length; ++i) { + s = series[i]; + s.datapoints = { points: [] }; + + executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); + } + + // first pass: clean and copy data + for (i = 0; i < series.length; ++i) { + s = series[i]; + + data = s.data; + format = s.datapoints.format; + + if (!format) { + format = []; + // find out how to copy + format.push({ x: true, number: true, required: true }); + format.push({ y: true, number: true, required: true }); + + if (s.bars.show || (s.lines.show && s.lines.fill)) { + var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); + format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); + if (s.bars.horizontal) { + delete format[format.length - 1].y; + format[format.length - 1].x = true; + } + } + + s.datapoints.format = format; + } + + if (s.datapoints.pointsize != null) + continue; // already filled in + + s.datapoints.pointsize = format.length; + + ps = s.datapoints.pointsize; + points = s.datapoints.points; + + var insertSteps = s.lines.show && s.lines.steps; + s.xaxis.used = s.yaxis.used = true; + + for (j = k = 0; j < data.length; ++j, k += ps) { + p = data[j]; + + var nullify = p == null; + if (!nullify) { + for (m = 0; m < ps; ++m) { + val = p[m]; + f = format[m]; + + if (f) { + if (f.number && val != null) { + val = +val; // convert to number + if (isNaN(val)) + val = null; + else if (val == Infinity) + val = fakeInfinity; + else if (val == -Infinity) + val = -fakeInfinity; + } + + if (val == null) { + if (f.required) + nullify = true; + + if (f.defaultValue != null) + val = f.defaultValue; + } + } + + points[k + m] = val; + } + } + + if (nullify) { + for (m = 0; m < ps; ++m) { + val = points[k + m]; + if (val != null) { + f = format[m]; + // extract min/max info + if (f.autoscale !== false) { + if (f.x) { + updateAxis(s.xaxis, val, val); + } + if (f.y) { + updateAxis(s.yaxis, val, val); + } + } + } + points[k + m] = null; + } + } + else { + // a little bit of line specific stuff that + // perhaps shouldn't be here, but lacking + // better means... + if (insertSteps && k > 0 + && points[k - ps] != null + && points[k - ps] != points[k] + && points[k - ps + 1] != points[k + 1]) { + // copy the point to make room for a middle point + for (m = 0; m < ps; ++m) + points[k + ps + m] = points[k + m]; + + // middle point has same y + points[k + 1] = points[k - ps + 1]; + + // we've added a point, better reflect that + k += ps; + } + } + } + } + + // give the hooks a chance to run + for (i = 0; i < series.length; ++i) { + s = series[i]; + + executeHooks(hooks.processDatapoints, [ s, s.datapoints]); + } + + // second pass: find datamax/datamin for auto-scaling + for (i = 0; i < series.length; ++i) { + s = series[i]; + points = s.datapoints.points; + ps = s.datapoints.pointsize; + format = s.datapoints.format; + + var xmin = topSentry, ymin = topSentry, + xmax = bottomSentry, ymax = bottomSentry; + + for (j = 0; j < points.length; j += ps) { + if (points[j] == null) + continue; + + for (m = 0; m < ps; ++m) { + val = points[j + m]; + f = format[m]; + if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) + continue; + + if (f.x) { + if (val < xmin) + xmin = val; + if (val > xmax) + xmax = val; + } + if (f.y) { + if (val < ymin) + ymin = val; + if (val > ymax) + ymax = val; + } + } + } + + if (s.bars.show) { + // make sure we got room for the bar on the dancing floor + var delta; + + switch (s.bars.align) { + case "left": + delta = 0; + break; + case "right": + delta = -s.bars.barWidth; + break; + default: + delta = -s.bars.barWidth / 2; + } + + if (s.bars.horizontal) { + ymin += delta; + ymax += delta + s.bars.barWidth; + } + else { + xmin += delta; + xmax += delta + s.bars.barWidth; + } + } + + updateAxis(s.xaxis, xmin, xmax); + updateAxis(s.yaxis, ymin, ymax); + } + + $.each(allAxes(), function (_, axis) { + if (axis.datamin == topSentry) + axis.datamin = null; + if (axis.datamax == bottomSentry) + axis.datamax = null; + }); + } + + function setupCanvases() { + + // Make sure the placeholder is clear of everything except canvases + // from a previous plot in this container that we'll try to re-use. + + placeholder.css("padding", 0) // padding messes up the positioning + .children(":not(.flot-base,.flot-overlay)").remove(); + + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay + + surface = new Canvas("flot-base", placeholder); + overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features + + ctx = surface.context; + octx = overlay.context; + + // define which element we're listening for events on + eventHolder = $(overlay.element).unbind(); + + // If we're re-using a plot object, shut down the old one + + var existing = placeholder.data("plot"); + + if (existing) { + existing.shutdown(); + overlay.clear(); + } + + // save in case we get replotted + placeholder.data("plot", plot); + } + + function bindEvents() { + // bind events + if (options.grid.hoverable) { + eventHolder.mousemove(onMouseMove); + + // Use bind, rather than .mouseleave, because we officially + // still support jQuery 1.2.6, which doesn't define a shortcut + // for mouseenter or mouseleave. This was a bug/oversight that + // was fixed somewhere around 1.3.x. We can return to using + // .mouseleave when we drop support for 1.2.6. + + eventHolder.bind("mouseleave", onMouseLeave); + } + + if (options.grid.clickable) + eventHolder.click(onClick); + + executeHooks(hooks.bindEvents, [eventHolder]); + } + + function shutdown() { + if (redrawTimeout) + clearTimeout(redrawTimeout); + + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mouseleave", onMouseLeave); + eventHolder.unbind("click", onClick); + + executeHooks(hooks.shutdown, [eventHolder]); + } + + function setTransformationHelpers(axis) { + // set helper functions on the axis, assumes plot area + // has been computed already + + function identity(x) { return x; } + + var s, m, t = axis.options.transform || identity, + it = axis.options.inverseTransform; + + // precompute how much the axis is scaling a point + // in canvas space + if (axis.direction == "x") { + s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); + m = Math.min(t(axis.max), t(axis.min)); + } + else { + s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); + s = -s; + m = Math.max(t(axis.max), t(axis.min)); + } + + // data point to canvas coordinate + if (t == identity) // slight optimization + axis.p2c = function (p) { return (p - m) * s; }; + else + axis.p2c = function (p) { return (t(p) - m) * s; }; + // canvas coordinate to data point + if (!it) + axis.c2p = function (c) { return m + c / s; }; + else + axis.c2p = function (c) { return it(m + c / s); }; + } + + function measureTickLabels(axis) { + + var opts = axis.options, + ticks = axis.ticks || [], + labelWidth = opts.labelWidth || 0, + labelHeight = opts.labelHeight || 0, + maxWidth = labelWidth || axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null, + legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, + font = opts.font || "flot-tick-label tickLabel"; + + for (var i = 0; i < ticks.length; ++i) { + + var t = ticks[i]; + + if (!t.label) + continue; + + var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); + + labelWidth = Math.max(labelWidth, info.width); + labelHeight = Math.max(labelHeight, info.height); + } + + axis.labelWidth = opts.labelWidth || labelWidth; + axis.labelHeight = opts.labelHeight || labelHeight; + } + + function allocateAxisBoxFirstPhase(axis) { + // find the bounding box of the axis by looking at label + // widths/heights and ticks, make room by diminishing the + // plotOffset; this first phase only looks at one + // dimension per axis, the other dimension depends on the + // other axes so will have to wait + + var lw = axis.labelWidth, + lh = axis.labelHeight, + pos = axis.options.position, + tickLength = axis.options.tickLength, + axisMargin = options.grid.axisMargin, + padding = options.grid.labelMargin, + all = axis.direction == "x" ? xaxes : yaxes, + index, innermost; + + // determine axis margin + var samePosition = $.grep(all, function (a) { + return a && a.options.position == pos && a.reserveSpace; + }); + if ($.inArray(axis, samePosition) == samePosition.length - 1) + axisMargin = 0; // outermost + + // Determine whether the axis is the first (innermost) on its side + + innermost = $.inArray(axis, samePosition) == 0; + + // determine tick length - if we're innermost, we can use "full" + + if (tickLength == null) { + if (innermost) + tickLength = "full"; + else + tickLength = 5; + } + + if (!isNaN(+tickLength)) + padding += +tickLength; + + // compute box + if (axis.direction == "x") { + lh += padding; + + if (pos == "bottom") { + plotOffset.bottom += lh + axisMargin; + axis.box = { top: surface.height - plotOffset.bottom, height: lh }; + } + else { + axis.box = { top: plotOffset.top + axisMargin, height: lh }; + plotOffset.top += lh + axisMargin; + } + } + else { + lw += padding; + + if (pos == "left") { + axis.box = { left: plotOffset.left + axisMargin, width: lw }; + plotOffset.left += lw + axisMargin; + } + else { + plotOffset.right += lw + axisMargin; + axis.box = { left: surface.width - plotOffset.right, width: lw }; + } + } + + // save for future reference + axis.position = pos; + axis.tickLength = tickLength; + axis.box.padding = padding; + axis.innermost = innermost; + } + + function allocateAxisBoxSecondPhase(axis) { + // now that all axis boxes have been placed in one + // dimension, we can set the remaining dimension coordinates + if (axis.direction == "x") { + axis.box.left = plotOffset.left - axis.labelWidth / 2; + axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; + } + else { + axis.box.top = plotOffset.top - axis.labelHeight / 2; + axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; + } + } + + function adjustLayoutForThingsStickingOut() { + // possibly adjust plot offset to ensure everything stays + // inside the canvas and isn't clipped off + + var minMargin = options.grid.minBorderMargin, + margins = { x: 0, y: 0 }, i, axis; + + // check stuff from the plot (FIXME: this should just read + // a value from the series, otherwise it's impossible to + // customize) + if (minMargin == null) { + minMargin = 0; + for (i = 0; i < series.length; ++i) + minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); + } + + margins.x = margins.y = Math.ceil(minMargin); + + // check axis labels, note we don't check the actual + // labels but instead use the overall width/height to not + // jump as much around with replots + $.each(allAxes(), function (_, axis) { + var dir = axis.direction; + if (axis.reserveSpace) + margins[dir] = Math.ceil(Math.max(margins[dir], (dir == "x" ? axis.labelWidth : axis.labelHeight) / 2)); + }); + + plotOffset.left = Math.max(margins.x, plotOffset.left); + plotOffset.right = Math.max(margins.x, plotOffset.right); + plotOffset.top = Math.max(margins.y, plotOffset.top); + plotOffset.bottom = Math.max(margins.y, plotOffset.bottom); + } + + function setupGrid() { + var i, axes = allAxes(), showGrid = options.grid.show; + + // Initialize the plot's offset from the edge of the canvas + + for (var a in plotOffset) { + var margin = options.grid.margin || 0; + plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; + } + + executeHooks(hooks.processOffset, [plotOffset]); + + // If the grid is visible, add its border width to the offset + + for (var a in plotOffset) { + if(typeof(options.grid.borderWidth) == "object") { + plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; + } + else { + plotOffset[a] += showGrid ? options.grid.borderWidth : 0; + } + } + + // init axes + $.each(axes, function (_, axis) { + axis.show = axis.options.show; + if (axis.show == null) + axis.show = axis.used; // by default an axis is visible if it's got data + + axis.reserveSpace = axis.show || axis.options.reserveSpace; + + setRange(axis); + }); + + if (showGrid) { + + var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); + + $.each(allocatedAxes, function (_, axis) { + // make the ticks + setupTickGeneration(axis); + setTicks(axis); + snapRangeToTicks(axis, axis.ticks); + // find labelWidth/Height for axis + measureTickLabels(axis); + }); + + // with all dimensions calculated, we can compute the + // axis bounding boxes, start from the outside + // (reverse order) + for (i = allocatedAxes.length - 1; i >= 0; --i) + allocateAxisBoxFirstPhase(allocatedAxes[i]); + + // make sure we've got enough space for things that + // might stick out + adjustLayoutForThingsStickingOut(); + + $.each(allocatedAxes, function (_, axis) { + allocateAxisBoxSecondPhase(axis); + }); + } + + plotWidth = surface.width - plotOffset.left - plotOffset.right; + plotHeight = surface.height - plotOffset.bottom - plotOffset.top; + + // now we got the proper plot dimensions, we can compute the scaling + $.each(axes, function (_, axis) { + setTransformationHelpers(axis); + }); + + if (showGrid) { + drawAxisLabels(); + } + + insertLegend(); + } + + function setRange(axis) { + var opts = axis.options, + min = +(opts.min != null ? opts.min : axis.datamin), + max = +(opts.max != null ? opts.max : axis.datamax), + delta = max - min; + + if (delta == 0.0) { + // degenerate case + var widen = max == 0 ? 1 : 0.01; + + if (opts.min == null) + min -= widen; + // always widen max if we couldn't widen min to ensure we + // don't fall into min == max which doesn't work + if (opts.max == null || opts.min != null) + max += widen; + } + else { + // consider autoscaling + var margin = opts.autoscaleMargin; + if (margin != null) { + if (opts.min == null) { + min -= delta * margin; + // make sure we don't go below zero if all values + // are positive + if (min < 0 && axis.datamin != null && axis.datamin >= 0) + min = 0; + } + if (opts.max == null) { + max += delta * margin; + if (max > 0 && axis.datamax != null && axis.datamax <= 0) + max = 0; + } + } + } + axis.min = min; + axis.max = max; + } + + function setupTickGeneration(axis) { + var opts = axis.options; + + // estimate number of ticks + var noTicks; + if (typeof opts.ticks == "number" && opts.ticks > 0) + noTicks = opts.ticks; + else + // heuristic based on the model a*sqrt(x) fitted to + // some data points that seemed reasonable + noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); + + var delta = (axis.max - axis.min) / noTicks, + dec = -Math.floor(Math.log(delta) / Math.LN10), + maxDec = opts.tickDecimals; + + if (maxDec != null && dec > maxDec) { + dec = maxDec; + } + + var magn = Math.pow(10, -dec), + norm = delta / magn, // norm is between 1.0 and 10.0 + size; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; + } + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + + if (opts.minTickSize != null && size < opts.minTickSize) { + size = opts.minTickSize; + } + + axis.delta = delta; + axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + axis.tickSize = opts.tickSize || size; + + // Time mode was moved to a plug-in in 0.8, but since so many people use this + // we'll add an especially friendly make sure they remembered to include it. + + if (opts.mode == "time" && !axis.tickGenerator) { + throw new Error("Time mode requires the flot.time plugin."); + } + + // Flot supports base-10 axes; any other mode else is handled by a plug-in, + // like flot.time.js. + + if (!axis.tickGenerator) { + + axis.tickGenerator = function (axis) { + + var ticks = [], + start = floorInBase(axis.min, axis.tickSize), + i = 0, + v = Number.NaN, + prev; + + do { + prev = v; + v = start + i * axis.tickSize; + ticks.push(v); + ++i; + } while (v < axis.max && v != prev); + return ticks; + }; + + axis.tickFormatter = function (value, axis) { + + var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; + var formatted = "" + Math.round(value * factor) / factor; + + // If tickDecimals was specified, ensure that we have exactly that + // much precision; otherwise default to the value's own precision. + + if (axis.tickDecimals != null) { + var decimal = formatted.indexOf("."); + var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; + if (precision < axis.tickDecimals) { + return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); + } + } + + return formatted; + }; + } + + if ($.isFunction(opts.tickFormatter)) + axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; + + if (opts.alignTicksWithAxis != null) { + var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; + if (otherAxis && otherAxis.used && otherAxis != axis) { + // consider snapping min/max to outermost nice ticks + var niceTicks = axis.tickGenerator(axis); + if (niceTicks.length > 0) { + if (opts.min == null) + axis.min = Math.min(axis.min, niceTicks[0]); + if (opts.max == null && niceTicks.length > 1) + axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); + } + + axis.tickGenerator = function (axis) { + // copy ticks, scaled to this axis + var ticks = [], v, i; + for (i = 0; i < otherAxis.ticks.length; ++i) { + v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); + v = axis.min + v * (axis.max - axis.min); + ticks.push(v); + } + return ticks; + }; + + // we might need an extra decimal since forced + // ticks don't necessarily fit naturally + if (!axis.mode && opts.tickDecimals == null) { + var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), + ts = axis.tickGenerator(axis); + + // only proceed if the tick interval rounded + // with an extra decimal doesn't give us a + // zero at end + if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) + axis.tickDecimals = extraDec; + } + } + } + } + + function setTicks(axis) { + var oticks = axis.options.ticks, ticks = []; + if (oticks == null || (typeof oticks == "number" && oticks > 0)) + ticks = axis.tickGenerator(axis); + else if (oticks) { + if ($.isFunction(oticks)) + // generate the ticks + ticks = oticks(axis); + else + ticks = oticks; + } + + // clean up/labelify the supplied ticks, copy them over + var i, v; + axis.ticks = []; + for (i = 0; i < ticks.length; ++i) { + var label = null; + var t = ticks[i]; + if (typeof t == "object") { + v = +t[0]; + if (t.length > 1) + label = t[1]; + } + else + v = +t; + if (label == null) + label = axis.tickFormatter(v, axis); + if (!isNaN(v)) + axis.ticks.push({ v: v, label: label }); + } + } + + function snapRangeToTicks(axis, ticks) { + if (axis.options.autoscaleMargin && ticks.length > 0) { + // snap to ticks + if (axis.options.min == null) + axis.min = Math.min(axis.min, ticks[0].v); + if (axis.options.max == null && ticks.length > 1) + axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); + } + } + + function draw() { + + surface.clear(); + + executeHooks(hooks.drawBackground, [ctx]); + + var grid = options.grid; + + // draw background, if any + if (grid.show && grid.backgroundColor) + drawBackground(); + + if (grid.show && !grid.aboveData) { + drawGrid(); + } + + for (var i = 0; i < series.length; ++i) { + executeHooks(hooks.drawSeries, [ctx, series[i]]); + drawSeries(series[i]); + } + + executeHooks(hooks.draw, [ctx]); + + if (grid.show && grid.aboveData) { + drawGrid(); + } + + surface.render(); + + // A draw implies that either the axes or data have changed, so we + // should probably update the overlay highlights as well. + + triggerRedrawOverlay(); + } + + function extractRange(ranges, coord) { + var axis, from, to, key, axes = allAxes(); + + for (var i = 0; i < axes.length; ++i) { + axis = axes[i]; + if (axis.direction == coord) { + key = coord + axis.n + "axis"; + if (!ranges[key] && axis.n == 1) + key = coord + "axis"; // support x1axis as xaxis + if (ranges[key]) { + from = ranges[key].from; + to = ranges[key].to; + break; + } + } + } + + // backwards-compat stuff - to be removed in future + if (!ranges[key]) { + axis = coord == "x" ? xaxes[0] : yaxes[0]; + from = ranges[coord + "1"]; + to = ranges[coord + "2"]; + } + + // auto-reverse as an added bonus + if (from != null && to != null && from > to) { + var tmp = from; + from = to; + to = tmp; + } + + return { from: from, to: to, axis: axis }; + } + + function drawBackground() { + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); + ctx.fillRect(0, 0, plotWidth, plotHeight); + ctx.restore(); + } + + function drawGrid() { + var i, axes, bw, bc; + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // draw markings + var markings = options.grid.markings; + if (markings) { + if ($.isFunction(markings)) { + axes = plot.getAxes(); + // xmin etc. is backwards compatibility, to be + // removed in the future + axes.xmin = axes.xaxis.min; + axes.xmax = axes.xaxis.max; + axes.ymin = axes.yaxis.min; + axes.ymax = axes.yaxis.max; + + markings = markings(axes); + } + + for (i = 0; i < markings.length; ++i) { + var m = markings[i], + xrange = extractRange(m, "x"), + yrange = extractRange(m, "y"); + + // fill in missing + if (xrange.from == null) + xrange.from = xrange.axis.min; + if (xrange.to == null) + xrange.to = xrange.axis.max; + if (yrange.from == null) + yrange.from = yrange.axis.min; + if (yrange.to == null) + yrange.to = yrange.axis.max; + + // clip + if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || + yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) + continue; + + xrange.from = Math.max(xrange.from, xrange.axis.min); + xrange.to = Math.min(xrange.to, xrange.axis.max); + yrange.from = Math.max(yrange.from, yrange.axis.min); + yrange.to = Math.min(yrange.to, yrange.axis.max); + + if (xrange.from == xrange.to && yrange.from == yrange.to) + continue; + + // then draw + xrange.from = xrange.axis.p2c(xrange.from); + xrange.to = xrange.axis.p2c(xrange.to); + yrange.from = yrange.axis.p2c(yrange.from); + yrange.to = yrange.axis.p2c(yrange.to); + + if (xrange.from == xrange.to || yrange.from == yrange.to) { + // draw line + ctx.beginPath(); + ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; + ctx.moveTo(xrange.from, yrange.from); + ctx.lineTo(xrange.to, yrange.to); + ctx.stroke(); + } + else { + // fill area + ctx.fillStyle = m.color || options.grid.markingsColor; + ctx.fillRect(xrange.from, yrange.to, + xrange.to - xrange.from, + yrange.from - yrange.to); + } + } + } + + // draw the ticks + axes = allAxes(); + bw = options.grid.borderWidth; + + for (var j = 0; j < axes.length; ++j) { + var axis = axes[j], box = axis.box, + t = axis.tickLength, x, y, xoff, yoff; + if (!axis.show || axis.ticks.length == 0) + continue; + + ctx.lineWidth = 1; + + // find the edges + if (axis.direction == "x") { + x = 0; + if (t == "full") + y = (axis.position == "top" ? 0 : plotHeight); + else + y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); + } + else { + y = 0; + if (t == "full") + x = (axis.position == "left" ? 0 : plotWidth); + else + x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); + } + + // draw tick bar + if (!axis.innermost) { + ctx.strokeStyle = axis.options.color; + ctx.beginPath(); + xoff = yoff = 0; + if (axis.direction == "x") + xoff = plotWidth + 1; + else + yoff = plotHeight + 1; + + if (ctx.lineWidth == 1) { + if (axis.direction == "x") { + y = Math.floor(y) + 0.5; + } else { + x = Math.floor(x) + 0.5; + } + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + ctx.stroke(); + } + + // draw ticks + + ctx.strokeStyle = axis.options.tickColor; + + ctx.beginPath(); + for (i = 0; i < axis.ticks.length; ++i) { + var v = axis.ticks[i].v; + + xoff = yoff = 0; + + if (isNaN(v) || v < axis.min || v > axis.max + // skip those lying on the axes if we got a border + || (t == "full" + && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) + && (v == axis.min || v == axis.max))) + continue; + + if (axis.direction == "x") { + x = axis.p2c(v); + yoff = t == "full" ? -plotHeight : t; + + if (axis.position == "top") + yoff = -yoff; + } + else { + y = axis.p2c(v); + xoff = t == "full" ? -plotWidth : t; + + if (axis.position == "left") + xoff = -xoff; + } + + if (ctx.lineWidth == 1) { + if (axis.direction == "x") + x = Math.floor(x) + 0.5; + else + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + } + + ctx.stroke(); + } + + + // draw border + if (bw) { + // If either borderWidth or borderColor is an object, then draw the border + // line by line instead of as one rectangle + bc = options.grid.borderColor; + if(typeof bw == "object" || typeof bc == "object") { + if (typeof bw !== "object") { + bw = {top: bw, right: bw, bottom: bw, left: bw}; + } + if (typeof bc !== "object") { + bc = {top: bc, right: bc, bottom: bc, left: bc}; + } + + if (bw.top > 0) { + ctx.strokeStyle = bc.top; + ctx.lineWidth = bw.top; + ctx.beginPath(); + ctx.moveTo(0 - bw.left, 0 - bw.top/2); + ctx.lineTo(plotWidth, 0 - bw.top/2); + ctx.stroke(); + } + + if (bw.right > 0) { + ctx.strokeStyle = bc.right; + ctx.lineWidth = bw.right; + ctx.beginPath(); + ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); + ctx.lineTo(plotWidth + bw.right / 2, plotHeight); + ctx.stroke(); + } + + if (bw.bottom > 0) { + ctx.strokeStyle = bc.bottom; + ctx.lineWidth = bw.bottom; + ctx.beginPath(); + ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); + ctx.lineTo(0, plotHeight + bw.bottom / 2); + ctx.stroke(); + } + + if (bw.left > 0) { + ctx.strokeStyle = bc.left; + ctx.lineWidth = bw.left; + ctx.beginPath(); + ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); + ctx.lineTo(0- bw.left/2, 0); + ctx.stroke(); + } + } + else { + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + } + } + + ctx.restore(); + } + + function drawAxisLabels() { + + $.each(allAxes(), function (_, axis) { + var box = axis.box, + legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, + font = axis.options.font || "flot-tick-label tickLabel", + tick, x, y, halign, valign; + + // Remove text before checking for axis.show and ticks.length; + // otherwise plugins, like flot-tickrotor, that draw their own + // tick labels will end up with both theirs and the defaults. + + surface.removeText(layer); + + if (!axis.show || axis.ticks.length == 0) + return; + + for (var i = 0; i < axis.ticks.length; ++i) { + + tick = axis.ticks[i]; + if (!tick.label || tick.v < axis.min || tick.v > axis.max) + continue; + + if (axis.direction == "x") { + halign = "center"; + x = plotOffset.left + axis.p2c(tick.v); + if (axis.position == "bottom") { + y = box.top + box.padding; + } else { + y = box.top + box.height - box.padding; + valign = "bottom"; + } + } else { + valign = "middle"; + y = plotOffset.top + axis.p2c(tick.v); + if (axis.position == "left") { + x = box.left + box.width - box.padding; + halign = "right"; + } else { + x = box.left + box.padding; + } + } + + surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); + } + }); + } + + function drawSeries(series) { + if (series.lines.show) + drawSeriesLines(series); + if (series.bars.show) + drawSeriesBars(series); + if (series.points.show) + drawSeriesPoints(series); + } + + function drawSeriesLines(series) { + function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + prevx = null, prevy = null; + + ctx.beginPath(); + for (var i = ps; i < points.length; i += ps) { + var x1 = points[i - ps], y1 = points[i - ps + 1], + x2 = points[i], y2 = points[i + 1]; + + if (x1 == null || x2 == null) + continue; + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min) { + if (y2 < axisy.min) + continue; // line segment is outside + // compute new intersection point + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min) { + if (y1 < axisy.min) + continue; + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max) { + if (y2 > axisy.max) + continue; + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max) { + if (y1 > axisy.max) + continue; + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (x1 != prevx || y1 != prevy) + ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); + + prevx = x2; + prevy = y2; + ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); + } + ctx.stroke(); + } + + function plotLineArea(datapoints, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + bottom = Math.min(Math.max(0, axisy.min), axisy.max), + i = 0, top, areaOpen = false, + ypos = 1, segmentStart = 0, segmentEnd = 0; + + // we process each segment in two turns, first forward + // direction to sketch out top, then once we hit the + // end we go backwards to sketch the bottom + while (true) { + if (ps > 0 && i > points.length + ps) + break; + + i += ps; // ps is negative if going backwards + + var x1 = points[i - ps], + y1 = points[i - ps + ypos], + x2 = points[i], y2 = points[i + ypos]; + + if (areaOpen) { + if (ps > 0 && x1 != null && x2 == null) { + // at turning point + segmentEnd = i; + ps = -ps; + ypos = 2; + continue; + } + + if (ps < 0 && i == segmentStart + ps) { + // done with the reverse sweep + ctx.fill(); + areaOpen = false; + ps = -ps; + ypos = 1; + i = segmentStart = segmentEnd + ps; + continue; + } + } + + if (x1 == null || x2 == null) + continue; + + // clip x values + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (!areaOpen) { + // open area + ctx.beginPath(); + ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); + areaOpen = true; + } + + // now first check the case where both is outside + if (y1 >= axisy.max && y2 >= axisy.max) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); + continue; + } + else if (y1 <= axisy.min && y2 <= axisy.min) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); + continue; + } + + // else it's a bit more complicated, there might + // be a flat maxed out rectangle first, then a + // triangular cutout or reverse; to find these + // keep track of the current x values + var x1old = x1, x2old = x2; + + // clip the y values, without shortcutting, we + // go through all cases in turn + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // if the x value was changed we got a rectangle + // to fill + if (x1 != x1old) { + ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); + // it goes to (x1, y1), but we fill that below + } + + // fill triangular section, this sometimes result + // in redundant points if (x1, y1) hasn't changed + // from previous line to, but we just ignore that + ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + + // fill the other rectangle if it's there + if (x2 != x2old) { + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); + } + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + ctx.lineJoin = "round"; + + var lw = series.lines.lineWidth, + sw = series.shadowSize; + // FIXME: consider another form of shadow when filling is turned on + if (lw > 0 && sw > 0) { + // draw shadow as a thick and thin line with transparency + ctx.lineWidth = sw; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + // position shadow at angle from the mid of line + var angle = Math.PI/18; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); + ctx.lineWidth = sw/2; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); + if (fillStyle) { + ctx.fillStyle = fillStyle; + plotLineArea(series.datapoints, series.xaxis, series.yaxis); + } + + if (lw > 0) + plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); + ctx.restore(); + } + + function drawSeriesPoints(series) { + function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + var x = points[i], y = points[i + 1]; + if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + continue; + + ctx.beginPath(); + x = axisx.p2c(x); + y = axisy.p2c(y) + offset; + if (symbol == "circle") + ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); + else + symbol(ctx, x, y, radius, shadow); + ctx.closePath(); + + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + ctx.stroke(); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var lw = series.points.lineWidth, + sw = series.shadowSize, + radius = series.points.radius, + symbol = series.points.symbol; + + // If the user sets the line width to 0, we change it to a very + // small value. A line width of 0 seems to force the default of 1. + // Doing the conditional here allows the shadow setting to still be + // optional even with a lineWidth of 0. + + if( lw == 0 ) + lw = 0.0001; + + if (lw > 0 && sw > 0) { + // draw shadow in two steps + var w = sw / 2; + ctx.lineWidth = w; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + plotPoints(series.datapoints, radius, null, w + w/2, true, + series.xaxis, series.yaxis, symbol); + + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + plotPoints(series.datapoints, radius, null, w/2, true, + series.xaxis, series.yaxis, symbol); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + plotPoints(series.datapoints, radius, + getFillStyle(series.points, series.color), 0, false, + series.xaxis, series.yaxis, symbol); + ctx.restore(); + } + + function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { + var left, right, bottom, top, + drawLeft, drawRight, drawTop, drawBottom, + tmp; + + // in horizontal mode, we start the bar from the left + // instead of from the bottom so it appears to be + // horizontal rather than vertical + if (horizontal) { + drawBottom = drawRight = drawTop = true; + drawLeft = false; + left = b; + right = x; + top = y + barLeft; + bottom = y + barRight; + + // account for negative bars + if (right < left) { + tmp = right; + right = left; + left = tmp; + drawLeft = true; + drawRight = false; + } + } + else { + drawLeft = drawRight = drawTop = true; + drawBottom = false; + left = x + barLeft; + right = x + barRight; + bottom = b; + top = y; + + // account for negative bars + if (top < bottom) { + tmp = top; + top = bottom; + bottom = tmp; + drawBottom = true; + drawTop = false; + } + } + + // clip + if (right < axisx.min || left > axisx.max || + top < axisy.min || bottom > axisy.max) + return; + + if (left < axisx.min) { + left = axisx.min; + drawLeft = false; + } + + if (right > axisx.max) { + right = axisx.max; + drawRight = false; + } + + if (bottom < axisy.min) { + bottom = axisy.min; + drawBottom = false; + } + + if (top > axisy.max) { + top = axisy.max; + drawTop = false; + } + + left = axisx.p2c(left); + bottom = axisy.p2c(bottom); + right = axisx.p2c(right); + top = axisy.p2c(top); + + // fill the bar + if (fillStyleCallback) { + c.fillStyle = fillStyleCallback(bottom, top); + c.fillRect(left, top, right - left, bottom - top) + } + + // draw outline + if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { + c.beginPath(); + + // FIXME: inline moveTo is buggy with excanvas + c.moveTo(left, bottom); + if (drawLeft) + c.lineTo(left, top); + else + c.moveTo(left, top); + if (drawTop) + c.lineTo(right, top); + else + c.moveTo(right, top); + if (drawRight) + c.lineTo(right, bottom); + else + c.moveTo(right, bottom); + if (drawBottom) + c.lineTo(left, bottom); + else + c.moveTo(left, bottom); + c.stroke(); + } + } + + function drawSeriesBars(series) { + function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + if (points[i] == null) + continue; + drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // FIXME: figure out a way to add shadows (for instance along the right edge) + ctx.lineWidth = series.bars.lineWidth; + ctx.strokeStyle = series.color; + + var barLeft; + + switch (series.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -series.bars.barWidth; + break; + default: + barLeft = -series.bars.barWidth / 2; + } + + var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; + plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); + ctx.restore(); + } + + function getFillStyle(filloptions, seriesColor, bottom, top) { + var fill = filloptions.fill; + if (!fill) + return null; + + if (filloptions.fillColor) + return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); + + var c = $.color.parse(seriesColor); + c.a = typeof fill == "number" ? fill : 0.4; + c.normalize(); + return c.toString(); + } + + function insertLegend() { + + placeholder.find(".legend").remove(); + + if (!options.legend.show) + return; + + var fragments = [], entries = [], rowStarted = false, + lf = options.legend.labelFormatter, s, label; + + // Build a list of legend entries, with each having a label and a color + + for (var i = 0; i < series.length; ++i) { + s = series[i]; + if (s.label) { + label = lf ? lf(s.label, s) : s.label; + if (label) { + entries.push({ + label: label, + color: s.color + }); + } + } + } + + // Sort the legend using either the default or a custom comparator + + if (options.legend.sorted) { + if ($.isFunction(options.legend.sorted)) { + entries.sort(options.legend.sorted); + } else if (options.legend.sorted == "reverse") { + entries.reverse(); + } else { + var ascending = options.legend.sorted != "descending"; + entries.sort(function(a, b) { + return a.label == b.label ? 0 : ( + (a.label < b.label) != ascending ? 1 : -1 // Logical XOR + ); + }); + } + } + + // Generate markup for the list of entries, in their final order + + for (var i = 0; i < entries.length; ++i) { + + var entry = entries[i]; + + if (i % options.legend.noColumns == 0) { + if (rowStarted) + fragments.push(''); + fragments.push('

      '); + rowStarted = true; + } + + fragments.push( + '' + + '' + ); + } + + if (rowStarted) + fragments.push(''); + + if (fragments.length == 0) + return; + + var table = '
      ' + entry.label + '
      ' + fragments.join("") + '
      '; + if (options.legend.container != null) + $(options.legend.container).html(table); + else { + var pos = "", + p = options.legend.position, + m = options.legend.margin; + if (m[0] == null) + m = [m, m]; + if (p.charAt(0) == "n") + pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; + else if (p.charAt(0) == "s") + pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; + if (p.charAt(1) == "e") + pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; + else if (p.charAt(1) == "w") + pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; + var legend = $('
      ' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
      ').appendTo(placeholder); + if (options.legend.backgroundOpacity != 0.0) { + // put in the transparent background + // separately to avoid blended labels and + // label boxes + var c = options.legend.backgroundColor; + if (c == null) { + c = options.grid.backgroundColor; + if (c && typeof c == "string") + c = $.color.parse(c); + else + c = $.color.extract(legend, 'background-color'); + c.a = 1; + c = c.toString(); + } + var div = legend.children(); + $('
      ').prependTo(legend).css('opacity', options.legend.backgroundOpacity); + } + } + } + + + // interactive features + + var highlights = [], + redrawTimeout = null; + + // returns the data item the mouse is over, or null if none is found + function findNearbyItem(mouseX, mouseY, seriesFilter) { + var maxDistance = options.grid.mouseActiveRadius, + smallestDistance = maxDistance * maxDistance + 1, + item = null, foundPoint = false, i, j, ps; + + for (i = series.length - 1; i >= 0; --i) { + if (!seriesFilter(series[i])) + continue; + + var s = series[i], + axisx = s.xaxis, + axisy = s.yaxis, + points = s.datapoints.points, + mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster + my = axisy.c2p(mouseY), + maxx = maxDistance / axisx.scale, + maxy = maxDistance / axisy.scale; + + ps = s.datapoints.pointsize; + // with inverse transforms, we can't use the maxx/maxy + // optimization, sadly + if (axisx.options.inverseTransform) + maxx = Number.MAX_VALUE; + if (axisy.options.inverseTransform) + maxy = Number.MAX_VALUE; + + if (s.lines.show || s.points.show) { + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1]; + if (x == null) + continue; + + // For points and lines, the cursor must be within a + // certain distance to the data point + if (x - mx > maxx || x - mx < -maxx || + y - my > maxy || y - my < -maxy) + continue; + + // We have to calculate distances in pixels, not in + // data units, because the scales of the axes may be different + var dx = Math.abs(axisx.p2c(x) - mouseX), + dy = Math.abs(axisy.p2c(y) - mouseY), + dist = dx * dx + dy * dy; // we save the sqrt + + // use <= to ensure last point takes precedence + // (last generally means on top of) + if (dist < smallestDistance) { + smallestDistance = dist; + item = [i, j / ps]; + } + } + } + + if (s.bars.show && !item) { // no other point can be nearby + + var barLeft, barRight; + + switch (s.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -s.bars.barWidth; + break; + default: + barLeft = -s.bars.barWidth / 2; + } + + barRight = barLeft + s.bars.barWidth; + + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1], b = points[j + 2]; + if (x == null) + continue; + + // for a bar graph, the cursor must be inside the bar + if (series[i].bars.horizontal ? + (mx <= Math.max(b, x) && mx >= Math.min(b, x) && + my >= y + barLeft && my <= y + barRight) : + (mx >= x + barLeft && mx <= x + barRight && + my >= Math.min(b, y) && my <= Math.max(b, y))) + item = [i, j / ps]; + } + } + } + + if (item) { + i = item[0]; + j = item[1]; + ps = series[i].datapoints.pointsize; + + return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), + dataIndex: j, + series: series[i], + seriesIndex: i }; + } + + return null; + } + + function onMouseMove(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return s["hoverable"] != false; }); + } + + function onMouseLeave(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return false; }); + } + + function onClick(e) { + triggerClickHoverEvent("plotclick", e, + function (s) { return s["clickable"] != false; }); + } + + // trigger click or hover event (they send the same parameters + // so we share their code) + function triggerClickHoverEvent(eventname, event, seriesFilter) { + var offset = eventHolder.offset(), + canvasX = event.pageX - offset.left - plotOffset.left, + canvasY = event.pageY - offset.top - plotOffset.top, + pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); + + pos.pageX = event.pageX; + pos.pageY = event.pageY; + + var item = findNearbyItem(canvasX, canvasY, seriesFilter); + + if (item) { + // fill in mouse pos for any listeners out there + item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); + item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); + } + + if (options.grid.autoHighlight) { + // clear auto-highlights + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.auto == eventname && + !(item && h.series == item.series && + h.point[0] == item.datapoint[0] && + h.point[1] == item.datapoint[1])) + unhighlight(h.series, h.point); + } + + if (item) + highlight(item.series, item.datapoint, eventname); + } + + placeholder.trigger(eventname, [ pos, item ]); + } + + function triggerRedrawOverlay() { + var t = options.interaction.redrawOverlayInterval; + if (t == -1) { // skip event queue + drawOverlay(); + return; + } + + if (!redrawTimeout) + redrawTimeout = setTimeout(drawOverlay, t); + } + + function drawOverlay() { + redrawTimeout = null; + + // draw highlights + octx.save(); + overlay.clear(); + octx.translate(plotOffset.left, plotOffset.top); + + var i, hi; + for (i = 0; i < highlights.length; ++i) { + hi = highlights[i]; + + if (hi.series.bars.show) + drawBarHighlight(hi.series, hi.point); + else + drawPointHighlight(hi.series, hi.point); + } + octx.restore(); + + executeHooks(hooks.drawOverlay, [octx]); + } + + function highlight(s, point, auto) { + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") { + var ps = s.datapoints.pointsize; + point = s.datapoints.points.slice(ps * point, ps * (point + 1)); + } + + var i = indexOfHighlight(s, point); + if (i == -1) { + highlights.push({ series: s, point: point, auto: auto }); + + triggerRedrawOverlay(); + } + else if (!auto) + highlights[i].auto = false; + } + + function unhighlight(s, point) { + if (s == null && point == null) { + highlights = []; + triggerRedrawOverlay(); + return; + } + + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") { + var ps = s.datapoints.pointsize; + point = s.datapoints.points.slice(ps * point, ps * (point + 1)); + } + + var i = indexOfHighlight(s, point); + if (i != -1) { + highlights.splice(i, 1); + + triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s, p) { + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.series == s && h.point[0] == p[0] + && h.point[1] == p[1]) + return i; + } + return -1; + } + + function drawPointHighlight(series, point) { + var x = point[0], y = point[1], + axisx = series.xaxis, axisy = series.yaxis, + highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); + + if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + return; + + var pointRadius = series.points.radius + series.points.lineWidth / 2; + octx.lineWidth = pointRadius; + octx.strokeStyle = highlightColor; + var radius = 1.5 * pointRadius; + x = axisx.p2c(x); + y = axisy.p2c(y); + + octx.beginPath(); + if (series.points.symbol == "circle") + octx.arc(x, y, radius, 0, 2 * Math.PI, false); + else + series.points.symbol(octx, x, y, radius, false); + octx.closePath(); + octx.stroke(); + } + + function drawBarHighlight(series, point) { + var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), + fillStyle = highlightColor, + barLeft; + + switch (series.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -series.bars.barWidth; + break; + default: + barLeft = -series.bars.barWidth / 2; + } + + octx.lineWidth = series.bars.lineWidth; + octx.strokeStyle = highlightColor; + + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, + function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); + } + + function getColorOrGradient(spec, bottom, top, defaultColor) { + if (typeof spec == "string") + return spec; + else { + // assume this is a gradient spec; IE currently only + // supports a simple vertical gradient properly, so that's + // what we support too + var gradient = ctx.createLinearGradient(0, top, 0, bottom); + + for (var i = 0, l = spec.colors.length; i < l; ++i) { + var c = spec.colors[i]; + if (typeof c != "string") { + var co = $.color.parse(defaultColor); + if (c.brightness != null) + co = co.scale('rgb', c.brightness); + if (c.opacity != null) + co.a *= c.opacity; + c = co.toString(); + } + gradient.addColorStop(i / (l - 1), c); + } + + return gradient; + } + } + } + + // Add the plot function to the top level of the jQuery object + + $.plot = function(placeholder, data, options) { + //var t0 = new Date(); + var plot = new Plot($(placeholder), data, options, $.plot.plugins); + //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); + return plot; + }; + + $.plot.version = "0.8.2-alpha"; + + $.plot.plugins = []; + + // Also add the plot function as a chainable property + + $.fn.plot = function(data, options) { + return this.each(function() { + $.plot(this, data, options); + }); + }; + + // round to nearby lower multiple of base + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + +})(jQuery); diff --git a/app/assets/javascripts/jquery.flot.tickrotor.js b/app/assets/javascripts/jquery.flot.tickrotor.js new file mode 100644 index 000000000..404b2b0a7 --- /dev/null +++ b/app/assets/javascripts/jquery.flot.tickrotor.js @@ -0,0 +1,205 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is flot-tickrotor. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mark Cote + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * flot-tickrotor: flot plugin to display angled X-axis tick labels. + * + * Requires flot 0.7 or higher and a browser supporting . + * + * To activate, just set xaxis.rotateTicks to an angle in degrees. Labels + * are rotated clockwise, so if you want the labels to angle up and to the right (/) + * you need to provide an angle > 90. The text will be flipped so that it is still + * right-side-up. + * Angles greater than or equal to 180 are ignored. + */ +(function ($) { + var options = { }; + + function init(plot) { + // Taken from flot-axislabels. + // This is kind of a hack. There are no hooks in Flot between + // the creation and measuring of the ticks (setTicks, measureTickLabels + // in setupGrid() ) and the drawing of the ticks and plot box + // (insertAxisLabels in setupGrid() ). + // + // Therefore, we use a trick where we run the draw routine twice: + // the first time to get the tick measurements, so that we can change + // them, and then have it draw it again. + var ticks = []; + var font; + var secondPass = false; + var rotateTicks, rotateTicksRads, radsAboveHoriz; + plot.hooks.draw.push(function (plot, ctx) { + if (!secondPass) { + var opts = plot.getAxes().xaxis.options; + if (opts.rotateTicks === undefined) { + return; + } + + rotateTicks = parseInt(opts.rotateTicks, 10); + if (rotateTicks.toString() != opts.rotateTicks || rotateTicks == 0 || rotateTicks >= 180) { + return; + } + + rotateTicksRads = rotateTicks * Math.PI/180; + if (rotateTicks > 90) { + radsAboveHoriz = Math.PI - rotateTicksRads; + } else { + radsAboveHoriz = Math.PI/2 - rotateTicksRads; + } + + font = opts.rotateTicksFont; + if (!font) { + font = $('.tickLabel').css('font'); + } + if (!font) { + font = 'smaller sans-serif'; + } + + var elem, maxLabelWidth = 0, maxLabelHeight = 0, minX = 0, maxX = 0; + + var xaxis = plot.getAxes().xaxis; + ticks = plot.getAxes().xaxis.ticks; + opts.ticks = []; // we'll make our own + + var x; + for (var i = 0; i < ticks.length; i++) { + elem = $('' + ticks[i].label + ''); + plot.getPlaceholder().append(elem); + ticks[i].height = elem.outerHeight(true); + ticks[i].width = elem.outerWidth(true); + elem.remove(); + if (ticks[i].height > maxLabelHeight) { + maxLabelHeight = ticks[i].height; + } + if (ticks[i].width > maxLabelWidth) { + maxLabelWidth = ticks[i].width; + } + var tick = ticks[i]; + // See second-draw code below for explanation of offsets. + if (rotateTicks > 90) { + // See if any labels are too long and require increased left + // padding. + x = Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) + - Math.ceil(Math.cos(radsAboveHoriz) * tick.height) + - Math.ceil(Math.cos(radsAboveHoriz) * tick.width); + if (x < minX) { + minX = x; + } + } else { + // See if any labels are too long and require increased right + // padding. + x = Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) + + Math.ceil(Math.cos(radsAboveHoriz) * tick.height) + + Math.ceil(Math.cos(radsAboveHoriz) * tick.width); + if (x > maxX) { + maxX = x; + } + } + } + + // Calculate maximum label height after rotating. + if (rotateTicks > 90) { + var acuteRads = rotateTicksRads - Math.PI/2; + opts.labelHeight = Math.ceil(Math.sin(acuteRads) * maxLabelWidth) + + Math.ceil(Math.sin(acuteRads) * maxLabelHeight); + } else { + var acuteRads = Math.PI/2 - rotateTicksRads; + // Center such that the top of the label is at the center of the tick. + opts.labelHeight = Math.ceil(Math.sin(rotateTicksRads) * maxLabelWidth) + + Math.ceil(Math.sin(acuteRads) * maxLabelHeight); + } + + if (minX < 0) { + plot.getAxes().yaxis.options.labelWidth = -1 * minX; + } + + // Doesn't seem to work if there are no values using the second y axis. + //if (maxX > xaxis.box.left + xaxis.box.width) { + // plot.getAxes().y2axis.options.labelWidth = maxX - xaxis.box.left - xaxis.box.width; + //} + + // re-draw with new label widths and heights + secondPass = true; + plot.setupGrid(); + plot.draw(); + } else { + if (ticks.length == 0) { + return; + } + var xaxis = plot.getAxes().xaxis; + var box = xaxis.box; + var tick, label, xoffset, yoffset; + for (var i = 0; i < ticks.length; i++) { + tick = ticks[i]; + if (!tick.label) { + continue; + } + ctx.save(); + ctx.font = font; + if (rotateTicks <= 90) { + // Center such that the top of the label is at the center of the tick. + xoffset = -Math.ceil(Math.cos(radsAboveHoriz) * tick.height); + yoffset = Math.ceil(Math.sin(radsAboveHoriz) * tick.height); + ctx.translate(Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) + xoffset, + box.top + box.padding + plot.getOptions().grid.labelMargin + yoffset); + ctx.rotate(rotateTicksRads); + } else { + // We want the text to facing up, so we have to rotate counterclockwise, + // which means the label has to *end* at the center of the tick. + xoffset = Math.ceil(Math.cos(radsAboveHoriz) * tick.height) + - Math.ceil(Math.cos(radsAboveHoriz) * tick.width); + yoffset = Math.ceil(Math.sin(radsAboveHoriz) * tick.width) + + Math.ceil(Math.sin(radsAboveHoriz) * tick.height); + ctx.translate(Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v) + xoffset), + box.top + box.padding + plot.getOptions().grid.labelMargin + yoffset); + ctx.rotate(-radsAboveHoriz); + } + ctx.fillText(tick.label, 0, 0); + ctx.restore(); + } + } + }); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'tickRotor', + version: '1.0' + }); +})(jQuery); diff --git a/app/assets/javascripts/jquery.flot.tickrotor.min.js b/app/assets/javascripts/jquery.flot.tickrotor.min.js new file mode 100644 index 000000000..87fc79d10 --- /dev/null +++ b/app/assets/javascripts/jquery.flot.tickrotor.min.js @@ -0,0 +1 @@ +(function($){var options={};function init(plot){var ticks=[];var font;var secondPass=false;var rotateTicks,rotateTicksRads,radsAboveHoriz;plot.hooks.draw.push(function(plot,ctx){if(!secondPass){var opts=plot.getAxes().xaxis.options;if(opts.rotateTicks===undefined){return}rotateTicks=parseInt(opts.rotateTicks,10);if(rotateTicks.toString()!=opts.rotateTicks||rotateTicks==0||rotateTicks>=180){return}rotateTicksRads=rotateTicks*Math.PI/180;if(rotateTicks>90){radsAboveHoriz=Math.PI-rotateTicksRads}else{radsAboveHoriz=Math.PI/2-rotateTicksRads}font=opts.rotateTicksFont;if(!font){font=$(".tickLabel").css("font")}if(!font){font="smaller sans-serif"}var elem,maxLabelWidth=0,maxLabelHeight=0,minX=0,maxX=0;var xaxis=plot.getAxes().xaxis;ticks=plot.getAxes().xaxis.ticks;opts.ticks=[];var x;for(var i=0;i'+ticks[i].label+"");plot.getPlaceholder().append(elem);ticks[i].height=elem.outerHeight(true);ticks[i].width=elem.outerWidth(true);elem.remove();if(ticks[i].height>maxLabelHeight){maxLabelHeight=ticks[i].height}if(ticks[i].width>maxLabelWidth){maxLabelWidth=ticks[i].width}var tick=ticks[i];if(rotateTicks>90){x=Math.round(plot.getPlotOffset().left+xaxis.p2c(tick.v))-Math.ceil(Math.cos(radsAboveHoriz)*tick.height)-Math.ceil(Math.cos(radsAboveHoriz)*tick.width);if(xmaxX){maxX=x}}}if(rotateTicks>90){var acuteRads=rotateTicksRads-Math.PI/2;opts.labelHeight=Math.ceil(Math.sin(acuteRads)*maxLabelWidth)+Math.ceil(Math.sin(acuteRads)*maxLabelHeight)}else{var acuteRads=Math.PI/2-rotateTicksRads;opts.labelHeight=Math.ceil(Math.sin(rotateTicksRads)*maxLabelWidth)+Math.ceil(Math.sin(acuteRads)*maxLabelHeight)}if(minX<0){plot.getAxes().yaxis.options.labelWidth=-1*minX}secondPass=true;plot.setupGrid();plot.draw()}else{if(ticks.length==0){return}var xaxis=plot.getAxes().xaxis;var box=xaxis.box;var tick,label,xoffset,yoffset;for(var i=0;i'+this.opts.axisLabel+"
      ");this.plot.getPlaceholder().append(elem);this.labelWidth=elem.outerWidth(true);this.labelHeight=elem.outerHeight(true);elem.remove();this.width=this.height=0;if(this.position=="left"||this.position=="right"){this.width=this.labelWidth+this.padding}else{this.height=this.labelHeight+this.padding}};HtmlAxisLabel.prototype["delete"]=function(){if(this.elem){this.elem.remove()}};HtmlAxisLabel.prototype.draw=function(box){this.plot.getPlaceholder().find("#"+this.axisName+"Label").remove();this.elem=$('
      '+this.opts.axisLabel+"
      ");this.plot.getPlaceholder().append(this.elem);if(this.position=="top"){this.elem.css("left",box.left+box.width/2-this.labelWidth/2+"px");this.elem.css("top",box.top+"px")}else{if(this.position=="bottom"){this.elem.css("left",box.left+box.width/2-this.labelWidth/2+"px");this.elem.css("top",box.top+box.height-this.labelHeight+"px")}else{if(this.position=="left"){this.elem.css("top",box.top+box.height/2-this.labelHeight/2+"px");this.elem.css("left",box.left+"px")}else{if(this.position=="right"){this.elem.css("top",box.top+box.height/2-this.labelHeight/2+"px");this.elem.css("left",box.left+box.width-this.labelWidth+"px")}}}}};CssTransformAxisLabel.prototype=new HtmlAxisLabel();CssTransformAxisLabel.prototype.constructor=CssTransformAxisLabel;function CssTransformAxisLabel(axisName,position,padding,plot,opts){HtmlAxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts)}CssTransformAxisLabel.prototype.calculateSize=function(){HtmlAxisLabel.prototype.calculateSize.call(this);this.width=this.height=0;if(this.position=="left"||this.position=="right"){this.width=this.labelHeight+this.padding}else{this.height=this.labelHeight+this.padding}};CssTransformAxisLabel.prototype.transforms=function(degrees,x,y){var stransforms={"-moz-transform":"","-webkit-transform":"","-o-transform":"","-ms-transform":""};if(x!=0||y!=0){var stdTranslate=" translate("+x+"px, "+y+"px)";stransforms["-moz-transform"]+=stdTranslate;stransforms["-webkit-transform"]+=stdTranslate;stransforms["-o-transform"]+=stdTranslate;stransforms["-ms-transform"]+=stdTranslate}if(degrees!=0){var rotation=degrees/90;var stdRotate=" rotate("+degrees+"deg)";stransforms["-moz-transform"]+=stdRotate;stransforms["-webkit-transform"]+=stdRotate;stransforms["-o-transform"]+=stdRotate;stransforms["-ms-transform"]+=stdRotate}var s="top: 0; left: 0; ";for(var prop in stransforms){if(stransforms[prop]){s+=prop+":"+stransforms[prop]+";"}}s+=";";return s};CssTransformAxisLabel.prototype.calculateOffsets=function(box){var offsets={x:0,y:0,degrees:0};if(this.position=="bottom"){offsets.x=box.left+box.width/2-this.labelWidth/2;offsets.y=box.top+box.height-this.labelHeight}else{if(this.position=="top"){offsets.x=box.left+box.width/2-this.labelWidth/2;offsets.y=box.top}else{if(this.position=="left"){offsets.degrees=-90;offsets.x=box.left-this.labelWidth/2+this.labelHeight/2;offsets.y=box.height/2+box.top}else{if(this.position=="right"){offsets.degrees=90;offsets.x=box.left+box.width-this.labelWidth/2-this.labelHeight/2;offsets.y=box.height/2+box.top}}}}return offsets};CssTransformAxisLabel.prototype.draw=function(box){this.plot.getPlaceholder().find("."+this.axisName+"Label").remove();var offsets=this.calculateOffsets(box);this.elem=$('
      '+this.opts.axisLabel+"
      ");this.plot.getPlaceholder().append(this.elem)};IeTransformAxisLabel.prototype=new CssTransformAxisLabel();IeTransformAxisLabel.prototype.constructor=IeTransformAxisLabel;function IeTransformAxisLabel(axisName,position,padding,plot,opts){CssTransformAxisLabel.prototype.constructor.call(this,axisName,position,padding,plot,opts);this.requiresResize=false}IeTransformAxisLabel.prototype.transforms=function(degrees,x,y){var s="";if(degrees!=0){var rotation=degrees/90;while(rotation<0){rotation+=4}s+=" filter: progid:DXImageTransform.Microsoft.BasicImage(rotation="+rotation+"); ";this.requiresResize=(this.position=="right")}if(x!=0){s+="left: "+x+"px; "}if(y!=0){s+="top: "+y+"px; "}return s};IeTransformAxisLabel.prototype.calculateOffsets=function(box){var offsets=CssTransformAxisLabel.prototype.calculateOffsets.call(this,box);if(this.position=="top"){offsets.y=box.top+1}else{if(this.position=="left"){offsets.x=box.left;offsets.y=box.height/2+box.top-this.labelWidth/2}else{if(this.position=="right"){offsets.x=box.left+box.width-this.labelHeight;offsets.y=box.height/2+box.top-this.labelWidth/2}}}return offsets};IeTransformAxisLabel.prototype.draw=function(box){CssTransformAxisLabel.prototype.draw.call(this,box);if(this.requiresResize){this.elem=this.plot.getPlaceholder().find("."+this.axisName+"Label");this.elem.css("width",this.labelWidth);this.elem.css("height",this.labelHeight)}};function init(plot){var secondPass=false;var axisLabels={};var axisOffsetCounts={left:0,right:0,top:0,bottom:0};var defaultPadding=2;plot.hooks.draw.push(function(plot,ctx){var hasAxisLabels=false;if(!secondPass){$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options||plot.getOptions()[axisName];if(axisName in axisLabels){axis.labelHeight=axis.labelHeight-axisLabels[axisName].height;axis.labelWidth=axis.labelWidth-axisLabels[axisName].width;opts.labelHeight=axis.labelHeight;opts.labelWidth=axis.labelWidth;axisLabels[axisName]["delete"]();delete axisLabels[axisName]}if(!opts||!opts.axisLabel||!axis.show){return}hasAxisLabels=true;var renderer=null;if(!opts.axisLabelUseHtml&&navigator.appName=="Microsoft Internet Explorer"){var ua=navigator.userAgent;var re=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");if(re.exec(ua)!=null){rv=parseFloat(RegExp.$1)}if(rv>=9&&!opts.axisLabelUseCanvas&&!opts.axisLabelUseHtml){renderer=CssTransformAxisLabel}else{if(!opts.axisLabelUseCanvas&&!opts.axisLabelUseHtml){renderer=IeTransformAxisLabel}else{if(opts.axisLabelUseCanvas){renderer=CanvasAxisLabel}else{renderer=HtmlAxisLabel}}}}else{if(opts.axisLabelUseHtml||(!css3TransitionSupported()&&!canvasTextSupported())&&!opts.axisLabelUseCanvas){renderer=HtmlAxisLabel}else{if(opts.axisLabelUseCanvas||!css3TransitionSupported()){renderer=CanvasAxisLabel}else{renderer=CssTransformAxisLabel}}}var padding=opts.axisLabelPadding===undefined?defaultPadding:opts.axisLabelPadding;axisLabels[axisName]=new renderer(axisName,axis.position,padding,plot,opts);axisLabels[axisName].calculateSize();opts.labelHeight=axis.labelHeight+axisLabels[axisName].height;opts.labelWidth=axis.labelWidth+axisLabels[axisName].width});if(hasAxisLabels){secondPass=true;plot.setupGrid();plot.draw()}}else{secondPass=false;$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options||plot.getOptions()[axisName];if(!opts||!opts.axisLabel||!axis.show){return}axisLabels[axisName].draw(axis.box)})}})}$.plot.plugins.push({init:init,options:options,name:"axisLabels",version:"2.0b0"})})(jQuery); \ No newline at end of file diff --git a/public/javascripts/jquery.flot.errorbars.js b/public/javascripts/jquery.flot.errorbars.js deleted file mode 100644 index 729843678..000000000 --- a/public/javascripts/jquery.flot.errorbars.js +++ /dev/null @@ -1,353 +0,0 @@ -/* Flot plugin for plotting error bars. - -Copyright (c) 2007-2013 IOLA and Ole Laursen. -Licensed under the MIT license. - -Error bars are used to show standard deviation and other statistical -properties in a plot. - -* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com - -This plugin allows you to plot error-bars over points. Set "errorbars" inside -the points series to the axis name over which there will be error values in -your data array (*even* if you do not intend to plot them later, by setting -"show: null" on xerr/yerr). - -The plugin supports these options: - - series: { - points: { - errorbars: "x" or "y" or "xy", - xerr: { - show: null/false or true, - asymmetric: null/false or true, - upperCap: null or "-" or function, - lowerCap: null or "-" or function, - color: null or color, - radius: null or number - }, - yerr: { same options as xerr } - } - } - -Each data point array is expected to be of the type: - - "x" [ x, y, xerr ] - "y" [ x, y, yerr ] - "xy" [ x, y, xerr, yerr ] - -Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and -equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric -error-bars on X and asymmetric on Y would be: - - [ x, y, xerr, yerr_lower, yerr_upper ] - -By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will -draw a small cap perpendicular to the error bar. They can also be set to a -user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg. - - function drawSemiCircle( ctx, x, y, radius ) { - ctx.beginPath(); - ctx.arc( x, y, radius, 0, Math.PI, false ); - ctx.moveTo( x - radius, y ); - ctx.lineTo( x + radius, y ); - ctx.stroke(); - } - -Color and radius both default to the same ones of the points series if not -set. The independent radius parameter on xerr/yerr is useful for the case when -we may want to add error-bars to a line, without showing the interconnecting -points (with radius: 0), and still showing end caps on the error-bars. -shadowSize and lineWidth are derived as well from the points series. - -*/ - -(function ($) { - var options = { - series: { - points: { - errorbars: null, //should be 'x', 'y' or 'xy' - xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}, - yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null} - } - } - }; - - function processRawData(plot, series, data, datapoints){ - if (!series.points.errorbars) - return; - - // x,y values - var format = [ - { x: true, number: true, required: true }, - { y: true, number: true, required: true } - ]; - - var errors = series.points.errorbars; - // error bars - first X then Y - if (errors == 'x' || errors == 'xy') { - // lower / upper error - if (series.points.xerr.asymmetric) { - format.push({ x: true, number: true, required: true }); - format.push({ x: true, number: true, required: true }); - } else - format.push({ x: true, number: true, required: true }); - } - if (errors == 'y' || errors == 'xy') { - // lower / upper error - if (series.points.yerr.asymmetric) { - format.push({ y: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - } else - format.push({ y: true, number: true, required: true }); - } - datapoints.format = format; - } - - function parseErrors(series, i){ - - var points = series.datapoints.points; - - // read errors from points array - var exl = null, - exu = null, - eyl = null, - eyu = null; - var xerr = series.points.xerr, - yerr = series.points.yerr; - - var eb = series.points.errorbars; - // error bars - first X - if (eb == 'x' || eb == 'xy') { - if (xerr.asymmetric) { - exl = points[i + 2]; - exu = points[i + 3]; - if (eb == 'xy') - if (yerr.asymmetric){ - eyl = points[i + 4]; - eyu = points[i + 5]; - } else eyl = points[i + 4]; - } else { - exl = points[i + 2]; - if (eb == 'xy') - if (yerr.asymmetric) { - eyl = points[i + 3]; - eyu = points[i + 4]; - } else eyl = points[i + 3]; - } - // only Y - } else if (eb == 'y') - if (yerr.asymmetric) { - eyl = points[i + 2]; - eyu = points[i + 3]; - } else eyl = points[i + 2]; - - // symmetric errors? - if (exu == null) exu = exl; - if (eyu == null) eyu = eyl; - - var errRanges = [exl, exu, eyl, eyu]; - // nullify if not showing - if (!xerr.show){ - errRanges[0] = null; - errRanges[1] = null; - } - if (!yerr.show){ - errRanges[2] = null; - errRanges[3] = null; - } - return errRanges; - } - - function drawSeriesErrors(plot, ctx, s){ - - var points = s.datapoints.points, - ps = s.datapoints.pointsize, - ax = [s.xaxis, s.yaxis], - radius = s.points.radius, - err = [s.points.xerr, s.points.yerr]; - - //sanity check, in case some inverted axis hack is applied to flot - var invertX = false; - if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) { - invertX = true; - var tmp = err[0].lowerCap; - err[0].lowerCap = err[0].upperCap; - err[0].upperCap = tmp; - } - - var invertY = false; - if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) { - invertY = true; - var tmp = err[1].lowerCap; - err[1].lowerCap = err[1].upperCap; - err[1].upperCap = tmp; - } - - for (var i = 0; i < s.datapoints.points.length; i += ps) { - - //parse - var errRanges = parseErrors(s, i); - - //cycle xerr & yerr - for (var e = 0; e < err.length; e++){ - - var minmax = [ax[e].min, ax[e].max]; - - //draw this error? - if (errRanges[e * err.length]){ - - //data coordinates - var x = points[i], - y = points[i + 1]; - - //errorbar ranges - var upper = [x, y][e] + errRanges[e * err.length + 1], - lower = [x, y][e] - errRanges[e * err.length]; - - //points outside of the canvas - if (err[e].err == 'x') - if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) - continue; - if (err[e].err == 'y') - if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) - continue; - - // prevent errorbars getting out of the canvas - var drawUpper = true, - drawLower = true; - - if (upper > minmax[1]) { - drawUpper = false; - upper = minmax[1]; - } - if (lower < minmax[0]) { - drawLower = false; - lower = minmax[0]; - } - - //sanity check, in case some inverted axis hack is applied to flot - if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) { - //swap coordinates - var tmp = lower; - lower = upper; - upper = tmp; - tmp = drawLower; - drawLower = drawUpper; - drawUpper = tmp; - tmp = minmax[0]; - minmax[0] = minmax[1]; - minmax[1] = tmp; - } - - // convert to pixels - x = ax[0].p2c(x), - y = ax[1].p2c(y), - upper = ax[e].p2c(upper); - lower = ax[e].p2c(lower); - minmax[0] = ax[e].p2c(minmax[0]); - minmax[1] = ax[e].p2c(minmax[1]); - - //same style as points by default - var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth, - sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize; - - //shadow as for points - if (lw > 0 && sw > 0) { - var w = sw / 2; - ctx.lineWidth = w; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax); - - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax); - } - - ctx.strokeStyle = err[e].color? err[e].color: s.color; - ctx.lineWidth = lw; - //draw it - drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax); - } - } - } - } - - function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){ - - //shadow offset - y += offset; - upper += offset; - lower += offset; - - // error bar - avoid plotting over circles - if (err.err == 'x'){ - if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]); - else drawUpper = false; - if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] ); - else drawLower = false; - } - else { - if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] ); - else drawUpper = false; - if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] ); - else drawLower = false; - } - - //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps - //this is a way to get errorbars on lines without visible connecting dots - radius = err.radius != null? err.radius: radius; - - // upper cap - if (drawUpper) { - if (err.upperCap == '-'){ - if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] ); - else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] ); - } else if ($.isFunction(err.upperCap)){ - if (err.err=='x') err.upperCap(ctx, upper, y, radius); - else err.upperCap(ctx, x, upper, radius); - } - } - // lower cap - if (drawLower) { - if (err.lowerCap == '-'){ - if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] ); - else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] ); - } else if ($.isFunction(err.lowerCap)){ - if (err.err=='x') err.lowerCap(ctx, lower, y, radius); - else err.lowerCap(ctx, x, lower, radius); - } - } - } - - function drawPath(ctx, pts){ - ctx.beginPath(); - ctx.moveTo(pts[0][0], pts[0][1]); - for (var p=1; p < pts.length; p++) - ctx.lineTo(pts[p][0], pts[p][1]); - ctx.stroke(); - } - - function draw(plot, ctx){ - var plotOffset = plot.getPlotOffset(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - $.each(plot.getData(), function (i, s) { - if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) - drawSeriesErrors(plot, ctx, s); - }); - ctx.restore(); - } - - function init(plot) { - plot.hooks.processRawData.push(processRawData); - plot.hooks.draw.push(draw); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'errorbars', - version: '1.0' - }); -})(jQuery); diff --git a/public/javascripts/jquery.flot.js b/public/javascripts/jquery.flot.js deleted file mode 100644 index 2855d2eb3..000000000 --- a/public/javascripts/jquery.flot.js +++ /dev/null @@ -1,3078 +0,0 @@ -/* Javascript plotting library for jQuery, version 0.8.2-alpha. - -Copyright (c) 2007-2013 IOLA and Ole Laursen. -Licensed under the MIT license. - -*/ - -// first an inline dependency, jquery.colorhelpers.js, we inline it here -// for convenience - -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - * var c = $.color.extract($("#mydiv"), 'background-color'); - * console.log(c.r, c.g, c.b, c.a); - * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */ -(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); - -// the actual Flot code -(function($) { - - // Cache the prototype hasOwnProperty for faster access - - var hasOwnProperty = Object.prototype.hasOwnProperty; - - /////////////////////////////////////////////////////////////////////////// - // The Canvas object is a wrapper around an HTML5 tag. - // - // @constructor - // @param {string} cls List of classes to apply to the canvas. - // @param {element} container Element onto which to append the canvas. - // - // Requiring a container is a little iffy, but unfortunately canvas - // operations don't work unless the canvas is attached to the DOM. - - function Canvas(cls, container) { - - var element = container.children("." + cls)[0]; - - if (element == null) { - - element = document.createElement("canvas"); - element.className = cls; - - $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) - .appendTo(container); - - // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas - - if (!element.getContext) { - if (window.G_vmlCanvasManager) { - element = window.G_vmlCanvasManager.initElement(element); - } else { - throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); - } - } - } - - this.element = element; - - var context = this.context = element.getContext("2d"); - - // Determine the screen's ratio of physical to device-independent - // pixels. This is the ratio between the canvas width that the browser - // advertises and the number of pixels actually present in that space. - - // The iPhone 4, for example, has a device-independent width of 320px, - // but its screen is actually 640px wide. It therefore has a pixel - // ratio of 2, while most normal devices have a ratio of 1. - - var devicePixelRatio = window.devicePixelRatio || 1, - backingStoreRatio = - context.webkitBackingStorePixelRatio || - context.mozBackingStorePixelRatio || - context.msBackingStorePixelRatio || - context.oBackingStorePixelRatio || - context.backingStorePixelRatio || 1; - - this.pixelRatio = devicePixelRatio / backingStoreRatio; - - // Size the canvas to match the internal dimensions of its container - - this.resize(container.width(), container.height()); - - // Collection of HTML div layers for text overlaid onto the canvas - - this.textContainer = null; - this.text = {}; - - // Cache of text fragments and metrics, so we can avoid expensively - // re-calculating them when the plot is re-rendered in a loop. - - this._textCache = {}; - } - - // Resizes the canvas to the given dimensions. - // - // @param {number} width New width of the canvas, in pixels. - // @param {number} width New height of the canvas, in pixels. - - Canvas.prototype.resize = function(width, height) { - - if (width <= 0 || height <= 0) { - throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); - } - - var element = this.element, - context = this.context, - pixelRatio = this.pixelRatio; - - // Resize the canvas, increasing its density based on the display's - // pixel ratio; basically giving it more pixels without increasing the - // size of its element, to take advantage of the fact that retina - // displays have that many more pixels in the same advertised space. - - // Resizing should reset the state (excanvas seems to be buggy though) - - if (this.width != width) { - element.width = width * pixelRatio; - element.style.width = width + "px"; - this.width = width; - } - - if (this.height != height) { - element.height = height * pixelRatio; - element.style.height = height + "px"; - this.height = height; - } - - // Save the context, so we can reset in case we get replotted. The - // restore ensure that we're really back at the initial state, and - // should be safe even if we haven't saved the initial state yet. - - context.restore(); - context.save(); - - // Scale the coordinate space to match the display density; so even though we - // may have twice as many pixels, we still want lines and other drawing to - // appear at the same size; the extra pixels will just make them crisper. - - context.scale(pixelRatio, pixelRatio); - }; - - // Clears the entire canvas area, not including any overlaid HTML text - - Canvas.prototype.clear = function() { - this.context.clearRect(0, 0, this.width, this.height); - }; - - // Finishes rendering the canvas, including managing the text overlay. - - Canvas.prototype.render = function() { - - var cache = this._textCache; - - // For each text layer, add elements marked as active that haven't - // already been rendered, and remove those that are no longer active. - - for (var layerKey in cache) { - if (hasOwnProperty.call(cache, layerKey)) { - - var layer = this.getTextLayer(layerKey), - layerCache = cache[layerKey]; - - layer.hide(); - - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - - var positions = styleCache[key].positions; - - for (var i = 0, position; position = positions[i]; i++) { - if (position.active) { - if (!position.rendered) { - layer.append(position.element); - position.rendered = true; - } - } else { - positions.splice(i--, 1); - if (position.rendered) { - position.element.detach(); - } - } - } - - if (positions.length == 0) { - delete styleCache[key]; - } - } - } - } - } - - layer.show(); - } - } - }; - - // Creates (if necessary) and returns the text overlay container. - // - // @param {string} classes String of space-separated CSS classes used to - // uniquely identify the text layer. - // @return {object} The jQuery-wrapped text-layer div. - - Canvas.prototype.getTextLayer = function(classes) { - - var layer = this.text[classes]; - - // Create the text layer if it doesn't exist - - if (layer == null) { - - // Create the text layer container, if it doesn't exist - - if (this.textContainer == null) { - this.textContainer = $("
      ") - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0, - 'font-size': "smaller", - color: "#545454" - }) - .insertAfter(this.element); - } - - layer = this.text[classes] = $("
      ") - .addClass(classes) - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0 - }) - .appendTo(this.textContainer); - } - - return layer; - }; - - // Creates (if necessary) and returns a text info object. - // - // The object looks like this: - // - // { - // width: Width of the text's wrapper div. - // height: Height of the text's wrapper div. - // element: The jQuery-wrapped HTML div containing the text. - // positions: Array of positions at which this text is drawn. - // } - // - // The positions array contains objects that look like this: - // - // { - // active: Flag indicating whether the text should be visible. - // rendered: Flag indicating whether the text is currently visible. - // element: The jQuery-wrapped HTML div containing the text. - // x: X coordinate at which to draw the text. - // y: Y coordinate at which to draw the text. - // } - // - // Each position after the first receives a clone of the original element. - // - // The idea is that that the width, height, and general 'identity' of the - // text is constant no matter where it is placed; the placements are a - // secondary property. - // - // Canvas maintains a cache of recently-used text info objects; getTextInfo - // either returns the cached element or creates a new entry. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {string} text Text string to retrieve info for. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @return {object} a text info object. - - Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { - - var textStyle, layerCache, styleCache, info; - - // Cast the value to a string, in case we were given a number or such - - text = "" + text; - - // If the font is a font-spec object, generate a CSS font definition - - if (typeof font === "object") { - textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; - } else { - textStyle = font; - } - - // Retrieve (or create) the cache for the text's layer and styles - - layerCache = this._textCache[layer]; - - if (layerCache == null) { - layerCache = this._textCache[layer] = {}; - } - - styleCache = layerCache[textStyle]; - - if (styleCache == null) { - styleCache = layerCache[textStyle] = {}; - } - - info = styleCache[text]; - - // If we can't find a matching element in our cache, create a new one - - if (info == null) { - - var element = $("
      ").html(text) - .css({ - position: "absolute", - 'max-width': width, - top: -9999 - }) - .appendTo(this.getTextLayer(layer)); - - if (typeof font === "object") { - element.css({ - font: textStyle, - color: font.color - }); - } else if (typeof font === "string") { - element.addClass(font); - } - - info = styleCache[text] = { - width: element.outerWidth(true), - height: element.outerHeight(true), - element: element, - positions: [] - }; - - element.detach(); - } - - return info; - }; - - // Adds a text string to the canvas text overlay. - // - // The text isn't drawn immediately; it is marked as rendering, which will - // result in its addition to the canvas on the next render pass. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number} x X coordinate at which to draw the text. - // @param {number} y Y coordinate at which to draw the text. - // @param {string} text Text string to draw. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @param {string=} halign Horizontal alignment of the text; either "left", - // "center" or "right". - // @param {string=} valign Vertical alignment of the text; either "top", - // "middle" or "bottom". - - Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { - - var info = this.getTextInfo(layer, text, font, angle, width), - positions = info.positions; - - // Tweak the div's position to match the text's alignment - - if (halign == "center") { - x -= info.width / 2; - } else if (halign == "right") { - x -= info.width; - } - - if (valign == "middle") { - y -= info.height / 2; - } else if (valign == "bottom") { - y -= info.height; - } - - // Determine whether this text already exists at this position. - // If so, mark it for inclusion in the next render pass. - - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = true; - return; - } - } - - // If the text doesn't exist at this position, create a new entry - - // For the very first position we'll re-use the original element, - // while for subsequent ones we'll clone it. - - position = { - active: true, - rendered: false, - element: positions.length ? info.element.clone() : info.element, - x: x, - y: y - }; - - positions.push(position); - - // Move the element to its final position within the container - - position.element.css({ - top: Math.round(y), - left: Math.round(x), - 'text-align': halign // In case the text wraps - }); - }; - - // Removes one or more text strings from the canvas text overlay. - // - // If no parameters are given, all text within the layer is removed. - // - // Note that the text is not immediately removed; it is simply marked as - // inactive, which will result in its removal on the next render pass. - // This avoids the performance penalty for 'clear and redraw' behavior, - // where we potentially get rid of all text on a layer, but will likely - // add back most or all of it later, as when redrawing axes, for example. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number=} x X coordinate of the text. - // @param {number=} y Y coordinate of the text. - // @param {string=} text Text string to remove. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which the text is rotated, in degrees. - // Angle is currently unused, it will be implemented in the future. - - Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { - if (text == null) { - var layerCache = this._textCache[layer]; - if (layerCache != null) { - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - var positions = styleCache[key].positions; - for (var i = 0, position; position = positions[i]; i++) { - position.active = false; - } - } - } - } - } - } - } else { - var positions = this.getTextInfo(layer, text, font, angle).positions; - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = false; - } - } - } - }; - - /////////////////////////////////////////////////////////////////////////// - // The top-level container for the entire plot. - - function Plot(placeholder, data_, options_, plugins) { - // data is on the form: - // [ series1, series2 ... ] - // where series is either just the data as [ [x1, y1], [x2, y2], ... ] - // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } - - var series = [], - options = { - // the color theme used for graphs - colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: true, - noColumns: 1, // number of colums in legend table - labelFormatter: null, // fn: string -> string - labelBoxBorderColor: "#ccc", // border color for the little label boxes - container: null, // container (as jQuery object) to put legend in, null means default on top of graph - position: "ne", // position of default legend container within plot - margin: 5, // distance from grid edge to default legend container within plot - backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85, // set to 0 to avoid background - sorted: null // default to no legend sorting - }, - xaxis: { - show: null, // null = auto-detect, true = always, false = never - position: "bottom", // or "top" - mode: null, // null or "time" - font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } - color: null, // base color, labels, ticks - tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" - transform: null, // null or f: number -> number to transform axis - inverseTransform: null, // if transform is set, this should be the inverse function - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - autoscaleMargin: null, // margin in % to add if auto-setting min/max - ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks - tickFormatter: null, // fn: number -> string - labelWidth: null, // size of tick labels in pixels - labelHeight: null, - reserveSpace: null, // whether to reserve space even if axis isn't shown - tickLength: null, // size in pixels of ticks, or "full" for whole line - alignTicksWithAxis: null, // axis number or null for no sync - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null // number or [number, "unit"] - }, - yaxis: { - autoscaleMargin: 0.02, - position: "left" // or "right" - }, - xaxes: [], - yaxes: [], - series: { - points: { - show: false, - radius: 3, - lineWidth: 2, // in pixels - fill: true, - fillColor: "#ffffff", - symbol: "circle" // or callback - }, - lines: { - // we don't put in show: false so we can see - // whether lines were actively disabled - lineWidth: 2, // in pixels - fill: false, - fillColor: null, - steps: false - // Omit 'zero', so we can later default its value to - // match that of the 'fill' option. - }, - bars: { - show: false, - lineWidth: 2, // in pixels - barWidth: 1, // in units of the x axis - fill: true, - fillColor: null, - align: "left", // "left", "right", or "center" - horizontal: false, - zero: true - }, - shadowSize: 3, - highlightColor: null - }, - grid: { - show: true, - aboveData: false, - color: "#545454", // primary color used for outline and labels - backgroundColor: null, // null for transparent, else color - borderColor: null, // set if different from the grid color - tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" - margin: 0, // distance from the canvas edge to the grid - labelMargin: 5, // in pixels - axisMargin: 8, // in pixels - borderWidth: 2, // in pixels - minBorderMargin: null, // in pixels, null means taken from points radius - markings: null, // array of ranges or fn: axes -> array of ranges - markingsColor: "#f4f4f4", - markingsLineWidth: 2, - // interactive stuff - clickable: false, - hoverable: false, - autoHighlight: true, // highlight in case mouse is near - mouseActiveRadius: 10 // how far the mouse can be away to activate an item - }, - interaction: { - redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow - }, - hooks: {} - }, - surface = null, // the canvas for the plot itself - overlay = null, // canvas for interactive stuff on top of plot - eventHolder = null, // jQuery object that events should be bound to - ctx = null, octx = null, - xaxes = [], yaxes = [], - plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - plotWidth = 0, plotHeight = 0, - hooks = { - processOptions: [], - processRawData: [], - processDatapoints: [], - processOffset: [], - drawBackground: [], - drawSeries: [], - draw: [], - bindEvents: [], - drawOverlay: [], - shutdown: [] - }, - plot = this; - - // public functions - plot.setData = setData; - plot.setupGrid = setupGrid; - plot.draw = draw; - plot.getPlaceholder = function() { return placeholder; }; - plot.getCanvas = function() { return surface.element; }; - plot.getPlotOffset = function() { return plotOffset; }; - plot.width = function () { return plotWidth; }; - plot.height = function () { return plotHeight; }; - plot.offset = function () { - var o = eventHolder.offset(); - o.left += plotOffset.left; - o.top += plotOffset.top; - return o; - }; - plot.getData = function () { return series; }; - plot.getAxes = function () { - var res = {}, i; - $.each(xaxes.concat(yaxes), function (_, axis) { - if (axis) - res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; - }); - return res; - }; - plot.getXAxes = function () { return xaxes; }; - plot.getYAxes = function () { return yaxes; }; - plot.c2p = canvasToAxisCoords; - plot.p2c = axisToCanvasCoords; - plot.getOptions = function () { return options; }; - plot.highlight = highlight; - plot.unhighlight = unhighlight; - plot.triggerRedrawOverlay = triggerRedrawOverlay; - plot.pointOffset = function(point) { - return { - left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), - top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) - }; - }; - plot.shutdown = shutdown; - plot.resize = function () { - var width = placeholder.width(), - height = placeholder.height(); - surface.resize(width, height); - overlay.resize(width, height); - }; - - // public attributes - plot.hooks = hooks; - - // initialize - initPlugins(plot); - parseOptions(options_); - setupCanvases(); - setData(data_); - setupGrid(); - draw(); - bindEvents(); - - - function executeHooks(hook, args) { - args = [plot].concat(args); - for (var i = 0; i < hook.length; ++i) - hook[i].apply(this, args); - } - - function initPlugins() { - - // References to key classes, allowing plugins to modify them - - var classes = { - Canvas: Canvas - }; - - for (var i = 0; i < plugins.length; ++i) { - var p = plugins[i]; - p.init(plot, classes); - if (p.options) - $.extend(true, options, p.options); - } - } - - function parseOptions(opts) { - - $.extend(true, options, opts); - - // $.extend merges arrays, rather than replacing them. When less - // colors are provided than the size of the default palette, we - // end up with those colors plus the remaining defaults, which is - // not expected behavior; avoid it by replacing them here. - - if (opts && opts.colors) { - options.colors = opts.colors; - } - - if (options.xaxis.color == null) - options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - if (options.yaxis.color == null) - options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility - options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; - if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility - options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; - - if (options.grid.borderColor == null) - options.grid.borderColor = options.grid.color; - if (options.grid.tickColor == null) - options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - // Fill in defaults for axis options, including any unspecified - // font-spec fields, if a font-spec was provided. - - // If no x/y axis options were provided, create one of each anyway, - // since the rest of the code assumes that they exist. - - var i, axisOptions, axisCount, - fontDefaults = { - style: placeholder.css("font-style"), - size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)), - variant: placeholder.css("font-variant"), - weight: placeholder.css("font-weight"), - family: placeholder.css("font-family") - }; - - fontDefaults.lineHeight = fontDefaults.size * 1.15; - - axisCount = options.xaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.xaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.xaxis, axisOptions); - options.xaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - } - } - - axisCount = options.yaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.yaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.yaxis, axisOptions); - options.yaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - } - } - - // backwards compatibility, to be removed in future - if (options.xaxis.noTicks && options.xaxis.ticks == null) - options.xaxis.ticks = options.xaxis.noTicks; - if (options.yaxis.noTicks && options.yaxis.ticks == null) - options.yaxis.ticks = options.yaxis.noTicks; - if (options.x2axis) { - options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); - options.xaxes[1].position = "top"; - } - if (options.y2axis) { - options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); - options.yaxes[1].position = "right"; - } - if (options.grid.coloredAreas) - options.grid.markings = options.grid.coloredAreas; - if (options.grid.coloredAreasColor) - options.grid.markingsColor = options.grid.coloredAreasColor; - if (options.lines) - $.extend(true, options.series.lines, options.lines); - if (options.points) - $.extend(true, options.series.points, options.points); - if (options.bars) - $.extend(true, options.series.bars, options.bars); - if (options.shadowSize != null) - options.series.shadowSize = options.shadowSize; - if (options.highlightColor != null) - options.series.highlightColor = options.highlightColor; - - // save options on axes for future reference - for (i = 0; i < options.xaxes.length; ++i) - getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; - for (i = 0; i < options.yaxes.length; ++i) - getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; - - // add hooks from options - for (var n in hooks) - if (options.hooks[n] && options.hooks[n].length) - hooks[n] = hooks[n].concat(options.hooks[n]); - - executeHooks(hooks.processOptions, [options]); - } - - function setData(d) { - series = parseData(d); - fillInSeriesOptions(); - processData(); - } - - function parseData(d) { - var res = []; - for (var i = 0; i < d.length; ++i) { - var s = $.extend(true, {}, options.series); - - if (d[i].data != null) { - s.data = d[i].data; // move the data instead of deep-copy - delete d[i].data; - - $.extend(true, s, d[i]); - - d[i].data = s.data; - } - else - s.data = d[i]; - res.push(s); - } - - return res; - } - - function axisNumber(obj, coord) { - var a = obj[coord + "axis"]; - if (typeof a == "object") // if we got a real axis, extract number - a = a.n; - if (typeof a != "number") - a = 1; // default to first axis - return a; - } - - function allAxes() { - // return flat array without annoying null entries - return $.grep(xaxes.concat(yaxes), function (a) { return a; }); - } - - function canvasToAxisCoords(pos) { - // return an object with x/y corresponding to all used axes - var res = {}, i, axis; - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) - res["x" + axis.n] = axis.c2p(pos.left); - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) - res["y" + axis.n] = axis.c2p(pos.top); - } - - if (res.x1 !== undefined) - res.x = res.x1; - if (res.y1 !== undefined) - res.y = res.y1; - - return res; - } - - function axisToCanvasCoords(pos) { - // get canvas coords from the first pair of x/y found in pos - var res = {}, i, axis, key; - - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) { - key = "x" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "x"; - - if (pos[key] != null) { - res.left = axis.p2c(pos[key]); - break; - } - } - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) { - key = "y" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "y"; - - if (pos[key] != null) { - res.top = axis.p2c(pos[key]); - break; - } - } - } - - return res; - } - - function getOrCreateAxis(axes, number) { - if (!axes[number - 1]) - axes[number - 1] = { - n: number, // save the number for future reference - direction: axes == xaxes ? "x" : "y", - options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) - }; - - return axes[number - 1]; - } - - function fillInSeriesOptions() { - - var neededColors = series.length, maxIndex = -1, i; - - // Subtract the number of series that already have fixed colors or - // color indexes from the number that we still need to generate. - - for (i = 0; i < series.length; ++i) { - var sc = series[i].color; - if (sc != null) { - neededColors--; - if (typeof sc == "number" && sc > maxIndex) { - maxIndex = sc; - } - } - } - - // If any of the series have fixed color indexes, then we need to - // generate at least as many colors as the highest index. - - if (neededColors <= maxIndex) { - neededColors = maxIndex + 1; - } - - // Generate all the colors, using first the option colors and then - // variations on those colors once they're exhausted. - - var c, colors = [], colorPool = options.colors, - colorPoolSize = colorPool.length, variation = 0; - - for (i = 0; i < neededColors; i++) { - - c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); - - // Each time we exhaust the colors in the pool we adjust - // a scaling factor used to produce more variations on - // those colors. The factor alternates negative/positive - // to produce lighter/darker colors. - - // Reset the variation after every few cycles, or else - // it will end up producing only white or black colors. - - if (i % colorPoolSize == 0 && i) { - if (variation >= 0) { - if (variation < 0.5) { - variation = -variation - 0.2; - } else variation = 0; - } else variation = -variation; - } - - colors[i] = c.scale('rgb', 1 + variation); - } - - // Finalize the series options, filling in their colors - - var colori = 0, s; - for (i = 0; i < series.length; ++i) { - s = series[i]; - - // assign colors - if (s.color == null) { - s.color = colors[colori].toString(); - ++colori; - } - else if (typeof s.color == "number") - s.color = colors[s.color].toString(); - - // turn on lines automatically in case nothing is set - if (s.lines.show == null) { - var v, show = true; - for (v in s) - if (s[v] && s[v].show) { - show = false; - break; - } - if (show) - s.lines.show = true; - } - - // If nothing was provided for lines.zero, default it to match - // lines.fill, since areas by default should extend to zero. - - if (s.lines.zero == null) { - s.lines.zero = !!s.lines.fill; - } - - // setup axes - s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); - s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); - } - } - - function processData() { - var topSentry = Number.POSITIVE_INFINITY, - bottomSentry = Number.NEGATIVE_INFINITY, - fakeInfinity = Number.MAX_VALUE, - i, j, k, m, length, - s, points, ps, x, y, axis, val, f, p, - data, format; - - function updateAxis(axis, min, max) { - if (min < axis.datamin && min != -fakeInfinity) - axis.datamin = min; - if (max > axis.datamax && max != fakeInfinity) - axis.datamax = max; - } - - $.each(allAxes(), function (_, axis) { - // init axis - axis.datamin = topSentry; - axis.datamax = bottomSentry; - axis.used = false; - }); - - for (i = 0; i < series.length; ++i) { - s = series[i]; - s.datapoints = { points: [] }; - - executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); - } - - // first pass: clean and copy data - for (i = 0; i < series.length; ++i) { - s = series[i]; - - data = s.data; - format = s.datapoints.format; - - if (!format) { - format = []; - // find out how to copy - format.push({ x: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - - if (s.bars.show || (s.lines.show && s.lines.fill)) { - var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); - format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); - if (s.bars.horizontal) { - delete format[format.length - 1].y; - format[format.length - 1].x = true; - } - } - - s.datapoints.format = format; - } - - if (s.datapoints.pointsize != null) - continue; // already filled in - - s.datapoints.pointsize = format.length; - - ps = s.datapoints.pointsize; - points = s.datapoints.points; - - var insertSteps = s.lines.show && s.lines.steps; - s.xaxis.used = s.yaxis.used = true; - - for (j = k = 0; j < data.length; ++j, k += ps) { - p = data[j]; - - var nullify = p == null; - if (!nullify) { - for (m = 0; m < ps; ++m) { - val = p[m]; - f = format[m]; - - if (f) { - if (f.number && val != null) { - val = +val; // convert to number - if (isNaN(val)) - val = null; - else if (val == Infinity) - val = fakeInfinity; - else if (val == -Infinity) - val = -fakeInfinity; - } - - if (val == null) { - if (f.required) - nullify = true; - - if (f.defaultValue != null) - val = f.defaultValue; - } - } - - points[k + m] = val; - } - } - - if (nullify) { - for (m = 0; m < ps; ++m) { - val = points[k + m]; - if (val != null) { - f = format[m]; - // extract min/max info - if (f.autoscale !== false) { - if (f.x) { - updateAxis(s.xaxis, val, val); - } - if (f.y) { - updateAxis(s.yaxis, val, val); - } - } - } - points[k + m] = null; - } - } - else { - // a little bit of line specific stuff that - // perhaps shouldn't be here, but lacking - // better means... - if (insertSteps && k > 0 - && points[k - ps] != null - && points[k - ps] != points[k] - && points[k - ps + 1] != points[k + 1]) { - // copy the point to make room for a middle point - for (m = 0; m < ps; ++m) - points[k + ps + m] = points[k + m]; - - // middle point has same y - points[k + 1] = points[k - ps + 1]; - - // we've added a point, better reflect that - k += ps; - } - } - } - } - - // give the hooks a chance to run - for (i = 0; i < series.length; ++i) { - s = series[i]; - - executeHooks(hooks.processDatapoints, [ s, s.datapoints]); - } - - // second pass: find datamax/datamin for auto-scaling - for (i = 0; i < series.length; ++i) { - s = series[i]; - points = s.datapoints.points; - ps = s.datapoints.pointsize; - format = s.datapoints.format; - - var xmin = topSentry, ymin = topSentry, - xmax = bottomSentry, ymax = bottomSentry; - - for (j = 0; j < points.length; j += ps) { - if (points[j] == null) - continue; - - for (m = 0; m < ps; ++m) { - val = points[j + m]; - f = format[m]; - if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) - continue; - - if (f.x) { - if (val < xmin) - xmin = val; - if (val > xmax) - xmax = val; - } - if (f.y) { - if (val < ymin) - ymin = val; - if (val > ymax) - ymax = val; - } - } - } - - if (s.bars.show) { - // make sure we got room for the bar on the dancing floor - var delta; - - switch (s.bars.align) { - case "left": - delta = 0; - break; - case "right": - delta = -s.bars.barWidth; - break; - default: - delta = -s.bars.barWidth / 2; - } - - if (s.bars.horizontal) { - ymin += delta; - ymax += delta + s.bars.barWidth; - } - else { - xmin += delta; - xmax += delta + s.bars.barWidth; - } - } - - updateAxis(s.xaxis, xmin, xmax); - updateAxis(s.yaxis, ymin, ymax); - } - - $.each(allAxes(), function (_, axis) { - if (axis.datamin == topSentry) - axis.datamin = null; - if (axis.datamax == bottomSentry) - axis.datamax = null; - }); - } - - function setupCanvases() { - - // Make sure the placeholder is clear of everything except canvases - // from a previous plot in this container that we'll try to re-use. - - placeholder.css("padding", 0) // padding messes up the positioning - .children(":not(.flot-base,.flot-overlay)").remove(); - - if (placeholder.css("position") == 'static') - placeholder.css("position", "relative"); // for positioning labels and overlay - - surface = new Canvas("flot-base", placeholder); - overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features - - ctx = surface.context; - octx = overlay.context; - - // define which element we're listening for events on - eventHolder = $(overlay.element).unbind(); - - // If we're re-using a plot object, shut down the old one - - var existing = placeholder.data("plot"); - - if (existing) { - existing.shutdown(); - overlay.clear(); - } - - // save in case we get replotted - placeholder.data("plot", plot); - } - - function bindEvents() { - // bind events - if (options.grid.hoverable) { - eventHolder.mousemove(onMouseMove); - - // Use bind, rather than .mouseleave, because we officially - // still support jQuery 1.2.6, which doesn't define a shortcut - // for mouseenter or mouseleave. This was a bug/oversight that - // was fixed somewhere around 1.3.x. We can return to using - // .mouseleave when we drop support for 1.2.6. - - eventHolder.bind("mouseleave", onMouseLeave); - } - - if (options.grid.clickable) - eventHolder.click(onClick); - - executeHooks(hooks.bindEvents, [eventHolder]); - } - - function shutdown() { - if (redrawTimeout) - clearTimeout(redrawTimeout); - - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mouseleave", onMouseLeave); - eventHolder.unbind("click", onClick); - - executeHooks(hooks.shutdown, [eventHolder]); - } - - function setTransformationHelpers(axis) { - // set helper functions on the axis, assumes plot area - // has been computed already - - function identity(x) { return x; } - - var s, m, t = axis.options.transform || identity, - it = axis.options.inverseTransform; - - // precompute how much the axis is scaling a point - // in canvas space - if (axis.direction == "x") { - s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); - m = Math.min(t(axis.max), t(axis.min)); - } - else { - s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); - s = -s; - m = Math.max(t(axis.max), t(axis.min)); - } - - // data point to canvas coordinate - if (t == identity) // slight optimization - axis.p2c = function (p) { return (p - m) * s; }; - else - axis.p2c = function (p) { return (t(p) - m) * s; }; - // canvas coordinate to data point - if (!it) - axis.c2p = function (c) { return m + c / s; }; - else - axis.c2p = function (c) { return it(m + c / s); }; - } - - function measureTickLabels(axis) { - - var opts = axis.options, - ticks = axis.ticks || [], - labelWidth = opts.labelWidth || 0, - labelHeight = opts.labelHeight || 0, - maxWidth = labelWidth || axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null, - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = opts.font || "flot-tick-label tickLabel"; - - for (var i = 0; i < ticks.length; ++i) { - - var t = ticks[i]; - - if (!t.label) - continue; - - var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - - labelWidth = Math.max(labelWidth, info.width); - labelHeight = Math.max(labelHeight, info.height); - } - - axis.labelWidth = opts.labelWidth || labelWidth; - axis.labelHeight = opts.labelHeight || labelHeight; - } - - function allocateAxisBoxFirstPhase(axis) { - // find the bounding box of the axis by looking at label - // widths/heights and ticks, make room by diminishing the - // plotOffset; this first phase only looks at one - // dimension per axis, the other dimension depends on the - // other axes so will have to wait - - var lw = axis.labelWidth, - lh = axis.labelHeight, - pos = axis.options.position, - tickLength = axis.options.tickLength, - axisMargin = options.grid.axisMargin, - padding = options.grid.labelMargin, - all = axis.direction == "x" ? xaxes : yaxes, - index, innermost; - - // determine axis margin - var samePosition = $.grep(all, function (a) { - return a && a.options.position == pos && a.reserveSpace; - }); - if ($.inArray(axis, samePosition) == samePosition.length - 1) - axisMargin = 0; // outermost - - // Determine whether the axis is the first (innermost) on its side - - innermost = $.inArray(axis, samePosition) == 0; - - // determine tick length - if we're innermost, we can use "full" - - if (tickLength == null) { - if (innermost) - tickLength = "full"; - else - tickLength = 5; - } - - if (!isNaN(+tickLength)) - padding += +tickLength; - - // compute box - if (axis.direction == "x") { - lh += padding; - - if (pos == "bottom") { - plotOffset.bottom += lh + axisMargin; - axis.box = { top: surface.height - plotOffset.bottom, height: lh }; - } - else { - axis.box = { top: plotOffset.top + axisMargin, height: lh }; - plotOffset.top += lh + axisMargin; - } - } - else { - lw += padding; - - if (pos == "left") { - axis.box = { left: plotOffset.left + axisMargin, width: lw }; - plotOffset.left += lw + axisMargin; - } - else { - plotOffset.right += lw + axisMargin; - axis.box = { left: surface.width - plotOffset.right, width: lw }; - } - } - - // save for future reference - axis.position = pos; - axis.tickLength = tickLength; - axis.box.padding = padding; - axis.innermost = innermost; - } - - function allocateAxisBoxSecondPhase(axis) { - // now that all axis boxes have been placed in one - // dimension, we can set the remaining dimension coordinates - if (axis.direction == "x") { - axis.box.left = plotOffset.left - axis.labelWidth / 2; - axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; - } - else { - axis.box.top = plotOffset.top - axis.labelHeight / 2; - axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; - } - } - - function adjustLayoutForThingsStickingOut() { - // possibly adjust plot offset to ensure everything stays - // inside the canvas and isn't clipped off - - var minMargin = options.grid.minBorderMargin, - margins = { x: 0, y: 0 }, i, axis; - - // check stuff from the plot (FIXME: this should just read - // a value from the series, otherwise it's impossible to - // customize) - if (minMargin == null) { - minMargin = 0; - for (i = 0; i < series.length; ++i) - minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); - } - - margins.x = margins.y = Math.ceil(minMargin); - - // check axis labels, note we don't check the actual - // labels but instead use the overall width/height to not - // jump as much around with replots - $.each(allAxes(), function (_, axis) { - var dir = axis.direction; - if (axis.reserveSpace) - margins[dir] = Math.ceil(Math.max(margins[dir], (dir == "x" ? axis.labelWidth : axis.labelHeight) / 2)); - }); - - plotOffset.left = Math.max(margins.x, plotOffset.left); - plotOffset.right = Math.max(margins.x, plotOffset.right); - plotOffset.top = Math.max(margins.y, plotOffset.top); - plotOffset.bottom = Math.max(margins.y, plotOffset.bottom); - } - - function setupGrid() { - var i, axes = allAxes(), showGrid = options.grid.show; - - // Initialize the plot's offset from the edge of the canvas - - for (var a in plotOffset) { - var margin = options.grid.margin || 0; - plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; - } - - executeHooks(hooks.processOffset, [plotOffset]); - - // If the grid is visible, add its border width to the offset - - for (var a in plotOffset) { - if(typeof(options.grid.borderWidth) == "object") { - plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; - } - else { - plotOffset[a] += showGrid ? options.grid.borderWidth : 0; - } - } - - // init axes - $.each(axes, function (_, axis) { - axis.show = axis.options.show; - if (axis.show == null) - axis.show = axis.used; // by default an axis is visible if it's got data - - axis.reserveSpace = axis.show || axis.options.reserveSpace; - - setRange(axis); - }); - - if (showGrid) { - - var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); - - $.each(allocatedAxes, function (_, axis) { - // make the ticks - setupTickGeneration(axis); - setTicks(axis); - snapRangeToTicks(axis, axis.ticks); - // find labelWidth/Height for axis - measureTickLabels(axis); - }); - - // with all dimensions calculated, we can compute the - // axis bounding boxes, start from the outside - // (reverse order) - for (i = allocatedAxes.length - 1; i >= 0; --i) - allocateAxisBoxFirstPhase(allocatedAxes[i]); - - // make sure we've got enough space for things that - // might stick out - adjustLayoutForThingsStickingOut(); - - $.each(allocatedAxes, function (_, axis) { - allocateAxisBoxSecondPhase(axis); - }); - } - - plotWidth = surface.width - plotOffset.left - plotOffset.right; - plotHeight = surface.height - plotOffset.bottom - plotOffset.top; - - // now we got the proper plot dimensions, we can compute the scaling - $.each(axes, function (_, axis) { - setTransformationHelpers(axis); - }); - - if (showGrid) { - drawAxisLabels(); - } - - insertLegend(); - } - - function setRange(axis) { - var opts = axis.options, - min = +(opts.min != null ? opts.min : axis.datamin), - max = +(opts.max != null ? opts.max : axis.datamax), - delta = max - min; - - if (delta == 0.0) { - // degenerate case - var widen = max == 0 ? 1 : 0.01; - - if (opts.min == null) - min -= widen; - // always widen max if we couldn't widen min to ensure we - // don't fall into min == max which doesn't work - if (opts.max == null || opts.min != null) - max += widen; - } - else { - // consider autoscaling - var margin = opts.autoscaleMargin; - if (margin != null) { - if (opts.min == null) { - min -= delta * margin; - // make sure we don't go below zero if all values - // are positive - if (min < 0 && axis.datamin != null && axis.datamin >= 0) - min = 0; - } - if (opts.max == null) { - max += delta * margin; - if (max > 0 && axis.datamax != null && axis.datamax <= 0) - max = 0; - } - } - } - axis.min = min; - axis.max = max; - } - - function setupTickGeneration(axis) { - var opts = axis.options; - - // estimate number of ticks - var noTicks; - if (typeof opts.ticks == "number" && opts.ticks > 0) - noTicks = opts.ticks; - else - // heuristic based on the model a*sqrt(x) fitted to - // some data points that seemed reasonable - noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); - - var delta = (axis.max - axis.min) / noTicks, - dec = -Math.floor(Math.log(delta) / Math.LN10), - maxDec = opts.tickDecimals; - - if (maxDec != null && dec > maxDec) { - dec = maxDec; - } - - var magn = Math.pow(10, -dec), - norm = delta / magn, // norm is between 1.0 and 10.0 - size; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - - if (opts.minTickSize != null && size < opts.minTickSize) { - size = opts.minTickSize; - } - - axis.delta = delta; - axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); - axis.tickSize = opts.tickSize || size; - - // Time mode was moved to a plug-in in 0.8, but since so many people use this - // we'll add an especially friendly make sure they remembered to include it. - - if (opts.mode == "time" && !axis.tickGenerator) { - throw new Error("Time mode requires the flot.time plugin."); - } - - // Flot supports base-10 axes; any other mode else is handled by a plug-in, - // like flot.time.js. - - if (!axis.tickGenerator) { - - axis.tickGenerator = function (axis) { - - var ticks = [], - start = floorInBase(axis.min, axis.tickSize), - i = 0, - v = Number.NaN, - prev; - - do { - prev = v; - v = start + i * axis.tickSize; - ticks.push(v); - ++i; - } while (v < axis.max && v != prev); - return ticks; - }; - - axis.tickFormatter = function (value, axis) { - - var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; - var formatted = "" + Math.round(value * factor) / factor; - - // If tickDecimals was specified, ensure that we have exactly that - // much precision; otherwise default to the value's own precision. - - if (axis.tickDecimals != null) { - var decimal = formatted.indexOf("."); - var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; - if (precision < axis.tickDecimals) { - return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); - } - } - - return formatted; - }; - } - - if ($.isFunction(opts.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; - - if (opts.alignTicksWithAxis != null) { - var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; - if (otherAxis && otherAxis.used && otherAxis != axis) { - // consider snapping min/max to outermost nice ticks - var niceTicks = axis.tickGenerator(axis); - if (niceTicks.length > 0) { - if (opts.min == null) - axis.min = Math.min(axis.min, niceTicks[0]); - if (opts.max == null && niceTicks.length > 1) - axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); - } - - axis.tickGenerator = function (axis) { - // copy ticks, scaled to this axis - var ticks = [], v, i; - for (i = 0; i < otherAxis.ticks.length; ++i) { - v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); - v = axis.min + v * (axis.max - axis.min); - ticks.push(v); - } - return ticks; - }; - - // we might need an extra decimal since forced - // ticks don't necessarily fit naturally - if (!axis.mode && opts.tickDecimals == null) { - var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), - ts = axis.tickGenerator(axis); - - // only proceed if the tick interval rounded - // with an extra decimal doesn't give us a - // zero at end - if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) - axis.tickDecimals = extraDec; - } - } - } - } - - function setTicks(axis) { - var oticks = axis.options.ticks, ticks = []; - if (oticks == null || (typeof oticks == "number" && oticks > 0)) - ticks = axis.tickGenerator(axis); - else if (oticks) { - if ($.isFunction(oticks)) - // generate the ticks - ticks = oticks(axis); - else - ticks = oticks; - } - - // clean up/labelify the supplied ticks, copy them over - var i, v; - axis.ticks = []; - for (i = 0; i < ticks.length; ++i) { - var label = null; - var t = ticks[i]; - if (typeof t == "object") { - v = +t[0]; - if (t.length > 1) - label = t[1]; - } - else - v = +t; - if (label == null) - label = axis.tickFormatter(v, axis); - if (!isNaN(v)) - axis.ticks.push({ v: v, label: label }); - } - } - - function snapRangeToTicks(axis, ticks) { - if (axis.options.autoscaleMargin && ticks.length > 0) { - // snap to ticks - if (axis.options.min == null) - axis.min = Math.min(axis.min, ticks[0].v); - if (axis.options.max == null && ticks.length > 1) - axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); - } - } - - function draw() { - - surface.clear(); - - executeHooks(hooks.drawBackground, [ctx]); - - var grid = options.grid; - - // draw background, if any - if (grid.show && grid.backgroundColor) - drawBackground(); - - if (grid.show && !grid.aboveData) { - drawGrid(); - } - - for (var i = 0; i < series.length; ++i) { - executeHooks(hooks.drawSeries, [ctx, series[i]]); - drawSeries(series[i]); - } - - executeHooks(hooks.draw, [ctx]); - - if (grid.show && grid.aboveData) { - drawGrid(); - } - - surface.render(); - - // A draw implies that either the axes or data have changed, so we - // should probably update the overlay highlights as well. - - triggerRedrawOverlay(); - } - - function extractRange(ranges, coord) { - var axis, from, to, key, axes = allAxes(); - - for (var i = 0; i < axes.length; ++i) { - axis = axes[i]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? xaxes[0] : yaxes[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function drawBackground() { - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); - ctx.fillRect(0, 0, plotWidth, plotHeight); - ctx.restore(); - } - - function drawGrid() { - var i, axes, bw, bc; - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // draw markings - var markings = options.grid.markings; - if (markings) { - if ($.isFunction(markings)) { - axes = plot.getAxes(); - // xmin etc. is backwards compatibility, to be - // removed in the future - axes.xmin = axes.xaxis.min; - axes.xmax = axes.xaxis.max; - axes.ymin = axes.yaxis.min; - axes.ymax = axes.yaxis.max; - - markings = markings(axes); - } - - for (i = 0; i < markings.length; ++i) { - var m = markings[i], - xrange = extractRange(m, "x"), - yrange = extractRange(m, "y"); - - // fill in missing - if (xrange.from == null) - xrange.from = xrange.axis.min; - if (xrange.to == null) - xrange.to = xrange.axis.max; - if (yrange.from == null) - yrange.from = yrange.axis.min; - if (yrange.to == null) - yrange.to = yrange.axis.max; - - // clip - if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || - yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) - continue; - - xrange.from = Math.max(xrange.from, xrange.axis.min); - xrange.to = Math.min(xrange.to, xrange.axis.max); - yrange.from = Math.max(yrange.from, yrange.axis.min); - yrange.to = Math.min(yrange.to, yrange.axis.max); - - if (xrange.from == xrange.to && yrange.from == yrange.to) - continue; - - // then draw - xrange.from = xrange.axis.p2c(xrange.from); - xrange.to = xrange.axis.p2c(xrange.to); - yrange.from = yrange.axis.p2c(yrange.from); - yrange.to = yrange.axis.p2c(yrange.to); - - if (xrange.from == xrange.to || yrange.from == yrange.to) { - // draw line - ctx.beginPath(); - ctx.strokeStyle = m.color || options.grid.markingsColor; - ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; - ctx.moveTo(xrange.from, yrange.from); - ctx.lineTo(xrange.to, yrange.to); - ctx.stroke(); - } - else { - // fill area - ctx.fillStyle = m.color || options.grid.markingsColor; - ctx.fillRect(xrange.from, yrange.to, - xrange.to - xrange.from, - yrange.from - yrange.to); - } - } - } - - // draw the ticks - axes = allAxes(); - bw = options.grid.borderWidth; - - for (var j = 0; j < axes.length; ++j) { - var axis = axes[j], box = axis.box, - t = axis.tickLength, x, y, xoff, yoff; - if (!axis.show || axis.ticks.length == 0) - continue; - - ctx.lineWidth = 1; - - // find the edges - if (axis.direction == "x") { - x = 0; - if (t == "full") - y = (axis.position == "top" ? 0 : plotHeight); - else - y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); - } - else { - y = 0; - if (t == "full") - x = (axis.position == "left" ? 0 : plotWidth); - else - x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); - } - - // draw tick bar - if (!axis.innermost) { - ctx.strokeStyle = axis.options.color; - ctx.beginPath(); - xoff = yoff = 0; - if (axis.direction == "x") - xoff = plotWidth + 1; - else - yoff = plotHeight + 1; - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") { - y = Math.floor(y) + 0.5; - } else { - x = Math.floor(x) + 0.5; - } - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - ctx.stroke(); - } - - // draw ticks - - ctx.strokeStyle = axis.options.tickColor; - - ctx.beginPath(); - for (i = 0; i < axis.ticks.length; ++i) { - var v = axis.ticks[i].v; - - xoff = yoff = 0; - - if (isNaN(v) || v < axis.min || v > axis.max - // skip those lying on the axes if we got a border - || (t == "full" - && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) - && (v == axis.min || v == axis.max))) - continue; - - if (axis.direction == "x") { - x = axis.p2c(v); - yoff = t == "full" ? -plotHeight : t; - - if (axis.position == "top") - yoff = -yoff; - } - else { - y = axis.p2c(v); - xoff = t == "full" ? -plotWidth : t; - - if (axis.position == "left") - xoff = -xoff; - } - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") - x = Math.floor(x) + 0.5; - else - y = Math.floor(y) + 0.5; - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - } - - ctx.stroke(); - } - - - // draw border - if (bw) { - // If either borderWidth or borderColor is an object, then draw the border - // line by line instead of as one rectangle - bc = options.grid.borderColor; - if(typeof bw == "object" || typeof bc == "object") { - if (typeof bw !== "object") { - bw = {top: bw, right: bw, bottom: bw, left: bw}; - } - if (typeof bc !== "object") { - bc = {top: bc, right: bc, bottom: bc, left: bc}; - } - - if (bw.top > 0) { - ctx.strokeStyle = bc.top; - ctx.lineWidth = bw.top; - ctx.beginPath(); - ctx.moveTo(0 - bw.left, 0 - bw.top/2); - ctx.lineTo(plotWidth, 0 - bw.top/2); - ctx.stroke(); - } - - if (bw.right > 0) { - ctx.strokeStyle = bc.right; - ctx.lineWidth = bw.right; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); - ctx.lineTo(plotWidth + bw.right / 2, plotHeight); - ctx.stroke(); - } - - if (bw.bottom > 0) { - ctx.strokeStyle = bc.bottom; - ctx.lineWidth = bw.bottom; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); - ctx.lineTo(0, plotHeight + bw.bottom / 2); - ctx.stroke(); - } - - if (bw.left > 0) { - ctx.strokeStyle = bc.left; - ctx.lineWidth = bw.left; - ctx.beginPath(); - ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); - ctx.lineTo(0- bw.left/2, 0); - ctx.stroke(); - } - } - else { - ctx.lineWidth = bw; - ctx.strokeStyle = options.grid.borderColor; - ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); - } - } - - ctx.restore(); - } - - function drawAxisLabels() { - - $.each(allAxes(), function (_, axis) { - var box = axis.box, - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = axis.options.font || "flot-tick-label tickLabel", - tick, x, y, halign, valign; - - // Remove text before checking for axis.show and ticks.length; - // otherwise plugins, like flot-tickrotor, that draw their own - // tick labels will end up with both theirs and the defaults. - - surface.removeText(layer); - - if (!axis.show || axis.ticks.length == 0) - return; - - for (var i = 0; i < axis.ticks.length; ++i) { - - tick = axis.ticks[i]; - if (!tick.label || tick.v < axis.min || tick.v > axis.max) - continue; - - if (axis.direction == "x") { - halign = "center"; - x = plotOffset.left + axis.p2c(tick.v); - if (axis.position == "bottom") { - y = box.top + box.padding; - } else { - y = box.top + box.height - box.padding; - valign = "bottom"; - } - } else { - valign = "middle"; - y = plotOffset.top + axis.p2c(tick.v); - if (axis.position == "left") { - x = box.left + box.width - box.padding; - halign = "right"; - } else { - x = box.left + box.padding; - } - } - - surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); - } - }); - } - - function drawSeries(series) { - if (series.lines.show) - drawSeriesLines(series); - if (series.bars.show) - drawSeriesBars(series); - if (series.points.show) - drawSeriesPoints(series); - } - - function drawSeriesLines(series) { - function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - prevx = null, prevy = null; - - ctx.beginPath(); - for (var i = ps; i < points.length; i += ps) { - var x1 = points[i - ps], y1 = points[i - ps + 1], - x2 = points[i], y2 = points[i + 1]; - - if (x1 == null || x2 == null) - continue; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (x1 != prevx || y1 != prevy) - ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); - - prevx = x2; - prevy = y2; - ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); - } - ctx.stroke(); - } - - function plotLineArea(datapoints, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - bottom = Math.min(Math.max(0, axisy.min), axisy.max), - i = 0, top, areaOpen = false, - ypos = 1, segmentStart = 0, segmentEnd = 0; - - // we process each segment in two turns, first forward - // direction to sketch out top, then once we hit the - // end we go backwards to sketch the bottom - while (true) { - if (ps > 0 && i > points.length + ps) - break; - - i += ps; // ps is negative if going backwards - - var x1 = points[i - ps], - y1 = points[i - ps + ypos], - x2 = points[i], y2 = points[i + ypos]; - - if (areaOpen) { - if (ps > 0 && x1 != null && x2 == null) { - // at turning point - segmentEnd = i; - ps = -ps; - ypos = 2; - continue; - } - - if (ps < 0 && i == segmentStart + ps) { - // done with the reverse sweep - ctx.fill(); - areaOpen = false; - ps = -ps; - ypos = 1; - i = segmentStart = segmentEnd + ps; - continue; - } - } - - if (x1 == null || x2 == null) - continue; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (!areaOpen) { - // open area - ctx.beginPath(); - ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= axisy.max && y2 >= axisy.max) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); - continue; - } - else if (y1 <= axisy.min && y2 <= axisy.min) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); - continue; - } - - // else it's a bit more complicated, there might - // be a flat maxed out rectangle first, then a - // triangular cutout or reverse; to find these - // keep track of the current x values - var x1old = x1, x2old = x2; - - // clip the y values, without shortcutting, we - // go through all cases in turn - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); - // it goes to (x1, y1), but we fill that below - } - - // fill triangular section, this sometimes result - // in redundant points if (x1, y1) hasn't changed - // from previous line to, but we just ignore that - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); - } - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - var lw = series.lines.lineWidth, - sw = series.shadowSize; - // FIXME: consider another form of shadow when filling is turned on - if (lw > 0 && sw > 0) { - // draw shadow as a thick and thin line with transparency - ctx.lineWidth = sw; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - // position shadow at angle from the mid of line - var angle = Math.PI/18; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); - ctx.lineWidth = sw/2; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); - if (fillStyle) { - ctx.fillStyle = fillStyle; - plotLineArea(series.datapoints, series.xaxis, series.yaxis); - } - - if (lw > 0) - plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); - ctx.restore(); - } - - function drawSeriesPoints(series) { - function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - var x = points[i], y = points[i + 1]; - if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - - ctx.beginPath(); - x = axisx.p2c(x); - y = axisy.p2c(y) + offset; - if (symbol == "circle") - ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); - else - symbol(ctx, x, y, radius, shadow); - ctx.closePath(); - - if (fillStyle) { - ctx.fillStyle = fillStyle; - ctx.fill(); - } - ctx.stroke(); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var lw = series.points.lineWidth, - sw = series.shadowSize, - radius = series.points.radius, - symbol = series.points.symbol; - - // If the user sets the line width to 0, we change it to a very - // small value. A line width of 0 seems to force the default of 1. - // Doing the conditional here allows the shadow setting to still be - // optional even with a lineWidth of 0. - - if( lw == 0 ) - lw = 0.0001; - - if (lw > 0 && sw > 0) { - // draw shadow in two steps - var w = sw / 2; - ctx.lineWidth = w; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotPoints(series.datapoints, radius, null, w + w/2, true, - series.xaxis, series.yaxis, symbol); - - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotPoints(series.datapoints, radius, null, w/2, true, - series.xaxis, series.yaxis, symbol); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - plotPoints(series.datapoints, radius, - getFillStyle(series.points, series.color), 0, false, - series.xaxis, series.yaxis, symbol); - ctx.restore(); - } - - function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { - var left, right, bottom, top, - drawLeft, drawRight, drawTop, drawBottom, - tmp; - - // in horizontal mode, we start the bar from the left - // instead of from the bottom so it appears to be - // horizontal rather than vertical - if (horizontal) { - drawBottom = drawRight = drawTop = true; - drawLeft = false; - left = b; - right = x; - top = y + barLeft; - bottom = y + barRight; - - // account for negative bars - if (right < left) { - tmp = right; - right = left; - left = tmp; - drawLeft = true; - drawRight = false; - } - } - else { - drawLeft = drawRight = drawTop = true; - drawBottom = false; - left = x + barLeft; - right = x + barRight; - bottom = b; - top = y; - - // account for negative bars - if (top < bottom) { - tmp = top; - top = bottom; - bottom = tmp; - drawBottom = true; - drawTop = false; - } - } - - // clip - if (right < axisx.min || left > axisx.max || - top < axisy.min || bottom > axisy.max) - return; - - if (left < axisx.min) { - left = axisx.min; - drawLeft = false; - } - - if (right > axisx.max) { - right = axisx.max; - drawRight = false; - } - - if (bottom < axisy.min) { - bottom = axisy.min; - drawBottom = false; - } - - if (top > axisy.max) { - top = axisy.max; - drawTop = false; - } - - left = axisx.p2c(left); - bottom = axisy.p2c(bottom); - right = axisx.p2c(right); - top = axisy.p2c(top); - - // fill the bar - if (fillStyleCallback) { - c.fillStyle = fillStyleCallback(bottom, top); - c.fillRect(left, top, right - left, bottom - top) - } - - // draw outline - if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { - c.beginPath(); - - // FIXME: inline moveTo is buggy with excanvas - c.moveTo(left, bottom); - if (drawLeft) - c.lineTo(left, top); - else - c.moveTo(left, top); - if (drawTop) - c.lineTo(right, top); - else - c.moveTo(right, top); - if (drawRight) - c.lineTo(right, bottom); - else - c.moveTo(right, bottom); - if (drawBottom) - c.lineTo(left, bottom); - else - c.moveTo(left, bottom); - c.stroke(); - } - } - - function drawSeriesBars(series) { - function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - if (points[i] == null) - continue; - drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // FIXME: figure out a way to add shadows (for instance along the right edge) - ctx.lineWidth = series.bars.lineWidth; - ctx.strokeStyle = series.color; - - var barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; - plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); - ctx.restore(); - } - - function getFillStyle(filloptions, seriesColor, bottom, top) { - var fill = filloptions.fill; - if (!fill) - return null; - - if (filloptions.fillColor) - return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); - - var c = $.color.parse(seriesColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - return c.toString(); - } - - function insertLegend() { - - placeholder.find(".legend").remove(); - - if (!options.legend.show) - return; - - var fragments = [], entries = [], rowStarted = false, - lf = options.legend.labelFormatter, s, label; - - // Build a list of legend entries, with each having a label and a color - - for (var i = 0; i < series.length; ++i) { - s = series[i]; - if (s.label) { - label = lf ? lf(s.label, s) : s.label; - if (label) { - entries.push({ - label: label, - color: s.color - }); - } - } - } - - // Sort the legend using either the default or a custom comparator - - if (options.legend.sorted) { - if ($.isFunction(options.legend.sorted)) { - entries.sort(options.legend.sorted); - } else if (options.legend.sorted == "reverse") { - entries.reverse(); - } else { - var ascending = options.legend.sorted != "descending"; - entries.sort(function(a, b) { - return a.label == b.label ? 0 : ( - (a.label < b.label) != ascending ? 1 : -1 // Logical XOR - ); - }); - } - } - - // Generate markup for the list of entries, in their final order - - for (var i = 0; i < entries.length; ++i) { - - var entry = entries[i]; - - if (i % options.legend.noColumns == 0) { - if (rowStarted) - fragments.push(''); - fragments.push(''); - rowStarted = true; - } - - fragments.push( - '
      ' + - '' + entry.label + '' - ); - } - - if (rowStarted) - fragments.push(''); - - if (fragments.length == 0) - return; - - var table = '' + fragments.join("") + '
      '; - if (options.legend.container != null) - $(options.legend.container).html(table); - else { - var pos = "", - p = options.legend.position, - m = options.legend.margin; - if (m[0] == null) - m = [m, m]; - if (p.charAt(0) == "n") - pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; - else if (p.charAt(0) == "s") - pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; - if (p.charAt(1) == "e") - pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; - else if (p.charAt(1) == "w") - pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; - var legend = $('
      ' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
      ').appendTo(placeholder); - if (options.legend.backgroundOpacity != 0.0) { - // put in the transparent background - // separately to avoid blended labels and - // label boxes - var c = options.legend.backgroundColor; - if (c == null) { - c = options.grid.backgroundColor; - if (c && typeof c == "string") - c = $.color.parse(c); - else - c = $.color.extract(legend, 'background-color'); - c.a = 1; - c = c.toString(); - } - var div = legend.children(); - $('
      ').prependTo(legend).css('opacity', options.legend.backgroundOpacity); - } - } - } - - - // interactive features - - var highlights = [], - redrawTimeout = null; - - // returns the data item the mouse is over, or null if none is found - function findNearbyItem(mouseX, mouseY, seriesFilter) { - var maxDistance = options.grid.mouseActiveRadius, - smallestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false, i, j, ps; - - for (i = series.length - 1; i >= 0; --i) { - if (!seriesFilter(series[i])) - continue; - - var s = series[i], - axisx = s.xaxis, - axisy = s.yaxis, - points = s.datapoints.points, - mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster - my = axisy.c2p(mouseY), - maxx = maxDistance / axisx.scale, - maxy = maxDistance / axisy.scale; - - ps = s.datapoints.pointsize; - // with inverse transforms, we can't use the maxx/maxy - // optimization, sadly - if (axisx.options.inverseTransform) - maxx = Number.MAX_VALUE; - if (axisy.options.inverseTransform) - maxy = Number.MAX_VALUE; - - if (s.lines.show || s.points.show) { - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1]; - if (x == null) - continue; - - // For points and lines, the cursor must be within a - // certain distance to the data point - if (x - mx > maxx || x - mx < -maxx || - y - my > maxy || y - my < -maxy) - continue; - - // We have to calculate distances in pixels, not in - // data units, because the scales of the axes may be different - var dx = Math.abs(axisx.p2c(x) - mouseX), - dy = Math.abs(axisy.p2c(y) - mouseY), - dist = dx * dx + dy * dy; // we save the sqrt - - // use <= to ensure last point takes precedence - // (last generally means on top of) - if (dist < smallestDistance) { - smallestDistance = dist; - item = [i, j / ps]; - } - } - } - - if (s.bars.show && !item) { // no other point can be nearby - - var barLeft, barRight; - - switch (s.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -s.bars.barWidth; - break; - default: - barLeft = -s.bars.barWidth / 2; - } - - barRight = barLeft + s.bars.barWidth; - - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1], b = points[j + 2]; - if (x == null) - continue; - - // for a bar graph, the cursor must be inside the bar - if (series[i].bars.horizontal ? - (mx <= Math.max(b, x) && mx >= Math.min(b, x) && - my >= y + barLeft && my <= y + barRight) : - (mx >= x + barLeft && mx <= x + barRight && - my >= Math.min(b, y) && my <= Math.max(b, y))) - item = [i, j / ps]; - } - } - } - - if (item) { - i = item[0]; - j = item[1]; - ps = series[i].datapoints.pointsize; - - return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), - dataIndex: j, - series: series[i], - seriesIndex: i }; - } - - return null; - } - - function onMouseMove(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return s["hoverable"] != false; }); - } - - function onMouseLeave(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return false; }); - } - - function onClick(e) { - triggerClickHoverEvent("plotclick", e, - function (s) { return s["clickable"] != false; }); - } - - // trigger click or hover event (they send the same parameters - // so we share their code) - function triggerClickHoverEvent(eventname, event, seriesFilter) { - var offset = eventHolder.offset(), - canvasX = event.pageX - offset.left - plotOffset.left, - canvasY = event.pageY - offset.top - plotOffset.top, - pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); - - pos.pageX = event.pageX; - pos.pageY = event.pageY; - - var item = findNearbyItem(canvasX, canvasY, seriesFilter); - - if (item) { - // fill in mouse pos for any listeners out there - item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); - item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); - } - - if (options.grid.autoHighlight) { - // clear auto-highlights - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.auto == eventname && - !(item && h.series == item.series && - h.point[0] == item.datapoint[0] && - h.point[1] == item.datapoint[1])) - unhighlight(h.series, h.point); - } - - if (item) - highlight(item.series, item.datapoint, eventname); - } - - placeholder.trigger(eventname, [ pos, item ]); - } - - function triggerRedrawOverlay() { - var t = options.interaction.redrawOverlayInterval; - if (t == -1) { // skip event queue - drawOverlay(); - return; - } - - if (!redrawTimeout) - redrawTimeout = setTimeout(drawOverlay, t); - } - - function drawOverlay() { - redrawTimeout = null; - - // draw highlights - octx.save(); - overlay.clear(); - octx.translate(plotOffset.left, plotOffset.top); - - var i, hi; - for (i = 0; i < highlights.length; ++i) { - hi = highlights[i]; - - if (hi.series.bars.show) - drawBarHighlight(hi.series, hi.point); - else - drawPointHighlight(hi.series, hi.point); - } - octx.restore(); - - executeHooks(hooks.drawOverlay, [octx]); - } - - function highlight(s, point, auto) { - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i == -1) { - highlights.push({ series: s, point: point, auto: auto }); - - triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s, point) { - if (s == null && point == null) { - highlights = []; - triggerRedrawOverlay(); - return; - } - - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i != -1) { - highlights.splice(i, 1); - - triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s, p) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.series == s && h.point[0] == p[0] - && h.point[1] == p[1]) - return i; - } - return -1; - } - - function drawPointHighlight(series, point) { - var x = point[0], y = point[1], - axisx = series.xaxis, axisy = series.yaxis, - highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); - - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - return; - - var pointRadius = series.points.radius + series.points.lineWidth / 2; - octx.lineWidth = pointRadius; - octx.strokeStyle = highlightColor; - var radius = 1.5 * pointRadius; - x = axisx.p2c(x); - y = axisy.p2c(y); - - octx.beginPath(); - if (series.points.symbol == "circle") - octx.arc(x, y, radius, 0, 2 * Math.PI, false); - else - series.points.symbol(octx, x, y, radius, false); - octx.closePath(); - octx.stroke(); - } - - function drawBarHighlight(series, point) { - var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), - fillStyle = highlightColor, - barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - octx.lineWidth = series.bars.lineWidth; - octx.strokeStyle = highlightColor; - - drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, - function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); - } - - function getColorOrGradient(spec, bottom, top, defaultColor) { - if (typeof spec == "string") - return spec; - else { - // assume this is a gradient spec; IE currently only - // supports a simple vertical gradient properly, so that's - // what we support too - var gradient = ctx.createLinearGradient(0, top, 0, bottom); - - for (var i = 0, l = spec.colors.length; i < l; ++i) { - var c = spec.colors[i]; - if (typeof c != "string") { - var co = $.color.parse(defaultColor); - if (c.brightness != null) - co = co.scale('rgb', c.brightness); - if (c.opacity != null) - co.a *= c.opacity; - c = co.toString(); - } - gradient.addColorStop(i / (l - 1), c); - } - - return gradient; - } - } - } - - // Add the plot function to the top level of the jQuery object - - $.plot = function(placeholder, data, options) { - //var t0 = new Date(); - var plot = new Plot($(placeholder), data, options, $.plot.plugins); - //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); - return plot; - }; - - $.plot.version = "0.8.2-alpha"; - - $.plot.plugins = []; - - // Also add the plot function as a chainable property - - $.fn.plot = function(data, options) { - return this.each(function() { - $.plot(this, data, options); - }); - }; - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - -})(jQuery); diff --git a/public/javascripts/jquery.flot.tickrotor.js b/public/javascripts/jquery.flot.tickrotor.js deleted file mode 100644 index 404b2b0a7..000000000 --- a/public/javascripts/jquery.flot.tickrotor.js +++ /dev/null @@ -1,205 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is flot-tickrotor. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Mark Cote - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/* - * flot-tickrotor: flot plugin to display angled X-axis tick labels. - * - * Requires flot 0.7 or higher and a browser supporting . - * - * To activate, just set xaxis.rotateTicks to an angle in degrees. Labels - * are rotated clockwise, so if you want the labels to angle up and to the right (/) - * you need to provide an angle > 90. The text will be flipped so that it is still - * right-side-up. - * Angles greater than or equal to 180 are ignored. - */ -(function ($) { - var options = { }; - - function init(plot) { - // Taken from flot-axislabels. - // This is kind of a hack. There are no hooks in Flot between - // the creation and measuring of the ticks (setTicks, measureTickLabels - // in setupGrid() ) and the drawing of the ticks and plot box - // (insertAxisLabels in setupGrid() ). - // - // Therefore, we use a trick where we run the draw routine twice: - // the first time to get the tick measurements, so that we can change - // them, and then have it draw it again. - var ticks = []; - var font; - var secondPass = false; - var rotateTicks, rotateTicksRads, radsAboveHoriz; - plot.hooks.draw.push(function (plot, ctx) { - if (!secondPass) { - var opts = plot.getAxes().xaxis.options; - if (opts.rotateTicks === undefined) { - return; - } - - rotateTicks = parseInt(opts.rotateTicks, 10); - if (rotateTicks.toString() != opts.rotateTicks || rotateTicks == 0 || rotateTicks >= 180) { - return; - } - - rotateTicksRads = rotateTicks * Math.PI/180; - if (rotateTicks > 90) { - radsAboveHoriz = Math.PI - rotateTicksRads; - } else { - radsAboveHoriz = Math.PI/2 - rotateTicksRads; - } - - font = opts.rotateTicksFont; - if (!font) { - font = $('.tickLabel').css('font'); - } - if (!font) { - font = 'smaller sans-serif'; - } - - var elem, maxLabelWidth = 0, maxLabelHeight = 0, minX = 0, maxX = 0; - - var xaxis = plot.getAxes().xaxis; - ticks = plot.getAxes().xaxis.ticks; - opts.ticks = []; // we'll make our own - - var x; - for (var i = 0; i < ticks.length; i++) { - elem = $('' + ticks[i].label + ''); - plot.getPlaceholder().append(elem); - ticks[i].height = elem.outerHeight(true); - ticks[i].width = elem.outerWidth(true); - elem.remove(); - if (ticks[i].height > maxLabelHeight) { - maxLabelHeight = ticks[i].height; - } - if (ticks[i].width > maxLabelWidth) { - maxLabelWidth = ticks[i].width; - } - var tick = ticks[i]; - // See second-draw code below for explanation of offsets. - if (rotateTicks > 90) { - // See if any labels are too long and require increased left - // padding. - x = Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) - - Math.ceil(Math.cos(radsAboveHoriz) * tick.height) - - Math.ceil(Math.cos(radsAboveHoriz) * tick.width); - if (x < minX) { - minX = x; - } - } else { - // See if any labels are too long and require increased right - // padding. - x = Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) - + Math.ceil(Math.cos(radsAboveHoriz) * tick.height) - + Math.ceil(Math.cos(radsAboveHoriz) * tick.width); - if (x > maxX) { - maxX = x; - } - } - } - - // Calculate maximum label height after rotating. - if (rotateTicks > 90) { - var acuteRads = rotateTicksRads - Math.PI/2; - opts.labelHeight = Math.ceil(Math.sin(acuteRads) * maxLabelWidth) - + Math.ceil(Math.sin(acuteRads) * maxLabelHeight); - } else { - var acuteRads = Math.PI/2 - rotateTicksRads; - // Center such that the top of the label is at the center of the tick. - opts.labelHeight = Math.ceil(Math.sin(rotateTicksRads) * maxLabelWidth) - + Math.ceil(Math.sin(acuteRads) * maxLabelHeight); - } - - if (minX < 0) { - plot.getAxes().yaxis.options.labelWidth = -1 * minX; - } - - // Doesn't seem to work if there are no values using the second y axis. - //if (maxX > xaxis.box.left + xaxis.box.width) { - // plot.getAxes().y2axis.options.labelWidth = maxX - xaxis.box.left - xaxis.box.width; - //} - - // re-draw with new label widths and heights - secondPass = true; - plot.setupGrid(); - plot.draw(); - } else { - if (ticks.length == 0) { - return; - } - var xaxis = plot.getAxes().xaxis; - var box = xaxis.box; - var tick, label, xoffset, yoffset; - for (var i = 0; i < ticks.length; i++) { - tick = ticks[i]; - if (!tick.label) { - continue; - } - ctx.save(); - ctx.font = font; - if (rotateTicks <= 90) { - // Center such that the top of the label is at the center of the tick. - xoffset = -Math.ceil(Math.cos(radsAboveHoriz) * tick.height); - yoffset = Math.ceil(Math.sin(radsAboveHoriz) * tick.height); - ctx.translate(Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v)) + xoffset, - box.top + box.padding + plot.getOptions().grid.labelMargin + yoffset); - ctx.rotate(rotateTicksRads); - } else { - // We want the text to facing up, so we have to rotate counterclockwise, - // which means the label has to *end* at the center of the tick. - xoffset = Math.ceil(Math.cos(radsAboveHoriz) * tick.height) - - Math.ceil(Math.cos(radsAboveHoriz) * tick.width); - yoffset = Math.ceil(Math.sin(radsAboveHoriz) * tick.width) - + Math.ceil(Math.sin(radsAboveHoriz) * tick.height); - ctx.translate(Math.round(plot.getPlotOffset().left + xaxis.p2c(tick.v) + xoffset), - box.top + box.padding + plot.getOptions().grid.labelMargin + yoffset); - ctx.rotate(-radsAboveHoriz); - } - ctx.fillText(tick.label, 0, 0); - ctx.restore(); - } - } - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'tickRotor', - version: '1.0' - }); -})(jQuery); diff --git a/public/javascripts/jquery.flot.tickrotor.min.js b/public/javascripts/jquery.flot.tickrotor.min.js deleted file mode 100644 index 87fc79d10..000000000 --- a/public/javascripts/jquery.flot.tickrotor.min.js +++ /dev/null @@ -1 +0,0 @@ -(function($){var options={};function init(plot){var ticks=[];var font;var secondPass=false;var rotateTicks,rotateTicksRads,radsAboveHoriz;plot.hooks.draw.push(function(plot,ctx){if(!secondPass){var opts=plot.getAxes().xaxis.options;if(opts.rotateTicks===undefined){return}rotateTicks=parseInt(opts.rotateTicks,10);if(rotateTicks.toString()!=opts.rotateTicks||rotateTicks==0||rotateTicks>=180){return}rotateTicksRads=rotateTicks*Math.PI/180;if(rotateTicks>90){radsAboveHoriz=Math.PI-rotateTicksRads}else{radsAboveHoriz=Math.PI/2-rotateTicksRads}font=opts.rotateTicksFont;if(!font){font=$(".tickLabel").css("font")}if(!font){font="smaller sans-serif"}var elem,maxLabelWidth=0,maxLabelHeight=0,minX=0,maxX=0;var xaxis=plot.getAxes().xaxis;ticks=plot.getAxes().xaxis.ticks;opts.ticks=[];var x;for(var i=0;i'+ticks[i].label+"");plot.getPlaceholder().append(elem);ticks[i].height=elem.outerHeight(true);ticks[i].width=elem.outerWidth(true);elem.remove();if(ticks[i].height>maxLabelHeight){maxLabelHeight=ticks[i].height}if(ticks[i].width>maxLabelWidth){maxLabelWidth=ticks[i].width}var tick=ticks[i];if(rotateTicks>90){x=Math.round(plot.getPlotOffset().left+xaxis.p2c(tick.v))-Math.ceil(Math.cos(radsAboveHoriz)*tick.height)-Math.ceil(Math.cos(radsAboveHoriz)*tick.width);if(xmaxX){maxX=x}}}if(rotateTicks>90){var acuteRads=rotateTicksRads-Math.PI/2;opts.labelHeight=Math.ceil(Math.sin(acuteRads)*maxLabelWidth)+Math.ceil(Math.sin(acuteRads)*maxLabelHeight)}else{var acuteRads=Math.PI/2-rotateTicksRads;opts.labelHeight=Math.ceil(Math.sin(rotateTicksRads)*maxLabelWidth)+Math.ceil(Math.sin(acuteRads)*maxLabelHeight)}if(minX<0){plot.getAxes().yaxis.options.labelWidth=-1*minX}secondPass=true;plot.setupGrid();plot.draw()}else{if(ticks.length==0){return}var xaxis=plot.getAxes().xaxis;var box=xaxis.box;var tick,label,xoffset,yoffset;for(var i=0;i Date: Mon, 25 Nov 2013 15:42:07 +0000 Subject: Don't include the main stylesheet directly; it's now in application.css --- app/views/general/_stylesheet_includes.html.erb | 2 +- app/views/layouts/no_chrome.html.erb | 3 +-- config/application.rb | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/general/_stylesheet_includes.html.erb b/app/views/general/_stylesheet_includes.html.erb index 390b5b10d..2571dd4ad 100644 --- a/app/views/general/_stylesheet_includes.html.erb +++ b/app/views/general/_stylesheet_includes.html.erb @@ -4,7 +4,7 @@ <%= Rails.application.assets["print.css"].to_s %> <%- else %> - <%= stylesheet_link_tag 'main', :title => "Main", :rel => "stylesheet", :media => "all" %> + <%= stylesheet_link_tag 'application', :title => "Main", :rel => "stylesheet", :media => "all" %> <%= stylesheet_link_tag 'fonts', :rel => "stylesheet", :media => "all" %> <%= stylesheet_link_tag 'print', :rel => "stylesheet", :media => "print" %> <% if !params[:print_stylesheet].nil? %> diff --git a/app/views/layouts/no_chrome.html.erb b/app/views/layouts/no_chrome.html.erb index f7a40490e..589e1bb76 100644 --- a/app/views/layouts/no_chrome.html.erb +++ b/app/views/layouts/no_chrome.html.erb @@ -12,13 +12,12 @@ <%= javascript_include_tag "application" %> - <%= stylesheet_link_tag 'main', :title => "Main", :rel => "stylesheet" %> + <%= stylesheet_link_tag 'application', :title => "Main", :rel => "stylesheet" %> <%= stylesheet_link_tag 'fonts', :rel => "stylesheet" %> <%= stylesheet_link_tag 'theme', :rel => "stylesheet" %> - <%= stylesheet_link_tag 'custom', :title => "Main", :rel => "stylesheet" %>
      diff --git a/config/application.rb b/config/application.rb index 10b1fc5c5..9ef9520b0 100644 --- a/config/application.rb +++ b/config/application.rb @@ -94,7 +94,6 @@ module Alaveteli 'excanvas.min.js', 'fonts.css', 'print.css', - 'main.css', 'admin.css', 'ie6.css', 'ie7.css'] -- cgit v1.2.3 From 68a8a9945d73e22d19d58f89162f46c3d175fc58 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 25 Nov 2013 17:44:58 +0000 Subject: Remove jquery-ui; in a later commit we'll add it back via the gem The intention is to stop including our own custom build of jquery-ui but instead use the jquery-ui-rails gem, which works well with the asset pipeline. This commit should remove all traces of the old download of jquery-ui. --- .../admin-theme/ui-bg_flat_0_aaaaaa_40x100.png | Bin 180 -> 0 bytes .../admin-theme/ui-bg_flat_55_fbf9ee_40x100.png | Bin 182 -> 0 bytes .../admin-theme/ui-bg_flat_65_ffffff_40x100.png | Bin 178 -> 0 bytes .../admin-theme/ui-bg_flat_75_cccccc_40x100.png | Bin 180 -> 0 bytes .../admin-theme/ui-bg_flat_75_dadada_40x100.png | Bin 180 -> 0 bytes .../admin-theme/ui-bg_flat_75_e6e6e6_40x100.png | Bin 180 -> 0 bytes .../admin-theme/ui-bg_flat_75_ffffff_40x100.png | Bin 178 -> 0 bytes .../ui-bg_inset-soft_95_fef1ec_1x100.png | Bin 123 -> 0 bytes .../images/admin-theme/ui-icons_222222_256x240.png | Bin 4369 -> 0 bytes .../images/admin-theme/ui-icons_2e83ff_256x240.png | Bin 4369 -> 0 bytes .../images/admin-theme/ui-icons_454545_256x240.png | Bin 4369 -> 0 bytes .../images/admin-theme/ui-icons_888888_256x240.png | Bin 4369 -> 0 bytes .../images/admin-theme/ui-icons_cd0a0a_256x240.png | Bin 4369 -> 0 bytes app/assets/javascripts/admin.js | 1 - app/assets/javascripts/admin/jquery-ui.min.js | 356 --------------------- app/assets/javascripts/application.js | 1 - app/assets/javascripts/jquery-ui.min.js | 168 ---------- app/views/general/_stylesheet_includes.html.erb | 1 - app/views/layouts/admin.html.erb | 1 - config/application.rb | 1 - 20 files changed, 529 deletions(-) delete mode 100755 app/assets/images/admin-theme/ui-bg_flat_0_aaaaaa_40x100.png delete mode 100755 app/assets/images/admin-theme/ui-bg_flat_55_fbf9ee_40x100.png delete mode 100755 app/assets/images/admin-theme/ui-bg_flat_65_ffffff_40x100.png delete mode 100755 app/assets/images/admin-theme/ui-bg_flat_75_cccccc_40x100.png delete mode 100755 app/assets/images/admin-theme/ui-bg_flat_75_dadada_40x100.png delete mode 100755 app/assets/images/admin-theme/ui-bg_flat_75_e6e6e6_40x100.png delete mode 100755 app/assets/images/admin-theme/ui-bg_flat_75_ffffff_40x100.png delete mode 100755 app/assets/images/admin-theme/ui-bg_inset-soft_95_fef1ec_1x100.png delete mode 100755 app/assets/images/admin-theme/ui-icons_222222_256x240.png delete mode 100755 app/assets/images/admin-theme/ui-icons_2e83ff_256x240.png delete mode 100755 app/assets/images/admin-theme/ui-icons_454545_256x240.png delete mode 100755 app/assets/images/admin-theme/ui-icons_888888_256x240.png delete mode 100755 app/assets/images/admin-theme/ui-icons_cd0a0a_256x240.png delete mode 100644 app/assets/javascripts/admin/jquery-ui.min.js delete mode 100644 app/assets/javascripts/jquery-ui.min.js diff --git a/app/assets/images/admin-theme/ui-bg_flat_0_aaaaaa_40x100.png b/app/assets/images/admin-theme/ui-bg_flat_0_aaaaaa_40x100.png deleted file mode 100755 index 5b5dab2ab..000000000 Binary files a/app/assets/images/admin-theme/ui-bg_flat_0_aaaaaa_40x100.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-bg_flat_55_fbf9ee_40x100.png b/app/assets/images/admin-theme/ui-bg_flat_55_fbf9ee_40x100.png deleted file mode 100755 index 062f58072..000000000 Binary files a/app/assets/images/admin-theme/ui-bg_flat_55_fbf9ee_40x100.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-bg_flat_65_ffffff_40x100.png b/app/assets/images/admin-theme/ui-bg_flat_65_ffffff_40x100.png deleted file mode 100755 index ac8b229af..000000000 Binary files a/app/assets/images/admin-theme/ui-bg_flat_65_ffffff_40x100.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-bg_flat_75_cccccc_40x100.png b/app/assets/images/admin-theme/ui-bg_flat_75_cccccc_40x100.png deleted file mode 100755 index 5473afffb..000000000 Binary files a/app/assets/images/admin-theme/ui-bg_flat_75_cccccc_40x100.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-bg_flat_75_dadada_40x100.png b/app/assets/images/admin-theme/ui-bg_flat_75_dadada_40x100.png deleted file mode 100755 index 7b7b0744d..000000000 Binary files a/app/assets/images/admin-theme/ui-bg_flat_75_dadada_40x100.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-bg_flat_75_e6e6e6_40x100.png b/app/assets/images/admin-theme/ui-bg_flat_75_e6e6e6_40x100.png deleted file mode 100755 index 5b4ca1a03..000000000 Binary files a/app/assets/images/admin-theme/ui-bg_flat_75_e6e6e6_40x100.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-bg_flat_75_ffffff_40x100.png b/app/assets/images/admin-theme/ui-bg_flat_75_ffffff_40x100.png deleted file mode 100755 index ac8b229af..000000000 Binary files a/app/assets/images/admin-theme/ui-bg_flat_75_ffffff_40x100.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-bg_inset-soft_95_fef1ec_1x100.png b/app/assets/images/admin-theme/ui-bg_inset-soft_95_fef1ec_1x100.png deleted file mode 100755 index 0e05810ff..000000000 Binary files a/app/assets/images/admin-theme/ui-bg_inset-soft_95_fef1ec_1x100.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-icons_222222_256x240.png b/app/assets/images/admin-theme/ui-icons_222222_256x240.png deleted file mode 100755 index b273ff111..000000000 Binary files a/app/assets/images/admin-theme/ui-icons_222222_256x240.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-icons_2e83ff_256x240.png b/app/assets/images/admin-theme/ui-icons_2e83ff_256x240.png deleted file mode 100755 index 09d1cdc85..000000000 Binary files a/app/assets/images/admin-theme/ui-icons_2e83ff_256x240.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-icons_454545_256x240.png b/app/assets/images/admin-theme/ui-icons_454545_256x240.png deleted file mode 100755 index 59bd45b90..000000000 Binary files a/app/assets/images/admin-theme/ui-icons_454545_256x240.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-icons_888888_256x240.png b/app/assets/images/admin-theme/ui-icons_888888_256x240.png deleted file mode 100755 index 6d02426c1..000000000 Binary files a/app/assets/images/admin-theme/ui-icons_888888_256x240.png and /dev/null differ diff --git a/app/assets/images/admin-theme/ui-icons_cd0a0a_256x240.png b/app/assets/images/admin-theme/ui-icons_cd0a0a_256x240.png deleted file mode 100755 index 2ab019b73..000000000 Binary files a/app/assets/images/admin-theme/ui-icons_cd0a0a_256x240.png and /dev/null differ diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index 25f3dc81f..f7684f3be 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -1,6 +1,5 @@ // ... //= require jquery -//= require admin/jquery-ui.min //= require admin/bootstrap-collapse //= require admin/bootstrap-tab //= require admin/admin diff --git a/app/assets/javascripts/admin/jquery-ui.min.js b/app/assets/javascripts/admin/jquery-ui.min.js deleted file mode 100644 index f00a62f13..000000000 --- a/app/assets/javascripts/admin/jquery-ui.min.js +++ /dev/null @@ -1,356 +0,0 @@ -/*! - * jQuery UI 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI - */(function(a,b){function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;if(!b.href||!g||f.nodeName.toLowerCase()!=="map")return!1;h=a("img[usemap=#"+g+"]")[0];return!!h&&d(h)}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}a.ui=a.ui||{};a.ui.version||(a.extend(a.ui,{version:"1.8.18",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)});return c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){if(c===b)return g["inner"+d].call(this);return this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){if(typeof b!="number")return g["outer"+d].call(this,b);return this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!!d&&!!a.element[0].parentNode)for(var e=0;e0)return!0;b[d]=1,e=b[d]>0,b[d]=0;return e},isOverAxis:function(a,b,c){return a>b&&a=9)&&!b.button)return this._mouseUp(b);if(this._mouseStarted){this._mouseDrag(b);return b.preventDefault()}this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b));return!this._mouseStarted},_mouseUp:function(b){a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b));return!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})})(jQuery);/* - * jQuery UI Position 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Position - */(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1];return this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]!==e){var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0}},top:function(b,c){if(c.at[1]!==e){var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];if(!c||!c.ownerDocument)return null;if(b)return this.each(function(){a.offset.setOffset(this,b)});return h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);/* - * jQuery UI Draggable 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Draggables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!!this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy();return this}},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle"))return!1;this.handle=this._getHandle(b);if(!this.handle)return!1;c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('
      ').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")});return!0},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment();if(this._trigger("start",b)===!1){this._clear();return!1}this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.helper.addClass("ui-draggable-dragging"),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b);return!0},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1){this._mouseUp({});return!1}this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";a.ui.ddmanager&&a.ui.ddmanager.drag(this,b);return!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var d=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){d._trigger("stop",b)!==!1&&d._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b);return a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)});return c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute");return d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.lefth[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.toph[3]?j-this.offset.click.toph[2]?k-this.offset.click.left=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f=k&&g<=l||h>=k&&h<=l||gl)&&(e>=i&&e<=j||f>=i&&f<=j||ej);default:return!1}},a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(b,c){var d=a.ui.ddmanager.droppables[b.options.scope]||[],e=c?c.type:null,f=(b.currentItem||b.element).find(":data(droppable)").andSelf();droppablesLoop:for(var g=0;g
      ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=c.handles||(a(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var d=this.handles.split(",");this.handles={};for(var e=0;e
      ');/sw|se|ne|nw/.test(f)&&h.css({zIndex:++c.zIndex}),"se"==f&&h.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[f]=".ui-resizable-"+f,this.element.append(h)}}this._renderAxis=function(b){b=b||this.element;for(var c in this.handles){this.handles[c].constructor==String&&(this.handles[c]=a(this.handles[c],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var d=a(this.handles[c],this.element),e=0;e=/sw|ne|nw|se|n|s/.test(c)?d.outerHeight():d.outerWidth();var f=["padding",/ne|nw|n/.test(c)?"Top":/se|sw|s/.test(c)?"Bottom":/^e$/.test(c)?"Right":"Left"].join("");b.css(f,e),this._proportionallyResize()}if(!a(this.handles[c]).length)continue}},this._renderAxis(this.element),this._handles=a(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!b.resizing){if(this.className)var a=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=a&&a[1]?a[1]:"se"}}),c.autoHide&&(this._handles.hide(),a(this.element).addClass("ui-resizable-autohide").hover(function(){c.disabled||(a(this).removeClass("ui-resizable-autohide"),b._handles.show())},function(){c.disabled||b.resizing||(a(this).addClass("ui-resizable-autohide"),b._handles.hide())})),this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(b){a(b).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var c=this.element;c.after(this.originalElement.css({position:c.css("position"),width:c.outerWidth(),height:c.outerHeight(),top:c.css("top"),left:c.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle),b(this.originalElement);return this},_mouseCapture:function(b){var c=!1;for(var d in this.handles)a(this.handles[d])[0]==b.target&&(c=!0);return!this.options.disabled&&c},_mouseStart:function(b){var d=this.options,e=this.element.position(),f=this.element;this.resizing=!0,this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()},(f.is(".ui-draggable")||/absolute/.test(f.css("position")))&&f.css({position:"absolute",top:e.top,left:e.left}),this._renderProxy();var g=c(this.helper.css("left")),h=c(this.helper.css("top"));d.containment&&(g+=a(d.containment).scrollLeft()||0,h+=a(d.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:g,top:h},this.size=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalSize=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalPosition={left:g,top:h},this.sizeDiff={width:f.outerWidth()-f.width(),height:f.outerHeight()-f.height()},this.originalMousePosition={left:b.pageX,top:b.pageY},this.aspectRatio=typeof d.aspectRatio=="number"?d.aspectRatio:this.originalSize.width/this.originalSize.height||1;var i=a(".ui-resizable-"+this.axis).css("cursor");a("body").css("cursor",i=="auto"?this.axis+"-resize":i),f.addClass("ui-resizable-resizing"),this._propagate("start",b);return!0},_mouseDrag:function(b){var c=this.helper,d=this.options,e={},f=this,g=this.originalMousePosition,h=this.axis,i=b.pageX-g.left||0,j=b.pageY-g.top||0,k=this._change[h];if(!k)return!1;var l=k.apply(this,[b,i,j]),m=a.browser.msie&&a.browser.version<7,n=this.sizeDiff;this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)l=this._updateRatio(l,b);l=this._respectSize(l,b),this._propagate("resize",b),c.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",b,this.ui());return!1},_mouseStop:function(b){this.resizing=!1;var c=this.options,d=this;if(this._helper){var e=this._proportionallyResizeElements,f=e.length&&/textarea/i.test(e[0].nodeName),g=f&&a.ui.hasScroll(e[0],"left")?0:d.sizeDiff.height,h=f?0:d.sizeDiff.width,i={width:d.helper.width()-h,height:d.helper.height()-g},j=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,k=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;c.animate||this.element.css(a.extend(i,{top:k,left:j})),d.helper.height(d.size.height),d.helper.width(d.size.width),this._helper&&!c.animate&&this._proportionallyResize()}a("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",b),this._helper&&this.helper.remove();return!1},_updateVirtualBoundaries:function(a){var b=this.options,c,e,f,g,h;h={minWidth:d(b.minWidth)?b.minWidth:0,maxWidth:d(b.maxWidth)?b.maxWidth:Infinity,minHeight:d(b.minHeight)?b.minHeight:0,maxHeight:d(b.maxHeight)?b.maxHeight:Infinity};if(this._aspectRatio||a)c=h.minHeight*this.aspectRatio,f=h.minWidth/this.aspectRatio,e=h.maxHeight*this.aspectRatio,g=h.maxWidth/this.aspectRatio,c>h.minWidth&&(h.minWidth=c),f>h.minHeight&&(h.minHeight=f),ea.width,k=d(a.height)&&e.minHeight&&e.minHeight>a.height;j&&(a.width=e.minWidth),k&&(a.height=e.minHeight),h&&(a.width=e.maxWidth),i&&(a.height=e.maxHeight);var l=this.originalPosition.left+this.originalSize.width,m=this.position.top+this.size.height,n=/sw|nw|w/.test(g),o=/nw|ne|n/.test(g);j&&n&&(a.left=l-e.minWidth),h&&n&&(a.left=l-e.maxWidth),k&&o&&(a.top=m-e.minHeight),i&&o&&(a.top=m-e.maxHeight);var p=!a.width&&!a.height;p&&!a.left&&a.top?a.top=null:p&&!a.top&&a.left&&(a.left=null);return a},_proportionallyResize:function(){var b=this.options;if(!!this._proportionallyResizeElements.length){var c=this.helper||this.element;for(var d=0;d
      ');var d=a.browser.msie&&a.browser.version<7,e=d?1:0,f=d?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+f,height:this.element.outerHeight()+f,position:"absolute",left:this.elementOffset.left-e+"px",top:this.elementOffset.top-e+"px",zIndex:++c.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(a,b,c){return{width:this.originalSize.width+b}},w:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{left:f.left+b,width:e.width-b}},n:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{top:f.top+c,height:e.height-c}},s:function(a,b,c){return{height:this.originalSize.height+c}},se:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},sw:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,c,d]))},ne:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},nw:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,c,d]))}},_propagate:function(b,c){a.ui.plugin.call(this,b,[c,this.ui()]),b!="resize"&&this._trigger(b,c,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),a.extend(a.ui.resizable,{version:"1.8.18"}),a.ui.plugin.add("resizable","alsoResize",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=function(b){a(b).each(function(){var b=a(this);b.data("resizable-alsoresize",{width:parseInt(b.width(),10),height:parseInt(b.height(),10),left:parseInt(b.css("left"),10),top:parseInt(b.css("top"),10)})})};typeof e.alsoResize=="object"&&!e.alsoResize.parentNode?e.alsoResize.length?(e.alsoResize=e.alsoResize[0],f(e.alsoResize)):a.each(e.alsoResize,function(a){f(a)}):f(e.alsoResize)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.originalSize,g=d.originalPosition,h={height:d.size.height-f.height||0,width:d.size.width-f.width||0,top:d.position.top-g.top||0,left:d.position.left-g.left||0},i=function(b,d){a(b).each(function(){var b=a(this),e=a(this).data("resizable-alsoresize"),f={},g=d&&d.length?d:b.parents(c.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(g,function(a,b){var c=(e[b]||0)+(h[b]||0);c&&c>=0&&(f[b]=c||null)}),b.css(f)})};typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?a.each(e.alsoResize,function(a,b){i(a,b)}):i(e.alsoResize)},stop:function(b,c){a(this).removeData("resizable-alsoresize")}}),a.ui.plugin.add("resizable","animate",{stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d._proportionallyResizeElements,g=f.length&&/textarea/i.test(f[0].nodeName),h=g&&a.ui.hasScroll(f[0],"left")?0:d.sizeDiff.height,i=g?0:d.sizeDiff.width,j={width:d.size.width-i,height:d.size.height-h},k=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,l=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;d.element.animate(a.extend(j,l&&k?{top:l,left:k}:{}),{duration:e.animateDuration,easing:e.animateEasing,step:function(){var c={width:parseInt(d.element.css("width"),10),height:parseInt(d.element.css("height"),10),top:parseInt(d.element.css("top"),10),left:parseInt(d.element.css("left"),10)};f&&f.length&&a(f[0]).css({width:c.width,height:c.height}),d._updateCache(c),d._propagate("resize",b)}})}}),a.ui.plugin.add("resizable","containment",{start:function(b,d){var e=a(this).data("resizable"),f=e.options,g=e.element,h=f.containment,i=h instanceof a?h.get(0):/parent/.test(h)?g.parent().get(0):h;if(!!i){e.containerElement=a(i);if(/document/.test(h)||h==document)e.containerOffset={left:0,top:0},e.containerPosition={left:0,top:0},e.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight};else{var j=a(i),k=[];a(["Top","Right","Left","Bottom"]).each(function(a,b){k[a]=c(j.css("padding"+b))}),e.containerOffset=j.offset(),e.containerPosition=j.position(),e.containerSize={height:j.innerHeight()-k[3],width:j.innerWidth()-k[1]};var l=e.containerOffset,m=e.containerSize.height,n=e.containerSize.width,o=a.ui.hasScroll(i,"left")?i.scrollWidth:n,p=a.ui.hasScroll(i)?i.scrollHeight:m;e.parentData={element:i,left:l.left,top:l.top,width:o,height:p}}}},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.containerSize,g=d.containerOffset,h=d.size,i=d.position,j=d._aspectRatio||b.shiftKey,k={top:0,left:0},l=d.containerElement;l[0]!=document&&/static/.test(l.css("position"))&&(k=g),i.left<(d._helper?g.left:0)&&(d.size.width=d.size.width+(d._helper?d.position.left-g.left:d.position.left-k.left),j&&(d.size.height=d.size.width/e.aspectRatio),d.position.left=e.helper?g.left:0),i.top<(d._helper?g.top:0)&&(d.size.height=d.size.height+(d._helper?d.position.top-g.top:d.position.top),j&&(d.size.width=d.size.height*e.aspectRatio),d.position.top=d._helper?g.top:0),d.offset.left=d.parentData.left+d.position.left,d.offset.top=d.parentData.top+d.position.top;var m=Math.abs((d._helper?d.offset.left-k.left:d.offset.left-k.left)+d.sizeDiff.width),n=Math.abs((d._helper?d.offset.top-k.top:d.offset.top-g.top)+d.sizeDiff.height),o=d.containerElement.get(0)==d.element.parent().get(0),p=/relative|absolute/.test(d.containerElement.css("position"));o&&p&&(m-=d.parentData.left),m+d.size.width>=d.parentData.width&&(d.size.width=d.parentData.width-m,j&&(d.size.height=d.size.width/d.aspectRatio)),n+d.size.height>=d.parentData.height&&(d.size.height=d.parentData.height-n,j&&(d.size.width=d.size.height*d.aspectRatio))},stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.position,g=d.containerOffset,h=d.containerPosition,i=d.containerElement,j=a(d.helper),k=j.offset(),l=j.outerWidth()-d.sizeDiff.width,m=j.outerHeight()-d.sizeDiff.height;d._helper&&!e.animate&&/relative/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m}),d._helper&&!e.animate&&/static/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m})}}),a.ui.plugin.add("resizable","ghost",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size;d.ghost=d.originalElement.clone(),d.ghost.css({opacity:.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof e.ghost=="string"?e.ghost:""),d.ghost.appendTo(d.helper)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})},stop:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.helper&&d.helper.get(0).removeChild(d.ghost.get(0))}}),a.ui.plugin.add("resizable","grid",{resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size,g=d.originalSize,h=d.originalPosition,i=d.axis,j=e._aspectRatio||b.shiftKey;e.grid=typeof e.grid=="number"?[e.grid,e.grid]:e.grid;var k=Math.round((f.width-g.width)/(e.grid[0]||1))*(e.grid[0]||1),l=Math.round((f.height-g.height)/(e.grid[1]||1))*(e.grid[1]||1);/^(se|s|e)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l):/^(ne)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l):/^(sw)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.left=h.left-k):(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l,d.position.left=h.left-k)}});var c=function(a){return parseInt(a,10)||0},d=function(a){return!isNaN(parseInt(a,10))}})(jQuery);/* - * jQuery UI Selectable 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Selectables - * - * Depends: - * jquery.ui.core.js - * jquery.ui.mouse.js - * jquery.ui.widget.js - */(function(a,b){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var b=this;this.element.addClass("ui-selectable"),this.dragged=!1;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]),c.addClass("ui-selectee"),c.each(function(){var b=a(this),c=b.offset();a.data(this,"selectable-item",{element:this,$element:b,left:c.left,top:c.top,right:c.left+b.outerWidth(),bottom:c.top+b.outerHeight(),startselected:!1,selected:b.hasClass("ui-selected"),selecting:b.hasClass("ui-selecting"),unselecting:b.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=c.addClass("ui-selectee"),this._mouseInit(),this.helper=a("
      ")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable"),this._mouseDestroy();return this},_mouseStart:function(b){var c=this;this.opos=[b.pageX,b.pageY];if(!this.options.disabled){var d=this.options;this.selectees=a(d.filter,this.element[0]),this._trigger("start",b),a(d.appendTo).append(this.helper),this.helper.css({left:b.clientX,top:b.clientY,width:0,height:0}),d.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var d=a.data(this,"selectable-item");d.startselected=!0,!b.metaKey&&!b.ctrlKey&&(d.$element.removeClass("ui-selected"),d.selected=!1,d.$element.addClass("ui-unselecting"),d.unselecting=!0,c._trigger("unselecting",b,{unselecting:d.element}))}),a(b.target).parents().andSelf().each(function(){var d=a.data(this,"selectable-item");if(d){var e=!b.metaKey&&!b.ctrlKey||!d.$element.hasClass("ui-selected");d.$element.removeClass(e?"ui-unselecting":"ui-selected").addClass(e?"ui-selecting":"ui-unselecting"),d.unselecting=!e,d.selecting=e,d.selected=e,e?c._trigger("selecting",b,{selecting:d.element}):c._trigger("unselecting",b,{unselecting:d.element});return!1}})}},_mouseDrag:function(b){var c=this;this.dragged=!0;if(!this.options.disabled){var d=this.options,e=this.opos[0],f=this.opos[1],g=b.pageX,h=b.pageY;if(e>g){var i=g;g=e,e=i}if(f>h){var i=h;h=f,f=i}this.helper.css({left:e,top:f,width:g-e,height:h-f}),this.selectees.each(function(){var i=a.data(this,"selectable-item");if(!!i&&i.element!=c.element[0]){var j=!1;d.tolerance=="touch"?j=!(i.left>g||i.righth||i.bottome&&i.rightf&&i.bottom *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var a=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},destroy:function(){a.Widget.prototype.destroy.call(this),this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--)this.items[b].item.removeData(this.widgetName+"-item");return this},_setOption:function(b,c){b==="disabled"?(this.options[b]=c,this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")):a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(b,c){var d=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(b);var e=null,f=this,g=a(b.target).parents().each(function(){if(a.data(this,d.widgetName+"-item")==f){e=a(this);return!1}});a.data(b.target,d.widgetName+"-item")==f&&(e=a(b.target));if(!e)return!1;if(this.options.handle&&!c){var h=!1;a(this.options.handle,e).find("*").andSelf().each(function(){this==b.target&&(h=!0)});if(!h)return!1}this.currentItem=e,this._removeCurrentsFromItems();return!0},_mouseStart:function(b,c,d){var e=this.options,f=this;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(b),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),e.containment&&this._setContainment(),e.cursor&&(a("body").css("cursor")&&(this._storedCursor=a("body").css("cursor")),a("body").css("cursor",e.cursor)),e.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",e.opacity)),e.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",e.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",b,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(var g=this.containers.length-1;g>=0;g--)this.containers[g]._trigger("activate",b,f._uiHash(this));a.ui.ddmanager&&(a.ui.ddmanager.current=this),a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(b);return!0},_mouseDrag:function(b){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var c=this.options,d=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-b.pageY=0;e--){var f=this.items[e],g=f.item[0],h=this._intersectsWithPointer(f);if(!h)continue;if(g!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=g&&!a.ui.contains(this.placeholder[0],g)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],g):!0)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(f))this._rearrange(b,f);else break;this._trigger("change",b,this._uiHash());break}}this._contactContainers(b),a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),this._trigger("sort",b,this._uiHash()),this.lastPositionAbs=this.positionAbs;return!1},_mouseStop:function(b,c){if(!!b){a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,b);if(this.options.revert){var d=this,e=d.placeholder.offset();d.reverting=!0,a(this.helper).animate({left:e.left-this.offset.parent.left-d.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-d.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){d._clear(b)})}else this._clear(b,c);return!1}},cancel:function(){var b=this;if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("deactivate",null,b._uiHash(this)),this.containers[c].containerCache.over&&(this.containers[c]._trigger("out",null,b._uiHash(this)),this.containers[c].containerCache.over=0)}this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),a.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem));return this},serialize:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];b=b||{},a(c).each(function(){var c=(a(b.item||this).attr(b.attribute||"id")||"").match(b.expression||/(.+)[-=_](.+)/);c&&d.push((b.key||c[1]+"[]")+"="+(b.key&&b.expression?c[1]:c[2]))}),!d.length&&b.key&&d.push(b.key+"=");return d.join("&")},toArray:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];b=b||{},c.each(function(){d.push(a(b.item||this).attr(b.attribute||"id")||"")});return d},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,d=this.positionAbs.top,e=d+this.helperProportions.height,f=a.left,g=f+a.width,h=a.top,i=h+a.height,j=this.offset.click.top,k=this.offset.click.left,l=d+j>h&&d+jf&&b+ka[this.floating?"width":"height"]?l:f0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a),this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(b){var c=this,d=[],e=[],f=this._connectWith();if(f&&b)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&e.push([a.isFunction(j.options.items)?j.options.items.call(j.element):a(j.options.items,j.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),j])}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var g=e.length-1;g>=0;g--)e[g][0].each(function(){d.push(this)});return a(d)},_removeCurrentsFromItems:function(){var a=this.currentItem.find(":data("+this.widgetName+"-item)");for(var b=0;b=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&(e.push([a.isFunction(j.options.items)?j.options.items.call(j.element[0],b,{item:this.currentItem}):a(j.options.items,j.element),j]),this.containers.push(j))}}for(var g=e.length-1;g>=0;g--){var k=e[g][1],l=e[g][0];for(var i=0,m=l.length;i=0;c--){var d=this.items[c];if(d.instance!=this.currentContainer&&this.currentContainer&&d.item[0]!=this.currentItem[0])continue;var e=this.options.toleranceElement?a(this.options.toleranceElement,d.item):d.item;b||(d.width=e.outerWidth(),d.height=e.outerHeight());var f=e.offset();d.left=f.left,d.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var c=this.containers.length-1;c>=0;c--){var f=this.containers[c].element.offset();this.containers[c].containerCache.left=f.left,this.containers[c].containerCache.top=f.top,this.containers[c].containerCache.width=this.containers[c].element.outerWidth(),this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(b){var c=b||this,d=c.options;if(!d.placeholder||d.placeholder.constructor==String){var e=d.placeholder;d.placeholder={element:function(){var b=a(document.createElement(c.currentItem[0].nodeName)).addClass(e||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];e||(b.style.visibility="hidden");return b},update:function(a,b){if(!e||!!d.forcePlaceholderSize)b.height()||b.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10)),b.width()||b.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||0,10))}}}c.placeholder=a(d.placeholder.element.call(c.element,c.currentItem)),c.currentItem.after(c.placeholder),d.placeholder.update(c,c.placeholder)},_contactContainers:function(b){var c=null,d=null;for(var e=this.containers.length-1;e>=0;e--){if(a.ui.contains(this.currentItem[0],this.containers[e].element[0]))continue;if(this._intersectsWith(this.containers[e].containerCache)){if(c&&a.ui.contains(this.containers[e].element[0],c.element[0]))continue;c=this.containers[e],d=e}else this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",b,this._uiHash(this)),this.containers[e].containerCache.over=0)}if(!!c)if(this.containers.length===1)this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1;else if(this.currentContainer!=this.containers[d]){var f=1e4,g=null,h=this.positionAbs[this.containers[d].floating?"left":"top"];for(var i=this.items.length-1;i>=0;i--){if(!a.ui.contains(this.containers[d].element[0],this.items[i].item[0]))continue;var j=this.items[i][this.containers[d].floating?"left":"top"];Math.abs(j-h)this.containment[2]&&(f=this.containment[2]+this.offset.click.left),b.pageY-this.offset.click.top>this.containment[3]&&(g=this.containment[3]+this.offset.click.top));if(c.grid){var h=this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1];g=this.containment?h-this.offset.click.topthis.containment[3]?h-this.offset.click.topthis.containment[2]?i-this.offset.click.left=0;f--)a.ui.contains(this.containers[f].element[0],this.currentItem[0])&&!c&&(d.push(function(a){return function(b){a._trigger("receive",b,this._uiHash(this))}}.call(this,this.containers[f])),d.push(function(a){return function(b){a._trigger("update",b,this._uiHash(this))}}.call(this,this.containers[f])))}for(var f=this.containers.length-1;f>=0;f--)c||d.push(function(a){return function(b){a._trigger("deactivate",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over&&(d.push(function(a){return function(b){a._trigger("out",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over=0);this._storedCursor&&a("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",b,this._uiHash());for(var f=0;f li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"),b.headers=b.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){c.disabled||a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){c.disabled||a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){c.disabled||a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){c.disabled||a(this).removeClass("ui-state-focus")}),b.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");if(c.navigation){var d=b.element.find("a").filter(c.navigationFilter).eq(0);if(d.length){var e=d.closest(".ui-accordion-header");e.length?b.active=e:b.active=d.closest(".ui-accordion-content").prev()}}b.active=b._findActive(b.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top"),b.active.next().addClass("ui-accordion-content-active"),b._createIcons(),b.resize(),b.element.attr("role","tablist"),b.headers.attr("role","tab").bind("keydown.accordion",function(a){return b._keydown(a)}).next().attr("role","tabpanel"),b.headers.not(b.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide(),b.active.length?b.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):b.headers.eq(0).attr("tabIndex",0),a.browser.safari||b.headers.find("a").attr("tabIndex",-1),c.event&&b.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(a){b._clickHandler.call(b,a,this),a.preventDefault()})},_createIcons:function(){var b=this.options;b.icons&&(a("").addClass("ui-icon "+b.icons.header).prependTo(this.headers),this.active.children(".ui-icon").toggleClass(b.icons.header).toggleClass(b.icons.headerSelected),this.element.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.children(".ui-icon").remove(),this.element.removeClass("ui-accordion-icons")},destroy:function(){var b=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"),this.headers.find("a").removeAttr("tabIndex"),this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");(b.autoHeight||b.fillHeight)&&c.css("height","");return a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b=="active"&&this.activate(c),b=="icons"&&(this._destroyIcons(),c&&this._createIcons()),b=="disabled"&&this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(b){if(!(this.options.disabled||b.altKey||b.ctrlKey)){var c=a.ui.keyCode,d=this.headers.length,e=this.headers.index(b.target),f=!1;switch(b.keyCode){case c.RIGHT:case c.DOWN:f=this.headers[(e+1)%d];break;case c.LEFT:case c.UP:f=this.headers[(e-1+d)%d];break;case c.SPACE:case c.ENTER:this._clickHandler({target:b.target},b.target),b.preventDefault()}if(f){a(b.target).attr("tabIndex",-1),a(f).attr("tabIndex",0),f.focus();return!1}return!0}},resize:function(){var b=this.options,c;if(b.fillSpace){if(a.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height(),a.browser.msie&&this.element.parent().css("overflow",d),this.headers.each(function(){c-=a(this).outerHeight(!0)}),this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+a(this).height()))}).css("overflow","auto")}else b.autoHeight&&(c=0,this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c));return this},activate:function(a){this.options.active=a;var b=this._findActive(a)[0];this._clickHandler({target:b},b);return this},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===!1?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,c){var d=this.options;if(!d.disabled){if(!b.target){if(!d.collapsible)return;this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),this.active.next().addClass("ui-accordion-content-active");var e=this.active.next(),f={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:e},g=this.active=a([]);this._toggle(g,e,f);return}var h=a(b.currentTarget||c),i=h[0]===this.active[0];d.active=d.collapsible&&i?!1:this.headers.index(h);if(this.running||!d.collapsible&&i)return;var j=this.active,g=h.next(),e=this.active.next(),f={options:d,newHeader:i&&d.collapsible?a([]):h,oldHeader:this.active,newContent:i&&d.collapsible?a([]):g,oldContent:e},k=this.headers.index(this.active[0])>this.headers.index(h[0]);this.active=i?a([]):h,this._toggle(g,e,f,i,k),j.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),i||(h.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected),h.next().addClass("ui-accordion-content-active"));return}},_toggle:function(b,c,d,e,f){var g=this,h=g.options;g.toShow=b,g.toHide=c,g.data=d;var i=function(){if(!!g)return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data),g.running=c.size()===0?b.size():c.size();if(h.animated){var j={};h.collapsible&&e?j={toShow:a([]),toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace}:j={toShow:b,toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace},h.proxied||(h.proxied=h.animated),h.proxiedDuration||(h.proxiedDuration=h.duration),h.animated=a.isFunction(h.proxied)?h.proxied(j):h.proxied,h.duration=a.isFunction(h.proxiedDuration)?h.proxiedDuration(j):h.proxiedDuration;var k=a.ui.accordion.animations,l=h.duration,m=h.animated;m&&!k[m]&&!a.easing[m]&&(m="slide"),k[m]||(k[m]=function(a){this.slide(a,{easing:m,duration:l||700})}),k[m](j)}else h.collapsible&&e?b.toggle():(c.hide(),b.show()),i(!0);c.prev().attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).blur(),b.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;this.running||(this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""}),this.toHide.removeClass("ui-accordion-content-active"),this.toHide.length&&(this.toHide.parent()[0].className=this.toHide.parent()[0].className),this._trigger("change",null,this.data))}}),a.extend(a.ui.accordion,{version:"1.8.18",animations:{slide:function(b,c){b=a.extend({easing:"swing",duration:300},b,c);if(!b.toHide.size())b.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},b);else{if(!b.toShow.size()){b.toHide.animate({height:"hide",paddingTop:"hide",paddingBottom:"hide"},b);return}var d=b.toShow.css("overflow"),e=0,f={},g={},h=["height","paddingTop","paddingBottom"],i,j=b.toShow;i=j[0].style.width,j.width(j.parent().width()-parseFloat(j.css("paddingLeft"))-parseFloat(j.css("paddingRight"))-(parseFloat(j.css("borderLeftWidth"))||0)-(parseFloat(j.css("borderRightWidth"))||0)),a.each(h,function(c,d){g[d]="hide";var e=(""+a.css(b.toShow[0],d)).match(/^([\d+-.]+)(.*)$/);f[d]={value:e[1],unit:e[2]||"px"}}),b.toShow.css({height:0,overflow:"hidden"}).show(),b.toHide.filter(":hidden").each(b.complete).end().filter(":visible").animate(g,{step:function(a,c){c.prop=="height"&&(e=c.end-c.start===0?0:(c.now-c.start)/(c.end-c.start)),b.toShow[0].style[c.prop]=e*f[c.prop].value+f[c.prop].unit},duration:b.duration,easing:b.easing,complete:function(){b.autoHeight||b.toShow.css("height",""),b.toShow.css({width:i,overflow:d}),b.complete()}})}},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1e3:200})}}})})(jQuery);/* - * jQuery UI Autocomplete 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Autocomplete - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - * jquery.ui.position.js - */(function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!b.options.disabled&&!b.element.propAttr("readOnly")){d=!1;var e=a.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:b._move("previousPage",c);break;case e.PAGE_DOWN:b._move("nextPage",c);break;case e.UP:b._move("previous",c),c.preventDefault();break;case e.DOWN:b._move("next",c),c.preventDefault();break;case e.ENTER:case e.NUMPAD_ENTER:b.menu.active&&(d=!0,c.preventDefault());case e.TAB:if(!b.menu.active)return;b.menu.select(c);break;case e.ESCAPE:b.element.val(b.term),b.close(c);break;default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}}).bind("keypress.autocomplete",function(a){d&&(d=!1,a.preventDefault())}).bind("focus.autocomplete",function(){b.options.disabled||(b.selectedItem=null,b.previous=b.element.val())}).bind("blur.autocomplete",function(a){b.options.disabled||(clearTimeout(b.searching),b.closing=setTimeout(function(){b.close(a),b._change(a)},150))}),this._initSource(),this.response=function(){return b._response.apply(b,arguments)},this.menu=a("
        ").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",c)[0]).mousedown(function(c){var d=b.menu.element[0];a(c.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(c){c.target!==b.element[0]&&c.target!==d&&!a.ui.contains(d,c.target)&&b.close()})},1),setTimeout(function(){clearTimeout(b.closing)},13)}).menu({focus:function(a,c){var d=c.item.data("item.autocomplete");!1!==b._trigger("focus",a,{item:d})&&/^key/.test(a.originalEvent.type)&&b.element.val(d.value)},selected:function(a,d){var e=d.item.data("item.autocomplete"),f=b.previous;b.element[0]!==c.activeElement&&(b.element.focus(),b.previous=f,setTimeout(function(){b.previous=f,b.selectedItem=e},1)),!1!==b._trigger("select",a,{item:e})&&b.element.val(e.value),b.term=b.element.val(),b.close(a),b.selectedItem=e},blur:function(a,c){b.menu.element.is(":visible")&&b.element.val()!==b.term&&b.element.val(b.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"),a.fn.bgiframe&&this.menu.element.bgiframe(),b.beforeunloadHandler=function(){b.element.removeAttr("autocomplete")},a(window).bind("beforeunload",b.beforeunloadHandler)},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"),this.menu.element.remove(),a(window).unbind("beforeunload",this.beforeunloadHandler),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b==="source"&&this._initSource(),b==="appendTo"&&this.menu.element.appendTo(a(c||"body",this.element[0].ownerDocument)[0]),b==="disabled"&&c&&this.xhr&&this.xhr.abort()},_initSource:function(){var b=this,d,e;a.isArray(this.options.source)?(d=this.options.source,this.source=function(b,c){c(a.ui.autocomplete.filter(d,b.term))}):typeof this.options.source=="string"?(e=this.options.source,this.source=function(d,f){b.xhr&&b.xhr.abort(),b.xhr=a.ajax({url:e,data:d,dataType:"json",context:{autocompleteRequest:++c},success:function(a,b){this.autocompleteRequest===c&&f(a)},error:function(){this.autocompleteRequest===c&&f([])}})}):this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val(),this.term=this.element.val();if(a.length").data("item.autocomplete",c).append(a("").text(c.label)).appendTo(b)},_move:function(a,b){if(!this.menu.element.is(":visible"))this.search(null,b);else{if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term),this.menu.deactivate();return}this.menu[a](b)}},widget:function(){return this.menu.element}}),a.extend(a.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(b,c){var d=new RegExp(a.ui.autocomplete.escapeRegex(c),"i");return a.grep(b,function(a){return d.test(a.label||a.value||a)})}})})(jQuery),function(a){a.widget("ui.menu",{_create:function(){var b=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){!a(c.target).closest(".ui-menu-item a").length||(c.preventDefault(),b.select(c))}),this.refresh()},refresh:function(){var b=this,c=this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem");c.children("a").addClass("ui-corner-all").attr("tabindex",-1).mouseenter(function(c){b.activate(c,a(this).parent())}).mouseleave(function(){b.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.scrollTop(),e=this.element.height();c<0?this.element.scrollTop(d+c):c>=e&&this.element.scrollTop(d+c-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end(),this._trigger("focus",a,{item:b})},deactivate:function(){!this.active||(this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null)},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(!this.active)this.activate(c,this.element.children(b));else{var d=this.active[a+"All"](".ui-menu-item").eq(0);d.length?this.activate(c,d):this.activate(c,this.element.children(b))}},nextPage:function(b){if(this.hasScroll()){if(!this.active||this.last()){this.activate(b,this.element.children(".ui-menu-item:first"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c-d+a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:last")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(b){if(this.hasScroll()){if(!this.active||this.first()){this.activate(b,this.element.children(".ui-menu-item:last"));return}var c=this.active.offset().top,d=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c+d-a(this).height();return b<10&&b>-10}),result.length||(result=this.element.children(".ui-menu-item:first")),this.activate(b,result)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()",this.element[0].ownerDocument).addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary,f=[];d.primary||d.secondary?(this.options.text&&f.push("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary")),d.primary&&b.prepend(""),d.secondary&&b.append(""),this.options.text||(f.push(e?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||b.attr("title",c))):f.push("ui-button-text-only"),b.addClass(f.join(" "))}}}),a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c),a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var b=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(b?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(b?"ui-corner-left":"ui-corner-right").end().end()},destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"),a.Widget.prototype.destroy.call(this)}})})(jQuery);/* - * jQuery UI Dialog 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Dialog - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - * jquery.ui.button.js - * jquery.ui.draggable.js - * jquery.ui.mouse.js - * jquery.ui.position.js - * jquery.ui.resizable.js - */(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||" ",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("
        ")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("
        ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){b.close(a);return!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle);return a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1!==c._trigger("beforeClose",b)){c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d);return c}},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;if(e.modal&&!b||!e.stack&&!e.modal)return d._trigger("focus",c);e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c);return d},open:function(){if(!this._isOpen){var b=this,c=b.options,d=b.uiDialog;b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode===a.ui.keyCode.TAB){var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey){d.focus(1);return!1}if(b.target===d[0]&&b.shiftKey){e.focus(1);return!1}}}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open");return b}},_createButtons:function(b){var c=this,d=!1,e=a("
        ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("
        ").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){a!=="click"&&(a in f?e[a](b):e.attr(a,b))}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||" "))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.18",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");b||(this.uuid+=1,b=this.uuid);return"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()
        ").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});a.fn.bgiframe&&c.bgiframe(),this.instances.push(c);return c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;if(a.browser.msie&&a.browser.version<7){b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return b
        ").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(d.range==="min"||d.range==="max"?" ui-slider-range-"+d.range:"")));for(var i=e.length;ic&&(f=c,g=a(this),i=b)}),c.range===!0&&this.values(1)===c.min&&(i+=1,g=a(this.handles[i])),j=this._start(b,i);if(j===!1)return!1;this._mouseSliding=!0,h._handleIndex=i,g.addClass("ui-state-active").focus(),k=g.offset(),l=!a(b.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:b.pageX-k.left-g.width()/2,top:b.pageY-k.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(b,i,e),this._animateOff=!0;return!0},_mouseStart:function(a){return!0},_mouseDrag:function(a){var b={x:a.pageX,y:a.pageY},c=this._normValueFromMouse(b);this._slide(a,this._handleIndex,c);return!1},_mouseStop:function(a){this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(a,this._handleIndex),this._change(a,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1;return!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b,c,d,e,f;this.orientation==="horizontal"?(b=this.elementSize.width,c=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(b=this.elementSize.height,c=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),d=c/b,d>1&&(d=1),d<0&&(d=0),this.orientation==="vertical"&&(d=1-d),e=this._valueMax()-this._valueMin(),f=this._valueMin()+d*e;return this._trimAlignValue(f)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values());return this._trigger("start",a,c)},_slide:function(a,b,c){var d,e,f;this.options.values&&this.options.values.length?(d=this.values(b?0:1),this.options.values.length===2&&this.options.range===!0&&(b===0&&c>d||b===1&&c1)this.options.values[b]=this._trimAlignValue(c),this._refreshValue(),this._change(null,b);else{if(!arguments.length)return this._values();if(!a.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(b):this.value();d=this.options.values,e=arguments[0];for(f=0;f=this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=(a-this._valueMin())%b,d=a-c;Math.abs(c)*2>=b&&(d+=c>0?b:-b);return parseFloat(d.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var b=this.options.range,c=this.options,d=this,e=this._animateOff?!1:c.animate,f,g={},h,i,j,k;this.options.values&&this.options.values.length?this.handles.each(function(b,i){f=(d.values(b)-d._valueMin())/(d._valueMax()-d._valueMin())*100,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",a(this).stop(1,1)[e?"animate":"css"](g,c.animate),d.options.range===!0&&(d.orientation==="horizontal"?(b===0&&d.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({width:f-h+"%"},{queue:!1,duration:c.animate})):(b===0&&d.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({height:f-h+"%"},{queue:!1,duration:c.animate}))),h=f}):(i=this.value(),j=this._valueMin(),k=this._valueMax(),f=k!==j?(i-j)/(k-j)*100:0,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",this.handle.stop(1,1)[e?"animate":"css"](g,c.animate),b==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},c.animate),b==="max"&&this.orientation==="horizontal"&&this.range[e?"animate":"css"]({width:100-f+"%"},{queue:!1,duration:c.animate}),b==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},c.animate),b==="max"&&this.orientation==="vertical"&&this.range[e?"animate":"css"]({height:100-f+"%"},{queue:!1,duration:c.animate}))}}),a.extend(a.ui.slider,{version:"1.8.18"})})(jQuery);/* - * jQuery UI Tabs 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Tabs - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - */(function(a,b){function f(){return++d}function e(){return++c}var c=0,d=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:!1,cookie:null,collapsible:!1,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
        ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
      • #{label}
      • "},_create:function(){this._tabify(!0)},_setOption:function(a,b){if(a=="selected"){if(this.options.collapsible&&b==this.options.selected)return;this.select(b)}else this.options[a]=b,this._tabify()},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+e()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+f());return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(a,b){return{tab:a,panel:b,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function m(b,c){b.css("display",""),!a.support.opacity&&c.opacity&&b[0].style.removeAttribute("filter")}var d=this,e=this.options,f=/^#.+/;this.list=this.element.find("ol,ul").eq(0),this.lis=a(" > li:has(a[href])",this.list),this.anchors=this.lis.map(function(){return a("a",this)[0]}),this.panels=a([]),this.anchors.each(function(b,c){var g=a(c).attr("href"),h=g.split("#")[0],i;h&&(h===location.toString().split("#")[0]||(i=a("base")[0])&&h===i.href)&&(g=c.hash,c.href=g);if(f.test(g))d.panels=d.panels.add(d.element.find(d._sanitizeSelector(g)));else if(g&&g!=="#"){a.data(c,"href.tabs",g),a.data(c,"load.tabs",g.replace(/#.*$/,""));var j=d._tabId(c);c.href="#"+j;var k=d.element.find("#"+j);k.length||(k=a(e.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(d.panels[b-1]||d.list),k.data("destroy.tabs",!0)),d.panels=d.panels.add(k)}else e.disabled.push(b)}),c?(this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"),this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.lis.addClass("ui-state-default ui-corner-top"),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom"),e.selected===b?(location.hash&&this.anchors.each(function(a,b){if(b.hash==location.hash){e.selected=a;return!1}}),typeof e.selected!="number"&&e.cookie&&(e.selected=parseInt(d._cookie(),10)),typeof e.selected!="number"&&this.lis.filter(".ui-tabs-selected").length&&(e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))),e.selected=e.selected||(this.lis.length?0:-1)):e.selected===null&&(e.selected=-1),e.selected=e.selected>=0&&this.anchors[e.selected]||e.selected<0?e.selected:0,e.disabled=a.unique(e.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(a,b){return d.lis.index(a)}))).sort(),a.inArray(e.selected,e.disabled)!=-1&&e.disabled.splice(a.inArray(e.selected,e.disabled),1),this.panels.addClass("ui-tabs-hide"),this.lis.removeClass("ui-tabs-selected ui-state-active"),e.selected>=0&&this.anchors.length&&(d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash)).removeClass("ui-tabs-hide"),this.lis.eq(e.selected).addClass("ui-tabs-selected ui-state-active"),d.element.queue("tabs",function(){d._trigger("show",null,d._ui(d.anchors[e.selected],d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash))[0]))}),this.load(e.selected)),a(window).bind("unload",function(){d.lis.add(d.anchors).unbind(".tabs"),d.lis=d.anchors=d.panels=null})):e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")),this.element[e.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible"),e.cookie&&this._cookie(e.selected,e.cookie);for(var g=0,h;h=this.lis[g];g++)a(h)[a.inArray(g,e.disabled)!=-1&&!a(h).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");e.cache===!1&&this.anchors.removeData("cache.tabs"),this.lis.add(this.anchors).unbind(".tabs");if(e.event!=="mouseover"){var i=function(a,b){b.is(":not(.ui-state-disabled)")&&b.addClass("ui-state-"+a)},j=function(a,b){b.removeClass("ui-state-"+a)};this.lis.bind("mouseover.tabs",function(){i("hover",a(this))}),this.lis.bind("mouseout.tabs",function(){j("hover",a(this))}),this.anchors.bind("focus.tabs",function(){i("focus",a(this).closest("li"))}),this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var k,l;e.fx&&(a.isArray(e.fx)?(k=e.fx[0],l=e.fx[1]):k=l=e.fx);var n=l?function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.hide().removeClass("ui-tabs-hide").animate(l,l.duration||"normal",function(){m(c,l),d._trigger("show",null,d._ui(b,c[0]))})}:function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.removeClass("ui-tabs-hide"),d._trigger("show",null,d._ui(b,c[0]))},o=k?function(a,b){b.animate(k,k.duration||"normal",function(){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),m(b,k),d.element.dequeue("tabs")})}:function(a,b,c){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),d.element.dequeue("tabs")};this.anchors.bind(e.event+".tabs",function(){var b=this,c=a(b).closest("li"),f=d.panels.filter(":not(.ui-tabs-hide)"),g=d.element.find(d._sanitizeSelector(b.hash));if(c.hasClass("ui-tabs-selected")&&!e.collapsible||c.hasClass("ui-state-disabled")||c.hasClass("ui-state-processing")||d.panels.filter(":animated").length||d._trigger("select",null,d._ui(this,g[0]))===!1){this.blur();return!1}e.selected=d.anchors.index(this),d.abort();if(e.collapsible){if(c.hasClass("ui-tabs-selected")){e.selected=-1,e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){o(b,f)}).dequeue("tabs"),this.blur();return!1}if(!f.length){e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this)),this.blur();return!1}}e.cookie&&d._cookie(e.selected,e.cookie);if(g.length)f.length&&d.element.queue("tabs",function(){o(b,f)}),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this));else throw"jQuery UI Tabs: Mismatching fragment identifier.";a.browser.msie&&this.blur()}),this.anchors.bind("click.tabs",function(){return!1})},_getIndex:function(a){typeof a=="string"&&(a=this.anchors.index(this.anchors.filter("[href$="+a+"]")));return a},destroy:function(){var b=this.options;this.abort(),this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs"),this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.anchors.each(function(){var b=a.data(this,"href.tabs");b&&(this.href=b);var c=a(this).unbind(".tabs");a.each(["href","load","cache"],function(a,b){c.removeData(b+".tabs")})}),this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}),b.cookie&&this._cookie(null,b.cookie);return this},add:function(c,d,e){e===b&&(e=this.anchors.length);var f=this,g=this.options,h=a(g.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,d)),i=c.indexOf("#")?this._tabId(a("a",h)[0]):c.replace("#","");h.addClass("ui-state-default ui-corner-top").data("destroy.tabs",!0);var j=f.element.find("#"+i);j.length||(j=a(g.panelTemplate).attr("id",i).data("destroy.tabs",!0)),j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"),e>=this.lis.length?(h.appendTo(this.list),j.appendTo(this.list[0].parentNode)):(h.insertBefore(this.lis[e]),j.insertBefore(this.panels[e])),g.disabled=a.map(g.disabled,function(a,b){return a>=e?++a:a}),this._tabify(),this.anchors.length==1&&(g.selected=0,h.addClass("ui-tabs-selected ui-state-active"),j.removeClass("ui-tabs-hide"),this.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[0],f.panels[0]))}),this.load(0)),this._trigger("add",null,this._ui(this.anchors[e],this.panels[e]));return this},remove:function(b){b=this._getIndex(b);var c=this.options,d=this.lis.eq(b).remove(),e=this.panels.eq(b).remove();d.hasClass("ui-tabs-selected")&&this.anchors.length>1&&this.select(b+(b+1=b?--a:a}),this._tabify(),this._trigger("remove",null,this._ui(d.find("a")[0],e[0]));return this},enable:function(b){b=this._getIndex(b);var c=this.options;if(a.inArray(b,c.disabled)!=-1){this.lis.eq(b).removeClass("ui-state-disabled"),c.disabled=a.grep(c.disabled,function(a,c){return a!=b}),this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b]));return this}},disable:function(a){a=this._getIndex(a);var b=this,c=this.options;a!=c.selected&&(this.lis.eq(a).addClass("ui-state-disabled"),c.disabled.push(a),c.disabled.sort(),this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a])));return this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;this.anchors.eq(a).trigger(this.options.event+".tabs");return this},load:function(b){b=this._getIndex(b);var c=this,d=this.options,e=this.anchors.eq(b)[0],f=a.data(e,"load.tabs");this.abort();if(!f||this.element.queue("tabs").length!==0&&a.data(e,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(b).addClass("ui-state-processing");if(d.spinner){var g=a("span",e);g.data("label.tabs",g.html()).html(d.spinner)}this.xhr=a.ajax(a.extend({},d.ajaxOptions,{url:f,success:function(f,g){c.element.find(c._sanitizeSelector(e.hash)).html(f),c._cleanup(),d.cache&&a.data(e,"cache.tabs",!0),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.success(f,g)}catch(h){}},error:function(a,f,g){c._cleanup(),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.error(a,f,b,e)}catch(g){}}})),c.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]),this.panels.stop(!1,!0),this.element.queue("tabs",this.element.queue("tabs").splice(-2,2)),this.xhr&&(this.xhr.abort(),delete this.xhr),this._cleanup();return this},url:function(a,b){this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",b);return this},length:function(){return this.anchors.length}}),a.extend(a.ui.tabs,{version:"1.8.18"}),a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(a,b){var c=this,d=this.options,e=c._rotate||(c._rotate=function(b){clearTimeout(c.rotation),c.rotation=setTimeout(function(){var a=d.selected;c.select(++a
        '))}$.extend($.ui,{datepicker:{version:"1.8.18"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){extendRemove(this._defaults,a||{});return this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('
        ')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);c.hasClass(this.markerClassName)||(this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a))},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$(''+c+""),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('').addClass(this._triggerClass).html(g==""?f:$("").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){$.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]);return!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;db&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);c.hasClass(this.markerClassName)||(c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block"))},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$(''),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f);return this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!!b.hasClass(this.markerClassName)){var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!!b.hasClass(this.markerClassName)){var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})}},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!!b.hasClass(this.markerClassName)){var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(a){$.datepicker.log(a)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if(!$.datepicker._isDisabledDatepicker(a)&&$.datepicker._lastInput!=a){var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){e|=$(this).css("position")=="fixed";return!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0);return b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=$.data(a,PROP_NAME))&&this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=this,f=function(){$.datepicker._tidyDialog(b),e._curInst=null};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,f):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,f),c||f(),this._datepickerShowing=!1;var g=this._get(b,"onClose");g&&g.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!!$.datepicker._curInst){var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);this._isDisabledDatepicker(d[0])||(this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e))},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if(!$(d).hasClass(this._unselectableClass)&&!this._isDisabledDatepicker(e[0])){var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();b.setMonth(0),b.setDate(1);return Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1-1){j=1,k=l;for(;;){var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+112?a.getHours()+2:0);return a},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&pp)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?''+q+"":e?"":''+q+"",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?''+s+"":e?"":''+s+"",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'",x=d?'
        '+(c?w:"")+(this._isInRange(a,v)?'":"")+(c?"":w)+"
        ":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='
        '+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'
        '+"";var R=z?'":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="=5?' class="ui-datepicker-week-end"':"")+">"+''+C[T]+""}Q+=R+"";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z";var _=z?'":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Ym;_+='",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+""}n++,n>11&&(n=0,o++),Q+="
        '+this._get(a,"weekHeader")+"
        '+this._get(a,"calculateWeek")(Y)+""+(bb&&!G?" ":bc?''+Y.getDate()+"":''+Y.getDate()+"")+"
        "+(j?"
        "+(g[0]>0&&N==g[1]-1?'
        ':""):""),M+=Q}K+=M}K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'':""), -a._keyEvent=!1;return K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='
        ',m="";if(f||!i)m+=''+g[b]+"";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+=''+c+"";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='",l+=a.yearshtml,a.yearshtml=null}}l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="
        ";return l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&bd?d:e;return e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth()));return this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return $.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return $.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b));return this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)})},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.18",window["DP_jQuery_"+dpuuid]=$})(jQuery);/* - * jQuery UI Progressbar 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Progressbar - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - */(function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("
        ").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove(),a.Widget.prototype.destroy.apply(this,arguments)},value:function(a){if(a===b)return this._value();this._setOption("value",a);return this},_setOption:function(b,c){b==="value"&&(this.options.value=c,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;typeof a!="number"&&(a=0);return Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var a=this.value(),b=this._percentage();this.oldValue!==a&&(this.oldValue=a,this._trigger("change")),this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(b.toFixed(0)+"%"),this.element.attr("aria-valuenow",a)}}),a.extend(a.ui.progressbar,{version:"1.8.18"})})(jQuery);/* - * jQuery UI Effects 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/ - */jQuery.effects||function(a,b){function l(b){if(!b||typeof b=="number"||a.fx.speeds[b])return!0;if(typeof b=="string"&&!a.effects[b])return!0;return!1}function k(b,c,d,e){typeof b=="object"&&(e=c,d=null,c=b,b=c.effect),a.isFunction(c)&&(e=c,d=null,c={});if(typeof c=="number"||a.fx.speeds[c])e=d,d=c,c={};a.isFunction(d)&&(e=d,d=null),c=c||{},d=d||c.duration,d=a.fx.off?0:typeof d=="number"?d:d in a.fx.speeds?a.fx.speeds[d]:a.fx.speeds._default,e=e||c.complete;return[b,c,d,e]}function j(a,b){var c={_:0},d;for(d in b)a[d]!=b[d]&&(c[d]=b[d]);return c}function i(b){var c,d;for(c in b)d=b[c],(d==null||a.isFunction(d)||c in g||/scrollbar/.test(c)||!/color/i.test(c)&&isNaN(parseFloat(d)))&&delete b[c];return b}function h(){var a=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,b={},c,d;if(a&&a.length&&a[0]&&a[a[0]]){var e=a.length;while(e--)c=a[e],typeof a[c]=="string"&&(d=c.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()}),b[d]=a[c])}else for(c in a)typeof a[c]=="string"&&(b[c]=a[c]);return b}function d(b,d){var e;do{e=a.curCSS(b,d);if(e!=""&&e!="transparent"||a.nodeName(b,"body"))break;d="backgroundColor"}while(b=b.parentNode);return c(e)}function c(b){var c;if(b&&b.constructor==Array&&b.length==3)return b;if(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))return[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)];if(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))return[parseFloat(c[1])*2.55,parseFloat(c[2])*2.55,parseFloat(c[3])*2.55];if(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))return[parseInt(c[1],16),parseInt(c[2],16),parseInt(c[3],16)];if(c=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))return[parseInt(c[1]+c[1],16),parseInt(c[2]+c[2],16),parseInt(c[3]+c[3],16)];if(c=/rgba\(0, 0, 0, 0\)/.exec(b))return e.transparent;return e[a.trim(b).toLowerCase()]}a.effects={},a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","borderColor","color","outlineColor"],function(b,e){a.fx.step[e]=function(a){a.colorInit||(a.start=d(a.elem,e),a.end=c(a.end),a.colorInit=!0),a.elem.style[e]="rgb("+Math.max(Math.min(parseInt(a.pos*(a.end[0]-a.start[0])+a.start[0],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[1]-a.start[1])+a.start[1],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[2]-a.start[2])+a.start[2],10),255),0)+")"}});var e={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},f=["add","remove","toggle"],g={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(b,c,d,e){a.isFunction(d)&&(e=d,d=null);return this.queue(function(){var g=a(this),k=g.attr("style")||" ",l=i(h.call(this)),m,n=g.attr("class");a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),m=i(h.call(this)),g.attr("class",n),g.animate(j(l,m),{queue:!1,duration:c,easing:d,complete:function(){a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),typeof g.attr("style")=="object"?(g.attr("style").cssText="",g.attr("style").cssText=k):g.attr("style",k),e&&e.apply(this,arguments),a.dequeue(this)}})})},a.fn.extend({_addClass:a.fn.addClass,addClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{add:b},c,d,e]):this._addClass(b)},_removeClass:a.fn.removeClass,removeClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{remove:b},c,d,e]):this._removeClass(b)},_toggleClass:a.fn.toggleClass,toggleClass:function(c,d,e,f,g){return typeof d=="boolean"||d===b?e?a.effects.animateClass.apply(this,[d?{add:c}:{remove:c},e,f,g]):this._toggleClass(c,d):a.effects.animateClass.apply(this,[{toggle:c},d,e,f])},switchClass:function(b,c,d,e,f){return a.effects.animateClass.apply(this,[{add:c,remove:b},d,e,f])}}),a.extend(a.effects,{version:"1.8.18",save:function(a,b){for(var c=0;c
        ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"}));return d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;if(b.parent().is(".ui-effects-wrapper")){c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus();return c}return b},setTransition:function(b,c,d,e){e=e||{},a.each(c,function(a,c){unit=b.cssUnit(c),unit[0]>0&&(e[c]=unit[0]*d+unit[1])});return e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];if(a.fx.off||!i)return h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)});return i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);b[1].mode="show";return this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);b[1].mode="hide";return this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);c[1].mode="toggle";return this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])});return d}}),a.easing.jswing=a.easing.swing,a.extend(a.easing,{def:"easeOutQuad",swing:function(b,c,d,e,f){return a.easing[a.easing.def](b,c,d,e,f)},easeInQuad:function(a,b,c,d,e){return d*(b/=e)*b+c},easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c},easeInOutQuad:function(a,b,c,d,e){if((b/=e/2)<1)return d/2*b*b+c;return-d/2*(--b*(b-2)-1)+c},easeInCubic:function(a,b,c,d,e){return d*(b/=e)*b*b+c},easeOutCubic:function(a,b,c,d,e){return d*((b=b/e-1)*b*b+1)+c},easeInOutCubic:function(a,b,c,d,e){if((b/=e/2)<1)return d/2*b*b*b+c;return d/2*((b-=2)*b*b+2)+c},easeInQuart:function(a,b,c,d,e){return d*(b/=e)*b*b*b+c},easeOutQuart:function(a,b,c,d,e){return-d*((b=b/e-1)*b*b*b-1)+c},easeInOutQuart:function(a,b,c,d,e){if((b/=e/2)<1)return d/2*b*b*b*b+c;return-d/2*((b-=2)*b*b*b-2)+c},easeInQuint:function(a,b,c,d,e){return d*(b/=e)*b*b*b*b+c},easeOutQuint:function(a,b,c,d,e){return d*((b=b/e-1)*b*b*b*b+1)+c},easeInOutQuint:function(a,b,c,d,e){if((b/=e/2)<1)return d/2*b*b*b*b*b+c;return d/2*((b-=2)*b*b*b*b+2)+c},easeInSine:function(a,b,c,d,e){return-d*Math.cos(b/e*(Math.PI/2))+d+c},easeOutSine:function(a,b,c,d,e){return d*Math.sin(b/e*(Math.PI/2))+c},easeInOutSine:function(a,b,c,d,e){return-d/2*(Math.cos(Math.PI*b/e)-1)+c},easeInExpo:function(a,b,c,d,e){return b==0?c:d*Math.pow(2,10*(b/e-1))+c},easeOutExpo:function(a,b,c,d,e){return b==e?c+d:d*(-Math.pow(2,-10*b/e)+1)+c},easeInOutExpo:function(a,b,c,d,e){if(b==0)return c;if(b==e)return c+d;if((b/=e/2)<1)return d/2*Math.pow(2,10*(b-1))+c;return d/2*(-Math.pow(2,-10*--b)+2)+c},easeInCirc:function(a,b,c,d,e){return-d*(Math.sqrt(1-(b/=e)*b)-1)+c},easeOutCirc:function(a,b,c,d,e){return d*Math.sqrt(1-(b=b/e-1)*b)+c},easeInOutCirc:function(a,b,c,d,e){if((b/=e/2)<1)return-d/2*(Math.sqrt(1-b*b)-1)+c;return d/2*(Math.sqrt(1-(b-=2)*b)+1)+c},easeInElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e)==1)return c+d;g||(g=e*.3);if(h
        ").css({position:"absolute",visibility:"visible",left:-j*(g/d),top:-i*(h/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/d,height:h/c,left:f.left+j*(g/d)+(b.options.mode=="show"?(j-Math.floor(d/2))*(g/d):0),top:f.top+i*(h/c)+(b.options.mode=="show"?(i-Math.floor(c/2))*(h/c):0),opacity:b.options.mode=="show"?0:1}).animate({left:f.left+j*(g/d)+(b.options.mode=="show"?0:(j-Math.floor(d/2))*(g/d)),top:f.top+i*(h/c)+(b.options.mode=="show"?0:(i-Math.floor(c/2))*(h/c)),opacity:b.options.mode=="show"?1:0},b.duration||500);setTimeout(function(){b.options.mode=="show"?e.css({visibility:"visible"}):e.css({visibility:"visible"}).hide(),b.callback&&b.callback.apply(e[0]),e.dequeue(),a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);/* - * jQuery UI Effects Fade 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/Fade - * - * Depends: - * jquery.effects.core.js - */(function(a,b){a.effects.fade=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);/* - * jQuery UI Effects Fold 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/Fold - * - * Depends: - * jquery.effects.core.js - */(function(a,b){a.effects.fold=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.size||15,g=!!b.options.horizFirst,h=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(c,d),c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),j=e=="show"!=g,k=j?["width","height"]:["height","width"],l=j?[i.width(),i.height()]:[i.height(),i.width()],m=/([0-9]+)%/.exec(f);m&&(f=parseInt(m[1],10)/100*l[e=="hide"?0:1]),e=="show"&&i.css(g?{height:0,width:f}:{height:f,width:0});var n={},p={};n[k[0]]=e=="show"?l[0]:f,p[k[1]]=e=="show"?l[1]:0,i.animate(n,h,b.options.easing).animate(p,h,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);/* - * jQuery UI Effects Highlight 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/Highlight - * - * Depends: - * jquery.effects.core.js - */(function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e=="show"&&!a.support.opacity&&this.style.removeAttribute("filter"),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);/* - * jQuery UI Effects Pulsate 1.8.18 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Effects/Pulsate - * - * Depends: - * jquery.effects.core.js - */(function(a,b){a.effects.pulsate=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"show");times=(b.options.times||5)*2-1,duration=b.duration?b.duration/2:a.fx.speeds._default/2,isVisible=c.is(":visible"),animateTo=0,isVisible||(c.css("opacity",0).show(),animateTo=1),(d=="hide"&&isVisible||d=="show"&&!isVisible)&×--;for(var e=0;e
        ').appendTo(document.body).addClass(b.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,b.duration,b.options.easing,function(){h.remove(),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index f92f0562a..f2413bde9 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,6 +1,5 @@ // ... //= require jquery -//= require jquery-ui.min //= require jquery.cookie //= require general //= require ba-throttle-debounce diff --git a/app/assets/javascripts/jquery-ui.min.js b/app/assets/javascripts/jquery-ui.min.js deleted file mode 100644 index fb641f675..000000000 --- a/app/assets/javascripts/jquery-ui.min.js +++ /dev/null @@ -1,168 +0,0 @@ -/*! - * jQuery UI 1.8.16 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI - */ -(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.16", -keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({propAttr:c.fn.prop||c.fn.attr,_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d= -this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this, -"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart": -"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight, -outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a, -"tabindex"),d=isNaN(b);return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&& -a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= -a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), -g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); -;/* - * jQuery UI Tabs 1.8.16 - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Tabs - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - */ -(function(d,p){function u(){return++v}function w(){return++x}var v=0,x=0;d.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
        ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
      • #{label}
      • "},_create:function(){this._tabify(true)},_setOption:function(b,e){if(b=="selected")this.options.collapsible&& -e==this.options.selected||this.select(e);else{this.options[b]=e;this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[b].concat(d.makeArray(arguments)))},_ui:function(b,e){return{tab:b,panel:e,index:this.anchors.index(b)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b= -d(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(b){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var a=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var i=d(f).attr("href"),l=i.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]|| -(q=d("base")[0])&&l===q.href)){i=f.hash;f.href=i}if(h.test(i))a.panels=a.panels.add(a.element.find(a._sanitizeSelector(i)));else if(i&&i!=="#"){d.data(f,"href.tabs",i);d.data(f,"load.tabs",i.replace(/#.*$/,""));i=a._tabId(f);f.href="#"+i;f=a.element.find("#"+i);if(!f.length){f=d(c.panelTemplate).attr("id",i).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(a.panels[g-1]||a.list);f.data("destroy.tabs",true)}a.panels=a.panels.add(f)}else c.disabled.push(g)});if(b){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); -this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(a._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected= -this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return a.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); -if(c.selected>=0&&this.anchors.length){a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");a.element.queue("tabs",function(){a._trigger("show",null,a._ui(a.anchors[c.selected],a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash))[0]))});this.load(c.selected)}d(window).bind("unload",function(){a.lis.add(a.anchors).unbind(".tabs");a.lis=a.anchors=a.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")); -this.element[c.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);b=0;for(var j;j=this.lis[b];b++)d(j)[d.inArray(b,c.disabled)!=-1&&!d(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+ -g)};this.lis.bind("mouseover.tabs",function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal", -function(){e(f,o);a._trigger("show",null,a._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");a._trigger("show",null,a._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);a.element.dequeue("tabs")})}:function(g,f){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");a.element.dequeue("tabs")}; -this.anchors.bind(c.event+".tabs",function(){var g=this,f=d(g).closest("li"),i=a.panels.filter(":not(.ui-tabs-hide)"),l=a.element.find(a._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||a.panels.filter(":animated").length||a._trigger("select",null,a._ui(this,l[0]))===false){this.blur();return false}c.selected=a.anchors.index(this);a.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected= --1;c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){s(g,i)}).dequeue("tabs");this.blur();return false}else if(!i.length){c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this));this.blur();return false}c.cookie&&a._cookie(c.selected,c.cookie);if(l.length){i.length&&a.element.queue("tabs",function(){s(g,i)});a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier."; -d.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(b){if(typeof b=="string")b=this.anchors.index(this.anchors.filter("[href$="+b+"]"));return b},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e= -d.data(this,"href.tabs");if(e)this.href=e;var a=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){a.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});b.cookie&&this._cookie(null,b.cookie);return this},add:function(b, -e,a){if(a===p)a=this.anchors.length;var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,b).replace(/#\{label\}/g,e));b=!b.indexOf("#")?b.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var j=c.element.find("#"+b);j.length||(j=d(h.panelTemplate).attr("id",b).data("destroy.tabs",true));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(a>=this.lis.length){e.appendTo(this.list);j.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[a]); -j.insertBefore(this.panels[a])}h.disabled=d.map(h.disabled,function(k){return k>=a?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");j.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[a],this.panels[a]));return this},remove:function(b){b=this._getIndex(b);var e=this.options,a=this.lis.eq(b).remove(),c=this.panels.eq(b).remove(); -if(a.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(b+(b+1=b?--h:h});this._tabify();this._trigger("remove",null,this._ui(a.find("a")[0],c[0]));return this},enable:function(b){b=this._getIndex(b);var e=this.options;if(d.inArray(b,e.disabled)!=-1){this.lis.eq(b).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(a){return a!=b});this._trigger("enable",null, -this._ui(this.anchors[b],this.panels[b]));return this}},disable:function(b){b=this._getIndex(b);var e=this.options;if(b!=e.selected){this.lis.eq(b).addClass("ui-state-disabled");e.disabled.push(b);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[b],this.panels[b]))}return this},select:function(b){b=this._getIndex(b);if(b==-1)if(this.options.collapsible&&this.options.selected!=-1)b=this.options.selected;else return this;this.anchors.eq(b).trigger(this.options.event+".tabs");return this}, -load:function(b){b=this._getIndex(b);var e=this,a=this.options,c=this.anchors.eq(b)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(b).addClass("ui-state-processing");if(a.spinner){var j=d("span",c);j.data("label.tabs",j.html()).html(a.spinner)}this.xhr=d.ajax(d.extend({},a.ajaxOptions,{url:h,success:function(k,n){e.element.find(e._sanitizeSelector(c.hash)).html(k);e._cleanup();a.cache&&d.data(c, -"cache.tabs",true);e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.error(k,n,b,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this}, -url:function(b,e){this.anchors.eq(b).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.16"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(b,e){var a=this,c=this.options,h=a._rotate||(a._rotate=function(j){clearTimeout(a.rotation);a.rotation=setTimeout(function(){var k=c.selected;a.select(++k
        '))}function N(a){return a.bind("mouseout", -function(b){b=d(b.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");b.length&&b.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(b){b=d(b.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");if(!(d.datepicker._isDisabledDatepicker(J.inline?a.parent()[0]:J.input[0])||!b.length)){b.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); -b.addClass("ui-state-hover");b.hasClass("ui-datepicker-prev")&&b.addClass("ui-datepicker-prev-hover");b.hasClass("ui-datepicker-next")&&b.addClass("ui-datepicker-next-hover")}})}function H(a,b){d.extend(a,b);for(var c in b)if(b[c]==null||b[c]==C)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.16"}});var B=(new Date).getTime(),J;d.extend(M.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv}, -setDefaults:function(a){H(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]=f}}}e=a.nodeName.toLowerCase();f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_-])/g, -"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:N(d('
        '))}},_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker", -function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b);b.settings.disabled&&this._disableDatepicker(a)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&&b.append.remove();if(c){b.append=d(''+c+"");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c== -"focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('').addClass(this._triggerClass).html(f==""?c:d("").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker(): -d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;gh){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a, -b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b),true);this._updateDatepicker(b);this._updateAlternate(b);b.settings.disabled&&this._disableDatepicker(a);b.dpDiv.css("display","block")}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+= -1;this._dialogInput=d('');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}H(a.settings,e||{});b=b&&b.constructor==Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/ -2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b= -d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e= -a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span"){b=b.children("."+this._inlineClass);b.children().removeClass("ui-state-disabled");b.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b=d(a),c=d.data(a, -"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span"){b=b.children("."+this._inlineClass);b.children().addClass("ui-state-disabled");b.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=d.map(this._disabledInputs,function(f){return f== -a?null:f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;for(var b=0;b-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true},_showDatepicker:function(a){a=a.target||a;if(a.nodeName.toLowerCase()!="input")a=d("input", -a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);if(d.datepicker._curInst&&d.datepicker._curInst!=b){d.datepicker._datepickerShowing&&d.datepicker._triggerOnClose(d.datepicker._curInst);d.datepicker._curInst.dpDiv.stop(true,true)}var c=d.datepicker._get(b,"beforeShow");c=c?c.apply(a,[a,b]):{};if(c!==false){H(b.settings,c);b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value= -"";if(!d.datepicker._pos){d.datepicker._pos=d.datepicker._findPos(a);d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.empty();b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b); -c=d.datepicker._checkOffset(b,c,e);b.dpDiv.css({position:d.datepicker._inDialog&&d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){var i=b.dpDiv.find("iframe.ui-datepicker-cover");if(i.length){var g=d.datepicker._getBorders(b.dpDiv);i.css({left:-g[0],top:-g[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex(d(a).zIndex()+1);d.datepicker._datepickerShowing= -true;d.effects&&d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f,h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}}},_updateDatepicker:function(a){this.maxRows=4;var b=d.datepicker._getBorders(a.dpDiv);J=a;a.dpDiv.empty().append(this._generateHTML(a));var c=a.dpDiv.find("iframe.ui-datepicker-cover");c.length&&c.css({left:-b[0],top:-b[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}); -a.dpDiv.find("."+this._dayOverClass+" a").mouseover();b=this._getNumberOfMonths(a);c=b[1];a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");c>1&&a.dpDiv.addClass("ui-datepicker-multi-"+c).css("width",17*c+"em");a.dpDiv[(b[0]!=1||b[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&& -!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var e=a.yearshtml;setTimeout(function(){e===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml);e=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(), -h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(),j=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e-g):0);b.top-=Math.min(b.top,b.top+f>j&&j>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b= -this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1||d.expr.filters.hidden(a));)a=a[b?"previousSibling":"nextSibling"];a=d(a).offset();return[a.left,a.top]},_triggerOnClose:function(a){var b=this._get(a,"onClose");if(b)b.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a])},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b); -this._curInst=null};d.effects&&d.effects[a]?b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();d.datepicker._triggerOnClose(b);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")}, -_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&&!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"): -0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth;b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e["selected"+(c=="M"? -"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay=d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=d(a); -this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,"altField"); -if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b=a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"? -b.toString():b+"";if(b=="")return null;var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;e=typeof e!="string"?e:(new Date).getFullYear()%100+parseInt(e,10);for(var f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,j=c=-1,l=-1,u=-1,k=false,o=function(p){(p=A+1-1){j=1;l=u;do{e=this._getDaysInMonth(c,j-1);if(l<=e)break;j++;l-=e}while(1)}v=this._daylightSavingAdjust(new Date(c,j-1,l));if(v.getFullYear()!=c||v.getMonth()+1!=j||v.getDate()!=l)throw"Invalid date";return v},ATOM:"yy-mm-dd", -COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames: -null)||this._defaults.monthNames;var i=function(o){(o=k+1 -12?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&& -a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),j=this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay? -new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),k=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n=this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=k&&nn;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-j,1)),this._getFormatConfig(a)); -n=this._canAdjustMonth(a,-1,m,g)?''+n+"":f?"":''+n+"";var s=this._get(a,"nextText");s=!h?s:this.formatDate(s,this._daylightSavingAdjust(new Date(m, -g+j,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?''+s+"":f?"":''+s+"";j=this._get(a,"currentText");s=this._get(a,"gotoCurrent")&& -a.currentDay?u:b;j=!h?j:this.formatDate(j,s,this._getFormatConfig(a));h=!a.inline?'":"";e=e?'
        '+(c?h:"")+(this._isInRange(a,s)?'":"")+(c?"":h)+"
        ":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;j=this._get(a,"showWeek");s=this._get(a,"dayNames");this._get(a,"dayNamesShort");var q=this._get(a,"dayNamesMin"),A=this._get(a,"monthNames"),v=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),D=this._get(a,"showOtherMonths"),K=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var E=this._getDefaultDate(a),w="",x=0;x1)switch(G){case 0:y+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]-1:y+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:y+=" ui-datepicker-group-middle";t="";break}y+='">'}y+='
        '+(/all|left/.test(t)&& -x==0?c?f:n:"")+(/all|right/.test(t)&&x==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,k,o,x>0||G>0,A,v)+'
        ';var z=j?'":"";for(t=0;t<7;t++){var r=(t+h)%7;z+="=5?' class="ui-datepicker-week-end"':"")+'>'+q[r]+""}y+=z+"";z=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay, -z);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;z=Math.ceil((t+z)/7);this.maxRows=z=l?this.maxRows>z?this.maxRows:z:z;r=this._daylightSavingAdjust(new Date(m,g,1-t));for(var Q=0;Q";var R=!j?"":'";for(t=0;t<7;t++){var I=p?p.apply(a.input?a.input[0]:null,[r]):[true,""],F=r.getMonth()!=g,L=F&&!K||!I[0]||k&&ro;R+='";r.setDate(r.getDate()+1);r=this._daylightSavingAdjust(r)}y+=R+""}g++;if(g>11){g=0;m++}y+="
        '+this._get(a,"weekHeader")+"
        '+this._get(a,"calculateWeek")(r)+""+(F&&!D?" ":L?''+ -r.getDate()+"":''+r.getDate()+"")+"
        "+(l?"
        "+(i[0]>0&&G==i[1]-1?'
        ':""):"");O+=y}w+=O}w+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'': -"");a._keyEvent=false;return w},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var j=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),k='
        ',o="";if(h||!j)o+=''+i[b]+"";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='"}u||(k+=o+(h||!(j&&l)?" ":""));if(!a.yearshtml){a.yearshtml="";if(h||!l)k+=''+c+"";else{g=this._get(a,"yearRange").split(":");var s=(new Date).getFullYear();i=function(q){q=q.match(/c[+-].*/)?c+parseInt(q.substring(1),10):q.match(/[+-].*/)?s+parseInt(q,10):parseInt(q,10);return isNaN(q)?s:q};b=i(g[0]);g=Math.max(b,i(g[1]||""));b=e?Math.max(b, -e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(a.yearshtml+='";k+=a.yearshtml;a.yearshtml=null}}k+=this._get(a,"yearSuffix");if(u)k+=(h||!(j&&l)?" ":"")+o;k+="
        ";return k},_adjustInstDate:function(a,b,c){var e=a.drawYear+(c=="Y"?b:0),f=a.drawMonth+ -(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&ba?a:b},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");if(b)b.apply(a.input? -a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a);c=this._daylightSavingAdjust(new Date(c, -e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a, -"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker=function(a){if(!this.length)return this; -if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));return this.each(function(){typeof a== -"string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new M;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.16";window["DP_jQuery_"+B]=d})(jQuery); -; \ No newline at end of file diff --git a/app/views/general/_stylesheet_includes.html.erb b/app/views/general/_stylesheet_includes.html.erb index 2571dd4ad..b3f32054c 100644 --- a/app/views/general/_stylesheet_includes.html.erb +++ b/app/views/general/_stylesheet_includes.html.erb @@ -10,7 +10,6 @@ <% if !params[:print_stylesheet].nil? %> <%= stylesheet_link_tag 'print', :rel => "stylesheet", :media => "all" %> <% end %> - <%= stylesheet_link_tag 'jquery-ui-1.8.15.custom.css', :rel => 'stylesheet'%> diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index 0815809b3..c1f9335b1 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -5,7 +5,6 @@ <%= site_name %> admin<%= @title ? ":" : "" %> <%=@title%> <%= javascript_include_tag "admin" %> - <%= stylesheet_link_tag "jquery-ui-1.8.15.custom.css", :rel => 'stylesheet'%> <%= stylesheet_link_tag "admin", :title => "Main", :rel => "stylesheet" %> diff --git a/config/application.rb b/config/application.rb index 9ef9520b0..4fc6f83e5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -89,7 +89,6 @@ module Alaveteli # ... while these are individual files that can't easily be # grouped: config.assets.precompile += ['jquery.fancybox-1.3.4.pack.js', - 'jquery-ui-1.8.15.custom.css', 'jquery.Jcrop.css', 'excanvas.min.js', 'fonts.css', -- cgit v1.2.3 From 27b0f4016b4dec406261061b4916e02df346b57d Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 25 Nov 2013 17:48:17 +0000 Subject: Update to jquery-rails to a version without jquery-ui The jquery-rails documentation for the latest version now recommends using the jquery-ui-rails plugin instead. To avoid the possible confusion of having two copies of jquery-ui in different gems, update jquery-rails to the version that doesn't contain jquery-ui. --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 25ac5ae61..9fa16540a 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'charlock_holmes' gem 'dynamic_form' gem 'exception_notification' gem 'fastercsv', '>=1.5.5' -gem 'jquery-rails', '~> 2.1' +gem 'jquery-rails', '~> 3.0.4' gem 'json' gem 'mahoro' gem 'net-http-local' diff --git a/Gemfile.lock b/Gemfile.lock index 43926e6c5..cdbfcba64 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -114,7 +114,7 @@ GEM highline (1.6.19) hike (1.2.3) i18n (0.6.5) - jquery-rails (2.3.0) + jquery-rails (3.0.4) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.0) @@ -293,7 +293,7 @@ DEPENDENCIES gettext gettext_i18n_rails globalize3! - jquery-rails (~> 2.1) + jquery-rails (~> 3.0.4) json locale mahoro -- cgit v1.2.3 From f91d66d42f517f778cac130466b7cffc7fd8b085 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 25 Nov 2013 18:02:58 +0000 Subject: Use the jquery-rails-ui gem, just including the modules we need As far as I can tell, we only use the 'tabs' module in admin and 'datepicker' on the user-facing part of the site. The advantage of using this packaging of the gem is that its assets are in the gem, which simplifies things greatly - otherwise we'd end up doing something like rewrite the jquery-ui CSS to SCSS, referencing the image assets via sass-rails helpers or keep them in their expected paths in public or something. (Thanks to Louise Crow for pointing out the problem of just moving jquery-ui's image assets into the asset pipeline.) --- Gemfile | 1 + Gemfile.lock | 3 +++ app/assets/javascripts/admin.js | 1 + app/assets/javascripts/application.js | 1 + app/assets/stylesheets/application.css | 2 ++ 5 files changed, 8 insertions(+) diff --git a/Gemfile b/Gemfile index 9fa16540a..071808e5d 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ gem 'dynamic_form' gem 'exception_notification' gem 'fastercsv', '>=1.5.5' gem 'jquery-rails', '~> 3.0.4' +gem 'jquery-ui-rails' gem 'json' gem 'mahoro' gem 'net-http-local' diff --git a/Gemfile.lock b/Gemfile.lock index cdbfcba64..3f73bcce7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -117,6 +117,8 @@ GEM jquery-rails (3.0.4) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) + jquery-ui-rails (4.1.0) + railties (>= 3.1.0) json (1.8.0) libv8 (3.16.14.3) linecache (0.46) @@ -294,6 +296,7 @@ DEPENDENCIES gettext_i18n_rails globalize3! jquery-rails (~> 3.0.4) + jquery-ui-rails json locale mahoro diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index f7684f3be..0b5d56525 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -1,5 +1,6 @@ // ... //= require jquery +//= require jquery.ui.tabs //= require admin/bootstrap-collapse //= require admin/bootstrap-tab //= require admin/admin diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index f2413bde9..d8aed6346 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,5 +1,6 @@ // ... //= require jquery +//= require jquery.ui.datepicker //= require jquery.cookie //= require general //= require ba-throttle-debounce diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 207e3e980..097221b2f 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -2,4 +2,6 @@ *= require_self *= require main *= require custom +*= require jquery.ui.datepicker +*= require jquery.ui.tabs */ -- cgit v1.2.3 From 171cd53005c439ab76f2e8ca41d13b9b329a800c Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 25 Nov 2013 17:31:55 +0000 Subject: Ruby 1.8 compatibility fixes. --- lib/tasks/temp.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tasks/temp.rake b/lib/tasks/temp.rake index bdee3027b..67fa10174 100644 --- a/lib/tasks/temp.rake +++ b/lib/tasks/temp.rake @@ -12,10 +12,10 @@ namespace :temp do f = is_gz ? Zlib::GzipReader.open(log_file_path) : File.open(log_file_path, 'r') processed = 0 f.each_line do |line| - line.force_encoding('ASCII-8BIT') - if request_match = line.match(/^Started (?GET|OPTIONS|POST) "(?\/request\/.*?)"/) + line.force_encoding('ASCII-8BIT') if RUBY_VERSION.to_f >= 1.9 + if request_match = line.match(/^Started (GET|OPTIONS|POST) "(\/request\/.*?)"/) next if line.match(/request\/\d+\/response/) - urls[request_match[:path]] += 1 + urls[request_match[2]] += 1 processed += 1 end end -- cgit v1.2.3 From dda7f4f96e794fd705fc1d80818cd1f291fdc94e Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Tue, 26 Nov 2013 20:35:47 -0200 Subject: Specify a build as rbenv doesn't fuzzy match versions (Thanks to @mhl) --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index 227cea215..7fa1d1ef4 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.0.0 +2.0.0-p353 -- cgit v1.2.3 From 20089cf7b5045d504b7240bc92e2b6068fcc2246 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 29 Nov 2013 10:41:32 +0000 Subject: With Rails 3.2, switching theme requires asset recompilation --- script/switch-theme.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/script/switch-theme.rb b/script/switch-theme.rb index 47f81c7a8..03d59a2f9 100755 --- a/script/switch-theme.rb +++ b/script/switch-theme.rb @@ -117,4 +117,8 @@ symlink(full_theme_path, requested_theme) STDERR.puts """Switched to #{requested_theme}! -You will need to restart any development server you have running.""" +You will need to: + 1. restart any development server you have running. + 2. run: bundle exec rake assets:clean + 3. run: bundle exec rake assets:precompile +""" -- cgit v1.2.3 From 600c3b356d804546bfa31fef89979cc1c44f3756 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 28 Nov 2013 14:36:36 +0000 Subject: Move strip_attributes out of vendor/plugins --- config/initializers/strip_attributes.rb | 2 + lib/strip_attributes/README.rdoc | 77 ++++++++++++++++++ lib/strip_attributes/Rakefile | 30 ++++++++ lib/strip_attributes/strip_attributes.rb | 37 +++++++++ lib/strip_attributes/test/strip_attributes_test.rb | 90 ++++++++++++++++++++++ lib/strip_attributes/test/test_helper.rb | 20 +++++ spec/spec_helper.rb | 1 + vendor/plugins/strip_attributes/.gitignore | 1 - vendor/plugins/strip_attributes/README.rdoc | 77 ------------------ vendor/plugins/strip_attributes/Rakefile | 30 -------- vendor/plugins/strip_attributes/init.rb | 2 - .../strip_attributes/lib/strip_attributes.rb | 37 --------- .../strip_attributes/test/strip_attributes_test.rb | 90 ---------------------- .../plugins/strip_attributes/test/test_helper.rb | 20 ----- 14 files changed, 257 insertions(+), 257 deletions(-) create mode 100644 config/initializers/strip_attributes.rb create mode 100644 lib/strip_attributes/README.rdoc create mode 100644 lib/strip_attributes/Rakefile create mode 100644 lib/strip_attributes/strip_attributes.rb create mode 100644 lib/strip_attributes/test/strip_attributes_test.rb create mode 100644 lib/strip_attributes/test/test_helper.rb delete mode 100644 vendor/plugins/strip_attributes/.gitignore delete mode 100644 vendor/plugins/strip_attributes/README.rdoc delete mode 100644 vendor/plugins/strip_attributes/Rakefile delete mode 100644 vendor/plugins/strip_attributes/init.rb delete mode 100644 vendor/plugins/strip_attributes/lib/strip_attributes.rb delete mode 100644 vendor/plugins/strip_attributes/test/strip_attributes_test.rb delete mode 100644 vendor/plugins/strip_attributes/test/test_helper.rb diff --git a/config/initializers/strip_attributes.rb b/config/initializers/strip_attributes.rb new file mode 100644 index 000000000..25f70b2f3 --- /dev/null +++ b/config/initializers/strip_attributes.rb @@ -0,0 +1,2 @@ +require 'strip_attributes/strip_attributes' +ActiveRecord::Base.extend(StripAttributes) diff --git a/lib/strip_attributes/README.rdoc b/lib/strip_attributes/README.rdoc new file mode 100644 index 000000000..bd55c0c1c --- /dev/null +++ b/lib/strip_attributes/README.rdoc @@ -0,0 +1,77 @@ +== StripAttributes + +StripAttributes is a Rails plugin that automatically strips all ActiveRecord +model attributes of leading and trailing whitespace before validation. If the +attribute is blank, it strips the value to +nil+. + +It works by adding a before_validation hook to the record. By default, all +attributes are stripped of whitespace, but :only and :except +options can be used to limit which attributes are stripped. Both options accept +a single attribute (:only => :field) or arrays of attributes (:except => +[:field1, :field2, :field3]). + +=== Examples + + class DrunkPokerPlayer < ActiveRecord::Base + strip_attributes! + end + + class SoberPokerPlayer < ActiveRecord::Base + strip_attributes! :except => :boxers + end + + class ConservativePokerPlayer < ActiveRecord::Base + strip_attributes! :only => [:shoe, :sock, :glove] + end + +=== Installation + +Option 1. Use the standard Rails plugin install (assuming Rails 2.1). + + ./script/plugin install git://github.com/rmm5t/strip_attributes.git + +Option 2. Use git submodules + + git submodule add git://github.com/rmm5t/strip_attributes.git vendor/plugins/strip_attributes + +Option 3. Use braid[http://github.com/evilchelu/braid/tree/master] (assuming +you're using git) + + braid add --rails_plugin git://github.com/rmm5t/strip_attributes.git + git merge braid/track + +=== Other + +If you want to use this outside of Rails, extend StripAttributes in your +ActiveRecord model after putting strip_attributes in your $LOAD_PATH: + + require 'strip_attributes' + class SomeModel < ActiveRecord::Base + extend StripAttributes + strip_attributes! + end + +=== Support + +The StripAttributes homepage is http://stripattributes.rubyforge.org. You can +find the StripAttributes RubyForge progject page at: +http://rubyforge.org/projects/stripattributes + +StripAttributes source is hosted on GitHub[http://github.com/]: +http://github.com/rmm5t/strip_attributes + +Feel free to submit suggestions or feature requests. If you send a patch, +remember to update the corresponding unit tests. In fact, I prefer new features +to be submitted in the form of new unit tests. + +=== Credits + +The idea was triggered by the information at +http://wiki.rubyonrails.org/rails/pages/HowToStripWhitespaceFromModelFields +but was modified from the original to include more idiomatic ruby and rails +support. + +=== License + +Copyright (c) 2007-2008 Ryan McGeary released under the MIT license +http://en.wikipedia.org/wiki/MIT_License \ No newline at end of file diff --git a/lib/strip_attributes/Rakefile b/lib/strip_attributes/Rakefile new file mode 100644 index 000000000..05b0c14ad --- /dev/null +++ b/lib/strip_attributes/Rakefile @@ -0,0 +1,30 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the stripattributes plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the stripattributes plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Stripattributes' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +desc 'Publishes rdoc to rubyforge server' +task :publish_rdoc => :rdoc do + cmd = "scp -r rdoc/* rmm5t@rubyforge.org:/var/www/gforge-projects/stripattributes" + puts "\nPublishing rdoc: #{cmd}\n\n" + system(cmd) +end + diff --git a/lib/strip_attributes/strip_attributes.rb b/lib/strip_attributes/strip_attributes.rb new file mode 100644 index 000000000..130d10185 --- /dev/null +++ b/lib/strip_attributes/strip_attributes.rb @@ -0,0 +1,37 @@ +module StripAttributes + # Strips whitespace from model fields and leaves nil values as nil. + # XXX this differs from official StripAttributes, as it doesn't make blank cells null. + def strip_attributes!(options = nil) + before_validation do |record| + attribute_names = StripAttributes.narrow(record.attribute_names, options) + + attribute_names.each do |attribute_name| + value = record[attribute_name] + if value.respond_to?(:strip) + stripped = value.strip + if stripped != value + record[attribute_name] = (value.nil?) ? nil : stripped + end + end + end + end + end + + # Necessary because Rails has removed the narrowing of attributes using :only + # and :except on Base#attributes + def self.narrow(attribute_names, options) + if options.nil? + attribute_names + else + if except = options[:except] + except = Array(except).collect { |attribute| attribute.to_s } + attribute_names - except + elsif only = options[:only] + only = Array(only).collect { |attribute| attribute.to_s } + attribute_names & only + else + raise ArgumentError, "Options does not specify :except or :only (#{options.keys.inspect})" + end + end + end +end diff --git a/lib/strip_attributes/test/strip_attributes_test.rb b/lib/strip_attributes/test/strip_attributes_test.rb new file mode 100644 index 000000000..8158dc664 --- /dev/null +++ b/lib/strip_attributes/test/strip_attributes_test.rb @@ -0,0 +1,90 @@ +require "#{File.dirname(__FILE__)}/test_helper" + +module MockAttributes + def self.included(base) + base.column :foo, :string + base.column :bar, :string + base.column :biz, :string + base.column :baz, :string + end +end + +class StripAllMockRecord < ActiveRecord::Base + include MockAttributes + strip_attributes! +end + +class StripOnlyOneMockRecord < ActiveRecord::Base + include MockAttributes + strip_attributes! :only => :foo +end + +class StripOnlyThreeMockRecord < ActiveRecord::Base + include MockAttributes + strip_attributes! :only => [:foo, :bar, :biz] +end + +class StripExceptOneMockRecord < ActiveRecord::Base + include MockAttributes + strip_attributes! :except => :foo +end + +class StripExceptThreeMockRecord < ActiveRecord::Base + include MockAttributes + strip_attributes! :except => [:foo, :bar, :biz] +end + +class StripAttributesTest < Test::Unit::TestCase + def setup + @init_params = { :foo => "\tfoo", :bar => "bar \t ", :biz => "\tbiz ", :baz => "" } + end + + def test_should_exist + assert Object.const_defined?(:StripAttributes) + end + + def test_should_strip_all_fields + record = StripAllMockRecord.new(@init_params) + record.valid? + assert_equal "foo", record.foo + assert_equal "bar", record.bar + assert_equal "biz", record.biz + assert_equal "", record.baz + end + + def test_should_strip_only_one_field + record = StripOnlyOneMockRecord.new(@init_params) + record.valid? + assert_equal "foo", record.foo + assert_equal "bar \t ", record.bar + assert_equal "\tbiz ", record.biz + assert_equal "", record.baz + end + + def test_should_strip_only_three_fields + record = StripOnlyThreeMockRecord.new(@init_params) + record.valid? + assert_equal "foo", record.foo + assert_equal "bar", record.bar + assert_equal "biz", record.biz + assert_equal "", record.baz + end + + def test_should_strip_all_except_one_field + record = StripExceptOneMockRecord.new(@init_params) + record.valid? + assert_equal "\tfoo", record.foo + assert_equal "bar", record.bar + assert_equal "biz", record.biz + assert_equal "", record.baz + end + + def test_should_strip_all_except_three_fields + record = StripExceptThreeMockRecord.new(@init_params) + record.valid? + assert_equal "\tfoo", record.foo + assert_equal "bar \t ", record.bar + assert_equal "\tbiz ", record.biz + assert_equal "", record.baz + end +end diff --git a/lib/strip_attributes/test/test_helper.rb b/lib/strip_attributes/test/test_helper.rb new file mode 100644 index 000000000..7d06c40db --- /dev/null +++ b/lib/strip_attributes/test/test_helper.rb @@ -0,0 +1,20 @@ +require 'test/unit' +require 'rubygems' +require 'active_record' + +PLUGIN_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..")) + +$LOAD_PATH.unshift "#{PLUGIN_ROOT}/lib" +require "#{PLUGIN_ROOT}/init" + +class ActiveRecord::Base + alias_method :save, :valid? + def self.columns() + @columns ||= [] + end + + def self.column(name, sql_type = nil, default = nil, null = true) + @columns ||= [] + @columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type, null) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d22f3c0ff..b61251246 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,7 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ SimpleCov.start('rails') do add_filter 'commonlib' add_filter 'vendor/plugins' + add_filter 'lib/strip_attributes' end Spork.prefork do diff --git a/vendor/plugins/strip_attributes/.gitignore b/vendor/plugins/strip_attributes/.gitignore deleted file mode 100644 index 1a240903a..000000000 --- a/vendor/plugins/strip_attributes/.gitignore +++ /dev/null @@ -1 +0,0 @@ -rdoc \ No newline at end of file diff --git a/vendor/plugins/strip_attributes/README.rdoc b/vendor/plugins/strip_attributes/README.rdoc deleted file mode 100644 index bd55c0c1c..000000000 --- a/vendor/plugins/strip_attributes/README.rdoc +++ /dev/null @@ -1,77 +0,0 @@ -== StripAttributes - -StripAttributes is a Rails plugin that automatically strips all ActiveRecord -model attributes of leading and trailing whitespace before validation. If the -attribute is blank, it strips the value to +nil+. - -It works by adding a before_validation hook to the record. By default, all -attributes are stripped of whitespace, but :only and :except -options can be used to limit which attributes are stripped. Both options accept -a single attribute (:only => :field) or arrays of attributes (:except => -[:field1, :field2, :field3]). - -=== Examples - - class DrunkPokerPlayer < ActiveRecord::Base - strip_attributes! - end - - class SoberPokerPlayer < ActiveRecord::Base - strip_attributes! :except => :boxers - end - - class ConservativePokerPlayer < ActiveRecord::Base - strip_attributes! :only => [:shoe, :sock, :glove] - end - -=== Installation - -Option 1. Use the standard Rails plugin install (assuming Rails 2.1). - - ./script/plugin install git://github.com/rmm5t/strip_attributes.git - -Option 2. Use git submodules - - git submodule add git://github.com/rmm5t/strip_attributes.git vendor/plugins/strip_attributes - -Option 3. Use braid[http://github.com/evilchelu/braid/tree/master] (assuming -you're using git) - - braid add --rails_plugin git://github.com/rmm5t/strip_attributes.git - git merge braid/track - -=== Other - -If you want to use this outside of Rails, extend StripAttributes in your -ActiveRecord model after putting strip_attributes in your $LOAD_PATH: - - require 'strip_attributes' - class SomeModel < ActiveRecord::Base - extend StripAttributes - strip_attributes! - end - -=== Support - -The StripAttributes homepage is http://stripattributes.rubyforge.org. You can -find the StripAttributes RubyForge progject page at: -http://rubyforge.org/projects/stripattributes - -StripAttributes source is hosted on GitHub[http://github.com/]: -http://github.com/rmm5t/strip_attributes - -Feel free to submit suggestions or feature requests. If you send a patch, -remember to update the corresponding unit tests. In fact, I prefer new features -to be submitted in the form of new unit tests. - -=== Credits - -The idea was triggered by the information at -http://wiki.rubyonrails.org/rails/pages/HowToStripWhitespaceFromModelFields -but was modified from the original to include more idiomatic ruby and rails -support. - -=== License - -Copyright (c) 2007-2008 Ryan McGeary released under the MIT license -http://en.wikipedia.org/wiki/MIT_License \ No newline at end of file diff --git a/vendor/plugins/strip_attributes/Rakefile b/vendor/plugins/strip_attributes/Rakefile deleted file mode 100644 index 05b0c14ad..000000000 --- a/vendor/plugins/strip_attributes/Rakefile +++ /dev/null @@ -1,30 +0,0 @@ -require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' - -desc 'Default: run unit tests.' -task :default => :test - -desc 'Test the stripattributes plugin.' -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -desc 'Generate documentation for the stripattributes plugin.' -Rake::RDocTask.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'Stripattributes' - rdoc.options << '--line-numbers' << '--inline-source' - rdoc.rdoc_files.include('README.rdoc') - rdoc.rdoc_files.include('lib/**/*.rb') -end - -desc 'Publishes rdoc to rubyforge server' -task :publish_rdoc => :rdoc do - cmd = "scp -r rdoc/* rmm5t@rubyforge.org:/var/www/gforge-projects/stripattributes" - puts "\nPublishing rdoc: #{cmd}\n\n" - system(cmd) -end - diff --git a/vendor/plugins/strip_attributes/init.rb b/vendor/plugins/strip_attributes/init.rb deleted file mode 100644 index 2c71b29bc..000000000 --- a/vendor/plugins/strip_attributes/init.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'strip_attributes' -ActiveRecord::Base.extend(StripAttributes) diff --git a/vendor/plugins/strip_attributes/lib/strip_attributes.rb b/vendor/plugins/strip_attributes/lib/strip_attributes.rb deleted file mode 100644 index 130d10185..000000000 --- a/vendor/plugins/strip_attributes/lib/strip_attributes.rb +++ /dev/null @@ -1,37 +0,0 @@ -module StripAttributes - # Strips whitespace from model fields and leaves nil values as nil. - # XXX this differs from official StripAttributes, as it doesn't make blank cells null. - def strip_attributes!(options = nil) - before_validation do |record| - attribute_names = StripAttributes.narrow(record.attribute_names, options) - - attribute_names.each do |attribute_name| - value = record[attribute_name] - if value.respond_to?(:strip) - stripped = value.strip - if stripped != value - record[attribute_name] = (value.nil?) ? nil : stripped - end - end - end - end - end - - # Necessary because Rails has removed the narrowing of attributes using :only - # and :except on Base#attributes - def self.narrow(attribute_names, options) - if options.nil? - attribute_names - else - if except = options[:except] - except = Array(except).collect { |attribute| attribute.to_s } - attribute_names - except - elsif only = options[:only] - only = Array(only).collect { |attribute| attribute.to_s } - attribute_names & only - else - raise ArgumentError, "Options does not specify :except or :only (#{options.keys.inspect})" - end - end - end -end diff --git a/vendor/plugins/strip_attributes/test/strip_attributes_test.rb b/vendor/plugins/strip_attributes/test/strip_attributes_test.rb deleted file mode 100644 index 8158dc664..000000000 --- a/vendor/plugins/strip_attributes/test/strip_attributes_test.rb +++ /dev/null @@ -1,90 +0,0 @@ -require "#{File.dirname(__FILE__)}/test_helper" - -module MockAttributes - def self.included(base) - base.column :foo, :string - base.column :bar, :string - base.column :biz, :string - base.column :baz, :string - end -end - -class StripAllMockRecord < ActiveRecord::Base - include MockAttributes - strip_attributes! -end - -class StripOnlyOneMockRecord < ActiveRecord::Base - include MockAttributes - strip_attributes! :only => :foo -end - -class StripOnlyThreeMockRecord < ActiveRecord::Base - include MockAttributes - strip_attributes! :only => [:foo, :bar, :biz] -end - -class StripExceptOneMockRecord < ActiveRecord::Base - include MockAttributes - strip_attributes! :except => :foo -end - -class StripExceptThreeMockRecord < ActiveRecord::Base - include MockAttributes - strip_attributes! :except => [:foo, :bar, :biz] -end - -class StripAttributesTest < Test::Unit::TestCase - def setup - @init_params = { :foo => "\tfoo", :bar => "bar \t ", :biz => "\tbiz ", :baz => "" } - end - - def test_should_exist - assert Object.const_defined?(:StripAttributes) - end - - def test_should_strip_all_fields - record = StripAllMockRecord.new(@init_params) - record.valid? - assert_equal "foo", record.foo - assert_equal "bar", record.bar - assert_equal "biz", record.biz - assert_equal "", record.baz - end - - def test_should_strip_only_one_field - record = StripOnlyOneMockRecord.new(@init_params) - record.valid? - assert_equal "foo", record.foo - assert_equal "bar \t ", record.bar - assert_equal "\tbiz ", record.biz - assert_equal "", record.baz - end - - def test_should_strip_only_three_fields - record = StripOnlyThreeMockRecord.new(@init_params) - record.valid? - assert_equal "foo", record.foo - assert_equal "bar", record.bar - assert_equal "biz", record.biz - assert_equal "", record.baz - end - - def test_should_strip_all_except_one_field - record = StripExceptOneMockRecord.new(@init_params) - record.valid? - assert_equal "\tfoo", record.foo - assert_equal "bar", record.bar - assert_equal "biz", record.biz - assert_equal "", record.baz - end - - def test_should_strip_all_except_three_fields - record = StripExceptThreeMockRecord.new(@init_params) - record.valid? - assert_equal "\tfoo", record.foo - assert_equal "bar \t ", record.bar - assert_equal "\tbiz ", record.biz - assert_equal "", record.baz - end -end diff --git a/vendor/plugins/strip_attributes/test/test_helper.rb b/vendor/plugins/strip_attributes/test/test_helper.rb deleted file mode 100644 index 7d06c40db..000000000 --- a/vendor/plugins/strip_attributes/test/test_helper.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'test/unit' -require 'rubygems' -require 'active_record' - -PLUGIN_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..")) - -$LOAD_PATH.unshift "#{PLUGIN_ROOT}/lib" -require "#{PLUGIN_ROOT}/init" - -class ActiveRecord::Base - alias_method :save, :valid? - def self.columns() - @columns ||= [] - end - - def self.column(name, sql_type = nil, default = nil, null = true) - @columns ||= [] - @columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type, null) - end -end -- cgit v1.2.3 From 1f1bae22d4f7fda389bc4f86bbf666c57b93ab83 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 28 Nov 2013 15:10:23 +0000 Subject: Move has_tag_string out of vendor/plugins --- config/initializers/has_tag_string.rb | 2 + lib/has_tag_string/README.txt | 1 + lib/has_tag_string/has_tag_string.rb | 165 +++++++++++++++++++++ spec/spec_helper.rb | 1 + vendor/plugins/has_tag_string/README.txt | 1 - vendor/plugins/has_tag_string/init.rb | 2 - .../plugins/has_tag_string/lib/has_tag_string.rb | 165 --------------------- 7 files changed, 169 insertions(+), 168 deletions(-) create mode 100644 config/initializers/has_tag_string.rb create mode 100644 lib/has_tag_string/README.txt create mode 100644 lib/has_tag_string/has_tag_string.rb delete mode 100644 vendor/plugins/has_tag_string/README.txt delete mode 100644 vendor/plugins/has_tag_string/init.rb delete mode 100644 vendor/plugins/has_tag_string/lib/has_tag_string.rb diff --git a/config/initializers/has_tag_string.rb b/config/initializers/has_tag_string.rb new file mode 100644 index 000000000..5fa33cc70 --- /dev/null +++ b/config/initializers/has_tag_string.rb @@ -0,0 +1,2 @@ +require 'has_tag_string/has_tag_string' + diff --git a/lib/has_tag_string/README.txt b/lib/has_tag_string/README.txt new file mode 100644 index 000000000..0d3a38229 --- /dev/null +++ b/lib/has_tag_string/README.txt @@ -0,0 +1 @@ +Plugin used only in WhatDoTheyKnow right now. diff --git a/lib/has_tag_string/has_tag_string.rb b/lib/has_tag_string/has_tag_string.rb new file mode 100644 index 000000000..4022faaac --- /dev/null +++ b/lib/has_tag_string/has_tag_string.rb @@ -0,0 +1,165 @@ +# lib/has_tag_string.rb: +# Lets a model have tags, represented as space separate strings in a public +# interface, but stored in the database as keys. Each tag can have a value +# followed by a colon - e.g. url:http://www.flourish.org +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ + +module HasTagString + # Represents one tag of one model. + # The migration to make this is currently only in WDTK code. + class HasTagStringTag < ActiveRecord::Base + # XXX strip_attributes! + + validates_presence_of :name + + # Return instance of the model that this tag tags + def tagged_model + return self.model.constantize.find(self.model_id) + end + + # For display purposes, returns the name and value as a:b, or + # if there is no value just the name a + def name_and_value + ret = self.name + if !self.value.nil? + ret += ":" + self.value + end + return ret + end + + # Parses a text version of one single tag, such as "a:b" and returns + # the name and value, with nil for value if there isn't one. + def HasTagStringTag.split_tag_into_name_value(tag) + sections = tag.split(/:/) + name = sections[0] + if sections[1] + value = sections[1,sections.size].join(":") + else + value = nil + end + return name, value + end + end + + # Methods which are added to the model instances being tagged + module InstanceMethods + # Given an input string of tags, sets all tags to that string. + # XXX This immediately saves the new tags. + def tag_string=(tag_string) + if tag_string.nil? + tag_string = "" + end + + tag_string = tag_string.strip + # split tags apart + tags = tag_string.split(/\s+/).uniq + + ActiveRecord::Base.transaction do + for tag in self.tags + tag.destroy + end + self.tags = [] + for tag in tags + # see if is a machine tags (i.e. a tag which has a value) + name, value = HasTagStringTag.split_tag_into_name_value(tag) + + tag = HasTagStringTag.new( + :model => self.class.base_class.to_s, + :model_id => self.id, + :name => name, :value => value + ) + self.tags << tag + end + end + end + + # Returns the tags the model has, as a space separated string + def tag_string + return self.tags.map { |t| t.name_and_value }.join(' ') + end + + # Returns the tags the model has, as an array of pairs of key/value + # (this can't be a dictionary as you can have multiple instances of a + # key with different values) + def tag_array + return self.tags.map { |t| [t.name, t.value] } + end + + # Returns a list of all the strings someone might want to search for. + # So that is the key by itself, or the key and value. + # e.g. if a request was tagged openlylocal_id:12345, they might + # want to search for "openlylocal_id" or for "openlylocal_id:12345" to find it. + def tag_array_for_search + ret = {} + for tag in self.tags + ret[tag.name] = 1 + ret[tag.name_and_value] = 1 + end + + return ret.keys.sort + end + + # Test to see if class is tagged with the given tag + def has_tag?(tag_as_string) + for tag in self.tags + if tag.name == tag_as_string + return true + end + end + return false + end + + class TagNotFound < StandardError + end + + # If the tag is a machine tag, returns array of its values + def get_tag_values(tag_as_string) + found = false + results = [] + for tag in self.tags + if tag.name == tag_as_string + found = true + if !tag.value.nil? + results << tag.value + end + end + end + if !found + raise TagNotFound + end + return results + end + + # Adds a new tag to the model, if it isn't already there + def add_tag_if_not_already_present(tag_as_string) + self.tag_string = self.tag_string + " " + tag_as_string + end + end + + # Methods which are added to the model class being tagged + module ClassMethods + # Find all public bodies with a particular tag + def find_by_tag(tag_as_string) + return HasTagStringTag.find(:all, :conditions => + ['name = ? and model = ?', tag_as_string, self.to_s ] + ).map { |t| t.tagged_model }.sort { |a,b| a.name <=> b.name }.uniq + end + end + + ###################################################################### + # Main entry point, add has_tag_string to your model. + module HasMethods + def has_tag_string() + has_many :tags, :conditions => "model = '" + self.to_s + "'", :foreign_key => "model_id", :class_name => 'HasTagString::HasTagStringTag' + + include InstanceMethods + self.class.send :include, ClassMethods + end + end + +end + +ActiveRecord::Base.extend HasTagString::HasMethods + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b61251246..d3cf7c9af 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,7 @@ SimpleCov.start('rails') do add_filter 'commonlib' add_filter 'vendor/plugins' add_filter 'lib/strip_attributes' + add_filter 'lib/has_tag_string' end Spork.prefork do diff --git a/vendor/plugins/has_tag_string/README.txt b/vendor/plugins/has_tag_string/README.txt deleted file mode 100644 index 0d3a38229..000000000 --- a/vendor/plugins/has_tag_string/README.txt +++ /dev/null @@ -1 +0,0 @@ -Plugin used only in WhatDoTheyKnow right now. diff --git a/vendor/plugins/has_tag_string/init.rb b/vendor/plugins/has_tag_string/init.rb deleted file mode 100644 index 4a07073a7..000000000 --- a/vendor/plugins/has_tag_string/init.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'has_tag_string' - diff --git a/vendor/plugins/has_tag_string/lib/has_tag_string.rb b/vendor/plugins/has_tag_string/lib/has_tag_string.rb deleted file mode 100644 index 4022faaac..000000000 --- a/vendor/plugins/has_tag_string/lib/has_tag_string.rb +++ /dev/null @@ -1,165 +0,0 @@ -# lib/has_tag_string.rb: -# Lets a model have tags, represented as space separate strings in a public -# interface, but stored in the database as keys. Each tag can have a value -# followed by a colon - e.g. url:http://www.flourish.org -# -# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. -# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ - -module HasTagString - # Represents one tag of one model. - # The migration to make this is currently only in WDTK code. - class HasTagStringTag < ActiveRecord::Base - # XXX strip_attributes! - - validates_presence_of :name - - # Return instance of the model that this tag tags - def tagged_model - return self.model.constantize.find(self.model_id) - end - - # For display purposes, returns the name and value as a:b, or - # if there is no value just the name a - def name_and_value - ret = self.name - if !self.value.nil? - ret += ":" + self.value - end - return ret - end - - # Parses a text version of one single tag, such as "a:b" and returns - # the name and value, with nil for value if there isn't one. - def HasTagStringTag.split_tag_into_name_value(tag) - sections = tag.split(/:/) - name = sections[0] - if sections[1] - value = sections[1,sections.size].join(":") - else - value = nil - end - return name, value - end - end - - # Methods which are added to the model instances being tagged - module InstanceMethods - # Given an input string of tags, sets all tags to that string. - # XXX This immediately saves the new tags. - def tag_string=(tag_string) - if tag_string.nil? - tag_string = "" - end - - tag_string = tag_string.strip - # split tags apart - tags = tag_string.split(/\s+/).uniq - - ActiveRecord::Base.transaction do - for tag in self.tags - tag.destroy - end - self.tags = [] - for tag in tags - # see if is a machine tags (i.e. a tag which has a value) - name, value = HasTagStringTag.split_tag_into_name_value(tag) - - tag = HasTagStringTag.new( - :model => self.class.base_class.to_s, - :model_id => self.id, - :name => name, :value => value - ) - self.tags << tag - end - end - end - - # Returns the tags the model has, as a space separated string - def tag_string - return self.tags.map { |t| t.name_and_value }.join(' ') - end - - # Returns the tags the model has, as an array of pairs of key/value - # (this can't be a dictionary as you can have multiple instances of a - # key with different values) - def tag_array - return self.tags.map { |t| [t.name, t.value] } - end - - # Returns a list of all the strings someone might want to search for. - # So that is the key by itself, or the key and value. - # e.g. if a request was tagged openlylocal_id:12345, they might - # want to search for "openlylocal_id" or for "openlylocal_id:12345" to find it. - def tag_array_for_search - ret = {} - for tag in self.tags - ret[tag.name] = 1 - ret[tag.name_and_value] = 1 - end - - return ret.keys.sort - end - - # Test to see if class is tagged with the given tag - def has_tag?(tag_as_string) - for tag in self.tags - if tag.name == tag_as_string - return true - end - end - return false - end - - class TagNotFound < StandardError - end - - # If the tag is a machine tag, returns array of its values - def get_tag_values(tag_as_string) - found = false - results = [] - for tag in self.tags - if tag.name == tag_as_string - found = true - if !tag.value.nil? - results << tag.value - end - end - end - if !found - raise TagNotFound - end - return results - end - - # Adds a new tag to the model, if it isn't already there - def add_tag_if_not_already_present(tag_as_string) - self.tag_string = self.tag_string + " " + tag_as_string - end - end - - # Methods which are added to the model class being tagged - module ClassMethods - # Find all public bodies with a particular tag - def find_by_tag(tag_as_string) - return HasTagStringTag.find(:all, :conditions => - ['name = ? and model = ?', tag_as_string, self.to_s ] - ).map { |t| t.tagged_model }.sort { |a,b| a.name <=> b.name }.uniq - end - end - - ###################################################################### - # Main entry point, add has_tag_string to your model. - module HasMethods - def has_tag_string() - has_many :tags, :conditions => "model = '" + self.to_s + "'", :foreign_key => "model_id", :class_name => 'HasTagString::HasTagStringTag' - - include InstanceMethods - self.class.send :include, ClassMethods - end - end - -end - -ActiveRecord::Base.extend HasTagString::HasMethods - -- cgit v1.2.3 From da7f8535505ac77feb09f51b7751558529dee67e Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 28 Nov 2013 16:13:44 +0000 Subject: Move acts_as_xapian out of vendor/plugins This includes making making sure that xapiandbs directory is moved with this version of the code. --- LICENSE.txt | 2 +- config/deploy.rb | 2 +- config/initializers/acts_as_xapian.rb | 25 + lib/acts_as_xapian/.gitignore | 3 + lib/acts_as_xapian/LICENSE.txt | 21 + lib/acts_as_xapian/README.txt | 276 ++++++ lib/acts_as_xapian/acts_as_xapian.rb | 979 +++++++++++++++++++++ lib/acts_as_xapian/generators/acts_as_xapian/USAGE | 1 + .../acts_as_xapian/acts_as_xapian_generator.rb | 13 + .../acts_as_xapian/templates/migration.rb | 14 + lib/acts_as_xapian/tasks/xapian.rake | 66 ++ script/compact-xapian-database | 2 +- spec/models/xapian_spec.rb | 2 +- spec/spec_helper.rb | 1 + vendor/plugins/acts_as_xapian/.cvsignore | 2 - vendor/plugins/acts_as_xapian/.gitignore | 3 - vendor/plugins/acts_as_xapian/LICENSE.txt | 21 - vendor/plugins/acts_as_xapian/README.txt | 276 ------ .../acts_as_xapian/generators/acts_as_xapian/USAGE | 1 - .../acts_as_xapian/acts_as_xapian_generator.rb | 13 - .../acts_as_xapian/templates/migration.rb | 14 - vendor/plugins/acts_as_xapian/init.rb | 7 - .../plugins/acts_as_xapian/lib/acts_as_xapian.rb | 979 --------------------- .../plugins/acts_as_xapian/lib/tasks/xapian.rake | 66 -- 24 files changed, 1403 insertions(+), 1386 deletions(-) create mode 100644 config/initializers/acts_as_xapian.rb create mode 100644 lib/acts_as_xapian/.gitignore create mode 100644 lib/acts_as_xapian/LICENSE.txt create mode 100644 lib/acts_as_xapian/README.txt create mode 100644 lib/acts_as_xapian/acts_as_xapian.rb create mode 100644 lib/acts_as_xapian/generators/acts_as_xapian/USAGE create mode 100644 lib/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb create mode 100644 lib/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb create mode 100644 lib/acts_as_xapian/tasks/xapian.rake delete mode 100644 vendor/plugins/acts_as_xapian/.cvsignore delete mode 100644 vendor/plugins/acts_as_xapian/.gitignore delete mode 100644 vendor/plugins/acts_as_xapian/LICENSE.txt delete mode 100644 vendor/plugins/acts_as_xapian/README.txt delete mode 100644 vendor/plugins/acts_as_xapian/generators/acts_as_xapian/USAGE delete mode 100644 vendor/plugins/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb delete mode 100644 vendor/plugins/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb delete mode 100644 vendor/plugins/acts_as_xapian/init.rb delete mode 100644 vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb delete mode 100644 vendor/plugins/acts_as_xapian/lib/tasks/xapian.rake diff --git a/LICENSE.txt b/LICENSE.txt index 23976d86e..21e66eedb 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -10,7 +10,7 @@ by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Note in particular, -- acts_as_xapian in vendor/plugins/ is licensed with an MIT license. +- acts_as_xapian in lib/acts_as_xapian is licensed with an MIT license. Can you explain briefly what the GNU Affero GPL is? We offer the source code of our websites to our users. The GNU Affero GPL has the diff --git a/config/deploy.rb b/config/deploy.rb index 8ab67bc98..a0189c855 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -58,7 +58,7 @@ namespace :deploy do "#{release_path}/public/favicon.ico" => "#{shared_path}/favicon.ico", "#{release_path}/files" => "#{shared_path}/files", "#{release_path}/cache" => "#{shared_path}/cache", - "#{release_path}/vendor/plugins/acts_as_xapian/xapiandbs" => "#{shared_path}/xapiandbs", + "#{release_path}/lib/acts_as_xapian/xapiandbs" => "#{shared_path}/xapiandbs", } # "ln -sf " creates a symbolic link but deletes if it already exists diff --git a/config/initializers/acts_as_xapian.rb b/config/initializers/acts_as_xapian.rb new file mode 100644 index 000000000..f82193c85 --- /dev/null +++ b/config/initializers/acts_as_xapian.rb @@ -0,0 +1,25 @@ +# acts_as_xapian/init.rb: +# +# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ + +# We're moving plugins out of vendor/plugins, since keeping them there +# is deprecated as of Rails 3.2, and the xapiandbs directory should be +# moved out of there along with the plugin itself. + +old_xapiandbs_path = Rails.root.join('vendor', + 'plugins', + 'acts_as_xapian', + 'xapiandbs') + +current_xapiandbs_path = Rails.root.join('lib', + 'acts_as_xapian', + 'xapiandbs') + +if File.exists? old_xapiandbs_path + unless File.exists? current_xapiandbs_path + File.rename old_xapiandbs_path, current_xapiandbs_path + end +end + +require 'acts_as_xapian/acts_as_xapian' diff --git a/lib/acts_as_xapian/.gitignore b/lib/acts_as_xapian/.gitignore new file mode 100644 index 000000000..60e95666f --- /dev/null +++ b/lib/acts_as_xapian/.gitignore @@ -0,0 +1,3 @@ +/xapiandbs +CVS +*.swp diff --git a/lib/acts_as_xapian/LICENSE.txt b/lib/acts_as_xapian/LICENSE.txt new file mode 100644 index 000000000..72d93c4be --- /dev/null +++ b/lib/acts_as_xapian/LICENSE.txt @@ -0,0 +1,21 @@ +acts_as_xapian is released under the MIT License. + +Copyright (c) 2008 UK Citizens Online Democracy. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the acts_as_xapian software and associated documentation files (the +"Software"), to deal in the Software without restriction, including without +limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/acts_as_xapian/README.txt b/lib/acts_as_xapian/README.txt new file mode 100644 index 000000000..a1d22ef3f --- /dev/null +++ b/lib/acts_as_xapian/README.txt @@ -0,0 +1,276 @@ +The official page for acts_as_xapian is now the Google Groups page. + +http://groups.google.com/group/acts_as_xapian + +frabcus's github repository is no longer the official repository, +find the official one from the Google Groups page. + +------------------------------------------------------------------------ + +Do patch this file if there is documentation missing / wrong. It's called +README.txt and is in git, using Textile formatting. The wiki page is just +copied from the README.txt file. + +Contents +======== + +* a. Introduction to acts_as_xapian +* b. Installation +* c. Comparison to acts_as_solr (as on 24 April 2008) +* d. Documentation - indexing +* e. Documentation - querying +* f. Configuration +* g. Performance +* h. Support + + +a. Introduction to acts_as_xapian +================================= + +"Xapian":http://www.xapian.org is a full text search engine library which has +Ruby bindings. acts_as_xapian adds support for it to Rails. It is an +alternative to acts_as_solr, acts_as_ferret, Ultrasphinx, acts_as_indexed, +acts_as_searchable or acts_as_tsearch. + +acts_as_xapian is deployed in production on these websites. +* "WhatDoTheyKnow":http://www.whatdotheyknow.com +* "MindBites":http://www.mindbites.com + +The section "c. Comparison to acts_as_solr" below will give you an idea of +acts_as_xapian's features. + +acts_as_xapian was started by Francis Irving in May 2008 for search and email +alerts in WhatDoTheyKnow, and so was supported by "mySociety":http://www.mysociety.org +and initially paid for by the "JRSST Charitable Trust":http://www.jrrt.org.uk/jrsstct.htm + + +b. Installation +=============== + +Retrieve the plugin directly from the git version control system by running +this command within your Rails app. + + git clone git://github.com/frabcus/acts_as_xapian.git vendor/plugins/acts_as_xapian + +Xapian 1.0.5 and associated Ruby bindings are also required. + +Debian or Ubuntu - install the packages libxapian15 and libxapian-ruby1.8. + +Mac OSX - follow the instructions for installing from source on +the "Installing Xapian":http://xapian.org/docs/install.html page - you need the +Xapian library and bindings (you don't need Omega). + +There is no Ruby Gem for Xapian, it would be great if you could make one! + + +c. Comparison to acts_as_solr (as on 24 April 2008) +============================= + +* Offline indexing only mode - which is a minus if you want changes +immediately reflected in the search index, and a plus if you were going to +have to implement your own offline indexing anyway. + +* Collapsing - the equivalent of SQL's "group by". You can specify a field +to collapse on, and only the most relevant result from each value of that +field is returned. Along with a count of how many there are in total. +acts_as_solr doesn't have this. + +* No highlighting - Xapian can't return you text highlighted with a search +query. You can try and make do with TextHelper::highlight (combined with +words_to_highlight below). I found the highlighting in acts_as_solr didn't +really understand the query anyway. + +* Date range searching - this exists in acts_as_solr, but I found it +wasn't documented well enough, and was hard to get working. + +* Spelling correction - "did you mean?" built in and just works. + +* Similar documents - acts_as_xapian has a simple command to find other models +that are like a specified model. + +* Multiple models - acts_as_xapian searches multiple types of model if you +like, returning them mixed up together by relevancy. This is like +multi_solr_search, only it is the default mode of operation and is properly +supported. + +* No daemons - However, if you have more than one web server, you'll need to +work out how to use "Xapian's remote backend":http://xapian.org/docs/remote.html. + +* One layer - full-powered Xapian is called directly from the Ruby, without +Solr getting in the way whenever you want to use a new feature from Lucene. + +* No Java - an advantage if you're more used to working in the rest of the +open source world. acts_as_xapian, it's pure Ruby and C++. + +* Xapian's awesome email list - the kids over at +"xapian-discuss":http://lists.xapian.org/mailman/listinfo/xapian-discuss +are super helpful. Useful if you need to extend and improve acts_as_xapian. The +Ruby bindings are mature and well maintained as part of Xapian. + + +d. Documentation - indexing +=========================== + +Xapian is an *offline indexing* search library - only one process can have the +Xapian database open for writing at once, and others that try meanwhile are +unceremoniously kicked out. For this reason, acts_as_xapian does not support +immediate writing to the database when your models change. + +Instead, there is a ActsAsXapianJob model which stores which models need +updating or deleting in the search index. A rake task 'xapian:update_index' +then performs the updates since last change. You can run it on a cron job, or +similar. + +Here's how to add indexing to your Rails app: + +1. Put acts_as_xapian in your models that need search indexing. e.g. + + acts_as_xapian :texts => [ :name, :short_name ], + :values => [ [ :created_at, 0, "created_at", :date ] ], + :terms => [ [ :variety, 'V', "variety" ] ] + +Options must include: + +* :texts, an array of fields for indexing with full text search. +e.g. :texts => [ :title, :body ] + +* :values, things which have a range of values for sorting, or for collapsing. +Specify an array quadruple of [ field, identifier, prefix, type ] where +** identifier is an arbitary numeric identifier for use in the Xapian database +** prefix is the part to use in search queries that goes before the : +** type can be any of :string, :number or :date + +e.g. :values => [ [ :created_at, 0, "created_at", :date ], +[ :size, 1, "size", :string ] ] + +* :terms, things which come with a prefix (before a :) in search queries. +Specify an array triple of [ field, char, prefix ] where +** char is an arbitary single upper case char used in the Xapian database, just +pick any single uppercase character, but use a different one for each prefix. +** prefix is the part to use in search queries that goes before the : +For example, if you were making Google and indexing to be able to later do a +query like "site:www.whatdotheyknow.com", then the prefix would be "site". + +e.g. :terms => [ [ :variety, 'V', "variety" ] ] + +A 'field' is a symbol referring to either an attribute or a function which +returns the text, date or number to index. Both 'identifier' and 'char' must be +the same for the same prefix in different models. + +Options may include: +* :eager_load, added as an :include clause when looking up search results in +database +* :if, either an attribute or a function which if returns false means the +object isn't indexed + +2. Generate a database migration to create the ActsAsXapianJob model: + + script/generate acts_as_xapian + rake db:migrate + +3. Call 'rake xapian:rebuild_index models="ModelName1 ModelName2"' to build the index +the first time (you must specify all your indexed models). It's put in a +development/test/production dir in acts_as_xapian/xapiandbs. See f. Configuration +below if you want to change this. + +4. Then from a cron job or a daemon, or by hand regularly!, call 'rake xapian:update_index' + + +e. Documentation - querying +=========================== + +Testing indexing +---------------- + +If you just want to test indexing is working, you'll find this rake task +useful (it has more options, see tasks/xapian.rake) + + rake xapian:query models="PublicBody User" query="moo" + +Performing a query +------------------ + +To perform a query from code call ActsAsXapian::Search.new. This takes in turn: +* model_classes - list of models to search, e.g. [PublicBody, InfoRequestEvent] +* query_string - Google like syntax, see below + +And then a hash of options: +* :offset - Offset of first result (default 0) +* :limit - Number of results per page +* :sort_by_prefix - Optionally, prefix of value to sort by, otherwise sort by relevance +* :sort_by_ascending - Default true (documents with higher values better/earlier), set to false for descending sort +* :collapse_by_prefix - Optionally, prefix of value to collapse by (i.e. only return most relevant result from group) + +Google like query syntax is as described in + "Xapian::QueryParser Syntax":http://www.xapian.org/docs/queryparser.html +Queries can include prefix:value parts, according to what you indexed in the +acts_as_xapian part above. You can also say things like model:InfoRequestEvent +to constrain by model in more complex ways than the :model parameter, or +modelid:InfoRequestEvent-100 to only find one specific object. + +Returns an ActsAsXapian::Search object. Useful methods are: +* description - a techy one, to check how the query has been parsed +* matches_estimated - a guesstimate at the total number of hits +* spelling_correction - the corrected query string if there is a correction, otherwise nil +* words_to_highlight - list of words for you to highlight, perhaps with TextHelper::highlight +* results - an array of hashes each containing: +** :model - your Rails model, this is what you most want! +** :weight - relevancy measure +** :percent - the weight as a %, 0 meaning the item did not match the query at all +** :collapse_count - number of results with the same prefix, if you specified collapse_by_prefix + +Finding similar models +---------------------- + +To find models that are similar to a given set of models call ActsAsXapian::Similar.new. This takes: +* model_classes - list of model classes to return models from within +* models - list of models that you want to find related ones to + +Returns an ActsAsXapian::Similar object. Has all methods from ActsAsXapian::Search above, except +for words_to_highlight. In addition has: +* important_terms - the terms extracted from the input models, that were used to search for output +You need the results methods to get the similar models. + + +f. Configuration +================ + +If you want to customise the configuration of acts_as_xapian, it will look for +a file called 'xapian.yml' under Rails.root/config. As is familiar from the +format of the database.yml file, separate :development, :test and :production +sections are expected. + +The following options are available: +* base_db_path - specifies the directory, relative to Rails.root, in which +acts_as_xapian stores its search index databases. Default is the directory +xapiandbs within the acts_as_xapian directory. + + +g. Performance +============== + +On development sites, acts_as_xapian automatically logs the time taken to do +searches. The time displayed is for the Xapian parts of the query; the Rails +database model lookups will be logged separately by ActiveRecord. Example: + + Xapian query (0.00029s) Search: hello + +To enable this, and other performance logging, on a production site, +temporarily add this to the end of your config/environment.rb + + ActiveRecord::Base.logger = Logger.new(STDOUT) + + +h. Support +========== + +Please ask any questions on the +"acts_as_xapian Google Group":http://groups.google.com/group/acts_as_xapian + +The official home page and repository for acts_as_xapian are the +"acts_as_xapian github page":http://github.com/frabcus/acts_as_xapian/wikis + +For more details about anything, see source code in lib/acts_as_xapian.rb + +Merging source instructions "Using git for collaboration" here: +http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html diff --git a/lib/acts_as_xapian/acts_as_xapian.rb b/lib/acts_as_xapian/acts_as_xapian.rb new file mode 100644 index 000000000..b30bb4d10 --- /dev/null +++ b/lib/acts_as_xapian/acts_as_xapian.rb @@ -0,0 +1,979 @@ +# encoding: utf-8 +# acts_as_xapian/lib/acts_as_xapian.rb: +# Xapian full text search in Ruby on Rails. +# +# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. +# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ +# +# Documentation +# ============= +# +# See ../README.txt foocumentation. Please update that file if you edit +# code. + +# Make it so if Xapian isn't installed, the Rails app doesn't fail completely, +# just when somebody does a search. +begin + require 'xapian' + $acts_as_xapian_bindings_available = true +rescue LoadError + STDERR.puts "acts_as_xapian: No Ruby bindings for Xapian installed" + $acts_as_xapian_bindings_available = false +end + +module ActsAsXapian + ###################################################################### + # Module level variables + # XXX must be some kind of cattr_accessor that can do this better + def ActsAsXapian.bindings_available + $acts_as_xapian_bindings_available + end + class NoXapianRubyBindingsError < StandardError + end + + @@db = nil + @@db_path = nil + @@writable_db = nil + @@init_values = [] + + # There used to be a problem with this module being loaded more than once. + # Keep a check here, so we can tell if the problem recurs. + if $acts_as_xapian_class_var_init + raise "The acts_as_xapian module has already been loaded" + else + $acts_as_xapian_class_var_init = true + end + + def ActsAsXapian.db + @@db + end + def ActsAsXapian.db_path=(db_path) + @@db_path = db_path + end + def ActsAsXapian.db_path + @@db_path + end + def ActsAsXapian.writable_db + @@writable_db + end + def ActsAsXapian.stemmer + @@stemmer + end + def ActsAsXapian.term_generator + @@term_generator + end + def ActsAsXapian.enquire + @@enquire + end + def ActsAsXapian.query_parser + @@query_parser + end + def ActsAsXapian.values_by_prefix + @@values_by_prefix + end + def ActsAsXapian.config + @@config + end + + ###################################################################### + # Initialisation + def ActsAsXapian.init(classname = nil, options = nil) + if not classname.nil? + # store class and options for use later, when we open the db in readable_init + @@init_values.push([classname,options]) + end + end + + # Reads the config file (if any) and sets up the path to the database we'll be using + def ActsAsXapian.prepare_environment + return unless @@db_path.nil? + + # barf if we can't figure out the environment + environment = (ENV['RAILS_ENV'] or Rails.env) + raise "Set RAILS_ENV, so acts_as_xapian can find the right Xapian database" if not environment + + # check for a config file + config_file = Rails.root.join("config","xapian.yml") + @@config = File.exists?(config_file) ? YAML.load_file(config_file)[environment] : {} + + # figure out where the DBs should go + if config['base_db_path'] + db_parent_path = Rails.root.join(config['base_db_path']) + else + db_parent_path = File.join(File.dirname(__FILE__), 'xapiandbs') + end + + # make the directory for the xapian databases to go in + Dir.mkdir(db_parent_path) unless File.exists?(db_parent_path) + + @@db_path = File.join(db_parent_path, environment) + + # make some things that don't depend on the db + # XXX this gets made once for each acts_as_xapian. Oh well. + @@stemmer = Xapian::Stem.new('english') + end + + # Opens / reopens the db for reading + # XXX we perhaps don't need to rebuild database and enquire and queryparser - + # but db.reopen wasn't enough by itself, so just do everything it's easier. + def ActsAsXapian.readable_init + raise NoXapianRubyBindingsError.new("Xapian Ruby bindings not installed") unless ActsAsXapian.bindings_available + raise "acts_as_xapian hasn't been called in any models" if @@init_values.empty? + + prepare_environment + + # We need to reopen the database each time, so Xapian gets changes to it. + # Calling reopen() does not always pick up changes for reasons that I can + # only speculate about at the moment. (It is easy to reproduce this by + # changing the code below to use reopen() rather than open() followed by + # close(), and running rake spec.) + if !@@db.nil? + @@db.close + end + + # basic Xapian objects + begin + @@db = Xapian::Database.new(@@db_path) + @@enquire = Xapian::Enquire.new(@@db) + rescue IOError => e + raise "Failed to open Xapian database #{@@db_path}: #{e.message}" + end + + init_query_parser + end + + # Make a new query parser + def ActsAsXapian.init_query_parser + # for queries + @@query_parser = Xapian::QueryParser.new + @@query_parser.stemmer = @@stemmer + @@query_parser.stemming_strategy = Xapian::QueryParser::STEM_SOME + @@query_parser.database = @@db + @@query_parser.default_op = Xapian::Query::OP_AND + begin + @@query_parser.set_max_wildcard_expansion(1000) + rescue NoMethodError + # The set_max_wildcard_expansion method was introduced in Xapian 1.2.7, + # so may legitimately not be available. + # + # Large installations of Alaveteli should consider + # upgrading, because uncontrolled wildcard expansion + # can crash the whole server: see http://trac.xapian.org/ticket/350 + end + + @@stopper = Xapian::SimpleStopper.new + @@stopper.add("and") + @@stopper.add("of") + @@stopper.add("&") + @@query_parser.stopper = @@stopper + + @@terms_by_capital = {} + @@values_by_number = {} + @@values_by_prefix = {} + @@value_ranges_store = [] + + for init_value_pair in @@init_values + classname = init_value_pair[0] + options = init_value_pair[1] + + # go through the various field types, and tell query parser about them, + # and error check them - i.e. check for consistency between models + @@query_parser.add_boolean_prefix("model", "M") + @@query_parser.add_boolean_prefix("modelid", "I") + if options[:terms] + for term in options[:terms] + raise "Use a single capital letter for term code" if not term[1].match(/^[A-Z]$/) + raise "M and I are reserved for use as the model/id term" if term[1] == "M" or term[1] == "I" + raise "model and modelid are reserved for use as the model/id prefixes" if term[2] == "model" or term[2] == "modelid" + raise "Z is reserved for stemming terms" if term[1] == "Z" + raise "Already have code '" + term[1] + "' in another model but with different prefix '" + @@terms_by_capital[term[1]] + "'" if @@terms_by_capital.include?(term[1]) && @@terms_by_capital[term[1]] != term[2] + @@terms_by_capital[term[1]] = term[2] + # XXX use boolean here so doesn't stem our URL names in WhatDoTheyKnow + # If making acts_as_xapian generic, would really need to make the :terms have + # another option that lets people choose non-boolean for terms that need it + # (i.e. searching explicitly within a free text field) + @@query_parser.add_boolean_prefix(term[2], term[1]) + end + end + if options[:values] + for value in options[:values] + raise "Value index '"+value[1].to_s+"' must be an integer, is " + value[1].class.to_s if value[1].class != 1.class + raise "Already have value index '" + value[1].to_s + "' in another model but with different prefix '" + @@values_by_number[value[1]].to_s + "'" if @@values_by_number.include?(value[1]) && @@values_by_number[value[1]] != value[2] + + # date types are special, mark them so the first model they're seen for + if !@@values_by_number.include?(value[1]) + if value[3] == :date + value_range = Xapian::DateValueRangeProcessor.new(value[1]) + elsif value[3] == :string + value_range = Xapian::StringValueRangeProcessor.new(value[1]) + elsif value[3] == :number + value_range = Xapian::NumberValueRangeProcessor.new(value[1]) + else + raise "Unknown value type '" + value[3].to_s + "'" + end + + @@query_parser.add_valuerangeprocessor(value_range) + + # stop it being garbage collected, as + # add_valuerangeprocessor ref is outside Ruby's GC + @@value_ranges_store.push(value_range) + end + + @@values_by_number[value[1]] = value[2] + @@values_by_prefix[value[2]] = value[1] + end + end + end + end + + def ActsAsXapian.writable_init(suffix = "") + raise NoXapianRubyBindingsError.new("Xapian Ruby bindings not installed") unless ActsAsXapian.bindings_available + raise "acts_as_xapian hasn't been called in any models" if @@init_values.empty? + + # if DB is not nil, then we're already initialised, so don't do it + # again XXX reopen it each time, xapian_spec.rb needs this so database + # gets written twice correctly. + # return unless @@writable_db.nil? + + prepare_environment + + full_path = @@db_path + suffix + + # for indexing + @@writable_db = Xapian::WritableDatabase.new(full_path, Xapian::DB_CREATE_OR_OPEN) + @@enquire = Xapian::Enquire.new(@@writable_db) + @@term_generator = Xapian::TermGenerator.new() + @@term_generator.set_flags(Xapian::TermGenerator::FLAG_SPELLING, 0) + @@term_generator.database = @@writable_db + @@term_generator.stemmer = @@stemmer + end + + ###################################################################### + # Search with a query or for similar models + + # Base class for Search and Similar below + class QueryBase + attr_accessor :offset + attr_accessor :limit + attr_accessor :query + attr_accessor :matches + attr_accessor :query_models + attr_accessor :runtime + attr_accessor :cached_results + + def initialize_db + self.runtime = 0.0 + + ActsAsXapian.readable_init + if ActsAsXapian.db.nil? + raise "ActsAsXapian not initialized" + end + end + + MSET_MAX_TRIES = 5 + MSET_MAX_DELAY = 5 + # Set self.query before calling this + def initialize_query(options) + #raise options.to_yaml + + self.runtime += Benchmark::realtime { + offset = options[:offset] || 0; offset = offset.to_i + limit = options[:limit] + raise "please specifiy maximum number of results to return with parameter :limit" if not limit + limit = limit.to_i + sort_by_prefix = options[:sort_by_prefix] || nil + sort_by_ascending = options[:sort_by_ascending].nil? ? true : options[:sort_by_ascending] + collapse_by_prefix = options[:collapse_by_prefix] || nil + + ActsAsXapian.enquire.query = self.query + + if sort_by_prefix.nil? + ActsAsXapian.enquire.sort_by_relevance! + else + value = ActsAsXapian.values_by_prefix[sort_by_prefix] + raise "couldn't find prefix '" + sort_by_prefix.to_s + "'" if value.nil? + ActsAsXapian.enquire.sort_by_value_then_relevance!(value, sort_by_ascending) + end + if collapse_by_prefix.nil? + ActsAsXapian.enquire.collapse_key = Xapian.BAD_VALUENO + else + value = ActsAsXapian.values_by_prefix[collapse_by_prefix] + raise "couldn't find prefix '" + collapse_by_prefix + "'" if value.nil? + ActsAsXapian.enquire.collapse_key = value + end + + tries = 0 + delay = 1 + begin + self.matches = ActsAsXapian.enquire.mset(offset, limit, 100) + rescue IOError => e + if e.message =~ /DatabaseModifiedError: / + # This should be a transient error, so back off and try again, up to a point + if tries > MSET_MAX_TRIES + raise "Received DatabaseModifiedError from Xapian even after retrying #{MSET_MAX_TRIES} times" + else + sleep delay + end + tries += 1 + delay *= 2 + delay = MSET_MAX_DELAY if delay > MSET_MAX_DELAY + + ActsAsXapian.db.reopen() + retry + else + raise + end + end + self.cached_results = nil + } + end + + # Return a description of the query + def description + self.query.description + end + + # Does the query have non-prefixed search terms in it? + def has_normal_search_terms? + ret = false + #x = '' + for t in self.query.terms + term = t.term + #x = x + term.to_yaml + term.size.to_s + term[0..0] + "*" + if term.size >= 2 && term[0..0] == 'Z' + # normal terms begin Z (for stemmed), then have no capital letter prefix + if term[1..1] == term[1..1].downcase + ret = true + end + end + end + return ret + end + + # Estimate total number of results + def matches_estimated + self.matches.matches_estimated + end + + # Return query string with spelling correction + def spelling_correction + correction = ActsAsXapian.query_parser.get_corrected_query_string + if correction.empty? + return nil + end + return correction + end + + # Return array of models found + def results + # If they've already pulled out the results, just return them. + if !self.cached_results.nil? + return self.cached_results + end + + docs = [] + self.runtime += Benchmark::realtime { + # Pull out all the results + iter = self.matches._begin + while not iter.equals(self.matches._end) + docs.push({:data => iter.document.data, + :percent => iter.percent, + :weight => iter.weight, + :collapse_count => iter.collapse_count}) + iter.next + end + } + + # Log time taken, excluding database lookups below which will be displayed separately by ActiveRecord + if ActiveRecord::Base.logger + ActiveRecord::Base.logger.add(Logger::DEBUG, " Xapian query (#{'%.5fs' % self.runtime}) #{self.log_description}") + end + + # Look up without too many SQL queries + lhash = {} + lhash.default = [] + for doc in docs + k = doc[:data].split('-') + lhash[k[0]] = lhash[k[0]] + [k[1]] + end + # for each class, look up all ids + chash = {} + for cls, ids in lhash + conditions = [ "#{cls.constantize.table_name}.#{cls.constantize.primary_key} in (?)", ids ] + found = cls.constantize.find(:all, :conditions => conditions, :include => cls.constantize.xapian_options[:eager_load]) + for f in found + chash[[cls, f.id]] = f + end + end + # now get them in right order again + results = [] + docs.each do |doc| + k = doc[:data].split('-') + model_instance = chash[[k[0], k[1].to_i]] + if model_instance + results << { :model => model_instance, + :percent => doc[:percent], + :weight => doc[:weight], + :collapse_count => doc[:collapse_count] } + end + end + self.cached_results = results + return results + end + end + + # Search for a query string, returns an array of hashes in result order. + # Each hash contains the actual Rails object in :model, and other detail + # about relevancy etc. in other keys. + class Search < QueryBase + attr_accessor :query_string + + # Note that model_classes is not only sometimes useful here - it's + # essential to make sure the classes have been loaded, and thus + # acts_as_xapian called on them, so we know the fields for the query + # parser. + + # model_classes - model classes to search within, e.g. [PublicBody, + # User]. Can take a single model class, or you can express the model + # class names in strings if you like. + # query_string - user inputed query string, with syntax much like Google Search + def initialize(model_classes, query_string, options = {}, user_query = nil) + # Check parameters, convert to actual array of model classes + new_model_classes = [] + model_classes = [model_classes] if model_classes.class != Array + for model_class in model_classes + raise "pass in the model class itself, or a string containing its name" if model_class.class != Class && model_class.class != String + model_class = model_class.constantize if model_class.class == String + new_model_classes.push(model_class) + end + model_classes = new_model_classes + + # Set things up + self.initialize_db + + # Case of a string, searching for a Google-like syntax query + self.query_string = query_string + + # Construct query which only finds things from specified models + model_query = Xapian::Query.new(Xapian::Query::OP_OR, model_classes.map{|mc| "M" + mc.to_s}) + if user_query.nil? + user_query = ActsAsXapian.query_parser.parse_query( + self.query_string, + Xapian::QueryParser::FLAG_BOOLEAN | Xapian::QueryParser::FLAG_PHRASE | + Xapian::QueryParser::FLAG_LOVEHATE | + Xapian::QueryParser::FLAG_SPELLING_CORRECTION) + end + self.query = Xapian::Query.new(Xapian::Query::OP_AND, model_query, user_query) + + # Call base class constructor + self.initialize_query(options) + end + + # Return just normal words in the query i.e. Not operators, ones in + # date ranges or similar. Use this for cheap highlighting with + # TextHelper::highlight, and excerpt. + def words_to_highlight + # TODO: In Ruby 1.9 we can do matching of any unicode letter with \p{L} + # But we still need to support ruby 1.8 for the time being so... + query_nopunc = self.query_string.gsub(/[^ёЁа-яА-Яa-zA-Zà-üÀ-Ü0-9:\.\/_]/iu, " ") + query_nopunc = query_nopunc.gsub(/\s+/, " ") + words = query_nopunc.split(" ") + # Remove anything with a :, . or / in it + words = words.find_all {|o| !o.match(/(:|\.|\/)/) } + words = words.find_all {|o| !o.match(/^(AND|NOT|OR|XOR)$/) } + return words + end + + # Text for lines in log file + def log_description + "Search: " + self.query_string + end + + end + + # Search for models which contain theimportant terms taken from a specified + # list of models. i.e. Use to find documents similar to one (or more) + # documents, or use to refine searches. + class Similar < QueryBase + attr_accessor :query_models + attr_accessor :important_terms + + # model_classes - model classes to search within, e.g. [PublicBody, User] + # query_models - list of models you want to find things similar to + def initialize(model_classes, query_models, options = {}) + self.initialize_db + + self.runtime += Benchmark::realtime { + # Case of an array, searching for models similar to those models in the array + self.query_models = query_models + + # Find the documents by their unique term + input_models_query = Xapian::Query.new(Xapian::Query::OP_OR, query_models.map{|m| "I" + m.xapian_document_term}) + ActsAsXapian.enquire.query = input_models_query + matches = ActsAsXapian.enquire.mset(0, 100, 100) # XXX so this whole method will only work with 100 docs + + # Get set of relevant terms for those documents + selection = Xapian::RSet.new() + iter = matches._begin + while not iter.equals(matches._end) + selection.add_document(iter) + iter.next + end + + # Bit weird that the function to make esets is part of the enquire + # object. This explains what exactly it does, which is to exclude + # terms in the existing query. + # http://thread.gmane.org/gmane.comp.search.xapian.general/3673/focus=3681 + eset = ActsAsXapian.enquire.eset(40, selection) + + # Do main search for them + self.important_terms = [] + iter = eset._begin + while not iter.equals(eset._end) + self.important_terms.push(iter.term) + iter.next + end + similar_query = Xapian::Query.new(Xapian::Query::OP_OR, self.important_terms) + # Exclude original + combined_query = Xapian::Query.new(Xapian::Query::OP_AND_NOT, similar_query, input_models_query) + + # Restrain to model classes + model_query = Xapian::Query.new(Xapian::Query::OP_OR, model_classes.map{|mc| "M" + mc.to_s}) + self.query = Xapian::Query.new(Xapian::Query::OP_AND, model_query, combined_query) + } + + # Call base class constructor + self.initialize_query(options) + end + + # Text for lines in log file + def log_description + "Similar: " + self.query_models.to_s + end + end + + ###################################################################### + # Index + + # Offline indexing job queue model, create with migration made + # using "script/generate acts_as_xapian" as described in ../README.txt + class ActsAsXapianJob < ActiveRecord::Base + end + + # Update index with any changes needed, call this offline. Usually call it + # from a script that exits - otherwise Xapian's writable database won't + # flush your changes. Specifying flush will reduce performance, but make + # sure that each index update is definitely saved to disk before + # logging in the database that it has been. + def ActsAsXapian.update_index(flush = false, verbose = false) + # STDOUT.puts("start of ActsAsXapian.update_index") if verbose + + # Before calling writable_init we have to make sure every model class has been initialized. + # i.e. has had its class code loaded, so acts_as_xapian has been called inside it, and + # we have the info from acts_as_xapian. + model_classes = ActsAsXapianJob.find_by_sql("select model from acts_as_xapian_jobs group by model").map {|a| a.model.constantize} + # If there are no models in the queue, then nothing to do + return if model_classes.size == 0 + + ActsAsXapian.writable_init + # Abort if full rebuild is going on + new_path = ActsAsXapian.db_path + ".new" + if File.exist?(new_path) + raise "aborting incremental index update while full index rebuild happens; found existing " + new_path + end + + ids_to_refresh = ActsAsXapianJob.find(:all).map() { |i| i.id } + for id in ids_to_refresh + job = nil + begin + ActiveRecord::Base.transaction do + begin + job = ActsAsXapianJob.find(id, :lock =>true) + rescue ActiveRecord::RecordNotFound => e + # This could happen if while we are working the model + # was updated a second time by another process. In that case + # ActsAsXapianJob.delete_all in xapian_mark_needs_index below + # might have removed the first job record while we are working on it. + #STDERR.puts("job with #{id} vanished under foot") if verbose + next + end + STDOUT.puts("ActsAsXapian.update_index #{job.action} #{job.model} #{job.model_id.to_s} #{Time.now.to_s}") if verbose + + begin + if job.action == 'update' + # XXX Index functions may reference other models, so we could eager load here too? + model = job.model.constantize.find(job.model_id) # :include => cls.constantize.xapian_options[:include] + model.xapian_index + elsif job.action == 'destroy' + # Make dummy model with right id, just for destruction + model = job.model.constantize.new + model.id = job.model_id + model.xapian_destroy + else + raise "unknown ActsAsXapianJob action '" + job.action + "'" + end + rescue ActiveRecord::RecordNotFound => e + # this can happen if the record was hand deleted in the database + job.action = 'destroy' + retry + end + if flush + ActsAsXapian.writable_db.flush + end + job.destroy + end + rescue => detail + # print any error, and carry on so other things are indexed + STDERR.puts(detail.backtrace.join("\n") + "\nFAILED ActsAsXapian.update_index job #{id} #{$!} " + (job.nil? ? "" : "model " + job.model + " id " + job.model_id.to_s)) + end + end + # We close the database when we're finished to remove the lock file. Since writable_init + # reopens it and recreates the environment every time we don't need to do further cleanup + ActsAsXapian.writable_db.flush + ActsAsXapian.writable_db.close + end + + def ActsAsXapian._is_xapian_db(path) + is_db = File.exist?(File.join(path, "iamflint")) || File.exist?(File.join(path, "iamchert")) + return is_db + end + + # You must specify *all* the models here, this totally rebuilds the Xapian + # database. You'll want any readers to reopen the database after this. + # + # Incremental update_index calls above are suspended while this rebuild + # happens (i.e. while the .new database is there) - any index update jobs + # are left in the database, and will run after the rebuild has finished. + + def ActsAsXapian.rebuild_index(model_classes, verbose = false, terms = true, values = true, texts = true, safe_rebuild = true) + #raise "when rebuilding all, please call as first and only thing done in process / task" if not ActsAsXapian.writable_db.nil? + prepare_environment + + update_existing = !(terms == true && values == true && texts == true) + # Delete any existing .new database, and open a new one which is a copy of the current one + new_path = ActsAsXapian.db_path + ".new" + old_path = ActsAsXapian.db_path + if File.exist?(new_path) + raise "found existing " + new_path + " which is not Xapian flint database, please delete for me" if not ActsAsXapian._is_xapian_db(new_path) + FileUtils.rm_r(new_path) + end + if update_existing + FileUtils.cp_r(old_path, new_path) + end + ActsAsXapian.writable_init + ActsAsXapian.writable_db.close # just to make an empty one to read + # Index everything + if safe_rebuild + _rebuild_index_safely(model_classes, verbose, terms, values, texts) + else + @@db_path = ActsAsXapian.db_path + ".new" + ActsAsXapian.writable_init + # Save time by running the indexing in one go and in-process + for model_class in model_classes + STDOUT.puts("ActsAsXapian.rebuild_index: Rebuilding #{model_class.to_s}") if verbose + model_class.find(:all).each do |model| + STDOUT.puts("ActsAsXapian.rebuild_index #{model_class} #{model.id}") if verbose + model.xapian_index(terms, values, texts) + end + end + ActsAsXapian.writable_db.flush + ActsAsXapian.writable_db.close + end + + # Rename into place + temp_path = old_path + ".tmp" + if File.exist?(temp_path) + @@db_path = old_path + raise "temporary database found " + temp_path + " which is not Xapian flint database, please delete for me" if not ActsAsXapian._is_xapian_db(temp_path) + FileUtils.rm_r(temp_path) + end + if File.exist?(old_path) + FileUtils.mv old_path, temp_path + end + FileUtils.mv new_path, old_path + + # Delete old database + if File.exist?(temp_path) + if not ActsAsXapian._is_xapian_db(temp_path) + @@db_path = old_path + raise "old database now at " + temp_path + " is not Xapian flint database, please delete for me" + end + FileUtils.rm_r(temp_path) + end + + # You'll want to restart your FastCGI or Mongrel processes after this, + # so they get the new db + @@db_path = old_path + end + + def ActsAsXapian._rebuild_index_safely(model_classes, verbose, terms, values, texts) + batch_size = 1000 + for model_class in model_classes + model_class_count = model_class.count + 0.step(model_class_count, batch_size) do |i| + # We fork here, so each batch is run in a different process. This is + # because otherwise we get a memory "leak" and you can't rebuild very + # large databases (however long you have!) + + ActiveRecord::Base.connection.disconnect! + + pid = Process.fork # XXX this will only work on Unix, tough + if pid + Process.waitpid(pid) + if not $?.success? + raise "batch fork child failed, exiting also" + end + # database connection doesn't survive a fork, rebuild it + else + # fully reopen the database each time (with a new object) + # (so doc ids and so on aren't preserved across the fork) + ActiveRecord::Base.establish_connection + @@db_path = ActsAsXapian.db_path + ".new" + ActsAsXapian.writable_init + STDOUT.puts("ActsAsXapian.rebuild_index: New batch. #{model_class.to_s} from #{i} to #{i + batch_size} of #{model_class_count} pid #{Process.pid.to_s}") if verbose + model_class.find(:all, :limit => batch_size, :offset => i, :order => :id).each do |model| + STDOUT.puts("ActsAsXapian.rebuild_index #{model_class} #{model.id}") if verbose + model.xapian_index(terms, values, texts) + end + ActsAsXapian.writable_db.flush + ActsAsXapian.writable_db.close + # database connection won't survive a fork, so shut it down + ActiveRecord::Base.connection.disconnect! + # brutal exit, so other shutdown code not run (for speed and safety) + Kernel.exit! 0 + end + + ActiveRecord::Base.establish_connection + + end + end + end + + ###################################################################### + # Instance methods that get injected into your model. + + module InstanceMethods + # Used internally + def xapian_document_term + self.class.to_s + "-" + self.id.to_s + end + + def xapian_value(field, type = nil, index_translations = false) + if index_translations && self.respond_to?("translations") + if type == :date or type == :boolean + value = single_xapian_value(field, type = type) + else + values = [] + for locale in self.translations.map{|x| x.locale} + I18n.with_locale(locale) do + values << single_xapian_value(field, type=type) + end + end + if values[0].kind_of?(Array) + values = values.flatten + value = values.reject{|x| x.nil?} + else + values = values.reject{|x| x.nil?} + value = values.join(" ") + end + end + else + value = single_xapian_value(field, type = type) + end + return value + end + + # Extract value of a field from the model + def single_xapian_value(field, type = nil) + value = self.send(field.to_sym) || self[field] + if type == :date + if value.kind_of?(Time) + value.utc.strftime("%Y%m%d") + elsif value.kind_of?(Date) + value.to_time.utc.strftime("%Y%m%d") + else + raise "Only Time or Date types supported by acts_as_xapian for :date fields, got " + value.class.to_s + end + elsif type == :boolean + value ? true : false + else + # Arrays are for terms which require multiple of them, e.g. tags + if value.kind_of?(Array) + value.map {|v| v.to_s} + else + value.to_s + end + end + end + + # Store record in the Xapian database + def xapian_index(terms = true, values = true, texts = true) + # if we have a conditional function for indexing, call it and destroy object if failed + if self.class.xapian_options.include?(:if) + if_value = xapian_value(self.class.xapian_options[:if], :boolean) + if not if_value + self.xapian_destroy + return + end + end + + existing_query = Xapian::Query.new("I" + self.xapian_document_term) + ActsAsXapian.enquire.query = existing_query + match = ActsAsXapian.enquire.mset(0,1,1).matches[0] + + if !match.nil? + doc = match.document + else + doc = Xapian::Document.new + doc.data = self.xapian_document_term + doc.add_term("M" + self.class.to_s) + doc.add_term("I" + doc.data) + end + # work out what to index + # 1. Which terms to index? We allow the user to specify particular ones + terms_to_index = [] + drop_all_terms = false + if terms and self.xapian_options[:terms] + terms_to_index = self.xapian_options[:terms].dup + if terms.is_a?(String) + terms_to_index.reject!{|term| !terms.include?(term[1])} + if terms_to_index.length == self.xapian_options[:terms].length + drop_all_terms = true + end + else + drop_all_terms = true + end + end + # 2. Texts to index? Currently, it's all or nothing + texts_to_index = [] + if texts and self.xapian_options[:texts] + texts_to_index = self.xapian_options[:texts] + end + # 3. Values to index? Currently, it's all or nothing + values_to_index = [] + if values and self.xapian_options[:values] + values_to_index = self.xapian_options[:values] + end + + # clear any existing data that we might want to replace + if drop_all_terms && texts + # as an optimisation, if we're reindexing all of both, we remove everything + doc.clear_terms + doc.add_term("M" + self.class.to_s) + doc.add_term("I" + doc.data) + else + term_prefixes_to_index = terms_to_index.map {|x| x[1]} + for existing_term in doc.terms + first_letter = existing_term.term[0...1] + if !"MI".include?(first_letter) # it's not one of the reserved value + if first_letter.match("^[A-Z]+") # it's a "value" (rather than indexed text) + if term_prefixes_to_index.include?(first_letter) # it's a value that we've been asked to index + doc.remove_term(existing_term.term) + end + elsif texts + doc.remove_term(existing_term.term) # it's text and we've been asked to reindex it + end + end + end + end + + for term in terms_to_index + value = xapian_value(term[0]) + if value.kind_of?(Array) + for v in value + doc.add_term(term[1] + v) + end + else + doc.add_term(term[1] + value) + end + end + + if values + doc.clear_values + for value in values_to_index + doc.add_value(value[1], xapian_value(value[0], value[3])) + end + end + if texts + ActsAsXapian.term_generator.document = doc + for text in texts_to_index + ActsAsXapian.term_generator.increase_termpos # stop phrases spanning different text fields + # XXX the "1" here is a weight that could be varied for a boost function + ActsAsXapian.term_generator.index_text(xapian_value(text, nil, true), 1) + end + end + + ActsAsXapian.writable_db.replace_document("I" + doc.data, doc) + end + + # Delete record from the Xapian database + def xapian_destroy + ActsAsXapian.writable_db.delete_document("I" + self.xapian_document_term) + end + + # Used to mark changes needed by batch indexer + def xapian_mark_needs_index + xapian_create_job('update', self.class.base_class.to_s, self.id) + end + + def xapian_mark_needs_destroy + xapian_create_job('destroy', self.class.base_class.to_s, self.id) + end + + # Allow reindexing to be skipped if a flag is set + def xapian_mark_needs_index_if_reindex + return true if (self.respond_to?(:no_xapian_reindex) && self.no_xapian_reindex == true) + xapian_mark_needs_index + end + + def xapian_create_job(action, model, model_id) + begin + ActiveRecord::Base.transaction(:requires_new => true) do + ActsAsXapianJob.delete_all([ "model = ? and model_id = ?", model, model_id]) + xapian_before_create_job_hook(action, model, model_id) + ActsAsXapianJob.create!(:model => model, + :model_id => model_id, + :action => action) + end + rescue ActiveRecord::RecordNotUnique => e + # Given the error handling in ActsAsXapian::update_index, we can just fail silently if + # another process has inserted an acts_as_xapian_jobs record for this model. + raise unless (e.message =~ /duplicate key value violates unique constraint "index_acts_as_xapian_jobs_on_model_and_model_id"/) + end + end + + # A hook method that can be used in tests to simulate e.g. an external process inserting a record + def xapian_before_create_job_hook(action, model, model_id) + end + + end + + ###################################################################### + # Main entry point, add acts_as_xapian to your model. + + module ActsMethods + # See top of this file for docs + def acts_as_xapian(options) + # Give error only on queries if bindings not available + if not ActsAsXapian.bindings_available + return + end + + include InstanceMethods + + cattr_accessor :xapian_options + self.xapian_options = options + + ActsAsXapian.init(self.class.to_s, options) + + after_save :xapian_mark_needs_index_if_reindex + after_destroy :xapian_mark_needs_destroy + end + end + +end + +# Reopen ActiveRecord and include the acts_as_xapian method +ActiveRecord::Base.extend ActsAsXapian::ActsMethods + + diff --git a/lib/acts_as_xapian/generators/acts_as_xapian/USAGE b/lib/acts_as_xapian/generators/acts_as_xapian/USAGE new file mode 100644 index 000000000..2d027c46f --- /dev/null +++ b/lib/acts_as_xapian/generators/acts_as_xapian/USAGE @@ -0,0 +1 @@ +./script/generate acts_as_xapian diff --git a/lib/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb b/lib/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb new file mode 100644 index 000000000..a1cd1801d --- /dev/null +++ b/lib/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb @@ -0,0 +1,13 @@ +class ActsAsXapianGenerator < Rails::Generator::Base + def manifest + record do |m| + m.migration_template 'migration.rb', 'db/migrate', + :migration_file_name => "create_acts_as_xapian" + end + end + + protected + def banner + "Usage: #{$0} acts_as_xapian" + end +end diff --git a/lib/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb b/lib/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb new file mode 100644 index 000000000..84a9dd766 --- /dev/null +++ b/lib/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb @@ -0,0 +1,14 @@ +class CreateActsAsXapian < ActiveRecord::Migration + def self.up + create_table :acts_as_xapian_jobs do |t| + t.column :model, :string, :null => false + t.column :model_id, :integer, :null => false + t.column :action, :string, :null => false + end + add_index :acts_as_xapian_jobs, [:model, :model_id], :unique => true + end + def self.down + drop_table :acts_as_xapian_jobs + end +end + diff --git a/lib/acts_as_xapian/tasks/xapian.rake b/lib/acts_as_xapian/tasks/xapian.rake new file mode 100644 index 000000000..c1986ce1e --- /dev/null +++ b/lib/acts_as_xapian/tasks/xapian.rake @@ -0,0 +1,66 @@ +require 'rubygems' +require 'rake' +require 'rake/testtask' +require 'active_record' + +namespace :xapian do + # Parameters - specify "flush=true" to save changes to the Xapian database + # after each model that is updated. This is safer, but slower. Specify + # "verbose=true" to print model name as it is run. + desc 'Updates Xapian search index with changes to models since last call' + task :update_index => :environment do + ActsAsXapian.update_index(ENV['flush'] ? true : false, ENV['verbose'] ? true : false) + end + + # Parameters - specify 'models="PublicBody User"' to say which models + # you index with Xapian. + + # This totally rebuilds the database, so you will want to restart + # any web server afterwards to make sure it gets the changes, + # rather than still pointing to the old deleted database. Specify + # "verbose=true" to print model name as it is run. By default, + # all of the terms, values and texts are reindexed. You can + # suppress any of these by specifying, for example, "texts=false". + # You can specify that only certain terms should be updated by + # specifying their prefix(es) as a string, e.g. "terms=IV" will + # index the two terms I and V (and "terms=false" will index none, + # and "terms=true", the default, will index all) + + + desc 'Completely rebuilds Xapian search index (must specify all models)' + task :rebuild_index => :environment do + def coerce_arg(arg, default) + if arg == "false" + return false + elsif arg == "true" + return true + elsif arg.nil? + return default + else + return arg + end + end + raise "specify ALL your models with models=\"ModelName1 ModelName2\" as parameter" if ENV['models'].nil? + ActsAsXapian.rebuild_index(ENV['models'].split(" ").map{|m| m.constantize}, + coerce_arg(ENV['verbose'], false), + coerce_arg(ENV['terms'], true), + coerce_arg(ENV['values'], true), + coerce_arg(ENV['texts'], true)) + end + + # Parameters - are models, query, offset, limit, sort_by_prefix, + # collapse_by_prefix + desc 'Run a query, return YAML of results' + task :query => :environment do + raise "specify models=\"ModelName1 ModelName2\" as parameter" if ENV['models'].nil? + raise "specify query=\"your terms\" as parameter" if ENV['query'].nil? + s = ActsAsXapian::Search.new(ENV['models'].split(" ").map{|m| m.constantize}, + ENV['query'], + :offset => (ENV['offset'] || 0), :limit => (ENV['limit'] || 10), + :sort_by_prefix => (ENV['sort_by_prefix'] || nil), + :collapse_by_prefix => (ENV['collapse_by_prefix'] || nil) + ) + STDOUT.puts(s.results.to_yaml) + end +end + diff --git a/script/compact-xapian-database b/script/compact-xapian-database index f1a6058b0..982c0e878 100755 --- a/script/compact-xapian-database +++ b/script/compact-xapian-database @@ -4,7 +4,7 @@ export RAILS_ENV=$1 set -e if [ -x /usr/bin/xapian-compact ]; then - XAPIAN_DB_DIR=$( cd "$( dirname "$0" )" && pwd )/../vendor/plugins/acts_as_xapian/xapiandbs + XAPIAN_DB_DIR=$( cd "$( dirname "$0" )" && pwd )/../lib/acts_as_xapian/xapiandbs if [ -e "$XAPIAN_DB_DIR/$RAILS_ENV.new" ]; then echo >&2 "Didn't compact Xapian database because there was an existing database at $XAPIAN_DB_DIR/$RAILS_ENV.new" exit 1 diff --git a/spec/models/xapian_spec.rb b/spec/models/xapian_spec.rb index 3c9fff784..a1e060d8e 100644 --- a/spec/models/xapian_spec.rb +++ b/spec/models/xapian_spec.rb @@ -370,7 +370,7 @@ describe PublicBody, " when only indexing selected things on a rebuild" do end end -# I would expect ActsAsXapian to have some tests under vendor/plugins/acts_as_xapian, but +# I would expect ActsAsXapian to have some tests under lib/acts_as_xapian, but # it looks like this is not the case. Putting a test here instead. describe ActsAsXapian::Search, "#words_to_highlight" do before(:each) do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d3cf7c9af..57d58e276 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -15,6 +15,7 @@ SimpleCov.start('rails') do add_filter 'vendor/plugins' add_filter 'lib/strip_attributes' add_filter 'lib/has_tag_string' + add_filter 'lib/acts_as_xapian' end Spork.prefork do diff --git a/vendor/plugins/acts_as_xapian/.cvsignore b/vendor/plugins/acts_as_xapian/.cvsignore deleted file mode 100644 index 6379fb207..000000000 --- a/vendor/plugins/acts_as_xapian/.cvsignore +++ /dev/null @@ -1,2 +0,0 @@ -xapiandbs -.git diff --git a/vendor/plugins/acts_as_xapian/.gitignore b/vendor/plugins/acts_as_xapian/.gitignore deleted file mode 100644 index 60e95666f..000000000 --- a/vendor/plugins/acts_as_xapian/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/xapiandbs -CVS -*.swp diff --git a/vendor/plugins/acts_as_xapian/LICENSE.txt b/vendor/plugins/acts_as_xapian/LICENSE.txt deleted file mode 100644 index 72d93c4be..000000000 --- a/vendor/plugins/acts_as_xapian/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -acts_as_xapian is released under the MIT License. - -Copyright (c) 2008 UK Citizens Online Democracy. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of the acts_as_xapian software and associated documentation files (the -"Software"), to deal in the Software without restriction, including without -limitation the rights to use, copy, modify, merge, publish, distribute, -sublicense, and/or sell copies of the Software, and to permit persons to whom -the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/plugins/acts_as_xapian/README.txt b/vendor/plugins/acts_as_xapian/README.txt deleted file mode 100644 index a1d22ef3f..000000000 --- a/vendor/plugins/acts_as_xapian/README.txt +++ /dev/null @@ -1,276 +0,0 @@ -The official page for acts_as_xapian is now the Google Groups page. - -http://groups.google.com/group/acts_as_xapian - -frabcus's github repository is no longer the official repository, -find the official one from the Google Groups page. - ------------------------------------------------------------------------- - -Do patch this file if there is documentation missing / wrong. It's called -README.txt and is in git, using Textile formatting. The wiki page is just -copied from the README.txt file. - -Contents -======== - -* a. Introduction to acts_as_xapian -* b. Installation -* c. Comparison to acts_as_solr (as on 24 April 2008) -* d. Documentation - indexing -* e. Documentation - querying -* f. Configuration -* g. Performance -* h. Support - - -a. Introduction to acts_as_xapian -================================= - -"Xapian":http://www.xapian.org is a full text search engine library which has -Ruby bindings. acts_as_xapian adds support for it to Rails. It is an -alternative to acts_as_solr, acts_as_ferret, Ultrasphinx, acts_as_indexed, -acts_as_searchable or acts_as_tsearch. - -acts_as_xapian is deployed in production on these websites. -* "WhatDoTheyKnow":http://www.whatdotheyknow.com -* "MindBites":http://www.mindbites.com - -The section "c. Comparison to acts_as_solr" below will give you an idea of -acts_as_xapian's features. - -acts_as_xapian was started by Francis Irving in May 2008 for search and email -alerts in WhatDoTheyKnow, and so was supported by "mySociety":http://www.mysociety.org -and initially paid for by the "JRSST Charitable Trust":http://www.jrrt.org.uk/jrsstct.htm - - -b. Installation -=============== - -Retrieve the plugin directly from the git version control system by running -this command within your Rails app. - - git clone git://github.com/frabcus/acts_as_xapian.git vendor/plugins/acts_as_xapian - -Xapian 1.0.5 and associated Ruby bindings are also required. - -Debian or Ubuntu - install the packages libxapian15 and libxapian-ruby1.8. - -Mac OSX - follow the instructions for installing from source on -the "Installing Xapian":http://xapian.org/docs/install.html page - you need the -Xapian library and bindings (you don't need Omega). - -There is no Ruby Gem for Xapian, it would be great if you could make one! - - -c. Comparison to acts_as_solr (as on 24 April 2008) -============================= - -* Offline indexing only mode - which is a minus if you want changes -immediately reflected in the search index, and a plus if you were going to -have to implement your own offline indexing anyway. - -* Collapsing - the equivalent of SQL's "group by". You can specify a field -to collapse on, and only the most relevant result from each value of that -field is returned. Along with a count of how many there are in total. -acts_as_solr doesn't have this. - -* No highlighting - Xapian can't return you text highlighted with a search -query. You can try and make do with TextHelper::highlight (combined with -words_to_highlight below). I found the highlighting in acts_as_solr didn't -really understand the query anyway. - -* Date range searching - this exists in acts_as_solr, but I found it -wasn't documented well enough, and was hard to get working. - -* Spelling correction - "did you mean?" built in and just works. - -* Similar documents - acts_as_xapian has a simple command to find other models -that are like a specified model. - -* Multiple models - acts_as_xapian searches multiple types of model if you -like, returning them mixed up together by relevancy. This is like -multi_solr_search, only it is the default mode of operation and is properly -supported. - -* No daemons - However, if you have more than one web server, you'll need to -work out how to use "Xapian's remote backend":http://xapian.org/docs/remote.html. - -* One layer - full-powered Xapian is called directly from the Ruby, without -Solr getting in the way whenever you want to use a new feature from Lucene. - -* No Java - an advantage if you're more used to working in the rest of the -open source world. acts_as_xapian, it's pure Ruby and C++. - -* Xapian's awesome email list - the kids over at -"xapian-discuss":http://lists.xapian.org/mailman/listinfo/xapian-discuss -are super helpful. Useful if you need to extend and improve acts_as_xapian. The -Ruby bindings are mature and well maintained as part of Xapian. - - -d. Documentation - indexing -=========================== - -Xapian is an *offline indexing* search library - only one process can have the -Xapian database open for writing at once, and others that try meanwhile are -unceremoniously kicked out. For this reason, acts_as_xapian does not support -immediate writing to the database when your models change. - -Instead, there is a ActsAsXapianJob model which stores which models need -updating or deleting in the search index. A rake task 'xapian:update_index' -then performs the updates since last change. You can run it on a cron job, or -similar. - -Here's how to add indexing to your Rails app: - -1. Put acts_as_xapian in your models that need search indexing. e.g. - - acts_as_xapian :texts => [ :name, :short_name ], - :values => [ [ :created_at, 0, "created_at", :date ] ], - :terms => [ [ :variety, 'V', "variety" ] ] - -Options must include: - -* :texts, an array of fields for indexing with full text search. -e.g. :texts => [ :title, :body ] - -* :values, things which have a range of values for sorting, or for collapsing. -Specify an array quadruple of [ field, identifier, prefix, type ] where -** identifier is an arbitary numeric identifier for use in the Xapian database -** prefix is the part to use in search queries that goes before the : -** type can be any of :string, :number or :date - -e.g. :values => [ [ :created_at, 0, "created_at", :date ], -[ :size, 1, "size", :string ] ] - -* :terms, things which come with a prefix (before a :) in search queries. -Specify an array triple of [ field, char, prefix ] where -** char is an arbitary single upper case char used in the Xapian database, just -pick any single uppercase character, but use a different one for each prefix. -** prefix is the part to use in search queries that goes before the : -For example, if you were making Google and indexing to be able to later do a -query like "site:www.whatdotheyknow.com", then the prefix would be "site". - -e.g. :terms => [ [ :variety, 'V', "variety" ] ] - -A 'field' is a symbol referring to either an attribute or a function which -returns the text, date or number to index. Both 'identifier' and 'char' must be -the same for the same prefix in different models. - -Options may include: -* :eager_load, added as an :include clause when looking up search results in -database -* :if, either an attribute or a function which if returns false means the -object isn't indexed - -2. Generate a database migration to create the ActsAsXapianJob model: - - script/generate acts_as_xapian - rake db:migrate - -3. Call 'rake xapian:rebuild_index models="ModelName1 ModelName2"' to build the index -the first time (you must specify all your indexed models). It's put in a -development/test/production dir in acts_as_xapian/xapiandbs. See f. Configuration -below if you want to change this. - -4. Then from a cron job or a daemon, or by hand regularly!, call 'rake xapian:update_index' - - -e. Documentation - querying -=========================== - -Testing indexing ----------------- - -If you just want to test indexing is working, you'll find this rake task -useful (it has more options, see tasks/xapian.rake) - - rake xapian:query models="PublicBody User" query="moo" - -Performing a query ------------------- - -To perform a query from code call ActsAsXapian::Search.new. This takes in turn: -* model_classes - list of models to search, e.g. [PublicBody, InfoRequestEvent] -* query_string - Google like syntax, see below - -And then a hash of options: -* :offset - Offset of first result (default 0) -* :limit - Number of results per page -* :sort_by_prefix - Optionally, prefix of value to sort by, otherwise sort by relevance -* :sort_by_ascending - Default true (documents with higher values better/earlier), set to false for descending sort -* :collapse_by_prefix - Optionally, prefix of value to collapse by (i.e. only return most relevant result from group) - -Google like query syntax is as described in - "Xapian::QueryParser Syntax":http://www.xapian.org/docs/queryparser.html -Queries can include prefix:value parts, according to what you indexed in the -acts_as_xapian part above. You can also say things like model:InfoRequestEvent -to constrain by model in more complex ways than the :model parameter, or -modelid:InfoRequestEvent-100 to only find one specific object. - -Returns an ActsAsXapian::Search object. Useful methods are: -* description - a techy one, to check how the query has been parsed -* matches_estimated - a guesstimate at the total number of hits -* spelling_correction - the corrected query string if there is a correction, otherwise nil -* words_to_highlight - list of words for you to highlight, perhaps with TextHelper::highlight -* results - an array of hashes each containing: -** :model - your Rails model, this is what you most want! -** :weight - relevancy measure -** :percent - the weight as a %, 0 meaning the item did not match the query at all -** :collapse_count - number of results with the same prefix, if you specified collapse_by_prefix - -Finding similar models ----------------------- - -To find models that are similar to a given set of models call ActsAsXapian::Similar.new. This takes: -* model_classes - list of model classes to return models from within -* models - list of models that you want to find related ones to - -Returns an ActsAsXapian::Similar object. Has all methods from ActsAsXapian::Search above, except -for words_to_highlight. In addition has: -* important_terms - the terms extracted from the input models, that were used to search for output -You need the results methods to get the similar models. - - -f. Configuration -================ - -If you want to customise the configuration of acts_as_xapian, it will look for -a file called 'xapian.yml' under Rails.root/config. As is familiar from the -format of the database.yml file, separate :development, :test and :production -sections are expected. - -The following options are available: -* base_db_path - specifies the directory, relative to Rails.root, in which -acts_as_xapian stores its search index databases. Default is the directory -xapiandbs within the acts_as_xapian directory. - - -g. Performance -============== - -On development sites, acts_as_xapian automatically logs the time taken to do -searches. The time displayed is for the Xapian parts of the query; the Rails -database model lookups will be logged separately by ActiveRecord. Example: - - Xapian query (0.00029s) Search: hello - -To enable this, and other performance logging, on a production site, -temporarily add this to the end of your config/environment.rb - - ActiveRecord::Base.logger = Logger.new(STDOUT) - - -h. Support -========== - -Please ask any questions on the -"acts_as_xapian Google Group":http://groups.google.com/group/acts_as_xapian - -The official home page and repository for acts_as_xapian are the -"acts_as_xapian github page":http://github.com/frabcus/acts_as_xapian/wikis - -For more details about anything, see source code in lib/acts_as_xapian.rb - -Merging source instructions "Using git for collaboration" here: -http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html diff --git a/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/USAGE b/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/USAGE deleted file mode 100644 index 2d027c46f..000000000 --- a/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/USAGE +++ /dev/null @@ -1 +0,0 @@ -./script/generate acts_as_xapian diff --git a/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb b/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb deleted file mode 100644 index a1cd1801d..000000000 --- a/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb +++ /dev/null @@ -1,13 +0,0 @@ -class ActsAsXapianGenerator < Rails::Generator::Base - def manifest - record do |m| - m.migration_template 'migration.rb', 'db/migrate', - :migration_file_name => "create_acts_as_xapian" - end - end - - protected - def banner - "Usage: #{$0} acts_as_xapian" - end -end diff --git a/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb b/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb deleted file mode 100644 index 84a9dd766..000000000 --- a/vendor/plugins/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb +++ /dev/null @@ -1,14 +0,0 @@ -class CreateActsAsXapian < ActiveRecord::Migration - def self.up - create_table :acts_as_xapian_jobs do |t| - t.column :model, :string, :null => false - t.column :model_id, :integer, :null => false - t.column :action, :string, :null => false - end - add_index :acts_as_xapian_jobs, [:model, :model_id], :unique => true - end - def self.down - drop_table :acts_as_xapian_jobs - end -end - diff --git a/vendor/plugins/acts_as_xapian/init.rb b/vendor/plugins/acts_as_xapian/init.rb deleted file mode 100644 index 1e5b8557b..000000000 --- a/vendor/plugins/acts_as_xapian/init.rb +++ /dev/null @@ -1,7 +0,0 @@ -# acts_as_xapian/init.rb: -# -# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ - -require 'acts_as_xapian' - diff --git a/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb b/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb deleted file mode 100644 index 2e486f328..000000000 --- a/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb +++ /dev/null @@ -1,979 +0,0 @@ -# encoding: utf-8 -# acts_as_xapian/lib/acts_as_xapian.rb: -# Xapian full text search in Ruby on Rails. -# -# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: hello@mysociety.org; WWW: http://www.mysociety.org/ -# -# Documentation -# ============= -# -# See ../README.txt foocumentation. Please update that file if you edit -# code. - -# Make it so if Xapian isn't installed, the Rails app doesn't fail completely, -# just when somebody does a search. -begin - require 'xapian' - $acts_as_xapian_bindings_available = true -rescue LoadError - STDERR.puts "acts_as_xapian: No Ruby bindings for Xapian installed" - $acts_as_xapian_bindings_available = false -end - -module ActsAsXapian - ###################################################################### - # Module level variables - # XXX must be some kind of cattr_accessor that can do this better - def ActsAsXapian.bindings_available - $acts_as_xapian_bindings_available - end - class NoXapianRubyBindingsError < StandardError - end - - @@db = nil - @@db_path = nil - @@writable_db = nil - @@init_values = [] - - # There used to be a problem with this module being loaded more than once. - # Keep a check here, so we can tell if the problem recurs. - if $acts_as_xapian_class_var_init - raise "The acts_as_xapian module has already been loaded" - else - $acts_as_xapian_class_var_init = true - end - - def ActsAsXapian.db - @@db - end - def ActsAsXapian.db_path=(db_path) - @@db_path = db_path - end - def ActsAsXapian.db_path - @@db_path - end - def ActsAsXapian.writable_db - @@writable_db - end - def ActsAsXapian.stemmer - @@stemmer - end - def ActsAsXapian.term_generator - @@term_generator - end - def ActsAsXapian.enquire - @@enquire - end - def ActsAsXapian.query_parser - @@query_parser - end - def ActsAsXapian.values_by_prefix - @@values_by_prefix - end - def ActsAsXapian.config - @@config - end - - ###################################################################### - # Initialisation - def ActsAsXapian.init(classname = nil, options = nil) - if not classname.nil? - # store class and options for use later, when we open the db in readable_init - @@init_values.push([classname,options]) - end - end - - # Reads the config file (if any) and sets up the path to the database we'll be using - def ActsAsXapian.prepare_environment - return unless @@db_path.nil? - - # barf if we can't figure out the environment - environment = (ENV['RAILS_ENV'] or Rails.env) - raise "Set RAILS_ENV, so acts_as_xapian can find the right Xapian database" if not environment - - # check for a config file - config_file = Rails.root.join("config","xapian.yml") - @@config = File.exists?(config_file) ? YAML.load_file(config_file)[environment] : {} - - # figure out where the DBs should go - if config['base_db_path'] - db_parent_path = Rails.root.join(config['base_db_path']) - else - db_parent_path = File.join(File.dirname(__FILE__), '../xapiandbs/') - end - - # make the directory for the xapian databases to go in - Dir.mkdir(db_parent_path) unless File.exists?(db_parent_path) - - @@db_path = File.join(db_parent_path, environment) - - # make some things that don't depend on the db - # XXX this gets made once for each acts_as_xapian. Oh well. - @@stemmer = Xapian::Stem.new('english') - end - - # Opens / reopens the db for reading - # XXX we perhaps don't need to rebuild database and enquire and queryparser - - # but db.reopen wasn't enough by itself, so just do everything it's easier. - def ActsAsXapian.readable_init - raise NoXapianRubyBindingsError.new("Xapian Ruby bindings not installed") unless ActsAsXapian.bindings_available - raise "acts_as_xapian hasn't been called in any models" if @@init_values.empty? - - prepare_environment - - # We need to reopen the database each time, so Xapian gets changes to it. - # Calling reopen() does not always pick up changes for reasons that I can - # only speculate about at the moment. (It is easy to reproduce this by - # changing the code below to use reopen() rather than open() followed by - # close(), and running rake spec.) - if !@@db.nil? - @@db.close - end - - # basic Xapian objects - begin - @@db = Xapian::Database.new(@@db_path) - @@enquire = Xapian::Enquire.new(@@db) - rescue IOError => e - raise "Failed to open Xapian database #{@@db_path}: #{e.message}" - end - - init_query_parser - end - - # Make a new query parser - def ActsAsXapian.init_query_parser - # for queries - @@query_parser = Xapian::QueryParser.new - @@query_parser.stemmer = @@stemmer - @@query_parser.stemming_strategy = Xapian::QueryParser::STEM_SOME - @@query_parser.database = @@db - @@query_parser.default_op = Xapian::Query::OP_AND - begin - @@query_parser.set_max_wildcard_expansion(1000) - rescue NoMethodError - # The set_max_wildcard_expansion method was introduced in Xapian 1.2.7, - # so may legitimately not be available. - # - # Large installations of Alaveteli should consider - # upgrading, because uncontrolled wildcard expansion - # can crash the whole server: see http://trac.xapian.org/ticket/350 - end - - @@stopper = Xapian::SimpleStopper.new - @@stopper.add("and") - @@stopper.add("of") - @@stopper.add("&") - @@query_parser.stopper = @@stopper - - @@terms_by_capital = {} - @@values_by_number = {} - @@values_by_prefix = {} - @@value_ranges_store = [] - - for init_value_pair in @@init_values - classname = init_value_pair[0] - options = init_value_pair[1] - - # go through the various field types, and tell query parser about them, - # and error check them - i.e. check for consistency between models - @@query_parser.add_boolean_prefix("model", "M") - @@query_parser.add_boolean_prefix("modelid", "I") - if options[:terms] - for term in options[:terms] - raise "Use a single capital letter for term code" if not term[1].match(/^[A-Z]$/) - raise "M and I are reserved for use as the model/id term" if term[1] == "M" or term[1] == "I" - raise "model and modelid are reserved for use as the model/id prefixes" if term[2] == "model" or term[2] == "modelid" - raise "Z is reserved for stemming terms" if term[1] == "Z" - raise "Already have code '" + term[1] + "' in another model but with different prefix '" + @@terms_by_capital[term[1]] + "'" if @@terms_by_capital.include?(term[1]) && @@terms_by_capital[term[1]] != term[2] - @@terms_by_capital[term[1]] = term[2] - # XXX use boolean here so doesn't stem our URL names in WhatDoTheyKnow - # If making acts_as_xapian generic, would really need to make the :terms have - # another option that lets people choose non-boolean for terms that need it - # (i.e. searching explicitly within a free text field) - @@query_parser.add_boolean_prefix(term[2], term[1]) - end - end - if options[:values] - for value in options[:values] - raise "Value index '"+value[1].to_s+"' must be an integer, is " + value[1].class.to_s if value[1].class != 1.class - raise "Already have value index '" + value[1].to_s + "' in another model but with different prefix '" + @@values_by_number[value[1]].to_s + "'" if @@values_by_number.include?(value[1]) && @@values_by_number[value[1]] != value[2] - - # date types are special, mark them so the first model they're seen for - if !@@values_by_number.include?(value[1]) - if value[3] == :date - value_range = Xapian::DateValueRangeProcessor.new(value[1]) - elsif value[3] == :string - value_range = Xapian::StringValueRangeProcessor.new(value[1]) - elsif value[3] == :number - value_range = Xapian::NumberValueRangeProcessor.new(value[1]) - else - raise "Unknown value type '" + value[3].to_s + "'" - end - - @@query_parser.add_valuerangeprocessor(value_range) - - # stop it being garbage collected, as - # add_valuerangeprocessor ref is outside Ruby's GC - @@value_ranges_store.push(value_range) - end - - @@values_by_number[value[1]] = value[2] - @@values_by_prefix[value[2]] = value[1] - end - end - end - end - - def ActsAsXapian.writable_init(suffix = "") - raise NoXapianRubyBindingsError.new("Xapian Ruby bindings not installed") unless ActsAsXapian.bindings_available - raise "acts_as_xapian hasn't been called in any models" if @@init_values.empty? - - # if DB is not nil, then we're already initialised, so don't do it - # again XXX reopen it each time, xapian_spec.rb needs this so database - # gets written twice correctly. - # return unless @@writable_db.nil? - - prepare_environment - - full_path = @@db_path + suffix - - # for indexing - @@writable_db = Xapian::WritableDatabase.new(full_path, Xapian::DB_CREATE_OR_OPEN) - @@enquire = Xapian::Enquire.new(@@writable_db) - @@term_generator = Xapian::TermGenerator.new() - @@term_generator.set_flags(Xapian::TermGenerator::FLAG_SPELLING, 0) - @@term_generator.database = @@writable_db - @@term_generator.stemmer = @@stemmer - end - - ###################################################################### - # Search with a query or for similar models - - # Base class for Search and Similar below - class QueryBase - attr_accessor :offset - attr_accessor :limit - attr_accessor :query - attr_accessor :matches - attr_accessor :query_models - attr_accessor :runtime - attr_accessor :cached_results - - def initialize_db - self.runtime = 0.0 - - ActsAsXapian.readable_init - if ActsAsXapian.db.nil? - raise "ActsAsXapian not initialized" - end - end - - MSET_MAX_TRIES = 5 - MSET_MAX_DELAY = 5 - # Set self.query before calling this - def initialize_query(options) - #raise options.to_yaml - - self.runtime += Benchmark::realtime { - offset = options[:offset] || 0; offset = offset.to_i - limit = options[:limit] - raise "please specifiy maximum number of results to return with parameter :limit" if not limit - limit = limit.to_i - sort_by_prefix = options[:sort_by_prefix] || nil - sort_by_ascending = options[:sort_by_ascending].nil? ? true : options[:sort_by_ascending] - collapse_by_prefix = options[:collapse_by_prefix] || nil - - ActsAsXapian.enquire.query = self.query - - if sort_by_prefix.nil? - ActsAsXapian.enquire.sort_by_relevance! - else - value = ActsAsXapian.values_by_prefix[sort_by_prefix] - raise "couldn't find prefix '" + sort_by_prefix.to_s + "'" if value.nil? - ActsAsXapian.enquire.sort_by_value_then_relevance!(value, sort_by_ascending) - end - if collapse_by_prefix.nil? - ActsAsXapian.enquire.collapse_key = Xapian.BAD_VALUENO - else - value = ActsAsXapian.values_by_prefix[collapse_by_prefix] - raise "couldn't find prefix '" + collapse_by_prefix + "'" if value.nil? - ActsAsXapian.enquire.collapse_key = value - end - - tries = 0 - delay = 1 - begin - self.matches = ActsAsXapian.enquire.mset(offset, limit, 100) - rescue IOError => e - if e.message =~ /DatabaseModifiedError: / - # This should be a transient error, so back off and try again, up to a point - if tries > MSET_MAX_TRIES - raise "Received DatabaseModifiedError from Xapian even after retrying #{MSET_MAX_TRIES} times" - else - sleep delay - end - tries += 1 - delay *= 2 - delay = MSET_MAX_DELAY if delay > MSET_MAX_DELAY - - ActsAsXapian.db.reopen() - retry - else - raise - end - end - self.cached_results = nil - } - end - - # Return a description of the query - def description - self.query.description - end - - # Does the query have non-prefixed search terms in it? - def has_normal_search_terms? - ret = false - #x = '' - for t in self.query.terms - term = t.term - #x = x + term.to_yaml + term.size.to_s + term[0..0] + "*" - if term.size >= 2 && term[0..0] == 'Z' - # normal terms begin Z (for stemmed), then have no capital letter prefix - if term[1..1] == term[1..1].downcase - ret = true - end - end - end - return ret - end - - # Estimate total number of results - def matches_estimated - self.matches.matches_estimated - end - - # Return query string with spelling correction - def spelling_correction - correction = ActsAsXapian.query_parser.get_corrected_query_string - if correction.empty? - return nil - end - return correction - end - - # Return array of models found - def results - # If they've already pulled out the results, just return them. - if !self.cached_results.nil? - return self.cached_results - end - - docs = [] - self.runtime += Benchmark::realtime { - # Pull out all the results - iter = self.matches._begin - while not iter.equals(self.matches._end) - docs.push({:data => iter.document.data, - :percent => iter.percent, - :weight => iter.weight, - :collapse_count => iter.collapse_count}) - iter.next - end - } - - # Log time taken, excluding database lookups below which will be displayed separately by ActiveRecord - if ActiveRecord::Base.logger - ActiveRecord::Base.logger.add(Logger::DEBUG, " Xapian query (#{'%.5fs' % self.runtime}) #{self.log_description}") - end - - # Look up without too many SQL queries - lhash = {} - lhash.default = [] - for doc in docs - k = doc[:data].split('-') - lhash[k[0]] = lhash[k[0]] + [k[1]] - end - # for each class, look up all ids - chash = {} - for cls, ids in lhash - conditions = [ "#{cls.constantize.table_name}.#{cls.constantize.primary_key} in (?)", ids ] - found = cls.constantize.find(:all, :conditions => conditions, :include => cls.constantize.xapian_options[:eager_load]) - for f in found - chash[[cls, f.id]] = f - end - end - # now get them in right order again - results = [] - docs.each do |doc| - k = doc[:data].split('-') - model_instance = chash[[k[0], k[1].to_i]] - if model_instance - results << { :model => model_instance, - :percent => doc[:percent], - :weight => doc[:weight], - :collapse_count => doc[:collapse_count] } - end - end - self.cached_results = results - return results - end - end - - # Search for a query string, returns an array of hashes in result order. - # Each hash contains the actual Rails object in :model, and other detail - # about relevancy etc. in other keys. - class Search < QueryBase - attr_accessor :query_string - - # Note that model_classes is not only sometimes useful here - it's - # essential to make sure the classes have been loaded, and thus - # acts_as_xapian called on them, so we know the fields for the query - # parser. - - # model_classes - model classes to search within, e.g. [PublicBody, - # User]. Can take a single model class, or you can express the model - # class names in strings if you like. - # query_string - user inputed query string, with syntax much like Google Search - def initialize(model_classes, query_string, options = {}, user_query = nil) - # Check parameters, convert to actual array of model classes - new_model_classes = [] - model_classes = [model_classes] if model_classes.class != Array - for model_class in model_classes - raise "pass in the model class itself, or a string containing its name" if model_class.class != Class && model_class.class != String - model_class = model_class.constantize if model_class.class == String - new_model_classes.push(model_class) - end - model_classes = new_model_classes - - # Set things up - self.initialize_db - - # Case of a string, searching for a Google-like syntax query - self.query_string = query_string - - # Construct query which only finds things from specified models - model_query = Xapian::Query.new(Xapian::Query::OP_OR, model_classes.map{|mc| "M" + mc.to_s}) - if user_query.nil? - user_query = ActsAsXapian.query_parser.parse_query( - self.query_string, - Xapian::QueryParser::FLAG_BOOLEAN | Xapian::QueryParser::FLAG_PHRASE | - Xapian::QueryParser::FLAG_LOVEHATE | - Xapian::QueryParser::FLAG_SPELLING_CORRECTION) - end - self.query = Xapian::Query.new(Xapian::Query::OP_AND, model_query, user_query) - - # Call base class constructor - self.initialize_query(options) - end - - # Return just normal words in the query i.e. Not operators, ones in - # date ranges or similar. Use this for cheap highlighting with - # TextHelper::highlight, and excerpt. - def words_to_highlight - # TODO: In Ruby 1.9 we can do matching of any unicode letter with \p{L} - # But we still need to support ruby 1.8 for the time being so... - query_nopunc = self.query_string.gsub(/[^ёЁа-яА-Яa-zA-Zà-üÀ-Ü0-9:\.\/_]/iu, " ") - query_nopunc = query_nopunc.gsub(/\s+/, " ") - words = query_nopunc.split(" ") - # Remove anything with a :, . or / in it - words = words.find_all {|o| !o.match(/(:|\.|\/)/) } - words = words.find_all {|o| !o.match(/^(AND|NOT|OR|XOR)$/) } - return words - end - - # Text for lines in log file - def log_description - "Search: " + self.query_string - end - - end - - # Search for models which contain theimportant terms taken from a specified - # list of models. i.e. Use to find documents similar to one (or more) - # documents, or use to refine searches. - class Similar < QueryBase - attr_accessor :query_models - attr_accessor :important_terms - - # model_classes - model classes to search within, e.g. [PublicBody, User] - # query_models - list of models you want to find things similar to - def initialize(model_classes, query_models, options = {}) - self.initialize_db - - self.runtime += Benchmark::realtime { - # Case of an array, searching for models similar to those models in the array - self.query_models = query_models - - # Find the documents by their unique term - input_models_query = Xapian::Query.new(Xapian::Query::OP_OR, query_models.map{|m| "I" + m.xapian_document_term}) - ActsAsXapian.enquire.query = input_models_query - matches = ActsAsXapian.enquire.mset(0, 100, 100) # XXX so this whole method will only work with 100 docs - - # Get set of relevant terms for those documents - selection = Xapian::RSet.new() - iter = matches._begin - while not iter.equals(matches._end) - selection.add_document(iter) - iter.next - end - - # Bit weird that the function to make esets is part of the enquire - # object. This explains what exactly it does, which is to exclude - # terms in the existing query. - # http://thread.gmane.org/gmane.comp.search.xapian.general/3673/focus=3681 - eset = ActsAsXapian.enquire.eset(40, selection) - - # Do main search for them - self.important_terms = [] - iter = eset._begin - while not iter.equals(eset._end) - self.important_terms.push(iter.term) - iter.next - end - similar_query = Xapian::Query.new(Xapian::Query::OP_OR, self.important_terms) - # Exclude original - combined_query = Xapian::Query.new(Xapian::Query::OP_AND_NOT, similar_query, input_models_query) - - # Restrain to model classes - model_query = Xapian::Query.new(Xapian::Query::OP_OR, model_classes.map{|mc| "M" + mc.to_s}) - self.query = Xapian::Query.new(Xapian::Query::OP_AND, model_query, combined_query) - } - - # Call base class constructor - self.initialize_query(options) - end - - # Text for lines in log file - def log_description - "Similar: " + self.query_models.to_s - end - end - - ###################################################################### - # Index - - # Offline indexing job queue model, create with migration made - # using "script/generate acts_as_xapian" as described in ../README.txt - class ActsAsXapianJob < ActiveRecord::Base - end - - # Update index with any changes needed, call this offline. Usually call it - # from a script that exits - otherwise Xapian's writable database won't - # flush your changes. Specifying flush will reduce performance, but make - # sure that each index update is definitely saved to disk before - # logging in the database that it has been. - def ActsAsXapian.update_index(flush = false, verbose = false) - # STDOUT.puts("start of ActsAsXapian.update_index") if verbose - - # Before calling writable_init we have to make sure every model class has been initialized. - # i.e. has had its class code loaded, so acts_as_xapian has been called inside it, and - # we have the info from acts_as_xapian. - model_classes = ActsAsXapianJob.find_by_sql("select model from acts_as_xapian_jobs group by model").map {|a| a.model.constantize} - # If there are no models in the queue, then nothing to do - return if model_classes.size == 0 - - ActsAsXapian.writable_init - # Abort if full rebuild is going on - new_path = ActsAsXapian.db_path + ".new" - if File.exist?(new_path) - raise "aborting incremental index update while full index rebuild happens; found existing " + new_path - end - - ids_to_refresh = ActsAsXapianJob.find(:all).map() { |i| i.id } - for id in ids_to_refresh - job = nil - begin - ActiveRecord::Base.transaction do - begin - job = ActsAsXapianJob.find(id, :lock =>true) - rescue ActiveRecord::RecordNotFound => e - # This could happen if while we are working the model - # was updated a second time by another process. In that case - # ActsAsXapianJob.delete_all in xapian_mark_needs_index below - # might have removed the first job record while we are working on it. - #STDERR.puts("job with #{id} vanished under foot") if verbose - next - end - STDOUT.puts("ActsAsXapian.update_index #{job.action} #{job.model} #{job.model_id.to_s} #{Time.now.to_s}") if verbose - - begin - if job.action == 'update' - # XXX Index functions may reference other models, so we could eager load here too? - model = job.model.constantize.find(job.model_id) # :include => cls.constantize.xapian_options[:include] - model.xapian_index - elsif job.action == 'destroy' - # Make dummy model with right id, just for destruction - model = job.model.constantize.new - model.id = job.model_id - model.xapian_destroy - else - raise "unknown ActsAsXapianJob action '" + job.action + "'" - end - rescue ActiveRecord::RecordNotFound => e - # this can happen if the record was hand deleted in the database - job.action = 'destroy' - retry - end - if flush - ActsAsXapian.writable_db.flush - end - job.destroy - end - rescue => detail - # print any error, and carry on so other things are indexed - STDERR.puts(detail.backtrace.join("\n") + "\nFAILED ActsAsXapian.update_index job #{id} #{$!} " + (job.nil? ? "" : "model " + job.model + " id " + job.model_id.to_s)) - end - end - # We close the database when we're finished to remove the lock file. Since writable_init - # reopens it and recreates the environment every time we don't need to do further cleanup - ActsAsXapian.writable_db.flush - ActsAsXapian.writable_db.close - end - - def ActsAsXapian._is_xapian_db(path) - is_db = File.exist?(File.join(path, "iamflint")) || File.exist?(File.join(path, "iamchert")) - return is_db - end - - # You must specify *all* the models here, this totally rebuilds the Xapian - # database. You'll want any readers to reopen the database after this. - # - # Incremental update_index calls above are suspended while this rebuild - # happens (i.e. while the .new database is there) - any index update jobs - # are left in the database, and will run after the rebuild has finished. - - def ActsAsXapian.rebuild_index(model_classes, verbose = false, terms = true, values = true, texts = true, safe_rebuild = true) - #raise "when rebuilding all, please call as first and only thing done in process / task" if not ActsAsXapian.writable_db.nil? - prepare_environment - - update_existing = !(terms == true && values == true && texts == true) - # Delete any existing .new database, and open a new one which is a copy of the current one - new_path = ActsAsXapian.db_path + ".new" - old_path = ActsAsXapian.db_path - if File.exist?(new_path) - raise "found existing " + new_path + " which is not Xapian flint database, please delete for me" if not ActsAsXapian._is_xapian_db(new_path) - FileUtils.rm_r(new_path) - end - if update_existing - FileUtils.cp_r(old_path, new_path) - end - ActsAsXapian.writable_init - ActsAsXapian.writable_db.close # just to make an empty one to read - # Index everything - if safe_rebuild - _rebuild_index_safely(model_classes, verbose, terms, values, texts) - else - @@db_path = ActsAsXapian.db_path + ".new" - ActsAsXapian.writable_init - # Save time by running the indexing in one go and in-process - for model_class in model_classes - STDOUT.puts("ActsAsXapian.rebuild_index: Rebuilding #{model_class.to_s}") if verbose - model_class.find(:all).each do |model| - STDOUT.puts("ActsAsXapian.rebuild_index #{model_class} #{model.id}") if verbose - model.xapian_index(terms, values, texts) - end - end - ActsAsXapian.writable_db.flush - ActsAsXapian.writable_db.close - end - - # Rename into place - temp_path = old_path + ".tmp" - if File.exist?(temp_path) - @@db_path = old_path - raise "temporary database found " + temp_path + " which is not Xapian flint database, please delete for me" if not ActsAsXapian._is_xapian_db(temp_path) - FileUtils.rm_r(temp_path) - end - if File.exist?(old_path) - FileUtils.mv old_path, temp_path - end - FileUtils.mv new_path, old_path - - # Delete old database - if File.exist?(temp_path) - if not ActsAsXapian._is_xapian_db(temp_path) - @@db_path = old_path - raise "old database now at " + temp_path + " is not Xapian flint database, please delete for me" - end - FileUtils.rm_r(temp_path) - end - - # You'll want to restart your FastCGI or Mongrel processes after this, - # so they get the new db - @@db_path = old_path - end - - def ActsAsXapian._rebuild_index_safely(model_classes, verbose, terms, values, texts) - batch_size = 1000 - for model_class in model_classes - model_class_count = model_class.count - 0.step(model_class_count, batch_size) do |i| - # We fork here, so each batch is run in a different process. This is - # because otherwise we get a memory "leak" and you can't rebuild very - # large databases (however long you have!) - - ActiveRecord::Base.connection.disconnect! - - pid = Process.fork # XXX this will only work on Unix, tough - if pid - Process.waitpid(pid) - if not $?.success? - raise "batch fork child failed, exiting also" - end - # database connection doesn't survive a fork, rebuild it - else - # fully reopen the database each time (with a new object) - # (so doc ids and so on aren't preserved across the fork) - ActiveRecord::Base.establish_connection - @@db_path = ActsAsXapian.db_path + ".new" - ActsAsXapian.writable_init - STDOUT.puts("ActsAsXapian.rebuild_index: New batch. #{model_class.to_s} from #{i} to #{i + batch_size} of #{model_class_count} pid #{Process.pid.to_s}") if verbose - model_class.find(:all, :limit => batch_size, :offset => i, :order => :id).each do |model| - STDOUT.puts("ActsAsXapian.rebuild_index #{model_class} #{model.id}") if verbose - model.xapian_index(terms, values, texts) - end - ActsAsXapian.writable_db.flush - ActsAsXapian.writable_db.close - # database connection won't survive a fork, so shut it down - ActiveRecord::Base.connection.disconnect! - # brutal exit, so other shutdown code not run (for speed and safety) - Kernel.exit! 0 - end - - ActiveRecord::Base.establish_connection - - end - end - end - - ###################################################################### - # Instance methods that get injected into your model. - - module InstanceMethods - # Used internally - def xapian_document_term - self.class.to_s + "-" + self.id.to_s - end - - def xapian_value(field, type = nil, index_translations = false) - if index_translations && self.respond_to?("translations") - if type == :date or type == :boolean - value = single_xapian_value(field, type = type) - else - values = [] - for locale in self.translations.map{|x| x.locale} - I18n.with_locale(locale) do - values << single_xapian_value(field, type=type) - end - end - if values[0].kind_of?(Array) - values = values.flatten - value = values.reject{|x| x.nil?} - else - values = values.reject{|x| x.nil?} - value = values.join(" ") - end - end - else - value = single_xapian_value(field, type = type) - end - return value - end - - # Extract value of a field from the model - def single_xapian_value(field, type = nil) - value = self.send(field.to_sym) || self[field] - if type == :date - if value.kind_of?(Time) - value.utc.strftime("%Y%m%d") - elsif value.kind_of?(Date) - value.to_time.utc.strftime("%Y%m%d") - else - raise "Only Time or Date types supported by acts_as_xapian for :date fields, got " + value.class.to_s - end - elsif type == :boolean - value ? true : false - else - # Arrays are for terms which require multiple of them, e.g. tags - if value.kind_of?(Array) - value.map {|v| v.to_s} - else - value.to_s - end - end - end - - # Store record in the Xapian database - def xapian_index(terms = true, values = true, texts = true) - # if we have a conditional function for indexing, call it and destroy object if failed - if self.class.xapian_options.include?(:if) - if_value = xapian_value(self.class.xapian_options[:if], :boolean) - if not if_value - self.xapian_destroy - return - end - end - - existing_query = Xapian::Query.new("I" + self.xapian_document_term) - ActsAsXapian.enquire.query = existing_query - match = ActsAsXapian.enquire.mset(0,1,1).matches[0] - - if !match.nil? - doc = match.document - else - doc = Xapian::Document.new - doc.data = self.xapian_document_term - doc.add_term("M" + self.class.to_s) - doc.add_term("I" + doc.data) - end - # work out what to index - # 1. Which terms to index? We allow the user to specify particular ones - terms_to_index = [] - drop_all_terms = false - if terms and self.xapian_options[:terms] - terms_to_index = self.xapian_options[:terms].dup - if terms.is_a?(String) - terms_to_index.reject!{|term| !terms.include?(term[1])} - if terms_to_index.length == self.xapian_options[:terms].length - drop_all_terms = true - end - else - drop_all_terms = true - end - end - # 2. Texts to index? Currently, it's all or nothing - texts_to_index = [] - if texts and self.xapian_options[:texts] - texts_to_index = self.xapian_options[:texts] - end - # 3. Values to index? Currently, it's all or nothing - values_to_index = [] - if values and self.xapian_options[:values] - values_to_index = self.xapian_options[:values] - end - - # clear any existing data that we might want to replace - if drop_all_terms && texts - # as an optimisation, if we're reindexing all of both, we remove everything - doc.clear_terms - doc.add_term("M" + self.class.to_s) - doc.add_term("I" + doc.data) - else - term_prefixes_to_index = terms_to_index.map {|x| x[1]} - for existing_term in doc.terms - first_letter = existing_term.term[0...1] - if !"MI".include?(first_letter) # it's not one of the reserved value - if first_letter.match("^[A-Z]+") # it's a "value" (rather than indexed text) - if term_prefixes_to_index.include?(first_letter) # it's a value that we've been asked to index - doc.remove_term(existing_term.term) - end - elsif texts - doc.remove_term(existing_term.term) # it's text and we've been asked to reindex it - end - end - end - end - - for term in terms_to_index - value = xapian_value(term[0]) - if value.kind_of?(Array) - for v in value - doc.add_term(term[1] + v) - end - else - doc.add_term(term[1] + value) - end - end - - if values - doc.clear_values - for value in values_to_index - doc.add_value(value[1], xapian_value(value[0], value[3])) - end - end - if texts - ActsAsXapian.term_generator.document = doc - for text in texts_to_index - ActsAsXapian.term_generator.increase_termpos # stop phrases spanning different text fields - # XXX the "1" here is a weight that could be varied for a boost function - ActsAsXapian.term_generator.index_text(xapian_value(text, nil, true), 1) - end - end - - ActsAsXapian.writable_db.replace_document("I" + doc.data, doc) - end - - # Delete record from the Xapian database - def xapian_destroy - ActsAsXapian.writable_db.delete_document("I" + self.xapian_document_term) - end - - # Used to mark changes needed by batch indexer - def xapian_mark_needs_index - xapian_create_job('update', self.class.base_class.to_s, self.id) - end - - def xapian_mark_needs_destroy - xapian_create_job('destroy', self.class.base_class.to_s, self.id) - end - - # Allow reindexing to be skipped if a flag is set - def xapian_mark_needs_index_if_reindex - return true if (self.respond_to?(:no_xapian_reindex) && self.no_xapian_reindex == true) - xapian_mark_needs_index - end - - def xapian_create_job(action, model, model_id) - begin - ActiveRecord::Base.transaction(:requires_new => true) do - ActsAsXapianJob.delete_all([ "model = ? and model_id = ?", model, model_id]) - xapian_before_create_job_hook(action, model, model_id) - ActsAsXapianJob.create!(:model => model, - :model_id => model_id, - :action => action) - end - rescue ActiveRecord::RecordNotUnique => e - # Given the error handling in ActsAsXapian::update_index, we can just fail silently if - # another process has inserted an acts_as_xapian_jobs record for this model. - raise unless (e.message =~ /duplicate key value violates unique constraint "index_acts_as_xapian_jobs_on_model_and_model_id"/) - end - end - - # A hook method that can be used in tests to simulate e.g. an external process inserting a record - def xapian_before_create_job_hook(action, model, model_id) - end - - end - - ###################################################################### - # Main entry point, add acts_as_xapian to your model. - - module ActsMethods - # See top of this file for docs - def acts_as_xapian(options) - # Give error only on queries if bindings not available - if not ActsAsXapian.bindings_available - return - end - - include InstanceMethods - - cattr_accessor :xapian_options - self.xapian_options = options - - ActsAsXapian.init(self.class.to_s, options) - - after_save :xapian_mark_needs_index_if_reindex - after_destroy :xapian_mark_needs_destroy - end - end - -end - -# Reopen ActiveRecord and include the acts_as_xapian method -ActiveRecord::Base.extend ActsAsXapian::ActsMethods - - diff --git a/vendor/plugins/acts_as_xapian/lib/tasks/xapian.rake b/vendor/plugins/acts_as_xapian/lib/tasks/xapian.rake deleted file mode 100644 index c1986ce1e..000000000 --- a/vendor/plugins/acts_as_xapian/lib/tasks/xapian.rake +++ /dev/null @@ -1,66 +0,0 @@ -require 'rubygems' -require 'rake' -require 'rake/testtask' -require 'active_record' - -namespace :xapian do - # Parameters - specify "flush=true" to save changes to the Xapian database - # after each model that is updated. This is safer, but slower. Specify - # "verbose=true" to print model name as it is run. - desc 'Updates Xapian search index with changes to models since last call' - task :update_index => :environment do - ActsAsXapian.update_index(ENV['flush'] ? true : false, ENV['verbose'] ? true : false) - end - - # Parameters - specify 'models="PublicBody User"' to say which models - # you index with Xapian. - - # This totally rebuilds the database, so you will want to restart - # any web server afterwards to make sure it gets the changes, - # rather than still pointing to the old deleted database. Specify - # "verbose=true" to print model name as it is run. By default, - # all of the terms, values and texts are reindexed. You can - # suppress any of these by specifying, for example, "texts=false". - # You can specify that only certain terms should be updated by - # specifying their prefix(es) as a string, e.g. "terms=IV" will - # index the two terms I and V (and "terms=false" will index none, - # and "terms=true", the default, will index all) - - - desc 'Completely rebuilds Xapian search index (must specify all models)' - task :rebuild_index => :environment do - def coerce_arg(arg, default) - if arg == "false" - return false - elsif arg == "true" - return true - elsif arg.nil? - return default - else - return arg - end - end - raise "specify ALL your models with models=\"ModelName1 ModelName2\" as parameter" if ENV['models'].nil? - ActsAsXapian.rebuild_index(ENV['models'].split(" ").map{|m| m.constantize}, - coerce_arg(ENV['verbose'], false), - coerce_arg(ENV['terms'], true), - coerce_arg(ENV['values'], true), - coerce_arg(ENV['texts'], true)) - end - - # Parameters - are models, query, offset, limit, sort_by_prefix, - # collapse_by_prefix - desc 'Run a query, return YAML of results' - task :query => :environment do - raise "specify models=\"ModelName1 ModelName2\" as parameter" if ENV['models'].nil? - raise "specify query=\"your terms\" as parameter" if ENV['query'].nil? - s = ActsAsXapian::Search.new(ENV['models'].split(" ").map{|m| m.constantize}, - ENV['query'], - :offset => (ENV['offset'] || 0), :limit => (ENV['limit'] || 10), - :sort_by_prefix => (ENV['sort_by_prefix'] || nil), - :collapse_by_prefix => (ENV['collapse_by_prefix'] || nil) - ) - STDOUT.puts(s.results.to_yaml) - end -end - -- cgit v1.2.3 From 47f070d5732fa90edcc92b018709f1e3c5ca6e99 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 28 Nov 2013 16:39:14 +0000 Subject: Move the acts_as_xapian generator into lib/generators --- lib/acts_as_xapian/generators/acts_as_xapian/USAGE | 1 - .../generators/acts_as_xapian/acts_as_xapian_generator.rb | 13 ------------- .../generators/acts_as_xapian/templates/migration.rb | 14 -------------- lib/generators/acts_as_xapian/USAGE | 1 + lib/generators/acts_as_xapian/acts_as_xapian_generator.rb | 13 +++++++++++++ lib/generators/acts_as_xapian/templates/migration.rb | 14 ++++++++++++++ 6 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 lib/acts_as_xapian/generators/acts_as_xapian/USAGE delete mode 100644 lib/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb delete mode 100644 lib/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb create mode 100644 lib/generators/acts_as_xapian/USAGE create mode 100644 lib/generators/acts_as_xapian/acts_as_xapian_generator.rb create mode 100644 lib/generators/acts_as_xapian/templates/migration.rb diff --git a/lib/acts_as_xapian/generators/acts_as_xapian/USAGE b/lib/acts_as_xapian/generators/acts_as_xapian/USAGE deleted file mode 100644 index 2d027c46f..000000000 --- a/lib/acts_as_xapian/generators/acts_as_xapian/USAGE +++ /dev/null @@ -1 +0,0 @@ -./script/generate acts_as_xapian diff --git a/lib/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb b/lib/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb deleted file mode 100644 index a1cd1801d..000000000 --- a/lib/acts_as_xapian/generators/acts_as_xapian/acts_as_xapian_generator.rb +++ /dev/null @@ -1,13 +0,0 @@ -class ActsAsXapianGenerator < Rails::Generator::Base - def manifest - record do |m| - m.migration_template 'migration.rb', 'db/migrate', - :migration_file_name => "create_acts_as_xapian" - end - end - - protected - def banner - "Usage: #{$0} acts_as_xapian" - end -end diff --git a/lib/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb b/lib/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb deleted file mode 100644 index 84a9dd766..000000000 --- a/lib/acts_as_xapian/generators/acts_as_xapian/templates/migration.rb +++ /dev/null @@ -1,14 +0,0 @@ -class CreateActsAsXapian < ActiveRecord::Migration - def self.up - create_table :acts_as_xapian_jobs do |t| - t.column :model, :string, :null => false - t.column :model_id, :integer, :null => false - t.column :action, :string, :null => false - end - add_index :acts_as_xapian_jobs, [:model, :model_id], :unique => true - end - def self.down - drop_table :acts_as_xapian_jobs - end -end - diff --git a/lib/generators/acts_as_xapian/USAGE b/lib/generators/acts_as_xapian/USAGE new file mode 100644 index 000000000..2d027c46f --- /dev/null +++ b/lib/generators/acts_as_xapian/USAGE @@ -0,0 +1 @@ +./script/generate acts_as_xapian diff --git a/lib/generators/acts_as_xapian/acts_as_xapian_generator.rb b/lib/generators/acts_as_xapian/acts_as_xapian_generator.rb new file mode 100644 index 000000000..a1cd1801d --- /dev/null +++ b/lib/generators/acts_as_xapian/acts_as_xapian_generator.rb @@ -0,0 +1,13 @@ +class ActsAsXapianGenerator < Rails::Generator::Base + def manifest + record do |m| + m.migration_template 'migration.rb', 'db/migrate', + :migration_file_name => "create_acts_as_xapian" + end + end + + protected + def banner + "Usage: #{$0} acts_as_xapian" + end +end diff --git a/lib/generators/acts_as_xapian/templates/migration.rb b/lib/generators/acts_as_xapian/templates/migration.rb new file mode 100644 index 000000000..84a9dd766 --- /dev/null +++ b/lib/generators/acts_as_xapian/templates/migration.rb @@ -0,0 +1,14 @@ +class CreateActsAsXapian < ActiveRecord::Migration + def self.up + create_table :acts_as_xapian_jobs do |t| + t.column :model, :string, :null => false + t.column :model_id, :integer, :null => false + t.column :action, :string, :null => false + end + add_index :acts_as_xapian_jobs, [:model, :model_id], :unique => true + end + def self.down + drop_table :acts_as_xapian_jobs + end +end + -- cgit v1.2.3 From 6c4bf2ee10fca5030810a16c5b5b7c2a9b65dc9a Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 29 Nov 2013 07:38:53 +0000 Subject: Update the acts_as_xapian generator to work under Rails 3 --- .../acts_as_xapian/acts_as_xapian_generator.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/generators/acts_as_xapian/acts_as_xapian_generator.rb b/lib/generators/acts_as_xapian/acts_as_xapian_generator.rb index a1cd1801d..434c02cb5 100644 --- a/lib/generators/acts_as_xapian/acts_as_xapian_generator.rb +++ b/lib/generators/acts_as_xapian/acts_as_xapian_generator.rb @@ -1,13 +1,10 @@ -class ActsAsXapianGenerator < Rails::Generator::Base - def manifest - record do |m| - m.migration_template 'migration.rb', 'db/migrate', - :migration_file_name => "create_acts_as_xapian" - end +require 'rails/generators/active_record/migration' + +class ActsAsXapianGenerator < Rails::Generators::Base + include Rails::Generators::Migration + extend ActiveRecord::Generators::Migration + source_root File.expand_path("../templates", __FILE__) + def create_migration_file + migration_template "migration.rb", "db/migrate/add_acts_as_xapian_jobs.rb" end - - protected - def banner - "Usage: #{$0} acts_as_xapian" - end end -- cgit v1.2.3 From b6f2d93f8daaffdc80cf656d7bfe4fbf933fb9eb Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Fri, 29 Nov 2013 16:58:33 +0000 Subject: Add timestamps to public_body_translation fixtures. Between rails 3.2.0 and 4.0.1rc4, timestamps were constrained to be non-null. As globalize creates the public_body_translation table with timestamps, this means there are errors in the tests when trying to insert the fixture data, which doesn't have timestamps. These errors were only appearing on Travis because locally the test database is cloned from the development structure rather than being created by running the migrations. --- spec/fixtures/public_body_translations.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/fixtures/public_body_translations.yml b/spec/fixtures/public_body_translations.yml index 2030804ac..225bd74e2 100644 --- a/spec/fixtures/public_body_translations.yml +++ b/spec/fixtures/public_body_translations.yml @@ -10,6 +10,8 @@ geraldine_es_public_body_translation: notes: "" publication_scheme: "" disclosure_log: "" + created_at: 2007-10-24 10:51:01.161639 + updated_at: 2007-10-24 10:51:01.161639 geraldine_en_public_body_translation: name: Geraldine Quango @@ -23,6 +25,8 @@ geraldine_en_public_body_translation: notes: "" publication_scheme: "" disclosure_log: "" + created_at: 2007-10-24 10:51:01.161639 + updated_at: 2007-10-24 10:51:01.161639 humpadink_es_public_body_translation: name: "El Department for Humpadinking" @@ -36,6 +40,8 @@ humpadink_es_public_body_translation: notes: Baguette publication_scheme: "" disclosure_log: "" + created_at: 2007-10-24 10:51:01.161639 + updated_at: 2007-10-24 10:51:01.161639 humpadink_en_public_body_translation: name: "Department for Humpadinking" @@ -49,6 +55,8 @@ humpadink_en_public_body_translation: notes: An albatross told me!!! publication_scheme: "" disclosure_log: "" + created_at: 2007-10-24 10:51:01.161639 + updated_at: 2007-10-24 10:51:01.161639 forlorn_en_public_body_translation: name: "Department of Loneliness" @@ -62,6 +70,8 @@ forlorn_en_public_body_translation: notes: A very lonely public body that no one has corresponded with publication_scheme: "" disclosure_log: "" + created_at: 2007-10-24 10:51:01.161639 + updated_at: 2007-10-24 10:51:01.161639 silly_walks_en_public_body_translation: id: 6 @@ -75,6 +85,8 @@ silly_walks_en_public_body_translation: notes: You know the one. publication_scheme: "" disclosure_log: "" + created_at: 2007-10-24 10:51:01.161639 + updated_at: 2007-10-24 10:51:01.161639 sensible_walks_en_public_body_translation: id: 7 @@ -88,6 +100,8 @@ sensible_walks_en_public_body_translation: notes: I bet you’ve never heard of it. publication_scheme: "" disclosure_log: "" + created_at: 2008-10-25 10:51:01.161639 + updated_at: 2008-10-25 10:51:01.161639 other_public_body_translation: id: 8 @@ -101,6 +115,8 @@ other_public_body_translation: notes: More notes publication_scheme: "" disclosure_log: "" + created_at: 2008-10-25 10:51:01.161639 + updated_at: 2008-10-25 10:51:01.161639 humpadink_he_IL_public_body_translation: name: "Hebrew Humpadinking" @@ -114,6 +130,8 @@ humpadink_he_IL_public_body_translation: notes: An albatross told me!!! publication_scheme: "" disclosure_log: "" + created_at: 2007-10-24 10:51:01.161639 + updated_at: 2007-10-24 10:51:01.161639 accented_public_body_translation: id: 10 @@ -127,3 +145,5 @@ accented_public_body_translation: notes: This is to test unicode handling in body names publication_scheme: "" disclosure_log: "" + created_at: 2008-10-25 10:51:01.161639 + updated_at: 2008-10-25 10:51:01.161639 -- cgit v1.2.3 From aa9c6b15d3a021709ad5f7b3b653ca914a8c40b8 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 29 Nov 2013 08:28:20 +0000 Subject: Move themes from vendor/plugins to lib/themes These are essentially required in exactly the same way as before, but from lib/themes rather than vendor/plugins. This is the simplest possible change in order make the themes work outside vendor/plugins, I think, but it's not necessarily ideal. It would be worth considering whether these should be changed to Rails engines, as described here: http://guides.rubyonrails.org/engines.html --- .gitignore | 2 +- config/initializers/theme_loader.rb | 4 +++- doc/TRANSLATE.md | 2 +- lib/tasks/gettext.rake | 4 ++-- lib/tasks/themes.rake | 2 +- script/switch-theme.rb | 2 +- spec/spec_helper.rb | 1 + 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 87c55ce97..9e9a4b9f7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ .autotest *#*# TAGS -/vendor/plugins/*theme +/lib/themes /locale/model_attributes.rb /files/ /public/download diff --git a/config/initializers/theme_loader.rb b/config/initializers/theme_loader.rb index b3ae11e1e..9c79e513c 100644 --- a/config/initializers/theme_loader.rb +++ b/config/initializers/theme_loader.rb @@ -3,7 +3,9 @@ $alaveteli_route_extensions = [] def require_theme(theme_name) - theme_main_include = File.expand_path "../../../vendor/plugins/#{theme_name}/lib/alavetelitheme.rb", __FILE__ + theme_lib = Rails.root.join 'lib', 'themes', theme_name, 'lib' + $LOAD_PATH.unshift theme_lib.to_s + theme_main_include = Rails.root.join theme_lib, "alavetelitheme.rb" if File.exists? theme_main_include require theme_main_include end diff --git a/doc/TRANSLATE.md b/doc/TRANSLATE.md index 2a8b0269e..aef2cfdc9 100644 --- a/doc/TRANSLATE.md +++ b/doc/TRANSLATE.md @@ -79,7 +79,7 @@ must: language, using `bundle exec rake gettext:store_model_attributes`, followed by `bundle exec rake gettext:find` - * careful of including msgids from themes in `vendor/plugin`; + * careful of including msgids from themes in `lib/themes`; you might want to move them out of the way before running the above commands * this updates the PO template, but also merges it with the diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake index 366dfbe88..3f357213f 100644 --- a/lib/tasks/gettext.rake +++ b/lib/tasks/gettext.rake @@ -29,11 +29,11 @@ namespace :gettext do end def theme_files_to_translate(theme) - Dir.glob("{vendor/plugins/#{theme}/lib}/**/*.{rb,erb}") + Dir.glob("{lib/themes/#{theme}/lib}/**/*.{rb,erb}") end def theme_locale_path(theme) - File.join(Rails.root, "vendor", "plugins", theme, "locale-theme") + Rails.root.join "lib", "themes", theme, "locale-theme" end end diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake index 1eed92f1e..78ffe73be 100644 --- a/lib/tasks/themes.rake +++ b/lib/tasks/themes.rake @@ -2,7 +2,7 @@ namespace :themes do def plugin_dir - File.join(Rails.root,"vendor","plugins") + File.join(Rails.root,"lib","themes") end def theme_dir(theme_name) diff --git a/script/switch-theme.rb b/script/switch-theme.rb index 03d59a2f9..e6afcebb9 100755 --- a/script/switch-theme.rb +++ b/script/switch-theme.rb @@ -113,7 +113,7 @@ symlink(File.join(full_theme_path, 'public'), 'alavetelitheme') symlink(full_theme_path, - File.join(alaveteli_directory, 'vendor', 'plugins'), + File.join(alaveteli_directory, 'lib', 'themes'), requested_theme) STDERR.puts """Switched to #{requested_theme}! diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 57d58e276..1eeb8603b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,6 +16,7 @@ SimpleCov.start('rails') do add_filter 'lib/strip_attributes' add_filter 'lib/has_tag_string' add_filter 'lib/acts_as_xapian' + add_filter 'lib/themes' end Spork.prefork do -- cgit v1.2.3 From 2720064fc7978b922e373a5fc69029eaff8efc6d Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 29 Nov 2013 12:30:15 +0000 Subject: Revert "Silence deprecation warnings." This reverts commit 084cc9574c254cc0af2de4cd615fa99a5be8bc83. --- Rakefile | 1 - config/environment.rb | 2 -- 2 files changed, 3 deletions(-) diff --git a/Rakefile b/Rakefile index e134250fa..5fa2a360d 100644 --- a/Rakefile +++ b/Rakefile @@ -2,7 +2,6 @@ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) -ActiveSupport::Deprecation.silenced = true require 'rake' Alaveteli::Application.load_tasks if Rails.env == 'test' diff --git a/config/environment.rb b/config/environment.rb index 05f25a29e..196680b23 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,7 +1,5 @@ - # Load the rails application require File.expand_path('../application', __FILE__) -ActiveSupport::Deprecation.silenced = true # Initialize the rails application Alaveteli::Application.initialize! -- cgit v1.2.3 From aa521fc92857502598fb8b91fff6e8930f7eab05 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 3 Dec 2013 11:36:54 +0000 Subject: Ensure that the lib/themes directory exists before installing to there (An alternative would have been to add an empty lib/themes/.gitkeep file to this directory, but that makes ignoring the installed themes more complicated.) --- lib/tasks/themes.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake index 78ffe73be..7651145ca 100644 --- a/lib/tasks/themes.rake +++ b/lib/tasks/themes.rake @@ -84,6 +84,7 @@ namespace :themes do end def install_theme(theme_url, verbose, deprecated=false) + FileUtils.mkdir_p plugin_dir deprecation_string = deprecated ? " using deprecated THEME_URL" : "" theme_name = theme_url_to_theme_name theme_url puts "Installing theme #{theme_name}#{deprecation_string} from #{theme_url}" -- cgit v1.2.3 From 986b812ed34f5515e32fbd12165c175054ab3a1d Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 8 Oct 2013 14:31:44 +0100 Subject: Precompile assets only for production. Conflicts: script/rails-post-deploy --- script/rails-post-deploy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/script/rails-post-deploy b/script/rails-post-deploy index bd5165a72..a88e28b19 100755 --- a/script/rails-post-deploy +++ b/script/rails-post-deploy @@ -97,4 +97,7 @@ bundle exec rake db:migrate #--trace bundle exec rake themes:install -bundle exec rake assets:precompile +if [ "$OPTION_STAGING_SITE" = "0" ] +then + bundle exec rake assets:precompile +fi -- cgit v1.2.3 From d4e75c6715c7ab15b257bbdd42625301783e84f6 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 3 Dec 2013 12:01:07 +0000 Subject: Try to uninstall the old theme from vendor/plugins and lib/themes The theme install task would fail if there's an old theme present in vendor/plugins, since it doesn't try to uninstall the plugin from that location, only the new location. Then when the install.rb in the new plugin runs, it'll complain that there's a public/alavetelitheme symlink already present. This commit changes themes:install to try to uninstall the plugin from both locations. --- lib/tasks/themes.rake | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake index 7651145ca..65b142a63 100644 --- a/lib/tasks/themes.rake +++ b/lib/tasks/themes.rake @@ -9,6 +9,14 @@ namespace :themes do File.join(plugin_dir, theme_name) end + def old_theme_dir(theme_name) + File.join(Rails.root, "vendor", "plugins", theme_name) + end + + def possible_theme_dirs(theme_name) + [theme_dir(theme_name), old_theme_dir(theme_name)] + end + def checkout(commitish) puts "Checking out #{commitish}" if verbose system "git checkout #{commitish}" @@ -61,13 +69,14 @@ namespace :themes do end def uninstall(theme_name, verbose=false) - dir = theme_dir(theme_name) - if File.directory?(dir) - run_hook(theme_name, 'uninstall', verbose) - puts "Removing '#{dir}'" if verbose - rm_r dir - else - puts "Plugin doesn't exist: #{dir}" + possible_theme_dirs(theme_name).each do |dir| + if File.directory?(dir) + run_hook(theme_name, 'uninstall', verbose) + puts "Removing '#{dir}'" if verbose + rm_r dir + else + puts "Plugin doesn't exist: #{dir}" + end end end @@ -80,7 +89,7 @@ namespace :themes do end def installed?(theme_name) - File.directory?(theme_dir(theme_name)) + possible_theme_dirs(theme_name).any? { |dir| File.directory? dir } end def install_theme(theme_url, verbose, deprecated=false) -- cgit v1.2.3 From 0bb7893e3691da1afbfb0954872b42ce030f5de6 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 3 Dec 2013 15:16:53 +0000 Subject: Slightly nicer and more robust path reference. --- app/views/request/_bubble.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/request/_bubble.html.erb b/app/views/request/_bubble.html.erb index 8827d114d..e038bb3dc 100644 --- a/app/views/request/_bubble.html.erb +++ b/app/views/request/_bubble.html.erb @@ -13,7 +13,7 @@ :file_name => a.display_filename + '.html') %> <% img_filename = "icon_" + a.content_type.sub('/', '_') + "_large.png" - full_filename = File.expand_path(File.join(File.dirname(__FILE__), "../../assets/images", img_filename)) + full_filename = File.expand_path(Rails.root.join('app', 'assets', 'images', img_filename)) if File.exist?(full_filename) %> <%= link_to image_tag(img_filename, :class => "attachment_image", :alt => "Attachment"), attachment_path %> <% else %> -- cgit v1.2.3 From bd7c4da8c492ba7a1654378094ddc4870539e54b Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 3 Dec 2013 15:19:36 +0000 Subject: Ignore db/structure.sql Rails 3.2 uses structure.sql for schema dumps --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9e9a4b9f7..537a7abf2 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ alaveteli.sublime* webrat.log /.rbenv-version /db/development_structure.sql +/db/structure.sql /public/assets -- cgit v1.2.3 From 272c53a249abe4d302586c2b668d05306c978d8b Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 3 Dec 2013 11:20:12 +0000 Subject: Only precompile assets on non-staging sites. --- script/rails-post-deploy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/script/rails-post-deploy b/script/rails-post-deploy index bd5165a72..9b3730323 100755 --- a/script/rails-post-deploy +++ b/script/rails-post-deploy @@ -97,4 +97,7 @@ bundle exec rake db:migrate #--trace bundle exec rake themes:install +if [ "$OPTION_STAGING_SITE" = "0" ] +then bundle exec rake assets:precompile +fi -- cgit v1.2.3 From 723d6c1d73176bf723b0c4726a150b1842bedf8e Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 2 Dec 2013 16:11:31 +0000 Subject: Treat any document/pdf attachment as if it were application/pdf This is a fix for issue #1232. Richard Taylor pointed out that some PDF attachments had the non-standard content-type document/pdf, and that these weren't being treated as PDFs. (Ganesh Sittampalam discovered that all of these PDFs were generated by a Lexmark X945e, according to the PDF metadata.) This commit adds an extra case to normalise_content_type to map document/pdf to application/pdf. In fact, since the upgrade of the Mail gem in ccebe3c3d6d4dc5f81 the behaviour when handling the non-standard content-type document/pdf was much better, but this commit also means that you get the right icon for the attachment, and can be cherry-picked onto older versions to fix #1232. --- lib/mail_handler/mail_handler.rb | 2 +- spec/fixtures/files/document-pdf.email | 110 +++++++++++++++++++++++++++++ spec/lib/mail_handler/mail_handler_spec.rb | 6 ++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/files/document-pdf.email diff --git a/lib/mail_handler/mail_handler.rb b/lib/mail_handler/mail_handler.rb index 918f91180..53033d440 100644 --- a/lib/mail_handler/mail_handler.rb +++ b/lib/mail_handler/mail_handler.rb @@ -59,7 +59,7 @@ module MailHandler end # e.g. http://www.whatdotheyknow.com/request/copy_of_current_swessex_scr_opt#incoming-9928 - if content_type == 'application/acrobat' + if content_type == 'application/acrobat' or content_type == 'document/pdf' content_type = 'application/pdf' end diff --git a/spec/fixtures/files/document-pdf.email b/spec/fixtures/files/document-pdf.email new file mode 100644 index 000000000..f4fc6f0fe --- /dev/null +++ b/spec/fixtures/files/document-pdf.email @@ -0,0 +1,110 @@ +From authority@example.org Tue Dec 3 11:13:02 2013 +Return-path: +Envelope-to: requester@example.org +Delivery-date: Tue, 03 Dec 2013 11:13:00 +0000 +From: Test Authority +To: requester@example.org +Subject: testing a PDF attachment with the wrong content-type +Date: Tue, 03 Dec 2013 11:12:45 +0000 +Message-ID: <87li09xuasdfasdfpoija@blahblah> +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="=-=-=" + +--=-=-= +Content-Type: text/plain + +Here's a PDF attachement which has a document/pdf content-type, +when it really should be application/pdf. + + +--=-=-= +Content-Type: application/pdf +Content-Disposition: attachment; filename=tiny-example.pdf +Content-Transfer-Encoding: base64 +Content-Description: a very small example PDF + +JVBERi0xLjUKJbXtrvsKMyAwIG9iago8PCAvTGVuZ3RoIDQgMCBSCiAgIC9GaWx0ZXIgL0ZsYXRl +RGVjb2RlCj4+CnN0cmVhbQp4nCvkMlAAwaJ0Bf1EA4X0Yi6nEC5DA1M9IwNLYzNjsBwS18hIz9TI +0tBSwdzIQs/Y3MLAzEQhJJdLP03XQBeoUCEkjStawyM1JydfUTM2xIvLNYQrkAsAJG8VmQplbmRz +dHJlYW0KZW5kb2JqCjQgMCBvYmoKICAgOTIKZW5kb2JqCjIgMCBvYmoKPDwKICAgL0V4dEdTdGF0 +ZSA8PAogICAgICAvYTAgPDwgL0NBIDEgL2NhIDEgPj4KICAgPj4KICAgL0ZvbnQgPDwKICAgICAg +L2YtMC0wIDUgMCBSCiAgID4+Cj4+CmVuZG9iago2IDAgb2JqCjw8IC9UeXBlIC9QYWdlCiAgIC9Q +YXJlbnQgMSAwIFIKICAgL01lZGlhQm94IFsgMCAwIDU5NS4yNzU1NzQgODQxLjg4OTc3MSBdCiAg +IC9Db250ZW50cyAzIDAgUgogICAvR3JvdXAgPDwKICAgICAgL1R5cGUgL0dyb3VwCiAgICAgIC9T +IC9UcmFuc3BhcmVuY3kKICAgICAgL0kgdHJ1ZQogICAgICAvQ1MgL0RldmljZVJHQgogICA+Pgog +ICAvUmVzb3VyY2VzIDIgMCBSCj4+CmVuZG9iago3IDAgb2JqCjw8IC9MZW5ndGggOCAwIFIKICAg +L0ZpbHRlciAvRmxhdGVEZWNvZGUKICAgL0xlbmd0aDEgMzcxNgo+PgpzdHJlYW0KeJzlVnt0lMUV +v/P9vtndZB/5drMbiUlww7ookJCQECCAzYJBBSrlETXYxgayxIjQJAQVTOOCFAREgwKLAtIUkSpE +mlIkG2OtVN4xrZXH8Y0gFGkjRouoa5j0btCe0/boOW3/6OnpzM58cx9z5/7ud+fuR4KI4mkBgbxl +s6dVvXnmiTlECBJpt5bdPddLd6TlE8mXiIQqr7p9dvXgu2cSmZmmbbfPml9etrfAzOtG1o9UzJgW +7O56fQSRZRbzhlQwwxYx3cL0ZqavrJg9d97lhs76lgNM95pVWTaNKM7N9HtMp86eNq9KrzFVM93F +tLdqzoyqEeaPeRmXyj5UkEblKqyXy83srZkuD9j0L8n0pbDIkKZT1p4jHYPIONJxpCM70Znu9Kc7 +08t16qpBStdpFTY7Pv9kjqkfCTrItkbIo2SljIDbskZ7VqeF8SYzUuWwOJFKus3o6sjh3yDKGnmK +F9k7JtqFKEnMZaO5Th/PvoOHtHcPHbrY55A8enGDFowO0PZf5DBpVN39vh7Sa8lNKVQVuJI8Im6J +Zan0PCNki0209mpxRWwrUlM8msVjofGaK2FMKp92vmOP05XPzp8633HKOMf9/DlmZAf6FaRVpTWk +vZrWmSYLqEAUaAWeghSZYc6yZMVlxFdSpajUKj2VKXEl1aJEeNJ7i9ycIUM9DuHzktOg3BwyDxS+ +PiazHuraYWtvnrl/etmrd6rzar/o13VSmCPaU0vXtTi02259cf/gwdv7Z4hhIl4kimvVO3vW7ty+ +kV877eZpvsnNMe/TTGs1YaHrdHaaw9ORHbAbMiAnylJZJTulqSdKvt2RiMn9RcfX8biL42Gly2hc +INHU4qIWW8S1olecK2ESXJ4xvXrgX4JunMsO+AqSa6nWFDKHLKG4UHzIWmsL2UOOUELICDlrXQ3J +nclORtrH5HEnMdS8wX2vyokh9fWJzVrN6sZta1Y1Nq7qFC51rvNj9ZFw4viZgwfPfHBg/9kN6oDq +UB8y+HzG6BbDOLU0kRdzlPMBFEd5gQQzLdIXahazFND5ahhd45usRcUtRN0vDZs6siMnn19U1qmu +V7LFLrJ6rROtKPHnenycGvBB5LW1tbk3eZTizKhW68UM6mmCrcdumo10bQI/e5PBHAeFqFtMEdPE +PHGfeFTbp73t7evN9g73Nqb36e6O3QFqEJNFKcvrvpInsjz/b/JvboLPeFusExvERu4NX/V93A+I +Az0a8lv3/6tN/Ns7tb+j0DPr/5Ev/0eNszdCbdx301baILYwVc7sauY0aDtoMd3FnJdFm1imZTJv +C3XSYdZ8gNqwVScxjnKZS/SG1Oi8KKKdbCOfb0a+2aSTPkHfqU/WI/oZvZ2G6jV6u16q14hcbJI3 +yy088rFXc3E9vYIi4jjV0PM4i1y8oBfqDjqOdmyl03xK7F22UT1tplr2xS0qKaTVapOZs1+20zru +lSxv5yw9zN49LxbRUXoMunYDbRRHGVcbXaBFKNJCnBy5Wjn7v59ttfP+dVSj880V8aS0Acxj7/ms +6T1zGjLl0Z7eybeslopos4nLktnHp8QitkW8LDpMq6iBDuMHqMZbYrHu05/Wb6D6SxFAKdWz7XWx +PaZyMZ+xx3ptzLp2j14qttJZvdQ8nW3vjSHiM3dqkxlROb3A4x6TwZhGiMVYxp7GpGnUbh6nZ/F+ +tmCuY9RElcijmbyqpe20gzIRpnq21IPXNFRe4J0b9BOMuV48pF2gdhRSPyrXz3Gs+S+GwkTNZpPU +oQnK8BpNmn9ssCkwqdh7YGp6ZsY/kF7D7G2iiU32+d5Id/fEYj1FTm2SqU3wW5p0v+/ENwlPZGaM +n1jsbbo4pvArq2NKC5k3pZiXMYrZzB9T2COLHdok/fwbW9rkLavwLjeW+4YvN2YMz7xUI7Sie2dN +WLj6hwkjP6UrLD053L47tfTr52fHurbb58TdxGRMeKmq8GyerdKI7Cc/OxadZJ/zT9XGxBlazil7 +UO+gai2fduunqRp5sdre0wp5NLChuTyaYk7Evld6rJhQRAOogiuzxjX58dipukdL4qce0RYEur9U +iLrxhR+f5+CzMC448KnCeYW/+PGJAx+H0enHR8tHyY8UzoXxYRgdUfw5ij8pnB2OD0bjjMIfc3D6 +1BR5OoxTrHhqCt4/mSXfj+JkFk4ovKdwPAfvuvFOGG8rvOXCm3V4oxWvKxxj9WN1OHrkenm0Dkeu +x+HXUuRhhddS8AeFVxV+r/A7hfYwXmnrLV9RaOuNQzk4qLBvsVPuS8XeJOxReFnhtwq7FV5S+I3C +iwq/VnhBoVXheSdalvhli0KkuVVGFJp3lcjmVjQv0Hc955e7SgLd2BXQn/Njp8Kvwtih8EuFJoVf +KGwP4lkHGrf5ZWMQ27a65DY/trrwDDv9TBRPK/xcYYvCUy5sVnhyk0M+mYNNDvwsiAZWaQjjpwob +n7DJjQpP2LBhfbLcEMT6dYZcn4x1Bh6Px2MKa8N2uVYhbMca3rQmjNWrHHL11VjlwKNRPLKyVT6i +sLK+RK5sxcoFev3DfllfgvqA/rAfDymseHCgXKHw4EAsZ5jLR2HZUqtc5sZSKx5gxgNBLOFILfFj +sRM/UVh0v1MuUrjfiYUKCxRCCoHu++rq5H0KdXX4cRC1RR5Z68e9CvMV5jlwjw13x+MuhblR1EQx +J4rqKKoUKhV+pDArHXcqzHSOljOn4A6FijrczkS5wgyFoEKZwnSFacNRGsVtNpQofF/hVoWpxfFy +ahTF8bglKVnekoObFW7ik28ajSIPpghDTumFyW5MGpcoJynwN8j3FCbcaMgJCjca+K7CeJaMVxg3 +1pDjEjE2zS7HGrjBjusVrgtjTBiFCtdqmfLaKEa3YtR4BBQKFL5zjUt+x41rRibIa1wYOcIuRwa6 +EzDCjuEK+QrDhrrlsCiGDjHkUDeG5FnlEAN5VgzujVw7cgZZZY7CICuys6wy244sKwZmxsmBBjLj +kJGDAf39ckAQ/fu5ZH8/+rlw9VV+efUoXOVHX79V9k2A34orFXwKfRKQzjjTXfAGcUUUvRlC7yDS +7EjlCKYqpERx+WgkM5Gs0CuIyzhSlykk8aakZHgU3AqJCi5WcCk4GatzNIw6JAThULDbkqRdwcba +tiRYFeINxClYWM2iYHbDFITOQp0zwAPmQnGVNaSWCWGAFEREBBc/JAb8LzT6bzvwrS3tr1SHeSwK +ZW5kc3RyZWFtCmVuZG9iago4IDAgb2JqCiAgIDI0MDIKZW5kb2JqCjkgMCBvYmoKPDwgL0xlbmd0 +aCAxMCAwIFIKICAgL0ZpbHRlciAvRmxhdGVEZWNvZGUKPj4Kc3RyZWFtCnicXVAxbsQgEOx5xZZ3 +xQnbykUpkKXo0ri4JIqTB2BYHKQYEMaFf58FThcpBcwsuzOMll+Gl8HZBPw9ejViAmOdjrj6LSqE +CWfrWNuBtirdqnKrRQbGSTzua8JlcMYzIYB/UHNNcYfDs/YTHhkA8LeoMVo3w+HrMtancQvhBxd0 +CRrW96DRkN1Vhle5IPAiPg2a+jbtJ5L9TXzuAaErdVsjKa9xDVJhlG5GJpqmB2FMz9Dpf71zVUxG +fcvIxMMTTTYNAROP58IJiKvKVeamcvITXVs4Qfa+ueRf8jru8dUWIyUvOyuRc1jr8L7W4ENWlfML +jQ547AplbmRzdHJlYW0KZW5kb2JqCjEwIDAgb2JqCiAgIDI0NgplbmRvYmoKMTEgMCBvYmoKPDwg +L1R5cGUgL0ZvbnREZXNjcmlwdG9yCiAgIC9Gb250TmFtZSAvUlFaWlJTK0RlamFWdVNhbnMKICAg +L0ZvbnRGYW1pbHkgKERlamFWdSBTYW5zKQogICAvRmxhZ3MgMzIKICAgL0ZvbnRCQm94IFsgLTEw +MjAgLTQxNSAxNjgwIDExNjYgXQogICAvSXRhbGljQW5nbGUgMAogICAvQXNjZW50IDkyOAogICAv +RGVzY2VudCAtMjM1CiAgIC9DYXBIZWlnaHQgMTE2NgogICAvU3RlbVYgODAKICAgL1N0ZW1IIDgw +CiAgIC9Gb250RmlsZTIgNyAwIFIKPj4KZW5kb2JqCjUgMCBvYmoKPDwgL1R5cGUgL0ZvbnQKICAg +L1N1YnR5cGUgL1RydWVUeXBlCiAgIC9CYXNlRm9udCAvUlFaWlJTK0RlamFWdVNhbnMKICAgL0Zp +cnN0Q2hhciAzMgogICAvTGFzdENoYXIgMTExCiAgIC9Gb250RGVzY3JpcHRvciAxMSAwIFIKICAg +L0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcKICAgL1dpZHRocyBbIDAgNDAwIDAgMCAwIDAgMCAw +IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAg +MCAwIDAgMCA3NTEgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAw +IDAgMCAwIDAgMCA2MTUgMCAwIDAgMCAwIDAgMjc3IDAgMCA2MTEgXQogICAgL1RvVW5pY29kZSA5 +IDAgUgo+PgplbmRvYmoKMSAwIG9iago8PCAvVHlwZSAvUGFnZXMKICAgL0tpZHMgWyA2IDAgUiBd +CiAgIC9Db3VudCAxCj4+CmVuZG9iagoxMiAwIG9iago8PCAvQ3JlYXRvciAoY2Fpcm8gMS4xMi4x +NiAoaHR0cDovL2NhaXJvZ3JhcGhpY3Mub3JnKSkKICAgL1Byb2R1Y2VyIChjYWlybyAxLjEyLjE2 +IChodHRwOi8vY2Fpcm9ncmFwaGljcy5vcmcpKQo+PgplbmRvYmoKMTMgMCBvYmoKPDwgL1R5cGUg +L0NhdGFsb2cKICAgL1BhZ2VzIDEgMCBSCj4+CmVuZG9iagp4cmVmCjAgMTQKMDAwMDAwMDAwMCA2 +NTUzNSBmIAowMDAwMDA0MDYyIDAwMDAwIG4gCjAwMDAwMDAyMDUgMDAwMDAgbiAKMDAwMDAwMDAx +NSAwMDAwMCBuIAowMDAwMDAwMTg0IDAwMDAwIG4gCjAwMDAwMDM2NzkgMDAwMDAgbiAKMDAwMDAw +MDMxNCAwMDAwMCBuIAowMDAwMDAwNTQyIDAwMDAwIG4gCjAwMDAwMDMwMzggMDAwMDAgbiAKMDAw +MDAwMzA2MSAwMDAwMCBuIAowMDAwMDAzMzg1IDAwMDAwIG4gCjAwMDAwMDM0MDggMDAwMDAgbiAK +MDAwMDAwNDEyNyAwMDAwMCBuIAowMDAwMDA0MjU3IDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUg +MTQKICAgL1Jvb3QgMTMgMCBSCiAgIC9JbmZvIDEyIDAgUgo+PgpzdGFydHhyZWYKNDMxMAolJUVP +Rgo= +--=-=-=-- + diff --git a/spec/lib/mail_handler/mail_handler_spec.rb b/spec/lib/mail_handler/mail_handler_spec.rb index bc027eaec..49a65dade 100644 --- a/spec/lib/mail_handler/mail_handler_spec.rb +++ b/spec/lib/mail_handler/mail_handler_spec.rb @@ -409,6 +409,12 @@ describe 'when getting attachment attributes' do attributes[1][:body].length.should == 7769 end + it 'should treat a document/pdf attachment as application/pdf' do + mail = get_fixture_mail('document-pdf.email') + attributes = MailHandler.get_attachment_attributes(mail) + attributes[1][:content_type].should == "application/pdf" + end + it 'should produce a consistent set of url_part_numbers, content_types, within_rfc822_subjects and filenames from an example mail with lots of attachments' do mail = get_fixture_mail('many-attachments-date-header.email') -- cgit v1.2.3 From baf03db06dba70bab6e0b9ceb3a42da3a6a135c5 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 3 Dec 2013 18:02:03 +0000 Subject: Add back the acts_as_xapian Rake tasks An effect of moving the acts_as_xapian plugin out of vendor/plugins is that its Rake tasks are no longer found. A fix for this (although not necessarily the best) is in this commit - load everything in lib/acts_as_xapian/tasks/*.rake in the Rakefile. --- Rakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Rakefile b/Rakefile index 5fa2a360d..3c4766bec 100644 --- a/Rakefile +++ b/Rakefile @@ -7,3 +7,5 @@ Alaveteli::Application.load_tasks if Rails.env == 'test' Dir[File.join(File.dirname(__FILE__),'commonlib','rblib','tests','*.rake')].each { |file| load(file) } end +# Make sure the the acts_as_xapian tasks are also loaded: +Dir[File.join(File.dirname(__FILE__),'lib','acts_as_xapian','tasks','*.rake')].each { |file| load(file) } -- cgit v1.2.3 From 3e5696d71df5a2726f58ea0c446b68055924c2d7 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 3 Dec 2013 18:08:03 +0000 Subject: Add an 'Allow' directive to allow crawling of cached attachments. Should resolve #1233 --- public/robots.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/robots.txt b/public/robots.txt index 279573d31..969e10826 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -8,7 +8,10 @@ # Crawl-delay: 1 # http://www.bing.com/community/blogs/webmaster/archive/2009/08/10/crawl-delay-and-the-bing-crawler-msnbot.aspx -# This file uses the non-standard extension characters * and $, which are supported by Google and Yahoo! +# This file uses the non-standard extension characters * and $, which +# are supported by Google and Yahoo! and the 'Allow' directive, which +# is supported by Google. + # http://code.google.com/web/controlcrawlindex/docs/robots_txt.html # http://help.yahoo.com/l/us/yahoo/search/webcrawler/slurp-02.html @@ -23,6 +26,7 @@ Disallow: */user/contact/ Disallow: */feed/ Disallow: */profile/ Disallow: */signin +Allow: */request/*/response/*/attach/* Disallow: */request/*/response/ Disallow: */body/*/view_email$ -- cgit v1.2.3 From 656d2d1170cbcf42b8ff0f0d8321e95702636707 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 3 Dec 2013 18:27:50 +0000 Subject: Use Rails.root in the Rakefile It seems more robust (or certainly easier to understand on reading the code) to make these paths relative to the Rails root rather than File.dirname(__FILE__). --- Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 3c4766bec..09582dd77 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ require File.expand_path('../config/application', __FILE__) require 'rake' Alaveteli::Application.load_tasks if Rails.env == 'test' - Dir[File.join(File.dirname(__FILE__),'commonlib','rblib','tests','*.rake')].each { |file| load(file) } + Dir[Rails.root.join('commonlib','rblib','tests','*.rake')].each { |file| load(file) } end # Make sure the the acts_as_xapian tasks are also loaded: -Dir[File.join(File.dirname(__FILE__),'lib','acts_as_xapian','tasks','*.rake')].each { |file| load(file) } +Dir[Rails.root.join('lib','acts_as_xapian','tasks','*.rake')].each { |file| load(file) } -- cgit v1.2.3 From f920268650b6bb906be1828ed26a3732f4d10cc1 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 3 Dec 2013 17:30:16 +0000 Subject: Fix the command-line CSV importer under Ruby 1.9 Under Ruby 1.8.7, you can parse a CSV file with the following code (Example A): require 'csv' CSV.parse('foo.csv') do |row| puts "got row: #{row.inspect}" end Rather confusingly, under Ruby 1.8.7, CSV.parse can also take a string representation of the contents of the file as its parameter, so this also works (Example B): require 'csv' CSV.parse("1,hello,red\n2,goodbye,green") do |row| puts "got row: #{row.inspect}" end However under Ruby 1.9.3, CSV.parse only expects a string representation of the contents of the CSV file, so only Example B works; Example B fails silently (interpreting the filename as a single cell CSV file, typically). The import:import_csv rake task unfortunately relied on both A and B working. This commit fixes this by adding PublicBody.import_csv_from_file, and refactoring PublicBody.import_csv to use the newly added class method, and adds a test to check for any regression in this behaviour. (This means that the usage of import_csv in the admin public body controller's import_csv action could now be changed to use PublicBody.import_csv_from_file directly from the uploaded file, which would be more efficient and cope with larger files without using lots of memory.) Fixes #1229 --- app/controllers/admin_public_body_controller.rb | 2 ++ app/models/public_body.rb | 23 +++++++++++++++++++---- lib/tasks/import.rake | 12 ++++++------ spec/models/public_body_spec.rb | 14 ++++++++++++++ 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb index e0da234b0..88e275960 100644 --- a/app/controllers/admin_public_body_controller.rb +++ b/app/controllers/admin_public_body_controller.rb @@ -143,6 +143,8 @@ class AdminPublicBodyController < AdminController @errors = "" if request.post? dry_run_only = (params['commit'] == 'Upload' ? false : true) + # (FIXME: both of these cases could now be changed to use + # PublicBody.import_csv_from_file.) # Read file from params if params[:csv_file] csv_contents = params[:csv_file].read diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 8e474c797..eb0905f9e 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -369,10 +369,24 @@ class PublicBody < ActiveRecord::Base class ImportCSVDryRun < StandardError end - # Import from CSV. Just tests things and returns messages if dry_run is true. - # Returns an array of [array of errors, array of notes]. If there are errors, - # always rolls back (as with dry_run). + # Import from a string in CSV format. + # Just tests things and returns messages if dry_run is true. + # Returns an array of [array of errors, array of notes]. If there + # are errors, always rolls back (as with dry_run). def self.import_csv(csv, tag, tag_behaviour, dry_run, editor, available_locales = []) + tmp_csv = nil + Tempfile.open('alaveteli') do |f| + f.write csv + tmp_csv = f + end + PublicBody.import_csv_from_file(tmp_csv.path, tag, tag_behaviour, dry_run, editor, available_locales) + end + + # Import from a CSV file. + # Just tests things and returns messages if dry_run is true. + # Returns an array of [array of errors, array of notes]. If there + # are errors, always rolls back (as with dry_run). + def self.import_csv_from_file(csv_filename, tag, tag_behaviour, dry_run, editor, available_locales = []) errors = [] notes = [] available_locales = [I18n.default_locale] if available_locales.empty? @@ -398,7 +412,8 @@ class PublicBody < ActiveRecord::Base set_of_importing = Set.new() field_names = { 'name'=>1, 'request_email'=>2 } # Default values in case no field list is given line = 0 - CSV.parse(csv) do |row| + + CSV.foreach(csv_filename) do |row| line = line + 1 # Parse the first line as a field list if it starts with '#' diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index 0e8397fde..c8183c745 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -54,12 +54,12 @@ namespace :import do STDERR.puts "Now importing the public bodies..." # Now it's (probably) safe to try to import: - errors, notes = PublicBody.import_csv(tmp_csv.path, - tag='', - tag_behaviour='replace', - dryrun, - editor="#{ENV['USER']} (Unix user)", - I18n.available_locales) do |row_number, fields| + errors, notes = PublicBody.import_csv_from_file(tmp_csv.path, + tag='', + tag_behaviour='replace', + dryrun, + editor="#{ENV['USER']} (Unix user)", + I18n.available_locales) do |row_number, fields| percent_complete = (100 * row_number.to_f / number_of_rows).to_i STDERR.print "#{row_number} out of #{number_of_rows} " STDERR.puts "(#{percent_complete}% complete)" diff --git a/spec/models/public_body_spec.rb b/spec/models/public_body_spec.rb index 23842ccff..d1e2e233d 100644 --- a/spec/models/public_body_spec.rb +++ b/spec/models/public_body_spec.rb @@ -473,6 +473,20 @@ describe PublicBody, " when loading CSV files" do PublicBody.count.should == original_count end + + it "should be able to load CSV from a file as well as a string" do + # Essentially the same code is used for import_csv_from_file + # as import_csv, so this is just a basic check that + # import_csv_from_file can load from a file at all. (It would + # be easy to introduce a regression that broke this, because + # of the confusing change in behaviour of CSV.parse between + # Ruby 1.8 and 1.9.) + original_count = PublicBody.count + filename = file_fixture_name('fake-authority-type-with-field-names.csv') + PublicBody.import_csv_from_file(filename, '', 'replace', false, 'someadmin') + PublicBody.count.should == original_count + 3 + end + end describe PublicBody do -- cgit v1.2.3 From b02aabf77d9dbe137a6c0893d42355dc89df29a2 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 4 Dec 2013 08:26:22 +0000 Subject: Upgrade Rails to 3.2.16 to get fixes for CVE-2013-6414, CVE-2013-4491, CVE-2013-6417, CVE-2013-6415. --- Gemfile | 2 +- Gemfile.lock | 56 ++++++++++++++++++++++++++++---------------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Gemfile b/Gemfile index e4b12100e..c5cd85233 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ if File.exist? "/etc/debian_version" and File.open("/etc/debian_version").read.s end source 'https://rubygems.org' -gem 'rails', '3.2.15' +gem 'rails', '3.2.16' gem 'pg' diff --git a/Gemfile.lock b/Gemfile.lock index 46c018352..6c0c99eeb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,12 +18,12 @@ GIT GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.15) - actionpack (= 3.2.15) + actionmailer (3.2.16) + actionpack (= 3.2.16) mail (~> 2.5.4) - actionpack (3.2.15) - activemodel (= 3.2.15) - activesupport (= 3.2.15) + actionpack (3.2.16) + activemodel (= 3.2.16) + activesupport (= 3.2.16) builder (~> 3.0.0) erubis (~> 2.7.0) journey (~> 1.0.4) @@ -31,23 +31,23 @@ GEM rack-cache (~> 1.2) rack-test (~> 0.6.1) sprockets (~> 2.2.1) - activemodel (3.2.15) - activesupport (= 3.2.15) + activemodel (3.2.16) + activesupport (= 3.2.16) builder (~> 3.0.0) - activerecord (3.2.15) - activemodel (= 3.2.15) - activesupport (= 3.2.15) + activerecord (3.2.16) + activemodel (= 3.2.16) + activesupport (= 3.2.16) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activeresource (3.2.15) - activemodel (= 3.2.15) - activesupport (= 3.2.15) - activesupport (3.2.15) + activeresource (3.2.16) + activemodel (= 3.2.16) + activesupport (= 3.2.16) + activesupport (3.2.16) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) annotate (2.5.0) rake - arel (3.0.2) + arel (3.0.3) bootstrap-sass (2.3.1.2) sass (~> 3.2) builder (3.0.4) @@ -113,7 +113,7 @@ GEM tilt highline (1.6.19) hike (1.2.3) - i18n (0.6.5) + i18n (0.6.9) journey (1.0.4) jquery-rails (3.0.4) railties (>= 3.0, < 5.0) @@ -139,7 +139,7 @@ GEM sqlite3 (~> 1.3) thin (~> 1.5.0) memcache-client (1.8.5) - mime-types (1.25) + mime-types (1.25.1) multi_json (1.8.2) net-http-local (0.1.2) net-purge (0.1.0) @@ -166,19 +166,19 @@ GEM rack rack-test (0.6.2) rack (>= 1.0) - rails (3.2.15) - actionmailer (= 3.2.15) - actionpack (= 3.2.15) - activerecord (= 3.2.15) - activeresource (= 3.2.15) - activesupport (= 3.2.15) + rails (3.2.16) + actionmailer (= 3.2.16) + actionpack (= 3.2.16) + activerecord (= 3.2.16) + activeresource (= 3.2.16) + activesupport (= 3.2.16) bundler (~> 1.0) - railties (= 3.2.15) + railties (= 3.2.16) rails-i18n (0.7.3) i18n (~> 0.5) - railties (3.2.15) - actionpack (= 3.2.15) - activesupport (= 3.2.15) + railties (3.2.16) + actionpack (= 3.2.16) + activesupport (= 3.2.16) rack-ssl (~> 1.3.2) rake (>= 0.8.7) rdoc (~> 3.4) @@ -306,7 +306,7 @@ DEPENDENCIES nokogiri pg rack - rails (= 3.2.15) + rails (= 3.2.16) rails-i18n rake (= 0.9.2.2) rdoc -- cgit v1.2.3 From 5b709d61c1c897f10d9900018fecca4c10731673 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 4 Dec 2013 08:27:38 +0000 Subject: Set enforce_available_locales to false. This was the previous behaviour. Setting it explicitly avoids a deprecation warning. --- config/application.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/application.rb b/config/application.rb index f2b662abc..dba3a0c57 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,6 +31,7 @@ module Alaveteli # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + I18n.config.enforce_available_locales = false # JavaScript files you want as :defaults (application.js is always included). # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) -- cgit v1.2.3 From 4e486821a479fbb96ca83c025805a19baa9a6df9 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 4 Dec 2013 13:59:07 +0000 Subject: Add some instructions on removing the symlinking code. The code in install.rb and uninstall.rb for a theme that symlinks its public directory from the main application directory is no longer needed. --- doc/THEME-ASSETS-UPGRADE.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/THEME-ASSETS-UPGRADE.md b/doc/THEME-ASSETS-UPGRADE.md index 66a1e95f4..2c6e49986 100644 --- a/doc/THEME-ASSETS-UPGRADE.md +++ b/doc/THEME-ASSETS-UPGRADE.md @@ -67,3 +67,8 @@ should be mentioned in `lib/alavetelitheme.rb` with: You should be left with nothing in the `public` directory after making these changes, except possibly custom error pages. + +Remove the code that symlinks the theme 'public' directory to a +subdirectory of the main application's 'public' directory from +install.rb. Also remove the code from uninstall.rb that removes that +symlink. The asset pipeline will handle making assets available. -- cgit v1.2.3 From 7ddfc17de981455fe63b590dd5a434526d0929d8 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 4 Dec 2013 15:54:47 +0000 Subject: Move disclosure log to common part of form. It isn't in the list of attributes that is translated, so was causing a mass assignment error when the code in public_body.translated_versions= was attempting to assign it. There's a possible underlying inconsistency here between the treatment of publication_scheme and disclosure_log - one is translated and one isn't. I've ticketed that separately (#1264) --- app/views/admin_public_body/_form.html.erb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/admin_public_body/_form.html.erb b/app/views/admin_public_body/_form.html.erb index c577d1e18..ebf2c1151 100644 --- a/app/views/admin_public_body/_form.html.erb +++ b/app/views/admin_public_body/_form.html.erb @@ -50,12 +50,6 @@ <%= t.text_field :publication_scheme, :size => 60, :id => form_tag_id(t.object_name, :publication_scheme, locale), :class => "span3" %>
      -
      - -
      - <%= t.text_field :disclosure_log, :size => 60, :id => form_tag_id(t.object_name, :disclosure_log, locale), :class => "span3" %> -
      -
      @@ -88,6 +82,12 @@

      (of whole authority, not just their FOI page; set to blank (empty string) to guess it from the email)

      +
      + +
      + <%= f.text_field :disclosure_log, :size => 60, :class => "span4" %> +
      +
      -- cgit v1.2.3 From 72130f1c6c393ec94dad5bda9de72e85afa2918a Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 4 Dec 2013 16:09:42 +0000 Subject: Fix label. --- app/views/admin_public_body/_form.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin_public_body/_form.html.erb b/app/views/admin_public_body/_form.html.erb index c577d1e18..18bf1d15b 100644 --- a/app/views/admin_public_body/_form.html.erb +++ b/app/views/admin_public_body/_form.html.erb @@ -82,7 +82,7 @@
      - +
      <%= f.text_field :home_page, :class => "span4" %>

      (of whole authority, not just their FOI page; set to blank (empty string) to guess it from the email)

      -- cgit v1.2.3 From d35b7fec9ad723496c95791ae314964f4d3360dd Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 5 Dec 2013 09:50:44 +0000 Subject: Update INSTALL.md to say that Alaveteli won't run in a EC2 Micro instance The constrained memory on Micro instances has caused two separate bug reports on alaveteli-dev, so update the documentation to discourage people from trying that. --- doc/INSTALL.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/INSTALL.md b/doc/INSTALL.md index f39789936..3decb53a3 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -15,11 +15,10 @@ This creates an instance that runs in development mode, so we wouldn't recommend you use it for a production system without changing the configuration. -If you haven't used Amazon Web Services before, then you can get -a Micro instance which will be -[free for a year](http://aws.amazon.com/free/). You will find -that a micro instance isn't powerful enough for anything other -very basic testing of Alaveteli, however. +Unfortunately, Alaveteli will not run properly on a free Micro +instance due to the low amount of memory available on those +instances; you will need to use at least a Small instance, which +Amazon will charge for. The AMI can be found in the EU West (Ireland) region, with the ID ami-0f24c678 and name “Basic Alaveteli installation -- cgit v1.2.3 From 3a84c03bb1a4b1b6465548e4c878b67cfea35602 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 2 Dec 2013 16:11:31 +0000 Subject: Treat any document/pdf attachment as if it were application/pdf This is a fix for issue #1232. Richard Taylor pointed out that some PDF attachments had the non-standard content-type document/pdf, and that these weren't being treated as PDFs. (Ganesh Sittampalam discovered that all of these PDFs were generated by a Lexmark X945e, according to the PDF metadata.) This commit adds an extra case to normalise_content_type to map document/pdf to application/pdf. In fact, since the upgrade of the Mail gem in ccebe3c3d6d4dc5f81 the behaviour when handling the non-standard content-type document/pdf was much better, but this commit also means that you get the right icon for the attachment, and can be cherry-picked onto older versions to fix #1232. --- lib/mail_handler/mail_handler.rb | 2 +- spec/fixtures/files/document-pdf.email | 110 +++++++++++++++++++++++++++++ spec/lib/mail_handler/mail_handler_spec.rb | 6 ++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/files/document-pdf.email diff --git a/lib/mail_handler/mail_handler.rb b/lib/mail_handler/mail_handler.rb index 918f91180..53033d440 100644 --- a/lib/mail_handler/mail_handler.rb +++ b/lib/mail_handler/mail_handler.rb @@ -59,7 +59,7 @@ module MailHandler end # e.g. http://www.whatdotheyknow.com/request/copy_of_current_swessex_scr_opt#incoming-9928 - if content_type == 'application/acrobat' + if content_type == 'application/acrobat' or content_type == 'document/pdf' content_type = 'application/pdf' end diff --git a/spec/fixtures/files/document-pdf.email b/spec/fixtures/files/document-pdf.email new file mode 100644 index 000000000..f4fc6f0fe --- /dev/null +++ b/spec/fixtures/files/document-pdf.email @@ -0,0 +1,110 @@ +From authority@example.org Tue Dec 3 11:13:02 2013 +Return-path: +Envelope-to: requester@example.org +Delivery-date: Tue, 03 Dec 2013 11:13:00 +0000 +From: Test Authority +To: requester@example.org +Subject: testing a PDF attachment with the wrong content-type +Date: Tue, 03 Dec 2013 11:12:45 +0000 +Message-ID: <87li09xuasdfasdfpoija@blahblah> +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="=-=-=" + +--=-=-= +Content-Type: text/plain + +Here's a PDF attachement which has a document/pdf content-type, +when it really should be application/pdf. + + +--=-=-= +Content-Type: application/pdf +Content-Disposition: attachment; filename=tiny-example.pdf +Content-Transfer-Encoding: base64 +Content-Description: a very small example PDF + +JVBERi0xLjUKJbXtrvsKMyAwIG9iago8PCAvTGVuZ3RoIDQgMCBSCiAgIC9GaWx0ZXIgL0ZsYXRl +RGVjb2RlCj4+CnN0cmVhbQp4nCvkMlAAwaJ0Bf1EA4X0Yi6nEC5DA1M9IwNLYzNjsBwS18hIz9TI +0tBSwdzIQs/Y3MLAzEQhJJdLP03XQBeoUCEkjStawyM1JydfUTM2xIvLNYQrkAsAJG8VmQplbmRz +dHJlYW0KZW5kb2JqCjQgMCBvYmoKICAgOTIKZW5kb2JqCjIgMCBvYmoKPDwKICAgL0V4dEdTdGF0 +ZSA8PAogICAgICAvYTAgPDwgL0NBIDEgL2NhIDEgPj4KICAgPj4KICAgL0ZvbnQgPDwKICAgICAg +L2YtMC0wIDUgMCBSCiAgID4+Cj4+CmVuZG9iago2IDAgb2JqCjw8IC9UeXBlIC9QYWdlCiAgIC9Q +YXJlbnQgMSAwIFIKICAgL01lZGlhQm94IFsgMCAwIDU5NS4yNzU1NzQgODQxLjg4OTc3MSBdCiAg +IC9Db250ZW50cyAzIDAgUgogICAvR3JvdXAgPDwKICAgICAgL1R5cGUgL0dyb3VwCiAgICAgIC9T +IC9UcmFuc3BhcmVuY3kKICAgICAgL0kgdHJ1ZQogICAgICAvQ1MgL0RldmljZVJHQgogICA+Pgog +ICAvUmVzb3VyY2VzIDIgMCBSCj4+CmVuZG9iago3IDAgb2JqCjw8IC9MZW5ndGggOCAwIFIKICAg +L0ZpbHRlciAvRmxhdGVEZWNvZGUKICAgL0xlbmd0aDEgMzcxNgo+PgpzdHJlYW0KeJzlVnt0lMUV +v/P9vtndZB/5drMbiUlww7ookJCQECCAzYJBBSrlETXYxgayxIjQJAQVTOOCFAREgwKLAtIUkSpE +mlIkG2OtVN4xrZXH8Y0gFGkjRouoa5j0btCe0/boOW3/6OnpzM58cx9z5/7ud+fuR4KI4mkBgbxl +s6dVvXnmiTlECBJpt5bdPddLd6TlE8mXiIQqr7p9dvXgu2cSmZmmbbfPml9etrfAzOtG1o9UzJgW +7O56fQSRZRbzhlQwwxYx3cL0ZqavrJg9d97lhs76lgNM95pVWTaNKM7N9HtMp86eNq9KrzFVM93F +tLdqzoyqEeaPeRmXyj5UkEblKqyXy83srZkuD9j0L8n0pbDIkKZT1p4jHYPIONJxpCM70Znu9Kc7 +08t16qpBStdpFTY7Pv9kjqkfCTrItkbIo2SljIDbskZ7VqeF8SYzUuWwOJFKus3o6sjh3yDKGnmK +F9k7JtqFKEnMZaO5Th/PvoOHtHcPHbrY55A8enGDFowO0PZf5DBpVN39vh7Sa8lNKVQVuJI8Im6J +Zan0PCNki0209mpxRWwrUlM8msVjofGaK2FMKp92vmOP05XPzp8633HKOMf9/DlmZAf6FaRVpTWk +vZrWmSYLqEAUaAWeghSZYc6yZMVlxFdSpajUKj2VKXEl1aJEeNJ7i9ycIUM9DuHzktOg3BwyDxS+ +PiazHuraYWtvnrl/etmrd6rzar/o13VSmCPaU0vXtTi02259cf/gwdv7Z4hhIl4kimvVO3vW7ty+ +kV877eZpvsnNMe/TTGs1YaHrdHaaw9ORHbAbMiAnylJZJTulqSdKvt2RiMn9RcfX8biL42Gly2hc +INHU4qIWW8S1olecK2ESXJ4xvXrgX4JunMsO+AqSa6nWFDKHLKG4UHzIWmsL2UOOUELICDlrXQ3J +nclORtrH5HEnMdS8wX2vyokh9fWJzVrN6sZta1Y1Nq7qFC51rvNj9ZFw4viZgwfPfHBg/9kN6oDq +UB8y+HzG6BbDOLU0kRdzlPMBFEd5gQQzLdIXahazFND5ahhd45usRcUtRN0vDZs6siMnn19U1qmu +V7LFLrJ6rROtKPHnenycGvBB5LW1tbk3eZTizKhW68UM6mmCrcdumo10bQI/e5PBHAeFqFtMEdPE +PHGfeFTbp73t7evN9g73Nqb36e6O3QFqEJNFKcvrvpInsjz/b/JvboLPeFusExvERu4NX/V93A+I +Az0a8lv3/6tN/Ns7tb+j0DPr/5Ev/0eNszdCbdx301baILYwVc7sauY0aDtoMd3FnJdFm1imZTJv +C3XSYdZ8gNqwVScxjnKZS/SG1Oi8KKKdbCOfb0a+2aSTPkHfqU/WI/oZvZ2G6jV6u16q14hcbJI3 +yy088rFXc3E9vYIi4jjV0PM4i1y8oBfqDjqOdmyl03xK7F22UT1tplr2xS0qKaTVapOZs1+20zru +lSxv5yw9zN49LxbRUXoMunYDbRRHGVcbXaBFKNJCnBy5Wjn7v59ttfP+dVSj880V8aS0Acxj7/ms +6T1zGjLl0Z7eybeslopos4nLktnHp8QitkW8LDpMq6iBDuMHqMZbYrHu05/Wb6D6SxFAKdWz7XWx +PaZyMZ+xx3ptzLp2j14qttJZvdQ8nW3vjSHiM3dqkxlROb3A4x6TwZhGiMVYxp7GpGnUbh6nZ/F+ +tmCuY9RElcijmbyqpe20gzIRpnq21IPXNFRe4J0b9BOMuV48pF2gdhRSPyrXz3Gs+S+GwkTNZpPU +oQnK8BpNmn9ssCkwqdh7YGp6ZsY/kF7D7G2iiU32+d5Id/fEYj1FTm2SqU3wW5p0v+/ENwlPZGaM +n1jsbbo4pvArq2NKC5k3pZiXMYrZzB9T2COLHdok/fwbW9rkLavwLjeW+4YvN2YMz7xUI7Sie2dN +WLj6hwkjP6UrLD053L47tfTr52fHurbb58TdxGRMeKmq8GyerdKI7Cc/OxadZJ/zT9XGxBlazil7 +UO+gai2fduunqRp5sdre0wp5NLChuTyaYk7Evld6rJhQRAOogiuzxjX58dipukdL4qce0RYEur9U +iLrxhR+f5+CzMC448KnCeYW/+PGJAx+H0enHR8tHyY8UzoXxYRgdUfw5ij8pnB2OD0bjjMIfc3D6 +1BR5OoxTrHhqCt4/mSXfj+JkFk4ovKdwPAfvuvFOGG8rvOXCm3V4oxWvKxxj9WN1OHrkenm0Dkeu +x+HXUuRhhddS8AeFVxV+r/A7hfYwXmnrLV9RaOuNQzk4qLBvsVPuS8XeJOxReFnhtwq7FV5S+I3C +iwq/VnhBoVXheSdalvhli0KkuVVGFJp3lcjmVjQv0Hc955e7SgLd2BXQn/Njp8Kvwtih8EuFJoVf +KGwP4lkHGrf5ZWMQ27a65DY/trrwDDv9TBRPK/xcYYvCUy5sVnhyk0M+mYNNDvwsiAZWaQjjpwob +n7DJjQpP2LBhfbLcEMT6dYZcn4x1Bh6Px2MKa8N2uVYhbMca3rQmjNWrHHL11VjlwKNRPLKyVT6i +sLK+RK5sxcoFev3DfllfgvqA/rAfDymseHCgXKHw4EAsZ5jLR2HZUqtc5sZSKx5gxgNBLOFILfFj +sRM/UVh0v1MuUrjfiYUKCxRCCoHu++rq5H0KdXX4cRC1RR5Z68e9CvMV5jlwjw13x+MuhblR1EQx +J4rqKKoUKhV+pDArHXcqzHSOljOn4A6FijrczkS5wgyFoEKZwnSFacNRGsVtNpQofF/hVoWpxfFy +ahTF8bglKVnekoObFW7ik28ajSIPpghDTumFyW5MGpcoJynwN8j3FCbcaMgJCjca+K7CeJaMVxg3 +1pDjEjE2zS7HGrjBjusVrgtjTBiFCtdqmfLaKEa3YtR4BBQKFL5zjUt+x41rRibIa1wYOcIuRwa6 +EzDCjuEK+QrDhrrlsCiGDjHkUDeG5FnlEAN5VgzujVw7cgZZZY7CICuys6wy244sKwZmxsmBBjLj +kJGDAf39ckAQ/fu5ZH8/+rlw9VV+efUoXOVHX79V9k2A34orFXwKfRKQzjjTXfAGcUUUvRlC7yDS +7EjlCKYqpERx+WgkM5Gs0CuIyzhSlykk8aakZHgU3AqJCi5WcCk4GatzNIw6JAThULDbkqRdwcba +tiRYFeINxClYWM2iYHbDFITOQp0zwAPmQnGVNaSWCWGAFEREBBc/JAb8LzT6bzvwrS3tr1SHeSwK +ZW5kc3RyZWFtCmVuZG9iago4IDAgb2JqCiAgIDI0MDIKZW5kb2JqCjkgMCBvYmoKPDwgL0xlbmd0 +aCAxMCAwIFIKICAgL0ZpbHRlciAvRmxhdGVEZWNvZGUKPj4Kc3RyZWFtCnicXVAxbsQgEOx5xZZ3 +xQnbykUpkKXo0ri4JIqTB2BYHKQYEMaFf58FThcpBcwsuzOMll+Gl8HZBPw9ejViAmOdjrj6LSqE +CWfrWNuBtirdqnKrRQbGSTzua8JlcMYzIYB/UHNNcYfDs/YTHhkA8LeoMVo3w+HrMtancQvhBxd0 +CRrW96DRkN1Vhle5IPAiPg2a+jbtJ5L9TXzuAaErdVsjKa9xDVJhlG5GJpqmB2FMz9Dpf71zVUxG +fcvIxMMTTTYNAROP58IJiKvKVeamcvITXVs4Qfa+ueRf8jru8dUWIyUvOyuRc1jr8L7W4ENWlfML +jQ547AplbmRzdHJlYW0KZW5kb2JqCjEwIDAgb2JqCiAgIDI0NgplbmRvYmoKMTEgMCBvYmoKPDwg +L1R5cGUgL0ZvbnREZXNjcmlwdG9yCiAgIC9Gb250TmFtZSAvUlFaWlJTK0RlamFWdVNhbnMKICAg +L0ZvbnRGYW1pbHkgKERlamFWdSBTYW5zKQogICAvRmxhZ3MgMzIKICAgL0ZvbnRCQm94IFsgLTEw +MjAgLTQxNSAxNjgwIDExNjYgXQogICAvSXRhbGljQW5nbGUgMAogICAvQXNjZW50IDkyOAogICAv +RGVzY2VudCAtMjM1CiAgIC9DYXBIZWlnaHQgMTE2NgogICAvU3RlbVYgODAKICAgL1N0ZW1IIDgw +CiAgIC9Gb250RmlsZTIgNyAwIFIKPj4KZW5kb2JqCjUgMCBvYmoKPDwgL1R5cGUgL0ZvbnQKICAg +L1N1YnR5cGUgL1RydWVUeXBlCiAgIC9CYXNlRm9udCAvUlFaWlJTK0RlamFWdVNhbnMKICAgL0Zp +cnN0Q2hhciAzMgogICAvTGFzdENoYXIgMTExCiAgIC9Gb250RGVzY3JpcHRvciAxMSAwIFIKICAg +L0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcKICAgL1dpZHRocyBbIDAgNDAwIDAgMCAwIDAgMCAw +IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAg +MCAwIDAgMCA3NTEgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAw +IDAgMCAwIDAgMCA2MTUgMCAwIDAgMCAwIDAgMjc3IDAgMCA2MTEgXQogICAgL1RvVW5pY29kZSA5 +IDAgUgo+PgplbmRvYmoKMSAwIG9iago8PCAvVHlwZSAvUGFnZXMKICAgL0tpZHMgWyA2IDAgUiBd +CiAgIC9Db3VudCAxCj4+CmVuZG9iagoxMiAwIG9iago8PCAvQ3JlYXRvciAoY2Fpcm8gMS4xMi4x +NiAoaHR0cDovL2NhaXJvZ3JhcGhpY3Mub3JnKSkKICAgL1Byb2R1Y2VyIChjYWlybyAxLjEyLjE2 +IChodHRwOi8vY2Fpcm9ncmFwaGljcy5vcmcpKQo+PgplbmRvYmoKMTMgMCBvYmoKPDwgL1R5cGUg +L0NhdGFsb2cKICAgL1BhZ2VzIDEgMCBSCj4+CmVuZG9iagp4cmVmCjAgMTQKMDAwMDAwMDAwMCA2 +NTUzNSBmIAowMDAwMDA0MDYyIDAwMDAwIG4gCjAwMDAwMDAyMDUgMDAwMDAgbiAKMDAwMDAwMDAx +NSAwMDAwMCBuIAowMDAwMDAwMTg0IDAwMDAwIG4gCjAwMDAwMDM2NzkgMDAwMDAgbiAKMDAwMDAw +MDMxNCAwMDAwMCBuIAowMDAwMDAwNTQyIDAwMDAwIG4gCjAwMDAwMDMwMzggMDAwMDAgbiAKMDAw +MDAwMzA2MSAwMDAwMCBuIAowMDAwMDAzMzg1IDAwMDAwIG4gCjAwMDAwMDM0MDggMDAwMDAgbiAK +MDAwMDAwNDEyNyAwMDAwMCBuIAowMDAwMDA0MjU3IDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUg +MTQKICAgL1Jvb3QgMTMgMCBSCiAgIC9JbmZvIDEyIDAgUgo+PgpzdGFydHhyZWYKNDMxMAolJUVP +Rgo= +--=-=-=-- + diff --git a/spec/lib/mail_handler/mail_handler_spec.rb b/spec/lib/mail_handler/mail_handler_spec.rb index aa351bd94..f62b1d6fc 100644 --- a/spec/lib/mail_handler/mail_handler_spec.rb +++ b/spec/lib/mail_handler/mail_handler_spec.rb @@ -409,6 +409,12 @@ describe 'when getting attachment attributes' do attributes[1][:body].length.should == 7769 end + it 'should treat a document/pdf attachment as application/pdf' do + mail = get_fixture_mail('document-pdf.email') + attributes = MailHandler.get_attachment_attributes(mail) + attributes[1][:content_type].should == "application/pdf" + end + it 'should produce a consistent set of url_part_numbers, content_types, within_rfc822_subjects and filenames from an example mail with lots of attachments' do mail = get_fixture_mail('many-attachments-date-header.email') -- cgit v1.2.3 From 76e3942e3f68d258ee96c1fc48f0aeeb0ac68759 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 3 Dec 2013 18:08:03 +0000 Subject: Add an 'Allow' directive to allow crawling of cached attachments. Should resolve #1233 --- public/robots.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/robots.txt b/public/robots.txt index 279573d31..969e10826 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -8,7 +8,10 @@ # Crawl-delay: 1 # http://www.bing.com/community/blogs/webmaster/archive/2009/08/10/crawl-delay-and-the-bing-crawler-msnbot.aspx -# This file uses the non-standard extension characters * and $, which are supported by Google and Yahoo! +# This file uses the non-standard extension characters * and $, which +# are supported by Google and Yahoo! and the 'Allow' directive, which +# is supported by Google. + # http://code.google.com/web/controlcrawlindex/docs/robots_txt.html # http://help.yahoo.com/l/us/yahoo/search/webcrawler/slurp-02.html @@ -23,6 +26,7 @@ Disallow: */user/contact/ Disallow: */feed/ Disallow: */profile/ Disallow: */signin +Allow: */request/*/response/*/attach/* Disallow: */request/*/response/ Disallow: */body/*/view_email$ -- cgit v1.2.3 From 5c19b4ba8d875e2aca7975e0cc604b3c441bda47 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 5 Dec 2013 16:41:20 +0000 Subject: Fix the link to the fancybox CSS --- app/views/general/_stylesheet_includes.html.erb | 2 +- config/application.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/general/_stylesheet_includes.html.erb b/app/views/general/_stylesheet_includes.html.erb index b3f32054c..7a1648efd 100644 --- a/app/views/general/_stylesheet_includes.html.erb +++ b/app/views/general/_stylesheet_includes.html.erb @@ -17,6 +17,6 @@ <%= stylesheet_link_tag 'ie7.css' %> <% if AlaveteliConfiguration::force_registration_on_new_request %> - <%= stylesheet_link_tag 'jquery.fancybox-1.3.4.pack.js', :rel => "stylesheet" %> + <%= stylesheet_link_tag 'jquery.fancybox-1.3.4.css', :rel => "stylesheet" %> <% end %> <% end %> diff --git a/config/application.rb b/config/application.rb index dba3a0c57..3c749a531 100644 --- a/config/application.rb +++ b/config/application.rb @@ -94,6 +94,7 @@ module Alaveteli # ... while these are individual files that can't easily be # grouped: config.assets.precompile += ['jquery.fancybox-1.3.4.pack.js', + 'jquery.fancybox-1.3.4.css', 'jquery.Jcrop.css', 'excanvas.min.js', 'fonts.css', -- cgit v1.2.3 From 58819831e0c2e4047011120f50d73456a09c95f7 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 5 Dec 2013 16:42:20 +0000 Subject: Remove link to the now-removed theme.css --- app/views/layouts/no_chrome.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/layouts/no_chrome.html.erb b/app/views/layouts/no_chrome.html.erb index 589e1bb76..e613b8ca2 100644 --- a/app/views/layouts/no_chrome.html.erb +++ b/app/views/layouts/no_chrome.html.erb @@ -14,7 +14,6 @@ <%= stylesheet_link_tag 'application', :title => "Main", :rel => "stylesheet" %> <%= stylesheet_link_tag 'fonts', :rel => "stylesheet" %> - <%= stylesheet_link_tag 'theme', :rel => "stylesheet" %> -- cgit v1.2.3 From 7c92fb67028c0371b796cd26edabe9a905d47561 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 5 Dec 2013 15:15:54 +0000 Subject: Move "Act on what you've" learnt to partial In order to make it easier to override. --- app/views/request/_act.html.erb | 15 +++++++++++++++ app/views/request/_sidebar.html.erb | 17 +---------------- 2 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 app/views/request/_act.html.erb diff --git a/app/views/request/_act.html.erb b/app/views/request/_act.html.erb new file mode 100644 index 000000000..1199cb4a2 --- /dev/null +++ b/app/views/request/_act.html.erb @@ -0,0 +1,15 @@ +

      <%= _("Act on what you've learnt") %>

      + + + diff --git a/app/views/request/_sidebar.html.erb b/app/views/request/_sidebar.html.erb index 8400cd6ac..0f7965ffa 100644 --- a/app/views/request/_sidebar.html.erb +++ b/app/views/request/_sidebar.html.erb @@ -33,22 +33,7 @@ <%= link_to _("Report this request"), new_request_report_path(:request_id => @info_request.url_title) %> <% end %> <% end %> -

      <%= _("Act on what you've learnt") %>

      - - - - + <%= render :partial => 'request/act' %> <%= render :partial => 'request/next_actions' %> <% cache_if_caching_fragments(@similar_cache_key, :expires_in => 1.day) do %> -- cgit v1.2.3 From 3ad5277ed2c9f98508a7208848e125d274c86a3b Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 5 Dec 2013 16:57:02 +0000 Subject: Search for bodies using the underscore version of the locale. --- app/models/public_body.rb | 3 ++- spec/models/public_body_spec.rb | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 8e474c797..c007afb6e 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -727,7 +727,8 @@ class PublicBody < ActiveRecord::Base # either from config, or based on a (slow!) query if not set body_short_names = AlaveteliConfiguration::frontpage_publicbody_examples.split(/\s*;\s*/) locale_condition = 'public_body_translations.locale = ?' - conditions = [locale_condition, locale] + underscore_locale = locale.gsub '-', '_' + conditions = [locale_condition, underscore_locale] bodies = [] I18n.with_locale(locale) do if body_short_names.empty? diff --git a/spec/models/public_body_spec.rb b/spec/models/public_body_spec.rb index 23842ccff..0c29503a3 100644 --- a/spec/models/public_body_spec.rb +++ b/spec/models/public_body_spec.rb @@ -604,3 +604,12 @@ describe PublicBody, "when calculating statistics" do end end + +describe PublicBody, 'when asked for popular bodies' do + + it 'should return bodies correctly when passed the hyphenated version of the locale' do + AlaveteliConfiguration.stub!(:frontpage_publicbody_examples).and_return('') + PublicBody.popular_bodies('he-IL').should == [public_bodies(:humpadink_public_body)] + end + +end -- cgit v1.2.3 From 0cbf14f4780a9c38eb73f24f0052e50c60c40769 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 5 Dec 2013 18:50:10 +0000 Subject: Make sure globalize uses the right locale version when updating. --- app/models/public_body.rb | 4 ++-- spec/models/public_body_spec.rb | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/models/public_body.rb b/app/models/public_body.rb index c007afb6e..c0ad19b76 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -260,13 +260,13 @@ class PublicBody < ActiveRecord::Base # When name or short name is changed, also change the url name def short_name=(short_name) - globalize.write(I18n.locale, :short_name, short_name) + globalize.write(Globalize.locale, :short_name, short_name) self[:short_name] = short_name self.update_url_name end def name=(name) - globalize.write(I18n.locale, :name, name) + globalize.write(Globalize.locale, :name, name) self[:name] = name self.update_url_name end diff --git a/spec/models/public_body_spec.rb b/spec/models/public_body_spec.rb index 0c29503a3..73a07db41 100644 --- a/spec/models/public_body_spec.rb +++ b/spec/models/public_body_spec.rb @@ -213,6 +213,15 @@ describe PublicBody, " when saving" do public_body.name.should == "Mark's Public Body" end + it 'should update the right translation when in a locale with an underscore' do + AlaveteliLocalization.set_locales('he_IL', 'he_IL') + public_body = public_bodies(:humpadink_public_body) + translation_count = public_body.translations.size + public_body.name = 'Renamed' + public_body.save! + public_body.translations.size.should == translation_count + end + it 'should not create a new version when nothing has changed' do @public_body.versions.size.should == 0 set_default_attributes(@public_body) -- cgit v1.2.3 From 26c30604fafde5a3e8cc00fbb210f42c4012e144 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 6 Dec 2013 08:22:36 +0000 Subject: Use a better subject line than "Fake Response" for uploaded responses The subject line wasn't being explicitly specified in RequestMailer.fake_response and a default of "Fake Response" (somehow based on the method name) was being used instead; this subject line would sometimes be visible to users and authorities and caused alarm to them. Instead, we use the standard subject lines for followup emails for the InfoRequest. Fixes #1105. --- app/mailers/request_mailer.rb | 3 ++- spec/mailers/request_mailer_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/mailers/request_mailer.rb b/app/mailers/request_mailer.rb index c8a19afa8..af1a75df9 100644 --- a/app/mailers/request_mailer.rb +++ b/app/mailers/request_mailer.rb @@ -19,7 +19,8 @@ class RequestMailer < ApplicationMailer end mail(:from => from_user.name_and_email, - :to => info_request.incoming_name_and_email) + :to => info_request.incoming_name_and_email, + :subject => info_request.email_subject_followup) end # Used when a response is uploaded using the API diff --git a/spec/mailers/request_mailer_spec.rb b/spec/mailers/request_mailer_spec.rb index 4e0765921..516d13127 100644 --- a/spec/mailers/request_mailer_spec.rb +++ b/spec/mailers/request_mailer_spec.rb @@ -332,6 +332,27 @@ describe RequestMailer, 'when sending mail when someone has updated an old uncla end +describe RequestMailer, 'when generating a fake response for an upload' do + + before do + @foi_officer = mock_model(User, :name_and_email => "FOI officer's name and email") + @request_user = mock_model(User) + @public_body = mock_model(PublicBody, :name => 'Test public body') + @info_request = mock_model(InfoRequest, :user => @request_user, + :email_subject_followup => 'Re: Freedom of Information - Test request', + :incoming_name_and_email => 'Someone ') + end + + it 'should should generate a "fake response" email with a reasonable subject line' do + fake_email = RequestMailer.fake_response(@info_request, + @foi_officer, + "The body of the email...", + "blah.txt", + "The content of blah.txt") + fake_email.subject.should == "Re: Freedom of Information - Test request" + end + +end describe RequestMailer, 'when sending a new response email' do -- cgit v1.2.3 From 6ada52492dda1cd3d27995d1b9d7a917beef5b1a Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 6 Dec 2013 12:59:20 +0000 Subject: Make 'rake themes:install' safer for developers Previously, the themes:install rake task would remove the existing theme with 'rm -rf' and re-clone the theme into place. This is unfortunate for a developer who has been making changes to a theme and then runs the rails-post-deploy script, since it calls the themes:install task which will wipe out those changes. In addition, when installing themes it would deliberately remove the .git directory of the theme, so if you do want to work in that theme you'd have to reinitialize the theme directory to be a git repository again. This commit changes the task so that now: - If a theme directory is present but it isn't a git repository, it's moved out of the way. - If there's no theme directory at the expected location after that step, the theme repository is cloned into place - The task ensures that the origin remote points to the theme's URL, and fetches from that remote. - If there are any uncommitted changes in the theme repository or the current commit appears not to have been pushed, the task exits with a helpful error. - The preferred branch or tag is checked out in the theme repository as before. (The uninstall, install and post_install hooks are run as before.) This shouldn't make a difference to deployed instances of Alaveteli but will be helpful for developers who want to work on developing a theme. Fixes #1111. --- commonlib | 2 +- lib/tasks/themes.rake | 138 +++++++++++++++++++++++++++++--------------------- 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/commonlib b/commonlib index 8070e4c27..ad27f5409 160000 --- a/commonlib +++ b/commonlib @@ -1 +1 @@ -Subproject commit 8070e4c27c903d886963d662db40bb91d56f8c53 +Subproject commit ad27f5409ef3ed1b800aa08c1d70a018443dcfd9 diff --git a/lib/tasks/themes.rake b/lib/tasks/themes.rake index 65b142a63..4a864d141 100644 --- a/lib/tasks/themes.rake +++ b/lib/tasks/themes.rake @@ -1,104 +1,123 @@ +require Rails.root.join('commonlib', 'rblib', 'git') + namespace :themes do - def plugin_dir + # Alias the module so we don't need the MySociety prefix here + Git = MySociety::Git + + def all_themes_dir File.join(Rails.root,"lib","themes") end def theme_dir(theme_name) - File.join(plugin_dir, theme_name) + File.join(all_themes_dir, theme_name) end - def old_theme_dir(theme_name) + def old_all_themes_dir(theme_name) File.join(Rails.root, "vendor", "plugins", theme_name) end def possible_theme_dirs(theme_name) - [theme_dir(theme_name), old_theme_dir(theme_name)] - end - - def checkout(commitish) - puts "Checking out #{commitish}" if verbose - system "git checkout #{commitish}" + [theme_dir(theme_name), old_all_themes_dir(theme_name)] end - def checkout_tag(version) - checkout usage_tag(version) - end - - def checkout_remote_branch(branch) - checkout "origin/#{branch}" + def installed?(theme_name) + possible_theme_dirs(theme_name).any? { |dir| File.directory? dir } end def usage_tag(version) "use-with-alaveteli-#{version}" end - def install_theme_using_git(name, uri, verbose=false, options={}) - install_path = theme_dir(name) - Dir.chdir(plugin_dir) do - clone_command = "git clone #{uri} #{name}" - if system(clone_command) - Dir.chdir install_path do - # First try to checkout a specific branch of the theme - tag_checked_out = checkout_remote_branch(AlaveteliConfiguration::theme_branch) if AlaveteliConfiguration::theme_branch - if !tag_checked_out - # try to checkout a tag exactly matching ALAVETELI VERSION - tag_checked_out = checkout_tag(ALAVETELI_VERSION) - end - if ! tag_checked_out - # if we're on a hotfix release (four sequence elements or more), - # look for a usage tag matching the minor release (three sequence elements) - # and check that out if found - if hotfix_version = /^(\d+\.\d+\.\d+)(\.\d+)+/.match(ALAVETELI_VERSION) - base_version = hotfix_version[1] - tag_checked_out = checkout_tag(base_version) - end - end - if ! tag_checked_out - puts "No specific tag for this version: using HEAD" if verbose - end - puts "removing: .git .gitignore" if verbose - rm_rf %w(.git .gitignore) - end - else - rm_rf install_path - raise "#{clone_command} failed! Stopping." - end - end - end - def uninstall(theme_name, verbose=false) possible_theme_dirs(theme_name).each do |dir| if File.directory?(dir) run_hook(theme_name, 'uninstall', verbose) - puts "Removing '#{dir}'" if verbose - rm_r dir - else - puts "Plugin doesn't exist: #{dir}" end end end def run_hook(theme_name, hook_name, verbose=false) - hook_file = File.join(theme_dir(theme_name), "#{hook_name}.rb") + directory = theme_dir(theme_name) + hook_file = File.join(directory, "#{hook_name}.rb") if File.exist? hook_file - puts "Running #{hook_name} hook for #{theme_name}" if verbose + puts "Running #{hook_name} hook in #{directory}" if verbose load hook_file end end - def installed?(theme_name) - possible_theme_dirs(theme_name).any? { |dir| File.directory? dir } + def move_old_theme(old_theme_directory) + puts "There was an old-style theme at #{old_theme_directory}" if verbose + moved_directory = "#{old_theme_directory}-moved" + begin + File.rename old_theme_directory, moved_directory + rescue Errno::ENOTEMPTY, Errno::EEXIST + raise "Tried to move #{old_theme_directory} out of the way, " \ + "but #{moved_directory} already existed" + end + end + + def committishes_to_try + result = [] + theme_branch = AlaveteliConfiguration::theme_branch + result.push "origin/#{theme_branch}" if theme_branch + result.push usage_tag(ALAVETELI_VERSION) + hotfix_match = /^(\d+\.\d+\.\d+)(\.\d+)+/.match(ALAVETELI_VERSION) + result.push usage_tag(hotfix_match[1]) if hotfix_match + result + end + + def checkout_best_option(theme_name) + theme_directory = theme_dir theme_name + all_failed = true + committishes_to_try.each do |committish| + if Git.committish_exists? theme_directory, committish + puts "Checking out #{committish}" if verbose + Git.checkout theme_directory, committish + all_failed = false + break + else + puts "Failed to find #{committish}; skipping..." if verbose + end + end + puts "Falling to using HEAD instead" if all_failed and verbose end def install_theme(theme_url, verbose, deprecated=false) - FileUtils.mkdir_p plugin_dir + FileUtils.mkdir_p all_themes_dir deprecation_string = deprecated ? " using deprecated THEME_URL" : "" theme_name = theme_url_to_theme_name theme_url puts "Installing theme #{theme_name}#{deprecation_string} from #{theme_url}" + # Make sure any uninstall hooks have been run: uninstall(theme_name, verbose) if installed?(theme_name) - install_theme_using_git(theme_name, theme_url, verbose) + theme_directory = theme_dir theme_name + # Is there an old-style theme directory there? If so, move it + # out of the way so that there's no risk that work is lost: + if File.directory? theme_directory + unless Git.non_bare_repository? theme_directory + move_old_theme theme_directory + end + end + # If there isn't a directory there already, clone it into place: + unless File.directory? theme_directory + unless system "git", "clone", theme_url, theme_directory + raise "Cloning from #{theme_url} to #{theme_directory} failed" + end + end + # Set the URL for origin in case it has changed, and fetch from there: + Git.remote_set_url theme_directory, 'origin', theme_url + Git.fetch theme_directory, 'origin' + # Check that checking-out a new commit will be safe: + unless Git.status_clean theme_directory + raise "There were uncommitted changes in #{theme_directory}" + end + unless Git.is_HEAD_pushed? theme_directory + raise "The current work in #{theme_directory} is unpushed" + end + # Now try to checkout various commits in order of preference: + checkout_best_option theme_name + # Finally run the install hooks: run_hook(theme_name, 'install', verbose) run_hook(theme_name, 'post_install', verbose) end @@ -112,4 +131,5 @@ namespace :themes do install_theme(AlaveteliConfiguration::theme_url, verbose, deprecated=true) end end + end -- cgit v1.2.3 From 4b3bdf38a19a1aa8c4cf6d216cb68c37e2238bf2 Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Sat, 7 Dec 2013 13:48:22 -0300 Subject: Make admin link order consistent Previously the admin links and permalinks on annotations and incoming correspondence were reversed, this commit makes the admin link consistently appear on the left and the permalink thingo appear on the right. --- app/views/comment/_single_comment.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/comment/_single_comment.html.erb b/app/views/comment/_single_comment.html.erb index d2edc8dbe..a6d234b34 100644 --- a/app/views/comment/_single_comment.html.erb +++ b/app/views/comment/_single_comment.html.erb @@ -17,10 +17,10 @@

      <% if !comment.id.nil? %> - <%= link_to "Link to this", comment_path(comment), :class => "link_to_this" %> <% if !@user.nil? && @user.admin_page_links? %> - | <%= link_to "Admin", admin_request_edit_comment_path(comment) %> + <%= link_to "Admin", admin_request_edit_comment_path(comment) %> | <% end %> + <%= link_to "Link to this", comment_path(comment), :class => "link_to_this" %> <% end %>

      -- cgit v1.2.3 From 441d5c7e222ef16ef4e3513dcc1b9d510011e1f7 Mon Sep 17 00:00:00 2001 From: Guy Freeman Date: Sun, 8 Dec 2013 21:49:32 +0800 Subject: Correct small typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dc3125a1..74d06b1f4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ drop a line to hello@alaveteli.org to let us know that you're using Alaveteli. Some documentation can be found in the [`doc/` folder](https://github.com/mysociety/alaveteli/tree/master/doc). -There's background information and a more documentation on +There's background information and more documentation on [our wiki](https://github.com/mysociety/alaveteli/wiki/Home/), and lots of useful information (including a blog) on [the project website](http://alaveteli.org) -- cgit v1.2.3 From 5102fa12d0fcbef03a980327ee3d87aaa4a3b780 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 6 Dec 2013 13:27:54 +0000 Subject: Revert "Add byebug, for interactive debugging under Ruby 2" This reverts commit 3b86cb6129140fc123dc3aeffcccdb5652f19085. Conflicts: Gemfile The problem with this is the combination of these things: * bundler's behaviour on seeing a platform that it doesn't understand is to error, rather than to ignore it: https://github.com/bundler/bundler/issues/2428 * mySociety's servers currently only have version 1.1.4 of bundler * The :ruby_20 platform was only introduced in version 1.3.0 of bundler: https://github.com/bundler/bundler/blob/master/CHANGELOG.md#130pre3-dec-21-2012 So, for the moment we're just going to revert this change; upgrading bundler on our servers is a non-trivial job and being able to deploy on our servers is urgent and important. --- Gemfile | 1 - Gemfile.lock | 4 ---- 2 files changed, 5 deletions(-) diff --git a/Gemfile b/Gemfile index 7e33a5f12..824b4f1f7 100644 --- a/Gemfile +++ b/Gemfile @@ -77,7 +77,6 @@ end group :develop do gem 'ruby-debug', :platforms => :ruby_18 gem 'debugger', :platforms => :ruby_19 - gem 'byebug', :platforms => :ruby_20 gem 'annotate' end diff --git a/Gemfile.lock b/Gemfile.lock index c5c1fa3b4..62258c0c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,9 +51,6 @@ GEM bootstrap-sass (2.3.1.2) sass (~> 3.2) builder (3.0.4) - byebug (2.3.1) - columnize (~> 0.3.6) - debugger-linecache (~> 1.2.0) capistrano (2.15.4) highline net-scp (>= 1.0.0) @@ -281,7 +278,6 @@ DEPENDENCIES acts_as_versioned! annotate bootstrap-sass - byebug capistrano charlock_holmes coffee-rails (~> 3.2.1) -- cgit v1.2.3 From 8a597b7ff7093f8a828a6d5a8af1ccedd8eac05e Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 9 Dec 2013 10:59:26 +0000 Subject: More detail on how to make a pull request. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 74d06b1f4..1a2aeee05 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,10 @@ If you find what looks like a bug: If you want to contribute an enhancement or a fix: * Fork the project on GitHub. +* Make a topic branch from the rails-3-develop branch. * Make your changes with tests. * Commit the changes without making changes to any files that aren't related to your enhancement or fix. -* Send a pull request. +* Send a pull request against the rails-3-develop branch. Looking for the latest stable release? It's on the [master branch](https://github.com/mysociety/alaveteli/tree/master). -- cgit v1.2.3 From 06c9c6b9a1d548a6022f145f2f75cfd88d2122c3 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 9 Dec 2013 11:43:27 +0000 Subject: Use a favicon link helper to point to our pipeline favicon. --- app/views/layouts/_favicon.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_favicon.html.erb b/app/views/layouts/_favicon.html.erb index 4f3859d6c..0ee7d48e3 100644 --- a/app/views/layouts/_favicon.html.erb +++ b/app/views/layouts/_favicon.html.erb @@ -1 +1 @@ - +<%= favicon_link_tag 'favicon.ico' %> -- cgit v1.2.3 From 71746d1a486e8c5f6d5107376256ef8c1b8c9902 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 5 Dec 2013 15:23:51 +0000 Subject: Only show the twitter link if an account is configured. --- app/views/general/_footer.html.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/general/_footer.html.erb b/app/views/general/_footer.html.erb index 04c60be3d..2e10a0f94 100644 --- a/app/views/general/_footer.html.erb +++ b/app/views/general/_footer.html.erb @@ -1,6 +1,8 @@
      -- cgit v1.2.3 From 31187a4d13a7f914ee5fe4eae0af0a268bce90f9 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 5 Dec 2013 15:28:54 +0000 Subject: Only show the blog if a BLOG_FEED is configured. --- app/controllers/general_controller.rb | 4 ++++ app/views/general/_topnav.html.erb | 2 ++ spec/controllers/general_controller_spec.rb | 11 +++++++++++ 3 files changed, 17 insertions(+) diff --git a/app/controllers/general_controller.rb b/app/controllers/general_controller.rb index aac078829..b01a67027 100644 --- a/app/controllers/general_controller.rb +++ b/app/controllers/general_controller.rb @@ -17,6 +17,10 @@ class GeneralController < ApplicationController # Display blog entries def blog + if AlaveteliConfiguration::blog_feed.empty? + raise ActiveRecord::RecordNotFound.new("Page not enabled") + end + medium_cache @feed_autodetect = [] @feed_url = AlaveteliConfiguration::blog_feed diff --git a/app/views/general/_topnav.html.erb b/app/views/general/_topnav.html.erb index c7f2cedea..d37bd97d1 100644 --- a/app/views/general/_topnav.html.erb +++ b/app/views/general/_topnav.html.erb @@ -4,7 +4,9 @@
    • <%= link_to _("Make a request"), select_authority_path, :id => 'make-request-link' %>
    • <%= link_to _("View requests"), request_list_successful_path %>
    • <%= link_to _("View authorities"), list_public_bodies_default_path %>
    • +<% unless AlaveteliConfiguration::blog_feed.empty? %>
    • <%= link_to _("Read blog"), blog_path %>
    • +<% end %>
    • <%= link_to _("Help"), help_about_path %>
    diff --git a/spec/controllers/general_controller_spec.rb b/spec/controllers/general_controller_spec.rb index e67cc9492..7590a5b42 100644 --- a/spec/controllers/general_controller_spec.rb +++ b/spec/controllers/general_controller_spec.rb @@ -42,6 +42,17 @@ describe GeneralController, 'when getting the blog feed' do assigns[:blog_items].count.should == 1 end + context 'if no feed is configured' do + + before do + AlaveteliConfiguration.stub!(:blog_feed).and_return('') + end + + it 'should raise an ActiveRecord::RecordNotFound error' do + lambda{ get :blog }.should raise_error(ActiveRecord::RecordNotFound) + end + end + end describe GeneralController, "when showing the frontpage" do -- cgit v1.2.3 From fdf33ff65939d35dc888432914aee4d8606c1d6a Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 5 Dec 2013 14:55:52 +0000 Subject: Add Ugandan English translation file. N.B. as we haven't been able to get Transifex to add en_UG yet, this file was updated in poedit locally. --- locale/en_UG/app.po | 3510 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3510 insertions(+) create mode 100644 locale/en_UG/app.po diff --git a/locale/en_UG/app.po b/locale/en_UG/app.po new file mode 100644 index 000000000..c304dccf0 --- /dev/null +++ b/locale/en_UG/app.po @@ -0,0 +1,3510 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: alaveteli\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-11-08 12:10+0000\n" +"PO-Revision-Date: 2013-12-05 15:18-0000\n" +"Last-Translator: Louise Crow \n" +"Language-Team: LANGUAGE \n" +"Language: en_UG\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Poedit 1.6.1\n" + +msgid " This will appear on your {{site_name}} profile, to make it\\n easier for others to get involved with what you're doing." +msgstr "" + +msgid " (no ranty politics, read our moderation policy)" +msgstr "" + +msgid " (patience, especially for large files, it may take a while!)" +msgstr "" + +msgid " (you)" +msgstr "" + +msgid " - view and make Freedom of Information requests" +msgstr " - view and make Access to Information requests" + +msgid " - wall" +msgstr "" + +msgid " Note:\\n We will send you an email. Follow the instructions in it to change\\n your password." +msgstr "" + +msgid " Privacy note: Your email address will be given to" +msgstr "" + +msgid " Summarise the content of any information returned. " +msgstr "" + +msgid " Advise on how to best clarify the request." +msgstr "" + +msgid " Ideas on what other documents to request which the authority may hold. " +msgstr " Ideas on what other documents to request which the agency may hold. " + +msgid " If you know the address to use, then please send it to us.\\n You may be able to find the address on their website, or by phoning them up and asking." +msgstr "" + +msgid " Include relevant links, such as to a campaign page, your blog or a\\n twitter account. They will be made clickable. \\n e.g." +msgstr "" + +msgid " Link to the information requested, if it is already available on the Internet. " +msgstr "" + +msgid " Offer better ways of wording the request to get the information. " +msgstr "" + +msgid " Say how you've used the information, with links if possible." +msgstr "" + +msgid " Suggest where else the requester might find the information. " +msgstr "" + +msgid " What are you investigating using Freedom of Information? " +msgstr " What are you investigating using Access to Information? " + +msgid " You are already being emailed updates about the request." +msgstr "" + +msgid " You will also be emailed updates about the request." +msgstr "" + +msgid " made by " +msgstr "" + +msgid " or " +msgstr "" + +msgid " when you send this message." +msgstr "" + +msgid "\"Hello! We have an important message for visitors outside {{country_name}}\"" +msgstr "" + +msgid "'Crime statistics by ward level for Wales'" +msgstr "" + +msgid "'Pollution levels over time for the River Tyne'" +msgstr "" + +msgid "'{{link_to_authority}}', a public authority" +msgstr "'{{link_to_authority}}', a public agency" + +msgid "'{{link_to_request}}', a request" +msgstr "" + +msgid "'{{link_to_user}}', a person" +msgstr "" + +msgid "*unknown*" +msgstr "" + +msgid ",\\n\\n\\n\\nYours,\\n\\n{{user_name}}" +msgstr "" + +msgid "- or -" +msgstr "" + +msgid "1. Select an authority" +msgstr "1. Select an agency" + +msgid "2. Ask for Information" +msgstr "" + +msgid "3. Now check your request" +msgstr "3. Now check your request" + +msgid "Browse all or ask us to add one." +msgstr "" + +msgid "Add an annotation (to help the requester or others)" +msgstr "" + +msgid "Sign in to change password, subscriptions and more ({{user_name}} only)" +msgstr "" + +msgid "

    All done! Thank you very much for your help.

    There are more things you can do to help {{site_name}}.

    " +msgstr "" + +msgid "

    Thank you! Here are some ideas on what to do next:

    \\n
      \\n
    • To send your request to another authority, first copy the text of your request below, then find the other authority.
    • \\n
    • If you would like to contest the authority's claim that they do not hold the information, here is\\n how to complain.\\n
    • \\n
    • We have suggestions\\n on other means to answer your question.\\n
    • \\n
    " +msgstr "

    Thank you! Here are some ideas on what to do next:

    \\n
      \\n
    • To send your request to another agency, first copy the text of your request below, then find the other agency.
    • \\n
    • If you would like to contest the agency's claim that they do not hold the information, here is\\n how to complain.\\n
    • \\n
    • We have suggestions\\n on other means to answer your question.\\n
    • \\n
    " + +msgid "

    Thank you! Hope you don't have to wait much longer.

    By law, you should have got a response promptly, and normally before the end of {{date_response_required_by}}.

    " +msgstr "" + +msgid "

    Thank you! Hopefully your wait isn't too long.

    By law, you should get a response promptly, and normally before the end of \\n{{date_response_required_by}}.

    " +msgstr "" + +msgid "

    Thank you! Hopefully your wait isn't too long.

    You should get a response within {{late_number_of_days}} days, or be told if it will take longer (details).

    " +msgstr "" + +msgid "

    Thank you! Your request is long overdue, by more than {{very_late_number_of_days}} working days. Most requests should be answered within {{late_number_of_days}} working days. You might like to complain about this, see below.

    " +msgstr "" + +msgid "

    Thanks for changing the text about you on your profile.

    \\n

    Next... You can upload a profile photograph too.

    " +msgstr "" + +msgid "

    Thanks for updating your profile photo.

    \\n

    Next... You can put some text about you and your research on your profile.

    " +msgstr "" + +msgid "

    We recommend that you edit your request and remove the email address.\\n If you leave it, the email address will be sent to the authority, but will not be displayed on the site.

    " +msgstr "

    We recommend that you edit your request and remove the email address.\\n If you leave it, the email address will be sent to the agency, but will not be displayed on the site.

    " + +msgid "

    We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

    " +msgstr "" + +msgid "

    We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

    If you found {{site_name}} useful, make a donation to the charity which runs it.

    " +msgstr "" + +msgid "

    We're glad you got some of the information that you wanted. If you found {{site_name}} useful, make a donation to the charity which runs it.

    If you want to try and get the rest of the information, here's what to do now.

    " +msgstr "" + +msgid "

    We're glad you got some of the information that you wanted.

    If you want to try and get the rest of the information, here's what to do now.

    " +msgstr "" + +msgid "

    You do not need to include your email in the request in order to get a reply (details).

    " +msgstr "" + +msgid "

    You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (details).

    " +msgstr "" + +msgid "

    Your request contains a postcode. Unless it directly relates to the subject of your request, please remove any address as it will appear publicly on the Internet.

    " +msgstr "" + +msgid "

    Your {{law_used_full}} request has been sent on its way!

    \\n

    We will email you when there is a response, or after {{late_number_of_days}} working days if the authority still hasn't\\n replied by then.

    \\n

    If you write about this request (for example in a forum or a blog) please link to this page, and add an\\n annotation below telling people about your writing.

    " +msgstr "

    Your {{law_used_full}} request has been sent on its way!

    \\n

    We will email you when there is a response, or after {{late_number_of_days}} working days if the agency still hasn't\\n replied by then.

    \\n

    If you write about this request (for example in a forum or a blog) please link to this page, and add an\\n annotation below telling people about your writing.

    " + +msgid "

    {{site_name}} is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.

    {{read_only}}

    " +msgstr "" + +msgid "If you use web-based email or have \"junk mail\" filters, also check your\\nbulk/spam mail folders. Sometimes, our messages are marked that way.\\n

    " +msgstr "" + +msgid " Can I request information about myself?\\n\t\t\tNo! (Click here for details)" +msgstr "" + +msgid "commented_by:tony_bowden to search annotations made by Tony Bowden, typing the name as in the URL." +msgstr "" + +msgid "filetype:pdf to find all responses with PDF attachments. Or try these: {{list_of_file_extensions}}" +msgstr "" + +msgid "request: to restrict to a specific request, typing the title as in the URL." +msgstr "" + +msgid "requested_by:julian_todd to search requests made by Julian Todd, typing the name as in the URL." +msgstr "" + +msgid "requested_from:home_office to search requests from the Home Office, typing the name as in the URL." +msgstr "" + +msgid "status: to select based on the status or historical status of the request, see the table of statuses below." +msgstr "" + +msgid "tag:charity to find all public authorities or requests with a given tag. You can include multiple tags, \\n and tag values, e.g. tag:openlylocal AND tag:financial_transaction:335633. Note that by default any of the tags\\n can be present, you have to put AND explicitly if you only want results them all present." +msgstr "tag:charity to find all public agencies or requests with a given tag. You can include multiple tags, \\n and tag values, e.g. tag:openlylocal AND tag:financial_transaction:335633. Note that by default any of the tags\\n can be present, you have to put AND explicitly if you only want results them all present." + +msgid "variety: to select type of thing to search for, see the table of varieties below." +msgstr "" + +msgid "Advice on how to get a response that will satisfy the requester. " +msgstr "" + +msgid "All the information has been sent" +msgstr "" + +msgid "Anything else, such as clarifying, prompting, thanking" +msgstr "" + +msgid "Caveat emptor! To use this data in an honourable way, you will need \\na good internal knowledge of user behaviour on {{site_name}}. How, \\nwhy and by whom requests are categorised is not straightforward, and there will\\nbe user error and ambiguity. You will also need to understand FOI law, and the\\nway authorities use it. Plus you'll need to be an elite statistician. Please\\ncontact us with questions." +msgstr "Caveat emptor! To use this data in an honourable way, you will need \\na good internal knowledge of user behaviour on {{site_name}}. How, \\nwhy and by whom requests are categorised is not straightforward, and there will\\nbe user error and ambiguity. You will also need to understand ATI law, and the\\nway agencies use it. Plus you'll need to be an elite statistician. Please\\ncontact us with questions." + +msgid "Clarification has been requested" +msgstr "" + +msgid "No response has been received\\n (maybe there's just an acknowledgement)" +msgstr "" + +msgid "Note: Because we're testing, requests are being sent to {{email}} rather than to the actual authority." +msgstr "Note: Because we're testing, requests are being sent to {{email}} rather than to the actual agency." + +msgid "Note: You're sending a message to yourself, presumably\\n to try out how it works." +msgstr "" + +msgid "Note:\\n We will send an email to your new email address. Follow the\\n instructions in it to confirm changing your email." +msgstr "" + +msgid "Privacy note: If you want to request private information about\\n yourself then click here." +msgstr "" + +msgid "Privacy note: Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." +msgstr "" + +msgid "Privacy warning: Your message, and any response\\n to it, will be displayed publicly on this website." +msgstr "" + +msgid "Some of the information has been sent " +msgstr "" + +msgid "Thank the public authority or " +msgstr "Thank the public agency or " + +msgid "did not have the information requested." +msgstr "" + +msgid "A follow up to {{request_title}} was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "A response to {{request_title}} was sent by {{public_body_name}} to {{info_request_user}} on {{date}}. The request status is: {{request_status}}" +msgstr "" + +msgid "A summary of the response if you have received it by post. " +msgstr "" + +msgid "A Freedom of Information request" +msgstr "An Access to Information request" + +msgid "A full history of my FOI request and all correspondence is available on the Internet at this address: {{url}}" +msgstr "A full history of my ATI request and all correspondence is available on the Internet at this address: {{url}}" + +msgid "A new request, {{request_title}}, was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "A public authority" +msgstr "A public agency" + +msgid "A response will be sent by post" +msgstr "A response will be sent by post" + +msgid "A strange reponse, required attention by the {{site_name}} team" +msgstr "A strange reponse, required attention by the {{site_name}} team" + +msgid "A vexatious request" +msgstr "A vexatious request" + +msgid "A {{site_name}} user" +msgstr "" + +msgid "About you:" +msgstr "" + +msgid "Act on what you've learnt" +msgstr "" + +msgid "Acts as xapian/acts as xapian job" +msgstr "" + +msgid "ActsAsXapian::ActsAsXapianJob|Action" +msgstr "" + +msgid "ActsAsXapian::ActsAsXapianJob|Model" +msgstr "" + +msgid "Add an annotation" +msgstr "" + +msgid "Add an annotation to your request with choice quotes, or\\n a summary of the response." +msgstr "" + +msgid "Added on {{date}}" +msgstr "" + +msgid "Admin level is not included in list" +msgstr "" + +msgid "Administration URL:" +msgstr "" + +msgid "Advanced search" +msgstr "" + +msgid "Advanced search tips" +msgstr "" + +msgid "Advise on whether the refusal is legal, and how to complain about it if not." +msgstr "" + +msgid "Air, water, soil, land, flora and fauna (including how these effect\\n human beings)" +msgstr "" + +msgid "All of the information requested has been received" +msgstr "" + +msgid "All the options below can use status or latest_status before the colon. For example, status:not_held will match requests which have ever been marked as not held; latest_status:not_held will match only requests that are currently marked as not held." +msgstr "" + +msgid "All the options below can use variety or latest_variety before the colon. For example, variety:sent will match requests which have ever been sent; latest_variety:sent will match only requests that are currently marked as sent." +msgstr "" + +msgid "Also called {{other_name}}." +msgstr "" + +msgid "Also send me alerts by email" +msgstr "" + +msgid "Alter your subscription" +msgstr "" + +msgid "Although all responses are automatically published, we depend on\\nyou, the original requester, to evaluate them." +msgstr "" + +msgid "An annotation to {{request_title}} was made by {{event_comment_user}} on {{date}}" +msgstr "" + +msgid "An error message has been received" +msgstr "" + +msgid "An Environmental Information Regulations request" +msgstr "" + +msgid "An anonymous user" +msgstr "" + +msgid "Annotation added to request" +msgstr "" + +msgid "Annotations" +msgstr "" + +msgid "Annotations are so anyone, including you, can help the requester with their request. For example:" +msgstr "" + +msgid "Annotations will be posted publicly here, and are\\n not sent to {{public_body_name}}." +msgstr "" + +msgid "Anonymous user" +msgstr "" + +msgid "Anyone:" +msgstr "" + +msgid "Applies to" +msgstr "" + +msgid "Are we missing a public authority?" +msgstr "Are we missing a public agency?" + +msgid "Are you the owner of any commercial copyright on this page?" +msgstr "" + +msgid "Ask for specific documents or information, this site is not suitable for general enquiries." +msgstr "" + +msgid "At the bottom of this page, write a reply to them trying to persuade them to scan it in\\n (more details)." +msgstr "" + +msgid "Attachment (optional):" +msgstr "" + +msgid "Attachment:" +msgstr "" + +msgid "Awaiting classification." +msgstr "" + +msgid "Awaiting internal review." +msgstr "" + +msgid "Awaiting response." +msgstr "" + +msgid "Beginning with" +msgstr "" + +msgid "Browse other requests for examples of how to word your request." +msgstr "Browse other requests for examples of how to word your request." + +msgid "Browse other requests to '{{public_body_name}}' for examples of how to word your request." +msgstr "Browse other requests to '{{public_body_name}}' for examples of how to word your request." + +msgid "Browse all authorities..." +msgstr "Browse all agencies…" + +msgid "By law, under all circumstances, {{public_body_link}} should have responded by now" +msgstr "" + +msgid "By law, {{public_body_link}} should normally have responded promptly and" +msgstr "" + +msgid "Calculated home page" +msgstr "" + +msgid "Can't find the one you want?" +msgstr "" + +msgid "Cancel a {{site_name}} alert" +msgstr "" + +msgid "Cancel some {{site_name}} alerts" +msgstr "" + +msgid "Cancel, return to your profile page" +msgstr "" + +msgid "Censor rule" +msgstr "" + +msgid "CensorRule|Last edit comment" +msgstr "" + +msgid "CensorRule|Last edit editor" +msgstr "" + +msgid "CensorRule|Regexp" +msgstr "" + +msgid "CensorRule|Replacement" +msgstr "" + +msgid "CensorRule|Text" +msgstr "" + +msgid "Change email on {{site_name}}" +msgstr "" + +msgid "Change password on {{site_name}}" +msgstr "" + +msgid "Change profile photo" +msgstr "" + +msgid "Change the text about you on your profile at {{site_name}}" +msgstr "" + +msgid "Change your email" +msgstr "" + +msgid "Change your email address used on {{site_name}}" +msgstr "" + +msgid "Change your password" +msgstr "" + +msgid "Change your password on {{site_name}}" +msgstr "" + +msgid "Change your password {{site_name}}" +msgstr "" + +msgid "Charity registration" +msgstr "" + +msgid "Check for mistakes if you typed or copied the address." +msgstr "" + +msgid "Check you haven't included any personal information." +msgstr "" + +msgid "Choose your profile photo" +msgstr "" + +msgid "Clarification" +msgstr "" + +msgid "Clarify your FOI request - " +msgstr "Clarify your ATI request - " + +msgid "Classify an FOI response from " +msgstr "Classify an ATI response from " + +msgid "Clear photo" +msgstr "" + +msgid "Click on the link below to send a message to {{public_body_name}} telling them to reply to your request. You might like to ask for an internal\\nreview, asking them to find out why response to the request has been so slow." +msgstr "" + +msgid "Click on the link below to send a message to {{public_body}} reminding them to reply to your request." +msgstr "" + +msgid "Close" +msgstr "" + +msgid "Comment" +msgstr "" + +msgid "Comment|Body" +msgstr "" + +msgid "Comment|Comment type" +msgstr "" + +msgid "Comment|Locale" +msgstr "" + +msgid "Comment|Visible" +msgstr "" + +msgid "Confirm you want to follow all successful FOI requests" +msgstr "Confirm you want to follow all successful ATI requests" + +msgid "Confirm you want to follow new requests" +msgstr "" + +msgid "Confirm you want to follow new requests or responses matching your search" +msgstr "" + +msgid "Confirm you want to follow requests by '{{user_name}}'" +msgstr "" + +msgid "Confirm you want to follow requests to '{{public_body_name}}'" +msgstr "" + +msgid "Confirm you want to follow the request '{{request_title}}'" +msgstr "" + +msgid "Confirm your FOI request to " +msgstr "Confirm your ATI request to " + +msgid "Confirm your account on {{site_name}}" +msgstr "" + +msgid "Confirm your annotation to {{info_request_title}}" +msgstr "" + +msgid "Confirm your email address" +msgstr "" + +msgid "Confirm your new email address on {{site_name}}" +msgstr "" + +msgid "Considered by administrators as not an FOI request and hidden from site." +msgstr "Considered by administrators as not an ATI request and hidden from site." + +msgid "Considered by administrators as vexatious and hidden from site." +msgstr "" + +msgid "Contact {{recipient}}" +msgstr "" + +msgid "Contact {{site_name}}" +msgstr "" + +msgid "Could not identify the request from the email address" +msgstr "" + +msgid "Couldn't understand the image file that you uploaded. PNG, JPEG, GIF and many other common image file formats are supported." +msgstr "" + +msgid "Crop your profile photo" +msgstr "" + +msgid "Cultural sites and built structures (as they may be affected by the\\n environmental factors listed above)" +msgstr "" + +msgid "Currently waiting for a response from {{public_body_link}}, they must respond promptly and" +msgstr "" + +msgid "Date:" +msgstr "" + +msgid "Dear {{name}}," +msgstr "" + +msgid "Dear {{public_body_name}}," +msgstr "" + +msgid "Default locale" +msgstr "" + +msgid "Defunct." +msgstr "" + +msgid "Delayed response to your FOI request - " +msgstr "Delayed response to your ATI request - " + +msgid "Delayed." +msgstr "" + +msgid "Delivery error" +msgstr "" + +msgid "Destroy {{name}}" +msgstr "" + +msgid "Details of request '" +msgstr "" + +msgid "Did you mean: {{correction}}" +msgstr "" + +msgid "Disclaimer: This message and any reply that you make will be published on the internet. Our privacy and copyright policies:" +msgstr "" + +msgid "Disclosure log" +msgstr "" + +msgid "Disclosure log URL" +msgstr "" + +msgid "Don't want to address your message to {{person_or_body}}? You can also write to:" +msgstr "" + +msgid "Done" +msgstr "" + +msgid "Done >>" +msgstr "" + +msgid "Download a zip file of all correspondence" +msgstr "" + +msgid "Download original attachment" +msgstr "" + +msgid "EIR" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit and add more details to the message above,\\n explaining why you are dissatisfied with their response." +msgstr "" + +msgid "Edit text about you" +msgstr "" + +msgid "Edit this request" +msgstr "" + +msgid "Either the email or password was not recognised, please try again." +msgstr "" + +msgid "Either the email or password was not recognised, please try again. Or create a new account using the form on the right." +msgstr "" + +msgid "Email doesn't look like a valid address" +msgstr "" + +msgid "Email me future updates to this request" +msgstr "" + +msgid "Enter words that you want to find separated by spaces, e.g. climbing lane" +msgstr "" + +msgid "Enter your response below. You may attach one file (use email, or\\n contact us if you need more)." +msgstr "" + +msgid "Environmental Information Regulations" +msgstr "" + +msgid "Environmental Information Regulations requests made" +msgstr "" + +msgid "Environmental Information Regulations requests made using this site" +msgstr "" + +msgid "Event history" +msgstr "" + +msgid "Event history details" +msgstr "" + +msgid "Event {{id}}" +msgstr "" + +msgid "Everything that you enter on this page, including your name,\\n will be displayed publicly on\\n this website forever (why?)." +msgstr "" + +msgid "Everything that you enter on this page\\n will be displayed publicly on\\n this website forever (why?)." +msgstr "" + +msgid "FOI" +msgstr "ATI" + +msgid "FOI email address for {{public_body}}" +msgstr "ATI email address for {{public_body}}" + +msgid "FOI request – {{title}}" +msgstr "ATI request – {{title}}" + +msgid "FOI requests" +msgstr "ATI requests" + +msgid "FOI requests by '{{user_name}}'" +msgstr "ATI requests by '{{user_name}}'" + +msgid "FOI requests {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "ATI requests {{start_count}} to {{end_count}} of {{total_count}}" + +msgid "FOI response requires admin ({{reason}}) - {{title}}" +msgstr "ATI response requires admin ({{reason}}) - {{title}}" + +msgid "Failed to convert image to a PNG" +msgstr "" + +msgid "Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}" +msgstr "" + +msgid "Filter" +msgstr "" + +msgid "First, did your other requests succeed?" +msgstr "" + +msgid "First, type in the name of the UK public authority you'd\\n like information from. By law, they have to respond\\n (why?)." +msgstr "First, search the name of the Ugandan public agency you would\\n like information from. By law, they have to respond\\n (why?)." + +msgid "Foi attachment" +msgstr "" + +msgid "FoiAttachment|Charset" +msgstr "" + +msgid "FoiAttachment|Content type" +msgstr "" + +msgid "FoiAttachment|Display size" +msgstr "" + +msgid "FoiAttachment|Filename" +msgstr "" + +msgid "FoiAttachment|Hexdigest" +msgstr "" + +msgid "FoiAttachment|Url part number" +msgstr "" + +msgid "FoiAttachment|Within rfc822 subject" +msgstr "" + +msgid "Follow" +msgstr "" + +msgid "Follow all new requests" +msgstr "" + +msgid "Follow new successful responses" +msgstr "" + +msgid "Follow requests to {{public_body_name}}" +msgstr "" + +msgid "Follow these requests" +msgstr "" + +msgid "Follow things matching this search" +msgstr "" + +msgid "Follow this authority" +msgstr "Follow this agency" + +msgid "Follow this link to see the request:" +msgstr "" + +msgid "Follow this person" +msgstr "" + +msgid "Follow this request" +msgstr "" + +#. "Follow up" in this context means a further +#. message sent by the requester to the authority after +#. the initial request +msgid "Follow up" +msgstr "Follow up" + +#. "Follow up message" in this context means a +#. further message sent by the requester to the authority after +#. the initial request +msgid "Follow up message sent by requester" +msgstr "" + +msgid "Follow up messages to existing requests are sent to " +msgstr "" + +#. "Follow ups" in this context means further +#. messages sent by the requester to the authority after +#. the initial request +msgid "Follow ups and new responses to this request have been stopped to prevent spam. Please contact us if you are {{user_link}} and need to send a follow up." +msgstr "Follow ups and new responses to this request have been stopped to prevent spam. Please contact us if you are {{user_link}} and need to send a follow up." + +msgid "Follow us on twitter" +msgstr "" + +msgid "Followups cannot be sent for this request, as it was made externally, and published here by {{public_body_name}} on the requester's behalf." +msgstr "" + +msgid "For an unknown reason, it is not possible to make a request to this authority." +msgstr "For an unknown reason, it is not possible to make a request to this agency." + +msgid "Forgotten your password?" +msgstr "" + +msgid "Found {{count}} public authority {{description}}" +msgid_plural "Found {{count}} public authorities {{description}}" +msgstr[0] "Found {{count}} public agency {{description}}" +msgstr[1] "Found {{count}} public agencies {{description}}" + +msgid "Freedom of Information" +msgstr "Access to Information" + +msgid "Freedom of Information Act" +msgstr "Access to Information Act" + +msgid "Freedom of Information law does not apply to this authority, so you cannot make\\n a request to it." +msgstr "Access to Information law does not apply to this agency, so you cannot make\\n a request to it." + +msgid "Freedom of Information law no longer applies to" +msgstr "Access to Information law no longer applies to" + +msgid "Freedom of Information law no longer applies to this authority.Follow up messages to existing requests are sent to " +msgstr "Access to Information law no longer applies to this agency. Follow up messages to existing requests are sent to " + +msgid "Freedom of Information requests made" +msgstr "Access to Information requests made" + +msgid "Freedom of Information requests made by this person" +msgstr "Access to Information requests made by this person" + +msgid "Freedom of Information requests made by you" +msgstr "Access to Information requests made by you" + +msgid "Freedom of Information requests made using this site" +msgstr "Access to Information requests made using this site" + +msgid "Freedom of information requests to" +msgstr "Access to Information requests to" + +msgid "From" +msgstr "" + +msgid "From the request page, try replying to a particular message, rather than sending\\n a general followup. If you need to make a general followup, and know\\n an email which will go to the right place, please send it to us." +msgstr "" + +msgid "From:" +msgstr "" + +msgid "GIVE DETAILS ABOUT YOUR COMPLAINT HERE" +msgstr "" + +msgid "Handled by post." +msgstr "" + +msgid "Has tag string/has tag string tag" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Model" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Name" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Value" +msgstr "" + +msgid "Hello! You can make Freedom of Information requests within {{country_name}} at {{link_to_website}}" +msgstr "Hello! You can make Access to Information requests within {{country_name}} at {{link_to_website}}" + +msgid "Hello, {{username}}!" +msgstr "" + +msgid "Help" +msgstr "" + +msgid "Here described means when a user selected a status for the request, and\\nthe most recent event had its status updated to that value. calculated is then inferred by\\n{{site_name}} for intermediate events, which weren't given an explicit\\ndescription by a user. See the search tips for description of the states." +msgstr "" + +msgid "Here is the message you wrote, in case you would like to copy the text and save it for later." +msgstr "" + +msgid "Hi! We need your help. The person who made the following request\\n hasn't told us whether or not it was successful. Would you mind taking\\n a moment to read it and help us keep the place tidy for everyone?\\n Thanks." +msgstr "" + +msgid "Hide request" +msgstr "" + +msgid "Holiday" +msgstr "" + +msgid "Holiday|Day" +msgstr "" + +msgid "Holiday|Description" +msgstr "" + +msgid "Home" +msgstr "" + +msgid "Home page" +msgstr "" + +msgid "Home page of authority" +msgstr "Home page of agency" + +msgid "However, you have the right to request environmental\\n information under a different law" +msgstr "" + +msgid "Human health and safety" +msgstr "" + +msgid "I am asking for new information" +msgstr "" + +msgid "I am requesting an internal review" +msgstr "" + +msgid "I am writing to request an internal review of {{public_body_name}}'s handling of my FOI request '{{info_request_title}}'." +msgstr "I am writing to request an internal review of {{public_body_name}}'s handling of my ATI request '{{info_request_title}}'." + +msgid "I don't like these ones — give me some more!" +msgstr "" + +msgid "I don't want to do any more tidying now!" +msgstr "" + +msgid "I like this request" +msgstr "" + +msgid "I would like to withdraw this request" +msgstr "" + +msgid "I'm still waiting for my information\\n (maybe you got an acknowledgement)" +msgstr "" + +msgid "I'm still waiting for the internal review" +msgstr "" + +msgid "I'm waiting for an internal review response" +msgstr "" + +msgid "I've been asked to clarify my request" +msgstr "" + +msgid "I've received all the information" +msgstr "" + +msgid "I've received some of the information" +msgstr "" + +msgid "I've received an error message" +msgstr "" + +msgid "I've received an error message" +msgstr "" + +msgid "Id" +msgstr "" + +msgid "If the address is wrong, or you know a better address, please contact us." +msgstr "" + +msgid "If the error was a delivery failure, and you can find an up to date FOI email address for the authority, please tell us using the form below." +msgstr "If the error was a delivery failure, and you can find an up to date ATI email address for the agency, please tell us using the form below." + +msgid "If this is incorrect, or you would like to send a late response to the request\\nor an email on another subject to {{user}}, then please\\nemail {{contact_email}} for help." +msgstr "" + +msgid "If you are dissatisfied by the response you got from\\n the public authority, you have the right to\\n complain (details)." +msgstr "If you are dissatisfied by the response you got from\\n the public agency, you have the right to\\n complain (details)." + +msgid "If you are still having trouble, please contact us." +msgstr "" + +msgid "If you are the requester, then you may sign in to view the message." +msgstr "" + +msgid "If you are the requester, then you may sign in to view the request." +msgstr "" + +msgid "If you are thinking of using a pseudonym,\\n please read this first." +msgstr "" + +msgid "If you are {{user_link}}, please" +msgstr "" + +msgid "If you believe this request is not suitable, you can report it for attention by the site administrators" +msgstr "" + +msgid "If you can't click on it in the email, you'll have to select and copy\\nit from the email. Then paste it into your browser, into the place\\nyou would type the address of any other webpage." +msgstr "" + +msgid "If you can, scan in or photograph the response, and send us\\n a copy to upload." +msgstr "" + +msgid "If you find this service useful as an FOI officer, please ask your web manager to link to us from your organisation's FOI page." +msgstr "If you find this service useful as an ATI officer, please ask your web manager to link to us from your organisation's ATI page." + +msgid "If you got the email more than six months ago, then this login link won't work any\\nmore. Please try doing what you were doing from the beginning." +msgstr "" + +msgid "If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn." +msgstr "If you have not done so already, please write a message below telling the agency that you have withdrawn your request. Otherwise they will not know it has been withdrawn." + +msgid "If you reply to this message it will go directly to {{user_name}}, who will\\nlearn your email address. Only reply if that is okay." +msgstr "" + +msgid "If you use web-based email or have \"junk mail\" filters, also check your\\nbulk/spam mail folders. Sometimes, our messages are marked that way." +msgstr "" + +msgid "If you would like us to lift this ban, then you may politely\\ncontact us giving reasons.\\n" +msgstr "" + +msgid "If you're new to {{site_name}}" +msgstr "" + +msgid "If you've used {{site_name}} before" +msgstr "" + +msgid "If your browser is set to accept cookies and you are seeing this message,\\nthen there is probably a fault with our server." +msgstr "" + +msgid "Incoming email address" +msgstr "" + +msgid "Incoming message" +msgstr "" + +msgid "IncomingMessage|Cached attachment text clipped" +msgstr "" + +msgid "IncomingMessage|Cached main body text folded" +msgstr "" + +msgid "IncomingMessage|Cached main body text unfolded" +msgstr "" + +msgid "IncomingMessage|Last parsed" +msgstr "" + +msgid "IncomingMessage|Mail from" +msgstr "" + +msgid "IncomingMessage|Mail from domain" +msgstr "" + +msgid "IncomingMessage|Prominence" +msgstr "" + +msgid "IncomingMessage|Prominence reason" +msgstr "" + +msgid "IncomingMessage|Sent at" +msgstr "" + +msgid "IncomingMessage|Subject" +msgstr "" + +msgid "IncomingMessage|Valid to reply to" +msgstr "" + +msgid "Individual requests" +msgstr "" + +msgid "Info request" +msgstr "" + +msgid "Info request event" +msgstr "" + +msgid "InfoRequestEvent|Calculated state" +msgstr "" + +msgid "InfoRequestEvent|Described state" +msgstr "" + +msgid "InfoRequestEvent|Event type" +msgstr "" + +msgid "InfoRequestEvent|Last described at" +msgstr "" + +msgid "InfoRequestEvent|Params yaml" +msgstr "" + +msgid "InfoRequest|Allow new responses from" +msgstr "" + +msgid "InfoRequest|Attention requested" +msgstr "" + +msgid "InfoRequest|Awaiting description" +msgstr "" + +msgid "InfoRequest|Comments allowed" +msgstr "" + +msgid "InfoRequest|Described state" +msgstr "" + +msgid "InfoRequest|External url" +msgstr "" + +msgid "InfoRequest|External user name" +msgstr "" + +msgid "InfoRequest|Handle rejected responses" +msgstr "" + +msgid "InfoRequest|Idhash" +msgstr "" + +msgid "InfoRequest|Law used" +msgstr "" + +msgid "InfoRequest|Prominence" +msgstr "" + +msgid "InfoRequest|Title" +msgstr "" + +msgid "InfoRequest|Url title" +msgstr "" + +msgid "Information not held." +msgstr "" + +msgid "Information on emissions and discharges (e.g. noise, energy,\\n radiation, waste materials)" +msgstr "" + +msgid "Internal review request" +msgstr "" + +msgid "Is {{email_address}} the wrong address for {{type_of_request}} requests to {{public_body_name}}? If so, please contact us using this form:" +msgstr "" + +msgid "It may be that your browser is not set to accept a thing called \"cookies\",\\nor cannot do so. If you can, please enable cookies, or try using a different\\nbrowser. Then press refresh to have another go." +msgstr "" + +msgid "Items matching the following conditions are currently displayed on your wall." +msgstr "" + +msgid "Items sent in last month" +msgstr "" + +msgid "Joined in" +msgstr "" + +msgid "Joined {{site_name}} in" +msgstr "" + +msgid "Just one more thing" +msgstr "" + +msgid "Keep it focused, you'll be more likely to get what you want (why?)." +msgstr "" + +msgid "Keywords" +msgstr "" + +msgid "Last authority viewed: " +msgstr "Last agency viewed: " + +msgid "Last request viewed: " +msgstr "" + +msgid "Let us know what you were doing when this message\\nappeared and your browser and operating system type and version." +msgstr "" + +msgid "Link to this" +msgstr "" + +msgid "List all" +msgstr "" + +msgid "List of all authorities (CSV)" +msgstr "List of all agencies (CSV)" + +msgid "Listing FOI requests" +msgstr "Listing ATI requests" + +msgid "Listing public authorities" +msgstr "Listing public agencies" + +msgid "Listing public authorities matching '{{query}}'" +msgstr "Listing public agencies matching '{{query}}'" + +msgid "Listing tracks" +msgstr "" + +msgid "Listing users" +msgstr "" + +msgid "Log in to download a zip file of {{info_request_title}}" +msgstr "" + +msgid "Log into the admin interface" +msgstr "" + +msgid "Long overdue." +msgstr "" + +msgid "Made between" +msgstr "" + +msgid "Mail server log" +msgstr "" + +msgid "Mail server log done" +msgstr "" + +msgid "MailServerLogDone|Filename" +msgstr "" + +msgid "MailServerLogDone|Last stat" +msgstr "" + +msgid "MailServerLog|Line" +msgstr "" + +msgid "MailServerLog|Order" +msgstr "" + +msgid "Make a new
    \\n Freedom of
    \\n Information
    \\n request
    " +msgstr "Make a new
    \\n Access to
    \\n Information
    \\n request
    " + +msgid "Make a request" +msgstr "" + +msgid "Make a request to this authority" +msgstr "Make a request to this agency" + +msgid "Make an {{law_used_short}} request to '{{public_body_name}}'" +msgstr "" + +msgid "Make and browse Freedom of Information (FOI) requests" +msgstr "Make and browse Access to Information (ATI) requests" + +msgid "Make your own request" +msgstr "" + +msgid "Many requests" +msgstr "" + +msgid "Message" +msgstr "" + +msgid "Message has been removed" +msgstr "" + +msgid "Message sent using {{site_name}} contact form, " +msgstr "" + +msgid "Missing contact details for '" +msgstr "" + +msgid "More about this authority" +msgstr "More about this agency" + +msgid "More requests..." +msgstr "" + +msgid "More similar requests" +msgstr "" + +msgid "More successful requests..." +msgstr "" + +msgid "My profile" +msgstr "" + +msgid "My request has been refused" +msgstr "" + +msgid "My requests" +msgstr "" + +msgid "My wall" +msgstr "" + +msgid "Name can't be blank" +msgstr "" + +msgid "Name is already taken" +msgstr "" + +msgid "New Freedom of Information requests" +msgstr "New Access to Information requests" + +msgid "New censor rule" +msgstr "" + +msgid "New e-mail:" +msgstr "" + +msgid "New email doesn't look like a valid address" +msgstr "" + +msgid "New password:" +msgstr "" + +msgid "New password: (again)" +msgstr "" + +msgid "New response to '{{title}}'" +msgstr "" + +msgid "New response to your FOI request - " +msgstr "New response to your ATI request - " + +msgid "New response to your request" +msgstr "" + +msgid "New response to {{law_used_short}} request" +msgstr "" + +msgid "New updates for the request '{{request_title}}'" +msgstr "" + +msgid "Newest results first" +msgstr "" + +msgid "Next" +msgstr "" + +msgid "Next, crop your photo >>" +msgstr "" + +msgid "No requests of this sort yet." +msgstr "" + +msgid "No results found." +msgstr "" + +msgid "No similar requests found." +msgstr "" + +msgid "No tracked things found." +msgstr "" + +msgid "Nobody has made any Freedom of Information requests to {{public_body_name}} using this site yet." +msgstr "Nobody has made any Access to Information requests to {{public_body_name}} using this site yet." + +msgid "None found." +msgstr "" + +msgid "None made." +msgstr "" + +msgid "Not a valid FOI request" +msgstr "Not a valid ATI request" + +msgid "Note that the requester will not be notified about your annotation, because the request was published by {{public_body_name}} on their behalf." +msgstr "" + +msgid "Now check your email!" +msgstr "" + +msgid "Now preview your annotation" +msgstr "" + +msgid "Now preview your follow up" +msgstr "" + +msgid "Now preview your message asking for an internal review" +msgstr "" + +msgid "Number of requests" +msgstr "" + +msgid "OR remove the existing photo" +msgstr "" + +msgid "Offensive? Unsuitable?" +msgstr "" + +msgid "Oh no! Sorry to hear that your request was refused. Here is what to do now." +msgstr "" + +msgid "Old e-mail:" +msgstr "" + +msgid "Old email address isn't the same as the address of the account you are logged in with" +msgstr "" + +msgid "Old email doesn't look like a valid address" +msgstr "" + +msgid "On this page" +msgstr "" + +msgid "One FOI request found" +msgstr "One ATI request found" + +msgid "One person found" +msgstr "" + +msgid "One public authority found" +msgstr "One public agency found" + +msgid "Only put in abbreviations which are really used, otherwise leave blank. Short or long name is used in the URL – don't worry about breaking URLs through renaming, as the history is used to redirect" +msgstr "" + +msgid "Only requests made using {{site_name}} are shown." +msgstr "" + +msgid "Only the authority can reply to this request, and I don't recognise the address this reply was sent from" +msgstr "Only the agency can reply to this request, and I don't recognise the address this reply was sent from" + +msgid "Only the authority can reply to this request, but there is no \"From\" address to check against" +msgstr "Only the agency can reply to this request, but there is no \"From\" address to check against" + +msgid "Or search in their website for this information." +msgstr "" + +msgid "Original request sent" +msgstr "" + +msgid "Other:" +msgstr "" + +msgid "Outgoing message" +msgstr "" + +msgid "OutgoingMessage|Body" +msgstr "" + +msgid "OutgoingMessage|Last sent at" +msgstr "" + +msgid "OutgoingMessage|Message type" +msgstr "" + +msgid "OutgoingMessage|Prominence" +msgstr "" + +msgid "OutgoingMessage|Prominence reason" +msgstr "" + +msgid "OutgoingMessage|Status" +msgstr "" + +msgid "OutgoingMessage|What doing" +msgstr "" + +msgid "Partially successful." +msgstr "" + +msgid "Password is not correct" +msgstr "" + +msgid "Password:" +msgstr "" + +msgid "Password: (again)" +msgstr "" + +msgid "Paste this link into emails, tweets, and anywhere else:" +msgstr "" + +msgid "People" +msgstr "" + +msgid "People {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "" + +msgid "Percentage of requests that are overdue" +msgstr "" + +msgid "Percentage of total requests" +msgstr "" + +msgid "Photo of you:" +msgstr "" + +msgid "Plans and administrative measures that affect these matters" +msgstr "" + +msgid "Play the request categorisation game" +msgstr "" + +msgid "Play the request categorisation game!" +msgstr "" + +msgid "Please" +msgstr "" + +msgid "Please contact us if you have any questions." +msgstr "" + +msgid "Please get in touch with us so we can fix it." +msgstr "" + +msgid "Please answer the question above so we know whether the " +msgstr "" + +msgid "Please go to the following requests, and let us\\n know if there was information in the recent responses to them." +msgstr "" + +msgid "Please only write messages directly relating to your request {{request_link}}. If you would like to ask for information that was not in your original request, then file a new request." +msgstr "" + +msgid "Please ask for environmental information only" +msgstr "" + +msgid "Please check the URL (i.e. the long code of letters and numbers) is copied\\ncorrectly from your email." +msgstr "" + +msgid "Please choose a file containing your photo." +msgstr "" + +msgid "Please choose a reason" +msgstr "" + +msgid "Please choose what sort of reply you are making." +msgstr "" + +msgid "Please choose whether or not you got some of the information that you wanted." +msgstr "" + +msgid "Please click on the link below to cancel or alter these emails." +msgstr "" + +msgid "Please click on the link below to confirm that you want to \\nchange the email address that you use for {{site_name}}\\nfrom {{old_email}} to {{new_email}}" +msgstr "" + +msgid "Please click on the link below to confirm your email address." +msgstr "" + +msgid "Please describe more what the request is about in the subject. There is no need to say it is an FOI request, we add that on anyway." +msgstr "Please describe more what the request is about in the subject. There is no need to say it is an ATI request, we add that on anyway." + +msgid "Please don't upload offensive pictures. We will take down images\\n that we consider inappropriate." +msgstr "" + +msgid "Please enable \"cookies\" to carry on" +msgstr "" + +msgid "Please enter a password" +msgstr "" + +msgid "Please enter a subject" +msgstr "" + +msgid "Please enter a summary of your request" +msgstr "" + +msgid "Please enter a valid email address" +msgstr "" + +msgid "Please enter the message you want to send" +msgstr "" + +msgid "Please enter the same password twice" +msgstr "" + +msgid "Please enter your annotation" +msgstr "" + +msgid "Please enter your email address" +msgstr "" + +msgid "Please enter your follow up message" +msgstr "" + +msgid "Please enter your letter requesting information" +msgstr "" + +msgid "Please enter your name" +msgstr "" + +msgid "Please enter your name, not your email address, in the name field." +msgstr "" + +msgid "Please enter your new email address" +msgstr "" + +msgid "Please enter your old email address" +msgstr "" + +msgid "Please enter your password" +msgstr "" + +msgid "Please give details explaining why you want a review" +msgstr "" + +msgid "Please keep it shorter than 500 characters" +msgstr "" + +msgid "Please keep the summary short, like in the subject of an email. You can use a phrase, rather than a full sentence." +msgstr "" + +msgid "Please only request information that comes under those categories, do not waste your\\n time or the time of the public authority by requesting unrelated information." +msgstr "Please only request information that comes under those categories, do not waste your\\n time or the time of the public agency by requesting unrelated information." + +msgid "Please pass this on to the person who conducts Freedom of Information reviews." +msgstr "Please pass this on to the person who conducts Access to Information reviews." + +msgid "Please select each of these requests in turn, and let everyone know\\nif they are successful yet or not." +msgstr "" + +msgid "Please sign at the bottom with your name, or alter the \"{{signoff}}\" signature" +msgstr "" + +msgid "Please sign in as " +msgstr "" + +msgid "Please sign in or make a new account." +msgstr "" + +msgid "Please type a message and/or choose a file containing your response." +msgstr "" + +msgid "Please use this email address for all replies to this request:" +msgstr "" + +msgid "Please write a summary with some text in it" +msgstr "" + +msgid "Please write the summary using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Please write your annotation using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Please write your follow up message containing the necessary clarifications below." +msgstr "" + +msgid "Please write your message using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Point to related information, campaigns or forums which may be useful." +msgstr "" + +msgid "Possibly related requests:" +msgstr "" + +msgid "Post annotation" +msgstr "" + +msgid "Post redirect" +msgstr "" + +msgid "PostRedirect|Circumstance" +msgstr "" + +msgid "PostRedirect|Email token" +msgstr "" + +msgid "PostRedirect|Post params yaml" +msgstr "" + +msgid "PostRedirect|Reason params yaml" +msgstr "" + +msgid "PostRedirect|Token" +msgstr "" + +msgid "PostRedirect|Uri" +msgstr "" + +msgid "Posted on {{date}} by {{author}}" +msgstr "" + +msgid "Powered by Alaveteli" +msgstr "" + +msgid "Prev" +msgstr "" + +msgid "Preview follow up to '" +msgstr "" + +msgid "Preview new annotation on '{{info_request_title}}'" +msgstr "" + +msgid "Preview your annotation" +msgstr "" + +msgid "Preview your message" +msgstr "" + +msgid "Preview your public request" +msgstr "" + +msgid "Profile photo" +msgstr "" + +msgid "ProfilePhoto|Data" +msgstr "" + +msgid "ProfilePhoto|Draft" +msgstr "" + +msgid "Public Bodies" +msgstr "" + +msgid "Public Body Statistics" +msgstr "" + +msgid "Public authorities" +msgstr "Public agencies" + +msgid "Public authorities - {{description}}" +msgstr "Public agencies - {{description}}" + +msgid "Public authorities {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "Public agencies {{start_count}} to {{end_count}} of {{total_count}}" + +msgid "Public authority – {{name}}" +msgstr "Public agency – {{name}}" + +msgid "Public bodies that most frequently replied with \"Not Held\"" +msgstr "Public bodies that most frequently replied with \"Not Held\"" + +msgid "Public bodies with most overdue requests" +msgstr "Public bodies with most overdue requests" + +msgid "Public bodies with the fewest successful requests" +msgstr "" + +msgid "Public bodies with the most requests" +msgstr "" + +msgid "Public bodies with the most successful requests" +msgstr "" + +msgid "Public body" +msgstr "" + +msgid "Public notes" +msgstr "" + +msgid "Public page" +msgstr "" + +msgid "Public page not available" +msgstr "" + +msgid "PublicBody|Api key" +msgstr "" + +msgid "PublicBody|Disclosure log" +msgstr "" + +msgid "PublicBody|First letter" +msgstr "" + +msgid "PublicBody|Home page" +msgstr "" + +msgid "PublicBody|Info requests count" +msgstr "" + +msgid "PublicBody|Info requests not held count" +msgstr "" + +msgid "PublicBody|Info requests overdue count" +msgstr "" + +msgid "PublicBody|Info requests successful count" +msgstr "" + +msgid "PublicBody|Info requests visible classified count" +msgstr "" + +msgid "PublicBody|Last edit comment" +msgstr "" + +msgid "PublicBody|Last edit editor" +msgstr "" + +msgid "PublicBody|Name" +msgstr "" + +msgid "PublicBody|Notes" +msgstr "" + +msgid "PublicBody|Publication scheme" +msgstr "" + +msgid "PublicBody|Request email" +msgstr "" + +msgid "PublicBody|Short name" +msgstr "" + +msgid "PublicBody|Url name" +msgstr "" + +msgid "PublicBody|Version" +msgstr "" + +msgid "Publication scheme" +msgstr "" + +msgid "Publication scheme URL" +msgstr "" + +msgid "Purge request" +msgstr "" + +msgid "PurgeRequest|Model" +msgstr "" + +msgid "PurgeRequest|Url" +msgstr "" + +msgid "RSS feed" +msgstr "" + +msgid "RSS feed of updates" +msgstr "" + +msgid "Re-edit this annotation" +msgstr "" + +msgid "Re-edit this message" +msgstr "" + +msgid "Read about advanced search operators, such as proximity and wildcards." +msgstr "" + +msgid "Read blog" +msgstr "" + +msgid "Received an error message, such as delivery failure." +msgstr "" + +msgid "Recently described results first" +msgstr "" + +msgid "Refused." +msgstr "" + +msgid "Remember me (keeps you signed in longer;\\n do not use on a public computer) " +msgstr "" + +msgid "Report abuse" +msgstr "" + +msgid "Report an offensive or unsuitable request" +msgstr "" + +msgid "Report request" +msgstr "" + +msgid "Report this request" +msgstr "" + +msgid "Reported for administrator attention." +msgstr "" + +msgid "Request an internal review" +msgstr "" + +msgid "Request an internal review from {{person_or_body}}" +msgstr "" + +msgid "Request email" +msgstr "" + +msgid "Request has been removed" +msgstr "" + +msgid "Request sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "Request to {{public_body_name}} by {{info_request_user}}. Annotated by {{event_comment_user}} on {{date}}." +msgstr "" + +msgid "Requested from {{public_body_name}} by {{info_request_user}} on {{date}}" +msgstr "" + +msgid "Requested on {{date}}" +msgstr "" + +msgid "Requests are considered overdue if they are in the 'Overdue' or 'Very Overdue' states." +msgstr "" + +msgid "Requests are considered successful if they were classified as either 'Successful' or 'Partially Successful'." +msgstr "" + +msgid "Requests for personal information and vexatious requests are not considered valid for FOI purposes (read more)." +msgstr "Requests for personal information and vexatious requests are not considered valid for ATI purposes (read more)." + +msgid "Requests or responses matching your saved search" +msgstr "" + +msgid "Requests similar to '{{request_title}}'" +msgstr "" + +msgid "Requests similar to '{{request_title}}' (page {{page}})" +msgstr "" + +msgid "Respond by email" +msgstr "" + +msgid "Respond to request" +msgstr "" + +msgid "Respond to the FOI request" +msgstr "Respond to the ATI request" + +msgid "Respond using the web" +msgstr "" + +msgid "Response" +msgstr "" + +msgid "Response from a public authority" +msgstr "Response from a public agency" + +msgid "Response to '{{title}}'" +msgstr "" + +msgid "Response to this request is delayed." +msgstr "" + +msgid "Response to this request is long overdue." +msgstr "" + +msgid "Response to your request" +msgstr "" + +msgid "Response:" +msgstr "" + +msgid "Restrict to" +msgstr "" + +msgid "Results page {{page_number}}" +msgstr "" + +msgid "Save" +msgstr "" + +msgid "Search" +msgstr "" + +msgid "Search Freedom of Information requests, public authorities and users" +msgstr "Search Access to Information requests, public agencies and users" + +msgid "Search contributions by this person" +msgstr "" + +msgid "Search for words in:" +msgstr "" + +msgid "Search in" +msgstr "" + +msgid "Search over
    \\n {{number_of_requests}} requests and
    \\n {{number_of_authorities}} authorities" +msgstr "Search over
    \\n {{number_of_requests}} requests and
    \\n {{number_of_authorities}} agencies " + +msgid "Search queries" +msgstr "" + +msgid "Search results" +msgstr "" + +msgid "Search the site to find what you were looking for." +msgstr "" + +msgid "Search within the {{count}} Freedom of Information requests to {{public_body_name}}" +msgid_plural "Search within the {{count}} Freedom of Information requests made to {{public_body_name}}" +msgstr[0] "Search within the {{count}} Access to Information requests to {{public_body_name}}" +msgstr[1] "Search within the {{count}} Access to Information requests made to {{public_body_name}}" + +msgid "Search your contributions" +msgstr "" + +msgid "See bounce message" +msgstr "" + +msgid "Select one to see more information about the authority." +msgstr "Select one to see more information about the agency." + +msgid "Select the authority to write to" +msgstr "Select the agency to write to" + +msgid "Send a followup" +msgstr "" + +msgid "Send a message to " +msgstr "" + +msgid "Send a public follow up message to {{person_or_body}}" +msgstr "" + +msgid "Send a public reply to {{person_or_body}}" +msgstr "" + +msgid "Send follow up to '{{title}}'" +msgstr "" + +msgid "Send message" +msgstr "" + +msgid "Send message to " +msgstr "" + +msgid "Send request" +msgstr "" + +msgid "Set your profile photo" +msgstr "" + +msgid "Short name" +msgstr "" + +msgid "Short name is already taken" +msgstr "" + +msgid "Show most relevant results first" +msgstr "" + +msgid "Show only..." +msgstr "" + +msgid "Showing" +msgstr "" + +msgid "Sign in" +msgstr "" + +msgid "Sign in or make a new account" +msgstr "" + +msgid "Sign in or sign up" +msgstr "" + +msgid "Sign out" +msgstr "" + +msgid "Sign up" +msgstr "" + +msgid "Similar requests" +msgstr "" + +msgid "Simple search" +msgstr "" + +msgid "Some notes have been added to your FOI request - " +msgstr "Some notes have been added to your ATI request - " + +msgid "Some of the information requested has been received" +msgstr "" + +msgid "Some people who've made requests haven't let us know whether they were\\nsuccessful or not. We need your help –\\nchoose one of these requests, read it, and let everyone know whether or not the\\ninformation has been provided. Everyone'll be exceedingly grateful." +msgstr "" + +msgid "Somebody added a note to your FOI request - " +msgstr "Somebody added a note to your ATI request - " + +msgid "Someone has updated the status of your request" +msgstr "" + +msgid "Someone, perhaps you, just tried to change their email address on\\n{{site_name}} from {{old_email}} to {{new_email}}." +msgstr "" + +msgid "Sorry - you cannot respond to this request via {{site_name}}, because this is a copy of the request originally at {{link_to_original_request}}." +msgstr "" + +msgid "Sorry, but only {{user_name}} is allowed to do that." +msgstr "" + +msgid "Sorry, there was a problem processing this page" +msgstr "" + +msgid "Sorry, we couldn't find that page" +msgstr "" + +msgid "Special note for this authority!" +msgstr "Special note for this agency!" + +msgid "Start now »" +msgstr "" + +msgid "Start your own blog" +msgstr "" + +msgid "Stay up to date" +msgstr "" + +msgid "Still awaiting an internal review" +msgstr "" + +msgid "Subject" +msgstr "" + +msgid "Subject:" +msgstr "" + +msgid "Submit" +msgstr "" + +msgid "Submit status" +msgstr "" + +msgid "Submit status and send message" +msgstr "" + +msgid "Subscribe to blog" +msgstr "" + +msgid "Successful Freedom of Information requests" +msgstr "Successful Access to Information requests" + +msgid "Successful." +msgstr "" + +msgid "Suggest how the requester can find the rest of the information." +msgstr "" + +msgid "Summary:" +msgstr "" + +msgid "Table of statuses" +msgstr "" + +msgid "Table of varieties" +msgstr "" + +msgid "Tags" +msgstr "" + +msgid "Tags (separated by a space):" +msgstr "" + +msgid "Tags:" +msgstr "" + +msgid "Technical details" +msgstr "" + +msgid "Thank you for helping us keep the site tidy!" +msgstr "" + +msgid "Thank you for making an annotation!" +msgstr "" + +msgid "Thank you for responding to this FOI request! Your response has been published below, and a link to your response has been emailed to " +msgstr "Thank you for responding to this ATI request! Your response has been published below, and a link to your response has been emailed to " + +msgid "Thank you for updating the status of the request '{{info_request_title}}'. There are some more requests below for you to classify." +msgstr "" + +msgid "Thank you for updating this request!" +msgstr "" + +msgid "Thank you for updating your profile photo" +msgstr "" + +msgid "Thank you! We'll look into what happened and try and fix it up." +msgstr "" + +msgid "Thanks for helping - your work will make it easier for everyone to find successful\\nresponses, and maybe even let us make league tables..." +msgstr "" + +msgid "Thanks very much - this will help others find useful stuff. We'll\\n also, if you need it, give advice on what to do next about your\\n requests." +msgstr "" + +msgid "Thanks very much for helping keep everything neat and organised.\\n We'll also, if you need it, give you advice on what to do next about each of your\\n requests." +msgstr "" + +msgid "That doesn't look like a valid email address. Please check you have typed it correctly." +msgstr "" + +msgid "The review has finished and overall:" +msgstr "" + +msgid "The Freedom of Information Act does not apply to" +msgstr "The Access to Information Act does not apply to" + +msgid "The accounts have been left as they previously were." +msgstr "" + +msgid "The authority do not have the information (maybe they say who does)" +msgstr "The agency do not have the information (maybe they say who does)" + +msgid "The authority only has a paper copy of the information." +msgstr "The agency only has a paper copy of the information." + +msgid "The authority say that they need a postal\\n address, not just an email, for it to be a valid FOI request" +msgstr "The agency say that they need a postal\\n address, not just an email, for it to be a valid ATI request" + +msgid "The authority would like to / has responded by post to this request." +msgstr "The agency would like to / has responded by post to this request." + +msgid "The classification of requests (e.g. to say whether they were successful or not) is done manually by users and administrators of the site, which means that they are subject to error." +msgstr "" + +msgid "The email that you, on behalf of {{public_body}}, sent to\\n{{user}} to reply to an {{law_used_short}}\\nrequest has not been delivered." +msgstr "" + +msgid "The error bars shown are 95% confidence intervals for the hypothesized underlying proportion (i.e. that which you would obtain by making an infinite number of requests through this site to that authority). In other words, the population being sampled is all the current and future requests to the authority through this site, rather than, say, all requests that have been made to the public body by any means." +msgstr "The error bars shown are 95% confidence intervals for the hypothesized underlying proportion (i.e. that which you would obtain by making an infinite number of requests through this site to that agency). In other words, the population being sampled is all the current and future requests to the agency through this site, rather than, say, all requests that have been made to the public body by any means." + +msgid "The page doesn't exist. Things you can try now:" +msgstr "" + +msgid "The percentages are calculated with respect to the total number of requests, which includes invalid requests; this is a known problem that will be fixed in a later release." +msgstr "" + +msgid "The public authority does not have the information requested" +msgstr "The public agency does not have the information requested" + +msgid "The public authority would like part of the request explained" +msgstr "The public agency would like part of the request explained" + +msgid "The public authority would like to / has responded by post" +msgstr "The public agency would like to / has responded by post" + +msgid "The request has been refused" +msgstr "" + +msgid "The request has been updated since you originally loaded this page. Please check for any new incoming messages below, and try again." +msgstr "" + +msgid "The request is waiting for clarification." +msgstr "" + +msgid "The request was partially successful." +msgstr "" + +msgid "The request was refused by" +msgstr "" + +msgid "The request was successful." +msgstr "" + +msgid "The request was refused by the public authority" +msgstr "The request was refused by the public agency" + +msgid "The request you have tried to view has been removed. There are\\nvarious reasons why we might have done this, sorry we can't be more specific here. Please contact us if you have any questions." +msgstr "" + +msgid "The requester has abandoned this request for some reason" +msgstr "" + +msgid "The response to your request has been delayed. You can say that,\\n by law, the authority should normally have responded\\n promptly and" +msgstr "The response to your request has been delayed. You can say that,\\n by law, the agency should normally have responded\\n promptly and" + +msgid "The response to your request is long overdue. You can say that, by\\n law, under all circumstances, the authority should have responded\\n by now" +msgstr "The response to your request is long overdue. You can say that, by\\n law, under all circumstances, the agency should have responded\\n by now" + +msgid "The search index is currently offline, so we can't show the Freedom of Information requests that have been made to this authority." +msgstr "The search index is currently offline, so we can't show the Access to Information requests that have been made to this agency." + +msgid "The search index is currently offline, so we can't show the Freedom of Information requests this person has made." +msgstr "The search index is currently offline, so we can't show the Access to Information requests this person has made." + +msgid "The {{site_name}} team." +msgstr "" + +msgid "Then you can cancel the alert." +msgstr "" + +msgid "Then you can cancel the alerts." +msgstr "" + +msgid "Then you can change your email address used on {{site_name}}" +msgstr "" + +msgid "Then you can change your password on {{site_name}}" +msgstr "" + +msgid "Then you can classify the FOI response you have got from " +msgstr "Then you can classify the ATI response you have got from " + +msgid "Then you can download a zip file of {{info_request_title}}." +msgstr "" + +msgid "Then you can log into the administrative interface" +msgstr "" + +msgid "Then you can play the request categorisation game." +msgstr "" + +msgid "Then you can report the request '{{title}}'" +msgstr "" + +msgid "Then you can send a message to " +msgstr "" + +msgid "Then you can sign in to {{site_name}}" +msgstr "" + +msgid "Then you can update the status of your request to " +msgstr "" + +msgid "Then you can upload an FOI response. " +msgstr "Then you can upload an ATI response. " + +msgid "Then you can write follow up message to " +msgstr "" + +msgid "Then you can write your reply to " +msgstr "" + +msgid "Then you will be following all new FOI requests." +msgstr "Then you will be following all new ATI requests." + +msgid "Then you will be notified whenever '{{user_name}}' requests something or gets a response." +msgstr "" + +msgid "Then you will be notified whenever a new request or response matches your search." +msgstr "" + +msgid "Then you will be notified whenever an FOI request succeeds." +msgstr "Then you will be notified whenever an ATI request succeeds." + +msgid "Then you will be notified whenever someone requests something or gets a response from '{{public_body_name}}'." +msgstr "" + +msgid "Then you will be updated whenever the request '{{request_title}}' is updated." +msgstr "" + +msgid "Then you'll be allowed to send FOI requests." +msgstr "Then you'll be allowed to send ATI requests." + +msgid "Then your FOI request to {{public_body_name}} will be sent." +msgstr "Then your ATI request to {{public_body_name}} will be sent." + +msgid "Then your annotation to {{info_request_title}} will be posted." +msgstr "" + +msgid "There are {{count}} new annotations on your {{info_request}} request. Follow this link to see what they wrote." +msgstr "" + +msgid "There is more than one person who uses this site and has this name.\\n One of them is shown below, you may mean a different one:" +msgstr "" + +msgid "There is a limit on the number of requests you can make in a day, because we don’t want public authorities to be bombarded with large numbers of inappropriate requests. If you feel you have a good reason to ask for the limit to be lifted in your case, please get in touch." +msgstr "There is a limit on the number of requests you can make in a day, because we don’t want public agencies to be bombarded with large numbers of inappropriate requests. If you feel you have a good reason to ask for the limit to be lifted in your case, please get in touch." + +msgid "There is {{count}} person following this request" +msgid_plural "There are {{count}} people following this request" +msgstr[0] "" +msgstr[1] "" + +msgid "There was a delivery error or similar, which needs fixing by the {{site_name}} team." +msgstr "" + +msgid "There was an error with the words you entered, please try again." +msgstr "" + +msgid "There was no data calculated for this graph yet." +msgstr "" + +msgid "There were no requests matching your query." +msgstr "" + +msgid "There were no results matching your query." +msgstr "" + +msgid "These graphs were partly inspired by some statistics that Mark Goodge produced for WhatDoTheyKnow, so thanks are due to him." +msgstr "" + +msgid "They are going to reply by post" +msgstr "" + +msgid "They do not have the information (maybe they say who does)" +msgstr "" + +msgid "They have been given the following explanation:" +msgstr "" + +msgid "They have not replied to your {{law_used_short}} request {{title}} promptly, as normally required by law" +msgstr "" + +msgid "They have not replied to your {{law_used_short}} request {{title}}, \\nas required by law" +msgstr "" + +msgid "Things to do with this request" +msgstr "" + +msgid "Things you're following" +msgstr "" + +msgid "This authority no longer exists, so you cannot make a request to it." +msgstr "This agency no longer exists, so you cannot make a request to it." + +msgid "This covers a very wide spectrum of information about the state of\\n the natural and built environment, such as:" +msgstr "" + +msgid "This external request has been hidden" +msgstr "" + +msgid "This is a plain-text version of the Freedom of Information request \"{{request_title}}\". The latest, full version is available online at {{full_url}}" +msgstr "This is a plain-text version of the Access to Information request \"{{request_title}}\". The latest, full version is available online at {{full_url}}" + +msgid "This is an HTML version of an attachment to the Freedom of Information request" +msgstr "This is an HTML version of an attachment to the Access to Information request" + +msgid "This is because {{title}} is an old request that has been\\nmarked to no longer receive responses." +msgstr "" + +msgid "This is the first version." +msgstr "" + +msgid "This is your own request, so you will be automatically emailed when new responses arrive." +msgstr "" + +msgid "This message has been hidden." +msgstr "" + +msgid "This message has been hidden. There are various reasons why we might have done this, sorry we can't be more specific here." +msgstr "" + +msgid "This message has prominence 'hidden'. You can only see it because you are logged in as a super user." +msgstr "" + +msgid "This message has prominence 'hidden'. {{reason}} You can only see it because you are logged in as a super user." +msgstr "" + +msgid "This message is hidden, so that only you, the requester, can see it. Please contact us if you are not sure why." +msgstr "" + +msgid "This message is hidden, so that only you, the requester, can see it. {{reason}}" +msgstr "" + +msgid "This page of public body statistics is currently experimental, so there are some caveats that should be borne in mind:" +msgstr "" + +msgid "This particular request is finished:" +msgstr "" + +msgid "This person has made no Freedom of Information requests using this site." +msgstr "This person has made no Access to Information requests using this site." + +msgid "This person's annotations" +msgstr "" + +msgid "This person's {{count}} Freedom of Information request" +msgid_plural "This person's {{count}} Freedom of Information requests" +msgstr[0] "This person's {{count}} Access to Information request" +msgstr[1] "This person's {{count}} Access to Information requests" + +msgid "This person's {{count}} annotation" +msgid_plural "This person's {{count}} annotations" +msgstr[0] "" +msgstr[1] "" + +msgid "This request requires administrator attention" +msgstr "" + +msgid "This request has already been reported for administrator attention" +msgstr "" + +msgid "This request has an unknown status." +msgstr "" + +msgid "This request has been hidden from the site, because an administrator considers it not to be an FOI request" +msgstr "This request has been hidden from the site, because an administrator considers it not to be an ATI request" + +msgid "This request has been hidden from the site, because an administrator considers it vexatious" +msgstr "" + +msgid "This request has been reported as needing administrator attention (perhaps because it is vexatious, or a request for personal information)" +msgstr "" + +msgid "This request has been withdrawn by the person who made it.\\n There may be an explanation in the correspondence below." +msgstr "" + +msgid "This request has been marked for review by the site administrators, who have not hidden it at this time. If you believe it should be hidden, please contact us." +msgstr "" + +msgid "This request has been reported for administrator attention" +msgstr "" + +msgid "This request has been set by an administrator to \"allow new responses from nobody\"" +msgstr "" + +msgid "This request has had an unusual response, and requires attention from the {{site_name}} team." +msgstr "" + +msgid "This request has prominence 'hidden'. You can only see it because you are logged\\n in as a super user." +msgstr "" + +msgid "This request is hidden, so that only you the requester can see it. Please\\n contact us if you are not sure why." +msgstr "" + +msgid "This request is still in progress:" +msgstr "" + +msgid "This request requires administrator attention" +msgstr "" + +msgid "This request was not made via {{site_name}}" +msgstr "" + +msgid "This table shows the technical details of the internal events that happened\\nto this request on {{site_name}}. This could be used to generate information about\\nthe speed with which authorities respond to requests, the number of requests\\nwhich require a postal response and much more." +msgstr "This table shows the technical details of the internal events that happened\\nto this request on {{site_name}}. This could be used to generate information about\\nthe speed with which agencies respond to requests, the number of requests\\nwhich require a postal response and much more." + +msgid "This user has been banned from {{site_name}} " +msgstr "" + +msgid "This was not possible because there is already an account using \\nthe email address {{email}}." +msgstr "" + +msgid "To cancel these alerts" +msgstr "" + +msgid "To cancel this alert" +msgstr "" + +msgid "To carry on, you need to sign in or make an account. Unfortunately, there\\nwas a technical problem trying to do this." +msgstr "" + +msgid "To change your email address used on {{site_name}}" +msgstr "" + +msgid "To classify the response to this FOI request" +msgstr "To classify the response to this ATI request" + +msgid "To do that please send a private email to " +msgstr "" + +msgid "To do this, first click on the link below." +msgstr "" + +msgid "To download the zip file" +msgstr "" + +msgid "To follow all successful requests" +msgstr "" + +msgid "To follow new requests" +msgstr "" + +msgid "To follow requests and responses matching your search" +msgstr "" + +msgid "To follow requests by '{{user_name}}'" +msgstr "" + +msgid "To follow requests made using {{site_name}} to the public authority '{{public_body_name}}'" +msgstr "To follow requests made using {{site_name}} to the public agency '{{public_body_name}}'" + +msgid "To follow the request '{{request_title}}'" +msgstr "" + +msgid "To help us keep the site tidy, someone else has updated the status of the \\n{{law_used_full}} request {{title}} that you made to {{public_body}}, to \"{{display_status}}\" If you disagree with their categorisation, please update the status again yourself to what you believe to be more accurate." +msgstr "" + +msgid "To let everyone know, follow this link and then select the appropriate box." +msgstr "" + +msgid "To log into the administrative interface" +msgstr "" + +msgid "To play the request categorisation game" +msgstr "" + +msgid "To post your annotation" +msgstr "" + +msgid "To reply to " +msgstr "" + +msgid "To report this request" +msgstr "" + +msgid "To send a follow up message to " +msgstr "" + +msgid "To send a message to " +msgstr "" + +msgid "To send your FOI request" +msgstr "To send your ATI request" + +msgid "To update the status of this FOI request" +msgstr "To update the status of this ATI request" + +msgid "To upload a response, you must be logged in using an email address from " +msgstr "" + +msgid "To use the advanced search, combine phrases and labels as described in the search tips below." +msgstr "" + +msgid "To view the email address that we use to send FOI requests to {{public_body_name}}, please enter these words." +msgstr "To view the email address that we use to send ATI requests to {{public_body_name}}, please enter these words." + +msgid "To view the response, click on the link below." +msgstr "" + +msgid "To {{public_body_link_absolute}}" +msgstr "" + +msgid "To:" +msgstr "" + +msgid "Today" +msgstr "" + +msgid "Too many requests" +msgstr "" + +msgid "Top search results:" +msgstr "" + +msgid "Track thing" +msgstr "" + +msgid "Track this person" +msgstr "" + +msgid "Track this search" +msgstr "" + +msgid "TrackThing|Track medium" +msgstr "" + +msgid "TrackThing|Track query" +msgstr "" + +msgid "TrackThing|Track type" +msgstr "" + +msgid "Turn off email alerts" +msgstr "" + +msgid "Tweet this request" +msgstr "" + +msgid "Type 01/01/2008..14/01/2008 to only show things that happened in the first two weeks of January." +msgstr "" + +msgid "URL name can't be blank" +msgstr "" + +msgid "Unable to change email address on {{site_name}}" +msgstr "" + +msgid "Unable to send a reply to {{username}}" +msgstr "" + +msgid "Unable to send follow up message to {{username}}" +msgstr "" + +msgid "Unexpected search result type" +msgstr "" + +msgid "Unexpected search result type " +msgstr "" + +msgid "Unfortunately we don't know the FOI\\nemail address for that authority, so we can't validate this.\\nPlease contact us to sort it out." +msgstr "Unfortunately we don't know the ATI\\nemail address for that agency, so we can't validate this.\\nPlease contact us to sort it out." + +msgid "Unfortunately, we do not have a working {{info_request_law_used_full}}\\naddress for" +msgstr "" + +msgid "Unknown" +msgstr "" + +msgid "Unsubscribe" +msgstr "" + +msgid "Unusual response." +msgstr "" + +msgid "Update the status of this request" +msgstr "" + +msgid "Update the status of your request to " +msgstr "" + +msgid "Upload FOI response" +msgstr "Upload ATI response" + +msgid "Use OR (in capital letters) where you don't mind which word, e.g. commons OR lords" +msgstr "" + +msgid "Use quotes when you want to find an exact phrase, e.g. \"Liverpool City Council\"" +msgstr "" + +msgid "User" +msgstr "" + +msgid "User info request sent alert" +msgstr "" + +msgid "User – {{name}}" +msgstr "" + +msgid "UserInfoRequestSentAlert|Alert type" +msgstr "" + +msgid "User|About me" +msgstr "" + +msgid "User|Admin level" +msgstr "" + +msgid "User|Ban text" +msgstr "" + +msgid "User|Email" +msgstr "" + +msgid "User|Email bounce message" +msgstr "" + +msgid "User|Email bounced at" +msgstr "" + +msgid "User|Email confirmed" +msgstr "" + +msgid "User|Hashed password" +msgstr "" + +msgid "User|Last daily track email" +msgstr "" + +msgid "User|Locale" +msgstr "" + +msgid "User|Name" +msgstr "" + +msgid "User|No limit" +msgstr "" + +msgid "User|Receive email alerts" +msgstr "" + +msgid "User|Salt" +msgstr "" + +msgid "User|Url name" +msgstr "" + +msgid "Version {{version}}" +msgstr "" + +msgid "View FOI email address" +msgstr "View ATI email address" + +msgid "View FOI email address for '{{public_body_name}}'" +msgstr "View ATI email address for '{{public_body_name}}'" + +msgid "View FOI email address for {{public_body_name}}" +msgstr "View ATI email address for {{public_body_name}}" + +msgid "View Freedom of Information requests made by {{user_name}}:" +msgstr "View Access to Information requests made by {{user_name}}:" + +msgid "View and search requests" +msgstr "" + +msgid "View authorities" +msgstr "View agencies" + +msgid "View email" +msgstr "" + +msgid "View requests" +msgstr "" + +msgid "Waiting clarification." +msgstr "" + +msgid "Waiting for an internal review by {{public_body_link}} of their handling of this request." +msgstr "" + +msgid "Waiting for the public authority to complete an internal review of their handling of the request" +msgstr "Waiting for the public agency to complete an internal review of their handling of the request" + +msgid "Waiting for the public authority to reply" +msgstr "Waiting for the public agency to reply" + +msgid "Was the response you got to your FOI request any good?" +msgstr "Was the response you got to your ATI request any good?" + +msgid "We consider it is not a valid FOI request, and have therefore hidden it from other users." +msgstr "We consider it is not a valid ATI request, and have therefore hidden it from other users." + +msgid "We consider it to be vexatious, and have therefore hidden it from other users." +msgstr "" + +msgid "We do not have a working request email address for this authority." +msgstr "We do not have a working request email address for this agency." + +msgid "We do not have a working {{law_used_full}} address for {{public_body_name}}." +msgstr "" + +msgid "We don't know whether the most recent response to this request contains\\n information or not\\n –\\n\tif you are {{user_link}} please sign in and let everyone know." +msgstr "" + +msgid "We will not reveal your email address to anybody unless you or\\n the law tell us to (details). " +msgstr "" + +msgid "We will not reveal your email address to anybody unless you\\nor the law tell us to." +msgstr "" + +msgid "We will not reveal your email addresses to anybody unless you\\nor the law tell us to." +msgstr "" + +msgid "We're waiting for" +msgstr "" + +msgid "We're waiting for someone to read" +msgstr "" + +msgid "We've sent an email to your new email address. You'll need to click the link in\\nit before your email address will be changed." +msgstr "" + +msgid "We've sent you an email, and you'll need to click the link in it before you can\\ncontinue." +msgstr "" + +msgid "We've sent you an email, click the link in it, then you can change your password." +msgstr "" + +msgid "What are you doing?" +msgstr "" + +msgid "What best describes the status of this request now?" +msgstr "" + +msgid "What information has been released?" +msgstr "" + +msgid "What information has been requested?" +msgstr "" + +msgid "When you get there, please update the status to say if the response \\ncontains any useful information." +msgstr "" + +msgid "When you receive the paper response, please help\\n others find out what it says:" +msgstr "" + +msgid "When you're done, come back here, reload this page and file your new request." +msgstr "" + +msgid "Which of these is happening?" +msgstr "" + +msgid "Who can I request information from?" +msgstr "" + +msgid "Withdrawn by the requester." +msgstr "" + +msgid "Wk" +msgstr "" + +msgid "Would you like to see a website like this in your country?" +msgstr "" + +msgid "Write a reply" +msgstr "" + +msgid "Write a reply to " +msgstr "" + +msgid "Write your FOI follow up message to " +msgstr "Write your ATI follow up message to " + +msgid "Write your request in simple, precise language." +msgstr "" + +msgid "You" +msgstr "" + +msgid "You are already following new requests" +msgstr "" + +msgid "You are already following requests to {{public_body_name}}" +msgstr "" + +msgid "You are already following things matching this search" +msgstr "" + +msgid "You are already following this person" +msgstr "" + +msgid "You are already following this request" +msgstr "" + +msgid "You are already following updates about {{track_description}}" +msgstr "" + +msgid "You are currently receiving notification of new activity on your wall by email." +msgstr "" + +msgid "You are following all new successful responses" +msgstr "" + +msgid "You are no longer following {{track_description}}." +msgstr "" + +msgid "You are now following updates about {{track_description}}" +msgstr "" + +msgid "You can complain by" +msgstr "" + +msgid "You can change the requests and users you are following on your profile page." +msgstr "" + +msgid "You can get this page in computer-readable format as part of the main JSON\\npage for the request. See the API documentation." +msgstr "" + +msgid "You can only request information about the environment from this authority." +msgstr "You can only request information about the environment from this agency." + +msgid "You have a new response to the {{law_used_full}} request " +msgstr "" + +msgid "You have found a bug. Please contact us to tell us about the problem" +msgstr "" + +msgid "You have hit the rate limit on new requests. Users are ordinarily limited to {{max_requests_per_user_per_day}} requests in any rolling 24-hour period. You will be able to make another request in {{can_make_another_request}}." +msgstr "" + +msgid "You have made no Freedom of Information requests using this site." +msgstr "You have made no Access to Information requests using this site." + +msgid "You have now changed the text about you on your profile." +msgstr "" + +msgid "You have now changed your email address used on {{site_name}}" +msgstr "" + +msgid "You just tried to sign up to {{site_name}}, when you\\nalready have an account. Your name and password have been\\nleft as they previously were.\\n\\nPlease click on the link below." +msgstr "" + +msgid "You know what caused the error, and can suggest a solution, such as a working email address." +msgstr "" + +msgid "You may include attachments. If you would like to attach a\\n file too large for email, use the form below." +msgstr "" + +msgid "You may be able to find\\n one on their website, or by phoning them up and asking. If you manage\\n to find one, then please send it to us." +msgstr "" + +msgid "You may be able to find\\none on their website, or by phoning them up and asking. If you manage\\nto find one, then please send it to us." +msgstr "" + +msgid "You need to be logged in to change the text about you on your profile." +msgstr "" + +msgid "You need to be logged in to change your profile photo." +msgstr "" + +msgid "You need to be logged in to clear your profile photo." +msgstr "" + +msgid "You need to be logged in to edit your profile." +msgstr "" + +msgid "You need to be logged in to report a request for administrator attention" +msgstr "" + +msgid "You previously submitted that exact follow up message for this request." +msgstr "" + +msgid "You should have received a copy of the request by email, and you can respond\\n by simply replying to that email. For your convenience, here is the address:" +msgstr "" + +msgid "You want to give your postal address to the authority in private." +msgstr "You want to give your postal address to the agency in private." + +msgid "You will be unable to make new requests, send follow ups, add annotations or\\nsend messages to other users. You may continue to view other requests, and set\\nup\\nemail alerts." +msgstr "" + +msgid "You will no longer be emailed updates for those alerts" +msgstr "" + +msgid "You will now be emailed updates about {{track_description}}. Prefer not to receive emails?" +msgstr "" + +msgid "You will only get an answer to your request if you follow up\\nwith the clarification." +msgstr "" + +msgid "You will still be able to view it while logged in to the site. Please reply to this email if you would like to discuss this decision further." +msgstr "" + +msgid "You're in. Continue sending your request" +msgstr "" + +msgid "You're long overdue a response to your FOI request - " +msgstr "You're long overdue a response to your ATI request - " + +msgid "You're not following anything." +msgstr "" + +msgid "You've now cleared your profile photo" +msgstr "" + +msgid "Your name will appear publicly\\n (why?)\\n on this website and in search engines. If you\\n are thinking of using a pseudonym, please\\n read this first." +msgstr "" + +msgid "Your annotations" +msgstr "" + +msgid "Your details, including your email address, have not been given to anyone." +msgstr "" + +msgid "Your e-mail:" +msgstr "" + +msgid "Your follow up has not been sent because this request has been stopped to prevent spam. Please contact us if you really want to send a follow up message." +msgstr "" + +msgid "Your follow up message has been sent on its way." +msgstr "" + +msgid "Your internal review request has been sent on its way." +msgstr "" + +msgid "Your message has been sent. Thank you for getting in touch! We'll get back to you soon." +msgstr "" + +msgid "Your message to {{recipient_user_name}} has been sent" +msgstr "" + +msgid "Your message to {{recipient_user_name}} has been sent!" +msgstr "" + +msgid "Your message will appear in search engines" +msgstr "" + +msgid "Your name and annotation will appear in search engines." +msgstr "" + +msgid "Your name, request and any responses will appear in search engines\\n (details)." +msgstr "" + +msgid "Your name:" +msgstr "" + +msgid "Your original message is attached." +msgstr "" + +msgid "Your password has been changed." +msgstr "" + +msgid "Your password:" +msgstr "" + +msgid "Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." +msgstr "" + +msgid "Your request '{{request}}' at {{url}} has been reviewed by moderators." +msgstr "" + +msgid "Your request on {{site_name}} hidden" +msgstr "" + +msgid "Your request was called {{info_request}}. Letting everyone know whether you got the information will help us keep tabs on" +msgstr "" + +msgid "Your request:" +msgstr "" + +msgid "Your response to an FOI request was not delivered" +msgstr "Your response to an ATI request was not delivered" + +msgid "Your response will appear on the Internet, read why and answers to other questions." +msgstr "" + +msgid "Your thoughts on what the {{site_name}} administrators should do about the request." +msgstr "" + +msgid "Your {{count}} Freedom of Information request" +msgid_plural "Your {{count}} Freedom of Information requests" +msgstr[0] "Your {{count}} Access to Information request" +msgstr[1] "Your {{count}} Access to Information requests" + +msgid "Your {{count}} annotation" +msgid_plural "Your {{count}} annotations" +msgstr[0] "" +msgstr[1] "" + +msgid "Your {{site_name}} email alert" +msgstr "" + +msgid "Yours faithfully," +msgstr "" + +msgid "Yours sincerely," +msgstr "" + +msgid "Yours," +msgstr "" + +msgid "[FOI #{{request}} email]" +msgstr "[ATI #{{request}} email]" + +msgid "[{{public_body}} request email]" +msgstr "" + +msgid "[{{site_name}} contact email]" +msgstr "" + +msgid "\\n\\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]" +msgstr "" + +msgid "a one line summary of the information you are requesting, \\n\t\t\te.g." +msgstr "" + +msgid "admin" +msgstr "" + +msgid "alaveteli_foi:The software that runs {{site_name}}" +msgstr "alaveteli_foi:The software that runs {{site_name}}" + +msgid "all requests" +msgstr "" + +msgid "also called {{public_body_short_name}}" +msgstr "" + +msgid "an anonymous user" +msgstr "" + +msgid "and" +msgstr "" + +msgid "and update the status accordingly. Perhaps you might like to help out by doing that?" +msgstr "" + +msgid "and update the status." +msgstr "" + +msgid "and we'll suggest what to do next" +msgstr "" + +msgid "any new requests" +msgstr "" + +msgid "any successful requests" +msgstr "" + +msgid "anything" +msgstr "" + +msgid "are long overdue." +msgstr "" + +msgid "at" +msgstr "" + +msgid "authorities" +msgstr "agencies" + +msgid "awaiting a response" +msgstr "" + +msgid "beginning with ‘{{first_letter}}’" +msgstr "" + +msgid "between two dates" +msgstr "" + +msgid "but followupable" +msgstr "" + +msgid "by" +msgstr "" + +msgid "by {{date}}" +msgstr "" + +msgid "by {{public_body_name}} to {{info_request_user}} on {{date}}." +msgstr "" + +msgid "by {{user_link_absolute}}" +msgstr "" + +msgid "comments" +msgstr "" + +msgid "containing your postal address, and asking them to reply to this request.\\n Or you could phone them." +msgstr "" + +msgid "details" +msgstr "" + +msgid "display_status only works for incoming and outgoing messages right now" +msgstr "" + +msgid "during term time" +msgstr "" + +msgid "edit text about you" +msgstr "" + +msgid "even during holidays" +msgstr "" + +msgid "everything" +msgstr "" + +msgid "external" +msgstr "" + +msgid "has reported an" +msgstr "" + +msgid "have delayed." +msgstr "" + +msgid "hide quoted sections" +msgstr "" + +msgid "in term time" +msgstr "" + +msgid "in the category ‘{{category_name}}’" +msgstr "" + +msgid "internal error" +msgstr "" + +msgid "internal reviews" +msgstr "" + +msgid "is waiting for your clarification." +msgstr "" + +msgid "just to see how it works" +msgstr "" + +msgid "left an annotation" +msgstr "" + +msgid "made." +msgstr "" + +msgid "matching the tag ‘{{tag_name}}’" +msgstr "" + +msgid "messages from authorities" +msgstr "messages from agencies" + +msgid "messages from users" +msgstr "" + +msgid "move..." +msgstr "" + +msgid "no later than" +msgstr "" + +msgid "no longer exists. If you are trying to make\\n From the request page, try replying to a particular message, rather than sending\\n a general followup. If you need to make a general followup, and know\\n an email which will go to the right place, please send it to us." +msgstr "" + +msgid "normally" +msgstr "" + +msgid "not requestable due to: {{reason}}" +msgstr "" + +msgid "please sign in as " +msgstr "" + +msgid "requesting an internal review" +msgstr "" + +msgid "requests" +msgstr "" + +msgid "requests which are {{list_of_statuses}}" +msgstr "" + +msgid "response as needing administrator attention. Take a look, and reply to this\\nemail to let them know what you are going to do about it." +msgstr "" + +msgid "send a follow up message" +msgstr "" + +msgid "sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "set to blank (empty string) if can't find an address; these emails are public as anyone can view with a CAPTCHA" +msgstr "" + +msgid "show quoted sections" +msgstr "" + +msgid "sign in" +msgstr "" + +msgid "simple_date_format" +msgstr "" + +msgid "successful" +msgstr "" + +msgid "successful requests" +msgstr "" + +msgid "that you made to" +msgstr "" + +msgid "the main FOI contact address for {{public_body}}" +msgstr "the main ATI contact address for {{public_body}}" + +#. This phrase completes the following sentences: +#. Request an internal review from... +#. Send a public follow up message to... +#. Send a public reply to... +#. Don't want to address your message to... ? +msgid "the main FOI contact at {{public_body}}" +msgstr "the main ATI contact at {{public_body}}" + +msgid "the requester" +msgstr "" + +msgid "the {{site_name}} team" +msgstr "" + +msgid "to read" +msgstr "" + +msgid "to send a follow up message." +msgstr "" + +msgid "to {{public_body}}" +msgstr "" + +msgid "unknown reason " +msgstr "" + +msgid "unknown status " +msgstr "" + +msgid "unresolved requests" +msgstr "" + +msgid "unsubscribe" +msgstr "" + +msgid "unsubscribe all" +msgstr "" + +msgid "unsuccessful" +msgstr "" + +msgid "unsuccessful requests" +msgstr "" + +msgid "useful information." +msgstr "" + +msgid "users" +msgstr "" + +msgid "what's that?" +msgstr "" + +msgid "{{count}} FOI requests found" +msgstr "{{count}} ATI requests found" + +msgid "{{count}} Freedom of Information request to {{public_body_name}}" +msgid_plural "{{count}} Freedom of Information requests to {{public_body_name}}" +msgstr[0] "{{count}} Access to Information request to {{public_body_name}}" +msgstr[1] "{{count}} Access to Information requests to {{public_body_name}}" + +msgid "{{count}} person is following this authority" +msgid_plural "{{count}} people are following this authority" +msgstr[0] "{{count}} person is following this agency" +msgstr[1] "{{count}} people are following this agency" + +msgid "{{count}} request" +msgid_plural "{{count}} requests" +msgstr[0] "" +msgstr[1] "" + +msgid "{{count}} request made." +msgid_plural "{{count}} requests made." +msgstr[0] "" +msgstr[1] "" + +msgid "{{existing_request_user}} already\\n created the same request on {{date}}. You can either view the existing request,\\n or edit the details below to make a new but similar request." +msgstr "" + +msgid "{{info_request_user_name}} only:" +msgstr "" + +msgid "{{law_used_full}} request - {{title}}" +msgstr "" + +msgid "{{law_used}} requests at {{public_body}}" +msgstr "" + +msgid "{{length_of_time}} ago" +msgstr "" + +msgid "{{list_of_things}} matching text '{{search_query}}'" +msgstr "" + +msgid "{{number_of_comments}} comments" +msgstr "" + +msgid "{{public_body_link}} answered a request about" +msgstr "" + +msgid "{{public_body_link}} was sent a request about" +msgstr "" + +msgid "{{public_body_name}} only:" +msgstr "" + +msgid "{{public_body}} has asked you to explain part of your {{law_used}} request." +msgstr "" + +msgid "{{public_body}} sent a response to {{user_name}}" +msgstr "" + +msgid "{{reason}}, please sign in or make a new account." +msgstr "" + +msgid "{{search_results}} matching '{{query}}'" +msgstr "" + +msgid "{{site_name}} blog and tweets" +msgstr "" + +msgid "{{site_name}} covers requests to {{number_of_authorities}} authorities, including:" +msgstr "{{site_name}} covers requests to {{number_of_authorities}} agencies, including:" + +msgid "{{site_name}} sends new requests to {{request_email}} for this authority." +msgstr "{{site_name}} sends new requests to {{request_email}} for this agency." + +msgid "{{site_name}} users have made {{number_of_requests}} requests, including:" +msgstr "" + +msgid "{{thing_changed}} was changed from {{from_value}} to {{to_value}}" +msgstr "" + +msgid "{{title}} - a Freedom of Information request to {{public_body}}" +msgstr "{{title}} - a Access to Information request to {{public_body}}" + +msgid "{{user_name}} (Account suspended)" +msgstr "" + +msgid "{{user_name}} - Freedom of Information requests" +msgstr "{{user_name}} - Access to Information requests" + +msgid "{{user_name}} - user profile" +msgstr "" + +msgid "{{user_name}} added an annotation" +msgstr "" + +msgid "{{user_name}} has annotated your {{law_used_short}} \\nrequest. Follow this link to see what they wrote." +msgstr "" + +msgid "{{user_name}} has used {{site_name}} to send you the message below." +msgstr "" + +msgid "{{user_name}} sent a follow up message to {{public_body}}" +msgstr "" + +msgid "{{user_name}} sent a request to {{public_body}}" +msgstr "" + +msgid "{{username}} left an annotation:" +msgstr "" + +msgid "{{user}} ({{user_admin_link}}) made this {{law_used_full}} request (admin) to {{public_body_link}} (admin)" +msgstr "" + +msgid "{{user}} made this {{law_used_full}} request" +msgstr "" -- cgit v1.2.3 From c0fe50de0c5ba3d5af70ee677e37e553f5a006fa Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 9 Dec 2013 12:43:15 +0000 Subject: Add the newest Alaveteli sites. --- lib/world_foi_websites.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/world_foi_websites.rb b/lib/world_foi_websites.rb index c3f3655df..50976c897 100644 --- a/lib/world_foi_websites.rb +++ b/lib/world_foi_websites.rb @@ -53,7 +53,20 @@ class WorldFOIWebsites {:name => "Informace pro Vsechny", :country_name => "Česká republika", :country_iso_code => "CZ", - :url => "http://www.infoprovsechny.cz"} + :url => "http://www.infoprovsechny.cz"}, + {:name => "¿Qué Sabés?", + :country_name => "Uruguay", + :country_iso_code => "UY", + :url => "http://www.quesabes.org/"}, + {:name => "Nu Vă Supărați", + :country_name => "România", + :country_iso_code => "RO", + :url => "http://nuvasuparati.info/"}, + {:name => "Marsoum41", + :country_name => "تونس", + :country_iso_code => "TN", + :url => "http://www.marsoum41.org"} + ] return world_foi_websites end -- cgit v1.2.3 From 5e2b5a9c8c7f96fe2455534e9b404a692b698753 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 9 Dec 2013 17:57:21 +0000 Subject: Latest translations from Transifex --- locale/aln/app.po | 2 +- locale/ar/app.po | 2 +- locale/bg/app.po | 2 +- locale/bs/app.po | 2 +- locale/ca/app.po | 2 +- locale/cs/app.po | 2 +- locale/cy/app.po | 700 +++++----- locale/de/app.po | 2 +- locale/en_IE/app.po | 2 +- locale/es/app.po | 2 +- locale/eu/app.po | 2 +- locale/fi/app.po | 2 +- locale/fr/app.po | 75 +- locale/fr_CA/app.po | 2 +- locale/gl/app.po | 2 +- locale/he_IL/app.po | 2 +- locale/hr/app.po | 2 +- locale/hr_HR/app.po | 2 +- locale/hu_HU/app.po | 2 +- locale/id/app.po | 2 +- locale/it/app.po | 2 +- locale/mk_MK/app.po | 3536 ++++++++++++++++++++++++++++++++++++++++++++++++ locale/nb_NO/app.po | 2 +- locale/nl/app.po | 2 +- locale/pl/app.po | 2 +- locale/pt_BR/app.po | 2 +- locale/pt_PT/app.po | 2 +- locale/ro_RO/app.po | 2 +- locale/sl/app.po | 2 +- locale/sq/app.po | 2 +- locale/sr@latin/app.po | 2 +- locale/sv/app.po | 2 +- locale/sw_KE/app.po | 3510 +++++++++++++++++++++++++++++++++++++++++++++++ locale/tr/app.po | 2 +- locale/uk/app.po | 2 +- locale/vi/app.po | 2 +- locale/zh_HK/app.po | 3499 +++++++++++++++++++++++++++++++++++++++++++++++ 37 files changed, 10965 insertions(+), 419 deletions(-) create mode 100644 locale/mk_MK/app.po create mode 100644 locale/sw_KE/app.po create mode 100644 locale/zh_HK/app.po diff --git a/locale/aln/app.po b/locale/aln/app.po index 1acd58a2a..64a58424c 100644 --- a/locale/aln/app.po +++ b/locale/aln/app.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Albanian Gheg (http://www.transifex.com/projects/p/alaveteli/language/aln/)\n" "Language: aln\n" diff --git a/locale/ar/app.po b/locale/ar/app.po index 6b0725bcb..ad5adf8e8 100644 --- a/locale/ar/app.po +++ b/locale/ar/app.po @@ -15,7 +15,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Arabic (http://www.transifex.com/projects/p/alaveteli/language/ar/)\n" "Language: ar\n" diff --git a/locale/bg/app.po b/locale/bg/app.po index f42e6e10c..5779fe21d 100644 --- a/locale/bg/app.po +++ b/locale/bg/app.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 14:16+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: Valentin Laskov \n" "Language-Team: Bulgarian (http://www.transifex.com/projects/p/alaveteli/language/bg/)\n" "Language: bg\n" diff --git a/locale/bs/app.po b/locale/bs/app.po index 0a4aeb082..014331a5f 100644 --- a/locale/bs/app.po +++ b/locale/bs/app.po @@ -14,7 +14,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Bosnian (http://www.transifex.com/projects/p/alaveteli/language/bs/)\n" "Language: bs\n" diff --git a/locale/ca/app.po b/locale/ca/app.po index 9937760da..2b37d5b21 100644 --- a/locale/ca/app.po +++ b/locale/ca/app.po @@ -13,7 +13,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Catalan (http://www.transifex.com/projects/p/alaveteli/language/ca/)\n" "Language: ca\n" diff --git a/locale/cs/app.po b/locale/cs/app.po index 404a4b07f..74d4e4544 100644 --- a/locale/cs/app.po +++ b/locale/cs/app.po @@ -20,7 +20,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Czech (http://www.transifex.com/projects/p/alaveteli/language/cs/)\n" "Language: cs\n" diff --git a/locale/cy/app.po b/locale/cy/app.po index fe51e253d..db3a085f6 100644 --- a/locale/cy/app.po +++ b/locale/cy/app.po @@ -20,7 +20,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-13 23:56+0000\n" +"PO-Revision-Date: 2013-12-05 23:40+0000\n" "Last-Translator: Hywel \n" "Language-Team: Welsh (http://www.transifex.com/projects/p/alaveteli/language/cy/)\n" "Language: cy\n" @@ -60,7 +60,7 @@ msgid " Advise on how to best clarify the request." msgstr " Rhoi cyngor ar sut orau i egluro y cais." msgid " Ideas on what other documents to request which the authority may hold. " -msgstr " Syniadau ar ba ddogfennau eraill i ofyn amdanynt y gall yr awdurdod fod yn eu cadw. " +msgstr " Syniadau ar ba ddogfennau eraill i ofyn amdanynt y gall yr awdurdod fod yn eu cadw. " msgid " If you know the address to use, then please send it to us.\\n You may be able to find the address on their website, or by phoning them up and asking." msgstr " Os ydych yn gwybod pa gyfeiriad i'w ddefnyddio, yna os gwelwch yn dda anfonwch ef atom. Efallai y byddwch yn gallu dod o hyd i'r cyfeiriad ar eu gwefan, neu drwy eu ffonio nhw a gofyn." @@ -150,13 +150,13 @@ msgid "

    Thank you! Here are some ideas on what to do next:

    \\n < msgstr "

    Diolch yn fawr! Dyma rai syniadau ar beth i'w wneud nesaf:

    " msgid "

    Thank you! Hope you don't have to wait much longer.

    By law, you should have got a response promptly, and normally before the end of {{date_response_required_by}}.

    " -msgstr "

    Diolch yn fawr! Gobeithio na fydd yn rhaid i chi aros lawer yn hwy.

    Yn ôl y gyfraith, dylech fod wedi cael ymateb yn ddi-oed, ac fel arfer cyn diwedd {{date_response_required_by}}.

    " +msgstr "

    Diolch yn fawr! Gobeithio na fydd yn rhaid i chi aros lawer yn hwy.

    Yn ôl y gyfraith, dylech fod wedi cael ymateb yn ddi-oed, ac fel arfer cyn diwedd {{date_response_required_by}}.

    " msgid "

    Thank you! Hopefully your wait isn't too long.

    By law, you should get a response promptly, and normally before the end of \\n{{date_response_required_by}}.

    " msgstr "

    Diolch yn fawr! Gobeithio na fydd rhaid i chi aros yn rhy hir. Yn ôl y gyfraith, dylech gael ymateb yn brydlon, ac fel arfer cyn diwedd {{date_response_required_by}}.

    " msgid "

    Thank you! Hopefully your wait isn't too long.

    You should get a response within {{late_number_of_days}} days, or be told if it will take longer (details).

    " -msgstr "

    Diolch yn fawr! Gobeithio na fydd yn rhaid i chi aros yn rhy hir.

    Dylech gael ymateb o fewn {{late_number_of_days}} diwrnod, neu yn cael gwybod os bydd yn cymryd mwy o amser (manylion).

    " +msgstr "

    Diolch yn fawr! Gobeithio na fydd yn rhaid i chi aros yn rhy hir.

    Dylech gael ymateb o fewn {{late_number_of_days}} diwrnod, neu yn cael gwybod os bydd yn cymryd mwy o amser (manylion).

    " msgid "

    Thank you! Your request is long overdue, by more than {{very_late_number_of_days}} working days. Most requests should be answered within {{late_number_of_days}} working days. You might like to complain about this, see below.

    " msgstr "

    Diolch yn fawr! Mae eich cais yn ddyledus ers tro, gan ei fod fwy na {{very_late_number_of_days}} diwrnod gwaith yn hwyr. Dylai'r rhan fwyaf o geisiadau gael eu hateb o fewn {{late_number_of_days}} diwrnod gwaith. Efallai yr hoffech chi gwyno am hyn, gweler isod.

    " @@ -192,7 +192,7 @@ msgid "

    Your request contains a postcode. Unless it directly msgstr "

    Mae'ch cais yn cynnwys cod post. Oni bai ei fod o'n ymwneud yn uniongyrchol â'r cais, dilewch unrhyw gyfeiriad os gwelwch yn dda, oherwydd y bydd yn ymddangos yn gyhoeddus ar y Rhyngrwyd.

    " msgid "

    Your {{law_used_full}} request has been sent on its way!

    \\n

    We will email you when there is a response, or after {{late_number_of_days}} working days if the authority still hasn't\\n replied by then.

    \\n

    If you write about this request (for example in a forum or a blog) please link to this page, and add an\\n annotation below telling people about your writing.

    " -msgstr "

    Mae eich cais {{law_used_full}} wedi cael ei anfon!

    Byddwn yn eich e-bostio pan fydd ymateb , neu ar ôl {{late_number_of_days}} diwrnod gwaith os yw'r awdurdod yn dal heb ateb erbyn hynny.

    Os ydych yn ysgrifennu am y cais hwn (er enghraifft, mewn fforwm neu flog) rhowch ddolen at y dudalen hon, ac ychwanegwch anodiad isod i ddweud wrth bobl am eich ysgrifennu.

    " +msgstr "

    Mae eich cais {{law_used_full}} wedi cael ei anfon!

    Byddwn yn eich e-bostio pan fydd ymateb , neu ar ôl {{late_number_of_days}} diwrnod gwaith os yw'r awdurdod yn dal heb ateb erbyn hynny.

    Os ydych yn ysgrifennu am y cais hwn (er enghraifft, mewn fforwm neu flog) rhowch ddolen at y dudalen hon, ac ychwanegwch anodiad isod i ddweud wrth bobl am eich ysgrifennu.

    " msgid "

    {{site_name}} is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.

    {{read_only}}

    " msgstr "

    Mae {{site_name}} ar hyn o bryd yn cael ei chynnal a chadw. Gweld y ceisiadau presennol yn unig y gallwch. Ni allwch wneud rhai newydd, ychwanegu straenon dilynol neu anodiadau, neu newid y gronfa ddata mewn ffordd arall.

    {{read_only}}

    " @@ -213,7 +213,7 @@ msgid "request: to restrict to a specific request, msgstr "cais: i gyfyngu i gais penodol, teipiwch y teitl fel yn yr URL." msgid "requested_by:julian_todd to search requests made by Julian Todd, typing the name as in the URL." -msgstr "requested_by: julian_todd i chwilio ceisiadau a wnaed gan Julian Todd, deipio yr enw fel yn yr URL." +msgstr "requested_by: julian_todd i chwilio ceisiadau a wnaed gan Julian Todd, teipiwch yr enw fel yn yr URL." msgid "requested_from:home_office to search requests from the Home Office, typing the name as in the URL." msgstr "requested_from: home_office i chwilio ceisiadau gan y Swyddfa Gartref, teipiwch yr enw fel yn yr URL." @@ -237,7 +237,7 @@ msgid "Anything else, such as clarifying, prompting, thanking" msgstr "Unrhywbeth arall, megis egluro, annog, diolch" msgid "Caveat emptor! To use this data in an honourable way, you will need \\na good internal knowledge of user behaviour on {{site_name}}. How, \\nwhy and by whom requests are categorised is not straightforward, and there will\\nbe user error and ambiguity. You will also need to understand FOI law, and the\\nway authorities use it. Plus you'll need to be an elite statistician. Please\\ncontact us with questions." -msgstr "Caveat Emptor! Er mwyn defnyddio'r data hwn mewn ffordd anrhydeddus, bydd angen i chi \\ n gwybodaeth fewnol dda o ymddygiad defnyddwyr ar {{site_name}}. Nid yw sut, pam a chan bwy y bydd ceisiadau yn cael eu categoreiddio yn syml, a bydd gwall defnyddiwr ac amwysedd. Bydd angen i chi hefyd ddeall cyfraith Rhyddid Gwybodaeth, a sut y mae awdurdodau yn ei defnyddio. Yn ogystal bydd angen i chi fod yn ystadegydd prin. Cysylltwch â ni gyda chwestiynau." +msgstr "Caveat Emptor! Er mwyn defnyddio'r data hwn mewn ffordd anrhydeddus, bydd angen gwybodaeth fewnol dda o ymddygiad defnyddwyr ar {{site_name}}. Nid yw sut, pam a chan bwy y bydd ceisiadau yn cael eu categoreiddio yn syml, a bydd gwallau defnyddiwr ac amwysedd. Bydd angen i chi hefyd ddeall cyfraith Rhyddid Gwybodaeth, a sut y mae awdurdodau yn ei defnyddio. Yn ogystal bydd angen i chi fod yn ystadegydd prin. Cysylltwch â ni gyda chwestiynau." msgid "Clarification has been requested" msgstr "Ceisiwyd eglurhad" @@ -249,19 +249,19 @@ msgid "Note: Because we're testing, requests are being sent to msgstr "Nodyn: Oherwydd ein bod yn profi, mae ceisiadau yn cael eu hanfon at {{email}} yn hytrach nag at yr awdurdod ei hun." msgid "Note: You're sending a message to yourself, presumably\\n to try out how it works." -msgstr "Nodyn: Rydych yn anfon neges i chi eich hun, yn ôl pob tebyg i roi cynnig ar sut mae'n gweithio." +msgstr "Nodyn: Rydych yn anfon neges i chi eich hun, yn ôl pob tebyg i roi cynnig ar sut mae'n gweithio." msgid "Note:\\n We will send an email to your new email address. Follow the\\n instructions in it to confirm changing your email." -msgstr "Nodyn: Byddwn yn anfon e-bost at eich cyfeiriad e-bost newydd. Dilynwch y cyfarwyddiadau ynddo i gadarnhau newid eich e-bost." +msgstr "Nodyn: Byddwn yn anfon e-bost at eich cyfeiriad e-bost newydd. Dilynwch y cyfarwyddiadau ynddo i gadarnhau newid eich e-bost." msgid "Privacy note: If you want to request private information about\\n yourself then click here." -msgstr "Nodyn Preifatrwydd: Os ydych am wneud cais am wybodaeth breifat amdanoch eich hun, yna cliciwch yma." +msgstr "Nodyn Preifatrwydd: Os ydych am wneud cais am wybodaeth breifat amdanoch eich hun, yna cliciwch yma." msgid "Privacy note: Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." -msgstr "Nodyn Preifatrwydd: Bydd eich llun yn cael ei ddangos yn gyhoeddus ar y Rhyngrwyd, lle bynnag y byddwch yn gwneud rhywbeth ar {{site_name}}." +msgstr "Nodyn Preifatrwydd: Bydd eich llun yn cael ei ddangos yn gyhoeddus ar y Rhyngrwyd, lle bynnag y byddwch yn gwneud rhywbeth ar {{site_name}}." msgid "Privacy warning: Your message, and any response\\n to it, will be displayed publicly on this website." -msgstr "Rhybudd preifatrwydd: Bydd eich neges, ac unrhyw ymateb iddo, yn cael ei harddangos yn gyhoeddus ar y wefan hon." +msgstr "Rhybudd preifatrwydd: Bydd eich neges, ac unrhyw ymateb iddo, yn cael ei harddangos yn gyhoeddus ar y wefan hon." msgid "Some of the information has been sent " msgstr "Anfonwyd rhan o'r wybodaeth" @@ -288,7 +288,7 @@ msgid "A full history of my FOI request and all correspondence is available on t msgstr "Mae hanes llawn fy nghais Rhyddid Gwybodaeth a'r holl ohebiaeth ar gael ar y Rhyngrwyd yn y cyfeiriad hwn: {{url}}" msgid "A new request, {{request_title}}, was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." -msgstr "Anfonwyd cais newydd, {{request_title}}, anfon i {{public_body_name}} gan {{info_request_user}} ar {{date}}." +msgstr "Anfonwyd cais newydd, {{request_title}}, anfon i {{public_body_name}} gan {{info_request_user}} ar {{date}}." msgid "A public authority" msgstr "Awdurdod cyhoeddus" @@ -297,7 +297,7 @@ msgid "A response will be sent by post" msgstr "Bydd ymateb yn cael ei anfon drwy'r post" msgid "A strange reponse, required attention by the {{site_name}} team" -msgstr "Ymateb rhyfedd, yr oedd angen sylw arno gan dîm {{site_name}} " +msgstr "Ymateb rhyfedd, yr oedd angen sylw arno gan dîm {{site_name}} " msgid "A vexatious request" msgstr "Cais blinderus" @@ -411,7 +411,7 @@ msgid "Ask for specific documents or information, this site is msgstr "Gofynnwch am ddogfennau neu wybodaeth benodol. Nid yw'r wefan hon yn addas ar gyfer ymholiadau cyffredinol." msgid "At the bottom of this page, write a reply to them trying to persuade them to scan it in\\n (more details)." -msgstr "Ar waelod y dudalen hon, ysgrifennwch ateb iddynt i geisio eu perswadio i'w sganio (mwy o fanylion)." +msgstr "Ar waelod y dudalen hon, ysgrifennwch ateb iddynt i geisio eu perswadio i'w sganio (mwy o fanylion)." msgid "Attachment (optional):" msgstr "Atodiad (dewisol)" @@ -444,7 +444,7 @@ msgid "By law, under all circumstances, {{public_body_link}} should have respond msgstr "Yn ôl y gyfraith, ym mhob amgylchiad, dylai {{public_body_link}} fod wedi ymateb erbyn hyn." msgid "By law, {{public_body_link}} should normally have responded promptly and" -msgstr "Yn ôl y gyfraith, dylai {{public_body_link}} fel arfer wedi ymateb yn brydlon a" +msgstr "Yn ôl y gyfraith, dylai {{public_body_link}} fel arfer wedi ymateb yn brydlon a" msgid "Calculated home page" msgstr "Hafan wedi ei chyfrifo" @@ -531,7 +531,7 @@ msgid "Clear photo" msgstr "Gwaredwch y ffoto" msgid "Click on the link below to send a message to {{public_body_name}} telling them to reply to your request. You might like to ask for an internal\\nreview, asking them to find out why response to the request has been so slow." -msgstr "Cliciwch ar y ddolen isod i anfon neges at {{public_body_name}} i ddweud wrthynt i ymateb i'ch cais. Efallai yr hoffech chi ofyn am adolygiad mewnol, gan ofyn iddynt i gael gwybod pam ymateb i'r cais wedi bod mor araf." +msgstr "Cliciwch ar y ddolen isod i anfon neges at {{public_body_name}} i ddweud wrthynt i ymateb i'ch cais. Efallai yr hoffech chi ofyn am adolygiad mewnol, gan ofyn iddynt i gael gwybod pam ymateb i'r cais wedi bod mor araf." msgid "Click on the link below to send a message to {{public_body}} reminding them to reply to your request." msgstr "Cliciwch ar y ddolen isod i anfon neges at {{public_body}} i'w atgoffa i ymateb i'ch cais." @@ -726,7 +726,7 @@ msgid "Everything that you enter on this page, including your nameeich enw, yn gyhoeddus ar y wefan hon am byth (pam?)." msgid "Everything that you enter on this page\\n will be displayed publicly on\\n this website forever (why?)." -msgstr "Dangosir popeth yr ydych yn rhoi i mewn ar y dudalen hon yn gyhoeddus ar y wefan hon am byth (pam?) ." +msgstr "Dangosir popeth yr ydych yn rhoi i mewn ar y dudalen hon yn gyhoeddus ar y wefan hon am byth (pam?) ." msgid "FOI" msgstr "Rhyddid Gwybodaeth" @@ -747,7 +747,7 @@ msgid "FOI requests {{start_count}} to {{end_count}} of {{total_count}}" msgstr "Ceisiadau Rhyddid Gwybodaeth {{start_count}} i {{end_count}} o {{total_count}}" msgid "FOI response requires admin ({{reason}}) - {{title}}" -msgstr "Mae ar Ymateb Rhyddid Gwybodaeth angen gweinyddu ({{reason}}) - {{title}}" +msgstr "Mae ar Ymateb Rhyddid Gwybodaeth angen gweinyddu ({{reason}}) - {{title}}" msgid "Failed to convert image to a PNG" msgstr "Methu trosi delwedd i PNG" @@ -837,7 +837,7 @@ msgstr "Anfonir negeseuon dilynol i geisiadau presennol at " #. messages sent by the requester to the authority after #. the initial request msgid "Follow ups and new responses to this request have been stopped to prevent spam. Please contact us if you are {{user_link}} and need to send a follow up." -msgstr "Rhwystrwyd dilyniannau ac ymatebion newydd i'r cais hwn i atal spam. Cysylltwch â ni os ydych yn {{user_link}} ac mae angen anfon neges ddilynol." +msgstr "Rhwystrwyd dilyniannau ac ymatebion newydd i'r cais hwn i atal spam. Cysylltwch â ni os ydych yn {{user_link}} ac mae angen anfon neges ddilynol." msgid "Follow us on twitter" msgstr "Dilynwch ni ar Twitter" @@ -925,13 +925,13 @@ msgid "Help" msgstr "Help" msgid "Here described means when a user selected a status for the request, and\\nthe most recent event had its status updated to that value. calculated is then inferred by\\n{{site_name}} for intermediate events, which weren't given an explicit\\ndescription by a user. See the search tips for description of the states." -msgstr "Yma mae wedi ei ddisgrifio yn golygu pan fydd defnyddiwr yn dewis statws ar gyfer y cais, a statws y digwyddiad mwyaf diweddar wedi ei diweddaru statws i'r gwerth hwnnw. Wedyn mae wedi'i gyfrifo yn cael ei dybio gan {{site_name}} ar gyfer digwyddiadau canolradd, na chawsant ddisgrifiad penodol gan ddefnyddiwr. Gweler awgrymiadau chwilio am ddisgrifiad o'r cyflyrau." +msgstr "Yma mae wedi ei ddisgrifio yn golygu pan fydd defnyddiwr yn dewis statws ar gyfer y cais, a statws y digwyddiad mwyaf diweddar wedi ei diweddaru statws i'r gwerth hwnnw. Wedyn mae wedi'i gyfrifo yn cael ei dybio gan {{site_name}} ar gyfer digwyddiadau canolradd, na chawsant ddisgrifiad penodol gan ddefnyddiwr. Gweler awgrymiadau chwilio am ddisgrifiad o'r cyflyrau." msgid "Here is the message you wrote, in case you would like to copy the text and save it for later." msgstr "Dyma'r neges ysgrifennoch, rhag ofn yr hoffech gopïo'r testun a'i gadw ar gyfer yn ddiweddarach." msgid "Hi! We need your help. The person who made the following request\\n hasn't told us whether or not it was successful. Would you mind taking\\n a moment to read it and help us keep the place tidy for everyone?\\n Thanks." -msgstr "Hiya! Rydym angen eich help. Dydy]r person a wnaeth y cais canlynol sydd ddim wedi dweud wrthym ai oedd yn llwyddiannus. A fyddech chi'n meddwl cymryd eiliad i'w ddarllen a'n helpu ni i gadw'r lle'n daclus i bawb? Diolch." +msgstr "Hiya! Rydym angen eich help. Dydy'r person a wnaeth y cais canlynol sydd ddim wedi dweud wrthym ai oedd yn llwyddiannus. A fyddech chi'n meddwl cymryd eiliad i'w ddarllen a'n helpu ni i gadw'r lle'n daclus i bawb? Diolch." msgid "Hide request" msgstr "Cuddio cais" @@ -1390,7 +1390,7 @@ msgid "No tracked things found." msgstr "Dim wedi dod o hyd i bethau wedi eu tracio." msgid "Nobody has made any Freedom of Information requests to {{public_body_name}} using this site yet." -msgstr "Nid oes neb wedi gwneud unrhyw geisiadau Rhyddid Gwybodaeth i {{public_body_name}} gan ddefnyddio'r safle hwn eto." +msgstr "Nid oes neb wedi gwneud unrhyw geisiadau Rhyddid Gwybodaeth i {{public_body_name}} drwy ddefnyddio'r wefan hon eto." msgid "None found." msgstr "Heb ganfod yr un." @@ -1402,7 +1402,7 @@ msgid "Not a valid FOI request" msgstr "Ddim yn gais Rhyddid Gwybodaeth dilys" msgid "Note that the requester will not be notified about your annotation, because the request was published by {{public_body_name}} on their behalf." -msgstr "Noder na fydd y ceisydd yn cael gwybod am eich anodi, oherwydd i'r cais gael ei gyhoeddi gan {{public_body_name}} ar eu rhan." +msgstr "Noder na fydd y ceisydd yn cael gwybod am eich anodi, oherwydd i'r cais gael ei gyhoeddi gan {{public_body_name}} ar eu rhan." msgid "Now check your email!" msgstr "Nawr sieciwch eich ebost!" @@ -1459,7 +1459,7 @@ msgid "Only the authority can reply to this request, and I don't recognise the a msgstr "Dim ond yr awdurdod a all ymateb i'r cais hwn, ac nid wyf yn adnabod y cyfeiriad o ble yr anfonwyd yr ateb hwn." msgid "Only the authority can reply to this request, but there is no \"From\" address to check against" -msgstr "Dim ond yr awdurdod a all ymateb i'r cais hwn, ond nid oes cyfeiriad \"O\" i wirio yn ei erbyn" +msgstr "Dim ond yr awdurdod a all ymateb i'r cais hwn, ond nid oes cyfeiriad \"O\" i wirio yn ei erbyn" msgid "Or search in their website for this information." msgstr "Neu chwilio yn eu gwefan am y wybodaeth hon." @@ -2002,815 +2002,815 @@ msgid "Search in" msgstr "Chwilio mewn" msgid "Search over
    \\n {{number_of_requests}} requests and
    \\n {{number_of_authorities}} authorities" -msgstr "" +msgstr "Chwilio dros {{number_of_requests}} cais a {{number_of_authorities}} awdurdod" msgid "Search queries" -msgstr "" +msgstr "Chwilio ymholiadau " msgid "Search results" -msgstr "" +msgstr "Chwilio canlyniadau" msgid "Search the site to find what you were looking for." -msgstr "" +msgstr "Chwilio'r safle i ddod o hyd i'r hyn roeddech yn chwilio amdano." msgid "Search within the {{count}} Freedom of Information requests to {{public_body_name}}" msgid_plural "Search within the {{count}} Freedom of Information requests made to {{public_body_name}}" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +msgstr[0] "Chwilio o fewn y {{count}} cais Rhyddid Gwybodaeth a wnaed i {{public_body_name}} " +msgstr[1] "Chwilio o fewn y {{count}} cais Rhyddid Gwybodaeth a wnaed i {{public_body_name}} " +msgstr[2] "Chwilio o fewn y {{count}} cais Rhyddid Gwybodaeth a wnaed i {{public_body_name}} " +msgstr[3] "Chwilio o fewn y {{count}} cais Rhyddid Gwybodaeth a wnaed i {{public_body_name}} " msgid "Search your contributions" -msgstr "" +msgstr "Chwilio eich cyfraniadau" msgid "See bounce message" -msgstr "" +msgstr "Gweler neges bownsio" msgid "Select one to see more information about the authority." -msgstr "" +msgstr "Dewiswch un i weld rhagor o wybodaeth am yr awdurdod." msgid "Select the authority to write to" -msgstr "" +msgstr "Dewiswch yr awdurdod i ysgrifennu ato" msgid "Send a followup" -msgstr "" +msgstr "Anfon neges ddilynol" msgid "Send a message to " -msgstr "" +msgstr "Anfonwch neges i " msgid "Send a public follow up message to {{person_or_body}}" -msgstr "" +msgstr "Anfonwch neges gyhoeddus ddilynol i {{person_or_body}} " msgid "Send a public reply to {{person_or_body}}" -msgstr "" +msgstr "Anfon ateb cyhoeddus i {{person_or_body}} " msgid "Send follow up to '{{title}}'" -msgstr "" +msgstr "Anfonwch neges ddilynol i '{{title}}'" msgid "Send message" msgstr "Anfon neges" msgid "Send message to " -msgstr "Anfon neges at" +msgstr "Anfon neges i" msgid "Send request" msgstr "Anfon cais" msgid "Set your profile photo" -msgstr "" +msgstr "Gosodwch lun eich proffil" msgid "Short name" -msgstr "" +msgstr "Enw byr" msgid "Short name is already taken" -msgstr "" +msgstr "Mae'r enw byr hwnnw wedi'i ddefnyddio'n barod" msgid "Show most relevant results first" -msgstr "" +msgstr "Dangoswch y canlyniadau mwyaf perthnasol yn gyntaf" msgid "Show only..." -msgstr "" +msgstr "Dangos yn unig ..." msgid "Showing" -msgstr "" +msgstr "Yn dangos" msgid "Sign in" msgstr "Mewngofnodi" msgid "Sign in or make a new account" -msgstr "Mewngofnodi neu gofrestru" +msgstr "Mewngofnodi neu agor cyfrif newydd" msgid "Sign in or sign up" -msgstr "Mewngofnodi neu gofrestru" +msgstr "Mewngofnodwch neu gofrestrwch" msgid "Sign out" -msgstr "" +msgstr "Allgofnodwch" msgid "Sign up" msgstr "Cofrestrwch" msgid "Similar requests" -msgstr "" +msgstr "Ceisiadau tebyg" msgid "Simple search" -msgstr "" +msgstr "Chwiliad syml" msgid "Some notes have been added to your FOI request - " -msgstr "" +msgstr "Mae rhai nodiadau wedi cael eu hychwanegu at eich cais Rhyddid Gwybodaeth - " msgid "Some of the information requested has been received" -msgstr "" +msgstr "Mae peth o'r wybodaeth y gwnaed cais amdani wedi ei dderbyn." msgid "Some people who've made requests haven't let us know whether they were\\nsuccessful or not. We need your help –\\nchoose one of these requests, read it, and let everyone know whether or not the\\ninformation has been provided. Everyone'll be exceedingly grateful." -msgstr "" +msgstr "Nid yw rhai pobl sydd wedi gwneud ceisiadau wedi rhoi gwybod i ni a oeddynt yn llwyddiannus ai peidio. Mae angen eich help - dewisiwch un o'r ceisiadau hyn, ei ddarllen, a gadael i bawb wybod a yw'r wybodaeth wedi cael ei darparu. Bydd pawb yn eithriadol o ddiolchgar." msgid "Somebody added a note to your FOI request - " -msgstr "" +msgstr "Ychwanegodd rhywun nodyn at eich cais Rhyddid Gwybodaeth - " msgid "Someone has updated the status of your request" -msgstr "" +msgstr "Mae rhywun wedi diweddaru statws eich cais" msgid "Someone, perhaps you, just tried to change their email address on\\n{{site_name}} from {{old_email}} to {{new_email}}." -msgstr "" +msgstr "Mae rhywun, efallai chi, newydd geisio newid eu cyfeiriad e-bost ar {{site_name}} o {{old_email}} i {{new_email}}." msgid "Sorry - you cannot respond to this request via {{site_name}}, because this is a copy of the request originally at {{link_to_original_request}}." -msgstr "" +msgstr "Mae'n ddrwg gennym - ni allwch ymateb i'r cais hwn trwy {{site_name}}, gan fod hwn yn gopi o'r cais a oedd yn wreiddiol yn {{link_to_original_request}} ." msgid "Sorry, but only {{user_name}} is allowed to do that." -msgstr "" +msgstr "Mae'n ddrwg gennym, ond dim ond {{USER_NAME}} sy'n cael gwneud hynny." msgid "Sorry, there was a problem processing this page" -msgstr "" +msgstr "Mae'n ddrwg gennym, roedd problem wrth brosesu'r dudalen hon" msgid "Sorry, we couldn't find that page" -msgstr "" +msgstr "Mae'n ddrwg gennym, ni allem ddod o hyd i'r dudalen honno" msgid "Special note for this authority!" -msgstr "" +msgstr "Nodyn arbennig ar gyfer yr awdurdod hwn!" msgid "Start now »" -msgstr "" +msgstr "Dechrau nawr »" msgid "Start your own blog" -msgstr "" +msgstr "Dechreuwch eich blog eich hun" msgid "Stay up to date" -msgstr "" +msgstr "Parha yn gyfoes" msgid "Still awaiting an internal review" -msgstr "" +msgstr "Dal i ddisgwyl am adolygiad mewnol" msgid "Subject" -msgstr "" +msgstr "Pwnc" msgid "Subject:" -msgstr "" +msgstr "Pwnc:" msgid "Submit" -msgstr "" +msgstr "Cyflwyno" msgid "Submit status" -msgstr "" +msgstr "Cyflwyno statws" msgid "Submit status and send message" -msgstr "" +msgstr "Cyflwyno statws ac anfon neges" msgid "Subscribe to blog" -msgstr "" +msgstr "Tanysgrifio i flog" msgid "Successful Freedom of Information requests" -msgstr "" +msgstr "Ceisiadau Rhyddid Gwybodaeth llwyddiannus" msgid "Successful." -msgstr "" +msgstr "Llwyddiannus." msgid "Suggest how the requester can find the rest of the information." -msgstr "" +msgstr "Awgrymwch sut y gall y ceisydd ddod o hyd i weddill y wybodaeth ." msgid "Summary:" msgstr "Crynodeb:" msgid "Table of statuses" -msgstr "" +msgstr "Tabl o statws" msgid "Table of varieties" -msgstr "" +msgstr "Tabl o fathau" msgid "Tags" -msgstr "" +msgstr "Tagiau" msgid "Tags (separated by a space):" -msgstr "" +msgstr "Tagiau (wedi eu gwahanu gan fwlch):" msgid "Tags:" -msgstr "" +msgstr "Tagiau:" msgid "Technical details" -msgstr "" +msgstr "Manylion technegol" msgid "Thank you for helping us keep the site tidy!" -msgstr "" +msgstr "Diolch i chi am ein helpu ni i gadw'r safle'n daclus!" msgid "Thank you for making an annotation!" -msgstr "" +msgstr "Diolch i chi am wneud anodiad!" msgid "Thank you for responding to this FOI request! Your response has been published below, and a link to your response has been emailed to " -msgstr "" +msgstr "Diolch i chi am ymateb i'r cais Rhyddid Gwybodaeth hwn! Mae eich ymateb wedi ei gyhoeddi isod, a dolen at eich ymateb wedi cael ei e-bostio at " msgid "Thank you for updating the status of the request '{{info_request_title}}'. There are some more requests below for you to classify." -msgstr "" +msgstr "Diolch i chi am ddiweddaru statws y cais '{{ info_request_title}}'. Mae ychydig yn rhagor o geisiadau isod i chi eu dosbarthu." msgid "Thank you for updating this request!" -msgstr "" +msgstr "Diolch i chi am ddiweddaru'r cais hwn!" msgid "Thank you for updating your profile photo" -msgstr "" +msgstr "Diolch i chi am ddiweddaru llun eich proffil" msgid "Thank you! We'll look into what happened and try and fix it up." -msgstr "" +msgstr "Diolch yn fawr! Byddwn yn ymchwilio i beth ddigwyddodd ac yn ceisio ei drwsio." msgid "Thanks for helping - your work will make it easier for everyone to find successful\\nresponses, and maybe even let us make league tables..." -msgstr "" +msgstr "Diolch am helpu - bydd eich gwaith yn ei wneud yn haws i bawb ddod o hyd i geisiadau llwyddiannus, ac efallai hyd yn oed yn gadael i ni lunio tablau cynghrair ..." msgid "Thanks very much - this will help others find useful stuff. We'll\\n also, if you need it, give advice on what to do next about your\\n requests." -msgstr "" +msgstr "Diolch yn fawr iawn - bydd hyn yn helpu eraill i ddod o hyd i bethau defnyddiol. Byddwn hefyd, os bydd ei angen arnoch, yn rhoi cyngor ar beth i'w wneud nesaf am eich ceisiadau." msgid "Thanks very much for helping keep everything neat and organised.\\n We'll also, if you need it, give you advice on what to do next about each of your\\n requests." -msgstr "" +msgstr "Diolch yn fawr am helpu i gadw popethyn daclus a threfnus. Byddwn hefyd, os bydd ei angen arnoch, yn rhoi cyngor ar beth i'w wneud nesaf am bob un o'ch ceisiadau." msgid "That doesn't look like a valid email address. Please check you have typed it correctly." -msgstr "" +msgstr "Nid yw hynny'n edrych fel cyfeiriad e-bost dilys. Gwiriwch eich bod wedi ei deipio'n gywir." msgid "The review has finished and overall:" -msgstr "" +msgstr "Mae'r adolygiad wedi gorffen ac yn gyffredinol:" msgid "The Freedom of Information Act does not apply to" -msgstr "" +msgstr "Nid yw'r Ddeddf Rhyddid Gwybodaeth yn gymwys i" msgid "The accounts have been left as they previously were." -msgstr "" +msgstr "Mae'r cyfrifon wedi cael eu gadael fel yr oeddent o'r blaen." msgid "The authority do not have the information (maybe they say who does)" -msgstr "" +msgstr "Nid yw'r wybodaeth gan yr awdurdod(efallai eu bod yn dweud pwy gan bwy y mae)" msgid "The authority only has a paper copy of the information." -msgstr "" +msgstr "Dim ond copi papur o'r wybodaeth sydd gan yr awdurdod." msgid "The authority say that they need a postal\\n address, not just an email, for it to be a valid FOI request" -msgstr "" +msgstr "Mae'r awdurdod yn dweud bod angen cyfeiriad post, nid dim ond e-bost, er mwyn iddo fod yn gais Rhyddid Gwybodaeth dilys" msgid "The authority would like to / has responded by post to this request." -msgstr "" +msgstr "Byddai'r awdurdod yn hoffi / wedi ymateb drwy'r post i'r cais hwn." msgid "The classification of requests (e.g. to say whether they were successful or not) is done manually by users and administrators of the site, which means that they are subject to error." -msgstr "" +msgstr "Mae dosbarthiad y ceisiadau (ee i ddweud a oeddent yn llwyddiannus neu beidio) yn cael ei wneud â llaw gan ddefnyddwyr a gweinyddwyr y safle, sy'n golygu eu bod yn agored i gamgymeriadau." msgid "The email that you, on behalf of {{public_body}}, sent to\\n{{user}} to reply to an {{law_used_short}}\\nrequest has not been delivered." -msgstr "" +msgstr "Nid yw'r e-bost anfonoch, ar ran {{public_body}}, at {{user}} i ymateb i gais {{law_used_short}} wedi cael ei gyflwyno." msgid "The error bars shown are 95% confidence intervals for the hypothesized underlying proportion (i.e. that which you would obtain by making an infinite number of requests through this site to that authority). In other words, the population being sampled is all the current and future requests to the authority through this site, rather than, say, all requests that have been made to the public body by any means." -msgstr "" +msgstr "Mae'r bariau gwall a ddangosir yn gyfyngau hyder 95% ar gyfer y gyfran sylfaenol damcaniaethol (h.y. un y byddech chi'n cael drwy wneud nifer anfeidrol o geisiadau drwy'r safle hwn i'r awdurdod hwnnw). Mewn geiriau eraill, holl geisiadau presennol ac yn y dyfodol i'r awdurdod drwy'r safle hwn yw'r boblogaeth sy'n cael ei samplo, yn hytrach na, dyweder, yr holl geisiadau sydd wedi cael eu gwneud i'r corff cyhoeddus mewn unrhyw fodd." msgid "The page doesn't exist. Things you can try now:" -msgstr "" +msgstr "Nid yw'r dudalen yn bodoli. Pethau y gallwch roi cynnig arnynt yn awr:" msgid "The percentages are calculated with respect to the total number of requests, which includes invalid requests; this is a known problem that will be fixed in a later release." -msgstr "" +msgstr "Mae'r canrannau yn cael eu cyfrifo mewn perthynas â chyfanswm nifer y ceisiadau, sy'n cynnwys ceisiadau annilys; mae hyn yn broblem hysbys a fydd yn cael ei gywiro pan caiff fersiwn ddiweddarach ei rhyddhau." msgid "The public authority does not have the information requested" -msgstr "" +msgstr "Nid oes gan yr awdurdod cyhoeddus y wybodaeth y gofynnwyd amdani" msgid "The public authority would like part of the request explained" -msgstr "" +msgstr "Byddai'r awdurdod cyhoeddus yn hoffi cael eglurhad am ran o'r cais" msgid "The public authority would like to / has responded by post" -msgstr "" +msgstr "Byddai'r awdurdod cyhoeddus yn hoffi / wedi ymateb drwy'r post" msgid "The request has been refused" -msgstr "" +msgstr "Mae'r cais wedi cael ei wrthod" msgid "The request has been updated since you originally loaded this page. Please check for any new incoming messages below, and try again." -msgstr "" +msgstr "Mae'r cais wedi cael ei ddiweddaru ers i chi lwytho'r dudalen hon yn wreiddiol. Edrychwch i weld a oes unrhyw negeseuon newydd isod ac yna, ceisiwch eto." msgid "The request is waiting for clarification." -msgstr "" +msgstr "Mae'r cais yn aros am eglurhad." msgid "The request was partially successful." -msgstr "" +msgstr "Roedd y cais yn rhannol lwyddiannus." msgid "The request was refused by" -msgstr "" +msgstr "Gwrthodwyd y cais gan" msgid "The request was successful." -msgstr "" +msgstr "Roedd y cais yn llwyddiannus." msgid "The request was refused by the public authority" -msgstr "" +msgstr "Gwrthodwyd y cais gan yr awdurdod cyhoeddus" msgid "The request you have tried to view has been removed. There are\\nvarious reasons why we might have done this, sorry we can't be more specific here. Please contact us if you have any questions." -msgstr "" +msgstr "Mae'r cais yr ydych wedi ceisio ei weld wedi cael ei ddileu. Mae amryw o resymau pam y gallem fod wedi gwneud hyn. Mae'n ddrwg gennym na allwn fod yn fwy penodol yma. Cysylltwch â ni os oes gennych unrhyw gwestiynau." msgid "The requester has abandoned this request for some reason" -msgstr "" +msgstr "Mae'r ceisydd wedi rhoi'r gorau i'r cais hwn am ryw reswm" msgid "The response to your request has been delayed. You can say that,\\n by law, the authority should normally have responded\\n promptly and" -msgstr "" +msgstr "Mae'r ymateb i'ch cais wedi cael ei ohirio. Gallwch ddweud, yn ôl y gyfraith, y dylai'r awdurdod wedi ymateb yn brydlonfel rheol a" msgid "The response to your request is long overdue. You can say that, by\\n law, under all circumstances, the authority should have responded\\n by now" -msgstr "" +msgstr "Mae'r ymateb i'ch cais yn hwyr iawn. Gallwch ddweud, yn ôl y gyfraith, ymhob achos, y dylai'r awdurdod fod wedi ymateb erbyn hyn" msgid "The search index is currently offline, so we can't show the Freedom of Information requests that have been made to this authority." -msgstr "" +msgstr "Mae'r mynegai chwilio ar hyn o bryd all-lein, fel na allwn ddangos geisiadau Rhyddid Gwybodaeth sydd wedi cael eu gwneud i'r awdurdod hwn." msgid "The search index is currently offline, so we can't show the Freedom of Information requests this person has made." -msgstr "" +msgstr "Mae'r mynegai chwilio ar hyn o bryd all-lein, fel na allwn ddangos y ceisiadau Rhyddid Gwybodaeth y mae'r person yma wedi gwneud." msgid "The {{site_name}} team." msgstr "Tîm {{site_name}}." msgid "Then you can cancel the alert." -msgstr "" +msgstr "Yna gallwch ganslo'r rhybudd." msgid "Then you can cancel the alerts." -msgstr "" +msgstr "Yna gallwch ganslo'r rhybuddion." msgid "Then you can change your email address used on {{site_name}}" -msgstr "" +msgstr "Yna gallwch newid eich cyfeiriad e-bost a ddefnyddir ar {{site_name}}" msgid "Then you can change your password on {{site_name}}" -msgstr "" +msgstr "Yna gallwch newid eich cyfrinair ar {{site_name}}" msgid "Then you can classify the FOI response you have got from " -msgstr "" +msgstr "Yna gallwch ddosbarthu'r ymateb Rhyddid Gwybodaeth yr ydych wedi'i gael gan " msgid "Then you can download a zip file of {{info_request_title}}." -msgstr "" +msgstr "Yna gallwch lawrlwytho ffeil zip o {{ info_request_title}}." msgid "Then you can log into the administrative interface" -msgstr "" +msgstr "Yna gallwch logio i mewn i'r rhyngwyneb gweinyddol" msgid "Then you can play the request categorisation game." -msgstr "" +msgstr "Yna gallwch chi chwarae y gêm categoreiddio cais." msgid "Then you can report the request '{{title}}'" -msgstr "" +msgstr "Yna gallwch roi gwybod am y cais '{{title}}'" msgid "Then you can send a message to " -msgstr "" +msgstr "Yna, gallwch anfon neges i " msgid "Then you can sign in to {{site_name}}" -msgstr "" +msgstr "Yna gallwch lofnodi i mewn i {{site_name}}" msgid "Then you can update the status of your request to " -msgstr "" +msgstr "Yna gallwch ddiweddaru statws eich cais i " msgid "Then you can upload an FOI response. " -msgstr "" +msgstr "Yna gallwch lwytho ymateb Rhyddid Gwybodaeth i fyny. " msgid "Then you can write follow up message to " -msgstr "" +msgstr "Yna, gallwch ysgrifennu neges ddilynol i " msgid "Then you can write your reply to " -msgstr "" +msgstr "Yna, gallwch ysgrifennu eich ateb i " msgid "Then you will be following all new FOI requests." -msgstr "" +msgstr "Yna byddwch yn dilyn yr holl geisiadau Rhyddid Gwybodaeth newydd." msgid "Then you will be notified whenever '{{user_name}}' requests something or gets a response." -msgstr "" +msgstr "Yna byddwch yn cael eich hysbysu pryd bynnag y bydd '{{user_name}}' yn gofyn am rywbeth neu yn cael ymateb." msgid "Then you will be notified whenever a new request or response matches your search." -msgstr "" +msgstr "Yna byddwch yn cael eich hysbysu pryd bynnag y bydd cais neu ymateb newydd yn cyfateb â'ch chwiliad." msgid "Then you will be notified whenever an FOI request succeeds." -msgstr "" +msgstr "Yna byddwch yn cael eich hysbysu pryd bynnag y bydd cais Rhyddid Gwybodaeth yn llwyddo." msgid "Then you will be notified whenever someone requests something or gets a response from '{{public_body_name}}'." -msgstr "" +msgstr "Yna byddwch yn cael eich hysbysu pryd bynnag y bydd rhywun yn gofyn am rywbeth gan, neu'n cael ymateb gan, '{{public_body_name}}'." msgid "Then you will be updated whenever the request '{{request_title}}' is updated." -msgstr "" +msgstr "Yna byddwch yn cael diweddariad pryd bynnag y bydd cais '{{request_title}}' yn cael ei ddiweddaru." msgid "Then you'll be allowed to send FOI requests." -msgstr "" +msgstr "Yna byddwch yn cael anfon ceisiadau Rhyddid Gwybodaeth." msgid "Then your FOI request to {{public_body_name}} will be sent." -msgstr "" +msgstr "Yna bydd eich cais Rhyddid Gwybodaeth i {{public_body_name}} yn cael ei anfon." msgid "Then your annotation to {{info_request_title}} will be posted." -msgstr "" +msgstr "Yna eich anodiad i {{info_request_title}} yn cael ei bostio." msgid "There are {{count}} new annotations on your {{info_request}} request. Follow this link to see what they wrote." -msgstr "" +msgstr "Mae {{count}} anodiad newydd ar eich cais {{info_request}}. Dilynwch y ddolen hon i weld beth ysgrifennwyd." msgid "There is more than one person who uses this site and has this name.\\n One of them is shown below, you may mean a different one:" -msgstr "" +msgstr "Mae gan fwy nag un person sy'n defnyddio'r safle hwn yr enw hwn Mae un ohonynt yn cael ei ddangos isod. Efallai eich bod wedi golygu un gwahanol:" msgid "There is a limit on the number of requests you can make in a day, because we don’t want public authorities to be bombarded with large numbers of inappropriate requests. If you feel you have a good reason to ask for the limit to be lifted in your case, please get in touch." -msgstr "" +msgstr "Mae cyfyngiad ar y nifer o geisiadau y gallwch eu gwneud mewn diwrnod, oherwydd nid ydym am i awdurdodau cyhoeddus gael eu peledu gyda nifer fawr o geisiadau amhriodol. Os ydych yn teimlo bod gennych reswm da dros ofyn i'r terfyn gael ei godi yn eich achos chi, cysylltwch â ni." msgid "There is {{count}} person following this request" msgid_plural "There are {{count}} people following this request" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +msgstr[0] "Mae {{count}} person yn dilyn y cais hwn" +msgstr[1] "Mae {{count}} person yn dilyn y cais hwn" +msgstr[2] "Mae {{count}} person yn dilyn y cais hwn" +msgstr[3] "Mae {{count}} person yn dilyn y cais hwn" msgid "There was a delivery error or similar, which needs fixing by the {{site_name}} team." -msgstr "" +msgstr "Roedd gwall cyflenwi neu rywbeth tebyg, sy angen ei drwsio gan dîm {{site_name}}." msgid "There was an error with the words you entered, please try again." -msgstr "" +msgstr "Roedd gwall gyda'r geiriau a roddoch, ceisiwch eto os gwelwch yn dda." msgid "There was no data calculated for this graph yet." -msgstr "" +msgstr "Ni chyfrifwyd data ar gyfer y graff hwn eto." msgid "There were no requests matching your query." -msgstr "" +msgstr "Nid oedd unrhyw geisiadau yn cyfateb i'ch ymholiad." msgid "There were no results matching your query." -msgstr "" +msgstr "Ni chafwyd canlyniadau sy'n cyfateb i'ch ymholiad." msgid "These graphs were partly inspired by some statistics that Mark Goodge produced for WhatDoTheyKnow, so thanks are due to him." -msgstr "" +msgstr "Mae'r graffiau hyn wedi eu hysbrydoli yn rhannol gan rai ystadegau a gynhyrchodd Mark Goodge ar gyfer WhatDoTheyKnow, felly mae diolch yn ddyledus iddo." msgid "They are going to reply by post" -msgstr "" +msgstr "Maent yn mynd i ateb drwy'r post" msgid "They do not have the information (maybe they say who does)" -msgstr "" +msgstr "Nid yw'r wybodaeth ganddynt (efallai eu bod yn dweud pwy gan bwy y mae)" msgid "They have been given the following explanation:" -msgstr "" +msgstr "Maent wedi cael yr esboniad canlynol:" msgid "They have not replied to your {{law_used_short}} request {{title}} promptly, as normally required by law" -msgstr "" +msgstr "Nid ydynt wedi ymateb i'ch cais {{title}} {{law_used_short}} yn brydlon, fel sy'n ofynnol fel arfer yn ôl y gyfraith" msgid "They have not replied to your {{law_used_short}} request {{title}}, \\nas required by law" -msgstr "" +msgstr "Nid ydynt wedi ateb i'ch cais {{teitl}} {{law_used_short}}, fel sy'n ofynnol yn ôl y gyfraith" msgid "Things to do with this request" -msgstr "" +msgstr "Pethau i'w gwneud gyda'r cais hwn" msgid "Things you're following" -msgstr "" +msgstr "Pethau rydych yn eu dilyn" msgid "This authority no longer exists, so you cannot make a request to it." -msgstr "" +msgstr "Nid yw'r awdurdod hwn yn bodoli bellach, felly ni allwch wneud cais iddo." msgid "This covers a very wide spectrum of information about the state of\\n the natural and built environment, such as:" -msgstr "" +msgstr "Mae hyn yn cwmpasu sbectrwm eang iawn o wybodaeth am gyflwr yr amgylchedd naturiol ac adeiledig, megis:" msgid "This external request has been hidden" -msgstr "" +msgstr "Mae'r cais allanol wedi cael ei guddio" msgid "This is a plain-text version of the Freedom of Information request \"{{request_title}}\". The latest, full version is available online at {{full_url}}" -msgstr "" +msgstr "Mae hwn yn fersiwn destun plaen y cais Rhyddid Gwybodaeth \"{{request_title}}\". Mae'r fersiwn lawn ddiweddaraf ar gael ar-lein yn {{full_url}} " msgid "This is an HTML version of an attachment to the Freedom of Information request" -msgstr "" +msgstr "Mae hwn yn fersiwn HTML o atodiad i'r cais Rhyddid Gwybodaeth" msgid "This is because {{title}} is an old request that has been\\nmarked to no longer receive responses." -msgstr "" +msgstr "Mae hyn oherwydd bod {{title}} yn hen gais sydd wedi ei nodi i beidio â derbyn ymatebion mwyach." msgid "This is the first version." -msgstr "" +msgstr "Dyma'r fersiwn gyntaf." msgid "This is your own request, so you will be automatically emailed when new responses arrive." -msgstr "" +msgstr "Dyma'ch eich cais eich hun, felly byddwch yn cael e-bost yn awtomatig pan fydd ymatebion newydd yn cyrraedd." msgid "This message has been hidden." -msgstr "" +msgstr "Mae'r neges hon wedi cael ei chuddio." msgid "This message has been hidden. There are various reasons why we might have done this, sorry we can't be more specific here." -msgstr "" +msgstr "Mae'r neges hon wedi cael ei chuddio. Mae yna amryw o resymau pam y gallem fod wedi gwneud hyn, mae'n ddrwg gennym na allwn fod yn fwy penodol yma." msgid "This message has prominence 'hidden'. You can only see it because you are logged in as a super user." -msgstr "" +msgstr "'Cudd' yw amlygrwydd y cais hwn. Gallwch ond ei gweld oherwydd eich bod wedi mewngofnodi fel arch-ddefnyddiwr." msgid "This message has prominence 'hidden'. {{reason}} You can only see it because you are logged in as a super user." -msgstr "" +msgstr "'Cudd' yw amlygrwydd y cais hwn. {{reason}} Gallwch ond ei gweld oherwydd eich bod wedi mewngofnodi fel arch-ddefnyddiwr." msgid "This message is hidden, so that only you, the requester, can see it. Please contact us if you are not sure why." -msgstr "" +msgstr "Mae'r cais hwn wedi ei guddio, fel mai dim ond chi y ceisydd all ei weld. cysylltwch â ni os nad ydych yn siŵr pam." msgid "This message is hidden, so that only you, the requester, can see it. {{reason}}" -msgstr "" +msgstr "Mae'r cais hwn wedi ei guddio, fel mai dim ond chi y ceisydd all ei weld. {{reason}} " msgid "This page of public body statistics is currently experimental, so there are some caveats that should be borne in mind:" -msgstr "" +msgstr "Mae'r dudalen hon o ystadegau corff cyhoeddus yn arbrofol ar hyn o bryd, felly mae rhai cafeatau y dylid eu cadw mewn cof:" msgid "This particular request is finished:" -msgstr "" +msgstr "Mae'r cais penodol hwn wedi gorffen:" msgid "This person has made no Freedom of Information requests using this site." -msgstr "" +msgstr "Nid yw'r person hwn wedi gwneud unrhyw geisiadau Rhyddid Gwybodaeth drwy ddefnyddio'r wefan hon." msgid "This person's annotations" -msgstr "" +msgstr "Anodiadau'r person hwn" msgid "This person's {{count}} Freedom of Information request" msgid_plural "This person's {{count}} Freedom of Information requests" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +msgstr[0] "{{count}} cais Rhyddid Gwybodaeth y person hwn " +msgstr[1] "{{count}} cais Rhyddid Gwybodaeth y person hwn" +msgstr[2] "{{count}} cais Rhyddid Gwybodaeth y person hwn" +msgstr[3] "{{count}} cais Rhyddid Gwybodaeth y person hwn" msgid "This person's {{count}} annotation" msgid_plural "This person's {{count}} annotations" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +msgstr[0] "{{count}} anodiad y person hwn" +msgstr[1] "{{count}} anodiad y person hwn" +msgstr[2] "{{count}} anodiad y person hwn" +msgstr[3] "{{count}} anodiad y person hwn" msgid "This request requires administrator attention" -msgstr "" +msgstr "Mae angen sylw gweinyddwr ar y cais hwn" msgid "This request has already been reported for administrator attention" -msgstr "" +msgstr "Mae'r cais eisoes wedi cael ei adrodd i gael sylw gweinyddwr" msgid "This request has an unknown status." -msgstr "" +msgstr "Mae gan y cais hwn ." msgid "This request has been hidden from the site, because an administrator considers it not to be an FOI request" -msgstr "" +msgstr "Mae'r cais hwn wedi cael ei guddio o'r wefan oherwydd i weinyddwr ystyried nad yw'n gais Rhyddid Gwybodaeth." msgid "This request has been hidden from the site, because an administrator considers it vexatious" -msgstr "" +msgstr "Mae'r cais hwn wedi cael ei guddio o'r wefan oherwydd i weinyddwr ystyried ei fod yn flinderus." msgid "This request has been reported as needing administrator attention (perhaps because it is vexatious, or a request for personal information)" -msgstr "" +msgstr "Mae adroddiad am y cais hwn gan ddweud bod angen sylw gweinyddwr arno (efallai am ei fod yn flinderus, neu'n gais am wybodaeth bersonol)" msgid "This request has been withdrawn by the person who made it.\\n There may be an explanation in the correspondence below." -msgstr "" +msgstr "Mae'r cais hwn wedi cael ei dynnu'n ôl gan y sawl a'i wnaeth. Efallai y bydd esboniad yn yr ohebiaeth isod." msgid "This request has been marked for review by the site administrators, who have not hidden it at this time. If you believe it should be hidden, please contact us." -msgstr "" +msgstr "Mae'r cais wedi cael ei farcio i'w adolygu gan weinyddwyr y wefan, nad ydynt wedi ei guddio ar hyn o bryd. Os ydych yn credu y dylid ei guddio, cysylltwch â ni." msgid "This request has been reported for administrator attention" -msgstr "" +msgstr "Mae adroddiad am y cais hwn wedi cael ei anfon, at sylw gweinyddwr" msgid "This request has been set by an administrator to \"allow new responses from nobody\"" -msgstr "" +msgstr "Mae'r cais hwn wedi cael ei osod gan weinyddwr fel \"na chaniateir ymatebion newydd gan neb\"" msgid "This request has had an unusual response, and requires attention from the {{site_name}} team." -msgstr "" +msgstr "Mae'r cais hwn wedi derbyn ymateb anarferol, ac mae angen sylw arno gan dîm {{site_name}}." msgid "This request has prominence 'hidden'. You can only see it because you are logged\\n in as a super user." -msgstr "" +msgstr "'Cudd' yw amlygrwydd y cais hwn. Gallwch ond ei weld oherwydd eich bod wedi mewngofnodi fel arch-ddefnyddiwr." msgid "This request is hidden, so that only you the requester can see it. Please\\n contact us if you are not sure why." -msgstr "" +msgstr "Mae'r cais hwn wedi ei guddio, fel mai dim ond y ceisydd all ei weld. cysylltwch â ni os nad ydych yn siŵr pam." msgid "This request is still in progress:" -msgstr "" +msgstr "Mae'r cais hwn yn dal i fod ar y gweill:" msgid "This request requires administrator attention" -msgstr "" +msgstr "Mae angen sylw gweinyddwr ar y cais hwn" msgid "This request was not made via {{site_name}}" -msgstr "" +msgstr "Ni wnaed y cais hwn drwy {{site_name}}" msgid "This table shows the technical details of the internal events that happened\\nto this request on {{site_name}}. This could be used to generate information about\\nthe speed with which authorities respond to requests, the number of requests\\nwhich require a postal response and much more." -msgstr "" +msgstr "Mae'r tabl hwn yn dangos manylion technegol y digwyddiadau mewnol a ddigwyddodd i'r cais hwn ar {{site_name}}. Gallai hwn gael ei ddefnyddio i gynhyrchu gwybodaeth am ba mor gyflym y mae awdurdodau yn ymateb i geisiadau, nifer y ceisiadau sy'n gofyn am ymateb drwy'r post a llawer mwy." msgid "This user has been banned from {{site_name}} " -msgstr "" +msgstr "Mae'r defnyddiwr hwn wedi ei wahardd o {{site_name}} " msgid "This was not possible because there is already an account using \\nthe email address {{email}}." -msgstr "" +msgstr "Nid oedd hyn yn bosibl gan fod cyfrif sydd eisoes yn defnyddio'r cyfeiriad e-bost {{email}}." msgid "To cancel these alerts" -msgstr "" +msgstr "I ganslo'r rhybuddion hyn" msgid "To cancel this alert" -msgstr "" +msgstr "I ddileu'r rhybudd hwn" msgid "To carry on, you need to sign in or make an account. Unfortunately, there\\nwas a technical problem trying to do this." -msgstr "" +msgstr "I barhau, mae angen i chi fewngofnodi neu agor cyfrif newydd. Yn anffodus,roedd problem dechnegol wrth geisio gwneud hyn." msgid "To change your email address used on {{site_name}}" -msgstr "" +msgstr "I newid eich cyfeiriad e-bost a ddefnyddir ar {{site_name}}" msgid "To classify the response to this FOI request" -msgstr "" +msgstr "I ddosbarthu'r ymateb i'r cais Rhyddid Gwybodaeth hwn" msgid "To do that please send a private email to " -msgstr "" +msgstr "I wneud hynny, anfonwch e-bost preifat i " msgid "To do this, first click on the link below." -msgstr "" +msgstr "I wneud hyn, cliciwch yn gyntaf ar y ddolen isod." msgid "To download the zip file" -msgstr "" +msgstr "I lawrlwytho'r ffeil zip" msgid "To follow all successful requests" -msgstr "" +msgstr "I ddilyn yr holl geisiadau llwyddiannus" msgid "To follow new requests" -msgstr "" +msgstr "I ddilyn ceisiadau newydd" msgid "To follow requests and responses matching your search" -msgstr "" +msgstr "I ddilyn ceisiadau ac ymatebion sy'n gyfateb i'ch chwiliad" msgid "To follow requests by '{{user_name}}'" -msgstr "" +msgstr "I ddilyn ceisiadau gan '{{user_name}'" msgid "To follow requests made using {{site_name}} to the public authority '{{public_body_name}}'" -msgstr "" +msgstr "I ddilyn ceisiadau a wnaed gan ddefnyddio {{site_name}} i'r awdurdod cyhoeddus '{{public_body_name}} '" msgid "To follow the request '{{request_title}}'" -msgstr "" +msgstr "I ddilyn y cais '{{request_title}}'" msgid "To help us keep the site tidy, someone else has updated the status of the \\n{{law_used_full}} request {{title}} that you made to {{public_body}}, to \"{{display_status}}\" If you disagree with their categorisation, please update the status again yourself to what you believe to be more accurate." -msgstr "" +msgstr "I'n helpu ni i gadw'r wefan yn daclus, mae rhywun arall wedi diweddaru statws y cais {{law_used_full}} {{teitl}} a wnaethoch i {{public_body}} , i \"{{display_status}}\" Os ydych yn anghytuno â'u categoreiddio, diweddarwch y statws unwaith eto eich hun i'r hyn y credwch i fod yn fwy cywir." msgid "To let everyone know, follow this link and then select the appropriate box." -msgstr "" +msgstr "I adael i bawb wybod, dilynwch y ddolen hon ac yna dewiswch y blwch priodol." msgid "To log into the administrative interface" -msgstr "" +msgstr "I fewngofnodi i'r rhyngwyneb gweinyddol" msgid "To play the request categorisation game" -msgstr "" +msgstr "I chwarae'r gêm categoreiddio cais" msgid "To post your annotation" -msgstr "" +msgstr "I bostio eich anodiad" msgid "To reply to " -msgstr "" +msgstr "I ymateb i " msgid "To report this request" -msgstr "" +msgstr "I roi gwybod am y cais hwn" msgid "To send a follow up message to " -msgstr "" +msgstr "I anfon neges ddilynol i " msgid "To send a message to " -msgstr "" +msgstr "I anfon neges i " msgid "To send your FOI request" -msgstr "" +msgstr "I anfon eich cais Rhyddid Gwybodaeth" msgid "To update the status of this FOI request" -msgstr "" +msgstr "I roi'r wybodaeth ddiweddaraf i statws y cais Rhyddid Gwybodaeth hwn" msgid "To upload a response, you must be logged in using an email address from " -msgstr "" +msgstr "I lwytho ymateb i fyny, rhaid i chi fod wedi mewngofnodi gan ddefnyddio cyfeiriad e-bost gan " msgid "To use the advanced search, combine phrases and labels as described in the search tips below." -msgstr "" +msgstr "I ddefnyddio'r chwiliad uwch, cyfunwch ymadroddion a labeli fel y'i disgrifir yn yr awgrymiadau chwilio isod." msgid "To view the email address that we use to send FOI requests to {{public_body_name}}, please enter these words." -msgstr "" +msgstr "I weld y cyfeiriad e-bost yr ydym yn ei ddefnyddio i anfon ceisiadau Rhyddid Gwybodaeth i {{public_body_name}}, nodwch y geiriau hyn." msgid "To view the response, click on the link below." -msgstr "" +msgstr "I weld yr ymateb, cliciwch ar y ddolen isod." msgid "To {{public_body_link_absolute}}" -msgstr "" +msgstr "I {{public_body_link_absolute}} " msgid "To:" msgstr "I:" msgid "Today" -msgstr "" +msgstr "Heddiw" msgid "Too many requests" -msgstr "" +msgstr "Gormod o geisiadau" msgid "Top search results:" -msgstr "" +msgstr "Y canlyniadau chwilio uchaf:" msgid "Track thing" -msgstr "" +msgstr "Traciwch hwn" msgid "Track this person" -msgstr "" +msgstr "Traciwch y person hwn" msgid "Track this search" -msgstr "" +msgstr "Traciwch y chwiliad hwn" msgid "TrackThing|Track medium" -msgstr "" +msgstr "TrackThing|Traciwch y cyfrwng" msgid "TrackThing|Track query" -msgstr "" +msgstr "TrackThing|Traciwch ymholiad" msgid "TrackThing|Track type" -msgstr "" +msgstr "TrackThing|Math o drac" msgid "Turn off email alerts" -msgstr "" +msgstr "Diffoddwch rybuddion e-bost" msgid "Tweet this request" -msgstr "" +msgstr "Trydarwch y cais hwn" msgid "Type 01/01/2008..14/01/2008 to only show things that happened in the first two weeks of January." -msgstr "" +msgstr "Teipiwch 01/01/2008..14/01/2008 i ddangos yn unig bethau a ddigwyddodd yn ystod dwy wythnos gyntaf mis Ionawr." msgid "URL name can't be blank" -msgstr "" +msgstr "Ni all enw URL fod yn wag" msgid "Unable to change email address on {{site_name}}" -msgstr "" +msgstr "Yn methu newid cyfeiriad e-bost ar {{site_name}}" msgid "Unable to send a reply to {{username}}" -msgstr "" +msgstr "Yn methu anfon ateb i {{username}}" msgid "Unable to send follow up message to {{username}}" -msgstr "" +msgstr "Yn methu anfon neges ddilynol i {{username}}" msgid "Unexpected search result type" -msgstr "" +msgstr "Canlyniad chwiliad o fath annisgwyl" msgid "Unexpected search result type " -msgstr "" +msgstr "Canlyniad chwiliad o fath annisgwyl " msgid "Unfortunately we don't know the FOI\\nemail address for that authority, so we can't validate this.\\nPlease contact us to sort it out." -msgstr "" +msgstr "Yn anffodus, nid ydym yn gwybod y cyfeiriad ebost Rhyddid Gwybodaeth i'r awdurdod hwnnw, felly ni allwn ddilysu hwn. cysylltwch â ni os gwelwch yn dda i'w ddatrys." msgid "Unfortunately, we do not have a working {{info_request_law_used_full}}\\naddress for" -msgstr "" +msgstr "Yn anffodus, nid oes gennym cyfeiriad {{info_request_law_used_full}} sy'n gweithio ar gyfer" msgid "Unknown" -msgstr "" +msgstr "Anhysbys" msgid "Unsubscribe" -msgstr "" +msgstr "Dad-danysgrifio" msgid "Unusual response." -msgstr "" +msgstr "Ymateb anarferol." msgid "Update the status of this request" -msgstr "" +msgstr "Diweddaru statws y cais hwn" msgid "Update the status of your request to " -msgstr "" +msgstr "Diweddaru statws eich cais i " msgid "Upload FOI response" -msgstr "" +msgstr "Llwytho ymateb Rhyddid Gwybodaeth i fyny" msgid "Use OR (in capital letters) where you don't mind which word, e.g. commons OR lords" -msgstr "" +msgstr "Defnyddiwch OR (mewn prif lythrennau) lle nad oes ots gennych pa air, ee commons OR lords" msgid "Use quotes when you want to find an exact phrase, e.g. \"Liverpool City Council\"" -msgstr "" +msgstr "Defnyddiwch ddyfynodau pan fyddwch am ddod o hyd i'r union ymadrodd, ee \"Cardiff City Council\"" msgid "User" -msgstr "" +msgstr "Defnyddiwr" msgid "User info request sent alert" -msgstr "" +msgstr "Rhybudd i gais gwybodaeth defnyddiwr gael ei anfon" msgid "User – {{name}}" -msgstr "" +msgstr "Defnyddiwr - {{name}}" msgid "UserInfoRequestSentAlert|Alert type" -msgstr "" +msgstr "UserInfoRequestSentAlert|Math o rybudd" msgid "User|About me" -msgstr "" +msgstr "User|Amdanaf i" msgid "User|Admin level" -msgstr "" +msgstr "User|Lefel weinyddu" msgid "User|Ban text" -msgstr "" +msgstr "User|Gwahardd testun" msgid "User|Email" -msgstr "" +msgstr "User|E-bost" msgid "User|Email bounce message" -msgstr "" +msgstr "User|Neges fownsio e-bost" msgid "User|Email bounced at" -msgstr "" +msgstr "User|Bownsiodd e-bost bownsio ar" msgid "User|Email confirmed" -msgstr "" +msgstr "User|Cadarhawyd e-bost" msgid "User|Hashed password" -msgstr "" +msgstr "User|Cyfrinair stwnsh" msgid "User|Last daily track email" -msgstr "" +msgstr "User|Trac e-bost dyddiol olaf" msgid "User|Locale" -msgstr "" +msgstr "User|Locale" msgid "User|Name" -msgstr "" +msgstr "User|Enw" msgid "User|No limit" -msgstr "" +msgstr "User|Dim cyfyngiad" msgid "User|Receive email alerts" -msgstr "" +msgstr "User|Derbyn rhybuddion e-bost" msgid "User|Salt" -msgstr "" +msgstr "User|Salt" msgid "User|Url name" -msgstr "" +msgstr "User|Enw Url" msgid "Version {{version}}" -msgstr "" +msgstr "Fersiwn {{version}}" msgid "View FOI email address" -msgstr "" +msgstr "Gweld cyfeiriad e-bost i gais Rhyddid Gwybodaeth " msgid "View FOI email address for '{{public_body_name}}'" -msgstr "" +msgstr "Gweld cyfeiriad e-bost Rhyddid Gwybodaeth ar gyfer '{{public_body_name}}'" msgid "View FOI email address for {{public_body_name}}" -msgstr "" +msgstr "Gweld cyfeiriad e-bost Rhyddid Gwybodaeth ar gyfer '{{public_body_name}}'" msgid "View Freedom of Information requests made by {{user_name}}:" -msgstr "" +msgstr "Gweld ceisiadau Rhyddid Gwybodaeth a wnaed gan {{user_name}}:" msgid "View and search requests" -msgstr "" +msgstr "Gweld a chwilio ceisiadau" msgid "View authorities" msgstr "Gweld yr awdurdodau" @@ -2822,34 +2822,34 @@ msgid "View requests" msgstr "Gweld ceisiadau" msgid "Waiting clarification." -msgstr "" +msgstr "Yn disgwyl eglurhad." msgid "Waiting for an internal review by {{public_body_link}} of their handling of this request." -msgstr "" +msgstr "Yn disgwyl am adolygiad mewnol gan {{public_body_link}} o'u triniaeth o'r cais hwn." msgid "Waiting for the public authority to complete an internal review of their handling of the request" -msgstr "" +msgstr "Yn disgwyl i'r awdurdod cyhoeddus gwblhau adolygiad mewnol o'u triniaeth o'r cais" msgid "Waiting for the public authority to reply" -msgstr "" +msgstr "Yn aros i'r awdurdod cyhoeddus ymateb" msgid "Was the response you got to your FOI request any good?" -msgstr "" +msgstr "A oedd yr ymateb a gawsoch i'ch cais rhyddid gwybodaeth o ddefnydd?" msgid "We consider it is not a valid FOI request, and have therefore hidden it from other users." -msgstr "" +msgstr "Rydym yn ystyried nad yw'n gais Rhyddid Gwybodaeth dilys, ac felly wedi ei guddio oddi wrth ddefnyddwyr eraill." msgid "We consider it to be vexatious, and have therefore hidden it from other users." -msgstr "" +msgstr "Rydym yn ystyried ei fod yn flinderus, ac felly wedi ei guddio oddi wrth ddefnyddwyr eraill." msgid "We do not have a working request email address for this authority." -msgstr "" +msgstr "Nid oes gennym gyfeiriad e-bost sy'n gweithio i wneud cais i'r awdurdod hwn." msgid "We do not have a working {{law_used_full}} address for {{public_body_name}}." -msgstr "" +msgstr "Nid oes gennym gyfeiriad {{law_used_full}} sy'n gweithio ar gyfer {{public_body_name}}." msgid "We don't know whether the most recent response to this request contains\\n information or not\\n –\\n\tif you are {{user_link}} please sign in and let everyone know." -msgstr "" +msgstr "Nid ydym yn gwybod a yw'r ymateb mwyaf diweddar i'r cais hwn yn cynnwys gwybodaeth neuai peidio - os chi yw{{user_link}} mewngofnodwch a gadael i bawb wybod." msgid "We will not reveal your email address to anybody unless you or\\n the law tell us to (details). " msgstr "Ni fyddwn yn datgelu eich cyfeiriad e-bost i neb oni bai eich bod chi neu'r gyfraith yn dweud wrthym i wneud (manylion)." @@ -2861,136 +2861,136 @@ msgid "We will not reveal your email addresses to anybody unless you\\nor the la msgstr "Ni fyddwn yn datgelu eich cyfeiriad e-bost i neb oni bai eich bod chi neu'r gyfraith yn dweud wrthym i wneud." msgid "We're waiting for" -msgstr "" +msgstr "Rydym yn aros am" msgid "We're waiting for someone to read" -msgstr "" +msgstr "Rydym yn aros i rywun ddarllen" msgid "We've sent an email to your new email address. You'll need to click the link in\\nit before your email address will be changed." -msgstr "" +msgstr "Rydyn ni wedi anfon e-bost i'ch cyfeiriad e-bost newydd. Bydd angen i chi glicio ar y ddolen yn cyn y bydd eich cyfeiriad e-bost yn cael ei newid." msgid "We've sent you an email, and you'll need to click the link in it before you can\\ncontinue." -msgstr "" +msgstr "Rydyn ni wedi anfon e-bost atoch, a bydd angen i chi glicio ar y ddolen ynddo cyn y gallwch barhau." msgid "We've sent you an email, click the link in it, then you can change your password." -msgstr "" +msgstr "Rydyn ni wedi anfon e-bost atoch, cliciwch ar y ddolen ynddo, yna gallwch newid eich cyfrinair." msgid "What are you doing?" -msgstr "" +msgstr "Beth ydych chi'n ei wneud?" msgid "What best describes the status of this request now?" -msgstr "" +msgstr "Beth sy'n disgrifio orau statws y cais hwn nawr?" msgid "What information has been released?" -msgstr "" +msgstr "Pa wybodaeth sydd wedi cael ei rhyddhau?" msgid "What information has been requested?" -msgstr "" +msgstr "Pa wybodaeth a ofynnwyd amdani?" msgid "When you get there, please update the status to say if the response \\ncontains any useful information." -msgstr "" +msgstr "Pan fyddwch yn cyrraedd yno, diweddarwch y statws i ddweud a yw'r ymateb yn cynnwys unrhyw wybodaeth ddefnyddiol." msgid "When you receive the paper response, please help\\n others find out what it says:" -msgstr "" +msgstr "Pan fyddwch yn derbyn yr ymateb papur, helpwch eraill i gael gwybod beth y mae'n ei ddweud:" msgid "When you're done, come back here, reload this page and file your new request." -msgstr "" +msgstr "Pan fyddwch chi wedi gorffen, dewch yn ôl yma, ail-lwytho'r dudalen hon a ffeilio eich cais newydd." msgid "Which of these is happening?" -msgstr "" +msgstr "Pa un o'r rhain sy'n digwydd?" msgid "Who can I request information from?" -msgstr "" +msgstr "Gan bwy y gallaf ofyn cael y wybodaeth?" msgid "Withdrawn by the requester." -msgstr "" +msgstr "Fe'i tynnwyd yn ôl gan y ceisydd." msgid "Wk" -msgstr "" +msgstr "Wythnos" msgid "Would you like to see a website like this in your country?" -msgstr "" +msgstr "A fyddech yn hoffi gweld gwefan fel hon yn eich gwlad?" msgid "Write a reply" -msgstr "" +msgstr "Ysgrifennwch ateb" msgid "Write a reply to " -msgstr "" +msgstr "Ysgrifennwch ateb i " msgid "Write your FOI follow up message to " -msgstr "" +msgstr "Ysgrifennwch eich neges Rhyddid Gwybodaeth ddilynol i " msgid "Write your request in simple, precise language." msgstr "Ysgrifennwch eich cais mewn iaith syml, gryno." msgid "You" -msgstr "" +msgstr "Chi" msgid "You are already following new requests" -msgstr "" +msgstr "Rydych eisoes yn dilyn ceisiadau newydd" msgid "You are already following requests to {{public_body_name}}" -msgstr "" +msgstr "Rydych eisoes yn dilyn ceisiadau i {{public_body_name}}" msgid "You are already following things matching this search" -msgstr "" +msgstr "Rydych eisoes yn dilyn pethau sy'n cyfateb i'r chwiliad hwn" msgid "You are already following this person" -msgstr "" +msgstr "Rydych eisoes yn dilyn person hwn" msgid "You are already following this request" -msgstr "" +msgstr "Rydych eisoes yn dilyn cais hwn" msgid "You are already following updates about {{track_description}}" -msgstr "" +msgstr "Rydych eisoes yn dilyn diweddariadau am {{track_description}} " msgid "You are currently receiving notification of new activity on your wall by email." -msgstr "" +msgstr "Ar hyn o bryd yr ydych yn derbyn hysbysiad o weithgarwch newydd ar eich wal drwy e-bost." msgid "You are following all new successful responses" -msgstr "" +msgstr "Rydych yn dilyn yr holl ymatebion llwyddiannus newydd " msgid "You are no longer following {{track_description}}." -msgstr "" +msgstr "Nid ydych chi bellach yn dilyn {{track_description}} " msgid "You are now following updates about {{track_description}}" -msgstr "" +msgstr "Yr ydych yn awr yn dilyn diweddariadau am {{track_description}} " msgid "You can complain by" -msgstr "" +msgstr "Gallwch gwyno drwy" msgid "You can change the requests and users you are following on your profile page." -msgstr "" +msgstr "Gallwch newid pa geisiadau a defnyddwyr rydych yn dilyn ar eich tudalen broffil." msgid "You can get this page in computer-readable format as part of the main JSON\\npage for the request. See the API documentation." -msgstr "" +msgstr "Gallwch gael y dudalen hon ar ffurf gyfrifiadurol-darllenadwy fel rhan o'r prif dudalen JSON am y cais. Gweler dogfennaeth yr API." msgid "You can only request information about the environment from this authority." -msgstr "" +msgstr "Ceisio gwybodaeth am yr amgylchedd yn unig y gallwch ei wneud gan yr awdurdod hwn." msgid "You have a new response to the {{law_used_full}} request " -msgstr "" +msgstr "Mae gennych ymateb newydd i'r cais {{law_used_full}} " msgid "You have found a bug. Please contact us to tell us about the problem" -msgstr "" +msgstr "Rydych wedi dod o hyd i fyg. Cysylltwch â ni i ddweud wrthym am y broblem" msgid "You have hit the rate limit on new requests. Users are ordinarily limited to {{max_requests_per_user_per_day}} requests in any rolling 24-hour period. You will be able to make another request in {{can_make_another_request}}." -msgstr "" +msgstr "Rydych chi wedi cyrraedd y terfyn cyfradd ar geisiadau newydd. Fel arfer cyfyngir defnyddwyr i {{max_requests_per_user_per_day}} cais mewn unrhyw gyfnod treigl 24-awr. Byddwch yn gallu gwneud cais arall ymhen {{ can_make_another_request}}." msgid "You have made no Freedom of Information requests using this site." -msgstr "" +msgstr "Nid ydych wedi gwneud unrhyw geisiadau Rhyddid Gwybodaeth drwy ddefnyddio'r wefan hon." msgid "You have now changed the text about you on your profile." -msgstr "" +msgstr "Rydych yn awr wedi newid y testun amdanoch chi ar eich proffil." msgid "You have now changed your email address used on {{site_name}}" -msgstr "" +msgstr "Rydych bellach wedi newid eich cyfeiriad e-bost a ddefnyddir ar {{site_name}}" msgid "You just tried to sign up to {{site_name}}, when you\\nalready have an account. Your name and password have been\\nleft as they previously were.\\n\\nPlease click on the link below." -msgstr "" +msgstr "Rydych newydd geisio i gofrestru ar {{site_name}}, er bod gennych gyfrif eisoes. Mae'ch enw a'ch cyfrinair wedi eu gadael fel yr oedden. Cliciwch ar y ddolen isod." msgid "You know what caused the error, and can suggest a solution, such as a working email address." -msgstr "" +msgstr "Rydych yn gwybod beth achosodd y gwall, a gallwch awgrymu datrrysiad, fel cyfeiriad e-bost sy'n gweithio." msgid "You may include attachments. If you would like to attach a\\n file too large for email, use the form below." msgstr "" @@ -3242,7 +3242,7 @@ msgid "by {{user_link_absolute}}" msgstr "" msgid "comments" -msgstr "" +msgstr "sylwadau" msgid "containing your postal address, and asking them to reply to this request.\\n Or you could phone them." msgstr "" @@ -3332,7 +3332,7 @@ msgid "requesting an internal review" msgstr "" msgid "requests" -msgstr "" +msgstr "ceisiadau" msgid "requests which are {{list_of_statuses}}" msgstr "" diff --git a/locale/de/app.po b/locale/de/app.po index c090a066f..71fdfefad 100644 --- a/locale/de/app.po +++ b/locale/de/app.po @@ -13,7 +13,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: German (http://www.transifex.com/projects/p/alaveteli/language/de/)\n" "Language: de\n" diff --git a/locale/en_IE/app.po b/locale/en_IE/app.po index 28ec39424..ec48993f5 100644 --- a/locale/en_IE/app.po +++ b/locale/en_IE/app.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: English (Ireland) (http://www.transifex.com/projects/p/alaveteli/language/en_IE/)\n" "Language: en_IE\n" diff --git a/locale/es/app.po b/locale/es/app.po index 88140765b..7ec55180b 100644 --- a/locale/es/app.po +++ b/locale/es/app.po @@ -16,7 +16,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Spanish (http://www.transifex.com/projects/p/alaveteli/language/es/)\n" "Language: es\n" diff --git a/locale/eu/app.po b/locale/eu/app.po index 7772b517a..4bbc76246 100644 --- a/locale/eu/app.po +++ b/locale/eu/app.po @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Basque (http://www.transifex.com/projects/p/alaveteli/language/eu/)\n" "Language: eu\n" diff --git a/locale/fi/app.po b/locale/fi/app.po index 49729aeba..b6353d309 100644 --- a/locale/fi/app.po +++ b/locale/fi/app.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Finnish (http://www.transifex.com/projects/p/alaveteli/language/fi/)\n" "Language: fi\n" diff --git a/locale/fr/app.po b/locale/fr/app.po index 388041060..b72f71dea 100644 --- a/locale/fr/app.po +++ b/locale/fr/app.po @@ -3,6 +3,7 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Adrien Chauvet , 2013 # skenaja , 2011 # andreas.pavlou , 2013 # andreas.pavlou , 2013 @@ -31,8 +32,8 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" -"Last-Translator: mysociety \n" +"PO-Revision-Date: 2013-11-24 21:52+0000\n" +"Last-Translator: Adrien Chauvet \n" "Language-Team: French (http://www.transifex.com/projects/p/alaveteli/language/fr/)\n" "Language: fr\n" "MIME-Version: 1.0\n" @@ -1037,7 +1038,7 @@ msgid "If you are still having trouble, please contact uscontact us." msgid "If you are the requester, then you may sign in to view the message." -msgstr "" +msgstr "Si vous êtes l'auteur de la demande, connectez-vous pour voir le message." msgid "If you are the requester, then you may sign in to view the request." msgstr "Si vous êtes le demandeur, alors vous pouvez sign in voir à la demande." @@ -1109,10 +1110,10 @@ msgid "IncomingMessage|Mail from domain" msgstr "IncomingMessage|Mail from domain" msgid "IncomingMessage|Prominence" -msgstr "" +msgstr "Nouveau message|important" msgid "IncomingMessage|Prominence reason" -msgstr "" +msgstr "Nouveau message | Objet important" msgid "IncomingMessage|Sent at" msgstr "IncomingMessage|Envoyé à" @@ -1310,7 +1311,7 @@ msgid "Message" msgstr "Message" msgid "Message has been removed" -msgstr "" +msgstr "Le message a été supprimé" msgid "Message sent using {{site_name}} contact form, " msgstr "Message envoyé en utilisant le formulaire {{site_name}}" @@ -1430,7 +1431,7 @@ msgid "Now preview your message asking for an internal review" msgstr "Maintenant prévisualisez votre message demandant un examen interne" msgid "Number of requests" -msgstr "" +msgstr "Nombre de requêtes" msgid "OR remove the existing photo" msgstr "Ou effacer la photo existante" @@ -1496,10 +1497,10 @@ msgid "OutgoingMessage|Message type" msgstr "OutgoingMessage|Message type" msgid "OutgoingMessage|Prominence" -msgstr "" +msgstr "Message sortant |Important" msgid "OutgoingMessage|Prominence reason" -msgstr "" +msgstr "Message sortant | Objet important" msgid "OutgoingMessage|Status" msgstr "OutgoingMessage|Status" @@ -1529,10 +1530,10 @@ msgid "People {{start_count}} to {{end_count}} of {{total_count}}" msgstr "Personnes {{start_count}} à {{end_count}} de {{total_count}}" msgid "Percentage of requests that are overdue" -msgstr "" +msgstr "Taux de requêtes en retard" msgid "Percentage of total requests" -msgstr "" +msgstr "Taux de requêtes total" msgid "Photo of you:" msgstr "Votre photo :" @@ -1550,7 +1551,7 @@ msgid "Please" msgstr "S'il vous plait" msgid "Please contact us if you have any questions." -msgstr "" +msgstr "Merci de nous contacter si vous avez des questions." msgid "Please get in touch with us so we can fix it." msgstr "Veuillez nous contacter afin que nous puissions le corriger." @@ -1757,10 +1758,10 @@ msgid "ProfilePhoto|Draft" msgstr "ProfilePhoto|Draft" msgid "Public Bodies" -msgstr "" +msgstr "Organismes publics" msgid "Public Body Statistics" -msgstr "" +msgstr "Statistiques de l'organisme public" msgid "Public authorities" msgstr "Organismes publics" @@ -1775,19 +1776,19 @@ msgid "Public authority – {{name}}" msgstr "Organisme public – {{name}}" msgid "Public bodies that most frequently replied with \"Not Held\"" -msgstr "" +msgstr "Organismes publics ayant le plus souvent répondu par \"Non approuvé\"" msgid "Public bodies with most overdue requests" -msgstr "" +msgstr "Organismes publics ayant le plus de requêtes en retard" msgid "Public bodies with the fewest successful requests" -msgstr "" +msgstr "Organismes publics ayant le moins de requêtes réussies" msgid "Public bodies with the most requests" -msgstr "" +msgstr "Organismes publics ayant le plus de requêtes" msgid "Public bodies with the most successful requests" -msgstr "" +msgstr "Organismes publics ayant le plus de requêtes réussies" msgid "Public body" msgstr "Organisme public" @@ -1817,16 +1818,16 @@ msgid "PublicBody|Info requests count" msgstr "PublicBody|Info requests count" msgid "PublicBody|Info requests not held count" -msgstr "" +msgstr "Organisme public | Compteur de requêtes non approuvées" msgid "PublicBody|Info requests overdue count" -msgstr "" +msgstr "Organisme public | Compteur de requête en retard" msgid "PublicBody|Info requests successful count" -msgstr "" +msgstr "Organisme public | Compteur de requêtes réussies" msgid "PublicBody|Info requests visible classified count" -msgstr "" +msgstr "Organisme public | Compteur classé d'info requête visible" msgid "PublicBody|Last edit comment" msgstr "PublicBody|Last edit comment" @@ -1940,10 +1941,10 @@ msgid "Requested on {{date}}" msgstr "Demandé le {{date}}" msgid "Requests are considered overdue if they are in the 'Overdue' or 'Very Overdue' states." -msgstr "" +msgstr "Les requêtes sont considérées comme dépassées si elles sont classées 'en retard' ou 'très en retard'." msgid "Requests are considered successful if they were classified as either 'Successful' or 'Partially Successful'." -msgstr "" +msgstr "Les requêtes sont considérées comme réussies si elles sont classées en tant que 'Réussies' ou 'Partiellement réussies'." msgid "Requests for personal information and vexatious requests are not considered valid for FOI purposes (read more)." msgstr "Les demandes de renseignements personnels et les demandes abusives ne sont pas considérées comme demandes valides (Lire plus)." @@ -2254,19 +2255,19 @@ msgid "The authority would like to / has responded by post to t msgstr "L'autorité aimerait répondre, ou a répondu, par la Poste à cette demande." msgid "The classification of requests (e.g. to say whether they were successful or not) is done manually by users and administrators of the site, which means that they are subject to error." -msgstr "" +msgstr "La classification des requêtes (e.g. si elles sont réussies ou non) étant effectuée manuellement par les utilisateurs et administrateurs du site, des erreurs peuvent apparaître." msgid "The email that you, on behalf of {{public_body}}, sent to\\n{{user}} to reply to an {{law_used_short}}\\nrequest has not been delivered." msgstr "Le message que vous , au nom de {{public_body}}, avez envoyé à \\n{{user}} en réponse à la demande {{law_used_short}}\\n n'a pas été delivré" msgid "The error bars shown are 95% confidence intervals for the hypothesized underlying proportion (i.e. that which you would obtain by making an infinite number of requests through this site to that authority). In other words, the population being sampled is all the current and future requests to the authority through this site, rather than, say, all requests that have been made to the public body by any means." -msgstr "" +msgstr "Les barres d'erreurs affichées sont précises à 95% pour les proportions sous-jacentes supposées (i.e. celles que vous obtiendriez en effectuant un nombre infini de requêtes via ce site aux autorités). En d'autres mots, la population échantillonnée concerne toute les requêtes futures et actuelles aux autorités via ce site plutôt que, par exemple, toutes les requêtes effectuées aux organismes publics par tous les moyens." msgid "The page doesn't exist. Things you can try now:" msgstr "La page n'existe pas . Vous pouvez essayer de :" msgid "The percentages are calculated with respect to the total number of requests, which includes invalid requests; this is a known problem that will be fixed in a later release." -msgstr "" +msgstr "Les taux sont calculés selon le nombre total de requêtes, qui inclue les requêtes invalides ; c'est un problème connu qui sera fixé dans une prochaine version." msgid "The public authority does not have the information requested" msgstr "L'autorité administrative n'a pas les informations demandées" @@ -2412,7 +2413,7 @@ msgid "There was an error with the words you entered, please try again." msgstr "Il y a une erreur liée aux mots que vous avez saisis, veuillez réessayer." msgid "There was no data calculated for this graph yet." -msgstr "" +msgstr "Il n'y a actuellement pas de données calculées pour ce graphique." msgid "There were no requests matching your query." msgstr "Aucune demande d'information ne correspond à votre recherche." @@ -2421,7 +2422,7 @@ msgid "There were no results matching your query." msgstr "Aucun résultat correspondant à votre recherche." msgid "These graphs were partly inspired by some statistics that Mark Goodge produced for WhatDoTheyKnow, so thanks are due to him." -msgstr "" +msgstr "Ces graphiques son issus en partie des statistiques que Mark Goodge a produit pour WhatDoTheyKnow, donc le mérite lui revient." msgid "They are going to reply by post" msgstr "Ils vont répondre par courrier" @@ -2469,25 +2470,25 @@ msgid "This is your own request, so you will be automatically emailed when new r msgstr "Ceci est votre propre demande, afin que vous receviez automatiquement un e-mail dès que de nouvelles réponses arrivent." msgid "This message has been hidden." -msgstr "" +msgstr "Ce message a été masqué." msgid "This message has been hidden. There are various reasons why we might have done this, sorry we can't be more specific here." -msgstr "" +msgstr "Ce message a été masqué. Plusieurs raisons peuvent expliquer ce phénomène, désolé de ne pouvoir vous en dire davantage ici." msgid "This message has prominence 'hidden'. You can only see it because you are logged in as a super user." -msgstr "" +msgstr "Ce message possède le statu 'caché'. Vous pouvez le voir uniquement car vous êtes connecté en tant que super-utilisateur." msgid "This message has prominence 'hidden'. {{reason}} You can only see it because you are logged in as a super user." -msgstr "" +msgstr "Ce message possède le statut 'caché'. {{Raison}} Vous pouvez le voir uniquement car vous êtes connecté en tant que super-utilisateur." msgid "This message is hidden, so that only you, the requester, can see it. Please contact us if you are not sure why." -msgstr "" +msgstr "Ce message est caché, afin que seulement vous, le demandeur, puissiez le voir. Merci de nous contacter si vous ne savez pas pourquoi." msgid "This message is hidden, so that only you, the requester, can see it. {{reason}}" -msgstr "" +msgstr "Ce message est caché, afin que seulement vous, le demandeur, puissiez le voir. {{raison}}" msgid "This page of public body statistics is currently experimental, so there are some caveats that should be borne in mind:" -msgstr "" +msgstr "Cette page des statistiques des organismes publics est actuellement expérimentale, c'est pourquoi certaines mises en gardes doivent être portées à votre connaissance :" msgid "This particular request is finished:" msgstr "Cette demande particulière est terminée:" diff --git a/locale/fr_CA/app.po b/locale/fr_CA/app.po index d869db194..0477869b5 100644 --- a/locale/fr_CA/app.po +++ b/locale/fr_CA/app.po @@ -19,7 +19,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: French (Canada) (http://www.transifex.com/projects/p/alaveteli/language/fr_CA/)\n" "Language: fr_CA\n" diff --git a/locale/gl/app.po b/locale/gl/app.po index ac360f29d..d2bb6197e 100644 --- a/locale/gl/app.po +++ b/locale/gl/app.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Galician (http://www.transifex.com/projects/p/alaveteli/language/gl/)\n" "Language: gl\n" diff --git a/locale/he_IL/app.po b/locale/he_IL/app.po index fa3cf388b..1497ac25f 100644 --- a/locale/he_IL/app.po +++ b/locale/he_IL/app.po @@ -21,7 +21,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Hebrew (Israel) (http://www.transifex.com/projects/p/alaveteli/language/he_IL/)\n" "Language: he_IL\n" diff --git a/locale/hr/app.po b/locale/hr/app.po index d18f11f98..3716d41a9 100644 --- a/locale/hr/app.po +++ b/locale/hr/app.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Croatian (http://www.transifex.com/projects/p/alaveteli/language/hr/)\n" "Language: hr\n" diff --git a/locale/hr_HR/app.po b/locale/hr_HR/app.po index 3375c93dd..635379c4d 100644 --- a/locale/hr_HR/app.po +++ b/locale/hr_HR/app.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Croatian (Croatia) (http://www.transifex.com/projects/p/alaveteli/language/hr_HR/)\n" "Language: hr_HR\n" diff --git a/locale/hu_HU/app.po b/locale/hu_HU/app.po index 43e0f6418..2819e699d 100644 --- a/locale/hu_HU/app.po +++ b/locale/hu_HU/app.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Hungarian (Hungary) (http://www.transifex.com/projects/p/alaveteli/language/hu_HU/)\n" "Language: hu_HU\n" diff --git a/locale/id/app.po b/locale/id/app.po index 33d7ef797..be8301454 100644 --- a/locale/id/app.po +++ b/locale/id/app.po @@ -15,7 +15,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Indonesian (http://www.transifex.com/projects/p/alaveteli/language/id/)\n" "Language: id\n" diff --git a/locale/it/app.po b/locale/it/app.po index fdbce2732..4c9d1a684 100644 --- a/locale/it/app.po +++ b/locale/it/app.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Italian (http://www.transifex.com/projects/p/alaveteli/language/it/)\n" "Language: it\n" diff --git a/locale/mk_MK/app.po b/locale/mk_MK/app.po new file mode 100644 index 000000000..d6fc877d6 --- /dev/null +++ b/locale/mk_MK/app.po @@ -0,0 +1,3536 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: alaveteli\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-11-08 12:10+0000\n" +"PO-Revision-Date: 2013-12-08 21:16+0000\n" +"Last-Translator: slobodenpristap\n" +"Language-Team: Macedonian (Macedonia) (http://www.transifex.com/projects/p/alaveteli/language/mk_MK/)\n" +"Language: mk_MK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n" + +msgid " This will appear on your {{site_name}} profile, to make it\\n easier for others to get involved with what you're doing." +msgstr " Ова ќе се прикаже на вашиот {{site_name}} профил, за да го олесни\\n вклучувањето на други во она што го работите." + +msgid " (no ranty politics, read our moderation policy)" +msgstr " (без нападно обраќање, прочитајте ја нашата уредувачка политика)" + +msgid " (patience, especially for large files, it may take a while!)" +msgstr " (бидете трпеливи, особено за големи датотеки, процесот може да потрае!)" + +msgid " (you)" +msgstr " (вие)" + +msgid " - view and make Freedom of Information requests" +msgstr " - погледнете и креирајте барање за слободен пристап до информации" + +msgid " - wall" +msgstr " - ѕид" + +msgid " Note:\\n We will send you an email. Follow the instructions in it to change\\n your password." +msgstr " Забелешка:\\n Ќе ви испратиме е-пошта. Следете ги инструкциите од пораката за да ја промените\\n вашата лозинка." + +msgid " Privacy note: Your email address will be given to" +msgstr " Забелешка за приватност: Вашата адреса за е-пошта ќе биде дадена на" + +msgid " Summarise the content of any information returned. " +msgstr " Резимирајте ја содржината од вратената информација. " + +msgid " Advise on how to best clarify the request." +msgstr " Препорачајте како најдобро да се објасни барањето." + +msgid " Ideas on what other documents to request which the authority may hold. " +msgstr "Идеи за барање на други документи кои надлежниот орган можеби ги поседува. " + +msgid " If you know the address to use, then please send it to us.\\n You may be able to find the address on their website, or by phoning them up and asking." +msgstr "Ако ја знаете адресата која треба да се користи, тогаш ве молиме испратете ни ја.\\n Можеби ќе успеете да ја најдете адресата на нивната интернет-страна или ако ја побарате од нив со јавување по телефон. " + +msgid " Include relevant links, such as to a campaign page, your blog or a\\n twitter account. They will be made clickable. \\n e.g." +msgstr " Вклучете релевантни врски, како што се страна за кампања, вашиот блог или\\n Твитер профил. Истите ќе бидат линкови. \\n на пр." + +msgid " Link to the information requested, if it is already available on the Internet. " +msgstr " Врска до бараната информација, доколку е веќе достапна на интернет. " + +msgid " Offer better ways of wording the request to get the information. " +msgstr " Понуди подобри начини за формулација на барањето за добивање на информацијата. " + +msgid " Say how you've used the information, with links if possible." +msgstr " Изјаснете се како ја употребивте информацијата и вклучете линкови, доколку е возможно." + +msgid " Suggest where else the requester might find the information. " +msgstr " Предложете каде на друго место барателот може да најде информации. " + +msgid " What are you investigating using Freedom of Information? " +msgstr " Што истражувате преку употреба на слободниот пристап до информации? " + +msgid " You are already being emailed updates about the request." +msgstr " Веќе ви се испратени новости за барањето по е-пошта. " + +msgid " You will also be emailed updates about the request." +msgstr " Исто така ќе ви бидат испратени новости за барањето по е-пошта." + +msgid " made by " +msgstr " креирано од " + +msgid " or " +msgstr " или " + +msgid " when you send this message." +msgstr " кога ќе ја испратите оваа порака." + +msgid "\"Hello! We have an important message for visitors outside {{country_name}}\"" +msgstr "\"Здраво! Имаме важна порака за посетителите надвор од {{country_name}}\"" + +msgid "'Crime statistics by ward level for Wales'" +msgstr "'Криминална статистика по области за Македонија'" + +msgid "'Pollution levels over time for the River Tyne'" +msgstr "'Ниво на загадување на реката Вардар со тек на времето'" + +msgid "'{{link_to_authority}}', a public authority" +msgstr "'{{link_to_authority}}', имател" + +msgid "'{{link_to_request}}', a request" +msgstr "'{{link_to_request}}', барање" + +msgid "'{{link_to_user}}', a person" +msgstr "'{{link_to_user}}', личност" + +msgid "*unknown*" +msgstr "*непознат*" + +msgid ",\\n\\n\\n\\nYours,\\n\\n{{user_name}}" +msgstr ",\\n\\n\\n\\nВаш,\\n\\n{{user_name}}" + +msgid "- or -" +msgstr "- или -" + +msgid "1. Select an authority" +msgstr "1. Изберете имател на информација" + +msgid "2. Ask for Information" +msgstr "2. Побарајте информација" + +msgid "3. Now check your request" +msgstr "3. Проверете го вашето барање" + +msgid "Browse all or ask us to add one." +msgstr "Прелистајте ги сите или побарајте ние да додадеме." + +msgid "Add an annotation (to help the requester or others)" +msgstr "Додајте белешка (за да му помогнете на барателот или на другите)" + +msgid "Sign in to change password, subscriptions and more ({{user_name}} only)" +msgstr "Најавете се за да ја промените лозинка, претплатата и друго (само {{user_name}})" + +msgid "

    All done! Thank you very much for your help.

    There are more things you can do to help {{site_name}}.

    " +msgstr "

    Готово! Благодариме за вашата помош.

    Постојат повеќе работи кои може да ги направите за да помогнете {{site_name}}.

    " + +msgid "

    Thank you! Here are some ideas on what to do next:

    \\n
      \\n
    • To send your request to another authority, first copy the text of your request below, then find the other authority.
    • \\n
    • If you would like to contest the authority's claim that they do not hold the information, here is\\n how to complain.\\n
    • \\n
    • We have suggestions\\n on other means to answer your question.\\n
    • \\n
    " +msgstr "

    Ви благодариме! Следуваат неколку идеи што следно може да направите:

    \\n
      \\n
    • За да го испратите вашето барање до друг имател, прво копирајте го текстот од вашето барање подолу, а потоа најдете го другиот имател.
    • \\n
    • Ако сакате да го оспорите тврдењето на имателите дека тие не ги поседуваат информациите, еве \\n како да се пожалите.\\n
    • \\n
    • Имаме предлози\\n за други начини за добивање одговор на вашето прашање.\\n
    • \\n
    " + +msgid "

    Thank you! Hope you don't have to wait much longer.

    By law, you should have got a response promptly, and normally before the end of {{date_response_required_by}}.

    " +msgstr "

    Ви благодариме! Се надеваме нема да чекате уште многу.

    Според законот, требаше да добиете одговор брзо, обично пред крајот на {{date_response_required_by}}.

    " + +msgid "

    Thank you! Hopefully your wait isn't too long.

    By law, you should get a response promptly, and normally before the end of \\n{{date_response_required_by}}.

    " +msgstr "

    Ви благодариме! Се надеваме нема да чекате премногу.

    Според законот, требаше да добиете одговор брзо, обично пред крајот на \\n{{date_response_required_by}}.

    " + +msgid "

    Thank you! Hopefully your wait isn't too long.

    You should get a response within {{late_number_of_days}} days, or be told if it will take longer (details).

    " +msgstr "

    Ви благодариме! Се надеваме нема да чекате премногу.

    Треба да добиете одговор во рок од {{late_number_of_days}} дена или да бидете известени ако е потребно повеќе време (детали).

    " + +msgid "

    Thank you! Your request is long overdue, by more than {{very_late_number_of_days}} working days. Most requests should be answered within {{late_number_of_days}} working days. You might like to complain about this, see below.

    " +msgstr "

    Ви благодариме! Вашето барање го надмина рокот за повеќе од {{very_late_number_of_days}} работни дена. Повеќето барања треба да се одговорени во рок од {{late_number_of_days}} работни дена. Можеби ќе сакате да испратите жалба за ова, погледнете подолу.

    " + +msgid "

    Thanks for changing the text about you on your profile.

    \\n

    Next... You can upload a profile photograph too.

    " +msgstr "

    Во благодариме за менување на текстот за вас на вашиот профил.

    \\n

    Следно... Може да прикачите и ваша фотографија.

    " + +msgid "

    Thanks for updating your profile photo.

    \\n

    Next... You can put some text about you and your research on your profile.

    " +msgstr "

    Во благодариме за ажурирање на фотографијата од профилот.

    \\n

    Следно... Може да внесете текст за вас и за вашето истражување на вашиот профил.

    " + +msgid "

    We recommend that you edit your request and remove the email address.\\n If you leave it, the email address will be sent to the authority, but will not be displayed on the site.

    " +msgstr "

    Препорачуваме да го уредите вашето барање и да ја тргнете адресата за е-пошта.\\n Ако го оставите како што е, адресата за е-пошта ќе биде пратена до имателот, но истата нема повеќе да биде прикажана на интернет страната.

    " + +msgid "

    We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

    " +msgstr "

    Драго ни е што ги добивте сите информации кои ги баравте. Ако ги употребите или пишувате за овие информации, ве молиме вратете се со цел да додадете белешка подолу каде ќе наведете како сте ги искористиле.

    " + +msgid "

    We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

    If you found {{site_name}} useful, make a donation to the charity which runs it.

    " +msgstr "

    Драго ни е што ги добивте сите информации кои ги баравте. Ако ги употребите или пишувате за овие информации, ве молиме вратете се со цел да додадете белешка подолу каде ќе наведете како сте ги искористиле.

    Ако {{site_name}} ви беше од корист, донирајте на организацијата која го раководи.

    " + +msgid "

    We're glad you got some of the information that you wanted. If you found {{site_name}} useful, make a donation to the charity which runs it.

    If you want to try and get the rest of the information, here's what to do now.

    " +msgstr "

    Драго ни е што добивте дел информации кои ги баравте. Ако {{site_name}} ви беше од корист, донирајте на организацијата која го раководи.

    Ако сакате да се обидете да ги добиете и останатите информации, еве што треба да направите следно.

    " + +msgid "

    We're glad you got some of the information that you wanted.

    If you want to try and get the rest of the information, here's what to do now.

    " +msgstr "

    Драго ни е што добивте дел од информациите кои ги баравте.

    Ако сакате да се обидете да ги добиете останатите информации, еве што треба да направите следно.

    " + +msgid "

    You do not need to include your email in the request in order to get a reply (details).

    " +msgstr "

    Нема потреба од вклучување на вашата е-пошта во барањето со цел да добиете одговор. (детали).

    " + +msgid "

    You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (details).

    " +msgstr "

    Нема потреба од вклучување на вашата е-пошта во барањето со цел да добиете одговор, бидејќи ние ќе ја побараме во следниот екран (детали).

    " + +msgid "

    Your request contains a postcode. Unless it directly relates to the subject of your request, please remove any address as it will appear publicly on the Internet.

    " +msgstr "

    Вашето барање содржи поштенски број. Ве молиме тргнете било каква адреса, освен ако директно се однесува на барањето, бидејќи ќе се појави јавно на интернет.

    " + +msgid "

    Your {{law_used_full}} request has been sent on its way!

    \\n

    We will email you when there is a response, or after {{late_number_of_days}} working days if the authority still hasn't\\n replied by then.

    \\n

    If you write about this request (for example in a forum or a blog) please link to this page, and add an\\n annotation below telling people about your writing.

    " +msgstr "

    Вашето {{law_used_full}} барање е испратено!

    \\n

    Ќе ви испратиме е-пошта кога ќе има одговор или после {{late_number_of_days}} работни дена доколку имателот се уште нема\\n одговорено до тогаш.

    \\n

    Ако пишувате за ова барање (на пример на форум или блог) ве молам направете врска со оваа страница и додадете\\n белешка подолу каде што ќе кажете на корисниците за што пишувате.

    " + +msgid "

    {{site_name}} is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.

    {{read_only}}

    " +msgstr "

    {{site_name}} во моментов се ажурира. Можете да ги прегледате само постоечките барања. Не може да креирате нови барања, да реплицирате или да додавате белешки или на било кој начин да ја менувате базата на податоци.

    {{read_only}}

    " + +msgid "If you use web-based email or have \"junk mail\" filters, also check your\\nbulk/spam mail folders. Sometimes, our messages are marked that way.\\n

    " +msgstr "Доколку користите веб-базирана е-пошта или пак имате \"junk mail\" филтри, проверете го и вашето\\nсандаче за спам пораки. Понекогаш, нашите пораки се означени како спам пораки.\\n

    " + +msgid " Can I request information about myself?\\n\t\t\tNo! (Click here for details)" +msgstr " Дали може да побарам информации за себе?\\n\t\t\tНе! (Кликнете тука за детали)" + +msgid "commented_by:tony_bowden to search annotations made by Tony Bowden, typing the name as in the URL." +msgstr "commented_by:tony_bowden за да пребарате белешки креирани од Tony Bowden, со внесување на името како што е во URL врската." + +msgid "filetype:pdf to find all responses with PDF attachments. Or try these: {{list_of_file_extensions}}" +msgstr "filetype:pdf за да ги најдете сите одговори кои содржат PDF прилози. Или пробајте со: {{list_of_file_extensions}}" + +msgid "request: to restrict to a specific request, typing the title as in the URL." +msgstr "request: за да ограничите на одредено барање, со внесување на насловот како што е во URL врската." + +msgid "requested_by:julian_todd to search requests made by Julian Todd, typing the name as in the URL." +msgstr "requested_by:julian_todd за да пребарате барања креирани од Julian Todd, со внесување на името како што е во URL врската." + +msgid "requested_from:home_office to search requests from the Home Office, typing the name as in the URL." +msgstr "requested_from:vlada за да се пребараат барања од Влада на РМ, со внесување на името како што е во URL врската." + +msgid "status: to select based on the status or historical status of the request, see the table of statuses below." +msgstr "status: за да одберете во зависност од статусот на барањето, погледнете ја табелата на статуси подолу." + +msgid "tag:charity to find all public authorities or requests with a given tag. You can include multiple tags, \\n and tag values, e.g. tag:openlylocal AND tag:financial_transaction:335633. Note that by default any of the tags\\n can be present, you have to put AND explicitly if you only want results them all present." +msgstr "tag:charity за да најдете сите иматели или барања за одреден клучен збор. Може да вклучите повеќе клучни зборови, \\n и нивни вредности, на пр. tag:openlylocal AND tag:financial_transaction:335633. Забележете дека стандардно било кој од клучните зборови\\n може да се внесат, но мора да внесете AND експлицитно ако сакате резултати да ги содржат сите наведени клучни зборови." + +msgid "variety: to select type of thing to search for, see the table of varieties below." +msgstr "variety: за да одберете тип на информација која се пребарува, погледнете ја табелата со можности подолу." + +msgid "Advice on how to get a response that will satisfy the requester. " +msgstr "Совет за како да се добие одговор кој ќе биде задоволителен за барателот. " + +msgid "All the information has been sent" +msgstr "Сите информации се испратени" + +msgid "Anything else, such as clarifying, prompting, thanking" +msgstr "Уште нешто, како што е појаснување, потсетување, заблагодарување" + +msgid "Caveat emptor! To use this data in an honourable way, you will need \\na good internal knowledge of user behaviour on {{site_name}}. How, \\nwhy and by whom requests are categorised is not straightforward, and there will\\nbe user error and ambiguity. You will also need to understand FOI law, and the\\nway authorities use it. Plus you'll need to be an elite statistician. Please\\ncontact us with questions." +msgstr "Внимание! За да ги користите овие податоци на чесен начин, ќе ви биде потребно \\nдобро познавање за однесувањето на корисниците на {{site_name}}. Како, \\nзошто и од кого барањата кои се категоризирани не се јасни и ќе\\nпостои човечка грешка и двосмисленост. Исто така ќе треба да го разберете законот за слободен пристап до информации од јавен карактер, како и\\nначинот на кои имателите на информации го користат. Дополнително, ќе треба да бидете елитен статистичар. Ве молиме\\nконтактирајте не доколку имате прашања." + +msgid "Clarification has been requested" +msgstr "Беше побарано појаснување" + +msgid "No response has been received\\n (maybe there's just an acknowledgement)" +msgstr "Не беше добиен никаков одговор\\n (можеби има само потврда за прием)" + +msgid "Note: Because we're testing, requests are being sent to {{email}} rather than to the actual authority." +msgstr "Забелешка: Бидејќи тестираме, барањата се праќаат до {{email}} наместо до имателот." + +msgid "Note: You're sending a message to yourself, presumably\\n to try out how it works." +msgstr "Забелешка: Испраќате е-пошта до самите себе, веројатно\\n за да пробате како функционира системот." + +msgid "Note:\\n We will send an email to your new email address. Follow the\\n instructions in it to confirm changing your email." +msgstr "Забелешка:\\n Ќе испратиме е-пошта до вашата нова адреса за е-пошта. Следете ги\\n насоките во неа за да го потврдите менувањето на адресата." + +msgid "Privacy note: If you want to request private information about\\n yourself then click here." +msgstr "Забелешка за приватност: Ако сакате да побарате приватни информации за\\n себе, тогаш кликнете тука." + +msgid "Privacy note: Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." +msgstr "Забелешка за приватност: Вашата фотографија ќе биде јавно прикажана на интернет,\\n секогаш кога ќе сакате нешто да направите на {{site_name}}." + +msgid "Privacy warning: Your message, and any response\\n to it, will be displayed publicly on this website." +msgstr "Забелешка за приватност: Вашата порака и секој одговор\\n на неа, ќе бидат јавно прикажани на оваа интернет-страна." + +msgid "Some of the information has been sent " +msgstr "Некои од информациите беа испратени " + +msgid "Thank the public authority or " +msgstr "Заблагодарете му се ја имателот или " + +msgid "did not have the information requested." +msgstr "ја немаше бараната информација." + +msgid "A follow up to {{request_title}} was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "Реакција на {{request_title}} беше испратена на {{public_body_name}} од {{info_request_user}} на {{date}}." + +msgid "A response to {{request_title}} was sent by {{public_body_name}} to {{info_request_user}} on {{date}}. The request status is: {{request_status}}" +msgstr "Одговор на {{request_title}} беше пратен од {{public_body_name}} до {{info_request_user}} на {{date}}. Статусот на одговорот е: {{request_status}}" + +msgid "A summary of the response if you have received it by post. " +msgstr "Резиме на одговорот ако го добивте преку пошта. " + +msgid "A Freedom of Information request" +msgstr "Барање за слободен пристап до информации од јавен карактер" + +msgid "A full history of my FOI request and all correspondence is available on the Internet at this address: {{url}}" +msgstr "Целосна историја за моето барање и сите кореспонденции се достапни на интернет на следнава адреса: {{url}}" + +msgid "A new request, {{request_title}}, was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "Ново барање, {{request_title}}, беше пратено до {{public_body_name}} од {{info_request_user}} на {{date}}." + +msgid "A public authority" +msgstr "Имател на информација" + +msgid "A response will be sent by post" +msgstr "Одговорот ќе биде испратен по пошта" + +msgid "A strange reponse, required attention by the {{site_name}} team" +msgstr "Чуден одговор, бара внимание од страна на {{site_name}} тимот" + +msgid "A vexatious request" +msgstr "Вознемирувачко барање" + +msgid "A {{site_name}} user" +msgstr "Корисник на {{site_name}}" + +msgid "About you:" +msgstr "За вас:" + +msgid "Act on what you've learnt" +msgstr "Постапувајте по тоа што сте го научиле" + +msgid "Acts as xapian/acts as xapian job" +msgstr "Постапува како xapian/постапува како xapian работна задача" + +msgid "ActsAsXapian::ActsAsXapianJob|Action" +msgstr "ActsAsXapian::ActsAsXapianJob|Акција" + +msgid "ActsAsXapian::ActsAsXapianJob|Model" +msgstr "ActsAsXapian::ActsAsXapianJob|Модел" + +msgid "Add an annotation" +msgstr "Додадете белешка" + +msgid "Add an annotation to your request with choice quotes, or\\n a summary of the response." +msgstr "Додадете белешка на вашето барање со избрани цитати, или\\n резиме на одговорот." + +msgid "Added on {{date}}" +msgstr "Додадено на {{date}}" + +msgid "Admin level is not included in list" +msgstr "Администраторското ниво не е наведено во листата" + +msgid "Administration URL:" +msgstr "URL за администрирање:" + +msgid "Advanced search" +msgstr "Напредно пребарување" + +msgid "Advanced search tips" +msgstr "Препораки за напредно пребарување" + +msgid "Advise on whether the refusal is legal, and how to complain about it if not." +msgstr "Советувај ако одбивањето е легално и како да се пожалите доколку не е." + +msgid "Air, water, soil, land, flora and fauna (including how these effect\\n human beings)" +msgstr "Воздух, вода, почва, земјиште, флора и фауна (вклучувајќи и каков ефект имаат овие\\n на човечките суштества)" + +msgid "All of the information requested has been received" +msgstr "Сите побарани информации се примени" + +msgid "All the options below can use status or latest_status before the colon. For example, status:not_held will match requests which have ever been marked as not held; latest_status:not_held will match only requests that are currently marked as not held." +msgstr "Сите опции подолу може да користат status или latest_status пред колоната. На пример, status:not_held ќе одговара на барањата кои некогаш биле маркирани да не се чуваат; latest_status:not_held ќе одговара на барањата кои во моментов се маркирани да не се чуваат." + +msgid "All the options below can use variety or latest_variety before the colon. For example, variety:sent will match requests which have ever been sent; latest_variety:sent will match only requests that are currently marked as sent." +msgstr "Сите опции подолу може да користат variety или latest_variety пред колоната. На пример, variety:sent ќе одговара на барањата кои ever биле испратени; latest_variety:sent ќе одговара на барањата кои се во моментов се маркирани да не се чуваат." + +msgid "Also called {{other_name}}." +msgstr "Исто така наречени {{other_name}}." + +msgid "Also send me alerts by email" +msgstr "Дополнително, испрати ми предупредувања по е-пошта" + +msgid "Alter your subscription" +msgstr "Променете ја својата претплата" + +msgid "Although all responses are automatically published, we depend on\\nyou, the original requester, to evaluate them." +msgstr "И покрај тоа што сите одговори се објавуваат автоматски, ние зависиме од \\nвас, изворниот барател, за да ги оцените." + +msgid "An annotation to {{request_title}} was made by {{event_comment_user}} on {{date}}" +msgstr "Белешка за {{request_title}} беше направена од {{event_comment_user}} на {{date}}" + +msgid "An error message has been received" +msgstr "Беше примена порака за грешка" + +msgid "An Environmental Information Regulations request" +msgstr "Барање за регулативи за животна средина" + +msgid "An anonymous user" +msgstr "Анонимен корисник" + +msgid "Annotation added to request" +msgstr "Белешката е додадена на барањето" + +msgid "Annotations" +msgstr "Белешки" + +msgid "Annotations are so anyone, including you, can help the requester with their request. For example:" +msgstr "Белешките се за сите, вклучувајќи ве и вас, да му помогнат на барателот со неговото барање. На пример:" + +msgid "Annotations will be posted publicly here, and are\\n not sent to {{public_body_name}}." +msgstr "Белешките ќе бидат јавно објавени тука и \\n не се испратени до {{public_body_name}}." + +msgid "Anonymous user" +msgstr "Анонимен корисник" + +msgid "Anyone:" +msgstr "Секој:" + +msgid "Applies to" +msgstr "Се однесува на" + +msgid "Are we missing a public authority?" +msgstr "Дали недостасува некој имател?" + +msgid "Are you the owner of any commercial copyright on this page?" +msgstr "Дали сте сопственик на било кои комерцијални авторски права на оваа страница?" + +msgid "Ask for specific documents or information, this site is not suitable for general enquiries." +msgstr "Побарајте специфични документи или информации, оваа страна не е соодветна за општи побарувања." + +msgid "At the bottom of this page, write a reply to them trying to persuade them to scan it in\\n (more details)." +msgstr "На дното од оваа страница, напишете одговор до нив со што ќе се обидете да ги убедите да го скенираат со\\n (повеќе детали)." + +msgid "Attachment (optional):" +msgstr "Прилог (необврзувачко):" + +msgid "Attachment:" +msgstr "Прилог:" + +msgid "Awaiting classification." +msgstr "Се чека класифицирање." + +msgid "Awaiting internal review." +msgstr "Се чека внатрешна ревизија." + +msgid "Awaiting response." +msgstr "Се чека одговор." + +msgid "Beginning with" +msgstr "Започнувајќи со" + +msgid "Browse other requests for examples of how to word your request." +msgstr "Прелистајте други барања за примери како да го формулирате вашето барање." + +msgid "Browse other requests to '{{public_body_name}}' for examples of how to word your request." +msgstr "Прелистајте други барања од '{{public_body_name}}' за примери како да го формулирате вашето барање." + +msgid "Browse all authorities..." +msgstr "Прелистајте ги сите иматели..." + +msgid "By law, under all circumstances, {{public_body_link}} should have responded by now" +msgstr "Според законот, под сите околности, {{public_body_link}} требаше да одговори до сега" + +msgid "By law, {{public_body_link}} should normally have responded promptly and" +msgstr "Според закон, {{public_body_link}} требало да одговори веднаш и" + +msgid "Calculated home page" +msgstr "Пресметана почетна страна" + +msgid "Can't find the one you want?" +msgstr "Не може да го најдете она што го барате?" + +msgid "Cancel a {{site_name}} alert" +msgstr "Откажете ги предупредувањата за {{site_name}} " + +msgid "Cancel some {{site_name}} alerts" +msgstr "Откажете некои предупредувања за {{site_name}} " + +msgid "Cancel, return to your profile page" +msgstr "Откажете и вратете се до страницата на вашиот профил" + +msgid "Censor rule" +msgstr "Правило за цензура" + +msgid "CensorRule|Last edit comment" +msgstr "CensorRule|Последно уреден коментар" + +msgid "CensorRule|Last edit editor" +msgstr "CensorRule|Последно изменет уредник" + +msgid "CensorRule|Regexp" +msgstr "CensorRule|Regexp" + +msgid "CensorRule|Replacement" +msgstr "CensorRule|Замена" + +msgid "CensorRule|Text" +msgstr "CensorRule|Текст" + +msgid "Change email on {{site_name}}" +msgstr "Промени е-пошта на {{site_name}}" + +msgid "Change password on {{site_name}}" +msgstr "Промени лозинка на {{site_name}}" + +msgid "Change profile photo" +msgstr "Промени фотографија за профил" + +msgid "Change the text about you on your profile at {{site_name}}" +msgstr "Променете го текстот за себе на {{site_name}}" + +msgid "Change your email" +msgstr "Променете ја својата е-пошта" + +msgid "Change your email address used on {{site_name}}" +msgstr "Променете ја својата адреса за е-пошта употребена на {{site_name}}" + +msgid "Change your password" +msgstr "Променете ја лозинката" + +msgid "Change your password on {{site_name}}" +msgstr "Променете ја лозинката на {{site_name}}" + +msgid "Change your password {{site_name}}" +msgstr "Променете ја лозинката {{site_name}}" + +msgid "Charity registration" +msgstr "Регистрација на добротворна органзиација" + +msgid "Check for mistakes if you typed or copied the address." +msgstr "Проверете за грешки доколку ја впишавте или копиравте адресата." + +msgid "Check you haven't included any personal information." +msgstr "Проверете дали вклучивте лични информации." + +msgid "Choose your profile photo" +msgstr "Изберете фотографија за профил" + +msgid "Clarification" +msgstr "Објаснување" + +msgid "Clarify your FOI request - " +msgstr "Објаснете го своето барање за слободен пристап до информации - " + +msgid "Classify an FOI response from " +msgstr "Класифицарајте го одговорот од " + +msgid "Clear photo" +msgstr "Поништете фотографија" + +msgid "Click on the link below to send a message to {{public_body_name}} telling them to reply to your request. You might like to ask for an internal\\nreview, asking them to find out why response to the request has been so slow." +msgstr "Кликнете на линкот подолу за да испратите порака до {{public_body_name}} со што им кажувате да ви одговорат на барањето. Можеби ќе сакате да побарате внатрешна\\nревизија, со што ќе побарате од нив да ви кажат зошто добивањето одговор на барањето е толку споро." + +msgid "Click on the link below to send a message to {{public_body}} reminding them to reply to your request." +msgstr "Кликнете на линкот подолу за да испратите порака до {{public_body}} со што ќе ги потсетите да ви одговорат на барањето." + +msgid "Close" +msgstr "Затворете" + +msgid "Comment" +msgstr "Коментирајте" + +msgid "Comment|Body" +msgstr "Comment|Body" + +msgid "Comment|Comment type" +msgstr "Comment|Comment type" + +msgid "Comment|Locale" +msgstr "Comment|Locale" + +msgid "Comment|Visible" +msgstr "Comment|Visible" + +msgid "Confirm you want to follow all successful FOI requests" +msgstr "Потврдете дека сакате да ги следите сите успешни барања за слободен пристап до информации од јавен карактер" + +msgid "Confirm you want to follow new requests" +msgstr "Потврдете дека сакате да ги следите сите нови барања" + +msgid "Confirm you want to follow new requests or responses matching your search" +msgstr "Потврдете дека сакате да ги следите сите нови барања или одговори кои одговараат на вашето пребарување" + +msgid "Confirm you want to follow requests by '{{user_name}}'" +msgstr "Потврдете дека сакате да ги следите барањата од '{{user_name}}'" + +msgid "Confirm you want to follow requests to '{{public_body_name}}'" +msgstr "Потврдете дека сакате да ги следите барањата до '{{public_body_name}}'" + +msgid "Confirm you want to follow the request '{{request_title}}'" +msgstr "Потврдете дека сакате да го следите барањето '{{request_title}}'" + +msgid "Confirm your FOI request to " +msgstr "Потврдете го вашето барање за слободен пристап до " + +msgid "Confirm your account on {{site_name}}" +msgstr "Потврдете го вашиот профил на {{site_name}}" + +msgid "Confirm your annotation to {{info_request_title}}" +msgstr "Потврдете ја вашата белешка до {{info_request_title}}" + +msgid "Confirm your email address" +msgstr "Потврдете ја вашата адреса за е-пошта" + +msgid "Confirm your new email address on {{site_name}}" +msgstr "Потврдете ја вашата нова адреса за е-пошта на {{site_name}}" + +msgid "Considered by administrators as not an FOI request and hidden from site." +msgstr "Од страна на администраторите, ова барање се смета дека не е барање за слободен пристап и истото е скриено од страната." + +msgid "Considered by administrators as vexatious and hidden from site." +msgstr "Од страна на администраторите, ова барање се смета за вознемирувачко и истото е скриено од страната." + +msgid "Contact {{recipient}}" +msgstr "Контакт {{recipient}}" + +msgid "Contact {{site_name}}" +msgstr "Контакт {{site_name}}" + +msgid "Could not identify the request from the email address" +msgstr "Не може да се идентификува барањето од адресата за е-пошта" + +msgid "Couldn't understand the image file that you uploaded. PNG, JPEG, GIF and many other common image file formats are supported." +msgstr "Форматот на фотографијата е непознат. Поддржани се PNG, JPEG, GIF и многу други чести формати за фотографија." + +msgid "Crop your profile photo" +msgstr "Исечете ја вашата фотографија за профил" + +msgid "Cultural sites and built structures (as they may be affected by the\\n environmental factors listed above)" +msgstr "Културни знаменитости и изградени објекти (кои може да се засегнати од\\n факторите наведени погоре)" + +msgid "Currently waiting for a response from {{public_body_link}}, they must respond promptly and" +msgstr "Во моментов се чека на одговор од {{public_body_link}}, тие мора да одговорат веднаш и" + +msgid "Date:" +msgstr "Дата:" + +msgid "Dear {{name}}," +msgstr "Драг {{name}},\\n\\nВрз основа на член 4 и член 12 од Законот за слободен пристап до информации од јавен карактер (“Службен весник на Република Македонија бр. 13/ 1.2.2006 год.), ја барам следната информација од јавен карактер:" + +msgid "Dear {{public_body_name}}," +msgstr "Драг {{public_body_name}},\\n\\nВрз основа на член 4 и член 12 од Законот за слободен пристап до информации од јавен карактер (“Службен весник на Република Македонија бр. 13/ 1.2.2006 год.), ја барам следната информација од јавен карактер:" + +msgid "Default locale" +msgstr "Зададено место" + +msgid "Defunct." +msgstr "Мртов." + +msgid "Delayed response to your FOI request - " +msgstr "Одложен одговор на вашето барање за слободен пристап - " + +msgid "Delayed." +msgstr "Одложен." + +msgid "Delivery error" +msgstr "Грешка при испорака" + +msgid "Destroy {{name}}" +msgstr "Уништи {{name}}" + +msgid "Details of request '" +msgstr "Детали за барањето '" + +msgid "Did you mean: {{correction}}" +msgstr "Дали мислевте: {{correction}}" + +msgid "Disclaimer: This message and any reply that you make will be published on the internet. Our privacy and copyright policies:" +msgstr "Оградување: Оваа порака како и секој одговор кој ќе го направите ќе биде објавен на интернет. Нашите политики за приватност и авторски права:" + +msgid "Disclosure log" +msgstr "Дневник на објави" + +msgid "Disclosure log URL" +msgstr "URL на дневник на објави" + +msgid "Don't want to address your message to {{person_or_body}}? You can also write to:" +msgstr "Не сакате да ја упатите вашата порака до {{person_or_body}}? Можете да пишете и до:" + +msgid "Done" +msgstr "Готово" + +msgid "Done >>" +msgstr "Готово >>" + +msgid "Download a zip file of all correspondence" +msgstr "Преземете zip датотека од целата кореспонденција" + +msgid "Download original attachment" +msgstr "Преземете го оригиналниот прилог" + +msgid "EIR" +msgstr "EIR" + +msgid "Edit" +msgstr "Уреди" + +msgid "Edit and add more details to the message above,\\n explaining why you are dissatisfied with their response." +msgstr "Уредете и додајте повеќе детали на пораката погоре,\\n и притоа објаснете зошто не сте задоволен од нивниот одговор." + +msgid "Edit text about you" +msgstr "Уредете го текстот за вас" + +msgid "Edit this request" +msgstr "Уредете го ова барање" + +msgid "Either the email or password was not recognised, please try again." +msgstr "Или е-поштата или лозинката не се препознаени, обидете се повторно." + +msgid "Either the email or password was not recognised, please try again. Or create a new account using the form on the right." +msgstr "Или е-поштата или лозинката не се препознаени, обидете се повторно. Или креирајте нов профил преку формата од десната страна." + +msgid "Email doesn't look like a valid address" +msgstr "Адресата за е-пошта не е валидна" + +msgid "Email me future updates to this request" +msgstr "Сакам да добивам е-пошта со новости за ова барање" + +msgid "Enter words that you want to find separated by spaces, e.g. climbing lane" +msgstr "Внесете зборови кои сакате да ги најдете, одвоени со празно место, на пр. искачување јаже" + +msgid "Enter your response below. You may attach one file (use email, or\\n contact us if you need more)." +msgstr "Внесете го вашиот одговор подолу. Може да прикачите еден документ (употребете е-пошта, или\\n контактирајте не ако имате потреба од повеќе)." + +msgid "Environmental Information Regulations" +msgstr "Регулативи за инфорамции за животна средина" + +msgid "Environmental Information Regulations requests made" +msgstr "Направени се барања за регулативи за информации за животна средина" + +msgid "Environmental Information Regulations requests made using this site" +msgstr "Преку оваа страна, направени се барања за регулативи за информации за животна средина" + +msgid "Event history" +msgstr "Историја на настани" + +msgid "Event history details" +msgstr "Детали за историја на настани" + +msgid "Event {{id}}" +msgstr "Настан {{id}}" + +msgid "Everything that you enter on this page, including your name,\\n will be displayed publicly on\\n this website forever (why?)." +msgstr "Се што ќе внесете на оваа страница, вклучувајќи го и вашето име,\\n ќе биде јавно прикажано засекогаш на\\n оваа интернет-страна (зошто?)." + +msgid "Everything that you enter on this page\\n will be displayed publicly on\\n this website forever (why?)." +msgstr "Се што ќе внесете на оваа страница\\n ќе биде јавно прикажано засекогаш на\\n оваа интернет-страна (зошто?)." + +msgid "FOI" +msgstr "Слободен пристап до информации од јавен карактер" + +msgid "FOI email address for {{public_body}}" +msgstr "Адреса за е-пошта за слободен пристап до информации на {{public_body}}" + +msgid "FOI request – {{title}}" +msgstr "Барање за слободен пристап до информации – {{title}}" + +msgid "FOI requests" +msgstr "Барања за слободен пристап до информации од јавен карактер" + +msgid "FOI requests by '{{user_name}}'" +msgstr "Барања за слободен пристап до информации од '{{user_name}}'" + +msgid "FOI requests {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "Барања за слободен пристап до информации {{start_count}} до {{end_count}} од {{total_count}}" + +msgid "FOI response requires admin ({{reason}}) - {{title}}" +msgstr "Одговор на барање за слободен пристап до информации бара администрирање ({{reason}}) - {{title}}" + +msgid "Failed to convert image to a PNG" +msgstr "Неуспешно конвертирање на фотографија во PNG формат" + +msgid "Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}" +msgstr "Неуспешно конвертирање на фотографија во точната големина: со {{cols}}x{{rows}}, треба {{width}}x{{height}}" + +msgid "Filter" +msgstr "Филтрирај" + +msgid "First, did your other requests succeed?" +msgstr "Прво, дали другите ваши барања беа успешни?" + +msgid "First, type in the name of the UK public authority you'd\\n like information from. By law, they have to respond\\n (why?)." +msgstr "Прво, внесете име на надлежниот орган во МК од кој\\n ви треба информација. Според закон, тие мора да одговорат\\n (зошто?)." + +msgid "Foi attachment" +msgstr "Прилог за барањето за слободен пристап" + +msgid "FoiAttachment|Charset" +msgstr "FoiAttachment|Charset" + +msgid "FoiAttachment|Content type" +msgstr "FoiAttachment|Content type" + +msgid "FoiAttachment|Display size" +msgstr "FoiAttachment|Display size" + +msgid "FoiAttachment|Filename" +msgstr "FoiAttachment|Filename" + +msgid "FoiAttachment|Hexdigest" +msgstr "FoiAttachment|Hexdigest" + +msgid "FoiAttachment|Url part number" +msgstr "FoiAttachment|Url part number" + +msgid "FoiAttachment|Within rfc822 subject" +msgstr "FoiAttachment|Within rfc822 subject" + +msgid "Follow" +msgstr "Следи" + +msgid "Follow all new requests" +msgstr "Следи ги сите нови барања" + +msgid "Follow new successful responses" +msgstr "Следете ги новите успешни одговори" + +msgid "Follow requests to {{public_body_name}}" +msgstr "Следете ги барањата до {{public_body_name}}" + +msgid "Follow these requests" +msgstr "Следете ги овие барања" + +msgid "Follow things matching this search" +msgstr "Следете ги активностите кои одговараат на ова пребарување" + +msgid "Follow this authority" +msgstr "Следете го овој надлежен орган" + +msgid "Follow this link to see the request:" +msgstr "Следете ја оваа врска за да го видите барањето:" + +msgid "Follow this person" +msgstr "Следете ја оваа личност" + +msgid "Follow this request" +msgstr "Следете го ова барање" + +#. "Follow up" in this context means a further +#. message sent by the requester to the authority after +#. the initial request +msgid "Follow up" +msgstr "Реакција" + +#. "Follow up message" in this context means a +#. further message sent by the requester to the authority after +#. the initial request +msgid "Follow up message sent by requester" +msgstr "Реакција е испратена од барател" + +msgid "Follow up messages to existing requests are sent to " +msgstr "Реакции на постоечки барања се пратени до " + +#. "Follow ups" in this context means further +#. messages sent by the requester to the authority after +#. the initial request +msgid "Follow ups and new responses to this request have been stopped to prevent spam. Please contact us if you are {{user_link}} and need to send a follow up." +msgstr "Реакциите и новите одговори на ова барање беа стопирани поради превенција за спам. Ве молиме контактирајте не ако сте {{user_link}} и сакате да се испратите реакција." + +msgid "Follow us on twitter" +msgstr "Следи не на twitter" + +msgid "Followups cannot be sent for this request, as it was made externally, and published here by {{public_body_name}} on the requester's behalf." +msgstr "Реакција за ова барање не може да се испрати, бидејќи беше направено надворешно, а објавено тука од {{public_body_name}} во име на барателот." + +msgid "For an unknown reason, it is not possible to make a request to this authority." +msgstr "Од непознати причини, не може да се направи барање до овој имател." + +msgid "Forgotten your password?" +msgstr "Ја заборавивте лозинката?" + +msgid "Found {{count}} public authority {{description}}" +msgid_plural "Found {{count}} public authorities {{description}}" +msgstr[0] "Најден е {{count}} имател {{description}}" +msgstr[1] "Најдени се {{count}} иматели {{description}}" + +msgid "Freedom of Information" +msgstr "Слободен пристап до информации" + +msgid "Freedom of Information Act" +msgstr "Закон за слободен пристап до информации" + +msgid "Freedom of Information law does not apply to this authority, so you cannot make\\n a request to it." +msgstr "Законот за Слободен пристап до информации не се однесува за овој имател, затоа не можете да направите\\n барање до нив." + +msgid "Freedom of Information law no longer applies to" +msgstr "Законот за Слободен пристап до информации повеќе не се однесува на" + +msgid "Freedom of Information law no longer applies to this authority.Follow up messages to existing requests are sent to " +msgstr "Законот за Слободен пристап до информации не се однесува за овој имател. Надоврзаните пораки на барањето се праќаат до " + +msgid "Freedom of Information requests made" +msgstr "Направени се барања за слободен пристап до информации" + +msgid "Freedom of Information requests made by this person" +msgstr "Направени се барања за слободен пристап до информации од оваа личност" + +msgid "Freedom of Information requests made by you" +msgstr "Барања за слободен пристап до информации направени од вас" + +msgid "Freedom of Information requests made using this site" +msgstr "Барања за слободен пристап до информации направени преку оваа страна" + +msgid "Freedom of information requests to" +msgstr "Барања за слободен пристап до информации до" + +msgid "From" +msgstr "Од" + +msgid "From the request page, try replying to a particular message, rather than sending\\n a general followup. If you need to make a general followup, and know\\n an email which will go to the right place, please send it to us." +msgstr "Од страната со барањето, обидете се да одговорите на конкретна порака, наместо да испратите\\n општа реакција. Ако сакате да направите општа реакција и ја знаете\\n е-поштата со која директно се упатува на правото место, ве молиме пратете ни ја нас." + +msgid "From:" +msgstr "Од:" + +msgid "GIVE DETAILS ABOUT YOUR COMPLAINT HERE" +msgstr "ДАДЕТЕ ДЕТАЛИ ЗА ВАЖАТА ЖАЛБА ТУКА" + +msgid "Handled by post." +msgstr "Решено по пошта." + +msgid "Has tag string/has tag string tag" +msgstr "Has tag string/has tag string tag" + +msgid "HasTagString::HasTagStringTag|Model" +msgstr "HasTagString::HasTagStringTag|Model" + +msgid "HasTagString::HasTagStringTag|Name" +msgstr "HasTagString::HasTagStringTag|Name" + +msgid "HasTagString::HasTagStringTag|Value" +msgstr "HasTagString::HasTagStringTag|Value" + +msgid "Hello! You can make Freedom of Information requests within {{country_name}} at {{link_to_website}}" +msgstr "Здраво! Може да направите барање за слободен пристап до информации во рамки на {{country_name}} на {{link_to_website}}" + +msgid "Hello, {{username}}!" +msgstr "Здраво, {{username}}!" + +msgid "Help" +msgstr "Помош" + +msgid "Here described means when a user selected a status for the request, and\\nthe most recent event had its status updated to that value. calculated is then inferred by\\n{{site_name}} for intermediate events, which weren't given an explicit\\ndescription by a user. See the search tips for description of the states." +msgstr "Овде зборот опишано значи кога корисник има одбрано статус за барањето и\\nстатусот на последната новост ја има таа вредност. Зборот пресметано е претставен од страна на\\n{{site_name}} за посредни настани, за кои не беше експлицитно даден\\nопис од страна на корисник. Погледни ги советите за пребарување за описите на статусите." + +msgid "Here is the message you wrote, in case you would like to copy the text and save it for later." +msgstr "Еве ја пораката која ја напишавте, во случај да сакате да го ископирате текстот и да го зачувате за подоцна." + +msgid "Hi! We need your help. The person who made the following request\\n hasn't told us whether or not it was successful. Would you mind taking\\n a moment to read it and help us keep the place tidy for everyone?\\n Thanks." +msgstr "Здраво! Ни треба вашата помош. Личноста која го напиша следново барање\\n не не извести дали истото беше успешно. Дали може да одвоите време\\n за да го прочитате и да ни помогнете да го одржиме местово уредно за сите?\\nВи благодариме." + +msgid "Hide request" +msgstr "Сокриј барање" + +msgid "Holiday" +msgstr "Празник" + +msgid "Holiday|Day" +msgstr "Holiday|Day" + +msgid "Holiday|Description" +msgstr "Holiday|Description" + +msgid "Home" +msgstr "Почеток" + +msgid "Home page" +msgstr "Почетна страна" + +msgid "Home page of authority" +msgstr "Почетна страна на имателот" + +msgid "However, you have the right to request environmental\\n information under a different law" +msgstr "Секако, вие имате право да побарате информации\\n за животната средина под друг закон" + +msgid "Human health and safety" +msgstr "Човеково здравје и безбедност" + +msgid "I am asking for new information" +msgstr "Барам нови информации" + +msgid "I am requesting an internal review" +msgstr "Побарувам внатрешна ревизија" + +msgid "I am writing to request an internal review of {{public_body_name}}'s handling of my FOI request '{{info_request_title}}'." +msgstr "Пишувам со цел да побарам внатрешна ревизија од {{public_body_name}} за разгледувањто на моето барање за слободен пристап до информации '{{info_request_title}}'." + +msgid "I don't like these ones — give me some more!" +msgstr "Не ми се допаѓаат овие — дај ми уште неколку!" + +msgid "I don't want to do any more tidying now!" +msgstr "Не сакам да уредувам повеќе!" + +msgid "I like this request" +msgstr "Ми се допаѓа ова барање" + +msgid "I would like to withdraw this request" +msgstr "Би сакал да го повлечам ова барање" + +msgid "I'm still waiting for my information\\n (maybe you got an acknowledgement)" +msgstr "Се уште чекам за мојата информација\\n (можеби имате известување)" + +msgid "I'm still waiting for the internal review" +msgstr "Се уште чекам внатрешна ревизија" + +msgid "I'm waiting for an internal review response" +msgstr "Чекам одговор од внатрешна ревизија" + +msgid "I've been asked to clarify my request" +msgstr "Бев замолен да го појаснам моето барање" + +msgid "I've received all the information" +msgstr "Ги добив сите информации" + +msgid "I've received some of the information" +msgstr "Добив дел од информациите" + +msgid "I've received an error message" +msgstr "Добив порака за грешка" + +msgid "I've received an error message" +msgstr "Добив порака за грешка" + +msgid "Id" +msgstr "Идентификациски број" + +msgid "If the address is wrong, or you know a better address, please contact us." +msgstr "Ако адресата е грешна или имате подобра адреса, Ве молиме контактирајте не." + +msgid "If the error was a delivery failure, and you can find an up to date FOI email address for the authority, please tell us using the form below." +msgstr "Ако грешката е заради неуспешна достава и може да најдете нова, соодветна адреса за е-пошта од имателот, Ве молиме употребете ја формата подолу." + +msgid "If this is incorrect, or you would like to send a late response to the request\\nor an email on another subject to {{user}}, then please\\nemail {{contact_email}} for help." +msgstr "Ако ова не е точно или сакате да испратите задоцнет одговор на барањето\\nили е-пошта на друга тема на {{user}}, тогаш ве молиме\\nиспратете ни е-пошта {{contact_email}} за помош." + +msgid "If you are dissatisfied by the response you got from\\n the public authority, you have the right to\\n complain (details)." +msgstr "Доколку не сте задоволни со одговорот кој го добивте од\\n имателот, имате право да се\\n пожалите (детали)." + +msgid "If you are still having trouble, please contact us." +msgstr "Ако и понатака имате проблеми, Ве молиме контактирајте не." + +msgid "If you are the requester, then you may sign in to view the message." +msgstr "Ако сте вие барателот, тогаш најавете се за да ја видите пораката." + +msgid "If you are the requester, then you may sign in to view the request." +msgstr "Ако сте вие барателот, тогаш најавете се за да го видите барањето." + +msgid "If you are thinking of using a pseudonym,\\n please read this first." +msgstr "Ако размилувате да употребите псевдоним,\\n Ве молимепрочитајте го прво ова." + +msgid "If you are {{user_link}}, please" +msgstr "Ако сте {{user_link}}, Ве молиме" + +msgid "If you believe this request is not suitable, you can report it for attention by the site administrators" +msgstr "Ако мислите дека ова барање не е соодветно, можете да го пријавите со цел да биде прегледано од администраторите на страната" + +msgid "If you can't click on it in the email, you'll have to select and copy\\nit from the email. Then paste it into your browser, into the place\\nyou would type the address of any other webpage." +msgstr "Ако не можете да го кликнете линкот во пораката, ќе мора да го селектирате и копирате\\n од е-поштата. Потоа вметнете го во вашиот прелистувач, во полето каде\\nобично би ја напишале адресата на било која веб-страна." + +msgid "If you can, scan in or photograph the response, and send us\\n a copy to upload." +msgstr "Ако сте во можност, скенирајте или направете фотографија од одговорот и пратете ни\\n копија за прикачување." + +msgid "If you find this service useful as an FOI officer, please ask your web manager to link to us from your organisation's FOI page." +msgstr "Ако овој сервис ви е корисен за добивање информации од јавен карактер, замолете го вашиот веб-администратор да направи врска до нас од вашата организациска страна." + +msgid "If you got the email more than six months ago, then this login link won't work any\\nmore. Please try doing what you were doing from the beginning." +msgstr "Ако ја добивте оваа е-пошта пред повеќе од шест месеци, тогаш оваа врска за најавување нема да работи\\nповеќе. Обидете се да го направите повторно тоа што го направивте претходно." + +msgid "If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn." +msgstr "Ако не го направивте тоа до сега, Ве молиме напишете порака подолу со која ќе го известите надлежниот орган дека го повлековте вашето барање. Инаку тие нема да знаат дека барањето е повлечено." + +msgid "If you reply to this message it will go directly to {{user_name}}, who will\\nlearn your email address. Only reply if that is okay." +msgstr "Ако одговорите на оваа порака, одговорот ќе биде пратен директно до {{user_name}}, кој ќе\\nја дознае вашата адреса за е-пошта. Одговорете само во случај тоа да е во ред." + +msgid "If you use web-based email or have \"junk mail\" filters, also check your\\nbulk/spam mail folders. Sometimes, our messages are marked that way." +msgstr "Ако користите веб-базирана е-пошта или имате \"junk mail\" филтри, исто така проверете го вашиот\\nbulk/фолдер за спам пораки. Понекогаш, нашите пораки се означени како такви." + +msgid "If you would like us to lift this ban, then you may politely\\ncontact us giving reasons.\\n" +msgstr "Ако сакате да ја тргнеме оваа забрана, тогаш може учтиво да не\\nконтактирате со што ќе ги наведете причините.\\n" + +msgid "If you're new to {{site_name}}" +msgstr "Ако сте нов корисник на {{site_name}}" + +msgid "If you've used {{site_name}} before" +msgstr "Ако го имате претходно користено {{site_name}}" + +msgid "If your browser is set to accept cookies and you are seeing this message,\\nthen there is probably a fault with our server." +msgstr "Ако вашиот прелистувач е конфигуриран да прифаќа cookies и ја гледате оваа порака,\\nтогаш веројатно има проблем со нашиот сервер." + +msgid "Incoming email address" +msgstr "Адреса за е-пошта" + +msgid "Incoming message" +msgstr "Нова порака" + +msgid "IncomingMessage|Cached attachment text clipped" +msgstr "IncomingMessage|Cached attachment text clipped" + +msgid "IncomingMessage|Cached main body text folded" +msgstr "IncomingMessage|Cached main body text folded" + +msgid "IncomingMessage|Cached main body text unfolded" +msgstr "IncomingMessage|Cached main body text unfolded" + +msgid "IncomingMessage|Last parsed" +msgstr "IncomingMessage|Last parsed" + +msgid "IncomingMessage|Mail from" +msgstr "IncomingMessage|Mail from" + +msgid "IncomingMessage|Mail from domain" +msgstr "IncomingMessage|Mail from domain" + +msgid "IncomingMessage|Prominence" +msgstr "IncomingMessage|Prominence" + +msgid "IncomingMessage|Prominence reason" +msgstr "IncomingMessage|Prominence reason" + +msgid "IncomingMessage|Sent at" +msgstr "IncomingMessage|Sent at" + +msgid "IncomingMessage|Subject" +msgstr "IncomingMessage|Subject" + +msgid "IncomingMessage|Valid to reply to" +msgstr "IncomingMessage|Valid to reply to" + +msgid "Individual requests" +msgstr "Индивидуално барање" + +msgid "Info request" +msgstr "Инфо барање" + +msgid "Info request event" +msgstr "Настан за инфо барање" + +msgid "InfoRequestEvent|Calculated state" +msgstr "InfoRequestEvent|Calculated state" + +msgid "InfoRequestEvent|Described state" +msgstr "InfoRequestEvent|Described state" + +msgid "InfoRequestEvent|Event type" +msgstr "InfoRequestEvent|Event type" + +msgid "InfoRequestEvent|Last described at" +msgstr "InfoRequestEvent|Last described at" + +msgid "InfoRequestEvent|Params yaml" +msgstr "InfoRequestEvent|Params yaml" + +msgid "InfoRequest|Allow new responses from" +msgstr "InfoRequest|Allow new responses from" + +msgid "InfoRequest|Attention requested" +msgstr "InfoRequest|Attention requested" + +msgid "InfoRequest|Awaiting description" +msgstr "InfoRequest|Awaiting description" + +msgid "InfoRequest|Comments allowed" +msgstr "InfoRequest|Comments allowed" + +msgid "InfoRequest|Described state" +msgstr "InfoRequest|Described state" + +msgid "InfoRequest|External url" +msgstr "InfoRequest|External url" + +msgid "InfoRequest|External user name" +msgstr "InfoRequest|External user name" + +msgid "InfoRequest|Handle rejected responses" +msgstr "InfoRequest|Handle rejected responses" + +msgid "InfoRequest|Idhash" +msgstr "InfoRequest|Idhash" + +msgid "InfoRequest|Law used" +msgstr "InfoRequest|Law used" + +msgid "InfoRequest|Prominence" +msgstr "InfoRequest|Prominence" + +msgid "InfoRequest|Title" +msgstr "InfoRequest|Title" + +msgid "InfoRequest|Url title" +msgstr "InfoRequest|Url title" + +msgid "Information not held." +msgstr "Информациите не се чуваат." + +msgid "Information on emissions and discharges (e.g. noise, energy,\\n radiation, waste materials)" +msgstr "Информации за емисиите и испуштањата (на пр. шум, енергија,\\n радијација, отпадни материјали)" + +msgid "Internal review request" +msgstr "Барање за внатрешна ревизија" + +msgid "Is {{email_address}} the wrong address for {{type_of_request}} requests to {{public_body_name}}? If so, please contact us using this form:" +msgstr "Дали {{email_address}} е грешна адреса за {{type_of_request}} барањето до {{public_body_name}}? Ако да, Ве молиме контактирајте не преку оваа форма:" + +msgid "It may be that your browser is not set to accept a thing called \"cookies\",\\nor cannot do so. If you can, please enable cookies, or try using a different\\nbrowser. Then press refresh to have another go." +msgstr "Причината може да е доколку вашиот прелистувач не е конфигуриран да прифаќа \"cookies\",\\nили не може да ги прифаќа. Ако можете, вклучете ја опцијата за cookies или пробајте да користите друг\\nпрелистувач. Потоа притиснете освежи, за да пробате повторно." + +msgid "Items matching the following conditions are currently displayed on your wall." +msgstr "Во моментов на вашиот ѕид се прикажани ставките кои одговараат на наведените услови." + +msgid "Items sent in last month" +msgstr "Случаи кои се испратени последниов месец" + +msgid "Joined in" +msgstr "Се приклучивте во" + +msgid "Joined {{site_name}} in" +msgstr "Почнавте да го користите {{site_name}} на" + +msgid "Just one more thing" +msgstr "Само уште една работа" + +msgid "Keep it focused, you'll be more likely to get what you want (why?)." +msgstr "Барањето треба да е фокусирано, со што ќе Ви се зголемат шансите да го добиете тоа што го сакате (зошто?)." + +msgid "Keywords" +msgstr "Клучни зборови" + +msgid "Last authority viewed: " +msgstr "Последен прегледан имател: " + +msgid "Last request viewed: " +msgstr "Последно видено барање: " + +msgid "Let us know what you were doing when this message\\nappeared and your browser and operating system type and version." +msgstr "Известете не што правевте кога оваа порака се\\nпојави и кои се типот и верзијата на вашиот прелисутвач и оперативен систем." + +msgid "Link to this" +msgstr "Врска до ова" + +msgid "List all" +msgstr "Излистај ги сите" + +msgid "List of all authorities (CSV)" +msgstr "Листа од сите иматели (CSV)" + +msgid "Listing FOI requests" +msgstr "Листање на барања за слободен пристап" + +msgid "Listing public authorities" +msgstr "Листање на иматели" + +msgid "Listing public authorities matching '{{query}}'" +msgstr "Листање на иматели кои одговараат на '{{query}}'" + +msgid "Listing tracks" +msgstr "Листање на патеки" + +msgid "Listing users" +msgstr "Листање на корисници" + +msgid "Log in to download a zip file of {{info_request_title}}" +msgstr "Најавете се за да преземете zip датотека од {{info_request_title}}" + +msgid "Log into the admin interface" +msgstr "Најавете се на администраторскиот интерфејс" + +msgid "Long overdue." +msgstr "Одамна е поминат рокот." + +msgid "Made between" +msgstr "Изработен во периодот помеѓу" + +msgid "Mail server log" +msgstr "Дневник од серверот за пошта" + +msgid "Mail server log done" +msgstr "Дневникот од серверот за пошта е готов" + +msgid "MailServerLogDone|Filename" +msgstr "MailServerLogDone|Filename" + +msgid "MailServerLogDone|Last stat" +msgstr "MailServerLogDone|Last stat" + +msgid "MailServerLog|Line" +msgstr "MailServerLog|Line" + +msgid "MailServerLog|Order" +msgstr "MailServerLog|Order" + +msgid "Make a new
    \\n Freedom of
    \\n Information
    \\n request
    " +msgstr "Направи ново
    \\n Барање за Слободен пристап до
    \\n информации
    " + +msgid "Make a request" +msgstr "Поднесете барање" + +msgid "Make a request to this authority" +msgstr "Поднесете барање до овој имател" + +msgid "Make an {{law_used_short}} request to '{{public_body_name}}'" +msgstr "Поднесете {{law_used_short}} барање до '{{public_body_name}}'" + +msgid "Make and browse Freedom of Information (FOI) requests" +msgstr "Поднесете и прелистајте барања за слободен пристап до информации" + +msgid "Make your own request" +msgstr "Поднесете свое барање" + +msgid "Many requests" +msgstr "Повеќе барања" + +msgid "Message" +msgstr "Порака" + +msgid "Message has been removed" +msgstr "Пораката беше отстранета" + +msgid "Message sent using {{site_name}} contact form, " +msgstr "Пораката е испратена преку контакт формата од {{site_name}}, " + +msgid "Missing contact details for '" +msgstr "Недостасуваат контакт-детали за '" + +msgid "More about this authority" +msgstr "Повеќе за овој имател" + +msgid "More requests..." +msgstr "Повеќе барања..." + +msgid "More similar requests" +msgstr "Повеќе слични барања" + +msgid "More successful requests..." +msgstr "Повеќе успешни барања..." + +msgid "My profile" +msgstr "Мојот профил" + +msgid "My request has been refused" +msgstr "Моите барања се одбиени" + +msgid "My requests" +msgstr "Мои барања" + +msgid "My wall" +msgstr "Мојот ѕид" + +msgid "Name can't be blank" +msgstr "Името не може да е празно" + +msgid "Name is already taken" +msgstr "Веќе постои такво име" + +msgid "New Freedom of Information requests" +msgstr "Ново барање за слободен пристап до информации" + +msgid "New censor rule" +msgstr "Ново правило за цензура" + +msgid "New e-mail:" +msgstr "Нова е-пошта:" + +msgid "New email doesn't look like a valid address" +msgstr "Не е валидна новата адреса за е-пошта" + +msgid "New password:" +msgstr "Нова лозинка:" + +msgid "New password: (again)" +msgstr "Нова лозинка: (повторно)" + +msgid "New response to '{{title}}'" +msgstr "Нов одговор до '{{title}}'" + +msgid "New response to your FOI request - " +msgstr "Нов одговор до вашето барање за слободен пристап - " + +msgid "New response to your request" +msgstr "Нов одговор до вашето барање" + +msgid "New response to {{law_used_short}} request" +msgstr "Нов одговор до {{law_used_short}} барање" + +msgid "New updates for the request '{{request_title}}'" +msgstr "Новости за барањето '{{request_title}}'" + +msgid "Newest results first" +msgstr "Прво најновите резултати" + +msgid "Next" +msgstr "Следно" + +msgid "Next, crop your photo >>" +msgstr "Следно, пресечете ја вашата слика >>" + +msgid "No requests of this sort yet." +msgstr "Се уште нема барања од овој вид." + +msgid "No results found." +msgstr "Нема резултати." + +msgid "No similar requests found." +msgstr "Нема слични барања." + +msgid "No tracked things found." +msgstr "Не се најдени работи кои се следат." + +msgid "Nobody has made any Freedom of Information requests to {{public_body_name}} using this site yet." +msgstr "Никој нема направено барање за слободен пристап до информации до {{public_body_name}} преку оваа страна." + +msgid "None found." +msgstr "Ништо не е најдено." + +msgid "None made." +msgstr "Ништо не е направено." + +msgid "Not a valid FOI request" +msgstr "Не е валидно барање за слободен пристап" + +msgid "Note that the requester will not be notified about your annotation, because the request was published by {{public_body_name}} on their behalf." +msgstr "Имајте на ум дека барателот нема да биде известен за вашата белешка, бидејќи барањето беше објавено од име на {{public_body_name}}." + +msgid "Now check your email!" +msgstr "Проверете ја вашата е-пошта!" + +msgid "Now preview your annotation" +msgstr "Прегледајте ја вашата белешка" + +msgid "Now preview your follow up" +msgstr "Прегледајте ја вашата реакција" + +msgid "Now preview your message asking for an internal review" +msgstr "Прегледајте ја вашата порака каде барате внатрешна ревизија" + +msgid "Number of requests" +msgstr "Број на барања" + +msgid "OR remove the existing photo" +msgstr "или отстрани ја тековната фотографија" + +msgid "Offensive? Unsuitable?" +msgstr "Навредливо? Несоодветно?" + +msgid "Oh no! Sorry to hear that your request was refused. Here is what to do now." +msgstr "Ох не! Жалиме што вашето барање беше одбиено. Еве што може да направите следно." + +msgid "Old e-mail:" +msgstr "Стара е-пошта:" + +msgid "Old email address isn't the same as the address of the account you are logged in with" +msgstr "Старата адреса за е-пошта не е иста со онаа која се користи за профилот со кој сте најавени" + +msgid "Old email doesn't look like a valid address" +msgstr "Старата адреса за е-пошта не е валидна адреса" + +msgid "On this page" +msgstr "На оваа страна" + +msgid "One FOI request found" +msgstr "Најдено е едно барање" + +msgid "One person found" +msgstr "Најдена е една личност" + +msgid "One public authority found" +msgstr "Најден е еден имател" + +msgid "Only put in abbreviations which are really used, otherwise leave blank. Short or long name is used in the URL – don't worry about breaking URLs through renaming, as the history is used to redirect" +msgstr "Внесете само кратенки кои реално се употребуваат, во спротивно оставете празно. Кратко или долго име се користи во URL – не се грижете за кршење на URL-ата преку реименување, бидејќи историјата се користи за пренасочување" + +msgid "Only requests made using {{site_name}} are shown." +msgstr "Прикажани се само барањата кои се направени преку {{site_name}}." + +msgid "Only the authority can reply to this request, and I don't recognise the address this reply was sent from" +msgstr "Само имателот може да одговори на ова барање и јас не ја препознавам адресата од која е пратено ова барање" + +msgid "Only the authority can reply to this request, but there is no \"From\" address to check against" +msgstr "Само имателот може да одговори на ова барање, ама нема \"From\" адреса со која треба да се провери" + +msgid "Or search in their website for this information." +msgstr "Или пребарајте ја нивната веб-страна за овие информации." + +msgid "Original request sent" +msgstr "Оригиналното барање е испратено" + +msgid "Other:" +msgstr "Друго:" + +msgid "Outgoing message" +msgstr "Порака за испраќање" + +msgid "OutgoingMessage|Body" +msgstr "OutgoingMessage|Body" + +msgid "OutgoingMessage|Last sent at" +msgstr "OutgoingMessage|Last sent at" + +msgid "OutgoingMessage|Message type" +msgstr "OutgoingMessage|Message type" + +msgid "OutgoingMessage|Prominence" +msgstr "OutgoingMessage|Prominence" + +msgid "OutgoingMessage|Prominence reason" +msgstr "OutgoingMessage|Prominence reason" + +msgid "OutgoingMessage|Status" +msgstr "OutgoingMessage|Status" + +msgid "OutgoingMessage|What doing" +msgstr "OutgoingMessage|What doing" + +msgid "Partially successful." +msgstr "Делумно успешно." + +msgid "Password is not correct" +msgstr "Лозинката не е точна" + +msgid "Password:" +msgstr "Лозинка:" + +msgid "Password: (again)" +msgstr "Лозинка: (повторно)" + +msgid "Paste this link into emails, tweets, and anywhere else:" +msgstr "Вметнете ја оваа врска во пораките, твитовите и на сите други места:" + +msgid "People" +msgstr "Луѓе" + +msgid "People {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "Луѓе {{start_count}} до {{end_count}} од {{total_count}}" + +msgid "Percentage of requests that are overdue" +msgstr "Процент на барања со пречекорен рок" + +msgid "Percentage of total requests" +msgstr "Процент на вкупно барања" + +msgid "Photo of you:" +msgstr "Фотографија од вас:" + +msgid "Plans and administrative measures that affect these matters" +msgstr "Планови и административни мерки кои влијаат на овие прашања" + +msgid "Play the request categorisation game" +msgstr "Играј ја играта за категоризација на барања" + +msgid "Play the request categorisation game!" +msgstr "Играј ја играта за категоризација на барања!" + +msgid "Please" +msgstr "Ве молиме" + +msgid "Please contact us if you have any questions." +msgstr "Ве молиме контактирајте не ако имате други прашања." + +msgid "Please get in touch with us so we can fix it." +msgstr "Ве молиме стапете во контакт со нас за да може да го поправиме." + +msgid "Please answer the question above so we know whether the " +msgstr "Ве молиме одговорете го прашањето погоре за да знаеме дали да" + +msgid "Please go to the following requests, and let us\\n know if there was information in the recent responses to them." +msgstr "Ве молиме одете до следниве барања и известете не\\n ако постои информација кај последните барања до нив." + +msgid "Please only write messages directly relating to your request {{request_link}}. If you would like to ask for information that was not in your original request, then file a new request." +msgstr "Ве молиме пишувајте само пораки кои се директно поврзани со вашето барање {{request_link}}. Ако сакате да побарате информации кои не се дел од оригиналното барање, тогаш испратете ново барање." + +msgid "Please ask for environmental information only" +msgstr "Ве молиме прашајте само за информации од областа" + +msgid "Please check the URL (i.e. the long code of letters and numbers) is copied\\ncorrectly from your email." +msgstr "Ве молиме проверете дали URL (долгиот код со букви и бројки) е копиран\\nкоректно од вашата е-пошта." + +msgid "Please choose a file containing your photo." +msgstr "Ве молиме изберете датотека која содржи фотографија од вас." + +msgid "Please choose a reason" +msgstr "Ве молиме изберете причина" + +msgid "Please choose what sort of reply you are making." +msgstr "Ве молиме изберете од кој вид е одговорот кој го правите." + +msgid "Please choose whether or not you got some of the information that you wanted." +msgstr "Ве молиме изберете дали добивте или не дел од инфорамциите кои ги баравте." + +msgid "Please click on the link below to cancel or alter these emails." +msgstr "Ве молиме кликнете на врската подолу за да ги откажете или промените овие пораки." + +msgid "Please click on the link below to confirm that you want to \\nchange the email address that you use for {{site_name}}\\nfrom {{old_email}} to {{new_email}}" +msgstr "Ве молиме кликнете на врската подолу за да потврдите дека сакате да \\nја ја промените адресата за е-пошта која ја користите за {{site_name}}\\nод {{old_email}} во {{new_email}}" + +msgid "Please click on the link below to confirm your email address." +msgstr "Ве молиме кликнете на врската подолу за да ја потврдите адресата за е-пошта." + +msgid "Please describe more what the request is about in the subject. There is no need to say it is an FOI request, we add that on anyway." +msgstr "Ве молиме во насловот опишете за што се работи вашето барање. Нема потреба да кажете дека се работи за барање за слободен пристап, ние тоа го додаваме секако." + +msgid "Please don't upload offensive pictures. We will take down images\\n that we consider inappropriate." +msgstr "Ве молиме не прикачувате фотографии со навредлива содржина. Ние ќе ги тргнеме фотографиите\\nкои сметаме дека се несоодветни." + +msgid "Please enable \"cookies\" to carry on" +msgstr "Ве молиме вклучете \"cookies\" за да продолжите " + +msgid "Please enter a password" +msgstr "Ве молиме внесете лозинка" + +msgid "Please enter a subject" +msgstr "Ве молиме внесете наслов" + +msgid "Please enter a summary of your request" +msgstr "Ве молиме внесете резиме од вашето барање" + +msgid "Please enter a valid email address" +msgstr "Ве молиме внесете валидна адреса за е-пошта" + +msgid "Please enter the message you want to send" +msgstr "Ве молиме внесете ја пораката која сакате да ја испратите" + +msgid "Please enter the same password twice" +msgstr "Ве молиме внесете ја истата лозинка два пати" + +msgid "Please enter your annotation" +msgstr "Ве молиме внесете ја вашата белешка" + +msgid "Please enter your email address" +msgstr "Ве молиме внесете ја вашата адреса за е-пошта" + +msgid "Please enter your follow up message" +msgstr "Ве молиме внесете ја вашата реакција" + +msgid "Please enter your letter requesting information" +msgstr "Ве молиме внесете го вашето писмо за барање информации" + +msgid "Please enter your name" +msgstr "Ве молиме внесете го вашето име" + +msgid "Please enter your name, not your email address, in the name field." +msgstr "Ве молиме внесете го вашето име, не вашата адреса за е-пошта, во полето за име." + +msgid "Please enter your new email address" +msgstr "Ве молиме внесете ја новата адреса за е-пошта" + +msgid "Please enter your old email address" +msgstr "Ве молиме внесете ја старата адреса за е-пошта" + +msgid "Please enter your password" +msgstr "Ве молиме внесете ја вашата лозинка" + +msgid "Please give details explaining why you want a review" +msgstr "Ве молиме дадете детали каде објаснувате зошто барате ревизија" + +msgid "Please keep it shorter than 500 characters" +msgstr "Ве молам држете се до должина од 500 букви" + +msgid "Please keep the summary short, like in the subject of an email. You can use a phrase, rather than a full sentence." +msgstr "Ве молиме за кратко резиме, како наслов на е-пошта. Може да користите фраза, наместо цела реченица." + +msgid "Please only request information that comes under those categories, do not waste your\\n time or the time of the public authority by requesting unrelated information." +msgstr "Ве молиме барајте само информации кои спаѓаат во овие категории, не го губете вашето\\n време или времето на имателот барајќи нерелевантни информации." + +msgid "Please pass this on to the person who conducts Freedom of Information reviews." +msgstr "Ве молиме проследете го ова до личноста која управува со барањата за слободен пристап до информации." + +msgid "Please select each of these requests in turn, and let everyone know\\nif they are successful yet or not." +msgstr "Ве молиме изберете го секое барање по ред и известете ги сите\\nако се успешни или неусшешни." + +msgid "Please sign at the bottom with your name, or alter the \"{{signoff}}\" signature" +msgstr "Ве молиме најавете се на дното со вашето име или променете го \"{{signoff}}\" потпис" + +msgid "Please sign in as " +msgstr "Ве молиме најавете се како " + +msgid "Please sign in or make a new account." +msgstr "Ве молиме најавете се или креирајте нова лозинка." + +msgid "Please type a message and/or choose a file containing your response." +msgstr "Ве молиме напишете порака и/или изберете датотека која го содржи вашиот одговор." + +msgid "Please use this email address for all replies to this request:" +msgstr "Ве молиме користете ја оваа адреса за е-пошта за сите одговори на ова барање:" + +msgid "Please write a summary with some text in it" +msgstr "Ве молиме напишете кратко резиме" + +msgid "Please write the summary using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "Ве молиме напишете резиме употребувајќи комбинација од големи и мали букви. На тој начин читањето ќе биде полесно." + +msgid "Please write your annotation using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "Ве молиме напишете ја вашата белешка употребувајќи големи и мали букви. На тој начин читањето ќе биде полесно." + +msgid "Please write your follow up message containing the necessary clarifications below." +msgstr "Ве молиме подолу напишете ја вашата порака за реакција која ќе ги содржи потребните разјаснувања." + +msgid "Please write your message using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "Ве молиме напишете ја вашата порака употребувајќи комбинација од големи и мали букви. На тој начин читањето ќе биде полесно." + +msgid "Point to related information, campaigns or forums which may be useful." +msgstr "Посочете поврзани информации, кампањи или форуми кои може да се корисни." + +msgid "Possibly related requests:" +msgstr "Можни поврзани информации:" + +msgid "Post annotation" +msgstr "Објавете белешка" + +msgid "Post redirect" +msgstr "Објавете пренасочување" + +msgid "PostRedirect|Circumstance" +msgstr "PostRedirect|Circumstance" + +msgid "PostRedirect|Email token" +msgstr "PostRedirect|Email token" + +msgid "PostRedirect|Post params yaml" +msgstr "PostRedirect|Post params yaml" + +msgid "PostRedirect|Reason params yaml" +msgstr "PostRedirect|Reason params yaml" + +msgid "PostRedirect|Token" +msgstr "PostRedirect|Token" + +msgid "PostRedirect|Uri" +msgstr "PostRedirect|Uri" + +msgid "Posted on {{date}} by {{author}}" +msgstr "Објавено на {{date}} од {{author}}" + +msgid "Powered by Alaveteli" +msgstr "Поддржано од Alaveteli" + +msgid "Prev" +msgstr "Претходно" + +msgid "Preview follow up to '" +msgstr "Прегледајте реакција до '" + +msgid "Preview new annotation on '{{info_request_title}}'" +msgstr "Прегледајте нови белешки од '{{info_request_title}}'" + +msgid "Preview your annotation" +msgstr "Прегледајте ги своите белешки" + +msgid "Preview your message" +msgstr "Прегледајте ги своите пораки" + +msgid "Preview your public request" +msgstr "Прегледајте го своето јавно барање" + +msgid "Profile photo" +msgstr "Фотографија од профил" + +msgid "ProfilePhoto|Data" +msgstr "ProfilePhoto|Data" + +msgid "ProfilePhoto|Draft" +msgstr "ProfilePhoto|Draft" + +msgid "Public Bodies" +msgstr "Јавни институции" + +msgid "Public Body Statistics" +msgstr "Статистика за јавни институции" + +msgid "Public authorities" +msgstr "Јавни надлежни органи" + +msgid "Public authorities - {{description}}" +msgstr "Јавни надлежни органи - {{description}}" + +msgid "Public authorities {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "Јавни надлежни органи {{start_count}} до {{end_count}} од {{total_count}}" + +msgid "Public authority – {{name}}" +msgstr "Јавен надлежен орган – {{name}}" + +msgid "Public bodies that most frequently replied with \"Not Held\"" +msgstr "Јавни институции кои најчесто одговараат со \"Not Held\"" + +msgid "Public bodies with most overdue requests" +msgstr "Јавна институција со најголем број барања со прекорачен рок" + +msgid "Public bodies with the fewest successful requests" +msgstr "Јавни институции со најмалку успешни барања" + +msgid "Public bodies with the most requests" +msgstr "Јавни институции со најмногу барања" + +msgid "Public bodies with the most successful requests" +msgstr "Јавни институции со најмногу успешни барања" + +msgid "Public body" +msgstr "Јавна институција" + +msgid "Public notes" +msgstr "Јавни белешки" + +msgid "Public page" +msgstr "Јавна страна" + +msgid "Public page not available" +msgstr "Јавната страна не е достапна" + +msgid "PublicBody|Api key" +msgstr "PublicBody|Api клуч" + +msgid "PublicBody|Disclosure log" +msgstr "PublicBody|Дневник на разоткривање" + +msgid "PublicBody|First letter" +msgstr "PublicBody|Прво писмо" + +msgid "PublicBody|Home page" +msgstr "PublicBody|Почетна страна" + +msgid "PublicBody|Info requests count" +msgstr "PublicBody|Број на барања за информации" + +msgid "PublicBody|Info requests not held count" +msgstr "PublicBody|Број на неостварени барања за информации" + +msgid "PublicBody|Info requests overdue count" +msgstr "PublicBody|Број на пречекорени барања за информации" + +msgid "PublicBody|Info requests successful count" +msgstr "PublicBody|Број на успешни барања за информации" + +msgid "PublicBody|Info requests visible classified count" +msgstr "" + +msgid "PublicBody|Last edit comment" +msgstr "PublicBody|Последен уреден коментар" + +msgid "PublicBody|Last edit editor" +msgstr "PublicBody|Последен уреден уредник" + +msgid "PublicBody|Name" +msgstr "PublicBody|Име" + +msgid "PublicBody|Notes" +msgstr "PublicBody|Белешки" + +msgid "PublicBody|Publication scheme" +msgstr "PublicBody|Шема на објави" + +msgid "PublicBody|Request email" +msgstr "PublicBody|Побарај е-пошта" + +msgid "PublicBody|Short name" +msgstr "PublicBody|Кратко име" + +msgid "PublicBody|Url name" +msgstr "PublicBody|Име на URL" + +msgid "PublicBody|Version" +msgstr "PublicBody|Верзија" + +msgid "Publication scheme" +msgstr "Шема на објави" + +msgid "Publication scheme URL" +msgstr "URL за шема на објави" + +msgid "Purge request" +msgstr "Барање за пречистување" + +msgid "PurgeRequest|Model" +msgstr "PurgeRequest|Модел" + +msgid "PurgeRequest|Url" +msgstr "PurgeRequest|Url" + +msgid "RSS feed" +msgstr "RSS фид" + +msgid "RSS feed of updates" +msgstr "RSS фид за новости" + +msgid "Re-edit this annotation" +msgstr "Повторно уредете ја оваа белешка" + +msgid "Re-edit this message" +msgstr "Повторно уредете ја оваа порака" + +msgid "Read about advanced search operators, such as proximity and wildcards." +msgstr "Прочитајте за напредните оператори за пребарување, како што се proximity и wildcards." + +msgid "Read blog" +msgstr "Читајте го блогот" + +msgid "Received an error message, such as delivery failure." +msgstr "Примена е порака за грешка, можеби е неуспешна доставата." + +msgid "Recently described results first" +msgstr "Прво се прикажуваат резултатите кои се опишани неодамна" + +msgid "Refused." +msgstr "Одбиено." + +msgid "Remember me (keeps you signed in longer;\\n do not use on a public computer) " +msgstr "Запомни ме (останувате подолго време најавени;\\n не го употребувајте на јавен компјутер) " + +msgid "Report abuse" +msgstr "Пријавете злоупотреба" + +msgid "Report an offensive or unsuitable request" +msgstr "Пријавете навредливо или несоодветно барање" + +msgid "Report request" +msgstr "Пријавете барање" + +msgid "Report this request" +msgstr "Пријавете го ова барање" + +msgid "Reported for administrator attention." +msgstr "Пријавете кај администратор." + +msgid "Request an internal review" +msgstr "Побарајте внатрешна ревизија" + +msgid "Request an internal review from {{person_or_body}}" +msgstr "Побарајте внатрешна ревизија од {{person_or_body}}" + +msgid "Request email" +msgstr "Побарајте е-пошта" + +msgid "Request has been removed" +msgstr "Барањето е отстрането" + +msgid "Request sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "Барањето е испратено до {{public_body_name}} од {{info_request_user}} на {{date}}." + +msgid "Request to {{public_body_name}} by {{info_request_user}}. Annotated by {{event_comment_user}} on {{date}}." +msgstr "Барање до {{public_body_name}} од {{info_request_user}}. Појаснето од {{event_comment_user}} на {{date}}." + +msgid "Requested from {{public_body_name}} by {{info_request_user}} on {{date}}" +msgstr "Побарано од {{public_body_name}} од {{info_request_user}} на {{date}}" + +msgid "Requested on {{date}}" +msgstr "Побарано на {{date}}" + +msgid "Requests are considered overdue if they are in the 'Overdue' or 'Very Overdue' states." +msgstr "Барањата се сметаат со пречекорен рок ако се наоѓаат во 'Пречекорен рок' или 'Многу пречекорен рок' состојба." + +msgid "Requests are considered successful if they were classified as either 'Successful' or 'Partially Successful'." +msgstr "Барањата се сметаат за успешни ако се класифицирани како 'Успешно' или 'Делумно успешно'." + +msgid "Requests for personal information and vexatious requests are not considered valid for FOI purposes (read more)." +msgstr "Барањата за лични информации и вознемирувачките барања не се сметаат за валидни(прочитај повеќе)." + +msgid "Requests or responses matching your saved search" +msgstr "Барања или одговори кои одговараат на зачуваното пребарување" + +msgid "Requests similar to '{{request_title}}'" +msgstr "Барања слични на '{{request_title}}'" + +msgid "Requests similar to '{{request_title}}' (page {{page}})" +msgstr "Барања слични на '{{request_title}}' (страна {{page}})" + +msgid "Respond by email" +msgstr "Одговорете по е-пошта" + +msgid "Respond to request" +msgstr "Одговорете на барање" + +msgid "Respond to the FOI request" +msgstr "Одговорете на барањето за слободен пристап до информации од јавен карактер" + +msgid "Respond using the web" +msgstr "Одговорете преку веб" + +msgid "Response" +msgstr "Одговор" + +msgid "Response from a public authority" +msgstr "Одговор од имателот" + +msgid "Response to '{{title}}'" +msgstr "Одговори на '{{title}}'" + +msgid "Response to this request is delayed." +msgstr "Одговорот на ова барање е одложено." + +msgid "Response to this request is long overdue." +msgstr "Одговорот на ова барање е со многу пречекорен рок." + +msgid "Response to your request" +msgstr "Одговор на вашето барање" + +msgid "Response:" +msgstr "Одговор:" + +msgid "Restrict to" +msgstr "Ограничете на" + +msgid "Results page {{page_number}}" +msgstr "Страна со резултати {{page_number}}" + +msgid "Save" +msgstr "Зачувајте" + +msgid "Search" +msgstr "Барајте" + +msgid "Search Freedom of Information requests, public authorities and users" +msgstr "Пребарајте барања за слободен пристап до информации, иматели и корисници" + +msgid "Search contributions by this person" +msgstr "Пребарајте придонеси од оваа личност" + +msgid "Search for words in:" +msgstr "Пребарајте зборови во:" + +msgid "Search in" +msgstr "Пребарајте во" + +msgid "Search over
    \\n {{number_of_requests}} requests and
    \\n {{number_of_authorities}} authorities" +msgstr "Пребарајте
    \\n {{number_of_requests}} барања и
    \\n {{number_of_authorities}} иматели" + +msgid "Search queries" +msgstr "Фрази за пребарување" + +msgid "Search results" +msgstr "Резултати од пребарување" + +msgid "Search the site to find what you were looking for." +msgstr "Пребарајте го сајтот за да го најдете тоа што го барате." + +msgid "Search within the {{count}} Freedom of Information requests to {{public_body_name}}" +msgid_plural "Search within the {{count}} Freedom of Information requests made to {{public_body_name}}" +msgstr[0] "Пребарајте во рамки на {{count}} барање за слободен пристап до информации до {{public_body_name}}" +msgstr[1] "Пребарај во рамки на {{count}} барањата за слободен пристап до информации направени до {{public_body_name}}" + +msgid "Search your contributions" +msgstr "Пребарајте ги вашите придонеси" + +msgid "See bounce message" +msgstr "Видете ја одбиената порака" + +msgid "Select one to see more information about the authority." +msgstr "Изберете имател за да видите повеќе информации." + +msgid "Select the authority to write to" +msgstr "Изберете имател до кој ќе пишете" + +msgid "Send a followup" +msgstr "Испратете реакција" + +msgid "Send a message to " +msgstr "Испратете порака до" + +msgid "Send a public follow up message to {{person_or_body}}" +msgstr "Испратете јавна порака со реакција на {{person_or_body}}" + +msgid "Send a public reply to {{person_or_body}}" +msgstr "Испратете јавен одговор до {{person_or_body}}" + +msgid "Send follow up to '{{title}}'" +msgstr "Испратете реакција до '{{title}}'" + +msgid "Send message" +msgstr "Испратете порака" + +msgid "Send message to " +msgstr "Испратете порака до" + +msgid "Send request" +msgstr "Испратете барање" + +msgid "Set your profile photo" +msgstr "Поставете фотографија за профилот" + +msgid "Short name" +msgstr "Кратко име" + +msgid "Short name is already taken" +msgstr "Краткото име е веќе зафатено" + +msgid "Show most relevant results first" +msgstr "Прикажи ги релевантните резултати први" + +msgid "Show only..." +msgstr "Прикажи само..." + +msgid "Showing" +msgstr "Прикажани се" + +msgid "Sign in" +msgstr "Најавете се" + +msgid "Sign in or make a new account" +msgstr "Најавете се или креирајте нов профил" + +msgid "Sign in or sign up" +msgstr "Најавете се или регистрирајте се" + +msgid "Sign out" +msgstr "Одјавете се" + +msgid "Sign up" +msgstr "Регистрирајте се" + +msgid "Similar requests" +msgstr "Слични барања" + +msgid "Simple search" +msgstr "Едноставно пребарување" + +msgid "Some notes have been added to your FOI request - " +msgstr "Додадени се неколку белешки во вашето барање - " + +msgid "Some of the information requested has been received" +msgstr "Дел од побараните информации се примени" + +msgid "Some people who've made requests haven't let us know whether they were\\nsuccessful or not. We need your help –\\nchoose one of these requests, read it, and let everyone know whether or not the\\ninformation has been provided. Everyone'll be exceedingly grateful." +msgstr "Некои од луѓето кои имаат направено барања не немаат известено дали тие се\\nуспешни или не. Ни треба вашата помош –\\nИзберете едно од барањата, прочитајте го и известете ги сите дали \\nинформациите се обезбедени или не. Сите ќе бидат мошне благодарни." + +msgid "Somebody added a note to your FOI request - " +msgstr "Некој додаде белешка на вашето барање - " + +msgid "Someone has updated the status of your request" +msgstr "Некој го ажурираше статусот на вашето барање" + +msgid "Someone, perhaps you, just tried to change their email address on\\n{{site_name}} from {{old_email}} to {{new_email}}." +msgstr "Некој, можеби вие, само што се обиде да ја промени адресата за е-пошта на\\n{{site_name}} од {{old_email}} во {{new_email}}." + +msgid "Sorry - you cannot respond to this request via {{site_name}}, because this is a copy of the request originally at {{link_to_original_request}}." +msgstr "Се извинуваме - не можете да одговорите на ова барање преку {{site_name}}, бидејќи ова е копија од оригиналното барање на {{link_to_original_request}}." + +msgid "Sorry, but only {{user_name}} is allowed to do that." +msgstr "Се извинуваме, но само {{user_name}} може да го направи тоа." + +msgid "Sorry, there was a problem processing this page" +msgstr "Се извинуваме, настана проблем при процесирање на оваа страница" + +msgid "Sorry, we couldn't find that page" +msgstr "Се извинуваме, не можевме да ја најдеме таа страница" + +msgid "Special note for this authority!" +msgstr "Специјална белешка за овој имател!" + +msgid "Start now »" +msgstr "Почнете сега »" + +msgid "Start your own blog" +msgstr "Почнете сопствен блог" + +msgid "Stay up to date" +msgstr "Останете во тек" + +msgid "Still awaiting an internal review" +msgstr "Се уште се чека на внатрешна ревизија" + +msgid "Subject" +msgstr "Наслов" + +msgid "Subject:" +msgstr "Наслов:" + +msgid "Submit" +msgstr "Испратете" + +msgid "Submit status" +msgstr "Испратете статус" + +msgid "Submit status and send message" +msgstr "Испратете статус и испратете порака" + +msgid "Subscribe to blog" +msgstr "Претплатете се на блогот" + +msgid "Successful Freedom of Information requests" +msgstr "Успешно барање за слободен пристап до информации" + +msgid "Successful." +msgstr "Успешно." + +msgid "Suggest how the requester can find the rest of the information." +msgstr "Предложете како барателот може да го најде остатокот од информацијата." + +msgid "Summary:" +msgstr "Резиме:" + +msgid "Table of statuses" +msgstr "Табела од статуси" + +msgid "Table of varieties" +msgstr "Табела од разноличности" + +msgid "Tags" +msgstr "Тагови" + +msgid "Tags (separated by a space):" +msgstr "Тагови (одвоени со празно место):" + +msgid "Tags:" +msgstr "Тагови:" + +msgid "Technical details" +msgstr "Технички детали" + +msgid "Thank you for helping us keep the site tidy!" +msgstr "Ви благодариме што помагате да го одржиме сајтот уреден!" + +msgid "Thank you for making an annotation!" +msgstr "Ви благодариме што направивте белешка!" + +msgid "Thank you for responding to this FOI request! Your response has been published below, and a link to your response has been emailed to " +msgstr "Ви благодариме што одговоривте на ова барање за слободен пристап до информации од јавен карактер! Вашиот одговор е објавен подолу и пратена е врска до вашето барање по е-пошта до " + +msgid "Thank you for updating the status of the request '{{info_request_title}}'. There are some more requests below for you to classify." +msgstr "Ви благодариме за ажурирање на статусот на барањето '{{info_request_title}}'. Постојат уште неколку барања подолу кои може да ги класифицирате." + +msgid "Thank you for updating this request!" +msgstr "Ви благодариме што го ажуриравте барањето!" + +msgid "Thank you for updating your profile photo" +msgstr "Ви благодариме за ажурирање на фотографијата за профил" + +msgid "Thank you! We'll look into what happened and try and fix it up." +msgstr "Ви благодариме! Ќе погледнеме што се случило и ќе се обидеме да го поправиме." + +msgid "Thanks for helping - your work will make it easier for everyone to find successful\\nresponses, and maybe even let us make league tables..." +msgstr "Благодариме за помошта - вашиот придонес ќе помогне сите да може лесно да најдат успешни\\nодговори." + +msgid "Thanks very much - this will help others find useful stuff. We'll\\n also, if you need it, give advice on what to do next about your\\n requests." +msgstr "Многу ви благодариме - ова ќе помогне другите да најдат корисни работи. Ние исто така\\n ќе ви дадеме совет, доколку ви треба, што да напрвите следно со вашите\\n барања." + +msgid "Thanks very much for helping keep everything neat and organised.\\n We'll also, if you need it, give you advice on what to do next about each of your\\n requests." +msgstr "Многу ви благодариме што помогнавте се да е уредно и организирано.\\n Ние исто така ќе ви дадеме совет, доколку ви треба, што да направите следно за секое од вашите\\n барања." + +msgid "That doesn't look like a valid email address. Please check you have typed it correctly." +msgstr "Тоа не е валидна адреса за е-пошта. Ве молиме проверете дали ја внесовте правилно." + +msgid "The review has finished and overall:" +msgstr "Ревизијата заврши и севкупно:" + +msgid "The Freedom of Information Act does not apply to" +msgstr "Законот за слободен пристап до информации не важи на" + +msgid "The accounts have been left as they previously were." +msgstr "Сметките се оставени во првобитната состојба." + +msgid "The authority do not have the information (maybe they say who does)" +msgstr "Имателот гинема информациите (можеби имаат кажано кој може да ги има)" + +msgid "The authority only has a paper copy of the information." +msgstr "Имателот поседува само печатена копија од информациите." + +msgid "The authority say that they need a postal\\n address, not just an email, for it to be a valid FOI request" +msgstr "Имателот вели дека им е потребна поштенска\\n адреса, не само е-пошта, за барањето да биде валидно" + +msgid "The authority would like to / has responded by post to this request." +msgstr "Имателот би сакал да/има одговорено по пошта на ова барање." + +msgid "The classification of requests (e.g. to say whether they were successful or not) is done manually by users and administrators of the site, which means that they are subject to error." +msgstr "Класификацијата на барањата (на пр. да се каже дали се успешни или не) се направени рачно од страна на корисниците и администраторите на сајтот, што значи дека можно е да постои грешка." + +msgid "The email that you, on behalf of {{public_body}}, sent to\\n{{user}} to reply to an {{law_used_short}}\\nrequest has not been delivered." +msgstr "Е-поштата која вие, во име на {{public_body}}, ја испративте до\\n{{user}} за да одговорите на барањето {{law_used_short}}\\n не беше доставена." + +msgid "The error bars shown are 95% confidence intervals for the hypothesized underlying proportion (i.e. that which you would obtain by making an infinite number of requests through this site to that authority). In other words, the population being sampled is all the current and future requests to the authority through this site, rather than, say, all requests that have been made to the public body by any means." +msgstr "Баровите за грешка од дијаграмот се 95% интервали на доверба за претпоставената основна пропорција (т.е. она што ќе се добие со пласирање бесконечен број на барања преку овој сајт до тој имател). Со други зборови, популацијата која е примерокот е составена од моментални и идни баратели до имателот преку овој сајт, наместо, да речеме, сите барања кои биле направени до јавната институција по секоја цена." + +msgid "The page doesn't exist. Things you can try now:" +msgstr "Страната не постои. Работи кои може да ги пробате:" + +msgid "The percentages are calculated with respect to the total number of requests, which includes invalid requests; this is a known problem that will be fixed in a later release." +msgstr "Процентите се пресметуваат во однос на вкупниот број на барања, кој вклучува неважечки барања. Ова е познат проблем кој ќе биде надминат во следните верзии." + +msgid "The public authority does not have the information requested" +msgstr "Имателот ја нема бараната информација" + +msgid "The public authority would like part of the request explained" +msgstr "Имателот би сакал дел од барањето да се објасни" + +msgid "The public authority would like to / has responded by post" +msgstr "Имателот би сакал да/веќе одговорил по пошта" + +msgid "The request has been refused" +msgstr "Барањето беше одбиено" + +msgid "The request has been updated since you originally loaded this page. Please check for any new incoming messages below, and try again." +msgstr "Барањето беше ажурирано откако ја вчитавте оваа страна. Ве молиме проверете за нови дојдовни пораки подолу и обидете се повторно." + +msgid "The request is waiting for clarification." +msgstr "Барањето чека за класификација." + +msgid "The request was partially successful." +msgstr "Барањето е делумно успешно." + +msgid "The request was refused by" +msgstr "Барањето е одбиено by" + +msgid "The request was successful." +msgstr "Барањето беше успешно." + +msgid "The request was refused by the public authority" +msgstr "Барањето беше одбиено од имателот" + +msgid "The request you have tried to view has been removed. There are\\nvarious reasons why we might have done this, sorry we can't be more specific here. Please contact us if you have any questions." +msgstr "Барањето кое се обидовте да го видете беше отстрането. Постојат\\nразни причини зошто го направивме ова, се извинуваме, не можеме да бидеме поконкретни овде. Ве молиме контактирајте не ако имате прашања." + +msgid "The requester has abandoned this request for some reason" +msgstr "Барателот го напушти ова барање од некои причини" + +msgid "The response to your request has been delayed. You can say that,\\n by law, the authority should normally have responded\\n promptly and" +msgstr "Одговорот на вашето барање е одложен. Можете да кажете дека,\\n според закон, имателот нормално би требало да одговори\\n веднаш и" + +msgid "The response to your request is long overdue. You can say that, by\\n law, under all circumstances, the authority should have responded\\n by now" +msgstr "Одговорот на вашето барање е со многу пречекорено рок. Може да кажете дека, според\\n закон, под сите околности, имателот требало да одговори\\n до сега" + +msgid "The search index is currently offline, so we can't show the Freedom of Information requests that have been made to this authority." +msgstr "Регистарот за пребарување во моментов е офлајн, па не сме во можност да ги прикажеме барањата за слободен пристап до информации кои се направени до овој имател." + +msgid "The search index is currently offline, so we can't show the Freedom of Information requests this person has made." +msgstr "Регистарот за пребарување во моментов е офлајн, па не сме во можност да ги прикажеме барањата за слободен пристап до информации кои се направени од оваа личност." + +msgid "The {{site_name}} team." +msgstr "Тимот на {{site_name}}." + +msgid "Then you can cancel the alert." +msgstr "Тогаш можете да го откажете предупредувањето." + +msgid "Then you can cancel the alerts." +msgstr "Тогаш можете да ги откажете предупредувањата." + +msgid "Then you can change your email address used on {{site_name}}" +msgstr "Тогаш можете да ја промените адресата за е-пошта која се користи на {{site_name}}" + +msgid "Then you can change your password on {{site_name}}" +msgstr "Тогаш можете да ја промените лозинката на {{site_name}}" + +msgid "Then you can classify the FOI response you have got from " +msgstr "Тогаш можете да го класифицирате одговорот кој го добивте од " + +msgid "Then you can download a zip file of {{info_request_title}}." +msgstr "Тогаш можете да преземете zip датотека од {{info_request_title}}." + +msgid "Then you can log into the administrative interface" +msgstr "Тогаш можете да се најавите на интерфејсот за администрирање " + +msgid "Then you can play the request categorisation game." +msgstr "Тогаш можете да ја играте играта за категоризација на барањата." + +msgid "Then you can report the request '{{title}}'" +msgstr "Тогаш можете да го пријавите барањето '{{title}}'" + +msgid "Then you can send a message to " +msgstr "Тогаш можете да пратите порака до" + +msgid "Then you can sign in to {{site_name}}" +msgstr "Тогаш можете да се најавите на {{site_name}}" + +msgid "Then you can update the status of your request to " +msgstr "Тогаш можете да го ажурирате статусот на вашето барање до " + +msgid "Then you can upload an FOI response. " +msgstr "Тогаш можете да прикачите одговор. " + +msgid "Then you can write follow up message to " +msgstr "Тогаш можете да напишете порака за реакција до" + +msgid "Then you can write your reply to " +msgstr "Тогаш можете да напишете одговор до " + +msgid "Then you will be following all new FOI requests." +msgstr "Тогаш ќе ги следите сите нови барања." + +msgid "Then you will be notified whenever '{{user_name}}' requests something or gets a response." +msgstr "Тогаш ќе бидете известени секогаш кога '{{user_name}}' ќе побара нешто или ќе добие одговор." + +msgid "Then you will be notified whenever a new request or response matches your search." +msgstr "Тогаш ќе бидете известени секогаш кога ново барање или одговор ќе одговара на вашето пребарување." + +msgid "Then you will be notified whenever an FOI request succeeds." +msgstr "Тогаш ќе бидете известени секогаш кога барање за слободен пристап ќе биде успешно." + +msgid "Then you will be notified whenever someone requests something or gets a response from '{{public_body_name}}'." +msgstr "Тогаш ќе бидете известени секогаш кога некој ќе побара нешто или ќе добие одговор од '{{public_body_name}}'." + +msgid "Then you will be updated whenever the request '{{request_title}}' is updated." +msgstr "Тогаш ќе бидете известени секогаш кога барањето '{{request_title}}' е ажурирано." + +msgid "Then you'll be allowed to send FOI requests." +msgstr "Тогаш ќе ви биде дозволено да праќате барања." + +msgid "Then your FOI request to {{public_body_name}} will be sent." +msgstr "Тогаш вашето барање до {{public_body_name}} ќе биде испратено." + +msgid "Then your annotation to {{info_request_title}} will be posted." +msgstr "Тогаш вашата белешка до {{info_request_title}} ќе биде објавена." + +msgid "There are {{count}} new annotations on your {{info_request}} request. Follow this link to see what they wrote." +msgstr "Постојат {{count}} нови белешки на вашето {{info_request}} барање. Кликнете на оваа врска за да видете што напишале." + +msgid "There is more than one person who uses this site and has this name.\\n One of them is shown below, you may mean a different one:" +msgstr "Постои повеќе од еден корисник кој ја користи оваа страна и го има ова име.\\n Еден од нив е прикажан подолу, можеби мислевте на друг:" + +msgid "There is a limit on the number of requests you can make in a day, because we don’t want public authorities to be bombarded with large numbers of inappropriate requests. If you feel you have a good reason to ask for the limit to be lifted in your case, please get in touch." +msgstr "Постои ограничување за бројот на барања кои може да ги направите во еден ден, бидејќи не сакаме имателите да бидат „бомбардирани“ со голем број на барања. Доколку сметате дека имате јака причина зошто ова ограничување не треба да важи за вас, ве молиме стапете во контакт." + +msgid "There is {{count}} person following this request" +msgid_plural "There are {{count}} people following this request" +msgstr[0] "Постои {{count}} корисник кој го следи ова барање" +msgstr[1] "Постојат {{count}} корисници кои го следат ова барање" + +msgid "There was a delivery error or similar, which needs fixing by the {{site_name}} team." +msgstr "Настана грешка при достава или слична грешка, за која е потребна исправка од {{site_name}} тимот." + +msgid "There was an error with the words you entered, please try again." +msgstr "Настана грешка со зборовите кои ги внесовте, ве молиме обидете се повторно." + +msgid "There was no data calculated for this graph yet." +msgstr "Се уште не постои пресметан податок за овој график." + +msgid "There were no requests matching your query." +msgstr "Не постојат барања кои одговараат на вашата фраза за пребарување." + +msgid "There were no results matching your query." +msgstr "Не постојат резултати кои одговараат на вашата фраза за пребарување." + +msgid "These graphs were partly inspired by some statistics that Mark Goodge produced for WhatDoTheyKnow, so thanks are due to him." +msgstr "Овие графикони беа делумно инспирирани од некои статистики кои Mark Goodge ги изработи за WhatDoTheyKnow, па затоа голема благодарност до него." + +msgid "They are going to reply by post" +msgstr "Тие ќе одговорат преку пошта" + +msgid "They do not have the information (maybe they say who does)" +msgstr "Тие јанемаат информацијата (можеби ќе кажат кој ја има)" + +msgid "They have been given the following explanation:" +msgstr "Им беше дадено следново објаснување:" + +msgid "They have not replied to your {{law_used_short}} request {{title}} promptly, as normally required by law" +msgstr "Тие немаат одговорено на вашето {{law_used_short}} барање {{title}} веднаш, како што нормално законот изнудува" + +msgid "They have not replied to your {{law_used_short}} request {{title}}, \\nas required by law" +msgstr "Тие немаат одговорено на вашето {{law_used_short}} барање {{title}}, \\nкако што изнудува законот" + +msgid "Things to do with this request" +msgstr "Работи кои може да се направат со ова барање" + +msgid "Things you're following" +msgstr "Работи кои ги следите" + +msgid "This authority no longer exists, so you cannot make a request to it." +msgstr "Имателот не постои повеќе, затоа не може да направите барање до него." + +msgid "This covers a very wide spectrum of information about the state of\\n the natural and built environment, such as:" +msgstr "Ова покрива широк спектар од информации за состојбата на\\n околината, како што се:" + +msgid "This external request has been hidden" +msgstr "Надворешното барање беше сокриено" + +msgid "This is a plain-text version of the Freedom of Information request \"{{request_title}}\". The latest, full version is available online at {{full_url}}" +msgstr "Ова е верзија од барањето за слободен пристап до информации \"{{request_title}}\", во која е прикажано само текст. Последната, целосна верзија е достапна онлајн на {{full_url}}" + +msgid "This is an HTML version of an attachment to the Freedom of Information request" +msgstr "Ова е HTML верзија од прилогот за барањето за слободен пристап до информации" + +msgid "This is because {{title}} is an old request that has been\\nmarked to no longer receive responses." +msgstr "Ова е бидејќи {{title}} е старо барање кое било\\nмаркирано повеќе да не добива одговори." + +msgid "This is the first version." +msgstr "Ова е првата верзија." + +msgid "This is your own request, so you will be automatically emailed when new responses arrive." +msgstr "Ова е ваше сопствено барање, затоа автоматски ќе ви биде испратена е-пошта кога ќе пристигне нов одговор." + +msgid "This message has been hidden." +msgstr "Оваа порака беше скриена." + +msgid "This message has been hidden. There are various reasons why we might have done this, sorry we can't be more specific here." +msgstr "Оваа порака беше скриена. Постојат повеќе причини зошто сме го направиле ова, се извинуваме што не можеме да бидеме поконкретни тука." + +msgid "This message has prominence 'hidden'. You can only see it because you are logged in as a super user." +msgstr "Оваа порака има ознака 'hidden' (скриена). Може да ја видите затоа што се најавени како супер корисник." + +msgid "This message has prominence 'hidden'. {{reason}} You can only see it because you are logged in as a super user." +msgstr "Оваа порака има ознака 'hidden' (скриена). {{reason}} Може да ја видите затоа што се најавени како супер корисник." + +msgid "This message is hidden, so that only you, the requester, can see it. Please contact us if you are not sure why." +msgstr "Оваа порака е сокриена, за да може само вие, барателот, да ја видите. Ве молиме контактирајте не ако не сте сигурни зошто." + +msgid "This message is hidden, so that only you, the requester, can see it. {{reason}}" +msgstr "Оваа порака е сокриена, за да може само вие, барателот, да ја видите. {{reason}}" + +msgid "This page of public body statistics is currently experimental, so there are some caveats that should be borne in mind:" +msgstr "Оваа страница со статистика за имателот е во експериментална фаза, па постојат некои ограничувања кои треба да ги имате на ум:" + +msgid "This particular request is finished:" +msgstr "Ова конкретно барање е завршено:" + +msgid "This person has made no Freedom of Information requests using this site." +msgstr "Оваа личност нема направено барања за слободен пристап до информации преку оваа страна." + +msgid "This person's annotations" +msgstr "Белешки од оваа личност" + +msgid "This person's {{count}} Freedom of Information request" +msgid_plural "This person's {{count}} Freedom of Information requests" +msgstr[0] "{{count}} барање за слободен пристап до информации од оваа личност" +msgstr[1] "{{count}} барања за слободен пристап до информации од оваа личност" + +msgid "This person's {{count}} annotation" +msgid_plural "This person's {{count}} annotations" +msgstr[0] "{{count}} белешка од оваа личност" +msgstr[1] "{{count}} белешки од оваа личност" + +msgid "This request requires administrator attention" +msgstr "Ова барање изнудува внимание од администратор" + +msgid "This request has already been reported for administrator attention" +msgstr "Ова барање е веќе пријавено за проверка од администратор" + +msgid "This request has an unknown status." +msgstr "Ова барање има непознат статус." + +msgid "This request has been hidden from the site, because an administrator considers it not to be an FOI request" +msgstr "Ова барање беше сокриено на страната, бидејќи администраторот сметал дека истото не е барање за слободен пристап до информации од јавен карактер" + +msgid "This request has been hidden from the site, because an administrator considers it vexatious" +msgstr "Ова барање беше сокриено на страната, бидејќи администраторот сметал дека истото е вознемирувачко" + +msgid "This request has been reported as needing administrator attention (perhaps because it is vexatious, or a request for personal information)" +msgstr "Ова барање беше пријавено за проверка од администратор (можеби е вознемирувачко или побарува лични податоци)" + +msgid "This request has been withdrawn by the person who made it.\\n There may be an explanation in the correspondence below." +msgstr "Ова барање беше повлечено од корисникот кој го направи.\\n Можеби има објаснување во коресподенцијата подолу." + +msgid "This request has been marked for review by the site administrators, who have not hidden it at this time. If you believe it should be hidden, please contact us." +msgstr "Ова барање беше означено за проверка од администраторите, кои засега го немаат сокриено. Ако верувате дека треба да биде сокриено, ве молиме контактирајте не." + +msgid "This request has been reported for administrator attention" +msgstr "Ова барање беше пријавено за проверка од администратор" + +msgid "This request has been set by an administrator to \"allow new responses from nobody\"" +msgstr "Ова барање беше означено од администратор да \"не прима нови барања\"" + +msgid "This request has had an unusual response, and requires attention from the {{site_name}} team." +msgstr "За ова барање беше испратен невообичаен одговор и изнудува внимание од {{site_name}} тимот." + +msgid "This request has prominence 'hidden'. You can only see it because you are logged\\n in as a super user." +msgstr "Ова барање има ознака 'hidden' (сокриено). Може да го гледате само затоа што сте најавени\\n како супер корисник." + +msgid "This request is hidden, so that only you the requester can see it. Please\\n contact us if you are not sure why." +msgstr "Ова барање е сокриено, затоа само вие, барателот, може да го видите. Ве молиме\\n контактирајте не ако не сте сигурни зошто." + +msgid "This request is still in progress:" +msgstr "Ова барање се уште се процесира:" + +msgid "This request requires administrator attention" +msgstr "Ова барање изнудува внимание од администратор" + +msgid "This request was not made via {{site_name}}" +msgstr "Ова барање не беше направено преку {{site_name}}" + +msgid "This table shows the technical details of the internal events that happened\\nto this request on {{site_name}}. This could be used to generate information about\\nthe speed with which authorities respond to requests, the number of requests\\nwhich require a postal response and much more." +msgstr "Оваа табела ги прикажува техничките детали од интерните настани кои се случија\\nза ова барање на {{site_name}}. Ова може да се користи за генерирање информации за\\nбрзината со која имателите одговараат на барањата, бројот на барањата\\nкои изнудуваат одговор преку пошта и др.." + +msgid "This user has been banned from {{site_name}} " +msgstr "Овој корисник има забрана за пристап до {{site_name}} " + +msgid "This was not possible because there is already an account using \\nthe email address {{email}}." +msgstr "Ова не е возможно бидејќи веќе постои сметка која ја користи \\nоваа адреса за е-пошта {{email}}." + +msgid "To cancel these alerts" +msgstr "За да ги откажете овие предупредувања" + +msgid "To cancel this alert" +msgstr "За да го откажете ова предупредување" + +msgid "To carry on, you need to sign in or make an account. Unfortunately, there\\nwas a technical problem trying to do this." +msgstr "За да продолжите, треба да се најавите или да креирате сметка. За жал, настана\\nтехнички проблем при обидот." + +msgid "To change your email address used on {{site_name}}" +msgstr "За да ја промените адресата за е-пошта која се користи на {{site_name}}" + +msgid "To classify the response to this FOI request" +msgstr "За да го класифицирате одговорот на ова барање за слободен пристап до информации" + +msgid "To do that please send a private email to " +msgstr "За да го направите тоа, ве молиме испратете приватна е-пошта до " + +msgid "To do this, first click on the link below." +msgstr "За да го напрвите ова, прво кликнете на врската подолу." + +msgid "To download the zip file" +msgstr "За да преземете zip датотека" + +msgid "To follow all successful requests" +msgstr "За да ги следите успешните барања" + +msgid "To follow new requests" +msgstr "За да ги следите новите барања" + +msgid "To follow requests and responses matching your search" +msgstr "За да ги следите барањата и одговорите кои одговараат на вашето пребарување" + +msgid "To follow requests by '{{user_name}}'" +msgstr "За да ги следите барањата од '{{user_name}}'" + +msgid "To follow requests made using {{site_name}} to the public authority '{{public_body_name}}'" +msgstr "За да ги следите барањата направени преку {{site_name}} до имателот '{{public_body_name}}'" + +msgid "To follow the request '{{request_title}}'" +msgstr "За да го следите барањето '{{request_title}}'" + +msgid "To help us keep the site tidy, someone else has updated the status of the \\n{{law_used_full}} request {{title}} that you made to {{public_body}}, to \"{{display_status}}\" If you disagree with their categorisation, please update the status again yourself to what you believe to be more accurate." +msgstr "За да ни помогне да ја одржиме страната уредна, некој друг го ажурирал статусот за \\n{{law_used_full}} барањето {{title}} кое го направивте до {{public_body}}, во \"{{display_status}}\" Ако не се согласувате со категоризацијата, Ве молиме ажурирајте го статусот одново во она што сметате дека е поточно." + +msgid "To let everyone know, follow this link and then select the appropriate box." +msgstr "За да ги известите сите, следете ја оваа врска и потоа изберете го соодветното сандаче." + +msgid "To log into the administrative interface" +msgstr "За да се најавите на администраторскиот интерфејс" + +msgid "To play the request categorisation game" +msgstr "За да ја играте играта за категоризација на барања" + +msgid "To post your annotation" +msgstr "За да ја објавите вашата белешка" + +msgid "To reply to " +msgstr "За да одговорите до " + +msgid "To report this request" +msgstr "За да го пријавите ова барање" + +msgid "To send a follow up message to " +msgstr "За да пратите порака за реакција до " + +msgid "To send a message to " +msgstr "За да пратите порака до " + +msgid "To send your FOI request" +msgstr "За да го пратите вашето барање" + +msgid "To update the status of this FOI request" +msgstr "За да го ажурирате статусот на ова барање" + +msgid "To upload a response, you must be logged in using an email address from " +msgstr "За да прикачите одговор, мора да сте најавени со адресата за е-пошта од " + +msgid "To use the advanced search, combine phrases and labels as described in the search tips below." +msgstr "За да го користете напредното пребарување, комбинирајте фрази и натписи како што е објаснето во препораките за пребарување подолу." + +msgid "To view the email address that we use to send FOI requests to {{public_body_name}}, please enter these words." +msgstr "За да ја видите адресата за е-пошта која ја користиме за испраќање барања до {{public_body_name}}, Ве молиме внесете ги овие зборови." + +msgid "To view the response, click on the link below." +msgstr "За да го видите одговорот, кликнете на врската подолу." + +msgid "To {{public_body_link_absolute}}" +msgstr "До {{public_body_link_absolute}}" + +msgid "To:" +msgstr "До:" + +msgid "Today" +msgstr "Денес" + +msgid "Too many requests" +msgstr "Премногу барања" + +msgid "Top search results:" +msgstr "За да пребарувате барања:" + +msgid "Track thing" +msgstr "Следете го предметот" + +msgid "Track this person" +msgstr "Следете ја оваа личност" + +msgid "Track this search" +msgstr "Следете го ова пребарување" + +msgid "TrackThing|Track medium" +msgstr "TrackThing|Следи медиум" + +msgid "TrackThing|Track query" +msgstr "TrackThing|Следи фраза" + +msgid "TrackThing|Track type" +msgstr "TrackThing|Следи тип" + +msgid "Turn off email alerts" +msgstr "Исклучете ги предупредувањата преку е-пошта" + +msgid "Tweet this request" +msgstr "Твитнете за ова барање" + +msgid "Type 01/01/2008..14/01/2008 to only show things that happened in the first two weeks of January." +msgstr "Внеси 01/01/2013..14/01/2013 за да ги покажеш работите кои се случиле во првите две недели од јануари." + +msgid "URL name can't be blank" +msgstr "URL името не може да биде празно" + +msgid "Unable to change email address on {{site_name}}" +msgstr "Не може да се промени адресата за е-пошта на {{site_name}}" + +msgid "Unable to send a reply to {{username}}" +msgstr "Не може да се испрати одговор на {{username}}" + +msgid "Unable to send follow up message to {{username}}" +msgstr "Не може да се испрати порака за реакција до {{username}}" + +msgid "Unexpected search result type" +msgstr "Неочекуван тип на резултат од пребарување" + +msgid "Unexpected search result type " +msgstr "Неочекуван тип на резултат од пребарување " + +msgid "Unfortunately we don't know the FOI\\nemail address for that authority, so we can't validate this.\\nPlease contact us to sort it out." +msgstr "" +"За жал, не ја знаеме адресата за е-пошта за слободен пристап до информации\n" +"за тој имател, така што не можеме да го потврдиме ова.\n" +"Ве молиме контактирајте не за да ја разјасниме ситуацијава." + +msgid "Unfortunately, we do not have a working {{info_request_law_used_full}}\\naddress for" +msgstr "" +"За жал, немаме исправна {{info_request_law_used_full}}\n" +"адреса за" + +msgid "Unknown" +msgstr "Непознат" + +msgid "Unsubscribe" +msgstr "Откажи претплата" + +msgid "Unusual response." +msgstr "Невообичаен одговор." + +msgid "Update the status of this request" +msgstr "Ажурирајте го статусот за ова барање" + +msgid "Update the status of your request to " +msgstr "Ажурирајте го статусот на вашето барање до " + +msgid "Upload FOI response" +msgstr "Прикачи одговор" + +msgid "Use OR (in capital letters) where you don't mind which word, e.g. commons OR lords" +msgstr "Користете OR (со големи букви) кога ви е сеедно кој збор, на пр. улици OR булевари" + +msgid "Use quotes when you want to find an exact phrase, e.g. \"Liverpool City Council\"" +msgstr "Користете наводници кога сакате да пронајдете идентична фраза, на пр. \"Министерство за внатрешни работи\"" + +msgid "User" +msgstr "Корисник" + +msgid "User info request sent alert" +msgstr "Барањето за информации за корисник испрати предупредување" + +msgid "User – {{name}}" +msgstr "Корисник – {{name}}" + +msgid "UserInfoRequestSentAlert|Alert type" +msgstr "UserInfoRequestSentAlert|Тип на предупредување" + +msgid "User|About me" +msgstr "User|За мене" + +msgid "User|Admin level" +msgstr "User|Администраторско ниво" + +msgid "User|Ban text" +msgstr "User|Текст за исклучување" + +msgid "User|Email" +msgstr "User|Е-пошта" + +msgid "User|Email bounce message" +msgstr "User|Прака за одбиената порака" + +msgid "User|Email bounced at" +msgstr "User|Е-пошта одбиена кај" + +msgid "User|Email confirmed" +msgstr "User|Е-пошта потврдена" + +msgid "User|Hashed password" +msgstr "User|Хаширани лозинки" + +msgid "User|Last daily track email" +msgstr "User|Последна е-пошта од дневно следење" + +msgid "User|Locale" +msgstr "User|Јазик" + +msgid "User|Name" +msgstr "User|Име" + +msgid "User|No limit" +msgstr "User|Без ограничување" + +msgid "User|Receive email alerts" +msgstr "User|Добивај предупредувања по е-пошта" + +msgid "User|Salt" +msgstr "User|Сол" + +msgid "User|Url name" +msgstr "User|Url име" + +msgid "Version {{version}}" +msgstr "Верзија {{version}}" + +msgid "View FOI email address" +msgstr "Видете ја адресата за е-пошта за слободен пристап до информации од јавен карактер" + +msgid "View FOI email address for '{{public_body_name}}'" +msgstr "Видете ја адресата за е-пошта за слободен пристап до информации за '{{public_body_name}}'" + +msgid "View FOI email address for {{public_body_name}}" +msgstr "Видете ја адресата за е-пошта за слободен пристап со информации од јавен карактер за {{public_body_name}}" + +msgid "View Freedom of Information requests made by {{user_name}}:" +msgstr "Видете ги барањата за слободен пристап до информации од јавен карактер направени од {{user_name}}:" + +msgid "View and search requests" +msgstr "Видете и пребарајте барања" + +msgid "View authorities" +msgstr "Видете ги имателите" + +msgid "View email" +msgstr "Видете е-пошта" + +msgid "View requests" +msgstr "Видете барања" + +msgid "Waiting clarification." +msgstr "Се чека појаснување." + +msgid "Waiting for an internal review by {{public_body_link}} of their handling of this request." +msgstr "Се чека на интерна ревизија од {{public_body_link}} во врска со нивната обработка на ова барање." + +msgid "Waiting for the public authority to complete an internal review of their handling of the request" +msgstr "Се чека имателот да ја заврши внатрешната ревизија за нивната обработка на барањето" + +msgid "Waiting for the public authority to reply" +msgstr "Се чека имателот да одговори" + +msgid "Was the response you got to your FOI request any good?" +msgstr "Дали одговорот што го добивте на вашето барање ви е од корист?" + +msgid "We consider it is not a valid FOI request, and have therefore hidden it from other users." +msgstr "Сметаме дека ова не е важечко барање за слободен пристап, па од тие причини го сокривме од останатите корисници." + +msgid "We consider it to be vexatious, and have therefore hidden it from other users." +msgstr "Сметаме дека ова барање е вознемирувачко, па од тие причини го сокривме од останатите корисници." + +msgid "We do not have a working request email address for this authority." +msgstr "Немаме исправна адреса за е-пошта за овој имател." + +msgid "We do not have a working {{law_used_full}} address for {{public_body_name}}." +msgstr "Немаме исправна {{public_body_name}} адреса за {{law_used_full}}." + +msgid "We don't know whether the most recent response to this request contains\\n information or not\\n –\\n\tif you are {{user_link}} please sign in and let everyone know." +msgstr "Не знаеме дали последниот одговор на ова барање содржи\\n информации или не\\n –\\n\tако сте вие {{user_link}} Ве молиме најавете се и известете ги сите." + +msgid "We will not reveal your email address to anybody unless you or\\n the law tell us to (details). " +msgstr "Нема да ја откриеме вашата адреса за е-пошта на никого освен ако вие\\nили законот не не натера да го направиме тоа (детали). " + +msgid "We will not reveal your email address to anybody unless you\\nor the law tell us to." +msgstr "Нема да ја откриеме вашата адреса за е-пошта на никого освен ако вие\\nили законот не не натера да го направиме тоа." + +msgid "We will not reveal your email addresses to anybody unless you\\nor the law tell us to." +msgstr "Нема да ја откриеме вашата адреса за е-пошта на никого, освен ако вие\\nили законот не не натера да го направиме тоа." + +msgid "We're waiting for" +msgstr "Чекаме за" + +msgid "We're waiting for someone to read" +msgstr "Чекаме некој да прочита" + +msgid "We've sent an email to your new email address. You'll need to click the link in\\nit before your email address will be changed." +msgstr "" +"Испративме е-пошта на вашата нова адреса за е-пошта. Ќе мора да кликнете на врската во неа\n" +"пред вашата адреса за е-пошта да биде променета." + +msgid "We've sent you an email, and you'll need to click the link in it before you can\\ncontinue." +msgstr "Ви испративме е-пошта, потребно е да кликнете на врската во неа пред да \\nпродолжите." + +msgid "We've sent you an email, click the link in it, then you can change your password." +msgstr "Ви испративме е-пошта, кликнете на врската во неа, па потоа ќе може да ја промените лозинката." + +msgid "What are you doing?" +msgstr "Што правите?" + +msgid "What best describes the status of this request now?" +msgstr "Што најдобро го опишува статусот на ова барање?" + +msgid "What information has been released?" +msgstr "Кои информации се објавени?" + +msgid "What information has been requested?" +msgstr "Кои информации се побарани?" + +msgid "When you get there, please update the status to say if the response \\ncontains any useful information." +msgstr "Кога ќе стигнете таму, ве молиме ажурирајте го статусот за да известите дали одговорот\\nсодржи корисни информации." + +msgid "When you receive the paper response, please help\\n others find out what it says:" +msgstr "" +"Кога ќе добиете одговор на хартија, ве молиме помогнете\n" +" на другите да дознаат што пишува во него:" + +msgid "When you're done, come back here, reload this page and file your new request." +msgstr "Кога ќе завршите, вратете се овде, вчитајте ја одново оваа страница и поднесете го вашето ново барање." + +msgid "Which of these is happening?" +msgstr "Што од следново се случува?" + +msgid "Who can I request information from?" +msgstr "Од кого може да побарам информации?" + +msgid "Withdrawn by the requester." +msgstr "Повлечено од страна на барателот." + +msgid "Wk" +msgstr "Нд" + +msgid "Would you like to see a website like this in your country?" +msgstr "Дали би сакале да имате ваква интернет-страна во вашата земја?" + +msgid "Write a reply" +msgstr "Напишете одговор" + +msgid "Write a reply to " +msgstr "Напишете одговор до" + +msgid "Write your FOI follow up message to " +msgstr "Напишете ја пораката за реакција до " + +msgid "Write your request in simple, precise language." +msgstr "Напишете го вашето барање со едноставен, прецизен речник." + +msgid "You" +msgstr "Вие" + +msgid "You are already following new requests" +msgstr "Веќе ги следите новите барања" + +msgid "You are already following requests to {{public_body_name}}" +msgstr "Веќе ги следите барања до '{{public_body_name}}'" + +msgid "You are already following things matching this search" +msgstr "Веќе ги следите новите работи кои одговараат на ова пребарување" + +msgid "You are already following this person" +msgstr "Веќе го следите овој корисник" + +msgid "You are already following this request" +msgstr "Веќе го следите ова барање" + +msgid "You are already following updates about {{track_description}}" +msgstr "Веќе ги следите новостите за {{track_description}}" + +msgid "You are currently receiving notification of new activity on your wall by email." +msgstr "Во моментов, преку е-пошта, добивате известувања за нови активности на вашиот ѕид." + +msgid "You are following all new successful responses" +msgstr "Ги следите сите нови успешни барања" + +msgid "You are no longer following {{track_description}}." +msgstr "Повеќе не ги следите {{track_description}}." + +msgid "You are now following updates about {{track_description}}" +msgstr "Сега следите новости за {{track_description}}" + +msgid "You can complain by" +msgstr "Можете да се жалите така што" + +msgid "You can change the requests and users you are following on your profile page." +msgstr "Може да ги промените барањата и корисниците кои ги следите на вашата профил-страница." + +msgid "You can get this page in computer-readable format as part of the main JSON\\npage for the request. See the API documentation." +msgstr "Може да ја добиете оваа страница во компјутерски-читлив формат како дел од главната JSON\\nстраница за ова барање. Видете ја API документацијата." + +msgid "You can only request information about the environment from this authority." +msgstr "Од овој имател може да побарате информација само за животната средина." + +msgid "You have a new response to the {{law_used_full}} request " +msgstr "Имате нов одговор на {{law_used_full}} барање " + +msgid "You have found a bug. Please contact us to tell us about the problem" +msgstr "Најдовте софтверска грешка. Ве молиме контактирајте не за да ни го посочите проблемот" + +msgid "You have hit the rate limit on new requests. Users are ordinarily limited to {{max_requests_per_user_per_day}} requests in any rolling 24-hour period. You will be able to make another request in {{can_make_another_request}}." +msgstr "Го исполнивте ограничувањето за нови барања. Корисниците обично се ограничени на {{max_requests_per_user_per_day}} барања во период од 24 часа. Ќе може да направите ново барање за {{can_make_another_request}}." + +msgid "You have made no Freedom of Information requests using this site." +msgstr "Немате направено барање за слободен пристап до информации преку оваа страна." + +msgid "You have now changed the text about you on your profile." +msgstr "Го променивте текстот за вас на вашиот профил." + +msgid "You have now changed your email address used on {{site_name}}" +msgstr "Ја променивте адресата за е-пошта која се користи на {{site_name}}" + +msgid "You just tried to sign up to {{site_name}}, when you\\nalready have an account. Your name and password have been\\nleft as they previously were.\\n\\nPlease click on the link below." +msgstr "" +"Пробавте да се регистрирате на {{site_name}}, а веќе\n" +"имате сметка. Вашето име и лозинка останаа\n" +"исти како претходно.\n" +"\n" +"Ве молиме кликнете на врската подолу." + +msgid "You know what caused the error, and can suggest a solution, such as a working email address." +msgstr "Знаете што ја предизвика оваа грешка и можете да предложите решение, како што е важечка адреса за е-пошта." + +msgid "You may include attachments. If you would like to attach a\\n file too large for email, use the form below." +msgstr "Можете да вклучите прилози. Ако сакате да прикачите\\n датотека која е голема за е-пошта, тогаш користете ја формата подолу." + +msgid "You may be able to find\\n one on their website, or by phoning them up and asking. If you manage\\n to find one, then please send it to us." +msgstr "" +"Можеби ќе можете да најдете\n" +" на нивната веб-страна или со прашање преку телефонски повик. Ако успеете\n" +" да најдете, ве молиме да ни ја пратите." + +msgid "You may be able to find\\none on their website, or by phoning them up and asking. If you manage\\nto find one, then please send it to us." +msgstr "" +"Можеби ќе може да најдете\n" +"на нивната веб-страна или да ги прашате по телефон. Ако успеете\n" +"да најдете, ве молиме пратете и до нас." + +msgid "You need to be logged in to change the text about you on your profile." +msgstr "Мора да сте најавени за да го промените текстот за вас на вашиот профил." + +msgid "You need to be logged in to change your profile photo." +msgstr "Мора да сте најавени за да ја промените фотографијата на вашиот профил." + +msgid "You need to be logged in to clear your profile photo." +msgstr "Мора да сте најавени за да ја избришете фотографијата на вашиот профил." + +msgid "You need to be logged in to edit your profile." +msgstr "Мора да сте најавени за да го уредите вашиот профил." + +msgid "You need to be logged in to report a request for administrator attention" +msgstr "Мора да сте најавени за да пријавите барање за разгледување од администратори" + +msgid "You previously submitted that exact follow up message for this request." +msgstr "Веќе имате испратено идентична порака за надоврзување за ова барање." + +msgid "You should have received a copy of the request by email, and you can respond\\n by simply replying to that email. For your convenience, here is the address:" +msgstr "Би требало да имате добиено копија од барањето по е-пошта и може да одговорите\\n со одговарање (reply) на таа порака. Еве ја адресата:" + +msgid "You want to give your postal address to the authority in private." +msgstr "Сакате да ја дадете вашата поштенска адреса на имателот приватно, без да ја споделите со сите." + +msgid "You will be unable to make new requests, send follow ups, add annotations or\\nsend messages to other users. You may continue to view other requests, and set\\nup\\nemail alerts." +msgstr "Нема да можете да правите нови барања, да реагирате, додавате белешки или\\nда праќате пораки до други корисници. Можете да продолжите со гледање други барања и да прилагодувате\\nпредупредувања преку е-пошта." + +msgid "You will no longer be emailed updates for those alerts" +msgstr "Нема да добивате повеќе новости за овие предупредувања по е-пошта" + +msgid "You will now be emailed updates about {{track_description}}. Prefer not to receive emails?" +msgstr "Сега, преку е-пошта, ќе добивате новости за {{track_description}}. Преферирате да не добивате е-пошта?" + +msgid "You will only get an answer to your request if you follow up\\nwith the clarification." +msgstr "Ќе добиете одговор на вашето барање само ако се надоврзете\\n со класификација." + +msgid "You will still be able to view it while logged in to the site. Please reply to this email if you would like to discuss this decision further." +msgstr "И понатаму ќе бидете во можност да го видите додека сте најавени на страната. Ве молиме одговорете на оваа е-пошта доколку сакате да ја дискутирате оваа одлука во иднина." + +msgid "You're in. Continue sending your request" +msgstr "Регистрирани сте. Продолжете со испраќање на вашето барање" + +msgid "You're long overdue a response to your FOI request - " +msgstr "Одговорот на вашето барање е со значително пречекорен рок - " + +msgid "You're not following anything." +msgstr "Не следите ништо." + +msgid "You've now cleared your profile photo" +msgstr "Ја избришавте сликата од профилот" + +msgid "Your name will appear publicly\\n (why?)\\n on this website and in search engines. If you\\n are thinking of using a pseudonym, please\\n read this first." +msgstr "Вашето име ќе биде јавно прикажано\\n (зошто?)\\n на оваа веб-страна и на интернет пребарувачите. Ако\\n размислувате за употреба на псевдоним, ве молиме\\n прво прочитајте го ова." + +msgid "Your annotations" +msgstr "Вашите белешки" + +msgid "Your details, including your email address, have not been given to anyone." +msgstr "Вашите детали, вклучително и вашата адреса за е-пошта, не се дадени никому." + +msgid "Your e-mail:" +msgstr "Вашата е-пошта:" + +msgid "Your follow up has not been sent because this request has been stopped to prevent spam. Please contact us if you really want to send a follow up message." +msgstr "Вашето надоврзување не е испратено бидејќи барањето е стопирано за да се спречи spam. Ве молиме контактирајте не доколку навистина сакате да се надоврзете." + +msgid "Your follow up message has been sent on its way." +msgstr "Вашето надоврзување е испратено." + +msgid "Your internal review request has been sent on its way." +msgstr "Вашето барање за интерна ревизија е пратено." + +msgid "Your message has been sent. Thank you for getting in touch! We'll get back to you soon." +msgstr "Вашата порака е испратена. Ви благодариме што стапивте во контакт! Ќе ве известиме наскоро." + +msgid "Your message to {{recipient_user_name}} has been sent" +msgstr "Вашата порака за {{recipient_user_name}} е испратена" + +msgid "Your message to {{recipient_user_name}} has been sent!" +msgstr "Вашата порака за {{recipient_user_name}} е испратена!" + +msgid "Your message will appear in search engines" +msgstr "Вашата порака ќе се појави во интернет пребарувачите" + +msgid "Your name and annotation will appear in search engines." +msgstr "Вашето име и белешка ќе се појават во интернет пребарувачите." + +msgid "Your name, request and any responses will appear in search engines\\n (details)." +msgstr "" +"Вашето име, барање и секој одговор ќе се појават кај интернет пребарувачите\n" +" (детали)." + +msgid "Your name:" +msgstr "Вашето име:" + +msgid "Your original message is attached." +msgstr "Вашата оригинална порака е прикачена." + +msgid "Your password has been changed." +msgstr "Вашата лозинка е променета." + +msgid "Your password:" +msgstr "Вашата лозинка:" + +msgid "Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." +msgstr "Вашата фотографија ќе биде јавно прикажана на интернет,\\n што и да направите на системот {{site_name}}." + +msgid "Your request '{{request}}' at {{url}} has been reviewed by moderators." +msgstr "Вашето барање '{{request}}' на {{url}} беше прегледан од модераторите." + +msgid "Your request on {{site_name}} hidden" +msgstr "Вашето барање на {{site_name}} е сокриено" + +msgid "Your request was called {{info_request}}. Letting everyone know whether you got the information will help us keep tabs on" +msgstr "Вашето барање беше наречено {{info_request}}. Со ажурирање на податокот за тоа дали сте добиле одговор, ни помагате да подобро го следиме однесувањето" + +msgid "Your request:" +msgstr "Вашето барање:" + +msgid "Your response to an FOI request was not delivered" +msgstr "Вашиот одговор на барањето не е доставен" + +msgid "Your response will appear on the Internet, read why and answers to other questions." +msgstr "Вашиот одговор ќе се појави на интернет, прочитајте зошто и одговорете на други прашања." + +msgid "Your thoughts on what the {{site_name}} administrators should do about the request." +msgstr "Вашето мислење за што треба {{site_name}} администраторите да направат за барањето." + +msgid "Your {{count}} Freedom of Information request" +msgid_plural "Your {{count}} Freedom of Information requests" +msgstr[0] "Вашето {{count}} барање за слободен пристап до информации" +msgstr[1] "Вашите {{count}} барања за слободен пристап до информации" + +msgid "Your {{count}} annotation" +msgid_plural "Your {{count}} annotations" +msgstr[0] "Вашата {{count}} белешка" +msgstr[1] "Вашите {{count}} белешки" + +msgid "Your {{site_name}} email alert" +msgstr "Вашите предупредувања по е-пошта за {{site_name}}" + +msgid "Yours faithfully," +msgstr "Со почит," + +msgid "Yours sincerely," +msgstr "Со почит," + +msgid "Yours," +msgstr "Со почит," + +msgid "[FOI #{{request}} email]" +msgstr "[FOI #{{request}} е-пошта]" + +msgid "[{{public_body}} request email]" +msgstr "[{{public_body}} е-пошта за барање]" + +msgid "[{{site_name}} contact email]" +msgstr "[{{site_name}} контакт е-пошта]" + +msgid "\\n\\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]" +msgstr "\\n\\n[ Белешка од {{site_name}}: Текстот погоре беше лошо енкодиран, па се отстранија сите чудни карактери. ]" + +msgid "a one line summary of the information you are requesting, \\n\t\t\te.g." +msgstr "резиме во еден ред за информацијата која ја барате, \\n\t\t\tна пр." + +msgid "admin" +msgstr "администратор" + +msgid "alaveteli_foi:The software that runs {{site_name}}" +msgstr "alaveteli_foi:Софтверот кој се користи за {{site_name}}" + +msgid "all requests" +msgstr "сите барања" + +msgid "also called {{public_body_short_name}}" +msgstr "исто така познат како {{public_body_short_name}}" + +msgid "an anonymous user" +msgstr "анонимен корисник" + +msgid "and" +msgstr "и" + +msgid "and update the status accordingly. Perhaps you might like to help out by doing that?" +msgstr "и ажурирајте го статусот соодветно. Можеби вие сакате да помогнете правејќи го тоа?" + +msgid "and update the status." +msgstr "и ажурирање на статусот." + +msgid "and we'll suggest what to do next" +msgstr "и ние ќе предложиме што да се прави следно" + +msgid "any new requests" +msgstr "сите нови барања" + +msgid "any successful requests" +msgstr "сите успешни барања" + +msgid "anything" +msgstr "било што" + +msgid "are long overdue." +msgstr "значително доцнат." + +msgid "at" +msgstr "на" + +msgid "authorities" +msgstr "иматели" + +msgid "awaiting a response" +msgstr "се чека одговор" + +msgid "beginning with ‘{{first_letter}}’" +msgstr "кое почнува со буквата ‘{{first_letter}}’" + +msgid "between two dates" +msgstr "помеѓу два датуми" + +msgid "but followupable" +msgstr "но може да постои реакција" + +msgid "by" +msgstr "од" + +msgid "by {{date}}" +msgstr "до {{date}}" + +msgid "by {{public_body_name}} to {{info_request_user}} on {{date}}." +msgstr "од страна на {{public_body_name}} за корисникот {{info_request_user}} на ден {{date}}." + +msgid "by {{user_link_absolute}}" +msgstr "од {{user_link_absolute}}" + +msgid "comments" +msgstr "коментари" + +msgid "containing your postal address, and asking them to reply to this request.\\n Or you could phone them." +msgstr "" +"со Вашата поштенска адреса и побарајте од нив да одоговорат на ова барање.\n" +" Можете да ги контактирате и по телефон." + +msgid "details" +msgstr "детали" + +msgid "display_status only works for incoming and outgoing messages right now" +msgstr "display_status моментално работи само за примени и спратени пораки" + +msgid "during term time" +msgstr "за време на терминот" + +msgid "edit text about you" +msgstr "изменете го текстот за вас" + +msgid "even during holidays" +msgstr "и за време на празници" + +msgid "everything" +msgstr "се" + +msgid "external" +msgstr "надворешен" + +msgid "has reported an" +msgstr "пријави" + +msgid "have delayed." +msgstr "е одложен." + +msgid "hide quoted sections" +msgstr "сокриј ги наведените секции" + +msgid "in term time" +msgstr "во текот на школската година" + +msgid "in the category ‘{{category_name}}’" +msgstr "во категоријата ‘{{category_name}}’" + +msgid "internal error" +msgstr "внатрешна грешка" + +msgid "internal reviews" +msgstr "внатрешна ревизија" + +msgid "is waiting for your clarification." +msgstr "чека на Ваше објаснување." + +msgid "just to see how it works" +msgstr "само да видите како работи" + +msgid "left an annotation" +msgstr "остави белешка" + +msgid "made." +msgstr "направен." + +msgid "matching the tag ‘{{tag_name}}’" +msgstr "одговара на клучниот збор ‘{{tag_name}}’" + +msgid "messages from authorities" +msgstr "пораки од имателите" + +msgid "messages from users" +msgstr "пораки од корисниците" + +msgid "move..." +msgstr "помести..." + +msgid "no later than" +msgstr "најдоцна до" + +msgid "no longer exists. If you are trying to make\\n From the request page, try replying to a particular message, rather than sending\\n a general followup. If you need to make a general followup, and know\\n an email which will go to the right place, please send it to us." +msgstr "веќе не постои. Ако сакате да го направите тоа\\n од страницата со барања, обидете се да одговорите на конкретна порака, наместо да пратите\\n реакција. Ако е потребно да пратите реакција и ја знете адресата на е-пошта\\n за да стигне до правото место, Ве молиме испратете ни ја и нам." + +msgid "normally" +msgstr "вообичаено" + +msgid "not requestable due to: {{reason}}" +msgstr "барањето не е возможно бидејќи: {{reason}}" + +msgid "please sign in as " +msgstr "Ве молиме најавете се како " + +msgid "requesting an internal review" +msgstr "барање на внатрешна ревизија" + +msgid "requests" +msgstr "барања" + +msgid "requests which are {{list_of_statuses}}" +msgstr "барања кои се {{list_of_statuses}}" + +msgid "response as needing administrator attention. Take a look, and reply to this\\nemail to let them know what you are going to do about it." +msgstr "одговор зошто бара реакција од администраторот. Погледнете и одговорете на оваа \\nза да ги известите што ќе направите за ова прашање." + +msgid "send a follow up message" +msgstr "испратете реакција" + +msgid "sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "испратено на {{public_body_name}} од {{info_request_user}} на {{date}}." + +msgid "set to blank (empty string) if can't find an address; these emails are public as anyone can view with a CAPTCHA" +msgstr "има вредност blank (празен стринг) ако не може да се најде адреса; овие е-пораки се јавни бидејчи секој може да ги види ако внесе CAPTCHA" + +msgid "show quoted sections" +msgstr "прикажи ги наведените секции" + +msgid "sign in" +msgstr "најавете се" + +msgid "simple_date_format" +msgstr "simple_date_format" + +msgid "successful" +msgstr "успешно" + +msgid "successful requests" +msgstr "успешни барања" + +msgid "that you made to" +msgstr "кои ги имате поднесено до" + +msgid "the main FOI contact address for {{public_body}}" +msgstr "главниот контакт за барања за слободен пристап до информации за {{public_body}}" + +#. This phrase completes the following sentences: +#. Request an internal review from... +#. Send a public follow up message to... +#. Send a public reply to... +#. Don't want to address your message to... ? +msgid "the main FOI contact at {{public_body}}" +msgstr "главниот контакт за барања за слободен пристап до информации од јавен карактер за {{public_body}}" + +msgid "the requester" +msgstr "барателот" + +msgid "the {{site_name}} team" +msgstr "тимот на {{site_name}}" + +msgid "to read" +msgstr "за читање" + +msgid "to send a follow up message." +msgstr "да испратите реакција." + +msgid "to {{public_body}}" +msgstr "за {{public_body}}" + +msgid "unknown reason " +msgstr "непозната причина " + +msgid "unknown status " +msgstr "непознат статус " + +msgid "unresolved requests" +msgstr "нерешени барања" + +msgid "unsubscribe" +msgstr "откажете претплата" + +msgid "unsubscribe all" +msgstr "откажете претплата од сите" + +msgid "unsuccessful" +msgstr "неуспешно" + +msgid "unsuccessful requests" +msgstr "неуспешни барања" + +msgid "useful information." +msgstr "корисна информација." + +msgid "users" +msgstr "корисници" + +msgid "what's that?" +msgstr "што е тоа?" + +msgid "{{count}} FOI requests found" +msgstr "пронајдени се {{count}} барања за слободен пристап до информации од јавен карактерznačaja" + +msgid "{{count}} Freedom of Information request to {{public_body_name}}" +msgid_plural "{{count}} Freedom of Information requests to {{public_body_name}}" +msgstr[0] "{{count}} барање за слободен пристап до информации до {{public_body_name}}" +msgstr[1] "{{count}} барања за слободен пристап до информации до {{public_body_name}}" + +msgid "{{count}} person is following this authority" +msgid_plural "{{count}} people are following this authority" +msgstr[0] "{{count}} корисник го следи овој имател" +msgstr[1] "{{count}} корисници го следат овој имател" + +msgid "{{count}} request" +msgid_plural "{{count}} requests" +msgstr[0] "{{count}} барање" +msgstr[1] "{{count}} барања" + +msgid "{{count}} request made." +msgid_plural "{{count}} requests made." +msgstr[0] "{{count}} барање е поднесено." +msgstr[1] "{{count}} барања се поднесени." + +msgid "{{existing_request_user}} already\\n created the same request on {{date}}. You can either view the existing request,\\n or edit the details below to make a new but similar request." +msgstr "{{existing_request_user}} веќе\\n го има поднесено истото барање на ден {{date}}. Можете да го погледнете постоечкото барање,\\n или да ги промените деталите и да направите ново, слично барање." + +msgid "{{info_request_user_name}} only:" +msgstr "само за {{info_request_user_name}}:" + +msgid "{{law_used_full}} request - {{title}}" +msgstr "Барања кои се повикуваат на {{law_used_full}} - {{title}}" + +msgid "{{law_used}} requests at {{public_body}}" +msgstr "Барања за {{law_used}} за {{public_body}}" + +msgid "{{length_of_time}} ago" +msgstr "пред {{length_of_time}}" + +msgid "{{list_of_things}} matching text '{{search_query}}'" +msgstr "{{list_of_things}} кои одговараат на '{{search_query}}'" + +msgid "{{number_of_comments}} comments" +msgstr "{{number_of_comments}} коментари" + +msgid "{{public_body_link}} answered a request about" +msgstr "{{public_body_link}} даде одговор за" + +msgid "{{public_body_link}} was sent a request about" +msgstr "{{public_body_link}} прими барање за" + +msgid "{{public_body_name}} only:" +msgstr "само {{public_body_name}}:" + +msgid "{{public_body}} has asked you to explain part of your {{law_used}} request." +msgstr "{{public_body}} побара да го дообјасните вашето барање за {{law_used}}." + +msgid "{{public_body}} sent a response to {{user_name}}" +msgstr "{{public_body}} испрати порака до {{user_name}}" + +msgid "{{reason}}, please sign in or make a new account." +msgstr "{{reason}}, Ве молиме најавете се или креирајте нов профил." + +msgid "{{search_results}} matching '{{query}}'" +msgstr "{{search_results}} кои одговараат на '{{query}}'" + +msgid "{{site_name}} blog and tweets" +msgstr "блогови и твитови од {{site_name}} " + +msgid "{{site_name}} covers requests to {{number_of_authorities}} authorities, including:" +msgstr "{{site_name}} овозможува испраќање на барања кон {{number_of_authorities}} иматели, меѓу кои:" + +msgid "{{site_name}} sends new requests to {{request_email}} for this authority." +msgstr "{{site_name}} ги испраќа новите барања за овој имател на оваа адреса на е-пошта {{request_email}}." + +msgid "{{site_name}} users have made {{number_of_requests}} requests, including:" +msgstr "Корисниците на {{site_name}} имаат поднесено {{number_of_requests}} барања, меѓу кои:" + +msgid "{{thing_changed}} was changed from {{from_value}} to {{to_value}}" +msgstr "{{thing_changed}} се променети од {{from_value}} на {{to_value}}" + +msgid "{{title}} - a Freedom of Information request to {{public_body}}" +msgstr "{{title}} - барање за слободен пристап до информации од јавен карактер за {{public_body}}" + +msgid "{{user_name}} (Account suspended)" +msgstr "{{user_name}} (профилот е блокиран)" + +msgid "{{user_name}} - Freedom of Information requests" +msgstr "{{user_name}} - барања за слободен пристап до информации од јавен карактер" + +msgid "{{user_name}} - user profile" +msgstr "{{user_name}} - кориснички профил" + +msgid "{{user_name}} added an annotation" +msgstr "{{user_name}} додаде белешка" + +msgid "{{user_name}} has annotated your {{law_used_short}} \\nrequest. Follow this link to see what they wrote." +msgstr "" +"{{user_name}} додаде белешка за вашето барање за {{law_used_short}}.\n" +"Следете го овој линк за да го видите коментарот." + +msgid "{{user_name}} has used {{site_name}} to send you the message below." +msgstr "{{user_name}} го искористи{{site_name}} за да ви ја испрати пораката подолу." + +msgid "{{user_name}} sent a follow up message to {{public_body}}" +msgstr "{{user_name}} испрати реакција до {{public_body}}" + +msgid "{{user_name}} sent a request to {{public_body}}" +msgstr "{{user_name}} испрати барање до {{public_body}}" + +msgid "{{username}} left an annotation:" +msgstr "{{username}} остави белешка:" + +msgid "{{user}} ({{user_admin_link}}) made this {{law_used_full}} request (admin) to {{public_body_link}} (admin)" +msgstr "{{user}} ({{user_admin_link}}) го поднесе ова барање за {{law_used_full}} (admin) за имателот '{{public_body_link}}' (admin)" + +msgid "{{user}} made this {{law_used_full}} request" +msgstr "{{user}} го поднесе ова барање за {{law_used_full}}" diff --git a/locale/nb_NO/app.po b/locale/nb_NO/app.po index f3cf8417e..e30b16d7b 100644 --- a/locale/nb_NO/app.po +++ b/locale/nb_NO/app.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Norwegian Bokmål (Norway) (http://www.transifex.com/projects/p/alaveteli/language/nb_NO/)\n" "Language: nb_NO\n" diff --git a/locale/nl/app.po b/locale/nl/app.po index a310b4a47..4f165b56a 100644 --- a/locale/nl/app.po +++ b/locale/nl/app.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Dutch (http://www.transifex.com/projects/p/alaveteli/language/nl/)\n" "Language: nl\n" diff --git a/locale/pl/app.po b/locale/pl/app.po index ba6c4ec10..8c4bfc90c 100644 --- a/locale/pl/app.po +++ b/locale/pl/app.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Polish (http://www.transifex.com/projects/p/alaveteli/language/pl/)\n" "Language: pl\n" diff --git a/locale/pt_BR/app.po b/locale/pt_BR/app.po index 0dde37721..21e124083 100644 --- a/locale/pt_BR/app.po +++ b/locale/pt_BR/app.po @@ -43,7 +43,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/alaveteli/language/pt_BR/)\n" "Language: pt_BR\n" diff --git a/locale/pt_PT/app.po b/locale/pt_PT/app.po index e30271443..0abd50ab6 100644 --- a/locale/pt_PT/app.po +++ b/locale/pt_PT/app.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-09 16:17+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: luispaisbernardo \n" "Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/alaveteli/language/pt_PT/)\n" "Language: pt_PT\n" diff --git a/locale/ro_RO/app.po b/locale/ro_RO/app.po index d5dd82148..892058b0e 100644 --- a/locale/ro_RO/app.po +++ b/locale/ro_RO/app.po @@ -22,7 +22,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Romanian (Romania) (http://www.transifex.com/projects/p/alaveteli/language/ro_RO/)\n" "Language: ro_RO\n" diff --git a/locale/sl/app.po b/locale/sl/app.po index 7c586d6d7..1ab4d822f 100644 --- a/locale/sl/app.po +++ b/locale/sl/app.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Slovenian (http://www.transifex.com/projects/p/alaveteli/language/sl/)\n" "Language: sl\n" diff --git a/locale/sq/app.po b/locale/sq/app.po index 8702f096b..11cc8148e 100644 --- a/locale/sq/app.po +++ b/locale/sq/app.po @@ -18,7 +18,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Albanian (http://www.transifex.com/projects/p/alaveteli/language/sq/)\n" "Language: sq\n" diff --git a/locale/sr@latin/app.po b/locale/sr@latin/app.po index a58936e40..fca017238 100644 --- a/locale/sr@latin/app.po +++ b/locale/sr@latin/app.po @@ -16,7 +16,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:26+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Serbian (Latin) (http://www.transifex.com/projects/p/alaveteli/language/sr@latin/)\n" "Language: sr@latin\n" diff --git a/locale/sv/app.po b/locale/sv/app.po index a22d01e06..b820de7b9 100644 --- a/locale/sv/app.po +++ b/locale/sv/app.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Swedish (http://www.transifex.com/projects/p/alaveteli/language/sv/)\n" "Language: sv\n" diff --git a/locale/sw_KE/app.po b/locale/sw_KE/app.po new file mode 100644 index 000000000..ad9610194 --- /dev/null +++ b/locale/sw_KE/app.po @@ -0,0 +1,3510 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: alaveteli\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-11-08 12:10+0000\n" +"PO-Revision-Date: 2013-11-25 09:13+0000\n" +"Last-Translator: mysociety \n" +"Language-Team: Swahili (Kenya) (http://www.transifex.com/projects/p/alaveteli/language/sw_KE/)\n" +"Language: sw_KE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid " This will appear on your {{site_name}} profile, to make it\\n easier for others to get involved with what you're doing." +msgstr "" + +msgid " (no ranty politics, read our moderation policy)" +msgstr "" + +msgid " (patience, especially for large files, it may take a while!)" +msgstr "" + +msgid " (you)" +msgstr "" + +msgid " - view and make Freedom of Information requests" +msgstr "" + +msgid " - wall" +msgstr "" + +msgid " Note:\\n We will send you an email. Follow the instructions in it to change\\n your password." +msgstr "" + +msgid " Privacy note: Your email address will be given to" +msgstr "" + +msgid " Summarise the content of any information returned. " +msgstr "" + +msgid " Advise on how to best clarify the request." +msgstr "" + +msgid " Ideas on what other documents to request which the authority may hold. " +msgstr "" + +msgid " If you know the address to use, then please send it to us.\\n You may be able to find the address on their website, or by phoning them up and asking." +msgstr "" + +msgid " Include relevant links, such as to a campaign page, your blog or a\\n twitter account. They will be made clickable. \\n e.g." +msgstr "" + +msgid " Link to the information requested, if it is already available on the Internet. " +msgstr "" + +msgid " Offer better ways of wording the request to get the information. " +msgstr "" + +msgid " Say how you've used the information, with links if possible." +msgstr "" + +msgid " Suggest where else the requester might find the information. " +msgstr "" + +msgid " What are you investigating using Freedom of Information? " +msgstr "" + +msgid " You are already being emailed updates about the request." +msgstr "" + +msgid " You will also be emailed updates about the request." +msgstr "" + +msgid " made by " +msgstr "" + +msgid " or " +msgstr "" + +msgid " when you send this message." +msgstr "" + +msgid "\"Hello! We have an important message for visitors outside {{country_name}}\"" +msgstr "" + +msgid "'Crime statistics by ward level for Wales'" +msgstr "" + +msgid "'Pollution levels over time for the River Tyne'" +msgstr "" + +msgid "'{{link_to_authority}}', a public authority" +msgstr "" + +msgid "'{{link_to_request}}', a request" +msgstr "" + +msgid "'{{link_to_user}}', a person" +msgstr "" + +msgid "*unknown*" +msgstr "" + +msgid ",\\n\\n\\n\\nYours,\\n\\n{{user_name}}" +msgstr "" + +msgid "- or -" +msgstr "" + +msgid "1. Select an authority" +msgstr "" + +msgid "2. Ask for Information" +msgstr "" + +msgid "3. Now check your request" +msgstr "" + +msgid "Browse all or ask us to add one." +msgstr "" + +msgid "Add an annotation (to help the requester or others)" +msgstr "" + +msgid "Sign in to change password, subscriptions and more ({{user_name}} only)" +msgstr "" + +msgid "

    All done! Thank you very much for your help.

    There are more things you can do to help {{site_name}}.

    " +msgstr "" + +msgid "

    Thank you! Here are some ideas on what to do next:

    \\n
      \\n
    • To send your request to another authority, first copy the text of your request below, then find the other authority.
    • \\n
    • If you would like to contest the authority's claim that they do not hold the information, here is\\n how to complain.\\n
    • \\n
    • We have suggestions\\n on other means to answer your question.\\n
    • \\n
    " +msgstr "" + +msgid "

    Thank you! Hope you don't have to wait much longer.

    By law, you should have got a response promptly, and normally before the end of {{date_response_required_by}}.

    " +msgstr "" + +msgid "

    Thank you! Hopefully your wait isn't too long.

    By law, you should get a response promptly, and normally before the end of \\n{{date_response_required_by}}.

    " +msgstr "" + +msgid "

    Thank you! Hopefully your wait isn't too long.

    You should get a response within {{late_number_of_days}} days, or be told if it will take longer (details).

    " +msgstr "" + +msgid "

    Thank you! Your request is long overdue, by more than {{very_late_number_of_days}} working days. Most requests should be answered within {{late_number_of_days}} working days. You might like to complain about this, see below.

    " +msgstr "" + +msgid "

    Thanks for changing the text about you on your profile.

    \\n

    Next... You can upload a profile photograph too.

    " +msgstr "" + +msgid "

    Thanks for updating your profile photo.

    \\n

    Next... You can put some text about you and your research on your profile.

    " +msgstr "" + +msgid "

    We recommend that you edit your request and remove the email address.\\n If you leave it, the email address will be sent to the authority, but will not be displayed on the site.

    " +msgstr "" + +msgid "

    We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

    " +msgstr "" + +msgid "

    We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

    If you found {{site_name}} useful, make a donation to the charity which runs it.

    " +msgstr "" + +msgid "

    We're glad you got some of the information that you wanted. If you found {{site_name}} useful, make a donation to the charity which runs it.

    If you want to try and get the rest of the information, here's what to do now.

    " +msgstr "" + +msgid "

    We're glad you got some of the information that you wanted.

    If you want to try and get the rest of the information, here's what to do now.

    " +msgstr "" + +msgid "

    You do not need to include your email in the request in order to get a reply (details).

    " +msgstr "" + +msgid "

    You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (details).

    " +msgstr "" + +msgid "

    Your request contains a postcode. Unless it directly relates to the subject of your request, please remove any address as it will appear publicly on the Internet.

    " +msgstr "" + +msgid "

    Your {{law_used_full}} request has been sent on its way!

    \\n

    We will email you when there is a response, or after {{late_number_of_days}} working days if the authority still hasn't\\n replied by then.

    \\n

    If you write about this request (for example in a forum or a blog) please link to this page, and add an\\n annotation below telling people about your writing.

    " +msgstr "" + +msgid "

    {{site_name}} is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.

    {{read_only}}

    " +msgstr "" + +msgid "If you use web-based email or have \"junk mail\" filters, also check your\\nbulk/spam mail folders. Sometimes, our messages are marked that way.\\n

    " +msgstr "" + +msgid " Can I request information about myself?\\n\t\t\tNo! (Click here for details)" +msgstr "" + +msgid "commented_by:tony_bowden to search annotations made by Tony Bowden, typing the name as in the URL." +msgstr "" + +msgid "filetype:pdf to find all responses with PDF attachments. Or try these: {{list_of_file_extensions}}" +msgstr "" + +msgid "request: to restrict to a specific request, typing the title as in the URL." +msgstr "" + +msgid "requested_by:julian_todd to search requests made by Julian Todd, typing the name as in the URL." +msgstr "" + +msgid "requested_from:home_office to search requests from the Home Office, typing the name as in the URL." +msgstr "" + +msgid "status: to select based on the status or historical status of the request, see the table of statuses below." +msgstr "" + +msgid "tag:charity to find all public authorities or requests with a given tag. You can include multiple tags, \\n and tag values, e.g. tag:openlylocal AND tag:financial_transaction:335633. Note that by default any of the tags\\n can be present, you have to put AND explicitly if you only want results them all present." +msgstr "" + +msgid "variety: to select type of thing to search for, see the table of varieties below." +msgstr "" + +msgid "Advice on how to get a response that will satisfy the requester. " +msgstr "" + +msgid "All the information has been sent" +msgstr "" + +msgid "Anything else, such as clarifying, prompting, thanking" +msgstr "" + +msgid "Caveat emptor! To use this data in an honourable way, you will need \\na good internal knowledge of user behaviour on {{site_name}}. How, \\nwhy and by whom requests are categorised is not straightforward, and there will\\nbe user error and ambiguity. You will also need to understand FOI law, and the\\nway authorities use it. Plus you'll need to be an elite statistician. Please\\ncontact us with questions." +msgstr "" + +msgid "Clarification has been requested" +msgstr "" + +msgid "No response has been received\\n (maybe there's just an acknowledgement)" +msgstr "" + +msgid "Note: Because we're testing, requests are being sent to {{email}} rather than to the actual authority." +msgstr "" + +msgid "Note: You're sending a message to yourself, presumably\\n to try out how it works." +msgstr "" + +msgid "Note:\\n We will send an email to your new email address. Follow the\\n instructions in it to confirm changing your email." +msgstr "" + +msgid "Privacy note: If you want to request private information about\\n yourself then click here." +msgstr "" + +msgid "Privacy note: Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." +msgstr "" + +msgid "Privacy warning: Your message, and any response\\n to it, will be displayed publicly on this website." +msgstr "" + +msgid "Some of the information has been sent " +msgstr "" + +msgid "Thank the public authority or " +msgstr "" + +msgid "did not have the information requested." +msgstr "" + +msgid "A follow up to {{request_title}} was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "A response to {{request_title}} was sent by {{public_body_name}} to {{info_request_user}} on {{date}}. The request status is: {{request_status}}" +msgstr "" + +msgid "A summary of the response if you have received it by post. " +msgstr "" + +msgid "A Freedom of Information request" +msgstr "" + +msgid "A full history of my FOI request and all correspondence is available on the Internet at this address: {{url}}" +msgstr "" + +msgid "A new request, {{request_title}}, was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "A public authority" +msgstr "" + +msgid "A response will be sent by post" +msgstr "" + +msgid "A strange reponse, required attention by the {{site_name}} team" +msgstr "" + +msgid "A vexatious request" +msgstr "" + +msgid "A {{site_name}} user" +msgstr "" + +msgid "About you:" +msgstr "" + +msgid "Act on what you've learnt" +msgstr "" + +msgid "Acts as xapian/acts as xapian job" +msgstr "" + +msgid "ActsAsXapian::ActsAsXapianJob|Action" +msgstr "" + +msgid "ActsAsXapian::ActsAsXapianJob|Model" +msgstr "" + +msgid "Add an annotation" +msgstr "" + +msgid "Add an annotation to your request with choice quotes, or\\n a summary of the response." +msgstr "" + +msgid "Added on {{date}}" +msgstr "" + +msgid "Admin level is not included in list" +msgstr "" + +msgid "Administration URL:" +msgstr "" + +msgid "Advanced search" +msgstr "" + +msgid "Advanced search tips" +msgstr "" + +msgid "Advise on whether the refusal is legal, and how to complain about it if not." +msgstr "" + +msgid "Air, water, soil, land, flora and fauna (including how these effect\\n human beings)" +msgstr "" + +msgid "All of the information requested has been received" +msgstr "" + +msgid "All the options below can use status or latest_status before the colon. For example, status:not_held will match requests which have ever been marked as not held; latest_status:not_held will match only requests that are currently marked as not held." +msgstr "" + +msgid "All the options below can use variety or latest_variety before the colon. For example, variety:sent will match requests which have ever been sent; latest_variety:sent will match only requests that are currently marked as sent." +msgstr "" + +msgid "Also called {{other_name}}." +msgstr "" + +msgid "Also send me alerts by email" +msgstr "" + +msgid "Alter your subscription" +msgstr "" + +msgid "Although all responses are automatically published, we depend on\\nyou, the original requester, to evaluate them." +msgstr "" + +msgid "An annotation to {{request_title}} was made by {{event_comment_user}} on {{date}}" +msgstr "" + +msgid "An error message has been received" +msgstr "" + +msgid "An Environmental Information Regulations request" +msgstr "" + +msgid "An anonymous user" +msgstr "" + +msgid "Annotation added to request" +msgstr "" + +msgid "Annotations" +msgstr "" + +msgid "Annotations are so anyone, including you, can help the requester with their request. For example:" +msgstr "" + +msgid "Annotations will be posted publicly here, and are\\n not sent to {{public_body_name}}." +msgstr "" + +msgid "Anonymous user" +msgstr "" + +msgid "Anyone:" +msgstr "" + +msgid "Applies to" +msgstr "" + +msgid "Are we missing a public authority?" +msgstr "" + +msgid "Are you the owner of any commercial copyright on this page?" +msgstr "" + +msgid "Ask for specific documents or information, this site is not suitable for general enquiries." +msgstr "" + +msgid "At the bottom of this page, write a reply to them trying to persuade them to scan it in\\n (more details)." +msgstr "" + +msgid "Attachment (optional):" +msgstr "" + +msgid "Attachment:" +msgstr "" + +msgid "Awaiting classification." +msgstr "" + +msgid "Awaiting internal review." +msgstr "" + +msgid "Awaiting response." +msgstr "" + +msgid "Beginning with" +msgstr "" + +msgid "Browse other requests for examples of how to word your request." +msgstr "" + +msgid "Browse other requests to '{{public_body_name}}' for examples of how to word your request." +msgstr "" + +msgid "Browse all authorities..." +msgstr "" + +msgid "By law, under all circumstances, {{public_body_link}} should have responded by now" +msgstr "" + +msgid "By law, {{public_body_link}} should normally have responded promptly and" +msgstr "" + +msgid "Calculated home page" +msgstr "" + +msgid "Can't find the one you want?" +msgstr "" + +msgid "Cancel a {{site_name}} alert" +msgstr "" + +msgid "Cancel some {{site_name}} alerts" +msgstr "" + +msgid "Cancel, return to your profile page" +msgstr "" + +msgid "Censor rule" +msgstr "" + +msgid "CensorRule|Last edit comment" +msgstr "" + +msgid "CensorRule|Last edit editor" +msgstr "" + +msgid "CensorRule|Regexp" +msgstr "" + +msgid "CensorRule|Replacement" +msgstr "" + +msgid "CensorRule|Text" +msgstr "" + +msgid "Change email on {{site_name}}" +msgstr "" + +msgid "Change password on {{site_name}}" +msgstr "" + +msgid "Change profile photo" +msgstr "" + +msgid "Change the text about you on your profile at {{site_name}}" +msgstr "" + +msgid "Change your email" +msgstr "" + +msgid "Change your email address used on {{site_name}}" +msgstr "" + +msgid "Change your password" +msgstr "" + +msgid "Change your password on {{site_name}}" +msgstr "" + +msgid "Change your password {{site_name}}" +msgstr "" + +msgid "Charity registration" +msgstr "" + +msgid "Check for mistakes if you typed or copied the address." +msgstr "" + +msgid "Check you haven't included any personal information." +msgstr "" + +msgid "Choose your profile photo" +msgstr "" + +msgid "Clarification" +msgstr "" + +msgid "Clarify your FOI request - " +msgstr "" + +msgid "Classify an FOI response from " +msgstr "" + +msgid "Clear photo" +msgstr "" + +msgid "Click on the link below to send a message to {{public_body_name}} telling them to reply to your request. You might like to ask for an internal\\nreview, asking them to find out why response to the request has been so slow." +msgstr "" + +msgid "Click on the link below to send a message to {{public_body}} reminding them to reply to your request." +msgstr "" + +msgid "Close" +msgstr "" + +msgid "Comment" +msgstr "" + +msgid "Comment|Body" +msgstr "" + +msgid "Comment|Comment type" +msgstr "" + +msgid "Comment|Locale" +msgstr "" + +msgid "Comment|Visible" +msgstr "" + +msgid "Confirm you want to follow all successful FOI requests" +msgstr "" + +msgid "Confirm you want to follow new requests" +msgstr "" + +msgid "Confirm you want to follow new requests or responses matching your search" +msgstr "" + +msgid "Confirm you want to follow requests by '{{user_name}}'" +msgstr "" + +msgid "Confirm you want to follow requests to '{{public_body_name}}'" +msgstr "" + +msgid "Confirm you want to follow the request '{{request_title}}'" +msgstr "" + +msgid "Confirm your FOI request to " +msgstr "" + +msgid "Confirm your account on {{site_name}}" +msgstr "" + +msgid "Confirm your annotation to {{info_request_title}}" +msgstr "" + +msgid "Confirm your email address" +msgstr "" + +msgid "Confirm your new email address on {{site_name}}" +msgstr "" + +msgid "Considered by administrators as not an FOI request and hidden from site." +msgstr "" + +msgid "Considered by administrators as vexatious and hidden from site." +msgstr "" + +msgid "Contact {{recipient}}" +msgstr "" + +msgid "Contact {{site_name}}" +msgstr "" + +msgid "Could not identify the request from the email address" +msgstr "" + +msgid "Couldn't understand the image file that you uploaded. PNG, JPEG, GIF and many other common image file formats are supported." +msgstr "" + +msgid "Crop your profile photo" +msgstr "" + +msgid "Cultural sites and built structures (as they may be affected by the\\n environmental factors listed above)" +msgstr "" + +msgid "Currently waiting for a response from {{public_body_link}}, they must respond promptly and" +msgstr "" + +msgid "Date:" +msgstr "" + +msgid "Dear {{name}}," +msgstr "" + +msgid "Dear {{public_body_name}}," +msgstr "" + +msgid "Default locale" +msgstr "" + +msgid "Defunct." +msgstr "" + +msgid "Delayed response to your FOI request - " +msgstr "" + +msgid "Delayed." +msgstr "" + +msgid "Delivery error" +msgstr "" + +msgid "Destroy {{name}}" +msgstr "" + +msgid "Details of request '" +msgstr "" + +msgid "Did you mean: {{correction}}" +msgstr "" + +msgid "Disclaimer: This message and any reply that you make will be published on the internet. Our privacy and copyright policies:" +msgstr "" + +msgid "Disclosure log" +msgstr "" + +msgid "Disclosure log URL" +msgstr "" + +msgid "Don't want to address your message to {{person_or_body}}? You can also write to:" +msgstr "" + +msgid "Done" +msgstr "" + +msgid "Done >>" +msgstr "" + +msgid "Download a zip file of all correspondence" +msgstr "" + +msgid "Download original attachment" +msgstr "" + +msgid "EIR" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit and add more details to the message above,\\n explaining why you are dissatisfied with their response." +msgstr "" + +msgid "Edit text about you" +msgstr "" + +msgid "Edit this request" +msgstr "" + +msgid "Either the email or password was not recognised, please try again." +msgstr "" + +msgid "Either the email or password was not recognised, please try again. Or create a new account using the form on the right." +msgstr "" + +msgid "Email doesn't look like a valid address" +msgstr "" + +msgid "Email me future updates to this request" +msgstr "" + +msgid "Enter words that you want to find separated by spaces, e.g. climbing lane" +msgstr "" + +msgid "Enter your response below. You may attach one file (use email, or\\n contact us if you need more)." +msgstr "" + +msgid "Environmental Information Regulations" +msgstr "" + +msgid "Environmental Information Regulations requests made" +msgstr "" + +msgid "Environmental Information Regulations requests made using this site" +msgstr "" + +msgid "Event history" +msgstr "" + +msgid "Event history details" +msgstr "" + +msgid "Event {{id}}" +msgstr "" + +msgid "Everything that you enter on this page, including your name,\\n will be displayed publicly on\\n this website forever (why?)." +msgstr "" + +msgid "Everything that you enter on this page\\n will be displayed publicly on\\n this website forever (why?)." +msgstr "" + +msgid "FOI" +msgstr "" + +msgid "FOI email address for {{public_body}}" +msgstr "" + +msgid "FOI request – {{title}}" +msgstr "" + +msgid "FOI requests" +msgstr "" + +msgid "FOI requests by '{{user_name}}'" +msgstr "" + +msgid "FOI requests {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "" + +msgid "FOI response requires admin ({{reason}}) - {{title}}" +msgstr "" + +msgid "Failed to convert image to a PNG" +msgstr "" + +msgid "Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}" +msgstr "" + +msgid "Filter" +msgstr "" + +msgid "First, did your other requests succeed?" +msgstr "" + +msgid "First, type in the name of the UK public authority you'd\\n like information from. By law, they have to respond\\n (why?)." +msgstr "" + +msgid "Foi attachment" +msgstr "" + +msgid "FoiAttachment|Charset" +msgstr "" + +msgid "FoiAttachment|Content type" +msgstr "" + +msgid "FoiAttachment|Display size" +msgstr "" + +msgid "FoiAttachment|Filename" +msgstr "" + +msgid "FoiAttachment|Hexdigest" +msgstr "" + +msgid "FoiAttachment|Url part number" +msgstr "" + +msgid "FoiAttachment|Within rfc822 subject" +msgstr "" + +msgid "Follow" +msgstr "" + +msgid "Follow all new requests" +msgstr "" + +msgid "Follow new successful responses" +msgstr "" + +msgid "Follow requests to {{public_body_name}}" +msgstr "" + +msgid "Follow these requests" +msgstr "" + +msgid "Follow things matching this search" +msgstr "" + +msgid "Follow this authority" +msgstr "" + +msgid "Follow this link to see the request:" +msgstr "" + +msgid "Follow this person" +msgstr "" + +msgid "Follow this request" +msgstr "" + +#. "Follow up" in this context means a further +#. message sent by the requester to the authority after +#. the initial request +msgid "Follow up" +msgstr "" + +#. "Follow up message" in this context means a +#. further message sent by the requester to the authority after +#. the initial request +msgid "Follow up message sent by requester" +msgstr "" + +msgid "Follow up messages to existing requests are sent to " +msgstr "" + +#. "Follow ups" in this context means further +#. messages sent by the requester to the authority after +#. the initial request +msgid "Follow ups and new responses to this request have been stopped to prevent spam. Please contact us if you are {{user_link}} and need to send a follow up." +msgstr "" + +msgid "Follow us on twitter" +msgstr "" + +msgid "Followups cannot be sent for this request, as it was made externally, and published here by {{public_body_name}} on the requester's behalf." +msgstr "" + +msgid "For an unknown reason, it is not possible to make a request to this authority." +msgstr "" + +msgid "Forgotten your password?" +msgstr "" + +msgid "Found {{count}} public authority {{description}}" +msgid_plural "Found {{count}} public authorities {{description}}" +msgstr[0] "" +msgstr[1] "" + +msgid "Freedom of Information" +msgstr "" + +msgid "Freedom of Information Act" +msgstr "" + +msgid "Freedom of Information law does not apply to this authority, so you cannot make\\n a request to it." +msgstr "" + +msgid "Freedom of Information law no longer applies to" +msgstr "" + +msgid "Freedom of Information law no longer applies to this authority.Follow up messages to existing requests are sent to " +msgstr "" + +msgid "Freedom of Information requests made" +msgstr "" + +msgid "Freedom of Information requests made by this person" +msgstr "" + +msgid "Freedom of Information requests made by you" +msgstr "" + +msgid "Freedom of Information requests made using this site" +msgstr "" + +msgid "Freedom of information requests to" +msgstr "" + +msgid "From" +msgstr "" + +msgid "From the request page, try replying to a particular message, rather than sending\\n a general followup. If you need to make a general followup, and know\\n an email which will go to the right place, please send it to us." +msgstr "" + +msgid "From:" +msgstr "" + +msgid "GIVE DETAILS ABOUT YOUR COMPLAINT HERE" +msgstr "" + +msgid "Handled by post." +msgstr "" + +msgid "Has tag string/has tag string tag" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Model" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Name" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Value" +msgstr "" + +msgid "Hello! You can make Freedom of Information requests within {{country_name}} at {{link_to_website}}" +msgstr "" + +msgid "Hello, {{username}}!" +msgstr "" + +msgid "Help" +msgstr "" + +msgid "Here described means when a user selected a status for the request, and\\nthe most recent event had its status updated to that value. calculated is then inferred by\\n{{site_name}} for intermediate events, which weren't given an explicit\\ndescription by a user. See the search tips for description of the states." +msgstr "" + +msgid "Here is the message you wrote, in case you would like to copy the text and save it for later." +msgstr "" + +msgid "Hi! We need your help. The person who made the following request\\n hasn't told us whether or not it was successful. Would you mind taking\\n a moment to read it and help us keep the place tidy for everyone?\\n Thanks." +msgstr "" + +msgid "Hide request" +msgstr "" + +msgid "Holiday" +msgstr "" + +msgid "Holiday|Day" +msgstr "" + +msgid "Holiday|Description" +msgstr "" + +msgid "Home" +msgstr "" + +msgid "Home page" +msgstr "" + +msgid "Home page of authority" +msgstr "" + +msgid "However, you have the right to request environmental\\n information under a different law" +msgstr "" + +msgid "Human health and safety" +msgstr "" + +msgid "I am asking for new information" +msgstr "" + +msgid "I am requesting an internal review" +msgstr "" + +msgid "I am writing to request an internal review of {{public_body_name}}'s handling of my FOI request '{{info_request_title}}'." +msgstr "" + +msgid "I don't like these ones — give me some more!" +msgstr "" + +msgid "I don't want to do any more tidying now!" +msgstr "" + +msgid "I like this request" +msgstr "" + +msgid "I would like to withdraw this request" +msgstr "" + +msgid "I'm still waiting for my information\\n (maybe you got an acknowledgement)" +msgstr "" + +msgid "I'm still waiting for the internal review" +msgstr "" + +msgid "I'm waiting for an internal review response" +msgstr "" + +msgid "I've been asked to clarify my request" +msgstr "" + +msgid "I've received all the information" +msgstr "" + +msgid "I've received some of the information" +msgstr "" + +msgid "I've received an error message" +msgstr "" + +msgid "I've received an error message" +msgstr "" + +msgid "Id" +msgstr "" + +msgid "If the address is wrong, or you know a better address, please contact us." +msgstr "" + +msgid "If the error was a delivery failure, and you can find an up to date FOI email address for the authority, please tell us using the form below." +msgstr "" + +msgid "If this is incorrect, or you would like to send a late response to the request\\nor an email on another subject to {{user}}, then please\\nemail {{contact_email}} for help." +msgstr "" + +msgid "If you are dissatisfied by the response you got from\\n the public authority, you have the right to\\n complain (details)." +msgstr "" + +msgid "If you are still having trouble, please contact us." +msgstr "" + +msgid "If you are the requester, then you may sign in to view the message." +msgstr "" + +msgid "If you are the requester, then you may sign in to view the request." +msgstr "" + +msgid "If you are thinking of using a pseudonym,\\n please read this first." +msgstr "" + +msgid "If you are {{user_link}}, please" +msgstr "" + +msgid "If you believe this request is not suitable, you can report it for attention by the site administrators" +msgstr "" + +msgid "If you can't click on it in the email, you'll have to select and copy\\nit from the email. Then paste it into your browser, into the place\\nyou would type the address of any other webpage." +msgstr "" + +msgid "If you can, scan in or photograph the response, and send us\\n a copy to upload." +msgstr "" + +msgid "If you find this service useful as an FOI officer, please ask your web manager to link to us from your organisation's FOI page." +msgstr "" + +msgid "If you got the email more than six months ago, then this login link won't work any\\nmore. Please try doing what you were doing from the beginning." +msgstr "" + +msgid "If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn." +msgstr "" + +msgid "If you reply to this message it will go directly to {{user_name}}, who will\\nlearn your email address. Only reply if that is okay." +msgstr "" + +msgid "If you use web-based email or have \"junk mail\" filters, also check your\\nbulk/spam mail folders. Sometimes, our messages are marked that way." +msgstr "" + +msgid "If you would like us to lift this ban, then you may politely\\ncontact us giving reasons.\\n" +msgstr "" + +msgid "If you're new to {{site_name}}" +msgstr "" + +msgid "If you've used {{site_name}} before" +msgstr "" + +msgid "If your browser is set to accept cookies and you are seeing this message,\\nthen there is probably a fault with our server." +msgstr "" + +msgid "Incoming email address" +msgstr "" + +msgid "Incoming message" +msgstr "" + +msgid "IncomingMessage|Cached attachment text clipped" +msgstr "" + +msgid "IncomingMessage|Cached main body text folded" +msgstr "" + +msgid "IncomingMessage|Cached main body text unfolded" +msgstr "" + +msgid "IncomingMessage|Last parsed" +msgstr "" + +msgid "IncomingMessage|Mail from" +msgstr "" + +msgid "IncomingMessage|Mail from domain" +msgstr "" + +msgid "IncomingMessage|Prominence" +msgstr "" + +msgid "IncomingMessage|Prominence reason" +msgstr "" + +msgid "IncomingMessage|Sent at" +msgstr "" + +msgid "IncomingMessage|Subject" +msgstr "" + +msgid "IncomingMessage|Valid to reply to" +msgstr "" + +msgid "Individual requests" +msgstr "" + +msgid "Info request" +msgstr "" + +msgid "Info request event" +msgstr "" + +msgid "InfoRequestEvent|Calculated state" +msgstr "" + +msgid "InfoRequestEvent|Described state" +msgstr "" + +msgid "InfoRequestEvent|Event type" +msgstr "" + +msgid "InfoRequestEvent|Last described at" +msgstr "" + +msgid "InfoRequestEvent|Params yaml" +msgstr "" + +msgid "InfoRequest|Allow new responses from" +msgstr "" + +msgid "InfoRequest|Attention requested" +msgstr "" + +msgid "InfoRequest|Awaiting description" +msgstr "" + +msgid "InfoRequest|Comments allowed" +msgstr "" + +msgid "InfoRequest|Described state" +msgstr "" + +msgid "InfoRequest|External url" +msgstr "" + +msgid "InfoRequest|External user name" +msgstr "" + +msgid "InfoRequest|Handle rejected responses" +msgstr "" + +msgid "InfoRequest|Idhash" +msgstr "" + +msgid "InfoRequest|Law used" +msgstr "" + +msgid "InfoRequest|Prominence" +msgstr "" + +msgid "InfoRequest|Title" +msgstr "" + +msgid "InfoRequest|Url title" +msgstr "" + +msgid "Information not held." +msgstr "" + +msgid "Information on emissions and discharges (e.g. noise, energy,\\n radiation, waste materials)" +msgstr "" + +msgid "Internal review request" +msgstr "" + +msgid "Is {{email_address}} the wrong address for {{type_of_request}} requests to {{public_body_name}}? If so, please contact us using this form:" +msgstr "" + +msgid "It may be that your browser is not set to accept a thing called \"cookies\",\\nor cannot do so. If you can, please enable cookies, or try using a different\\nbrowser. Then press refresh to have another go." +msgstr "" + +msgid "Items matching the following conditions are currently displayed on your wall." +msgstr "" + +msgid "Items sent in last month" +msgstr "" + +msgid "Joined in" +msgstr "" + +msgid "Joined {{site_name}} in" +msgstr "" + +msgid "Just one more thing" +msgstr "" + +msgid "Keep it focused, you'll be more likely to get what you want (why?)." +msgstr "" + +msgid "Keywords" +msgstr "" + +msgid "Last authority viewed: " +msgstr "" + +msgid "Last request viewed: " +msgstr "" + +msgid "Let us know what you were doing when this message\\nappeared and your browser and operating system type and version." +msgstr "" + +msgid "Link to this" +msgstr "" + +msgid "List all" +msgstr "" + +msgid "List of all authorities (CSV)" +msgstr "" + +msgid "Listing FOI requests" +msgstr "" + +msgid "Listing public authorities" +msgstr "" + +msgid "Listing public authorities matching '{{query}}'" +msgstr "" + +msgid "Listing tracks" +msgstr "" + +msgid "Listing users" +msgstr "" + +msgid "Log in to download a zip file of {{info_request_title}}" +msgstr "" + +msgid "Log into the admin interface" +msgstr "" + +msgid "Long overdue." +msgstr "" + +msgid "Made between" +msgstr "" + +msgid "Mail server log" +msgstr "" + +msgid "Mail server log done" +msgstr "" + +msgid "MailServerLogDone|Filename" +msgstr "" + +msgid "MailServerLogDone|Last stat" +msgstr "" + +msgid "MailServerLog|Line" +msgstr "" + +msgid "MailServerLog|Order" +msgstr "" + +msgid "Make a new
    \\n Freedom of
    \\n Information
    \\n request
    " +msgstr "" + +msgid "Make a request" +msgstr "" + +msgid "Make a request to this authority" +msgstr "" + +msgid "Make an {{law_used_short}} request to '{{public_body_name}}'" +msgstr "" + +msgid "Make and browse Freedom of Information (FOI) requests" +msgstr "" + +msgid "Make your own request" +msgstr "" + +msgid "Many requests" +msgstr "" + +msgid "Message" +msgstr "" + +msgid "Message has been removed" +msgstr "" + +msgid "Message sent using {{site_name}} contact form, " +msgstr "" + +msgid "Missing contact details for '" +msgstr "" + +msgid "More about this authority" +msgstr "" + +msgid "More requests..." +msgstr "" + +msgid "More similar requests" +msgstr "" + +msgid "More successful requests..." +msgstr "" + +msgid "My profile" +msgstr "" + +msgid "My request has been refused" +msgstr "" + +msgid "My requests" +msgstr "" + +msgid "My wall" +msgstr "" + +msgid "Name can't be blank" +msgstr "" + +msgid "Name is already taken" +msgstr "" + +msgid "New Freedom of Information requests" +msgstr "" + +msgid "New censor rule" +msgstr "" + +msgid "New e-mail:" +msgstr "" + +msgid "New email doesn't look like a valid address" +msgstr "" + +msgid "New password:" +msgstr "" + +msgid "New password: (again)" +msgstr "" + +msgid "New response to '{{title}}'" +msgstr "" + +msgid "New response to your FOI request - " +msgstr "" + +msgid "New response to your request" +msgstr "" + +msgid "New response to {{law_used_short}} request" +msgstr "" + +msgid "New updates for the request '{{request_title}}'" +msgstr "" + +msgid "Newest results first" +msgstr "" + +msgid "Next" +msgstr "" + +msgid "Next, crop your photo >>" +msgstr "" + +msgid "No requests of this sort yet." +msgstr "" + +msgid "No results found." +msgstr "" + +msgid "No similar requests found." +msgstr "" + +msgid "No tracked things found." +msgstr "" + +msgid "Nobody has made any Freedom of Information requests to {{public_body_name}} using this site yet." +msgstr "" + +msgid "None found." +msgstr "" + +msgid "None made." +msgstr "" + +msgid "Not a valid FOI request" +msgstr "" + +msgid "Note that the requester will not be notified about your annotation, because the request was published by {{public_body_name}} on their behalf." +msgstr "" + +msgid "Now check your email!" +msgstr "" + +msgid "Now preview your annotation" +msgstr "" + +msgid "Now preview your follow up" +msgstr "" + +msgid "Now preview your message asking for an internal review" +msgstr "" + +msgid "Number of requests" +msgstr "" + +msgid "OR remove the existing photo" +msgstr "" + +msgid "Offensive? Unsuitable?" +msgstr "" + +msgid "Oh no! Sorry to hear that your request was refused. Here is what to do now." +msgstr "" + +msgid "Old e-mail:" +msgstr "" + +msgid "Old email address isn't the same as the address of the account you are logged in with" +msgstr "" + +msgid "Old email doesn't look like a valid address" +msgstr "" + +msgid "On this page" +msgstr "" + +msgid "One FOI request found" +msgstr "" + +msgid "One person found" +msgstr "" + +msgid "One public authority found" +msgstr "" + +msgid "Only put in abbreviations which are really used, otherwise leave blank. Short or long name is used in the URL – don't worry about breaking URLs through renaming, as the history is used to redirect" +msgstr "" + +msgid "Only requests made using {{site_name}} are shown." +msgstr "" + +msgid "Only the authority can reply to this request, and I don't recognise the address this reply was sent from" +msgstr "" + +msgid "Only the authority can reply to this request, but there is no \"From\" address to check against" +msgstr "" + +msgid "Or search in their website for this information." +msgstr "" + +msgid "Original request sent" +msgstr "" + +msgid "Other:" +msgstr "" + +msgid "Outgoing message" +msgstr "" + +msgid "OutgoingMessage|Body" +msgstr "" + +msgid "OutgoingMessage|Last sent at" +msgstr "" + +msgid "OutgoingMessage|Message type" +msgstr "" + +msgid "OutgoingMessage|Prominence" +msgstr "" + +msgid "OutgoingMessage|Prominence reason" +msgstr "" + +msgid "OutgoingMessage|Status" +msgstr "" + +msgid "OutgoingMessage|What doing" +msgstr "" + +msgid "Partially successful." +msgstr "" + +msgid "Password is not correct" +msgstr "" + +msgid "Password:" +msgstr "" + +msgid "Password: (again)" +msgstr "" + +msgid "Paste this link into emails, tweets, and anywhere else:" +msgstr "" + +msgid "People" +msgstr "" + +msgid "People {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "" + +msgid "Percentage of requests that are overdue" +msgstr "" + +msgid "Percentage of total requests" +msgstr "" + +msgid "Photo of you:" +msgstr "" + +msgid "Plans and administrative measures that affect these matters" +msgstr "" + +msgid "Play the request categorisation game" +msgstr "" + +msgid "Play the request categorisation game!" +msgstr "" + +msgid "Please" +msgstr "" + +msgid "Please contact us if you have any questions." +msgstr "" + +msgid "Please get in touch with us so we can fix it." +msgstr "" + +msgid "Please answer the question above so we know whether the " +msgstr "" + +msgid "Please go to the following requests, and let us\\n know if there was information in the recent responses to them." +msgstr "" + +msgid "Please only write messages directly relating to your request {{request_link}}. If you would like to ask for information that was not in your original request, then file a new request." +msgstr "" + +msgid "Please ask for environmental information only" +msgstr "" + +msgid "Please check the URL (i.e. the long code of letters and numbers) is copied\\ncorrectly from your email." +msgstr "" + +msgid "Please choose a file containing your photo." +msgstr "" + +msgid "Please choose a reason" +msgstr "" + +msgid "Please choose what sort of reply you are making." +msgstr "" + +msgid "Please choose whether or not you got some of the information that you wanted." +msgstr "" + +msgid "Please click on the link below to cancel or alter these emails." +msgstr "" + +msgid "Please click on the link below to confirm that you want to \\nchange the email address that you use for {{site_name}}\\nfrom {{old_email}} to {{new_email}}" +msgstr "" + +msgid "Please click on the link below to confirm your email address." +msgstr "" + +msgid "Please describe more what the request is about in the subject. There is no need to say it is an FOI request, we add that on anyway." +msgstr "" + +msgid "Please don't upload offensive pictures. We will take down images\\n that we consider inappropriate." +msgstr "" + +msgid "Please enable \"cookies\" to carry on" +msgstr "" + +msgid "Please enter a password" +msgstr "" + +msgid "Please enter a subject" +msgstr "" + +msgid "Please enter a summary of your request" +msgstr "" + +msgid "Please enter a valid email address" +msgstr "" + +msgid "Please enter the message you want to send" +msgstr "" + +msgid "Please enter the same password twice" +msgstr "" + +msgid "Please enter your annotation" +msgstr "" + +msgid "Please enter your email address" +msgstr "" + +msgid "Please enter your follow up message" +msgstr "" + +msgid "Please enter your letter requesting information" +msgstr "" + +msgid "Please enter your name" +msgstr "" + +msgid "Please enter your name, not your email address, in the name field." +msgstr "" + +msgid "Please enter your new email address" +msgstr "" + +msgid "Please enter your old email address" +msgstr "" + +msgid "Please enter your password" +msgstr "" + +msgid "Please give details explaining why you want a review" +msgstr "" + +msgid "Please keep it shorter than 500 characters" +msgstr "" + +msgid "Please keep the summary short, like in the subject of an email. You can use a phrase, rather than a full sentence." +msgstr "" + +msgid "Please only request information that comes under those categories, do not waste your\\n time or the time of the public authority by requesting unrelated information." +msgstr "" + +msgid "Please pass this on to the person who conducts Freedom of Information reviews." +msgstr "" + +msgid "Please select each of these requests in turn, and let everyone know\\nif they are successful yet or not." +msgstr "" + +msgid "Please sign at the bottom with your name, or alter the \"{{signoff}}\" signature" +msgstr "" + +msgid "Please sign in as " +msgstr "" + +msgid "Please sign in or make a new account." +msgstr "" + +msgid "Please type a message and/or choose a file containing your response." +msgstr "" + +msgid "Please use this email address for all replies to this request:" +msgstr "" + +msgid "Please write a summary with some text in it" +msgstr "" + +msgid "Please write the summary using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Please write your annotation using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Please write your follow up message containing the necessary clarifications below." +msgstr "" + +msgid "Please write your message using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Point to related information, campaigns or forums which may be useful." +msgstr "" + +msgid "Possibly related requests:" +msgstr "" + +msgid "Post annotation" +msgstr "" + +msgid "Post redirect" +msgstr "" + +msgid "PostRedirect|Circumstance" +msgstr "" + +msgid "PostRedirect|Email token" +msgstr "" + +msgid "PostRedirect|Post params yaml" +msgstr "" + +msgid "PostRedirect|Reason params yaml" +msgstr "" + +msgid "PostRedirect|Token" +msgstr "" + +msgid "PostRedirect|Uri" +msgstr "" + +msgid "Posted on {{date}} by {{author}}" +msgstr "" + +msgid "Powered by Alaveteli" +msgstr "" + +msgid "Prev" +msgstr "" + +msgid "Preview follow up to '" +msgstr "" + +msgid "Preview new annotation on '{{info_request_title}}'" +msgstr "" + +msgid "Preview your annotation" +msgstr "" + +msgid "Preview your message" +msgstr "" + +msgid "Preview your public request" +msgstr "" + +msgid "Profile photo" +msgstr "" + +msgid "ProfilePhoto|Data" +msgstr "" + +msgid "ProfilePhoto|Draft" +msgstr "" + +msgid "Public Bodies" +msgstr "" + +msgid "Public Body Statistics" +msgstr "" + +msgid "Public authorities" +msgstr "" + +msgid "Public authorities - {{description}}" +msgstr "" + +msgid "Public authorities {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "" + +msgid "Public authority – {{name}}" +msgstr "" + +msgid "Public bodies that most frequently replied with \"Not Held\"" +msgstr "" + +msgid "Public bodies with most overdue requests" +msgstr "" + +msgid "Public bodies with the fewest successful requests" +msgstr "" + +msgid "Public bodies with the most requests" +msgstr "" + +msgid "Public bodies with the most successful requests" +msgstr "" + +msgid "Public body" +msgstr "" + +msgid "Public notes" +msgstr "" + +msgid "Public page" +msgstr "" + +msgid "Public page not available" +msgstr "" + +msgid "PublicBody|Api key" +msgstr "" + +msgid "PublicBody|Disclosure log" +msgstr "" + +msgid "PublicBody|First letter" +msgstr "" + +msgid "PublicBody|Home page" +msgstr "" + +msgid "PublicBody|Info requests count" +msgstr "" + +msgid "PublicBody|Info requests not held count" +msgstr "" + +msgid "PublicBody|Info requests overdue count" +msgstr "" + +msgid "PublicBody|Info requests successful count" +msgstr "" + +msgid "PublicBody|Info requests visible classified count" +msgstr "" + +msgid "PublicBody|Last edit comment" +msgstr "" + +msgid "PublicBody|Last edit editor" +msgstr "" + +msgid "PublicBody|Name" +msgstr "" + +msgid "PublicBody|Notes" +msgstr "" + +msgid "PublicBody|Publication scheme" +msgstr "" + +msgid "PublicBody|Request email" +msgstr "" + +msgid "PublicBody|Short name" +msgstr "" + +msgid "PublicBody|Url name" +msgstr "" + +msgid "PublicBody|Version" +msgstr "" + +msgid "Publication scheme" +msgstr "" + +msgid "Publication scheme URL" +msgstr "" + +msgid "Purge request" +msgstr "" + +msgid "PurgeRequest|Model" +msgstr "" + +msgid "PurgeRequest|Url" +msgstr "" + +msgid "RSS feed" +msgstr "" + +msgid "RSS feed of updates" +msgstr "" + +msgid "Re-edit this annotation" +msgstr "" + +msgid "Re-edit this message" +msgstr "" + +msgid "Read about advanced search operators, such as proximity and wildcards." +msgstr "" + +msgid "Read blog" +msgstr "" + +msgid "Received an error message, such as delivery failure." +msgstr "" + +msgid "Recently described results first" +msgstr "" + +msgid "Refused." +msgstr "" + +msgid "Remember me (keeps you signed in longer;\\n do not use on a public computer) " +msgstr "" + +msgid "Report abuse" +msgstr "" + +msgid "Report an offensive or unsuitable request" +msgstr "" + +msgid "Report request" +msgstr "" + +msgid "Report this request" +msgstr "" + +msgid "Reported for administrator attention." +msgstr "" + +msgid "Request an internal review" +msgstr "" + +msgid "Request an internal review from {{person_or_body}}" +msgstr "" + +msgid "Request email" +msgstr "" + +msgid "Request has been removed" +msgstr "" + +msgid "Request sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "Request to {{public_body_name}} by {{info_request_user}}. Annotated by {{event_comment_user}} on {{date}}." +msgstr "" + +msgid "Requested from {{public_body_name}} by {{info_request_user}} on {{date}}" +msgstr "" + +msgid "Requested on {{date}}" +msgstr "" + +msgid "Requests are considered overdue if they are in the 'Overdue' or 'Very Overdue' states." +msgstr "" + +msgid "Requests are considered successful if they were classified as either 'Successful' or 'Partially Successful'." +msgstr "" + +msgid "Requests for personal information and vexatious requests are not considered valid for FOI purposes (read more)." +msgstr "" + +msgid "Requests or responses matching your saved search" +msgstr "" + +msgid "Requests similar to '{{request_title}}'" +msgstr "" + +msgid "Requests similar to '{{request_title}}' (page {{page}})" +msgstr "" + +msgid "Respond by email" +msgstr "" + +msgid "Respond to request" +msgstr "" + +msgid "Respond to the FOI request" +msgstr "" + +msgid "Respond using the web" +msgstr "" + +msgid "Response" +msgstr "" + +msgid "Response from a public authority" +msgstr "" + +msgid "Response to '{{title}}'" +msgstr "" + +msgid "Response to this request is delayed." +msgstr "" + +msgid "Response to this request is long overdue." +msgstr "" + +msgid "Response to your request" +msgstr "" + +msgid "Response:" +msgstr "" + +msgid "Restrict to" +msgstr "" + +msgid "Results page {{page_number}}" +msgstr "" + +msgid "Save" +msgstr "" + +msgid "Search" +msgstr "" + +msgid "Search Freedom of Information requests, public authorities and users" +msgstr "" + +msgid "Search contributions by this person" +msgstr "" + +msgid "Search for words in:" +msgstr "" + +msgid "Search in" +msgstr "" + +msgid "Search over
    \\n {{number_of_requests}} requests and
    \\n {{number_of_authorities}} authorities" +msgstr "" + +msgid "Search queries" +msgstr "" + +msgid "Search results" +msgstr "" + +msgid "Search the site to find what you were looking for." +msgstr "" + +msgid "Search within the {{count}} Freedom of Information requests to {{public_body_name}}" +msgid_plural "Search within the {{count}} Freedom of Information requests made to {{public_body_name}}" +msgstr[0] "" +msgstr[1] "" + +msgid "Search your contributions" +msgstr "" + +msgid "See bounce message" +msgstr "" + +msgid "Select one to see more information about the authority." +msgstr "" + +msgid "Select the authority to write to" +msgstr "" + +msgid "Send a followup" +msgstr "" + +msgid "Send a message to " +msgstr "" + +msgid "Send a public follow up message to {{person_or_body}}" +msgstr "" + +msgid "Send a public reply to {{person_or_body}}" +msgstr "" + +msgid "Send follow up to '{{title}}'" +msgstr "" + +msgid "Send message" +msgstr "" + +msgid "Send message to " +msgstr "" + +msgid "Send request" +msgstr "" + +msgid "Set your profile photo" +msgstr "" + +msgid "Short name" +msgstr "" + +msgid "Short name is already taken" +msgstr "" + +msgid "Show most relevant results first" +msgstr "" + +msgid "Show only..." +msgstr "" + +msgid "Showing" +msgstr "" + +msgid "Sign in" +msgstr "" + +msgid "Sign in or make a new account" +msgstr "" + +msgid "Sign in or sign up" +msgstr "" + +msgid "Sign out" +msgstr "" + +msgid "Sign up" +msgstr "" + +msgid "Similar requests" +msgstr "" + +msgid "Simple search" +msgstr "" + +msgid "Some notes have been added to your FOI request - " +msgstr "" + +msgid "Some of the information requested has been received" +msgstr "" + +msgid "Some people who've made requests haven't let us know whether they were\\nsuccessful or not. We need your help –\\nchoose one of these requests, read it, and let everyone know whether or not the\\ninformation has been provided. Everyone'll be exceedingly grateful." +msgstr "" + +msgid "Somebody added a note to your FOI request - " +msgstr "" + +msgid "Someone has updated the status of your request" +msgstr "" + +msgid "Someone, perhaps you, just tried to change their email address on\\n{{site_name}} from {{old_email}} to {{new_email}}." +msgstr "" + +msgid "Sorry - you cannot respond to this request via {{site_name}}, because this is a copy of the request originally at {{link_to_original_request}}." +msgstr "" + +msgid "Sorry, but only {{user_name}} is allowed to do that." +msgstr "" + +msgid "Sorry, there was a problem processing this page" +msgstr "" + +msgid "Sorry, we couldn't find that page" +msgstr "" + +msgid "Special note for this authority!" +msgstr "" + +msgid "Start now »" +msgstr "" + +msgid "Start your own blog" +msgstr "" + +msgid "Stay up to date" +msgstr "" + +msgid "Still awaiting an internal review" +msgstr "" + +msgid "Subject" +msgstr "" + +msgid "Subject:" +msgstr "" + +msgid "Submit" +msgstr "" + +msgid "Submit status" +msgstr "" + +msgid "Submit status and send message" +msgstr "" + +msgid "Subscribe to blog" +msgstr "" + +msgid "Successful Freedom of Information requests" +msgstr "" + +msgid "Successful." +msgstr "" + +msgid "Suggest how the requester can find the rest of the information." +msgstr "" + +msgid "Summary:" +msgstr "" + +msgid "Table of statuses" +msgstr "" + +msgid "Table of varieties" +msgstr "" + +msgid "Tags" +msgstr "" + +msgid "Tags (separated by a space):" +msgstr "" + +msgid "Tags:" +msgstr "" + +msgid "Technical details" +msgstr "" + +msgid "Thank you for helping us keep the site tidy!" +msgstr "" + +msgid "Thank you for making an annotation!" +msgstr "" + +msgid "Thank you for responding to this FOI request! Your response has been published below, and a link to your response has been emailed to " +msgstr "" + +msgid "Thank you for updating the status of the request '{{info_request_title}}'. There are some more requests below for you to classify." +msgstr "" + +msgid "Thank you for updating this request!" +msgstr "" + +msgid "Thank you for updating your profile photo" +msgstr "" + +msgid "Thank you! We'll look into what happened and try and fix it up." +msgstr "" + +msgid "Thanks for helping - your work will make it easier for everyone to find successful\\nresponses, and maybe even let us make league tables..." +msgstr "" + +msgid "Thanks very much - this will help others find useful stuff. We'll\\n also, if you need it, give advice on what to do next about your\\n requests." +msgstr "" + +msgid "Thanks very much for helping keep everything neat and organised.\\n We'll also, if you need it, give you advice on what to do next about each of your\\n requests." +msgstr "" + +msgid "That doesn't look like a valid email address. Please check you have typed it correctly." +msgstr "" + +msgid "The review has finished and overall:" +msgstr "" + +msgid "The Freedom of Information Act does not apply to" +msgstr "" + +msgid "The accounts have been left as they previously were." +msgstr "" + +msgid "The authority do not have the information (maybe they say who does)" +msgstr "" + +msgid "The authority only has a paper copy of the information." +msgstr "" + +msgid "The authority say that they need a postal\\n address, not just an email, for it to be a valid FOI request" +msgstr "" + +msgid "The authority would like to / has responded by post to this request." +msgstr "" + +msgid "The classification of requests (e.g. to say whether they were successful or not) is done manually by users and administrators of the site, which means that they are subject to error." +msgstr "" + +msgid "The email that you, on behalf of {{public_body}}, sent to\\n{{user}} to reply to an {{law_used_short}}\\nrequest has not been delivered." +msgstr "" + +msgid "The error bars shown are 95% confidence intervals for the hypothesized underlying proportion (i.e. that which you would obtain by making an infinite number of requests through this site to that authority). In other words, the population being sampled is all the current and future requests to the authority through this site, rather than, say, all requests that have been made to the public body by any means." +msgstr "" + +msgid "The page doesn't exist. Things you can try now:" +msgstr "" + +msgid "The percentages are calculated with respect to the total number of requests, which includes invalid requests; this is a known problem that will be fixed in a later release." +msgstr "" + +msgid "The public authority does not have the information requested" +msgstr "" + +msgid "The public authority would like part of the request explained" +msgstr "" + +msgid "The public authority would like to / has responded by post" +msgstr "" + +msgid "The request has been refused" +msgstr "" + +msgid "The request has been updated since you originally loaded this page. Please check for any new incoming messages below, and try again." +msgstr "" + +msgid "The request is waiting for clarification." +msgstr "" + +msgid "The request was partially successful." +msgstr "" + +msgid "The request was refused by" +msgstr "" + +msgid "The request was successful." +msgstr "" + +msgid "The request was refused by the public authority" +msgstr "" + +msgid "The request you have tried to view has been removed. There are\\nvarious reasons why we might have done this, sorry we can't be more specific here. Please contact us if you have any questions." +msgstr "" + +msgid "The requester has abandoned this request for some reason" +msgstr "" + +msgid "The response to your request has been delayed. You can say that,\\n by law, the authority should normally have responded\\n promptly and" +msgstr "" + +msgid "The response to your request is long overdue. You can say that, by\\n law, under all circumstances, the authority should have responded\\n by now" +msgstr "" + +msgid "The search index is currently offline, so we can't show the Freedom of Information requests that have been made to this authority." +msgstr "" + +msgid "The search index is currently offline, so we can't show the Freedom of Information requests this person has made." +msgstr "" + +msgid "The {{site_name}} team." +msgstr "" + +msgid "Then you can cancel the alert." +msgstr "" + +msgid "Then you can cancel the alerts." +msgstr "" + +msgid "Then you can change your email address used on {{site_name}}" +msgstr "" + +msgid "Then you can change your password on {{site_name}}" +msgstr "" + +msgid "Then you can classify the FOI response you have got from " +msgstr "" + +msgid "Then you can download a zip file of {{info_request_title}}." +msgstr "" + +msgid "Then you can log into the administrative interface" +msgstr "" + +msgid "Then you can play the request categorisation game." +msgstr "" + +msgid "Then you can report the request '{{title}}'" +msgstr "" + +msgid "Then you can send a message to " +msgstr "" + +msgid "Then you can sign in to {{site_name}}" +msgstr "" + +msgid "Then you can update the status of your request to " +msgstr "" + +msgid "Then you can upload an FOI response. " +msgstr "" + +msgid "Then you can write follow up message to " +msgstr "" + +msgid "Then you can write your reply to " +msgstr "" + +msgid "Then you will be following all new FOI requests." +msgstr "" + +msgid "Then you will be notified whenever '{{user_name}}' requests something or gets a response." +msgstr "" + +msgid "Then you will be notified whenever a new request or response matches your search." +msgstr "" + +msgid "Then you will be notified whenever an FOI request succeeds." +msgstr "" + +msgid "Then you will be notified whenever someone requests something or gets a response from '{{public_body_name}}'." +msgstr "" + +msgid "Then you will be updated whenever the request '{{request_title}}' is updated." +msgstr "" + +msgid "Then you'll be allowed to send FOI requests." +msgstr "" + +msgid "Then your FOI request to {{public_body_name}} will be sent." +msgstr "" + +msgid "Then your annotation to {{info_request_title}} will be posted." +msgstr "" + +msgid "There are {{count}} new annotations on your {{info_request}} request. Follow this link to see what they wrote." +msgstr "" + +msgid "There is more than one person who uses this site and has this name.\\n One of them is shown below, you may mean a different one:" +msgstr "" + +msgid "There is a limit on the number of requests you can make in a day, because we don’t want public authorities to be bombarded with large numbers of inappropriate requests. If you feel you have a good reason to ask for the limit to be lifted in your case, please get in touch." +msgstr "" + +msgid "There is {{count}} person following this request" +msgid_plural "There are {{count}} people following this request" +msgstr[0] "" +msgstr[1] "" + +msgid "There was a delivery error or similar, which needs fixing by the {{site_name}} team." +msgstr "" + +msgid "There was an error with the words you entered, please try again." +msgstr "" + +msgid "There was no data calculated for this graph yet." +msgstr "" + +msgid "There were no requests matching your query." +msgstr "" + +msgid "There were no results matching your query." +msgstr "" + +msgid "These graphs were partly inspired by some statistics that Mark Goodge produced for WhatDoTheyKnow, so thanks are due to him." +msgstr "" + +msgid "They are going to reply by post" +msgstr "" + +msgid "They do not have the information (maybe they say who does)" +msgstr "" + +msgid "They have been given the following explanation:" +msgstr "" + +msgid "They have not replied to your {{law_used_short}} request {{title}} promptly, as normally required by law" +msgstr "" + +msgid "They have not replied to your {{law_used_short}} request {{title}}, \\nas required by law" +msgstr "" + +msgid "Things to do with this request" +msgstr "" + +msgid "Things you're following" +msgstr "" + +msgid "This authority no longer exists, so you cannot make a request to it." +msgstr "" + +msgid "This covers a very wide spectrum of information about the state of\\n the natural and built environment, such as:" +msgstr "" + +msgid "This external request has been hidden" +msgstr "" + +msgid "This is a plain-text version of the Freedom of Information request \"{{request_title}}\". The latest, full version is available online at {{full_url}}" +msgstr "" + +msgid "This is an HTML version of an attachment to the Freedom of Information request" +msgstr "" + +msgid "This is because {{title}} is an old request that has been\\nmarked to no longer receive responses." +msgstr "" + +msgid "This is the first version." +msgstr "" + +msgid "This is your own request, so you will be automatically emailed when new responses arrive." +msgstr "" + +msgid "This message has been hidden." +msgstr "" + +msgid "This message has been hidden. There are various reasons why we might have done this, sorry we can't be more specific here." +msgstr "" + +msgid "This message has prominence 'hidden'. You can only see it because you are logged in as a super user." +msgstr "" + +msgid "This message has prominence 'hidden'. {{reason}} You can only see it because you are logged in as a super user." +msgstr "" + +msgid "This message is hidden, so that only you, the requester, can see it. Please contact us if you are not sure why." +msgstr "" + +msgid "This message is hidden, so that only you, the requester, can see it. {{reason}}" +msgstr "" + +msgid "This page of public body statistics is currently experimental, so there are some caveats that should be borne in mind:" +msgstr "" + +msgid "This particular request is finished:" +msgstr "" + +msgid "This person has made no Freedom of Information requests using this site." +msgstr "" + +msgid "This person's annotations" +msgstr "" + +msgid "This person's {{count}} Freedom of Information request" +msgid_plural "This person's {{count}} Freedom of Information requests" +msgstr[0] "" +msgstr[1] "" + +msgid "This person's {{count}} annotation" +msgid_plural "This person's {{count}} annotations" +msgstr[0] "" +msgstr[1] "" + +msgid "This request requires administrator attention" +msgstr "" + +msgid "This request has already been reported for administrator attention" +msgstr "" + +msgid "This request has an unknown status." +msgstr "" + +msgid "This request has been hidden from the site, because an administrator considers it not to be an FOI request" +msgstr "" + +msgid "This request has been hidden from the site, because an administrator considers it vexatious" +msgstr "" + +msgid "This request has been reported as needing administrator attention (perhaps because it is vexatious, or a request for personal information)" +msgstr "" + +msgid "This request has been withdrawn by the person who made it.\\n There may be an explanation in the correspondence below." +msgstr "" + +msgid "This request has been marked for review by the site administrators, who have not hidden it at this time. If you believe it should be hidden, please contact us." +msgstr "" + +msgid "This request has been reported for administrator attention" +msgstr "" + +msgid "This request has been set by an administrator to \"allow new responses from nobody\"" +msgstr "" + +msgid "This request has had an unusual response, and requires attention from the {{site_name}} team." +msgstr "" + +msgid "This request has prominence 'hidden'. You can only see it because you are logged\\n in as a super user." +msgstr "" + +msgid "This request is hidden, so that only you the requester can see it. Please\\n contact us if you are not sure why." +msgstr "" + +msgid "This request is still in progress:" +msgstr "" + +msgid "This request requires administrator attention" +msgstr "" + +msgid "This request was not made via {{site_name}}" +msgstr "" + +msgid "This table shows the technical details of the internal events that happened\\nto this request on {{site_name}}. This could be used to generate information about\\nthe speed with which authorities respond to requests, the number of requests\\nwhich require a postal response and much more." +msgstr "" + +msgid "This user has been banned from {{site_name}} " +msgstr "" + +msgid "This was not possible because there is already an account using \\nthe email address {{email}}." +msgstr "" + +msgid "To cancel these alerts" +msgstr "" + +msgid "To cancel this alert" +msgstr "" + +msgid "To carry on, you need to sign in or make an account. Unfortunately, there\\nwas a technical problem trying to do this." +msgstr "" + +msgid "To change your email address used on {{site_name}}" +msgstr "" + +msgid "To classify the response to this FOI request" +msgstr "" + +msgid "To do that please send a private email to " +msgstr "" + +msgid "To do this, first click on the link below." +msgstr "" + +msgid "To download the zip file" +msgstr "" + +msgid "To follow all successful requests" +msgstr "" + +msgid "To follow new requests" +msgstr "" + +msgid "To follow requests and responses matching your search" +msgstr "" + +msgid "To follow requests by '{{user_name}}'" +msgstr "" + +msgid "To follow requests made using {{site_name}} to the public authority '{{public_body_name}}'" +msgstr "" + +msgid "To follow the request '{{request_title}}'" +msgstr "" + +msgid "To help us keep the site tidy, someone else has updated the status of the \\n{{law_used_full}} request {{title}} that you made to {{public_body}}, to \"{{display_status}}\" If you disagree with their categorisation, please update the status again yourself to what you believe to be more accurate." +msgstr "" + +msgid "To let everyone know, follow this link and then select the appropriate box." +msgstr "" + +msgid "To log into the administrative interface" +msgstr "" + +msgid "To play the request categorisation game" +msgstr "" + +msgid "To post your annotation" +msgstr "" + +msgid "To reply to " +msgstr "" + +msgid "To report this request" +msgstr "" + +msgid "To send a follow up message to " +msgstr "" + +msgid "To send a message to " +msgstr "" + +msgid "To send your FOI request" +msgstr "" + +msgid "To update the status of this FOI request" +msgstr "" + +msgid "To upload a response, you must be logged in using an email address from " +msgstr "" + +msgid "To use the advanced search, combine phrases and labels as described in the search tips below." +msgstr "" + +msgid "To view the email address that we use to send FOI requests to {{public_body_name}}, please enter these words." +msgstr "" + +msgid "To view the response, click on the link below." +msgstr "" + +msgid "To {{public_body_link_absolute}}" +msgstr "" + +msgid "To:" +msgstr "" + +msgid "Today" +msgstr "" + +msgid "Too many requests" +msgstr "" + +msgid "Top search results:" +msgstr "" + +msgid "Track thing" +msgstr "" + +msgid "Track this person" +msgstr "" + +msgid "Track this search" +msgstr "" + +msgid "TrackThing|Track medium" +msgstr "" + +msgid "TrackThing|Track query" +msgstr "" + +msgid "TrackThing|Track type" +msgstr "" + +msgid "Turn off email alerts" +msgstr "" + +msgid "Tweet this request" +msgstr "" + +msgid "Type 01/01/2008..14/01/2008 to only show things that happened in the first two weeks of January." +msgstr "" + +msgid "URL name can't be blank" +msgstr "" + +msgid "Unable to change email address on {{site_name}}" +msgstr "" + +msgid "Unable to send a reply to {{username}}" +msgstr "" + +msgid "Unable to send follow up message to {{username}}" +msgstr "" + +msgid "Unexpected search result type" +msgstr "" + +msgid "Unexpected search result type " +msgstr "" + +msgid "Unfortunately we don't know the FOI\\nemail address for that authority, so we can't validate this.\\nPlease contact us to sort it out." +msgstr "" + +msgid "Unfortunately, we do not have a working {{info_request_law_used_full}}\\naddress for" +msgstr "" + +msgid "Unknown" +msgstr "" + +msgid "Unsubscribe" +msgstr "" + +msgid "Unusual response." +msgstr "" + +msgid "Update the status of this request" +msgstr "" + +msgid "Update the status of your request to " +msgstr "" + +msgid "Upload FOI response" +msgstr "" + +msgid "Use OR (in capital letters) where you don't mind which word, e.g. commons OR lords" +msgstr "" + +msgid "Use quotes when you want to find an exact phrase, e.g. \"Liverpool City Council\"" +msgstr "" + +msgid "User" +msgstr "" + +msgid "User info request sent alert" +msgstr "" + +msgid "User – {{name}}" +msgstr "" + +msgid "UserInfoRequestSentAlert|Alert type" +msgstr "" + +msgid "User|About me" +msgstr "" + +msgid "User|Admin level" +msgstr "" + +msgid "User|Ban text" +msgstr "" + +msgid "User|Email" +msgstr "" + +msgid "User|Email bounce message" +msgstr "" + +msgid "User|Email bounced at" +msgstr "" + +msgid "User|Email confirmed" +msgstr "" + +msgid "User|Hashed password" +msgstr "" + +msgid "User|Last daily track email" +msgstr "" + +msgid "User|Locale" +msgstr "" + +msgid "User|Name" +msgstr "" + +msgid "User|No limit" +msgstr "" + +msgid "User|Receive email alerts" +msgstr "" + +msgid "User|Salt" +msgstr "" + +msgid "User|Url name" +msgstr "" + +msgid "Version {{version}}" +msgstr "" + +msgid "View FOI email address" +msgstr "" + +msgid "View FOI email address for '{{public_body_name}}'" +msgstr "" + +msgid "View FOI email address for {{public_body_name}}" +msgstr "" + +msgid "View Freedom of Information requests made by {{user_name}}:" +msgstr "" + +msgid "View and search requests" +msgstr "" + +msgid "View authorities" +msgstr "" + +msgid "View email" +msgstr "" + +msgid "View requests" +msgstr "" + +msgid "Waiting clarification." +msgstr "" + +msgid "Waiting for an internal review by {{public_body_link}} of their handling of this request." +msgstr "" + +msgid "Waiting for the public authority to complete an internal review of their handling of the request" +msgstr "" + +msgid "Waiting for the public authority to reply" +msgstr "" + +msgid "Was the response you got to your FOI request any good?" +msgstr "" + +msgid "We consider it is not a valid FOI request, and have therefore hidden it from other users." +msgstr "" + +msgid "We consider it to be vexatious, and have therefore hidden it from other users." +msgstr "" + +msgid "We do not have a working request email address for this authority." +msgstr "" + +msgid "We do not have a working {{law_used_full}} address for {{public_body_name}}." +msgstr "" + +msgid "We don't know whether the most recent response to this request contains\\n information or not\\n –\\n\tif you are {{user_link}} please sign in and let everyone know." +msgstr "" + +msgid "We will not reveal your email address to anybody unless you or\\n the law tell us to (details). " +msgstr "" + +msgid "We will not reveal your email address to anybody unless you\\nor the law tell us to." +msgstr "" + +msgid "We will not reveal your email addresses to anybody unless you\\nor the law tell us to." +msgstr "" + +msgid "We're waiting for" +msgstr "" + +msgid "We're waiting for someone to read" +msgstr "" + +msgid "We've sent an email to your new email address. You'll need to click the link in\\nit before your email address will be changed." +msgstr "" + +msgid "We've sent you an email, and you'll need to click the link in it before you can\\ncontinue." +msgstr "" + +msgid "We've sent you an email, click the link in it, then you can change your password." +msgstr "" + +msgid "What are you doing?" +msgstr "" + +msgid "What best describes the status of this request now?" +msgstr "" + +msgid "What information has been released?" +msgstr "" + +msgid "What information has been requested?" +msgstr "" + +msgid "When you get there, please update the status to say if the response \\ncontains any useful information." +msgstr "" + +msgid "When you receive the paper response, please help\\n others find out what it says:" +msgstr "" + +msgid "When you're done, come back here, reload this page and file your new request." +msgstr "" + +msgid "Which of these is happening?" +msgstr "" + +msgid "Who can I request information from?" +msgstr "" + +msgid "Withdrawn by the requester." +msgstr "" + +msgid "Wk" +msgstr "" + +msgid "Would you like to see a website like this in your country?" +msgstr "" + +msgid "Write a reply" +msgstr "" + +msgid "Write a reply to " +msgstr "" + +msgid "Write your FOI follow up message to " +msgstr "" + +msgid "Write your request in simple, precise language." +msgstr "" + +msgid "You" +msgstr "" + +msgid "You are already following new requests" +msgstr "" + +msgid "You are already following requests to {{public_body_name}}" +msgstr "" + +msgid "You are already following things matching this search" +msgstr "" + +msgid "You are already following this person" +msgstr "" + +msgid "You are already following this request" +msgstr "" + +msgid "You are already following updates about {{track_description}}" +msgstr "" + +msgid "You are currently receiving notification of new activity on your wall by email." +msgstr "" + +msgid "You are following all new successful responses" +msgstr "" + +msgid "You are no longer following {{track_description}}." +msgstr "" + +msgid "You are now following updates about {{track_description}}" +msgstr "" + +msgid "You can complain by" +msgstr "" + +msgid "You can change the requests and users you are following on your profile page." +msgstr "" + +msgid "You can get this page in computer-readable format as part of the main JSON\\npage for the request. See the API documentation." +msgstr "" + +msgid "You can only request information about the environment from this authority." +msgstr "" + +msgid "You have a new response to the {{law_used_full}} request " +msgstr "" + +msgid "You have found a bug. Please contact us to tell us about the problem" +msgstr "" + +msgid "You have hit the rate limit on new requests. Users are ordinarily limited to {{max_requests_per_user_per_day}} requests in any rolling 24-hour period. You will be able to make another request in {{can_make_another_request}}." +msgstr "" + +msgid "You have made no Freedom of Information requests using this site." +msgstr "" + +msgid "You have now changed the text about you on your profile." +msgstr "" + +msgid "You have now changed your email address used on {{site_name}}" +msgstr "" + +msgid "You just tried to sign up to {{site_name}}, when you\\nalready have an account. Your name and password have been\\nleft as they previously were.\\n\\nPlease click on the link below." +msgstr "" + +msgid "You know what caused the error, and can suggest a solution, such as a working email address." +msgstr "" + +msgid "You may include attachments. If you would like to attach a\\n file too large for email, use the form below." +msgstr "" + +msgid "You may be able to find\\n one on their website, or by phoning them up and asking. If you manage\\n to find one, then please send it to us." +msgstr "" + +msgid "You may be able to find\\none on their website, or by phoning them up and asking. If you manage\\nto find one, then please send it to us." +msgstr "" + +msgid "You need to be logged in to change the text about you on your profile." +msgstr "" + +msgid "You need to be logged in to change your profile photo." +msgstr "" + +msgid "You need to be logged in to clear your profile photo." +msgstr "" + +msgid "You need to be logged in to edit your profile." +msgstr "" + +msgid "You need to be logged in to report a request for administrator attention" +msgstr "" + +msgid "You previously submitted that exact follow up message for this request." +msgstr "" + +msgid "You should have received a copy of the request by email, and you can respond\\n by simply replying to that email. For your convenience, here is the address:" +msgstr "" + +msgid "You want to give your postal address to the authority in private." +msgstr "" + +msgid "You will be unable to make new requests, send follow ups, add annotations or\\nsend messages to other users. You may continue to view other requests, and set\\nup\\nemail alerts." +msgstr "" + +msgid "You will no longer be emailed updates for those alerts" +msgstr "" + +msgid "You will now be emailed updates about {{track_description}}. Prefer not to receive emails?" +msgstr "" + +msgid "You will only get an answer to your request if you follow up\\nwith the clarification." +msgstr "" + +msgid "You will still be able to view it while logged in to the site. Please reply to this email if you would like to discuss this decision further." +msgstr "" + +msgid "You're in. Continue sending your request" +msgstr "" + +msgid "You're long overdue a response to your FOI request - " +msgstr "" + +msgid "You're not following anything." +msgstr "" + +msgid "You've now cleared your profile photo" +msgstr "" + +msgid "Your name will appear publicly\\n (why?)\\n on this website and in search engines. If you\\n are thinking of using a pseudonym, please\\n read this first." +msgstr "" + +msgid "Your annotations" +msgstr "" + +msgid "Your details, including your email address, have not been given to anyone." +msgstr "" + +msgid "Your e-mail:" +msgstr "" + +msgid "Your follow up has not been sent because this request has been stopped to prevent spam. Please contact us if you really want to send a follow up message." +msgstr "" + +msgid "Your follow up message has been sent on its way." +msgstr "" + +msgid "Your internal review request has been sent on its way." +msgstr "" + +msgid "Your message has been sent. Thank you for getting in touch! We'll get back to you soon." +msgstr "" + +msgid "Your message to {{recipient_user_name}} has been sent" +msgstr "" + +msgid "Your message to {{recipient_user_name}} has been sent!" +msgstr "" + +msgid "Your message will appear in search engines" +msgstr "" + +msgid "Your name and annotation will appear in search engines." +msgstr "" + +msgid "Your name, request and any responses will appear in search engines\\n (details)." +msgstr "" + +msgid "Your name:" +msgstr "" + +msgid "Your original message is attached." +msgstr "" + +msgid "Your password has been changed." +msgstr "" + +msgid "Your password:" +msgstr "" + +msgid "Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." +msgstr "" + +msgid "Your request '{{request}}' at {{url}} has been reviewed by moderators." +msgstr "" + +msgid "Your request on {{site_name}} hidden" +msgstr "" + +msgid "Your request was called {{info_request}}. Letting everyone know whether you got the information will help us keep tabs on" +msgstr "" + +msgid "Your request:" +msgstr "" + +msgid "Your response to an FOI request was not delivered" +msgstr "" + +msgid "Your response will appear on the Internet, read why and answers to other questions." +msgstr "" + +msgid "Your thoughts on what the {{site_name}} administrators should do about the request." +msgstr "" + +msgid "Your {{count}} Freedom of Information request" +msgid_plural "Your {{count}} Freedom of Information requests" +msgstr[0] "" +msgstr[1] "" + +msgid "Your {{count}} annotation" +msgid_plural "Your {{count}} annotations" +msgstr[0] "" +msgstr[1] "" + +msgid "Your {{site_name}} email alert" +msgstr "" + +msgid "Yours faithfully," +msgstr "" + +msgid "Yours sincerely," +msgstr "" + +msgid "Yours," +msgstr "" + +msgid "[FOI #{{request}} email]" +msgstr "" + +msgid "[{{public_body}} request email]" +msgstr "" + +msgid "[{{site_name}} contact email]" +msgstr "" + +msgid "\\n\\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]" +msgstr "" + +msgid "a one line summary of the information you are requesting, \\n\t\t\te.g." +msgstr "" + +msgid "admin" +msgstr "" + +msgid "alaveteli_foi:The software that runs {{site_name}}" +msgstr "" + +msgid "all requests" +msgstr "" + +msgid "also called {{public_body_short_name}}" +msgstr "" + +msgid "an anonymous user" +msgstr "" + +msgid "and" +msgstr "" + +msgid "and update the status accordingly. Perhaps you might like to help out by doing that?" +msgstr "" + +msgid "and update the status." +msgstr "" + +msgid "and we'll suggest what to do next" +msgstr "" + +msgid "any new requests" +msgstr "" + +msgid "any successful requests" +msgstr "" + +msgid "anything" +msgstr "" + +msgid "are long overdue." +msgstr "" + +msgid "at" +msgstr "" + +msgid "authorities" +msgstr "" + +msgid "awaiting a response" +msgstr "" + +msgid "beginning with ‘{{first_letter}}’" +msgstr "" + +msgid "between two dates" +msgstr "" + +msgid "but followupable" +msgstr "" + +msgid "by" +msgstr "" + +msgid "by {{date}}" +msgstr "" + +msgid "by {{public_body_name}} to {{info_request_user}} on {{date}}." +msgstr "" + +msgid "by {{user_link_absolute}}" +msgstr "" + +msgid "comments" +msgstr "" + +msgid "containing your postal address, and asking them to reply to this request.\\n Or you could phone them." +msgstr "" + +msgid "details" +msgstr "" + +msgid "display_status only works for incoming and outgoing messages right now" +msgstr "" + +msgid "during term time" +msgstr "" + +msgid "edit text about you" +msgstr "" + +msgid "even during holidays" +msgstr "" + +msgid "everything" +msgstr "" + +msgid "external" +msgstr "" + +msgid "has reported an" +msgstr "" + +msgid "have delayed." +msgstr "" + +msgid "hide quoted sections" +msgstr "" + +msgid "in term time" +msgstr "" + +msgid "in the category ‘{{category_name}}’" +msgstr "" + +msgid "internal error" +msgstr "" + +msgid "internal reviews" +msgstr "" + +msgid "is waiting for your clarification." +msgstr "" + +msgid "just to see how it works" +msgstr "" + +msgid "left an annotation" +msgstr "" + +msgid "made." +msgstr "" + +msgid "matching the tag ‘{{tag_name}}’" +msgstr "" + +msgid "messages from authorities" +msgstr "" + +msgid "messages from users" +msgstr "" + +msgid "move..." +msgstr "" + +msgid "no later than" +msgstr "" + +msgid "no longer exists. If you are trying to make\\n From the request page, try replying to a particular message, rather than sending\\n a general followup. If you need to make a general followup, and know\\n an email which will go to the right place, please send it to us." +msgstr "" + +msgid "normally" +msgstr "" + +msgid "not requestable due to: {{reason}}" +msgstr "" + +msgid "please sign in as " +msgstr "" + +msgid "requesting an internal review" +msgstr "" + +msgid "requests" +msgstr "" + +msgid "requests which are {{list_of_statuses}}" +msgstr "" + +msgid "response as needing administrator attention. Take a look, and reply to this\\nemail to let them know what you are going to do about it." +msgstr "" + +msgid "send a follow up message" +msgstr "" + +msgid "sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "set to blank (empty string) if can't find an address; these emails are public as anyone can view with a CAPTCHA" +msgstr "" + +msgid "show quoted sections" +msgstr "" + +msgid "sign in" +msgstr "" + +msgid "simple_date_format" +msgstr "" + +msgid "successful" +msgstr "" + +msgid "successful requests" +msgstr "" + +msgid "that you made to" +msgstr "" + +msgid "the main FOI contact address for {{public_body}}" +msgstr "" + +#. This phrase completes the following sentences: +#. Request an internal review from... +#. Send a public follow up message to... +#. Send a public reply to... +#. Don't want to address your message to... ? +msgid "the main FOI contact at {{public_body}}" +msgstr "" + +msgid "the requester" +msgstr "" + +msgid "the {{site_name}} team" +msgstr "" + +msgid "to read" +msgstr "" + +msgid "to send a follow up message." +msgstr "" + +msgid "to {{public_body}}" +msgstr "" + +msgid "unknown reason " +msgstr "" + +msgid "unknown status " +msgstr "" + +msgid "unresolved requests" +msgstr "" + +msgid "unsubscribe" +msgstr "" + +msgid "unsubscribe all" +msgstr "" + +msgid "unsuccessful" +msgstr "" + +msgid "unsuccessful requests" +msgstr "" + +msgid "useful information." +msgstr "" + +msgid "users" +msgstr "" + +msgid "what's that?" +msgstr "" + +msgid "{{count}} FOI requests found" +msgstr "" + +msgid "{{count}} Freedom of Information request to {{public_body_name}}" +msgid_plural "{{count}} Freedom of Information requests to {{public_body_name}}" +msgstr[0] "" +msgstr[1] "" + +msgid "{{count}} person is following this authority" +msgid_plural "{{count}} people are following this authority" +msgstr[0] "" +msgstr[1] "" + +msgid "{{count}} request" +msgid_plural "{{count}} requests" +msgstr[0] "" +msgstr[1] "" + +msgid "{{count}} request made." +msgid_plural "{{count}} requests made." +msgstr[0] "" +msgstr[1] "" + +msgid "{{existing_request_user}} already\\n created the same request on {{date}}. You can either view the existing request,\\n or edit the details below to make a new but similar request." +msgstr "" + +msgid "{{info_request_user_name}} only:" +msgstr "" + +msgid "{{law_used_full}} request - {{title}}" +msgstr "" + +msgid "{{law_used}} requests at {{public_body}}" +msgstr "" + +msgid "{{length_of_time}} ago" +msgstr "" + +msgid "{{list_of_things}} matching text '{{search_query}}'" +msgstr "" + +msgid "{{number_of_comments}} comments" +msgstr "" + +msgid "{{public_body_link}} answered a request about" +msgstr "" + +msgid "{{public_body_link}} was sent a request about" +msgstr "" + +msgid "{{public_body_name}} only:" +msgstr "" + +msgid "{{public_body}} has asked you to explain part of your {{law_used}} request." +msgstr "" + +msgid "{{public_body}} sent a response to {{user_name}}" +msgstr "" + +msgid "{{reason}}, please sign in or make a new account." +msgstr "" + +msgid "{{search_results}} matching '{{query}}'" +msgstr "" + +msgid "{{site_name}} blog and tweets" +msgstr "" + +msgid "{{site_name}} covers requests to {{number_of_authorities}} authorities, including:" +msgstr "" + +msgid "{{site_name}} sends new requests to {{request_email}} for this authority." +msgstr "" + +msgid "{{site_name}} users have made {{number_of_requests}} requests, including:" +msgstr "" + +msgid "{{thing_changed}} was changed from {{from_value}} to {{to_value}}" +msgstr "" + +msgid "{{title}} - a Freedom of Information request to {{public_body}}" +msgstr "" + +msgid "{{user_name}} (Account suspended)" +msgstr "" + +msgid "{{user_name}} - Freedom of Information requests" +msgstr "" + +msgid "{{user_name}} - user profile" +msgstr "" + +msgid "{{user_name}} added an annotation" +msgstr "" + +msgid "{{user_name}} has annotated your {{law_used_short}} \\nrequest. Follow this link to see what they wrote." +msgstr "" + +msgid "{{user_name}} has used {{site_name}} to send you the message below." +msgstr "" + +msgid "{{user_name}} sent a follow up message to {{public_body}}" +msgstr "" + +msgid "{{user_name}} sent a request to {{public_body}}" +msgstr "" + +msgid "{{username}} left an annotation:" +msgstr "" + +msgid "{{user}} ({{user_admin_link}}) made this {{law_used_full}} request (admin) to {{public_body_link}} (admin)" +msgstr "" + +msgid "{{user}} made this {{law_used_full}} request" +msgstr "" diff --git a/locale/tr/app.po b/locale/tr/app.po index f7059000b..56ac010da 100644 --- a/locale/tr/app.po +++ b/locale/tr/app.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Turkish (http://www.transifex.com/projects/p/alaveteli/language/tr/)\n" "Language: tr\n" diff --git a/locale/uk/app.po b/locale/uk/app.po index b5d29c947..2cc14351c 100644 --- a/locale/uk/app.po +++ b/locale/uk/app.po @@ -15,7 +15,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Ukrainian (http://www.transifex.com/projects/p/alaveteli/language/uk/)\n" "Language: uk\n" diff --git a/locale/vi/app.po b/locale/vi/app.po index c8bc3c580..2389b351d 100644 --- a/locale/vi/app.po +++ b/locale/vi/app.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: alaveteli\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-08 12:10+0000\n" -"PO-Revision-Date: 2013-11-08 12:13+0000\n" +"PO-Revision-Date: 2013-11-20 10:14+0000\n" "Last-Translator: mysociety \n" "Language-Team: Vietnamese (http://www.transifex.com/projects/p/alaveteli/language/vi/)\n" "Language: vi\n" diff --git a/locale/zh_HK/app.po b/locale/zh_HK/app.po new file mode 100644 index 000000000..e64c7063b --- /dev/null +++ b/locale/zh_HK/app.po @@ -0,0 +1,3499 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: alaveteli\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-11-08 12:10+0000\n" +"PO-Revision-Date: 2013-12-08 14:22+0000\n" +"Last-Translator: mysociety \n" +"Language-Team: Chinese (Hong Kong) (http://www.transifex.com/projects/p/alaveteli/language/zh_HK/)\n" +"Language: zh_HK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgid " This will appear on your {{site_name}} profile, to make it\\n easier for others to get involved with what you're doing." +msgstr "" + +msgid " (no ranty politics, read our moderation policy)" +msgstr "" + +msgid " (patience, especially for large files, it may take a while!)" +msgstr "" + +msgid " (you)" +msgstr "" + +msgid " - view and make Freedom of Information requests" +msgstr "" + +msgid " - wall" +msgstr "" + +msgid " Note:\\n We will send you an email. Follow the instructions in it to change\\n your password." +msgstr "" + +msgid " Privacy note: Your email address will be given to" +msgstr "" + +msgid " Summarise the content of any information returned. " +msgstr "" + +msgid " Advise on how to best clarify the request." +msgstr "" + +msgid " Ideas on what other documents to request which the authority may hold. " +msgstr "" + +msgid " If you know the address to use, then please send it to us.\\n You may be able to find the address on their website, or by phoning them up and asking." +msgstr "" + +msgid " Include relevant links, such as to a campaign page, your blog or a\\n twitter account. They will be made clickable. \\n e.g." +msgstr "" + +msgid " Link to the information requested, if it is already available on the Internet. " +msgstr "" + +msgid " Offer better ways of wording the request to get the information. " +msgstr "" + +msgid " Say how you've used the information, with links if possible." +msgstr "" + +msgid " Suggest where else the requester might find the information. " +msgstr "" + +msgid " What are you investigating using Freedom of Information? " +msgstr "" + +msgid " You are already being emailed updates about the request." +msgstr "" + +msgid " You will also be emailed updates about the request." +msgstr "" + +msgid " made by " +msgstr "" + +msgid " or " +msgstr "" + +msgid " when you send this message." +msgstr "" + +msgid "\"Hello! We have an important message for visitors outside {{country_name}}\"" +msgstr "" + +msgid "'Crime statistics by ward level for Wales'" +msgstr "" + +msgid "'Pollution levels over time for the River Tyne'" +msgstr "" + +msgid "'{{link_to_authority}}', a public authority" +msgstr "" + +msgid "'{{link_to_request}}', a request" +msgstr "" + +msgid "'{{link_to_user}}', a person" +msgstr "" + +msgid "*unknown*" +msgstr "" + +msgid ",\\n\\n\\n\\nYours,\\n\\n{{user_name}}" +msgstr "" + +msgid "- or -" +msgstr "" + +msgid "1. Select an authority" +msgstr "" + +msgid "2. Ask for Information" +msgstr "" + +msgid "3. Now check your request" +msgstr "" + +msgid "Browse all or ask us to add one." +msgstr "" + +msgid "Add an annotation (to help the requester or others)" +msgstr "" + +msgid "Sign in to change password, subscriptions and more ({{user_name}} only)" +msgstr "" + +msgid "

    All done! Thank you very much for your help.

    There are more things you can do to help {{site_name}}.

    " +msgstr "" + +msgid "

    Thank you! Here are some ideas on what to do next:

    \\n
      \\n
    • To send your request to another authority, first copy the text of your request below, then find the other authority.
    • \\n
    • If you would like to contest the authority's claim that they do not hold the information, here is\\n how to complain.\\n
    • \\n
    • We have suggestions\\n on other means to answer your question.\\n
    • \\n
    " +msgstr "" + +msgid "

    Thank you! Hope you don't have to wait much longer.

    By law, you should have got a response promptly, and normally before the end of {{date_response_required_by}}.

    " +msgstr "" + +msgid "

    Thank you! Hopefully your wait isn't too long.

    By law, you should get a response promptly, and normally before the end of \\n{{date_response_required_by}}.

    " +msgstr "" + +msgid "

    Thank you! Hopefully your wait isn't too long.

    You should get a response within {{late_number_of_days}} days, or be told if it will take longer (details).

    " +msgstr "" + +msgid "

    Thank you! Your request is long overdue, by more than {{very_late_number_of_days}} working days. Most requests should be answered within {{late_number_of_days}} working days. You might like to complain about this, see below.

    " +msgstr "" + +msgid "

    Thanks for changing the text about you on your profile.

    \\n

    Next... You can upload a profile photograph too.

    " +msgstr "" + +msgid "

    Thanks for updating your profile photo.

    \\n

    Next... You can put some text about you and your research on your profile.

    " +msgstr "" + +msgid "

    We recommend that you edit your request and remove the email address.\\n If you leave it, the email address will be sent to the authority, but will not be displayed on the site.

    " +msgstr "" + +msgid "

    We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

    " +msgstr "" + +msgid "

    We're glad you got all the information that you wanted. If you write about or make use of the information, please come back and add an annotation below saying what you did.

    If you found {{site_name}} useful, make a donation to the charity which runs it.

    " +msgstr "" + +msgid "

    We're glad you got some of the information that you wanted. If you found {{site_name}} useful, make a donation to the charity which runs it.

    If you want to try and get the rest of the information, here's what to do now.

    " +msgstr "" + +msgid "

    We're glad you got some of the information that you wanted.

    If you want to try and get the rest of the information, here's what to do now.

    " +msgstr "" + +msgid "

    You do not need to include your email in the request in order to get a reply (details).

    " +msgstr "" + +msgid "

    You do not need to include your email in the request in order to get a reply, as we will ask for it on the next screen (details).

    " +msgstr "" + +msgid "

    Your request contains a postcode. Unless it directly relates to the subject of your request, please remove any address as it will appear publicly on the Internet.

    " +msgstr "" + +msgid "

    Your {{law_used_full}} request has been sent on its way!

    \\n

    We will email you when there is a response, or after {{late_number_of_days}} working days if the authority still hasn't\\n replied by then.

    \\n

    If you write about this request (for example in a forum or a blog) please link to this page, and add an\\n annotation below telling people about your writing.

    " +msgstr "" + +msgid "

    {{site_name}} is currently in maintenance. You can only view existing requests. You cannot make new ones, add followups or annotations, or otherwise change the database.

    {{read_only}}

    " +msgstr "" + +msgid "If you use web-based email or have \"junk mail\" filters, also check your\\nbulk/spam mail folders. Sometimes, our messages are marked that way.\\n

    " +msgstr "" + +msgid " Can I request information about myself?\\n\t\t\tNo! (Click here for details)" +msgstr "" + +msgid "commented_by:tony_bowden to search annotations made by Tony Bowden, typing the name as in the URL." +msgstr "" + +msgid "filetype:pdf to find all responses with PDF attachments. Or try these: {{list_of_file_extensions}}" +msgstr "" + +msgid "request: to restrict to a specific request, typing the title as in the URL." +msgstr "" + +msgid "requested_by:julian_todd to search requests made by Julian Todd, typing the name as in the URL." +msgstr "" + +msgid "requested_from:home_office to search requests from the Home Office, typing the name as in the URL." +msgstr "" + +msgid "status: to select based on the status or historical status of the request, see the table of statuses below." +msgstr "" + +msgid "tag:charity to find all public authorities or requests with a given tag. You can include multiple tags, \\n and tag values, e.g. tag:openlylocal AND tag:financial_transaction:335633. Note that by default any of the tags\\n can be present, you have to put AND explicitly if you only want results them all present." +msgstr "" + +msgid "variety: to select type of thing to search for, see the table of varieties below." +msgstr "" + +msgid "Advice on how to get a response that will satisfy the requester. " +msgstr "" + +msgid "All the information has been sent" +msgstr "" + +msgid "Anything else, such as clarifying, prompting, thanking" +msgstr "" + +msgid "Caveat emptor! To use this data in an honourable way, you will need \\na good internal knowledge of user behaviour on {{site_name}}. How, \\nwhy and by whom requests are categorised is not straightforward, and there will\\nbe user error and ambiguity. You will also need to understand FOI law, and the\\nway authorities use it. Plus you'll need to be an elite statistician. Please\\ncontact us with questions." +msgstr "" + +msgid "Clarification has been requested" +msgstr "" + +msgid "No response has been received\\n (maybe there's just an acknowledgement)" +msgstr "" + +msgid "Note: Because we're testing, requests are being sent to {{email}} rather than to the actual authority." +msgstr "" + +msgid "Note: You're sending a message to yourself, presumably\\n to try out how it works." +msgstr "" + +msgid "Note:\\n We will send an email to your new email address. Follow the\\n instructions in it to confirm changing your email." +msgstr "" + +msgid "Privacy note: If you want to request private information about\\n yourself then click here." +msgstr "" + +msgid "Privacy note: Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." +msgstr "" + +msgid "Privacy warning: Your message, and any response\\n to it, will be displayed publicly on this website." +msgstr "" + +msgid "Some of the information has been sent " +msgstr "" + +msgid "Thank the public authority or " +msgstr "" + +msgid "did not have the information requested." +msgstr "" + +msgid "A follow up to {{request_title}} was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "A response to {{request_title}} was sent by {{public_body_name}} to {{info_request_user}} on {{date}}. The request status is: {{request_status}}" +msgstr "" + +msgid "A summary of the response if you have received it by post. " +msgstr "" + +msgid "A Freedom of Information request" +msgstr "" + +msgid "A full history of my FOI request and all correspondence is available on the Internet at this address: {{url}}" +msgstr "" + +msgid "A new request, {{request_title}}, was sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "A public authority" +msgstr "" + +msgid "A response will be sent by post" +msgstr "" + +msgid "A strange reponse, required attention by the {{site_name}} team" +msgstr "" + +msgid "A vexatious request" +msgstr "" + +msgid "A {{site_name}} user" +msgstr "" + +msgid "About you:" +msgstr "" + +msgid "Act on what you've learnt" +msgstr "" + +msgid "Acts as xapian/acts as xapian job" +msgstr "" + +msgid "ActsAsXapian::ActsAsXapianJob|Action" +msgstr "" + +msgid "ActsAsXapian::ActsAsXapianJob|Model" +msgstr "" + +msgid "Add an annotation" +msgstr "" + +msgid "Add an annotation to your request with choice quotes, or\\n a summary of the response." +msgstr "" + +msgid "Added on {{date}}" +msgstr "" + +msgid "Admin level is not included in list" +msgstr "" + +msgid "Administration URL:" +msgstr "" + +msgid "Advanced search" +msgstr "" + +msgid "Advanced search tips" +msgstr "" + +msgid "Advise on whether the refusal is legal, and how to complain about it if not." +msgstr "" + +msgid "Air, water, soil, land, flora and fauna (including how these effect\\n human beings)" +msgstr "" + +msgid "All of the information requested has been received" +msgstr "" + +msgid "All the options below can use status or latest_status before the colon. For example, status:not_held will match requests which have ever been marked as not held; latest_status:not_held will match only requests that are currently marked as not held." +msgstr "" + +msgid "All the options below can use variety or latest_variety before the colon. For example, variety:sent will match requests which have ever been sent; latest_variety:sent will match only requests that are currently marked as sent." +msgstr "" + +msgid "Also called {{other_name}}." +msgstr "" + +msgid "Also send me alerts by email" +msgstr "" + +msgid "Alter your subscription" +msgstr "" + +msgid "Although all responses are automatically published, we depend on\\nyou, the original requester, to evaluate them." +msgstr "" + +msgid "An annotation to {{request_title}} was made by {{event_comment_user}} on {{date}}" +msgstr "" + +msgid "An error message has been received" +msgstr "" + +msgid "An Environmental Information Regulations request" +msgstr "" + +msgid "An anonymous user" +msgstr "" + +msgid "Annotation added to request" +msgstr "" + +msgid "Annotations" +msgstr "" + +msgid "Annotations are so anyone, including you, can help the requester with their request. For example:" +msgstr "" + +msgid "Annotations will be posted publicly here, and are\\n not sent to {{public_body_name}}." +msgstr "" + +msgid "Anonymous user" +msgstr "" + +msgid "Anyone:" +msgstr "" + +msgid "Applies to" +msgstr "" + +msgid "Are we missing a public authority?" +msgstr "" + +msgid "Are you the owner of any commercial copyright on this page?" +msgstr "" + +msgid "Ask for specific documents or information, this site is not suitable for general enquiries." +msgstr "" + +msgid "At the bottom of this page, write a reply to them trying to persuade them to scan it in\\n (more details)." +msgstr "" + +msgid "Attachment (optional):" +msgstr "" + +msgid "Attachment:" +msgstr "" + +msgid "Awaiting classification." +msgstr "" + +msgid "Awaiting internal review." +msgstr "" + +msgid "Awaiting response." +msgstr "" + +msgid "Beginning with" +msgstr "" + +msgid "Browse other requests for examples of how to word your request." +msgstr "" + +msgid "Browse other requests to '{{public_body_name}}' for examples of how to word your request." +msgstr "" + +msgid "Browse all authorities..." +msgstr "" + +msgid "By law, under all circumstances, {{public_body_link}} should have responded by now" +msgstr "" + +msgid "By law, {{public_body_link}} should normally have responded promptly and" +msgstr "" + +msgid "Calculated home page" +msgstr "" + +msgid "Can't find the one you want?" +msgstr "" + +msgid "Cancel a {{site_name}} alert" +msgstr "" + +msgid "Cancel some {{site_name}} alerts" +msgstr "" + +msgid "Cancel, return to your profile page" +msgstr "" + +msgid "Censor rule" +msgstr "" + +msgid "CensorRule|Last edit comment" +msgstr "" + +msgid "CensorRule|Last edit editor" +msgstr "" + +msgid "CensorRule|Regexp" +msgstr "" + +msgid "CensorRule|Replacement" +msgstr "" + +msgid "CensorRule|Text" +msgstr "" + +msgid "Change email on {{site_name}}" +msgstr "" + +msgid "Change password on {{site_name}}" +msgstr "" + +msgid "Change profile photo" +msgstr "" + +msgid "Change the text about you on your profile at {{site_name}}" +msgstr "" + +msgid "Change your email" +msgstr "" + +msgid "Change your email address used on {{site_name}}" +msgstr "" + +msgid "Change your password" +msgstr "" + +msgid "Change your password on {{site_name}}" +msgstr "" + +msgid "Change your password {{site_name}}" +msgstr "" + +msgid "Charity registration" +msgstr "" + +msgid "Check for mistakes if you typed or copied the address." +msgstr "" + +msgid "Check you haven't included any personal information." +msgstr "" + +msgid "Choose your profile photo" +msgstr "" + +msgid "Clarification" +msgstr "" + +msgid "Clarify your FOI request - " +msgstr "" + +msgid "Classify an FOI response from " +msgstr "" + +msgid "Clear photo" +msgstr "" + +msgid "Click on the link below to send a message to {{public_body_name}} telling them to reply to your request. You might like to ask for an internal\\nreview, asking them to find out why response to the request has been so slow." +msgstr "" + +msgid "Click on the link below to send a message to {{public_body}} reminding them to reply to your request." +msgstr "" + +msgid "Close" +msgstr "" + +msgid "Comment" +msgstr "" + +msgid "Comment|Body" +msgstr "" + +msgid "Comment|Comment type" +msgstr "" + +msgid "Comment|Locale" +msgstr "" + +msgid "Comment|Visible" +msgstr "" + +msgid "Confirm you want to follow all successful FOI requests" +msgstr "" + +msgid "Confirm you want to follow new requests" +msgstr "" + +msgid "Confirm you want to follow new requests or responses matching your search" +msgstr "" + +msgid "Confirm you want to follow requests by '{{user_name}}'" +msgstr "" + +msgid "Confirm you want to follow requests to '{{public_body_name}}'" +msgstr "" + +msgid "Confirm you want to follow the request '{{request_title}}'" +msgstr "" + +msgid "Confirm your FOI request to " +msgstr "" + +msgid "Confirm your account on {{site_name}}" +msgstr "" + +msgid "Confirm your annotation to {{info_request_title}}" +msgstr "" + +msgid "Confirm your email address" +msgstr "" + +msgid "Confirm your new email address on {{site_name}}" +msgstr "" + +msgid "Considered by administrators as not an FOI request and hidden from site." +msgstr "" + +msgid "Considered by administrators as vexatious and hidden from site." +msgstr "" + +msgid "Contact {{recipient}}" +msgstr "" + +msgid "Contact {{site_name}}" +msgstr "" + +msgid "Could not identify the request from the email address" +msgstr "" + +msgid "Couldn't understand the image file that you uploaded. PNG, JPEG, GIF and many other common image file formats are supported." +msgstr "" + +msgid "Crop your profile photo" +msgstr "" + +msgid "Cultural sites and built structures (as they may be affected by the\\n environmental factors listed above)" +msgstr "" + +msgid "Currently waiting for a response from {{public_body_link}}, they must respond promptly and" +msgstr "" + +msgid "Date:" +msgstr "" + +msgid "Dear {{name}}," +msgstr "" + +msgid "Dear {{public_body_name}}," +msgstr "" + +msgid "Default locale" +msgstr "" + +msgid "Defunct." +msgstr "" + +msgid "Delayed response to your FOI request - " +msgstr "" + +msgid "Delayed." +msgstr "" + +msgid "Delivery error" +msgstr "" + +msgid "Destroy {{name}}" +msgstr "" + +msgid "Details of request '" +msgstr "" + +msgid "Did you mean: {{correction}}" +msgstr "" + +msgid "Disclaimer: This message and any reply that you make will be published on the internet. Our privacy and copyright policies:" +msgstr "" + +msgid "Disclosure log" +msgstr "" + +msgid "Disclosure log URL" +msgstr "" + +msgid "Don't want to address your message to {{person_or_body}}? You can also write to:" +msgstr "" + +msgid "Done" +msgstr "" + +msgid "Done >>" +msgstr "" + +msgid "Download a zip file of all correspondence" +msgstr "" + +msgid "Download original attachment" +msgstr "" + +msgid "EIR" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit and add more details to the message above,\\n explaining why you are dissatisfied with their response." +msgstr "" + +msgid "Edit text about you" +msgstr "" + +msgid "Edit this request" +msgstr "" + +msgid "Either the email or password was not recognised, please try again." +msgstr "" + +msgid "Either the email or password was not recognised, please try again. Or create a new account using the form on the right." +msgstr "" + +msgid "Email doesn't look like a valid address" +msgstr "" + +msgid "Email me future updates to this request" +msgstr "" + +msgid "Enter words that you want to find separated by spaces, e.g. climbing lane" +msgstr "" + +msgid "Enter your response below. You may attach one file (use email, or\\n contact us if you need more)." +msgstr "" + +msgid "Environmental Information Regulations" +msgstr "" + +msgid "Environmental Information Regulations requests made" +msgstr "" + +msgid "Environmental Information Regulations requests made using this site" +msgstr "" + +msgid "Event history" +msgstr "" + +msgid "Event history details" +msgstr "" + +msgid "Event {{id}}" +msgstr "" + +msgid "Everything that you enter on this page, including your name,\\n will be displayed publicly on\\n this website forever (why?)." +msgstr "" + +msgid "Everything that you enter on this page\\n will be displayed publicly on\\n this website forever (why?)." +msgstr "" + +msgid "FOI" +msgstr "" + +msgid "FOI email address for {{public_body}}" +msgstr "" + +msgid "FOI request – {{title}}" +msgstr "" + +msgid "FOI requests" +msgstr "" + +msgid "FOI requests by '{{user_name}}'" +msgstr "" + +msgid "FOI requests {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "" + +msgid "FOI response requires admin ({{reason}}) - {{title}}" +msgstr "" + +msgid "Failed to convert image to a PNG" +msgstr "" + +msgid "Failed to convert image to the correct size: at {{cols}}x{{rows}}, need {{width}}x{{height}}" +msgstr "" + +msgid "Filter" +msgstr "" + +msgid "First, did your other requests succeed?" +msgstr "" + +msgid "First, type in the name of the UK public authority you'd\\n like information from. By law, they have to respond\\n (why?)." +msgstr "" + +msgid "Foi attachment" +msgstr "" + +msgid "FoiAttachment|Charset" +msgstr "" + +msgid "FoiAttachment|Content type" +msgstr "" + +msgid "FoiAttachment|Display size" +msgstr "" + +msgid "FoiAttachment|Filename" +msgstr "" + +msgid "FoiAttachment|Hexdigest" +msgstr "" + +msgid "FoiAttachment|Url part number" +msgstr "" + +msgid "FoiAttachment|Within rfc822 subject" +msgstr "" + +msgid "Follow" +msgstr "" + +msgid "Follow all new requests" +msgstr "" + +msgid "Follow new successful responses" +msgstr "" + +msgid "Follow requests to {{public_body_name}}" +msgstr "" + +msgid "Follow these requests" +msgstr "" + +msgid "Follow things matching this search" +msgstr "" + +msgid "Follow this authority" +msgstr "" + +msgid "Follow this link to see the request:" +msgstr "" + +msgid "Follow this person" +msgstr "" + +msgid "Follow this request" +msgstr "" + +#. "Follow up" in this context means a further +#. message sent by the requester to the authority after +#. the initial request +msgid "Follow up" +msgstr "" + +#. "Follow up message" in this context means a +#. further message sent by the requester to the authority after +#. the initial request +msgid "Follow up message sent by requester" +msgstr "" + +msgid "Follow up messages to existing requests are sent to " +msgstr "" + +#. "Follow ups" in this context means further +#. messages sent by the requester to the authority after +#. the initial request +msgid "Follow ups and new responses to this request have been stopped to prevent spam. Please contact us if you are {{user_link}} and need to send a follow up." +msgstr "" + +msgid "Follow us on twitter" +msgstr "" + +msgid "Followups cannot be sent for this request, as it was made externally, and published here by {{public_body_name}} on the requester's behalf." +msgstr "" + +msgid "For an unknown reason, it is not possible to make a request to this authority." +msgstr "" + +msgid "Forgotten your password?" +msgstr "" + +msgid "Found {{count}} public authority {{description}}" +msgid_plural "Found {{count}} public authorities {{description}}" +msgstr[0] "" + +msgid "Freedom of Information" +msgstr "" + +msgid "Freedom of Information Act" +msgstr "" + +msgid "Freedom of Information law does not apply to this authority, so you cannot make\\n a request to it." +msgstr "" + +msgid "Freedom of Information law no longer applies to" +msgstr "" + +msgid "Freedom of Information law no longer applies to this authority.Follow up messages to existing requests are sent to " +msgstr "" + +msgid "Freedom of Information requests made" +msgstr "" + +msgid "Freedom of Information requests made by this person" +msgstr "" + +msgid "Freedom of Information requests made by you" +msgstr "" + +msgid "Freedom of Information requests made using this site" +msgstr "" + +msgid "Freedom of information requests to" +msgstr "" + +msgid "From" +msgstr "" + +msgid "From the request page, try replying to a particular message, rather than sending\\n a general followup. If you need to make a general followup, and know\\n an email which will go to the right place, please send it to us." +msgstr "" + +msgid "From:" +msgstr "" + +msgid "GIVE DETAILS ABOUT YOUR COMPLAINT HERE" +msgstr "" + +msgid "Handled by post." +msgstr "" + +msgid "Has tag string/has tag string tag" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Model" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Name" +msgstr "" + +msgid "HasTagString::HasTagStringTag|Value" +msgstr "" + +msgid "Hello! You can make Freedom of Information requests within {{country_name}} at {{link_to_website}}" +msgstr "" + +msgid "Hello, {{username}}!" +msgstr "" + +msgid "Help" +msgstr "" + +msgid "Here described means when a user selected a status for the request, and\\nthe most recent event had its status updated to that value. calculated is then inferred by\\n{{site_name}} for intermediate events, which weren't given an explicit\\ndescription by a user. See the search tips for description of the states." +msgstr "" + +msgid "Here is the message you wrote, in case you would like to copy the text and save it for later." +msgstr "" + +msgid "Hi! We need your help. The person who made the following request\\n hasn't told us whether or not it was successful. Would you mind taking\\n a moment to read it and help us keep the place tidy for everyone?\\n Thanks." +msgstr "" + +msgid "Hide request" +msgstr "" + +msgid "Holiday" +msgstr "" + +msgid "Holiday|Day" +msgstr "" + +msgid "Holiday|Description" +msgstr "" + +msgid "Home" +msgstr "" + +msgid "Home page" +msgstr "" + +msgid "Home page of authority" +msgstr "" + +msgid "However, you have the right to request environmental\\n information under a different law" +msgstr "" + +msgid "Human health and safety" +msgstr "" + +msgid "I am asking for new information" +msgstr "" + +msgid "I am requesting an internal review" +msgstr "" + +msgid "I am writing to request an internal review of {{public_body_name}}'s handling of my FOI request '{{info_request_title}}'." +msgstr "" + +msgid "I don't like these ones — give me some more!" +msgstr "" + +msgid "I don't want to do any more tidying now!" +msgstr "" + +msgid "I like this request" +msgstr "" + +msgid "I would like to withdraw this request" +msgstr "" + +msgid "I'm still waiting for my information\\n (maybe you got an acknowledgement)" +msgstr "" + +msgid "I'm still waiting for the internal review" +msgstr "" + +msgid "I'm waiting for an internal review response" +msgstr "" + +msgid "I've been asked to clarify my request" +msgstr "" + +msgid "I've received all the information" +msgstr "" + +msgid "I've received some of the information" +msgstr "" + +msgid "I've received an error message" +msgstr "" + +msgid "I've received an error message" +msgstr "" + +msgid "Id" +msgstr "" + +msgid "If the address is wrong, or you know a better address, please contact us." +msgstr "" + +msgid "If the error was a delivery failure, and you can find an up to date FOI email address for the authority, please tell us using the form below." +msgstr "" + +msgid "If this is incorrect, or you would like to send a late response to the request\\nor an email on another subject to {{user}}, then please\\nemail {{contact_email}} for help." +msgstr "" + +msgid "If you are dissatisfied by the response you got from\\n the public authority, you have the right to\\n complain (details)." +msgstr "" + +msgid "If you are still having trouble, please contact us." +msgstr "" + +msgid "If you are the requester, then you may sign in to view the message." +msgstr "" + +msgid "If you are the requester, then you may sign in to view the request." +msgstr "" + +msgid "If you are thinking of using a pseudonym,\\n please read this first." +msgstr "" + +msgid "If you are {{user_link}}, please" +msgstr "" + +msgid "If you believe this request is not suitable, you can report it for attention by the site administrators" +msgstr "" + +msgid "If you can't click on it in the email, you'll have to select and copy\\nit from the email. Then paste it into your browser, into the place\\nyou would type the address of any other webpage." +msgstr "" + +msgid "If you can, scan in or photograph the response, and send us\\n a copy to upload." +msgstr "" + +msgid "If you find this service useful as an FOI officer, please ask your web manager to link to us from your organisation's FOI page." +msgstr "" + +msgid "If you got the email more than six months ago, then this login link won't work any\\nmore. Please try doing what you were doing from the beginning." +msgstr "" + +msgid "If you have not done so already, please write a message below telling the authority that you have withdrawn your request. Otherwise they will not know it has been withdrawn." +msgstr "" + +msgid "If you reply to this message it will go directly to {{user_name}}, who will\\nlearn your email address. Only reply if that is okay." +msgstr "" + +msgid "If you use web-based email or have \"junk mail\" filters, also check your\\nbulk/spam mail folders. Sometimes, our messages are marked that way." +msgstr "" + +msgid "If you would like us to lift this ban, then you may politely\\ncontact us giving reasons.\\n" +msgstr "" + +msgid "If you're new to {{site_name}}" +msgstr "" + +msgid "If you've used {{site_name}} before" +msgstr "" + +msgid "If your browser is set to accept cookies and you are seeing this message,\\nthen there is probably a fault with our server." +msgstr "" + +msgid "Incoming email address" +msgstr "" + +msgid "Incoming message" +msgstr "" + +msgid "IncomingMessage|Cached attachment text clipped" +msgstr "" + +msgid "IncomingMessage|Cached main body text folded" +msgstr "" + +msgid "IncomingMessage|Cached main body text unfolded" +msgstr "" + +msgid "IncomingMessage|Last parsed" +msgstr "" + +msgid "IncomingMessage|Mail from" +msgstr "" + +msgid "IncomingMessage|Mail from domain" +msgstr "" + +msgid "IncomingMessage|Prominence" +msgstr "" + +msgid "IncomingMessage|Prominence reason" +msgstr "" + +msgid "IncomingMessage|Sent at" +msgstr "" + +msgid "IncomingMessage|Subject" +msgstr "" + +msgid "IncomingMessage|Valid to reply to" +msgstr "" + +msgid "Individual requests" +msgstr "" + +msgid "Info request" +msgstr "" + +msgid "Info request event" +msgstr "" + +msgid "InfoRequestEvent|Calculated state" +msgstr "" + +msgid "InfoRequestEvent|Described state" +msgstr "" + +msgid "InfoRequestEvent|Event type" +msgstr "" + +msgid "InfoRequestEvent|Last described at" +msgstr "" + +msgid "InfoRequestEvent|Params yaml" +msgstr "" + +msgid "InfoRequest|Allow new responses from" +msgstr "" + +msgid "InfoRequest|Attention requested" +msgstr "" + +msgid "InfoRequest|Awaiting description" +msgstr "" + +msgid "InfoRequest|Comments allowed" +msgstr "" + +msgid "InfoRequest|Described state" +msgstr "" + +msgid "InfoRequest|External url" +msgstr "" + +msgid "InfoRequest|External user name" +msgstr "" + +msgid "InfoRequest|Handle rejected responses" +msgstr "" + +msgid "InfoRequest|Idhash" +msgstr "" + +msgid "InfoRequest|Law used" +msgstr "" + +msgid "InfoRequest|Prominence" +msgstr "" + +msgid "InfoRequest|Title" +msgstr "" + +msgid "InfoRequest|Url title" +msgstr "" + +msgid "Information not held." +msgstr "" + +msgid "Information on emissions and discharges (e.g. noise, energy,\\n radiation, waste materials)" +msgstr "" + +msgid "Internal review request" +msgstr "" + +msgid "Is {{email_address}} the wrong address for {{type_of_request}} requests to {{public_body_name}}? If so, please contact us using this form:" +msgstr "" + +msgid "It may be that your browser is not set to accept a thing called \"cookies\",\\nor cannot do so. If you can, please enable cookies, or try using a different\\nbrowser. Then press refresh to have another go." +msgstr "" + +msgid "Items matching the following conditions are currently displayed on your wall." +msgstr "" + +msgid "Items sent in last month" +msgstr "" + +msgid "Joined in" +msgstr "" + +msgid "Joined {{site_name}} in" +msgstr "" + +msgid "Just one more thing" +msgstr "" + +msgid "Keep it focused, you'll be more likely to get what you want (why?)." +msgstr "" + +msgid "Keywords" +msgstr "" + +msgid "Last authority viewed: " +msgstr "" + +msgid "Last request viewed: " +msgstr "" + +msgid "Let us know what you were doing when this message\\nappeared and your browser and operating system type and version." +msgstr "" + +msgid "Link to this" +msgstr "" + +msgid "List all" +msgstr "" + +msgid "List of all authorities (CSV)" +msgstr "" + +msgid "Listing FOI requests" +msgstr "" + +msgid "Listing public authorities" +msgstr "" + +msgid "Listing public authorities matching '{{query}}'" +msgstr "" + +msgid "Listing tracks" +msgstr "" + +msgid "Listing users" +msgstr "" + +msgid "Log in to download a zip file of {{info_request_title}}" +msgstr "" + +msgid "Log into the admin interface" +msgstr "" + +msgid "Long overdue." +msgstr "" + +msgid "Made between" +msgstr "" + +msgid "Mail server log" +msgstr "" + +msgid "Mail server log done" +msgstr "" + +msgid "MailServerLogDone|Filename" +msgstr "" + +msgid "MailServerLogDone|Last stat" +msgstr "" + +msgid "MailServerLog|Line" +msgstr "" + +msgid "MailServerLog|Order" +msgstr "" + +msgid "Make a new
    \\n Freedom of
    \\n Information
    \\n request
    " +msgstr "" + +msgid "Make a request" +msgstr "" + +msgid "Make a request to this authority" +msgstr "" + +msgid "Make an {{law_used_short}} request to '{{public_body_name}}'" +msgstr "" + +msgid "Make and browse Freedom of Information (FOI) requests" +msgstr "" + +msgid "Make your own request" +msgstr "" + +msgid "Many requests" +msgstr "" + +msgid "Message" +msgstr "" + +msgid "Message has been removed" +msgstr "" + +msgid "Message sent using {{site_name}} contact form, " +msgstr "" + +msgid "Missing contact details for '" +msgstr "" + +msgid "More about this authority" +msgstr "" + +msgid "More requests..." +msgstr "" + +msgid "More similar requests" +msgstr "" + +msgid "More successful requests..." +msgstr "" + +msgid "My profile" +msgstr "" + +msgid "My request has been refused" +msgstr "" + +msgid "My requests" +msgstr "" + +msgid "My wall" +msgstr "" + +msgid "Name can't be blank" +msgstr "" + +msgid "Name is already taken" +msgstr "" + +msgid "New Freedom of Information requests" +msgstr "" + +msgid "New censor rule" +msgstr "" + +msgid "New e-mail:" +msgstr "" + +msgid "New email doesn't look like a valid address" +msgstr "" + +msgid "New password:" +msgstr "" + +msgid "New password: (again)" +msgstr "" + +msgid "New response to '{{title}}'" +msgstr "" + +msgid "New response to your FOI request - " +msgstr "" + +msgid "New response to your request" +msgstr "" + +msgid "New response to {{law_used_short}} request" +msgstr "" + +msgid "New updates for the request '{{request_title}}'" +msgstr "" + +msgid "Newest results first" +msgstr "" + +msgid "Next" +msgstr "" + +msgid "Next, crop your photo >>" +msgstr "" + +msgid "No requests of this sort yet." +msgstr "" + +msgid "No results found." +msgstr "" + +msgid "No similar requests found." +msgstr "" + +msgid "No tracked things found." +msgstr "" + +msgid "Nobody has made any Freedom of Information requests to {{public_body_name}} using this site yet." +msgstr "" + +msgid "None found." +msgstr "" + +msgid "None made." +msgstr "" + +msgid "Not a valid FOI request" +msgstr "" + +msgid "Note that the requester will not be notified about your annotation, because the request was published by {{public_body_name}} on their behalf." +msgstr "" + +msgid "Now check your email!" +msgstr "" + +msgid "Now preview your annotation" +msgstr "" + +msgid "Now preview your follow up" +msgstr "" + +msgid "Now preview your message asking for an internal review" +msgstr "" + +msgid "Number of requests" +msgstr "" + +msgid "OR remove the existing photo" +msgstr "" + +msgid "Offensive? Unsuitable?" +msgstr "" + +msgid "Oh no! Sorry to hear that your request was refused. Here is what to do now." +msgstr "" + +msgid "Old e-mail:" +msgstr "" + +msgid "Old email address isn't the same as the address of the account you are logged in with" +msgstr "" + +msgid "Old email doesn't look like a valid address" +msgstr "" + +msgid "On this page" +msgstr "" + +msgid "One FOI request found" +msgstr "" + +msgid "One person found" +msgstr "" + +msgid "One public authority found" +msgstr "" + +msgid "Only put in abbreviations which are really used, otherwise leave blank. Short or long name is used in the URL – don't worry about breaking URLs through renaming, as the history is used to redirect" +msgstr "" + +msgid "Only requests made using {{site_name}} are shown." +msgstr "" + +msgid "Only the authority can reply to this request, and I don't recognise the address this reply was sent from" +msgstr "" + +msgid "Only the authority can reply to this request, but there is no \"From\" address to check against" +msgstr "" + +msgid "Or search in their website for this information." +msgstr "" + +msgid "Original request sent" +msgstr "" + +msgid "Other:" +msgstr "" + +msgid "Outgoing message" +msgstr "" + +msgid "OutgoingMessage|Body" +msgstr "" + +msgid "OutgoingMessage|Last sent at" +msgstr "" + +msgid "OutgoingMessage|Message type" +msgstr "" + +msgid "OutgoingMessage|Prominence" +msgstr "" + +msgid "OutgoingMessage|Prominence reason" +msgstr "" + +msgid "OutgoingMessage|Status" +msgstr "" + +msgid "OutgoingMessage|What doing" +msgstr "" + +msgid "Partially successful." +msgstr "" + +msgid "Password is not correct" +msgstr "" + +msgid "Password:" +msgstr "" + +msgid "Password: (again)" +msgstr "" + +msgid "Paste this link into emails, tweets, and anywhere else:" +msgstr "" + +msgid "People" +msgstr "" + +msgid "People {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "" + +msgid "Percentage of requests that are overdue" +msgstr "" + +msgid "Percentage of total requests" +msgstr "" + +msgid "Photo of you:" +msgstr "" + +msgid "Plans and administrative measures that affect these matters" +msgstr "" + +msgid "Play the request categorisation game" +msgstr "" + +msgid "Play the request categorisation game!" +msgstr "" + +msgid "Please" +msgstr "" + +msgid "Please contact us if you have any questions." +msgstr "" + +msgid "Please get in touch with us so we can fix it." +msgstr "" + +msgid "Please answer the question above so we know whether the " +msgstr "" + +msgid "Please go to the following requests, and let us\\n know if there was information in the recent responses to them." +msgstr "" + +msgid "Please only write messages directly relating to your request {{request_link}}. If you would like to ask for information that was not in your original request, then file a new request." +msgstr "" + +msgid "Please ask for environmental information only" +msgstr "" + +msgid "Please check the URL (i.e. the long code of letters and numbers) is copied\\ncorrectly from your email." +msgstr "" + +msgid "Please choose a file containing your photo." +msgstr "" + +msgid "Please choose a reason" +msgstr "" + +msgid "Please choose what sort of reply you are making." +msgstr "" + +msgid "Please choose whether or not you got some of the information that you wanted." +msgstr "" + +msgid "Please click on the link below to cancel or alter these emails." +msgstr "" + +msgid "Please click on the link below to confirm that you want to \\nchange the email address that you use for {{site_name}}\\nfrom {{old_email}} to {{new_email}}" +msgstr "" + +msgid "Please click on the link below to confirm your email address." +msgstr "" + +msgid "Please describe more what the request is about in the subject. There is no need to say it is an FOI request, we add that on anyway." +msgstr "" + +msgid "Please don't upload offensive pictures. We will take down images\\n that we consider inappropriate." +msgstr "" + +msgid "Please enable \"cookies\" to carry on" +msgstr "" + +msgid "Please enter a password" +msgstr "" + +msgid "Please enter a subject" +msgstr "" + +msgid "Please enter a summary of your request" +msgstr "" + +msgid "Please enter a valid email address" +msgstr "" + +msgid "Please enter the message you want to send" +msgstr "" + +msgid "Please enter the same password twice" +msgstr "" + +msgid "Please enter your annotation" +msgstr "" + +msgid "Please enter your email address" +msgstr "" + +msgid "Please enter your follow up message" +msgstr "" + +msgid "Please enter your letter requesting information" +msgstr "" + +msgid "Please enter your name" +msgstr "" + +msgid "Please enter your name, not your email address, in the name field." +msgstr "" + +msgid "Please enter your new email address" +msgstr "" + +msgid "Please enter your old email address" +msgstr "" + +msgid "Please enter your password" +msgstr "" + +msgid "Please give details explaining why you want a review" +msgstr "" + +msgid "Please keep it shorter than 500 characters" +msgstr "" + +msgid "Please keep the summary short, like in the subject of an email. You can use a phrase, rather than a full sentence." +msgstr "" + +msgid "Please only request information that comes under those categories, do not waste your\\n time or the time of the public authority by requesting unrelated information." +msgstr "" + +msgid "Please pass this on to the person who conducts Freedom of Information reviews." +msgstr "" + +msgid "Please select each of these requests in turn, and let everyone know\\nif they are successful yet or not." +msgstr "" + +msgid "Please sign at the bottom with your name, or alter the \"{{signoff}}\" signature" +msgstr "" + +msgid "Please sign in as " +msgstr "" + +msgid "Please sign in or make a new account." +msgstr "" + +msgid "Please type a message and/or choose a file containing your response." +msgstr "" + +msgid "Please use this email address for all replies to this request:" +msgstr "" + +msgid "Please write a summary with some text in it" +msgstr "" + +msgid "Please write the summary using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Please write your annotation using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Please write your follow up message containing the necessary clarifications below." +msgstr "" + +msgid "Please write your message using a mixture of capital and lower case letters. This makes it easier for others to read." +msgstr "" + +msgid "Point to related information, campaigns or forums which may be useful." +msgstr "" + +msgid "Possibly related requests:" +msgstr "" + +msgid "Post annotation" +msgstr "" + +msgid "Post redirect" +msgstr "" + +msgid "PostRedirect|Circumstance" +msgstr "" + +msgid "PostRedirect|Email token" +msgstr "" + +msgid "PostRedirect|Post params yaml" +msgstr "" + +msgid "PostRedirect|Reason params yaml" +msgstr "" + +msgid "PostRedirect|Token" +msgstr "" + +msgid "PostRedirect|Uri" +msgstr "" + +msgid "Posted on {{date}} by {{author}}" +msgstr "" + +msgid "Powered by Alaveteli" +msgstr "" + +msgid "Prev" +msgstr "" + +msgid "Preview follow up to '" +msgstr "" + +msgid "Preview new annotation on '{{info_request_title}}'" +msgstr "" + +msgid "Preview your annotation" +msgstr "" + +msgid "Preview your message" +msgstr "" + +msgid "Preview your public request" +msgstr "" + +msgid "Profile photo" +msgstr "" + +msgid "ProfilePhoto|Data" +msgstr "" + +msgid "ProfilePhoto|Draft" +msgstr "" + +msgid "Public Bodies" +msgstr "" + +msgid "Public Body Statistics" +msgstr "" + +msgid "Public authorities" +msgstr "" + +msgid "Public authorities - {{description}}" +msgstr "" + +msgid "Public authorities {{start_count}} to {{end_count}} of {{total_count}}" +msgstr "" + +msgid "Public authority – {{name}}" +msgstr "" + +msgid "Public bodies that most frequently replied with \"Not Held\"" +msgstr "" + +msgid "Public bodies with most overdue requests" +msgstr "" + +msgid "Public bodies with the fewest successful requests" +msgstr "" + +msgid "Public bodies with the most requests" +msgstr "" + +msgid "Public bodies with the most successful requests" +msgstr "" + +msgid "Public body" +msgstr "" + +msgid "Public notes" +msgstr "" + +msgid "Public page" +msgstr "" + +msgid "Public page not available" +msgstr "" + +msgid "PublicBody|Api key" +msgstr "" + +msgid "PublicBody|Disclosure log" +msgstr "" + +msgid "PublicBody|First letter" +msgstr "" + +msgid "PublicBody|Home page" +msgstr "" + +msgid "PublicBody|Info requests count" +msgstr "" + +msgid "PublicBody|Info requests not held count" +msgstr "" + +msgid "PublicBody|Info requests overdue count" +msgstr "" + +msgid "PublicBody|Info requests successful count" +msgstr "" + +msgid "PublicBody|Info requests visible classified count" +msgstr "" + +msgid "PublicBody|Last edit comment" +msgstr "" + +msgid "PublicBody|Last edit editor" +msgstr "" + +msgid "PublicBody|Name" +msgstr "" + +msgid "PublicBody|Notes" +msgstr "" + +msgid "PublicBody|Publication scheme" +msgstr "" + +msgid "PublicBody|Request email" +msgstr "" + +msgid "PublicBody|Short name" +msgstr "" + +msgid "PublicBody|Url name" +msgstr "" + +msgid "PublicBody|Version" +msgstr "" + +msgid "Publication scheme" +msgstr "" + +msgid "Publication scheme URL" +msgstr "" + +msgid "Purge request" +msgstr "" + +msgid "PurgeRequest|Model" +msgstr "" + +msgid "PurgeRequest|Url" +msgstr "" + +msgid "RSS feed" +msgstr "" + +msgid "RSS feed of updates" +msgstr "" + +msgid "Re-edit this annotation" +msgstr "" + +msgid "Re-edit this message" +msgstr "" + +msgid "Read about advanced search operators, such as proximity and wildcards." +msgstr "" + +msgid "Read blog" +msgstr "" + +msgid "Received an error message, such as delivery failure." +msgstr "" + +msgid "Recently described results first" +msgstr "" + +msgid "Refused." +msgstr "" + +msgid "Remember me (keeps you signed in longer;\\n do not use on a public computer) " +msgstr "" + +msgid "Report abuse" +msgstr "" + +msgid "Report an offensive or unsuitable request" +msgstr "" + +msgid "Report request" +msgstr "" + +msgid "Report this request" +msgstr "" + +msgid "Reported for administrator attention." +msgstr "" + +msgid "Request an internal review" +msgstr "" + +msgid "Request an internal review from {{person_or_body}}" +msgstr "" + +msgid "Request email" +msgstr "" + +msgid "Request has been removed" +msgstr "" + +msgid "Request sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "Request to {{public_body_name}} by {{info_request_user}}. Annotated by {{event_comment_user}} on {{date}}." +msgstr "" + +msgid "Requested from {{public_body_name}} by {{info_request_user}} on {{date}}" +msgstr "" + +msgid "Requested on {{date}}" +msgstr "" + +msgid "Requests are considered overdue if they are in the 'Overdue' or 'Very Overdue' states." +msgstr "" + +msgid "Requests are considered successful if they were classified as either 'Successful' or 'Partially Successful'." +msgstr "" + +msgid "Requests for personal information and vexatious requests are not considered valid for FOI purposes (read more)." +msgstr "" + +msgid "Requests or responses matching your saved search" +msgstr "" + +msgid "Requests similar to '{{request_title}}'" +msgstr "" + +msgid "Requests similar to '{{request_title}}' (page {{page}})" +msgstr "" + +msgid "Respond by email" +msgstr "" + +msgid "Respond to request" +msgstr "" + +msgid "Respond to the FOI request" +msgstr "" + +msgid "Respond using the web" +msgstr "" + +msgid "Response" +msgstr "" + +msgid "Response from a public authority" +msgstr "" + +msgid "Response to '{{title}}'" +msgstr "" + +msgid "Response to this request is delayed." +msgstr "" + +msgid "Response to this request is long overdue." +msgstr "" + +msgid "Response to your request" +msgstr "" + +msgid "Response:" +msgstr "" + +msgid "Restrict to" +msgstr "" + +msgid "Results page {{page_number}}" +msgstr "" + +msgid "Save" +msgstr "" + +msgid "Search" +msgstr "" + +msgid "Search Freedom of Information requests, public authorities and users" +msgstr "" + +msgid "Search contributions by this person" +msgstr "" + +msgid "Search for words in:" +msgstr "" + +msgid "Search in" +msgstr "" + +msgid "Search over
    \\n {{number_of_requests}} requests and
    \\n {{number_of_authorities}} authorities" +msgstr "" + +msgid "Search queries" +msgstr "" + +msgid "Search results" +msgstr "" + +msgid "Search the site to find what you were looking for." +msgstr "" + +msgid "Search within the {{count}} Freedom of Information requests to {{public_body_name}}" +msgid_plural "Search within the {{count}} Freedom of Information requests made to {{public_body_name}}" +msgstr[0] "" + +msgid "Search your contributions" +msgstr "" + +msgid "See bounce message" +msgstr "" + +msgid "Select one to see more information about the authority." +msgstr "" + +msgid "Select the authority to write to" +msgstr "" + +msgid "Send a followup" +msgstr "" + +msgid "Send a message to " +msgstr "" + +msgid "Send a public follow up message to {{person_or_body}}" +msgstr "" + +msgid "Send a public reply to {{person_or_body}}" +msgstr "" + +msgid "Send follow up to '{{title}}'" +msgstr "" + +msgid "Send message" +msgstr "" + +msgid "Send message to " +msgstr "" + +msgid "Send request" +msgstr "" + +msgid "Set your profile photo" +msgstr "" + +msgid "Short name" +msgstr "" + +msgid "Short name is already taken" +msgstr "" + +msgid "Show most relevant results first" +msgstr "" + +msgid "Show only..." +msgstr "" + +msgid "Showing" +msgstr "" + +msgid "Sign in" +msgstr "" + +msgid "Sign in or make a new account" +msgstr "" + +msgid "Sign in or sign up" +msgstr "" + +msgid "Sign out" +msgstr "" + +msgid "Sign up" +msgstr "" + +msgid "Similar requests" +msgstr "" + +msgid "Simple search" +msgstr "" + +msgid "Some notes have been added to your FOI request - " +msgstr "" + +msgid "Some of the information requested has been received" +msgstr "" + +msgid "Some people who've made requests haven't let us know whether they were\\nsuccessful or not. We need your help –\\nchoose one of these requests, read it, and let everyone know whether or not the\\ninformation has been provided. Everyone'll be exceedingly grateful." +msgstr "" + +msgid "Somebody added a note to your FOI request - " +msgstr "" + +msgid "Someone has updated the status of your request" +msgstr "" + +msgid "Someone, perhaps you, just tried to change their email address on\\n{{site_name}} from {{old_email}} to {{new_email}}." +msgstr "" + +msgid "Sorry - you cannot respond to this request via {{site_name}}, because this is a copy of the request originally at {{link_to_original_request}}." +msgstr "" + +msgid "Sorry, but only {{user_name}} is allowed to do that." +msgstr "" + +msgid "Sorry, there was a problem processing this page" +msgstr "" + +msgid "Sorry, we couldn't find that page" +msgstr "" + +msgid "Special note for this authority!" +msgstr "" + +msgid "Start now »" +msgstr "" + +msgid "Start your own blog" +msgstr "" + +msgid "Stay up to date" +msgstr "" + +msgid "Still awaiting an internal review" +msgstr "" + +msgid "Subject" +msgstr "" + +msgid "Subject:" +msgstr "" + +msgid "Submit" +msgstr "" + +msgid "Submit status" +msgstr "" + +msgid "Submit status and send message" +msgstr "" + +msgid "Subscribe to blog" +msgstr "" + +msgid "Successful Freedom of Information requests" +msgstr "" + +msgid "Successful." +msgstr "" + +msgid "Suggest how the requester can find the rest of the information." +msgstr "" + +msgid "Summary:" +msgstr "" + +msgid "Table of statuses" +msgstr "" + +msgid "Table of varieties" +msgstr "" + +msgid "Tags" +msgstr "" + +msgid "Tags (separated by a space):" +msgstr "" + +msgid "Tags:" +msgstr "" + +msgid "Technical details" +msgstr "" + +msgid "Thank you for helping us keep the site tidy!" +msgstr "" + +msgid "Thank you for making an annotation!" +msgstr "" + +msgid "Thank you for responding to this FOI request! Your response has been published below, and a link to your response has been emailed to " +msgstr "" + +msgid "Thank you for updating the status of the request '{{info_request_title}}'. There are some more requests below for you to classify." +msgstr "" + +msgid "Thank you for updating this request!" +msgstr "" + +msgid "Thank you for updating your profile photo" +msgstr "" + +msgid "Thank you! We'll look into what happened and try and fix it up." +msgstr "" + +msgid "Thanks for helping - your work will make it easier for everyone to find successful\\nresponses, and maybe even let us make league tables..." +msgstr "" + +msgid "Thanks very much - this will help others find useful stuff. We'll\\n also, if you need it, give advice on what to do next about your\\n requests." +msgstr "" + +msgid "Thanks very much for helping keep everything neat and organised.\\n We'll also, if you need it, give you advice on what to do next about each of your\\n requests." +msgstr "" + +msgid "That doesn't look like a valid email address. Please check you have typed it correctly." +msgstr "" + +msgid "The review has finished and overall:" +msgstr "" + +msgid "The Freedom of Information Act does not apply to" +msgstr "" + +msgid "The accounts have been left as they previously were." +msgstr "" + +msgid "The authority do not have the information (maybe they say who does)" +msgstr "" + +msgid "The authority only has a paper copy of the information." +msgstr "" + +msgid "The authority say that they need a postal\\n address, not just an email, for it to be a valid FOI request" +msgstr "" + +msgid "The authority would like to / has responded by post to this request." +msgstr "" + +msgid "The classification of requests (e.g. to say whether they were successful or not) is done manually by users and administrators of the site, which means that they are subject to error." +msgstr "" + +msgid "The email that you, on behalf of {{public_body}}, sent to\\n{{user}} to reply to an {{law_used_short}}\\nrequest has not been delivered." +msgstr "" + +msgid "The error bars shown are 95% confidence intervals for the hypothesized underlying proportion (i.e. that which you would obtain by making an infinite number of requests through this site to that authority). In other words, the population being sampled is all the current and future requests to the authority through this site, rather than, say, all requests that have been made to the public body by any means." +msgstr "" + +msgid "The page doesn't exist. Things you can try now:" +msgstr "" + +msgid "The percentages are calculated with respect to the total number of requests, which includes invalid requests; this is a known problem that will be fixed in a later release." +msgstr "" + +msgid "The public authority does not have the information requested" +msgstr "" + +msgid "The public authority would like part of the request explained" +msgstr "" + +msgid "The public authority would like to / has responded by post" +msgstr "" + +msgid "The request has been refused" +msgstr "" + +msgid "The request has been updated since you originally loaded this page. Please check for any new incoming messages below, and try again." +msgstr "" + +msgid "The request is waiting for clarification." +msgstr "" + +msgid "The request was partially successful." +msgstr "" + +msgid "The request was refused by" +msgstr "" + +msgid "The request was successful." +msgstr "" + +msgid "The request was refused by the public authority" +msgstr "" + +msgid "The request you have tried to view has been removed. There are\\nvarious reasons why we might have done this, sorry we can't be more specific here. Please contact us if you have any questions." +msgstr "" + +msgid "The requester has abandoned this request for some reason" +msgstr "" + +msgid "The response to your request has been delayed. You can say that,\\n by law, the authority should normally have responded\\n promptly and" +msgstr "" + +msgid "The response to your request is long overdue. You can say that, by\\n law, under all circumstances, the authority should have responded\\n by now" +msgstr "" + +msgid "The search index is currently offline, so we can't show the Freedom of Information requests that have been made to this authority." +msgstr "" + +msgid "The search index is currently offline, so we can't show the Freedom of Information requests this person has made." +msgstr "" + +msgid "The {{site_name}} team." +msgstr "" + +msgid "Then you can cancel the alert." +msgstr "" + +msgid "Then you can cancel the alerts." +msgstr "" + +msgid "Then you can change your email address used on {{site_name}}" +msgstr "" + +msgid "Then you can change your password on {{site_name}}" +msgstr "" + +msgid "Then you can classify the FOI response you have got from " +msgstr "" + +msgid "Then you can download a zip file of {{info_request_title}}." +msgstr "" + +msgid "Then you can log into the administrative interface" +msgstr "" + +msgid "Then you can play the request categorisation game." +msgstr "" + +msgid "Then you can report the request '{{title}}'" +msgstr "" + +msgid "Then you can send a message to " +msgstr "" + +msgid "Then you can sign in to {{site_name}}" +msgstr "" + +msgid "Then you can update the status of your request to " +msgstr "" + +msgid "Then you can upload an FOI response. " +msgstr "" + +msgid "Then you can write follow up message to " +msgstr "" + +msgid "Then you can write your reply to " +msgstr "" + +msgid "Then you will be following all new FOI requests." +msgstr "" + +msgid "Then you will be notified whenever '{{user_name}}' requests something or gets a response." +msgstr "" + +msgid "Then you will be notified whenever a new request or response matches your search." +msgstr "" + +msgid "Then you will be notified whenever an FOI request succeeds." +msgstr "" + +msgid "Then you will be notified whenever someone requests something or gets a response from '{{public_body_name}}'." +msgstr "" + +msgid "Then you will be updated whenever the request '{{request_title}}' is updated." +msgstr "" + +msgid "Then you'll be allowed to send FOI requests." +msgstr "" + +msgid "Then your FOI request to {{public_body_name}} will be sent." +msgstr "" + +msgid "Then your annotation to {{info_request_title}} will be posted." +msgstr "" + +msgid "There are {{count}} new annotations on your {{info_request}} request. Follow this link to see what they wrote." +msgstr "" + +msgid "There is more than one person who uses this site and has this name.\\n One of them is shown below, you may mean a different one:" +msgstr "" + +msgid "There is a limit on the number of requests you can make in a day, because we don’t want public authorities to be bombarded with large numbers of inappropriate requests. If you feel you have a good reason to ask for the limit to be lifted in your case, please get in touch." +msgstr "" + +msgid "There is {{count}} person following this request" +msgid_plural "There are {{count}} people following this request" +msgstr[0] "" + +msgid "There was a delivery error or similar, which needs fixing by the {{site_name}} team." +msgstr "" + +msgid "There was an error with the words you entered, please try again." +msgstr "" + +msgid "There was no data calculated for this graph yet." +msgstr "" + +msgid "There were no requests matching your query." +msgstr "" + +msgid "There were no results matching your query." +msgstr "" + +msgid "These graphs were partly inspired by some statistics that Mark Goodge produced for WhatDoTheyKnow, so thanks are due to him." +msgstr "" + +msgid "They are going to reply by post" +msgstr "" + +msgid "They do not have the information (maybe they say who does)" +msgstr "" + +msgid "They have been given the following explanation:" +msgstr "" + +msgid "They have not replied to your {{law_used_short}} request {{title}} promptly, as normally required by law" +msgstr "" + +msgid "They have not replied to your {{law_used_short}} request {{title}}, \\nas required by law" +msgstr "" + +msgid "Things to do with this request" +msgstr "" + +msgid "Things you're following" +msgstr "" + +msgid "This authority no longer exists, so you cannot make a request to it." +msgstr "" + +msgid "This covers a very wide spectrum of information about the state of\\n the natural and built environment, such as:" +msgstr "" + +msgid "This external request has been hidden" +msgstr "" + +msgid "This is a plain-text version of the Freedom of Information request \"{{request_title}}\". The latest, full version is available online at {{full_url}}" +msgstr "" + +msgid "This is an HTML version of an attachment to the Freedom of Information request" +msgstr "" + +msgid "This is because {{title}} is an old request that has been\\nmarked to no longer receive responses." +msgstr "" + +msgid "This is the first version." +msgstr "" + +msgid "This is your own request, so you will be automatically emailed when new responses arrive." +msgstr "" + +msgid "This message has been hidden." +msgstr "" + +msgid "This message has been hidden. There are various reasons why we might have done this, sorry we can't be more specific here." +msgstr "" + +msgid "This message has prominence 'hidden'. You can only see it because you are logged in as a super user." +msgstr "" + +msgid "This message has prominence 'hidden'. {{reason}} You can only see it because you are logged in as a super user." +msgstr "" + +msgid "This message is hidden, so that only you, the requester, can see it. Please contact us if you are not sure why." +msgstr "" + +msgid "This message is hidden, so that only you, the requester, can see it. {{reason}}" +msgstr "" + +msgid "This page of public body statistics is currently experimental, so there are some caveats that should be borne in mind:" +msgstr "" + +msgid "This particular request is finished:" +msgstr "" + +msgid "This person has made no Freedom of Information requests using this site." +msgstr "" + +msgid "This person's annotations" +msgstr "" + +msgid "This person's {{count}} Freedom of Information request" +msgid_plural "This person's {{count}} Freedom of Information requests" +msgstr[0] "" + +msgid "This person's {{count}} annotation" +msgid_plural "This person's {{count}} annotations" +msgstr[0] "" + +msgid "This request requires administrator attention" +msgstr "" + +msgid "This request has already been reported for administrator attention" +msgstr "" + +msgid "This request has an unknown status." +msgstr "" + +msgid "This request has been hidden from the site, because an administrator considers it not to be an FOI request" +msgstr "" + +msgid "This request has been hidden from the site, because an administrator considers it vexatious" +msgstr "" + +msgid "This request has been reported as needing administrator attention (perhaps because it is vexatious, or a request for personal information)" +msgstr "" + +msgid "This request has been withdrawn by the person who made it.\\n There may be an explanation in the correspondence below." +msgstr "" + +msgid "This request has been marked for review by the site administrators, who have not hidden it at this time. If you believe it should be hidden, please contact us." +msgstr "" + +msgid "This request has been reported for administrator attention" +msgstr "" + +msgid "This request has been set by an administrator to \"allow new responses from nobody\"" +msgstr "" + +msgid "This request has had an unusual response, and requires attention from the {{site_name}} team." +msgstr "" + +msgid "This request has prominence 'hidden'. You can only see it because you are logged\\n in as a super user." +msgstr "" + +msgid "This request is hidden, so that only you the requester can see it. Please\\n contact us if you are not sure why." +msgstr "" + +msgid "This request is still in progress:" +msgstr "" + +msgid "This request requires administrator attention" +msgstr "" + +msgid "This request was not made via {{site_name}}" +msgstr "" + +msgid "This table shows the technical details of the internal events that happened\\nto this request on {{site_name}}. This could be used to generate information about\\nthe speed with which authorities respond to requests, the number of requests\\nwhich require a postal response and much more." +msgstr "" + +msgid "This user has been banned from {{site_name}} " +msgstr "" + +msgid "This was not possible because there is already an account using \\nthe email address {{email}}." +msgstr "" + +msgid "To cancel these alerts" +msgstr "" + +msgid "To cancel this alert" +msgstr "" + +msgid "To carry on, you need to sign in or make an account. Unfortunately, there\\nwas a technical problem trying to do this." +msgstr "" + +msgid "To change your email address used on {{site_name}}" +msgstr "" + +msgid "To classify the response to this FOI request" +msgstr "" + +msgid "To do that please send a private email to " +msgstr "" + +msgid "To do this, first click on the link below." +msgstr "" + +msgid "To download the zip file" +msgstr "" + +msgid "To follow all successful requests" +msgstr "" + +msgid "To follow new requests" +msgstr "" + +msgid "To follow requests and responses matching your search" +msgstr "" + +msgid "To follow requests by '{{user_name}}'" +msgstr "" + +msgid "To follow requests made using {{site_name}} to the public authority '{{public_body_name}}'" +msgstr "" + +msgid "To follow the request '{{request_title}}'" +msgstr "" + +msgid "To help us keep the site tidy, someone else has updated the status of the \\n{{law_used_full}} request {{title}} that you made to {{public_body}}, to \"{{display_status}}\" If you disagree with their categorisation, please update the status again yourself to what you believe to be more accurate." +msgstr "" + +msgid "To let everyone know, follow this link and then select the appropriate box." +msgstr "" + +msgid "To log into the administrative interface" +msgstr "" + +msgid "To play the request categorisation game" +msgstr "" + +msgid "To post your annotation" +msgstr "" + +msgid "To reply to " +msgstr "" + +msgid "To report this request" +msgstr "" + +msgid "To send a follow up message to " +msgstr "" + +msgid "To send a message to " +msgstr "" + +msgid "To send your FOI request" +msgstr "" + +msgid "To update the status of this FOI request" +msgstr "" + +msgid "To upload a response, you must be logged in using an email address from " +msgstr "" + +msgid "To use the advanced search, combine phrases and labels as described in the search tips below." +msgstr "" + +msgid "To view the email address that we use to send FOI requests to {{public_body_name}}, please enter these words." +msgstr "" + +msgid "To view the response, click on the link below." +msgstr "" + +msgid "To {{public_body_link_absolute}}" +msgstr "" + +msgid "To:" +msgstr "" + +msgid "Today" +msgstr "" + +msgid "Too many requests" +msgstr "" + +msgid "Top search results:" +msgstr "" + +msgid "Track thing" +msgstr "" + +msgid "Track this person" +msgstr "" + +msgid "Track this search" +msgstr "" + +msgid "TrackThing|Track medium" +msgstr "" + +msgid "TrackThing|Track query" +msgstr "" + +msgid "TrackThing|Track type" +msgstr "" + +msgid "Turn off email alerts" +msgstr "" + +msgid "Tweet this request" +msgstr "" + +msgid "Type 01/01/2008..14/01/2008 to only show things that happened in the first two weeks of January." +msgstr "" + +msgid "URL name can't be blank" +msgstr "" + +msgid "Unable to change email address on {{site_name}}" +msgstr "" + +msgid "Unable to send a reply to {{username}}" +msgstr "" + +msgid "Unable to send follow up message to {{username}}" +msgstr "" + +msgid "Unexpected search result type" +msgstr "" + +msgid "Unexpected search result type " +msgstr "" + +msgid "Unfortunately we don't know the FOI\\nemail address for that authority, so we can't validate this.\\nPlease contact us to sort it out." +msgstr "" + +msgid "Unfortunately, we do not have a working {{info_request_law_used_full}}\\naddress for" +msgstr "" + +msgid "Unknown" +msgstr "" + +msgid "Unsubscribe" +msgstr "" + +msgid "Unusual response." +msgstr "" + +msgid "Update the status of this request" +msgstr "" + +msgid "Update the status of your request to " +msgstr "" + +msgid "Upload FOI response" +msgstr "" + +msgid "Use OR (in capital letters) where you don't mind which word, e.g. commons OR lords" +msgstr "" + +msgid "Use quotes when you want to find an exact phrase, e.g. \"Liverpool City Council\"" +msgstr "" + +msgid "User" +msgstr "" + +msgid "User info request sent alert" +msgstr "" + +msgid "User – {{name}}" +msgstr "" + +msgid "UserInfoRequestSentAlert|Alert type" +msgstr "" + +msgid "User|About me" +msgstr "" + +msgid "User|Admin level" +msgstr "" + +msgid "User|Ban text" +msgstr "" + +msgid "User|Email" +msgstr "" + +msgid "User|Email bounce message" +msgstr "" + +msgid "User|Email bounced at" +msgstr "" + +msgid "User|Email confirmed" +msgstr "" + +msgid "User|Hashed password" +msgstr "" + +msgid "User|Last daily track email" +msgstr "" + +msgid "User|Locale" +msgstr "" + +msgid "User|Name" +msgstr "" + +msgid "User|No limit" +msgstr "" + +msgid "User|Receive email alerts" +msgstr "" + +msgid "User|Salt" +msgstr "" + +msgid "User|Url name" +msgstr "" + +msgid "Version {{version}}" +msgstr "" + +msgid "View FOI email address" +msgstr "" + +msgid "View FOI email address for '{{public_body_name}}'" +msgstr "" + +msgid "View FOI email address for {{public_body_name}}" +msgstr "" + +msgid "View Freedom of Information requests made by {{user_name}}:" +msgstr "" + +msgid "View and search requests" +msgstr "" + +msgid "View authorities" +msgstr "" + +msgid "View email" +msgstr "" + +msgid "View requests" +msgstr "" + +msgid "Waiting clarification." +msgstr "" + +msgid "Waiting for an internal review by {{public_body_link}} of their handling of this request." +msgstr "" + +msgid "Waiting for the public authority to complete an internal review of their handling of the request" +msgstr "" + +msgid "Waiting for the public authority to reply" +msgstr "" + +msgid "Was the response you got to your FOI request any good?" +msgstr "" + +msgid "We consider it is not a valid FOI request, and have therefore hidden it from other users." +msgstr "" + +msgid "We consider it to be vexatious, and have therefore hidden it from other users." +msgstr "" + +msgid "We do not have a working request email address for this authority." +msgstr "" + +msgid "We do not have a working {{law_used_full}} address for {{public_body_name}}." +msgstr "" + +msgid "We don't know whether the most recent response to this request contains\\n information or not\\n –\\n\tif you are {{user_link}} please sign in and let everyone know." +msgstr "" + +msgid "We will not reveal your email address to anybody unless you or\\n the law tell us to (details). " +msgstr "" + +msgid "We will not reveal your email address to anybody unless you\\nor the law tell us to." +msgstr "" + +msgid "We will not reveal your email addresses to anybody unless you\\nor the law tell us to." +msgstr "" + +msgid "We're waiting for" +msgstr "" + +msgid "We're waiting for someone to read" +msgstr "" + +msgid "We've sent an email to your new email address. You'll need to click the link in\\nit before your email address will be changed." +msgstr "" + +msgid "We've sent you an email, and you'll need to click the link in it before you can\\ncontinue." +msgstr "" + +msgid "We've sent you an email, click the link in it, then you can change your password." +msgstr "" + +msgid "What are you doing?" +msgstr "" + +msgid "What best describes the status of this request now?" +msgstr "" + +msgid "What information has been released?" +msgstr "" + +msgid "What information has been requested?" +msgstr "" + +msgid "When you get there, please update the status to say if the response \\ncontains any useful information." +msgstr "" + +msgid "When you receive the paper response, please help\\n others find out what it says:" +msgstr "" + +msgid "When you're done, come back here, reload this page and file your new request." +msgstr "" + +msgid "Which of these is happening?" +msgstr "" + +msgid "Who can I request information from?" +msgstr "" + +msgid "Withdrawn by the requester." +msgstr "" + +msgid "Wk" +msgstr "" + +msgid "Would you like to see a website like this in your country?" +msgstr "" + +msgid "Write a reply" +msgstr "" + +msgid "Write a reply to " +msgstr "" + +msgid "Write your FOI follow up message to " +msgstr "" + +msgid "Write your request in simple, precise language." +msgstr "" + +msgid "You" +msgstr "" + +msgid "You are already following new requests" +msgstr "" + +msgid "You are already following requests to {{public_body_name}}" +msgstr "" + +msgid "You are already following things matching this search" +msgstr "" + +msgid "You are already following this person" +msgstr "" + +msgid "You are already following this request" +msgstr "" + +msgid "You are already following updates about {{track_description}}" +msgstr "" + +msgid "You are currently receiving notification of new activity on your wall by email." +msgstr "" + +msgid "You are following all new successful responses" +msgstr "" + +msgid "You are no longer following {{track_description}}." +msgstr "" + +msgid "You are now following updates about {{track_description}}" +msgstr "" + +msgid "You can complain by" +msgstr "" + +msgid "You can change the requests and users you are following on your profile page." +msgstr "" + +msgid "You can get this page in computer-readable format as part of the main JSON\\npage for the request. See the API documentation." +msgstr "" + +msgid "You can only request information about the environment from this authority." +msgstr "" + +msgid "You have a new response to the {{law_used_full}} request " +msgstr "" + +msgid "You have found a bug. Please contact us to tell us about the problem" +msgstr "" + +msgid "You have hit the rate limit on new requests. Users are ordinarily limited to {{max_requests_per_user_per_day}} requests in any rolling 24-hour period. You will be able to make another request in {{can_make_another_request}}." +msgstr "" + +msgid "You have made no Freedom of Information requests using this site." +msgstr "" + +msgid "You have now changed the text about you on your profile." +msgstr "" + +msgid "You have now changed your email address used on {{site_name}}" +msgstr "" + +msgid "You just tried to sign up to {{site_name}}, when you\\nalready have an account. Your name and password have been\\nleft as they previously were.\\n\\nPlease click on the link below." +msgstr "" + +msgid "You know what caused the error, and can suggest a solution, such as a working email address." +msgstr "" + +msgid "You may include attachments. If you would like to attach a\\n file too large for email, use the form below." +msgstr "" + +msgid "You may be able to find\\n one on their website, or by phoning them up and asking. If you manage\\n to find one, then please send it to us." +msgstr "" + +msgid "You may be able to find\\none on their website, or by phoning them up and asking. If you manage\\nto find one, then please send it to us." +msgstr "" + +msgid "You need to be logged in to change the text about you on your profile." +msgstr "" + +msgid "You need to be logged in to change your profile photo." +msgstr "" + +msgid "You need to be logged in to clear your profile photo." +msgstr "" + +msgid "You need to be logged in to edit your profile." +msgstr "" + +msgid "You need to be logged in to report a request for administrator attention" +msgstr "" + +msgid "You previously submitted that exact follow up message for this request." +msgstr "" + +msgid "You should have received a copy of the request by email, and you can respond\\n by simply replying to that email. For your convenience, here is the address:" +msgstr "" + +msgid "You want to give your postal address to the authority in private." +msgstr "" + +msgid "You will be unable to make new requests, send follow ups, add annotations or\\nsend messages to other users. You may continue to view other requests, and set\\nup\\nemail alerts." +msgstr "" + +msgid "You will no longer be emailed updates for those alerts" +msgstr "" + +msgid "You will now be emailed updates about {{track_description}}. Prefer not to receive emails?" +msgstr "" + +msgid "You will only get an answer to your request if you follow up\\nwith the clarification." +msgstr "" + +msgid "You will still be able to view it while logged in to the site. Please reply to this email if you would like to discuss this decision further." +msgstr "" + +msgid "You're in. Continue sending your request" +msgstr "" + +msgid "You're long overdue a response to your FOI request - " +msgstr "" + +msgid "You're not following anything." +msgstr "" + +msgid "You've now cleared your profile photo" +msgstr "" + +msgid "Your name will appear publicly\\n (why?)\\n on this website and in search engines. If you\\n are thinking of using a pseudonym, please\\n read this first." +msgstr "" + +msgid "Your annotations" +msgstr "" + +msgid "Your details, including your email address, have not been given to anyone." +msgstr "" + +msgid "Your e-mail:" +msgstr "" + +msgid "Your follow up has not been sent because this request has been stopped to prevent spam. Please contact us if you really want to send a follow up message." +msgstr "" + +msgid "Your follow up message has been sent on its way." +msgstr "" + +msgid "Your internal review request has been sent on its way." +msgstr "" + +msgid "Your message has been sent. Thank you for getting in touch! We'll get back to you soon." +msgstr "" + +msgid "Your message to {{recipient_user_name}} has been sent" +msgstr "" + +msgid "Your message to {{recipient_user_name}} has been sent!" +msgstr "" + +msgid "Your message will appear in search engines" +msgstr "" + +msgid "Your name and annotation will appear in search engines." +msgstr "" + +msgid "Your name, request and any responses will appear in search engines\\n (details)." +msgstr "" + +msgid "Your name:" +msgstr "" + +msgid "Your original message is attached." +msgstr "" + +msgid "Your password has been changed." +msgstr "" + +msgid "Your password:" +msgstr "" + +msgid "Your photo will be shown in public on the Internet,\\n wherever you do something on {{site_name}}." +msgstr "" + +msgid "Your request '{{request}}' at {{url}} has been reviewed by moderators." +msgstr "" + +msgid "Your request on {{site_name}} hidden" +msgstr "" + +msgid "Your request was called {{info_request}}. Letting everyone know whether you got the information will help us keep tabs on" +msgstr "" + +msgid "Your request:" +msgstr "" + +msgid "Your response to an FOI request was not delivered" +msgstr "" + +msgid "Your response will appear on the Internet, read why and answers to other questions." +msgstr "" + +msgid "Your thoughts on what the {{site_name}} administrators should do about the request." +msgstr "" + +msgid "Your {{count}} Freedom of Information request" +msgid_plural "Your {{count}} Freedom of Information requests" +msgstr[0] "" + +msgid "Your {{count}} annotation" +msgid_plural "Your {{count}} annotations" +msgstr[0] "" + +msgid "Your {{site_name}} email alert" +msgstr "" + +msgid "Yours faithfully," +msgstr "" + +msgid "Yours sincerely," +msgstr "" + +msgid "Yours," +msgstr "" + +msgid "[FOI #{{request}} email]" +msgstr "" + +msgid "[{{public_body}} request email]" +msgstr "" + +msgid "[{{site_name}} contact email]" +msgstr "" + +msgid "\\n\\n[ {{site_name}} note: The above text was badly encoded, and has had strange characters removed. ]" +msgstr "" + +msgid "a one line summary of the information you are requesting, \\n\t\t\te.g." +msgstr "" + +msgid "admin" +msgstr "" + +msgid "alaveteli_foi:The software that runs {{site_name}}" +msgstr "" + +msgid "all requests" +msgstr "" + +msgid "also called {{public_body_short_name}}" +msgstr "" + +msgid "an anonymous user" +msgstr "" + +msgid "and" +msgstr "" + +msgid "and update the status accordingly. Perhaps you might like to help out by doing that?" +msgstr "" + +msgid "and update the status." +msgstr "" + +msgid "and we'll suggest what to do next" +msgstr "" + +msgid "any new requests" +msgstr "" + +msgid "any successful requests" +msgstr "" + +msgid "anything" +msgstr "" + +msgid "are long overdue." +msgstr "" + +msgid "at" +msgstr "" + +msgid "authorities" +msgstr "" + +msgid "awaiting a response" +msgstr "" + +msgid "beginning with ‘{{first_letter}}’" +msgstr "" + +msgid "between two dates" +msgstr "" + +msgid "but followupable" +msgstr "" + +msgid "by" +msgstr "" + +msgid "by {{date}}" +msgstr "" + +msgid "by {{public_body_name}} to {{info_request_user}} on {{date}}." +msgstr "" + +msgid "by {{user_link_absolute}}" +msgstr "" + +msgid "comments" +msgstr "" + +msgid "containing your postal address, and asking them to reply to this request.\\n Or you could phone them." +msgstr "" + +msgid "details" +msgstr "" + +msgid "display_status only works for incoming and outgoing messages right now" +msgstr "" + +msgid "during term time" +msgstr "" + +msgid "edit text about you" +msgstr "" + +msgid "even during holidays" +msgstr "" + +msgid "everything" +msgstr "" + +msgid "external" +msgstr "" + +msgid "has reported an" +msgstr "" + +msgid "have delayed." +msgstr "" + +msgid "hide quoted sections" +msgstr "" + +msgid "in term time" +msgstr "" + +msgid "in the category ‘{{category_name}}’" +msgstr "" + +msgid "internal error" +msgstr "" + +msgid "internal reviews" +msgstr "" + +msgid "is waiting for your clarification." +msgstr "" + +msgid "just to see how it works" +msgstr "" + +msgid "left an annotation" +msgstr "" + +msgid "made." +msgstr "" + +msgid "matching the tag ‘{{tag_name}}’" +msgstr "" + +msgid "messages from authorities" +msgstr "" + +msgid "messages from users" +msgstr "" + +msgid "move..." +msgstr "" + +msgid "no later than" +msgstr "" + +msgid "no longer exists. If you are trying to make\\n From the request page, try replying to a particular message, rather than sending\\n a general followup. If you need to make a general followup, and know\\n an email which will go to the right place, please send it to us." +msgstr "" + +msgid "normally" +msgstr "" + +msgid "not requestable due to: {{reason}}" +msgstr "" + +msgid "please sign in as " +msgstr "" + +msgid "requesting an internal review" +msgstr "" + +msgid "requests" +msgstr "" + +msgid "requests which are {{list_of_statuses}}" +msgstr "" + +msgid "response as needing administrator attention. Take a look, and reply to this\\nemail to let them know what you are going to do about it." +msgstr "" + +msgid "send a follow up message" +msgstr "" + +msgid "sent to {{public_body_name}} by {{info_request_user}} on {{date}}." +msgstr "" + +msgid "set to blank (empty string) if can't find an address; these emails are public as anyone can view with a CAPTCHA" +msgstr "" + +msgid "show quoted sections" +msgstr "" + +msgid "sign in" +msgstr "" + +msgid "simple_date_format" +msgstr "" + +msgid "successful" +msgstr "" + +msgid "successful requests" +msgstr "" + +msgid "that you made to" +msgstr "" + +msgid "the main FOI contact address for {{public_body}}" +msgstr "" + +#. This phrase completes the following sentences: +#. Request an internal review from... +#. Send a public follow up message to... +#. Send a public reply to... +#. Don't want to address your message to... ? +msgid "the main FOI contact at {{public_body}}" +msgstr "" + +msgid "the requester" +msgstr "" + +msgid "the {{site_name}} team" +msgstr "" + +msgid "to read" +msgstr "" + +msgid "to send a follow up message." +msgstr "" + +msgid "to {{public_body}}" +msgstr "" + +msgid "unknown reason " +msgstr "" + +msgid "unknown status " +msgstr "" + +msgid "unresolved requests" +msgstr "" + +msgid "unsubscribe" +msgstr "" + +msgid "unsubscribe all" +msgstr "" + +msgid "unsuccessful" +msgstr "" + +msgid "unsuccessful requests" +msgstr "" + +msgid "useful information." +msgstr "" + +msgid "users" +msgstr "" + +msgid "what's that?" +msgstr "" + +msgid "{{count}} FOI requests found" +msgstr "" + +msgid "{{count}} Freedom of Information request to {{public_body_name}}" +msgid_plural "{{count}} Freedom of Information requests to {{public_body_name}}" +msgstr[0] "" + +msgid "{{count}} person is following this authority" +msgid_plural "{{count}} people are following this authority" +msgstr[0] "" + +msgid "{{count}} request" +msgid_plural "{{count}} requests" +msgstr[0] "" + +msgid "{{count}} request made." +msgid_plural "{{count}} requests made." +msgstr[0] "" + +msgid "{{existing_request_user}} already\\n created the same request on {{date}}. You can either view the existing request,\\n or edit the details below to make a new but similar request." +msgstr "" + +msgid "{{info_request_user_name}} only:" +msgstr "" + +msgid "{{law_used_full}} request - {{title}}" +msgstr "" + +msgid "{{law_used}} requests at {{public_body}}" +msgstr "" + +msgid "{{length_of_time}} ago" +msgstr "" + +msgid "{{list_of_things}} matching text '{{search_query}}'" +msgstr "" + +msgid "{{number_of_comments}} comments" +msgstr "" + +msgid "{{public_body_link}} answered a request about" +msgstr "" + +msgid "{{public_body_link}} was sent a request about" +msgstr "" + +msgid "{{public_body_name}} only:" +msgstr "" + +msgid "{{public_body}} has asked you to explain part of your {{law_used}} request." +msgstr "" + +msgid "{{public_body}} sent a response to {{user_name}}" +msgstr "" + +msgid "{{reason}}, please sign in or make a new account." +msgstr "" + +msgid "{{search_results}} matching '{{query}}'" +msgstr "" + +msgid "{{site_name}} blog and tweets" +msgstr "" + +msgid "{{site_name}} covers requests to {{number_of_authorities}} authorities, including:" +msgstr "" + +msgid "{{site_name}} sends new requests to {{request_email}} for this authority." +msgstr "" + +msgid "{{site_name}} users have made {{number_of_requests}} requests, including:" +msgstr "" + +msgid "{{thing_changed}} was changed from {{from_value}} to {{to_value}}" +msgstr "" + +msgid "{{title}} - a Freedom of Information request to {{public_body}}" +msgstr "" + +msgid "{{user_name}} (Account suspended)" +msgstr "" + +msgid "{{user_name}} - Freedom of Information requests" +msgstr "" + +msgid "{{user_name}} - user profile" +msgstr "" + +msgid "{{user_name}} added an annotation" +msgstr "" + +msgid "{{user_name}} has annotated your {{law_used_short}} \\nrequest. Follow this link to see what they wrote." +msgstr "" + +msgid "{{user_name}} has used {{site_name}} to send you the message below." +msgstr "" + +msgid "{{user_name}} sent a follow up message to {{public_body}}" +msgstr "" + +msgid "{{user_name}} sent a request to {{public_body}}" +msgstr "" + +msgid "{{username}} left an annotation:" +msgstr "" + +msgid "{{user}} ({{user_admin_link}}) made this {{law_used_full}} request (admin) to {{public_body_link}} (admin)" +msgstr "" + +msgid "{{user}} made this {{law_used_full}} request" +msgstr "" -- cgit v1.2.3 From da94fee2c7dc3d689359da0321e028722d3187bc Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 9 Dec 2013 18:04:18 +0000 Subject: Syntax fixes. --- locale/cy/app.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locale/cy/app.po b/locale/cy/app.po index db3a085f6..7ff2f3d48 100644 --- a/locale/cy/app.po +++ b/locale/cy/app.po @@ -2117,7 +2117,7 @@ msgid "Sorry - you cannot respond to this request via {{site_name}}, because thi msgstr "Mae'n ddrwg gennym - ni allwch ymateb i'r cais hwn trwy {{site_name}}, gan fod hwn yn gopi o'r cais a oedd yn wreiddiol yn {{link_to_original_request}} ." msgid "Sorry, but only {{user_name}} is allowed to do that." -msgstr "Mae'n ddrwg gennym, ond dim ond {{USER_NAME}} sy'n cael gwneud hynny." +msgstr "Mae'n ddrwg gennym, ond dim ond {{user_name}} sy'n cael gwneud hynny." msgid "Sorry, there was a problem processing this page" msgstr "Mae'n ddrwg gennym, roedd problem wrth brosesu'r dudalen hon" @@ -2129,7 +2129,7 @@ msgid "Special note for this authority!" msgstr "Nodyn arbennig ar gyfer yr awdurdod hwn!" msgid "Start now »" -msgstr "Dechrau nawr »" +msgstr "Dechrau nawr »" msgid "Start your own blog" msgstr "Dechreuwch eich blog eich hun" @@ -2198,7 +2198,7 @@ msgid "Thank you for responding to this FOI request! Your response has been publ msgstr "Diolch i chi am ymateb i'r cais Rhyddid Gwybodaeth hwn! Mae eich ymateb wedi ei gyhoeddi isod, a dolen at eich ymateb wedi cael ei e-bostio at " msgid "Thank you for updating the status of the request '{{info_request_title}}'. There are some more requests below for you to classify." -msgstr "Diolch i chi am ddiweddaru statws y cais '{{ info_request_title}}'. Mae ychydig yn rhagor o geisiadau isod i chi eu dosbarthu." +msgstr "Diolch i chi am ddiweddaru statws y cais '{{info_request_title}}'. Mae ychydig yn rhagor o geisiadau isod i chi eu dosbarthu." msgid "Thank you for updating this request!" msgstr "Diolch i chi am ddiweddaru'r cais hwn!" @@ -2324,7 +2324,7 @@ msgid "Then you can classify the FOI response you have got from " msgstr "Yna gallwch ddosbarthu'r ymateb Rhyddid Gwybodaeth yr ydych wedi'i gael gan " msgid "Then you can download a zip file of {{info_request_title}}." -msgstr "Yna gallwch lawrlwytho ffeil zip o {{ info_request_title}}." +msgstr "Yna gallwch lawrlwytho ffeil zip o {{info_request_title}}." msgid "Then you can log into the administrative interface" msgstr "Yna gallwch logio i mewn i'r rhyngwyneb gweinyddol" @@ -2975,7 +2975,7 @@ msgid "You have found a bug. Please contact us msgstr "Rydych wedi dod o hyd i fyg. Cysylltwch â ni i ddweud wrthym am y broblem" msgid "You have hit the rate limit on new requests. Users are ordinarily limited to {{max_requests_per_user_per_day}} requests in any rolling 24-hour period. You will be able to make another request in {{can_make_another_request}}." -msgstr "Rydych chi wedi cyrraedd y terfyn cyfradd ar geisiadau newydd. Fel arfer cyfyngir defnyddwyr i {{max_requests_per_user_per_day}} cais mewn unrhyw gyfnod treigl 24-awr. Byddwch yn gallu gwneud cais arall ymhen {{ can_make_another_request}}." +msgstr "Rydych chi wedi cyrraedd y terfyn cyfradd ar geisiadau newydd. Fel arfer cyfyngir defnyddwyr i {{max_requests_per_user_per_day}} cais mewn unrhyw gyfnod treigl 24-awr. Byddwch yn gallu gwneud cais arall ymhen {{can_make_another_request}}." msgid "You have made no Freedom of Information requests using this site." msgstr "Nid ydych wedi gwneud unrhyw geisiadau Rhyddid Gwybodaeth drwy ddefnyddio'r wefan hon." -- cgit v1.2.3 From 833b2d1cde3c621199b9e5d6344c299fb38e5fd2 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 10 Dec 2013 12:20:59 +0000 Subject: Add release notes for 0.16 --- doc/CHANGES.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/doc/CHANGES.md b/doc/CHANGES.md index 45b8c3cc9..ae3263ce4 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -1,3 +1,46 @@ +# Version 0.16 + +## Highlighted features + +* Upgrade of the Rails framework to 3.2.16 +* Enabling the Rails asset pipeline for managing assets (more about the + asset pipeline at http://guides.rubyonrails.org/asset_pipeline.html). +* The all authorities csv download now uses less system resources +* Ruby 2.0 is now included in the matrix of versions we run continuous + integration tests against +* When using capistrano, the RAILS_ENV can now be explicitly set from + deploy.yml +* The front page and request pages once more use fragment caching backed + by memcached to speed up serving of slow parts of these pages +* The robots.txt file has been updated to allow crawling of response + attachment files (in original and HTML versions) +* The `themes:install` rake task is kinder to developers; it no longer + removes and reclones themes, destroying local changes, and it keeps + themes as git repositories. +* Social media elements (the blog, twitter feed) are only included if + the appropriate config variables (BLOG_FEED and TWITTER_USERNAME) have + been populated. +* Some fixes to the treatment of hyphenated/underscored locales so that + public body translations are consistently stored using the underscore + format of the locale (so 'he_IL', not 'he-IL'). + +## Upgrade notes + +* You will need to update your theme to use the asset pipeline - notes + on how to do this are in doc/THEME-ASSETS-UPGRADE.md +* The syntax of the highlight and excerpt methods has changed, so if you + use these in your theme, you may see deprecation warnings until you + update them. More information at http://apidock.com/rails/v3.2.13/ActionView/Helpers/TextHelper/highlight + and + http://apidock.com/rails/v3.2.13/ActionView/Helpers/TextHelper/excerpt +* If you don't want to use fragment caching, you can turn it off in your + config file by setting `CACHE_FRAGMENTS` to `false`. +* If you use a locale with an underscore in it, you should double check + that the locale field of your `public_body_translations` table shows + the underscore version of the locale name. +* This release includes an update to the commonlib submodule - you + should be warned about this when running rails-post-deploy + # Version 0.15 ## Highlighted features -- cgit v1.2.3 From 9cac4f2387416b5da77a4d5bbe7e21f937cf9129 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 10 Dec 2013 16:33:46 +0000 Subject: Small fixes for favicon.ico --- app/assets/images/favicon.ico | Bin 0 -> 22382 bytes config/deploy.rb | 1 - doc/THEME-ASSETS-UPGRADE.md | 2 ++ public/favicon.ico | Bin 22382 -> 0 bytes 4 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 app/assets/images/favicon.ico delete mode 100644 public/favicon.ico diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 000000000..26127495c Binary files /dev/null and b/app/assets/images/favicon.ico differ diff --git a/config/deploy.rb b/config/deploy.rb index a0189c855..3a4f175b4 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -55,7 +55,6 @@ namespace :deploy do "#{release_path}/config/aliases" => "#{shared_path}/aliases", "#{release_path}/public/foi-live-creation.png" => "#{shared_path}/foi-live-creation.png", "#{release_path}/public/foi-user-use.png" => "#{shared_path}/foi-user-use.png", - "#{release_path}/public/favicon.ico" => "#{shared_path}/favicon.ico", "#{release_path}/files" => "#{shared_path}/files", "#{release_path}/cache" => "#{shared_path}/cache", "#{release_path}/lib/acts_as_xapian/xapiandbs" => "#{shared_path}/xapiandbs", diff --git a/doc/THEME-ASSETS-UPGRADE.md b/doc/THEME-ASSETS-UPGRADE.md index 2c6e49986..12c1a60d1 100644 --- a/doc/THEME-ASSETS-UPGRADE.md +++ b/doc/THEME-ASSETS-UPGRADE.md @@ -33,6 +33,8 @@ tag to use `image_tag` instead. For example, instead of: image_tag('helpmeinvestigate.png', :alt => "", :class => "rss") +If you have a favicon.ico file in your theme's `public` directory, you should move it to `assets/images` as well. + You should similarly move your stylesheets into `assets/stylesheets`. If a stylesheet refers to images, you should rename the `.css` file to `.css.scss`, and change `url` diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 26127495c..000000000 Binary files a/public/favicon.ico and /dev/null differ -- cgit v1.2.3 From 39fa94fd6152f882c9214ac9dbea49c5bae1345c Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 10 Dec 2013 17:57:09 +0000 Subject: Remove the duplicate close button on the popup banner --- app/views/layouts/default.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 5895becf7..63e22b004 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -70,7 +70,6 @@ <% popup_banner = render(:partial => "general/popup_banner").strip %> <% if popup_banner.present? %>
    - <%= raw popup_banner %>
    -- cgit v1.2.3 From 2c9288e5cc7eb0209f185018134ec6e3ebb91eda Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Wed, 11 Dec 2013 09:51:30 +0000 Subject: Remove old patch. --- config/initializers/alaveteli.rb | 1 - lib/actionmailer_patches.rb | 15 --------------- 2 files changed, 16 deletions(-) delete mode 100644 lib/actionmailer_patches.rb diff --git a/config/initializers/alaveteli.rb b/config/initializers/alaveteli.rb index 631251b87..760c138a7 100644 --- a/config/initializers/alaveteli.rb +++ b/config/initializers/alaveteli.rb @@ -50,7 +50,6 @@ require 'normalize_string' require 'alaveteli_file_types' require 'alaveteli_localization' require 'message_prominence' -require 'actionmailer_patches' require 'theme' AlaveteliLocalization.set_locales(AlaveteliConfiguration::available_locales, diff --git a/lib/actionmailer_patches.rb b/lib/actionmailer_patches.rb deleted file mode 100644 index 600d3c8cc..000000000 --- a/lib/actionmailer_patches.rb +++ /dev/null @@ -1,15 +0,0 @@ -# Monkey patch for CVE-2013-4389 -# derived from http://seclists.org/oss-sec/2013/q4/118 to fix -# a possible DoS vulnerability in the log subscriber component of -# Action Mailer. - -require 'action_mailer' -module ActionMailer - class LogSubscriber < ActiveSupport::LogSubscriber - def deliver(event) - recipients = Array.wrap(event.payload[:to]).join(', ') - info("\nSent mail to #{recipients} (#{event.duration.round(1)}ms)") - debug(event.payload[:mail]) - end - end -end -- cgit v1.2.3 From 7538075754a3fbd4fa4a11ca1374203d2463f8d3 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 12 Dec 2013 09:28:46 +0000 Subject: Add extra argument for MissingInterpolationArgument error Revert translation of interpolated variable in French locale. --- lib/i18n_fixes.rb | 2 +- locale/fr/app.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/i18n_fixes.rb b/lib/i18n_fixes.rb index 9f0849e75..64c370477 100644 --- a/lib/i18n_fixes.rb +++ b/lib/i18n_fixes.rb @@ -35,7 +35,7 @@ def gettext_interpolate(string, values) pattern, key = $1, $1.to_sym if !values.include?(key) - raise I18n::MissingInterpolationArgument.new(pattern, string) + raise I18n::MissingInterpolationArgument.new(pattern, string, values) else v = values[key].to_s if safe && !v.html_safe? diff --git a/locale/fr/app.po b/locale/fr/app.po index b72f71dea..5b22e2172 100644 --- a/locale/fr/app.po +++ b/locale/fr/app.po @@ -634,7 +634,7 @@ msgid "Date:" msgstr "Date:" msgid "Dear {{name}}," -msgstr "Cher {{nom}}," +msgstr "Cher {{name}}," msgid "Dear {{public_body_name}}," msgstr "Cher {{public_body_name}}," -- cgit v1.2.3 From 960ad7e765ecfa49b114e2bb1ad024ffdf74c82b Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 19 Dec 2013 13:31:44 +0000 Subject: Add a note about deleting vendor/plugins --- doc/CHANGES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/CHANGES.md b/doc/CHANGES.md index ae3263ce4..07d8cf613 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -40,6 +40,15 @@ the underscore version of the locale name. * This release includes an update to the commonlib submodule - you should be warned about this when running rails-post-deploy +* All code has been moved out of the deprecated plugin path + `vendor/plugins`. Once you are up and running under 0.16, you should + check that your xapian databases have all been copied to + `lib/acts_as_xapian/xapiandbs` (the code in + `config/initializers/acts_as_xapian` should do this), and then check + and remove any files under vendor/plugins so that you won't get + deprecation warnings about having Rails 2.3 style plugins (deprecation + warnings can result in incoming mail getting an auto reply under some + email configs). # Version 0.15 -- cgit v1.2.3 From b1bca914e4719b6d9aaa62e2f7c4881e14c05aeb Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 13 Jan 2014 16:51:44 +0000 Subject: Neaten up spacing of admin bar in main site context. --- public/admin/stylesheets/admin.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/admin/stylesheets/admin.css b/public/admin/stylesheets/admin.css index 9c98ac432..ada9cd173 100644 --- a/public/admin/stylesheets/admin.css +++ b/public/admin/stylesheets/admin.css @@ -8,7 +8,7 @@ body.admin { margin: 0; font-family: "Helvetica Neue", Helvetica, Arial, sans-se /* When the admin stylesheet is loaded (and so the user is seeing the admin navbar), pad the banner of the front end interface so that it isn't hidden, and move any special notice down too. */ -.entirebody, #banner { padding-top: 50px; } +.entirebody, #banner { padding-top: 42px; } #special-notice { margin-top: 50px; } @@ -987,7 +987,7 @@ body.admin { margin: 0; font-family: "Helvetica Neue", Helvetica, Arial, sans-se .admin .clearfix:after { clear: both; } .admin .hide-text { font: 0/0 a; color: transparent; text-shadow: none; background-color: transparent; border: 0; } .admin .input-block-level { display: block; width: 100%; min-height: 30px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } -.admin #main { padding-top: 50px; } +.admin #main { padding-top: 42px; } .admin .form-inline { display: inline; } .admin table .form { display: inline-block; margin: 0; } .admin .accordion-group { border: none; } -- cgit v1.2.3 From 9e9c5e4bf090e98ef16d7a54c46f16e87ce3e4ae Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 14 Jan 2014 10:51:30 +0000 Subject: Whitespace changes. --- public/javascripts/general.js | 66 +++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/public/javascripts/general.js b/public/javascripts/general.js index b52131b83..989907998 100644 --- a/public/javascripts/general.js +++ b/public/javascripts/general.js @@ -2,49 +2,49 @@ $(document).ready(function() { // flash message for people coming from other countries if(window.location.search.substring(1).search("country_name") == -1) { if (!$.cookie('has_seen_country_message')) { - $.ajax({ - url: "/country_message", - dataType: 'html', - success: function(country_message){ - if (country_message != ''){ - $('#other-country-notice').html(country_message); - $('body:not(.front) #other-country-notice').show() - } - } - }) + $.ajax({ + url: "/country_message", + dataType: 'html', + success: function(country_message){ + if (country_message != ''){ + $('#other-country-notice').html(country_message); + $('body:not(.front) #other-country-notice').show() + } + } + }) } } $('#other-country-notice').click(function() { - $('#other-country-notice').hide(); - $.cookie('has_seen_country_message', 1, {expires: 365, path: '/'}); + $('#other-country-notice').hide(); + $.cookie('has_seen_country_message', 1, {expires: 365, path: '/'}); }); // "link to this" widget $('a.link_to_this').click(function() { - var box = $('div#link_box'); - var location = window.location.protocol + "//" + window.location.hostname + $(this).attr('href'); - box.width(location.length + " em"); - box.find('input').val(location).attr('size', location.length + " em"); - box.show(); - box.find('input').select(); - box.position({ - my: "left top", - at: "left bottom", - of: this, - collision: "fit" }); - return false; - }); - + var box = $('div#link_box'); + var location = window.location.protocol + "//" + window.location.hostname + $(this).attr('href'); + box.width(location.length + " em"); + box.find('input').val(location).attr('size', location.length + " em"); + box.show(); + box.find('input').select(); + box.position({ + my: "left top", + at: "left bottom", + of: this, + collision: "fit" }); + return false; + }); + $('.close-button').click(function() { $(this).parent().hide() }); $('div#variety-filter a').each(function() { - $(this).click(function() { - var form = $('form#search_form'); - form.attr('action', $(this).attr('href')); - form.submit(); - return false; - }) - }) + $(this).click(function() { + var form = $('form#search_form'); + form.attr('action', $(this).attr('href')); + form.submit(); + return false; + }) + }) if($.cookie('seen_foi2') == 1) { $('#everypage').hide(); -- cgit v1.2.3 From c2bf5a90fc76eda220b35b510b7a200a43c858b1 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 14 Jan 2014 10:52:53 +0000 Subject: Cleanup popup notices. Move HTML to view from controller, use same elements for other country popup and everypage - partly so they don't display on top of each other anymore. Don't position them over existing content, position them at the top of the page. Use consistent styling, and keep the javascript unobtrusive. --- app/controllers/services_controller.rb | 3 -- app/views/layouts/default.html.erb | 23 ++++++---- public/images/small-green-cross.png | Bin 0 -> 356 bytes public/javascripts/general.js | 13 ++++-- public/stylesheets/main.css | 62 +++++++++++++-------------- spec/controllers/services_controller_spec.rb | 2 +- 6 files changed, 57 insertions(+), 46 deletions(-) create mode 100644 public/images/small-green-cross.png diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb index 11ed4ac8f..78c494dba 100644 --- a/app/controllers/services_controller.rb +++ b/app/controllers/services_controller.rb @@ -24,9 +24,6 @@ class ServicesController < ApplicationController FastGettext.locale = old_fgt_locale end end - if !text.empty? - text += ' X'.html_safe - end render :text => text, :content_type => "text/plain" # XXX workaround the HTML validation in test suite end diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 63e22b004..07e1bb808 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -67,15 +67,23 @@ <% if is_admin? %> <%= render :partial => 'admin_general/admin_navbar' %> <% end %> -<% popup_banner = render(:partial => "general/popup_banner").strip %> -<% if popup_banner.present? %> -
    - <%= raw popup_banner %> - -
    -<% end %>
    + <% popup_banner = render(:partial => "general/popup_banner").strip %> + <% if popup_banner.present? and ! @render_to_file %> + + <% end %> + +