Merge pull request #614 from NicolasConstant/develop

1.6.0 PR
This commit is contained in:
Nicolas Constant 2023-08-24 00:30:44 -04:00 committed by GitHub
commit 0f58252c61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1710 additions and 48 deletions

1169
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "sengi", "name": "sengi",
"version": "1.5.0", "version": "1.6.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"main": "main-electron.js", "main": "main-electron.js",
"description": "A multi-account desktop client for Mastodon and Pleroma", "description": "A multi-account desktop client for Mastodon and Pleroma",
@ -25,13 +25,14 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^7.2.7", "@angular/animations": "^7.2.16",
"@angular/cdk": "^7.2.7", "@angular/cdk": "^7.3.7",
"@angular/common": "^7.2.7", "@angular/common": "^7.2.7",
"@angular/compiler": "^7.2.7", "@angular/compiler": "^7.2.7",
"@angular/core": "^7.2.7", "@angular/core": "^7.2.7",
"@angular/forms": "^7.2.7", "@angular/forms": "^7.2.7",
"@angular/http": "^7.2.7", "@angular/http": "^7.2.7",
"@angular/material": "^16.2.1",
"@angular/platform-browser": "^7.2.7", "@angular/platform-browser": "^7.2.7",
"@angular/platform-browser-dynamic": "^7.2.7", "@angular/platform-browser-dynamic": "^7.2.7",
"@angular/pwa": "^0.12.4", "@angular/pwa": "^0.12.4",

View File

@ -5,6 +5,7 @@ import { HttpModule } from "@angular/http";
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { NgModule, APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; import { NgModule, APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { DragDropModule } from '@angular/cdk/drag-drop';
// import { NgxElectronModule } from 'ngx-electron'; // import { NgxElectronModule } from 'ngx-electron';
import { NgxsModule } from '@ngxs/store'; import { NgxsModule } from '@ngxs/store';
@ -177,6 +178,7 @@ const routes: Routes = [
OwlDateTimeModule, OwlDateTimeModule,
OwlNativeDateTimeModule, OwlNativeDateTimeModule,
OverlayModule, OverlayModule,
DragDropModule,
// NgxElectronModule, // NgxElectronModule,
RouterModule.forRoot(routes), RouterModule.forRoot(routes),

View File

@ -68,6 +68,8 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
this.detectAutosuggestion(value); this.detectAutosuggestion(value);
this._status = value; this._status = value;
this.languageService.autoDetectLang(value);
setTimeout(() => { setTimeout(() => {
this.autoGrow(); this.autoGrow();
}, 0); }, 0);

View File

@ -79,6 +79,8 @@ export class PollEditorComponent implements OnInit {
} }
private loadPollParameters(poll: Poll) { private loadPollParameters(poll: Poll) {
if(!this.oldPoll) return;
const isMulti = poll.multiple; const isMulti = poll.multiple;
this.entries.length = 0; this.entries.length = 0;

View File

@ -15,7 +15,9 @@
<button type="submit" class="form-button" <button type="submit" class="form-button"
title="add account" title="add account"
[class.comrade__button]="isComrade"> [class.comrade__button]="isComrade">
<span *ngIf="!isLoading">Submit</span>
<span *ngIf="!isLoading && !this.isInstanceMultiAccountLoading">Submit</span>
<span *ngIf="!isLoading && this.isInstanceMultiAccountLoading" class="faq__warning">See FAQ</span>
<app-waiting-animation *ngIf="isLoading" class="waiting-icon"></app-waiting-animation> <app-waiting-animation *ngIf="isLoading" class="waiting-icon"></app-waiting-animation>
</button> </button>
@ -29,5 +31,12 @@
allowfullscreen></iframe> allowfullscreen></iframe>
</div> </div>
<div class="faq" *ngIf="isInstanceMultiAccount">
<p>
FAQ<br/>
<a href="https://github.com/NicolasConstant/sengi/wiki/How-to-add-multiple-accounts-from-the-same-instance" target="_blank">How to add multiple accounts from the same instance?</a>
</p>
</div>
</div> </div>
</div> </div>

View File

@ -109,4 +109,21 @@ $comrade_red: #a50000;
background-color: $comrade_red; background-color: $comrade_red;
background-position: 0 0; background-position: 0 0;
} }
}
.faq {
margin: 20px 0 0 0;
& a {
color: #ffcc00;
text-decoration: underline;
&:hover {
color: #ffe88a;
}
}
&__warning {
color: #ffdc52;
}
} }

View File

