Merge pull request #130 from NicolasConstant/topic_context-menu_ngx-contextmenu

Topic context menu ngx contextmenu
This commit is contained in:
Nicolas Constant 2019-07-03 23:30:18 -04:00 committed by GitHub
commit 8aac46a4c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 428 additions and 72 deletions

27
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "sengi",
"version": "0.9.1",
"version": "0.11.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -214,6 +214,23 @@
"tslib": "^1.9.0"
}
},
"@angular/cdk": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-7.3.7.tgz",
"integrity": "sha512-xbXxhHHKGkVuW6K7pzPmvpJXIwpl0ykBnvA2g+/7Sgy5Pd35wCC+UtHD9RYczDM/mkygNxMQtagyCErwFnDtQA==",
"requires": {
"parse5": "^5.0.0",
"tslib": "^1.7.1"
},
"dependencies": {
"parse5": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
"integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==",
"optional": true
}
}
},
"@angular/cli": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.4.tgz",
@ -8160,6 +8177,14 @@
"integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
"dev": true
},
"ngx-contextmenu": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ngx-contextmenu/-/ngx-contextmenu-5.2.0.tgz",
"integrity": "sha512-S8W7YUJJ+McWCfHv04gWURK7doJBl+1YZUynyP8QQMMwGd02OiggBp38HrEHAdMr4TdvKyo4Td5Ud63x28tpLg==",
"requires": {
"tslib": "^1.9.0"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",

View File

@ -23,11 +23,12 @@
"e2e": "ng e2e",
"electron": "ng build --prod && electron .",
"electron-debug": "ng build && electron .",
"dist": "npm run build && build --publish onTagOrDraft"
"dist": "npm run build && electron-builder --publish onTagOrDraft"
},
"private": true,
"dependencies": {
"@angular/animations": "^7.2.7",
"@angular/cdk": "^7.2.7",
"@angular/common": "^7.2.7",
"@angular/compiler": "^7.2.7",
"@angular/core": "^7.2.7",
@ -46,6 +47,7 @@
"bootstrap": "^4.1.3",
"core-js": "^2.5.4",
"emojione": "~4.5.0",
"ngx-contextmenu": "^5.2.0",
"rxjs": "^6.4.0",
"tslib": "^1.9.0",
"zone.js": "^0.8.29"

View File

@ -4,7 +4,7 @@ import { debounceTime, map } from 'rxjs/operators';
import { Select } from '@ngxs/store';
// import { ElectronService } from 'ngx-electron';
import { NavigationService, LeftPanelType } from './services/navigation.service';
import { NavigationService, LeftPanelType, OpenLeftPanelEvent } from './services/navigation.service';
import { StreamElement } from './states/streams.state';
import { OpenMediaEvent } from './models/common.model';
import { ToolsService } from './services/tools.service';
@ -44,8 +44,8 @@ export class AppComponent implements OnInit, OnDestroy {
}
});
this.columnEditorSub = this.navigationService.activatedPanelSubject.subscribe((type: LeftPanelType) => {
if (type === LeftPanelType.Closed) {
this.columnEditorSub = this.navigationService.activatedPanelSubject.subscribe((event: OpenLeftPanelEvent) => {
if (event.type === LeftPanelType.Closed) {
this.floatingColumnActive = false;
} else {
this.floatingColumnActive = true;

View File

@ -11,6 +11,7 @@ import { NgxsModule } from '@ngxs/store';
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { ContextMenuModule } from 'ngx-contextmenu';
import { AppComponent } from "./app.component";
import { LeftSideBarComponent } from "./components/left-side-bar/left-side-bar.component";
@ -128,7 +129,8 @@ const routes: Routes = [
StreamsState,
SettingsState
]),
NgxsStoragePluginModule.forRoot()
NgxsStoragePluginModule.forRoot(),
ContextMenuModule.forRoot()
],
providers: [AuthService, NavigationService, NotificationService, MastodonService, StreamingService],
bootstrap: [AppComponent],

View File

@ -21,7 +21,7 @@ import { identifierModuleUrl } from '@angular/compiler';
})
export class CreateStatusComponent implements OnInit, OnDestroy {
private _title: string;
set title(value: string){
set title(value: string) {
this._title = value;
this.countStatusChar(this.status);
}
@ -48,6 +48,26 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
@Output() onClose = new EventEmitter();
@ViewChild('reply') replyElement: ElementRef;
private _isDirectMention: boolean;
@Input('isDirectMention')
set isDirectMention(value: boolean) {
this._isDirectMention = value;
this.initMention();
}
get isDirectMention(): boolean {
return this._isDirectMention;
}
private _replyingUserHandle: string;
@Input('replyingUserHandle')
set replyingUserHandle(value: string) {
this._replyingUserHandle = value;
this.initMention();
}
get replyingUserHandle(): string {
return this._replyingUserHandle;
}
private statusReplyingTo: Status;
selectedPrivacy = 'Public';
@ -55,6 +75,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
private accounts$: Observable<AccountInfo[]>;
private accountSub: Subscription;
private selectedAccount: AccountInfo;
constructor(
private readonly store: Store,
@ -70,7 +91,8 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
this.accountChanged(accounts);
});
this.selectedAccount = this.toolsService.getSelectedAccounts()[0];
if (this.statusReplyingToWrapper) {
if (this.statusReplyingToWrapper.status.reblog) {
this.statusReplyingTo = this.statusReplyingToWrapper.status.reblog;
@ -99,27 +121,49 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
}
this.title = this.statusReplyingTo.spoiler_text;
} else if (this.replyingUserHandle) {
this.initMention();
}
setTimeout(() => {
this.replyElement.nativeElement.focus();
}, 0);
this.focus();
}
ngOnDestroy() {
this.accountSub.unsubscribe();
}
private focus() {
setTimeout(() => {
this.replyElement.nativeElement.focus();
}, 0);
}
private initMention() {
if (!this.selectedAccount) {
this.selectedAccount = this.toolsService.getSelectedAccounts()[0];
}
if (this.isDirectMention) {
this.setVisibility(VisibilityEnum.Direct);
} else {
this.getDefaultPrivacy();
}
this.status = `@${this.replyingUserHandle} `;
this.countStatusChar(this.status);
this.focus();
}
private accountChanged(accounts: AccountInfo[]): void {
if (accounts && accounts.length > 0) {
const selectedAccount = accounts.filter(x => x.isSelected)[0];
this.selectedAccount = accounts.filter(x => x.isSelected)[0];
const settings = this.toolsService.getAccountSettings(selectedAccount);
const settings = this.toolsService.getAccountSettings(this.selectedAccount);
if (settings.customStatusCharLengthEnabled) {
this.maxCharLength = settings.customStatusCharLength;
this.countStatusChar(this.status);
} else {
this.instancesInfoService.getMaxStatusChars(selectedAccount.instance)
this.instancesInfoService.getMaxStatusChars(this.selectedAccount.instance)
.then((maxChars: number) => {
this.maxCharLength = maxChars;
this.countStatusChar(this.status);
@ -129,18 +173,22 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
});
}
if (!this.statusReplyingToWrapper) {
this.instancesInfoService.getDefaultPrivacy(selectedAccount)
.then((defaultPrivacy: VisibilityEnum) => {
this.setVisibility(defaultPrivacy);
})
.catch((err: HttpErrorResponse) => {
this.notificationService.notifyHttpError(err);
});
if (!this.statusReplyingToWrapper && !this.replyingUserHandle) {
this.getDefaultPrivacy();
}
}
}
private getDefaultPrivacy() {
this.instancesInfoService.getDefaultPrivacy(this.selectedAccount)
.then((defaultPrivacy: VisibilityEnum) => {
this.setVisibility(defaultPrivacy);
})
.catch((err: HttpErrorResponse) => {
this.notificationService.notifyHttpError(err);
});
}
private setVisibility(defaultPrivacy: VisibilityEnum) {
switch (defaultPrivacy) {
case VisibilityEnum.Public:

View File

@ -1,5 +1,7 @@
<div class="panel">
<h3 class="panel__title">new message</h3>
<app-create-status (onClose)="closeColumn()"></app-create-status>
<app-create-status (onClose)="closeColumn()"
[isDirectMention]="isDirectMention"
[replyingUserHandle]="userHandle"></app-create-status>
</div>

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Input } from '@angular/core';
import { NavigationService } from '../../../services/navigation.service';
@ -8,10 +8,14 @@ import { NavigationService } from '../../../services/navigation.service';
styleUrls: ['./add-new-status.component.scss']
})
export class AddNewStatusComponent implements OnInit {
constructor(
private readonly navigationService: NavigationService) { }
ngOnInit() {
@Input() isDirectMention: boolean;
@Input() userHandle: string;
constructor(private readonly navigationService: NavigationService) {
}
ngOnInit() {
}
closeColumn() {

View File

@ -15,7 +15,9 @@
(browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-manage-account>
<app-add-new-status *ngIf="openPanel === 'createNewStatus'"></app-add-new-status>
<app-add-new-status *ngIf="openPanel === 'createNewStatus'"
[isDirectMention]="isDirectMention"
[userHandle]="userHandle"></app-add-new-status>
<app-add-new-account *ngIf="openPanel === 'addNewAccount'"></app-add-new-account>
<app-search *ngIf="openPanel === 'search'"
(browseAccountEvent)="browseAccount($event)"

View File

@ -1,7 +1,7 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { faTimes } from "@fortawesome/free-solid-svg-icons";
import { NavigationService, LeftPanelType } from '../../services/navigation.service';
import { NavigationService, LeftPanelType, OpenLeftPanelEvent, LeftPanelAction } from '../../services/navigation.service';
import { AccountWrapper } from '../../models/account.models';
import { OpenThreadEvent } from '../../services/tools.service';
import { Subscription } from 'rxjs';
@ -21,6 +21,9 @@ export class FloatingColumnComponent implements OnInit, OnDestroy {
userAccountUsed: AccountWrapper;
isDirectMention: boolean;
userHandle: string;
openPanel: string = '';
private activatedPanelSub: Subscription;
@ -28,9 +31,11 @@ export class FloatingColumnComponent implements OnInit, OnDestroy {
constructor(private readonly navigationService: NavigationService) { }
ngOnInit() {
this.activatedPanelSub = this.navigationService.activatedPanelSubject.subscribe((type: LeftPanelType) => {
this.activatedPanelSub = this.navigationService.activatedPanelSubject.subscribe((event: OpenLeftPanelEvent) => {
this.isDirectMention = false;
this.userHandle = null;
this.overlayActive = false;
switch (type) {
switch (event.type) {
case LeftPanelType.Closed:
this.openPanel = '';
break;
@ -42,9 +47,11 @@ export class FloatingColumnComponent implements OnInit, OnDestroy {
}
break;
case LeftPanelType.CreateNewStatus:
if (this.openPanel === 'createNewStatus') {
if (this.openPanel === 'createNewStatus' && !event.userHandle) {
this.closePanel();
} else {
this.isDirectMention = event.action === LeftPanelAction.DM;
this.userHandle = event.userHandle;
this.openPanel = 'createNewStatus';
}
break;

View File

@ -4,16 +4,16 @@
</a>
<fa-icon *ngIf="isLocked" class="action-bar__lock" title="Account can't access this post" [icon]="faLock"></fa-icon>
<a *ngIf="!(isBoostLocked || isLocked)" href class="action-bar__link action-bar__link--boost" title="Boost" [class.boosted]="isBoosted"
[class.boosting]="boostIsLoading" (click)="boost()">
<a *ngIf="!(isBoostLocked || isLocked)" href class="action-bar__link action-bar__link--boost" title="Boost"
[class.boosted]="isBoosted" [class.boosting]="boostIsLoading" (click)="boost()">
<fa-icon [icon]="faRetweet"></fa-icon>
</a>
<fa-icon *ngIf="isBoostLocked && !isLocked" class="action-bar__lock" title="This post cannot be boosted"
[icon]="faLock"></fa-icon>
<fa-icon *ngIf="isLocked" class="action-bar__lock" title="Account can't access this post" [icon]="faLock"></fa-icon>
<a *ngIf="!isLocked" href class="action-bar__link action-bar__link--fav" title="Favourite" [class.favorited]="isFavorited"
[class.favoriting]="favoriteIsLoading" (click)="favorite()">
<a *ngIf="!isLocked" href class="action-bar__link action-bar__link--fav" title="Favourite"
[class.favorited]="isFavorited" [class.favoriting]="favoriteIsLoading" (click)="favorite()">
<fa-icon [icon]="faStar"></fa-icon>
</a>
<fa-icon *ngIf="isLocked" class="action-bar__lock" title="Account can't access this post" [icon]="faLock"></fa-icon>
@ -27,7 +27,32 @@
<fa-icon [icon]="faWindowCloseRegular"></fa-icon>
</a>
<!-- <a href class="action-bar__link action-bar__link--more" title="More" (click)="more()">
<a href class="action-bar__link action-bar__link--more" (click)="onContextMenu($event)" title="More">
<fa-icon [icon]="faEllipsisH"></fa-icon>
</a> -->
</a>
<context-menu #contextMenu>
<ng-template contextMenuItem (execute)="expandStatus()">
Expand status
</ng-template>
<ng-template contextMenuItem (execute)="copyStatusLink()">
Copy link to status
</ng-template>
<ng-template contextMenuItem divider="true"></ng-template>
<ng-template contextMenuItem (execute)="mentionAccount()">
Mention @{{ this.username }}
</ng-template>
<ng-template contextMenuItem (execute)="dmAccount()">
Direct message @{{ this.username }}
</ng-template>
<ng-template contextMenuItem divider="true"></ng-template>
<ng-template contextMenuItem (execute)="muteAccount()">
Mute @{{ this.username }}
</ng-template>
<ng-template contextMenuItem (execute)="blockAccount()">
Block @{{ this.username }}
</ng-template>
<!-- <ng-template contextMenuItem (execute)="showMessage('Hi, ' + $event.item.name)">
Mute @account
</ng-template> -->
</context-menu>
</div>

View File

@ -1,8 +1,9 @@
@import "variables";
@import "context-menu";
.action-bar {
// outline: 1px solid greenyellow; // height: 20px;
margin: 5px 10px 5px $avatar-column-space;
margin: 5px 0px 5px $avatar-column-space;
padding: 0;
font-size: 18px;
height: 30px;
@ -41,14 +42,12 @@
font-size: 22px;
}
// &--more {
// position: absolute;
// left: 155px;
// bottom: -6px;
// }
&--more {
position: absolute;
right: -5px;
// left: 155px;
bottom: -2px;
}
}
&__lock {

View File

@ -1,16 +1,18 @@
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Store } from '@ngxs/store';
import { Observable, Subscription } from 'rxjs';
import { faWindowClose, faReply, faRetweet, faStar, faEllipsisH, faLock } from "@fortawesome/free-solid-svg-icons";
import { faWindowClose as faWindowCloseRegular } from "@fortawesome/free-regular-svg-icons";
import { ContextMenuComponent, ContextMenuService } from 'ngx-contextmenu';
import { MastodonService } from '../../../../services/mastodon.service';
import { AccountInfo } from '../../../../states/accounts.state';
import { Status } from '../../../../services/models/mastodon.interfaces';
import { ToolsService } from '../../../../services/tools.service';
import { Status, Account } from '../../../../services/models/mastodon.interfaces';
import { ToolsService, OpenThreadEvent } from '../../../../services/tools.service';
import { NotificationService } from '../../../../services/notification.service';
import { StatusWrapper } from '../../../../models/common.model';
import { NavigationService } from '../../../../services/navigation.service';
@Component({
selector: 'app-action-bar',
@ -26,10 +28,18 @@ export class ActionBarComponent implements OnInit, OnDestroy {
faEllipsisH = faEllipsisH;
faLock = faLock;
@ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent;
public items = [
{ name: 'John', otherProperty: 'Foo' },
{ name: 'Joe', otherProperty: 'Bar' }
];
@Input() statusWrapper: StatusWrapper;
@Output() replyEvent = new EventEmitter();
@Output() cwIsActiveEvent = new EventEmitter<boolean>();
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
isFavorited: boolean;
isBoosted: boolean;
@ -51,6 +61,8 @@ export class ActionBarComponent implements OnInit, OnDestroy {
private accountSub: Subscription;
constructor(
private readonly navigationService: NavigationService,
private readonly contextMenuService: ContextMenuService,
private readonly store: Store,
private readonly toolsService: ToolsService,
private readonly mastodonService: MastodonService,
@ -59,23 +71,42 @@ export class ActionBarComponent implements OnInit, OnDestroy {
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
}
username: string;
private fullHandle: string;
private displayedStatus: Status;
private loadedAccounts: AccountInfo[];
ngOnInit() {
const status = this.statusWrapper.status;
const account = this.statusWrapper.provider;
if(status.reblog){
if (status.reblog) {
this.favoriteStatePerAccountId[account.id] = status.reblog.favourited;
this.bootedStatePerAccountId[account.id] = status.reblog.reblogged;
this.extractHandle(status.reblog.account);
this.displayedStatus = status.reblog;
} else {
this.favoriteStatePerAccountId[account.id] = status.favourited;
this.bootedStatePerAccountId[account.id] = status.reblogged;
}
this.extractHandle(status.account);
this.displayedStatus = status;
}
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
this.loadedAccounts = accounts;
this.checkStatus(accounts);
});
}
private extractHandle(account: Account) {
this.username = account.acct.split('@')[0];
this.fullHandle = account.acct.toLowerCase();
if (!this.fullHandle.includes('@')) {
this.fullHandle += `@${account.url.replace('https://', '').split('/')[0]}`;
}
this.fullHandle = `@${this.fullHandle}`;
}
ngOnDestroy(): void {
this.accountSub.unsubscribe();
}
@ -124,7 +155,7 @@ export class ActionBarComponent implements OnInit, OnDestroy {
}
boost(): boolean {
if(this.boostIsLoading) return;
if (this.boostIsLoading) return;
this.boostIsLoading = true;
const account = this.toolsService.getSelectedAccounts()[0];
@ -140,12 +171,12 @@ export class ActionBarComponent implements OnInit, OnDestroy {
}
})
.then((boostedStatus: Status) => {
if(boostedStatus.pleroma){
if (boostedStatus.pleroma) {
this.bootedStatePerAccountId[account.id] = boostedStatus.reblog !== null; //FIXME: when Pleroma will return the good status
} else {
this.bootedStatePerAccountId[account.id] = boostedStatus.reblogged;
}
}
this.checkIfBoosted();
})
.catch((err: HttpErrorResponse) => {
@ -159,7 +190,7 @@ export class ActionBarComponent implements OnInit, OnDestroy {
}
favorite(): boolean {
if(this.favoriteIsLoading) return;
if (this.favoriteIsLoading) return;
this.favoriteIsLoading = true;
const account = this.toolsService.getSelectedAccounts()[0];
@ -208,8 +239,82 @@ export class ActionBarComponent implements OnInit, OnDestroy {
}
}
more(): boolean {
console.warn('more'); //TODO
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;
}
mentionAccount(): boolean {
this.navigationService.replyToUser(this.fullHandle, false);
return false;
}
dmAccount(): boolean {
this.navigationService.replyToUser(this.fullHandle, true);
return false;
}
muteAccount(): boolean {
this.loadedAccounts.forEach(acc => {
this.toolsService.findAccount(acc, this.fullHandle)
.then((target: Account) => {
this.mastodonService.mute(acc, target.id);
return target;
})
.then((target: Account) => {
this.notificationService.hideAccount(target);
})
.catch(err => {
this.notificationService.notifyHttpError(err);
});
});
return false;
}
blockAccount(): boolean {
this.loadedAccounts.forEach(acc => {
this.toolsService.findAccount(acc, this.fullHandle)
.then((target: Account) => {
this.mastodonService.block(acc, target.id);
return target;
})
.then((target: Account) => {
this.notificationService.hideAccount(target);
})
.catch(err => {
this.notificationService.notifyHttpError(err);
});
});
return false;
}
}

View File

@ -93,7 +93,9 @@
[attachments]="displayedStatus.media_attachments">
</app-attachements>
<app-action-bar #appActionBar [statusWrapper]="displayedStatusWrapper" (cwIsActiveEvent)="changeCw($event)"
<app-action-bar #appActionBar [statusWrapper]="displayedStatusWrapper"
(browseThreadEvent)="browseThread($event)"
(cwIsActiveEvent)="changeCw($event)"
(replyEvent)="openReply()"></app-action-bar>
</div>
<app-create-status *ngIf="replyingToStatus" [statusReplyingToWrapper]="displayedStatusWrapper"

View File

@ -177,6 +177,10 @@ export class StatusComponent implements OnInit {
return false;
}
browseThread(event: OpenThreadEvent): void {
this.browseThreadEvent.next(event);
}
openUrl(url: string): boolean {
event.preventDefault();
window.open(url, "_blank");

View File

@ -59,6 +59,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
private goToTopSubscription: Subscription;
private streamsSubscription: Subscription;
private hideAccountSubscription: Subscription;
private streams$: Observable<StreamElement[]>;
constructor(
@ -78,18 +79,39 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
this.streamsSubscription = this.streams$.subscribe((streams: StreamElement[]) => {
let updatedStream = streams.find(x => x.id === this.streamElement.id);
if (this.hideBoosts !== updatedStream.hideBoosts
|| this.hideBots !== updatedStream.hideBots
|| this.hideReplies !== updatedStream.hideReplies) {
this.streamElement = updatedStream;
}
});
this.hideAccountSubscription = this.notificationService.hideAccountUrlStream.subscribe((accountUrl: string) => {
if (accountUrl) {
this.statuses = this.statuses.filter(x => {
if(x.status.reblog){
return x.status.reblog.account.url != accountUrl;
} else {
return x.status.account.url != accountUrl;
}
});
this.bufferStream = this.bufferStream.filter(x => {
if(x.reblog){
return x.reblog.account.url != accountUrl;
} else {
return x.account.url != accountUrl;
}
});
}
});
}
ngOnDestroy() {
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
if (this.streamsSubscription) this.streamsSubscription.unsubscribe();
if (this.hideAccountSubscription) this.hideAccountSubscription.unsubscribe();
}
refresh(): any {

View File

@ -39,6 +39,7 @@ export class ThreadComponent implements OnInit, OnDestroy {
@ViewChildren(StatusComponent) statusChildren: QueryList<StatusComponent>;
private newPostSub: Subscription;
private hideAccountSubscription: Subscription;
constructor(
private readonly notificationService: NotificationService,
@ -69,12 +70,23 @@ export class ThreadComponent implements OnInit, OnDestroy {
}
}
});
this.hideAccountSubscription = this.notificationService.hideAccountUrlStream.subscribe((accountUrl: string) => {
if (accountUrl) {
this.statuses = this.statuses.filter(x => {
if(x.status.reblog){
return x.status.reblog.account.url != accountUrl;
} else {
return x.status.account.url != accountUrl;
}
});
}
});
}
ngOnDestroy(): void {
if (this.newPostSub) {
this.newPostSub.unsubscribe();
}
if (this.newPostSub) this.newPostSub.unsubscribe();
if (this.hideAccountSubscription) this.hideAccountSubscription.unsubscribe();
}
private getThread(openThreadEvent: OpenThreadEvent) {

View File

@ -7,7 +7,7 @@ import { AccountInfo } from '../states/accounts.state';
import { StreamTypeEnum, StreamElement } from '../states/streams.state';
@Injectable()
export class MastodonService {
export class MastodonService {
private apiRoutes = new ApiRoutes();
constructor(private readonly httpClient: HttpClient) { }
@ -319,6 +319,18 @@ export class MastodonService {
let route = `https://${account.instance}${this.apiRoutes.getPoll}`.replace('{0}', pollId);
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.get<Poll>(route, { headers: headers }).toPromise();
}
mute(account: AccountInfo, accounId: number): Promise<Relationship> {
let route = `https://${account.instance}${this.apiRoutes.mute}`.replace('{0}', accounId.toString());
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.post<Relationship>(route, null, { headers: headers }).toPromise();
}
block(account: AccountInfo, accounId: number): Promise<Relationship> {
let route = `https://${account.instance}${this.apiRoutes.block}`.replace('{0}', accounId.toString());
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.post<Relationship>(route, null, { headers: headers }).toPromise();
}
}

View File

@ -7,7 +7,7 @@ import { OpenMediaEvent } from '../models/common.model';
@Injectable()
export class NavigationService {
private accountToManage: AccountWrapper;
activatedPanelSubject = new BehaviorSubject<LeftPanelType>(LeftPanelType.Closed);
activatedPanelSubject = new BehaviorSubject<OpenLeftPanelEvent>(new OpenLeftPanelEvent(LeftPanelType.Closed));
activatedMediaSubject: Subject<OpenMediaEvent> = new Subject<OpenMediaEvent>();
columnSelectedSubject = new BehaviorSubject<number>(-1);
@ -15,18 +15,27 @@ export class NavigationService {
openColumnEditor(acc: AccountWrapper) {
this.accountToManage = acc;
this.activatedPanelSubject.next(LeftPanelType.ManageAccount);
const newEvent = new OpenLeftPanelEvent(LeftPanelType.ManageAccount);
this.activatedPanelSubject.next(newEvent);
}
openPanel(type: LeftPanelType){
this.activatedPanelSubject.next(type);
const newEvent = new OpenLeftPanelEvent(type);
this.activatedPanelSubject.next(newEvent);
}
closePanel() {
this.activatedPanelSubject.next(LeftPanelType.Closed);
const newEvent = new OpenLeftPanelEvent(LeftPanelType.Closed);
this.activatedPanelSubject.next(newEvent);
this.accountToManage = null;
}
replyToUser(userHandle: string, isDirectMessage: boolean = false) {
const action = isDirectMessage ? LeftPanelAction.DM : LeftPanelAction.Mention;
const newEvent = new OpenLeftPanelEvent(LeftPanelType.CreateNewStatus, action, userHandle);
this.activatedPanelSubject.next(newEvent);
}
columnSelected(index: number): void {
this.columnSelectedSubject.next(index);
}
@ -40,6 +49,20 @@ export class NavigationService {
}
}
export class OpenLeftPanelEvent {
constructor(
public type: LeftPanelType,
public action: LeftPanelAction = LeftPanelAction.None,
public userHandle: string = null ) {
}
}
export enum LeftPanelAction {
None = 0,
DM = 1,
Mention = 2
}
export enum LeftPanelType {
Closed = 0,
ManageAccount = 1,

View File

@ -2,12 +2,13 @@ import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
import { StatusWrapper } from '../models/common.model';
import { Status } from './models/mastodon.interfaces';
import { Account } from './models/mastodon.interfaces';
@Injectable()
export class NotificationService {
public notifactionStream = new Subject<NotificatioData>();
public newRespondPostedStream = new Subject<NewReplyData>();
public hideAccountUrlStream = new Subject<string>();
constructor() {
}
@ -31,6 +32,10 @@ export class NotificationService {
const notification = new NewReplyData(uiStatusRepliedToId, response);
this.newRespondPostedStream.next(notification);
}
public hideAccount(account: Account){
this.hideAccountUrlStream.next(account.url);
}
}
export class NotificatioData {

View File

@ -9,7 +9,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> -->
<style>
.lds-ripple {

View File

@ -0,0 +1,46 @@
@import "variables";
::ng-deep .ngx-contextmenu {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
-o-box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
& .dropdown-menu {
border: solid 1px $context-menu-border-color;
background-color: $context-menu-background;
padding: 2px 0;
border-radius: 2px;
//border: solid 2px $context-menu-border-color;
}
& li {
display: block;
// border-top: solid 1px $context-menu-border-color;
// text-transform: uppercase;
// text-align: center;
// &:first-child {
// border-top: none;
// }
}
& a {
color: $context-menu-font-color;
display: block;
padding: 0.5em 1em;
padding: 3px 10px;
text-decoration: none;
transition: all .2s;
&:hover {
color: $context-menu-font-color;
background-color: $context-menu-background-hover;
text-decoration: none;
}
}
& .divider {
border-top: solid 2px $context-menu-border-color;
}
}

View File

@ -52,4 +52,13 @@ $button-border-color: #303957;
$column-background: #0f111a;
$card-border-color: #1e2435;
$card-border-color: #1e2435;
$context-menu-background: #090b10;
$context-menu-background: #d9e1e8;
$context-menu-background-hover: #16171d;
$context-menu-background-hover: #a9c9e6;
$context-menu-font-color: #4e5572;
$context-menu-font-color: #000000;
$context-menu-border-color: #4e5572;
$context-menu-border-color: #c0cdd9;