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 registerEvents from 'retrospring/utilities/registerEvents';
|
||||
|
||||
export default (): void => {
|
||||
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 }
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
.d-grid.gap-2
|
||||
- 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 } }
|
||||
= t("voc.unfollow")
|
||||
= render "relationships/destroy", type: "follow", screen_name: user.screen_name
|
||||
- else
|
||||
%button.btn.btn-primary{ type: :button, name: 'user-action', data: { action: :follow, type: type, target: user.screen_name } }
|
||||
= t("voc.follow")
|
||||
= render "relationships/create", type: "follow", screen_name: user.screen_name
|
||||
.btn-group
|
||||
%button.btn.btn-light.btn-sm.dropdown-toggle{ data: { bs_toggle: :dropdown }, aria: { expanded: false } }
|
||||
= t(".title")
|
||||
|
@ -23,21 +21,13 @@
|
|||
%i.fa.fa-list.fa-fw
|
||||
= t(".list")
|
||||
- if own_blocks&.include?(user.id) || current_user.blocking?(user)
|
||||
%a.dropdown-item{ href: '#', data: { action: :unblock, target: user.screen_name } }
|
||||
%i.fa.fa-minus-circle.fa-fw
|
||||
%span.pe-none= t("voc.unblock")
|
||||
= render "relationships/destroy", type: "block", screen_name: user.screen_name
|
||||
- else
|
||||
%a.dropdown-item{ href: '#', data: { action: :block, target: user.screen_name } }
|
||||
%i.fa.fa-minus-circle.fa-fw
|
||||
%span.pe-none= t("voc.block")
|
||||
= render "relationships/create", type: "block", screen_name: user.screen_name
|
||||
- if own_mutes&.include?(user.id) || current_user.muting?(user)
|
||||
%a.dropdown-item{ href: '#', data: { action: :unmute, target: user.screen_name } }
|
||||
%i.fa.fa-volume-off.fa-fw
|
||||
%span.pe-none= t("voc.unmute")
|
||||
= render "relationships/destroy", type: "mute", screen_name: user.screen_name
|
||||
- else
|
||||
%a.dropdown-item{ href: '#', data: { action: :mute, target: user.screen_name } }
|
||||
%i.fa.fa-volume-off.fa-fw
|
||||
%span.pe-none= t("voc.mute")
|
||||
= render "relationships/create", type: "mute", screen_name: user.screen_name
|
||||
%a.dropdown-item{ href: '#', data: { action: 'report-user', target: user.screen_name } }
|
||||
%i.fa.fa-exclamation-triangle.fa-fw
|
||||
= t("voc.report")
|
||||
|
|
|
@ -101,27 +101,6 @@ en:
|
|||
notfound: "Question does not exist."
|
||||
noauth: "You are not allowed to delete this 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:
|
||||
create:
|
||||
noauth: :ajax.noauth
|
||||
|
@ -216,6 +195,27 @@ en:
|
|||
registrations:
|
||||
destroy:
|
||||
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:
|
||||
public:
|
||||
title: "Public Timeline"
|
||||
|
|
|
@ -147,6 +147,7 @@ Rails.application.routes.draw do
|
|||
get "/inbox", to: "inbox#show", as: :inbox
|
||||
|
||||
resource :subscriptions, controller: :subscriptions, only: %i[create destroy]
|
||||
resource :relationships, only: %i[create destroy]
|
||||
|
||||
get "/user/:username", to: "user#show"
|
||||
get "/@:username", to: "user#show", as: :user
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
require "rails_helper"
|
||||
|
||||
describe Ajax::RelationshipController, type: :controller do
|
||||
describe RelationshipsController, type: :controller do
|
||||
render_views
|
||||
|
||||
shared_examples_for "params is empty" do
|
||||
let(:params) { {} }
|
||||
|
||||
include_examples "ajax does not succeed", "is required"
|
||||
include_examples "turbo does not succeed", "is required"
|
||||
end
|
||||
|
||||
let!(:user) { FactoryBot.create(:user) }
|
||||
|
@ -20,13 +22,13 @@ describe Ajax::RelationshipController, type: :controller do
|
|||
context "screen_name does not exist" do
|
||||
let(:screen_name) { "peter-witzig" }
|
||||
|
||||
include_examples "ajax does not succeed", "not found"
|
||||
include_examples "turbo does not succeed", "not found"
|
||||
end
|
||||
|
||||
context "screen_name is current user" do
|
||||
let(:screen_name) { user.screen_name }
|
||||
|
||||
include_examples "ajax does not succeed", "yourself"
|
||||
include_examples "turbo does not succeed", "yourself"
|
||||
end
|
||||
|
||||
context "screen_name is different from current_user" do
|
||||
|
@ -41,9 +43,9 @@ describe Ajax::RelationshipController, type: :controller do
|
|||
|
||||
let(:type) { "Sauerkraut" }
|
||||
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"
|
||||
|
||||
|
@ -82,7 +84,7 @@ describe Ajax::RelationshipController, type: :controller do
|
|||
let(:type) { "dick" }
|
||||
|
||||
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
|
||||
|
@ -110,15 +112,15 @@ describe Ajax::RelationshipController, type: :controller do
|
|||
context "screen_name does not exist" do
|
||||
let(:screen_name) { "peter-witzig" }
|
||||
|
||||
include_examples "ajax does not succeed", "not found"
|
||||
include_examples "turbo does not succeed", "not found"
|
||||
end
|
||||
end
|
||||
|
||||
let(:type) { "Sauerkraut" }
|
||||
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"
|
||||
|
||||
|
@ -146,7 +148,7 @@ describe Ajax::RelationshipController, type: :controller do
|
|||
context "type = 'dick'" do
|
||||
let(:type) { "dick" }
|
||||
|
||||
include_examples "ajax does not succeed", "Invalid parameter"
|
||||
include_examples "turbo does not succeed", "Invalid parameter"
|
||||
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)
|
||||
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