@ -6,13 +6,14 @@ import { RegisteredAppsStateModel, AppInfo, AddRegisteredApp } from '../../../st
import { AuthService, CurrentAuthProcess } from '../../../services/auth.service'; import { AuthService, CurrentAuthProcess } from '../../../services/auth.service';
import { AppData } from '../../../services/models/mastodon.interfaces'; import { AppData } from '../../../services/models/mastodon.interfaces';
import { NotificationService } from '../../../services/notification.service'; import { NotificationService } from '../../../services/notification.service';
import { ToolsService } from '../../../services/tools.service';
@Component({ @Component({
selector: 'app-add-new-account', selector: 'app-add-new-account',
templateUrl: './add-new-account.component.html', templateUrl: './add-new-account.component.html',
styleUrls: ['./add-new-account.component.scss'] styleUrls: ['./add-new-account.component.scss']
}) })
export class AddNewAccountComponent implements OnInit { export class AddNewAccountComponent implements OnInit {
private blockList = ['gab.com', 'gab.ai', 'cyzed.com']; private blockList = ['gab.com', 'gab.ai', 'cyzed.com'];
private comradeList = ['juche.town']; private comradeList = ['juche.town'];
@ -24,12 +25,14 @@ export class AddNewAccountComponent implements OnInit {
set setInstance(value: string) { set setInstance(value: string) {
this.instance = value.replace('http://', '').replace('https://', '').replace('/', '').toLowerCase().trim(); this.instance = value.replace('http://', '').replace('https://', '').replace('/', '').toLowerCase().trim();
this.checkComrad(); this.checkComrad();
this.checkInstanceMultiAccount(value);
} }
get setInstance(): string { get setInstance(): string {
return this.instance; return this.instance;
} }
constructor( constructor(
private readonly toolsService: ToolsService,
private readonly notificationService: NotificationService, private readonly notificationService: NotificationService,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly store: Store) { } private readonly store: Store) { }
@ -51,8 +54,27 @@ export class AddNewAccountComponent implements OnInit {
this.isComrade = false; this.isComrade = false;
} }
isInstanceMultiAccount: boolean;
isInstanceMultiAccountLoading: boolean;
checkInstanceMultiAccount(value: string) {
if(value) {
const instances: string[] = this.toolsService.getAllAccounts().map(x => x.instance);
if(instances && instances.indexOf(value) > -1){
this.isInstanceMultiAccount = true;
this.isInstanceMultiAccountLoading = true;
setTimeout(() => {
this.isInstanceMultiAccountLoading = false;
}, 2000);
} else {
this.isInstanceMultiAccount = false;
this.isInstanceMultiAccountLoading = false;
}
}
}
onSubmit(): boolean { onSubmit(): boolean {
if(this.isLoading || !this.instance) return false; if(this.isLoading || !this.instance || this.isInstanceMultiAccountLoading) return false;
this.isLoading = true; this.isLoading = true;

View File

@ -62,12 +62,20 @@
<a href (click)="onRemoveLang(l)" class="form-button language__entry__action sound__play">remove</a> <a href (click)="onRemoveLang(l)" class="form-button language__entry__action sound__play">remove</a>
</div> </div>
<input type="text" (input)="onSearchLang($event.target.value)" [(ngModel)]="searchLang" <input type="text" (input)="onSearchLang($event.target.value)" [(ngModel)]="searchLang"
placeholder="Find Language" autocomplete="off" class="form-control form-control-sm language__search"/> placeholder="Find Language" autocomplete="off"
class="form-control form-control-sm language__search" />
<div *ngFor="let l of searchedLangs" class="language__entry"> <div *ngFor="let l of searchedLangs" class="language__entry">
<span class="language__entry__name">{{ l.name }} ({{l.iso639}})</span> <span class="language__entry__name">{{ l.name }} ({{l.iso639}})</span>
<a href (click)="onAddLang(l)" class="form-button language__entry__action sound__play">add</a> <a href (click)="onAddLang(l)" class="form-button language__entry__action sound__play">add</a>
</div> </div>
</div> </div>
<input class="sub-section__checkbox" [(ngModel)]="disableLangAutodetectEnabled"
(change)="onDisableLangAutodetectChanged()" type="checkbox" name="disableLangAutodetec"
value="disableLangAutodetec" id="disableLangAutodetec">
<label class="noselect sub-section__label" for="disableLangAutodetec">disable language autodetection</label>
</div> </div>
<h4 class="panel__subtitle">Twitter Bridge</h4> <h4 class="panel__subtitle">Twitter Bridge</h4>
<div class="sub-section"> <div class="sub-section">
@ -159,7 +167,8 @@
<input class="sub-section__checkbox" [checked]="timeLineHeader === 6" (change)="onTimeLineHeaderChange(6)" <input class="sub-section__checkbox" [checked]="timeLineHeader === 6" (change)="onTimeLineHeaderChange(6)"
type="radio" name="timelineheader-6" value="timelineheader-6" id="timelineheader-6"> type="radio" name="timelineheader-6" value="timelineheader-6" id="timelineheader-6">
<label class="noselect sub-section__label" for="timelineheader-6">Title - Account Icon - Username - Domain Name</label> <label class="noselect sub-section__label" for="timelineheader-6">Title - Account Icon - Username - Domain
Name</label>
<br> <br>
<span class="sub-section__title">loading behavior:</span><br /> <span class="sub-section__title">loading behavior:</span><br />
@ -186,7 +195,8 @@
<input class="sub-section__checkbox" [(ngModel)]="autoFollowOnListEnabled" <input class="sub-section__checkbox" [(ngModel)]="autoFollowOnListEnabled"
(change)="onAutoFollowOnListChanged()" type="checkbox" name="onAutoFollowOnListChanged" (change)="onAutoFollowOnListChanged()" type="checkbox" name="onAutoFollowOnListChanged"
value="onAutoFollowOnListChanged" id="onAutoFollowOnListChanged"> value="onAutoFollowOnListChanged" id="onAutoFollowOnListChanged">
<label class="noselect sub-section__label" for="onAutoFollowOnListChanged">autofollow accounts when adding to list</label> <label class="noselect sub-section__label" for="onAutoFollowOnListChanged">autofollow accounts when
adding to list</label>
<br> <br>
</div> </div>
</div> </div>
@ -199,6 +209,20 @@
<label class="noselect sub-section__label" for="disableRemoteFetching">disable remote status <label class="noselect sub-section__label" for="disableRemoteFetching">disable remote status
fetching</label> fetching</label>
<br> <br>
<input class="sub-section__checkbox" [(ngModel)]="enableAltLabelEnabled"
(change)="onEnableAltLabelChanged()" type="checkbox" name="enableAltLabel"
value="enableAltLabel" id="enableAltLabel">
<label class="noselect sub-section__label" for="enableAltLabel">enable alt label</label>
<br>
<input class="sub-section__checkbox" [(ngModel)]="enableFreezeAvatarEnabled"
(change)="onEnableFreezeAvatarChanged()" type="checkbox" name="enableFreezeAvatar"
value="enableFreezeAvatar" id="enableFreezeAvatar">
<label class="noselect sub-section__label" for="enableFreezeAvatar">freeze animated avatar</label>
<br>
reorder account's icons: <a href class="toogle-lock-icon-menu" (click)="toogleLockIconMenu()"><span *ngIf="iconMenuLocked">Unlock Icons</span><span *ngIf="!iconMenuLocked">Lock Icons</span></a>
</div> </div>
<h4 class="panel__subtitle">About</h4> <h4 class="panel__subtitle">About</h4>

View File

@ -153,4 +153,22 @@
background-color: #32384d; background-color: #32384d;
} }
} }
}
.toogle-lock-icon-menu {
display: block;
padding: 3px 40px;
width: 170px;
float: right;
text-align: center;
color: white;
background-color: #1f2330;
&:hover {
text-decoration: none;
background-color: #32384d;
}
} }

View File

