diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b7a78fbc..27e7c5e3 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -67,6 +67,7 @@ import { PollComponent } from './components/stream/status/poll/poll.component';
import { TimeLeftPipe } from './pipes/time-left.pipe';
import { AutosuggestComponent } from './components/create-status/autosuggest/autosuggest.component';
import { EmojiPickerComponent } from './components/create-status/emoji-picker/emoji-picker.component';
+import { StatusUserContextMenuComponent } from './components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component';
const routes: Routes = [
@@ -120,7 +121,8 @@ const routes: Routes = [
PollComponent,
TimeLeftPipe,
AutosuggestComponent,
- EmojiPickerComponent
+ EmojiPickerComponent,
+ StatusUserContextMenuComponent
],
entryComponents: [
EmojiPickerComponent
diff --git a/src/app/components/stream/status/action-bar/action-bar.component.html b/src/app/components/stream/status/action-bar/action-bar.component.html
index 0720e8f7..f783c925 100644
--- a/src/app/components/stream/status/action-bar/action-bar.component.html
+++ b/src/app/components/stream/status/action-bar/action-bar.component.html
@@ -28,7 +28,8 @@
-
+
+
\ No newline at end of file
diff --git a/src/app/components/stream/status/action-bar/action-bar.component.scss b/src/app/components/stream/status/action-bar/action-bar.component.scss
index e109df3e..e49a119d 100644
--- a/src/app/components/stream/status/action-bar/action-bar.component.scss
+++ b/src/app/components/stream/status/action-bar/action-bar.component.scss
@@ -1,5 +1,4 @@
@import "variables";
-@import "context-menu";
.action-bar {
// outline: 1px solid greenyellow; // height: 20px;
@@ -44,7 +43,7 @@
&--more {
position: absolute;
- right: -5px;
+ right: 11px;
// left: 155px;
bottom: -2px;
}
@@ -150,5 +149,4 @@
-moz-animation: loadingAnimation 1s infinite;
-o-animation: loadingAnimation 1s infinite;
animation: loadingAnimation 1s infinite;
-
}
\ No newline at end of file
diff --git a/src/app/components/stream/status/action-bar/action-bar.component.ts b/src/app/components/stream/status/action-bar/action-bar.component.ts
index 12fe5de7..31a5c17c 100644
--- a/src/app/components/stream/status/action-bar/action-bar.component.ts
+++ b/src/app/components/stream/status/action-bar/action-bar.component.ts
@@ -1,10 +1,9 @@
-import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewChild } from '@angular/core';
+import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } 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, faEnvelope } 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';
@@ -12,7 +11,6 @@ import { Status, Account, Results } from '../../../../services/models/mastodon.i
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',
@@ -29,12 +27,6 @@ export class ActionBarComponent implements OnInit, OnDestroy {
faLock = faLock;
faEnvelope = faEnvelope;
- @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();
@@ -51,27 +43,20 @@ export class ActionBarComponent implements OnInit, OnDestroy {
favoriteIsLoading: boolean;
boostIsLoading: boolean;
- isContentWarningActive: boolean = false;
+ isContentWarningActive: boolean = false;
- isOwnerSelected: boolean;
+ displayedStatus: Status;
private isProviderSelected: boolean;
private selectedAccounts: AccountInfo[];
- username: string;
- displayedStatus: Status;
- private fullHandle: string;
- private loadedAccounts: AccountInfo[];
-
private favoriteStatePerAccountId: { [id: string]: boolean; } = {};
private bootedStatePerAccountId: { [id: string]: boolean; } = {};
private accounts$: Observable;
private accountSub: Subscription;
- constructor(
- private readonly navigationService: NavigationService,
- private readonly contextMenuService: ContextMenuService,
+ constructor(
private readonly store: Store,
private readonly toolsService: ToolsService,
private readonly mastodonService: MastodonService,
@@ -87,12 +72,10 @@ export class ActionBarComponent implements OnInit, OnDestroy {
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;
}
@@ -101,18 +84,10 @@ export class ActionBarComponent implements OnInit, OnDestroy {
}
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 = this.toolsService.getAccountFullHandle(account);
- // this.fullHandle = `@${this.fullHandle}`;
- }
-
ngOnDestroy(): void {
this.accountSub.unsubscribe();
}
@@ -123,9 +98,6 @@ export class ActionBarComponent implements OnInit, OnDestroy {
this.selectedAccounts = accounts.filter(x => x.isSelected);
this.isProviderSelected = this.selectedAccounts.filter(x => x.id === provider.id).length > 0;
- this.isOwnerSelected = this.selectedAccounts[0].username === this.displayedStatus.account.username
- && this.selectedAccounts[0].instance === this.displayedStatus.account.url.replace('https://', '').split('/')[0];
-
if (status.visibility === 'direct' || status.visibility === 'private') {
this.isBoostLocked = true;
} else {
@@ -246,187 +218,9 @@ export class ActionBarComponent implements OnInit, OnDestroy {
} else {
this.isFavorited = false;
}
- }
+ }
- 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;
- }
-
- muteConversation(): boolean {
- const selectedAccount = this.selectedAccounts[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);
- });
-
- return false;
- }
-
- unmuteConversation(): boolean {
- const selectedAccount = this.selectedAccounts[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);
- });
-
- return false;
- }
-
- pinOnProfile(): boolean {
- const selectedAccount = this.selectedAccounts[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);
- });
-
- return false;
- }
-
- unpinFromProfile(): boolean {
- const selectedAccount = this.selectedAccounts[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);
- });
-
- return false;
- }
-
- delete(redraft: boolean): boolean {
- const selectedAccount = this.selectedAccounts[0];
-
- this.getStatus(selectedAccount)
- .then((status: Status) => {
- return this.mastodonService.deleteStatus(selectedAccount, status.id);
- })
- .then(() => {
- if (redraft) {
- this.navigationService.redraft(this.statusWrapper)
- }
-
- const deletedStatus = new StatusWrapper(this.displayedStatus, selectedAccount);
- this.notificationService.deleteStatus(deletedStatus);
- })
- .catch(err => {
- this.notificationService.notifyHttpError(err);
- });
-
- return false;
- }
-
- private getStatus(account: AccountInfo): Promise {
- let statusPromise: Promise = Promise.resolve(this.statusWrapper.status);
-
- if (account.id !== this.statusWrapper.provider.id) {
- statusPromise = this.mastodonService.search(account, this.statusWrapper.status.url, true)
- .then((result: Results) => {
- return result.statuses[0];
- });
- }
-
- return statusPromise;
+ browseThread(event: OpenThreadEvent){
+ this.browseThreadEvent.next(event);
}
}
diff --git a/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.html b/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.html
new file mode 100644
index 00000000..c681df4b
--- /dev/null
+++ b/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.html
@@ -0,0 +1,45 @@
+
+
+
+ Expand status
+
+
+ Copy link to status
+
+
+
+ Mention @{{ this.username }}
+
+
+ Direct message @{{ this.username }}
+
+
+ Mute conversation
+
+
+ Unmute conversation
+
+
+
+ Mute @{{ this.username }}
+
+
+ Block @{{ this.username }}
+
+
+ Pin on profile
+
+
+ Unpin from profile
+
+
+ Delete
+
+
+ Delete & re-draft
+
+
\ No newline at end of file
diff --git a/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.scss b/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.scss
new file mode 100644
index 00000000..330bff85
--- /dev/null
+++ b/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.scss
@@ -0,0 +1,16 @@
+@import "variables";
+@import "context-menu";
+
+.context-menu-link {
+ &__status {
+ color: $status-secondary-color;
+
+ &:hover {
+ color: $status-links-color;
+ }
+ }
+
+ &__profile {
+
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.spec.ts b/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.spec.ts
new file mode 100644
index 00000000..833bea46
--- /dev/null
+++ b/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { StatusUserContextMenuComponent } from './status-user-context-menu.component';
+
+xdescribe('StatusUserContextMenuComponent', () => {
+ let component: StatusUserContextMenuComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ StatusUserContextMenuComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(StatusUserContextMenuComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.ts b/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.ts
new file mode 100644
index 00000000..f90cacd5
--- /dev/null
+++ b/src/app/components/stream/status/action-bar/status-user-context-menu/status-user-context-menu.component.ts
@@ -0,0 +1,262 @@
+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 } from '../../../../../services/models/mastodon.interfaces';
+import { ToolsService, OpenThreadEvent } from '../../../../../services/tools.service';
+import { StatusWrapper } from '../../../../../models/common.model';
+import { NavigationService } from '../../../../../services/navigation.service';
+import { AccountInfo } from '../../../../../states/accounts.state';
+import { MastodonService } from '../../../../../services/mastodon.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;
+ isOwnerSelected: boolean;
+
+ @Input() statusWrapper: StatusWrapper;
+
+ @Output() browseThreadEvent = new EventEmitter();
+
+ @ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent;
+
+ private accounts$: Observable;
+ private accountSub: Subscription;
+
+ constructor(
+ private readonly store: Store,
+ private readonly mastodonService: MastodonService,
+ 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() {
+ const status = this.statusWrapper.status;
+
+ if (status.reblog) {
+ this.displayedStatus = status.reblog;
+ } else {
+ this.displayedStatus = status;
+ }
+
+ this.username = this.displayedStatus.account.acct.split('@')[0];
+ this.fullHandle = this.toolsService.getAccountFullHandle(this.displayedStatus.account);
+
+ this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
+ this.loadedAccounts = accounts;
+ this.checkStatus(accounts);
+ });
+
+ }
+
+ private checkStatus(accounts: AccountInfo[]): void {
+ const selectedAccount = accounts.find(x => x.isSelected);
+
+ this.isOwnerSelected = selectedAccount.username === this.displayedStatus.account.username
+ && selectedAccount.instance === this.displayedStatus.account.url.replace('https://', '').split('/')[0];
+ }
+
+
+ ngOnDestroy(): void {
+ 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;
+ }
+
+ 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;
+ }
+
+ 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);
+ });
+
+ 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);
+ });
+
+ return false;
+ }
+
+ pinOnProfile(): boolean {
+ const selectedAccount = this.toolsService.getSelectedAccounts()[0];
+
+ this.getStatus(selectedAccount)
+ .then((status: Status) => {
+ console.warn(status);
+ return this.mastodonService.pinOnProfile(selectedAccount, status.id)
+ })
+ .then((status: Status) => {
+ this.displayedStatus.pinned = status.pinned;
+ })
+ .catch(err => {
+ this.notificationService.notifyHttpError(err);
+ });
+
+ 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);
+ });
+
+ 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)
+ }
+
+ const deletedStatus = new StatusWrapper(this.displayedStatus, selectedAccount);
+ this.notificationService.deleteStatus(deletedStatus);
+ })
+ .catch(err => {
+ this.notificationService.notifyHttpError(err);
+ });
+
+ return false;
+ }
+
+ private getStatus(account: AccountInfo): Promise {
+ let statusPromise: Promise = Promise.resolve(this.statusWrapper.status);
+
+ if (account.id !== this.statusWrapper.provider.id) {
+ statusPromise = this.mastodonService.search(account, this.statusWrapper.status.url, true)
+ .then((result: Results) => {
+ return result.statuses[0];
+ });
+ }
+
+ return statusPromise;
+ }
+}