aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--app/helpers/application_helper.rb14
-rwxr-xr-xapp/helpers/link_to_helper.rb19
-rw-r--r--app/models/censor_rule.rb9
-rw-r--r--app/models/comment.rb8
-rw-r--r--app/models/incoming_message.rb12
-rw-r--r--app/models/info_request.rb8
-rw-r--r--app/models/info_request_event.rb43
-rw-r--r--app/models/outgoing_message.rb6
-rw-r--r--app/models/public_body.rb25
-rw-r--r--app/models/user.rb12
-rw-r--r--app/views/general/_locale_switcher.rhtml22
m---------commonlib0
-rw-r--r--config/general.yml-example8
-rw-r--r--config/initializers/theme_loader.rb7
-rw-r--r--public/javascripts/admin.coffee10
-rw-r--r--public/javascripts/admin.js14
-rw-r--r--public/javascripts/bootstrap-collapse.js138
-rw-r--r--public/javascripts/bootstrap-tab.js130
-rwxr-xr-xscript/rails-post-deploy10
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