Merge pull request #130 from NicolasConstant/topic_context-menu_ngx-contextmenu
Topic context menu ngx contextmenu
This commit is contained in:
commit
8aac46a4c2
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
|
@ -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() {
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
Loading…
Reference in New Issue