Merge pull request #1421 from Retrospring/feature/turbo-relationships
Move relationship functionality to Turbo Streams
This commit is contained in:
commit
742492555f
|
@ -1,35 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Ajax::RelationshipController < AjaxController
|
|
||||||
before_action :authenticate_user!
|
|
||||||
|
|
||||||
def create
|
|
||||||
params.require :screen_name
|
|
||||||
|
|
||||||
UseCase::Relationship::Create.call(
|
|
||||||
source_user: current_user,
|
|
||||||
target_user: ::User.find_by!(screen_name: params[:screen_name]),
|
|
||||||
type: params[:type]
|
|
||||||
)
|
|
||||||
@response[:success] = true
|
|
||||||
@response[:message] = t(".#{params[:type]}.success")
|
|
||||||
rescue Errors::Base => e
|
|
||||||
@response[:message] = t(e.locale_tag)
|
|
||||||
ensure
|
|
||||||
return_response
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
UseCase::Relationship::Destroy.call(
|
|
||||||
source_user: current_user,
|
|
||||||
target_user: ::User.find_by!(screen_name: params[:screen_name]),
|
|
||||||
type: params[:type]
|
|
||||||
)
|
|
||||||
@response[:success] = true
|
|
||||||
@response[:message] = t(".#{params[:type]}.success")
|
|
||||||
rescue Errors::Base => e
|
|
||||||
@response[:message] = t(e.locale_tag)
|
|
||||||
ensure
|
|
||||||
return_response
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RelationshipsController < ApplicationController
|
||||||
|
include TurboStreamable
|
||||||
|
|
||||||
|
before_action :authenticate_user!
|
||||||
|
|
||||||
|
turbo_stream_actions :create, :destroy
|
||||||
|
|
||||||
|
def create
|
||||||
|
params.require :screen_name
|
||||||
|
|
||||||
|
UseCase::Relationship::Create.call(
|
||||||
|
source_user: current_user,
|
||||||
|
target_user: ::User.find_by!(screen_name: params[:screen_name]),
|
||||||
|
type: params[:type],
|
||||||
|
)
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.turbo_stream do
|
||||||
|
render turbo_stream: [
|
||||||
|
turbo_stream.replace("#{params[:type]}-#{params[:screen_name]}", partial: "relationships/destroy", locals: { type: params[:type], screen_name: params[:screen_name] }),
|
||||||
|
render_toast(t(".#{params[:type]}.success"))
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
format.html { redirect_back(fallback_location: user_path(username: params[:screen_name])) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
UseCase::Relationship::Destroy.call(
|
||||||
|
source_user: current_user,
|
||||||
|
target_user: ::User.find_by!(screen_name: params[:screen_name]),
|
||||||
|
type: params[:type],
|
||||||
|
)
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.turbo_stream do
|
||||||
|
render turbo_stream: [
|
||||||
|
turbo_stream.replace("#{params[:type]}-#{params[:screen_name]}", partial: "relationships/create", locals: { type: params[:type], screen_name: params[:screen_name] }),
|
||||||
|
render_toast(t(".#{params[:type]}.success"))
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
format.html { redirect_back(fallback_location: user_path(username: params[:screen_name])) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,115 +0,0 @@
|
||||||
import { post } from '@rails/request.js';
|
|
||||||
import { showNotification, showErrorNotification } from 'utilities/notifications';
|
|
||||||
import I18n from 'retrospring/i18n';
|
|
||||||
|
|
||||||
export function userActionHandler(event: Event): void {
|
|
||||||
event.preventDefault();
|
|
||||||
const button: HTMLButtonElement = event.target as HTMLButtonElement;
|
|
||||||
const target = button.dataset.target;
|
|
||||||
const action = button.dataset.action;
|
|
||||||
button.disabled = true;
|
|
||||||
|
|
||||||
let targetURL, relationshipType;
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case 'follow':
|
|
||||||
targetURL = '/ajax/create_relationship';
|
|
||||||
relationshipType = 'follow';
|
|
||||||
break;
|
|
||||||
case 'unfollow':
|
|
||||||
targetURL = '/ajax/destroy_relationship';
|
|
||||||
relationshipType = 'follow';
|
|
||||||
break;
|
|
||||||
case 'block':
|
|
||||||
targetURL = '/ajax/create_relationship';
|
|
||||||
relationshipType = 'block';
|
|
||||||
break;
|
|
||||||
case 'unblock':
|
|
||||||
targetURL = '/ajax/destroy_relationship';
|
|
||||||
relationshipType = 'block';
|
|
||||||
break;
|
|
||||||
case 'mute':
|
|
||||||
targetURL = '/ajax/create_relationship';
|
|
||||||
relationshipType = 'mute';
|
|
||||||
break;
|
|
||||||
case 'unmute':
|
|
||||||
targetURL = '/ajax/destroy_relationship';
|
|
||||||
relationshipType = 'mute';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let success = false;
|
|
||||||
|
|
||||||
post(targetURL, {
|
|
||||||
body: {
|
|
||||||
screen_name: target,
|
|
||||||
type: relationshipType,
|
|
||||||
},
|
|
||||||
contentType: 'application/json'
|
|
||||||
})
|
|
||||||
.then(async response => {
|
|
||||||
const data = await response.json;
|
|
||||||
|
|
||||||
success = data.success;
|
|
||||||
showNotification(data.message, data.success);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
showErrorNotification(I18n.translate('frontend.error.message'));
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
button.disabled = false;
|
|
||||||
if (!success) return;
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case 'follow':
|
|
||||||
button.dataset.action = 'unfollow';
|
|
||||||
button.innerText = I18n.translate('voc.unfollow');
|
|
||||||
button.classList.remove('btn-primary');
|
|
||||||
button.classList.add('btn-default');
|
|
||||||
break;
|
|
||||||
case 'unfollow':
|
|
||||||
resetFollowButton(button);
|
|
||||||
break;
|
|
||||||
case 'block':
|
|
||||||
button.dataset.action = 'unblock';
|
|
||||||
button.querySelector('span').innerText = I18n.translate('voc.unblock');
|
|
||||||
if (button.classList.contains('btn')) {
|
|
||||||
button.classList.remove('btn-primary');
|
|
||||||
button.classList.add('btn-default');
|
|
||||||
}
|
|
||||||
resetFollowButton(document.querySelector<HTMLButtonElement>('button[data-action="unfollow"]'));
|
|
||||||
break;
|
|
||||||
case 'unblock':
|
|
||||||
button.dataset.action = 'block';
|
|
||||||
button.querySelector('span').innerText = I18n.translate('voc.block');
|
|
||||||
if (button.classList.contains('btn')) {
|
|
||||||
button.classList.remove('btn-default');
|
|
||||||
button.classList.add('btn-primary');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'mute':
|
|
||||||
button.dataset.action = 'unmute';
|
|
||||||
button.querySelector('span').innerText = I18n.translate('voc.unmute');
|
|
||||||
if (button.classList.contains('btn')) {
|
|
||||||
button.classList.remove('btn-primary');
|
|
||||||
button.classList.add('btn-default');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'unmute':
|
|
||||||
button.dataset.action = 'mute';
|
|
||||||
button.querySelector('span').innerText = I18n.translate('voc.mute');
|
|
||||||
if (button.classList.contains('btn')) {
|
|
||||||
button.classList.remove('btn-default');
|
|
||||||
button.classList.add('btn-primary');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetFollowButton(button: HTMLButtonElement) {
|
|
||||||
button.dataset.action = 'follow';
|
|
||||||
button.innerText = I18n.translate('voc.follow');
|
|
||||||
button.classList.remove('btn-default');
|
|
||||||
button.classList.add('btn-primary');
|
|
||||||
}
|
|
|
@ -1,12 +1,8 @@
|
||||||
import { userActionHandler } from './action';
|
|
||||||
import { userReportHandler } from './report';
|
import { userReportHandler } from './report';
|
||||||
import registerEvents from 'retrospring/utilities/registerEvents';
|
import registerEvents from 'retrospring/utilities/registerEvents';
|
||||||
|
|
||||||
export default (): void => {
|
export default (): void => {
|
||||||
registerEvents([
|
registerEvents([
|
||||||
{ type: 'click', target: 'button[name=user-action]', handler: userActionHandler, global: true },
|
|
||||||
{ type: 'click', target: '[data-action=block], [data-action=unblock]', handler: userActionHandler, global: true },
|
|
||||||
{ type: 'click', target: '[data-action=mute], [data-action=unmute]', handler: userActionHandler, global: true },
|
|
||||||
{ type: 'click', target: 'a[data-action=report-user]', handler: userReportHandler, global: true }
|
{ type: 'click', target: 'a[data-action=report-user]', handler: userReportHandler, global: true }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
- if type == "follow"
|
||||||
|
= button_to relationships_path(screen_name:, type:), form: { id: "#{type}-#{screen_name}" }, class: "btn btn-primary", form_class: "d-grid" do
|
||||||
|
= t("voc.follow")
|
||||||
|
|
||||||
|
- if type == "block"
|
||||||
|
= button_to relationships_path(screen_name:, type:), form: { id: "#{type}-#{screen_name}" }, class: "dropdown-item" do
|
||||||
|
%i.fa.fa-minus-circle.fa-fw
|
||||||
|
= t("voc.block")
|
||||||
|
|
||||||
|
- if type == "mute"
|
||||||
|
= button_to relationships_path(screen_name:, type:), form: { id: "#{type}-#{screen_name}" }, class: "dropdown-item" do
|
||||||
|
%i.fa.fa-volume-off.fa-fw
|
||||||
|
= t("voc.mute")
|
|
@ -0,0 +1,14 @@
|
||||||
|
- if type == "follow"
|
||||||
|
= button_to relationships_path(screen_name:, type:), method: :delete, form: { id: "#{type}-#{screen_name}" }, class: "btn btn-primary",
|
||||||
|
form_class: "d-grid" do
|
||||||
|
= t("voc.unfollow")
|
||||||
|
|
||||||
|
- if type == "block"
|
||||||
|
= button_to relationships_path(screen_name:, type:), method: :delete, form: { id: "#{type}-#{screen_name}" }, class: "dropdown-item" do
|
||||||
|
%i.fa.fa-minus-circle.fa-fw
|
||||||
|
= t("voc.unblock")
|
||||||
|
|
||||||
|
- if type == "mute"
|
||||||
|
= button_to relationships_path(screen_name:, type:), method: :delete, form: { id: "#{type}-#{screen_name}" }, class: "dropdown-item" do
|
||||||
|
%i.fa.fa-volume-off.fa-fw
|
||||||
|
= t("voc.unmute")
|
|
@ -9,11 +9,9 @@
|
||||||
- elsif user_signed_in?
|
- elsif user_signed_in?
|
||||||
.d-grid.gap-2
|
.d-grid.gap-2
|
||||||
- if own_followings&.include?(user.id) || current_user.following?(user)
|
- if own_followings&.include?(user.id) || current_user.following?(user)
|
||||||
%button.btn.btn-primary{ type: :button, name: 'user-action', data: { action: :unfollow, type: type, target: user.screen_name } }
|
= render "relationships/destroy", type: "follow", screen_name: user.screen_name
|
||||||
= t("voc.unfollow")
|
|
||||||
- else
|
- else
|
||||||
%button.btn.btn-primary{ type: :button, name: 'user-action', data: { action: :follow, type: type, target: user.screen_name } }
|
= render "relationships/create", type: "follow", screen_name: user.screen_name
|
||||||
= t("voc.follow")
|
|
||||||
.btn-group
|
.btn-group
|
||||||
%button.btn.btn-light.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } }
|
%button.btn.btn-light.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } }
|
||||||
= t(".title")
|
= t(".title")
|
||||||
|
@ -23,21 +21,13 @@
|
||||||
%i.fa.fa-list.fa-fw
|
%i.fa.fa-list.fa-fw
|
||||||
= t(".list")
|
= t(".list")
|
||||||
- if own_blocks&.include?(user.id) || current_user.blocking?(user)
|
- if own_blocks&.include?(user.id) || current_user.blocking?(user)
|
||||||
%a.dropdown-item{ href: '#', data: { action: :unblock, target: user.screen_name } }
|
= render "relationships/destroy", type: "block", screen_name: user.screen_name
|
||||||
%i.fa.fa-minus-circle.fa-fw
|
|
||||||
%span.pe-none= t("voc.unblock")
|
|
||||||
- else
|
- else
|
||||||
%a.dropdown-item{ href: '#', data: { action: :block, target: user.screen_name } }
|
= render "relationships/create", type: "block", screen_name: user.screen_name
|
||||||
%i.fa.fa-minus-circle.fa-fw
|
|
||||||
%span.pe-none= t("voc.block")
|
|
||||||
- if own_mutes&.include?(user.id) || current_user.muting?(user)
|
- if own_mutes&.include?(user.id) || current_user.muting?(user)
|
||||||
%a.dropdown-item{ href: '#', data: { action: :unmute, target: user.screen_name } }
|
= render "relationships/destroy", type: "mute", screen_name: user.screen_name
|
||||||
%i.fa.fa-volume-off.fa-fw
|
|
||||||
%span.pe-none= t("voc.unmute")
|
|
||||||
- else
|
- else
|
||||||
%a.dropdown-item{ href: '#', data: { action: :mute, target: user.screen_name } }
|
= render "relationships/create", type: "mute", screen_name: user.screen_name
|
||||||
%i.fa.fa-volume-off.fa-fw
|
|
||||||
%span.pe-none= t("voc.mute")
|
|
||||||
%a.dropdown-item{ href: '#', data: { action: 'report-user', target: user.screen_name } }
|
%a.dropdown-item{ href: '#', data: { action: 'report-user', target: user.screen_name } }
|
||||||
%i.fa.fa-exclamation-triangle.fa-fw
|
%i.fa.fa-exclamation-triangle.fa-fw
|
||||||
= t("voc.report")
|
= t("voc.report")
|
||||||
|
|
|
@ -101,27 +101,6 @@ en:
|
||||||
notfound: "Question does not exist."
|
notfound: "Question does not exist."
|
||||||
noauth: "You are not allowed to delete this question."
|
noauth: "You are not allowed to delete this question."
|
||||||
success: "Successfully deleted question."
|
success: "Successfully deleted question."
|
||||||
relationship:
|
|
||||||
create:
|
|
||||||
block:
|
|
||||||
success: "Successfully blocked user."
|
|
||||||
error: "You are already blocking that user."
|
|
||||||
follow:
|
|
||||||
success: "Successfully followed user."
|
|
||||||
error: "You are already following that user."
|
|
||||||
mute:
|
|
||||||
success: "Successfully muted user."
|
|
||||||
error: "You are already muting that user."
|
|
||||||
destroy:
|
|
||||||
block:
|
|
||||||
success: "Successfully unblocked user."
|
|
||||||
error: "You are not blocking that user."
|
|
||||||
follow:
|
|
||||||
success: "Successfully unfollowed user."
|
|
||||||
error: "You are not following that user."
|
|
||||||
mute:
|
|
||||||
success: "Successfully unmuted user."
|
|
||||||
error: "You are not muting that user."
|
|
||||||
report:
|
report:
|
||||||
create:
|
create:
|
||||||
noauth: :ajax.noauth
|
noauth: :ajax.noauth
|
||||||
|
@ -216,6 +195,27 @@ en:
|
||||||
registrations:
|
registrations:
|
||||||
destroy:
|
destroy:
|
||||||
export_pending: "You may not delete your account while account data is currently being exported."
|
export_pending: "You may not delete your account while account data is currently being exported."
|
||||||
|
relationships:
|
||||||
|
create:
|
||||||
|
block:
|
||||||
|
success: "Successfully blocked user."
|
||||||
|
error: "You are already blocking that user."
|
||||||
|
follow:
|
||||||
|
success: "Successfully followed user."
|
||||||
|
error: "You are already following that user."
|
||||||
|
mute:
|
||||||
|
success: "Successfully muted user."
|
||||||
|
error: "You are already muting that user."
|
||||||
|
destroy:
|
||||||
|
block:
|
||||||
|
success: "Successfully unblocked user."
|
||||||
|
error: "You are not blocking that user."
|
||||||
|
follow:
|
||||||
|
success: "Successfully unfollowed user."
|
||||||
|
error: "You are not following that user."
|
||||||
|
mute:
|
||||||
|
success: "Successfully unmuted user."
|
||||||
|
error: "You are not muting that user."
|
||||||
timeline:
|
timeline:
|
||||||
public:
|
public:
|
||||||
title: "Public Timeline"
|
title: "Public Timeline"
|
||||||
|
|
|
@ -147,6 +147,7 @@ Rails.application.routes.draw do
|
||||||
get "/inbox", to: "inbox#show", as: :inbox
|
get "/inbox", to: "inbox#show", as: :inbox
|
||||||
|
|
||||||
resource :subscriptions, controller: :subscriptions, only: %i[create destroy]
|
resource :subscriptions, controller: :subscriptions, only: %i[create destroy]
|
||||||
|
resource :relationships, only: %i[create destroy]
|
||||||
|
|
||||||
get "/user/:username", to: "user#show"
|
get "/user/:username", to: "user#show"
|
||||||
get "/@:username", to: "user#show", as: :user
|
get "/@:username", to: "user#show", as: :user
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
|
|
||||||
require "rails_helper"
|
require "rails_helper"
|
||||||
|
|
||||||
describe Ajax::RelationshipController, type: :controller do
|
describe RelationshipsController, type: :controller do
|
||||||
|
render_views
|
||||||
|
|
||||||
shared_examples_for "params is empty" do
|
shared_examples_for "params is empty" do
|
||||||
let(:params) { {} }
|
let(:params) { {} }
|
||||||
|
|
||||||
include_examples "ajax does not succeed", "is required"
|
include_examples "turbo does not succeed", "is required"
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:user) { FactoryBot.create(:user) }
|
let!(:user) { FactoryBot.create(:user) }
|
||||||
|
@ -20,13 +22,13 @@ describe Ajax::RelationshipController, type: :controller do
|
||||||
context "screen_name does not exist" do
|
context "screen_name does not exist" do
|
||||||
let(:screen_name) { "peter-witzig" }
|
let(:screen_name) { "peter-witzig" }
|
||||||
|
|
||||||
include_examples "ajax does not succeed", "not found"
|
include_examples "turbo does not succeed", "not found"
|
||||||
end
|
end
|
||||||
|
|
||||||
context "screen_name is current user" do
|
context "screen_name is current user" do
|
||||||
let(:screen_name) { user.screen_name }
|
let(:screen_name) { user.screen_name }
|
||||||
|
|
||||||
include_examples "ajax does not succeed", "yourself"
|
include_examples "turbo does not succeed", "yourself"
|
||||||
end
|
end
|
||||||
|
|
||||||
context "screen_name is different from current_user" do
|
context "screen_name is different from current_user" do
|
||||||
|
@ -41,9 +43,9 @@ describe Ajax::RelationshipController, type: :controller do
|
||||||
|
|
||||||
let(:type) { "Sauerkraut" }
|
let(:type) { "Sauerkraut" }
|
||||||
let(:screen_name) { user2.screen_name }
|
let(:screen_name) { user2.screen_name }
|
||||||
let(:params) { { type: type, screen_name: screen_name } }
|
let(:params) { { type:, screen_name: } }
|
||||||
|
|
||||||
subject { post(:create, params: params) }
|
subject { post(:create, params:, format: :turbo_stream) }
|
||||||
|
|
||||||
it_behaves_like "requires login"
|
it_behaves_like "requires login"
|
||||||
|
|
||||||
|
@ -82,7 +84,7 @@ describe Ajax::RelationshipController, type: :controller do
|
||||||
let(:type) { "dick" }
|
let(:type) { "dick" }
|
||||||
|
|
||||||
it_behaves_like "params is empty"
|
it_behaves_like "params is empty"
|
||||||
include_examples "ajax does not succeed", "Invalid parameter"
|
include_examples "turbo does not succeed", "Invalid parameter"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -110,15 +112,15 @@ describe Ajax::RelationshipController, type: :controller do
|
||||||
context "screen_name does not exist" do
|
context "screen_name does not exist" do
|
||||||
let(:screen_name) { "peter-witzig" }
|
let(:screen_name) { "peter-witzig" }
|
||||||
|
|
||||||
include_examples "ajax does not succeed", "not found"
|
include_examples "turbo does not succeed", "not found"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:type) { "Sauerkraut" }
|
let(:type) { "Sauerkraut" }
|
||||||
let(:screen_name) { user2.screen_name }
|
let(:screen_name) { user2.screen_name }
|
||||||
let(:params) { { type: type, screen_name: screen_name } }
|
let(:params) { { type:, screen_name: } }
|
||||||
|
|
||||||
subject { delete(:destroy, params: params) }
|
subject { delete(:destroy, params:, format: :turbo_stream) }
|
||||||
|
|
||||||
it_behaves_like "requires login"
|
it_behaves_like "requires login"
|
||||||
|
|
||||||
|
@ -146,7 +148,7 @@ describe Ajax::RelationshipController, type: :controller do
|
||||||
context "type = 'dick'" do
|
context "type = 'dick'" do
|
||||||
let(:type) { "dick" }
|
let(:type) { "dick" }
|
||||||
|
|
||||||
include_examples "ajax does not succeed", "Invalid parameter"
|
include_examples "turbo does not succeed", "Invalid parameter"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -13,3 +13,10 @@ RSpec.shared_examples_for "ajax does not succeed" do |part_of_error_message|
|
||||||
expect(assigns(:response)[:message]).to include(part_of_error_message)
|
expect(assigns(:response)[:message]).to include(part_of_error_message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples_for "turbo does not succeed" do |part_of_error_message|
|
||||||
|
it "turbo does not succeed" do
|
||||||
|
subject
|
||||||
|
expect(response.body).to include(part_of_error_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in New Issue