diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | app/helpers/application_helper.rb | 14 | ||||
-rwxr-xr-x | app/helpers/link_to_helper.rb | 19 | ||||
-rw-r--r-- | app/models/censor_rule.rb | 9 | ||||
-rw-r--r-- | app/models/comment.rb | 8 | ||||
-rw-r--r-- | app/models/incoming_message.rb | 12 | ||||
-rw-r--r-- | app/models/info_request.rb | 8 | ||||
-rw-r--r-- | app/models/info_request_event.rb | 43 | ||||
-rw-r--r-- | app/models/outgoing_message.rb | 6 | ||||
-rw-r--r-- | app/models/public_body.rb | 25 | ||||
-rw-r--r-- | app/models/user.rb | 12 | ||||
-rw-r--r-- | app/views/general/_locale_switcher.rhtml | 22 | ||||
m--------- | commonlib | 0 | ||||
-rw-r--r-- | config/general.yml-example | 8 | ||||
-rw-r--r-- | config/initializers/theme_loader.rb | 7 | ||||
-rw-r--r-- | public/javascripts/admin.coffee | 10 | ||||
-rw-r--r-- | public/javascripts/admin.js | 14 | ||||
-rw-r--r-- | public/javascripts/bootstrap-collapse.js | 138 | ||||
-rw-r--r-- | public/javascripts/bootstrap-tab.js | 130 | ||||
-rwxr-xr-x | script/rails-post-deploy | 10 |
20 files changed, 445 insertions, 51 deletions
diff --git a/.gitignore b/.gitignore index 258ea5a17..45865fd02 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ TAGS .bundle bin/ config/aliases +.sass-cache diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index df89a372c..0520a8c77 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -113,5 +113,19 @@ module ApplicationHelper end end + def admin_value(v) + if v.nil? + nil + elsif v.instance_of?(Time) + admin_date(v) + else + h(v) + end + end + + def admin_date(date) + "#{I18n.l(date, :format => "%e %B %Y %H:%M:%S")} (#{_('{{length_of_time}} ago', :length_of_time => time_ago_in_words(date))})" + end + end diff --git a/app/helpers/link_to_helper.rb b/app/helpers/link_to_helper.rb index 914b4dc12..f621721b6 100755 --- a/app/helpers/link_to_helper.rb +++ b/app/helpers/link_to_helper.rb @@ -17,14 +17,18 @@ module LinkToHelper return show_request_url(params.merge(extra_params)) end - def request_link(info_request) - link_to h(info_request.title), request_url(info_request) + def request_link(info_request, cls=nil ) + link_to h(info_request.title), request_url(info_request), :class => cls end def request_admin_url(info_request) return admin_url('request/show/' + info_request.id.to_s) end + def request_admin_link(info_request, name="admin", cls=nil) + link_to name, request_admin_url(info_request), :class => cls + end + def request_both_links(info_request) link_to(h(info_request.title), main_url(request_url(info_request))) + " (" + link_to("admin", request_admin_url(info_request)) + ")" end @@ -66,8 +70,8 @@ module LinkToHelper def public_body_link_short(public_body) link_to h(public_body.short_or_long_name), public_body_url(public_body) end - def public_body_link(public_body) - link_to h(public_body.name), public_body_url(public_body) + def public_body_link(public_body, cls=nil) + link_to h(public_body.name), public_body_url(public_body), :class => cls end def public_body_link_absolute(public_body) # e.g. for in RSS link_to h(public_body.name), main_url(public_body_url(public_body)) @@ -86,8 +90,8 @@ module LinkToHelper def user_url(user) return show_user_url(:url_name => user.url_name, :only_path => true) end - def user_link(user) - link_to h(user.name), user_url(user) + def user_link(user, cls=nil) + link_to h(user.name), user_url(user), :class => cls end def user_link_absolute(user) link_to h(user.name), main_url(user_url(user)) @@ -112,6 +116,9 @@ module LinkToHelper def user_admin_url(user) return admin_url('user/show/' + user.id.to_s) end + def user_admin_link(user, name="admin", cls=nil) + link_to name, user_admin_url(user), :class => cls + end def user_both_links(user) link_to(h(user.name), main_url(user_url(user))) + " (" + link_to("admin", user_admin_url(user)) + ")" end diff --git a/app/models/censor_rule.rb b/app/models/censor_rule.rb index 201e60746..72b92d462 100644 --- a/app/models/censor_rule.rb +++ b/app/models/censor_rule.rb @@ -51,7 +51,10 @@ class CensorRule < ActiveRecord::Base errors.add("Censor must apply to an info request a user or a body; ") end end -end - - + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index 44a1079cd..b3d5ba640 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -84,7 +84,9 @@ class Comment < ActiveRecord::Base return Comment.find(:first, :conditions => [ "info_request_id = ? and body = ?", info_request_id, body ]) end end - + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end end - - diff --git a/app/models/incoming_message.rb b/app/models/incoming_message.rb index 8de6e5ba8..9dcd8c1bc 100644 --- a/app/models/incoming_message.rb +++ b/app/models/incoming_message.rb @@ -674,7 +674,6 @@ class IncomingMessage < ActiveRecord::Base end end - # Fix DOS style linefeeds to Unix style ones (or other later regexps won't work) # Needed for e.g. http://www.whatdotheyknow.com/request/60/response/98 text = text.gsub(/\r\n/, "\n") @@ -1029,8 +1028,6 @@ class IncomingMessage < ActiveRecord::Base return get_body_for_quoting + "\n\n" + get_attachment_text_clipped end - - # Has message arrived "recently"? def recently_arrived (Time.now - self.created_at) <= 3.days @@ -1133,7 +1130,14 @@ class IncomingMessage < ActiveRecord::Base return content_type end - private :normalise_content_type + + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end + + private :normalise_content_type end diff --git a/app/models/info_request.rb b/app/models/info_request.rb index 3b86f4cb3..1e55f92ae 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -1059,6 +1059,10 @@ public req.save() end end -end - + def for_admin_column + self.class.content_columns.map{|c| c unless %w(title url_title).include?(c.name) }.compact.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end +end diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb index 9ce191f6b..dacd3223e 100644 --- a/app/models/info_request_event.rb +++ b/app/models/info_request_event.rb @@ -39,25 +39,26 @@ class InfoRequestEvent < ActiveRecord::Base def self.enumerate_event_types [ - 'sent', - 'resent', - 'followup_sent', - 'followup_resent', - - 'edit', # title etc. edited (in admin interface) - 'edit_outgoing', # outgoing message edited (in admin interface) - 'edit_comment', # comment edited (in admin interface) - 'destroy_incoming', # deleted an incoming message (in admin interface) - 'destroy_outgoing', # deleted an outgoing message (in admin interface) - 'redeliver_incoming', # redelivered an incoming message elsewhere (in admin interface) - 'move_request', # changed user or public body (in admin interface) - 'manual', # you did something in the db by hand - - 'response', - 'comment', - 'status_update', + 'sent', + 'resent', + 'followup_sent', + 'followup_resent', + + 'edit', # title etc. edited (in admin interface) + 'edit_outgoing', # outgoing message edited (in admin interface) + 'edit_comment', # comment edited (in admin interface) + 'destroy_incoming', # deleted an incoming message (in admin interface) + 'destroy_outgoing', # deleted an outgoing message (in admin interface) + 'redeliver_incoming', # redelivered an incoming message elsewhere (in admin interface) + 'move_request', # changed user or public body (in admin interface) + 'manual', # you did something in the db by hand + + 'response', + 'comment', + 'status_update' ] end + validates_inclusion_of :event_type, :in => enumerate_event_types # user described state (also update in info_request) @@ -440,7 +441,9 @@ class InfoRequestEvent < ActiveRecord::Base return ret end - + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end end - - diff --git a/app/models/outgoing_message.rb b/app/models/outgoing_message.rb index 29445d587..c29cbb785 100644 --- a/app/models/outgoing_message.rb +++ b/app/models/outgoing_message.rb @@ -271,6 +271,12 @@ class OutgoingMessage < ActiveRecord::Base def purge_in_cache self.info_request.purge_in_cache end + + def for_admin_column + self.class.content_columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end end diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 0e21037ef..ae902dac6 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -189,6 +189,25 @@ class PublicBody < ActiveRecord::Base text = text.gsub(/\n/, '<br>') return text end + + def compare(previous = nil) + if previous.nil? + yield([]) + else + v = self + changes = self.class.content_columns.inject([]) {|memo, c| + unless %w(version last_edit_editor last_edit_comment updated_at).include?(c.name) + from = previous.send(c.name) + to = self.send(c.name) + memo << { :name => c.human_name, :from => from, :to => to } if from != to + end + memo + } + changes.each do |change| + yield(change) + end + end + end end acts_as_xapian :texts => [ :name, :short_name, :notes ], @@ -552,6 +571,12 @@ class PublicBody < ActiveRecord::Base self.info_requests.each {|x| x.purge_in_cache} end + def for_admin_column + self.class.content_columns.map{|c| c unless %w(name last_edit_comment).include?(c.name)}.compact.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s, column.name) + end + end + end diff --git a/app/models/user.rb b/app/models/user.rb index cd8d3e721..4a7153b3f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -400,6 +400,17 @@ class User < ActiveRecord::Base return self.email_confirmed end + def for_admin_column(complete = false) + if complete + columns = self.class.content_columns + else + columns = self.class.content_columns.map{|c| c if %w(created_at updated_at admin_level email_confirmed).include?(c.name) }.compact + end + columns.each do |column| + yield(column.human_name, self.send(column.name), column.type.to_s) + end + end + ## Private instance methods private @@ -430,4 +441,3 @@ class User < ActiveRecord::Base end end - diff --git a/app/views/general/_locale_switcher.rhtml b/app/views/general/_locale_switcher.rhtml index 27e492e84..6d1d5fad3 100644 --- a/app/views/general/_locale_switcher.rhtml +++ b/app/views/general/_locale_switcher.rhtml @@ -1,11 +1,13 @@ - <% if FastGettext.default_available_locales.length > 1 && !params.empty? %> - <div id="user_locale_switcher"> - <% for possible_locale in FastGettext.default_available_locales %> - <% if possible_locale == I18n.locale.to_s %> - <span class="active"><%= locale_name(possible_locale) %></span> - <% else %> - <a href="<%= locale_switcher(possible_locale, params) %>"><%= locale_name(possible_locale) %></a> - <% end %> - <% end %> + <% if FastGettext.default_available_locales.length > 1 && !params.empty? %> + <div id="user_locale_switcher" class="well btn-toolbar"> + <div class="btn-group"> + <% for possible_locale in FastGettext.default_available_locales %> + <% if possible_locale == I18n.locale.to_s %> + <a href="#" class="btn disabled"><%= locale_name(possible_locale) %></a> + <% else %> + <a href="<%= locale_switcher(possible_locale, params) %>" class="btn"><%= locale_name(possible_locale) %></a> + <% end %> + <% end %> </div> - <% end %> + </div> + <% end %> diff --git a/commonlib b/commonlib -Subproject 95be1291ff71e37196643b6b83b99d8e25c0d82 +Subproject 3e8038f025de2b916f686b8e90fe00a7111cf5b diff --git a/config/general.yml-example b/config/general.yml-example index 3c50e8005..019eb7ada 100644 --- a/config/general.yml-example +++ b/config/general.yml-example @@ -34,8 +34,12 @@ SPECIAL_REPLY_VERY_LATE_AFTER_DAYS: 60 # example public bodies for the home page, semicolon delimited - short_names FRONTPAGE_PUBLICBODY_EXAMPLES: 'tgq' -# URL of theme to install (when running rails-post-deploy script) -THEME_URL: 'git://github.com/sebbacon/alavetelitheme.git' +# URLs of themes to download and use (when running rails-post-deploy +# script). Earlier in the list means the templates have a higher +# priority. +THEME_URLS: + - 'git://github.com/sebbacon/adminbootstraptheme.git' + - 'git://github.com/sebbacon/alavetelitheme.git' # Whether a user needs to sign in to start the New Request process FORCE_REGISTRATION_ON_NEW_REQUEST: false diff --git a/config/initializers/theme_loader.rb b/config/initializers/theme_loader.rb new file mode 100644 index 000000000..df392cc08 --- /dev/null +++ b/config/initializers/theme_loader.rb @@ -0,0 +1,7 @@ +theme_urls = MySociety::Config.get("THEME_URLS", []) +if ENV["RAILS_ENV"] != "test" # Don't let the theme interfere with Alaveteli specs + for url in theme_urls.reverse + theme_name = url.sub(/.*\/(.*).git/, "\\1") + require File.expand_path "../../../vendor/plugins/#{theme_name}/lib/alavetelitheme.rb", __FILE__ + end +end diff --git a/public/javascripts/admin.coffee b/public/javascripts/admin.coffee new file mode 100644 index 000000000..59c5c0a6f --- /dev/null +++ b/public/javascripts/admin.coffee @@ -0,0 +1,10 @@ +(($) -> + $(document).ready(-> + $('.locales a:first').tab('show') + ) + $('.toggle-hidden').live('click', -> + $(@).parents('td').find('div:hidden').show() + false + ) +)(jQuery) + diff --git a/public/javascripts/admin.js b/public/javascripts/admin.js new file mode 100644 index 000000000..21725ded4 --- /dev/null +++ b/public/javascripts/admin.js @@ -0,0 +1,14 @@ +// Generated by CoffeeScript 1.3.1 +(function() { + + (function($) { + $(document).ready(function() { + return $('.locales a:first').tab('show'); + }); + return $('.toggle-hidden').live('click', function() { + $(this).parents('td').find('div:hidden').show(); + return false; + }); + })(jQuery); + +}).call(this); diff --git a/public/javascripts/bootstrap-collapse.js b/public/javascripts/bootstrap-collapse.js new file mode 100644 index 000000000..9a364468b --- /dev/null +++ b/public/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/public/javascripts/bootstrap-tab.js b/public/javascripts/bootstrap-tab.js new file mode 100644 index 000000000..26c9ece75 --- /dev/null +++ b/public/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/script/rails-post-deploy b/script/rails-post-deploy index b0c0acdc5..370819c67 100755 --- a/script/rails-post-deploy +++ b/script/rails-post-deploy @@ -81,6 +81,16 @@ else bundle exec bundle install fi +if [ -n "$OPTION_THEME_URLS" ] +then + echo $OPTION_THEME_URLS + for THEME in $OPTION_THEME_URLS + do + script/plugin install --force $THEME + done +fi + +# Old version of the above, for backwards compatibility if [ -n "$OPTION_THEME_URL" ] then script/plugin install --force $OPTION_THEME_URL |