Added blocking service.
CI / lint (push) Failing after 26s
CI / test (3.9) (push) Failing after 25s
CI / test (3.12) (push) Failing after 26s
CI / build (push) Failing after 27s
CI / test (3.8) (push) Failing after 24s
CI / test (3.11) (push) Failing after 47s
CI / test (3.10) (push) Failing after 49s
CI / lint (push) Failing after 26s
CI / test (3.9) (push) Failing after 25s
CI / test (3.12) (push) Failing after 26s
CI / build (push) Failing after 27s
CI / test (3.8) (push) Failing after 24s
CI / test (3.11) (push) Failing after 47s
CI / test (3.10) (push) Failing after 49s
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { BaseComponent } from './base-component.js';
|
||||
|
||||
class BlockedUsersManager extends BaseComponent {
|
||||
init() {
|
||||
this.blockChangeHandler = () => this.render();
|
||||
this.render();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
render() {
|
||||
const blockedUsers = this.getBlock()?.getBlockedUsers() || [];
|
||||
|
||||
this.addClass('blocked-users-manager');
|
||||
|
||||
this.setHtml(`
|
||||
<div class="block-header">
|
||||
<h3>Blocked Users</h3>
|
||||
<span class="block-count">${blockedUsers.length} blocked</span>
|
||||
</div>
|
||||
<form class="block-add-form">
|
||||
<input type="text"
|
||||
class="block-input"
|
||||
placeholder="Enter username to block"
|
||||
autocomplete="off"
|
||||
autocapitalize="none">
|
||||
<button type="submit" class="btn btn-danger block-add-btn">Block</button>
|
||||
</form>
|
||||
<div class="blocked-list">
|
||||
${blockedUsers.length > 0 ? blockedUsers.map(username => `
|
||||
<div class="blocked-item" data-username="${username}">
|
||||
<span class="blocked-username">@${username}</span>
|
||||
<button class="btn btn-small btn-secondary unblock-btn" data-username="${username}">
|
||||
Unblock
|
||||
</button>
|
||||
</div>
|
||||
`).join('') : `
|
||||
<div class="blocked-empty">
|
||||
<p>No blocked users</p>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
${blockedUsers.length > 0 ? `
|
||||
<div class="block-actions">
|
||||
<button class="btn btn-secondary btn-small clear-all-btn">Unblock All</button>
|
||||
</div>
|
||||
` : ''}
|
||||
`);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.on(this, 'submit', this.handleSubmit);
|
||||
this.on(this, 'click', this.handleClick);
|
||||
window.addEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
onDisconnected() {
|
||||
window.removeEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const input = this.$('.block-input');
|
||||
if (!input) return;
|
||||
|
||||
const username = input.value.trim().replace(/^@/, '');
|
||||
if (!username) return;
|
||||
|
||||
const block = this.getBlock();
|
||||
if (block?.isBlocked(username)) {
|
||||
window.app?.toast?.error(`@${username} is already blocked`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (block?.block(username)) {
|
||||
window.app?.toast?.success(`Blocked @${username}`);
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
const unblockBtn = e.target.closest('.unblock-btn');
|
||||
const clearAllBtn = e.target.closest('.clear-all-btn');
|
||||
|
||||
if (unblockBtn) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const username = unblockBtn.dataset.username;
|
||||
if (username && this.getBlock()?.unblock(username)) {
|
||||
window.app?.toast?.success(`Unblocked @${username}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (clearAllBtn) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (confirm('Unblock all users?')) {
|
||||
this.getBlock()?.clear();
|
||||
window.app?.toast?.success('All users unblocked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getBlock() {
|
||||
return window.app?.block;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('blocked-users-manager', BlockedUsersManager);
|
||||
|
||||
export { BlockedUsersManager };
|
||||
@@ -13,10 +13,21 @@ class NotificationList extends BaseComponent {
|
||||
this.notifications = [];
|
||||
this.isLoading = false;
|
||||
this.unreadCount = 0;
|
||||
this.blockChangeHandler = () => this.render();
|
||||
this.render();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
getBlock() {
|
||||
return window.app?.block;
|
||||
}
|
||||
|
||||
getFilteredNotifications() {
|
||||
const block = this.getBlock();
|
||||
if (!block) return this.notifications;
|
||||
return block.filterNotifications(this.notifications);
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (!this.isLoggedIn()) {
|
||||
this.render();
|
||||
@@ -62,7 +73,9 @@ class NotificationList extends BaseComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.notifications.length === 0) {
|
||||
const filteredNotifications = this.getFilteredNotifications();
|
||||
|
||||
if (filteredNotifications.length === 0) {
|
||||
this.setHtml(`
|
||||
<div class="notification-empty">
|
||||
<svg viewBox="0 0 24 24" width="48" height="48">
|
||||
@@ -82,7 +95,7 @@ class NotificationList extends BaseComponent {
|
||||
` : ''}
|
||||
</header>
|
||||
<div class="notification-items">
|
||||
${this.notifications.map(notif => `
|
||||
${filteredNotifications.map(notif => `
|
||||
<notification-item
|
||||
data-notif='${JSON.stringify(notif).replace(/'/g, ''')}'>
|
||||
</notification-item>
|
||||
@@ -90,24 +103,21 @@ class NotificationList extends BaseComponent {
|
||||
</div>
|
||||
`);
|
||||
|
||||
this.initNotificationItems();
|
||||
this.initNotificationItems(filteredNotifications);
|
||||
}
|
||||
|
||||
initNotificationItems() {
|
||||
initNotificationItems(filteredNotifications) {
|
||||
const items = this.$$('notification-item');
|
||||
items.forEach(item => {
|
||||
const notifData = item.dataset.notif;
|
||||
if (notifData) {
|
||||
try {
|
||||
const notif = JSON.parse(notifData.replace(/'/g, "'"));
|
||||
item.setNotification(notif);
|
||||
} catch (e) {}
|
||||
items.forEach((item, index) => {
|
||||
if (filteredNotifications[index]) {
|
||||
item.setNotification(filteredNotifications[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.on(this, 'click', this.handleClick);
|
||||
window.addEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
@@ -139,6 +149,7 @@ class NotificationList extends BaseComponent {
|
||||
|
||||
onDisconnected() {
|
||||
this.isLoading = false;
|
||||
window.removeEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
getUnreadCount() {
|
||||
|
||||
@@ -18,10 +18,27 @@ class RantDetail extends BaseComponent {
|
||||
this.rantData = null;
|
||||
this.comments = [];
|
||||
this.isLoading = false;
|
||||
this.blockChangeHandler = () => this.render();
|
||||
this.render();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
getBlock() {
|
||||
return window.app?.block;
|
||||
}
|
||||
|
||||
getFilteredComments() {
|
||||
const block = this.getBlock();
|
||||
if (!block) return this.comments;
|
||||
return block.filterComments(this.comments);
|
||||
}
|
||||
|
||||
isRantBlocked() {
|
||||
const block = this.getBlock();
|
||||
if (!block || !this.rantData) return false;
|
||||
return block.isBlocked(this.rantData.user_username);
|
||||
}
|
||||
|
||||
async load(rantId) {
|
||||
this.setAttr('rant-id', rantId);
|
||||
this.isLoading = true;
|
||||
@@ -69,6 +86,30 @@ class RantDetail extends BaseComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isRantBlocked()) {
|
||||
this.setHtml(`
|
||||
<div class="rant-detail-blocked">
|
||||
<header class="detail-header">
|
||||
<button class="back-btn" aria-label="Go back">
|
||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<h1 class="detail-title">Blocked User</h1>
|
||||
</header>
|
||||
<div class="blocked-message">
|
||||
<svg viewBox="0 0 24 24" width="48" height="48">
|
||||
<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"/>
|
||||
</svg>
|
||||
<p>This rant is from a blocked user</p>
|
||||
<p class="blocked-username">@${this.rantData.user_username}</p>
|
||||
<button class="btn btn-secondary unblock-btn" data-username="${this.rantData.user_username}">Unblock User</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
const rant = this.rantData;
|
||||
const hasImage = rant.attached_image && typeof rant.attached_image === 'object';
|
||||
const imageUrl = hasImage ? buildDevrantImageUrl(rant.attached_image.url) : null;
|
||||
@@ -131,7 +172,7 @@ class RantDetail extends BaseComponent {
|
||||
<section class="comments-section">
|
||||
<comment-form rant-id="${rant.id}"></comment-form>
|
||||
<div class="comments-list">
|
||||
${this.comments.map(comment => `
|
||||
${this.getFilteredComments().map(comment => `
|
||||
<comment-item
|
||||
comment-id="${comment.id}"
|
||||
data-comment='${JSON.stringify(comment).replace(/'/g, ''')}'>
|
||||
@@ -155,14 +196,11 @@ class RantDetail extends BaseComponent {
|
||||
}
|
||||
|
||||
initComments() {
|
||||
const filteredComments = this.getFilteredComments();
|
||||
const commentItems = this.$$('comment-item');
|
||||
commentItems.forEach(item => {
|
||||
const commentData = item.dataset.comment;
|
||||
if (commentData) {
|
||||
try {
|
||||
const comment = JSON.parse(commentData.replace(/'/g, "'"));
|
||||
item.setComment(comment);
|
||||
} catch (e) {}
|
||||
commentItems.forEach((item, index) => {
|
||||
if (filteredComments[index]) {
|
||||
item.setComment(filteredComments[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -171,6 +209,11 @@ class RantDetail extends BaseComponent {
|
||||
this.on(this, 'click', this.handleClick);
|
||||
this.on(this, 'vote', this.handleVote);
|
||||
this.on(this, 'comment-posted', this.handleCommentPosted);
|
||||
window.addEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
onDisconnected() {
|
||||
window.removeEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
@@ -178,6 +221,7 @@ class RantDetail extends BaseComponent {
|
||||
const username = e.target.closest('.author-username');
|
||||
const avatar = e.target.closest('user-avatar');
|
||||
const tag = e.target.closest('.tag');
|
||||
const unblockBtn = e.target.closest('.unblock-btn');
|
||||
|
||||
if (backBtn) {
|
||||
e.preventDefault();
|
||||
@@ -185,6 +229,17 @@ class RantDetail extends BaseComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (unblockBtn) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const usernameToUnblock = unblockBtn.dataset.username;
|
||||
if (usernameToUnblock) {
|
||||
this.getBlock()?.unblock(usernameToUnblock);
|
||||
window.app?.toast?.success(`Unblocked @${usernameToUnblock}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (username || avatar) {
|
||||
this.getRouter()?.goToUser(this.rantData.user_username);
|
||||
return;
|
||||
|
||||
@@ -20,11 +20,22 @@ class RantFeed extends BaseComponent {
|
||||
this.hasMore = true;
|
||||
this.sort = this.getAttr('sort') || 'recent';
|
||||
this.feedType = this.getAttr('feed-type') || 'rants';
|
||||
this.blockChangeHandler = () => this.render();
|
||||
|
||||
this.render();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
getBlock() {
|
||||
return window.app?.block;
|
||||
}
|
||||
|
||||
getFilteredRants() {
|
||||
const block = this.getBlock();
|
||||
if (!block) return this.rants;
|
||||
return block.filterRants(this.rants);
|
||||
}
|
||||
|
||||
async load(reset = false) {
|
||||
if (this.isLoading) return;
|
||||
if (!reset && !this.hasMore) return;
|
||||
@@ -98,7 +109,9 @@ class RantFeed extends BaseComponent {
|
||||
render() {
|
||||
this.addClass('rant-feed');
|
||||
|
||||
if (this.rants.length === 0 && !this.isLoading) {
|
||||
const filteredRants = this.getFilteredRants();
|
||||
|
||||
if (filteredRants.length === 0 && !this.isLoading) {
|
||||
this.setHtml(`
|
||||
<div class="feed-empty">
|
||||
<p>No rants found</p>
|
||||
@@ -116,7 +129,7 @@ class RantFeed extends BaseComponent {
|
||||
</div>
|
||||
</div>
|
||||
<div class="feed-list">
|
||||
${this.rants.map(rant => `
|
||||
${filteredRants.map(rant => `
|
||||
<rant-card rant-id="${rant.id}"></rant-card>
|
||||
`).join('')}
|
||||
</div>
|
||||
@@ -132,14 +145,14 @@ class RantFeed extends BaseComponent {
|
||||
` : ''}
|
||||
`);
|
||||
|
||||
this.initRantCards();
|
||||
this.initRantCards(filteredRants);
|
||||
}
|
||||
|
||||
initRantCards() {
|
||||
initRantCards(filteredRants) {
|
||||
const cards = this.$$('rant-card');
|
||||
cards.forEach((card, index) => {
|
||||
if (this.rants[index]) {
|
||||
card.setRant(this.rants[index]);
|
||||
if (filteredRants[index]) {
|
||||
card.setRant(filteredRants[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -157,6 +170,7 @@ class RantFeed extends BaseComponent {
|
||||
bindEvents() {
|
||||
this.on(this, 'click', this.handleClick);
|
||||
this.setupInfiniteScroll();
|
||||
window.addEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
@@ -203,6 +217,7 @@ class RantFeed extends BaseComponent {
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.hasMore = true;
|
||||
window.removeEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
onAttributeChanged(name, oldValue, newValue) {
|
||||
|
||||
@@ -17,10 +17,21 @@ class UserProfile extends BaseComponent {
|
||||
this.profileData = null;
|
||||
this.isLoading = false;
|
||||
this.activeTab = 'rants';
|
||||
this.blockChangeHandler = () => this.render();
|
||||
this.render();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
getBlock() {
|
||||
return window.app?.block;
|
||||
}
|
||||
|
||||
isUserBlocked() {
|
||||
const block = this.getBlock();
|
||||
if (!block || !this.profileData) return false;
|
||||
return block.isBlocked(this.profileData.username);
|
||||
}
|
||||
|
||||
async load(username) {
|
||||
if (!username) return;
|
||||
|
||||
@@ -64,6 +75,8 @@ class UserProfile extends BaseComponent {
|
||||
const profile = this.profileData;
|
||||
const rants = profile.content?.content?.rants || [];
|
||||
const comments = profile.content?.content?.comments || [];
|
||||
const isBlocked = this.isUserBlocked();
|
||||
const isOwnProfile = window.app?.auth?.getUsername()?.toLowerCase() === profile.username.toLowerCase();
|
||||
|
||||
this.addClass('user-profile');
|
||||
|
||||
@@ -84,8 +97,21 @@ class UserProfile extends BaseComponent {
|
||||
<div class="profile-info">
|
||||
<h1 class="profile-username">${profile.username}</h1>
|
||||
<div class="profile-score">+${profile.score}</div>
|
||||
${!isOwnProfile ? `
|
||||
<button class="btn btn-small ${isBlocked ? 'btn-secondary' : 'btn-danger'} block-user-btn" data-username="${profile.username}">
|
||||
${isBlocked ? 'Unblock' : 'Block'}
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</header>
|
||||
${isBlocked ? `
|
||||
<div class="profile-blocked-banner">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20">
|
||||
<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"/>
|
||||
</svg>
|
||||
<span>This user is blocked</span>
|
||||
</div>
|
||||
` : ''}
|
||||
<section class="profile-details">
|
||||
${profile.about ? `
|
||||
<div class="detail-item">
|
||||
@@ -179,13 +205,36 @@ class UserProfile extends BaseComponent {
|
||||
|
||||
bindEvents() {
|
||||
this.on(this, 'click', this.handleClick);
|
||||
window.addEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
onDisconnected() {
|
||||
window.removeEventListener('rantii:block-change', this.blockChangeHandler);
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
const blockBtn = e.target.closest('.block-user-btn');
|
||||
const backBtn = e.target.closest('.back-btn');
|
||||
const tab = e.target.closest('.tab');
|
||||
const profileComment = e.target.closest('.profile-comment');
|
||||
|
||||
if (blockBtn) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const username = blockBtn.dataset.username;
|
||||
if (username) {
|
||||
const block = this.getBlock();
|
||||
if (block?.isBlocked(username)) {
|
||||
block.unblock(username);
|
||||
window.app?.toast?.success(`Unblocked @${username}`);
|
||||
} else {
|
||||
block?.block(username);
|
||||
window.app?.toast?.success(`Blocked @${username}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (backBtn) {
|
||||
e.preventDefault();
|
||||
window.history.back();
|
||||
|
||||
Reference in New Issue
Block a user