Merge pull request #2183 from consul/related-content
Related contents functionalities
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
// 18. Banners
|
// 18. Banners
|
||||||
// 19. Recommended Section Home
|
// 19. Recommended Section Home
|
||||||
// 20. Documents
|
// 20. Documents
|
||||||
|
// 21. Related content
|
||||||
//
|
//
|
||||||
|
|
||||||
// 01. Global styles
|
// 01. Global styles
|
||||||
@@ -2391,3 +2392,56 @@ table {
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 21. Related content
|
||||||
|
// -------------------
|
||||||
|
|
||||||
|
.related-content {
|
||||||
|
border-top: 1px solid $border;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: rem-calc(24);
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #4f4f4f;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-related-content {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
@include breakpoint(medium) {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-content-list {
|
||||||
|
list-style-type: none;
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
border-bottom: 1px solid $border;
|
||||||
|
padding: $line-height / 4;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top: 1px solid $border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: $base-font-size;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #4f4f4f;
|
||||||
|
font-size: rem-calc(12);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag {
|
||||||
|
margin-top: $line-height / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ class Management::ProposalsController < Management::BaseController
|
|||||||
def show
|
def show
|
||||||
super
|
super
|
||||||
@notifications = @proposal.notifications
|
@notifications = @proposal.notifications
|
||||||
|
@related_contents = Kaminari.paginate_array(@proposal.relationed_contents).page(params[:page]).per(5)
|
||||||
|
|
||||||
redirect_to management_proposal_path(@proposal), status: :moved_permanently if request.path != management_proposal_path(@proposal)
|
redirect_to management_proposal_path(@proposal), status: :moved_permanently if request.path != management_proposal_path(@proposal)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class ProposalsController < ApplicationController
|
|||||||
def show
|
def show
|
||||||
super
|
super
|
||||||
@notifications = @proposal.notifications
|
@notifications = @proposal.notifications
|
||||||
|
@related_contents = Kaminari.paginate_array(@proposal.relationed_contents).page(params[:page]).per(5)
|
||||||
|
|
||||||
redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal)
|
redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
42
app/controllers/related_contents_controller.rb
Normal file
42
app/controllers/related_contents_controller.rb
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
class RelatedContentsController < ApplicationController
|
||||||
|
VALID_URL = /#{Setting['url']}\/.*\/.*/
|
||||||
|
|
||||||
|
skip_authorization_check
|
||||||
|
|
||||||
|
def create
|
||||||
|
if relationable_object && related_object
|
||||||
|
@relationable.relate_content(@related)
|
||||||
|
|
||||||
|
flash[:success] = t('related_content.success')
|
||||||
|
else
|
||||||
|
flash[:error] = t('related_content.error', url: Setting['url'])
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to @relationable
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid_url?
|
||||||
|
params[:url].match(VALID_URL)
|
||||||
|
end
|
||||||
|
|
||||||
|
def relationable_object
|
||||||
|
@relationable = (params[:relationable_klass].singularize.camelize.constantize).find_by_id(params[:relationable_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def related_object
|
||||||
|
begin
|
||||||
|
if valid_url?
|
||||||
|
url = params[:url]
|
||||||
|
|
||||||
|
related_klass = url.match(/\/(#{RelatedContent::RELATIONABLE_MODELS.join("|")})\//)[0].gsub("/", "")
|
||||||
|
related_id = url.match(/\/[0-9]+/)[0].gsub("/", "")
|
||||||
|
|
||||||
|
@related = (related_klass.singularize.camelize.constantize).find_by_id(related_id)
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
class RelatedContent < ActiveRecord::Base
|
class RelatedContent < ActiveRecord::Base
|
||||||
RELATED_CONTENTS_REPORT_THRESHOLD = Setting['related_contents_report_threshold'].to_i
|
RELATED_CONTENTS_REPORT_THRESHOLD = Setting['related_contents_report_threshold'].to_i
|
||||||
|
RELATIONABLE_MODELS = %w{proposals debates}
|
||||||
|
|
||||||
belongs_to :parent_relationable, polymorphic: true
|
belongs_to :parent_relationable, polymorphic: true
|
||||||
belongs_to :child_relationable, polymorphic: true
|
belongs_to :child_relationable, polymorphic: true
|
||||||
|
|||||||
@@ -108,6 +108,8 @@
|
|||||||
|
|
||||||
<%= render 'shared/geozone', geozonable: @proposal %>
|
<%= render 'shared/geozone', geozonable: @proposal %>
|
||||||
|
|
||||||
|
<%= render 'relationable/related_content', relationable: @proposal %>
|
||||||
|
|
||||||
<div class="js-moderator-proposal-actions margin">
|
<div class="js-moderator-proposal-actions margin">
|
||||||
<%= render 'proposals/actions', proposal: @proposal %>
|
<%= render 'proposals/actions', proposal: @proposal %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
22
app/views/relationable/_form.html.erb
Normal file
22
app/views/relationable/_form.html.erb
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<%= form_tag related_contents_path, method: :post, id: "related_content", class: "hide", "data-toggler": ".hide" do %>
|
||||||
|
<label><%= t("related_content.label") %></label>
|
||||||
|
|
||||||
|
<p class="help-text" id="related_content_help_text">
|
||||||
|
<%= t("related_content.help", models: t('related_content.content_title').values.to_sentence, org: setting['org_name']) %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-field">
|
||||||
|
<%= text_field_tag :url, '',
|
||||||
|
"aria-describedby": "related_content_help_text",
|
||||||
|
placeholder: t("related_content.placeholder", url: setting['url']) %>
|
||||||
|
|
||||||
|
<%= hidden_field_tag :relationable_klass, relationable.class.name %>
|
||||||
|
<%= hidden_field_tag :relationable_id, relationable.id %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group-button">
|
||||||
|
<%= submit_tag t("related_content.submit"), class: "button" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
18
app/views/relationable/_related_content.html.erb
Normal file
18
app/views/relationable/_related_content.html.erb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<div class="related-content padding">
|
||||||
|
|
||||||
|
<div class="margin-bottom">
|
||||||
|
<h2 class="inline-block">
|
||||||
|
<%= t("related_content.title") %> <span>(<%= relationable.relationed_contents.count %>)</span>
|
||||||
|
</h2>
|
||||||
|
<a>
|
||||||
|
<button type="button" data-toggle="related_content" class="add-related-content" id="add-related-content">
|
||||||
|
<%= t("related_content.add") %>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render 'relationable/form', relationable: relationable %>
|
||||||
|
|
||||||
|
<%= render 'relationable/related_list', relationable: relationable %>
|
||||||
|
|
||||||
|
</div>
|
||||||
16
app/views/relationable/_related_list.html.erb
Normal file
16
app/views/relationable/_related_list.html.erb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<ul class="related-content-list" id="related-content-list">
|
||||||
|
<% @related_contents.each do |related| %>
|
||||||
|
<li>
|
||||||
|
<a href="#" class="float-right flag">
|
||||||
|
<span class="icon-flag"></span> <!-- This should be similar to comments/flag_actions -->
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<span><%= t("related_content.content_title.#{related.class.name.downcase}") %></span><br>
|
||||||
|
<h3 class="inline-block">
|
||||||
|
<%= link_to related.title, eval("#{related.class.name.downcase}_path(related)") %>
|
||||||
|
</h3>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<%= paginate @related_contents %>
|
||||||
@@ -811,3 +811,15 @@ en:
|
|||||||
invisible_captcha:
|
invisible_captcha:
|
||||||
sentence_for_humans: "If you are human, ignore this field"
|
sentence_for_humans: "If you are human, ignore this field"
|
||||||
timestamp_error_message: "Sorry, that was too quick! Please resubmit."
|
timestamp_error_message: "Sorry, that was too quick! Please resubmit."
|
||||||
|
related_content:
|
||||||
|
title: "Related content"
|
||||||
|
add: "Add related content"
|
||||||
|
label: "Link to related content"
|
||||||
|
placeholder: "%{url}"
|
||||||
|
help: "You can add links of %{models} inside of %{org}."
|
||||||
|
submit: "Add"
|
||||||
|
error: "Link not valid. Remember to start with %{url}."
|
||||||
|
success: "You added a new related content"
|
||||||
|
content_title:
|
||||||
|
proposal: "Proposal"
|
||||||
|
debate: "Debate"
|
||||||
|
|||||||
@@ -808,3 +808,15 @@ es:
|
|||||||
invisible_captcha:
|
invisible_captcha:
|
||||||
sentence_for_humans: "Si eres humano, por favor ignora este campo"
|
sentence_for_humans: "Si eres humano, por favor ignora este campo"
|
||||||
timestamp_error_message: "Eso ha sido demasiado rápido. Por favor, reenvía el formulario."
|
timestamp_error_message: "Eso ha sido demasiado rápido. Por favor, reenvía el formulario."
|
||||||
|
related_content:
|
||||||
|
title: "Contenido relacionado"
|
||||||
|
add: "Añadir contenido relacionado"
|
||||||
|
label: "Enlace a contenido relacionado"
|
||||||
|
placeholder: "%{url}"
|
||||||
|
help: "Puedes introducir cualquier enlace de %{models} que esté dentro de %{org}."
|
||||||
|
submit: "Añadir"
|
||||||
|
error: "Enlace no válido. Recuerda que debe empezar por %{url}."
|
||||||
|
success: "Has añadido un nuevo contenido relacionado"
|
||||||
|
content_title:
|
||||||
|
proposal: "Propuesta"
|
||||||
|
debate: "Debate"
|
||||||
|
|||||||
@@ -462,6 +462,8 @@ Rails.application.routes.draw do
|
|||||||
root to: "dashboard#index"
|
root to: "dashboard#index"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :related_contents, only: [:create]
|
||||||
|
|
||||||
# GraphQL
|
# GraphQL
|
||||||
get '/graphql', to: 'graphql#query'
|
get '/graphql', to: 'graphql#query'
|
||||||
post '/graphql', to: 'graphql#query'
|
post '/graphql', to: 'graphql#query'
|
||||||
|
|||||||
@@ -142,6 +142,73 @@ feature 'Proposals' do
|
|||||||
visit proposal_path(proposal)
|
visit proposal_path(proposal)
|
||||||
expect(page).not_to have_content "Access the community"
|
expect(page).not_to have_content "Access the community"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scenario 'related contents are listed' do
|
||||||
|
proposal1 = create(:proposal)
|
||||||
|
proposal2 = create(:proposal)
|
||||||
|
related_content = create(:related_content, parent_relationable: proposal1, child_relationable: proposal2)
|
||||||
|
|
||||||
|
visit proposal_path(proposal1)
|
||||||
|
within("#related-content-list") do
|
||||||
|
expect(page).to have_content(proposal2.title)
|
||||||
|
end
|
||||||
|
|
||||||
|
visit proposal_path(proposal2)
|
||||||
|
within("#related-content-list") do
|
||||||
|
expect(page).to have_content(proposal1.title)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'related contents can be added' do
|
||||||
|
proposal1 = create(:proposal)
|
||||||
|
proposal2 = create(:proposal)
|
||||||
|
debate1 = create(:debate)
|
||||||
|
|
||||||
|
visit proposal_path(proposal1)
|
||||||
|
|
||||||
|
expect(page).to have_selector('#related_content', visible: false)
|
||||||
|
click_on("Add related content")
|
||||||
|
expect(page).to have_selector('#related_content', visible: true)
|
||||||
|
|
||||||
|
within("#related_content") do
|
||||||
|
fill_in 'url', with: "#{Setting['url']}/proposals/#{proposal2.to_param}"
|
||||||
|
click_button "Add"
|
||||||
|
end
|
||||||
|
|
||||||
|
within("#related-content-list") do
|
||||||
|
expect(page).to have_content(proposal2.title)
|
||||||
|
end
|
||||||
|
|
||||||
|
visit proposal_path(proposal2)
|
||||||
|
|
||||||
|
within("#related-content-list") do
|
||||||
|
expect(page).to have_content(proposal1.title)
|
||||||
|
end
|
||||||
|
|
||||||
|
within("#related_content") do
|
||||||
|
fill_in 'url', with: "#{Setting['url']}/debates/#{debate1.to_param}"
|
||||||
|
click_button "Add"
|
||||||
|
end
|
||||||
|
|
||||||
|
within("#related-content-list") do
|
||||||
|
expect(page).to have_content(debate1.title)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'if related content URL is invalid returns error' do
|
||||||
|
proposal1 = create(:proposal)
|
||||||
|
|
||||||
|
visit proposal_path(proposal1)
|
||||||
|
|
||||||
|
click_on("Add related content")
|
||||||
|
|
||||||
|
within("#related_content") do
|
||||||
|
fill_in 'url', with: "http://invalidurl.com"
|
||||||
|
click_button "Add"
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(page).to have_content("Link not valid. Remember to start with #{Setting[:url]}.")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "Embedded video" do
|
context "Embedded video" do
|
||||||
|
|||||||
Reference in New Issue
Block a user