@ -29,6 +29,9 @@ export class SettingsComponent implements OnInit, OnDestroy {
disableRemoteStatusFetchingEnabled: boolean; disableRemoteStatusFetchingEnabled: boolean;
disableAvatarNotificationsEnabled: boolean; disableAvatarNotificationsEnabled: boolean;
disableSoundsEnabled: boolean; disableSoundsEnabled: boolean;
disableLangAutodetectEnabled: boolean;
enableAltLabelEnabled: boolean;
enableFreezeAvatarEnabled: boolean;
version: string; version: string;
hasPleromaAccount: boolean; hasPleromaAccount: boolean;
@ -146,11 +149,21 @@ export class SettingsComponent implements OnInit, OnDestroy {
this.twitterBridgeInstance = settings.twitterBridgeInstance; this.twitterBridgeInstance = settings.twitterBridgeInstance;
this.configuredLangs = this.languageService.getConfiguredLanguages(); this.configuredLangs = this.languageService.getConfiguredLanguages();
this.disableLangAutodetectEnabled = settings.disableLangAutodetec;
this.enableAltLabelEnabled = settings.enableAltLabel;
this.enableFreezeAvatarEnabled = settings.enableFreezeAvatar;
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if(this.languageSub) this.languageSub.unsubscribe(); if(this.languageSub) this.languageSub.unsubscribe();
} }
iconMenuLocked = true;
toogleLockIconMenu(): boolean {
this.navigationService.changeIconMenuState(this.iconMenuLocked);
this.iconMenuLocked = ! this.iconMenuLocked;
return false;
}
onSearchLang(input: string) { onSearchLang(input: string) {
this.searchedLangs = this.languageService.searchLanguage(input); this.searchedLangs = this.languageService.searchLanguage(input);
@ -273,6 +286,27 @@ export class SettingsComponent implements OnInit, OnDestroy {
return false; return false;
} }
onEnableFreezeAvatarChanged(){
this.notifyRestartNeeded();
let settings = this.settingsService.getSettings();
settings.enableFreezeAvatar = this.enableFreezeAvatarEnabled;
this.settingsService.saveSettings(settings);
}
onEnableAltLabelChanged(){
this.notifyRestartNeeded();
let settings = this.settingsService.getSettings();
settings.enableAltLabel = this.enableAltLabelEnabled;
this.settingsService.saveSettings(settings);
}
onDisableLangAutodetectChanged() {
this.notifyRestartNeeded();
let settings = this.settingsService.getSettings();
settings.disableLangAutodetec = this.disableLangAutodetectEnabled;
this.settingsService.saveSettings(settings);
}
onDisableAutofocusChanged() { onDisableAutofocusChanged() {
this.notifyRestartNeeded(); this.notifyRestartNeeded();
let settings = this.settingsService.getSettings(); let settings = this.settingsService.getSettings();

View File

@ -8,27 +8,36 @@
<fa-icon [icon]="faSearch"></fa-icon> <fa-icon [icon]="faSearch"></fa-icon>
</a> </a>
<div *ngFor="let account of accounts"> <div *ngIf="!iconMenuIsDraggable">
<app-account-icon [account]="account" (toogleAccountNotify)="onToogleAccountNotify($event)" <div *ngFor="let account of accounts">
(openMenuNotify)="onOpenMenuNotify($event)"> <app-account-icon [account]="account" (toogleAccountNotify)="onToogleAccountNotify($event)"
</app-account-icon> (openMenuNotify)="onOpenMenuNotify($event)">
</app-account-icon>
</div>
</div> </div>
<div *ngIf="iconMenuIsDraggable" cdkDropList [cdkDropListData]="accounts" (cdkDropListDropped)="onDrop($event)">
<div *ngFor="let account of accounts" cdkDrag class="draggable">
<fa-icon class="draggable__icon" [icon]="faArrowsAltV"></fa-icon>
<img class="draggable__avatar" src="{{ account.avatar }}" />
</div>
</div>
<a class="left-bar-button left-bar-button--add left-bar-link" [ngClass]="{'no-accounts': hasAccounts === false }" <a class="left-bar-button left-bar-button--add left-bar-link" [ngClass]="{'no-accounts': hasAccounts === false }"
href title="add new account" (click)="addNewAccount()" (contextmenu)="addNewAccount()"> href title="add new account" (click)="addNewAccount()" (contextmenu)="addNewAccount()">
<fa-icon [icon]="faPlus"></fa-icon> <fa-icon [icon]="faPlus"></fa-icon>
</a> </a>
<a class="left-bar-button left-bar-button--scheduled left-bar-button--bottom left-bar-link" href title="scheduled statuses" <a class="left-bar-button left-bar-button--scheduled left-bar-button--bottom left-bar-link" href
*ngIf="hasAccounts && hasScheduledStatuses" title="scheduled statuses" *ngIf="hasAccounts && hasScheduledStatuses" (click)="openScheduledStatuses()"
(click)="openScheduledStatuses()"
(contextmenu)="openScheduledStatuses()"> (contextmenu)="openScheduledStatuses()">
<fa-icon [icon]="faCalendarAlt"></fa-icon> <fa-icon [icon]="faCalendarAlt"></fa-icon>
</a> </a>
<a class="left-bar-button left-bar-button--cog left-bar-button--bottom left-bar-link" href title="settings" (click)="openSettings()" <a class="left-bar-button left-bar-button--cog left-bar-button--bottom left-bar-link" href title="settings"
(contextmenu)="openSettings()"> (click)="openSettings()" (contextmenu)="openSettings()">
<fa-icon [icon]="faCog"></fa-icon> <fa-icon [icon]="faCog"></fa-icon>
</a> </a>
</div> </div>

View File

@ -82,4 +82,38 @@ $height-button: 40px;
.no-accounts { .no-accounts {
padding-top: 10px; padding-top: 10px;
// color: cornflowerblue; // color: cornflowerblue;
}
$draggable-accent-color: #47e927;
// $draggable-accent-color: #a8ff97;
.draggable {
width: 40px;
height: 40px;
margin: auto;
margin-bottom: 5px;
border: 2px solid #df0adf;
border: 2px solid $draggable-accent-color;
border-radius: 2px;
position: relative;
&__avatar {
width: calc(100%);
opacity: .8;
}
&__icon {
position: absolute;
float: left;
z-index: 5;
color:$draggable-accent-color;
top: 6px;
left: 12px;
font-size: 18px;
}
} }

View File

@ -1,12 +1,13 @@
import { Component, OnInit, OnDestroy } from "@angular/core"; import { Component, OnInit, OnDestroy } from "@angular/core";
import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop";
import { Subscription, Observable } from "rxjs"; import { Subscription, Observable } from "rxjs";
import { Store } from "@ngxs/store"; import { Store } from "@ngxs/store";
import { faPlus, faCog, faSearch } from "@fortawesome/free-solid-svg-icons"; import { faPlus, faCog, faSearch, faArrowsAltV } from "@fortawesome/free-solid-svg-icons";
import { faCommentAlt, faCalendarAlt } from "@fortawesome/free-regular-svg-icons"; import { faCommentAlt, faCalendarAlt } from "@fortawesome/free-regular-svg-icons";
import { HotkeysService, Hotkey } from 'angular2-hotkeys'; import { HotkeysService, Hotkey } from 'angular2-hotkeys';
import { AccountWrapper } from "../../models/account.models"; import { AccountWrapper } from "../../models/account.models";
import { AccountInfo, SelectAccount } from "../../states/accounts.state"; import { AccountInfo, ReorderAccounts, SelectAccount } from "../../states/accounts.state";
import { NavigationService, LeftPanelType } from "../../services/navigation.service"; import { NavigationService, LeftPanelType } from "../../services/navigation.service";
import { UserNotificationService, UserNotification } from '../../services/user-notification.service'; import { UserNotificationService, UserNotification } from '../../services/user-notification.service';
import { ToolsService } from '../../services/tools.service'; import { ToolsService } from '../../services/tools.service';
@ -24,6 +25,7 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
faPlus = faPlus; faPlus = faPlus;
faCog = faCog; faCog = faCog;
faCalendarAlt = faCalendarAlt; faCalendarAlt = faCalendarAlt;
faArrowsAltV = faArrowsAltV;
accounts: AccountWithNotificationWrapper[] = []; accounts: AccountWithNotificationWrapper[] = [];
hasAccounts: boolean; hasAccounts: boolean;
@ -33,6 +35,7 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
private accountSub: Subscription; private accountSub: Subscription;
private scheduledSub: Subscription; private scheduledSub: Subscription;
private notificationSub: Subscription; private notificationSub: Subscription;
private draggableIconMenuSub: Subscription;
constructor( constructor(
private readonly settingsService: SettingsService, private readonly settingsService: SettingsService,
@ -103,7 +106,13 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
} }
} }
iconMenuIsDraggable = false;
ngOnInit() { ngOnInit() {
this.draggableIconMenuSub = this.navigationService.enableDraggableIconMenu.subscribe(x => {
this.iconMenuIsDraggable = x;
});
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => { this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
if (accounts) { if (accounts) {
//Update and Add //Update and Add
@ -164,6 +173,17 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
this.accountSub.unsubscribe(); this.accountSub.unsubscribe();
this.notificationSub.unsubscribe(); this.notificationSub.unsubscribe();
this.scheduledSub.unsubscribe(); this.scheduledSub.unsubscribe();
this.draggableIconMenuSub.unsubscribe();
}
onDrop(event: CdkDragDrop<AccountWithNotificationWrapper[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data,
event.previousIndex,
event.currentIndex);
this.store.dispatch([new ReorderAccounts(this.accounts.map(x => x.info))])
}
} }
onToogleAccountNotify(acc: AccountWrapper) { onToogleAccountNotify(acc: AccountWrapper) {

View File

@ -27,7 +27,13 @@
<ng-template contextMenuItem (execute)="unmuteConversation()" *ngIf="statusWrapper && isOwnerSelected && displayedStatus.muted"> <ng-template contextMenuItem (execute)="unmuteConversation()" *ngIf="statusWrapper && isOwnerSelected && displayedStatus.muted">
Unmute conversation Unmute conversation
</ng-template> </ng-template>
<ng-template contextMenuItem divider="true"></ng-template> <ng-template contextMenuItem (execute)="hideBoosts()" *ngIf="!isOwnerSelected && this.relationship && this.relationship.following && this.relationship.showing_reblogs">
Hide boosts from @{{ this.username }}
</ng-template>
<ng-template contextMenuItem (execute)="unhideBoosts()" *ngIf="!isOwnerSelected && this.relationship && this.relationship.following && !this.relationship.showing_reblogs">
Unhide boosts from @{{ this.username }}
</ng-template>
<ng-template contextMenuItem divider="true" *ngIf="!isOwnerSelected"></ng-template>
<ng-template contextMenuItem (execute)="muteAccount()" *ngIf="!isOwnerSelected && this.relationship && !this.relationship.muting"> <ng-template contextMenuItem (execute)="muteAccount()" *ngIf="!isOwnerSelected && this.relationship && !this.relationship.muting">
Mute @{{ this.username }} Mute @{{ this.username }}
</ng-template> </ng-template>
@ -40,6 +46,14 @@
<ng-template contextMenuItem (execute)="unblockAccount()" *ngIf="!isOwnerSelected && this.relationship && this.relationship.blocking"> <ng-template contextMenuItem (execute)="unblockAccount()" *ngIf="!isOwnerSelected && this.relationship && this.relationship.blocking">
Unblock @{{ this.username }} Unblock @{{ this.username }}
</ng-template> </ng-template>
<ng-template contextMenuItem divider="true" *ngIf="!isOwnerSelected"></ng-template>
<ng-template contextMenuItem (execute)="blockDomain()" *ngIf="!isOwnerSelected && this.relationship && !this.relationship.domain_blocking">
Block domain {{ this.domain }}
</ng-template>
<ng-template contextMenuItem (execute)="unblockDomain()" *ngIf="!isOwnerSelected && this.relationship && this.relationship.domain_blocking">
Unblock domain {{ this.domain }}
</ng-template>
<ng-template contextMenuItem divider="true" *ngIf="isOwnerSelected"></ng-template>
<ng-template contextMenuItem (execute)="pinOnProfile()" *ngIf="statusWrapper && isOwnerSelected && !displayedStatus.pinned && displayedStatus.visibility === 'public'"> <ng-template contextMenuItem (execute)="pinOnProfile()" *ngIf="statusWrapper && isOwnerSelected && !displayedStatus.pinned && displayedStatus.visibility === 'public'">
Pin on profile Pin on profile
</ng-template> </ng-template>

View File

@ -25,6 +25,7 @@ export class StatusUserContextMenuComponent implements OnInit, OnDestroy {
private loadedAccounts: AccountInfo[]; private loadedAccounts: AccountInfo[];
displayedStatus: Status; displayedStatus: Status;
username: string; username: string;
domain: string;
isOwnerSelected: boolean; isOwnerSelected: boolean;
isEditingAvailable: boolean; isEditingAvailable: boolean;
@ -74,6 +75,7 @@ export class StatusUserContextMenuComponent implements OnInit, OnDestroy {
} }
this.username = account.acct.split('@')[0]; this.username = account.acct.split('@')[0];
this.domain = account.acct.split('@')[1];
this.fullHandle = this.toolsService.getAccountFullHandle(account); this.fullHandle = this.toolsService.getAccountFullHandle(account);
} }
@ -167,6 +169,38 @@ export class StatusUserContextMenuComponent implements OnInit, OnDestroy {
return false; 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 { muteAccount(): boolean {
const acc = this.toolsService.getSelectedAccounts()[0]; const acc = this.toolsService.getSelectedAccounts()[0];
@ -241,6 +275,37 @@ export class StatusUserContextMenuComponent implements OnInit, OnDestroy {
return false; 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 { muteConversation(): boolean {
const selectedAccount = this.toolsService.getSelectedAccounts()[0]; const selectedAccount = this.toolsService.getSelectedAccounts()[0];

View File

@ -1,4 +1,5 @@
<div class="image"> <div class="image">
<div class="image__alt" *ngIf="displayAltLabel">ALT</div>
<a href class="image__link" (click)="openExternal()" (auxclick)="openExternal()" title="open image"> <a href class="image__link" (click)="openExternal()" (auxclick)="openExternal()" title="open image">
<fa-icon class="image__link--icon" [icon]="faLink"></fa-icon> <fa-icon class="image__link--icon" [icon]="faLink"></fa-icon>
</a> </a>

View File

@ -29,6 +29,24 @@
opacity: 1; opacity: 1;
cursor: pointer; cursor: pointer;
} }
&__alt {
display: inline;
color: white;
z-index: 10;
position: absolute;
bottom: 5px;
left: 5px;
font-size: 10px;
font-weight: bolder;
background-color: rgba($color: #000000, $alpha: 0.5);
border-radius: 3px;
padding: 2px 5px;
}
} }
img, img,

View File

@ -1,6 +1,7 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { faLink } from "@fortawesome/free-solid-svg-icons"; import { faLink } from "@fortawesome/free-solid-svg-icons";
import { SettingsService } from '../../../../../services/settings.service';
import { Attachment } from '../../../../../services/models/mastodon.interfaces'; import { Attachment } from '../../../../../services/models/mastodon.interfaces';
@Component({ @Component({
@ -10,11 +11,16 @@ import { Attachment } from '../../../../../services/models/mastodon.interfaces';
}) })
export class AttachementImageComponent implements OnInit { export class AttachementImageComponent implements OnInit {
faLink = faLink; faLink = faLink;
displayAltLabel: boolean;
@Input() attachment: Attachment; @Input() attachment: Attachment;
@Output() openEvent = new EventEmitter(); @Output() openEvent = new EventEmitter();
constructor() { } constructor(
private readonly settingsService: SettingsService
) {
this.displayAltLabel = this.settingsService.getSettings().enableAltLabel;
}
ngOnInit() { ngOnInit() {
} }

View File

@ -64,6 +64,7 @@ export class StatusTranslateComponent implements OnInit, OnDestroy {
.then(canTranslate => { .then(canTranslate => {
if (canTranslate if (canTranslate
&& !this.status.isRemote && !this.status.isRemote
&& this.status.status.language
&& this.configuredLanguages.length > 0 && this.configuredLanguages.length > 0
&& this.configuredLanguages.findIndex(x => x.iso639 === this.status.status.language) === -1) { && this.configuredLanguages.findIndex(x => x.iso639 === this.status.status.language) === -1) {

View File

@ -2,7 +2,7 @@
<div class="reblog" *ngIf="reblog"> <div class="reblog" *ngIf="reblog">
<a class="reblog__profile-link" href title="{{ status.account.acct }}" <a class="reblog__profile-link" href title="{{ status.account.acct }}"
(click)="openAccount(status.account)" (click)="openAccount(status.account)"
(auxclick)="openUrl(status.account.url)"><span innerHTML="{{ status.account | accountEmoji }}"></span> <img *ngIf="reblog" class="reblog__avatar" src="{{ status.account.avatar | ensureHttps }}" /></a> boosted (auxclick)="openUrl(status.account.url)"><span innerHTML="{{ status.account | accountEmoji }}"></span> <img *ngIf="reblog" class="reblog__avatar" src="{{ getAvatar(status.account) | ensureHttps }}" /></a> boosted
</div> </div>
<div *ngIf="statusWrapper.status.pinned && !notificationType" class="pinned"> <div *ngIf="statusWrapper.status.pinned && !notificationType" class="pinned">
<div class="notification--icon"> <div class="notification--icon">
@ -60,9 +60,9 @@
<div [ngClass]="{'notification--status': notificationAccount }"> <div [ngClass]="{'notification--status': notificationAccount }">
<a href class="status__profile-link" title="{{displayedStatus.account.acct}}" <a href class="status__profile-link" title="{{displayedStatus.account.acct}}"
(click)="openAccount(displayedStatus.account)" (auxclick)="openUrl(displayedStatus.account.url)"> (click)="openAccount(displayedStatus.account)" (auxclick)="openUrl(displayedStatus.account.url)">
<img [class.status__avatar--boosted]="reblog || notificationAccount" class="status__avatar" src="{{ displayedStatus.account.avatar | ensureHttps }}" /> <img [class.status__avatar--boosted]="reblog || notificationAccount" class="status__avatar" src="{{ getAvatar(displayedStatus.account) | ensureHttps }}" />
<!-- <img *ngIf="reblog" class="status__avatar--reblog" src="{{ status.account.avatar }}" /> --> <!-- <img *ngIf="reblog" class="status__avatar--reblog" src="{{ status.account.avatar }}" /> -->
<img *ngIf="notificationAccount" class="notification--avatar" src="{{ notificationAccount.avatar | ensureHttps }}" /> <img *ngIf="notificationAccount" class="notification--avatar" src="{{ getAvatar(notificationAccount) | ensureHttps }}" />
<span class="status__name"> <span class="status__name">
<span class="status__name--displayname" <span class="status__name--displayname"
innerHTML="{{displayedStatus.account | accountEmoji}}"></span><span innerHTML="{{displayedStatus.account | accountEmoji}}"></span><span
@ -109,6 +109,11 @@
<span class="status__content-warning--title">sensitive content</span> <span class="status__content-warning--title">sensitive content</span>
<span innerHTML="{{ contentWarningText }}"></span> <span innerHTML="{{ contentWarningText }}"></span>
</a> </a>
<div class="status__content-warning__closed" *ngIf="!isContentWarned && contentWarningText" title="content warning">
<span innerHTML="{{ contentWarningText }}"></span>
</div>
<app-databinded-text #databindedtext class="status__content" *ngIf="!isContentWarned" [text]="statusContent" [selected]="isSelected" <app-databinded-text #databindedtext class="status__content" *ngIf="!isContentWarned" [text]="statusContent" [selected]="isSelected"
(accountSelected)="accountSelected($event)" (hashtagSelected)="hashtagSelected($event)" (accountSelected)="accountSelected($event)" (hashtagSelected)="hashtagSelected($event)"
(textSelected)="textSelected()"></app-databinded-text> (textSelected)="textSelected()"></app-databinded-text>

View File

@ -172,6 +172,26 @@
border: 3px solid $status-secondary-color; border: 3px solid $status-secondary-color;
color: whitesmoke; color: whitesmoke;
&__closed {
//margin: 0 5px 0 $avatar-column-space;
margin: 0 5px 0 calc(#{$avatar-column-space} - 1px);
padding: 3px 5px 3px 5px;
margin-bottom: 5px;
overflow-wrap: break-word;
font-size: 12px;
border-radius: 4px;
// color: #6d8fd3;
// color: #7282a1;
// color: #838da1;
color: #919bb1;
// background-color: #273149;
// background-color: #1f273a;
background-color: #171d2b;
}
&--title { &--title {
color: $content-warning-font-color; color: $content-warning-font-color;
font-size: 11px; font-size: 11px;

View File

@ -10,6 +10,7 @@ import { EmojiConverter, EmojiTypeEnum } from '../../../tools/emoji.tools';
import { ContentWarningPolicyEnum } from '../../../states/settings.state'; import { ContentWarningPolicyEnum } from '../../../states/settings.state';
import { StatusesStateService, StatusState } from "../../../services/statuses-state.service"; import { StatusesStateService, StatusState } from "../../../services/statuses-state.service";
import { DatabindedTextComponent } from "./databinded-text/databinded-text.component"; import { DatabindedTextComponent } from "./databinded-text/databinded-text.component";
import { SettingsService } from "../../../services/settings.service";
@Component({ @Component({
selector: "app-status", selector: "app-status",
@ -44,6 +45,8 @@ export class StatusComponent implements OnInit {
isSelected: boolean; isSelected: boolean;
isRemote: boolean; isRemote: boolean;
private freezeAvatarEnabled: boolean;
hideStatus: boolean = false; hideStatus: boolean = false;
@Output() browseAccountEvent = new EventEmitter<string>(); @Output() browseAccountEvent = new EventEmitter<string>();
@ -103,6 +106,7 @@ export class StatusComponent implements OnInit {
constructor( constructor(
public elem: ElementRef, public elem: ElementRef,
private readonly toolsService: ToolsService, private readonly toolsService: ToolsService,
private readonly settingsService: SettingsService,
private readonly statusesStateService: StatusesStateService) { } private readonly statusesStateService: StatusesStateService) { }
ngOnInit() { ngOnInit() {
@ -111,12 +115,22 @@ export class StatusComponent implements OnInit {
this.statusWrapper = notification.editedStatus; this.statusWrapper = notification.editedStatus;
} }
}); });
this.freezeAvatarEnabled = this.settingsService.getSettings().enableFreezeAvatar;
} }
ngOnDestroy() { ngOnDestroy() {
if (this.statusesStateServiceSub) this.statusesStateServiceSub.unsubscribe(); if (this.statusesStateServiceSub) this.statusesStateServiceSub.unsubscribe();
} }
getAvatar(acc: Account): string {
if(this.freezeAvatarEnabled){
return acc.avatar_static;
} else {
return acc.avatar;
}
}
private ensureMentionAreDisplayed(data: string): string { private ensureMentionAreDisplayed(data: string): string {
const mentions = this.displayedStatus.mentions; const mentions = this.displayedStatus.mentions;
if (!mentions || mentions.length === 0) return data; if (!mentions || mentions.length === 0) return data;

View File

@ -30,7 +30,7 @@ export class TimeAgoPipe implements PipeTransform {
const hours = minutes / 60; const hours = minutes / 60;
const days = hours / 24; const days = hours / 24;
// const months = days / 30.416; // const months = days / 30.416;
// const years = days / 365; const years = days / 365;
if (seconds <= 59) { if (seconds <= 59) {
text = Math.round(seconds) + 's'; text = Math.round(seconds) + 's';
@ -38,8 +38,10 @@ export class TimeAgoPipe implements PipeTransform {
text = Math.round(minutes) + 'm'; text = Math.round(minutes) + 'm';
} else if (hours <= 23) { } else if (hours <= 23) {
text = Math.round(hours) + 'h'; text = Math.round(hours) + 'h';
} else { } else if (days < 365) {
text = Math.round(days) + 'd'; text = Math.round(days) + 'd';
} else {
text = Math.round(years) + 'y';
} }
if (minutes < 1) { if (minutes < 1) {

View File

@ -1,10 +1,30 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class MyElectronService { export class MyElectronService {
detectedLangSubject = new Subject<DetectedLang[]>();
constructor() { constructor() {
try {
if ((<any>window).api) {
(<any>window).api.receive("detectedLang", (data) => {
const result = [];
for (const l of data) {
let newLang = new DetectedLang(l[0], l[1]);
result.push(newLang);
}
this.detectedLangSubject.next(result);
});
}
}
catch (err) {
console.error(err);
}
this.detectLang("ceci est une phrase");
} }
setLang(lang: string) { setLang(lang: string) {
@ -17,4 +37,22 @@ export class MyElectronService {
console.error(err); console.error(err);
} }
} }
detectLang(text: string) {
try {
if ((<any>window).api) {
(<any>window).api.send("detectLang", text);
}
}
catch (err) {
console.error(err);
}
}
}
export class DetectedLang {
constructor(
public lang: string,
public score: number
) {}
} }

View File

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { ILanguage } from '../states/settings.state'; import { ILanguage } from '../states/settings.state';
import { MyElectronService } from './electron.service'; import { DetectedLang, MyElectronService } from './electron.service';
import { SettingsService } from './settings.service'; import { SettingsService } from './settings.service';
@Injectable({ @Injectable({
@ -19,6 +19,42 @@ export class LanguageService {
) { ) {
this.configuredLanguagesChanged.next(this.getConfiguredLanguages()); this.configuredLanguagesChanged.next(this.getConfiguredLanguages());
this.selectedLanguageChanged.next(this.getSelectedLanguage()); this.selectedLanguageChanged.next(this.getSelectedLanguage());
this.electronService.detectedLangSubject.subscribe(l => {
this.detectedLanguage(l);
});
}
private detectedLanguage(lang: DetectedLang[]) {
if (!lang) return;
if (lang.length >= 1) {
const languages = this.getConfiguredLanguages();
let firstLang = lang[0].lang;
let firstLocalLang = languages.find(x => x.iso639 == firstLang);
if (firstLocalLang) {
this.setSelectedLanguage(firstLocalLang);
return;
}
if (lang.length > 1) {
firstLang = lang[1].lang;
firstLocalLang = languages.find(x => x.iso639 == firstLang);
if (firstLocalLang) {
this.setSelectedLanguage(firstLocalLang);
return;
}
}
}
}
autoDetectLang(text: string): void {
if (!text || text.length < 5) return;
if (!this.settingsService.getSettings().disableLangAutodetec) {
this.electronService.detectLang(text);
}
} }
getSelectedLanguage(): ILanguage { getSelectedLanguage(): ILanguage {

View File

@ -22,14 +22,14 @@ export class MastodonWrapperService {
private readonly mastodonService: MastodonService) { } private readonly mastodonService: MastodonService) { }
refreshAccountIfNeeded(accountInfo: AccountInfo): Promise<AccountInfo> { refreshAccountIfNeeded(accountInfo: AccountInfo): Promise<AccountInfo> {
if(this.refreshingToken[accountInfo.id]){ if (this.refreshingToken[accountInfo.id]) {
return this.refreshingToken[accountInfo.id]; return this.refreshingToken[accountInfo.id];
} }
let isExpired = false; let isExpired = false;
let storedAccountInfo = this.getStoreAccountInfo(accountInfo.id); let storedAccountInfo = this.getStoreAccountInfo(accountInfo.id);
if(!storedAccountInfo || !(storedAccountInfo.token)) if (!storedAccountInfo || !(storedAccountInfo.token))
return Promise.resolve(accountInfo); return Promise.resolve(accountInfo);
try { try {
@ -96,7 +96,7 @@ export class MastodonWrapperService {
return this.mastodonService.getInstance(instance); return this.mastodonService.getInstance(instance);
} }
translate(account: AccountInfo, statusId: string, lang: string): Promise<Translation>{ translate(account: AccountInfo, statusId: string, lang: string): Promise<Translation> {
return this.refreshAccountIfNeeded(account) return this.refreshAccountIfNeeded(account)
.then((refreshedAccount: AccountInfo) => { .then((refreshedAccount: AccountInfo) => {
return this.mastodonService.translate(refreshedAccount, statusId, lang); return this.mastodonService.translate(refreshedAccount, statusId, lang);
@ -146,7 +146,7 @@ export class MastodonWrapperService {
} }
search(account: AccountInfo, query: string, version: 'v1' | 'v2', resolve: boolean = false): Promise<Results> { search(account: AccountInfo, query: string, version: 'v1' | 'v2', resolve: boolean = false): Promise<Results> {
if(query.includes('twitter.com')){ if (query.includes('twitter.com')) {
query = this.processTwitterQuery(query); query = this.processTwitterQuery(query);
} }
@ -158,17 +158,17 @@ export class MastodonWrapperService {
private processTwitterQuery(query: string): string { private processTwitterQuery(query: string): string {
const settings = this.settingsService.getSettings(); const settings = this.settingsService.getSettings();
if(!settings.twitterBridgeInstance) return query; if (!settings.twitterBridgeInstance) return query;
let name; let name;
if(query.includes('twitter.com/')){ if (query.includes('twitter.com/')) {
console.log(query.replace('https://', '').replace('http://', '').split('/')); console.log(query.replace('https://', '').replace('http://', '').split('/'));
name = query.replace('https://', '').replace('http://', '').split('/')[1]; name = query.replace('https://', '').replace('http://', '').split('/')[1];
} }
if(query.includes('@twitter.com')){ if (query.includes('@twitter.com')) {
console.log(query.split('@')); console.log(query.split('@'));
name = query.split('@')[0]; name = query.split('@')[0];
if(name === '' || name == null){ if (name === '' || name == null) {
name = query.split('@')[1]; name = query.split('@')[1];
} }
} }
@ -208,7 +208,7 @@ export class MastodonWrapperService {
} }
searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false, resolve = true): Promise<Account[]> { searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false, resolve = true): Promise<Account[]> {
if(query.includes('twitter.com')){ if (query.includes('twitter.com')) {
query = this.processTwitterQuery(query); query = this.processTwitterQuery(query);
} }
@ -281,6 +281,20 @@ export class MastodonWrapperService {
}); });
} }
hideBoosts(currentlyUsedAccount: AccountInfo, account: Account): Promise<Relationship> {
return this.refreshAccountIfNeeded(currentlyUsedAccount)
.then((refreshedAccount: AccountInfo) => {
return this.mastodonService.hideBoosts(refreshedAccount, account);
});
}
unhideBoosts(currentlyUsedAccount: AccountInfo, account: Account): Promise<Relationship> {
return this.refreshAccountIfNeeded(currentlyUsedAccount)
.then((refreshedAccount: AccountInfo) => {
return this.mastodonService.unhideBoosts(refreshedAccount, account);
});
}
followHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> { followHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> {
return this.refreshAccountIfNeeded(currentlyUsedAccount) return this.refreshAccountIfNeeded(currentlyUsedAccount)
.then((refreshedAccount: AccountInfo) => { .then((refreshedAccount: AccountInfo) => {
@ -407,6 +421,20 @@ export class MastodonWrapperService {
}); });
} }
blockDomain(account: AccountInfo, domain: string): Promise<void> {
return this.refreshAccountIfNeeded(account)
.then((refreshedAccount: AccountInfo) => {
return this.mastodonService.blockDomain(refreshedAccount, domain);
});
}
unblockDomain(account: AccountInfo, domain: string): Promise<void> {
return this.refreshAccountIfNeeded(account)
.then((refreshedAccount: AccountInfo) => {
return this.mastodonService.unblockDomain(refreshedAccount, domain);
});
}
pinOnProfile(account: AccountInfo, statusId: string): Promise<Status> { pinOnProfile(account: AccountInfo, statusId: string): Promise<Status> {
return this.refreshAccountIfNeeded(account) return this.refreshAccountIfNeeded(account)
.then((refreshedAccount: AccountInfo) => { .then((refreshedAccount: AccountInfo) => {
@ -470,14 +498,14 @@ export class MastodonWrapperService {
}); });
} }
getFollowing(account: AccountInfo, accountId: number, maxId: string, sinceId: string, limit: number = 40): Promise<FollowingResult> { getFollowing(account: AccountInfo, accountId: number, maxId: string, sinceId: string, limit: number = 40): Promise<FollowingResult> {
return this.refreshAccountIfNeeded(account) return this.refreshAccountIfNeeded(account)
.then((refreshedAccount: AccountInfo) => { .then((refreshedAccount: AccountInfo) => {
return this.mastodonService.getFollowing(refreshedAccount, accountId, maxId, sinceId, limit); return this.mastodonService.getFollowing(refreshedAccount, accountId, maxId, sinceId, limit);
}); });
} }
getFollowers(account: AccountInfo, accountId: number, maxId: string, sinceId: string, limit: number = 40): Promise<FollowingResult> { getFollowers(account: AccountInfo, accountId: number, maxId: string, sinceId: string, limit: number = 40): Promise<FollowingResult> {
return this.refreshAccountIfNeeded(account) return this.refreshAccountIfNeeded(account)
.then((refreshedAccount: AccountInfo) => { .then((refreshedAccount: AccountInfo) => {
return this.mastodonService.getFollowers(refreshedAccount, accountId, maxId, sinceId, limit); return this.mastodonService.getFollowers(refreshedAccount, accountId, maxId, sinceId, limit);

View File

@ -357,6 +357,26 @@ export class MastodonService {
} }
hideBoosts(currentlyUsedAccount: AccountInfo, account: Account): Promise<Relationship> {
const route = `https://${currentlyUsedAccount.instance}${this.apiRoutes.follow}`.replace('{0}', account.id.toString());
const headers = new HttpHeaders({ 'Authorization': `Bearer ${currentlyUsedAccount.token.access_token}` });
let input = new FormData();
input.append('reblogs', 'false');
return this.httpClient.post<Relationship>(route, input, { headers: headers }).toPromise();
}
unhideBoosts(currentlyUsedAccount: AccountInfo, account: Account): Promise<Relationship> {
const route = `https://${currentlyUsedAccount.instance}${this.apiRoutes.follow}`.replace('{0}', account.id.toString());
const headers = new HttpHeaders({ 'Authorization': `Bearer ${currentlyUsedAccount.token.access_token}` });
let input = new FormData();
input.append('reblogs', 'true');
return this.httpClient.post<Relationship>(route, input, { headers: headers }).toPromise();
}
followHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> { followHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> {
const route = `https://${currentlyUsedAccount.instance}${this.apiRoutes.followHashtag}`.replace('{0}', hashtag); const route = `https://${currentlyUsedAccount.instance}${this.apiRoutes.followHashtag}`.replace('{0}', hashtag);
const headers = new HttpHeaders({ 'Authorization': `Bearer ${currentlyUsedAccount.token.access_token}` }); const headers = new HttpHeaders({ 'Authorization': `Bearer ${currentlyUsedAccount.token.access_token}` });
@ -524,6 +544,23 @@ export class MastodonService {
return this.httpClient.post<Relationship>(route, null, { headers: headers }).toPromise(); return this.httpClient.post<Relationship>(route, null, { headers: headers }).toPromise();
} }
blockDomain(account: AccountInfo, domain: string): Promise<void> {
let route = `https://${account.instance}${this.apiRoutes.blockDomain}`;
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
let input = new FormData();
input.append('domain', domain);
return this.httpClient.post<void>(route, input, { headers: headers }).toPromise();
}
unblockDomain(account: AccountInfo, domain: string): Promise<void> {
let route = `https://${account.instance}${this.apiRoutes.blockDomain}?domain=${domain}`;
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}`});
return this.httpClient.delete<void>(route, { headers: headers }).toPromise();
}
pinOnProfile(account: AccountInfo, statusId: string): Promise<Status> { pinOnProfile(account: AccountInfo, statusId: string): Promise<Status> {
let route = `https://${account.instance}${this.apiRoutes.pinStatus}`.replace('{0}', statusId.toString()); let route = `https://${account.instance}${this.apiRoutes.pinStatus}`.replace('{0}', statusId.toString());
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });

View File

@ -12,6 +12,7 @@ export class ApiRoutes {
unfollow = '/api/v1/accounts/{0}/unfollow'; unfollow = '/api/v1/accounts/{0}/unfollow';
block = '/api/v1/accounts/{0}/block'; block = '/api/v1/accounts/{0}/block';
unblock = '/api/v1/accounts/{0}/unblock'; unblock = '/api/v1/accounts/{0}/unblock';
blockDomain = '/api/v1/domain_blocks';
mute = '/api/v1/accounts/{0}/mute'; mute = '/api/v1/accounts/{0}/mute';
unmute = '/api/v1/accounts/{0}/unmute'; unmute = '/api/v1/accounts/{0}/unmute';
muteStatus = '/api/v1/statuses/{0}/mute'; muteStatus = '/api/v1/statuses/{0}/mute';

View File

@ -10,6 +10,7 @@ export class NavigationService {
activatedPanelSubject = new BehaviorSubject<OpenLeftPanelEvent>(new OpenLeftPanelEvent(LeftPanelType.Closed)); activatedPanelSubject = new BehaviorSubject<OpenLeftPanelEvent>(new OpenLeftPanelEvent(LeftPanelType.Closed));
activatedMediaSubject: Subject<OpenMediaEvent> = new Subject<OpenMediaEvent>(); activatedMediaSubject: Subject<OpenMediaEvent> = new Subject<OpenMediaEvent>();
columnSelectedSubject = new BehaviorSubject<number>(-1); columnSelectedSubject = new BehaviorSubject<number>(-1);
enableDraggableIconMenu = new BehaviorSubject<boolean>(false);
constructor() { } constructor() { }
@ -19,6 +20,10 @@ export class NavigationService {
this.activatedPanelSubject.next(newEvent); this.activatedPanelSubject.next(newEvent);
} }
changeIconMenuState(draggable: boolean) {
this.enableDraggableIconMenu.next(draggable);
}
openPanel(type: LeftPanelType){ openPanel(type: LeftPanelType){
const newEvent = new OpenLeftPanelEvent(type); const newEvent = new OpenLeftPanelEvent(type);
this.activatedPanelSubject.next(newEvent); this.activatedPanelSubject.next(newEvent);

View File

@ -6,6 +6,11 @@ export class AddAccount {
constructor(public account: AccountInfo) {} constructor(public account: AccountInfo) {}
} }
export class ReorderAccounts {
static readonly type = '[Accounts] Reorder';
constructor(public accounts: AccountInfo[]) {}
}
export class SelectAccount { export class SelectAccount {
static readonly type = '[Accounts] Select account'; static readonly type = '[Accounts] Select account';
constructor(public account: AccountInfo, public multiselection: boolean = false) {} constructor(public account: AccountInfo, public multiselection: boolean = false) {}
@ -46,6 +51,16 @@ export class AccountsState {
}); });
} }
@Action(ReorderAccounts)
ReorderAccounts(ctx: StateContext<AccountsStateModel>, action: ReorderAccounts){
// const state = ctx.getState();
const reorderedAccounts = action.accounts;
ctx.patchState({
accounts: [...reorderedAccounts]
});
}
@Action(UpdateAccount) @Action(UpdateAccount)
UpdateAccount(ctx: StateContext<AccountsStateModel>, action: UpdateAccount){ UpdateAccount(ctx: StateContext<AccountsStateModel>, action: UpdateAccount){
const state = ctx.getState(); const state = ctx.getState();

View File

@ -84,6 +84,10 @@ export class GlobalSettings {
configuredLanguages: ILanguage[] = []; configuredLanguages: ILanguage[] = [];
selectedLanguage: ILanguage; selectedLanguage: ILanguage;
disableLangAutodetec: boolean;
enableAltLabel: boolean;
enableFreezeAvatar: boolean;
} }
export interface ILanguage { export interface ILanguage {
@ -181,6 +185,9 @@ export class SettingsState {
newSettings.twitterBridgeInstance = oldSettings.twitterBridgeInstance; newSettings.twitterBridgeInstance = oldSettings.twitterBridgeInstance;
newSettings.configuredLanguages = oldSettings.configuredLanguages; newSettings.configuredLanguages = oldSettings.configuredLanguages;
newSettings.selectedLanguage = oldSettings.selectedLanguage; newSettings.selectedLanguage = oldSettings.selectedLanguage;
newSettings.disableLangAutodetec = oldSettings.disableLangAutodetec;
newSettings.enableAltLabel = oldSettings.enableAltLabel;
newSettings.enableFreezeAvatar = oldSettings.enableFreezeAvatar;
return newSettings; return newSettings;
} }