diff --git a/app/assets/stylesheets/responsive/_citations.scss b/app/assets/stylesheets/responsive/_citations.scss new file mode 100644 index 0000000000..e84e21d271 --- /dev/null +++ b/app/assets/stylesheets/responsive/_citations.scss @@ -0,0 +1,33 @@ +.citations-types { + border-top: 1px solid #d1d1d1; + border-bottom: 1px solid #d1d1d1; + margin-top: 2em; + margin-bottom: 1em; +} + +ul.citations-list { + list-style: none; + padding-left: 0; + + li.citations-list__citation { + padding: 1em 0; + border-bottom: 1px solid #d1d1d1; + + display: flex; + align-items: flex-start; + gap: 1em; + + .citation_icon { + flex-shrink: 0; + } + + .citation__title { + flex-basis: 50%; + word-break: break-word; + } + + .citation__description { + flex-basis: 50%; + } + } +} diff --git a/app/assets/stylesheets/responsive/_sidebar_layout.scss b/app/assets/stylesheets/responsive/_sidebar_layout.scss index 8b8c6691d4..d0d689eac3 100644 --- a/app/assets/stylesheets/responsive/_sidebar_layout.scss +++ b/app/assets/stylesheets/responsive/_sidebar_layout.scss @@ -36,7 +36,7 @@ } .sidebar__section.citations { - .citations-list { + .citations-list--compact { overflow-wrap: break-word; } } diff --git a/app/assets/stylesheets/responsive/_sidebar_style.scss b/app/assets/stylesheets/responsive/_sidebar_style.scss index 094a85d0df..29bc3d11df 100644 --- a/app/assets/stylesheets/responsive/_sidebar_style.scss +++ b/app/assets/stylesheets/responsive/_sidebar_style.scss @@ -109,7 +109,7 @@ } .sidebar__section.citations { - .citations-list { + .citations-list--compact { list-style: none; padding: 0; } diff --git a/app/assets/stylesheets/responsive/all.scss b/app/assets/stylesheets/responsive/all.scss index 0181ce6d08..deed7da137 100644 --- a/app/assets/stylesheets/responsive/all.scss +++ b/app/assets/stylesheets/responsive/all.scss @@ -89,6 +89,8 @@ @import "responsive/_notes_layout.scss"; @import "responsive/_notes_styles.scss"; +@import "responsive/_citations"; + @import "responsive/alaveteli_pro/_pro_layout"; @import "responsive/alaveteli_pro/_pro_style"; diff --git a/app/controllers/admin/citations_controller.rb b/app/controllers/admin/citations_controller.rb index 51a42fa130..f5d0d348c9 100644 --- a/app/controllers/admin/citations_controller.rb +++ b/app/controllers/admin/citations_controller.rb @@ -1,8 +1,36 @@ class Admin::CitationsController < AdminController + before_action :find_citation, except: :index + def index @citations = Citation. order(created_at: :desc). paginate(page: params[:page], per_page: 50) end + + def edit + end + + def update + if @citation.update(citation_params) + redirect_to admin_citations_path, notice: 'Citation updated successfully.' + else + render :edit + end + end + + def destroy + @citation.destroy + redirect_to admin_citations_path, notice: 'Citation deleted successfully.' + end + + private + + def find_citation + @citation = Citation.find(params[:id]) + end + + def citation_params + params.require(:citation).permit(:source_url, :title, :description) + end end diff --git a/app/controllers/citations_controller.rb b/app/controllers/citations_controller.rb index 541c42a5f9..6c1968eddf 100644 --- a/app/controllers/citations_controller.rb +++ b/app/controllers/citations_controller.rb @@ -2,9 +2,24 @@ # Controller to create a new Citation for an InfoRequest or an InfoRequestBatch. # class CitationsController < ApplicationController - before_action :authenticate - before_action :load_resource_and_authorise - before_action :set_in_pro_area + before_action :authenticate, except: :index + before_action :load_resource_and_authorise, except: :index + before_action :set_in_pro_area, except: :index + + skip_before_action :html_response, only: :index + + def index + per_page = 10 + page = get_search_page_from_params + + @citations = Citation.not_embargoed.order(created_at: :desc). + paginate(page: page, per_page: per_page) + + respond_to do |format| + format.html { @has_json = true } + format.json { render json: @citations } + end + end def new @citation = current_user.citations.build diff --git a/app/helpers/admin/citations_helper.rb b/app/helpers/admin/citations_helper.rb index a383aa7d67..a26e947f73 100644 --- a/app/helpers/admin/citations_helper.rb +++ b/app/helpers/admin/citations_helper.rb @@ -3,16 +3,24 @@ module Admin::CitationsHelper ICONS = { journalism: '🗞️', campaigning: '📣', - academic: '🎓', + research: '📚', other: '🌐' }.with_indifferent_access.freeze + def citation_title(citation) + citation.title.presence || citation.source_url + end + def citation_icon(citation) + citation_icon_for_type(citation.type) + end + + def citation_icon_for_type(type) html_attrs = { - title: citation.type.humanize, - class: "citation-icon citation-icon--#{citation.type}" + title: type.humanize, + class: "citation-icon citation-icon--#{type}" } - tag.span(ICONS.fetch(citation.type), **html_attrs) + tag.span(ICONS.fetch(type), **html_attrs) end end diff --git a/app/helpers/admin/link_helper.rb b/app/helpers/admin/link_helper.rb index d9241392d8..8cc4c433c6 100644 --- a/app/helpers/admin/link_helper.rb +++ b/app/helpers/admin/link_helper.rb @@ -108,6 +108,15 @@ def category_both_links(category) title: admin_title) end + def citation_both_links(citation) + title = 'View citation' + icon = eye + + link_to(icon, citation.source_url, title: title) + ' ' + + link_to(citation.source_url, edit_admin_citation_path(citation), + title: admin_title) + end + def admin_title 'View full details' end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index b3ed67569f..a52c3985bb 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -8,4 +8,28 @@ class ApplicationRecord < ActiveRecord::Base def self.admin_title name end + + def self.belongs_to(name, scope = nil, **options) + if options.key?(:via) + via = options.delete(:via) + polymorphic_association = reflect_on_association(via) + + unless polymorphic_association&.polymorphic? + raise ArgumentError, "Association #{via} must be polymorphic" + end + + options[:foreign_key] ||= polymorphic_association.foreign_key + options[:class_name] ||= name.to_s.classify + + scope = -> { + where( + polymorphic_association.active_record.table_name => { + polymorphic_association.foreign_type => options[:class_name] + } + ) + } + end + + super(name, scope, **options) + end end diff --git a/app/models/citation.rb b/app/models/citation.rb index 460eb357ef..11bc602fc1 100644 --- a/app/models/citation.rb +++ b/app/models/citation.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20210114161442 +# Schema version: 20241007090524 # # Table name: citations # @@ -11,31 +11,48 @@ # type :string # created_at :datetime not null # updated_at :datetime not null +# title :string +# description :text # ## -# A Citation of an InfoRequest or InfoRequestBatch in news stories or an -# academic paper +# A Citation of an InfoRequest or InfoRequestBatch # class Citation < ApplicationRecord + include Rails.application.routes.url_helpers + include LinkToHelper + self.inheritance_column = nil belongs_to :user, inverse_of: :citations belongs_to :citable, polymorphic: true + belongs_to :info_request, via: :citable + belongs_to :info_request_batch, via: :citable + validates :user, :citable, presence: true validates :citable_type, inclusion: { in: %w(InfoRequest InfoRequestBatch) } validates :source_url, length: { maximum: 255, message: _('Source URL is too long') }, format: { with: /\Ahttps?:\/\/.*\z/, message: _('Please enter a Source URL') } - validates :type, inclusion: { in: %w(journalism academic campaigning other), + validates :type, inclusion: { in: %w(journalism research campaigning other), message: _('Please select a type') } scope :newest, ->(limit = 1) do order(created_at: :desc).limit(limit) end + scope :not_embargoed, -> do + left_joins(info_request: :embargo, info_request_batch: []). + where(citable_type: 'InfoRequest'). + merge(InfoRequest.not_embargoed). + or( + where(citable_type: 'InfoRequestBatch'). + merge(InfoRequestBatch.not_embargoed) + ) + end + scope :for_request, ->(info_request) do where(citable: info_request). or(where(citable: info_request.info_request_batch)) @@ -49,4 +66,17 @@ class Citation < ApplicationRecord def applies_to_batch_request? citable.is_a?(InfoRequestBatch) end + + def as_json(_options) + citable_path = case citable + when InfoRequest + request_path(citable) + when InfoRequestBatch + info_request_batch_path(citable) + end + + attributes. + except('user_id', 'citable_id', 'citable_type'). + merge(citable_path: citable_path) + end end diff --git a/app/views/admin/citations/_list.html.erb b/app/views/admin/citations/_list.html.erb index d620a9b7f7..63b4a59715 100644 --- a/app/views/admin/citations/_list.html.erb +++ b/app/views/admin/citations/_list.html.erb @@ -4,7 +4,7 @@
<%= citation_icon(citation) %> - <%= link_to citation.source_url, citation.source_url %> + <%= both_links(citation) %> diff --git a/app/views/admin/citations/edit.html.erb b/app/views/admin/citations/edit.html.erb new file mode 100644 index 0000000000..68469be920 --- /dev/null +++ b/app/views/admin/citations/edit.html.erb @@ -0,0 +1,41 @@ +
+
+ +
+
+ +<%= form_for [:admin, @citation], class: 'form form-inline' do |f| %> +
+ <%= f.label :source_url %> +
+ <%= f.text_field :source_url, class: 'form-control' %> +
+
+ +
+ <%= f.label :title %> +
+ <%= f.text_field :title, class: 'form-control' %> +
+
+ +
+ <%= f.label :description, class: 'control-label' %> +
+ <%= f.text_area :description, class: 'input-block-level', rows: 10 %> +
+
+ +
+ <%= f.submit 'Update ciation', class: 'btn btn-success' %> +
+<% end %> + +<%= form_for [:admin, @citation], class: 'form form-inline', method: 'delete' do |f| %> + <%= f.submit 'Destroy citation', + class: 'btn btn-danger', + data: { confirm: 'Are you sure? This is irreversible.' } %> + (this is permanent!) +<% end %> diff --git a/app/views/citations/_citation.html.erb b/app/views/citations/_citation.html.erb index eb4543302d..88324c94a6 100644 --- a/app/views/citations/_citation.html.erb +++ b/app/views/citations/_citation.html.erb @@ -1,7 +1,29 @@
  • - <%= - MySociety::Format.make_clickable( - citation.source_url, contract: true, nofollow: true - ).html_safe - %> +
    + <%= citation_icon(citation) %> +
    + +
    + <%= link_to citation_title(citation), citation.source_url, rel: 'nofollow' %> +
    + <% case citation.citable %> + <% when InfoRequest %> + <%= _('{{title}} requested from {{public_body_name}} on {{date}}', + title: citation.citable.title, + info_request_url: request_path(citation.citable), + public_body_name: citation.citable.public_body.name, + public_body_url: public_body_path(citation.citable.public_body), + date: simple_date(citation.citable.created_at)) %> + <% when InfoRequestBatch %> + <%= _('{{title}} part of a batch sent to {{count}} authorities on {{date}}', + title: citation.citable.title, + info_request_batch_url: info_request_batch_path(citation.citable), + count: citation.citable.public_bodies.count, + date: simple_date(citation.citable.created_at)) %> + <% end %> +
    + +
    + <%= citation.description %> +
  • diff --git a/app/views/citations/_compact.html.erb b/app/views/citations/_compact.html.erb new file mode 100644 index 0000000000..1df58e66b7 --- /dev/null +++ b/app/views/citations/_compact.html.erb @@ -0,0 +1,3 @@ +
  • + <%= link_to citation_title(citation), citation.source_url, rel: 'nofollow' %> +
  • diff --git a/app/views/citations/index.html.erb b/app/views/citations/index.html.erb new file mode 100644 index 0000000000..1221e42dd0 --- /dev/null +++ b/app/views/citations/index.html.erb @@ -0,0 +1,76 @@ +<% @title = _('FOI in Action') %> +

    <%= @title %>

    + +

    + <%= _(<<~TXT) + Freedom of Information requests empower individuals, journalists, \ + researchers, and campaigners to hold organisations accountable and shed \ + light on matters of public interest. Below are real-world examples where \ + FOI responses have made a significant impact. + TXT + %> +

    + +
    +
    +

    + <%= citation_icon_for_type('journalism') %> + <%= _('Journalism') %> +

    +

    + <%= _(<<~TXT) + Journalists often use FOI data to uncover stories that inform the \ + public, reveal injustices, or investigate policy failures + TXT + %> +

    +
    + +
    +

    + <%= citation_icon_for_type('research') %> + <%= _('Research') %> +

    +

    + <%= _(<<~TXT) + FOI requests are a valuable tool for researchers seeking data that \ + might otherwise remain behind closed doors + TXT + %> +

    +
    + +
    +

    + <%= citation_icon_for_type('campaigning') %> + <%= _('Campaigning and Advocacy') %> +

    +

    + <%= _(<<~TXT) + FOI requests help campaigners bring critical information to light, \ + often influencing public discourse and policy decisions + TXT + %> +

    +
    + +
    +

    + <%= citation_icon_for_type('other') %> + <%= _('Other') %> +

    +

    + <%= _(<<~TXT) + FOI requests serve as a cornerstone of democracy by providing \ + transparency in government and public institutions + TXT + %> +

    +
    +
    + + + +<%= will_paginate(@citations) %> diff --git a/app/views/citations/new.html.erb b/app/views/citations/new.html.erb index 996858f3dd..ac78742a95 100644 --- a/app/views/citations/new.html.erb +++ b/app/views/citations/new.html.erb @@ -1,13 +1,17 @@ -

    <%= _('In the News') %>

    +

    <%= _('FOI in Action') %>

    <% if @resource.is_a?(InfoRequestBatch) %> - <%= _('Has this batch request been referenced in a news article or ' \ - 'academic paper? Let us know:') %> + <%= _('Has this batch request been referenced in journalism, ' \ + 'campaigning, or research?') %> <% else %> - <%= _('Has this request been referenced in a news article or academic ' \ - 'paper? Let us know:') %> + <%= _('Has this request been referenced in journalism, campaigning, ' \ + 'or research?') %> <% end %> + + <%= _('Let us know about great examples of ' \ + 'FOI in Action.', + citations_path: citations_path) %>

    <%= form_for @citation, @@ -40,9 +44,9 @@ <%= _('Campaigning') %> -