Use StimulusJS for vomit confirmations in Internal (#7255)
This PR implements the async vomit-reaction functionality using StimulusJS. Prior to this, the JS lived in `_abuse-reports.html.erb`. Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
This commit is contained in:
parent
fd9fdebc0b
commit
1d1a8456a5
5 changed files with 141 additions and 89 deletions
|
|
@ -88,7 +88,6 @@
|
|||
input {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.chat__channels--expanded {
|
||||
|
|
@ -172,7 +171,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.chat_chatconfig {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
|
|
@ -367,7 +365,7 @@
|
|||
@media screen and (max-width: 426px) {
|
||||
width: calc(100% - 16px);
|
||||
min-width: calc(100% - 16px);
|
||||
}
|
||||
}
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
|
@ -396,7 +394,8 @@
|
|||
max-width: 52%;
|
||||
}
|
||||
|
||||
.chat--content-visible .activechatchannel__activecontent.activechatchannel__activecontent--video {
|
||||
.chat--content-visible
|
||||
.activechatchannel__activecontent.activechatchannel__activecontent--video {
|
||||
width: 180px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
|
@ -446,7 +445,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.activechatchannel__activecontentexitbutton {
|
||||
font-size: 30px;
|
||||
position: absolute;
|
||||
|
|
@ -600,13 +598,11 @@
|
|||
border: 1px solid var(--base-30);
|
||||
}
|
||||
|
||||
|
||||
.chatchanneltab--new {
|
||||
border: 1px solid var(--accent-brand);
|
||||
color: var(--accent-brand-darker);
|
||||
}
|
||||
|
||||
|
||||
.chatchanneltab--video {
|
||||
background: lighten($green, 10%);
|
||||
animation: small-pulser 0.5s linear infinite;
|
||||
|
|
@ -718,7 +714,7 @@
|
|||
display: flex;
|
||||
max-width: 100%;
|
||||
@media not all and (pointer: coarse) {
|
||||
&:hover {
|
||||
&:hover {
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-background-hover,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable consistent-return,no-unused-vars,react/destructuring-assignment,react/no-access-state-in-setstate,react/button-has-type */
|
||||
import { h, Component } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import ConfigImage from '../../assets/images/three-dots.svg';
|
||||
|
|
@ -79,16 +80,11 @@ export default class Chat extends Component {
|
|||
channelUsers: [],
|
||||
showMemberlist: false,
|
||||
memberFilterQuery: null,
|
||||
rerenderIfUnchangedCheck: null
|
||||
rerenderIfUnchangedCheck: null,
|
||||
};
|
||||
getAllMessages(chatOptions.activeChannelId, 0, this.receiveAllMessages);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if(this.state.rerenderIfUnchangedCheck !== nextState.rerenderIfUnchangedCheck) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
const {
|
||||
chatChannels,
|
||||
|
|
@ -144,6 +140,14 @@ export default class Chat extends Component {
|
|||
getChannelInvites(this.handleChannelInvites, null);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (
|
||||
this.state.rerenderIfUnchangedCheck !== nextState.rerenderIfUnchangedCheck
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { scrolled, currentMessageLocation } = this.state;
|
||||
const messageList = document.getElementById('messagelist');
|
||||
|
|
@ -344,7 +348,10 @@ export default class Chat extends Component {
|
|||
if (entry.isIntersecting && this.state.scrolled === true) {
|
||||
this.setState({ scrolled: false, showAlert: false });
|
||||
} else if (this.state.scrolled === false) {
|
||||
this.setState({ scrolled: true, rerenderIfUnchangedCheck: Math.random() });
|
||||
this.setState({
|
||||
scrolled: true,
|
||||
rerenderIfUnchangedCheck: Math.random(),
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -601,7 +608,7 @@ export default class Chat extends Component {
|
|||
message: '/call',
|
||||
mentionedUsersId: this.getMentionedUsers(message),
|
||||
};
|
||||
this.setState({ videoPath: `/video_chats/${ activeChannelId}` });
|
||||
this.setState({ videoPath: `/video_chats/${activeChannelId}` });
|
||||
sendMessage(messageObject, this.handleSuccess, this.handleFailure);
|
||||
} else if (message.startsWith('/new')) {
|
||||
this.setActiveContentState(activeChannelId, {
|
||||
|
|
@ -816,10 +823,11 @@ export default class Chat extends Component {
|
|||
});
|
||||
} else if (target.dataset.content === 'exit') {
|
||||
this.setActiveContentState(activeChannelId, null);
|
||||
this.setState({fullscreenContent: null});
|
||||
this.setState({ fullscreenContent: null });
|
||||
} else if (target.dataset.content === 'fullscreen') {
|
||||
const mode = this.state.fullscreenContent === 'sidecar' ? null : 'sidecar'
|
||||
this.setState({fullscreenContent: mode})
|
||||
const mode =
|
||||
this.state.fullscreenContent === 'sidecar' ? null : 'sidecar';
|
||||
this.setState({ fullscreenContent: mode });
|
||||
}
|
||||
}
|
||||
document.getElementById('messageform').focus();
|
||||
|
|
@ -1018,12 +1026,12 @@ export default class Chat extends Component {
|
|||
|
||||
toggleSearchShowing = () => {
|
||||
if (!this.state.searchShowing) {
|
||||
setTimeout(function(){
|
||||
document.getElementById("chatchannelsearchbar").focus()
|
||||
},100)
|
||||
setTimeout(function () {
|
||||
document.getElementById('chatchannelsearchbar').focus();
|
||||
}, 100);
|
||||
}
|
||||
this.setState({searchShowing: !this.state.searchShowing})
|
||||
}
|
||||
this.setState({ searchShowing: !this.state.searchShowing });
|
||||
};
|
||||
|
||||
renderChatChannels = () => {
|
||||
const { state } = this;
|
||||
|
|
@ -1074,10 +1082,32 @@ export default class Chat extends Component {
|
|||
>
|
||||
{'<'}
|
||||
</button>
|
||||
{state.searchShowing ? <input placeholder="Search Channels" onKeyUp={this.debouncedChannelFilter} id="chatchannelsearchbar" className="crayons-textfield" /> : ''}
|
||||
{state.searchShowing ? (
|
||||
<input
|
||||
placeholder="Search Channels"
|
||||
onKeyUp={this.debouncedChannelFilter}
|
||||
id="chatchannelsearchbar"
|
||||
className="crayons-textfield"
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{invitesButton}
|
||||
<div className="chat__channeltypefilter">
|
||||
<button className="chat__channelssearchtoggle" onClick={this.toggleSearchShowing}><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="17" height="17"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z"/></svg></button>
|
||||
<button
|
||||
className="chat__channelssearchtoggle"
|
||||
onClick={this.toggleSearchShowing}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="17"
|
||||
height="17"
|
||||
>
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z" />
|
||||
</svg>
|
||||
</button>
|
||||
{this.renderChannelFilterButton(
|
||||
'all',
|
||||
'all',
|
||||
|
|
@ -1145,7 +1175,7 @@ export default class Chat extends Component {
|
|||
}
|
||||
|
||||
const jumpbackButton = document.getElementById('jumpback_button');
|
||||
|
||||
|
||||
if (this.scroller) {
|
||||
const scrolledRatio =
|
||||
(this.scroller.scrollTop + this.scroller.clientHeight) /
|
||||
|
|
@ -1264,7 +1294,7 @@ export default class Chat extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
onTriggerVideoContent = e => {
|
||||
onTriggerVideoContent = (e) => {
|
||||
if (e.target.dataset.content === 'exit') {
|
||||
this.setState({ videoPath: null, fullscreenContent: null });
|
||||
} else if (this.state.fullscreenContent === 'video') {
|
||||
|
|
@ -1578,9 +1608,9 @@ export default class Chat extends Component {
|
|||
}
|
||||
let fullscreenMode = '';
|
||||
if (state.fullscreenContent === 'sidecar') {
|
||||
fullscreenMode = 'chat--content-visible-full'
|
||||
fullscreenMode = 'chat--content-visible-full';
|
||||
} else if (state.fullscreenContent === 'video') {
|
||||
fullscreenMode = 'chat--video-visible-full'
|
||||
fullscreenMode = 'chat--video-visible-full';
|
||||
}
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
70
app/javascript/internal/controllers/reaction_controller.js
Normal file
70
app/javascript/internal/controllers/reaction_controller.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* eslint-disable no-alert */
|
||||
import { Controller } from 'stimulus';
|
||||
|
||||
export default class ReactionController extends Controller {
|
||||
static targets = ['invalid', 'confirmed'];
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
/* eslint no-alert: "error" */
|
||||
updateReaction(id, status) {
|
||||
fetch(`/internal/reactions/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': document.querySelector("meta[name='csrf-token']")
|
||||
.content,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id,
|
||||
status,
|
||||
}),
|
||||
credentials: 'same-origin',
|
||||
}).then((response) =>
|
||||
response
|
||||
.json()
|
||||
.then((json) => {
|
||||
if (json.outcome === 'Success') {
|
||||
this.element.remove();
|
||||
document.getElementById(`js__reaction__div__hr__${id}`).remove();
|
||||
} else {
|
||||
alert(json.error);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(error);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
updateReactionInvalid() {
|
||||
this.updateReaction(this.reactionId, this.invalidStatus);
|
||||
}
|
||||
|
||||
reactableUserCheck() {
|
||||
if (this.reactableType === 'user') {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (confirm('You are confirming a User vomit reaction; are you sure?')) {
|
||||
this.updateReaction(this.reactionId, this.confirmedStatus);
|
||||
}
|
||||
} else {
|
||||
this.updateReaction(this.reactionId, this.confirmedStatus);
|
||||
}
|
||||
}
|
||||
|
||||
get reactionId() {
|
||||
return parseInt(this.data.get('id'), 10);
|
||||
}
|
||||
|
||||
get confirmedStatus() {
|
||||
return this.confirmedTarget.dataset.status;
|
||||
}
|
||||
|
||||
get reactableType() {
|
||||
return this.confirmedTarget.dataset.reactable;
|
||||
}
|
||||
|
||||
get invalidStatus() {
|
||||
return this.invalidTarget.dataset.altstatus;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<div class="card-body" style="overflow: scroll; max-height: 500px;">
|
||||
<% @vomits.each do |reaction| %>
|
||||
<% next if (reaction.reactable_type == "Article" && !reaction.reactable.published) || (reaction.reactable_type == "User" && reaction.reactable&.banished?) %>
|
||||
<div class="d-flex justify-content-between" id="js__reaction__div__<%= reaction.id %>">
|
||||
<div class="d-flex justify-content-between" data-controller="reaction" data-reaction-id="<%= reaction.id %>">
|
||||
<span>
|
||||
🤢 <a href="<%= reaction.user.path %>">@<%= reaction.user.username %></a>
|
||||
</span>
|
||||
|
|
@ -45,28 +45,31 @@
|
|||
<% if params[:status] == "Open" || params[:status].blank? %>
|
||||
<% if reaction.reactable_type == "User" %>
|
||||
<button
|
||||
class="btn btn-success btn-sm js-reaction-confirm"
|
||||
class="btn btn-success btn-sm"
|
||||
type="button"
|
||||
data-reactable="user"
|
||||
data-id="<%= reaction.id %>"
|
||||
data-status="confirmed">
|
||||
data-status="confirmed"
|
||||
data-target="reaction.confirmed"
|
||||
data-action="reaction#reactableUserCheck">
|
||||
CONFIRM
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
class="btn btn-success btn-sm js-reaction-confirm"
|
||||
class="btn btn-success btn-sm"
|
||||
type="button"
|
||||
data-reactable="not-user"
|
||||
data-id="<%= reaction.id %>"
|
||||
data-status="confirmed">
|
||||
data-reactable="non-user"
|
||||
data-status="confirmed"
|
||||
data-target="reaction.confirmed"
|
||||
data-action="reaction#reactableUserCheck">
|
||||
CONFIRM
|
||||
</button>
|
||||
<% end %>
|
||||
<button
|
||||
class="btn btn-danger btn-sm js-reaction-confirm"
|
||||
class="btn btn-danger btn-sm"
|
||||
type="button"
|
||||
data-id="<%= reaction.id %>"
|
||||
data-status="invalid">
|
||||
data-altstatus="invalid"
|
||||
data-target="reaction.invalid"
|
||||
data-action="reaction#updateReactionInvalid">
|
||||
INVALID
|
||||
</button>
|
||||
<% end %>
|
||||
|
|
@ -78,49 +81,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function removeReactionDiv(id){
|
||||
document.getElementById(`js__reaction__div__${id}`).remove();
|
||||
document.getElementById(`js__reaction__div__hr__${id}`).remove();
|
||||
}
|
||||
|
||||
function updateReactionStatus(id, status){
|
||||
fetch(`/internal/reactions/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': document.querySelector("meta[name='csrf-token']").content,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: id,
|
||||
status: status,
|
||||
}),
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => response.json()
|
||||
.then(json => {
|
||||
if (json.outcome === 'Success') {
|
||||
removeReactionDiv(id)
|
||||
} else {
|
||||
alert(json.error)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert(error);
|
||||
}))
|
||||
}
|
||||
|
||||
document.querySelectorAll('.js-reaction-confirm').forEach(item => {
|
||||
item.addEventListener('click', function () {
|
||||
if (item.dataset.reactable === 'user') {
|
||||
if (confirm("You are confirming a reaction for a User; are you sure?")){
|
||||
updateReactionStatus(item.dataset.id, item.dataset.status);
|
||||
}
|
||||
} else {
|
||||
updateReactionStatus(item.dataset.id, item.dataset.status);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
|
||||
#local-media video {
|
||||
width: 150px;
|
||||
position: fixed;
|
||||
|
|
@ -32,7 +31,7 @@
|
|||
right: 10px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
|
||||
#local-media.video-hidden {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue