Sengi-Windows-MacOS-Linux/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.co...

431 lines
15 KiB
TypeScript

import { Component, OnInit, ViewChild, Output, EventEmitter, Input, OnDestroy } from '@angular/core';
import { faEllipsisH } from "@fortawesome/free-solid-svg-icons";
import { ContextMenuComponent, ContextMenuService } from 'ngx-contextmenu';
import { Observable, Subscription } from 'rxjs';
import { Store } from '@ngxs/store';
import { Status, Account, Results, Relationship } from '../../../../../services/models/mastodon.interfaces';
import { ToolsService, OpenThreadEvent, InstanceInfo } from '../../../../../services/tools.service';
import { StatusWrapper } from '../../../../../models/common.model';
import { NavigationService } from '../../../../../services/navigation.service';
import { AccountInfo } from '../../../../../states/accounts.state';
import { MastodonWrapperService } from '../../../../../services/mastodon-wrapper.service';
import { NotificationService } from '../../../../../services/notification.service';
@Component({
selector: 'app-status-user-context-menu',
templateUrl: './status-user-context-menu.component.html',
styleUrls: ['./status-user-context-menu.component.scss']
})
export class StatusUserContextMenuComponent implements OnInit, OnDestroy {
faEllipsisH = faEllipsisH;
private fullHandle: string;
private loadedAccounts: AccountInfo[];
displayedStatus: Status;
username: string;
domain: string;
isOwnerSelected: boolean;
isEditingAvailable: boolean;
@Input() statusWrapper: StatusWrapper;
@Input() displayedAccount: Account;
@Input() relationship: Relationship;
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
@Output() relationshipChanged = new EventEmitter<Relationship>();
@ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent;
private accounts$: Observable<AccountInfo[]>;
private accountSub: Subscription;
constructor(
private readonly store: Store,
private readonly mastodonService: MastodonWrapperService,
private readonly notificationService: NotificationService,
private readonly navigationService: NavigationService,
private readonly toolsService: ToolsService,
private readonly contextMenuService: ContextMenuService) {
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
}
ngOnInit() {
if (this.statusWrapper) {
const status = this.statusWrapper.status;
if (status.reblog) {
this.displayedStatus = status.reblog;
} else {
this.displayedStatus = status;
}
}
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
this.loadedAccounts = accounts;
if (this.statusWrapper) this.checkStatus(accounts);
});
let account: Account;
if (this.statusWrapper) {
account = this.displayedStatus.account;
} else {
account = this.displayedAccount;
}
this.username = account.acct.split('@')[0];
this.domain = account.acct.split('@')[1];
this.fullHandle = this.toolsService.getAccountFullHandle(account);
}
private checkStatus(accounts: AccountInfo[]): void {
const selectedAccount = accounts.find(x => x.isSelected);
this.isOwnerSelected = selectedAccount.username.toLowerCase() === this.displayedStatus.account.username.toLowerCase()
&& selectedAccount.instance.toLowerCase() === this.displayedStatus.account.url.replace('https://', '').split('/')[0].toLowerCase();
this.toolsService.getInstanceInfo(selectedAccount).then((instanceInfo: InstanceInfo) => {
if (instanceInfo.major >= 4) {
this.isEditingAvailable = true;
} else {
this.isEditingAvailable = false;
}
});
}
ngOnDestroy(): void {
if (this.accountSub) this.accountSub.unsubscribe();
}
public onContextMenu($event: MouseEvent): void {
this.contextMenuService.show.next({
// Optional - if unspecified, all context menu components will open
contextMenu: this.contextMenu,
event: $event,
item: null
});
$event.preventDefault();
$event.stopPropagation();
}
expandStatus(): boolean {
const openThread = new OpenThreadEvent(this.displayedStatus, this.statusWrapper.provider);
this.browseThreadEvent.next(openThread);
return false;
}
copyStatusLink(): boolean {
let selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = this.displayedStatus.url;
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);
return false;
}
copyAllData(): boolean {
const newLine = String.fromCharCode(13, 10);
let selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = `${this.displayedStatus.url}${newLine}${newLine}${this.displayedStatus.content}${newLine}${newLine}`;
let parser = new DOMParser();
var dom = parser.parseFromString(this.displayedStatus.content, 'text/html')
selBox.value += `${dom.body.textContent}${newLine}${newLine}`;
for (const att of this.displayedStatus.media_attachments) {
selBox.value += `${att.url}${newLine}${newLine}`;
}
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);
return false;
}
mentionAccount(): boolean {
this.navigationService.replyToUser(this.fullHandle, false);
return false;
}
dmAccount(): boolean {
this.navigationService.replyToUser(this.fullHandle, true);
return false;
}
hideBoosts(): boolean {
const acc = this.toolsService.getSelectedAccounts()[0];
this.toolsService.findAccount(acc, this.fullHandle)
.then(async (target: Account) => {
const relationship = await this.mastodonService.hideBoosts(acc, target);
this.relationship = relationship;
this.relationshipChanged.next(relationship);
})
.catch(err => {
this.notificationService.notifyHttpError(err, acc);
});
return false;
}
unhideBoosts(): boolean {
const acc = this.toolsService.getSelectedAccounts()[0];
this.toolsService.findAccount(acc, this.fullHandle)
.then(async (target: Account) => {
const relationship = await this.mastodonService.unhideBoosts(acc, target);
this.relationship = relationship;
this.relationshipChanged.next(relationship);
})
.catch(err => {
this.notificationService.notifyHttpError(err, acc);
});
return false;
}
muteAccount(): boolean {
const acc = this.toolsService.getSelectedAccounts()[0];
this.toolsService.findAccount(acc, this.fullHandle)
.then(async (target: Account) => {
const relationship = await this.mastodonService.mute(acc, target.id);
this.relationship = relationship;
this.relationshipChanged.next(relationship);
return target;
})
.then((target: Account) => {
this.notificationService.hideAccount(target);
})
.catch(err => {
this.notificationService.notifyHttpError(err, acc);
});
return false;
}
unmuteAccount(): boolean {
const acc = this.toolsService.getSelectedAccounts()[0];
this.toolsService.findAccount(acc, this.fullHandle)
.then(async (target: Account) => {
const relationship = await this.mastodonService.unmute(acc, target.id);
this.relationship = relationship;
this.relationshipChanged.next(relationship);
return target;
})
.catch(err => {
this.notificationService.notifyHttpError(err, acc);
});
return false;
}
blockAccount(): boolean {
const acc = this.toolsService.getSelectedAccounts()[0];
this.toolsService.findAccount(acc, this.fullHandle)
.then(async (target: Account) => {
const relationship = await this.mastodonService.block(acc, target.id);
this.relationship = relationship;
this.relationshipChanged.next(relationship);
return target;
})
.then((target: Account) => {
this.notificationService.hideAccount(target);
})
.catch(err => {
this.notificationService.notifyHttpError(err, acc);
});
return false;
}
unblockAccount(): boolean {
const acc = this.toolsService.getSelectedAccounts()[0];
this.toolsService.findAccount(acc, this.fullHandle)
.then(async (target: Account) => {
const relationship = await this.mastodonService.unblock(acc, target.id);
this.relationship = relationship;
this.relationshipChanged.next(relationship);
return target;
})
.catch(err => {
this.notificationService.notifyHttpError(err, acc);
});
return false;
}
blockDomain(): boolean {
const response = confirm(`Are you really sure you want to block the entire ${this.domain} domain? You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.`);
if (response) {
const acc = this.toolsService.getSelectedAccounts()[0];
this.mastodonService.blockDomain(acc, this.domain)
.then(_ => {
this.relationship.domain_blocking = true;
})
.catch(err => {
this.notificationService.notifyHttpError(err, acc);
});
}
return false;
}
unblockDomain(): boolean {
const acc = this.toolsService.getSelectedAccounts()[0];
this.mastodonService.blockDomain(acc, this.domain)
.then(_ => {
this.relationship.domain_blocking = false;
})
.catch(err => {
this.notificationService.notifyHttpError(err, acc);
});
return false;
}
muteConversation(): boolean {
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
this.getStatus(selectedAccount)
.then((status: Status) => {
return this.mastodonService.muteConversation(selectedAccount, status.id)
})
.then((status: Status) => {
this.displayedStatus.muted = status.muted;
})
.catch(err => {
this.notificationService.notifyHttpError(err, selectedAccount);
});
return false;
}
unmuteConversation(): boolean {
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
this.getStatus(selectedAccount)
.then((status: Status) => {
return this.mastodonService.unmuteConversation(selectedAccount, status.id)
})
.then((status: Status) => {
this.displayedStatus.muted = status.muted;
})
.catch(err => {
this.notificationService.notifyHttpError(err, selectedAccount);
});
return false;
}
pinOnProfile(): boolean {
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
this.getStatus(selectedAccount)
.then((status: Status) => {
return this.mastodonService.pinOnProfile(selectedAccount, status.id)
})
.then((status: Status) => {
this.displayedStatus.pinned = status.pinned;
})
.catch(err => {
this.notificationService.notifyHttpError(err, selectedAccount);
});
return false;
}
unpinFromProfile(): boolean {
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
this.getStatus(selectedAccount)
.then((status: Status) => {
return this.mastodonService.unpinFromProfile(selectedAccount, status.id)
})
.then((status: Status) => {
this.displayedStatus.pinned = status.pinned;
})
.catch(err => {
this.notificationService.notifyHttpError(err, selectedAccount);
});
return false;
}
delete(redraft: boolean): boolean {
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
this.getStatus(selectedAccount)
.then((status: Status) => {
return this.mastodonService.deleteStatus(selectedAccount, status.id);
})
.then(() => {
if (redraft) {
this.navigationService.redraft(this.statusWrapper)
}
let cwPolicy = this.toolsService.checkContentWarning(this.displayedStatus);
const deletedStatus = new StatusWrapper(cwPolicy.status, selectedAccount, cwPolicy.applyCw, cwPolicy.hide);
this.notificationService.deleteStatus(deletedStatus);
})
.catch(err => {
this.notificationService.notifyHttpError(err, selectedAccount);
});
return false;
}
edit(): boolean {
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
this.getStatus(selectedAccount)
.then(() => {
this.navigationService.edit(this.statusWrapper);
})
.catch(err => {
this.notificationService.notifyHttpError(err, selectedAccount);
});
return false;
}
private getStatus(account: AccountInfo): Promise<Status> {
let statusPromise: Promise<Status> = Promise.resolve(this.statusWrapper.status);
if (account.id !== this.statusWrapper.provider.id) {
statusPromise =
this.toolsService.getInstanceInfo(account)
.then(instance => {
let version: 'v1' | 'v2' = 'v1';
if (instance.major >= 3) version = 'v2';
return this.mastodonService.search(account, this.statusWrapper.status.url, version, true);
})
.then((result: Results) => {
return result.statuses[0];
});
}
return statusPromise;
}
}